Import source: backend/src/routes/links
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
@@ -0,0 +1,89 @@
|
|||||||
|
import express from 'express';
|
||||||
|
import { HttpError } from '../middleware/errorHandler.js';
|
||||||
|
import { store } from '../services/store.js';
|
||||||
|
import { fileToApi } from '../utils/files.js';
|
||||||
|
import { getPreviewKind, getPreviewMeta } from '../utils/preview.js';
|
||||||
|
import * as gdrive from '../utils/gdrive.js';
|
||||||
|
import { recordEventInDb } from '../services/analytics.js';
|
||||||
|
import { rateLimit } from '../middleware/rateLimit.js';
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
function isExpired(expiresAt: string) {
|
||||||
|
return new Date(expiresAt).getTime() <= Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
router.get('/:linkId', async (req, res) => {
|
||||||
|
const ip = req.ip;
|
||||||
|
const userAgent = req.headers['user-agent'] as string;
|
||||||
|
const referrer = (req.headers.referer || req.headers.referrer) as string;
|
||||||
|
|
||||||
|
const file = await store.update((db) => {
|
||||||
|
const row = db.files.find((f) => f.linkId === req.params.linkId && f.status === 'uploaded');
|
||||||
|
if (!row) return null;
|
||||||
|
if (isExpired(row.expiresAt)) {
|
||||||
|
row.status = 'expired';
|
||||||
|
row.deletedAt = new Date().toISOString();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
recordEventInDb(db, row.linkId, 'view', ip, userAgent, referrer);
|
||||||
|
return row;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!file) throw new HttpError(404, 'LINK_NOT_FOUND', 'Shared file not found or expired');
|
||||||
|
res.json({ data: fileToApi(file) });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Limit downloads and previews to 60 per minute per IP to prevent egress flooding
|
||||||
|
const downloadLimiter = rateLimit(60, 60000);
|
||||||
|
|
||||||
|
/** Increment download counter then redirect to Google Drive's direct download URL. */
|
||||||
|
router.get('/:linkId/download', downloadLimiter, async (req, res) => {
|
||||||
|
const ip = req.ip;
|
||||||
|
const userAgent = req.headers['user-agent'] as string;
|
||||||
|
const referrer = (req.headers.referer || req.headers.referrer) as string;
|
||||||
|
|
||||||
|
const file = await store.update((db) => {
|
||||||
|
const row = db.files.find((f) => f.linkId === req.params.linkId && f.status === 'uploaded');
|
||||||
|
if (!row || isExpired(row.expiresAt)) return null;
|
||||||
|
recordEventInDb(db, row.linkId, 'download', ip, userAgent, referrer);
|
||||||
|
return row;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!file) throw new HttpError(404, 'LINK_NOT_FOUND', 'Shared file not found or expired');
|
||||||
|
res.redirect(302, gdrive.downloadUrl(file.gdriveId));
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stream preview content directly from Drive via rclone cat — no disk writes.
|
||||||
|
* Only available for supported types under the preview size limit.
|
||||||
|
* The browser follows this as a normal src= URL (img, video, audio, iframe, fetch).
|
||||||
|
*/
|
||||||
|
router.get('/:linkId/preview', downloadLimiter, async (req, res) => {
|
||||||
|
const db = await store.read();
|
||||||
|
const file = db.files.find((f) => f.linkId === req.params.linkId && f.status === 'uploaded');
|
||||||
|
if (!file || isExpired(file.expiresAt)) {
|
||||||
|
throw new HttpError(404, 'LINK_NOT_FOUND', 'Shared file not found or expired');
|
||||||
|
}
|
||||||
|
|
||||||
|
const preview = getPreviewMeta(file);
|
||||||
|
const kind = getPreviewKind(file.mimeType);
|
||||||
|
if (!preview.available || !kind) {
|
||||||
|
throw new HttpError(413, 'PREVIEW_NOT_AVAILABLE', 'Preview is not available for this file');
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentType = kind === 'text' ? 'text/plain; charset=utf-8' : file.mimeType;
|
||||||
|
|
||||||
|
res.setHeader('X-Content-Type-Options', 'nosniff');
|
||||||
|
res.setHeader('Content-Type', contentType);
|
||||||
|
res.setHeader('Content-Disposition', `inline; filename="${encodeURIComponent(file.originalName)}"`);
|
||||||
|
res.setHeader('Content-Security-Policy', "default-src 'none'; style-src 'unsafe-inline'");
|
||||||
|
|
||||||
|
const fileStream = await gdrive.getStream(file.gdriveId);
|
||||||
|
fileStream.pipe(res);
|
||||||
|
fileStream.on('error', () => {
|
||||||
|
if (!res.headersSent) res.status(502).json({ error: 'STREAM_ERROR', message: 'Could not stream file from storage' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export { router as linksRouter };
|
||||||
Reference in New Issue
Block a user