fix: Update Swagger Grouping
This commit is contained in:
parent
6332b82c6a
commit
7a14c239d4
|
|
@ -15,7 +15,6 @@ Im Rahmen der OpenAPI-Auto-Generation wurden **massive Änderungen** an der API-
|
||||||
- **`backend/src/routes/README.md`** - Vollständige API-Route-Dokumentation
|
- **`backend/src/routes/README.md`** - Vollständige API-Route-Dokumentation
|
||||||
- **`AUTHENTICATION.md`** - Auth-System-Setup und Verwendung
|
- **`AUTHENTICATION.md`** - Auth-System-Setup und Verwendung
|
||||||
|
|
||||||
**Geschätzter Migrations-Aufwand**: 2-3 Stunden
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -34,7 +33,7 @@ docker compose -f docker/dev/docker-compose.yml up -d
|
||||||
### Zugriff
|
### Zugriff
|
||||||
- **Frontend**: http://localhost:3000 (Hot Module Reloading aktiv)
|
- **Frontend**: http://localhost:3000 (Hot Module Reloading aktiv)
|
||||||
- **Backend**: http://localhost:5001 (API)
|
- **Backend**: http://localhost:5001 (API)
|
||||||
- **API Documentation**: http://localhost:5001/api/docs (Swagger UI)
|
- **API Documentation**: http://localhost:5001/api/docs/ (Swagger UI)
|
||||||
- **Slideshow**: http://localhost:3000/slideshow
|
- **Slideshow**: http://localhost:3000/slideshow
|
||||||
- **Moderation**: http://localhost:3000/moderation (Login über Admin Session)
|
- **Moderation**: http://localhost:3000/moderation (Login über Admin Session)
|
||||||
|
|
||||||
|
|
@ -116,6 +115,8 @@ Router mit spezifischen Routes **vor** generischen Routes mounten!
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Management Portal (UUID Token)**:
|
2. **Management Portal (UUID Token)**:
|
||||||
|
User, die Bilder hochladen, erhalten automatisch einen UUID-Token für das Self-Service Management Portal.
|
||||||
|
Über diesen Token / Link können sie ihre hochgeladenen Gruppen verwalten:
|
||||||
```bash
|
```bash
|
||||||
# Automatisch beim Upload generiert
|
# Automatisch beim Upload generiert
|
||||||
GET /api/manage/550e8400-e29b-41d4-a716-446655440000
|
GET /api/manage/550e8400-e29b-41d4-a716-446655440000
|
||||||
|
|
@ -125,12 +126,8 @@ Router mit spezifischen Routes **vor** generischen Routes mounten!
|
||||||
|
|
||||||
#### Admin-Hinweise: Logout & neue Nutzer
|
#### Admin-Hinweise: Logout & neue Nutzer
|
||||||
|
|
||||||
- **Logout:** Bis ein eigener Button im UI existiert, kann die Session jederzeit über den vorhandenen Endpoint beendet werden, z. B. in der Browser-Konsole:
|
- **Logout:** Der Moderationsbereich enthält jetzt einen Logout-Button (Icon in der Kopfzeile). Klick → `POST /auth/logout` → Session beendet, Login erscheint erneut. Für Skripte kannst du weiterhin `curl -b cookies.txt -X POST http://localhost:5001/auth/logout` verwenden.
|
||||||
```js
|
- **Weiterer Admin:** Verwende das neue API-basierte Skript `./scripts/create_admin_user.sh --server http://localhost:5001 --username zweiteradmin --password 'SuperPasswort123!' [--admin-user bestehend --admin-password ... --role ... --require-password-change]`. Das Skript erledigt Login, CSRF, Duplikats-Check und legt zusätzliche Admins über `/api/admin/users` an (Fallback: `backend/src/scripts/createAdminUser.js`).
|
||||||
await fetch('/auth/logout', { method: 'POST', credentials: 'include' });
|
|
||||||
```
|
|
||||||
Alternativ per CLI: `curl -b cookies.txt -X POST http://localhost:5001/auth/logout`. Danach ist das `sid`-Cookie entfernt und die Moderationsseite zeigt wieder den Login.
|
|
||||||
- **Weiterer Admin:** `npm run create-admin -- --username zweiteradmin --password 'SuperPasswort123!' [--role admin --require-password-change]` oder alternativ `./scripts/create_admin_user.sh --username zweiteradmin --password 'SuperPasswort123!' [...]` ruft das Skript (`backend/src/scripts/createAdminUser.js`) auf und legt einen weiteren User an. Das Skript prüft Duplikate, nutzt dieselben Bcrypt-Runden wie das Backend und kann bei Bedarf weiterhin über die DB nachvollzogen werden. Falls du lieber manuell arbeitest, kannst du wie bisher einen Hash erzeugen und direkt in `admin_users` einfügen.
|
|
||||||
|
|
||||||
### OpenAPI-Spezifikation
|
### OpenAPI-Spezifikation
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,15 +12,24 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
|
{
|
||||||
|
"name": "Admin Authentication"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Upload"
|
"name": "Upload"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Management Portal"
|
"name": "Download"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Public Groups"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Consent Management"
|
"name": "Consent Management"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Management Portal"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Admin - Users"
|
"name": "Admin - Users"
|
||||||
},
|
},
|
||||||
|
|
@ -43,7 +52,11 @@
|
||||||
"paths": {
|
"paths": {
|
||||||
"/auth/setup/status": {
|
"/auth/setup/status": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "",
|
"tags": [
|
||||||
|
"Admin Authentication"
|
||||||
|
],
|
||||||
|
"summary": "Check onboarding status",
|
||||||
|
"description": "Returns whether the initial admin setup is still pending and if a session already exists.",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK"
|
"description": "OK"
|
||||||
|
|
@ -56,7 +69,11 @@
|
||||||
},
|
},
|
||||||
"/auth/setup/initial-admin": {
|
"/auth/setup/initial-admin": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "",
|
"tags": [
|
||||||
|
"Admin Authentication"
|
||||||
|
],
|
||||||
|
"summary": "Complete initial admin setup",
|
||||||
|
"description": "Creates the very first admin account and immediately starts a session.",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "body",
|
"name": "body",
|
||||||
|
|
@ -92,7 +109,11 @@
|
||||||
},
|
},
|
||||||
"/auth/login": {
|
"/auth/login": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "",
|
"tags": [
|
||||||
|
"Admin Authentication"
|
||||||
|
],
|
||||||
|
"summary": "Admin login",
|
||||||
|
"description": "Starts a server-side admin session and returns a CSRF token.",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "body",
|
"name": "body",
|
||||||
|
|
@ -131,7 +152,11 @@
|
||||||
},
|
},
|
||||||
"/auth/logout": {
|
"/auth/logout": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "",
|
"tags": [
|
||||||
|
"Admin Authentication"
|
||||||
|
],
|
||||||
|
"summary": "Terminate admin session",
|
||||||
|
"description": "Destroys the current session and clears the sid cookie.",
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": "No Content"
|
"description": "No Content"
|
||||||
|
|
@ -144,7 +169,11 @@
|
||||||
},
|
},
|
||||||
"/auth/csrf-token": {
|
"/auth/csrf-token": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "",
|
"tags": [
|
||||||
|
"Admin Authentication"
|
||||||
|
],
|
||||||
|
"summary": "Fetch CSRF token",
|
||||||
|
"description": "Returns a CSRF token for the active admin session (session required).",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK"
|
"description": "OK"
|
||||||
|
|
@ -157,7 +186,11 @@
|
||||||
},
|
},
|
||||||
"/auth/change-password": {
|
"/auth/change-password": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "",
|
"tags": [
|
||||||
|
"Admin Authentication"
|
||||||
|
],
|
||||||
|
"summary": "Change admin password",
|
||||||
|
"description": "Allows a logged-in admin to rotate their password (CSRF protected).",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "x-csrf-token",
|
"name": "x-csrf-token",
|
||||||
|
|
@ -297,25 +330,40 @@
|
||||||
},
|
},
|
||||||
"/api/download/{id}": {
|
"/api/download/{id}": {
|
||||||
"get": {
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Download"
|
||||||
|
],
|
||||||
|
"summary": "Download original image",
|
||||||
"description": "",
|
"description": "",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "id",
|
"name": "id",
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"required": true,
|
"required": true,
|
||||||
"type": "string"
|
"type": "string",
|
||||||
|
"description": "Filename of the uploaded image"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"default": {
|
"200": {
|
||||||
"description": ""
|
"description": "Binary image response"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "File not found"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/upload/batch": {
|
"/api/upload/batch": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "",
|
"tags": [
|
||||||
|
"Upload"
|
||||||
|
],
|
||||||
|
"summary": "Batch upload multiple images",
|
||||||
|
"description": "Accepts multiple images + metadata/consents and creates a managed group with management token.",
|
||||||
|
"consumes": [
|
||||||
|
"multipart/form-data"
|
||||||
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "body",
|
"name": "body",
|
||||||
|
|
@ -341,57 +389,98 @@
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK"
|
"description": "Batch upload successful (returns management token)"
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
"description": "Bad Request"
|
"description": "Missing files or workshop consent"
|
||||||
},
|
},
|
||||||
"500": {
|
"500": {
|
||||||
"description": "Internal Server Error"
|
"description": "Unexpected server error"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/groups": {
|
"/api/groups": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "",
|
"tags": [
|
||||||
|
"Public Groups"
|
||||||
|
],
|
||||||
|
"summary": "Get approved groups with images",
|
||||||
|
"description": "Returns all approved groups (slideshow feed). Automatically triggers JSON→SQLite migration if required.",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK"
|
"description": "List of approved groups",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"groups": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"groupId": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "cTV24Yn-a"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Familie Mueller"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"totalCount": {
|
||||||
|
"type": "number",
|
||||||
|
"example": 73
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"xml": {
|
||||||
|
"name": "main"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"500": {
|
"500": {
|
||||||
"description": "Internal Server Error"
|
"description": "Server error"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/groups/{groupId}": {
|
"/api/groups/{groupId}": {
|
||||||
"get": {
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Public Groups"
|
||||||
|
],
|
||||||
|
"summary": "Get approved group by ID",
|
||||||
"description": "",
|
"description": "",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "groupId",
|
"name": "groupId",
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"required": true,
|
"required": true,
|
||||||
"type": "string"
|
"type": "string",
|
||||||
|
"description": "Public groupId (e.g. cTV24Yn-a)"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK"
|
"description": "Group payload (images + metadata)"
|
||||||
},
|
},
|
||||||
"404": {
|
"404": {
|
||||||
"description": "Not Found"
|
"description": "Group not found or not approved"
|
||||||
},
|
},
|
||||||
"500": {
|
"500": {
|
||||||
"description": "Internal Server Error"
|
"description": "Server error"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/social-media/platforms": {
|
"/api/social-media/platforms": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "",
|
"tags": [
|
||||||
|
"Consent Management"
|
||||||
|
],
|
||||||
|
"summary": "List active social media platforms",
|
||||||
|
"description": "Public endpoint that exposes the available platforms for consent selection on the upload form.",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK"
|
"description": "OK"
|
||||||
|
|
@ -2483,13 +2572,18 @@
|
||||||
},
|
},
|
||||||
"/api/admin/{groupId}/reorder": {
|
"/api/admin/{groupId}/reorder": {
|
||||||
"put": {
|
"put": {
|
||||||
|
"tags": [
|
||||||
|
"Admin - Groups Moderation"
|
||||||
|
],
|
||||||
|
"summary": "Reorder images within a group",
|
||||||
"description": "",
|
"description": "",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "groupId",
|
"name": "groupId",
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"required": true,
|
"required": true,
|
||||||
"type": "string"
|
"type": "string",
|
||||||
|
"description": "Admin groupId"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "body",
|
"name": "body",
|
||||||
|
|
@ -2506,19 +2600,19 @@
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK"
|
"description": "Order updated successfully"
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
"description": "Bad Request"
|
"description": "Validation error"
|
||||||
},
|
},
|
||||||
"403": {
|
"403": {
|
||||||
"description": "Forbidden"
|
"description": "Forbidden"
|
||||||
},
|
},
|
||||||
"404": {
|
"404": {
|
||||||
"description": "Not Found"
|
"description": "Group not found"
|
||||||
},
|
},
|
||||||
"500": {
|
"500": {
|
||||||
"description": "Internal Server Error"
|
"description": "Internal server error"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,11 @@ const { requireAdminAuth } = require('../middlewares/auth');
|
||||||
const { requireCsrf } = require('../middlewares/csrf');
|
const { requireCsrf } = require('../middlewares/csrf');
|
||||||
|
|
||||||
router.get('/setup/status', async (req, res) => {
|
router.get('/setup/status', async (req, res) => {
|
||||||
|
/*
|
||||||
|
#swagger.tags = ['Admin Authentication']
|
||||||
|
#swagger.summary = 'Check onboarding status'
|
||||||
|
#swagger.description = 'Returns whether the initial admin setup is still pending and if a session already exists.'
|
||||||
|
*/
|
||||||
try {
|
try {
|
||||||
const needsSetup = await AdminAuthService.needsInitialSetup();
|
const needsSetup = await AdminAuthService.needsInitialSetup();
|
||||||
const sessionUser = req.session && req.session.user
|
const sessionUser = req.session && req.session.user
|
||||||
|
|
@ -27,6 +32,11 @@ router.get('/setup/status', async (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/setup/initial-admin', async (req, res) => {
|
router.post('/setup/initial-admin', async (req, res) => {
|
||||||
|
/*
|
||||||
|
#swagger.tags = ['Admin Authentication']
|
||||||
|
#swagger.summary = 'Complete initial admin setup'
|
||||||
|
#swagger.description = 'Creates the very first admin account and immediately starts a session.'
|
||||||
|
*/
|
||||||
try {
|
try {
|
||||||
const { username, password } = req.body || {};
|
const { username, password } = req.body || {};
|
||||||
if (!username || !password) {
|
if (!username || !password) {
|
||||||
|
|
@ -67,6 +77,11 @@ router.post('/setup/initial-admin', async (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/login', async (req, res) => {
|
router.post('/login', async (req, res) => {
|
||||||
|
/*
|
||||||
|
#swagger.tags = ['Admin Authentication']
|
||||||
|
#swagger.summary = 'Admin login'
|
||||||
|
#swagger.description = 'Starts a server-side admin session and returns a CSRF token.'
|
||||||
|
*/
|
||||||
try {
|
try {
|
||||||
const { username, password } = req.body || {};
|
const { username, password } = req.body || {};
|
||||||
if (!username || !password) {
|
if (!username || !password) {
|
||||||
|
|
@ -100,6 +115,11 @@ router.post('/login', async (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/logout', async (req, res) => {
|
router.post('/logout', async (req, res) => {
|
||||||
|
/*
|
||||||
|
#swagger.tags = ['Admin Authentication']
|
||||||
|
#swagger.summary = 'Terminate admin session'
|
||||||
|
#swagger.description = 'Destroys the current session and clears the sid cookie.'
|
||||||
|
*/
|
||||||
try {
|
try {
|
||||||
await AdminAuthService.destroySession(req);
|
await AdminAuthService.destroySession(req);
|
||||||
res.clearCookie('sid');
|
res.clearCookie('sid');
|
||||||
|
|
@ -111,6 +131,11 @@ router.post('/logout', async (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/csrf-token', requireAdminAuth, (req, res) => {
|
router.get('/csrf-token', requireAdminAuth, (req, res) => {
|
||||||
|
/*
|
||||||
|
#swagger.tags = ['Admin Authentication']
|
||||||
|
#swagger.summary = 'Fetch CSRF token'
|
||||||
|
#swagger.description = 'Returns a CSRF token for the active admin session (session required).'
|
||||||
|
*/
|
||||||
if (!req.session.csrfToken) {
|
if (!req.session.csrfToken) {
|
||||||
req.session.csrfToken = AdminAuthService.generateCsrfToken();
|
req.session.csrfToken = AdminAuthService.generateCsrfToken();
|
||||||
}
|
}
|
||||||
|
|
@ -119,6 +144,11 @@ router.get('/csrf-token', requireAdminAuth, (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/change-password', requireAdminAuth, requireCsrf, async (req, res) => {
|
router.post('/change-password', requireAdminAuth, requireCsrf, async (req, res) => {
|
||||||
|
/*
|
||||||
|
#swagger.tags = ['Admin Authentication']
|
||||||
|
#swagger.summary = 'Change admin password'
|
||||||
|
#swagger.description = 'Allows a logged-in admin to rotate their password (CSRF protected).'
|
||||||
|
*/
|
||||||
try {
|
try {
|
||||||
const { currentPassword, newPassword } = req.body || {};
|
const { currentPassword, newPassword } = req.body || {};
|
||||||
if (!currentPassword || !newPassword) {
|
if (!currentPassword || !newPassword) {
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,15 @@ const router = Router();
|
||||||
*/
|
*/
|
||||||
// Batch-Upload für mehrere Bilder
|
// Batch-Upload für mehrere Bilder
|
||||||
router.post('/upload/batch', async (req, res) => {
|
router.post('/upload/batch', async (req, res) => {
|
||||||
|
/*
|
||||||
|
#swagger.tags = ['Upload']
|
||||||
|
#swagger.summary = 'Batch upload multiple images'
|
||||||
|
#swagger.description = 'Accepts multiple images + metadata/consents and creates a managed group with management token.'
|
||||||
|
#swagger.consumes = ['multipart/form-data']
|
||||||
|
#swagger.responses[200] = { description: 'Batch upload successful (returns management token)' }
|
||||||
|
#swagger.responses[400] = { description: 'Missing files or workshop consent' }
|
||||||
|
#swagger.responses[500] = { description: 'Unexpected server error' }
|
||||||
|
*/
|
||||||
try {
|
try {
|
||||||
// Überprüfe ob Dateien hochgeladen wurden
|
// Überprüfe ob Dateien hochgeladen wurden
|
||||||
if (!req.files || !req.files.images) {
|
if (!req.files || !req.files.images) {
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,18 @@ const router = Router();
|
||||||
* description: File not found
|
* description: File not found
|
||||||
*/
|
*/
|
||||||
router.get('/download/:id', (req, res) => {
|
router.get('/download/:id', (req, res) => {
|
||||||
|
/*
|
||||||
|
#swagger.tags = ['Download']
|
||||||
|
#swagger.summary = 'Download original image'
|
||||||
|
#swagger.parameters['id'] = {
|
||||||
|
in: 'path',
|
||||||
|
required: true,
|
||||||
|
type: 'string',
|
||||||
|
description: 'Filename of the uploaded image'
|
||||||
|
}
|
||||||
|
#swagger.responses[200] = { description: 'Binary image response' }
|
||||||
|
#swagger.responses[404] = { description: 'File not found' }
|
||||||
|
*/
|
||||||
const filePath = path.join(__dirname, '..', UPLOAD_FS_DIR, req.params.id);
|
const filePath = path.join(__dirname, '..', UPLOAD_FS_DIR, req.params.id);
|
||||||
res.download(filePath);
|
res.download(filePath);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -4,54 +4,21 @@ const MigrationService = require('../services/MigrationService');
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
/**
|
|
||||||
* @swagger
|
|
||||||
* /groups:
|
|
||||||
* get:
|
|
||||||
* tags: [Groups]
|
|
||||||
* summary: Get all approved groups with images
|
|
||||||
* description: Returns all approved groups with their images for public slideshow display. Automatically triggers migration if needed.
|
|
||||||
* responses:
|
|
||||||
* 200:
|
|
||||||
* description: List of approved groups
|
|
||||||
* content:
|
|
||||||
* application/json:
|
|
||||||
* schema:
|
|
||||||
* type: object
|
|
||||||
* properties:
|
|
||||||
* groups:
|
|
||||||
* type: array
|
|
||||||
* items:
|
|
||||||
* type: object
|
|
||||||
* properties:
|
|
||||||
* groupId:
|
|
||||||
* type: string
|
|
||||||
* example: "cTV24Yn-a"
|
|
||||||
* year:
|
|
||||||
* type: integer
|
|
||||||
* example: 2024
|
|
||||||
* title:
|
|
||||||
* type: string
|
|
||||||
* example: "Familie Mueller"
|
|
||||||
* description:
|
|
||||||
* type: string
|
|
||||||
* name:
|
|
||||||
* type: string
|
|
||||||
* approved:
|
|
||||||
* type: boolean
|
|
||||||
* example: true
|
|
||||||
* images:
|
|
||||||
* type: array
|
|
||||||
* items:
|
|
||||||
* type: object
|
|
||||||
* totalCount:
|
|
||||||
* type: integer
|
|
||||||
* example: 73
|
|
||||||
* 500:
|
|
||||||
* description: Server error
|
|
||||||
*/
|
|
||||||
// Alle Gruppen abrufen (für Slideshow mit vollständigen Bilddaten)
|
// Alle Gruppen abrufen (für Slideshow mit vollständigen Bilddaten)
|
||||||
router.get('/groups', async (req, res) => {
|
router.get('/groups', async (req, res) => {
|
||||||
|
/*
|
||||||
|
#swagger.tags = ['Public Groups']
|
||||||
|
#swagger.summary = 'Get approved groups with images'
|
||||||
|
#swagger.description = 'Returns all approved groups (slideshow feed). Automatically triggers JSON→SQLite migration if required.'
|
||||||
|
#swagger.responses[200] = {
|
||||||
|
description: 'List of approved groups',
|
||||||
|
schema: {
|
||||||
|
groups: [{ groupId: 'cTV24Yn-a', title: 'Familie Mueller' }],
|
||||||
|
totalCount: 73
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#swagger.responses[500] = { description: 'Server error' }
|
||||||
|
*/
|
||||||
try {
|
try {
|
||||||
// Auto-Migration beim ersten Zugriff
|
// Auto-Migration beim ersten Zugriff
|
||||||
const migrationStatus = await MigrationService.getMigrationStatus();
|
const migrationStatus = await MigrationService.getMigrationStatus();
|
||||||
|
|
@ -75,52 +42,21 @@ router.get('/groups', async (req, res) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* @swagger
|
|
||||||
* /groups/{groupId}:
|
|
||||||
* get:
|
|
||||||
* tags: [Groups]
|
|
||||||
* summary: Get a specific approved group by ID
|
|
||||||
* description: Returns details of a single approved group with all its images
|
|
||||||
* parameters:
|
|
||||||
* - in: path
|
|
||||||
* name: groupId
|
|
||||||
* required: true
|
|
||||||
* schema:
|
|
||||||
* type: string
|
|
||||||
* example: "cTV24Yn-a"
|
|
||||||
* description: Unique identifier of the group
|
|
||||||
* responses:
|
|
||||||
* 200:
|
|
||||||
* description: Group details
|
|
||||||
* content:
|
|
||||||
* application/json:
|
|
||||||
* schema:
|
|
||||||
* type: object
|
|
||||||
* properties:
|
|
||||||
* groupId:
|
|
||||||
* type: string
|
|
||||||
* year:
|
|
||||||
* type: integer
|
|
||||||
* title:
|
|
||||||
* type: string
|
|
||||||
* description:
|
|
||||||
* type: string
|
|
||||||
* name:
|
|
||||||
* type: string
|
|
||||||
* approved:
|
|
||||||
* type: boolean
|
|
||||||
* images:
|
|
||||||
* type: array
|
|
||||||
* items:
|
|
||||||
* type: object
|
|
||||||
* 404:
|
|
||||||
* description: Group not found
|
|
||||||
* 500:
|
|
||||||
* description: Server error
|
|
||||||
*/
|
|
||||||
// Einzelne Gruppe abrufen (nur freigegebene)
|
// Einzelne Gruppe abrufen (nur freigegebene)
|
||||||
router.get('/groups/:groupId', async (req, res) => {
|
router.get('/groups/:groupId', async (req, res) => {
|
||||||
|
/*
|
||||||
|
#swagger.tags = ['Public Groups']
|
||||||
|
#swagger.summary = 'Get approved group by ID'
|
||||||
|
#swagger.parameters['groupId'] = {
|
||||||
|
in: 'path',
|
||||||
|
required: true,
|
||||||
|
type: 'string',
|
||||||
|
description: 'Public groupId (e.g. cTV24Yn-a)'
|
||||||
|
}
|
||||||
|
#swagger.responses[200] = { description: 'Group payload (images + metadata)' }
|
||||||
|
#swagger.responses[404] = { description: 'Group not found or not approved' }
|
||||||
|
#swagger.responses[500] = { description: 'Server error' }
|
||||||
|
*/
|
||||||
try {
|
try {
|
||||||
const { groupId } = req.params;
|
const { groupId } = req.params;
|
||||||
const group = await GroupRepository.getGroupById(groupId);
|
const group = await GroupRepository.getGroupById(groupId);
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,20 @@ router.use(requireCsrf);
|
||||||
* description: Server error during reordering
|
* description: Server error during reordering
|
||||||
*/
|
*/
|
||||||
router.put('/:groupId/reorder', async (req, res) => {
|
router.put('/:groupId/reorder', async (req, res) => {
|
||||||
|
/*
|
||||||
|
#swagger.tags = ['Admin - Groups Moderation']
|
||||||
|
#swagger.summary = 'Reorder images within a group'
|
||||||
|
#swagger.parameters['groupId'] = {
|
||||||
|
in: 'path',
|
||||||
|
required: true,
|
||||||
|
type: 'string',
|
||||||
|
description: 'Admin groupId'
|
||||||
|
}
|
||||||
|
#swagger.responses[200] = { description: 'Order updated successfully' }
|
||||||
|
#swagger.responses[400] = { description: 'Validation error' }
|
||||||
|
#swagger.responses[404] = { description: 'Group not found' }
|
||||||
|
#swagger.responses[500] = { description: 'Internal server error' }
|
||||||
|
*/
|
||||||
try {
|
try {
|
||||||
const { groupId } = req.params;
|
const { groupId } = req.params;
|
||||||
const { imageIds } = req.body;
|
const { imageIds } = req.body;
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,11 @@ const router = express.Router();
|
||||||
* Public endpoint: list active social media platforms for consent selection
|
* Public endpoint: list active social media platforms for consent selection
|
||||||
*/
|
*/
|
||||||
router.get('/social-media/platforms', async (req, res) => {
|
router.get('/social-media/platforms', async (req, res) => {
|
||||||
|
/*
|
||||||
|
#swagger.tags = ['Consent Management']
|
||||||
|
#swagger.summary = 'List active social media platforms'
|
||||||
|
#swagger.description = 'Public endpoint that exposes the available platforms for consent selection on the upload form.'
|
||||||
|
*/
|
||||||
try {
|
try {
|
||||||
const socialMediaRepo = new SocialMediaRepository(dbManager);
|
const socialMediaRepo = new SocialMediaRepository(dbManager);
|
||||||
const platforms = await socialMediaRepo.getActivePlatforms();
|
const platforms = await socialMediaRepo.getActivePlatforms();
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ RUN npm install --production
|
||||||
COPY backend/src ./src
|
COPY backend/src ./src
|
||||||
|
|
||||||
# Copy production environment configuration
|
# Copy production environment configuration
|
||||||
COPY docker/prod/backend/config/.env ./.env
|
#COPY docker/prod/backend/config/.env ./.env
|
||||||
|
|
||||||
# Create data directories for file storage
|
# Create data directories for file storage
|
||||||
RUN mkdir -p src/data/images src/data/previews src/data/groups
|
RUN mkdir -p src/data/images src/data/previews src/data/groups
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,9 @@ services:
|
||||||
networks:
|
networks:
|
||||||
- prod-internal
|
- prod-internal
|
||||||
environment:
|
environment:
|
||||||
|
- REMOVE_IMAGES=false
|
||||||
- NODE_ENV=production
|
- NODE_ENV=production
|
||||||
- ADMIN_SESSION_SECRET=${ADMIN_SESSION_SECRET}
|
- ADMIN_SESSION_SECRET=MvFhivVIPIXvSGvWGfGOiQCkUJrmUsjWQTNGUgnSmtpsGHQlKruTBEBZgbVvOHHr
|
||||||
- ADMIN_SESSION_DIR=/usr/src/app/src/data/sessions
|
- ADMIN_SESSION_DIR=/usr/src/app/src/data/sessions
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user