create-nexst-monorepo 0.1.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/index.js +1727 -0
- package/package.json +37 -0
- package/template/.agents/plugins/marketplace.json +21 -0
- package/template/.claude/settings.json +6 -0
- package/template/.claude/skills/frontend-design/LICENSE.txt +177 -0
- package/template/.claude/skills/frontend-design/SKILL.md +154 -0
- package/template/.claude/skills/theme-factory/SKILL.md +95 -0
- package/template/.claude/skills/theme-factory/references/schema.md +104 -0
- package/template/.claude/skills/theme-factory/scripts/theme.mjs +261 -0
- package/template/.dockerignore +43 -0
- package/template/.editorconfig +10 -0
- package/template/.github/workflows/ci.yaml +289 -0
- package/template/.github/workflows/publish-app.yaml +66 -0
- package/template/.github/workflows/publish.yaml +148 -0
- package/template/.github/workflows/snyk.yaml +34 -0
- package/template/.vscode/settings.json +8 -0
- package/template/AGENTS.md +42 -0
- package/template/Dockerfile.base +9 -0
- package/template/README.md +54 -0
- package/template/_gitignore +10 -0
- package/template/apps/api/.env.example +32 -0
- package/template/apps/api/AGENTS.md +44 -0
- package/template/apps/api/Dockerfile +69 -0
- package/template/apps/api/_gitignore +8 -0
- package/template/apps/api/knip.config.ts +22 -0
- package/template/apps/api/kustomize/base/application.properties +16 -0
- package/template/apps/api/kustomize/base/deployment.yaml +94 -0
- package/template/apps/api/kustomize/base/kustomization.yaml +17 -0
- package/template/apps/api/kustomize/base/service.yaml +10 -0
- package/template/apps/api/kustomize/overlays/prod/kustomization.yaml +17 -0
- package/template/apps/api/kustomize/overlays/staging/application.properties +1 -0
- package/template/apps/api/kustomize/overlays/staging/kustomization.yaml +21 -0
- package/template/apps/api/kustomize/secret.keys +14 -0
- package/template/apps/api/oxfmt.config.ts +8 -0
- package/template/apps/api/oxlint.config.ts +107 -0
- package/template/apps/api/package.json +79 -0
- package/template/apps/api/prisma/migrations/20260927000000_init/migration.sql +113 -0
- package/template/apps/api/prisma/migrations/migration_lock.toml +3 -0
- package/template/apps/api/prisma/schema.prisma +113 -0
- package/template/apps/api/prisma.config.ts +7 -0
- package/template/apps/api/scripts/auth.cli.ts +19 -0
- package/template/apps/api/scripts/seal-secret.sh +171 -0
- package/template/apps/api/sonar-project.properties +9 -0
- package/template/apps/api/src/app-info/app-info.dto.ts +10 -0
- package/template/apps/api/src/app-info/app-info.module.ts +7 -0
- package/template/apps/api/src/app-info/app-info.resolver.spec.ts +14 -0
- package/template/apps/api/src/app-info/app-info.resolver.ts +21 -0
- package/template/apps/api/src/app.module.spec.ts +93 -0
- package/template/apps/api/src/app.module.ts +77 -0
- package/template/apps/api/src/auth/auth.guard.spec.ts +90 -0
- package/template/apps/api/src/auth/auth.guard.ts +97 -0
- package/template/apps/api/src/auth/auth.ts +129 -0
- package/template/apps/api/src/auth/decorator/allow-anonymous.decorator.ts +17 -0
- package/template/apps/api/src/auth/decorator/is-email-verified.decorator.spec.ts +41 -0
- package/template/apps/api/src/auth/decorator/is-email-verified.decorator.ts +20 -0
- package/template/apps/api/src/auth/decorator/roles.decorator.ts +27 -0
- package/template/apps/api/src/auth/testing/make-session.ts +31 -0
- package/template/apps/api/src/auth/types/auth.types.ts +27 -0
- package/template/apps/api/src/bootstrap/register-graphql-enums.ts +12 -0
- package/template/apps/api/src/bootstrap/security.ts +24 -0
- package/template/apps/api/src/bootstrap/swagger.ts +28 -0
- package/template/apps/api/src/config/app.config.spec.ts +22 -0
- package/template/apps/api/src/config/app.config.ts +122 -0
- package/template/apps/api/src/config/graphql-depth-limit.ts +103 -0
- package/template/apps/api/src/config/graphql.config.ts +77 -0
- package/template/apps/api/src/config/resources/app.yaml +29 -0
- package/template/apps/api/src/errors/app-parse-uuid.pipe.ts +27 -0
- package/template/apps/api/src/errors/app.exception.ts +47 -0
- package/template/apps/api/src/errors/error-code.enum.ts +54 -0
- package/template/apps/api/src/errors/http-status-fallback-code.util.ts +27 -0
- package/template/apps/api/src/errors/validation-exception.factory.ts +43 -0
- package/template/apps/api/src/health/health.controller.spec.ts +82 -0
- package/template/apps/api/src/health/health.controller.ts +57 -0
- package/template/apps/api/src/health/health.module.ts +12 -0
- package/template/apps/api/src/logging/context-logger.ts +18 -0
- package/template/apps/api/src/logging/logger.config.ts +23 -0
- package/template/apps/api/src/logging/logging.module.ts +22 -0
- package/template/apps/api/src/mail/mail.module.ts +12 -0
- package/template/apps/api/src/mail/mail.service.spec.ts +47 -0
- package/template/apps/api/src/mail/mail.service.ts +123 -0
- package/template/apps/api/src/main.ts +41 -0
- package/template/apps/api/src/prisma/prisma.module.ts +12 -0
- package/template/apps/api/src/prisma/prisma.service.spec.ts +136 -0
- package/template/apps/api/src/prisma/prisma.service.ts +138 -0
- package/template/apps/api/src/queue/dto/enqueue-example.input.ts +11 -0
- package/template/apps/api/src/queue/example-queue.resolver.ts +14 -0
- package/template/apps/api/src/queue/example-queue.service.spec.ts +21 -0
- package/template/apps/api/src/queue/example-queue.service.ts +32 -0
- package/template/apps/api/src/queue/queue.module.ts +27 -0
- package/template/apps/api/src/r2/r2.module.ts +12 -0
- package/template/apps/api/src/r2/r2.service.spec.ts +117 -0
- package/template/apps/api/src/r2/r2.service.ts +131 -0
- package/template/apps/api/src/user/dto/user.dto.ts +26 -0
- package/template/apps/api/src/user/user.module.ts +10 -0
- package/template/apps/api/src/user/user.resolver.spec.ts +18 -0
- package/template/apps/api/src/user/user.resolver.ts +24 -0
- package/template/apps/api/src/utils/catch-everything-exception.filter.spec.ts +292 -0
- package/template/apps/api/src/utils/catch-everything-exception.filter.ts +70 -0
- package/template/apps/api/src/utils/error-handler.util.spec.ts +20 -0
- package/template/apps/api/src/utils/error-handler.util.ts +12 -0
- package/template/apps/api/src/utils/escape-like-pattern.util.spec.ts +29 -0
- package/template/apps/api/src/utils/escape-like-pattern.util.ts +11 -0
- package/template/apps/api/src/utils/pagination.input.ts +20 -0
- package/template/apps/api/src/utils/prisma-exception.filter.spec.ts +186 -0
- package/template/apps/api/src/utils/prisma-exception.filter.ts +45 -0
- package/template/apps/api/src/utils/prisma-graphql-exception.filter.spec.ts +216 -0
- package/template/apps/api/src/utils/prisma-graphql-exception.filter.ts +43 -0
- package/template/apps/api/tests/app.e2e-spec.ts +35 -0
- package/template/apps/api/tests/auth.e2e-spec.ts +38 -0
- package/template/apps/api/tests/helpers/api.ts +27 -0
- package/template/apps/api/tsconfig.json +11 -0
- package/template/apps/api/vitest.config.e2e.ts +16 -0
- package/template/apps/api/vitest.config.ts +40 -0
- package/template/apps/web/.env.example +7 -0
- package/template/apps/web/.storybook/decorators.tsx +38 -0
- package/template/apps/web/.storybook/main.ts +17 -0
- package/template/apps/web/.storybook/preview.ts +38 -0
- package/template/apps/web/.storybook/vitest.setup.ts +5 -0
- package/template/apps/web/AGENTS.md +29 -0
- package/template/apps/web/Dockerfile +49 -0
- package/template/apps/web/_gitignore +16 -0
- package/template/apps/web/knip.config.ts +15 -0
- package/template/apps/web/kustomize/base/application.properties +5 -0
- package/template/apps/web/kustomize/base/deployment.yaml +60 -0
- package/template/apps/web/kustomize/base/kustomization.yaml +17 -0
- package/template/apps/web/kustomize/base/service.yaml +10 -0
- package/template/apps/web/kustomize/overlays/prod/kustomization.yaml +12 -0
- package/template/apps/web/kustomize/overlays/staging/application.properties +2 -0
- package/template/apps/web/kustomize/overlays/staging/kustomization.yaml +18 -0
- package/template/apps/web/messages/de.json +124 -0
- package/template/apps/web/messages/en.json +124 -0
- package/template/apps/web/next-env.d.ts +7 -0
- package/template/apps/web/next.config.ts +39 -0
- package/template/apps/web/oxfmt.config.ts +9 -0
- package/template/apps/web/oxlint.config.ts +36 -0
- package/template/apps/web/package.json +80 -0
- package/template/apps/web/playwright.config.ts +34 -0
- package/template/apps/web/postcss.config.mjs +7 -0
- package/template/apps/web/sonar-project.properties +9 -0
- package/template/apps/web/src/app/[locale]/(auth)/recovery/page.tsx +22 -0
- package/template/apps/web/src/app/[locale]/(auth)/reset-password/page.tsx +29 -0
- package/template/apps/web/src/app/[locale]/(auth)/sign-in/page.tsx +24 -0
- package/template/apps/web/src/app/[locale]/(auth)/sign-up/page.tsx +24 -0
- package/template/apps/web/src/app/[locale]/(auth)/verify/page.tsx +24 -0
- package/template/apps/web/src/app/[locale]/dashboard/page.tsx +34 -0
- package/template/apps/web/src/app/[locale]/layout.tsx +60 -0
- package/template/apps/web/src/app/[locale]/page.tsx +23 -0
- package/template/apps/web/src/app/api/health/route.ts +3 -0
- package/template/apps/web/src/app/global-error.tsx +19 -0
- package/template/apps/web/src/app/globals.css +98 -0
- package/template/apps/web/src/app/icon.svg +1 -0
- package/template/apps/web/src/app/robots.ts +15 -0
- package/template/apps/web/src/app/sitemap.ts +24 -0
- package/template/apps/web/src/components/alert/alert.stories.tsx +25 -0
- package/template/apps/web/src/components/alert/alert.tsx +50 -0
- package/template/apps/web/src/components/avatar/avatar.stories.tsx +116 -0
- package/template/apps/web/src/components/avatar/avatar.tsx +88 -0
- package/template/apps/web/src/components/button/button-variants.ts +28 -0
- package/template/apps/web/src/components/button/button.stories.tsx +33 -0
- package/template/apps/web/src/components/button/button.tsx +32 -0
- package/template/apps/web/src/components/checkbox/checkbox.stories.tsx +312 -0
- package/template/apps/web/src/components/checkbox/checkbox.tsx +38 -0
- package/template/apps/web/src/components/dialog/danger-panel.stories.tsx +41 -0
- package/template/apps/web/src/components/dialog/danger-panel.tsx +32 -0
- package/template/apps/web/src/components/dialog/dialog.stories.tsx +239 -0
- package/template/apps/web/src/components/dialog/dialog.tsx +136 -0
- package/template/apps/web/src/components/dialog-buttons.stories.tsx +55 -0
- package/template/apps/web/src/components/dialog-buttons.tsx +48 -0
- package/template/apps/web/src/components/drawer/drawer.stories.tsx +251 -0
- package/template/apps/web/src/components/drawer/drawer.tsx +120 -0
- package/template/apps/web/src/components/dropdown-menu/dropdown-menu.stories.tsx +329 -0
- package/template/apps/web/src/components/dropdown-menu/dropdown-menu.tsx +263 -0
- package/template/apps/web/src/components/empty-state.stories.tsx +49 -0
- package/template/apps/web/src/components/empty-state.tsx +33 -0
- package/template/apps/web/src/components/field-group.stories.tsx +47 -0
- package/template/apps/web/src/components/field-group.tsx +20 -0
- package/template/apps/web/src/components/field-label.stories.tsx +34 -0
- package/template/apps/web/src/components/field-label.tsx +28 -0
- package/template/apps/web/src/components/icons.tsx +25 -0
- package/template/apps/web/src/components/input/input-otp.stories.tsx +86 -0
- package/template/apps/web/src/components/input/input-otp.tsx +184 -0
- package/template/apps/web/src/components/input/input.stories.tsx +35 -0
- package/template/apps/web/src/components/input/input.tsx +180 -0
- package/template/apps/web/src/components/popover/popover.stories.tsx +479 -0
- package/template/apps/web/src/components/popover/popover.tsx +45 -0
- package/template/apps/web/src/components/responsive-dialog/responsive-dialog.stories.tsx +307 -0
- package/template/apps/web/src/components/responsive-dialog/responsive-dialog.tsx +96 -0
- package/template/apps/web/src/components/searchbar/searchbar.stories.tsx +59 -0
- package/template/apps/web/src/components/searchbar/searchbar.tsx +64 -0
- package/template/apps/web/src/components/select/select.stories.tsx +293 -0
- package/template/apps/web/src/components/select/select.tsx +445 -0
- package/template/apps/web/src/components/skeleton/skeleton.stories.tsx +76 -0
- package/template/apps/web/src/components/skeleton/skeleton.tsx +13 -0
- package/template/apps/web/src/components/spinner/spinner.tsx +18 -0
- package/template/apps/web/src/components/switch/switch.stories.tsx +71 -0
- package/template/apps/web/src/components/switch/switch.tsx +35 -0
- package/template/apps/web/src/components/tabs/tabs.stories.tsx +209 -0
- package/template/apps/web/src/components/tabs/tabs.tsx +69 -0
- package/template/apps/web/src/components/tabs/url-tabs.stories.tsx +68 -0
- package/template/apps/web/src/components/tabs/url-tabs.tsx +42 -0
- package/template/apps/web/src/components/textarea/textarea.stories.tsx +144 -0
- package/template/apps/web/src/components/textarea/textarea.tsx +115 -0
- package/template/apps/web/src/components/toast/toaster.tsx +38 -0
- package/template/apps/web/src/components/tooltip/tooltip.stories.tsx +237 -0
- package/template/apps/web/src/components/tooltip/tooltip.tsx +49 -0
- package/template/apps/web/src/config/csp.ts +59 -0
- package/template/apps/web/src/config/routes.ts +14 -0
- package/template/apps/web/src/config/site.ts +5 -0
- package/template/apps/web/src/env.ts +15 -0
- package/template/apps/web/src/features/auth/auth-card.tsx +20 -0
- package/template/apps/web/src/features/auth/auth-divider.tsx +14 -0
- package/template/apps/web/src/features/auth/google-auth-button.tsx +49 -0
- package/template/apps/web/src/features/auth/google-one-tap.tsx +23 -0
- package/template/apps/web/src/features/auth/lib/auth-error.test.ts +22 -0
- package/template/apps/web/src/features/auth/lib/auth-error.ts +16 -0
- package/template/apps/web/src/features/auth/lib/return-to.test.ts +41 -0
- package/template/apps/web/src/features/auth/lib/return-to.ts +19 -0
- package/template/apps/web/src/features/auth/lib/validator.test.ts +45 -0
- package/template/apps/web/src/features/auth/lib/validator.ts +49 -0
- package/template/apps/web/src/features/auth/passkey-settings.tsx +106 -0
- package/template/apps/web/src/features/auth/recovery-form.tsx +61 -0
- package/template/apps/web/src/features/auth/resend-verification-button.tsx +36 -0
- package/template/apps/web/src/features/auth/reset-password-form.tsx +62 -0
- package/template/apps/web/src/features/auth/sign-in-form.stories.tsx +22 -0
- package/template/apps/web/src/features/auth/sign-in-form.tsx +116 -0
- package/template/apps/web/src/features/auth/sign-out-button.tsx +26 -0
- package/template/apps/web/src/features/auth/sign-up-form.tsx +107 -0
- package/template/apps/web/src/features/shared/provider.tsx +16 -0
- package/template/apps/web/src/features/shared/site-header.tsx +48 -0
- package/template/apps/web/src/features/shared/theme-toggle.tsx +24 -0
- package/template/apps/web/src/i18n/navigation.ts +5 -0
- package/template/apps/web/src/i18n/request.ts +21 -0
- package/template/apps/web/src/i18n/routing.ts +6 -0
- package/template/apps/web/src/lib/auth-client.ts +19 -0
- package/template/apps/web/src/lib/auth-server.ts +46 -0
- package/template/apps/web/src/lib/auth.ts +8 -0
- package/template/apps/web/src/lib/codegen.ts +30 -0
- package/template/apps/web/src/lib/graphql-client.ts +29 -0
- package/template/apps/web/src/lib/graphql-server.ts +28 -0
- package/template/apps/web/src/lib/graphql-utils.test.ts +32 -0
- package/template/apps/web/src/lib/graphql-utils.ts +76 -0
- package/template/apps/web/src/lib/hooks/use-media-query.ts +21 -0
- package/template/apps/web/src/lib/metadata.ts +17 -0
- package/template/apps/web/src/lib/network-error.test.ts +30 -0
- package/template/apps/web/src/lib/network-error.ts +17 -0
- package/template/apps/web/src/lib/proxy-utils.ts +42 -0
- package/template/apps/web/src/lib/query-client.ts +29 -0
- package/template/apps/web/src/lib/utils.ts +5 -0
- package/template/apps/web/src/proxy.ts +57 -0
- package/template/apps/web/test-results/.last-run.json +4 -0
- package/template/apps/web/tests/auth.spec.ts +17 -0
- package/template/apps/web/tests/csp-theme-script.spec.ts +37 -0
- package/template/apps/web/tests/home.spec.ts +25 -0
- package/template/apps/web/tsconfig.build.json +4 -0
- package/template/apps/web/tsconfig.json +28 -0
- package/template/apps/web/vitest.config.ts +40 -0
- package/template/apps/web/vitest.shims.d.ts +1 -0
- package/template/apps/worker/.env.example +4 -0
- package/template/apps/worker/AGENTS.md +11 -0
- package/template/apps/worker/Dockerfile +35 -0
- package/template/apps/worker/knip.config.ts +3 -0
- package/template/apps/worker/kustomize/base/application.properties +4 -0
- package/template/apps/worker/kustomize/base/deployment.yaml +62 -0
- package/template/apps/worker/kustomize/base/kustomization.yaml +16 -0
- package/template/apps/worker/kustomize/overlays/prod/kustomization.yaml +12 -0
- package/template/apps/worker/kustomize/overlays/staging/application.properties +1 -0
- package/template/apps/worker/kustomize/overlays/staging/kustomization.yaml +18 -0
- package/template/apps/worker/oxfmt.config.ts +8 -0
- package/template/apps/worker/oxlint.config.ts +15 -0
- package/template/apps/worker/package.json +33 -0
- package/template/apps/worker/sonar-project.properties +8 -0
- package/template/apps/worker/src/env.ts +13 -0
- package/template/apps/worker/src/lib/graceful-shutdown.ts +27 -0
- package/template/apps/worker/src/lib/logger.ts +20 -0
- package/template/apps/worker/src/main.ts +17 -0
- package/template/apps/worker/src/processors/example-processor.ts +15 -0
- package/template/apps/worker/src/worker/redis.ts +19 -0
- package/template/apps/worker/src/worker/worker.ts +37 -0
- package/template/apps/worker/tests/example-processor.test.ts +13 -0
- package/template/apps/worker/tests/graceful-shutdown.test.ts +34 -0
- package/template/apps/worker/tsconfig.json +4 -0
- package/template/apps/worker/vitest.config.ts +13 -0
- package/template/bun.lock +3053 -0
- package/template/bunfig.toml +2 -0
- package/template/compose.yaml +53 -0
- package/template/docker-bake.hcl +86 -0
- package/template/package.json +47 -0
- package/template/packages/lint/knip.ts +40 -0
- package/template/packages/lint/oxfmt.ts +8 -0
- package/template/packages/lint/oxlint.ts +136 -0
- package/template/packages/lint/package.json +13 -0
- package/template/packages/queues/oxfmt.config.ts +8 -0
- package/template/packages/queues/oxlint.config.ts +7 -0
- package/template/packages/queues/package.json +15 -0
- package/template/packages/queues/src/example-job.ts +11 -0
- package/template/packages/queues/src/index.ts +3 -0
- package/template/packages/queues/src/queue-names.ts +2 -0
- package/template/packages/queues/src/redis-connection.ts +27 -0
- package/template/packages/queues/tsconfig.json +4 -0
- package/template/packages/tsconfig/base.json +23 -0
- package/template/packages/tsconfig/node.json +11 -0
- package/template/packages/tsconfig/package.json +4 -0
- package/template/turbo.json +50 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Allowlist for scripts/seal-secret.sh: only these keys from .env are sealed
|
|
2
|
+
# into nexst-api-secret. Everything else is plain config and belongs in
|
|
3
|
+
# kustomize/*/application.properties — the Secret is mounted after the
|
|
4
|
+
# ConfigMap (envFrom), so any extra key sealed here would override it.
|
|
5
|
+
DATABASE_URL
|
|
6
|
+
# @if auth
|
|
7
|
+
BETTER_AUTH_SECRET
|
|
8
|
+
GOOGLE_CLIENT_SECRET
|
|
9
|
+
RESEND_API_KEY
|
|
10
|
+
# @endif
|
|
11
|
+
# @if r2
|
|
12
|
+
R2_ACCESS_KEY_ID
|
|
13
|
+
R2_SECRET_ACCESS_KEY
|
|
14
|
+
# @endif
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { baseOxfmtConfig } from "@nexst/lint/oxfmt";
|
|
2
|
+
import { baseIgnorePatterns } from "@nexst/lint/oxlint";
|
|
3
|
+
import { defineConfig } from "oxfmt";
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
...baseOxfmtConfig,
|
|
7
|
+
ignorePatterns: [...baseIgnorePatterns, "migrations/**/*", "src/generated/prisma/**"],
|
|
8
|
+
});
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import base, { baseIgnorePatterns } from "@nexst/lint/oxlint";
|
|
2
|
+
import { defineConfig } from "oxlint";
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
extends: [base],
|
|
6
|
+
ignorePatterns: [...baseIgnorePatterns, "migrations/**/*", "src/generated/prisma/**"],
|
|
7
|
+
overrides: [
|
|
8
|
+
{
|
|
9
|
+
files: ["**/*.spec.ts", "**/*.e2e-spec.ts", "tests/**"],
|
|
10
|
+
rules: {
|
|
11
|
+
"no-console": "off",
|
|
12
|
+
"no-restricted-imports": "off",
|
|
13
|
+
"security/detect-non-literal-fs-filename": "off",
|
|
14
|
+
"sonarjs/no-duplicate-string": "off",
|
|
15
|
+
"typescript/no-misused-spread": "off",
|
|
16
|
+
"typescript/no-non-null-assertion": "off",
|
|
17
|
+
"typescript/unbound-method": "off",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
files: ["scripts/**"],
|
|
22
|
+
rules: {
|
|
23
|
+
"no-console": "off",
|
|
24
|
+
"security/detect-non-literal-fs-filename": "off",
|
|
25
|
+
"security/detect-unsafe-regex": "off",
|
|
26
|
+
"sonarjs/cognitive-complexity": "off",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
files: ["prisma/**"],
|
|
31
|
+
rules: {
|
|
32
|
+
"no-console": "off",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
files: ["**/*-exception.filter.ts", "**/*-exception.filter.spec.ts"],
|
|
37
|
+
rules: {
|
|
38
|
+
"promise/valid-params": "off",
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
rules: {
|
|
43
|
+
eqeqeq: ["error", "always", { null: "ignore" }],
|
|
44
|
+
"import/no-cycle": "error",
|
|
45
|
+
"import/no-duplicates": "error",
|
|
46
|
+
"import/no-self-import": "error",
|
|
47
|
+
|
|
48
|
+
"no-console": "error",
|
|
49
|
+
"no-restricted-imports": [
|
|
50
|
+
"error",
|
|
51
|
+
{
|
|
52
|
+
paths: [
|
|
53
|
+
{
|
|
54
|
+
message: "Prisma-Client immer aus #src/generated/prisma/client importieren.",
|
|
55
|
+
name: "@prisma/client",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
importNames: [
|
|
59
|
+
"BadRequestException",
|
|
60
|
+
"ConflictException",
|
|
61
|
+
"ForbiddenException",
|
|
62
|
+
"InternalServerErrorException",
|
|
63
|
+
"NotFoundException",
|
|
64
|
+
"ServiceUnavailableException",
|
|
65
|
+
"UnauthorizedException",
|
|
66
|
+
"UnprocessableEntityException",
|
|
67
|
+
],
|
|
68
|
+
message:
|
|
69
|
+
"Codes-only API: throw `new AppException(ErrorCode.X, HttpStatus.Y, params?)` (see src/errors/app.exception.ts) instead of a free-text HttpException.",
|
|
70
|
+
name: "@nestjs/common",
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
|
|
76
|
+
"oxc/no-accumulating-spread": "warn",
|
|
77
|
+
"oxc/no-map-spread": "warn",
|
|
78
|
+
|
|
79
|
+
"typescript/await-thenable": "error",
|
|
80
|
+
"typescript/no-base-to-string": "error",
|
|
81
|
+
"typescript/no-confusing-void-expression": ["warn", { ignoreArrowShorthand: true }],
|
|
82
|
+
"typescript/no-explicit-any": "warn",
|
|
83
|
+
"typescript/no-floating-promises": "error",
|
|
84
|
+
"typescript/no-misused-promises": "error",
|
|
85
|
+
"typescript/no-non-null-assertion": "warn",
|
|
86
|
+
"typescript/no-unnecessary-type-assertion": "warn",
|
|
87
|
+
"typescript/prefer-nullish-coalescing": "warn",
|
|
88
|
+
"typescript/prefer-optional-chain": "warn",
|
|
89
|
+
"typescript/require-await": "warn",
|
|
90
|
+
"typescript/restrict-plus-operands": "error",
|
|
91
|
+
"typescript/return-await": ["error", "error-handling-correctness-only"],
|
|
92
|
+
"typescript/switch-exhaustiveness-check": "warn",
|
|
93
|
+
|
|
94
|
+
"unicorn/error-message": "error",
|
|
95
|
+
"unicorn/prefer-node-protocol": "error",
|
|
96
|
+
"unicorn/prefer-structured-clone": "warn",
|
|
97
|
+
"unicorn/throw-new-error": "error",
|
|
98
|
+
|
|
99
|
+
"vitest/expect-expect": "warn",
|
|
100
|
+
"vitest/no-disabled-tests": "warn",
|
|
101
|
+
"vitest/no-focused-tests": "error",
|
|
102
|
+
"vitest/no-identical-title": "error",
|
|
103
|
+
"vitest/require-mock-type-parameters": "off",
|
|
104
|
+
"vitest/require-to-throw-message": "off",
|
|
105
|
+
"vitest/valid-expect": "error",
|
|
106
|
+
},
|
|
107
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nexst/api",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"imports": {
|
|
7
|
+
"#src/*": "./src/*",
|
|
8
|
+
"#tests/*": "./tests/*"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"dev": "bun --watch src/main.ts",
|
|
12
|
+
"lint": "oxlint",
|
|
13
|
+
"lint:fix": "oxlint --fix",
|
|
14
|
+
"fmt": "oxfmt",
|
|
15
|
+
"fmt:check": "oxfmt --check",
|
|
16
|
+
"lint:ts": "tsc --noEmit",
|
|
17
|
+
"lint:error-codes": "! grep -rnE \"new (BadRequestException|NotFoundException|ConflictException|ForbiddenException|UnauthorizedException|UnprocessableEntityException|InternalServerErrorException|ServiceUnavailableException)\\(\" src --include=\"*.ts\" --exclude=\"*.spec.ts\" --exclude-dir=generated",
|
|
18
|
+
"unused": "knip --no-config-hints",
|
|
19
|
+
"auth:generate": "better-auth generate --config scripts/auth.cli.ts -y",
|
|
20
|
+
"db:generate": "bunx --bun prisma generate",
|
|
21
|
+
"db:push": "bunx --bun prisma db push",
|
|
22
|
+
"db:migrate": "bunx --bun prisma migrate dev",
|
|
23
|
+
"db:reset": "bunx --bun prisma migrate reset --force",
|
|
24
|
+
"test:unit": "vitest run",
|
|
25
|
+
"test:cov": "vitest run --coverage",
|
|
26
|
+
"test:e2e": "vitest run --config ./vitest.config.e2e.ts"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@apollo/server": "^5.5.1",
|
|
30
|
+
"@apollo/utils.keyvaluecache": "^4.0.0",
|
|
31
|
+
"@as-integrations/fastify": "^3.1.0",
|
|
32
|
+
"@aws-sdk/client-s3": "^3.1141.0",
|
|
33
|
+
"@aws-sdk/s3-request-presigner": "^3.1141.0",
|
|
34
|
+
"@better-auth/passkey": "^1.7.6",
|
|
35
|
+
"@fastify/cors": "^11.3.0",
|
|
36
|
+
"@fastify/helmet": "^13.1.1",
|
|
37
|
+
"@fastify/static": "^10.1.5",
|
|
38
|
+
"@nestjs/apollo": "^14.0.2",
|
|
39
|
+
"@nestjs/bullmq": "^12.0.0",
|
|
40
|
+
"@nestjs/common": "^12.1.0",
|
|
41
|
+
"@nestjs/config": "^12.0.1",
|
|
42
|
+
"@nestjs/core": "^12.1.0",
|
|
43
|
+
"@nestjs/graphql": "^14.0.2",
|
|
44
|
+
"@nestjs/platform-fastify": "^12.1.0",
|
|
45
|
+
"@nestjs/swagger": "^12.0.2",
|
|
46
|
+
"@nestjs/terminus": "^12.1.0",
|
|
47
|
+
"@nestjs/throttler": "^6.7.1",
|
|
48
|
+
"@nexst/queues": "workspace:*",
|
|
49
|
+
"@prisma/adapter-pg": "^7.10.0",
|
|
50
|
+
"@prisma/client-runtime-utils": "^7.10.0",
|
|
51
|
+
"@thallesp/nestjs-better-auth": "^2.8.0",
|
|
52
|
+
"better-auth": "^1.7.6",
|
|
53
|
+
"bullmq": "^6.3.9",
|
|
54
|
+
"class-transformer": "^0.5.1",
|
|
55
|
+
"class-validator": "^0.15.1",
|
|
56
|
+
"fastify": "^5.12.5",
|
|
57
|
+
"graphql": "^17.0.2",
|
|
58
|
+
"ioredis": "6.0.0",
|
|
59
|
+
"js-yaml": "^5.4.2",
|
|
60
|
+
"nestjs-pino": "^5.2.1",
|
|
61
|
+
"nodemailer": "^10.0.10",
|
|
62
|
+
"pino": "^10.3.1",
|
|
63
|
+
"pino-http": "^11.0.0",
|
|
64
|
+
"reflect-metadata": "^0.2.2",
|
|
65
|
+
"resend": "^6.30.0",
|
|
66
|
+
"rxjs": "^7.8.2",
|
|
67
|
+
"zod": "^4.6.5"
|
|
68
|
+
},
|
|
69
|
+
"devDependencies": {
|
|
70
|
+
"@nestjs/testing": "^12.1.0",
|
|
71
|
+
"@prisma/client": "^7.10.0",
|
|
72
|
+
"@types/js-yaml": "^4.0.9",
|
|
73
|
+
"@types/node": "^26.6.3",
|
|
74
|
+
"@types/nodemailer": "^8.0.2",
|
|
75
|
+
"aws-sdk-client-mock": "^4.1.0",
|
|
76
|
+
"pino-pretty": "^13.1.3",
|
|
77
|
+
"prisma": "^7.10.0"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
-- CreateSchema
|
|
2
|
+
CREATE SCHEMA IF NOT EXISTS "nexst_user";
|
|
3
|
+
|
|
4
|
+
-- CreateEnum
|
|
5
|
+
CREATE TYPE "nexst_user"."UserRole" AS ENUM ('USER', 'ADMIN');
|
|
6
|
+
|
|
7
|
+
-- CreateTable
|
|
8
|
+
CREATE TABLE "nexst_user"."users" (
|
|
9
|
+
"id" TEXT NOT NULL,
|
|
10
|
+
"email" TEXT NOT NULL,
|
|
11
|
+
"emailVerified" BOOLEAN NOT NULL DEFAULT false,
|
|
12
|
+
"name" TEXT,
|
|
13
|
+
"image" TEXT,
|
|
14
|
+
"role" "nexst_user"."UserRole"[] DEFAULT ARRAY['USER']::"nexst_user"."UserRole"[],
|
|
15
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
16
|
+
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
17
|
+
|
|
18
|
+
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
-- CreateIndex
|
|
22
|
+
CREATE UNIQUE INDEX "users_email_key" ON "nexst_user"."users"("email");
|
|
23
|
+
-- @if auth
|
|
24
|
+
|
|
25
|
+
-- CreateTable
|
|
26
|
+
CREATE TABLE "nexst_user"."sessions" (
|
|
27
|
+
"id" TEXT NOT NULL,
|
|
28
|
+
"expiresAt" TIMESTAMP(3) NOT NULL,
|
|
29
|
+
"token" TEXT NOT NULL,
|
|
30
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
31
|
+
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
32
|
+
"ipAddress" TEXT,
|
|
33
|
+
"userAgent" TEXT,
|
|
34
|
+
"userId" TEXT NOT NULL,
|
|
35
|
+
|
|
36
|
+
CONSTRAINT "sessions_pkey" PRIMARY KEY ("id")
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
-- CreateTable
|
|
40
|
+
CREATE TABLE "nexst_user"."accounts" (
|
|
41
|
+
"id" TEXT NOT NULL,
|
|
42
|
+
"accountId" TEXT NOT NULL,
|
|
43
|
+
"providerId" TEXT NOT NULL,
|
|
44
|
+
"accessToken" TEXT,
|
|
45
|
+
"refreshToken" TEXT,
|
|
46
|
+
"idToken" TEXT,
|
|
47
|
+
"accessTokenExpiresAt" TIMESTAMP(3),
|
|
48
|
+
"refreshTokenExpiresAt" TIMESTAMP(3),
|
|
49
|
+
"scope" TEXT,
|
|
50
|
+
"password" TEXT,
|
|
51
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
52
|
+
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
53
|
+
"userId" TEXT NOT NULL,
|
|
54
|
+
|
|
55
|
+
CONSTRAINT "accounts_pkey" PRIMARY KEY ("id")
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
-- CreateTable
|
|
59
|
+
CREATE TABLE "nexst_user"."verifications" (
|
|
60
|
+
"id" TEXT NOT NULL,
|
|
61
|
+
"identifier" TEXT NOT NULL,
|
|
62
|
+
"value" TEXT NOT NULL,
|
|
63
|
+
"expiresAt" TIMESTAMP(3) NOT NULL,
|
|
64
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
65
|
+
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
66
|
+
|
|
67
|
+
CONSTRAINT "verifications_pkey" PRIMARY KEY ("id")
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
-- CreateTable
|
|
71
|
+
CREATE TABLE "nexst_user"."passkeys" (
|
|
72
|
+
"id" TEXT NOT NULL,
|
|
73
|
+
"name" TEXT,
|
|
74
|
+
"publicKey" TEXT NOT NULL,
|
|
75
|
+
"credentialID" TEXT NOT NULL,
|
|
76
|
+
"counter" INTEGER NOT NULL,
|
|
77
|
+
"deviceType" TEXT NOT NULL,
|
|
78
|
+
"backedUp" BOOLEAN NOT NULL,
|
|
79
|
+
"transports" TEXT,
|
|
80
|
+
"aaguid" TEXT,
|
|
81
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
82
|
+
"userId" TEXT NOT NULL,
|
|
83
|
+
|
|
84
|
+
CONSTRAINT "passkeys_pkey" PRIMARY KEY ("id")
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
-- CreateIndex
|
|
88
|
+
CREATE UNIQUE INDEX "sessions_token_key" ON "nexst_user"."sessions"("token");
|
|
89
|
+
|
|
90
|
+
-- CreateIndex
|
|
91
|
+
CREATE INDEX "sessions_userId_idx" ON "nexst_user"."sessions"("userId");
|
|
92
|
+
|
|
93
|
+
-- CreateIndex
|
|
94
|
+
CREATE INDEX "accounts_userId_idx" ON "nexst_user"."accounts"("userId");
|
|
95
|
+
|
|
96
|
+
-- CreateIndex
|
|
97
|
+
CREATE INDEX "verifications_identifier_idx" ON "nexst_user"."verifications"("identifier");
|
|
98
|
+
|
|
99
|
+
-- CreateIndex
|
|
100
|
+
CREATE INDEX "passkeys_userId_idx" ON "nexst_user"."passkeys"("userId");
|
|
101
|
+
|
|
102
|
+
-- CreateIndex
|
|
103
|
+
CREATE INDEX "passkeys_credentialID_idx" ON "nexst_user"."passkeys"("credentialID");
|
|
104
|
+
|
|
105
|
+
-- AddForeignKey
|
|
106
|
+
ALTER TABLE "nexst_user"."sessions" ADD CONSTRAINT "sessions_userId_fkey" FOREIGN KEY ("userId") REFERENCES "nexst_user"."users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
107
|
+
|
|
108
|
+
-- AddForeignKey
|
|
109
|
+
ALTER TABLE "nexst_user"."accounts" ADD CONSTRAINT "accounts_userId_fkey" FOREIGN KEY ("userId") REFERENCES "nexst_user"."users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
110
|
+
|
|
111
|
+
-- AddForeignKey
|
|
112
|
+
ALTER TABLE "nexst_user"."passkeys" ADD CONSTRAINT "passkeys_userId_fkey" FOREIGN KEY ("userId") REFERENCES "nexst_user"."users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
113
|
+
-- @endif
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
generator client {
|
|
2
|
+
provider = "prisma-client-js"
|
|
3
|
+
output = "../src/generated/prisma"
|
|
4
|
+
previewFeatures = ["relationJoins"]
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
datasource db {
|
|
8
|
+
provider = "postgresql"
|
|
9
|
+
schemas = ["nexst_user"]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
model User {
|
|
13
|
+
id String @id
|
|
14
|
+
email String @unique
|
|
15
|
+
emailVerified Boolean @default(false)
|
|
16
|
+
name String?
|
|
17
|
+
image String?
|
|
18
|
+
role UserRole[] @default([USER])
|
|
19
|
+
|
|
20
|
+
createdAt DateTime @default(now())
|
|
21
|
+
updatedAt DateTime @updatedAt
|
|
22
|
+
// @if auth
|
|
23
|
+
|
|
24
|
+
sessions Session[]
|
|
25
|
+
accounts Account[]
|
|
26
|
+
passkeys Passkey[]
|
|
27
|
+
// @endif
|
|
28
|
+
|
|
29
|
+
@@map("users")
|
|
30
|
+
@@schema("nexst_user")
|
|
31
|
+
}
|
|
32
|
+
// @if auth
|
|
33
|
+
|
|
34
|
+
model Session {
|
|
35
|
+
id String @id
|
|
36
|
+
expiresAt DateTime
|
|
37
|
+
token String @unique
|
|
38
|
+
createdAt DateTime @default(now())
|
|
39
|
+
updatedAt DateTime @updatedAt
|
|
40
|
+
ipAddress String?
|
|
41
|
+
userAgent String?
|
|
42
|
+
|
|
43
|
+
userId String
|
|
44
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
45
|
+
|
|
46
|
+
@@index([userId])
|
|
47
|
+
@@map("sessions")
|
|
48
|
+
@@schema("nexst_user")
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
model Account {
|
|
52
|
+
id String @id
|
|
53
|
+
accountId String
|
|
54
|
+
providerId String
|
|
55
|
+
accessToken String?
|
|
56
|
+
refreshToken String?
|
|
57
|
+
idToken String?
|
|
58
|
+
accessTokenExpiresAt DateTime?
|
|
59
|
+
refreshTokenExpiresAt DateTime?
|
|
60
|
+
scope String?
|
|
61
|
+
password String?
|
|
62
|
+
createdAt DateTime @default(now())
|
|
63
|
+
updatedAt DateTime @updatedAt
|
|
64
|
+
|
|
65
|
+
userId String
|
|
66
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
67
|
+
|
|
68
|
+
@@index([userId])
|
|
69
|
+
@@map("accounts")
|
|
70
|
+
@@schema("nexst_user")
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
model Passkey {
|
|
74
|
+
id String @id
|
|
75
|
+
name String?
|
|
76
|
+
publicKey String
|
|
77
|
+
credentialID String
|
|
78
|
+
counter Int
|
|
79
|
+
deviceType String
|
|
80
|
+
backedUp Boolean
|
|
81
|
+
transports String?
|
|
82
|
+
aaguid String?
|
|
83
|
+
createdAt DateTime @default(now())
|
|
84
|
+
|
|
85
|
+
userId String
|
|
86
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
87
|
+
|
|
88
|
+
@@index([userId])
|
|
89
|
+
@@index([credentialID])
|
|
90
|
+
@@map("passkeys")
|
|
91
|
+
@@schema("nexst_user")
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
model Verification {
|
|
95
|
+
id String @id
|
|
96
|
+
identifier String
|
|
97
|
+
value String
|
|
98
|
+
expiresAt DateTime
|
|
99
|
+
createdAt DateTime @default(now())
|
|
100
|
+
updatedAt DateTime @updatedAt
|
|
101
|
+
|
|
102
|
+
@@index([identifier])
|
|
103
|
+
@@map("verifications")
|
|
104
|
+
@@schema("nexst_user")
|
|
105
|
+
}
|
|
106
|
+
// @endif
|
|
107
|
+
|
|
108
|
+
enum UserRole {
|
|
109
|
+
USER
|
|
110
|
+
ADMIN
|
|
111
|
+
|
|
112
|
+
@@schema("nexst_user")
|
|
113
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { PrismaPg } from "@prisma/adapter-pg";
|
|
2
|
+
import { betterAuth } from "better-auth/minimal";
|
|
3
|
+
import { buildBaseAuthOptions } from "#src/auth/auth.js";
|
|
4
|
+
import { loadConfig } from "#src/config/app.config.js";
|
|
5
|
+
import { PrismaClient } from "#src/generated/prisma/client.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Standalone better-auth instance used **only** by the better-auth CLI
|
|
9
|
+
* (`bun run auth:generate`) to derive the database schema.
|
|
10
|
+
*
|
|
11
|
+
* The CLI cannot construct the NestJS PrismaService (it needs the DI container),
|
|
12
|
+
* so it gets a bare client here. `generate` only inspects the config and never
|
|
13
|
+
* connects to the database; the adapter is required because the Prisma 7
|
|
14
|
+
* driver-adapter client refuses to be constructed without one.
|
|
15
|
+
*/
|
|
16
|
+
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL });
|
|
17
|
+
const { allowedOrigins } = loadConfig().security;
|
|
18
|
+
|
|
19
|
+
export const auth = betterAuth(buildBaseAuthOptions(new PrismaClient({ adapter }), allowedOrigins));
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Regenerates (or merges) the SealedSecret for nexst-api from .env and
|
|
4
|
+
# writes it into kustomize/ where Flux picks it up. Only the keys listed in
|
|
5
|
+
# kustomize/secret.keys are sealed; everything else is ConfigMap material.
|
|
6
|
+
#
|
|
7
|
+
# One command, no flags needed:
|
|
8
|
+
# ./scripts/seal-secret.sh
|
|
9
|
+
#
|
|
10
|
+
# On the first run it fetches the sealed-secrets controller's public cert via
|
|
11
|
+
# kubectl and caches it at kustomize/sealed-secrets.pem (commit that file — it
|
|
12
|
+
# is a public key). Every later run seals offline from the cached cert.
|
|
13
|
+
#
|
|
14
|
+
# Requires: kubectl, kubeseal (bash >= 4 — do NOT run with `sh`)
|
|
15
|
+
#
|
|
16
|
+
# Usage:
|
|
17
|
+
# ./scripts/seal-secret.sh [OUT_FILE]
|
|
18
|
+
# OUT_FILE default: kustomize/overlays/prod/sealedsecret.yaml
|
|
19
|
+
# "-" writes to stdout instead of a file
|
|
20
|
+
#
|
|
21
|
+
# Env vars (all optional):
|
|
22
|
+
# ENV_FILE env file to seal from (default: <repo>/.env)
|
|
23
|
+
# NAMESPACE target namespace (default: nexst)
|
|
24
|
+
# SECRET_NAME Secret / SealedSecret name (default: nexst-api-secret)
|
|
25
|
+
# KEYS_FILE allowlist, one key per line (default: <repo>/kustomize/secret.keys)
|
|
26
|
+
# SEALED_SECRETS_CERT explicit cert path/URL; skips the cached pem
|
|
27
|
+
# CERT_FILE where the cached cert lives (default: <repo>/kustomize/sealed-secrets.pem)
|
|
28
|
+
# CONTROLLER_NS controller namespace (default: sealed-secrets)
|
|
29
|
+
# CONTROLLER_NAME controller service name (default: sealed-secrets-controller)
|
|
30
|
+
# REFETCH_CERT=1 re-fetch the cert even if the cache exists
|
|
31
|
+
# MERGE=1 merge into OUT_FILE instead of regenerating it
|
|
32
|
+
#
|
|
33
|
+
# Staging:
|
|
34
|
+
# NAMESPACE=nexst-staging \
|
|
35
|
+
# ./scripts/seal-secret.sh kustomize/overlays/staging/sealedsecret.yaml
|
|
36
|
+
|
|
37
|
+
if [ -z "${BASH_VERSINFO:-}" ] || [ "${BASH_VERSINFO:-0}" -lt 4 ]; then
|
|
38
|
+
if [ -z "${_SEAL_REEXEC:-}" ]; then
|
|
39
|
+
for _b in /opt/homebrew/bin/bash /usr/local/bin/bash; do
|
|
40
|
+
[ -x "$_b" ] && exec env _SEAL_REEXEC=1 "$_b" "$0" "$@"
|
|
41
|
+
done
|
|
42
|
+
fi
|
|
43
|
+
echo "error: this script needs bash >= 4; install one with 'brew install bash'" >&2
|
|
44
|
+
echo " then run: ./scripts/seal-secret.sh $*" >&2
|
|
45
|
+
exit 1
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
set -euo pipefail
|
|
49
|
+
|
|
50
|
+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
51
|
+
|
|
52
|
+
OUT_FILE="${1:-$REPO_ROOT/kustomize/overlays/prod/sealedsecret.yaml}"
|
|
53
|
+
ENV_FILE="${ENV_FILE:-$REPO_ROOT/.env}"
|
|
54
|
+
NAMESPACE="${NAMESPACE:-nexst}"
|
|
55
|
+
SECRET_NAME="${SECRET_NAME:-nexst-api-secret}"
|
|
56
|
+
KEYS_FILE="${KEYS_FILE:-$REPO_ROOT/kustomize/secret.keys}"
|
|
57
|
+
CERT_FILE="${CERT_FILE:-$REPO_ROOT/kustomize/sealedsecrets.pem}"
|
|
58
|
+
CONTROLLER_NS="${CONTROLLER_NS:-sealed-secrets}"
|
|
59
|
+
CONTROLLER_NAME="${CONTROLLER_NAME:-sealed-secrets-controller}"
|
|
60
|
+
MERGE="${MERGE:-}"
|
|
61
|
+
|
|
62
|
+
for bin in kubectl kubeseal; do
|
|
63
|
+
command -v "$bin" >/dev/null || { echo "error: $bin not found in PATH" >&2; exit 1; }
|
|
64
|
+
done
|
|
65
|
+
[ -f "$ENV_FILE" ] || { echo "error: env file not found: $ENV_FILE" >&2; exit 1; }
|
|
66
|
+
if [ -n "$MERGE" ] && { [ "$OUT_FILE" = "-" ] || [ ! -f "$OUT_FILE" ]; }; then
|
|
67
|
+
echo "error: MERGE=1 needs an existing OUT_FILE" >&2; exit 1
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
# --- controller cert: explicit > cached > fetch once via kubectl ----------
|
|
71
|
+
if [ -n "${SEALED_SECRETS_CERT:-}" ]; then
|
|
72
|
+
CERT_FILE="$SEALED_SECRETS_CERT"
|
|
73
|
+
elif [ ! -f "$CERT_FILE" ] || [ -n "${REFETCH_CERT:-}" ]; then
|
|
74
|
+
kubectl get svc -n "$CONTROLLER_NS" "$CONTROLLER_NAME" >/dev/null 2>&1 || {
|
|
75
|
+
echo "error: service $CONTROLLER_NS/$CONTROLLER_NAME not reachable via kubectl." >&2
|
|
76
|
+
echo " set CONTROLLER_NS / CONTROLLER_NAME, or SEALED_SECRETS_CERT=<pem>." >&2
|
|
77
|
+
exit 1
|
|
78
|
+
}
|
|
79
|
+
echo "fetching public cert from $CONTROLLER_NS/$CONTROLLER_NAME ..." >&2
|
|
80
|
+
mkdir -p "$(dirname "$CERT_FILE")"
|
|
81
|
+
tmpc="$(mktemp "${CERT_FILE}.XXXXXX")"
|
|
82
|
+
trap 'rm -f "$tmpc"' EXIT
|
|
83
|
+
kubeseal --controller-namespace "$CONTROLLER_NS" --controller-name "$CONTROLLER_NAME" \
|
|
84
|
+
--fetch-cert > "$tmpc"
|
|
85
|
+
mv "$tmpc" "$CERT_FILE"
|
|
86
|
+
trap - EXIT
|
|
87
|
+
echo "cached $CERT_FILE (public key — commit it)" >&2
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
# --- optional key allowlist -------------------------------------------------
|
|
91
|
+
declare -A ALLOW=()
|
|
92
|
+
use_allow=0
|
|
93
|
+
if [ -f "$KEYS_FILE" ]; then
|
|
94
|
+
use_allow=1
|
|
95
|
+
while IFS= read -r k || [ -n "$k" ]; do
|
|
96
|
+
k="${k%%#*}"; k="$(printf '%s' "$k" | tr -d '[:space:]')"
|
|
97
|
+
[ -n "$k" ] && ALLOW["$k"]=1
|
|
98
|
+
done < "$KEYS_FILE"
|
|
99
|
+
echo "allowlist: $KEYS_FILE (${#ALLOW[@]} keys)" >&2
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
# --- env parsing ------------------------------------------------------------
|
|
103
|
+
declare -A VALUES=()
|
|
104
|
+
|
|
105
|
+
load_env_file() {
|
|
106
|
+
local file="$1" key val
|
|
107
|
+
[ -f "$file" ] || return 0
|
|
108
|
+
while IFS= read -r line || [ -n "$line" ]; do
|
|
109
|
+
line="${line#"${line%%[![:space:]]*}"}" # ltrim
|
|
110
|
+
[ -z "$line" ] && continue
|
|
111
|
+
case "$line" in \#*) continue ;; esac
|
|
112
|
+
line="${line#export }"
|
|
113
|
+
[ "${line%%=*}" = "$line" ] && continue # no '=' on the line
|
|
114
|
+
key="$(printf '%s' "${line%%=*}" | tr -d '[:space:]')"
|
|
115
|
+
[ -z "$key" ] && continue
|
|
116
|
+
val="${line#*=}"
|
|
117
|
+
if [ "${val#\"}" != "$val" ] && [ "${val%\"}" != "$val" ]; then
|
|
118
|
+
val="${val#\"}"; val="${val%\"}"
|
|
119
|
+
elif [ "${val#\'}" != "$val" ] && [ "${val%\'}" != "$val" ]; then
|
|
120
|
+
val="${val#\'}"; val="${val%\'}"
|
|
121
|
+
fi
|
|
122
|
+
VALUES["$key"]="$val"
|
|
123
|
+
done < "$file"
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
load_env_file "$ENV_FILE"
|
|
127
|
+
|
|
128
|
+
# --- select keys to seal ------------------------------------------------
|
|
129
|
+
declare -a LITERALS=() SEALED_KEYS=()
|
|
130
|
+
skipped_empty=0 skipped_filtered=0
|
|
131
|
+
for key in "${!VALUES[@]}"; do
|
|
132
|
+
if [ "$use_allow" = 1 ] && [ -z "${ALLOW[$key]+x}" ]; then
|
|
133
|
+
skipped_filtered=$((skipped_filtered + 1)); continue
|
|
134
|
+
fi
|
|
135
|
+
if [ -z "${VALUES[$key]}" ]; then
|
|
136
|
+
skipped_empty=$((skipped_empty + 1)); continue
|
|
137
|
+
fi
|
|
138
|
+
LITERALS+=(--from-literal="$key=${VALUES[$key]}")
|
|
139
|
+
SEALED_KEYS+=("$key")
|
|
140
|
+
done
|
|
141
|
+
[ "${#SEALED_KEYS[@]}" -gt 0 ] || { echo "error: no keys to seal" >&2; exit 1; }
|
|
142
|
+
|
|
143
|
+
raw_secret() {
|
|
144
|
+
kubectl create secret generic "$SECRET_NAME" \
|
|
145
|
+
--namespace "$NAMESPACE" "${LITERALS[@]}" \
|
|
146
|
+
--dry-run=client -o yaml
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
KS_ARGS=(--format yaml --scope strict --namespace "$NAMESPACE" --name "$SECRET_NAME" --cert "$CERT_FILE")
|
|
150
|
+
|
|
151
|
+
echo "sealing ${#SEALED_KEYS[@]} keys for $NAMESPACE/$SECRET_NAME:" >&2
|
|
152
|
+
for key in $(printf '%s\n' "${SEALED_KEYS[@]}" | sort); do
|
|
153
|
+
printf ' %s\n' "$key" >&2
|
|
154
|
+
done
|
|
155
|
+
[ "$skipped_filtered" -gt 0 ] && echo " (skipped $skipped_filtered not in allowlist)" >&2
|
|
156
|
+
[ "$skipped_empty" -gt 0 ] && echo " (skipped $skipped_empty empty values)" >&2
|
|
157
|
+
|
|
158
|
+
if [ -n "$MERGE" ]; then
|
|
159
|
+
raw_secret | kubeseal "${KS_ARGS[@]}" --merge-into "$OUT_FILE"
|
|
160
|
+
echo "merged into $OUT_FILE" >&2
|
|
161
|
+
elif [ "$OUT_FILE" = "-" ]; then
|
|
162
|
+
raw_secret | kubeseal "${KS_ARGS[@]}"
|
|
163
|
+
else
|
|
164
|
+
mkdir -p "$(dirname "$OUT_FILE")"
|
|
165
|
+
tmp="$(mktemp "${OUT_FILE}.XXXXXX")"
|
|
166
|
+
trap 'rm -f "$tmp"' EXIT
|
|
167
|
+
raw_secret | kubeseal "${KS_ARGS[@]}" > "$tmp"
|
|
168
|
+
mv "$tmp" "$OUT_FILE"
|
|
169
|
+
trap - EXIT
|
|
170
|
+
echo "wrote $OUT_FILE" >&2
|
|
171
|
+
fi
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
sonar.projectKey=nexst-api
|
|
2
|
+
|
|
3
|
+
sonar.sources=src
|
|
4
|
+
sonar.exclusions=src/generated/**,**/*.spec.ts
|
|
5
|
+
sonar.tests=src,tests
|
|
6
|
+
sonar.test.inclusions=**/*.spec.ts,**/*.e2e-spec.ts
|
|
7
|
+
|
|
8
|
+
sonar.javascript.lcov.reportPaths=coverage/lcov.info
|
|
9
|
+
sonar.typescript.tsconfigPath=tsconfig.json
|