@startuml ' skinparam linetype ortho ' skinparam groupInheritance 2 allowmixing package authentication_api <> { package controller as auth.controller <> { class AuthenticationController <<@RestController>> { + <> AuthenticationController(AuthenticationService, EMailConfigProperties): + logout(String, HttpServletResponse): HttpStatus + registerUser(UserInfoRequest): HttpStatus + forgotPassword(String): HttpStatus + getDeviceList(String, HttpServletResponse): ResponseEntity> + login(String, HttpServletResponse): HttpStatus + deleteUser(String, PasswordRequest): HttpStatus + changePassword(String, ChangePasswordRequest): HttpStatus + verifyRegistration(String, String, HttpServletResponse): HttpStatus + resetPassword(String, String, PasswordRequest): HttpStatus } entity AuthenticationResponse << record >> { + <> AuthenticationResponse(String): + token(): String } entity ChangePasswordRequest << record >> { + <> ChangePasswordRequest(String, String): + oldPassword(): String + newPassword(): String } entity DeviceWrapper << record >> { + <> DeviceWrapper(): + <> DeviceWrapper(String, String, String, int): + id(): String + caption(): String + type(): String + subscriptions(): int } entity ForgotPasswordRequest << record >> { + <> ForgotPasswordRequest(String): + email(): String } entity PasswordRequest << record >> { + <> PasswordRequest(String): + password(): String } entity UserInfoRequest << record >> { + <> UserInfoRequest(String, String, String): + password(): String + email(): String + username(): String } } package data_access as auth.dao <> { interface AuthenticationDao <<@Repository>> { + findByEmail(String): Optional + deleteAllByEnabledFalseAndCreatedAtLessThan(long): void + existsByUsername(String): boolean + findByUsername(String): Optional } } package service as auth.service <> { class AuthenticationService <<@Service>> { + <> AuthenticationService(AuthenticationDao, PasswordEncoder, JwtService, EMailServiceImpl, EncryptionService, InputCheckService): + verifyRegistration(String, String): HttpStatus + logout(String, HttpServletResponse): HttpStatus + changePassword(String, ChangePasswordRequest): HttpStatus + registerUser(UserInfoRequest): HttpStatus + deleteUser(String, PasswordRequest): HttpStatus + forgotPassword(String): HttpStatus + resetPassword(String, String, PasswordRequest): HttpStatus + deleteInvalidUsersOlderThan(long): void + login(String, HttpServletResponse): HttpStatus } class EMailServiceImpl <<@Service>> { + <> EMailServiceImpl(JavaMailSender, EMailConfigProperties, JwtService): - substitutePlaceholders(String, UserDetails, String): String + sendVerification(String, UserDetails): void + sendPasswordReset(String, UserDetails): void - generatePasswordResetURLString(UserDetails): String - sendMail(String, String, String): void - generateVerificationURLString(UserDetails): String } class EncryptionService <<@Service>> { + <> EncryptionService(SecurityConfigProperties): + saltAndHashEmail(String): String - getSalt(): byte[] } class InputCheckService <<@Service>> { + <> InputCheckService(): + validateEmail(String): boolean + validateUsername(String): boolean + validatePassword(String): boolean } class ResourceReader { + <> ResourceReader(): + readFileToString(String): String } } } package config <> { class ApplicationConfig <<@Configuration>> { + <> ApplicationConfig(AuthenticationDao): + userDetailsService(): UserDetailsService + addInterceptors(InterceptorRegistry): void + authenticationManager(AuthenticationConfiguration): AuthenticationManager + passwordEncoder(): PasswordEncoder + corsConfigurer(): WebMvcConfigurer + authenticationProvider(): AuthenticationProvider } class AuthenticationValidatorInterceptor { + <> AuthenticationValidatorInterceptor(): - extractUsernamePathVariable(HttpServletRequest): String? + preHandle(HttpServletRequest, HttpServletResponse, Object): boolean } entity EMailConfigProperties << record >> { + <> EMailConfigProperties(String, String, String): + resetUrlPath(): String + verificationUrl(): String + dashboardBaseUrl(): String } class JwtAuthenticationFilter <<@Component>> { + <> JwtAuthenticationFilter(JwtService, UserDetailsService): # doFilterInternal(HttpServletRequest, HttpServletResponse, FilterChain): void - authenticateIfValid(Cookie, HttpServletRequest): void } class JwtService <<@Service>> { + <> JwtService(SecurityConfigProperties): + generateUrlTokenString(UserDetails): String + isTokenValid(String, UserDetails): boolean + generateAccessTokenString(UserDetails): String + extractUsername(String): String + extractClaim(String, Function): T - isTokenExpired(String): boolean + generateTokenString(Map, UserDetails, long): String - extractExpiration(String): Date - extractAllClaims(String): Claims - getSigningKey(): Key } class SecurityConfig <<@Configuration>> { + <> SecurityConfig(JwtAuthenticationFilter, AuthenticationProvider): ~ corsConfigurationSource(): CorsConfigurationSource + securityFilterChain(HttpSecurity): SecurityFilterChain } entity SecurityConfigProperties << record >> { + <> SecurityConfigProperties(String, String): + jwtSigningKey(): String + emailSigningKey(): String } } package episode_actions_api <> { package controller as episode.controller <> { class EpisodeActionController <<@RestController>> { + <> EpisodeActionController(EpisodeActionService): + addEpisodeActions(String, List): ResponseEntity + getEpisodeActionsOfPodcast(String, String): ResponseEntity + getEpisodeActions(String): ResponseEntity + getEpisodeActionsSince(String, long): ResponseEntity + getEpisodeActionsOfPodcastSince(String, String, long): ResponseEntity } class EpisodeActionGetResponse { + <> EpisodeActionGetResponse(List): + getTimestamp(): long + getActions(): List } class EpisodeActionPost { + <> EpisodeActionPost(): + <> EpisodeActionPost(String, String, String, String, int, EpisodeAction): + equals(Object): boolean + hashCode(): int + builder(): EpisodeActionPostBuilder + getPodcastURL(): String + getEpisodeURL(): String + getTitle(): String + getGuid(): String + setGuid(String): void + setEpisodeAction(EpisodeAction): void + setTotal(int): void + getTotal(): int + setTitle(String): void # canEqual(Object): boolean + getEpisodeAction(): EpisodeAction + toString(): String + setPodcastURL(String): void + setEpisodeURL(String): void } } package data_access as episode.dao <> { interface EpisodeActionDao <<@Repository>> { + findByUserUsernameAndEpisodeSubscriptionUrl(String, String): List + delete(EpisodeAction): void + findByUserUsernameAndTimestampGreaterThanEqual(String, LocalDateTime): List + findByUserUsername(String): List + findByUserAndEpisodeUrlAndAction(User, String, Action): Optional + findByUserUsernameAndTimestampGreaterThanEqualAndEpisodeSubscriptionUrl(String, LocalDateTime, String): List + deleteByUserUsernameAndEpisodeSubscriptionUrl(String, String): void + existsByUserAndEpisodeUrlAndAction(User, String, Action): boolean } interface EpisodeDao <<@Repository>> { + existsByGuid(String): boolean + findByUrl(String): Optional + existsByUrl(String): boolean + findByGuid(String): Optional } } package service as episode.service <> { class EpisodeActionService <<@Service>> { + <> EpisodeActionService(EpisodeActionDao, EpisodeDao, AuthenticationDao, SubscriptionDao, SubscriptionActionDao, RSSParser): - episodeActionPostsToEpisodeActions(User, List): List - createEpisode(EpisodeActionPost): Episode + getEpisodeActionsOfPodcastSince(String, String, long): List - getEpisodeFromDatabase(EpisodeActionPost): Episode - addEpisodeActionsToDatabase(User, List): void - addNewestEpisodeActionToDatabase(User, EpisodeAction): void - episodeActionsToEpisodeActionPosts(List): List - addEpisodeActionToDatabase(User, EpisodeAction): void + getEpisodeActionsSince(String, long): List - episodeActionPostToEpisodeAction(User, EpisodeActionPost): EpisodeAction + getEpisodeActions(String): List + getEpisodeActionsOfPodcast(String, String): List + addEpisodeActions(String, List): void } } } package model <> { enum Action << enumeration >> { + <> Action(): + valueOf(String): Action + getJsonProperty(): String + values(): Action[] } class Episode <<@Entity>> { + <> Episode(): + <> Episode(Long, String, String, String, int, Subscription, List): + getId(): Long # canEqual(Object): boolean + getGuid(): String + setTitle(String): void + builder(): EpisodeBuilder + setTotal(int): void + setGuid(String): void + equals(Object): boolean + setSubscription(Subscription): void + getUrl(): String + getTitle(): String + toString(): String + getTotal(): int + setUrl(String): void + setEpisodeActions(List): void + getSubscription(): Subscription + getEpisodeActions(): List + setId(Long): void + hashCode(): int } class EpisodeAction <<@Entity>> { + <> EpisodeAction(Long, User, Episode, LocalDateTime, Action, int, int): + <> EpisodeAction(): + getEpisode(): Episode + setEpisode(Episode): void + setPosition(int): void # canEqual(Object): boolean + setUser(User): void + setStarted(int): void + getId(): Long + getUser(): User + getTimestamp(): LocalDateTime + getAction(): Action + getStarted(): int + hashCode(): int + setTimestamp(LocalDateTime): void + equals(Object): boolean + getPosition(): int + builder(): EpisodeActionBuilder + setId(Long): void + setAction(Action): void + toString(): String + toEpisodeActionPost(): EpisodeActionPost } enum Role << enumeration >> { + <> Role(): + valueOf(String): Role + toString(): String + values(): Role[] } class Subscription <<@Entity>> { + <> Subscription(): + <> Subscription(Long, String, String, long, List, List): + setId(Long): void + getId(): Long + equals(Object): boolean + getUrl(): String + hashCode(): int + builder(): SubscriptionBuilder # canEqual(Object): boolean + toString(): String + getTitle(): String + getTimestamp(): long + setEpisodes(List): void + getSubscriptionActions(): List + getEpisodes(): List + setSubscriptionActions(List): void + setUrl(String): void + setTitle(String): void + setTimestamp(long): void + addEpisode(Episode): void } class SubscriptionAction <<@Entity>> { + <> SubscriptionAction(): + <> SubscriptionAction(int, User, long, Subscription, boolean): + equals(Object): boolean + getId(): int + getUser(): User + getTimestamp(): long + getSubscription(): Subscription + isAdded(): boolean # canEqual(Object): boolean + setId(int): void + hashCode(): int + setUser(User): void + toString(): String + builder(): SubscriptionActionBuilder + setTimestamp(long): void + setSubscription(Subscription): void + setAdded(boolean): void } class User <<@Entity>> { + <> User(Long, String, String, String, boolean, long, Role, List, List): + <> User(): + getId(): Long + setCreatedAt(long): void + getUsername(): String + builder(): UserBuilder + toString(): String + getEmail(): String + setPassword(String): void + setSubscriptionActions(List): void + equals(Object): boolean + getPassword(): String + setEmail(String): void + setRole(Role): void + isEnabled(): boolean + setUsername(String): void + getCreatedAt(): long + getRole(): Role + getSubscriptionActions(): List # canEqual(Object): boolean + hashCode(): int + setEnabled(boolean): void + setEpisodeActions(List): void + getEpisodeActions(): List + setId(Long): void + getAuthorities(): Collection + isCredentialsNonExpired(): boolean + isAccountNonLocked(): boolean + isAccountNonExpired(): boolean } } package subscriptions_api <> { package controller as subscription.controller <> { class SubscriptionController <<@RestController>> { + <> SubscriptionController(SubscriptionService): + applySubscriptionDelta(String, String, SubscriptionDelta): ResponseEntity + getSubscriptions(String, String, String): ResponseEntity> + getSubscriptionDelta(String, String, long): ResponseEntity + getTitles(String): ResponseEntity> + uploadSubscriptions(String, String, List): ResponseEntity } class SubscriptionDelta { + <> SubscriptionDelta(List, List): + getTimestamp(): long + getRemove(): List + getAdd(): List } entity SubscriptionTitles << record >> { + <> SubscriptionTitles(Subscription, List): + episodes(): List + subscription(): Subscription } } package data_access as subscription.dao <> { interface SubscriptionActionDao <<@Repository>> { + findByUserUsernameAndAddedTrue(String): List + existsByUserAndSubscription(User, Subscription): boolean + findByUserAndSubscription(User, Subscription): Optional + findByUserUsernameAndTimestampGreaterThanEqual(String, long): List + findByUserUsernameAndAddedTrueAndTimestampGreaterThanEqual(String, long): List } interface SubscriptionDao <<@Repository>> { + findByUrl(String): Optional + existsByUrl(String): boolean } } package service as subscription.service <> { class SubscriptionService <<@Service>> { + <> SubscriptionService(RSSParser, AuthenticationDao, SubscriptionDao, SubscriptionActionDao, EpisodeActionDao, EpisodeActionService): + getTitles(String): List + getSubscriptions(String): List + applySubscriptionDelta(String, SubscriptionDelta): int + getSubscriptionDelta(String, long): SubscriptionDelta + uploadSubscriptions(String, List): int } } } package util <> { class RSSParser <<@Component>> { + <> RSSParser(EpisodeDao, SubscriptionDao): + validate(Subscription): void - parseTimeToSeconds(String): int - parseEpisode(SyndEntry, Subscription): Episode - saveEpisodes(List): void - fetchSubscriptionFeed(Subscription): Map? - saveSubscription(Subscription): void - deleteSubscription(Subscription): void - getFetchedEpisodeForURL(String, Map): Episode - deleteEpisodes(List): void } class Scheduler <<@Component>> { + <> Scheduler(): + clean(): void } class UpdateURLsWrapper { + <> UpdateURLsWrapper(): + getTimestamp(): long + getUpdateURLs(): List> } } class ServerApplication <<@SpringBootApplication>> { + <> ServerApplication(): + main(String[]): void } database Datenbank Datenbank <-[hidden]d- subscriptions_api Datenbank <-[hidden]d- episode_actions_api Datenbank <-[hidden]d- authentication_api () SQL as SQLSub () SQL as SQLAuth () SQL as SQLEpisode Datenbank -- SQLSub Datenbank -- SQLAuth Datenbank -- SQLEpisode SubscriptionController ..o ServerApplication AuthenticationController ..o ServerApplication EpisodeActionController ..o ServerApplication ServerApplication --() HTTP SQLSub )-- SubscriptionActionDao: JPA SQLSub )-- SubscriptionDao: JPA SQLAuth )-- AuthenticationDao: JPA SQLEpisode )-- EpisodeActionDao: JPA SQLEpisode )-- EpisodeDao: JPA model .o Datenbank: ORM (User, SubscriptionAction, Subscription, EpisodeAction, Episode) ' Datenbank o.. Subscription: ORM ' Datenbank o.. SubscriptionAction: ORM ' Datenbank o.. Episode: ORM ' Datenbank o.. EpisodeAction: ORM ' Datenbank o.. User: ORM ApplicationConfig "1" *-[#595959,plain]-> "authenticationDao\n1" AuthenticationDao ApplicationConfig -[#595959,dashed]-> AuthenticationValidatorInterceptor : "«create»" AuthenticationController "1" *-[#595959,plain]-> "authenticationService\n1" AuthenticationService AuthenticationController -[#595959,dashed]-> DeviceWrapper : "«create»" AuthenticationController "1" *-[#595959,plain]-> "eMailConfigProperties\n1" EMailConfigProperties AuthenticationService "1" *-[#595959,plain]-> "authenticationDao\n1" AuthenticationDao AuthenticationService "1" *-[#595959,plain]-> "eMailService\n1" EMailServiceImpl AuthenticationService "1" *-[#595959,plain]-> "encryptionService\n1" EncryptionService AuthenticationService "1" *-[#595959,plain]-> "inputCheckService\n1" InputCheckService AuthenticationService "1" *-[#595959,plain]-> "jwtService\n1" JwtService AuthenticationService "1" *-[#595959,plain]-> "DEFAULT_USER\n1" Role EMailServiceImpl "1" *-[#595959,plain]-> "eMailConfigProperties\n1" EMailConfigProperties EMailServiceImpl "1" *-[#595959,plain]-> "jwtService\n1" JwtService EncryptionService "1" *-[#595959,plain]-> "securityConfigProperties\n1" SecurityConfigProperties Episode "1" *-[#595959,plain]-> "episodeActions\n*" EpisodeAction Episode "1" *-[#595959,plain]-> "subscription\n1" Subscription EpisodeAction "1" *-[#595959,plain]-> "action\n1" Action EpisodeAction "1" *-[#595959,plain]-> "episode\n1" Episode EpisodeAction -[#595959,dashed]-> EpisodeActionPost : "«create»" EpisodeAction "1" *-[#595959,plain]-> "user\n1" User EpisodeActionController -[#595959,dashed]-> EpisodeActionGetResponse : "«create»" EpisodeActionController "1" *-[#595959,plain]-> "episodeActionService\n1" EpisodeActionService EpisodeActionController -[#595959,dashed]-> UpdateURLsWrapper : "«create»" EpisodeActionGetResponse "1" *-[#595959,plain]-> "actions\n*" EpisodeActionPost EpisodeActionPost "1" *-[#595959,plain]-> "episodeAction\n1" EpisodeAction EpisodeActionService "1" *-[#595959,plain]-> "authenticationDao\n1" AuthenticationDao EpisodeActionService "1" *-[#595959,plain]-> "episodeActionDao\n1" EpisodeActionDao EpisodeActionService "1" *-[#595959,plain]-> "episodeDao\n1" EpisodeDao EpisodeActionService "1" *-[#595959,plain]-> "rssParser\n1" RSSParser EpisodeActionService -[#595959,dashed]-> Subscription : "«create»" EpisodeActionService "1" *-[#595959,plain]-> "subscriptionActionDao\n1" SubscriptionActionDao EpisodeActionService "1" *-[#595959,plain]-> "subscriptionDao\n1" SubscriptionDao JwtAuthenticationFilter "1" *-[#595959,plain]-> "jwtService\n1" JwtService JwtService "1" *-[#595959,plain]-> "securityConfigProperties\n1" SecurityConfigProperties RSSParser "1" *-[#595959,plain]-> "episodeDao\n1" EpisodeDao RSSParser "1" *-[#595959,plain]-> "subscriptionDao\n1" SubscriptionDao Scheduler "1" *-[#595959,plain]-> "authenticationService\n1" AuthenticationService SecurityConfig "1" *-[#595959,plain]-> "jwtAuthFilter\n1" JwtAuthenticationFilter Subscription "1" *-[#595959,plain]-> "episodes\n*" Episode Subscription "1" *-[#595959,plain]-> "subscriptionActions\n*" SubscriptionAction SubscriptionAction "1" *-[#595959,plain]-> "subscription\n1" Subscription SubscriptionAction "1" *-[#595959,plain]-> "user\n1" User SubscriptionController "1" *-[#595959,plain]-> "subscriptionService\n1" SubscriptionService SubscriptionController -[#595959,dashed]-> UpdateURLsWrapper : "«create»" SubscriptionService "1" *-[#595959,plain]-> "authenticationDao\n1" AuthenticationDao SubscriptionService "1" *-[#595959,plain]-> "episodeActionDao\n1" EpisodeActionDao SubscriptionService "1" *-[#595959,plain]-> "episodeActionService\n1" EpisodeActionService SubscriptionService "1" *-[#595959,plain]-> "rssParser\n1" RSSParser SubscriptionService "1" *-[#595959,plain]-> "subscriptionActionDao\n1" SubscriptionActionDao SubscriptionService "1" *-[#595959,plain]-> "subscriptionDao\n1" SubscriptionDao SubscriptionService -[#595959,dashed]-> SubscriptionDelta : "«create»" SubscriptionService -[#595959,dashed]-> SubscriptionTitles : "«create»" SubscriptionTitles "1" *-[#595959,plain]-> "subscription\n1" Subscription User "1" *-[#595959,plain]-> "episodeActions\n*" EpisodeAction User "1" *-[#595959,plain]-> "role\n1" Role User "1" *-[#595959,plain]-> "subscriptionActions\n*" SubscriptionAction @enduml