import { Elysia, t } from 'elysia' import { db } from '@/utils/database' import { m, eta, error as errorPage } from '@/utils/html' import { getSessionToken, createAdminSession, isValidAdminSession, destroyAdminSession, buildSessionCookie, clearSessionCookie } from '@/utils/adminAuth' import { initiateRestore } from '@/utils/glacier' import { approveRequest, rejectRequest, dismissRequest } from '@/utils/archiveRequests' const app = new Elysia({ prefix: '/admin' }) app.onBeforeHandle(async ({ path, request, set, headers, redirect }) => { if (path === '/admin/login') return const token = getSessionToken(headers.cookie) if (await isValidAdminSession(token)) return if (request.method === 'GET') return redirect('/admin/login') set.status = 401 return { success: false, message: 'Unauthorized' } }) app.get('/login', async ({ set }) => { set.headers['Content-Type'] = 'text/html; charset=utf-8' return await m(eta.render('./admin/login', { title: 'Admin Login | PreserveTube' })) }) app.post('/login', async ({ body, set, redirect }) => { if (body.password !== process.env.ADMIN_SECRET) { set.headers['Content-Type'] = 'text/html; charset=utf-8' set.status = 401 return await m(eta.render('./admin/login', { title: 'Admin Login | PreserveTube', loginError: 'Incorrect password.' })) } const token = await createAdminSession() set.headers['Set-Cookie'] = buildSessionCookie(token) return redirect('/admin') }, { body: t.Object({ password: t.String() }) }) app.post('/logout', async ({ headers, redirect }) => { const token = getSessionToken(headers.cookie) if (token) await destroyAdminSession(token) return new Response(null, { status: 302, headers: { 'Set-Cookie': clearSessionCookie(), Location: '/admin/login' } }) }) app.get('/', async ({ set }) => { const requests = await db.selectFrom('restore_requests') .selectAll() .orderBy('created_at', 'desc') .execute() const archiveRequests = await db.selectFrom('archive_requests') .selectAll() .orderBy('created_at', 'desc') .limit(100) .execute() const autoApproveSenders = await db.selectFrom('auto_approve_senders') .selectAll() .orderBy('created_at', 'asc') .execute() const fmtLength = (seconds: number | null) => { if (seconds === null || seconds === undefined) return '?' const h = Math.floor(seconds / 3600) const mm = String(Math.floor((seconds % 3600) / 60)).padStart(h ? 2 : 1, '0') const ss = String(seconds % 60).padStart(2, '0') return h ? `${h}:${mm}:${ss}` : `${mm}:${ss}` } set.headers['Content-Type'] = 'text/html; charset=utf-8' return await m(eta.render('./admin/dashboard', { title: 'Admin | PreserveTube', requests, archiveRequests, autoApproveSenders, fmtLength })) }) app.post('/restore', async ({ body, redirect, error }) => { const { videoId, requesterEmail } = body const video = await db.selectFrom('videos') .select(['id', 'deletion_stage']) .where('id', '=', videoId) .executeTakeFirst() if (!video) return error(404, 'No archived video found with that ID.') if (video.deletion_stage !== 'cold_storage') return error(400, 'That video is not currently in cold storage.') const inserted = await db.insertInto('restore_requests') .values({ videoId, requester_email: requesterEmail, status: 'requested' }) .returning('uuid') .executeTakeFirstOrThrow() await initiateRestore(videoId) await db.updateTable('restore_requests') .set({ status: 'restoring', aws_restore_requested_at: new Date(), updated_at: new Date() }) .where('uuid', '=', inserted.uuid) .execute() return redirect('/admin') }, { body: t.Object({ videoId: t.String(), requesterEmail: t.String() }) }) app.post('/requests/:id/approve', async ({ params, redirect }) => { await approveRequest(params.id) return redirect('/admin') }) app.post('/requests/:id/reject', async ({ params, body, redirect }) => { await rejectRequest(params.id, body.note) return redirect('/admin') }, { body: t.Object({ note: t.Optional(t.String()) }) }) app.post('/requests/:id/dismiss', async ({ params, redirect }) => { await dismissRequest(params.id) return redirect('/admin') }) app.post('/auto-approve', async ({ body, redirect }) => { await db.insertInto('auto_approve_senders') .values({ email: body.email.trim().toLowerCase(), note: body.note?.trim() || null }) .onConflict(oc => oc.column('email').doNothing()) .execute() return redirect('/admin') }, { body: t.Object({ email: t.String(), note: t.Optional(t.String()) }) }) app.post('/auto-approve/delete', async ({ body, redirect }) => { await db.deleteFrom('auto_approve_senders') .where('email', '=', body.email) .execute() return redirect('/admin') }, { body: t.Object({ email: t.String() }) }) app.onError(errorPage) export default app