- Backend: users table + admin seed (004), /api/auth endpoints, JWT auth dep gating benchmarks + research routes - Frontend: AuthContext, LoginPage, ProtectedRoute, subpath-aware via VITE_BASE / import.meta.env.BASE_URL so same build works at /opt/ - deploy/: Dockerfile.prod, docker-compose.prod.yml, Apache vhost fragment template, and idempotent deploy.sh (port scan, rsync, env generation, Apache Include + reload) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
116 lines
4.3 KiB
Python
116 lines
4.3 KiB
Python
import uuid
|
|
from datetime import datetime, timezone
|
|
|
|
from sqlalchemy import (
|
|
Boolean,
|
|
DateTime,
|
|
Float,
|
|
ForeignKey,
|
|
Integer,
|
|
String,
|
|
UniqueConstraint,
|
|
)
|
|
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
|
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
|
|
|
|
|
|
class Base(DeclarativeBase):
|
|
pass
|
|
|
|
|
|
class Location(Base):
|
|
__tablename__ = "locations"
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
city: Mapped[str] = mapped_column(String(255))
|
|
country: Mapped[str] = mapped_column(String(100), default="US")
|
|
normalized_name: Mapped[str] = mapped_column(String(255), unique=True)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
|
|
)
|
|
|
|
benchmarks: Mapped[list["Benchmark"]] = relationship(back_populates="location")
|
|
research_sessions: Mapped[list["ResearchSession"]] = relationship(
|
|
back_populates="location"
|
|
)
|
|
|
|
|
|
class Role(Base):
|
|
__tablename__ = "roles"
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
title: Mapped[str] = mapped_column(String(255))
|
|
normalized_title: Mapped[str] = mapped_column(String(255), unique=True)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
|
|
)
|
|
|
|
benchmarks: Mapped[list["Benchmark"]] = relationship(back_populates="role")
|
|
research_sessions: Mapped[list["ResearchSession"]] = relationship(
|
|
back_populates="role"
|
|
)
|
|
|
|
|
|
class Benchmark(Base):
|
|
__tablename__ = "benchmarks"
|
|
__table_args__ = (
|
|
UniqueConstraint("role_id", "location_id", "level", name="uq_benchmark"),
|
|
)
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
role_id: Mapped[int] = mapped_column(ForeignKey("roles.id"))
|
|
location_id: Mapped[int] = mapped_column(ForeignKey("locations.id"))
|
|
level: Mapped[str] = mapped_column(String(20))
|
|
salary: Mapped[int] = mapped_column(Integer)
|
|
source: Mapped[str] = mapped_column(String(50), default="seed")
|
|
confidence_score: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
validated: Mapped[bool] = mapped_column(Boolean, default=False)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
|
|
)
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True),
|
|
default=lambda: datetime.now(timezone.utc),
|
|
onupdate=lambda: datetime.now(timezone.utc),
|
|
)
|
|
|
|
role: Mapped["Role"] = relationship(back_populates="benchmarks")
|
|
location: Mapped["Location"] = relationship(back_populates="benchmarks")
|
|
|
|
|
|
class User(Base):
|
|
__tablename__ = "users"
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
email: Mapped[str] = mapped_column(String(255), unique=True)
|
|
password_hash: Mapped[str] = mapped_column(String(255))
|
|
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
|
|
)
|
|
|
|
|
|
class ResearchSession(Base):
|
|
__tablename__ = "research_sessions"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
|
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
|
)
|
|
role_id: Mapped[int] = mapped_column(ForeignKey("roles.id"))
|
|
location_id: Mapped[int] = mapped_column(ForeignKey("locations.id"))
|
|
status: Mapped[str] = mapped_column(String(30), default="searching")
|
|
serper_results: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
|
|
firecrawl_results: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
|
|
cohere_ranked: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
|
|
claude_analysis: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
|
|
proposed_benchmarks: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
|
|
error_message: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
|
|
)
|
|
completed_at: Mapped[datetime | None] = mapped_column(
|
|
DateTime(timezone=True), nullable=True
|
|
)
|
|
|
|
role: Mapped["Role"] = relationship(back_populates="research_sessions")
|
|
location: Mapped["Location"] = relationship(back_populates="research_sessions")
|