Spaces:
Sleeping
Sleeping
| require('dotenv').config(); | |
| const { query } = require('./db'); | |
| const migrate = async () => { | |
| console.log('Running database migrations...'); | |
| await query(` | |
| CREATE TABLE IF NOT EXISTS organizations ( | |
| id UUID PRIMARY KEY DEFAULT gen_random_uuid(), | |
| name VARCHAR(255) NOT NULL, | |
| slug VARCHAR(100) UNIQUE NOT NULL, | |
| created_at TIMESTAMPTZ DEFAULT NOW(), | |
| updated_at TIMESTAMPTZ DEFAULT NOW() | |
| ); | |
| `); | |
| await query(` | |
| CREATE TABLE IF NOT EXISTS users ( | |
| id UUID PRIMARY KEY DEFAULT gen_random_uuid(), | |
| organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, | |
| name VARCHAR(255) NOT NULL, | |
| email VARCHAR(255) NOT NULL, | |
| password_hash VARCHAR(255), | |
| role VARCHAR(50) NOT NULL DEFAULT 'member' CHECK (role IN ('admin', 'member')), | |
| oauth_provider VARCHAR(50), | |
| oauth_provider_id VARCHAR(255), | |
| is_active BOOLEAN DEFAULT TRUE, | |
| created_at TIMESTAMPTZ DEFAULT NOW(), | |
| updated_at TIMESTAMPTZ DEFAULT NOW(), | |
| UNIQUE(email, organization_id) | |
| ); | |
| `); | |
| await query(`CREATE INDEX IF NOT EXISTS idx_users_org ON users(organization_id);`); | |
| await query(`CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);`); | |
| await query(` | |
| CREATE TABLE IF NOT EXISTS tasks ( | |
| id UUID PRIMARY KEY DEFAULT gen_random_uuid(), | |
| organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, | |
| title VARCHAR(500) NOT NULL, | |
| description TEXT, | |
| status VARCHAR(50) NOT NULL DEFAULT 'todo' CHECK (status IN ('todo', 'in_progress', 'in_review', 'done', 'cancelled')), | |
| priority VARCHAR(50) NOT NULL DEFAULT 'medium' CHECK (priority IN ('low', 'medium', 'high', 'critical')), | |
| creator_id UUID NOT NULL REFERENCES users(id) ON DELETE SET NULL, | |
| assignee_id UUID REFERENCES users(id) ON DELETE SET NULL, | |
| due_date TIMESTAMPTZ, | |
| created_at TIMESTAMPTZ DEFAULT NOW(), | |
| updated_at TIMESTAMPTZ DEFAULT NOW() | |
| ); | |
| `); | |
| await query(`CREATE INDEX IF NOT EXISTS idx_tasks_org ON tasks(organization_id);`); | |
| await query(`CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(organization_id, status);`); | |
| await query(`CREATE INDEX IF NOT EXISTS idx_tasks_assignee ON tasks(assignee_id);`); | |
| await query(`CREATE INDEX IF NOT EXISTS idx_tasks_priority ON tasks(organization_id, priority);`); | |
| await query(`CREATE INDEX IF NOT EXISTS idx_tasks_due_date ON tasks(organization_id, due_date);`); | |
| await query(` | |
| CREATE TABLE IF NOT EXISTS task_comments ( | |
| id UUID PRIMARY KEY DEFAULT gen_random_uuid(), | |
| task_id UUID NOT NULL REFERENCES tasks(id) ON DELETE CASCADE, | |
| organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, | |
| author_id UUID NOT NULL REFERENCES users(id) ON DELETE SET NULL, | |
| body TEXT NOT NULL, | |
| created_at TIMESTAMPTZ DEFAULT NOW(), | |
| updated_at TIMESTAMPTZ DEFAULT NOW() | |
| ); | |
| `); | |
| await query(`CREATE INDEX IF NOT EXISTS idx_comments_task ON task_comments(task_id);`); | |
| await query(` | |
| CREATE TABLE IF NOT EXISTS audit_logs ( | |
| id UUID PRIMARY KEY DEFAULT gen_random_uuid(), | |
| organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, | |
| task_id UUID REFERENCES tasks(id) ON DELETE SET NULL, | |
| actor_id UUID REFERENCES users(id) ON DELETE SET NULL, | |
| actor_name VARCHAR(255) NOT NULL, | |
| actor_email VARCHAR(255) NOT NULL, | |
| action VARCHAR(100) NOT NULL, | |
| entity_type VARCHAR(50) NOT NULL DEFAULT 'task', | |
| old_values JSONB, | |
| new_values JSONB, | |
| metadata JSONB, | |
| created_at TIMESTAMPTZ DEFAULT NOW() | |
| ); | |
| `); | |
| await query(`CREATE INDEX IF NOT EXISTS idx_audit_org ON audit_logs(organization_id);`); | |
| await query(`CREATE INDEX IF NOT EXISTS idx_audit_task ON audit_logs(task_id);`); | |
| await query(`CREATE INDEX IF NOT EXISTS idx_audit_created ON audit_logs(organization_id, created_at DESC);`); | |
| await query(` | |
| CREATE TABLE IF NOT EXISTS invites ( | |
| id UUID PRIMARY KEY DEFAULT gen_random_uuid(), | |
| organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, | |
| email VARCHAR(255) NOT NULL, | |
| role VARCHAR(50) NOT NULL DEFAULT 'member', | |
| token VARCHAR(255) UNIQUE NOT NULL, | |
| invited_by UUID REFERENCES users(id) ON DELETE SET NULL, | |
| expires_at TIMESTAMPTZ NOT NULL, | |
| used_at TIMESTAMPTZ, | |
| created_at TIMESTAMPTZ DEFAULT NOW() | |
| ); | |
| `); | |
| await query(`CREATE INDEX IF NOT EXISTS idx_invites_token ON invites(token);`); | |
| await query(`CREATE INDEX IF NOT EXISTS idx_invites_org ON invites(organization_id);`); | |
| console.log('✅ Migrations complete.'); | |
| process.exit(0); | |
| }; | |
| migrate().catch(err => { | |
| console.error('Migration failed:', err); | |
| process.exit(1); | |
| }); | |