best / backend /app /models /user.py
anky2002's picture
fix(P0): Add key_prefix and key_last4 to UserAPIKey model, settings route uses them"
9dea83b verified
Raw
History Blame Contribute Delete
5.51 kB
"""
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) # e.g., "sk-p"
key_last4: Mapped[str | None] = mapped_column(String(8), nullable=True) # e.g., "ab4f"
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")