Compare commits
No commits in common. "d59ad4177f68c0675bef5543bbf8d3e05c0659e9" and "c2ea700011d1a6471c35e1a43b68006737907ab5" have entirely different histories.
d59ad4177f
...
c2ea700011
5
pom.xml
5
pom.xml
@ -32,11 +32,6 @@
|
|||||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.security</groupId>
|
|
||||||
<artifactId>spring-security-core</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||||
|
|||||||
@ -1,46 +0,0 @@
|
|||||||
import {Subscription} from "rxjs";
|
|
||||||
import {Next} from "./common/types";
|
|
||||||
|
|
||||||
export class Subscribed<T> {
|
|
||||||
|
|
||||||
private subscription: Subscription | null = null;
|
|
||||||
|
|
||||||
private _value: T | null = null;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private readonly same: (a: T, b: T) => boolean,
|
|
||||||
private readonly subscribe: (value: T, next: Next<T>) => Subscription,
|
|
||||||
) {
|
|
||||||
// -
|
|
||||||
}
|
|
||||||
|
|
||||||
get value(): T | null {
|
|
||||||
return this._value;
|
|
||||||
}
|
|
||||||
|
|
||||||
set value(value: T | null) {
|
|
||||||
if (!this.isSame(value)) {
|
|
||||||
if (this.subscription) {
|
|
||||||
this.subscription.unsubscribe();
|
|
||||||
this.subscription = null;
|
|
||||||
}
|
|
||||||
if (value) {
|
|
||||||
this.subscription = this.subscribe(value, next => this.value = next);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this._value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private isSame(value: T | null) {
|
|
||||||
if (this._value === null) {
|
|
||||||
return value === null;
|
|
||||||
} else {
|
|
||||||
if (value === null) {
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
return this.same(this._value, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import {validateBoolean, validateDate, validateList, validateString} from "../common/validators";
|
import {validateDate, validateList, validateString} from "../common/validators";
|
||||||
import {Group} from "../group/Group";
|
import {Group} from "../group/Group";
|
||||||
|
|
||||||
export class UserPrivate {
|
export class UserPrivate {
|
||||||
@ -7,9 +7,8 @@ export class UserPrivate {
|
|||||||
readonly privateUuid: string,
|
readonly privateUuid: string,
|
||||||
readonly publicUuid: string,
|
readonly publicUuid: string,
|
||||||
readonly created: Date,
|
readonly created: Date,
|
||||||
readonly name: string,
|
|
||||||
readonly password: boolean,
|
|
||||||
readonly groups: Group[],
|
readonly groups: Group[],
|
||||||
|
readonly name: string,
|
||||||
) {
|
) {
|
||||||
// -
|
// -
|
||||||
}
|
}
|
||||||
@ -19,9 +18,8 @@ export class UserPrivate {
|
|||||||
validateString(json['privateUuid']),
|
validateString(json['privateUuid']),
|
||||||
validateString(json['publicUuid']),
|
validateString(json['publicUuid']),
|
||||||
validateDate(json['created']),
|
validateDate(json['created']),
|
||||||
validateString(json['name']),
|
|
||||||
validateBoolean(json['password']),
|
|
||||||
validateList(json['groups'], Group.fromJson),
|
validateList(json['groups'], Group.fromJson),
|
||||||
|
validateString(json['name']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,8 +30,4 @@ export class UserPrivate {
|
|||||||
return UserPrivate.fromJson(json);
|
return UserPrivate.fromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
static samePrivateUuid(a: UserPrivate, b: UserPrivate): boolean {
|
|
||||||
return a.privateUuid === b.privateUuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,11 +4,10 @@ import {UserPrivate} from "./UserPrivate";
|
|||||||
import {Next} from "../common/types";
|
import {Next} from "../common/types";
|
||||||
import {UserCommon} from "./UserCommon";
|
import {UserCommon} from "./UserCommon";
|
||||||
import {UserPublic} from "./UserPublic";
|
import {UserPublic} from "./UserPublic";
|
||||||
import {EventType, Router} from "@angular/router";
|
import {Router} from "@angular/router";
|
||||||
import {BehaviorSubject, Subscription} from "rxjs";
|
import {BehaviorSubject, 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";
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@ -17,8 +16,6 @@ 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;
|
|
||||||
|
|
||||||
get user(): UserPrivate | null {
|
get user(): UserPrivate | null {
|
||||||
return this.subject.value;
|
return this.subject.value;
|
||||||
}
|
}
|
||||||
@ -28,13 +25,9 @@ export class UserService {
|
|||||||
protected readonly api: ApiService,
|
protected readonly api: ApiService,
|
||||||
protected readonly stompService: StompService,
|
protected readonly stompService: StompService,
|
||||||
) {
|
) {
|
||||||
this.router.events.subscribe(e => {
|
this.stompService.connected$.subscribe(() => {
|
||||||
if (e.type === EventType.NavigationEnd || e.type === EventType.NavigationSkipped) {
|
|
||||||
console.log(e);
|
|
||||||
this.refresh();
|
this.refresh();
|
||||||
}
|
|
||||||
});
|
});
|
||||||
this.stompService.connected$.subscribe(() => this.refresh());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getCommonByUuid(uuid: string, next: Next<UserCommon>): void {
|
getCommonByUuid(uuid: string, next: Next<UserCommon>): void {
|
||||||
@ -42,15 +35,17 @@ export class UserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
delete(next?: Next<void>) {
|
delete(next?: Next<void>) {
|
||||||
this.api.getNone(['User', 'delete'], next);
|
this.api.getNone(['User', 'delete'], _ => {
|
||||||
|
this.refresh();
|
||||||
|
next && next();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
changeName(name: string, next?: Next<UserPrivate>) {
|
changeName(name: string, next?: Next<UserPrivate>) {
|
||||||
this.api.postSingle(['User', 'changeName'], name, UserPrivate.fromJson, next);
|
this.api.postSingle(['User', 'changeName'], name, UserPrivate.fromJson, user => {
|
||||||
}
|
this.refresh();
|
||||||
|
next && next(user);
|
||||||
changePassword(password: string, next?: Next<UserPrivate>) {
|
});
|
||||||
this.api.postSingle(['User', 'changePassword'], password, UserPrivate.fromJson, next);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
goto(user: UserPublic) {
|
goto(user: UserPublic) {
|
||||||
@ -58,7 +53,7 @@ export class UserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
refresh() {
|
refresh() {
|
||||||
this.api.getSingle(['User', 'whoAmI'], UserPrivate.fromJsonOrNull, user => this.setUser(user));
|
this.api.getSingle(['User', 'whoAmI'], UserPrivate.fromJsonOrNull, user => this.subject.next(user));
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribe(next: Next<UserPrivate | null>): Subscription {
|
subscribe(next: Next<UserPrivate | null>): Subscription {
|
||||||
@ -69,20 +64,4 @@ export class UserService {
|
|||||||
return this.user?.publicUuid === group.owner.publicUuid;
|
return this.user?.publicUuid === group.owner.publicUuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
private setUser(user: UserPrivate | null) {
|
|
||||||
if (this.subject.value?.privateUuid !== user?.privateUuid) {
|
|
||||||
if (this.subscription) {
|
|
||||||
this.subscription.unsubscribe();
|
|
||||||
this.subscription = undefined;
|
|
||||||
}
|
|
||||||
if (user) {
|
|
||||||
this.subscription = this.api.subscribe(["User", user.privateUuid], UserPrivate.fromJson, user => this.setUser(user));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.subject.next(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
newSubscriber(): Subscribed<UserPrivate> {
|
|
||||||
return new Subscribed<UserPrivate>(UserPrivate.samePrivateUuid, (user, next) => this.api.subscribe(['UserPrivate', user.privateUuid], UserPrivate.fromJson, next));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,6 +31,10 @@ export class Group {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static compareCreated(a: Group, b: Group): number {
|
||||||
|
return a.created.getTime() - b.created.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
usersByNameOwnerFirst() {
|
usersByNameOwnerFirst() {
|
||||||
return this.users.sort((a, b) => this.compareOwnerFirstThenName(a, b));
|
return this.users.sort((a, b) => this.compareOwnerFirstThenName(a, b));
|
||||||
}
|
}
|
||||||
@ -38,19 +42,9 @@ export class Group {
|
|||||||
private compareOwnerFirstThenName(a: UserPublic, b: UserPublic): number {
|
private compareOwnerFirstThenName(a: UserPublic, b: UserPublic): number {
|
||||||
if (a.publicUuid === this.owner.publicUuid) {
|
if (a.publicUuid === this.owner.publicUuid) {
|
||||||
return -1;
|
return -1;
|
||||||
} else if (b.publicUuid === this.owner.publicUuid) {
|
|
||||||
return +1;
|
|
||||||
}
|
}
|
||||||
return UserPublic.compareName(a, b);
|
return UserPublic.compareName(a, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
static sameUuid(a: Group, b: Group): boolean {
|
|
||||||
return a.uuid === b.uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
static compareCreated(a: Group, b: Group): number {
|
|
||||||
return a.created.getTime() - b.created.getTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import {Router} from "@angular/router";
|
|||||||
import {UserService} from "../User/user.service";
|
import {UserService} from "../User/user.service";
|
||||||
import {validateBoolean} from "../common/validators";
|
import {validateBoolean} from "../common/validators";
|
||||||
import {Injectable} from "@angular/core";
|
import {Injectable} from "@angular/core";
|
||||||
import {Subscribed} from "../Subscribed";
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@ -29,7 +28,10 @@ export class GroupService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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, group => {
|
||||||
|
next(group);
|
||||||
|
this.userService.refresh();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
changeTitle(group: Group, title: string, next?: Next<Group>) {
|
changeTitle(group: Group, title: string, next?: Next<Group>) {
|
||||||
@ -64,9 +66,5 @@ export class GroupService {
|
|||||||
this.router.navigate(['Group', group.uuid]);
|
this.router.navigate(['Group', group.uuid]);
|
||||||
}
|
}
|
||||||
|
|
||||||
newSubscriber(): Subscribed<Group> {
|
|
||||||
return new Subscribed<Group>(Group.sameUuid, (group, next) => this.api.subscribe(['Group', group.uuid], Group.fromJson, next));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1,34 +1,34 @@
|
|||||||
<ng-container *ngIf="group.value">
|
<ng-container *ngIf="group !== null">
|
||||||
<h1>Gruppe</h1>
|
<h1>Nummern</h1>
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Erstellt</th>
|
<th>Erstellt</th>
|
||||||
<td>{{ group.value.created | date:'yyyy-MM-dd HH:mm' }}</td>
|
<td>{{ group.created | date:'yyyy-MM-dd HH:mm' }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Titel</th>
|
<th>Titel</th>
|
||||||
<td>
|
<td>
|
||||||
<app-text [initial]="group.value.title" [editable]="userService.iOwn(group.value)" (onChange)="changeTitle(group.value, $event)"></app-text>
|
<app-text [initial]="group.title" [editable]="userService.iOwn(group)" (onChange)="changeTitle(group, $event)"></app-text>
|
||||||
<ng-container *ngIf="!userService.iOwn(group.value)"></ng-container>
|
<ng-container *ngIf="!userService.iOwn(group)"></ng-container>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Passwort</th>
|
<th>Passwort</th>
|
||||||
<td>
|
<td>
|
||||||
<app-text [initial]="group.value.password" [editable]="userService.iOwn(group.value)" (onChange)="changePassword(group.value, $event)"></app-text>
|
<app-text [initial]="group.password" [editable]="userService.iOwn(group)" (onChange)="changePassword(group, $event)"></app-text>
|
||||||
<ng-container *ngIf="!userService.iOwn(group.value)"></ng-container>
|
<ng-container *ngIf="!userService.iOwn(group)"></ng-container>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Teilnehmer</th>
|
<th>Teilnehmer</th>
|
||||||
<td>
|
<td>
|
||||||
<div class="user" [class.user_owner]="group.value.isOwner(user)" *ngFor="let user of group.value.usersByNameOwnerFirst()" (click)="userService.goto(user)">{{ user.name }}</div>
|
<div class="user" [class.user_owner]="group.isOwner(user)" *ngFor="let user of group.usersByNameOwnerFirst()" (click)="userService.goto(user)">{{ user.name }}</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="uuid && !group.value && accessDenied">
|
<ng-container *ngIf="uuid && !group && accessDenied">
|
||||||
<h1>Passwort</h1>
|
<h1>Passwort</h1>
|
||||||
<input type="text" [(ngModel)]="password" (keydown.enter)="join()">
|
<input type="text" [(ngModel)]="password" (keydown.enter)="join()">
|
||||||
<button (click)="join()">Beitreten</button>
|
<button (click)="join()">Beitreten</button>
|
||||||
|
|||||||
@ -3,10 +3,9 @@ import {DatePipe, NgForOf, NgIf} from "@angular/common";
|
|||||||
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
|
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
|
||||||
import {TextComponent} from "../../../shared/text/text.component";
|
import {TextComponent} from "../../../shared/text/text.component";
|
||||||
import {ActivatedRoute, Router} from "@angular/router";
|
import {ActivatedRoute, Router} from "@angular/router";
|
||||||
import {GroupService} from "../../../api/group/group.service";
|
import {GroupService} from "../../../api/group/GroupService";
|
||||||
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";
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-group',
|
selector: 'app-group',
|
||||||
@ -24,7 +23,7 @@ import {Subscribed} from "../../../api/Subscribed";
|
|||||||
})
|
})
|
||||||
export class GroupComponent implements OnInit {
|
export class GroupComponent implements OnInit {
|
||||||
|
|
||||||
protected readonly group: Subscribed<Group>;
|
protected group: Group | null = null;
|
||||||
|
|
||||||
protected uuid: string | null = null;
|
protected uuid: string | null = null;
|
||||||
|
|
||||||
@ -38,7 +37,7 @@ export class GroupComponent implements OnInit {
|
|||||||
protected readonly groupService: GroupService,
|
protected readonly groupService: GroupService,
|
||||||
protected readonly userService: UserService,
|
protected readonly userService: UserService,
|
||||||
) {
|
) {
|
||||||
this.group = this.groupService.newSubscriber();
|
// -
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
@ -50,25 +49,29 @@ export class GroupComponent implements OnInit {
|
|||||||
this.groupService.canAccess(this.uuid, canAccess => {
|
this.groupService.canAccess(this.uuid, canAccess => {
|
||||||
this.accessDenied = !canAccess;
|
this.accessDenied = !canAccess;
|
||||||
if (canAccess && this.uuid) {
|
if (canAccess && this.uuid) {
|
||||||
this.groupService.get(this.uuid, group => this.group.value = group);
|
this.groupService.get(this.uuid, group => this.setGroup(group));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private setGroup(group: Group): void {
|
||||||
|
this.group = group;
|
||||||
|
}
|
||||||
|
|
||||||
protected join() {
|
protected join() {
|
||||||
if (this.uuid) {
|
if (this.uuid) {
|
||||||
this.groupService.join(this.uuid, this.password, group => this.group.value = group);
|
this.groupService.join(this.uuid, this.password, group => this.setGroup(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.setGroup(group));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected changePassword(group: Group, password: string) {
|
protected changePassword(group: Group, password: string) {
|
||||||
this.groupService.changePassword(group, password, group => this.group.value = group);
|
this.groupService.changePassword(group, password, group => this.setGroup(group));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import {Component} from '@angular/core';
|
import {Component, 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/GroupService";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-groups',
|
selector: 'app-groups',
|
||||||
@ -14,7 +14,7 @@ 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 {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected readonly userService: UserService,
|
protected readonly userService: UserService,
|
||||||
@ -23,6 +23,10 @@ export class GroupsComponent {
|
|||||||
// -
|
// -
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.userService.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
create(): void {
|
create(): void {
|
||||||
this.groupService.create(group => this.groupService.goto(group));
|
this.groupService.create(group => this.groupService.goto(group));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import {Component, Input} from '@angular/core';
|
|||||||
import {UserService} from "../../../../api/User/user.service";
|
import {UserService} from "../../../../api/User/user.service";
|
||||||
import {Group} from "../../../../api/group/Group";
|
import {Group} from "../../../../api/group/Group";
|
||||||
import {DatePipe, NgForOf} from "@angular/common";
|
import {DatePipe, NgForOf} from "@angular/common";
|
||||||
import {GroupService} from "../../../../api/group/group.service";
|
import {GroupService} from "../../../../api/group/GroupService";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-group-list',
|
selector: 'app-group-list',
|
||||||
|
|||||||
@ -1,60 +1,5 @@
|
|||||||
<ng-container *ngIf="userService.user !== null">
|
<ng-container *ngIf="userService.user !== null">
|
||||||
<h1>Profil</h1>
|
<h1>Profil</h1>
|
||||||
<table>
|
<app-text [initial]="userService.user.name" (onChange)="userService.changeName($event)"></app-text>
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
Name:
|
|
||||||
</th>
|
|
||||||
<td>
|
|
||||||
<app-text [initial]="userService.user.name" [editable]="true" (onChange)="userService.changeName($event)" [validator]="nameValidator"></app-text>
|
|
||||||
</td>
|
|
||||||
<td class="hint">
|
|
||||||
Mindestens {{ USER_NAME_MIN_LENGTH }} Zeichen. Keine Leerzeichen. Buchstaben oder Zahlen müssen enthalten sein.
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<th colspan="3"> </th>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
Passwort:
|
|
||||||
</th>
|
|
||||||
<td>
|
|
||||||
<div *ngIf="!userService.user.password" class="passwordNotSet">Nicht gesetzt</div>
|
|
||||||
<div *ngIf="userService.user.password" class="passwordSet">Gesetzt</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
Neues Passwort:
|
|
||||||
</th>
|
|
||||||
<td>
|
|
||||||
<input #p0 type="text" [(ngModel)]="password0" [class.passwordInvalid]="p0Invalid()" [class.passwordValid]="p0Valid()" (keydown.enter)="p0Enter()">
|
|
||||||
</td>
|
|
||||||
<td class="hint">
|
|
||||||
Mindestens {{ USER_PASSWORD_MIN_LENGTH }} Zeichen. Nicht nur Zahlen. Nicht nur Buchstaben.
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
Wiederholung:
|
|
||||||
</th>
|
|
||||||
<td>
|
|
||||||
<input #p1 type="text" [(ngModel)]="password1" [class.passwordInvalid]="p1Invalid()" [class.passwordValid]="p1Valid()" (keydown.enter)="p1Enter()">
|
|
||||||
</td>
|
|
||||||
<td class="hint">
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<th colspan="3"> </th>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<app-group-list [groups]="userService.user.groups"></app-group-list>
|
<app-group-list [groups]="userService.user.groups"></app-group-list>
|
||||||
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
@ -1,23 +0,0 @@
|
|||||||
th {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.passwordSet {
|
|
||||||
color: green;
|
|
||||||
}
|
|
||||||
|
|
||||||
.passwordNotSet {
|
|
||||||
color: darkred;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
-webkit-text-security: disc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.passwordInvalid {
|
|
||||||
background-color: indianred;
|
|
||||||
}
|
|
||||||
|
|
||||||
.passwordValid {
|
|
||||||
background-color: lightgreen;
|
|
||||||
}
|
|
||||||
@ -1,14 +1,10 @@
|
|||||||
import {Component, ElementRef, ViewChild} from '@angular/core';
|
import {Component, OnInit} 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";
|
||||||
|
|
||||||
const USER_NAME_MIN_LENGTH = 2;
|
|
||||||
|
|
||||||
const USER_PASSWORD_MIN_LENGTH = 10;
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-profile',
|
selector: 'app-profile',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
@ -22,18 +18,7 @@ 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 {
|
||||||
|
|
||||||
protected readonly USER_NAME_MIN_LENGTH = USER_NAME_MIN_LENGTH;
|
|
||||||
|
|
||||||
protected readonly USER_PASSWORD_MIN_LENGTH = USER_PASSWORD_MIN_LENGTH;
|
|
||||||
|
|
||||||
protected password0: string = "";
|
|
||||||
|
|
||||||
protected password1: string = "";
|
|
||||||
|
|
||||||
@ViewChild('p1')
|
|
||||||
p1!: ElementRef;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected readonly userService: UserService,
|
protected readonly userService: UserService,
|
||||||
@ -41,50 +26,8 @@ export class ProfileComponent {
|
|||||||
// -
|
// -
|
||||||
}
|
}
|
||||||
|
|
||||||
protected nameValidator(name: string): boolean {
|
ngOnInit(): void {
|
||||||
console.log(name);
|
this.userService.refresh();
|
||||||
return name.length >= USER_NAME_MIN_LENGTH && !/\s+|^[^a-zA-Z0-9]+$/.test(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected passwordValidator(password: string) {
|
|
||||||
return password.length >= USER_PASSWORD_MIN_LENGTH && !/^[a-zA-Z]+$|^[0-9]+$/.test(password);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected p0Invalid(): boolean {
|
|
||||||
return this.password0 !== '' && !this.passwordValidator(this.password0);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected p0Valid(): boolean {
|
|
||||||
return this.password0 !== '' && this.passwordValidator(this.password0);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected p1Invalid(): boolean {
|
|
||||||
return this.p0Invalid() || this.password0 !== this.password1;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected p1Valid(): boolean {
|
|
||||||
return this.p0Valid() && this.password0 === this.password1;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected p0Enter() {
|
|
||||||
if (this.p1Valid()) {
|
|
||||||
this.doChangePassword();
|
|
||||||
} else if (this.p0Valid()) {
|
|
||||||
this.p1.nativeElement.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected p1Enter() {
|
|
||||||
if (this.p1Valid()) {
|
|
||||||
this.doChangePassword();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private doChangePassword() {
|
|
||||||
this.userService.changePassword(this.password0, _ => {
|
|
||||||
this.password0 = "";
|
|
||||||
this.password1 = "";
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,2 @@
|
|||||||
<input
|
<input *ngIf="editable" [(ngModel)]="model" (focus)="begin()" (blur)="apply()" (keydown.enter)="apply()" (keydown.escape)="abort()">
|
||||||
*ngIf="editable"
|
|
||||||
[(ngModel)]="model"
|
|
||||||
(focus)="begin()"
|
|
||||||
(blur)="apply()"
|
|
||||||
(keydown.enter)="apply()"
|
|
||||||
(keydown.escape)="abort()"
|
|
||||||
[class.invalid]="validator !== null && !validator(model)"
|
|
||||||
[class.unsaved]="model !== _initial"
|
|
||||||
>
|
|
||||||
|
|
||||||
<div *ngIf="!editable">{{ _initial }}</div>
|
<div *ngIf="!editable">{{ _initial }}</div>
|
||||||
|
|||||||
@ -1,9 +1 @@
|
|||||||
@import "../../../common.less";
|
@import "../../../common.less";
|
||||||
|
|
||||||
.unsaved {
|
|
||||||
background-color: yellow;
|
|
||||||
}
|
|
||||||
|
|
||||||
.invalid {
|
|
||||||
background-color: indianred;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -34,9 +34,6 @@ export class TextComponent implements OnInit {
|
|||||||
@Input()
|
@Input()
|
||||||
editable: boolean = false;
|
editable: boolean = false;
|
||||||
|
|
||||||
@Input()
|
|
||||||
validator: ((password: string) => boolean) | null = null;
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.model = this._initial;
|
this.model = this._initial;
|
||||||
}
|
}
|
||||||
@ -46,7 +43,7 @@ export class TextComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
apply() {
|
apply() {
|
||||||
if (this.model !== this._initial && (!this.validator || this.validator(this.model))) {
|
if (this.model !== this._initial) {
|
||||||
this.onChange.emit(this.model);
|
this.onChange.emit(this.model);
|
||||||
}
|
}
|
||||||
this.end();
|
this.end();
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
@import "./config.less";
|
@import "./config.less";
|
||||||
|
|
||||||
.user {
|
.user {
|
||||||
|
clear: left;
|
||||||
float: left;
|
float: left;
|
||||||
padding: @halfSpace;
|
padding: @halfSpace;
|
||||||
background-color: lightskyblue;
|
background-color: lightskyblue;
|
||||||
|
|||||||
@ -3,9 +3,6 @@ package de.ph87.tools;
|
|||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
|
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
public class Backend extends SpringBootServletInitializer {
|
public class Backend extends SpringBootServletInitializer {
|
||||||
@ -14,9 +11,4 @@ public class Backend extends SpringBootServletInitializer {
|
|||||||
SpringApplication.run(Backend.class, args);
|
SpringApplication.run(Backend.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
|
||||||
public PasswordEncoder passwordEncoder() {
|
|
||||||
return new BCryptPasswordEncoder();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1,19 +1,17 @@
|
|||||||
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;
|
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;
|
||||||
@ -45,9 +43,4 @@ public class GroupDto implements IWebSocketMessage {
|
|||||||
this.initial = group.isInitial();
|
this.initial = group.isInitial();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Object> getWebsocketTopic() {
|
|
||||||
return List.of("Group", uuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -79,7 +79,7 @@ public class GroupService {
|
|||||||
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 User user = userService.getByPrivateUuidOrThrow(privateUuid);
|
||||||
final Group group = groupOfUserService.getGroupOfUser(request.uuid, user);
|
final Group group = groupOfUserService.getGroupOfUser(request.uuid, user);
|
||||||
if (!group.isOwnedBy(user)) {
|
if (group.isOwnedBy(user)) {
|
||||||
throw new ResponseStatusException(HttpStatus.FORBIDDEN);
|
throw new ResponseStatusException(HttpStatus.FORBIDDEN);
|
||||||
}
|
}
|
||||||
group.setPassword(request.password);
|
group.setPassword(request.password);
|
||||||
|
|||||||
@ -1,16 +0,0 @@
|
|||||||
package de.ph87.tools.user;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.ToString;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@ToString
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class LoginRequest {
|
|
||||||
|
|
||||||
public final String name;
|
|
||||||
|
|
||||||
public final String password;
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -6,7 +6,6 @@ import jakarta.persistence.Entity;
|
|||||||
import jakarta.persistence.Id;
|
import jakarta.persistence.Id;
|
||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
|
||||||
|
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -38,19 +37,8 @@ public class User implements IWebSocketMessage {
|
|||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
@NonNull
|
@NonNull
|
||||||
@Column(nullable = false, unique = true)
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private boolean admin = false;
|
private String name = "Neuer Benutzer";
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Column(nullable = false)
|
|
||||||
private String password = "";
|
|
||||||
|
|
||||||
public User(@NonNull final String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void touch() {
|
public void touch() {
|
||||||
lastAccess = ZonedDateTime.now();
|
lastAccess = ZonedDateTime.now();
|
||||||
@ -74,8 +62,4 @@ public class User implements IWebSocketMessage {
|
|||||||
return privateUuid.hashCode();
|
return privateUuid.hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPassword(@NonNull final PasswordEncoder passwordEncoder, @NonNull final String password) {
|
|
||||||
this.password = passwordEncoder.encode(password);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,24 +24,12 @@ public class UserController {
|
|||||||
return userService.getUserByPrivateUuidOrNull(userUuid, response);
|
return userService.getUserByPrivateUuidOrNull(userUuid, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@GetMapping("login")
|
|
||||||
public UserPrivateDto login(@NonNull @RequestBody final LoginRequest loginRequest, @NonNull final HttpServletResponse response) {
|
|
||||||
return userService.login(loginRequest, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@PostMapping("changeName")
|
@PostMapping("changeName")
|
||||||
public UserPrivateDto changeName(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String userUuid, @NonNull @RequestBody final String name) {
|
public UserPrivateDto changeName(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String userUuid, @NonNull @RequestBody final String name) {
|
||||||
return userService.changeName(userUuid, name);
|
return userService.changeName(userUuid, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@PostMapping("changePassword")
|
|
||||||
public UserPrivateDto changePassword(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String userUuid, @NonNull @RequestBody final String password) {
|
|
||||||
return userService.changePassword(userUuid, password);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@PostMapping("getCommonByUuid")
|
@PostMapping("getCommonByUuid")
|
||||||
public UserCommonDto getCommonByUuid(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull 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) {
|
||||||
|
|||||||
@ -1,18 +1,16 @@
|
|||||||
package de.ph87.tools.user;
|
package de.ph87.tools.user;
|
||||||
|
|
||||||
import de.ph87.tools.group.GroupDto;
|
import de.ph87.tools.group.GroupDto;
|
||||||
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;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@ToString
|
@ToString
|
||||||
public class UserPrivateDto implements IWebSocketMessage {
|
public class UserPrivateDto {
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public final String publicUuid;
|
public final String publicUuid;
|
||||||
@ -29,20 +27,12 @@ public class UserPrivateDto implements IWebSocketMessage {
|
|||||||
@NonNull
|
@NonNull
|
||||||
private final Set<GroupDto> groups;
|
private final Set<GroupDto> groups;
|
||||||
|
|
||||||
private final boolean password;
|
|
||||||
|
|
||||||
public UserPrivateDto(@NonNull final User user, final @NonNull Set<GroupDto> groups) {
|
public UserPrivateDto(@NonNull final User user, final @NonNull Set<GroupDto> groups) {
|
||||||
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.groups = groups;
|
this.groups = groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Object> getWebsocketTopic() {
|
|
||||||
return List.of("User", privateUuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import jakarta.annotation.Nullable;
|
|||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.context.ApplicationEventPublisher;
|
|
||||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
@ -22,8 +21,6 @@ public class UserPrivateMapper {
|
|||||||
|
|
||||||
private final GroupMapper groupMapper;
|
private final GroupMapper groupMapper;
|
||||||
|
|
||||||
private final ApplicationEventPublisher applicationEventPublisher;
|
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public UserPrivateDto toPrivateDto(@NonNull final User user) {
|
public UserPrivateDto toPrivateDto(@NonNull final User user) {
|
||||||
final Set<GroupDto> groups = groupMapper.findAllByUser(user);
|
final Set<GroupDto> groups = groupMapper.findAllByUser(user);
|
||||||
@ -38,11 +35,4 @@ public class UserPrivateMapper {
|
|||||||
return toPrivateDto(user);
|
return toPrivateDto(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public UserPrivateDto publish(@NonNull final User user) {
|
|
||||||
final UserPrivateDto dto = toPrivateDto(user);
|
|
||||||
applicationEventPublisher.publishEvent(dto);
|
|
||||||
return dto;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,10 +7,6 @@ import java.util.Optional;
|
|||||||
|
|
||||||
public interface UserRepository extends ListCrudRepository<User, String> {
|
public interface UserRepository extends ListCrudRepository<User, String> {
|
||||||
|
|
||||||
boolean existsByName(@NonNull String name);
|
|
||||||
|
|
||||||
Optional<User> findByName(@NonNull String name);
|
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
Optional<User> findByPublicUuid(@NonNull String publicUuid);
|
Optional<User> findByPublicUuid(@NonNull String publicUuid);
|
||||||
|
|
||||||
|
|||||||
@ -10,16 +10,13 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
|
||||||
import org.springframework.stereotype.Service;
|
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.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Random;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import static de.ph87.tools.UserArgumentResolver.USER_UUID_COOKIE_NAME;
|
import static de.ph87.tools.UserArgumentResolver.USER_UUID_COOKIE_NAME;
|
||||||
|
|
||||||
@ -30,16 +27,6 @@ import static de.ph87.tools.UserArgumentResolver.USER_UUID_COOKIE_NAME;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class UserService {
|
public class UserService {
|
||||||
|
|
||||||
private static final String USER_NAME_DEFAULT_BASE = "Neuer Benutzer";
|
|
||||||
|
|
||||||
private static final int NAME_MIN_LENGTH = 2;
|
|
||||||
|
|
||||||
private static final Pattern NAME_NEGATIVE_REGEX = Pattern.compile("\\s+|^[^a-zA-Z0-9]+$");
|
|
||||||
|
|
||||||
private static final int PASSWORD_MIN_LENGTH = 10;
|
|
||||||
|
|
||||||
private static final Pattern PASSWORD_NEGATIVE_REGEX = Pattern.compile("^[a-zA-Z]+$|^[0-9]+$");
|
|
||||||
|
|
||||||
private final UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
|
|
||||||
private final UserPrivateMapper userPrivateMapper;
|
private final UserPrivateMapper userPrivateMapper;
|
||||||
@ -48,20 +35,6 @@ public class UserService {
|
|||||||
|
|
||||||
private final GroupMapper groupMapper;
|
private final GroupMapper groupMapper;
|
||||||
|
|
||||||
private final PasswordEncoder passwordEncoder;
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public UserPrivateDto login(@NonNull final LoginRequest loginRequest, @NonNull final HttpServletResponse response) {
|
|
||||||
final User user = userRepository.findByName(loginRequest.name).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST));
|
|
||||||
if (passwordEncoder.matches(loginRequest.password, user.getPassword())) {
|
|
||||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
|
|
||||||
}
|
|
||||||
user.touch();
|
|
||||||
writeUserUuidCookie(response, user);
|
|
||||||
userPublicMapper.publish(user);
|
|
||||||
return userPrivateMapper.publish(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public User getUserByPrivateUuidOrElseCreate(@Nullable final String privateUuid, @NonNull final HttpServletResponse response) {
|
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);
|
final User user = Optional.ofNullable(privateUuid).map(userRepository::findByPrivateUuid).filter(Optional::isPresent).map(Optional::get).orElseGet(this::createUnchecked);
|
||||||
@ -82,41 +55,9 @@ public class UserService {
|
|||||||
@NonNull
|
@NonNull
|
||||||
public UserPrivateDto changeName(@NonNull final String privateUuid, @NonNull final String name) {
|
public UserPrivateDto changeName(@NonNull final String privateUuid, @NonNull final String name) {
|
||||||
return modify(privateUuid, user -> {
|
return modify(privateUuid, user -> {
|
||||||
if (user.getName().equals(name)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final User duplicate = userRepository.findByName(name).orElse(null);
|
|
||||||
if (duplicate != null) {
|
|
||||||
log.warn("Cannot change User name because of duplicate: name={}, user={}, duplicate={}", name, user, duplicate);
|
|
||||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
|
|
||||||
}
|
|
||||||
if (name.length() < NAME_MIN_LENGTH) {
|
|
||||||
log.warn("Cannot change User name: too short: length={}/{}, user={}", name.length(), NAME_MIN_LENGTH, user);
|
|
||||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
|
|
||||||
}
|
|
||||||
if (NAME_NEGATIVE_REGEX.matcher(name).find()) {
|
|
||||||
log.warn("Cannot change User name: Matches negative regex: name={}, user={}", name, user);
|
|
||||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
|
|
||||||
}
|
|
||||||
user.setName(name);
|
user.setName(name);
|
||||||
log.info("User name changed: user={}", user);
|
log.info("User name changed: user={}", user);
|
||||||
});
|
}, true);
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public UserPrivateDto changePassword(@NonNull final String privateUuid, @NonNull final String password) {
|
|
||||||
return modify(privateUuid, user -> {
|
|
||||||
if (password.length() < PASSWORD_MIN_LENGTH) {
|
|
||||||
log.warn("Cannot change User password: too short: length={}/{}, user={}", password.length(), PASSWORD_MIN_LENGTH, user);
|
|
||||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
|
|
||||||
}
|
|
||||||
if (PASSWORD_NEGATIVE_REGEX.matcher(password).find()) {
|
|
||||||
log.warn("Cannot change User password: Matches negative regex: user={}", user);
|
|
||||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
|
|
||||||
}
|
|
||||||
user.setPassword(passwordEncoder, password);
|
|
||||||
log.info("User password changed: user={}", user);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@ -136,29 +77,18 @@ public class UserService {
|
|||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private User createUnchecked() {
|
private User createUnchecked() {
|
||||||
final String name = generateFreeUserName();
|
final User user = userRepository.save(new User());
|
||||||
final User user = userRepository.save(new User(name));
|
|
||||||
log.info("User CREATED: {}", user);
|
log.info("User CREATED: {}", user);
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private String generateFreeUserName() {
|
private UserPrivateDto modify(@NonNull final String privateUuid, @NonNull Consumer<User> modifier, final boolean doPublish) {
|
||||||
int index = new Random().nextInt(1234, 5723);
|
|
||||||
String name = USER_NAME_DEFAULT_BASE;
|
|
||||||
while (userRepository.existsByName(name)) {
|
|
||||||
index++;
|
|
||||||
name = "%s #%d".formatted(USER_NAME_DEFAULT_BASE, index);
|
|
||||||
}
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private UserPrivateDto modify(@NonNull final String privateUuid, @NonNull Consumer<User> modifier) {
|
|
||||||
final User user = getByPrivateUuidOrThrow(privateUuid);
|
final User user = getByPrivateUuidOrThrow(privateUuid);
|
||||||
modifier.accept(user);
|
modifier.accept(user);
|
||||||
|
if (doPublish) {
|
||||||
userPublicMapper.publish(user);
|
userPublicMapper.publish(user);
|
||||||
userPrivateMapper.publish(user);
|
}
|
||||||
return userPrivateMapper.toPrivateDto(user);
|
return userPrivateMapper.toPrivateDto(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,7 +122,7 @@ public class UserService {
|
|||||||
if (user != null) {
|
if (user != null) {
|
||||||
cookie.setValue(user.getPrivateUuid());
|
cookie.setValue(user.getPrivateUuid());
|
||||||
}
|
}
|
||||||
cookie.setMaxAge(PASSWORD_MIN_LENGTH * 365 * 24 * 60 * 60);
|
cookie.setMaxAge(10 * 365 * 24 * 60 * 60);
|
||||||
cookie.setPath("/");
|
cookie.setPath("/");
|
||||||
response.addCookie(cookie);
|
response.addCookie(cookie);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,28 +0,0 @@
|
|||||||
package de.ph87.tools.web;
|
|
||||||
|
|
||||||
import lombok.NonNull;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.messaging.Message;
|
|
||||||
import org.springframework.messaging.MessageChannel;
|
|
||||||
import org.springframework.messaging.simp.stomp.StompCommand;
|
|
||||||
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
|
|
||||||
import org.springframework.messaging.support.ChannelInterceptor;
|
|
||||||
import org.springframework.messaging.support.MessageHeaderAccessor;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
@Component
|
|
||||||
public class SubscriptionInterceptor implements ChannelInterceptor {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Message<?> preSend(@NonNull final Message<?> message, @NonNull final MessageChannel channel) {
|
|
||||||
final StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
|
|
||||||
if (accessor != null && StompCommand.SUBSCRIBE.equals(accessor.getCommand())) {
|
|
||||||
final String sessionId = accessor.getSessionId();
|
|
||||||
final String destination = accessor.getDestination();
|
|
||||||
log.info("User subscribed with sessionId: {} to destination: {}", sessionId, destination);
|
|
||||||
}
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user