rdev/internal/db/migrations/013_project_domains.sql
jordan 1ac8efa4c7 feat: Expose Woodpecker pipeline errors in API response
- Add CIPipelineError struct to domain with Type, Message, IsWarning fields
- Map Woodpecker Pipeline.Errors to domain.CIPipeline.Errors
- Fix migration 013: UUID type for project_id, cast id to text for MD5
- Remove invalid domain data migration (columns don't exist)
- Update release.sh with --deploy flag and migration support
- Fix test nil pointer: check errors in TestAPIKeyRepository_ProjectIDArrayHandling

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 16:16:36 -07:00

69 lines
3.3 KiB
PL/PgSQL

-- Add slug to projects and create project_domains table for flexible multi-domain support.
-- This enables:
-- 1. Auto-generated random subdomains (e.g., k7m2x9p4.threesix.ai)
-- 2. Optional custom subdomains (e.g., my-app.threesix.ai)
-- 3. Multiple alias domains per project (e.g., www.myapp.com, myapp.com)
-- 4. Clean tracking of all DNS records for resource management
-- Add slug column to projects (immutable, auto-generated identifier)
ALTER TABLE projects ADD COLUMN IF NOT EXISTS
slug VARCHAR(16);
-- Backfill existing projects with slugs derived from their IDs
-- Uses first 8 chars of MD5 hash for consistent, reproducible slugs
UPDATE projects
SET slug = LOWER(SUBSTRING(MD5(id::text), 1, 8))
WHERE slug IS NULL;
-- Make slug NOT NULL and UNIQUE after backfill
ALTER TABLE projects ALTER COLUMN slug SET NOT NULL;
CREATE UNIQUE INDEX IF NOT EXISTS idx_projects_slug ON projects(slug);
-- Project domains table for flexible multi-domain support
CREATE TABLE IF NOT EXISTS project_domains (
id SERIAL PRIMARY KEY,
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
domain VARCHAR(255) NOT NULL,
type VARCHAR(20) NOT NULL CHECK (type IN ('primary_auto', 'primary_custom', 'alias')),
dns_record_id VARCHAR(64), -- Cloudflare record ID for cleanup
dns_record_type VARCHAR(10), -- A, CNAME, etc.
verified BOOLEAN DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT unique_domain UNIQUE (domain)
);
-- Indexes for common queries
CREATE INDEX IF NOT EXISTS idx_project_domains_project_id ON project_domains(project_id);
CREATE INDEX IF NOT EXISTS idx_project_domains_type ON project_domains(type);
-- Note: Migration of existing domain data was removed as projects table
-- does not have domain/custom_domain columns. Domains are created via API
-- when deploying projects with auto-generated slugs.
-- Update trigger for project_domains updated_at
CREATE OR REPLACE FUNCTION update_project_domains_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS project_domains_updated_at ON project_domains;
CREATE TRIGGER project_domains_updated_at
BEFORE UPDATE ON project_domains
FOR EACH ROW
EXECUTE FUNCTION update_project_domains_updated_at();
-- Comments
COMMENT ON TABLE project_domains IS 'Domains associated with projects - supports multiple domains per project';
COMMENT ON COLUMN project_domains.project_id IS 'Reference to parent project';
COMMENT ON COLUMN project_domains.domain IS 'Fully qualified domain name (e.g., k7m2x9p4.threesix.ai or www.myapp.com)';
COMMENT ON COLUMN project_domains.type IS 'Domain type: primary_auto (system-generated), primary_custom (user subdomain), alias (external domain)';
COMMENT ON COLUMN project_domains.dns_record_id IS 'Cloudflare DNS record ID for automated cleanup';
COMMENT ON COLUMN project_domains.dns_record_type IS 'DNS record type: A, CNAME, etc.';
COMMENT ON COLUMN project_domains.verified IS 'Whether domain ownership has been verified (for external domains)';
COMMENT ON COLUMN projects.slug IS 'Immutable 8-char random identifier used for auto-generated subdomain';