├── log └── .gitignore ├── settings.gradle ├── src ├── test │ ├── resources │ │ └── application.properties │ └── java │ │ └── cuenation │ │ └── api │ │ ├── TestConfiguration.java │ │ ├── AbstractContextControllerTests.java │ │ ├── user │ │ ├── UserTokenControllerTest.java │ │ ├── UserCueCategoryControllerTest.java │ │ └── UserCueControllerTest.java │ │ └── cue │ │ └── CategoryControllerTest.java └── main │ ├── resources │ ├── application.properties │ └── log4j.properties │ └── java │ └── cuenation │ └── api │ ├── cue │ ├── persistence │ │ ├── CueRepositoryCustom.java │ │ ├── CueCategoryRepositoryCustom.java │ │ ├── UserCueRepositoryCustom.java │ │ ├── CueCategoryRepository.java │ │ ├── CueRepository.java │ │ ├── UserCueRepository.java │ │ ├── CueCategoryRepositoryImpl.java │ │ ├── UserCueRepositoryImpl.java │ │ └── CueRepositoryImpl.java │ ├── service │ │ ├── RssFetcher.java │ │ ├── CategoriesHtmlParser.java │ │ ├── RssParser.java │ │ └── CueService.java │ ├── representation │ │ ├── CueCategoryResource.java │ │ └── CueCategoryResourceAssembler.java │ ├── BgTasks.java │ ├── domain │ │ ├── CueCategory.java │ │ ├── Cue.java │ │ └── UserCue.java │ └── CategoryController.java │ ├── user │ ├── persistence │ │ ├── UserRepositoryCustom.java │ │ ├── UserRepository.java │ │ └── UserRepositoryImpl.java │ ├── representation │ │ ├── UserResourceAssembler.java │ │ ├── UserCueResourceAssembler.java │ │ ├── UserCueCategoryResource.java │ │ ├── UserResource.java │ │ ├── UserCueResource.java │ │ └── UserCueCategoryResourceAssembler.java │ ├── service │ │ └── UserCueService.java │ ├── domain │ │ └── User.java │ ├── UserTokenController.java │ ├── UserCueCategoryController.java │ └── UserCueController.java │ ├── Application.java │ ├── WebConfig.java │ ├── util │ └── ETag.java │ └── cli │ ├── UpdateCues.java │ └── UpdateCueCategories.java ├── .travis.yml ├── dev ├── categories.html └── feed.xml ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── README.md ├── gradlew.bat └── gradlew /log/.gitignore: -------------------------------------------------------------------------------- 1 | !.gitignore 2 | * 3 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'cuenation-api' 2 | 3 | -------------------------------------------------------------------------------- /src/test/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.data.mongodb.uri=mongodb://localhost/cuenation_test -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | - oraclejdk7 5 | services: 6 | - mongodb 7 | -------------------------------------------------------------------------------- /dev/categories.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmitryVarennikov/cuenation-api/master/dev/categories.html -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmitryVarennikov/cuenation-api/master/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Java class files 2 | *.class 3 | 4 | # IDE metadata dirs 5 | .idea 6 | *.iml 7 | META-INF/ 8 | 9 | 10 | # gradle files 11 | .gradle 12 | build/ 13 | 14 | 15 | # log files 16 | /log/ 17 | !/log/.gitkeep -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.data.mongodb.uri=mongodb://localhost/cuenation 2 | 3 | logging.level.org.springframework.web: INFO 4 | logging.file: /var/www/cuenation-api/log/app.log 5 | 6 | server.port=8080 -------------------------------------------------------------------------------- /src/main/java/cuenation/api/cue/persistence/CueRepositoryCustom.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.cue.persistence; 2 | 3 | import cuenation.api.cue.domain.Cue; 4 | 5 | public interface CueRepositoryCustom { 6 | 7 | boolean saveIfNotExists(Cue cue); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Jul 13 20:45:00 PDT 2014 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-1.12-all.zip 7 | -------------------------------------------------------------------------------- /src/main/java/cuenation/api/cue/persistence/CueCategoryRepositoryCustom.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.cue.persistence; 2 | 3 | import cuenation.api.cue.domain.CueCategory; 4 | 5 | public interface CueCategoryRepositoryCustom { 6 | 7 | boolean saveIfNotExists(CueCategory category); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/cuenation/api/user/persistence/UserRepositoryCustom.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.user.persistence; 2 | 3 | import cuenation.api.user.UserCueCategoryController; 4 | import cuenation.api.user.domain.User; 5 | 6 | public interface UserRepositoryCustom { 7 | 8 | void saveUserCategories(User user, UserCueCategoryController.PutRequest request); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/cuenation/api/cue/persistence/UserCueRepositoryCustom.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.cue.persistence; 2 | 3 | import cuenation.api.cue.domain.Cue; 4 | import cuenation.api.user.domain.User; 5 | 6 | import java.util.List; 7 | 8 | public interface UserCueRepositoryCustom { 9 | 10 | void markCuesAsViewed(User user, List ids); 11 | 12 | void reSaveCues(User user, List cues); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/cuenation/api/user/persistence/UserRepository.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.user.persistence; 2 | 3 | import org.springframework.data.mongodb.repository.MongoRepository; 4 | import org.springframework.stereotype.Repository; 5 | import cuenation.api.user.domain.User; 6 | 7 | @Repository 8 | public interface UserRepository extends MongoRepository, UserRepositoryCustom { 9 | 10 | public User findByToken(String token); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/cuenation/api/cue/persistence/CueCategoryRepository.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.cue.persistence; 2 | 3 | import cuenation.api.cue.domain.CueCategory; 4 | import org.springframework.data.mongodb.repository.MongoRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.Collection; 8 | import java.util.List; 9 | 10 | @Repository 11 | public interface CueCategoryRepository extends MongoRepository, CueCategoryRepositoryCustom { 12 | 13 | CueCategory findByName(String name); 14 | 15 | List findByIdIn(Collection ids); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/cuenation/api/cue/persistence/CueRepository.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.cue.persistence; 2 | 3 | import cuenation.api.cue.domain.Cue; 4 | import cuenation.api.cue.domain.CueCategory; 5 | import org.springframework.data.mongodb.repository.MongoRepository; 6 | import org.springframework.stereotype.Repository; 7 | 8 | import java.util.Date; 9 | import java.util.List; 10 | 11 | @Repository 12 | public interface CueRepository extends MongoRepository, CueRepositoryCustom { 13 | 14 | List findByCategoryInAndCreatedAtGreaterThan(List categories, Date laterThan); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/cuenation/api/user/representation/UserResourceAssembler.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.user.representation; 2 | 3 | import org.springframework.hateoas.mvc.ResourceAssemblerSupport; 4 | import cuenation.api.user.UserTokenController; 5 | import cuenation.api.user.domain.User; 6 | 7 | public class UserResourceAssembler extends ResourceAssemblerSupport { 8 | 9 | public UserResourceAssembler() { 10 | super(UserTokenController.class, UserResource.class); 11 | } 12 | 13 | @Override 14 | public UserResource toResource(User user) { 15 | return new UserResource(user); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/cuenation/api/user/service/UserCueService.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.user.service; 2 | 3 | import cuenation.api.cue.persistence.UserCueRepository; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.stereotype.Service; 6 | import cuenation.api.user.UserCueController; 7 | import cuenation.api.user.domain.User; 8 | 9 | @Service 10 | public class UserCueService { 11 | 12 | @Autowired 13 | private UserCueRepository userCueRepository; 14 | 15 | public void markCuesAsViewed(User user, UserCueController.PuRequest request) { 16 | userCueRepository.markCuesAsViewed(user, request.getIds()); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/cuenation/api/Application.java: -------------------------------------------------------------------------------- 1 | package cuenation.api; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 5 | import org.springframework.context.annotation.ComponentScan; 6 | import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; 7 | import org.springframework.data.web.config.EnableSpringDataWebSupport; 8 | 9 | @ComponentScan 10 | @EnableAutoConfiguration 11 | @EnableMongoRepositories 12 | @EnableSpringDataWebSupport 13 | public class Application { 14 | 15 | public static void main(String[] args) { 16 | SpringApplication.run(Application.class, args); 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | # Root logger option 2 | log4j.rootLogger=INFO, file 3 | 4 | # Direct log messages to a log file 5 | log4j.appender.file=org.apache.log4j.DailyRollingFileAppender 6 | log4j.appender.file.DatePattern = '.'yyyy-MM-dd 7 | log4j.appender.file.Append = true 8 | log4j.appender.file.File=/var/www/cuenation-api/log/4j.log 9 | log4j.appender.file.layout=org.apache.log4j.PatternLayout 10 | log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n 11 | 12 | #log4j.category.org.apache.activemq=ERROR 13 | #log4j.category.org.springframework.batch=DEBUG 14 | #log4j.category.org.springframework.data.document.mongodb=DEBUG 15 | #log4j.category.org.springframework.transaction=INFO -------------------------------------------------------------------------------- /src/main/java/cuenation/api/user/representation/UserCueResourceAssembler.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.user.representation; 2 | 3 | import cuenation.api.cue.domain.UserCue; 4 | import org.springframework.hateoas.mvc.ResourceAssemblerSupport; 5 | import org.springframework.stereotype.Component; 6 | import cuenation.api.user.UserCueController; 7 | 8 | @Component 9 | public class UserCueResourceAssembler extends ResourceAssemblerSupport { 10 | 11 | public UserCueResourceAssembler() { 12 | super(UserCueController.class, UserCueResource.class); 13 | } 14 | 15 | @Override 16 | public UserCueResource toResource(UserCue userCue) { 17 | return new UserCueResource(userCue); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/cuenation/api/TestConfiguration.java: -------------------------------------------------------------------------------- 1 | package cuenation.api; 2 | 3 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 4 | import org.springframework.context.annotation.ComponentScan; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.context.annotation.PropertySource; 7 | import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; 8 | import org.springframework.data.web.config.EnableSpringDataWebSupport; 9 | 10 | @Configuration 11 | @ComponentScan 12 | @EnableMongoRepositories 13 | @EnableSpringDataWebSupport 14 | @EnableAutoConfiguration 15 | @PropertySource("classpath:application.properties") 16 | public class TestConfiguration { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/cuenation/api/cue/service/RssFetcher.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.cue.service; 2 | 3 | import com.sun.syndication.feed.synd.SyndFeed; 4 | import com.sun.syndication.fetcher.FeedFetcher; 5 | import com.sun.syndication.fetcher.FetcherException; 6 | import com.sun.syndication.fetcher.impl.HttpURLFeedFetcher; 7 | import com.sun.syndication.io.FeedException; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.io.IOException; 11 | import java.net.URL; 12 | 13 | @Component 14 | public class RssFetcher { 15 | 16 | public SyndFeed fetch() throws IOException, FetcherException, FeedException { 17 | URL feedUrl = new URL("http://cuenation.com/feed.php"); 18 | 19 | FeedFetcher feedFetcher = new HttpURLFeedFetcher(); 20 | return feedFetcher.retrieveFeed(feedUrl); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CueNation API 2 | ============= 3 | 4 | ![Build status](https://travis-ci.org/dVaffection/cuenation-api.svg?branch=master) 5 | 6 | Provides API ends for [CueNation Chrome ext](https://github.com/dVaffection/cuenation-chrome-ext) 7 | 8 | [Wiki documentation](https://github.com/dVaffection/cuenation-api/wiki) 9 | 10 | ## How to test 11 | 12 | `./gradlew test` 13 | 14 | ## How to run 15 | 16 | `./gradlew run` 17 | 18 | ## How to deploy 19 | 20 | ### Build jar 21 | `./gradlew build` --> `build/libs/cuenation-api-.jar` 22 | 23 | ### Run jar 24 | 25 | * Remove current symlink `rm build/libs/cuenation-api.jar` 26 | * Create a new symlink for the latest build `ln -s build/libs/cuenation-api-.jar build/libs/cuenation-api.jar` 27 | * Kill current process `kill ` 28 | * Start a new process `/usr/bin/java -jar build/libs/cuenation-api.jar &> /dev/null &` -------------------------------------------------------------------------------- /src/main/java/cuenation/api/WebConfig.java: -------------------------------------------------------------------------------- 1 | package cuenation.api; 2 | 3 | import org.springframework.boot.context.embedded.FilterRegistrationBean; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.web.filter.ShallowEtagHeaderFilter; 7 | 8 | import javax.servlet.DispatcherType; 9 | import java.util.EnumSet; 10 | 11 | @Configuration 12 | public class WebConfig { 13 | 14 | @Bean 15 | public FilterRegistrationBean shallowEtagHeaderFilter() { 16 | FilterRegistrationBean registration = new FilterRegistrationBean(); 17 | registration.setFilter(new ShallowEtagHeaderFilter()); 18 | registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class)); 19 | registration.addUrlPatterns("/cue-categories"); 20 | return registration; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/cuenation/api/cue/persistence/UserCueRepository.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.cue.persistence; 2 | 3 | import cuenation.api.cue.domain.CueCategory; 4 | import cuenation.api.cue.domain.UserCue; 5 | import cuenation.api.user.domain.User; 6 | import org.springframework.data.domain.Page; 7 | import org.springframework.data.domain.Pageable; 8 | import org.springframework.data.mongodb.repository.MongoRepository; 9 | import org.springframework.stereotype.Repository; 10 | 11 | import java.util.List; 12 | 13 | 14 | @Repository 15 | public interface UserCueRepository extends MongoRepository, UserCueRepositoryCustom { 16 | 17 | Page findAllByUser(User user, Pageable pageable); 18 | 19 | Page findAllByUserAndViewedAtExists(User user, boolean exists, Pageable pageable); 20 | 21 | List removeByUserAndCategoryNotIn(User user, List categories); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/cuenation/api/user/persistence/UserRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.user.persistence; 2 | 3 | import cuenation.api.cue.persistence.CueCategoryRepository; 4 | import cuenation.api.cue.domain.CueCategory; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import cuenation.api.user.UserCueCategoryController; 7 | import cuenation.api.user.domain.User; 8 | 9 | import java.util.List; 10 | 11 | public class UserRepositoryImpl implements UserRepositoryCustom { 12 | 13 | @Autowired 14 | private CueCategoryRepository cueCategoryRepository; 15 | 16 | @Autowired 17 | private UserRepository userRepository; 18 | 19 | @Override 20 | public void saveUserCategories(User user, UserCueCategoryController.PutRequest request) { 21 | List categories = cueCategoryRepository.findByIdIn(request.getIds()); 22 | 23 | user.setCategories(categories); 24 | userRepository.save(user); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/cuenation/api/util/ETag.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.util; 2 | 3 | import java.io.UnsupportedEncodingException; 4 | import java.math.BigInteger; 5 | import java.security.MessageDigest; 6 | import java.security.NoSuchAlgorithmException; 7 | 8 | public class ETag { 9 | 10 | public static String create(String data) throws Exception { 11 | String eTag = ""; 12 | 13 | try { 14 | byte[] bytes = MessageDigest.getInstance("MD5").digest(data.getBytes("UTF-8")); 15 | BigInteger number = new BigInteger(1, bytes); 16 | eTag = number.toString(16); 17 | 18 | return eTag; 19 | } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { 20 | throw new Exception(e.getMessage(), e); 21 | } 22 | } 23 | 24 | public static class Exception extends java.lang.Exception { 25 | 26 | public Exception(String message, Throwable cause) { 27 | super(message, cause); 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/cuenation/api/cue/representation/CueCategoryResource.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.cue.representation; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import cuenation.api.cue.domain.CueCategory; 6 | import org.springframework.hateoas.ResourceSupport; 7 | import org.springframework.hateoas.core.Relation; 8 | 9 | @Relation(collectionRelation = "cueCategories") 10 | public class CueCategoryResource extends ResourceSupport { 11 | 12 | @JsonProperty("id") 13 | private String id; 14 | 15 | @JsonProperty("name") 16 | private String name; 17 | 18 | @JsonProperty("host") 19 | private String host; 20 | 21 | @JsonProperty("link") 22 | private String link; 23 | 24 | @JsonCreator 25 | public CueCategoryResource(CueCategory cueCategory) { 26 | id = cueCategory.getId(); 27 | name = cueCategory.getName(); 28 | host = cueCategory.getHost(); 29 | link = cueCategory.getLink(); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/cuenation/api/user/representation/UserCueCategoryResource.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.user.representation; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import cuenation.api.cue.domain.CueCategory; 6 | import org.springframework.hateoas.ResourceSupport; 7 | import org.springframework.hateoas.core.Relation; 8 | 9 | @Relation(collectionRelation = "userCueCategories") 10 | public class UserCueCategoryResource extends ResourceSupport { 11 | 12 | @JsonProperty("id") 13 | private String id; 14 | 15 | @JsonProperty("name") 16 | private String name; 17 | 18 | @JsonProperty("host") 19 | private String host; 20 | 21 | @JsonProperty("link") 22 | private String link; 23 | 24 | @JsonCreator 25 | public UserCueCategoryResource(CueCategory cueCategory) { 26 | id = cueCategory.getId(); 27 | name = cueCategory.getName(); 28 | host = cueCategory.getHost(); 29 | link = cueCategory.getLink(); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/cuenation/api/user/representation/UserResource.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.user.representation; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import org.springframework.hateoas.ResourceSupport; 6 | import org.springframework.hateoas.mvc.ControllerLinkBuilder; 7 | import cuenation.api.user.UserCueCategoryController; 8 | import cuenation.api.user.UserCueController; 9 | import cuenation.api.user.UserTokenController; 10 | import cuenation.api.user.domain.User; 11 | 12 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; 13 | 14 | public class UserResource extends ResourceSupport { 15 | 16 | @JsonProperty("token") 17 | String token; 18 | 19 | @JsonCreator 20 | public UserResource(User user) { 21 | token = user.getToken(); 22 | 23 | add(ControllerLinkBuilder.linkTo(methodOn(UserTokenController.class).get(token)).withSelfRel()); 24 | add(ControllerLinkBuilder.linkTo(methodOn(UserCueCategoryController.class).list(token)).withRel("userCueCategories")); 25 | add(ControllerLinkBuilder.linkTo(UserCueController.class, token).withRel("userCues")); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/cuenation/api/cue/BgTasks.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.cue; 2 | 3 | import cuenation.api.cue.service.CueService; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.scheduling.annotation.EnableScheduling; 8 | import org.springframework.scheduling.annotation.Scheduled; 9 | import org.springframework.stereotype.Component; 10 | 11 | @Component 12 | @EnableScheduling 13 | public class BgTasks { 14 | 15 | Logger logger = LoggerFactory.getLogger(BgTasks.class); 16 | 17 | @Autowired 18 | private CueService cueService; 19 | 20 | @Scheduled(cron = "0 */10 * * * *") 21 | public void updateCues() { 22 | int cuesNumber = cueService.updateCues(); 23 | 24 | String message = String.format("%d cues added", cuesNumber); 25 | logger.info(message); 26 | } 27 | 28 | @Scheduled(cron = "0 0 0 * * *") 29 | public void updateCueCategories() { 30 | int cueCategoriesNumber = cueService.updateCueCategories(); 31 | 32 | String message = String.format("%d cue categories added", cueCategoriesNumber); 33 | logger.info(message); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/cuenation/api/cue/persistence/CueCategoryRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.cue.persistence; 2 | 3 | import com.mongodb.WriteResult; 4 | import cuenation.api.cue.domain.CueCategory; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.data.mongodb.core.MongoOperations; 7 | import org.springframework.data.mongodb.core.query.Criteria; 8 | import org.springframework.data.mongodb.core.query.Query; 9 | import org.springframework.data.mongodb.core.query.Update; 10 | 11 | public class CueCategoryRepositoryImpl implements CueCategoryRepositoryCustom { 12 | 13 | @Autowired 14 | private MongoOperations operations; 15 | 16 | @Override 17 | public boolean saveIfNotExists(CueCategory category) { 18 | Query query = new Query(); 19 | query.addCriteria(Criteria.where("name").is(category.getName())); 20 | 21 | Update update = new Update(); 22 | update 23 | .set("name", category.getName()) 24 | .set("link", category.getLink()) 25 | ; 26 | 27 | String host = category.getHost(); 28 | if (host != null) { 29 | update.set("host", category.getHost()); 30 | } 31 | 32 | WriteResult writeResult = operations.upsert(query, update, CueCategory.class); 33 | return !writeResult.isUpdateOfExisting(); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/cuenation/api/cli/UpdateCues.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.cli; 2 | 3 | import cuenation.api.cue.service.CueService; 4 | import cuenation.api.user.UserCueController; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.CommandLineRunner; 9 | import org.springframework.boot.SpringApplication; 10 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 11 | import org.springframework.context.annotation.ComponentScan; 12 | import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; 13 | 14 | @ComponentScan(basePackages = {"cuenation.api.cue"}) 15 | @EnableAutoConfiguration 16 | @EnableMongoRepositories(basePackages = {"cuenation.api.cue"}) 17 | public class UpdateCues implements CommandLineRunner { 18 | 19 | final private Logger logger = LoggerFactory.getLogger(UserCueController.class); 20 | 21 | @Autowired 22 | private CueService cueService; 23 | 24 | public static void main(String[] args) { 25 | SpringApplication.run(UpdateCues.class, args); 26 | } 27 | 28 | @Override 29 | public void run(String... args) throws Exception { 30 | int cuesNumber = cueService.updateCues(); 31 | 32 | String message = String.format("%d cues added", cuesNumber); 33 | logger.info(message); 34 | 35 | System.exit(0); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/cuenation/api/cue/representation/CueCategoryResourceAssembler.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.cue.representation; 2 | 3 | import cuenation.api.cue.CategoryController; 4 | import cuenation.api.cue.domain.CueCategory; 5 | import org.springframework.hateoas.ResourceSupport; 6 | import org.springframework.hateoas.Resources; 7 | import org.springframework.hateoas.mvc.ControllerLinkBuilder; 8 | import org.springframework.hateoas.mvc.ResourceAssemblerSupport; 9 | import org.springframework.stereotype.Component; 10 | 11 | import java.util.List; 12 | 13 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; 14 | 15 | @Component 16 | public class CueCategoryResourceAssembler extends ResourceAssemblerSupport { 17 | 18 | public CueCategoryResourceAssembler() { 19 | super(CategoryController.class, CueCategoryResource.class); 20 | } 21 | 22 | @Override 23 | public CueCategoryResource toResource(CueCategory category) { 24 | return new CueCategoryResource(category); 25 | } 26 | 27 | public ResourceSupport getResponse(List categories) { 28 | List categoryResources = toResources(categories); 29 | Resources resource = Resources.wrap(categoryResources); 30 | 31 | resource.add(ControllerLinkBuilder.linkTo(methodOn(CategoryController.class).list(null)).withSelfRel()); 32 | 33 | return resource; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/cuenation/api/cli/UpdateCueCategories.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.cli; 2 | 3 | import cuenation.api.cue.service.CueService; 4 | import cuenation.api.user.UserCueController; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.CommandLineRunner; 9 | import org.springframework.boot.SpringApplication; 10 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 11 | import org.springframework.context.annotation.ComponentScan; 12 | import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; 13 | 14 | @ComponentScan(basePackages = {"cuenation.api.cue"}) 15 | @EnableAutoConfiguration 16 | @EnableMongoRepositories(basePackages = {"cuenation.api.cue"}) 17 | public class UpdateCueCategories implements CommandLineRunner { 18 | 19 | final private Logger logger = LoggerFactory.getLogger(UserCueController.class); 20 | 21 | @Autowired 22 | private CueService cueService; 23 | 24 | public static void main(String[] args) { 25 | SpringApplication.run(UpdateCueCategories.class, args); 26 | } 27 | 28 | @Override 29 | public void run(String... args) throws Exception { 30 | int cueCategoriesNumber = cueService.updateCueCategories(); 31 | 32 | String message = String.format("%d cue categories added", cueCategoriesNumber); 33 | logger.info(message); 34 | 35 | System.exit(0); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/cuenation/api/user/representation/UserCueResource.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.user.representation; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import cuenation.api.cue.domain.Cue; 6 | import cuenation.api.cue.domain.UserCue; 7 | import org.springframework.hateoas.ResourceSupport; 8 | import org.springframework.hateoas.core.Relation; 9 | 10 | import java.util.Date; 11 | 12 | @Relation(collectionRelation = "userCues") 13 | public class UserCueResource extends ResourceSupport { 14 | 15 | @JsonProperty("id") 16 | private String id; 17 | 18 | @JsonProperty("categoryId") 19 | private String categoryId; 20 | 21 | @JsonProperty("title") 22 | private String title; 23 | 24 | @JsonProperty("link") 25 | private String link; 26 | 27 | @JsonProperty("createdAt") 28 | private Date createdAt; 29 | 30 | @JsonCreator 31 | public UserCueResource(UserCue userCue) { 32 | Cue cue = userCue.getCue(); 33 | 34 | // NOTE: this is not an original Cue ID but a UserCue Id! 35 | id = userCue.getId(); 36 | // time of cues creation is duplicated for user cues in order to fetch them in a reverse order 37 | createdAt = userCue.getCreatedAt(); 38 | // in order to know to which category a cue belongs to 39 | categoryId = userCue.getCategory().getId(); 40 | 41 | title = cue.getTitle(); 42 | link = cue.getLink(); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/cuenation/api/cue/domain/CueCategory.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.cue.domain; 2 | 3 | import org.springframework.data.annotation.Id; 4 | import org.springframework.data.mongodb.core.index.Indexed; 5 | import org.springframework.data.mongodb.core.mapping.Document; 6 | 7 | @Document(collection = "cue_categories") 8 | public class CueCategory { 9 | 10 | @Id 11 | private String id; 12 | 13 | @Indexed(unique = true) 14 | private String name; 15 | 16 | private String host; 17 | 18 | private String link; 19 | 20 | public CueCategory() { 21 | 22 | } 23 | 24 | public CueCategory(String name, String link) { 25 | this.name = name; 26 | this.link = link; 27 | } 28 | 29 | public CueCategory(String name, String host, String link) { 30 | this.name = name; 31 | this.host = host; 32 | this.link = link; 33 | } 34 | 35 | public String getId() { 36 | return id; 37 | } 38 | 39 | public String getName() { 40 | return name; 41 | } 42 | 43 | public String getLink() { 44 | return link; 45 | } 46 | 47 | public String getHost() { 48 | return host; 49 | } 50 | 51 | @Override 52 | public String toString() { 53 | return "CueCategory{" + 54 | "id='" + id + '\'' + 55 | ", name='" + name + '\'' + 56 | ", host='" + host + '\'' + 57 | ", link='" + link + '\'' + 58 | '}'; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/cuenation/api/user/domain/User.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.user.domain; 2 | 3 | import cuenation.api.cue.domain.CueCategory; 4 | import org.springframework.data.annotation.Id; 5 | import org.springframework.data.mongodb.core.index.Indexed; 6 | import org.springframework.data.mongodb.core.mapping.DBRef; 7 | import org.springframework.data.mongodb.core.mapping.Document; 8 | 9 | import java.util.Collections; 10 | import java.util.List; 11 | 12 | @Document(collection = "users") 13 | public class User { 14 | 15 | @Id 16 | private String id; 17 | 18 | @Indexed(unique = true) 19 | private String token; 20 | 21 | @DBRef 22 | private List categories; 23 | 24 | public User(String token) { 25 | setToken(token); 26 | } 27 | 28 | public String getId() { 29 | return id; 30 | } 31 | 32 | public String getToken() { 33 | return token; 34 | } 35 | 36 | public void setToken(String token) { 37 | this.token = token; 38 | } 39 | 40 | public List getCategories() { 41 | if (categories == null) { 42 | categories = Collections.EMPTY_LIST; 43 | } 44 | 45 | return categories; 46 | } 47 | 48 | public void setCategories(List categories) { 49 | this.categories = categories; 50 | } 51 | 52 | @Override 53 | public String toString() { 54 | return "User{" + 55 | "id='" + id + '\'' + 56 | ", token='" + token + '\'' + 57 | '}'; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/cuenation/api/user/representation/UserCueCategoryResourceAssembler.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.user.representation; 2 | 3 | import cuenation.api.cue.domain.CueCategory; 4 | import cuenation.api.user.UserCueCategoryController; 5 | import cuenation.api.user.domain.User; 6 | import org.springframework.hateoas.ResourceSupport; 7 | import org.springframework.hateoas.Resources; 8 | import org.springframework.hateoas.mvc.ControllerLinkBuilder; 9 | import org.springframework.hateoas.mvc.ResourceAssemblerSupport; 10 | import org.springframework.stereotype.Component; 11 | 12 | import java.util.List; 13 | 14 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; 15 | 16 | @Component 17 | public class UserCueCategoryResourceAssembler extends ResourceAssemblerSupport { 18 | 19 | public UserCueCategoryResourceAssembler() { 20 | super(UserCueCategoryController.class, UserCueCategoryResource.class); 21 | } 22 | 23 | @Override 24 | public UserCueCategoryResource toResource(CueCategory category) { 25 | return new UserCueCategoryResource(category); 26 | } 27 | 28 | public ResourceSupport getResponse(User user, List categories) { 29 | List categoryResources = toResources(categories); 30 | Resources resource = Resources.wrap(categoryResources); 31 | 32 | resource.add(ControllerLinkBuilder.linkTo(methodOn(UserCueCategoryController.class).list(user.getToken())).withSelfRel()); 33 | 34 | return resource; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/cuenation/api/cue/service/CategoriesHtmlParser.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.cue.service; 2 | 3 | import cuenation.api.cue.domain.CueCategory; 4 | import org.jsoup.Jsoup; 5 | import org.jsoup.nodes.Document; 6 | import org.jsoup.nodes.Element; 7 | import org.jsoup.select.Elements; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.io.IOException; 11 | import java.util.LinkedList; 12 | import java.util.List; 13 | 14 | @Component 15 | public class CategoriesHtmlParser { 16 | 17 | public List parse() throws IOException { 18 | List categories = new LinkedList<>(); 19 | String text, host, href; 20 | 21 | Document doc = Jsoup.connect("http://cuenation.com/?page=categories").get(); 22 | Elements lists = doc.getElementsByClass("list"); 23 | for (Element list : lists) { 24 | Elements links = list.getElementsByTag("a"); 25 | 26 | for (Element link : links) { 27 | text = link.text().trim(); 28 | // yeah, category links are relative URIs 29 | href = "http://cuenation.com/" + link.attr("href"); 30 | 31 | if (text.length() > 0 && link.attr("href").length() > 0) { 32 | Elements hosts = link.parent().getElementsByTag("i"); 33 | if (hosts.size() > 0) { 34 | host = hosts.get(0).text(); 35 | categories.add(new CueCategory(text, host, href)); 36 | } else { 37 | categories.add(new CueCategory(text, href)); 38 | } 39 | } 40 | } 41 | } 42 | 43 | return categories; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/cuenation/api/cue/persistence/UserCueRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.cue.persistence; 2 | 3 | import cuenation.api.cue.domain.Cue; 4 | import cuenation.api.cue.domain.UserCue; 5 | import cuenation.api.user.domain.User; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.data.mongodb.core.MongoOperations; 8 | import org.springframework.data.mongodb.core.query.Criteria; 9 | import org.springframework.data.mongodb.core.query.Query; 10 | import org.springframework.data.mongodb.core.query.Update; 11 | 12 | import java.util.Date; 13 | import java.util.List; 14 | 15 | public class UserCueRepositoryImpl implements UserCueRepositoryCustom { 16 | 17 | @Autowired 18 | private MongoOperations operations; 19 | 20 | @Override 21 | public void markCuesAsViewed(User user, List ids) { 22 | Date now = new Date(); 23 | 24 | Query query = new Query(); 25 | query.addCriteria(Criteria.where("user").is(user).and("id").in(ids)); 26 | 27 | Update update = new Update(); 28 | update.set("viewedAt", now); 29 | 30 | operations.updateMulti(query, update, UserCue.class); 31 | } 32 | 33 | @Override 34 | public void reSaveCues(User user, List cues) { 35 | UserCue foundUserCue, newUserCue; 36 | 37 | // retain existing cues and add new ones 38 | for (Cue cue : cues) { 39 | Query query = new Query(); 40 | query.addCriteria(Criteria.where("user").is(user).and("cue").in(cue)); 41 | foundUserCue = operations.findOne(query, UserCue.class); 42 | 43 | if (foundUserCue == null) { 44 | newUserCue = new UserCue(user, cue); 45 | operations.insert(newUserCue); 46 | } 47 | } 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/cuenation/api/cue/domain/Cue.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.cue.domain; 2 | 3 | import org.springframework.data.annotation.Id; 4 | import org.springframework.data.mongodb.core.index.CompoundIndex; 5 | import org.springframework.data.mongodb.core.index.CompoundIndexes; 6 | import org.springframework.data.mongodb.core.index.Indexed; 7 | import org.springframework.data.mongodb.core.mapping.DBRef; 8 | import org.springframework.data.mongodb.core.mapping.Document; 9 | 10 | import java.util.Date; 11 | 12 | @Document(collection = "cues") 13 | @CompoundIndexes({ 14 | @CompoundIndex(def = "{'category': 1, 'createdAt': -1}") 15 | }) 16 | public class Cue { 17 | 18 | @Id 19 | private String id; 20 | 21 | @Indexed(unique = true) 22 | private String title; 23 | 24 | private String link; 25 | 26 | private Date createdAt; 27 | 28 | @DBRef 29 | private CueCategory category; 30 | 31 | public Cue(String title, String link, Date createdAt, CueCategory category) { 32 | this.title = title; 33 | this.link = link; 34 | this.createdAt = createdAt; 35 | setCategory(category); 36 | } 37 | 38 | public CueCategory getCategory() { 39 | return category; 40 | } 41 | 42 | public void setCategory(CueCategory category) { 43 | this.category = category; 44 | } 45 | 46 | public String getId() { 47 | return id; 48 | } 49 | 50 | public String getLink() { 51 | return link; 52 | } 53 | 54 | public String getTitle() { 55 | return title; 56 | } 57 | 58 | public Date getCreatedAt() { 59 | return createdAt; 60 | } 61 | 62 | @Override 63 | public String toString() { 64 | return "Cue{" + 65 | "id='" + id + '\'' + 66 | ", title='" + title + '\'' + 67 | ", link='" + link + '\'' + 68 | ", category=" + category + 69 | '}'; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/cuenation/api/cue/service/RssParser.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.cue.service; 2 | 3 | import com.sun.syndication.feed.synd.SyndEntry; 4 | import com.sun.syndication.feed.synd.SyndFeed; 5 | import cuenation.api.cue.domain.Cue; 6 | import cuenation.api.cue.domain.CueCategory; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.util.Date; 10 | import java.util.LinkedList; 11 | import java.util.List; 12 | import java.util.regex.Matcher; 13 | import java.util.regex.Pattern; 14 | 15 | @Component 16 | public class RssParser { 17 | 18 | public List parse(SyndFeed feed) { 19 | List cues = new LinkedList<>(); 20 | String title, link, category; 21 | Date createdAt; 22 | CueCategory cueCategory; 23 | 24 | for (SyndEntry entry : (List) feed.getEntries()) { 25 | title = entry.getTitle(); 26 | link = entry.getLink(); 27 | createdAt = entry.getPublishedDate(); 28 | category = trimCategory(entry.getCategories().get(0).toString()); 29 | 30 | // avoid adding non-full cues 31 | if (title.length() > 0 && link.length() > 0 && category.length() > 0) { 32 | cueCategory = new CueCategory(category, "", ""); 33 | 34 | cues.add(new Cue(title, link, createdAt, cueCategory)); 35 | } 36 | } 37 | 38 | return cues; 39 | } 40 | 41 | /** 42 | * @param input example: "SyndCategoryImpl.taxonomyUri=null\nSyndCategoryImpl.name=Corsten's Countdown\n" 43 | * @return example: "Corsten's Countdown" 44 | */ 45 | private String trimCategory(String input) { 46 | String regex = ".*SyndCategoryImpl.name=(.+)"; 47 | Pattern pattern = Pattern.compile(regex, Pattern.DOTALL | Pattern.CASE_INSENSITIVE); 48 | Matcher matcher = pattern.matcher(input); 49 | 50 | String match = ""; 51 | if (matcher.matches() && matcher.groupCount() > 0) { 52 | match = matcher.group(1).trim(); 53 | } 54 | 55 | return match; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/cuenation/api/cue/persistence/CueRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.cue.persistence; 2 | 3 | import com.mongodb.WriteResult; 4 | import cuenation.api.cue.domain.Cue; 5 | import cuenation.api.cue.domain.CueCategory; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.data.mongodb.core.MongoOperations; 10 | import org.springframework.data.mongodb.core.query.Criteria; 11 | import org.springframework.data.mongodb.core.query.Query; 12 | import org.springframework.data.mongodb.core.query.Update; 13 | 14 | public class CueRepositoryImpl implements CueRepositoryCustom { 15 | 16 | private final Logger logger = LoggerFactory.getLogger(CueRepositoryImpl.class); 17 | 18 | @Autowired 19 | private CueCategoryRepository cueCategoryRepository; 20 | 21 | @Autowired 22 | private MongoOperations operations; 23 | 24 | @Override 25 | public boolean saveIfNotExists(Cue cue) { 26 | // first make sure the given category exists in our db 27 | CueCategory cueCategory = cueCategoryRepository.findByName(cue.getCategory().getName()); 28 | if (cueCategory == null) { 29 | logger.info("Cue category \"{}\" was not found, cue is not saved", cue.getCategory().getName()); 30 | return false; 31 | } 32 | 33 | cue.setCategory(cueCategory); 34 | 35 | 36 | // insert cue if it's not in db yet 37 | Query query = new Query(); 38 | query.addCriteria(Criteria.where("title").is(cue.getTitle())); 39 | 40 | Update update = new Update(); 41 | update 42 | .set("title", cue.getTitle()) 43 | .set("link", cue.getLink()) 44 | .set("createdAt", cue.getCreatedAt()) 45 | .set("category", cue.getCategory()); 46 | 47 | WriteResult writeResult = operations.upsert(query, update, Cue.class); 48 | // if record was updated we treat it wasn't added anew 49 | // records are unlikely to be changed, so we basically need to know how many new were added 50 | return !writeResult.isUpdateOfExisting(); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/cuenation/api/cue/domain/UserCue.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.cue.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import cuenation.api.user.domain.User; 5 | import org.springframework.data.annotation.Id; 6 | import org.springframework.data.annotation.TypeAlias; 7 | import org.springframework.data.mongodb.core.index.CompoundIndex; 8 | import org.springframework.data.mongodb.core.index.CompoundIndexes; 9 | import org.springframework.data.mongodb.core.mapping.DBRef; 10 | import org.springframework.data.mongodb.core.mapping.Document; 11 | 12 | import java.util.Date; 13 | 14 | @TypeAlias("user_cue") 15 | @Document(collection = "user_cues") 16 | @CompoundIndexes({ 17 | @CompoundIndex(def = "{'user': 1, 'category': 1}"), 18 | @CompoundIndex(def = "{'user': 1, 'viewedAt': 1, 'createdAt': -1}") 19 | }) 20 | public class UserCue { 21 | 22 | @Id 23 | private String id; 24 | 25 | @DBRef 26 | private User user; 27 | 28 | @DBRef 29 | private Cue cue; 30 | 31 | @DBRef 32 | // required for old cues deletion, see `UserCueRepository.removeByUserAndCategoryNotIn` 33 | private CueCategory category; 34 | 35 | // required for cues request sorting, see `UserCueController.list` 36 | private Date createdAt; 37 | 38 | // required for cues filtration upon request, see `UserCueRepository.findAllByUserAndViewedAtExists` 39 | private Date viewedAt; 40 | 41 | @JsonCreator 42 | public UserCue(User user, Cue cue) { 43 | this.user = user; 44 | this.cue = cue; 45 | this.category = cue.getCategory(); 46 | this.createdAt = cue.getCreatedAt(); 47 | } 48 | 49 | public String getId() { 50 | return id; 51 | } 52 | 53 | public Cue getCue() { 54 | return cue; 55 | } 56 | 57 | public CueCategory getCategory() { 58 | return category; 59 | } 60 | 61 | public Date getViewedAt() { 62 | return viewedAt; 63 | } 64 | 65 | public User getUser() { 66 | return user; 67 | } 68 | 69 | public Date getCreatedAt() { 70 | return createdAt; 71 | } 72 | 73 | @Override 74 | public String toString() { 75 | return "UserCue{" + 76 | "id='" + id + '\'' + 77 | ", user=" + user + 78 | ", cue=" + cue + 79 | ", createdAt=" + createdAt + 80 | ", viewedAt=" + viewedAt + 81 | '}'; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/cuenation/api/user/UserTokenController.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.user; 2 | 3 | import cuenation.api.user.domain.User; 4 | import cuenation.api.user.persistence.UserRepository; 5 | import cuenation.api.user.representation.UserResource; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.hateoas.MediaTypes; 8 | import org.springframework.http.HttpEntity; 9 | import org.springframework.http.HttpHeaders; 10 | import org.springframework.http.HttpStatus; 11 | import org.springframework.http.ResponseEntity; 12 | import org.springframework.web.bind.annotation.PathVariable; 13 | import org.springframework.web.bind.annotation.RequestMapping; 14 | import org.springframework.web.bind.annotation.RequestMethod; 15 | import org.springframework.web.bind.annotation.RestController; 16 | 17 | import java.util.UUID; 18 | 19 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; 20 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; 21 | 22 | @RestController 23 | @RequestMapping("/user-tokens") 24 | public class UserTokenController { 25 | 26 | @Autowired 27 | private UserRepository userRepository; 28 | 29 | @RequestMapping(method = RequestMethod.GET, value = "/{token}") 30 | public HttpEntity get(@PathVariable("token") String token) { 31 | User user = userRepository.findByToken(token); 32 | 33 | HttpEntity responseEntity; 34 | if (user == null) { 35 | responseEntity = new ResponseEntity<>(HttpStatus.NOT_FOUND); 36 | } else { 37 | UserResource userResource = new UserResource(user); 38 | 39 | HttpHeaders headers = new HttpHeaders(); 40 | headers.add("Content-Type", MediaTypes.HAL_JSON.toString()); 41 | 42 | responseEntity = new ResponseEntity<>(userResource, headers, HttpStatus.OK); 43 | } 44 | 45 | return responseEntity; 46 | } 47 | 48 | @RequestMapping(method = RequestMethod.POST) 49 | public HttpEntity post() { 50 | UUID uuid = UUID.randomUUID(); 51 | 52 | User user = new User(uuid.toString()); 53 | userRepository.save(user); 54 | 55 | HttpHeaders headers = new HttpHeaders(); 56 | headers.setLocation(linkTo(methodOn(UserTokenController.class).get(user.getToken())).toUri()); 57 | headers.add("Content-Type", MediaTypes.HAL_JSON.toString()); 58 | 59 | return new ResponseEntity<>(headers, HttpStatus.CREATED); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/cuenation/api/cue/CategoryController.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.cue; 2 | 3 | import cuenation.api.cue.domain.CueCategory; 4 | import cuenation.api.cue.persistence.CueCategoryRepository; 5 | import cuenation.api.cue.representation.CueCategoryResourceAssembler; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.data.domain.Sort; 10 | import org.springframework.hateoas.ResourceSupport; 11 | import org.springframework.http.HttpEntity; 12 | import org.springframework.http.HttpStatus; 13 | import org.springframework.http.ResponseEntity; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.RequestMethod; 16 | import org.springframework.web.bind.annotation.RestController; 17 | import org.springframework.web.context.request.WebRequest; 18 | 19 | import java.util.List; 20 | 21 | @RestController 22 | @RequestMapping("/cue-categories") 23 | public class CategoryController { 24 | 25 | final private Logger logger = LoggerFactory.getLogger(CategoryController.class); 26 | 27 | @Autowired 28 | private CueCategoryRepository cueCategoryRepository; 29 | 30 | @Autowired 31 | private CueCategoryResourceAssembler cueCategoryResourceAssembler; 32 | 33 | @RequestMapping(method = RequestMethod.GET) 34 | public HttpEntity list(WebRequest request) { 35 | List categories = cueCategoryRepository.findAll(new Sort(Sort.Direction.ASC, "name")); 36 | ResourceSupport responseBody = cueCategoryResourceAssembler.getResponse(categories); 37 | 38 | return new ResponseEntity<>(responseBody, HttpStatus.OK); 39 | } 40 | 41 | // @RequestMapping(method = RequestMethod.GET) 42 | // public HttpEntity list(WebRequest request) { 43 | // List categories = cueCategoryRepository.findAll(new Sort(Sort.Direction.ASC, "name")); 44 | // ResourceSupport responseBody = cueCategoryResourceAssembler.getResponse(categories); 45 | // 46 | // 47 | // String eTag = null; 48 | // try { 49 | // eTag = ETag.create(responseBody.toString()); 50 | // } catch (ETag.Exception e) { 51 | // logger.error(e.toString()); 52 | // e.printStackTrace(); 53 | // } 54 | // 55 | // if (request.checkNotModified(eTag)) { 56 | // return null; 57 | // } else { 58 | // return new ResponseEntity<>(responseBody, HttpStatus.OK); 59 | // } 60 | // } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/test/java/cuenation/api/AbstractContextControllerTests.java: -------------------------------------------------------------------------------- 1 | package cuenation.api; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.data.mongodb.core.MongoOperations; 7 | import org.springframework.http.MediaType; 8 | import org.springframework.test.context.ContextConfiguration; 9 | import org.springframework.test.context.web.WebAppConfiguration; 10 | import org.springframework.test.web.servlet.MockMvc; 11 | import org.springframework.test.web.servlet.MvcResult; 12 | import org.springframework.web.context.WebApplicationContext; 13 | 14 | import java.io.IOException; 15 | import java.util.regex.Matcher; 16 | import java.util.regex.Pattern; 17 | 18 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 19 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 20 | 21 | @WebAppConfiguration 22 | @ContextConfiguration(classes = {TestConfiguration.class}) 23 | abstract public class AbstractContextControllerTests { 24 | 25 | @Autowired 26 | protected WebApplicationContext wac; 27 | 28 | @Autowired 29 | protected MongoOperations operations; 30 | 31 | protected static byte[] convertObjectToJsonBytes(Object object) throws IOException { 32 | ObjectMapper mapper = new ObjectMapper(); 33 | mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); 34 | return mapper.writeValueAsBytes(object); 35 | } 36 | 37 | protected String createUser(MockMvc mockMvc) throws Exception { 38 | MvcResult mvcResult = mockMvc.perform(post("/user-tokens").accept(MediaType.APPLICATION_JSON)) 39 | .andExpect(status().isCreated()) 40 | .andReturn(); 41 | 42 | String locationHeader = mvcResult.getResponse().getHeader("Location"); 43 | return pullOutTokenFromUrl(locationHeader); 44 | } 45 | 46 | protected String pullOutTokenFromUrl(String url) { 47 | String regex = ".*([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}).*"; 48 | Pattern pattern = Pattern.compile(regex); 49 | Matcher matcher = pattern.matcher(url); 50 | 51 | 52 | String token = ""; 53 | if (matcher.matches() && matcher.groupCount() > 0) { 54 | token = matcher.group(1).trim(); 55 | } 56 | 57 | if (0 == token.length()) { 58 | String message = String.format("Could not locate token in the url: [%s]", url); 59 | throw new RuntimeException(message); 60 | } 61 | 62 | return token; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /src/test/java/cuenation/api/user/UserTokenControllerTest.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.user; 2 | 3 | import cuenation.api.AbstractContextControllerTests; 4 | import cuenation.api.user.domain.User; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.springframework.hateoas.MediaTypes; 9 | import org.springframework.http.MediaType; 10 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 11 | import org.springframework.test.web.servlet.MockMvc; 12 | import org.springframework.test.web.servlet.MvcResult; 13 | 14 | import static org.hamcrest.Matchers.anything; 15 | import static org.hamcrest.Matchers.containsString; 16 | import static org.hamcrest.Matchers.is; 17 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 18 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 19 | import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; 20 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; 21 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; 22 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 23 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 24 | import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup; 25 | 26 | @RunWith(SpringJUnit4ClassRunner.class) 27 | public class UserTokenControllerTest extends AbstractContextControllerTests { 28 | 29 | private MockMvc mockMvc; 30 | 31 | @Before 32 | public void setUp() { 33 | operations.dropCollection(User.class); 34 | mockMvc = webAppContextSetup(this.wac).build(); 35 | } 36 | 37 | @Test 38 | public void createAndCheckUserToken() throws Exception { 39 | MvcResult mvcResult = mockMvc.perform(post("/user-tokens").accept(MediaType.APPLICATION_JSON)) 40 | .andDo(print()) 41 | .andExpect(status().isCreated()) 42 | .andExpect(content().contentType(MediaTypes.HAL_JSON)) 43 | .andExpect(header().string("Location", containsString("http://localhost/user-tokens/"))) 44 | .andReturn(); 45 | 46 | String locationHeader = mvcResult.getResponse().getHeader("Location"); 47 | String token = pullOutTokenFromUrl(locationHeader); 48 | 49 | mockMvc.perform(get("/user-tokens/{token}", token).accept(MediaType.APPLICATION_JSON)) 50 | .andDo(print()) 51 | .andExpect(status().isOk()) 52 | .andExpect(content().contentType(MediaTypes.HAL_JSON)) 53 | .andExpect(jsonPath("$._links.self.href", anything())) 54 | .andExpect(jsonPath("$._links.userCueCategories.href", anything())) 55 | .andExpect(jsonPath("$._links.userCues.href", anything())) 56 | .andExpect(jsonPath("$.token", is(token))) 57 | ; 58 | } 59 | } -------------------------------------------------------------------------------- /src/main/java/cuenation/api/user/UserCueCategoryController.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.user; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import cuenation.api.cue.domain.CueCategory; 5 | import cuenation.api.user.domain.User; 6 | import cuenation.api.user.persistence.UserRepository; 7 | import cuenation.api.user.representation.UserCueCategoryResource; 8 | import cuenation.api.user.representation.UserCueCategoryResourceAssembler; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.hateoas.MediaTypes; 11 | import org.springframework.hateoas.ResourceSupport; 12 | import org.springframework.http.HttpEntity; 13 | import org.springframework.http.HttpHeaders; 14 | import org.springframework.http.HttpStatus; 15 | import org.springframework.http.ResponseEntity; 16 | import org.springframework.web.bind.annotation.*; 17 | 18 | import java.util.List; 19 | 20 | @RestController 21 | @RequestMapping("/user-tokens/{token}/cue-categories") 22 | public class UserCueCategoryController { 23 | 24 | @Autowired 25 | private UserRepository userRepository; 26 | 27 | @Autowired 28 | private UserCueCategoryResourceAssembler userCueCategoryResourceAssembler; 29 | 30 | @RequestMapping(method = RequestMethod.GET) 31 | public HttpEntity list(@PathVariable("token") String token) { 32 | User user = userRepository.findByToken(token); 33 | 34 | ResponseEntity responseEntity; 35 | if (user == null) { 36 | responseEntity = new ResponseEntity<>(HttpStatus.NOT_FOUND); 37 | } else { 38 | List categories = user.getCategories(); 39 | ResourceSupport response = userCueCategoryResourceAssembler.getResponse(user, categories); 40 | 41 | responseEntity = new ResponseEntity<>(response, HttpStatus.OK); 42 | } 43 | 44 | return responseEntity; 45 | } 46 | 47 | @RequestMapping(method = RequestMethod.PUT) 48 | public HttpEntity> put( 49 | @PathVariable("token") String token, 50 | @RequestBody PutRequest request) { 51 | User user = userRepository.findByToken(token); 52 | 53 | ResponseEntity responseEntity; 54 | if (user == null) { 55 | responseEntity = new ResponseEntity<>(HttpStatus.NOT_FOUND); 56 | } else { 57 | userRepository.saveUserCategories(user, request); 58 | 59 | HttpHeaders headers = new HttpHeaders(); 60 | headers.add("Content-Type", MediaTypes.HAL_JSON.toString()); 61 | 62 | responseEntity = new ResponseEntity<>(headers, HttpStatus.OK); 63 | } 64 | 65 | return responseEntity; 66 | } 67 | 68 | public static class PutRequest { 69 | 70 | @JsonProperty("ids") 71 | private List ids; 72 | 73 | public List getIds() { 74 | return ids; 75 | } 76 | 77 | @Override 78 | public String toString() { 79 | return "PutRequest{" + 80 | "ids=" + ids + 81 | '}'; 82 | } 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/cuenation/api/user/UserCueController.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.user; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import cuenation.api.cue.domain.UserCue; 5 | import cuenation.api.cue.persistence.UserCueRepository; 6 | import cuenation.api.cue.service.CueService; 7 | import cuenation.api.user.domain.User; 8 | import cuenation.api.user.persistence.UserRepository; 9 | import cuenation.api.user.representation.UserCueResourceAssembler; 10 | import cuenation.api.user.service.UserCueService; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.data.domain.Page; 13 | import org.springframework.data.domain.Pageable; 14 | import org.springframework.data.domain.Sort; 15 | import org.springframework.data.web.PageableDefault; 16 | import org.springframework.data.web.PagedResourcesAssembler; 17 | import org.springframework.hateoas.PagedResources; 18 | import org.springframework.http.HttpEntity; 19 | import org.springframework.http.HttpStatus; 20 | import org.springframework.http.ResponseEntity; 21 | import org.springframework.web.bind.annotation.*; 22 | 23 | import java.util.List; 24 | 25 | @RestController 26 | @RequestMapping("/user-tokens/{token}/cues") 27 | public class UserCueController { 28 | 29 | @Autowired 30 | private UserRepository userRepository; 31 | 32 | @Autowired 33 | private UserCueRepository userCueRepository; 34 | 35 | @Autowired 36 | private CueService cueService; 37 | 38 | @Autowired 39 | private UserCueService userCueService; 40 | 41 | @Autowired 42 | private UserCueResourceAssembler userCueResourceAssembler; 43 | 44 | @RequestMapping(method = RequestMethod.GET) 45 | public HttpEntity> list(@PathVariable("token") 46 | String token, 47 | @PageableDefault(sort = "createdAt", direction = Sort.Direction.DESC) 48 | Pageable pageable, 49 | PagedResourcesAssembler assembler) { 50 | User user = userRepository.findByToken(token); 51 | 52 | ResponseEntity> responseEntity; 53 | if (user == null) { 54 | responseEntity = new ResponseEntity<>(HttpStatus.NOT_FOUND); 55 | } else { 56 | // first find out if user has new cues 57 | cueService.updateUserCues(user); 58 | 59 | 60 | Page userCues = userCueRepository.findAllByUserAndViewedAtExists(user, false, pageable); 61 | responseEntity = new ResponseEntity>( 62 | assembler.toResource(userCues, userCueResourceAssembler), HttpStatus.OK); 63 | } 64 | 65 | return responseEntity; 66 | } 67 | 68 | @RequestMapping(method = RequestMethod.PUT) 69 | public HttpEntity put( 70 | @PathVariable("token") String token, 71 | @RequestBody PuRequest request) { 72 | 73 | User user = userRepository.findByToken(token); 74 | 75 | ResponseEntity responseEntity; 76 | if (user == null) { 77 | responseEntity = new ResponseEntity<>(HttpStatus.NOT_FOUND); 78 | } else { 79 | userCueService.markCuesAsViewed(user, request); 80 | responseEntity = new ResponseEntity<>(HttpStatus.OK); 81 | } 82 | 83 | return responseEntity; 84 | } 85 | 86 | public static class PuRequest { 87 | 88 | @JsonProperty("ids") 89 | private List ids; 90 | 91 | public List getIds() { 92 | return ids; 93 | } 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/test/java/cuenation/api/cue/CategoryControllerTest.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.cue; 2 | 3 | import cuenation.api.AbstractContextControllerTests; 4 | import cuenation.api.cue.domain.CueCategory; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.springframework.hateoas.MediaTypes; 9 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 10 | import org.springframework.test.web.servlet.MockMvc; 11 | import org.springframework.test.web.servlet.MvcResult; 12 | import org.springframework.web.filter.ShallowEtagHeaderFilter; 13 | 14 | import java.util.Arrays; 15 | import java.util.List; 16 | 17 | import static org.hamcrest.CoreMatchers.notNullValue; 18 | import static org.hamcrest.Matchers.equalTo; 19 | import static org.hamcrest.Matchers.hasSize; 20 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 21 | import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; 22 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; 23 | import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup; 24 | 25 | @RunWith(SpringJUnit4ClassRunner.class) 26 | public class CategoryControllerTest extends AbstractContextControllerTests { 27 | 28 | private MockMvc mockMvc; 29 | 30 | @Before 31 | public void setUp() { 32 | operations.dropCollection(CueCategory.class); 33 | mockMvc = webAppContextSetup(this.wac) 34 | // @TODO: somehow move it to the `TestConfiguration` and merge with `WebConfig` 35 | .addFilter(new ShallowEtagHeaderFilter(), "/cue-categories") 36 | .build(); 37 | } 38 | 39 | @Test 40 | public void getCategories() throws Exception { 41 | List ids = savePreparedCategories(); 42 | 43 | MvcResult mvcResult = mockMvc.perform(get("/cue-categories")) 44 | .andDo(print()) 45 | .andExpect(status().isOk()) 46 | .andExpect(header().string("ETag", notNullValue())) 47 | .andExpect(content().contentType(MediaTypes.HAL_JSON)) 48 | .andExpect(jsonPath("$._embedded.cueCategories", hasSize(2))) 49 | .andExpect(jsonPath("$._embedded.cueCategories[0].id", equalTo(ids.get(0)))) 50 | .andExpect(jsonPath("$._embedded.cueCategories[0].name", equalTo("name1"))) 51 | .andExpect(jsonPath("$._embedded.cueCategories[0].host", equalTo("host1"))) 52 | .andExpect(jsonPath("$._embedded.cueCategories[0].link", equalTo("link1"))) 53 | .andExpect(jsonPath("$._embedded.cueCategories[1].id", equalTo(ids.get(1)))) 54 | .andExpect(jsonPath("$._embedded.cueCategories[1].name", equalTo("name2"))) 55 | .andExpect(jsonPath("$._embedded.cueCategories[1].host", equalTo(null))) 56 | .andExpect(jsonPath("$._embedded.cueCategories[1].link", equalTo("link2"))) 57 | .andReturn(); 58 | 59 | String eTag = mvcResult.getResponse().getHeader("ETag"); 60 | 61 | mockMvc.perform(get("/cue-categories").header("If-None-Match", eTag)) 62 | .andDo(print()) 63 | .andExpect(status().isNotModified()) 64 | .andExpect(content().string("")); 65 | } 66 | 67 | private List savePreparedCategories() { 68 | CueCategory category1 = new CueCategory("name1", "host1", "link1"); 69 | CueCategory category2 = new CueCategory("name2", "link2"); 70 | 71 | operations.save(category1); 72 | operations.save(category2); 73 | 74 | return Arrays.asList(category1.getId(), category2.getId()); 75 | } 76 | 77 | } -------------------------------------------------------------------------------- /src/main/java/cuenation/api/cue/service/CueService.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.cue.service; 2 | 3 | import com.sun.syndication.feed.synd.SyndFeed; 4 | import com.sun.syndication.fetcher.FetcherException; 5 | import com.sun.syndication.io.FeedException; 6 | import cuenation.api.cue.domain.Cue; 7 | import cuenation.api.cue.domain.CueCategory; 8 | import cuenation.api.cue.persistence.CueCategoryRepository; 9 | import cuenation.api.cue.persistence.CueRepository; 10 | import cuenation.api.cue.persistence.UserCueRepository; 11 | import cuenation.api.user.domain.User; 12 | import org.apache.commons.logging.Log; 13 | import org.apache.commons.logging.LogFactory; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.stereotype.Service; 16 | 17 | import java.io.IOException; 18 | import java.util.Calendar; 19 | import java.util.Collections; 20 | import java.util.Date; 21 | import java.util.List; 22 | 23 | //import org.slf4j.Logger; 24 | //import org.slf4j.LoggerFactory; 25 | 26 | @Service 27 | public class CueService { 28 | 29 | // Logger logger = LoggerFactory.getLogger(CueService.class); 30 | 31 | private static final Log logger = LogFactory.getLog(CueService.class); 32 | 33 | @Autowired 34 | private UserCueRepository userCueRepository; 35 | 36 | @Autowired 37 | private CueRepository cueRepository; 38 | 39 | @Autowired 40 | private CueCategoryRepository cueCategoryRepository; 41 | 42 | @Autowired 43 | private RssFetcher fetcher; 44 | 45 | @Autowired 46 | private RssParser parser; 47 | 48 | @Autowired 49 | private CategoriesHtmlParser categoriesHtmlParser; 50 | 51 | public void updateUserCues(User user) { 52 | // potentially there may be user cues which don't belong to the new categories 53 | // How do I know if there are new categories? Actually I don't, 54 | // that's why we process this clean up upon every request 55 | userCueRepository.removeByUserAndCategoryNotIn(user, user.getCategories()); 56 | 57 | // let's find all the cues not later than a month ago 58 | // as we don't want to clutter user notifications with a year ago cues 59 | Calendar calendar = Calendar.getInstance(); 60 | calendar.setTime(new Date()); 61 | calendar.add(Calendar.MONTH, -1); 62 | Date laterThan = calendar.getTime(); 63 | 64 | List cues = cueRepository.findByCategoryInAndCreatedAtGreaterThan(user.getCategories(), laterThan); 65 | userCueRepository.reSaveCues(user, cues); 66 | } 67 | 68 | public int updateCues() { 69 | int cuesNumber = 0; 70 | 71 | try { 72 | SyndFeed feed = fetcher.fetch(); 73 | 74 | List cues = parser.parse(feed); 75 | for (Cue cue : cues) { 76 | if (cueRepository.saveIfNotExists(cue)) { 77 | cuesNumber++; 78 | } 79 | } 80 | } catch (FetcherException | FeedException | IOException e) { 81 | logger.error(e.getMessage()); 82 | logger.error(e.getStackTrace()); 83 | } 84 | 85 | return cuesNumber; 86 | } 87 | 88 | public int updateCueCategories() { 89 | int cueCategoriesNumber = 0; 90 | 91 | List categories = Collections.EMPTY_LIST; 92 | try { 93 | categories = categoriesHtmlParser.parse(); 94 | } catch (IOException e) { 95 | logger.error(e.getMessage()); 96 | } 97 | 98 | for (CueCategory category : categories) { 99 | if (cueCategoryRepository.saveIfNotExists(category)) { 100 | cueCategoriesNumber++; 101 | } 102 | } 103 | 104 | return cueCategoriesNumber; 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/test/java/cuenation/api/user/UserCueCategoryControllerTest.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.user; 2 | 3 | import cuenation.api.AbstractContextControllerTests; 4 | import cuenation.api.cue.domain.CueCategory; 5 | import cuenation.api.user.domain.User; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.springframework.http.MediaType; 10 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 11 | import org.springframework.test.util.ReflectionTestUtils; 12 | import org.springframework.test.web.servlet.MockMvc; 13 | 14 | import java.util.Arrays; 15 | import java.util.List; 16 | 17 | import static org.hamcrest.CoreMatchers.equalTo; 18 | import static org.hamcrest.Matchers.hasSize; 19 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 20 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; 21 | import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; 22 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 23 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 24 | import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup; 25 | 26 | @RunWith(SpringJUnit4ClassRunner.class) 27 | public class UserCueCategoryControllerTest extends AbstractContextControllerTests { 28 | 29 | private MockMvc mockMvc; 30 | 31 | @Before 32 | public void setUp() { 33 | operations.dropCollection(User.class); 34 | operations.dropCollection(CueCategory.class); 35 | mockMvc = webAppContextSetup(this.wac).build(); 36 | } 37 | 38 | @Test 39 | public void userNotFound() throws Exception { 40 | mockMvc.perform(get("/user-tokens/{token}/cue-categories", "non-existing-token") 41 | .accept(MediaType.APPLICATION_JSON)) 42 | .andExpect(status().isNotFound()) 43 | ; 44 | } 45 | 46 | @Test 47 | public void userCategoriesNotSet() throws Exception { 48 | String token = createUser(mockMvc); 49 | 50 | mockMvc.perform(get("/user-tokens/{token}/cue-categories", token) 51 | .accept(MediaType.APPLICATION_JSON)) 52 | .andDo(print()) 53 | .andExpect(status().isOk()) 54 | ; 55 | } 56 | 57 | @Test 58 | public void createAndGetUserCategories() throws Exception { 59 | String token = createUser(mockMvc); 60 | 61 | List ids = savePreparedCategories(); 62 | UserCueCategoryController.PutRequest request = new UserCueCategoryController.PutRequest(); 63 | ReflectionTestUtils.setField(request, "ids", ids); 64 | 65 | 66 | mockMvc.perform(put("/user-tokens/{token}/cue-categories", token) 67 | .accept(MediaType.APPLICATION_JSON) 68 | .contentType(MediaType.APPLICATION_JSON) 69 | .content(convertObjectToJsonBytes(request))) 70 | .andDo(print()) 71 | .andExpect(status().isOk()) 72 | ; 73 | 74 | mockMvc.perform(get("/user-tokens/{token}/cue-categories", token) 75 | .accept(MediaType.APPLICATION_JSON)) 76 | .andDo(print()) 77 | .andExpect(status().isOk()) 78 | // @TODO: must be "application/hal+json" 79 | // .andExpect(content().contentType(MediaTypes.HAL_JSON)) 80 | .andExpect(jsonPath("$._embedded.userCueCategories", hasSize(2))) 81 | .andExpect(jsonPath("$._embedded.userCueCategories[0].name", equalTo("name1"))) 82 | .andExpect(jsonPath("$._embedded.userCueCategories[0].link", equalTo("link1"))) 83 | .andExpect(jsonPath("$._embedded.userCueCategories[0].host", equalTo("host1"))) 84 | .andExpect(jsonPath("$._embedded.userCueCategories[1].name", equalTo("name2"))) 85 | .andExpect(jsonPath("$._embedded.userCueCategories[1].host", equalTo(null))) 86 | .andExpect(jsonPath("$._embedded.userCueCategories[1].link", equalTo("link2"))) 87 | ; 88 | } 89 | 90 | private List savePreparedCategories() { 91 | CueCategory category1 = new CueCategory("name1", "host1", "link1"); 92 | CueCategory category2 = new CueCategory("name2", "link2"); 93 | 94 | operations.save(category1); 95 | operations.save(category2); 96 | 97 | return Arrays.asList(category1.getId(), category2.getId()); 98 | } 99 | } -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /src/test/java/cuenation/api/user/UserCueControllerTest.java: -------------------------------------------------------------------------------- 1 | package cuenation.api.user; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import cuenation.api.AbstractContextControllerTests; 6 | import cuenation.api.cue.domain.Cue; 7 | import cuenation.api.cue.domain.CueCategory; 8 | import cuenation.api.cue.domain.UserCue; 9 | import cuenation.api.user.domain.User; 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | import org.junit.runner.RunWith; 13 | import org.springframework.http.MediaType; 14 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 15 | import org.springframework.test.util.ReflectionTestUtils; 16 | import org.springframework.test.web.servlet.MockMvc; 17 | import org.springframework.test.web.servlet.MvcResult; 18 | 19 | import java.io.IOException; 20 | import java.util.*; 21 | 22 | import static org.hamcrest.Matchers.equalTo; 23 | import static org.hamcrest.Matchers.hasSize; 24 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 25 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; 26 | import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; 27 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 28 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 29 | import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup; 30 | 31 | @RunWith(SpringJUnit4ClassRunner.class) 32 | public class UserCueControllerTest extends AbstractContextControllerTests { 33 | 34 | private MockMvc mockMvc; 35 | 36 | @Before 37 | public void setUp() { 38 | operations.dropCollection(User.class); 39 | operations.dropCollection(CueCategory.class); 40 | operations.dropCollection(Cue.class); 41 | operations.dropCollection(UserCue.class); 42 | mockMvc = webAppContextSetup(this.wac).build(); 43 | } 44 | 45 | @Test 46 | public void userNotFound() throws Exception { 47 | mockMvc.perform(get("/user-tokens/{token}/cues", "non-existing-token") 48 | .accept(MediaType.APPLICATION_JSON)) 49 | .andExpect(status().isNotFound()) 50 | ; 51 | } 52 | 53 | @Test 54 | public void noUserCues() throws Exception { 55 | String token = createUser(mockMvc); 56 | 57 | mockMvc.perform(get("/user-tokens/{token}/cues", token) 58 | .accept(MediaType.APPLICATION_JSON)) 59 | .andDo(print()) 60 | .andExpect(status().isOk()) 61 | // @TODO: must be "application/hal+json" 62 | // .andExpect(content().contentType(MediaTypes.HAL_JSON)) 63 | .andExpect(jsonPath("$.page.totalElements", equalTo(0))) 64 | .andExpect(jsonPath("$.page.totalPages", equalTo(0))) 65 | .andExpect(jsonPath("$.page.number", equalTo(0))) 66 | ; 67 | } 68 | 69 | @Test 70 | public void createAndGetUserCues() throws Exception { 71 | String token = createUser(mockMvc); 72 | 73 | List categories = savePreparedCategories(); 74 | subscribeForCategories(token, categories); 75 | // implies bg task processed by the remote sever 76 | savePreparedCues(categories); 77 | 78 | 79 | // fetch 2 cues we subscribed on through categories 80 | MvcResult cuesResult = mockMvc.perform(get("/user-tokens/{token}/cues", token) 81 | .accept(MediaType.APPLICATION_JSON)) 82 | .andDo(print()) 83 | .andExpect(status().isOk()) 84 | .andExpect(jsonPath("$.page.totalElements", equalTo(2))) 85 | .andExpect(jsonPath("$.page.totalPages", equalTo(1))) 86 | .andExpect(jsonPath("$.page.number", equalTo(0))) 87 | // also pay attention we can not verify cue IDs as coming IDs are actually belong UserCue 88 | // objects, not Cue objects 89 | .andExpect(jsonPath("$._embedded.userCues", hasSize(2))) 90 | .andExpect(jsonPath("$._embedded.userCues[0].title", equalTo("title1"))) 91 | .andExpect(jsonPath("$._embedded.userCues[0].link", equalTo("link1"))) 92 | .andExpect(jsonPath("$._embedded.userCues[0].categoryId", equalTo(categories.get(0).getId()))) 93 | .andExpect(jsonPath("$._embedded.userCues[1].title", equalTo("title2"))) 94 | .andExpect(jsonPath("$._embedded.userCues[1].link", equalTo("link2"))) 95 | .andExpect(jsonPath("$._embedded.userCues[1].categoryId", equalTo(categories.get(1).getId()))) 96 | .andReturn(); 97 | 98 | String content = cuesResult.getResponse().getContentAsString(); 99 | UserCueController.PuRequest request = prepareRequestToSetCueAsViewed(content); 100 | 101 | // now let's mark one cue as viewed 102 | mockMvc.perform(put("/user-tokens/{token}/cues", token) 103 | .accept(MediaType.APPLICATION_JSON) 104 | .contentType(MediaType.APPLICATION_JSON) 105 | .content(convertObjectToJsonBytes(request))) 106 | .andDo(print()) 107 | .andExpect(status().isOk()) 108 | ; 109 | 110 | 111 | // then grab user cues again and make sure now it returns just one cue 112 | mockMvc.perform(get("/user-tokens/{token}/cues", token) 113 | .accept(MediaType.APPLICATION_JSON)) 114 | .andDo(print()) 115 | .andExpect(status().isOk()) 116 | .andExpect(jsonPath("$.page.totalElements", equalTo(1))) 117 | .andExpect(jsonPath("$.page.totalPages", equalTo(1))) 118 | .andExpect(jsonPath("$.page.number", equalTo(0))) 119 | // also pay attention we can not verify cue IDs as coming IDs are actually belong UserCue 120 | // objects, not Cue objects 121 | .andExpect(jsonPath("$._embedded.userCues", hasSize(1))) 122 | ; 123 | } 124 | 125 | private List savePreparedCategories() { 126 | CueCategory category1 = new CueCategory("name1", "link1"); 127 | CueCategory category2 = new CueCategory("name2", "link2"); 128 | 129 | operations.save(category1); 130 | operations.save(category2); 131 | 132 | return Arrays.asList(category1, category2); 133 | } 134 | 135 | private void subscribeForCategories(String token, List categories) throws Exception { 136 | List ids = Arrays.asList(categories.get(0).getId(), categories.get(1).getId()); 137 | UserCueCategoryController.PutRequest request = new UserCueCategoryController.PutRequest(); 138 | ReflectionTestUtils.setField(request, "ids", ids); 139 | 140 | mockMvc.perform(put("/user-tokens/{token}/cue-categories", token) 141 | .accept(MediaType.APPLICATION_JSON) 142 | .contentType(MediaType.APPLICATION_JSON) 143 | .content(convertObjectToJsonBytes(request))) 144 | .andDo(print()) 145 | .andExpect(status().isOk()) 146 | ; 147 | } 148 | 149 | private void savePreparedCues(List categories) { 150 | Calendar calendar = Calendar.getInstance(); 151 | 152 | calendar.setTime(new Date()); 153 | calendar.add(Calendar.DAY_OF_MONTH, -5); 154 | Date createdAt1 = calendar.getTime(); 155 | 156 | calendar.setTime(new Date()); 157 | calendar.add(Calendar.DAY_OF_MONTH, -4); 158 | Date createdAt2 = calendar.getTime(); 159 | 160 | Cue cue1 = new Cue("title1", "link1", createdAt2, categories.get(0)); 161 | Cue cue2 = new Cue("title2", "link2", createdAt1, categories.get(1)); 162 | 163 | operations.save(cue1); 164 | operations.save(cue2); 165 | } 166 | 167 | private UserCueController.PuRequest prepareRequestToSetCueAsViewed(String jsonData) throws IOException { 168 | ObjectMapper mapper = new ObjectMapper(); 169 | JsonNode rootNode = mapper.readTree(jsonData); 170 | Iterator elements = rootNode.path("_embedded").path("userCues").elements(); 171 | 172 | String firstId = null; 173 | while (elements.hasNext()) { 174 | JsonNode cue = elements.next(); 175 | firstId = String.valueOf(cue.path("id").asText()); 176 | break; 177 | } 178 | 179 | UserCueController.PuRequest request = new UserCueController.PuRequest(); 180 | ReflectionTestUtils.setField(request, "ids", Arrays.asList(firstId)); 181 | 182 | return request; 183 | } 184 | 185 | } -------------------------------------------------------------------------------- /dev/feed.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CueNation | Newest Cuesheets 6 | Newest cuesheets on Cuesite V1 7 | http://cuenation.com 8 | en-us 9 | Fri, 03 Jan 2014 00:46:10 +0000 10 | 11 | Armin van Buuren - A State of Trance 646 (2014-01-02) (RT) 12 | http://cuenation.com?page=tracklist&folder=asot&filename=Armin+van+Buuren+-+A+State+Of+Trance+646+%282014-01-02%29.cue 13 | A State of Trance 14 | asot/Armin van Buuren - A State Of Trance 646 (2014-01-02).cue 15 | Fri, 03 Jan 2014 00:46:10 +0000 16 |
18 | 01. A State of Trance - Intro
19 | 02. Ilan Bluestone - Spheres
20 | 03. David Gravell - Nighthawk
21 | 04. Luke Bond feat. Roxanne Emery - On Fire
22 | 05. Jerome Isma-Ae - Hold That Sucker Down (Sick Individuals Remix)
23 | 06. Icona Pop - Just Another Night (Marcus Schossow Remix)
24 | 07. Mark Sixma & Jerome Isma-Ae - Refused
25 | 08. Yuri Kane feat. Sopheary - Obsession
26 | 09. George Acosta, Shakeh - These Dreams
27 | 10. Kevin Charm - Sydneysider
28 | 11. Cosmic Gate - So Get Up (Ben Gold Remix)
29 | 12. Eximinds - Phoenix [Tune Of The Week]
30 | 13. Arisen Flame - Orion
31 | 14. Svenson & Gielen - Sunlight Theory (Richard Durand Rework)
32 | 15. Gaia - Aisha (Hazem Beltagui Melo Bootleg) [Future Favorite]
33 | 16. M.I.K.E. - The Motive
34 | 17. Armin van Buuren - Precious (Mike Saint Jules Bootleg)
35 | 18. Max Graham vs. Maarten De Jong - Lekker
36 | 19. Thomas Bronzwaer - Sundown
37 | 20. Armin van Buuren - Save My Night
38 | 21. Stoneface & Terminal - Skyfall
39 | 22. Dirt Devils - The Drill (MaRLo Remix)
40 | 23. Maurice Lekkerkerkerker - Madeline
41 | 24. Allen Watts - Lifelines
42 | 25. Danny Young - The Shadow
43 | 26. Obsidian Radio Feat. Jan Johnston - Love Like This (Allen & Envy Remix)
44 | 27. Manuel Le Saux - One (Ferry Tayle Remix)
45 | 28. Cold Rush - Horizon
46 | 29. E.T Project - Northern Lights
47 | 30. The Thrillseekers - When All Else Fails
48 | 31. Manuel Le Saux - Phoenix (James Dymond Remix)
49 | 32. Paul Denton & McGregor - Crossing Borders
50 | 33. Andy Moor & Adam White present Whiteroom - The Whiteroom [ASOT Radio Classic]
51 | 34. A State of Trance - Outro
52 | ]]>
53 |
54 | 55 | Friction - Drum & Bass Show (2013-29-12) (Best Of 2013) [TMB] 56 | http://cuenation.com?page=tracklist&folder=bbcradio1&filename=2013+12+29+Friction+Best+of+2013.cue 57 | BBC Radio 1 Shows 58 | bbcradio1/2013 12 29 Friction Best of 2013.cue 59 | Fri, 03 Jan 2014 00:37:15 +0000 60 |
62 | 01. Camo & Krooked - All Night
63 | 02. Sub Focus feat. Alex Clare - Endorphins (Sub Focus vs. Fred V & Grafix Remix)
64 | 03. Technimatic - Bristol
65 | 04. Amy Steele feat. Mario - Eyes On You (SpectraSoul Remix)
66 | 05. Brookes Brothers feat. Chrom3 - Carry Me On
67 | 06. Wilkinson - Take You Higher
68 | 07. Friction & Skream feat. Scrufizzer, P Money & Riko Dan - Kingpin (Calyx & TeeBee Remix)
69 | 08. Ulterior Motive & Judda - Timekeeper
70 | 09. DC Breaks - Swag
71 | 10. Sigma feat. Doctor - Rudeboy
72 | 11. Dub Phizix feat. Skittles - I'm A Creator
73 | 12. London Grammar - Nightcall (Special Request VIP Edit)
74 | 13. Sam Binga feat. Redders - AYO
75 | 14. Nu:Logic feat. Lifford - Everlasting Days
76 | 15. Chase & Status feat. Louis M^ttrs - Lost & Not Found
77 | 16. DJ Die & Jenna G - 1000 Soul Songs
78 | 17. Etherwood - Weightless
79 | 18. Camo & Krooked - Loving You Is Easy (S.P.Y Remix)
80 | 19. TC - Get Down Low
81 | 20. Knife Party - LRAD (The Prototypes Bootleg)
82 | 21. Rockwell - Detroit
83 | 22. DJ Hazard - Time Tripping
84 | 23. InsideInfo & Mefjus - Mythos
85 | 24. Original Sin - Never Gonna Change
86 | 25. Black Sun Empire - Arrakis (Noisia Remix)
87 | 26. Loadstar feat. Jakes - Warrior
88 | 27. Dexcell feat. Katie's Ambition - Close Your Eyes
89 | 28. Wilkinson - Afterglow
90 | 29. Andy C - Workout
91 | 30. Kove - Searching
92 | 31. Chase & Status feat. Jacob Banks - Alive
93 | ]]>
94 |
95 | 96 | Armin van Buuren - A State of Trance 646 (2014-01-02) [MM] (SBD) 97 | http://cuenation.com?page=tracklist&folder=asot&filename=armin_van_buuren_-_a_state_of_trance_646_-_2014-01-02.cue 98 | A State of Trance 99 | asot/armin_van_buuren_-_a_state_of_trance_646_-_2014-01-02.cue 100 | Thu, 02 Jan 2014 22:51:19 +0000 101 |
103 | 01. A State of Trance - Intro
104 | 02. Ilan Bluestone - Spheres
105 | 03. David Gravell - Nighthawk
106 | 04. Luke Bond feat. Roxanne Emery - On Fire
107 | 05. Jerome Isma-Ae - Hold That Sucker Down (Sick Individuals Remix)
108 | 06. Icona Pop - Just Another Night (Marcus Schossow Remix)
109 | 07. Mark Sixma & Jerome Isma-Ae - Refused
110 | 08. Yuri Kane feat. Sopheary - Obsession
111 | 09. George Acosta, Shakeh - These Dreams
112 | 10. Kevin Charm - Sydneysider
113 | 11. Cosmic Gate - So Get Up (Ben Gold Remix)
114 | 12. Eximinds - Phoenix [Tune of The Week]
115 | 13. Arisen Flame - Orion
116 | 14. Svenson & Gielen - Sunlight Theory (Richard Durand Rework)
117 | 15. Armin van Buuren pres. Gaia - Aisha (Hazem Beltagui Melo Bootleg) [Future Favorite]
118 | 16. M.I.K.E. - The Motive
119 | 17. Armin van Buuren - Precious (Mike Saint-Jules Bootleg)
120 | 18. Max Graham vs. Maarten De Jong - Lekker
121 | 19. Thomas Bronzwaer - Sundown
122 | 20. Armin van Buuren - Save My Night
123 | 21. Stoneface & Terminal - Skyfall
124 | 22. Dirt Devils - The Drill (MaRLo Remix)
125 | 23. Maurice Lekkerkerkerker - Madeline
126 | 24. Allen Watts - Lifelines
127 | 25. Danny Young - The Shadow
128 | 26. Obsidian Radio Feat. Jan Johnston - Love Like This (Beautiful Needs) (Allen & Envy Remix)
129 | 27. Manuel Le Saux - One (Ferry Tayle Remix)
130 | 28. Cold Rush - Horizon
131 | 29. E.T Project - Northern Lights
132 | 30. The Thrillseekers - When All Else Fails
133 | 31. Manuel Le Saux - Phoenix (James Dymond Remix)
134 | 32. Paul Denton & McGregor - Crossing Borders
135 | 33. Andy Moor & Adam White pres. Whiteroom - The Whiteroom [ASOT Radio Classic]
136 | 34. A State of Trance - Outro
137 | ]]>
138 |
139 | 140 | Armin van Buuren - A State of Trance 646 (2014-01-02) [Inspiron] 141 | http://cuenation.com?page=tracklist&folder=asot&filename=Armin+van+Buuren+-+A+State+of+Trance+Episode+646+%28Inspiron%29.cue 142 | A State of Trance 143 | asot/Armin van Buuren - A State of Trance Episode 646 (Inspiron).cue 144 | Thu, 02 Jan 2014 22:13:42 +0000 145 |
147 | 01. A State of Trance - Intro
148 | 02. Ilan Bluestone - Spheres
149 | 03. David Gravell - Nighthawk
150 | 04. Luke Bond feat. Roxanne Emery - On Fire
151 | 05. Jerome Isma-Ae - Hold That Sucker Down (Sick Individuals Remix)
152 | 06. Icona Pop - Just Another Night (Marcus Schossow Remix)
153 | 07. Mark Sixma & Jerome Isma-Ae - Refused
154 | 08. Yuri Kane feat. Sopheary - Obsession
155 | 09. George Acosta, Shakeh - These Dreams
156 | 10. Kevin Charm - Sydneysider
157 | 11. Cosmic Gate - So Get Up (Ben Gold Remix)
158 | 12. Eximinds - Phoenix [Tune of The Week]
159 | 13. Arisen Flame - Orion
160 | 14. Svenson & Gielen - Sunlight Theory (Richard Durand Rework)
161 | 15. Gaia - Aisha (Hazem Beltagui Melo Bootleg) [Future Favorite]
162 | 16. M.I.K.E. - The Motive
163 | 17. Armin van Buuren - Precious (Mike Saint-Jules Bootleg)
164 | 18. Max Graham vs. Maarten De Jong - Lekker
165 | 19. Thomas Bronzwaer - Sundown
166 | 20. Armin van Buuren - Save My Night
167 | 21. Stoneface & Terminal - Skyfall
168 | 22. Dirt Devils - The Drill (MaRLo Remix)
169 | 23. Maurice Lekkerkerkerker - Madeline
170 | 24. Allen Watts - Lifelines
171 | 25. Danny Young - The Shadow
172 | 26. Obsidian Radio Feat. Jan Johnston - Love Like This (Beautiful Needs) (Allen & Envy Remix)
173 | 27. Manuel Le Saux - One (Ferry Tayle Remix)
174 | 28. Cold Rush - Horizon
175 | 29. E.T Project - Northern Lights
176 | 30. The Thrillseekers - When All Else Fails
177 | 31. Manuel Le Saux - Phoenix (James Dymond Remix)
178 | 32. Paul Denton & McGregor - Crossing Borders
179 | 33. Andy Moor & Adam White present Whiteroom - The Whiteroom [ASOT Radio Classic]
180 | 34. A State of Trance - Outro
181 | ]]>
182 |
183 |
184 |
185 | --------------------------------------------------------------------------------