@nextsparkjs/theme-default 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.
- package/about/business.md +49 -0
- package/about/features.json +302 -0
- package/about/team.md +79 -0
- package/api/ai/chat/stream/route.ts +212 -0
- package/api/ai/orchestrator/route.ts +226 -0
- package/api/ai/single-agent/route.ts +291 -0
- package/api/ai/usage/route.ts +122 -0
- package/blocks/benefits/component.tsx +100 -0
- package/blocks/benefits/config.ts +11 -0
- package/blocks/benefits/examples.ts +85 -0
- package/blocks/benefits/fields.ts +156 -0
- package/blocks/benefits/schema.ts +33 -0
- package/blocks/cta-section/component.tsx +100 -0
- package/blocks/cta-section/config.ts +11 -0
- package/blocks/cta-section/examples.ts +41 -0
- package/blocks/cta-section/fields.ts +89 -0
- package/blocks/cta-section/index.ts +6 -0
- package/blocks/cta-section/schema.ts +32 -0
- package/blocks/cta-section/thumbnail.png +1 -0
- package/blocks/faq-accordion/component.tsx +156 -0
- package/blocks/faq-accordion/config.ts +11 -0
- package/blocks/faq-accordion/examples.ts +77 -0
- package/blocks/faq-accordion/fields.ts +119 -0
- package/blocks/faq-accordion/index.ts +6 -0
- package/blocks/faq-accordion/schema.ts +45 -0
- package/blocks/features-grid/component.tsx +112 -0
- package/blocks/features-grid/config.ts +11 -0
- package/blocks/features-grid/examples.ts +63 -0
- package/blocks/features-grid/fields.ts +97 -0
- package/blocks/features-grid/index.ts +6 -0
- package/blocks/features-grid/schema.ts +40 -0
- package/blocks/features-grid/thumbnail.png +1 -0
- package/blocks/hero/component.tsx +100 -0
- package/blocks/hero/config.ts +11 -0
- package/blocks/hero/examples.ts +35 -0
- package/blocks/hero/fields.ts +60 -0
- package/blocks/hero/index.ts +6 -0
- package/blocks/hero/schema.ts +32 -0
- package/blocks/hero/thumbnail.png +1 -0
- package/blocks/hero/thumbnail.png.txt +6 -0
- package/blocks/hero-with-form/component.tsx +232 -0
- package/blocks/hero-with-form/config.ts +11 -0
- package/blocks/hero-with-form/examples.ts +16 -0
- package/blocks/hero-with-form/fields.ts +207 -0
- package/blocks/hero-with-form/index.ts +6 -0
- package/blocks/hero-with-form/schema.ts +54 -0
- package/blocks/jumbotron/component.tsx +136 -0
- package/blocks/jumbotron/config.ts +11 -0
- package/blocks/jumbotron/examples.ts +36 -0
- package/blocks/jumbotron/fields.ts +202 -0
- package/blocks/jumbotron/index.ts +6 -0
- package/blocks/jumbotron/schema.ts +55 -0
- package/blocks/logo-cloud/component.tsx +154 -0
- package/blocks/logo-cloud/config.ts +11 -0
- package/blocks/logo-cloud/examples.ts +34 -0
- package/blocks/logo-cloud/fields.ts +133 -0
- package/blocks/logo-cloud/index.ts +6 -0
- package/blocks/logo-cloud/schema.ts +46 -0
- package/blocks/post-content/component.tsx +197 -0
- package/blocks/post-content/config.ts +11 -0
- package/blocks/post-content/examples.ts +33 -0
- package/blocks/post-content/fields.ts +165 -0
- package/blocks/post-content/index.ts +4 -0
- package/blocks/post-content/schema.ts +46 -0
- package/blocks/pricing-table/component.tsx +154 -0
- package/blocks/pricing-table/config.ts +11 -0
- package/blocks/pricing-table/examples.ts +96 -0
- package/blocks/pricing-table/fields.ts +161 -0
- package/blocks/pricing-table/index.ts +4 -0
- package/blocks/pricing-table/schema.ts +50 -0
- package/blocks/split-content/component.tsx +135 -0
- package/blocks/split-content/config.ts +11 -0
- package/blocks/split-content/examples.ts +38 -0
- package/blocks/split-content/fields.ts +198 -0
- package/blocks/split-content/index.ts +6 -0
- package/blocks/split-content/schema.ts +67 -0
- package/blocks/stats-counter/component.tsx +124 -0
- package/blocks/stats-counter/config.ts +11 -0
- package/blocks/stats-counter/examples.ts +61 -0
- package/blocks/stats-counter/fields.ts +134 -0
- package/blocks/stats-counter/index.ts +6 -0
- package/blocks/stats-counter/schema.ts +47 -0
- package/blocks/testimonials/component.tsx +114 -0
- package/blocks/testimonials/config.ts +11 -0
- package/blocks/testimonials/examples.ts +65 -0
- package/blocks/testimonials/fields.ts +105 -0
- package/blocks/testimonials/index.ts +6 -0
- package/blocks/testimonials/schema.ts +41 -0
- package/blocks/testimonials/thumbnail.png +1 -0
- package/blocks/text-content/component.tsx +97 -0
- package/blocks/text-content/config.ts +11 -0
- package/blocks/text-content/examples.ts +30 -0
- package/blocks/text-content/fields.ts +88 -0
- package/blocks/text-content/index.ts +6 -0
- package/blocks/text-content/schema.ts +30 -0
- package/blocks/text-content/thumbnail.png +1 -0
- package/blocks/timeline/component.tsx +267 -0
- package/blocks/timeline/config.ts +11 -0
- package/blocks/timeline/examples.ts +68 -0
- package/blocks/timeline/fields.ts +147 -0
- package/blocks/timeline/index.ts +6 -0
- package/blocks/timeline/schema.ts +49 -0
- package/blocks/video-hero/component.tsx +270 -0
- package/blocks/video-hero/config.ts +11 -0
- package/blocks/video-hero/examples.ts +24 -0
- package/blocks/video-hero/fields.ts +98 -0
- package/blocks/video-hero/index.ts +6 -0
- package/blocks/video-hero/schema.ts +39 -0
- package/components/ai-chat/ChatPanel.tsx +575 -0
- package/components/ai-chat/ConversationItem.tsx +266 -0
- package/components/ai-chat/ConversationSidebar.tsx +99 -0
- package/components/ai-chat/MarkdownRenderer.tsx +15 -0
- package/components/ai-chat/Message.tsx +42 -0
- package/components/ai-chat/MessageInput.tsx +49 -0
- package/components/ai-chat/MessageList.tsx +46 -0
- package/components/ai-chat/TypingIndicator.tsx +11 -0
- package/config/app.config.ts +367 -0
- package/config/billing.config.ts +349 -0
- package/config/dashboard.config.ts +506 -0
- package/config/dev.config.ts +104 -0
- package/config/features.config.ts +203 -0
- package/config/flows.config.ts +129 -0
- package/config/permissions.config.ts +245 -0
- package/config/theme.config.ts +74 -0
- package/docs/01-overview/01-introduction.md +335 -0
- package/docs/01-overview/02-customization.md +671 -0
- package/docs/02-features/01-components.md +155 -0
- package/docs/02-features/02-styling.md +139 -0
- package/docs/02-features/03-tasks-entity.md +407 -0
- package/docs/03-ai/01-overview.md +211 -0
- package/docs/03-ai/02-customization.md +436 -0
- package/entities/customers/customers.config.ts +75 -0
- package/entities/customers/customers.fields.ts +165 -0
- package/entities/customers/customers.service.ts +516 -0
- package/entities/customers/customers.types.ts +83 -0
- package/entities/customers/messages/en.json +66 -0
- package/entities/customers/messages/es.json +66 -0
- package/entities/customers/migrations/001_customers_table.sql +102 -0
- package/entities/customers/migrations/002_customers_metas.sql +92 -0
- package/entities/pages/messages/en.json +41 -0
- package/entities/pages/messages/es.json +41 -0
- package/entities/pages/migrations/001_pages_table.sql +112 -0
- package/entities/pages/migrations/002_pages_metas.sql +56 -0
- package/entities/pages/migrations/003_add_status.sql +50 -0
- package/entities/pages/pages-management.service.ts +610 -0
- package/entities/pages/pages.config.ts +94 -0
- package/entities/pages/pages.fields.ts +101 -0
- package/entities/pages/pages.service.ts +290 -0
- package/entities/pages/pages.types.ts +124 -0
- package/entities/posts/components/post-header.tsx +97 -0
- package/entities/posts/messages/en.json +55 -0
- package/entities/posts/messages/es.json +55 -0
- package/entities/posts/migrations/001_posts_table.sql +115 -0
- package/entities/posts/migrations/003_add_status.sql +44 -0
- package/entities/posts/migrations/004_entity_taxonomy_relations.sql +129 -0
- package/entities/posts/migrations/006_posts_metas.sql +56 -0
- package/entities/posts/posts.config.ts +101 -0
- package/entities/posts/posts.fields.ts +116 -0
- package/entities/posts/posts.service.ts +376 -0
- package/entities/posts/posts.types.ts +74 -0
- package/entities/tasks/messages/en.json +204 -0
- package/entities/tasks/messages/es.json +204 -0
- package/entities/tasks/migrations/001_tasks_table.sql +105 -0
- package/entities/tasks/migrations/002_task_metas.sql +85 -0
- package/entities/tasks/migrations/sample_data.json +77 -0
- package/entities/tasks/tasks.config.ts +79 -0
- package/entities/tasks/tasks.fields.ts +196 -0
- package/entities/tasks/tasks.service.ts +541 -0
- package/entities/tasks/tasks.types.ts +56 -0
- package/lib/hooks/useAiChat.ts +114 -0
- package/lib/hooks/useConversations.ts +376 -0
- package/lib/hooks/useOrchestratorChat.ts +122 -0
- package/lib/hooks/usePersistentChat.ts +315 -0
- package/lib/hooks/useStreamingChat.ts +127 -0
- package/lib/hooks/useTokenUsage.ts +63 -0
- package/lib/langchain/agents/customer-assistant.md +69 -0
- package/lib/langchain/agents/index.ts +61 -0
- package/lib/langchain/agents/orchestrator.md +59 -0
- package/lib/langchain/agents/page-assistant.md +85 -0
- package/lib/langchain/agents/single-agent.md +46 -0
- package/lib/langchain/agents/task-assistant.md +55 -0
- package/lib/langchain/config.ts +45 -0
- package/lib/langchain/handlers/customer-handler.ts +338 -0
- package/lib/langchain/handlers/page-handler.ts +232 -0
- package/lib/langchain/handlers/task-handler.ts +323 -0
- package/lib/langchain/langchain.config.ts +223 -0
- package/lib/langchain/observability.config.ts +30 -0
- package/lib/langchain/orchestrator.ts +562 -0
- package/lib/langchain/tools/customers.ts +176 -0
- package/lib/langchain/tools/index.ts +10 -0
- package/lib/langchain/tools/orchestrator.ts +92 -0
- package/lib/langchain/tools/pages.ts +289 -0
- package/lib/langchain/tools/tasks.ts +167 -0
- package/lib/scheduled-actions/billing.ts +149 -0
- package/lib/scheduled-actions/index.ts +170 -0
- package/lib/scheduled-actions/webhook.ts +231 -0
- package/lib/selectors.ts +197 -0
- package/messages/de/admin.json +219 -0
- package/messages/de/aiUsage.json +36 -0
- package/messages/de/buttons.json +19 -0
- package/messages/de/categories.json +35 -0
- package/messages/de/common.json +16 -0
- package/messages/de/dev.json +101 -0
- package/messages/de/docs.json +27 -0
- package/messages/de/entities.json +7 -0
- package/messages/de/features.json +119 -0
- package/messages/de/footer.json +22 -0
- package/messages/de/home.json +57 -0
- package/messages/de/index.ts +39 -0
- package/messages/de/mobileNav.json +13 -0
- package/messages/de/navigation.json +8 -0
- package/messages/de/observability.json +74 -0
- package/messages/de/posts.json +54 -0
- package/messages/de/pricing.json +102 -0
- package/messages/de/support.json +9 -0
- package/messages/de/teams.json +8 -0
- package/messages/en/admin.json +219 -0
- package/messages/en/aiUsage.json +36 -0
- package/messages/en/buttons.json +19 -0
- package/messages/en/categories.json +35 -0
- package/messages/en/common.json +16 -0
- package/messages/en/dev.json +106 -0
- package/messages/en/docs.json +27 -0
- package/messages/en/entities.json +7 -0
- package/messages/en/features.json +119 -0
- package/messages/en/footer.json +22 -0
- package/messages/en/home.json +57 -0
- package/messages/en/index.ts +39 -0
- package/messages/en/mobileNav.json +13 -0
- package/messages/en/navigation.json +8 -0
- package/messages/en/observability.json +74 -0
- package/messages/en/posts.json +54 -0
- package/messages/en/pricing.json +102 -0
- package/messages/en/support.json +9 -0
- package/messages/en/teams.json +8 -0
- package/messages/es/admin.json +219 -0
- package/messages/es/aiUsage.json +36 -0
- package/messages/es/buttons.json +19 -0
- package/messages/es/categories.json +35 -0
- package/messages/es/common.json +16 -0
- package/messages/es/dev.json +101 -0
- package/messages/es/docs.json +27 -0
- package/messages/es/entities.json +7 -0
- package/messages/es/features.json +119 -0
- package/messages/es/footer.json +22 -0
- package/messages/es/home.json +57 -0
- package/messages/es/index.ts +39 -0
- package/messages/es/mobileNav.json +13 -0
- package/messages/es/navigation.json +8 -0
- package/messages/es/observability.json +74 -0
- package/messages/es/posts.json +54 -0
- package/messages/es/pricing.json +102 -0
- package/messages/es/support.json +9 -0
- package/messages/es/teams.json +8 -0
- package/messages/fr/admin.json +219 -0
- package/messages/fr/aiUsage.json +36 -0
- package/messages/fr/buttons.json +19 -0
- package/messages/fr/categories.json +35 -0
- package/messages/fr/common.json +16 -0
- package/messages/fr/dev.json +101 -0
- package/messages/fr/docs.json +27 -0
- package/messages/fr/entities.json +7 -0
- package/messages/fr/features.json +119 -0
- package/messages/fr/footer.json +22 -0
- package/messages/fr/home.json +57 -0
- package/messages/fr/index.ts +39 -0
- package/messages/fr/mobileNav.json +13 -0
- package/messages/fr/navigation.json +8 -0
- package/messages/fr/observability.json +74 -0
- package/messages/fr/posts.json +54 -0
- package/messages/fr/pricing.json +102 -0
- package/messages/fr/support.json +9 -0
- package/messages/fr/teams.json +8 -0
- package/messages/it/admin.json +219 -0
- package/messages/it/aiUsage.json +36 -0
- package/messages/it/buttons.json +19 -0
- package/messages/it/categories.json +35 -0
- package/messages/it/common.json +16 -0
- package/messages/it/dev.json +101 -0
- package/messages/it/docs.json +27 -0
- package/messages/it/entities.json +7 -0
- package/messages/it/features.json +119 -0
- package/messages/it/footer.json +22 -0
- package/messages/it/home.json +57 -0
- package/messages/it/index.ts +39 -0
- package/messages/it/mobileNav.json +13 -0
- package/messages/it/navigation.json +8 -0
- package/messages/it/observability.json +74 -0
- package/messages/it/posts.json +54 -0
- package/messages/it/pricing.json +102 -0
- package/messages/it/support.json +9 -0
- package/messages/it/teams.json +8 -0
- package/messages/pt/admin.json +219 -0
- package/messages/pt/aiUsage.json +36 -0
- package/messages/pt/buttons.json +19 -0
- package/messages/pt/categories.json +35 -0
- package/messages/pt/common.json +16 -0
- package/messages/pt/dev.json +101 -0
- package/messages/pt/docs.json +27 -0
- package/messages/pt/entities.json +7 -0
- package/messages/pt/features.json +119 -0
- package/messages/pt/footer.json +22 -0
- package/messages/pt/home.json +57 -0
- package/messages/pt/index.ts +39 -0
- package/messages/pt/mobileNav.json +13 -0
- package/messages/pt/navigation.json +8 -0
- package/messages/pt/observability.json +74 -0
- package/messages/pt/posts.json +54 -0
- package/messages/pt/pricing.json +102 -0
- package/messages/pt/support.json +9 -0
- package/messages/pt/teams.json +8 -0
- package/migrations/089_add_editor_team_role.sql +39 -0
- package/migrations/090_demo_users_teams.sql +540 -0
- package/migrations/091_greek_teams_billing.sql +523 -0
- package/migrations/092_billing_sample_data.sql +774 -0
- package/migrations/093_pages_sample_data.sql +1158 -0
- package/migrations/094_posts_sample_data.sql +278 -0
- package/migrations/095_tasks_sample_data.sql +440 -0
- package/migrations/096_customers_sample_data.sql +358 -0
- package/migrations/097_scheduled_actions_sample_data.sql +111 -0
- package/package.json +22 -0
- package/public/docs/desktop-layout-example.png +0 -0
- package/styles/components.css +11 -0
- package/styles/globals.css +179 -0
- package/templates/(public)/blog/[slug]/page.tsx +65 -0
- package/templates/(public)/layout.tsx +25 -0
- package/templates/(public)/page.tsx +200 -0
- package/templates/(public)/support/page.tsx +321 -0
- package/templates/dashboard/(main)/agent-multi/page.tsx +63 -0
- package/templates/dashboard/(main)/agent-single/page.tsx +142 -0
- package/templates/dashboard/(main)/settings/ai-usage/page.tsx +157 -0
- package/templates/superadmin/ai-observability/[traceId]/page.tsx +27 -0
- package/templates/superadmin/ai-observability/page.tsx +17 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Customers Service Types
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for the CustomersService.
|
|
5
|
+
* Customers is a private entity (access.shared: true) - all authenticated
|
|
6
|
+
* users can access all records within their team context.
|
|
7
|
+
*
|
|
8
|
+
* @module CustomersTypes
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Day of week values for visit/contact days
|
|
13
|
+
*/
|
|
14
|
+
export type DayOfWeek = 'lun' | 'mar' | 'mie' | 'jue' | 'vie'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Customer entity
|
|
18
|
+
*/
|
|
19
|
+
export interface Customer {
|
|
20
|
+
id: string
|
|
21
|
+
name: string
|
|
22
|
+
account: number
|
|
23
|
+
office: string
|
|
24
|
+
phone?: string
|
|
25
|
+
salesRep?: string
|
|
26
|
+
visitDays?: DayOfWeek[]
|
|
27
|
+
contactDays?: DayOfWeek[]
|
|
28
|
+
createdAt?: string
|
|
29
|
+
updatedAt?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Options for listing customers
|
|
34
|
+
*/
|
|
35
|
+
export interface CustomerListOptions {
|
|
36
|
+
limit?: number
|
|
37
|
+
offset?: number
|
|
38
|
+
orderBy?: 'name' | 'account' | 'office' | 'salesRep' | 'createdAt'
|
|
39
|
+
orderDir?: 'asc' | 'desc'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Result of listing customers with pagination
|
|
44
|
+
*/
|
|
45
|
+
export interface CustomerListResult {
|
|
46
|
+
customers: Customer[]
|
|
47
|
+
total: number
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Search options
|
|
52
|
+
*/
|
|
53
|
+
export interface CustomerSearchOptions {
|
|
54
|
+
query: string
|
|
55
|
+
limit?: number
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Data required to create a new customer
|
|
60
|
+
*/
|
|
61
|
+
export interface CustomerCreateData {
|
|
62
|
+
name: string
|
|
63
|
+
account: number
|
|
64
|
+
office: string
|
|
65
|
+
teamId: string
|
|
66
|
+
phone?: string
|
|
67
|
+
salesRep?: string
|
|
68
|
+
visitDays?: DayOfWeek[]
|
|
69
|
+
contactDays?: DayOfWeek[]
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Data for updating an existing customer
|
|
74
|
+
*/
|
|
75
|
+
export interface CustomerUpdateData {
|
|
76
|
+
name?: string
|
|
77
|
+
account?: number
|
|
78
|
+
office?: string
|
|
79
|
+
phone?: string
|
|
80
|
+
salesRep?: string
|
|
81
|
+
visitDays?: DayOfWeek[]
|
|
82
|
+
contactDays?: DayOfWeek[]
|
|
83
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"entity": {
|
|
3
|
+
"name": "Customer",
|
|
4
|
+
"plural": "Customers",
|
|
5
|
+
"description": "Manage your customers and their information"
|
|
6
|
+
},
|
|
7
|
+
"fields": {
|
|
8
|
+
"office": {
|
|
9
|
+
"label": "Office",
|
|
10
|
+
"description": "Customer office or branch location",
|
|
11
|
+
"placeholder": "Enter office..."
|
|
12
|
+
},
|
|
13
|
+
"number": {
|
|
14
|
+
"label": "Customer Number",
|
|
15
|
+
"description": "Unique customer identifier",
|
|
16
|
+
"placeholder": "Enter customer number..."
|
|
17
|
+
},
|
|
18
|
+
"name": {
|
|
19
|
+
"label": "Name",
|
|
20
|
+
"description": "Customer name",
|
|
21
|
+
"placeholder": "Enter customer name..."
|
|
22
|
+
},
|
|
23
|
+
"phone": {
|
|
24
|
+
"label": "Phone",
|
|
25
|
+
"description": "Customer phone number",
|
|
26
|
+
"placeholder": "Enter phone number..."
|
|
27
|
+
},
|
|
28
|
+
"salesRep": {
|
|
29
|
+
"label": "Sales Representative",
|
|
30
|
+
"description": "Assigned sales representative",
|
|
31
|
+
"placeholder": "Enter sales rep..."
|
|
32
|
+
},
|
|
33
|
+
"visitDays": {
|
|
34
|
+
"label": "Visit Days",
|
|
35
|
+
"description": "Scheduled visit days",
|
|
36
|
+
"placeholder": "Enter visit days..."
|
|
37
|
+
},
|
|
38
|
+
"contactDays": {
|
|
39
|
+
"label": "Contact Days",
|
|
40
|
+
"description": "Preferred contact days",
|
|
41
|
+
"placeholder": "Enter contact days..."
|
|
42
|
+
},
|
|
43
|
+
"image": {
|
|
44
|
+
"label": "Image",
|
|
45
|
+
"description": "Customer logo or image URL",
|
|
46
|
+
"placeholder": "Enter image URL..."
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"actions": {
|
|
50
|
+
"create": "Create Customer",
|
|
51
|
+
"edit": "Edit Customer",
|
|
52
|
+
"delete": "Delete Customer",
|
|
53
|
+
"view": "View Customer",
|
|
54
|
+
"list": "List Customers",
|
|
55
|
+
"search": "Search Customers",
|
|
56
|
+
"export": "Export Customers",
|
|
57
|
+
"import": "Import Customers"
|
|
58
|
+
},
|
|
59
|
+
"messages": {
|
|
60
|
+
"created": "Customer created successfully",
|
|
61
|
+
"updated": "Customer updated successfully",
|
|
62
|
+
"deleted": "Customer deleted successfully",
|
|
63
|
+
"notFound": "Customer not found",
|
|
64
|
+
"error": "An error occurred while processing the customer"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"entity": {
|
|
3
|
+
"name": "Cliente",
|
|
4
|
+
"plural": "Clientes",
|
|
5
|
+
"description": "Gestiona tus clientes y su información"
|
|
6
|
+
},
|
|
7
|
+
"fields": {
|
|
8
|
+
"office": {
|
|
9
|
+
"label": "Oficina",
|
|
10
|
+
"description": "Oficina o sucursal del cliente",
|
|
11
|
+
"placeholder": "Ingrese oficina..."
|
|
12
|
+
},
|
|
13
|
+
"number": {
|
|
14
|
+
"label": "Número de Cliente",
|
|
15
|
+
"description": "Identificador único del cliente",
|
|
16
|
+
"placeholder": "Ingrese número de cliente..."
|
|
17
|
+
},
|
|
18
|
+
"name": {
|
|
19
|
+
"label": "Nombre",
|
|
20
|
+
"description": "Nombre del cliente",
|
|
21
|
+
"placeholder": "Ingrese nombre del cliente..."
|
|
22
|
+
},
|
|
23
|
+
"phone": {
|
|
24
|
+
"label": "Teléfono",
|
|
25
|
+
"description": "Número de teléfono del cliente",
|
|
26
|
+
"placeholder": "Ingrese número de teléfono..."
|
|
27
|
+
},
|
|
28
|
+
"salesRep": {
|
|
29
|
+
"label": "Representante de Ventas",
|
|
30
|
+
"description": "Representante de ventas asignado",
|
|
31
|
+
"placeholder": "Ingrese representante de ventas..."
|
|
32
|
+
},
|
|
33
|
+
"visitDays": {
|
|
34
|
+
"label": "Días de Visita",
|
|
35
|
+
"description": "Días programados de visita",
|
|
36
|
+
"placeholder": "Ingrese días de visita..."
|
|
37
|
+
},
|
|
38
|
+
"contactDays": {
|
|
39
|
+
"label": "Días de Contacto",
|
|
40
|
+
"description": "Días preferidos de contacto",
|
|
41
|
+
"placeholder": "Ingrese días de contacto..."
|
|
42
|
+
},
|
|
43
|
+
"image": {
|
|
44
|
+
"label": "Imagen",
|
|
45
|
+
"description": "Logo o URL de imagen del cliente",
|
|
46
|
+
"placeholder": "Ingrese URL de imagen..."
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"actions": {
|
|
50
|
+
"create": "Crear Cliente",
|
|
51
|
+
"edit": "Editar Cliente",
|
|
52
|
+
"delete": "Eliminar Cliente",
|
|
53
|
+
"view": "Ver Cliente",
|
|
54
|
+
"list": "Listar Clientes",
|
|
55
|
+
"search": "Buscar Clientes",
|
|
56
|
+
"export": "Exportar Clientes",
|
|
57
|
+
"import": "Importar Clientes"
|
|
58
|
+
},
|
|
59
|
+
"messages": {
|
|
60
|
+
"created": "Cliente creado exitosamente",
|
|
61
|
+
"updated": "Cliente actualizado exitosamente",
|
|
62
|
+
"deleted": "Cliente eliminado exitosamente",
|
|
63
|
+
"notFound": "Cliente no encontrado",
|
|
64
|
+
"error": "Ocurrió un error al procesar el cliente"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
-- Migration: 001_customers_table.sql
|
|
2
|
+
-- Description: Customers (table, indexes, RLS)
|
|
3
|
+
-- Date: 2025-01-24
|
|
4
|
+
-- Updated: 2025-11-26 (Phase 2 - Team Isolation)
|
|
5
|
+
|
|
6
|
+
-- ============================================
|
|
7
|
+
-- TABLE
|
|
8
|
+
-- ============================================
|
|
9
|
+
DROP TABLE IF EXISTS public."customers" CASCADE;
|
|
10
|
+
|
|
11
|
+
CREATE TABLE IF NOT EXISTS public."customers" (
|
|
12
|
+
-- Primary Key
|
|
13
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
14
|
+
|
|
15
|
+
-- Relational Fields (al inicio)
|
|
16
|
+
"userId" TEXT NOT NULL REFERENCES public."users"(id) ON DELETE CASCADE,
|
|
17
|
+
"teamId" TEXT NOT NULL REFERENCES public."teams"(id) ON DELETE CASCADE,
|
|
18
|
+
|
|
19
|
+
-- Entity-specific fields
|
|
20
|
+
office TEXT NOT NULL,
|
|
21
|
+
account INTEGER NOT NULL UNIQUE,
|
|
22
|
+
name TEXT NOT NULL,
|
|
23
|
+
phone TEXT,
|
|
24
|
+
"salesRep" TEXT,
|
|
25
|
+
"visitDays" JSONB DEFAULT '[]'::jsonb,
|
|
26
|
+
"contactDays" JSONB DEFAULT '[]'::jsonb,
|
|
27
|
+
|
|
28
|
+
-- System fields
|
|
29
|
+
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
30
|
+
"updatedAt" TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
COMMENT ON TABLE public."customers" IS 'Customers table with team isolation via RLS';
|
|
34
|
+
COMMENT ON COLUMN public."customers"."userId" IS 'User who created this customer record';
|
|
35
|
+
COMMENT ON COLUMN public."customers"."teamId" IS 'Team context for isolation';
|
|
36
|
+
COMMENT ON COLUMN public."customers".office IS 'Customer office or branch';
|
|
37
|
+
COMMENT ON COLUMN public."customers".account IS 'Unique customer account number (used for identification and external integrations)';
|
|
38
|
+
COMMENT ON COLUMN public."customers".name IS 'Customer name or company name';
|
|
39
|
+
COMMENT ON COLUMN public."customers".phone IS 'Customer phone number';
|
|
40
|
+
COMMENT ON COLUMN public."customers"."salesRep" IS 'Sales representative assigned to customer';
|
|
41
|
+
COMMENT ON COLUMN public."customers"."visitDays" IS 'Days of the week for customer visits as JSONB array';
|
|
42
|
+
COMMENT ON COLUMN public."customers"."contactDays" IS 'Days of the week for customer contact as JSONB array';
|
|
43
|
+
|
|
44
|
+
-- ============================================
|
|
45
|
+
-- TRIGGER updatedAt (uses Better Auth function)
|
|
46
|
+
-- ============================================
|
|
47
|
+
DROP TRIGGER IF EXISTS customer_set_updated_at ON public."customers";
|
|
48
|
+
CREATE TRIGGER customer_set_updated_at
|
|
49
|
+
BEFORE UPDATE ON public."customers"
|
|
50
|
+
FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();
|
|
51
|
+
|
|
52
|
+
-- ============================================
|
|
53
|
+
-- INDEXES
|
|
54
|
+
-- ============================================
|
|
55
|
+
CREATE INDEX IF NOT EXISTS idx_customer_user_id ON public."customers"("userId");
|
|
56
|
+
CREATE INDEX IF NOT EXISTS idx_customer_team_id ON public."customers"("teamId");
|
|
57
|
+
CREATE INDEX IF NOT EXISTS idx_customer_office ON public."customers"(office);
|
|
58
|
+
CREATE INDEX IF NOT EXISTS idx_customer_account ON public."customers"(account);
|
|
59
|
+
CREATE INDEX IF NOT EXISTS idx_customer_name ON public."customers"(name);
|
|
60
|
+
CREATE INDEX IF NOT EXISTS idx_customer_sales_rep ON public."customers"("salesRep");
|
|
61
|
+
CREATE INDEX IF NOT EXISTS idx_customer_created_at ON public."customers"("createdAt");
|
|
62
|
+
CREATE INDEX IF NOT EXISTS idx_customer_team_created ON public."customers"("teamId", "createdAt" DESC);
|
|
63
|
+
|
|
64
|
+
-- JSONB indexes
|
|
65
|
+
CREATE INDEX IF NOT EXISTS idx_customer_visit_days_gin ON public."customers" USING GIN ("visitDays");
|
|
66
|
+
CREATE INDEX IF NOT EXISTS idx_customer_contact_days_gin ON public."customers" USING GIN ("contactDays");
|
|
67
|
+
|
|
68
|
+
-- ============================================
|
|
69
|
+
-- RLS
|
|
70
|
+
-- ============================================
|
|
71
|
+
ALTER TABLE public."customers" ENABLE ROW LEVEL SECURITY;
|
|
72
|
+
|
|
73
|
+
-- Cleanup existing policies
|
|
74
|
+
DROP POLICY IF EXISTS "Users can view own customers" ON public."customers";
|
|
75
|
+
DROP POLICY IF EXISTS "Users can create customers" ON public."customers";
|
|
76
|
+
DROP POLICY IF EXISTS "Users can insert own customers" ON public."customers";
|
|
77
|
+
DROP POLICY IF EXISTS "Users can update own customers" ON public."customers";
|
|
78
|
+
DROP POLICY IF EXISTS "Users can delete own customers" ON public."customers";
|
|
79
|
+
DROP POLICY IF EXISTS "Customers auth can do all" ON public."customers";
|
|
80
|
+
DROP POLICY IF EXISTS "Customers team can do all" ON public."customers";
|
|
81
|
+
|
|
82
|
+
-- ============================
|
|
83
|
+
-- RLS: TEAM ISOLATION ONLY
|
|
84
|
+
-- ============================
|
|
85
|
+
-- IMPORTANTE: RLS solo verifica team membership
|
|
86
|
+
-- La lógica de access.shared (user isolation) se maneja a NIVEL APP
|
|
87
|
+
-- Esto permite cambiar el comportamiento desde el config sin modificar RLS
|
|
88
|
+
CREATE POLICY "Customers team can do all"
|
|
89
|
+
ON public."customers"
|
|
90
|
+
FOR ALL TO authenticated
|
|
91
|
+
USING (
|
|
92
|
+
-- Superadmin bypass
|
|
93
|
+
public.is_superadmin()
|
|
94
|
+
OR
|
|
95
|
+
-- Team isolation only: user must be member of the team
|
|
96
|
+
"teamId" = ANY(public.get_user_team_ids())
|
|
97
|
+
)
|
|
98
|
+
WITH CHECK (
|
|
99
|
+
public.is_superadmin()
|
|
100
|
+
OR
|
|
101
|
+
"teamId" = ANY(public.get_user_team_ids())
|
|
102
|
+
);
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
-- Migration: 002_customers_metas.sql
|
|
2
|
+
-- Description: Customers metas (table, indexes, RLS)
|
|
3
|
+
-- Date: 2025-01-24
|
|
4
|
+
-- Updated: 2025-11-26 (Phase 2 - Team Isolation via parent)
|
|
5
|
+
|
|
6
|
+
-- ============================================
|
|
7
|
+
-- TABLE
|
|
8
|
+
-- ============================================
|
|
9
|
+
-- No DROP needed - removed automatically by parent table CASCADE
|
|
10
|
+
CREATE TABLE IF NOT EXISTS public."customers_metas" (
|
|
11
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
12
|
+
"entityId" TEXT NOT NULL REFERENCES public."customers"(id) ON DELETE CASCADE,
|
|
13
|
+
"metaKey" TEXT NOT NULL,
|
|
14
|
+
"metaValue" JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
15
|
+
"dataType" TEXT DEFAULT 'json',
|
|
16
|
+
"isPublic" BOOLEAN NOT NULL DEFAULT false,
|
|
17
|
+
"isSearchable" BOOLEAN NOT NULL DEFAULT false,
|
|
18
|
+
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
19
|
+
"updatedAt" TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
20
|
+
CONSTRAINT customers_metas_unique_key UNIQUE ("entityId", "metaKey")
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
COMMENT ON TABLE public."customers_metas" IS 'Customers metadata table - stores additional key-value pairs for customers';
|
|
24
|
+
COMMENT ON COLUMN public."customers_metas"."entityId" IS 'Generic foreign key to parent customer entity';
|
|
25
|
+
COMMENT ON COLUMN public."customers_metas"."metaKey" IS 'Metadata key name';
|
|
26
|
+
COMMENT ON COLUMN public."customers_metas"."metaValue" IS 'Metadata value as JSONB';
|
|
27
|
+
COMMENT ON COLUMN public."customers_metas"."dataType" IS 'Type hint for the value: json, string, number, boolean';
|
|
28
|
+
COMMENT ON COLUMN public."customers_metas"."isPublic" IS 'Whether this metadata is publicly readable';
|
|
29
|
+
COMMENT ON COLUMN public."customers_metas"."isSearchable" IS 'Whether this metadata is searchable';
|
|
30
|
+
|
|
31
|
+
-- ============================================
|
|
32
|
+
-- TRIGGER updatedAt (uses Better Auth function)
|
|
33
|
+
-- ============================================
|
|
34
|
+
DROP TRIGGER IF EXISTS customers_metas_set_updated_at ON public."customers_metas";
|
|
35
|
+
CREATE TRIGGER customers_metas_set_updated_at
|
|
36
|
+
BEFORE UPDATE ON public."customers_metas"
|
|
37
|
+
FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();
|
|
38
|
+
|
|
39
|
+
-- ============================================
|
|
40
|
+
-- INDEXES
|
|
41
|
+
-- ============================================
|
|
42
|
+
CREATE INDEX IF NOT EXISTS idx_customers_metas_customer_id ON public."customers_metas"("entityId");
|
|
43
|
+
CREATE INDEX IF NOT EXISTS idx_customers_metas_key ON public."customers_metas"("metaKey");
|
|
44
|
+
CREATE INDEX IF NOT EXISTS idx_customers_metas_composite ON public."customers_metas"("entityId", "metaKey", "isPublic");
|
|
45
|
+
CREATE INDEX IF NOT EXISTS idx_customers_metas_is_public ON public."customers_metas"("isPublic") WHERE "isPublic" = true;
|
|
46
|
+
CREATE INDEX IF NOT EXISTS idx_customers_metas_is_searchable ON public."customers_metas"("isSearchable") WHERE "isSearchable" = true;
|
|
47
|
+
CREATE INDEX IF NOT EXISTS idx_customers_metas_searchable_key ON public."customers_metas"("metaKey") WHERE "isSearchable" = true;
|
|
48
|
+
CREATE INDEX IF NOT EXISTS idx_customers_metas_value_gin ON public."customers_metas" USING GIN ("metaValue");
|
|
49
|
+
CREATE INDEX IF NOT EXISTS idx_customers_metas_value_ops ON public."customers_metas" USING GIN ("metaValue" jsonb_path_ops);
|
|
50
|
+
|
|
51
|
+
-- ============================================
|
|
52
|
+
-- RLS
|
|
53
|
+
-- ============================================
|
|
54
|
+
ALTER TABLE public."customers_metas" ENABLE ROW LEVEL SECURITY;
|
|
55
|
+
|
|
56
|
+
-- Cleanup existing policies
|
|
57
|
+
DROP POLICY IF EXISTS "Users can view own customer metas" ON public."customers_metas";
|
|
58
|
+
DROP POLICY IF EXISTS "Users can insert customer metas" ON public."customers_metas";
|
|
59
|
+
DROP POLICY IF EXISTS "Users can insert own customer metas" ON public."customers_metas";
|
|
60
|
+
DROP POLICY IF EXISTS "Users can update own customer metas" ON public."customers_metas";
|
|
61
|
+
DROP POLICY IF EXISTS "Users can delete own customer metas" ON public."customers_metas";
|
|
62
|
+
DROP POLICY IF EXISTS "Customer metas auth can do all" ON public."customers_metas";
|
|
63
|
+
DROP POLICY IF EXISTS "Customer metas team can do all" ON public."customers_metas";
|
|
64
|
+
|
|
65
|
+
-- ============================
|
|
66
|
+
-- RLS: TEAM ISOLATION VIA PARENT
|
|
67
|
+
-- ============================
|
|
68
|
+
-- Hereda el aislamiento del parent customer via teamId
|
|
69
|
+
-- La lógica de access.shared se maneja a NIVEL APP
|
|
70
|
+
CREATE POLICY "Customer metas team can do all"
|
|
71
|
+
ON public."customers_metas"
|
|
72
|
+
FOR ALL TO authenticated
|
|
73
|
+
USING (
|
|
74
|
+
-- Superadmin bypass
|
|
75
|
+
public.is_superadmin()
|
|
76
|
+
OR
|
|
77
|
+
-- Team isolation via parent customer
|
|
78
|
+
EXISTS (
|
|
79
|
+
SELECT 1 FROM public."customers" c
|
|
80
|
+
WHERE c.id = "entityId"
|
|
81
|
+
AND c."teamId" = ANY(public.get_user_team_ids())
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
WITH CHECK (
|
|
85
|
+
public.is_superadmin()
|
|
86
|
+
OR
|
|
87
|
+
EXISTS (
|
|
88
|
+
SELECT 1 FROM public."customers" c
|
|
89
|
+
WHERE c.id = "entityId"
|
|
90
|
+
AND c."teamId" = ANY(public.get_user_team_ids())
|
|
91
|
+
)
|
|
92
|
+
);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"entity": {
|
|
3
|
+
"singular": "Page",
|
|
4
|
+
"plural": "Pages"
|
|
5
|
+
},
|
|
6
|
+
"fields": {
|
|
7
|
+
"title": {
|
|
8
|
+
"label": "Title",
|
|
9
|
+
"placeholder": "Enter page title...",
|
|
10
|
+
"description": "Page title displayed in browser and navigation"
|
|
11
|
+
},
|
|
12
|
+
"slug": {
|
|
13
|
+
"label": "Slug",
|
|
14
|
+
"placeholder": "page-slug",
|
|
15
|
+
"description": "URL-friendly identifier (e.g., 'about' for /about)"
|
|
16
|
+
},
|
|
17
|
+
"status": {
|
|
18
|
+
"label": "Status",
|
|
19
|
+
"description": "Publication status",
|
|
20
|
+
"options": {
|
|
21
|
+
"draft": "Draft",
|
|
22
|
+
"published": "Published",
|
|
23
|
+
"scheduled": "Scheduled",
|
|
24
|
+
"archived": "Archived"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"actions": {
|
|
29
|
+
"create": "Create Page",
|
|
30
|
+
"edit": "Edit Page",
|
|
31
|
+
"delete": "Delete Page",
|
|
32
|
+
"publish": "Publish",
|
|
33
|
+
"unpublish": "Unpublish"
|
|
34
|
+
},
|
|
35
|
+
"messages": {
|
|
36
|
+
"created": "Page created successfully",
|
|
37
|
+
"updated": "Page updated successfully",
|
|
38
|
+
"deleted": "Page deleted successfully",
|
|
39
|
+
"published": "Page published successfully"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"entity": {
|
|
3
|
+
"singular": "Pagina",
|
|
4
|
+
"plural": "Paginas"
|
|
5
|
+
},
|
|
6
|
+
"fields": {
|
|
7
|
+
"title": {
|
|
8
|
+
"label": "Titulo",
|
|
9
|
+
"placeholder": "Ingresa el titulo de la pagina...",
|
|
10
|
+
"description": "Titulo de la pagina mostrado en el navegador y navegacion"
|
|
11
|
+
},
|
|
12
|
+
"slug": {
|
|
13
|
+
"label": "Slug",
|
|
14
|
+
"placeholder": "slug-pagina",
|
|
15
|
+
"description": "Identificador amigable para URL (ej: 'acerca' para /acerca)"
|
|
16
|
+
},
|
|
17
|
+
"status": {
|
|
18
|
+
"label": "Estado",
|
|
19
|
+
"description": "Estado de publicacion",
|
|
20
|
+
"options": {
|
|
21
|
+
"draft": "Borrador",
|
|
22
|
+
"published": "Publicado",
|
|
23
|
+
"scheduled": "Programado",
|
|
24
|
+
"archived": "Archivado"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"actions": {
|
|
29
|
+
"create": "Crear Pagina",
|
|
30
|
+
"edit": "Editar Pagina",
|
|
31
|
+
"delete": "Eliminar Pagina",
|
|
32
|
+
"publish": "Publicar",
|
|
33
|
+
"unpublish": "Despublicar"
|
|
34
|
+
},
|
|
35
|
+
"messages": {
|
|
36
|
+
"created": "Pagina creada exitosamente",
|
|
37
|
+
"updated": "Pagina actualizada exitosamente",
|
|
38
|
+
"deleted": "Pagina eliminada exitosamente",
|
|
39
|
+
"published": "Pagina publicada exitosamente"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
-- Migration: 008_pages_table.sql
|
|
2
|
+
-- Description: Create pages table for dynamic page builder system with blocks
|
|
3
|
+
-- Date: 2025-01-21
|
|
4
|
+
-- Updated: 2025-12-17 (Add userId, teamId system fields + team isolation RLS)
|
|
5
|
+
|
|
6
|
+
-- ============================================
|
|
7
|
+
-- TABLE: pages
|
|
8
|
+
-- ============================================
|
|
9
|
+
DROP TABLE IF EXISTS public.pages CASCADE;
|
|
10
|
+
|
|
11
|
+
CREATE TABLE IF NOT EXISTS public.pages (
|
|
12
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
13
|
+
|
|
14
|
+
-- Relational Fields (at the beginning)
|
|
15
|
+
"userId" TEXT NOT NULL REFERENCES public."users"(id) ON DELETE CASCADE,
|
|
16
|
+
"teamId" TEXT NOT NULL REFERENCES public."teams"(id) ON DELETE CASCADE,
|
|
17
|
+
|
|
18
|
+
slug VARCHAR(255) NOT NULL,
|
|
19
|
+
title VARCHAR(255) NOT NULL,
|
|
20
|
+
blocks JSONB NOT NULL DEFAULT '[]'::JSONB,
|
|
21
|
+
locale VARCHAR(10) NOT NULL DEFAULT 'en',
|
|
22
|
+
|
|
23
|
+
-- SEO fields
|
|
24
|
+
"seoTitle" VARCHAR(255),
|
|
25
|
+
"seoDescription" TEXT,
|
|
26
|
+
"seoKeywords" TEXT,
|
|
27
|
+
"ogImage" TEXT,
|
|
28
|
+
noindex BOOLEAN DEFAULT FALSE,
|
|
29
|
+
nofollow BOOLEAN DEFAULT FALSE,
|
|
30
|
+
|
|
31
|
+
-- Meta fields
|
|
32
|
+
published BOOLEAN DEFAULT FALSE,
|
|
33
|
+
"authorId" TEXT REFERENCES public."users"(id) ON DELETE SET NULL,
|
|
34
|
+
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
35
|
+
"updatedAt" TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
36
|
+
|
|
37
|
+
-- Constraints
|
|
38
|
+
CONSTRAINT unique_slug_locale UNIQUE(slug, locale),
|
|
39
|
+
CONSTRAINT valid_slug CHECK (slug ~ '^[a-z0-9\-]+$'),
|
|
40
|
+
CONSTRAINT valid_locale CHECK (locale ~ '^[a-z]{2}(-[A-Z]{2})?$'),
|
|
41
|
+
CONSTRAINT slug_length CHECK (LENGTH(slug) >= 2 AND LENGTH(slug) <= 100),
|
|
42
|
+
CONSTRAINT title_not_empty CHECK (LENGTH(TRIM(title)) > 0)
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
-- ============================================
|
|
46
|
+
-- COMMENTS
|
|
47
|
+
-- ============================================
|
|
48
|
+
COMMENT ON TABLE public.pages IS 'Dynamic pages created via block editor system';
|
|
49
|
+
COMMENT ON COLUMN public.pages."userId" IS 'User who created this page';
|
|
50
|
+
COMMENT ON COLUMN public.pages."teamId" IS 'Team context for isolation';
|
|
51
|
+
COMMENT ON COLUMN public.pages.blocks IS 'Array of block instances with props (JSONB)';
|
|
52
|
+
COMMENT ON COLUMN public.pages.slug IS 'URL-friendly identifier (lowercase, hyphens only)';
|
|
53
|
+
COMMENT ON COLUMN public.pages.locale IS 'Language/locale code (e.g., en, es, en-US)';
|
|
54
|
+
|
|
55
|
+
-- ============================================
|
|
56
|
+
-- TRIGGER: updatedAt
|
|
57
|
+
-- ============================================
|
|
58
|
+
DROP TRIGGER IF EXISTS pages_set_updated_at ON public.pages;
|
|
59
|
+
CREATE TRIGGER pages_set_updated_at
|
|
60
|
+
BEFORE UPDATE ON public.pages
|
|
61
|
+
FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();
|
|
62
|
+
|
|
63
|
+
-- ============================================
|
|
64
|
+
-- INDEXES
|
|
65
|
+
-- ============================================
|
|
66
|
+
CREATE INDEX IF NOT EXISTS idx_pages_user_id ON public.pages("userId");
|
|
67
|
+
CREATE INDEX IF NOT EXISTS idx_pages_team_id ON public.pages("teamId");
|
|
68
|
+
CREATE INDEX IF NOT EXISTS idx_pages_slug ON public.pages(slug);
|
|
69
|
+
CREATE INDEX IF NOT EXISTS idx_pages_locale ON public.pages(locale);
|
|
70
|
+
CREATE INDEX IF NOT EXISTS idx_pages_published ON public.pages(published);
|
|
71
|
+
CREATE INDEX IF NOT EXISTS idx_pages_author ON public.pages("authorId");
|
|
72
|
+
CREATE INDEX IF NOT EXISTS idx_pages_created ON public.pages("createdAt" DESC);
|
|
73
|
+
CREATE INDEX IF NOT EXISTS idx_pages_slug_locale ON public.pages(slug, locale);
|
|
74
|
+
CREATE INDEX IF NOT EXISTS idx_pages_published_locale ON public.pages(published, locale) WHERE published = TRUE;
|
|
75
|
+
CREATE INDEX IF NOT EXISTS idx_pages_blocks_gin ON public.pages USING GIN (blocks);
|
|
76
|
+
CREATE INDEX IF NOT EXISTS idx_pages_team_created ON public.pages("teamId", "createdAt" DESC);
|
|
77
|
+
|
|
78
|
+
-- ============================================
|
|
79
|
+
-- RLS
|
|
80
|
+
-- ============================================
|
|
81
|
+
ALTER TABLE public.pages ENABLE ROW LEVEL SECURITY;
|
|
82
|
+
|
|
83
|
+
DROP POLICY IF EXISTS "pages public can select" ON public.pages;
|
|
84
|
+
DROP POLICY IF EXISTS "pages auth can do all" ON public.pages;
|
|
85
|
+
DROP POLICY IF EXISTS "Pages team can do all" ON public.pages;
|
|
86
|
+
|
|
87
|
+
-- Public can read published pages
|
|
88
|
+
CREATE POLICY "pages public can select"
|
|
89
|
+
ON public.pages
|
|
90
|
+
FOR SELECT TO anon
|
|
91
|
+
USING (published = TRUE);
|
|
92
|
+
|
|
93
|
+
-- ============================
|
|
94
|
+
-- RLS: TEAM ISOLATION ONLY
|
|
95
|
+
-- ============================
|
|
96
|
+
-- IMPORTANT: RLS only verifies team membership
|
|
97
|
+
-- access.shared logic (user isolation) is handled at APP LEVEL
|
|
98
|
+
CREATE POLICY "Pages team can do all"
|
|
99
|
+
ON public.pages
|
|
100
|
+
FOR ALL TO authenticated
|
|
101
|
+
USING (
|
|
102
|
+
-- Superadmin bypass
|
|
103
|
+
public.is_superadmin()
|
|
104
|
+
OR
|
|
105
|
+
-- Team isolation only: user must be member of the team
|
|
106
|
+
"teamId" = ANY(public.get_user_team_ids())
|
|
107
|
+
)
|
|
108
|
+
WITH CHECK (
|
|
109
|
+
public.is_superadmin()
|
|
110
|
+
OR
|
|
111
|
+
"teamId" = ANY(public.get_user_team_ids())
|
|
112
|
+
);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
-- Migration: 002_pages_metas.sql
|
|
2
|
+
-- Description: Create pages_metas table for flexible key-value metadata storage
|
|
3
|
+
-- Date: 2025-01-25
|
|
4
|
+
|
|
5
|
+
-- Create pages_metas table
|
|
6
|
+
CREATE TABLE IF NOT EXISTS "pages_metas" (
|
|
7
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
8
|
+
"pageId" UUID NOT NULL REFERENCES public."pages"(id) ON DELETE CASCADE,
|
|
9
|
+
"metaKey" TEXT NOT NULL,
|
|
10
|
+
"metaValue" JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
11
|
+
"dataType" TEXT,
|
|
12
|
+
"isPublic" BOOLEAN NOT NULL DEFAULT FALSE,
|
|
13
|
+
"isSearchable" BOOLEAN NOT NULL DEFAULT FALSE,
|
|
14
|
+
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
15
|
+
"updatedAt" TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
16
|
+
CONSTRAINT pages_metas_unique_key UNIQUE ("pageId", "metaKey")
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
-- Indices
|
|
20
|
+
CREATE INDEX IF NOT EXISTS idx_pages_metas_page_id ON "pages_metas"("pageId");
|
|
21
|
+
CREATE INDEX IF NOT EXISTS idx_pages_metas_key ON "pages_metas"("metaKey");
|
|
22
|
+
CREATE INDEX IF NOT EXISTS idx_pages_metas_composite ON "pages_metas"("pageId", "metaKey", "isPublic");
|
|
23
|
+
CREATE INDEX IF NOT EXISTS idx_pages_metas_searchable ON "pages_metas"("isSearchable") WHERE "isSearchable" = true;
|
|
24
|
+
CREATE INDEX IF NOT EXISTS idx_pages_metas_public ON "pages_metas"("isPublic") WHERE "isPublic" = true;
|
|
25
|
+
CREATE INDEX IF NOT EXISTS idx_pages_metas_value_gin ON "pages_metas" USING GIN ("metaValue");
|
|
26
|
+
|
|
27
|
+
-- Trigger for updatedAt
|
|
28
|
+
DROP TRIGGER IF EXISTS pages_metas_set_updated_at ON "pages_metas";
|
|
29
|
+
CREATE TRIGGER pages_metas_set_updated_at
|
|
30
|
+
BEFORE UPDATE ON "pages_metas"
|
|
31
|
+
FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();
|
|
32
|
+
|
|
33
|
+
-- ============================================
|
|
34
|
+
-- RLS POLICIES
|
|
35
|
+
-- ============================================
|
|
36
|
+
ALTER TABLE "pages_metas" ENABLE ROW LEVEL SECURITY;
|
|
37
|
+
|
|
38
|
+
-- Public can read public metadata
|
|
39
|
+
DROP POLICY IF EXISTS "pages_metas_public_select" ON "pages_metas";
|
|
40
|
+
CREATE POLICY "pages_metas_public_select"
|
|
41
|
+
ON "pages_metas" FOR SELECT TO anon
|
|
42
|
+
USING ("isPublic" = TRUE);
|
|
43
|
+
|
|
44
|
+
-- Authenticated users can manage all metadata
|
|
45
|
+
DROP POLICY IF EXISTS "pages_metas_auth_all" ON "pages_metas";
|
|
46
|
+
CREATE POLICY "pages_metas_auth_all"
|
|
47
|
+
ON "pages_metas" FOR ALL TO authenticated
|
|
48
|
+
USING (true)
|
|
49
|
+
WITH CHECK (true);
|
|
50
|
+
|
|
51
|
+
-- Comments
|
|
52
|
+
COMMENT ON TABLE "pages_metas" IS 'Flexible key-value metadata storage for pages';
|
|
53
|
+
COMMENT ON COLUMN "pages_metas"."pageId" IS 'Reference to the parent page';
|
|
54
|
+
COMMENT ON COLUMN "pages_metas"."metaKey" IS 'Unique key identifier for the metadata';
|
|
55
|
+
COMMENT ON COLUMN "pages_metas"."metaValue" IS 'JSONB value for flexible data storage';
|
|
56
|
+
COMMENT ON COLUMN "pages_metas"."isPublic" IS 'If true, this metadata can be read by anyone';
|