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 @@
+
+
+
+
+
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 -> "
\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);
}
}