summaryrefslogtreecommitdiff
path: root/20-implementierungsheft/assets/diagrams/class_after.puml
diff options
context:
space:
mode:
Diffstat (limited to '20-implementierungsheft/assets/diagrams/class_after.puml')
-rw-r--r--20-implementierungsheft/assets/diagrams/class_after.puml574
1 files changed, 574 insertions, 0 deletions
diff --git a/20-implementierungsheft/assets/diagrams/class_after.puml b/20-implementierungsheft/assets/diagrams/class_after.puml
new file mode 100644
index 0000000..0a8f475
--- /dev/null
+++ b/20-implementierungsheft/assets/diagrams/class_after.puml
@@ -0,0 +1,574 @@
+@startuml
+' skinparam linetype ortho
+' skinparam groupInheritance 2
+allowmixing
+
+package authentication_api <<Frame>> {
+
+ package controller as auth.controller <<Frame>> {
+
+ class AuthenticationController <<@RestController>> {
+ + <<create>> AuthenticationController(AuthenticationService, EMailConfigProperties):
+ + logout(String, HttpServletResponse): HttpStatus
+ + registerUser(UserInfoRequest): HttpStatus
+ + forgotPassword(String): HttpStatus
+ + getDeviceList(String, HttpServletResponse): ResponseEntity<List<DeviceWrapper>>
+ + 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 >> {
+ + <<create>> AuthenticationResponse(String):
+ + token(): String
+ }
+
+ entity ChangePasswordRequest << record >> {
+ + <<create>> ChangePasswordRequest(String, String):
+ + oldPassword(): String
+ + newPassword(): String
+ }
+
+ entity DeviceWrapper << record >> {
+ + <<create>> DeviceWrapper():
+ + <<create>> DeviceWrapper(String, String, String, int):
+ + id(): String
+ + caption(): String
+ + type(): String
+ + subscriptions(): int
+ }
+
+ entity ForgotPasswordRequest << record >> {
+ + <<create>> ForgotPasswordRequest(String):
+ + email(): String
+ }
+
+ entity PasswordRequest << record >> {
+ + <<create>> PasswordRequest(String):
+ + password(): String
+ }
+
+ entity UserInfoRequest << record >> {
+ + <<create>> UserInfoRequest(String, String, String):
+ + password(): String
+ + email(): String
+ + username(): String
+ }
+ }
+
+ package data_access as auth.dao <<Frame>> {
+
+ interface AuthenticationDao <<@Repository>> {
+ + findByEmail(String): Optional<User>
+ + deleteAllByEnabledFalseAndCreatedAtLessThan(long): void
+ + existsByUsername(String): boolean
+ + findByUsername(String): Optional<User>
+ }
+ }
+
+ package service as auth.service <<Frame>> {
+
+ class AuthenticationService <<@Service>> {
+ + <<create>> 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>> {
+ + <<create>> 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>> {
+ + <<create>> EncryptionService(SecurityConfigProperties):
+ + saltAndHashEmail(String): String
+ - getSalt(): byte[]
+ }
+
+ class InputCheckService <<@Service>> {
+ + <<create>> InputCheckService():
+ + validateEmail(String): boolean
+ + validateUsername(String): boolean
+ + validatePassword(String): boolean
+ }
+
+ class ResourceReader {
+ + <<create>> ResourceReader():
+ + readFileToString(String): String
+ }
+ }
+}
+
+package config <<Frame>> {
+
+ class ApplicationConfig <<@Configuration>> {
+ + <<create>> ApplicationConfig(AuthenticationDao):
+ + userDetailsService(): UserDetailsService
+ + addInterceptors(InterceptorRegistry): void
+ + authenticationManager(AuthenticationConfiguration): AuthenticationManager
+ + passwordEncoder(): PasswordEncoder
+ + corsConfigurer(): WebMvcConfigurer
+ + authenticationProvider(): AuthenticationProvider
+ }
+
+ class AuthenticationValidatorInterceptor {
+ + <<create>> AuthenticationValidatorInterceptor():
+ - extractUsernamePathVariable(HttpServletRequest): String?
+ + preHandle(HttpServletRequest, HttpServletResponse, Object): boolean
+ }
+
+ entity EMailConfigProperties << record >> {
+ + <<create>> EMailConfigProperties(String, String, String):
+ + resetUrlPath(): String
+ + verificationUrl(): String
+ + dashboardBaseUrl(): String
+ }
+
+ class JwtAuthenticationFilter <<@Component>> {
+ + <<create>> JwtAuthenticationFilter(JwtService, UserDetailsService):
+ # doFilterInternal(HttpServletRequest, HttpServletResponse, FilterChain): void
+ - authenticateIfValid(Cookie, HttpServletRequest): void
+ }
+
+ class JwtService <<@Service>> {
+ + <<create>> JwtService(SecurityConfigProperties):
+ + generateUrlTokenString(UserDetails): String
+ + isTokenValid(String, UserDetails): boolean
+ + generateAccessTokenString(UserDetails): String
+ + extractUsername(String): String
+ + extractClaim(String, Function<Claims, T>): T
+ - isTokenExpired(String): boolean
+ + generateTokenString(Map<String, Object>, UserDetails, long): String
+ - extractExpiration(String): Date
+ - extractAllClaims(String): Claims
+ - getSigningKey(): Key
+ }
+
+ class SecurityConfig <<@Configuration>> {
+ + <<create>> SecurityConfig(JwtAuthenticationFilter, AuthenticationProvider):
+ ~ corsConfigurationSource(): CorsConfigurationSource
+ + securityFilterChain(HttpSecurity): SecurityFilterChain
+ }
+
+ entity SecurityConfigProperties << record >> {
+ + <<create>> SecurityConfigProperties(String, String):
+ + jwtSigningKey(): String
+ + emailSigningKey(): String
+ }
+}
+
+package episode_actions_api <<Frame>> {
+
+ package controller as episode.controller <<Frame>> {
+
+ class EpisodeActionController <<@RestController>> {
+ + <<create>> EpisodeActionController(EpisodeActionService):
+ + addEpisodeActions(String, List<EpisodeActionPost>): ResponseEntity<UpdateURLsWrapper>
+ + getEpisodeActionsOfPodcast(String, String): ResponseEntity<EpisodeActionGetResponse>
+ + getEpisodeActions(String): ResponseEntity<EpisodeActionGetResponse>
+ + getEpisodeActionsSince(String, long): ResponseEntity<EpisodeActionGetResponse>
+ + getEpisodeActionsOfPodcastSince(String, String, long): ResponseEntity<EpisodeActionGetResponse>
+ }
+
+ class EpisodeActionGetResponse {
+ + <<create>> EpisodeActionGetResponse(List<EpisodeActionPost>):
+ + getTimestamp(): long
+ + getActions(): List<EpisodeActionPost>
+ }
+
+ class EpisodeActionPost {
+ + <<create>> EpisodeActionPost():
+ + <<create>> 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 <<Frame>> {
+
+ interface EpisodeActionDao <<@Repository>> {
+ + findByUserUsernameAndEpisodeSubscriptionUrl(String, String): List<EpisodeAction>
+ + delete(EpisodeAction): void
+ + findByUserUsernameAndTimestampGreaterThanEqual(String, LocalDateTime): List<EpisodeAction>
+ + findByUserUsername(String): List<EpisodeAction>
+ + findByUserAndEpisodeUrlAndAction(User, String, Action): Optional<EpisodeAction>
+ + findByUserUsernameAndTimestampGreaterThanEqualAndEpisodeSubscriptionUrl(String, LocalDateTime, String): List<EpisodeAction>
+ + deleteByUserUsernameAndEpisodeSubscriptionUrl(String, String): void
+ + existsByUserAndEpisodeUrlAndAction(User, String, Action): boolean
+ }
+
+ interface EpisodeDao <<@Repository>> {
+ + existsByGuid(String): boolean
+ + findByUrl(String): Optional<Episode>
+ + existsByUrl(String): boolean
+ + findByGuid(String): Optional<Episode>
+ }
+ }
+
+ package service as episode.service <<Frame>> {
+
+ class EpisodeActionService <<@Service>> {
+ + <<create>> EpisodeActionService(EpisodeActionDao, EpisodeDao, AuthenticationDao, SubscriptionDao, SubscriptionActionDao, RSSParser):
+ - episodeActionPostsToEpisodeActions(User, List<EpisodeActionPost>): List<EpisodeAction>
+ - createEpisode(EpisodeActionPost): Episode
+ + getEpisodeActionsOfPodcastSince(String, String, long): List<EpisodeActionPost>
+ - getEpisodeFromDatabase(EpisodeActionPost): Episode
+ - addEpisodeActionsToDatabase(User, List<EpisodeAction>): void
+ - addNewestEpisodeActionToDatabase(User, EpisodeAction): void
+ - episodeActionsToEpisodeActionPosts(List<EpisodeAction>): List<EpisodeActionPost>
+ - addEpisodeActionToDatabase(User, EpisodeAction): void
+ + getEpisodeActionsSince(String, long): List<EpisodeActionPost>
+ - episodeActionPostToEpisodeAction(User, EpisodeActionPost): EpisodeAction
+ + getEpisodeActions(String): List<EpisodeActionPost>
+ + getEpisodeActionsOfPodcast(String, String): List<EpisodeActionPost>
+ + addEpisodeActions(String, List<EpisodeActionPost>): void
+ }
+ }
+}
+
+package model <<Frame>> {
+
+ enum Action << enumeration >> {
+ + <<create>> Action():
+ + valueOf(String): Action
+ + getJsonProperty(): String
+ + values(): Action[]
+ }
+
+ class Episode <<@Entity>> {
+ + <<create>> Episode():
+ + <<create>> Episode(Long, String, String, String, int, Subscription, List<EpisodeAction>):
+ + 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<EpisodeAction>): void
+ + getSubscription(): Subscription
+ + getEpisodeActions(): List<EpisodeAction>
+ + setId(Long): void
+ + hashCode(): int
+ }
+
+ class EpisodeAction <<@Entity>> {
+ + <<create>> EpisodeAction(Long, User, Episode, LocalDateTime, Action, int, int):
+ + <<create>> 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 >> {
+ + <<create>> Role():
+ + valueOf(String): Role
+ + toString(): String
+ + values(): Role[]
+ }
+
+ class Subscription <<@Entity>> {
+ + <<create>> Subscription():
+ + <<create>> Subscription(Long, String, String, long, List<SubscriptionAction>, List<Episode>):
+ + 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<Episode>): void
+ + getSubscriptionActions(): List<SubscriptionAction>
+ + getEpisodes(): List<Episode>
+ + setSubscriptionActions(List<SubscriptionAction>): void
+ + setUrl(String): void
+ + setTitle(String): void
+ + setTimestamp(long): void
+ + addEpisode(Episode): void
+ }
+
+ class SubscriptionAction <<@Entity>> {
+ + <<create>> SubscriptionAction():
+ + <<create>> 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>> {
+ + <<create>> User(Long, String, String, String, boolean, long, Role, List<SubscriptionAction>, List<EpisodeAction>):
+ + <<create>> User():
+ + getId(): Long
+ + setCreatedAt(long): void
+ + getUsername(): String
+ + builder(): UserBuilder
+ + toString(): String
+ + getEmail(): String
+ + setPassword(String): void
+ + setSubscriptionActions(List<SubscriptionAction>): void
+ + equals(Object): boolean
+ + getPassword(): String
+ + setEmail(String): void
+ + setRole(Role): void
+ + isEnabled(): boolean
+ + setUsername(String): void
+ + getCreatedAt(): long
+ + getRole(): Role
+ + getSubscriptionActions(): List<SubscriptionAction>
+ # canEqual(Object): boolean
+ + hashCode(): int
+ + setEnabled(boolean): void
+ + setEpisodeActions(List<EpisodeAction>): void
+ + getEpisodeActions(): List<EpisodeAction>
+ + setId(Long): void
+ + getAuthorities(): Collection<GrantedAuthority>
+ + isCredentialsNonExpired(): boolean
+ + isAccountNonLocked(): boolean
+ + isAccountNonExpired(): boolean
+ }
+}
+
+package subscriptions_api <<Frame>> {
+
+ package controller as subscription.controller <<Frame>> {
+
+ class SubscriptionController <<@RestController>> {
+ + <<create>> SubscriptionController(SubscriptionService):
+ + applySubscriptionDelta(String, String, SubscriptionDelta): ResponseEntity<UpdateURLsWrapper>
+ + getSubscriptions(String, String, String): ResponseEntity<List<String>>
+ + getSubscriptionDelta(String, String, long): ResponseEntity<SubscriptionDelta>
+ + getTitles(String): ResponseEntity<List<SubscriptionTitles>>
+ + uploadSubscriptions(String, String, List<String>): ResponseEntity<String>
+ }
+
+ class SubscriptionDelta {
+ + <<create>> SubscriptionDelta(List<String>, List<String>):
+ + getTimestamp(): long
+ + getRemove(): List<String>
+ + getAdd(): List<String>
+ }
+
+ entity SubscriptionTitles << record >> {
+ + <<create>> SubscriptionTitles(Subscription, List<EpisodeActionPost>):
+ + episodes(): List<EpisodeActionPost>
+ + subscription(): Subscription
+ }
+ }
+
+ package data_access as subscription.dao <<Frame>> {
+
+ interface SubscriptionActionDao <<@Repository>> {
+ + findByUserUsernameAndAddedTrue(String): List<SubscriptionAction>
+ + existsByUserAndSubscription(User, Subscription): boolean
+ + findByUserAndSubscription(User, Subscription): Optional<SubscriptionAction>
+ + findByUserUsernameAndTimestampGreaterThanEqual(String, long): List<SubscriptionAction>
+ + findByUserUsernameAndAddedTrueAndTimestampGreaterThanEqual(String, long): List<SubscriptionAction>
+ }
+
+ interface SubscriptionDao <<@Repository>> {
+ + findByUrl(String): Optional<Subscription>
+ + existsByUrl(String): boolean
+ }
+ }
+
+ package service as subscription.service <<Frame>> {
+
+ class SubscriptionService <<@Service>> {
+ + <<create>> SubscriptionService(RSSParser, AuthenticationDao, SubscriptionDao, SubscriptionActionDao, EpisodeActionDao, EpisodeActionService):
+ + getTitles(String): List<SubscriptionTitles>
+ + getSubscriptions(String): List<String>
+ + applySubscriptionDelta(String, SubscriptionDelta): int
+ + getSubscriptionDelta(String, long): SubscriptionDelta
+ + uploadSubscriptions(String, List<String>): int
+ }
+ }
+}
+
+package util <<Frame>> {
+
+ class RSSParser <<@Component>> {
+ + <<create>> RSSParser(EpisodeDao, SubscriptionDao):
+ + validate(Subscription): void
+ - parseTimeToSeconds(String): int
+ - parseEpisode(SyndEntry, Subscription): Episode
+ - saveEpisodes(List<Episode>): void
+ - fetchSubscriptionFeed(Subscription): Map<String, Episode>?
+ - saveSubscription(Subscription): void
+ - deleteSubscription(Subscription): void
+ - getFetchedEpisodeForURL(String, Map<String, Episode>): Episode
+ - deleteEpisodes(List<Episode>): void
+ }
+
+ class Scheduler <<@Component>> {
+ + <<create>> Scheduler():
+ + clean(): void
+ }
+
+ class UpdateURLsWrapper {
+ + <<create>> UpdateURLsWrapper():
+ + getTimestamp(): long
+ + getUpdateURLs(): List<Pair<String, String>>
+ }
+}
+
+class ServerApplication <<@SpringBootApplication>> {
+ + <<create>> 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