@skalfa/skalfa-api 1.0.0 → 1.0.2

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 (34) hide show
  1. package/.env.example +75 -75
  2. package/.github/workflows/publish.yml +7 -0
  3. package/CONTRIBUTING.md +45 -0
  4. package/LICENSE +21 -0
  5. package/README.md +79 -85
  6. package/app/app.ts +74 -85
  7. package/app/controllers/base.controller.ts +16 -16
  8. package/app/controllers/iam/auth.controller.ts +143 -143
  9. package/app/controllers/iam/user.controller.ts +92 -92
  10. package/app/jobs/crons/index.ts +8 -8
  11. package/app/jobs/crons/worker.cron.ts +8 -8
  12. package/app/jobs/queues/access-log.queue.worker.ts +32 -32
  13. package/app/jobs/queues/activity-log.queue.worker.ts +32 -32
  14. package/app/jobs/queues/auth.queue.worker.ts +9 -9
  15. package/app/jobs/queues/error-log.queue.worker.ts +32 -32
  16. package/app/jobs/queues/notification.queue.worker.ts +11 -11
  17. package/app/jobs/queues/worker.queue.ts +37 -37
  18. package/app/jobs/sockets/worker.socket.ts +2 -2
  19. package/app/models/iam/user.model.ts +17 -17
  20. package/app/outputs/mails/templates/layout.mail.stub +102 -102
  21. package/app/outputs/mails/templates/user-mail-token.mail.stub +28 -28
  22. package/app/outputs/mails/user-mail-token.mail.ts +17 -17
  23. package/app/outputs/notifications/example.notification.ts +10 -0
  24. package/app/outputs/notifications/index.ts +1 -0
  25. package/app/routes/base.routes.ts +25 -25
  26. package/barrels.json +9 -9
  27. package/database/da.migrations/0000_00/activity_logs.ts +27 -27
  28. package/database/da.migrations/0000_00/logs.ts +41 -41
  29. package/database/migrations/0000_00/notifications.ts +28 -0
  30. package/database/migrations/0000_00/storages.ts +22 -22
  31. package/database/migrations/0000_00/users.ts +55 -55
  32. package/database/seeders/user.seeder.ts +23 -23
  33. package/eslint.config.mjs +36 -36
  34. package/package.json +40 -39
@@ -1,32 +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
- }
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
+ }
@@ -1,11 +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
- }
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
+ }
@@ -1,38 +1,38 @@
1
- import { logger, queue } from "@utils";
2
- import { activityLogQueueWorker } from "./activity-log.queue.worker";
3
- import { accessLogQueueWorker } from "./access-log.queue.worker";
4
- import { errorLogQueueWorker } from "./error-log.queue.worker";
5
- import { notificationQueueWorker } from "./notification.queue.worker";
6
-
7
-
8
-
9
- // ============================================>
10
- // ## Run of queue workers.
11
- // ============================================>
12
- queue.worker("example", async (payload, id) => {
13
- logger.queue(`Start queue ${id}`)
14
-
15
- if (Math.random() < 0.5) {
16
- logger.queueError(`Queue ${id} intentionally failed`)
17
- throw new Error(`Random failure for job ${id}`)
18
- }
19
-
20
- const wait = () => new Promise((resolve) =>
21
- setTimeout(() => {
22
- logger.queue("Queue payload date:" + payload?.date)
23
- resolve("")
24
- }, 5000)
25
- )
26
-
27
- await wait()
28
-
29
- logger.queue(`Finish queue ${id}`)
30
- });
31
-
32
- activityLogQueueWorker()
33
- accessLogQueueWorker()
34
- errorLogQueueWorker()
35
- notificationQueueWorker()
36
-
37
-
1
+ import { logger, queue } from "@utils";
2
+ import { activityLogQueueWorker } from "./activity-log.queue.worker";
3
+ import { accessLogQueueWorker } from "./access-log.queue.worker";
4
+ import { errorLogQueueWorker } from "./error-log.queue.worker";
5
+ import { notificationQueueWorker } from "./notification.queue.worker";
6
+
7
+
8
+
9
+ // ============================================>
10
+ // ## Run of queue workers.
11
+ // ============================================>
12
+ queue.worker("example", async (payload, id) => {
13
+ logger.queue(`Start queue ${id}`)
14
+
15
+ if (Math.random() < 0.5) {
16
+ logger.queueError(`Queue ${id} intentionally failed`)
17
+ throw new Error(`Random failure for job ${id}`)
18
+ }
19
+
20
+ const wait = () => new Promise((resolve) =>
21
+ setTimeout(() => {
22
+ logger.queue("Queue payload date:" + payload?.date)
23
+ resolve("")
24
+ }, 5000)
25
+ )
26
+
27
+ await wait()
28
+
29
+ logger.queue(`Finish queue ${id}`)
30
+ });
31
+
32
+ activityLogQueueWorker()
33
+ accessLogQueueWorker()
34
+ errorLogQueueWorker()
35
+ notificationQueueWorker()
36
+
37
+
38
38
  logger.start(`Queue job workers is running!`)
@@ -1,3 +1,3 @@
1
- import { socket } from "@utils";
2
-
1
+ import { socket } from "@utils";
2
+
3
3
  socket.start(Number(process.env.SOCKET_PORT ?? 4500));
@@ -1,18 +1,18 @@
1
- import { Field, Model } from '@utils'
2
-
3
- export class User extends Model {
4
- @Field(["fillable", "selectable", "searchable"])
5
- name!: string
6
-
7
- @Field(["fillable", "selectable", "searchable"])
8
- email!: string
9
-
10
- @Field(["fillable"])
11
- password!: string
12
-
13
- @Field(["fillable", "selectable"])
14
- image!: string
15
-
16
- @Field(["fillable", "selectable", "searchable"])
17
- email_verification_at!: Date
1
+ import { Field, Model } from '@utils'
2
+
3
+ export class User extends Model {
4
+ @Field(["fillable", "selectable", "searchable"])
5
+ name!: string
6
+
7
+ @Field(["fillable", "selectable", "searchable"])
8
+ email!: string
9
+
10
+ @Field(["fillable"])
11
+ password!: string
12
+
13
+ @Field(["fillable", "selectable"])
14
+ image!: string
15
+
16
+ @Field(["fillable", "selectable", "searchable"])
17
+ email_verification_at!: Date
18
18
  }
@@ -1,102 +1,102 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <meta http-equiv="X-UA-Compatible" content="ie=edge" />
7
- <title>{{title}}</title>
8
-
9
- <link
10
- href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600&display=swap"
11
- rel="stylesheet"
12
- />
13
- </head>
14
- <body
15
- style="
16
- margin: 0;
17
- font-family: 'Poppins', sans-serif;
18
- background: #ebf4fc;
19
- font-size: 14px;
20
- "
21
- >
22
- <div
23
- style="
24
- max-width: 680px;
25
- margin: 0 auto;
26
- padding: 35px 10px 40px;
27
- background: #ebf4fc;
28
- font-size: 14px;
29
- color: #575757;
30
- "
31
- >
32
- <header>
33
- <table style="width: 100%;">
34
- <tbody>
35
- <tr style="height: 0;">
36
- <td style="font-weight: bold;font-size: 20;">
37
- {{app_name}}
38
- </td>
39
- <td style="text-align: right;">
40
- <span style="font-size: 16px; line-height: 30px;">{{date}}</span>
41
- </td>
42
- </tr>
43
- </tbody>
44
- </table>
45
- </header>
46
-
47
- <main>
48
- <div
49
- style="
50
- margin: 0;
51
- margin-top: 30px;
52
- padding: 50px 20px 55px;
53
- background: #ffffff;
54
- border-radius: 30px;
55
- text-align: center;
56
- "
57
- >
58
- <div style="width: 100%; max-width: 489px; margin: 0 auto;">
59
- <h1
60
- style="
61
- margin: 0;
62
- font-size: 24px;
63
- font-weight: 500;
64
- "
65
- >{{title}}</h1>
66
-
67
- {{content}}
68
-
69
- </div>
70
- </div>
71
- </main>
72
-
73
- <footer
74
- style="
75
- width: 100%;
76
- max-width: 490px;
77
- margin: 20px auto 0;
78
- text-align: center;
79
- border-top: 1px solid #575757;
80
- "
81
- >
82
- <p
83
- style="
84
- margin: 0;
85
- margin-top: 20px;
86
- font-size: 16px;
87
- font-weight: 600;
88
- color: #5aafff;
89
- "
90
- >
91
- {{app_name}}
92
- </p>
93
- <p style="margin: 0; margin-top: 8px; color: #575757;">
94
- sejedigital.com
95
- </p>
96
- <p style="margin: 0; margin-top: 10px; color: #575757;">
97
- Copyright © 2022 {{app_name}}. All rights reserved.
98
- </p>
99
- </footer>
100
- </div>
101
- </body>
102
- </html>
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <meta http-equiv="X-UA-Compatible" content="ie=edge" />
7
+ <title>{{title}}</title>
8
+
9
+ <link
10
+ href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600&display=swap"
11
+ rel="stylesheet"
12
+ />
13
+ </head>
14
+ <body
15
+ style="
16
+ margin: 0;
17
+ font-family: 'Poppins', sans-serif;
18
+ background: #ebf4fc;
19
+ font-size: 14px;
20
+ "
21
+ >
22
+ <div
23
+ style="
24
+ max-width: 680px;
25
+ margin: 0 auto;
26
+ padding: 35px 10px 40px;
27
+ background: #ebf4fc;
28
+ font-size: 14px;
29
+ color: #575757;
30
+ "
31
+ >
32
+ <header>
33
+ <table style="width: 100%;">
34
+ <tbody>
35
+ <tr style="height: 0;">
36
+ <td style="font-weight: bold;font-size: 20;">
37
+ {{app_name}}
38
+ </td>
39
+ <td style="text-align: right;">
40
+ <span style="font-size: 16px; line-height: 30px;">{{date}}</span>
41
+ </td>
42
+ </tr>
43
+ </tbody>
44
+ </table>
45
+ </header>
46
+
47
+ <main>
48
+ <div
49
+ style="
50
+ margin: 0;
51
+ margin-top: 30px;
52
+ padding: 50px 20px 55px;
53
+ background: #ffffff;
54
+ border-radius: 30px;
55
+ text-align: center;
56
+ "
57
+ >
58
+ <div style="width: 100%; max-width: 489px; margin: 0 auto;">
59
+ <h1
60
+ style="
61
+ margin: 0;
62
+ font-size: 24px;
63
+ font-weight: 500;
64
+ "
65
+ >{{title}}</h1>
66
+
67
+ {{content}}
68
+
69
+ </div>
70
+ </div>
71
+ </main>
72
+
73
+ <footer
74
+ style="
75
+ width: 100%;
76
+ max-width: 490px;
77
+ margin: 20px auto 0;
78
+ text-align: center;
79
+ border-top: 1px solid #575757;
80
+ "
81
+ >
82
+ <p
83
+ style="
84
+ margin: 0;
85
+ margin-top: 20px;
86
+ font-size: 16px;
87
+ font-weight: 600;
88
+ color: #5aafff;
89
+ "
90
+ >
91
+ {{app_name}}
92
+ </p>
93
+ <p style="margin: 0; margin-top: 8px; color: #575757;">
94
+ sejedigital.com
95
+ </p>
96
+ <p style="margin: 0; margin-top: 10px; color: #575757;">
97
+ Copyright © 2022 {{app_name}}. All rights reserved.
98
+ </p>
99
+ </footer>
100
+ </div>
101
+ </body>
102
+ </html>
@@ -1,29 +1,29 @@
1
- <p style="
2
- margin: 0;
3
- margin-top: 17px;
4
- font-size: 16px;
5
- font-weight: 500;
6
- ">Hai {{name}},</p>
7
-
8
- <p style="
9
- margin: 0;
10
- margin-top: 17px;
11
- font-weight: 500;
12
- letter-spacing: 0.56px;
13
- ">Masukkan kode dibawah ini untuk verifikasi akun kamu, jika kode tidak berhasil coba meminta untuk kirim email kembali. kode hanya bisa digunakan selama 1 jam setelah email terkirim.</p>
14
-
15
- <p style="
16
- margin: 0;
17
- margin-top: 40px;
18
- font-size: 40px;
19
- font-weight: 600;
20
- letter-spacing: 15px;
21
- color: #5aafff;
22
- ">{{token}}</p>
23
-
24
- <p style="
25
- margin: 0;
26
- margin-top: 70px;
27
- font-weight: 500;
28
- letter-spacing: 0.56px;
1
+ <p style="
2
+ margin: 0;
3
+ margin-top: 17px;
4
+ font-size: 16px;
5
+ font-weight: 500;
6
+ ">Hai {{name}},</p>
7
+
8
+ <p style="
9
+ margin: 0;
10
+ margin-top: 17px;
11
+ font-weight: 500;
12
+ letter-spacing: 0.56px;
13
+ ">Masukkan kode dibawah ini untuk verifikasi akun kamu, jika kode tidak berhasil coba meminta untuk kirim email kembali. kode hanya bisa digunakan selama 1 jam setelah email terkirim.</p>
14
+
15
+ <p style="
16
+ margin: 0;
17
+ margin-top: 40px;
18
+ font-size: 40px;
19
+ font-weight: 600;
20
+ letter-spacing: 15px;
21
+ color: #5aafff;
22
+ ">{{token}}</p>
23
+
24
+ <p style="
25
+ margin: 0;
26
+ margin-top: 70px;
27
+ font-weight: 500;
28
+ letter-spacing: 0.56px;
29
29
  ">Demi keamanan, jangan beritahukan kode verifikasi ini kepada siapapun! Abaikan jika kamu tidak merasa melakukan pendaftaran akun</p>
@@ -1,17 +1,17 @@
1
- import { renderMailTemplate, sendMail } from "@utils"
2
-
3
- export async function UserMailToken(user: Record<string, any>, token: string) {
4
- const content = renderMailTemplate("user-mail-token", {
5
- title: "Verifikasi E-mail",
6
- ...user,
7
- token,
8
- })
9
-
10
- const send = await sendMail({
11
- subject: "Email Verification",
12
- to: user?.email,
13
- content: content
14
- })
15
-
16
- return send;
17
- }
1
+ import { renderMailTemplate, sendMail } from "@utils"
2
+
3
+ export async function UserMailToken(user: Record<string, any>, token: string) {
4
+ const content = renderMailTemplate("user-mail-token", {
5
+ title: "Verifikasi E-mail",
6
+ ...user,
7
+ token,
8
+ })
9
+
10
+ const send = await sendMail({
11
+ subject: "Email Verification",
12
+ to: user?.email,
13
+ content: content
14
+ })
15
+
16
+ return send;
17
+ }
@@ -0,0 +1,10 @@
1
+ import { notification } from "@utils";
2
+
3
+ export async function ExampleNotification(userIds: number[], payload: Record<string, any>) {
4
+ await notification.send({
5
+ title: payload.title || "New Notification",
6
+ body: payload.body || "You have received a new notification.",
7
+ type: payload.type || "info",
8
+ userIds,
9
+ });
10
+ }
@@ -0,0 +1 @@
1
+ export * from "./example.notification";
@@ -1,26 +1,26 @@
1
- import {
2
- Elysia } from 'elysia'
3
- import { api, middleware } from '@utils'
4
- import {
5
- AuthController,
6
- BaseController,
7
- UserController,
8
- } from '@controllers'
9
-
10
- export const routes = (app: any) => app.group('/api', (route: any) => {
11
- route.get('/', BaseController.index)
12
- route.get('/features', BaseController.feature)
13
- route.get('/accesses', BaseController.access)
14
-
15
- route.post('/login', AuthController.login)
16
- route.post('/register', AuthController.register)
17
-
18
- route.use(middleware.Private)
19
-
20
- route.post('/verify', AuthController.verify)
21
- route.get('/me', AuthController.me)
22
- route.post('/me/update', AuthController.update)
23
-
24
- api(route, "/users", UserController);
25
- return route;
1
+ import {
2
+ Elysia } from 'elysia'
3
+ import { api, middleware } from '@utils'
4
+ import {
5
+ AuthController,
6
+ BaseController,
7
+ UserController,
8
+ } from '@controllers'
9
+
10
+ export const routes = (app: any) => app.group('/api', (route: any) => {
11
+ route.get('/', BaseController.index)
12
+ route.get('/features', BaseController.feature)
13
+ route.get('/accesses', BaseController.access)
14
+
15
+ route.post('/login', AuthController.login)
16
+ route.post('/register', AuthController.register)
17
+
18
+ route.use(middleware.Private)
19
+
20
+ route.post('/verify', AuthController.verify)
21
+ route.get('/me', AuthController.me)
22
+ route.post('/me/update', AuthController.update)
23
+
24
+ api(route, "/users", UserController);
25
+ return route;
26
26
  })
package/barrels.json CHANGED
@@ -1,10 +1,10 @@
1
- {
2
- "directory" : [
3
- "app/models",
4
- "app/controllers",
5
- "app/routes"
6
- ],
7
- "delete" : true,
8
- "exclude" : ["index.ts"],
9
- "structure" : "flat"
1
+ {
2
+ "directory" : [
3
+ "app/models",
4
+ "app/controllers",
5
+ "app/routes"
6
+ ],
7
+ "delete" : true,
8
+ "exclude" : ["index.ts"],
9
+ "structure" : "flat"
10
10
  }
@@ -1,27 +1,27 @@
1
- import { DAMigration } from "@utils"
2
-
3
- export default class CreateActivityLogsTable extends DAMigration {
4
- async up() {
5
- await this.createTable(
6
- "activity_logs",
7
- (table) => {
8
- table.uuid()
9
- table.string("feature")
10
- table.string("action")
11
- table.uint64("user_id")
12
- table.json("changes")
13
- table.dateTime("at")
14
- },
15
- {
16
- engine: "MergeTree",
17
- orderBy: ["feature", "action", "at"],
18
- partitionBy: "toYYYYMM(at)",
19
- ttl: "at + INTERVAL 90 DAY DELETE"
20
- }
21
- )
22
- }
23
-
24
- async down() {
25
- await this.dropTable("activity_logs")
26
- }
27
- }
1
+ import { DAMigration } from "@utils"
2
+
3
+ export default class CreateActivityLogsTable extends DAMigration {
4
+ async up() {
5
+ await this.createTable(
6
+ "activity_logs",
7
+ (table) => {
8
+ table.uuid()
9
+ table.string("feature")
10
+ table.string("action")
11
+ table.uint64("user_id")
12
+ table.json("changes")
13
+ table.dateTime("at")
14
+ },
15
+ {
16
+ engine: "MergeTree",
17
+ orderBy: ["feature", "action", "at"],
18
+ partitionBy: "toYYYYMM(at)",
19
+ ttl: "at + INTERVAL 90 DAY DELETE"
20
+ }
21
+ )
22
+ }
23
+
24
+ async down() {
25
+ await this.dropTable("activity_logs")
26
+ }
27
+ }