@skalfa/skalfa-api 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/.env.example +76 -0
  2. package/.github/workflows/publish.yml +33 -0
  3. package/README.md +85 -0
  4. package/app/app.ts +85 -0
  5. package/app/controllers/base.controller.ts +17 -0
  6. package/app/controllers/iam/auth.controller.ts +144 -0
  7. package/app/controllers/iam/user.controller.ts +93 -0
  8. package/app/controllers/index.ts +7 -0
  9. package/app/jobs/crons/index.ts +9 -0
  10. package/app/jobs/crons/worker.cron.ts +9 -0
  11. package/app/jobs/queues/access-log.queue.worker.ts +32 -0
  12. package/app/jobs/queues/activity-log.queue.worker.ts +32 -0
  13. package/app/jobs/queues/auth.queue.worker.ts +10 -0
  14. package/app/jobs/queues/error-log.queue.worker.ts +32 -0
  15. package/app/jobs/queues/notification.queue.worker.ts +11 -0
  16. package/app/jobs/queues/worker.queue.ts +38 -0
  17. package/app/jobs/sockets/worker.socket.ts +3 -0
  18. package/app/models/iam/role.model.ts +10 -0
  19. package/app/models/iam/user.model.ts +18 -0
  20. package/app/models/index.ts +6 -0
  21. package/app/outputs/mails/index.ts +5 -0
  22. package/app/outputs/mails/templates/layout.mail.stub +102 -0
  23. package/app/outputs/mails/templates/user-mail-token.mail.stub +29 -0
  24. package/app/outputs/mails/user-mail-token.mail.ts +17 -0
  25. package/app/routes/base.routes.ts +26 -0
  26. package/app/routes/index.ts +5 -0
  27. package/barrels.json +10 -0
  28. package/database/da.migrations/0000_00/activity_logs.ts +27 -0
  29. package/database/da.migrations/0000_00/logs.ts +41 -0
  30. package/database/migrations/0000_00/storages.ts +22 -0
  31. package/database/migrations/0000_00/users.ts +56 -0
  32. package/database/seeders/user.seeder.ts +24 -0
  33. package/eslint.config.mjs +36 -0
  34. package/package.json +39 -0
  35. package/tsconfig.json +121 -0
  36. package/utils/commands/skalfa.ts +3 -0
  37. package/utils/index.ts +2 -0
package/.env.example ADDED
@@ -0,0 +1,76 @@
1
+ # =========================>
2
+ # ## APP
3
+ # =========================>
4
+ APP_NAME=aluna_api
5
+ APP_PORT=4000
6
+ APP_CORS_ORIGINS=
7
+ APP_CORS_METHODS=
8
+ APP_CORS_HEADERS=
9
+
10
+
11
+ # =========================>
12
+ # ## DATABASE (OLTP)
13
+ # =========================>
14
+ DB_CONNECTION=pg
15
+ DB_HOST=127.0.0.1
16
+ DB_PORT=5432
17
+ DB_USERNAME=postgres
18
+ DB_PASSWORD=password
19
+ DB_DATABASE=db_aluna_api
20
+
21
+
22
+ # =========================>
23
+ # ## DATABASE (OLAP)
24
+ # =========================>
25
+
26
+
27
+ # =========================>
28
+ # ## REDIS
29
+ # =========================>
30
+
31
+
32
+ # =========================>
33
+ # ## CRONJOB
34
+ # =========================>
35
+
36
+
37
+ # =========================>
38
+ # ## SOCKET
39
+ # =========================>
40
+
41
+
42
+ # =========================>
43
+ # ## LOGGER
44
+ # =========================>
45
+ ACCESS_LOG_DRIVER=
46
+ ACCESS_LOG_DIR=
47
+ ACCESS_LOG_TABLE=
48
+ ACCESS_LOG_QUEUE=
49
+ ACCESS_LOG_CONCURRENCY=
50
+ ACCESS_LOG_FLUSH=
51
+
52
+ ERROR_LOG_DRIVER=
53
+ ERROR_LOG_DIR=
54
+ ERROR_LOG_TABLE=
55
+ ERROR_LOG_QUEUE=
56
+ ERROR_LOG_CONCURRENCY=
57
+ ERROR_LOG_FLUSH=
58
+
59
+ ACTIVITY_LOG_DRIVER=
60
+ ACTIVITY_LOG_DB_TABLE=
61
+ ACTIVITY_LOG_DA_TABLE=
62
+ ACTIVITY_LOG_QUEUE=
63
+ ACTIVITY_LOG_CONCURRENCY=
64
+
65
+
66
+ # =========================>
67
+ # ## MAIL
68
+ # =========================>
69
+ MAIL_MAILER=smtp
70
+ MAIL_HOST=smtp.gmail.com
71
+ MAIL_PORT=587
72
+ MAIL_USERNAME=
73
+ MAIL_PASSWORD=
74
+ MAIL_ENCRYPTION=tls
75
+ MAIL_FROM_ADDRESS=
76
+ MAIL_FROM_NAME=
@@ -0,0 +1,33 @@
1
+ name: Publish to npm
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ - master
8
+
9
+ jobs:
10
+ publish:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - name: Checkout repository
14
+ uses: actions/checkout@v4
15
+
16
+ - name: Setup Node.js
17
+ uses: actions/setup-node@v4
18
+ with:
19
+ node-version: 20
20
+ registry-url: https://registry.npmjs.org/
21
+
22
+ - name: Publish if version bumped
23
+ run: |
24
+ LOCAL_VER=$(node -p "require('./package.json').version")
25
+ NPM_VER=$(npm view @skalfa/skalfa-api version 2>/dev/null || echo "0.0.0")
26
+ if [ "$LOCAL_VER" != "$NPM_VER" ]; then
27
+ echo "Publishing @skalfa/skalfa-api $LOCAL_VER (Registry version: $NPM_VER)"
28
+ npm publish --access public
29
+ else
30
+ echo "@skalfa/skalfa-api is up to date ($LOCAL_VER)"
31
+ fi
32
+ env:
33
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/README.md ADDED
@@ -0,0 +1,85 @@
1
+ ## About Elysia Light
2
+
3
+ Bun & Elysia stater kit for API Service Development with CRUD Generation, API Service Configuration, Etc. with elysia light save your time just for more creative thinking.
4
+
5
+ <br>
6
+
7
+ ## Version Elysia ^ 1.4
8
+
9
+ <p align="center"><a href="https://elysiajs.com/" target="_blank"><img src="https://elysiajs.com/assets/elysia_v.webp" width="70%" alt="Elysia Banner"></a></p>
10
+
11
+ <br>
12
+ <br>
13
+
14
+ ## Magic Command
15
+
16
+ ### Make Controller
17
+
18
+ ```
19
+ bun light make:controller {name}
20
+ ```
21
+
22
+ ### Make Light Controller
23
+
24
+ ```
25
+ bun light make:light-controller {name} --model={model[optional]}
26
+ ```
27
+
28
+ ### Make Model
29
+
30
+ ```
31
+ bun light make:model {name}
32
+ ```
33
+
34
+ ### Make Light Model
35
+
36
+ ```
37
+ bun light make:light-model {name}
38
+ ```
39
+
40
+ ### Make Migration
41
+
42
+ ```
43
+ bun light make:migration {name}
44
+ ```
45
+
46
+ ### Run Migration
47
+
48
+ ```
49
+ bun light migrate
50
+ ```
51
+
52
+ ### Run Fresh Migration
53
+
54
+ ```
55
+ bun light migrate:fresh
56
+ ```
57
+
58
+ ### Make Seeder
59
+
60
+ ```
61
+ bun light make:seeder {name}
62
+ ```
63
+
64
+ ### Run Seeder
65
+
66
+ ```
67
+ bun light seed
68
+ ```
69
+
70
+ ### Make Blueprint
71
+
72
+ ```
73
+ bun light make:blueprint {name}
74
+ ```
75
+
76
+ ### Run Blueprints
77
+
78
+ ```
79
+ bun light blueprint {name[optional]}
80
+ ```
81
+
82
+
83
+ ========================= <br>
84
+ Creative by: [SEJE Digital](https://sejedigital.com) <br>
85
+ ========================= <br>
package/app/app.ts ADDED
@@ -0,0 +1,85 @@
1
+ import os from 'os'
2
+ import { Elysia } from 'elysia'
3
+ import { controller, db, logger, middleware, storage, registry } from "@utils"
4
+ import { routes } from '@routes'
5
+
6
+
7
+ // =====================================>
8
+ // ## Init: middleware & router app
9
+ // =====================================>
10
+ export const app = new Elysia()
11
+ .use(middleware.AccessLog)
12
+ .use(middleware.Cors)
13
+ .use(middleware.Auth)
14
+ .use(middleware.BodyParse)
15
+ .use(controller)
16
+ .use(storage)
17
+ .use(routes)
18
+ .use(middleware.ErrorHandler)
19
+
20
+
21
+ // =====================================>
22
+ // ## Init: database
23
+ // =====================================>
24
+ if (process.env.DB_HOST && process.env.DB_PORT && process.env.DB_USERNAME && process.env.DB_PASSWORD && process.env.DB_DATABASE) {
25
+ db.schema
26
+ logger.start(`Database connected ${process.env.DB_DATABASE}!`)
27
+ }
28
+
29
+
30
+ // =====================================>
31
+ // ## Init: database olap
32
+ // =====================================>
33
+ // if (process.env.DA_HOST && process.env.DA_PORT && process.env.DA_USERNAME && process.env.DA_PASSWORD && process.env.DA_DATABASE) {
34
+ // daClient.ping();
35
+ // logger.start(`Database (OLAP) connected ${process.env.DA_DATABASE}!`)
36
+ // }
37
+
38
+
39
+ // =====================================>
40
+ // ## Init: redis
41
+ // =====================================>
42
+ // if (process.env.REDIS_HOST && process.env.REDIS_PORT) {
43
+ // redis.on("connect", () => {
44
+ // logger.start(`Redis connected ${process.env.REDIS_HOST}:${process.env.REDIS_PORT}!`)
45
+ // })
46
+ //
47
+ // redis.on("error", (err) => {
48
+ // const em = err instanceof Error ? err.message : String(err)
49
+ // logger.error(`Redis error: ${em}`, { error: em })
50
+ // })
51
+ // }
52
+
53
+
54
+ // =====================================>
55
+ // ## Init: cron
56
+ // =====================================>
57
+ // cron.worker()
58
+
59
+
60
+ // =====================================>
61
+ // ## Init: socket
62
+ // =====================================>
63
+ // if (process.env.SOCKET_PORT) {
64
+ // socket.start(Number(process.env.SOCKET_PORT))
65
+ // }
66
+
67
+
68
+ // =====================================>
69
+ // ## Init: running server
70
+ // =====================================>
71
+ function getLocalIP() {
72
+ const interfaces = os.networkInterfaces()
73
+ for (const name of Object.keys(interfaces)) {
74
+ for (const net of interfaces[name] || []) {
75
+ if (net.family === 'IPv4' && !net.internal) return net.address
76
+ }
77
+ }
78
+ }
79
+
80
+ app.listen({ port: process.env.APP_PORT, hostname: '0.0.0.0' })
81
+ setTimeout(() => logger.start(`Server is running at \n [LOCAL] http://localhost:${process.env.APP_PORT || 4000} \n [NETWORK] http://${getLocalIP()}:${process.env.APP_PORT || 4000}!`), 200)
82
+
83
+
84
+
85
+
@@ -0,0 +1,17 @@
1
+ import { permission } from "@utils";
2
+
3
+ export class BaseController {
4
+ static async index() {
5
+ return {
6
+ message: `Welcome to the API of ${process.env.APP_NAME}!`,
7
+ };
8
+ }
9
+
10
+ static async feature() {
11
+ return permission.getFeatures()
12
+ }
13
+
14
+ static async access() {
15
+ return permission.getAccesses()
16
+ }
17
+ }
@@ -0,0 +1,144 @@
1
+ import { ControllerContext } from "elysia"
2
+ import bcrypt from 'bcrypt';
3
+ import { User } from "app/models"
4
+ import { auth, db } from '@utils';
5
+ import { UserMailToken } from "app/outputs/mails";
6
+
7
+ export class AuthController {
8
+ // =============================================>
9
+ // ## Login with email & password
10
+ // =============================================>
11
+ static async login(c: ControllerContext) {
12
+ await c.validation({
13
+ email : "required",
14
+ password : "required",
15
+ })
16
+
17
+ const { email, password } = c.body as Record<string, any>
18
+
19
+ const user = await User.query().where("email", email).whereNotNull("email_verification_at").first();
20
+ if (!user) return c.responseErrorValidation({email: ["E-mail not found!"]})
21
+
22
+ const checkPassword = await bcrypt.compare(password, user.password)
23
+ if (!checkPassword) return c.responseErrorValidation({password: ["Wrong password!"]})
24
+
25
+ const { token } = await auth.createAccessToken(user.id, c.request)
26
+
27
+ c.responseSuccess({ user, token }, "Success")
28
+ }
29
+
30
+
31
+ // =============================================>
32
+ // ## Register new account.
33
+ // =============================================>
34
+ static async register(c: ControllerContext) {
35
+ await c.validation({
36
+ name : "required",
37
+ email : "required",
38
+ password : "required",
39
+ })
40
+
41
+ const trx = await db.transaction()
42
+
43
+ const { email, password } = c.body as Record<string, any>
44
+
45
+ const checkRegisteredUser = await User.query().where("email", email).whereNotNull("email_verification_at").first()
46
+ if (checkRegisteredUser) c.responseErrorValidation({email: ["Email is registered!"]})
47
+
48
+ await User.query().where("email", email).whereNull("email_verification_at").delete();
49
+
50
+ const model = new User().fill(c.body as Record<string, any>)
51
+
52
+ model.password = await bcrypt.hash(password, 10)
53
+
54
+ try {
55
+ await model.save()
56
+ } catch (err) {
57
+ await trx.rollback()
58
+ c.responseError(err as Error, "Create User")
59
+ }
60
+
61
+ const user = model.toJSON()
62
+
63
+ const { token } = await auth.createAccessToken(user.id, c.request, false)
64
+
65
+ const { token: mailToken } = await auth.createUserMailToken(user.id)
66
+
67
+ await UserMailToken(user, mailToken)
68
+
69
+ await trx.commit()
70
+
71
+ c.responseSuccess({ user, token }, "Success")
72
+ }
73
+
74
+
75
+ // =============================================>
76
+ // ## Verify user mail token.
77
+ // =============================================>
78
+ static async verify(c: ControllerContext) {
79
+ await c.validation({
80
+ token : "required",
81
+ })
82
+
83
+ const { token } = c.body as Record<string, any>
84
+
85
+ if (!c.user) {
86
+ throw c.status(401, {
87
+ message: "Unauthorized!"
88
+ })
89
+ }
90
+
91
+ const verify = await auth.verifyUserMailToken(c?.user?.id, String(token));
92
+ if(!verify) {
93
+ c.responseErrorValidation({token: ["Invalid Token!"]})
94
+ }
95
+
96
+ await User.query().where("id", c?.user?.id).update({ email_verification_at: new Date() })
97
+
98
+
99
+ c.responseSuccess({ user: c.user }, "Success")
100
+ }
101
+
102
+
103
+ // =============================================>
104
+ // ## Get logged account
105
+ // =============================================>
106
+ static async me(c: ControllerContext) {
107
+ c.responseSuccess(c.user, "Success")
108
+ }
109
+
110
+ // =============================================>
111
+ // ## Edit logged account
112
+ // =============================================>
113
+ static async update(c: ControllerContext) {
114
+ const model = await User.query().findOrNotFound(c.user.id)
115
+
116
+ await c.validation({
117
+ name : "required",
118
+ email : "required",
119
+ })
120
+
121
+ const trx = await db.transaction()
122
+
123
+ const body = c.body as Record<string, any>;
124
+
125
+ model.fill(body)
126
+
127
+
128
+ if (body.image && body.image instanceof File) {
129
+ const imageSource = await c.uploadFile(body.image, 'users');
130
+
131
+ model.fill({image: imageSource});
132
+ }
133
+
134
+ try {
135
+ await model.save({ trx })
136
+ } catch (err) {
137
+ await trx.rollback()
138
+ c.responseError(err as Error, "Create User")
139
+ }
140
+
141
+ await trx.commit()
142
+ c.responseSaved(model.toJSON())
143
+ }
144
+ }
@@ -0,0 +1,93 @@
1
+ import type { ControllerContext } from "elysia"
2
+ import { db, permission, ValidationRules } from '@utils'
3
+ import { User } from "app/models"
4
+
5
+ export const UserPermission = permission.register({
6
+ "100": {
7
+ name: "User Management",
8
+ accesses: {
9
+ "01": "View",
10
+ "02": "create",
11
+ "03": "update",
12
+ "04": "delete",
13
+ }
14
+ },
15
+ })
16
+
17
+ export class UserController {
18
+ // ========================================>
19
+ // ## Display a listing of the resource.
20
+ // ========================================>
21
+ static async index(c: ControllerContext) {
22
+ const users = await User.query().resolve(c)
23
+
24
+ c.responseData(users.data, users.total)
25
+ }
26
+
27
+ // =============================================>
28
+ // ## Store a newly created resource.
29
+ // =============================================>
30
+ static async store(c: ControllerContext) {
31
+
32
+ c.validation<User>({
33
+ name : "required",
34
+ email : ["required", "email"],
35
+ })
36
+
37
+ const trx = await db.transaction()
38
+
39
+ let record = new User();
40
+
41
+ try {
42
+ record = await record.pump(c.body as Record<string, any>, { trx })
43
+ } catch (err) {
44
+ await trx.rollback()
45
+ return c.responseError(err as Error, "Create User")
46
+ }
47
+
48
+ await trx.commit()
49
+
50
+ c.responseSaved(record)
51
+ }
52
+
53
+
54
+ // ============================================>
55
+ // ## Update the specified resource.
56
+ // ============================================>
57
+ static async update(c: ControllerContext) {
58
+ let record = await User.query().findOrNotFound(c.params.id);
59
+
60
+ c.validation({
61
+ name : "required",
62
+ email : "required",
63
+ })
64
+
65
+ const trx = await db.transaction()
66
+
67
+ try {
68
+ record = await record.pump(c.body as Record<string, any>, { trx })
69
+ } catch (err) {
70
+ await trx.rollback()
71
+ return c.responseError(err as Error, "Create User")
72
+ }
73
+
74
+ await trx.commit()
75
+ c.responseSaved(record)
76
+ }
77
+
78
+
79
+ // ===============================================>
80
+ // ## Remove the specified resource.
81
+ // ===============================================>
82
+ static async destroy(c: ControllerContext) {
83
+ let record = await User.query().findOrNotFound(c.params.id)
84
+
85
+ try {
86
+ record = await record.delete()
87
+ } catch (err) {
88
+ c.responseError(err as Error, "Delete User")
89
+ }
90
+
91
+ c.responseSuccess(record)
92
+ }
93
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @file Automatically generated by barrelsby.
3
+ */
4
+
5
+ export * from "./base.controller";
6
+ export * from "./iam/auth.controller";
7
+ export * from "./iam/user.controller";
@@ -0,0 +1,9 @@
1
+ import { cron } from "@utils";
2
+
3
+
4
+
5
+ // ============================================>
6
+ // ## List of cron jobs.
7
+ // ============================================>
8
+ // eslint-disable-next-line no-console
9
+ cron.add("1 * * * *", () => console.log('example cron job...'), 'example');
@@ -0,0 +1,9 @@
1
+ import { cron, logger } from "@utils";
2
+ import "./index"
3
+
4
+
5
+ // ============================================>
6
+ // ## Run of cron job worker.
7
+ // ============================================>
8
+ cron.worker();
9
+ logger.start(`Cron job workers is running!`)
@@ -0,0 +1,32 @@
1
+ import { queue, da, AccessLog } from '@utils'
2
+
3
+ const ACCESS_LOG_QUEUE = process.env.ACCESS_LOG_QUEUE || "access-log"
4
+ const ACCESS_LOG_TABLE = process.env.ACCESS_LOG_TABLE || 'access_logs'
5
+ const ACCESS_LOG_CONCURRENCY = process.env.ACCESS_LOG_CONCURRENCY || 500
6
+ const ACCESS_LOG_FLUSH = process.env.ACCESS_LOG_FLUSH || 1000
7
+
8
+ let accessBuffer: AccessLog[] = []
9
+ let lastAccessFlush = Date.now()
10
+
11
+ export const accessLogQueueWorker = () => {
12
+ queue.worker(ACCESS_LOG_QUEUE, async (payload) => {
13
+ accessBuffer.push(payload as AccessLog)
14
+
15
+ const now = Date.now()
16
+
17
+ if (accessBuffer.length >= Number(ACCESS_LOG_CONCURRENCY) || now - lastAccessFlush >= Number(ACCESS_LOG_FLUSH)) {
18
+ if (!accessBuffer.length) return
19
+
20
+ const batch = accessBuffer
21
+ accessBuffer = []
22
+ lastAccessFlush = Date.now()
23
+
24
+ await da.insert(ACCESS_LOG_TABLE, {
25
+ values : batch,
26
+ format : 'JSONEachRow'
27
+ })
28
+ }
29
+ },
30
+ { concurrency: 1, interval: 50 }
31
+ )
32
+ }
@@ -0,0 +1,32 @@
1
+ import { queue, da } from '@utils'
2
+
3
+ const ACTIVITY_LOG_QUEUE = process.env.ACTIVITY_LOG_QUEUE ||'activity-log'
4
+ const ACTIVITY_LOG_CONCURRENCY = process.env.ACCESS_LOG_CONCURRENCY || 500
5
+ const ACTIVITY_LOG_FLUSH = process.env.ACCESS_LOG_FLUS || 2000
6
+ const ACTIVITY_LOG_DA_TABLE = process.env.ACTIVITY_LOG_DA_TABLE || 'activity_logs'
7
+
8
+ let buffer: any[] = []
9
+ let lastFlush = Date.now()
10
+
11
+ export const activityLogQueueWorker = () => {
12
+ queue.worker(ACTIVITY_LOG_QUEUE, async (payload) => {
13
+ buffer.push(payload)
14
+
15
+ const now = Date.now()
16
+ if (buffer.length >= Number(ACTIVITY_LOG_CONCURRENCY) || now - lastFlush >= Number(ACTIVITY_LOG_FLUSH)) {
17
+ if (!buffer.length) return
18
+
19
+ const batch = buffer
20
+ buffer = []
21
+ lastFlush = Date.now()
22
+
23
+ await da.insert(ACTIVITY_LOG_DA_TABLE, {
24
+ values : batch,
25
+ format : 'JSONEachRow'
26
+ })
27
+ }
28
+ },
29
+ { concurrency: 1, interval: 50 }
30
+ )
31
+
32
+ }
@@ -0,0 +1,10 @@
1
+ import { auth, queue } from "@utils"
2
+
3
+ export const activityLogQueueWorker = () => {
4
+ queue.worker("auth:revalidate-permission", async (payload) => {
5
+ const userId = payload?.userId
6
+
7
+ await auth.revalidateUserPermissions(userId)
8
+ }
9
+ )
10
+ }
@@ -0,0 +1,32 @@
1
+ import { queue, da, ErrorLog } from '@utils'
2
+
3
+ const ERROR_LOG_QUEUE = process.env.ERROR_LOG_QUEUE || "error-log"
4
+ const ACCESS_LOG_TABLE = process.env.ACCESS_LOG_TABLE || 'error_logs'
5
+ const ERROR_LOG_CONCURRENCY = process.env.ERROR_LOG_CONCURRENCY || 10
6
+ const ERROR_LOG_FLUSH = process.env.ERROR_LOG_FLUSH || 200
7
+
8
+ let errorBuffer: ErrorLog[] = []
9
+ let lastErrorFlush = Date.now()
10
+
11
+ export const errorLogQueueWorker = () => {
12
+ queue.worker(ERROR_LOG_QUEUE, async (payload) => {
13
+ errorBuffer.push(payload as ErrorLog)
14
+
15
+ const now = Date.now()
16
+
17
+ if (errorBuffer.length >= Number(ERROR_LOG_CONCURRENCY) || now - lastErrorFlush >= Number(ERROR_LOG_FLUSH)) {
18
+ if (!errorBuffer.length) return
19
+
20
+ const batch = errorBuffer
21
+ errorBuffer = []
22
+ lastErrorFlush = Date.now()
23
+
24
+ await da.insert(ACCESS_LOG_TABLE, {
25
+ values : batch,
26
+ format : 'JSONEachRow'
27
+ })
28
+ }
29
+ },
30
+ { concurrency: 1, interval: 50 }
31
+ )
32
+ }
@@ -0,0 +1,11 @@
1
+ import { queue, notification, NotificationPayload, NotificationCancelPayload } from '@utils'
2
+
3
+ export const notificationQueueWorker = () => {
4
+ queue.worker("notifications", async (payload) => {
5
+ if (payload?.type != "cancel") {
6
+ notification.send(payload as NotificationPayload)
7
+ } else {
8
+ notification.cancel(payload as NotificationCancelPayload)
9
+ }
10
+ }, { concurrency: 1, interval: 50 })
11
+ }