From 55b1af92420d30579fbdcf6f8cc3e77367d5bfe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Ha=C3=9Fel?= Date: Sat, 9 Aug 2025 10:29:11 +0200 Subject: [PATCH] admin --- .gitignore | 2 + application.properties | 2 +- src/main/angular/src/app/admin.service.ts | 10 ++++ .../src/app/admin/admin.component.html | 7 +++ .../src/app/admin/admin.component.less | 4 ++ .../angular/src/app/admin/admin.component.ts | 23 ++++++++ src/main/angular/src/app/app.component.html | 2 + src/main/angular/src/app/app.component.less | 6 +++ src/main/angular/src/app/app.component.ts | 4 +- src/main/angular/src/app/app.routes.ts | 2 + src/main/angular/src/app/gallery/Picture.ts | 8 ++- .../src/app/gallery/gallery.component.html | 2 +- .../src/app/gallery/gallery.component.less | 5 ++ .../src/app/gallery/gallery.component.ts | 12 ++++- .../src/app/gallery/picture.service.ts | 14 ++++- src/main/angular/src/app/validators.ts | 7 +++ src/main/angular/src/styles.less | 2 +- .../picture/PictureAdminController.java | 54 ------------------- .../picture/PictureController.java | 25 ++++++--- .../isabell_und_timo/picture/PictureDto.java | 3 ++ .../picture/PictureRepository.java | 4 ++ .../picture/PictureService.java | 5 +- 22 files changed, 131 insertions(+), 72 deletions(-) create mode 100644 src/main/angular/src/app/admin.service.ts create mode 100644 src/main/angular/src/app/admin/admin.component.html create mode 100644 src/main/angular/src/app/admin/admin.component.less create mode 100644 src/main/angular/src/app/admin/admin.component.ts delete mode 100644 src/main/java/de/ph87/isabell_und_timo/picture/PictureAdminController.java diff --git a/.gitignore b/.gitignore index 5ff6309..2f822f6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +/images/ + target/ !.mvn/wrapper/maven-wrapper.jar !**/src/main/**/target/ diff --git a/application.properties b/application.properties index 8e9362c..cb36b5b 100644 --- a/application.properties +++ b/application.properties @@ -5,7 +5,7 @@ spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password=password #- -spring.jpa.hibernate.ddl-auto=create +#spring.jpa.hibernate.ddl-auto=create #- spring.jackson.serialization.indent_output=true #- diff --git a/src/main/angular/src/app/admin.service.ts b/src/main/angular/src/app/admin.service.ts new file mode 100644 index 0000000..50055c1 --- /dev/null +++ b/src/main/angular/src/app/admin.service.ts @@ -0,0 +1,10 @@ +import {Injectable} from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class AdminService { + + password: string = ""; + +} diff --git a/src/main/angular/src/app/admin/admin.component.html b/src/main/angular/src/app/admin/admin.component.html new file mode 100644 index 0000000..a97b66e --- /dev/null +++ b/src/main/angular/src/app/admin/admin.component.html @@ -0,0 +1,7 @@ + +
+
+ +
+ Zur Galerie → +
diff --git a/src/main/angular/src/app/admin/admin.component.less b/src/main/angular/src/app/admin/admin.component.less new file mode 100644 index 0000000..42d6340 --- /dev/null +++ b/src/main/angular/src/app/admin/admin.component.less @@ -0,0 +1,4 @@ +input { + border: 1px solid red; + background-color: white; +} diff --git a/src/main/angular/src/app/admin/admin.component.ts b/src/main/angular/src/app/admin/admin.component.ts new file mode 100644 index 0000000..86bb95d --- /dev/null +++ b/src/main/angular/src/app/admin/admin.component.ts @@ -0,0 +1,23 @@ +import {Component} from '@angular/core'; +import {FormsModule} from '@angular/forms'; +import {AdminService} from '../admin.service'; +import {RouterLink} from '@angular/router'; + +@Component({ + selector: 'app-admin', + imports: [ + FormsModule, + RouterLink + ], + templateUrl: './admin.component.html', + styleUrl: './admin.component.less' +}) +export class AdminComponent { + + constructor( + readonly adminService: AdminService, + ) { + // + } + +} diff --git a/src/main/angular/src/app/app.component.html b/src/main/angular/src/app/app.component.html index 4576560..195131a 100644 --- a/src/main/angular/src/app/app.component.html +++ b/src/main/angular/src/app/app.component.html @@ -1,3 +1,5 @@ +
Admin
+
Isabell⚭Timo
09. August 2025
diff --git a/src/main/angular/src/app/app.component.less b/src/main/angular/src/app/app.component.less index 476d309..f4de94d 100644 --- a/src/main/angular/src/app/app.component.less +++ b/src/main/angular/src/app/app.component.less @@ -4,3 +4,9 @@ font-size: 150%; margin-bottom: @space; } + +.admin { + font-size: 50%; + color: gray; + margin: calc(@space / 2); +} diff --git a/src/main/angular/src/app/app.component.ts b/src/main/angular/src/app/app.component.ts index 00ecfae..82086bd 100644 --- a/src/main/angular/src/app/app.component.ts +++ b/src/main/angular/src/app/app.component.ts @@ -1,9 +1,9 @@ import { Component } from '@angular/core'; -import { RouterOutlet } from '@angular/router'; +import {RouterLink, RouterOutlet} from '@angular/router'; @Component({ selector: 'app-root', - imports: [RouterOutlet], + imports: [RouterOutlet, RouterLink], templateUrl: './app.component.html', styleUrl: './app.component.less' }) diff --git a/src/main/angular/src/app/app.routes.ts b/src/main/angular/src/app/app.routes.ts index 8551339..03e9775 100644 --- a/src/main/angular/src/app/app.routes.ts +++ b/src/main/angular/src/app/app.routes.ts @@ -2,8 +2,10 @@ import {Routes} from '@angular/router'; import {UploadComponent} from './upload/upload.component'; import {GalleryComponent} from './gallery/gallery.component'; import {PendingChangesGuard} from './upload/pending.guard'; +import {AdminComponent} from './admin/admin.component'; export const routes: Routes = [ + {path: 'admin', component: AdminComponent}, {path: 'gallery', component: GalleryComponent}, {path: '', component: UploadComponent, canDeactivate: [PendingChangesGuard]}, {path: '**', redirectTo: ''}, diff --git a/src/main/angular/src/app/gallery/Picture.ts b/src/main/angular/src/app/gallery/Picture.ts index 448b6de..066a2a6 100644 --- a/src/main/angular/src/app/gallery/Picture.ts +++ b/src/main/angular/src/app/gallery/Picture.ts @@ -1,11 +1,16 @@ -import {orNull, validateDate, validateString} from '../validators'; +import {orNull, validateBoolean, validateDate, validateString} from '../validators'; export class Picture { + static trackBy(_: number, picture: Picture) { + return picture.uuid; + } + constructor( readonly uuid: string, readonly originalDate: Date | null, readonly uploadDate: Date, + readonly visible: boolean, ) { // } @@ -15,6 +20,7 @@ export class Picture { validateString(json.uuid), orNull(json.originalDate, validateDate), validateDate(json.uploadDate), + validateBoolean(json.visible), ); } diff --git a/src/main/angular/src/app/gallery/gallery.component.html b/src/main/angular/src/app/gallery/gallery.component.html index f5a17c0..aacd45c 100644 --- a/src/main/angular/src/app/gallery/gallery.component.html +++ b/src/main/angular/src/app/gallery/gallery.component.html @@ -8,7 +8,7 @@
- +
diff --git a/src/main/angular/src/app/gallery/gallery.component.less b/src/main/angular/src/app/gallery/gallery.component.less index 26c71ad..3396e64 100644 --- a/src/main/angular/src/app/gallery/gallery.component.less +++ b/src/main/angular/src/app/gallery/gallery.component.less @@ -10,3 +10,8 @@ img { .noImages { margin: calc(5 * @space) 0; } + +.hiddenBorder { + border: 0.25em solid red; + box-sizing: border-box; +} diff --git a/src/main/angular/src/app/gallery/gallery.component.ts b/src/main/angular/src/app/gallery/gallery.component.ts index 06f4124..0c41052 100644 --- a/src/main/angular/src/app/gallery/gallery.component.ts +++ b/src/main/angular/src/app/gallery/gallery.component.ts @@ -16,6 +16,8 @@ import {PictureService} from './picture.service'; }) export class GalleryComponent implements OnInit { + protected readonly Picture = Picture; + protected pictures: Picture[] = []; protected sortByUpload: boolean = true; @@ -27,7 +29,7 @@ export class GalleryComponent implements OnInit { } ngOnInit(): void { - this.pictureService.all(list => this.pictures = list); + this.pictureService.all(list => this.update(list)); } pictureUrl(picture: Picture): string { @@ -38,4 +40,12 @@ export class GalleryComponent implements OnInit { return this.pictures.sort(this.sortByUpload ? Picture.byUploadDateDesc : Picture.byOriginalDateDesc); } + toggleVisible(picture: Picture) { + this.pictureService.toggleVisible(picture, list => this.update(list)); + } + + private update(list: Picture[]) { + this.pictures = list; + } + } diff --git a/src/main/angular/src/app/gallery/picture.service.ts b/src/main/angular/src/app/gallery/picture.service.ts index d184bd3..6c6f9fe 100644 --- a/src/main/angular/src/app/gallery/picture.service.ts +++ b/src/main/angular/src/app/gallery/picture.service.ts @@ -2,6 +2,7 @@ import {Injectable} from "@angular/core"; import {HttpClient} from "@angular/common/http"; import {map} from "rxjs"; import {Picture} from "./Picture"; +import {AdminService} from '../admin.service'; @Injectable({ providedIn: 'root' @@ -12,12 +13,13 @@ export class PictureService { constructor( readonly http: HttpClient, + readonly adminService: AdminService, ) { // } all(next: (list: Picture[]) => any): void { - this.http.get(this.apiUrl("http", ['Picture', 'all'])).pipe(map(list => list.map(Picture.fromJson))).subscribe(next); + this.http.post(this.apiUrl("http", ['Picture', 'all']), this.adminService.password).pipe(map(list => list.map(Picture.fromJson))).subscribe(next); } upload(file: File, success: () => any, error: () => any): void { @@ -26,7 +28,15 @@ export class PictureService { this.http.post(this.apiUrl("http", ['Picture', 'upload']), form).subscribe({next: success, error: error}); } - apiUrl(protocol: string, path: string[]): string { + toggleVisible(picture: Picture, next: (list: Picture[]) => any) { + if (!this.adminService.password) { + throw new Error("You are not logged in."); + } + this.http.post(this.apiUrl("http", ['Picture', picture.uuid, 'visible', !picture.visible]), this.adminService.password).pipe(map(list => list.map(Picture.fromJson))).subscribe(next); + } + + apiUrl(protocol: string, path: any[]): string { + // return `${protocol}${this.secure}://${location.hostname}:8084/${path.join('/')}`; return `${protocol}${this.secure}://${location.hostname}/api/${path.join('/')}`; } diff --git a/src/main/angular/src/app/validators.ts b/src/main/angular/src/app/validators.ts index b9016e6..5a1015b 100644 --- a/src/main/angular/src/app/validators.ts +++ b/src/main/angular/src/app/validators.ts @@ -5,6 +5,13 @@ export function validateString(value: any) { return value; } +export function validateBoolean(value: any) { + if (!(typeof value === 'boolean')) { + throw new Error("Not a boolean: " + JSON.stringify(value)); + } + return value; +} + export function validateDate(value: any): Date { const date = new Date(Date.parse(validateString(value))); if (!date) { diff --git a/src/main/angular/src/styles.less b/src/main/angular/src/styles.less index 6848e16..7e7a2cd 100644 --- a/src/main/angular/src/styles.less +++ b/src/main/angular/src/styles.less @@ -1,7 +1,7 @@ @import "config"; body { - font-size: 3vh; + font-size: 2.8vh; font-family: sans-serif; text-align: center; user-select: none; diff --git a/src/main/java/de/ph87/isabell_und_timo/picture/PictureAdminController.java b/src/main/java/de/ph87/isabell_und_timo/picture/PictureAdminController.java deleted file mode 100644 index 8cc0e6b..0000000 --- a/src/main/java/de/ph87/isabell_und_timo/picture/PictureAdminController.java +++ /dev/null @@ -1,54 +0,0 @@ -package de.ph87.isabell_und_timo.picture; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.CrossOrigin; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.server.ResponseStatusException; - -import java.io.IOException; -import java.util.stream.Collectors; - -@Slf4j -@CrossOrigin -@RestController -@RequiredArgsConstructor -@RequestMapping("PictureAdmin") -public class PictureAdminController { - - private final PictureService pictureService; - - private final PictureRepository pictureRepository; - - @NonNull - @GetMapping("visible") - public String visible(@NonNull final HttpServletRequest request) { - verifyLocal(request); - return pictureRepository - .findAllDtoByVisibleTrue() - .stream() - .map(p -> "%s\n".formatted(p.uuid, p.uuid, p.uuid)) - .collect(Collectors.joining("\n")); - } - -// @GetMapping("{uuid}/visible/{visible}") -// public void visible(@PathVariable @NonNull final String uuid, @PathVariable final boolean visible, @NonNull final HttpServletRequest request, @NonNull final HttpServletResponse response) throws IOException { -// verifyLocal(request); -// pictureService.visible(uuid, visible); -// response.sendRedirect(request.get); -// } - - private static void verifyLocal(final HttpServletRequest request) { - if (!request.getRemoteAddr().startsWith("10.") && !request.getRemoteAddr().equals("127.0.0.1")) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND); - } - } - -} diff --git a/src/main/java/de/ph87/isabell_und_timo/picture/PictureController.java b/src/main/java/de/ph87/isabell_und_timo/picture/PictureController.java index 8645523..fa70497 100644 --- a/src/main/java/de/ph87/isabell_und_timo/picture/PictureController.java +++ b/src/main/java/de/ph87/isabell_und_timo/picture/PictureController.java @@ -23,6 +23,7 @@ import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -65,22 +66,34 @@ public class PictureController { return factory.createMultipartConfig(); } + private static boolean isAdmin(@Nullable final String password) { + return password != null && password.equals("IbPAadstIT25!"); + } + @NonNull - @GetMapping("all") - public List all() { + @PostMapping("all") + public List all(@RequestBody(required = false) final String password) { + if (isAdmin(password)) { + return pictureRepository.findAllDto(); + } return pictureRepository.findAllDtoByVisibleTrue(); } + @NonNull + @PostMapping("{uuid}/visible/{visible}") + public List visible(@PathVariable @NonNull final String uuid, @PathVariable final boolean visible, @RequestBody(required = false) final String password) { + if (isAdmin(password)) { + pictureService.visible(uuid, visible); + } + return all(password); + } + @GetMapping("{uuid}/preview") public void preview(@PathVariable @NonNull final String uuid, @NonNull final HttpServletRequest request, @NonNull final HttpServletResponse response) throws IOException { final PictureInternal pictureDto = pictureRepository.findDtoByUuid(uuid).orElseThrow(() -> { log.warn("Tried accessing NON-EXISTENT picture: {}", request); return new ResponseStatusException(HttpStatus.NOT_FOUND); }); - if (!pictureDto.visible) { - log.warn("Tried accessing INVISIBLE picture: {}", request); - throw new ResponseStatusException(HttpStatus.NOT_FOUND); - } try (final FileInputStream input = new FileInputStream(pictureDto.getPreviewPath().toFile())) { response.getOutputStream().write(input.readAllBytes()); } diff --git a/src/main/java/de/ph87/isabell_und_timo/picture/PictureDto.java b/src/main/java/de/ph87/isabell_und_timo/picture/PictureDto.java index 36e882e..6211280 100644 --- a/src/main/java/de/ph87/isabell_und_timo/picture/PictureDto.java +++ b/src/main/java/de/ph87/isabell_und_timo/picture/PictureDto.java @@ -18,10 +18,13 @@ public class PictureDto { @NonNull public final ZonedDateTime uploadDate; + public final boolean visible; + public PictureDto(@NonNull final Picture picture) { this.uuid = picture.getUuid(); this.originalDate = picture.getOriginalDate(); this.uploadDate = picture.getUploadDate(); + this.visible = picture.isVisible(); } } diff --git a/src/main/java/de/ph87/isabell_und_timo/picture/PictureRepository.java b/src/main/java/de/ph87/isabell_und_timo/picture/PictureRepository.java index 61c4c0a..45ff98c 100644 --- a/src/main/java/de/ph87/isabell_und_timo/picture/PictureRepository.java +++ b/src/main/java/de/ph87/isabell_und_timo/picture/PictureRepository.java @@ -9,6 +9,10 @@ import java.util.Optional; public interface PictureRepository extends ListCrudRepository { + @NonNull + @Query("select new de.ph87.isabell_und_timo.picture.PictureDto(p) from Picture p") + List findAllDto(); + @NonNull @Query("select new de.ph87.isabell_und_timo.picture.PictureDto(p) from Picture p where p.visible = true") List findAllDtoByVisibleTrue(); diff --git a/src/main/java/de/ph87/isabell_und_timo/picture/PictureService.java b/src/main/java/de/ph87/isabell_und_timo/picture/PictureService.java index 7f593fa..73a80c1 100644 --- a/src/main/java/de/ph87/isabell_und_timo/picture/PictureService.java +++ b/src/main/java/de/ph87/isabell_und_timo/picture/PictureService.java @@ -4,7 +4,6 @@ import jakarta.annotation.Nullable; import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -19,8 +18,6 @@ public class PictureService { private final PictureRepository pictureRepository; - private final ResourcePatternResolver resourcePatternResolver; - @NonNull @Transactional public PictureInternal create( @@ -41,9 +38,11 @@ public class PictureService { return new PictureInternal(picture); } + @Transactional public void visible(final @NonNull String uuid, final boolean visible) { final Picture picture = pictureRepository.findById(uuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); picture.setVisible(visible); + log.info("Visible: {}", picture); } }