Files
myworkspace/LinkSyncServer/app.py
DavidSaylor 77b076c7d7 feat: add web UI with login, CRUD, admin, and API key management
- 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
2026-05-21 07:21:49 -05:00

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})