## Copyright © 2026 Olaf Kolkman ## SPDX-License-Identifier: GPL-3.0-or-later from fastapi import FastAPI, Request import logging from uuid import uuid4 from fastapi.responses import HTMLResponse from fastapi.responses import RedirectResponse from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates from starlette.requests import Request from backend.app.api.admin import router as admin_router from backend.app.api.auth import router as auth_router from backend.app.api.links import router as links_router from backend.app.api.mastodon import router as mastodon_router from backend.app.api.public import router as public_router from backend.app.api.setup import router as setup_router from backend.app.api.setup import has_administrator from backend.app.api.user_config import router as user_config_router from backend.app.core.config import settings, validate_configuration from backend.app.database import AVATARS_DIR from backend.app.services.link_service import get_public_profile, list_public_links logging.basicConfig(level=getattr(logging, settings.log_level, logging.INFO)) validate_configuration(settings) app = FastAPI(title='LinkLog API', version=settings.version) @app.middleware('http') async def add_request_id(request: Request, call_next): request.state.request_id = request.headers.get('X-Request-ID') or str(uuid4()) response = await call_next(request) response.headers['X-Request-ID'] = request.state.request_id return response app.mount('/static', StaticFiles(directory='frontend/static'), name='static') app.mount('/media', StaticFiles(directory=AVATARS_DIR), name='media') app.include_router(auth_router, prefix='/api/auth') app.include_router(links_router, prefix='/api') app.include_router(mastodon_router, prefix='/api/mastodon') app.include_router(public_router, prefix='/api/public') app.include_router(admin_router, prefix='/api/admin') app.include_router(user_config_router, prefix='/api/user') app.include_router(setup_router, prefix='/api/setup') templates = Jinja2Templates(directory='frontend/templates') templates.env.globals['app_version'] = settings.version @app.get('/', response_class=HTMLResponse) async def public_root(request: Request): if not has_administrator(): return RedirectResponse('/setup') feed = list_public_links() return templates.TemplateResponse(request, 'feed.html', {'feed': feed}) @app.get('/admin', response_class=HTMLResponse) async def admin_dashboard(request: Request): if not has_administrator(): return RedirectResponse('/setup') return templates.TemplateResponse(request, 'admin.html', {}) @app.get('/profile', response_class=HTMLResponse) async def user_profile_page(request: Request): if not has_administrator(): return RedirectResponse('/setup') return templates.TemplateResponse(request, 'user_profile.html', {}) @app.get('/labels', response_class=HTMLResponse) async def labels_page(request: Request): if not has_administrator(): return RedirectResponse('/setup') return templates.TemplateResponse(request, 'labels.html', {}) @app.get('/new-entry', response_class=HTMLResponse) async def new_entry_page(request: Request): if not has_administrator(): return RedirectResponse('/setup') return templates.TemplateResponse(request, 'new-entry.html', {}) @app.get('/about', response_class=HTMLResponse) async def about_page(request: Request): return templates.TemplateResponse(request, 'about.html', {}) @app.get('/login', response_class=HTMLResponse) async def login_page(request: Request): if not has_administrator(): return RedirectResponse('/setup') return templates.TemplateResponse(request, 'login.html', {}) @app.get('/reset-password', response_class=HTMLResponse) async def reset_password_page(request: Request): return templates.TemplateResponse(request, 'reset-password.html', {}) @app.get('/setup', response_class=HTMLResponse) async def setup_page(request: Request): if has_administrator(): return RedirectResponse('/') return templates.TemplateResponse(request, 'setup.html', {}) @app.get('/health') def health_check(): return {'status': 'ok'} @app.get('/{username}', response_class=HTMLResponse) @app.get('/{username}/', response_class=HTMLResponse) async def public_user_feed(request: Request, username: str): feed = list_public_links(username) profile = get_public_profile(username) return templates.TemplateResponse( request, 'feed.html', {'feed': feed, 'profile': profile, 'user_filter': username}, )