R1 foundation - Phase 1 live build

This commit is contained in:
2026-02-28 03:33:33 +00:00
commit f36ea194f3
45 changed files with 4009 additions and 0 deletions

113
routers/links.py Normal file
View File

@@ -0,0 +1,113 @@
"""Links: URL references attached to domains/projects."""
from fastapi import APIRouter, Request, Form, Depends
from fastapi.templating import Jinja2Templates
from fastapi.responses import RedirectResponse
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import text
from typing import Optional
from core.database import get_db
from core.base_repository import BaseRepository
from core.sidebar import get_sidebar_data
router = APIRouter(prefix="/links", tags=["links"])
templates = Jinja2Templates(directory="templates")
@router.get("/")
async def list_links(request: Request, domain_id: Optional[str] = None, db: AsyncSession = Depends(get_db)):
sidebar = await get_sidebar_data(db)
where_clauses = ["l.is_deleted = false"]
params = {}
if domain_id:
where_clauses.append("l.domain_id = :domain_id")
params["domain_id"] = domain_id
where_sql = " AND ".join(where_clauses)
result = await db.execute(text(f"""
SELECT l.*, d.name as domain_name, d.color as domain_color, p.name as project_name
FROM links l
LEFT JOIN domains d ON l.domain_id = d.id
LEFT JOIN projects p ON l.project_id = p.id
WHERE {where_sql} ORDER BY l.sort_order, l.created_at
"""), params)
items = [dict(r._mapping) for r in result]
domains_repo = BaseRepository("domains", db)
domains = await domains_repo.list()
return templates.TemplateResponse("links.html", {
"request": request, "sidebar": sidebar, "items": items, "domains": domains,
"current_domain_id": domain_id or "",
"page_title": "Links", "active_nav": "links",
})
@router.get("/create")
async def create_form(request: Request, domain_id: Optional[str] = None, project_id: Optional[str] = None, db: AsyncSession = Depends(get_db)):
sidebar = await get_sidebar_data(db)
domains_repo = BaseRepository("domains", db)
domains = await domains_repo.list()
projects_repo = BaseRepository("projects", db)
projects = await projects_repo.list()
return templates.TemplateResponse("link_form.html", {
"request": request, "sidebar": sidebar, "domains": domains, "projects": projects,
"page_title": "New Link", "active_nav": "links",
"item": None, "prefill_domain_id": domain_id or "", "prefill_project_id": project_id or "",
})
@router.post("/create")
async def create_link(
request: Request, label: str = Form(...), url: str = Form(...),
domain_id: str = Form(...), project_id: Optional[str] = Form(None),
description: Optional[str] = Form(None), db: AsyncSession = Depends(get_db),
):
repo = BaseRepository("links", db)
data = {"label": label, "url": url, "domain_id": domain_id, "description": description}
if project_id and project_id.strip():
data["project_id"] = project_id
await repo.create(data)
referer = request.headers.get("referer", "/links")
return RedirectResponse(url="/links", status_code=303)
@router.get("/{link_id}/edit")
async def edit_form(link_id: str, request: Request, db: AsyncSession = Depends(get_db)):
repo = BaseRepository("links", db)
sidebar = await get_sidebar_data(db)
item = await repo.get(link_id)
if not item:
return RedirectResponse(url="/links", status_code=303)
domains_repo = BaseRepository("domains", db)
domains = await domains_repo.list()
projects_repo = BaseRepository("projects", db)
projects = await projects_repo.list()
return templates.TemplateResponse("link_form.html", {
"request": request, "sidebar": sidebar, "domains": domains, "projects": projects,
"page_title": "Edit Link", "active_nav": "links",
"item": item, "prefill_domain_id": "", "prefill_project_id": "",
})
@router.post("/{link_id}/edit")
async def update_link(
link_id: str, label: str = Form(...), url: str = Form(...),
domain_id: str = Form(...), project_id: Optional[str] = Form(None),
description: Optional[str] = Form(None), db: AsyncSession = Depends(get_db),
):
repo = BaseRepository("links", db)
await repo.update(link_id, {
"label": label, "url": url, "domain_id": domain_id,
"project_id": project_id if project_id and project_id.strip() else None,
"description": description,
})
return RedirectResponse(url="/links", status_code=303)
@router.post("/{link_id}/delete")
async def delete_link(link_id: str, request: Request, db: AsyncSession = Depends(get_db)):
repo = BaseRepository("links", db)
await repo.soft_delete(link_id)
return RedirectResponse(url=request.headers.get("referer", "/links"), status_code=303)