Subscribed class
This commit is contained in:
parent
a4c0969ce6
commit
4104c46f3d
46
src/main/angular/src/app/api/Subscribed.ts
Normal file
46
src/main/angular/src/app/api/Subscribed.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import {Subscription} from "rxjs";
|
||||
import {Next} from "./common/types";
|
||||
|
||||
export class Subscribed<T> {
|
||||
|
||||
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<T>) => 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -30,4 +30,8 @@ export class UserPrivate {
|
||||
return UserPrivate.fromJson(json);
|
||||
}
|
||||
|
||||
static samePrivateUuid(a: UserPrivate, b: UserPrivate): boolean {
|
||||
return a.privateUuid === b.privateUuid;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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<UserPrivate | null> = new BehaviorSubject<UserPrivate | null>(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.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<UserCommon>): void {
|
||||
@ -35,17 +42,11 @@ export class UserService {
|
||||
}
|
||||
|
||||
delete(next?: Next<void>) {
|
||||
this.api.getNone(['User', 'delete'], _ => {
|
||||
this.refresh();
|
||||
next && next();
|
||||
});
|
||||
this.api.getNone(['User', 'delete'], next);
|
||||
}
|
||||
|
||||
changeName(name: string, next?: Next<UserPrivate>) {
|
||||
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<UserPrivate | null>): 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<UserPrivate> {
|
||||
return new Subscribed<UserPrivate>(UserPrivate.samePrivateUuid, (user, next) => this.api.subscribe(['UserPrivate', user.privateUuid], UserPrivate.fromJson, next));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -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<Group>): 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<Group>) {
|
||||
@ -66,5 +64,9 @@ export class GroupService {
|
||||
this.router.navigate(['Group', group.uuid]);
|
||||
}
|
||||
|
||||
newSubscriber(): Subscribed<Group> {
|
||||
return new Subscribed<Group>(Group.sameUuid, (group, next) => this.api.subscribe(['Group', group.uuid], Group.fromJson, next));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,34 +1,34 @@
|
||||
<ng-container *ngIf="group !== null">
|
||||
<ng-container *ngIf="group.value">
|
||||
<h1>Nummern</h1>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Erstellt</th>
|
||||
<td>{{ group.created | date:'yyyy-MM-dd HH:mm' }}</td>
|
||||
<td>{{ group.value.created | date:'yyyy-MM-dd HH:mm' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Titel</th>
|
||||
<td>
|
||||
<app-text [initial]="group.title" [editable]="userService.iOwn(group)" (onChange)="changeTitle(group, $event)"></app-text>
|
||||
<ng-container *ngIf="!userService.iOwn(group)"></ng-container>
|
||||
<app-text [initial]="group.value.title" [editable]="userService.iOwn(group.value)" (onChange)="changeTitle(group.value, $event)"></app-text>
|
||||
<ng-container *ngIf="!userService.iOwn(group.value)"></ng-container>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Passwort</th>
|
||||
<td>
|
||||
<app-text [initial]="group.password" [editable]="userService.iOwn(group)" (onChange)="changePassword(group, $event)"></app-text>
|
||||
<ng-container *ngIf="!userService.iOwn(group)"></ng-container>
|
||||
<app-text [initial]="group.value.password" [editable]="userService.iOwn(group.value)" (onChange)="changePassword(group.value, $event)"></app-text>
|
||||
<ng-container *ngIf="!userService.iOwn(group.value)"></ng-container>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Teilnehmer</th>
|
||||
<td>
|
||||
<div class="user" [class.user_owner]="group.isOwner(user)" *ngFor="let user of group.usersByNameOwnerFirst()" (click)="userService.goto(user)">{{ user.name }}</div>
|
||||
<div class="user" [class.user_owner]="group.value.isOwner(user)" *ngFor="let user of group.value.usersByNameOwnerFirst()" (click)="userService.goto(user)">{{ user.name }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="uuid && !group && accessDenied">
|
||||
<ng-container *ngIf="uuid && !group.value && accessDenied">
|
||||
<h1>Passwort</h1>
|
||||
<input type="text" [(ngModel)]="password" (keydown.enter)="join()">
|
||||
<button (click)="join()">Beitreten</button>
|
||||
|
||||
@ -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<Group>;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<ng-container *ngIf="userService.user !== null">
|
||||
<h1>Profil</h1>
|
||||
<app-text [initial]="userService.user.name" (onChange)="userService.changeName($event)"></app-text>
|
||||
<app-text [initial]="userService.user.name" [editable]="true" (onChange)="userService.changeName($event)"></app-text>
|
||||
<app-group-list [groups]="userService.user.groups"></app-group-list>
|
||||
</ng-container>
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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<Object> getWebsocketTopic() {
|
||||
return List.of("Group", uuid);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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<Object> getWebsocketTopic() {
|
||||
return List.of("User", privateUuid);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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<GroupDto> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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<User> modifier, final boolean doPublish) {
|
||||
private UserPrivateDto modify(@NonNull final String privateUuid, @NonNull Consumer<User> modifier) {
|
||||
final User user = getByPrivateUuidOrThrow(privateUuid);
|
||||
modifier.accept(user);
|
||||
if (doPublish) {
|
||||
userPublicMapper.publish(user);
|
||||
}
|
||||
userPrivateMapper.publish(user);
|
||||
return userPrivateMapper.toPrivateDto(user);
|
||||
}
|
||||
|
||||
|
||||
28
src/main/java/de/ph87/tools/web/SubscriptionInterceptor.java
Normal file
28
src/main/java/de/ph87/tools/web/SubscriptionInterceptor.java
Normal file
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user