feat: env-driven upload limits + request timeout, inline video playback, range requests
- Add UPLOAD_FILE_LIMIT_MB/UPLOAD_TOTAL_LIMIT_MB env (defaults 50/200), compute request timeout from total limit or UPLOAD_REQUEST_TIMEOUT_MS - Expose upload limits via /api/public-settings and sync in frontend (remove hardcoded 50MB assumption) - Add byte-range support in storage (getRange/streamRangeTo) and serve Content-Range/Accept-Ranges for S3/local - Implement inline playable video delivery for browser formats (mp4/m4v/webm/ogv) with ?play=1, range requests, proper 206/416 - Add video modal in journal UI with player and download fallback - Update docs (AGENTS.md/PRD.md/README.md), styles for video modal, add instructions/TODO.md and screenshots - Extend MIME types for media
This commit is contained in:
+63
@@ -35,6 +35,22 @@ const MIME_TYPES = {
|
||||
'.7z': 'application/x-7z-compressed',
|
||||
'.doc': 'application/msword',
|
||||
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'.mp4': 'video/mp4',
|
||||
'.m4v': 'video/mp4',
|
||||
'.mov': 'video/quicktime',
|
||||
'.webm': 'video/webm',
|
||||
'.ogv': 'video/ogg',
|
||||
'.mpeg': 'video/mpeg',
|
||||
'.mpg': 'video/mpeg',
|
||||
'.mkv': 'video/x-matroska',
|
||||
'.avi': 'video/x-msvideo',
|
||||
'.3gp': 'video/3gpp',
|
||||
'.m3u8': 'application/vnd.apple.mpegurl',
|
||||
'.mp3': 'audio/mpeg',
|
||||
'.m4a': 'audio/mp4',
|
||||
'.oga': 'audio/ogg',
|
||||
'.wav': 'audio/wav',
|
||||
'.flac': 'audio/flac',
|
||||
};
|
||||
|
||||
const SAFE_SEGMENT = /^[\w,.()-]+$/;
|
||||
@@ -249,6 +265,33 @@ function createStorage(options = {}) {
|
||||
return null;
|
||||
}
|
||||
|
||||
async function getRange(key, start, end) {
|
||||
const k = normalizeKey(key);
|
||||
if (!k || !Number.isInteger(start) || start < 0) return null;
|
||||
const last = Number.isInteger(end) && end >= start ? end : null;
|
||||
if (localFallback && localExists(k)) {
|
||||
const fp = localFile(k);
|
||||
const size = fs.statSync(fp).size;
|
||||
const to = last === null ? size - 1 : Math.min(last, size - 1);
|
||||
if (start > to) return null;
|
||||
return { stream: fs.createReadStream(fp, { start, end: to }), contentLength: to - start + 1, contentType: mimeFor(k) };
|
||||
}
|
||||
const objKey = objectKey(k);
|
||||
if (!objKey) return null;
|
||||
try {
|
||||
const out = await client.send(new GetObjectCommand({
|
||||
Bucket: bucket,
|
||||
Key: objKey,
|
||||
Range: `bytes=${start}-${last === null ? '' : last}`,
|
||||
}));
|
||||
return { stream: out.Body, contentLength: out.ContentLength || 0, contentType: out.ContentType || mimeFor(k) };
|
||||
} catch (err) {
|
||||
if (isMissingError(err)) return null;
|
||||
if (err && (err.name === 'InvalidRange' || (err.$metadata && err.$metadata.httpStatusCode === 416))) return null;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async function del(key) {
|
||||
const k = normalizeKey(key);
|
||||
if (!k) return false;
|
||||
@@ -402,6 +445,24 @@ function createStorage(options = {}) {
|
||||
return true;
|
||||
}
|
||||
|
||||
async function streamRangeTo(res, key, start, end, opts = {}) {
|
||||
const k = normalizeKey(key);
|
||||
if (!k) return false;
|
||||
const total = await sizeOf(k);
|
||||
if (!total) return false;
|
||||
const source = await getRange(k, start, end);
|
||||
if (!source || !source.contentLength) return false;
|
||||
const lastByte = start + source.contentLength - 1;
|
||||
res.status(206);
|
||||
res.setHeader('Accept-Ranges', 'bytes');
|
||||
res.setHeader('Content-Range', `bytes ${start}-${lastByte}/${total}`);
|
||||
res.setHeader('Content-Type', opts.contentType || source.contentType || mimeFor(k));
|
||||
res.setHeader('Content-Length', String(source.contentLength));
|
||||
if (opts.cacheControl) res.setHeader('Cache-Control', opts.cacheControl);
|
||||
await pipeline(source.stream, res);
|
||||
return true;
|
||||
}
|
||||
|
||||
async function downloadAll(destDir, opts = {}) {
|
||||
fs.mkdirSync(destDir, { recursive: true });
|
||||
const root = path.resolve(destDir);
|
||||
@@ -525,12 +586,14 @@ function createStorage(options = {}) {
|
||||
sizeOf,
|
||||
getStream,
|
||||
getBuffer,
|
||||
getRange,
|
||||
del,
|
||||
copyObject,
|
||||
listAll,
|
||||
localize,
|
||||
persist,
|
||||
streamTo,
|
||||
streamRangeTo,
|
||||
downloadAll,
|
||||
uploadTree,
|
||||
ensureBucket,
|
||||
|
||||
Reference in New Issue
Block a user