@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,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
|
+
}
|
package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/storage.controller.ts
ADDED
|
@@ -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 {}
|
package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/storage.service.ts
ADDED
|
@@ -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,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,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
|
+
}
|