From 0a03ac60db6d427dd1a6eab1cabbadde5305fc3d Mon Sep 17 00:00:00 2001 From: Ivo Oskamp Date: Thu, 28 May 2026 15:50:36 +0200 Subject: [PATCH] auth: add 0003_auth_tables migration --- .../migrations/versions/0003_auth_tables.py | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 containers/clearview/src/clearview_app/migrations/versions/0003_auth_tables.py diff --git a/containers/clearview/src/clearview_app/migrations/versions/0003_auth_tables.py b/containers/clearview/src/clearview_app/migrations/versions/0003_auth_tables.py new file mode 100644 index 0000000..3270263 --- /dev/null +++ b/containers/clearview/src/clearview_app/migrations/versions/0003_auth_tables.py @@ -0,0 +1,67 @@ +"""Create users, user_sessions, auth_audit tables. + +Revision ID: 0003_auth_tables +Revises: 0002_timestamptz +Create Date: 2026-05-28 +""" +from __future__ import annotations + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +revision = "0003_auth_tables" +down_revision = "0002_timestamptz" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.create_table( + "users", + sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True), + sa.Column("username", sa.String(length=128), nullable=False, unique=True), + sa.Column("password_hash", sa.Text(), nullable=False), + sa.Column("role", sa.String(length=16), nullable=False), + sa.Column("is_active", sa.Boolean(), nullable=False, server_default=sa.true()), + sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")), + sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")), + ) + op.create_index("ix_users_username", "users", ["username"], unique=True) + + op.create_table( + "user_sessions", + sa.Column("id", sa.String(length=64), primary_key=True), + sa.Column("user_id", sa.Integer(), sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=False), + sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")), + sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False), + sa.Column("last_seen_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")), + sa.Column("ip", sa.String(length=64), nullable=True), + sa.Column("user_agent", sa.Text(), nullable=True), + sa.Column("remember", sa.Boolean(), nullable=False, server_default=sa.false()), + ) + op.create_index("ix_user_sessions_user_id", "user_sessions", ["user_id"]) + op.create_index("ix_user_sessions_expires_at", "user_sessions", ["expires_at"]) + + op.create_table( + "auth_audit", + sa.Column("id", sa.BigInteger(), primary_key=True, autoincrement=True), + sa.Column("ts", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")), + sa.Column("user_id", sa.Integer(), sa.ForeignKey("users.id", ondelete="SET NULL"), nullable=True), + sa.Column("event", sa.String(length=32), nullable=False), + sa.Column("ip", sa.String(length=64), nullable=True), + sa.Column("detail", postgresql.JSONB(astext_type=sa.Text()), nullable=True), + ) + op.create_index("ix_auth_audit_ts", "auth_audit", ["ts"]) + op.create_index("ix_auth_audit_event", "auth_audit", ["event"]) + + +def downgrade() -> None: + op.drop_index("ix_auth_audit_event", table_name="auth_audit") + op.drop_index("ix_auth_audit_ts", table_name="auth_audit") + op.drop_table("auth_audit") + op.drop_index("ix_user_sessions_expires_at", table_name="user_sessions") + op.drop_index("ix_user_sessions_user_id", table_name="user_sessions") + op.drop_table("user_sessions") + op.drop_index("ix_users_username", table_name="users") + op.drop_table("users")