mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-22 05:46:42 +02:00
feat(chat): integrate chat project into monorepo with full app structure
- Restructure chat as apps/mobile, apps/web, apps/landing, backend - Add NestJS backend for secure Azure OpenAI API calls - Remove exposed API key from mobile app (security fix) - Add shared chat-types package - Create SvelteKit web app scaffold - Create Astro landing page scaffold - Update pnpm workspace configuration - Add project-level CLAUDE.md documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
fcf3a344b1
commit
c638a7ffee
155 changed files with 22622 additions and 348 deletions
176
chat/apps/mobile/scripts/spaces/create_spaces_rls.sql
Normal file
176
chat/apps/mobile/scripts/spaces/create_spaces_rls.sql
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
-- Enable Row Level Security for spaces tables
|
||||
ALTER TABLE public.spaces ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.space_members ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- RLS policies for spaces
|
||||
|
||||
-- Space owners can do everything with their spaces
|
||||
CREATE POLICY spaces_owner_policy
|
||||
ON public.spaces
|
||||
TO authenticated
|
||||
USING (owner_id = auth.uid());
|
||||
|
||||
-- Members can view spaces they belong to
|
||||
CREATE POLICY spaces_member_select_policy
|
||||
ON public.spaces
|
||||
FOR SELECT
|
||||
TO authenticated
|
||||
USING (
|
||||
id IN (
|
||||
SELECT space_id
|
||||
FROM public.space_members
|
||||
WHERE user_id = auth.uid() AND invitation_status = 'accepted'
|
||||
)
|
||||
);
|
||||
|
||||
-- RLS policies for space_members
|
||||
|
||||
-- Space owners can manage all members
|
||||
CREATE POLICY space_members_owner_policy
|
||||
ON public.space_members
|
||||
TO authenticated
|
||||
USING (
|
||||
space_id IN (
|
||||
SELECT id FROM public.spaces WHERE owner_id = auth.uid()
|
||||
)
|
||||
);
|
||||
|
||||
-- Space admins can manage members (except owners)
|
||||
CREATE POLICY space_members_admin_policy
|
||||
ON public.space_members
|
||||
TO authenticated
|
||||
USING (
|
||||
space_id IN (
|
||||
SELECT space_id FROM public.space_members
|
||||
WHERE user_id = auth.uid() AND role = 'admin' AND invitation_status = 'accepted'
|
||||
)
|
||||
AND role != 'owner'
|
||||
);
|
||||
|
||||
-- Users can see which spaces they are members of
|
||||
CREATE POLICY space_members_self_select_policy
|
||||
ON public.space_members
|
||||
FOR SELECT
|
||||
TO authenticated
|
||||
USING (user_id = auth.uid());
|
||||
|
||||
-- Users can accept/decline their own invitations
|
||||
CREATE POLICY space_members_invitation_update_policy
|
||||
ON public.space_members
|
||||
FOR UPDATE
|
||||
TO authenticated
|
||||
USING (user_id = auth.uid())
|
||||
WITH CHECK (
|
||||
user_id = auth.uid()
|
||||
AND (OLD.invitation_status = 'pending')
|
||||
AND (NEW.invitation_status IN ('accepted', 'declined'))
|
||||
AND (OLD.role = NEW.role)
|
||||
AND (OLD.space_id = NEW.space_id)
|
||||
AND (OLD.user_id = NEW.user_id)
|
||||
);
|
||||
|
||||
-- Update RLS policies for conversations
|
||||
|
||||
-- Modify existing policies to include space-based access
|
||||
DROP POLICY IF EXISTS conversations_select_policy ON conversations;
|
||||
CREATE POLICY conversations_select_policy
|
||||
ON conversations
|
||||
FOR SELECT
|
||||
TO authenticated
|
||||
USING (
|
||||
user_id = auth.uid()
|
||||
OR
|
||||
(
|
||||
space_id IN (
|
||||
SELECT space_id FROM public.space_members
|
||||
WHERE user_id = auth.uid() AND invitation_status = 'accepted'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
-- Allow space members to create conversations in spaces they belong to
|
||||
CREATE POLICY conversations_space_insert_policy
|
||||
ON conversations
|
||||
FOR INSERT
|
||||
TO authenticated
|
||||
WITH CHECK (
|
||||
user_id = auth.uid()
|
||||
AND
|
||||
(
|
||||
space_id IS NULL
|
||||
OR
|
||||
space_id IN (
|
||||
SELECT space_id FROM public.space_members
|
||||
WHERE user_id = auth.uid() AND invitation_status = 'accepted'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
-- Allow updates to conversations in spaces based on role
|
||||
DROP POLICY IF EXISTS conversations_update_policy ON conversations;
|
||||
CREATE POLICY conversations_update_policy
|
||||
ON conversations
|
||||
FOR UPDATE
|
||||
TO authenticated
|
||||
USING (
|
||||
user_id = auth.uid()
|
||||
OR
|
||||
(
|
||||
space_id IN (
|
||||
SELECT sm.space_id FROM public.space_members sm
|
||||
WHERE sm.user_id = auth.uid()
|
||||
AND sm.invitation_status = 'accepted'
|
||||
AND sm.role IN ('owner', 'admin')
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
-- Allow deletion of conversations in spaces based on role
|
||||
DROP POLICY IF EXISTS conversations_delete_policy ON conversations;
|
||||
CREATE POLICY conversations_delete_policy
|
||||
ON conversations
|
||||
FOR DELETE
|
||||
TO authenticated
|
||||
USING (
|
||||
user_id = auth.uid()
|
||||
OR
|
||||
(
|
||||
space_id IN (
|
||||
SELECT sm.space_id FROM public.space_members sm
|
||||
WHERE sm.user_id = auth.uid()
|
||||
AND sm.invitation_status = 'accepted'
|
||||
AND sm.role IN ('owner', 'admin')
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
-- Helper function to check if a user has access to a space
|
||||
CREATE OR REPLACE FUNCTION public.user_has_space_access(space_uuid UUID, role_level TEXT DEFAULT 'viewer')
|
||||
RETURNS BOOLEAN AS $$
|
||||
DECLARE
|
||||
has_access BOOLEAN;
|
||||
role_hierarchy TEXT[];
|
||||
BEGIN
|
||||
-- Define role hierarchy from highest to lowest
|
||||
role_hierarchy := ARRAY['owner', 'admin', 'member', 'viewer'];
|
||||
|
||||
-- Find position of requested role in hierarchy
|
||||
WITH role_positions AS (
|
||||
SELECT
|
||||
unnest(role_hierarchy) AS role,
|
||||
row_number() OVER () AS position
|
||||
)
|
||||
|
||||
SELECT EXISTS (
|
||||
SELECT 1 FROM public.space_members sm
|
||||
JOIN role_positions rp1 ON sm.role = rp1.role
|
||||
JOIN role_positions rp2 ON rp2.role = role_level
|
||||
WHERE sm.space_id = space_uuid
|
||||
AND sm.user_id = auth.uid()
|
||||
AND sm.invitation_status = 'accepted'
|
||||
AND rp1.position <= rp2.position -- Check if user's role is at least the required level
|
||||
) INTO has_access;
|
||||
|
||||
RETURN has_access;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
||||
45
chat/apps/mobile/scripts/spaces/create_spaces_tables.sql
Normal file
45
chat/apps/mobile/scripts/spaces/create_spaces_tables.sql
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
-- Create spaces table
|
||||
CREATE TABLE IF NOT EXISTS public.spaces (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
owner_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
is_archived BOOLEAN DEFAULT false
|
||||
);
|
||||
|
||||
-- Add comments for documentation
|
||||
COMMENT ON TABLE public.spaces IS 'Collaborative spaces for organizing conversations';
|
||||
COMMENT ON COLUMN spaces.name IS 'Name of the space';
|
||||
COMMENT ON COLUMN spaces.description IS 'Optional description of the space';
|
||||
COMMENT ON COLUMN spaces.owner_id IS 'User ID of the space owner';
|
||||
COMMENT ON COLUMN spaces.is_archived IS 'Indicates whether the space is archived';
|
||||
|
||||
-- Create space_members table with roles/permissions
|
||||
CREATE TABLE IF NOT EXISTS public.space_members (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
space_id UUID NOT NULL REFERENCES public.spaces(id) ON DELETE CASCADE,
|
||||
user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
|
||||
role TEXT NOT NULL CHECK (role IN ('owner', 'admin', 'member', 'viewer')),
|
||||
invitation_status TEXT NOT NULL DEFAULT 'pending' CHECK (invitation_status IN ('pending', 'accepted', 'declined')),
|
||||
invited_by UUID REFERENCES public.users(id),
|
||||
invited_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
joined_at TIMESTAMP WITH TIME ZONE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
UNIQUE(space_id, user_id)
|
||||
);
|
||||
|
||||
-- Add comments for space_members
|
||||
COMMENT ON TABLE public.space_members IS 'Members of collaborative spaces with defined roles';
|
||||
COMMENT ON COLUMN space_members.role IS 'Role of the user in the space (owner, admin, member, viewer)';
|
||||
COMMENT ON COLUMN space_members.invitation_status IS 'Status of the invitation (pending, accepted, declined)';
|
||||
|
||||
-- Modify conversations table to add space_id
|
||||
ALTER TABLE public.conversations
|
||||
ADD COLUMN IF NOT EXISTS space_id UUID REFERENCES public.spaces(id) ON DELETE SET NULL;
|
||||
|
||||
-- Create indexes for faster queries
|
||||
CREATE INDEX IF NOT EXISTS idx_conversations_space_id ON conversations(space_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_conversations_space_user ON conversations(space_id, user_id);
|
||||
97
chat/apps/mobile/scripts/spaces/create_spaces_triggers.sql
Normal file
97
chat/apps/mobile/scripts/spaces/create_spaces_triggers.sql
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
-- Create updated_at trigger for spaces
|
||||
CREATE OR REPLACE FUNCTION set_updated_at()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Apply updated_at trigger to spaces table if it doesn't exist
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_trigger WHERE tgname = 'set_spaces_updated_at'
|
||||
) THEN
|
||||
CREATE TRIGGER set_spaces_updated_at
|
||||
BEFORE UPDATE ON public.spaces
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION set_updated_at();
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
-- Apply updated_at trigger to space_members table if it doesn't exist
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_trigger WHERE tgname = 'set_space_members_updated_at'
|
||||
) THEN
|
||||
CREATE TRIGGER set_space_members_updated_at
|
||||
BEFORE UPDATE ON public.space_members
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION set_updated_at();
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
-- Automatically add space owner as member with owner role
|
||||
CREATE OR REPLACE FUNCTION add_owner_to_space_members()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
INSERT INTO public.space_members (
|
||||
space_id,
|
||||
user_id,
|
||||
role,
|
||||
invitation_status,
|
||||
joined_at
|
||||
)
|
||||
VALUES (
|
||||
NEW.id,
|
||||
NEW.owner_id,
|
||||
'owner',
|
||||
'accepted',
|
||||
NOW()
|
||||
);
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Apply owner trigger to spaces table if it doesn't exist
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_trigger WHERE tgname = 'add_owner_to_space_members_trigger'
|
||||
) THEN
|
||||
CREATE TRIGGER add_owner_to_space_members_trigger
|
||||
AFTER INSERT ON public.spaces
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION add_owner_to_space_members();
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
-- Update space modification timestamp when members are added/changed
|
||||
CREATE OR REPLACE FUNCTION update_space_timestamp_on_member_change()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
UPDATE public.spaces
|
||||
SET updated_at = NOW()
|
||||
WHERE id = NEW.space_id;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Apply space timestamp update trigger if it doesn't exist
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_trigger WHERE tgname = 'update_space_timestamp_trigger'
|
||||
) THEN
|
||||
CREATE TRIGGER update_space_timestamp_trigger
|
||||
AFTER INSERT OR UPDATE ON public.space_members
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_space_timestamp_on_member_change();
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
86
chat/apps/mobile/scripts/spaces/fix_rls_policies.sql
Normal file
86
chat/apps/mobile/scripts/spaces/fix_rls_policies.sql
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
-- Drop problematic policies that cause infinite recursion
|
||||
DROP POLICY IF EXISTS space_members_owner_policy ON space_members;
|
||||
DROP POLICY IF EXISTS space_members_admin_policy ON space_members;
|
||||
DROP POLICY IF EXISTS space_members_self_select_policy ON space_members;
|
||||
DROP POLICY IF EXISTS space_members_invitation_update_policy ON space_members;
|
||||
DROP POLICY IF EXISTS conversations_select_policy ON conversations;
|
||||
DROP POLICY IF EXISTS conversations_space_insert_policy ON conversations;
|
||||
DROP POLICY IF EXISTS conversations_update_policy ON conversations;
|
||||
DROP POLICY IF EXISTS conversations_delete_policy ON conversations;
|
||||
|
||||
-- Recreate RLS policies for space_members (simplified to avoid recursion)
|
||||
CREATE POLICY space_members_owner_policy
|
||||
ON public.space_members
|
||||
TO authenticated
|
||||
USING (
|
||||
EXISTS (
|
||||
SELECT 1 FROM public.spaces
|
||||
WHERE id = space_id AND owner_id = auth.uid()
|
||||
)
|
||||
);
|
||||
|
||||
-- Users can see which spaces they are members of
|
||||
CREATE POLICY space_members_self_select_policy
|
||||
ON public.space_members
|
||||
FOR SELECT
|
||||
TO authenticated
|
||||
USING (user_id = auth.uid());
|
||||
|
||||
-- Users can accept/decline their own invitations
|
||||
CREATE POLICY space_members_invitation_update_policy
|
||||
ON public.space_members
|
||||
FOR UPDATE
|
||||
TO authenticated
|
||||
USING (user_id = auth.uid())
|
||||
WITH CHECK (
|
||||
user_id = auth.uid()
|
||||
AND invitation_status = 'pending'
|
||||
);
|
||||
|
||||
-- Create simplified policies for conversations
|
||||
|
||||
-- Allow users to see their own conversations or shared with them
|
||||
CREATE POLICY conversations_select_policy
|
||||
ON conversations
|
||||
FOR SELECT
|
||||
TO authenticated
|
||||
USING (
|
||||
user_id = auth.uid()
|
||||
OR
|
||||
space_id IN (
|
||||
SELECT space_id FROM public.space_members
|
||||
WHERE user_id = auth.uid() AND invitation_status = 'accepted'
|
||||
)
|
||||
);
|
||||
|
||||
-- Allow users to create conversations in spaces they belong to
|
||||
CREATE POLICY conversations_space_insert_policy
|
||||
ON conversations
|
||||
FOR INSERT
|
||||
TO authenticated
|
||||
WITH CHECK (
|
||||
user_id = auth.uid()
|
||||
AND
|
||||
(
|
||||
space_id IS NULL
|
||||
OR
|
||||
space_id IN (
|
||||
SELECT space_id FROM public.space_members
|
||||
WHERE user_id = auth.uid() AND invitation_status = 'accepted'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
-- Allow users to update their own conversations
|
||||
CREATE POLICY conversations_update_policy
|
||||
ON conversations
|
||||
FOR UPDATE
|
||||
TO authenticated
|
||||
USING (user_id = auth.uid());
|
||||
|
||||
-- Allow users to delete their own conversations
|
||||
CREATE POLICY conversations_delete_policy
|
||||
ON conversations
|
||||
FOR DELETE
|
||||
TO authenticated
|
||||
USING (user_id = auth.uid());
|
||||
69
chat/apps/mobile/scripts/spaces/fix_rls_policies_v2.sql
Normal file
69
chat/apps/mobile/scripts/spaces/fix_rls_policies_v2.sql
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
-- Completely drop ALL RLS policies for the affected tables
|
||||
DROP POLICY IF EXISTS spaces_owner_policy ON spaces;
|
||||
DROP POLICY IF EXISTS spaces_member_select_policy ON spaces;
|
||||
DROP POLICY IF EXISTS space_members_owner_policy ON space_members;
|
||||
DROP POLICY IF EXISTS space_members_admin_policy ON space_members;
|
||||
DROP POLICY IF EXISTS space_members_self_select_policy ON space_members;
|
||||
DROP POLICY IF EXISTS space_members_invitation_update_policy ON space_members;
|
||||
DROP POLICY IF EXISTS conversations_select_policy ON conversations;
|
||||
DROP POLICY IF EXISTS conversations_space_insert_policy ON conversations;
|
||||
DROP POLICY IF EXISTS conversations_update_policy ON conversations;
|
||||
DROP POLICY IF EXISTS conversations_delete_policy ON conversations;
|
||||
|
||||
-- Create minimal basic policies for spaces
|
||||
CREATE POLICY spaces_select_policy
|
||||
ON public.spaces
|
||||
FOR SELECT
|
||||
TO authenticated
|
||||
USING (true); -- Allow all users to see all spaces for now
|
||||
|
||||
CREATE POLICY spaces_insert_policy
|
||||
ON public.spaces
|
||||
FOR INSERT
|
||||
TO authenticated
|
||||
WITH CHECK (owner_id = auth.uid()); -- Only allow users to create spaces where they are the owner
|
||||
|
||||
-- Create minimal basic policies for space_members
|
||||
CREATE POLICY space_members_select_policy
|
||||
ON public.space_members
|
||||
FOR SELECT
|
||||
TO authenticated
|
||||
USING (true); -- Allow all users to see all space members for now
|
||||
|
||||
CREATE POLICY space_members_insert_policy
|
||||
ON public.space_members
|
||||
FOR INSERT
|
||||
TO authenticated
|
||||
WITH CHECK (true); -- Allow all insertions for now
|
||||
|
||||
CREATE POLICY space_members_update_policy
|
||||
ON public.space_members
|
||||
FOR UPDATE
|
||||
TO authenticated
|
||||
USING (true) -- Allow all updates for now
|
||||
WITH CHECK (true);
|
||||
|
||||
-- Revert conversations back to simple user-based policies
|
||||
CREATE POLICY conversations_select_policy
|
||||
ON conversations
|
||||
FOR SELECT
|
||||
TO authenticated
|
||||
USING (user_id = auth.uid()); -- Only see your own conversations
|
||||
|
||||
CREATE POLICY conversations_insert_policy
|
||||
ON conversations
|
||||
FOR INSERT
|
||||
TO authenticated
|
||||
WITH CHECK (user_id = auth.uid()); -- Only create your own conversations
|
||||
|
||||
CREATE POLICY conversations_update_policy
|
||||
ON conversations
|
||||
FOR UPDATE
|
||||
TO authenticated
|
||||
USING (user_id = auth.uid()); -- Only update your own conversations
|
||||
|
||||
CREATE POLICY conversations_delete_policy
|
||||
ON conversations
|
||||
FOR DELETE
|
||||
TO authenticated
|
||||
USING (user_id = auth.uid()); -- Only delete your own conversations
|
||||
83
chat/apps/mobile/scripts/spaces/setup_spaces.js
Executable file
83
chat/apps/mobile/scripts/spaces/setup_spaces.js
Executable file
|
|
@ -0,0 +1,83 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* This script sets up the spaces feature by running the necessary SQL scripts
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { spawnSync } = require('child_process');
|
||||
const { createClient } = require('@supabase/supabase-js');
|
||||
|
||||
// Get environment variables
|
||||
const SUPABASE_URL = process.env.SUPABASE_URL;
|
||||
const SUPABASE_SERVICE_KEY = process.env.SUPABASE_SERVICE_KEY;
|
||||
|
||||
if (!SUPABASE_URL || !SUPABASE_SERVICE_KEY) {
|
||||
console.error('Error: SUPABASE_URL and SUPABASE_SERVICE_KEY environment variables must be set');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Create Supabase client
|
||||
const supabase = createClient(SUPABASE_URL, SUPABASE_SERVICE_KEY, {
|
||||
auth: {
|
||||
autoRefreshToken: false,
|
||||
persistSession: false
|
||||
}
|
||||
});
|
||||
|
||||
async function executeSQL(filename) {
|
||||
try {
|
||||
const filePath = path.join(__dirname, filename);
|
||||
const sql = fs.readFileSync(filePath, 'utf8');
|
||||
|
||||
// Split the SQL file by semicolons to get individual statements
|
||||
const statements = sql
|
||||
.split(';')
|
||||
.map(statement => statement.trim())
|
||||
.filter(statement => statement.length > 0);
|
||||
|
||||
console.log(`Executing ${statements.length} statements from ${filename}...`);
|
||||
|
||||
for (const statement of statements) {
|
||||
const { error } = await supabase.rpc('exec_sql', { sql: statement });
|
||||
|
||||
if (error) {
|
||||
console.error(`Error executing statement:`, error);
|
||||
console.error(`Statement was: ${statement.substring(0, 100)}...`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`✅ Successfully executed ${filename}`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(`❌ Error executing ${filename}:`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('Setting up spaces feature...');
|
||||
|
||||
// Run the SQL scripts in the correct order
|
||||
const scripts = [
|
||||
'create_spaces_tables.sql',
|
||||
'create_spaces_triggers.sql',
|
||||
'create_spaces_rls.sql'
|
||||
];
|
||||
|
||||
for (const script of scripts) {
|
||||
const success = await executeSQL(script);
|
||||
if (!success) {
|
||||
console.error(`Failed to execute ${script}. Aborting.`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('✅ Spaces feature setup complete!');
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error('Unhandled error:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue