diff --git a/backend/src/routes/analytics.ts b/backend/src/routes/analytics.ts new file mode 100644 index 0000000..798489d --- /dev/null +++ b/backend/src/routes/analytics.ts @@ -0,0 +1,79 @@ +import express from 'express'; +import { HttpError } from '../middleware/errorHandler.js'; +import { store } from '../services/store.js'; +import { getSessionId } from '../middleware/session.js'; +import { getDailyBreakdown, getTopReferrers, recordEvent } from '../services/analytics.js'; + +const router = express.Router(); + +/** + * GET /api/v1/files/:id/analytics + * Retrieve detailed analytics for a specific file owned by the user. + */ +router.get('/files/:id/analytics', async (req, res, next) => { + try { + const sid = getSessionId(req); + const db = await store.read(); + const file = db.files.find((f) => f.id === req.params.id && f.status === 'uploaded'); + + if (!file) { + throw new HttpError(404, 'FILE_NOT_FOUND', 'File not found'); + } + if (file.sessionId !== sid) { + throw new HttpError(403, 'ACCESS_DENIED', 'Access denied to this file analytics'); + } + + const dailyBreakdown = await getDailyBreakdown(file.linkId); + const topReferrers = await getTopReferrers(file.linkId); + + res.json({ + data: { + fileId: file.id, + linkId: file.linkId, + views: file.views, + downloads: file.downloads, + dailyBreakdown, + topReferrers, + }, + }); + } catch (err) { + next(err); + } +}); + +/** + * POST /api/v1/events + * Record an event (view or download) on a shared link. + */ +router.post('/events', async (req, res, next) => { + try { + const { linkId, type } = req.body; + + if (!linkId || !type || (type !== 'view' && type !== 'download')) { + throw new HttpError(400, 'INVALID_INPUT', 'Missing or invalid linkId or type'); + } + + const db = await store.read(); + const file = db.files.find((f) => f.linkId === linkId && f.status === 'uploaded'); + + if (!file) { + throw new HttpError(404, 'LINK_NOT_FOUND', 'Active shared link not found'); + } + + const ip = req.ip; + const userAgent = req.headers['user-agent'] as string; + const referrer = (req.headers.referer || req.headers.referrer) as string; + + await recordEvent(linkId, type, ip, userAgent, referrer); + + res.status(201).json({ + data: { + status: 'recorded', + }, + }); + } catch (err) { + next(err); + } +}); + +export { router as analyticsRouter };