199 lines
5.4 KiB
Python
199 lines
5.4 KiB
Python
# Copyright 2025 Aman
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
import os
|
|
from fastapi import APIRouter, Request, Depends, Form
|
|
from fastapi.responses import HTMLResponse, RedirectResponse
|
|
from fastapi.templating import Jinja2Templates
|
|
from passlib.hash import argon2
|
|
|
|
from db.database import Database
|
|
from admin.settings_store import get_setting
|
|
from admin.auth import admin_required
|
|
|
|
router = APIRouter(prefix="/admin", tags=["admin"])
|
|
templates = Jinja2Templates(directory="admin/templates")
|
|
|
|
@router.get("")
|
|
async def admin_root():
|
|
return RedirectResponse("/admin/", status_code=302)
|
|
|
|
@router.get("/login", response_class=HTMLResponse)
|
|
async def login_page(request: Request):
|
|
return templates.TemplateResponse("login.html", {"request": request})
|
|
|
|
@router.post("/login")
|
|
async def login(
|
|
request: Request,
|
|
email: str = Form(...),
|
|
password: str = Form(...),
|
|
):
|
|
admin = await Database.pool.fetchrow(
|
|
"SELECT id, password_hash FROM admins WHERE email=$1",
|
|
email,
|
|
)
|
|
|
|
if not admin or not argon2.verify(password, admin["password_hash"]):
|
|
return templates.TemplateResponse(
|
|
"login.html",
|
|
{"request": request, "error": "Invalid credentials"},
|
|
status_code=401,
|
|
)
|
|
|
|
request.session["admin_id"] = admin["id"]
|
|
return RedirectResponse("/admin/", status_code=303)
|
|
|
|
@router.get("/settings", response_class=HTMLResponse)
|
|
async def settings_page(
|
|
request: Request,
|
|
auth=Depends(admin_required),
|
|
):
|
|
if isinstance(auth, RedirectResponse):
|
|
return auth
|
|
|
|
stats = await Database.pool.fetchrow("""
|
|
SELECT
|
|
COALESCE(SUM(file_size), 0) AS used_bytes,
|
|
COUNT(*) AS total_files,
|
|
COALESCE(MAX(file_size), 0) AS largest_file
|
|
FROM files
|
|
""")
|
|
|
|
cleanup = await get_setting("cleanup_enabled", "true")
|
|
|
|
return templates.TemplateResponse(
|
|
"settings.html",
|
|
{
|
|
"request": request,
|
|
"used_bytes": stats["used_bytes"],
|
|
"total_files": stats["total_files"],
|
|
"largest_file": stats["largest_file"],
|
|
"cleanup_enabled": cleanup == "true",
|
|
},
|
|
)
|
|
|
|
@router.post("/settings/save")
|
|
async def save_settings(
|
|
request: Request,
|
|
auth=Depends(admin_required),
|
|
):
|
|
if isinstance(auth, RedirectResponse):
|
|
return auth
|
|
|
|
return RedirectResponse("/admin/settings", status_code=303)
|
|
|
|
@router.post("/logout")
|
|
async def logout(request: Request):
|
|
request.session.clear()
|
|
return RedirectResponse("/admin/login", status_code=303)
|
|
|
|
@router.get("/", response_class=HTMLResponse)
|
|
async def dashboard(
|
|
request: Request,
|
|
q: str | None = None,
|
|
auth=Depends(admin_required),
|
|
):
|
|
if isinstance(auth, RedirectResponse):
|
|
return auth
|
|
|
|
stats = await Database.pool.fetchrow("""
|
|
SELECT
|
|
COUNT(*) AS total_files,
|
|
COALESCE(SUM(downloads), 0) AS total_downloads,
|
|
COUNT(*) FILTER (
|
|
WHERE expires_at IS NULL OR expires_at > NOW()
|
|
) AS active_files
|
|
FROM files
|
|
""")
|
|
|
|
files = await Database.pool.fetch("""
|
|
SELECT file_id, name, downloads, expires_at
|
|
FROM files
|
|
WHERE name ILIKE $1
|
|
ORDER BY downloads DESC
|
|
LIMIT 50
|
|
""", f"%{q or ''}%")
|
|
|
|
top_files = await Database.pool.fetch("""
|
|
SELECT name, downloads
|
|
FROM files
|
|
ORDER BY downloads DESC
|
|
LIMIT 5
|
|
""")
|
|
|
|
recent_files = await Database.pool.fetch("""
|
|
SELECT name, created_at
|
|
FROM files
|
|
ORDER BY created_at DESC
|
|
LIMIT 5
|
|
""")
|
|
|
|
expiring_files = await Database.pool.fetch("""
|
|
SELECT name, expires_at
|
|
FROM files
|
|
WHERE expires_at IS NOT NULL
|
|
AND expires_at > NOW()
|
|
ORDER BY expires_at ASC
|
|
LIMIT 5
|
|
""")
|
|
|
|
return templates.TemplateResponse(
|
|
"dashboard.html",
|
|
{
|
|
"request": request,
|
|
"stats": stats,
|
|
"files": files,
|
|
"top_files": top_files,
|
|
"recent_files": recent_files,
|
|
"expiring_files": expiring_files,
|
|
"query": q or "",
|
|
},
|
|
)
|
|
|
|
@router.post("/file/{file_id}/delete")
|
|
async def delete_file(file_id: str, auth=Depends(admin_required)):
|
|
if isinstance(auth, RedirectResponse):
|
|
return auth
|
|
|
|
row = await Database.pool.fetchrow(
|
|
"SELECT path FROM files WHERE file_id=$1",
|
|
file_id,
|
|
)
|
|
|
|
if row:
|
|
try:
|
|
os.remove(row["path"])
|
|
except Exception:
|
|
pass
|
|
|
|
await Database.pool.execute(
|
|
"DELETE FROM files WHERE file_id=$1",
|
|
file_id,
|
|
)
|
|
|
|
return RedirectResponse("/admin/", status_code=303)
|
|
|
|
|
|
@router.post("/file/{file_id}/disable")
|
|
async def disable_file(file_id: str, auth=Depends(admin_required)):
|
|
if isinstance(auth, RedirectResponse):
|
|
return auth
|
|
|
|
await Database.pool.execute(
|
|
"UPDATE files SET expires_at = NOW() WHERE file_id=$1",
|
|
file_id,
|
|
)
|
|
|
|
return RedirectResponse("/admin/", status_code=303)
|
|
|
|
|