skrift-orders: oeffentlicher Upload-Proxy /upload (FilesService, Base64) fuer Motiv-/Quelldatei-Upload durch Gaeste
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
import crypto from 'node:crypto';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { Readable } from 'node:stream';
|
||||
|
||||
/** Basisordner der generierten Auftragsdateien (read-only in Directus gemountet). */
|
||||
const OUTPUT_DIR = process.env.SKRIFT_OUTPUT_DIR || '/var/skrift-output';
|
||||
@@ -155,7 +156,7 @@ async function paypalToken() {
|
||||
}
|
||||
|
||||
const handler = (router, { services, getSchema, logger }) => {
|
||||
const { ItemsService, MailService, UsersService, RolesService, AssetsService } = services;
|
||||
const { ItemsService, MailService, UsersService, RolesService, AssetsService, FilesService } = services;
|
||||
|
||||
// Signatur-Plotter-Service (intern über das npm-Netz per container_name erreichbar).
|
||||
const SIGN_URL = (process.env.SKRIFT_SIGN_URL || 'http://skrift-signature-service:8000').replace(/\/$/, '');
|
||||
@@ -774,6 +775,41 @@ const handler = (router, { services, getSchema, logger }) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Öffentlicher Datei-Upload (Motiv/Quelldatei). Gäste bestellen ohne Login und
|
||||
* haben keinen Directus-Token für /files – daher hier serverseitig via FilesService
|
||||
* (admin) ablegen. Datei kommt als Base64 im JSON. Nur Bilder + PDF, max. 20 MB.
|
||||
*/
|
||||
router.post('/upload', async (req, res) => {
|
||||
const body = req.body || {};
|
||||
const roh = typeof body.data_base64 === 'string' ? body.data_base64.replace(/^data:[^;]+;base64,/, '') : '';
|
||||
if (!roh) return res.status(400).json({ error: 'Keine Datei übergeben.' });
|
||||
const type = String(body.content_type || 'application/octet-stream');
|
||||
if (!/^image\/(png|jpe?g|gif|webp|svg\+xml)$|^application\/pdf$/i.test(type)) {
|
||||
return res.status(415).json({ error: 'Dateityp nicht erlaubt (nur Bilder oder PDF).' });
|
||||
}
|
||||
let buffer;
|
||||
try { buffer = Buffer.from(roh, 'base64'); } catch { return res.status(400).json({ error: 'Ungültige Datei.' }); }
|
||||
if (!buffer.length) return res.status(400).json({ error: 'Leere Datei.' });
|
||||
if (buffer.length > 20 * 1024 * 1024) return res.status(413).json({ error: 'Datei zu groß (max. 20 MB).' });
|
||||
try {
|
||||
const schema = await getSchema();
|
||||
const files = new FilesService({ schema, accountability: null });
|
||||
const name = (String(body.filename || 'upload').replace(/[^\w.\- ]+/g, '_').slice(0, 120) || 'upload');
|
||||
const storage = (process.env.STORAGE_LOCATIONS || 'local').split(',')[0].trim() || 'local';
|
||||
const id = await files.uploadOne(
|
||||
Readable.from(buffer),
|
||||
{ storage, filename_download: name, title: name, type },
|
||||
undefined,
|
||||
{ emitEvents: false },
|
||||
);
|
||||
return res.json({ id });
|
||||
} catch (err) {
|
||||
logger.error(`[skrift-orders] upload: ${err.stack || err.message}`);
|
||||
return res.status(500).json({ error: 'Upload fehlgeschlagen.' });
|
||||
}
|
||||
});
|
||||
|
||||
/** Aktuelles Kontingent abfragen, ohne etwas zu verbrauchen. */
|
||||
router.get('/preview/quota', async (req, res) => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user