create-nexwire 4.3.0 → 4.4.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 +10 -5
- package/express/src/framework/facade.ts +1 -0
- package/express/src/framework/http/logger.ts +13 -4
- package/express/src/framework/maker-cli/runtime/core.mjs +1 -2
- package/express/src/framework/notification/channels/mail.ts +8 -6
- package/express/src/middlewares/auth-middleware.ts +1 -2
- package/express/src/middlewares/role-middleware.ts +3 -13
- package/express/src/modules/auth/routes/index.ts +1 -2
- package/express/src/providers/notification.ts +2 -2
- package/express/src/resources/src/components/Switch.vue +60 -65
- package/express/src/resources/src/pages/auth/forgetPassword.vue +23 -30
- package/express/src/resources/src/pages/auth/login.vue +25 -28
- package/express/src/resources/src/pages/auth/register.vue +35 -39
- package/express/src/resources/src/pages/auth/resetPassword.vue +29 -37
- package/express/src/resources/src/pages/auth/verifyEmail.vue +15 -19
- package/express/src/resources/src/plugins/gum.ts +519 -203
- package/express/src/resources/src/stores/auth.ts +9 -6
- package/express/src/storage/database/nexwire.sqlite +0 -0
- package/fastify/.dockerignore +14 -0
- package/fastify/.env +44 -0
- package/fastify/.env.example +42 -0
- package/fastify/.vscode/extensions.json +6 -0
- package/fastify/.vscode/settings.json +37 -0
- package/fastify/LICENSE +21 -0
- package/fastify/README.md +232 -0
- package/fastify/biome.json +99 -0
- package/fastify/drizzle.config.ts +25 -0
- package/fastify/gitignore-stub +31 -0
- package/fastify/package.json +130 -0
- package/fastify/public/.gitkeep +0 -0
- package/fastify/src/config/app.ts +41 -0
- package/fastify/src/config/auth.ts +12 -0
- package/fastify/src/config/cache.ts +16 -0
- package/fastify/src/config/cookie.ts +15 -0
- package/fastify/src/config/cors.ts +12 -0
- package/fastify/src/config/database.ts +48 -0
- package/fastify/src/config/index.ts +83 -0
- package/fastify/src/config/jwt.ts +19 -0
- package/fastify/src/config/logging.ts +13 -0
- package/fastify/src/config/mail.ts +24 -0
- package/fastify/src/config/openapi.ts +36 -0
- package/fastify/src/config/queue.ts +30 -0
- package/fastify/src/config/rateLimit.ts +17 -0
- package/fastify/src/config/realtime.ts +15 -0
- package/fastify/src/config/redis.ts +18 -0
- package/fastify/src/config/security.ts +44 -0
- package/fastify/src/config/session.ts +17 -0
- package/fastify/src/config/storage.ts +22 -0
- package/fastify/src/config/validate.ts +50 -0
- package/fastify/src/database/migrations/sqlite/0000_init.sql +48 -0
- package/fastify/src/database/migrations/sqlite/meta/0000_snapshot.json +324 -0
- package/fastify/src/database/migrations/sqlite/meta/_journal.json +13 -0
- package/fastify/src/database/schema.ts +2 -0
- package/fastify/src/env.ts +76 -0
- package/fastify/src/framework/cache/cache.ts +114 -0
- package/fastify/src/framework/circuit-breaker/cb.ts +88 -0
- package/fastify/src/framework/database/app-schema.ts +37 -0
- package/fastify/src/framework/database/connection.ts +223 -0
- package/fastify/src/framework/database/migrate-hooks.ts +87 -0
- package/fastify/src/framework/database/paginate.ts +226 -0
- package/fastify/src/framework/database/schema.ts +20 -0
- package/fastify/src/framework/database/seed.ts +152 -0
- package/fastify/src/framework/events/dispatcher.ts +61 -0
- package/fastify/src/framework/facade.ts +96 -0
- package/fastify/src/framework/http/app.ts +176 -0
- package/fastify/src/framework/http/cors.ts +13 -0
- package/fastify/src/framework/http/fastify.d.ts +13 -0
- package/fastify/src/framework/http/logger.ts +110 -0
- package/fastify/src/framework/http/metrics.ts +22 -0
- package/fastify/src/framework/http/openapi.ts +48 -0
- package/fastify/src/framework/http/ratelimiter.ts +47 -0
- package/fastify/src/framework/http/security.ts +25 -0
- package/fastify/src/framework/http/static.ts +83 -0
- package/fastify/src/framework/http/validation.ts +49 -0
- package/fastify/src/framework/kernel.ts +43 -0
- package/fastify/src/framework/maker-cli/db/core.mjs +291 -0
- package/fastify/src/framework/maker-cli/db/index.mjs +204 -0
- package/fastify/src/framework/maker-cli/deploy/core.mjs +1046 -0
- package/fastify/src/framework/maker-cli/deploy/index.mjs +91 -0
- package/fastify/src/framework/maker-cli/index.mjs +65 -0
- package/fastify/src/framework/maker-cli/middleware/core.mjs +41 -0
- package/fastify/src/framework/maker-cli/middleware/index.mjs +17 -0
- package/fastify/src/framework/maker-cli/module/core.mjs +1066 -0
- package/fastify/src/framework/maker-cli/module/index.mjs +211 -0
- package/fastify/src/framework/maker-cli/provider/core.mjs +49 -0
- package/fastify/src/framework/maker-cli/provider/index.mjs +12 -0
- package/fastify/src/framework/maker-cli/runtime/build-ui.mjs +16 -0
- package/fastify/src/framework/maker-cli/runtime/core.mjs +560 -0
- package/fastify/src/framework/maker-cli/runtime/index.mjs +120 -0
- package/fastify/src/framework/maker-cli/runtime/redis-commander-patch.cjs +20 -0
- package/fastify/src/framework/maker-cli/stubs/controller/openapi.ts.stub +74 -0
- package/fastify/src/framework/maker-cli/stubs/controller/plain.ts.stub +77 -0
- package/fastify/src/framework/maker-cli/stubs/controller/standalone.ts.stub +51 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/Dockerfile.bun.stub +52 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/Dockerfile.pnpm.stub +55 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/Dockerfile.stub +51 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/Dockerfile.yarn.stub +55 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/README.stub +59 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/compose/mysql.server.stub +45 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/compose/postgres.server.stub +45 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/compose/sqlite.stub +45 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/env/mysql.server.stub +68 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/env/postgres.server.stub +68 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/env/sqlite.stub +67 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/scripts/auto-migrate.sh.stub +41 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/server/README.stub +78 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/server/compose/noredis.stub +120 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/server/compose/redis.dev.stub +133 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/server/compose/redis.stub +131 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/server/env/local.example.stub +11 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/server/env/noredis.stub +25 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/server/env/redis.stub +25 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/server/nginx-vhost/README.stub +16 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/server/nginx-vhost/app.example.com.stub +13 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/server/pgadmin/servers.stub +14 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/server/redis/redis.conf.stub +7 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/supervisor/noredis.stub +54 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/supervisor/redis.stub +70 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/workflow/local.json.stub +25 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/workflow/remote.json.stub +24 -0
- package/fastify/src/framework/maker-cli/stubs/facade/name.ts.stub +12 -0
- package/fastify/src/framework/maker-cli/stubs/helper/name.ts.stub +14 -0
- package/fastify/src/framework/maker-cli/stubs/job/name.ts.stub +25 -0
- package/fastify/src/framework/maker-cli/stubs/middleware/name.local.ts.stub +15 -0
- package/fastify/src/framework/maker-cli/stubs/middleware/name.ts.stub +15 -0
- package/fastify/src/framework/maker-cli/stubs/model/name.mysql.ts.stub +10 -0
- package/fastify/src/framework/maker-cli/stubs/model/name.postgresql.ts.stub +10 -0
- package/fastify/src/framework/maker-cli/stubs/model/name.sqlite.ts.stub +10 -0
- package/fastify/src/framework/maker-cli/stubs/notification/controller.ts.stub +79 -0
- package/fastify/src/framework/maker-cli/stubs/notification/job.ts.stub +11 -0
- package/fastify/src/framework/maker-cli/stubs/notification/model.ts.stub +29 -0
- package/fastify/src/framework/maker-cli/stubs/notification/provider.ts.stub +174 -0
- package/fastify/src/framework/maker-cli/stubs/notification/route.api.ts.stub +71 -0
- package/fastify/src/framework/maker-cli/stubs/notification/route.plain.ts.stub +15 -0
- package/fastify/src/framework/maker-cli/stubs/notification/schema.plain.ts.stub +26 -0
- package/fastify/src/framework/maker-cli/stubs/notification/schema.ts.stub +26 -0
- package/fastify/src/framework/maker-cli/stubs/provider/module-barrel.ts.stub +19 -0
- package/fastify/src/framework/maker-cli/stubs/provider/module.ts.stub +21 -0
- package/fastify/src/framework/maker-cli/stubs/provider/name.ts.stub +21 -0
- package/fastify/src/framework/maker-cli/stubs/route/api.ts.stub +101 -0
- package/fastify/src/framework/maker-cli/stubs/route/plain.ts.stub +23 -0
- package/fastify/src/framework/maker-cli/stubs/route/standalone.api.ts.stub +120 -0
- package/fastify/src/framework/maker-cli/stubs/route/standalone.plain.ts.stub +50 -0
- package/fastify/src/framework/maker-cli/stubs/schedule/name.ts.stub +36 -0
- package/fastify/src/framework/maker-cli/stubs/schema/name.plain.ts.stub +14 -0
- package/fastify/src/framework/maker-cli/stubs/schema/name.ts.stub +33 -0
- package/fastify/src/framework/maker-cli/stubs/seeder/name.ts.stub +18 -0
- package/fastify/src/framework/maker-cli/stubs/seeder/standalone.ts.stub +22 -0
- package/fastify/src/framework/maker-cli/stubs/service/name.plain.ts.stub +67 -0
- package/fastify/src/framework/maker-cli/stubs/service/name.ts.stub +65 -0
- package/fastify/src/framework/maker-cli/stubs/test/unit.ts.stub +22 -0
- package/fastify/src/framework/maker-cli/stubs/type/name.ts.stub +22 -0
- package/fastify/src/framework/maker-cli/utils/env-config.mjs +44 -0
- package/fastify/src/framework/maker-cli/utils/env-db.mjs +61 -0
- package/fastify/src/framework/maker-cli/utils/file-ops.mjs +30 -0
- package/fastify/src/framework/maker-cli/utils/flags.mjs +14 -0
- package/fastify/src/framework/maker-cli/utils/help.mjs +379 -0
- package/fastify/src/framework/maker-cli/utils/naming.mjs +119 -0
- package/fastify/src/framework/maker-cli/utils/process.mjs +52 -0
- package/fastify/src/framework/maker-cli/utils/project-commands.mjs +201 -0
- package/fastify/src/framework/modules/discover.ts +57 -0
- package/fastify/src/framework/modules/routes.ts +31 -0
- package/fastify/src/framework/notification/channels/broadcast.ts +15 -0
- package/fastify/src/framework/notification/channels/mail.ts +42 -0
- package/fastify/src/framework/notification/contracts.ts +29 -0
- package/fastify/src/framework/notification/index.ts +59 -0
- package/fastify/src/framework/notification/registry.ts +15 -0
- package/fastify/src/framework/providers/adapter.ts +149 -0
- package/fastify/src/framework/providers/contracts.ts +126 -0
- package/fastify/src/framework/providers/loader.ts +105 -0
- package/fastify/src/framework/providers/registry.ts +88 -0
- package/fastify/src/framework/queue/clear.ts +22 -0
- package/fastify/src/framework/queue/queue.ts +401 -0
- package/fastify/src/framework/queue/ui.ts +194 -0
- package/fastify/src/framework/queue/worker.ts +61 -0
- package/fastify/src/framework/realtime/broadcast.ts +70 -0
- package/fastify/src/framework/realtime/index.ts +2 -0
- package/fastify/src/framework/realtime/socket-cookie.ts +72 -0
- package/fastify/src/framework/realtime/socket.ts +149 -0
- package/fastify/src/framework/realtime/types.ts +6 -0
- package/fastify/src/framework/realtime/ui.ts +16 -0
- package/fastify/src/framework/redis/client.ts +148 -0
- package/fastify/src/framework/runtime/runtime.ts +23 -0
- package/fastify/src/framework/runtime/server-adapter.ts +33 -0
- package/fastify/src/framework/scheduler/lock.ts +128 -0
- package/fastify/src/framework/scheduler/run.ts +48 -0
- package/fastify/src/framework/scheduler/scheduler.ts +155 -0
- package/fastify/src/framework/server.ts +162 -0
- package/fastify/src/framework/session/session.ts +160 -0
- package/fastify/src/framework/storage/storage.ts +744 -0
- package/fastify/src/framework/support/cookie.ts +71 -0
- package/fastify/src/framework/support/jwt.ts +33 -0
- package/fastify/src/framework/support/lifecycle.ts +35 -0
- package/fastify/src/framework/support/logger.ts +148 -0
- package/fastify/src/framework/support/mail.ts +56 -0
- package/fastify/src/framework/support/password.ts +23 -0
- package/fastify/src/framework/support/status-codes.ts +75 -0
- package/fastify/src/framework/support/upload.ts +181 -0
- package/fastify/src/framework/support/url.ts +41 -0
- package/fastify/src/middlewares/auth-middleware.ts +134 -0
- package/fastify/src/middlewares/role-middleware.ts +19 -0
- package/fastify/src/modules/auth/__tests__/forgetpass.test.ts +200 -0
- package/fastify/src/modules/auth/__tests__/login.test.ts +171 -0
- package/fastify/src/modules/auth/__tests__/logout.test.ts +60 -0
- package/fastify/src/modules/auth/__tests__/logoutalldevices.test.ts +49 -0
- package/fastify/src/modules/auth/__tests__/me.test.ts +91 -0
- package/fastify/src/modules/auth/__tests__/refreshtoken.test.ts +305 -0
- package/fastify/src/modules/auth/__tests__/register.test.ts +291 -0
- package/fastify/src/modules/auth/__tests__/resetpass.test.ts +206 -0
- package/fastify/src/modules/auth/__tests__/verifyemail.test.ts +183 -0
- package/fastify/src/modules/auth/controllers/auth.ts +252 -0
- package/fastify/src/modules/auth/controllers/role.ts +35 -0
- package/fastify/src/modules/auth/database/models/role.ts +14 -0
- package/fastify/src/modules/auth/database/models/user.ts +46 -0
- package/fastify/src/modules/auth/database/seeders/role.ts +18 -0
- package/fastify/src/modules/auth/database/seeders/user.ts +35 -0
- package/fastify/src/modules/auth/facade.ts +12 -0
- package/fastify/src/modules/auth/helpers/auth.ts +115 -0
- package/fastify/src/modules/auth/jobs/forgetpass.ts +20 -0
- package/fastify/src/modules/auth/jobs/registeruser.ts +23 -0
- package/fastify/src/modules/auth/jobs/verifyemail.ts +18 -0
- package/fastify/src/modules/auth/routes/index.ts +185 -0
- package/fastify/src/modules/auth/routes/role.ts +56 -0
- package/fastify/src/modules/auth/schemas/auth.ts +116 -0
- package/fastify/src/modules/auth/services/forgetpassword.ts +66 -0
- package/fastify/src/modules/auth/services/login.ts +63 -0
- package/fastify/src/modules/auth/services/logout.ts +25 -0
- package/fastify/src/modules/auth/services/logoutalldevices.ts +19 -0
- package/fastify/src/modules/auth/services/me.ts +31 -0
- package/fastify/src/modules/auth/services/refreshtoken.ts +110 -0
- package/fastify/src/modules/auth/services/register.ts +141 -0
- package/fastify/src/modules/auth/services/resetpassword.ts +69 -0
- package/fastify/src/modules/auth/services/verifyemail.ts +77 -0
- package/fastify/src/modules/auth/types/auth.ts +31 -0
- package/fastify/src/providers/README.md +107 -0
- package/fastify/src/providers/notification.ts +4 -0
- package/fastify/src/resources/index.html +17 -0
- package/fastify/src/resources/src/App.vue +5 -0
- package/fastify/src/resources/src/assets/css/styles.css +14761 -0
- package/fastify/src/resources/src/assets/css/styles.css.map +1 -0
- package/fastify/src/resources/src/assets/fonts/public-sans/public-sans-latin-ext.woff2 +0 -0
- package/fastify/src/resources/src/assets/fonts/public-sans/public-sans-latin.woff2 +0 -0
- package/fastify/src/resources/src/assets/images/favicon/favicon.ico +0 -0
- package/fastify/src/resources/src/assets/images/logo.png +0 -0
- package/fastify/src/resources/src/assets/scss/custom.css +217 -0
- package/fastify/src/resources/src/assets/scss/custom.css.map +1 -0
- package/fastify/src/resources/src/assets/scss/custom.scss +1346 -0
- package/fastify/src/resources/src/components/Button.vue +32 -0
- package/fastify/src/resources/src/components/Checkbox.vue +32 -0
- package/fastify/src/resources/src/components/Datepicker.vue +106 -0
- package/fastify/src/resources/src/components/FeatureButton.vue +53 -0
- package/fastify/src/resources/src/components/FloatButton.vue +33 -0
- package/fastify/src/resources/src/components/Href.vue +32 -0
- package/fastify/src/resources/src/components/Input.vue +243 -0
- package/fastify/src/resources/src/components/InputGroup.vue +147 -0
- package/fastify/src/resources/src/components/InputPasswordToggle.vue +229 -0
- package/fastify/src/resources/src/components/Modal.vue +71 -0
- package/fastify/src/resources/src/components/Pagebar.vue +26 -0
- package/fastify/src/resources/src/components/Refresh.vue +19 -0
- package/fastify/src/resources/src/components/Select.vue +384 -0
- package/fastify/src/resources/src/components/Spinner.vue +42 -0
- package/fastify/src/resources/src/components/Switch.vue +60 -0
- package/fastify/src/resources/src/components/TextArea.vue +117 -0
- package/fastify/src/resources/src/components/Toast.vue +56 -0
- package/fastify/src/resources/src/components/datatable/DataTableSkeleton.vue +98 -0
- package/fastify/src/resources/src/components/datatable/Pagination.vue +147 -0
- package/fastify/src/resources/src/components/datatable/SelectOpption.vue +64 -0
- package/fastify/src/resources/src/components/datatable/index.vue +199 -0
- package/fastify/src/resources/src/composables/useAuth.ts +52 -0
- package/fastify/src/resources/src/helpers/assets.ts +51 -0
- package/fastify/src/resources/src/helpers/nformatter.ts +10 -0
- package/fastify/src/resources/src/helpers/utils.ts +75 -0
- package/fastify/src/resources/src/layouts/AuthLayout.vue +20 -0
- package/fastify/src/resources/src/layouts/Layout/Footer.vue +23 -0
- package/fastify/src/resources/src/layouts/Layout/Header.vue +124 -0
- package/fastify/src/resources/src/layouts/Layout/Sidebar.vue +137 -0
- package/fastify/src/resources/src/layouts/Layout/index.vue +87 -0
- package/fastify/src/resources/src/main.ts +28 -0
- package/fastify/src/resources/src/pages/auth/forgetPassword.vue +76 -0
- package/fastify/src/resources/src/pages/auth/login.vue +96 -0
- package/fastify/src/resources/src/pages/auth/register.vue +129 -0
- package/fastify/src/resources/src/pages/auth/resetPassword.vue +110 -0
- package/fastify/src/resources/src/pages/auth/verifyEmail.vue +64 -0
- package/fastify/src/resources/src/pages/dashboard/index.vue +66 -0
- package/fastify/src/resources/src/plugins/axios.ts +31 -0
- package/fastify/src/resources/src/plugins/browserDetect.ts +55 -0
- package/fastify/src/resources/src/plugins/dialog.ts +216 -0
- package/fastify/src/resources/src/plugins/gum.ts +683 -0
- package/fastify/src/resources/src/plugins/pulse.ts +157 -0
- package/fastify/src/resources/src/plugins/routeProgress.ts +87 -0
- package/fastify/src/resources/src/router/index.ts +85 -0
- package/fastify/src/resources/src/stores/admin-ui.ts +148 -0
- package/fastify/src/resources/src/stores/auth.ts +187 -0
- package/fastify/src/resources/tsconfig.json +19 -0
- package/fastify/src/resources/vite.config.ts +64 -0
- package/fastify/src/storage/app/private/.gitkeep +0 -0
- package/fastify/src/storage/app/public/.gitkeep +0 -0
- package/fastify/src/storage/app/tmp/generated/.gitkeep +0 -0
- package/fastify/src/storage/database/nexwire.sqlite +0 -0
- package/fastify/src/storage/logs/.gitkeep +0 -0
- package/fastify/src/storage/logs/app.log +0 -0
- package/fastify/src/storage/logs/fatal.log +0 -0
- package/fastify/src/storage/tmp/.gitkeep +0 -0
- package/fastify/src/storage/trash/modules/.gitkeep +0 -0
- package/fastify/src/types/global.d.ts +32 -0
- package/fastify/tsconfig.json +19 -0
- package/fastify/vitest.config.ts +38 -0
- package/hono/src/framework/facade.ts +1 -0
- package/hono/src/framework/http/logger.ts +86 -81
- package/hono/src/framework/maker-cli/runtime/core.mjs +1 -2
- package/hono/src/framework/notification/channels/mail.ts +8 -6
- package/hono/src/middlewares/auth-middleware.ts +1 -2
- package/hono/src/middlewares/role-middleware.ts +3 -13
- package/hono/src/modules/auth/routes/index.ts +1 -2
- package/hono/src/providers/notification.ts +2 -2
- package/hono/src/resources/src/components/Switch.vue +60 -65
- package/hono/src/resources/src/pages/auth/forgetPassword.vue +23 -29
- package/hono/src/resources/src/pages/auth/login.vue +26 -29
- package/hono/src/resources/src/pages/auth/register.vue +36 -40
- package/hono/src/resources/src/pages/auth/resetPassword.vue +29 -37
- package/hono/src/resources/src/pages/auth/verifyEmail.vue +15 -19
- package/hono/src/resources/src/plugins/gum.ts +519 -203
- package/hono/src/resources/src/stores/auth.ts +9 -6
- package/hono/src/storage/database/nexwire.sqlite +0 -0
- package/package.json +3 -2
- package/src/index.js +11 -8
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import fsPromises from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import { writeFileAlways, writeFileIfMissing } from "./file-ops.mjs";
|
|
6
|
+
import { PROJECT_COMMAND_FLAG } from "./help.mjs";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Where a project keeps its own maker commands.
|
|
10
|
+
*
|
|
11
|
+
* Deliberately outside `src/`. Anything under `src/framework/` is copied into
|
|
12
|
+
* the published package by `scripts/sync-template.mjs`, so a developer edit
|
|
13
|
+
* there would be overwritten on the next framework update - and `src/modules/`
|
|
14
|
+
* is swept by the route, job and seeder globs. The project root is the one
|
|
15
|
+
* place nothing else owns.
|
|
16
|
+
*
|
|
17
|
+
* Two names are read: commands.mjs (plain JavaScript, loaded by plain node)
|
|
18
|
+
* and commands.ts (TypeScript, loaded through tsx). The .mjs is the incumbent
|
|
19
|
+
* and wins if a project keeps both.
|
|
20
|
+
*/
|
|
21
|
+
export const PROJECT_COMMANDS_PATH = "maker/commands.mjs";
|
|
22
|
+
export const PROJECT_COMMANDS_TS_PATH = "maker/commands.ts";
|
|
23
|
+
|
|
24
|
+
/** Process-global so a re-evaluated module cannot register tsx twice. */
|
|
25
|
+
const TSX_REGISTERED = "__nexwireMakerTsxRegistered";
|
|
26
|
+
|
|
27
|
+
const PROJECT_COMMANDS_TEMPLATE = `/**
|
|
28
|
+
* Your project's maker commands.
|
|
29
|
+
*
|
|
30
|
+
* This file is yours. Nothing in nexwire writes to it, and no framework update
|
|
31
|
+
* will touch it. Whatever you export here is registered on the maker CLI
|
|
32
|
+
* alongside the built-in commands:
|
|
33
|
+
*
|
|
34
|
+
* maker example:hello run your command
|
|
35
|
+
* maker --help every command, yours included
|
|
36
|
+
*
|
|
37
|
+
* \`program\` is the same commander object the built-in commands are built with,
|
|
38
|
+
* so \`.command()\`, \`.description()\`, \`.option()\` and \`.action()\` behave exactly
|
|
39
|
+
* as they do in nexwire's own commands.
|
|
40
|
+
*
|
|
41
|
+
* Three things worth knowing:
|
|
42
|
+
*
|
|
43
|
+
* - A name that collides with a framework command is ignored, and you are told
|
|
44
|
+
* which one. Rename yours; the built-in stays.
|
|
45
|
+
* - An error in this file is reported, never swallowed. If your commands stop
|
|
46
|
+
* appearing, the fault is here rather than in your command code.
|
|
47
|
+
* - This file runs as plain JavaScript under plain Node, so it cannot import
|
|
48
|
+
* your TypeScript source or use your tsconfig path alias. If a command
|
|
49
|
+
* needs either, name this file maker/commands.ts instead and delete this
|
|
50
|
+
* one - nexwire loads that file through tsx, with your tsconfig applied.
|
|
51
|
+
* For editor type support, create maker/tsconfig.json as shown in the docs.
|
|
52
|
+
*
|
|
53
|
+
* Replace or delete the example below once you have a command of your own.
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
export default function register(program) {
|
|
57
|
+
program
|
|
58
|
+
.command("example:hello <name>")
|
|
59
|
+
.description("Example command - replace or delete this")
|
|
60
|
+
.option("--loud", "Shout the greeting")
|
|
61
|
+
.action((name, options) => {
|
|
62
|
+
const greeting = \`Hello, \${name}!\`;
|
|
63
|
+
console.log(options.loud ? greeting.toUpperCase() : greeting);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
`;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Why: Plain node cannot import TypeScript, so maker/commands.ts needs tsx
|
|
70
|
+
* registered before it is imported. Registered at most once per process
|
|
71
|
+
* and never unregistered: two overlapping registrations corrupt the
|
|
72
|
+
* loader, and commands routinely import more TypeScript at action time,
|
|
73
|
+
* long after this function returns. The flag lives on globalThis because
|
|
74
|
+
* this module can be evaluated more than once in one process (vitest's
|
|
75
|
+
* per-file isolation) while the loader hooks are process-wide.
|
|
76
|
+
* When: Only when the project's file is TypeScript - maker/commands.mjs never
|
|
77
|
+
* pays for tsx being installed.
|
|
78
|
+
* Where: utils/project-commands.mjs.
|
|
79
|
+
*/
|
|
80
|
+
async function registerTsLoader(rel) {
|
|
81
|
+
if (globalThis[TSX_REGISTERED]) return;
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const { register } = await import("tsx/esm/api");
|
|
85
|
+
register();
|
|
86
|
+
} catch (error) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`${rel} needs tsx to load TypeScript, but it could not be registered: ${error?.message || error}. tsx ships as a devDependency of every nexwire project - run npm install, or rename the file to ${PROJECT_COMMANDS_PATH}.`,
|
|
89
|
+
{ cause: error }
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
globalThis[TSX_REGISTERED] = true;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Why: Registers the commands a project defined for itself.
|
|
98
|
+
* When: Every maker invocation, right after the framework's own registrars.
|
|
99
|
+
* Where: Maker CLI entry flow, from utils/project-commands.mjs.
|
|
100
|
+
* How: Imports maker/commands.mjs - or maker/commands.ts, loaded through tsx,
|
|
101
|
+
* which resolves the project's tsconfig path aliases - resolved from
|
|
102
|
+
* process.cwd(), not from import.meta.dirname, which points inside
|
|
103
|
+
* node_modules and would find the framework's own directory instead of the
|
|
104
|
+
* project. If a project keeps both files the .mjs wins and the .ts file is
|
|
105
|
+
* skipped with a warning naming it, so neither file is ever silently dead:
|
|
106
|
+
* a project that already runs must not be re-registered by a file that
|
|
107
|
+
* appeared later, and a file that is skipped must say so. Absent is silent
|
|
108
|
+
* and free; broken is loud, because a developer's commands silently not
|
|
109
|
+
* existing is a far worse experience than an error message. A name that
|
|
110
|
+
* collides with a framework command is refused by commander itself, which
|
|
111
|
+
* is the right outcome: the built-in wins and the project cannot shadow a
|
|
112
|
+
* command it did not write. That refusal is re-thrown here with the file
|
|
113
|
+
* name attached, because commander's own message does not say which file
|
|
114
|
+
* asked for the name.
|
|
115
|
+
*/
|
|
116
|
+
export async function registerProjectCommands(program, rawArgs, cwd = process.cwd()) {
|
|
117
|
+
const mjsFile = path.resolve(cwd, PROJECT_COMMANDS_PATH);
|
|
118
|
+
const tsFile = path.resolve(cwd, PROJECT_COMMANDS_TS_PATH);
|
|
119
|
+
const hasMjs = fs.existsSync(mjsFile);
|
|
120
|
+
const hasTs = fs.existsSync(tsFile);
|
|
121
|
+
|
|
122
|
+
if (!hasMjs && !hasTs) {
|
|
123
|
+
return { loaded: false, file: mjsFile, added: [] };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const isTs = !hasMjs && hasTs;
|
|
127
|
+
const file = isTs ? tsFile : mjsFile;
|
|
128
|
+
const rel = isTs ? PROJECT_COMMANDS_TS_PATH : PROJECT_COMMANDS_PATH;
|
|
129
|
+
|
|
130
|
+
if (hasMjs && hasTs) {
|
|
131
|
+
console.warn(
|
|
132
|
+
`${PROJECT_COMMANDS_TS_PATH} is ignored because ${PROJECT_COMMANDS_PATH} exists. Keep one - delete the file you are not using.`
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Not wrapped in a bare try/catch on purpose. A file that exists but fails to
|
|
137
|
+
// load has to say so, or the developer goes looking for a bug in their own
|
|
138
|
+
// command that is actually in this loader. The catches below only attach the
|
|
139
|
+
// file name, because an error from inside the project file otherwise reads as
|
|
140
|
+
// if it came from the framework.
|
|
141
|
+
if (isTs) {
|
|
142
|
+
await registerTsLoader(rel);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
let module;
|
|
146
|
+
try {
|
|
147
|
+
module = await import(pathToFileURL(file).href);
|
|
148
|
+
} catch (error) {
|
|
149
|
+
throw new Error(`Could not load ${rel}: ${error?.message || error}`, { cause: error });
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const register = module.default ?? module.register;
|
|
153
|
+
|
|
154
|
+
if (typeof register !== "function") {
|
|
155
|
+
throw new TypeError(
|
|
156
|
+
`${rel} must export a default function (program, args) => void. Found: ${typeof register}.`
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const before = new Set(program.commands);
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
await register(program, rawArgs);
|
|
164
|
+
} catch (error) {
|
|
165
|
+
throw new Error(`Could not register commands from ${rel}: ${error?.message || error}`, {
|
|
166
|
+
cause: error
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const added = [];
|
|
171
|
+
|
|
172
|
+
for (const command of program.commands) {
|
|
173
|
+
if (before.has(command)) continue;
|
|
174
|
+
// Tagged so `maker --help` files these under "Your commands" beside
|
|
175
|
+
// `maker:init`, rather than in the section for unclassified commands.
|
|
176
|
+
command[PROJECT_COMMAND_FLAG] = true;
|
|
177
|
+
added.push(command.name());
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return { loaded: true, file, added };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Why: Creates maker/commands.mjs so a project can add its own commands.
|
|
185
|
+
* When: A developer runs `maker maker:init`.
|
|
186
|
+
* Where: utils/project-commands.mjs.
|
|
187
|
+
* How: Writes the template with the same no-clobber default as every other
|
|
188
|
+
* generator, so an existing file is left alone unless --force is given.
|
|
189
|
+
*/
|
|
190
|
+
export async function initProjectCommands(cwd = process.cwd(), { force = false } = {}) {
|
|
191
|
+
const file = path.resolve(cwd, PROJECT_COMMANDS_PATH);
|
|
192
|
+
|
|
193
|
+
if (force) {
|
|
194
|
+
await writeFileAlways(file, PROJECT_COMMANDS_TEMPLATE);
|
|
195
|
+
return { file, created: false, overwritten: true };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
await fsPromises.mkdir(path.dirname(file), { recursive: true });
|
|
199
|
+
const created = await writeFileIfMissing(file, PROJECT_COMMANDS_TEMPLATE);
|
|
200
|
+
return { file, created, overwritten: false };
|
|
201
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { glob } from "glob";
|
|
5
|
+
|
|
6
|
+
function resolveModulesPath() {
|
|
7
|
+
const cwd = process.cwd();
|
|
8
|
+
const srcModules = path.resolve(cwd, "src/modules");
|
|
9
|
+
const distModules = path.resolve(cwd, "dist/src/modules");
|
|
10
|
+
const entry = String(process.argv[1] || "");
|
|
11
|
+
const runningDist = /[\\/]dist[\\/]/.test(entry);
|
|
12
|
+
|
|
13
|
+
if (runningDist && fs.existsSync(distModules)) return distModules;
|
|
14
|
+
return srcModules;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const modulesPath = resolveModulesPath();
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Why: Finds module files by glob from active src/dist modules path.
|
|
21
|
+
* When: Framework needs module routes/jobs/seeders/schedules discovery.
|
|
22
|
+
* Where: Route registrar, queue boot, scheduler boot, seeding.
|
|
23
|
+
* How: Runs glob with absolute paths and stable sorting.
|
|
24
|
+
*/
|
|
25
|
+
export async function discoverModuleFiles(pattern: string) {
|
|
26
|
+
const files = await glob(pattern, {
|
|
27
|
+
cwd: modulesPath,
|
|
28
|
+
absolute: true,
|
|
29
|
+
nodir: true,
|
|
30
|
+
windowsPathsNoEscape: true
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return files.sort();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Why: Derives the module name from an absolute discovered file path.
|
|
38
|
+
* When: Mounting route prefixes and grouping module assets.
|
|
39
|
+
* Where: Route registration logic.
|
|
40
|
+
* How: Strips the artifact folder (e.g. `routes/`), keeping any panel segment so
|
|
41
|
+
* `admin/post/routes/index.ts` resolves to `admin/post` rather than `admin`.
|
|
42
|
+
* Returns POSIX separators because the result is used as a URL prefix.
|
|
43
|
+
*/
|
|
44
|
+
export function moduleNameFromPath(filePath: string) {
|
|
45
|
+
const relative = path.relative(modulesPath, filePath);
|
|
46
|
+
return path.dirname(path.dirname(relative)).split(path.sep).join("/");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Why: Dynamically imports a discovered module file.
|
|
51
|
+
* When: Bootstrapping pluggable module artifacts.
|
|
52
|
+
* Where: Discovery consumers across framework.
|
|
53
|
+
* How: Converts file path to file URL and imports it.
|
|
54
|
+
*/
|
|
55
|
+
export async function importFile(filePath: string) {
|
|
56
|
+
return await import(pathToFileURL(filePath).href);
|
|
57
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { FastifyInstance } from "fastify";
|
|
2
|
+
import { discoverModuleFiles, importFile, moduleNameFromPath } from "@/framework/modules/discover.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Why: Discovers and mounts every module route plugin.
|
|
6
|
+
* When: Kernel boot, after the HTTP app exists.
|
|
7
|
+
* Where: Framework startup.
|
|
8
|
+
* How: Each `modules/<name>/routes/*.ts` default export is a Fastify plugin,
|
|
9
|
+
* registered under `/api/<module-name>`.
|
|
10
|
+
*/
|
|
11
|
+
export async function registerModuleRoutes(app: FastifyInstance) {
|
|
12
|
+
const files = await discoverModuleFiles("**/routes/*.{ts,js}");
|
|
13
|
+
|
|
14
|
+
for (const file of files) {
|
|
15
|
+
const route = await importFile(file);
|
|
16
|
+
|
|
17
|
+
if (!route.default) {
|
|
18
|
+
throw new Error(`Route file ${file} has no default export`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Optional Guard: Ensure it's a Fastify plugin function
|
|
22
|
+
if (typeof route.default !== "function") {
|
|
23
|
+
throw new Error(`Route file ${file} must export a Fastify plugin as default`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const prefix = `/api/${moduleNameFromPath(file)}`;
|
|
27
|
+
await app.register(route.default, { prefix });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return files.length;
|
|
31
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { dispatchEvent } from "@/framework/events/dispatcher.js";
|
|
2
|
+
import type { NotificationChannel } from "@/framework/notification/contracts.js";
|
|
3
|
+
|
|
4
|
+
export const broadcastChannel: NotificationChannel = {
|
|
5
|
+
name: "broadcast",
|
|
6
|
+
async send(target, notification, options) {
|
|
7
|
+
if (!options.broadcast) return false;
|
|
8
|
+
|
|
9
|
+
await dispatchEvent("notification.created", notification, {
|
|
10
|
+
broadcast: { users: [target.id] }
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { dispatchEvent } from "@/framework/events/dispatcher.js";
|
|
2
|
+
import type { NotificationChannel } from "@/framework/notification/contracts.js";
|
|
3
|
+
import { resolveAuthProvider } from "@/framework/providers/adapter.js";
|
|
4
|
+
import { redisClientIfReady } from "@/framework/redis/client.js";
|
|
5
|
+
import { mail } from "@/framework/support/mail.js";
|
|
6
|
+
|
|
7
|
+
function escapeHtml(str: string) {
|
|
8
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const mailChannel: NotificationChannel = {
|
|
12
|
+
name: "mail",
|
|
13
|
+
async send(target, notification, options) {
|
|
14
|
+
if (!options.mail) return false;
|
|
15
|
+
|
|
16
|
+
let to = options.mail.to || null;
|
|
17
|
+
if (!to) {
|
|
18
|
+
try {
|
|
19
|
+
const provider = await resolveAuthProvider();
|
|
20
|
+
if (provider?.getUserContact) {
|
|
21
|
+
const contact = await provider.getUserContact(target.id);
|
|
22
|
+
if (contact?.email) to = contact.email as string;
|
|
23
|
+
}
|
|
24
|
+
} catch {}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!to) return false;
|
|
28
|
+
|
|
29
|
+
const subject = options.mail.subject || notification.title;
|
|
30
|
+
const html =
|
|
31
|
+
options.mail.html ||
|
|
32
|
+
`<h2>${escapeHtml(notification.title)}</h2>${notification.body ? `<p>${escapeHtml(notification.body)}</p>` : ""}`;
|
|
33
|
+
|
|
34
|
+
if (redisClientIfReady()) {
|
|
35
|
+
await dispatchEvent("notification:mail", { to, subject, html }, { queue: "mail" });
|
|
36
|
+
} else {
|
|
37
|
+
await mail.sendMail({ to, subject, html });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { NotifiableTarget, NotificationRecord } from "@/framework/providers/contracts.js";
|
|
2
|
+
|
|
3
|
+
export type NotificationTarget = number | string | NotifiableTarget;
|
|
4
|
+
export type NormalizedTarget = { type: string; id: number | string };
|
|
5
|
+
|
|
6
|
+
export type NotificationOptions = {
|
|
7
|
+
type?: string;
|
|
8
|
+
title: string;
|
|
9
|
+
body?: string;
|
|
10
|
+
link?: string;
|
|
11
|
+
data?: Record<string, unknown>;
|
|
12
|
+
|
|
13
|
+
// Built-in Channels
|
|
14
|
+
broadcast?: boolean;
|
|
15
|
+
mail?: {
|
|
16
|
+
to?: string;
|
|
17
|
+
subject?: string;
|
|
18
|
+
html?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Extensible: Add any custom channel options here (e.g., sms, push, discord)
|
|
22
|
+
sms?: { to?: string; message?: string };
|
|
23
|
+
push?: { token?: string; payload?: any };
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export interface NotificationChannel {
|
|
27
|
+
name: string;
|
|
28
|
+
send(target: NormalizedTarget, notification: NotificationRecord, options: NotificationOptions): Promise<boolean>;
|
|
29
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { NormalizedTarget, NotificationOptions, NotificationTarget } from "@/framework/notification/contracts.js";
|
|
2
|
+
import { getChannels } from "@/framework/notification/registry.js";
|
|
3
|
+
import { resolveNotificationProvider } from "@/framework/providers/adapter.js";
|
|
4
|
+
import type { NotificationRecord } from "@/framework/providers/contracts.js";
|
|
5
|
+
|
|
6
|
+
let missingProviderWarned = false;
|
|
7
|
+
|
|
8
|
+
/** Turn the public target argument into the polymorphic owner pair. */
|
|
9
|
+
function normalizeTarget(target: NotificationTarget): NormalizedTarget {
|
|
10
|
+
if (target !== null && typeof target === "object") {
|
|
11
|
+
return {
|
|
12
|
+
type: String(target.type || "user").trim() || "user",
|
|
13
|
+
id: target.id
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
return { type: "user", id: target as number | string };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Why: Persists a notification to the database and triggers all active channels.
|
|
21
|
+
* When: A feature wants to record something for a user.
|
|
22
|
+
* Where: Application code, through the framework facade.
|
|
23
|
+
* How: The provider owns database storage. The channels handle delivery (mail, sms, ws).
|
|
24
|
+
*/
|
|
25
|
+
export async function notify(target: NotificationTarget, options: NotificationOptions): Promise<NotificationRecord | null> {
|
|
26
|
+
const provider = await resolveNotificationProvider();
|
|
27
|
+
if (!provider) {
|
|
28
|
+
if (!missingProviderWarned) {
|
|
29
|
+
missingProviderWarned = true;
|
|
30
|
+
console.warn(
|
|
31
|
+
'[notification] skipped: no notification provider is registered. Run "bun maker module:make-notification notification" then "bun maker module:migrate notification", or register a provider in src/providers/.'
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const normalizedTarget = normalizeTarget(target);
|
|
38
|
+
|
|
39
|
+
// 1. Save to Database via the Provider
|
|
40
|
+
const normalized = await provider.create(normalizedTarget, {
|
|
41
|
+
type: options.type,
|
|
42
|
+
title: options.title,
|
|
43
|
+
body: options.body ?? null,
|
|
44
|
+
link: options.link ?? null,
|
|
45
|
+
data: options.data ?? null
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// 2. Dispatch to all registered channels in parallel
|
|
49
|
+
const channels = getChannels();
|
|
50
|
+
await Promise.allSettled(
|
|
51
|
+
channels.map((channel) =>
|
|
52
|
+
channel.send(normalizedTarget, normalized, options).catch((err) => {
|
|
53
|
+
console.error(`[notification] Channel ${channel.name} failed:`, err);
|
|
54
|
+
})
|
|
55
|
+
)
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
return normalized;
|
|
59
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { NotificationChannel } from "@/framework/notification/contracts.js";
|
|
2
|
+
|
|
3
|
+
const channels = new Map<string, NotificationChannel>();
|
|
4
|
+
|
|
5
|
+
export function registerChannel(channel: NotificationChannel) {
|
|
6
|
+
channels.set(channel.name, channel);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function getChannels(): NotificationChannel[] {
|
|
10
|
+
return Array.from(channels.values());
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function clearChannels() {
|
|
14
|
+
channels.clear();
|
|
15
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { eq } from "drizzle-orm";
|
|
2
|
+
import { db } from "@/framework/database/connection.js";
|
|
3
|
+
import type { AuthProvider, NotificationProvider } from "./contracts.js";
|
|
4
|
+
import { loadProviders } from "./loader.js";
|
|
5
|
+
import { getAuthProvider, getNotificationProvider } from "./registry.js";
|
|
6
|
+
|
|
7
|
+
let defaultProvider: AuthProvider | null = null;
|
|
8
|
+
let defaultLoadAttempted = false;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Why: Builds the built-in auth provider from the default auth module.
|
|
12
|
+
* When: No custom provider was registered by the application.
|
|
13
|
+
* Where: Auth provider adapter.
|
|
14
|
+
* How: Lazily imports the default user/refresh-token models. When the default
|
|
15
|
+
* auth module was deleted or replaced, no provider is produced.
|
|
16
|
+
*/
|
|
17
|
+
async function loadDefaultProvider(): Promise<AuthProvider | null> {
|
|
18
|
+
if (defaultProvider) return defaultProvider;
|
|
19
|
+
if (defaultLoadAttempted) return null;
|
|
20
|
+
defaultLoadAttempted = true;
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const userMod = await import("@/modules/auth/database/models/user.js").catch(() => null);
|
|
24
|
+
if (!userMod?.users || !userMod?.refreshTokens) return null;
|
|
25
|
+
|
|
26
|
+
const users = userMod.users;
|
|
27
|
+
const refreshTokens = userMod.refreshTokens;
|
|
28
|
+
|
|
29
|
+
// Define the core logic as standalone functions to avoid `this` context issues
|
|
30
|
+
const getUserById = async (id: number | string) => {
|
|
31
|
+
try {
|
|
32
|
+
const user = await (db.query.users.findFirst as any)?.({
|
|
33
|
+
where: eq(users.id, id as any),
|
|
34
|
+
with: { role: true }
|
|
35
|
+
});
|
|
36
|
+
if (user) return user;
|
|
37
|
+
} catch {}
|
|
38
|
+
try {
|
|
39
|
+
const rows = await db
|
|
40
|
+
.select()
|
|
41
|
+
.from(users)
|
|
42
|
+
.where(eq(users.id, id as any));
|
|
43
|
+
return rows?.[0] ?? null;
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const provider: AuthProvider = {
|
|
50
|
+
getUserById,
|
|
51
|
+
async findRefreshToken(jti: string) {
|
|
52
|
+
try {
|
|
53
|
+
const token = await (db.query.refreshTokens.findFirst as any)?.({
|
|
54
|
+
where: eq(refreshTokens.jti, jti)
|
|
55
|
+
});
|
|
56
|
+
if (token) return token;
|
|
57
|
+
} catch {}
|
|
58
|
+
try {
|
|
59
|
+
const rows = await db.select().from(refreshTokens).where(eq(refreshTokens.jti, jti));
|
|
60
|
+
return rows?.[0] ?? null;
|
|
61
|
+
} catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
async deleteRefreshToken(tokenId: number | string) {
|
|
66
|
+
try {
|
|
67
|
+
await db.delete(refreshTokens).where(eq(refreshTokens.id, tokenId as any));
|
|
68
|
+
} catch {}
|
|
69
|
+
},
|
|
70
|
+
async revokeRefreshToken(tokenId: number | string) {
|
|
71
|
+
try {
|
|
72
|
+
await db.delete(refreshTokens).where(eq(refreshTokens.id, tokenId as any));
|
|
73
|
+
} catch {}
|
|
74
|
+
},
|
|
75
|
+
async getAuthContext(user: any, payload?: any) {
|
|
76
|
+
try {
|
|
77
|
+
if (user?.role && typeof user.role === "object") {
|
|
78
|
+
return {
|
|
79
|
+
...(payload || {}),
|
|
80
|
+
id: user.id,
|
|
81
|
+
email: user.email,
|
|
82
|
+
roleId: user.role?.id ?? null,
|
|
83
|
+
role: user.role?.name ?? null,
|
|
84
|
+
...user
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
} catch {}
|
|
88
|
+
return {
|
|
89
|
+
...(payload || {}),
|
|
90
|
+
id: user.id,
|
|
91
|
+
email: user.email,
|
|
92
|
+
roleId: user.roleId ?? null,
|
|
93
|
+
role: user.role ?? null,
|
|
94
|
+
...user
|
|
95
|
+
};
|
|
96
|
+
},
|
|
97
|
+
async getUserContact(userId: number | string) {
|
|
98
|
+
try {
|
|
99
|
+
// Use the standalone function instead of `this.getUserById`
|
|
100
|
+
const user = await getUserById(userId);
|
|
101
|
+
if (!user) return null;
|
|
102
|
+
return {
|
|
103
|
+
email: user.email ?? null,
|
|
104
|
+
name: user.name ?? user.fullName ?? user.username ?? null,
|
|
105
|
+
phone: user.phone ?? user.phoneNumber ?? null
|
|
106
|
+
};
|
|
107
|
+
} catch {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
defaultProvider = provider;
|
|
114
|
+
return defaultProvider;
|
|
115
|
+
} catch {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Why: Resolves the auth provider the framework should use.
|
|
122
|
+
* When: The auth middleware or the notification mailer needs user data.
|
|
123
|
+
* Where: Auth provider adapter.
|
|
124
|
+
* How: Application providers are loaded first, so a file in `src/providers/`
|
|
125
|
+
* wins. Otherwise the built-in provider from the default auth module is
|
|
126
|
+
* used. When neither exists the framework degrades gracefully.
|
|
127
|
+
*/
|
|
128
|
+
export async function resolveAuthProvider(): Promise<AuthProvider | null> {
|
|
129
|
+
await loadProviders();
|
|
130
|
+
const registered = getAuthProvider();
|
|
131
|
+
if (registered) return registered;
|
|
132
|
+
const def = await loadDefaultProvider();
|
|
133
|
+
if (def) return def;
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Why: Resolves the notification storage provider.
|
|
139
|
+
* When: `notify()` persists a notification; the HTTP handlers read it back.
|
|
140
|
+
* Where: Notification runtime and the generated notification controller.
|
|
141
|
+
* How: Provider files are loaded first, so a project can replace the store from
|
|
142
|
+
* `src/providers/` or a module provider folder. The notification module is
|
|
143
|
+
* opt-in, so an absent provider is a normal state: callers warn once
|
|
144
|
+
* (runtime) or answer 503 (HTTP) instead of throwing.
|
|
145
|
+
*/
|
|
146
|
+
export async function resolveNotificationProvider(): Promise<NotificationProvider | null> {
|
|
147
|
+
await loadProviders();
|
|
148
|
+
return getNotificationProvider();
|
|
149
|
+
}
|