feat: PocketVeto v1.0.0 — initial public release

Self-hosted US Congress monitoring platform with AI policy briefs,
bill/member/topic follows, ntfy + RSS + email notifications,
alignment scoring, collections, and draft-letter generator.

Authored by: Jack Levy
This commit is contained in:
Jack Levy
2026-03-15 01:35:01 -04:00
commit 4c86a5b9ca
150 changed files with 19859 additions and 0 deletions

89
backend/app/api/notes.py Normal file
View File

@@ -0,0 +1,89 @@
"""
Bill Notes API — private per-user notes on individual bills.
One note per (user, bill). PUT upserts, DELETE removes.
"""
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.dependencies import get_current_user
from app.database import get_db
from app.models.bill import Bill
from app.models.note import BillNote
from app.models.user import User
from app.schemas.schemas import BillNoteSchema, BillNoteUpsert
router = APIRouter()
@router.get("/{bill_id}", response_model=BillNoteSchema)
async def get_note(
bill_id: str,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
result = await db.execute(
select(BillNote).where(
BillNote.user_id == current_user.id,
BillNote.bill_id == bill_id,
)
)
note = result.scalar_one_or_none()
if not note:
raise HTTPException(status_code=404, detail="No note for this bill")
return note
@router.put("/{bill_id}", response_model=BillNoteSchema)
async def upsert_note(
bill_id: str,
body: BillNoteUpsert,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
bill = await db.get(Bill, bill_id)
if not bill:
raise HTTPException(status_code=404, detail="Bill not found")
result = await db.execute(
select(BillNote).where(
BillNote.user_id == current_user.id,
BillNote.bill_id == bill_id,
)
)
note = result.scalar_one_or_none()
if note:
note.content = body.content
note.pinned = body.pinned
else:
note = BillNote(
user_id=current_user.id,
bill_id=bill_id,
content=body.content,
pinned=body.pinned,
)
db.add(note)
await db.commit()
await db.refresh(note)
return note
@router.delete("/{bill_id}", status_code=204)
async def delete_note(
bill_id: str,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
result = await db.execute(
select(BillNote).where(
BillNote.user_id == current_user.id,
BillNote.bill_id == bill_id,
)
)
note = result.scalar_one_or_none()
if not note:
raise HTTPException(status_code=404, detail="No note for this bill")
await db.delete(note)
await db.commit()