@nextsparkjs/theme-default 0.1.0-beta.49 → 0.1.0-beta.51

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.
@@ -26,6 +26,7 @@
26
26
  import { NextRequest } from 'next/server'
27
27
  import { z } from 'zod'
28
28
  import { authenticateRequest } from '@nextsparkjs/core/lib/api/auth/dual-auth'
29
+ import { withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit'
29
30
  import { streamChat } from '@/plugins/langchain/lib/agent-factory'
30
31
  import { createSSEEncoder } from '@/plugins/langchain/lib/streaming'
31
32
  import { loadSystemPrompt } from '@/themes/default/lib/langchain/agents'
@@ -46,7 +47,7 @@ const StreamChatRequestSchema = z.object({
46
47
  /**
47
48
  * POST - Stream chat response
48
49
  */
49
- export async function POST(request: NextRequest) {
50
+ const postHandler = async (request: NextRequest) => {
50
51
  try {
51
52
  // 1. Authentication
52
53
  const authResult = await authenticateRequest(request)
@@ -210,3 +211,5 @@ export async function POST(request: NextRequest) {
210
211
  ) as any
211
212
  }
212
213
  }
214
+
215
+ export const POST = withRateLimitTier(postHandler, 'write')
@@ -1,6 +1,7 @@
1
1
  import { NextRequest, NextResponse } from 'next/server'
2
2
  import { z } from 'zod'
3
3
  import { authenticateRequest } from '@nextsparkjs/core/lib/api/auth/dual-auth'
4
+ import { withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit'
4
5
  import { processMessage } from '@/themes/default/lib/langchain/orchestrator'
5
6
  import { dbMemoryStore } from '@/plugins/langchain/lib/db-memory-store'
6
7
  import type { ChatMessage } from '@/plugins/langchain/types/langchain.types'
@@ -52,7 +53,7 @@ function convertToApiMessages(
52
53
  * - Sub-agents: task-assistant, customer-assistant, page-assistant
53
54
  * - Handles ambiguous requests by asking for clarification
54
55
  */
55
- export async function POST(req: NextRequest) {
56
+ const postHandler = async (req: NextRequest) => {
56
57
  try {
57
58
  // 1. Dual authentication (API key or session)
58
59
  const authResult = await authenticateRequest(req)
@@ -118,10 +119,12 @@ export async function POST(req: NextRequest) {
118
119
  }
119
120
  }
120
121
 
122
+ export const POST = withRateLimitTier(postHandler, 'write')
123
+
121
124
  /**
122
125
  * GET - Retrieve conversation history
123
126
  */
124
- export async function GET(req: NextRequest) {
127
+ const getHandler = async (req: NextRequest) => {
125
128
  const { searchParams } = new URL(req.url)
126
129
  const sessionId = searchParams.get('sessionId')
127
130
 
@@ -187,10 +190,12 @@ export async function GET(req: NextRequest) {
187
190
  }
188
191
  }
189
192
 
193
+ export const GET = withRateLimitTier(getHandler, 'read')
194
+
190
195
  /**
191
196
  * DELETE - Clear conversation
192
197
  */
193
- export async function DELETE(req: NextRequest) {
198
+ const deleteHandler = async (req: NextRequest) => {
194
199
  const authResult = await authenticateRequest(req)
195
200
  if (!authResult.success || !authResult.user) {
196
201
  return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
@@ -224,3 +229,5 @@ export async function DELETE(req: NextRequest) {
224
229
  return NextResponse.json({ success: false, error: 'Failed to clear' }, { status: 500 })
225
230
  }
226
231
  }
232
+
233
+ export const DELETE = withRateLimitTier(deleteHandler, 'write')
@@ -14,6 +14,7 @@
14
14
  import { NextRequest, NextResponse } from 'next/server'
15
15
  import { z } from 'zod'
16
16
  import { authenticateRequest } from '@nextsparkjs/core/lib/api/auth/dual-auth'
17
+ import { withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit'
17
18
  import { createAgent } from '@/plugins/langchain/lib/agent-factory'
18
19
  import {
19
20
  dbMemoryStore,
@@ -74,7 +75,7 @@ function convertToApiMessages(
74
75
  /**
75
76
  * POST - Send message to single agent
76
77
  */
77
- export async function POST(req: NextRequest) {
78
+ const postHandler = async (req: NextRequest) => {
78
79
  try {
79
80
  // 1. Authentication
80
81
  const authResult = await authenticateRequest(req)
@@ -181,10 +182,12 @@ export async function POST(req: NextRequest) {
181
182
  }
182
183
  }
183
184
 
185
+ export const POST = withRateLimitTier(postHandler, 'write')
186
+
184
187
  /**
185
188
  * GET - Retrieve conversation history
186
189
  */
187
- export async function GET(req: NextRequest) {
190
+ const getHandler = async (req: NextRequest) => {
188
191
  const { searchParams } = new URL(req.url)
189
192
  const sessionId = searchParams.get('sessionId')
190
193
 
@@ -252,10 +255,12 @@ export async function GET(req: NextRequest) {
252
255
  }
253
256
  }
254
257
 
258
+ export const GET = withRateLimitTier(getHandler, 'read')
259
+
255
260
  /**
256
261
  * DELETE - Clear conversation
257
262
  */
258
- export async function DELETE(req: NextRequest) {
263
+ const deleteHandler = async (req: NextRequest) => {
259
264
  const authResult = await authenticateRequest(req)
260
265
  if (!authResult.success || !authResult.user) {
261
266
  return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
@@ -289,3 +294,5 @@ export async function DELETE(req: NextRequest) {
289
294
  return NextResponse.json({ success: false, error: 'Failed to clear' }, { status: 500 })
290
295
  }
291
296
  }
297
+
298
+ export const DELETE = withRateLimitTier(deleteHandler, 'write')
@@ -20,6 +20,7 @@
20
20
 
21
21
  import { NextRequest, NextResponse } from 'next/server'
22
22
  import { authenticateRequest } from '@nextsparkjs/core/lib/api/auth/dual-auth'
23
+ import { withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit'
23
24
  import { tokenTracker } from '@/plugins/langchain/lib/token-tracker'
24
25
  import { queryOne } from '@nextsparkjs/core/lib/db'
25
26
 
@@ -28,7 +29,7 @@ type Period = 'today' | '7d' | '30d' | 'all'
28
29
  /**
29
30
  * GET - Retrieve token usage statistics
30
31
  */
31
- export async function GET(request: NextRequest) {
32
+ const getHandler = async (request: NextRequest) => {
32
33
  try {
33
34
  // 1. Authentication
34
35
  const authResult = await authenticateRequest(request)
@@ -120,3 +121,5 @@ export async function GET(request: NextRequest) {
120
121
  )
121
122
  }
122
123
  }
124
+
125
+ export const GET = withRateLimitTier(getHandler, 'read')
@@ -65,14 +65,16 @@ export function TextContentBlock({
65
65
  )}
66
66
 
67
67
  {/* Rich Text Content */}
68
- <div
69
- className={cn(
70
- 'prose prose-lg dark:prose-invert',
71
- maxWidthClasses[maxWidth],
72
- alignmentClasses[alignment]
73
- )}
74
- dangerouslySetInnerHTML={{ __html: content }}
75
- />
68
+ {content && (
69
+ <div
70
+ className={cn(
71
+ 'prose prose-lg dark:prose-invert',
72
+ maxWidthClasses[maxWidth],
73
+ alignmentClasses[alignment]
74
+ )}
75
+ dangerouslySetInnerHTML={{ __html: content }}
76
+ />
77
+ )}
76
78
 
77
79
  {/* Optional CTA */}
78
80
  {cta && (
@@ -94,6 +94,20 @@ export const DASHBOARD_CONFIG = {
94
94
  enabled: true,
95
95
  },
96
96
 
97
+ /**
98
+ * Settings menu dropdown (gear icon)
99
+ */
100
+ settingsMenu: {
101
+ enabled: true,
102
+ links: [
103
+ {
104
+ label: 'navigation.patterns',
105
+ href: '/dashboard/patterns',
106
+ icon: 'layers',
107
+ },
108
+ ],
109
+ },
110
+
97
111
  /**
98
112
  * User menu dropdown
99
113
  */
@@ -110,6 +110,17 @@ export const PERMISSIONS_CONFIG_OVERRIDES: ThemePermissionsConfig = {
110
110
  { action: 'delete', label: 'Delete Pages', description: 'Can delete pages', roles: ['owner', 'admin'], dangerous: true },
111
111
  { action: 'publish', label: 'Publish Pages', description: 'Can publish pages to make them public', roles: ['owner', 'admin'] },
112
112
  ],
113
+
114
+ // ------------------------------------------
115
+ // PATTERNS
116
+ // ------------------------------------------
117
+ patterns: [
118
+ { action: 'create', label: 'Create Patterns', description: 'Can create reusable patterns', roles: ['owner', 'admin'] },
119
+ { action: 'read', label: 'View Patterns', description: 'Can view pattern details', roles: ['owner', 'admin', 'member', 'viewer'] },
120
+ { action: 'list', label: 'List Patterns', description: 'Can see the patterns list', roles: ['owner', 'admin', 'member', 'viewer'] },
121
+ { action: 'update', label: 'Edit Patterns', description: 'Can modify patterns', roles: ['owner', 'admin'] },
122
+ { action: 'delete', label: 'Delete Patterns', description: 'Can delete patterns', roles: ['owner', 'admin'], dangerous: true },
123
+ ],
113
124
  },
114
125
 
115
126
  // ==========================================
@@ -258,7 +258,18 @@
258
258
  "noDesignFields": "No design fields",
259
259
  "noAdvancedFields": "No advanced fields"
260
260
  },
261
- "noFields": "This block has no configurable fields"
261
+ "noFields": "This block has no configurable fields",
262
+ "pattern": {
263
+ "title": "Pattern Reference",
264
+ "locked": {
265
+ "title": "Pattern is read-only",
266
+ "description": "To edit this pattern's content, edit the pattern directly. Changes will be reflected everywhere it's used."
267
+ },
268
+ "actions": {
269
+ "edit": "Edit Pattern",
270
+ "remove": "Remove from page"
271
+ }
272
+ }
262
273
  }
263
274
  }
264
275
  }
@@ -258,7 +258,18 @@
258
258
  "noDesignFields": "Sin campos de diseño",
259
259
  "noAdvancedFields": "Sin campos avanzados"
260
260
  },
261
- "noFields": "Este bloque no tiene campos configurables"
261
+ "noFields": "Este bloque no tiene campos configurables",
262
+ "pattern": {
263
+ "title": "Referencia de Patrón",
264
+ "locked": {
265
+ "title": "El patrón es de solo lectura",
266
+ "description": "Para editar el contenido de este patrón, edítalo directamente. Los cambios se reflejarán en todos los lugares donde se usa."
267
+ },
268
+ "actions": {
269
+ "edit": "Editar Patrón",
270
+ "remove": "Quitar de la página"
271
+ }
272
+ }
262
273
  }
263
274
  }
264
275
  }
@@ -20,7 +20,7 @@ INSERT INTO public.pages (
20
20
  "createdAt",
21
21
  "updatedAt"
22
22
  ) VALUES (
23
- gen_random_uuid(),
23
+ '00000000-0000-4000-a000-000000000001',
24
24
  'usr-carlos-001',
25
25
  'team-everpoint-001',
26
26
  'home',
@@ -332,7 +332,7 @@ INSERT INTO public.pages (
332
332
  "createdAt",
333
333
  "updatedAt"
334
334
  ) VALUES (
335
- gen_random_uuid(),
335
+ '00000000-0000-4000-a000-000000000002',
336
336
  'usr-carlos-001',
337
337
  'team-everpoint-001',
338
338
  'features',
@@ -500,7 +500,7 @@ INSERT INTO public.pages (
500
500
  "createdAt",
501
501
  "updatedAt"
502
502
  ) VALUES (
503
- gen_random_uuid(),
503
+ '00000000-0000-4000-a000-000000000003',
504
504
  'usr-carlos-001',
505
505
  'team-everpoint-001',
506
506
  'pricing',
@@ -644,7 +644,7 @@ INSERT INTO public.pages (
644
644
  "createdAt",
645
645
  "updatedAt"
646
646
  ) VALUES (
647
- gen_random_uuid(),
647
+ '00000000-0000-4000-a000-000000000004',
648
648
  'usr-carlos-001',
649
649
  'team-everpoint-001',
650
650
  'about',
@@ -803,7 +803,7 @@ INSERT INTO public.pages (
803
803
  "createdAt",
804
804
  "updatedAt"
805
805
  ) VALUES (
806
- gen_random_uuid(),
806
+ '00000000-0000-4000-a000-000000000005',
807
807
  'usr-carlos-001',
808
808
  'team-everpoint-001',
809
809
  'getting-started',
@@ -955,7 +955,7 @@ INSERT INTO public.pages (
955
955
  "createdAt",
956
956
  "updatedAt"
957
957
  ) VALUES (
958
- gen_random_uuid(),
958
+ '00000000-0000-4000-a000-000000000006',
959
959
  'usr-carlos-001',
960
960
  'team-everpoint-001',
961
961
  'contact',
@@ -1068,7 +1068,7 @@ INSERT INTO public.pages (
1068
1068
  "createdAt",
1069
1069
  "updatedAt"
1070
1070
  ) VALUES (
1071
- gen_random_uuid(),
1071
+ '00000000-0000-4000-a000-000000000007',
1072
1072
  'usr-carlos-001',
1073
1073
  'team-everpoint-001',
1074
1074
  'demo',
@@ -0,0 +1,234 @@
1
+ -- Migration: 098_patterns_sample_data.sql
2
+ -- Description: Sample patterns for testing pattern references and usage tracking
3
+ -- Date: 2025-01-17
4
+
5
+ -- =====================================================
6
+ -- PATTERNS SAMPLE DATA
7
+ -- =====================================================
8
+
9
+ -- Pattern 1: Newsletter CTA (Published)
10
+ INSERT INTO public.patterns (
11
+ id,
12
+ "userId",
13
+ "teamId",
14
+ title,
15
+ slug,
16
+ blocks,
17
+ status,
18
+ description,
19
+ "createdAt",
20
+ "updatedAt"
21
+ ) VALUES (
22
+ '00000000-0000-4000-b000-000000000001',
23
+ 'usr-carlos-001',
24
+ 'team-everpoint-001',
25
+ 'Newsletter CTA',
26
+ 'newsletter-cta',
27
+ '[
28
+ {
29
+ "id": "newsletter-cta-block",
30
+ "blockSlug": "cta-section",
31
+ "props": {
32
+ "title": "Stay Updated",
33
+ "content": "Subscribe to our newsletter for the latest updates, tips, and exclusive content delivered straight to your inbox.",
34
+ "backgroundColor": "primary",
35
+ "textColor": "white",
36
+ "primaryCta": {
37
+ "label": "Subscribe Now",
38
+ "url": "/newsletter",
39
+ "variant": "secondary"
40
+ },
41
+ "secondaryCta": {
42
+ "label": "Learn More",
43
+ "url": "/about",
44
+ "variant": "outline"
45
+ }
46
+ }
47
+ }
48
+ ]',
49
+ 'published',
50
+ 'Reusable newsletter call-to-action section for pages',
51
+ NOW() - INTERVAL '7 days',
52
+ NOW() - INTERVAL '2 days'
53
+ ) ON CONFLICT (id) DO UPDATE SET
54
+ title = EXCLUDED.title,
55
+ slug = EXCLUDED.slug,
56
+ blocks = EXCLUDED.blocks,
57
+ status = EXCLUDED.status,
58
+ description = EXCLUDED.description,
59
+ "updatedAt" = EXCLUDED."updatedAt";
60
+
61
+ -- Pattern 2: Footer Links (Published)
62
+ INSERT INTO public.patterns (
63
+ id,
64
+ "userId",
65
+ "teamId",
66
+ title,
67
+ slug,
68
+ blocks,
69
+ status,
70
+ description,
71
+ "createdAt",
72
+ "updatedAt"
73
+ ) VALUES (
74
+ '00000000-0000-4000-b000-000000000002',
75
+ 'usr-carlos-001',
76
+ 'team-everpoint-001',
77
+ 'Footer Links',
78
+ 'footer-links',
79
+ '[
80
+ {
81
+ "id": "footer-links-block",
82
+ "blockSlug": "text-content",
83
+ "props": {
84
+ "content": "## Quick Links\n\n- [Home](/)\n- [Features](/features)\n- [Pricing](/pricing)\n- [About](/about)\n- [Contact](/contact)\n\n## Resources\n\n- [Documentation](/getting-started)\n- [API Reference](/api)\n- [Support](/support)\n- [Blog](/blog)",
85
+ "textAlign": "left",
86
+ "maxWidth": "4xl",
87
+ "padding": "lg"
88
+ }
89
+ }
90
+ ]',
91
+ 'published',
92
+ 'Common footer links section for all pages',
93
+ NOW() - INTERVAL '14 days',
94
+ NOW() - INTERVAL '5 days'
95
+ ) ON CONFLICT (id) DO UPDATE SET
96
+ title = EXCLUDED.title,
97
+ slug = EXCLUDED.slug,
98
+ blocks = EXCLUDED.blocks,
99
+ status = EXCLUDED.status,
100
+ description = EXCLUDED.description,
101
+ "updatedAt" = EXCLUDED."updatedAt";
102
+
103
+ -- Pattern 3: Hero Header (Draft)
104
+ INSERT INTO public.patterns (
105
+ id,
106
+ "userId",
107
+ "teamId",
108
+ title,
109
+ slug,
110
+ blocks,
111
+ status,
112
+ description,
113
+ "createdAt",
114
+ "updatedAt"
115
+ ) VALUES (
116
+ '00000000-0000-4000-b000-000000000003',
117
+ 'usr-carlos-001',
118
+ 'team-everpoint-001',
119
+ 'Hero Header',
120
+ 'hero-header',
121
+ '[
122
+ {
123
+ "id": "hero-header-block",
124
+ "blockSlug": "hero",
125
+ "props": {
126
+ "title": "Welcome to NextSpark",
127
+ "content": "The complete SaaS boilerplate for modern web applications. Build faster, scale smarter.",
128
+ "textAlign": "center",
129
+ "size": "lg",
130
+ "cta": {
131
+ "text": "Get Started",
132
+ "link": "/signup",
133
+ "variant": "default"
134
+ },
135
+ "secondaryCta": {
136
+ "text": "View Demo",
137
+ "link": "/demo",
138
+ "variant": "outline"
139
+ }
140
+ }
141
+ }
142
+ ]',
143
+ 'draft',
144
+ 'Standard hero header for landing pages (work in progress)',
145
+ NOW() - INTERVAL '3 days',
146
+ NOW() - INTERVAL '1 day'
147
+ ) ON CONFLICT (id) DO UPDATE SET
148
+ title = EXCLUDED.title,
149
+ slug = EXCLUDED.slug,
150
+ blocks = EXCLUDED.blocks,
151
+ status = EXCLUDED.status,
152
+ description = EXCLUDED.description,
153
+ "updatedAt" = EXCLUDED."updatedAt";
154
+
155
+ -- =====================================================
156
+ -- PATTERN USAGES SAMPLE DATA
157
+ -- =====================================================
158
+
159
+ -- Usage 1: Newsletter CTA used in Home page
160
+ INSERT INTO public.pattern_usages (
161
+ id,
162
+ "patternId",
163
+ "entityType",
164
+ "entityId",
165
+ "teamId",
166
+ "createdAt"
167
+ ) VALUES (
168
+ '00000000-0000-4000-c000-000000000001',
169
+ '00000000-0000-4000-b000-000000000001',
170
+ 'pages',
171
+ '00000000-0000-4000-a000-000000000001',
172
+ 'team-everpoint-001',
173
+ NOW() - INTERVAL '5 days'
174
+ ) ON CONFLICT ("patternId", "entityType", "entityId") DO NOTHING;
175
+
176
+ -- Usage 2: Newsletter CTA used in Features page
177
+ INSERT INTO public.pattern_usages (
178
+ id,
179
+ "patternId",
180
+ "entityType",
181
+ "entityId",
182
+ "teamId",
183
+ "createdAt"
184
+ ) VALUES (
185
+ '00000000-0000-4000-c000-000000000002',
186
+ '00000000-0000-4000-b000-000000000001',
187
+ 'pages',
188
+ '00000000-0000-4000-a000-000000000002',
189
+ 'team-everpoint-001',
190
+ NOW() - INTERVAL '4 days'
191
+ ) ON CONFLICT ("patternId", "entityType", "entityId") DO NOTHING;
192
+
193
+ -- Usage 3: Footer Links used in Home page
194
+ INSERT INTO public.pattern_usages (
195
+ id,
196
+ "patternId",
197
+ "entityType",
198
+ "entityId",
199
+ "teamId",
200
+ "createdAt"
201
+ ) VALUES (
202
+ '00000000-0000-4000-c000-000000000003',
203
+ '00000000-0000-4000-b000-000000000002',
204
+ 'pages',
205
+ '00000000-0000-4000-a000-000000000001',
206
+ 'team-everpoint-001',
207
+ NOW() - INTERVAL '10 days'
208
+ ) ON CONFLICT ("patternId", "entityType", "entityId") DO NOTHING;
209
+
210
+ -- Usage 4: Footer Links used in About page
211
+ INSERT INTO public.pattern_usages (
212
+ id,
213
+ "patternId",
214
+ "entityType",
215
+ "entityId",
216
+ "teamId",
217
+ "createdAt"
218
+ ) VALUES (
219
+ '00000000-0000-4000-c000-000000000004',
220
+ '00000000-0000-4000-b000-000000000002',
221
+ 'pages',
222
+ '00000000-0000-4000-a000-000000000004',
223
+ 'team-everpoint-001',
224
+ NOW() - INTERVAL '8 days'
225
+ ) ON CONFLICT ("patternId", "entityType", "entityId") DO NOTHING;
226
+
227
+ -- =====================================================
228
+ -- VERIFICATION QUERIES (for manual testing)
229
+ -- =====================================================
230
+ -- SELECT id, title, status FROM patterns WHERE id::text LIKE '00000000-0000-4000-b000-%';
231
+ -- Expected: 3 rows (newsletter, footer-links, hero-header)
232
+
233
+ -- SELECT id, "patternId", "entityType", "entityId" FROM pattern_usages WHERE id::text LIKE '00000000-0000-4000-c000-%';
234
+ -- Expected: 4 rows
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextsparkjs/theme-default",
3
- "version": "0.1.0-beta.49",
3
+ "version": "0.1.0-beta.51",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./config/theme.config.ts",
@@ -17,8 +17,8 @@
17
17
  "react-dom": "^19.0.0",
18
18
  "react-markdown": "^10.1.0",
19
19
  "zod": "^4.0.0",
20
- "@nextsparkjs/core": "0.1.0-beta.49",
21
- "@nextsparkjs/testing": "0.1.0-beta.49"
20
+ "@nextsparkjs/core": "0.1.0-beta.51",
21
+ "@nextsparkjs/testing": "0.1.0-beta.51"
22
22
  },
23
23
  "nextspark": {
24
24
  "type": "theme",