Compare commits

...

3 Commits

56 changed files with 907 additions and 439 deletions

View File

@ -1,5 +1,4 @@
import {validateBoolean, validateDate, validateList, validateString} from "../common/validators"; import {validateBoolean, validateDate, validateString} from "../common/validators";
import {Group} from "../group/Group";
export class UserPrivate { export class UserPrivate {
@ -9,7 +8,6 @@ export class UserPrivate {
readonly created: Date, readonly created: Date,
readonly name: string, readonly name: string,
readonly password: boolean, readonly password: boolean,
readonly groups: Group[],
) { ) {
// - // -
} }
@ -21,7 +19,6 @@ export class UserPrivate {
validateDate(json['created']), validateDate(json['created']),
validateString(json['name']), validateString(json['name']),
validateBoolean(json['password']), validateBoolean(json['password']),
validateList(json['groups'], Group.fromJson),
); );
} }

View File

@ -2,13 +2,23 @@ import {Injectable} from '@angular/core';
import {ApiService} from "../common/api.service"; import {ApiService} from "../common/api.service";
import {UserPrivate} from "./UserPrivate"; import {UserPrivate} from "./UserPrivate";
import {Next} from "../common/types"; import {Next} from "../common/types";
import {UserCommon} from "./UserCommon";
import {UserPublic} from "./UserPublic"; import {UserPublic} from "./UserPublic";
import {EventType, Router} from "@angular/router"; import {EventType, Router} from "@angular/router";
import {BehaviorSubject, Subscription} from "rxjs"; import {BehaviorSubject, filter, Subject, Subscription} from "rxjs";
import {Group} from "../group/Group"; import {Group} from "../group/Group";
import {StompService} from "@stomp/ng2-stompjs"; 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({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -17,7 +27,9 @@ export class UserService {
private readonly subject: BehaviorSubject<UserPrivate | null> = new BehaviorSubject<UserPrivate | null>(null); private readonly subject: BehaviorSubject<UserPrivate | null> = new BehaviorSubject<UserPrivate | null>(null);
private subscription?: Subscription; private readonly pushSubject: Subject<any> = new Subject<any>();
private userSubscriptions: Subscription[] = [];
get user(): UserPrivate | null { get user(): UserPrivate | null {
return this.subject.value; return this.subject.value;
@ -30,15 +42,14 @@ export class UserService {
) { ) {
this.router.events.subscribe(e => { this.router.events.subscribe(e => {
if (e.type === EventType.NavigationEnd || e.type === EventType.NavigationSkipped) { if (e.type === EventType.NavigationEnd || e.type === EventType.NavigationSkipped) {
console.log(e);
this.refresh(); this.refresh();
} }
}); });
this.stompService.connected$.subscribe(() => this.refresh()); this.stompService.connected$.subscribe(() => this.refresh());
} }
getCommonByUuid(uuid: string, next: Next<UserCommon>): void { getByPublicUuid(publicUuid: any, next: Next<UserPublic>) {
this.api.postSingle(['User', 'getCommonByUuid'], uuid, UserCommon.fromJson, next); this.api.postSingle(['User', 'getByPublicUuid'], publicUuid, UserPublic.fromJson, next);
} }
delete(next?: Next<void>) { delete(next?: Next<void>) {
@ -54,7 +65,7 @@ export class UserService {
} }
goto(user: UserPublic) { goto(user: UserPublic) {
this.router.navigate(['/User', user.publicUuid]); this.router.navigate(['User', user.publicUuid]);
} }
refresh() { refresh() {
@ -65,24 +76,25 @@ export class UserService {
return this.subject.subscribe(next); return this.subject.subscribe(next);
} }
subscribePush<T>(predicate: (m: any) => boolean, next: Next<T>): Subscription {
return this.pushSubject
.pipe(filter(predicate))
.subscribe(next);
}
iOwn(group: Group): boolean { iOwn(group: Group): boolean {
return this.user?.publicUuid === group.owner.publicUuid; return this.user?.publicUuid === group.owner.publicUuid;
} }
private setUser(user: UserPrivate | null) { private setUser(user: UserPrivate | null) {
if (this.subject.value?.privateUuid !== user?.privateUuid) { if (user === null || this.subject.value?.privateUuid !== user.privateUuid) {
if (this.subscription) { this.userSubscriptions.forEach(subscription => subscription.unsubscribe());
this.subscription.unsubscribe(); this.userSubscriptions.length = 0;
this.subscription = undefined;
}
if (user) { 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); this.subject.next(user);
} }
newSubscriber(): Subscribed<UserPrivate> {
return new Subscribed<UserPrivate>(UserPrivate.samePrivateUuid, (user, next) => this.api.subscribe(['UserPrivate', user.privateUuid], UserPrivate.fromJson, next));
}
} }

View File

@ -28,6 +28,14 @@ export class GroupService {
this.api.postSingle(['Group', 'get'], uuid, Group.fromJson, next); this.api.postSingle(['Group', 'get'], uuid, Group.fromJson, next);
} }
findAllCommon(uuid: string, next: Next<Group[]>): void {
this.api.postList(['Group', 'findAllCommon'], uuid, Group.fromJson, next);
}
findAllJoined(next: Next<Group[]>) {
this.api.getList(['Group', 'findAllJoined'], Group.fromJson, next);
}
create(next: Next<Group>): void { create(next: Next<Group>): void {
this.api.getSingle(['Group', 'create'], Group.fromJson, next); this.api.getSingle(['Group', 'create'], Group.fromJson, next);
} }
@ -60,8 +68,8 @@ export class GroupService {
this.api.postNone(['Group', 'leave'], group, next); this.api.postNone(['Group', 'leave'], group, next);
} }
goto(group: Group) { goto(uuid: string) {
this.router.navigate(['Group', group.uuid]); this.router.navigate(['Group', uuid]);
} }
newSubscriber(): Subscribed<Group> { newSubscriber(): Subscribed<Group> {

View File

@ -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']),
);
}
}

View File

@ -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']),
);
}
}

View File

@ -2,6 +2,9 @@ import {Injectable} from '@angular/core';
import {ApiService} from "../../common/api.service"; import {ApiService} from "../../common/api.service";
import {Router} from "@angular/router"; import {Router} from "@angular/router";
import {UserService} from "../../User/user.service"; import {UserService} from "../../User/user.service";
import {Next} from "../../common/types";
import {NumbersPrivateDto} from "./NumbersPrivateDto";
import {validateBoolean, validateString} from "../../common/validators";
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -13,7 +16,25 @@ export class NumbersService {
protected readonly router: Router, protected readonly router: Router,
protected readonly userService: UserService protected readonly userService: UserService
) { ) {
// - this.userService.subscribePush<NumbersPrivateDto>(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<NumbersPrivateDto>) {
this.api.postSingle(['Numbers', 'byUuid'], uuid, NumbersPrivateDto.fromJson, next);
}
canAccess(uuid: string, next: Next<boolean>) {
this.api.postSingle(['Numbers', 'canAccess'], uuid, validateBoolean, next);
}
getGroupUuid(uuid: string | null, next: Next<string>) {
this.api.postSingle(['Numbers', 'getGroupUuid'], uuid, validateString, next);
} }
} }

View File

@ -1,6 +1,6 @@
<div id="mainMenu" *ngIf="menuVisible"> <div id="mainMenu" *ngIf="menuVisible">
<div class="mainMenuItem" routerLinkActive="mainMenuItemActive" routerLink="/SolarSystem">Planeten</div> <div class="mainMenuItem" routerLinkActive="mainMenuItemActive" routerLink="/SolarSystem">Planeten</div>
<div class="mainMenuItem" routerLinkActive="mainMenuItemActive" routerLink="/VoltageDrop">Leitung</div> <div class="mainMenuItem" routerLinkActive="mainMenuItemActive" routerLink="/VoltageDrop">Kabel</div>
<div class="mainMenuItem" routerLinkActive="mainMenuItemActive" routerLink="/Groups">Gruppen</div> <div class="mainMenuItem" routerLinkActive="mainMenuItemActive" routerLink="/Groups">Gruppen</div>
<ng-container *ngIf="user !== null"> <ng-container *ngIf="user !== null">
<div class="mainMenuItem mainMenuItemRight" routerLinkActive="mainMenuItemActive" routerLink="/Profile">{{ user.name }}</div> <div class="mainMenuItem mainMenuItemRight" routerLinkActive="mainMenuItemActive" routerLink="/Profile">{{ user.name }}</div>

View File

@ -2,7 +2,6 @@ import {Component, OnDestroy, OnInit} from '@angular/core';
import {Router, RouterLink, RouterLinkActive, RouterOutlet} from '@angular/router'; import {Router, RouterLink, RouterLinkActive, RouterOutlet} from '@angular/router';
import {NgIf} from "@angular/common"; import {NgIf} from "@angular/common";
import {UserService} from "./api/User/user.service"; import {UserService} from "./api/User/user.service";
import {Numbers} from "./api/tools/Numbers/Numbers";
import {UserPrivate} from "./api/User/UserPrivate"; import {UserPrivate} from "./api/User/UserPrivate";
import {Subscription} from "rxjs"; import {Subscription} from "rxjs";
@ -15,8 +14,6 @@ import {Subscription} from "rxjs";
}) })
export class AppComponent implements OnInit, OnDestroy { export class AppComponent implements OnInit, OnDestroy {
protected readonly NumbersGroup = Numbers;
private readonly subscriptions: Subscription[] = []; private readonly subscriptions: Subscription[] = [];
protected menuVisible = true; protected menuVisible = true;
@ -37,7 +34,7 @@ export class AppComponent implements OnInit, OnDestroy {
ngOnInit(): void { ngOnInit(): void {
this.subscriptions.push(this.userService.subscribe(user => { this.subscriptions.push(this.userService.subscribe(user => {
if (this.user !== null && user === null) { if (this.user !== null && user === null) {
this.router.navigate(['/']); this.router.navigate(['']);
} }
this.user = user; this.user = user;
})); }));
@ -47,8 +44,4 @@ export class AppComponent implements OnInit, OnDestroy {
this.subscriptions.forEach(subscription => subscription.unsubscribe()); this.subscriptions.forEach(subscription => subscription.unsubscribe());
} }
delete() {
this.userService.delete();
}
} }

View File

@ -6,6 +6,7 @@ import {ProfileComponent} from "./pages/profile/profile.component";
import {UserComponent} from "./pages/user/user.component"; import {UserComponent} from "./pages/user/user.component";
import {GroupsComponent} from "./pages/group/groups/groups.component"; import {GroupsComponent} from "./pages/group/groups/groups.component";
import {GroupComponent} from "./pages/group/group/group.component"; import {GroupComponent} from "./pages/group/group/group.component";
import {NumbersComponent} from "./pages/tools/numbers/numbers.component";
export const routes: Routes = [ export const routes: Routes = [
{path: 'SolarSystemPrintout', component: SolarSystemPrintoutComponent}, {path: 'SolarSystemPrintout', component: SolarSystemPrintoutComponent},
@ -16,6 +17,8 @@ export const routes: Routes = [
{path: 'Groups', component: GroupsComponent}, {path: 'Groups', component: GroupsComponent},
{path: 'Group/:uuid', component: GroupComponent}, {path: 'Group/:uuid', component: GroupComponent},
{path: 'Numbers/:uuid', component: NumbersComponent},
{path: 'User/:publicUuid', component: UserComponent}, {path: 'User/:publicUuid', component: UserComponent},
{path: 'Profile', component: ProfileComponent}, {path: 'Profile', component: ProfileComponent},

View File

@ -26,10 +26,9 @@
</td> </td>
</tr> </tr>
</table> </table>
<button *ngIf="userService.iOwn(group.value)" (click)="numbersService.create(group.value.uuid)">Numbers</button>
</ng-container> </ng-container>
<ng-container *ngIf="uuid && !group.value && accessDenied"> <app-password [visible]="granted === false" (join)="join($event)"></app-password>
<h1>Passwort</h1>
<input type="text" [(ngModel)]="password" (keydown.enter)="join()">
<button (click)="join()">Beitreten</button>
</ng-container>

View File

@ -7,6 +7,8 @@ import {GroupService} from "../../../api/group/group.service";
import {UserService} from "../../../api/User/user.service"; import {UserService} from "../../../api/User/user.service";
import {Group} from "../../../api/group/Group"; import {Group} from "../../../api/group/Group";
import {Subscribed} from "../../../api/Subscribed"; import {Subscribed} from "../../../api/Subscribed";
import {NumbersService} from "../../../api/tools/Numbers/numbers.service";
import {PasswordComponent} from "../../../shared/password/password.component";
@Component({ @Component({
selector: 'app-group', selector: 'app-group',
@ -17,7 +19,8 @@ import {Subscribed} from "../../../api/Subscribed";
NgIf, NgIf,
ReactiveFormsModule, ReactiveFormsModule,
TextComponent, TextComponent,
FormsModule FormsModule,
PasswordComponent
], ],
templateUrl: './group.component.html', templateUrl: './group.component.html',
styleUrl: './group.component.less' styleUrl: './group.component.less'
@ -26,43 +29,36 @@ export class GroupComponent implements OnInit {
protected readonly group: Subscribed<Group>; protected readonly group: Subscribed<Group>;
protected granted: boolean | null = null;
protected uuid: string | null = null; protected uuid: string | null = null;
protected accessDenied: boolean | null = null;
protected password: string = "";
constructor( constructor(
protected readonly router: Router, protected readonly router: Router,
protected readonly activatedRoute: ActivatedRoute, protected readonly activatedRoute: ActivatedRoute,
protected readonly groupService: GroupService, protected readonly groupService: GroupService,
protected readonly userService: UserService, protected readonly userService: UserService,
protected readonly numbersService: NumbersService,
) { ) {
this.group = this.groupService.newSubscriber(); this.group = this.groupService.newSubscriber();
} }
ngOnInit(): void { ngOnInit(): void {
this.password = "";
this.accessDenied = null;
this.activatedRoute.params.subscribe(params => { this.activatedRoute.params.subscribe(params => {
this.uuid = params['uuid']; const uuid = params['uuid'];
if (this.uuid) { this.uuid = uuid;
this.groupService.canAccess(this.uuid, canAccess => { this.granted = null;
this.accessDenied = !canAccess; if (uuid) {
if (canAccess && this.uuid) { this.groupService.canAccess(uuid, granted => {
this.groupService.get(this.uuid, group => this.group.value = group); this.granted = granted;
if (granted) {
this.groupService.get(uuid, group => this.group.value = group);
} }
}); });
} }
}); });
} }
protected join() {
if (this.uuid) {
this.groupService.join(this.uuid, this.password, group => this.group.value = group);
}
}
protected changeTitle(group: Group, title: string) { protected changeTitle(group: Group, title: string) {
this.groupService.changeTitle(group, title, group => this.group.value = group); this.groupService.changeTitle(group, title, group => this.group.value = group);
} }
@ -71,4 +67,10 @@ export class GroupComponent implements OnInit {
this.groupService.changePassword(group, password, group => this.group.value = group); this.groupService.changePassword(group, password, group => this.group.value = group);
} }
protected join(password: string) {
if (this.uuid) {
this.groupService.join(this.uuid, password, group => this.group.value = group);
}
}
} }

View File

@ -7,7 +7,7 @@
</div> </div>
<div class="tileContent"> <div class="tileContent">
<ng-container *ngIf="userService.user"> <ng-container *ngIf="userService.user">
<app-group-list [groups]="userService.user.groups"></app-group-list> <app-group-list [groups]="groups"></app-group-list>
</ng-container> </ng-container>
</div> </div>
<div class="tileFooter"> <div class="tileFooter">

View File

@ -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 {GroupListComponent} from "../shared/group-list/group-list.component";
import {UserService} from "../../../api/User/user.service"; import {UserService} from "../../../api/User/user.service";
import {NgIf} from "@angular/common"; import {NgIf} from "@angular/common";
import {GroupService} from "../../../api/group/group.service"; import {GroupService} from "../../../api/group/group.service";
import {Group} from "../../../api/group/Group";
import {Subscription} from "rxjs";
@Component({ @Component({
selector: 'app-groups', selector: 'app-groups',
@ -14,7 +16,11 @@ import {GroupService} from "../../../api/group/group.service";
templateUrl: './groups.component.html', templateUrl: './groups.component.html',
styleUrl: './groups.component.less' styleUrl: './groups.component.less'
}) })
export class GroupsComponent { export class GroupsComponent implements OnInit, OnDestroy {
private readonly subscriptions: Subscription[] = [];
protected groups: Group[] = [];
constructor( constructor(
protected readonly userService: UserService, 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 { create(): void {
this.groupService.create(group => this.groupService.goto(group)); this.groupService.create(group => this.groupService.goto(group.uuid));
} }
} }

View File

@ -3,7 +3,7 @@
<th>Titel</th> <th>Titel</th>
<th>Teilnehmer</th> <th>Teilnehmer</th>
</tr> </tr>
<tr *ngFor="let group of sorted()" (click)="groupService.goto(group)"> <tr *ngFor="let group of sorted()" (click)="groupService.goto(group.uuid)">
<td class="title">{{ group.title }}</td> <td class="title">{{ group.title }}</td>
<td class="users">{{ group.users.length }}</td> <td class="users">{{ group.users.length }}</td>
</tr> </tr>

View File

@ -30,7 +30,7 @@ export class GroupListComponent {
} }
protected create() { protected create() {
this.groupService.create(group => this.groupService.goto(group)); this.groupService.create(group => this.groupService.goto(group.uuid));
} }
protected sorted(): Group[] { protected sorted(): Group[] {

View File

@ -8,13 +8,15 @@
<td> <td>
<app-text [initial]="userService.user.name" [editable]="true" (onChange)="userService.changeName($event)" [validator]="nameValidator"></app-text> <app-text [initial]="userService.user.name" [editable]="true" (onChange)="userService.changeName($event)" [validator]="nameValidator"></app-text>
</td> </td>
<td class="hint"> </tr>
<tr>
<td class="hint" colspan="2">
Mindestens {{ USER_NAME_MIN_LENGTH }} Zeichen. Keine Leerzeichen. Buchstaben oder Zahlen müssen enthalten sein. Mindestens {{ USER_NAME_MIN_LENGTH }} Zeichen. Keine Leerzeichen. Buchstaben oder Zahlen müssen enthalten sein.
</td> </td>
</tr> </tr>
<tr> <tr>
<th colspan="3">&nbsp;</th> <th colspan="2">&nbsp;</th>
</tr> </tr>
<tr> <tr>
@ -33,9 +35,6 @@
<td> <td>
<input #p0 type="text" [(ngModel)]="password0" [class.passwordInvalid]="p0Invalid()" [class.passwordValid]="p0Valid()" (keydown.enter)="p0Enter()"> <input #p0 type="text" [(ngModel)]="password0" [class.passwordInvalid]="p0Invalid()" [class.passwordValid]="p0Valid()" (keydown.enter)="p0Enter()">
</td> </td>
<td class="hint">
Mindestens {{ USER_PASSWORD_MIN_LENGTH }} Zeichen. Nicht nur Zahlen. Nicht nur Buchstaben.
</td>
</tr> </tr>
<tr> <tr>
<th> <th>
@ -44,17 +43,19 @@
<td> <td>
<input #p1 type="text" [(ngModel)]="password1" [class.passwordInvalid]="p1Invalid()" [class.passwordValid]="p1Valid()" (keydown.enter)="p1Enter()"> <input #p1 type="text" [(ngModel)]="password1" [class.passwordInvalid]="p1Invalid()" [class.passwordValid]="p1Valid()" (keydown.enter)="p1Enter()">
</td> </td>
<td class="hint"> </tr>
&nbsp; <tr>
<td class="hint" colspan="2">
Mindestens {{ USER_PASSWORD_MIN_LENGTH }} Zeichen. Nicht nur Zahlen. Nicht nur Buchstaben.
</td> </td>
</tr> </tr>
<tr> <tr>
<th colspan="3">&nbsp;</th> <th colspan="2">&nbsp;</th>
</tr> </tr>
</table> </table>
<app-group-list [groups]="userService.user.groups"></app-group-list> <app-group-list [groups]="groups"></app-group-list>
</ng-container> </ng-container>

View File

@ -1,3 +1,7 @@
table {
width: 100%;
}
th { th {
text-align: left; text-align: left;
} }
@ -12,6 +16,7 @@ th {
input { input {
-webkit-text-security: disc; -webkit-text-security: disc;
width: 100%;
} }
.passwordInvalid { .passwordInvalid {
@ -21,3 +26,8 @@ input {
.passwordValid { .passwordValid {
background-color: lightgreen; background-color: lightgreen;
} }
.hint {
font-size: 80%;
color: gray;
}

View File

@ -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 {NgForOf, NgIf} from "@angular/common";
import {UserService} from "../../api/User/user.service"; import {UserService} from "../../api/User/user.service";
import {FormsModule} from "@angular/forms"; import {FormsModule} from "@angular/forms";
import {TextComponent} from "../../shared/text/text.component"; import {TextComponent} from "../../shared/text/text.component";
import {GroupListComponent} from "../group/shared/group-list/group-list.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; const USER_NAME_MIN_LENGTH = 2;
@ -22,27 +25,41 @@ const USER_PASSWORD_MIN_LENGTH = 10;
templateUrl: './profile.component.html', templateUrl: './profile.component.html',
styleUrl: './profile.component.less' 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_NAME_MIN_LENGTH = USER_NAME_MIN_LENGTH;
protected readonly USER_PASSWORD_MIN_LENGTH = USER_PASSWORD_MIN_LENGTH; protected readonly USER_PASSWORD_MIN_LENGTH = USER_PASSWORD_MIN_LENGTH;
private readonly subscriptions: Subscription[] = [];
protected password0: string = ""; protected password0: string = "";
protected password1: string = ""; protected password1: string = "";
protected groups: Group[] = [];
@ViewChild('p1') @ViewChild('p1')
p1!: ElementRef; p1!: ElementRef;
constructor( constructor(
protected readonly userService: UserService, 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 { protected nameValidator(name: string): boolean {
console.log(name);
return name.length >= USER_NAME_MIN_LENGTH && !/\s+|^[^a-zA-Z0-9]+$/.test(name); return name.length >= USER_NAME_MIN_LENGTH && !/\s+|^[^a-zA-Z0-9]+$/.test(name);
} }

View File

@ -0,0 +1,20 @@
<div
*ngIf="numbers"
class="numbers"
[class.date_middle]="now.getTime() - numbers.date.getTime() >= 30 * 1000"
[class.date_old]="now.getTime() - numbers.date.getTime() >= 5 * 60 * 1000"
>
<button *ngIf="userService.iOwn(numbers.group)" (click)="numbersService.create(numbers.group.uuid)">
Nächste Runde
</button>
<div class="date">
{{ numbers.date | relative:now }}
</div>
<div class="huge">
{{ numbers.number }}
</div>
</div>

View File

@ -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;
}

View File

@ -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;
}
}
}

View File

@ -1,6 +1,6 @@
<ng-container *ngIf="user !== null"> <ng-container *ngIf="user !== null">
<h2>Benutzer: {{ user.name }}</h2> <h2>Benutzer: {{ user.name }}</h2>
<h3>Gemeinsame Sitzungen</h3> <h3>Gemeinsame Gruppen</h3>
<app-group-list [groups]="user.commonGroups"></app-group-list> <app-group-list [groups]="commonGroups"></app-group-list>
</ng-container> </ng-container>

View File

@ -1,9 +1,11 @@
import {Component, OnInit} from '@angular/core'; import {Component, OnInit} from '@angular/core';
import {NgForOf, NgIf} from "@angular/common"; import {NgForOf, NgIf} from "@angular/common";
import {ActivatedRoute} from "@angular/router"; import {ActivatedRoute} from "@angular/router";
import {UserService} from "../../api/User/user.service";
import {GroupListComponent} from "../group/shared/group-list/group-list.component"; 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({ @Component({
selector: 'app-user', selector: 'app-user',
@ -18,11 +20,14 @@ import {UserCommon} from "../../api/User/UserCommon";
}) })
export class UserComponent implements OnInit { export class UserComponent implements OnInit {
protected user: UserCommon | null = null; protected user: UserPublic | null = null;
protected commonGroups: Group[] = [];
constructor( constructor(
protected readonly activatedRoute: ActivatedRoute, protected readonly activatedRoute: ActivatedRoute,
protected readonly userService: UserService, protected readonly userService: UserService,
protected readonly groupService: GroupService,
) { ) {
// - // -
} }
@ -31,7 +36,8 @@ export class UserComponent implements OnInit {
this.activatedRoute.params.subscribe(params => { this.activatedRoute.params.subscribe(params => {
const publicUuid = params['publicUuid']; const publicUuid = params['publicUuid'];
if (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);
} }
}); });
} }

View File

@ -0,0 +1,5 @@
<ng-container *ngIf="visible">
<h1>Passwort</h1>
<input type="text" [(ngModel)]="password" (keydown.enter)="join.emit(password)">
<button (click)="join.emit(password)">Beitreten</button>
</ng-container>

View File

@ -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<string> = new EventEmitter();
protected password: string = "";
ngOnInit(): void {
this.password = "";
}
}

View File

@ -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'}`
}
}

View File

@ -7,3 +7,7 @@
.invalid { .invalid {
background-color: indianred; background-color: indianred;
} }
input {
width: 100%;
}

View File

@ -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;
}

View File

@ -68,6 +68,7 @@ public class Group implements IWebSocketMessage {
return List.of("Number", uuid); return List.of("Number", uuid);
} }
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean isOwnedBy(@NonNull final User user) { public boolean isOwnedBy(@NonNull final User user) {
return owner.equals(user); return owner.equals(user);
} }

View File

@ -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));
}
}

View File

@ -6,6 +6,8 @@ import lombok.NonNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.Set;
import static de.ph87.tools.UserArgumentResolver.USER_UUID_COOKIE_NAME; import static de.ph87.tools.UserArgumentResolver.USER_UUID_COOKIE_NAME;
@CrossOrigin @CrossOrigin
@ -16,6 +18,8 @@ public class GroupController {
private final GroupService groupService; private final GroupService groupService;
private final GroupAccessService groupAccessService;
@GetMapping("create") @GetMapping("create")
public GroupDto create(@CookieValue(name = USER_UUID_COOKIE_NAME, required = false) @Nullable final String privateUuid, @NonNull final HttpServletResponse response) { public GroupDto create(@CookieValue(name = USER_UUID_COOKIE_NAME, required = false) @Nullable final String privateUuid, @NonNull final HttpServletResponse response) {
return groupService.create(privateUuid, response); return groupService.create(privateUuid, response);
@ -23,7 +27,7 @@ public class GroupController {
@PostMapping("canAccess") @PostMapping("canAccess")
public boolean canAccess(@CookieValue(name = USER_UUID_COOKIE_NAME, required = false) @Nullable final String privateUuid, @NonNull @RequestBody final String groupUuid) { 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") @PostMapping("get")
@ -31,9 +35,21 @@ public class GroupController {
return groupService.get(privateUuid, groupUuid); return groupService.get(privateUuid, groupUuid);
} }
@NonNull
@GetMapping("findAllJoined")
public Set<GroupDto> findAllJoined(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String userUuid) {
return groupService.findAllJoined(userUuid);
}
@NonNull
@PostMapping("findAllCommon")
public Set<GroupDto> findAllCommon(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String userUuid, @NonNull @RequestBody final String targetUuid) {
return groupService.findAllCommon(userUuid, targetUuid);
}
@PostMapping("join") @PostMapping("join")
public GroupDto join(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String privateUuid, @NonNull @RequestBody final GroupJoinRequest 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); return groupService.join(privateUuid, request, response);
} }
@PostMapping("changeTitle") @PostMapping("changeTitle")

View File

@ -1,19 +1,16 @@
package de.ph87.tools.group; package de.ph87.tools.group;
import de.ph87.tools.user.UserPublicDto; import de.ph87.tools.user.UserPublicDto;
import de.ph87.tools.web.IWebSocketMessage;
import lombok.Getter; import lombok.Getter;
import lombok.NonNull; import lombok.NonNull;
import lombok.ToString; import lombok.ToString;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
@Getter @Getter
@ToString @ToString
public class GroupDto implements IWebSocketMessage { public class GroupDto {
@NonNull @NonNull
public final String uuid; public final String uuid;
@ -35,19 +32,14 @@ public class GroupDto implements IWebSocketMessage {
public final boolean initial; 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<UserPublicDto> users) {
this.uuid = group.getUuid(); this.uuid = group.getUuid();
this.title = group.getTitle(); this.title = group.getTitle();
this.created = group.getCreated(); this.created = group.getCreated();
this.password = group.getPassword(); this.password = group.getPassword();
this.owner = owner; this.owner = owner;
this.users = group.getUsers().stream().map(UserPublicDto::new).collect(Collectors.toSet()); this.users = users;
this.initial = group.isInitial(); this.initial = group.isInitial();
} }
@Override
public List<Object> getWebsocketTopic() {
return List.of("Group", uuid);
}
} }

View File

@ -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<GroupDto> findAllByUser(@NonNull final User user) {
return groupRepository.findAllByUsersContains(user).stream().map(this::toDto).collect(Collectors.toSet());
}
@NonNull
public Set<GroupDto> findAllCommonGroups(@NonNull final User a, @NonNull final User b) {
return groupRepository.findAllByUsersContainsAndUsersContains(a, b).stream().map(this::toDto).collect(Collectors.toSet());
}
}

View File

@ -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<Group> 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;
}
}

View File

@ -12,9 +12,6 @@ public interface GroupRepository extends ListCrudRepository<Group, String> {
@NonNull @NonNull
Optional<Group> findByUuid(@NonNull String uuid); Optional<Group> findByUuid(@NonNull String uuid);
@NonNull
Optional<Group> findByUuidAndUsersContains(final @NonNull String groupUuid, @NonNull User user);
@NonNull @NonNull
Set<Group> findAllByUsersContains(@NonNull User user); Set<Group> findAllByUsersContains(@NonNull User user);

View File

@ -1,8 +1,9 @@
package de.ph87.tools.group; package de.ph87.tools.group;
import de.ph87.tools.user.User; 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.UserService;
import de.ph87.tools.user.push.UserPushService;
import jakarta.annotation.Nullable; import jakarta.annotation.Nullable;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.NonNull; import lombok.NonNull;
@ -13,22 +14,23 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException; import org.springframework.web.server.ResponseStatusException;
import java.util.Set;
import java.util.stream.Collectors;
@Slf4j @Slf4j
@Service @Service
@Transactional @Transactional
@RequiredArgsConstructor @RequiredArgsConstructor
public class GroupService { public class GroupService {
private final GroupAccessService groupAccessService;
private final GroupRepository groupRepository; private final GroupRepository groupRepository;
private final UserPushService userPushService;
private final UserService userService; private final UserService userService;
private final GroupMapper groupMapper;
private final UserPublicMapper userPublicMapper;
private final GroupOfUserService groupOfUserService;
@NonNull @NonNull
public GroupDto create(@Nullable final String privateUuid, @NonNull final HttpServletResponse response) { public GroupDto create(@Nullable final String privateUuid, @NonNull final HttpServletResponse response) {
final User user = userService.getUserByPrivateUuidOrElseCreate(privateUuid, response); final User user = userService.getUserByPrivateUuidOrElseCreate(privateUuid, response);
@ -36,22 +38,15 @@ public class GroupService {
return doJoinUnchecked(group, user); 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 @NonNull
public GroupDto get(@NonNull final String privateUuid, @NonNull final String groupUuid) { public GroupDto get(@NonNull final String privateUuid, @NonNull final String groupUuid) {
final GroupOfUserService.GroupOfUser ug = groupOfUserService.accessGroupOfUser(groupUuid, privateUuid); final Access ug = groupAccessService.access(groupUuid, privateUuid);
return groupMapper.toDto(ug.group); return toDto(ug.group);
} }
@NonNull @NonNull
public GroupDto join(@NonNull final String privateUuid, @NonNull final GroupJoinRequest request) { public GroupDto join(@Nullable final String privateUuid, @NonNull final GroupJoinRequest request, @NonNull final HttpServletResponse response) {
final User user = userService.getByPrivateUuidOrThrow(privateUuid); final User user = userService.getUserByPrivateUuidOrElseCreate(privateUuid, response);
final Group group = getByGroupByGroupUuid(request.uuid); final Group group = getByGroupByGroupUuid(request.uuid);
if (!group.getPassword().equals(request.password)) { if (!group.getPassword().equals(request.password)) {
log.error("Wrong password: user={}, group={}", user, group); log.error("Wrong password: user={}, group={}", user, group);
@ -67,27 +62,23 @@ public class GroupService {
@NonNull @NonNull
public GroupDto changeTitle(@NonNull final String privateUuid, @NonNull final GroupChangeTitleRequest request) { 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)) { if (!ug.group.isOwnedBy(ug.user)) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN); throw new ResponseStatusException(HttpStatus.FORBIDDEN);
} }
ug.group.setTitle(request.title); ug.group.setTitle(request.title);
return groupMapper.publish(ug.group); return publish(ug.group);
} }
@NonNull @NonNull
public GroupDto changePassword(@NonNull final String privateUuid, @NonNull final GroupChangePasswordInbound request) { public GroupDto changePassword(@NonNull final String privateUuid, @NonNull final GroupChangePasswordInbound request) {
final User user = userService.getByPrivateUuidOrThrow(privateUuid); final Access access = groupAccessService.accessAsOwner(privateUuid, request.uuid);
final Group group = groupOfUserService.getGroupOfUser(request.uuid, user); access.group.setPassword(request.password);
if (!group.isOwnedBy(user)) { return publish(access.group);
throw new ResponseStatusException(HttpStatus.FORBIDDEN);
}
group.setPassword(request.password);
return groupMapper.publish(group);
} }
public void leave(@NonNull final String privateUuid, @NonNull final String groupUuid) { 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); final Group group = getByGroupByGroupUuid(groupUuid);
doLeaveUnchecked(group, user); doLeaveUnchecked(group, user);
} }
@ -107,9 +98,7 @@ public class GroupService {
group.touch(); group.touch();
user.touch(); user.touch();
log.info("User joined Group: user={}, group={}", user, group); log.info("User joined Group: user={}, group={}", user, group);
final GroupDto groupDto = groupMapper.publish(group); return publish(group);
userPublicMapper.publish(user);
return groupDto;
} }
private void doLeaveUnchecked(final Group group, final User user) { private void doLeaveUnchecked(final Group group, final User user) {
@ -117,8 +106,45 @@ public class GroupService {
group.touch(); group.touch();
user.touch(); user.touch();
log.info("User left Group: user={}, group={}", user, group); log.info("User left Group: user={}, group={}", user, group);
groupMapper.publish(group); publish(group);
userPublicMapper.publish(user); }
/* DTO ------------------------------------------------------------------------------------------ */
@NonNull
public GroupDto toDto(@NonNull final Group group) {
final UserPublicDto owner = new UserPublicDto(group.getOwner());
final Set<UserPublicDto> 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<GroupDto> findAllCommonGroups(@NonNull final User a, @NonNull final User b) {
return groupRepository.findAllByUsersContainsAndUsersContains(a, b).stream().map(this::toDto).collect(Collectors.toSet());
}
@NonNull
public Set<GroupDto> 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<GroupDto> 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);
} }
} }

View File

@ -1,12 +1,16 @@
package de.ph87.tools.tools.numbers; package de.ph87.tools.tools.numbers;
import de.ph87.tools.group.Group; import de.ph87.tools.group.Group;
import de.ph87.tools.user.User;
import de.ph87.tools.user.reference.UserReference;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.NonNull; import lombok.NonNull;
import lombok.ToString; import lombok.ToString;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.UUID; import java.util.UUID;
@Entity @Entity
@ -22,15 +26,20 @@ public class Numbers {
private String uuid = UUID.randomUUID().toString(); private String uuid = UUID.randomUUID().toString();
@NonNull @NonNull
@Column(nullable = false) @ManyToOne(optional = false)
private String name = "Ohne Namen";
@NonNull
@OneToOne
private Group group; private Group group;
public Numbers(@NonNull final Group group) { @NonNull
@Column(nullable = false)
private ZonedDateTime date = ZonedDateTime.now();
@NonNull
@OneToMany(orphanRemoval = true)
private List<UserReference> users;
public Numbers(@NonNull final Group group, @NonNull final List<UserReference> users) {
this.group = group; this.group = group;
this.users = users;
} }
@Override @Override
@ -46,4 +55,18 @@ public class Numbers {
return uuid.hashCode(); 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()));
}
} }

View File

@ -1,14 +1,41 @@
package de.ph87.tools.tools.numbers; package de.ph87.tools.tools.numbers;
import lombok.NonNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RequestMapping;
import static de.ph87.tools.UserArgumentResolver.USER_UUID_COOKIE_NAME;
@Slf4j @Slf4j
@CrossOrigin @CrossOrigin
@RestController
@RequiredArgsConstructor @RequiredArgsConstructor
@RequestMapping("Numbers") @RequestMapping("Numbers")
public class NumbersController { 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);
}
} }

View File

@ -5,23 +5,28 @@ import lombok.Getter;
import lombok.NonNull; import lombok.NonNull;
import lombok.ToString; import lombok.ToString;
import java.time.ZonedDateTime;
@Getter @Getter
@ToString(callSuper = true) @ToString(callSuper = true)
public class NumbersDto { public class NumbersPrivateDto {
@NonNull @NonNull
private final String uuid; private final String uuid;
@NonNull
private final String name;
@NonNull @NonNull
private final GroupDto group; 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.uuid = numbers.getUuid();
this.name = numbers.getName(); this.date = numbers.getDate();
this.group = group; this.group = group;
this.number = number;
} }
} }

View File

@ -0,0 +1,7 @@
package de.ph87.tools.tools.numbers;
import org.springframework.data.repository.ListCrudRepository;
public interface NumbersRepository extends ListCrudRepository<Numbers, String> {
}

View File

@ -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<UserReference> 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;
}
}
}

View File

@ -1,6 +1,6 @@
package de.ph87.tools.user; 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.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.Id; import jakarta.persistence.Id;
@ -9,7 +9,6 @@ import lombok.*;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.List;
import java.util.UUID; import java.util.UUID;
@Entity @Entity
@ -17,7 +16,7 @@ import java.util.UUID;
@ToString @ToString
@NoArgsConstructor @NoArgsConstructor
@Table(name = "`user`") @Table(name = "`user`")
public class User implements IWebSocketMessage { public class User extends IUser {
@Id @Id
@NonNull @NonNull
@ -45,6 +44,7 @@ public class User implements IWebSocketMessage {
private boolean admin = false; private boolean admin = false;
@NonNull @NonNull
@ToString.Exclude
@Column(nullable = false) @Column(nullable = false)
private String password = ""; private String password = "";
@ -56,24 +56,6 @@ public class User implements IWebSocketMessage {
lastAccess = ZonedDateTime.now(); lastAccess = ZonedDateTime.now();
} }
@Override
public List<Object> 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) { public void setPassword(@NonNull final PasswordEncoder passwordEncoder, @NonNull final String password) {
this.password = passwordEncoder.encode(password); this.password = passwordEncoder.encode(password);
} }

View File

@ -21,7 +21,13 @@ public class UserController {
@Nullable @Nullable
@GetMapping("whoAmI") @GetMapping("whoAmI")
public UserPrivateDto whoAmI(@CookieValue(name = USER_UUID_COOKIE_NAME, required = false) @Nullable final String userUuid, @NonNull final HttpServletResponse response) { 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 @Nullable
@ -42,12 +48,6 @@ public class UserController {
return userService.changePassword(userUuid, password); 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") @GetMapping("delete")
public void delete(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String userUuid, @NonNull final HttpServletResponse response) { public void delete(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String userUuid, @NonNull final HttpServletResponse response) {
userService.delete(userUuid, response); userService.delete(userUuid, response);

View File

@ -1,18 +1,19 @@
package de.ph87.tools.user; package de.ph87.tools.user;
import de.ph87.tools.group.GroupDto; import de.ph87.tools.user.reference.IUser;
import de.ph87.tools.web.IWebSocketMessage; import jakarta.annotation.Nullable;
import lombok.Getter; import lombok.Getter;
import lombok.NonNull; import lombok.NonNull;
import lombok.ToString; import lombok.ToString;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.List;
import java.util.Set;
@Getter @Getter
@ToString @ToString
public class UserPrivateDto implements IWebSocketMessage { public class UserPrivateDto extends IUser {
@NonNull
private final String pushType = "UserPrivate";
@NonNull @NonNull
public final String publicUuid; public final String publicUuid;
@ -26,23 +27,22 @@ public class UserPrivateDto implements IWebSocketMessage {
@NonNull @NonNull
public final ZonedDateTime created; public final ZonedDateTime created;
@NonNull
private final Set<GroupDto> groups;
private final boolean password; private final boolean password;
public UserPrivateDto(@NonNull final User user, final @NonNull Set<GroupDto> groups) { public UserPrivateDto(@NonNull final User user) {
this.publicUuid = user.getPublicUuid(); this.publicUuid = user.getPublicUuid();
this.name = user.getName(); this.name = user.getName();
this.privateUuid = user.getPrivateUuid(); this.privateUuid = user.getPrivateUuid();
this.created = user.getCreated(); this.created = user.getCreated();
this.password = !user.getPassword().isEmpty(); this.password = !user.getPassword().isEmpty();
this.groups = groups;
} }
@Override @Nullable
public List<Object> getWebsocketTopic() { public static UserPrivateDto orNull(@Nullable final User user) {
return List.of("User", privateUuid); if (user == null) {
return null;
}
return new UserPrivateDto(user);
} }
} }

View File

@ -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<GroupDto> 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;
}
}

View File

@ -1,15 +1,13 @@
package de.ph87.tools.user; package de.ph87.tools.user;
import de.ph87.tools.web.IWebSocketMessage; import de.ph87.tools.user.reference.IUser;
import lombok.Getter; import lombok.Getter;
import lombok.NonNull; import lombok.NonNull;
import lombok.ToString; import lombok.ToString;
import java.util.List;
@Getter @Getter
@ToString @ToString
public class UserPublicDto implements IWebSocketMessage { public class UserPublicDto extends IUser {
@NonNull @NonNull
public final String publicUuid; public final String publicUuid;
@ -22,9 +20,4 @@ public class UserPublicDto implements IWebSocketMessage {
this.name = user.getName(); this.name = user.getName();
} }
@Override
public List<Object> getWebsocketTopic() {
return List.of("User", publicUuid);
}
} }

View File

@ -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);
}
}

View File

@ -1,7 +1,5 @@
package de.ph87.tools.user; package de.ph87.tools.user;
import de.ph87.tools.group.GroupDto;
import de.ph87.tools.group.GroupMapper;
import jakarta.annotation.Nullable; import jakarta.annotation.Nullable;
import jakarta.servlet.http.Cookie; import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
@ -17,7 +15,6 @@ import org.springframework.web.server.ResponseStatusException;
import java.util.Optional; import java.util.Optional;
import java.util.Random; import java.util.Random;
import java.util.Set;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -42,12 +39,6 @@ public class UserService {
private final UserRepository userRepository; private final UserRepository userRepository;
private final UserPrivateMapper userPrivateMapper;
private final UserPublicMapper userPublicMapper;
private final GroupMapper groupMapper;
private final PasswordEncoder passwordEncoder; private final PasswordEncoder passwordEncoder;
@NonNull @NonNull
@ -58,8 +49,7 @@ public class UserService {
} }
user.touch(); user.touch();
writeUserUuidCookie(response, user); writeUserUuidCookie(response, user);
userPublicMapper.publish(user); return new UserPrivateDto(user);
return userPrivateMapper.publish(user);
} }
@NonNull @NonNull
@ -70,13 +60,13 @@ public class UserService {
} }
@Nullable @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()) { if (privateUuid == null || privateUuid.isEmpty()) {
return null; return null;
} }
final User user = userRepository.findByPrivateUuid(privateUuid).orElse(null); final User user = userRepository.findByPrivateUuid(privateUuid).orElse(null);
writeUserUuidCookie(response, user); writeUserUuidCookie(response, user);
return userPrivateMapper.toPrivateDtoOrNull(user); return UserPrivateDto.orNull(user);
} }
@NonNull @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<GroupDto> commonGroups = groupMapper.findAllCommonGroups(principal, target);
return new UserCommonDto(target, commonGroups);
}
public void delete(@NonNull final String privateUuid, final @NonNull HttpServletResponse response) { public void delete(@NonNull final String privateUuid, final @NonNull HttpServletResponse response) {
final User user = userRepository.findByPrivateUuid(privateUuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST)); final User user = userRepository.findByPrivateUuid(privateUuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST));
deleteUnchecked(response, user); deleteUnchecked(response, user);
@ -145,7 +127,7 @@ public class UserService {
@NonNull @NonNull
private String generateFreeUserName() { private String generateFreeUserName() {
int index = new Random().nextInt(1234, 5723); 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)) { while (userRepository.existsByName(name)) {
index++; index++;
name = "%s #%d".formatted(USER_NAME_DEFAULT_BASE, index); name = "%s #%d".formatted(USER_NAME_DEFAULT_BASE, index);
@ -155,11 +137,9 @@ public class UserService {
@NonNull @NonNull
private UserPrivateDto modify(@NonNull final String privateUuid, @NonNull Consumer<User> modifier) { private UserPrivateDto modify(@NonNull final String privateUuid, @NonNull Consumer<User> modifier) {
final User user = getByPrivateUuidOrThrow(privateUuid); final User user = access(privateUuid);
modifier.accept(user); modifier.accept(user);
userPublicMapper.publish(user); return new UserPrivateDto(user);
userPrivateMapper.publish(user);
return userPrivateMapper.toPrivateDto(user);
} }
private void deleteUnchecked(final HttpServletResponse response, final User user) { private void deleteUnchecked(final HttpServletResponse response, final User user) {
@ -168,10 +148,25 @@ public class UserService {
writeUserUuidCookie(response, null); writeUserUuidCookie(response, null);
} }
/* ACCESS --------------------------------------------------------------------------------------- */
@Nullable
public User accessOrNull(@Nullable final String userPrivateUuid) {
if (userPrivateUuid == null || userPrivateUuid.isEmpty()) {
return null;
}
return access(userPrivateUuid);
}
/* GETTERS & FINDERS ---------------------------------------------------------------------------- */ /* GETTERS & FINDERS ---------------------------------------------------------------------------- */
@NonNull @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)); 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)); return userRepository.findByPublicUuid(publicUuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST));
} }
@NonNull
public Optional<User> findByPrivateUuid(@NonNull final String privateUuid) {
return userRepository.findByPrivateUuid(privateUuid);
}
/* COOKIES -------------------------------------------------------------------------------------- */ /* COOKIES -------------------------------------------------------------------------------------- */
private static void writeUserUuidCookie(@NonNull final HttpServletResponse response, @Nullable final User user) { private static void writeUserUuidCookie(@NonNull final HttpServletResponse response, @Nullable final User user) {

View File

@ -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;
}
}

View File

@ -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<Object> topic = List.of("User", user.getPrivateUuid());
final UserPushMessage message = new UserPushMessage(payload);
webSocketService.send(topic, message);
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -0,0 +1,7 @@
package de.ph87.tools.user.reference;
import org.springframework.data.repository.ListCrudRepository;
public interface UserReferenceRepository extends ListCrudRepository<UserReference, Long> {
}

View File

@ -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));
}
}

View File

@ -7,6 +7,7 @@ import org.springframework.lang.NonNull;
import org.springframework.messaging.simp.SimpMessageSendingOperations; import org.springframework.messaging.simp.SimpMessageSendingOperations;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Slf4j @Slf4j
@ -18,8 +19,12 @@ public class WebSocketService {
@EventListener(IWebSocketMessage.class) @EventListener(IWebSocketMessage.class)
public void send(@NonNull final IWebSocketMessage message) { public void send(@NonNull final IWebSocketMessage message) {
final String topic = message.getWebsocketTopic().stream().map(Object::toString).collect(Collectors.joining("/")); send(message.getWebsocketTopic(), message);
simpMessageSendingOperations.convertAndSend(topic, message); }
public void send(@NonNull final List<Object> topic, @NonNull final Object message) {
final String topicString = topic.stream().map(Object::toString).collect(Collectors.joining("/"));
simpMessageSendingOperations.convertAndSend(topicString, message);
} }
} }