Files
appks/migrate/001_init.sql
T
zhenyi dbbfb747a4 feat(auth): replace internal auth with JWT token service
- Replace InternalAuthService with TokenService using JWT tokens
- Add support for token issuance, refresh, verification and revocation
- Implement automatic signing key rotation with Redis storage
- Add database migration checks for indexes and foreign key constraints
- Update gRPC endpoints to use token-based authentication
- Remove deprecated API key based authentication system
- Add JSON Web Token support with HMAC-SHA256 signing
- Implement refresh token handling with automatic rotation
- Add token revocation by JTI and user ID
- Update build configuration to include core proto files
- Migrate database schema to handle token-based authentication
- Add comprehensive token validation and verification logic
2026-06-11 15:08:13 +08:00

2122 lines
93 KiB
PL/PgSQL

-- ============================================================
-- Migration: 001_init.sql
-- Tables: 106 | ALL FKs → ON DELETE CASCADE
-- storage_node_id → NO FK (business-layer ref to etcd)
-- Circular/self-referencing FKs deferred to ALTER TABLE
-- ============================================================
BEGIN;
-- PHASE A: Create tables in dependency order
-- models/agents/agent_execution_steps.rs → agent_execution_step
CREATE TABLE IF NOT EXISTS agent_execution_step (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
execution_id UUID NOT NULL,
step_index INTEGER NOT NULL,
step_type TEXT NOT NULL,
name TEXT NOT NULL,
status TEXT NOT NULL,
model_id UUID NULL,
tool_name TEXT NULL,
input JSONB NULL,
output JSONB NULL,
error_message TEXT NULL,
token_input_count INTEGER NULL,
token_output_count INTEGER NULL,
started_at TIMESTAMPTZ NULL,
finished_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_agent_execution_step_execution_id ON agent_execution_step (execution_id);
CREATE INDEX IF NOT EXISTS idx_agent_execution_step_model_id ON agent_execution_step (model_id);
CREATE INDEX IF NOT EXISTS idx_agent_execution_step_status_created ON agent_execution_step (status, created_at DESC);
-- models/ais/ai_models.rs → ai_model
CREATE TABLE IF NOT EXISTS ai_model (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
provider TEXT NOT NULL,
model_id TEXT NOT NULL,
name TEXT NOT NULL,
description TEXT NULL,
model_type TEXT NOT NULL,
family TEXT NULL,
version TEXT NULL,
context_window INTEGER NULL,
max_output_tokens INTEGER NULL,
input_modalities TEXT[] NOT NULL,
output_modalities TEXT[] NOT NULL,
supported_features TEXT[] NOT NULL,
pricing_unit TEXT NULL,
input_price_per_unit TEXT NULL,
output_price_per_unit TEXT NULL,
enabled BOOLEAN NOT NULL,
deprecated BOOLEAN NOT NULL,
released_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL,
deleted_at TIMESTAMPTZ NULL
);
CREATE INDEX IF NOT EXISTS idx_ai_model_model_id ON ai_model (model_id);
CREATE INDEX IF NOT EXISTS idx_ai_model_deleted ON ai_model (deleted_at) WHERE deleted_at IS NOT NULL;
-- models/users/user.rs → user
CREATE TABLE IF NOT EXISTS "user" (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
username TEXT NOT NULL,
display_name TEXT NULL,
avatar_url TEXT NULL,
bio TEXT NULL,
status TEXT NOT NULL,
role TEXT NOT NULL,
visibility TEXT NOT NULL,
is_active BOOLEAN NOT NULL,
is_bot BOOLEAN NOT NULL,
last_login_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL,
deleted_at TIMESTAMPTZ NULL
);
CREATE INDEX IF NOT EXISTS idx_user_status_created ON "user" (status, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_user_deleted ON "user" (deleted_at) WHERE deleted_at IS NOT NULL;
-- models/ais/ai_model_capabilities.rs → ai_model_capability
CREATE TABLE IF NOT EXISTS ai_model_capability (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
ai_model_id UUID NOT NULL REFERENCES ai_model(id) ON DELETE CASCADE,
capability TEXT NOT NULL,
supported BOOLEAN NOT NULL,
config JSONB NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_ai_model_capability_ai_model_id ON ai_model_capability (ai_model_id);
-- models/ais/ai_model_health.rs → ai_model_health
CREATE TABLE IF NOT EXISTS ai_model_health (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
ai_model_id UUID NOT NULL REFERENCES ai_model(id) ON DELETE CASCADE,
status TEXT NOT NULL,
latency_ms INTEGER NULL,
error_rate TEXT NULL,
last_error TEXT NULL,
checked_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_ai_model_health_ai_model_id ON ai_model_health (ai_model_id);
CREATE INDEX IF NOT EXISTS idx_ai_model_health_status_created ON ai_model_health (status, created_at DESC);
-- models/ais/ai_model_versions.rs → ai_model_version
CREATE TABLE IF NOT EXISTS ai_model_version (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
ai_model_id UUID NOT NULL REFERENCES ai_model(id) ON DELETE CASCADE,
version TEXT NOT NULL,
provider_model_id TEXT NOT NULL,
context_window INTEGER NULL,
max_output_tokens INTEGER NULL,
changelog TEXT NULL,
stable BOOLEAN NOT NULL,
deprecated BOOLEAN NOT NULL,
released_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_ai_model_version_ai_model_id ON ai_model_version (ai_model_id);
CREATE INDEX IF NOT EXISTS idx_ai_model_version_provider_model_id ON ai_model_version (provider_model_id);
-- models/notifications/notification_deliveries.rs → notification_delivery
CREATE TABLE IF NOT EXISTS notification_delivery (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
notification_id UUID NOT NULL,
user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
channel TEXT NOT NULL,
destination TEXT NULL,
status TEXT NOT NULL,
provider TEXT NULL,
provider_message_id TEXT NULL,
attempts INTEGER NOT NULL,
last_error TEXT NULL,
scheduled_at TIMESTAMPTZ NULL,
sent_at TIMESTAMPTZ NULL,
delivered_at TIMESTAMPTZ NULL,
failed_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_notification_delivery_notification_id ON notification_delivery (notification_id);
CREATE INDEX IF NOT EXISTS idx_notification_delivery_user_id ON notification_delivery (user_id);
CREATE INDEX IF NOT EXISTS idx_notification_delivery_provider_message_id ON notification_delivery (provider_message_id);
CREATE INDEX IF NOT EXISTS idx_notification_delivery_user_created ON notification_delivery (user_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_notification_delivery_status_created ON notification_delivery (status, created_at DESC);
-- models/notifications/notification_templates.rs → notification_template
CREATE TABLE IF NOT EXISTS notification_template (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
key TEXT NOT NULL,
notification_type TEXT NOT NULL,
channel TEXT NOT NULL,
locale TEXT NOT NULL,
subject_template TEXT NULL,
title_template TEXT NOT NULL,
body_template TEXT NOT NULL,
action_text_template TEXT NULL,
enabled BOOLEAN NOT NULL,
created_by UUID NULL REFERENCES "user"(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
-- models/users/user_appearance.rs → user_appearance
CREATE TABLE IF NOT EXISTS user_appearance (
PRIMARY KEY (user_id),
user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
theme TEXT NOT NULL,
color_scheme TEXT NOT NULL,
density TEXT NOT NULL,
font_size TEXT NOT NULL,
editor_theme TEXT NULL,
markdown_preview BOOLEAN NOT NULL,
reduced_motion BOOLEAN NOT NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_user_appearance_user_created ON user_appearance (user_id, created_at DESC);
-- models/users/user_block.rs → user_block
CREATE TABLE IF NOT EXISTS user_block (
PRIMARY KEY (blocker_id, blocked_id),
blocker_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
blocked_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
reason TEXT NULL,
created_at TIMESTAMPTZ NOT NULL
);
-- models/users/user_device.rs → user_device
CREATE TABLE IF NOT EXISTS user_device (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
device_name TEXT NOT NULL,
device_type TEXT NOT NULL,
fingerprint TEXT NULL,
ip_address TEXT NULL,
user_agent TEXT NULL,
trusted BOOLEAN NOT NULL,
last_seen_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_user_device_user_id ON user_device (user_id);
CREATE INDEX IF NOT EXISTS idx_user_device_user_created ON user_device (user_id, created_at DESC);
-- models/users/user_follow.rs → user_follow
CREATE TABLE IF NOT EXISTS user_follow (
PRIMARY KEY (follower_id, following_id),
follower_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
following_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL
);
-- models/users/user_gpg_key.rs → user_gpg_key
CREATE TABLE IF NOT EXISTS user_gpg_key (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
key_id TEXT NOT NULL,
public_key TEXT NOT NULL,
fingerprint TEXT NOT NULL,
primary_email TEXT NULL,
expires_at TIMESTAMPTZ NULL,
verified_at TIMESTAMPTZ NULL,
revoked_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_user_gpg_key_user_id ON user_gpg_key (user_id);
CREATE INDEX IF NOT EXISTS idx_user_gpg_key_key_id ON user_gpg_key (key_id);
CREATE INDEX IF NOT EXISTS idx_user_gpg_key_user_created ON user_gpg_key (user_id, created_at DESC);
-- models/users/user_mail.rs → user_mail
CREATE TABLE IF NOT EXISTS user_mail (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
email TEXT NOT NULL,
is_primary BOOLEAN NOT NULL,
is_verified BOOLEAN NOT NULL,
verification_token_hash TEXT NULL,
verified_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_user_mail_user_id ON user_mail (user_id);
CREATE INDEX IF NOT EXISTS idx_user_mail_user_created ON user_mail (user_id, created_at DESC);
-- models/users/user_notify_setting.rs → user_notify_setting
CREATE TABLE IF NOT EXISTS user_notify_setting (
PRIMARY KEY (user_id),
user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
email_notifications BOOLEAN NOT NULL,
web_notifications BOOLEAN NOT NULL,
mention_notifications BOOLEAN NOT NULL,
review_notifications BOOLEAN NOT NULL,
security_notifications BOOLEAN NOT NULL,
marketing_emails BOOLEAN NOT NULL,
digest_frequency TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_user_notify_setting_user_created ON user_notify_setting (user_id, created_at DESC);
-- models/users/user_oauth.rs → user_oauth
CREATE TABLE IF NOT EXISTS user_oauth (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
provider TEXT NOT NULL,
provider_user_id TEXT NOT NULL,
provider_username TEXT NULL,
provider_email TEXT NULL,
access_token_ciphertext TEXT NULL,
refresh_token_ciphertext TEXT NULL,
token_expires_at TIMESTAMPTZ NULL,
linked_at TIMESTAMPTZ NOT NULL,
last_used_at TIMESTAMPTZ NULL
);
CREATE INDEX IF NOT EXISTS idx_user_oauth_user_id ON user_oauth (user_id);
CREATE INDEX IF NOT EXISTS idx_user_oauth_provider_user_id ON user_oauth (provider_user_id);
-- models/users/user_password.rs → user_password
CREATE TABLE IF NOT EXISTS user_password (
PRIMARY KEY (user_id),
user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
password_hash TEXT NOT NULL,
password_algo TEXT NOT NULL,
password_salt TEXT NULL,
must_change_password BOOLEAN NOT NULL,
password_updated_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_user_password_user_created ON user_password (user_id, created_at DESC);
-- models/users/user_password_reset.rs → user_password_reset
CREATE TABLE IF NOT EXISTS user_password_reset (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
token_hash TEXT NOT NULL,
requested_ip TEXT NULL,
user_agent TEXT NULL,
expires_at TIMESTAMPTZ NOT NULL,
used_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_user_password_reset_user_id ON user_password_reset (user_id);
CREATE INDEX IF NOT EXISTS idx_user_password_reset_user_created ON user_password_reset (user_id, created_at DESC);
-- models/users/user_personal_access_token.rs → user_personal_access_token
CREATE TABLE IF NOT EXISTS user_personal_access_token (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
name TEXT NOT NULL,
token_hash TEXT NOT NULL,
scopes TEXT[] NOT NULL,
last_used_at TIMESTAMPTZ NULL,
expires_at TIMESTAMPTZ NULL,
revoked_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_user_personal_access_token_user_id ON user_personal_access_token (user_id);
CREATE INDEX IF NOT EXISTS idx_user_personal_access_token_user_created ON user_personal_access_token (user_id, created_at DESC);
-- models/users/user_profile.rs → user_profile
CREATE TABLE IF NOT EXISTS user_profile (
PRIMARY KEY (user_id),
user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
full_name TEXT NULL,
company TEXT NULL,
location TEXT NULL,
website_url TEXT NULL,
twitter_username TEXT NULL,
timezone TEXT NULL,
language TEXT NULL,
profile_readme TEXT NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_user_profile_user_created ON user_profile (user_id, created_at DESC);
-- models/users/user_security_log.rs → user_security_log
CREATE TABLE IF NOT EXISTS user_security_log (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
event_type TEXT NOT NULL,
description TEXT NULL,
ip_address TEXT NULL,
user_agent TEXT NULL,
metadata JSONB NULL,
created_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_user_security_log_user_id ON user_security_log (user_id);
CREATE INDEX IF NOT EXISTS idx_user_security_log_user_created ON user_security_log (user_id, created_at DESC);
-- models/users/user_session.rs → user_session
CREATE TABLE IF NOT EXISTS user_session (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
session_token_hash TEXT NOT NULL,
refresh_token_hash TEXT NULL,
ip_address TEXT NULL,
user_agent TEXT NULL,
last_active_at TIMESTAMPTZ NOT NULL,
expires_at TIMESTAMPTZ NOT NULL,
revoked_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_user_session_user_id ON user_session (user_id);
CREATE INDEX IF NOT EXISTS idx_user_session_user_created ON user_session (user_id, created_at DESC);
-- models/users/user_ssh_key.rs → user_ssh_key
CREATE TABLE IF NOT EXISTS user_ssh_key (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
title TEXT NOT NULL,
public_key TEXT NOT NULL,
fingerprint_sha256 TEXT NOT NULL,
key_type TEXT NOT NULL,
last_used_at TIMESTAMPTZ NULL,
expires_at TIMESTAMPTZ NULL,
revoked_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_user_ssh_key_user_id ON user_ssh_key (user_id);
CREATE INDEX IF NOT EXISTS idx_user_ssh_key_user_created ON user_ssh_key (user_id, created_at DESC);
-- models/workspaces/workspace.rs → workspace
CREATE TABLE IF NOT EXISTS workspace (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
owner_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
slug TEXT NOT NULL,
name TEXT NOT NULL,
description TEXT NULL,
avatar_url TEXT NULL,
visibility TEXT NOT NULL,
plan TEXT NOT NULL,
status TEXT NOT NULL,
default_role TEXT NOT NULL,
is_personal BOOLEAN NOT NULL,
archived_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL,
deleted_at TIMESTAMPTZ NULL
);
CREATE INDEX IF NOT EXISTS idx_workspace_owner_id ON workspace (owner_id);
CREATE INDEX IF NOT EXISTS idx_workspace_status_created ON workspace (status, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_workspace_deleted ON workspace (deleted_at) WHERE deleted_at IS NOT NULL;
-- models/agents/agent.rs → agent
CREATE TABLE IF NOT EXISTS agent (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
owner_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
workspace_id UUID NULL REFERENCES workspace(id) ON DELETE CASCADE,
name TEXT NOT NULL,
slug TEXT NOT NULL,
description TEXT NULL,
avatar_url TEXT NULL,
agent_type TEXT NOT NULL,
status TEXT NOT NULL,
visibility TEXT NOT NULL,
default_model_id UUID NULL REFERENCES ai_model(id) ON DELETE CASCADE,
current_version_id UUID NULL,
system_prompt TEXT NULL,
tools TEXT[] NOT NULL,
tags TEXT[] NOT NULL,
enabled BOOLEAN NOT NULL,
last_run_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL,
deleted_at TIMESTAMPTZ NULL
);
CREATE INDEX IF NOT EXISTS idx_agent_owner_id ON agent (owner_id);
CREATE INDEX IF NOT EXISTS idx_agent_workspace_id ON agent (workspace_id);
CREATE INDEX IF NOT EXISTS idx_agent_default_model_id ON agent (default_model_id);
CREATE INDEX IF NOT EXISTS idx_agent_current_version_id ON agent (current_version_id);
CREATE INDEX IF NOT EXISTS idx_agent_ws_created ON agent (workspace_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_agent_status_created ON agent (status, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_agent_deleted ON agent (deleted_at) WHERE deleted_at IS NOT NULL;
-- models/repos/repo.rs → repo
CREATE TABLE IF NOT EXISTS repo (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
workspace_id UUID NOT NULL REFERENCES workspace(id) ON DELETE CASCADE,
owner_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
slug TEXT NOT NULL,
name TEXT NOT NULL,
description TEXT NULL,
default_branch TEXT NOT NULL,
visibility TEXT NOT NULL,
status TEXT NOT NULL,
is_fork BOOLEAN NOT NULL,
forked_from_repo_id UUID NULL REFERENCES repo(id) ON DELETE CASCADE,
storage_node_ids UUID[] NOT NULL,
primary_storage_node_id UUID NOT NULL,
storage_path TEXT NOT NULL,
git_service TEXT NOT NULL,
archived_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL,
deleted_at TIMESTAMPTZ NULL
);
CREATE INDEX IF NOT EXISTS idx_repo_workspace_id ON repo (workspace_id);
CREATE INDEX IF NOT EXISTS idx_repo_owner_id ON repo (owner_id);
CREATE INDEX IF NOT EXISTS idx_repo_forked_from_repo_id ON repo (forked_from_repo_id);
CREATE INDEX IF NOT EXISTS idx_repo_primary_storage_node_id ON repo (primary_storage_node_id);
CREATE INDEX IF NOT EXISTS idx_repo_ws_created ON repo (workspace_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_repo_status_created ON repo (status, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_repo_deleted ON repo (deleted_at) WHERE deleted_at IS NOT NULL;
-- models/workspaces/workspace_audit_logs.rs → workspace_audit_log
CREATE TABLE IF NOT EXISTS workspace_audit_log (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
workspace_id UUID NOT NULL REFERENCES workspace(id) ON DELETE CASCADE,
actor_id UUID NULL REFERENCES "user"(id) ON DELETE CASCADE,
action TEXT NOT NULL,
target_type TEXT NULL,
target_id UUID NULL,
ip_address TEXT NULL,
user_agent TEXT NULL,
metadata JSONB NULL,
created_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_workspace_audit_log_workspace_id ON workspace_audit_log (workspace_id);
CREATE INDEX IF NOT EXISTS idx_workspace_audit_log_actor_id ON workspace_audit_log (actor_id);
CREATE INDEX IF NOT EXISTS idx_workspace_audit_log_target_id ON workspace_audit_log (target_id);
CREATE INDEX IF NOT EXISTS idx_workspace_audit_log_ws_created ON workspace_audit_log (workspace_id, created_at DESC);
-- models/workspaces/workspace_billing.rs → workspace_billing
CREATE TABLE IF NOT EXISTS workspace_billing (
PRIMARY KEY (workspace_id),
workspace_id UUID NOT NULL REFERENCES workspace(id) ON DELETE CASCADE,
customer_id TEXT NULL,
subscription_id TEXT NULL,
plan TEXT NOT NULL,
billing_email TEXT NULL,
status TEXT NOT NULL,
seats INTEGER NOT NULL,
trial_ends_at TIMESTAMPTZ NULL,
current_period_start TIMESTAMPTZ NULL,
current_period_end TIMESTAMPTZ NULL,
canceled_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_workspace_billing_customer_id ON workspace_billing (customer_id);
CREATE INDEX IF NOT EXISTS idx_workspace_billing_subscription_id ON workspace_billing (subscription_id);
CREATE INDEX IF NOT EXISTS idx_workspace_billing_ws_created ON workspace_billing (workspace_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_workspace_billing_status_created ON workspace_billing (status, created_at DESC);
-- models/workspaces/workspace_custom_branding.rs → workspace_custom_branding
CREATE TABLE IF NOT EXISTS workspace_custom_branding (
PRIMARY KEY (workspace_id),
workspace_id UUID NOT NULL REFERENCES workspace(id) ON DELETE CASCADE,
logo_url TEXT NULL,
favicon_url TEXT NULL,
primary_color TEXT NULL,
accent_color TEXT NULL,
custom_css TEXT NULL,
support_url TEXT NULL,
enabled BOOLEAN NOT NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_workspace_custom_branding_ws_created ON workspace_custom_branding (workspace_id, created_at DESC);
-- models/workspaces/workspace_domains.rs → workspace_domain
CREATE TABLE IF NOT EXISTS workspace_domain (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
workspace_id UUID NOT NULL REFERENCES workspace(id) ON DELETE CASCADE,
domain TEXT NOT NULL,
verification_token_hash TEXT NULL,
is_primary BOOLEAN NOT NULL,
is_verified BOOLEAN NOT NULL,
verified_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
,
CONSTRAINT uq_workspace_domain UNIQUE (workspace_id, domain)
);
CREATE INDEX IF NOT EXISTS idx_workspace_domain_workspace_id ON workspace_domain (workspace_id);
CREATE INDEX IF NOT EXISTS idx_workspace_domain_ws_created ON workspace_domain (workspace_id, created_at DESC);
-- models/workspaces/workspace_integrations.rs → workspace_integration
CREATE TABLE IF NOT EXISTS workspace_integration (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
workspace_id UUID NOT NULL REFERENCES workspace(id) ON DELETE CASCADE,
provider TEXT NOT NULL,
name TEXT NOT NULL,
config JSONB NULL,
secret_ciphertext TEXT NULL,
enabled BOOLEAN NOT NULL,
installed_by UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
last_used_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_workspace_integration_workspace_id ON workspace_integration (workspace_id);
CREATE INDEX IF NOT EXISTS idx_workspace_integration_ws_created ON workspace_integration (workspace_id, created_at DESC);
-- models/workspaces/workspace_invitations.rs → workspace_invitation
CREATE TABLE IF NOT EXISTS workspace_invitation (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
workspace_id UUID NOT NULL REFERENCES workspace(id) ON DELETE CASCADE,
email TEXT NOT NULL,
role TEXT NOT NULL,
token_hash TEXT NOT NULL,
invited_by UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
accepted_by UUID NULL REFERENCES "user"(id) ON DELETE CASCADE,
accepted_at TIMESTAMPTZ NULL,
revoked_at TIMESTAMPTZ NULL,
expires_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_workspace_invitation_workspace_id ON workspace_invitation (workspace_id);
CREATE INDEX IF NOT EXISTS idx_workspace_invitation_ws_created ON workspace_invitation (workspace_id, created_at DESC);
-- models/workspaces/workspace_members.rs → workspace_member
CREATE TABLE IF NOT EXISTS workspace_member (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
workspace_id UUID NOT NULL REFERENCES workspace(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
role TEXT NOT NULL,
status TEXT NOT NULL,
invited_by UUID NULL REFERENCES "user"(id) ON DELETE CASCADE,
joined_at TIMESTAMPTZ NULL,
last_active_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
,
CONSTRAINT uq_workspace_member UNIQUE (workspace_id, user_id)
);
CREATE INDEX IF NOT EXISTS idx_workspace_member_workspace_id ON workspace_member (workspace_id);
CREATE INDEX IF NOT EXISTS idx_workspace_member_user_id ON workspace_member (user_id);
CREATE INDEX IF NOT EXISTS idx_workspace_member_ws_created ON workspace_member (workspace_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_workspace_member_user_created ON workspace_member (user_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_workspace_member_status_created ON workspace_member (status, created_at DESC);
-- models/workspaces/workspace_pending_approvals.rs → workspace_pending_approval
CREATE TABLE IF NOT EXISTS workspace_pending_approval (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
workspace_id UUID NOT NULL REFERENCES workspace(id) ON DELETE CASCADE,
requester_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
request_type TEXT NOT NULL,
status TEXT NOT NULL,
reason TEXT NULL,
reviewed_by UUID NULL REFERENCES "user"(id) ON DELETE CASCADE,
reviewed_at TIMESTAMPTZ NULL,
expires_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_workspace_pending_approval_workspace_id ON workspace_pending_approval (workspace_id);
CREATE INDEX IF NOT EXISTS idx_workspace_pending_approval_requester_id ON workspace_pending_approval (requester_id);
CREATE INDEX IF NOT EXISTS idx_workspace_pending_approval_ws_created ON workspace_pending_approval (workspace_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_workspace_pending_approval_status_created ON workspace_pending_approval (status, created_at DESC);
-- models/workspaces/workspace_settings.rs → workspace_settings
CREATE TABLE IF NOT EXISTS workspace_settings (
PRIMARY KEY (workspace_id),
workspace_id UUID NOT NULL REFERENCES workspace(id) ON DELETE CASCADE,
allow_public_repos BOOLEAN NOT NULL,
allow_member_invites BOOLEAN NOT NULL,
require_two_factor BOOLEAN NOT NULL,
default_repo_visibility TEXT NOT NULL,
default_branch_name TEXT NOT NULL,
issue_tracking_enabled BOOLEAN NOT NULL,
pull_requests_enabled BOOLEAN NOT NULL,
wiki_enabled BOOLEAN NOT NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_workspace_settings_ws_created ON workspace_settings (workspace_id, created_at DESC);
-- models/workspaces/workspace_stats.rs → workspace_stats
CREATE TABLE IF NOT EXISTS workspace_stats (
PRIMARY KEY (workspace_id),
workspace_id UUID NOT NULL REFERENCES workspace(id) ON DELETE CASCADE,
members_count BIGINT NOT NULL,
repos_count BIGINT NOT NULL,
issues_count BIGINT NOT NULL,
pull_requests_count BIGINT NOT NULL,
storage_bytes BIGINT NOT NULL,
bandwidth_bytes BIGINT NOT NULL,
build_minutes_used BIGINT NOT NULL,
last_activity_at TIMESTAMPTZ NULL,
updated_at TIMESTAMPTZ NOT NULL
);
-- models/workspaces/workspace_webhooks.rs → workspace_webhook
CREATE TABLE IF NOT EXISTS workspace_webhook (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
workspace_id UUID NOT NULL REFERENCES workspace(id) ON DELETE CASCADE,
url TEXT NOT NULL,
secret_ciphertext TEXT NULL,
events TEXT[] NOT NULL,
active BOOLEAN NOT NULL,
last_delivery_status TEXT NULL,
last_delivery_at TIMESTAMPTZ NULL,
created_by UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_workspace_webhook_workspace_id ON workspace_webhook (workspace_id);
CREATE INDEX IF NOT EXISTS idx_workspace_webhook_ws_created ON workspace_webhook (workspace_id, created_at DESC);
-- models/agents/agent_versions.rs → agent_version
CREATE TABLE IF NOT EXISTS agent_version (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
agent_id UUID NOT NULL REFERENCES agent(id) ON DELETE CASCADE,
version TEXT NOT NULL,
name TEXT NULL,
description TEXT NULL,
model_id UUID NULL,
system_prompt TEXT NOT NULL,
instructions TEXT NULL,
tools TEXT[] NOT NULL,
config JSONB NULL,
changelog TEXT NULL,
stable BOOLEAN NOT NULL,
published_by UUID NOT NULL,
published_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_agent_version_agent_id ON agent_version (agent_id);
CREATE INDEX IF NOT EXISTS idx_agent_version_model_id ON agent_version (model_id);
-- models/agents/agent_event_subscriptions.rs → agent_event_subscription
CREATE TABLE IF NOT EXISTS agent_event_subscription (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
agent_id UUID NOT NULL REFERENCES agent(id) ON DELETE CASCADE,
workspace_id UUID NULL REFERENCES workspace(id) ON DELETE CASCADE,
repo_id UUID NULL REFERENCES repo(id) ON DELETE CASCADE,
event_type TEXT NOT NULL,
filters JSONB NULL,
enabled BOOLEAN NOT NULL,
created_by UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_agent_event_subscription_agent_id ON agent_event_subscription (agent_id);
CREATE INDEX IF NOT EXISTS idx_agent_event_subscription_workspace_id ON agent_event_subscription (workspace_id);
CREATE INDEX IF NOT EXISTS idx_agent_event_subscription_repo_id ON agent_event_subscription (repo_id);
CREATE INDEX IF NOT EXISTS idx_agent_event_subscription_repo_created ON agent_event_subscription (repo_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_agent_event_subscription_ws_created ON agent_event_subscription (workspace_id, created_at DESC);
-- models/agents/agent_schedules.rs → agent_schedule
CREATE TABLE IF NOT EXISTS agent_schedule (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
agent_id UUID NOT NULL REFERENCES agent(id) ON DELETE CASCADE,
workspace_id UUID NULL REFERENCES workspace(id) ON DELETE CASCADE,
repo_id UUID NULL REFERENCES repo(id) ON DELETE CASCADE,
name TEXT NOT NULL,
cron_expr TEXT NOT NULL,
timezone TEXT NOT NULL,
payload JSONB NULL,
enabled BOOLEAN NOT NULL,
last_run_at TIMESTAMPTZ NULL,
next_run_at TIMESTAMPTZ NULL,
created_by UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_agent_schedule_agent_id ON agent_schedule (agent_id);
CREATE INDEX IF NOT EXISTS idx_agent_schedule_workspace_id ON agent_schedule (workspace_id);
CREATE INDEX IF NOT EXISTS idx_agent_schedule_repo_id ON agent_schedule (repo_id);
CREATE INDEX IF NOT EXISTS idx_agent_schedule_repo_created ON agent_schedule (repo_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_agent_schedule_ws_created ON agent_schedule (workspace_id, created_at DESC);
-- models/agents/agent_workspace_bindings.rs → agent_workspace_binding
CREATE TABLE IF NOT EXISTS agent_workspace_binding (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
agent_id UUID NOT NULL REFERENCES agent(id) ON DELETE CASCADE,
workspace_id UUID NOT NULL REFERENCES workspace(id) ON DELETE CASCADE,
repo_id UUID NULL REFERENCES repo(id) ON DELETE CASCADE,
role TEXT NOT NULL,
permissions TEXT[] NOT NULL,
enabled BOOLEAN NOT NULL,
bound_by UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
,
CONSTRAINT uq_agent_workspace_binding UNIQUE (agent_id, workspace_id)
);
CREATE INDEX IF NOT EXISTS idx_agent_workspace_binding_agent_id ON agent_workspace_binding (agent_id);
CREATE INDEX IF NOT EXISTS idx_agent_workspace_binding_workspace_id ON agent_workspace_binding (workspace_id);
CREATE INDEX IF NOT EXISTS idx_agent_workspace_binding_repo_id ON agent_workspace_binding (repo_id);
CREATE INDEX IF NOT EXISTS idx_agent_workspace_binding_repo_created ON agent_workspace_binding (repo_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_agent_workspace_binding_ws_created ON agent_workspace_binding (workspace_id, created_at DESC);
-- models/channels/channel.rs → channel
CREATE TABLE IF NOT EXISTS channel (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
workspace_id UUID NOT NULL REFERENCES workspace(id) ON DELETE CASCADE,
repo_id UUID NULL REFERENCES repo(id) ON DELETE CASCADE,
created_by UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
name TEXT NOT NULL,
topic TEXT NULL,
description TEXT NULL,
channel_type TEXT NOT NULL,
visibility TEXT NOT NULL,
archived BOOLEAN NOT NULL,
read_only BOOLEAN NOT NULL,
last_message_id UUID NULL,
last_message_at TIMESTAMPTZ NULL,
archived_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL,
deleted_at TIMESTAMPTZ NULL
);
CREATE INDEX IF NOT EXISTS idx_channel_workspace_id ON channel (workspace_id);
CREATE INDEX IF NOT EXISTS idx_channel_repo_id ON channel (repo_id);
CREATE INDEX IF NOT EXISTS idx_channel_last_message_id ON channel (last_message_id);
CREATE INDEX IF NOT EXISTS idx_channel_repo_created ON channel (repo_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_channel_ws_created ON channel (workspace_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_channel_deleted ON channel (deleted_at) WHERE deleted_at IS NOT NULL;
-- models/issues/issue.rs → issue
CREATE TABLE IF NOT EXISTS issue (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
repo_id UUID NOT NULL REFERENCES repo(id) ON DELETE CASCADE,
author_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
number BIGINT NOT NULL,
title TEXT NOT NULL,
body TEXT NULL,
state TEXT NOT NULL,
priority TEXT NOT NULL,
visibility TEXT NOT NULL,
locked BOOLEAN NOT NULL,
closed_by UUID NULL REFERENCES "user"(id) ON DELETE CASCADE,
closed_at TIMESTAMPTZ NULL,
due_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL,
deleted_at TIMESTAMPTZ NULL
);
CREATE INDEX IF NOT EXISTS idx_issue_author_id ON issue (author_id);
CREATE INDEX IF NOT EXISTS idx_issue_deleted ON issue (deleted_at) WHERE deleted_at IS NOT NULL;
DO $$ BEGIN
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'issue' AND column_name = 'repo_id') THEN
CREATE INDEX IF NOT EXISTS idx_issue_repo_id ON issue (repo_id);
CREATE INDEX IF NOT EXISTS idx_issue_repo_created ON issue (repo_id, created_at DESC);
END IF;
END $$;
-- models/issues/issue_labels.rs → issue_label
CREATE TABLE IF NOT EXISTS issue_label (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
repo_id UUID NOT NULL REFERENCES repo(id) ON DELETE CASCADE,
name TEXT NOT NULL,
color TEXT NOT NULL,
description TEXT NULL,
created_by UUID NULL REFERENCES "user"(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_issue_label_repo_id ON issue_label (repo_id);
CREATE INDEX IF NOT EXISTS idx_issue_label_repo_created ON issue_label (repo_id, created_at DESC);
-- models/issues/issue_milestones.rs → issue_milestone
CREATE TABLE IF NOT EXISTS issue_milestone (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
repo_id UUID NOT NULL REFERENCES repo(id) ON DELETE CASCADE,
title TEXT NOT NULL,
description TEXT NULL,
state TEXT NOT NULL,
due_at TIMESTAMPTZ NULL,
closed_at TIMESTAMPTZ NULL,
created_by UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_issue_milestone_repo_id ON issue_milestone (repo_id);
CREATE INDEX IF NOT EXISTS idx_issue_milestone_repo_created ON issue_milestone (repo_id, created_at DESC);
-- models/issues/issue_templates.rs → issue_template
CREATE TABLE IF NOT EXISTS issue_template (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
repo_id UUID NOT NULL REFERENCES repo(id) ON DELETE CASCADE,
name TEXT NOT NULL,
description TEXT NULL,
title_template TEXT NULL,
body_template TEXT NOT NULL,
labels TEXT[] NOT NULL,
active BOOLEAN NOT NULL,
created_by UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_issue_template_repo_id ON issue_template (repo_id);
CREATE INDEX IF NOT EXISTS idx_issue_template_repo_created ON issue_template (repo_id, created_at DESC);
-- models/notifications/notification_blocks.rs → notification_block
CREATE TABLE IF NOT EXISTS notification_block (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
workspace_id UUID NULL REFERENCES workspace(id) ON DELETE CASCADE,
repo_id UUID NULL REFERENCES repo(id) ON DELETE CASCADE,
target_type TEXT NOT NULL,
target_id UUID NULL,
notification_type TEXT NULL,
channel TEXT NULL,
reason TEXT NULL,
expires_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_notification_block_user_id ON notification_block (user_id);
CREATE INDEX IF NOT EXISTS idx_notification_block_workspace_id ON notification_block (workspace_id);
CREATE INDEX IF NOT EXISTS idx_notification_block_repo_id ON notification_block (repo_id);
CREATE INDEX IF NOT EXISTS idx_notification_block_target_id ON notification_block (target_id);
CREATE INDEX IF NOT EXISTS idx_notification_block_repo_created ON notification_block (repo_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_notification_block_ws_created ON notification_block (workspace_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_notification_block_user_created ON notification_block (user_id, created_at DESC);
-- models/notifications/notification_subscriptions.rs → notification_subscription
CREATE TABLE IF NOT EXISTS notification_subscription (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
workspace_id UUID NULL REFERENCES workspace(id) ON DELETE CASCADE,
repo_id UUID NULL REFERENCES repo(id) ON DELETE CASCADE,
target_type TEXT NOT NULL,
target_id UUID NULL,
event_types TEXT[] NOT NULL,
channels TEXT[] NOT NULL,
level TEXT NOT NULL,
muted BOOLEAN NOT NULL,
muted_until TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_notification_subscription_user_id ON notification_subscription (user_id);
CREATE INDEX IF NOT EXISTS idx_notification_subscription_workspace_id ON notification_subscription (workspace_id);
CREATE INDEX IF NOT EXISTS idx_notification_subscription_repo_id ON notification_subscription (repo_id);
CREATE INDEX IF NOT EXISTS idx_notification_subscription_target_id ON notification_subscription (target_id);
CREATE INDEX IF NOT EXISTS idx_notification_subscription_repo_created ON notification_subscription (repo_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_notification_subscription_ws_created ON notification_subscription (workspace_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_notification_subscription_user_created ON notification_subscription (user_id, created_at DESC);
-- models/prs/pr_labels.rs → pr_label
CREATE TABLE IF NOT EXISTS pr_label (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
repo_id UUID NOT NULL REFERENCES repo(id) ON DELETE CASCADE,
name TEXT NOT NULL,
color TEXT NOT NULL,
description TEXT NULL,
created_by UUID NULL REFERENCES "user"(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_pr_label_repo_id ON pr_label (repo_id);
CREATE INDEX IF NOT EXISTS idx_pr_label_repo_created ON pr_label (repo_id, created_at DESC);
-- models/prs/pull_request.rs → pull_request
CREATE TABLE IF NOT EXISTS pull_request (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
repo_id UUID NOT NULL REFERENCES repo(id) ON DELETE CASCADE,
author_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
number BIGINT NOT NULL,
title TEXT NOT NULL,
body TEXT NULL,
state TEXT NOT NULL,
source_repo_id UUID NOT NULL REFERENCES repo(id) ON DELETE CASCADE,
source_branch TEXT NOT NULL,
target_repo_id UUID NOT NULL REFERENCES repo(id) ON DELETE CASCADE,
target_branch TEXT NOT NULL,
base_commit_sha TEXT NULL,
head_commit_sha TEXT NOT NULL,
merge_commit_sha TEXT NULL,
draft BOOLEAN NOT NULL,
locked BOOLEAN NOT NULL,
merged_by UUID NULL REFERENCES "user"(id) ON DELETE CASCADE,
merged_at TIMESTAMPTZ NULL,
closed_by UUID NULL REFERENCES "user"(id) ON DELETE CASCADE,
closed_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL,
deleted_at TIMESTAMPTZ NULL
);
CREATE INDEX IF NOT EXISTS idx_pull_request_repo_id ON pull_request (repo_id);
CREATE INDEX IF NOT EXISTS idx_pull_request_author_id ON pull_request (author_id);
CREATE INDEX IF NOT EXISTS idx_pull_request_source_repo_id ON pull_request (source_repo_id);
CREATE INDEX IF NOT EXISTS idx_pull_request_target_repo_id ON pull_request (target_repo_id);
CREATE INDEX IF NOT EXISTS idx_pull_request_repo_created ON pull_request (repo_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_pull_request_deleted ON pull_request (deleted_at) WHERE deleted_at IS NOT NULL;
-- models/repos/repo_deploy_keys.rs → repo_deploy_key
CREATE TABLE IF NOT EXISTS repo_deploy_key (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
repo_id UUID NOT NULL REFERENCES repo(id) ON DELETE CASCADE,
title TEXT NOT NULL,
public_key TEXT NOT NULL,
fingerprint_sha256 TEXT NOT NULL,
key_type TEXT NOT NULL,
read_only BOOLEAN NOT NULL,
last_used_at TIMESTAMPTZ NULL,
expires_at TIMESTAMPTZ NULL,
revoked_at TIMESTAMPTZ NULL,
created_by UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_repo_deploy_key_repo_id ON repo_deploy_key (repo_id);
CREATE INDEX IF NOT EXISTS idx_repo_deploy_key_repo_created ON repo_deploy_key (repo_id, created_at DESC);
-- models/repos/repo_invitations.rs → repo_invitation
CREATE TABLE IF NOT EXISTS repo_invitation (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
repo_id UUID NOT NULL REFERENCES repo(id) ON DELETE CASCADE,
email TEXT NOT NULL,
role TEXT NOT NULL,
token_hash TEXT NOT NULL,
invited_by UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
accepted_by UUID NULL REFERENCES "user"(id) ON DELETE CASCADE,
accepted_at TIMESTAMPTZ NULL,
revoked_at TIMESTAMPTZ NULL,
expires_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_repo_invitation_repo_id ON repo_invitation (repo_id);
CREATE INDEX IF NOT EXISTS idx_repo_invitation_repo_created ON repo_invitation (repo_id, created_at DESC);
-- models/repos/repo_members.rs → repo_member
CREATE TABLE IF NOT EXISTS repo_member (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
repo_id UUID NOT NULL REFERENCES repo(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
role TEXT NOT NULL,
status TEXT NOT NULL,
invited_by UUID NULL REFERENCES "user"(id) ON DELETE CASCADE,
joined_at TIMESTAMPTZ NULL,
last_active_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
,
CONSTRAINT uq_repo_member UNIQUE (repo_id, user_id)
);
CREATE INDEX IF NOT EXISTS idx_repo_member_repo_id ON repo_member (repo_id);
CREATE INDEX IF NOT EXISTS idx_repo_member_user_id ON repo_member (user_id);
CREATE INDEX IF NOT EXISTS idx_repo_member_repo_created ON repo_member (repo_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_repo_member_user_created ON repo_member (user_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_repo_member_status_created ON repo_member (status, created_at DESC);
-- models/repos/repo_push_commit.rs → repo_push_commit
CREATE TABLE IF NOT EXISTS repo_push_commit (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
repo_id UUID NOT NULL REFERENCES repo(id) ON DELETE CASCADE,
pusher_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
branch_name TEXT NOT NULL,
old_commit_sha TEXT NULL,
latest_commit_sha TEXT NOT NULL,
commit_shas TEXT[] NOT NULL,
commit_count INTEGER NOT NULL,
push_status TEXT NOT NULL,
pushed_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_repo_push_commit_repo_id ON repo_push_commit (repo_id);
CREATE INDEX IF NOT EXISTS idx_repo_push_commit_pusher_id ON repo_push_commit (pusher_id);
CREATE INDEX IF NOT EXISTS idx_repo_push_commit_repo_created ON repo_push_commit (repo_id, created_at DESC);
-- models/repos/repo_push_lock.rs → repo_push_lock
CREATE TABLE IF NOT EXISTS repo_push_lock (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
repo_id UUID NOT NULL REFERENCES repo(id) ON DELETE CASCADE,
pusher_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
ref_name TEXT NOT NULL,
status TEXT NOT NULL,
queue_position INTEGER NOT NULL,
queued_at TIMESTAMPTZ NOT NULL,
started_at TIMESTAMPTZ NULL,
finished_at TIMESTAMPTZ NULL,
storage_node_id UUID NULL,
lease_token TEXT NULL,
error_message TEXT NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_repo_push_lock_repo_id ON repo_push_lock (repo_id);
CREATE INDEX IF NOT EXISTS idx_repo_push_lock_pusher_id ON repo_push_lock (pusher_id);
CREATE INDEX IF NOT EXISTS idx_repo_push_lock_storage_node_id ON repo_push_lock (storage_node_id);
CREATE INDEX IF NOT EXISTS idx_repo_push_lock_repo_created ON repo_push_lock (repo_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_repo_push_lock_status_created ON repo_push_lock (status, created_at DESC);
-- models/repos/repo_stars.rs → repo_star
CREATE TABLE IF NOT EXISTS repo_star (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
repo_id UUID NOT NULL REFERENCES repo(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL
,
CONSTRAINT uq_repo_star UNIQUE (repo_id, user_id)
);
CREATE INDEX IF NOT EXISTS idx_repo_star_repo_id ON repo_star (repo_id);
CREATE INDEX IF NOT EXISTS idx_repo_star_user_id ON repo_star (user_id);
CREATE INDEX IF NOT EXISTS idx_repo_star_repo_created ON repo_star (repo_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_repo_star_user_created ON repo_star (user_id, created_at DESC);
-- models/repos/repo_stats.rs → repo_stats
CREATE TABLE IF NOT EXISTS repo_stats (
PRIMARY KEY (repo_id),
repo_id UUID NOT NULL REFERENCES repo(id) ON DELETE CASCADE,
stars_count BIGINT NOT NULL,
watchers_count BIGINT NOT NULL,
forks_count BIGINT NOT NULL,
branches_count BIGINT NOT NULL,
tags_count BIGINT NOT NULL,
commits_count BIGINT NOT NULL,
releases_count BIGINT NOT NULL,
open_issues_count BIGINT NOT NULL,
open_pull_requests_count BIGINT NOT NULL,
size_bytes BIGINT NOT NULL,
last_push_at TIMESTAMPTZ NULL,
updated_at TIMESTAMPTZ NOT NULL
);
-- models/repos/repo_tags.rs → repo_tag
CREATE TABLE IF NOT EXISTS repo_tag (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
repo_id UUID NOT NULL REFERENCES repo(id) ON DELETE CASCADE,
name TEXT NOT NULL,
target_commit_sha TEXT NOT NULL,
tagger_id UUID NULL REFERENCES "user"(id) ON DELETE CASCADE,
message TEXT NULL,
signed BOOLEAN NOT NULL,
created_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_repo_tag_repo_id ON repo_tag (repo_id);
CREATE INDEX IF NOT EXISTS idx_repo_tag_tagger_id ON repo_tag (tagger_id);
CREATE INDEX IF NOT EXISTS idx_repo_tag_repo_created ON repo_tag (repo_id, created_at DESC);
-- models/repos/repo_watches.rs → repo_watch
CREATE TABLE IF NOT EXISTS repo_watch (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
repo_id UUID NOT NULL REFERENCES repo(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
level TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
,
CONSTRAINT uq_repo_watch UNIQUE (repo_id, user_id)
);
CREATE INDEX IF NOT EXISTS idx_repo_watch_repo_id ON repo_watch (repo_id);
CREATE INDEX IF NOT EXISTS idx_repo_watch_user_id ON repo_watch (user_id);
CREATE INDEX IF NOT EXISTS idx_repo_watch_repo_created ON repo_watch (repo_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_repo_watch_user_created ON repo_watch (user_id, created_at DESC);
-- models/repos/repo_webhooks.rs → repo_webhook
CREATE TABLE IF NOT EXISTS repo_webhook (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
repo_id UUID NOT NULL REFERENCES repo(id) ON DELETE CASCADE,
url TEXT NOT NULL,
secret_ciphertext TEXT NULL,
events TEXT[] NOT NULL,
active BOOLEAN NOT NULL,
last_delivery_status TEXT NULL,
last_delivery_at TIMESTAMPTZ NULL,
created_by UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_repo_webhook_repo_id ON repo_webhook (repo_id);
CREATE INDEX IF NOT EXISTS idx_repo_webhook_repo_created ON repo_webhook (repo_id, created_at DESC);
-- models/channels/channel_events.rs → channel_event
CREATE TABLE IF NOT EXISTS channel_event (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
channel_id UUID NOT NULL REFERENCES channel(id) ON DELETE CASCADE,
actor_id UUID NULL REFERENCES "user"(id) ON DELETE CASCADE,
event_type TEXT NOT NULL,
target_type TEXT NULL,
target_id UUID NULL,
old_value JSONB NULL,
new_value JSONB NULL,
metadata JSONB NULL,
created_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_channel_event_channel_id ON channel_event (channel_id);
CREATE INDEX IF NOT EXISTS idx_channel_event_actor_id ON channel_event (actor_id);
CREATE INDEX IF NOT EXISTS idx_channel_event_target_id ON channel_event (target_id);
-- models/channels/channel_invitations.rs → channel_invitation
CREATE TABLE IF NOT EXISTS channel_invitation (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
channel_id UUID NOT NULL REFERENCES channel(id) ON DELETE CASCADE,
workspace_id UUID NOT NULL REFERENCES workspace(id) ON DELETE CASCADE,
invited_user_id UUID NULL REFERENCES "user"(id) ON DELETE CASCADE,
email TEXT NULL,
role TEXT NOT NULL,
token_hash TEXT NOT NULL,
invited_by UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
accepted_at TIMESTAMPTZ NULL,
revoked_at TIMESTAMPTZ NULL,
expires_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_channel_invitation_channel_id ON channel_invitation (channel_id);
CREATE INDEX IF NOT EXISTS idx_channel_invitation_workspace_id ON channel_invitation (workspace_id);
CREATE INDEX IF NOT EXISTS idx_channel_invitation_invited_user_id ON channel_invitation (invited_user_id);
CREATE INDEX IF NOT EXISTS idx_channel_invitation_ws_created ON channel_invitation (workspace_id, created_at DESC);
-- models/channels/channel_member_roles.rs → channel_member_role
CREATE TABLE IF NOT EXISTS channel_member_role (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
channel_id UUID NOT NULL REFERENCES channel(id) ON DELETE CASCADE,
name TEXT NOT NULL,
description TEXT NULL,
permissions TEXT[] NOT NULL,
assignable BOOLEAN NOT NULL,
created_by UUID NULL REFERENCES "user"(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_channel_member_role_channel_id ON channel_member_role (channel_id);
-- models/channels/channel_repo_links.rs → channel_repo_link
CREATE TABLE IF NOT EXISTS channel_repo_link (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
channel_id UUID NOT NULL REFERENCES channel(id) ON DELETE CASCADE,
repo_id UUID NOT NULL REFERENCES repo(id) ON DELETE CASCADE,
link_type TEXT NOT NULL,
notify_events TEXT[] NOT NULL,
active BOOLEAN NOT NULL,
created_by UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
,
CONSTRAINT uq_channel_repo_link UNIQUE (repo_id, channel_id)
);
CREATE INDEX IF NOT EXISTS idx_channel_repo_link_channel_id ON channel_repo_link (channel_id);
CREATE INDEX IF NOT EXISTS idx_channel_repo_link_repo_id ON channel_repo_link (repo_id);
CREATE INDEX IF NOT EXISTS idx_channel_repo_link_repo_created ON channel_repo_link (repo_id, created_at DESC);
-- models/channels/channel_slash_commands.rs → channel_slash_command
CREATE TABLE IF NOT EXISTS channel_slash_command (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
channel_id UUID NULL REFERENCES channel(id) ON DELETE CASCADE,
workspace_id UUID NOT NULL REFERENCES workspace(id) ON DELETE CASCADE,
command TEXT NOT NULL,
description TEXT NULL,
request_url TEXT NOT NULL,
secret_ciphertext TEXT NULL,
scopes TEXT[] NOT NULL,
enabled BOOLEAN NOT NULL,
created_by UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_channel_slash_command_channel_id ON channel_slash_command (channel_id);
CREATE INDEX IF NOT EXISTS idx_channel_slash_command_workspace_id ON channel_slash_command (workspace_id);
CREATE INDEX IF NOT EXISTS idx_channel_slash_command_ws_created ON channel_slash_command (workspace_id, created_at DESC);
-- models/channels/channel_stats.rs → channel_stats
CREATE TABLE IF NOT EXISTS channel_stats (
PRIMARY KEY (channel_id),
channel_id UUID NOT NULL REFERENCES channel(id) ON DELETE CASCADE,
members_count BIGINT NOT NULL,
messages_count BIGINT NOT NULL,
threads_count BIGINT NOT NULL,
reactions_count BIGINT NOT NULL,
mentions_count BIGINT NOT NULL,
files_count BIGINT NOT NULL,
last_activity_at TIMESTAMPTZ NULL,
updated_at TIMESTAMPTZ NOT NULL
);
-- models/channels/channel_webhooks.rs → channel_webhook
CREATE TABLE IF NOT EXISTS channel_webhook (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
channel_id UUID NOT NULL REFERENCES channel(id) ON DELETE CASCADE,
name TEXT NOT NULL,
url TEXT NOT NULL,
secret_ciphertext TEXT NULL,
events TEXT[] NOT NULL,
active BOOLEAN NOT NULL,
last_delivery_status TEXT NULL,
last_delivery_at TIMESTAMPTZ NULL,
created_by UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_channel_webhook_channel_id ON channel_webhook (channel_id);
-- models/channels/message.rs → message
CREATE TABLE IF NOT EXISTS message (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
channel_id UUID NOT NULL REFERENCES channel(id) ON DELETE CASCADE,
author_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
thread_id UUID NULL,
reply_to_message_id UUID NULL,
message_type TEXT NOT NULL,
body TEXT NOT NULL,
metadata JSONB NULL,
pinned BOOLEAN NOT NULL,
system BOOLEAN NOT NULL,
edited_at TIMESTAMPTZ NULL,
deleted_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_message_channel_id ON message (channel_id);
CREATE INDEX IF NOT EXISTS idx_message_author_id ON message (author_id);
CREATE INDEX IF NOT EXISTS idx_message_thread_id ON message (thread_id);
CREATE INDEX IF NOT EXISTS idx_message_reply_to_message_id ON message (reply_to_message_id);
CREATE INDEX IF NOT EXISTS idx_message_deleted ON message (deleted_at) WHERE deleted_at IS NOT NULL;
-- models/issues/issue_assignees.rs → issue_assignee
CREATE TABLE IF NOT EXISTS issue_assignee (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
issue_id UUID NOT NULL REFERENCES issue(id) ON DELETE CASCADE,
assignee_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
assigned_by UUID NULL REFERENCES "user"(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL
,
CONSTRAINT uq_issue_assignee UNIQUE (issue_id, assignee_id)
);
CREATE INDEX IF NOT EXISTS idx_issue_assignee_issue_id ON issue_assignee (issue_id);
CREATE INDEX IF NOT EXISTS idx_issue_assignee_assignee_id ON issue_assignee (assignee_id);
-- models/issues/issue_comments.rs → issue_comment
CREATE TABLE IF NOT EXISTS issue_comment (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
issue_id UUID NOT NULL REFERENCES issue(id) ON DELETE CASCADE,
author_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
body TEXT NOT NULL,
reply_to_comment_id UUID NULL,
edited_at TIMESTAMPTZ NULL,
deleted_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_issue_comment_issue_id ON issue_comment (issue_id);
CREATE INDEX IF NOT EXISTS idx_issue_comment_author_id ON issue_comment (author_id);
CREATE INDEX IF NOT EXISTS idx_issue_comment_reply_to_comment_id ON issue_comment (reply_to_comment_id);
CREATE INDEX IF NOT EXISTS idx_issue_comment_deleted ON issue_comment (deleted_at) WHERE deleted_at IS NOT NULL;
-- models/issues/issue_events.rs → issue_event
CREATE TABLE IF NOT EXISTS issue_event (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
issue_id UUID NOT NULL REFERENCES issue(id) ON DELETE CASCADE,
actor_id UUID NULL REFERENCES "user"(id) ON DELETE CASCADE,
event_type TEXT NOT NULL,
old_value JSONB NULL,
new_value JSONB NULL,
metadata JSONB NULL,
created_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_issue_event_issue_id ON issue_event (issue_id);
CREATE INDEX IF NOT EXISTS idx_issue_event_actor_id ON issue_event (actor_id);
-- models/issues/issue_reminder.rs → issue_reminder
CREATE TABLE IF NOT EXISTS issue_reminder (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
issue_id UUID NOT NULL REFERENCES issue(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
remind_at TIMESTAMPTZ NOT NULL,
message TEXT NULL,
sent_at TIMESTAMPTZ NULL,
canceled_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_issue_reminder_issue_id ON issue_reminder (issue_id);
CREATE INDEX IF NOT EXISTS idx_issue_reminder_user_id ON issue_reminder (user_id);
CREATE INDEX IF NOT EXISTS idx_issue_reminder_user_created ON issue_reminder (user_id, created_at DESC);
-- models/issues/issue_stats.rs → issue_stats
CREATE TABLE IF NOT EXISTS issue_stats (
PRIMARY KEY (issue_id),
issue_id UUID NOT NULL REFERENCES issue(id) ON DELETE CASCADE,
comments_count BIGINT NOT NULL,
reactions_count BIGINT NOT NULL,
assignees_count BIGINT NOT NULL,
labels_count BIGINT NOT NULL,
subscribers_count BIGINT NOT NULL,
last_commented_at TIMESTAMPTZ NULL,
updated_at TIMESTAMPTZ NOT NULL
);
-- models/issues/issue_subscribers.rs → issue_subscriber
CREATE TABLE IF NOT EXISTS issue_subscriber (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
issue_id UUID NOT NULL REFERENCES issue(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
reason TEXT NOT NULL,
muted BOOLEAN NOT NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
,
CONSTRAINT uq_issue_subscriber UNIQUE (issue_id, user_id)
);
CREATE INDEX IF NOT EXISTS idx_issue_subscriber_issue_id ON issue_subscriber (issue_id);
CREATE INDEX IF NOT EXISTS idx_issue_subscriber_user_id ON issue_subscriber (user_id);
CREATE INDEX IF NOT EXISTS idx_issue_subscriber_user_created ON issue_subscriber (user_id, created_at DESC);
-- models/issues/issue_label_relations.rs → issue_label_relation
CREATE TABLE IF NOT EXISTS issue_label_relation (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
issue_id UUID NOT NULL REFERENCES issue(id) ON DELETE CASCADE,
label_id UUID NOT NULL REFERENCES issue_label(id) ON DELETE CASCADE,
created_by UUID NULL REFERENCES "user"(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_issue_label_relation_issue_id ON issue_label_relation (issue_id);
CREATE INDEX IF NOT EXISTS idx_issue_label_relation_label_id ON issue_label_relation (label_id);
-- models/agents/agent_executions.rs → agent_execution
CREATE TABLE IF NOT EXISTS agent_execution (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
agent_id UUID NOT NULL REFERENCES agent(id) ON DELETE CASCADE,
agent_version_id UUID NULL REFERENCES agent_version(id) ON DELETE CASCADE,
workspace_id UUID NULL REFERENCES workspace(id) ON DELETE CASCADE,
repo_id UUID NULL REFERENCES repo(id) ON DELETE CASCADE,
issue_id UUID NULL REFERENCES issue(id) ON DELETE CASCADE,
pull_request_id UUID NULL REFERENCES pull_request(id) ON DELETE CASCADE,
triggered_by UUID NULL REFERENCES "user"(id) ON DELETE CASCADE,
trigger_type TEXT NOT NULL,
status TEXT NOT NULL,
input JSONB NULL,
output JSONB NULL,
error_message TEXT NULL,
started_at TIMESTAMPTZ NULL,
finished_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_agent_execution_agent_id ON agent_execution (agent_id);
CREATE INDEX IF NOT EXISTS idx_agent_execution_agent_version_id ON agent_execution (agent_version_id);
CREATE INDEX IF NOT EXISTS idx_agent_execution_workspace_id ON agent_execution (workspace_id);
CREATE INDEX IF NOT EXISTS idx_agent_execution_repo_id ON agent_execution (repo_id);
CREATE INDEX IF NOT EXISTS idx_agent_execution_issue_id ON agent_execution (issue_id);
CREATE INDEX IF NOT EXISTS idx_agent_execution_pull_request_id ON agent_execution (pull_request_id);
CREATE INDEX IF NOT EXISTS idx_agent_execution_repo_created ON agent_execution (repo_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_agent_execution_ws_created ON agent_execution (workspace_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_agent_execution_status_created ON agent_execution (status, created_at DESC);
-- models/issues/issue_pr_relations.rs → issue_pr_relation
CREATE TABLE IF NOT EXISTS issue_pr_relation (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
issue_id UUID NOT NULL REFERENCES issue(id) ON DELETE CASCADE,
pull_request_id UUID NOT NULL REFERENCES pull_request(id) ON DELETE CASCADE,
relation_type TEXT NOT NULL,
created_by UUID NULL REFERENCES "user"(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL
,
CONSTRAINT uq_issue_pr_relation UNIQUE (issue_id, pull_request_id)
);
CREATE INDEX IF NOT EXISTS idx_issue_pr_relation_issue_id ON issue_pr_relation (issue_id);
CREATE INDEX IF NOT EXISTS idx_issue_pr_relation_pull_request_id ON issue_pr_relation (pull_request_id);
-- models/prs/pr_assignees.rs → pr_assignee
CREATE TABLE IF NOT EXISTS pr_assignee (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
pull_request_id UUID NOT NULL REFERENCES pull_request(id) ON DELETE CASCADE,
assignee_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
assigned_by UUID NULL REFERENCES "user"(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL
,
CONSTRAINT uq_pr_assignee UNIQUE (pull_request_id, assignee_id)
);
CREATE INDEX IF NOT EXISTS idx_pr_assignee_pull_request_id ON pr_assignee (pull_request_id);
CREATE INDEX IF NOT EXISTS idx_pr_assignee_assignee_id ON pr_assignee (assignee_id);
-- models/prs/pr_check_runs.rs → pr_check_run
CREATE TABLE IF NOT EXISTS pr_check_run (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
pull_request_id UUID NOT NULL REFERENCES pull_request(id) ON DELETE CASCADE,
commit_sha TEXT NOT NULL,
name TEXT NOT NULL,
status TEXT NOT NULL,
conclusion TEXT NULL,
details_url TEXT NULL,
external_id TEXT NULL,
started_at TIMESTAMPTZ NULL,
completed_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_pr_check_run_pull_request_id ON pr_check_run (pull_request_id);
CREATE INDEX IF NOT EXISTS idx_pr_check_run_external_id ON pr_check_run (external_id);
CREATE INDEX IF NOT EXISTS idx_pr_check_run_status_created ON pr_check_run (status, created_at DESC);
-- models/prs/pr_commits.rs → pr_commit
CREATE TABLE IF NOT EXISTS pr_commit (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
pull_request_id UUID NOT NULL REFERENCES pull_request(id) ON DELETE CASCADE,
repo_id UUID NOT NULL REFERENCES repo(id) ON DELETE CASCADE,
commit_sha TEXT NOT NULL,
position INTEGER NOT NULL,
authored_at TIMESTAMPTZ NULL,
committed_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_pr_commit_pull_request_id ON pr_commit (pull_request_id);
CREATE INDEX IF NOT EXISTS idx_pr_commit_repo_id ON pr_commit (repo_id);
CREATE INDEX IF NOT EXISTS idx_pr_commit_repo_created ON pr_commit (repo_id, created_at DESC);
-- models/prs/pr_events.rs → pr_event
CREATE TABLE IF NOT EXISTS pr_event (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
pull_request_id UUID NOT NULL REFERENCES pull_request(id) ON DELETE CASCADE,
actor_id UUID NULL REFERENCES "user"(id) ON DELETE CASCADE,
event_type TEXT NOT NULL,
old_value JSONB NULL,
new_value JSONB NULL,
metadata JSONB NULL,
created_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_pr_event_pull_request_id ON pr_event (pull_request_id);
CREATE INDEX IF NOT EXISTS idx_pr_event_actor_id ON pr_event (actor_id);
-- models/prs/pr_files.rs → pr_file
CREATE TABLE IF NOT EXISTS pr_file (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
pull_request_id UUID NOT NULL REFERENCES pull_request(id) ON DELETE CASCADE,
path TEXT NOT NULL,
old_path TEXT NULL,
status TEXT NOT NULL,
additions INTEGER NOT NULL,
deletions INTEGER NOT NULL,
changes INTEGER NOT NULL,
patch TEXT NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_pr_file_pull_request_id ON pr_file (pull_request_id);
CREATE INDEX IF NOT EXISTS idx_pr_file_status_created ON pr_file (status, created_at DESC);
-- models/prs/pr_label_relations.rs → pr_label_relation
CREATE TABLE IF NOT EXISTS pr_label_relation (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
pull_request_id UUID NOT NULL REFERENCES pull_request(id) ON DELETE CASCADE,
label_id UUID NOT NULL REFERENCES pr_label(id) ON DELETE CASCADE,
created_by UUID NULL REFERENCES "user"(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_pr_label_relation_pull_request_id ON pr_label_relation (pull_request_id);
CREATE INDEX IF NOT EXISTS idx_pr_label_relation_label_id ON pr_label_relation (label_id);
-- models/prs/pr_merge_strategy.rs → pr_merge_strategy
CREATE TABLE IF NOT EXISTS pr_merge_strategy (
PRIMARY KEY (pull_request_id),
pull_request_id UUID NOT NULL REFERENCES pull_request(id) ON DELETE CASCADE,
strategy TEXT NOT NULL,
auto_merge BOOLEAN NOT NULL,
squash_title TEXT NULL,
squash_message TEXT NULL,
delete_source_branch BOOLEAN NOT NULL,
merge_when_checks_pass BOOLEAN NOT NULL,
selected_by UUID NULL REFERENCES "user"(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
-- models/prs/pr_reactions.rs → pr_reaction
CREATE TABLE IF NOT EXISTS pr_reaction (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
pull_request_id UUID NOT NULL REFERENCES pull_request(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
content TEXT NOT NULL,
target_type TEXT NOT NULL,
target_id UUID NULL,
created_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_pr_reaction_pull_request_id ON pr_reaction (pull_request_id);
CREATE INDEX IF NOT EXISTS idx_pr_reaction_user_id ON pr_reaction (user_id);
CREATE INDEX IF NOT EXISTS idx_pr_reaction_target_id ON pr_reaction (target_id);
CREATE INDEX IF NOT EXISTS idx_pr_reaction_user_created ON pr_reaction (user_id, created_at DESC);
-- models/prs/pr_status.rs → pr_status
CREATE TABLE IF NOT EXISTS pr_status (
PRIMARY KEY (pull_request_id),
pull_request_id UUID NOT NULL REFERENCES pull_request(id) ON DELETE CASCADE,
head_commit_sha TEXT NOT NULL,
checks_state TEXT NOT NULL,
mergeable_state TEXT NOT NULL,
conflicts BOOLEAN NOT NULL,
approvals_count INTEGER NOT NULL,
requested_reviews_count INTEGER NOT NULL,
changed_files_count INTEGER NOT NULL,
additions_count INTEGER NOT NULL,
deletions_count INTEGER NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
-- models/prs/pr_subscriptions.rs → pr_subscription
CREATE TABLE IF NOT EXISTS pr_subscription (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
pull_request_id UUID NOT NULL REFERENCES pull_request(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
reason TEXT NOT NULL,
muted BOOLEAN NOT NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
,
CONSTRAINT uq_pr_subscription UNIQUE (pull_request_id, user_id)
);
CREATE INDEX IF NOT EXISTS idx_pr_subscription_pull_request_id ON pr_subscription (pull_request_id);
CREATE INDEX IF NOT EXISTS idx_pr_subscription_user_id ON pr_subscription (user_id);
CREATE INDEX IF NOT EXISTS idx_pr_subscription_user_created ON pr_subscription (user_id, created_at DESC);
-- models/issues/issue_commit_relations.rs → issue_commit_relation
CREATE TABLE IF NOT EXISTS issue_commit_relation (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
issue_id UUID NOT NULL REFERENCES issue(id) ON DELETE CASCADE,
repo_id UUID NOT NULL REFERENCES repo(id) ON DELETE CASCADE,
push_commit_id UUID NULL REFERENCES repo_push_commit(id) ON DELETE CASCADE,
commit_sha TEXT NOT NULL,
relation_type TEXT NOT NULL,
created_by UUID NULL REFERENCES "user"(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_issue_commit_relation_issue_id ON issue_commit_relation (issue_id);
CREATE INDEX IF NOT EXISTS idx_issue_commit_relation_repo_id ON issue_commit_relation (repo_id);
CREATE INDEX IF NOT EXISTS idx_issue_commit_relation_push_commit_id ON issue_commit_relation (push_commit_id);
CREATE INDEX IF NOT EXISTS idx_issue_commit_relation_repo_created ON issue_commit_relation (repo_id, created_at DESC);
-- models/repos/repo_branches.rs → repo_branch
CREATE TABLE IF NOT EXISTS repo_branch (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
repo_id UUID NOT NULL REFERENCES repo(id) ON DELETE CASCADE,
name TEXT NOT NULL,
commit_sha TEXT NOT NULL,
protected BOOLEAN NOT NULL,
default_branch BOOLEAN NOT NULL,
created_by UUID NULL REFERENCES "user"(id) ON DELETE CASCADE,
last_push_id UUID NULL REFERENCES repo_push_commit(id) ON DELETE CASCADE,
last_push_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_repo_branch_repo_id ON repo_branch (repo_id);
CREATE INDEX IF NOT EXISTS idx_repo_branch_last_push_id ON repo_branch (last_push_id);
CREATE INDEX IF NOT EXISTS idx_repo_branch_repo_created ON repo_branch (repo_id, created_at DESC);
-- models/repos/repo_commit_comments.rs → repo_commit_comment
CREATE TABLE IF NOT EXISTS repo_commit_comment (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
repo_id UUID NOT NULL REFERENCES repo(id) ON DELETE CASCADE,
push_commit_id UUID NOT NULL REFERENCES repo_push_commit(id) ON DELETE CASCADE,
commit_sha TEXT NOT NULL,
author_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
body TEXT NOT NULL,
path TEXT NULL,
line INTEGER NULL,
resolved BOOLEAN NOT NULL,
resolved_by UUID NULL REFERENCES "user"(id) ON DELETE CASCADE,
resolved_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL,
deleted_at TIMESTAMPTZ NULL
);
CREATE INDEX IF NOT EXISTS idx_repo_commit_comment_repo_id ON repo_commit_comment (repo_id);
CREATE INDEX IF NOT EXISTS idx_repo_commit_comment_push_commit_id ON repo_commit_comment (push_commit_id);
CREATE INDEX IF NOT EXISTS idx_repo_commit_comment_author_id ON repo_commit_comment (author_id);
CREATE INDEX IF NOT EXISTS idx_repo_commit_comment_repo_created ON repo_commit_comment (repo_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_repo_commit_comment_deleted ON repo_commit_comment (deleted_at) WHERE deleted_at IS NOT NULL;
-- models/repos/repo_commit_statuses.rs → repo_commit_status
CREATE TABLE IF NOT EXISTS repo_commit_status (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
repo_id UUID NOT NULL REFERENCES repo(id) ON DELETE CASCADE,
push_commit_id UUID NOT NULL REFERENCES repo_push_commit(id) ON DELETE CASCADE,
latest_commit_sha TEXT NOT NULL,
context TEXT NOT NULL,
state TEXT NOT NULL,
target_url TEXT NULL,
description TEXT NULL,
reported_by UUID NULL REFERENCES "user"(id) ON DELETE CASCADE,
reported_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_repo_commit_status_repo_id ON repo_commit_status (repo_id);
CREATE INDEX IF NOT EXISTS idx_repo_commit_status_push_commit_id ON repo_commit_status (push_commit_id);
CREATE INDEX IF NOT EXISTS idx_repo_commit_status_repo_created ON repo_commit_status (repo_id, created_at DESC);
-- models/repos/repo_releases.rs → repo_release
CREATE TABLE IF NOT EXISTS repo_release (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
repo_id UUID NOT NULL REFERENCES repo(id) ON DELETE CASCADE,
tag_id UUID NULL REFERENCES repo_tag(id) ON DELETE CASCADE,
tag_name TEXT NOT NULL,
title TEXT NOT NULL,
body TEXT NULL,
draft BOOLEAN NOT NULL,
prerelease BOOLEAN NOT NULL,
author_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
published_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL,
deleted_at TIMESTAMPTZ NULL
);
CREATE INDEX IF NOT EXISTS idx_repo_release_repo_id ON repo_release (repo_id);
CREATE INDEX IF NOT EXISTS idx_repo_release_tag_id ON repo_release (tag_id);
CREATE INDEX IF NOT EXISTS idx_repo_release_author_id ON repo_release (author_id);
CREATE INDEX IF NOT EXISTS idx_repo_release_repo_created ON repo_release (repo_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_repo_release_deleted ON repo_release (deleted_at) WHERE deleted_at IS NOT NULL;
-- models/channels/channel_members.rs → channel_member
CREATE TABLE IF NOT EXISTS channel_member (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
channel_id UUID NOT NULL REFERENCES channel(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
role TEXT NOT NULL,
status TEXT NOT NULL,
muted BOOLEAN NOT NULL,
pinned BOOLEAN NOT NULL,
last_read_message_id UUID NULL REFERENCES message(id) ON DELETE CASCADE,
last_read_at TIMESTAMPTZ NULL,
joined_at TIMESTAMPTZ NULL,
left_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
,
CONSTRAINT uq_channel_member UNIQUE (channel_id, user_id)
);
CREATE INDEX IF NOT EXISTS idx_channel_member_channel_id ON channel_member (channel_id);
CREATE INDEX IF NOT EXISTS idx_channel_member_user_id ON channel_member (user_id);
CREATE INDEX IF NOT EXISTS idx_channel_member_last_read_message_id ON channel_member (last_read_message_id);
CREATE INDEX IF NOT EXISTS idx_channel_member_user_created ON channel_member (user_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_channel_member_status_created ON channel_member (status, created_at DESC);
-- models/channels/message_bookmarks.rs → message_bookmark
CREATE TABLE IF NOT EXISTS message_bookmark (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
message_id UUID NOT NULL REFERENCES message(id) ON DELETE CASCADE,
channel_id UUID NOT NULL REFERENCES channel(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
note TEXT NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_message_bookmark_message_id ON message_bookmark (message_id);
CREATE INDEX IF NOT EXISTS idx_message_bookmark_channel_id ON message_bookmark (channel_id);
CREATE INDEX IF NOT EXISTS idx_message_bookmark_user_id ON message_bookmark (user_id);
CREATE INDEX IF NOT EXISTS idx_message_bookmark_user_created ON message_bookmark (user_id, created_at DESC);
-- models/channels/message_mentions.rs → message_mention
CREATE TABLE IF NOT EXISTS message_mention (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
message_id UUID NOT NULL REFERENCES message(id) ON DELETE CASCADE,
channel_id UUID NOT NULL REFERENCES channel(id) ON DELETE CASCADE,
mentioned_user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
mentioned_by UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
read_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_message_mention_message_id ON message_mention (message_id);
CREATE INDEX IF NOT EXISTS idx_message_mention_channel_id ON message_mention (channel_id);
CREATE INDEX IF NOT EXISTS idx_message_mention_mentioned_user_id ON message_mention (mentioned_user_id);
-- models/channels/message_reactions.rs → message_reaction
CREATE TABLE IF NOT EXISTS message_reaction (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
message_id UUID NOT NULL REFERENCES message(id) ON DELETE CASCADE,
channel_id UUID NOT NULL REFERENCES channel(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
content TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_message_reaction_message_id ON message_reaction (message_id);
CREATE INDEX IF NOT EXISTS idx_message_reaction_channel_id ON message_reaction (channel_id);
CREATE INDEX IF NOT EXISTS idx_message_reaction_user_id ON message_reaction (user_id);
CREATE INDEX IF NOT EXISTS idx_message_reaction_user_created ON message_reaction (user_id, created_at DESC);
-- models/channels/message_threads.rs → message_thread
CREATE TABLE IF NOT EXISTS message_thread (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
channel_id UUID NOT NULL REFERENCES channel(id) ON DELETE CASCADE,
root_message_id UUID NOT NULL,
created_by UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
replies_count BIGINT NOT NULL,
participants_count BIGINT NOT NULL,
last_reply_message_id UUID NULL REFERENCES message(id) ON DELETE CASCADE,
last_reply_at TIMESTAMPTZ NULL,
resolved BOOLEAN NOT NULL,
resolved_by UUID NULL REFERENCES "user"(id) ON DELETE CASCADE,
resolved_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_message_thread_channel_id ON message_thread (channel_id);
CREATE INDEX IF NOT EXISTS idx_message_thread_root_message_id ON message_thread (root_message_id);
CREATE INDEX IF NOT EXISTS idx_message_thread_last_reply_message_id ON message_thread (last_reply_message_id);
-- models/conversations/conversation.rs → conversation
CREATE TABLE IF NOT EXISTS conversation (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
workspace_id UUID NULL REFERENCES workspace(id) ON DELETE CASCADE,
repo_id UUID NULL REFERENCES repo(id) ON DELETE CASCADE,
issue_id UUID NULL REFERENCES issue(id) ON DELETE CASCADE,
pull_request_id UUID NULL REFERENCES pull_request(id) ON DELETE CASCADE,
agent_id UUID NULL REFERENCES agent(id) ON DELETE CASCADE,
created_by UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
title TEXT NOT NULL,
description TEXT NULL,
conversation_type TEXT NOT NULL,
status TEXT NOT NULL,
visibility TEXT NOT NULL,
pinned BOOLEAN NOT NULL,
archived BOOLEAN NOT NULL,
metadata JSONB NULL,
last_message_id UUID NULL REFERENCES message(id) ON DELETE CASCADE,
last_message_at TIMESTAMPTZ NULL,
archived_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL,
deleted_at TIMESTAMPTZ NULL
);
CREATE INDEX IF NOT EXISTS idx_conversation_workspace_id ON conversation (workspace_id);
CREATE INDEX IF NOT EXISTS idx_conversation_repo_id ON conversation (repo_id);
CREATE INDEX IF NOT EXISTS idx_conversation_issue_id ON conversation (issue_id);
CREATE INDEX IF NOT EXISTS idx_conversation_pull_request_id ON conversation (pull_request_id);
CREATE INDEX IF NOT EXISTS idx_conversation_agent_id ON conversation (agent_id);
CREATE INDEX IF NOT EXISTS idx_conversation_last_message_id ON conversation (last_message_id);
CREATE INDEX IF NOT EXISTS idx_conversation_repo_created ON conversation (repo_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_conversation_ws_created ON conversation (workspace_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_conversation_status_created ON conversation (status, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_conversation_deleted ON conversation (deleted_at) WHERE deleted_at IS NOT NULL;
-- models/notifications/notification.rs → notification
CREATE TABLE IF NOT EXISTS notification (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
actor_id UUID NULL REFERENCES "user"(id) ON DELETE CASCADE,
workspace_id UUID NULL REFERENCES workspace(id) ON DELETE CASCADE,
notification_type TEXT NOT NULL,
title TEXT NOT NULL,
body TEXT NULL,
read_at TIMESTAMPTZ NULL,
dismissed_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_notification_user_id ON notification (user_id);
CREATE INDEX IF NOT EXISTS idx_notification_actor_id ON notification (actor_id);
CREATE INDEX IF NOT EXISTS idx_notification_workspace_id ON notification (workspace_id);
CREATE INDEX IF NOT EXISTS idx_notification_user_created ON notification (user_id, created_at DESC);
-- models/agents/agent_feedback.rs → agent_feedback
CREATE TABLE IF NOT EXISTS agent_feedback (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
agent_id UUID NOT NULL REFERENCES agent(id) ON DELETE CASCADE,
execution_id UUID NULL REFERENCES agent_execution(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
rating INTEGER NOT NULL,
feedback_type TEXT NOT NULL,
comment TEXT NULL,
metadata JSONB NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_agent_feedback_agent_id ON agent_feedback (agent_id);
CREATE INDEX IF NOT EXISTS idx_agent_feedback_execution_id ON agent_feedback (execution_id);
CREATE INDEX IF NOT EXISTS idx_agent_feedback_user_id ON agent_feedback (user_id);
CREATE INDEX IF NOT EXISTS idx_agent_feedback_user_created ON agent_feedback (user_id, created_at DESC);
-- models/conversations/conversation_attachments.rs → conversation_attachment
CREATE TABLE IF NOT EXISTS conversation_attachment (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
conversation_id UUID NOT NULL REFERENCES conversation(id) ON DELETE CASCADE,
message_id UUID NULL REFERENCES message(id) ON DELETE CASCADE,
file_id UUID NULL REFERENCES conversation_attachment(id) ON DELETE CASCADE,
uploaded_by UUID NULL REFERENCES "user"(id) ON DELETE CASCADE,
name TEXT NOT NULL,
content_type TEXT NULL,
size_bytes BIGINT NOT NULL,
storage_path TEXT NULL,
url TEXT NULL,
metadata JSONB NULL,
created_at TIMESTAMPTZ NOT NULL,
deleted_at TIMESTAMPTZ NULL
);
CREATE INDEX IF NOT EXISTS idx_conversation_attachment_conversation_id ON conversation_attachment (conversation_id);
CREATE INDEX IF NOT EXISTS idx_conversation_attachment_message_id ON conversation_attachment (message_id);
CREATE INDEX IF NOT EXISTS idx_conversation_attachment_file_id ON conversation_attachment (file_id);
CREATE INDEX IF NOT EXISTS idx_conversation_attachment_deleted ON conversation_attachment (deleted_at) WHERE deleted_at IS NOT NULL;
-- models/conversations/conversation_bookmarks.rs → conversation_bookmark
CREATE TABLE IF NOT EXISTS conversation_bookmark (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
conversation_id UUID NOT NULL REFERENCES conversation(id) ON DELETE CASCADE,
message_id UUID NULL REFERENCES message(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
title TEXT NULL,
note TEXT NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_conversation_bookmark_conversation_id ON conversation_bookmark (conversation_id);
CREATE INDEX IF NOT EXISTS idx_conversation_bookmark_message_id ON conversation_bookmark (message_id);
CREATE INDEX IF NOT EXISTS idx_conversation_bookmark_user_id ON conversation_bookmark (user_id);
CREATE INDEX IF NOT EXISTS idx_conversation_bookmark_user_created ON conversation_bookmark (user_id, created_at DESC);
-- models/conversations/conversation_messages.rs → conversation_message
CREATE TABLE IF NOT EXISTS conversation_message (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
conversation_id UUID NOT NULL REFERENCES conversation(id) ON DELETE CASCADE,
parent_message_id UUID NULL,
author_id UUID NULL REFERENCES "user"(id) ON DELETE CASCADE,
agent_id UUID NULL REFERENCES agent(id) ON DELETE CASCADE,
ai_model_id UUID NULL REFERENCES ai_model(id) ON DELETE CASCADE,
role TEXT NOT NULL,
message_type TEXT NOT NULL,
content TEXT NOT NULL,
content_format TEXT NOT NULL,
status TEXT NOT NULL,
metadata JSONB NULL,
token_input_count INTEGER NULL,
token_output_count INTEGER NULL,
edited_at TIMESTAMPTZ NULL,
deleted_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_conversation_message_conversation_id ON conversation_message (conversation_id);
CREATE INDEX IF NOT EXISTS idx_conversation_message_parent_message_id ON conversation_message (parent_message_id);
CREATE INDEX IF NOT EXISTS idx_conversation_message_author_id ON conversation_message (author_id);
CREATE INDEX IF NOT EXISTS idx_conversation_message_agent_id ON conversation_message (agent_id);
CREATE INDEX IF NOT EXISTS idx_conversation_message_ai_model_id ON conversation_message (ai_model_id);
CREATE INDEX IF NOT EXISTS idx_conversation_message_status_created ON conversation_message (status, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_conversation_message_deleted ON conversation_message (deleted_at) WHERE deleted_at IS NOT NULL;
-- models/conversations/conversation_participants.rs → conversation_participant
CREATE TABLE IF NOT EXISTS conversation_participant (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
conversation_id UUID NOT NULL REFERENCES conversation(id) ON DELETE CASCADE,
user_id UUID NULL REFERENCES "user"(id) ON DELETE CASCADE,
agent_id UUID NULL REFERENCES agent(id) ON DELETE CASCADE,
role TEXT NOT NULL,
participant_type TEXT NOT NULL,
status TEXT NOT NULL,
muted BOOLEAN NOT NULL,
last_read_message_id UUID NULL REFERENCES message(id) ON DELETE CASCADE,
last_read_at TIMESTAMPTZ NULL,
joined_at TIMESTAMPTZ NULL,
left_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_conversation_participant_conversation_id ON conversation_participant (conversation_id);
CREATE INDEX IF NOT EXISTS idx_conversation_participant_user_id ON conversation_participant (user_id);
CREATE INDEX IF NOT EXISTS idx_conversation_participant_agent_id ON conversation_participant (agent_id);
CREATE INDEX IF NOT EXISTS idx_conversation_participant_last_read_message_id ON conversation_participant (last_read_message_id);
CREATE INDEX IF NOT EXISTS idx_conversation_participant_user_created ON conversation_participant (user_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_conversation_participant_status_created ON conversation_participant (status, created_at DESC);
-- models/conversations/conversation_tool_calls.rs → conversation_tool_call
CREATE TABLE IF NOT EXISTS conversation_tool_call (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
conversation_id UUID NOT NULL REFERENCES conversation(id) ON DELETE CASCADE,
message_id UUID NOT NULL REFERENCES message(id) ON DELETE CASCADE,
agent_id UUID NULL REFERENCES agent(id) ON DELETE CASCADE,
tool_name TEXT NOT NULL,
call_id TEXT NULL,
status TEXT NOT NULL,
arguments JSONB NULL,
result JSONB NULL,
error_message TEXT NULL,
started_at TIMESTAMPTZ NULL,
finished_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_conversation_tool_call_conversation_id ON conversation_tool_call (conversation_id);
CREATE INDEX IF NOT EXISTS idx_conversation_tool_call_message_id ON conversation_tool_call (message_id);
CREATE INDEX IF NOT EXISTS idx_conversation_tool_call_agent_id ON conversation_tool_call (agent_id);
CREATE INDEX IF NOT EXISTS idx_conversation_tool_call_call_id ON conversation_tool_call (call_id);
CREATE INDEX IF NOT EXISTS idx_conversation_tool_call_status_created ON conversation_tool_call (status, created_at DESC);
-- models/conversations/conversation_summaries.rs → conversation_summary
CREATE TABLE IF NOT EXISTS conversation_summary (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
conversation_id UUID NOT NULL REFERENCES conversation(id) ON DELETE CASCADE,
ai_model_id UUID NULL REFERENCES ai_model(id) ON DELETE CASCADE,
generated_by UUID NULL REFERENCES "user"(id) ON DELETE CASCADE,
from_message_id UUID NULL REFERENCES conversation_message(id) ON DELETE CASCADE,
to_message_id UUID NULL REFERENCES conversation_message(id) ON DELETE CASCADE,
summary_type TEXT NOT NULL,
content TEXT NOT NULL,
token_count INTEGER NULL,
metadata JSONB NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_conversation_summary_conversation_id ON conversation_summary (conversation_id);
CREATE INDEX IF NOT EXISTS idx_conversation_summary_ai_model_id ON conversation_summary (ai_model_id);
CREATE INDEX IF NOT EXISTS idx_conversation_summary_from_message_id ON conversation_summary (from_message_id);
CREATE INDEX IF NOT EXISTS idx_conversation_summary_to_message_id ON conversation_summary (to_message_id);
-- PHASE B: Deferred FKs (circular / self-referencing)
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_agent_execution_step_execution_id') THEN
ALTER TABLE agent_execution_step ADD CONSTRAINT fk_agent_execution_step_execution_id
FOREIGN KEY (execution_id) REFERENCES agent_execution(id) ON DELETE CASCADE;
END IF;
END $$;
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_agent_current_version_id') THEN
ALTER TABLE agent ADD CONSTRAINT fk_agent_current_version_id
FOREIGN KEY (current_version_id) REFERENCES agent_version(id) ON DELETE CASCADE;
END IF;
END $$;
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_channel_last_message_id') THEN
ALTER TABLE channel ADD CONSTRAINT fk_channel_last_message_id
FOREIGN KEY (last_message_id) REFERENCES message(id) ON DELETE CASCADE;
END IF;
END $$;
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_message_thread_id') THEN
ALTER TABLE message ADD CONSTRAINT fk_message_thread_id
FOREIGN KEY (thread_id) REFERENCES message_thread(id) ON DELETE CASCADE;
END IF;
END $$;
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_message_reply_to_message_id') THEN
ALTER TABLE message ADD CONSTRAINT fk_message_reply_to_message_id
FOREIGN KEY (reply_to_message_id) REFERENCES message(id) ON DELETE CASCADE;
END IF;
END $$;
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_issue_comment_reply_to_comment_id') THEN
ALTER TABLE issue_comment ADD CONSTRAINT fk_issue_comment_reply_to_comment_id
FOREIGN KEY (reply_to_comment_id) REFERENCES issue_comment(id) ON DELETE CASCADE;
END IF;
END $$;
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_message_thread_root_message_id') THEN
ALTER TABLE message_thread ADD CONSTRAINT fk_message_thread_root_message_id
FOREIGN KEY (root_message_id) REFERENCES message(id) ON DELETE CASCADE;
END IF;
END $$;
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_conversation_message_parent_message_id') THEN
ALTER TABLE conversation_message ADD CONSTRAINT fk_conversation_message_parent_message_id
FOREIGN KEY (parent_message_id) REFERENCES conversation_message(id) ON DELETE CASCADE;
END IF;
END $$;
COMMIT;