@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,31 @@
1
+ import type { S3ClientConfig } from "@aws-sdk/client-s3";
2
+
3
+ export interface StorageSettings {
4
+ provider: "minio" | "aws";
5
+ bucket: string;
6
+ }
7
+
8
+ export function storageSettings(): StorageSettings {
9
+ const provider = process.env.STORAGE_PROVIDER === "aws" ? "aws" : "minio";
10
+ return { provider, bucket: process.env.S3_BUCKET ?? "podokit" };
11
+ }
12
+
13
+ // Build an S3 client config that works for both AWS S3 and MinIO.
14
+ // MinIO needs a custom endpoint and path-style addressing; AWS does not.
15
+ export function storageClientConfig(): S3ClientConfig {
16
+ const { provider } = storageSettings();
17
+ const region = process.env.S3_REGION ?? "us-east-1";
18
+
19
+ const accessKeyId = process.env.S3_ACCESS_KEY_ID;
20
+ const secretAccessKey = process.env.S3_SECRET_ACCESS_KEY;
21
+ const credentials = accessKeyId && secretAccessKey ? { accessKeyId, secretAccessKey } : undefined;
22
+
23
+ const forcePathStyle = process.env.S3_FORCE_PATH_STYLE
24
+ ? process.env.S3_FORCE_PATH_STYLE === "true"
25
+ : provider === "minio";
26
+
27
+ const config: S3ClientConfig = { region, forcePathStyle };
28
+ if (credentials) config.credentials = credentials;
29
+ if (process.env.S3_ENDPOINT) config.endpoint = process.env.S3_ENDPOINT;
30
+ return config;
31
+ }
@@ -0,0 +1,29 @@
1
+ import { Body, Controller, Get, Param, Put } from "@nestjs/common";
2
+ import { ApiTags } from "@nestjs/swagger";
3
+ import { StorageService } from "./storage.service";
4
+ import { PutObjectDto } from "./dto/put-object.dto";
5
+
6
+ // Demo endpoints — replace with your own. See the file-upload module for
7
+ // multipart uploads built on this service.
8
+ @ApiTags("storage")
9
+ @Controller("storage")
10
+ export class StorageController {
11
+ constructor(private readonly storage: StorageService) {}
12
+
13
+ @Put(":key")
14
+ async put(@Param("key") key: string, @Body() dto: PutObjectDto): Promise<{ key: string }> {
15
+ await this.storage.put(key, dto.content, "text/plain");
16
+ return { key };
17
+ }
18
+
19
+ @Get(":key")
20
+ async get(@Param("key") key: string): Promise<{ key: string; content: string }> {
21
+ const buffer = await this.storage.get(key);
22
+ return { key, content: buffer.toString("utf8") };
23
+ }
24
+
25
+ @Get(":key/presigned")
26
+ async presigned(@Param("key") key: string): Promise<{ url: string }> {
27
+ return { url: await this.storage.presignedGetUrl(key) };
28
+ }
29
+ }
@@ -0,0 +1,11 @@
1
+ import { Global, Module } from "@nestjs/common";
2
+ import { StorageController } from "./storage.controller";
3
+ import { StorageService } from "./storage.service";
4
+
5
+ @Global()
6
+ @Module({
7
+ controllers: [StorageController],
8
+ providers: [StorageService],
9
+ exports: [StorageService],
10
+ })
11
+ export class StorageModule {}
@@ -0,0 +1,37 @@
1
+ import { Injectable } from "@nestjs/common";
2
+ import {
3
+ DeleteObjectCommand,
4
+ GetObjectCommand,
5
+ PutObjectCommand,
6
+ S3Client,
7
+ } from "@aws-sdk/client-s3";
8
+ import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
9
+ import { storageClientConfig, storageSettings } from "./storage.config";
10
+
11
+ @Injectable()
12
+ export class StorageService {
13
+ private readonly client = new S3Client(storageClientConfig());
14
+ private readonly bucket = storageSettings().bucket;
15
+
16
+ async put(key: string, body: Buffer | string, contentType?: string): Promise<void> {
17
+ await this.client.send(
18
+ new PutObjectCommand({ Bucket: this.bucket, Key: key, Body: body, ContentType: contentType }),
19
+ );
20
+ }
21
+
22
+ async get(key: string): Promise<Buffer> {
23
+ const res = await this.client.send(new GetObjectCommand({ Bucket: this.bucket, Key: key }));
24
+ const bytes = await res.Body?.transformToByteArray();
25
+ return Buffer.from(bytes ?? new Uint8Array());
26
+ }
27
+
28
+ presignedGetUrl(key: string, expiresIn = 3600): Promise<string> {
29
+ return getSignedUrl(this.client, new GetObjectCommand({ Bucket: this.bucket, Key: key }), {
30
+ expiresIn,
31
+ });
32
+ }
33
+
34
+ async delete(key: string): Promise<void> {
35
+ await this.client.send(new DeleteObjectCommand({ Bucket: this.bucket, Key: key }));
36
+ }
37
+ }
@@ -0,0 +1,33 @@
1
+ # MinIO for local S3-compatible object storage (dev).
2
+ # The default docker-compose.yml is postgres + redis; add MinIO with:
3
+ #
4
+ # docker compose -f infra/docker/docker-compose.yml -f infra/docker/minio.compose.yml up -d
5
+ #
6
+ # Console: http://localhost:9001 (S3 API: http://localhost:9000)
7
+ services:
8
+ minio:
9
+ image: minio/minio:latest
10
+ command: server /data --console-address ":9001"
11
+ environment:
12
+ MINIO_ROOT_USER: ${S3_ACCESS_KEY_ID:-podokit}
13
+ MINIO_ROOT_PASSWORD: ${S3_SECRET_ACCESS_KEY:-podokitsecret}
14
+ ports:
15
+ - "${S3_PORT:-9000}:9000"
16
+ - "${S3_CONSOLE_PORT:-9001}:9001"
17
+ volumes:
18
+ - minio-data:/data
19
+
20
+ # Waits for MinIO, then creates the bucket and exits.
21
+ minio-init:
22
+ image: minio/mc:latest
23
+ depends_on:
24
+ - minio
25
+ entrypoint: >
26
+ /bin/sh -c "
27
+ until mc alias set local http://minio:9000 ${S3_ACCESS_KEY_ID:-podokit} ${S3_SECRET_ACCESS_KEY:-podokitsecret}; do echo 'waiting for minio'; sleep 1; done;
28
+ mc mb -p local/${S3_BUCKET:-podokit};
29
+ echo 'bucket ready';
30
+ exit 0"
31
+
32
+ volumes:
33
+ minio-data:
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "object-storage-s3",
3
+ "description": "S3-compatible object storage (AWS S3 or MinIO, chosen by STORAGE_PROVIDER) with a StorageService and presigned URLs.",
4
+ "requires": [],
5
+ "targetApp": "api",
6
+ "dependencies": {
7
+ "@aws-sdk/client-s3": "^3.700.0",
8
+ "@aws-sdk/s3-request-presigner": "^3.700.0"
9
+ },
10
+ "env": [
11
+ "# object storage — STORAGE_PROVIDER: minio | aws",
12
+ "STORAGE_PROVIDER=minio",
13
+ "S3_ENDPOINT=http://localhost:9000",
14
+ "S3_REGION=us-east-1",
15
+ "S3_BUCKET=podokit",
16
+ "S3_ACCESS_KEY_ID=podokit",
17
+ "S3_SECRET_ACCESS_KEY=podokitsecret",
18
+ "S3_FORCE_PATH_STYLE=true"
19
+ ],
20
+ "inject": [
21
+ { "file": "apps/api/src/app.module.ts", "marker": "// podokit:imports", "text": "import { StorageModule } from \"./storage/storage.module\";" },
22
+ { "file": "apps/api/src/app.module.ts", "marker": "// podokit:module-imports", "text": "StorageModule," }
23
+ ],
24
+ "instructions": [
25
+ "Start MinIO for local dev: docker compose -f infra/docker/docker-compose.yml -f infra/docker/minio.compose.yml up -d",
26
+ "Store an object: curl -XPUT localhost:3000/storage/hello -H 'content-type: application/json' -d '{\"content\":\"hi\"}'",
27
+ "Read it back: curl localhost:3000/storage/hello",
28
+ "Presigned URL: curl localhost:3000/storage/hello/presigned",
29
+ "For AWS S3: set STORAGE_PROVIDER=aws, remove S3_ENDPOINT, set S3_FORCE_PATH_STYLE=false and real credentials/bucket."
30
+ ]
31
+ }
@@ -0,0 +1,26 @@
1
+ import { Module } from "@nestjs/common";
2
+ import { APP_GUARD } from "@nestjs/core";
3
+ import { ThrottlerGuard, ThrottlerModule } from "@nestjs/throttler";
4
+ import { ThrottlerStorageRedisService } from "@nest-lab/throttler-storage-redis";
5
+ import Redis from "ioredis";
6
+
7
+ const ttlSeconds = Number(process.env.RATE_LIMIT_TTL ?? 60);
8
+ const limit = Number(process.env.RATE_LIMIT_MAX ?? 100);
9
+
10
+ // Distributed rate limiting: the counter lives in Redis, so the limit holds
11
+ // across API replicas. Registers a global guard (429 when exceeded).
12
+ @Module({
13
+ imports: [
14
+ ThrottlerModule.forRoot({
15
+ throttlers: [{ ttl: ttlSeconds * 1000, limit }],
16
+ storage: new ThrottlerStorageRedisService(
17
+ new Redis({
18
+ host: process.env.REDIS_HOST ?? "localhost",
19
+ port: Number(process.env.REDIS_PORT ?? 6379),
20
+ }),
21
+ ),
22
+ }),
23
+ ],
24
+ providers: [{ provide: APP_GUARD, useClass: ThrottlerGuard }],
25
+ })
26
+ export class RateLimitModule {}
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "rate-limit",
3
+ "description": "Rate limiting with @nestjs/throttler backed by Redis (correct across API replicas). Installs a global throttler guard; configurable via env.",
4
+ "requires": ["redis"],
5
+ "targetApp": "api",
6
+ "dependencies": {
7
+ "@nestjs/throttler": "^6.5.0",
8
+ "@nest-lab/throttler-storage-redis": "^1.2.0"
9
+ },
10
+ "env": [
11
+ "# rate limit (window in seconds, max requests per window per client)",
12
+ "RATE_LIMIT_TTL=60",
13
+ "RATE_LIMIT_MAX=100"
14
+ ],
15
+ "inject": [
16
+ { "file": "apps/api/src/app.module.ts", "marker": "// podokit:imports", "text": "import { RateLimitModule } from \"./rate-limit/rate-limit.module\";" },
17
+ { "file": "apps/api/src/app.module.ts", "marker": "// podokit:module-imports", "text": "RateLimitModule," }
18
+ ],
19
+ "instructions": [
20
+ "Ensure Redis is running: docker compose -f infra/docker/docker-compose.yml up -d",
21
+ "Tune RATE_LIMIT_TTL / RATE_LIMIT_MAX in .env.",
22
+ "Exceeding the limit returns HTTP 429. Skip a route with @SkipThrottle() or tune it with @Throttle().",
23
+ "The limit is shared across API replicas because it is stored in Redis."
24
+ ]
25
+ }
@@ -0,0 +1,21 @@
1
+ import { Body, Controller, Get, Param, Put } from "@nestjs/common";
2
+ import { ApiTags } from "@nestjs/swagger";
3
+ import { RedisService } from "./redis.service";
4
+ import { SetCacheDto } from "./dto/set-cache.dto";
5
+
6
+ @ApiTags("cache")
7
+ @Controller("cache")
8
+ export class CacheController {
9
+ constructor(private readonly redis: RedisService) {}
10
+
11
+ @Put(":key")
12
+ async put(@Param("key") key: string, @Body() dto: SetCacheDto): Promise<{ key: string }> {
13
+ await this.redis.set(key, dto.value, dto.ttl);
14
+ return { key };
15
+ }
16
+
17
+ @Get(":key")
18
+ async get(@Param("key") key: string): Promise<{ key: string; value: string | null }> {
19
+ return { key, value: await this.redis.get(key) };
20
+ }
21
+ }
@@ -0,0 +1,14 @@
1
+ import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
2
+ import { IsInt, IsOptional, IsString, Min } from "class-validator";
3
+
4
+ export class SetCacheDto {
5
+ @ApiProperty({ example: "hello" })
6
+ @IsString()
7
+ value!: string;
8
+
9
+ @ApiPropertyOptional({ example: 60, description: "TTL in seconds" })
10
+ @IsOptional()
11
+ @IsInt()
12
+ @Min(1)
13
+ ttl?: number;
14
+ }
@@ -0,0 +1,12 @@
1
+ import { Global, Module } from "@nestjs/common";
2
+ import { CacheController } from "./cache.controller";
3
+ import { RedisService } from "./redis.service";
4
+
5
+ // Global so any module can inject RedisService (cache, pub/sub, etc.).
6
+ @Global()
7
+ @Module({
8
+ controllers: [CacheController],
9
+ providers: [RedisService],
10
+ exports: [RedisService],
11
+ })
12
+ export class RedisModule {}
@@ -0,0 +1,43 @@
1
+ import { Injectable, type OnModuleDestroy } from "@nestjs/common";
2
+ import Redis from "ioredis";
3
+
4
+ @Injectable()
5
+ export class RedisService implements OnModuleDestroy {
6
+ readonly client = new Redis({
7
+ host: process.env.REDIS_HOST ?? "localhost",
8
+ port: Number(process.env.REDIS_PORT ?? 6379),
9
+ maxRetriesPerRequest: null,
10
+ });
11
+ private readonly subscribers: Redis[] = [];
12
+
13
+ get(key: string): Promise<string | null> {
14
+ return this.client.get(key);
15
+ }
16
+
17
+ async set(key: string, value: string, ttlSeconds?: number): Promise<void> {
18
+ if (ttlSeconds) await this.client.set(key, value, "EX", ttlSeconds);
19
+ else await this.client.set(key, value);
20
+ }
21
+
22
+ del(key: string): Promise<number> {
23
+ return this.client.del(key);
24
+ }
25
+
26
+ publish(channel: string, message: string): Promise<number> {
27
+ return this.client.publish(channel, message);
28
+ }
29
+
30
+ // Subscribe on a dedicated connection (ioredis can't mix subscribe + commands).
31
+ subscribe(channel: string, handler: (message: string) => void): void {
32
+ const sub = this.client.duplicate();
33
+ this.subscribers.push(sub);
34
+ void sub.subscribe(channel);
35
+ sub.on("message", (ch, message) => {
36
+ if (ch === channel) handler(message);
37
+ });
38
+ }
39
+
40
+ async onModuleDestroy(): Promise<void> {
41
+ await Promise.allSettled([this.client.quit(), ...this.subscribers.map((s) => s.quit())]);
42
+ }
43
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "redis",
3
+ "description": "Redis client (ioredis) with get/set/del and publish/subscribe, plus demo cache endpoints.",
4
+ "requires": [],
5
+ "targetApp": "api",
6
+ "dependencies": {
7
+ "ioredis": "^5.4.1"
8
+ },
9
+ "env": [
10
+ "# redis (REDIS_HOST / REDIS_PORT already defined above)"
11
+ ],
12
+ "inject": [
13
+ { "file": "apps/api/src/app.module.ts", "marker": "// podokit:imports", "text": "import { RedisModule } from \"./redis/redis.module\";" },
14
+ { "file": "apps/api/src/app.module.ts", "marker": "// podokit:module-imports", "text": "RedisModule," }
15
+ ],
16
+ "instructions": [
17
+ "Start Redis: docker compose -f infra/docker/docker-compose.yml up -d",
18
+ "Set a value: curl -XPUT localhost:3000/cache/greeting -H 'content-type: application/json' -d '{\"value\":\"hi\",\"ttl\":60}'",
19
+ "Read it back: curl localhost:3000/cache/greeting"
20
+ ]
21
+ }
@@ -0,0 +1,8 @@
1
+ import { ApiProperty } from "@nestjs/swagger";
2
+ import { IsString } from "class-validator";
3
+
4
+ export class PublishEventDto {
5
+ @ApiProperty({ example: "hello" })
6
+ @IsString()
7
+ message!: string;
8
+ }
@@ -0,0 +1,25 @@
1
+ import { Body, Controller, Post, Sse, type MessageEvent } from "@nestjs/common";
2
+ import { ApiTags } from "@nestjs/swagger";
3
+ import { interval, map, merge, type Observable } from "rxjs";
4
+ import { EventsService } from "./events.service";
5
+ import { PublishEventDto } from "./dto/publish-event.dto";
6
+
7
+ @ApiTags("events")
8
+ @Controller("events")
9
+ export class EventsController {
10
+ constructor(private readonly events: EventsService) {}
11
+
12
+ @Sse("stream")
13
+ stream(): Observable<MessageEvent> {
14
+ const heartbeat = interval(5000).pipe(
15
+ map((n): MessageEvent => ({ data: { type: "heartbeat", n } })),
16
+ );
17
+ return merge(this.events.asObservable(), heartbeat);
18
+ }
19
+
20
+ @Post()
21
+ publish(@Body() dto: PublishEventDto): { ok: true } {
22
+ this.events.publish({ type: "message", message: dto.message });
23
+ return { ok: true };
24
+ }
25
+ }
@@ -0,0 +1,12 @@
1
+ import { Global, Module } from "@nestjs/common";
2
+ import { EventsController } from "./events.controller";
3
+ import { EventsService } from "./events.service";
4
+
5
+ // Global so any module can inject EventsService to broadcast updates.
6
+ @Global()
7
+ @Module({
8
+ controllers: [EventsController],
9
+ providers: [EventsService],
10
+ exports: [EventsService],
11
+ })
12
+ export class EventsModule {}
@@ -0,0 +1,17 @@
1
+ import { Injectable, type MessageEvent } from "@nestjs/common";
2
+ import { Observable, Subject } from "rxjs";
3
+
4
+ @Injectable()
5
+ export class EventsService {
6
+ private readonly subject = new Subject<MessageEvent>();
7
+
8
+ // Push an event to all connected clients. Inject this service anywhere
9
+ // (e.g. a queue processor) to broadcast progress or notifications.
10
+ publish(data: unknown): void {
11
+ this.subject.next({ data } as MessageEvent);
12
+ }
13
+
14
+ asObservable(): Observable<MessageEvent> {
15
+ return this.subject.asObservable();
16
+ }
17
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "sse",
3
+ "description": "Server-Sent Events: a /events/stream endpoint (heartbeat + published messages) and a POST /events publisher.",
4
+ "requires": [],
5
+ "targetApp": "api",
6
+ "env": [],
7
+ "inject": [
8
+ { "file": "apps/api/src/app.module.ts", "marker": "// podokit:imports", "text": "import { EventsModule } from \"./events/events.module\";" },
9
+ { "file": "apps/api/src/app.module.ts", "marker": "// podokit:module-imports", "text": "EventsModule," }
10
+ ],
11
+ "instructions": [
12
+ "Stream events: curl -N localhost:3000/events/stream",
13
+ "Publish a message (in another terminal): curl -XPOST localhost:3000/events -H 'content-type: application/json' -d '{\"message\":\"hello\"}'",
14
+ "Inject EventsService anywhere to push updates (e.g. job progress)."
15
+ ]
16
+ }
@@ -0,0 +1,40 @@
1
+ # {{projectName}}
2
+
3
+ Full-stack TypeScript app generated with [PodoKit](https://github.com/podosoft-dev/podokit).
4
+
5
+ - `apps/api` — NestJS API: schema-validated env, `/health` + `/health/ready`, a `todos` CRUD resource (TypeORM + PostgreSQL), Swagger docs at `/api-docs`, and a standard error envelope.
6
+ - `apps/web` — SvelteKit app (TailwindCSS v4, shadcn-svelte, typesafe-i18n) with a todo UI that talks to the API through a server-side proxy.
7
+ - `infra/` — Docker Compose (PostgreSQL, Redis) and k3s manifests.
8
+
9
+ ## Getting started
10
+
11
+ ```bash
12
+ {{packageManager}} install
13
+ cp .env.example .env
14
+
15
+ # start local PostgreSQL + Redis
16
+ docker compose -f infra/docker/docker-compose.yml up -d
17
+
18
+ # run database migrations
19
+ {{packageManager}} run migration:run -w {{projectName}}-api
20
+
21
+ # run api + web
22
+ {{packageManager}} run dev
23
+ ```
24
+
25
+ - API: http://localhost:3000 — health at `/health`, docs at `/api-docs`
26
+ - Web: http://localhost:5173
27
+
28
+ ## Database & migrations
29
+
30
+ The API uses TypeORM with PostgreSQL. A sample `Todo` entity and an initial migration are included — replace them with your own domain model.
31
+
32
+ ```bash
33
+ {{packageManager}} run migration:run -w {{projectName}}-api # apply migrations
34
+ {{packageManager}} run migration:revert -w {{projectName}}-api # roll back the last one
35
+ ```
36
+
37
+ ## Deploy
38
+
39
+ Docker Compose in `infra/docker`; example k3s manifests in `infra/k3s`
40
+ (use `secret.example.yaml` as a template — never commit real secrets).
@@ -0,0 +1,22 @@
1
+ # syntax=docker/dockerfile:1
2
+ FROM node:20-alpine AS deps
3
+ WORKDIR /app
4
+ COPY package.json ./
5
+ RUN npm install --omit=dev=false --no-audit --no-fund
6
+
7
+ FROM node:20-alpine AS build
8
+ WORKDIR /app
9
+ COPY --from=deps /app/node_modules ./node_modules
10
+ COPY . .
11
+ RUN npm run build
12
+
13
+ FROM node:20-alpine AS runtime
14
+ WORKDIR /app
15
+ ENV NODE_ENV=production
16
+ RUN addgroup -S app && adduser -S app -G app
17
+ COPY --from=build /app/dist ./dist
18
+ COPY --from=build /app/node_modules ./node_modules
19
+ COPY package.json ./
20
+ USER app
21
+ EXPOSE 3000
22
+ CMD ["node", "dist/main"]
@@ -0,0 +1,5 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/nest-cli",
3
+ "collection": "@nestjs/schematics",
4
+ "sourceRoot": "src"
5
+ }
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "{{projectName}}-api",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "nest start --watch",
7
+ "build": "nest build",
8
+ "start": "node dist/main",
9
+ "lint": "tsc -p tsconfig.json --noEmit",
10
+ "test": "jest",
11
+ "typeorm": "typeorm-ts-node-commonjs -d src/database/data-source.ts",
12
+ "migration:run": "npm run typeorm -- migration:run",
13
+ "migration:revert": "npm run typeorm -- migration:revert",
14
+ "migration:generate": "npm run typeorm -- migration:generate"
15
+ },
16
+ "dependencies": {
17
+ "@nestjs/common": "^11.1.27",
18
+ "@nestjs/config": "^4.0.4",
19
+ "@nestjs/core": "^11.1.27",
20
+ "@nestjs/platform-express": "^11.1.27",
21
+ "@nestjs/swagger": "^11.4.5",
22
+ "@nestjs/typeorm": "^11.0.3",
23
+ "class-transformer": "^0.5.1",
24
+ "class-validator": "^0.14.1",
25
+ "dotenv": "^16.4.5",
26
+ "pg": "^8.12.0",
27
+ "reflect-metadata": "^0.2.2",
28
+ "rxjs": "^7.8.1",
29
+ "typeorm": "^0.3.20",
30
+ "zod": "^3.23.8"
31
+ },
32
+ "devDependencies": {
33
+ "@nestjs/cli": "^11.0.23",
34
+ "@nestjs/testing": "^11.1.27",
35
+ "@types/express": "^5.0.1",
36
+ "@types/node": "^20.17.0",
37
+ "@types/supertest": "^6.0.2",
38
+ "jest": "^29.7.0",
39
+ "supertest": "^7.0.0",
40
+ "ts-jest": "^29.2.5",
41
+ "ts-node": "^10.9.2",
42
+ "typescript": "^5.6.3"
43
+ }
44
+ }
@@ -0,0 +1,22 @@
1
+ import { Module } from "@nestjs/common";
2
+ import { ConfigModule } from "@nestjs/config";
3
+ import { TypeOrmModule } from "@nestjs/typeorm";
4
+ import { validateEnv } from "./config/env.validation";
5
+ import { dataSourceOptions } from "./database/data-source";
6
+ import { HealthModule } from "./health/health.module";
7
+ import { TodosModule } from "./todos/todos.module";
8
+ // podokit:imports
9
+
10
+ @Module({
11
+ imports: [
12
+ ConfigModule.forRoot({ isGlobal: true, validate: validateEnv }),
13
+ TypeOrmModule.forRoot(dataSourceOptions),
14
+ HealthModule,
15
+ TodosModule,
16
+ // podokit:module-imports
17
+ ],
18
+ providers: [
19
+ // podokit:providers
20
+ ],
21
+ })
22
+ export class AppModule {}
@@ -0,0 +1,43 @@
1
+ import { ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus } from "@nestjs/common";
2
+ import type { Request, Response } from "express";
3
+ import { AppException } from "./app-exception";
4
+
5
+ interface ErrorBody {
6
+ success: false;
7
+ error: {
8
+ code: string;
9
+ message: string;
10
+ statusCode: number;
11
+ path: string;
12
+ timestamp: string;
13
+ };
14
+ }
15
+
16
+ @Catch()
17
+ export class AllExceptionsFilter implements ExceptionFilter {
18
+ catch(exception: unknown, host: ArgumentsHost): void {
19
+ const ctx = host.switchToHttp();
20
+ const response = ctx.getResponse<Response>();
21
+ const request = ctx.getRequest<Request>();
22
+
23
+ let statusCode = HttpStatus.INTERNAL_SERVER_ERROR;
24
+ let code = "INTERNAL_ERROR";
25
+ let message = "Internal server error";
26
+
27
+ if (exception instanceof AppException) {
28
+ statusCode = exception.statusCode;
29
+ code = exception.code;
30
+ message = exception.message;
31
+ } else if (exception instanceof HttpException) {
32
+ statusCode = exception.getStatus();
33
+ code = "HTTP_ERROR";
34
+ message = exception.message;
35
+ }
36
+
37
+ const body: ErrorBody = {
38
+ success: false,
39
+ error: { code, message, statusCode, path: request.url, timestamp: new Date().toISOString() },
40
+ };
41
+ response.status(statusCode).json(body);
42
+ }
43
+ }
@@ -0,0 +1,12 @@
1
+ // Stable, language-independent error codes. The frontend branches on `code`,
2
+ // not on the human-readable message.
3
+ export class AppException extends Error {
4
+ constructor(
5
+ readonly code: string,
6
+ message: string,
7
+ readonly statusCode = 400,
8
+ ) {
9
+ super(message);
10
+ this.name = "AppException";
11
+ }
12
+ }
@@ -0,0 +1,23 @@
1
+ import { z } from "zod";
2
+
3
+ // Schema-validated environment. Fails fast at boot if something is wrong.
4
+ const schema = z.object({
5
+ NODE_ENV: z.string().default("development"),
6
+ PORT: z.coerce.number().default(3000),
7
+ CORS_ORIGIN: z.string().optional(),
8
+ POSTGRES_HOST: z.string().default("localhost"),
9
+ POSTGRES_PORT: z.coerce.number().default(5432),
10
+ POSTGRES_USER: z.string().default("podokit"),
11
+ POSTGRES_PASSWORD: z.string().default("podokit"),
12
+ POSTGRES_DB: z.string().default("podokit"),
13
+ });
14
+
15
+ export type AppEnv = z.infer<typeof schema>;
16
+
17
+ export function validateEnv(config: Record<string, unknown>): AppEnv {
18
+ const parsed = schema.safeParse(config);
19
+ if (!parsed.success) {
20
+ throw new Error(`Invalid environment:\n${parsed.error.toString()}`);
21
+ }
22
+ return parsed.data;
23
+ }