"""events 1차 컨테이너 ORM (개인 운영 로그 / 일정 / 할 일 / 회고) PR-1 (migrations 239~247) 의 본체. kind enum 으로 task/calendar_event/activity_log 세 변형을 통합 관리. memo_document_id 는 메모 link (optional). """ from datetime import datetime from typing import Any from sqlalchemy import ( BigInteger, Boolean, DateTime, ForeignKey, SmallInteger, String, Text, ) from sqlalchemy.dialects.postgresql import ENUM as PgEnum from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.orm import Mapped, mapped_column from core.database import Base # Postgres enum 재선언 X (create_type=False) — migration 239~243 이 권위. EventKindEnum = PgEnum( "task", "calendar_event", "activity_log", name="event_kind", create_type=False, ) EventStatusEnum = PgEnum( "inbox", "next", "scheduled", "in_progress", "done", "cancelled", "deferred", name="event_status", create_type=False, ) EventSourceEnum = PgEnum( "manual", "memo", "email", "chat", "webhook", "git_commit", "claude_code", name="event_source", create_type=False, ) EventActorEnum = PgEnum( "manual", "eid", "email_ingest", "system", name="event_actor", create_type=False, ) class Event(Base): __tablename__ = "events" id: Mapped[int] = mapped_column(BigInteger, primary_key=True) title: Mapped[str] = mapped_column(Text, nullable=False) description: Mapped[str | None] = mapped_column(Text) kind: Mapped[str] = mapped_column(EventKindEnum, nullable=False) status: Mapped[str] = mapped_column(EventStatusEnum, nullable=False, default="inbox") # 시간 필드 — kind 별 의미가 다름 (CHECK 제약은 migration 244) due_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) start_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) end_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) started_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) ended_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) all_day: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) timezone: Mapped[str | None] = mapped_column(Text) # lifecycle defer_until: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) cancelled_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) priority: Mapped[int | None] = mapped_column(SmallInteger) project_tag: Mapped[str | None] = mapped_column(String(64)) tags: Mapped[list[Any]] = mapped_column(JSONB, nullable=False, default=list) # 출처 / 외부 식별자 source: Mapped[str] = mapped_column(EventSourceEnum, nullable=False, default="manual") source_ref: Mapped[str | None] = mapped_column(Text) raw_metadata: Mapped[dict[str, Any]] = mapped_column(JSONB, nullable=False, default=dict) # 메모 link (optional, ON DELETE SET NULL) memo_document_id: Mapped[int | None] = mapped_column( BigInteger, ForeignKey("documents.id", ondelete="SET NULL") ) # 인증 / actor user_id: Mapped[int] = mapped_column( BigInteger, ForeignKey("users.id"), nullable=False ) created_by: Mapped[str] = mapped_column(EventActorEnum, nullable=False, default="manual") created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=datetime.now, nullable=False ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=datetime.now, onupdate=datetime.now, nullable=False )