diff --git a/src/main/angular/src/app/api/group/GroupUuid.ts b/src/main/angular/src/app/api/group/GroupUuid.ts index fcf67bc..a517da1 100644 --- a/src/main/angular/src/app/api/group/GroupUuid.ts +++ b/src/main/angular/src/app/api/group/GroupUuid.ts @@ -6,8 +6,8 @@ export class GroupUuid { // - } - is(other: GroupUuid | null) { - return other !== null && this.uuid === other.uuid; + is(other: GroupUuid | null | undefined) { + return other !== null && other !== undefined && this.uuid === other.uuid; } } diff --git a/src/main/angular/src/app/api/group/group.service.ts b/src/main/angular/src/app/api/group/group.service.ts index 23a577d..ea1a9c7 100644 --- a/src/main/angular/src/app/api/group/group.service.ts +++ b/src/main/angular/src/app/api/group/group.service.ts @@ -59,7 +59,10 @@ export class GroupService { join(groupUuid: string, password: string, next: Next): void { const data = new GroupJoinRequest(groupUuid, password); - this.api.postSingle(['Group', 'join'], data, Group.fromJson, next); + this.api.postSingle(['Group', 'join'], data, Group.fromJson, group => { + this.userService.fetchUser(); // to update userService with possible freshly created user + next(group); + }); } leave(group: Group, next: Next): void { diff --git a/src/main/angular/src/app/api/tools/Numbers/Numbers.ts b/src/main/angular/src/app/api/tools/Numbers/Numbers.ts index 68bd3b0..ce409f0 100644 --- a/src/main/angular/src/app/api/tools/Numbers/Numbers.ts +++ b/src/main/angular/src/app/api/tools/Numbers/Numbers.ts @@ -2,6 +2,7 @@ import {Group} from "../../group/Group"; import {validateDate, validateList, validateString} from "../../common/validators"; import {NumbersLot} from "./NumbersLot"; import {UserPrivate} from "../../User/UserPrivate"; +import {UserPublic} from "../../User/UserPublic"; export class Numbers { @@ -24,11 +25,38 @@ export class Numbers { } getMine(user: UserPrivate | null): NumbersLot | null { - return this.lots.filter(u => u.user.is(user))[0]; + return this.lots.filter(lot => lot.is(user))[0]; } sameGroup(numbers: Numbers): boolean { return this.group.equals(numbers.group); } + getLotsByUserName(): NumbersLot[] { + return this.lots.sort((a, b) => UserPublic.compareName(a.user, b.user)); + } + + canIReveal(user: UserPrivate | null): boolean { + const mine = this.getMine(user); + if (mine === null) { + return false; + } + return !mine.revealed; + } + + is(numbers: Numbers | null): boolean { + return this.uuid === numbers?.uuid; + } + + isNewerThan(numbers: Numbers | null): boolean { + if (numbers === null) { + return false; + } + return numbers.date.getTime() < this.date.getTime(); + } + + allRevealed(): boolean { + return this.lots.every(lot => lot.revealed); + } + } diff --git a/src/main/angular/src/app/api/tools/Numbers/NumbersLot.ts b/src/main/angular/src/app/api/tools/Numbers/NumbersLot.ts index 42a0b8f..953490b 100644 --- a/src/main/angular/src/app/api/tools/Numbers/NumbersLot.ts +++ b/src/main/angular/src/app/api/tools/Numbers/NumbersLot.ts @@ -1,11 +1,13 @@ import {UserPublic} from "../../User/UserPublic"; -import {validateNumberOrNull} from "../../common/validators"; +import {validateBoolean, validateNumberOrNull} from "../../common/validators"; +import {UserPrivate} from "../../User/UserPrivate"; export class NumbersLot { constructor( readonly user: UserPublic, readonly number: number | null, + readonly revealed: boolean, ) { // - } @@ -14,7 +16,12 @@ export class NumbersLot { return new NumbersLot( UserPublic.fromJson(json['user']), validateNumberOrNull(json['number']), + validateBoolean(json['revealed']), ); } + is(user: UserPrivate | null) { + return this.user.is(user); + } + } diff --git a/src/main/angular/src/app/api/tools/Numbers/numbers.service.ts b/src/main/angular/src/app/api/tools/Numbers/numbers.service.ts index cd51a2d..02d8964 100644 --- a/src/main/angular/src/app/api/tools/Numbers/numbers.service.ts +++ b/src/main/angular/src/app/api/tools/Numbers/numbers.service.ts @@ -44,4 +44,8 @@ export class NumbersService { this.router.navigate(['Numbers', numbers.uuid]); } + reveal(numbersUuid: string) { + this.api.postNone(['Numbers', 'reveal'], numbersUuid); + } + } diff --git a/src/main/angular/src/app/pages/tools/numbers/numbers.component.html b/src/main/angular/src/app/pages/tools/numbers/numbers.component.html index b125f71..cc60d98 100644 --- a/src/main/angular/src/app/pages/tools/numbers/numbers.component.html +++ b/src/main/angular/src/app/pages/tools/numbers/numbers.component.html @@ -1,22 +1,36 @@ -
+ -
- {{ numbers.date | relative:now }} -
- -
- {{ numbers.getMine(userService.user)?.number || '-' }} -
- -
-
- Nächste Runde +
+
+
+
+ Aktuelle Runde +
+
+ + + + + + +
+ {{ lot.user.name }} + +
+ Offen legen +
+
+ {{ lot.number }} +
+
+
+
+ Nächste Runde +
+
+
+
-
+ diff --git a/src/main/angular/src/app/pages/tools/numbers/numbers.component.ts b/src/main/angular/src/app/pages/tools/numbers/numbers.component.ts index 996e25e..21200b6 100644 --- a/src/main/angular/src/app/pages/tools/numbers/numbers.component.ts +++ b/src/main/angular/src/app/pages/tools/numbers/numbers.component.ts @@ -2,13 +2,14 @@ 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"; -import {NgIf} from "@angular/common"; +import {NgForOf, NgIf} from "@angular/common"; import {FormsModule} from "@angular/forms"; import {PasswordTileComponent} from "../../../shared/password-tile/password-tile.component"; import {GroupService} from "../../../api/group/group.service"; import {UserService} from "../../../api/User/user.service"; import {RelativePipe} from "../../../shared/relative.pipe"; import {Subscription, timer} from "rxjs"; +import {Group} from "../../../api/group/Group"; @Component({ selector: 'app-numbers', @@ -17,7 +18,8 @@ import {Subscription, timer} from "rxjs"; NgIf, FormsModule, PasswordTileComponent, - RelativePipe + RelativePipe, + NgForOf ], templateUrl: './numbers.component.html', styleUrl: './numbers.component.less' @@ -30,6 +32,8 @@ export class NumbersComponent implements OnInit, OnDestroy { protected now: Date = new Date(); + private uuid: string | null = null; + constructor( protected readonly activatedRoute: ActivatedRoute, protected readonly numbersService: NumbersService, @@ -43,10 +47,11 @@ export class NumbersComponent implements OnInit, OnDestroy { this.subs.push(timer(1000, 1000).subscribe(() => this.now = new Date())); this.subs.push(this.activatedRoute.params.subscribe(params => { const uuid = params['uuid']; + this.uuid = uuid; if (uuid) { this.numbersService.canAccess(uuid, granted => { if (granted) { - this.numbersService.byUuid(uuid, numbers => this.numbers = numbers); + this.fetchNumbers(); } else { this.numbersService.getGroupUuid(uuid, groupUuid => this.groupService.goto(groupUuid)); } @@ -56,10 +61,23 @@ export class NumbersComponent implements OnInit, OnDestroy { } })); this.userService.subscribePush(Numbers, numbers => { - if (this.numbers?.sameGroup(numbers)) { + if (numbers.is(this.numbers)) { this.numbers = numbers; + } else if (numbers.isNewerThan(this.numbers)) { + this.numbersService.goto(numbers); } }); + this.userService.subscribePush(Group, group => { + if (group.is(this.numbers?.group)) { + this.fetchNumbers(); + } + }); + } + + private fetchNumbers() { + if (this.uuid) { + this.numbersService.byUuid(this.uuid, numbers => this.numbers = numbers); + } } ngOnDestroy(): void { diff --git a/src/main/java/de/ph87/tools/group/uuid/GroupUuid.java b/src/main/java/de/ph87/tools/group/uuid/GroupUuid.java index da400fc..e29d75b 100644 --- a/src/main/java/de/ph87/tools/group/uuid/GroupUuid.java +++ b/src/main/java/de/ph87/tools/group/uuid/GroupUuid.java @@ -1,12 +1,15 @@ package de.ph87.tools.group.uuid; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import de.ph87.tools.common.uuid.AbstractUuid; +import de.ph87.tools.common.uuid.UuidSerializer; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.NonNull; @Getter @NoArgsConstructor +@JsonSerialize(using = UuidSerializer.class) public class GroupUuid extends AbstractUuid { public GroupUuid(@NonNull final String uuid) { diff --git a/src/main/java/de/ph87/tools/tools/numbers/NumbersAccess.java b/src/main/java/de/ph87/tools/tools/numbers/NumbersAccess.java index ced282d..e6edf9b 100644 --- a/src/main/java/de/ph87/tools/tools/numbers/NumbersAccess.java +++ b/src/main/java/de/ph87/tools/tools/numbers/NumbersAccess.java @@ -1,10 +1,13 @@ package de.ph87.tools.tools.numbers; +import de.ph87.tools.tools.numbers.lot.NumberLot; import de.ph87.tools.user.User; import lombok.Getter; import lombok.NonNull; import lombok.ToString; +import java.util.Optional; + @Getter @ToString public class NumbersAccess { @@ -20,4 +23,9 @@ public class NumbersAccess { this.principal = principal; } + @NonNull + public Optional findPrincipalLot() { + return numbers.getLots().stream().filter(lot -> lot.getUser().equals(principal)).findFirst(); + } + } diff --git a/src/main/java/de/ph87/tools/tools/numbers/NumbersController.java b/src/main/java/de/ph87/tools/tools/numbers/NumbersController.java index d1cebdd..00a0c0b 100644 --- a/src/main/java/de/ph87/tools/tools/numbers/NumbersController.java +++ b/src/main/java/de/ph87/tools/tools/numbers/NumbersController.java @@ -1,8 +1,11 @@ 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.uuid.GroupUuid; import de.ph87.tools.tools.numbers.uuid.NumbersUuid; import de.ph87.tools.user.uuid.UserPrivateUuid; +import jakarta.annotation.Nullable; import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -30,20 +33,27 @@ public class NumbersController { } @PostMapping("canAccess") - public boolean canAccess(@NonNull final UserPrivateUuid privateUuid, final NumbersUuid numbersUuid) { + public boolean canAccess(@Nullable final UserPrivateUuid privateUuid, @NonNull final NumbersUuid numbersUuid) { return numbersService.canAccess(privateUuid, numbersUuid); } @NonNull @PostMapping("getGroupUuid") + @JsonSerialize(using = UuidSerializer.class) public GroupUuid getGroupUuid(final NumbersUuid numbersUuid) { return numbersService.getGroupUuid(numbersUuid); } @NonNull @PostMapping("byUuid") - public NumbersDto byUuid(@NonNull final UserPrivateUuid privateUuid, final NumbersUuid numbersUuid) { + public NumbersDto byUuid(@NonNull final UserPrivateUuid privateUuid, @NonNull final NumbersUuid numbersUuid) { return numbersService.byUuid(privateUuid, numbersUuid); } + @NonNull + @PostMapping("reveal") + public NumbersDto reveal(@NonNull final UserPrivateUuid privateUuid, @NonNull final NumbersUuid numbersUuid) { + return numbersService.reveal(privateUuid, numbersUuid); + } + } diff --git a/src/main/java/de/ph87/tools/tools/numbers/NumbersService.java b/src/main/java/de/ph87/tools/tools/numbers/NumbersService.java index 375e94c..0e8f5c6 100644 --- a/src/main/java/de/ph87/tools/tools/numbers/NumbersService.java +++ b/src/main/java/de/ph87/tools/tools/numbers/NumbersService.java @@ -5,11 +5,13 @@ import de.ph87.tools.group.access.GroupAccess; import de.ph87.tools.group.access.GroupAccessService; import de.ph87.tools.group.dto.GroupDto; import de.ph87.tools.group.uuid.GroupUuid; +import de.ph87.tools.tools.numbers.lot.NumberLot; import de.ph87.tools.tools.numbers.uuid.NumbersUuid; import de.ph87.tools.user.User; import de.ph87.tools.user.UserAccessService; import de.ph87.tools.user.push.UserPushService; import de.ph87.tools.user.uuid.UserPrivateUuid; +import jakarta.annotation.Nullable; import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -22,6 +24,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.server.ResponseStatusException; import java.util.Objects; +import java.util.function.Supplier; @Slf4j @Service @@ -42,17 +45,20 @@ public class NumbersService { public void create(@NonNull final UserPrivateUuid userPrivateUuid, @NonNull final GroupUuid groupUuid) { final GroupAccess access = groupAccessService.ownerAccess(userPrivateUuid, groupUuid); final Numbers numbers = numbersRepository.save(new Numbers(access.group)); - pushAllLots(numbers); + push(numbers); } - public boolean canAccess(@NonNull final UserPrivateUuid privateUuid, @NonNull final NumbersUuid numbersUuid) { + public boolean canAccess(@Nullable final UserPrivateUuid privateUuid, @NonNull final NumbersUuid numbersUuid) { + if (privateUuid == null) { + return false; + } final NumbersAccess access = access(privateUuid, numbersUuid); return access.numbers.getGroup().getUsers().contains(access.principal); } @NonNull public GroupUuid getGroupUuid(@NonNull final NumbersUuid numbersUuid) { - final Numbers numbers = numbersRepository.findById(numbersUuid.uuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST)); + final Numbers numbers = numbersRepository.findById(numbersUuid.uuid).orElseThrow(newBadRequest()); return numbers.getGroup().getUuid(); } @@ -62,6 +68,15 @@ public class NumbersService { return toDto(lotAccess); } + @NonNull + public NumbersDto reveal(@NonNull final UserPrivateUuid privateUuid, @NonNull final NumbersUuid numbersUuid) { + final NumbersAccess lotAccess = access(privateUuid, numbersUuid); + final NumberLot lot = lotAccess.findPrincipalLot().orElseThrow(newBadRequest()); + lot.reveal(); + push(lotAccess.numbers); + return toDto(lotAccess); + } + @NonNull public Page page(@NonNull final UserPrivateUuid privateUuid, @NonNull final GroupUuid groupUuid, final int page, final int pageSize) { final GroupAccess groupAccess = groupAccessService.access(privateUuid, groupUuid); @@ -74,7 +89,7 @@ public class NumbersService { @NonNull private NumbersAccess access(@NonNull final UserPrivateUuid privateUuid, @NonNull final NumbersUuid numbersUuid) { final User principal = userAccessService.access(privateUuid); - final Numbers numbers = numbersRepository.findById(numbersUuid.uuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST)); + final Numbers numbers = numbersRepository.findById(numbersUuid.uuid).orElseThrow(newBadRequest()); if (!numbers.getGroup().getUsers().contains(principal)) { throw new ResponseStatusException(HttpStatus.FORBIDDEN); } @@ -83,7 +98,7 @@ public class NumbersService { /* PUSH, DTO ------------------------------------------------------------------------------------ */ - private void pushAllLots(@NonNull final Numbers numbers) { + private void push(@NonNull final Numbers numbers) { numbers.getLots() .stream() .filter(Objects::nonNull) @@ -97,4 +112,10 @@ public class NumbersService { return new NumbersDto(lotAccess, group); } + /* BAD REQUEST ---------------------------------------------------------------------------------- */ + + private static @NonNull Supplier newBadRequest() { + return () -> new ResponseStatusException(HttpStatus.BAD_REQUEST); + } + } diff --git a/src/main/java/de/ph87/tools/tools/numbers/lot/NumberLot.java b/src/main/java/de/ph87/tools/tools/numbers/lot/NumberLot.java index 09ce762..a3ce967 100644 --- a/src/main/java/de/ph87/tools/tools/numbers/lot/NumberLot.java +++ b/src/main/java/de/ph87/tools/tools/numbers/lot/NumberLot.java @@ -34,4 +34,8 @@ public class NumberLot { return this.user.equals(user); } + public void reveal() { + this.revealed = true; + } + } diff --git a/src/main/java/de/ph87/tools/tools/numbers/lot/NumberLotDto.java b/src/main/java/de/ph87/tools/tools/numbers/lot/NumberLotDto.java index f3e21d6..fb5c080 100644 --- a/src/main/java/de/ph87/tools/tools/numbers/lot/NumberLotDto.java +++ b/src/main/java/de/ph87/tools/tools/numbers/lot/NumberLotDto.java @@ -14,11 +14,14 @@ public class NumberLotDto { @NonNull private final UserPublicDto user; + private final boolean revealed; + @Nullable private final Integer number; public NumberLotDto(@NonNull final NumberLot lot, @NonNull final User principal) { this.user = new UserPublicDto(lot.getUser()); + this.revealed = lot.isRevealed(); this.number = lot.isRevealed() || lot.getUser().equals(principal) ? lot.getNumber() : null; }