Files
impactflow_discovery/app/main.py
T
Joel Salmon 33674f92f4 Complete Phase 1: goals, cookie auth, profile editing
Close the remaining Phase 1 DoD gaps and reconcile the browser flow with
the auth layer.

Goals (5 -> 7 prompts):
- Add near-term (6-12mo) and long-term (3-5yr) goal prompts; collect raw
  text on the conversation and store AI-articulated goal summaries on the
  profile. Extractor articulates the person's own stated goals (mirror,
  not compass) and never fabricates. Alembic 003 adds the four columns.

Cookie-based browser sessions (fixes frontend<->auth desync):
- OAuth callback now sets httpOnly session cookies and redirects into the
  app instead of returning JSON. get_current_user gains a cookie fallback
  (X-API-Key -> Bearer -> cookie). refresh/logout read the refresh cookie
  and set/clear cookies. New shared auth.js (authedFetch) sends cookies and
  silently refreshes on 401. Static pages drop the bogus user_id and call
  the correct /me endpoints.

Profile editing (read/edit/affirm):
- PATCH /discovery/profile/me edits the prose (Ikigai summaries, overlap
  narrative, goals); owner-scoped, partial update, 409 when locked. Edit
  mode in profile.html with Save/Cancel.

Also: bump default model to claude-sonnet-4-6, align ports to 8011
(OAuth redirect, CORS), add COOKIE_SECURE/POST_LOGIN_REDIRECT config, and
refresh the README to match the shipped behavior.

Tests: 33 passing (added cookie-auth, profile-edit, goal-extraction cases;
factored a shared app_client fixture into conftest.py).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 18:26:08 -05:00

78 lines
2.5 KiB
Python

"""FastAPI entrypoint for the ImpactFlow self-discovery module."""
import os
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import RedirectResponse
from fastapi.staticfiles import StaticFiles
from starlette.middleware.sessions import SessionMiddleware
from app import models # noqa: F401 - register ORM models with Base.metadata
from app.auth import ensure_api_key_admin
from app.database import AsyncSessionLocal, Base, engine
from app.routers import activity, auth as auth_router, discovery
from app.routers.activity import prune_old_activity
from app.tracking import ActivityTrackingMiddleware
# Path to the static directory, resolved relative to this file so it works
# regardless of the current working directory.
STATIC_DIR = os.path.join(os.path.dirname(__file__), "static")
@asynccontextmanager
async def lifespan(app: FastAPI):
# Alembic owns schema migrations in Docker; this create_all is an
# idempotent safety net so the app also runs cleanly in local/dev where
# migrations may not have been applied.
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
async with AsyncSessionLocal() as db:
await ensure_api_key_admin(db)
await prune_old_activity(db)
yield
app = FastAPI(title="ImpactFlow Self-Discovery", lifespan=lifespan)
# add_middleware wraps the previous app, so the LAST call becomes the
# OUTERMOST middleware. Register inner-to-outer:
# tracking (innermost — sees request.state.user set by deps) ->
# session (needs to wrap routes for the OAuth state cookie) ->
# CORS (outermost — preflights must short-circuit before anything else).
app.add_middleware(ActivityTrackingMiddleware)
_session_secret = os.getenv("JWT_SECRET") or "dev-session-secret-change-me"
app.add_middleware(SessionMiddleware, secret_key=_session_secret)
_cors_origins = [
o.strip()
for o in os.getenv(
"CORS_ALLOWED_ORIGINS", "http://localhost:8011"
).split(",")
if o.strip()
]
app.add_middleware(
CORSMiddleware,
allow_origins=_cors_origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(auth_router.router)
app.include_router(activity.router)
app.include_router(discovery.router)
app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
@app.get("/health")
async def health():
return {"status": "ok"}
@app.get("/")
async def root():
return RedirectResponse(url="/static/discovery.html")