@skalfa/skalfa-api 1.0.1 → 1.0.3

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 -74
  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,144 +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
- }
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
144
  }
@@ -1,93 +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
- }
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
93
  }
@@ -1,9 +1,9 @@
1
- import { cron } from "@utils";
2
-
3
-
4
-
5
- // ============================================>
6
- // ## List of cron jobs.
7
- // ============================================>
8
- // eslint-disable-next-line no-console
1
+ import { cron } from "@utils";
2
+
3
+
4
+
5
+ // ============================================>
6
+ // ## List of cron jobs.
7
+ // ============================================>
8
+ // eslint-disable-next-line no-console
9
9
  cron.add("1 * * * *", () => console.log('example cron job...'), 'example');
@@ -1,9 +1,9 @@
1
- import { cron, logger } from "@utils";
2
- import "./index"
3
-
4
-
5
- // ============================================>
6
- // ## Run of cron job worker.
7
- // ============================================>
8
- cron.worker();
1
+ import { cron, logger } from "@utils";
2
+ import "./index"
3
+
4
+
5
+ // ============================================>
6
+ // ## Run of cron job worker.
7
+ // ============================================>
8
+ cron.worker();
9
9
  logger.start(`Cron job workers is running!`)
@@ -1,32 +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
- }
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
+ }
@@ -1,32 +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
- }
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
+ }
@@ -1,10 +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
- )
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
10
  }