@nextsparkjs/theme-crm 0.1.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/CRM_PLAN.md +343 -0
  2. package/about.md +122 -0
  3. package/config/app.config.ts +185 -0
  4. package/config/billing.config.ts +187 -0
  5. package/config/dashboard.config.ts +372 -0
  6. package/config/dev.config.ts +55 -0
  7. package/config/features.config.ts +336 -0
  8. package/config/flows.config.ts +511 -0
  9. package/config/permissions.config.ts +297 -0
  10. package/config/theme.config.ts +111 -0
  11. package/entities/activities/activities.config.ts +61 -0
  12. package/entities/activities/activities.fields.ts +362 -0
  13. package/entities/activities/activities.service.ts +503 -0
  14. package/entities/activities/activities.types.ts +117 -0
  15. package/entities/activities/messages/en.json +123 -0
  16. package/entities/activities/messages/es.json +123 -0
  17. package/entities/activities/migrations/020_activities_table.sql +123 -0
  18. package/entities/activities/migrations/021_activities_metas.sql +114 -0
  19. package/entities/activities/migrations/022_activities_sample_data.sql +420 -0
  20. package/entities/campaigns/campaigns.config.ts +61 -0
  21. package/entities/campaigns/campaigns.fields.ts +413 -0
  22. package/entities/campaigns/campaigns.service.ts +426 -0
  23. package/entities/campaigns/campaigns.types.ts +124 -0
  24. package/entities/campaigns/messages/en.json +145 -0
  25. package/entities/campaigns/messages/es.json +145 -0
  26. package/entities/campaigns/migrations/001_campaigns_table.sql +127 -0
  27. package/entities/campaigns/migrations/002_campaigns_metas.sql +114 -0
  28. package/entities/campaigns/migrations/003_campaigns_sample_data.sql +364 -0
  29. package/entities/companies/companies.config.ts +61 -0
  30. package/entities/companies/companies.fields.ts +429 -0
  31. package/entities/companies/companies.service.ts +566 -0
  32. package/entities/companies/companies.types.ts +125 -0
  33. package/entities/companies/messages/en.json +146 -0
  34. package/entities/companies/messages/es.json +146 -0
  35. package/entities/companies/migrations/001_companies_table.sql +150 -0
  36. package/entities/companies/migrations/002_companies_metas.sql +114 -0
  37. package/entities/companies/migrations/003_companies_sample_data.sql +246 -0
  38. package/entities/contacts/contacts.config.ts +61 -0
  39. package/entities/contacts/contacts.fields.ts +359 -0
  40. package/entities/contacts/contacts.service.ts +509 -0
  41. package/entities/contacts/contacts.types.ts +108 -0
  42. package/entities/contacts/messages/en.json +117 -0
  43. package/entities/contacts/messages/es.json +117 -0
  44. package/entities/contacts/migrations/001_contacts_table.sql +134 -0
  45. package/entities/contacts/migrations/002_contacts_metas.sql +114 -0
  46. package/entities/contacts/migrations/003_contacts_sample_data.sql +421 -0
  47. package/entities/leads/leads.config.ts +61 -0
  48. package/entities/leads/leads.fields.ts +336 -0
  49. package/entities/leads/leads.service.ts +496 -0
  50. package/entities/leads/leads.types.ts +114 -0
  51. package/entities/leads/messages/en.json +132 -0
  52. package/entities/leads/messages/es.json +132 -0
  53. package/entities/leads/migrations/001_leads_table.sql +150 -0
  54. package/entities/leads/migrations/002_leads_metas.sql +120 -0
  55. package/entities/leads/migrations/003_leads_sample_data.sql +242 -0
  56. package/entities/notes/messages/en.json +114 -0
  57. package/entities/notes/messages/es.json +114 -0
  58. package/entities/notes/migrations/020_notes_table.sql +118 -0
  59. package/entities/notes/migrations/021_notes_metas.sql +114 -0
  60. package/entities/notes/migrations/022_notes_sample_data.sql +275 -0
  61. package/entities/notes/notes.config.ts +61 -0
  62. package/entities/notes/notes.fields.ts +283 -0
  63. package/entities/notes/notes.service.ts +320 -0
  64. package/entities/notes/notes.types.ts +102 -0
  65. package/entities/opportunities/messages/en.json +107 -0
  66. package/entities/opportunities/messages/es.json +107 -0
  67. package/entities/opportunities/migrations/010_opportunities_table.sql +145 -0
  68. package/entities/opportunities/migrations/011_opportunities_metas.sql +114 -0
  69. package/entities/opportunities/migrations/012_opportunities_sample_data.sql +438 -0
  70. package/entities/opportunities/opportunities.config.ts +61 -0
  71. package/entities/opportunities/opportunities.fields.ts +416 -0
  72. package/entities/opportunities/opportunities.service.ts +525 -0
  73. package/entities/opportunities/opportunities.types.ts +135 -0
  74. package/entities/pipelines/messages/en.json +115 -0
  75. package/entities/pipelines/messages/es.json +115 -0
  76. package/entities/pipelines/migrations/001_pipelines_table.sql +106 -0
  77. package/entities/pipelines/migrations/002_pipelines_metas.sql +114 -0
  78. package/entities/pipelines/migrations/003_pipelines_sample_data.sql +91 -0
  79. package/entities/pipelines/pipelines.config.ts +62 -0
  80. package/entities/pipelines/pipelines.fields.ts +193 -0
  81. package/entities/pipelines/pipelines.service.ts +383 -0
  82. package/entities/pipelines/pipelines.types.ts +78 -0
  83. package/entities/products/messages/en.json +135 -0
  84. package/entities/products/messages/es.json +135 -0
  85. package/entities/products/migrations/001_products_table.sql +117 -0
  86. package/entities/products/migrations/002_products_metas.sql +114 -0
  87. package/entities/products/migrations/003_products_sample_data.sql +247 -0
  88. package/entities/products/products.config.ts +62 -0
  89. package/entities/products/products.fields.ts +361 -0
  90. package/entities/products/products.service.ts +437 -0
  91. package/entities/products/products.types.ts +125 -0
  92. package/lib/crm-constants.ts +77 -0
  93. package/lib/crm-utils.ts +185 -0
  94. package/lib/selectors.ts +333 -0
  95. package/messages/en.json +131 -0
  96. package/messages/es.json +131 -0
  97. package/migrations/999_theme_sample_data.sql +473 -0
  98. package/package.json +18 -0
  99. package/pendings.md +205 -0
  100. package/permissions-matrix.md +216 -0
  101. package/styles/components.css +414 -0
  102. package/styles/crm-theme.css +358 -0
  103. package/styles/globals.css +576 -0
  104. package/styles/variables.css +111 -0
  105. package/templates/dashboard/(main)/activities/components/ActivityCard.tsx +169 -0
  106. package/templates/dashboard/(main)/activities/components/ActivityTimeline.tsx +165 -0
  107. package/templates/dashboard/(main)/activities/page.tsx +297 -0
  108. package/templates/dashboard/(main)/campaigns/page.tsx +373 -0
  109. package/templates/dashboard/(main)/companies/page.tsx +296 -0
  110. package/templates/dashboard/(main)/contacts/page.tsx +347 -0
  111. package/templates/dashboard/(main)/layout.tsx +98 -0
  112. package/templates/dashboard/(main)/leads/page.tsx +335 -0
  113. package/templates/dashboard/(main)/opportunities/[id]/edit/page.tsx +95 -0
  114. package/templates/dashboard/(main)/opportunities/create/page.tsx +94 -0
  115. package/templates/dashboard/(main)/opportunities/page.tsx +350 -0
  116. package/templates/dashboard/(main)/pipelines/[id]/edit/page.tsx +95 -0
  117. package/templates/dashboard/(main)/pipelines/[id]/page.tsx +143 -0
  118. package/templates/dashboard/(main)/pipelines/create/page.tsx +94 -0
  119. package/templates/dashboard/(main)/pipelines/page.tsx +234 -0
  120. package/templates/dashboard/(main)/products/[id]/edit/page.tsx +97 -0
  121. package/templates/dashboard/(main)/products/[id]/page.tsx +509 -0
  122. package/templates/dashboard/(main)/products/create/page.tsx +96 -0
  123. package/templates/dashboard/(main)/products/page.tsx +308 -0
  124. package/templates/shared/ActionButtons.tsx +41 -0
  125. package/templates/shared/CRMDashboard.tsx +519 -0
  126. package/templates/shared/CRMDataTable.tsx +441 -0
  127. package/templates/shared/CRMMetricCard.tsx +76 -0
  128. package/templates/shared/CRMMobileNav.tsx +172 -0
  129. package/templates/shared/CRMSidebar.tsx +346 -0
  130. package/templates/shared/CRMTopBar.tsx +265 -0
  131. package/templates/shared/DealCard.tsx +123 -0
  132. package/templates/shared/EntityCard.tsx +58 -0
  133. package/templates/shared/OpportunityForm.tsx +649 -0
  134. package/templates/shared/PipelineForm.tsx +367 -0
  135. package/templates/shared/PipelineKanban.tsx +194 -0
  136. package/templates/shared/QuickFilters.tsx +47 -0
  137. package/templates/shared/StageColumn.tsx +175 -0
  138. package/templates/shared/StageSelect.tsx +177 -0
  139. package/templates/shared/StagesRepeater.tsx +317 -0
  140. package/templates/shared/index.ts +9 -0
@@ -0,0 +1,123 @@
1
+ {
2
+ "entity": {
3
+ "name": "Activity",
4
+ "plural": "Activities",
5
+ "description": "Track interactions and communications"
6
+ },
7
+ "fields": {
8
+ "entityType": {
9
+ "label": "Related Entity Type",
10
+ "description": "Type of entity this activity is related to",
11
+ "placeholder": "Select entity type..."
12
+ },
13
+ "entityId": {
14
+ "label": "Related Entity",
15
+ "description": "Entity this activity is related to",
16
+ "placeholder": "Select entity..."
17
+ },
18
+ "type": {
19
+ "label": "Activity Type",
20
+ "description": "Type of activity",
21
+ "placeholder": "Select type..."
22
+ },
23
+ "subject": {
24
+ "label": "Subject",
25
+ "description": "Activity subject or title",
26
+ "placeholder": "Enter subject..."
27
+ },
28
+ "description": {
29
+ "label": "Description",
30
+ "description": "Detailed activity description",
31
+ "placeholder": "Enter description..."
32
+ },
33
+ "status": {
34
+ "label": "Status",
35
+ "description": "Current activity status",
36
+ "placeholder": "Select status..."
37
+ },
38
+ "priority": {
39
+ "label": "Priority",
40
+ "description": "Activity priority level",
41
+ "placeholder": "Select priority..."
42
+ },
43
+ "dueDate": {
44
+ "label": "Due Date",
45
+ "description": "When the activity is due",
46
+ "placeholder": "Select date..."
47
+ },
48
+ "completedAt": {
49
+ "label": "Completed At",
50
+ "description": "When the activity was completed"
51
+ },
52
+ "duration": {
53
+ "label": "Duration (minutes)",
54
+ "description": "Activity duration in minutes",
55
+ "placeholder": "0"
56
+ },
57
+ "outcome": {
58
+ "label": "Outcome",
59
+ "description": "Activity outcome or result",
60
+ "placeholder": "Select outcome..."
61
+ },
62
+ "assignedTo": {
63
+ "label": "Assigned To",
64
+ "description": "User assigned to this activity",
65
+ "placeholder": "Select user..."
66
+ }
67
+ },
68
+ "options": {
69
+ "entityType": {
70
+ "lead": "Lead",
71
+ "contact": "Contact",
72
+ "company": "Company",
73
+ "opportunity": "Opportunity"
74
+ },
75
+ "type": {
76
+ "call": "Phone Call",
77
+ "email": "Email",
78
+ "meeting": "Meeting",
79
+ "task": "Task",
80
+ "note": "Note",
81
+ "demo": "Demo",
82
+ "proposal": "Proposal",
83
+ "follow_up": "Follow Up"
84
+ },
85
+ "status": {
86
+ "pending": "Pending",
87
+ "in_progress": "In Progress",
88
+ "completed": "Completed",
89
+ "cancelled": "Cancelled",
90
+ "deferred": "Deferred"
91
+ },
92
+ "priority": {
93
+ "low": "Low",
94
+ "medium": "Medium",
95
+ "high": "High",
96
+ "urgent": "Urgent"
97
+ },
98
+ "outcome": {
99
+ "successful": "Successful",
100
+ "partially_successful": "Partially Successful",
101
+ "unsuccessful": "Unsuccessful",
102
+ "no_response": "No Response",
103
+ "reschedule": "Reschedule Required"
104
+ }
105
+ },
106
+ "actions": {
107
+ "create": "Create Activity",
108
+ "edit": "Edit Activity",
109
+ "delete": "Delete Activity",
110
+ "view": "View Activity",
111
+ "list": "List Activities",
112
+ "complete": "Mark Complete",
113
+ "schedule": "Schedule Activity"
114
+ },
115
+ "messages": {
116
+ "created": "Activity created successfully",
117
+ "updated": "Activity updated successfully",
118
+ "deleted": "Activity deleted successfully",
119
+ "completed": "Activity marked as completed",
120
+ "notFound": "Activity not found",
121
+ "error": "An error occurred while processing the activity"
122
+ }
123
+ }
@@ -0,0 +1,123 @@
1
+ {
2
+ "entity": {
3
+ "name": "Actividad",
4
+ "plural": "Actividades",
5
+ "description": "Rastrea interacciones y comunicaciones"
6
+ },
7
+ "fields": {
8
+ "entityType": {
9
+ "label": "Tipo de Entidad",
10
+ "description": "Tipo de entidad relacionada",
11
+ "placeholder": "Seleccionar tipo..."
12
+ },
13
+ "entityId": {
14
+ "label": "Entidad Relacionada",
15
+ "description": "Entidad relacionada",
16
+ "placeholder": "Seleccionar entidad..."
17
+ },
18
+ "type": {
19
+ "label": "Tipo",
20
+ "description": "Tipo de actividad",
21
+ "placeholder": "Seleccionar tipo..."
22
+ },
23
+ "subject": {
24
+ "label": "Asunto",
25
+ "description": "Asunto o título",
26
+ "placeholder": "Ingrese asunto..."
27
+ },
28
+ "description": {
29
+ "label": "Descripción",
30
+ "description": "Descripción detallada",
31
+ "placeholder": "Ingrese descripción..."
32
+ },
33
+ "status": {
34
+ "label": "Estado",
35
+ "description": "Estado actual",
36
+ "placeholder": "Seleccionar estado..."
37
+ },
38
+ "priority": {
39
+ "label": "Prioridad",
40
+ "description": "Nivel de prioridad",
41
+ "placeholder": "Seleccionar prioridad..."
42
+ },
43
+ "dueDate": {
44
+ "label": "Fecha Límite",
45
+ "description": "Cuándo vence la actividad",
46
+ "placeholder": "Seleccionar fecha..."
47
+ },
48
+ "completedAt": {
49
+ "label": "Completada el",
50
+ "description": "Cuándo se completó"
51
+ },
52
+ "duration": {
53
+ "label": "Duración (minutos)",
54
+ "description": "Duración en minutos",
55
+ "placeholder": "0"
56
+ },
57
+ "outcome": {
58
+ "label": "Resultado",
59
+ "description": "Resultado de la actividad",
60
+ "placeholder": "Seleccionar resultado..."
61
+ },
62
+ "assignedTo": {
63
+ "label": "Asignado a",
64
+ "description": "Usuario asignado",
65
+ "placeholder": "Seleccionar usuario..."
66
+ }
67
+ },
68
+ "options": {
69
+ "entityType": {
70
+ "lead": "Lead",
71
+ "contact": "Contacto",
72
+ "company": "Empresa",
73
+ "opportunity": "Oportunidad"
74
+ },
75
+ "type": {
76
+ "call": "Llamada",
77
+ "email": "Email",
78
+ "meeting": "Reunión",
79
+ "task": "Tarea",
80
+ "note": "Nota",
81
+ "demo": "Demo",
82
+ "proposal": "Propuesta",
83
+ "follow_up": "Seguimiento"
84
+ },
85
+ "status": {
86
+ "pending": "Pendiente",
87
+ "in_progress": "En Progreso",
88
+ "completed": "Completada",
89
+ "cancelled": "Cancelada",
90
+ "deferred": "Diferida"
91
+ },
92
+ "priority": {
93
+ "low": "Baja",
94
+ "medium": "Media",
95
+ "high": "Alta",
96
+ "urgent": "Urgente"
97
+ },
98
+ "outcome": {
99
+ "successful": "Exitosa",
100
+ "partially_successful": "Parcialmente Exitosa",
101
+ "unsuccessful": "No Exitosa",
102
+ "no_response": "Sin Respuesta",
103
+ "reschedule": "Reprogramar"
104
+ }
105
+ },
106
+ "actions": {
107
+ "create": "Crear Actividad",
108
+ "edit": "Editar Actividad",
109
+ "delete": "Eliminar Actividad",
110
+ "view": "Ver Actividad",
111
+ "list": "Listar Actividades",
112
+ "complete": "Marcar Completa",
113
+ "schedule": "Programar Actividad"
114
+ },
115
+ "messages": {
116
+ "created": "Actividad creada exitosamente",
117
+ "updated": "Actividad actualizada exitosamente",
118
+ "deleted": "Actividad eliminada exitosamente",
119
+ "completed": "Actividad marcada como completada",
120
+ "notFound": "Actividad no encontrada",
121
+ "error": "Error al procesar la actividad"
122
+ }
123
+ }
@@ -0,0 +1,123 @@
1
+ -- ============================================================================
2
+ -- Activities Table Migration
3
+ -- CRM theme: Tasks and activities related to CRM records
4
+ -- Updated with team support and RLS
5
+ -- ============================================================================
6
+
7
+ -- ============================================
8
+ -- ENUM TYPES
9
+ -- ============================================
10
+ DO $$ BEGIN
11
+ CREATE TYPE activity_type AS ENUM ('call', 'email', 'meeting', 'task', 'note', 'demo', 'follow_up');
12
+ EXCEPTION
13
+ WHEN duplicate_object THEN null;
14
+ END $$;
15
+
16
+ DO $$ BEGIN
17
+ CREATE TYPE activity_status AS ENUM ('scheduled', 'in_progress', 'completed', 'cancelled');
18
+ EXCEPTION
19
+ WHEN duplicate_object THEN null;
20
+ END $$;
21
+
22
+ DO $$ BEGIN
23
+ CREATE TYPE activity_priority AS ENUM ('low', 'medium', 'high', 'urgent');
24
+ EXCEPTION
25
+ WHEN duplicate_object THEN null;
26
+ END $$;
27
+
28
+ -- ============================================
29
+ -- TABLE
30
+ -- ============================================
31
+ CREATE TABLE IF NOT EXISTS "activities" (
32
+ "id" TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
33
+
34
+ -- Activity info
35
+ "type" activity_type NOT NULL DEFAULT 'task',
36
+ "subject" VARCHAR(255) NOT NULL,
37
+ "description" TEXT,
38
+ "status" activity_status DEFAULT 'scheduled',
39
+ "priority" activity_priority DEFAULT 'medium',
40
+
41
+ -- Timing
42
+ "dueDate" TIMESTAMPTZ,
43
+ "completedAt" TIMESTAMPTZ,
44
+ "duration" INTEGER, -- Duration in minutes
45
+ "outcome" TEXT,
46
+ "location" VARCHAR(255),
47
+
48
+ -- Direct relations (optional)
49
+ "contactId" TEXT REFERENCES "contacts"("id") ON DELETE SET NULL,
50
+ "companyId" TEXT REFERENCES "companies"("id") ON DELETE SET NULL,
51
+ "opportunityId" TEXT REFERENCES "opportunities"("id") ON DELETE SET NULL,
52
+
53
+ -- Assignment
54
+ "assignedTo" TEXT REFERENCES "users"("id") ON DELETE SET NULL,
55
+
56
+ -- Ownership
57
+ "userId" TEXT NOT NULL REFERENCES "users"("id") ON DELETE CASCADE,
58
+ "teamId" TEXT NOT NULL REFERENCES "teams"("id") ON DELETE CASCADE,
59
+
60
+ -- Timestamps
61
+ "createdAt" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
62
+ "updatedAt" TIMESTAMPTZ NOT NULL DEFAULT NOW()
63
+ );
64
+
65
+ -- ============================================
66
+ -- INDEXES
67
+ -- ============================================
68
+ CREATE INDEX IF NOT EXISTS "activities_teamId_idx" ON "activities" ("teamId");
69
+ CREATE INDEX IF NOT EXISTS "activities_userId_idx" ON "activities" ("userId");
70
+ CREATE INDEX IF NOT EXISTS "activities_type_idx" ON "activities" ("type");
71
+ CREATE INDEX IF NOT EXISTS "activities_status_idx" ON "activities" ("status");
72
+ CREATE INDEX IF NOT EXISTS "activities_priority_idx" ON "activities" ("priority");
73
+ CREATE INDEX IF NOT EXISTS "activities_dueDate_idx" ON "activities" ("dueDate");
74
+ CREATE INDEX IF NOT EXISTS "activities_assignedTo_idx" ON "activities" ("assignedTo");
75
+ CREATE INDEX IF NOT EXISTS "activities_contactId_idx" ON "activities" ("contactId");
76
+ CREATE INDEX IF NOT EXISTS "activities_companyId_idx" ON "activities" ("companyId");
77
+ CREATE INDEX IF NOT EXISTS "activities_opportunityId_idx" ON "activities" ("opportunityId");
78
+
79
+ -- ============================================
80
+ -- RLS
81
+ -- ============================================
82
+ ALTER TABLE "activities" ENABLE ROW LEVEL SECURITY;
83
+
84
+ DROP POLICY IF EXISTS "activities_select_policy" ON "activities";
85
+ DROP POLICY IF EXISTS "activities_insert_policy" ON "activities";
86
+ DROP POLICY IF EXISTS "activities_update_policy" ON "activities";
87
+ DROP POLICY IF EXISTS "activities_delete_policy" ON "activities";
88
+
89
+ CREATE POLICY "activities_select_policy" ON "activities"
90
+ FOR SELECT
91
+ USING ("teamId" = ANY(public.get_user_team_ids()) OR public.is_superadmin());
92
+
93
+ CREATE POLICY "activities_insert_policy" ON "activities"
94
+ FOR INSERT
95
+ WITH CHECK ("teamId" = ANY(public.get_user_team_ids()));
96
+
97
+ CREATE POLICY "activities_update_policy" ON "activities"
98
+ FOR UPDATE
99
+ USING ("teamId" = ANY(public.get_user_team_ids()) OR public.is_superadmin());
100
+
101
+ CREATE POLICY "activities_delete_policy" ON "activities"
102
+ FOR DELETE
103
+ USING ("teamId" = ANY(public.get_user_team_ids()) OR public.is_superadmin());
104
+
105
+ -- ============================================
106
+ -- TRIGGER updatedAt
107
+ -- ============================================
108
+ CREATE OR REPLACE FUNCTION update_activities_updated_at()
109
+ RETURNS TRIGGER AS $$
110
+ BEGIN
111
+ NEW."updatedAt" = NOW();
112
+ RETURN NEW;
113
+ END;
114
+ $$ LANGUAGE plpgsql;
115
+
116
+ DROP TRIGGER IF EXISTS activities_updated_at_trigger ON "activities";
117
+ CREATE TRIGGER activities_updated_at_trigger
118
+ BEFORE UPDATE ON "activities"
119
+ FOR EACH ROW
120
+ EXECUTE FUNCTION update_activities_updated_at();
121
+
122
+ COMMENT ON TABLE "activities" IS 'Tasks and activities related to CRM records';
123
+ COMMENT ON COLUMN "activities"."duration" IS 'Activity duration in minutes';
@@ -0,0 +1,114 @@
1
+ -- Migration: 002_activities_metas.sql
2
+ -- Description: Contacts metas (table, indexes, RLS)
3
+ -- Date: 2025-09-27
4
+
5
+ -- ============================================
6
+ -- TABLE
7
+ -- ============================================
8
+ -- No DROP needed - removed automatically by parent table CASCADE
9
+ CREATE TABLE IF NOT EXISTS public."activities_metas" (
10
+ id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
11
+ "entityId" TEXT NOT NULL REFERENCES public."activities"(id) ON DELETE CASCADE,
12
+ "metaKey" TEXT NOT NULL,
13
+ "metaValue" JSONB NOT NULL DEFAULT '{}'::jsonb,
14
+ "dataType" TEXT DEFAULT 'json',
15
+ "isPublic" BOOLEAN NOT NULL DEFAULT false,
16
+ "isSearchable" BOOLEAN NOT NULL DEFAULT false,
17
+ "createdAt" TIMESTAMPTZ NOT NULL DEFAULT now(),
18
+ "updatedAt" TIMESTAMPTZ NOT NULL DEFAULT now(),
19
+ CONSTRAINT activities_metas_unique_key UNIQUE ("entityId", "metaKey")
20
+ );
21
+
22
+ COMMENT ON TABLE public."activities_metas" IS 'Contacts metadata table - stores additional key-value pairs for activities';
23
+ COMMENT ON COLUMN public."activities_metas"."entityId" IS 'Generic foreign key to parent activitie entity';
24
+ COMMENT ON COLUMN public."activities_metas"."metaKey" IS 'Metadata key name';
25
+ COMMENT ON COLUMN public."activities_metas"."metaValue" IS 'Metadata value as JSONB';
26
+ COMMENT ON COLUMN public."activities_metas"."dataType" IS 'Type hint for the value: json, string, number, boolean';
27
+ COMMENT ON COLUMN public."activities_metas"."isPublic" IS 'Whether this metadata is publicly readable';
28
+ COMMENT ON COLUMN public."activities_metas"."isSearchable" IS 'Whether this metadata is searchable';
29
+
30
+ -- ============================================
31
+ -- TRIGGER updatedAt (uses Better Auth function)
32
+ -- ============================================
33
+ DROP TRIGGER IF EXISTS activities_metas_set_updated_at ON public."activities_metas";
34
+ CREATE TRIGGER activities_metas_set_updated_at
35
+ BEFORE UPDATE ON public."activities_metas"
36
+ FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();
37
+
38
+ -- ============================================
39
+ -- INDEXES
40
+ -- ============================================
41
+ CREATE INDEX IF NOT EXISTS idx_activities_metas_entity_id ON public."activities_metas"("entityId");
42
+ CREATE INDEX IF NOT EXISTS idx_activities_metas_key ON public."activities_metas"("metaKey");
43
+ CREATE INDEX IF NOT EXISTS idx_activities_metas_composite ON public."activities_metas"("entityId", "metaKey", "isPublic");
44
+ CREATE INDEX IF NOT EXISTS idx_activities_metas_is_public ON public."activities_metas"("isPublic") WHERE "isPublic" = true;
45
+ CREATE INDEX IF NOT EXISTS idx_activities_metas_is_searchable ON public."activities_metas"("isSearchable") WHERE "isSearchable" = true;
46
+ CREATE INDEX IF NOT EXISTS idx_activities_metas_searchable_key ON public."activities_metas"("metaKey") WHERE "isSearchable" = true;
47
+ CREATE INDEX IF NOT EXISTS idx_activities_metas_value_gin ON public."activities_metas" USING GIN ("metaValue");
48
+ CREATE INDEX IF NOT EXISTS idx_activities_metas_value_ops ON public."activities_metas" USING GIN ("metaValue" jsonb_path_ops);
49
+
50
+ -- ============================================
51
+ -- RLS
52
+ -- ============================================
53
+ ALTER TABLE public."activities_metas" ENABLE ROW LEVEL SECURITY;
54
+
55
+ -- Cleanup existing policies
56
+ DROP POLICY IF EXISTS "Users can view activitie metas" ON public."activities_metas";
57
+ DROP POLICY IF EXISTS "Users can create activitie metas" ON public."activities_metas";
58
+ DROP POLICY IF EXISTS "Users can update activitie metas" ON public."activities_metas";
59
+ DROP POLICY IF EXISTS "Users can delete activitie metas" ON public."activities_metas";
60
+
61
+ -- ============================
62
+ -- AUTHENTICATED USER POLICIES
63
+ -- ============================
64
+ -- Inherit permissions from parent entity
65
+ CREATE POLICY "Users can view activitie metas"
66
+ ON public."activities_metas"
67
+ FOR SELECT TO authenticated
68
+ USING (
69
+ EXISTS (
70
+ SELECT 1 FROM public."activities" c
71
+ WHERE c.id = activities_metas."entityId"
72
+ AND c."userId" = public.get_auth_user_id()
73
+ )
74
+ );
75
+
76
+ CREATE POLICY "Users can create activitie metas"
77
+ ON public."activities_metas"
78
+ FOR INSERT TO authenticated
79
+ WITH CHECK (
80
+ EXISTS (
81
+ SELECT 1 FROM public."activities" c
82
+ WHERE c.id = activities_metas."entityId"
83
+ AND c."userId" = public.get_auth_user_id()
84
+ )
85
+ );
86
+
87
+ CREATE POLICY "Users can update activitie metas"
88
+ ON public."activities_metas"
89
+ FOR UPDATE TO authenticated
90
+ USING (
91
+ EXISTS (
92
+ SELECT 1 FROM public."activities" c
93
+ WHERE c.id = activities_metas."entityId"
94
+ AND c."userId" = public.get_auth_user_id()
95
+ )
96
+ )
97
+ WITH CHECK (
98
+ EXISTS (
99
+ SELECT 1 FROM public."activities" c
100
+ WHERE c.id = activities_metas."entityId"
101
+ AND c."userId" = public.get_auth_user_id()
102
+ )
103
+ );
104
+
105
+ CREATE POLICY "Users can delete activitie metas"
106
+ ON public."activities_metas"
107
+ FOR DELETE TO authenticated
108
+ USING (
109
+ EXISTS (
110
+ SELECT 1 FROM public."activities" c
111
+ WHERE c.id = activities_metas."entityId"
112
+ AND c."userId" = public.get_auth_user_id()
113
+ )
114
+ );