AI-News/backend/app/db/migrations/versions/20251122_add_roles_tables.py
2025-12-04 10:04:21 +08:00

121 lines
3.1 KiB
Python

"""add roles and user_roles tables
Revision ID: add_roles_tables
Revises: add_article_views
Create Date: 2025-11-21
"""
from typing import Tuple
import sqlalchemy as sa
from alembic import op
from sqlalchemy import func, text
from sqlalchemy.dialects.postgresql import JSONB
revision = "add_roles_tables"
down_revision = "add_article_views"
branch_labels = None
depends_on = None
def timestamps() -> Tuple[sa.Column, sa.Column]:
return (
sa.Column(
"created_at",
sa.TIMESTAMP(timezone=True),
nullable=False,
server_default=func.now(),
),
sa.Column(
"updated_at",
sa.TIMESTAMP(timezone=True),
nullable=False,
server_default=func.now(),
onupdate=func.current_timestamp(),
),
)
def upgrade() -> None:
op.create_table(
"roles",
sa.Column("id", sa.Integer, primary_key=True),
sa.Column("name", sa.String(length=64), nullable=False, unique=True),
sa.Column("description", sa.Text, nullable=False, server_default=""),
sa.Column(
"permissions",
JSONB,
nullable=False,
server_default=text("'[]'::jsonb"),
),
*timestamps(),
)
op.execute(
"""
CREATE TRIGGER update_role_modtime
BEFORE UPDATE
ON roles
FOR EACH ROW
EXECUTE PROCEDURE update_updated_at_column();
""",
)
op.create_table(
"user_roles",
sa.Column(
"user_id",
sa.Integer,
sa.ForeignKey("users.id", ondelete="CASCADE"),
nullable=False,
),
sa.Column(
"role_id",
sa.Integer,
sa.ForeignKey("roles.id", ondelete="CASCADE"),
nullable=False,
),
sa.Column(
"assigned_at",
sa.TIMESTAMP(timezone=True),
nullable=False,
server_default=func.now(),
),
)
op.create_primary_key(
"pk_user_roles",
"user_roles",
["user_id", "role_id"],
)
op.create_index("ix_user_roles_role_id", "user_roles", ["role_id"])
op.execute(
"""
INSERT INTO roles (name, description, permissions)
VALUES ('admin', 'System administrator with full privileges', '["*"]')
ON CONFLICT (name) DO NOTHING;
""",
)
op.execute(
"""
DO $$
DECLARE
admin_role_id INTEGER;
first_user_id INTEGER;
BEGIN
SELECT id INTO admin_role_id FROM roles WHERE name = 'admin';
SELECT id INTO first_user_id FROM users ORDER BY id ASC LIMIT 1;
IF admin_role_id IS NOT NULL AND first_user_id IS NOT NULL THEN
INSERT INTO user_roles (user_id, role_id)
VALUES (first_user_id, admin_role_id)
ON CONFLICT DO NOTHING;
END IF;
END $$;
""",
)
def downgrade() -> None:
op.drop_index("ix_user_roles_role_id", table_name="user_roles")
op.drop_table("user_roles")
op.drop_table("roles")