from datetime import date, datetime, timezone import uuid from sqlalchemy import Boolean, Date, DateTime, ForeignKey, Integer, String, Text, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column, relationship from app.database import Base class User(Base): __tablename__ = "users" id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4())) email: Mapped[str] = mapped_column(String(255), unique=True, index=True, nullable=False) password_hash: Mapped[str] = mapped_column(String(255), nullable=False) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False ) weekly_target_minutes: Mapped[int] = mapped_column(Integer, default=1500, nullable=False) role: Mapped[str] = mapped_column(String(32), default="user", nullable=False) is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) preferred_home_view: Mapped[str] = mapped_column(String(16), default="week", nullable=False) theme_preference: Mapped[str] = mapped_column(String(16), default="auto", nullable=False) preferred_month_view_mode: Mapped[str] = mapped_column(String(16), default="flat", nullable=False) entry_mode: Mapped[str] = mapped_column(String(16), default="manual", nullable=False) working_days_csv: Mapped[str] = mapped_column(String(32), default="0,1,2,3,4", nullable=False) count_vacation_as_worktime: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) count_holiday_as_worktime: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) count_sick_as_worktime: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) automatic_break_rules_enabled: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) default_break_minutes: Mapped[int] = mapped_column(Integer, default=0, nullable=False) overtime_start_date: Mapped[date | None] = mapped_column(Date, default=None) overtime_expiry_days: Mapped[int | None] = mapped_column(Integer, default=None) expire_negative_overtime: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) vacation_days_total: Mapped[int] = mapped_column(Integer, default=0, nullable=False) vacation_show_in_header: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) workhours_counter_enabled: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) workhours_counter_show_in_header: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) workhours_counter_start_date: Mapped[date | None] = mapped_column(Date, default=None) workhours_counter_end_date: Mapped[date | None] = mapped_column(Date, default=None) workhours_counter_manual_offset_minutes: Mapped[int] = mapped_column(Integer, default=0, nullable=False) workhours_counter_target_minutes: Mapped[int | None] = mapped_column(Integer, default=None) workhours_counter_target_email_enabled: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) workhours_counter_warning_last_sent_on: Mapped[date | None] = mapped_column(Date, default=None) workhours_counter_warning_last_sent_key: Mapped[str | None] = mapped_column(String(120), default=None) federal_state: Mapped[str | None] = mapped_column(String(8), default=None) email_verified: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) email_verification_token_hash: Mapped[str | None] = mapped_column(String(128), default=None, index=True) email_verification_expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), default=None) email_verification_sent_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), default=None) mfa_method: Mapped[str] = mapped_column(String(16), default="none", nullable=False) mfa_totp_secret_encrypted: Mapped[str | None] = mapped_column(Text, default=None) mfa_email_code_hash: Mapped[str | None] = mapped_column(String(255), default=None) mfa_email_code_expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), default=None) mfa_email_code_sent_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), default=None) time_entries: Mapped[list["TimeEntry"]] = relationship( "TimeEntry", back_populates="user", cascade="all, delete-orphan" ) weekly_target_rules: Mapped[list["WeeklyTargetRule"]] = relationship( "WeeklyTargetRule", back_populates="user", cascade="all, delete-orphan", order_by="WeeklyTargetRule.effective_from", ) vacation_periods: Mapped[list["VacationPeriod"]] = relationship( "VacationPeriod", back_populates="user", cascade="all, delete-orphan", order_by="VacationPeriod.start_date", ) special_day_statuses: Mapped[list["SpecialDayStatus"]] = relationship( "SpecialDayStatus", back_populates="user", cascade="all, delete-orphan", order_by="SpecialDayStatus.date", ) overtime_adjustments: Mapped[list["OvertimeAdjustment"]] = relationship( "OvertimeAdjustment", back_populates="user", cascade="all, delete-orphan", order_by="OvertimeAdjustment.date", ) auto_entry_skips: Mapped[list["AutoEntrySkip"]] = relationship( "AutoEntrySkip", back_populates="user", cascade="all, delete-orphan", order_by="AutoEntrySkip.date", ) password_reset_tokens: Mapped[list["PasswordResetToken"]] = relationship( "PasswordResetToken", back_populates="user", cascade="all, delete-orphan", order_by="PasswordResetToken.created_at.desc()", ) import_previews: Mapped[list["ImportPreview"]] = relationship( "ImportPreview", back_populates="user", cascade="all, delete-orphan", order_by="ImportPreview.created_at.desc()", ) support_tickets: Mapped[list["SupportTicket"]] = relationship( "SupportTicket", back_populates="user", cascade="all, delete-orphan", order_by="SupportTicket.created_at.desc()", foreign_keys="SupportTicket.user_id", ) class TimeEntry(Base): __tablename__ = "time_entries" __table_args__ = (UniqueConstraint("user_id", "date", name="uq_user_date"),) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4())) user_id: Mapped[str] = mapped_column(String(36), ForeignKey("users.id", ondelete="CASCADE"), index=True) date: Mapped[date] = mapped_column(Date, index=True, nullable=False) start_minutes: Mapped[int] = mapped_column(Integer, nullable=False) end_minutes: Mapped[int] = mapped_column(Integer, nullable=False) break_minutes: Mapped[int] = mapped_column(Integer, default=0, nullable=False) break_rule_mode: Mapped[str] = mapped_column(String(16), default="manual", nullable=False) notes: Mapped[str | None] = mapped_column(Text, default=None) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc) ) user: Mapped[User] = relationship("User", back_populates="time_entries") class LoginAttempt(Base): __tablename__ = "login_attempts" id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4())) email: Mapped[str] = mapped_column(String(255), index=True, nullable=False) ip_address: Mapped[str] = mapped_column(String(64), index=True, nullable=False) success: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False, index=True ) class WeeklyTargetRule(Base): __tablename__ = "weekly_target_rules" __table_args__ = (UniqueConstraint("user_id", "effective_from", name="uq_user_effective_from"),) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4())) user_id: Mapped[str] = mapped_column(String(36), ForeignKey("users.id", ondelete="CASCADE"), index=True) effective_from: Mapped[date] = mapped_column(Date, index=True, nullable=False) weekly_target_minutes: Mapped[int] = mapped_column(Integer, nullable=False) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False ) user: Mapped[User] = relationship("User", back_populates="weekly_target_rules") class VacationPeriod(Base): __tablename__ = "vacation_periods" id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4())) user_id: Mapped[str] = mapped_column(String(36), ForeignKey("users.id", ondelete="CASCADE"), index=True) start_date: Mapped[date] = mapped_column(Date, index=True, nullable=False) end_date: Mapped[date] = mapped_column(Date, index=True, nullable=False) include_weekends: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) notes: Mapped[str | None] = mapped_column(Text, default=None) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False ) user: Mapped[User] = relationship("User", back_populates="vacation_periods") class SpecialDayStatus(Base): __tablename__ = "special_day_statuses" __table_args__ = (UniqueConstraint("user_id", "date", name="uq_user_special_day_date"),) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4())) user_id: Mapped[str] = mapped_column(String(36), ForeignKey("users.id", ondelete="CASCADE"), index=True) date: Mapped[date] = mapped_column(Date, index=True, nullable=False) status: Mapped[str] = mapped_column(String(16), nullable=False) # holiday | sick notes: Mapped[str | None] = mapped_column(Text, default=None) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False ) user: Mapped[User] = relationship("User", back_populates="special_day_statuses") class OvertimeAdjustment(Base): __tablename__ = "overtime_adjustments" __table_args__ = (UniqueConstraint("user_id", "date", name="uq_user_overtime_adjustment_date"),) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4())) user_id: Mapped[str] = mapped_column(String(36), ForeignKey("users.id", ondelete="CASCADE"), index=True) date: Mapped[date] = mapped_column(Date, index=True, nullable=False) minutes: Mapped[int] = mapped_column(Integer, nullable=False) notes: Mapped[str | None] = mapped_column(Text, default=None) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False ) user: Mapped[User] = relationship("User", back_populates="overtime_adjustments") class AutoEntrySkip(Base): __tablename__ = "auto_entry_skips" __table_args__ = (UniqueConstraint("user_id", "date", name="uq_user_auto_entry_skip_date"),) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4())) user_id: Mapped[str] = mapped_column(String(36), ForeignKey("users.id", ondelete="CASCADE"), index=True) date: Mapped[date] = mapped_column(Date, index=True, nullable=False) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False ) user: Mapped[User] = relationship("User", back_populates="auto_entry_skips") class PasswordResetToken(Base): __tablename__ = "password_reset_tokens" id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4())) user_id: Mapped[str] = mapped_column(String(36), ForeignKey("users.id", ondelete="CASCADE"), index=True) token_hash: Mapped[str] = mapped_column(String(128), unique=True, index=True, nullable=False) expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, index=True) used_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), default=None) requested_ip: Mapped[str | None] = mapped_column(String(64), default=None) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False, index=True ) user: Mapped[User] = relationship("User", back_populates="password_reset_tokens") class ImportPreview(Base): __tablename__ = "import_previews" id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4())) user_id: Mapped[str] = mapped_column(String(36), ForeignKey("users.id", ondelete="CASCADE"), index=True) mode: Mapped[str] = mapped_column(String(32), nullable=False) payload_json: Mapped[str] = mapped_column(Text, nullable=False) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False, index=True ) user: Mapped[User] = relationship("User", back_populates="import_previews") class EmailServerConfig(Base): __tablename__ = "email_server_config" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) smtp_host: Mapped[str] = mapped_column(String(255), default="", nullable=False) smtp_port: Mapped[int] = mapped_column(Integer, default=587, nullable=False) smtp_username: Mapped[str | None] = mapped_column(String(255), default=None) smtp_password_encrypted: Mapped[str | None] = mapped_column(Text, default=None) from_email: Mapped[str] = mapped_column(String(255), default="", nullable=False) from_name: Mapped[str] = mapped_column(String(255), default="Stundenfuchs", nullable=False) use_starttls: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) use_ssl: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) verify_tls: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) registration_mails_enabled: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) password_reset_mails_enabled: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) registration_admin_notify_enabled: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) registration_admin_notify_admin_ids_csv: Mapped[str | None] = mapped_column(String(1024), default=None) updated_by_user_id: Mapped[str | None] = mapped_column(String(36), ForeignKey("users.id", ondelete="SET NULL")) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc) ) class SiteContent(Base): __tablename__ = "site_content" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) key: Mapped[str] = mapped_column(String(64), unique=True, index=True, nullable=False) markdown_text: Mapped[str] = mapped_column(Text, default="", nullable=False) updated_by_user_id: Mapped[str | None] = mapped_column(String(36), ForeignKey("users.id", ondelete="SET NULL")) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc) ) class SupportTicket(Base): __tablename__ = "support_tickets" id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4())) user_id: Mapped[str | None] = mapped_column(String(36), ForeignKey("users.id", ondelete="SET NULL"), index=True) category: Mapped[str] = mapped_column(String(24), default="problem", nullable=False) status: Mapped[str] = mapped_column(String(24), default="open", nullable=False, index=True) name: Mapped[str] = mapped_column(String(255), default="", nullable=False) email: Mapped[str] = mapped_column(String(255), index=True, nullable=False) subject: Mapped[str] = mapped_column(String(255), nullable=False) message: Mapped[str] = mapped_column(Text, nullable=False) admin_notes: Mapped[str | None] = mapped_column(Text, default=None) source_ip_hash: Mapped[str | None] = mapped_column(String(128), index=True) source_user_agent: Mapped[str | None] = mapped_column(String(512), default=None) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False, index=True ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc) ) closed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), default=None) user: Mapped[User | None] = relationship("User", back_populates="support_tickets", foreign_keys=[user_id])