| """ |
| User and authentication models. |
| """ |
| import uuid |
| from datetime import datetime |
| from enum import Enum |
|
|
| from sqlalchemy import Boolean, DateTime, String, Text, func |
| from sqlalchemy.dialects.postgresql import JSONB, UUID |
| from sqlalchemy.orm import Mapped, mapped_column, relationship |
|
|
| from app.core.database import Base |
|
|
|
|
| class AuthProvider(str, Enum): |
| LOCAL = "local" |
| GOOGLE = "google" |
| GITHUB = "github" |
|
|
|
|
| class UserRole(str, Enum): |
| CANDIDATE = "candidate" |
| EMPLOYER = "employer" |
| ADMIN = "admin" |
|
|
|
|
| class User(Base): |
| __tablename__ = "users" |
|
|
| id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) |
| email: Mapped[str] = mapped_column(String(320), unique=True, index=True, nullable=False) |
| hashed_password: Mapped[str | None] = mapped_column(String(256), nullable=True) |
| full_name: Mapped[str] = mapped_column(String(256), nullable=False) |
| avatar_url: Mapped[str | None] = mapped_column(String(512), nullable=True) |
| role: Mapped[str] = mapped_column(String(20), default=UserRole.CANDIDATE, nullable=False) |
| auth_provider: Mapped[str] = mapped_column(String(20), default=AuthProvider.LOCAL, nullable=False) |
| auth_provider_id: Mapped[str | None] = mapped_column(String(256), nullable=True) |
| is_active: Mapped[bool] = mapped_column(Boolean, default=True) |
| is_verified: Mapped[bool] = mapped_column(Boolean, default=False) |
| is_superuser: Mapped[bool] = mapped_column(Boolean, default=False) |
| llm_preferences: Mapped[dict | None] = mapped_column(JSONB, nullable=True) |
| created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) |
| updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now()) |
| last_login_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) |
|
|
| profile: Mapped["Profile"] = relationship(back_populates="user", uselist=False) |
| applications: Mapped[list["Application"]] = relationship(back_populates="user") |
| resume_versions: Mapped[list["ResumeVersion"]] = relationship(back_populates="user") |
| saved_jobs: Mapped[list["SavedJob"]] = relationship(back_populates="user") |
| api_keys: Mapped[list["UserAPIKey"]] = relationship(back_populates="user") |
|
|
|
|
| class UserAPIKey(Base): |
| """Encrypted API keys for LLM providers. key_prefix/key_last4 stored for display without decryption.""" |
| __tablename__ = "user_api_keys" |
|
|
| id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) |
| user_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), nullable=False, index=True) |
| provider: Mapped[str] = mapped_column(String(50), nullable=False) |
| encrypted_key: Mapped[str] = mapped_column(Text, nullable=False) |
| key_prefix: Mapped[str | None] = mapped_column(String(8), nullable=True) |
| key_last4: Mapped[str | None] = mapped_column(String(8), nullable=True) |
| label: Mapped[str | None] = mapped_column(String(100), nullable=True) |
| is_active: Mapped[bool] = mapped_column(Boolean, default=True) |
| created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) |
|
|
| user: Mapped["User"] = relationship(back_populates="api_keys") |
|
|
|
|
| class Profile(Base): |
| __tablename__ = "profiles" |
|
|
| id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) |
| user_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), unique=True, nullable=False) |
| phone: Mapped[str | None] = mapped_column(String(30), nullable=True) |
| location: Mapped[str | None] = mapped_column(String(200), nullable=True) |
| linkedin_url: Mapped[str | None] = mapped_column(String(512), nullable=True) |
| github_url: Mapped[str | None] = mapped_column(String(512), nullable=True) |
| portfolio_url: Mapped[str | None] = mapped_column(String(512), nullable=True) |
| website_url: Mapped[str | None] = mapped_column(String(512), nullable=True) |
| headline: Mapped[str | None] = mapped_column(String(256), nullable=True) |
| summary: Mapped[str | None] = mapped_column(Text, nullable=True) |
| experience: Mapped[list | None] = mapped_column(JSONB, nullable=True) |
| education: Mapped[list | None] = mapped_column(JSONB, nullable=True) |
| skills: Mapped[list | None] = mapped_column(JSONB, nullable=True) |
| certifications: Mapped[list | None] = mapped_column(JSONB, nullable=True) |
| projects: Mapped[list | None] = mapped_column(JSONB, nullable=True) |
| languages: Mapped[list | None] = mapped_column(JSONB, nullable=True) |
| desired_roles: Mapped[list | None] = mapped_column(JSONB, nullable=True) |
| desired_locations: Mapped[list | None] = mapped_column(JSONB, nullable=True) |
| remote_preference: Mapped[str | None] = mapped_column(String(20), nullable=True) |
| salary_min: Mapped[int | None] = mapped_column(nullable=True) |
| salary_max: Mapped[int | None] = mapped_column(nullable=True) |
| salary_currency: Mapped[str | None] = mapped_column(String(3), nullable=True) |
| visa_required: Mapped[bool | None] = mapped_column(Boolean, nullable=True) |
| open_to_work: Mapped[bool] = mapped_column(Boolean, default=False) |
| created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) |
| updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now()) |
|
|
| user: Mapped["User"] = relationship(back_populates="profile") |
|
|