Files
sysops 1fedd683e0 Initial commit – TimeMaster Zeiterfassung & HR-Tool
Stand: agent-06 (Audit-Log), agent-05 (Krankmeldung), agent-07 Phase 1 (Personalnummer),
Busylight-Pull-Integration, TOTP/2FA, Abwesenheiten, Zeiterfassung, Kiosk-Grundgerüst.
Migrations 0001–0023 deployed auf 192.168.1.137 + .164.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 20:03:27 +02:00

130 lines
6.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""initial schema - auth tables
Revision ID: 0001_initial
Revises:
Create Date: 2026-03-25
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
revision = "0001_initial"
down_revision = None
branch_labels = None
depends_on = None
def upgrade() -> None:
# ── companies ─────────────────────────────────────────────────────────────
op.create_table(
"companies",
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column("name", sa.String(255), nullable=False),
sa.Column("slug", sa.String(100), nullable=False, unique=True),
sa.Column("plan", sa.String(50), server_default="trial"),
sa.Column("logo_url", sa.Text),
sa.Column("country", sa.String(10), server_default="DE"),
sa.Column("state", sa.String(10)),
sa.Column("settings", postgresql.JSONB, server_default="{}"),
)
# ── departments (before users FK target) ─────────────────────────────────
op.create_table(
"departments",
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column("company_id", postgresql.UUID(as_uuid=True),
sa.ForeignKey("companies.id", ondelete="CASCADE"), nullable=False),
sa.Column("name", sa.String(255), nullable=False),
sa.Column("manager_id", postgresql.UUID(as_uuid=True)), # FK added after users
)
# ── users ─────────────────────────────────────────────────────────────────
op.create_table(
"users",
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column("company_id", postgresql.UUID(as_uuid=True),
sa.ForeignKey("companies.id", ondelete="CASCADE")),
sa.Column("department_id", postgresql.UUID(as_uuid=True),
sa.ForeignKey("departments.id", ondelete="SET NULL")),
sa.Column("email", sa.String(255), nullable=False, unique=True),
sa.Column("password_hash", sa.Text, nullable=False),
sa.Column("first_name", sa.String(100), nullable=False),
sa.Column("last_name", sa.String(100), nullable=False),
sa.Column("role", sa.Enum(
"SUPER_ADMIN", "COMPANY_ADMIN", "HR", "MANAGER", "EMPLOYEE",
name="userrole",
), nullable=False),
sa.Column("kiosk_pin_hash", sa.Text),
sa.Column("kiosk_qr_token", sa.Text, unique=True),
sa.Column("is_active", sa.Boolean, server_default="true"),
sa.Column("invite_token_hash", sa.Text),
sa.Column("invite_expires", sa.DateTime(timezone=True)),
sa.Column("last_login", sa.DateTime(timezone=True)),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
)
op.create_index("ix_users_email", "users", ["email"])
# Now add FK for departments.manager_id → users
op.create_foreign_key(
"fk_departments_manager", "departments", "users", ["manager_id"], ["id"],
ondelete="SET NULL",
)
# ── sessions ──────────────────────────────────────────────────────────────
op.create_table(
"sessions",
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column("user_id", postgresql.UUID(as_uuid=True),
sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=False),
sa.Column("refresh_token_hash", sa.Text, nullable=False, unique=True),
sa.Column("device", sa.String(255)),
sa.Column("ip", sa.String(45)),
sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
)
op.create_index("ix_sessions_user_id", "sessions", ["user_id"])
# ── password_resets ───────────────────────────────────────────────────────
op.create_table(
"password_resets",
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column("user_id", postgresql.UUID(as_uuid=True),
sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=False),
sa.Column("token_hash", sa.Text, nullable=False, unique=True),
sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("used_at", sa.DateTime(timezone=True)),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
)
op.create_index("ix_password_resets_user_id", "password_resets", ["user_id"])
# ── audit_logs ────────────────────────────────────────────────────────────
op.create_table(
"audit_logs",
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column("company_id", postgresql.UUID(as_uuid=True),
sa.ForeignKey("companies.id", ondelete="SET NULL")),
sa.Column("user_id", postgresql.UUID(as_uuid=True),
sa.ForeignKey("users.id", ondelete="SET NULL")),
sa.Column("action", sa.String(100), nullable=False),
sa.Column("entity_type", sa.String(100)),
sa.Column("entity_id", postgresql.UUID(as_uuid=True)),
sa.Column("old_value", postgresql.JSONB),
sa.Column("new_value", postgresql.JSONB),
sa.Column("ip", sa.String(45)),
sa.Column("created_at", sa.DateTime(timezone=True),
server_default=sa.func.now(), index=True),
)
op.create_index("ix_audit_logs_company_id", "audit_logs", ["company_id"])
op.create_index("ix_audit_logs_user_id", "audit_logs", ["user_id"])
def downgrade() -> None:
op.drop_table("audit_logs")
op.drop_table("password_resets")
op.drop_table("sessions")
op.drop_constraint("fk_departments_manager", "departments", type_="foreignkey")
op.drop_table("users")
op.execute("DROP TYPE IF EXISTS userrole")
op.drop_table("departments")
op.drop_table("companies")