264 lines
13 KiB
SQL
264 lines
13 KiB
SQL
-- =============================================================================
|
|
-- Life OS - R0 to R1 Data Migration (FIXED)
|
|
-- Source: lifeos_prod (R0 schema - actual)
|
|
-- Target: lifeos_dev (R1 schema)
|
|
--
|
|
-- PREREQUISITE: R1 schema must already be applied to lifeos_dev
|
|
-- RUN FROM: lifeos_dev database as postgres user
|
|
-- =============================================================================
|
|
|
|
CREATE EXTENSION IF NOT EXISTS dblink;
|
|
|
|
-- =============================================================================
|
|
-- 1. DOMAINS
|
|
-- R0: id, name, color, created_at
|
|
-- R1: + description, icon, sort_order, is_deleted, deleted_at, updated_at, search_vector
|
|
-- =============================================================================
|
|
|
|
INSERT INTO domains (id, name, color, sort_order, is_deleted, created_at, updated_at)
|
|
SELECT id, name, color,
|
|
(ROW_NUMBER() OVER (ORDER BY created_at))::INTEGER * 10,
|
|
false, created_at, created_at
|
|
FROM dblink('dbname=lifeos_prod', '
|
|
SELECT id, name, color, created_at FROM domains
|
|
') AS r0(id UUID, name TEXT, color TEXT, created_at TIMESTAMPTZ);
|
|
|
|
-- =============================================================================
|
|
-- 2. AREAS
|
|
-- R0: id, domain_id, name, description, status, created_at
|
|
-- R1: + icon, color, sort_order, is_deleted, deleted_at, updated_at, search_vector
|
|
-- =============================================================================
|
|
|
|
INSERT INTO areas (id, domain_id, name, description, status, sort_order, is_deleted, created_at, updated_at)
|
|
SELECT id, domain_id, name, description, COALESCE(status, 'active'),
|
|
(ROW_NUMBER() OVER (PARTITION BY domain_id ORDER BY created_at))::INTEGER * 10,
|
|
false, created_at, created_at
|
|
FROM dblink('dbname=lifeos_prod', '
|
|
SELECT id, domain_id, name, description, status, created_at FROM areas
|
|
') AS r0(id UUID, domain_id UUID, name TEXT, description TEXT, status TEXT, created_at TIMESTAMPTZ);
|
|
|
|
-- =============================================================================
|
|
-- 3. PROJECTS
|
|
-- R0: id, domain_id, name, description, status, priority, start_date,
|
|
-- target_date, completed_at, tags, created_at, updated_at, area_id
|
|
-- R1: + color, sort_order, is_deleted, deleted_at, search_vector
|
|
-- =============================================================================
|
|
|
|
INSERT INTO projects (id, domain_id, area_id, name, description, status, priority,
|
|
start_date, target_date, completed_at, tags, sort_order, is_deleted, created_at, updated_at)
|
|
SELECT id, domain_id, area_id, name, description,
|
|
COALESCE(status, 'active'), COALESCE(priority, 3),
|
|
start_date, target_date, completed_at, tags,
|
|
(ROW_NUMBER() OVER (PARTITION BY domain_id ORDER BY created_at))::INTEGER * 10,
|
|
false, created_at, COALESCE(updated_at, created_at)
|
|
FROM dblink('dbname=lifeos_prod', '
|
|
SELECT id, domain_id, area_id, name, description, status, priority,
|
|
start_date, target_date, completed_at, tags, created_at, updated_at
|
|
FROM projects
|
|
') AS r0(
|
|
id UUID, domain_id UUID, area_id UUID, name TEXT, description TEXT,
|
|
status TEXT, priority INTEGER, start_date DATE, target_date DATE,
|
|
completed_at TIMESTAMPTZ, tags TEXT[], created_at TIMESTAMPTZ, updated_at TIMESTAMPTZ
|
|
);
|
|
|
|
-- =============================================================================
|
|
-- 4. TASKS
|
|
-- R0: id, domain_id, project_id, parent_id, title, description, priority,
|
|
-- status, due_date, deadline, recurrence, tags, context, is_custom_context,
|
|
-- created_at, updated_at, completed_at
|
|
-- R1: + area_id, release_id, estimated_minutes, energy_required,
|
|
-- waiting_for_contact_id, waiting_since, import_batch_id,
|
|
-- sort_order, is_deleted, deleted_at, search_vector
|
|
-- NOTE: R0 has no area_id on tasks. Left NULL in R1.
|
|
-- =============================================================================
|
|
|
|
INSERT INTO tasks (id, domain_id, project_id, parent_id, title, description,
|
|
priority, status, due_date, deadline, recurrence, tags, context,
|
|
is_custom_context, sort_order, is_deleted, created_at, updated_at, completed_at)
|
|
SELECT id, domain_id, project_id, parent_id, title, description,
|
|
COALESCE(priority, 3), COALESCE(status, 'open'),
|
|
due_date, deadline, recurrence, tags, context,
|
|
COALESCE(is_custom_context, false),
|
|
(ROW_NUMBER() OVER (PARTITION BY project_id ORDER BY created_at))::INTEGER * 10,
|
|
false, created_at, COALESCE(updated_at, created_at), completed_at
|
|
FROM dblink('dbname=lifeos_prod', '
|
|
SELECT id, domain_id, project_id, parent_id, title, description,
|
|
priority, status, due_date, deadline, recurrence, tags, context,
|
|
is_custom_context, created_at, updated_at, completed_at
|
|
FROM tasks
|
|
') AS r0(
|
|
id UUID, domain_id UUID, project_id UUID, parent_id UUID,
|
|
title TEXT, description TEXT, priority INTEGER, status TEXT,
|
|
due_date DATE, deadline TIMESTAMPTZ, recurrence TEXT, tags TEXT[],
|
|
context TEXT, is_custom_context BOOLEAN,
|
|
created_at TIMESTAMPTZ, updated_at TIMESTAMPTZ, completed_at TIMESTAMPTZ
|
|
);
|
|
|
|
-- =============================================================================
|
|
-- 5. NOTES
|
|
-- R0: id, domain_id, project_id, task_id, title, body, tags,
|
|
-- created_at, updated_at, content_format (default 'markdown')
|
|
-- R1: + folder_id, meeting_id, is_meeting_note, sort_order,
|
|
-- is_deleted, deleted_at, search_vector
|
|
-- Transform: content_format 'markdown' -> 'rich'
|
|
-- NOTE: R0 task_id dropped (no equivalent in R1 notes).
|
|
-- =============================================================================
|
|
|
|
INSERT INTO notes (id, domain_id, project_id, title, body, content_format, tags,
|
|
is_meeting_note, sort_order, is_deleted, created_at, updated_at)
|
|
SELECT id, domain_id, project_id,
|
|
CASE WHEN title IS NULL OR title = '' THEN 'Untitled Note' ELSE title END,
|
|
body,
|
|
CASE WHEN content_format = 'markdown' THEN 'rich' ELSE COALESCE(content_format, 'rich') END,
|
|
tags, false,
|
|
(ROW_NUMBER() OVER (ORDER BY created_at))::INTEGER * 10,
|
|
false, created_at, COALESCE(updated_at, created_at)
|
|
FROM dblink('dbname=lifeos_prod', '
|
|
SELECT id, domain_id, project_id, title, body, content_format, tags,
|
|
created_at, updated_at
|
|
FROM notes
|
|
') AS r0(
|
|
id UUID, domain_id UUID, project_id UUID, title TEXT, body TEXT,
|
|
content_format TEXT, tags TEXT[],
|
|
created_at TIMESTAMPTZ, updated_at TIMESTAMPTZ
|
|
);
|
|
|
|
-- =============================================================================
|
|
-- 6. LINKS
|
|
-- R0: id, domain_id, project_id, task_id, label, url, description, created_at
|
|
-- R1: + area_id, tags, sort_order, is_deleted, deleted_at, updated_at, search_vector
|
|
-- NOTE: R0 task_id dropped (no equivalent in R1 links).
|
|
-- =============================================================================
|
|
|
|
INSERT INTO links (id, domain_id, project_id, label, url, description,
|
|
sort_order, is_deleted, created_at, updated_at)
|
|
SELECT id, domain_id, project_id, label, url, description,
|
|
(ROW_NUMBER() OVER (ORDER BY created_at))::INTEGER * 10,
|
|
false, created_at, created_at
|
|
FROM dblink('dbname=lifeos_prod', '
|
|
SELECT id, domain_id, project_id, label, url, description, created_at
|
|
FROM links
|
|
') AS r0(
|
|
id UUID, domain_id UUID, project_id UUID,
|
|
label TEXT, url TEXT, description TEXT, created_at TIMESTAMPTZ
|
|
);
|
|
|
|
-- =============================================================================
|
|
-- 7. DAILY FOCUS
|
|
-- R0: id, focus_date, task_id, slot, completed, note, created_at
|
|
-- R1: + sort_order, is_deleted, deleted_at
|
|
-- =============================================================================
|
|
|
|
INSERT INTO daily_focus (id, focus_date, task_id, slot, completed, note,
|
|
sort_order, is_deleted, created_at)
|
|
SELECT id, focus_date, task_id, slot, COALESCE(completed, false), note,
|
|
COALESCE(slot, (ROW_NUMBER() OVER (PARTITION BY focus_date ORDER BY created_at))::INTEGER) * 10,
|
|
false, created_at
|
|
FROM dblink('dbname=lifeos_prod', '
|
|
SELECT id, focus_date, task_id, slot, completed, note, created_at
|
|
FROM daily_focus
|
|
') AS r0(
|
|
id UUID, focus_date DATE, task_id UUID, slot INTEGER,
|
|
completed BOOLEAN, note TEXT, created_at TIMESTAMPTZ
|
|
);
|
|
|
|
-- =============================================================================
|
|
-- 8. CAPTURE
|
|
-- R0: id, raw_text, processed, task_id, created_at
|
|
-- R1: + converted_to_type, converted_to_id, area_id, project_id, list_id,
|
|
-- import_batch_id, sort_order, is_deleted, deleted_at
|
|
-- Map: R0 task_id -> R1 converted_to_type='task', converted_to_id=task_id
|
|
-- =============================================================================
|
|
|
|
INSERT INTO capture (id, raw_text, processed, converted_to_type, converted_to_id,
|
|
sort_order, is_deleted, created_at)
|
|
SELECT id, raw_text, COALESCE(processed, false),
|
|
CASE WHEN task_id IS NOT NULL THEN 'task' ELSE NULL END,
|
|
task_id,
|
|
(ROW_NUMBER() OVER (ORDER BY created_at))::INTEGER * 10,
|
|
false, created_at
|
|
FROM dblink('dbname=lifeos_prod', '
|
|
SELECT id, raw_text, processed, task_id, created_at FROM capture
|
|
') AS r0(
|
|
id UUID, raw_text TEXT, processed BOOLEAN, task_id UUID, created_at TIMESTAMPTZ
|
|
);
|
|
|
|
-- =============================================================================
|
|
-- 9. CONTEXT TYPES
|
|
-- R0: id (SERIAL), name, description, is_system
|
|
-- R1: id (SERIAL), value, label, description, is_system, sort_order, is_deleted
|
|
-- Map: R0.name -> R1.value, generate label from name via INITCAP
|
|
-- =============================================================================
|
|
|
|
DELETE FROM context_types;
|
|
|
|
INSERT INTO context_types (id, value, label, description, is_system, sort_order, is_deleted)
|
|
SELECT id, name,
|
|
INITCAP(REPLACE(name, '_', ' ')),
|
|
description, COALESCE(is_system, true),
|
|
id * 10,
|
|
false
|
|
FROM dblink('dbname=lifeos_prod', '
|
|
SELECT id, name, description, is_system FROM context_types
|
|
') AS r0(id INTEGER, name TEXT, description TEXT, is_system BOOLEAN);
|
|
|
|
SELECT setval('context_types_id_seq', GREATEST((SELECT MAX(id) FROM context_types), 1));
|
|
|
|
-- =============================================================================
|
|
-- VERIFICATION
|
|
-- =============================================================================
|
|
|
|
DO $$
|
|
DECLARE
|
|
r0_domains INTEGER;
|
|
r0_areas INTEGER;
|
|
r0_projects INTEGER;
|
|
r0_tasks INTEGER;
|
|
r0_notes INTEGER;
|
|
r0_links INTEGER;
|
|
r0_daily_focus INTEGER;
|
|
r0_capture INTEGER;
|
|
r0_context INTEGER;
|
|
r1_domains INTEGER;
|
|
r1_areas INTEGER;
|
|
r1_projects INTEGER;
|
|
r1_tasks INTEGER;
|
|
r1_notes INTEGER;
|
|
r1_links INTEGER;
|
|
r1_daily_focus INTEGER;
|
|
r1_capture INTEGER;
|
|
r1_context INTEGER;
|
|
BEGIN
|
|
SELECT count INTO r0_domains FROM dblink('dbname=lifeos_prod', 'SELECT count(*) FROM domains') AS t(count INTEGER);
|
|
SELECT count INTO r0_areas FROM dblink('dbname=lifeos_prod', 'SELECT count(*) FROM areas') AS t(count INTEGER);
|
|
SELECT count INTO r0_projects FROM dblink('dbname=lifeos_prod', 'SELECT count(*) FROM projects') AS t(count INTEGER);
|
|
SELECT count INTO r0_tasks FROM dblink('dbname=lifeos_prod', 'SELECT count(*) FROM tasks') AS t(count INTEGER);
|
|
SELECT count INTO r0_notes FROM dblink('dbname=lifeos_prod', 'SELECT count(*) FROM notes') AS t(count INTEGER);
|
|
SELECT count INTO r0_links FROM dblink('dbname=lifeos_prod', 'SELECT count(*) FROM links') AS t(count INTEGER);
|
|
SELECT count INTO r0_daily_focus FROM dblink('dbname=lifeos_prod', 'SELECT count(*) FROM daily_focus') AS t(count INTEGER);
|
|
SELECT count INTO r0_capture FROM dblink('dbname=lifeos_prod', 'SELECT count(*) FROM capture') AS t(count INTEGER);
|
|
SELECT count INTO r0_context FROM dblink('dbname=lifeos_prod', 'SELECT count(*) FROM context_types') AS t(count INTEGER);
|
|
|
|
SELECT count(*) INTO r1_domains FROM domains;
|
|
SELECT count(*) INTO r1_areas FROM areas;
|
|
SELECT count(*) INTO r1_projects FROM projects;
|
|
SELECT count(*) INTO r1_tasks FROM tasks;
|
|
SELECT count(*) INTO r1_notes FROM notes;
|
|
SELECT count(*) INTO r1_links FROM links;
|
|
SELECT count(*) INTO r1_daily_focus FROM daily_focus;
|
|
SELECT count(*) INTO r1_capture FROM capture;
|
|
SELECT count(*) INTO r1_context FROM context_types;
|
|
|
|
RAISE NOTICE '=== MIGRATION VERIFICATION ===';
|
|
RAISE NOTICE 'domains: R0=% R1=% %', r0_domains, r1_domains, CASE WHEN r0_domains = r1_domains THEN 'OK' ELSE 'MISMATCH' END;
|
|
RAISE NOTICE 'areas: R0=% R1=% %', r0_areas, r1_areas, CASE WHEN r0_areas = r1_areas THEN 'OK' ELSE 'MISMATCH' END;
|
|
RAISE NOTICE 'projects: R0=% R1=% %', r0_projects, r1_projects, CASE WHEN r0_projects = r1_projects THEN 'OK' ELSE 'MISMATCH' END;
|
|
RAISE NOTICE 'tasks: R0=% R1=% %', r0_tasks, r1_tasks, CASE WHEN r0_tasks = r1_tasks THEN 'OK' ELSE 'MISMATCH' END;
|
|
RAISE NOTICE 'notes: R0=% R1=% %', r0_notes, r1_notes, CASE WHEN r0_notes = r1_notes THEN 'OK' ELSE 'MISMATCH' END;
|
|
RAISE NOTICE 'links: R0=% R1=% %', r0_links, r1_links, CASE WHEN r0_links = r1_links THEN 'OK' ELSE 'MISMATCH' END;
|
|
RAISE NOTICE 'daily_focus: R0=% R1=% %', r0_daily_focus, r1_daily_focus, CASE WHEN r0_daily_focus = r1_daily_focus THEN 'OK' ELSE 'MISMATCH' END;
|
|
RAISE NOTICE 'capture: R0=% R1=% %', r0_capture, r1_capture, CASE WHEN r0_capture = r1_capture THEN 'OK' ELSE 'MISMATCH' END;
|
|
RAISE NOTICE 'context_types:R0=% R1=% %', r0_context, r1_context, CASE WHEN r0_context = r1_context THEN 'OK' ELSE 'MISMATCH' END;
|
|
RAISE NOTICE '=== END VERIFICATION ===';
|
|
END $$;
|