@nextsparkjs/core 0.1.0-beta.173 → 0.1.0-beta.175
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/dist/styles/classes.json +1 -1
- package/dist/templates/app/api/v1/auth/signup-with-invite/route.ts +7 -2
- package/dist/templates/app/api/v1/devtools/docs/route.ts +3 -1
- package/package.json +2 -2
- package/scripts/build/registry/discovery/api-presets.mjs +72 -63
- package/templates/app/api/v1/auth/signup-with-invite/route.ts +7 -2
- package/templates/app/api/v1/devtools/docs/route.ts +3 -1
package/dist/styles/classes.json
CHANGED
|
@@ -62,11 +62,16 @@ export const POST = withRateLimitTier(withApiLogging(
|
|
|
62
62
|
return addCorsHeaders(response)
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
// Step 1: Validate invitation token
|
|
65
|
+
// Step 1: Validate the invitation token via the service pool (RLS bypass).
|
|
66
|
+
// The invitee has no session yet, so the unguessable token IS the
|
|
67
|
+
// credential. Passing a 'system' string as the userId does NOT bypass RLS
|
|
68
|
+
// — it sets a bogus RLS context on the gated pool, so under enforced RLS
|
|
69
|
+
// the row is invisible and the lookup returns null (false "not found").
|
|
66
70
|
const invitation = await queryOneWithRLS<TeamInvitation>(
|
|
67
71
|
'SELECT * FROM "team_invitations" WHERE token = $1',
|
|
68
72
|
[inviteToken],
|
|
69
|
-
|
|
73
|
+
undefined,
|
|
74
|
+
{ service: true }
|
|
70
75
|
)
|
|
71
76
|
|
|
72
77
|
if (!invitation) {
|
|
@@ -24,8 +24,10 @@ import { withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit'
|
|
|
24
24
|
const VALID_PATH_PATTERNS = [
|
|
25
25
|
// Entity folders
|
|
26
26
|
/^(contents\/)?themes\/[\w-]+\/entities\/[\w-]+\/api\/docs\.md$/,
|
|
27
|
-
// Theme routes
|
|
27
|
+
// Theme routes (legacy: docs mirrored under app/api)
|
|
28
28
|
/^(contents\/)?themes\/[\w-]+\/app\/api\/.*\/docs\.md$/,
|
|
29
|
+
// Theme routes (colocated next to the handler under api/)
|
|
30
|
+
/^(contents\/)?themes\/[\w-]+\/api\/.*\/docs\.md$/,
|
|
29
31
|
// Core routes (monorepo mode)
|
|
30
32
|
/^packages\/core\/templates\/app\/api\/.*\/docs\.md$/,
|
|
31
33
|
// Core routes (NPM mode)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextsparkjs/core",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.175",
|
|
4
4
|
"description": "NextSpark - The complete SaaS framework for Next.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "NextSpark <hello@nextspark.dev>",
|
|
@@ -469,7 +469,7 @@
|
|
|
469
469
|
"tailwind-merge": "^3.3.1",
|
|
470
470
|
"uuid": "^13.0.0",
|
|
471
471
|
"zod": "^4.1.5",
|
|
472
|
-
"@nextsparkjs/testing": "0.1.0-beta.
|
|
472
|
+
"@nextsparkjs/testing": "0.1.0-beta.175"
|
|
473
473
|
},
|
|
474
474
|
"scripts": {
|
|
475
475
|
"postinstall": "node scripts/postinstall.mjs || true",
|
|
@@ -439,75 +439,84 @@ async function discoverEntityFolders(config, results, processedEndpoints) {
|
|
|
439
439
|
*/
|
|
440
440
|
async function discoverThemeRoutes(config, results, processedEndpoints) {
|
|
441
441
|
const themeName = config.activeTheme
|
|
442
|
-
const themeApiDir = join(config.themesDir, themeName, 'app', 'api')
|
|
443
442
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
443
|
+
// Process every docs.md / presets.ts under `baseDir`, mapping each route
|
|
444
|
+
// directory to an endpoint via `endpointForDir(routeDir)`.
|
|
445
|
+
const processTree = async (baseDir, endpointForDir) => {
|
|
446
|
+
if (!existsSync(baseDir)) return
|
|
447
|
+
|
|
448
|
+
const docFiles = await findFilesRecursive(baseDir, 'docs.md')
|
|
449
|
+
const presetFiles = await findFilesRecursive(baseDir, 'presets.ts')
|
|
450
|
+
|
|
451
|
+
for (const docsPath of docFiles) {
|
|
452
|
+
try {
|
|
453
|
+
const routeDir = dirname(docsPath)
|
|
454
|
+
const endpoint = endpointForDir(routeDir)
|
|
455
|
+
if (processedEndpoints.has(endpoint)) continue
|
|
456
|
+
|
|
457
|
+
const content = await readFile(docsPath, 'utf-8')
|
|
458
|
+
const title = extractMarkdownTitle(content)
|
|
459
|
+
const slug = basename(routeDir)
|
|
460
|
+
|
|
461
|
+
results.docs.push({
|
|
462
|
+
slug,
|
|
463
|
+
title,
|
|
464
|
+
endpoint,
|
|
465
|
+
filePath: getStoragePath(docsPath, config),
|
|
466
|
+
source: 'route',
|
|
467
|
+
themeName
|
|
468
|
+
})
|
|
469
|
+
verbose(` Theme route doc discovered: ${endpoint}`)
|
|
448
470
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
// Process docs
|
|
454
|
-
for (const docsPath of docFiles) {
|
|
455
|
-
try {
|
|
456
|
-
const routeDir = dirname(docsPath)
|
|
457
|
-
const relativeDir = relative(join(config.themesDir, themeName, 'app'), routeDir)
|
|
458
|
-
const endpoint = '/' + normalizePath(relativeDir)
|
|
459
|
-
|
|
460
|
-
const content = await readFile(docsPath, 'utf-8')
|
|
461
|
-
const title = extractMarkdownTitle(content)
|
|
462
|
-
|
|
463
|
-
// Extract slug from endpoint
|
|
464
|
-
const slug = basename(routeDir)
|
|
465
|
-
|
|
466
|
-
results.docs.push({
|
|
467
|
-
slug,
|
|
468
|
-
title,
|
|
469
|
-
endpoint,
|
|
470
|
-
filePath: getStoragePath(docsPath, config),
|
|
471
|
-
source: 'route',
|
|
472
|
-
themeName
|
|
473
|
-
})
|
|
474
|
-
verbose(` Theme route doc discovered: ${endpoint}`)
|
|
475
|
-
|
|
476
|
-
processedEndpoints.add(endpoint)
|
|
477
|
-
} catch (error) {
|
|
478
|
-
log(` ERROR: Failed to read theme route doc: ${error.message}`, 'error')
|
|
471
|
+
processedEndpoints.add(endpoint)
|
|
472
|
+
} catch (error) {
|
|
473
|
+
log(` ERROR: Failed to read theme route doc: ${error.message}`, 'error')
|
|
474
|
+
}
|
|
479
475
|
}
|
|
480
|
-
}
|
|
481
476
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
477
|
+
for (const presetsPath of presetFiles) {
|
|
478
|
+
try {
|
|
479
|
+
const routeDir = dirname(presetsPath)
|
|
480
|
+
const endpoint = endpointForDir(routeDir)
|
|
481
|
+
|
|
482
|
+
const content = await readFile(presetsPath, 'utf-8')
|
|
483
|
+
const presetConfig = parsePresetFile(content)
|
|
484
|
+
|
|
485
|
+
const slug = basename(routeDir)
|
|
486
|
+
const finalEndpoint = presetConfig?.endpoint || endpoint
|
|
487
|
+
if (processedEndpoints.has(finalEndpoint)) continue
|
|
488
|
+
|
|
489
|
+
results.presets.push({
|
|
490
|
+
slug,
|
|
491
|
+
endpoint: finalEndpoint,
|
|
492
|
+
summary: presetConfig?.summary || '',
|
|
493
|
+
presets: presetConfig?.presets || [],
|
|
494
|
+
sourcePath: getStoragePath(presetsPath, config),
|
|
495
|
+
source: 'route',
|
|
496
|
+
themeName
|
|
497
|
+
})
|
|
498
|
+
verbose(` Theme route presets discovered: ${finalEndpoint} (${presetConfig?.presets?.length || 0} presets)`)
|
|
499
|
+
|
|
500
|
+
processedEndpoints.add(finalEndpoint)
|
|
501
|
+
} catch (error) {
|
|
502
|
+
log(` ERROR: Failed to parse theme route presets: ${error.message}`, 'error')
|
|
503
|
+
}
|
|
509
504
|
}
|
|
510
505
|
}
|
|
506
|
+
|
|
507
|
+
// Legacy tree: {theme}/app/api/** — the docs path mirrors the full request URL
|
|
508
|
+
// under `app/` (e.g. app/api/v1/theme/{theme}/foo -> /api/v1/theme/{theme}/foo).
|
|
509
|
+
const appDir = join(config.themesDir, themeName, 'app')
|
|
510
|
+
await processTree(join(appDir, 'api'), (routeDir) =>
|
|
511
|
+
'/' + normalizePath(relative(appDir, routeDir))
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
// Colocated tree: {theme}/api/** — docs/presets live next to the route handler,
|
|
515
|
+
// mapped the same way the handlers are (api/foo -> /api/v1/theme/{theme}/foo).
|
|
516
|
+
const apiDir = join(config.themesDir, themeName, 'api')
|
|
517
|
+
await processTree(apiDir, (routeDir) =>
|
|
518
|
+
'/api/v1/theme/' + themeName + '/' + normalizePath(relative(apiDir, routeDir))
|
|
519
|
+
)
|
|
511
520
|
}
|
|
512
521
|
|
|
513
522
|
/**
|
|
@@ -62,11 +62,16 @@ export const POST = withRateLimitTier(withApiLogging(
|
|
|
62
62
|
return addCorsHeaders(response)
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
// Step 1: Validate invitation token
|
|
65
|
+
// Step 1: Validate the invitation token via the service pool (RLS bypass).
|
|
66
|
+
// The invitee has no session yet, so the unguessable token IS the
|
|
67
|
+
// credential. Passing a 'system' string as the userId does NOT bypass RLS
|
|
68
|
+
// — it sets a bogus RLS context on the gated pool, so under enforced RLS
|
|
69
|
+
// the row is invisible and the lookup returns null (false "not found").
|
|
66
70
|
const invitation = await queryOneWithRLS<TeamInvitation>(
|
|
67
71
|
'SELECT * FROM "team_invitations" WHERE token = $1',
|
|
68
72
|
[inviteToken],
|
|
69
|
-
|
|
73
|
+
undefined,
|
|
74
|
+
{ service: true }
|
|
70
75
|
)
|
|
71
76
|
|
|
72
77
|
if (!invitation) {
|
|
@@ -24,8 +24,10 @@ import { withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit'
|
|
|
24
24
|
const VALID_PATH_PATTERNS = [
|
|
25
25
|
// Entity folders
|
|
26
26
|
/^(contents\/)?themes\/[\w-]+\/entities\/[\w-]+\/api\/docs\.md$/,
|
|
27
|
-
// Theme routes
|
|
27
|
+
// Theme routes (legacy: docs mirrored under app/api)
|
|
28
28
|
/^(contents\/)?themes\/[\w-]+\/app\/api\/.*\/docs\.md$/,
|
|
29
|
+
// Theme routes (colocated next to the handler under api/)
|
|
30
|
+
/^(contents\/)?themes\/[\w-]+\/api\/.*\/docs\.md$/,
|
|
29
31
|
// Core routes (monorepo mode)
|
|
30
32
|
/^packages\/core\/templates\/app\/api\/.*\/docs\.md$/,
|
|
31
33
|
// Core routes (NPM mode)
|