from datetime import datetime from enum import Enum from typing import Optional from sqlalchemy import ( Boolean, Column, DateTime, Enum as SqlEnum, ForeignKey, Integer, String, Text, UniqueConstraint, ) from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.orm import relationship, Mapped, mapped_column from .db import Base class UserRole(str, Enum): USER = "user" ADMIN = "admin" class User(Base): __tablename__ = "users" id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) email: Mapped[str] = mapped_column(String(255), unique=True, index=True, nullable=False) password_hash: Mapped[str] = mapped_column(String(255), nullable=False) role: Mapped[UserRole] = mapped_column(SqlEnum(UserRole), default=UserRole.USER, nullable=False) fcm_token: Mapped[Optional[str]] = mapped_column(String(512), nullable=True) is_active: Mapped[bool] = mapped_column(Boolean, default=True) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) favorites = relationship("Favorite", back_populates="user", cascade="all,delete") class Cemetery(Base): __tablename__ = "cemeteries" id: Mapped[int] = mapped_column(Integer, primary_key=True) name: Mapped[str] = mapped_column(String(255), nullable=False) description: Mapped[Optional[str]] = mapped_column(Text, nullable=True) region: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) center_lat: Mapped[float] = mapped_column() center_lon: Mapped[float] = mapped_column() zoom: Mapped[int] = mapped_column(Integer, default=15) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) graves = relationship("Grave", back_populates="cemetery", cascade="all,delete") class GraveStatus(str, Enum): PENDING = "pending" # yellow APPROVED = "approved" # green REJECTED = "rejected" class Grave(Base): __tablename__ = "graves" id: Mapped[int] = mapped_column(Integer, primary_key=True) cemetery_id: Mapped[int] = mapped_column(ForeignKey("cemeteries.id", ondelete="CASCADE"), index=True) lat: Mapped[float] = mapped_column() lon: Mapped[float] = mapped_column() full_name: Mapped[str] = mapped_column(String(255), index=True) birth_date: Mapped[Optional[str]] = mapped_column(String(32), nullable=True) death_date: Mapped[Optional[str]] = mapped_column(String(32), nullable=True) temp_photo_path: Mapped[Optional[str]] = mapped_column(String(512), nullable=True) photo_url: Mapped[Optional[str]] = mapped_column(String(512), nullable=True) status: Mapped[GraveStatus] = mapped_column(SqlEnum(GraveStatus), default=GraveStatus.PENDING, index=True) created_by: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) cemetery = relationship("Cemetery", back_populates="graves") histories = relationship("GraveHistory", back_populates="grave", cascade="all,delete") favorites = relationship("Favorite", back_populates="grave", cascade="all,delete") class GraveHistory(Base): __tablename__ = "grave_histories" id: Mapped[int] = mapped_column(Integer, primary_key=True) grave_id: Mapped[int] = mapped_column(ForeignKey("graves.id", ondelete="CASCADE"), index=True) action: Mapped[str] = mapped_column(String(64)) # created, updated, approved, rejected changes: Mapped[Optional[dict]] = mapped_column(JSONB, nullable=True) actor_id: Mapped[Optional[int]] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) grave = relationship("Grave", back_populates="histories") class Favorite(Base): __tablename__ = "favorites" __table_args__ = (UniqueConstraint("user_id", "grave_id", name="uq_user_grave"),) id: Mapped[int] = mapped_column(Integer, primary_key=True) user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True) grave_id: Mapped[int] = mapped_column(ForeignKey("graves.id", ondelete="CASCADE"), index=True) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) user = relationship("User", back_populates="favorites") grave = relationship("Grave", back_populates="favorites") class AppLog(Base): __tablename__ = "app_logs" id: Mapped[int] = mapped_column(Integer, primary_key=True) level: Mapped[str] = mapped_column(String(32)) # info, warning, error message: Mapped[str] = mapped_column(Text) context: Mapped[Optional[dict]] = mapped_column(JSONB, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)