diff options
Diffstat (limited to 'pse-server/src/main/java/org/psesquared/server/subscriptions')
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;  | 
