├── .editorconfig ├── .gitattributes ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Dockerfile ├── INSTALL.md ├── LICENSE ├── README.md ├── pom.xml └── src ├── assembly └── assembly.xml ├── main ├── filters │ ├── dev.properties │ └── package.properties ├── java │ └── com │ │ └── jeanchampemont │ │ └── wtfdyum │ │ ├── WTFDYUMApplication.java │ │ ├── config │ │ ├── DozerConfiguration.java │ │ ├── FeatureConfiguration.java │ │ ├── RedisConfiguration.java │ │ ├── ThymeleafConfiguration.java │ │ └── WebMvcConfiguration.java │ │ ├── dto │ │ ├── Event.java │ │ ├── Feature.java │ │ ├── Principal.java │ │ ├── User.java │ │ └── type │ │ │ ├── EventSeverityType.java │ │ │ ├── EventType.java │ │ │ └── UserLimitType.java │ │ ├── security │ │ ├── Secured.java │ │ ├── SecurityAspect.java │ │ └── SecurityException.java │ │ ├── service │ │ ├── AdminService.java │ │ ├── AuthenticationService.java │ │ ├── CronService.java │ │ ├── FeatureService.java │ │ ├── FollowersService.java │ │ ├── PrincipalService.java │ │ ├── TwitterService.java │ │ ├── UserService.java │ │ ├── feature │ │ │ ├── FeatureStrategy.java │ │ │ └── impl │ │ │ │ ├── AbstractFeatureStrategy.java │ │ │ │ ├── NotifyUnfollowFeatureStrategy.java │ │ │ │ └── TweetUnfollowFeatureStrategy.java │ │ └── impl │ │ │ ├── AdminServiceImpl.java │ │ │ ├── CronServiceImpl.java │ │ │ ├── FeatureServiceImpl.java │ │ │ ├── FollowersServiceImpl.java │ │ │ ├── PrincipalServiceImpl.java │ │ │ ├── SessionAuthenticationServiceImpl.java │ │ │ ├── TwitterServiceImpl.java │ │ │ └── UserServiceImpl.java │ │ ├── utils │ │ ├── AuthenticationInterceptor.java │ │ ├── EnumRedisSerializer.java │ │ ├── LongRedisSerializer.java │ │ ├── SessionManager.java │ │ ├── SessionProvider.java │ │ ├── TwitterFactoryHolder.java │ │ ├── WTFDYUMException.java │ │ └── WTFDYUMExceptionType.java │ │ └── web │ │ ├── AdminController.java │ │ ├── AjaxController.java │ │ ├── MainController.java │ │ └── UserController.java └── resources │ ├── application.properties │ ├── static │ ├── css │ │ ├── bootstrap-toggle.min.css │ │ ├── bootstrap.min.css │ │ └── custom.css │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ └── js │ │ ├── bootstrap-toggle.min.js │ │ ├── bootstrap.min.js │ │ ├── custom.js │ │ └── jquery-2.1.4.min.js │ └── templates │ ├── admin │ └── index.html │ ├── common │ └── template.html │ ├── error.html │ ├── index.html │ └── user │ ├── fragment │ └── events.html │ └── index.html └── test └── java └── com └── jeanchampemont └── wtfdyum ├── WTFDYUMApplicationTests.java ├── security └── SecurityAspectTest.java ├── service ├── AdminServiceTest.java ├── AuthenticationServiceTest.java ├── CronServiceTest.java ├── FeatureServiceTest.java ├── FollowersServiceTest.java ├── PrincipalServiceTest.java ├── TwitterServiceTest.java ├── UserServiceTest.java └── feature │ ├── AbstractFeatureStrategyTest.java │ ├── NotifyUnfollowFeatureStrategyTest.java │ └── TweetUnfollowFeatureStrategyTest.java ├── utils └── ResponseListMockForTest.java └── web ├── AbstractControllerTest.java ├── AjaxControllerTest.java ├── MainControllerTest.java └── UserControllerTest.java /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text=auto 3 | 4 | # Explicitly declare text files you want to always be normalized and converted 5 | # to native line endings on checkout. 6 | *.java text 7 | *.html text 8 | *.css text 9 | *.js text 10 | *.properties text 11 | *.xml text 12 | *.md text 13 | 14 | # Denote all files that are truly binary and should not be modified. 15 | *.png binary 16 | *.jpg binary 17 | *.eot binary 18 | *.svg binary 19 | *.ttf binary 20 | *.woff binary 21 | *.woff2 binary -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .metadata 3 | .settings/ 4 | .classpath 5 | .project 6 | .idea/ 7 | wtfdyum.iml 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | script: 5 | - mvn test jacoco:report coveralls:report -Dmaven.javadoc.skip=true 6 | after_success: 7 | - "curl -T /home/travis/build/jchampemont/WTFDYUM/target/wtfdyum-1.1.0-SNAPSHOT.jar -H \"X-Bintray-Publish: 1\" -H \"X-Bintray-Override: 1\" -u$BINTRAY_USER:$BINTRAY_KEY https://api.bintray.com/content/$BINTRAY_USER/wtfdyum/binary/0/wtfdyum-1.1.0-SNAPSHOT.jar" 8 | - "curl -T /home/travis/build/jchampemont/WTFDYUM/target/wtfdyum-1.1.0-SNAPSHOT-main.zip -H \"X-Bintray-Publish: 1\" -H \"X-Bintray-Override: 1\" -u$BINTRAY_USER:$BINTRAY_KEY https://api.bintray.com/content/$BINTRAY_USER/wtfdyum/binary/0/wtfdyum-1.1.0-SNAPSHOT.zip" 9 | - "curl -T /home/travis/build/jchampemont/WTFDYUM/target/wtfdyum-1.1.0-SNAPSHOT.jar.md5 -H \"X-Bintray-Publish: 1\" -H \"X-Bintray-Override: 1\" -u$BINTRAY_USER:$BINTRAY_KEY https://api.bintray.com/content/$BINTRAY_USER/wtfdyum/binary/0/wtfdyum-1.1.0-SNAPSHOT.jar.md5" 10 | - "curl -T /home/travis/build/jchampemont/WTFDYUM/target/wtfdyum-1.1.0-SNAPSHOT-main.zip.md5 -H \"X-Bintray-Publish: 1\" -H \"X-Bintray-Override: 1\" -u$BINTRAY_USER:$BINTRAY_KEY https://api.bintray.com/content/$BINTRAY_USER/wtfdyum/binary/0/wtfdyum-1.1.0-SNAPSHOT.zip.md5" 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | This project adheres to [Semantic Versioning](http://semver.org/). 5 | 6 | ## [Unreleased][unreleased] 7 | ### Added 8 | - This CHANGELOG file 9 | - Add a property to include an analytics tracking code (`wtfdyum.tracking.code`) 10 | - Add an admin screen with some statistics. 11 | 12 | ### Changed 13 | - 5 invalid credentials check will disable all account's features. 14 | 15 | ### Changed 16 | Nothing yet 17 | ### Fixed 18 | Nothing yet 19 | 20 | ## 1.0.0 - 2015-08-26 21 | ### Added 22 | - Sign in with twitter 23 | - Feature: send user a direct message to himself whenever someone stops following him 24 | - Feature: send a public tweet mentioning the unfollower whenever someone stops following the user 25 | - Documentation in INSTALL.md and README.md 26 | - Continuous Integration and test coverage thanks to Travis and Coveralls 27 | 28 | [unreleased]: https://github.com/jchampemont/WTFDYUM/compare/1.0.0...HEAD 29 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM java:8 2 | 3 | ENV VERSION=1.1.0-SNAPSHOT 4 | 5 | RUN wget https://bintray.com/artifact/download/jchampemont/wtfdyum/wtfdyum-${VERSION}.zip && \ 6 | unzip wtfdyum-${VERSION}.zip && \ 7 | cd wtfdyum-${VERSION} && \ 8 | sed -i "s/wtfdyum.redis.server=localhost/wtfdyum.redis.server=redis/g" application.properties && \ 9 | mv wtfdyum-${VERSION}.jar wtfdyum.jar 10 | 11 | WORKDIR wtfdyum-${VERSION} 12 | 13 | ENTRYPOINT java -jar wtfdyum.jar 14 | 15 | EXPOSE 8080 16 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | ### Requirements : 2 | 3 | - Java 1.8 4 | - Redis server 5 | 6 | ### Configuration : 7 | 8 | You just need to configure WTFDYUM to use your redis instance and twitter application. 9 | 10 | Your twitter application should have the following settings: 11 | 12 | - `Enable Callback Locking` should be disabled. 13 | - `Allow this application to be used to Sign in with Twitter` should be enabled. 14 | - Permissions should be `Read, Write and Access direct messages`. 15 | 16 | 17 | Edit application.properties and change the following values accordingly: 18 | 19 | - `wtfdyum.redis.server` 20 | - `wtfdyum.redis.port` 21 | - `wtfdyum.redis.database` 22 | - `wtfdyum.twitter.appId` 23 | - `wtfdyum.twitter.appSecret` 24 | - `wtfdyum.server-base-url` 25 | 26 | You might want to edit other values too, just refer to the comments in the file. 27 | 28 | **You're done !** 29 | 30 | ### Run : 31 | 32 | To run the application, just launch the following command : 33 | 34 | java -jar wtfdyum-XXX.jar 35 | 36 | The application should be running within seconds, and listening on http port 8080. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WTFDYUM 2 | ### a.k.a. Why The Fuck Did you Unfollow me ? 3 | [![Build Status](https://travis-ci.org/jchampemont/WTFDYUM.svg?branch=master)](https://travis-ci.org/jchampemont/WTFDYUM) 4 | [![Coverage Status](https://coveralls.io/repos/jchampemont/WTFDYUM/badge.svg?branch=master&service=github)](https://coveralls.io/github/jchampemont/WTFDYUM?branch=master) 5 | 6 | WTFDYUM is a small tool for twitter. Originally, it was to find out who stopped following you on Twitter, but we plan to add more features. 7 | 8 | ### Demo 9 | A demo instance is available at http://www.wtfdyu.me/. There is however a max users limitation set to 100. 10 | 11 | ### For developers 12 | #### 3 minutes installation : 13 | 14 | - Clone, fork or download the source code from this Github page 15 | - Create a twitter application here : https://apps.twitter.com/ 16 | - Install Maven 3 17 | - Edit file [src/main/filters/dev.properties](https://github.com/jchampemont/WTFDYUM/blob/master/src/main/filters/dev.properties) with your twitter app credentials 18 | - Start a redis development instance with `mvn redis:run` 19 | - Start the application with `mvn spring-boot:run` 20 | - Connect to the application at `http://127.0.0.1:8080` 21 | 22 | ### Installation for production use 23 | 24 | Download latest release from here : https://github.com/jchampemont/WTFDYUM/releases 25 | 26 | See bundled `INSTALL.md` file for installation details. 27 | 28 | ### Docker 29 | 30 | A docker image is available on Docker Hub. ([jchampemont/wtfdyum](https://hub.docker.com/r/jchampemont/wtfdyum/)) 31 | 32 | ### Bug reporting 33 | 34 | If you encounter any bug or issue while using this software, feel free to report it using GitHub issues tracker. We will definitely have a look at it. 35 | 36 | ### Contributing 37 | I am happy to accept any pull request as long as it respects the following guidelines : 38 | 39 | - Meets some basic code quality requirements 40 | - Please do some basic unit testing on your code 41 | - Use current technology stack before introducing any new dependency 42 | - You accept your code to be licensed under the Apache License, Version 2.0 43 | - WTFDYUM should stay *KISS* : Keep It Simple, Stupid. 44 | 45 | Feel free to add your name on the list of contributors below. 46 | 47 | ### Contributors 48 | 49 | - Jean Champémont 50 | 51 | ### License 52 | 53 | Copyright 2015 [Jean Champémont](http://www.jeanchampemont.com) 54 | 55 | Licensed under the Apache License, Version 2.0 (the "License"); 56 | you may not use this application except in compliance with the License. 57 | You may obtain a copy of the License at 58 | 59 | [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) 60 | 61 | Unless required by applicable law or agreed to in writing, software 62 | distributed under the License is distributed on an "AS IS" BASIS, 63 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 64 | See the License for the specific language governing permissions and 65 | limitations under the License. 66 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.jeanchampemont.wtfdyum 7 | wtfdyum 8 | 1.1.0-SNAPSHOT 9 | jar 10 | 11 | Why the fuck did you unfollow me ? 12 | Toy project sending you a Twitter's DM when someone unfollow you. 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 1.3.1.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | 1.8 24 | 19.0 25 | 5.5.1 26 | 4.0.4 27 | 2.1.0.RELEASE 28 | 29 | 3.2.0 30 | 31 | 0.7.5.201505241946 32 | 33 | 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-redis 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-starter-thymeleaf 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-starter-web 46 | 47 | 48 | org.springframework.boot 49 | spring-boot-starter-aop 50 | 51 | 52 | 53 | com.google.guava 54 | guava 55 | ${guava.version} 56 | 57 | 58 | net.sf.dozer 59 | dozer 60 | ${dozer.version} 61 | 62 | 63 | net.sf.dozer 64 | dozer-spring 65 | ${dozer.version} 66 | 67 | 68 | 69 | org.twitter4j 70 | twitter4j-core 71 | ${twitter4j.version} 72 | 73 | 74 | com.fasterxml.jackson.datatype 75 | jackson-datatype-jsr310 76 | 77 | 78 | org.thymeleaf.extras 79 | thymeleaf-extras-java8time 80 | ${thymeleaf.extras-java8time.version} 81 | 82 | 83 | 84 | org.springframework.boot 85 | spring-boot-starter-test 86 | test 87 | 88 | 89 | org.assertj 90 | assertj-core 91 | ${assertj.version} 92 | test 93 | 94 | 95 | 96 | 97 | 98 | 99 | org.springframework.boot 100 | spring-boot-maven-plugin 101 | 102 | 103 | ru.trylogic.maven.plugins 104 | redis-maven-plugin 105 | 1.4.6 106 | 107 | 108 | maven-assembly-plugin 109 | 110 | src/assembly/assembly.xml 111 | 112 | 113 | 114 | create-archive 115 | package 116 | 117 | single 118 | 119 | 120 | 121 | 122 | 123 | net.nicoulaj.maven.plugins 124 | checksum-maven-plugin 125 | 1.6 126 | 127 | 128 | 129 | artifacts 130 | 131 | 132 | 133 | 134 | 135 | 136 | src/main/filters/${build.profile.id}.properties 137 | 138 | 139 | 140 | true 141 | src/main/resources 142 | 143 | **/*.properties 144 | 145 | 146 | 147 | false 148 | src/main/resources 149 | 150 | **/*.properties 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | dev 159 | 160 | true 161 | 162 | 163 | dev 164 | 165 | 166 | 167 | package 168 | 169 | package 170 | 171 | 172 | 173 | travis 174 | 175 | 176 | env.TRAVIS 177 | true 178 | 179 | 180 | 181 | dev 182 | 183 | 184 | 185 | 186 | org.eluder.coveralls 187 | coveralls-maven-plugin 188 | 3.2.0 189 | 190 | 191 | org.jacoco 192 | jacoco-maven-plugin 193 | ${jacoco.version} 194 | 195 | 196 | prepare-agent 197 | 198 | prepare-agent 199 | 200 | 201 | 202 | 203 | 204 | com/jeanchampemont/wtfdyum/utils/* 205 | com/jeanchampemont/wtfdyum/WTFDYUMApplication.class 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | -------------------------------------------------------------------------------- /src/assembly/assembly.xml: -------------------------------------------------------------------------------- 1 | 5 | main 6 | 7 | zip 8 | 9 | 10 | 11 | ${project.basedir} 12 | / 13 | 14 | INSTALL* 15 | LICENSE* 16 | 17 | 18 | 19 | ${project.build.directory} 20 | / 21 | 22 | *.jar 23 | 24 | 25 | 26 | 27 | 28 | src/main/resources/application.properties 29 | /${project.artifactId}-${project.version} 30 | true 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/main/filters/dev.properties: -------------------------------------------------------------------------------- 1 | twitter.appId= 2 | twitter.appSecret= 3 | wtfdyum.logging.level=DEBUG 4 | -------------------------------------------------------------------------------- /src/main/filters/package.properties: -------------------------------------------------------------------------------- 1 | twitter.appId= 2 | twitter.appSecret= 3 | wtfdyum.logging.level=INFO -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/WTFDYUMApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum; 19 | 20 | import org.springframework.boot.SpringApplication; 21 | import org.springframework.boot.autoconfigure.SpringBootApplication; 22 | import org.springframework.context.annotation.Bean; 23 | import org.springframework.scheduling.annotation.EnableScheduling; 24 | 25 | import java.time.Clock; 26 | 27 | @SpringBootApplication 28 | @EnableScheduling 29 | public class WTFDYUMApplication { 30 | 31 | @Bean 32 | public Clock clock() { 33 | return Clock.systemDefaultZone(); 34 | } 35 | 36 | public static void main(final String[] args) { 37 | SpringApplication.run(WTFDYUMApplication.class, args); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/config/DozerConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.config; 19 | 20 | import org.dozer.loader.api.BeanMappingBuilder; 21 | import org.dozer.spring.DozerBeanMapperFactoryBean; 22 | import org.springframework.context.annotation.Bean; 23 | import org.springframework.context.annotation.Configuration; 24 | 25 | import java.util.Arrays; 26 | 27 | /** 28 | * Spring Configuration for Dozer related beans 29 | */ 30 | @Configuration 31 | public class DozerConfiguration { 32 | 33 | @Bean 34 | public DozerBeanMapperFactoryBean dozerBeanMapperFactoryBean() { 35 | final DozerBeanMapperFactoryBean dozerBeanMapperFactoryBean = new DozerBeanMapperFactoryBean(); 36 | dozerBeanMapperFactoryBean.setMappingBuilders(Arrays.asList(beanMappingBuilder())); 37 | return dozerBeanMapperFactoryBean; 38 | } 39 | 40 | private BeanMappingBuilder beanMappingBuilder() { 41 | return new BeanMappingBuilder() { 42 | @Override 43 | protected void configure() { 44 | // add mappings here 45 | } 46 | }; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/config/FeatureConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.config; 19 | 20 | import com.jeanchampemont.wtfdyum.dto.Feature; 21 | import com.jeanchampemont.wtfdyum.service.feature.FeatureStrategy; 22 | import org.springframework.context.annotation.Bean; 23 | import org.springframework.context.annotation.Configuration; 24 | 25 | import java.util.HashMap; 26 | import java.util.List; 27 | import java.util.Map; 28 | 29 | /** 30 | * Spring configuration for feature strategies beans 31 | */ 32 | @Configuration 33 | public class FeatureConfiguration { 34 | 35 | @Bean(name = "featureStrategies") 36 | public Map featureStrategies(final List featureStrategies) { 37 | final Map result = new HashMap<>(); 38 | for (final FeatureStrategy featureStrategy : featureStrategies) { 39 | result.put(featureStrategy.getFeature(), featureStrategy); 40 | } 41 | return result; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/config/RedisConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.config; 19 | 20 | import com.fasterxml.jackson.databind.ObjectMapper; 21 | import com.jeanchampemont.wtfdyum.dto.Event; 22 | import com.jeanchampemont.wtfdyum.dto.Feature; 23 | import com.jeanchampemont.wtfdyum.dto.Principal; 24 | import com.jeanchampemont.wtfdyum.utils.EnumRedisSerializer; 25 | import com.jeanchampemont.wtfdyum.utils.LongRedisSerializer; 26 | import org.springframework.beans.factory.annotation.Autowired; 27 | import org.springframework.context.annotation.Bean; 28 | import org.springframework.context.annotation.Configuration; 29 | import org.springframework.core.env.Environment; 30 | import org.springframework.data.redis.connection.RedisConnectionFactory; 31 | import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; 32 | import org.springframework.data.redis.core.RedisTemplate; 33 | import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; 34 | import org.springframework.data.redis.serializer.StringRedisSerializer; 35 | 36 | @Configuration 37 | public class RedisConfiguration { 38 | @Autowired 39 | private Environment env; 40 | 41 | @Bean 42 | public RedisTemplate eventRedisTemplate() { 43 | final RedisTemplate template = new RedisTemplate<>(); 44 | template.setConnectionFactory(redisConnectionFactory()); 45 | template.setKeySerializer(new StringRedisSerializer()); 46 | template.setHashKeySerializer(jsonSerializer(Event.class, objectMapper())); 47 | template.setValueSerializer(jsonSerializer(Event.class, objectMapper())); 48 | return template; 49 | } 50 | 51 | @Bean 52 | public RedisTemplate featureRedisTemplate() { 53 | final RedisTemplate template = new RedisTemplate<>(); 54 | template.setConnectionFactory(redisConnectionFactory()); 55 | template.setKeySerializer(new StringRedisSerializer()); 56 | template.setHashKeySerializer(new EnumRedisSerializer<>(Feature.class)); 57 | template.setValueSerializer(new EnumRedisSerializer<>(Feature.class)); 58 | return template; 59 | } 60 | 61 | @Bean 62 | public RedisTemplate longRedisTemplate() { 63 | final RedisTemplate template = new RedisTemplate<>(); 64 | template.setConnectionFactory(redisConnectionFactory()); 65 | template.setKeySerializer(new StringRedisSerializer()); 66 | template.setHashKeySerializer(new LongRedisSerializer()); 67 | template.setValueSerializer(new LongRedisSerializer()); 68 | return template; 69 | } 70 | 71 | @Bean 72 | public ObjectMapper objectMapper() { 73 | final ObjectMapper result = new ObjectMapper(); 74 | result.findAndRegisterModules(); 75 | return result; 76 | } 77 | 78 | @Bean 79 | public RedisTemplate principalRedisTemplate() { 80 | final RedisTemplate template = new RedisTemplate<>(); 81 | template.setConnectionFactory(redisConnectionFactory()); 82 | template.setKeySerializer(new StringRedisSerializer()); 83 | template.setHashKeySerializer(jsonSerializer(Principal.class, objectMapper())); 84 | template.setValueSerializer(jsonSerializer(Principal.class, objectMapper())); 85 | return template; 86 | } 87 | 88 | @Bean 89 | public RedisConnectionFactory redisConnectionFactory() { 90 | final JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(); 91 | jedisConnectionFactory.setHostName(env.getProperty("wtfdyum.redis.server")); 92 | jedisConnectionFactory.setPort(env.getProperty("wtfdyum.redis.port", Integer.class)); 93 | jedisConnectionFactory.setDatabase(env.getProperty("wtfdyum.redis.database", Integer.class)); 94 | return jedisConnectionFactory; 95 | } 96 | 97 | private Jackson2JsonRedisSerializer jsonSerializer(final Class clazz, final ObjectMapper mapper) { 98 | final Jackson2JsonRedisSerializer result = new Jackson2JsonRedisSerializer<>(clazz); 99 | result.setObjectMapper(mapper); 100 | return result; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/config/ThymeleafConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.config; 19 | 20 | import org.springframework.context.annotation.Bean; 21 | import org.springframework.context.annotation.Configuration; 22 | import org.thymeleaf.extras.java8time.dialect.Java8TimeDialect; 23 | 24 | /** 25 | * Spring configuration for Thymeleaf 26 | */ 27 | @Configuration 28 | public class ThymeleafConfiguration { 29 | 30 | @Bean 31 | public Java8TimeDialect java8TimeDialect() { 32 | return new Java8TimeDialect(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/config/WebMvcConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.config; 19 | 20 | import com.jeanchampemont.wtfdyum.utils.AuthenticationInterceptor; 21 | import org.springframework.beans.factory.annotation.Autowired; 22 | import org.springframework.context.annotation.Configuration; 23 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 24 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 25 | 26 | /** 27 | * Configuration for Spring MVC 28 | */ 29 | @Configuration 30 | public class WebMvcConfiguration extends WebMvcConfigurerAdapter { 31 | 32 | @Autowired 33 | private AuthenticationInterceptor authenticationInterceptor; 34 | 35 | @Override 36 | public void addInterceptors(final InterceptorRegistry registry) { 37 | registry.addInterceptor(authenticationInterceptor); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/dto/Event.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.dto; 19 | 20 | import com.fasterxml.jackson.annotation.JsonIgnore; 21 | import com.jeanchampemont.wtfdyum.dto.type.EventType; 22 | 23 | import java.time.LocalDateTime; 24 | import java.util.Objects; 25 | 26 | public class Event { 27 | 28 | public Event() { 29 | // left deliberately empty 30 | } 31 | 32 | public Event(final EventType type, final String additionalData) { 33 | this.type = type; 34 | this.additionalData = additionalData; 35 | } 36 | 37 | private EventType type; 38 | 39 | private String additionalData; 40 | 41 | private LocalDateTime creationDateTime; 42 | 43 | @Override 44 | public boolean equals(final Object obj) { 45 | if (this == obj) { 46 | return true; 47 | } 48 | if (obj == null) { 49 | return false; 50 | } 51 | if (getClass() != obj.getClass()) { 52 | return false; 53 | } 54 | final Event other = (Event) obj; 55 | if (additionalData == null) { 56 | if (other.additionalData != null) { 57 | return false; 58 | } 59 | } else if (!additionalData.equals(other.additionalData)) { 60 | return false; 61 | } 62 | if (creationDateTime == null) { 63 | if (other.creationDateTime != null) { 64 | return false; 65 | } 66 | } else if (!creationDateTime.equals(other.creationDateTime)) { 67 | return false; 68 | } 69 | if (type != other.type) { 70 | return false; 71 | } 72 | return true; 73 | } 74 | 75 | public String getAdditionalData() { 76 | return additionalData; 77 | } 78 | 79 | public LocalDateTime getCreationDateTime() { 80 | return creationDateTime; 81 | } 82 | 83 | @JsonIgnore 84 | public String getMessage() { 85 | return String.format(type.getMessage(), additionalData); 86 | } 87 | 88 | public EventType getType() { 89 | return type; 90 | } 91 | 92 | @Override 93 | public int hashCode() { 94 | return Objects.hash(this.type, this.creationDateTime, this.additionalData); 95 | } 96 | 97 | public void setAdditionalData(final String additionalData) { 98 | this.additionalData = additionalData; 99 | } 100 | 101 | public void setCreationDateTime(final LocalDateTime creationDateTime) { 102 | this.creationDateTime = creationDateTime; 103 | } 104 | 105 | public void setType(final EventType type) { 106 | this.type = type; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/dto/Feature.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.dto; 19 | 20 | public enum Feature { 21 | 22 | NOTIFY_UNFOLLOW("Send me a direct message when someone stops following me", "unfollow notifications"), 23 | 24 | TWEET_UNFOLLOW("Send a public tweet with @mention when someone stops following me", "unfollow tweet"); 25 | 26 | private Feature(final String message, final String shortName) { 27 | this.message = message; 28 | this.shortName = shortName; 29 | } 30 | 31 | private String message; 32 | 33 | private String shortName; 34 | 35 | public String getMessage() { 36 | return message; 37 | } 38 | 39 | public String getShortName() { 40 | return shortName; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/dto/Principal.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.dto; 19 | 20 | import java.util.Objects; 21 | 22 | public class Principal { 23 | 24 | public Principal() { 25 | // left deliberately empty 26 | } 27 | 28 | public Principal(final Long userId, final String token, final String tokenSecret) { 29 | this.userId = userId; 30 | this.token = token; 31 | this.tokenSecret = tokenSecret; 32 | } 33 | 34 | private Long userId; 35 | 36 | private String token; 37 | 38 | private String tokenSecret; 39 | 40 | @Override 41 | public boolean equals(final Object obj) { 42 | if (this == obj) { 43 | return true; 44 | } 45 | if (obj == null) { 46 | return false; 47 | } 48 | if (getClass() != obj.getClass()) { 49 | return false; 50 | } 51 | final Principal other = (Principal) obj; 52 | if (token == null) { 53 | if (other.token != null) { 54 | return false; 55 | } 56 | } else if (!token.equals(other.token)) { 57 | return false; 58 | } 59 | if (tokenSecret == null) { 60 | if (other.tokenSecret != null) { 61 | return false; 62 | } 63 | } else if (!tokenSecret.equals(other.tokenSecret)) { 64 | return false; 65 | } 66 | if (userId == null) { 67 | if (other.userId != null) { 68 | return false; 69 | } 70 | } else if (!userId.equals(other.userId)) { 71 | return false; 72 | } 73 | return true; 74 | } 75 | 76 | public String getToken() { 77 | return token; 78 | } 79 | 80 | public String getTokenSecret() { 81 | return tokenSecret; 82 | } 83 | 84 | public Long getUserId() { 85 | return userId; 86 | } 87 | 88 | @Override 89 | public int hashCode() { 90 | return Objects.hash(token, tokenSecret, userId); 91 | } 92 | 93 | public void setToken(final String token) { 94 | this.token = token; 95 | } 96 | 97 | public void setTokenSecret(final String tokenSecret) { 98 | this.tokenSecret = tokenSecret; 99 | } 100 | 101 | public void setUserId(final Long userId) { 102 | this.userId = userId; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/dto/User.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.dto; 19 | 20 | public class User { 21 | 22 | private long id; 23 | 24 | private String name; 25 | 26 | private String screenName; 27 | 28 | private String profileImageUrl; 29 | 30 | private String URL; 31 | 32 | public long getId() { 33 | return id; 34 | } 35 | 36 | public String getName() { 37 | return name; 38 | } 39 | 40 | public String getProfileImageURL() { 41 | return profileImageUrl; 42 | } 43 | 44 | public String getScreenName() { 45 | return screenName; 46 | } 47 | 48 | public String getURL() { 49 | return URL; 50 | } 51 | 52 | public void setId(final long id) { 53 | this.id = id; 54 | } 55 | 56 | public void setName(final String name) { 57 | this.name = name; 58 | } 59 | 60 | public void setProfileImageURL(final String profileImageUrl) { 61 | this.profileImageUrl = profileImageUrl; 62 | } 63 | 64 | public void setScreenName(final String screenName) { 65 | this.screenName = screenName; 66 | } 67 | 68 | public void setURL(final String url) { 69 | this.URL = url; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/dto/type/EventSeverityType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.dto.type; 19 | 20 | public enum EventSeverityType { 21 | INFO, 22 | WARNING, 23 | ERROR 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/dto/type/EventType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.dto.type; 19 | 20 | public enum EventType { 21 | REGISTRATION("You registered to WTFDYUM!", EventSeverityType.INFO), 22 | FEATURE_ENABLED("You enabled %s.", EventSeverityType.INFO), 23 | FEATURE_DISABLED("You disabled %s.", EventSeverityType.WARNING), 24 | UNFOLLOW("@%s stopped following you.", EventSeverityType.WARNING), 25 | TWITTER_ERROR("Error while contacting twitter.", EventSeverityType.ERROR), 26 | RATE_LIMIT_EXCEEDED("Twitter's rate limit is exceeded, you might have too many followers.", EventSeverityType.ERROR), 27 | INVALID_TWITTER_CREDENTIALS( 28 | "Could not access your twitter account. If this error persists, please verify you allowed this application.", 29 | EventSeverityType.ERROR), 30 | CREDENTIALS_INVALID_LIMIT_REACHED( 31 | "All settings where disabled due to several errors while accessing your twitter account", 32 | EventSeverityType.ERROR), 33 | UNKNOWN_ERROR("An unknown error occured", EventSeverityType.ERROR); 34 | 35 | private EventType(final String message, final EventSeverityType severity) { 36 | this.message = message; 37 | this.severity = severity; 38 | } 39 | 40 | private String message; 41 | 42 | private EventSeverityType severity; 43 | 44 | public String getMessage() { 45 | return message; 46 | } 47 | 48 | public EventSeverityType getSeverity() { 49 | return severity; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/dto/type/UserLimitType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.dto.type; 19 | 20 | public enum UserLimitType { 21 | CREDENTIALS_INVALID(5); 22 | 23 | private UserLimitType(final int limitValue) { 24 | this.limitValue = limitValue; 25 | } 26 | 27 | private int limitValue; 28 | 29 | public int getLimitValue() { 30 | return limitValue; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/security/Secured.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.security; 19 | 20 | import java.lang.annotation.ElementType; 21 | import java.lang.annotation.Retention; 22 | import java.lang.annotation.RetentionPolicy; 23 | import java.lang.annotation.Target; 24 | 25 | @Target({ ElementType.METHOD }) 26 | @Retention(RetentionPolicy.RUNTIME) 27 | /** 28 | * Mark a method as requiring the user to be logged in 29 | */ 30 | public @interface Secured { 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/security/SecurityAspect.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.security; 19 | 20 | import com.jeanchampemont.wtfdyum.service.AuthenticationService; 21 | import org.aspectj.lang.ProceedingJoinPoint; 22 | import org.aspectj.lang.annotation.Around; 23 | import org.aspectj.lang.annotation.Aspect; 24 | import org.slf4j.Logger; 25 | import org.slf4j.LoggerFactory; 26 | import org.springframework.beans.factory.annotation.Autowired; 27 | import org.springframework.stereotype.Component; 28 | 29 | @Aspect 30 | @Component 31 | public class SecurityAspect { 32 | 33 | private final Logger log = LoggerFactory.getLogger(this.getClass()); 34 | 35 | @Autowired 36 | private AuthenticationService authenticationService; 37 | 38 | /** 39 | * Around method annotated with @Secured. 40 | * 41 | */ 42 | @Around("execution(public * *(..)) && @annotation(secured)") 43 | public Object aroundSecuredMethod(final ProceedingJoinPoint pjp, final Secured secured) throws Throwable { 44 | log.trace("Secured method called: {}", pjp.getSignature()); 45 | if (!authenticationService.isAuthenticated()) { 46 | log.trace("Secured call not authorized, throwing SecurityException"); 47 | throw new SecurityException(); 48 | } 49 | log.trace("Secured call authorized"); 50 | return pjp.proceed(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/security/SecurityException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.security; 19 | 20 | import org.springframework.http.HttpStatus; 21 | import org.springframework.web.bind.annotation.ResponseStatus; 22 | 23 | @SuppressWarnings("serial") 24 | @ResponseStatus(value = HttpStatus.UNAUTHORIZED, reason = "Authentication required") 25 | public class SecurityException extends RuntimeException { 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/service/AdminService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.service; 19 | 20 | import com.jeanchampemont.wtfdyum.dto.Feature; 21 | 22 | import java.util.Map; 23 | import java.util.Set; 24 | 25 | public interface AdminService { 26 | Map countEnabledFeature(Set members); 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/service/AuthenticationService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016, 2018 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.service; 19 | 20 | import com.jeanchampemont.wtfdyum.dto.Principal; 21 | 22 | public interface AuthenticationService { 23 | 24 | /** 25 | * Authenticate. 26 | * 27 | * @param user 28 | * the user 29 | * @return the connected userId 30 | */ 31 | Long authenticate(Principal user); 32 | 33 | /** 34 | * Gets the current user id. 35 | * 36 | * @return the current user id 37 | */ 38 | Long getCurrentUserId(); 39 | 40 | /** 41 | * Checks if the current user is authenticated. 42 | * 43 | * @return the boolean 44 | */ 45 | Boolean isAuthenticated(); 46 | 47 | /** 48 | * @return whether or not the current user is an admin. 49 | */ 50 | Boolean isAdmin(); 51 | 52 | /** 53 | * Log out. 54 | */ 55 | void logOut(); 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/service/CronService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.service; 19 | 20 | public interface CronService { 21 | 22 | void checkCredentials(); 23 | 24 | void cron(); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/service/FeatureService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.service; 19 | 20 | import com.jeanchampemont.wtfdyum.dto.Event; 21 | import com.jeanchampemont.wtfdyum.dto.Feature; 22 | import com.jeanchampemont.wtfdyum.utils.WTFDYUMException; 23 | 24 | import java.util.Set; 25 | 26 | public interface FeatureService { 27 | 28 | /** 29 | * Complete cron. 30 | * 31 | * This method is called after all cron for this user have been executed 32 | * 33 | * @param userId 34 | * the user id 35 | * @param feature 36 | * the feature 37 | * @throws WTFDYUMException 38 | * the WTFDYUM exception 39 | */ 40 | void completeCron(Long userId, Feature feature) throws WTFDYUMException; 41 | 42 | /** 43 | * Method that should be executed periodically for this feature. 44 | * 45 | * @param userId 46 | * the user id 47 | * @param feature 48 | * the feature 49 | * @return the resulting events set 50 | * @throws WTFDYUMException 51 | * the WTFDYUM exception 52 | */ 53 | Set cron(Long userId, Feature feature) throws WTFDYUMException; 54 | 55 | /** 56 | * Disable the feature for this userId. 57 | * 58 | * @param userId 59 | * the user id 60 | * @param feature 61 | * the feature 62 | * @return true if the feature was enabled and has been disabled, false 63 | * otherwise 64 | */ 65 | boolean disableFeature(Long userId, Feature feature); 66 | 67 | /** 68 | * Enable the feature for this userId. 69 | * 70 | * @param userId 71 | * the user id 72 | * @param feature 73 | * the feature 74 | * @return true if the feature was disabled and has been enabled, false 75 | * otherwise 76 | */ 77 | boolean enableFeature(Long userId, Feature feature); 78 | 79 | /** 80 | * Checks if is enabled. 81 | * 82 | * @param userId 83 | * the user id 84 | * @param feature 85 | * the feature 86 | * @return whether or not this feature is enabled 87 | */ 88 | boolean isEnabled(Long userId, Feature feature); 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/service/FollowersService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.service; 19 | 20 | import java.util.Set; 21 | 22 | public interface FollowersService { 23 | Set getUnfollowers(Long userId, Set currentFollowersId); 24 | 25 | void saveFollowers(Long userId, Set followersId); 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/service/PrincipalService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.service; 19 | 20 | import com.jeanchampemont.wtfdyum.dto.Principal; 21 | 22 | import java.util.Set; 23 | 24 | public interface PrincipalService { 25 | 26 | int countMembers(); 27 | 28 | Principal get(Long id); 29 | 30 | Set getMembers(); 31 | 32 | void saveUpdate(Principal user); 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/service/TwitterService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.service; 19 | 20 | import com.jeanchampemont.wtfdyum.dto.Principal; 21 | import com.jeanchampemont.wtfdyum.dto.User; 22 | import com.jeanchampemont.wtfdyum.utils.WTFDYUMException; 23 | import twitter4j.auth.AccessToken; 24 | import twitter4j.auth.RequestToken; 25 | 26 | import java.util.List; 27 | import java.util.Optional; 28 | import java.util.Set; 29 | 30 | /** 31 | * The Interface TwitterService. This is used to interact with twitter. 32 | */ 33 | public interface TwitterService { 34 | 35 | /** 36 | * Complete signin. 37 | * 38 | * @param requestToken 39 | * the request token returned by {@link #signin(String) signin} 40 | * method 41 | * @param verifier 42 | * the oauth verifier 43 | * @return the access token to the user account 44 | * @throws WTFDYUMException 45 | * if something is wrong with Twitter communication 46 | */ 47 | AccessToken completeSignin(RequestToken requestToken, String verifier) throws WTFDYUMException; 48 | 49 | /** 50 | * Gets the followers of the specified userId. 51 | * 52 | * If the principal is present, the request is made on the behalf of 53 | * principal. Else, the request is made on the behalf of the application 54 | * 55 | * @param userId 56 | * the user id 57 | * @param principal 58 | * the principal 59 | * @return the followers 60 | * @throws WTFDYUMException 61 | * the WTFDYUM exception 62 | */ 63 | Set getFollowers(Long userId, Optional principal) throws WTFDYUMException; 64 | 65 | /** 66 | * Gets the user. 67 | * 68 | * @param principal 69 | * the principal 70 | * @param id 71 | * the id 72 | * @return the user 73 | * @throws WTFDYUMException 74 | * the WTFDYUM exception 75 | */ 76 | User getUser(Principal principal, Long id) throws WTFDYUMException; 77 | 78 | /** 79 | * Gets the users. 80 | * 81 | * @param principal 82 | * the principal 83 | * @param ids 84 | * the ids 85 | * @return the users 86 | * @throws WTFDYUMException 87 | */ 88 | List getUsers(Principal principal, long... ids) throws WTFDYUMException; 89 | 90 | /** 91 | * Send direct message. 92 | * 93 | * @param principal 94 | * the principal 95 | * @param toUserId 96 | * the to user id 97 | * @param text 98 | * the text 99 | * @throws WTFDYUMException 100 | * the WTFDYUM exception 101 | */ 102 | void sendDirectMessage(Principal principal, Long toUserId, String text) throws WTFDYUMException; 103 | 104 | /** 105 | * Signin with Twitter. 106 | * 107 | * @param path 108 | * the path of this application where twitter should redirect 109 | * after sign-on process. 110 | * @return the request token 111 | * @throws WTFDYUMException 112 | * if something is wrong with Twitter communication 113 | */ 114 | RequestToken signin(String path) throws WTFDYUMException; 115 | 116 | /** 117 | * Tweet. 118 | * 119 | * @param principal the principal 120 | * @param text the text 121 | * @throws WTFDYUMException the WTFDYUM exception 122 | */ 123 | void tweet(Principal principal, String text) throws WTFDYUMException; 124 | 125 | /** 126 | * Verify credentials. 127 | * 128 | * @param principal 129 | * the principal 130 | * @return true, if successful 131 | */ 132 | boolean verifyCredentials(Principal principal); 133 | } 134 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/service/UserService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.service; 19 | 20 | import com.jeanchampemont.wtfdyum.dto.Event; 21 | import com.jeanchampemont.wtfdyum.dto.Feature; 22 | import com.jeanchampemont.wtfdyum.dto.type.UserLimitType; 23 | 24 | import java.util.List; 25 | import java.util.Set; 26 | 27 | public interface UserService { 28 | 29 | void addEvent(Long userId, Event event); 30 | 31 | boolean applyLimit(Long userId, UserLimitType type); 32 | 33 | Set getEnabledFeatures(Long userId); 34 | 35 | List getRecentEvents(Long userId, int count); 36 | 37 | List getRecentEvents(Long userId, int count, int start); 38 | 39 | void resetLimit(Long userId, UserLimitType type); 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/service/feature/FeatureStrategy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.service.feature; 19 | 20 | import com.jeanchampemont.wtfdyum.dto.Event; 21 | import com.jeanchampemont.wtfdyum.dto.Feature; 22 | import com.jeanchampemont.wtfdyum.utils.WTFDYUMException; 23 | 24 | import java.util.Set; 25 | 26 | /** 27 | * This interface should be implemented by all feature's strategies. 28 | */ 29 | public interface FeatureStrategy { 30 | 31 | /** 32 | * Complete cron. 33 | * 34 | * This method is called after all cron for this user have been executed 35 | * 36 | * @param userId 37 | * the user id 38 | * @throws WTFDYUMException 39 | */ 40 | void completeCron(Long userId) throws WTFDYUMException; 41 | 42 | /** 43 | * Method that should be executed periodically for this feature. 44 | * 45 | * @param userId 46 | * the user id 47 | * @return the resulting events set 48 | * @throws WTFDYUMException 49 | * the WTFDYUM exception 50 | */ 51 | Set cron(Long userId) throws WTFDYUMException; 52 | 53 | /** 54 | * Disable the feature for this userId. 55 | * 56 | * @param userId 57 | * the user id 58 | * @return true if the feature was enabled and has been disabled, false 59 | * otherwise 60 | */ 61 | boolean disableFeature(Long userId); 62 | 63 | /** 64 | * Enable the feature for this userId. 65 | * 66 | * @param userId 67 | * the user id 68 | * @return true if the feature was disabled and has been enabled, false 69 | * otherwise 70 | */ 71 | boolean enableFeature(Long userId); 72 | 73 | /** 74 | * Gets the feature. 75 | * 76 | * @return the feature 77 | */ 78 | Feature getFeature(); 79 | 80 | /** 81 | * Checks for cron. 82 | * 83 | * @return whether or not this feature has a cron that should be executed 84 | * periodically 85 | */ 86 | boolean hasCron(); 87 | 88 | /** 89 | * Checks if is enabled. 90 | * 91 | * @param userId 92 | * the user id 93 | * @return whether or not this feature is enabled 94 | */ 95 | boolean isEnabled(Long userId); 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/service/feature/impl/AbstractFeatureStrategy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.service.feature.impl; 19 | 20 | import com.jeanchampemont.wtfdyum.dto.Event; 21 | import com.jeanchampemont.wtfdyum.dto.Feature; 22 | import com.jeanchampemont.wtfdyum.service.feature.FeatureStrategy; 23 | import com.jeanchampemont.wtfdyum.utils.WTFDYUMException; 24 | import org.springframework.beans.factory.annotation.Autowired; 25 | import org.springframework.data.redis.core.RedisTemplate; 26 | 27 | import java.util.Collections; 28 | import java.util.Set; 29 | 30 | public abstract class AbstractFeatureStrategy implements FeatureStrategy { 31 | 32 | private static final String FEATURES_KEY_PREFIX = "FEATURES_"; 33 | 34 | public AbstractFeatureStrategy(final Feature feature) { 35 | this.feature = feature; 36 | } 37 | 38 | @Autowired 39 | private RedisTemplate featureRedisTemplate; 40 | 41 | private final Feature feature; 42 | 43 | @Override 44 | public void completeCron(final Long userId) throws WTFDYUMException { 45 | // Explicitly doing nothing 46 | } 47 | 48 | @Override 49 | public Set cron(final Long userId) throws WTFDYUMException { 50 | // Explicitly doing nothing 51 | return Collections.emptySet(); 52 | } 53 | 54 | @Override 55 | public boolean disableFeature(final Long userId) { 56 | return featureRedisTemplate.opsForSet().remove(featuresKey(userId), feature) == 1; 57 | } 58 | 59 | @Override 60 | public boolean enableFeature(final Long userId) { 61 | return featureRedisTemplate.opsForSet().add(featuresKey(userId), feature) == 1; 62 | } 63 | 64 | @Override 65 | public Feature getFeature() { 66 | return feature; 67 | } 68 | 69 | @Override 70 | public boolean isEnabled(final Long userId) { 71 | return featureRedisTemplate.opsForSet().isMember(featuresKey(userId), feature); 72 | } 73 | 74 | private String featuresKey(final Long userId) { 75 | return new StringBuilder(FEATURES_KEY_PREFIX).append(userId.toString()).toString(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/service/feature/impl/NotifyUnfollowFeatureStrategy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.service.feature.impl; 19 | 20 | import com.google.common.primitives.Longs; 21 | import com.jeanchampemont.wtfdyum.dto.Event; 22 | import com.jeanchampemont.wtfdyum.dto.Feature; 23 | import com.jeanchampemont.wtfdyum.dto.Principal; 24 | import com.jeanchampemont.wtfdyum.dto.User; 25 | import com.jeanchampemont.wtfdyum.dto.type.EventType; 26 | import com.jeanchampemont.wtfdyum.service.FollowersService; 27 | import com.jeanchampemont.wtfdyum.service.PrincipalService; 28 | import com.jeanchampemont.wtfdyum.service.TwitterService; 29 | import com.jeanchampemont.wtfdyum.utils.WTFDYUMException; 30 | import org.springframework.beans.factory.annotation.Autowired; 31 | import org.springframework.beans.factory.annotation.Value; 32 | import org.springframework.stereotype.Service; 33 | 34 | import java.util.HashSet; 35 | import java.util.List; 36 | import java.util.Optional; 37 | import java.util.Set; 38 | 39 | @Service 40 | public class NotifyUnfollowFeatureStrategy extends AbstractFeatureStrategy { 41 | 42 | @Autowired 43 | public NotifyUnfollowFeatureStrategy(final PrincipalService principalService, 44 | final FollowersService followersService, 45 | final TwitterService twitterService, 46 | @Value("${wtfdyum.unfollow.dm-text}") final String unfollowDMText) { 47 | super(Feature.NOTIFY_UNFOLLOW); 48 | this.principalService = principalService; 49 | this.followersService = followersService; 50 | this.twitterService = twitterService; 51 | this.unfollowDMText = unfollowDMText; 52 | } 53 | 54 | private final PrincipalService principalService; 55 | 56 | private final FollowersService followersService; 57 | 58 | private final TwitterService twitterService; 59 | 60 | private final String unfollowDMText; 61 | 62 | @Override 63 | public void completeCron(final Long userId) throws WTFDYUMException { 64 | final Principal principal = principalService.get(userId); 65 | final Set followers = twitterService.getFollowers(userId, Optional.ofNullable(principal)); 66 | followersService.saveFollowers(userId, followers); 67 | } 68 | 69 | @Override 70 | public Set cron(final Long userId) throws WTFDYUMException { 71 | final Set result = new HashSet<>(); 72 | final Principal principal = principalService.get(userId); 73 | final Set followers = twitterService.getFollowers(userId, Optional.ofNullable(principal)); 74 | 75 | final Set unfollowersId = followersService.getUnfollowers(userId, followers); 76 | 77 | final List unfollowers = twitterService.getUsers(principal, Longs.toArray(unfollowersId)); 78 | for (final User unfollower : unfollowers) { 79 | result.add(new Event(EventType.UNFOLLOW, unfollower.getScreenName())); 80 | twitterService.sendDirectMessage(principal, userId, 81 | String.format(unfollowDMText, unfollower.getScreenName())); 82 | } 83 | return result; 84 | } 85 | 86 | @Override 87 | public boolean hasCron() { 88 | return true; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/service/feature/impl/TweetUnfollowFeatureStrategy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.service.feature.impl; 19 | 20 | import com.google.common.primitives.Longs; 21 | import com.jeanchampemont.wtfdyum.dto.Event; 22 | import com.jeanchampemont.wtfdyum.dto.Feature; 23 | import com.jeanchampemont.wtfdyum.dto.Principal; 24 | import com.jeanchampemont.wtfdyum.dto.User; 25 | import com.jeanchampemont.wtfdyum.dto.type.EventType; 26 | import com.jeanchampemont.wtfdyum.service.FollowersService; 27 | import com.jeanchampemont.wtfdyum.service.PrincipalService; 28 | import com.jeanchampemont.wtfdyum.service.TwitterService; 29 | import com.jeanchampemont.wtfdyum.utils.WTFDYUMException; 30 | import org.springframework.beans.factory.annotation.Autowired; 31 | import org.springframework.beans.factory.annotation.Value; 32 | import org.springframework.stereotype.Service; 33 | 34 | import java.util.HashSet; 35 | import java.util.List; 36 | import java.util.Optional; 37 | import java.util.Set; 38 | 39 | @Service 40 | public class TweetUnfollowFeatureStrategy extends AbstractFeatureStrategy { 41 | 42 | @Autowired 43 | public TweetUnfollowFeatureStrategy(final PrincipalService principalService, 44 | final FollowersService followersService, 45 | final TwitterService twitterService, 46 | @Value("${wtfdyum.unfollow.tweet-text}") final String unfollowTweetText) { 47 | super(Feature.TWEET_UNFOLLOW); 48 | this.principalService = principalService; 49 | this.followersService = followersService; 50 | this.twitterService = twitterService; 51 | this.unfollowTweetText = unfollowTweetText; 52 | } 53 | 54 | private final PrincipalService principalService; 55 | 56 | private final FollowersService followersService; 57 | 58 | private final TwitterService twitterService; 59 | 60 | private final String unfollowTweetText; 61 | 62 | @Override 63 | public void completeCron(final Long userId) throws WTFDYUMException { 64 | final Principal principal = principalService.get(userId); 65 | final Set followers = twitterService.getFollowers(userId, Optional.ofNullable(principal)); 66 | followersService.saveFollowers(userId, followers); 67 | } 68 | 69 | @Override 70 | public Set cron(final Long userId) throws WTFDYUMException { 71 | final Set result = new HashSet<>(); 72 | final Principal principal = principalService.get(userId); 73 | final Set followers = twitterService.getFollowers(userId, Optional.ofNullable(principal)); 74 | 75 | final Set unfollowersId = followersService.getUnfollowers(userId, followers); 76 | 77 | final List unfollowers = twitterService.getUsers(principal, Longs.toArray(unfollowersId)); 78 | for (final User unfollower : unfollowers) { 79 | result.add(new Event(EventType.UNFOLLOW, unfollower.getScreenName())); 80 | twitterService.tweet(principal, 81 | String.format(unfollowTweetText, unfollower.getScreenName())); 82 | } 83 | return result; 84 | } 85 | 86 | @Override 87 | public boolean hasCron() { 88 | return true; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/service/impl/AdminServiceImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.service.impl; 19 | 20 | import com.jeanchampemont.wtfdyum.dto.Feature; 21 | import com.jeanchampemont.wtfdyum.service.AdminService; 22 | import com.jeanchampemont.wtfdyum.service.FeatureService; 23 | import org.springframework.beans.factory.annotation.Autowired; 24 | import org.springframework.stereotype.Service; 25 | 26 | import java.util.HashMap; 27 | import java.util.Map; 28 | import java.util.Set; 29 | 30 | @Service 31 | public class AdminServiceImpl implements AdminService { 32 | 33 | @Autowired 34 | public AdminServiceImpl(FeatureService featureService) { 35 | this.featureService = featureService; 36 | } 37 | 38 | private final FeatureService featureService; 39 | 40 | @Override 41 | public Map countEnabledFeature(Set members) { 42 | Map result = new HashMap<>(); 43 | for (Feature f : Feature.values()) { 44 | int count = 0; 45 | for (Long member : members) { 46 | if (featureService.isEnabled(member, f)) { 47 | count++; 48 | } 49 | } 50 | result.put(f, count); 51 | } 52 | return result; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/service/impl/CronServiceImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.service.impl; 19 | 20 | import com.jeanchampemont.wtfdyum.dto.Event; 21 | import com.jeanchampemont.wtfdyum.dto.Feature; 22 | import com.jeanchampemont.wtfdyum.dto.Principal; 23 | import com.jeanchampemont.wtfdyum.dto.type.EventType; 24 | import com.jeanchampemont.wtfdyum.dto.type.UserLimitType; 25 | import com.jeanchampemont.wtfdyum.service.*; 26 | import com.jeanchampemont.wtfdyum.utils.WTFDYUMException; 27 | import com.jeanchampemont.wtfdyum.utils.WTFDYUMExceptionType; 28 | import org.slf4j.Logger; 29 | import org.slf4j.LoggerFactory; 30 | import org.springframework.beans.factory.annotation.Autowired; 31 | import org.springframework.scheduling.annotation.Scheduled; 32 | import org.springframework.stereotype.Service; 33 | import org.springframework.util.StopWatch; 34 | 35 | import java.util.HashSet; 36 | import java.util.Set; 37 | 38 | @Service 39 | public class CronServiceImpl implements CronService { 40 | 41 | @Autowired 42 | public CronServiceImpl(final PrincipalService principalService, 43 | final UserService userService, 44 | final TwitterService twitterService, 45 | final FeatureService featureService) { 46 | this.principalService = principalService; 47 | this.userService = userService; 48 | this.twitterService = twitterService; 49 | this.featureService = featureService; 50 | } 51 | 52 | private final Logger log = LoggerFactory.getLogger(this.getClass()); 53 | 54 | private final PrincipalService principalService; 55 | 56 | private final UserService userService; 57 | 58 | private final TwitterService twitterService; 59 | 60 | private final FeatureService featureService; 61 | 62 | @Override 63 | @Scheduled(fixedDelayString = "${wtfdyum.credentials-check-delay}", initialDelay = 120000L) 64 | public void checkCredentials() { 65 | log.debug("Checking credentials..."); 66 | final StopWatch watch = new StopWatch(); 67 | watch.start(); 68 | final Set members = principalService.getMembers(); 69 | 70 | for (final Long userId : members) { 71 | final Principal principal = principalService.get(userId); 72 | 73 | if (!twitterService.verifyCredentials(principal)) { 74 | userService.applyLimit(userId, UserLimitType.CREDENTIALS_INVALID); 75 | userService.addEvent(userId, new Event(EventType.INVALID_TWITTER_CREDENTIALS, "")); 76 | } else { 77 | userService.resetLimit(userId, UserLimitType.CREDENTIALS_INVALID); 78 | } 79 | } 80 | watch.stop(); 81 | log.debug("Finished checking credentials in {} ms", watch.getTotalTimeMillis()); 82 | } 83 | 84 | @Override 85 | @Scheduled(fixedDelayString = "${wtfdyum.unfollow-check-delay}", initialDelay = 120000L) 86 | public void cron() { 87 | log.debug("Starting cron method..."); 88 | final StopWatch watch = new StopWatch(); 89 | watch.start(); 90 | final Set members = principalService.getMembers(); 91 | 92 | for (final Long userId : members) { 93 | try { 94 | final Set enabledFeatures = userService.getEnabledFeatures(userId); 95 | final Set events = new HashSet<>(); 96 | for (final Feature enabledFeature : enabledFeatures) { 97 | final Set es = featureService.cron(userId, enabledFeature); 98 | events.addAll(es); 99 | } 100 | 101 | for (final Event e : events) { 102 | userService.addEvent(userId, e); 103 | } 104 | 105 | for (final Feature enabledFeature : enabledFeatures) { 106 | featureService.completeCron(userId, enabledFeature); 107 | } 108 | } catch (final WTFDYUMException e) { 109 | if (WTFDYUMExceptionType.GET_FOLLOWERS_RATE_LIMIT_EXCEEDED.equals(e.getType())) { 110 | userService.addEvent(userId, new Event(EventType.RATE_LIMIT_EXCEEDED, null)); 111 | log.warn("GET_FOLLOWERS_RATE_LIMIT_EXCEEDED for user id {}", userId); 112 | } else { 113 | userService.addEvent(userId, new Event(EventType.TWITTER_ERROR, null)); 114 | log.error("Twitter error for userId " + userId, e.getCause()); 115 | } 116 | } catch (final Throwable t) { 117 | userService.addEvent(userId, new Event(EventType.UNKNOWN_ERROR, null)); 118 | log.error("Unknown error for user id " + userId, t); 119 | } 120 | } 121 | watch.stop(); 122 | log.debug("Finished cron in {} ms", watch.getTotalTimeMillis()); 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/service/impl/FeatureServiceImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.service.impl; 19 | 20 | import com.jeanchampemont.wtfdyum.dto.Event; 21 | import com.jeanchampemont.wtfdyum.dto.Feature; 22 | import com.jeanchampemont.wtfdyum.service.FeatureService; 23 | import com.jeanchampemont.wtfdyum.service.feature.FeatureStrategy; 24 | import com.jeanchampemont.wtfdyum.utils.WTFDYUMException; 25 | import org.springframework.stereotype.Service; 26 | 27 | import javax.annotation.Resource; 28 | import java.util.Map; 29 | import java.util.Set; 30 | 31 | @Service 32 | public class FeatureServiceImpl implements FeatureService { 33 | 34 | public FeatureServiceImpl() { 35 | // left deliberately empty 36 | } 37 | 38 | public FeatureServiceImpl(final Map featureStrategies) { 39 | this.featureStrategies = featureStrategies; 40 | } 41 | 42 | @Resource 43 | private Map featureStrategies; 44 | 45 | @Override 46 | public void completeCron(final Long userId, final Feature feature) throws WTFDYUMException { 47 | featureStrategies.get(feature).completeCron(userId); 48 | } 49 | 50 | @Override 51 | public Set cron(final Long userId, final Feature feature) throws WTFDYUMException { 52 | return featureStrategies.get(feature).cron(userId); 53 | } 54 | 55 | @Override 56 | public boolean disableFeature(final Long userId, final Feature feature) { 57 | return featureStrategies.get(feature).disableFeature(userId); 58 | } 59 | 60 | @Override 61 | public boolean enableFeature(final Long userId, final Feature feature) { 62 | return featureStrategies.get(feature).enableFeature(userId); 63 | } 64 | 65 | @Override 66 | public boolean isEnabled(final Long userId, final Feature feature) { 67 | return featureStrategies.get(feature).isEnabled(userId); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/service/impl/FollowersServiceImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.service.impl; 19 | 20 | import com.jeanchampemont.wtfdyum.service.FollowersService; 21 | import org.springframework.beans.factory.annotation.Autowired; 22 | import org.springframework.data.redis.core.RedisTemplate; 23 | import org.springframework.stereotype.Service; 24 | 25 | import java.util.Set; 26 | 27 | @Service 28 | public class FollowersServiceImpl implements FollowersService { 29 | 30 | private static final String FOLLOWERS_KEY_PREFIX = "FOLLOWERS_"; 31 | 32 | private static final String TEMP_FOLLOWERS_KEY_PREFIX = "TEMP_FOLLOWERS_"; 33 | 34 | @Autowired 35 | public FollowersServiceImpl(final RedisTemplate longRedisTemplate) { 36 | this.longRedisTemplate = longRedisTemplate; 37 | } 38 | 39 | private final RedisTemplate longRedisTemplate; 40 | 41 | @Override 42 | public Set getUnfollowers(final Long userId, final Set currentFollowersId) { 43 | longRedisTemplate.opsForSet().add(tempFollowersKey(userId), 44 | currentFollowersId.toArray(new Long[currentFollowersId.size()])); 45 | 46 | final Set unfollowers = longRedisTemplate.opsForSet().difference(followersKey(userId), 47 | tempFollowersKey(userId)); 48 | longRedisTemplate.delete(tempFollowersKey(userId)); 49 | return unfollowers; 50 | } 51 | 52 | @Override 53 | public void saveFollowers(final Long userId, final Set followersId) { 54 | longRedisTemplate.delete(followersKey(userId)); 55 | longRedisTemplate.opsForSet().add(followersKey(userId), followersId.toArray(new Long[followersId.size()])); 56 | } 57 | 58 | private String followersKey(final Long userId) { 59 | return new StringBuilder(FOLLOWERS_KEY_PREFIX).append(userId.toString()).toString(); 60 | } 61 | 62 | private String tempFollowersKey(final Long userId) { 63 | return new StringBuilder(TEMP_FOLLOWERS_KEY_PREFIX).append(userId.toString()).toString(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/service/impl/PrincipalServiceImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.service.impl; 19 | 20 | import com.google.common.base.Preconditions; 21 | import com.jeanchampemont.wtfdyum.dto.Principal; 22 | import com.jeanchampemont.wtfdyum.service.PrincipalService; 23 | import org.springframework.beans.factory.annotation.Autowired; 24 | import org.springframework.data.redis.core.RedisTemplate; 25 | import org.springframework.stereotype.Service; 26 | 27 | import java.util.Set; 28 | 29 | @Service 30 | public class PrincipalServiceImpl implements PrincipalService { 31 | 32 | private static final String MEMBERS_KEY = "MEMBERS"; 33 | 34 | @Autowired 35 | public PrincipalServiceImpl(final RedisTemplate principalRedisTemplate, 36 | final RedisTemplate longRedisTemplate) { 37 | this.principalRedisTemplate = principalRedisTemplate; 38 | this.longRedisTemplate = longRedisTemplate; 39 | } 40 | 41 | private final RedisTemplate principalRedisTemplate; 42 | 43 | private final RedisTemplate longRedisTemplate; 44 | 45 | @Override 46 | public int countMembers() { 47 | return longRedisTemplate.opsForSet().size(MEMBERS_KEY).intValue(); 48 | } 49 | 50 | @Override 51 | public Principal get(final Long id) { 52 | Preconditions.checkNotNull(id); 53 | return principalRedisTemplate.opsForValue().get(id.toString()); 54 | } 55 | 56 | @Override 57 | public Set getMembers() { 58 | return longRedisTemplate.opsForSet().members(MEMBERS_KEY); 59 | } 60 | 61 | @Override 62 | public void saveUpdate(final Principal user) { 63 | Preconditions.checkNotNull(user); 64 | Preconditions.checkNotNull(user.getUserId()); 65 | 66 | principalRedisTemplate.opsForValue().set(user.getUserId().toString(), user); 67 | longRedisTemplate.opsForSet().add(MEMBERS_KEY, user.getUserId()); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/service/impl/SessionAuthenticationServiceImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016, 2018 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.service.impl; 19 | 20 | import com.google.common.base.Preconditions; 21 | import com.jeanchampemont.wtfdyum.dto.Principal; 22 | import com.jeanchampemont.wtfdyum.service.AuthenticationService; 23 | import com.jeanchampemont.wtfdyum.utils.SessionProvider; 24 | import org.springframework.beans.factory.annotation.Autowired; 25 | import org.springframework.beans.factory.annotation.Value; 26 | import org.springframework.stereotype.Service; 27 | 28 | import javax.servlet.http.HttpSession; 29 | import java.util.Objects; 30 | 31 | @Service 32 | public class SessionAuthenticationServiceImpl implements AuthenticationService { 33 | 34 | private static final String CURRENT_USER_ID = "CURRENT_USER_ID"; 35 | 36 | @Autowired 37 | public SessionAuthenticationServiceImpl(SessionProvider sessionProvider, @Value("${wtfdyum.admin.twitterId}") Long adminTwitterId) { 38 | this.sessionProvider = sessionProvider; 39 | this.adminTwitterId = adminTwitterId; 40 | } 41 | 42 | private final SessionProvider sessionProvider; 43 | private final Long adminTwitterId; 44 | 45 | @Override 46 | public Long authenticate(final Principal user) { 47 | Preconditions.checkNotNull(user); 48 | Preconditions.checkNotNull(user.getUserId()); 49 | 50 | session().setAttribute(CURRENT_USER_ID, user.getUserId()); 51 | return user.getUserId(); 52 | } 53 | 54 | @Override 55 | public Long getCurrentUserId() { 56 | return (Long) session().getAttribute(CURRENT_USER_ID); 57 | } 58 | 59 | @Override 60 | public Boolean isAuthenticated() { 61 | return getCurrentUserId() != null; 62 | } 63 | 64 | @Override 65 | public Boolean isAdmin() { 66 | return Objects.equals(getCurrentUserId(), adminTwitterId); 67 | } 68 | 69 | @Override 70 | public void logOut() { 71 | session().removeAttribute(CURRENT_USER_ID); 72 | } 73 | 74 | private HttpSession session() { 75 | return sessionProvider.getSession(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/service/impl/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.service.impl; 19 | 20 | import com.jeanchampemont.wtfdyum.dto.Event; 21 | import com.jeanchampemont.wtfdyum.dto.Feature; 22 | import com.jeanchampemont.wtfdyum.dto.type.EventType; 23 | import com.jeanchampemont.wtfdyum.dto.type.UserLimitType; 24 | import com.jeanchampemont.wtfdyum.service.FeatureService; 25 | import com.jeanchampemont.wtfdyum.service.UserService; 26 | import org.springframework.beans.factory.annotation.Autowired; 27 | import org.springframework.data.redis.core.RedisTemplate; 28 | import org.springframework.stereotype.Service; 29 | 30 | import java.time.Clock; 31 | import java.time.LocalDateTime; 32 | import java.util.List; 33 | import java.util.Set; 34 | 35 | @Service 36 | public class UserServiceImpl implements UserService { 37 | 38 | private static final String EVENTS_KEY_PREFIX = "EVENTS_"; 39 | 40 | private static final String FEATURES_KEY_PREFIX = "FEATURES_"; 41 | 42 | @Autowired 43 | public UserServiceImpl(final RedisTemplate eventRedisTemplate, 44 | final RedisTemplate featureRedisTemplate, 45 | final RedisTemplate longRedisTemplate, 46 | final FeatureService featureService, 47 | final Clock clock) { 48 | this.eventRedisTemplate = eventRedisTemplate; 49 | this.featureRedisTemplate = featureRedisTemplate; 50 | this.longRedisTemplate = longRedisTemplate; 51 | this.featureService = featureService; 52 | this.clock = clock; 53 | } 54 | 55 | private final RedisTemplate eventRedisTemplate; 56 | 57 | private final RedisTemplate featureRedisTemplate; 58 | 59 | private final RedisTemplate longRedisTemplate; 60 | 61 | private final FeatureService featureService; 62 | 63 | private final Clock clock; 64 | 65 | @Override 66 | public void addEvent(final Long userId, final Event event) { 67 | event.setCreationDateTime(LocalDateTime.now(clock)); 68 | eventRedisTemplate.opsForList().leftPush(eventsKey(userId), event); 69 | } 70 | 71 | @Override 72 | public boolean applyLimit(final Long userId, final UserLimitType type) { 73 | final boolean reached = longRedisTemplate.opsForValue().increment(limitKey(userId, type), 1L) >= type 74 | .getLimitValue(); 75 | if (reached) { 76 | for (final Feature f : Feature.values()) { 77 | featureService.disableFeature(userId, f); 78 | } 79 | addEvent(userId, new Event(EventType.CREDENTIALS_INVALID_LIMIT_REACHED, "")); 80 | } 81 | return reached; 82 | } 83 | 84 | @Override 85 | public Set getEnabledFeatures(final Long userId) { 86 | return featureRedisTemplate.opsForSet().members(featuresKey(userId)); 87 | } 88 | 89 | @Override 90 | public List getRecentEvents(final Long userId, final int count) { 91 | return getRecentEvents(userId, count, 0); 92 | } 93 | 94 | @Override 95 | public List getRecentEvents(Long userId, int count, int start) { 96 | return eventRedisTemplate.opsForList().range(eventsKey(userId), start, start + count); 97 | } 98 | 99 | @Override 100 | public void resetLimit(final Long userId, final UserLimitType type) { 101 | longRedisTemplate.delete(limitKey(userId, type)); 102 | } 103 | 104 | private String eventsKey(final Long userId) { 105 | return new StringBuilder(EVENTS_KEY_PREFIX).append(userId.toString()).toString(); 106 | } 107 | 108 | private String featuresKey(final Long userId) { 109 | return new StringBuilder(FEATURES_KEY_PREFIX).append(userId.toString()).toString(); 110 | } 111 | 112 | private String limitKey(final Long userId, final UserLimitType type) { 113 | return new StringBuilder(type.name()).append("_").append(userId.toString()).toString(); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/utils/AuthenticationInterceptor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016, 2018 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.utils; 19 | 20 | import com.jeanchampemont.wtfdyum.dto.Principal; 21 | import com.jeanchampemont.wtfdyum.service.AuthenticationService; 22 | import com.jeanchampemont.wtfdyum.service.PrincipalService; 23 | import org.springframework.beans.factory.annotation.Autowired; 24 | import org.springframework.stereotype.Component; 25 | import org.springframework.web.servlet.ModelAndView; 26 | import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; 27 | 28 | import javax.servlet.http.HttpServletRequest; 29 | import javax.servlet.http.HttpServletResponse; 30 | 31 | /** 32 | * The Class AuthenticationInterceptor. This class: 33 | * 34 | * - adds authentication information in each model, for the templates to use. - 35 | * add the principal to the SessionManager 36 | */ 37 | @Component 38 | public class AuthenticationInterceptor extends HandlerInterceptorAdapter { 39 | 40 | @Autowired 41 | private AuthenticationService authenticationService; 42 | 43 | @Autowired 44 | private PrincipalService principalService; 45 | 46 | @Override 47 | public void postHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler, 48 | final ModelAndView modelAndView) throws Exception { 49 | if (modelAndView != null) { 50 | modelAndView.getModel().put("authenticated", authenticationService.isAuthenticated()); 51 | modelAndView.getModel().put("admin", authenticationService.isAdmin()); 52 | } 53 | SessionManager.setPrincipal(null); 54 | super.postHandle(request, response, handler, modelAndView); 55 | } 56 | 57 | @Override 58 | public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) 59 | throws Exception { 60 | final Long currentUserId = authenticationService.getCurrentUserId(); 61 | if (currentUserId != null) { 62 | final Principal principal = principalService.get(currentUserId); 63 | SessionManager.setPrincipal(principal); 64 | } 65 | return super.preHandle(request, response, handler); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/utils/EnumRedisSerializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.utils; 19 | 20 | import org.springframework.data.redis.serializer.RedisSerializer; 21 | import org.springframework.data.redis.serializer.SerializationException; 22 | 23 | import java.nio.charset.Charset; 24 | 25 | public class EnumRedisSerializer> implements RedisSerializer { 26 | 27 | public EnumRedisSerializer(final Class enumType) { 28 | charset = Charset.forName("UTF8"); 29 | this.enumType = enumType; 30 | } 31 | 32 | private final Charset charset; 33 | 34 | private final Class enumType; 35 | 36 | @Override 37 | public T deserialize(final byte[] bytes) throws SerializationException { 38 | return (bytes == null ? null : Enum.valueOf(enumType, new String(bytes, charset))); 39 | } 40 | 41 | @Override 42 | public byte[] serialize(final T val) throws SerializationException { 43 | return (val == null ? null : val.name().getBytes(charset)); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/utils/LongRedisSerializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.utils; 19 | 20 | import org.springframework.data.redis.serializer.RedisSerializer; 21 | import org.springframework.data.redis.serializer.SerializationException; 22 | 23 | import java.nio.charset.Charset; 24 | 25 | public class LongRedisSerializer implements RedisSerializer { 26 | 27 | public LongRedisSerializer() { 28 | this(Charset.forName("UTF8")); 29 | } 30 | 31 | public LongRedisSerializer(final Charset charset) { 32 | this.charset = charset; 33 | } 34 | 35 | private final Charset charset; 36 | 37 | @Override 38 | public Long deserialize(final byte[] bytes) throws SerializationException { 39 | return (bytes == null ? null : Long.parseLong(new String(bytes, charset))); 40 | } 41 | 42 | @Override 43 | public byte[] serialize(final Long val) throws SerializationException { 44 | return (val == null ? null : val.toString().getBytes(charset)); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/utils/SessionManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.utils; 19 | 20 | import com.jeanchampemont.wtfdyum.dto.Principal; 21 | 22 | public class SessionManager { 23 | 24 | private static ThreadLocal principal = new ThreadLocal<>(); 25 | 26 | public static Principal getPrincipal() { 27 | return principal.get(); 28 | } 29 | 30 | public static void setPrincipal(final Principal principal) { 31 | if (principal == null) { 32 | SessionManager.principal.remove(); 33 | } else { 34 | SessionManager.principal.set(principal); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/utils/SessionProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.jeanchampemont.wtfdyum.utils; 20 | 21 | import org.springframework.stereotype.Component; 22 | import org.springframework.web.context.request.RequestContextHolder; 23 | import org.springframework.web.context.request.ServletRequestAttributes; 24 | 25 | import javax.servlet.http.HttpSession; 26 | 27 | /** 28 | * Utility class to provide session. 29 | */ 30 | @Component 31 | public class SessionProvider { 32 | 33 | public HttpSession getSession() { 34 | return ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest().getSession(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/utils/TwitterFactoryHolder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.utils; 19 | 20 | import org.springframework.stereotype.Component; 21 | import twitter4j.Twitter; 22 | import twitter4j.TwitterFactory; 23 | 24 | /** 25 | * Simple Holder of TwitterFactory to facilitate mocking. (TwitterFactory is 26 | * final...) 27 | */ 28 | @Component 29 | public class TwitterFactoryHolder { 30 | 31 | public TwitterFactoryHolder() { 32 | twitterFactory = new TwitterFactory(); 33 | } 34 | 35 | private final TwitterFactory twitterFactory; 36 | 37 | public Twitter getInstance() { 38 | return twitterFactory.getInstance(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/utils/WTFDYUMException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.utils; 19 | 20 | import org.springframework.http.HttpStatus; 21 | import org.springframework.web.bind.annotation.ResponseStatus; 22 | 23 | /** 24 | * The Class WTFDYUMException. This class simply wrap exceptions. 25 | */ 26 | @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR, reason = "WTFDYUMException") 27 | public class WTFDYUMException extends Exception { 28 | 29 | private static final long serialVersionUID = 4147162490508812242L; 30 | 31 | public WTFDYUMException(final Exception e, final WTFDYUMExceptionType type) { 32 | super(e); 33 | this.type = type; 34 | } 35 | 36 | public WTFDYUMException(final WTFDYUMExceptionType type) { 37 | this.type = type; 38 | } 39 | 40 | private final WTFDYUMExceptionType type; 41 | 42 | public WTFDYUMExceptionType getType() { 43 | return type; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/utils/WTFDYUMExceptionType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.utils; 19 | 20 | public enum WTFDYUMExceptionType { 21 | 22 | TWITTER_ERROR("Something went wrong while communicating with Twitter..."), 23 | 24 | MEMBER_LIMIT_EXCEEDED("This application cannot accept more members"), 25 | 26 | GET_FOLLOWERS_RATE_LIMIT_EXCEEDED( 27 | "You have too many followers for this application to work properly. (More than 75.000...)"); 28 | 29 | private WTFDYUMExceptionType(final String message) { 30 | this.message = message; 31 | } 32 | 33 | private String message; 34 | 35 | public String getMessage() { 36 | return message; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/web/AdminController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.web; 19 | 20 | import com.jeanchampemont.wtfdyum.dto.Feature; 21 | import com.jeanchampemont.wtfdyum.security.Secured; 22 | import com.jeanchampemont.wtfdyum.service.AdminService; 23 | import com.jeanchampemont.wtfdyum.service.AuthenticationService; 24 | import com.jeanchampemont.wtfdyum.service.PrincipalService; 25 | import com.jeanchampemont.wtfdyum.service.TwitterService; 26 | import com.jeanchampemont.wtfdyum.utils.SessionManager; 27 | import com.jeanchampemont.wtfdyum.utils.WTFDYUMException; 28 | import org.springframework.beans.factory.annotation.Autowired; 29 | import org.springframework.stereotype.Controller; 30 | import org.springframework.web.bind.annotation.RequestMapping; 31 | import org.springframework.web.bind.annotation.RequestMethod; 32 | import org.springframework.web.servlet.ModelAndView; 33 | 34 | import java.util.Map; 35 | import java.util.stream.Collectors; 36 | 37 | import static java.util.stream.Collectors.toMap; 38 | 39 | @Controller 40 | @RequestMapping(value = "/admin") 41 | public class AdminController { 42 | 43 | @Autowired 44 | private AuthenticationService authenticationService; 45 | 46 | @Autowired 47 | private TwitterService twitterService; 48 | 49 | @Autowired 50 | private PrincipalService principalService; 51 | 52 | @Autowired 53 | private AdminService adminService; 54 | 55 | @RequestMapping(method = RequestMethod.GET) 56 | @Secured 57 | public ModelAndView index() { 58 | if(!authenticationService.isAdmin()) { 59 | return new ModelAndView("redirect:/"); 60 | } 61 | 62 | ModelAndView result = new ModelAndView("admin/index"); 63 | 64 | final Long userId = authenticationService.getCurrentUserId(); 65 | 66 | try { 67 | result.getModel().put("user", twitterService.getUser(SessionManager.getPrincipal(), userId)); 68 | } catch (final WTFDYUMException e) { 69 | authenticationService.logOut(); 70 | return new ModelAndView("redirect:/"); 71 | } 72 | 73 | result.getModel().put("membersCount", principalService.countMembers()); 74 | 75 | Map featureEnabledCount = adminService.countEnabledFeature(principalService.getMembers()).entrySet().stream().collect(toMap(e -> e.getKey().name(), Map.Entry::getValue)); 76 | 77 | result.getModel().put("availableFeatures", Feature.values()); 78 | result.getModel().put("featureEnabledCount", featureEnabledCount); 79 | 80 | return result; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/web/AjaxController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.jeanchampemont.wtfdyum.web; 20 | 21 | import com.jeanchampemont.wtfdyum.service.AuthenticationService; 22 | import com.jeanchampemont.wtfdyum.service.UserService; 23 | import org.springframework.beans.factory.annotation.Autowired; 24 | import org.springframework.stereotype.Controller; 25 | import org.springframework.web.bind.annotation.PathVariable; 26 | import org.springframework.web.bind.annotation.RequestMapping; 27 | import org.springframework.web.bind.annotation.RequestMethod; 28 | import org.springframework.web.servlet.ModelAndView; 29 | 30 | @Controller 31 | @RequestMapping(path = "/ajax") 32 | public class AjaxController { 33 | 34 | @Autowired 35 | private UserService userService; 36 | 37 | @Autowired 38 | private AuthenticationService authenticationService; 39 | 40 | @RequestMapping(path = "/recentEvents/{start}", method = RequestMethod.GET) 41 | public ModelAndView getRecentEvents(@PathVariable int start) { 42 | ModelAndView result = new ModelAndView("user/fragment/events"); 43 | result.getModel().put("events", userService.getRecentEvents(authenticationService.getCurrentUserId(), 10, start)); 44 | return result; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/web/MainController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.web; 19 | 20 | import com.jeanchampemont.wtfdyum.dto.Event; 21 | import com.jeanchampemont.wtfdyum.dto.Principal; 22 | import com.jeanchampemont.wtfdyum.dto.type.EventType; 23 | import com.jeanchampemont.wtfdyum.service.AuthenticationService; 24 | import com.jeanchampemont.wtfdyum.service.PrincipalService; 25 | import com.jeanchampemont.wtfdyum.service.TwitterService; 26 | import com.jeanchampemont.wtfdyum.service.UserService; 27 | import com.jeanchampemont.wtfdyum.utils.WTFDYUMException; 28 | import com.jeanchampemont.wtfdyum.utils.WTFDYUMExceptionType; 29 | import org.springframework.beans.factory.annotation.Autowired; 30 | import org.springframework.beans.factory.annotation.Value; 31 | import org.springframework.stereotype.Controller; 32 | import org.springframework.web.bind.annotation.RequestMapping; 33 | import org.springframework.web.bind.annotation.RequestMethod; 34 | import org.springframework.web.bind.annotation.RequestParam; 35 | import org.springframework.web.servlet.view.RedirectView; 36 | import twitter4j.auth.AccessToken; 37 | import twitter4j.auth.RequestToken; 38 | 39 | import javax.servlet.http.HttpServletRequest; 40 | 41 | /** 42 | * WTFDYUM Main controller. 43 | */ 44 | @Controller 45 | public class MainController { 46 | 47 | static final String SESSION_REQUEST_TOKEN = "requestToken"; 48 | 49 | @Autowired 50 | private TwitterService twitterService; 51 | 52 | @Autowired 53 | private PrincipalService principalService; 54 | 55 | @Autowired 56 | private AuthenticationService authenticationService; 57 | 58 | @Autowired 59 | private UserService userService; 60 | 61 | @Value("${wtfdyum.max-members}") 62 | private int maxMembers; 63 | 64 | @RequestMapping(value = "/", method = RequestMethod.GET) 65 | public String index() { 66 | return "index"; 67 | } 68 | 69 | @RequestMapping(value = "/signin", method = RequestMethod.GET) 70 | public RedirectView signin(final HttpServletRequest request) throws WTFDYUMException { 71 | if (authenticationService.isAuthenticated()) { 72 | return new RedirectView("/user", true); 73 | } 74 | 75 | if (maxMembers > 0 && principalService.countMembers() >= maxMembers) { 76 | throw new WTFDYUMException(WTFDYUMExceptionType.MEMBER_LIMIT_EXCEEDED); 77 | } 78 | 79 | final RequestToken requestToken = twitterService.signin("/signin/callback"); 80 | 81 | request.getSession().setAttribute(SESSION_REQUEST_TOKEN, requestToken); 82 | 83 | return new RedirectView(requestToken.getAuthenticationURL()); 84 | } 85 | 86 | @RequestMapping(value = "/signin/callback", method = RequestMethod.GET) 87 | public RedirectView signinCallback(@RequestParam("oauth_verifier") final String verifier, 88 | final HttpServletRequest request) throws WTFDYUMException { 89 | final RequestToken requestToken = (RequestToken) request.getSession().getAttribute(SESSION_REQUEST_TOKEN); 90 | request.getSession().removeAttribute(SESSION_REQUEST_TOKEN); 91 | 92 | final AccessToken accessToken = twitterService.completeSignin(requestToken, verifier); 93 | 94 | if (principalService.get(accessToken.getUserId()) == null) { 95 | userService.addEvent(accessToken.getUserId(), new Event(EventType.REGISTRATION, null)); 96 | } 97 | 98 | final Principal user = new Principal(accessToken.getUserId(), accessToken.getToken(), accessToken.getTokenSecret()); 99 | principalService.saveUpdate(user); 100 | authenticationService.authenticate(user); 101 | 102 | return new RedirectView("/user", true); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/com/jeanchampemont/wtfdyum/web/UserController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015, 2016 WTFDYUM 3 | * 4 | * This file is part of the WTFDYUM project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.jeanchampemont.wtfdyum.web; 19 | 20 | import com.jeanchampemont.wtfdyum.dto.Event; 21 | import com.jeanchampemont.wtfdyum.dto.Feature; 22 | import com.jeanchampemont.wtfdyum.dto.type.EventType; 23 | import com.jeanchampemont.wtfdyum.security.Secured; 24 | import com.jeanchampemont.wtfdyum.service.AuthenticationService; 25 | import com.jeanchampemont.wtfdyum.service.FeatureService; 26 | import com.jeanchampemont.wtfdyum.service.TwitterService; 27 | import com.jeanchampemont.wtfdyum.service.UserService; 28 | import com.jeanchampemont.wtfdyum.utils.SessionManager; 29 | import com.jeanchampemont.wtfdyum.utils.WTFDYUMException; 30 | import org.springframework.beans.factory.annotation.Autowired; 31 | import org.springframework.stereotype.Controller; 32 | import org.springframework.web.bind.annotation.PathVariable; 33 | import org.springframework.web.bind.annotation.RequestMapping; 34 | import org.springframework.web.bind.annotation.RequestMethod; 35 | import org.springframework.web.servlet.ModelAndView; 36 | import org.springframework.web.servlet.view.RedirectView; 37 | 38 | import java.util.HashMap; 39 | import java.util.Map; 40 | 41 | @Controller 42 | @RequestMapping(value = "/user") 43 | public class UserController { 44 | 45 | @Autowired 46 | private AuthenticationService authenticationService; 47 | 48 | @Autowired 49 | private TwitterService twitterService; 50 | 51 | @Autowired 52 | private UserService userService; 53 | 54 | @Autowired 55 | private FeatureService featureService; 56 | 57 | @RequestMapping(value = "/feature/disable/{feature}", method = RequestMethod.GET) 58 | @Secured 59 | public RedirectView disableFeature(@PathVariable("feature") final Feature feature) { 60 | final Long userId = authenticationService.getCurrentUserId(); 61 | 62 | if (featureService.disableFeature(userId, feature)) { 63 | userService.addEvent(userId, new Event(EventType.FEATURE_DISABLED, feature.getShortName())); 64 | } 65 | 66 | return new RedirectView("/user", true); 67 | } 68 | 69 | @RequestMapping(value = "/feature/enable/{feature}", method = RequestMethod.GET) 70 | @Secured 71 | public RedirectView enableFeature(@PathVariable("feature") final Feature feature) { 72 | final Long userId = authenticationService.getCurrentUserId(); 73 | 74 | if (featureService.enableFeature(userId, feature)) { 75 | userService.addEvent(userId, new Event(EventType.FEATURE_ENABLED, feature.getShortName())); 76 | } 77 | 78 | return new RedirectView("/user", true); 79 | } 80 | 81 | @RequestMapping(method = RequestMethod.GET) 82 | @Secured 83 | public ModelAndView index() { 84 | final ModelAndView result = new ModelAndView("user/index"); 85 | 86 | final Long userId = authenticationService.getCurrentUserId(); 87 | 88 | try { 89 | result.getModel().put("user", twitterService.getUser(SessionManager.getPrincipal(), userId)); 90 | } catch (final WTFDYUMException e) { 91 | authenticationService.logOut(); 92 | return new ModelAndView("redirect:/"); 93 | } 94 | result.getModel().put("events", userService.getRecentEvents(userId, 10)); 95 | result.getModel().put("availableFeatures", Feature.values()); 96 | 97 | final Map featuresStatus = new HashMap<>(); 98 | for (final Feature f : Feature.values()) { 99 | featuresStatus.put(f.name(), featureService.isEnabled(userId, f)); 100 | } 101 | 102 | result.getModel().put("featuresStatus", featuresStatus); 103 | 104 | return result; 105 | } 106 | 107 | @RequestMapping(value = "/logout", method = RequestMethod.GET) 108 | public RedirectView logout() { 109 | authenticationService.logOut(); 110 | return new RedirectView("/", true); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # This is a usual Java property file, syntax is : 2 | #key=value 3 | # Comments starts with a # 4 | # Feel free to change those properties 5 | 6 | # This URL is used for Twitter callback. Please specify the URL your user access your application with 7 | wtfdyum.server-base-url=http://localhost:8080 8 | 9 | # Twitter credentials 10 | wtfdyum.twitter.appId=@twitter.appId@ 11 | wtfdyum.twitter.appSecret=@twitter.appSecret@ 12 | 13 | # Redis instance 14 | wtfdyum.redis.server=localhost 15 | wtfdyum.redis.port=6379 16 | wtfdyum.redis.database=0 17 | 18 | # This is the maximum number of members allowed to register on this instance of WTFDYUM. 19 | # 0 for unlimited (not recommended for public instance) 20 | wtfdyum.max-members=0 21 | 22 | # How often should WTFDYUM check for unfollowers in ms. 23 | # Default to 1 hour 24 | wtfdyum.unfollow-check-delay=3600000 25 | 26 | # How often should WTFDYUM check for twitter's credential validity. 27 | # Default to 24 hours 28 | wtfdyum.credentials-check-delay=86400000 29 | 30 | # Sent DM when someone get unfollowed. Use %s as a placeholder for twitter screen name 31 | wtfdyum.unfollow.dm-text=Message from WTFDYUM: @%s just stopped following you. 32 | 33 | # Sent tweet when someone get unfollowed. Use %s as a placeholder for twitter screen name 34 | wtfdyum.unfollow.tweet-text=@%s, Why The Fuck Did You Unfollow Me? 35 | 36 | # An HTML fragment that get included on every page. Useful for analytics. 37 | wtfdyum.tracking.code= 38 | 39 | # Numeric twitter id of user with admin privileges. User with admin privileges will be shown some statistics about the current instance. 40 | wtfdyum.admin.twitterId= 41 | 42 | # ADVANCED USER ONLY 43 | # Properties below this line are for advanced user only! 44 | logging.level.com.jeanchampemont.wtfdyum=@wtfdyum.logging.level@ 45 | spring.mvc.favicon.enabled=false 46 | -------------------------------------------------------------------------------- /src/main/resources/static/css/bootstrap-toggle.min.css: -------------------------------------------------------------------------------- 1 | /*! ======================================================================== 2 | * Bootstrap Toggle: bootstrap-toggle.css v2.2.0 3 | * http://www.bootstraptoggle.com 4 | * ======================================================================== 5 | * Copyright 2014 Min Hur, The New York Times Company 6 | * Licensed under MIT 7 | * ======================================================================== */ 8 | .checkbox label .toggle,.checkbox-inline .toggle{margin-left:-20px;margin-right:5px} 9 | .toggle{position:relative;overflow:hidden} 10 | .toggle input[type=checkbox]{display:none} 11 | .toggle-group{position:absolute;width:200%;top:0;bottom:0;left:0;transition:left .35s;-webkit-transition:left .35s;-moz-user-select:none;-webkit-user-select:none} 12 | .toggle.off .toggle-group{left:-100%} 13 | .toggle-on{position:absolute;top:0;bottom:0;left:0;right:50%;margin:0;border:0;border-radius:0} 14 | .toggle-off{position:absolute;top:0;bottom:0;left:50%;right:0;margin:0;border:0;border-radius:0} 15 | .toggle-handle{position:relative;margin:0 auto;padding-top:0;padding-bottom:0;height:100%;width:0;border-width:0 1px} 16 | .toggle.btn{min-width:59px;min-height:34px} 17 | .toggle-on.btn{padding-right:24px} 18 | .toggle-off.btn{padding-left:24px} 19 | .toggle.btn-lg{min-width:79px;min-height:45px} 20 | .toggle-on.btn-lg{padding-right:31px} 21 | .toggle-off.btn-lg{padding-left:31px} 22 | .toggle-handle.btn-lg{width:40px} 23 | .toggle.btn-sm{min-width:50px;min-height:30px} 24 | .toggle-on.btn-sm{padding-right:20px} 25 | .toggle-off.btn-sm{padding-left:20px} 26 | .toggle.btn-xs{min-width:35px;min-height:22px} 27 | .toggle-on.btn-xs{padding-right:12px} 28 | .toggle-off.btn-xs{padding-left:12px} -------------------------------------------------------------------------------- /src/main/resources/static/css/custom.css: -------------------------------------------------------------------------------- 1 | /* Space out content a bit */ 2 | body { 3 | padding-top: 20px; 4 | padding-bottom: 20px; 5 | } 6 | 7 | /* Everything but the jumbotron gets side spacing for mobile first views */ 8 | .header, .marketing, .footer { 9 | padding-right: 15px; 10 | padding-left: 15px; 11 | } 12 | 13 | /* Custom page header */ 14 | .header { 15 | padding-bottom: 20px; 16 | border-bottom: 1px solid #e5e5e5; 17 | } 18 | /* Make the masthead heading the same height as the navigation */ 19 | .header h3 { 20 | margin-top: 0; 21 | margin-bottom: 0; 22 | line-height: 40px; 23 | } 24 | 25 | /* Custom page footer */ 26 | .footer { 27 | padding-top: 19px; 28 | color: #777; 29 | border-top: 1px solid #e5e5e5; 30 | } 31 | 32 | /* Customize container */ 33 | @media ( min-width : 768px) { 34 | .container { 35 | max-width: 730px; 36 | } 37 | } 38 | 39 | .container-narrow>hr { 40 | margin: 30px 0; 41 | } 42 | 43 | /* Main marketing message and sign up button */ 44 | .jumbotron { 45 | text-align: center; 46 | border-bottom: 1px solid #e5e5e5; 47 | } 48 | 49 | .jumbotron .btn { 50 | padding: 14px 24px; 51 | font-size: 21px; 52 | } 53 | 54 | /* Supporting marketing content */ 55 | .marketing { 56 | margin: 40px 0; 57 | } 58 | 59 | .marketing p+h4 { 60 | margin-top: 28px; 61 | } 62 | 63 | /* Responsive: Portrait tablets and up */ 64 | @media screen and (min-width: 768px) { 65 | /* Remove the padding we set earlier */ 66 | .header, .marketing, .footer { 67 | padding-right: 0; 68 | padding-left: 0; 69 | } 70 | /* Space out the masthead */ 71 | .header { 72 | margin-bottom: 30px; 73 | } 74 | /* Remove the bottom border on the jumbotron for visual effect */ 75 | .jumbotron { 76 | border-bottom: 0; 77 | } 78 | } 79 | 80 | #more-button { 81 | margin-bottom: 20px; 82 | } 83 | -------------------------------------------------------------------------------- /src/main/resources/static/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchampemont/WTFDYUM/f66a33450199ad14fa1da507ef7089e1a3a5077f/src/main/resources/static/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /src/main/resources/static/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchampemont/WTFDYUM/f66a33450199ad14fa1da507ef7089e1a3a5077f/src/main/resources/static/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /src/main/resources/static/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchampemont/WTFDYUM/f66a33450199ad14fa1da507ef7089e1a3a5077f/src/main/resources/static/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /src/main/resources/static/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchampemont/WTFDYUM/f66a33450199ad14fa1da507ef7089e1a3a5077f/src/main/resources/static/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /src/main/resources/static/js/bootstrap-toggle.min.js: -------------------------------------------------------------------------------- 1 | /*! ======================================================================== 2 | * Bootstrap Toggle: bootstrap-toggle.js v2.2.0 3 | * http://www.bootstraptoggle.com 4 | * ======================================================================== 5 | * Copyright 2014 Min Hur, The New York Times Company 6 | * Licensed under MIT 7 | * ======================================================================== */ 8 | +function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.toggle"),f="object"==typeof b&&b;e||d.data("bs.toggle",e=new c(this,f)),"string"==typeof b&&e[b]&&e[b]()})}var c=function(b,c){this.$element=a(b),this.options=a.extend({},this.defaults(),c),this.render()};c.VERSION="2.2.0",c.DEFAULTS={on:"On",off:"Off",onstyle:"primary",offstyle:"default",size:"normal",style:"",width:null,height:null},c.prototype.defaults=function(){return{on:this.$element.attr("data-on")||c.DEFAULTS.on,off:this.$element.attr("data-off")||c.DEFAULTS.off,onstyle:this.$element.attr("data-onstyle")||c.DEFAULTS.onstyle,offstyle:this.$element.attr("data-offstyle")||c.DEFAULTS.offstyle,size:this.$element.attr("data-size")||c.DEFAULTS.size,style:this.$element.attr("data-style")||c.DEFAULTS.style,width:this.$element.attr("data-width")||c.DEFAULTS.width,height:this.$element.attr("data-height")||c.DEFAULTS.height}},c.prototype.render=function(){this._onstyle="btn-"+this.options.onstyle,this._offstyle="btn-"+this.options.offstyle;var b="large"===this.options.size?"btn-lg":"small"===this.options.size?"btn-sm":"mini"===this.options.size?"btn-xs":"",c=a('