feat: recurring tasks, habits, attachments, import/export, audit and security
This commit is contained in:
+99
-2
@@ -1,7 +1,18 @@
|
||||
from datetime import UTC, datetime
|
||||
from datetime import UTC, date, datetime
|
||||
from uuid import UUID
|
||||
|
||||
from sqlalchemy import Boolean, DateTime, ForeignKey, Integer, String, Text, UniqueConstraint
|
||||
from sqlalchemy import (
|
||||
JSON,
|
||||
Boolean,
|
||||
Date,
|
||||
DateTime,
|
||||
Float,
|
||||
ForeignKey,
|
||||
Integer,
|
||||
String,
|
||||
Text,
|
||||
UniqueConstraint,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from uuid_utils import uuid7
|
||||
|
||||
@@ -38,6 +49,9 @@ class Session(Base):
|
||||
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):
|
||||
@@ -80,6 +94,7 @@ class TaskTag(Base):
|
||||
|
||||
class Task(Base):
|
||||
__tablename__ = "tasks"
|
||||
__table_args__ = (UniqueConstraint("user_id", "external_id", name="uq_tasks_external_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)
|
||||
@@ -94,3 +109,85 @@ class Task(Base):
|
||||
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 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)
|
||||
|
||||
Reference in New Issue
Block a user