diff options
Diffstat (limited to '21-implementierungsheft-kolloquium/assets/diagrams')
17 files changed, 987 insertions, 0 deletions
diff --git a/21-implementierungsheft-kolloquium/assets/diagrams/backendComponentDiagram.puml b/21-implementierungsheft-kolloquium/assets/diagrams/backendComponentDiagram.puml new file mode 100644 index 0000000..e382351 --- /dev/null +++ b/21-implementierungsheft-kolloquium/assets/diagrams/backendComponentDiagram.puml @@ -0,0 +1,61 @@ +@startuml +' skinparam linetype ortho + +'######################################################################### +'SubscriptionsAPI +component SubscriptionsAPI { + + component SubscriptionService + component SubscriptionController + component SubscriptionDataAccessLayer + + portout "Webserver" as wSub + portin "Database" as dSub + } + +dSub --0)- SubscriptionDataAccessLayer +SubscriptionDataAccessLayer --0)- SubscriptionService +SubscriptionService --0)- SubscriptionController +SubscriptionController --0)- wSub + +'######################################################################### + + +'######################################################################### +'EpisodeActionsAPI + +component EpisodeActionsAPI { + component EpisodeActionService + component EpisodeActionController + component EpisodeActionDataAccessLayer + + portout "Webserver" as wEpisode + portin "Database" as dEpisode +} + +dEpisode --0)- EpisodeActionDataAccessLayer +EpisodeActionController --0)- wEpisode +EpisodeActionDataAccessLayer --0)- EpisodeActionService +EpisodeActionService --0)- EpisodeActionController + +'######################################################################### + + +'######################################################################### +'AuthenticationAPI + +component AuthenticationAPI { + component AuthenticationService + component AuthenticationController + component AuthenticationDataAccessLayer + + portout "Webserver" as wAuth + portin "Database" as dAuth +} + +dAuth --0)- AuthenticationDataAccessLayer +AuthenticationController --0)- wAuth +AuthenticationDataAccessLayer --0)- AuthenticationService +AuthenticationService --0)- AuthenticationController + +@enduml diff --git a/21-implementierungsheft-kolloquium/assets/diagrams/classdiagram-authentication.puml b/21-implementierungsheft-kolloquium/assets/diagrams/classdiagram-authentication.puml new file mode 100644 index 0000000..a2b3518 --- /dev/null +++ b/21-implementierungsheft-kolloquium/assets/diagrams/classdiagram-authentication.puml @@ -0,0 +1,112 @@ +@startuml + +package authenticationAPI <<Frame>> { + package authenticationDataAccessLayer <<Frame>> { + ' interface AuthenticationDao { + ' String login(String username) + ' int logout(String username) + ' } + + ' class AuthenticationDataAccessService <<@Respository>> { + ' <<create>> AuthenticationDataAccessService(JpaTemplate jpaTemplate) + ' String login(String username) + ' int logout(String username) + ' } + + interface UserDetailsManager { + void createUser(UserDetails userDetails) + void changePassword(String oldPassword, String newPassword) + void deleteUser(String username) + void updateUser(UserDetails user) + boolean userExists(String username) + } + note left + Aus org.springframework.security.provisioning + - liefert Methoden zum Erstellen neuer User + und zum Aktualisieren bestehender. + end note + + class JdbcUserDetailsManager <<@Repository>> { + <<create>> JdbcUserDetailsManager(DataSource dataSource) + void createUser(UserDetails user) + void changePassword(String oldPassword, String newPassword) + void deleteUser(String username) + void updateUser(UserDetails user) + boolean userExists(String username) + } + note right + User Management Service aus dem Paket + org.springframework.security.provisioning + der CRUD Operationen für User bereitstellt. + Hier sind nur die relevanten Methoden modelliert. + end note + } + + package authenticationService <<Frame>> { + class AuthenticationService <<@Service>> { + -- + <<create>> AuthenticationService(UserDetailsManager userDetailsManager) + List<String> verifyLogin(String username) + int logout(String username) + int forgotPassword(ForgotPasswordRequest forgotPasswordRequest) + .. via JdbcUserDetailsManager .. + int resetPassword(String username, RequestWithPassword requestWithPassword) + int registerUser(UserDetails user) + int changePassword(String username, ChangePasswordRequest changePasswordRequest) + int deleteUser(String username, RequestWithPassword requestWithPassword) + } + + class JavaMailSenderImpl {} + note left + Aus org.springframework.mail.javamail. + Implementierung des JavaMailSender Interfaces, + welches das MailSender Interface durch Unterstützung + von MIME Nachrichten erweitert. + Das MailSender Interface definiert dabei eine + Strategie zum Versenden einfacher Mails. + Unterstützt sowohl JavaMail MimeMessages und + Spring SimpleMailMessages. + end note + } + + package authenticationController <<Frame>> { + class AuthenticationController <<@Controller>> { + <<create>> AuthenticationController(AuthenticationService authenticationService) + ResponseEntity<List<String>> verifyLogin(String username) + ResponseEntity<Integer> logout(String username) + ResponseEntity<Integer> forgotPassword(ForgotPasswordRequest forgotPasswordRequest) + ResponseEntity<Integer> resetPassword(String username, RequestWithPassword requestWithPassword) + ResponseEntity<Integer> registerUser(UserDetails user) + ResponseEntity<Integer> changePassword(String username, ChangePasswordRequest changePasswordRequest) + ResponseEntity<Integer> deleteUser(String username, RequestWithPassword requestWithPassword) + } + + class ChangePasswordRequest { + <<create>> ChangePasswordRequest(String oldPassword, String newPassword) + String getOldPassword() + String getNewPassword() + } + + class ForgotPasswordRequest { + <<create>> ForgotPasswordRequest(String email) + String getEmail() + } + + class RequestWithPassword { + <<create>> ResetPasswordRequest(String password) + String getPassword() + } + } +} + +' User <.. AuthenticationDataAccessService: DB +' User <.. JdbcUserDetailsManager: DB +UserDetailsManager <.. AuthenticationService: <<use>> +' AuthenticationDao <.. AuthenticationService: <<use>> +AuthenticationService --o AuthenticationController +' AuthenticationDao <|. AuthenticationDataAccessService: <<realize>> +UserDetailsManager <|. JdbcUserDetailsManager: <<realize>> +JavaMailSenderImpl <. AuthenticationService: <<use>> + +@enduml + diff --git a/21-implementierungsheft-kolloquium/assets/diagrams/classdiagram-episode-actions.puml b/21-implementierungsheft-kolloquium/assets/diagrams/classdiagram-episode-actions.puml new file mode 100644 index 0000000..7a4530e --- /dev/null +++ b/21-implementierungsheft-kolloquium/assets/diagrams/classdiagram-episode-actions.puml @@ -0,0 +1,84 @@ +@startuml + +package episodeActionsAPI <<Frame>> { + package episodeActionDataAccessLayer <<Frame>> { + class EpisodeActionDataAccessService <<@Repository>> { + <<create>> EpisodeActionDataAccessService (JpaTemplate jpaTemplate) + long addEpisodeActions(String username, List<EpisodeActionPost> episodeActionPosts) + List<EpisodeActionPost> getEpisodeActions(String username) + List<EpisodeActionPost> getEpisodeActionsOfPodcast(String username, String podcastURL) + List<EpisodeActionPost> getEpisodeActionsSince(String username, LocalDateTime since) + List<EpisodeActionPost> getEpisodeActionsOfPodcastSince(String username, String podcastURL, LocalDateTime since) + } + + interface EpisodeActionDao { + long addEpisodeActions(String username, List<EpisodeActionPost> episodeActionPosts) + List<EpisodeActionPost> getEpisodeActions(String username) + List<EpisodeActionPost> getEpisodeActionsOfPodcast(String username, String podcastURL) + List<EpisodeActionPost> getEpisodeActionsSince(String username, LocalDateTime since) + List<EpisodeActionPost> getEpisodeActionsOfPodcastSince(String username, String podcastURL, LocalDateTime since) + } + } + + package episodeActionService <<Frame>> { + class EpisodeActionService <<@Service>> { + <<create>> EpisodeActionService (EpisodeActionDao episodeActionDao) + LocalDateTime addEpisodeActions(String username, List<EpisodeActionPosts> episodeActionPosts) + List<EpisodeActionPost> getEpisodeActions(String username) + List<EpisodeActionPost> getEpisodeActionsOfPodcast(String username, String podcastURL) + List<EpisodeActionPost> getEpisodeActionsSince(String username, LocalDateTime since) + List<EpisodeActionPost> getEpisodeActionsOfPodcastSince(String username, String podcastURL, LocalDateTime since) + } + } + + package episodeActionController <<Frame>> { + class EpisodeActionController <<@Controller>>{ + <<create>> EpisodeActionController (EpisodeActionService episodeActionService) + ResponseEntity<EpisodeActionPostResponse> addEpisodeActions(String username, EpisodeActionPostRequest episodeActionPostRequest) + ResponseEntity<EpisodeActionGetResponse> getEpisodeActions(String username, String deviceID, boolean aggregated) + ResponseEntity<EpisodeActionGetResponse> getEpisodeActionsOfPodcast(String username, String podcastURL, String deviceID, boolean aggregated) + ResponseEntity<EpisodeActionGetResponse> getEpisodeActionsSince(String username, String deviceID, long since, boolean aggregated) + ResponseEntity<EpisodeActionGetResponse> getEpisodeActionsOfPodcastSince(String username, String podcastURL, String deviceID, long since, boolean aggregated) + } + + class EpisodeActionPostResponse { + <<create>> EpisodeActionPostResponse(List<Pair<String, String>> updateURLs) + long getTimestamp() + List<Pair<String, String>> getUpdatedURLs() + } + + class EpisodeActionPost { + <<create>> EpisodeActionPost(String podcastURL, String episodeURL, Action action, LocalDateTime timestamp, int started, int position) + String getPodcastURL() + String getEpisodeURL() + int getGUID() + Action getAction() + LocalDateTime getTimestamp() + int getStarted() + int getPosition() + EpisodeAction getEpisodeAction() + } + + class EpisodeActionPostRequest { + <<create>> EpisodeActionPostRequest(List<EpisodeActionPost> episodeActionPosts) + List<EpisodeActionPost> getEpisodeActionPosts() + } + + class EpisodeActionGetResponse { + <<create>> EpisodeActionGetResponse(List<EpisodeActionPost> episodeActionPosts) + List<EpisodeActionPost> getEpisodeActionPosts() + long getTimestamp() + } + } +} + +EpisodeActionPost -o EpisodeActionGetResponse +EpisodeActionPost -o EpisodeActionPostRequest +' EpisodeAction <.. EpisodeActionDataAccessService: DB +' Episode <.. EpisodeActionDataAccessService: DB +EpisodeActionDao <.. EpisodeActionService: <<use>> +EpisodeActionService --o EpisodeActionController +EpisodeActionDao <|. EpisodeActionDataAccessService: <<realize>> + +@enduml + diff --git a/21-implementierungsheft-kolloquium/assets/diagrams/classdiagram-model.puml b/21-implementierungsheft-kolloquium/assets/diagrams/classdiagram-model.puml new file mode 100644 index 0000000..72ad49f --- /dev/null +++ b/21-implementierungsheft-kolloquium/assets/diagrams/classdiagram-model.puml @@ -0,0 +1,109 @@ +@startuml + +package model <<Frame>> { + class Subscription { + <<create>> Subscription(String url, String title) + int getID() + String getURL() + long getLastActionTimestamp() + String getTitle() + } + + class SubscriptionAction { + <<create>> SubscriptionAction(int userID, int subscriptionID) + int getID() + int getUserID() + int getSubscriptionID() + long getTimestamp() + boolean getAdded() + } + + class Episode { + <<create>> Episode(int subscriptionID, int id, String url, String title, String thumbnailURL, int total) + int getSubscriptionID() + int getID() + int getGUID() + String getURL() + String getTitle() + int getTotal() + } + + enum Action { + Download + Play + Delete + New + Flattr + String getJsonProperty() + } + + class EpisodeAction { + <<create>> EpisodeAction(Action action, LocalDateTime timestamp, int started, int position) + int getEpisodeID() + Action getAction() + long getTimestamp() + int getStarted() + int getPosition() + void setEpisodeID() + EpisodeActionPost getEpisodeActionPost(String podcastURL, String episodeURL) + } + + interface UserDetails { + String getUsername() + String getPassword() + Collection<Authority> getAuthorities() + boolean isAccountExpired() + boolean isAccountLocked() + boolean isCredentialsNonExpired() + boolean isEnabled() + } + note left + Aus org.springframework.security.core.userdetails. + Wird für die Schnittstelle UserDetailsManager benötigt. + Stellt wichtige Informationen eines Users bereit. + Diese werden nur indirekt von Spring Security + benutzt, indem sie vorher in Authentication Objekten + gekapselt werden. + end note + + class User { + -- + <<create>> User(String username, String password) + int getID() + String getSessionToken() + boolean getEmailIsValidated() + .. interface methods .. + String getUsername() + String getPassword() + Collection<Authority> getAuthorities() + boolean isAccountExpired() + boolean isAccountLocked() + boolean isCredentialsNonExpired() + boolean isEnabled() + } + + interface GrantedAuthority { + String getAuthority() + } + note right + Aus org.springframework.security.core. + Wird für die Schnittstelle UserDetails benötigt. + Repräsentiert eine Autorisierung, die einem + Authentication Objekt gewährt wird. + end note + + class Authority { + <<create>> Authority() + String getAuthority() + } +} + +Subscription <. SubscriptionAction: ID +Action <-- EpisodeAction +EpisodeAction .> Episode: ID +UserDetails <|.. User: <<realize>> +User -> Authority +GrantedAuthority <|.. Authority: <<realize>> + +@enduml + diff --git a/21-implementierungsheft-kolloquium/assets/diagrams/classdiagram-subscriptions.puml b/21-implementierungsheft-kolloquium/assets/diagrams/classdiagram-subscriptions.puml new file mode 100644 index 0000000..432f185 --- /dev/null +++ b/21-implementierungsheft-kolloquium/assets/diagrams/classdiagram-subscriptions.puml @@ -0,0 +1,75 @@ +@startuml + +package subscriptionsAPI <<Frame>> { + package subscriptionDataAccessLayer <<Frame>> { + class SubscriptionDataAccessService <<@Repository>> { + <<create>> SubscriptionDataAccessService(JpaTemplate jpaTemplate) + int uploadSubscriptions(String username, List<SubscriptionAction> subscriptions) + List<String> getSubscriptions(String username) + List<String> getSubscriptionsSince(String username, LocalDateTime time) + int addSubscriptions(String username, List<SubscriptionAction> addedSubscriptions) + int removeSubscriptions(String username, List<SubscriptionAction> removedSubscriptions) + List<SubscriptionTitles> getTitles(String username) + } + + interface SubscriptionDao { + int uploadSubscriptions(String username, List<SubscriptionAction> subscriptions) + List<String> getSubscriptions(String username) + List<String> getSubscriptionsSince(String username, LocalDateTime time) + int addSubscriptions(String username, List<SubscriptionAction> addedSubscriptions) + int removeSubscriptions(String username, List<SubscriptionAction> removedSubscriptions) + List<SubscriptionTitles> getTitles(String username) + } + } + + package subscriptionService <<Frame>> { + class SubscriptionService <<@Service>> { + <<create>> SubscriptionService(SubscriptionDao subscriptionDao) + int uploadSubscriptions(String username, List<SubscriptionAction> subscriptions) + List<String> getSubscriptions(String username) + List<String> getSubscriptionsSince(String username, LocalDateTime time) + int addSubscriptions(String username, List<SubscriptionAction> addedSubscriptions) + int removeSubscriptions(String username, List<SubscriptionAction> removedSubscriptions) + List<SubscriptionTitles> getTitles(String username) + } + } + + package subscriptionController <<Frame>> { + class SubscriptionController <<@Controller>>{ + ' @Autowired + <<create>> SubscriptionController(SubscriptionService subscriptionService) + ' @GetMapping + ResponseEntity<List<String>> getSubscriptions(String username, String deviceID, String functionJSONP) + ' @PutMapping + ResponseEntity<String> uploadSubscriptions(String username, String deviceID, List<String> subscriptions) + ' @PostMapping + ResponseEntity<SubscriptionDelta> applySubscriptionDelta(String username, String deviceID, SubscriptionDelta delta) + ' @GetMapping + ResponseEntity<SubscriptionDelta> getSubscriptionDelta(String username, String deviceID, long since) + ResponseEntity<List<SubscriptionTitles>> getTitles(String username, String deviceID) + } + + class SubscriptionTitles { + <<create>> SubscriptionTitles(Subscription subscription, List<EpisodeActionPost> episodeTitles) + Subscription getSubscription() + List<EpisodeActionPost> getEpisodesTitles() + } + + class SubscriptionDelta { + <<create>> SubscriptionDelta(List<String> add, List<String> remove) + List<String> getRemove() + LocalDate getTimestamp() + List<List<String>> getUpdate_urls() + } + } + +} + +' Subscription <.. SubscriptionDataAccessService: DB +' SubscriptionAction <.. SubscriptionDataAccessService: DB +SubscriptionService --o SubscriptionController +SubscriptionDao <.. SubscriptionService: <<use>> +SubscriptionDao <|. SubscriptionDataAccessService: <<realize>> + +@enduml + diff --git a/21-implementierungsheft-kolloquium/assets/diagrams/classdiagram-util.puml b/21-implementierungsheft-kolloquium/assets/diagrams/classdiagram-util.puml new file mode 100644 index 0000000..03dfc9a --- /dev/null +++ b/21-implementierungsheft-kolloquium/assets/diagrams/classdiagram-util.puml @@ -0,0 +1,43 @@ +@startuml + +package util <<Frame>> { + class RSSParser { + <<create>> RSSParser(String subscriptionURL) + String getSubscriptionTitle() + List<Episode> getEpisodes() + Episode getEpisodeForURL(String episodeURL) + } + note bottom + Verwendet intern Spring um + HTTP-Anfragen zu erstellen. + end note + + class CleanCronJob { + <<create>> CleanCronJob(JdbcUserDetailsManager jdbcUserDetailsManager) + void cleanInvalidUsers() + } + note bottom + Hintergrundservice, der in periodischen Abständen + Nutzer, die ihre E-Mail-Adresse nicht nach 24 Stunden + bestätigt haben, wieder aus der Datenbank löscht. + (Auf die Assoziation zu JdbcUserDetailsManager wird + im Sinne der Übersichtlichkeit verzichtet.) + end note + + class ResponseEntity<T> { + <<create>> ResponseEntity(T body, HttpStatusCode status) + T getBody() + HttpStatusCode getStatusCode() + } + note bottom + Aus org.springframework.http. + Erweitert die Klasse HttpEntity, welche + ein HTTP Anfrage- oder Antwort-Objekt + repräsentiert, durch einen HttpStatusCode. + Wird von den Controller-Methoden als + Rückgabewert verwendet. + end note +} + +@enduml + diff --git a/21-implementierungsheft-kolloquium/assets/diagrams/classdiagram.puml b/21-implementierungsheft-kolloquium/assets/diagrams/classdiagram.puml new file mode 100644 index 0000000..f833aa2 --- /dev/null +++ b/21-implementierungsheft-kolloquium/assets/diagrams/classdiagram.puml @@ -0,0 +1,68 @@ +@startuml +' skinparam linetype ortho +' skinparam groupInheritance 2 +allowmixing + +!include classdiagram-authentication.puml +!include classdiagram-episode-actions.puml +!include classdiagram-model.puml +!include classdiagram-subscriptions.puml +!include classdiagram-util.puml + +class SecurityConfigurationBasicAuth { + <<create>> SecurityConfigurationBasicAuth() + PasswordEncoder encoder() + UserDetailsManager userDetailsService() + SecuryFilterChain fiterChain(HTTPSecurity http) throws Excpetion +} +note top + Erstellt einen Servlet Filter (springSecurityFilterChain) + der für die gesamte Sicherheit zuständig ist (Schutz der URLs, + Validierung von Anmeldedaten, Weiterleitung zur Anmeldung, etc.). +end note + +class PSEApplication { + <<create>> PSEApplication() + void main(String[] args) +} + +database Datenbank +Datenbank <-[hidden]d- subscriptionsAPI +Datenbank <-[hidden]d- episodeActionsAPI +Datenbank <-[hidden]d- authenticationAPI +() SQL as SQLSub +() SQL as SQLAuth +() SQL as SQLEpisode + +Datenbank -- SQLSub +Datenbank -- SQLAuth +Datenbank -- SQLEpisode + +Subscription --o SubscriptionTitles +EpisodeActionPost -o SubscriptionTitles +UserDetailsManager <.. SecurityConfigurationBasicAuth: <<use>> + +SubscriptionController ..o PSEApplication +AuthenticationController ..o PSEApplication +EpisodeActionController ..o PSEApplication +SecurityConfigurationBasicAuth ..o PSEApplication + +PSEApplication --() HTTP + +SQLSub )-- SubscriptionDataAccessService: JPA +' SQLAuth )-- AuthenticationDataAccessService: JPA +SQLAuth )-- JdbcUserDetailsManager: JDBC +SQLEpisode )-- EpisodeActionDataAccessService: JPA + +RSSParser <. SubscriptionDataAccessService: <<use>> +RSSParser <. EpisodeActionDataAccessService: <<use>> +' JdbcUserDetailsManager <-- CleanCronJob + +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 + +@enduml diff --git a/21-implementierungsheft-kolloquium/assets/diagrams/componentdiagram.puml b/21-implementierungsheft-kolloquium/assets/diagrams/componentdiagram.puml new file mode 100644 index 0000000..dea4a1d --- /dev/null +++ b/21-implementierungsheft-kolloquium/assets/diagrams/componentdiagram.puml @@ -0,0 +1,53 @@ +@startuml + +[App] as app +[VueRouter] as router +[NavbarComponent] as navbar +[LoginPage] as login_page +[SubscriptionsPage] as abo_page +[EpisodesPage] as episodes_page +[SettingsPage] as settings_page +[ForgotPasswordPage] as forgot_page +[ResetPasswordPage] as reset_page +note top + Wird in der E-Mail zum Zurücksetzen des Passworts mit dem JWT-Token verlinkt. + Sendet das alte und neue Passwort und den JWT an die API. +end note +[RegistrationPage] as registration_page + +[SubscriptionComponent] as sub +[EpisodeComponent] as episode +[LastUpdateComponent] as last_update +[PasswordValidatorComponent] as password + +app --> router + +app --> navbar +router --> login_page +router --> forgot_page +router --> reset_page +router --> registration_page +router --> abo_page +router --> episodes_page +router --> settings_page + +navbar -[hidden] router + +episodes_page -[hidden] abo_page +login_page -[hidden] forgot_page +registration_page -[hidden] reset_page +abo_page -[hidden] settings_page +forgot_page -[hidden] episodes_page +' forgot_page -[hidden] settings_page + +abo_page --> sub +episodes_page --> episode + +sub --> last_update +episode --> last_update + +settings_page --> password +reset_page --> password +registration_page --> password + +@enduml diff --git a/21-implementierungsheft-kolloquium/assets/diagrams/db.puml b/21-implementierungsheft-kolloquium/assets/diagrams/db.puml new file mode 100644 index 0000000..bdefaea --- /dev/null +++ b/21-implementierungsheft-kolloquium/assets/diagrams/db.puml @@ -0,0 +1,78 @@ +@startuml +' Type Symbol +' Zero or One |o-- +' Exactly One ||-- +' Zero or Many }o-- +' One or Many }|-- + +skinparam linetype ortho + +entity User { + * int id <<unique>> + * <u>String email</u> + * String password + * boolean verified + * long created_at +} + +entity SubscriptionAction { + * int id <<unique>> + * <u>int user_id</u> + * long timestamp + * int subscription_id + * boolean added +} + +entity Subscription { + * int id <<unique>> + * <u>String url</u> + * long timestamp + * String title +} + +entity Episode { + * int id <<unique>> + * <u>int guid <<unique>></u> + * <u>String url</u> + * String title + * int total + * int subscription_id +} +note right + Wenn der Client eine GUID aus dem Feed mitsendet, wird + diese statt der URL verwendet um die Episode zu finden. + So wird die Episode auch noch gefunden, nachdem sich + die URL geändert hat. +end note +note bottom of Episode + Wenn für die Episoden-URL einer EpisodeAction noch keine Episode in der Datenbank steht, + dann schreibe dafür ein Dummy-Objekt in Datenbank und lade asynchron die Episoden der Subscription. + Ersetze dann die Dummy-Objekte durch die Episoden und setze den Timestamp der Subscription auf + die aktuelle Zeit. + Um DoS-Angriffe auf den Backend-Server abzuwenden, können die Episoden einer Subscription erst + nach einer Stunde erneut gefetched werden. Bis dahin werden für EpisodeActions, die sich auf noch + nicht geladene Episoden beziehen, nur Dummy-Objekte für die Episoden in die Datenbank geschrieben. + Es sei noch darauf hingewiesen, dass diese Dummy-Episoden bei Anfragen nicht mit ausgegeben werden. +end note + +entity EpisodeAction { + * int id <<unique>> + * <u>int user_id</u> + * int episode_id + * long timestamp + * int action + * int started + * int position +} +note right + Speichere für jede Episode + nur letzte Play-Action. +endnote + +User ||--o{ EpisodeAction +User ||--o{ SubscriptionAction +SubscriptionAction }|--|| Subscription +EpisodeAction }|--|| Episode +Subscription ||-right-|{ Episode + +@enduml diff --git a/21-implementierungsheft-kolloquium/assets/diagrams/deployment.puml b/21-implementierungsheft-kolloquium/assets/diagrams/deployment.puml new file mode 100644 index 0000000..b8d0491 --- /dev/null +++ b/21-implementierungsheft-kolloquium/assets/diagrams/deployment.puml @@ -0,0 +1,59 @@ +@startuml + +node "<<device>> \nBackend Server" as backendServer{ + database " <<database system>> \n MariaDB Server 10.6" as database { + rectangle rectangle1 [ + <<schema>> + User + ] + rectangle rectangle2 [ + <<schema>> + SubscriptionAction + ] + rectangle rectangle3 [ + <<schema>> + EpisodeAction + ] + rectangle rectangle4 [ + <<schema>> + Subscription + ] + rectangle rectangle5 [ + <<schema>> + Episode + ] + } + + node "<<framework>> \nJava Spring" as javaSpring{ + node " <<device>> \n Tomcat Webserver" + } +} + +node "<<device>> \nFrontend" as frontendServer { + +} + +node "<<device>> \nEndgerät" as terminal { + node "<<application>> \nBrowser" as browser + node "<<application>> \nPodcatcher" as podcatcher +} + +backendServer "1" - "*" podcatcher + +node "<<device>> \nFrontend Server" as frontendServer{ + node "<<framework>> \nVue.js" as vuejs { + + } +} + +podcatcher -[hidden] browser + +backendServer - "1" frontendServer + +database "1" -- "1" javaSpring + +browser "*" -- frontendServer + + + +@enduml diff --git a/21-implementierungsheft-kolloquium/assets/diagrams/sequencediagram-forgotAndResetPW.puml b/21-implementierungsheft-kolloquium/assets/diagrams/sequencediagram-forgotAndResetPW.puml new file mode 100644 index 0000000..603130c --- /dev/null +++ b/21-implementierungsheft-kolloquium/assets/diagrams/sequencediagram-forgotAndResetPW.puml @@ -0,0 +1,41 @@ +@startuml + +skinparam ParticipantPadding 30 + +participant AuthenticationController << (C, #ADD1B2) @Controller >> +-> AuthenticationController: ""POST /api/2/auth/forgot.json"" \n//@RequestBody ForgotPasswordRequest forgotPasswordRequest// \n\n-> forgotPassword(//forgotPasswordRequest//) +activate AuthenticationController +participant AuthenticationService << (C, #ADD1B2) @Service >> +AuthenticationController -> AuthenticationService: forgotPassword(//forgotPasswordRequest//) +activate AuthenticationService +participant JavaMailSenderImpl << (C, #ADD1B2) >> +AuthenticationService -> JavaMailSenderImpl: create link to reset password with JWT as URL parameter \n-> send(SimpleMailMessage simpleMessage) with link +activate JavaMailSenderImpl +<<- JavaMailSenderImpl: sends email with link containing a JWT to reset password +JavaMailSenderImpl --> AuthenticationService +deactivate JavaMailSenderImpl +AuthenticationService --> AuthenticationController: int indicating status +deactivate AuthenticationService +<-- AuthenticationController: ResponseEntity<Integer> indicating status \n\n-> ""HTTP status code"" +deactivate AuthenticationController +||60|| +-> AuthenticationController: ""PUT /api/2/auth/{username}/resetpassword.json"" \n//@RequestParam String jwt// \n//@RequestBody ResetPasswordRequest resetPasswordRequest// \n\n-> login user (""username"") via JWT (//jwt//) \n-> resetPassword(""username"", //resetPasswordRequest//) +activate AuthenticationController +AuthenticationController -> AuthenticationService: resetPassword(""username"", //resetPasswordRequest//) +activate AuthenticationService +participant JdbcUserDetailsManager << (C, #ADD1B2) @Repository >> +AuthenticationService -> JdbcUserDetailsManager: String oldPassword = //resetPasswordRequest//.getOldPassword() \nString newPassword = //resetPasswordRequest//.getNewPassword() \n-> changePassword(newPassword, oldPassword) +activate JdbcUserDetailsManager +database Database +JdbcUserDetailsManager -> Database: change password of logged in user +activate Database +Database --> JdbcUserDetailsManager +deactivate Database +JdbcUserDetailsManager --> AuthenticationService: int indicating status +deactivate JdbcUserDetailsManager +AuthenticationService --> AuthenticationController: int indicating status +deactivate AuthenticationService +<-- AuthenticationController: ResponseEntity<Integer> indicating status \n\n-> ""HTTP status code"" +deactivate AuthenticationController + +@enduml
\ No newline at end of file diff --git a/21-implementierungsheft-kolloquium/assets/diagrams/sequencediagram-getEpisodeActions.puml b/21-implementierungsheft-kolloquium/assets/diagrams/sequencediagram-getEpisodeActions.puml new file mode 100644 index 0000000..47497d5 --- /dev/null +++ b/21-implementierungsheft-kolloquium/assets/diagrams/sequencediagram-getEpisodeActions.puml @@ -0,0 +1,38 @@ +@startuml + +' title =**Get All Episode Actions** + +participant EpisodeActionController << (C, #ADD1B2) @Controller >> +-> EpisodeActionController: ""GET /api/2/episodes/{username}.json"" \n//@RequestParam("device") String deviceID// \n//@RequestParam("aggregated") boolean aggregated// \n\n-> getEpisodeActions(""username"", //deviceID//, //aggregated//) +note right + Die Parameter //deviceID// und //aggregated// werden ignoriert, + da nicht zwischen Geräten unterschieden und für jede + Episode sowieso nur die letzte Play-Action gespeichert + wird. Dies gilt für alle GET-Anfragen der Episode Actions API. +end note +activate EpisodeActionController +participant EpisodeActionService << (C, #ADD1B2) @Service >> +EpisodeActionController -> EpisodeActionService: getEpisodeActions(""username"") +activate EpisodeActionService +participant EpisodeActionDataAccessService << (C, #ADD1B2) @Repository >> +EpisodeActionService -> EpisodeActionDataAccessService: getEpisodeActions(""username"") +activate EpisodeActionDataAccessService +EpisodeActionDataAccessService -> EpisodeActionDataAccessService: getEpisodeActionsSince(""username"", \nLocalDateTime.MIN.toEpochSecond(ZoneOffset.UTC)) +database Database +activate EpisodeActionDataAccessService +EpisodeActionDataAccessService -> Database: get all EpisodeActions for all subscribed podcasts +activate Database +Database --> EpisodeActionDataAccessService: List<EpisodeAction> selectedEpisodeActions \n-> then remove all older than LocalDateTime.MIN (none) +EpisodeActionDataAccessService -> Database: join EpisodeActions in selectedEpisodeActions with episodeURL of Episode +Database --> EpisodeActionDataAccessService +deactivate Database +EpisodeActionDataAccessService --> EpisodeActionDataAccessService: List<EpisodeActionPost> episodeActionPosts +deactivate EpisodeActionDataAccessService +EpisodeActionDataAccessService --> EpisodeActionService: List<EpisodeActionPost> episodeActionPosts +deactivate EpisodeActionDataAccessService +EpisodeActionService --> EpisodeActionController: List<EpisodeActionPost> episodeActionPosts +deactivate EpisodeActionService +<-- EpisodeActionController: ResponseEntity<EpisodeActionGetResponse> response \n\n-> ""HTTP status code"" \n-> ""JSON"" +deactivate EpisodeActionController + +@enduml
\ No newline at end of file diff --git a/21-implementierungsheft-kolloquium/assets/diagrams/sequencediagram-getEpisodeActionsOfPodcastSince.puml b/21-implementierungsheft-kolloquium/assets/diagrams/sequencediagram-getEpisodeActionsOfPodcastSince.puml new file mode 100644 index 0000000..d8797d1 --- /dev/null +++ b/21-implementierungsheft-kolloquium/assets/diagrams/sequencediagram-getEpisodeActionsOfPodcastSince.puml @@ -0,0 +1,32 @@ +@startuml + +' title =**Get Episode Actions of Podcast Since** + +participant EpisodeActionController << (C, #ADD1B2) @Controller >> +-> EpisodeActionController: ""GET /api/2/episodes/{username}.json"" \n//@RequestParam("podcast") String podcastURL// \n//@RequestParam("device") String deviceID// \n//@RequestParam("since") long since// \n//@RequestParam("aggregated") boolean aggregated// \n\n-> getEpisodeActionsOfPodcastSince(""username"", //podcastURL//, //deviceID//, //since//, //aggregated//) +note right + Die Parameter //deviceID// und //aggregated// werden ignoriert. + Siehe Notiz in Sequenzdiagramm **Get All Episode Actions**. +end note +activate EpisodeActionController +participant EpisodeActionService << (C, #ADD1B2) @Service >> +EpisodeActionController -> EpisodeActionService: getEpisodeActionsOfPodcastSince(""username"", //podcastURL//, //since//) +activate EpisodeActionService +participant EpisodeActionDataAccessService << (C, #ADD1B2) @Repository >> +EpisodeActionService -> EpisodeActionDataAccessService: getEpisodeActionsOfPodcastSince(""username"", //podcastURL//, //since//) +activate EpisodeActionDataAccessService +database Database +EpisodeActionDataAccessService -> Database: get all EpisodeActions the given podcast (//podcastURL//) +activate Database +Database --> EpisodeActionDataAccessService: List<EpisodeAction> selectedEpisodeActions \n-> then remove all older than //since// +EpisodeActionDataAccessService -> Database: join EpisodeActions in selectedEpisodeActions with episodeURL of Episode +Database --> EpisodeActionDataAccessService +deactivate Database +EpisodeActionDataAccessService --> EpisodeActionService: List<EpisodeActionPost> episodeActionPosts +deactivate EpisodeActionDataAccessService +EpisodeActionService --> EpisodeActionController: List<EpisodeActionPost> episodeActionPosts +deactivate EpisodeActionService +<-- EpisodeActionController: ResponseEntity<EpisodeActionGetResponse> response \n\n-> ""HTTP status code"" \n-> ""JSON"" +deactivate EpisodeActionController + +@enduml
\ No newline at end of file diff --git a/21-implementierungsheft-kolloquium/assets/diagrams/sequencediagram-getSubscriptions.puml b/21-implementierungsheft-kolloquium/assets/diagrams/sequencediagram-getSubscriptions.puml new file mode 100644 index 0000000..4d8ab90 --- /dev/null +++ b/21-implementierungsheft-kolloquium/assets/diagrams/sequencediagram-getSubscriptions.puml @@ -0,0 +1,38 @@ +@startuml + +' title =**Get All Subscriptions** + +participant SubscriptionController << (C, #ADD1B2) @Controller >> +-> SubscriptionController: ""GET /subscriptions/{username}.json"" \n"" /subscriptions/{username}/{deviceid}.json"" \n//@RequestParam("jsonp") String functionJSONP// \n\n-> getSubscriptions(""username"", ""deviceid"", //functionJSONP//) +activate SubscriptionController +note right + Die Parameter ""deviceid"" und + //functionJSONP// werden ignoriert, + da nicht zwischen Geräten unterschieden + und JSONP nicht unterstützt wird. +end note +participant SubscriptionService << (C, #ADD1B2) @Service >> +SubscriptionController -> SubscriptionService: getSubscriptions(""username"") +activate SubscriptionService +participant SubscriptionDataAccessService << (C, #ADD1B2) @Repository >> +SubscriptionService -> SubscriptionDataAccessService: getSubscriptions(""username"") +activate SubscriptionDataAccessService +SubscriptionDataAccessService -> SubscriptionDataAccessService: getSubscriptionsSince(""username"", LocalDateTime.MIN) +database Database +activate SubscriptionDataAccessService +SubscriptionDataAccessService -> Database: get all Subscriptions for ""username"" +activate Database +Database --> SubscriptionDataAccessService: List<Subscription> subscriptions +SubscriptionDataAccessService -> Database: get Podcasts from Subscriptions +Database --> SubscriptionDataAccessService: List<Podcast> subscribedPodcasts +deactivate Database +SubscriptionDataAccessService --> SubscriptionDataAccessService: List<String> podcastURLs +deactivate SubscriptionDataAccessService +SubscriptionDataAccessService --> SubscriptionService: List<String> podcastURLs +deactivate SubscriptionDataAccessService +SubscriptionService --> SubscriptionController: List<String> podcastURLs +deactivate SubscriptionService +<-- SubscriptionController: ResponseEntity<List<String>> podcastURLs \n \n-> ""HTTP status code"" \n-> ""JSON"" +deactivate SubscriptionController + +@enduml
\ No newline at end of file diff --git a/21-implementierungsheft-kolloquium/assets/diagrams/sequencediagram-register.puml b/21-implementierungsheft-kolloquium/assets/diagrams/sequencediagram-register.puml new file mode 100644 index 0000000..b7b7aa1 --- /dev/null +++ b/21-implementierungsheft-kolloquium/assets/diagrams/sequencediagram-register.puml @@ -0,0 +1,26 @@ +@startuml + +' title =**Register** + +participant AuthenticationController << (C, #ADD1B2) @Controller >> +-> AuthenticationController: ""POST /api/2/auth/register.json"" \n//@RequestBody UserDetails user// \n\n-> registerUser(//user//) +activate AuthenticationController +participant AuthenticationService << (C, #ADD1B2) @Service >> +AuthenticationController -> AuthenticationService: registerUser(//user//) +activate AuthenticationService +participant JdbcUserDetailsManager << (C, #ADD1B2) @Repository >> +AuthenticationService -> JdbcUserDetailsManager: createUser(//user//) +activate JdbcUserDetailsManager +database Database +JdbcUserDetailsManager -> Database: create new User with given UserDetails (//user//) +activate Database +Database --> JdbcUserDetailsManager +deactivate Database +JdbcUserDetailsManager --> AuthenticationService: int indicating status +deactivate JdbcUserDetailsManager +AuthenticationService --> AuthenticationController: int indicating status +deactivate AuthenticationService +<-- AuthenticationController: ResponseEntity<Integer> indicating status \n\n-> ""HTTP status code"" +deactivate AuthenticationController + +@enduml
\ No newline at end of file diff --git a/21-implementierungsheft-kolloquium/assets/diagrams/sequencediagram-uploadEpisodeActions.puml b/21-implementierungsheft-kolloquium/assets/diagrams/sequencediagram-uploadEpisodeActions.puml new file mode 100644 index 0000000..d3dac57 --- /dev/null +++ b/21-implementierungsheft-kolloquium/assets/diagrams/sequencediagram-uploadEpisodeActions.puml @@ -0,0 +1,38 @@ +@startuml + +' title =**Upload Episode Actions** + +participant EpisodeActionController << (C, #ADD1B2) @Controller >> +-> EpisodeActionController: ""POST /api/2/episodes/{username}.json"" \n//@RequestBody EpisodeActionPostRequest episodeActionPostRequest// \n\n-> addEpisodeActions(""username"", //episodeActionPostRequest//) +activate EpisodeActionController +participant EpisodeActionService << (C, #ADD1B2) @Service >> +EpisodeActionController -> EpisodeActionService: addEpisodeActions(""username"", \nepisodeActionPosts = //episodeActionPostRequest//.getEpisodeActionPosts()) +activate EpisodeActionService +participant EpisodeActionDataAccessService << (C, #ADD1B2) @Repository >> +EpisodeActionService -> EpisodeActionDataAccessService: addEpisodeActions(""username"", episodeActionPosts) +database Database +activate EpisodeActionDataAccessService +loop for each EpisodeActionPost in episodeActionPosts -> episodeAction = episodeActionPost.getEpisodeAction() +opt episodeAction.getAction().equals(Action.PLAY) +EpisodeActionDataAccessService -> Database: set episodeID field of episodeAction for this ""username"" via podcastURL and episodeURL +activate Database +Database --> EpisodeActionDataAccessService +EpisodeActionDataAccessService -> Database: get last EpisodeAction with this episodeID if present +Database --> EpisodeActionDataAccessService: Optional<EpisodeAction> lastEpisodeAction +opt lastEpisodeAction.isPresent() +EpisodeActionDataAccessService -> Database: replace lastEpisodeAction with episodeAction +else else +EpisodeActionDataAccessService -> Database: add episodeAction to DB as new entry +end +Database --> EpisodeActionDataAccessService +deactivate Database +end +end +EpisodeActionDataAccessService --> EpisodeActionService: long latestTimestamp +deactivate EpisodeActionDataAccessService +EpisodeActionService --> EpisodeActionController: LocalDateTime timestamp = LocalDateTime.ofEpochSecond(latestTimestamp, 0, ZoneOffset.UTC) +deactivate EpisodeActionService +<-- EpisodeActionController: ResponseEntity<EpisodeActionPostResponse> \n(with empty list for updateURLs) \n\n-> ""HTTP status code"" \n-> ""JSON"" +deactivate EpisodeActionController + +@enduml
\ No newline at end of file diff --git a/21-implementierungsheft-kolloquium/assets/diagrams/sequencediagram-uploadSubscriptions.puml b/21-implementierungsheft-kolloquium/assets/diagrams/sequencediagram-uploadSubscriptions.puml new file mode 100644 index 0000000..1edc8cf --- /dev/null +++ b/21-implementierungsheft-kolloquium/assets/diagrams/sequencediagram-uploadSubscriptions.puml @@ -0,0 +1,32 @@ +@startuml + +' title =**Upload Subscriptions** + +participant SubscriptionController << (C, #ADD1B2) @Controller >> +-> SubscriptionController: ""PUT /subscriptions/{username}/{deviceid}.json"" \n//@RequestBody List<String> subscriptions// \n\n-> uploadSubscriptions(""username"", ""deviceid"", //subscriptions//) +activate SubscriptionController +participant SubscriptionService << (C, #ADD1B2) @Service >> +SubscriptionController -> SubscriptionService: uploadSubscriptions(""username"", //subscriptions//) +activate SubscriptionService +participant SubscriptionDataAccessService << (C, #ADD1B2) @Repository >> +SubscriptionService -> SubscriptionDataAccessService: uploadSubscriptions(""username"", //subscriptions//) +activate SubscriptionDataAccessService +database Database +SubscriptionDataAccessService -> Database: delete all subsciptions of ""username"" +activate Database +Database --> SubscriptionDataAccessService +SubscriptionDataAccessService -> SubscriptionDataAccessService: addSubscriptions(""username"", //subscriptions//) +activate SubscriptionDataAccessService +SubscriptionDataAccessService -> Database: upload all subscriptions (//subscriptions//) for ""username"" +Database --> SubscriptionDataAccessService +deactivate Database +SubscriptionDataAccessService --> SubscriptionDataAccessService: int indicating status +deactivate SubscriptionDataAccessService +SubscriptionDataAccessService --> SubscriptionService: int indicating status +deactivate SubscriptionDataAccessService +SubscriptionService --> SubscriptionController: int indicating status +deactivate SubscriptionService +<-- SubscriptionController: ResponseEntity<String> with empty String for success \n\n-> ""HTTP status code"" \n-> ""JSON"" +deactivate SubscriptionController + +@enduml
\ No newline at end of file |