Phase 3 completion — Personal Workflow feature set is now complete.
Collections / Watchlists:
- New tables: collections (UUID share_token, slug, public/private) and
collection_bills (unique bill-per-collection constraint)
- Full CRUD API at /api/collections with bill add/remove endpoints
- Public share endpoint /api/collections/share/{token} (no auth)
- /collections list page with inline create form and delete
- /collections/[id] detail page: inline rename, public toggle,
copy-share-link, bill search/add/remove
- CollectionPicker bookmark-icon popover on bill detail pages
- Collections nav link in sidebar (auth-required)
Shareable Brief Links:
- share_token UUID column on bill_briefs (backfilled on migration)
- Unified public share router at /api/share (brief + collection)
- /share/brief/[token] — minimal layout, full AIBriefCard, CTAs
- /share/collection/[token] — minimal layout, bill list, CTA
- Share2 button in BriefPanel header row, "Link copied!" flash
AuthGuard: /collections → AUTH_REQUIRED; /share prefix → NO_SHELL_PATHS
Authored-By: Jack Levy
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
35 lines
1.5 KiB
Python
35 lines
1.5 KiB
Python
from sqlalchemy import Column, Integer, String, Text, ForeignKey, DateTime, Index
|
|
from sqlalchemy.dialects import postgresql
|
|
from sqlalchemy.dialects.postgresql import JSONB
|
|
from sqlalchemy.orm import relationship
|
|
from sqlalchemy.sql import func
|
|
|
|
from app.database import Base
|
|
|
|
|
|
class BillBrief(Base):
|
|
__tablename__ = "bill_briefs"
|
|
|
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
bill_id = Column(String, ForeignKey("bills.bill_id", ondelete="CASCADE"), nullable=False)
|
|
document_id = Column(Integer, ForeignKey("bill_documents.id", ondelete="SET NULL"), nullable=True)
|
|
brief_type = Column(String(20), nullable=False, server_default="full") # full | amendment
|
|
summary = Column(Text)
|
|
key_points = Column(JSONB) # list[{text, citation, quote}]
|
|
risks = Column(JSONB) # list[{text, citation, quote}]
|
|
deadlines = Column(JSONB) # list[{date: str, description: str}]
|
|
topic_tags = Column(JSONB) # list[str]
|
|
llm_provider = Column(String(50))
|
|
llm_model = Column(String(100))
|
|
govinfo_url = Column(String, nullable=True)
|
|
share_token = Column(postgresql.UUID(as_uuid=False), nullable=True, server_default=func.gen_random_uuid())
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
|
|
bill = relationship("Bill", back_populates="briefs")
|
|
document = relationship("BillDocument", back_populates="briefs")
|
|
|
|
__table_args__ = (
|
|
Index("ix_bill_briefs_bill_id", "bill_id"),
|
|
Index("ix_bill_briefs_topic_tags", "topic_tags", postgresql_using="gin"),
|
|
)
|