Skip to content

Commit 81f611f

Browse files
committed
refac
1 parent 245e0ee commit 81f611f

6 files changed

Lines changed: 203 additions & 120 deletions

backend/open_webui/migrations/versions/56359461a091_add_calendar_tables.py

Lines changed: 74 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -18,58 +18,81 @@
1818
depends_on: Union[str, Sequence[str], None] = None
1919

2020

21+
def _index_exists(inspector, index_name, table_name):
22+
"""Check if an index already exists on the given table."""
23+
indexes = inspector.get_indexes(table_name)
24+
return any(idx['name'] == index_name for idx in indexes)
25+
26+
2127
def upgrade() -> None:
22-
op.create_table(
23-
'calendar',
24-
sa.Column('id', sa.Text(), nullable=False),
25-
sa.Column('user_id', sa.Text(), nullable=False),
26-
sa.Column('name', sa.Text(), nullable=False),
27-
sa.Column('color', sa.Text(), nullable=True),
28-
sa.Column('is_default', sa.Boolean(), nullable=False),
29-
sa.Column('data', sa.JSON(), nullable=True),
30-
sa.Column('meta', sa.JSON(), nullable=True),
31-
sa.Column('created_at', sa.BigInteger(), nullable=False),
32-
sa.Column('updated_at', sa.BigInteger(), nullable=False),
33-
sa.PrimaryKeyConstraint('id'),
34-
)
35-
op.create_index('ix_calendar_user', 'calendar', ['user_id'], unique=False)
36-
37-
op.create_table(
38-
'calendar_event',
39-
sa.Column('id', sa.Text(), nullable=False),
40-
sa.Column('calendar_id', sa.Text(), nullable=False),
41-
sa.Column('user_id', sa.Text(), nullable=False),
42-
sa.Column('title', sa.Text(), nullable=False),
43-
sa.Column('description', sa.Text(), nullable=True),
44-
sa.Column('start_at', sa.BigInteger(), nullable=False),
45-
sa.Column('end_at', sa.BigInteger(), nullable=True),
46-
sa.Column('all_day', sa.Boolean(), nullable=False),
47-
sa.Column('rrule', sa.Text(), nullable=True),
48-
sa.Column('color', sa.Text(), nullable=True),
49-
sa.Column('location', sa.Text(), nullable=True),
50-
sa.Column('data', sa.JSON(), nullable=True),
51-
sa.Column('meta', sa.JSON(), nullable=True),
52-
sa.Column('is_cancelled', sa.Boolean(), nullable=False),
53-
sa.Column('created_at', sa.BigInteger(), nullable=False),
54-
sa.Column('updated_at', sa.BigInteger(), nullable=False),
55-
sa.PrimaryKeyConstraint('id'),
56-
)
57-
op.create_index('ix_calendar_event_calendar', 'calendar_event', ['calendar_id', 'start_at'], unique=False)
58-
op.create_index('ix_calendar_event_user_date', 'calendar_event', ['user_id', 'start_at'], unique=False)
59-
60-
op.create_table(
61-
'calendar_event_attendee',
62-
sa.Column('id', sa.Text(), nullable=False),
63-
sa.Column('event_id', sa.Text(), nullable=False),
64-
sa.Column('user_id', sa.Text(), nullable=False),
65-
sa.Column('status', sa.Text(), nullable=False),
66-
sa.Column('meta', sa.JSON(), nullable=True),
67-
sa.Column('created_at', sa.BigInteger(), nullable=False),
68-
sa.Column('updated_at', sa.BigInteger(), nullable=False),
69-
sa.PrimaryKeyConstraint('id'),
70-
sa.UniqueConstraint('event_id', 'user_id', name='uq_event_attendee'),
71-
)
72-
op.create_index('ix_calendar_event_attendee_user', 'calendar_event_attendee', ['user_id', 'status'], unique=False)
28+
conn = op.get_bind()
29+
inspector = sa.inspect(conn)
30+
tables = inspector.get_table_names()
31+
32+
if 'calendar' not in tables:
33+
op.create_table(
34+
'calendar',
35+
sa.Column('id', sa.Text(), nullable=False),
36+
sa.Column('user_id', sa.Text(), nullable=False),
37+
sa.Column('name', sa.Text(), nullable=False),
38+
sa.Column('color', sa.Text(), nullable=True),
39+
sa.Column('is_default', sa.Boolean(), nullable=False),
40+
sa.Column('data', sa.JSON(), nullable=True),
41+
sa.Column('meta', sa.JSON(), nullable=True),
42+
sa.Column('created_at', sa.BigInteger(), nullable=False),
43+
sa.Column('updated_at', sa.BigInteger(), nullable=False),
44+
sa.PrimaryKeyConstraint('id'),
45+
)
46+
47+
if 'calendar' in inspector.get_table_names():
48+
if not _index_exists(inspector, 'ix_calendar_user', 'calendar'):
49+
op.create_index('ix_calendar_user', 'calendar', ['user_id'], unique=False)
50+
51+
if 'calendar_event' not in tables:
52+
op.create_table(
53+
'calendar_event',
54+
sa.Column('id', sa.Text(), nullable=False),
55+
sa.Column('calendar_id', sa.Text(), nullable=False),
56+
sa.Column('user_id', sa.Text(), nullable=False),
57+
sa.Column('title', sa.Text(), nullable=False),
58+
sa.Column('description', sa.Text(), nullable=True),
59+
sa.Column('start_at', sa.BigInteger(), nullable=False),
60+
sa.Column('end_at', sa.BigInteger(), nullable=True),
61+
sa.Column('all_day', sa.Boolean(), nullable=False),
62+
sa.Column('rrule', sa.Text(), nullable=True),
63+
sa.Column('color', sa.Text(), nullable=True),
64+
sa.Column('location', sa.Text(), nullable=True),
65+
sa.Column('data', sa.JSON(), nullable=True),
66+
sa.Column('meta', sa.JSON(), nullable=True),
67+
sa.Column('is_cancelled', sa.Boolean(), nullable=False),
68+
sa.Column('created_at', sa.BigInteger(), nullable=False),
69+
sa.Column('updated_at', sa.BigInteger(), nullable=False),
70+
sa.PrimaryKeyConstraint('id'),
71+
)
72+
73+
if 'calendar_event' in inspector.get_table_names():
74+
if not _index_exists(inspector, 'ix_calendar_event_calendar', 'calendar_event'):
75+
op.create_index('ix_calendar_event_calendar', 'calendar_event', ['calendar_id', 'start_at'], unique=False)
76+
if not _index_exists(inspector, 'ix_calendar_event_user_date', 'calendar_event'):
77+
op.create_index('ix_calendar_event_user_date', 'calendar_event', ['user_id', 'start_at'], unique=False)
78+
79+
if 'calendar_event_attendee' not in tables:
80+
op.create_table(
81+
'calendar_event_attendee',
82+
sa.Column('id', sa.Text(), nullable=False),
83+
sa.Column('event_id', sa.Text(), nullable=False),
84+
sa.Column('user_id', sa.Text(), nullable=False),
85+
sa.Column('status', sa.Text(), nullable=False),
86+
sa.Column('meta', sa.JSON(), nullable=True),
87+
sa.Column('created_at', sa.BigInteger(), nullable=False),
88+
sa.Column('updated_at', sa.BigInteger(), nullable=False),
89+
sa.PrimaryKeyConstraint('id'),
90+
sa.UniqueConstraint('event_id', 'user_id', name='uq_event_attendee'),
91+
)
92+
93+
if 'calendar_event_attendee' in inspector.get_table_names():
94+
if not _index_exists(inspector, 'ix_calendar_event_attendee_user', 'calendar_event_attendee'):
95+
op.create_index('ix_calendar_event_attendee_user', 'calendar_event_attendee', ['user_id', 'status'], unique=False)
7396

7497

7598
def downgrade() -> None:

backend/open_webui/migrations/versions/a3dd5bedd151_add_tasks_and_summary_to_chat.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,14 @@
1919

2020

2121
def upgrade() -> None:
22-
op.add_column('chat', sa.Column('tasks', sa.JSON(), nullable=True))
23-
op.add_column('chat', sa.Column('summary', sa.Text(), nullable=True))
22+
conn = op.get_bind()
23+
inspector = sa.inspect(conn)
24+
columns = [col['name'] for col in inspector.get_columns('chat')]
25+
26+
if 'tasks' not in columns:
27+
op.add_column('chat', sa.Column('tasks', sa.JSON(), nullable=True))
28+
if 'summary' not in columns:
29+
op.add_column('chat', sa.Column('summary', sa.Text(), nullable=True))
2430

2531

2632
def downgrade() -> None:

backend/open_webui/migrations/versions/b7c8d9e0f1a2_add_last_read_at_to_chat.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,14 @@
1717

1818

1919
def upgrade():
20-
op.add_column('chat', sa.Column('last_read_at', sa.BigInteger(), nullable=True))
21-
# Set existing chats to be marked as read
22-
op.execute('UPDATE chat SET last_read_at = updated_at')
20+
conn = op.get_bind()
21+
inspector = sa.inspect(conn)
22+
columns = [col['name'] for col in inspector.get_columns('chat')]
23+
24+
if 'last_read_at' not in columns:
25+
op.add_column('chat', sa.Column('last_read_at', sa.BigInteger(), nullable=True))
26+
# Set existing chats to be marked as read
27+
op.execute('UPDATE chat SET last_read_at = updated_at')
2328

2429

2530
def downgrade():

backend/open_webui/migrations/versions/c1d2e3f4a5b6_add_shared_chat_table.py

Lines changed: 59 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -61,18 +61,21 @@
6161

6262
def upgrade():
6363
conn = op.get_bind()
64-
65-
# 1. Create shared_chat table
66-
op.create_table(
67-
'shared_chat',
68-
sa.Column('id', sa.Text(), primary_key=True),
69-
sa.Column('chat_id', sa.Text(), sa.ForeignKey('chat.id', ondelete='CASCADE'), nullable=False),
70-
sa.Column('user_id', sa.Text(), nullable=False),
71-
sa.Column('title', sa.Text(), nullable=True),
72-
sa.Column('chat', sa.JSON(), nullable=True),
73-
sa.Column('created_at', sa.BigInteger(), nullable=True),
74-
sa.Column('updated_at', sa.BigInteger(), nullable=True),
75-
)
64+
inspector = sa.inspect(conn)
65+
tables = inspector.get_table_names()
66+
67+
# 1. Create shared_chat table (idempotent)
68+
if 'shared_chat' not in tables:
69+
op.create_table(
70+
'shared_chat',
71+
sa.Column('id', sa.Text(), primary_key=True),
72+
sa.Column('chat_id', sa.Text(), sa.ForeignKey('chat.id', ondelete='CASCADE'), nullable=False),
73+
sa.Column('user_id', sa.Text(), nullable=False),
74+
sa.Column('title', sa.Text(), nullable=True),
75+
sa.Column('chat', sa.JSON(), nullable=True),
76+
sa.Column('created_at', sa.BigInteger(), nullable=True),
77+
sa.Column('updated_at', sa.BigInteger(), nullable=True),
78+
)
7679

7780
# 2. Migrate existing shared-* rows
7881
shared_rows = conn.execute(
@@ -96,31 +99,53 @@ def upgrade():
9699
if not original:
97100
continue
98101

99-
# Insert snapshot into shared_chat
100-
conn.execute(
101-
shared_chat_t.insert().values(
102-
id=share_token,
103-
chat_id=original_chat_id,
104-
user_id=original.user_id,
105-
title=row.title,
106-
chat=row.chat,
107-
created_at=row.created_at,
108-
updated_at=row.updated_at,
102+
# Check if shared_chat record already exists (idempotent)
103+
existing_shared = conn.execute(
104+
sa.select(shared_chat_t.c.id).where(
105+
shared_chat_t.c.id == share_token
106+
)
107+
).fetchone()
108+
109+
if not existing_shared:
110+
# Insert snapshot into shared_chat
111+
conn.execute(
112+
shared_chat_t.insert().values(
113+
id=share_token,
114+
chat_id=original_chat_id,
115+
user_id=original.user_id,
116+
title=row.title,
117+
chat=row.chat,
118+
created_at=row.created_at,
119+
updated_at=row.updated_at,
120+
)
109121
)
110-
)
111122

112-
# Create user:*:read grant for backward compat
113-
conn.execute(
114-
access_grant_t.insert().values(
115-
id=str(uuid.uuid4()),
116-
resource_type='shared_chat',
117-
resource_id=original_chat_id,
118-
principal_type='user',
119-
principal_id='*',
120-
permission='read',
121-
created_at=row.created_at or int(time.time()),
123+
# Check if access_grant record already exists (idempotent)
124+
existing_grant = conn.execute(
125+
sa.select(access_grant_t.c.id).where(
126+
sa.and_(
127+
access_grant_t.c.resource_type == 'shared_chat',
128+
access_grant_t.c.resource_id == original_chat_id,
129+
access_grant_t.c.principal_type == 'user',
130+
access_grant_t.c.principal_id == '*',
131+
access_grant_t.c.permission == 'read',
132+
)
133+
)
134+
).fetchone()
135+
136+
if not existing_grant:
137+
# Create user:*:read grant for backward compat
138+
conn.execute(
139+
access_grant_t.insert().values(
140+
id=str(uuid.uuid4()),
141+
resource_type='shared_chat',
142+
resource_id=original_chat_id,
143+
principal_type='user',
144+
principal_id='*',
145+
permission='read',
146+
created_at=row.created_at or int(time.time()),
147+
)
122148
)
123-
)
124149

125150
# 3. Clean up old phantom rows
126151
conn.execute(

backend/open_webui/migrations/versions/d4e5f6a7b8c9_add_automation_tables.py

Lines changed: 48 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,36 +16,55 @@
1616
depends_on = None
1717

1818

19+
def _index_exists(inspector, index_name, table_name):
20+
"""Check if an index already exists on the given table (works for both SQLite and PostgreSQL)."""
21+
indexes = inspector.get_indexes(table_name)
22+
return any(idx['name'] == index_name for idx in indexes)
23+
24+
1925
def upgrade():
20-
op.create_table(
21-
'automation',
22-
sa.Column('id', sa.Text(), primary_key=True),
23-
sa.Column('user_id', sa.Text(), nullable=False),
24-
sa.Column('name', sa.Text(), nullable=False),
25-
sa.Column('data', sa.JSON(), nullable=False),
26-
sa.Column('meta', sa.JSON(), nullable=True),
27-
sa.Column('is_active', sa.Boolean(), nullable=False, default=True),
28-
sa.Column('last_run_at', sa.BigInteger(), nullable=True),
29-
sa.Column('next_run_at', sa.BigInteger(), nullable=True),
30-
sa.Column('created_at', sa.BigInteger(), nullable=False),
31-
sa.Column('updated_at', sa.BigInteger(), nullable=False),
32-
)
33-
op.create_index('ix_automation_next_run', 'automation', ['next_run_at'])
34-
35-
op.create_table(
36-
'automation_run',
37-
sa.Column('id', sa.Text(), primary_key=True),
38-
sa.Column('automation_id', sa.Text(), nullable=False),
39-
sa.Column('chat_id', sa.Text(), nullable=True),
40-
sa.Column('status', sa.Text(), nullable=False),
41-
sa.Column('error', sa.Text(), nullable=True),
42-
sa.Column('created_at', sa.BigInteger(), nullable=False),
43-
)
44-
op.create_index(
45-
'ix_automation_run_automation_id',
46-
'automation_run',
47-
['automation_id'],
48-
)
26+
conn = op.get_bind()
27+
inspector = sa.inspect(conn)
28+
tables = inspector.get_table_names()
29+
30+
if 'automation' not in tables:
31+
op.create_table(
32+
'automation',
33+
sa.Column('id', sa.Text(), primary_key=True),
34+
sa.Column('user_id', sa.Text(), nullable=False),
35+
sa.Column('name', sa.Text(), nullable=False),
36+
sa.Column('data', sa.JSON(), nullable=False),
37+
sa.Column('meta', sa.JSON(), nullable=True),
38+
sa.Column('is_active', sa.Boolean(), nullable=False, default=True),
39+
sa.Column('last_run_at', sa.BigInteger(), nullable=True),
40+
sa.Column('next_run_at', sa.BigInteger(), nullable=True),
41+
sa.Column('created_at', sa.BigInteger(), nullable=False),
42+
sa.Column('updated_at', sa.BigInteger(), nullable=False),
43+
)
44+
45+
# Re-check tables in case we just created it
46+
if 'automation' in inspector.get_table_names():
47+
if not _index_exists(inspector, 'ix_automation_next_run', 'automation'):
48+
op.create_index('ix_automation_next_run', 'automation', ['next_run_at'])
49+
50+
if 'automation_run' not in tables:
51+
op.create_table(
52+
'automation_run',
53+
sa.Column('id', sa.Text(), primary_key=True),
54+
sa.Column('automation_id', sa.Text(), nullable=False),
55+
sa.Column('chat_id', sa.Text(), nullable=True),
56+
sa.Column('status', sa.Text(), nullable=False),
57+
sa.Column('error', sa.Text(), nullable=True),
58+
sa.Column('created_at', sa.BigInteger(), nullable=False),
59+
)
60+
61+
if 'automation_run' in inspector.get_table_names():
62+
if not _index_exists(inspector, 'ix_automation_run_automation_id', 'automation_run'):
63+
op.create_index(
64+
'ix_automation_run_automation_id',
65+
'automation_run',
66+
['automation_id'],
67+
)
4968

5069

5170
def downgrade():

backend/open_webui/migrations/versions/e1f2a3b4c5d6_add_is_pinned_to_note.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@
1616

1717

1818
def upgrade():
19-
op.add_column('note', sa.Column('is_pinned', sa.Boolean(), nullable=True))
19+
conn = op.get_bind()
20+
inspector = sa.inspect(conn)
21+
columns = [col['name'] for col in inspector.get_columns('note')]
22+
23+
if 'is_pinned' not in columns:
24+
op.add_column('note', sa.Column('is_pinned', sa.Boolean(), nullable=True))
2025

2126

2227
def downgrade():

0 commit comments

Comments
 (0)