R1 foundation - Phase 1 live build
This commit is contained in:
169
main.py
Normal file
169
main.py
Normal file
@@ -0,0 +1,169 @@
|
||||
"""
|
||||
Life OS - Main Application
|
||||
FastAPI server-rendered monolith with async PostgreSQL.
|
||||
"""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from contextlib import asynccontextmanager
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from fastapi.responses import RedirectResponse
|
||||
|
||||
from core.database import check_db
|
||||
from core.sidebar import get_sidebar_data
|
||||
from core.database import get_db
|
||||
|
||||
# Import routers
|
||||
from routers import (
|
||||
domains as domains_router,
|
||||
areas as areas_router,
|
||||
projects as projects_router,
|
||||
tasks as tasks_router,
|
||||
notes as notes_router,
|
||||
links as links_router,
|
||||
focus as focus_router,
|
||||
capture as capture_router,
|
||||
contacts as contacts_router,
|
||||
)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
"""Startup/shutdown events."""
|
||||
# Verify database connection
|
||||
try:
|
||||
ok = await check_db()
|
||||
if ok:
|
||||
print("Database connection OK")
|
||||
else:
|
||||
print("WARNING: Database check returned unexpected result")
|
||||
except Exception as e:
|
||||
print(f"WARNING: Database connection failed: {e}")
|
||||
yield
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
title="Life OS",
|
||||
version="1.0.0",
|
||||
lifespan=lifespan,
|
||||
)
|
||||
|
||||
# Static files
|
||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||
|
||||
# Templates
|
||||
templates = Jinja2Templates(directory="templates")
|
||||
|
||||
|
||||
# ---- Template globals and filters ----
|
||||
|
||||
@app.middleware("http")
|
||||
async def add_request_context(request: Request, call_next):
|
||||
"""Make environment available to all templates."""
|
||||
request.state.environment = os.getenv("ENVIRONMENT", "production")
|
||||
response = await call_next(request)
|
||||
return response
|
||||
|
||||
|
||||
# ---- Dashboard ----
|
||||
|
||||
@app.get("/")
|
||||
async def dashboard(request: Request):
|
||||
"""Main dashboard view."""
|
||||
from core.database import async_session_factory
|
||||
async with async_session_factory() as db:
|
||||
sidebar = await get_sidebar_data(db)
|
||||
|
||||
# Today's focus items
|
||||
from sqlalchemy import text
|
||||
result = await db.execute(text("""
|
||||
SELECT df.*, t.title, t.priority, t.status as task_status,
|
||||
t.project_id, p.name as project_name,
|
||||
d.name as domain_name, d.color as domain_color
|
||||
FROM daily_focus df
|
||||
JOIN tasks t ON df.task_id = t.id
|
||||
LEFT JOIN projects p ON t.project_id = p.id
|
||||
LEFT JOIN domains d ON t.domain_id = d.id
|
||||
WHERE df.focus_date = CURRENT_DATE AND df.is_deleted = false
|
||||
ORDER BY df.sort_order, df.created_at
|
||||
"""))
|
||||
focus_items = [dict(r._mapping) for r in result]
|
||||
|
||||
# Overdue tasks
|
||||
result = await db.execute(text("""
|
||||
SELECT t.*, d.name as domain_name, d.color as domain_color,
|
||||
p.name as project_name
|
||||
FROM tasks t
|
||||
LEFT JOIN domains d ON t.domain_id = d.id
|
||||
LEFT JOIN projects p ON t.project_id = p.id
|
||||
WHERE t.is_deleted = false AND t.status NOT IN ('done', 'cancelled')
|
||||
AND t.due_date < CURRENT_DATE
|
||||
ORDER BY t.due_date ASC
|
||||
LIMIT 10
|
||||
"""))
|
||||
overdue_tasks = [dict(r._mapping) for r in result]
|
||||
|
||||
# Upcoming deadlines (next 7 days)
|
||||
result = await db.execute(text("""
|
||||
SELECT t.*, d.name as domain_name, d.color as domain_color,
|
||||
p.name as project_name
|
||||
FROM tasks t
|
||||
LEFT JOIN domains d ON t.domain_id = d.id
|
||||
LEFT JOIN projects p ON t.project_id = p.id
|
||||
WHERE t.is_deleted = false AND t.status NOT IN ('done', 'cancelled')
|
||||
AND t.due_date >= CURRENT_DATE AND t.due_date <= CURRENT_DATE + INTERVAL '7 days'
|
||||
ORDER BY t.due_date ASC
|
||||
LIMIT 10
|
||||
"""))
|
||||
upcoming_tasks = [dict(r._mapping) for r in result]
|
||||
|
||||
# Task stats
|
||||
result = await db.execute(text("""
|
||||
SELECT
|
||||
count(*) FILTER (WHERE status NOT IN ('done', 'cancelled')) as open_tasks,
|
||||
count(*) FILTER (WHERE status = 'done' AND completed_at >= CURRENT_DATE - INTERVAL '7 days') as done_this_week,
|
||||
count(*) FILTER (WHERE status = 'in_progress') as in_progress
|
||||
FROM tasks WHERE is_deleted = false
|
||||
"""))
|
||||
stats = dict(result.first()._mapping)
|
||||
|
||||
return templates.TemplateResponse("dashboard.html", {
|
||||
"request": request,
|
||||
"sidebar": sidebar,
|
||||
"focus_items": focus_items,
|
||||
"overdue_tasks": overdue_tasks,
|
||||
"upcoming_tasks": upcoming_tasks,
|
||||
"stats": stats,
|
||||
"page_title": "Dashboard",
|
||||
"active_nav": "dashboard",
|
||||
})
|
||||
|
||||
|
||||
# ---- Health check ----
|
||||
|
||||
@app.get("/health")
|
||||
async def health():
|
||||
try:
|
||||
ok = await check_db()
|
||||
return {"status": "ok" if ok else "degraded", "database": ok}
|
||||
except Exception as e:
|
||||
return {"status": "error", "database": False, "error": str(e)}
|
||||
|
||||
|
||||
# ---- Include routers ----
|
||||
|
||||
app.include_router(domains_router.router)
|
||||
app.include_router(areas_router.router)
|
||||
app.include_router(projects_router.router)
|
||||
app.include_router(tasks_router.router)
|
||||
app.include_router(notes_router.router)
|
||||
app.include_router(links_router.router)
|
||||
app.include_router(focus_router.router)
|
||||
app.include_router(capture_router.router)
|
||||
app.include_router(contacts_router.router)
|
||||
Reference in New Issue
Block a user