Files
dodo/backend/models.py
T
2026-09-06 09:33:37 +08:00

211 lines
10 KiB
Python

from datetime import UTC, date, datetime
from uuid import UUID
from sqlalchemy import (
JSON,
Boolean,
Date,
DateTime,
Float,
ForeignKey,
Index,
Integer,
String,
Text,
UniqueConstraint,
)
from sqlalchemy.orm import Mapped, mapped_column
from uuid_utils import uuid7
from .db import Base
def new_id() -> UUID:
return UUID(str(uuid7()))
def utcnow() -> datetime:
return datetime.now(UTC)
class AppState(Base):
__tablename__ = "app_state"
key: Mapped[str] = mapped_column(String(64), primary_key=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
class User(Base):
__tablename__ = "users"
id: Mapped[UUID] = mapped_column(primary_key=True, default=new_id)
username: Mapped[str] = mapped_column(String(64), unique=True, index=True)
password_hash: Mapped[str] = mapped_column(Text)
timezone: Mapped[str] = mapped_column(String(64), default="Asia/Shanghai")
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
class Session(Base):
__tablename__ = "sessions"
id: Mapped[UUID] = mapped_column(primary_key=True, default=new_id)
token_hash: Mapped[str] = mapped_column(String(64), unique=True, index=True)
user_id: Mapped[UUID] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True)
expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True))
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
last_seen_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
ip_address: Mapped[str | None] = mapped_column(String(64), nullable=True)
user_agent: Mapped[str | None] = mapped_column(String(500), nullable=True)
class Folder(Base):
__tablename__ = "folders"
id: Mapped[UUID] = mapped_column(primary_key=True, default=new_id)
user_id: Mapped[UUID] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True)
name: Mapped[str] = mapped_column(String(120))
position: Mapped[int] = mapped_column(Integer, default=0)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
deleted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
class TaskList(Base):
__tablename__ = "task_lists"
id: Mapped[UUID] = mapped_column(primary_key=True, default=new_id)
user_id: Mapped[UUID] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True)
folder_id: Mapped[UUID | None] = mapped_column(ForeignKey("folders.id", ondelete="SET NULL"), nullable=True)
name: Mapped[str] = mapped_column(String(120))
is_inbox: Mapped[bool] = mapped_column(Boolean, default=False)
position: Mapped[int] = mapped_column(Integer, default=0)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
deleted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
class Tag(Base):
__tablename__ = "tags"
__table_args__ = (UniqueConstraint("user_id", "name", name="uq_tags_user_id_name"),)
id: Mapped[UUID] = mapped_column(primary_key=True, default=new_id)
user_id: Mapped[UUID] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True)
name: Mapped[str] = mapped_column(String(80))
color: Mapped[str] = mapped_column(String(32), default="#f15a29")
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
class TaskTag(Base):
__tablename__ = "task_tags"
task_id: Mapped[UUID] = mapped_column(ForeignKey("tasks.id", ondelete="CASCADE"), primary_key=True)
tag_id: Mapped[UUID] = mapped_column(ForeignKey("tags.id", ondelete="CASCADE"), primary_key=True)
class Task(Base):
__tablename__ = "tasks"
__table_args__ = (
UniqueConstraint("user_id", "external_id", name="uq_tasks_external_id"),
Index("ix_tasks_user_active_created", "user_id", "deleted_at", "parent_id", "created_at", "id"),
Index("ix_tasks_user_due", "user_id", "deleted_at", "due_at"),
Index("ix_tasks_user_list_active", "user_id", "list_id", "deleted_at", "parent_id", "created_at", "id"),
)
id: Mapped[UUID] = mapped_column(primary_key=True, default=new_id)
user_id: Mapped[UUID] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True)
list_id: Mapped[UUID] = mapped_column(ForeignKey("task_lists.id", ondelete="CASCADE"), index=True)
parent_id: Mapped[UUID | None] = mapped_column(ForeignKey("tasks.id", ondelete="CASCADE"), nullable=True)
title: Mapped[str] = mapped_column(String(500))
description: Mapped[str] = mapped_column(Text, default="")
priority: Mapped[int] = mapped_column(Integer, default=0)
completed: Mapped[bool] = mapped_column(Boolean, default=False, index=True)
due_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
version: Mapped[int] = mapped_column(Integer, default=1)
position: Mapped[int] = mapped_column(Integer, default=0)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow, onupdate=utcnow)
deleted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
external_id: Mapped[str | None] = mapped_column(String(255), nullable=True)
class RecurrenceTemplate(Base):
__tablename__ = "recurrence_templates"
id: Mapped[UUID] = mapped_column(primary_key=True, default=new_id)
user_id: Mapped[UUID] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True)
task_id: Mapped[UUID] = mapped_column(ForeignKey("tasks.id", ondelete="CASCADE"), unique=True)
rrule: Mapped[str] = mapped_column(Text)
starts_at: Mapped[datetime] = mapped_column(DateTime(timezone=True))
ends_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
class RecurrenceException(Base):
__tablename__ = "recurrence_exceptions"
__table_args__ = (UniqueConstraint("template_id", "occurrence_at", name="uq_recurrence_exception"),)
id: Mapped[UUID] = mapped_column(primary_key=True, default=new_id)
template_id: Mapped[UUID] = mapped_column(ForeignKey("recurrence_templates.id", ondelete="CASCADE"), index=True)
occurrence_at: Mapped[datetime] = mapped_column(DateTime(timezone=True))
title: Mapped[str | None] = mapped_column(String(500), nullable=True)
due_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
completed: Mapped[bool] = mapped_column(Boolean, default=False)
deleted: Mapped[bool] = mapped_column(Boolean, default=False)
class Habit(Base):
__tablename__ = "habits"
id: Mapped[UUID] = mapped_column(primary_key=True, default=new_id)
user_id: Mapped[UUID] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True)
name: Mapped[str] = mapped_column(String(200))
kind: Mapped[str] = mapped_column(String(16), default="boolean")
target: Mapped[float] = mapped_column(Float, default=1)
max_value: Mapped[float | None] = mapped_column(Float, nullable=True)
schedule_type: Mapped[str] = mapped_column(String(16), default="daily")
weekdays: Mapped[str | None] = mapped_column(String(32), nullable=True)
month_days: Mapped[str | None] = mapped_column(String(100), nullable=True)
interval_days: Mapped[int | None] = mapped_column(Integer, nullable=True)
start_date: Mapped[date] = mapped_column(Date)
archived_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
class HabitLog(Base):
__tablename__ = "habit_logs"
__table_args__ = (UniqueConstraint("habit_id", "day", name="uq_habit_log_day"),)
id: Mapped[UUID] = mapped_column(primary_key=True, default=new_id)
habit_id: Mapped[UUID] = mapped_column(ForeignKey("habits.id", ondelete="CASCADE"), index=True)
day: Mapped[date] = mapped_column(Date)
value: Mapped[float] = mapped_column(Float)
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
class HabitPause(Base):
__tablename__ = "habit_pauses"
id: Mapped[UUID] = mapped_column(primary_key=True, default=new_id)
habit_id: Mapped[UUID] = mapped_column(ForeignKey("habits.id", ondelete="CASCADE"), index=True)
start_date: Mapped[date] = mapped_column(Date)
end_date: Mapped[date] = mapped_column(Date)
class Attachment(Base):
__tablename__ = "attachments"
id: Mapped[UUID] = mapped_column(primary_key=True, default=new_id)
user_id: Mapped[UUID] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True)
task_id: Mapped[UUID] = mapped_column(ForeignKey("tasks.id", ondelete="CASCADE"), index=True)
filename: Mapped[str] = mapped_column(String(255))
storage_name: Mapped[str] = mapped_column(String(255), unique=True)
mime_type: Mapped[str] = mapped_column(String(127))
size: Mapped[int] = mapped_column(Integer)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
class CalendarSubscription(Base):
__tablename__ = "calendar_subscriptions"
id: Mapped[UUID] = mapped_column(primary_key=True, default=new_id)
user_id: Mapped[UUID] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True)
name: Mapped[str] = mapped_column(String(120))
url: Mapped[str] = mapped_column(Text)
color: Mapped[str] = mapped_column(String(32), default="#f15a29")
enabled: Mapped[bool] = mapped_column(Boolean, default=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
class AuditLog(Base):
__tablename__ = "audit_logs"
id: Mapped[UUID] = mapped_column(primary_key=True, default=new_id)
user_id: Mapped[UUID] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True)
action: Mapped[str] = mapped_column(String(32))
entity_type: Mapped[str] = mapped_column(String(32))
entity_id: Mapped[UUID | None] = mapped_column(nullable=True)
details: Mapped[dict] = mapped_column(JSON, default=dict)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)