1+ """Add API Key table
2+
3+ Revision ID: 002_add_api_key_table
4+ Revises: 001_initial_schema
5+ Create Date: 2025-07-11 09:30:00.000000
6+
7+ """
8+ from alembic import op
9+ import sqlalchemy as sa
10+ from sqlalchemy .dialects import postgresql
11+
12+ # revision identifiers, used by Alembic.
13+ revision = '002_add_api_key_table'
14+ down_revision = '001_initial_schema'
15+ branch_labels = None
16+ depends_on = None
17+
18+
19+ def upgrade () -> None :
20+ """Create API keys table."""
21+ # Create api_keys table
22+ op .create_table (
23+ 'api_keys' ,
24+ sa .Column ('id' , postgresql .UUID (as_uuid = True ), nullable = False ),
25+ sa .Column ('name' , sa .String (length = 255 ), nullable = False ),
26+ sa .Column ('key_hash' , sa .String (length = 64 ), nullable = False ),
27+ sa .Column ('key_prefix' , sa .String (length = 8 ), nullable = False ),
28+ sa .Column ('user_id' , sa .String (length = 255 ), nullable = True ),
29+ sa .Column ('organization' , sa .String (length = 255 ), nullable = True ),
30+ sa .Column ('is_active' , sa .Boolean (), nullable = False , default = True ),
31+ sa .Column ('is_admin' , sa .Boolean (), nullable = False , default = False ),
32+ sa .Column ('max_concurrent_jobs' , sa .Integer (), nullable = False , default = 5 ),
33+ sa .Column ('monthly_limit_minutes' , sa .Integer (), nullable = False , default = 10000 ),
34+ sa .Column ('total_requests' , sa .Integer (), nullable = False , default = 0 ),
35+ sa .Column ('last_used_at' , sa .DateTime (timezone = True ), nullable = True ),
36+ sa .Column ('created_at' , sa .DateTime (timezone = True ), server_default = sa .text ('now()' ), nullable = False ),
37+ sa .Column ('expires_at' , sa .DateTime (timezone = True ), nullable = True ),
38+ sa .Column ('revoked_at' , sa .DateTime (timezone = True ), nullable = True ),
39+ sa .Column ('description' , sa .Text (), nullable = True ),
40+ sa .Column ('created_by' , sa .String (length = 255 ), nullable = True ),
41+ sa .PrimaryKeyConstraint ('id' )
42+ )
43+
44+ # Create indexes
45+ op .create_index ('ix_api_keys_key_hash' , 'api_keys' , ['key_hash' ], unique = True )
46+ op .create_index ('ix_api_keys_key_prefix' , 'api_keys' , ['key_prefix' ])
47+ op .create_index ('ix_api_keys_user_id' , 'api_keys' , ['user_id' ])
48+ op .create_index ('ix_api_keys_organization' , 'api_keys' , ['organization' ])
49+ op .create_index ('ix_api_keys_is_active' , 'api_keys' , ['is_active' ])
50+ op .create_index ('ix_api_keys_created_at' , 'api_keys' , ['created_at' ])
51+ op .create_index ('ix_api_keys_expires_at' , 'api_keys' , ['expires_at' ])
52+
53+ # Add composite index for common queries
54+ op .create_index ('ix_api_keys_active_lookup' , 'api_keys' , ['is_active' , 'revoked_at' , 'expires_at' ])
55+
56+
57+ def downgrade () -> None :
58+ """Drop API keys table."""
59+ # Drop indexes
60+ op .drop_index ('ix_api_keys_active_lookup' , table_name = 'api_keys' )
61+ op .drop_index ('ix_api_keys_expires_at' , table_name = 'api_keys' )
62+ op .drop_index ('ix_api_keys_created_at' , table_name = 'api_keys' )
63+ op .drop_index ('ix_api_keys_is_active' , table_name = 'api_keys' )
64+ op .drop_index ('ix_api_keys_organization' , table_name = 'api_keys' )
65+ op .drop_index ('ix_api_keys_user_id' , table_name = 'api_keys' )
66+ op .drop_index ('ix_api_keys_key_prefix' , table_name = 'api_keys' )
67+ op .drop_index ('ix_api_keys_key_hash' , table_name = 'api_keys' )
68+
69+ # Drop table
70+ op .drop_table ('api_keys' )
0 commit comments