@podosoft/podokit 0.1.1 → 0.3.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 (326) hide show
  1. package/README.md +17 -3
  2. package/dist/add.d.ts +40 -0
  3. package/dist/add.js +114 -0
  4. package/dist/create.d.ts +2 -1
  5. package/dist/create.js +3 -2
  6. package/dist/index.js +39 -1
  7. package/dist/prompt.d.ts +0 -1
  8. package/dist/prompt.js +6 -7
  9. package/dist/templates/fullstack-nest-svelte/README.md +20 -8
  10. package/dist/templates/fullstack-nest-svelte/apps/api/package.json +20 -8
  11. package/dist/templates/fullstack-nest-svelte/apps/api/src/app.module.ts +14 -1
  12. package/dist/templates/fullstack-nest-svelte/apps/api/src/config/env.validation.ts +20 -15
  13. package/dist/templates/fullstack-nest-svelte/apps/api/src/database/data-source.ts +19 -0
  14. package/dist/templates/fullstack-nest-svelte/apps/api/src/health/health.controller.ts +15 -5
  15. package/dist/templates/fullstack-nest-svelte/apps/api/src/main.ts +8 -0
  16. package/dist/templates/fullstack-nest-svelte/apps/web/components.json +1 -1
  17. package/dist/templates/fullstack-nest-svelte/apps/web/package.json +10 -2
  18. package/dist/templates/fullstack-nest-svelte/apps/web/src/app.css +72 -8
  19. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/alert/alert-action.svelte +20 -0
  20. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/alert/alert-description.svelte +23 -0
  21. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/alert/alert-title.svelte +23 -0
  22. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/alert/alert.svelte +43 -0
  23. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/alert/index.ts +17 -0
  24. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/avatar/avatar-badge.svelte +26 -0
  25. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/avatar/avatar-fallback.svelte +20 -0
  26. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/avatar/avatar-group-count.svelte +23 -0
  27. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/avatar/avatar-group.svelte +23 -0
  28. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/avatar/avatar-image.svelte +17 -0
  29. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/avatar/avatar.svelte +26 -0
  30. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/avatar/index.ts +22 -0
  31. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/badge/badge.svelte +49 -0
  32. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/badge/index.ts +2 -0
  33. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/button/button.svelte +82 -0
  34. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/button/index.ts +17 -0
  35. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card-action.svelte +23 -0
  36. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card-content.svelte +20 -0
  37. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card-description.svelte +20 -0
  38. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card-footer.svelte +20 -0
  39. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card-header.svelte +23 -0
  40. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card-title.svelte +20 -0
  41. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card.svelte +22 -0
  42. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/index.ts +25 -0
  43. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/checkbox/checkbox.svelte +39 -0
  44. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/checkbox/index.ts +6 -0
  45. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog-close.svelte +11 -0
  46. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog-content.svelte +48 -0
  47. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog-description.svelte +17 -0
  48. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog-footer.svelte +32 -0
  49. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog-header.svelte +20 -0
  50. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog-overlay.svelte +17 -0
  51. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog-portal.svelte +7 -0
  52. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog-title.svelte +17 -0
  53. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog-trigger.svelte +11 -0
  54. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog.svelte +7 -0
  55. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/index.ts +34 -0
  56. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte +16 -0
  57. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +44 -0
  58. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte +31 -0
  59. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte +22 -0
  60. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte +7 -0
  61. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte +27 -0
  62. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte +24 -0
  63. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte +7 -0
  64. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte +16 -0
  65. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +34 -0
  66. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte +17 -0
  67. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte +20 -0
  68. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +17 -0
  69. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +29 -0
  70. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte +7 -0
  71. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +7 -0
  72. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte +7 -0
  73. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/index.ts +54 -0
  74. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/input/index.ts +7 -0
  75. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/input/input.svelte +48 -0
  76. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/label/index.ts +7 -0
  77. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/label/label.svelte +20 -0
  78. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/index.ts +37 -0
  79. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-content.svelte +45 -0
  80. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-group-heading.svelte +21 -0
  81. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-group.svelte +17 -0
  82. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-item.svelte +40 -0
  83. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-label.svelte +20 -0
  84. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-portal.svelte +7 -0
  85. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-scroll-down-button.svelte +20 -0
  86. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-scroll-up-button.svelte +20 -0
  87. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-separator.svelte +18 -0
  88. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-trigger.svelte +29 -0
  89. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select.svelte +11 -0
  90. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/separator/index.ts +7 -0
  91. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/separator/separator.svelte +23 -0
  92. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/skeleton/index.ts +7 -0
  93. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/skeleton/skeleton.svelte +17 -0
  94. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/sonner/index.ts +1 -0
  95. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/sonner/sonner.svelte +34 -0
  96. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/table/index.ts +28 -0
  97. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/table/table-body.svelte +15 -0
  98. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/table/table-caption.svelte +20 -0
  99. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/table/table-cell.svelte +15 -0
  100. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/table/table-footer.svelte +20 -0
  101. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/table/table-head.svelte +15 -0
  102. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/table/table-header.svelte +20 -0
  103. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/table/table-row.svelte +15 -0
  104. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/table/table.svelte +17 -0
  105. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tabs/index.ts +18 -0
  106. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tabs/tabs-content.svelte +17 -0
  107. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tabs/tabs-list.svelte +40 -0
  108. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tabs/tabs-trigger.svelte +23 -0
  109. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tabs/tabs.svelte +19 -0
  110. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tooltip/index.ts +19 -0
  111. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tooltip/tooltip-content.svelte +52 -0
  112. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tooltip/tooltip-portal.svelte +7 -0
  113. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tooltip/tooltip-provider.svelte +7 -0
  114. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tooltip/tooltip-trigger.svelte +7 -0
  115. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tooltip/tooltip.svelte +7 -0
  116. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/i18n/ko.ts +5 -2
  117. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/utils.ts +11 -0
  118. package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/+page.svelte +19 -12
  119. package/dist/templates/modules/api-key-auth/files/apps/api/src/api-key/api-key.decorator.ts +9 -0
  120. package/dist/templates/modules/api-key-auth/files/apps/api/src/api-key/api-key.guard.ts +29 -0
  121. package/dist/templates/modules/api-key-auth/files/apps/api/src/api-key/api-key.module.ts +9 -0
  122. package/dist/templates/modules/api-key-auth/files/apps/api/src/machine/machine.controller.ts +14 -0
  123. package/dist/templates/modules/api-key-auth/module.manifest.json +19 -0
  124. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-log.entity.ts +25 -0
  125. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.controller.ts +16 -0
  126. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.interceptor.ts +39 -0
  127. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.module.ts +13 -0
  128. package/dist/templates/modules/audit-log/files/apps/api/src/migrations/1720300000000-InitAuditLogs.ts +25 -0
  129. package/dist/templates/modules/audit-log/module.manifest.json +16 -0
  130. package/dist/templates/modules/auth/files/apps/api/src/account/account.controller.ts +13 -0
  131. package/dist/templates/modules/auth/files/apps/api/src/account/account.module.ts +5 -0
  132. package/dist/templates/modules/auth/files/apps/api/src/auth/auth.ts +36 -0
  133. package/dist/templates/modules/auth/files/apps/api/src/health/health.controller.ts +26 -0
  134. package/dist/templates/modules/auth/module.manifest.json +70 -0
  135. package/dist/templates/modules/bullmq/files/apps/api/src/jobs/demo.processor.ts +12 -0
  136. package/dist/templates/modules/bullmq/files/apps/api/src/jobs/dto/create-job.dto.ts +9 -0
  137. package/dist/templates/modules/bullmq/files/apps/api/src/jobs/jobs.controller.ts +29 -0
  138. package/dist/templates/modules/bullmq/files/apps/api/src/jobs/jobs.module.ts +15 -0
  139. package/dist/templates/modules/bullmq/files/apps/api/src/jobs/queue.ts +8 -0
  140. package/dist/templates/modules/bullmq/files/apps/api/src/jobs/worker.module.ts +20 -0
  141. package/dist/templates/modules/bullmq/files/apps/api/src/main-worker.ts +14 -0
  142. package/dist/templates/modules/bullmq/files/infra/docker/worker.compose.example.yml +18 -0
  143. package/dist/templates/modules/bullmq/files/infra/k3s/worker-deployment.yaml +22 -0
  144. package/dist/templates/modules/bullmq/module.manifest.json +36 -0
  145. package/dist/templates/modules/file-upload/files/apps/api/src/files/files.controller.ts +29 -0
  146. package/dist/templates/modules/file-upload/files/apps/api/src/files/files.module.ts +9 -0
  147. package/dist/templates/modules/file-upload/module.manifest.json +19 -0
  148. package/dist/templates/modules/job-progress/files/apps/api/src/progress/dto/start-job.dto.ts +11 -0
  149. package/dist/templates/modules/job-progress/files/apps/api/src/progress/job-progress.module.ts +13 -0
  150. package/dist/templates/modules/job-progress/files/apps/api/src/progress/progress.bridge.ts +19 -0
  151. package/dist/templates/modules/job-progress/files/apps/api/src/progress/progress.controller.ts +17 -0
  152. package/dist/templates/modules/job-progress/files/apps/api/src/progress/progress.processor.ts +25 -0
  153. package/dist/templates/modules/job-progress/module.manifest.json +23 -0
  154. package/dist/templates/modules/logging/files/apps/api/src/logging/logging.module.ts +29 -0
  155. package/dist/templates/modules/logging/module.manifest.json +27 -0
  156. package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/dto/put-object.dto.ts +8 -0
  157. package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/storage.config.ts +31 -0
  158. package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/storage.controller.ts +29 -0
  159. package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/storage.module.ts +11 -0
  160. package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/storage.service.ts +37 -0
  161. package/dist/templates/modules/object-storage-s3/files/infra/docker/minio.compose.yml +33 -0
  162. package/dist/templates/modules/object-storage-s3/module.manifest.json +31 -0
  163. package/dist/templates/modules/rate-limit/files/apps/api/src/rate-limit/rate-limit.module.ts +26 -0
  164. package/dist/templates/modules/rate-limit/module.manifest.json +25 -0
  165. package/dist/templates/modules/redis/files/apps/api/src/redis/cache.controller.ts +21 -0
  166. package/dist/templates/modules/redis/files/apps/api/src/redis/dto/set-cache.dto.ts +14 -0
  167. package/dist/templates/modules/redis/files/apps/api/src/redis/redis.module.ts +12 -0
  168. package/dist/templates/modules/redis/files/apps/api/src/redis/redis.service.ts +43 -0
  169. package/dist/templates/modules/redis/module.manifest.json +21 -0
  170. package/dist/templates/modules/sse/files/apps/api/src/events/dto/publish-event.dto.ts +8 -0
  171. package/dist/templates/modules/sse/files/apps/api/src/events/events.controller.ts +25 -0
  172. package/dist/templates/modules/sse/files/apps/api/src/events/events.module.ts +12 -0
  173. package/dist/templates/modules/sse/files/apps/api/src/events/events.service.ts +17 -0
  174. package/dist/templates/modules/sse/module.manifest.json +16 -0
  175. package/dist/templates/todo/README.md +40 -0
  176. package/dist/templates/todo/apps/api/Dockerfile +22 -0
  177. package/dist/templates/todo/apps/api/nest-cli.json +5 -0
  178. package/dist/templates/todo/apps/api/package.json +44 -0
  179. package/dist/templates/todo/apps/api/src/app.module.ts +22 -0
  180. package/dist/templates/todo/apps/api/src/common/all-exceptions.filter.ts +43 -0
  181. package/dist/templates/todo/apps/api/src/common/app-exception.ts +12 -0
  182. package/dist/templates/todo/apps/api/src/config/env.validation.ts +23 -0
  183. package/dist/templates/todo/apps/api/src/database/data-source.ts +19 -0
  184. package/dist/templates/todo/apps/api/src/health/health.controller.ts +23 -0
  185. package/dist/templates/todo/apps/api/src/health/health.module.ts +7 -0
  186. package/dist/templates/todo/apps/api/src/main.ts +29 -0
  187. package/dist/templates/todo/apps/api/src/migrations/1720100000000-InitTodos.ts +22 -0
  188. package/dist/templates/todo/apps/api/src/todos/dto/create-todo.dto.ts +10 -0
  189. package/dist/templates/todo/apps/api/src/todos/dto/update-todo.dto.ts +10 -0
  190. package/dist/templates/todo/apps/api/src/todos/todo.entity.ts +16 -0
  191. package/dist/templates/todo/apps/api/src/todos/todos.controller.ts +38 -0
  192. package/dist/templates/todo/apps/api/src/todos/todos.module.ts +12 -0
  193. package/dist/templates/todo/apps/api/src/todos/todos.service.ts +41 -0
  194. package/dist/templates/todo/apps/api/test/health.e2e-spec.ts +23 -0
  195. package/dist/templates/todo/apps/api/tsconfig.json +21 -0
  196. package/dist/templates/todo/apps/web/Dockerfile +22 -0
  197. package/dist/templates/todo/apps/web/components.json +15 -0
  198. package/dist/templates/todo/apps/web/package.json +36 -0
  199. package/dist/templates/todo/apps/web/src/app.css +81 -0
  200. package/dist/templates/todo/apps/web/src/app.d.ts +11 -0
  201. package/dist/templates/todo/apps/web/src/app.html +11 -0
  202. package/dist/templates/todo/apps/web/src/lib/components/ui/alert/alert-action.svelte +20 -0
  203. package/dist/templates/todo/apps/web/src/lib/components/ui/alert/alert-description.svelte +23 -0
  204. package/dist/templates/todo/apps/web/src/lib/components/ui/alert/alert-title.svelte +23 -0
  205. package/dist/templates/todo/apps/web/src/lib/components/ui/alert/alert.svelte +43 -0
  206. package/dist/templates/todo/apps/web/src/lib/components/ui/alert/index.ts +17 -0
  207. package/dist/templates/todo/apps/web/src/lib/components/ui/avatar/avatar-badge.svelte +26 -0
  208. package/dist/templates/todo/apps/web/src/lib/components/ui/avatar/avatar-fallback.svelte +20 -0
  209. package/dist/templates/todo/apps/web/src/lib/components/ui/avatar/avatar-group-count.svelte +23 -0
  210. package/dist/templates/todo/apps/web/src/lib/components/ui/avatar/avatar-group.svelte +23 -0
  211. package/dist/templates/todo/apps/web/src/lib/components/ui/avatar/avatar-image.svelte +17 -0
  212. package/dist/templates/todo/apps/web/src/lib/components/ui/avatar/avatar.svelte +26 -0
  213. package/dist/templates/todo/apps/web/src/lib/components/ui/avatar/index.ts +22 -0
  214. package/dist/templates/todo/apps/web/src/lib/components/ui/badge/badge.svelte +49 -0
  215. package/dist/templates/todo/apps/web/src/lib/components/ui/badge/index.ts +2 -0
  216. package/dist/templates/todo/apps/web/src/lib/components/ui/button/button.svelte +82 -0
  217. package/dist/templates/todo/apps/web/src/lib/components/ui/button/index.ts +17 -0
  218. package/dist/templates/todo/apps/web/src/lib/components/ui/card/card-action.svelte +23 -0
  219. package/dist/templates/todo/apps/web/src/lib/components/ui/card/card-content.svelte +20 -0
  220. package/dist/templates/todo/apps/web/src/lib/components/ui/card/card-description.svelte +20 -0
  221. package/dist/templates/todo/apps/web/src/lib/components/ui/card/card-footer.svelte +20 -0
  222. package/dist/templates/todo/apps/web/src/lib/components/ui/card/card-header.svelte +23 -0
  223. package/dist/templates/todo/apps/web/src/lib/components/ui/card/card-title.svelte +20 -0
  224. package/dist/templates/todo/apps/web/src/lib/components/ui/card/card.svelte +22 -0
  225. package/dist/templates/todo/apps/web/src/lib/components/ui/card/index.ts +25 -0
  226. package/dist/templates/todo/apps/web/src/lib/components/ui/checkbox/checkbox.svelte +39 -0
  227. package/dist/templates/todo/apps/web/src/lib/components/ui/checkbox/index.ts +6 -0
  228. package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog-close.svelte +11 -0
  229. package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog-content.svelte +48 -0
  230. package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog-description.svelte +17 -0
  231. package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog-footer.svelte +32 -0
  232. package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog-header.svelte +20 -0
  233. package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog-overlay.svelte +17 -0
  234. package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog-portal.svelte +7 -0
  235. package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog-title.svelte +17 -0
  236. package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog-trigger.svelte +11 -0
  237. package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog.svelte +7 -0
  238. package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/index.ts +34 -0
  239. package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte +16 -0
  240. package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +44 -0
  241. package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte +31 -0
  242. package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte +22 -0
  243. package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte +7 -0
  244. package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte +27 -0
  245. package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte +24 -0
  246. package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte +7 -0
  247. package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte +16 -0
  248. package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +34 -0
  249. package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte +17 -0
  250. package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte +20 -0
  251. package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +17 -0
  252. package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +29 -0
  253. package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte +7 -0
  254. package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +7 -0
  255. package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte +7 -0
  256. package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/index.ts +54 -0
  257. package/dist/templates/todo/apps/web/src/lib/components/ui/input/index.ts +7 -0
  258. package/dist/templates/todo/apps/web/src/lib/components/ui/input/input.svelte +48 -0
  259. package/dist/templates/todo/apps/web/src/lib/components/ui/label/index.ts +7 -0
  260. package/dist/templates/todo/apps/web/src/lib/components/ui/label/label.svelte +20 -0
  261. package/dist/templates/todo/apps/web/src/lib/components/ui/select/index.ts +37 -0
  262. package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-content.svelte +45 -0
  263. package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-group-heading.svelte +21 -0
  264. package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-group.svelte +17 -0
  265. package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-item.svelte +40 -0
  266. package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-label.svelte +20 -0
  267. package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-portal.svelte +7 -0
  268. package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-scroll-down-button.svelte +20 -0
  269. package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-scroll-up-button.svelte +20 -0
  270. package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-separator.svelte +18 -0
  271. package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-trigger.svelte +29 -0
  272. package/dist/templates/todo/apps/web/src/lib/components/ui/select/select.svelte +11 -0
  273. package/dist/templates/todo/apps/web/src/lib/components/ui/separator/index.ts +7 -0
  274. package/dist/templates/todo/apps/web/src/lib/components/ui/separator/separator.svelte +23 -0
  275. package/dist/templates/todo/apps/web/src/lib/components/ui/skeleton/index.ts +7 -0
  276. package/dist/templates/todo/apps/web/src/lib/components/ui/skeleton/skeleton.svelte +17 -0
  277. package/dist/templates/todo/apps/web/src/lib/components/ui/sonner/index.ts +1 -0
  278. package/dist/templates/todo/apps/web/src/lib/components/ui/sonner/sonner.svelte +34 -0
  279. package/dist/templates/todo/apps/web/src/lib/components/ui/table/index.ts +28 -0
  280. package/dist/templates/todo/apps/web/src/lib/components/ui/table/table-body.svelte +15 -0
  281. package/dist/templates/todo/apps/web/src/lib/components/ui/table/table-caption.svelte +20 -0
  282. package/dist/templates/todo/apps/web/src/lib/components/ui/table/table-cell.svelte +15 -0
  283. package/dist/templates/todo/apps/web/src/lib/components/ui/table/table-footer.svelte +20 -0
  284. package/dist/templates/todo/apps/web/src/lib/components/ui/table/table-head.svelte +15 -0
  285. package/dist/templates/todo/apps/web/src/lib/components/ui/table/table-header.svelte +20 -0
  286. package/dist/templates/todo/apps/web/src/lib/components/ui/table/table-row.svelte +15 -0
  287. package/dist/templates/todo/apps/web/src/lib/components/ui/table/table.svelte +17 -0
  288. package/dist/templates/todo/apps/web/src/lib/components/ui/tabs/index.ts +18 -0
  289. package/dist/templates/todo/apps/web/src/lib/components/ui/tabs/tabs-content.svelte +17 -0
  290. package/dist/templates/todo/apps/web/src/lib/components/ui/tabs/tabs-list.svelte +40 -0
  291. package/dist/templates/todo/apps/web/src/lib/components/ui/tabs/tabs-trigger.svelte +23 -0
  292. package/dist/templates/todo/apps/web/src/lib/components/ui/tabs/tabs.svelte +19 -0
  293. package/dist/templates/todo/apps/web/src/lib/components/ui/tooltip/index.ts +19 -0
  294. package/dist/templates/todo/apps/web/src/lib/components/ui/tooltip/tooltip-content.svelte +52 -0
  295. package/dist/templates/todo/apps/web/src/lib/components/ui/tooltip/tooltip-portal.svelte +7 -0
  296. package/dist/templates/todo/apps/web/src/lib/components/ui/tooltip/tooltip-provider.svelte +7 -0
  297. package/dist/templates/todo/apps/web/src/lib/components/ui/tooltip/tooltip-trigger.svelte +7 -0
  298. package/dist/templates/todo/apps/web/src/lib/components/ui/tooltip/tooltip.svelte +7 -0
  299. package/dist/templates/todo/apps/web/src/lib/i18n/README.md +7 -0
  300. package/dist/templates/todo/apps/web/src/lib/i18n/en.ts +10 -0
  301. package/dist/templates/todo/apps/web/src/lib/i18n/ko.ts +11 -0
  302. package/dist/templates/todo/apps/web/src/lib/server/backend-proxy.ts +16 -0
  303. package/dist/templates/todo/apps/web/src/lib/utils.ts +11 -0
  304. package/dist/templates/todo/apps/web/src/routes/+layout.svelte +9 -0
  305. package/dist/templates/todo/apps/web/src/routes/+page.svelte +95 -0
  306. package/dist/templates/todo/apps/web/src/routes/api/health/+server.ts +12 -0
  307. package/dist/templates/todo/apps/web/src/routes/api/todos/+server.ts +24 -0
  308. package/dist/templates/todo/apps/web/src/routes/api/todos/[id]/+server.ts +24 -0
  309. package/dist/templates/todo/apps/web/static/.gitkeep +0 -0
  310. package/dist/templates/todo/apps/web/svelte.config.js +15 -0
  311. package/dist/templates/todo/apps/web/tsconfig.json +9 -0
  312. package/dist/templates/todo/apps/web/vite.config.ts +7 -0
  313. package/dist/templates/todo/dot-env.example +16 -0
  314. package/dist/templates/todo/dot-gitignore +9 -0
  315. package/dist/templates/todo/infra/docker/docker-compose.yml +29 -0
  316. package/dist/templates/todo/infra/k3s/api-deployment.yaml +24 -0
  317. package/dist/templates/todo/infra/k3s/configmap.yaml +10 -0
  318. package/dist/templates/todo/infra/k3s/ingress.yaml +18 -0
  319. package/dist/templates/todo/infra/k3s/namespace.yaml +4 -0
  320. package/dist/templates/todo/infra/k3s/secret.example.yaml +10 -0
  321. package/dist/templates/todo/infra/k3s/services.yaml +21 -0
  322. package/dist/templates/todo/infra/k3s/web-deployment.yaml +20 -0
  323. package/dist/templates/todo/package.json +13 -0
  324. package/dist/templates.d.ts +10 -0
  325. package/dist/templates.js +33 -0
  326. package/package.json +2 -2
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "audit-log",
3
+ "description": "Audit logging: a global interceptor records who did each mutating request (who/method/path/status) to an audit_logs table. Requires auth.",
4
+ "requires": ["auth"],
5
+ "targetApp": "api",
6
+ "env": [],
7
+ "inject": [
8
+ { "file": "apps/api/src/app.module.ts", "marker": "// podokit:imports", "text": "import { AuditModule } from \"./audit/audit.module\";" },
9
+ { "file": "apps/api/src/app.module.ts", "marker": "// podokit:module-imports", "text": "AuditModule," }
10
+ ],
11
+ "instructions": [
12
+ "Run migrations to create the audit_logs table: npm run migration:run -w <app>-api",
13
+ "Mutating requests (POST/PUT/PATCH/DELETE) are recorded with the acting user.",
14
+ "List recent entries (requires a session): curl -b cookies.txt localhost:3000/audit-logs"
15
+ ]
16
+ }
@@ -0,0 +1,13 @@
1
+ import { Controller, Get } from "@nestjs/common";
2
+ import { Session, type UserSession } from "@thallesp/nestjs-better-auth";
3
+ import { ApiTags } from "@nestjs/swagger";
4
+
5
+ // Protected by the global AuthGuard. Use @Session() to read the current user.
6
+ @ApiTags("account")
7
+ @Controller("account")
8
+ export class AccountController {
9
+ @Get("me")
10
+ me(@Session() session: UserSession) {
11
+ return session.user;
12
+ }
13
+ }
@@ -0,0 +1,5 @@
1
+ import { Module } from "@nestjs/common";
2
+ import { AccountController } from "./account.controller";
3
+
4
+ @Module({ controllers: [AccountController] })
5
+ export class AccountModule {}
@@ -0,0 +1,36 @@
1
+ import { betterAuth, type BetterAuthOptions } from "better-auth";
2
+ import { twoFactor } from "better-auth/plugins";
3
+ import { Pool } from "pg";
4
+
5
+ // OAuth providers are enabled only when their credentials are present.
6
+ function socialProviders(): NonNullable<BetterAuthOptions["socialProviders"]> {
7
+ const providers: NonNullable<BetterAuthOptions["socialProviders"]> = {};
8
+ if (process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET) {
9
+ providers.google = {
10
+ clientId: process.env.GOOGLE_CLIENT_ID,
11
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET,
12
+ };
13
+ }
14
+ if (process.env.GITHUB_CLIENT_ID && process.env.GITHUB_CLIENT_SECRET) {
15
+ providers.github = {
16
+ clientId: process.env.GITHUB_CLIENT_ID,
17
+ clientSecret: process.env.GITHUB_CLIENT_SECRET,
18
+ };
19
+ }
20
+ return providers;
21
+ }
22
+
23
+ export const auth = betterAuth({
24
+ database: new Pool({
25
+ host: process.env.POSTGRES_HOST ?? "localhost",
26
+ port: Number(process.env.POSTGRES_PORT ?? 5432),
27
+ user: process.env.POSTGRES_USER ?? "podokit",
28
+ password: process.env.POSTGRES_PASSWORD ?? "podokit",
29
+ database: process.env.POSTGRES_DB ?? "podokit",
30
+ }),
31
+ emailAndPassword: { enabled: true },
32
+ socialProviders: socialProviders(),
33
+ plugins: process.env.AUTH_TWO_FACTOR === "true" ? [twoFactor()] : [],
34
+ secret: process.env.BETTER_AUTH_SECRET ?? "change-me-in-production-min-32-characters",
35
+ baseURL: process.env.BETTER_AUTH_URL ?? "http://localhost:3000",
36
+ });
@@ -0,0 +1,26 @@
1
+ import { Controller, Get } from "@nestjs/common";
2
+ import { InjectDataSource } from "@nestjs/typeorm";
3
+ import { DataSource } from "typeorm";
4
+ import { Public } from "@thallesp/nestjs-better-auth";
5
+
6
+ // Health checks stay public under the global auth guard.
7
+ @Public()
8
+ @Controller("health")
9
+ export class HealthController {
10
+ constructor(@InjectDataSource() private readonly dataSource: DataSource) {}
11
+
12
+ @Get()
13
+ liveness(): { status: string; uptime: number; timestamp: string } {
14
+ return { status: "ok", uptime: process.uptime(), timestamp: new Date().toISOString() };
15
+ }
16
+
17
+ @Get("ready")
18
+ async readiness(): Promise<{ status: string; db: string }> {
19
+ try {
20
+ await this.dataSource.query("SELECT 1");
21
+ return { status: "ready", db: "up" };
22
+ } catch {
23
+ return { status: "degraded", db: "down" };
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "auth",
3
+ "description": "Full authentication (better-auth): email/password, sessions, and \u2014 via config \u2014 OAuth and 2FA. Installs a global guard so the API is secure by default; health and /api/auth stay public.",
4
+ "requires": [],
5
+ "targetApp": "api",
6
+ "dependencies": {
7
+ "better-auth": "^1.6.23",
8
+ "@thallesp/nestjs-better-auth": "^2.7.0",
9
+ "pg": "^8.12.0"
10
+ },
11
+ "devDependencies": {
12
+ "@types/pg": "^8.11.10"
13
+ },
14
+ "env": [
15
+ "# auth (better-auth) \u2014 set a strong secret (>= 32 chars) in production",
16
+ "BETTER_AUTH_SECRET=change-me-in-production-min-32-characters",
17
+ "BETTER_AUTH_URL=http://localhost:3000",
18
+ "# 2FA (TOTP): set true to enable the plugin",
19
+ "AUTH_TWO_FACTOR=false",
20
+ "# OAuth (optional) \u2014 set a provider's id+secret to enable it",
21
+ "# GOOGLE_CLIENT_ID=",
22
+ "# GOOGLE_CLIENT_SECRET=",
23
+ "# GITHUB_CLIENT_ID=",
24
+ "# GITHUB_CLIENT_SECRET="
25
+ ],
26
+ "inject": [
27
+ {
28
+ "file": "apps/api/src/app.module.ts",
29
+ "marker": "// podokit:imports",
30
+ "text": "import { APP_GUARD } from \"@nestjs/core\";"
31
+ },
32
+ {
33
+ "file": "apps/api/src/app.module.ts",
34
+ "marker": "// podokit:imports",
35
+ "text": "import { AuthModule, AuthGuard } from \"@thallesp/nestjs-better-auth\";"
36
+ },
37
+ {
38
+ "file": "apps/api/src/app.module.ts",
39
+ "marker": "// podokit:imports",
40
+ "text": "import { auth } from \"./auth/auth\";"
41
+ },
42
+ {
43
+ "file": "apps/api/src/app.module.ts",
44
+ "marker": "// podokit:imports",
45
+ "text": "import { AccountModule } from \"./account/account.module\";"
46
+ },
47
+ {
48
+ "file": "apps/api/src/app.module.ts",
49
+ "marker": "// podokit:module-imports",
50
+ "text": "AuthModule.forRoot(auth),"
51
+ },
52
+ {
53
+ "file": "apps/api/src/app.module.ts",
54
+ "marker": "// podokit:module-imports",
55
+ "text": "AccountModule,"
56
+ },
57
+ {
58
+ "file": "apps/api/src/app.module.ts",
59
+ "marker": "// podokit:providers",
60
+ "text": "{ provide: APP_GUARD, useClass: AuthGuard },"
61
+ }
62
+ ],
63
+ "instructions": [
64
+ "Set BETTER_AUTH_SECRET in .env (>= 32 chars).",
65
+ "Create the auth tables: npx @better-auth/cli migrate -y --config apps/api/src/auth/auth.ts",
66
+ "Sign up: curl -c cookies.txt -XPOST localhost:3000/api/auth/sign-up/email -H 'content-type: application/json' -d '{\"email\":\"a@example.com\",\"password\":\"password123\",\"name\":\"A\"}'",
67
+ "Call a protected route with the session: curl -b cookies.txt localhost:3000/account/me",
68
+ "Everything is now protected by default; mark public routes with @Public(). Enable OAuth by setting a provider's env vars; enable 2FA with AUTH_TWO_FACTOR=true."
69
+ ]
70
+ }
@@ -0,0 +1,12 @@
1
+ import { Processor, WorkerHost } from "@nestjs/bullmq";
2
+ import type { Job } from "bullmq";
3
+ import { DEMO_QUEUE } from "./queue";
4
+
5
+ @Processor(DEMO_QUEUE)
6
+ export class DemoProcessor extends WorkerHost {
7
+ async process(job: Job<{ text: string }>): Promise<{ upper: string }> {
8
+ // Simulate work.
9
+ await new Promise((resolve) => setTimeout(resolve, 500));
10
+ return { upper: String(job.data.text ?? "").toUpperCase() };
11
+ }
12
+ }
@@ -0,0 +1,9 @@
1
+ import { ApiProperty } from "@nestjs/swagger";
2
+ import { IsNotEmpty, IsString } from "class-validator";
3
+
4
+ export class CreateJobDto {
5
+ @ApiProperty({ example: "hello" })
6
+ @IsString()
7
+ @IsNotEmpty()
8
+ text!: string;
9
+ }
@@ -0,0 +1,29 @@
1
+ import { Body, Controller, Get, NotFoundException, Param, Post } from "@nestjs/common";
2
+ import { InjectQueue } from "@nestjs/bullmq";
3
+ import { ApiTags } from "@nestjs/swagger";
4
+ import { Queue } from "bullmq";
5
+ import { CreateJobDto } from "./dto/create-job.dto";
6
+ import { DEMO_QUEUE } from "./queue";
7
+
8
+ @ApiTags("jobs")
9
+ @Controller("jobs")
10
+ export class JobsController {
11
+ constructor(@InjectQueue(DEMO_QUEUE) private readonly queue: Queue) {}
12
+
13
+ @Post()
14
+ async enqueue(@Body() dto: CreateJobDto): Promise<{ id: string | undefined }> {
15
+ const job = await this.queue.add("demo", { text: dto.text });
16
+ return { id: job.id };
17
+ }
18
+
19
+ @Get(":id")
20
+ async status(
21
+ @Param("id") id: string,
22
+ ): Promise<{ id: string | undefined; state: string; result: unknown }> {
23
+ const job = await this.queue.getJob(id);
24
+ if (!job) {
25
+ throw new NotFoundException(`Job ${id} not found`);
26
+ }
27
+ return { id: job.id, state: await job.getState(), result: job.returnvalue ?? null };
28
+ }
29
+ }
@@ -0,0 +1,15 @@
1
+ import { Module } from "@nestjs/common";
2
+ import { BullModule } from "@nestjs/bullmq";
3
+ import { JobsController } from "./jobs.controller";
4
+ import { DEMO_QUEUE, redisConnection } from "./queue";
5
+
6
+ // Producer side: registers the queue and exposes enqueue/status endpoints.
7
+ // The processor runs in a separate worker process (see main-worker.ts).
8
+ @Module({
9
+ imports: [
10
+ BullModule.forRoot({ connection: redisConnection() }),
11
+ BullModule.registerQueue({ name: DEMO_QUEUE }),
12
+ ],
13
+ controllers: [JobsController],
14
+ })
15
+ export class JobsModule {}
@@ -0,0 +1,8 @@
1
+ export const DEMO_QUEUE = "demo";
2
+
3
+ export function redisConnection(): { host: string; port: number } {
4
+ return {
5
+ host: process.env.REDIS_HOST ?? "localhost",
6
+ port: Number(process.env.REDIS_PORT ?? 6379),
7
+ };
8
+ }
@@ -0,0 +1,20 @@
1
+ import { Module } from "@nestjs/common";
2
+ import { BullModule } from "@nestjs/bullmq";
3
+ import { DemoProcessor } from "./demo.processor";
4
+ import { DEMO_QUEUE, redisConnection } from "./queue";
5
+ // podokit:worker-imports
6
+
7
+ // Consumer side: runs BullMQ processors. Bootstrapped by main-worker.ts as a
8
+ // separate process so workers scale independently of the API.
9
+ @Module({
10
+ imports: [
11
+ BullModule.forRoot({ connection: redisConnection() }),
12
+ BullModule.registerQueue({ name: DEMO_QUEUE }),
13
+ // podokit:worker-queues
14
+ ],
15
+ providers: [
16
+ DemoProcessor,
17
+ // podokit:worker-providers
18
+ ],
19
+ })
20
+ export class WorkerModule {}
@@ -0,0 +1,14 @@
1
+ import "dotenv/config";
2
+ import { NestFactory } from "@nestjs/core";
3
+ import { WorkerModule } from "./jobs/worker.module";
4
+
5
+ // Runs BullMQ processors in their own process (no HTTP server).
6
+ async function bootstrap(): Promise<void> {
7
+ const app = await NestFactory.createApplicationContext(WorkerModule);
8
+ await app.init();
9
+ // Keep the process alive to consume jobs.
10
+ process.on("SIGINT", () => void app.close().then(() => process.exit(0)));
11
+ process.on("SIGTERM", () => void app.close().then(() => process.exit(0)));
12
+ }
13
+
14
+ void bootstrap();
@@ -0,0 +1,18 @@
1
+ # Example: run the BullMQ worker as its own service.
2
+ #
3
+ # The default infra/docker/docker-compose.yml is dev infra (PostgreSQL + Redis)
4
+ # only, with the app running on the host. For a fully containerized deployment,
5
+ # build the API image and run the worker entrypoint against it — for example:
6
+ #
7
+ # docker compose -f infra/docker/docker-compose.yml -f infra/docker/worker.compose.example.yml up -d
8
+ services:
9
+ worker:
10
+ build:
11
+ context: ../../apps/api
12
+ command: ["node", "dist/main-worker"]
13
+ environment:
14
+ REDIS_HOST: redis
15
+ REDIS_PORT: "6379"
16
+ depends_on:
17
+ redis:
18
+ condition: service_healthy
@@ -0,0 +1,22 @@
1
+ # BullMQ worker — runs the same API image with the worker entrypoint.
2
+ # It has no HTTP server, so there is no Service or Ingress.
3
+ apiVersion: apps/v1
4
+ kind: Deployment
5
+ metadata:
6
+ name: worker
7
+ namespace: podokit
8
+ spec:
9
+ replicas: 1
10
+ selector:
11
+ matchLabels: { app: worker }
12
+ template:
13
+ metadata:
14
+ labels: { app: worker }
15
+ spec:
16
+ containers:
17
+ - name: worker
18
+ image: ghcr.io/example/podokit-api:latest
19
+ command: ["node", "dist/main-worker"]
20
+ envFrom:
21
+ - configMapRef: { name: app-config }
22
+ - secretRef: { name: app-secrets }
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "bullmq",
3
+ "description": "Background jobs with BullMQ: a demo queue, a separate worker process, and enqueue/status endpoints (needs Redis).",
4
+ "requires": [],
5
+ "targetApp": "api",
6
+ "dependencies": {
7
+ "@nestjs/bullmq": "^11.0.4",
8
+ "bullmq": "^5.34.0"
9
+ },
10
+ "scripts": {
11
+ "start:worker": "node dist/main-worker",
12
+ "dev:worker": "nest start --watch --entryFile main-worker"
13
+ },
14
+ "env": [
15
+ "# bullmq uses REDIS_HOST / REDIS_PORT (already defined above)"
16
+ ],
17
+ "inject": [
18
+ {
19
+ "file": "apps/api/src/app.module.ts",
20
+ "marker": "// podokit:imports",
21
+ "text": "import { JobsModule } from \"./jobs/jobs.module\";"
22
+ },
23
+ {
24
+ "file": "apps/api/src/app.module.ts",
25
+ "marker": "// podokit:module-imports",
26
+ "text": "JobsModule,"
27
+ }
28
+ ],
29
+ "instructions": [
30
+ "Ensure Redis is running: docker compose -f infra/docker/docker-compose.yml up -d",
31
+ "Run the worker in a separate terminal: npm run dev:worker -w <app>-api",
32
+ "Enqueue a job: curl -XPOST localhost:3000/jobs -H 'content-type: application/json' -d '{\"text\":\"hello\"}'",
33
+ "Check status: curl localhost:3000/jobs/<id> (state goes waiting -> active -> completed)",
34
+ "Deploy the worker as its own process: k3s manifest added at infra/k3s/worker-deployment.yaml; Docker Compose example at infra/docker/worker.compose.example.yml"
35
+ ]
36
+ }
@@ -0,0 +1,29 @@
1
+ import { BadRequestException, Controller, Get, Param, Post, UploadedFile, UseInterceptors } from "@nestjs/common";
2
+ import { FileInterceptor } from "@nestjs/platform-express";
3
+ import { ApiConsumes, ApiTags } from "@nestjs/swagger";
4
+ import { randomUUID } from "node:crypto";
5
+ import { StorageService } from "../storage/storage.service";
6
+
7
+ @ApiTags("files")
8
+ @Controller("files")
9
+ export class FilesController {
10
+ constructor(private readonly storage: StorageService) {}
11
+
12
+ @Post()
13
+ @ApiConsumes("multipart/form-data")
14
+ @UseInterceptors(FileInterceptor("file"))
15
+ async upload(@UploadedFile() file?: Express.Multer.File): Promise<{ key: string; url: string }> {
16
+ if (!file) {
17
+ throw new BadRequestException("A file field is required (multipart/form-data)");
18
+ }
19
+ const safeName = file.originalname.replace(/[^\w.-]+/g, "_");
20
+ const key = `uploads/${randomUUID()}-${safeName}`;
21
+ await this.storage.put(key, file.buffer, file.mimetype);
22
+ return { key, url: await this.storage.presignedGetUrl(key) };
23
+ }
24
+
25
+ @Get(":key/url")
26
+ async url(@Param("key") key: string): Promise<{ url: string }> {
27
+ return { url: await this.storage.presignedGetUrl(key) };
28
+ }
29
+ }
@@ -0,0 +1,9 @@
1
+ import { Module } from "@nestjs/common";
2
+ import { FilesController } from "./files.controller";
3
+
4
+ // Uploads are stored through StorageService, provided globally by the
5
+ // object-storage-s3 module.
6
+ @Module({
7
+ controllers: [FilesController],
8
+ })
9
+ export class FilesModule {}
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "file-upload",
3
+ "description": "Multipart file upload endpoint that stores files via object storage and returns a presigned URL.",
4
+ "requires": ["object-storage-s3"],
5
+ "targetApp": "api",
6
+ "devDependencies": {
7
+ "@types/multer": "^1.4.12"
8
+ },
9
+ "env": [],
10
+ "inject": [
11
+ { "file": "apps/api/src/app.module.ts", "marker": "// podokit:imports", "text": "import { FilesModule } from \"./files/files.module\";" },
12
+ { "file": "apps/api/src/app.module.ts", "marker": "// podokit:module-imports", "text": "FilesModule," }
13
+ ],
14
+ "instructions": [
15
+ "Start MinIO (or configure AWS S3): docker compose -f infra/docker/docker-compose.yml -f infra/docker/minio.compose.yml up -d",
16
+ "Upload a file: curl -F 'file=@./some.png' localhost:3000/files",
17
+ "The response includes { key, url } — a presigned download URL."
18
+ ]
19
+ }
@@ -0,0 +1,11 @@
1
+ import { ApiPropertyOptional } from "@nestjs/swagger";
2
+ import { IsInt, IsOptional, Max, Min } from "class-validator";
3
+
4
+ export class StartJobDto {
5
+ @ApiPropertyOptional({ example: 5, description: "Number of progress steps" })
6
+ @IsOptional()
7
+ @IsInt()
8
+ @Min(1)
9
+ @Max(100)
10
+ steps?: number;
11
+ }
@@ -0,0 +1,13 @@
1
+ import { Module } from "@nestjs/common";
2
+ import { BullModule } from "@nestjs/bullmq";
3
+ import { ProgressController } from "./progress.controller";
4
+ import { ProgressBridge } from "./progress.bridge";
5
+
6
+ // API side: enqueue progress jobs and relay their progress to SSE.
7
+ // RedisService and EventsService are provided globally by their modules.
8
+ @Module({
9
+ imports: [BullModule.registerQueue({ name: "progress" })],
10
+ controllers: [ProgressController],
11
+ providers: [ProgressBridge],
12
+ })
13
+ export class JobProgressModule {}
@@ -0,0 +1,19 @@
1
+ import { Injectable, type OnModuleInit } from "@nestjs/common";
2
+ import { RedisService } from "../redis/redis.service";
3
+ import { EventsService } from "../events/events.service";
4
+ import { PROGRESS_CHANNEL } from "./progress.processor";
5
+
6
+ // Runs in the API process: relays worker progress (Redis pub/sub) to SSE.
7
+ @Injectable()
8
+ export class ProgressBridge implements OnModuleInit {
9
+ constructor(
10
+ private readonly redis: RedisService,
11
+ private readonly events: EventsService,
12
+ ) {}
13
+
14
+ onModuleInit(): void {
15
+ this.redis.subscribe(PROGRESS_CHANNEL, (message) => {
16
+ this.events.publish({ type: "job-progress", ...JSON.parse(message) });
17
+ });
18
+ }
19
+ }
@@ -0,0 +1,17 @@
1
+ import { Body, Controller, Post } from "@nestjs/common";
2
+ import { InjectQueue } from "@nestjs/bullmq";
3
+ import { ApiTags } from "@nestjs/swagger";
4
+ import { Queue } from "bullmq";
5
+ import { StartJobDto } from "./dto/start-job.dto";
6
+
7
+ @ApiTags("progress")
8
+ @Controller("progress")
9
+ export class ProgressController {
10
+ constructor(@InjectQueue("progress") private readonly queue: Queue) {}
11
+
12
+ @Post()
13
+ async start(@Body() dto: StartJobDto): Promise<{ jobId: string | undefined }> {
14
+ const job = await this.queue.add("progress", { steps: dto.steps ?? 5 });
15
+ return { jobId: job.id };
16
+ }
17
+ }
@@ -0,0 +1,25 @@
1
+ import { Processor, WorkerHost } from "@nestjs/bullmq";
2
+ import type { Job } from "bullmq";
3
+ import { RedisService } from "../redis/redis.service";
4
+
5
+ export const PROGRESS_CHANNEL = "job:progress";
6
+
7
+ // Runs in the worker process. Reports progress both to BullMQ and, via Redis
8
+ // pub/sub, to the API process (which relays it to SSE clients).
9
+ @Processor("progress")
10
+ export class ProgressProcessor extends WorkerHost {
11
+ constructor(private readonly redis: RedisService) {
12
+ super();
13
+ }
14
+
15
+ async process(job: Job<{ steps?: number }>): Promise<{ done: true }> {
16
+ const steps = job.data.steps ?? 5;
17
+ for (let i = 1; i <= steps; i += 1) {
18
+ await new Promise((resolve) => setTimeout(resolve, 400));
19
+ const progress = Math.round((i / steps) * 100);
20
+ await job.updateProgress(progress);
21
+ await this.redis.publish(PROGRESS_CHANNEL, JSON.stringify({ jobId: job.id, progress }));
22
+ }
23
+ return { done: true };
24
+ }
25
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "job-progress",
3
+ "description": "Live job progress: a worker reports progress over Redis pub/sub and the API relays it to SSE clients (combines bullmq + redis + sse).",
4
+ "requires": ["bullmq", "sse", "redis"],
5
+ "targetApp": "api",
6
+ "env": [],
7
+ "inject": [
8
+ { "file": "apps/api/src/app.module.ts", "marker": "// podokit:imports", "text": "import { JobProgressModule } from \"./progress/job-progress.module\";" },
9
+ { "file": "apps/api/src/app.module.ts", "marker": "// podokit:module-imports", "text": "JobProgressModule," },
10
+ { "file": "apps/api/src/jobs/worker.module.ts", "marker": "// podokit:worker-imports", "text": "import { ProgressProcessor } from \"../progress/progress.processor\";" },
11
+ { "file": "apps/api/src/jobs/worker.module.ts", "marker": "// podokit:worker-imports", "text": "import { RedisModule } from \"../redis/redis.module\";" },
12
+ { "file": "apps/api/src/jobs/worker.module.ts", "marker": "// podokit:worker-queues", "text": "RedisModule," },
13
+ { "file": "apps/api/src/jobs/worker.module.ts", "marker": "// podokit:worker-queues", "text": "BullModule.registerQueue({ name: \"progress\" })," },
14
+ { "file": "apps/api/src/jobs/worker.module.ts", "marker": "// podokit:worker-providers", "text": "ProgressProcessor," }
15
+ ],
16
+ "instructions": [
17
+ "Start Redis + Postgres: docker compose -f infra/docker/docker-compose.yml up -d",
18
+ "Run API and worker in separate terminals: npm run dev / npm run dev:worker -w <app>-api",
19
+ "Watch progress: curl -N localhost:3000/events/stream",
20
+ "Start a job: curl -XPOST localhost:3000/progress -H 'content-type: application/json' -d '{\"steps\":5}'",
21
+ "The stream shows job-progress events 20 -> 40 -> ... -> 100 pushed from the worker via Redis."
22
+ ]
23
+ }
@@ -0,0 +1,29 @@
1
+ import { Module } from "@nestjs/common";
2
+ import { randomUUID } from "node:crypto";
3
+ import type { IncomingMessage, ServerResponse } from "node:http";
4
+ import { LoggerModule } from "nestjs-pino";
5
+
6
+ const isProduction = process.env.NODE_ENV === "production";
7
+
8
+ // Structured request logging with a correlation id. Importing this module
9
+ // installs the pino-http middleware for all routes.
10
+ @Module({
11
+ imports: [
12
+ LoggerModule.forRoot({
13
+ pinoHttp: {
14
+ level: process.env.LOG_LEVEL ?? "info",
15
+ // Pretty, single-line logs in dev; JSON in production.
16
+ transport: isProduction ? undefined : { target: "pino-pretty", options: { singleLine: true } },
17
+ autoLogging: true,
18
+ // One id per request; honor an inbound x-request-id and echo it back.
19
+ genReqId: (req: IncomingMessage, res: ServerResponse): string => {
20
+ const header = req.headers["x-request-id"];
21
+ const id = (Array.isArray(header) ? header[0] : header) ?? randomUUID();
22
+ res.setHeader("x-request-id", id);
23
+ return id;
24
+ },
25
+ },
26
+ }),
27
+ ],
28
+ })
29
+ export class LoggingModule {}
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "logging",
3
+ "description": "Structured request logging (nestjs-pino) with a per-request correlation id (x-request-id). Pretty in dev, JSON in production.",
4
+ "requires": [],
5
+ "targetApp": "api",
6
+ "dependencies": {
7
+ "nestjs-pino": "^4.3.0",
8
+ "pino-http": "^10.3.0"
9
+ },
10
+ "devDependencies": {
11
+ "pino-pretty": "^13.0.0"
12
+ },
13
+ "env": [
14
+ "# logging",
15
+ "LOG_LEVEL=info"
16
+ ],
17
+ "inject": [
18
+ { "file": "apps/api/src/app.module.ts", "marker": "// podokit:imports", "text": "import { LoggingModule } from \"./logging/logging.module\";" },
19
+ { "file": "apps/api/src/app.module.ts", "marker": "// podokit:module-imports", "text": "LoggingModule," }
20
+ ],
21
+ "instructions": [
22
+ "Every HTTP request is now logged as structured JSON with a correlation id.",
23
+ "Make a request and watch the API logs: curl localhost:3000/health",
24
+ "Set LOG_LEVEL (debug|info|warn|error) in .env. Logs are pretty in dev and JSON when NODE_ENV=production.",
25
+ "To route Nest's own logs through pino too, add to main.ts: NestFactory.create(AppModule, { bufferLogs: true }) then app.useLogger(app.get(Logger))."
26
+ ]
27
+ }
@@ -0,0 +1,8 @@
1
+ import { ApiProperty } from "@nestjs/swagger";
2
+ import { IsString } from "class-validator";
3
+
4
+ export class PutObjectDto {
5
+ @ApiProperty({ example: "hello world" })
6
+ @IsString()
7
+ content!: string;
8
+ }