Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 99 additions & 48 deletions backend/database/01_create_integration_tables.sql
Original file line number Diff line number Diff line change
@@ -1,65 +1,116 @@
-- Table for storing organization registrations
CREATE TABLE IF NOT EXISTS organization_integrations (
-- =========================================================
-- STEP 1: FULL CLEANUP (Re-initialization)
-- =========================================================
DROP TABLE IF EXISTS public.indexed_repositories CASCADE;
DROP TABLE IF EXISTS public.interactions CASCADE;
DROP TABLE IF EXISTS public.conversation_context CASCADE;
DROP TABLE IF EXISTS public.organization_integrations CASCADE;
DROP TABLE IF EXISTS public.users CASCADE;

-- =========================================================
-- STEP 2: CREATE USERS TABLE
-- =========================================================
CREATE TABLE public.users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
discord_id TEXT UNIQUE,
email TEXT,
full_name TEXT,
avatar_url TEXT,
discord_username TEXT,
display_name TEXT,
preferred_languages TEXT[],
last_active_discord TIMESTAMPTZ DEFAULT NOW(),

-- Verification Columns
verification_token TEXT,
verification_token_expires_at TIMESTAMPTZ,

-- GitHub Data Columns
github_id TEXT,
github_username TEXT,
is_verified BOOLEAN DEFAULT FALSE,
verified_at TIMESTAMPTZ,

created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
Comment on lines +13 to +36
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Major: Missing indexes on UNIQUE identifier columns.

Lines 15 and 29 define discord_id and github_id as UNIQUE but don't create explicit indexes. While Postgres auto-indexes UNIQUE constraints, explicit indexes improve query performance for lookups. Additionally, discord_id is nullable but appears to be a user identifierβ€”this is inconsistent.

Add explicit indexes and clarify the design:

+ CREATE UNIQUE INDEX IF NOT EXISTS idx_users_discord_id ON public.users(discord_id) WHERE discord_id IS NOT NULL;
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_users_github_id ON public.users(github_id) WHERE github_id IS NOT NULL;

Consider whether discord_id should be NOT NULL if it's the primary user identifier. If users can register without Discord, document the onboarding flow.

πŸ€– Prompt for AI Agents
In backend/database/01_create_integration_tables.sql around lines 13 to 36, the
table defines UNIQUE identifiers (discord_id and github_id) but lacks explicit
indexes and discord_id is nullable which is inconsistent for a primary
identifier; add CREATE INDEX IF NOT EXISTS statements for discord_id and
github_id (and any other frequently queried identifier like email) to ensure
performant lookups, and decide whether discord_id should be NOT NULL (if Discord
is required) or keep it nullable and document the onboarding flow that allows
non-Discord usersβ€”update the table DDL accordingly or add developer notes
explaining the design choice.


ALTER TABLE public.users ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Public Access Users" ON public.users FOR ALL USING (true) WITH CHECK (true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | πŸ”΄ Critical

Critical: RLS policies expose all user data to all users.

All row-level security policies use permissive USING (true) WITH CHECK (true) access. This allows any authenticated user to read and modify any other user's data, including conversations, repository indexes, and credentials. This is a fundamental authorization flaw.

These policies should scope access to the authenticated user, not grant blanket public access. For example, replace line 39 with policies that check the user's identity.

- CREATE POLICY "Public Access Users" ON public.users FOR ALL USING (true) WITH CHECK (true);
+ CREATE POLICY "User Self Access" ON public.users 
+   FOR SELECT USING (auth.uid()::uuid = id)
+   FOR UPDATE USING (auth.uid()::uuid = id)
+   FOR DELETE USING (auth.uid()::uuid = id)
+   FOR INSERT WITH CHECK (auth.uid()::uuid = id);

Apply similar scoped access patterns to all other tables (organization_integrations, conversation_context, interactions, indexed_repositories). Verify this matches your application's intended access control model.

Also applies to: 58-58, 80-80, 83-83, 109-109

πŸ€– Prompt for AI Agents
In backend/database/01_create_integration_tables.sql around line 39 (and
similarly lines 58, 80, 83, 109), the RLS policies are overly permissive (USING
(true) WITH CHECK (true)); replace them with scoped policies that restrict rows
to the authenticated user or a valid membership check. For per-user tables
(e.g., users, conversation_context, interactions, indexed_repositories) change
the USING/WITH CHECK expressions to compare the row owner column (id, user_id,
owner_id, etc.) to auth.uid() so only the owning user can
SELECT/INSERT/UPDATE/DELETE their rows; for organization-scoped tables (e.g.,
organization_integrations) use a membership check (e.g., EXISTS against an
organization_members table or a function that verifies the current auth.uid() is
a member of the organization) in the USING/WITH CHECK clauses. Apply these
scoped replacements to the listed lines and audit other table policies to ensure
no policy remains USING (true) WITH CHECK (true).


-- =========================================================
-- STEP 3: INTEGRATIONS TABLE
-- =========================================================
CREATE TABLE public.organization_integrations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES public.users(id) ON DELETE CASCADE,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Major: Foreign key columns lack NOT NULL constraints.

Lines 46, 71, and 90 define user_id without NOT NULL, allowing orphaned rows when a user_id is missing. These columns should be mandatory.

- user_id UUID REFERENCES public.users(id) ON DELETE CASCADE,
+ user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,

Apply this fix to organization_integrations (line 46), interactions (line 71), and indexed_repositories (line 90).

Also applies to: 71-71, 90-90

platform VARCHAR(50) NOT NULL CHECK (platform IN ('github', 'discord', 'slack', 'discourse')),
organization_name VARCHAR(255) NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT true,
config JSONB DEFAULT '{}', -- Stores org link, discord_guild_id, etc.
config JSONB DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),

-- Ensure one integration per user per platform
UNIQUE(user_id, platform)
);

-- Create indexes for better query performance
CREATE INDEX IF NOT EXISTS idx_org_integrations_user_id ON organization_integrations(user_id);
CREATE INDEX IF NOT EXISTS idx_org_integrations_platform ON organization_integrations(platform);
CREATE INDEX IF NOT EXISTS idx_org_integrations_is_active ON organization_integrations(is_active);
ALTER TABLE public.organization_integrations ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Public Access Orgs" ON organization_integrations FOR ALL USING (true) WITH CHECK (true);

-- Create function to automatically update updated_at timestamp
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- Create triggers to automatically update updated_at
CREATE TRIGGER update_organization_integrations_updated_at
BEFORE UPDATE ON organization_integrations
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();

-- Enable Row Level Security (RLS)
ALTER TABLE organization_integrations ENABLE ROW LEVEL SECURITY;
-- =========================================================
-- STEP 4: MEMORY TABLES (Context & Interactions)
-- =========================================================
CREATE TABLE public.conversation_context (
user_id UUID REFERENCES public.users(id) ON DELETE CASCADE PRIMARY KEY,
Copy link
Contributor

@coderabbitai coderabbitai bot Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Major: conversation_context design limits one context per user.

Line 64 defines user_id as the PRIMARY KEY, which enforces a 1:1 relationshipβ€”each user can have only one conversation context. If the intent is to support multiple conversations per user, this design is insufficient.

If supporting multiple conversations is required:

- CREATE TABLE public.conversation_context (
-     user_id UUID REFERENCES public.users(id) ON DELETE CASCADE PRIMARY KEY,
+ CREATE TABLE public.conversation_context (
+     id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+     user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
      context JSONB DEFAULT '{}',
      updated_at TIMESTAMPTZ DEFAULT NOW()
+ );
+ CREATE INDEX IF NOT EXISTS idx_conversation_context_user_id ON conversation_context(user_id);

If limiting to one context per user is intentional, document this constraint clearly.

Committable suggestion skipped: line range outside the PR's diff.

πŸ€– Prompt for AI Agents
backend/database/01_create_integration_tables.sql around line 64: user_id is
declared as PRIMARY KEY which enforces a 1:1 relationship and prevents multiple
conversation contexts per user; to allow multiple contexts, remove the PRIMARY
KEY from user_id and instead add a dedicated primary key (e.g., id UUID PRIMARY
KEY) or introduce a conversation_id and use a composite key (user_id,
conversation_id) and index user_id as a foreign key; if the 1:1 constraint is
intentional, add a clear comment or documentation note above the column
explaining that only one conversation_context is allowed per user.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai Thanks for the review. Since this script is intended for local development setup to fix the PGRST204 schema errors, I have kept the RLS policies permissive (USING true) to avoid permission issues during initial onboarding. The DROP TABLE statements are also intentional to ensure a clean state during setup.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@HamzaNasiem Thanks for the context! I understand the RLS policies are permissive for local development, and the DROP TABLE statements ensure a clean setupβ€”that makes sense for the use case.

My original comment was specifically about the conversation_context table design (Line 64), where user_id is declared as the PRIMARY KEY. This enforces a 1:1 relationship, meaning each user can have only one conversation context record.

My question is: Is this intentional?

  • If you want to support multiple conversation contexts per user (e.g., multiple chat sessions), the current design won't allow it.
  • If you only need one context per user (e.g., storing a single global conversation state), then the current design is correct.

Could you clarify which behavior you're aiming for? That way, we can ensure the schema aligns with your requirements. 😊

context JSONB DEFAULT '{}',
updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- Create RLS policies for organization_integrations
-- Users can only see and manage their own integrations
CREATE POLICY "Users can view their own integrations"
ON organization_integrations
FOR SELECT
USING (auth.uid() = user_id);
CREATE TABLE public.interactions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES public.users(id) ON DELETE CASCADE,
session_id TEXT,
input_text TEXT,
output_text TEXT,
tool_usage JSONB DEFAULT '[]',
created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE POLICY "Users can create their own integrations"
ON organization_integrations
FOR INSERT
WITH CHECK (auth.uid() = user_id);
ALTER TABLE public.conversation_context ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Public Context Access" ON conversation_context FOR ALL USING (true) WITH CHECK (true);

CREATE POLICY "Users can update their own integrations"
ON organization_integrations
FOR UPDATE
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);
ALTER TABLE public.interactions ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Public Interaction Access" ON interactions FOR ALL USING (true) WITH CHECK (true);

CREATE POLICY "Users can delete their own integrations"
ON organization_integrations
FOR DELETE
USING (auth.uid() = user_id);
-- =========================================================
-- STEP 5: INDEXED REPOSITORIES (Complete Definition)
-- =========================================================
CREATE TABLE public.indexed_repositories (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES public.users(id) ON DELETE CASCADE,
repository_full_name TEXT NOT NULL,
graph_name TEXT NOT NULL,

-- βœ… All Critical Columns Included Here
indexed_by_discord_id TEXT,
last_error TEXT,
indexed_at TIMESTAMPTZ DEFAULT NOW(),

branch TEXT DEFAULT 'main',
node_count INTEGER DEFAULT 0,
edge_count INTEGER DEFAULT 0,
indexing_status TEXT DEFAULT 'pending',
is_deleted BOOLEAN DEFAULT FALSE,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(user_id, repository_full_name)
);

-- Add helpful comments
COMMENT ON TABLE organization_integrations IS 'Stores registered organizations (just metadata, no tokens)';
COMMENT ON COLUMN organization_integrations.config IS 'Platform-specific data: organization_link, discord_guild_id, etc.';
CREATE INDEX IF NOT EXISTS idx_indexed_repos_user ON indexed_repositories(user_id);
ALTER TABLE public.indexed_repositories ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Public Index Access" ON indexed_repositories FOR ALL USING (true) WITH CHECK (true);

-- =========================================================
-- STEP 6: FINAL REFRESH
-- =========================================================
NOTIFY pgrst, 'reload schema';