Files
FabledScribe/alembic/versions/0012_add_invitation_tokens.py
bvandeusen e02b681e91 Add invitation system, table of contents, actionable dashboard, and settings improvements
Invitation system (Phase 5.7):
- invitation_tokens table (migration 0012) with SHA256-hashed tokens, 7-day expiry
- Admin CRUD endpoints: POST/GET/DELETE /api/admin/invitations
- Branded invitation email with registration link
- /register-invite frontend view with token validation and account creation
- Admin UI: invite form + pending invitations table with revoke
- Configurable base URL setting for email links (replaces hardcoded Config.BASE_URL)

Table of contents + markdown improvements (Phase 5.8):
- TableOfContents component: sticky sidebar, heading parsing, smooth-scroll
- Custom marked renderer adds id attributes to headings for anchor links
- stripFirstLineTags() prevents leading #tags from rendering as headings
- NoteViewerView/TaskViewerView flex layout with TOC sidebar (hidden ≤1200px)
- Model catalog refresh: added llama3.2/3.3, gemma3, qwen3, phi4, deepseek-r1,
  qwen2.5-coder, dolphin3; added Reasoning category; removed discontinued models
- Settings model section split into Installed/Available tabs

Actionable dashboard (Phase 5.9):
- due_before/due_after query params on /api/tasks (exclusive < / inclusive >=)
- HomeView rewritten: overdue (red accent), due today, in progress, chats, notes
- 5 parallel API calls via Promise.allSettled
- Client-side done filtering and in-progress deduplication
- Task sections hidden when empty; status toggle removes done tasks

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 08:46:51 -05:00

27 lines
818 B
Python

"""Add invitation_tokens table."""
from alembic import op
revision = "0012"
down_revision = "0011"
def upgrade() -> None:
op.execute("""
CREATE TABLE invitation_tokens (
id SERIAL PRIMARY KEY,
email TEXT NOT NULL,
token_hash TEXT NOT NULL UNIQUE,
invited_by INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
expires_at TIMESTAMPTZ NOT NULL,
used BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)
""")
op.execute("CREATE INDEX ix_invitation_tokens_token_hash ON invitation_tokens (token_hash)")
op.execute("CREATE INDEX ix_invitation_tokens_email ON invitation_tokens (email)")
def downgrade() -> None:
op.execute("DROP TABLE IF EXISTS invitation_tokens")