From 87bdb9e8f3b5654a97adcabad3d34f116a473dd5 Mon Sep 17 00:00:00 2001 From: dev Date: Sun, 6 Sep 2026 12:25:39 +0300 Subject: [PATCH] add Files admin section: list with filters, search, student/group/date, pagination --- public/admin.js | 1 + public/files.html | 158 ++++++++++++++++++++++++++++++++++++++++++++++ server.js | 34 ++++++++++ 3 files changed, 193 insertions(+) create mode 100644 public/files.html diff --git a/public/admin.js b/public/admin.js index bc6df29..8e95f2b 100644 --- a/public/admin.js +++ b/public/admin.js @@ -23,6 +23,7 @@ function doLogout() { const NAV = [ { page: 'dashboard', label: 'Дашборд' }, { page: 'journal', label: 'Журнал' }, + { page: 'files', label: 'Файлы' }, { page: 'links', label: 'Ссылки' }, { page: 'students', label: 'Ученики' }, { page: 'groups', label: 'Группы' }, diff --git a/public/files.html b/public/files.html new file mode 100644 index 0000000..9b26494 --- /dev/null +++ b/public/files.html @@ -0,0 +1,158 @@ + + + + + +Файлы — Админ-панель + + + + +
+ +
+

Файлы

+
+
+
+
+
+
+
+
Загрузка...
+
+
+
+ + + +
+ + + + \ No newline at end of file diff --git a/server.js b/server.js index cd3007f..24ba1c3 100644 --- a/server.js +++ b/server.js @@ -514,6 +514,40 @@ function isImageName(name) { return /\.(jpe?g|png|gif|webp|bmp|avif|svg|ico)$/i.test(name || ''); } +app.get('/api/files', requireAdmin, async (req, res) => { + const { search, student_name, group_id, date_from, date_to, limit, offset } = req.query; + const conditions = []; + const params = []; + if (search) { params.push(`%${search}%`); conditions.push(`pf.name ILIKE $${params.length}`); } + if (student_name) { params.push(student_name); conditions.push(`e.student_name = $${params.length}`); } + if (group_id) { params.push(group_id); conditions.push(`e.group_id = $${params.length}`); } + if (date_from) { params.push(date_from); conditions.push(`e.created_at >= $${params.length}::date`); } + if (date_to) { params.push(date_to); conditions.push(`e.created_at < ($${params.length}::date + interval '1 day')`); } + const where = conditions.length ? ' WHERE ' + conditions.join(' AND ') : ''; + const { rows: crows } = await pool.query( + `SELECT count(*)::int AS n FROM project_files pf JOIN entries e ON e.id = pf.entry_id${where}`, + params + ); + const total = crows[0].n; + let q = `SELECT pf.id, pf.token, pf.name, pf.path, e.student_name, e.created_at, g.name AS group_name + FROM project_files pf + JOIN entries e ON e.id = pf.entry_id + JOIN groups g ON g.id = e.group_id${where} + ORDER BY pf.id DESC`; + const qparams = params.slice(); + const lim = parseInt(limit, 10); + if (lim > 0) { qparams.push(lim); q += ` LIMIT $${qparams.length}`; } + const off = parseInt(offset, 10); + if (off > 0) { qparams.push(off); q += ` OFFSET $${qparams.length}`; } + const { rows } = await pool.query(q, qparams); + const files = rows.map(r => { + let size = 0; + try { size = fs.statSync(path.join(__dirname, r.path)).size; } catch {} + return { id: r.id, token: r.token, name: r.name, student_name: r.student_name, group_name: r.group_name, created_at: r.created_at, size }; + }); + res.json({ files, total }); +}); + app.get('/api/files/:token', async (req, res) => { const { rows } = await pool.query('SELECT path, name FROM project_files WHERE token = $1', [req.params.token]); if (!rows.length) return res.status(404).json({ error: 'Not found' });