database
This commit is contained in:
parent
76586c720c
commit
8b3cf7bbb9
@ -5,4 +5,4 @@ spring.datasource.driverClassName=org.h2.Driver
|
||||
spring.datasource.username=sa
|
||||
spring.datasource.password=password
|
||||
#-
|
||||
spring.jpa.hibernate.ddl-auto=update
|
||||
#spring.jpa.hibernate.ddl-auto=create
|
||||
|
||||
@ -1,20 +0,0 @@
|
||||
import {UserPublic} from "../User/UserPublic";
|
||||
|
||||
export abstract class AbstractSession {
|
||||
|
||||
protected constructor(
|
||||
readonly type: string,
|
||||
readonly typeDisplayName: string,
|
||||
readonly uuid: string,
|
||||
readonly owner: UserPublic,
|
||||
readonly created: Date,
|
||||
readonly title: string,
|
||||
readonly password: string,
|
||||
readonly users: UserPublic[],
|
||||
readonly initial: boolean,
|
||||
) {
|
||||
// -
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,70 +0,0 @@
|
||||
import {ApiService} from "../common/api.service";
|
||||
import {FromJson, Next} from "../common/types";
|
||||
import {AbstractSession} from "./AbstractSession";
|
||||
import {Router} from "@angular/router";
|
||||
import {UserService} from "../User/user.service";
|
||||
import {validateBoolean} from "../common/validators";
|
||||
|
||||
export abstract class AbstractSessionService<SESSION extends AbstractSession> {
|
||||
|
||||
protected constructor(
|
||||
protected readonly api: ApiService,
|
||||
protected readonly router: Router,
|
||||
protected readonly userService: UserService,
|
||||
readonly apiPath: any[],
|
||||
readonly routerPath: any[],
|
||||
readonly fromJson: FromJson<SESSION>,
|
||||
) {
|
||||
// -
|
||||
}
|
||||
|
||||
canAccess(uuid: string, next: Next<boolean>): void {
|
||||
this.api.postSingle([...this.apiPath, 'canAccess'], uuid, validateBoolean, next);
|
||||
}
|
||||
|
||||
get(uuid: string, next: Next<SESSION>): void {
|
||||
this.api.postSingle([...this.apiPath, 'get'], uuid, this.fromJson, next);
|
||||
}
|
||||
|
||||
create(next: Next<SESSION>): void {
|
||||
this.api.getSingle([...this.apiPath, 'create'], this.fromJson, session => {
|
||||
next(session);
|
||||
this.userService.refresh();
|
||||
});
|
||||
}
|
||||
|
||||
changeTitle(session: SESSION, title: string, next?: Next<SESSION>) {
|
||||
const data = {
|
||||
uuid: session.uuid,
|
||||
title: title,
|
||||
};
|
||||
this.api.postSingle([...this.apiPath, 'changeTitle'], data, this.fromJson, next);
|
||||
}
|
||||
|
||||
changePassword(session: SESSION, password: string, next?: Next<SESSION>) {
|
||||
const data = {
|
||||
uuid: session.uuid,
|
||||
password: password,
|
||||
};
|
||||
this.api.postSingle([...this.apiPath, 'changePassword'], data, this.fromJson, next);
|
||||
}
|
||||
|
||||
join(uuid: string, password: string, next: Next<SESSION>): void {
|
||||
const data = {
|
||||
uuid: uuid,
|
||||
password: password,
|
||||
};
|
||||
this.api.postSingle([...this.apiPath, 'join'], data, this.fromJson, next);
|
||||
}
|
||||
|
||||
leave(session: SESSION, next: Next<void>): void {
|
||||
this.api.postNone([...this.apiPath, 'leave'], session, next);
|
||||
}
|
||||
|
||||
goto(session: SESSION) {
|
||||
const url = '/' + this.routerPath.join('/');
|
||||
this.router.navigate([url, {uuid: session.uuid}]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +0,0 @@
|
||||
import {AbstractSession} from "./AbstractSession";
|
||||
import {validateString} from "../common/validators";
|
||||
import {NumbersSession} from "../tools/Numbers/Session/NumbersSession";
|
||||
|
||||
export function sessionFromJsonOrNull(json: any): AbstractSession | null {
|
||||
const type = validateString(json['type']);
|
||||
switch (type) {
|
||||
case 'Numbers':
|
||||
return NumbersSession.fromJson(json);
|
||||
default:
|
||||
console.error("Not implemented: AbstractSession.type=" + type);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,5 @@
|
||||
import {validateListIgnoreNullItems, validateString} from "../common/validators";
|
||||
import {AbstractSession} from "../Session/AbstractSession";
|
||||
import {sessionFromJsonOrNull} from "../Session/sessionFromJsonOrNull";
|
||||
import {validateList, validateString} from "../common/validators";
|
||||
import {Group} from "../group/Group";
|
||||
import {UserPublic} from "./UserPublic";
|
||||
|
||||
export class UserCommon extends UserPublic {
|
||||
@ -8,7 +7,7 @@ export class UserCommon extends UserPublic {
|
||||
constructor(
|
||||
publicUuid: string,
|
||||
name: string,
|
||||
readonly commonSessions: AbstractSession[],
|
||||
readonly commonGroups: Group[],
|
||||
) {
|
||||
super(publicUuid, name);
|
||||
}
|
||||
@ -17,7 +16,7 @@ export class UserCommon extends UserPublic {
|
||||
return new UserCommon(
|
||||
validateString(json['publicUuid']),
|
||||
validateString(json['name']),
|
||||
validateListIgnoreNullItems(json['commonSessions'], sessionFromJsonOrNull),
|
||||
validateList(json['commonGroups'], Group.fromJson),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
import {validateDate, validateListIgnoreNullItems, validateString} from "../common/validators";
|
||||
import {AbstractSession} from "../Session/AbstractSession";
|
||||
|
||||
import {sessionFromJsonOrNull} from "../Session/sessionFromJsonOrNull";
|
||||
import {validateDate, validateList, validateString} from "../common/validators";
|
||||
import {Group} from "../group/Group";
|
||||
|
||||
export class UserPrivate {
|
||||
|
||||
@ -9,7 +7,7 @@ export class UserPrivate {
|
||||
readonly privateUuid: string,
|
||||
readonly publicUuid: string,
|
||||
readonly created: Date,
|
||||
readonly sessions: AbstractSession[],
|
||||
readonly groups: Group[],
|
||||
readonly name: string,
|
||||
) {
|
||||
// -
|
||||
@ -20,7 +18,7 @@ export class UserPrivate {
|
||||
validateString(json['privateUuid']),
|
||||
validateString(json['publicUuid']),
|
||||
validateDate(json['created']),
|
||||
validateListIgnoreNullItems(json['sessions'], sessionFromJsonOrNull),
|
||||
validateList(json['groups'], Group.fromJson),
|
||||
validateString(json['name']),
|
||||
);
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ import {UserCommon} from "./UserCommon";
|
||||
import {UserPublic} from "./UserPublic";
|
||||
import {Router} from "@angular/router";
|
||||
import {BehaviorSubject, Subscription} from "rxjs";
|
||||
import {AbstractSession} from "../Session/AbstractSession";
|
||||
import {Group} from "../group/Group";
|
||||
import {StompService} from "@stomp/ng2-stompjs";
|
||||
|
||||
@Injectable({
|
||||
@ -60,8 +60,7 @@ export class UserService {
|
||||
return this.subject.subscribe(next);
|
||||
}
|
||||
|
||||
owns(session: AbstractSession): boolean {
|
||||
return this.user?.publicUuid === session.owner.publicUuid;
|
||||
owns(group: Group): boolean {
|
||||
return this.user?.publicUuid === group.owner.publicUuid;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
35
src/main/angular/src/app/api/group/Group.ts
Normal file
35
src/main/angular/src/app/api/group/Group.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import {UserPublic} from "../User/UserPublic";
|
||||
import {validateBoolean, validateDate, validateList, validateString} from "../common/validators";
|
||||
|
||||
export class Group {
|
||||
|
||||
protected constructor(
|
||||
readonly uuid: string,
|
||||
readonly owner: UserPublic,
|
||||
readonly created: Date,
|
||||
readonly title: string,
|
||||
readonly password: string,
|
||||
readonly users: UserPublic[],
|
||||
readonly initial: boolean,
|
||||
) {
|
||||
// -
|
||||
}
|
||||
|
||||
static fromJson(json: any): Group {
|
||||
return new Group(
|
||||
validateString(json['uuid']),
|
||||
UserPublic.fromJson(json['owner']),
|
||||
validateDate(json['created']),
|
||||
validateString(json['title']),
|
||||
validateString(json['password']),
|
||||
validateList(json['users'], UserPublic.fromJson),
|
||||
validateBoolean(json['initial']),
|
||||
);
|
||||
}
|
||||
|
||||
static compareCreated(a: Group, b: Group): number {
|
||||
return a.created.getTime() - b.created.getTime();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
70
src/main/angular/src/app/api/group/GroupService.ts
Normal file
70
src/main/angular/src/app/api/group/GroupService.ts
Normal file
@ -0,0 +1,70 @@
|
||||
import {ApiService} from "../common/api.service";
|
||||
import {Next} from "../common/types";
|
||||
import {Group} from "./Group";
|
||||
import {Router} from "@angular/router";
|
||||
import {UserService} from "../User/user.service";
|
||||
import {validateBoolean} from "../common/validators";
|
||||
import {Injectable} from "@angular/core";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class GroupService {
|
||||
|
||||
protected constructor(
|
||||
protected readonly api: ApiService,
|
||||
protected readonly router: Router,
|
||||
protected readonly userService: UserService,
|
||||
) {
|
||||
// -
|
||||
}
|
||||
|
||||
canAccess(uuid: string, next: Next<boolean>): void {
|
||||
this.api.postSingle(['Group', 'canAccess'], uuid, validateBoolean, next);
|
||||
}
|
||||
|
||||
get(uuid: string, next: Next<Group>): void {
|
||||
this.api.postSingle(['Group', 'get'], uuid, Group.fromJson, next);
|
||||
}
|
||||
|
||||
create(next: Next<Group>): void {
|
||||
this.api.getSingle(['Group', 'create'], Group.fromJson, group => {
|
||||
next(group);
|
||||
this.userService.refresh();
|
||||
});
|
||||
}
|
||||
|
||||
changeTitle(group: Group, title: string, next?: Next<Group>) {
|
||||
const data = {
|
||||
uuid: group.uuid,
|
||||
title: title,
|
||||
};
|
||||
this.api.postSingle(['Group', 'changeTitle'], data, Group.fromJson, next);
|
||||
}
|
||||
|
||||
changePassword(group: Group, password: string, next?: Next<Group>) {
|
||||
const data = {
|
||||
uuid: group.uuid,
|
||||
password: password,
|
||||
};
|
||||
this.api.postSingle(['Group', 'changePassword'], data, Group.fromJson, next);
|
||||
}
|
||||
|
||||
join(uuid: string, password: string, next: Next<Group>): void {
|
||||
const data = {
|
||||
uuid: uuid,
|
||||
password: password,
|
||||
};
|
||||
this.api.postSingle(['Group', 'join'], data, Group.fromJson, next);
|
||||
}
|
||||
|
||||
leave(group: Group, next: Next<void>): void {
|
||||
this.api.postNone(['Group', 'leave'], group, next);
|
||||
}
|
||||
|
||||
goto(group: Group) {
|
||||
this.router.navigate(['Group', {uuid: group.uuid}]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
30
src/main/angular/src/app/api/tools/Numbers/Numbers.ts
Normal file
30
src/main/angular/src/app/api/tools/Numbers/Numbers.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import {UserPublic} from "../../User/UserPublic";
|
||||
import {validateBoolean, validateDate, validateList, validateString} from "../../common/validators";
|
||||
|
||||
export class Numbers {
|
||||
|
||||
constructor(
|
||||
readonly uuid: string,
|
||||
readonly title: string,
|
||||
readonly owner: UserPublic,
|
||||
readonly created: Date,
|
||||
readonly password: string,
|
||||
readonly users: UserPublic[],
|
||||
readonly initial: boolean,
|
||||
) {
|
||||
// -
|
||||
}
|
||||
|
||||
static fromJson(json: any): Numbers {
|
||||
return new Numbers(
|
||||
validateString(json['uuid']),
|
||||
validateString(json['title']),
|
||||
UserPublic.fromJson(json['owner']),
|
||||
validateDate(json['created']),
|
||||
validateString(json['password']),
|
||||
validateList(json['users'], UserPublic.fromJson),
|
||||
validateBoolean(json['initial']),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
import {UserPublic} from "../../../User/UserPublic";
|
||||
import {validateBoolean, validateDate, validateList, validateString} from "../../../common/validators";
|
||||
import {AbstractSession} from "../../../Session/AbstractSession";
|
||||
|
||||
export class NumbersSession extends AbstractSession {
|
||||
|
||||
static readonly TYPE = 'Numbers';
|
||||
|
||||
static readonly TYPE_DISPLAY_NAME = 'Nummern';
|
||||
|
||||
constructor(
|
||||
type: string,
|
||||
uuid: string,
|
||||
title: string,
|
||||
owner: UserPublic,
|
||||
created: Date,
|
||||
password: string,
|
||||
users: UserPublic[],
|
||||
initial: boolean,
|
||||
) {
|
||||
if (type !== NumbersSession.TYPE) {
|
||||
throw new Error();
|
||||
}
|
||||
super(NumbersSession.TYPE, NumbersSession.TYPE_DISPLAY_NAME, uuid, owner, created, title, password, users, initial);
|
||||
}
|
||||
|
||||
static fromJson(json: any): NumbersSession {
|
||||
return new NumbersSession(
|
||||
validateString(json['type']),
|
||||
validateString(json['uuid']),
|
||||
validateString(json['title']),
|
||||
UserPublic.fromJson(json['owner']),
|
||||
validateDate(json['created']),
|
||||
validateString(json['password']),
|
||||
validateList(json['users'], UserPublic.fromJson),
|
||||
validateBoolean(json['initial']),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,21 +0,0 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {AbstractSessionService} from "../../../Session/AbstractSessionService";
|
||||
import {NumbersSession} from "./NumbersSession";
|
||||
import {ApiService} from "../../../common/api.service";
|
||||
import {Router} from "@angular/router";
|
||||
import {UserService} from "../../../User/user.service";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class NumbersSessionService extends AbstractSessionService<NumbersSession> {
|
||||
|
||||
constructor(
|
||||
api: ApiService,
|
||||
router: Router,
|
||||
userService: UserService
|
||||
) {
|
||||
super(api, router, userService, ['Numbers'], ['Numbers', 'Session'], NumbersSession.fromJson);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {ApiService} from "../../common/api.service";
|
||||
import {Router} from "@angular/router";
|
||||
import {UserService} from "../../User/user.service";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class NumbersService {
|
||||
|
||||
constructor(
|
||||
protected readonly api: ApiService,
|
||||
protected readonly router: Router,
|
||||
protected readonly userService: UserService
|
||||
) {
|
||||
// -
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
<div id="mainMenu" *ngIf="menuVisible">
|
||||
<div class="mainMenuItem" routerLinkActive="mainMenuItemActive" routerLink="/SolarSystem">Planeten</div>
|
||||
<div class="mainMenuItem" routerLinkActive="mainMenuItemActive" routerLink="/VoltageDrop">Leitung</div>
|
||||
<div class="mainMenuItem" routerLinkActive="mainMenuItemActive" routerLink="/Numbers">{{ NumbersSession.TYPE_DISPLAY_NAME }}</div>
|
||||
<div class="mainMenuItem" routerLinkActive="mainMenuItemActive" routerLink="/Groups">Gruppen</div>
|
||||
<ng-container *ngIf="user !== null">
|
||||
<div class="mainMenuItem mainMenuItemRight" routerLinkActive="mainMenuItemActive" routerLink="/Profile">{{ user.name }}</div>
|
||||
</ng-container>
|
||||
|
||||
@ -2,7 +2,7 @@ import {Component, OnDestroy, OnInit} from '@angular/core';
|
||||
import {Router, RouterLink, RouterLinkActive, RouterOutlet} from '@angular/router';
|
||||
import {NgIf} from "@angular/common";
|
||||
import {UserService} from "./api/User/user.service";
|
||||
import {NumbersSession} from "./api/tools/Numbers/Session/NumbersSession";
|
||||
import {Numbers} from "./api/tools/Numbers/Numbers";
|
||||
import {UserPrivate} from "./api/User/UserPrivate";
|
||||
import {Subscription} from "rxjs";
|
||||
|
||||
@ -15,7 +15,7 @@ import {Subscription} from "rxjs";
|
||||
})
|
||||
export class AppComponent implements OnInit, OnDestroy {
|
||||
|
||||
protected readonly NumbersSession = NumbersSession;
|
||||
protected readonly NumbersGroup = Numbers;
|
||||
|
||||
private readonly subscriptions: Subscription[] = [];
|
||||
|
||||
|
||||
@ -2,10 +2,10 @@ import {Routes} from '@angular/router';
|
||||
import {SolarSystemComponent} from './pages/tools/solar-system/solar-system.component';
|
||||
import {VoltageDropComponent} from "./pages/tools/voltage-drop/voltage-drop.component";
|
||||
import {SolarSystemPrintoutComponent} from "./pages/tools/solar-system/printout/solar-system-printout.component";
|
||||
import {NumbersOverviewComponent} from "./pages/tools/numbers/overview/numbers-overview.component";
|
||||
import {ProfileComponent} from "./pages/profile/profile.component";
|
||||
import {NumbersSessionComponent} from "./pages/tools/numbers/session/numbers-session.component";
|
||||
import {UserComponent} from "./pages/user/user.component";
|
||||
import {GroupsComponent} from "./pages/group/groups/groups.component";
|
||||
import {GroupComponent} from "./pages/group/group/group.component";
|
||||
|
||||
export const routes: Routes = [
|
||||
{path: 'SolarSystemPrintout', component: SolarSystemPrintoutComponent},
|
||||
@ -13,8 +13,8 @@ export const routes: Routes = [
|
||||
|
||||
{path: 'VoltageDrop', component: VoltageDropComponent},
|
||||
|
||||
{path: 'Numbers', component: NumbersOverviewComponent},
|
||||
{path: 'Numbers/Session', component: NumbersSessionComponent},
|
||||
{path: 'Groups', component: GroupsComponent},
|
||||
{path: 'Group', component: GroupComponent},
|
||||
|
||||
{path: 'User', component: UserComponent},
|
||||
|
||||
|
||||
@ -0,0 +1,38 @@
|
||||
<ng-container *ngIf="group !== null">
|
||||
<h1>Nummern</h1>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Erstellt</th>
|
||||
<td>{{ group.created | date:'yyyy-MM-dd HH:mm' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Titel</th>
|
||||
<td>
|
||||
<app-text *ngIf="userService.owns(group)" [initial]="group.title" (onChange)="changeTitle(group, $event)"></app-text>
|
||||
<ng-container *ngIf="!userService.owns(group)"></ng-container>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Passwort</th>
|
||||
<td>
|
||||
<app-text *ngIf="userService.owns(group)" [initial]="group.password" (onChange)="changePassword(group, $event)"></app-text>
|
||||
<ng-container *ngIf="!userService.owns(group)"></ng-container>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Besitzer</th>
|
||||
<td class="user" (click)="userService.goto(group.owner)">{{ group.owner.name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Teilnehmer</th>
|
||||
<td>
|
||||
<div class="user" *ngFor="let user of group.users" (click)="userService.goto(user)">{{ user.name }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="uuid && !group && accessDenied">
|
||||
<h1>Passwort</h1>
|
||||
<input type="text" [(ngModel)]="password" (keydown.enter)="join()">
|
||||
</ng-container>
|
||||
@ -0,0 +1 @@
|
||||
@import "../../../../common.less";
|
||||
@ -0,0 +1,77 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {DatePipe, NgForOf, NgIf} from "@angular/common";
|
||||
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
|
||||
import {TextComponent} from "../../../shared/text/text.component";
|
||||
import {Numbers} from "../../../api/tools/Numbers/Numbers";
|
||||
import {ActivatedRoute, Router} from "@angular/router";
|
||||
import {GroupService} from "../../../api/group/GroupService";
|
||||
import {UserService} from "../../../api/User/user.service";
|
||||
|
||||
@Component({
|
||||
selector: 'app-group',
|
||||
standalone: true,
|
||||
imports: [
|
||||
DatePipe,
|
||||
NgForOf,
|
||||
NgIf,
|
||||
ReactiveFormsModule,
|
||||
TextComponent,
|
||||
FormsModule
|
||||
],
|
||||
templateUrl: './group.component.html',
|
||||
styleUrl: './group.component.less'
|
||||
})
|
||||
export class GroupComponent implements OnInit {
|
||||
|
||||
protected group: Numbers | null = null;
|
||||
|
||||
protected uuid: string | null = null;
|
||||
|
||||
protected accessDenied: boolean | null = null;
|
||||
|
||||
protected password: string = "";
|
||||
|
||||
constructor(
|
||||
protected readonly router: Router,
|
||||
protected readonly activatedRoute: ActivatedRoute,
|
||||
protected readonly groupService: GroupService,
|
||||
protected readonly userService: UserService,
|
||||
) {
|
||||
// -
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.password = "";
|
||||
this.accessDenied = null;
|
||||
this.activatedRoute.params.subscribe(params => {
|
||||
this.uuid = params['uuid'];
|
||||
if (this.uuid) {
|
||||
this.groupService.canAccess(this.uuid, canAccess => {
|
||||
this.accessDenied = !canAccess;
|
||||
if (canAccess && this.uuid) {
|
||||
this.groupService.get(this.uuid, group => this.setGroup(group));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private setGroup(group: Numbers): void {
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
protected join() {
|
||||
if (this.uuid) {
|
||||
this.groupService.join(this.uuid, this.password, group => this.setGroup(group));
|
||||
}
|
||||
}
|
||||
|
||||
protected changeTitle(group: Numbers, title: string) {
|
||||
this.groupService.changeTitle(group, title, group => this.setGroup(group));
|
||||
}
|
||||
|
||||
protected changePassword(group: Numbers, password: string) {
|
||||
this.groupService.changePassword(group, password, group => this.setGroup(group));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
<div class="tileContainer">
|
||||
|
||||
<div class="tile">
|
||||
<div class="tileInner">
|
||||
<div class="tileTitle">
|
||||
Gruppen
|
||||
</div>
|
||||
<div class="tileContent">
|
||||
<ng-container *ngIf="userService.user">
|
||||
<app-group-list [groups]="userService.user.groups"></app-group-list>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="tileFooter">
|
||||
<button (click)="create()">+ Neue Gruppen erstellen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@ -0,0 +1 @@
|
||||
@import "../../../../common.less";
|
||||
@ -0,0 +1,34 @@
|
||||
import {Component, 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/GroupService";
|
||||
|
||||
@Component({
|
||||
selector: 'app-groups',
|
||||
standalone: true,
|
||||
imports: [
|
||||
GroupListComponent,
|
||||
NgIf
|
||||
],
|
||||
templateUrl: './groups.component.html',
|
||||
styleUrl: './groups.component.less'
|
||||
})
|
||||
export class GroupsComponent implements OnInit {
|
||||
|
||||
constructor(
|
||||
protected readonly userService: UserService,
|
||||
protected readonly groupService: GroupService,
|
||||
) {
|
||||
// -
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.userService.refresh();
|
||||
}
|
||||
|
||||
create(): void {
|
||||
this.groupService.create(group => this.groupService.goto(group));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
<table>
|
||||
<tr>
|
||||
<th>Titel</th>
|
||||
<th>Teilnehmer</th>
|
||||
</tr>
|
||||
<tr *ngFor="let group of sorted()" (click)="groupService.goto(group)">
|
||||
<td class="title">{{ group.title }}</td>
|
||||
<td class="users">{{ group.users.length }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
@ -1,4 +1,4 @@
|
||||
@import "../../../../../../common.less";
|
||||
@import "../../../../../common.less";
|
||||
|
||||
td {
|
||||
border: 1px solid black;
|
||||
@ -0,0 +1,40 @@
|
||||
import {Component, Input} from '@angular/core';
|
||||
import {UserService} from "../../../../api/User/user.service";
|
||||
import {Group} from "../../../../api/group/Group";
|
||||
import {DatePipe, NgForOf} from "@angular/common";
|
||||
import {GroupService} from "../../../../api/group/GroupService";
|
||||
|
||||
@Component({
|
||||
selector: 'app-group-list',
|
||||
standalone: true,
|
||||
imports: [
|
||||
DatePipe,
|
||||
NgForOf
|
||||
],
|
||||
templateUrl: './group-list.component.html',
|
||||
styleUrl: './group-list.component.less'
|
||||
})
|
||||
export class GroupListComponent {
|
||||
|
||||
@Input()
|
||||
groups!: Group[];
|
||||
|
||||
@Input()
|
||||
compare: (a: Group, b: Group) => number = Group.compareCreated;
|
||||
|
||||
constructor(
|
||||
protected readonly userService: UserService,
|
||||
protected readonly groupService: GroupService,
|
||||
) {
|
||||
// -
|
||||
}
|
||||
|
||||
protected create() {
|
||||
this.groupService.create(group => this.groupService.goto(group));
|
||||
}
|
||||
|
||||
protected sorted(): Group[] {
|
||||
return this.groups.sort(this.compare);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
<ng-container *ngIf="userService.user !== null">
|
||||
<h1>Profil</h1>
|
||||
<app-text [initial]="userService.user.name" (onChange)="userService.changeName($event)"></app-text>
|
||||
<app-session-list [sessions]="userService.user.sessions"></app-session-list>
|
||||
<app-group-list [groups]="userService.user.groups"></app-group-list>
|
||||
</ng-container>
|
||||
|
||||
@ -3,7 +3,7 @@ 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 {SessionListComponent} from "../tools/numbers/shared/session-list/session-list.component";
|
||||
import {GroupListComponent} from "../group/shared/group-list/group-list.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-profile',
|
||||
@ -13,7 +13,7 @@ import {SessionListComponent} from "../tools/numbers/shared/session-list/session
|
||||
NgIf,
|
||||
FormsModule,
|
||||
TextComponent,
|
||||
SessionListComponent
|
||||
GroupListComponent
|
||||
],
|
||||
templateUrl: './profile.component.html',
|
||||
styleUrl: './profile.component.less'
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
<div class="tileContainer">
|
||||
|
||||
<div class="tile" *ngIf="userService.user">
|
||||
<div class="tileInner">
|
||||
<div class="tileTitle">
|
||||
Teilnahmen
|
||||
</div>
|
||||
<div class="tileContent">
|
||||
<app-session-list [sessions]="userService.user.sessions"></app-session-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tile">
|
||||
<div class="tileInner">
|
||||
<div class="tileTitle">
|
||||
Neues Spiel erstellen
|
||||
</div>
|
||||
<div class="tileContent">
|
||||
<button (click)="create()">+ Neu</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@ -1 +0,0 @@
|
||||
@import "../../../../../common.less";
|
||||
@ -1,36 +0,0 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {UserService} from "../../../../api/User/user.service";
|
||||
import {DatePipe, NgForOf, NgIf} from "@angular/common";
|
||||
import {NumbersSessionService} from "../../../../api/tools/Numbers/Session/numbers-session.service";
|
||||
import {SessionListComponent} from "../shared/session-list/session-list.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-numbers-overview',
|
||||
standalone: true,
|
||||
imports: [
|
||||
NgForOf,
|
||||
NgIf,
|
||||
DatePipe,
|
||||
SessionListComponent
|
||||
],
|
||||
templateUrl: './numbers-overview.component.html',
|
||||
styleUrl: './numbers-overview.component.less'
|
||||
})
|
||||
export class NumbersOverviewComponent implements OnInit {
|
||||
|
||||
constructor(
|
||||
protected readonly userService: UserService,
|
||||
protected readonly numberSessionService: NumbersSessionService,
|
||||
) {
|
||||
// -
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.userService.refresh();
|
||||
}
|
||||
|
||||
create() {
|
||||
this.numberSessionService.create(session => this.numberSessionService.goto(session));
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,38 +0,0 @@
|
||||
<ng-container *ngIf="session !== null">
|
||||
<h1>{{ session.typeDisplayName }}</h1>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Erstellt</th>
|
||||
<td>{{ session.created | date:'yyyy-MM-dd HH:mm' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Titel</th>
|
||||
<td>
|
||||
<app-text *ngIf="userService.owns(session)" [initial]="session.title" (onChange)="changeTitle(session, $event)"></app-text>
|
||||
<ng-container *ngIf="!userService.owns(session)"></ng-container>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Passwort</th>
|
||||
<td>
|
||||
<app-text *ngIf="userService.owns(session)" [initial]="session.password" (onChange)="changePassword(session, $event)"></app-text>
|
||||
<ng-container *ngIf="!userService.owns(session)"></ng-container>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Besitzer</th>
|
||||
<td class="user" (click)="userService.goto(session.owner)">{{ session.owner.name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Teilnehmer</th>
|
||||
<td>
|
||||
<div class="user" *ngFor="let user of session.users" (click)="userService.goto(user)">{{ user.name }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="uuid && !session && accessDenied">
|
||||
<h1>Passwort</h1>
|
||||
<input type="text" [(ngModel)]="password" (keydown.enter)="join()">
|
||||
</ng-container>
|
||||
@ -1 +0,0 @@
|
||||
@import "../../../../../common.less";
|
||||
@ -1,76 +0,0 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {ActivatedRoute, Router} from "@angular/router";
|
||||
import {NumbersSession} from "../../../../api/tools/Numbers/Session/NumbersSession";
|
||||
import {DatePipe, NgForOf, NgIf} from "@angular/common";
|
||||
import {UserService} from "../../../../api/User/user.service";
|
||||
import {NumbersSessionService} from "../../../../api/tools/Numbers/Session/numbers-session.service";
|
||||
import {FormsModule} from "@angular/forms";
|
||||
import {TextComponent} from "../../../../shared/text/text.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-numbers-session',
|
||||
standalone: true,
|
||||
imports: [
|
||||
NgForOf,
|
||||
NgIf,
|
||||
DatePipe,
|
||||
FormsModule,
|
||||
TextComponent
|
||||
],
|
||||
templateUrl: './numbers-session.component.html',
|
||||
styleUrl: './numbers-session.component.less'
|
||||
})
|
||||
export class NumbersSessionComponent implements OnInit {
|
||||
|
||||
protected session: NumbersSession | null = null;
|
||||
|
||||
protected uuid: string | null = null;
|
||||
|
||||
protected accessDenied: boolean | null = null;
|
||||
|
||||
protected password: string = "";
|
||||
|
||||
constructor(
|
||||
protected readonly router: Router,
|
||||
protected readonly activatedRoute: ActivatedRoute,
|
||||
protected readonly numbersSessionService: NumbersSessionService,
|
||||
protected readonly userService: UserService,
|
||||
) {
|
||||
// -
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.password = "";
|
||||
this.accessDenied = null;
|
||||
this.activatedRoute.params.subscribe(params => {
|
||||
this.uuid = params['uuid'];
|
||||
if (this.uuid) {
|
||||
this.numbersSessionService.canAccess(this.uuid, canAccess => {
|
||||
this.accessDenied = !canAccess;
|
||||
if (canAccess && this.uuid) {
|
||||
this.numbersSessionService.get(this.uuid, session => this.setSession(session));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private setSession(session: NumbersSession): void {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
protected join() {
|
||||
if (this.uuid) {
|
||||
this.numbersSessionService.join(this.uuid, this.password, session => this.setSession(session));
|
||||
}
|
||||
}
|
||||
|
||||
protected changeTitle(session: NumbersSession, title: string) {
|
||||
this.numbersSessionService.changeTitle(session, title, session => this.setSession(session));
|
||||
}
|
||||
|
||||
protected changePassword(session: NumbersSession, password: string) {
|
||||
this.numbersSessionService.changePassword(session, password, session => this.setSession(session));
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
<table>
|
||||
<tr>
|
||||
<th>Titel</th>
|
||||
<th>Teilnehmer</th>
|
||||
</tr>
|
||||
<tr *ngFor="let session of sessions" (click)="numbersSessionService.goto(session)">
|
||||
<td class="title">{{ session.title }}</td>
|
||||
<td class="users">{{ session.users.length }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
@ -1,32 +0,0 @@
|
||||
import {Component, Input} from '@angular/core';
|
||||
import {UserService} from "../../../../../api/User/user.service";
|
||||
import {NumbersSessionService} from "../../../../../api/tools/Numbers/Session/numbers-session.service";
|
||||
import {AbstractSession} from "../../../../../api/Session/AbstractSession";
|
||||
import {DatePipe, NgForOf} from "@angular/common";
|
||||
|
||||
@Component({
|
||||
selector: 'app-session-list',
|
||||
standalone: true,
|
||||
imports: [
|
||||
DatePipe,
|
||||
NgForOf
|
||||
],
|
||||
templateUrl: './session-list.component.html',
|
||||
styleUrl: './session-list.component.less'
|
||||
})
|
||||
export class SessionListComponent {
|
||||
|
||||
@Input() sessions!: AbstractSession[];
|
||||
|
||||
constructor(
|
||||
protected readonly userService: UserService,
|
||||
protected readonly numbersSessionService: NumbersSessionService,
|
||||
) {
|
||||
// -
|
||||
}
|
||||
|
||||
create() {
|
||||
this.numbersSessionService.create(session => this.numbersSessionService.goto(session));
|
||||
}
|
||||
|
||||
}
|
||||
@ -2,5 +2,5 @@
|
||||
<h2>Benutzer: {{ user.name }}</h2>
|
||||
|
||||
<h3>Gemeinsame Sitzungen</h3>
|
||||
<app-session-list [sessions]="user.commonSessions"></app-session-list>
|
||||
<app-group-list [groups]="user.commonGroups"></app-group-list>
|
||||
</ng-container>
|
||||
|
||||
@ -2,7 +2,7 @@ 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 {SessionListComponent} from "../tools/numbers/shared/session-list/session-list.component";
|
||||
import {GroupListComponent} from "../group/shared/group-list/group-list.component";
|
||||
import {UserCommon} from "../../api/User/UserCommon";
|
||||
|
||||
@Component({
|
||||
@ -11,7 +11,7 @@ import {UserCommon} from "../../api/User/UserCommon";
|
||||
imports: [
|
||||
NgForOf,
|
||||
NgIf,
|
||||
SessionListComponent
|
||||
GroupListComponent
|
||||
],
|
||||
templateUrl: './user.component.html',
|
||||
styleUrl: './user.component.less'
|
||||
|
||||
@ -31,6 +31,11 @@
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.tileFooter {
|
||||
padding: @halfSpace @space;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package de.ph87.tools.session;
|
||||
package de.ph87.tools.group;
|
||||
|
||||
import de.ph87.tools.user.User;
|
||||
import de.ph87.tools.web.IWebSocketMessage;
|
||||
@ -15,7 +15,8 @@ import java.util.UUID;
|
||||
@Getter
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
public abstract class AbstractSession implements IWebSocketMessage {
|
||||
@Table(name = "`group`")
|
||||
public class Group implements IWebSocketMessage {
|
||||
|
||||
@Id
|
||||
@NonNull
|
||||
@ -38,7 +39,7 @@ public abstract class AbstractSession implements IWebSocketMessage {
|
||||
@Setter
|
||||
@NonNull
|
||||
@Column(nullable = false)
|
||||
private String title = "Spiel ohne Namen";
|
||||
private String title = "Ohne Namen";
|
||||
|
||||
@Setter
|
||||
@NonNull
|
||||
@ -54,20 +55,11 @@ public abstract class AbstractSession implements IWebSocketMessage {
|
||||
@Column(nullable = false)
|
||||
private ZonedDateTime lastAccess = created;
|
||||
|
||||
protected AbstractSession(@NonNull final User user) {
|
||||
this.owner = user;
|
||||
protected Group(@NonNull final User owner) {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public void join(@NonNull final User user) {
|
||||
users.add(user);
|
||||
touch();
|
||||
}
|
||||
|
||||
public void leave(@NonNull final User user) {
|
||||
users.remove(user);
|
||||
}
|
||||
|
||||
private void touch() {
|
||||
public void touch() {
|
||||
lastAccess = ZonedDateTime.now();
|
||||
}
|
||||
|
||||
@ -82,10 +74,10 @@ public abstract class AbstractSession implements IWebSocketMessage {
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (!(obj instanceof final AbstractSession session)) {
|
||||
if (!(obj instanceof final Group group)) {
|
||||
return false;
|
||||
}
|
||||
return session.uuid.equals(this.uuid);
|
||||
return group.uuid.equals(this.uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1,4 +1,4 @@
|
||||
package de.ph87.tools.session;
|
||||
package de.ph87.tools.group;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
@ -8,7 +8,7 @@ import lombok.ToString;
|
||||
@Getter
|
||||
@ToString
|
||||
@AllArgsConstructor
|
||||
public class SessionChangePasswordInbound {
|
||||
public class GroupChangePasswordInbound {
|
||||
|
||||
@NonNull
|
||||
public final String uuid;
|
||||
@ -1,4 +1,4 @@
|
||||
package de.ph87.tools.session;
|
||||
package de.ph87.tools.group;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
@ -8,7 +8,7 @@ import lombok.ToString;
|
||||
@Getter
|
||||
@ToString
|
||||
@AllArgsConstructor
|
||||
public class SessionChangeTitleInbound {
|
||||
public class GroupChangeTitleRequest {
|
||||
|
||||
@NonNull
|
||||
public final String uuid;
|
||||
54
src/main/java/de/ph87/tools/group/GroupController.java
Normal file
54
src/main/java/de/ph87/tools/group/GroupController.java
Normal file
@ -0,0 +1,54 @@
|
||||
package de.ph87.tools.group;
|
||||
|
||||
import jakarta.annotation.Nullable;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import static de.ph87.tools.UserArgumentResolver.USER_UUID_COOKIE_NAME;
|
||||
|
||||
@CrossOrigin
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("Group")
|
||||
public class GroupController {
|
||||
|
||||
private final GroupService groupService;
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@PostMapping("get")
|
||||
public GroupDto get(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String privateUuid, @NonNull @RequestBody final String groupUuid) {
|
||||
return groupService.get(privateUuid, groupUuid);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@PostMapping("changeTitle")
|
||||
public GroupDto changeTitle(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String privateUuid, @NonNull @RequestBody final GroupChangeTitleRequest request) {
|
||||
return groupService.changeTitle(privateUuid, request);
|
||||
}
|
||||
|
||||
@PostMapping("changePassword")
|
||||
public GroupDto changePassword(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String privateUuid, @NonNull @RequestBody final GroupChangePasswordInbound request) {
|
||||
return groupService.changePassword(privateUuid, request);
|
||||
}
|
||||
|
||||
@PostMapping("leave")
|
||||
public void leave(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String privateUuid, @NonNull @RequestBody final String groupUuid) {
|
||||
groupService.leave(privateUuid, groupUuid);
|
||||
}
|
||||
|
||||
}
|
||||
46
src/main/java/de/ph87/tools/group/GroupDto.java
Normal file
46
src/main/java/de/ph87/tools/group/GroupDto.java
Normal file
@ -0,0 +1,46 @@
|
||||
package de.ph87.tools.group;
|
||||
|
||||
import de.ph87.tools.user.UserPublicDto;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
public class GroupDto {
|
||||
|
||||
@NonNull
|
||||
public final String uuid;
|
||||
|
||||
@NonNull
|
||||
public final String title;
|
||||
|
||||
@NonNull
|
||||
public final ZonedDateTime created;
|
||||
|
||||
@NonNull
|
||||
public final String password;
|
||||
|
||||
@NonNull
|
||||
public final UserPublicDto owner;
|
||||
|
||||
@NonNull
|
||||
public final Set<UserPublicDto> users;
|
||||
|
||||
public final boolean initial;
|
||||
|
||||
public GroupDto(@NonNull final Group group, @NonNull final UserPublicDto owner) {
|
||||
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.initial = group.isInitial();
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package de.ph87.tools.session;
|
||||
package de.ph87.tools.group;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
@ -8,7 +8,7 @@ import lombok.ToString;
|
||||
@Getter
|
||||
@ToString
|
||||
@AllArgsConstructor
|
||||
public class SessionJoinInbound {
|
||||
public class GroupJoinRequest {
|
||||
|
||||
@NonNull
|
||||
public final String uuid;
|
||||
57
src/main/java/de/ph87/tools/group/GroupMapper.java
Normal file
57
src/main/java/de/ph87/tools/group/GroupMapper.java
Normal file
@ -0,0 +1,57 @@
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
52
src/main/java/de/ph87/tools/group/GroupOfUserService.java
Normal file
52
src/main/java/de/ph87/tools/group/GroupOfUserService.java
Normal file
@ -0,0 +1,52 @@
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
24
src/main/java/de/ph87/tools/group/GroupRepository.java
Normal file
24
src/main/java/de/ph87/tools/group/GroupRepository.java
Normal file
@ -0,0 +1,24 @@
|
||||
package de.ph87.tools.group;
|
||||
|
||||
import de.ph87.tools.user.User;
|
||||
import lombok.NonNull;
|
||||
import org.springframework.data.repository.ListCrudRepository;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
public interface GroupRepository extends ListCrudRepository<Group, String> {
|
||||
|
||||
@NonNull
|
||||
Optional<Group> findByUuid(@NonNull String uuid);
|
||||
|
||||
@NonNull
|
||||
Optional<Group> findByUuidAndUsersContains(final @NonNull String groupUuid, @NonNull User user);
|
||||
|
||||
@NonNull
|
||||
Set<Group> findAllByUsersContains(@NonNull User user);
|
||||
|
||||
@NonNull
|
||||
Set<Group> findAllByUsersContainsAndUsersContains(@NonNull User a, @NonNull User b);
|
||||
|
||||
}
|
||||
124
src/main/java/de/ph87/tools/group/GroupService.java
Normal file
124
src/main/java/de/ph87/tools/group/GroupService.java
Normal file
@ -0,0 +1,124 @@
|
||||
package de.ph87.tools.group;
|
||||
|
||||
import de.ph87.tools.user.User;
|
||||
import de.ph87.tools.user.UserPublicMapper;
|
||||
import de.ph87.tools.user.UserService;
|
||||
import jakarta.annotation.Nullable;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
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 GroupService {
|
||||
|
||||
private final GroupRepository groupRepository;
|
||||
|
||||
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);
|
||||
final Group group = createUnchecked(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
|
||||
public GroupDto get(@NonNull final String privateUuid, @NonNull final String groupUuid) {
|
||||
final GroupOfUserService.GroupOfUser ug = groupOfUserService.accessGroupOfUser(groupUuid, privateUuid);
|
||||
return groupMapper.toDto(ug.group);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public GroupDto join(@NonNull final String privateUuid, @NonNull final GroupJoinRequest request) {
|
||||
final User user = userService.getByPrivateUuidOrThrow(privateUuid);
|
||||
final Group group = getByGroupByGroupUuid(request.uuid);
|
||||
if (!group.getPassword().equals(request.password)) {
|
||||
log.error("Wrong password: user={}, group={}", user, group);
|
||||
throw new ResponseStatusException(HttpStatus.FORBIDDEN);
|
||||
}
|
||||
return doJoinUnchecked(group, user);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Group getByGroupByGroupUuid(@NonNull final String groupUuid) {
|
||||
return groupRepository.findByUuid(groupUuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public GroupDto changeTitle(@NonNull final String privateUuid, @NonNull final GroupChangeTitleRequest request) {
|
||||
final GroupOfUserService.GroupOfUser ug = groupOfUserService.accessGroupOfUser(request.uuid, privateUuid);
|
||||
if (!ug.group.isOwnedBy(ug.user)) {
|
||||
throw new ResponseStatusException(HttpStatus.FORBIDDEN);
|
||||
}
|
||||
ug.group.setTitle(request.title);
|
||||
return groupMapper.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);
|
||||
}
|
||||
|
||||
public void leave(@NonNull final String privateUuid, @NonNull final String groupUuid) {
|
||||
final User user = userService.getByPrivateUuidOrThrow(privateUuid);
|
||||
final Group group = getByGroupByGroupUuid(groupUuid);
|
||||
doLeaveUnchecked(group, user);
|
||||
}
|
||||
|
||||
/* CREATE, JOIN, LEAVE -------------------------------------------------------------------------- */
|
||||
|
||||
@NonNull
|
||||
private Group createUnchecked(@NonNull final User user) {
|
||||
final Group group = groupRepository.save(new Group(user));
|
||||
log.info("Group CREATED: {}", group);
|
||||
return group;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private GroupDto doJoinUnchecked(@NonNull final Group group, @NonNull final User user) {
|
||||
group.getUsers().add(user);
|
||||
group.touch();
|
||||
user.touch();
|
||||
log.info("User joined Group: user={}, group={}", user, group);
|
||||
final GroupDto groupDto = groupMapper.publish(group);
|
||||
userPublicMapper.publish(user);
|
||||
return groupDto;
|
||||
}
|
||||
|
||||
private void doLeaveUnchecked(final Group group, final User user) {
|
||||
group.getUsers().remove(user);
|
||||
group.touch();
|
||||
user.touch();
|
||||
log.info("User left Group: user={}, group={}", user, group);
|
||||
groupMapper.publish(group);
|
||||
userPublicMapper.publish(user);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,160 +0,0 @@
|
||||
package de.ph87.tools.session;
|
||||
|
||||
import de.ph87.tools.user.User;
|
||||
import de.ph87.tools.user.UserService;
|
||||
import jakarta.annotation.Nullable;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static de.ph87.tools.UserArgumentResolver.USER_UUID_COOKIE_NAME;
|
||||
|
||||
@Slf4j
|
||||
@CrossOrigin
|
||||
@EnableScheduling
|
||||
@RequiredArgsConstructor
|
||||
public abstract class AbstractSessionController<SESSION extends AbstractSession> {
|
||||
|
||||
private final Set<SESSION> sessions = new HashSet<>();
|
||||
|
||||
private final UserService userService;
|
||||
|
||||
private final ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
private final Class<SESSION> sessionClazz;
|
||||
|
||||
@Scheduled(timeUnit = TimeUnit.MINUTES, initialDelay = 5, fixedRate = 5)
|
||||
public void cleanUp() {
|
||||
final ZonedDateTime deadline = ZonedDateTime.now().minusDays(30);
|
||||
sessions.stream().filter(session -> session.getLastAccess().isBefore(deadline)).forEach(this::delete);
|
||||
}
|
||||
|
||||
private void delete(@NonNull final SESSION session) {
|
||||
for (final User user : session.getUsers()) {
|
||||
user.leave(session);
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("create")
|
||||
public AbstractSessionDto create(@CookieValue(name = USER_UUID_COOKIE_NAME, required = false) @Nullable final String userUuid, @NonNull final HttpServletResponse response) {
|
||||
final User user = userService.getUserByUuidOrElseCreate(userUuid, response);
|
||||
|
||||
final Optional<SESSION> existing = user.getSessions().stream().filter(sessionClazz::isInstance).filter(AbstractSession::isInitial).map(sessionClazz::cast).min(Comparator.comparing(AbstractSession::getCreated));
|
||||
if (existing.isPresent()) {
|
||||
log.info("User wanted to create new Session but found existing: {}", existing);
|
||||
return toDto(existing.get(), user);
|
||||
}
|
||||
|
||||
final SESSION session = create(user);
|
||||
log.info("Session CREATED: {}", session);
|
||||
sessions.add(session);
|
||||
|
||||
return join(session, user);
|
||||
}
|
||||
|
||||
@PostMapping("canAccess")
|
||||
public boolean canAccess(@CookieValue(name = USER_UUID_COOKIE_NAME, required = false) @Nullable final String userUuid, @RequestBody final String sessionUuid) {
|
||||
if (userUuid == null) {
|
||||
return false;
|
||||
}
|
||||
return userService.findByPrivateUuid(userUuid).flatMap(user -> findUserSession(user, sessionUuid)).isPresent();
|
||||
}
|
||||
|
||||
@PostMapping("get")
|
||||
public AbstractSessionDto get(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String userUuid, @RequestBody final String sessionUuid) {
|
||||
final User user = userService.getByPrivateUuidOrThrow(userUuid);
|
||||
final SESSION session = getUserSession(user, sessionUuid);
|
||||
return toDto(session, user);
|
||||
}
|
||||
|
||||
@PostMapping("join")
|
||||
public AbstractSessionDto join(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String userUuid, @RequestBody final SessionJoinInbound inbound) {
|
||||
final User user = userService.getByPrivateUuidOrThrow(userUuid);
|
||||
final SESSION session = getSessionByUuid(inbound.uuid);
|
||||
if (!session.getPassword().equals(inbound.password)) {
|
||||
log.error("Wrong password: user={}, session={}", user, session);
|
||||
throw new ResponseStatusException(HttpStatus.FORBIDDEN);
|
||||
}
|
||||
return join(session, user);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private AbstractSessionDto join(@NonNull final SESSION session, @NonNull final User user) {
|
||||
session.join(user);
|
||||
user.join(session);
|
||||
log.info("User joined Session: user={}, session={}", user, session);
|
||||
applicationEventPublisher.publishEvent(session);
|
||||
applicationEventPublisher.publishEvent(user);
|
||||
return toDto(session, user);
|
||||
}
|
||||
|
||||
@PostMapping("changeTitle")
|
||||
public AbstractSessionDto changeTitle(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String userUuid, @RequestBody final SessionChangeTitleInbound inbound) {
|
||||
final User user = userService.getByPrivateUuidOrThrow(userUuid);
|
||||
final SESSION session = getUserSession(user, inbound.uuid);
|
||||
if (session.isOwnedBy(user)) {
|
||||
throw new ResponseStatusException(HttpStatus.FORBIDDEN);
|
||||
}
|
||||
session.setTitle(inbound.title);
|
||||
applicationEventPublisher.publishEvent(session);
|
||||
return toDto(session, user);
|
||||
}
|
||||
|
||||
@PostMapping("changePassword")
|
||||
public AbstractSessionDto changePassword(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String userUuid, @RequestBody final SessionChangePasswordInbound inbound) {
|
||||
final User user = userService.getByPrivateUuidOrThrow(userUuid);
|
||||
final SESSION session = getUserSession(user, inbound.uuid);
|
||||
if (session.isOwnedBy(user)) {
|
||||
throw new ResponseStatusException(HttpStatus.FORBIDDEN);
|
||||
}
|
||||
session.setPassword(inbound.password);
|
||||
applicationEventPublisher.publishEvent(session);
|
||||
return toDto(session, user);
|
||||
}
|
||||
|
||||
@PostMapping("leave")
|
||||
public void leave(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String userUuid, @RequestBody final String sessionUuid) {
|
||||
final User user = userService.getByPrivateUuidOrThrow(userUuid);
|
||||
final SESSION session = getSessionByUuid(sessionUuid);
|
||||
session.leave(user);
|
||||
user.leave(session);
|
||||
applicationEventPublisher.publishEvent(session);
|
||||
applicationEventPublisher.publishEvent(user);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private SESSION getUserSession(@NonNull final User user, @NonNull final String sessionUuid) {
|
||||
return findUserSession(user, sessionUuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Optional<SESSION> findUserSession(@NonNull final User user, @NonNull final String sessionUuid) {
|
||||
return user.getSessions().stream().filter(s -> s.getUuid().equals(sessionUuid)).filter(sessionClazz::isInstance).map(sessionClazz::cast).findFirst();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private SESSION getSessionByUuid(@NonNull final String uuid) {
|
||||
return sessions.stream().filter(u -> u.getUuid().equals(uuid)).findFirst().orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected abstract SESSION create(@NonNull final User user);
|
||||
|
||||
@NonNull
|
||||
protected abstract AbstractSessionDto toDto(@NonNull final SESSION session, @NonNull final User forUser);
|
||||
|
||||
}
|
||||
@ -1,60 +0,0 @@
|
||||
package de.ph87.tools.session;
|
||||
|
||||
import de.ph87.tools.tools.numbers.NumbersSession;
|
||||
import de.ph87.tools.tools.numbers.NumbersSessionDto;
|
||||
import de.ph87.tools.user.UserPublicDto;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
public abstract class AbstractSessionDto {
|
||||
|
||||
@NonNull
|
||||
private final String type;
|
||||
|
||||
@NonNull
|
||||
public final String uuid;
|
||||
|
||||
@NonNull
|
||||
public final String title;
|
||||
|
||||
@NonNull
|
||||
public final ZonedDateTime created;
|
||||
|
||||
@NonNull
|
||||
public final String password;
|
||||
|
||||
@NonNull
|
||||
public final UserPublicDto owner;
|
||||
|
||||
@NonNull
|
||||
public final Set<UserPublicDto> users;
|
||||
|
||||
public final boolean initial;
|
||||
|
||||
protected AbstractSessionDto(@NonNull final AbstractSession session, @NonNull final String type) {
|
||||
this.type = type;
|
||||
this.uuid = session.getUuid();
|
||||
this.title = session.getTitle();
|
||||
this.created = session.getCreated();
|
||||
this.password = session.getPassword();
|
||||
this.owner = new UserPublicDto(session.getOwner());
|
||||
this.users = session.getUsers().stream().map(UserPublicDto::new).collect(Collectors.toSet());
|
||||
this.initial = session.isInitial();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static AbstractSessionDto toDto(@NonNull final AbstractSession session) {
|
||||
return switch (session) {
|
||||
case NumbersSession numbersSession -> new NumbersSessionDto(numbersSession);
|
||||
default -> throw new IllegalStateException("No DTO mapping for: " + session);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
49
src/main/java/de/ph87/tools/tools/numbers/Numbers.java
Normal file
49
src/main/java/de/ph87/tools/tools/numbers/Numbers.java
Normal file
@ -0,0 +1,49 @@
|
||||
package de.ph87.tools.tools.numbers;
|
||||
|
||||
import de.ph87.tools.group.Group;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.NonNull;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Getter
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
@Table(name = "`numbers`")
|
||||
public class Numbers {
|
||||
|
||||
@Id
|
||||
@NonNull
|
||||
@Column(nullable = false)
|
||||
private String uuid = UUID.randomUUID().toString();
|
||||
|
||||
@NonNull
|
||||
@Column(nullable = false)
|
||||
private String name = "Ohne Namen";
|
||||
|
||||
@NonNull
|
||||
@OneToOne
|
||||
private Group group;
|
||||
|
||||
public Numbers(@NonNull final Group group) {
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (!(obj instanceof final Numbers numbers)) {
|
||||
return false;
|
||||
}
|
||||
return numbers.uuid.equals(this.uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return uuid.hashCode();
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,32 +1,14 @@
|
||||
package de.ph87.tools.tools.numbers;
|
||||
|
||||
import de.ph87.tools.session.AbstractSessionController;
|
||||
import de.ph87.tools.session.AbstractSessionDto;
|
||||
import de.ph87.tools.user.User;
|
||||
import de.ph87.tools.user.UserService;
|
||||
import lombok.NonNull;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
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.RestController;
|
||||
|
||||
@RestController
|
||||
@Slf4j
|
||||
@CrossOrigin
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("Numbers")
|
||||
public class NumbersController extends AbstractSessionController<NumbersSession> {
|
||||
|
||||
public NumbersController(final UserService userService, final ApplicationEventPublisher applicationEventPublisher) {
|
||||
super(userService, applicationEventPublisher, NumbersSession.class);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected NumbersSession create(@NonNull final User user) {
|
||||
return new NumbersSession(user);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected AbstractSessionDto toDto(@NonNull final NumbersSession session, @NonNull final User forUser) {
|
||||
return new NumbersSessionDto(session);
|
||||
}
|
||||
public class NumbersController {
|
||||
|
||||
}
|
||||
|
||||
27
src/main/java/de/ph87/tools/tools/numbers/NumbersDto.java
Normal file
27
src/main/java/de/ph87/tools/tools/numbers/NumbersDto.java
Normal file
@ -0,0 +1,27 @@
|
||||
package de.ph87.tools.tools.numbers;
|
||||
|
||||
import de.ph87.tools.group.GroupDto;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.ToString;
|
||||
|
||||
@Getter
|
||||
@ToString(callSuper = true)
|
||||
public class NumbersDto {
|
||||
|
||||
@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) {
|
||||
this.uuid = numbers.getUuid();
|
||||
this.name = numbers.getName();
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
package de.ph87.tools.tools.numbers;
|
||||
|
||||
import de.ph87.tools.session.AbstractSession;
|
||||
import de.ph87.tools.user.User;
|
||||
import lombok.NonNull;
|
||||
|
||||
public class NumbersSession extends AbstractSession {
|
||||
|
||||
protected NumbersSession(@NonNull final User user) {
|
||||
super(user);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
package de.ph87.tools.tools.numbers;
|
||||
|
||||
import de.ph87.tools.session.AbstractSessionDto;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.ToString;
|
||||
|
||||
@Getter
|
||||
@ToString(callSuper = true)
|
||||
public class NumbersSessionDto extends AbstractSessionDto {
|
||||
|
||||
public NumbersSessionDto(@NonNull final NumbersSession session) {
|
||||
super(session, "Numbers");
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,14 +1,14 @@
|
||||
package de.ph87.tools.user;
|
||||
|
||||
import de.ph87.tools.session.AbstractSession;
|
||||
import de.ph87.tools.web.IWebSocketMessage;
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.*;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@ -31,10 +31,6 @@ public class User implements IWebSocketMessage {
|
||||
@Column(nullable = false)
|
||||
private ZonedDateTime created = ZonedDateTime.now();
|
||||
|
||||
@ManyToMany
|
||||
@ToString.Exclude
|
||||
private Set<AbstractSession> sessions = new HashSet<>();
|
||||
|
||||
@NonNull
|
||||
@Column(nullable = false)
|
||||
private ZonedDateTime lastAccess = created;
|
||||
@ -44,19 +40,10 @@ public class User implements IWebSocketMessage {
|
||||
@Column(nullable = false)
|
||||
private String name = "Neuer Benutzer";
|
||||
|
||||
private void touch() {
|
||||
public void touch() {
|
||||
lastAccess = ZonedDateTime.now();
|
||||
}
|
||||
|
||||
public void join(@NonNull final AbstractSession session) {
|
||||
sessions.add(session);
|
||||
touch();
|
||||
}
|
||||
|
||||
public void leave(@NonNull final AbstractSession session) {
|
||||
sessions.remove(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Object> getWebsocketTopic() {
|
||||
return List.of("User", privateUuid);
|
||||
|
||||
@ -1,13 +1,11 @@
|
||||
package de.ph87.tools.user;
|
||||
|
||||
import de.ph87.tools.session.AbstractSessionDto;
|
||||
import jakarta.annotation.Nullable;
|
||||
import de.ph87.tools.group.GroupDto;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@ -20,15 +18,12 @@ public class UserCommonDto {
|
||||
public final String name;
|
||||
|
||||
@NonNull
|
||||
public final Set<AbstractSessionDto> commonSessions;
|
||||
public final Set<GroupDto> commonGroups;
|
||||
|
||||
public UserCommonDto(@NonNull final User target, @Nullable final User principal) {
|
||||
public UserCommonDto(@NonNull final User target, @NonNull final Set<GroupDto> commonGroups) {
|
||||
this.publicUuid = target.getPublicUuid();
|
||||
this.name = target.getName();
|
||||
this.commonSessions = target.getSessions().stream()
|
||||
.filter(session -> session.getUsers().contains(principal))
|
||||
.map(AbstractSessionDto::toDto)
|
||||
.collect(Collectors.toSet());
|
||||
this.commonGroups = commonGroups;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -21,7 +21,7 @@ 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.getUserByUuidOrNull(userUuid, response);
|
||||
return userService.getUserByPrivateUuidOrNull(userUuid, response);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@ -32,7 +32,7 @@ public class UserController {
|
||||
|
||||
@NonNull
|
||||
@PostMapping("getCommonByUuid")
|
||||
public UserCommonDto getCommonByUuid(@CookieValue(name = USER_UUID_COOKIE_NAME, required = false) @Nullable final String userUuid, @NonNull @RequestBody final String targetUuid) {
|
||||
public UserCommonDto getCommonByUuid(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String userUuid, @NonNull @RequestBody final String targetUuid) {
|
||||
return userService.getCommonByUuid(userUuid, targetUuid);
|
||||
}
|
||||
|
||||
|
||||
@ -1,14 +1,12 @@
|
||||
package de.ph87.tools.user;
|
||||
|
||||
import de.ph87.tools.session.AbstractSessionDto;
|
||||
import jakarta.annotation.Nullable;
|
||||
import de.ph87.tools.group.GroupDto;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@ -27,22 +25,14 @@ public class UserPrivateDto {
|
||||
public final ZonedDateTime created;
|
||||
|
||||
@NonNull
|
||||
private final Set<AbstractSessionDto> sessions;
|
||||
private final Set<GroupDto> groups;
|
||||
|
||||
public UserPrivateDto(@NonNull final User user) {
|
||||
public UserPrivateDto(@NonNull final User user, final @NonNull Set<GroupDto> groups) {
|
||||
this.publicUuid = user.getPublicUuid();
|
||||
this.name = user.getName();
|
||||
this.privateUuid = user.getPrivateUuid();
|
||||
this.created = user.getCreated();
|
||||
this.sessions = user.getSessions().stream().map(AbstractSessionDto::toDto).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static UserPrivateDto orNull(@Nullable final User user) {
|
||||
if (user == null) {
|
||||
return null;
|
||||
}
|
||||
return new UserPrivateDto(user);
|
||||
this.groups = groups;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
38
src/main/java/de/ph87/tools/user/UserPrivateMapper.java
Normal file
38
src/main/java/de/ph87/tools/user/UserPrivateMapper.java
Normal file
@ -0,0 +1,38 @@
|
||||
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.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;
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
}
|
||||
30
src/main/java/de/ph87/tools/user/UserPublicMapper.java
Normal file
30
src/main/java/de/ph87/tools/user/UserPublicMapper.java
Normal file
@ -0,0 +1,30 @@
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,7 +1,16 @@
|
||||
package de.ph87.tools.user;
|
||||
|
||||
import lombok.NonNull;
|
||||
import org.springframework.data.repository.ListCrudRepository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface UserRepository extends ListCrudRepository<User, String> {
|
||||
|
||||
@NonNull
|
||||
Optional<User> findByPublicUuid(@NonNull String publicUuid);
|
||||
|
||||
@NonNull
|
||||
Optional<User> findByPrivateUuid(@NonNull String privateUuid);
|
||||
|
||||
}
|
||||
|
||||
@ -1,84 +1,121 @@
|
||||
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;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static de.ph87.tools.UserArgumentResolver.USER_UUID_COOKIE_NAME;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@Transactional
|
||||
@EnableScheduling
|
||||
@RequiredArgsConstructor
|
||||
public class UserService {
|
||||
|
||||
private final ApplicationEventPublisher applicationEventPublisher;
|
||||
private final UserRepository userRepository;
|
||||
|
||||
private final Set<User> users = new HashSet<>();
|
||||
private final UserPrivateMapper userPrivateMapper;
|
||||
|
||||
private final UserPublicMapper userPublicMapper;
|
||||
|
||||
private final GroupMapper groupMapper;
|
||||
|
||||
@NonNull
|
||||
public User getUserByUuidOrElseCreate(@Nullable final String uuid, @NonNull final HttpServletResponse response) {
|
||||
final User user = Optional.ofNullable(uuid).map(this::findByPrivateUuid).filter(Optional::isPresent).map(Optional::get).orElseGet(this::create);
|
||||
public User getUserByPrivateUuidOrElseCreate(@Nullable final String privateUuid, @NonNull final HttpServletResponse response) {
|
||||
final User user = Optional.ofNullable(privateUuid).map(userRepository::findByPrivateUuid).filter(Optional::isPresent).map(Optional::get).orElseGet(this::createUnchecked);
|
||||
writeUserUuidCookie(response, user);
|
||||
return user;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public UserPrivateDto getUserByUuidOrNull(@Nullable final String userUuid, final @NonNull HttpServletResponse response) {
|
||||
if (userUuid == null || userUuid.isEmpty()) {
|
||||
public UserPrivateDto getUserByPrivateUuidOrNull(@Nullable final String privateUuid, final @NonNull HttpServletResponse response) {
|
||||
if (privateUuid == null || privateUuid.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
final User user = findByPrivateUuid(userUuid).orElse(null);
|
||||
final User user = userRepository.findByPrivateUuid(privateUuid).orElse(null);
|
||||
writeUserUuidCookie(response, user);
|
||||
return UserPrivateDto.orNull(user);
|
||||
return userPrivateMapper.toPrivateDtoOrNull(user);
|
||||
}
|
||||
|
||||
public void delete(@NonNull final String userUuid, final @NonNull HttpServletResponse response) {
|
||||
final User user = findByPrivateUuid(userUuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST));
|
||||
users.remove(user);
|
||||
@NonNull
|
||||
public UserPrivateDto changeName(@NonNull final String privateUuid, @NonNull final String name) {
|
||||
return modify(privateUuid, user -> {
|
||||
user.setName(name);
|
||||
log.info("User name changed: user={}", user);
|
||||
}, true);
|
||||
}
|
||||
|
||||
@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) {
|
||||
final User user = userRepository.findByPrivateUuid(privateUuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST));
|
||||
deleteUnchecked(response, user);
|
||||
}
|
||||
|
||||
/* CREATE, MODIFY, DELETE ----------------------------------------------------------------------- */
|
||||
|
||||
@NonNull
|
||||
private User createUnchecked() {
|
||||
final User user = userRepository.save(new User());
|
||||
log.info("User CREATED: {}", user);
|
||||
return user;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private UserPrivateDto modify(@NonNull final String privateUuid, @NonNull Consumer<User> modifier, final boolean doPublish) {
|
||||
final User user = getByPrivateUuidOrThrow(privateUuid);
|
||||
modifier.accept(user);
|
||||
if (doPublish) {
|
||||
userPublicMapper.publish(user);
|
||||
}
|
||||
return userPrivateMapper.toPrivateDto(user);
|
||||
}
|
||||
|
||||
private void deleteUnchecked(final HttpServletResponse response, final User user) {
|
||||
userRepository.delete(user);
|
||||
log.info("User DELETED: {}", user);
|
||||
writeUserUuidCookie(response, null);
|
||||
}
|
||||
|
||||
/* GETTERS & FINDERS ---------------------------------------------------------------------------- */
|
||||
|
||||
@NonNull
|
||||
public User getByPrivateUuidOrThrow(@NonNull final String uuid) {
|
||||
return findByPrivateUuid(uuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST));
|
||||
public User getByPrivateUuidOrThrow(@NonNull final String privateUuid) {
|
||||
return userRepository.findByPrivateUuid(privateUuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public User getByPublicUuid(@NonNull final String uuid) {
|
||||
return findByPublicUuid(uuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST));
|
||||
public User getByPublicUuid(@NonNull final String publicUuid) {
|
||||
return userRepository.findByPublicUuid(publicUuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Optional<User> findByPrivateUuid(@NonNull final String uuid) {
|
||||
return users.stream().filter(u -> u.getPrivateUuid().equals(uuid)).findFirst();
|
||||
public Optional<User> findByPrivateUuid(@NonNull final String privateUuid) {
|
||||
return userRepository.findByPrivateUuid(privateUuid);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Optional<User> findByPublicUuid(@NonNull final String uuid) {
|
||||
return users.stream().filter(u -> u.getPublicUuid().equals(uuid)).findFirst();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private User create() {
|
||||
final User user = new User();
|
||||
users.add(user);
|
||||
log.info("User CREATED: {}", user);
|
||||
return user;
|
||||
}
|
||||
/* COOKIES -------------------------------------------------------------------------------------- */
|
||||
|
||||
private static void writeUserUuidCookie(@NonNull final HttpServletResponse response, @Nullable final User user) {
|
||||
final Cookie cookie = new Cookie(USER_UUID_COOKIE_NAME, "");
|
||||
@ -90,19 +127,4 @@ public class UserService {
|
||||
response.addCookie(cookie);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public UserPrivateDto changeName(@NonNull final String uuid, @NonNull final String name) {
|
||||
final User user = getByPrivateUuidOrThrow(uuid);
|
||||
user.setName(name);
|
||||
applicationEventPublisher.publishEvent(new UserPublicDto(user));
|
||||
return new UserPrivateDto(user);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public UserCommonDto getCommonByUuid(@Nullable final String principalUuid, @NonNull final String targetUuid) {
|
||||
final User principal = principalUuid == null ? null : findByPrivateUuid(principalUuid).orElse(null);
|
||||
final User target = getByPublicUuid(targetUuid);
|
||||
return new UserCommonDto(target, principal);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
logging.level.root=WARN
|
||||
logging.level.de.ph87=INFO
|
||||
#-
|
||||
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyComponentPathImpl
|
||||
spring.jpa.hibernate.ddl-auto=update
|
||||
spring.jpa.open-in-view=false
|
||||
#-
|
||||
spring.jackson.serialization.indent_output=true
|
||||
#-
|
||||
spring.main.banner-mode=off
|
||||
|
||||
Loading…
Reference in New Issue
Block a user