002_saas_platform.sql 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. CREATE TABLE IF NOT EXISTS voc.workspace_member (
  2. id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  3. workspace_id bigint NOT NULL REFERENCES voc.workspace(id) ON DELETE CASCADE,
  4. user_external_id text NOT NULL,
  5. email text NOT NULL DEFAULT '',
  6. display_name text NOT NULL DEFAULT '',
  7. role text NOT NULL DEFAULT 'viewer' CHECK (role IN ('owner', 'admin', 'analyst', 'viewer')),
  8. status text NOT NULL DEFAULT 'active' CHECK (status IN ('invited', 'active', 'disabled')),
  9. invited_at timestamptz,
  10. last_seen_at timestamptz,
  11. created_at timestamptz NOT NULL DEFAULT now(),
  12. updated_at timestamptz NOT NULL DEFAULT now(),
  13. UNIQUE (workspace_id, user_external_id)
  14. );
  15. CREATE INDEX IF NOT EXISTS workspace_member_user_active_idx
  16. ON voc.workspace_member (user_external_id, workspace_id)
  17. WHERE status = 'active';
  18. CREATE INDEX IF NOT EXISTS workspace_member_workspace_role_idx
  19. ON voc.workspace_member (workspace_id, role, id);
  20. CREATE TABLE IF NOT EXISTS voc.analysis_run (
  21. id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  22. public_id text NOT NULL UNIQUE,
  23. workspace_id bigint NOT NULL REFERENCES voc.workspace(id) ON DELETE CASCADE,
  24. analysis_type text NOT NULL CHECK (analysis_type IN ('voice', 'pain_point', 'feature', 'scenario', 'risk', 'report')),
  25. target_kind text NOT NULL DEFAULT 'workspace' CHECK (target_kind IN ('workspace', 'category', 'product')),
  26. target_key text NOT NULL DEFAULT '',
  27. status text NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'processing', 'completed', 'partial', 'failed', 'cancelled')),
  28. input jsonb NOT NULL DEFAULT '{}'::jsonb,
  29. result jsonb,
  30. evidence_count integer NOT NULL DEFAULT 0 CHECK (evidence_count >= 0),
  31. requested_by_external_id text NOT NULL,
  32. error_summary text,
  33. requested_at timestamptz NOT NULL DEFAULT now(),
  34. started_at timestamptz,
  35. completed_at timestamptz,
  36. updated_at timestamptz NOT NULL DEFAULT now()
  37. );
  38. CREATE INDEX IF NOT EXISTS analysis_run_workspace_status_requested_idx
  39. ON voc.analysis_run (workspace_id, status, requested_at DESC, id DESC);
  40. CREATE INDEX IF NOT EXISTS analysis_run_workspace_target_idx
  41. ON voc.analysis_run (workspace_id, target_kind, target_key, requested_at DESC, id DESC);
  42. CREATE TABLE IF NOT EXISTS voc.action_item (
  43. id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  44. public_id text NOT NULL UNIQUE,
  45. workspace_id bigint NOT NULL REFERENCES voc.workspace(id) ON DELETE CASCADE,
  46. source_analysis_id bigint REFERENCES voc.analysis_run(id) ON DELETE SET NULL,
  47. action_type text NOT NULL DEFAULT 'general' CHECK (action_type IN ('general', 'experience', 'product', 'strategy', 'data_quality')),
  48. title text NOT NULL,
  49. description text NOT NULL DEFAULT '',
  50. priority text NOT NULL DEFAULT 'medium' CHECK (priority IN ('critical', 'high', 'medium', 'low')),
  51. status text NOT NULL DEFAULT 'open' CHECK (status IN ('open', 'planned', 'in_progress', 'blocked', 'completed', 'cancelled')),
  52. product_key text,
  53. assignee_external_id text,
  54. due_at timestamptz,
  55. created_by_external_id text NOT NULL,
  56. completed_at timestamptz,
  57. created_at timestamptz NOT NULL DEFAULT now(),
  58. updated_at timestamptz NOT NULL DEFAULT now()
  59. );
  60. CREATE INDEX IF NOT EXISTS action_item_workspace_status_priority_idx
  61. ON voc.action_item (workspace_id, status, priority, id DESC);
  62. CREATE INDEX IF NOT EXISTS action_item_workspace_assignee_open_idx
  63. ON voc.action_item (workspace_id, assignee_external_id, id DESC)
  64. WHERE status IN ('open', 'planned', 'in_progress', 'blocked');
  65. CREATE INDEX IF NOT EXISTS action_item_source_analysis_idx
  66. ON voc.action_item (source_analysis_id)
  67. WHERE source_analysis_id IS NOT NULL;
  68. CREATE TABLE IF NOT EXISTS voc.alert (
  69. id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  70. public_id text NOT NULL UNIQUE,
  71. workspace_id bigint NOT NULL REFERENCES voc.workspace(id) ON DELETE CASCADE,
  72. alert_type text NOT NULL DEFAULT 'voc_risk' CHECK (alert_type IN ('voc_risk', 'sentiment_spike', 'data_quality', 'sync_failure', 'quota')),
  73. severity text NOT NULL DEFAULT 'medium' CHECK (severity IN ('critical', 'high', 'medium', 'low')),
  74. status text NOT NULL DEFAULT 'open' CHECK (status IN ('open', 'acknowledged', 'resolved', 'dismissed')),
  75. product_key text,
  76. title text NOT NULL,
  77. summary text NOT NULL DEFAULT '',
  78. evidence jsonb NOT NULL DEFAULT '[]'::jsonb,
  79. detected_at timestamptz NOT NULL DEFAULT now(),
  80. acknowledged_by_external_id text,
  81. acknowledged_at timestamptz,
  82. resolved_at timestamptz,
  83. created_at timestamptz NOT NULL DEFAULT now(),
  84. updated_at timestamptz NOT NULL DEFAULT now()
  85. );
  86. CREATE INDEX IF NOT EXISTS alert_workspace_status_detected_idx
  87. ON voc.alert (workspace_id, status, detected_at DESC, id DESC);
  88. CREATE INDEX IF NOT EXISTS alert_workspace_product_open_idx
  89. ON voc.alert (workspace_id, product_key, detected_at DESC, id DESC)
  90. WHERE status IN ('open', 'acknowledged');
  91. CREATE TABLE IF NOT EXISTS voc.audit_log (
  92. id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  93. workspace_id bigint NOT NULL REFERENCES voc.workspace(id) ON DELETE CASCADE,
  94. actor_external_id text NOT NULL,
  95. action text NOT NULL,
  96. entity_type text NOT NULL,
  97. entity_public_id text,
  98. metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
  99. created_at timestamptz NOT NULL DEFAULT now()
  100. );
  101. CREATE INDEX IF NOT EXISTS audit_log_workspace_created_idx
  102. ON voc.audit_log (workspace_id, created_at DESC, id DESC);
  103. CREATE INDEX IF NOT EXISTS audit_log_workspace_entity_idx
  104. ON voc.audit_log (workspace_id, entity_type, entity_public_id, id DESC)
  105. WHERE entity_public_id IS NOT NULL;