@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.
- package/README.md +17 -3
- package/dist/add.d.ts +40 -0
- package/dist/add.js +114 -0
- package/dist/create.d.ts +2 -1
- package/dist/create.js +3 -2
- package/dist/index.js +39 -1
- package/dist/prompt.d.ts +0 -1
- package/dist/prompt.js +6 -7
- package/dist/templates/fullstack-nest-svelte/README.md +20 -8
- package/dist/templates/fullstack-nest-svelte/apps/api/package.json +20 -8
- package/dist/templates/fullstack-nest-svelte/apps/api/src/app.module.ts +14 -1
- package/dist/templates/fullstack-nest-svelte/apps/api/src/config/env.validation.ts +20 -15
- package/dist/templates/fullstack-nest-svelte/apps/api/src/database/data-source.ts +19 -0
- package/dist/templates/fullstack-nest-svelte/apps/api/src/health/health.controller.ts +15 -5
- package/dist/templates/fullstack-nest-svelte/apps/api/src/main.ts +8 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/components.json +1 -1
- package/dist/templates/fullstack-nest-svelte/apps/web/package.json +10 -2
- package/dist/templates/fullstack-nest-svelte/apps/web/src/app.css +72 -8
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/alert/alert-action.svelte +20 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/alert/alert-description.svelte +23 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/alert/alert-title.svelte +23 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/alert/alert.svelte +43 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/alert/index.ts +17 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/avatar/avatar-badge.svelte +26 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/avatar/avatar-fallback.svelte +20 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/avatar/avatar-group-count.svelte +23 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/avatar/avatar-group.svelte +23 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/avatar/avatar-image.svelte +17 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/avatar/avatar.svelte +26 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/avatar/index.ts +22 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/badge/badge.svelte +49 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/badge/index.ts +2 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/button/button.svelte +82 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/button/index.ts +17 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card-action.svelte +23 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card-content.svelte +20 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card-description.svelte +20 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card-footer.svelte +20 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card-header.svelte +23 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card-title.svelte +20 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card.svelte +22 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/index.ts +25 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/checkbox/checkbox.svelte +39 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/checkbox/index.ts +6 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog-close.svelte +11 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog-content.svelte +48 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog-description.svelte +17 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog-footer.svelte +32 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog-header.svelte +20 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog-overlay.svelte +17 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog-portal.svelte +7 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog-title.svelte +17 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog-trigger.svelte +11 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog.svelte +7 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/index.ts +34 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte +16 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +44 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte +31 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte +22 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte +7 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte +27 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte +24 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte +7 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte +16 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +34 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte +17 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte +20 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +17 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +29 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte +7 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +7 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte +7 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/index.ts +54 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/input/index.ts +7 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/input/input.svelte +48 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/label/index.ts +7 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/label/label.svelte +20 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/index.ts +37 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-content.svelte +45 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-group-heading.svelte +21 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-group.svelte +17 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-item.svelte +40 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-label.svelte +20 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-portal.svelte +7 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-scroll-down-button.svelte +20 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-scroll-up-button.svelte +20 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-separator.svelte +18 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-trigger.svelte +29 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select.svelte +11 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/separator/index.ts +7 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/separator/separator.svelte +23 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/skeleton/index.ts +7 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/skeleton/skeleton.svelte +17 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/sonner/index.ts +1 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/sonner/sonner.svelte +34 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/table/index.ts +28 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/table/table-body.svelte +15 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/table/table-caption.svelte +20 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/table/table-cell.svelte +15 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/table/table-footer.svelte +20 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/table/table-head.svelte +15 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/table/table-header.svelte +20 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/table/table-row.svelte +15 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/table/table.svelte +17 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tabs/index.ts +18 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tabs/tabs-content.svelte +17 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tabs/tabs-list.svelte +40 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tabs/tabs-trigger.svelte +23 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tabs/tabs.svelte +19 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tooltip/index.ts +19 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tooltip/tooltip-content.svelte +52 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tooltip/tooltip-portal.svelte +7 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tooltip/tooltip-provider.svelte +7 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tooltip/tooltip-trigger.svelte +7 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tooltip/tooltip.svelte +7 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/i18n/ko.ts +5 -2
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/utils.ts +11 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/+page.svelte +19 -12
- package/dist/templates/modules/api-key-auth/files/apps/api/src/api-key/api-key.decorator.ts +9 -0
- package/dist/templates/modules/api-key-auth/files/apps/api/src/api-key/api-key.guard.ts +29 -0
- package/dist/templates/modules/api-key-auth/files/apps/api/src/api-key/api-key.module.ts +9 -0
- package/dist/templates/modules/api-key-auth/files/apps/api/src/machine/machine.controller.ts +14 -0
- package/dist/templates/modules/api-key-auth/module.manifest.json +19 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-log.entity.ts +25 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.controller.ts +16 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.interceptor.ts +39 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.module.ts +13 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/migrations/1720300000000-InitAuditLogs.ts +25 -0
- package/dist/templates/modules/audit-log/module.manifest.json +16 -0
- package/dist/templates/modules/auth/files/apps/api/src/account/account.controller.ts +13 -0
- package/dist/templates/modules/auth/files/apps/api/src/account/account.module.ts +5 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth.ts +36 -0
- package/dist/templates/modules/auth/files/apps/api/src/health/health.controller.ts +26 -0
- package/dist/templates/modules/auth/module.manifest.json +70 -0
- package/dist/templates/modules/bullmq/files/apps/api/src/jobs/demo.processor.ts +12 -0
- package/dist/templates/modules/bullmq/files/apps/api/src/jobs/dto/create-job.dto.ts +9 -0
- package/dist/templates/modules/bullmq/files/apps/api/src/jobs/jobs.controller.ts +29 -0
- package/dist/templates/modules/bullmq/files/apps/api/src/jobs/jobs.module.ts +15 -0
- package/dist/templates/modules/bullmq/files/apps/api/src/jobs/queue.ts +8 -0
- package/dist/templates/modules/bullmq/files/apps/api/src/jobs/worker.module.ts +20 -0
- package/dist/templates/modules/bullmq/files/apps/api/src/main-worker.ts +14 -0
- package/dist/templates/modules/bullmq/files/infra/docker/worker.compose.example.yml +18 -0
- package/dist/templates/modules/bullmq/files/infra/k3s/worker-deployment.yaml +22 -0
- package/dist/templates/modules/bullmq/module.manifest.json +36 -0
- package/dist/templates/modules/file-upload/files/apps/api/src/files/files.controller.ts +29 -0
- package/dist/templates/modules/file-upload/files/apps/api/src/files/files.module.ts +9 -0
- package/dist/templates/modules/file-upload/module.manifest.json +19 -0
- package/dist/templates/modules/job-progress/files/apps/api/src/progress/dto/start-job.dto.ts +11 -0
- package/dist/templates/modules/job-progress/files/apps/api/src/progress/job-progress.module.ts +13 -0
- package/dist/templates/modules/job-progress/files/apps/api/src/progress/progress.bridge.ts +19 -0
- package/dist/templates/modules/job-progress/files/apps/api/src/progress/progress.controller.ts +17 -0
- package/dist/templates/modules/job-progress/files/apps/api/src/progress/progress.processor.ts +25 -0
- package/dist/templates/modules/job-progress/module.manifest.json +23 -0
- package/dist/templates/modules/logging/files/apps/api/src/logging/logging.module.ts +29 -0
- package/dist/templates/modules/logging/module.manifest.json +27 -0
- package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/dto/put-object.dto.ts +8 -0
- package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/storage.config.ts +31 -0
- package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/storage.controller.ts +29 -0
- package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/storage.module.ts +11 -0
- package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/storage.service.ts +37 -0
- package/dist/templates/modules/object-storage-s3/files/infra/docker/minio.compose.yml +33 -0
- package/dist/templates/modules/object-storage-s3/module.manifest.json +31 -0
- package/dist/templates/modules/rate-limit/files/apps/api/src/rate-limit/rate-limit.module.ts +26 -0
- package/dist/templates/modules/rate-limit/module.manifest.json +25 -0
- package/dist/templates/modules/redis/files/apps/api/src/redis/cache.controller.ts +21 -0
- package/dist/templates/modules/redis/files/apps/api/src/redis/dto/set-cache.dto.ts +14 -0
- package/dist/templates/modules/redis/files/apps/api/src/redis/redis.module.ts +12 -0
- package/dist/templates/modules/redis/files/apps/api/src/redis/redis.service.ts +43 -0
- package/dist/templates/modules/redis/module.manifest.json +21 -0
- package/dist/templates/modules/sse/files/apps/api/src/events/dto/publish-event.dto.ts +8 -0
- package/dist/templates/modules/sse/files/apps/api/src/events/events.controller.ts +25 -0
- package/dist/templates/modules/sse/files/apps/api/src/events/events.module.ts +12 -0
- package/dist/templates/modules/sse/files/apps/api/src/events/events.service.ts +17 -0
- package/dist/templates/modules/sse/module.manifest.json +16 -0
- package/dist/templates/todo/README.md +40 -0
- package/dist/templates/todo/apps/api/Dockerfile +22 -0
- package/dist/templates/todo/apps/api/nest-cli.json +5 -0
- package/dist/templates/todo/apps/api/package.json +44 -0
- package/dist/templates/todo/apps/api/src/app.module.ts +22 -0
- package/dist/templates/todo/apps/api/src/common/all-exceptions.filter.ts +43 -0
- package/dist/templates/todo/apps/api/src/common/app-exception.ts +12 -0
- package/dist/templates/todo/apps/api/src/config/env.validation.ts +23 -0
- package/dist/templates/todo/apps/api/src/database/data-source.ts +19 -0
- package/dist/templates/todo/apps/api/src/health/health.controller.ts +23 -0
- package/dist/templates/todo/apps/api/src/health/health.module.ts +7 -0
- package/dist/templates/todo/apps/api/src/main.ts +29 -0
- package/dist/templates/todo/apps/api/src/migrations/1720100000000-InitTodos.ts +22 -0
- package/dist/templates/todo/apps/api/src/todos/dto/create-todo.dto.ts +10 -0
- package/dist/templates/todo/apps/api/src/todos/dto/update-todo.dto.ts +10 -0
- package/dist/templates/todo/apps/api/src/todos/todo.entity.ts +16 -0
- package/dist/templates/todo/apps/api/src/todos/todos.controller.ts +38 -0
- package/dist/templates/todo/apps/api/src/todos/todos.module.ts +12 -0
- package/dist/templates/todo/apps/api/src/todos/todos.service.ts +41 -0
- package/dist/templates/todo/apps/api/test/health.e2e-spec.ts +23 -0
- package/dist/templates/todo/apps/api/tsconfig.json +21 -0
- package/dist/templates/todo/apps/web/Dockerfile +22 -0
- package/dist/templates/todo/apps/web/components.json +15 -0
- package/dist/templates/todo/apps/web/package.json +36 -0
- package/dist/templates/todo/apps/web/src/app.css +81 -0
- package/dist/templates/todo/apps/web/src/app.d.ts +11 -0
- package/dist/templates/todo/apps/web/src/app.html +11 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/alert/alert-action.svelte +20 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/alert/alert-description.svelte +23 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/alert/alert-title.svelte +23 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/alert/alert.svelte +43 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/alert/index.ts +17 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/avatar/avatar-badge.svelte +26 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/avatar/avatar-fallback.svelte +20 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/avatar/avatar-group-count.svelte +23 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/avatar/avatar-group.svelte +23 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/avatar/avatar-image.svelte +17 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/avatar/avatar.svelte +26 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/avatar/index.ts +22 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/badge/badge.svelte +49 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/badge/index.ts +2 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/button/button.svelte +82 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/button/index.ts +17 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/card/card-action.svelte +23 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/card/card-content.svelte +20 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/card/card-description.svelte +20 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/card/card-footer.svelte +20 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/card/card-header.svelte +23 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/card/card-title.svelte +20 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/card/card.svelte +22 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/card/index.ts +25 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/checkbox/checkbox.svelte +39 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/checkbox/index.ts +6 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog-close.svelte +11 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog-content.svelte +48 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog-description.svelte +17 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog-footer.svelte +32 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog-header.svelte +20 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog-overlay.svelte +17 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog-portal.svelte +7 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog-title.svelte +17 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog-trigger.svelte +11 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog.svelte +7 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/index.ts +34 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte +16 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +44 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte +31 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte +22 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte +7 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte +27 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte +24 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte +7 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte +16 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +34 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte +17 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte +20 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +17 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +29 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte +7 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +7 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte +7 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/index.ts +54 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/input/index.ts +7 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/input/input.svelte +48 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/label/index.ts +7 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/label/label.svelte +20 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/select/index.ts +37 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-content.svelte +45 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-group-heading.svelte +21 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-group.svelte +17 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-item.svelte +40 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-label.svelte +20 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-portal.svelte +7 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-scroll-down-button.svelte +20 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-scroll-up-button.svelte +20 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-separator.svelte +18 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-trigger.svelte +29 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/select/select.svelte +11 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/separator/index.ts +7 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/separator/separator.svelte +23 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/skeleton/index.ts +7 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/skeleton/skeleton.svelte +17 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/sonner/index.ts +1 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/sonner/sonner.svelte +34 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/table/index.ts +28 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/table/table-body.svelte +15 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/table/table-caption.svelte +20 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/table/table-cell.svelte +15 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/table/table-footer.svelte +20 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/table/table-head.svelte +15 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/table/table-header.svelte +20 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/table/table-row.svelte +15 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/table/table.svelte +17 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/tabs/index.ts +18 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/tabs/tabs-content.svelte +17 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/tabs/tabs-list.svelte +40 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/tabs/tabs-trigger.svelte +23 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/tabs/tabs.svelte +19 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/tooltip/index.ts +19 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/tooltip/tooltip-content.svelte +52 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/tooltip/tooltip-portal.svelte +7 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/tooltip/tooltip-provider.svelte +7 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/tooltip/tooltip-trigger.svelte +7 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/tooltip/tooltip.svelte +7 -0
- package/dist/templates/todo/apps/web/src/lib/i18n/README.md +7 -0
- package/dist/templates/todo/apps/web/src/lib/i18n/en.ts +10 -0
- package/dist/templates/todo/apps/web/src/lib/i18n/ko.ts +11 -0
- package/dist/templates/todo/apps/web/src/lib/server/backend-proxy.ts +16 -0
- package/dist/templates/todo/apps/web/src/lib/utils.ts +11 -0
- package/dist/templates/todo/apps/web/src/routes/+layout.svelte +9 -0
- package/dist/templates/todo/apps/web/src/routes/+page.svelte +95 -0
- package/dist/templates/todo/apps/web/src/routes/api/health/+server.ts +12 -0
- package/dist/templates/todo/apps/web/src/routes/api/todos/+server.ts +24 -0
- package/dist/templates/todo/apps/web/src/routes/api/todos/[id]/+server.ts +24 -0
- package/dist/templates/todo/apps/web/static/.gitkeep +0 -0
- package/dist/templates/todo/apps/web/svelte.config.js +15 -0
- package/dist/templates/todo/apps/web/tsconfig.json +9 -0
- package/dist/templates/todo/apps/web/vite.config.ts +7 -0
- package/dist/templates/todo/dot-env.example +16 -0
- package/dist/templates/todo/dot-gitignore +9 -0
- package/dist/templates/todo/infra/docker/docker-compose.yml +29 -0
- package/dist/templates/todo/infra/k3s/api-deployment.yaml +24 -0
- package/dist/templates/todo/infra/k3s/configmap.yaml +10 -0
- package/dist/templates/todo/infra/k3s/ingress.yaml +18 -0
- package/dist/templates/todo/infra/k3s/namespace.yaml +4 -0
- package/dist/templates/todo/infra/k3s/secret.example.yaml +10 -0
- package/dist/templates/todo/infra/k3s/services.yaml +21 -0
- package/dist/templates/todo/infra/k3s/web-deployment.yaml +20 -0
- package/dist/templates/todo/package.json +13 -0
- package/dist/templates.d.ts +10 -0
- package/dist/templates.js +33 -0
- 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,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,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,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
|
+
}
|
package/dist/templates/modules/job-progress/files/apps/api/src/progress/job-progress.module.ts
ADDED
|
@@ -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
|
+
}
|
package/dist/templates/modules/job-progress/files/apps/api/src/progress/progress.controller.ts
ADDED
|
@@ -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
|
+
}
|