Compare commits

..

No commits in common. "3881f9b15fa30371d8ab51117f4670c016ef06a4" and "24c873f0ba74c68f8f39e833eadd6cbd957c2f75" have entirely different histories.

52 changed files with 394 additions and 908 deletions

View File

@ -20,7 +20,10 @@ export class Subscribed<T> {
set value(value: T | null) { set value(value: T | null) {
if (!this.isSame(value)) { if (!this.isSame(value)) {
this.unsubscribe(); if (this.subscription) {
this.subscription.unsubscribe();
this.subscription = null;
}
if (value) { if (value) {
this.subscription = this.subscribe(value, next => this.value = next); this.subscription = this.subscribe(value, next => this.value = next);
} }
@ -28,13 +31,6 @@ export class Subscribed<T> {
this._value = value; this._value = value;
} }
public unsubscribe() {
if (this.subscription) {
this.subscription.unsubscribe();
this.subscription = null;
}
}
private isSame(value: T | null) { private isSame(value: T | null) {
if (this._value === null) { if (this._value === null) {
return value === null; return value === null;

View File

@ -7,17 +7,15 @@ import {EventType, Router} from "@angular/router";
import {BehaviorSubject, filter, Subject, Subscription} from "rxjs"; import {BehaviorSubject, filter, Subject, Subscription} from "rxjs";
import {Group} from "../group/Group"; import {Group} from "../group/Group";
import {StompService} from "@stomp/ng2-stompjs"; import {StompService} from "@stomp/ng2-stompjs";
import {Numbers} from "../tools/Numbers/Numbers"; import {NumbersPrivateDto} from "../tools/Numbers/NumbersPrivateDto";
function userPushMessageFromJson(json: any): object { function userPushMessageFromJson(json: any): object {
const type = json['_type_']; const type = json['_type_'];
switch (type) { switch (type) {
case 'NumbersDto': case 'NumbersPrivateDto':
return Numbers.fromJson(json['payload']); return NumbersPrivateDto.fromJson(json['payload']);
case 'UserPrivateDto': case 'UserPrivateDto':
return UserPrivate.fromJson(json['payload']); return UserPrivate.fromJson(json['payload']);
case 'GroupDto':
return Group.fromJson(json['payload']);
} }
throw new Error("Not implemented UserPushMessage._type_ = " + type); throw new Error("Not implemented UserPushMessage._type_ = " + type);
} }

View File

@ -52,10 +52,6 @@ export class ApiService {
return this.http.get<any>(getApiUrl('http', path), {withCredentials: true}).pipe(map(fromJson)).subscribe(next); return this.http.get<any>(getApiUrl('http', path), {withCredentials: true}).pipe(map(fromJson)).subscribe(next);
} }
getPage<T>(path: any[], fromJson: FromJson<T>, next: Next<Page<T>> | undefined = undefined): Subscription {
return this.http.get<any>(getApiUrl('http', path), {withCredentials: true}).pipe(map(Page.fromJson(fromJson))).subscribe(next);
}
getList<T>(path: any[], fromJson: FromJson<T>, next: Next<T[]> | undefined = undefined): Subscription { getList<T>(path: any[], fromJson: FromJson<T>, next: Next<T[]> | undefined = undefined): Subscription {
return this.http.get<any[]>(getApiUrl('http', path), {withCredentials: true}).pipe(map(list => list.map(fromJson))).subscribe(next); return this.http.get<any[]>(getApiUrl('http', path), {withCredentials: true}).pipe(map(list => list.map(fromJson))).subscribe(next);
} }

View File

@ -32,7 +32,7 @@ export class GroupService {
this.api.postList(['Group', 'findAllCommon'], uuid, Group.fromJson, next); this.api.postList(['Group', 'findAllCommon'], uuid, Group.fromJson, next);
} }
findAllJoined(next: Next<Group[]>): void { findAllJoined(next: Next<Group[]>) {
this.api.getList(['Group', 'findAllJoined'], Group.fromJson, next); this.api.getList(['Group', 'findAllJoined'], Group.fromJson, next);
} }
@ -40,7 +40,7 @@ export class GroupService {
this.api.getSingle(['Group', 'create'], Group.fromJson, next); this.api.getSingle(['Group', 'create'], Group.fromJson, next);
} }
changeTitle(group: Group, title: string, next?: Next<Group>): void { changeTitle(group: Group, title: string, next?: Next<Group>) {
const data = { const data = {
uuid: group.uuid, uuid: group.uuid,
title: title, title: title,
@ -48,7 +48,7 @@ export class GroupService {
this.api.postSingle(['Group', 'changeTitle'], data, Group.fromJson, next); this.api.postSingle(['Group', 'changeTitle'], data, Group.fromJson, next);
} }
changePassword(group: Group, password: string, next?: Next<Group>): void { changePassword(group: Group, password: string, next?: Next<Group>) {
const data = { const data = {
uuid: group.uuid, uuid: group.uuid,
password: password, password: password,
@ -68,7 +68,7 @@ export class GroupService {
this.api.postNone(['Group', 'leave'], group, next); this.api.postNone(['Group', 'leave'], group, next);
} }
goto(uuid: string): void { goto(uuid: string) {
this.router.navigate(['Group', uuid]); this.router.navigate(['Group', uuid]);
} }
@ -76,13 +76,5 @@ export class GroupService {
return new Subscribed<Group>(Group.sameUuid, (group, next) => this.api.subscribe(['Group', group.uuid], Group.fromJson, next)); return new Subscribed<Group>(Group.sameUuid, (group, next) => this.api.subscribe(['Group', group.uuid], Group.fromJson, next));
} }
gotoGroups(): void {
this.router.navigate(['Groups']);
}
delete(group: Group, next: Next<void>): void {
this.api.postNone(['Group', 'delete'], group.uuid, next);
}
} }

View File

@ -1,7 +1,7 @@
import {Group} from "../../group/Group"; import {Group} from "../../group/Group";
import {validateDate, validateNumber, validateString} from "../../common/validators"; import {validateDate, validateNumber, validateString} from "../../common/validators";
export class Numbers { export class NumbersPrivateDto {
constructor( constructor(
readonly uuid: string, readonly uuid: string,
@ -12,8 +12,8 @@ export class Numbers {
// - // -
} }
static fromJson(json: any): Numbers { static fromJson(json: any): NumbersPrivateDto {
return new Numbers( return new NumbersPrivateDto(
validateString(json['uuid']), validateString(json['uuid']),
Group.fromJson(json['group']), Group.fromJson(json['group']),
validateDate(json['date']), validateDate(json['date']),

View File

@ -3,9 +3,8 @@ import {ApiService} from "../../common/api.service";
import {Router} from "@angular/router"; import {Router} from "@angular/router";
import {UserService} from "../../User/user.service"; import {UserService} from "../../User/user.service";
import {Next} from "../../common/types"; import {Next} from "../../common/types";
import {Numbers} from "./Numbers"; import {NumbersPrivateDto} from "./NumbersPrivateDto";
import {validateBoolean, validateString} from "../../common/validators"; import {validateBoolean, validateString} from "../../common/validators";
import {Page} from "../../common/Page";
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -17,29 +16,25 @@ export class NumbersService {
protected readonly router: Router, protected readonly router: Router,
protected readonly userService: UserService protected readonly userService: UserService
) { ) {
this.userService.subscribePush<Numbers>(m => m instanceof Numbers, message => { this.userService.subscribePush<NumbersPrivateDto>(m => m instanceof NumbersPrivateDto, message => {
this.router.navigate(['Numbers', message.uuid]) this.router.navigate(['Numbers', message.uuid])
}); });
} }
create(groupUuid: string): void { create(groupUuid: string) {
this.api.getNone(['Numbers', 'create', groupUuid]); this.api.getNone(['Numbers', 'create', groupUuid]);
} }
page(groupUuid: string, page: number, pageSize: number, next: Next<Page<Numbers>>): void { byUuid(uuid: string, next: Next<NumbersPrivateDto>) {
this.api.getPage(['Numbers', 'page', groupUuid, page, pageSize], Numbers.fromJson, next); this.api.postSingle(['Numbers', 'byUuid'], uuid, NumbersPrivateDto.fromJson, next);
} }
byUuid(numbersUuid: string, next: Next<Numbers>): void { canAccess(uuid: string, next: Next<boolean>) {
this.api.postSingle(['Numbers', 'byUuid'], numbersUuid, Numbers.fromJson, next); this.api.postSingle(['Numbers', 'canAccess'], uuid, validateBoolean, next);
} }
canAccess(numbersUuid: string, next: Next<boolean>): void { getGroupUuid(uuid: string | null, next: Next<string>) {
this.api.postSingle(['Numbers', 'canAccess'], numbersUuid, validateBoolean, next); this.api.postSingle(['Numbers', 'getGroupUuid'], uuid, validateString, next);
}
getGroupUuid(numbersUuid: string | null, next: Next<string>): void {
this.api.postSingle(['Numbers', 'getGroupUuid'], numbersUuid, validateString, next);
} }
} }

View File

@ -1,86 +1,34 @@
<div class="tileContainer"> <ng-container *ngIf="group.value">
<h1>Gruppe</h1>
<table>
<tr>
<th>Erstellt</th>
<td>{{ group.value.created | date:'yyyy-MM-dd HH:mm' }}</td>
</tr>
<tr>
<th>Titel</th>
<td>
<app-text [initial]="group.value.title" [editable]="userService.iOwn(group.value)" (onChange)="changeTitle(group.value, $event)"></app-text>
<ng-container *ngIf="!userService.iOwn(group.value)"></ng-container>
</td>
</tr>
<tr>
<th>Passwort</th>
<td>
<app-text [initial]="group.value.password" [editable]="userService.iOwn(group.value)" (onChange)="changePassword(group.value, $event)"></app-text>
<ng-container *ngIf="!userService.iOwn(group.value)"></ng-container>
</td>
</tr>
<tr>
<th>Teilnehmer</th>
<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>
</td>
</tr>
</table>
<div class="tile" *ngIf="granted === false"> <button *ngIf="userService.iOwn(group.value)" (click)="numbersService.create(group.value.uuid)">Numbers</button>
<div class="tileInner">
<div class="tileTitle">
Passwort
</div>
</div>
<div class="tileContent">
<app-password [visible]="true" (join)="join($event)"></app-password>
</div>
</div>
<ng-container *ngIf="group.value"> </ng-container>
<div class="tile"> <app-password [visible]="granted === false" (join)="join($event)"></app-password>
<div class="tileInner">
<div class="tileTitle">
Gruppe
</div>
<div class="tileContent">
<table>
<tr>
<th>Erstellt</th>
<td>{{ group.value.created | date:'yyyy-MM-dd HH:mm' }}</td>
</tr>
<tr>
<th>Titel</th>
<td>
<app-text [initial]="group.value.title" [editable]="userService.iOwn(group.value)" (onChange)="changeTitle(group.value, $event)"></app-text>
<ng-container *ngIf="!userService.iOwn(group.value)"></ng-container>
</td>
</tr>
<tr>
<th>Passwort</th>
<td>
<app-text [initial]="group.value.password" [editable]="userService.iOwn(group.value)" (onChange)="changePassword(group.value, $event)"></app-text>
<ng-container *ngIf="!userService.iOwn(group.value)"></ng-container>
</td>
</tr>
<tr>
<th>Teilnehmer</th>
<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>
</td>
</tr>
</table>
</div>
</div>
</div>
<div class="tile">
<div class="tileInner">
<div class="tileTitle">
Nummern
</div>
<div class="tileContent">
<div class="numbers">
<table>
<tr class="number" *ngFor="let numbers of numbersList.content">
<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>
</ng-container>
</div>

View File

@ -1,4 +1,4 @@
import {Component, OnDestroy, OnInit} from '@angular/core'; import {Component, OnInit} from '@angular/core';
import {DatePipe, NgForOf, NgIf} from "@angular/common"; 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";
@ -9,10 +9,6 @@ import {Group} from "../../../api/group/Group";
import {Subscribed} from "../../../api/Subscribed"; import {Subscribed} from "../../../api/Subscribed";
import {NumbersService} from "../../../api/tools/Numbers/numbers.service"; import {NumbersService} from "../../../api/tools/Numbers/numbers.service";
import {PasswordComponent} from "../../../shared/password/password.component"; import {PasswordComponent} from "../../../shared/password/password.component";
import {Numbers} from "../../../api/tools/Numbers/Numbers";
import {Page} from "../../../api/common/Page";
import {RelativePipe} from "../../../shared/relative.pipe";
import {Subscription, timer} from "rxjs";
@Component({ @Component({
selector: 'app-group', selector: 'app-group',
@ -24,26 +20,19 @@ import {Subscription, timer} from "rxjs";
ReactiveFormsModule, ReactiveFormsModule,
TextComponent, TextComponent,
FormsModule, FormsModule,
PasswordComponent, PasswordComponent
RelativePipe
], ],
templateUrl: './group.component.html', templateUrl: './group.component.html',
styleUrl: './group.component.less' styleUrl: './group.component.less'
}) })
export class GroupComponent implements OnInit, OnDestroy { export class GroupComponent implements OnInit {
protected readonly group: Subscribed<Group>; protected readonly group: Subscribed<Group>;
protected numbersList: Page<Numbers> = Page.EMPTY;
protected granted: boolean | null = null; protected granted: boolean | null = null;
protected uuid: string | null = null; protected uuid: string | null = null;
protected now: Date = new Date();
private timer?: Subscription;
constructor( constructor(
protected readonly router: Router, protected readonly router: Router,
protected readonly activatedRoute: ActivatedRoute, protected readonly activatedRoute: ActivatedRoute,
@ -55,47 +44,33 @@ export class GroupComponent implements OnInit, OnDestroy {
} }
ngOnInit(): void { ngOnInit(): void {
this.timer = timer(1000, 1000).subscribe(() => this.now = new Date());
this.activatedRoute.params.subscribe(params => { this.activatedRoute.params.subscribe(params => {
const groupUuid = params['uuid']; const uuid = params['uuid'];
this.uuid = groupUuid; this.uuid = uuid;
this.granted = null; this.granted = null;
if (groupUuid) { if (uuid) {
this.groupService.canAccess(groupUuid, granted => { this.groupService.canAccess(uuid, granted => {
this.granted = granted; this.granted = granted;
if (granted) { if (granted) {
this.groupService.get(groupUuid, group => this.group.value = group); this.groupService.get(uuid, group => this.group.value = group);
this.numbersService.page(groupUuid, 0, 10, numbersList => this.numbersList = numbersList);
} }
}); });
} }
}); });
} }
ngOnDestroy(): void { protected changeTitle(group: Group, title: string) {
if (this.timer) {
this.timer.unsubscribe();
this.timer = undefined;
}
this.group.unsubscribe();
}
protected changeTitle(group: Group, title: string): void {
this.groupService.changeTitle(group, title, group => this.group.value = group); this.groupService.changeTitle(group, title, group => this.group.value = group);
} }
protected changePassword(group: Group, password: string): void { protected changePassword(group: Group, password: string) {
this.groupService.changePassword(group, password, group => this.group.value = group); this.groupService.changePassword(group, password, group => this.group.value = group);
} }
protected join(password: string): void { protected join(password: string) {
if (this.uuid) { if (this.uuid) {
this.groupService.join(this.uuid, password, group => this.group.value = group); this.groupService.join(this.uuid, password, group => this.group.value = group);
} }
} }
protected delete(group: Group): void {
this.groupService.delete(group, () => this.groupService.gotoGroups());
}
} }

View File

@ -1,5 +1,5 @@
import {Component, OnDestroy, OnInit} from '@angular/core'; import {Component, OnDestroy, OnInit} from '@angular/core';
import {Numbers} from "../../../api/tools/Numbers/Numbers"; import {NumbersPrivateDto} from "../../../api/tools/Numbers/NumbersPrivateDto";
import {ActivatedRoute} from "@angular/router"; import {ActivatedRoute} from "@angular/router";
import {NumbersService} from "../../../api/tools/Numbers/numbers.service"; import {NumbersService} from "../../../api/tools/Numbers/numbers.service";
import {NgIf} from "@angular/common"; import {NgIf} from "@angular/common";
@ -24,7 +24,7 @@ import {Subscription, timer} from "rxjs";
}) })
export class NumbersComponent implements OnInit, OnDestroy { export class NumbersComponent implements OnInit, OnDestroy {
protected numbers: Numbers | null = null; protected numbers: NumbersPrivateDto | null = null;
protected now: Date = new Date(); protected now: Date = new Date();

View File

@ -0,0 +1,40 @@
package de.ph87.tools;
import de.ph87.tools.user.User;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import lombok.NonNull;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import java.util.Arrays;
@Component
public class UserArgumentResolver implements HandlerMethodArgumentResolver {
public static final String USER_UUID_COOKIE_NAME = "PatrixToolsUserUuid";
@Override
public boolean supportsParameter(@NonNull final MethodParameter parameter) {
return parameter.getParameterType() == User.class;
}
@Override
public User resolveArgument(@NonNull final MethodParameter parameter, final ModelAndViewContainer mavContainer, @NonNull final NativeWebRequest webRequest, final WebDataBinderFactory binderFactory) {
if (!(webRequest instanceof final HttpServletRequest request)) {
return null;
}
final String uuid = Arrays.stream(request.getCookies()).filter(cookie -> USER_UUID_COOKIE_NAME.equalsIgnoreCase(cookie.getName())).findFirst().map(Cookie::getValue).orElse(null);
if (uuid == null) {
return null;
}
return null;
}
}

View File

@ -1,43 +0,0 @@
package de.ph87.tools.common.uuid;
import lombok.Getter;
import lombok.NonNull;
import java.util.UUID;
@Getter
public abstract class AbstractUuid {
public final String uuid;
@SuppressWarnings("unused") // used by lombok @NoArgsConstructor
protected AbstractUuid() {
this(UUID.randomUUID().toString());
}
protected AbstractUuid(@NonNull final String uuid) {
if (uuid.length() != 36) {
throw new RuntimeException();
}
this.uuid = uuid;
}
@Override
public String toString() {
return uuid;
}
@Override
public int hashCode() {
return uuid.hashCode();
}
@Override
public boolean equals(final Object obj) {
if (obj instanceof final AbstractUuid casted) {
return uuid.equals(casted.uuid);
}
return false;
}
}

View File

@ -1,20 +0,0 @@
package de.ph87.tools.common.uuid;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import lombok.NonNull;
import java.io.IOException;
public abstract class AbstractUuidDeserializer extends JsonDeserializer<AbstractUuid> {
@Override
public AbstractUuid deserialize(final JsonParser jsonParser, final DeserializationContext deserializationContext) throws IOException {
return create(jsonParser.readValueAs(String.class));
}
@NonNull
protected abstract AbstractUuid create(@NonNull final String s);
}

View File

@ -1,20 +0,0 @@
package de.ph87.tools.common.uuid;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
public class UuidSerializer extends JsonSerializer<AbstractUuid> {
@Override
public void serialize(final AbstractUuid t, final JsonGenerator jsonGenerator, final SerializerProvider serializerProvider) throws IOException {
if (t == null) {
jsonGenerator.writeNull();
} else {
jsonGenerator.writeString(t.uuid);
}
}
}

View File

@ -4,7 +4,7 @@ import de.ph87.tools.user.User;
import lombok.Data; import lombok.Data;
@Data @Data
public class GroupAccess { public class Access {
public final User user; public final User user;

View File

@ -1,7 +1,5 @@
package de.ph87.tools.group; package de.ph87.tools.group;
import de.ph87.tools.group.uuid.GroupAbstract;
import de.ph87.tools.group.uuid.GroupUuid;
import de.ph87.tools.user.User; import de.ph87.tools.user.User;
import de.ph87.tools.web.IWebSocketMessage; import de.ph87.tools.web.IWebSocketMessage;
import jakarta.persistence.*; import jakarta.persistence.*;
@ -18,36 +16,17 @@ import java.util.UUID;
@ToString @ToString
@NoArgsConstructor @NoArgsConstructor
@Table(name = "`group`") @Table(name = "`group`")
public class Group extends GroupAbstract implements IWebSocketMessage { public class Group implements IWebSocketMessage {
@Id @Id
@NonNull @NonNull
@Getter(AccessLevel.NONE) @Column(nullable = false)
private String uuid = UUID.randomUUID().toString(); private String uuid = UUID.randomUUID().toString();
@Transient
@ToString.Exclude
private GroupUuid _uuid;
@NonNull @NonNull
public GroupUuid getUuid() {
if (_uuid == null) {
_uuid = new GroupUuid(uuid);
}
return _uuid;
}
@NonNull
@ToString.Exclude
@ManyToOne(optional = false) @ManyToOne(optional = false)
private User owner; private User owner;
@NonNull
@ToString.Include
public String ownerPublicUuid() {
return getOwner().getPublicUuid().toString();
}
@NonNull @NonNull
@Column(nullable = false) @Column(nullable = false)
private ZonedDateTime created = ZonedDateTime.now(); private ZonedDateTime created = ZonedDateTime.now();
@ -89,4 +68,22 @@ public class Group extends GroupAbstract implements IWebSocketMessage {
return List.of("Number", uuid); return List.of("Number", uuid);
} }
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean isOwnedBy(@NonNull final User user) {
return owner.equals(user);
}
@Override
public boolean equals(final Object obj) {
if (!(obj instanceof final Group group)) {
return false;
}
return group.uuid.equals(this.uuid);
}
@Override
public int hashCode() {
return uuid.hashCode();
}
} }

View File

@ -1,10 +1,7 @@
package de.ph87.tools.group; package de.ph87.tools.group;
import de.ph87.tools.group.uuid.GroupUuid;
import de.ph87.tools.tools.numbers.NumbersRepository;
import de.ph87.tools.user.User; import de.ph87.tools.user.User;
import de.ph87.tools.user.UserService; import de.ph87.tools.user.UserService;
import de.ph87.tools.user.uuid.UserPrivateUuid;
import jakarta.annotation.Nullable; import jakarta.annotation.Nullable;
import lombok.NonNull; import lombok.NonNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -24,9 +21,7 @@ public class GroupAccessService {
private final UserService userService; private final UserService userService;
private final NumbersRepository numbersRepository; public boolean canAccess(@NonNull final String groupUuid, @Nullable final String userPrivateUuid) {
public boolean canAccess(@Nullable final UserPrivateUuid userPrivateUuid, @NonNull final GroupUuid groupUuid) {
final User user = userService.accessOrNull(userPrivateUuid); final User user = userService.accessOrNull(userPrivateUuid);
if (user == null) { if (user == null) {
return false; return false;
@ -35,32 +30,24 @@ public class GroupAccessService {
return group.getUsers().contains(user); return group.getUsers().contains(user);
} }
public void delete(@NonNull final UserPrivateUuid userPrivateUuid, @NonNull final GroupUuid groupUuid) {
final GroupAccess groupAccess = accessAsOwner(userPrivateUuid, groupUuid);
numbersRepository.deleteAllByGroup(groupAccess.group);
groupRepository.delete(groupAccess.group);
log.info("Group deleted: group={}", groupAccess.group);
}
@NonNull @NonNull
public GroupAccess access(@NonNull final UserPrivateUuid userPrivateUuid, @NonNull final GroupUuid groupUuid) { public Access access(@NonNull final String groupUuid, @NonNull final String userPrivateUuid) {
final User user = userService.access(userPrivateUuid); final User user = userService.access(userPrivateUuid);
final Group group = getByUuidOrThrow(groupUuid); final Group group = getByUuidOrThrow(groupUuid);
return new GroupAccess(user, group); return new Access(user, group);
} }
@NonNull public Access accessAsOwner(@NonNull final String userPrivateUuid, @NonNull final String groupUuid) {
public GroupAccess accessAsOwner(@NonNull final UserPrivateUuid userPrivateUuid, @NonNull final GroupUuid groupUuid) { final Access access = access(groupUuid, userPrivateUuid);
final GroupAccess groupAccess = access(userPrivateUuid, groupUuid); if (!access.group.isOwnedBy(access.user)) {
if (!groupAccess.group.isOwnedBy(groupAccess.user)) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST); throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
} }
return groupAccess; return access;
} }
@NonNull @NonNull
private Group getByUuidOrThrow(@NonNull final GroupUuid groupUuid) { private Group getByUuidOrThrow(@NonNull final String groupUuid) {
return groupRepository.findByUuid(groupUuid.uuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST)); return groupRepository.findById(groupUuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST));
} }
} }

View File

@ -0,0 +1,19 @@
package de.ph87.tools.group;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NonNull;
import lombok.ToString;
@Getter
@ToString
@AllArgsConstructor
public class GroupChangePasswordInbound {
@NonNull
public final String uuid;
@NonNull
public final String password;
}

View File

@ -0,0 +1,19 @@
package de.ph87.tools.group;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NonNull;
import lombok.ToString;
@Getter
@ToString
@AllArgsConstructor
public class GroupChangeTitleRequest {
@NonNull
public final String uuid;
@NonNull
public final String title;
}

View File

@ -1,11 +1,5 @@
package de.ph87.tools.group; 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.uuid.GroupUuid;
import de.ph87.tools.user.uuid.UserPrivateUuid;
import de.ph87.tools.user.uuid.UserPublicUuid;
import jakarta.annotation.Nullable; import jakarta.annotation.Nullable;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.NonNull; import lombok.NonNull;
@ -14,6 +8,8 @@ import org.springframework.web.bind.annotation.*;
import java.util.Set; import java.util.Set;
import static de.ph87.tools.UserArgumentResolver.USER_UUID_COOKIE_NAME;
@CrossOrigin @CrossOrigin
@RestController @RestController
@RequiredArgsConstructor @RequiredArgsConstructor
@ -25,54 +21,49 @@ public class GroupController {
private final GroupAccessService groupAccessService; private final GroupAccessService groupAccessService;
@GetMapping("create") @GetMapping("create")
public GroupDto create(@Nullable final UserPrivateUuid privateUuid, @NonNull final HttpServletResponse response) { public GroupDto create(@CookieValue(name = USER_UUID_COOKIE_NAME, required = false) @Nullable final String privateUuid, @NonNull final HttpServletResponse response) {
return groupService.create(privateUuid, response); return groupService.create(privateUuid, response);
} }
@PostMapping("canAccess") @PostMapping("canAccess")
public boolean canAccess(@Nullable final UserPrivateUuid privateUuid, @NonNull final GroupUuid groupUuid) { public boolean canAccess(@CookieValue(name = USER_UUID_COOKIE_NAME, required = false) @Nullable final String privateUuid, @NonNull @RequestBody final String groupUuid) {
return groupAccessService.canAccess(privateUuid, groupUuid); return groupAccessService.canAccess(groupUuid, privateUuid);
}
@PostMapping("delete")
public void delete(@NonNull final UserPrivateUuid privateUuid, @NonNull final GroupUuid groupUuid) {
groupAccessService.delete(privateUuid, groupUuid);
} }
@PostMapping("get") @PostMapping("get")
public GroupDto get(@NonNull final UserPrivateUuid privateUuid, @NonNull final GroupUuid groupUuid) { public GroupDto get(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String privateUuid, @NonNull @RequestBody final String groupUuid) {
return groupService.get(privateUuid, groupUuid); return groupService.get(privateUuid, groupUuid);
} }
@NonNull @NonNull
@GetMapping("findAllJoined") @GetMapping("findAllJoined")
public Set<GroupDto> findAllJoined(@NonNull final UserPrivateUuid userUuid) { public Set<GroupDto> findAllJoined(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String userUuid) {
return groupService.findAllJoined(userUuid); return groupService.findAllJoined(userUuid);
} }
@NonNull @NonNull
@PostMapping("findAllCommon") @PostMapping("findAllCommon")
public Set<GroupDto> findAllCommon(@NonNull final UserPrivateUuid userUuid, @NonNull final UserPublicUuid targetUuid) { public Set<GroupDto> findAllCommon(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String userUuid, @NonNull @RequestBody final String targetUuid) {
return groupService.findAllCommon(userUuid, targetUuid); return groupService.findAllCommon(userUuid, targetUuid);
} }
@PostMapping("join") @PostMapping("join")
public GroupDto join(@Nullable final UserPrivateUuid privateUuid, @NonNull @RequestBody final GroupJoinRequest request, @NonNull final HttpServletResponse response) { public GroupDto join(@CookieValue(name = USER_UUID_COOKIE_NAME, required = false) @Nullable final String privateUuid, @NonNull @RequestBody final GroupJoinRequest request, @NonNull final HttpServletResponse response) {
return groupService.join(privateUuid, request, response); return groupService.join(privateUuid, request, response);
} }
@PostMapping("changeTitle") @PostMapping("changeTitle")
public GroupDto changeTitle(@NonNull final UserPrivateUuid privateUuid, @NonNull @RequestBody final GroupChangeTitleRequest request) { public GroupDto changeTitle(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String privateUuid, @NonNull @RequestBody final GroupChangeTitleRequest request) {
return groupService.changeTitle(privateUuid, request); return groupService.changeTitle(privateUuid, request);
} }
@PostMapping("changePassword") @PostMapping("changePassword")
public GroupDto changePassword(@NonNull final UserPrivateUuid privateUuid, @NonNull @RequestBody final GroupChangePasswordRequest request) { public GroupDto changePassword(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String privateUuid, @NonNull @RequestBody final GroupChangePasswordInbound request) {
return groupService.changePassword(privateUuid, request); return groupService.changePassword(privateUuid, request);
} }
@PostMapping("leave") @PostMapping("leave")
public void leave(@NonNull final UserPrivateUuid privateUuid, @NonNull final GroupUuid groupUuid) { public void leave(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String privateUuid, @NonNull @RequestBody final String groupUuid) {
groupService.leave(privateUuid, groupUuid); groupService.leave(privateUuid, groupUuid);
} }

View File

@ -1,8 +1,5 @@
package de.ph87.tools.group; package de.ph87.tools.group;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import de.ph87.tools.common.uuid.UuidSerializer;
import de.ph87.tools.group.uuid.GroupUuid;
import de.ph87.tools.user.UserPublicDto; import de.ph87.tools.user.UserPublicDto;
import lombok.Getter; import lombok.Getter;
import lombok.NonNull; import lombok.NonNull;
@ -16,8 +13,7 @@ import java.util.Set;
public class GroupDto { public class GroupDto {
@NonNull @NonNull
@JsonSerialize(using = UuidSerializer.class) public final String uuid;
public final GroupUuid uuid;
@NonNull @NonNull
public final String title; public final String title;

View File

@ -0,0 +1,19 @@
package de.ph87.tools.group;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NonNull;
import lombok.ToString;
@Getter
@ToString
@AllArgsConstructor
public class GroupJoinRequest {
@NonNull
public final String uuid;
@NonNull
public final String password;
}

View File

@ -1,15 +1,9 @@
package de.ph87.tools.group; 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.uuid.GroupUuid;
import de.ph87.tools.user.User; import de.ph87.tools.user.User;
import de.ph87.tools.user.UserPublicDto; import de.ph87.tools.user.UserPublicDto;
import de.ph87.tools.user.UserService; import de.ph87.tools.user.UserService;
import de.ph87.tools.user.push.UserPushService; import de.ph87.tools.user.push.UserPushService;
import de.ph87.tools.user.uuid.UserPrivateUuid;
import de.ph87.tools.user.uuid.UserPublicUuid;
import jakarta.annotation.Nullable; import jakarta.annotation.Nullable;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.NonNull; import lombok.NonNull;
@ -38,20 +32,20 @@ public class GroupService {
private final UserService userService; private final UserService userService;
@NonNull @NonNull
public GroupDto create(@Nullable final UserPrivateUuid privateUuid, @NonNull final HttpServletResponse response) { public GroupDto create(@Nullable final String privateUuid, @NonNull final HttpServletResponse response) {
final User user = userService.getUserByPrivateUuidOrElseCreate(privateUuid, response); final User user = userService.getUserByPrivateUuidOrElseCreate(privateUuid, response);
final Group group = createUnchecked(user); final Group group = createUnchecked(user);
return doJoinUnchecked(group, user); return doJoinUnchecked(group, user);
} }
@NonNull @NonNull
public GroupDto get(@NonNull final UserPrivateUuid privateUuid, @NonNull final GroupUuid groupUuid) { public GroupDto get(@NonNull final String privateUuid, @NonNull final String groupUuid) {
final GroupAccess ug = groupAccessService.access(privateUuid, groupUuid); final Access ug = groupAccessService.access(groupUuid, privateUuid);
return toDto(ug.group); return toDto(ug.group);
} }
@NonNull @NonNull
public GroupDto join(@Nullable final UserPrivateUuid privateUuid, @NonNull final GroupJoinRequest request, @NonNull final HttpServletResponse response) { public GroupDto join(@Nullable final String privateUuid, @NonNull final GroupJoinRequest request, @NonNull final HttpServletResponse response) {
final User user = userService.getUserByPrivateUuidOrElseCreate(privateUuid, response); final User user = userService.getUserByPrivateUuidOrElseCreate(privateUuid, response);
final Group group = getByGroupByGroupUuid(request.uuid); final Group group = getByGroupByGroupUuid(request.uuid);
if (!group.getPassword().equals(request.password)) { if (!group.getPassword().equals(request.password)) {
@ -62,13 +56,13 @@ public class GroupService {
} }
@NonNull @NonNull
private Group getByGroupByGroupUuid(@NonNull final GroupUuid groupUuid) { private Group getByGroupByGroupUuid(@NonNull final String groupUuid) {
return groupRepository.findByUuid(groupUuid.uuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST)); return groupRepository.findByUuid(groupUuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST));
} }
@NonNull @NonNull
public GroupDto changeTitle(@NonNull final UserPrivateUuid privateUuid, @NonNull final GroupChangeTitleRequest request) { public GroupDto changeTitle(@NonNull final String privateUuid, @NonNull final GroupChangeTitleRequest request) {
final GroupAccess ug = groupAccessService.access(privateUuid, request.uuid); final Access ug = groupAccessService.access(request.uuid, privateUuid);
if (!ug.group.isOwnedBy(ug.user)) { if (!ug.group.isOwnedBy(ug.user)) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN); throw new ResponseStatusException(HttpStatus.FORBIDDEN);
} }
@ -77,13 +71,13 @@ public class GroupService {
} }
@NonNull @NonNull
public GroupDto changePassword(@NonNull final UserPrivateUuid privateUuid, @NonNull final GroupChangePasswordRequest request) { public GroupDto changePassword(@NonNull final String privateUuid, @NonNull final GroupChangePasswordInbound request) {
final GroupAccess access = groupAccessService.accessAsOwner(privateUuid, request.uuid); final Access access = groupAccessService.accessAsOwner(privateUuid, request.uuid);
access.group.setPassword(request.password); access.group.setPassword(request.password);
return publish(access.group); return publish(access.group);
} }
public void leave(@NonNull final UserPrivateUuid privateUuid, @NonNull final GroupUuid groupUuid) { public void leave(@NonNull final String privateUuid, @NonNull final String groupUuid) {
final User user = userService.access(privateUuid); final User user = userService.access(privateUuid);
final Group group = getByGroupByGroupUuid(groupUuid); final Group group = getByGroupByGroupUuid(groupUuid);
doLeaveUnchecked(group, user); doLeaveUnchecked(group, user);
@ -141,13 +135,13 @@ public class GroupService {
} }
@NonNull @NonNull
public Set<GroupDto> findAllJoined(@NonNull final UserPrivateUuid privateUuid) { public Set<GroupDto> findAllJoined(@NonNull final String privateUuid) {
final User principal = userService.access(privateUuid); final User principal = userService.access(privateUuid);
return groupRepository.findAllByUsersContains(principal).stream().map(this::toDto).collect(Collectors.toSet()); return groupRepository.findAllByUsersContains(principal).stream().map(this::toDto).collect(Collectors.toSet());
} }
@NonNull @NonNull
public Set<GroupDto> findAllCommon(@NonNull final UserPrivateUuid privateUuid, @NonNull final UserPublicUuid targetUuid) { public Set<GroupDto> findAllCommon(@NonNull final String privateUuid, @NonNull final String targetUuid) {
final User principal = userService.access(privateUuid); final User principal = userService.access(privateUuid);
final User target = userService.getByPublicUuid(targetUuid); final User target = userService.getByPublicUuid(targetUuid);
return findAllCommonGroups(principal, target); return findAllCommonGroups(principal, target);

View File

@ -1,23 +0,0 @@
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 lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NonNull;
import lombok.ToString;
@Getter
@ToString
@AllArgsConstructor
public class GroupChangePasswordRequest {
@NonNull
@JsonDeserialize(using = GroupUuidDeserializer.class)
public final GroupUuid uuid;
@NonNull
public final String password;
}

View File

@ -1,23 +0,0 @@
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 lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NonNull;
import lombok.ToString;
@Getter
@ToString
@AllArgsConstructor
public class GroupChangeTitleRequest {
@NonNull
@JsonDeserialize(using = GroupUuidDeserializer.class)
public final GroupUuid uuid;
@NonNull
public final String title;
}

View File

@ -1,23 +0,0 @@
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 lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NonNull;
import lombok.ToString;
@Getter
@ToString
@AllArgsConstructor
public class GroupJoinRequest {
@NonNull
@JsonDeserialize(using = GroupUuidDeserializer.class)
public final GroupUuid uuid;
@NonNull
public final String password;
}

View File

@ -1,30 +0,0 @@
package de.ph87.tools.group.uuid;
import de.ph87.tools.user.uuid.UserPublicAbstract;
import lombok.NonNull;
public abstract class GroupAbstract {
public abstract GroupUuid getUuid();
public abstract UserPublicAbstract getOwner();
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean isOwnedBy(@NonNull final UserPublicAbstract user) {
return getOwner().equals(user);
}
@Override
public boolean equals(final Object obj) {
if (obj instanceof final GroupAbstract casted) {
return casted.getUuid().equals(this.getUuid());
}
return false;
}
@Override
public int hashCode() {
return getUuid().hashCode();
}
}

View File

@ -1,16 +0,0 @@
package de.ph87.tools.group.uuid;
import de.ph87.tools.common.uuid.AbstractUuid;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
@Getter
@NoArgsConstructor
public class GroupUuid extends AbstractUuid {
public GroupUuid(@NonNull final String uuid) {
super(uuid);
}
}

View File

@ -1,33 +0,0 @@
package de.ph87.tools.group.uuid;
import jakarta.servlet.http.HttpServletRequest;
import lombok.NonNull;
import lombok.SneakyThrows;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import java.io.IOException;
@Component
public class GroupUuidArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(@NonNull final MethodParameter parameter) {
return parameter.getParameterType() == GroupUuid.class;
}
@Override
@SneakyThrows(IOException.class)
public GroupUuid resolveArgument(@NonNull final MethodParameter parameter, final ModelAndViewContainer mavContainer, @NonNull final NativeWebRequest webRequest, final WebDataBinderFactory binderFactory) {
if (!(webRequest.getNativeRequest() instanceof final HttpServletRequest request)) {
throw new RuntimeException();
}
final String uuid = new String(request.getInputStream().readAllBytes());
return new GroupUuid(uuid);
}
}

View File

@ -1,18 +0,0 @@
package de.ph87.tools.group.uuid;
import de.ph87.tools.common.uuid.AbstractUuidDeserializer;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
@Getter
@NoArgsConstructor
public class GroupUuidDeserializer extends AbstractUuidDeserializer {
@NonNull
@Override
protected GroupUuid create(@NonNull final String s) {
return new GroupUuid(s);
}
}

View File

@ -1,12 +1,13 @@
package de.ph87.tools.tools.numbers; package de.ph87.tools.tools.numbers;
import de.ph87.tools.group.Group; import de.ph87.tools.group.Group;
import de.ph87.tools.tools.numbers.uuid.NumbersAbstract;
import de.ph87.tools.tools.numbers.uuid.NumbersUuid;
import de.ph87.tools.user.User; import de.ph87.tools.user.User;
import de.ph87.tools.user.reference.UserReference; import de.ph87.tools.user.reference.UserReference;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.*; import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.ToString;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.List; import java.util.List;
@ -17,24 +18,13 @@ import java.util.UUID;
@ToString @ToString
@NoArgsConstructor @NoArgsConstructor
@Table(name = "`numbers`") @Table(name = "`numbers`")
public class Numbers extends NumbersAbstract { public class Numbers {
@Id @Id
@NonNull @NonNull
@Getter(AccessLevel.NONE) @Column(nullable = false)
private String uuid = UUID.randomUUID().toString(); private String uuid = UUID.randomUUID().toString();
@Transient
private NumbersUuid _uuid;
@NonNull
public NumbersUuid getUuid() {
if (_uuid == null) {
_uuid = new NumbersUuid(uuid);
}
return _uuid;
}
@NonNull @NonNull
@ManyToOne(optional = false) @ManyToOne(optional = false)
private Group group; private Group group;
@ -52,18 +42,31 @@ public class Numbers extends NumbersAbstract {
this.users = users; this.users = users;
} }
@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();
}
public int getNumberForUser(@NonNull final User user) { public int getNumberForUser(@NonNull final User user) {
for (int userReferenceIndex = 0; userReferenceIndex < users.size(); userReferenceIndex++) { for (int userReferenceIndex = 0; userReferenceIndex < users.size(); userReferenceIndex++) {
final UserReference userReference = users.get(userReferenceIndex); final UserReference userReference = users.get(userReferenceIndex);
if (userReference.getUser() != null && userReference.getUser().equals(user)) { if (userReference.getUser() != null && userReference.getUser().equals(user)) {
return userReferenceIndex + 1; return userReferenceIndex;
} }
} }
throw new RuntimeException(); throw new RuntimeException();
} }
public boolean containsUser(@NonNull final User user) { public boolean containsUser(@NonNull final User user) {
return users.stream().anyMatch(u -> user.equals(u.getUser())); return users.stream().anyMatch(u -> user.equalsIUser(u.getUser()));
} }
} }

View File

@ -1,20 +0,0 @@
package de.ph87.tools.tools.numbers;
import de.ph87.tools.user.User;
import lombok.Getter;
import lombok.ToString;
@Getter
@ToString
class NumbersAccess {
public final Numbers numbers;
public final User user;
public NumbersAccess(final Numbers numbers, final User user) {
this.numbers = numbers;
this.user = user;
}
}

View File

@ -1,14 +1,12 @@
package de.ph87.tools.tools.numbers; package de.ph87.tools.tools.numbers;
import de.ph87.tools.group.uuid.GroupUuid;
import de.ph87.tools.tools.numbers.uuid.NumbersUuid;
import de.ph87.tools.user.uuid.UserPrivateUuid;
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.data.domain.Page;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import static de.ph87.tools.UserArgumentResolver.USER_UUID_COOKIE_NAME;
@Slf4j @Slf4j
@CrossOrigin @CrossOrigin
@RestController @RestController
@ -19,31 +17,25 @@ public class NumbersController {
private final NumbersService numbersService; private final NumbersService numbersService;
@GetMapping("create/{groupUuid}") @GetMapping("create/{groupUuid}")
public void create(@NonNull final UserPrivateUuid privateUuid, @PathVariable final GroupUuid groupUuid) { public void create(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String privateUuid, @PathVariable final String groupUuid) {
numbersService.create(privateUuid, groupUuid); numbersService.create(privateUuid, groupUuid);
} }
@NonNull
@GetMapping("page/{groupUuid}/{page}/{pageSize}")
public Page<NumbersDto> page(@NonNull final UserPrivateUuid privateUuid, @PathVariable final GroupUuid groupUuid, @PathVariable final int page, @PathVariable final int pageSize) {
return numbersService.page(privateUuid, groupUuid, page, pageSize);
}
@PostMapping("canAccess") @PostMapping("canAccess")
public boolean canAccess(@NonNull final UserPrivateUuid privateUuid, final NumbersUuid numbersUuid) { public boolean canAccess(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String privateUuid, @RequestBody final String groupUuid) {
return numbersService.canAccess(privateUuid, numbersUuid); return numbersService.canAccess(privateUuid, groupUuid);
} }
@NonNull @NonNull
@PostMapping("getGroupUuid") @PostMapping("getGroupUuid")
public GroupUuid getGroupUuid(final NumbersUuid numbersUuid) { public String getGroupUuid(@RequestBody final String groupUuid) {
return numbersService.getGroupUuid(numbersUuid); return numbersService.getGroupUuid(groupUuid);
} }
@NonNull @NonNull
@PostMapping("byUuid") @PostMapping("byUuid")
public NumbersDto byUuid(@NonNull final UserPrivateUuid privateUuid, final NumbersUuid numbersUuid) { public NumbersPrivateDto byUuid(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String privateUuid, @RequestBody final String groupUuid) {
return numbersService.dtoByUuid(privateUuid, numbersUuid); return numbersService.dtoByUuid(privateUuid, groupUuid);
} }
} }

View File

@ -1,10 +1,6 @@
package de.ph87.tools.tools.numbers; package de.ph87.tools.tools.numbers;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import de.ph87.tools.common.uuid.UuidSerializer;
import de.ph87.tools.group.GroupDto; import de.ph87.tools.group.GroupDto;
import de.ph87.tools.tools.numbers.uuid.NumbersAbstract;
import de.ph87.tools.tools.numbers.uuid.NumbersUuid;
import lombok.Getter; import lombok.Getter;
import lombok.NonNull; import lombok.NonNull;
import lombok.ToString; import lombok.ToString;
@ -13,11 +9,10 @@ import java.time.ZonedDateTime;
@Getter @Getter
@ToString(callSuper = true) @ToString(callSuper = true)
public class NumbersDto extends NumbersAbstract { public class NumbersPrivateDto {
@NonNull @NonNull
@JsonSerialize(using = UuidSerializer.class) private final String uuid;
private final NumbersUuid uuid;
@NonNull @NonNull
private final GroupDto group; private final GroupDto group;
@ -27,7 +22,7 @@ public class NumbersDto extends NumbersAbstract {
private final int number; private final int number;
public NumbersDto(@NonNull final Numbers numbers, @NonNull final GroupDto group, final int number) { public NumbersPrivateDto(@NonNull final Numbers numbers, @NonNull final GroupDto group, final int number) {
this.uuid = numbers.getUuid(); this.uuid = numbers.getUuid();
this.date = numbers.getDate(); this.date = numbers.getDate();
this.group = group; this.group = group;

View File

@ -1,16 +1,7 @@
package de.ph87.tools.tools.numbers; package de.ph87.tools.tools.numbers;
import de.ph87.tools.group.Group;
import lombok.NonNull;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.ListCrudRepository; import org.springframework.data.repository.ListCrudRepository;
public interface NumbersRepository extends ListCrudRepository<Numbers, String> { public interface NumbersRepository extends ListCrudRepository<Numbers, String> {
@NonNull
Page<Numbers> findAllByGroup(@NonNull Group group, @NonNull Pageable pageable);
void deleteAllByGroup(@NonNull Group group);
} }

View File

@ -1,23 +1,18 @@
package de.ph87.tools.tools.numbers; package de.ph87.tools.tools.numbers;
import de.ph87.tools.group.GroupAccess;
import de.ph87.tools.group.GroupAccessService; import de.ph87.tools.group.GroupAccessService;
import de.ph87.tools.group.GroupDto; import de.ph87.tools.group.GroupDto;
import de.ph87.tools.group.GroupService; import de.ph87.tools.group.GroupService;
import de.ph87.tools.group.uuid.GroupUuid;
import de.ph87.tools.tools.numbers.uuid.NumbersUuid;
import de.ph87.tools.user.User; import de.ph87.tools.user.User;
import de.ph87.tools.user.UserService; import de.ph87.tools.user.UserService;
import de.ph87.tools.user.push.UserPushService; import de.ph87.tools.user.push.UserPushService;
import de.ph87.tools.user.reference.UserReference; import de.ph87.tools.user.reference.UserReference;
import de.ph87.tools.user.reference.UserReferenceService; import de.ph87.tools.user.reference.UserReferenceService;
import de.ph87.tools.user.uuid.UserPrivateUuid; import lombok.Getter;
import lombok.NonNull; import lombok.NonNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -46,8 +41,8 @@ public class NumbersService {
private final UserService userService; private final UserService userService;
public void create(@NonNull final UserPrivateUuid userPrivateUuid, @NonNull final GroupUuid groupUuid) { public void create(@NonNull final String userPrivateUuid, @NonNull final String groupUuid) {
final GroupAccess access = groupAccessService.accessAsOwner(userPrivateUuid, groupUuid); final de.ph87.tools.group.Access access = groupAccessService.accessAsOwner(userPrivateUuid, groupUuid);
final List<UserReference> users = access.getGroup().getUsers().stream().map(userReferenceService::create).collect(Collectors.toList()); final List<UserReference> users = access.getGroup().getUsers().stream().map(userReferenceService::create).collect(Collectors.toList());
Collections.shuffle(users); Collections.shuffle(users);
final Numbers numbers = numbersRepository.save(new Numbers(access.getGroup(), users)); final Numbers numbers = numbersRepository.save(new Numbers(access.getGroup(), users));
@ -63,51 +58,59 @@ public class NumbersService {
} }
private void publish(@NonNull final Numbers numbers, @NonNull final User user) { private void publish(@NonNull final Numbers numbers, @NonNull final User user) {
final NumbersDto dto = toDto(numbers, user); final NumbersPrivateDto dto = toPrivateDto(numbers, user);
log.debug("Sending event: {}", dto); log.debug("Sending event: {}", dto);
userPushService.push(user, dto); userPushService.push(user, dto);
} }
@NonNull @NonNull
public NumbersDto toDto(@NonNull final Numbers numbers, @NonNull final User user) { public NumbersPrivateDto toPrivateDto(@NonNull final Numbers numbers, @NonNull final User user) {
final GroupDto group = groupService.toDto(numbers.getGroup()); final GroupDto group = groupService.toDto(numbers.getGroup());
final int number = numbers.getNumberForUser(user); final int number = numbers.getNumberForUser(user);
return new NumbersDto(numbers, group, number); return new NumbersPrivateDto(numbers, group, number);
} }
public boolean canAccess(@NonNull final UserPrivateUuid privateUuid, @NonNull final NumbersUuid numbersUuid) { public boolean canAccess(@NonNull final String privateUuid, @NonNull final String numbersUuid) {
final User user = userService.access(privateUuid); final User user = userService.access(privateUuid);
final Numbers numbers = numbersRepository.findById(numbersUuid.uuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST)); final Numbers numbers = numbersRepository.findById(numbersUuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST));
return numbers.containsUser(user); return numbers.containsUser(user);
} }
@NonNull @NonNull
public GroupUuid getGroupUuid(@NonNull final NumbersUuid numbersUuid) { public String getGroupUuid(@NonNull final String numbersUuid) {
final Numbers numbers = numbersRepository.findById(numbersUuid.uuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST)); final Numbers numbers = numbersRepository.findById(numbersUuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST));
return numbers.getGroup().getUuid(); return numbers.getGroup().getUuid();
} }
@NonNull @NonNull
public NumbersDto dtoByUuid(@NonNull final UserPrivateUuid privateUuid, @NonNull final NumbersUuid numbersUuid) { public NumbersPrivateDto dtoByUuid(@NonNull final String privateUuid, @NonNull final String numbersUuid) {
final NumbersAccess access = access(privateUuid, numbersUuid); final Access access = access(privateUuid, numbersUuid);
return toDto(access.numbers, access.user); return toPrivateDto(access.numbers, access.user);
} }
@NonNull @NonNull
private NumbersAccess access(@NonNull final UserPrivateUuid privateUuid, @NonNull final NumbersUuid numbersUuid) { private Access access(@NonNull final String privateUuid, @NonNull final String numbersUuid) {
final User user = userService.access(privateUuid); final User user = userService.access(privateUuid);
final Numbers numbers = numbersRepository.findById(numbersUuid.uuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST)); final Numbers numbers = numbersRepository.findById(numbersUuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST));
if (!numbers.containsUser(user)) { if (!numbers.containsUser(user)) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST); throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
} }
return new NumbersAccess(numbers, user); return new Access(numbers, user);
} }
@NonNull @Getter
public Page<NumbersDto> page(@NonNull final UserPrivateUuid privateUuid, @NonNull final GroupUuid groupUuid, final int page, final int pageSize) { @ToString
final GroupAccess access = groupAccessService.access(privateUuid, groupUuid); private static class Access {
final PageRequest pageable = PageRequest.of(page, pageSize, Sort.by(new Sort.Order(Sort.Direction.DESC, "date")));
return numbersRepository.findAllByGroup(access.group, pageable).map(numbers -> toDto(numbers, access.user)); public final Numbers numbers;
public final User user;
public Access(final Numbers numbers, final User user) {
this.numbers = numbers;
this.user = user;
}
} }
} }

View File

@ -1,20 +0,0 @@
package de.ph87.tools.tools.numbers.uuid;
public abstract class NumbersAbstract {
public abstract NumbersUuid getUuid();
@Override
public boolean equals(final Object obj) {
if (obj instanceof final NumbersAbstract casted) {
return casted.getUuid().equals(this.getUuid());
}
return false;
}
@Override
public int hashCode() {
return getUuid().hashCode();
}
}

View File

@ -1,16 +0,0 @@
package de.ph87.tools.tools.numbers.uuid;
import de.ph87.tools.common.uuid.AbstractUuid;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
@Getter
@NoArgsConstructor
public class NumbersUuid extends AbstractUuid {
public NumbersUuid(@NonNull final String uuid) {
super(uuid);
}
}

View File

@ -1,33 +0,0 @@
package de.ph87.tools.tools.numbers.uuid;
import jakarta.servlet.http.HttpServletRequest;
import lombok.NonNull;
import lombok.SneakyThrows;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import java.io.IOException;
@Component
public class NumbersUuidArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(@NonNull final MethodParameter parameter) {
return parameter.getParameterType() == NumbersUuid.class;
}
@Override
@SneakyThrows(IOException.class)
public NumbersUuid resolveArgument(@NonNull final MethodParameter parameter, final ModelAndViewContainer mavContainer, @NonNull final NativeWebRequest webRequest, final WebDataBinderFactory binderFactory) {
if (!(webRequest.getNativeRequest() instanceof final HttpServletRequest request)) {
throw new RuntimeException();
}
final String uuid = new String(request.getInputStream().readAllBytes());
return new NumbersUuid(uuid);
}
}

View File

@ -1,4 +1,4 @@
package de.ph87.tools.user.requests; package de.ph87.tools.user;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -7,7 +7,7 @@ import lombok.ToString;
@Getter @Getter
@ToString @ToString
@RequiredArgsConstructor @RequiredArgsConstructor
public class UserLoginRequest { public class LoginRequest {
public final String name; public final String name;

View File

@ -1,9 +1,10 @@
package de.ph87.tools.user; package de.ph87.tools.user;
import de.ph87.tools.user.uuid.UserPrivateUuid; import de.ph87.tools.user.reference.IUser;
import de.ph87.tools.user.uuid.UserPublicAbstract; import jakarta.persistence.Column;
import de.ph87.tools.user.uuid.UserPublicUuid; import jakarta.persistence.Entity;
import jakarta.persistence.*; import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.*; import lombok.*;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
@ -15,39 +16,16 @@ import java.util.UUID;
@ToString @ToString
@NoArgsConstructor @NoArgsConstructor
@Table(name = "`user`") @Table(name = "`user`")
public class User extends UserPublicAbstract { public class User extends IUser {
@Id @Id
@NonNull @NonNull
@Getter(AccessLevel.NONE) @Column(nullable = false)
private String publicUuid = UUID.randomUUID().toString();
@Transient
private UserPublicUuid _publicUuid;
@NonNull
public UserPublicUuid getPublicUuid() {
if (_publicUuid == null) {
_publicUuid = new UserPublicUuid(publicUuid);
}
return _publicUuid;
}
@NonNull
@Getter(AccessLevel.NONE)
@Column(nullable = false, unique = true, updatable = false)
private String privateUuid = UUID.randomUUID().toString(); private String privateUuid = UUID.randomUUID().toString();
@Transient
private UserPrivateUuid _privateUuid;
@NonNull @NonNull
public UserPrivateUuid getPrivateUuid() { @Column(nullable = false)
if (_privateUuid == null) { private String publicUuid = UUID.randomUUID().toString();
_privateUuid = new UserPrivateUuid(privateUuid);
}
return _privateUuid;
}
@NonNull @NonNull
@Column(nullable = false) @Column(nullable = false)

View File

@ -0,0 +1,29 @@
package de.ph87.tools.user;
import de.ph87.tools.group.GroupDto;
import lombok.Getter;
import lombok.NonNull;
import lombok.ToString;
import java.util.Set;
@Getter
@ToString
public class UserCommonDto {
@NonNull
public final String publicUuid;
@NonNull
public final String name;
@NonNull
public final Set<GroupDto> commonGroups;
public UserCommonDto(@NonNull final User target, @NonNull final Set<GroupDto> commonGroups) {
this.publicUuid = target.getPublicUuid();
this.name = target.getName();
this.commonGroups = commonGroups;
}
}

View File

@ -1,8 +1,5 @@
package de.ph87.tools.user; package de.ph87.tools.user;
import de.ph87.tools.user.requests.UserLoginRequest;
import de.ph87.tools.user.uuid.UserPrivateUuid;
import de.ph87.tools.user.uuid.UserPublicUuid;
import jakarta.annotation.Nullable; import jakarta.annotation.Nullable;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.NonNull; import lombok.NonNull;
@ -10,6 +7,8 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import static de.ph87.tools.UserArgumentResolver.USER_UUID_COOKIE_NAME;
@Slf4j @Slf4j
@CrossOrigin @CrossOrigin
@RestController @RestController
@ -21,36 +20,36 @@ public class UserController {
@Nullable @Nullable
@GetMapping("whoAmI") @GetMapping("whoAmI")
public UserPrivateDto whoAmI(@Nullable final UserPrivateUuid userUuid, @NonNull final HttpServletResponse response) { public UserPrivateDto whoAmI(@CookieValue(name = USER_UUID_COOKIE_NAME, required = false) @Nullable final String userUuid, @NonNull final HttpServletResponse response) {
return userService.whoAmI(userUuid, response); return userService.whoAmI(userUuid, response);
} }
@NonNull @NonNull
@PostMapping("getByPublicUuid") @PostMapping("getByPublicUuid")
public UserPublicDto getByPublicUuid(@NonNull final UserPublicUuid publicUuid) { public UserPublicDto getByPublicUuid(@NonNull @RequestBody final String publicUuid) {
return userService.getDtoByPublicUuid(publicUuid); return userService.getDtoByPublicUuid(publicUuid);
} }
@Nullable @Nullable
@GetMapping("login") @GetMapping("login")
public UserPrivateDto login(@NonNull @RequestBody final UserLoginRequest loginRequest, @NonNull final HttpServletResponse response) { public UserPrivateDto login(@NonNull @RequestBody final LoginRequest loginRequest, @NonNull final HttpServletResponse response) {
return userService.login(loginRequest, response); return userService.login(loginRequest, response);
} }
@NonNull @NonNull
@PostMapping("changeName") @PostMapping("changeName")
public UserPrivateDto changeName(@NonNull final UserPrivateUuid 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 @NonNull
@PostMapping("changePassword") @PostMapping("changePassword")
public UserPrivateDto changePassword(@NonNull final UserPrivateUuid userUuid, @NonNull @RequestBody final String password) { public UserPrivateDto changePassword(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String userUuid, @NonNull @RequestBody final String password) {
return userService.changePassword(userUuid, password); return userService.changePassword(userUuid, password);
} }
@GetMapping("delete") @GetMapping("delete")
public void delete(@NonNull final UserPrivateUuid userUuid, @NonNull final HttpServletResponse response) { public void delete(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String userUuid, @NonNull final HttpServletResponse response) {
userService.delete(userUuid, response); userService.delete(userUuid, response);
} }

View File

@ -1,10 +1,6 @@
package de.ph87.tools.user; package de.ph87.tools.user;
import com.fasterxml.jackson.databind.annotation.JsonSerialize; import de.ph87.tools.user.reference.IUser;
import de.ph87.tools.common.uuid.UuidSerializer;
import de.ph87.tools.user.uuid.UserPrivateUuid;
import de.ph87.tools.user.uuid.UserPublicAbstract;
import de.ph87.tools.user.uuid.UserPublicUuid;
import jakarta.annotation.Nullable; import jakarta.annotation.Nullable;
import lombok.Getter; import lombok.Getter;
import lombok.NonNull; import lombok.NonNull;
@ -14,22 +10,20 @@ import java.time.ZonedDateTime;
@Getter @Getter
@ToString @ToString
public class UserPrivateDto extends UserPublicAbstract { public class UserPrivateDto extends IUser {
@NonNull @NonNull
private final String pushType = "UserPrivate"; private final String pushType = "UserPrivate";
@NonNull @NonNull
@JsonSerialize(using = UuidSerializer.class) public final String publicUuid;
public final UserPublicUuid publicUuid;
@NonNull
@JsonSerialize(using = UuidSerializer.class)
public final UserPrivateUuid privateUuid;
@NonNull @NonNull
public final String name; public final String name;
@NonNull
public final String privateUuid;
@NonNull @NonNull
public final ZonedDateTime created; public final ZonedDateTime created;
@ -37,8 +31,8 @@ public class UserPrivateDto extends UserPublicAbstract {
public UserPrivateDto(@NonNull final User user) { public UserPrivateDto(@NonNull final User user) {
this.publicUuid = user.getPublicUuid(); this.publicUuid = user.getPublicUuid();
this.privateUuid = user.getPrivateUuid();
this.name = user.getName(); this.name = user.getName();
this.privateUuid = user.getPrivateUuid();
this.created = user.getCreated(); this.created = user.getCreated();
this.password = !user.getPassword().isEmpty(); this.password = !user.getPassword().isEmpty();
} }

View File

@ -1,20 +1,16 @@
package de.ph87.tools.user; package de.ph87.tools.user;
import com.fasterxml.jackson.databind.annotation.JsonSerialize; import de.ph87.tools.user.reference.IUser;
import de.ph87.tools.common.uuid.UuidSerializer;
import de.ph87.tools.user.uuid.UserPublicAbstract;
import de.ph87.tools.user.uuid.UserPublicUuid;
import lombok.Getter; import lombok.Getter;
import lombok.NonNull; import lombok.NonNull;
import lombok.ToString; import lombok.ToString;
@Getter @Getter
@ToString @ToString
public class UserPublicDto extends UserPublicAbstract { public class UserPublicDto extends IUser {
@NonNull @NonNull
@JsonSerialize(using = UuidSerializer.class) public final String publicUuid;
public final UserPublicUuid publicUuid;
@NonNull @NonNull
public final String name; public final String name;

View File

@ -1,9 +1,5 @@
package de.ph87.tools.user; package de.ph87.tools.user;
import de.ph87.tools.common.uuid.AbstractUuid;
import de.ph87.tools.user.requests.UserLoginRequest;
import de.ph87.tools.user.uuid.UserPrivateUuid;
import de.ph87.tools.user.uuid.UserPublicUuid;
import jakarta.annotation.Nullable; import jakarta.annotation.Nullable;
import jakarta.servlet.http.Cookie; import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
@ -22,7 +18,7 @@ import java.util.Random;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static de.ph87.tools.user.uuid.UserPrivateUuidArgumentResolver.USER_UUID_COOKIE_NAME; import static de.ph87.tools.UserArgumentResolver.USER_UUID_COOKIE_NAME;
@Slf4j @Slf4j
@Service @Service
@ -46,7 +42,7 @@ public class UserService {
private final PasswordEncoder passwordEncoder; private final PasswordEncoder passwordEncoder;
@NonNull @NonNull
public UserPrivateDto login(@NonNull final UserLoginRequest loginRequest, @NonNull final HttpServletResponse response) { public UserPrivateDto login(@NonNull final LoginRequest loginRequest, @NonNull final HttpServletResponse response) {
final User user = userRepository.findByName(loginRequest.name).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST)); final User user = userRepository.findByName(loginRequest.name).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST));
if (passwordEncoder.matches(loginRequest.password, user.getPassword())) { if (passwordEncoder.matches(loginRequest.password, user.getPassword())) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST); throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
@ -57,30 +53,24 @@ public class UserService {
} }
@NonNull @NonNull
public User getUserByPrivateUuidOrElseCreate(@Nullable final UserPrivateUuid privateUuid, @NonNull final HttpServletResponse response) { public User getUserByPrivateUuidOrElseCreate(@Nullable final String privateUuid, @NonNull final HttpServletResponse response) {
final User user = Optional final User user = Optional.ofNullable(privateUuid).map(userRepository::findByPrivateUuid).filter(Optional::isPresent).map(Optional::get).orElseGet(this::createUnchecked);
.ofNullable(privateUuid)
.map(AbstractUuid::getUuid)
.map(userRepository::findByPrivateUuid)
.filter(Optional::isPresent)
.map(Optional::get)
.orElseGet(this::createUnchecked);
writeUserUuidCookie(response, user); writeUserUuidCookie(response, user);
return user; return user;
} }
@Nullable @Nullable
public UserPrivateDto whoAmI(@Nullable final UserPrivateUuid privateUuid, @NonNull final HttpServletResponse response) { public UserPrivateDto whoAmI(@Nullable final String privateUuid, final @NonNull HttpServletResponse response) {
if (privateUuid == null) { if (privateUuid == null || privateUuid.isEmpty()) {
return null; return null;
} }
final User user = userRepository.findByPrivateUuid(privateUuid.uuid).orElse(null); final User user = userRepository.findByPrivateUuid(privateUuid).orElse(null);
writeUserUuidCookie(response, user); writeUserUuidCookie(response, user);
return UserPrivateDto.orNull(user); return UserPrivateDto.orNull(user);
} }
@NonNull @NonNull
public UserPrivateDto changeName(@NonNull final UserPrivateUuid 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)) { if (user.getName().equals(name)) {
return; return;
@ -104,7 +94,7 @@ public class UserService {
} }
@NonNull @NonNull
public UserPrivateDto changePassword(@NonNull final UserPrivateUuid privateUuid, @NonNull final String password) { public UserPrivateDto changePassword(@NonNull final String privateUuid, @NonNull final String password) {
return modify(privateUuid, user -> { return modify(privateUuid, user -> {
if (password.length() < PASSWORD_MIN_LENGTH) { if (password.length() < PASSWORD_MIN_LENGTH) {
log.warn("Cannot change User password: too short: length={}/{}, user={}", password.length(), PASSWORD_MIN_LENGTH, user); log.warn("Cannot change User password: too short: length={}/{}, user={}", password.length(), PASSWORD_MIN_LENGTH, user);
@ -119,8 +109,8 @@ public class UserService {
}); });
} }
public void delete(@NonNull final UserPrivateUuid privateUuid, @NonNull final HttpServletResponse response) { public void delete(@NonNull final String privateUuid, final @NonNull HttpServletResponse response) {
final User user = userRepository.findByPrivateUuid(privateUuid.uuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST)); final User user = userRepository.findByPrivateUuid(privateUuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST));
deleteUnchecked(response, user); deleteUnchecked(response, user);
} }
@ -146,7 +136,7 @@ public class UserService {
} }
@NonNull @NonNull
private UserPrivateDto modify(@NonNull final UserPrivateUuid privateUuid, @NonNull Consumer<User> modifier) { private UserPrivateDto modify(@NonNull final String privateUuid, @NonNull Consumer<User> modifier) {
final User user = access(privateUuid); final User user = access(privateUuid);
modifier.accept(user); modifier.accept(user);
return new UserPrivateDto(user); return new UserPrivateDto(user);
@ -161,8 +151,8 @@ public class UserService {
/* ACCESS --------------------------------------------------------------------------------------- */ /* ACCESS --------------------------------------------------------------------------------------- */
@Nullable @Nullable
public User accessOrNull(@Nullable final UserPrivateUuid userPrivateUuid) { public User accessOrNull(@Nullable final String userPrivateUuid) {
if (userPrivateUuid == null) { if (userPrivateUuid == null || userPrivateUuid.isEmpty()) {
return null; return null;
} }
return access(userPrivateUuid); return access(userPrivateUuid);
@ -171,18 +161,18 @@ public class UserService {
/* GETTERS & FINDERS ---------------------------------------------------------------------------- */ /* GETTERS & FINDERS ---------------------------------------------------------------------------- */
@NonNull @NonNull
public UserPublicDto getDtoByPublicUuid(@NonNull final UserPublicUuid publicUuid) { public UserPublicDto getDtoByPublicUuid(@NonNull final String publicUuid) {
return new UserPublicDto(getByPublicUuid(publicUuid)); return new UserPublicDto(getByPublicUuid(publicUuid));
} }
@NonNull @NonNull
public User access(@NonNull final UserPrivateUuid privateUuid) { public User access(@NonNull final String privateUuid) {
return userRepository.findByPrivateUuid(privateUuid.uuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST)); return userRepository.findByPrivateUuid(privateUuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST));
} }
@NonNull @NonNull
public User getByPublicUuid(@NonNull final UserPublicUuid publicUuid) { public User getByPublicUuid(@NonNull final String publicUuid) {
return userRepository.findByPublicUuid(publicUuid.uuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST)); return userRepository.findByPublicUuid(publicUuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST));
} }
/* COOKIES -------------------------------------------------------------------------------------- */ /* COOKIES -------------------------------------------------------------------------------------- */
@ -190,7 +180,7 @@ public class UserService {
private static void writeUserUuidCookie(@NonNull final HttpServletResponse response, @Nullable final User user) { private static void writeUserUuidCookie(@NonNull final HttpServletResponse response, @Nullable final User user) {
final Cookie cookie = new Cookie(USER_UUID_COOKIE_NAME, ""); final Cookie cookie = new Cookie(USER_UUID_COOKIE_NAME, "");
if (user != null) { if (user != null) {
cookie.setValue(user.getPrivateUuid().uuid); cookie.setValue(user.getPrivateUuid());
} }
cookie.setMaxAge(PASSWORD_MIN_LENGTH * 365 * 24 * 60 * 60); cookie.setMaxAge(PASSWORD_MIN_LENGTH * 365 * 24 * 60 * 60);
cookie.setPath("/"); cookie.setPath("/");

View File

@ -0,0 +1,31 @@
package de.ph87.tools.user.reference;
import jakarta.annotation.Nullable;
import lombok.NonNull;
public abstract class IUser {
@NonNull
public abstract String getPublicUuid();
@Override
public boolean equals(final Object obj) {
if (!(obj instanceof final IUser user)) {
return false;
}
return equalsIUser(user);
}
public boolean equalsIUser(@Nullable final IUser user) {
if (user == null) {
return false;
}
return user.getPublicUuid().equals(this.getPublicUuid());
}
@Override
public int hashCode() {
return getPublicUuid().hashCode();
}
}

View File

@ -1,16 +0,0 @@
package de.ph87.tools.user.uuid;
import de.ph87.tools.common.uuid.AbstractUuid;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
@Getter
@NoArgsConstructor
public class UserPrivateUuid extends AbstractUuid {
public UserPrivateUuid(@NonNull final String uuid) {
super(uuid);
}
}

View File

@ -1,44 +0,0 @@
package de.ph87.tools.user.uuid;
import jakarta.annotation.Nullable;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import lombok.NonNull;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.server.ResponseStatusException;
import java.util.Arrays;
@Component
public class UserPrivateUuidArgumentResolver implements HandlerMethodArgumentResolver {
public static final String USER_UUID_COOKIE_NAME = "PatrixToolsUserUuid";
@Override
public boolean supportsParameter(@NonNull final MethodParameter parameter) {
return parameter.getParameterType() == UserPrivateUuid.class;
}
@Override
public UserPrivateUuid resolveArgument(@NonNull final MethodParameter parameter, final ModelAndViewContainer mavContainer, @NonNull final NativeWebRequest webRequest, final WebDataBinderFactory binderFactory) {
final boolean nullable = Arrays.stream(parameter.getParameterAnnotations()).anyMatch(annotation -> annotation.annotationType().equals(Nullable.class));
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);
if (uuid == null || uuid.length() != 36) {
if (!nullable) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
}
return null;
}
return new UserPrivateUuid(uuid);
}
}

View File

@ -1,20 +0,0 @@
package de.ph87.tools.user.uuid;
public abstract class UserPublicAbstract {
public abstract UserPublicUuid getPublicUuid();
@Override
public boolean equals(final Object obj) {
if (obj instanceof final UserPublicAbstract casted) {
return casted.getPublicUuid().equals(this.getPublicUuid());
}
return false;
}
@Override
public int hashCode() {
return getPublicUuid().hashCode();
}
}

View File

@ -1,16 +0,0 @@
package de.ph87.tools.user.uuid;
import de.ph87.tools.common.uuid.AbstractUuid;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
@Getter
@NoArgsConstructor
public class UserPublicUuid extends AbstractUuid {
public UserPublicUuid(@NonNull final String uuid) {
super(uuid);
}
}

View File

@ -1,33 +0,0 @@
package de.ph87.tools.user.uuid;
import jakarta.servlet.http.HttpServletRequest;
import lombok.NonNull;
import lombok.SneakyThrows;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import java.io.IOException;
@Component
public class UserPublicUuidArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(@NonNull final MethodParameter parameter) {
return parameter.getParameterType() == UserPublicUuid.class;
}
@Override
@SneakyThrows(IOException.class)
public UserPublicUuid resolveArgument(@NonNull final MethodParameter parameter, final ModelAndViewContainer mavContainer, @NonNull final NativeWebRequest webRequest, final WebDataBinderFactory binderFactory) {
if (!(webRequest.getNativeRequest() instanceof final HttpServletRequest request)) {
throw new RuntimeException();
}
final String uuid = new String(request.getInputStream().readAllBytes());
return new UserPublicUuid(uuid);
}
}

View File

@ -1,15 +1,11 @@
package de.ph87.tools.web; package de.ph87.tools.web;
import de.ph87.tools.group.uuid.GroupUuidArgumentResolver; import de.ph87.tools.UserArgumentResolver;
import de.ph87.tools.tools.numbers.uuid.NumbersUuidArgumentResolver;
import de.ph87.tools.user.uuid.UserPrivateUuidArgumentResolver;
import de.ph87.tools.user.uuid.UserPublicUuidArgumentResolver;
import lombok.NonNull; import lombok.NonNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.data.web.config.EnableSpringDataWebSupport;
import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
@ -19,19 +15,13 @@ import org.springframework.web.servlet.resource.PathResourceResolver;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import static org.springframework.data.web.config.EnableSpringDataWebSupport.PageSerializationMode.VIA_DTO;
@Configuration @Configuration
@RequiredArgsConstructor @RequiredArgsConstructor
@EnableSpringDataWebSupport(pageSerializationMode = VIA_DTO)
public class WebConfig implements WebMvcConfigurer { public class WebConfig implements WebMvcConfigurer {
@Override @Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new UserPrivateUuidArgumentResolver()); argumentResolvers.add(new UserArgumentResolver());
argumentResolvers.add(new UserPublicUuidArgumentResolver());
argumentResolvers.add(new GroupUuidArgumentResolver());
argumentResolvers.add(new NumbersUuidArgumentResolver());
} }
@Override @Override