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,25 @@
|
|
|
1
|
+
# Server infra env template (base values)
|
|
2
|
+
# Remote: copy to deploy/server/.env and use as-is
|
|
3
|
+
# Local: copy to deploy/server/.env then override in deploy/server/.env.local
|
|
4
|
+
LETSENCRYPT_EMAIL=ops@example.com
|
|
5
|
+
PROXY_HTTP_PORT=80
|
|
6
|
+
PROXY_HTTPS_PORT=443
|
|
7
|
+
|
|
8
|
+
MYSQL_ROOT_PASSWORD=change-me-root
|
|
9
|
+
MYSQL_DATABASE=nexwire
|
|
10
|
+
MYSQL_PORT=4000
|
|
11
|
+
|
|
12
|
+
POSTGRES_USER=postgres
|
|
13
|
+
POSTGRES_PASSWORD=change-me-postgres
|
|
14
|
+
POSTGRES_DB=postgres
|
|
15
|
+
POSTGRES_PORT=4001
|
|
16
|
+
|
|
17
|
+
REDIS=true
|
|
18
|
+
|
|
19
|
+
PHPMYADMIN_DOMAIN=pma.example.com
|
|
20
|
+
PGADMIN_DOMAIN=pga.example.com
|
|
21
|
+
PGADMIN_DEFAULT_EMAIL=admin@example.com
|
|
22
|
+
PGADMIN_DEFAULT_PASSWORD=admin123
|
|
23
|
+
PHPMYADMIN_LOCAL_PORT=127.0.0.1:8082
|
|
24
|
+
PGADMIN_LOCAL_PORT=127.0.0.1:8083
|
|
25
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# nginx-vhost overrides
|
|
2
|
+
|
|
3
|
+
This folder holds per-domain nginx overrides for `jwilder/nginx-proxy`.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
1. Copy `app.example.com` to your real app domain file name.
|
|
8
|
+
2. Edit rules as needed (timeouts, upload limits, auth for `/board`, etc).
|
|
9
|
+
3. Recreate or reload proxy container if needed.
|
|
10
|
+
|
|
11
|
+
Example:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
cp deploy/server/nginx-vhost/app.example.com deploy/server/nginx-vhost/api.yourdomain.com
|
|
15
|
+
```
|
|
16
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Custom nginx vhost overrides for app.example.com
|
|
2
|
+
# File name should match your APP_DOMAIN exactly.
|
|
3
|
+
|
|
4
|
+
client_max_body_size 50m;
|
|
5
|
+
proxy_read_timeout 300;
|
|
6
|
+
proxy_send_timeout 300;
|
|
7
|
+
|
|
8
|
+
# Protect Queue Dashboard in production
|
|
9
|
+
location /board {
|
|
10
|
+
auth_basic "Restricted";
|
|
11
|
+
auth_basic_user_file /etc/nginx/vhost.d/.htpasswd;
|
|
12
|
+
}
|
|
13
|
+
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
[supervisord]
|
|
2
|
+
nodaemon=true
|
|
3
|
+
user=root
|
|
4
|
+
logfile=/dev/null
|
|
5
|
+
logfile_maxbytes=0
|
|
6
|
+
pidfile=/var/run/supervisord.pid
|
|
7
|
+
loglevel=info
|
|
8
|
+
|
|
9
|
+
[program:migrate]
|
|
10
|
+
command=/bin/sh -lc "/bin/sh /app/deploy/scripts/auto-migrate.sh {{RUNTIME_EXEC}}"
|
|
11
|
+
directory=/app
|
|
12
|
+
autostart=true
|
|
13
|
+
autorestart=false
|
|
14
|
+
priority=5
|
|
15
|
+
startsecs=0
|
|
16
|
+
startretries=1
|
|
17
|
+
exitcodes=0
|
|
18
|
+
stdout_logfile=/dev/stdout
|
|
19
|
+
stderr_logfile=/dev/stderr
|
|
20
|
+
stdout_logfile_maxbytes=0
|
|
21
|
+
stderr_logfile_maxbytes=0
|
|
22
|
+
|
|
23
|
+
[program:api]
|
|
24
|
+
command=/bin/sh -lc "{{RUNTIME_EXEC}} src/framework/maker-cli/index.mjs serve --prod --runtime={{RUNTIME_NAME}}"
|
|
25
|
+
directory=/app
|
|
26
|
+
autostart=true
|
|
27
|
+
autorestart=true
|
|
28
|
+
priority=20
|
|
29
|
+
startsecs=3
|
|
30
|
+
startretries=5
|
|
31
|
+
stopsignal=TERM
|
|
32
|
+
stopwaitsecs=20
|
|
33
|
+
redirect_stderr=true
|
|
34
|
+
stdout_logfile=/dev/stdout
|
|
35
|
+
stdout_logfile_maxbytes=0
|
|
36
|
+
stopasgroup=true
|
|
37
|
+
killasgroup=true
|
|
38
|
+
|
|
39
|
+
[program:scheduler]
|
|
40
|
+
command=/bin/sh -lc "{{RUNTIME_EXEC}} src/framework/maker-cli/index.mjs schedule:work --prod --runtime={{RUNTIME_NAME}}"
|
|
41
|
+
directory=/app
|
|
42
|
+
autostart=true
|
|
43
|
+
autorestart=true
|
|
44
|
+
priority=40
|
|
45
|
+
startsecs=3
|
|
46
|
+
startretries=5
|
|
47
|
+
stopsignal=TERM
|
|
48
|
+
stopwaitsecs=20
|
|
49
|
+
redirect_stderr=true
|
|
50
|
+
stdout_logfile=/dev/stdout
|
|
51
|
+
stdout_logfile_maxbytes=0
|
|
52
|
+
stopasgroup=true
|
|
53
|
+
killasgroup=true
|
|
54
|
+
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
[supervisord]
|
|
2
|
+
nodaemon=true
|
|
3
|
+
user=root
|
|
4
|
+
logfile=/dev/null
|
|
5
|
+
logfile_maxbytes=0
|
|
6
|
+
pidfile=/var/run/supervisord.pid
|
|
7
|
+
loglevel=info
|
|
8
|
+
|
|
9
|
+
[program:migrate]
|
|
10
|
+
command=/bin/sh -lc "/bin/sh /app/deploy/scripts/auto-migrate.sh {{RUNTIME_EXEC}}"
|
|
11
|
+
directory=/app
|
|
12
|
+
autostart=true
|
|
13
|
+
autorestart=false
|
|
14
|
+
priority=5
|
|
15
|
+
startsecs=0
|
|
16
|
+
startretries=1
|
|
17
|
+
exitcodes=0
|
|
18
|
+
stdout_logfile=/dev/stdout
|
|
19
|
+
stderr_logfile=/dev/stderr
|
|
20
|
+
stdout_logfile_maxbytes=0
|
|
21
|
+
stderr_logfile_maxbytes=0
|
|
22
|
+
|
|
23
|
+
[program:api]
|
|
24
|
+
command=/bin/sh -lc "{{RUNTIME_EXEC}} src/framework/maker-cli/index.mjs serve --prod --runtime={{RUNTIME_NAME}}"
|
|
25
|
+
directory=/app
|
|
26
|
+
autostart=true
|
|
27
|
+
autorestart=true
|
|
28
|
+
priority=20
|
|
29
|
+
startsecs=3
|
|
30
|
+
startretries=5
|
|
31
|
+
stopsignal=TERM
|
|
32
|
+
stopwaitsecs=20
|
|
33
|
+
redirect_stderr=true
|
|
34
|
+
stdout_logfile=/dev/stdout
|
|
35
|
+
stdout_logfile_maxbytes=0
|
|
36
|
+
stopasgroup=true
|
|
37
|
+
killasgroup=true
|
|
38
|
+
|
|
39
|
+
[program:queue]
|
|
40
|
+
command=/bin/sh -lc "{{RUNTIME_EXEC}} src/framework/maker-cli/index.mjs queue:work --queue=default,mail,maintenance --prod --runtime={{RUNTIME_NAME}}"
|
|
41
|
+
directory=/app
|
|
42
|
+
autostart=true
|
|
43
|
+
autorestart=true
|
|
44
|
+
priority=30
|
|
45
|
+
startsecs=3
|
|
46
|
+
startretries=5
|
|
47
|
+
stopsignal=TERM
|
|
48
|
+
stopwaitsecs=20
|
|
49
|
+
redirect_stderr=true
|
|
50
|
+
stdout_logfile=/dev/stdout
|
|
51
|
+
stdout_logfile_maxbytes=0
|
|
52
|
+
stopasgroup=true
|
|
53
|
+
killasgroup=true
|
|
54
|
+
|
|
55
|
+
[program:scheduler]
|
|
56
|
+
command=/bin/sh -lc "{{RUNTIME_EXEC}} src/framework/maker-cli/index.mjs schedule:work --prod --runtime={{RUNTIME_NAME}}"
|
|
57
|
+
directory=/app
|
|
58
|
+
autostart=true
|
|
59
|
+
autorestart=true
|
|
60
|
+
priority=40
|
|
61
|
+
startsecs=3
|
|
62
|
+
startretries=5
|
|
63
|
+
stopsignal=TERM
|
|
64
|
+
stopwaitsecs=20
|
|
65
|
+
redirect_stderr=true
|
|
66
|
+
stdout_logfile=/dev/stdout
|
|
67
|
+
stdout_logfile_maxbytes=0
|
|
68
|
+
stopasgroup=true
|
|
69
|
+
killasgroup=true
|
|
70
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"steps": [
|
|
3
|
+
{
|
|
4
|
+
"name": "Generate deploy files",
|
|
5
|
+
"run": "deploy:init --force",
|
|
6
|
+
"enabled": false
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"name": "Start shared infra",
|
|
10
|
+
"run": "deploy:workflow --server-only",
|
|
11
|
+
"enabled": true
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"name": "Import database dump (optional)",
|
|
15
|
+
"run": "deploy:db:import --file=deploy/nexwire.sql --database=nexwire",
|
|
16
|
+
"enabled": false
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "Start app stack",
|
|
20
|
+
"run": "deploy:workflow --app-only",
|
|
21
|
+
"enabled": true
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"remote": {
|
|
3
|
+
"host": "127.0.0.1",
|
|
4
|
+
"user": "deploy",
|
|
5
|
+
"port": 22,
|
|
6
|
+
"keyPath": "~/.ssh/id_rsa",
|
|
7
|
+
"targetPath": "/home/deploy/nexwire"
|
|
8
|
+
},
|
|
9
|
+
"upload": {
|
|
10
|
+
"source": ".",
|
|
11
|
+
"targetSubPath": "."
|
|
12
|
+
},
|
|
13
|
+
"databaseImport": {
|
|
14
|
+
"enabled": false,
|
|
15
|
+
"file": "deploy/nexwire.sql",
|
|
16
|
+
"database": "nexwire",
|
|
17
|
+
"container": "mysql-global",
|
|
18
|
+
"user": "root"
|
|
19
|
+
},
|
|
20
|
+
"preDeployCommands": [
|
|
21
|
+
"docker rm -f old-app 2>/dev/null || true"
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public API for the {{module}} module.
|
|
3
|
+
*
|
|
4
|
+
* Other modules import from this file, never from controllers/ or services/ directly.
|
|
5
|
+
* Re-export the pieces they are allowed to depend on:
|
|
6
|
+
*
|
|
7
|
+
* export { list, findOne } from "./services/{{controller}}.js";
|
|
8
|
+
*
|
|
9
|
+
* Rule: a facade must never import another module's facade.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Why: Reusable {{name}} logic that does not belong to a controller or service.
|
|
3
|
+
* When: Called by controllers, services, jobs, or other helpers.
|
|
4
|
+
* Where: src/modules/{{module}}/helpers/{{name}}.ts
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Why: Placeholder shared helper.
|
|
9
|
+
* When: Replace this with real logic and rename it.
|
|
10
|
+
* Where: Anywhere inside the {{module}} module.
|
|
11
|
+
*/
|
|
12
|
+
export function {{Name}}Helper<T>(value: T): T {
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { DateTime } from "luxon";
|
|
2
|
+
import { dispatchEvent, shouldQueue } from "@/framework/facade.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* event: Event name this worker listens to.
|
|
6
|
+
* queue: Queue name where jobs are consumed from.
|
|
7
|
+
* handler: Async processor that receives `job.data`.
|
|
8
|
+
*/
|
|
9
|
+
shouldQueue("{{module}}.{{name}}", "default", async job => {
|
|
10
|
+
console.log("{{module}}.{{name}}", job.data);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* dispatchEvent name: Event id to publish to listeners.
|
|
14
|
+
* payload: Data sent to queue/broadcast consumers.
|
|
15
|
+
* queue: Optional queue target for async event consumers.
|
|
16
|
+
* broadcast: Optional realtime fanout (all/users/roles/rooms).
|
|
17
|
+
*/
|
|
18
|
+
// await dispatchEvent("{{module}}.{{name}}.finished", {
|
|
19
|
+
// ...job.data,
|
|
20
|
+
// processedAt: DateTime.now(),
|
|
21
|
+
// });
|
|
22
|
+
|
|
23
|
+
return { ok: true, processedAt: DateTime.now().toISO() };
|
|
24
|
+
});
|
|
25
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { FastifyReply, FastifyRequest } from "fastify";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Why: Route-scoped middleware for the {{module}} module.
|
|
5
|
+
* When: Attach to a route or route scope inside routes/*.ts.
|
|
6
|
+
* Where: src/modules/{{module}}/middlewares/{{name}}.ts
|
|
7
|
+
*
|
|
8
|
+
* Register it in a route file:
|
|
9
|
+
* import { {{camel}}Middleware } from "@/modules/{{module}}/middlewares/{{name}}.js";
|
|
10
|
+
* scope.addHook("preHandler", {{camel}}Middleware);
|
|
11
|
+
* // or: app.route({ ..., preHandler: {{camel}}Middleware, handler })
|
|
12
|
+
*/
|
|
13
|
+
export async function {{camel}}Middleware(_request: FastifyRequest, _reply: FastifyReply) {
|
|
14
|
+
// Guard the route here; send a reply to stop, or return to continue the chain.
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { FastifyReply, FastifyRequest } from "fastify";
|
|
2
|
+
import { db } from "@/framework/facade.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Why: Runs before route handlers to enforce cross-cutting rules for {{name}}.
|
|
6
|
+
* When: Attach to a route scope or a specific route.
|
|
7
|
+
* Where: Add it globally: app.addHook("preHandler", {{camel}}Middleware), or per route:
|
|
8
|
+
* app.route({ ..., preHandler: {{camel}}Middleware, handler })
|
|
9
|
+
* How: request.auth is populated when authMiddleware ran first; db gives
|
|
10
|
+
* typed access to your module models. Return nothing to continue, or
|
|
11
|
+
* return a reply to stop the request.
|
|
12
|
+
*/
|
|
13
|
+
export async function {{camel}}Middleware(_request: FastifyRequest, _reply: FastifyReply) {
|
|
14
|
+
// Guard the request here; send a reply to stop, or return to continue.
|
|
15
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { relations } from "drizzle-orm";
|
|
2
|
+
import { int, mysqlTable, timestamp, varchar } from "drizzle-orm/mysql-core";
|
|
3
|
+
|
|
4
|
+
export const {{tableVariable}} = mysqlTable("{{tableName}}", {
|
|
5
|
+
id: int("id").autoincrement().primaryKey(),
|
|
6
|
+
name: varchar("name", { length: 255 }).notNull(),
|
|
7
|
+
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
8
|
+
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
9
|
+
});
|
|
10
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { relations } from "drizzle-orm";
|
|
2
|
+
import { pgTable, serial, text, timestamp } from "drizzle-orm/pg-core";
|
|
3
|
+
|
|
4
|
+
export const {{tableVariable}} = pgTable("{{tableName}}", {
|
|
5
|
+
id: serial("id").primaryKey(),
|
|
6
|
+
name: text("name").notNull(),
|
|
7
|
+
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
|
|
8
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull()
|
|
9
|
+
});
|
|
10
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { relations } from "drizzle-orm";
|
|
2
|
+
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
|
3
|
+
|
|
4
|
+
export const {{tableVariable}} = sqliteTable("{{tableName}}", {
|
|
5
|
+
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
6
|
+
name: text("name").notNull(),
|
|
7
|
+
createdAt: integer("created_at", { mode: "timestamp" }).$defaultFn(() => new Date()).notNull(),
|
|
8
|
+
updatedAt: integer("updated_at", { mode: "timestamp" }).$defaultFn(() => new Date()).notNull()
|
|
9
|
+
});
|
|
10
|
+
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { FastifyReply, FastifyRequest } from "fastify";
|
|
2
|
+
import { HttpStatusCodes, resolveNotificationProvider } from "@/framework/facade.js";
|
|
3
|
+
import type { NotifiableTarget } from "@/framework/providers/contracts.js";
|
|
4
|
+
|
|
5
|
+
type IdRequest = FastifyRequest<{ Params: { id: string } }>;
|
|
6
|
+
type ListRequest = FastifyRequest<{ Querystring: { page?: string; perPage?: string } }>;
|
|
7
|
+
|
|
8
|
+
/** Read the authenticated owner. A missing auth context means the request is anonymous. */
|
|
9
|
+
function targetFrom(request: FastifyRequest): NotifiableTarget | null {
|
|
10
|
+
const auth = request.auth;
|
|
11
|
+
if (!auth || auth.id === undefined || auth.id === null) return null;
|
|
12
|
+
return { type: "{{notifiable}}", id: auth.id };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function unavailable(reply: FastifyReply) {
|
|
16
|
+
return reply.code(HttpStatusCodes.SERVICE_UNAVAILABLE).send({
|
|
17
|
+
message: "Notification provider not available. Restore src/modules/notification or register one in src/providers/."
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function unauthorized(reply: FastifyReply) {
|
|
22
|
+
return reply.code(HttpStatusCodes.UNAUTHORIZED).send({ message: "Unauthorized" });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const list = async (request: ListRequest, reply: FastifyReply) => {
|
|
26
|
+
const provider = await resolveNotificationProvider();
|
|
27
|
+
if (!provider) return unavailable(reply);
|
|
28
|
+
|
|
29
|
+
const target = targetFrom(request);
|
|
30
|
+
if (!target) return unauthorized(reply);
|
|
31
|
+
|
|
32
|
+
const page = Math.max(1, Number(request.query.page) || 1);
|
|
33
|
+
const perPage = Math.min(50, Math.max(1, Number(request.query.perPage) || 20));
|
|
34
|
+
|
|
35
|
+
return reply.code(HttpStatusCodes.OK).send(await provider.list(target, { page, perPage }));
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const unreadCount = async (request: FastifyRequest, reply: FastifyReply) => {
|
|
39
|
+
const provider = await resolveNotificationProvider();
|
|
40
|
+
if (!provider) return unavailable(reply);
|
|
41
|
+
|
|
42
|
+
const target = targetFrom(request);
|
|
43
|
+
if (!target) return unauthorized(reply);
|
|
44
|
+
|
|
45
|
+
return reply.code(HttpStatusCodes.OK).send({ count: await provider.unreadCount(target) });
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const markRead = async (request: IdRequest, reply: FastifyReply) => {
|
|
49
|
+
const provider = await resolveNotificationProvider();
|
|
50
|
+
if (!provider) return unavailable(reply);
|
|
51
|
+
|
|
52
|
+
const target = targetFrom(request);
|
|
53
|
+
if (!target) return unauthorized(reply);
|
|
54
|
+
|
|
55
|
+
await provider.markRead(target, String(request.params.id));
|
|
56
|
+
return reply.code(HttpStatusCodes.OK).send({ ok: true });
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const markAllRead = async (request: FastifyRequest, reply: FastifyReply) => {
|
|
60
|
+
const provider = await resolveNotificationProvider();
|
|
61
|
+
if (!provider) return unavailable(reply);
|
|
62
|
+
|
|
63
|
+
const target = targetFrom(request);
|
|
64
|
+
if (!target) return unauthorized(reply);
|
|
65
|
+
|
|
66
|
+
await provider.markAllRead(target);
|
|
67
|
+
return reply.code(HttpStatusCodes.OK).send({ ok: true });
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const remove = async (request: IdRequest, reply: FastifyReply) => {
|
|
71
|
+
const provider = await resolveNotificationProvider();
|
|
72
|
+
if (!provider) return unavailable(reply);
|
|
73
|
+
|
|
74
|
+
const target = targetFrom(request);
|
|
75
|
+
if (!target) return unauthorized(reply);
|
|
76
|
+
|
|
77
|
+
await provider.remove(target, String(request.params.id));
|
|
78
|
+
return reply.code(HttpStatusCodes.OK).send({ ok: true });
|
|
79
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { mail, shouldQueue } from "@/framework/facade.js";
|
|
2
|
+
import { logger } from "@/framework/support/logger.js";
|
|
3
|
+
|
|
4
|
+
shouldQueue("notification:mail", "mail", async (job) => {
|
|
5
|
+
const { to, subject, html } = job.data;
|
|
6
|
+
|
|
7
|
+
await mail.sendMail({ to, subject, html });
|
|
8
|
+
logger.info("Notification mail sent", { to, subject });
|
|
9
|
+
|
|
10
|
+
return { ok: true };
|
|
11
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { index, integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Why: Notifications are stored by a standalone module, not by auth.
|
|
5
|
+
* When: `notify()` writes a row and `/api/notification/*` reads it back.
|
|
6
|
+
* Where: `src/modules/notification/database/models/notification.ts`.
|
|
7
|
+
* How: A polymorphic owner (`notifiable_type` + `notifiable_id`) with no
|
|
8
|
+
* foreign key, so deleting or replacing auth cannot cascade-delete
|
|
9
|
+
* notifications and a UUID or numeric owner id stores the same way.
|
|
10
|
+
*/
|
|
11
|
+
export const notifications = sqliteTable(
|
|
12
|
+
"notifications",
|
|
13
|
+
{
|
|
14
|
+
id: text("id").primaryKey(),
|
|
15
|
+
notifiableType: text("notifiable_type").notNull().default("user"),
|
|
16
|
+
notifiableId: text("notifiable_id").notNull(),
|
|
17
|
+
type: text("type").notNull(),
|
|
18
|
+
title: text("title").notNull(),
|
|
19
|
+
body: text("body"),
|
|
20
|
+
data: text("data"),
|
|
21
|
+
link: text("link"),
|
|
22
|
+
readAt: integer("read_at", { mode: "timestamp" }),
|
|
23
|
+
createdAt: integer("created_at", { mode: "timestamp" }).defaultNow().notNull()
|
|
24
|
+
},
|
|
25
|
+
(table) => [
|
|
26
|
+
index("notifications_notifiable_read_idx").on(table.notifiableType, table.notifiableId, table.readAt),
|
|
27
|
+
index("notifications_notifiable_created_idx").on(table.notifiableType, table.notifiableId, table.createdAt)
|
|
28
|
+
]
|
|
29
|
+
);
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { and, count, desc, eq, isNull } from "drizzle-orm";
|
|
3
|
+
import { db } from "@/framework/database/connection.js";
|
|
4
|
+
import type {
|
|
5
|
+
NotifiableTarget,
|
|
6
|
+
NotificationCreateInput,
|
|
7
|
+
NotificationListOptions,
|
|
8
|
+
NotificationPage,
|
|
9
|
+
NotificationProvider,
|
|
10
|
+
NotificationRecord
|
|
11
|
+
} from "@/framework/providers/contracts.js";
|
|
12
|
+
import { registerNotificationProvider } from "@/framework/providers/registry.js";
|
|
13
|
+
import { notifications } from "@/modules/{{module}}/database/models/notification.js";
|
|
14
|
+
|
|
15
|
+
/** Normalize a target into the polymorphic pair stored in the table. */
|
|
16
|
+
function normalizeTarget(target: NotifiableTarget) {
|
|
17
|
+
const type = String(target?.type || "user").trim() || "user";
|
|
18
|
+
if (target?.id === undefined || target?.id === null || target?.id === "") {
|
|
19
|
+
throw new Error("A notification target needs an id.");
|
|
20
|
+
}
|
|
21
|
+
return { type, id: String(target.id) };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Build the owner scope shared by every read and write. */
|
|
25
|
+
function scope(target: NotifiableTarget) {
|
|
26
|
+
const normalized = normalizeTarget(target);
|
|
27
|
+
return {
|
|
28
|
+
target: normalized,
|
|
29
|
+
where: and(eq(notifications.notifiableType, normalized.type), eq(notifications.notifiableId, normalized.id))
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function toIso(value: Date | string | number | null | undefined): string | null {
|
|
34
|
+
if (value === null || value === undefined) return null;
|
|
35
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
36
|
+
return Number.isNaN(date.getTime()) ? null : date.toISOString();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function parseData(value: unknown): Record<string, unknown> | null {
|
|
40
|
+
if (!value) return null;
|
|
41
|
+
if (typeof value === "object") return value as Record<string, unknown>;
|
|
42
|
+
try {
|
|
43
|
+
const parsed = JSON.parse(String(value));
|
|
44
|
+
return parsed && typeof parsed === "object" ? (parsed as Record<string, unknown>) : null;
|
|
45
|
+
} catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Convert a DB row into the stable shape every consumer receives. */
|
|
51
|
+
function normalizeRow(row: typeof notifications.$inferSelect): NotificationRecord {
|
|
52
|
+
return {
|
|
53
|
+
id: String(row.id),
|
|
54
|
+
notifiableType: row.notifiableType,
|
|
55
|
+
notifiableId: row.notifiableId,
|
|
56
|
+
type: row.type,
|
|
57
|
+
title: row.title,
|
|
58
|
+
body: row.body ?? null,
|
|
59
|
+
data: parseData(row.data),
|
|
60
|
+
link: row.link ?? null,
|
|
61
|
+
readAt: toIso(row.readAt),
|
|
62
|
+
createdAt: toIso(row.createdAt) ?? new Date(0).toISOString()
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Why: Ships a working notification store so `notify()` needs no setup.
|
|
68
|
+
* When: Imported at boot by the provider loader (and lazily by the resolver).
|
|
69
|
+
* Where: `src/modules/notification/providers/notification.ts`.
|
|
70
|
+
* How: Drizzle-backed implementation of `NotificationProvider`. The primary
|
|
71
|
+
* key is a UUID string, so ids never depend on the auth table; numeric
|
|
72
|
+
* targets are accepted and stored as text by the contract.
|
|
73
|
+
*/
|
|
74
|
+
export const notificationProvider: NotificationProvider = {
|
|
75
|
+
async create(target: NotifiableTarget, input: NotificationCreateInput): Promise<NotificationRecord> {
|
|
76
|
+
const { target: normalized } = scope(target);
|
|
77
|
+
const id = randomUUID();
|
|
78
|
+
|
|
79
|
+
await db.insert(notifications).values({
|
|
80
|
+
id,
|
|
81
|
+
notifiableType: normalized.type,
|
|
82
|
+
notifiableId: normalized.id,
|
|
83
|
+
type: input.type || "info",
|
|
84
|
+
title: input.title,
|
|
85
|
+
body: input.body ?? null,
|
|
86
|
+
link: input.link ?? null,
|
|
87
|
+
data: input.data ? JSON.stringify(input.data) : null
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const [row] = await db.select().from(notifications).where(eq(notifications.id, id));
|
|
91
|
+
if (!row) throw new Error("Failed to create notification");
|
|
92
|
+
return normalizeRow(row);
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
async list(target: NotifiableTarget, options: NotificationListOptions = {}): Promise<NotificationPage> {
|
|
96
|
+
const { where } = scope(target);
|
|
97
|
+
const page = Math.max(1, Math.floor(Number(options.page) || 1));
|
|
98
|
+
const perPage = Math.min(100, Math.max(1, Math.floor(Number(options.perPage) || 20)));
|
|
99
|
+
|
|
100
|
+
const [totalRows, unreadRows, rows] = await Promise.all([
|
|
101
|
+
db.select({ count: count() }).from(notifications).where(where),
|
|
102
|
+
db.select({ count: count() }).from(notifications).where(and(where, isNull(notifications.readAt))),
|
|
103
|
+
db
|
|
104
|
+
.select()
|
|
105
|
+
.from(notifications)
|
|
106
|
+
.where(where)
|
|
107
|
+
.orderBy(desc(notifications.createdAt))
|
|
108
|
+
.limit(perPage)
|
|
109
|
+
.offset((page - 1) * perPage)
|
|
110
|
+
]);
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
data: rows.map(normalizeRow),
|
|
114
|
+
total: Number(totalRows[0]?.count ?? 0),
|
|
115
|
+
unread: Number(unreadRows[0]?.count ?? 0),
|
|
116
|
+
page,
|
|
117
|
+
perPage
|
|
118
|
+
};
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
async unreadCount(target: NotifiableTarget): Promise<number> {
|
|
122
|
+
const { where } = scope(target);
|
|
123
|
+
const rows = await db
|
|
124
|
+
.select({ count: count() })
|
|
125
|
+
.from(notifications)
|
|
126
|
+
.where(and(where, isNull(notifications.readAt)));
|
|
127
|
+
return Number(rows[0]?.count ?? 0);
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
async markRead(target: NotifiableTarget, id: number | string): Promise<boolean> {
|
|
131
|
+
const { where } = scope(target);
|
|
132
|
+
const notificationWhere = and(where, eq(notifications.id, String(id)));
|
|
133
|
+
const [existing] = await db.select({ id: notifications.id }).from(notifications).where(notificationWhere);
|
|
134
|
+
if (!existing) return false;
|
|
135
|
+
|
|
136
|
+
await db.update(notifications).set({ readAt: new Date() }).where(notificationWhere);
|
|
137
|
+
return true;
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
async markAllRead(target: NotifiableTarget): Promise<number> {
|
|
141
|
+
const { where } = scope(target);
|
|
142
|
+
const unreadWhere = and(where, isNull(notifications.readAt));
|
|
143
|
+
const rows = await db.select({ count: count() }).from(notifications).where(unreadWhere);
|
|
144
|
+
const unread = Number(rows[0]?.count ?? 0);
|
|
145
|
+
if (unread === 0) return 0;
|
|
146
|
+
|
|
147
|
+
await db.update(notifications).set({ readAt: new Date() }).where(unreadWhere);
|
|
148
|
+
return unread;
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
async remove(target: NotifiableTarget, id: number | string): Promise<boolean> {
|
|
152
|
+
const { where } = scope(target);
|
|
153
|
+
const notificationWhere = and(where, eq(notifications.id, String(id)));
|
|
154
|
+
const [existing] = await db.select({ id: notifications.id }).from(notifications).where(notificationWhere);
|
|
155
|
+
if (!existing) return false;
|
|
156
|
+
|
|
157
|
+
await db.delete(notifications).where(notificationWhere);
|
|
158
|
+
return true;
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
async deleteForTarget(target: NotifiableTarget): Promise<number> {
|
|
162
|
+
const { where } = scope(target);
|
|
163
|
+
const rows = await db.select({ count: count() }).from(notifications).where(where);
|
|
164
|
+
const total = Number(rows[0]?.count ?? 0);
|
|
165
|
+
if (total === 0) return 0;
|
|
166
|
+
|
|
167
|
+
await db.delete(notifications).where(where);
|
|
168
|
+
return total;
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
registerNotificationProvider(notificationProvider);
|
|
173
|
+
|
|
174
|
+
export default notificationProvider;
|