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.
Files changed (303) hide show
  1. package/dist/index.js +1727 -0
  2. package/package.json +37 -0
  3. package/template/.agents/plugins/marketplace.json +21 -0
  4. package/template/.claude/settings.json +6 -0
  5. package/template/.claude/skills/frontend-design/LICENSE.txt +177 -0
  6. package/template/.claude/skills/frontend-design/SKILL.md +154 -0
  7. package/template/.claude/skills/theme-factory/SKILL.md +95 -0
  8. package/template/.claude/skills/theme-factory/references/schema.md +104 -0
  9. package/template/.claude/skills/theme-factory/scripts/theme.mjs +261 -0
  10. package/template/.dockerignore +43 -0
  11. package/template/.editorconfig +10 -0
  12. package/template/.github/workflows/ci.yaml +289 -0
  13. package/template/.github/workflows/publish-app.yaml +66 -0
  14. package/template/.github/workflows/publish.yaml +148 -0
  15. package/template/.github/workflows/snyk.yaml +34 -0
  16. package/template/.vscode/settings.json +8 -0
  17. package/template/AGENTS.md +42 -0
  18. package/template/Dockerfile.base +9 -0
  19. package/template/README.md +54 -0
  20. package/template/_gitignore +10 -0
  21. package/template/apps/api/.env.example +32 -0
  22. package/template/apps/api/AGENTS.md +44 -0
  23. package/template/apps/api/Dockerfile +69 -0
  24. package/template/apps/api/_gitignore +8 -0
  25. package/template/apps/api/knip.config.ts +22 -0
  26. package/template/apps/api/kustomize/base/application.properties +16 -0
  27. package/template/apps/api/kustomize/base/deployment.yaml +94 -0
  28. package/template/apps/api/kustomize/base/kustomization.yaml +17 -0
  29. package/template/apps/api/kustomize/base/service.yaml +10 -0
  30. package/template/apps/api/kustomize/overlays/prod/kustomization.yaml +17 -0
  31. package/template/apps/api/kustomize/overlays/staging/application.properties +1 -0
  32. package/template/apps/api/kustomize/overlays/staging/kustomization.yaml +21 -0
  33. package/template/apps/api/kustomize/secret.keys +14 -0
  34. package/template/apps/api/oxfmt.config.ts +8 -0
  35. package/template/apps/api/oxlint.config.ts +107 -0
  36. package/template/apps/api/package.json +79 -0
  37. package/template/apps/api/prisma/migrations/20260927000000_init/migration.sql +113 -0
  38. package/template/apps/api/prisma/migrations/migration_lock.toml +3 -0
  39. package/template/apps/api/prisma/schema.prisma +113 -0
  40. package/template/apps/api/prisma.config.ts +7 -0
  41. package/template/apps/api/scripts/auth.cli.ts +19 -0
  42. package/template/apps/api/scripts/seal-secret.sh +171 -0
  43. package/template/apps/api/sonar-project.properties +9 -0
  44. package/template/apps/api/src/app-info/app-info.dto.ts +10 -0
  45. package/template/apps/api/src/app-info/app-info.module.ts +7 -0
  46. package/template/apps/api/src/app-info/app-info.resolver.spec.ts +14 -0
  47. package/template/apps/api/src/app-info/app-info.resolver.ts +21 -0
  48. package/template/apps/api/src/app.module.spec.ts +93 -0
  49. package/template/apps/api/src/app.module.ts +77 -0
  50. package/template/apps/api/src/auth/auth.guard.spec.ts +90 -0
  51. package/template/apps/api/src/auth/auth.guard.ts +97 -0
  52. package/template/apps/api/src/auth/auth.ts +129 -0
  53. package/template/apps/api/src/auth/decorator/allow-anonymous.decorator.ts +17 -0
  54. package/template/apps/api/src/auth/decorator/is-email-verified.decorator.spec.ts +41 -0
  55. package/template/apps/api/src/auth/decorator/is-email-verified.decorator.ts +20 -0
  56. package/template/apps/api/src/auth/decorator/roles.decorator.ts +27 -0
  57. package/template/apps/api/src/auth/testing/make-session.ts +31 -0
  58. package/template/apps/api/src/auth/types/auth.types.ts +27 -0
  59. package/template/apps/api/src/bootstrap/register-graphql-enums.ts +12 -0
  60. package/template/apps/api/src/bootstrap/security.ts +24 -0
  61. package/template/apps/api/src/bootstrap/swagger.ts +28 -0
  62. package/template/apps/api/src/config/app.config.spec.ts +22 -0
  63. package/template/apps/api/src/config/app.config.ts +122 -0
  64. package/template/apps/api/src/config/graphql-depth-limit.ts +103 -0
  65. package/template/apps/api/src/config/graphql.config.ts +77 -0
  66. package/template/apps/api/src/config/resources/app.yaml +29 -0
  67. package/template/apps/api/src/errors/app-parse-uuid.pipe.ts +27 -0
  68. package/template/apps/api/src/errors/app.exception.ts +47 -0
  69. package/template/apps/api/src/errors/error-code.enum.ts +54 -0
  70. package/template/apps/api/src/errors/http-status-fallback-code.util.ts +27 -0
  71. package/template/apps/api/src/errors/validation-exception.factory.ts +43 -0
  72. package/template/apps/api/src/health/health.controller.spec.ts +82 -0
  73. package/template/apps/api/src/health/health.controller.ts +57 -0
  74. package/template/apps/api/src/health/health.module.ts +12 -0
  75. package/template/apps/api/src/logging/context-logger.ts +18 -0
  76. package/template/apps/api/src/logging/logger.config.ts +23 -0
  77. package/template/apps/api/src/logging/logging.module.ts +22 -0
  78. package/template/apps/api/src/mail/mail.module.ts +12 -0
  79. package/template/apps/api/src/mail/mail.service.spec.ts +47 -0
  80. package/template/apps/api/src/mail/mail.service.ts +123 -0
  81. package/template/apps/api/src/main.ts +41 -0
  82. package/template/apps/api/src/prisma/prisma.module.ts +12 -0
  83. package/template/apps/api/src/prisma/prisma.service.spec.ts +136 -0
  84. package/template/apps/api/src/prisma/prisma.service.ts +138 -0
  85. package/template/apps/api/src/queue/dto/enqueue-example.input.ts +11 -0
  86. package/template/apps/api/src/queue/example-queue.resolver.ts +14 -0
  87. package/template/apps/api/src/queue/example-queue.service.spec.ts +21 -0
  88. package/template/apps/api/src/queue/example-queue.service.ts +32 -0
  89. package/template/apps/api/src/queue/queue.module.ts +27 -0
  90. package/template/apps/api/src/r2/r2.module.ts +12 -0
  91. package/template/apps/api/src/r2/r2.service.spec.ts +117 -0
  92. package/template/apps/api/src/r2/r2.service.ts +131 -0
  93. package/template/apps/api/src/user/dto/user.dto.ts +26 -0
  94. package/template/apps/api/src/user/user.module.ts +10 -0
  95. package/template/apps/api/src/user/user.resolver.spec.ts +18 -0
  96. package/template/apps/api/src/user/user.resolver.ts +24 -0
  97. package/template/apps/api/src/utils/catch-everything-exception.filter.spec.ts +292 -0
  98. package/template/apps/api/src/utils/catch-everything-exception.filter.ts +70 -0
  99. package/template/apps/api/src/utils/error-handler.util.spec.ts +20 -0
  100. package/template/apps/api/src/utils/error-handler.util.ts +12 -0
  101. package/template/apps/api/src/utils/escape-like-pattern.util.spec.ts +29 -0
  102. package/template/apps/api/src/utils/escape-like-pattern.util.ts +11 -0
  103. package/template/apps/api/src/utils/pagination.input.ts +20 -0
  104. package/template/apps/api/src/utils/prisma-exception.filter.spec.ts +186 -0
  105. package/template/apps/api/src/utils/prisma-exception.filter.ts +45 -0
  106. package/template/apps/api/src/utils/prisma-graphql-exception.filter.spec.ts +216 -0
  107. package/template/apps/api/src/utils/prisma-graphql-exception.filter.ts +43 -0
  108. package/template/apps/api/tests/app.e2e-spec.ts +35 -0
  109. package/template/apps/api/tests/auth.e2e-spec.ts +38 -0
  110. package/template/apps/api/tests/helpers/api.ts +27 -0
  111. package/template/apps/api/tsconfig.json +11 -0
  112. package/template/apps/api/vitest.config.e2e.ts +16 -0
  113. package/template/apps/api/vitest.config.ts +40 -0
  114. package/template/apps/web/.env.example +7 -0
  115. package/template/apps/web/.storybook/decorators.tsx +38 -0
  116. package/template/apps/web/.storybook/main.ts +17 -0
  117. package/template/apps/web/.storybook/preview.ts +38 -0
  118. package/template/apps/web/.storybook/vitest.setup.ts +5 -0
  119. package/template/apps/web/AGENTS.md +29 -0
  120. package/template/apps/web/Dockerfile +49 -0
  121. package/template/apps/web/_gitignore +16 -0
  122. package/template/apps/web/knip.config.ts +15 -0
  123. package/template/apps/web/kustomize/base/application.properties +5 -0
  124. package/template/apps/web/kustomize/base/deployment.yaml +60 -0
  125. package/template/apps/web/kustomize/base/kustomization.yaml +17 -0
  126. package/template/apps/web/kustomize/base/service.yaml +10 -0
  127. package/template/apps/web/kustomize/overlays/prod/kustomization.yaml +12 -0
  128. package/template/apps/web/kustomize/overlays/staging/application.properties +2 -0
  129. package/template/apps/web/kustomize/overlays/staging/kustomization.yaml +18 -0
  130. package/template/apps/web/messages/de.json +124 -0
  131. package/template/apps/web/messages/en.json +124 -0
  132. package/template/apps/web/next-env.d.ts +7 -0
  133. package/template/apps/web/next.config.ts +39 -0
  134. package/template/apps/web/oxfmt.config.ts +9 -0
  135. package/template/apps/web/oxlint.config.ts +36 -0
  136. package/template/apps/web/package.json +80 -0
  137. package/template/apps/web/playwright.config.ts +34 -0
  138. package/template/apps/web/postcss.config.mjs +7 -0
  139. package/template/apps/web/sonar-project.properties +9 -0
  140. package/template/apps/web/src/app/[locale]/(auth)/recovery/page.tsx +22 -0
  141. package/template/apps/web/src/app/[locale]/(auth)/reset-password/page.tsx +29 -0
  142. package/template/apps/web/src/app/[locale]/(auth)/sign-in/page.tsx +24 -0
  143. package/template/apps/web/src/app/[locale]/(auth)/sign-up/page.tsx +24 -0
  144. package/template/apps/web/src/app/[locale]/(auth)/verify/page.tsx +24 -0
  145. package/template/apps/web/src/app/[locale]/dashboard/page.tsx +34 -0
  146. package/template/apps/web/src/app/[locale]/layout.tsx +60 -0
  147. package/template/apps/web/src/app/[locale]/page.tsx +23 -0
  148. package/template/apps/web/src/app/api/health/route.ts +3 -0
  149. package/template/apps/web/src/app/global-error.tsx +19 -0
  150. package/template/apps/web/src/app/globals.css +98 -0
  151. package/template/apps/web/src/app/icon.svg +1 -0
  152. package/template/apps/web/src/app/robots.ts +15 -0
  153. package/template/apps/web/src/app/sitemap.ts +24 -0
  154. package/template/apps/web/src/components/alert/alert.stories.tsx +25 -0
  155. package/template/apps/web/src/components/alert/alert.tsx +50 -0
  156. package/template/apps/web/src/components/avatar/avatar.stories.tsx +116 -0
  157. package/template/apps/web/src/components/avatar/avatar.tsx +88 -0
  158. package/template/apps/web/src/components/button/button-variants.ts +28 -0
  159. package/template/apps/web/src/components/button/button.stories.tsx +33 -0
  160. package/template/apps/web/src/components/button/button.tsx +32 -0
  161. package/template/apps/web/src/components/checkbox/checkbox.stories.tsx +312 -0
  162. package/template/apps/web/src/components/checkbox/checkbox.tsx +38 -0
  163. package/template/apps/web/src/components/dialog/danger-panel.stories.tsx +41 -0
  164. package/template/apps/web/src/components/dialog/danger-panel.tsx +32 -0
  165. package/template/apps/web/src/components/dialog/dialog.stories.tsx +239 -0
  166. package/template/apps/web/src/components/dialog/dialog.tsx +136 -0
  167. package/template/apps/web/src/components/dialog-buttons.stories.tsx +55 -0
  168. package/template/apps/web/src/components/dialog-buttons.tsx +48 -0
  169. package/template/apps/web/src/components/drawer/drawer.stories.tsx +251 -0
  170. package/template/apps/web/src/components/drawer/drawer.tsx +120 -0
  171. package/template/apps/web/src/components/dropdown-menu/dropdown-menu.stories.tsx +329 -0
  172. package/template/apps/web/src/components/dropdown-menu/dropdown-menu.tsx +263 -0
  173. package/template/apps/web/src/components/empty-state.stories.tsx +49 -0
  174. package/template/apps/web/src/components/empty-state.tsx +33 -0
  175. package/template/apps/web/src/components/field-group.stories.tsx +47 -0
  176. package/template/apps/web/src/components/field-group.tsx +20 -0
  177. package/template/apps/web/src/components/field-label.stories.tsx +34 -0
  178. package/template/apps/web/src/components/field-label.tsx +28 -0
  179. package/template/apps/web/src/components/icons.tsx +25 -0
  180. package/template/apps/web/src/components/input/input-otp.stories.tsx +86 -0
  181. package/template/apps/web/src/components/input/input-otp.tsx +184 -0
  182. package/template/apps/web/src/components/input/input.stories.tsx +35 -0
  183. package/template/apps/web/src/components/input/input.tsx +180 -0
  184. package/template/apps/web/src/components/popover/popover.stories.tsx +479 -0
  185. package/template/apps/web/src/components/popover/popover.tsx +45 -0
  186. package/template/apps/web/src/components/responsive-dialog/responsive-dialog.stories.tsx +307 -0
  187. package/template/apps/web/src/components/responsive-dialog/responsive-dialog.tsx +96 -0
  188. package/template/apps/web/src/components/searchbar/searchbar.stories.tsx +59 -0
  189. package/template/apps/web/src/components/searchbar/searchbar.tsx +64 -0
  190. package/template/apps/web/src/components/select/select.stories.tsx +293 -0
  191. package/template/apps/web/src/components/select/select.tsx +445 -0
  192. package/template/apps/web/src/components/skeleton/skeleton.stories.tsx +76 -0
  193. package/template/apps/web/src/components/skeleton/skeleton.tsx +13 -0
  194. package/template/apps/web/src/components/spinner/spinner.tsx +18 -0
  195. package/template/apps/web/src/components/switch/switch.stories.tsx +71 -0
  196. package/template/apps/web/src/components/switch/switch.tsx +35 -0
  197. package/template/apps/web/src/components/tabs/tabs.stories.tsx +209 -0
  198. package/template/apps/web/src/components/tabs/tabs.tsx +69 -0
  199. package/template/apps/web/src/components/tabs/url-tabs.stories.tsx +68 -0
  200. package/template/apps/web/src/components/tabs/url-tabs.tsx +42 -0
  201. package/template/apps/web/src/components/textarea/textarea.stories.tsx +144 -0
  202. package/template/apps/web/src/components/textarea/textarea.tsx +115 -0
  203. package/template/apps/web/src/components/toast/toaster.tsx +38 -0
  204. package/template/apps/web/src/components/tooltip/tooltip.stories.tsx +237 -0
  205. package/template/apps/web/src/components/tooltip/tooltip.tsx +49 -0
  206. package/template/apps/web/src/config/csp.ts +59 -0
  207. package/template/apps/web/src/config/routes.ts +14 -0
  208. package/template/apps/web/src/config/site.ts +5 -0
  209. package/template/apps/web/src/env.ts +15 -0
  210. package/template/apps/web/src/features/auth/auth-card.tsx +20 -0
  211. package/template/apps/web/src/features/auth/auth-divider.tsx +14 -0
  212. package/template/apps/web/src/features/auth/google-auth-button.tsx +49 -0
  213. package/template/apps/web/src/features/auth/google-one-tap.tsx +23 -0
  214. package/template/apps/web/src/features/auth/lib/auth-error.test.ts +22 -0
  215. package/template/apps/web/src/features/auth/lib/auth-error.ts +16 -0
  216. package/template/apps/web/src/features/auth/lib/return-to.test.ts +41 -0
  217. package/template/apps/web/src/features/auth/lib/return-to.ts +19 -0
  218. package/template/apps/web/src/features/auth/lib/validator.test.ts +45 -0
  219. package/template/apps/web/src/features/auth/lib/validator.ts +49 -0
  220. package/template/apps/web/src/features/auth/passkey-settings.tsx +106 -0
  221. package/template/apps/web/src/features/auth/recovery-form.tsx +61 -0
  222. package/template/apps/web/src/features/auth/resend-verification-button.tsx +36 -0
  223. package/template/apps/web/src/features/auth/reset-password-form.tsx +62 -0
  224. package/template/apps/web/src/features/auth/sign-in-form.stories.tsx +22 -0
  225. package/template/apps/web/src/features/auth/sign-in-form.tsx +116 -0
  226. package/template/apps/web/src/features/auth/sign-out-button.tsx +26 -0
  227. package/template/apps/web/src/features/auth/sign-up-form.tsx +107 -0
  228. package/template/apps/web/src/features/shared/provider.tsx +16 -0
  229. package/template/apps/web/src/features/shared/site-header.tsx +48 -0
  230. package/template/apps/web/src/features/shared/theme-toggle.tsx +24 -0
  231. package/template/apps/web/src/i18n/navigation.ts +5 -0
  232. package/template/apps/web/src/i18n/request.ts +21 -0
  233. package/template/apps/web/src/i18n/routing.ts +6 -0
  234. package/template/apps/web/src/lib/auth-client.ts +19 -0
  235. package/template/apps/web/src/lib/auth-server.ts +46 -0
  236. package/template/apps/web/src/lib/auth.ts +8 -0
  237. package/template/apps/web/src/lib/codegen.ts +30 -0
  238. package/template/apps/web/src/lib/graphql-client.ts +29 -0
  239. package/template/apps/web/src/lib/graphql-server.ts +28 -0
  240. package/template/apps/web/src/lib/graphql-utils.test.ts +32 -0
  241. package/template/apps/web/src/lib/graphql-utils.ts +76 -0
  242. package/template/apps/web/src/lib/hooks/use-media-query.ts +21 -0
  243. package/template/apps/web/src/lib/metadata.ts +17 -0
  244. package/template/apps/web/src/lib/network-error.test.ts +30 -0
  245. package/template/apps/web/src/lib/network-error.ts +17 -0
  246. package/template/apps/web/src/lib/proxy-utils.ts +42 -0
  247. package/template/apps/web/src/lib/query-client.ts +29 -0
  248. package/template/apps/web/src/lib/utils.ts +5 -0
  249. package/template/apps/web/src/proxy.ts +57 -0
  250. package/template/apps/web/test-results/.last-run.json +4 -0
  251. package/template/apps/web/tests/auth.spec.ts +17 -0
  252. package/template/apps/web/tests/csp-theme-script.spec.ts +37 -0
  253. package/template/apps/web/tests/home.spec.ts +25 -0
  254. package/template/apps/web/tsconfig.build.json +4 -0
  255. package/template/apps/web/tsconfig.json +28 -0
  256. package/template/apps/web/vitest.config.ts +40 -0
  257. package/template/apps/web/vitest.shims.d.ts +1 -0
  258. package/template/apps/worker/.env.example +4 -0
  259. package/template/apps/worker/AGENTS.md +11 -0
  260. package/template/apps/worker/Dockerfile +35 -0
  261. package/template/apps/worker/knip.config.ts +3 -0
  262. package/template/apps/worker/kustomize/base/application.properties +4 -0
  263. package/template/apps/worker/kustomize/base/deployment.yaml +62 -0
  264. package/template/apps/worker/kustomize/base/kustomization.yaml +16 -0
  265. package/template/apps/worker/kustomize/overlays/prod/kustomization.yaml +12 -0
  266. package/template/apps/worker/kustomize/overlays/staging/application.properties +1 -0
  267. package/template/apps/worker/kustomize/overlays/staging/kustomization.yaml +18 -0
  268. package/template/apps/worker/oxfmt.config.ts +8 -0
  269. package/template/apps/worker/oxlint.config.ts +15 -0
  270. package/template/apps/worker/package.json +33 -0
  271. package/template/apps/worker/sonar-project.properties +8 -0
  272. package/template/apps/worker/src/env.ts +13 -0
  273. package/template/apps/worker/src/lib/graceful-shutdown.ts +27 -0
  274. package/template/apps/worker/src/lib/logger.ts +20 -0
  275. package/template/apps/worker/src/main.ts +17 -0
  276. package/template/apps/worker/src/processors/example-processor.ts +15 -0
  277. package/template/apps/worker/src/worker/redis.ts +19 -0
  278. package/template/apps/worker/src/worker/worker.ts +37 -0
  279. package/template/apps/worker/tests/example-processor.test.ts +13 -0
  280. package/template/apps/worker/tests/graceful-shutdown.test.ts +34 -0
  281. package/template/apps/worker/tsconfig.json +4 -0
  282. package/template/apps/worker/vitest.config.ts +13 -0
  283. package/template/bun.lock +3053 -0
  284. package/template/bunfig.toml +2 -0
  285. package/template/compose.yaml +53 -0
  286. package/template/docker-bake.hcl +86 -0
  287. package/template/package.json +47 -0
  288. package/template/packages/lint/knip.ts +40 -0
  289. package/template/packages/lint/oxfmt.ts +8 -0
  290. package/template/packages/lint/oxlint.ts +136 -0
  291. package/template/packages/lint/package.json +13 -0
  292. package/template/packages/queues/oxfmt.config.ts +8 -0
  293. package/template/packages/queues/oxlint.config.ts +7 -0
  294. package/template/packages/queues/package.json +15 -0
  295. package/template/packages/queues/src/example-job.ts +11 -0
  296. package/template/packages/queues/src/index.ts +3 -0
  297. package/template/packages/queues/src/queue-names.ts +2 -0
  298. package/template/packages/queues/src/redis-connection.ts +27 -0
  299. package/template/packages/queues/tsconfig.json +4 -0
  300. package/template/packages/tsconfig/base.json +23 -0
  301. package/template/packages/tsconfig/node.json +11 -0
  302. package/template/packages/tsconfig/package.json +4 -0
  303. 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,3 @@
1
+ # Please do not edit this file manually
2
+ # It should be added in your version-control system (e.g., Git)
3
+ provider = "postgresql"
@@ -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,7 @@
1
+ import { defineConfig } from "prisma/config";
2
+
3
+ export default defineConfig({
4
+ datasource: {
5
+ url: process.env.DATABASE_URL,
6
+ },
7
+ });
@@ -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
@@ -0,0 +1,10 @@
1
+ import { Field, ObjectType } from "@nestjs/graphql";
2
+
3
+ @ObjectType("AppInfo")
4
+ export class AppInfoDto {
5
+ @Field()
6
+ title: string;
7
+
8
+ @Field()
9
+ version: string;
10
+ }
@@ -0,0 +1,7 @@
1
+ import { Module } from "@nestjs/common";
2
+ import { AppInfoResolver } from "#src/app-info/app-info.resolver.js";
3
+
4
+ @Module({
5
+ providers: [AppInfoResolver],
6
+ })
7
+ export class AppInfoModule {}