- Add login page with JWT authentication - Add dashboard with stats and quick actions - Add links management page (full CRUD with search) - Add collections management page - Add API key management page with copy-to-clipboard - Add admin user management page (admin only) - Fix UUID type mismatches across all endpoints - Add updated_at column to api_keys and audit_log in schema.sql - Fix DB_PASSWORD default in docker-compose.yml - Add PyJWT to requirements.txt - Fix API docs URL (/docs instead of /api/docs) - Improve JS error handling (show actual messages) - Rewrite conftest.py with proper DB lifecycle management - Add 42 new integration tests (84 total, all passing) - test_admin.py: 15 tests for admin endpoints - test_auth_extended.py: 9 tests for API key CRUD - test_tags.py: 12 tests for tag endpoints - test_sync.py: 6 tests for sync endpoints
87 lines
2.0 KiB
Python
87 lines
2.0 KiB
Python
"""
|
|
LinkSyncServer - Main Application
|
|
"""
|
|
|
|
import os
|
|
from fastapi import FastAPI, Request
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.staticfiles import StaticFiles
|
|
from fastapi.templating import Jinja2Templates
|
|
from fastapi.responses import RedirectResponse
|
|
from contextlib import asynccontextmanager
|
|
|
|
from api.routes import router as api_router
|
|
from config.settings import settings
|
|
from models.base import Base, get_engine
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
engine = get_engine()
|
|
Base.metadata.create_all(engine)
|
|
yield
|
|
|
|
|
|
app = FastAPI(
|
|
title="LinkSyncServer",
|
|
description="Self-hosted bookmark server with collections",
|
|
version="1.0.0",
|
|
lifespan=lifespan,
|
|
)
|
|
|
|
cors_origins = [o.strip() for o in settings.CORS_ORIGINS.split(",") if o.strip()]
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=cors_origins,
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
app.include_router(api_router)
|
|
|
|
app.mount("/static", StaticFiles(directory="static"), name="static")
|
|
|
|
templates = Jinja2Templates(directory="templates")
|
|
|
|
|
|
@app.get("/health")
|
|
def health():
|
|
return {"status": "ok"}
|
|
|
|
|
|
@app.get("/")
|
|
def index():
|
|
return RedirectResponse(url="/login")
|
|
|
|
|
|
@app.get("/login")
|
|
def login_page(request: Request):
|
|
return templates.TemplateResponse("login.html", {"request": request})
|
|
|
|
|
|
@app.get("/dashboard")
|
|
def dashboard(request: Request):
|
|
return templates.TemplateResponse("dashboard.html", {"request": request})
|
|
|
|
|
|
@app.get("/links")
|
|
def links_page(request: Request):
|
|
return templates.TemplateResponse("links.html", {"request": request})
|
|
|
|
|
|
@app.get("/collections")
|
|
def collections_page(request: Request):
|
|
return templates.TemplateResponse("collections.html", {"request": request})
|
|
|
|
|
|
@app.get("/api-keys")
|
|
def apikeys_page(request: Request):
|
|
return templates.TemplateResponse("apikeys.html", {"request": request})
|
|
|
|
|
|
@app.get("/admin")
|
|
def admin_page(request: Request):
|
|
return templates.TemplateResponse("admin.html", {"request": request})
|