Import source: backend/src/routes/admin

Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
arsam
2026-09-18 23:12:31 +03:30
parent 142032462a
commit 62b82da64f
+162
View File
@@ -0,0 +1,162 @@
import express from 'express';
import { z } from 'zod';
import { requireAdmin } from '../middleware/adminAuth.js';
import { HttpError } from '../middleware/errorHandler.js';
import { store } from '../services/store.js';
import type { FileStatus, Plan } from '../types.js';
import { fileToApi, sanitizeFilename } from '../utils/files.js';
import * as gdrive from '../utils/gdrive.js';
const router = express.Router();
router.use(requireAdmin);
const filePatchSchema = z.object({
originalName: z.string().trim().min(1).max(255).optional(),
uploader: z.string().trim().min(1).max(80).optional(),
expiresAt: z.string().datetime().optional(),
status: z.enum(['uploaded', 'deleted', 'expired']).optional(),
});
const userPatchSchema = z.object({
plan: z.enum(['free', 'pro', 'team']).optional(),
verified: z.boolean().optional(),
});
function publicUser(user: {
id: string;
email: string;
plan: Plan;
verified: boolean;
createdAt: string;
updatedAt: string;
}) {
return {
id: user.id,
email: user.email,
plan: user.plan,
verified: user.verified,
createdAt: user.createdAt,
updatedAt: user.updatedAt,
};
}
async function unlinkStoredFile(fileId: string) {
await gdrive.deleteFile(fileId).catch(() => {});
}
router.get('/overview', async (_req, res) => {
const db = await store.read();
const filesByStatus = db.files.reduce<Record<FileStatus, number>>((acc, file) => {
acc[file.status] += 1;
return acc;
}, { uploaded: 0, deleted: 0, expired: 0 });
res.json({
data: {
users: db.users.length,
files: db.files.length,
filesByStatus,
events: db.events.length,
totalBytes: db.files.reduce((sum, file) => sum + file.sizeBytes, 0),
},
});
});
router.get('/files', async (req, res) => {
const status = String(req.query.status ?? 'all');
const db = await store.read();
const files = db.files
.filter((file) => status === 'all' || file.status === status)
.map(fileToApi);
res.json({ data: files, meta: { total: files.length } });
});
router.get('/files/:id', async (req, res) => {
const file = (await store.read()).files.find((row) => row.id === req.params.id);
if (!file) throw new HttpError(404, 'FILE_NOT_FOUND', 'File not found');
res.json({ data: fileToApi(file) });
});
router.patch('/files/:id', async (req, res) => {
const body = filePatchSchema.parse(req.body);
const file = await store.update((db) => {
const row = db.files.find((item) => item.id === req.params.id);
if (!row) return null;
if (body.originalName) row.originalName = sanitizeFilename(body.originalName);
if (body.uploader) row.uploader = body.uploader;
if (body.expiresAt) row.expiresAt = body.expiresAt;
if (body.status) {
row.status = body.status;
row.deletedAt = body.status === 'uploaded' ? undefined : new Date().toISOString();
}
return row;
});
if (!file) throw new HttpError(404, 'FILE_NOT_FOUND', 'File not found');
res.json({ data: fileToApi(file) });
});
router.delete('/files/:id', async (req, res) => {
const purge = req.query.purge !== 'false';
const deleted = await store.update(async (db) => {
const row = db.files.find((item) => item.id === req.params.id);
if (!row) return null;
row.status = 'deleted';
row.deletedAt = new Date().toISOString();
if (purge) await unlinkStoredFile(row.gdriveId);
return row;
});
if (!deleted) throw new HttpError(404, 'FILE_NOT_FOUND', 'File not found');
res.status(204).send();
});
router.get('/users', async (_req, res) => {
const users = (await store.read()).users.map(publicUser);
res.json({ data: users, meta: { total: users.length } });
});
router.patch('/users/:id', async (req, res) => {
const body = userPatchSchema.parse(req.body);
const user = await store.update((db) => {
const row = db.users.find((item) => item.id === req.params.id);
if (!row) return null;
if (body.plan) row.plan = body.plan;
if (typeof body.verified === 'boolean') row.verified = body.verified;
row.updatedAt = new Date().toISOString();
return row;
});
if (!user) throw new HttpError(404, 'USER_NOT_FOUND', 'User not found');
res.json({ data: publicUser(user) });
});
router.delete('/users/:id', async (req, res) => {
const removeFiles = req.query.files === 'delete';
const deleted = await store.update(async (db) => {
const index = db.users.findIndex((item) => item.id === req.params.id);
if (index === -1) return null;
const [user] = db.users.splice(index, 1);
db.refreshTokens = db.refreshTokens.filter((token) => token.userId !== user.id);
if (removeFiles) {
for (const file of db.files.filter((item) => item.uploader === user.email)) {
file.status = 'deleted';
file.deletedAt = new Date().toISOString();
await unlinkStoredFile(file.gdriveId);
}
}
return user;
});
if (!deleted) throw new HttpError(404, 'USER_NOT_FOUND', 'User not found');
res.status(204).send();
});
router.get('/events', async (_req, res) => {
const events = (await store.read()).events.slice().reverse();
res.json({ data: events, meta: { total: events.length } });
});
export { router as adminRouter };