create-nexwire 4.0.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 +233 -0
- package/express/.dockerignore +14 -0
- package/express/.env +44 -0
- package/express/.env.example +42 -0
- package/express/.vscode/settings.json +26 -0
- package/express/LICENSE +21 -0
- package/express/README.md +211 -0
- package/express/biome.json +66 -0
- package/express/drizzle.config.ts +25 -0
- package/express/gitignore-stub +24 -0
- package/express/package.json +108 -0
- package/express/public/.gitkeep +0 -0
- package/express/src/config/app.ts +41 -0
- package/express/src/config/auth.ts +12 -0
- package/express/src/config/cache.ts +16 -0
- package/express/src/config/cookie.ts +15 -0
- package/express/src/config/cors.ts +12 -0
- package/express/src/config/database.ts +29 -0
- package/express/src/config/index.ts +83 -0
- package/express/src/config/jwt.ts +19 -0
- package/express/src/config/logging.ts +13 -0
- package/express/src/config/mail.ts +24 -0
- package/express/src/config/openapi.ts +54 -0
- package/express/src/config/queue.ts +30 -0
- package/express/src/config/rateLimit.ts +17 -0
- package/express/src/config/realtime.ts +15 -0
- package/express/src/config/redis.ts +18 -0
- package/express/src/config/security.ts +44 -0
- package/express/src/config/session.ts +17 -0
- package/express/src/config/storage.ts +22 -0
- package/express/src/config/validate.ts +40 -0
- package/express/src/database/migrations/sqlite/0000_init.sql +61 -0
- package/express/src/database/migrations/sqlite/meta/0000_snapshot.json +412 -0
- package/express/src/database/migrations/sqlite/meta/_journal.json +13 -0
- package/express/src/database/schema.ts +3 -0
- package/express/src/env.ts +76 -0
- package/express/src/framework/cache/cache.ts +81 -0
- package/express/src/framework/circuit-breaker/cb.ts +60 -0
- package/express/src/framework/database/connection.ts +196 -0
- package/express/src/framework/database/migrate-hooks.ts +87 -0
- package/express/src/framework/database/paginate.ts +227 -0
- package/express/src/framework/database/schema.ts +20 -0
- package/express/src/framework/database/seed.ts +139 -0
- package/express/src/framework/events/dispatcher.ts +49 -0
- package/express/src/framework/facade.ts +48 -0
- package/express/src/framework/http/app.ts +107 -0
- package/express/src/framework/http/cors.ts +7 -0
- package/express/src/framework/http/logger.ts +69 -0
- package/express/src/framework/http/metrics.ts +22 -0
- package/express/src/framework/http/openapi.ts +54 -0
- package/express/src/framework/http/ratelimiter.ts +16 -0
- package/express/src/framework/http/router.ts +178 -0
- package/express/src/framework/http/security.ts +23 -0
- package/express/src/framework/http/static.ts +23 -0
- package/express/src/framework/http/validation.ts +18 -0
- package/express/src/framework/kernel.ts +52 -0
- package/express/src/framework/maker-cli/db/core.mjs +288 -0
- package/express/src/framework/maker-cli/db/index.mjs +204 -0
- package/express/src/framework/maker-cli/deploy/core.mjs +1047 -0
- package/express/src/framework/maker-cli/deploy/index.mjs +91 -0
- package/express/src/framework/maker-cli/index.mjs +53 -0
- package/express/src/framework/maker-cli/middleware/core.mjs +47 -0
- package/express/src/framework/maker-cli/middleware/index.mjs +17 -0
- package/express/src/framework/maker-cli/module/core.mjs +684 -0
- package/express/src/framework/maker-cli/module/index.mjs +140 -0
- package/express/src/framework/maker-cli/runtime/build-ui.mjs +16 -0
- package/express/src/framework/maker-cli/runtime/core.mjs +543 -0
- package/express/src/framework/maker-cli/runtime/index.mjs +104 -0
- package/express/src/framework/maker-cli/runtime/redis-commander-patch.cjs +20 -0
- package/express/src/framework/maker-cli/stubs/controller/openapi.ts.stub +47 -0
- package/express/src/framework/maker-cli/stubs/controller/openapi.with-model.ts.stub +48 -0
- package/express/src/framework/maker-cli/stubs/controller/plain.ts.stub +54 -0
- package/express/src/framework/maker-cli/stubs/controller/schema.plain.ts.stub +14 -0
- package/express/src/framework/maker-cli/stubs/controller/schema.ts.stub +33 -0
- package/express/src/framework/maker-cli/stubs/deploy/Dockerfile.bun.stub +52 -0
- package/express/src/framework/maker-cli/stubs/deploy/Dockerfile.pnpm.stub +55 -0
- package/express/src/framework/maker-cli/stubs/deploy/Dockerfile.stub +51 -0
- package/express/src/framework/maker-cli/stubs/deploy/Dockerfile.yarn.stub +55 -0
- package/express/src/framework/maker-cli/stubs/deploy/README.stub +59 -0
- package/express/src/framework/maker-cli/stubs/deploy/compose/mysql.server.stub +45 -0
- package/express/src/framework/maker-cli/stubs/deploy/compose/postgres.server.stub +45 -0
- package/express/src/framework/maker-cli/stubs/deploy/compose/sqlite.stub +45 -0
- package/express/src/framework/maker-cli/stubs/deploy/env/mysql.server.stub +68 -0
- package/express/src/framework/maker-cli/stubs/deploy/env/postgres.server.stub +68 -0
- package/express/src/framework/maker-cli/stubs/deploy/env/sqlite.stub +67 -0
- package/express/src/framework/maker-cli/stubs/deploy/scripts/auto-migrate.sh.stub +41 -0
- package/express/src/framework/maker-cli/stubs/deploy/server/README.stub +78 -0
- package/express/src/framework/maker-cli/stubs/deploy/server/compose/noredis.stub +120 -0
- package/express/src/framework/maker-cli/stubs/deploy/server/compose/redis.dev.stub +133 -0
- package/express/src/framework/maker-cli/stubs/deploy/server/compose/redis.stub +131 -0
- package/express/src/framework/maker-cli/stubs/deploy/server/env/local.example.stub +11 -0
- package/express/src/framework/maker-cli/stubs/deploy/server/env/noredis.stub +25 -0
- package/express/src/framework/maker-cli/stubs/deploy/server/env/redis.stub +25 -0
- package/express/src/framework/maker-cli/stubs/deploy/server/nginx-vhost/README.stub +16 -0
- package/express/src/framework/maker-cli/stubs/deploy/server/nginx-vhost/app.example.com.stub +13 -0
- package/express/src/framework/maker-cli/stubs/deploy/server/pgadmin/servers.stub +14 -0
- package/express/src/framework/maker-cli/stubs/deploy/server/redis/redis.conf.stub +7 -0
- package/express/src/framework/maker-cli/stubs/deploy/supervisor/noredis.stub +54 -0
- package/express/src/framework/maker-cli/stubs/deploy/supervisor/redis.stub +70 -0
- package/express/src/framework/maker-cli/stubs/deploy/workflow/local.json.stub +25 -0
- package/express/src/framework/maker-cli/stubs/deploy/workflow/remote.json.stub +24 -0
- package/express/src/framework/maker-cli/stubs/example/console.ts.stub +34 -0
- package/express/src/framework/maker-cli/stubs/example/controller.ts.stub +418 -0
- package/express/src/framework/maker-cli/stubs/example/job.ts.stub +75 -0
- package/express/src/framework/maker-cli/stubs/example/route.api.ts.stub +207 -0
- package/express/src/framework/maker-cli/stubs/example/route.plain.ts.stub +18 -0
- package/express/src/framework/maker-cli/stubs/example/schema.plain.ts.stub +42 -0
- package/express/src/framework/maker-cli/stubs/example/schema.ts.stub +42 -0
- package/express/src/framework/maker-cli/stubs/job/name.ts.stub +25 -0
- package/express/src/framework/maker-cli/stubs/middleware/name.ts.stub +14 -0
- package/express/src/framework/maker-cli/stubs/model/name.mysql.ts.stub +9 -0
- package/express/src/framework/maker-cli/stubs/model/name.postgresql.ts.stub +9 -0
- package/express/src/framework/maker-cli/stubs/model/name.sqlite.ts.stub +9 -0
- package/express/src/framework/maker-cli/stubs/notification/controller.ts.stub +77 -0
- package/express/src/framework/maker-cli/stubs/notification/job.ts.stub +13 -0
- package/express/src/framework/maker-cli/stubs/notification/route.api.ts.stub +50 -0
- package/express/src/framework/maker-cli/stubs/notification/route.plain.ts.stub +12 -0
- package/express/src/framework/maker-cli/stubs/notification/schema.plain.ts.stub +26 -0
- package/express/src/framework/maker-cli/stubs/notification/schema.ts.stub +26 -0
- package/express/src/framework/maker-cli/stubs/route/api.ts.stub +80 -0
- package/express/src/framework/maker-cli/stubs/route/plain.ts.stub +11 -0
- package/express/src/framework/maker-cli/stubs/schedule/name.ts.stub +36 -0
- package/express/src/framework/maker-cli/stubs/seeder/name.ts.stub +20 -0
- package/express/src/framework/maker-cli/stubs/test/unit.ts.stub +22 -0
- package/express/src/framework/maker-cli/utils/env-config.mjs +44 -0
- package/express/src/framework/maker-cli/utils/env-db.mjs +61 -0
- package/express/src/framework/maker-cli/utils/file-ops.mjs +30 -0
- package/express/src/framework/maker-cli/utils/flags.mjs +14 -0
- package/express/src/framework/maker-cli/utils/help.mjs +50 -0
- package/express/src/framework/maker-cli/utils/naming.mjs +13 -0
- package/express/src/framework/maker-cli/utils/process.mjs +52 -0
- package/express/src/framework/modules/discover.ts +54 -0
- package/express/src/framework/modules/routes.ts +21 -0
- package/express/src/framework/notification/index.ts +116 -0
- package/express/src/framework/queue/clear.ts +22 -0
- package/express/src/framework/queue/queue.ts +342 -0
- package/express/src/framework/queue/ui.ts +187 -0
- package/express/src/framework/queue/worker.ts +61 -0
- package/express/src/framework/realtime/broadcast.ts +46 -0
- package/express/src/framework/realtime/index.ts +2 -0
- package/express/src/framework/realtime/socket-cookie.ts +71 -0
- package/express/src/framework/realtime/socket.ts +119 -0
- package/express/src/framework/realtime/types.ts +6 -0
- package/express/src/framework/realtime/ui.ts +16 -0
- package/express/src/framework/redis/client.ts +151 -0
- package/express/src/framework/runtime/runtime.ts +23 -0
- package/express/src/framework/runtime/server-adapter.ts +34 -0
- package/express/src/framework/scheduler/lock.ts +116 -0
- package/express/src/framework/scheduler/run.ts +48 -0
- package/express/src/framework/scheduler/scheduler.ts +155 -0
- package/express/src/framework/server.ts +177 -0
- package/express/src/framework/session/session.ts +98 -0
- package/express/src/framework/storage/storage.ts +785 -0
- package/express/src/framework/support/cookie.ts +62 -0
- package/express/src/framework/support/jwt.ts +33 -0
- package/express/src/framework/support/lifecycle.ts +35 -0
- package/express/src/framework/support/logger.ts +148 -0
- package/express/src/framework/support/mail.ts +48 -0
- package/express/src/framework/support/password.ts +23 -0
- package/express/src/framework/support/upload.ts +160 -0
- package/express/src/framework/support/url.ts +41 -0
- package/express/src/middlewares/auth-middleware.ts +98 -0
- package/express/src/middlewares/role-middleware.ts +25 -0
- package/express/src/modules/auth/controllers/auth.controller.ts +358 -0
- package/express/src/modules/auth/controllers/auth.helpers.ts +104 -0
- package/express/src/modules/auth/controllers/auth.schema.ts +102 -0
- package/express/src/modules/auth/controllers/role.controller.ts +15 -0
- package/express/src/modules/auth/database/models/notifications.ts +24 -0
- package/express/src/modules/auth/database/models/role.ts +14 -0
- package/express/src/modules/auth/database/models/user.ts +46 -0
- package/express/src/modules/auth/database/seeders/role.ts +18 -0
- package/express/src/modules/auth/database/seeders/user.ts +35 -0
- package/express/src/modules/auth/jobs/forgetpass.ts +20 -0
- package/express/src/modules/auth/jobs/registeruser.ts +23 -0
- package/express/src/modules/auth/jobs/verifyemail.ts +18 -0
- package/express/src/modules/auth/routes/api.ts +150 -0
- package/express/src/modules/auth/routes/role.ts +33 -0
- package/express/src/resources/index.html +17 -0
- package/express/src/resources/src/App.vue +5 -0
- package/express/src/resources/src/assets/css/styles.css +14761 -0
- package/express/src/resources/src/assets/css/styles.css.map +1 -0
- package/express/src/resources/src/assets/fonts/public-sans/public-sans-latin-ext.woff2 +0 -0
- package/express/src/resources/src/assets/fonts/public-sans/public-sans-latin.woff2 +0 -0
- package/express/src/resources/src/assets/images/favicon/favicon.ico +0 -0
- package/express/src/resources/src/assets/images/logo.png +0 -0
- package/express/src/resources/src/assets/scss/custom.css +217 -0
- package/express/src/resources/src/assets/scss/custom.css.map +1 -0
- package/express/src/resources/src/assets/scss/custom.scss +1346 -0
- package/express/src/resources/src/components/Button.vue +32 -0
- package/express/src/resources/src/components/Checkbox.vue +29 -0
- package/express/src/resources/src/components/Datepicker.vue +106 -0
- package/express/src/resources/src/components/FeatureButton.vue +53 -0
- package/express/src/resources/src/components/FloatButton.vue +33 -0
- package/express/src/resources/src/components/Href.vue +32 -0
- package/express/src/resources/src/components/Input.vue +223 -0
- package/express/src/resources/src/components/InputGroup.vue +145 -0
- package/express/src/resources/src/components/InputPasswordToggle.vue +213 -0
- package/express/src/resources/src/components/Modal.vue +55 -0
- package/express/src/resources/src/components/Pagebar.vue +26 -0
- package/express/src/resources/src/components/Select.vue +384 -0
- package/express/src/resources/src/components/Spinner.vue +42 -0
- package/express/src/resources/src/components/Switch.vue +65 -0
- package/express/src/resources/src/components/TextArea.vue +117 -0
- package/express/src/resources/src/components/Toast.vue +56 -0
- package/express/src/resources/src/components/datatable/DataTableSkeleton.vue +98 -0
- package/express/src/resources/src/components/datatable/Pagination.vue +147 -0
- package/express/src/resources/src/components/datatable/SelectOpption.vue +64 -0
- package/express/src/resources/src/components/datatable/index.vue +199 -0
- package/express/src/resources/src/composables/useAuth.ts +52 -0
- package/express/src/resources/src/helpers/nformatter.ts +10 -0
- package/express/src/resources/src/helpers/utils.ts +75 -0
- package/express/src/resources/src/layouts/AuthLayout.vue +20 -0
- package/express/src/resources/src/layouts/Layout/Footer.vue +23 -0
- package/express/src/resources/src/layouts/Layout/Header.vue +105 -0
- package/express/src/resources/src/layouts/Layout/Sidebar.vue +137 -0
- package/express/src/resources/src/layouts/Layout/index.vue +87 -0
- package/express/src/resources/src/main.ts +26 -0
- package/express/src/resources/src/pages/auth/forgetPassword.vue +82 -0
- package/express/src/resources/src/pages/auth/login.vue +99 -0
- package/express/src/resources/src/pages/auth/register.vue +133 -0
- package/express/src/resources/src/pages/auth/resetPassword.vue +118 -0
- package/express/src/resources/src/pages/auth/verifyEmail.vue +68 -0
- package/express/src/resources/src/pages/dashboard/index.vue +66 -0
- package/express/src/resources/src/plugins/axios.ts +31 -0
- package/express/src/resources/src/plugins/browserDetect.ts +55 -0
- package/express/src/resources/src/plugins/dialog.ts +216 -0
- package/express/src/resources/src/plugins/gum.ts +367 -0
- package/express/src/resources/src/plugins/pulse.ts +147 -0
- package/express/src/resources/src/plugins/routeProgress.ts +87 -0
- package/express/src/resources/src/router/index.ts +85 -0
- package/express/src/resources/src/stores/admin-ui.ts +148 -0
- package/express/src/resources/src/stores/auth.ts +164 -0
- package/express/src/resources/tsconfig.json +19 -0
- package/express/src/resources/vite.config.ts +46 -0
- package/express/src/storage/app/private/.gitkeep +0 -0
- package/express/src/storage/app/public/.gitkeep +0 -0
- package/express/src/storage/app/tmp/generated/.gitkeep +0 -0
- package/express/src/storage/database/nexwire.sqlite +0 -0
- package/express/src/storage/logs/.gitkeep +0 -0
- package/express/src/storage/tmp/.gitkeep +0 -0
- package/express/src/storage/trash/modules/.gitkeep +0 -0
- package/express/src/types/global.d.ts +26 -0
- package/express/tsconfig.json +19 -0
- package/express/vitest.config.ts +23 -0
- package/hono/.dockerignore +14 -0
- package/hono/.env +44 -0
- package/hono/.env.example +42 -0
- package/hono/.vscode/settings.json +26 -0
- package/hono/LICENSE +21 -0
- package/hono/README.md +211 -0
- package/hono/biome.json +76 -0
- package/hono/drizzle.config.ts +25 -0
- package/hono/gitignore-stub +24 -0
- package/hono/package.json +99 -0
- package/hono/public/.gitkeep +0 -0
- package/hono/src/config/app.ts +41 -0
- package/hono/src/config/auth.ts +12 -0
- package/hono/src/config/cache.ts +16 -0
- package/hono/src/config/cookie.ts +15 -0
- package/hono/src/config/cors.ts +12 -0
- package/hono/src/config/database.ts +29 -0
- package/hono/src/config/index.ts +83 -0
- package/hono/src/config/jwt.ts +19 -0
- package/hono/src/config/logging.ts +13 -0
- package/hono/src/config/mail.ts +24 -0
- package/hono/src/config/openapi.ts +54 -0
- package/hono/src/config/queue.ts +30 -0
- package/hono/src/config/rateLimit.ts +17 -0
- package/hono/src/config/realtime.ts +15 -0
- package/hono/src/config/redis.ts +18 -0
- package/hono/src/config/security.ts +44 -0
- package/hono/src/config/session.ts +17 -0
- package/hono/src/config/storage.ts +22 -0
- package/hono/src/config/validate.ts +40 -0
- package/hono/src/database/migrations/sqlite/0000_init.sql +61 -0
- package/hono/src/database/migrations/sqlite/meta/0000_snapshot.json +412 -0
- package/hono/src/database/migrations/sqlite/meta/_journal.json +13 -0
- package/hono/src/database/schema.ts +3 -0
- package/hono/src/env.ts +76 -0
- package/hono/src/framework/cache/cache.ts +81 -0
- package/hono/src/framework/circuit-breaker/cb.ts +60 -0
- package/hono/src/framework/database/connection.ts +196 -0
- package/hono/src/framework/database/migrate-hooks.ts +87 -0
- package/hono/src/framework/database/paginate.ts +253 -0
- package/hono/src/framework/database/schema.ts +20 -0
- package/hono/src/framework/database/seed.ts +139 -0
- package/hono/src/framework/events/dispatcher.ts +49 -0
- package/hono/src/framework/facade.ts +29 -0
- package/hono/src/framework/http/app.ts +109 -0
- package/hono/src/framework/http/cors.ts +19 -0
- package/hono/src/framework/http/logger.ts +81 -0
- package/hono/src/framework/http/metrics.ts +22 -0
- package/hono/src/framework/http/openapi.ts +42 -0
- package/hono/src/framework/http/ratelimiter.ts +172 -0
- package/hono/src/framework/http/router.ts +76 -0
- package/hono/src/framework/http/security.ts +23 -0
- package/hono/src/framework/http/static.ts +70 -0
- package/hono/src/framework/http/validation.ts +21 -0
- package/hono/src/framework/kernel.ts +48 -0
- package/hono/src/framework/maker-cli/db/core.mjs +288 -0
- package/hono/src/framework/maker-cli/db/index.mjs +204 -0
- package/hono/src/framework/maker-cli/deploy/core.mjs +1038 -0
- package/hono/src/framework/maker-cli/deploy/index.mjs +91 -0
- package/hono/src/framework/maker-cli/index.mjs +53 -0
- package/hono/src/framework/maker-cli/middleware/core.mjs +47 -0
- package/hono/src/framework/maker-cli/middleware/index.mjs +12 -0
- package/hono/src/framework/maker-cli/module/core.mjs +684 -0
- package/hono/src/framework/maker-cli/module/index.mjs +140 -0
- package/hono/src/framework/maker-cli/runtime/build-ui.mjs +17 -0
- package/hono/src/framework/maker-cli/runtime/core.mjs +543 -0
- package/hono/src/framework/maker-cli/runtime/index.mjs +104 -0
- package/hono/src/framework/maker-cli/runtime/redis-commander-patch.cjs +20 -0
- package/hono/src/framework/maker-cli/stubs/controller/openapi.ts.stub +57 -0
- package/hono/src/framework/maker-cli/stubs/controller/openapi.with-model.ts.stub +58 -0
- package/hono/src/framework/maker-cli/stubs/controller/plain.ts.stub +59 -0
- package/hono/src/framework/maker-cli/stubs/controller/schema.plain.ts.stub +14 -0
- package/hono/src/framework/maker-cli/stubs/controller/schema.ts.stub +33 -0
- package/hono/src/framework/maker-cli/stubs/deploy/Dockerfile.bun.stub +52 -0
- package/hono/src/framework/maker-cli/stubs/deploy/Dockerfile.pnpm.stub +55 -0
- package/hono/src/framework/maker-cli/stubs/deploy/Dockerfile.stub +51 -0
- package/hono/src/framework/maker-cli/stubs/deploy/Dockerfile.yarn.stub +55 -0
- package/hono/src/framework/maker-cli/stubs/deploy/README.stub +59 -0
- package/hono/src/framework/maker-cli/stubs/deploy/compose/mysql.server.stub +45 -0
- package/hono/src/framework/maker-cli/stubs/deploy/compose/postgres.server.stub +45 -0
- package/hono/src/framework/maker-cli/stubs/deploy/compose/sqlite.stub +45 -0
- package/hono/src/framework/maker-cli/stubs/deploy/env/mysql.server.stub +68 -0
- package/hono/src/framework/maker-cli/stubs/deploy/env/postgres.server.stub +68 -0
- package/hono/src/framework/maker-cli/stubs/deploy/env/sqlite.stub +67 -0
- package/hono/src/framework/maker-cli/stubs/deploy/scripts/auto-migrate.sh.stub +41 -0
- package/hono/src/framework/maker-cli/stubs/deploy/server/README.stub +78 -0
- package/hono/src/framework/maker-cli/stubs/deploy/server/compose/noredis.stub +120 -0
- package/hono/src/framework/maker-cli/stubs/deploy/server/compose/redis.dev.stub +133 -0
- package/hono/src/framework/maker-cli/stubs/deploy/server/compose/redis.stub +131 -0
- package/hono/src/framework/maker-cli/stubs/deploy/server/env/local.example.stub +11 -0
- package/hono/src/framework/maker-cli/stubs/deploy/server/env/noredis.stub +25 -0
- package/hono/src/framework/maker-cli/stubs/deploy/server/env/redis.stub +25 -0
- package/hono/src/framework/maker-cli/stubs/deploy/server/nginx-vhost/README.stub +16 -0
- package/hono/src/framework/maker-cli/stubs/deploy/server/nginx-vhost/app.example.com.stub +13 -0
- package/hono/src/framework/maker-cli/stubs/deploy/server/pgadmin/servers.stub +14 -0
- package/hono/src/framework/maker-cli/stubs/deploy/server/redis/redis.conf.stub +7 -0
- package/hono/src/framework/maker-cli/stubs/deploy/supervisor/noredis.stub +54 -0
- package/hono/src/framework/maker-cli/stubs/deploy/supervisor/redis.stub +70 -0
- package/hono/src/framework/maker-cli/stubs/deploy/workflow/local.json.stub +25 -0
- package/hono/src/framework/maker-cli/stubs/deploy/workflow/remote.json.stub +24 -0
- package/hono/src/framework/maker-cli/stubs/example/console.ts.stub +34 -0
- package/hono/src/framework/maker-cli/stubs/example/controller.ts.stub +505 -0
- package/hono/src/framework/maker-cli/stubs/example/job.ts.stub +75 -0
- package/hono/src/framework/maker-cli/stubs/example/route.api.ts.stub +207 -0
- package/hono/src/framework/maker-cli/stubs/example/route.plain.ts.stub +18 -0
- package/hono/src/framework/maker-cli/stubs/example/schema.plain.ts.stub +42 -0
- package/hono/src/framework/maker-cli/stubs/example/schema.ts.stub +42 -0
- package/hono/src/framework/maker-cli/stubs/job/name.ts.stub +25 -0
- package/hono/src/framework/maker-cli/stubs/middleware/name.ts.stub +14 -0
- package/hono/src/framework/maker-cli/stubs/model/name.mysql.ts.stub +9 -0
- package/hono/src/framework/maker-cli/stubs/model/name.postgresql.ts.stub +9 -0
- package/hono/src/framework/maker-cli/stubs/model/name.sqlite.ts.stub +9 -0
- package/hono/src/framework/maker-cli/stubs/notification/controller.ts.stub +86 -0
- package/hono/src/framework/maker-cli/stubs/notification/job.ts.stub +13 -0
- package/hono/src/framework/maker-cli/stubs/notification/route.api.ts.stub +50 -0
- package/hono/src/framework/maker-cli/stubs/notification/route.plain.ts.stub +12 -0
- package/hono/src/framework/maker-cli/stubs/notification/schema.plain.ts.stub +26 -0
- package/hono/src/framework/maker-cli/stubs/notification/schema.ts.stub +26 -0
- package/hono/src/framework/maker-cli/stubs/route/api.ts.stub +80 -0
- package/hono/src/framework/maker-cli/stubs/route/plain.ts.stub +11 -0
- package/hono/src/framework/maker-cli/stubs/schedule/name.ts.stub +36 -0
- package/hono/src/framework/maker-cli/stubs/seeder/name.ts.stub +20 -0
- package/hono/src/framework/maker-cli/stubs/test/unit.ts.stub +22 -0
- package/hono/src/framework/maker-cli/utils/env-config.mjs +44 -0
- package/hono/src/framework/maker-cli/utils/env-db.mjs +61 -0
- package/hono/src/framework/maker-cli/utils/file-ops.mjs +30 -0
- package/hono/src/framework/maker-cli/utils/flags.mjs +14 -0
- package/hono/src/framework/maker-cli/utils/help.mjs +50 -0
- package/hono/src/framework/maker-cli/utils/naming.mjs +13 -0
- package/hono/src/framework/maker-cli/utils/process.mjs +52 -0
- package/hono/src/framework/modules/discover.ts +54 -0
- package/hono/src/framework/modules/routes.ts +24 -0
- package/hono/src/framework/notification/index.ts +116 -0
- package/hono/src/framework/queue/clear.ts +22 -0
- package/hono/src/framework/queue/queue.ts +342 -0
- package/hono/src/framework/queue/ui.ts +214 -0
- package/hono/src/framework/queue/worker.ts +61 -0
- package/hono/src/framework/realtime/broadcast.ts +46 -0
- package/hono/src/framework/realtime/index.ts +2 -0
- package/hono/src/framework/realtime/socket-cookie.ts +68 -0
- package/hono/src/framework/realtime/socket.ts +196 -0
- package/hono/src/framework/realtime/types.ts +6 -0
- package/hono/src/framework/realtime/ui.ts +16 -0
- package/hono/src/framework/redis/client.ts +151 -0
- package/hono/src/framework/runtime/runtime.ts +23 -0
- package/hono/src/framework/runtime/server-adapter.ts +94 -0
- package/hono/src/framework/scheduler/lock.ts +116 -0
- package/hono/src/framework/scheduler/run.ts +48 -0
- package/hono/src/framework/scheduler/scheduler.ts +155 -0
- package/hono/src/framework/server.ts +183 -0
- package/hono/src/framework/session/session.ts +116 -0
- package/hono/src/framework/storage/storage.ts +785 -0
- package/hono/src/framework/support/cookie.ts +99 -0
- package/hono/src/framework/support/jwt.ts +45 -0
- package/hono/src/framework/support/lifecycle.ts +35 -0
- package/hono/src/framework/support/logger.ts +148 -0
- package/hono/src/framework/support/mail.ts +48 -0
- package/hono/src/framework/support/password.ts +23 -0
- package/hono/src/framework/support/url.ts +41 -0
- package/hono/src/middlewares/auth-middleware.ts +98 -0
- package/hono/src/middlewares/role-middleware.ts +25 -0
- package/hono/src/modules/auth/controllers/auth.controller.ts +413 -0
- package/hono/src/modules/auth/controllers/auth.helpers.ts +128 -0
- package/hono/src/modules/auth/controllers/auth.schema.ts +102 -0
- package/hono/src/modules/auth/controllers/role.controller.ts +25 -0
- package/hono/src/modules/auth/database/models/notifications.ts +24 -0
- package/hono/src/modules/auth/database/models/role.ts +14 -0
- package/hono/src/modules/auth/database/models/user.ts +46 -0
- package/hono/src/modules/auth/database/seeders/role.ts +18 -0
- package/hono/src/modules/auth/database/seeders/user.ts +35 -0
- package/hono/src/modules/auth/jobs/forgetpass.ts +20 -0
- package/hono/src/modules/auth/jobs/registeruser.ts +23 -0
- package/hono/src/modules/auth/jobs/verifyemail.ts +18 -0
- package/hono/src/modules/auth/routes/api.ts +150 -0
- package/hono/src/modules/auth/routes/role.ts +33 -0
- package/hono/src/resources/index.html +17 -0
- package/hono/src/resources/src/App.vue +5 -0
- package/hono/src/resources/src/assets/css/styles.css +14913 -0
- package/hono/src/resources/src/assets/css/styles.css.map +1 -0
- package/hono/src/resources/src/assets/fonts/public-sans/public-sans-latin-ext.woff2 +0 -0
- package/hono/src/resources/src/assets/fonts/public-sans/public-sans-latin.woff2 +0 -0
- package/hono/src/resources/src/assets/images/favicon/favicon.ico +0 -0
- package/hono/src/resources/src/assets/images/logo.png +0 -0
- package/hono/src/resources/src/assets/scss/custom.css +217 -0
- package/hono/src/resources/src/assets/scss/custom.css.map +1 -0
- package/hono/src/resources/src/assets/scss/custom.scss +1346 -0
- package/hono/src/resources/src/components/Button.vue +32 -0
- package/hono/src/resources/src/components/Checkbox.vue +29 -0
- package/hono/src/resources/src/components/Datepicker.vue +106 -0
- package/hono/src/resources/src/components/FeatureButton.vue +53 -0
- package/hono/src/resources/src/components/FloatButton.vue +33 -0
- package/hono/src/resources/src/components/Href.vue +32 -0
- package/hono/src/resources/src/components/Input.vue +234 -0
- package/hono/src/resources/src/components/InputGroup.vue +149 -0
- package/hono/src/resources/src/components/InputPasswordToggle.vue +222 -0
- package/hono/src/resources/src/components/Modal.vue +55 -0
- package/hono/src/resources/src/components/Pagebar.vue +26 -0
- package/hono/src/resources/src/components/Select.vue +415 -0
- package/hono/src/resources/src/components/Spinner.vue +42 -0
- package/hono/src/resources/src/components/Switch.vue +65 -0
- package/hono/src/resources/src/components/TextArea.vue +121 -0
- package/hono/src/resources/src/components/Toast.vue +56 -0
- package/hono/src/resources/src/components/datatable/DataTableSkeleton.vue +98 -0
- package/hono/src/resources/src/components/datatable/Pagination.vue +147 -0
- package/hono/src/resources/src/components/datatable/SelectOpption.vue +64 -0
- package/hono/src/resources/src/components/datatable/index.vue +199 -0
- package/hono/src/resources/src/composables/useAuth.ts +52 -0
- package/hono/src/resources/src/helpers/nformatter.ts +10 -0
- package/hono/src/resources/src/helpers/utils.ts +75 -0
- package/hono/src/resources/src/layouts/AuthLayout.vue +20 -0
- package/hono/src/resources/src/layouts/Layout/Footer.vue +23 -0
- package/hono/src/resources/src/layouts/Layout/Header.vue +105 -0
- package/hono/src/resources/src/layouts/Layout/Sidebar.vue +137 -0
- package/hono/src/resources/src/layouts/Layout/index.vue +87 -0
- package/hono/src/resources/src/main.ts +26 -0
- package/hono/src/resources/src/pages/auth/forgetPassword.vue +82 -0
- package/hono/src/resources/src/pages/auth/login.vue +99 -0
- package/hono/src/resources/src/pages/auth/register.vue +133 -0
- package/hono/src/resources/src/pages/auth/resetPassword.vue +118 -0
- package/hono/src/resources/src/pages/auth/verifyEmail.vue +68 -0
- package/hono/src/resources/src/pages/dashboard/index.vue +66 -0
- package/hono/src/resources/src/plugins/axios.ts +31 -0
- package/hono/src/resources/src/plugins/browserDetect.ts +55 -0
- package/hono/src/resources/src/plugins/dialog.ts +216 -0
- package/hono/src/resources/src/plugins/gum.ts +367 -0
- package/hono/src/resources/src/plugins/pulse.ts +147 -0
- package/hono/src/resources/src/plugins/routeProgress.ts +87 -0
- package/hono/src/resources/src/router/index.ts +85 -0
- package/hono/src/resources/src/stores/admin-ui.ts +148 -0
- package/hono/src/resources/src/stores/auth.ts +164 -0
- package/hono/src/resources/tsconfig.json +19 -0
- package/hono/src/resources/vite.config.ts +46 -0
- package/hono/src/storage/app/private/.gitkeep +0 -0
- package/hono/src/storage/app/public/.gitkeep +0 -0
- package/hono/src/storage/app/tmp/generated/.gitkeep +0 -0
- package/hono/src/storage/database/nexwire.sqlite +0 -0
- package/hono/src/storage/logs/.gitkeep +0 -0
- package/hono/src/storage/tmp/.gitkeep +0 -0
- package/hono/src/storage/trash/modules/.gitkeep +0 -0
- package/hono/src/types/global.d.ts +26 -0
- package/hono/tsconfig.json +20 -0
- package/hono/vitest.config.ts +23 -0
- package/package.json +238 -0
- package/src/index.js +204 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { Request, Response } from "express";
|
|
2
|
+
import { appConfig, cookieConfig, jwtConfig } from "@/config/index.js";
|
|
3
|
+
|
|
4
|
+
function originOf(url?: string) {
|
|
5
|
+
if (!url) return "";
|
|
6
|
+
try {
|
|
7
|
+
return new URL(url).origin;
|
|
8
|
+
} catch {
|
|
9
|
+
return "";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function needsCrossSiteCookie() {
|
|
14
|
+
const appOrigin = originOf(appConfig.url);
|
|
15
|
+
const frontendOrigin = originOf(appConfig.frontendUrl);
|
|
16
|
+
return Boolean(appOrigin && frontendOrigin && appOrigin !== frontendOrigin);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function baseOptions() {
|
|
20
|
+
const crossSiteCookie = needsCrossSiteCookie();
|
|
21
|
+
const sameSite: "none" | "lax" = crossSiteCookie ? "none" : "lax";
|
|
22
|
+
return {
|
|
23
|
+
httpOnly: true,
|
|
24
|
+
secure: crossSiteCookie,
|
|
25
|
+
sameSite,
|
|
26
|
+
path: "/"
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const cookie = {
|
|
31
|
+
async setAuth(res: Response, token: string) {
|
|
32
|
+
res.cookie(`${cookieConfig.name}_access`, token, {
|
|
33
|
+
...baseOptions(),
|
|
34
|
+
maxAge: jwtConfig.accessExpirySeconds * 1000,
|
|
35
|
+
signed: true
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
async setRefresh(res: Response, token: string, maxAge?: number) {
|
|
40
|
+
res.cookie(`${cookieConfig.name}_refresh`, token, {
|
|
41
|
+
...baseOptions(),
|
|
42
|
+
maxAge: (typeof maxAge === "number" ? maxAge : jwtConfig.refreshExpirySeconds) * 1000,
|
|
43
|
+
signed: true
|
|
44
|
+
});
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
async getAuth(req: Request) {
|
|
48
|
+
return req.signedCookies?.[`${cookieConfig.name}_access`];
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
async getRefresh(req: Request) {
|
|
52
|
+
return req.signedCookies?.[`${cookieConfig.name}_refresh`];
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
deleteAuth(res: Response) {
|
|
56
|
+
res.clearCookie(`${cookieConfig.name}_access`, { path: "/" });
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
deleteRefresh(res: Response) {
|
|
60
|
+
res.clearCookie(`${cookieConfig.name}_refresh`, { path: "/" });
|
|
61
|
+
}
|
|
62
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import jwtLib from "jsonwebtoken";
|
|
3
|
+
import { jwtConfig } from "@/config/index.js";
|
|
4
|
+
|
|
5
|
+
export const jwt = {
|
|
6
|
+
async generateToken(payload: any, type: "access" | "refresh", expirySeconds?: number) {
|
|
7
|
+
const now = Math.floor(Date.now() / 1000);
|
|
8
|
+
const exp =
|
|
9
|
+
now +
|
|
10
|
+
(typeof expirySeconds === "number"
|
|
11
|
+
? expirySeconds
|
|
12
|
+
: type === "refresh"
|
|
13
|
+
? jwtConfig.refreshExpirySeconds
|
|
14
|
+
: jwtConfig.accessExpirySeconds);
|
|
15
|
+
const secret = type === "refresh" ? jwtConfig.refreshSecret : jwtConfig.accessSecret;
|
|
16
|
+
const jti = type === "refresh" ? randomUUID() : undefined;
|
|
17
|
+
|
|
18
|
+
const tokenPayload = { ...payload, type, iat: now, exp, ...(jti ? { jti } : {}) };
|
|
19
|
+
const token = await jwtLib.sign(tokenPayload, secret, { algorithm: jwtConfig.algorithm });
|
|
20
|
+
|
|
21
|
+
return { token, jti, exp };
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
async verifyToken(token: string, type: "access" | "refresh") {
|
|
25
|
+
try {
|
|
26
|
+
const secret = type === "refresh" ? jwtConfig.refreshSecret : jwtConfig.accessSecret;
|
|
27
|
+
const payload = (await jwtLib.verify(token, secret, { algorithms: [jwtConfig.algorithm] })) as Record<string, unknown>;
|
|
28
|
+
return payload.type === type ? payload : null;
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const shutdownSignals = ["SIGINT", "SIGTERM"] as const;
|
|
2
|
+
export type ShutdownSignal = (typeof shutdownSignals)[number];
|
|
3
|
+
|
|
4
|
+
type ShutdownHandler = (signal: ShutdownSignal) => Promise<void>;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Why: Avoid duplicated graceful-shutdown wiring across runtime entrypoints.
|
|
8
|
+
* When: Server/worker/scheduler need signal handling.
|
|
9
|
+
* Where: Runtime bootstrap files.
|
|
10
|
+
* How: Registers SIGINT/SIGTERM handlers and forwards signal to async callback.
|
|
11
|
+
*/
|
|
12
|
+
export function registerShutdownSignals(handler: ShutdownHandler) {
|
|
13
|
+
for (const signal of shutdownSignals) {
|
|
14
|
+
process.on(signal, () => {
|
|
15
|
+
void handler(signal);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Why: Keep comma-separated CLI/env parsing consistent.
|
|
22
|
+
* When: Runtime options are provided as a single CSV string.
|
|
23
|
+
* Where: Worker and other entrypoints.
|
|
24
|
+
* How: Trims tokens, removes blanks, and falls back when empty.
|
|
25
|
+
*/
|
|
26
|
+
export function parseCsvOrFallback(value: string | undefined, fallback: string[]) {
|
|
27
|
+
if (!value) return fallback;
|
|
28
|
+
|
|
29
|
+
const parsed = value
|
|
30
|
+
.split(",")
|
|
31
|
+
.map((item) => item.trim())
|
|
32
|
+
.filter(Boolean);
|
|
33
|
+
|
|
34
|
+
return parsed.length ? parsed : fallback;
|
|
35
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { Writable } from "node:stream";
|
|
4
|
+
import pino from "pino";
|
|
5
|
+
import { appConfig, loggingConfig } from "@/config/index.js";
|
|
6
|
+
|
|
7
|
+
const logDir = path.resolve(process.cwd(), "src", "storage", "logs");
|
|
8
|
+
const MAX_FILE_BYTES = 10 * 1024 * 1024;
|
|
9
|
+
|
|
10
|
+
if (!fs.existsSync(logDir)) {
|
|
11
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const LEVEL_COLORS: Record<number, string> = {
|
|
15
|
+
10: "\x1b[90m",
|
|
16
|
+
20: "\x1b[90m",
|
|
17
|
+
30: "\x1b[32m",
|
|
18
|
+
40: "\x1b[33m",
|
|
19
|
+
50: "\x1b[31m",
|
|
20
|
+
60: "\x1b[31m"
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const RESET = "\x1b[0m";
|
|
24
|
+
|
|
25
|
+
function formatLine(chunk: string, colorize: boolean): string {
|
|
26
|
+
const line = chunk.trim();
|
|
27
|
+
if (!line) return "";
|
|
28
|
+
|
|
29
|
+
let record: Record<string, unknown>;
|
|
30
|
+
try {
|
|
31
|
+
record = JSON.parse(line);
|
|
32
|
+
} catch {
|
|
33
|
+
return colorize ? line : `${line}\n`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const time = typeof record.time === "number" ? new Date(record.time) : new Date(String(record.time));
|
|
37
|
+
const timestamp = Number.isNaN(time.getTime()) ? "" : `[${time.toLocaleTimeString("en-GB", { hour12: false })}] `;
|
|
38
|
+
const levelNumber = typeof record.level === "number" ? record.level : 30;
|
|
39
|
+
const label = pino.levels.labels[levelNumber] ?? "info";
|
|
40
|
+
const message = typeof record.msg === "string" ? record.msg : "";
|
|
41
|
+
|
|
42
|
+
const ignored = new Set(["time", "level", "msg", "pid", "hostname", "service"]);
|
|
43
|
+
const meta: Record<string, unknown> = {};
|
|
44
|
+
for (const [key, value] of Object.entries(record)) {
|
|
45
|
+
if (!ignored.has(key)) meta[key] = value;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const level = colorize ? `${LEVEL_COLORS[levelNumber] ?? ""}${label}${RESET}` : label;
|
|
49
|
+
const metaString = Object.keys(meta).length ? `\n${JSON.stringify(meta, null, 2)}` : "";
|
|
50
|
+
return `${timestamp}${level} -> ${message}${metaString}\n`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function consoleWriter() {
|
|
54
|
+
return new Writable({
|
|
55
|
+
write(chunk, _encoding, callback) {
|
|
56
|
+
const text = formatLine(chunk.toString("utf8"), true);
|
|
57
|
+
if (text) process.stdout.write(text);
|
|
58
|
+
callback();
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function rotate(filePath: string, maxFiles: number) {
|
|
64
|
+
for (let index = maxFiles - 1; index >= 1; index--) {
|
|
65
|
+
const from = `${filePath}.${index}`;
|
|
66
|
+
const to = `${filePath}.${index + 1}`;
|
|
67
|
+
if (fs.existsSync(from)) fs.renameSync(from, to);
|
|
68
|
+
}
|
|
69
|
+
if (fs.existsSync(filePath)) fs.renameSync(filePath, `${filePath}.1`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function fileWriter(filePath: string, maxFiles: number) {
|
|
73
|
+
let bytes = 0;
|
|
74
|
+
|
|
75
|
+
return new Writable({
|
|
76
|
+
write(chunk, _encoding, callback) {
|
|
77
|
+
const text = formatLine(chunk.toString("utf8"), false);
|
|
78
|
+
bytes += Buffer.byteLength(text);
|
|
79
|
+
if (bytes >= MAX_FILE_BYTES) {
|
|
80
|
+
rotate(filePath, maxFiles);
|
|
81
|
+
bytes = 0;
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
fs.appendFileSync(filePath, text);
|
|
85
|
+
} catch {
|
|
86
|
+
// best-effort file logging
|
|
87
|
+
}
|
|
88
|
+
callback();
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const base = pino(
|
|
94
|
+
{
|
|
95
|
+
level: loggingConfig.level,
|
|
96
|
+
base: { service: appConfig.name },
|
|
97
|
+
timestamp: pino.stdTimeFunctions.isoTime
|
|
98
|
+
},
|
|
99
|
+
pino.multistream([
|
|
100
|
+
{ stream: consoleWriter(), level: loggingConfig.level },
|
|
101
|
+
{ stream: fileWriter(path.join(logDir, "app.log"), 5), level: "info" },
|
|
102
|
+
{ stream: fileWriter(path.join(logDir, "fatal.log"), 3), level: "error" }
|
|
103
|
+
])
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
type CompatLogger = {
|
|
107
|
+
fatal: (message: string, meta?: Record<string, unknown>) => void;
|
|
108
|
+
error: (message: string, meta?: Record<string, unknown>) => void;
|
|
109
|
+
warn: (message: string, meta?: Record<string, unknown>) => void;
|
|
110
|
+
info: (message: string, meta?: Record<string, unknown>) => void;
|
|
111
|
+
debug: (message: string, meta?: Record<string, unknown>) => void;
|
|
112
|
+
trace: (message: string, meta?: Record<string, unknown>) => void;
|
|
113
|
+
child: (bindings: Record<string, unknown>) => CompatLogger;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
function compat(child: pino.Logger): CompatLogger {
|
|
117
|
+
const wrap = (log: (obj: object, msg?: string, ...args: unknown[]) => void) => (message: string, meta?: Record<string, unknown>) =>
|
|
118
|
+
log.call(child, meta ?? {}, message);
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
fatal: wrap(child.fatal),
|
|
122
|
+
error: wrap(child.error),
|
|
123
|
+
warn: wrap(child.warn),
|
|
124
|
+
info: wrap(child.info),
|
|
125
|
+
debug: wrap(child.debug),
|
|
126
|
+
trace: wrap(child.trace),
|
|
127
|
+
child: (bindings) => compat(child.child(bindings))
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Why: Shared structured logger for app/runtime/framework internals.
|
|
133
|
+
* When: Any code needs operational logs or error reporting.
|
|
134
|
+
* Where: Imported across framework and modules.
|
|
135
|
+
* How: Emits JSON via pino to a color console sink plus rotating app/fatal
|
|
136
|
+
* files, keeping the legacy `(message, meta)` call signature.
|
|
137
|
+
*/
|
|
138
|
+
export const logger: CompatLogger = compat(base);
|
|
139
|
+
|
|
140
|
+
process.on("uncaughtException", (error) => {
|
|
141
|
+
logger.error("Uncaught exception", { error: { name: error.name, message: error.message, stack: error.stack } });
|
|
142
|
+
process.exit(1);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
process.on("unhandledRejection", (reason) => {
|
|
146
|
+
const error = reason instanceof Error ? reason : new Error(String(reason));
|
|
147
|
+
logger.error("Unhandled rejection", { error: { name: error.name, message: error.message, stack: error.stack } });
|
|
148
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import nodemailer from "nodemailer";
|
|
2
|
+
import { mailConfig } from "@/config/index.js";
|
|
3
|
+
import { logger } from "@/framework/support/logger.js";
|
|
4
|
+
import { CircuitBreaker } from "@/framework/circuit-breaker/cb.js";
|
|
5
|
+
|
|
6
|
+
type MailPayload = {
|
|
7
|
+
to: string;
|
|
8
|
+
subject: string;
|
|
9
|
+
html?: string;
|
|
10
|
+
text?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const transport = nodemailer.createTransport({
|
|
14
|
+
host: mailConfig.host,
|
|
15
|
+
port: mailConfig.port,
|
|
16
|
+
secure: mailConfig.encryption === "ssl",
|
|
17
|
+
auth: mailConfig.username ? { user: mailConfig.username, pass: mailConfig.password } : undefined
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const mailBreaker = new CircuitBreaker();
|
|
21
|
+
|
|
22
|
+
export const mail = {
|
|
23
|
+
/**
|
|
24
|
+
* Why: Sends transactional email through configured SMTP transport.
|
|
25
|
+
* When: Features need notifications/password reset/signup email.
|
|
26
|
+
* Where: Jobs and event handlers.
|
|
27
|
+
* How: Uses nodemailer transport and respects the fail-silent setting.
|
|
28
|
+
*/
|
|
29
|
+
async sendMail(payload: MailPayload) {
|
|
30
|
+
try {
|
|
31
|
+
return await mailBreaker.exec(() =>
|
|
32
|
+
transport.sendMail({
|
|
33
|
+
from: mailConfig.fromAddress,
|
|
34
|
+
...payload
|
|
35
|
+
})
|
|
36
|
+
);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
logger.error("Mail send failed", {
|
|
39
|
+
to: payload.to,
|
|
40
|
+
subject: payload.subject,
|
|
41
|
+
error: error instanceof Error ? error.message : error
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
if (!mailConfig.failSilent) throw error;
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import bcrypt from "bcryptjs";
|
|
2
|
+
|
|
3
|
+
export const password = {
|
|
4
|
+
/**
|
|
5
|
+
* Why: Hashes plaintext password before persistence.
|
|
6
|
+
* When: Registration and password reset flows.
|
|
7
|
+
* Where: Auth controllers/seeders.
|
|
8
|
+
* How: Uses bcrypt with cost factor 10.
|
|
9
|
+
*/
|
|
10
|
+
async hashPassword(inputPassword: string) {
|
|
11
|
+
return await bcrypt.hash(inputPassword, 10);
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Why: Compares plaintext password to stored hash.
|
|
16
|
+
* When: Login/authentication checks.
|
|
17
|
+
* Where: Auth controllers/middleware.
|
|
18
|
+
* How: Uses bcrypt secure compare.
|
|
19
|
+
*/
|
|
20
|
+
async verifyPassword(inputPassword: string, hashedPassword: string) {
|
|
21
|
+
return await bcrypt.compare(inputPassword, hashedPassword);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import type { RequestHandler } from "express";
|
|
3
|
+
import createError from "http-errors";
|
|
4
|
+
import multer from "multer";
|
|
5
|
+
|
|
6
|
+
export type UploadOptions = {
|
|
7
|
+
/**
|
|
8
|
+
* Form field name that carries the file(s). Matches the client's FormData key.
|
|
9
|
+
*/
|
|
10
|
+
field: string;
|
|
11
|
+
/**
|
|
12
|
+
* When set, accepts an array of up to `multiple` files on that field.
|
|
13
|
+
* Omit for a single-file upload (parsed into `req.file`).
|
|
14
|
+
*/
|
|
15
|
+
multiple?: number;
|
|
16
|
+
/**
|
|
17
|
+
* Maximum allowed file size in bytes. Defaults to unlimited.
|
|
18
|
+
*/
|
|
19
|
+
maxSize?: number;
|
|
20
|
+
/**
|
|
21
|
+
* Allowed file extensions (lowercase, with dot), e.g. [".xlsx", ".xlsb"].
|
|
22
|
+
* Rejects others with a 422 message.
|
|
23
|
+
*/
|
|
24
|
+
allowedExtensions?: string[];
|
|
25
|
+
/**
|
|
26
|
+
* Allowed MIME types, e.g. ["application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"].
|
|
27
|
+
* When provided alongside extensions, a file must satisfy both.
|
|
28
|
+
*/
|
|
29
|
+
allowedMimeTypes?: string[];
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type UploadFieldsSpec = Array<{ name: string; maxCount?: number }>;
|
|
33
|
+
|
|
34
|
+
function humanSize(bytes: number): string {
|
|
35
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
36
|
+
let i = 0;
|
|
37
|
+
let size = bytes;
|
|
38
|
+
while (size >= 1024 && i < units.length - 1) {
|
|
39
|
+
size /= 1024;
|
|
40
|
+
i += 1;
|
|
41
|
+
}
|
|
42
|
+
return `${Math.round(size * 100) / 100} ${units[i]}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Why: Parses multipart uploads as a reusable Express middleware factory.
|
|
47
|
+
* When: Any route needs to accept file uploads (single, multiple, or labeled fields).
|
|
48
|
+
* Where: Route files via `.api(route, [requireRole(...), upload({...})], handler)`.
|
|
49
|
+
* How: Wraps `multer` (memory storage) so controllers receive `req.file` /
|
|
50
|
+
* `req.files` buffers and enforce size/extension/MIME rules; failures are
|
|
51
|
+
* normalized into readable 422 errors before the controller runs.
|
|
52
|
+
*/
|
|
53
|
+
export function upload(options: UploadOptions): RequestHandler {
|
|
54
|
+
const { field, multiple, maxSize, allowedExtensions, allowedMimeTypes } = options;
|
|
55
|
+
const maxCount = multiple ?? 1;
|
|
56
|
+
|
|
57
|
+
const parser = multer({
|
|
58
|
+
storage: multer.memoryStorage(),
|
|
59
|
+
limits: {
|
|
60
|
+
fileSize: maxSize ?? Infinity,
|
|
61
|
+
files: maxCount
|
|
62
|
+
},
|
|
63
|
+
fileFilter: (_req, file, cb) => {
|
|
64
|
+
const ext = path.extname(file.originalname).toLowerCase();
|
|
65
|
+
if (allowedExtensions?.length && !allowedExtensions.includes(ext)) {
|
|
66
|
+
cb(createError(422, `File must be ${allowedExtensions.join(" or ")}`));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (allowedMimeTypes?.length && !allowedMimeTypes.includes(file.mimetype)) {
|
|
70
|
+
cb(createError(422, `File type ${file.mimetype} is not allowed`));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
cb(null, true);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const parse = multiple ? parser.array(field, maxCount) : parser.single(field);
|
|
78
|
+
|
|
79
|
+
return (req, res, next) => {
|
|
80
|
+
parse(req, res, (error) => {
|
|
81
|
+
if (error) {
|
|
82
|
+
if (error instanceof multer.MulterError) {
|
|
83
|
+
if (error.code === "LIMIT_FILE_SIZE") {
|
|
84
|
+
next(createError(422, `File size must be less than ${humanSize(maxSize ?? 0)}`));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (error.code === "LIMIT_FILE_COUNT" || error.code === "LIMIT_UNEXPECTED_FILE") {
|
|
88
|
+
next(createError(422, `Too many files, max ${maxCount} allowed`));
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
next(createError(422, error.message));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
next(error);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const received = multiple ? Array.isArray(req.files) && req.files.length > 0 : Boolean(req.file);
|
|
99
|
+
|
|
100
|
+
if (!received) {
|
|
101
|
+
next(createError(422, "File required"));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
next();
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Why: Parses multipart forms carrying multiple labeled file fields at once.
|
|
112
|
+
* When: A form uploads several different inputs (e.g. avatar + document) in one request.
|
|
113
|
+
* Where: Route files via `.api(route, [fields([{ name: "avatar" }, { name: "docs", maxCount: 5 }])], handler)`.
|
|
114
|
+
* How: Uses `multer.fields()` and applies shared size/MIME/extension limits to every file.
|
|
115
|
+
*/
|
|
116
|
+
export function fields(spec: UploadFieldsSpec, options: Omit<UploadOptions, "field" | "multiple"> = {}): RequestHandler {
|
|
117
|
+
const { maxSize, allowedExtensions, allowedMimeTypes } = options;
|
|
118
|
+
|
|
119
|
+
const parser = multer({
|
|
120
|
+
storage: multer.memoryStorage(),
|
|
121
|
+
limits: { fileSize: maxSize ?? Infinity },
|
|
122
|
+
fileFilter: (_req, file, cb) => {
|
|
123
|
+
const ext = path.extname(file.originalname).toLowerCase();
|
|
124
|
+
if (allowedExtensions?.length && !allowedExtensions.includes(ext)) {
|
|
125
|
+
cb(createError(422, `File must be ${allowedExtensions.join(" or ")}`));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (allowedMimeTypes?.length && !allowedMimeTypes.includes(file.mimetype)) {
|
|
129
|
+
cb(createError(422, `File type ${file.mimetype} is not allowed`));
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
cb(null, true);
|
|
133
|
+
}
|
|
134
|
+
}).fields(spec);
|
|
135
|
+
|
|
136
|
+
return (req, res, next) => {
|
|
137
|
+
parser(req, res, (error) => {
|
|
138
|
+
if (error) {
|
|
139
|
+
if (error instanceof multer.MulterError) {
|
|
140
|
+
if (error.code === "LIMIT_FILE_SIZE") {
|
|
141
|
+
next(createError(422, `File size must be less than ${humanSize(maxSize ?? 0)}`));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
next(createError(422, error.message));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
next(error);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const files: Express.Multer.File[] = Object.values((req.files as Record<string, Express.Multer.File[]>) ?? {}).flat();
|
|
152
|
+
if (files.length === 0) {
|
|
153
|
+
next(createError(422, "File required"));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
next();
|
|
158
|
+
});
|
|
159
|
+
};
|
|
160
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { appConfig } from "@/config/index.js";
|
|
2
|
+
import { hasUiBuild } from "@/framework/http/static.js";
|
|
3
|
+
|
|
4
|
+
export const urls = {
|
|
5
|
+
/**
|
|
6
|
+
* Why: Returns normalized app base URL without trailing slash.
|
|
7
|
+
* When: Building absolute links.
|
|
8
|
+
* Where: Mail/reset URL generation.
|
|
9
|
+
* How: Trims trailing slash from the configured app URL.
|
|
10
|
+
*/
|
|
11
|
+
appUrl() {
|
|
12
|
+
return appConfig.url.replace(/\/$/, "");
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Why: Builds absolute URL pointing at the SPA page that handles the path.
|
|
17
|
+
* When: Creating links for emails / external callbacks (reset, verify).
|
|
18
|
+
* Where: Auth/password reset and integrations.
|
|
19
|
+
* How: Three independent knobs decide the origin —
|
|
20
|
+
* appConfig.frontendUrl: the SPA is a separate build/origin (dev Vite
|
|
21
|
+
* dev server, or a separately deployed SPA). Link there.
|
|
22
|
+
* appConfig.uiEnabled + hasUiBuild(): the framework serves the built
|
|
23
|
+
* SPA itself, so the page resolves to the app origin.
|
|
24
|
+
* appConfig.uiEnabled + no UI build: the Vite dev server is still
|
|
25
|
+
* running (UI not built yet) — use the origin the maker-cli
|
|
26
|
+
* injected (NEXWIRE_FRONTEND_URL, dev Vite dev server).
|
|
27
|
+
* When BOTH origin knobs are unset (API-only mode) there is no SPA to
|
|
28
|
+
* point at — fall back to the app URL so the link is valid.
|
|
29
|
+
*/
|
|
30
|
+
url(path = "") {
|
|
31
|
+
const base =
|
|
32
|
+
appConfig.frontendUrl || // explicit SPA origin — wins
|
|
33
|
+
(appConfig.uiEnabled
|
|
34
|
+
? hasUiBuild()
|
|
35
|
+
? urls.appUrl() // built SPA, framework serves it — same origin
|
|
36
|
+
: process.env.NEXWIRE_FRONTEND_URL || urls.appUrl() // dev Vite dev server — injected by maker-cli
|
|
37
|
+
: appConfig.url); // API-only
|
|
38
|
+
if (!path) return base;
|
|
39
|
+
return `${base}${path.startsWith("/") ? path : `/${path}`}`;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { eq } from "drizzle-orm";
|
|
2
|
+
import { type NextFunction, type Request, type Response } from "express";
|
|
3
|
+
import { cookie, db, jwt, HttpStatusCodes } from "@/framework/facade.js";
|
|
4
|
+
import { refreshTokens, users } from "@/modules/auth/database/models/user.js";
|
|
5
|
+
|
|
6
|
+
export async function authMiddleware(req: Request, res: Response, next: NextFunction) {
|
|
7
|
+
const accessToken = await cookie.getAuth(req);
|
|
8
|
+
|
|
9
|
+
if (accessToken) {
|
|
10
|
+
const accessPayload = await jwt.verifyToken(accessToken, "access");
|
|
11
|
+
|
|
12
|
+
if (accessPayload) {
|
|
13
|
+
const user = await db.query.users.findFirst({
|
|
14
|
+
where: eq(users.id, accessPayload.id as number),
|
|
15
|
+
with: { role: true }
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
if (!user) {
|
|
19
|
+
cookie.deleteAuth(res);
|
|
20
|
+
cookie.deleteRefresh(res);
|
|
21
|
+
return res.status(HttpStatusCodes.UNAUTHORIZED).json({ message: "Unauthorized" });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
res.locals.auth = {
|
|
25
|
+
...accessPayload,
|
|
26
|
+
id: user.id,
|
|
27
|
+
email: user.email,
|
|
28
|
+
roleId: user.role?.id ?? null,
|
|
29
|
+
role: user.role?.name ?? null
|
|
30
|
+
};
|
|
31
|
+
return next();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const refreshToken = await cookie.getRefresh(req);
|
|
36
|
+
|
|
37
|
+
if (!refreshToken) {
|
|
38
|
+
cookie.deleteAuth(res);
|
|
39
|
+
cookie.deleteRefresh(res);
|
|
40
|
+
return res.status(HttpStatusCodes.UNAUTHORIZED).json({ message: "Unauthorized" });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const refreshPayload = await jwt.verifyToken(refreshToken, "refresh");
|
|
44
|
+
|
|
45
|
+
if (!refreshPayload?.jti) {
|
|
46
|
+
cookie.deleteAuth(res);
|
|
47
|
+
cookie.deleteRefresh(res);
|
|
48
|
+
return res.status(HttpStatusCodes.UNAUTHORIZED).json({ message: "Invalid token" });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const storedToken = await db.query.refreshTokens.findFirst({
|
|
52
|
+
where: eq(refreshTokens.jti, refreshPayload.jti as string)
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
if (!storedToken || storedToken.revoked) {
|
|
56
|
+
cookie.deleteAuth(res);
|
|
57
|
+
cookie.deleteRefresh(res);
|
|
58
|
+
return res.status(HttpStatusCodes.UNAUTHORIZED).json({ message: "Invalid token" });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (storedToken.expiresAt.getTime() < Date.now()) {
|
|
62
|
+
await db.delete(refreshTokens).where(eq(refreshTokens.id, storedToken.id));
|
|
63
|
+
cookie.deleteAuth(res);
|
|
64
|
+
cookie.deleteRefresh(res);
|
|
65
|
+
return res.status(HttpStatusCodes.UNAUTHORIZED).json({ message: "Invalid token" });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const user = await db.query.users.findFirst({
|
|
69
|
+
where: eq(users.id, refreshPayload.id as number),
|
|
70
|
+
with: { role: true }
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
if (!user) {
|
|
74
|
+
cookie.deleteAuth(res);
|
|
75
|
+
cookie.deleteRefresh(res);
|
|
76
|
+
return res.status(HttpStatusCodes.UNAUTHORIZED).json({ message: "Unauthorized" });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const newAccessToken = await jwt.generateToken(
|
|
80
|
+
{
|
|
81
|
+
id: user.id,
|
|
82
|
+
email: user.email,
|
|
83
|
+
roleId: user.role?.id ?? null,
|
|
84
|
+
role: user.role?.name ?? null
|
|
85
|
+
},
|
|
86
|
+
"access"
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
await cookie.setAuth(res, newAccessToken.token);
|
|
90
|
+
res.locals.auth = {
|
|
91
|
+
id: user.id,
|
|
92
|
+
email: user.email,
|
|
93
|
+
roleId: user.role?.id ?? null,
|
|
94
|
+
role: user.role?.name ?? null,
|
|
95
|
+
type: "access"
|
|
96
|
+
};
|
|
97
|
+
return next();
|
|
98
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { NextFunction, Request, Response } from "express";
|
|
2
|
+
import { HttpStatusCodes } from "@/framework/facade.js";
|
|
3
|
+
|
|
4
|
+
type AuthPayload = {
|
|
5
|
+
role?: string | null;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export function requireRole(...allowedRoles: string[]) {
|
|
9
|
+
const roles = allowedRoles.map((role) => role.toLowerCase());
|
|
10
|
+
|
|
11
|
+
return async (_req: Request, res: Response, next: NextFunction) => {
|
|
12
|
+
const auth = res.locals.auth as AuthPayload | undefined;
|
|
13
|
+
|
|
14
|
+
if (!auth) {
|
|
15
|
+
return res.status(HttpStatusCodes.UNAUTHORIZED).json({ message: "Unauthorized" });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const userRole = String(auth.role ?? "").toLowerCase();
|
|
19
|
+
if (!roles.includes(userRole)) {
|
|
20
|
+
return res.status(HttpStatusCodes.FORBIDDEN).json({ message: "Forbidden: Insufficient permissions" });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return next();
|
|
24
|
+
};
|
|
25
|
+
}
|