kick, ban, unbann, better RequestDtos
This commit is contained in:
parent
1f269a31f7
commit
dbceff2b58
@ -1,24 +1,27 @@
|
||||
import {validateBoolean, validateDate, validateString} from "../common/validators";
|
||||
import {UserPublic} from "./UserPublic";
|
||||
|
||||
export class UserPrivate {
|
||||
export class UserPrivate extends UserPublic {
|
||||
|
||||
constructor(
|
||||
readonly privateUuid: string,
|
||||
readonly publicUuid: string,
|
||||
publicUuid: string,
|
||||
readonly created: Date,
|
||||
readonly name: string,
|
||||
name: string,
|
||||
readonly password: boolean,
|
||||
admin: boolean,
|
||||
) {
|
||||
// -
|
||||
super(publicUuid, name, admin);
|
||||
}
|
||||
|
||||
static fromJson(json: any): UserPrivate {
|
||||
static override fromJson(json: any): UserPrivate {
|
||||
return new UserPrivate(
|
||||
validateString(json['privateUuid']),
|
||||
validateString(json['publicUuid']),
|
||||
validateDate(json['created']),
|
||||
validateString(json['name']),
|
||||
validateBoolean(json['password']),
|
||||
validateBoolean(json['admin']),
|
||||
);
|
||||
}
|
||||
|
||||
@ -29,8 +32,4 @@ export class UserPrivate {
|
||||
return UserPrivate.fromJson(json);
|
||||
}
|
||||
|
||||
static samePrivateUuid(a: UserPrivate, b: UserPrivate): boolean {
|
||||
return a.privateUuid === b.privateUuid;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
import {validateString} from "../common/validators";
|
||||
import {validateBoolean, validateString} from "../common/validators";
|
||||
import {UserPrivate} from "./UserPrivate";
|
||||
|
||||
export class UserPublic {
|
||||
|
||||
constructor(
|
||||
readonly publicUuid: string,
|
||||
readonly name: string,
|
||||
readonly admin: boolean,
|
||||
) {
|
||||
// -
|
||||
}
|
||||
@ -13,6 +15,7 @@ export class UserPublic {
|
||||
return new UserPublic(
|
||||
validateString(json['publicUuid']),
|
||||
validateString(json['name']),
|
||||
validateBoolean(json['admin']),
|
||||
);
|
||||
}
|
||||
|
||||
@ -20,4 +23,8 @@ export class UserPublic {
|
||||
return a.name.localeCompare(b.name);
|
||||
}
|
||||
|
||||
equals(user: UserPublic | UserPrivate | null) {
|
||||
return user !== null && this.publicUuid === user.publicUuid;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -85,7 +85,7 @@ export class UserService {
|
||||
}
|
||||
|
||||
iOwn(group: Group): boolean {
|
||||
return this.user?.publicUuid === group.owner.publicUuid;
|
||||
return group.owner.equals(this.user);
|
||||
}
|
||||
|
||||
private setUser(user: UserPrivate | null) {
|
||||
@ -99,4 +99,8 @@ export class UserService {
|
||||
this.subject.next(user);
|
||||
}
|
||||
|
||||
iAm(user: UserPublic | UserPrivate | null) {
|
||||
return this.user !== null && this.user.equals(user);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import {UserPublic} from "../User/UserPublic";
|
||||
import {validateBoolean, validateDate, validateList, validateString} from "../common/validators";
|
||||
import {UserPrivate} from "../User/UserPrivate";
|
||||
|
||||
export class Group {
|
||||
|
||||
@ -10,6 +11,7 @@ export class Group {
|
||||
readonly title: string,
|
||||
readonly password: string,
|
||||
readonly users: UserPublic[],
|
||||
readonly banned: UserPublic[],
|
||||
readonly initial: boolean,
|
||||
) {
|
||||
// -
|
||||
@ -27,6 +29,7 @@ export class Group {
|
||||
validateString(json['title']),
|
||||
validateString(json['password']),
|
||||
validateList(json['users'], UserPublic.fromJson),
|
||||
validateList(json['banned'], UserPublic.fromJson),
|
||||
validateBoolean(json['initial']),
|
||||
);
|
||||
}
|
||||
@ -52,5 +55,13 @@ export class Group {
|
||||
return a.created.getTime() - b.created.getTime();
|
||||
}
|
||||
|
||||
isOwnedBy(user: UserPublic | UserPrivate | null) {
|
||||
return user !== null && user.equals(this.owner);
|
||||
}
|
||||
|
||||
bannedByName() {
|
||||
return this.banned.sort(UserPublic.compareName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -6,6 +6,11 @@ import {UserService} from "../User/user.service";
|
||||
import {validateBoolean} from "../common/validators";
|
||||
import {Injectable} from "@angular/core";
|
||||
import {Subscribed} from "../Subscribed";
|
||||
import {UserPublic} from "../User/UserPublic";
|
||||
import {GroupUserRequest} from "./requests/GroupUserRequest";
|
||||
import {GroupChangeTitleRequest} from "./requests/GroupChangeTitleRequest";
|
||||
import {GroupChangePasswordRequest} from "./requests/GroupChangePasswordRequest";
|
||||
import {GroupJoinRequest} from "./requests/GroupJoinRequest";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@ -41,26 +46,17 @@ export class GroupService {
|
||||
}
|
||||
|
||||
changeTitle(group: Group, title: string, next?: Next<Group>): void {
|
||||
const data = {
|
||||
uuid: group.uuid,
|
||||
title: title,
|
||||
};
|
||||
const data = new GroupChangeTitleRequest(group, title);
|
||||
this.api.postSingle(['Group', 'changeTitle'], data, Group.fromJson, next);
|
||||
}
|
||||
|
||||
changePassword(group: Group, password: string, next?: Next<Group>): void {
|
||||
const data = {
|
||||
uuid: group.uuid,
|
||||
password: password,
|
||||
};
|
||||
const data = new GroupChangePasswordRequest(group, 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,
|
||||
};
|
||||
join(groupUuid: string, password: string, next: Next<Group>): void {
|
||||
const data = new GroupJoinRequest(groupUuid, password);
|
||||
this.api.postSingle(['Group', 'join'], data, Group.fromJson, next);
|
||||
}
|
||||
|
||||
@ -84,5 +80,17 @@ export class GroupService {
|
||||
this.api.postNone(['Group', 'delete'], group.uuid, next);
|
||||
}
|
||||
|
||||
kick(group: Group, user: UserPublic, next?: Next<void>): void {
|
||||
this.api.postNone(['Group', 'kick'], new GroupUserRequest(group, user), next);
|
||||
}
|
||||
|
||||
ban(group: Group, user: UserPublic, next?: Next<void>): void {
|
||||
this.api.postNone(['Group', 'ban'], new GroupUserRequest(group, user), next);
|
||||
}
|
||||
|
||||
unban(group: Group, user: UserPublic, next?: Next<void>): void {
|
||||
this.api.postNone(['Group', 'unban'], new GroupUserRequest(group, user), next);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
import {Group} from "../Group";
|
||||
|
||||
export class GroupChangePasswordRequest {
|
||||
|
||||
readonly groupUuid: string;
|
||||
|
||||
readonly password: string;
|
||||
|
||||
constructor(group: Group, password: string) {
|
||||
this.groupUuid = group.uuid;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
import {Group} from "../Group";
|
||||
|
||||
export class GroupChangeTitleRequest {
|
||||
|
||||
readonly groupUuid: string;
|
||||
|
||||
readonly title: string;
|
||||
|
||||
constructor(group: Group, title: string) {
|
||||
this.groupUuid = group.uuid;
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
export class GroupJoinRequest {
|
||||
|
||||
readonly groupUuid: string;
|
||||
|
||||
readonly password: string;
|
||||
|
||||
constructor(groupUuid: string, password: string) {
|
||||
this.groupUuid = groupUuid;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
import {Group} from "../Group";
|
||||
import {UserPublic} from "../../User/UserPublic";
|
||||
|
||||
export class GroupUserRequest {
|
||||
|
||||
readonly groupUuid: string;
|
||||
|
||||
readonly userPublicUuid: string;
|
||||
|
||||
constructor(group: Group, user: UserPublic) {
|
||||
this.groupUuid = group.uuid;
|
||||
this.userPublicUuid = user.publicUuid;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import {Group} from "../../group/Group";
|
||||
import {validateDate, validateNumberOrNull, validateString} from "../../common/validators";
|
||||
import {validateDate, validateDateOrNull, validateNumberOrNull, validateString} from "../../common/validators";
|
||||
|
||||
export class Numbers {
|
||||
|
||||
@ -7,6 +7,7 @@ export class Numbers {
|
||||
readonly uuid: string,
|
||||
readonly group: Group,
|
||||
readonly date: Date,
|
||||
readonly read: Date | null,
|
||||
readonly number: number | null,
|
||||
) {
|
||||
// -
|
||||
@ -17,6 +18,7 @@ export class Numbers {
|
||||
validateString(json['uuid']),
|
||||
Group.fromJson(json['group']),
|
||||
validateDate(json['date']),
|
||||
validateDateOrNull(json['read']),
|
||||
validateNumberOrNull(json['number']),
|
||||
);
|
||||
}
|
||||
|
||||
@ -30,8 +30,8 @@ export class NumbersService {
|
||||
this.api.getPage(['Numbers', 'page', groupUuid, page, pageSize], Numbers.fromJson, next);
|
||||
}
|
||||
|
||||
byUuid(numbersUuid: string, next: Next<Numbers>): void {
|
||||
this.api.postSingle(['Numbers', 'byUuid'], numbersUuid, Numbers.fromJson, next);
|
||||
fetchAndMarkAsRead(numbersUuid: string, next: Next<Numbers>): void {
|
||||
this.api.postSingle(['Numbers', 'fetchAndMarkAsRead'], numbersUuid, Numbers.fromJson, next);
|
||||
}
|
||||
|
||||
canAccess(numbersUuid: string, next: Next<boolean>): void {
|
||||
@ -42,4 +42,8 @@ export class NumbersService {
|
||||
this.api.postSingle(['Numbers', 'getGroupUuid'], numbersUuid, validateString, next);
|
||||
}
|
||||
|
||||
goto(numbers: Numbers) {
|
||||
this.router.navigate(['Numbers', numbers.uuid]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<div class="tileContainer">
|
||||
|
||||
<div class="tile" *ngIf="granted === false">
|
||||
<div class="tile" *ngIf="group.value === null && granted === false">
|
||||
<div class="tileInner">
|
||||
<div class="tileTitle">
|
||||
Passwort
|
||||
@ -19,6 +19,7 @@
|
||||
Gruppe
|
||||
</div>
|
||||
<div class="tileContent">
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Erstellt</th>
|
||||
@ -38,16 +39,69 @@
|
||||
<ng-container *ngIf="!userService.iOwn(group.value)"></ng-container>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Teilnehmer</th>
|
||||
</table>
|
||||
|
||||
<div class="buttons">
|
||||
<div class="button buttonRight buttonDelete" (click)="groupDelete(group.value)">
|
||||
Gruppe löschen
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tile">
|
||||
<div class="tileInner">
|
||||
<div class="tileTitle">
|
||||
Mitglieder
|
||||
</div>
|
||||
<div class="tileContent">
|
||||
<div class="numbers">
|
||||
<table>
|
||||
<tr [class.user_owner]="group.value.isOwner(user)" *ngFor="let user of group.value.usersByNameOwnerFirst()">
|
||||
<td (click)="userService.goto(user)">{{ user.name }}</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>
|
||||
<span class="owner" *ngIf="group.value.isOwnedBy(user)">
|
||||
Admin
|
||||
</span>
|
||||
<div class="buttons">
|
||||
<ng-container *ngIf="userService.iOwn(group.value) && !userService.iAm(user)">
|
||||
<div class="button buttonRight buttonBan" (click)="groupService.ban(group.value, user)">Verbannen</div>
|
||||
<div class="button buttonRight buttonRemove" (click)="groupService.kick(group.value, user)">Entfernen</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tile" *ngIf="group.value.banned.length > 0">
|
||||
<div class="tileInner">
|
||||
<div class="tileTitle">
|
||||
Verbannt
|
||||
</div>
|
||||
<div class="tileContent">
|
||||
<div class="numbers">
|
||||
<table>
|
||||
<tr [class.user_owner]="group.value.isOwner(user)" *ngFor="let user of group.value.bannedByName()" (click)="userService.goto(user)">
|
||||
<td>{{ user.name }}</td>
|
||||
<td>
|
||||
<div class="buttons">
|
||||
<ng-container *ngIf="userService.iOwn(group.value) && !userService.iAm(user)">
|
||||
<div class="button buttonRight buttonUnban" (click)="groupService.unban(group.value, user)">Aufheben</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tile">
|
||||
<div class="tileInner">
|
||||
@ -55,28 +109,17 @@
|
||||
Nummern
|
||||
</div>
|
||||
<div class="tileContent">
|
||||
<div class="buttons">
|
||||
<div class="button buttonRight buttonNext" *ngIf="userService.iOwn(group.value)" (click)="numbersService.create(group.value.uuid)">+ Nächste Runde</div>
|
||||
</div>
|
||||
<div class="numbers">
|
||||
<table>
|
||||
<tr class="number" *ngFor="let numbers of numbersList.content">
|
||||
<tr class="number" *ngFor="let numbers of numbersList.content" (click)="numbersService.goto(numbers)" [class.read]="numbers.read !== null">
|
||||
<td>{{ numbers.date | relative:now }}</td>
|
||||
<td>{{ numbers.number || '-' }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<button *ngIf="userService.iOwn(group.value)" (click)="numbersService.create(group.value.uuid)">Numbers</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tile" *ngIf="userService.iOwn(group.value)">
|
||||
<div class="tileInner">
|
||||
<div class="tileTitle">
|
||||
Löschen
|
||||
</div>
|
||||
<div class="tileContent">
|
||||
<button (click)="delete(group.value)">
|
||||
Löschen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -3,3 +3,23 @@
|
||||
th {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.read {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.unread {
|
||||
background-color: lightskyblue;
|
||||
}
|
||||
|
||||
.buttonRemove {
|
||||
color: orange;
|
||||
}
|
||||
|
||||
.buttonBan {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.buttonUnban {
|
||||
color: green;
|
||||
}
|
||||
|
||||
@ -94,8 +94,10 @@ export class GroupComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
protected delete(group: Group): void {
|
||||
protected groupDelete(group: Group): void {
|
||||
if (confirm("Gruppe \"" + group.title + "\" wirklich löschen?")) {
|
||||
this.groupService.delete(group, () => this.groupService.gotoGroups());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -11,7 +11,9 @@
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="tileFooter">
|
||||
<button (click)="create()">+ Neue Gruppen erstellen</button>
|
||||
<div class="buttons">
|
||||
<div class="button buttonRight buttonCreate" (click)="create()">+ Neue Gruppen erstellen</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -5,13 +5,15 @@ import {NgIf} from "@angular/common";
|
||||
import {GroupService} from "../../../api/group/group.service";
|
||||
import {Group} from "../../../api/group/Group";
|
||||
import {Subscription} from "rxjs";
|
||||
import {ReactiveFormsModule} from "@angular/forms";
|
||||
|
||||
@Component({
|
||||
selector: 'app-groups',
|
||||
standalone: true,
|
||||
imports: [
|
||||
GroupListComponent,
|
||||
NgIf
|
||||
NgIf,
|
||||
ReactiveFormsModule
|
||||
],
|
||||
templateUrl: './groups.component.html',
|
||||
styleUrl: './groups.component.less'
|
||||
|
||||
@ -5,16 +5,18 @@
|
||||
[class.date_old]="now.getTime() - numbers.date.getTime() >= 5 * 60 * 1000"
|
||||
>
|
||||
|
||||
<button *ngIf="userService.iOwn(numbers.group)" (click)="numbersService.create(numbers.group.uuid)">
|
||||
Nächste Runde
|
||||
</button>
|
||||
|
||||
<div class="date">
|
||||
{{ numbers.date | relative:now }}
|
||||
</div>
|
||||
|
||||
<div class="huge">
|
||||
<div class="huge" (click)="gotoGroup()">
|
||||
{{ numbers.number || '-' }}
|
||||
</div>
|
||||
|
||||
<div class="buttons">
|
||||
<div class="button buttonRight buttonNext" *ngIf="userService.iOwn(numbers.group)" (click)="numbersService.create(numbers.group.uuid)">
|
||||
Nächste Runde
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import {Component, OnDestroy, OnInit} from '@angular/core';
|
||||
import {Component, HostListener, OnDestroy, OnInit} from '@angular/core';
|
||||
import {Numbers} from "../../../api/tools/Numbers/Numbers";
|
||||
import {ActivatedRoute} from "@angular/router";
|
||||
import {NumbersService} from "../../../api/tools/Numbers/numbers.service";
|
||||
@ -46,7 +46,7 @@ export class NumbersComponent implements OnInit, OnDestroy {
|
||||
if (uuid) {
|
||||
this.numbersService.canAccess(uuid, granted => {
|
||||
if (granted) {
|
||||
this.numbersService.byUuid(uuid, numbers => this.numbers = numbers);
|
||||
this.numbersService.fetchAndMarkAsRead(uuid, numbers => this.numbers = numbers);
|
||||
} else {
|
||||
this.numbersService.getGroupUuid(uuid, groupUuid => this.groupService.goto(groupUuid));
|
||||
}
|
||||
@ -64,4 +64,11 @@ export class NumbersComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('window:keydown.escape')
|
||||
protected gotoGroup() {
|
||||
if (this.numbers?.group) {
|
||||
this.groupService.goto(this.numbers.group.uuid);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
<ng-container *ngIf="visible">
|
||||
<h1>Passwort</h1>
|
||||
<input type="text" [(ngModel)]="password" (keydown.enter)="join.emit(password)">
|
||||
<button (click)="join.emit(password)">Beitreten</button>
|
||||
<div class="buttons">
|
||||
<div class="button buttonRight buttonJoin" (click)="join.emit(password)">Beitreten</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
@ -1,2 +1,52 @@
|
||||
@import "./tile.less";
|
||||
@import "./user.less";
|
||||
|
||||
.buttons {
|
||||
margin-bottom: @halfSpace;
|
||||
|
||||
.button {
|
||||
float: left;
|
||||
margin-left: @quarterSpace;
|
||||
margin-right: @quarterSpace;
|
||||
padding: @halfSpace;
|
||||
border-radius: @halfSpace;
|
||||
background-color: #dddddd;
|
||||
}
|
||||
|
||||
.buttonRight {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.buttonCreate {
|
||||
background-color: lightgreen;
|
||||
}
|
||||
|
||||
.buttonCreate:hover {
|
||||
background-color: limegreen;
|
||||
}
|
||||
|
||||
.buttonJoin {
|
||||
background-color: lightskyblue;
|
||||
}
|
||||
|
||||
.buttonJoin:hover {
|
||||
background-color: dodgerblue;
|
||||
}
|
||||
|
||||
.buttonNext {
|
||||
background-color: lightgreen;
|
||||
}
|
||||
|
||||
.buttonNext:hover {
|
||||
background-color: limegreen;
|
||||
}
|
||||
|
||||
.buttonDelete {
|
||||
background-color: indianred;
|
||||
}
|
||||
|
||||
.buttonDelete:hover {
|
||||
background-color: palevioletred;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,2 +1,3 @@
|
||||
@space: 0.5em;
|
||||
@halfSpace: calc(@space / 2);
|
||||
@quarterSpace: calc(@halfSpace / 2);
|
||||
|
||||
23
src/main/java/de/ph87/tools/group/AdminRemoveResult.java
Normal file
23
src/main/java/de/ph87/tools/group/AdminRemoveResult.java
Normal file
@ -0,0 +1,23 @@
|
||||
package de.ph87.tools.group;
|
||||
|
||||
import de.ph87.tools.user.User;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.ToString;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
public class AdminRemoveResult {
|
||||
|
||||
@NonNull
|
||||
public final Group group;
|
||||
|
||||
@NonNull
|
||||
public final User kicked;
|
||||
|
||||
public AdminRemoveResult(@NonNull final GroupAccess access, @NonNull final User kicked) {
|
||||
this.group = access.group;
|
||||
this.kicked = kicked;
|
||||
}
|
||||
|
||||
}
|
||||
@ -55,8 +55,15 @@ public class Group extends GroupAbstract implements IWebSocketMessage {
|
||||
@NonNull
|
||||
@ManyToMany
|
||||
@ToString.Exclude
|
||||
@JoinTable(name = "`group_user`")
|
||||
private Set<User> users = new HashSet<>();
|
||||
|
||||
@NonNull
|
||||
@ManyToMany
|
||||
@ToString.Exclude
|
||||
@JoinTable(name = "`group_banned`")
|
||||
private Set<User> banned = new HashSet<>();
|
||||
|
||||
@Setter
|
||||
@NonNull
|
||||
@Column(nullable = false)
|
||||
@ -89,4 +96,8 @@ public class Group extends GroupAbstract implements IWebSocketMessage {
|
||||
return List.of("Number", uuid);
|
||||
}
|
||||
|
||||
public boolean isBanned(@NonNull final User user) {
|
||||
return banned.stream().anyMatch(u -> u.equals(user));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ public class GroupAccessService {
|
||||
}
|
||||
|
||||
public void delete(@NonNull final UserPrivateUuid userPrivateUuid, @NonNull final GroupUuid groupUuid) {
|
||||
final GroupAccess groupAccess = accessAsOwner(userPrivateUuid, groupUuid);
|
||||
final GroupAccess groupAccess = adminAccess(userPrivateUuid, groupUuid);
|
||||
numbersRepository.deleteAllByGroup(groupAccess.group);
|
||||
groupRepository.delete(groupAccess.group);
|
||||
log.info("Group deleted: group={}", groupAccess.group);
|
||||
@ -50,7 +50,7 @@ public class GroupAccessService {
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public GroupAccess accessAsOwner(@NonNull final UserPrivateUuid userPrivateUuid, @NonNull final GroupUuid groupUuid) {
|
||||
public GroupAccess adminAccess(@NonNull final UserPrivateUuid userPrivateUuid, @NonNull final GroupUuid groupUuid) {
|
||||
final GroupAccess groupAccess = access(userPrivateUuid, groupUuid);
|
||||
if (!groupAccess.group.isOwnedBy(groupAccess.user)) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
|
||||
|
||||
@ -3,6 +3,7 @@ package de.ph87.tools.group;
|
||||
import de.ph87.tools.group.requests.GroupChangePasswordRequest;
|
||||
import de.ph87.tools.group.requests.GroupChangeTitleRequest;
|
||||
import de.ph87.tools.group.requests.GroupJoinRequest;
|
||||
import de.ph87.tools.group.requests.GroupUserRequest;
|
||||
import de.ph87.tools.group.uuid.GroupUuid;
|
||||
import de.ph87.tools.user.uuid.UserPrivateUuid;
|
||||
import de.ph87.tools.user.uuid.UserPublicUuid;
|
||||
@ -61,6 +62,21 @@ public class GroupController {
|
||||
return groupService.join(privateUuid, request, response);
|
||||
}
|
||||
|
||||
@PostMapping("kick")
|
||||
public void kick(@NonNull final UserPrivateUuid privateUuid, @NonNull @RequestBody final GroupUserRequest request) {
|
||||
groupService.kick(privateUuid, request);
|
||||
}
|
||||
|
||||
@PostMapping("ban")
|
||||
public void ban(@NonNull final UserPrivateUuid privateUuid, @NonNull @RequestBody final GroupUserRequest request) {
|
||||
groupService.ban(privateUuid, request);
|
||||
}
|
||||
|
||||
@PostMapping("unban")
|
||||
public void unban(@NonNull final UserPrivateUuid privateUuid, @NonNull @RequestBody final GroupUserRequest request) {
|
||||
groupService.unban(privateUuid, request);
|
||||
}
|
||||
|
||||
@PostMapping("changeTitle")
|
||||
public GroupDto changeTitle(@NonNull final UserPrivateUuid privateUuid, @NonNull @RequestBody final GroupChangeTitleRequest request) {
|
||||
return groupService.changeTitle(privateUuid, request);
|
||||
|
||||
@ -34,15 +34,19 @@ public class GroupDto {
|
||||
@NonNull
|
||||
public final Set<UserPublicDto> users;
|
||||
|
||||
@NonNull
|
||||
public final Set<UserPublicDto> banned;
|
||||
|
||||
public final boolean initial;
|
||||
|
||||
public GroupDto(@NonNull final Group group, @NonNull final UserPublicDto owner, @NonNull final Set<UserPublicDto> users) {
|
||||
public GroupDto(@NonNull final Group group, @NonNull final UserPublicDto owner, @NonNull final Set<UserPublicDto> users, @NonNull final Set<UserPublicDto> banned) {
|
||||
this.uuid = group.getUuid();
|
||||
this.title = group.getTitle();
|
||||
this.created = group.getCreated();
|
||||
this.password = group.getPassword();
|
||||
this.owner = owner;
|
||||
this.users = users;
|
||||
this.banned = banned;
|
||||
this.initial = group.isInitial();
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ package de.ph87.tools.group;
|
||||
import de.ph87.tools.group.requests.GroupChangePasswordRequest;
|
||||
import de.ph87.tools.group.requests.GroupChangeTitleRequest;
|
||||
import de.ph87.tools.group.requests.GroupJoinRequest;
|
||||
import de.ph87.tools.group.requests.GroupUserRequest;
|
||||
import de.ph87.tools.group.uuid.GroupUuid;
|
||||
import de.ph87.tools.user.User;
|
||||
import de.ph87.tools.user.UserPublicDto;
|
||||
@ -41,7 +42,7 @@ public class GroupService {
|
||||
public GroupDto create(@Nullable final UserPrivateUuid privateUuid, @NonNull final HttpServletResponse response) {
|
||||
final User user = userService.getUserByPrivateUuidOrElseCreate(privateUuid, response);
|
||||
final Group group = createUnchecked(user);
|
||||
return doJoinUnchecked(group, user);
|
||||
return _join_unchecked(group, user);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@ -53,22 +54,69 @@ public class GroupService {
|
||||
@NonNull
|
||||
public GroupDto join(@Nullable final UserPrivateUuid privateUuid, @NonNull final GroupJoinRequest request, @NonNull final HttpServletResponse response) {
|
||||
final User user = userService.getUserByPrivateUuidOrElseCreate(privateUuid, response);
|
||||
final Group group = getByGroupByGroupUuid(request.uuid);
|
||||
final Group group = getGroupByGroupUuid(request.groupUuid);
|
||||
if (group.isBanned(user)) {
|
||||
log.error("User is banned from Group and cannot join it: user={}, group={}", user, group);
|
||||
throw new ResponseStatusException(HttpStatus.FORBIDDEN);
|
||||
}
|
||||
if (!group.getPassword().equals(request.password)) {
|
||||
log.error("Wrong password: user={}, group={}", user, group);
|
||||
throw new ResponseStatusException(HttpStatus.FORBIDDEN);
|
||||
}
|
||||
return doJoinUnchecked(group, user);
|
||||
return _join_unchecked(group, user);
|
||||
}
|
||||
|
||||
public void leave(@NonNull final UserPrivateUuid privateUuid, @NonNull final GroupUuid groupUuid) {
|
||||
final User user = userService.access(privateUuid);
|
||||
final Group group = getGroupByGroupUuid(groupUuid);
|
||||
if (group.isOwnedBy(user)) {
|
||||
// owner cannot remove itself from group
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
doLeaveUnchecked(group, user);
|
||||
}
|
||||
|
||||
public void kick(@NonNull final UserPrivateUuid privateUuid, @NonNull final GroupUserRequest request) {
|
||||
final AdminRemoveResult result = adminRemoveUser(privateUuid, request);
|
||||
log.info("User kicked out of group: user={}, group={}", result.kicked, result.group);
|
||||
publish(result.group);
|
||||
}
|
||||
|
||||
public void ban(@NonNull final UserPrivateUuid privateUuid, @NonNull final GroupUserRequest request) {
|
||||
final AdminRemoveResult result = adminRemoveUser(privateUuid, request);
|
||||
result.group.getBanned().add(result.kicked);
|
||||
log.info("User banned from group: user={}, group={}", result.kicked, result.group);
|
||||
publish(result.group);
|
||||
}
|
||||
|
||||
public void unban(@NonNull final UserPrivateUuid privateUuid, @NonNull final GroupUserRequest request) {
|
||||
final GroupAccess access = groupAccessService.adminAccess(privateUuid, request.groupUuid);
|
||||
final User user = access.group.getBanned().stream().filter(u -> u.getPublicUuid().equals(request.userPublicUuid)).findFirst().orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST));
|
||||
access.group.getBanned().remove(user);
|
||||
log.info("User unbanned from group: user={}, group={}", user, access.group);
|
||||
publish(access.group);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Group getByGroupByGroupUuid(@NonNull final GroupUuid groupUuid) {
|
||||
private AdminRemoveResult adminRemoveUser(@NonNull final UserPrivateUuid privateUuid, @NonNull final GroupUserRequest request) {
|
||||
final GroupAccess access = groupAccessService.adminAccess(privateUuid, request.groupUuid);
|
||||
final User user = access.group.getUsers().stream().filter(u -> u.getPublicUuid().equals(request.userPublicUuid)).findFirst().orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST));
|
||||
if (user.equals(access.user)) {
|
||||
// owner cannot kick itself from group
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
access.group.getUsers().remove(user);
|
||||
return new AdminRemoveResult(access, user);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Group getGroupByGroupUuid(@NonNull final GroupUuid groupUuid) {
|
||||
return groupRepository.findByUuid(groupUuid.uuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public GroupDto changeTitle(@NonNull final UserPrivateUuid privateUuid, @NonNull final GroupChangeTitleRequest request) {
|
||||
final GroupAccess ug = groupAccessService.access(privateUuid, request.uuid);
|
||||
final GroupAccess ug = groupAccessService.access(privateUuid, request.groupUuid);
|
||||
if (!ug.group.isOwnedBy(ug.user)) {
|
||||
throw new ResponseStatusException(HttpStatus.FORBIDDEN);
|
||||
}
|
||||
@ -78,17 +126,11 @@ public class GroupService {
|
||||
|
||||
@NonNull
|
||||
public GroupDto changePassword(@NonNull final UserPrivateUuid privateUuid, @NonNull final GroupChangePasswordRequest request) {
|
||||
final GroupAccess access = groupAccessService.accessAsOwner(privateUuid, request.uuid);
|
||||
final GroupAccess access = groupAccessService.adminAccess(privateUuid, request.groupUuid);
|
||||
access.group.setPassword(request.password);
|
||||
return publish(access.group);
|
||||
}
|
||||
|
||||
public void leave(@NonNull final UserPrivateUuid privateUuid, @NonNull final GroupUuid groupUuid) {
|
||||
final User user = userService.access(privateUuid);
|
||||
final Group group = getByGroupByGroupUuid(groupUuid);
|
||||
doLeaveUnchecked(group, user);
|
||||
}
|
||||
|
||||
/* CREATE, JOIN, LEAVE -------------------------------------------------------------------------- */
|
||||
|
||||
@NonNull
|
||||
@ -99,7 +141,7 @@ public class GroupService {
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private GroupDto doJoinUnchecked(@NonNull final Group group, @NonNull final User user) {
|
||||
private GroupDto _join_unchecked(@NonNull final Group group, @NonNull final User user) {
|
||||
group.getUsers().add(user);
|
||||
group.touch();
|
||||
user.touch();
|
||||
@ -121,7 +163,8 @@ public class GroupService {
|
||||
public GroupDto toDto(@NonNull final Group group) {
|
||||
final UserPublicDto owner = new UserPublicDto(group.getOwner());
|
||||
final Set<UserPublicDto> users = group.getUsers().stream().map(UserPublicDto::new).collect(Collectors.toSet());
|
||||
return new GroupDto(group, owner, users);
|
||||
final Set<UserPublicDto> banned = group.getBanned().stream().map(UserPublicDto::new).collect(Collectors.toSet());
|
||||
return new GroupDto(group, owner, users, banned);
|
||||
}
|
||||
|
||||
/* PUBLISH -------------------------------------------------------------------------------------- */
|
||||
|
||||
@ -15,7 +15,7 @@ public class GroupChangePasswordRequest {
|
||||
|
||||
@NonNull
|
||||
@JsonDeserialize(using = GroupUuidDeserializer.class)
|
||||
public final GroupUuid uuid;
|
||||
public final GroupUuid groupUuid;
|
||||
|
||||
@NonNull
|
||||
public final String password;
|
||||
|
||||
@ -15,7 +15,7 @@ public class GroupChangeTitleRequest {
|
||||
|
||||
@NonNull
|
||||
@JsonDeserialize(using = GroupUuidDeserializer.class)
|
||||
public final GroupUuid uuid;
|
||||
public final GroupUuid groupUuid;
|
||||
|
||||
@NonNull
|
||||
public final String title;
|
||||
|
||||
@ -15,7 +15,7 @@ public class GroupJoinRequest {
|
||||
|
||||
@NonNull
|
||||
@JsonDeserialize(using = GroupUuidDeserializer.class)
|
||||
public final GroupUuid uuid;
|
||||
public final GroupUuid groupUuid;
|
||||
|
||||
@NonNull
|
||||
public final String password;
|
||||
|
||||
@ -0,0 +1,26 @@
|
||||
package de.ph87.tools.group.requests;
|
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import de.ph87.tools.group.uuid.GroupUuid;
|
||||
import de.ph87.tools.group.uuid.GroupUuidDeserializer;
|
||||
import de.ph87.tools.user.uuid.UserPublicUuid;
|
||||
import de.ph87.tools.user.uuid.UserPublicUuidDeserializer;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.ToString;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@AllArgsConstructor
|
||||
public class GroupUserRequest {
|
||||
|
||||
@NonNull
|
||||
@JsonDeserialize(using = GroupUuidDeserializer.class)
|
||||
public final GroupUuid groupUuid;
|
||||
|
||||
@NonNull
|
||||
@JsonDeserialize(using = UserPublicUuidDeserializer.class)
|
||||
public final UserPublicUuid userPublicUuid;
|
||||
|
||||
}
|
||||
@ -44,6 +44,10 @@ public class Numbers extends NumbersAbstract {
|
||||
@Column(nullable = false)
|
||||
private ZonedDateTime date = ZonedDateTime.now();
|
||||
|
||||
@Column
|
||||
@Nullable
|
||||
private ZonedDateTime read = null;
|
||||
|
||||
@NonNull
|
||||
@OrderColumn
|
||||
@OneToMany(orphanRemoval = true)
|
||||
@ -54,6 +58,13 @@ public class Numbers extends NumbersAbstract {
|
||||
this.users = users;
|
||||
}
|
||||
|
||||
public void setRead(@NonNull final ZonedDateTime date) {
|
||||
if (this.read != null) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
this.read = date;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getNumberForUser(@NonNull final User user) {
|
||||
for (int userReferenceIndex = 0; userReferenceIndex < users.size(); userReferenceIndex++) {
|
||||
|
||||
@ -41,9 +41,9 @@ public class NumbersController {
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@PostMapping("byUuid")
|
||||
public NumbersDto byUuid(@NonNull final UserPrivateUuid privateUuid, final NumbersUuid numbersUuid) {
|
||||
return numbersService.dtoByUuid(privateUuid, numbersUuid);
|
||||
@PostMapping("fetchAndMarkAsRead")
|
||||
public NumbersDto fetchAndMarkAsRead(@NonNull final UserPrivateUuid privateUuid, final NumbersUuid numbersUuid) {
|
||||
return numbersService.fetchAndMarkAsRead(privateUuid, numbersUuid);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -26,12 +26,16 @@ public class NumbersDto extends NumbersAbstract {
|
||||
@NonNull
|
||||
private final ZonedDateTime date;
|
||||
|
||||
@Nullable
|
||||
private final ZonedDateTime read;
|
||||
|
||||
@Nullable
|
||||
private final Integer number;
|
||||
|
||||
public NumbersDto(@NonNull final Numbers numbers, @NonNull final GroupDto group, @Nullable final Integer number) {
|
||||
this.uuid = numbers.getUuid();
|
||||
this.date = numbers.getDate();
|
||||
this.read = numbers.getRead();
|
||||
this.group = group;
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
@ -6,6 +6,8 @@ import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.repository.ListCrudRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface NumbersRepository extends ListCrudRepository<Numbers, String> {
|
||||
|
||||
@NonNull
|
||||
@ -13,4 +15,6 @@ public interface NumbersRepository extends ListCrudRepository<Numbers, String> {
|
||||
|
||||
void deleteAllByGroup(@NonNull Group group);
|
||||
|
||||
List<Numbers> findAllByGroupAndReadNull(@NonNull Group group);
|
||||
|
||||
}
|
||||
|
||||
@ -23,6 +23,7 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
@ -47,7 +48,7 @@ public class NumbersService {
|
||||
private final UserService userService;
|
||||
|
||||
public void create(@NonNull final UserPrivateUuid userPrivateUuid, @NonNull final GroupUuid groupUuid) {
|
||||
final GroupAccess access = groupAccessService.accessAsOwner(userPrivateUuid, groupUuid);
|
||||
final GroupAccess access = groupAccessService.adminAccess(userPrivateUuid, groupUuid);
|
||||
final List<UserReference> users = access.getGroup().getUsers().stream().map(userReferenceService::create).collect(Collectors.toList());
|
||||
Collections.shuffle(users);
|
||||
final Numbers numbers = numbersRepository.save(new Numbers(access.getGroup(), users));
|
||||
@ -88,8 +89,10 @@ public class NumbersService {
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public NumbersDto dtoByUuid(@NonNull final UserPrivateUuid privateUuid, @NonNull final NumbersUuid numbersUuid) {
|
||||
public NumbersDto fetchAndMarkAsRead(@NonNull final UserPrivateUuid privateUuid, @NonNull final NumbersUuid numbersUuid) {
|
||||
final NumbersAccess access = access(privateUuid, numbersUuid);
|
||||
final ZonedDateTime now = ZonedDateTime.now();
|
||||
numbersRepository.findAllByGroupAndReadNull(access.numbers.getGroup()).forEach(numbers -> numbers.setRead(now));
|
||||
return toDto(access.numbers, access.user);
|
||||
}
|
||||
|
||||
|
||||
@ -35,12 +35,15 @@ public class UserPrivateDto extends UserPublicAbstract {
|
||||
|
||||
private final boolean password;
|
||||
|
||||
private final boolean admin;
|
||||
|
||||
public UserPrivateDto(@NonNull final User user) {
|
||||
this.publicUuid = user.getPublicUuid();
|
||||
this.privateUuid = user.getPrivateUuid();
|
||||
this.name = user.getName();
|
||||
this.created = user.getCreated();
|
||||
this.password = !user.getPassword().isEmpty();
|
||||
this.admin = user.isAdmin();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@ -19,9 +19,12 @@ public class UserPublicDto extends UserPublicAbstract {
|
||||
@NonNull
|
||||
public final String name;
|
||||
|
||||
public final boolean admin;
|
||||
|
||||
public UserPublicDto(@NonNull final User user) {
|
||||
this.publicUuid = user.getPublicUuid();
|
||||
this.name = user.getName();
|
||||
this.admin = user.isAdmin();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -31,7 +31,14 @@ public class UserPrivateUuidArgumentResolver implements HandlerMethodArgumentRes
|
||||
if (!(webRequest.getNativeRequest() instanceof final HttpServletRequest request)) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
final String uuid = Arrays.stream(request.getCookies()).filter(cookie -> USER_UUID_COOKIE_NAME.equalsIgnoreCase(cookie.getName())).findFirst().map(Cookie::getValue).orElse(null);
|
||||
final Cookie[] cookies = request.getCookies();
|
||||
if (cookies == null) {
|
||||
if (!nullable) {
|
||||
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
final String uuid = Arrays.stream(cookies).filter(cookie -> USER_UUID_COOKIE_NAME.equalsIgnoreCase(cookie.getName())).findFirst().map(Cookie::getValue).orElse(null);
|
||||
if (uuid == null || uuid.length() != 36) {
|
||||
if (!nullable) {
|
||||
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
package de.ph87.tools.user.uuid;
|
||||
|
||||
import de.ph87.tools.common.uuid.AbstractUuidDeserializer;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.NonNull;
|
||||
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
public class UserPublicUuidDeserializer extends AbstractUuidDeserializer {
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected UserPublicUuid create(@NonNull final String s) {
|
||||
return new UserPublicUuid(s);
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user