2026-02-26 17:58:37 +08:00
""" initial schema part 4: collaboration tables
Revision ID: 202602260004
Revises: 202602260003
Create Date: 2026-02-26 20:13:00
"""
from typing import Sequence , Union
from alembic import op
import sqlalchemy as sa
from sqlalchemy . dialects import postgresql
revision : str = " 202602260004 "
down_revision : Union [ str , Sequence [ str ] , None ] = " 202602260003 "
branch_labels : Union [ str , Sequence [ str ] , None ] = None
depends_on : Union [ str , Sequence [ str ] , None ] = None
def upgrade ( ) - > None :
op . create_table (
" schedule_items " ,
sa . Column ( " id " , sa . UUID ( ) , nullable = False ) ,
sa . Column ( " owner_id " , sa . UUID ( ) , nullable = False ) ,
sa . Column ( " title " , sa . String ( length = 255 ) , nullable = False ) ,
sa . Column ( " description " , sa . Text ( ) , nullable = True ) ,
sa . Column ( " start_at " , sa . DateTime ( timezone = True ) , nullable = False ) ,
sa . Column ( " end_at " , sa . DateTime ( timezone = True ) , nullable = True ) ,
sa . Column ( " timezone " , sa . String ( length = 50 ) , nullable = False ) ,
sa . Column (
" metadata " ,
postgresql . JSONB ( astext_type = sa . Text ( ) ) ,
server_default = " {} " ,
nullable = False ,
) ,
sa . Column ( " recurrence_rule " , sa . String ( length = 255 ) , nullable = True ) ,
sa . Column ( " source_type " , sa . String ( length = 20 ) , nullable = False ) ,
sa . Column ( " status " , sa . String ( length = 20 ) , nullable = False ) ,
sa . Column ( " created_by " , sa . UUID ( ) , nullable = True ) ,
sa . Column (
" created_at " ,
sa . DateTime ( timezone = True ) ,
server_default = sa . text ( " now() " ) ,
nullable = False ,
) ,
sa . Column (
" updated_at " ,
sa . DateTime ( timezone = True ) ,
server_default = sa . text ( " now() " ) ,
nullable = False ,
) ,
sa . Column ( " deleted_at " , sa . DateTime ( timezone = True ) , nullable = True ) ,
sa . PrimaryKeyConstraint ( " id " ) ,
)
op . create_index (
" ix_schedule_items_owner_start " ,
" schedule_items " ,
[ " owner_id " , " start_at " ] ,
unique = False ,
)
op . create_index (
" ix_schedule_items_status_start " ,
" schedule_items " ,
[ " status " , " start_at " ] ,
unique = False ,
)
op . execute (
" ALTER TABLE schedule_items ADD CONSTRAINT chk_schedule_item_source_type CHECK (source_type IN ( ' manual ' , ' imported ' , ' agent_generated ' )) "
)
op . execute (
" ALTER TABLE schedule_items ADD CONSTRAINT chk_schedule_item_status CHECK (status IN ( ' active ' , ' completed ' , ' canceled ' , ' archived ' )) "
)
op . create_foreign_key (
" fk_schedule_items_owner_id " ,
" schedule_items " ,
" users " ,
[ " owner_id " ] ,
[ " id " ] ,
referent_schema = " auth " ,
ondelete = " CASCADE " ,
)
op . create_foreign_key (
" fk_schedule_items_created_by " ,
" schedule_items " ,
" users " ,
[ " created_by " ] ,
[ " id " ] ,
referent_schema = " auth " ,
ondelete = " SET NULL " ,
)
_enable_rls ( " schedule_items " )
op . create_table (
" schedule_subscriptions " ,
sa . Column ( " id " , sa . UUID ( ) , nullable = False ) ,
sa . Column ( " item_id " , sa . UUID ( ) , nullable = False ) ,
sa . Column ( " subscriber_id " , sa . UUID ( ) , nullable = False ) ,
sa . Column (
" permission " , sa . Integer ( ) , nullable = False , server_default = sa . text ( " 1 " )
) ,
sa . Column (
" notify_level " ,
sa . String ( length = 20 ) ,
nullable = False ,
server_default = sa . text ( " ' all ' " ) ,
) ,
sa . Column (
" status " ,
sa . String ( length = 20 ) ,
nullable = False ,
server_default = sa . text ( " ' active ' " ) ,
) ,
sa . Column ( " created_by " , sa . UUID ( ) , nullable = True ) ,
sa . Column (
" created_at " ,
sa . DateTime ( timezone = True ) ,
server_default = sa . text ( " now() " ) ,
nullable = False ,
) ,
sa . Column (
" updated_at " ,
sa . DateTime ( timezone = True ) ,
server_default = sa . text ( " now() " ) ,
nullable = False ,
) ,
sa . PrimaryKeyConstraint ( " id " ) ,
sa . UniqueConstraint (
" item_id " , " subscriber_id " , name = " uq_schedule_subscriptions_item_subscriber "
) ,
)
op . create_index (
" ix_schedule_subscribers_subscriber_status " ,
" schedule_subscriptions " ,
[ " subscriber_id " , " status " ] ,
unique = False ,
)
op . create_index (
" ix_schedule_subscribers_item_status " ,
" schedule_subscriptions " ,
[ " item_id " , " status " ] ,
unique = False ,
)
op . execute (
" ALTER TABLE schedule_subscriptions ADD CONSTRAINT chk_schedule_subscription_permission CHECK (permission BETWEEN 0 AND 7) "
)
op . execute (
" ALTER TABLE schedule_subscriptions ADD CONSTRAINT chk_schedule_subscription_notify_level CHECK (notify_level IN ( ' all ' , ' mentions ' , ' none ' )) "
)
op . execute (
2026-03-11 20:51:56 +08:00
" ALTER TABLE schedule_subscriptions ADD CONSTRAINT chk_schedule_subscription_status CHECK (status IN ( ' active ' , ' paused ' , ' unsubscribed ' , ' pending ' )) "
2026-02-26 17:58:37 +08:00
)
op . create_foreign_key (
" fk_schedule_subscriptions_item_id " ,
" schedule_subscriptions " ,
" schedule_items " ,
[ " item_id " ] ,
[ " id " ] ,
ondelete = " CASCADE " ,
)
op . create_foreign_key (
" fk_schedule_subscriptions_subscriber_id " ,
" schedule_subscriptions " ,
" users " ,
[ " subscriber_id " ] ,
[ " id " ] ,
referent_schema = " auth " ,
ondelete = " CASCADE " ,
)
op . create_foreign_key (
" fk_schedule_subscriptions_created_by " ,
" schedule_subscriptions " ,
" users " ,
[ " created_by " ] ,
[ " id " ] ,
referent_schema = " auth " ,
ondelete = " SET NULL " ,
)
_enable_rls ( " schedule_subscriptions " )
op . create_table (
" todos " ,
sa . Column ( " id " , sa . UUID ( ) , nullable = False ) ,
sa . Column ( " owner_id " , sa . UUID ( ) , nullable = False ) ,
sa . Column ( " title " , sa . String ( length = 255 ) , nullable = False ) ,
sa . Column ( " description " , sa . String ( length = 1000 ) , nullable = True ) ,
sa . Column ( " due_at " , sa . DateTime ( timezone = True ) , nullable = True ) ,
sa . Column ( " priority " , sa . Integer ( ) , nullable = False ) ,
sa . Column ( " completed_at " , sa . DateTime ( timezone = True ) , nullable = True ) ,
sa . Column ( " status " , sa . String ( length = 20 ) , nullable = False ) ,
sa . Column ( " created_by " , sa . UUID ( ) , nullable = True ) ,
sa . Column (
" created_at " ,
sa . DateTime ( timezone = True ) ,
server_default = sa . text ( " now() " ) ,
nullable = False ,
) ,
sa . Column (
" updated_at " ,
sa . DateTime ( timezone = True ) ,
server_default = sa . text ( " now() " ) ,
nullable = False ,
) ,
sa . Column ( " deleted_at " , sa . DateTime ( timezone = True ) , nullable = True ) ,
sa . PrimaryKeyConstraint ( " id " ) ,
)
op . create_index (
" ix_todos_owner_status_due " ,
" todos " ,
[ " owner_id " , " status " , " due_at " ] ,
unique = False ,
)
op . create_index (
" ix_todos_owner_created " , " todos " , [ " owner_id " , " created_at " ] , unique = False
)
op . execute (
" ALTER TABLE todos ADD CONSTRAINT chk_todos_status CHECK (status IN ( ' pending ' , ' done ' , ' canceled ' )) "
)
op . execute (
" ALTER TABLE todos ADD CONSTRAINT chk_todos_priority CHECK (priority BETWEEN 1 AND 4) "
)
op . create_foreign_key (
" fk_todos_owner_id " ,
" todos " ,
" users " ,
[ " owner_id " ] ,
[ " id " ] ,
referent_schema = " auth " ,
ondelete = " CASCADE " ,
)
op . create_foreign_key (
" fk_todos_created_by " ,
" todos " ,
" users " ,
[ " created_by " ] ,
[ " id " ] ,
referent_schema = " auth " ,
ondelete = " SET NULL " ,
)
_enable_rls ( " todos " )
op . create_table (
" todo_sources " ,
sa . Column ( " id " , sa . UUID ( ) , nullable = False ) ,
sa . Column ( " todo_id " , sa . UUID ( ) , nullable = False ) ,
sa . Column ( " schedule_item_id " , sa . UUID ( ) , nullable = False ) ,
sa . Column (
" created_at " ,
sa . DateTime ( timezone = True ) ,
server_default = sa . text ( " now() " ) ,
nullable = False ,
) ,
sa . Column (
" updated_at " ,
sa . DateTime ( timezone = True ) ,
server_default = sa . text ( " now() " ) ,
nullable = False ,
) ,
sa . PrimaryKeyConstraint ( " id " ) ,
sa . UniqueConstraint (
" todo_id " , " schedule_item_id " , name = " uq_todo_sources_todo_schedule "
) ,
)
op . create_index ( " ix_todo_sources_todo " , " todo_sources " , [ " todo_id " ] , unique = False )
op . create_index (
" ix_todo_sources_schedule_item " ,
" todo_sources " ,
[ " schedule_item_id " ] ,
unique = False ,
)
op . create_foreign_key (
" fk_todo_sources_todo_id " ,
" todo_sources " ,
" todos " ,
[ " todo_id " ] ,
[ " id " ] ,
ondelete = " CASCADE " ,
)
op . create_foreign_key (
" fk_todo_sources_schedule_item_id " ,
" todo_sources " ,
" schedule_items " ,
[ " schedule_item_id " ] ,
[ " id " ] ,
ondelete = " CASCADE " ,
)
_enable_rls ( " todo_sources " )
op . create_table (
" inbox_messages " ,
sa . Column ( " id " , sa . UUID ( ) , nullable = False ) ,
sa . Column ( " recipient_id " , sa . UUID ( ) , nullable = False ) ,
sa . Column ( " sender_id " , sa . UUID ( ) , nullable = True ) ,
sa . Column ( " message_type " , sa . String ( length = 20 ) , nullable = False ) ,
sa . Column ( " friendship_id " , sa . UUID ( ) , nullable = True ) ,
sa . Column ( " schedule_item_id " , sa . UUID ( ) , nullable = True ) ,
sa . Column ( " group_id " , sa . UUID ( ) , nullable = True ) ,
sa . Column ( " content " , sa . Text ( ) , nullable = True ) ,
sa . Column (
" is_read " , sa . Boolean ( ) , nullable = False , server_default = sa . text ( " false " )
) ,
sa . Column ( " status " , sa . String ( length = 20 ) , nullable = False ) ,
sa . Column ( " created_by " , sa . UUID ( ) , nullable = True ) ,
sa . Column (
" created_at " ,
sa . DateTime ( timezone = True ) ,
server_default = sa . text ( " now() " ) ,
nullable = False ,
) ,
sa . Column (
" updated_at " ,
sa . DateTime ( timezone = True ) ,
server_default = sa . text ( " now() " ) ,
nullable = False ,
) ,
sa . PrimaryKeyConstraint ( " id " ) ,
)
op . create_index (
" ix_inbox_messages_recipient_status_created " ,
" inbox_messages " ,
[ " recipient_id " , " status " , " created_at " ] ,
unique = False ,
)
op . execute (
" ALTER TABLE inbox_messages ADD CONSTRAINT chk_inbox_message_type CHECK (message_type IN ( ' friend_request ' , ' calendar ' , ' system ' , ' group ' )) "
)
op . execute (
" ALTER TABLE inbox_messages ADD CONSTRAINT chk_inbox_message_status CHECK (status IN ( ' pending ' , ' accepted ' , ' rejected ' , ' dismissed ' )) "
)
op . execute (
" ALTER TABLE inbox_messages ADD CONSTRAINT chk_inbox_message_sender CHECK ((message_type = ' system ' AND sender_id IS NULL) OR (message_type <> ' system ' AND sender_id IS NOT NULL)) "
)
op . execute (
" ALTER TABLE inbox_messages ADD CONSTRAINT chk_inbox_message_friendship CHECK ((message_type = ' friend_request ' AND friendship_id IS NOT NULL) OR (message_type <> ' friend_request ' AND friendship_id IS NULL)) "
)
op . execute (
" ALTER TABLE inbox_messages ADD CONSTRAINT chk_inbox_message_schedule_item CHECK ((message_type = ' calendar ' AND schedule_item_id IS NOT NULL) OR (message_type <> ' calendar ' AND schedule_item_id IS NULL)) "
)
op . execute (
" ALTER TABLE inbox_messages ADD CONSTRAINT chk_inbox_message_group CHECK ((message_type = ' group ' AND group_id IS NOT NULL) OR (message_type <> ' group ' AND group_id IS NULL)) "
)
op . execute (
" ALTER TABLE inbox_messages ADD CONSTRAINT chk_inbox_message_system_fields CHECK ((message_type = ' system ' AND friendship_id IS NULL AND schedule_item_id IS NULL AND group_id IS NULL) OR (message_type <> ' system ' AND (friendship_id IS NOT NULL OR schedule_item_id IS NOT NULL OR group_id IS NOT NULL))) "
)
op . create_foreign_key (
" fk_inbox_messages_recipient_id " ,
" inbox_messages " ,
" users " ,
[ " recipient_id " ] ,
[ " id " ] ,
referent_schema = " auth " ,
ondelete = " CASCADE " ,
)
op . create_foreign_key (
" fk_inbox_messages_sender_id " ,
" inbox_messages " ,
" users " ,
[ " sender_id " ] ,
[ " id " ] ,
referent_schema = " auth " ,
ondelete = " SET NULL " ,
)
op . create_foreign_key (
" fk_inbox_messages_friendship_id " ,
" inbox_messages " ,
" friendships " ,
[ " friendship_id " ] ,
[ " id " ] ,
ondelete = " CASCADE " ,
)
op . create_foreign_key (
" fk_inbox_messages_schedule_item_id " ,
" inbox_messages " ,
" schedule_items " ,
[ " schedule_item_id " ] ,
[ " id " ] ,
ondelete = " CASCADE " ,
)
op . create_foreign_key (
" fk_inbox_messages_group_id " ,
" inbox_messages " ,
" groups " ,
[ " group_id " ] ,
[ " id " ] ,
ondelete = " CASCADE " ,
)
op . create_foreign_key (
" fk_inbox_messages_created_by " ,
" inbox_messages " ,
" users " ,
[ " created_by " ] ,
[ " id " ] ,
referent_schema = " auth " ,
ondelete = " SET NULL " ,
)
_enable_rls ( " inbox_messages " )
def downgrade ( ) - > None :
_drop_rls ( " inbox_messages " )
op . drop_constraint (
" fk_inbox_messages_created_by " , " inbox_messages " , type_ = " foreignkey "
)
op . drop_constraint (
" fk_inbox_messages_group_id " , " inbox_messages " , type_ = " foreignkey "
)
op . drop_constraint (
" fk_inbox_messages_schedule_item_id " , " inbox_messages " , type_ = " foreignkey "
)
op . drop_constraint (
" fk_inbox_messages_friendship_id " , " inbox_messages " , type_ = " foreignkey "
)
op . drop_constraint (
" fk_inbox_messages_sender_id " , " inbox_messages " , type_ = " foreignkey "
)
op . drop_constraint (
" fk_inbox_messages_recipient_id " , " inbox_messages " , type_ = " foreignkey "
)
op . drop_table ( " inbox_messages " )
_drop_rls ( " todo_sources " )
op . drop_constraint (
" fk_todo_sources_schedule_item_id " , " todo_sources " , type_ = " foreignkey "
)
op . drop_constraint ( " fk_todo_sources_todo_id " , " todo_sources " , type_ = " foreignkey " )
op . drop_table ( " todo_sources " )
_drop_rls ( " todos " )
op . drop_constraint ( " fk_todos_created_by " , " todos " , type_ = " foreignkey " )
op . drop_constraint ( " fk_todos_owner_id " , " todos " , type_ = " foreignkey " )
op . drop_table ( " todos " )
_drop_rls ( " schedule_subscriptions " )
op . drop_constraint (
" fk_schedule_subscriptions_created_by " ,
" schedule_subscriptions " ,
type_ = " foreignkey " ,
)
op . drop_constraint (
" fk_schedule_subscriptions_subscriber_id " ,
" schedule_subscriptions " ,
type_ = " foreignkey " ,
)
op . drop_constraint (
" fk_schedule_subscriptions_item_id " ,
" schedule_subscriptions " ,
type_ = " foreignkey " ,
)
op . drop_table ( " schedule_subscriptions " )
_drop_rls ( " schedule_items " )
op . drop_constraint (
" fk_schedule_items_created_by " , " schedule_items " , type_ = " foreignkey "
)
op . drop_constraint (
" fk_schedule_items_owner_id " , " schedule_items " , type_ = " foreignkey "
)
op . drop_table ( " schedule_items " )
def _enable_rls ( table_name : str ) - > None :
for role in [ " anon " , " authenticated " ] :
for action in [ " select " , " insert " , " update " , " delete " ] :
op . execute (
f " DROP POLICY IF EXISTS { role } _ { action } _ { table_name } ON { table_name } "
)
op . execute ( f " ALTER TABLE { table_name } ENABLE ROW LEVEL SECURITY " )
for role in [ " anon " , " authenticated " ] :
op . execute (
f " CREATE POLICY { role } _select_ { table_name } ON { table_name } FOR SELECT TO { role } USING (false) "
)
op . execute (
f " CREATE POLICY { role } _insert_ { table_name } ON { table_name } FOR INSERT TO { role } WITH CHECK (false) "
)
op . execute (
f " CREATE POLICY { role } _update_ { table_name } ON { table_name } FOR UPDATE TO { role } USING (false) WITH CHECK (false) "
)
op . execute (
f " CREATE POLICY { role } _delete_ { table_name } ON { table_name } FOR DELETE TO { role } USING (false) "
)
def _drop_rls ( table_name : str ) - > None :
for role in [ " anon " , " authenticated " ] :
op . execute ( f " DROP POLICY IF EXISTS { role } _delete_ { table_name } ON { table_name } " )
op . execute ( f " DROP POLICY IF EXISTS { role } _update_ { table_name } ON { table_name } " )
op . execute ( f " DROP POLICY IF EXISTS { role } _insert_ { table_name } ON { table_name } " )
op . execute ( f " DROP POLICY IF EXISTS { role } _select_ { table_name } ON { table_name } " )
op . execute ( f " ALTER TABLE { table_name } DISABLE ROW LEVEL SECURITY " )