@@ -230,6 +230,85 @@ def _migrate_schema():
230230 )
231231
232232
233+ # ── Workspace tables ──────────────────────────────────────────────────
234+ existing_tables = set (inspector .get_table_names ())
235+
236+ if "workspaces" not in existing_tables :
237+ try :
238+ with engine .begin () as conn :
239+ conn .execute (text ("""
240+ CREATE TABLE workspaces (
241+ id CHAR(36) PRIMARY KEY,
242+ name VARCHAR(255) NOT NULL,
243+ created_by CHAR(36) NOT NULL REFERENCES users(id),
244+ created_at TIMESTAMP
245+ )
246+ """ ))
247+ conn .execute (text (
248+ "CREATE INDEX IF NOT EXISTS ix_workspaces_created_by "
249+ "ON workspaces (created_by)"
250+ ))
251+ logger .info ("Migration: created table workspaces" )
252+ except Exception :
253+ logger .warning ("Migration skipped (may already exist): workspaces" )
254+
255+ if "workspace_members" not in existing_tables :
256+ try :
257+ with engine .begin () as conn :
258+ conn .execute (text ("""
259+ CREATE TABLE workspace_members (
260+ id CHAR(36) PRIMARY KEY,
261+ workspace_id CHAR(36) NOT NULL REFERENCES workspaces(id),
262+ user_id CHAR(36) NOT NULL REFERENCES users(id),
263+ role VARCHAR(20) NOT NULL DEFAULT 'viewer',
264+ joined_at TIMESTAMP,
265+ CONSTRAINT uq_workspace_member UNIQUE (workspace_id, user_id)
266+ )
267+ """ ))
268+ conn .execute (text (
269+ "CREATE INDEX IF NOT EXISTS ix_workspace_members_workspace_id "
270+ "ON workspace_members (workspace_id)"
271+ ))
272+ conn .execute (text (
273+ "CREATE INDEX IF NOT EXISTS ix_workspace_members_user_id "
274+ "ON workspace_members (user_id)"
275+ ))
276+ logger .info ("Migration: created table workspace_members" )
277+ except Exception :
278+ logger .warning ("Migration skipped (may already exist): workspace_members" )
279+
280+ if "workspace_invitations" not in existing_tables :
281+ try :
282+ with engine .begin () as conn :
283+ conn .execute (text ("""
284+ CREATE TABLE workspace_invitations (
285+ id CHAR(36) PRIMARY KEY,
286+ email VARCHAR(120) NOT NULL,
287+ token_hash VARCHAR(255) NOT NULL UNIQUE,
288+ inviter_id CHAR(36) NOT NULL REFERENCES users(id),
289+ workspace_name VARCHAR(255) NOT NULL,
290+ created_at TIMESTAMP,
291+ expires_at TIMESTAMP NOT NULL,
292+ accepted_at TIMESTAMP
293+ )
294+ """ ))
295+ conn .execute (text (
296+ "CREATE INDEX IF NOT EXISTS ix_workspace_invitations_email "
297+ "ON workspace_invitations (email)"
298+ ))
299+ conn .execute (text (
300+ "CREATE INDEX IF NOT EXISTS ix_workspace_invitations_token_hash "
301+ "ON workspace_invitations (token_hash)"
302+ ))
303+ conn .execute (text (
304+ "CREATE INDEX IF NOT EXISTS ix_workspace_invitations_inviter_id "
305+ "ON workspace_invitations (inviter_id)"
306+ ))
307+ logger .info ("Migration: created table workspace_invitations" )
308+ except Exception :
309+ logger .warning ("Migration skipped (may already exist): workspace_invitations" )
310+
311+
233312def advisory_lock (lock_id : int ):
234313 """Context manager that acquires a PostgreSQL advisory lock (xact scope).
235314
0 commit comments