@podosoft/podokit 0.3.0 → 0.4.0
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/templates/fullstack-nest-svelte/apps/api/package.json +20 -2
- package/dist/templates/fullstack-nest-svelte/apps/api/tsconfig.json +14 -5
- package/dist/templates/fullstack-nest-svelte/apps/web/package.json +3 -2
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/api.ts +5 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/server/api.ts +19 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/server/backend-proxy.ts +43 -4
- package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/+page.svelte +8 -3
- package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/api/[...path]/+server.ts +12 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/api/auth/[...all]/+server.ts +12 -0
- package/dist/templates/fullstack-nest-svelte/dot-gitignore +5 -0
- package/dist/templates/fullstack-nest-svelte/package.json +11 -3
- package/dist/templates/fullstack-nest-svelte/tests/README.md +15 -0
- package/dist/templates/fullstack-nest-svelte/tests/api/health.api.spec.ts +14 -0
- package/dist/templates/fullstack-nest-svelte/tests/package.json +14 -0
- package/dist/templates/fullstack-nest-svelte/tests/playwright.config.ts +25 -0
- package/dist/templates/fullstack-nest-svelte/tests/ui/smoke.ui.spec.ts +8 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/app.d.ts +23 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/hooks.server.ts +20 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/app-sidebar.svelte +59 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/language-switch.svelte +33 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/sidebar-user-menu.svelte +44 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/table-pagination.svelte +40 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/theme-toggle.svelte +27 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/collapsible/collapsible-content.svelte +7 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/collapsible/collapsible-trigger.svelte +7 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/collapsible/collapsible.svelte +11 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/collapsible/index.ts +13 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/index.ts +31 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-content.svelte +20 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-ellipsis.svelte +22 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-item.svelte +14 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-link.svelte +42 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-next-button.svelte +31 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-next.svelte +26 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-prev-button.svelte +31 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-previous.svelte +26 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination.svelte +28 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/index.ts +34 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-close.svelte +7 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-content.svelte +55 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-description.svelte +17 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-footer.svelte +20 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-header.svelte +20 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-overlay.svelte +17 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-portal.svelte +7 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-title.svelte +17 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-trigger.svelte +7 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet.svelte +7 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/constants.ts +6 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/context.svelte.ts +81 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/index.ts +75 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-content.svelte +24 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-footer.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-group-action.svelte +33 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-group-content.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-group-label.svelte +33 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-group.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-header.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-input.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-inset.svelte +20 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-action.svelte +37 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-badge.svelte +24 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-button.svelte +102 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-item.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-skeleton.svelte +36 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte +39 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-sub-item.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-sub.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-provider.svelte +53 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-rail.svelte +36 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-separator.svelte +19 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-trigger.svelte +36 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar.svelte +108 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/user-avatar.svelte +12 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/hooks/is-mobile.svelte.ts +9 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/index.ts +27 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/messages.ts +359 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/+layout.server.ts +14 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/+layout.svelte +50 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/+page.svelte +25 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/account/+page.server.ts +5 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/account/+page.svelte +374 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/audit/+page.server.ts +8 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/audit/+page.svelte +78 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/sessions/+page.server.ts +8 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/sessions/+page.svelte +122 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/users/+page.server.ts +9 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/users/+page.svelte +406 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/+layout.svelte +13 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/forgot-password/+page.svelte +54 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/login/+page.svelte +59 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/reset-password/+page.svelte +51 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/signup/+page.svelte +60 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/+layout.server.ts +22 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/+layout.svelte +20 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/+page.svelte +22 -0
- package/dist/templates/modules/admin-dashboard/files/tests/api/audit.api.spec.ts +48 -0
- package/dist/templates/modules/admin-dashboard/files/tests/api/auth.api.spec.ts +43 -0
- package/dist/templates/modules/admin-dashboard/files/tests/api/users-admin.api.spec.ts +134 -0
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/accounts.ts +6 -0
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/hydration.ts +9 -0
- package/dist/templates/modules/admin-dashboard/files/tests/playwright.config.ts +26 -0
- package/dist/templates/modules/admin-dashboard/files/tests/seed.setup.ts +29 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/account.ui.spec.ts +55 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/audit.ui.spec.ts +16 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/auth.ui.spec.ts +45 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/dashboard.ui.spec.ts +25 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/guards.ui.spec.ts +21 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/i18n.ui.spec.ts +13 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/sessions.ui.spec.ts +15 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/sidebar.ui.spec.ts +16 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/users-crud.ui.spec.ts +129 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/users.ui.spec.ts +19 -0
- package/dist/templates/modules/admin-dashboard/module.manifest.json +41 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/README.md +59 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-events.ts +34 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-hook.ts +70 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-log.entity.ts +29 -8
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.controller.ts +7 -2
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.decorator.ts +23 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.interceptor.ts +36 -21
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.module.ts +2 -1
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.service.ts +36 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/migrations/1720300000000-InitAuditLogs.ts +10 -4
- package/dist/templates/modules/audit-log/module.manifest.json +30 -7
- package/dist/templates/modules/auth/files/apps/api/src/account/account.controller.ts +17 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth.ts +26 -3
- package/dist/templates/todo/apps/api/package.json +20 -2
- package/dist/templates/todo/apps/api/tsconfig.json +14 -5
- package/dist/templates/todo/apps/web/package.json +3 -2
- package/dist/templates/todo/apps/web/src/lib/api.ts +5 -0
- package/dist/templates/todo/apps/web/src/lib/server/api.ts +19 -0
- package/dist/templates/todo/apps/web/src/lib/server/backend-proxy.ts +43 -4
- package/dist/templates/todo/apps/web/src/routes/+page.svelte +19 -18
- package/dist/templates/todo/apps/web/src/routes/api/[...path]/+server.ts +12 -0
- package/dist/templates/todo/apps/web/src/routes/api/auth/[...all]/+server.ts +12 -0
- package/dist/templates/todo/dot-gitignore +5 -0
- package/dist/templates/todo/package.json +11 -3
- package/dist/templates/todo/tests/README.md +15 -0
- package/dist/templates/todo/tests/api/health.api.spec.ts +14 -0
- package/dist/templates/todo/tests/package.json +14 -0
- package/dist/templates/todo/tests/playwright.config.ts +25 -0
- package/dist/templates/todo/tests/ui/smoke.ui.spec.ts +8 -0
- package/package.json +1 -1
- package/dist/templates/fullstack-nest-svelte/apps/api/test/health.e2e-spec.ts +0 -23
- package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/api/health/+server.ts +0 -12
- package/dist/templates/todo/apps/api/test/health.e2e-spec.ts +0 -23
- package/dist/templates/todo/apps/web/src/routes/api/health/+server.ts +0 -12
- package/dist/templates/todo/apps/web/src/routes/api/todos/+server.ts +0 -24
- package/dist/templates/todo/apps/web/src/routes/api/todos/[id]/+server.ts +0 -24
|
@@ -8,15 +8,21 @@ export class InitAuditLogs1720300000000 implements MigrationInterface {
|
|
|
8
8
|
await queryRunner.query(`
|
|
9
9
|
CREATE TABLE "audit_logs" (
|
|
10
10
|
"id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
11
|
+
"action" character varying(128) NOT NULL,
|
|
12
|
+
"actorId" character varying,
|
|
13
|
+
"actorName" character varying,
|
|
14
|
+
"actorEmail" character varying,
|
|
15
|
+
"targetType" character varying,
|
|
16
|
+
"targetId" character varying,
|
|
17
|
+
"targetLabel" character varying,
|
|
15
18
|
"ip" character varying,
|
|
19
|
+
"metadata" jsonb,
|
|
16
20
|
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
|
17
21
|
CONSTRAINT "PK_audit_logs_id" PRIMARY KEY ("id")
|
|
18
22
|
)
|
|
19
23
|
`);
|
|
24
|
+
await queryRunner.query(`CREATE INDEX "IDX_audit_logs_action" ON "audit_logs" ("action")`);
|
|
25
|
+
await queryRunner.query(`CREATE INDEX "IDX_audit_logs_createdAt" ON "audit_logs" ("createdAt")`);
|
|
20
26
|
}
|
|
21
27
|
|
|
22
28
|
async down(queryRunner: QueryRunner): Promise<void> {
|
|
@@ -1,16 +1,39 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "audit-log",
|
|
3
|
-
"description": "Audit logging:
|
|
4
|
-
"requires": [
|
|
3
|
+
"description": "Audit logging: records who did each mutating request (who/method/path/status) to an audit_logs table — a NestJS interceptor for your own API routes plus a better-auth hook for auth/admin actions (login, ban, role changes, ...). Requires auth.",
|
|
4
|
+
"requires": [
|
|
5
|
+
"auth"
|
|
6
|
+
],
|
|
5
7
|
"targetApp": "api",
|
|
6
|
-
"env": [
|
|
8
|
+
"env": [
|
|
9
|
+
"# audit-log: surfaces the admin Audit log page in the dashboard",
|
|
10
|
+
"AUDIT_LOG_ENABLED=true"
|
|
11
|
+
],
|
|
7
12
|
"inject": [
|
|
8
|
-
{
|
|
9
|
-
|
|
13
|
+
{
|
|
14
|
+
"file": "apps/api/src/app.module.ts",
|
|
15
|
+
"marker": "// podokit:imports",
|
|
16
|
+
"text": "import { AuditModule } from \"./audit/audit.module\";"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"file": "apps/api/src/app.module.ts",
|
|
20
|
+
"marker": "// podokit:module-imports",
|
|
21
|
+
"text": "AuditModule,"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"file": "apps/api/src/auth/auth.ts",
|
|
25
|
+
"marker": "// podokit:auth-imports",
|
|
26
|
+
"text": "import { auditAfterHook } from \"../audit/audit-hook\";"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"file": "apps/api/src/auth/auth.ts",
|
|
30
|
+
"marker": "// podokit:auth-options",
|
|
31
|
+
"text": "hooks: { after: auditAfterHook },"
|
|
32
|
+
}
|
|
10
33
|
],
|
|
11
34
|
"instructions": [
|
|
12
35
|
"Run migrations to create the audit_logs table: npm run migration:run -w <app>-api",
|
|
13
|
-
"
|
|
14
|
-
"List recent entries (
|
|
36
|
+
"Auth/admin actions (login, ban, role changes, session revocation, user CRUD) are recorded via a better-auth hook; your own mutating NestJS routes are recorded by the interceptor.",
|
|
37
|
+
"List recent entries (admin session required): curl -b cookies.txt localhost:3000/audit-logs"
|
|
15
38
|
]
|
|
16
39
|
}
|
|
@@ -2,6 +2,8 @@ import { Controller, Get } from "@nestjs/common";
|
|
|
2
2
|
import { Session, type UserSession } from "@thallesp/nestjs-better-auth";
|
|
3
3
|
import { ApiTags } from "@nestjs/swagger";
|
|
4
4
|
|
|
5
|
+
type Capabilities = { twoFactor: boolean; providers: string[]; deleteAccount: boolean; auditLog: boolean };
|
|
6
|
+
|
|
5
7
|
// Protected by the global AuthGuard. Use @Session() to read the current user.
|
|
6
8
|
@ApiTags("account")
|
|
7
9
|
@Controller("account")
|
|
@@ -10,4 +12,19 @@ export class AccountController {
|
|
|
10
12
|
me(@Session() session: UserSession) {
|
|
11
13
|
return session.user;
|
|
12
14
|
}
|
|
15
|
+
|
|
16
|
+
// Which optional auth features are enabled, so the UI can show/hide sections.
|
|
17
|
+
@Get("capabilities")
|
|
18
|
+
capabilities(): Capabilities {
|
|
19
|
+
const providers = [
|
|
20
|
+
process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET ? "google" : null,
|
|
21
|
+
process.env.GITHUB_CLIENT_ID && process.env.GITHUB_CLIENT_SECRET ? "github" : null,
|
|
22
|
+
].filter((p): p is string => p !== null);
|
|
23
|
+
return {
|
|
24
|
+
twoFactor: process.env.AUTH_TWO_FACTOR === "true",
|
|
25
|
+
providers,
|
|
26
|
+
deleteAccount: process.env.AUTH_ALLOW_DELETE === "true",
|
|
27
|
+
auditLog: process.env.AUDIT_LOG_ENABLED === "true",
|
|
28
|
+
};
|
|
29
|
+
}
|
|
13
30
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { betterAuth, type BetterAuthOptions } from "better-auth";
|
|
1
|
+
import { betterAuth, type BetterAuthOptions, type BetterAuthPlugin } from "better-auth";
|
|
2
2
|
import { twoFactor } from "better-auth/plugins";
|
|
3
3
|
import { Pool } from "pg";
|
|
4
|
+
// podokit:auth-imports
|
|
4
5
|
|
|
5
6
|
// OAuth providers are enabled only when their credentials are present.
|
|
6
7
|
function socialProviders(): NonNullable<BetterAuthOptions["socialProviders"]> {
|
|
@@ -20,6 +21,13 @@ function socialProviders(): NonNullable<BetterAuthOptions["socialProviders"]> {
|
|
|
20
21
|
return providers;
|
|
21
22
|
}
|
|
22
23
|
|
|
24
|
+
// Plugins are collected here so other PodoKit modules can add their own.
|
|
25
|
+
const plugins: BetterAuthPlugin[] = [];
|
|
26
|
+
if (process.env.AUTH_TWO_FACTOR === "true") {
|
|
27
|
+
plugins.push(twoFactor());
|
|
28
|
+
}
|
|
29
|
+
// podokit:auth-plugins
|
|
30
|
+
|
|
23
31
|
export const auth = betterAuth({
|
|
24
32
|
database: new Pool({
|
|
25
33
|
host: process.env.POSTGRES_HOST ?? "localhost",
|
|
@@ -28,9 +36,24 @@ export const auth = betterAuth({
|
|
|
28
36
|
password: process.env.POSTGRES_PASSWORD ?? "podokit",
|
|
29
37
|
database: process.env.POSTGRES_DB ?? "podokit",
|
|
30
38
|
}),
|
|
31
|
-
emailAndPassword: {
|
|
39
|
+
emailAndPassword: {
|
|
40
|
+
enabled: true,
|
|
41
|
+
// podokit:auth-email-password
|
|
42
|
+
},
|
|
32
43
|
socialProviders: socialProviders(),
|
|
33
|
-
plugins
|
|
44
|
+
plugins,
|
|
34
45
|
secret: process.env.BETTER_AUTH_SECRET ?? "change-me-in-production-min-32-characters",
|
|
35
46
|
baseURL: process.env.BETTER_AUTH_URL ?? "http://localhost:3000",
|
|
47
|
+
advanced: {
|
|
48
|
+
ipAddress: {
|
|
49
|
+
// The API sits behind the SvelteKit server proxy, which forwards the
|
|
50
|
+
// resolved client IP as x-forwarded-for; trust it so sessions record an IP.
|
|
51
|
+
ipAddressHeaders: ["x-forwarded-for"],
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
user: {
|
|
55
|
+
// Self-service account deletion — opt in with AUTH_ALLOW_DELETE=true.
|
|
56
|
+
deleteUser: { enabled: process.env.AUTH_ALLOW_DELETE === "true" },
|
|
57
|
+
},
|
|
58
|
+
// podokit:auth-options
|
|
36
59
|
});
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"build": "nest build",
|
|
8
8
|
"start": "node dist/main",
|
|
9
9
|
"lint": "tsc -p tsconfig.json --noEmit",
|
|
10
|
-
"test": "jest",
|
|
10
|
+
"test": "jest --passWithNoTests",
|
|
11
11
|
"typeorm": "typeorm-ts-node-commonjs -d src/database/data-source.ts",
|
|
12
12
|
"migration:run": "npm run typeorm -- migration:run",
|
|
13
13
|
"migration:revert": "npm run typeorm -- migration:revert",
|
|
@@ -39,6 +39,24 @@
|
|
|
39
39
|
"supertest": "^7.0.0",
|
|
40
40
|
"ts-jest": "^29.2.5",
|
|
41
41
|
"ts-node": "^10.9.2",
|
|
42
|
-
"typescript": "^
|
|
42
|
+
"typescript": "^6.0.3"
|
|
43
|
+
},
|
|
44
|
+
"jest": {
|
|
45
|
+
"moduleFileExtensions": [
|
|
46
|
+
"js",
|
|
47
|
+
"json",
|
|
48
|
+
"ts"
|
|
49
|
+
],
|
|
50
|
+
"rootDir": "src",
|
|
51
|
+
"testRegex": ".*\\.spec\\.ts$",
|
|
52
|
+
"transform": {
|
|
53
|
+
"^.+\\.ts$": [
|
|
54
|
+
"ts-jest",
|
|
55
|
+
{
|
|
56
|
+
"tsconfig": "tsconfig.json"
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
"testEnvironment": "node"
|
|
43
61
|
}
|
|
44
62
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
|
-
"module": "
|
|
3
|
+
"module": "nodenext",
|
|
4
4
|
"target": "ES2022",
|
|
5
|
-
"moduleResolution": "
|
|
5
|
+
"moduleResolution": "nodenext",
|
|
6
6
|
"outDir": "dist",
|
|
7
7
|
"rootDir": "src",
|
|
8
8
|
"declaration": false,
|
|
@@ -14,8 +14,17 @@
|
|
|
14
14
|
"noImplicitReturns": true,
|
|
15
15
|
"esModuleInterop": true,
|
|
16
16
|
"skipLibCheck": true,
|
|
17
|
-
"forceConsistentCasingInFileNames": true
|
|
17
|
+
"forceConsistentCasingInFileNames": true,
|
|
18
|
+
"types": [
|
|
19
|
+
"node"
|
|
20
|
+
]
|
|
18
21
|
},
|
|
19
|
-
"include": [
|
|
20
|
-
|
|
22
|
+
"include": [
|
|
23
|
+
"src/**/*.ts"
|
|
24
|
+
],
|
|
25
|
+
"exclude": [
|
|
26
|
+
"node_modules",
|
|
27
|
+
"dist",
|
|
28
|
+
"test"
|
|
29
|
+
]
|
|
21
30
|
}
|
|
@@ -8,11 +8,12 @@
|
|
|
8
8
|
"build": "vite build",
|
|
9
9
|
"preview": "vite preview",
|
|
10
10
|
"lint": "svelte-check --tsconfig ./tsconfig.json",
|
|
11
|
-
"test": "echo \"add
|
|
11
|
+
"test": "echo \"unit: add vitest; e2e: npm run test:e2e (see tests/)\" && exit 0"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@internationalized/date": "^3.12.2",
|
|
15
15
|
"@lucide/svelte": "^1.23.0",
|
|
16
|
+
"@podosoft/podokit-api-client": "^0.4.0",
|
|
16
17
|
"bits-ui": "^2.18.1",
|
|
17
18
|
"clsx": "^2.1.1",
|
|
18
19
|
"mode-watcher": "^1.1.0",
|
|
@@ -29,7 +30,7 @@
|
|
|
29
30
|
"svelte-check": "^4.0.0",
|
|
30
31
|
"tailwindcss": "^4.0.0",
|
|
31
32
|
"typesafe-i18n": "^5.26.2",
|
|
32
|
-
"typescript": "^
|
|
33
|
+
"typescript": "^6.0.3",
|
|
33
34
|
"vite": "^5.4.0",
|
|
34
35
|
"tw-animate-css": "^1.4.0"
|
|
35
36
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { RequestEvent } from "@sveltejs/kit";
|
|
2
|
+
import { createApiClient, type ApiClient } from "@podosoft/podokit-api-client";
|
|
3
|
+
import { backendBaseUrl } from "./backend-proxy";
|
|
4
|
+
|
|
5
|
+
// Server-side client for load functions and hooks: points at the internal
|
|
6
|
+
// backend URL and forwards the incoming request's cookies.
|
|
7
|
+
export function serverApiClient(event: RequestEvent): ApiClient {
|
|
8
|
+
const cookie = event.request.headers.get("cookie") ?? "";
|
|
9
|
+
const fetchWithCookies: typeof fetch = (input, init) => {
|
|
10
|
+
const headers = new Headers(init?.headers);
|
|
11
|
+
if (cookie) headers.set("cookie", cookie);
|
|
12
|
+
return event.fetch(input, { ...init, headers });
|
|
13
|
+
};
|
|
14
|
+
return createApiClient({
|
|
15
|
+
baseUrl: backendBaseUrl(),
|
|
16
|
+
apiBasePath: "",
|
|
17
|
+
fetch: fetchWithCookies,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -1,16 +1,55 @@
|
|
|
1
1
|
// Server-side proxy boundary. The browser never talks to the API directly:
|
|
2
|
-
//
|
|
3
|
-
|
|
2
|
+
// requests go through SvelteKit server routes, which forward an allowlist of
|
|
3
|
+
// headers to BACKEND_INTERNAL_URL and relay the response (including Set-Cookie).
|
|
4
|
+
const FORWARDED_HEADERS = ["authorization", "cookie", "content-type", "accept", "origin", "referer"];
|
|
5
|
+
const RELAYED_RESPONSE_HEADERS = ["content-type", "location", "cache-control"];
|
|
4
6
|
|
|
5
7
|
export function backendBaseUrl(): string {
|
|
6
8
|
return process.env.BACKEND_INTERNAL_URL ?? "http://localhost:3000";
|
|
7
9
|
}
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
// Normalize the resolved client IP for the forwarded header. better-auth stores
|
|
12
|
+
// IPv4 as-is but anonymizes IPv6 (masks the host bits), so an IPv6 loopback would
|
|
13
|
+
// be recorded as "::" — map loopback to 127.0.0.1 for a clean local value, and
|
|
14
|
+
// unwrap IPv4-mapped addresses.
|
|
15
|
+
function normalizeClientIp(address: string | undefined): string | undefined {
|
|
16
|
+
if (!address) return undefined;
|
|
17
|
+
if (address === "::1" || address === "::") return "127.0.0.1";
|
|
18
|
+
if (address.startsWith("::ffff:")) return address.slice("::ffff:".length);
|
|
19
|
+
return address;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function proxyRequest(
|
|
23
|
+
request: Request,
|
|
24
|
+
targetUrl: string,
|
|
25
|
+
clientAddress?: string,
|
|
26
|
+
): Promise<Response> {
|
|
10
27
|
const headers = new Headers();
|
|
11
28
|
for (const name of FORWARDED_HEADERS) {
|
|
12
29
|
const value = request.headers.get(name);
|
|
13
30
|
if (value) headers.set(name, value);
|
|
14
31
|
}
|
|
15
|
-
|
|
32
|
+
// Forward the resolved client IP so the API (better-auth) can record it.
|
|
33
|
+
const forwardedIp = normalizeClientIp(clientAddress);
|
|
34
|
+
if (forwardedIp) headers.set("x-forwarded-for", forwardedIp);
|
|
35
|
+
|
|
36
|
+
const hasBody = request.method !== "GET" && request.method !== "HEAD";
|
|
37
|
+
const upstream = await fetch(targetUrl, {
|
|
38
|
+
method: request.method,
|
|
39
|
+
headers,
|
|
40
|
+
body: hasBody ? await request.text() : undefined,
|
|
41
|
+
redirect: "manual",
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const responseHeaders = new Headers();
|
|
45
|
+
for (const name of RELAYED_RESPONSE_HEADERS) {
|
|
46
|
+
const value = upstream.headers.get(name);
|
|
47
|
+
if (value) responseHeaders.set(name, value);
|
|
48
|
+
}
|
|
49
|
+
// Relay Set-Cookie so auth session cookies reach the browser.
|
|
50
|
+
for (const cookie of upstream.headers.getSetCookie()) {
|
|
51
|
+
responseHeaders.append("set-cookie", cookie);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return new Response(upstream.body, { status: upstream.status, headers: responseHeaders });
|
|
16
55
|
}
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
import { Input } from "$lib/components/ui/input";
|
|
4
4
|
import { Checkbox } from "$lib/components/ui/checkbox";
|
|
5
5
|
import * as Card from "$lib/components/ui/card";
|
|
6
|
+
import { api } from "$lib/api";
|
|
7
|
+
import { ApiError } from "@podosoft/podokit-api-client";
|
|
6
8
|
|
|
7
9
|
type Todo = { id: string; title: string; completed: boolean };
|
|
8
10
|
|
|
@@ -10,40 +12,39 @@
|
|
|
10
12
|
let title = $state("");
|
|
11
13
|
let error = $state<string | null>(null);
|
|
12
14
|
|
|
15
|
+
function fail(err: unknown, action: string): void {
|
|
16
|
+
error = err instanceof ApiError ? `${action}: ${err.message}` : `${action} failed`;
|
|
17
|
+
}
|
|
18
|
+
|
|
13
19
|
async function load(): Promise<void> {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
try {
|
|
21
|
+
todos = await api.get<Todo[]>("/todos");
|
|
22
|
+
error = null;
|
|
23
|
+
} catch (err) {
|
|
24
|
+
fail(err, "Load");
|
|
25
|
+
}
|
|
17
26
|
}
|
|
18
27
|
|
|
19
28
|
async function add(event: SubmitEvent): Promise<void> {
|
|
20
29
|
event.preventDefault();
|
|
21
30
|
const value = title.trim();
|
|
22
31
|
if (!value) return;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
headers: { "content-type": "application/json" },
|
|
26
|
-
body: JSON.stringify({ title: value }),
|
|
27
|
-
});
|
|
28
|
-
if (res.ok) {
|
|
32
|
+
try {
|
|
33
|
+
await api.post("/todos", { title: value });
|
|
29
34
|
title = "";
|
|
30
35
|
await load();
|
|
31
|
-
}
|
|
32
|
-
|
|
36
|
+
} catch (err) {
|
|
37
|
+
fail(err, "Add");
|
|
33
38
|
}
|
|
34
39
|
}
|
|
35
40
|
|
|
36
41
|
async function toggle(todo: Todo): Promise<void> {
|
|
37
|
-
await
|
|
38
|
-
method: "PATCH",
|
|
39
|
-
headers: { "content-type": "application/json" },
|
|
40
|
-
body: JSON.stringify({ completed: !todo.completed }),
|
|
41
|
-
});
|
|
42
|
+
await api.patch(`/todos/${todo.id}`, { completed: !todo.completed });
|
|
42
43
|
await load();
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
async function remove(todo: Todo): Promise<void> {
|
|
46
|
-
await
|
|
47
|
+
await api.del(`/todos/${todo.id}`);
|
|
47
48
|
await load();
|
|
48
49
|
}
|
|
49
50
|
|
|
@@ -61,7 +62,7 @@
|
|
|
61
62
|
<Card.Root>
|
|
62
63
|
<Card.Header>
|
|
63
64
|
<Card.Title>Todos</Card.Title>
|
|
64
|
-
<Card.Description>A NestJS + SvelteKit CRUD example.</Card.Description>
|
|
65
|
+
<Card.Description>A NestJS + SvelteKit CRUD example via the typed ApiClient.</Card.Description>
|
|
65
66
|
</Card.Header>
|
|
66
67
|
<Card.Content class="flex flex-col gap-4">
|
|
67
68
|
<form class="flex gap-2" onsubmit={add}>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { RequestHandler } from "@sveltejs/kit";
|
|
2
|
+
import { backendBaseUrl, proxyRequest } from "$lib/server/backend-proxy";
|
|
3
|
+
|
|
4
|
+
// Proxy the app's REST API, stripping the /api prefix (backend routes are at root).
|
|
5
|
+
const handler: RequestHandler = ({ request, params, url, getClientAddress }) =>
|
|
6
|
+
proxyRequest(request, `${backendBaseUrl()}/${params.path}${url.search}`, getClientAddress());
|
|
7
|
+
|
|
8
|
+
export const GET = handler;
|
|
9
|
+
export const POST = handler;
|
|
10
|
+
export const PUT = handler;
|
|
11
|
+
export const PATCH = handler;
|
|
12
|
+
export const DELETE = handler;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { RequestHandler } from "@sveltejs/kit";
|
|
2
|
+
import { backendBaseUrl, proxyRequest } from "$lib/server/backend-proxy";
|
|
3
|
+
|
|
4
|
+
// Proxy the better-auth handler, keeping the /api/auth prefix.
|
|
5
|
+
const handler: RequestHandler = ({ request, params, url, getClientAddress }) =>
|
|
6
|
+
proxyRequest(request, `${backendBaseUrl()}/api/auth/${params.all}${url.search}`, getClientAddress());
|
|
7
|
+
|
|
8
|
+
export const GET = handler;
|
|
9
|
+
export const POST = handler;
|
|
10
|
+
export const PUT = handler;
|
|
11
|
+
export const PATCH = handler;
|
|
12
|
+
export const DELETE = handler;
|
|
@@ -2,12 +2,20 @@
|
|
|
2
2
|
"name": "{{projectName}}",
|
|
3
3
|
"version": "0.0.0",
|
|
4
4
|
"private": true,
|
|
5
|
-
"engines": {
|
|
6
|
-
|
|
5
|
+
"engines": {
|
|
6
|
+
"node": ">=20"
|
|
7
|
+
},
|
|
8
|
+
"workspaces": [
|
|
9
|
+
"apps/*",
|
|
10
|
+
"tests"
|
|
11
|
+
],
|
|
7
12
|
"scripts": {
|
|
8
13
|
"dev": "npm run dev --workspaces --if-present",
|
|
9
14
|
"build": "npm run build --workspaces --if-present",
|
|
10
15
|
"lint": "npm run lint --workspaces --if-present",
|
|
11
|
-
"test": "npm run test --workspaces --if-present"
|
|
16
|
+
"test": "npm run test --workspaces --if-present",
|
|
17
|
+
"test:e2e": "npm run test:e2e -w tests",
|
|
18
|
+
"test:e2e:ui": "npm run test:e2e:ui -w tests",
|
|
19
|
+
"test:e2e:api": "npm run test:e2e:api -w tests"
|
|
12
20
|
}
|
|
13
21
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# tests
|
|
2
|
+
|
|
3
|
+
End-to-end tests for this app, run with [`@playwright/test`](https://playwright.dev):
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm run test:e2e # all specs (api + ui)
|
|
7
|
+
npm run test:e2e:api # backend endpoints (request-only)
|
|
8
|
+
npm run test:e2e:ui # pages (chromium)
|
|
9
|
+
npm run test:e2e:report # open the HTML report
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
The suite runs against a live stack (web on `E2E_BASE_URL`, default
|
|
13
|
+
`http://localhost:5173`, proxying `/api/*` to the API). Start PostgreSQL, run the
|
|
14
|
+
auth migration, and start the API + web first. Files ending in `*.api.spec.ts` run
|
|
15
|
+
in the request-only `api` project; `*.ui.spec.ts` run in the chromium `ui` project.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { expect, test } from "@playwright/test";
|
|
2
|
+
|
|
3
|
+
// Health endpoints are public and present in every generated app.
|
|
4
|
+
test("GET /health is public and ok @smoke", async ({ request }) => {
|
|
5
|
+
const res = await request.get("/api/health");
|
|
6
|
+
expect(res.ok()).toBeTruthy();
|
|
7
|
+
expect((await res.json()).status).toBe("ok");
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("GET /health/ready reports the database", async ({ request }) => {
|
|
11
|
+
const res = await request.get("/api/health/ready");
|
|
12
|
+
expect(res.status()).toBe(200);
|
|
13
|
+
expect((await res.json()).status).toBe("ready");
|
|
14
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{projectName}}-tests",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"test:e2e": "playwright test",
|
|
7
|
+
"test:e2e:ui": "playwright test --project=ui",
|
|
8
|
+
"test:e2e:api": "playwright test --project=api",
|
|
9
|
+
"test:e2e:report": "playwright show-report"
|
|
10
|
+
},
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@playwright/test": "^1.61.1"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { defineConfig, devices } from "@playwright/test";
|
|
2
|
+
|
|
3
|
+
// The suite runs against a live stack: web on E2E_BASE_URL (proxying /api/* to
|
|
4
|
+
// the API). Start it first (see docs/testing.md) — locally via the dev harness,
|
|
5
|
+
// in CI via scripts/e2e-ci.mjs. Two projects: `api` (request-only, backend e2e)
|
|
6
|
+
// and `ui` (chromium, pages). Files are routed by suffix (*.api/*.ui.spec.ts).
|
|
7
|
+
const baseURL = process.env.E2E_BASE_URL ?? "http://localhost:5173";
|
|
8
|
+
|
|
9
|
+
export default defineConfig({
|
|
10
|
+
testDir: ".",
|
|
11
|
+
fullyParallel: true,
|
|
12
|
+
forbidOnly: !!process.env.CI,
|
|
13
|
+
retries: process.env.CI ? 2 : 0,
|
|
14
|
+
workers: process.env.CI ? 1 : undefined,
|
|
15
|
+
reporter: [["list"], ["html", { open: "never" }]],
|
|
16
|
+
use: {
|
|
17
|
+
baseURL,
|
|
18
|
+
trace: "on-first-retry",
|
|
19
|
+
screenshot: "only-on-failure",
|
|
20
|
+
},
|
|
21
|
+
projects: [
|
|
22
|
+
{ name: "api", testMatch: /.*\.api\.spec\.ts/ },
|
|
23
|
+
{ name: "ui", testMatch: /.*\.ui\.spec\.ts/, use: { ...devices["Desktop Chrome"] } },
|
|
24
|
+
],
|
|
25
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { expect, test } from "@playwright/test";
|
|
2
|
+
|
|
3
|
+
// Module-agnostic: the app responds at "/" (a page or a redirect to it).
|
|
4
|
+
test("app is reachable @smoke", async ({ page }) => {
|
|
5
|
+
const res = await page.goto("/");
|
|
6
|
+
expect(res?.status()).toBeLessThan(400);
|
|
7
|
+
await expect(page.locator("body")).toBeVisible();
|
|
8
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@podosoft/podokit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "An opinionated but extensible starter toolkit and CLI for full-stack TypeScript apps with NestJS, SvelteKit, TailwindCSS, shadcn-svelte, Docker, and k3s.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { Test } from "@nestjs/testing";
|
|
2
|
-
import type { INestApplication } from "@nestjs/common";
|
|
3
|
-
import request from "supertest";
|
|
4
|
-
import { HealthModule } from "../src/health/health.module";
|
|
5
|
-
|
|
6
|
-
describe("Health (e2e)", () => {
|
|
7
|
-
let app: INestApplication;
|
|
8
|
-
|
|
9
|
-
beforeAll(async () => {
|
|
10
|
-
const moduleRef = await Test.createTestingModule({ imports: [HealthModule] }).compile();
|
|
11
|
-
app = moduleRef.createNestApplication();
|
|
12
|
-
await app.init();
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
afterAll(async () => {
|
|
16
|
-
await app.close();
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it("GET /health returns ok", async () => {
|
|
20
|
-
const res = await request(app.getHttpServer()).get("/health").expect(200);
|
|
21
|
-
expect(res.body.status).toBe("ok");
|
|
22
|
-
});
|
|
23
|
-
});
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { RequestHandler } from "@sveltejs/kit";
|
|
2
|
-
import { backendBaseUrl, backendProxyHeaders } from "$lib/server/backend-proxy";
|
|
3
|
-
|
|
4
|
-
export const GET: RequestHandler = async ({ request }) => {
|
|
5
|
-
const upstream = await fetch(`${backendBaseUrl()}/health`, {
|
|
6
|
-
headers: backendProxyHeaders(request),
|
|
7
|
-
});
|
|
8
|
-
return new Response(await upstream.text(), {
|
|
9
|
-
status: upstream.status,
|
|
10
|
-
headers: { "content-type": "application/json" },
|
|
11
|
-
});
|
|
12
|
-
};
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { Test } from "@nestjs/testing";
|
|
2
|
-
import type { INestApplication } from "@nestjs/common";
|
|
3
|
-
import request from "supertest";
|
|
4
|
-
import { HealthModule } from "../src/health/health.module";
|
|
5
|
-
|
|
6
|
-
describe("Health (e2e)", () => {
|
|
7
|
-
let app: INestApplication;
|
|
8
|
-
|
|
9
|
-
beforeAll(async () => {
|
|
10
|
-
const moduleRef = await Test.createTestingModule({ imports: [HealthModule] }).compile();
|
|
11
|
-
app = moduleRef.createNestApplication();
|
|
12
|
-
await app.init();
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
afterAll(async () => {
|
|
16
|
-
await app.close();
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it("GET /health returns ok", async () => {
|
|
20
|
-
const res = await request(app.getHttpServer()).get("/health").expect(200);
|
|
21
|
-
expect(res.body.status).toBe("ok");
|
|
22
|
-
});
|
|
23
|
-
});
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { RequestHandler } from "@sveltejs/kit";
|
|
2
|
-
import { backendBaseUrl, backendProxyHeaders } from "$lib/server/backend-proxy";
|
|
3
|
-
|
|
4
|
-
export const GET: RequestHandler = async ({ request }) => {
|
|
5
|
-
const upstream = await fetch(`${backendBaseUrl()}/health`, {
|
|
6
|
-
headers: backendProxyHeaders(request),
|
|
7
|
-
});
|
|
8
|
-
return new Response(await upstream.text(), {
|
|
9
|
-
status: upstream.status,
|
|
10
|
-
headers: { "content-type": "application/json" },
|
|
11
|
-
});
|
|
12
|
-
};
|