diff options
author | Orangerot <purple@orangerot.dev> | 2024-06-19 00:14:49 +0200 |
---|---|---|
committer | Orangerot <purple@orangerot.dev> | 2024-06-27 12:11:14 +0200 |
commit | 5b8851b6c268d0e93c158908fbfae9f8473db5ff (patch) | |
tree | 7010eb85d86fa2da06ea4ffbcdb01a685d502ae8 /pse-server/src/main/java/org/psesquared/server/subscriptions/api |
Diffstat (limited to 'pse-server/src/main/java/org/psesquared/server/subscriptions/api')
9 files changed, 715 insertions, 0 deletions
diff --git a/pse-server/src/main/java/org/psesquared/server/subscriptions/api/controller/SubscriptionController.java b/pse-server/src/main/java/org/psesquared/server/subscriptions/api/controller/SubscriptionController.java new file mode 100644 index 0000000..1ffe137 --- /dev/null +++ b/pse-server/src/main/java/org/psesquared/server/subscriptions/api/controller/SubscriptionController.java @@ -0,0 +1,155 @@ +package org.psesquared.server.subscriptions.api.controller; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.psesquared.server.subscriptions.api.service.SubscriptionService; +import org.psesquared.server.util.UpdateUrlsWrapper; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * This is a controller class for the Subscription API that handles the requests + * from the client concerning adding subscriptions, removing subscriptions and + * getting all current subscriptions. + * In the end an appropriate response is sent back to the user. + */ +@RestController +@RequiredArgsConstructor +public class SubscriptionController { + + /** + * The response for uploading subscriptions successfully. + */ + private static final String UPLOAD_SUCCESS = ""; + + /** + * The service class that this controller calls to further process requests. + */ + private final SubscriptionService subscriptionService; + + /** + * It takes a list of strings containing the URLs of all subscribed podcasts, + * and saves them to the database. + * + * @param username The username of the user + * @param deviceId The device ID of the device that is uploading the + * subscriptions (will be ignored in this implementation) + * @param subscriptions A list of strings, each string is the URL to a podcast + * RSS-Feed that was subscribed + * @return The response containing an empty String with + * <br> + * {@link HttpStatus#OK} on success, + * <br> + * {@link HttpStatus#NOT_FOUND} user not found + */ + @PutMapping(path = "/subscriptions/{username}/{deviceId}.json") + public ResponseEntity<String> uploadSubscriptions( + @PathVariable final String username, + @PathVariable final String deviceId, + @RequestBody final List<String> subscriptions) { + HttpStatus status + = subscriptionService.uploadSubscriptions(username, subscriptions); + return new ResponseEntity<>(UPLOAD_SUCCESS, status); + } + + /** + * This function returns a list of subscriptions for a given user. + * + * @param username The username of the user whose subscriptions you want + * to retrieve + * @param deviceId This is the unique identifier for the device of the + * user whose subscriptions are asked for + * (will be ignored in this implementation) + * @param functionJsonp This parameter is not supported in this implementation + * and is thus ignored + * @return A list of strings containing the RSS-Feed URLs of all subscribed + * podcasts + */ + @GetMapping(path = {"/subscriptions/{username}.json", + "/subscriptions/{username}/{deviceId}.json"}) + public ResponseEntity<List<String>> getSubscriptions( + @PathVariable final String username, + @PathVariable(required = false) final String deviceId, + @RequestParam(value = "jsonp", + required = false) final String functionJsonp) { + List<String> subscriptions = subscriptionService.getSubscriptions(username); + return ResponseEntity.ok(subscriptions); + } + + /** + * This function takes the information of added and removed podcasts in the + * form of a SubcriptionDelta as a JSON object. + * <br> + * After that, it applies the changes to the given user in the database. + * + * @param username The username of the user who is making changes to their + * subscriptions + * @param deviceId The device ID of the device that is requesting the update + * (will be ignored in this implementation) + * @param delta Contains all the changes that were made to the + * subscriptions of the user + * @return The response containing a placeholder for not + * supported function with + * <br> + * {@link HttpStatus#OK} on success, + * <br> + * {@link HttpStatus#NOT_FOUND} user or subscription not found + */ + @PostMapping(path = "/api/2/subscriptions/{username}/{deviceId}.json") + public ResponseEntity<UpdateUrlsWrapper> applySubscriptionDelta( + @PathVariable final String username, + @PathVariable final String deviceId, + @RequestBody final SubscriptionDelta delta) { + subscriptionService.applySubscriptionDelta(username, delta); + return ResponseEntity.ok(new UpdateUrlsWrapper()); + } + + /** + * It returns a list of all the changes to the subscriptions of a user since a + * given time. + * + * @param username The username of the user whose SubscriptionDeltas are being + * requested + * @param deviceId The device ID of the device that is requesting the delta + * (will be ignored in this implementation) + * @param since The timestamp of the last time the client checked for + * updates + * @return A response containing the SubscriptionDelta of all changes that + * were made in a JSON format + */ + @GetMapping(path = "/api/2/subscriptions/{username}/{deviceId}.json") + public ResponseEntity<SubscriptionDelta> getSubscriptionDelta( + @PathVariable final String username, + @PathVariable final String deviceId, + @RequestParam("since") final long since) { + SubscriptionDelta delta + = subscriptionService.getSubscriptionDelta(username, since); + return ResponseEntity.ok(delta); + } + + /** + * This function returns a list of podcasts a user is subscribed to. + * <br> + * This includes not only the podcast itself, but also the latest 20 Episodes + * of the podcast. + * + * @param username The username of the user whose podcasts are being requested + * @return A response containing a List of podcasts and their episodes the + * user is subscribed to + */ + @GetMapping(path = "/subscriptions/titles/{username}.json") + public ResponseEntity<List<SubscriptionTitles>> getTitles( + @PathVariable final String username) { + List<SubscriptionTitles> responseBody + = subscriptionService.getTitles(username); + return ResponseEntity.ok(responseBody); + } + +} diff --git a/pse-server/src/main/java/org/psesquared/server/subscriptions/api/controller/SubscriptionDelta.java b/pse-server/src/main/java/org/psesquared/server/subscriptions/api/controller/SubscriptionDelta.java new file mode 100644 index 0000000..7611b05 --- /dev/null +++ b/pse-server/src/main/java/org/psesquared/server/subscriptions/api/controller/SubscriptionDelta.java @@ -0,0 +1,80 @@ +package org.psesquared.server.subscriptions.api.controller; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.List; +import lombok.NonNull; + +/** + * SubscriptionDeltas contain all changes that were made to the subscriptions + * of a user (added / removed podcasts) at a certain time. + */ +public class SubscriptionDelta { + + /** + * The list of recently subscribed podcasts. + */ + @JsonProperty(required = true) + @NonNull + private final List<String> add; + + /** + * The list of recently unsubscribed podcasts. + */ + @JsonProperty(required = true) + @NonNull + private final List<String> remove; + + /** + * The timestamp of the delta. + */ + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + private final long timestamp; + + /** + * Instantiates a new SubscriptionDelta with a current timestamp. + * + * @param addedPodcastUrls List of Strings containing the RSS-Feed URLs of + * all added podcasts + * @param removedPodcastUrls List of Strings containing the RSS-Feed URLs of + * all removed podcasts + */ + public SubscriptionDelta( + @org.springframework.lang.NonNull final List<String> addedPodcastUrls, + @org.springframework.lang.NonNull final List<String> removedPodcastUrls) { + this.add = addedPodcastUrls; + this.remove = removedPodcastUrls; + this.timestamp = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC); + } + + /** + * Returns the list of RSS-Feed URLs of all added podcasts. + * + * @return RSS-Feed URLs of all added podcasts + */ + @org.springframework.lang.NonNull + public List<String> getAdd() { + return add; + } + + /** + * Returns the list of RSS-Feed URLs of all removed podcasts. + * + * @return RSS-Feed URLs of all removed podcasts + */ + @org.springframework.lang.NonNull + public List<String> getRemove() { + return remove; + } + + /** + * Returns the timestamp of when this Subscription Delta was uploaded. + * + * @return The timestamp of when this Subscription Delta was uploaded + */ + public long getTimestamp() { + return timestamp; + } + +} diff --git a/pse-server/src/main/java/org/psesquared/server/subscriptions/api/controller/SubscriptionTitles.java b/pse-server/src/main/java/org/psesquared/server/subscriptions/api/controller/SubscriptionTitles.java new file mode 100644 index 0000000..682497b --- /dev/null +++ b/pse-server/src/main/java/org/psesquared/server/subscriptions/api/controller/SubscriptionTitles.java @@ -0,0 +1,17 @@ +package org.psesquared.server.subscriptions.api.controller; + +import com.fasterxml.jackson.annotation.JsonUnwrapped; +import java.util.List; +import org.psesquared.server.episode.actions.api.controller.EpisodeActionPost; +import org.psesquared.server.model.Subscription; + +/** + * Contains a podcast and its latest 20 Episodes. + * + * @param subscription The podcast + * @param episodes The episodes of the podcast + */ +public record SubscriptionTitles( + @JsonUnwrapped Subscription subscription, + List<EpisodeActionPost> episodes) { +} diff --git a/pse-server/src/main/java/org/psesquared/server/subscriptions/api/controller/package-info.java b/pse-server/src/main/java/org/psesquared/server/subscriptions/api/controller/package-info.java new file mode 100644 index 0000000..0238039 --- /dev/null +++ b/pse-server/src/main/java/org/psesquared/server/subscriptions/api/controller/package-info.java @@ -0,0 +1,13 @@ +/** + * This package represents the highest logical layer of the subscription API + * ({@link org.psesquared.server.subscriptions.api}) - the controller layer. + * <br> + * It contains the + * {@link + * org.psesquared.server.subscriptions.api.controller.SubscriptionController} + * along with some wrapper classes for JSON request and response bodies. + * + * @author PSE-Squared Team + * @version 1.0 + */ +package org.psesquared.server.subscriptions.api.controller; diff --git a/pse-server/src/main/java/org/psesquared/server/subscriptions/api/data/access/SubscriptionActionDao.java b/pse-server/src/main/java/org/psesquared/server/subscriptions/api/data/access/SubscriptionActionDao.java new file mode 100644 index 0000000..5d91453 --- /dev/null +++ b/pse-server/src/main/java/org/psesquared/server/subscriptions/api/data/access/SubscriptionActionDao.java @@ -0,0 +1,91 @@ +package org.psesquared.server.subscriptions.api.data.access; + +import java.util.List; +import java.util.Optional; +import org.psesquared.server.model.Subscription; +import org.psesquared.server.model.SubscriptionAction; +import org.psesquared.server.model.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +/** + * A DAO interface responsible for transactions involving SubscriptionActions. + */ +@Repository +public interface SubscriptionActionDao + extends JpaRepository<SubscriptionAction, Long> { + + /** + * True, if the given user is already subscribed to the given Subscription. + * + * @param user The user that could be subscribed + * @param subscription The subscription the user could be subscribed to + * @return A boolean value signifying whether the user is subscribed to the + * given subscription + */ + boolean existsByUserAndSubscription(User user, Subscription subscription); + + /** + * Find the SubscriptionAction signifying that the user is subscribed to the + * given Subscription. + * + * @param user The user who is subscribed to the subscription + * @param subscription The subscription that the user is subscribed to + * @return Contains the relevant SubscriptionAction. Could also be NULL if + * none was found. + */ + Optional<SubscriptionAction> findByUserAndSubscription( + User user, + Subscription subscription); + + /** + * Find the SubscriptionAction for a {@link User} with the given username + * and for a {@link Subscription} with the given URL. + * + * @param username The username of the user who is subscribed to + * the subscription + * @param subscriptionUrl The URL of the subscription that the user is + * subscribed to + * @return Contains the relevant SubscriptionAction. Could also be NULL if + * none was found. + */ + Optional<SubscriptionAction> findByUserUsernameAndSubscriptionUrl( + String username, String subscriptionUrl); + + /** + * All SubscriptionActions of a given user that were applied since a given + * timestamp are searched for and returned. + * + * @param username The username of the user whose SubscriptionActions are + * requested + * @param timestamp The timestamp signifying how old the SubscriptionActions + * are allowed to be + * @return A list of SubscriptionActions that have since been applied + */ + List<SubscriptionAction> findByUserUsernameAndTimestampGreaterThanEqual( + String username, + long timestamp); + + /** + * Returns a List of all Subscriptions the user is subscribed to. + * + * @param username The username of the user whose subscriptions are requested + * @return A list of subscriptions the user is subscribed to + */ + List<SubscriptionAction> findByUserUsernameAndAddedTrue(String username); + + /** + * Returns a List of RSS-Feed URLs of all podcasts the given user is + * subscribed to since a given timestamp. + * + * @param username The username of the user whose subscriptions are requested + * @param timestamp The timestamp signifying the time since when the user must + * have been subscribed + * @return List of RSS-Feed URLs + */ + List<SubscriptionAction> + findByUserUsernameAndAddedTrueAndTimestampGreaterThanEqual( + String username, + long timestamp); + +} diff --git a/pse-server/src/main/java/org/psesquared/server/subscriptions/api/data/access/SubscriptionDao.java b/pse-server/src/main/java/org/psesquared/server/subscriptions/api/data/access/SubscriptionDao.java new file mode 100644 index 0000000..d3d1fbf --- /dev/null +++ b/pse-server/src/main/java/org/psesquared/server/subscriptions/api/data/access/SubscriptionDao.java @@ -0,0 +1,34 @@ +package org.psesquared.server.subscriptions.api.data.access; + +import java.util.Optional; +import org.psesquared.server.model.Subscription; +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +/** + * A DAO interface responsible for transactions involving Subscriptions. + */ +@Repository +public interface SubscriptionDao extends JpaRepository<Subscription, Long> { + + /** + * Find a subscription by its URL. + * + * @param url The URL of the subscription + * @return The found subscription (could be NULL, if there was no match) + */ + @EntityGraph(value = "graph.Subscription.episodes") + Optional<Subscription> findByUrl(String url); + + /** + * Returns true if the database already has a Subscription that has the given + * URL. + * + * @param url The URL of the Subscription that could already exist in the + * database + * @return A boolean value signifying the existence of the Subscription + */ + boolean existsByUrl(String url); + +} diff --git a/pse-server/src/main/java/org/psesquared/server/subscriptions/api/data/access/package-info.java b/pse-server/src/main/java/org/psesquared/server/subscriptions/api/data/access/package-info.java new file mode 100644 index 0000000..db23d5f --- /dev/null +++ b/pse-server/src/main/java/org/psesquared/server/subscriptions/api/data/access/package-info.java @@ -0,0 +1,13 @@ +/** + * This package represents the lowest logical layer of the subscription API + * ({@link org.psesquared.server.subscriptions.api}) - the data-access layer. + * <br> + * It features the interfaces {@link + * org.psesquared.server.subscriptions.api.data.access.SubscriptionActionDao} + * and {@link + * org.psesquared.server.subscriptions.api.data.access.SubscriptionDao}. + * + * @author PSE-Squared Team + * @version 1.0 + */ +package org.psesquared.server.subscriptions.api.data.access; diff --git a/pse-server/src/main/java/org/psesquared/server/subscriptions/api/service/SubscriptionService.java b/pse-server/src/main/java/org/psesquared/server/subscriptions/api/service/SubscriptionService.java new file mode 100644 index 0000000..08dc1f9 --- /dev/null +++ b/pse-server/src/main/java/org/psesquared/server/subscriptions/api/service/SubscriptionService.java @@ -0,0 +1,299 @@ +package org.psesquared.server.subscriptions.api.service; + +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.ArrayList; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Map; +import java.util.HashMap; +import java.util.logging.Level; +import java.util.logging.Logger; +import lombok.RequiredArgsConstructor; +import org.psesquared.server.authentication.api.data.access.AuthenticationDao; +import org.psesquared.server.episode.actions.api.controller.EpisodeActionPost; +import org.psesquared.server.episode.actions.api.data.access.EpisodeActionDao; +import org.psesquared.server.episode.actions.api.service.EpisodeActionService; +import org.psesquared.server.model.Subscription; +import org.psesquared.server.model.SubscriptionAction; +import org.psesquared.server.model.User; +import org.psesquared.server.subscriptions.api.controller.SubscriptionDelta; +import org.psesquared.server.subscriptions.api.controller.SubscriptionTitles; +import org.psesquared.server.subscriptions.api.data.access.SubscriptionActionDao; +import org.psesquared.server.subscriptions.api.data.access.SubscriptionDao; +import org.psesquared.server.util.RssParser; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * This service class manages all business logic associated with the + * episode action API. + * <br> + * It is called from the + * {@link + * org.psesquared.server.subscriptions.api.controller.SubscriptionController} + * and passes on requests concerning data access mainly to the + * {@link SubscriptionDao} and {@link SubscriptionActionDao}. + */ +@Service +@Transactional +@RequiredArgsConstructor +public class SubscriptionService { + + /** + * The error message that is logged if no subscription exists + * for a remove action. + */ + private static final String NO_SUB_WARNING + = "Subscription for remove action does not exist!"; + + /** + * The logger for logging some warnings. + */ + private static final Logger LOGGER + = Logger.getLogger(SubscriptionService.class.getName()); + + /** + * The class for fetching data from RSS feeds. + */ + private final RssParser rssParser; + + /** + * The JPA repository that handles all user related database requests. + */ + private final AuthenticationDao authenticationDao; + + /** + * The JPA repository that handles all subscription related database requests. + */ + private final SubscriptionDao subscriptionDao; + + /** + * The JPA repository that handles all subscription action related database + * requests. + */ + private final SubscriptionActionDao subscriptionActionDao; + + /** + * The JPA repository that handles all episode action related database + * requests. + */ + private final EpisodeActionDao episodeActionDao; + + /** + * The service class of the episode action API. + */ + private final EpisodeActionService episodeActionService; + + /** + * It takes a list of podcast URLs in the form of strings and checks if they + * exist in the database. + * If they do not exist yet, it creates them. + * <br> + * Then it checks, if the user already has a subscription action for each + * subscription, separately. + * If not, it creates one. + * If yes, it updates the action. + * + * @param username The username of the user + * @param subscriptionStrings List of Strings, each String is a URL of a + * podcast + * @return {@link HttpStatus#OK} on success, + * <br> + * {@link HttpStatus#NOT_FOUND} user not found + */ + public HttpStatus uploadSubscriptions( + final String username, + final List<String> subscriptionStrings) { + User user; + try { + user = authenticationDao.findByUsername(username) + .orElseThrow(); + } catch (NoSuchElementException e) { + return HttpStatus.NOT_FOUND; + } + + Subscription subscription; + for (String subscriptionString : subscriptionStrings) { + + try { + subscription + = subscriptionDao.findByUrl(subscriptionString).orElseThrow(); + } catch (NoSuchElementException e) { + subscription = Subscription.builder() + .url(subscriptionString) + .timestamp(LocalDateTime.now().toEpochSecond(ZoneOffset.UTC)) + .build(); + subscriptionDao.save(subscription); + rssParser.validate(subscription); + } + + try { + SubscriptionAction subscriptionAction + = subscriptionActionDao + .findByUserAndSubscription(user, subscription) + .orElseThrow(); + subscriptionAction.setAdded(true); + subscriptionAction.setTimestamp( + LocalDateTime.now().toEpochSecond(ZoneOffset.UTC)); + subscriptionActionDao.save(subscriptionAction); + } catch (NoSuchElementException e) { + SubscriptionAction subscriptionAction = SubscriptionAction.builder() + .user(user) + .added(true) + .subscription(subscription) + .timestamp(LocalDateTime.now().toEpochSecond(ZoneOffset.UTC)) + .build(); + subscriptionActionDao.save(subscriptionAction); + } + } + + return HttpStatus.OK; + } + + /** + * It returns a URL List of all podcasts the user is subscribed to in the form + * of a String list. + * + * @param username The username of the user whose subscriptions are being + * requested + * @return A list of RSS-Feed URLs of all subscribed podcasts + */ + public List<String> getSubscriptions(final String username) { + List<SubscriptionAction> subscriptionActions + = subscriptionActionDao.findByUserUsernameAndAddedTrue(username); + List<String> subscriptionUrls = new ArrayList<>(); + for (SubscriptionAction subscriptionAction : subscriptionActions) { + subscriptionUrls.add(subscriptionAction.getSubscription().getUrl()); + } + return subscriptionUrls; + } + + /** + * All subscription changes of the user are uploaded to the database. + * + * @param username The username of the user uploading their subscription + * changes + * @param delta The subscription changes in the form of a SubscriptionDelta + * containing the added / removed subscriptions + */ + public void applySubscriptionDelta( + final String username, + final SubscriptionDelta delta) { + + SubscriptionDelta minimizedDelta = minimizeDelta(delta); + + uploadSubscriptions(username, minimizedDelta.getAdd()); + for (String removeSub : minimizedDelta.getRemove()) { + try { + SubscriptionAction subscriptionAction + = subscriptionActionDao + .findByUserUsernameAndSubscriptionUrl(username, removeSub) + .orElseThrow(); + subscriptionAction.setAdded(false); + subscriptionAction.setTimestamp( + LocalDateTime.now().toEpochSecond(ZoneOffset.UTC)); + subscriptionActionDao.save(subscriptionAction); + episodeActionDao + .deleteByUserUsernameAndEpisodeSubscriptionUrl(username, removeSub); + } catch (NoSuchElementException e) { + LOGGER.log(Level.WARNING, NO_SUB_WARNING); + } + } + } + + private SubscriptionDelta minimizeDelta(SubscriptionDelta oldDelta){ + SubscriptionDelta minimizedDelta = new SubscriptionDelta(new ArrayList<>(), new ArrayList<>()); + + Map<String, Integer> deltaMap = new HashMap<>(); + for (String addString : oldDelta.getAdd()) { + if(deltaMap.containsKey(addString)) { + deltaMap.put(addString, deltaMap.get(addString) + 1); + } + else{ + deltaMap.put(addString, 1); + } + } + + for (String removeString : oldDelta.getRemove()) { + if(deltaMap.containsKey(removeString)) { + deltaMap.put(removeString, deltaMap.get(removeString) - 1); + } + else{ + deltaMap.put(removeString, -1); + } + } + + for(Map.Entry<String, Integer> deltaEntry : deltaMap.entrySet()) { + if(deltaEntry.getValue() > 0) { + minimizedDelta.getAdd().add(deltaEntry.getKey()); + } else if(deltaEntry.getValue() < 0) { + minimizedDelta.getRemove().add(deltaEntry.getKey()); + } + } + + return minimizedDelta; + } + + /** + * Returns a SubscriptionDelta of all changes made to a users subscriptions + * since a given point in time. + * + * @param username The username of the user whose subscription changes are + * being requested + * @param since The timestamp signifying how old the changes are allowed to + * be + * @return The SubscriptionDelta of all changes made since the given timestamp + */ + public SubscriptionDelta getSubscriptionDelta( + final String username, + final long since) { + List<String> added = new ArrayList<>(); + List<String> removed = new ArrayList<>(); + + List<SubscriptionAction> subscriptionActions = subscriptionActionDao + .findByUserUsernameAndTimestampGreaterThanEqual(username, since); + for (SubscriptionAction subscriptionAction : subscriptionActions) { + if (subscriptionAction.isAdded()) { + added.add(subscriptionAction.getSubscription().getUrl()); + } else { + removed.add(subscriptionAction.getSubscription().getUrl()); + } + } + + return new SubscriptionDelta(added, removed); + } + + /** + * Returns all Subscriptions and their 20 latest episodes of a given user as a + * List of SubscriptionTitles. + * + * @param username The username of the user whose subscriptions are being + * requested + * @return A list of SubscriptionTitles containing each Subscription and their + * 20 latest Episodes + */ + public List<SubscriptionTitles> getTitles(final String username) { + List<SubscriptionAction> subscriptionActions + = subscriptionActionDao.findByUserUsernameAndAddedTrue(username); + List<Subscription> subscriptions = new ArrayList<>(); + List<SubscriptionTitles> subscriptionTitlesList = new ArrayList<>(); + + for (SubscriptionAction subscriptionAction : subscriptionActions) { + subscriptions.add(subscriptionAction.getSubscription()); + } + + for (Subscription subscription : subscriptions) { + List<EpisodeActionPost> episodeActionPosts + = episodeActionService + .getEpisodeActionsOfPodcast(username, subscription.getUrl()); + SubscriptionTitles subscriptionTitles + = new SubscriptionTitles(subscription, episodeActionPosts); + subscriptionTitlesList.add(subscriptionTitles); + } + + return subscriptionTitlesList; + } + +} diff --git a/pse-server/src/main/java/org/psesquared/server/subscriptions/api/service/package-info.java b/pse-server/src/main/java/org/psesquared/server/subscriptions/api/service/package-info.java new file mode 100644 index 0000000..9e2a598 --- /dev/null +++ b/pse-server/src/main/java/org/psesquared/server/subscriptions/api/service/package-info.java @@ -0,0 +1,13 @@ +/** + * This package represents the logical middle layer of the subscription API + * ({@link org.psesquared.server.subscriptions.api}) - the service layer. + * <br> + * All business logic is handled here with the + * {@link + * org.psesquared.server.subscriptions.api.service.SubscriptionService} + * class. + * + * @author PSE-Squared Team + * @version 1.0 + */ +package org.psesquared.server.subscriptions.api.service; |