From d9b84c580290670733e2b2cd25110afef0d2a7b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Ha=C3=9Fel?= Date: Mon, 4 Nov 2024 15:34:18 +0100 Subject: [PATCH] Numbers, UserPush, UserPublic cleanup, PasswordComponent --- .../angular/src/app/api/User/UserPrivate.ts | 5 +- .../angular/src/app/api/User/user.service.ts | 46 ++++--- .../src/app/api/group/group.service.ts | 12 +- .../src/app/api/tools/Numbers/Numbers.ts | 22 ---- .../api/tools/Numbers/NumbersPrivateDto.ts | 24 ++++ .../app/api/tools/Numbers/numbers.service.ts | 23 +++- src/main/angular/src/app/app.component.html | 2 +- src/main/angular/src/app/app.component.ts | 9 +- src/main/angular/src/app/app.routes.ts | 3 + .../pages/group/group/group.component.html | 9 +- .../app/pages/group/group/group.component.ts | 40 +++--- .../pages/group/groups/groups.component.html | 2 +- .../pages/group/groups/groups.component.ts | 22 +++- .../group-list/group-list.component.html | 2 +- .../shared/group-list/group-list.component.ts | 2 +- .../app/pages/profile/profile.component.html | 21 ++-- .../app/pages/profile/profile.component.less | 10 ++ .../app/pages/profile/profile.component.ts | 23 +++- .../tools/numbers/numbers.component.html | 20 +++ .../tools/numbers/numbers.component.less | 21 ++++ .../pages/tools/numbers/numbers.component.ts | 67 ++++++++++ .../src/app/pages/user/user.component.html | 4 +- .../src/app/pages/user/user.component.ts | 14 ++- .../shared/password/password.component.html | 5 + .../shared/password/password.component.less | 0 .../app/shared/password/password.component.ts | 29 +++++ .../angular/src/app/shared/relative.pipe.ts | 33 +++++ .../src/app/shared/text/text.component.less | 4 + src/main/java/de/ph87/tools/group/Access.java | 13 ++ src/main/java/de/ph87/tools/group/Group.java | 1 + .../ph87/tools/group/GroupAccessService.java | 53 ++++++++ .../de/ph87/tools/group/GroupController.java | 22 +++- .../java/de/ph87/tools/group/GroupDto.java | 14 +-- .../java/de/ph87/tools/group/GroupMapper.java | 57 --------- .../ph87/tools/group/GroupOfUserService.java | 52 -------- .../de/ph87/tools/group/GroupRepository.java | 3 - .../de/ph87/tools/group/GroupService.java | 92 +++++++++----- .../de/ph87/tools/tools/numbers/Numbers.java | 35 +++++- .../tools/numbers/NumbersController.java | 31 ++++- ...NumbersDto.java => NumbersPrivateDto.java} | 17 ++- .../tools/numbers/NumbersRepository.java | 7 ++ .../tools/tools/numbers/NumbersService.java | 116 ++++++++++++++++++ src/main/java/de/ph87/tools/user/User.java | 23 +--- .../de/ph87/tools/user/UserController.java | 14 +-- .../de/ph87/tools/user/UserPrivateDto.java | 26 ++-- .../de/ph87/tools/user/UserPrivateMapper.java | 48 -------- .../de/ph87/tools/user/UserPublicDto.java | 11 +- .../de/ph87/tools/user/UserPublicMapper.java | 30 ----- .../java/de/ph87/tools/user/UserService.java | 54 ++++---- .../ph87/tools/user/push/UserPushMessage.java | 22 ++++ .../ph87/tools/user/push/UserPushService.java | 25 ++++ .../de/ph87/tools/user/reference/IUser.java | 31 +++++ .../tools/user/reference/UserReference.java | 35 ++++++ .../reference/UserReferenceRepository.java | 7 ++ .../user/reference/UserReferenceService.java | 23 ++++ .../de/ph87/tools/web/WebSocketService.java | 9 +- 56 files changed, 906 insertions(+), 439 deletions(-) delete mode 100644 src/main/angular/src/app/api/tools/Numbers/Numbers.ts create mode 100644 src/main/angular/src/app/api/tools/Numbers/NumbersPrivateDto.ts create mode 100644 src/main/angular/src/app/pages/tools/numbers/numbers.component.html create mode 100644 src/main/angular/src/app/pages/tools/numbers/numbers.component.less create mode 100644 src/main/angular/src/app/pages/tools/numbers/numbers.component.ts create mode 100644 src/main/angular/src/app/shared/password/password.component.html create mode 100644 src/main/angular/src/app/shared/password/password.component.less create mode 100644 src/main/angular/src/app/shared/password/password.component.ts create mode 100644 src/main/angular/src/app/shared/relative.pipe.ts create mode 100644 src/main/java/de/ph87/tools/group/Access.java create mode 100644 src/main/java/de/ph87/tools/group/GroupAccessService.java delete mode 100644 src/main/java/de/ph87/tools/group/GroupMapper.java delete mode 100644 src/main/java/de/ph87/tools/group/GroupOfUserService.java rename src/main/java/de/ph87/tools/tools/numbers/{NumbersDto.java => NumbersPrivateDto.java} (53%) create mode 100644 src/main/java/de/ph87/tools/tools/numbers/NumbersRepository.java create mode 100644 src/main/java/de/ph87/tools/tools/numbers/NumbersService.java delete mode 100644 src/main/java/de/ph87/tools/user/UserPrivateMapper.java delete mode 100644 src/main/java/de/ph87/tools/user/UserPublicMapper.java create mode 100644 src/main/java/de/ph87/tools/user/push/UserPushMessage.java create mode 100644 src/main/java/de/ph87/tools/user/push/UserPushService.java create mode 100644 src/main/java/de/ph87/tools/user/reference/IUser.java create mode 100644 src/main/java/de/ph87/tools/user/reference/UserReference.java create mode 100644 src/main/java/de/ph87/tools/user/reference/UserReferenceRepository.java create mode 100644 src/main/java/de/ph87/tools/user/reference/UserReferenceService.java diff --git a/src/main/angular/src/app/api/User/UserPrivate.ts b/src/main/angular/src/app/api/User/UserPrivate.ts index d4f3721..507cd0a 100644 --- a/src/main/angular/src/app/api/User/UserPrivate.ts +++ b/src/main/angular/src/app/api/User/UserPrivate.ts @@ -1,5 +1,4 @@ -import {validateBoolean, validateDate, validateList, validateString} from "../common/validators"; -import {Group} from "../group/Group"; +import {validateBoolean, validateDate, validateString} from "../common/validators"; export class UserPrivate { @@ -9,7 +8,6 @@ export class UserPrivate { readonly created: Date, readonly name: string, readonly password: boolean, - readonly groups: Group[], ) { // - } @@ -21,7 +19,6 @@ export class UserPrivate { validateDate(json['created']), validateString(json['name']), validateBoolean(json['password']), - validateList(json['groups'], Group.fromJson), ); } 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 ad5c174..134f995 100644 --- a/src/main/angular/src/app/api/User/user.service.ts +++ b/src/main/angular/src/app/api/User/user.service.ts @@ -2,13 +2,23 @@ import {Injectable} from '@angular/core'; import {ApiService} from "../common/api.service"; import {UserPrivate} from "./UserPrivate"; import {Next} from "../common/types"; -import {UserCommon} from "./UserCommon"; import {UserPublic} from "./UserPublic"; import {EventType, Router} from "@angular/router"; -import {BehaviorSubject, Subscription} from "rxjs"; +import {BehaviorSubject, filter, Subject, Subscription} from "rxjs"; import {Group} from "../group/Group"; import {StompService} from "@stomp/ng2-stompjs"; -import {Subscribed} from "../Subscribed"; +import {NumbersPrivateDto} from "../tools/Numbers/NumbersPrivateDto"; + +function userPushMessageFromJson(json: any): object { + const type = json['_type_']; + switch (type) { + case 'NumbersPrivateDto': + return NumbersPrivateDto.fromJson(json['payload']); + case 'UserPrivateDto': + return UserPrivate.fromJson(json['payload']); + } + throw new Error("Not implemented UserPushMessage._type_ = " + type); +} @Injectable({ providedIn: 'root' @@ -17,7 +27,9 @@ export class UserService { private readonly subject: BehaviorSubject = new BehaviorSubject(null); - private subscription?: Subscription; + private readonly pushSubject: Subject = new Subject(); + + private userSubscriptions: Subscription[] = []; get user(): UserPrivate | null { return this.subject.value; @@ -30,15 +42,14 @@ export class UserService { ) { 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 { - this.api.postSingle(['User', 'getCommonByUuid'], uuid, UserCommon.fromJson, next); + getByPublicUuid(publicUuid: any, next: Next) { + this.api.postSingle(['User', 'getByPublicUuid'], publicUuid, UserPublic.fromJson, next); } delete(next?: Next) { @@ -54,7 +65,7 @@ export class UserService { } goto(user: UserPublic) { - this.router.navigate(['/User', user.publicUuid]); + this.router.navigate(['User', user.publicUuid]); } refresh() { @@ -65,24 +76,25 @@ export class UserService { return this.subject.subscribe(next); } + subscribePush(predicate: (m: any) => boolean, next: Next): Subscription { + return this.pushSubject + .pipe(filter(predicate)) + .subscribe(next); + } + iOwn(group: Group): boolean { 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 === null || this.subject.value?.privateUuid !== user.privateUuid) { + this.userSubscriptions.forEach(subscription => subscription.unsubscribe()); + this.userSubscriptions.length = 0; if (user) { - this.subscription = this.api.subscribe(["User", user.privateUuid], UserPrivate.fromJson, user => this.setUser(user)); + this.userSubscriptions.push(this.api.subscribe(["User", user.privateUuid], userPushMessageFromJson, message => this.pushSubject.next(message))); } } 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.service.ts b/src/main/angular/src/app/api/group/group.service.ts index 3fd2147..586271b 100644 --- a/src/main/angular/src/app/api/group/group.service.ts +++ b/src/main/angular/src/app/api/group/group.service.ts @@ -28,6 +28,14 @@ export class GroupService { this.api.postSingle(['Group', 'get'], uuid, Group.fromJson, next); } + findAllCommon(uuid: string, next: Next): void { + this.api.postList(['Group', 'findAllCommon'], uuid, Group.fromJson, next); + } + + findAllJoined(next: Next) { + this.api.getList(['Group', 'findAllJoined'], Group.fromJson, next); + } + create(next: Next): void { this.api.getSingle(['Group', 'create'], Group.fromJson, next); } @@ -60,8 +68,8 @@ export class GroupService { this.api.postNone(['Group', 'leave'], group, next); } - goto(group: Group) { - this.router.navigate(['Group', group.uuid]); + goto(uuid: string) { + this.router.navigate(['Group', uuid]); } newSubscriber(): Subscribed { diff --git a/src/main/angular/src/app/api/tools/Numbers/Numbers.ts b/src/main/angular/src/app/api/tools/Numbers/Numbers.ts deleted file mode 100644 index 2a882c4..0000000 --- a/src/main/angular/src/app/api/tools/Numbers/Numbers.ts +++ /dev/null @@ -1,22 +0,0 @@ -import {validateString} from "../../common/validators"; -import {Group} from "../../group/Group"; - -export class Numbers { - - constructor( - readonly uuid: string, - readonly name: string, - readonly group: Group, - ) { - // - - } - - static fromJson(json: any): Numbers { - return new Numbers( - validateString(json['uuid']), - validateString(json['name']), - Group.fromJson(json['group']), - ); - } - -} diff --git a/src/main/angular/src/app/api/tools/Numbers/NumbersPrivateDto.ts b/src/main/angular/src/app/api/tools/Numbers/NumbersPrivateDto.ts new file mode 100644 index 0000000..d2d2100 --- /dev/null +++ b/src/main/angular/src/app/api/tools/Numbers/NumbersPrivateDto.ts @@ -0,0 +1,24 @@ +import {Group} from "../../group/Group"; +import {validateDate, validateNumber, validateString} from "../../common/validators"; + +export class NumbersPrivateDto { + + constructor( + readonly uuid: string, + readonly group: Group, + readonly date: Date, + readonly number: number, + ) { + // - + } + + static fromJson(json: any): NumbersPrivateDto { + return new NumbersPrivateDto( + validateString(json['uuid']), + Group.fromJson(json['group']), + validateDate(json['date']), + validateNumber(json['number']), + ); + } + +} diff --git a/src/main/angular/src/app/api/tools/Numbers/numbers.service.ts b/src/main/angular/src/app/api/tools/Numbers/numbers.service.ts index 3863d7e..358f0d2 100644 --- a/src/main/angular/src/app/api/tools/Numbers/numbers.service.ts +++ b/src/main/angular/src/app/api/tools/Numbers/numbers.service.ts @@ -2,6 +2,9 @@ import {Injectable} from '@angular/core'; import {ApiService} from "../../common/api.service"; import {Router} from "@angular/router"; import {UserService} from "../../User/user.service"; +import {Next} from "../../common/types"; +import {NumbersPrivateDto} from "./NumbersPrivateDto"; +import {validateBoolean, validateString} from "../../common/validators"; @Injectable({ providedIn: 'root' @@ -13,7 +16,25 @@ export class NumbersService { protected readonly router: Router, protected readonly userService: UserService ) { - // - + this.userService.subscribePush(m => m instanceof NumbersPrivateDto, message => { + this.router.navigate(['Numbers', message.uuid]) + }); + } + + create(groupUuid: string) { + this.api.getNone(['Numbers', 'create', groupUuid]); + } + + byUuid(uuid: string, next: Next) { + this.api.postSingle(['Numbers', 'byUuid'], uuid, NumbersPrivateDto.fromJson, next); + } + + canAccess(uuid: string, next: Next) { + this.api.postSingle(['Numbers', 'canAccess'], uuid, validateBoolean, next); + } + + getGroupUuid(uuid: string | null, next: Next) { + this.api.postSingle(['Numbers', 'getGroupUuid'], uuid, validateString, next); } } diff --git a/src/main/angular/src/app/app.component.html b/src/main/angular/src/app/app.component.html index 40eb67e..03e77e6 100644 --- a/src/main/angular/src/app/app.component.html +++ b/src/main/angular/src/app/app.component.html @@ -1,6 +1,6 @@
- +
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 947b690..6562c95 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,10 @@ -import {Component} from '@angular/core'; +import {Component, OnDestroy, OnInit} 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/group.service"; +import {Group} from "../../../api/group/Group"; +import {Subscription} from "rxjs"; @Component({ selector: 'app-groups', @@ -14,7 +16,11 @@ import {GroupService} from "../../../api/group/group.service"; templateUrl: './groups.component.html', styleUrl: './groups.component.less' }) -export class GroupsComponent { +export class GroupsComponent implements OnInit, OnDestroy { + + private readonly subscriptions: Subscription[] = []; + + protected groups: Group[] = []; constructor( protected readonly userService: UserService, @@ -23,8 +29,18 @@ export class GroupsComponent { // - } + ngOnInit(): void { + this.subscriptions.push(this.userService.subscribe(_ => { + this.groupService.findAllJoined(groups => this.groups = groups); + })); + } + + ngOnDestroy(): void { + this.subscriptions.forEach(subscription => subscription.unsubscribe()); + } + create(): void { - this.groupService.create(group => this.groupService.goto(group)); + this.groupService.create(group => this.groupService.goto(group.uuid)); } } diff --git a/src/main/angular/src/app/pages/group/shared/group-list/group-list.component.html b/src/main/angular/src/app/pages/group/shared/group-list/group-list.component.html index 394b8ed..8c5a633 100644 --- a/src/main/angular/src/app/pages/group/shared/group-list/group-list.component.html +++ b/src/main/angular/src/app/pages/group/shared/group-list/group-list.component.html @@ -3,7 +3,7 @@ Titel Teilnehmer - + {{ group.title }} {{ group.users.length }} 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 bcda4ab..f3269ec 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 @@ -30,7 +30,7 @@ export class GroupListComponent { } protected create() { - this.groupService.create(group => this.groupService.goto(group)); + this.groupService.create(group => this.groupService.goto(group.uuid)); } protected sorted(): Group[] { 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 c65ad9e..3d84895 100644 --- a/src/main/angular/src/app/pages/profile/profile.component.html +++ b/src/main/angular/src/app/pages/profile/profile.component.html @@ -8,13 +8,15 @@ - + + + Mindestens {{ USER_NAME_MIN_LENGTH }} Zeichen. Keine Leerzeichen. Buchstaben oder Zahlen müssen enthalten sein. -   +   @@ -33,9 +35,6 @@ - - Mindestens {{ USER_PASSWORD_MIN_LENGTH }} Zeichen. Nicht nur Zahlen. Nicht nur Buchstaben. - @@ -44,17 +43,19 @@ - -   + + + + Mindestens {{ USER_PASSWORD_MIN_LENGTH }} Zeichen. Nicht nur Zahlen. Nicht nur Buchstaben. -   +   - - + + diff --git a/src/main/angular/src/app/pages/profile/profile.component.less b/src/main/angular/src/app/pages/profile/profile.component.less index 830a9c4..5ffa911 100644 --- a/src/main/angular/src/app/pages/profile/profile.component.less +++ b/src/main/angular/src/app/pages/profile/profile.component.less @@ -1,3 +1,7 @@ +table { + width: 100%; +} + th { text-align: left; } @@ -12,6 +16,7 @@ th { input { -webkit-text-security: disc; + width: 100%; } .passwordInvalid { @@ -21,3 +26,8 @@ input { .passwordValid { background-color: lightgreen; } + +.hint { + font-size: 80%; + color: gray; +} 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 4428599..65afe09 100644 --- a/src/main/angular/src/app/pages/profile/profile.component.ts +++ b/src/main/angular/src/app/pages/profile/profile.component.ts @@ -1,9 +1,12 @@ -import {Component, ElementRef, ViewChild} from '@angular/core'; +import {Component, ElementRef, OnDestroy, OnInit, ViewChild} from '@angular/core'; import {NgForOf, NgIf} from "@angular/common"; import {UserService} from "../../api/User/user.service"; import {FormsModule} from "@angular/forms"; import {TextComponent} from "../../shared/text/text.component"; import {GroupListComponent} from "../group/shared/group-list/group-list.component"; +import {Group} from "../../api/group/Group"; +import {Subscription} from "rxjs"; +import {GroupService} from "../../api/group/group.service"; const USER_NAME_MIN_LENGTH = 2; @@ -22,27 +25,41 @@ const USER_PASSWORD_MIN_LENGTH = 10; templateUrl: './profile.component.html', styleUrl: './profile.component.less' }) -export class ProfileComponent { +export class ProfileComponent implements OnInit, OnDestroy { protected readonly USER_NAME_MIN_LENGTH = USER_NAME_MIN_LENGTH; protected readonly USER_PASSWORD_MIN_LENGTH = USER_PASSWORD_MIN_LENGTH; + private readonly subscriptions: Subscription[] = []; + protected password0: string = ""; protected password1: string = ""; + protected groups: Group[] = []; + @ViewChild('p1') p1!: ElementRef; constructor( protected readonly userService: UserService, + protected readonly groupService: GroupService, ) { // - } + ngOnInit(): void { + this.subscriptions.push(this.userService.subscribe(_ => { + this.groupService.findAllJoined(groups => this.groups = groups); + })); + } + + ngOnDestroy(): void { + this.subscriptions.forEach(subscription => subscription.unsubscribe()); + } + protected nameValidator(name: string): boolean { - console.log(name); return name.length >= USER_NAME_MIN_LENGTH && !/\s+|^[^a-zA-Z0-9]+$/.test(name); } diff --git a/src/main/angular/src/app/pages/tools/numbers/numbers.component.html b/src/main/angular/src/app/pages/tools/numbers/numbers.component.html new file mode 100644 index 0000000..190fb1e --- /dev/null +++ b/src/main/angular/src/app/pages/tools/numbers/numbers.component.html @@ -0,0 +1,20 @@ +
+ + + +
+ {{ numbers.date | relative:now }} +
+ +
+ {{ numbers.number }} +
+ +
diff --git a/src/main/angular/src/app/pages/tools/numbers/numbers.component.less b/src/main/angular/src/app/pages/tools/numbers/numbers.component.less new file mode 100644 index 0000000..78c943f --- /dev/null +++ b/src/main/angular/src/app/pages/tools/numbers/numbers.component.less @@ -0,0 +1,21 @@ +.huge { + font-size: 50vmin; + white-space: nowrap; + text-align: center; +} + +.date { + text-align: center; +} + +.numbers { + color: green; +} + +.date_middle { + color: orange; +} + +.date_old { + color: red; +} diff --git a/src/main/angular/src/app/pages/tools/numbers/numbers.component.ts b/src/main/angular/src/app/pages/tools/numbers/numbers.component.ts new file mode 100644 index 0000000..769efb4 --- /dev/null +++ b/src/main/angular/src/app/pages/tools/numbers/numbers.component.ts @@ -0,0 +1,67 @@ +import {Component, OnDestroy, OnInit} from '@angular/core'; +import {NumbersPrivateDto} from "../../../api/tools/Numbers/NumbersPrivateDto"; +import {ActivatedRoute} from "@angular/router"; +import {NumbersService} from "../../../api/tools/Numbers/numbers.service"; +import {NgIf} from "@angular/common"; +import {FormsModule} from "@angular/forms"; +import {PasswordComponent} from "../../../shared/password/password.component"; +import {GroupService} from "../../../api/group/group.service"; +import {UserService} from "../../../api/User/user.service"; +import {RelativePipe} from "../../../shared/relative.pipe"; +import {Subscription, timer} from "rxjs"; + +@Component({ + selector: 'app-numbers', + standalone: true, + imports: [ + NgIf, + FormsModule, + PasswordComponent, + RelativePipe + ], + templateUrl: './numbers.component.html', + styleUrl: './numbers.component.less' +}) +export class NumbersComponent implements OnInit, OnDestroy { + + protected numbers: NumbersPrivateDto | null = null; + + protected now: Date = new Date(); + + private timer?: Subscription; + + constructor( + protected readonly activatedRoute: ActivatedRoute, + protected readonly numbersService: NumbersService, + protected readonly userService: UserService, + protected readonly groupService: GroupService, + ) { + // - + } + + ngOnInit(): void { + this.timer = timer(1000, 1000).subscribe(() => this.now = new Date()); + this.activatedRoute.params.subscribe(params => { + const uuid = params['uuid']; + if (uuid) { + this.numbersService.canAccess(uuid, granted => { + if (granted) { + this.numbersService.byUuid(uuid, numbers => this.numbers = numbers); + } else { + this.numbersService.getGroupUuid(uuid, groupUuid => this.groupService.goto(groupUuid)); + } + }); + } else { + this.numbers = null; + } + }); + } + + ngOnDestroy(): void { + if (this.timer) { + this.timer.unsubscribe(); + this.timer = undefined; + } + } + +} diff --git a/src/main/angular/src/app/pages/user/user.component.html b/src/main/angular/src/app/pages/user/user.component.html index 32b7045..23f21c6 100644 --- a/src/main/angular/src/app/pages/user/user.component.html +++ b/src/main/angular/src/app/pages/user/user.component.html @@ -1,6 +1,6 @@

Benutzer: {{ user.name }}

-

Gemeinsame Sitzungen

- +

Gemeinsame Gruppen

+
diff --git a/src/main/angular/src/app/pages/user/user.component.ts b/src/main/angular/src/app/pages/user/user.component.ts index 57afdf0..854ba64 100644 --- a/src/main/angular/src/app/pages/user/user.component.ts +++ b/src/main/angular/src/app/pages/user/user.component.ts @@ -1,9 +1,11 @@ import {Component, OnInit} from '@angular/core'; import {NgForOf, NgIf} from "@angular/common"; import {ActivatedRoute} from "@angular/router"; -import {UserService} from "../../api/User/user.service"; import {GroupListComponent} from "../group/shared/group-list/group-list.component"; -import {UserCommon} from "../../api/User/UserCommon"; +import {GroupService} from "../../api/group/group.service"; +import {UserService} from "../../api/User/user.service"; +import {Group} from "../../api/group/Group"; +import {UserPublic} from "../../api/User/UserPublic"; @Component({ selector: 'app-user', @@ -18,11 +20,14 @@ import {UserCommon} from "../../api/User/UserCommon"; }) export class UserComponent implements OnInit { - protected user: UserCommon | null = null; + protected user: UserPublic | null = null; + + protected commonGroups: Group[] = []; constructor( protected readonly activatedRoute: ActivatedRoute, protected readonly userService: UserService, + protected readonly groupService: GroupService, ) { // - } @@ -31,7 +36,8 @@ export class UserComponent implements OnInit { this.activatedRoute.params.subscribe(params => { const publicUuid = params['publicUuid']; if (publicUuid) { - this.userService.getCommonByUuid(publicUuid, user => this.user = user); + this.userService.getByPublicUuid(publicUuid, user => this.user = user); + this.groupService.findAllCommon(publicUuid, commonGroups => this.commonGroups = commonGroups); } }); } diff --git a/src/main/angular/src/app/shared/password/password.component.html b/src/main/angular/src/app/shared/password/password.component.html new file mode 100644 index 0000000..02ef367 --- /dev/null +++ b/src/main/angular/src/app/shared/password/password.component.html @@ -0,0 +1,5 @@ + +

Passwort

+ + +
diff --git a/src/main/angular/src/app/shared/password/password.component.less b/src/main/angular/src/app/shared/password/password.component.less new file mode 100644 index 0000000..e69de29 diff --git a/src/main/angular/src/app/shared/password/password.component.ts b/src/main/angular/src/app/shared/password/password.component.ts new file mode 100644 index 0000000..2d2f3bb --- /dev/null +++ b/src/main/angular/src/app/shared/password/password.component.ts @@ -0,0 +1,29 @@ +import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; +import {FormsModule} from "@angular/forms"; +import {NgIf} from "@angular/common"; + +@Component({ + selector: 'app-password', + standalone: true, + imports: [ + FormsModule, + NgIf + ], + templateUrl: './password.component.html', + styleUrl: './password.component.less' +}) +export class PasswordComponent implements OnInit { + + @Input() + visible: boolean = false; + + @Output() + join: EventEmitter = new EventEmitter(); + + protected password: string = ""; + + ngOnInit(): void { + this.password = ""; + } + +} diff --git a/src/main/angular/src/app/shared/relative.pipe.ts b/src/main/angular/src/app/shared/relative.pipe.ts new file mode 100644 index 0000000..dc4c5b4 --- /dev/null +++ b/src/main/angular/src/app/shared/relative.pipe.ts @@ -0,0 +1,33 @@ +import {Pipe, PipeTransform} from '@angular/core'; + +@Pipe({ + name: 'relative', + standalone: true +}) +export class RelativePipe implements PipeTransform { + + transform(value: Date | null | undefined, now: Date): unknown { + if (value === null || value === undefined) { + return value; + } + const totalMillis = now.getTime() - value.getTime(); + const totalSeconds = Math.floor(totalMillis / 1000); + const totalMinutes = Math.floor(totalSeconds / 60); + const totalHours = Math.floor(totalMinutes / 60); + const days = Math.floor(totalHours / 24); + const seconds = totalSeconds % 60; + const minutes = totalMinutes % 60; + const hours = totalHours % 24; + if (totalSeconds <= 0) { + return "Gerade eben"; + } else if (days > 0) { + return `Vor ${days} Tag${days === 1 ? '' : 'e'}`; + } else if (hours > 0) { + return `Vor ${hours} Stunde${hours === 1 ? '' : 'n'}`; + } else if (minutes > 0) { + return `Vor ${minutes} Minute${minutes === 1 ? '' : 'n'}`; + } + return `Vor ${seconds} Sekunde${seconds === 1 ? '' : 'n'}` + } + +} diff --git a/src/main/angular/src/app/shared/text/text.component.less b/src/main/angular/src/app/shared/text/text.component.less index ff9ef55..7522751 100644 --- a/src/main/angular/src/app/shared/text/text.component.less +++ b/src/main/angular/src/app/shared/text/text.component.less @@ -7,3 +7,7 @@ .invalid { background-color: indianred; } + +input { + width: 100%; +} diff --git a/src/main/java/de/ph87/tools/group/Access.java b/src/main/java/de/ph87/tools/group/Access.java new file mode 100644 index 0000000..20aeacd --- /dev/null +++ b/src/main/java/de/ph87/tools/group/Access.java @@ -0,0 +1,13 @@ +package de.ph87.tools.group; + +import de.ph87.tools.user.User; +import lombok.Data; + +@Data +public class Access { + + public final User user; + + public final Group group; + +} diff --git a/src/main/java/de/ph87/tools/group/Group.java b/src/main/java/de/ph87/tools/group/Group.java index b87905f..552026d 100644 --- a/src/main/java/de/ph87/tools/group/Group.java +++ b/src/main/java/de/ph87/tools/group/Group.java @@ -68,6 +68,7 @@ public class Group implements IWebSocketMessage { return List.of("Number", uuid); } + @SuppressWarnings("BooleanMethodIsAlwaysInverted") public boolean isOwnedBy(@NonNull final User user) { return owner.equals(user); } diff --git a/src/main/java/de/ph87/tools/group/GroupAccessService.java b/src/main/java/de/ph87/tools/group/GroupAccessService.java new file mode 100644 index 0000000..dbadd11 --- /dev/null +++ b/src/main/java/de/ph87/tools/group/GroupAccessService.java @@ -0,0 +1,53 @@ +package de.ph87.tools.group; + +import de.ph87.tools.user.User; +import de.ph87.tools.user.UserService; +import jakarta.annotation.Nullable; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.server.ResponseStatusException; + +@Slf4j +@Service +@Transactional +@RequiredArgsConstructor +public class GroupAccessService { + + private final GroupRepository groupRepository; + + private final UserService userService; + + public boolean canAccess(@NonNull final String groupUuid, @Nullable final String userPrivateUuid) { + final User user = userService.accessOrNull(userPrivateUuid); + if (user == null) { + return false; + } + final Group group = getByUuidOrThrow(groupUuid); + return group.getUsers().contains(user); + } + + @NonNull + public Access access(@NonNull final String groupUuid, @NonNull final String userPrivateUuid) { + final User user = userService.access(userPrivateUuid); + final Group group = getByUuidOrThrow(groupUuid); + return new Access(user, group); + } + + public Access accessAsOwner(@NonNull final String userPrivateUuid, @NonNull final String groupUuid) { + final Access access = access(groupUuid, userPrivateUuid); + if (!access.group.isOwnedBy(access.user)) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST); + } + return access; + } + + @NonNull + private Group getByUuidOrThrow(@NonNull final String groupUuid) { + return groupRepository.findById(groupUuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST)); + } + +} diff --git a/src/main/java/de/ph87/tools/group/GroupController.java b/src/main/java/de/ph87/tools/group/GroupController.java index 06f60e2..a2d2700 100644 --- a/src/main/java/de/ph87/tools/group/GroupController.java +++ b/src/main/java/de/ph87/tools/group/GroupController.java @@ -6,6 +6,8 @@ import lombok.NonNull; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; +import java.util.Set; + import static de.ph87.tools.UserArgumentResolver.USER_UUID_COOKIE_NAME; @CrossOrigin @@ -16,6 +18,8 @@ public class GroupController { private final GroupService groupService; + private final GroupAccessService groupAccessService; + @GetMapping("create") public GroupDto create(@CookieValue(name = USER_UUID_COOKIE_NAME, required = false) @Nullable final String privateUuid, @NonNull final HttpServletResponse response) { return groupService.create(privateUuid, response); @@ -23,7 +27,7 @@ public class GroupController { @PostMapping("canAccess") public boolean canAccess(@CookieValue(name = USER_UUID_COOKIE_NAME, required = false) @Nullable final String privateUuid, @NonNull @RequestBody final String groupUuid) { - return groupService.canAccess(privateUuid, groupUuid); + return groupAccessService.canAccess(groupUuid, privateUuid); } @PostMapping("get") @@ -31,9 +35,21 @@ public class GroupController { return groupService.get(privateUuid, groupUuid); } + @NonNull + @GetMapping("findAllJoined") + public Set findAllJoined(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String userUuid) { + return groupService.findAllJoined(userUuid); + } + + @NonNull + @PostMapping("findAllCommon") + public Set findAllCommon(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String userUuid, @NonNull @RequestBody final String targetUuid) { + return groupService.findAllCommon(userUuid, targetUuid); + } + @PostMapping("join") - public GroupDto join(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String privateUuid, @NonNull @RequestBody final GroupJoinRequest request) { - return groupService.join(privateUuid, request); + public GroupDto join(@CookieValue(name = USER_UUID_COOKIE_NAME, required = false) @Nullable final String privateUuid, @NonNull @RequestBody final GroupJoinRequest request, @NonNull final HttpServletResponse response) { + return groupService.join(privateUuid, request, response); } @PostMapping("changeTitle") diff --git a/src/main/java/de/ph87/tools/group/GroupDto.java b/src/main/java/de/ph87/tools/group/GroupDto.java index 484f136..e8b5336 100644 --- a/src/main/java/de/ph87/tools/group/GroupDto.java +++ b/src/main/java/de/ph87/tools/group/GroupDto.java @@ -1,19 +1,16 @@ 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 implements IWebSocketMessage { +public class GroupDto { @NonNull public final String uuid; @@ -35,19 +32,14 @@ public class GroupDto implements IWebSocketMessage { public final boolean initial; - public GroupDto(@NonNull final Group group, @NonNull final UserPublicDto owner) { + public GroupDto(@NonNull final Group group, @NonNull final UserPublicDto owner, @NonNull final Set users) { this.uuid = group.getUuid(); this.title = group.getTitle(); this.created = group.getCreated(); this.password = group.getPassword(); this.owner = owner; - this.users = group.getUsers().stream().map(UserPublicDto::new).collect(Collectors.toSet()); + this.users = users; this.initial = group.isInitial(); } - @Override - public List getWebsocketTopic() { - return List.of("Group", uuid); - } - } diff --git a/src/main/java/de/ph87/tools/group/GroupMapper.java b/src/main/java/de/ph87/tools/group/GroupMapper.java deleted file mode 100644 index ea9641a..0000000 --- a/src/main/java/de/ph87/tools/group/GroupMapper.java +++ /dev/null @@ -1,57 +0,0 @@ -package de.ph87.tools.group; - -import de.ph87.tools.user.User; -import de.ph87.tools.user.UserPublicDto; -import de.ph87.tools.user.UserPublicMapper; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.Set; -import java.util.stream.Collectors; - -@Slf4j -@Service -@Transactional -@RequiredArgsConstructor -public class GroupMapper { - - private final UserPublicMapper userPublicMapper; - - private final GroupRepository groupRepository; - - private final ApplicationEventPublisher applicationEventPublisher; - - /* DTO ------------------------------------------------------------------------------------------ */ - - @NonNull - public GroupDto toDto(@NonNull final Group group) { - final UserPublicDto owner = userPublicMapper.toPublicDto(group.getOwner()); - return new GroupDto(group, owner); - } - - /* PUBLISH -------------------------------------------------------------------------------------- */ - - @NonNull - public GroupDto publish(@NonNull final Group group) { - final GroupDto dto = toDto(group); - applicationEventPublisher.publishEvent(dto); - return dto; - } - - /* FIND & GET ----------------------------------------------------------------------------------- */ - - @NonNull - public Set findAllByUser(@NonNull final User user) { - return groupRepository.findAllByUsersContains(user).stream().map(this::toDto).collect(Collectors.toSet()); - } - - @NonNull - public Set findAllCommonGroups(@NonNull final User a, @NonNull final User b) { - return groupRepository.findAllByUsersContainsAndUsersContains(a, b).stream().map(this::toDto).collect(Collectors.toSet()); - } - -} diff --git a/src/main/java/de/ph87/tools/group/GroupOfUserService.java b/src/main/java/de/ph87/tools/group/GroupOfUserService.java deleted file mode 100644 index 20fbd96..0000000 --- a/src/main/java/de/ph87/tools/group/GroupOfUserService.java +++ /dev/null @@ -1,52 +0,0 @@ -package de.ph87.tools.group; - -import de.ph87.tools.user.User; -import de.ph87.tools.user.UserService; -import lombok.Data; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.server.ResponseStatusException; - -import java.util.Optional; - -@Slf4j -@Service -@Transactional -@RequiredArgsConstructor -public class GroupOfUserService { - - private final GroupRepository groupRepository; - - private final UserService userService; - - @NonNull - public Group getGroupOfUser(@NonNull final String groupUuid, @NonNull final User user) { - return findGroupOfUser(groupUuid, user).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST)); - } - - @NonNull - public Optional findGroupOfUser(@NonNull final String groupUuid, @NonNull final User user) { - return groupRepository.findByUuidAndUsersContains(groupUuid, user); - } - - @NonNull - public GroupOfUser accessGroupOfUser(@NonNull final String groupUuid, @NonNull final String privateUserUuid) { - final User user = userService.getByPrivateUuidOrThrow(privateUserUuid); - final Group group = getGroupOfUser(groupUuid, user); - return new GroupOfUser(user, group); - } - - @Data - public static class GroupOfUser { - - public final User user; - - public final Group group; - - } - -} diff --git a/src/main/java/de/ph87/tools/group/GroupRepository.java b/src/main/java/de/ph87/tools/group/GroupRepository.java index 167831c..39c6ce0 100644 --- a/src/main/java/de/ph87/tools/group/GroupRepository.java +++ b/src/main/java/de/ph87/tools/group/GroupRepository.java @@ -12,9 +12,6 @@ public interface GroupRepository extends ListCrudRepository { @NonNull Optional findByUuid(@NonNull String uuid); - @NonNull - Optional findByUuidAndUsersContains(final @NonNull String groupUuid, @NonNull User user); - @NonNull Set findAllByUsersContains(@NonNull User user); diff --git a/src/main/java/de/ph87/tools/group/GroupService.java b/src/main/java/de/ph87/tools/group/GroupService.java index 35bf00b..9105736 100644 --- a/src/main/java/de/ph87/tools/group/GroupService.java +++ b/src/main/java/de/ph87/tools/group/GroupService.java @@ -1,8 +1,9 @@ package de.ph87.tools.group; import de.ph87.tools.user.User; -import de.ph87.tools.user.UserPublicMapper; +import de.ph87.tools.user.UserPublicDto; import de.ph87.tools.user.UserService; +import de.ph87.tools.user.push.UserPushService; import jakarta.annotation.Nullable; import jakarta.servlet.http.HttpServletResponse; import lombok.NonNull; @@ -13,22 +14,23 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.server.ResponseStatusException; +import java.util.Set; +import java.util.stream.Collectors; + @Slf4j @Service @Transactional @RequiredArgsConstructor public class GroupService { + private final GroupAccessService groupAccessService; + private final GroupRepository groupRepository; + private final UserPushService userPushService; + private final UserService userService; - private final GroupMapper groupMapper; - - private final UserPublicMapper userPublicMapper; - - private final GroupOfUserService groupOfUserService; - @NonNull public GroupDto create(@Nullable final String privateUuid, @NonNull final HttpServletResponse response) { final User user = userService.getUserByPrivateUuidOrElseCreate(privateUuid, response); @@ -36,22 +38,15 @@ public class GroupService { return doJoinUnchecked(group, user); } - public boolean canAccess(@Nullable final String privateUuid, @NonNull final String groupUuid) { - if (privateUuid == null) { - return false; - } - return userService.findByPrivateUuid(privateUuid).flatMap(user -> groupOfUserService.findGroupOfUser(groupUuid, user)).isPresent(); - } - @NonNull public GroupDto get(@NonNull final String privateUuid, @NonNull final String groupUuid) { - final GroupOfUserService.GroupOfUser ug = groupOfUserService.accessGroupOfUser(groupUuid, privateUuid); - return groupMapper.toDto(ug.group); + final Access ug = groupAccessService.access(groupUuid, privateUuid); + return toDto(ug.group); } @NonNull - public GroupDto join(@NonNull final String privateUuid, @NonNull final GroupJoinRequest request) { - final User user = userService.getByPrivateUuidOrThrow(privateUuid); + public GroupDto join(@Nullable final String privateUuid, @NonNull final GroupJoinRequest request, @NonNull final HttpServletResponse response) { + final User user = userService.getUserByPrivateUuidOrElseCreate(privateUuid, response); final Group group = getByGroupByGroupUuid(request.uuid); if (!group.getPassword().equals(request.password)) { log.error("Wrong password: user={}, group={}", user, group); @@ -67,27 +62,23 @@ public class GroupService { @NonNull public GroupDto changeTitle(@NonNull final String privateUuid, @NonNull final GroupChangeTitleRequest request) { - final GroupOfUserService.GroupOfUser ug = groupOfUserService.accessGroupOfUser(request.uuid, privateUuid); + final Access ug = groupAccessService.access(request.uuid, privateUuid); if (!ug.group.isOwnedBy(ug.user)) { throw new ResponseStatusException(HttpStatus.FORBIDDEN); } ug.group.setTitle(request.title); - return groupMapper.publish(ug.group); + return publish(ug.group); } @NonNull public GroupDto changePassword(@NonNull final String privateUuid, @NonNull final GroupChangePasswordInbound request) { - final User user = userService.getByPrivateUuidOrThrow(privateUuid); - final Group group = groupOfUserService.getGroupOfUser(request.uuid, user); - if (!group.isOwnedBy(user)) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN); - } - group.setPassword(request.password); - return groupMapper.publish(group); + final Access access = groupAccessService.accessAsOwner(privateUuid, request.uuid); + access.group.setPassword(request.password); + return publish(access.group); } public void leave(@NonNull final String privateUuid, @NonNull final String groupUuid) { - final User user = userService.getByPrivateUuidOrThrow(privateUuid); + final User user = userService.access(privateUuid); final Group group = getByGroupByGroupUuid(groupUuid); doLeaveUnchecked(group, user); } @@ -107,9 +98,7 @@ public class GroupService { group.touch(); user.touch(); log.info("User joined Group: user={}, group={}", user, group); - final GroupDto groupDto = groupMapper.publish(group); - userPublicMapper.publish(user); - return groupDto; + return publish(group); } private void doLeaveUnchecked(final Group group, final User user) { @@ -117,8 +106,45 @@ public class GroupService { group.touch(); user.touch(); log.info("User left Group: user={}, group={}", user, group); - groupMapper.publish(group); - userPublicMapper.publish(user); + publish(group); + } + + /* DTO ------------------------------------------------------------------------------------------ */ + + @NonNull + public GroupDto toDto(@NonNull final Group group) { + final UserPublicDto owner = new UserPublicDto(group.getOwner()); + final Set users = group.getUsers().stream().map(UserPublicDto::new).collect(Collectors.toSet()); + return new GroupDto(group, owner, users); + } + + /* PUBLISH -------------------------------------------------------------------------------------- */ + + @NonNull + public GroupDto publish(@NonNull final Group group) { + final GroupDto dto = toDto(group); + group.getUsers().forEach(user -> userPushService.push(user, dto)); + return dto; + } + + /* FIND & GET ----------------------------------------------------------------------------------- */ + + @NonNull + public Set findAllCommonGroups(@NonNull final User a, @NonNull final User b) { + return groupRepository.findAllByUsersContainsAndUsersContains(a, b).stream().map(this::toDto).collect(Collectors.toSet()); + } + + @NonNull + public Set findAllJoined(@NonNull final String privateUuid) { + final User principal = userService.access(privateUuid); + return groupRepository.findAllByUsersContains(principal).stream().map(this::toDto).collect(Collectors.toSet()); + } + + @NonNull + public Set findAllCommon(@NonNull final String privateUuid, @NonNull final String targetUuid) { + final User principal = userService.access(privateUuid); + final User target = userService.getByPublicUuid(targetUuid); + return findAllCommonGroups(principal, target); } } diff --git a/src/main/java/de/ph87/tools/tools/numbers/Numbers.java b/src/main/java/de/ph87/tools/tools/numbers/Numbers.java index 2eef3e2..2016c2e 100644 --- a/src/main/java/de/ph87/tools/tools/numbers/Numbers.java +++ b/src/main/java/de/ph87/tools/tools/numbers/Numbers.java @@ -1,12 +1,16 @@ package de.ph87.tools.tools.numbers; import de.ph87.tools.group.Group; +import de.ph87.tools.user.User; +import de.ph87.tools.user.reference.UserReference; import jakarta.persistence.*; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.NonNull; import lombok.ToString; +import java.time.ZonedDateTime; +import java.util.List; import java.util.UUID; @Entity @@ -22,15 +26,20 @@ public class Numbers { private String uuid = UUID.randomUUID().toString(); @NonNull - @Column(nullable = false) - private String name = "Ohne Namen"; - - @NonNull - @OneToOne + @ManyToOne(optional = false) private Group group; - public Numbers(@NonNull final Group group) { + @NonNull + @Column(nullable = false) + private ZonedDateTime date = ZonedDateTime.now(); + + @NonNull + @OneToMany(orphanRemoval = true) + private List users; + + public Numbers(@NonNull final Group group, @NonNull final List users) { this.group = group; + this.users = users; } @Override @@ -46,4 +55,18 @@ public class Numbers { return uuid.hashCode(); } + public int getNumberForUser(@NonNull final User user) { + for (int userReferenceIndex = 0; userReferenceIndex < users.size(); userReferenceIndex++) { + final UserReference userReference = users.get(userReferenceIndex); + if (userReference.getUser() != null && userReference.getUser().equals(user)) { + return userReferenceIndex; + } + } + throw new RuntimeException(); + } + + public boolean containsUser(@NonNull final User user) { + return users.stream().anyMatch(u -> user.equalsIUser(u.getUser())); + } + } diff --git a/src/main/java/de/ph87/tools/tools/numbers/NumbersController.java b/src/main/java/de/ph87/tools/tools/numbers/NumbersController.java index f6fb4c2..ba4d666 100644 --- a/src/main/java/de/ph87/tools/tools/numbers/NumbersController.java +++ b/src/main/java/de/ph87/tools/tools/numbers/NumbersController.java @@ -1,14 +1,41 @@ package de.ph87.tools.tools.numbers; +import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.CrossOrigin; -import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.*; + +import static de.ph87.tools.UserArgumentResolver.USER_UUID_COOKIE_NAME; @Slf4j @CrossOrigin +@RestController @RequiredArgsConstructor @RequestMapping("Numbers") public class NumbersController { + private final NumbersService numbersService; + + @GetMapping("create/{groupUuid}") + public void create(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String privateUuid, @PathVariable final String groupUuid) { + numbersService.create(privateUuid, groupUuid); + } + + @PostMapping("canAccess") + public boolean canAccess(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String privateUuid, @RequestBody final String groupUuid) { + return numbersService.canAccess(privateUuid, groupUuid); + } + + @NonNull + @PostMapping("getGroupUuid") + public String getGroupUuid(@RequestBody final String groupUuid) { + return numbersService.getGroupUuid(groupUuid); + } + + @NonNull + @PostMapping("byUuid") + public NumbersPrivateDto byUuid(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String privateUuid, @RequestBody final String groupUuid) { + return numbersService.dtoByUuid(privateUuid, groupUuid); + } + } diff --git a/src/main/java/de/ph87/tools/tools/numbers/NumbersDto.java b/src/main/java/de/ph87/tools/tools/numbers/NumbersPrivateDto.java similarity index 53% rename from src/main/java/de/ph87/tools/tools/numbers/NumbersDto.java rename to src/main/java/de/ph87/tools/tools/numbers/NumbersPrivateDto.java index 6fe8173..0ef6843 100644 --- a/src/main/java/de/ph87/tools/tools/numbers/NumbersDto.java +++ b/src/main/java/de/ph87/tools/tools/numbers/NumbersPrivateDto.java @@ -5,23 +5,28 @@ import lombok.Getter; import lombok.NonNull; import lombok.ToString; +import java.time.ZonedDateTime; + @Getter @ToString(callSuper = true) -public class NumbersDto { +public class NumbersPrivateDto { @NonNull private final String uuid; - @NonNull - private final String name; - @NonNull private final GroupDto group; - public NumbersDto(@NonNull final Numbers numbers, @NonNull final GroupDto group) { + @NonNull + private final ZonedDateTime date; + + private final int number; + + public NumbersPrivateDto(@NonNull final Numbers numbers, @NonNull final GroupDto group, final int number) { this.uuid = numbers.getUuid(); - this.name = numbers.getName(); + this.date = numbers.getDate(); this.group = group; + this.number = number; } } diff --git a/src/main/java/de/ph87/tools/tools/numbers/NumbersRepository.java b/src/main/java/de/ph87/tools/tools/numbers/NumbersRepository.java new file mode 100644 index 0000000..6cccb43 --- /dev/null +++ b/src/main/java/de/ph87/tools/tools/numbers/NumbersRepository.java @@ -0,0 +1,7 @@ +package de.ph87.tools.tools.numbers; + +import org.springframework.data.repository.ListCrudRepository; + +public interface NumbersRepository extends ListCrudRepository { + +} diff --git a/src/main/java/de/ph87/tools/tools/numbers/NumbersService.java b/src/main/java/de/ph87/tools/tools/numbers/NumbersService.java new file mode 100644 index 0000000..41155a0 --- /dev/null +++ b/src/main/java/de/ph87/tools/tools/numbers/NumbersService.java @@ -0,0 +1,116 @@ +package de.ph87.tools.tools.numbers; + +import de.ph87.tools.group.GroupAccessService; +import de.ph87.tools.group.GroupDto; +import de.ph87.tools.group.GroupService; +import de.ph87.tools.user.User; +import de.ph87.tools.user.UserService; +import de.ph87.tools.user.push.UserPushService; +import de.ph87.tools.user.reference.UserReference; +import de.ph87.tools.user.reference.UserReferenceService; +import lombok.Getter; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.server.ResponseStatusException; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +@Slf4j +@Service +@Transactional +@RequiredArgsConstructor +public class NumbersService { + + private final NumbersRepository numbersRepository; + + private final GroupAccessService groupAccessService; + + private final UserReferenceService userReferenceService; + + private final GroupService groupService; + + private final UserPushService userPushService; + + private final UserService userService; + + public void create(@NonNull final String userPrivateUuid, @NonNull final String groupUuid) { + final de.ph87.tools.group.Access access = groupAccessService.accessAsOwner(userPrivateUuid, groupUuid); + final List users = access.getGroup().getUsers().stream().map(userReferenceService::create).collect(Collectors.toList()); + Collections.shuffle(users); + final Numbers numbers = numbersRepository.save(new Numbers(access.getGroup(), users)); + publish(numbers); + } + + private void publish(@NonNull final Numbers numbers) { + numbers.getUsers() + .stream() + .map(UserReference::getUser) + .filter(Objects::nonNull) + .forEach(user -> publish(numbers, user)); + } + + private void publish(@NonNull final Numbers numbers, @NonNull final User user) { + final NumbersPrivateDto dto = toPrivateDto(numbers, user); + log.debug("Sending event: {}", dto); + userPushService.push(user, dto); + } + + @NonNull + public NumbersPrivateDto toPrivateDto(@NonNull final Numbers numbers, @NonNull final User user) { + final GroupDto group = groupService.toDto(numbers.getGroup()); + final int number = numbers.getNumberForUser(user); + return new NumbersPrivateDto(numbers, group, number); + } + + public boolean canAccess(@NonNull final String privateUuid, @NonNull final String numbersUuid) { + final User user = userService.access(privateUuid); + final Numbers numbers = numbersRepository.findById(numbersUuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST)); + return numbers.containsUser(user); + } + + @NonNull + public String getGroupUuid(@NonNull final String numbersUuid) { + final Numbers numbers = numbersRepository.findById(numbersUuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST)); + return numbers.getGroup().getUuid(); + } + + @NonNull + public NumbersPrivateDto dtoByUuid(@NonNull final String privateUuid, @NonNull final String numbersUuid) { + final Access access = access(privateUuid, numbersUuid); + return toPrivateDto(access.numbers, access.user); + } + + @NonNull + private Access access(@NonNull final String privateUuid, @NonNull final String numbersUuid) { + final User user = userService.access(privateUuid); + final Numbers numbers = numbersRepository.findById(numbersUuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST)); + if (!numbers.containsUser(user)) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST); + } + return new Access(numbers, user); + } + + @Getter + @ToString + private static class Access { + + public final Numbers numbers; + + public final User user; + + public Access(final Numbers numbers, final User user) { + this.numbers = numbers; + this.user = user; + } + + } + +} diff --git a/src/main/java/de/ph87/tools/user/User.java b/src/main/java/de/ph87/tools/user/User.java index f5d56dd..9e6c067 100644 --- a/src/main/java/de/ph87/tools/user/User.java +++ b/src/main/java/de/ph87/tools/user/User.java @@ -1,6 +1,6 @@ package de.ph87.tools.user; -import de.ph87.tools.web.IWebSocketMessage; +import de.ph87.tools.user.reference.IUser; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Id; @@ -9,7 +9,6 @@ import lombok.*; import org.springframework.security.crypto.password.PasswordEncoder; import java.time.ZonedDateTime; -import java.util.List; import java.util.UUID; @Entity @@ -17,7 +16,7 @@ import java.util.UUID; @ToString @NoArgsConstructor @Table(name = "`user`") -public class User implements IWebSocketMessage { +public class User extends IUser { @Id @NonNull @@ -56,24 +55,6 @@ public class User implements IWebSocketMessage { lastAccess = ZonedDateTime.now(); } - @Override - public List getWebsocketTopic() { - return List.of("User", privateUuid); - } - - @Override - public boolean equals(final Object obj) { - if (!(obj instanceof final User user)) { - return false; - } - return user.privateUuid.equals(this.privateUuid); - } - - @Override - public int hashCode() { - return privateUuid.hashCode(); - } - public void setPassword(@NonNull final PasswordEncoder passwordEncoder, @NonNull final String password) { this.password = passwordEncoder.encode(password); } diff --git a/src/main/java/de/ph87/tools/user/UserController.java b/src/main/java/de/ph87/tools/user/UserController.java index 42f08f2..b7daef4 100644 --- a/src/main/java/de/ph87/tools/user/UserController.java +++ b/src/main/java/de/ph87/tools/user/UserController.java @@ -21,7 +21,13 @@ public class UserController { @Nullable @GetMapping("whoAmI") public UserPrivateDto whoAmI(@CookieValue(name = USER_UUID_COOKIE_NAME, required = false) @Nullable final String userUuid, @NonNull final HttpServletResponse response) { - return userService.getUserByPrivateUuidOrNull(userUuid, response); + return userService.whoAmI(userUuid, response); + } + + @NonNull + @PostMapping("getByPublicUuid") + public UserPublicDto getByPublicUuid(@NonNull @RequestBody final String publicUuid) { + return userService.getDtoByPublicUuid(publicUuid); } @Nullable @@ -42,12 +48,6 @@ public class UserController { return userService.changePassword(userUuid, password); } - @NonNull - @PostMapping("getCommonByUuid") - public UserCommonDto getCommonByUuid(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String userUuid, @NonNull @RequestBody final String targetUuid) { - return userService.getCommonByUuid(userUuid, targetUuid); - } - @GetMapping("delete") public void delete(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String userUuid, @NonNull final HttpServletResponse response) { userService.delete(userUuid, response); diff --git a/src/main/java/de/ph87/tools/user/UserPrivateDto.java b/src/main/java/de/ph87/tools/user/UserPrivateDto.java index 2d2921f..cf3c6a4 100644 --- a/src/main/java/de/ph87/tools/user/UserPrivateDto.java +++ b/src/main/java/de/ph87/tools/user/UserPrivateDto.java @@ -1,18 +1,19 @@ package de.ph87.tools.user; -import de.ph87.tools.group.GroupDto; -import de.ph87.tools.web.IWebSocketMessage; +import de.ph87.tools.user.reference.IUser; +import jakarta.annotation.Nullable; 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 implements IWebSocketMessage { +public class UserPrivateDto extends IUser { + + @NonNull + private final String pushType = "UserPrivate"; @NonNull public final String publicUuid; @@ -26,23 +27,22 @@ public class UserPrivateDto implements IWebSocketMessage { @NonNull public final ZonedDateTime created; - @NonNull - private final Set groups; - private final boolean password; - public UserPrivateDto(@NonNull final User user, final @NonNull Set groups) { + public UserPrivateDto(@NonNull final User user) { this.publicUuid = user.getPublicUuid(); this.name = user.getName(); this.privateUuid = user.getPrivateUuid(); this.created = user.getCreated(); this.password = !user.getPassword().isEmpty(); - this.groups = groups; } - @Override - public List getWebsocketTopic() { - return List.of("User", privateUuid); + @Nullable + public static UserPrivateDto orNull(@Nullable final User user) { + if (user == null) { + return null; + } + return new UserPrivateDto(user); } } diff --git a/src/main/java/de/ph87/tools/user/UserPrivateMapper.java b/src/main/java/de/ph87/tools/user/UserPrivateMapper.java deleted file mode 100644 index b8fd027..0000000 --- a/src/main/java/de/ph87/tools/user/UserPrivateMapper.java +++ /dev/null @@ -1,48 +0,0 @@ -package de.ph87.tools.user; - -import de.ph87.tools.group.GroupDto; -import de.ph87.tools.group.GroupMapper; -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; - -import java.util.Set; - -@Slf4j -@Service -@Transactional -@EnableScheduling -@RequiredArgsConstructor -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); - return new UserPrivateDto(user, groups); - } - - @Nullable - public UserPrivateDto toPrivateDtoOrNull(@Nullable final User user) { - if (user == null) { - return null; - } - return toPrivateDto(user); - } - - @NonNull - public UserPrivateDto publish(@NonNull final User user) { - final UserPrivateDto dto = toPrivateDto(user); - applicationEventPublisher.publishEvent(dto); - return dto; - } - -} diff --git a/src/main/java/de/ph87/tools/user/UserPublicDto.java b/src/main/java/de/ph87/tools/user/UserPublicDto.java index 38d4d17..604cae8 100644 --- a/src/main/java/de/ph87/tools/user/UserPublicDto.java +++ b/src/main/java/de/ph87/tools/user/UserPublicDto.java @@ -1,15 +1,13 @@ package de.ph87.tools.user; -import de.ph87.tools.web.IWebSocketMessage; +import de.ph87.tools.user.reference.IUser; import lombok.Getter; import lombok.NonNull; import lombok.ToString; -import java.util.List; - @Getter @ToString -public class UserPublicDto implements IWebSocketMessage { +public class UserPublicDto extends IUser { @NonNull public final String publicUuid; @@ -22,9 +20,4 @@ public class UserPublicDto implements IWebSocketMessage { this.name = user.getName(); } - @Override - public List getWebsocketTopic() { - return List.of("User", publicUuid); - } - } diff --git a/src/main/java/de/ph87/tools/user/UserPublicMapper.java b/src/main/java/de/ph87/tools/user/UserPublicMapper.java deleted file mode 100644 index 45bf448..0000000 --- a/src/main/java/de/ph87/tools/user/UserPublicMapper.java +++ /dev/null @@ -1,30 +0,0 @@ -package de.ph87.tools.user; - -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; - -@Slf4j -@Service -@Transactional -@EnableScheduling -@RequiredArgsConstructor -public class UserPublicMapper { - - private final ApplicationEventPublisher applicationEventPublisher; - - @NonNull - public UserPublicDto toPublicDto(@NonNull final User user) { - return new UserPublicDto(user); - } - - public void publish(@NonNull final User user) { - final UserPublicDto dto = toPublicDto(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 a526c33..17a1c1a 100644 --- a/src/main/java/de/ph87/tools/user/UserService.java +++ b/src/main/java/de/ph87/tools/user/UserService.java @@ -1,7 +1,5 @@ package de.ph87.tools.user; -import de.ph87.tools.group.GroupDto; -import de.ph87.tools.group.GroupMapper; import jakarta.annotation.Nullable; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletResponse; @@ -17,7 +15,6 @@ import org.springframework.web.server.ResponseStatusException; import java.util.Optional; import java.util.Random; -import java.util.Set; import java.util.function.Consumer; import java.util.regex.Pattern; @@ -42,12 +39,6 @@ public class UserService { private final UserRepository userRepository; - private final UserPrivateMapper userPrivateMapper; - - private final UserPublicMapper userPublicMapper; - - private final GroupMapper groupMapper; - private final PasswordEncoder passwordEncoder; @NonNull @@ -58,8 +49,7 @@ public class UserService { } user.touch(); writeUserUuidCookie(response, user); - userPublicMapper.publish(user); - return userPrivateMapper.publish(user); + return new UserPrivateDto(user); } @NonNull @@ -70,13 +60,13 @@ public class UserService { } @Nullable - public UserPrivateDto getUserByPrivateUuidOrNull(@Nullable final String privateUuid, final @NonNull HttpServletResponse response) { + public UserPrivateDto whoAmI(@Nullable final String privateUuid, final @NonNull HttpServletResponse response) { if (privateUuid == null || privateUuid.isEmpty()) { return null; } final User user = userRepository.findByPrivateUuid(privateUuid).orElse(null); writeUserUuidCookie(response, user); - return userPrivateMapper.toPrivateDtoOrNull(user); + return UserPrivateDto.orNull(user); } @NonNull @@ -119,14 +109,6 @@ public class UserService { }); } - @NonNull - public UserCommonDto getCommonByUuid(@NonNull final String privateUuid, @NonNull final String targetUuid) { - final User principal = getByPrivateUuidOrThrow(privateUuid); - final User target = getByPublicUuid(targetUuid); - final Set commonGroups = groupMapper.findAllCommonGroups(principal, target); - return new UserCommonDto(target, commonGroups); - } - public void delete(@NonNull final String privateUuid, final @NonNull HttpServletResponse response) { final User user = userRepository.findByPrivateUuid(privateUuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST)); deleteUnchecked(response, user); @@ -145,7 +127,7 @@ public class UserService { @NonNull private String generateFreeUserName() { int index = new Random().nextInt(1234, 5723); - String name = USER_NAME_DEFAULT_BASE; + String name = "%s #%d".formatted(USER_NAME_DEFAULT_BASE, index); while (userRepository.existsByName(name)) { index++; name = "%s #%d".formatted(USER_NAME_DEFAULT_BASE, index); @@ -155,11 +137,9 @@ public class UserService { @NonNull private UserPrivateDto modify(@NonNull final String privateUuid, @NonNull Consumer modifier) { - final User user = getByPrivateUuidOrThrow(privateUuid); + final User user = access(privateUuid); modifier.accept(user); - userPublicMapper.publish(user); - userPrivateMapper.publish(user); - return userPrivateMapper.toPrivateDto(user); + return new UserPrivateDto(user); } private void deleteUnchecked(final HttpServletResponse response, final User user) { @@ -168,10 +148,25 @@ public class UserService { writeUserUuidCookie(response, null); } + /* ACCESS --------------------------------------------------------------------------------------- */ + + @Nullable + public User accessOrNull(@Nullable final String userPrivateUuid) { + if (userPrivateUuid == null || userPrivateUuid.isEmpty()) { + return null; + } + return access(userPrivateUuid); + } + /* GETTERS & FINDERS ---------------------------------------------------------------------------- */ @NonNull - public User getByPrivateUuidOrThrow(@NonNull final String privateUuid) { + public UserPublicDto getDtoByPublicUuid(@NonNull final String publicUuid) { + return new UserPublicDto(getByPublicUuid(publicUuid)); + } + + @NonNull + public User access(@NonNull final String privateUuid) { return userRepository.findByPrivateUuid(privateUuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST)); } @@ -180,11 +175,6 @@ public class UserService { return userRepository.findByPublicUuid(publicUuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST)); } - @NonNull - public Optional findByPrivateUuid(@NonNull final String privateUuid) { - return userRepository.findByPrivateUuid(privateUuid); - } - /* COOKIES -------------------------------------------------------------------------------------- */ private static void writeUserUuidCookie(@NonNull final HttpServletResponse response, @Nullable final User user) { diff --git a/src/main/java/de/ph87/tools/user/push/UserPushMessage.java b/src/main/java/de/ph87/tools/user/push/UserPushMessage.java new file mode 100644 index 0000000..5c95a8e --- /dev/null +++ b/src/main/java/de/ph87/tools/user/push/UserPushMessage.java @@ -0,0 +1,22 @@ +package de.ph87.tools.user.push; + +import lombok.Getter; +import lombok.NonNull; +import lombok.ToString; + +@Getter +@ToString +public class UserPushMessage { + + @NonNull + private final String _type_; + + @NonNull + private final Object payload; + + public UserPushMessage(@NonNull final Object payload) { + this._type_ = payload.getClass().getSimpleName(); + this.payload = payload; + } + +} diff --git a/src/main/java/de/ph87/tools/user/push/UserPushService.java b/src/main/java/de/ph87/tools/user/push/UserPushService.java new file mode 100644 index 0000000..c9b8a28 --- /dev/null +++ b/src/main/java/de/ph87/tools/user/push/UserPushService.java @@ -0,0 +1,25 @@ +package de.ph87.tools.user.push; + +import de.ph87.tools.user.User; +import de.ph87.tools.web.WebSocketService; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Slf4j +@Service +@RequiredArgsConstructor +public class UserPushService { + + private final WebSocketService webSocketService; + + public void push(@NonNull final User user, @NonNull final Object payload) { + final List topic = List.of("User", user.getPrivateUuid()); + final UserPushMessage message = new UserPushMessage(payload); + webSocketService.send(topic, message); + } + +} diff --git a/src/main/java/de/ph87/tools/user/reference/IUser.java b/src/main/java/de/ph87/tools/user/reference/IUser.java new file mode 100644 index 0000000..2fc3e4b --- /dev/null +++ b/src/main/java/de/ph87/tools/user/reference/IUser.java @@ -0,0 +1,31 @@ +package de.ph87.tools.user.reference; + +import jakarta.annotation.Nullable; +import lombok.NonNull; + +public abstract class IUser { + + @NonNull + public abstract String getPublicUuid(); + + @Override + public boolean equals(final Object obj) { + if (!(obj instanceof final IUser user)) { + return false; + } + return equalsIUser(user); + } + + public boolean equalsIUser(@Nullable final IUser user) { + if (user == null) { + return false; + } + return user.getPublicUuid().equals(this.getPublicUuid()); + } + + @Override + public int hashCode() { + return getPublicUuid().hashCode(); + } + +} diff --git a/src/main/java/de/ph87/tools/user/reference/UserReference.java b/src/main/java/de/ph87/tools/user/reference/UserReference.java new file mode 100644 index 0000000..40d9fb5 --- /dev/null +++ b/src/main/java/de/ph87/tools/user/reference/UserReference.java @@ -0,0 +1,35 @@ +package de.ph87.tools.user.reference; + +import de.ph87.tools.user.User; +import jakarta.annotation.Nullable; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.NonNull; +import lombok.ToString; + +@Entity +@Getter +@ToString +@NoArgsConstructor +@Table(name = "`user_reference`") +public class UserReference { + + @Id + @GeneratedValue + private long id; + + @Nullable + @ManyToOne + private User user; + + @NonNull + @Column(nullable = false) + private String name; + + public UserReference(@NonNull final User user) { + this.user = user; + this.name = user.getName(); + } + +} diff --git a/src/main/java/de/ph87/tools/user/reference/UserReferenceRepository.java b/src/main/java/de/ph87/tools/user/reference/UserReferenceRepository.java new file mode 100644 index 0000000..afefac8 --- /dev/null +++ b/src/main/java/de/ph87/tools/user/reference/UserReferenceRepository.java @@ -0,0 +1,7 @@ +package de.ph87.tools.user.reference; + +import org.springframework.data.repository.ListCrudRepository; + +public interface UserReferenceRepository extends ListCrudRepository { + +} diff --git a/src/main/java/de/ph87/tools/user/reference/UserReferenceService.java b/src/main/java/de/ph87/tools/user/reference/UserReferenceService.java new file mode 100644 index 0000000..bcb5b98 --- /dev/null +++ b/src/main/java/de/ph87/tools/user/reference/UserReferenceService.java @@ -0,0 +1,23 @@ +package de.ph87.tools.user.reference; + +import de.ph87.tools.user.User; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@Transactional +@RequiredArgsConstructor +public class UserReferenceService { + + private final UserReferenceRepository userReferenceRepository; + + @NonNull + public UserReference create(@NonNull final User user) { + return userReferenceRepository.save(new UserReference(user)); + } + +} diff --git a/src/main/java/de/ph87/tools/web/WebSocketService.java b/src/main/java/de/ph87/tools/web/WebSocketService.java index cd9e6c7..acd5276 100644 --- a/src/main/java/de/ph87/tools/web/WebSocketService.java +++ b/src/main/java/de/ph87/tools/web/WebSocketService.java @@ -7,6 +7,7 @@ import org.springframework.lang.NonNull; import org.springframework.messaging.simp.SimpMessageSendingOperations; import org.springframework.stereotype.Service; +import java.util.List; import java.util.stream.Collectors; @Slf4j @@ -18,8 +19,12 @@ public class WebSocketService { @EventListener(IWebSocketMessage.class) public void send(@NonNull final IWebSocketMessage message) { - final String topic = message.getWebsocketTopic().stream().map(Object::toString).collect(Collectors.joining("/")); - simpMessageSendingOperations.convertAndSend(topic, message); + send(message.getWebsocketTopic(), message); + } + + public void send(@NonNull final List topic, @NonNull final Object message) { + final String topicString = topic.stream().map(Object::toString).collect(Collectors.joining("/")); + simpMessageSendingOperations.convertAndSend(topicString, message); } }