This commit is contained in:
@@ -20,10 +20,7 @@ const AUDIO_MIME_TYPES = new Set([
|
||||
'audio/webm'
|
||||
]);
|
||||
|
||||
const IMAGE_EXTENSIONS = new Set(['.jpg', '.jpeg', '.png', '.webp']);
|
||||
const AUDIO_EXTENSIONS = new Set(['.mp3', '.mp4', '.m4a', '.aac', '.ogg', '.wav', '.webm']);
|
||||
|
||||
const uniqueFilename = original => `${crypto.randomUUID()}${path.extname(original).toLowerCase()}`;
|
||||
const uniqueFilename = file => `${crypto.randomUUID()}.${file.detectedExtension}`;
|
||||
const pictureUrl = (routeId, poiId, pictureId) => `/api/routes/${routeId}/pois/${poiId}/pictures/${pictureId}`;
|
||||
const audioUrl = (routeId, poiId) => `/api/routes/${routeId}/pois/${poiId}/audio`;
|
||||
|
||||
@@ -50,16 +47,16 @@ function poiOnRoute(db, routeId, poiId) {
|
||||
function validateUpload(file, kind) {
|
||||
if (!file) throw new HttpError(400, `Eine ${kind === 'picture' ? 'Bilddatei' : 'Audiodatei'} ist erforderlich.`);
|
||||
|
||||
const extension = path.extname(file.originalname).toLowerCase();
|
||||
const baseMime = String(file.detectedMime || file.mimetype || '').split(';', 1)[0].toLowerCase();
|
||||
const valid = kind === 'picture'
|
||||
? IMAGE_MIME_TYPES.has(file.mimetype) || IMAGE_EXTENSIONS.has(extension)
|
||||
: AUDIO_MIME_TYPES.has(file.mimetype) || AUDIO_EXTENSIONS.has(extension);
|
||||
? IMAGE_MIME_TYPES.has(baseMime)
|
||||
: AUDIO_MIME_TYPES.has(baseMime);
|
||||
|
||||
if (!valid) {
|
||||
if (!valid || !file.detectedExtension) {
|
||||
removeUpload(file);
|
||||
throw new HttpError(415, kind === 'picture'
|
||||
? 'Der Upload ist keine unterstützte Bilddatei.'
|
||||
: 'Der Upload ist keine unterstützte Audiodatei.');
|
||||
? 'Der Dateiinhalt ist keine unterstützte Bilddatei.'
|
||||
: 'Der Dateiinhalt ist keine unterstützte Audiodatei.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +122,7 @@ export function createPicture(db, routeId, poiId, fields, file) {
|
||||
const caption = String(fields.caption ?? '');
|
||||
const root = ensureRouteDirectories(routeId);
|
||||
|
||||
relativePath = moveUploadedFile(file, path.join(root, 'images', uniqueFilename(file.originalname)));
|
||||
relativePath = moveUploadedFile(file, path.join(root, 'images', uniqueFilename(file)));
|
||||
const result = db.prepare('INSERT INTO poi_images (poi_id, path, caption, sequence) VALUES (?, ?, ?, ?)')
|
||||
.run(poiId, relativePath, caption, sequence);
|
||||
return getPicture(db, routeId, poiId, Number(result.lastInsertRowid));
|
||||
@@ -150,7 +147,7 @@ export function updatePicture(db, routeId, poiId, pictureId, fields, file) {
|
||||
if (file) {
|
||||
validateUpload(file, 'picture');
|
||||
const root = ensureRouteDirectories(routeId);
|
||||
newPath = moveUploadedFile(file, path.join(root, 'images', uniqueFilename(file.originalname)));
|
||||
newPath = moveUploadedFile(file, path.join(root, 'images', uniqueFilename(file)));
|
||||
}
|
||||
|
||||
db.prepare('UPDATE poi_images SET path = ?, caption = ?, sequence = ? WHERE id = ? AND poi_id = ?')
|
||||
@@ -216,7 +213,7 @@ function storeAudio(db, routeId, poiId, file, { requireAbsent, requireExisting }
|
||||
}
|
||||
|
||||
const root = ensureRouteDirectories(routeId);
|
||||
relativePath = moveUploadedFile(file, path.join(root, 'audio', uniqueFilename(file.originalname)));
|
||||
relativePath = moveUploadedFile(file, path.join(root, 'audio', uniqueFilename(file)));
|
||||
db.prepare('UPDATE pois SET audio_path = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND route_id = ?')
|
||||
.run(relativePath, poiId, routeId);
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { parseGpx } from './gpx.js';
|
||||
import { distanceMeters, routeMetrics } from './geo.js';
|
||||
import {
|
||||
ensureRouteDirectories, moveUploadedFile, routeDirectory,
|
||||
softDeleteRouteDirectory, restoreRouteDirectory, resolveStoredFile, removeUpload
|
||||
softDeleteRouteDirectory, restoreRouteDirectory, resolveStoredFile, removeUpload, removeStoredFile
|
||||
} from './storage.js';
|
||||
|
||||
const slugify = value => value.toLowerCase().normalize('NFKD').replace(/[\u0300-\u036f]/g, '').replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 80) || `route-${Date.now()}`;
|
||||
@@ -188,6 +188,22 @@ export function updatePoi(db, routeId, poiId, fields) {
|
||||
return listPois(db, existing.route_id).find(poi => poi.id === Number(poiId));
|
||||
}
|
||||
|
||||
export function deletePoi(db, routeId, poiId) {
|
||||
const existing = db.prepare(`SELECT p.id, p.audio_path FROM pois p JOIN routes r ON r.id = p.route_id
|
||||
WHERE p.id = ? AND p.route_id = ? AND r.status = 'active'`).get(poiId, routeId);
|
||||
if (!existing) throw new HttpError(404, 'POI nicht gefunden.');
|
||||
|
||||
const images = db.prepare('SELECT path FROM poi_images WHERE poi_id = ?').all(poiId);
|
||||
transaction(db, () => {
|
||||
db.prepare('DELETE FROM poi_images WHERE poi_id = ?').run(poiId);
|
||||
db.prepare('DELETE FROM pois WHERE id = ? AND route_id = ?').run(poiId, routeId);
|
||||
});
|
||||
|
||||
for (const image of images) removeStoredFile(image.path);
|
||||
removeStoredFile(existing.audio_path);
|
||||
return { id: poiId, routeId, deleted: true };
|
||||
}
|
||||
|
||||
export function softDeleteRoute(db, id) {
|
||||
const route = db.prepare("SELECT * FROM routes WHERE id = ? AND status = 'active'").get(id);
|
||||
if (!route) throw new HttpError(404, 'Aktive Strecke nicht gefunden.');
|
||||
|
||||
Reference in New Issue
Block a user