Mega push vol 4

This commit is contained in:
AlacrisDevs
2026-02-06 16:08:40 +02:00
parent b517bb975c
commit d8bbfd9dc3
95 changed files with 8019 additions and 3946 deletions

View File

@@ -0,0 +1,66 @@
-- Kanban Labels System
-- Adds label/tag support to kanban cards for categorization and filtering
-- Labels table (org-scoped)
CREATE TABLE IF NOT EXISTS kanban_labels (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
name TEXT NOT NULL,
color TEXT NOT NULL DEFAULT '#6366f1',
created_at TIMESTAMPTZ DEFAULT now(),
UNIQUE(org_id, name)
);
-- Card-Label junction table (many-to-many)
CREATE TABLE IF NOT EXISTS card_labels (
card_id UUID NOT NULL REFERENCES kanban_cards(id) ON DELETE CASCADE,
label_id UUID NOT NULL REFERENCES kanban_labels(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ DEFAULT now(),
PRIMARY KEY (card_id, label_id)
);
-- Enable RLS
ALTER TABLE kanban_labels ENABLE ROW LEVEL SECURITY;
ALTER TABLE card_labels ENABLE ROW LEVEL SECURITY;
-- Labels accessible to org members
CREATE POLICY "Labels accessible to org members" ON kanban_labels
FOR ALL USING (
EXISTS (
SELECT 1 FROM org_members m
WHERE m.org_id = kanban_labels.org_id
AND m.user_id = auth.uid()
)
);
-- Card labels inherit card access
CREATE POLICY "Card labels inherit card access" ON card_labels
FOR ALL USING (
EXISTS (
SELECT 1 FROM kanban_cards c
JOIN kanban_columns col ON c.column_id = col.id
JOIN kanban_boards b ON col.board_id = b.id
JOIN org_members m ON b.org_id = m.org_id
WHERE c.id = card_labels.card_id
AND m.user_id = auth.uid()
)
);
-- Indexes for performance
CREATE INDEX IF NOT EXISTS idx_kanban_labels_org ON kanban_labels(org_id);
CREATE INDEX IF NOT EXISTS idx_card_labels_card ON card_labels(card_id);
CREATE INDEX IF NOT EXISTS idx_card_labels_label ON card_labels(label_id);
-- Seed default labels for existing organizations
INSERT INTO kanban_labels (org_id, name, color)
SELECT DISTINCT o.id, label.name, label.color
FROM organizations o
CROSS JOIN (
VALUES
('Bug', '#ef4444'),
('Feature', '#22c55e'),
('Enhancement', '#3b82f6'),
('Documentation', '#a855f7'),
('Urgent', '#f97316')
) AS label(name, color)
ON CONFLICT (org_id, name) DO NOTHING;

View File

@@ -0,0 +1,101 @@
-- Migration: Document enhancements for file paths, positions, and kanban type
-- Adds path column for Google Drive-like unique paths
-- Adds position column for file ordering within folders
-- Extends type enum to include 'kanban' for kanban boards as files
-- Add path column (computed from parent hierarchy)
ALTER TABLE documents ADD COLUMN IF NOT EXISTS path TEXT;
-- Add position column for ordering files within a folder
ALTER TABLE documents ADD COLUMN IF NOT EXISTS position INTEGER DEFAULT 0;
-- Update the type constraint to allow 'kanban'
-- First drop the existing constraint if it exists
ALTER TABLE documents DROP CONSTRAINT IF EXISTS documents_type_check;
-- Add new constraint with kanban type
ALTER TABLE documents ADD CONSTRAINT documents_type_check
CHECK (type IN ('folder', 'document', 'kanban'));
-- Function to compute and update document path
CREATE OR REPLACE FUNCTION compute_document_path(doc_id UUID)
RETURNS TEXT AS $$
DECLARE
result TEXT := '';
current_doc RECORD;
path_parts TEXT[] := '{}';
BEGIN
-- Start with the document itself
SELECT * INTO current_doc FROM documents WHERE id = doc_id;
IF NOT FOUND THEN
RETURN NULL;
END IF;
-- Build path from document up to root
WHILE current_doc IS NOT NULL LOOP
path_parts := array_prepend(current_doc.name, path_parts);
IF current_doc.parent_id IS NULL THEN
EXIT;
END IF;
SELECT * INTO current_doc FROM documents WHERE id = current_doc.parent_id;
END LOOP;
-- Join with '/' separator
result := '/' || array_to_string(path_parts, '/');
RETURN result;
END;
$$ LANGUAGE plpgsql;
-- Function to update path on insert/update
CREATE OR REPLACE FUNCTION update_document_path()
RETURNS TRIGGER AS $$
BEGIN
NEW.path := compute_document_path(NEW.id);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Trigger to auto-update path
DROP TRIGGER IF EXISTS documents_path_trigger ON documents;
CREATE TRIGGER documents_path_trigger
BEFORE INSERT OR UPDATE OF name, parent_id ON documents
FOR EACH ROW
EXECUTE FUNCTION update_document_path();
-- Function to get next position in folder
CREATE OR REPLACE FUNCTION get_next_document_position(folder_id UUID)
RETURNS INTEGER AS $$
DECLARE
max_pos INTEGER;
BEGIN
SELECT COALESCE(MAX(position), -1) + 1 INTO max_pos
FROM documents
WHERE parent_id IS NOT DISTINCT FROM folder_id;
RETURN max_pos;
END;
$$ LANGUAGE plpgsql;
-- Update existing documents to have computed paths
UPDATE documents SET path = compute_document_path(id) WHERE path IS NULL;
-- Update existing documents to have sequential positions within their folders
WITH numbered AS (
SELECT id, ROW_NUMBER() OVER (PARTITION BY parent_id ORDER BY created_at) - 1 AS new_pos
FROM documents
WHERE position IS NULL OR position = 0
)
UPDATE documents d
SET position = n.new_pos
FROM numbered n
WHERE d.id = n.id;
-- Create index on path for faster lookups
CREATE INDEX IF NOT EXISTS idx_documents_path ON documents(path);
-- Create index on parent_id and position for faster ordering
CREATE INDEX IF NOT EXISTS idx_documents_parent_position ON documents(parent_id, position);

View File

@@ -0,0 +1,45 @@
-- Migration: Move existing kanban boards to documents table
-- This creates document entries for each kanban_board with type 'kanban'
-- The content field stores reference to the board_id for backwards compatibility
-- Insert existing kanban boards as documents
INSERT INTO documents (id, org_id, parent_id, type, name, path, position, content, created_by, created_at, updated_at)
SELECT
kb.id,
kb.org_id,
NULL as parent_id,
'kanban' as type,
kb.name,
'/' || kb.name as path,
COALESCE((
SELECT COUNT(*) FROM documents d
WHERE d.org_id = kb.org_id AND d.parent_id IS NULL
), 0) as position,
jsonb_build_object(
'type', 'kanban',
'board_id', kb.id
) as content,
COALESCE(kb.created_by, (
SELECT user_id FROM org_members
WHERE org_id = kb.org_id
ORDER BY invited_at ASC
LIMIT 1
)) as created_by,
kb.created_at,
kb.created_at as updated_at
FROM kanban_boards kb
WHERE NOT EXISTS (
SELECT 1 FROM documents d
WHERE d.id = kb.id
);
-- Update any duplicate paths by appending board ID
UPDATE documents
SET path = path || '-' || SUBSTRING(id::text, 1, 8)
WHERE type = 'kanban'
AND id IN (
SELECT d1.id
FROM documents d1
JOIN documents d2 ON d1.path = d2.path AND d1.id != d2.id
WHERE d1.type = 'kanban'
);

View File

@@ -0,0 +1,41 @@
-- Document locks: track who is currently editing a document
-- Uses a heartbeat model: editors must refresh their lock periodically
-- Stale locks (no heartbeat for 60s) are considered expired
CREATE TABLE document_locks (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
document_id UUID NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
locked_at TIMESTAMPTZ NOT NULL DEFAULT now(),
last_heartbeat TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE(document_id)
);
-- Index for fast lookups
CREATE INDEX idx_document_locks_document ON document_locks(document_id);
CREATE INDEX idx_document_locks_heartbeat ON document_locks(last_heartbeat);
-- RLS
ALTER TABLE document_locks ENABLE ROW LEVEL SECURITY;
-- Anyone in the org can view locks (to see who's editing)
CREATE POLICY "Org members can view document locks" ON document_locks FOR SELECT
USING (EXISTS (
SELECT 1 FROM documents d
JOIN org_members om ON d.org_id = om.org_id
WHERE d.id = document_locks.document_id AND om.user_id = auth.uid()
));
-- Users can manage their own locks
CREATE POLICY "Users can insert their own locks" ON document_locks FOR INSERT
WITH CHECK (user_id = auth.uid());
CREATE POLICY "Users can update their own locks" ON document_locks FOR UPDATE
USING (user_id = auth.uid());
CREATE POLICY "Users can delete their own locks" ON document_locks FOR DELETE
USING (user_id = auth.uid());
-- Allow taking over expired locks (heartbeat older than 60 seconds)
CREATE POLICY "Anyone can delete expired locks" ON document_locks FOR DELETE
USING (last_heartbeat < now() - interval '60 seconds');

View File

@@ -0,0 +1,28 @@
-- Create storage bucket for avatars
INSERT INTO storage.buckets (id, name, public)
VALUES ('avatars', 'avatars', true)
ON CONFLICT (id) DO NOTHING;
-- Allow authenticated users to upload to org-avatars folder
CREATE POLICY "Authenticated users can upload org avatars"
ON storage.objects FOR INSERT
TO authenticated
WITH CHECK (bucket_id = 'avatars' AND (storage.foldername(name))[1] = 'org-avatars');
-- Allow authenticated users to update (upsert) their org avatars
CREATE POLICY "Authenticated users can update org avatars"
ON storage.objects FOR UPDATE
TO authenticated
USING (bucket_id = 'avatars' AND (storage.foldername(name))[1] = 'org-avatars');
-- Allow public read access to all avatars
CREATE POLICY "Public read access for avatars"
ON storage.objects FOR SELECT
TO public
USING (bucket_id = 'avatars');
-- Allow authenticated users to delete org avatars
CREATE POLICY "Authenticated users can delete org avatars"
ON storage.objects FOR DELETE
TO authenticated
USING (bucket_id = 'avatars' AND (storage.foldername(name))[1] = 'org-avatars');