@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