@nextsparkjs/core 0.1.0-beta.195 → 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/billing/InvoicesPagination.js +2 -2
- 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/dashboard/navigation/DynamicNavigation.js +2 -2
- package/dist/components/settings/layouts/SettingsSidebar.d.ts.map +1 -1
- package/dist/components/settings/layouts/SettingsSidebar.js +0 -1
- package/dist/components/teams/CreateTeamButton.js +2 -2
- package/dist/components/teams/CreateTeamDialog.js +1 -1
- package/dist/components/teams/InviteMemberButton.js +3 -3
- package/dist/components/teams/InviteMemberDialog.js +1 -1
- package/dist/components/teams/TeamPendingInvitations.js +2 -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/api-error.d.ts +2 -0
- package/dist/lib/api/api-error.d.ts.map +1 -1
- package/dist/lib/api/api-error.js +3 -1
- package/dist/lib/api/auth/dual-auth.d.ts +2 -1
- package/dist/lib/api/auth/dual-auth.d.ts.map +1 -1
- package/dist/lib/api/auth/dual-auth.js +25 -6
- package/dist/lib/api/auth/scope-policy.d.ts +6 -3
- package/dist/lib/api/auth/scope-policy.d.ts.map +1 -1
- package/dist/lib/api/auth.d.ts.map +1 -1
- package/dist/lib/api/auth.js +7 -0
- package/dist/lib/api/cors-response.d.ts +3 -2
- package/dist/lib/api/cors-response.d.ts.map +1 -1
- package/dist/lib/api/cors-response.js +8 -6
- package/dist/lib/api/entity/generic-handler.d.ts.map +1 -1
- package/dist/lib/api/entity/generic-handler.js +66 -15
- 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 +9 -24
- package/dist/lib/api/permission-middleware.d.ts.map +1 -1
- package/dist/lib/api/permission-middleware.js +0 -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/api/rate-limit.d.ts +8 -0
- package/dist/lib/api/rate-limit.d.ts.map +1 -1
- package/dist/lib/api/rate-limit.js +34 -3
- package/dist/lib/api/request-origin.d.ts.map +1 -1
- package/dist/lib/api/request-origin.js +1 -1
- 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 +9 -6
- package/dist/lib/auth/sample-accounts.d.ts +18 -0
- package/dist/lib/auth/sample-accounts.d.ts.map +1 -0
- package/dist/lib/auth/sample-accounts.js +25 -0
- 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 +20 -17
- package/dist/lib/cache/public-entity-cache.d.ts +21 -0
- package/dist/lib/cache/public-entity-cache.d.ts.map +1 -0
- package/dist/lib/cache/public-entity-cache.js +25 -0
- 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/entities/entity-hooks.d.ts.map +1 -1
- package/dist/lib/entities/entity-hooks.js +4 -0
- package/dist/lib/mcp/errors.d.ts +2 -0
- package/dist/lib/mcp/errors.d.ts.map +1 -1
- package/dist/lib/mcp/errors.js +3 -1
- package/dist/lib/mcp/index.d.ts +1 -1
- package/dist/lib/mcp/index.d.ts.map +1 -1
- package/dist/lib/mcp/index.js +2 -1
- 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 +41 -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/teams/actions.d.ts.map +1 -1
- package/dist/lib/teams/actions.js +0 -1
- 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/messages/de/billing.json +12 -1
- package/dist/messages/de/common.json +2 -0
- package/dist/messages/de/docs.json +1 -0
- package/dist/messages/de/features.json +16 -4
- package/dist/messages/de/home.json +5 -0
- package/dist/messages/de/index.d.ts +68 -25
- package/dist/messages/de/index.d.ts.map +1 -1
- package/dist/messages/de/index.js +2 -0
- package/dist/messages/de/index.ts +2 -0
- package/dist/messages/de/settings.json +1 -0
- package/dist/messages/de/teams.json +3 -18
- package/dist/messages/en/billing.json +1 -1
- package/dist/messages/en/common.json +2 -0
- package/dist/messages/en/docs.json +1 -0
- package/dist/messages/en/features.json +16 -4
- package/dist/messages/en/home.json +5 -0
- package/dist/messages/en/index.d.ts +58 -25
- package/dist/messages/en/index.d.ts.map +1 -1
- package/dist/messages/en/index.js +2 -0
- package/dist/messages/en/index.ts +2 -0
- package/dist/messages/en/settings.json +1 -0
- package/dist/messages/en/teams.json +3 -18
- package/dist/messages/es/billing.json +1 -1
- package/dist/messages/es/common.json +2 -0
- package/dist/messages/es/docs.json +1 -0
- package/dist/messages/es/features.json +16 -4
- package/dist/messages/es/home.json +5 -0
- package/dist/messages/es/index.d.ts +41 -8
- package/dist/messages/es/index.d.ts.map +1 -1
- package/dist/messages/es/index.js +2 -0
- package/dist/messages/es/index.ts +2 -0
- package/dist/messages/es/settings.json +1 -0
- package/dist/messages/es/teams.json +3 -0
- package/dist/messages/fr/billing.json +12 -1
- package/dist/messages/fr/common.json +2 -0
- package/dist/messages/fr/docs.json +1 -0
- package/dist/messages/fr/features.json +16 -4
- package/dist/messages/fr/home.json +5 -0
- package/dist/messages/fr/index.d.ts +68 -25
- package/dist/messages/fr/index.d.ts.map +1 -1
- package/dist/messages/fr/index.js +2 -0
- package/dist/messages/fr/index.ts +2 -0
- package/dist/messages/fr/settings.json +1 -0
- package/dist/messages/fr/teams.json +3 -18
- package/dist/messages/it/billing.json +12 -1
- package/dist/messages/it/common.json +2 -0
- package/dist/messages/it/docs.json +1 -0
- package/dist/messages/it/features.json +16 -4
- package/dist/messages/it/home.json +5 -0
- package/dist/messages/it/index.d.ts +68 -25
- package/dist/messages/it/index.d.ts.map +1 -1
- package/dist/messages/it/index.js +2 -0
- package/dist/messages/it/index.ts +2 -0
- package/dist/messages/it/settings.json +1 -0
- package/dist/messages/it/teams.json +3 -18
- package/dist/messages/pt/billing.json +12 -1
- package/dist/messages/pt/common.json +2 -0
- package/dist/messages/pt/docs.json +1 -0
- package/dist/messages/pt/features.json +16 -4
- package/dist/messages/pt/home.json +5 -0
- package/dist/messages/pt/index.d.ts +68 -25
- package/dist/messages/pt/index.d.ts.map +1 -1
- package/dist/messages/pt/index.js +2 -0
- package/dist/messages/pt/index.ts +2 -0
- package/dist/messages/pt/settings.json +1 -0
- package/dist/messages/pt/teams.json +3 -18
- package/dist/migrations/029_sample_accounts_outside_development.sql +55 -0
- package/dist/migrations/030_taxonomies_team_writes.sql +29 -0
- package/dist/migrations/031_users_role_suspended.sql +41 -0
- package/dist/migrations/090_sample_data.sql +10 -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/_internal/entity-permission-layout.d.ts.map +1 -1
- package/dist/routes/_internal/entity-permission-layout.js +0 -7
- 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/mcp/route.d.ts.map +1 -1
- package/dist/routes/api/mcp/route.js +7 -1
- 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/media/upload/route.d.ts +2 -1
- package/dist/routes/api/v1/media/upload/route.d.ts.map +1 -1
- package/dist/routes/api/v1/media/upload/route.js +10 -2
- 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/api/v1/teams/[teamId]/members/route.d.ts.map +1 -1
- package/dist/routes/api/v1/teams/[teamId]/members/route.js +12 -10
- package/dist/routes/api/v1/teams/switch/route.d.ts.map +1 -1
- package/dist/routes/api/v1/teams/switch/route.js +4 -1
- package/dist/routes/dashboard/settings/password/page.d.ts.map +1 -1
- package/dist/routes/dashboard/settings/password/page.js +10 -12
- package/dist/routes/devtools/config/page.js +1 -1
- package/dist/routes/devtools/page.js +1 -1
- package/dist/routes/devtools/scheduled-actions/page.js +1 -1
- package/dist/routes/devtools/tests/[[...path]]/page.js +1 -1
- package/dist/routes/superadmin/users/page.js +2 -2
- package/dist/templates/ci-workflows/github/cypress-regression.yml +1 -1
- package/dist/templates/ci-workflows/github/cypress-smoke.yml +2 -1
- 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/README.md +2 -0
- package/dist/templates/projects/starter/config/app.config.ts +8 -0
- package/dist/templates/projects/starter/docs/public/01-overview/01-introduction.md +2 -0
- package/dist/templates/projects/starter/templates/dashboard/analytics/layout.tsx +14 -0
- package/dist/templates/proxy.ts +18 -8
- package/dist/templates/public/uploads/README.md +5 -94
- package/migrations/029_sample_accounts_outside_development.sql +55 -0
- package/migrations/030_taxonomies_team_writes.sql +29 -0
- package/migrations/031_users_role_suspended.sql +41 -0
- package/migrations/090_sample_data.sql +10 -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 +99 -1
- 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 +92 -2
- package/scripts/db/run-migrations.mjs +30 -10
- package/scripts/db/sample-data.mjs +44 -0
- package/scripts/db/verify-theme-migrations.mjs +7 -1
- package/templates/ci-workflows/github/cypress-regression.yml +1 -1
- package/templates/ci-workflows/github/cypress-smoke.yml +2 -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/README.md +2 -0
- package/templates/projects/starter/config/app.config.ts +8 -0
- package/templates/projects/starter/docs/public/01-overview/01-introduction.md +2 -0
- package/templates/projects/starter/templates/dashboard/analytics/layout.tsx +14 -0
- package/templates/proxy.ts +18 -8
- package/templates/public/uploads/README.md +5 -94
- package/tests/jest/__mocks__/@nextsparkjs/registries/api-presets-registry.ts +2 -0
- package/tests/jest/__mocks__/@nextsparkjs/registries/mcp-registry.ts +2 -0
- package/tests/jest/__mocks__/@nextsparkjs/registries/permissions-registry.ts +5 -4
- package/dist/templates/projects/starter/public/brand/.gitkeep +0 -0
- package/dist/templates/public/uploads/.gitignore +0 -6
- package/dist/templates/public/uploads/temp/.gitkeep +0 -1
- package/templates/projects/starter/public/brand/.gitkeep +0 -0
- package/templates/public/uploads/.gitignore +0 -6
- package/templates/public/uploads/temp/.gitkeep +0 -1
|
@@ -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);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../src/routes/api/v1/teams/[teamId]/members/route.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAwBvD,wBAAsB,OAAO,CAAC,OAAO,EAAE,WAAW,kCAEjD;AAGD,eAAO,MAAM,GAAG;YACiC,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;2BA2HlE,CAAA;AAGV,eAAO,MAAM,IAAI;YACgC,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../src/routes/api/v1/teams/[teamId]/members/route.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAwBvD,wBAAsB,OAAO,CAAC,OAAO,EAAE,WAAW,kCAEjD;AAGD,eAAO,MAAM,GAAG;YACiC,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;2BA2HlE,CAAA;AAGV,eAAO,MAAM,IAAI;YACgC,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;2BAkMjE,CAAA"}
|
|
@@ -237,18 +237,20 @@ export const POST = withRateLimitTier(withApiLogging(
|
|
|
237
237
|
subject: emailContent.subject,
|
|
238
238
|
html: emailContent.html
|
|
239
239
|
});
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
240
|
+
if (process.env.NODE_ENV === "development") {
|
|
241
|
+
console.log("\n" + "\u{1F3AB}".repeat(30));
|
|
242
|
+
console.log("\u{1F4E8} TEAM INVITATION CREATED");
|
|
243
|
+
console.log("\u{1F3AB}".repeat(30));
|
|
244
|
+
console.log(`\u{1F4E7} To: ${validatedData.email}`);
|
|
245
|
+
console.log(`\u{1F464} Invited by: ${inviterName}`);
|
|
246
|
+
console.log(`\u{1F3E2} Team: ${team.name}`);
|
|
247
|
+
console.log(`\u{1F451} Role: ${validatedData.role}`);
|
|
248
|
+
console.log(`\u{1F517} Accept URL: ${acceptUrl}`);
|
|
249
|
+
console.log("\u{1F3AB}".repeat(30) + "\n");
|
|
250
|
+
}
|
|
249
251
|
} catch (emailError) {
|
|
250
252
|
console.error("Failed to send invitation email:", emailError);
|
|
251
|
-
console.log(`\u26A0\uFE0F Email failed but invitation created. Accept URL: ${acceptUrl}`);
|
|
253
|
+
if (process.env.NODE_ENV === "development") console.log(`\u26A0\uFE0F Email failed but invitation created. Accept URL: ${acceptUrl}`);
|
|
252
254
|
}
|
|
253
255
|
const response = createApiResponse(invitation, { created: true }, 201);
|
|
254
256
|
return addCorsHeaders(response, req);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../src/routes/api/v1/teams/switch/route.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAuBvD,wBAAsB,OAAO,CAAC,OAAO,EAAE,WAAW,kCAEjD;AAGD,eAAO,MAAM,IAAI,
|
|
1
|
+
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../src/routes/api/v1/teams/switch/route.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAuBvD,wBAAsB,OAAO,CAAC,OAAO,EAAE,WAAW,kCAEjD;AAGD,eAAO,MAAM,IAAI,iDAkFL,CAAA"}
|
|
@@ -38,7 +38,10 @@ export const POST = withRateLimitTier(withApiLogging(async (req) => {
|
|
|
38
38
|
);
|
|
39
39
|
return addCorsHeaders(response, req);
|
|
40
40
|
}
|
|
41
|
-
const body = await req.json();
|
|
41
|
+
const body = await req.json().catch(() => void 0);
|
|
42
|
+
if (body === void 0) {
|
|
43
|
+
return addCorsHeaders(createApiError("Request body must be valid JSON", 400, null, "INVALID_JSON"), req);
|
|
44
|
+
}
|
|
42
45
|
const validatedData = switchTeamSchema.parse(body);
|
|
43
46
|
try {
|
|
44
47
|
await TeamService.switchActive(authResult.user.id, validatedData.teamId);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../../../../src/routes/dashboard/settings/password/page.tsx"],"names":[],"mappings":"AAwBA,iBAAS,kBAAkB,
|
|
1
|
+
{"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../../../../src/routes/dashboard/settings/password/page.tsx"],"names":[],"mappings":"AAwBA,iBAAS,kBAAkB,4CAsR1B;AAID,eAAe,kBAAkB,CAAA"}
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
CheckCircle,
|
|
10
10
|
Loader2
|
|
11
11
|
} from "lucide-react";
|
|
12
|
-
import { useState, useCallback } from "react";
|
|
12
|
+
import { useState, useCallback, useEffect } from "react";
|
|
13
13
|
import { useForm } from "react-hook-form";
|
|
14
14
|
import { useRouter } from "next/navigation";
|
|
15
15
|
import { Button } from "@nextsparkjs/core/components/ui/button";
|
|
@@ -25,6 +25,7 @@ function UpdatePasswordPage() {
|
|
|
25
25
|
const { hasPassword, isLoading: profileLoading } = useUserProfile();
|
|
26
26
|
const router = useRouter();
|
|
27
27
|
const t = useTranslations("settings");
|
|
28
|
+
const tCommon = useTranslations("common");
|
|
28
29
|
const [loading, setLoading] = useState(false);
|
|
29
30
|
const [error, setError] = useState(null);
|
|
30
31
|
const [success, setSuccess] = useState(false);
|
|
@@ -58,7 +59,7 @@ function UpdatePasswordPage() {
|
|
|
58
59
|
if (!result.success) {
|
|
59
60
|
const errorMsg = result.error || t("password.messages.updateError");
|
|
60
61
|
setError(errorMsg);
|
|
61
|
-
setStatusMessage(`${
|
|
62
|
+
setStatusMessage(`${tCommon("status.error")}: ${errorMsg}`);
|
|
62
63
|
} else {
|
|
63
64
|
setSuccess(true);
|
|
64
65
|
setStatusMessage(t("password.messages.updateSuccess"));
|
|
@@ -66,25 +67,22 @@ function UpdatePasswordPage() {
|
|
|
66
67
|
} catch {
|
|
67
68
|
const errorMsg = t("password.messages.unexpectedError");
|
|
68
69
|
setError(errorMsg);
|
|
69
|
-
setStatusMessage(`${
|
|
70
|
+
setStatusMessage(`${tCommon("status.error")}: ${errorMsg}`);
|
|
70
71
|
} finally {
|
|
71
72
|
setLoading(false);
|
|
72
73
|
}
|
|
73
|
-
}, [changePassword, t]);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
if (!profileLoading && !hasPassword) {
|
|
79
|
-
router.push("/dashboard/settings/profile");
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
74
|
+
}, [changePassword, t, tCommon]);
|
|
75
|
+
const mustRedirectToProfile = !!user && !profileLoading && !hasPassword;
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (mustRedirectToProfile) router.push("/dashboard/settings/profile");
|
|
78
|
+
}, [mustRedirectToProfile, router]);
|
|
82
79
|
if (profileLoading) {
|
|
83
80
|
return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center py-12", children: [
|
|
84
81
|
/* @__PURE__ */ jsx(Loader2, { className: "h-8 w-8 animate-spin text-muted-foreground" }),
|
|
85
82
|
/* @__PURE__ */ jsx("span", { className: "sr-only", children: t("password.loading.verifying") })
|
|
86
83
|
] });
|
|
87
84
|
}
|
|
85
|
+
if (!user || mustRedirectToProfile) return null;
|
|
88
86
|
if (success) {
|
|
89
87
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
90
88
|
/* @__PURE__ */ jsx(
|
|
@@ -4,7 +4,7 @@ import { ConfigViewer } from "@nextsparkjs/core/components/devtools/ConfigViewer
|
|
|
4
4
|
import { Settings } from "lucide-react";
|
|
5
5
|
import { getTranslations } from "next-intl/server";
|
|
6
6
|
async function DevConfigPageContent() {
|
|
7
|
-
const t = await getTranslations("
|
|
7
|
+
const t = await getTranslations("devtools.config");
|
|
8
8
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-6", "data-cy": "devtools-config-page", children: [
|
|
9
9
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
10
10
|
/* @__PURE__ */ jsx("div", { className: "flex items-center justify-center w-12 h-12 bg-violet-100 dark:bg-violet-950 rounded-lg", children: /* @__PURE__ */ jsx(Settings, { className: "h-6 w-6 text-violet-600 dark:text-violet-400" }) }),
|
|
@@ -5,7 +5,7 @@ import Link from "next/link";
|
|
|
5
5
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@nextsparkjs/core/components/ui/card";
|
|
6
6
|
import { getTranslations } from "next-intl/server";
|
|
7
7
|
async function DevHomePageContent() {
|
|
8
|
-
const t = await getTranslations("
|
|
8
|
+
const t = await getTranslations("devtools");
|
|
9
9
|
const tCommon = await getTranslations("common");
|
|
10
10
|
const sections = [
|
|
11
11
|
{
|
|
@@ -26,7 +26,7 @@ async function fetchScheduledActions(filters, page, limit) {
|
|
|
26
26
|
return response.json();
|
|
27
27
|
}
|
|
28
28
|
export default function ScheduledActionsPage() {
|
|
29
|
-
const t = useTranslations("
|
|
29
|
+
const t = useTranslations("devtools.scheduledActions");
|
|
30
30
|
const [filters, setFilters] = useState({});
|
|
31
31
|
const [page, setPage] = useState(1);
|
|
32
32
|
const limit = 20;
|
|
@@ -5,7 +5,7 @@ import { FileText } from "lucide-react";
|
|
|
5
5
|
import { getTranslations } from "next-intl/server";
|
|
6
6
|
async function DevTestsPageContent({ params }) {
|
|
7
7
|
const { path } = await params;
|
|
8
|
-
const t = await getTranslations("
|
|
8
|
+
const t = await getTranslations("devtools.tests");
|
|
9
9
|
const initialPath = (path == null ? void 0 : path.join("/")) || null;
|
|
10
10
|
return /* @__PURE__ */ jsxs(
|
|
11
11
|
"div",
|
|
@@ -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" },
|
|
@@ -59,7 +59,8 @@ jobs:
|
|
|
59
59
|
- name: Setup database
|
|
60
60
|
env:
|
|
61
61
|
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test
|
|
62
|
-
|
|
62
|
+
# a throwaway test database: migrations plus the sample users the tests sign in as
|
|
63
|
+
run: pnpm db:seed
|
|
63
64
|
|
|
64
65
|
- name: Build application
|
|
65
66
|
run: pnpm build
|
|
@@ -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:
|
|
@@ -17,6 +17,8 @@ Welcome to the Starter theme for NextSpark. This minimal theme provides everythi
|
|
|
17
17
|
3. Run migrations: `pnpm db:migrate`
|
|
18
18
|
4. Start development: `pnpm dev`
|
|
19
19
|
|
|
20
|
+
Sample users (DevKeyring, Cypress) are for local development only: `pnpm db:seed` loads them; `pnpm db:migrate` alone does not, and neither applies them when `NODE_ENV` is `production`.
|
|
21
|
+
|
|
20
22
|
## Project Structure
|
|
21
23
|
|
|
22
24
|
```
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { NextIntlClientProvider } from 'next-intl'
|
|
2
|
+
import { getMessages } from 'next-intl/server'
|
|
3
|
+
import { selectMessages } from '@nextsparkjs/core/lib/i18n/client-messages'
|
|
4
|
+
|
|
5
|
+
// TasksChart and RecentActivity are client components that read the `analytics`
|
|
6
|
+
// namespace, which the dashboard group does not carry to the browser by default.
|
|
7
|
+
export default async function AnalyticsLayout({ children }: { children: React.ReactNode }) {
|
|
8
|
+
const messages = await getMessages()
|
|
9
|
+
return (
|
|
10
|
+
<NextIntlClientProvider messages={selectMessages(messages, 'dashboard', ['analytics'])}>
|
|
11
|
+
{children}
|
|
12
|
+
</NextIntlClientProvider>
|
|
13
|
+
)
|
|
14
|
+
}
|