From 4104c46f3d8a4f49583afd37eb6aafec395ad652 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Ha=C3=9Fel?= Date: Tue, 29 Oct 2024 15:11:00 +0100 Subject: [PATCH] Subscribed class --- src/main/angular/src/app/api/Subscribed.ts | 46 +++++++++++++++++++ .../angular/src/app/api/User/UserPrivate.ts | 4 ++ .../angular/src/app/api/User/user.service.ts | 42 ++++++++++++----- src/main/angular/src/app/api/group/Group.ts | 12 +++-- .../{GroupService.ts => group.service.ts} | 10 ++-- .../pages/group/group/group.component.html | 16 +++---- .../app/pages/group/group/group.component.ts | 19 ++++---- .../pages/group/groups/groups.component.ts | 10 ++-- .../shared/group-list/group-list.component.ts | 2 +- .../app/pages/profile/profile.component.html | 2 +- .../app/pages/profile/profile.component.ts | 8 +--- .../java/de/ph87/tools/group/GroupDto.java | 9 +++- .../de/ph87/tools/user/UserPrivateDto.java | 9 +++- .../de/ph87/tools/user/UserPrivateMapper.java | 8 ++++ .../java/de/ph87/tools/user/UserService.java | 9 ++-- .../tools/web/SubscriptionInterceptor.java | 28 +++++++++++ 16 files changed, 173 insertions(+), 61 deletions(-) create mode 100644 src/main/angular/src/app/api/Subscribed.ts rename src/main/angular/src/app/api/group/{GroupService.ts => group.service.ts} (85%) create mode 100644 src/main/java/de/ph87/tools/web/SubscriptionInterceptor.java diff --git a/src/main/angular/src/app/api/Subscribed.ts b/src/main/angular/src/app/api/Subscribed.ts new file mode 100644 index 0000000..d77fdce --- /dev/null +++ b/src/main/angular/src/app/api/Subscribed.ts @@ -0,0 +1,46 @@ +import {Subscription} from "rxjs"; +import {Next} from "./common/types"; + +export class Subscribed { + + private subscription: Subscription | null = null; + + private _value: T | null = null; + + constructor( + private readonly same: (a: T, b: T) => boolean, + private readonly subscribe: (value: T, next: Next) => Subscription, + ) { + // - + } + + get value(): T | null { + return this._value; + } + + set value(value: T | null) { + if (!this.isSame(value)) { + if (this.subscription) { + this.subscription.unsubscribe(); + this.subscription = null; + } + if (value) { + this.subscription = this.subscribe(value, next => this.value = next); + } + } + this._value = value; + } + + private isSame(value: T | null) { + if (this._value === null) { + return value === null; + } else { + if (value === null) { + return false + } else { + return this.same(this._value, value); + } + } + } + +} diff --git a/src/main/angular/src/app/api/User/UserPrivate.ts b/src/main/angular/src/app/api/User/UserPrivate.ts index 290d3ec..8cd9546 100644 --- a/src/main/angular/src/app/api/User/UserPrivate.ts +++ b/src/main/angular/src/app/api/User/UserPrivate.ts @@ -30,4 +30,8 @@ export class UserPrivate { return UserPrivate.fromJson(json); } + static samePrivateUuid(a: UserPrivate, b: UserPrivate): boolean { + return a.privateUuid === b.privateUuid; + } + } diff --git a/src/main/angular/src/app/api/User/user.service.ts b/src/main/angular/src/app/api/User/user.service.ts index be49082..5496800 100644 --- a/src/main/angular/src/app/api/User/user.service.ts +++ b/src/main/angular/src/app/api/User/user.service.ts @@ -4,10 +4,11 @@ import {UserPrivate} from "./UserPrivate"; import {Next} from "../common/types"; import {UserCommon} from "./UserCommon"; import {UserPublic} from "./UserPublic"; -import {Router} from "@angular/router"; +import {EventType, Router} from "@angular/router"; import {BehaviorSubject, Subscription} from "rxjs"; import {Group} from "../group/Group"; import {StompService} from "@stomp/ng2-stompjs"; +import {Subscribed} from "../Subscribed"; @Injectable({ providedIn: 'root' @@ -16,6 +17,8 @@ export class UserService { private readonly subject: BehaviorSubject = new BehaviorSubject(null); + private subscription?: Subscription; + get user(): UserPrivate | null { return this.subject.value; } @@ -25,9 +28,13 @@ export class UserService { protected readonly api: ApiService, protected readonly stompService: StompService, ) { - this.stompService.connected$.subscribe(() => { - this.refresh(); + this.router.events.subscribe(e => { + if (e.type === EventType.NavigationEnd || e.type === EventType.NavigationSkipped) { + console.log(e); + this.refresh(); + } }); + this.stompService.connected$.subscribe(() => this.refresh()); } getCommonByUuid(uuid: string, next: Next): void { @@ -35,17 +42,11 @@ export class UserService { } delete(next?: Next) { - this.api.getNone(['User', 'delete'], _ => { - this.refresh(); - next && next(); - }); + this.api.getNone(['User', 'delete'], next); } changeName(name: string, next?: Next) { - this.api.postSingle(['User', 'changeName'], name, UserPrivate.fromJson, user => { - this.refresh(); - next && next(user); - }); + this.api.postSingle(['User', 'changeName'], name, UserPrivate.fromJson, next); } goto(user: UserPublic) { @@ -53,7 +54,7 @@ export class UserService { } refresh() { - this.api.getSingle(['User', 'whoAmI'], UserPrivate.fromJsonOrNull, user => this.subject.next(user)); + this.api.getSingle(['User', 'whoAmI'], UserPrivate.fromJsonOrNull, user => this.setUser(user)); } subscribe(next: Next): Subscription { @@ -64,4 +65,21 @@ export class UserService { return this.user?.publicUuid === group.owner.publicUuid; } + private setUser(user: UserPrivate | null) { + if (this.subject.value?.privateUuid !== user?.privateUuid) { + if (this.subscription) { + this.subscription.unsubscribe(); + this.subscription = undefined; + } + if (user) { + this.subscription = this.api.subscribe(["User", user.privateUuid], UserPrivate.fromJson, user => this.setUser(user)); + } + } + this.subject.next(user); + } + + newSubscriber(): Subscribed { + return new Subscribed(UserPrivate.samePrivateUuid, (user, next) => this.api.subscribe(['UserPrivate', user.privateUuid], UserPrivate.fromJson, next)); + } + } diff --git a/src/main/angular/src/app/api/group/Group.ts b/src/main/angular/src/app/api/group/Group.ts index 490cc0c..c6158af 100644 --- a/src/main/angular/src/app/api/group/Group.ts +++ b/src/main/angular/src/app/api/group/Group.ts @@ -31,10 +31,6 @@ export class Group { ); } - static compareCreated(a: Group, b: Group): number { - return a.created.getTime() - b.created.getTime(); - } - usersByNameOwnerFirst() { return this.users.sort((a, b) => this.compareOwnerFirstThenName(a, b)); } @@ -48,5 +44,13 @@ export class Group { return UserPublic.compareName(a, b); } + static sameUuid(a: Group, b: Group): boolean { + return a.uuid === b.uuid; + } + + static compareCreated(a: Group, b: Group): number { + return a.created.getTime() - b.created.getTime(); + } + } diff --git a/src/main/angular/src/app/api/group/GroupService.ts b/src/main/angular/src/app/api/group/group.service.ts similarity index 85% rename from src/main/angular/src/app/api/group/GroupService.ts rename to src/main/angular/src/app/api/group/group.service.ts index c5f6ee5..3fd2147 100644 --- a/src/main/angular/src/app/api/group/GroupService.ts +++ b/src/main/angular/src/app/api/group/group.service.ts @@ -5,6 +5,7 @@ import {Router} from "@angular/router"; import {UserService} from "../User/user.service"; import {validateBoolean} from "../common/validators"; import {Injectable} from "@angular/core"; +import {Subscribed} from "../Subscribed"; @Injectable({ providedIn: 'root' @@ -28,10 +29,7 @@ export class GroupService { } create(next: Next): void { - this.api.getSingle(['Group', 'create'], Group.fromJson, group => { - next(group); - this.userService.refresh(); - }); + this.api.getSingle(['Group', 'create'], Group.fromJson, next); } changeTitle(group: Group, title: string, next?: Next) { @@ -66,5 +64,9 @@ export class GroupService { this.router.navigate(['Group', group.uuid]); } + newSubscriber(): Subscribed { + return new Subscribed(Group.sameUuid, (group, next) => this.api.subscribe(['Group', group.uuid], Group.fromJson, next)); + } + } diff --git a/src/main/angular/src/app/pages/group/group/group.component.html b/src/main/angular/src/app/pages/group/group/group.component.html index 5ab3d41..4ca6bbc 100644 --- a/src/main/angular/src/app/pages/group/group/group.component.html +++ b/src/main/angular/src/app/pages/group/group/group.component.html @@ -1,34 +1,34 @@ - +

Nummern

- +
Erstellt{{ group.created | date:'yyyy-MM-dd HH:mm' }}{{ group.value.created | date:'yyyy-MM-dd HH:mm' }}
Titel - - + +
Passwort - - + +
Teilnehmer -
{{ user.name }}
+
{{ user.name }}
- +

Passwort

diff --git a/src/main/angular/src/app/pages/group/group/group.component.ts b/src/main/angular/src/app/pages/group/group/group.component.ts index 8d9b6fd..79181d3 100644 --- a/src/main/angular/src/app/pages/group/group/group.component.ts +++ b/src/main/angular/src/app/pages/group/group/group.component.ts @@ -3,9 +3,10 @@ import {DatePipe, NgForOf, NgIf} from "@angular/common"; import {FormsModule, ReactiveFormsModule} from "@angular/forms"; import {TextComponent} from "../../../shared/text/text.component"; import {ActivatedRoute, Router} from "@angular/router"; -import {GroupService} from "../../../api/group/GroupService"; +import {GroupService} from "../../../api/group/group.service"; import {UserService} from "../../../api/User/user.service"; import {Group} from "../../../api/group/Group"; +import {Subscribed} from "../../../api/Subscribed"; @Component({ selector: 'app-group', @@ -23,7 +24,7 @@ import {Group} from "../../../api/group/Group"; }) export class GroupComponent implements OnInit { - protected group: Group | null = null; + protected readonly group: Subscribed; protected uuid: string | null = null; @@ -37,7 +38,7 @@ export class GroupComponent implements OnInit { protected readonly groupService: GroupService, protected readonly userService: UserService, ) { - // - + this.group = this.groupService.newSubscriber(); } ngOnInit(): void { @@ -49,29 +50,25 @@ export class GroupComponent implements OnInit { this.groupService.canAccess(this.uuid, canAccess => { this.accessDenied = !canAccess; if (canAccess && this.uuid) { - this.groupService.get(this.uuid, group => this.setGroup(group)); + this.groupService.get(this.uuid, group => this.group.value = group); } }); } }); } - private setGroup(group: Group): void { - this.group = group; - } - protected join() { if (this.uuid) { - this.groupService.join(this.uuid, this.password, group => this.setGroup(group)); + this.groupService.join(this.uuid, this.password, group => this.group.value = group); } } protected changeTitle(group: Group, title: string) { - this.groupService.changeTitle(group, title, group => this.setGroup(group)); + this.groupService.changeTitle(group, title, group => this.group.value = group); } protected changePassword(group: Group, password: string) { - this.groupService.changePassword(group, password, group => this.setGroup(group)); + this.groupService.changePassword(group, password, group => this.group.value = group); } } diff --git a/src/main/angular/src/app/pages/group/groups/groups.component.ts b/src/main/angular/src/app/pages/group/groups/groups.component.ts index a891a69..947b690 100644 --- a/src/main/angular/src/app/pages/group/groups/groups.component.ts +++ b/src/main/angular/src/app/pages/group/groups/groups.component.ts @@ -1,8 +1,8 @@ -import {Component, OnInit} from '@angular/core'; +import {Component} from '@angular/core'; import {GroupListComponent} from "../shared/group-list/group-list.component"; import {UserService} from "../../../api/User/user.service"; import {NgIf} from "@angular/common"; -import {GroupService} from "../../../api/group/GroupService"; +import {GroupService} from "../../../api/group/group.service"; @Component({ selector: 'app-groups', @@ -14,7 +14,7 @@ import {GroupService} from "../../../api/group/GroupService"; templateUrl: './groups.component.html', styleUrl: './groups.component.less' }) -export class GroupsComponent implements OnInit { +export class GroupsComponent { constructor( protected readonly userService: UserService, @@ -23,10 +23,6 @@ export class GroupsComponent implements OnInit { // - } - ngOnInit(): void { - this.userService.refresh(); - } - create(): void { this.groupService.create(group => this.groupService.goto(group)); } diff --git a/src/main/angular/src/app/pages/group/shared/group-list/group-list.component.ts b/src/main/angular/src/app/pages/group/shared/group-list/group-list.component.ts index 1789628..bcda4ab 100644 --- a/src/main/angular/src/app/pages/group/shared/group-list/group-list.component.ts +++ b/src/main/angular/src/app/pages/group/shared/group-list/group-list.component.ts @@ -2,7 +2,7 @@ import {Component, Input} from '@angular/core'; import {UserService} from "../../../../api/User/user.service"; import {Group} from "../../../../api/group/Group"; import {DatePipe, NgForOf} from "@angular/common"; -import {GroupService} from "../../../../api/group/GroupService"; +import {GroupService} from "../../../../api/group/group.service"; @Component({ selector: 'app-group-list', diff --git a/src/main/angular/src/app/pages/profile/profile.component.html b/src/main/angular/src/app/pages/profile/profile.component.html index fe949f6..ac348cb 100644 --- a/src/main/angular/src/app/pages/profile/profile.component.html +++ b/src/main/angular/src/app/pages/profile/profile.component.html @@ -1,5 +1,5 @@

Profil

- +
diff --git a/src/main/angular/src/app/pages/profile/profile.component.ts b/src/main/angular/src/app/pages/profile/profile.component.ts index c657cca..1a8ed3d 100644 --- a/src/main/angular/src/app/pages/profile/profile.component.ts +++ b/src/main/angular/src/app/pages/profile/profile.component.ts @@ -1,4 +1,4 @@ -import {Component, OnInit} from '@angular/core'; +import {Component} from '@angular/core'; import {NgForOf, NgIf} from "@angular/common"; import {UserService} from "../../api/User/user.service"; import {FormsModule} from "@angular/forms"; @@ -18,7 +18,7 @@ import {GroupListComponent} from "../group/shared/group-list/group-list.componen templateUrl: './profile.component.html', styleUrl: './profile.component.less' }) -export class ProfileComponent implements OnInit { +export class ProfileComponent { constructor( protected readonly userService: UserService, @@ -26,8 +26,4 @@ export class ProfileComponent implements OnInit { // - } - ngOnInit(): void { - this.userService.refresh(); - } - } diff --git a/src/main/java/de/ph87/tools/group/GroupDto.java b/src/main/java/de/ph87/tools/group/GroupDto.java index 49222e4..484f136 100644 --- a/src/main/java/de/ph87/tools/group/GroupDto.java +++ b/src/main/java/de/ph87/tools/group/GroupDto.java @@ -1,17 +1,19 @@ package de.ph87.tools.group; import de.ph87.tools.user.UserPublicDto; +import de.ph87.tools.web.IWebSocketMessage; import lombok.Getter; import lombok.NonNull; import lombok.ToString; import java.time.ZonedDateTime; +import java.util.List; import java.util.Set; import java.util.stream.Collectors; @Getter @ToString -public class GroupDto { +public class GroupDto implements IWebSocketMessage { @NonNull public final String uuid; @@ -43,4 +45,9 @@ public class GroupDto { this.initial = group.isInitial(); } + @Override + public List getWebsocketTopic() { + return List.of("Group", uuid); + } + } diff --git a/src/main/java/de/ph87/tools/user/UserPrivateDto.java b/src/main/java/de/ph87/tools/user/UserPrivateDto.java index b1ba4a8..e34b6f0 100644 --- a/src/main/java/de/ph87/tools/user/UserPrivateDto.java +++ b/src/main/java/de/ph87/tools/user/UserPrivateDto.java @@ -1,16 +1,18 @@ package de.ph87.tools.user; import de.ph87.tools.group.GroupDto; +import de.ph87.tools.web.IWebSocketMessage; import lombok.Getter; import lombok.NonNull; import lombok.ToString; import java.time.ZonedDateTime; +import java.util.List; import java.util.Set; @Getter @ToString -public class UserPrivateDto { +public class UserPrivateDto implements IWebSocketMessage { @NonNull public final String publicUuid; @@ -35,4 +37,9 @@ public class UserPrivateDto { this.groups = groups; } + @Override + public List getWebsocketTopic() { + return List.of("User", privateUuid); + } + } diff --git a/src/main/java/de/ph87/tools/user/UserPrivateMapper.java b/src/main/java/de/ph87/tools/user/UserPrivateMapper.java index dcabd8a..4d96fa8 100644 --- a/src/main/java/de/ph87/tools/user/UserPrivateMapper.java +++ b/src/main/java/de/ph87/tools/user/UserPrivateMapper.java @@ -6,6 +6,7 @@ import jakarta.annotation.Nullable; import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -21,6 +22,8 @@ public class UserPrivateMapper { private final GroupMapper groupMapper; + private final ApplicationEventPublisher applicationEventPublisher; + @NonNull public UserPrivateDto toPrivateDto(@NonNull final User user) { final Set groups = groupMapper.findAllByUser(user); @@ -35,4 +38,9 @@ public class UserPrivateMapper { return toPrivateDto(user); } + public void publish(@NonNull final User user) { + final UserPrivateDto dto = toPrivateDto(user); + applicationEventPublisher.publishEvent(dto); + } + } diff --git a/src/main/java/de/ph87/tools/user/UserService.java b/src/main/java/de/ph87/tools/user/UserService.java index 0928c75..b8d3592 100644 --- a/src/main/java/de/ph87/tools/user/UserService.java +++ b/src/main/java/de/ph87/tools/user/UserService.java @@ -57,7 +57,7 @@ public class UserService { return modify(privateUuid, user -> { user.setName(name); log.info("User name changed: user={}", user); - }, true); + }); } @NonNull @@ -83,12 +83,11 @@ public class UserService { } @NonNull - private UserPrivateDto modify(@NonNull final String privateUuid, @NonNull Consumer modifier, final boolean doPublish) { + private UserPrivateDto modify(@NonNull final String privateUuid, @NonNull Consumer modifier) { final User user = getByPrivateUuidOrThrow(privateUuid); modifier.accept(user); - if (doPublish) { - userPublicMapper.publish(user); - } + userPublicMapper.publish(user); + userPrivateMapper.publish(user); return userPrivateMapper.toPrivateDto(user); } diff --git a/src/main/java/de/ph87/tools/web/SubscriptionInterceptor.java b/src/main/java/de/ph87/tools/web/SubscriptionInterceptor.java new file mode 100644 index 0000000..be21132 --- /dev/null +++ b/src/main/java/de/ph87/tools/web/SubscriptionInterceptor.java @@ -0,0 +1,28 @@ +package de.ph87.tools.web; + +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.simp.stomp.StompCommand; +import org.springframework.messaging.simp.stomp.StompHeaderAccessor; +import org.springframework.messaging.support.ChannelInterceptor; +import org.springframework.messaging.support.MessageHeaderAccessor; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class SubscriptionInterceptor implements ChannelInterceptor { + + @Override + public Message preSend(@NonNull final Message message, @NonNull final MessageChannel channel) { + final StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class); + if (accessor != null && StompCommand.SUBSCRIBE.equals(accessor.getCommand())) { + final String sessionId = accessor.getSessionId(); + final String destination = accessor.getDestination(); + log.info("User subscribed with sessionId: {} to destination: {}", sessionId, destination); + } + return message; + } + +}