This commit is contained in:
+311
@@ -0,0 +1,311 @@
|
||||
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)
|
||||
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])
|
||||
Reference in New Issue
Block a user