@nextsparkjs/core 0.1.0-beta.196 → 0.1.0-beta.197
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/README.md +2 -0
- package/dist/components/auth/forms/SignupForm.d.ts.map +1 -1
- package/dist/components/auth/forms/SignupForm.js +55 -45
- package/dist/components/dashboard/block-editor/config-panel.d.ts.map +1 -1
- package/dist/components/dashboard/block-editor/config-panel.js +3 -2
- package/dist/components/dashboard/block-editor/entity-fields-sidebar.d.ts.map +1 -1
- package/dist/components/dashboard/block-editor/entity-fields-sidebar.js +3 -2
- package/dist/components/users/tables/UsersTable.d.ts.map +1 -1
- package/dist/components/users/tables/UsersTable.js +3 -4
- package/dist/hooks/useAuthReadiness.d.ts +1 -0
- package/dist/hooks/useAuthReadiness.d.ts.map +1 -1
- package/dist/hooks/useAuthReadiness.js +3 -1
- package/dist/lib/actions/entity.actions.js +2 -2
- package/dist/lib/actions/team.actions.js +2 -2
- package/dist/lib/actions/user.actions.js +4 -4
- package/dist/lib/api/auth/dual-auth.d.ts.map +1 -1
- package/dist/lib/api/auth/dual-auth.js +3 -2
- package/dist/lib/api/auth.d.ts.map +1 -1
- package/dist/lib/api/auth.js +7 -0
- package/dist/lib/api/entity/generic-handler.d.ts.map +1 -1
- package/dist/lib/api/entity/generic-handler.js +66 -12
- package/dist/lib/api/entity-handler.js +2 -2
- package/dist/lib/api/helpers.d.ts.map +1 -1
- package/dist/lib/api/helpers.js +4 -2
- package/dist/lib/api/post-categories.d.ts +29 -0
- package/dist/lib/api/post-categories.d.ts.map +1 -0
- package/dist/lib/api/post-categories.js +33 -0
- package/dist/lib/auth/auth-methods.d.ts +5 -2
- package/dist/lib/auth/auth-methods.d.ts.map +1 -1
- package/dist/lib/auth/auth-methods.js +4 -2
- package/dist/lib/auth/authorization-session.d.ts +14 -0
- package/dist/lib/auth/authorization-session.d.ts.map +1 -0
- package/dist/lib/auth/authorization-session.js +10 -0
- package/dist/lib/auth/request-session.d.ts +5 -0
- package/dist/lib/auth/request-session.d.ts.map +1 -1
- package/dist/lib/auth/request-session.js +3 -0
- package/dist/lib/auth/runtime-readiness.d.ts +7 -4
- package/dist/lib/auth/runtime-readiness.d.ts.map +1 -1
- package/dist/lib/auth/runtime-readiness.js +7 -6
- package/dist/lib/auth/sample-accounts.d.ts +2 -1
- package/dist/lib/auth/sample-accounts.d.ts.map +1 -1
- package/dist/lib/auth/sample-accounts.js +4 -1
- package/dist/lib/auth/suspension.d.ts +22 -0
- package/dist/lib/auth/suspension.d.ts.map +1 -0
- package/dist/lib/auth/suspension.js +43 -0
- package/dist/lib/auth.d.ts.map +1 -1
- package/dist/lib/auth.js +15 -15
- package/dist/lib/config/app.config.d.ts.map +1 -1
- package/dist/lib/config/app.config.js +6 -7
- package/dist/lib/config/types.d.ts +8 -6
- package/dist/lib/config/types.d.ts.map +1 -1
- package/dist/lib/db-pool.d.ts +31 -0
- package/dist/lib/db-pool.d.ts.map +1 -0
- package/dist/lib/db-pool.js +75 -0
- package/dist/lib/db.d.ts +1 -1
- package/dist/lib/db.d.ts.map +1 -1
- package/dist/lib/db.js +11 -14
- package/dist/lib/entities/api-generator.js +5 -5
- package/dist/lib/oauth/token-refresh.d.ts.map +1 -1
- package/dist/lib/oauth/token-refresh.js +3 -6
- package/dist/lib/permissions/check.d.ts +9 -0
- package/dist/lib/permissions/check.d.ts.map +1 -1
- package/dist/lib/permissions/check.js +11 -1
- package/dist/lib/services/generic-entity.service.d.ts.map +1 -1
- package/dist/lib/services/generic-entity.service.js +39 -7
- package/dist/lib/services/transactional-meta.service.d.ts.map +1 -1
- package/dist/lib/services/transactional-meta.service.js +7 -8
- package/dist/lib/validation.d.ts +5 -0
- package/dist/lib/validation.d.ts.map +1 -1
- package/dist/lib/validation.js +2 -0
- package/dist/migrations/030_taxonomies_team_writes.sql +29 -0
- package/dist/migrations/031_users_role_suspended.sql +41 -0
- package/dist/routes/(auth)/signup/page.cc.d.ts +3 -1
- package/dist/routes/(auth)/signup/page.cc.d.ts.map +1 -1
- package/dist/routes/(auth)/signup/page.cc.js +2 -2
- package/dist/routes/(auth)/signup/page.d.ts +3 -1
- package/dist/routes/(auth)/signup/page.d.ts.map +1 -1
- package/dist/routes/(auth)/signup/page.js +4 -3
- package/dist/routes/_internal/area-access.js +2 -2
- package/dist/routes/api/devtools/config/entities/route.js +2 -2
- package/dist/routes/api/devtools/config/theme/route.js +2 -2
- package/dist/routes/api/devtools/tests/[...path]/route.js +2 -2
- package/dist/routes/api/devtools/tests/route.js +2 -2
- package/dist/routes/api/superadmin/subscriptions/route.js +2 -2
- package/dist/routes/api/superadmin/teams/[teamId]/route.js +2 -2
- package/dist/routes/api/superadmin/teams/route.js +2 -2
- package/dist/routes/api/superadmin/users/[userId]/route.d.ts +5 -3
- package/dist/routes/api/superadmin/users/[userId]/route.d.ts.map +1 -1
- package/dist/routes/api/superadmin/users/[userId]/route.js +46 -23
- package/dist/routes/api/superadmin/users/route.js +2 -2
- package/dist/routes/api/user/delete-account/route.d.ts.map +1 -1
- package/dist/routes/api/user/delete-account/route.js +2 -3
- package/dist/routes/api/user/plan-flags/route.d.ts.map +1 -1
- package/dist/routes/api/user/plan-flags/route.js +5 -5
- package/dist/routes/api/user/profile/route.js +3 -3
- package/dist/routes/api/v1/auth/signup-with-invite/route.d.ts.map +1 -1
- package/dist/routes/api/v1/auth/signup-with-invite/route.js +80 -44
- package/dist/routes/api/v1/post-categories/[id]/route.d.ts.map +1 -1
- package/dist/routes/api/v1/post-categories/[id]/route.js +39 -34
- package/dist/routes/api/v1/post-categories/route.d.ts.map +1 -1
- package/dist/routes/api/v1/post-categories/route.js +18 -14
- package/dist/routes/api/v1/team-invitations/route.d.ts.map +1 -1
- package/dist/routes/api/v1/team-invitations/route.js +12 -5
- package/dist/routes/superadmin/users/page.js +2 -2
- package/dist/templates/next.config.mjs +11 -31
- package/dist/templates/projects/blog/package.json +1 -1
- package/dist/templates/projects/crm/package.json +1 -1
- package/dist/templates/projects/productivity/package.json +1 -1
- package/dist/templates/projects/starter/config/app.config.ts +8 -0
- package/dist/templates/proxy.ts +6 -3
- package/migrations/030_taxonomies_team_writes.sql +29 -0
- package/migrations/031_users_role_suspended.sql +41 -0
- package/package.json +3 -3
- package/scripts/build/registry/__tests__/build-output.test.mjs +1 -1
- package/scripts/build/registry/__tests__/project-mode-root-first.test.mjs +6 -6
- package/scripts/build/registry/__tests__/write-places.test.mjs +1 -1
- package/scripts/build/registry/contracts/__tests__/contracts-wire.test.mjs +1 -1
- package/scripts/build/registry/contracts/__tests__/contracts.test.mjs +1 -1
- package/scripts/build/registry/host/README.md +2 -2
- package/scripts/build/registry/host/__tests__/facade-emitter.test.mjs +14 -13
- package/scripts/build/registry/host/__tests__/host-mode.test.mjs +1 -1
- package/scripts/build/registry/host/__tests__/host-project-composition.test.mjs +1 -1
- package/scripts/build/registry/host/__tests__/host-project.test.mjs +2 -2
- package/scripts/build/registry/host/__tests__/host-proxy-areas.test.mjs +29 -2
- package/scripts/build/registry/host/__tests__/host-robustness.test.mjs +1 -1
- package/scripts/build/registry/host/facade-emitter.mjs +1 -1
- package/scripts/build/registry/host/next-route-exports.json +2 -2
- package/scripts/build/registry/host/proxy-areas.mjs +29 -2
- package/scripts/db/verify-theme-migrations.mjs +1 -1
- package/templates/next.config.mjs +11 -31
- package/templates/projects/blog/package.json +1 -1
- package/templates/projects/crm/package.json +1 -1
- package/templates/projects/productivity/package.json +1 -1
- package/templates/projects/starter/config/app.config.ts +8 -0
- package/templates/proxy.ts +6 -3
- package/tests/jest/__mocks__/@nextsparkjs/registries/permissions-registry.ts +5 -4
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
|
-
import {
|
|
2
|
+
import { categoryReadTeam, categoryWriter } from "@nextsparkjs/core/lib/api/post-categories";
|
|
3
3
|
import { query as dbQuery } from "@nextsparkjs/core/lib/db";
|
|
4
4
|
import * as z from "zod";
|
|
5
5
|
import { withRateLimitTier } from "@nextsparkjs/core/lib/api/rate-limit";
|
|
@@ -15,15 +15,33 @@ const updateCategorySchema = z.object({
|
|
|
15
15
|
isDefault: z.boolean().optional(),
|
|
16
16
|
isActive: z.boolean().optional()
|
|
17
17
|
});
|
|
18
|
+
async function findCategory(id, writer) {
|
|
19
|
+
const found = await dbQuery(
|
|
20
|
+
`SELECT "teamId" FROM taxonomies WHERE id = $1 AND type = 'post_category' AND ("teamId" IS NULL OR "teamId" = $2)`,
|
|
21
|
+
[id, writer.teamId]
|
|
22
|
+
);
|
|
23
|
+
if (found.rows.length === 0) {
|
|
24
|
+
return { response: NextResponse.json({ success: false, error: "Category not found" }, { status: 404 }) };
|
|
25
|
+
}
|
|
26
|
+
if (found.rows[0].teamId === null && !writer.superadmin) {
|
|
27
|
+
return { response: NextResponse.json(
|
|
28
|
+
{ success: false, error: "Only a superadmin can change a category shared by every team", code: "PERMISSION_DENIED" },
|
|
29
|
+
{ status: 403 }
|
|
30
|
+
) };
|
|
31
|
+
}
|
|
32
|
+
return found.rows[0];
|
|
33
|
+
}
|
|
18
34
|
export const GET = withRateLimitTier(async (request, { params }) => {
|
|
19
35
|
try {
|
|
20
36
|
const { id } = await params;
|
|
37
|
+
const scope = await categoryReadTeam(request);
|
|
38
|
+
if ("response" in scope) return scope.response;
|
|
21
39
|
const result = await dbQuery(
|
|
22
40
|
`SELECT id, name, slug, description, icon, color, "parentId", "order", "isDefault", "isActive",
|
|
23
41
|
"createdAt", "updatedAt"
|
|
24
42
|
FROM taxonomies
|
|
25
|
-
WHERE id = $1 AND type = 'post_category' AND "deletedAt" IS NULL`,
|
|
26
|
-
[id]
|
|
43
|
+
WHERE id = $1 AND type = 'post_category' AND "deletedAt" IS NULL AND ("teamId" IS NULL OR "teamId" = $2)`,
|
|
44
|
+
[id, scope.teamId]
|
|
27
45
|
);
|
|
28
46
|
if (result.rows.length === 0) {
|
|
29
47
|
return NextResponse.json({ success: false, error: "Category not found" }, { status: 404 });
|
|
@@ -39,24 +57,17 @@ export const GET = withRateLimitTier(async (request, { params }) => {
|
|
|
39
57
|
}, "read");
|
|
40
58
|
export const PUT = withRateLimitTier(async (request, { params }) => {
|
|
41
59
|
try {
|
|
42
|
-
const
|
|
43
|
-
if (
|
|
44
|
-
return createAuthFailureResponse(authResult);
|
|
45
|
-
}
|
|
60
|
+
const writer = await categoryWriter(request, "update");
|
|
61
|
+
if ("response" in writer) return writer.response;
|
|
46
62
|
const body = await request.json();
|
|
47
63
|
const data = updateCategorySchema.parse(body);
|
|
48
64
|
const { id } = await params;
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
[id]
|
|
52
|
-
);
|
|
53
|
-
if (typeCheck.rows.length === 0) {
|
|
54
|
-
return NextResponse.json({ success: false, error: "Category not found" }, { status: 404 });
|
|
55
|
-
}
|
|
65
|
+
const row = await findCategory(id, writer);
|
|
66
|
+
if ("response" in row) return row.response;
|
|
56
67
|
if (data.slug) {
|
|
57
68
|
const slugCheck = await dbQuery(
|
|
58
|
-
|
|
59
|
-
[data.slug, id]
|
|
69
|
+
`SELECT id FROM taxonomies WHERE type = 'post_category' AND slug = $1 AND id != $2 AND "teamId" IS NOT DISTINCT FROM $3`,
|
|
70
|
+
[data.slug, id, row.teamId]
|
|
60
71
|
);
|
|
61
72
|
if (slugCheck.rows.length > 0) {
|
|
62
73
|
return NextResponse.json(
|
|
@@ -67,8 +78,8 @@ export const PUT = withRateLimitTier(async (request, { params }) => {
|
|
|
67
78
|
}
|
|
68
79
|
if (data.parentId) {
|
|
69
80
|
const parentCheck = await dbQuery(
|
|
70
|
-
|
|
71
|
-
[data.parentId]
|
|
81
|
+
`SELECT id FROM taxonomies WHERE id = $1 AND type = 'post_category' AND ("teamId" IS NULL OR "teamId" IS NOT DISTINCT FROM $2)`,
|
|
82
|
+
[data.parentId, row.teamId]
|
|
72
83
|
);
|
|
73
84
|
if (parentCheck.rows.length === 0) {
|
|
74
85
|
return NextResponse.json({
|
|
@@ -132,7 +143,8 @@ export const PUT = withRateLimitTier(async (request, { params }) => {
|
|
|
132
143
|
return NextResponse.json({ success: true, data: current.rows[0] });
|
|
133
144
|
}
|
|
134
145
|
values.push(id);
|
|
135
|
-
|
|
146
|
+
values.push(row.teamId);
|
|
147
|
+
const query = `UPDATE taxonomies SET ${updates.join(", ")} WHERE id = $${paramIndex} AND "teamId" IS NOT DISTINCT FROM $${paramIndex + 1} RETURNING *`;
|
|
136
148
|
const result = await dbQuery(query, values);
|
|
137
149
|
if (result.rows.length === 0) {
|
|
138
150
|
return NextResponse.json({ success: false, error: "Category not found" }, { status: 404 });
|
|
@@ -152,18 +164,11 @@ export const PUT = withRateLimitTier(async (request, { params }) => {
|
|
|
152
164
|
}, "write");
|
|
153
165
|
export const DELETE = withRateLimitTier(async (request, { params }) => {
|
|
154
166
|
try {
|
|
155
|
-
const
|
|
156
|
-
if (
|
|
157
|
-
return createAuthFailureResponse(authResult);
|
|
158
|
-
}
|
|
167
|
+
const writer = await categoryWriter(request, "delete");
|
|
168
|
+
if ("response" in writer) return writer.response;
|
|
159
169
|
const { id } = await params;
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
[id]
|
|
163
|
-
);
|
|
164
|
-
if (typeCheck.rows.length === 0) {
|
|
165
|
-
return NextResponse.json({ success: false, error: "Category not found" }, { status: 404 });
|
|
166
|
-
}
|
|
170
|
+
const row = await findCategory(id, writer);
|
|
171
|
+
if ("response" in row) return row.response;
|
|
167
172
|
let usageCount = 0;
|
|
168
173
|
try {
|
|
169
174
|
const usageCheck = await dbQuery(
|
|
@@ -177,8 +182,8 @@ export const DELETE = withRateLimitTier(async (request, { params }) => {
|
|
|
177
182
|
}
|
|
178
183
|
if (usageCount > 0) {
|
|
179
184
|
await dbQuery(
|
|
180
|
-
'UPDATE taxonomies SET "deletedAt" = now() WHERE id = $1',
|
|
181
|
-
[id]
|
|
185
|
+
'UPDATE taxonomies SET "deletedAt" = now() WHERE id = $1 AND "teamId" IS NOT DISTINCT FROM $2',
|
|
186
|
+
[id, row.teamId]
|
|
182
187
|
);
|
|
183
188
|
return NextResponse.json({
|
|
184
189
|
success: true,
|
|
@@ -186,8 +191,8 @@ export const DELETE = withRateLimitTier(async (request, { params }) => {
|
|
|
186
191
|
});
|
|
187
192
|
} else {
|
|
188
193
|
const result = await dbQuery(
|
|
189
|
-
|
|
190
|
-
[id]
|
|
194
|
+
'DELETE FROM taxonomies WHERE id = $1 AND "teamId" IS NOT DISTINCT FROM $2 RETURNING id',
|
|
195
|
+
[id, row.teamId]
|
|
191
196
|
);
|
|
192
197
|
if (result.rows.length === 0) {
|
|
193
198
|
return NextResponse.json({ success: false, error: "Category not found" }, { status: 404 });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../src/routes/api/v1/post-categories/route.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAKvD,OAAO,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAA;
|
|
1
|
+
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../src/routes/api/v1/post-categories/route.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAKvD,OAAO,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAA;AA0BvE,eAAO,MAAM,GAAG,iDAuBN,CAAC;AAGX,eAAO,MAAM,IAAI,iDAqEN,CAAC;AAEZ,eAAO,MAAM,OAAO,sBAAgB,CAAA"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
|
-
import {
|
|
2
|
+
import { categoryReadTeam, categoryWriter } from "@nextsparkjs/core/lib/api/post-categories";
|
|
3
3
|
import { query as dbQuery } from "@nextsparkjs/core/lib/db";
|
|
4
4
|
import * as z from "zod";
|
|
5
5
|
import { withRateLimitTier } from "@nextsparkjs/core/lib/api/rate-limit";
|
|
@@ -17,14 +17,18 @@ const createCategorySchema = z.object({
|
|
|
17
17
|
function generateSlug(name) {
|
|
18
18
|
return name.toLowerCase().trim().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
19
19
|
}
|
|
20
|
-
export const GET = withRateLimitTier(async () => {
|
|
20
|
+
export const GET = withRateLimitTier(async (request) => {
|
|
21
21
|
try {
|
|
22
|
+
const scope = await categoryReadTeam(request);
|
|
23
|
+
if ("response" in scope) return scope.response;
|
|
22
24
|
const result = await dbQuery(
|
|
23
25
|
`SELECT id, name, slug, description, icon, color, "parentId", "order", "isDefault", "isActive",
|
|
24
26
|
"createdAt", "updatedAt"
|
|
25
27
|
FROM taxonomies
|
|
26
28
|
WHERE type = 'post_category' AND "deletedAt" IS NULL AND "isActive" = true
|
|
27
|
-
|
|
29
|
+
AND ("teamId" IS NULL OR "teamId" = $1)
|
|
30
|
+
ORDER BY "order" ASC, name ASC`,
|
|
31
|
+
[scope.teamId]
|
|
28
32
|
);
|
|
29
33
|
return NextResponse.json({
|
|
30
34
|
success: true,
|
|
@@ -37,16 +41,15 @@ export const GET = withRateLimitTier(async () => {
|
|
|
37
41
|
}, "read");
|
|
38
42
|
export const POST = withRateLimitTier(async (request) => {
|
|
39
43
|
try {
|
|
40
|
-
const
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
+
const writer = await categoryWriter(request, "create");
|
|
45
|
+
if ("response" in writer) return writer.response;
|
|
46
|
+
const teamId = writer.teamId;
|
|
44
47
|
const body = await request.json();
|
|
45
48
|
const data = createCategorySchema.parse(body);
|
|
46
49
|
const slug = data.slug || generateSlug(data.name);
|
|
47
50
|
const existing = await dbQuery(
|
|
48
|
-
|
|
49
|
-
[slug]
|
|
51
|
+
`SELECT id FROM taxonomies WHERE type = 'post_category' AND slug = $1 AND "teamId" = $2`,
|
|
52
|
+
[slug, teamId]
|
|
50
53
|
);
|
|
51
54
|
if (existing.rows.length > 0) {
|
|
52
55
|
return NextResponse.json({
|
|
@@ -57,8 +60,8 @@ export const POST = withRateLimitTier(async (request) => {
|
|
|
57
60
|
}
|
|
58
61
|
if (data.parentId) {
|
|
59
62
|
const parentCheck = await dbQuery(
|
|
60
|
-
|
|
61
|
-
[data.parentId]
|
|
63
|
+
`SELECT id FROM taxonomies WHERE id = $1 AND type = 'post_category' AND ("teamId" IS NULL OR "teamId" = $2)`,
|
|
64
|
+
[data.parentId, teamId]
|
|
62
65
|
);
|
|
63
66
|
if (parentCheck.rows.length === 0) {
|
|
64
67
|
return NextResponse.json({
|
|
@@ -69,8 +72,8 @@ export const POST = withRateLimitTier(async (request) => {
|
|
|
69
72
|
}
|
|
70
73
|
const result = await dbQuery(
|
|
71
74
|
`INSERT INTO taxonomies (
|
|
72
|
-
type, slug, name, description, icon, color, "parentId", "order", "isDefault", "userId"
|
|
73
|
-
) VALUES ('post_category', $1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
75
|
+
type, slug, name, description, icon, color, "parentId", "order", "isDefault", "userId", "teamId"
|
|
76
|
+
) VALUES ('post_category', $1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
74
77
|
RETURNING *`,
|
|
75
78
|
[
|
|
76
79
|
slug,
|
|
@@ -81,7 +84,8 @@ export const POST = withRateLimitTier(async (request) => {
|
|
|
81
84
|
data.parentId || null,
|
|
82
85
|
data.order,
|
|
83
86
|
data.isDefault,
|
|
84
|
-
|
|
87
|
+
writer.userId,
|
|
88
|
+
teamId
|
|
85
89
|
]
|
|
86
90
|
);
|
|
87
91
|
return NextResponse.json({ success: true, data: result.rows[0] }, { status: 201 });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../src/routes/api/v1/team-invitations/route.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAgBvD,wBAAsB,OAAO,CAAC,OAAO,EAAE,WAAW,kCAEjD;AAGD,eAAO,MAAM,GAAG,
|
|
1
|
+
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../src/routes/api/v1/team-invitations/route.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAgBvD,wBAAsB,OAAO,CAAC,OAAO,EAAE,WAAW,kCAEjD;AAGD,eAAO,MAAM,GAAG,iDAkGL,CAAC"}
|
|
@@ -25,11 +25,13 @@ export const GET = withRateLimitTier(withApiLogging(async (req) => {
|
|
|
25
25
|
}
|
|
26
26
|
const userEmail = authResult.user.email;
|
|
27
27
|
const { searchParams } = new URL(req.url);
|
|
28
|
-
const queryParams =
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
const queryParams = Object.fromEntries(
|
|
29
|
+
Object.entries({
|
|
30
|
+
page: searchParams.get("page"),
|
|
31
|
+
limit: searchParams.get("limit"),
|
|
32
|
+
status: searchParams.get("status")
|
|
33
|
+
}).filter(([, value]) => value !== null && value !== "")
|
|
34
|
+
);
|
|
33
35
|
const validatedQuery = invitationListQuerySchema.parse(queryParams);
|
|
34
36
|
const { page, limit, status } = validatedQuery;
|
|
35
37
|
const offset = (page - 1) * limit;
|
|
@@ -75,6 +77,11 @@ export const GET = withRateLimitTier(withApiLogging(async (req) => {
|
|
|
75
77
|
const response = createApiResponse(invitations, paginationMeta);
|
|
76
78
|
return addCorsHeaders(response, req);
|
|
77
79
|
} catch (error) {
|
|
80
|
+
if (error instanceof Error && error.name === "ZodError") {
|
|
81
|
+
const zodError = error;
|
|
82
|
+
const response2 = createApiError("Validation error", 400, zodError.issues, "VALIDATION_ERROR");
|
|
83
|
+
return addCorsHeaders(response2, req);
|
|
84
|
+
}
|
|
78
85
|
console.error("Error fetching invitations:", error);
|
|
79
86
|
const response = createApiError("Internal server error", 500);
|
|
80
87
|
return addCorsHeaders(response, req);
|
|
@@ -39,8 +39,8 @@ import { sel } from "@nextsparkjs/core/lib/selectors";
|
|
|
39
39
|
import { withBasePath } from "@nextsparkjs/core/lib/base-path";
|
|
40
40
|
const roleOptions = [
|
|
41
41
|
{ value: "member", label: "Member" },
|
|
42
|
-
{ value: "
|
|
43
|
-
{ value: "
|
|
42
|
+
{ value: "developer", label: "Developer" },
|
|
43
|
+
{ value: "suspended", label: "Suspended" }
|
|
44
44
|
];
|
|
45
45
|
const statusOptions = [
|
|
46
46
|
{ value: "verified", label: "Verified" },
|
|
@@ -109,42 +109,21 @@ const nextConfig = {
|
|
|
109
109
|
formats: ['image/avif', 'image/webp'],
|
|
110
110
|
deviceSizes: [640, 750, 828, 1080, 1200, 1920],
|
|
111
111
|
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
|
|
112
|
+
// Hosts the server may fetch and resize through /_next/image.
|
|
113
|
+
// Keep this list to the exact hosts you use.
|
|
112
114
|
remotePatterns: [
|
|
115
|
+
// Google profile pictures shown for accounts that sign in with Google.
|
|
113
116
|
{
|
|
114
117
|
protocol: 'https',
|
|
115
118
|
hostname: 'lh3.googleusercontent.com',
|
|
116
119
|
pathname: '/**',
|
|
117
120
|
},
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
},
|
|
123
|
-
{
|
|
124
|
-
protocol: 'https',
|
|
125
|
-
hostname: 'images.unsplash.com',
|
|
126
|
-
pathname: '/**',
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
protocol: 'https',
|
|
130
|
-
hostname: 'upload.wikimedia.org',
|
|
131
|
-
pathname: '/**',
|
|
132
|
-
},
|
|
133
|
-
{
|
|
134
|
-
protocol: 'https',
|
|
135
|
-
hostname: 'i.pravatar.cc',
|
|
136
|
-
pathname: '/**',
|
|
137
|
-
},
|
|
138
|
-
{
|
|
139
|
-
protocol: 'https',
|
|
140
|
-
hostname: '*.supabase.co',
|
|
141
|
-
pathname: '/**',
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
protocol: 'https',
|
|
145
|
-
hostname: '*.cloudinary.com',
|
|
146
|
-
pathname: '/**',
|
|
147
|
-
},
|
|
121
|
+
// Examples: add the exact host of your own store, e.g. your Vercel Blob store
|
|
122
|
+
// (`<store-id>.public.blob.vercel-storage.com`) once uploads use it.
|
|
123
|
+
// { protocol: 'https', hostname: '<store-id>.public.blob.vercel-storage.com', pathname: '/**' },
|
|
124
|
+
// { protocol: 'https', hostname: '<project-ref>.supabase.co', pathname: '/**' },
|
|
125
|
+
// { protocol: 'https', hostname: 'res.cloudinary.com', pathname: '/<cloud-name>/**' },
|
|
126
|
+
// { protocol: 'https', hostname: 'images.unsplash.com', pathname: '/**' },
|
|
148
127
|
],
|
|
149
128
|
},
|
|
150
129
|
// Next.js sets TURBOPACK before it loads this file whenever Turbopack builds
|
|
@@ -158,7 +137,8 @@ const nextConfig = {
|
|
|
158
137
|
async headers() {
|
|
159
138
|
const isProduction = process.env.NODE_ENV === 'production';
|
|
160
139
|
|
|
161
|
-
//
|
|
140
|
+
// Image hosts the browser may load directly (CSP img-src). The server only fetches the hosts in
|
|
141
|
+
// images.remotePatterns above; add a host there too when next/image should serve it.
|
|
162
142
|
// NOTE: Wildcard patterns (*.public.blob.vercel-storage.com, *.supabase.co, *.cloudinary.com)
|
|
163
143
|
// allow images from any account on these services for development flexibility.
|
|
164
144
|
// For production with stricter security, consider restricting to specific account subdomains.
|
|
@@ -126,6 +126,14 @@ export const APP_CONFIG_OVERRIDES = {
|
|
|
126
126
|
// methods: ['email-password', 'google'],
|
|
127
127
|
// methods: ['email-otp', 'email-password', 'google'],
|
|
128
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Password endpoints (sign-in/email, sign-up/email, reset). Left undeclared
|
|
131
|
+
* they follow `methods` in production (off under the passwordless preset) and
|
|
132
|
+
* stay on in development (dev keyring, `pnpm db:seed` users, Cypress).
|
|
133
|
+
* Uncomment to keep password logins in production (API clients, test users):
|
|
134
|
+
*/
|
|
135
|
+
// emailAndPassword: { enabled: true },
|
|
136
|
+
|
|
129
137
|
/**
|
|
130
138
|
* Session duration / renewal in seconds (defaults: 7 days, renewed daily).
|
|
131
139
|
* Long-lived sessions for an installed PWA, for example:
|
package/dist/templates/proxy.ts
CHANGED
|
@@ -189,14 +189,17 @@ function redirectAccessDenied(request: NextRequest): NextResponse {
|
|
|
189
189
|
* Nothing here leaves the process, so no forwarded scheme or host can point
|
|
190
190
|
* the check anywhere. `disableRefresh`: this response cannot carry the renewed
|
|
191
191
|
* session cookie, so the rolling renewal is left to the auth route
|
|
192
|
-
* (lib/auth/session-refresh).
|
|
192
|
+
* (lib/auth/session-refresh). `disableCookieCache`: the session comes from the
|
|
193
|
+
* database, not the signed session_data cookie, so a sign-out, a revoked
|
|
194
|
+
* session or a role change counts at once (lib/auth/authorization-session).
|
|
195
|
+
* A suspended account's session is no session (lib/auth/suspension).
|
|
193
196
|
*/
|
|
194
197
|
async function getSession(request: NextRequest): Promise<Session | null> {
|
|
195
198
|
const session = await auth.api.getSession({
|
|
196
199
|
headers: new Headers({ cookie: request.headers.get('cookie') || '' }),
|
|
197
|
-
query: { disableRefresh: true },
|
|
200
|
+
query: { disableRefresh: true, disableCookieCache: true },
|
|
198
201
|
})
|
|
199
|
-
return session as Session
|
|
202
|
+
return session && (session.user as { role?: unknown }).role !== 'suspended' ? (session as Session) : null
|
|
200
203
|
}
|
|
201
204
|
|
|
202
205
|
/**
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
-- Migration: 030_taxonomies_team_writes.sql
|
|
2
|
+
-- Description: Taxonomy rows are written by members of their team; rows without a team only by a superadmin.
|
|
3
|
+
--
|
|
4
|
+
-- 021 let any authenticated user write a taxonomy row with "teamId" IS NULL. Post categories are now created in
|
|
5
|
+
-- the caller's team (lib/api/post-categories.ts), and the rows without a team that earlier releases created stay
|
|
6
|
+
-- readable by everyone but are changed only by a superadmin. This policy says the same on the RLS-enforced
|
|
7
|
+
-- connection: reads are as before, writes need the row's team (or is_superadmin()).
|
|
8
|
+
-- is_superadmin() also accepts a developer enrolled in the System Admin Team; the API (lib/api/post-categories.ts) is
|
|
9
|
+
-- stricter and lets only users.role = 'superadmin' change a row without a team.
|
|
10
|
+
-- Idempotent; the anon read policy from 006 is unchanged.
|
|
11
|
+
|
|
12
|
+
DROP POLICY IF EXISTS "taxonomies auth can do all" ON public.taxonomies;
|
|
13
|
+
DROP POLICY IF EXISTS "taxonomies auth can select" ON public.taxonomies;
|
|
14
|
+
DROP POLICY IF EXISTS "taxonomies team can write" ON public.taxonomies;
|
|
15
|
+
|
|
16
|
+
CREATE POLICY "taxonomies auth can select"
|
|
17
|
+
ON public.taxonomies
|
|
18
|
+
FOR SELECT TO authenticated
|
|
19
|
+
USING (
|
|
20
|
+
"teamId" IS NULL
|
|
21
|
+
OR "teamId" = ANY(public.get_user_team_ids())
|
|
22
|
+
OR public.is_superadmin()
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
CREATE POLICY "taxonomies team can write"
|
|
26
|
+
ON public.taxonomies
|
|
27
|
+
FOR ALL TO authenticated
|
|
28
|
+
USING ("teamId" = ANY(public.get_user_team_ids()) OR public.is_superadmin())
|
|
29
|
+
WITH CHECK ("teamId" = ANY(public.get_user_team_ids()) OR public.is_superadmin());
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
-- Migration: 031_users_role_suspended.sql
|
|
2
|
+
-- Description: users.role accepts 'suspended' (lib/auth/suspension.ts).
|
|
3
|
+
--
|
|
4
|
+
-- The superadmin "suspend" action sets users.role = 'suspended', which check_users_role (002: member, superadmin,
|
|
5
|
+
-- developer) refused. This adds 'suspended' to the roles the constraint already accepts and keeps every other one,
|
|
6
|
+
-- including roles a project added to it. Idempotent: a constraint that already accepts 'suspended' is left alone. A
|
|
7
|
+
-- constraint that is not a plain list of roles is left alone too, with a notice (add 'suspended' to it yourself).
|
|
8
|
+
|
|
9
|
+
DO $$
|
|
10
|
+
DECLARE
|
|
11
|
+
def TEXT;
|
|
12
|
+
roles TEXT[];
|
|
13
|
+
BEGIN
|
|
14
|
+
SELECT pg_get_constraintdef(c.oid) INTO def
|
|
15
|
+
FROM pg_constraint c
|
|
16
|
+
WHERE c.conrelid = 'public.users'::regclass AND c.conname = 'check_users_role';
|
|
17
|
+
|
|
18
|
+
IF def IS NULL THEN
|
|
19
|
+
ALTER TABLE public."users" ADD CONSTRAINT check_users_role
|
|
20
|
+
CHECK ("role" IN ('member', 'superadmin', 'developer', 'suspended'));
|
|
21
|
+
RETURN;
|
|
22
|
+
END IF;
|
|
23
|
+
|
|
24
|
+
IF def ~ '\msuspended\M' THEN
|
|
25
|
+
RETURN;
|
|
26
|
+
END IF;
|
|
27
|
+
|
|
28
|
+
-- "role" IN (...) is stored as: CHECK ((role = ANY (ARRAY['member'::text, ...])))
|
|
29
|
+
IF def !~ '^CHECK \(\(+"?role"?\)?(::text)? = ANY \(\(?ARRAY\[' THEN
|
|
30
|
+
RAISE NOTICE 'check_users_role is not a plain list of roles (%); add ''suspended'' to it to use the suspend action', def;
|
|
31
|
+
RETURN;
|
|
32
|
+
END IF;
|
|
33
|
+
|
|
34
|
+
SELECT array_agg(m[1]) INTO roles FROM regexp_matches(def, '''([^'']+)''', 'g') AS m;
|
|
35
|
+
|
|
36
|
+
ALTER TABLE public."users" DROP CONSTRAINT check_users_role;
|
|
37
|
+
EXECUTE format(
|
|
38
|
+
'ALTER TABLE public."users" ADD CONSTRAINT check_users_role CHECK ("role" IN (%s))',
|
|
39
|
+
(SELECT string_agg(quote_literal(r), ', ') FROM unnest(array_append(roles, 'suspended')) AS r)
|
|
40
|
+
);
|
|
41
|
+
END $$;
|
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.197",
|
|
4
4
|
"description": "NextSpark - The complete SaaS framework for Next.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "NextSpark <hello@nextspark.dev>",
|
|
@@ -390,7 +390,7 @@
|
|
|
390
390
|
"scripts"
|
|
391
391
|
],
|
|
392
392
|
"peerDependencies": {
|
|
393
|
-
"next": "~16.3.
|
|
393
|
+
"next": "~16.3.8",
|
|
394
394
|
"react": "^19.2.0",
|
|
395
395
|
"react-dom": "^19.2.0"
|
|
396
396
|
},
|
|
@@ -496,7 +496,7 @@
|
|
|
496
496
|
"use-intl": "^4.9.1",
|
|
497
497
|
"uuid": "^13.0.0",
|
|
498
498
|
"zod": "^4.3.6",
|
|
499
|
-
"@nextsparkjs/ui": "0.1.0-beta.
|
|
499
|
+
"@nextsparkjs/ui": "0.1.0-beta.197"
|
|
500
500
|
},
|
|
501
501
|
"engines": {
|
|
502
502
|
"node": ">=22.14.0"
|
|
@@ -29,7 +29,7 @@ const RAW_CONTROL = /[\u0000-\u0009\u000b-\u001f\u007f-\u009f\u061c\u200e\u200f\
|
|
|
29
29
|
async function createProject(parent = tmpdir(), name = 'nextspark-build-output-test-') {
|
|
30
30
|
const root = await mkdtemp(join(parent, name))
|
|
31
31
|
await writeFile(join(root, 'nextspark.config.ts'), 'export default {}\n')
|
|
32
|
-
await writeFile(join(root, 'package.json'), '{"dependencies":{"next":"16.3.
|
|
32
|
+
await writeFile(join(root, 'package.json'), '{"dependencies":{"next":"16.3.8"}}')
|
|
33
33
|
await mkdir(join(root, 'src', 'app'), { recursive: true })
|
|
34
34
|
return root
|
|
35
35
|
}
|
|
@@ -17,7 +17,7 @@ async function project(files = {}) {
|
|
|
17
17
|
|
|
18
18
|
test('the nearest nextspark.config.ts owns every project source path', async () => {
|
|
19
19
|
const root = await project({
|
|
20
|
-
'package.json': JSON.stringify({ dependencies: { next: '16.3.
|
|
20
|
+
'package.json': JSON.stringify({ dependencies: { next: '16.3.8' } }),
|
|
21
21
|
'nextspark.config.ts': 'export default {}\n',
|
|
22
22
|
})
|
|
23
23
|
const nested = join(root, 'entities', 'tasks')
|
|
@@ -40,7 +40,7 @@ test('the nearest nextspark.config.ts owns every project source path', async ()
|
|
|
40
40
|
|
|
41
41
|
test('a next.config file cannot select a project without nextspark.config.ts', async () => {
|
|
42
42
|
const root = await project({
|
|
43
|
-
'package.json': JSON.stringify({ dependencies: { next: '16.3.
|
|
43
|
+
'package.json': JSON.stringify({ dependencies: { next: '16.3.8' } }),
|
|
44
44
|
'next.config.mjs': 'export default {}\n',
|
|
45
45
|
})
|
|
46
46
|
try {
|
|
@@ -66,7 +66,7 @@ test('an enabled packaged plugin resolves through the project dependency tree',
|
|
|
66
66
|
const root = await project({
|
|
67
67
|
'package.json': JSON.stringify({
|
|
68
68
|
dependencies: {
|
|
69
|
-
next: '16.3.
|
|
69
|
+
next: '16.3.8',
|
|
70
70
|
'@example/plugin-search': '1.0.0',
|
|
71
71
|
},
|
|
72
72
|
}),
|
|
@@ -98,7 +98,7 @@ test('an enabled packaged plugin that is not installed fails with an actionable
|
|
|
98
98
|
const root = await project({
|
|
99
99
|
'package.json': JSON.stringify({
|
|
100
100
|
dependencies: {
|
|
101
|
-
next: '16.3.
|
|
101
|
+
next: '16.3.8',
|
|
102
102
|
'@example/plugin-search': '1.0.0',
|
|
103
103
|
},
|
|
104
104
|
}),
|
|
@@ -119,7 +119,7 @@ test('a packaged plugin cannot use a path segment as its logical name', async ()
|
|
|
119
119
|
const root = await project({
|
|
120
120
|
'package.json': JSON.stringify({
|
|
121
121
|
dependencies: {
|
|
122
|
-
next: '16.3.
|
|
122
|
+
next: '16.3.8',
|
|
123
123
|
'@example/plugin-search': '1.0.0',
|
|
124
124
|
},
|
|
125
125
|
}),
|
|
@@ -146,7 +146,7 @@ test('a local plugin shadows an installed packaged plugin with the same NextSpar
|
|
|
146
146
|
const root = await project({
|
|
147
147
|
'package.json': JSON.stringify({
|
|
148
148
|
dependencies: {
|
|
149
|
-
next: '16.3.
|
|
149
|
+
next: '16.3.8',
|
|
150
150
|
'@example/plugin-search': '1.0.0',
|
|
151
151
|
},
|
|
152
152
|
}),
|
|
@@ -269,7 +269,7 @@ async function snapshot(root) {
|
|
|
269
269
|
async function buildableProject() {
|
|
270
270
|
const project = await directory()
|
|
271
271
|
await writeIn(project.root, 'nextspark.config.ts', 'export default {}\n')
|
|
272
|
-
await writeIn(project.root, 'package.json', '{"dependencies":{"next":"16.3.
|
|
272
|
+
await writeIn(project.root, 'package.json', '{"dependencies":{"next":"16.3.8"}}')
|
|
273
273
|
await writeIn(project.root, 'templates/pricing/page.tsx', 'export default function Pricing() { return null }\n')
|
|
274
274
|
return project
|
|
275
275
|
}
|
|
@@ -22,7 +22,7 @@ async function planFor(files) {
|
|
|
22
22
|
const root = temp()
|
|
23
23
|
try {
|
|
24
24
|
write(root, 'nextspark.config.ts', 'export default { plugins: [] }\n')
|
|
25
|
-
write(root, 'package.json', JSON.stringify({ name: 'wire', dependencies: { next: '16.3.
|
|
25
|
+
write(root, 'package.json', JSON.stringify({ name: 'wire', dependencies: { next: '16.3.8' } }))
|
|
26
26
|
write(root, 'config/theme.config.ts', "export const wireThemeConfig = { name: 'wire' }\n")
|
|
27
27
|
for (const [path, content] of Object.entries(files)) write(root, path, content)
|
|
28
28
|
const entities = await discoverAllEntities(getConfig(root), { includeCore: false })
|
|
@@ -73,7 +73,7 @@ const NOTES_CONFIG = `export const noteEntityConfig = {
|
|
|
73
73
|
function project(extra = {}) {
|
|
74
74
|
const root = temp()
|
|
75
75
|
write(root, 'nextspark.config.ts', 'export default { plugins: [] }\n')
|
|
76
|
-
write(root, 'package.json', JSON.stringify({ name: 'contracts-project', dependencies: { next: '16.3.
|
|
76
|
+
write(root, 'package.json', JSON.stringify({ name: 'contracts-project', dependencies: { next: '16.3.8' } }))
|
|
77
77
|
write(root, 'config/theme.config.ts', "export const contractsThemeConfig = { name: 'contracts' }\n")
|
|
78
78
|
write(root, 'entities/tasks/tasks.config.ts', TASKS_CONFIG)
|
|
79
79
|
write(root, 'entities/tasks/tasks.fields.ts', FIELDS)
|
|
@@ -28,7 +28,7 @@ const { target, content } = await emitFacade({
|
|
|
28
28
|
Every problem is a `FacadeEmitError` carrying all diagnostics (`code`, `file`, `line`, `exportName`,
|
|
29
29
|
`message`); nothing is dropped silently.
|
|
30
30
|
|
|
31
|
-
## Export table (Next.js 16.3.
|
|
31
|
+
## Export table (Next.js 16.3.8)
|
|
32
32
|
|
|
33
33
|
Source of truth: `next-route-exports.json`. The tests fail when the installed Next.js version differs,
|
|
34
34
|
when Next's `AppSegmentConfigSchemaKeys` or `HTTP_METHODS` change, and when this rendering is stale.
|
|
@@ -71,7 +71,7 @@ export { listSlugs as generateStaticParams }
|
|
|
71
71
|
Metadata image kinds accept Next's one-digit variants (`icon1`, `apple-icon9`, `opengraph-image2`,
|
|
72
72
|
`twitter-image3`), mapped to the base kind.
|
|
73
73
|
|
|
74
|
-
The emitter resolves `next/package.json` once (project first, then core) and accepts only `~16.3.
|
|
74
|
+
The emitter resolves `next/package.json` once (project first, then core) and accepts only `~16.3.8`: the table's
|
|
75
75
|
`next` is the verified baseline, and any stable 16.3.x patch at or above it is accepted, while other minors/majors,
|
|
76
76
|
lower patches and prereleases are refused (`NS_HOST_UNSUPPORTED_NEXT_VERSION`). It loads Next's segment config
|
|
77
77
|
schema from that same package root.
|