@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.
- package/.env.example +76 -0
- package/.github/workflows/publish.yml +33 -0
- package/README.md +85 -0
- package/app/app.ts +85 -0
- package/app/controllers/base.controller.ts +17 -0
- package/app/controllers/iam/auth.controller.ts +144 -0
- package/app/controllers/iam/user.controller.ts +93 -0
- package/app/controllers/index.ts +7 -0
- package/app/jobs/crons/index.ts +9 -0
- package/app/jobs/crons/worker.cron.ts +9 -0
- package/app/jobs/queues/access-log.queue.worker.ts +32 -0
- package/app/jobs/queues/activity-log.queue.worker.ts +32 -0
- package/app/jobs/queues/auth.queue.worker.ts +10 -0
- package/app/jobs/queues/error-log.queue.worker.ts +32 -0
- package/app/jobs/queues/notification.queue.worker.ts +11 -0
- package/app/jobs/queues/worker.queue.ts +38 -0
- package/app/jobs/sockets/worker.socket.ts +3 -0
- package/app/models/iam/role.model.ts +10 -0
- package/app/models/iam/user.model.ts +18 -0
- package/app/models/index.ts +6 -0
- package/app/outputs/mails/index.ts +5 -0
- package/app/outputs/mails/templates/layout.mail.stub +102 -0
- package/app/outputs/mails/templates/user-mail-token.mail.stub +29 -0
- package/app/outputs/mails/user-mail-token.mail.ts +17 -0
- package/app/routes/base.routes.ts +26 -0
- package/app/routes/index.ts +5 -0
- package/barrels.json +10 -0
- package/database/da.migrations/0000_00/activity_logs.ts +27 -0
- package/database/da.migrations/0000_00/logs.ts +41 -0
- package/database/migrations/0000_00/storages.ts +22 -0
- package/database/migrations/0000_00/users.ts +56 -0
- package/database/seeders/user.seeder.ts +24 -0
- package/eslint.config.mjs +36 -0
- package/package.json +39 -0
- package/tsconfig.json +121 -0
- package/utils/commands/skalfa.ts +3 -0
- 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,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,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,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
|
+
}
|