├── .gitignore ├── LICENSE ├── README.adoc ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── example │ │ └── cfp │ │ ├── CfpApplication.java │ │ ├── CfpProperties.java │ │ ├── domain │ │ ├── Submission.java │ │ ├── SubmissionRepository.java │ │ ├── SubmissionStatus.java │ │ ├── Track.java │ │ ├── User.java │ │ └── UserRepository.java │ │ ├── integration │ │ └── github │ │ │ ├── Commit.java │ │ │ ├── CommitDeserializer.java │ │ │ ├── GithubClient.java │ │ │ └── GithubUser.java │ │ ├── security │ │ └── SecurityConfig.java │ │ ├── submission │ │ ├── SubmissionRequest.java │ │ └── SubmissionService.java │ │ └── web │ │ ├── AdminController.java │ │ ├── CfpController.java │ │ ├── HomeController.java │ │ ├── Navigation.java │ │ ├── NewsController.java │ │ ├── Section.java │ │ ├── SubmissionForm.java │ │ └── WebConfig.java └── resources │ ├── application-local.properties │ ├── application-secrets.properties │ ├── application.properties │ ├── db │ └── migration │ │ ├── V1_1__Add_Notes.sql │ │ └── V1__Initial_Setup.sql │ ├── static │ ├── bootstrap │ │ ├── css │ │ │ ├── bootstrap-theme.min.css │ │ │ ├── bootstrap-theme.min.css.map │ │ │ └── bootstrap.min.css │ │ └── js │ │ │ └── bootstrap.min.js │ ├── css │ │ └── main.css │ └── img │ │ └── cfp-background.png │ └── templates │ ├── admin │ └── index.html │ ├── error │ └── 4xx.html │ ├── index.html │ ├── layouts │ └── main.html │ ├── login.html │ ├── news.html │ └── submit.html └── test ├── java └── com │ └── example │ └── cfp │ ├── domain │ ├── SubmissionTest.java │ └── UserTest.java │ ├── integration │ └── github │ │ └── GithubClientTest.java │ ├── submission │ └── SubmissionServiceTest.java │ └── web │ └── CfpControllerTest.java └── resources ├── github ├── jsmith.json ├── no-commit.json ├── spring-framework-commits-no-polish.json └── spring-framework-commits.json └── logback-test.xml /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | *.ipr 4 | *.iws 5 | target -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2015-Present Pivotal Software Inc. 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | 204 | 205 | 206 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = 10 ways to get super-productive with Spring Boot 2 | 3 | This sample project is going to be used for a talk demoing how Spring Boot helps 4 | you to be super-productive. -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.example 7 | cfp-example 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | CFP example 12 | 10 ways to get super-productive with Spring Boot 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 1.4.0.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | 1.8 24 | 25 | 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter-web 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-actuator 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-data-jpa 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-starter-security 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-starter-thymeleaf 46 | 47 | 48 | org.springframework.boot 49 | spring-boot-starter-cache 50 | 51 | 52 | 53 | org.springframework.security.oauth 54 | spring-security-oauth2 55 | 56 | 57 | 58 | org.thymeleaf.extras 59 | thymeleaf-extras-springsecurity4 60 | 61 | 62 | com.fasterxml.jackson.datatype 63 | jackson-datatype-jsr310 64 | 65 | 66 | com.jayway.jsonpath 67 | json-path 68 | 69 | 70 | 71 | javax.cache 72 | cache-api 73 | 74 | 75 | org.ehcache 76 | ehcache 77 | 78 | 79 | 80 | org.flywaydb 81 | flyway-core 82 | 83 | 84 | 85 | org.webjars.bower 86 | jquery 87 | 88 | 89 | org.webjars 90 | webjars-locator 91 | 92 | 93 | 94 | com.h2database 95 | h2 96 | runtime 97 | 98 | 99 | mysql 100 | mysql-connector-java 101 | runtime 102 | 103 | 104 | 105 | org.springframework.boot 106 | spring-boot-devtools 107 | true 108 | 109 | 110 | org.springframework.boot 111 | spring-boot-configuration-processor 112 | true 113 | 114 | 115 | 116 | org.springframework.boot 117 | spring-boot-starter-test 118 | test 119 | 120 | 121 | org.springframework.security 122 | spring-security-test 123 | test 124 | 125 | 126 | 127 | 128 | 129 | 130 | org.webjars.bower 131 | jquery 132 | 2.1.1 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | pl.project13.maven 141 | git-commit-id-plugin 142 | 143 | 144 | org.springframework.boot 145 | spring-boot-maven-plugin 146 | 147 | 148 | 149 | build-info 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /src/main/java/com/example/cfp/CfpApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.cfp; 2 | 3 | import javax.cache.configuration.MutableConfiguration; 4 | import javax.cache.expiry.CreatedExpiryPolicy; 5 | import javax.cache.expiry.Duration; 6 | 7 | import org.springframework.boot.SpringApplication; 8 | import org.springframework.boot.autoconfigure.SpringBootApplication; 9 | import org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer; 10 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 11 | import org.springframework.cache.annotation.EnableCaching; 12 | import org.springframework.context.annotation.Bean; 13 | 14 | @SpringBootApplication 15 | @EnableConfigurationProperties(CfpProperties.class) 16 | @EnableCaching 17 | public class CfpApplication { 18 | 19 | public static void main(String[] args) { 20 | SpringApplication.run(CfpApplication.class, args); 21 | } 22 | 23 | @Bean 24 | public JCacheManagerCustomizer cacheManagerCustomizer() { 25 | return cm -> { 26 | cm.createCache("github.commits", initConfiguration(Duration.TEN_MINUTES)); 27 | cm.createCache("github.polishCommit", initConfiguration(Duration.ONE_HOUR)); 28 | cm.createCache("github.user", initConfiguration(Duration.ONE_HOUR)); 29 | }; 30 | } 31 | 32 | private MutableConfiguration initConfiguration(Duration duration) { 33 | return new MutableConfiguration<>() 34 | .setStoreByValue(false) 35 | .setStatisticsEnabled(true) 36 | .setExpiryPolicyFactory(CreatedExpiryPolicy.factoryOf(duration)); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/example/cfp/CfpProperties.java: -------------------------------------------------------------------------------- 1 | package com.example.cfp; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | import org.springframework.boot.context.properties.ConfigurationProperties; 7 | 8 | @ConfigurationProperties("cfp") 9 | public class CfpProperties { 10 | 11 | private final Github github = new Github(); 12 | 13 | private final Security security = new Security(); 14 | 15 | public Github getGithub() { 16 | return this.github; 17 | } 18 | 19 | public Security getSecurity() { 20 | return this.security; 21 | } 22 | 23 | 24 | public static class Github { 25 | 26 | /** 27 | * Access token ("username:access_token") to query public github endpoints. 28 | */ 29 | private String token; 30 | 31 | public String getToken() { 32 | return this.token; 33 | } 34 | 35 | public void setToken(String token) { 36 | this.token = token; 37 | } 38 | } 39 | 40 | public static class Security { 41 | 42 | /** 43 | * Github users that have admin rights. 44 | */ 45 | private List admins = Arrays.asList("snicoll", "bclozel"); 46 | 47 | public List getAdmins() { 48 | return admins; 49 | } 50 | 51 | public void setAdmins(List admins) { 52 | this.admins = admins; 53 | } 54 | 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/example/cfp/domain/Submission.java: -------------------------------------------------------------------------------- 1 | package com.example.cfp.domain; 2 | 3 | import java.io.Serializable; 4 | 5 | import javax.persistence.Column; 6 | import javax.persistence.Entity; 7 | import javax.persistence.Enumerated; 8 | import javax.persistence.GeneratedValue; 9 | import javax.persistence.Id; 10 | import javax.persistence.JoinColumn; 11 | import javax.persistence.Lob; 12 | import javax.persistence.ManyToOne; 13 | 14 | @Entity 15 | @SuppressWarnings("serial") 16 | public class Submission implements Serializable { 17 | 18 | @GeneratedValue 19 | @Id 20 | private Long id; 21 | 22 | @ManyToOne 23 | @JoinColumn(name = "speaker_id") 24 | private User speaker; 25 | 26 | @Enumerated 27 | private Track track; 28 | 29 | @Column 30 | private String title; 31 | 32 | @Column 33 | private SubmissionStatus status; 34 | 35 | @Column 36 | @Lob 37 | private String summary; 38 | 39 | @Column 40 | @Lob 41 | private String notes; 42 | 43 | public Submission() { 44 | this.status = SubmissionStatus.DRAFT; 45 | } 46 | 47 | public Long getId() { 48 | return this.id; 49 | } 50 | 51 | public User getSpeaker() { 52 | return speaker; 53 | } 54 | 55 | public void setSpeaker(User speaker) { 56 | this.speaker = speaker; 57 | } 58 | 59 | public Track getTrack() { 60 | return this.track; 61 | } 62 | 63 | public void setTrack(Track track) { 64 | this.track = track; 65 | } 66 | 67 | public String getTitle() { 68 | return this.title; 69 | } 70 | 71 | public void setTitle(String title) { 72 | this.title = title; 73 | } 74 | 75 | public SubmissionStatus getStatus() { 76 | return this.status; 77 | } 78 | 79 | public void setStatus(SubmissionStatus status) { 80 | this.status = status; 81 | } 82 | 83 | public String getSummary() { 84 | return this.summary; 85 | } 86 | 87 | public void setSummary(String summary) { 88 | this.summary = summary; 89 | } 90 | 91 | public String getNotes() { 92 | return this.notes; 93 | } 94 | 95 | public void setNotes(String notes) { 96 | this.notes = notes; 97 | } 98 | 99 | @Override 100 | public String toString() { 101 | return "Submission{" + "id=" + this.id + ", title='" + this.title + '\'' + ", status='" + this.status + '\'' + '}'; 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/com/example/cfp/domain/SubmissionRepository.java: -------------------------------------------------------------------------------- 1 | package com.example.cfp.domain; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.data.repository.PagingAndSortingRepository; 6 | 7 | public interface SubmissionRepository extends PagingAndSortingRepository { 8 | 9 | List findBySpeaker(User user); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/example/cfp/domain/SubmissionStatus.java: -------------------------------------------------------------------------------- 1 | package com.example.cfp.domain; 2 | 3 | public enum SubmissionStatus { 4 | 5 | DRAFT("Draft"), 6 | 7 | SUBMITTED("Submitted"), 8 | 9 | ACCEPTED("Accepted"), 10 | 11 | REJECTED("Rejected"); 12 | 13 | private String displayName; 14 | 15 | SubmissionStatus(String displayName) { 16 | this.displayName = displayName; 17 | } 18 | 19 | public String getDisplayName() { 20 | return this.displayName; 21 | } 22 | 23 | public String getId() { 24 | return name(); 25 | } 26 | 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/example/cfp/domain/Track.java: -------------------------------------------------------------------------------- 1 | package com.example.cfp.domain; 2 | 3 | public enum Track { 4 | 5 | ALTERNATE_LANGUAGES("Alternate Languages"), 6 | 7 | CLOUD("Cloud"), 8 | 9 | SERVER_SIDE_JAVA("Server side Java"), 10 | 11 | MOBILE("Mobile"), 12 | 13 | WEB("Web"); 14 | 15 | private String displayName; 16 | 17 | Track(String displayName) { 18 | this.displayName = displayName; 19 | } 20 | 21 | public String getDisplayName() { 22 | return this.displayName; 23 | } 24 | 25 | public String getId() { 26 | return name(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/example/cfp/domain/User.java: -------------------------------------------------------------------------------- 1 | package com.example.cfp.domain; 2 | 3 | import java.io.Serializable; 4 | import java.util.Set; 5 | 6 | import javax.persistence.CascadeType; 7 | import javax.persistence.Column; 8 | import javax.persistence.Entity; 9 | import javax.persistence.GeneratedValue; 10 | import javax.persistence.Id; 11 | import javax.persistence.Lob; 12 | import javax.persistence.OneToMany; 13 | 14 | @Entity 15 | @SuppressWarnings("serial") 16 | public class User implements Serializable { 17 | 18 | @GeneratedValue 19 | @Id 20 | private Long id; 21 | 22 | private String email; 23 | 24 | private String name; 25 | 26 | private String twitter; 27 | 28 | private String github; 29 | 30 | private String avatarUrl; 31 | 32 | @Column 33 | @Lob 34 | private String bio; 35 | 36 | @OneToMany(mappedBy = "speaker", cascade = CascadeType.ALL) 37 | private Set submissions; 38 | 39 | public User() { 40 | } 41 | 42 | public User(String email, String name) { 43 | this.email = email; 44 | this.name = name; 45 | } 46 | 47 | public Long getId() { 48 | return this.id; 49 | } 50 | 51 | public String getEmail() { 52 | return this.email; 53 | } 54 | 55 | public void setEmail(String email) { 56 | this.email = email; 57 | } 58 | 59 | public String getName() { 60 | return this.name; 61 | } 62 | 63 | public void setName(String name) { 64 | this.name = name; 65 | } 66 | 67 | public String getTwitter() { 68 | return this.twitter; 69 | } 70 | 71 | public void setTwitter(String twitter) { 72 | this.twitter = twitter; 73 | } 74 | 75 | public String getGithub() { 76 | return this.github; 77 | } 78 | 79 | public void setGithub(String github) { 80 | this.github = github; 81 | } 82 | 83 | public String getAvatarUrl() { 84 | return avatarUrl; 85 | } 86 | 87 | public void setAvatarUrl(String avatarUrl) { 88 | this.avatarUrl = avatarUrl; 89 | } 90 | 91 | public String getBio() { 92 | return this.bio; 93 | } 94 | 95 | public void setBio(String bio) { 96 | this.bio = bio; 97 | } 98 | 99 | public Set getSubmissions() { 100 | return this.submissions; 101 | } 102 | 103 | public void setSubmissions(Set submissions) { 104 | this.submissions = submissions; 105 | } 106 | 107 | @Override 108 | public String toString() { 109 | return "Speaker{" + "id=" + this.id + ", name='" + this.name + '\'' + '}'; 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/com/example/cfp/domain/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.example.cfp.domain; 2 | 3 | import java.util.Collection; 4 | 5 | import org.springframework.data.repository.CrudRepository; 6 | 7 | public interface UserRepository extends CrudRepository { 8 | 9 | User findByTwitter(String twitter); 10 | 11 | User findByGithub(String github); 12 | 13 | Collection findByName(String lastName); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/example/cfp/integration/github/Commit.java: -------------------------------------------------------------------------------- 1 | package com.example.cfp.integration.github; 2 | 3 | import java.io.Serializable; 4 | import java.time.Instant; 5 | 6 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 7 | 8 | @JsonDeserialize(using = CommitDeserializer.class) 9 | @SuppressWarnings("serial") 10 | public class Commit implements Serializable { 11 | 12 | private final String sha; 13 | 14 | private final String message; 15 | 16 | private final Committer committer; 17 | 18 | private final Instant date; 19 | 20 | public Commit(String sha, String message, Committer committer, Instant date) { 21 | this.sha = sha; 22 | this.message = message; 23 | this.committer = committer; 24 | this.date = date; 25 | } 26 | 27 | public String getSha() { 28 | return this.sha; 29 | } 30 | 31 | public String getMessage() { 32 | return this.message; 33 | } 34 | 35 | public Instant getDate() { 36 | return this.date; 37 | } 38 | 39 | public Committer getCommitter() { 40 | return this.committer; 41 | } 42 | 43 | public static class Committer implements Serializable { 44 | 45 | private final String id; 46 | 47 | private final String name; 48 | 49 | private final String avatarUrl; 50 | 51 | public Committer(String id, String name, String avatarUrl) { 52 | this.id = id; 53 | this.name = name; 54 | this.avatarUrl = avatarUrl; 55 | } 56 | 57 | public String getId() { 58 | return this.id; 59 | } 60 | 61 | public String getName() { 62 | return this.name; 63 | } 64 | 65 | public String getAvatarUrl() { 66 | return this.avatarUrl; 67 | } 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/example/cfp/integration/github/CommitDeserializer.java: -------------------------------------------------------------------------------- 1 | package com.example.cfp.integration.github; 2 | 3 | import java.io.IOException; 4 | import java.time.Instant; 5 | 6 | import com.fasterxml.jackson.core.JsonParser; 7 | import com.fasterxml.jackson.core.ObjectCodec; 8 | import com.fasterxml.jackson.databind.DeserializationContext; 9 | import com.fasterxml.jackson.databind.JsonNode; 10 | 11 | import org.springframework.boot.jackson.JsonObjectDeserializer; 12 | 13 | public class CommitDeserializer extends JsonObjectDeserializer { 14 | 15 | @Override 16 | protected Commit deserializeObject(JsonParser jsonParser, 17 | DeserializationContext deserializationContext, 18 | ObjectCodec objectCodec, JsonNode jsonNode) throws IOException { 19 | 20 | JsonNode commitNode = jsonNode.get("commit"); 21 | JsonNode committerNode = jsonNode.get("committer"); 22 | String sha = nullSafeValue(jsonNode.get("sha"), String.class); 23 | String message = singleLine(nullSafeValue(commitNode.get("message"), String.class)); 24 | 25 | String committerId = nullSafeValue(committerNode.get("login"), String.class); 26 | String committerName = nullSafeValue(commitNode.get("committer").get("name"), String.class); 27 | String date = nullSafeValue(commitNode.get("committer").get("date"), String.class); 28 | String committerAvatar = nullSafeValue(committerNode.get("avatar_url"), String.class); 29 | Commit.Committer committer = 30 | new Commit.Committer(committerId, committerName, committerAvatar); 31 | return new Commit(sha, message, committer, Instant.parse(date)); 32 | } 33 | 34 | private String singleLine(String s) { 35 | if (s == null) { 36 | return null; 37 | } 38 | int i = s.indexOf('\n'); 39 | if (i != -1) { 40 | return s.substring(0, i); 41 | } 42 | return s; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/example/cfp/integration/github/GithubClient.java: -------------------------------------------------------------------------------- 1 | package com.example.cfp.integration.github; 2 | 3 | import java.io.IOException; 4 | import java.net.URI; 5 | import java.net.URISyntaxException; 6 | import java.nio.charset.StandardCharsets; 7 | import java.util.Arrays; 8 | import java.util.Collections; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.regex.Matcher; 13 | import java.util.regex.Pattern; 14 | import java.util.stream.Stream; 15 | 16 | import com.example.cfp.CfpProperties; 17 | 18 | import org.springframework.boot.actuate.metrics.CounterService; 19 | import org.springframework.boot.web.client.RestTemplateBuilder; 20 | import org.springframework.cache.annotation.Cacheable; 21 | import org.springframework.http.HttpHeaders; 22 | import org.springframework.http.HttpRequest; 23 | import org.springframework.http.MediaType; 24 | import org.springframework.http.RequestEntity; 25 | import org.springframework.http.ResponseEntity; 26 | import org.springframework.http.client.ClientHttpRequestExecution; 27 | import org.springframework.http.client.ClientHttpRequestInterceptor; 28 | import org.springframework.http.client.ClientHttpResponse; 29 | import org.springframework.stereotype.Service; 30 | import org.springframework.util.Base64Utils; 31 | import org.springframework.util.StringUtils; 32 | import org.springframework.web.client.RestClientException; 33 | import org.springframework.web.client.RestTemplate; 34 | 35 | @Service 36 | public class GithubClient { 37 | 38 | private static final Pattern LINK_PATTERN = Pattern.compile("<(.+)>; rel=\"(.+)\""); 39 | 40 | private final CounterService counterService; 41 | 42 | private final RestTemplate restTemplate; 43 | 44 | public GithubClient(CounterService counterService, RestTemplateBuilder restTemplateBuilder, 45 | CfpProperties properties) { 46 | this.counterService = counterService; 47 | this.restTemplate = restTemplateBuilder.additionalCustomizers(rt -> 48 | rt.getInterceptors().add(new GithubAppTokenInterceptor(properties.getGithub().getToken()))).build(); 49 | } 50 | 51 | @Cacheable("github.commits") 52 | public List getRecentCommits(String organization, String project) { 53 | ResponseEntity response = doGetRecentCommit(organization, project); 54 | return Arrays.asList(response.getBody()); 55 | } 56 | 57 | @Cacheable("github.polishCommit") 58 | public Commit getRecentPolishCommit(String organization, String project) { 59 | ResponseEntity page = doGetRecentCommit(organization, project); 60 | for (int i = 0; i < 4; i++) { 61 | Commit commit = Stream.of(page.getBody()) 62 | .filter(c -> c.getMessage().toLowerCase().contains("polish")) 63 | .findFirst() 64 | .orElse(null); 65 | 66 | if (commit != null) { 67 | return commit; 68 | } 69 | String nextPage = parseLinkHeader(page).get("next"); 70 | if (nextPage != null) { 71 | page = invoke(createRequestEntity(nextPage), Commit[].class); 72 | } 73 | else { 74 | break; 75 | } 76 | } 77 | return null; 78 | } 79 | 80 | private ResponseEntity doGetRecentCommit(String organization, String project) { 81 | String url = String.format( 82 | "https://api.github.com/repos/%s/%s/commits", organization, project); 83 | return invoke(createRequestEntity(url), Commit[].class); 84 | } 85 | 86 | @Cacheable("github.user") 87 | public GithubUser getUser(String githubId) { 88 | return invoke(createRequestEntity( 89 | String.format("https://api.github.com/users/%s", githubId)), GithubUser.class).getBody(); 90 | } 91 | 92 | private ResponseEntity invoke(RequestEntity request, Class type) { 93 | this.counterService.increment("cfp.github.requests"); 94 | try { 95 | return this.restTemplate.exchange(request, type); 96 | } 97 | catch (RestClientException ex) { 98 | this.counterService.increment("cfp.github.failures"); 99 | throw ex; 100 | } 101 | } 102 | 103 | private RequestEntity createRequestEntity(String url) { 104 | try { 105 | return RequestEntity.get(new URI(url)) 106 | .accept(MediaType.APPLICATION_JSON).build(); 107 | } 108 | catch (URISyntaxException ex) { 109 | throw new IllegalStateException("Invalid URL " + url, ex); 110 | } 111 | } 112 | 113 | private Map parseLinkHeader(ResponseEntity response) { 114 | List links = response.getHeaders().get(HttpHeaders.LINK); 115 | if (links == null) { 116 | return Collections.emptyMap(); 117 | } 118 | Map result = new HashMap<>(); 119 | for (String link : links) { 120 | Matcher matcher = LINK_PATTERN.matcher(link.trim()); 121 | if (matcher.matches()) { 122 | result.put(matcher.group(2), matcher.group(1)); 123 | } 124 | } 125 | return result; 126 | } 127 | 128 | 129 | private static class GithubAppTokenInterceptor implements ClientHttpRequestInterceptor { 130 | 131 | private final String token; 132 | 133 | GithubAppTokenInterceptor(String token) { 134 | this.token = token; 135 | } 136 | 137 | @Override 138 | public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, 139 | ClientHttpRequestExecution clientHttpRequestExecution) throws IOException { 140 | if (StringUtils.hasText(this.token)) { 141 | byte[] basicAuthValue = this.token.getBytes(StandardCharsets.UTF_8); 142 | httpRequest.getHeaders().set(HttpHeaders.AUTHORIZATION, 143 | "Basic " + Base64Utils.encodeToString(basicAuthValue)); 144 | } 145 | return clientHttpRequestExecution.execute(httpRequest, bytes); 146 | } 147 | 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /src/main/java/com/example/cfp/integration/github/GithubUser.java: -------------------------------------------------------------------------------- 1 | package com.example.cfp.integration.github; 2 | 3 | import java.io.Serializable; 4 | 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | 7 | @SuppressWarnings("serial") 8 | public class GithubUser implements Serializable { 9 | 10 | private String email; 11 | 12 | private String name; 13 | 14 | private String company; 15 | 16 | private String blog; 17 | 18 | private String avatar; 19 | 20 | public String getEmail() { 21 | return this.email; 22 | } 23 | 24 | public void setEmail(String email) { 25 | this.email = email; 26 | } 27 | 28 | public String getName() { 29 | return this.name; 30 | } 31 | 32 | public void setName(String name) { 33 | this.name = name; 34 | } 35 | 36 | public String getCompany() { 37 | return this.company; 38 | } 39 | 40 | public void setCompany(String company) { 41 | this.company = company; 42 | } 43 | 44 | public String getBlog() { 45 | return this.blog; 46 | } 47 | 48 | public void setBlog(String blog) { 49 | this.blog = blog; 50 | } 51 | 52 | public String getAvatar() { 53 | return avatar; 54 | } 55 | 56 | @JsonProperty("avatar_url") 57 | public void setAvatarUrl(String avatar) { 58 | this.avatar = avatar; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/example/cfp/security/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.example.cfp.security; 2 | 3 | import com.example.cfp.CfpProperties; 4 | import com.example.cfp.domain.User; 5 | import com.example.cfp.domain.UserRepository; 6 | import com.example.cfp.integration.github.GithubClient; 7 | import com.example.cfp.integration.github.GithubUser; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso; 12 | import org.springframework.boot.autoconfigure.security.oauth2.resource.AuthoritiesExtractor; 13 | import org.springframework.boot.autoconfigure.security.oauth2.resource.PrincipalExtractor; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.context.annotation.Configuration; 16 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 17 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 18 | import org.springframework.security.core.authority.AuthorityUtils; 19 | 20 | @Configuration 21 | @EnableOAuth2Sso 22 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 23 | 24 | private static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class); 25 | 26 | private final CfpProperties cfpProperties; 27 | 28 | public SecurityConfig(CfpProperties cfpProperties) { 29 | this.cfpProperties = cfpProperties; 30 | } 31 | 32 | @Override 33 | protected void configure(HttpSecurity http) throws Exception { 34 | http.authorizeRequests() 35 | .antMatchers("/admin/**").hasRole("ADMIN") 36 | .antMatchers("/", "/news", "/login**", "/css/**", "/img/**", "/webjars/**", "/bootstrap/**").permitAll() 37 | .anyRequest().authenticated() 38 | .and() 39 | .csrf() 40 | .ignoringAntMatchers("/admin/h2-console/*") 41 | .and() 42 | .logout() 43 | .logoutSuccessUrl("/") 44 | .permitAll() 45 | .and() 46 | .headers() 47 | .frameOptions().sameOrigin(); 48 | } 49 | 50 | @Bean 51 | public AuthoritiesExtractor authoritiesExtractor() { 52 | return map -> { 53 | String username = (String) map.get("login"); 54 | if (this.cfpProperties.getSecurity().getAdmins().contains(username)) { 55 | return AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER,ROLE_ADMIN"); 56 | } 57 | else { 58 | return AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"); 59 | } 60 | }; 61 | } 62 | 63 | @Bean 64 | public PrincipalExtractor principalExtractor(GithubClient githubClient, UserRepository userRepository) { 65 | return map -> { 66 | String githubLogin = (String) map.get("login"); 67 | User speaker = userRepository.findByGithub(githubLogin); 68 | if (speaker == null) { 69 | logger.info("Initialize user with githubId {}", githubLogin); 70 | GithubUser user = githubClient.getUser(githubLogin); 71 | speaker = new User(); 72 | speaker.setEmail(user.getEmail()); 73 | speaker.setName(user.getName()); 74 | speaker.setGithub(githubLogin); 75 | speaker.setAvatarUrl(user.getAvatar()); 76 | userRepository.save(speaker); 77 | } 78 | return speaker; 79 | }; 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/com/example/cfp/submission/SubmissionRequest.java: -------------------------------------------------------------------------------- 1 | package com.example.cfp.submission; 2 | 3 | import com.example.cfp.domain.Track; 4 | import com.example.cfp.domain.User; 5 | 6 | public class SubmissionRequest { 7 | 8 | private User speaker; 9 | 10 | private String title; 11 | 12 | private String summary; 13 | 14 | private Track track; 15 | 16 | private String notes; 17 | 18 | public User getSpeaker() { 19 | return this.speaker; 20 | } 21 | 22 | public void setSpeaker(User speaker) { 23 | this.speaker = speaker; 24 | } 25 | 26 | public String getTitle() { 27 | return this.title; 28 | } 29 | 30 | public void setTitle(String title) { 31 | this.title = title; 32 | } 33 | 34 | public String getSummary() { 35 | return this.summary; 36 | } 37 | 38 | public void setSummary(String summary) { 39 | this.summary = summary; 40 | } 41 | 42 | public Track getTrack() { 43 | return this.track; 44 | } 45 | 46 | public void setTrack(Track track) { 47 | this.track = track; 48 | } 49 | 50 | public String getNotes() { 51 | return this.notes; 52 | } 53 | 54 | public void setNotes(String notes) { 55 | this.notes = notes; 56 | } 57 | 58 | public void setTalk(String title, String summary, Track track, String notes) { 59 | setTitle(title); 60 | setSummary(summary); 61 | setTrack(track); 62 | setNotes(notes); 63 | } 64 | 65 | @Override 66 | public boolean equals(Object o) { 67 | if (this == o) return true; 68 | if (o == null || getClass() != o.getClass()) return false; 69 | 70 | SubmissionRequest that = (SubmissionRequest) o; 71 | 72 | if (this.speaker != null ? !this.speaker.equals(that.speaker) : that.speaker != null) return false; 73 | if (this.title != null ? !this.title.equals(that.title) : that.title != null) return false; 74 | if (this.summary != null ? !this.summary.equals(that.summary) : that.summary != null) return false; 75 | return this.track == that.track; 76 | } 77 | 78 | @Override 79 | public int hashCode() { 80 | int result = this.speaker != null ? this.speaker.hashCode() : 0; 81 | result = 31 * result + (this.title != null ? this.title.hashCode() : 0); 82 | result = 31 * result + (this.summary != null ? this.summary.hashCode() : 0); 83 | result = 31 * result + (this.track != null ? this.track.hashCode() : 0); 84 | return result; 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/com/example/cfp/submission/SubmissionService.java: -------------------------------------------------------------------------------- 1 | package com.example.cfp.submission; 2 | 3 | import com.example.cfp.domain.Submission; 4 | import com.example.cfp.domain.SubmissionRepository; 5 | 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.transaction.annotation.Transactional; 8 | 9 | @Service 10 | public class SubmissionService { 11 | 12 | private final SubmissionRepository submissionRepository; 13 | 14 | public SubmissionService(SubmissionRepository submissionRepository) { 15 | this.submissionRepository = submissionRepository; 16 | } 17 | 18 | @Transactional 19 | public Submission create(SubmissionRequest request) { 20 | Submission submission = new Submission(); 21 | submission.setSpeaker(request.getSpeaker()); 22 | submission.setTitle(request.getTitle()); 23 | submission.setSummary(request.getSummary()); 24 | submission.setTrack(request.getTrack()); 25 | submission.setNotes(request.getNotes()); 26 | return this.submissionRepository.save(submission); 27 | } 28 | 29 | protected SubmissionRepository getSubmissionRepository() { 30 | return this.submissionRepository; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/example/cfp/web/AdminController.java: -------------------------------------------------------------------------------- 1 | package com.example.cfp.web; 2 | 3 | import com.example.cfp.domain.SubmissionRepository; 4 | import com.example.cfp.domain.SubmissionStatus; 5 | 6 | import org.springframework.stereotype.Controller; 7 | import org.springframework.ui.Model; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | 10 | @Controller 11 | @Navigation(Section.ADMIN) 12 | @RequestMapping("/admin") 13 | public class AdminController { 14 | 15 | private final SubmissionRepository submissionRepository; 16 | 17 | public AdminController(SubmissionRepository submissionRepository) { 18 | this.submissionRepository = submissionRepository; 19 | } 20 | 21 | @RequestMapping("") 22 | public String admin(Model model) { 23 | 24 | model.addAttribute("submissions", this.submissionRepository.findAll()); 25 | model.addAttribute("viewHelper", new ViewRenderingHelper()); 26 | return "admin/index"; 27 | } 28 | 29 | static class ViewRenderingHelper { 30 | 31 | public String submissionTableClass(SubmissionStatus status) { 32 | if (SubmissionStatus.ACCEPTED.equals(status)) { 33 | return "success"; 34 | } 35 | else if (SubmissionStatus.REJECTED.equals(status)) { 36 | return "error"; 37 | } 38 | return ""; 39 | } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/example/cfp/web/CfpController.java: -------------------------------------------------------------------------------- 1 | package com.example.cfp.web; 2 | 3 | import javax.validation.Valid; 4 | 5 | import com.example.cfp.domain.Track; 6 | import com.example.cfp.domain.User; 7 | import com.example.cfp.submission.SubmissionRequest; 8 | import com.example.cfp.submission.SubmissionService; 9 | 10 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 11 | import org.springframework.stereotype.Controller; 12 | import org.springframework.ui.Model; 13 | import org.springframework.validation.BindingResult; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.RequestMethod; 16 | import org.springframework.web.servlet.mvc.support.RedirectAttributes; 17 | 18 | @Controller 19 | @Navigation(Section.SUBMIT) 20 | public class CfpController { 21 | 22 | private final SubmissionService submissionService; 23 | 24 | public CfpController(SubmissionService submissionService) { 25 | this.submissionService = submissionService; 26 | } 27 | 28 | @RequestMapping(path = "/submit", method = RequestMethod.GET) 29 | public String submitForm(Model model) { 30 | model.addAttribute("tracks", Track.values()); 31 | model.addAttribute("submissionForm", new SubmissionForm()); 32 | return "submit"; 33 | } 34 | 35 | @RequestMapping(path = "/submit", method = RequestMethod.POST) 36 | public String submit(@Valid SubmissionForm submissionForm, BindingResult bindingResult, 37 | @AuthenticationPrincipal User user, RedirectAttributes attributes, Model model) { 38 | if (bindingResult.hasErrors()) { 39 | model.addAttribute("tracks", Track.values()); 40 | model.addAttribute("submissionForm", submissionForm); 41 | return "submit"; 42 | } 43 | else { 44 | this.submissionService.create(createRequest(submissionForm, user)); 45 | attributes.addFlashAttribute("successMessage", "Thanks! Your talk proposal has been submitted."); 46 | return "redirect:/submit"; 47 | } 48 | } 49 | 50 | private SubmissionRequest createRequest(SubmissionForm form, User user) { 51 | SubmissionRequest request = new SubmissionRequest(); 52 | request.setSpeaker(user); 53 | request.setTitle(form.getTitle()); 54 | request.setSummary(form.getSummary()); 55 | request.setTrack(form.getTrack()); 56 | request.setNotes(form.getNotes()); 57 | return request; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/example/cfp/web/HomeController.java: -------------------------------------------------------------------------------- 1 | package com.example.cfp.web; 2 | 3 | import com.example.cfp.integration.github.Commit; 4 | import com.example.cfp.integration.github.GithubClient; 5 | 6 | import org.springframework.stereotype.Controller; 7 | import org.springframework.ui.Model; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | 10 | @Controller 11 | @Navigation(Section.HOME) 12 | public class HomeController { 13 | 14 | private final GithubClient githubClient; 15 | 16 | public HomeController(GithubClient githubClient) { 17 | this.githubClient = githubClient; 18 | } 19 | 20 | @RequestMapping("/") 21 | public String home(Model model) { 22 | model.addAttribute("latestFrameworkCommit", 23 | getLatestCommit("spring-projects", "spring-framework")); 24 | model.addAttribute("latestBootCommit", 25 | getLatestCommit("spring-projects", "spring-boot")); 26 | return "index"; 27 | } 28 | 29 | private Commit getLatestCommit(String organization, String project) { 30 | return this.githubClient 31 | .getRecentCommits(organization, project) 32 | .stream().findFirst().get(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/example/cfp/web/Navigation.java: -------------------------------------------------------------------------------- 1 | package com.example.cfp.web; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.TYPE) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface Navigation { 11 | 12 | Section value(); 13 | 14 | } -------------------------------------------------------------------------------- /src/main/java/com/example/cfp/web/NewsController.java: -------------------------------------------------------------------------------- 1 | package com.example.cfp.web; 2 | 3 | import java.time.Duration; 4 | import java.time.Instant; 5 | import java.util.List; 6 | import java.util.Objects; 7 | import java.util.stream.Collectors; 8 | import java.util.stream.Stream; 9 | 10 | import com.example.cfp.integration.github.Commit; 11 | import com.example.cfp.integration.github.GithubClient; 12 | 13 | import org.springframework.stereotype.Controller; 14 | import org.springframework.ui.Model; 15 | import org.springframework.web.bind.annotation.RequestMapping; 16 | 17 | @Controller 18 | @Navigation(Section.NEWS) 19 | public class NewsController { 20 | 21 | private final GithubClient githubClient; 22 | 23 | public NewsController(GithubClient githubClient) { 24 | this.githubClient = githubClient; 25 | } 26 | 27 | @RequestMapping("/news") 28 | public String home(Model model) { 29 | model.addAttribute("latestFrameworkCommits", 30 | getRecentCommits("spring-projects", "spring-framework")); 31 | model.addAttribute("latestBootCommits", 32 | getRecentCommits("spring-projects", "spring-boot")); 33 | model.addAttribute("daysSinceLastPolish", daysSinceLastPolishCommit()); 34 | return "news"; 35 | } 36 | 37 | private List getRecentCommits(String organization, String project) { 38 | return this.githubClient 39 | .getRecentCommits(organization, project) 40 | .stream().limit(5).collect(Collectors.toList()); 41 | } 42 | 43 | private long daysSinceLastPolishCommit() { 44 | Commit fwkCommit = this.githubClient.getRecentPolishCommit("spring-projects", "spring-framework"); 45 | Commit bootCommit = this.githubClient.getRecentPolishCommit("spring-projects", "spring-boot"); 46 | Instant instant = Stream.of(fwkCommit, bootCommit) 47 | .filter(Objects::nonNull) 48 | .map(Commit::getDate) 49 | .max(Instant::compareTo).get(); 50 | return Duration.between(instant, Instant.now()).toDays(); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/example/cfp/web/Section.java: -------------------------------------------------------------------------------- 1 | package com.example.cfp.web; 2 | 3 | public enum Section { 4 | HOME, SUBMIT, NEWS, ADMIN 5 | } -------------------------------------------------------------------------------- /src/main/java/com/example/cfp/web/SubmissionForm.java: -------------------------------------------------------------------------------- 1 | package com.example.cfp.web; 2 | 3 | import javax.validation.constraints.NotNull; 4 | 5 | import com.example.cfp.domain.Track; 6 | import org.hibernate.validator.constraints.NotEmpty; 7 | 8 | class SubmissionForm { 9 | 10 | @NotEmpty(message="Title should not be empty") 11 | private String title; 12 | 13 | @NotEmpty(message="Summary should not be empty") 14 | private String summary; 15 | 16 | @NotNull 17 | private Track track; 18 | 19 | private String notes; 20 | 21 | public String getTitle() { 22 | return this.title; 23 | } 24 | 25 | public void setTitle(String title) { 26 | this.title = title; 27 | } 28 | 29 | public String getSummary() { 30 | return this.summary; 31 | } 32 | 33 | public void setSummary(String summary) { 34 | this.summary = summary; 35 | } 36 | 37 | public Track getTrack() { 38 | return this.track; 39 | } 40 | 41 | public void setTrack(Track track) { 42 | this.track = track; 43 | } 44 | 45 | public String getNotes() { 46 | return this.notes; 47 | } 48 | 49 | public void setNotes(String notes) { 50 | this.notes = notes; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/example/cfp/web/WebConfig.java: -------------------------------------------------------------------------------- 1 | package com.example.cfp.web; 2 | 3 | import javax.servlet.http.HttpServletRequest; 4 | import javax.servlet.http.HttpServletResponse; 5 | 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.web.method.HandlerMethod; 8 | import org.springframework.web.servlet.ModelAndView; 9 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 10 | import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; 11 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 12 | import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; 13 | 14 | @Configuration 15 | class WebConfig extends WebMvcConfigurerAdapter { 16 | 17 | @Override 18 | public void addViewControllers(ViewControllerRegistry registry) { 19 | registry.addViewController("/login").setViewName("login"); 20 | } 21 | 22 | @Override 23 | public void addInterceptors(InterceptorRegistry registry) { 24 | registry.addInterceptor(new HandlerInterceptorAdapter() { 25 | @Override 26 | public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, 27 | ModelAndView modelAndView) throws Exception { 28 | 29 | if (handler instanceof HandlerMethod) { 30 | HandlerMethod handlerMethod = (HandlerMethod) handler; 31 | Navigation navSection = handlerMethod.getBean().getClass().getAnnotation(Navigation.class); 32 | if (navSection != null && modelAndView != null) { 33 | modelAndView.addObject("navSection", navSection.value().toString().toLowerCase()); 34 | } 35 | } 36 | } 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/resources/application-local.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:mysql://localhost/cfp?useSSL=false 2 | spring.datasource.username=cfp 3 | spring.datasource.password=secret 4 | -------------------------------------------------------------------------------- /src/main/resources/application-secrets.properties: -------------------------------------------------------------------------------- 1 | # Works on localhost:8080 only 2 | security.oauth2.client.client-id=bd1c0a783ccdd1c9b9e4 3 | security.oauth2.client.client-secret=1a9030fbca47a5b2c28e92f19050bb77824b5ad1 -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.profiles.include=secrets 2 | 3 | spring.jpa.hibernate.ddl-auto=none 4 | 5 | spring.jackson.serialization.write_dates_as_timestamps=false 6 | 7 | management.context-path=/admin 8 | spring.h2.console.path=/admin/h2-console 9 | 10 | security.oauth2.client.access-token-uri=https://github.com/login/oauth/access_token 11 | security.oauth2.client.user-authorization-uri=https://github.com/login/oauth/authorize 12 | security.oauth2.client.client-authentication-scheme=form 13 | security.oauth2.resource.user-info-uri=https://api.github.com/user -------------------------------------------------------------------------------- /src/main/resources/db/migration/V1_1__Add_Notes.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE SUBMISSION ADD notes TEXT; -------------------------------------------------------------------------------- /src/main/resources/db/migration/V1__Initial_Setup.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE USER ( 2 | id BIGINT auto_increment, 3 | email VARCHAR(100), 4 | name VARCHAR(100) NOT NULL, 5 | github VARCHAR(30), 6 | twitter VARCHAR(30), 7 | avatar_url VARCHAR(200), 8 | bio TEXT, 9 | PRIMARY KEY (id) 10 | ); 11 | 12 | CREATE TABLE SUBMISSION ( 13 | id BIGINT auto_increment, 14 | speaker_id BIGINT, 15 | title VARCHAR(100), 16 | status INTEGER, 17 | summary TEXT, 18 | track INT, 19 | PRIMARY KEY (id) 20 | ); -------------------------------------------------------------------------------- /src/main/resources/static/bootstrap/css/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.6 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */.btn-danger,.btn-default,.btn-info,.btn-primary,.btn-success,.btn-warning{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-danger.disabled,.btn-danger[disabled],.btn-default.disabled,.btn-default[disabled],.btn-info.disabled,.btn-info[disabled],.btn-primary.disabled,.btn-primary[disabled],.btn-success.disabled,.btn-success[disabled],.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-danger,fieldset[disabled] .btn-default,fieldset[disabled] .btn-info,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-success,fieldset[disabled] .btn-warning{-webkit-box-shadow:none;box-shadow:none}.btn-danger .badge,.btn-default .badge,.btn-info .badge,.btn-primary .badge,.btn-success .badge,.btn-warning .badge{text-shadow:none}.btn.active,.btn:active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:focus,.btn-default:hover{background-color:#e0e0e0;background-position:0 -15px}.btn-default.active,.btn-default:active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:focus,.btn-primary:hover{background-color:#265a88;background-position:0 -15px}.btn-primary.active,.btn-primary:active{background-color:#265a88;border-color:#245580}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:focus,.btn-success:hover{background-color:#419641;background-position:0 -15px}.btn-success.active,.btn-success:active{background-color:#419641;border-color:#3e8f3e}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:focus,.btn-info:hover{background-color:#2aabd2;background-position:0 -15px}.btn-info.active,.btn-info:active{background-color:#2aabd2;border-color:#28a4c9}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:focus,.btn-warning:hover{background-color:#eb9316;background-position:0 -15px}.btn-warning.active,.btn-warning:active{background-color:#eb9316;border-color:#e38d13}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:focus,.btn-danger:hover{background-color:#c12e2a;background-position:0 -15px}.btn-danger.active,.btn-danger:active{background-color:#c12e2a;border-color:#b92c28}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#c12e2a;background-image:none}.img-thumbnail,.thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{background-color:#2e6da4;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:focus .badge,.list-group-item.active:hover .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} 6 | /*# sourceMappingURL=bootstrap-theme.min.css.map */ -------------------------------------------------------------------------------- /src/main/resources/static/bootstrap/css/bootstrap-theme.min.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["less/theme.less","less/mixins/vendor-prefixes.less","less/mixins/gradients.less","less/mixins/reset-filter.less"],"names":[],"mappings":";;;;AAmBA,YAAA,aAAA,UAAA,aAAA,aAAA,aAME,YAAA,EAAA,KAAA,EAAA,eC2CA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBDvCR,mBAAA,mBAAA,oBAAA,oBAAA,iBAAA,iBAAA,oBAAA,oBAAA,oBAAA,oBAAA,oBAAA,oBCsCA,mBAAA,MAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,iBDlCR,qBAAA,sBAAA,sBAAA,uBAAA,mBAAA,oBAAA,sBAAA,uBAAA,sBAAA,uBAAA,sBAAA,uBAAA,+BAAA,gCAAA,6BAAA,gCAAA,gCAAA,gCCiCA,mBAAA,KACQ,WAAA,KDlDV,mBAAA,oBAAA,iBAAA,oBAAA,oBAAA,oBAuBI,YAAA,KAyCF,YAAA,YAEE,iBAAA,KAKJ,aErEI,YAAA,EAAA,IAAA,EAAA,KACA,iBAAA,iDACA,iBAAA,4CAAA,iBAAA,qEAEA,iBAAA,+CCnBF,OAAA,+GH4CA,OAAA,0DACA,kBAAA,SAuC2C,aAAA,QAA2B,aAAA,KArCtE,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAgBN,aEtEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAiBN,aEvEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAkBN,UExEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,gBAAA,gBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,iBAAA,iBAEE,iBAAA,QACA,aAAA,QAMA,mBAAA,0BAAA,yBAAA,0BAAA,yBAAA,yBAAA,oBAAA,2BAAA,0BAAA,2BAAA,0BAAA,0BAAA,6BAAA,oCAAA,mCAAA,oCAAA,mCAAA,mCAME,iBAAA,QACA,iBAAA,KAmBN,aEzEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAoBN,YE1EI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,kBAAA,kBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,mBAAA,mBAEE,iBAAA,QACA,aAAA,QAMA,qBAAA,4BAAA,2BAAA,4BAAA,2BAAA,2BAAA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,+BAAA,sCAAA,qCAAA,sCAAA,qCAAA,qCAME,iBAAA,QACA,iBAAA,KA2BN,eAAA,WClCE,mBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,EAAA,IAAA,IAAA,iBD2CV,0BAAA,0BE3FI,iBAAA,QACA,iBAAA,oDACA,iBAAA,+CAAA,iBAAA,wEACA,iBAAA,kDACA,OAAA,+GF0FF,kBAAA,SAEF,yBAAA,+BAAA,+BEhGI,iBAAA,QACA,iBAAA,oDACA,iBAAA,+CAAA,iBAAA,wEACA,iBAAA,kDACA,OAAA,+GFgGF,kBAAA,SASF,gBE7GI,iBAAA,iDACA,iBAAA,4CACA,iBAAA,qEAAA,iBAAA,+CACA,OAAA,+GACA,OAAA,0DCnBF,kBAAA,SH+HA,cAAA,ICjEA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBD6DV,sCAAA,oCE7GI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SD2CF,mBAAA,MAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,iBD0EV,cAAA,iBAEE,YAAA,EAAA,IAAA,EAAA,sBAIF,gBEhII,iBAAA,iDACA,iBAAA,4CACA,iBAAA,qEAAA,iBAAA,+CACA,OAAA,+GACA,OAAA,0DCnBF,kBAAA,SHkJA,cAAA,IAHF,sCAAA,oCEhII,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SD2CF,mBAAA,MAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,gBDgFV,8BAAA,iCAYI,YAAA,EAAA,KAAA,EAAA,gBAKJ,qBAAA,kBAAA,mBAGE,cAAA,EAqBF,yBAfI,mDAAA,yDAAA,yDAGE,MAAA,KE7JF,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,UFqKJ,OACE,YAAA,EAAA,IAAA,EAAA,qBC3HA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,gBDsIV,eEtLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAKF,YEvLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAMF,eExLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAOF,cEzLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAeF,UEjMI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFuMJ,cE3MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFwMJ,sBE5MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFyMJ,mBE7MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF0MJ,sBE9MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF2MJ,qBE/MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF+MJ,sBElLI,iBAAA,yKACA,iBAAA,oKACA,iBAAA,iKFyLJ,YACE,cAAA,IC9KA,mBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,EAAA,IAAA,IAAA,iBDgLV,wBAAA,8BAAA,8BAGE,YAAA,EAAA,KAAA,EAAA,QEnOE,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFiOF,aAAA,QALF,+BAAA,qCAAA,qCAQI,YAAA,KAUJ,OCnME,mBAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,EAAA,IAAA,IAAA,gBD4MV,8BE5PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFyPJ,8BE7PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF0PJ,8BE9PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF2PJ,2BE/PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF4PJ,8BEhQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF6PJ,6BEjQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFoQJ,MExQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFsQF,aAAA,QC3NA,mBAAA,MAAA,EAAA,IAAA,IAAA,gBAAA,EAAA,IAAA,EAAA,qBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,gBAAA,EAAA,IAAA,EAAA"} -------------------------------------------------------------------------------- /src/main/resources/static/bootstrap/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.6 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under the MIT license 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>2)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 3")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.6",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.6",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),a(c.target).is('input[type="radio"]')||a(c.target).is('input[type="checkbox"]')||c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.6",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.6",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.6",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),c.isInStateTrue()?void 0:(clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide())},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.6",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.6",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.6",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); -------------------------------------------------------------------------------- /src/main/resources/static/css/main.css: -------------------------------------------------------------------------------- 1 | /* navbar */ 2 | nav.navbar { 3 | margin: 0; 4 | } 5 | 6 | .navbar-default .dropdown-menu > li button { 7 | display: block; 8 | padding: 3px 20px; 9 | clear: both; 10 | font-weight: 400; 11 | line-height: 1.42857143; 12 | color: #333; 13 | white-space: nowrap; 14 | text-align: left; 15 | width: 100%; 16 | } 17 | 18 | .navbar-default .dropdown-menu > li button:hover, .dropdown-menu > li button:focus { 19 | text-decoration: none; 20 | color: #ffffff; 21 | background-color: #dd4814; 22 | } 23 | 24 | .navbar-btn, .navbar-btn:focus, .navbar-btn:hover, .navbar-btn:active { 25 | background-color: #97310e; 26 | border-color: #97310e; 27 | } 28 | 29 | /* layout */ 30 | .wrapper { 31 | padding: 0; 32 | } 33 | 34 | /* billboard */ 35 | .billboard { 36 | background: url("../img/cfp-background.png") no-repeat 0px -100px;; 37 | background-size: cover; 38 | padding: 70px 20px 20px 20px; 39 | min-height: 400px; 40 | text-align: center; 41 | } 42 | 43 | .billboard > div > h2 { 44 | text-align: center; 45 | color: #fff; 46 | background: rgba(52, 48, 45, .5); 47 | display: inline-block; 48 | font-size: 48px; 49 | font-weight: 400; 50 | line-height: 68px; 51 | padding: 0 15px; 52 | letter-spacing: -2px; 53 | } 54 | 55 | /* commits */ 56 | .commits .list-group-item { 57 | min-height: 80px; 58 | } 59 | 60 | .commits .list-group-item > img { 61 | float: left; 62 | margin-right: 20px; 63 | margin-bottom: 10px; 64 | } 65 | 66 | .panel-body > img { 67 | float: left; 68 | margin: 20px 20px 10px 10px; 69 | } 70 | 71 | /* days-since-last-polish */ 72 | .days-since-last-polish { 73 | color: #fff; 74 | font-size: 1em; 75 | text-transform: uppercase; 76 | text-align: center; 77 | width: 36em; 78 | margin: 3em auto; 79 | border: 1em solid #fff; 80 | background: #fff; 81 | -webkit-box-shadow: 0 0 2em #201f1e; 82 | -moz-box-shadow: 0 0 2em #201f1e; 83 | box-shadow: 0 0 2em #201f1e; 84 | } 85 | 86 | .days-since-last-polish > div { 87 | background: #229260; 88 | padding: 0.5em 3em; 89 | border-radius: 2em; 90 | } 91 | 92 | .days-since-last-polish h2 { 93 | font-size: 5em; 94 | } 95 | 96 | .days-since-last-polish h2 > span { 97 | margin-top: 0.05em; 98 | margin-bottom: 0.05em; 99 | background-color: #efefef; 100 | display: inline-block; 101 | color: #201f1e; 102 | border-radius: 0.25em; 103 | min-width: 2em; 104 | } 105 | 106 | .days-since-last-polish hr { 107 | background: #fff; 108 | color: #fff; 109 | height: 1em; 110 | width: 100%; 111 | border: none; 112 | margin-top: 1em; 113 | margin-bottom: 1em; 114 | } 115 | 116 | .days-since-last-polish p { 117 | font-size: 3em; 118 | } 119 | 120 | /* login form */ 121 | .login { 122 | margin-top:10%; 123 | } -------------------------------------------------------------------------------- /src/main/resources/static/img/cfp-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringOnePlatform2016/cfp-example/95db00490571a8edf97b8df40147a033ecaf8363/src/main/resources/static/img/cfp-background.png -------------------------------------------------------------------------------- /src/main/resources/templates/admin/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Admin - Submissions list 4 | 5 | 6 |
7 |
8 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
#TitleSpeakerTrack
1What's new in Spring Boot 1.4John DoeTrack name
29 |
30 |
31 | 32 | -------------------------------------------------------------------------------- /src/main/resources/templates/error/4xx.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Ooops, page not found 5 | 15 | 16 | 17 |
18 |
19 |

¯\_(ツ)_/¯

20 |
21 |
22 | 23 | -------------------------------------------------------------------------------- /src/main/resources/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Home 4 | 5 | 6 |
7 |
8 |
9 |

Spring Conference

10 |
11 |
12 |
13 |
14 |

Submit a talk to the CfP!

15 |

Share your passion

16 |

Submit

17 |
18 |
19 |
20 | 23 |
24 |
25 |

Spring Framework

26 |
27 |
28 | 29 |

Fix a Spring Framework issue

30 |

Committed by [[${latestFrameworkCommit.committer.name}]]

31 |
32 |
33 |
34 |
35 |

Spring Boot

36 |
37 |
38 | 39 |

Fix a Spring Boot issue

40 |

Committed by [[${latestBootCommit.committer.name}]]

41 |
42 |
43 |
44 |
45 | 46 | -------------------------------------------------------------------------------- /src/main/resources/templates/layouts/main.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | CfP Application 12 | 13 | 14 | 15 | 16 | 17 | 57 |
58 |
59 |
60 | 61 | 62 |
63 |
64 |
65 |

Example content

66 |
67 |
68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /src/main/resources/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Cfp login page 4 | 5 | 6 | 7 | 8 |
9 | 41 |
42 | 43 | -------------------------------------------------------------------------------- /src/main/resources/templates/news.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | News 4 | 5 | 6 |
7 |
8 |
9 |
10 |

2 DAYS

11 |
12 |

since last Polish

13 |
14 |
15 | 18 |
19 |
20 | 21 | deadb33f 22 |

Fix a Spring Framework issue

23 |

Committed by [[${commit.committer.name}]]

24 |
25 |
26 | 29 |
30 |
31 | 32 | deadb33f 33 |

Fix a Spring Framework issue

34 |

Committed by [[${commit.committer.name}]]

35 |
36 |
37 | 38 |
39 |
40 | 41 | -------------------------------------------------------------------------------- /src/main/resources/templates/submit.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | Submit a new proposal 5 | 6 | 7 |
8 |
9 | 12 |
14 | 15 |
16 |
17 | 18 |
19 | 20 | Incorrect title 21 |
22 |
23 |
24 | 25 |
26 | 27 | Describe your talk proposal here. 28 | Incorrect Summary 29 |
30 |
31 |
32 | 33 |
34 | 35 | A message for the selection committee. 36 |
37 |
38 |
39 | 40 |
41 | 44 |
45 |
46 |
47 |
48 | 49 | 50 |
51 |
52 |
53 |
54 |
55 |
56 | 57 | -------------------------------------------------------------------------------- /src/test/java/com/example/cfp/domain/SubmissionTest.java: -------------------------------------------------------------------------------- 1 | package com.example.cfp.domain; 2 | 3 | import java.util.List; 4 | 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureTestDatabase; 10 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 11 | import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; 12 | import org.springframework.test.context.junit4.SpringRunner; 13 | 14 | import static org.assertj.core.api.Assertions.assertThat; 15 | 16 | @RunWith(SpringRunner.class) 17 | @DataJpaTest 18 | @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) 19 | public class SubmissionTest { 20 | 21 | @Autowired 22 | private TestEntityManager entityManager; 23 | 24 | @Autowired 25 | public SubmissionRepository submissionRepository; 26 | 27 | @Test 28 | public void newSubmissionHasDraftStatus() { 29 | User speaker = this.entityManager.persist( 30 | new User("juergen@example.com", "Jürgen Höller")); 31 | Submission submission = new Submission(); 32 | submission.setSpeaker(speaker); 33 | Submission saved = this.submissionRepository.save(submission); 34 | assertThat(saved.getStatus()).isEqualTo(SubmissionStatus.DRAFT); 35 | } 36 | 37 | @Test 38 | public void findBySpeaker() { 39 | User speaker = this.entityManager.persist( 40 | new User("juergen@example.com", "Jürgen Höller")); 41 | this.submissionRepository.save(createDummySubmission(speaker, "Foo")); 42 | this.submissionRepository.save(createDummySubmission(speaker, "Bar")); 43 | 44 | List submissions = this.submissionRepository.findBySpeaker(speaker); 45 | assertThat(submissions).hasSize(2); 46 | } 47 | 48 | private Submission createDummySubmission(User speaker, String title) { 49 | Submission submission = new Submission(); 50 | submission.setSpeaker(speaker); 51 | submission.setTitle(title); 52 | submission.setSummary("Live coding 4tw"); 53 | submission.setTrack(Track.SERVER_SIDE_JAVA); 54 | return submission; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/com/example/cfp/domain/UserTest.java: -------------------------------------------------------------------------------- 1 | package com.example.cfp.domain; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.autoconfigure.ImportAutoConfiguration; 8 | import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; 9 | import org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureTestDatabase; 10 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 11 | import org.springframework.test.context.junit4.SpringRunner; 12 | 13 | import static org.assertj.core.api.Assertions.assertThat; 14 | 15 | @RunWith(SpringRunner.class) 16 | @DataJpaTest 17 | @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) 18 | @ImportAutoConfiguration(FlywayAutoConfiguration.class) 19 | public class UserTest { 20 | 21 | @Autowired 22 | private UserRepository userRepository; 23 | 24 | @Test 25 | public void findByGithub() { 26 | User user = new User("brian@example.com", "Brian Clozel"); 27 | user.setGithub("bclozel"); 28 | this.userRepository.save(user); 29 | 30 | User brian = this.userRepository.findByGithub("bclozel"); 31 | assertThat(brian).isNotNull(); 32 | assertThat(brian.getName()).isEqualTo("Brian Clozel"); 33 | } 34 | 35 | @Test 36 | public void findByTwitter() { 37 | User user = new User("juergen@example.com", "Jürgen Höller"); 38 | user.setTwitter("springjuergen"); 39 | this.userRepository.save(user); 40 | 41 | User juergen = this.userRepository.findByTwitter("springjuergen"); 42 | assertThat(juergen).isNotNull(); 43 | assertThat(juergen.getName()).isEqualTo("Jürgen Höller"); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/com/example/cfp/integration/github/GithubClientTest.java: -------------------------------------------------------------------------------- 1 | package com.example.cfp.integration.github; 2 | 3 | import java.util.List; 4 | 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.actuate.metrics.CounterService; 10 | import org.springframework.boot.test.autoconfigure.web.client.RestClientTest; 11 | import org.springframework.boot.test.mock.mockito.MockBean; 12 | import org.springframework.core.io.ClassPathResource; 13 | import org.springframework.http.HttpHeaders; 14 | import org.springframework.http.HttpMethod; 15 | import org.springframework.http.HttpStatus; 16 | import org.springframework.http.MediaType; 17 | import org.springframework.test.context.junit4.SpringRunner; 18 | import org.springframework.test.web.client.MockRestServiceServer; 19 | 20 | import static org.assertj.core.api.Assertions.*; 21 | import static org.mockito.Mockito.*; 22 | import static org.springframework.test.web.client.match.MockRestRequestMatchers.*; 23 | import static org.springframework.test.web.client.response.MockRestResponseCreators.*; 24 | 25 | @RunWith(SpringRunner.class) 26 | @RestClientTest(GithubClient.class) 27 | public class GithubClientTest { 28 | 29 | @MockBean 30 | private CounterService counterService; 31 | 32 | @Autowired 33 | private GithubClient githubClient; 34 | 35 | @Autowired 36 | private MockRestServiceServer mockServer; 37 | 38 | @Test 39 | public void getRecentCommits() { 40 | expectJson("https://api.github.com/repos/spring-projects/spring-framework/commits", 41 | "github/spring-framework-commits.json"); 42 | List recentCommits = this.githubClient.getRecentCommits( 43 | "spring-projects", "spring-framework"); 44 | this.mockServer.verify(); 45 | assertThat(recentCommits).hasSize(5); 46 | assertCommit(recentCommits.get(0), "7737c3c", 47 | "Warn about non-static BeanDefinitionRegistryPostProcessor declarations on @Configuration classes", 48 | "2016-05-02T13:19:05Z", 49 | "jhoeller", "Juergen Hoeller", "https://avatars.githubusercontent.com/u/1263688?v=3"); 50 | assertCommit(recentCommits.get(3), "09b45d2", 51 | "Validate callback is always invoked in DMLC#stop", 52 | "2016-05-02T11:33:05Z", 53 | "snicoll", "Stephane Nicoll", "https://avatars.githubusercontent.com/u/490484?v=3"); 54 | verify(this.counterService, times(1)).increment("cfp.github.requests"); 55 | } 56 | 57 | @Test 58 | public void getRecentCommitsNoCommit() { 59 | expectJson("https://api.github.com/repos/spring-projects/spring-boot/commits", 60 | "github/no-commit.json"); 61 | List latestCommit = this.githubClient.getRecentCommits("spring-projects", "spring-boot"); 62 | assertThat(latestCommit).hasSize(0); 63 | verify(this.counterService, times(1)).increment("cfp.github.requests"); 64 | } 65 | 66 | @Test 67 | public void getRecentPolishCommitNoMatch() { 68 | expectJsonPages("https://api.github.com/repos/spring-projects/spring-framework/commits", 69 | "github/spring-framework-commits-no-polish.json", 70 | "github/spring-framework-commits-no-polish.json", 71 | "github/spring-framework-commits-no-polish.json", 72 | "github/spring-framework-commits-no-polish.json", 73 | "github/spring-framework-commits-no-polish.json"); 74 | Commit recentPolish = this.githubClient.getRecentPolishCommit("spring-projects", "spring-framework"); 75 | assertThat(recentPolish).isNull(); 76 | verify(this.counterService, times(5)).increment("cfp.github.requests"); 77 | } 78 | 79 | @Test 80 | public void getRecentPolishCommitMatch() { 81 | expectJsonPages("https://api.github.com/repos/spring-projects/spring-framework/commits", 82 | "github/spring-framework-commits-no-polish.json", 83 | "github/spring-framework-commits-no-polish.json", 84 | "github/spring-framework-commits.json", 85 | "github/spring-framework-commits-no-polish.json", 86 | "github/spring-framework-commits-no-polish.json"); 87 | Commit recentPolish = this.githubClient.getRecentPolishCommit("spring-projects", "spring-framework"); 88 | assertThat(recentPolish).isNotNull(); 89 | assertThat(recentPolish.getMessage()).isEqualTo("Polishing"); 90 | assertThat(recentPolish.getSha()).isEqualTo("07ea3745c49ec506e17dcb56107639cf36339d2c"); 91 | verify(this.counterService, times(3)).increment("cfp.github.requests"); 92 | } 93 | 94 | @Test 95 | public void getRecentPolishCommitNoMatchSinglePage() { 96 | expectJsonPages("https://api.github.com/repos/spring-projects/spring-framework/commits", 97 | "github/spring-framework-commits-no-polish.json"); 98 | Commit recentPolish = this.githubClient.getRecentPolishCommit("spring-projects", "spring-framework"); 99 | assertThat(recentPolish).isNull(); 100 | verify(this.counterService, times(1)).increment("cfp.github.requests"); 101 | } 102 | 103 | @Test 104 | public void getUser() { 105 | expectJson("https://api.github.com/users/jsmith", "github/jsmith.json"); 106 | GithubUser user = this.githubClient.getUser("jsmith"); 107 | assertThat(user.getEmail()).isEqualTo("john@example.com"); 108 | assertThat(user.getName()).isEqualTo("John Smith"); 109 | assertThat(user.getCompany()).isEqualTo("Acme Inc."); 110 | assertThat(user.getBlog()).isEqualTo("https://acme.org/blog"); 111 | assertThat(user.getAvatar()).isEqualTo("https://acme.org/team/jsmith/avatar"); 112 | } 113 | 114 | private void expectJsonPages(String initialUrl, String... pages) { 115 | for (int i = 0; i < pages.length; i++) { 116 | String url = i == 0 ? initialUrl : initialUrl + "?page=" + (i + 1); 117 | String nextPage = (i + 1) < pages.length ? initialUrl + "?page=" + (i + 2) : null; 118 | expectJson(url, pages[i], nextPage); 119 | } 120 | } 121 | 122 | private void expectJson(String url, String bodyPath) { 123 | expectJson(url, bodyPath, null); 124 | } 125 | 126 | private void expectJson(String url, String bodyPath, String nextPage) { 127 | HttpHeaders httpHeaders = new HttpHeaders(); 128 | httpHeaders.setContentType(MediaType.APPLICATION_JSON); 129 | if (nextPage != null) { 130 | httpHeaders.set(HttpHeaders.LINK, 131 | String.format("<%s>; rel=\"next\"", nextPage)); 132 | } 133 | this.mockServer.expect(requestTo(url)) 134 | .andExpect(method(HttpMethod.GET)) 135 | .andExpect(header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)) 136 | .andRespond(withStatus(HttpStatus.OK) 137 | .body(new ClassPathResource(bodyPath)) 138 | .headers(httpHeaders)); 139 | } 140 | 141 | private void assertCommit(Commit commit, String sha, String message, String date, 142 | String committerId, String committerName, String committerAvatar) { 143 | assertThat(commit).isNotNull(); 144 | assertThat(commit.getSha()).isEqualTo(sha); 145 | assertThat(commit.getMessage()).isEqualTo(message); 146 | assertThat(commit.getDate().toString()).isEqualTo(date); 147 | Commit.Committer committer = commit.getCommitter(); 148 | assertThat(committer).isNotNull(); 149 | assertThat(committer.getId()).isEqualTo(committerId); 150 | assertThat(committer.getName()).isEqualTo(committerName); 151 | assertThat(committer.getAvatarUrl()).isEqualTo(committerAvatar); 152 | } 153 | 154 | } 155 | -------------------------------------------------------------------------------- /src/test/java/com/example/cfp/submission/SubmissionServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.example.cfp.submission; 2 | 3 | import java.util.List; 4 | 5 | import com.example.cfp.domain.Submission; 6 | import com.example.cfp.domain.SubmissionStatus; 7 | import com.example.cfp.domain.Track; 8 | import com.example.cfp.domain.User; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 14 | import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; 15 | import org.springframework.context.annotation.ComponentScan; 16 | import org.springframework.context.annotation.FilterType; 17 | import org.springframework.test.context.junit4.SpringRunner; 18 | 19 | import static org.assertj.core.api.Assertions.assertThat; 20 | 21 | @RunWith(SpringRunner.class) 22 | @DataJpaTest(includeFilters = @ComponentScan.Filter( 23 | type = FilterType.ASSIGNABLE_TYPE, classes = SubmissionService.class)) 24 | public class SubmissionServiceTest { 25 | 26 | @Autowired 27 | private TestEntityManager entityManager; 28 | 29 | @Autowired 30 | private SubmissionService submissionService; 31 | 32 | @Test 33 | public void createTalk() throws Exception { 34 | User speaker = this.entityManager.persist( 35 | new User("john@example.com", "John Smith")); 36 | SubmissionRequest request = new SubmissionRequest(); 37 | request.setSpeaker(speaker); 38 | request.setTalk("Alice in Wonderland", "my abstract", Track.ALTERNATE_LANGUAGES, 39 | "this rocks"); 40 | Submission submission = this.submissionService.create(request); 41 | assertThat(submission).isNotNull(); 42 | assertThat(submission.getSpeaker()).isNotNull(); 43 | assertThat(submission.getSpeaker().getId()).isEqualTo(speaker.getId()); 44 | assertThat(submission.getTitle()).isEqualTo("Alice in Wonderland"); 45 | assertThat(submission.getSummary()).isEqualTo("my abstract"); 46 | assertThat(submission.getTrack()).isEqualTo(Track.ALTERNATE_LANGUAGES); 47 | assertThat(submission.getNotes()).isEqualTo("this rocks"); 48 | assertThat(submission.getStatus()).isEqualTo(SubmissionStatus.DRAFT); 49 | 50 | List submissions = this.submissionService.getSubmissionRepository() 51 | .findBySpeaker(speaker); 52 | assertThat(submissions).hasSize(1); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/com/example/cfp/web/CfpControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.example.cfp.web; 2 | 3 | import com.example.cfp.domain.Submission; 4 | import com.example.cfp.domain.Track; 5 | import com.example.cfp.domain.User; 6 | import com.example.cfp.submission.SubmissionService; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 12 | import org.springframework.boot.test.mock.mockito.MockBean; 13 | import org.springframework.http.HttpHeaders; 14 | import org.springframework.security.authentication.TestingAuthenticationToken; 15 | import org.springframework.security.core.Authentication; 16 | import org.springframework.security.test.context.support.WithMockUser; 17 | import org.springframework.test.context.junit4.SpringRunner; 18 | import org.springframework.test.web.servlet.MockMvc; 19 | 20 | import static org.mockito.BDDMockito.given; 21 | import static org.mockito.Matchers.any; 22 | import static org.mockito.Mockito.*; 23 | import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; 24 | import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; 25 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 26 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; 27 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 28 | 29 | @RunWith(SpringRunner.class) 30 | @WebMvcTest(controllers = CfpController.class) 31 | public class CfpControllerTest { 32 | 33 | @Autowired 34 | private MockMvc mvc; 35 | 36 | @MockBean 37 | private SubmissionService submissionService; 38 | 39 | @WithMockUser("jsmith") 40 | @Test 41 | public void submitTalk() throws Exception { 42 | Authentication authentication = new TestingAuthenticationToken( 43 | new User("jsmith", "John Smith"), "secret", "ROLE_USER"); 44 | 45 | given(this.submissionService.create(any())).willReturn(new Submission()); 46 | this.mvc.perform(post("/submit") 47 | .param("title", "Alice in Wonderland") 48 | .param("summary", "my abstract") 49 | .param("track", Track.ALTERNATE_LANGUAGES.getId()) 50 | .param("notes", "this rocks") 51 | .with(authentication(authentication)) 52 | .with(csrf())) 53 | .andExpect(status().isFound()) 54 | .andExpect(header().string(HttpHeaders.LOCATION, "/submit?navSection=submit")); 55 | verify(this.submissionService).create(any()); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/test/resources/github/jsmith.json: -------------------------------------------------------------------------------- 1 | { 2 | "login": "jsmith", 3 | "id": 123412341234, 4 | "avatar_url": "https://acme.org/team/jsmith/avatar", 5 | "gravatar_id": "", 6 | "url": "https://api.github.com/users/jsmith", 7 | "html_url": "https://github.com/jsmith", 8 | "followers_url": "https://api.github.com/users/jsmith/followers", 9 | "following_url": "https://api.github.com/users/jsmith/following{/other_user}", 10 | "gists_url": "https://api.github.com/users/jsmith/gists{/gist_id}", 11 | "starred_url": "https://api.github.com/users/jsmith/starred{/owner}{/repo}", 12 | "subscriptions_url": "https://api.github.com/users/jsmith/subscriptions", 13 | "organizations_url": "https://api.github.com/users/jsmith/orgs", 14 | "repos_url": "https://api.github.com/users/jsmith/repos", 15 | "events_url": "https://api.github.com/users/jsmith/events{/privacy}", 16 | "received_events_url": "https://api.github.com/users/jsmith/received_events", 17 | "type": "User", 18 | "site_admin": false, 19 | "name": "John Smith", 20 | "company": "Acme Inc.", 21 | "blog": "https://acme.org/blog", 22 | "location": null, 23 | "email": "john@example.com", 24 | "hireable": null, 25 | "bio": null, 26 | "public_repos": 55, 27 | "public_gists": 40, 28 | "followers": 145, 29 | "following": 13, 30 | "created_at": "2010-11-21T10:27:20Z", 31 | "updated_at": "2016-04-11T08:28:18Z" 32 | } -------------------------------------------------------------------------------- /src/test/resources/github/no-commit.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /src/test/resources/github/spring-framework-commits-no-polish.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "sha": "7737c3c", 4 | "commit": { 5 | "author": { 6 | "name": "Juergen Hoeller", 7 | "email": "jhoeller@pivotal.io", 8 | "date": "2016-05-02T13:19:05Z" 9 | }, 10 | "committer": { 11 | "name": "Juergen Hoeller", 12 | "email": "jhoeller@pivotal.io", 13 | "date": "2016-05-02T13:19:05Z" 14 | }, 15 | "message": "Warn about non-static BeanDefinitionRegistryPostProcessor declarations on @Configuration classes\n\nIssue: SPR-14234", 16 | "tree": { 17 | "sha": "03b355bd72dc30df77189165a6194ed364754d07", 18 | "url": "https://api.github.com/repos/spring-projects/spring-framework/git/trees/03b355bd72dc30df77189165a6194ed364754d07" 19 | }, 20 | "url": "https://api.github.com/repos/spring-projects/spring-framework/git/commits/7737c3c7e5e3ffe19e6cc151f1929982f5bba35d", 21 | "comment_count": 0 22 | }, 23 | "url": "https://api.github.com/repos/spring-projects/spring-framework/commits/7737c3c7e5e3ffe19e6cc151f1929982f5bba35d", 24 | "html_url": "https://github.com/spring-projects/spring-framework/commit/7737c3c7e5e3ffe19e6cc151f1929982f5bba35d", 25 | "comments_url": "https://api.github.com/repos/spring-projects/spring-framework/commits/7737c3c7e5e3ffe19e6cc151f1929982f5bba35d/comments", 26 | "author": { 27 | "login": "jhoeller", 28 | "id": 1263688, 29 | "avatar_url": "https://avatars.githubusercontent.com/u/1263688?v=3", 30 | "gravatar_id": "", 31 | "url": "https://api.github.com/users/jhoeller", 32 | "html_url": "https://github.com/jhoeller", 33 | "followers_url": "https://api.github.com/users/jhoeller/followers", 34 | "following_url": "https://api.github.com/users/jhoeller/following{/other_user}", 35 | "gists_url": "https://api.github.com/users/jhoeller/gists{/gist_id}", 36 | "starred_url": "https://api.github.com/users/jhoeller/starred{/owner}{/repo}", 37 | "subscriptions_url": "https://api.github.com/users/jhoeller/subscriptions", 38 | "organizations_url": "https://api.github.com/users/jhoeller/orgs", 39 | "repos_url": "https://api.github.com/users/jhoeller/repos", 40 | "events_url": "https://api.github.com/users/jhoeller/events{/privacy}", 41 | "received_events_url": "https://api.github.com/users/jhoeller/received_events", 42 | "type": "User", 43 | "site_admin": false 44 | }, 45 | "committer": { 46 | "login": "jhoeller", 47 | "id": 1263688, 48 | "avatar_url": "https://avatars.githubusercontent.com/u/1263688?v=3", 49 | "gravatar_id": "", 50 | "url": "https://api.github.com/users/jhoeller", 51 | "html_url": "https://github.com/jhoeller", 52 | "followers_url": "https://api.github.com/users/jhoeller/followers", 53 | "following_url": "https://api.github.com/users/jhoeller/following{/other_user}", 54 | "gists_url": "https://api.github.com/users/jhoeller/gists{/gist_id}", 55 | "starred_url": "https://api.github.com/users/jhoeller/starred{/owner}{/repo}", 56 | "subscriptions_url": "https://api.github.com/users/jhoeller/subscriptions", 57 | "organizations_url": "https://api.github.com/users/jhoeller/orgs", 58 | "repos_url": "https://api.github.com/users/jhoeller/repos", 59 | "events_url": "https://api.github.com/users/jhoeller/events{/privacy}", 60 | "received_events_url": "https://api.github.com/users/jhoeller/received_events", 61 | "type": "User", 62 | "site_admin": false 63 | }, 64 | "parents": [ 65 | { 66 | "sha": "cbc46760b74550fc573aaa62333cfdcb721a4e7b", 67 | "url": "https://api.github.com/repos/spring-projects/spring-framework/commits/cbc46760b74550fc573aaa62333cfdcb721a4e7b", 68 | "html_url": "https://github.com/spring-projects/spring-framework/commit/cbc46760b74550fc573aaa62333cfdcb721a4e7b" 69 | } 70 | ] 71 | }, 72 | { 73 | "sha": "cbc46760b74550fc573aaa62333cfdcb721a4e7b", 74 | "commit": { 75 | "author": { 76 | "name": "Juergen Hoeller", 77 | "email": "jhoeller@pivotal.io", 78 | "date": "2016-05-02T11:54:57Z" 79 | }, 80 | "committer": { 81 | "name": "Juergen Hoeller", 82 | "email": "jhoeller@pivotal.io", 83 | "date": "2016-05-02T11:54:57Z" 84 | }, 85 | "message": "Upgrade to Jackson 2.7.4", 86 | "tree": { 87 | "sha": "327af1dff6b57df6ceaea6c68f26996443a9c3c5", 88 | "url": "https://api.github.com/repos/spring-projects/spring-framework/git/trees/327af1dff6b57df6ceaea6c68f26996443a9c3c5" 89 | }, 90 | "url": "https://api.github.com/repos/spring-projects/spring-framework/git/commits/cbc46760b74550fc573aaa62333cfdcb721a4e7b", 91 | "comment_count": 0 92 | }, 93 | "url": "https://api.github.com/repos/spring-projects/spring-framework/commits/cbc46760b74550fc573aaa62333cfdcb721a4e7b", 94 | "html_url": "https://github.com/spring-projects/spring-framework/commit/cbc46760b74550fc573aaa62333cfdcb721a4e7b", 95 | "comments_url": "https://api.github.com/repos/spring-projects/spring-framework/commits/cbc46760b74550fc573aaa62333cfdcb721a4e7b/comments", 96 | "author": { 97 | "login": "jhoeller", 98 | "id": 1263688, 99 | "avatar_url": "https://avatars.githubusercontent.com/u/1263688?v=3", 100 | "gravatar_id": "", 101 | "url": "https://api.github.com/users/jhoeller", 102 | "html_url": "https://github.com/jhoeller", 103 | "followers_url": "https://api.github.com/users/jhoeller/followers", 104 | "following_url": "https://api.github.com/users/jhoeller/following{/other_user}", 105 | "gists_url": "https://api.github.com/users/jhoeller/gists{/gist_id}", 106 | "starred_url": "https://api.github.com/users/jhoeller/starred{/owner}{/repo}", 107 | "subscriptions_url": "https://api.github.com/users/jhoeller/subscriptions", 108 | "organizations_url": "https://api.github.com/users/jhoeller/orgs", 109 | "repos_url": "https://api.github.com/users/jhoeller/repos", 110 | "events_url": "https://api.github.com/users/jhoeller/events{/privacy}", 111 | "received_events_url": "https://api.github.com/users/jhoeller/received_events", 112 | "type": "User", 113 | "site_admin": false 114 | }, 115 | "committer": { 116 | "login": "jhoeller", 117 | "id": 1263688, 118 | "avatar_url": "https://avatars.githubusercontent.com/u/1263688?v=3", 119 | "gravatar_id": "", 120 | "url": "https://api.github.com/users/jhoeller", 121 | "html_url": "https://github.com/jhoeller", 122 | "followers_url": "https://api.github.com/users/jhoeller/followers", 123 | "following_url": "https://api.github.com/users/jhoeller/following{/other_user}", 124 | "gists_url": "https://api.github.com/users/jhoeller/gists{/gist_id}", 125 | "starred_url": "https://api.github.com/users/jhoeller/starred{/owner}{/repo}", 126 | "subscriptions_url": "https://api.github.com/users/jhoeller/subscriptions", 127 | "organizations_url": "https://api.github.com/users/jhoeller/orgs", 128 | "repos_url": "https://api.github.com/users/jhoeller/repos", 129 | "events_url": "https://api.github.com/users/jhoeller/events{/privacy}", 130 | "received_events_url": "https://api.github.com/users/jhoeller/received_events", 131 | "type": "User", 132 | "site_admin": false 133 | }, 134 | "parents": [ 135 | { 136 | "sha": "07ea3745c49ec506e17dcb56107639cf36339d2c", 137 | "url": "https://api.github.com/repos/spring-projects/spring-framework/commits/07ea3745c49ec506e17dcb56107639cf36339d2c", 138 | "html_url": "https://github.com/spring-projects/spring-framework/commit/07ea3745c49ec506e17dcb56107639cf36339d2c" 139 | } 140 | ] 141 | }, 142 | { 143 | "sha": "07ea3745c49ec506e17dcb56107639cf36339d2c", 144 | "commit": { 145 | "author": { 146 | "name": "Juergen Hoeller", 147 | "email": "jhoeller@pivotal.io", 148 | "date": "2016-05-02T11:54:47Z" 149 | }, 150 | "committer": { 151 | "name": "Juergen Hoeller", 152 | "email": "jhoeller@pivotal.io", 153 | "date": "2016-05-02T11:54:47Z" 154 | }, 155 | "message": "Hacking", 156 | "tree": { 157 | "sha": "aae6620ea14cdcde222b848e05907b85b6488a2a", 158 | "url": "https://api.github.com/repos/spring-projects/spring-framework/git/trees/aae6620ea14cdcde222b848e05907b85b6488a2a" 159 | }, 160 | "url": "https://api.github.com/repos/spring-projects/spring-framework/git/commits/07ea3745c49ec506e17dcb56107639cf36339d2c", 161 | "comment_count": 0 162 | }, 163 | "url": "https://api.github.com/repos/spring-projects/spring-framework/commits/07ea3745c49ec506e17dcb56107639cf36339d2c", 164 | "html_url": "https://github.com/spring-projects/spring-framework/commit/07ea3745c49ec506e17dcb56107639cf36339d2c", 165 | "comments_url": "https://api.github.com/repos/spring-projects/spring-framework/commits/07ea3745c49ec506e17dcb56107639cf36339d2c/comments", 166 | "author": { 167 | "login": "jhoeller", 168 | "id": 1263688, 169 | "avatar_url": "https://avatars.githubusercontent.com/u/1263688?v=3", 170 | "gravatar_id": "", 171 | "url": "https://api.github.com/users/jhoeller", 172 | "html_url": "https://github.com/jhoeller", 173 | "followers_url": "https://api.github.com/users/jhoeller/followers", 174 | "following_url": "https://api.github.com/users/jhoeller/following{/other_user}", 175 | "gists_url": "https://api.github.com/users/jhoeller/gists{/gist_id}", 176 | "starred_url": "https://api.github.com/users/jhoeller/starred{/owner}{/repo}", 177 | "subscriptions_url": "https://api.github.com/users/jhoeller/subscriptions", 178 | "organizations_url": "https://api.github.com/users/jhoeller/orgs", 179 | "repos_url": "https://api.github.com/users/jhoeller/repos", 180 | "events_url": "https://api.github.com/users/jhoeller/events{/privacy}", 181 | "received_events_url": "https://api.github.com/users/jhoeller/received_events", 182 | "type": "User", 183 | "site_admin": false 184 | }, 185 | "committer": { 186 | "login": "jhoeller", 187 | "id": 1263688, 188 | "avatar_url": "https://avatars.githubusercontent.com/u/1263688?v=3", 189 | "gravatar_id": "", 190 | "url": "https://api.github.com/users/jhoeller", 191 | "html_url": "https://github.com/jhoeller", 192 | "followers_url": "https://api.github.com/users/jhoeller/followers", 193 | "following_url": "https://api.github.com/users/jhoeller/following{/other_user}", 194 | "gists_url": "https://api.github.com/users/jhoeller/gists{/gist_id}", 195 | "starred_url": "https://api.github.com/users/jhoeller/starred{/owner}{/repo}", 196 | "subscriptions_url": "https://api.github.com/users/jhoeller/subscriptions", 197 | "organizations_url": "https://api.github.com/users/jhoeller/orgs", 198 | "repos_url": "https://api.github.com/users/jhoeller/repos", 199 | "events_url": "https://api.github.com/users/jhoeller/events{/privacy}", 200 | "received_events_url": "https://api.github.com/users/jhoeller/received_events", 201 | "type": "User", 202 | "site_admin": false 203 | }, 204 | "parents": [ 205 | { 206 | "sha": "09b45d2c0feee9ebdc7c03e3768cf834bf81a195", 207 | "url": "https://api.github.com/repos/spring-projects/spring-framework/commits/09b45d2c0feee9ebdc7c03e3768cf834bf81a195", 208 | "html_url": "https://github.com/spring-projects/spring-framework/commit/09b45d2c0feee9ebdc7c03e3768cf834bf81a195" 209 | } 210 | ] 211 | }, 212 | { 213 | "sha": "09b45d2", 214 | "commit": { 215 | "author": { 216 | "name": "Stephane Nicoll", 217 | "email": "snicoll@pivotal.io", 218 | "date": "2016-05-02T11:33:05Z" 219 | }, 220 | "committer": { 221 | "name": "Stephane Nicoll", 222 | "email": "snicoll@pivotal.io", 223 | "date": "2016-05-02T11:33:05Z" 224 | }, 225 | "message": "Validate callback is always invoked in DMLC#stop\n\nThe underlying issue has been fixed in e45d33f and this commit is merely\nadding a test of the expected behaviour.\n\nIssue: SPR-14233", 226 | "tree": { 227 | "sha": "1d1b0376d9927563527d53d11d768eac82511549", 228 | "url": "https://api.github.com/repos/spring-projects/spring-framework/git/trees/1d1b0376d9927563527d53d11d768eac82511549" 229 | }, 230 | "url": "https://api.github.com/repos/spring-projects/spring-framework/git/commits/09b45d2c0feee9ebdc7c03e3768cf834bf81a195", 231 | "comment_count": 0 232 | }, 233 | "url": "https://api.github.com/repos/spring-projects/spring-framework/commits/09b45d2c0feee9ebdc7c03e3768cf834bf81a195", 234 | "html_url": "https://github.com/spring-projects/spring-framework/commit/09b45d2c0feee9ebdc7c03e3768cf834bf81a195", 235 | "comments_url": "https://api.github.com/repos/spring-projects/spring-framework/commits/09b45d2c0feee9ebdc7c03e3768cf834bf81a195/comments", 236 | "author": { 237 | "login": "snicoll", 238 | "id": 490484, 239 | "avatar_url": "https://avatars.githubusercontent.com/u/490484?v=3", 240 | "gravatar_id": "", 241 | "url": "https://api.github.com/users/snicoll", 242 | "html_url": "https://github.com/snicoll", 243 | "followers_url": "https://api.github.com/users/snicoll/followers", 244 | "following_url": "https://api.github.com/users/snicoll/following{/other_user}", 245 | "gists_url": "https://api.github.com/users/snicoll/gists{/gist_id}", 246 | "starred_url": "https://api.github.com/users/snicoll/starred{/owner}{/repo}", 247 | "subscriptions_url": "https://api.github.com/users/snicoll/subscriptions", 248 | "organizations_url": "https://api.github.com/users/snicoll/orgs", 249 | "repos_url": "https://api.github.com/users/snicoll/repos", 250 | "events_url": "https://api.github.com/users/snicoll/events{/privacy}", 251 | "received_events_url": "https://api.github.com/users/snicoll/received_events", 252 | "type": "User", 253 | "site_admin": false 254 | }, 255 | "committer": { 256 | "login": "snicoll", 257 | "id": 490484, 258 | "avatar_url": "https://avatars.githubusercontent.com/u/490484?v=3", 259 | "gravatar_id": "", 260 | "url": "https://api.github.com/users/snicoll", 261 | "html_url": "https://github.com/snicoll", 262 | "followers_url": "https://api.github.com/users/snicoll/followers", 263 | "following_url": "https://api.github.com/users/snicoll/following{/other_user}", 264 | "gists_url": "https://api.github.com/users/snicoll/gists{/gist_id}", 265 | "starred_url": "https://api.github.com/users/snicoll/starred{/owner}{/repo}", 266 | "subscriptions_url": "https://api.github.com/users/snicoll/subscriptions", 267 | "organizations_url": "https://api.github.com/users/snicoll/orgs", 268 | "repos_url": "https://api.github.com/users/snicoll/repos", 269 | "events_url": "https://api.github.com/users/snicoll/events{/privacy}", 270 | "received_events_url": "https://api.github.com/users/snicoll/received_events", 271 | "type": "User", 272 | "site_admin": false 273 | }, 274 | "parents": [ 275 | { 276 | "sha": "f83cbff543b3519c9288c1eee0ed3ae7e4c749d2", 277 | "url": "https://api.github.com/repos/spring-projects/spring-framework/commits/f83cbff543b3519c9288c1eee0ed3ae7e4c749d2", 278 | "html_url": "https://github.com/spring-projects/spring-framework/commit/f83cbff543b3519c9288c1eee0ed3ae7e4c749d2" 279 | } 280 | ] 281 | }, 282 | { 283 | "sha": "f83cbff543b3519c9288c1eee0ed3ae7e4c749d2", 284 | "commit": { 285 | "author": { 286 | "name": "Juergen Hoeller", 287 | "email": "jhoeller@pivotal.io", 288 | "date": "2016-05-02T11:01:44Z" 289 | }, 290 | "committer": { 291 | "name": "Juergen Hoeller", 292 | "email": "jhoeller@pivotal.io", 293 | "date": "2016-05-02T11:01:44Z" 294 | }, 295 | "message": "Consistent SmartLifecycle implementations\n\nIssue: SPR-14233", 296 | "tree": { 297 | "sha": "7c431cdd4c2a480d68383144f81f720736cc562e", 298 | "url": "https://api.github.com/repos/spring-projects/spring-framework/git/trees/7c431cdd4c2a480d68383144f81f720736cc562e" 299 | }, 300 | "url": "https://api.github.com/repos/spring-projects/spring-framework/git/commits/f83cbff543b3519c9288c1eee0ed3ae7e4c749d2", 301 | "comment_count": 0 302 | }, 303 | "url": "https://api.github.com/repos/spring-projects/spring-framework/commits/f83cbff543b3519c9288c1eee0ed3ae7e4c749d2", 304 | "html_url": "https://github.com/spring-projects/spring-framework/commit/f83cbff543b3519c9288c1eee0ed3ae7e4c749d2", 305 | "comments_url": "https://api.github.com/repos/spring-projects/spring-framework/commits/f83cbff543b3519c9288c1eee0ed3ae7e4c749d2/comments", 306 | "author": { 307 | "login": "jhoeller", 308 | "id": 1263688, 309 | "avatar_url": "https://avatars.githubusercontent.com/u/1263688?v=3", 310 | "gravatar_id": "", 311 | "url": "https://api.github.com/users/jhoeller", 312 | "html_url": "https://github.com/jhoeller", 313 | "followers_url": "https://api.github.com/users/jhoeller/followers", 314 | "following_url": "https://api.github.com/users/jhoeller/following{/other_user}", 315 | "gists_url": "https://api.github.com/users/jhoeller/gists{/gist_id}", 316 | "starred_url": "https://api.github.com/users/jhoeller/starred{/owner}{/repo}", 317 | "subscriptions_url": "https://api.github.com/users/jhoeller/subscriptions", 318 | "organizations_url": "https://api.github.com/users/jhoeller/orgs", 319 | "repos_url": "https://api.github.com/users/jhoeller/repos", 320 | "events_url": "https://api.github.com/users/jhoeller/events{/privacy}", 321 | "received_events_url": "https://api.github.com/users/jhoeller/received_events", 322 | "type": "User", 323 | "site_admin": false 324 | }, 325 | "committer": { 326 | "login": "jhoeller", 327 | "id": 1263688, 328 | "avatar_url": "https://avatars.githubusercontent.com/u/1263688?v=3", 329 | "gravatar_id": "", 330 | "url": "https://api.github.com/users/jhoeller", 331 | "html_url": "https://github.com/jhoeller", 332 | "followers_url": "https://api.github.com/users/jhoeller/followers", 333 | "following_url": "https://api.github.com/users/jhoeller/following{/other_user}", 334 | "gists_url": "https://api.github.com/users/jhoeller/gists{/gist_id}", 335 | "starred_url": "https://api.github.com/users/jhoeller/starred{/owner}{/repo}", 336 | "subscriptions_url": "https://api.github.com/users/jhoeller/subscriptions", 337 | "organizations_url": "https://api.github.com/users/jhoeller/orgs", 338 | "repos_url": "https://api.github.com/users/jhoeller/repos", 339 | "events_url": "https://api.github.com/users/jhoeller/events{/privacy}", 340 | "received_events_url": "https://api.github.com/users/jhoeller/received_events", 341 | "type": "User", 342 | "site_admin": false 343 | }, 344 | "parents": [ 345 | { 346 | "sha": "e45d33f9de595d7348f3b4695960cb26bd9255c0", 347 | "url": "https://api.github.com/repos/spring-projects/spring-framework/commits/e45d33f9de595d7348f3b4695960cb26bd9255c0", 348 | "html_url": "https://github.com/spring-projects/spring-framework/commit/e45d33f9de595d7348f3b4695960cb26bd9255c0" 349 | } 350 | ] 351 | } 352 | ] -------------------------------------------------------------------------------- /src/test/resources/github/spring-framework-commits.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "sha": "7737c3c", 4 | "commit": { 5 | "author": { 6 | "name": "Juergen Hoeller", 7 | "email": "jhoeller@pivotal.io", 8 | "date": "2016-05-02T13:19:05Z" 9 | }, 10 | "committer": { 11 | "name": "Juergen Hoeller", 12 | "email": "jhoeller@pivotal.io", 13 | "date": "2016-05-02T13:19:05Z" 14 | }, 15 | "message": "Warn about non-static BeanDefinitionRegistryPostProcessor declarations on @Configuration classes\n\nIssue: SPR-14234", 16 | "tree": { 17 | "sha": "03b355bd72dc30df77189165a6194ed364754d07", 18 | "url": "https://api.github.com/repos/spring-projects/spring-framework/git/trees/03b355bd72dc30df77189165a6194ed364754d07" 19 | }, 20 | "url": "https://api.github.com/repos/spring-projects/spring-framework/git/commits/7737c3c7e5e3ffe19e6cc151f1929982f5bba35d", 21 | "comment_count": 0 22 | }, 23 | "url": "https://api.github.com/repos/spring-projects/spring-framework/commits/7737c3c7e5e3ffe19e6cc151f1929982f5bba35d", 24 | "html_url": "https://github.com/spring-projects/spring-framework/commit/7737c3c7e5e3ffe19e6cc151f1929982f5bba35d", 25 | "comments_url": "https://api.github.com/repos/spring-projects/spring-framework/commits/7737c3c7e5e3ffe19e6cc151f1929982f5bba35d/comments", 26 | "author": { 27 | "login": "jhoeller", 28 | "id": 1263688, 29 | "avatar_url": "https://avatars.githubusercontent.com/u/1263688?v=3", 30 | "gravatar_id": "", 31 | "url": "https://api.github.com/users/jhoeller", 32 | "html_url": "https://github.com/jhoeller", 33 | "followers_url": "https://api.github.com/users/jhoeller/followers", 34 | "following_url": "https://api.github.com/users/jhoeller/following{/other_user}", 35 | "gists_url": "https://api.github.com/users/jhoeller/gists{/gist_id}", 36 | "starred_url": "https://api.github.com/users/jhoeller/starred{/owner}{/repo}", 37 | "subscriptions_url": "https://api.github.com/users/jhoeller/subscriptions", 38 | "organizations_url": "https://api.github.com/users/jhoeller/orgs", 39 | "repos_url": "https://api.github.com/users/jhoeller/repos", 40 | "events_url": "https://api.github.com/users/jhoeller/events{/privacy}", 41 | "received_events_url": "https://api.github.com/users/jhoeller/received_events", 42 | "type": "User", 43 | "site_admin": false 44 | }, 45 | "committer": { 46 | "login": "jhoeller", 47 | "id": 1263688, 48 | "avatar_url": "https://avatars.githubusercontent.com/u/1263688?v=3", 49 | "gravatar_id": "", 50 | "url": "https://api.github.com/users/jhoeller", 51 | "html_url": "https://github.com/jhoeller", 52 | "followers_url": "https://api.github.com/users/jhoeller/followers", 53 | "following_url": "https://api.github.com/users/jhoeller/following{/other_user}", 54 | "gists_url": "https://api.github.com/users/jhoeller/gists{/gist_id}", 55 | "starred_url": "https://api.github.com/users/jhoeller/starred{/owner}{/repo}", 56 | "subscriptions_url": "https://api.github.com/users/jhoeller/subscriptions", 57 | "organizations_url": "https://api.github.com/users/jhoeller/orgs", 58 | "repos_url": "https://api.github.com/users/jhoeller/repos", 59 | "events_url": "https://api.github.com/users/jhoeller/events{/privacy}", 60 | "received_events_url": "https://api.github.com/users/jhoeller/received_events", 61 | "type": "User", 62 | "site_admin": false 63 | }, 64 | "parents": [ 65 | { 66 | "sha": "cbc46760b74550fc573aaa62333cfdcb721a4e7b", 67 | "url": "https://api.github.com/repos/spring-projects/spring-framework/commits/cbc46760b74550fc573aaa62333cfdcb721a4e7b", 68 | "html_url": "https://github.com/spring-projects/spring-framework/commit/cbc46760b74550fc573aaa62333cfdcb721a4e7b" 69 | } 70 | ] 71 | }, 72 | { 73 | "sha": "cbc46760b74550fc573aaa62333cfdcb721a4e7b", 74 | "commit": { 75 | "author": { 76 | "name": "Juergen Hoeller", 77 | "email": "jhoeller@pivotal.io", 78 | "date": "2016-05-02T11:54:57Z" 79 | }, 80 | "committer": { 81 | "name": "Juergen Hoeller", 82 | "email": "jhoeller@pivotal.io", 83 | "date": "2016-05-02T11:54:57Z" 84 | }, 85 | "message": "Upgrade to Jackson 2.7.4", 86 | "tree": { 87 | "sha": "327af1dff6b57df6ceaea6c68f26996443a9c3c5", 88 | "url": "https://api.github.com/repos/spring-projects/spring-framework/git/trees/327af1dff6b57df6ceaea6c68f26996443a9c3c5" 89 | }, 90 | "url": "https://api.github.com/repos/spring-projects/spring-framework/git/commits/cbc46760b74550fc573aaa62333cfdcb721a4e7b", 91 | "comment_count": 0 92 | }, 93 | "url": "https://api.github.com/repos/spring-projects/spring-framework/commits/cbc46760b74550fc573aaa62333cfdcb721a4e7b", 94 | "html_url": "https://github.com/spring-projects/spring-framework/commit/cbc46760b74550fc573aaa62333cfdcb721a4e7b", 95 | "comments_url": "https://api.github.com/repos/spring-projects/spring-framework/commits/cbc46760b74550fc573aaa62333cfdcb721a4e7b/comments", 96 | "author": { 97 | "login": "jhoeller", 98 | "id": 1263688, 99 | "avatar_url": "https://avatars.githubusercontent.com/u/1263688?v=3", 100 | "gravatar_id": "", 101 | "url": "https://api.github.com/users/jhoeller", 102 | "html_url": "https://github.com/jhoeller", 103 | "followers_url": "https://api.github.com/users/jhoeller/followers", 104 | "following_url": "https://api.github.com/users/jhoeller/following{/other_user}", 105 | "gists_url": "https://api.github.com/users/jhoeller/gists{/gist_id}", 106 | "starred_url": "https://api.github.com/users/jhoeller/starred{/owner}{/repo}", 107 | "subscriptions_url": "https://api.github.com/users/jhoeller/subscriptions", 108 | "organizations_url": "https://api.github.com/users/jhoeller/orgs", 109 | "repos_url": "https://api.github.com/users/jhoeller/repos", 110 | "events_url": "https://api.github.com/users/jhoeller/events{/privacy}", 111 | "received_events_url": "https://api.github.com/users/jhoeller/received_events", 112 | "type": "User", 113 | "site_admin": false 114 | }, 115 | "committer": { 116 | "login": "jhoeller", 117 | "id": 1263688, 118 | "avatar_url": "https://avatars.githubusercontent.com/u/1263688?v=3", 119 | "gravatar_id": "", 120 | "url": "https://api.github.com/users/jhoeller", 121 | "html_url": "https://github.com/jhoeller", 122 | "followers_url": "https://api.github.com/users/jhoeller/followers", 123 | "following_url": "https://api.github.com/users/jhoeller/following{/other_user}", 124 | "gists_url": "https://api.github.com/users/jhoeller/gists{/gist_id}", 125 | "starred_url": "https://api.github.com/users/jhoeller/starred{/owner}{/repo}", 126 | "subscriptions_url": "https://api.github.com/users/jhoeller/subscriptions", 127 | "organizations_url": "https://api.github.com/users/jhoeller/orgs", 128 | "repos_url": "https://api.github.com/users/jhoeller/repos", 129 | "events_url": "https://api.github.com/users/jhoeller/events{/privacy}", 130 | "received_events_url": "https://api.github.com/users/jhoeller/received_events", 131 | "type": "User", 132 | "site_admin": false 133 | }, 134 | "parents": [ 135 | { 136 | "sha": "07ea3745c49ec506e17dcb56107639cf36339d2c", 137 | "url": "https://api.github.com/repos/spring-projects/spring-framework/commits/07ea3745c49ec506e17dcb56107639cf36339d2c", 138 | "html_url": "https://github.com/spring-projects/spring-framework/commit/07ea3745c49ec506e17dcb56107639cf36339d2c" 139 | } 140 | ] 141 | }, 142 | { 143 | "sha": "07ea3745c49ec506e17dcb56107639cf36339d2c", 144 | "commit": { 145 | "author": { 146 | "name": "Juergen Hoeller", 147 | "email": "jhoeller@pivotal.io", 148 | "date": "2016-05-02T11:54:47Z" 149 | }, 150 | "committer": { 151 | "name": "Juergen Hoeller", 152 | "email": "jhoeller@pivotal.io", 153 | "date": "2016-05-02T11:54:47Z" 154 | }, 155 | "message": "Polishing", 156 | "tree": { 157 | "sha": "aae6620ea14cdcde222b848e05907b85b6488a2a", 158 | "url": "https://api.github.com/repos/spring-projects/spring-framework/git/trees/aae6620ea14cdcde222b848e05907b85b6488a2a" 159 | }, 160 | "url": "https://api.github.com/repos/spring-projects/spring-framework/git/commits/07ea3745c49ec506e17dcb56107639cf36339d2c", 161 | "comment_count": 0 162 | }, 163 | "url": "https://api.github.com/repos/spring-projects/spring-framework/commits/07ea3745c49ec506e17dcb56107639cf36339d2c", 164 | "html_url": "https://github.com/spring-projects/spring-framework/commit/07ea3745c49ec506e17dcb56107639cf36339d2c", 165 | "comments_url": "https://api.github.com/repos/spring-projects/spring-framework/commits/07ea3745c49ec506e17dcb56107639cf36339d2c/comments", 166 | "author": { 167 | "login": "jhoeller", 168 | "id": 1263688, 169 | "avatar_url": "https://avatars.githubusercontent.com/u/1263688?v=3", 170 | "gravatar_id": "", 171 | "url": "https://api.github.com/users/jhoeller", 172 | "html_url": "https://github.com/jhoeller", 173 | "followers_url": "https://api.github.com/users/jhoeller/followers", 174 | "following_url": "https://api.github.com/users/jhoeller/following{/other_user}", 175 | "gists_url": "https://api.github.com/users/jhoeller/gists{/gist_id}", 176 | "starred_url": "https://api.github.com/users/jhoeller/starred{/owner}{/repo}", 177 | "subscriptions_url": "https://api.github.com/users/jhoeller/subscriptions", 178 | "organizations_url": "https://api.github.com/users/jhoeller/orgs", 179 | "repos_url": "https://api.github.com/users/jhoeller/repos", 180 | "events_url": "https://api.github.com/users/jhoeller/events{/privacy}", 181 | "received_events_url": "https://api.github.com/users/jhoeller/received_events", 182 | "type": "User", 183 | "site_admin": false 184 | }, 185 | "committer": { 186 | "login": "jhoeller", 187 | "id": 1263688, 188 | "avatar_url": "https://avatars.githubusercontent.com/u/1263688?v=3", 189 | "gravatar_id": "", 190 | "url": "https://api.github.com/users/jhoeller", 191 | "html_url": "https://github.com/jhoeller", 192 | "followers_url": "https://api.github.com/users/jhoeller/followers", 193 | "following_url": "https://api.github.com/users/jhoeller/following{/other_user}", 194 | "gists_url": "https://api.github.com/users/jhoeller/gists{/gist_id}", 195 | "starred_url": "https://api.github.com/users/jhoeller/starred{/owner}{/repo}", 196 | "subscriptions_url": "https://api.github.com/users/jhoeller/subscriptions", 197 | "organizations_url": "https://api.github.com/users/jhoeller/orgs", 198 | "repos_url": "https://api.github.com/users/jhoeller/repos", 199 | "events_url": "https://api.github.com/users/jhoeller/events{/privacy}", 200 | "received_events_url": "https://api.github.com/users/jhoeller/received_events", 201 | "type": "User", 202 | "site_admin": false 203 | }, 204 | "parents": [ 205 | { 206 | "sha": "09b45d2c0feee9ebdc7c03e3768cf834bf81a195", 207 | "url": "https://api.github.com/repos/spring-projects/spring-framework/commits/09b45d2c0feee9ebdc7c03e3768cf834bf81a195", 208 | "html_url": "https://github.com/spring-projects/spring-framework/commit/09b45d2c0feee9ebdc7c03e3768cf834bf81a195" 209 | } 210 | ] 211 | }, 212 | { 213 | "sha": "09b45d2", 214 | "commit": { 215 | "author": { 216 | "name": "Stephane Nicoll", 217 | "email": "snicoll@pivotal.io", 218 | "date": "2016-05-02T11:33:05Z" 219 | }, 220 | "committer": { 221 | "name": "Stephane Nicoll", 222 | "email": "snicoll@pivotal.io", 223 | "date": "2016-05-02T11:33:05Z" 224 | }, 225 | "message": "Validate callback is always invoked in DMLC#stop\n\nThe underlying issue has been fixed in e45d33f and this commit is merely\nadding a test of the expected behaviour.\n\nIssue: SPR-14233", 226 | "tree": { 227 | "sha": "1d1b0376d9927563527d53d11d768eac82511549", 228 | "url": "https://api.github.com/repos/spring-projects/spring-framework/git/trees/1d1b0376d9927563527d53d11d768eac82511549" 229 | }, 230 | "url": "https://api.github.com/repos/spring-projects/spring-framework/git/commits/09b45d2c0feee9ebdc7c03e3768cf834bf81a195", 231 | "comment_count": 0 232 | }, 233 | "url": "https://api.github.com/repos/spring-projects/spring-framework/commits/09b45d2c0feee9ebdc7c03e3768cf834bf81a195", 234 | "html_url": "https://github.com/spring-projects/spring-framework/commit/09b45d2c0feee9ebdc7c03e3768cf834bf81a195", 235 | "comments_url": "https://api.github.com/repos/spring-projects/spring-framework/commits/09b45d2c0feee9ebdc7c03e3768cf834bf81a195/comments", 236 | "author": { 237 | "login": "snicoll", 238 | "id": 490484, 239 | "avatar_url": "https://avatars.githubusercontent.com/u/490484?v=3", 240 | "gravatar_id": "", 241 | "url": "https://api.github.com/users/snicoll", 242 | "html_url": "https://github.com/snicoll", 243 | "followers_url": "https://api.github.com/users/snicoll/followers", 244 | "following_url": "https://api.github.com/users/snicoll/following{/other_user}", 245 | "gists_url": "https://api.github.com/users/snicoll/gists{/gist_id}", 246 | "starred_url": "https://api.github.com/users/snicoll/starred{/owner}{/repo}", 247 | "subscriptions_url": "https://api.github.com/users/snicoll/subscriptions", 248 | "organizations_url": "https://api.github.com/users/snicoll/orgs", 249 | "repos_url": "https://api.github.com/users/snicoll/repos", 250 | "events_url": "https://api.github.com/users/snicoll/events{/privacy}", 251 | "received_events_url": "https://api.github.com/users/snicoll/received_events", 252 | "type": "User", 253 | "site_admin": false 254 | }, 255 | "committer": { 256 | "login": "snicoll", 257 | "id": 490484, 258 | "avatar_url": "https://avatars.githubusercontent.com/u/490484?v=3", 259 | "gravatar_id": "", 260 | "url": "https://api.github.com/users/snicoll", 261 | "html_url": "https://github.com/snicoll", 262 | "followers_url": "https://api.github.com/users/snicoll/followers", 263 | "following_url": "https://api.github.com/users/snicoll/following{/other_user}", 264 | "gists_url": "https://api.github.com/users/snicoll/gists{/gist_id}", 265 | "starred_url": "https://api.github.com/users/snicoll/starred{/owner}{/repo}", 266 | "subscriptions_url": "https://api.github.com/users/snicoll/subscriptions", 267 | "organizations_url": "https://api.github.com/users/snicoll/orgs", 268 | "repos_url": "https://api.github.com/users/snicoll/repos", 269 | "events_url": "https://api.github.com/users/snicoll/events{/privacy}", 270 | "received_events_url": "https://api.github.com/users/snicoll/received_events", 271 | "type": "User", 272 | "site_admin": false 273 | }, 274 | "parents": [ 275 | { 276 | "sha": "f83cbff543b3519c9288c1eee0ed3ae7e4c749d2", 277 | "url": "https://api.github.com/repos/spring-projects/spring-framework/commits/f83cbff543b3519c9288c1eee0ed3ae7e4c749d2", 278 | "html_url": "https://github.com/spring-projects/spring-framework/commit/f83cbff543b3519c9288c1eee0ed3ae7e4c749d2" 279 | } 280 | ] 281 | }, 282 | { 283 | "sha": "f83cbff543b3519c9288c1eee0ed3ae7e4c749d2", 284 | "commit": { 285 | "author": { 286 | "name": "Juergen Hoeller", 287 | "email": "jhoeller@pivotal.io", 288 | "date": "2016-05-02T11:01:44Z" 289 | }, 290 | "committer": { 291 | "name": "Juergen Hoeller", 292 | "email": "jhoeller@pivotal.io", 293 | "date": "2016-05-02T11:01:44Z" 294 | }, 295 | "message": "Consistent SmartLifecycle implementations\n\nIssue: SPR-14233", 296 | "tree": { 297 | "sha": "7c431cdd4c2a480d68383144f81f720736cc562e", 298 | "url": "https://api.github.com/repos/spring-projects/spring-framework/git/trees/7c431cdd4c2a480d68383144f81f720736cc562e" 299 | }, 300 | "url": "https://api.github.com/repos/spring-projects/spring-framework/git/commits/f83cbff543b3519c9288c1eee0ed3ae7e4c749d2", 301 | "comment_count": 0 302 | }, 303 | "url": "https://api.github.com/repos/spring-projects/spring-framework/commits/f83cbff543b3519c9288c1eee0ed3ae7e4c749d2", 304 | "html_url": "https://github.com/spring-projects/spring-framework/commit/f83cbff543b3519c9288c1eee0ed3ae7e4c749d2", 305 | "comments_url": "https://api.github.com/repos/spring-projects/spring-framework/commits/f83cbff543b3519c9288c1eee0ed3ae7e4c749d2/comments", 306 | "author": { 307 | "login": "jhoeller", 308 | "id": 1263688, 309 | "avatar_url": "https://avatars.githubusercontent.com/u/1263688?v=3", 310 | "gravatar_id": "", 311 | "url": "https://api.github.com/users/jhoeller", 312 | "html_url": "https://github.com/jhoeller", 313 | "followers_url": "https://api.github.com/users/jhoeller/followers", 314 | "following_url": "https://api.github.com/users/jhoeller/following{/other_user}", 315 | "gists_url": "https://api.github.com/users/jhoeller/gists{/gist_id}", 316 | "starred_url": "https://api.github.com/users/jhoeller/starred{/owner}{/repo}", 317 | "subscriptions_url": "https://api.github.com/users/jhoeller/subscriptions", 318 | "organizations_url": "https://api.github.com/users/jhoeller/orgs", 319 | "repos_url": "https://api.github.com/users/jhoeller/repos", 320 | "events_url": "https://api.github.com/users/jhoeller/events{/privacy}", 321 | "received_events_url": "https://api.github.com/users/jhoeller/received_events", 322 | "type": "User", 323 | "site_admin": false 324 | }, 325 | "committer": { 326 | "login": "jhoeller", 327 | "id": 1263688, 328 | "avatar_url": "https://avatars.githubusercontent.com/u/1263688?v=3", 329 | "gravatar_id": "", 330 | "url": "https://api.github.com/users/jhoeller", 331 | "html_url": "https://github.com/jhoeller", 332 | "followers_url": "https://api.github.com/users/jhoeller/followers", 333 | "following_url": "https://api.github.com/users/jhoeller/following{/other_user}", 334 | "gists_url": "https://api.github.com/users/jhoeller/gists{/gist_id}", 335 | "starred_url": "https://api.github.com/users/jhoeller/starred{/owner}{/repo}", 336 | "subscriptions_url": "https://api.github.com/users/jhoeller/subscriptions", 337 | "organizations_url": "https://api.github.com/users/jhoeller/orgs", 338 | "repos_url": "https://api.github.com/users/jhoeller/repos", 339 | "events_url": "https://api.github.com/users/jhoeller/events{/privacy}", 340 | "received_events_url": "https://api.github.com/users/jhoeller/received_events", 341 | "type": "User", 342 | "site_admin": false 343 | }, 344 | "parents": [ 345 | { 346 | "sha": "e45d33f9de595d7348f3b4695960cb26bd9255c0", 347 | "url": "https://api.github.com/repos/spring-projects/spring-framework/commits/e45d33f9de595d7348f3b4695960cb26bd9255c0", 348 | "html_url": "https://github.com/spring-projects/spring-framework/commit/e45d33f9de595d7348f3b4695960cb26bd9255c0" 349 | } 350 | ] 351 | } 352 | ] -------------------------------------------------------------------------------- /src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | --------------------------------------------------------------------------------