@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
@@ -0,0 +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
+
38
+ logger.start(`Queue job workers is running!`)
@@ -0,0 +1,3 @@
1
+ import { socket } from "@utils";
2
+
3
+ socket.start(Number(process.env.SOCKET_PORT ?? 4500));
@@ -0,0 +1,10 @@
1
+ import { Field, Model } from '@utils'
2
+
3
+ export class Role extends Model {
4
+ static getTable() {
5
+ return "user_roles";
6
+ }
7
+
8
+ @Field(["fillable", "selectable", "searchable"])
9
+ name!: string
10
+ }
@@ -0,0 +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
18
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @file Automatically generated by barrelsby.
3
+ */
4
+
5
+ export * from "./iam/role.model";
6
+ export * from "./iam/user.model";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @file Automatically generated by barrelsby.
3
+ */
4
+
5
+ export * from "./user-mail-token.mail";
@@ -0,0 +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>
@@ -0,0 +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;
29
+ ">Demi keamanan, jangan beritahukan kode verifikasi ini kepada siapapun! Abaikan jika kamu tidak merasa melakukan pendaftaran akun</p>
@@ -0,0 +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
+ }
@@ -0,0 +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;
26
+ })
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @file Automatically generated by barrelsby.
3
+ */
4
+
5
+ export * from "./base.routes";
package/barrels.json ADDED
@@ -0,0 +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"
10
+ }
@@ -0,0 +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
+ }
@@ -0,0 +1,41 @@
1
+ import { DAMigration } from "@utils"
2
+
3
+ export default class CreateAccessLogsTable extends DAMigration {
4
+ async up() {
5
+ await this.createTable("access_logs",(table) => {
6
+ table.uuid()
7
+ table.string("method")
8
+ table.string("path")
9
+ table.int32("status")
10
+ table.int32("latency")
11
+ table.string("ip")
12
+ table.string("agent")
13
+ table.dateTime("at")
14
+ }, {
15
+ engine: "MergeTree",
16
+ orderBy: ["at"],
17
+ partitionBy: "toYYYYMM(at)",
18
+ ttl: "at + INTERVAL 30 DAY DELETE"
19
+ })
20
+
21
+ await this.createTable("error_logs",(table) => {
22
+ table.uuid()
23
+ table.string("service")
24
+ table.string("key")
25
+ table.string("feature")
26
+ table.string("error")
27
+ table.string("reference")
28
+ table.dateTime("at")
29
+ }, {
30
+ engine: "MergeTree",
31
+ orderBy: ["at"],
32
+ partitionBy: "toYYYYMM(at)",
33
+ ttl: "at + INTERVAL 30 DAY DELETE"
34
+ })
35
+ }
36
+
37
+ async down() {
38
+ await this.dropTable("access_logs")
39
+ await this.dropTable("error_logs")
40
+ }
41
+ }
@@ -0,0 +1,22 @@
1
+ import type { Knex } from 'knex'
2
+
3
+ export async function up(knex: Knex): Promise<void> {
4
+ await knex.schema.createTable('storages', (table) => {
5
+ table.bigIncrements('id').primary()
6
+ table.bigInteger('user_id').nullable().index()
7
+ table.string('disk').notNullable().index()
8
+ table.string('path').notNullable().index()
9
+ table.string('filename').nullable()
10
+ table.string('filetype').nullable()
11
+ table.string('filesize').nullable()
12
+ table.timestamps(true, true)
13
+ })
14
+
15
+ await knex.schema.createTable('storage_permissions', (table) => {
16
+ table.bigIncrements('id').primary()
17
+ table.bigInteger('storage_id').unsigned().notNullable().index()
18
+ table.bigInteger('user_id').nullable().index()
19
+ table.bigInteger('role_id').nullable().index()
20
+ table.timestamps(true, true)
21
+ })
22
+ }
@@ -0,0 +1,56 @@
1
+ import type { Knex } from "knex"
2
+
3
+ export async function up(knex: Knex): Promise<void> {
4
+ await knex.schema.createTable("users", (table) => {
5
+ table.bigIncrements("id").primary()
6
+ table.string("name").notNullable()
7
+ table.string("email").unique().notNullable()
8
+ table.string("password")
9
+ table.string("image")
10
+ table.timestamp("email_verification_at")
11
+ table.timestamps(true, true)
12
+ table.softDelete()
13
+ })
14
+
15
+ await knex.schema.createTable("user_roles", (table) => {
16
+ table.bigIncrements("id").primary()
17
+ table.string("name").notNullable()
18
+ })
19
+
20
+ await knex.schema.createTable("user_has_user_roles", (table) => {
21
+ table.bigIncrements("id").primary()
22
+ table.foreignIdFor("users").notNullable()
23
+ table.foreignIdFor("user_roles").notNullable()
24
+ table.softDelete()
25
+ })
26
+
27
+ await knex.schema.createTable("user_access_tokens", (table) => {
28
+ table.bigIncrements("id").primary()
29
+ table.foreignIdFor("users").notNullable()
30
+ table.string("agent").index().notNullable()
31
+ table.string("token").unique().index().notNullable()
32
+ table.json("permissions").defaultTo(knex.raw(`'[]'::json`))
33
+ table.string("last_used_ip").nullable()
34
+ table.timestamp("last_used_at")
35
+ table.timestamp("expired_at")
36
+ table.timestamps(true, true)
37
+ table.softDelete()
38
+ })
39
+
40
+ await knex.schema.createTable("user_mail_tokens", (table) => {
41
+ table.bigIncrements("id").primary()
42
+ table.foreignIdFor("users").notNullable()
43
+ table.string("token").unique().notNullable()
44
+ table.timestamp("used_at")
45
+ table.timestamps(true, true)
46
+ table.softDelete()
47
+ })
48
+
49
+ await knex.schema.createTable("user_permissions", (table) => {
50
+ table.bigIncrements("id").primary()
51
+ table.foreignIdFor("users").notNullable()
52
+ table.foreignIdFor("user_roles").notNullable()
53
+ table.json("permissions").defaultTo(knex.raw(`'[]'::json`))
54
+ table.softDelete()
55
+ })
56
+ }
@@ -0,0 +1,24 @@
1
+ import { Role, User } from "@models";
2
+ import { db } from "@utils";
3
+
4
+ export default async function UserSeeder() {
5
+ // =========================>
6
+ // ## Seed the application's database
7
+ // =========================>
8
+ await (new Role).pump([
9
+ {"name": "Admin"},
10
+ {"name": "Petugas"}
11
+ ]);
12
+
13
+ await (new User).pump([
14
+ {"name": "Admin", "email": "admin@skalfa.id", "password": "$2b$10$tPX5QhnM.vUEDmDpht6O4OarVyTh43NTxhkzFrNxfRijJ3uhSHcli"},
15
+ {"name": "Petugas", "email": "petugas@skalfa.id", "password": "$2b$10$tPX5QhnM.vUEDmDpht6O4OarVyTh43NTxhkzFrNxfRijJ3uhSHcli"}
16
+ ]);
17
+
18
+ if (db) {
19
+ await db("user_has_user_roles").insert([
20
+ { "user_id": 1, "user_role_id": 1 },
21
+ { "user_id": 2, "user_role_id": 2 }
22
+ ]);
23
+ }
24
+ }
@@ -0,0 +1,36 @@
1
+ import { dirname } from "path";
2
+ import { fileURLToPath } from "url";
3
+ import { FlatCompat } from "@eslint/eslintrc";
4
+ import tsParser from "@typescript-eslint/parser"
5
+ import tsPlugin from "@typescript-eslint/eslint-plugin"
6
+ import prettier from "eslint-config-prettier";
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = dirname(__filename);
10
+
11
+ const compat = new FlatCompat({
12
+ baseDirectory: __dirname,
13
+ });
14
+
15
+ const eslintConfig = [
16
+ ...compat.extends("prettier"),
17
+ {
18
+ files: ['**/*.{js,ts}'],
19
+ languageOptions: {
20
+ parser: tsParser,
21
+ },
22
+ plugins: {
23
+ '@typescript-eslint': tsPlugin,
24
+ },
25
+ rules: {
26
+ "@typescript-eslint/no-explicit-any" : 0,
27
+ "no-console" : 1,
28
+ "no-nested-ternary" : 0,
29
+ "@typescript-eslint/no-unused-expressions" : 0,
30
+ "@typescript-eslint/no-array-constructor" : 0,
31
+ },
32
+ },
33
+ prettier,
34
+ ];
35
+
36
+ export default eslintConfig;
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@skalfa/skalfa-api",
3
+ "version": "1.0.0",
4
+ "scripts": {
5
+ "dev" : "concurrently --raw \"bun run --watch app/app.ts\" \"bun skalfa watch:barrels\"",
6
+ "watch" : "bun run --watch app/app.ts",
7
+ "start" : "bun run app/app.ts",
8
+ "test" : "bun tsc --noEmit --ignoreDeprecations 6.0",
9
+ "lint" : "bunx eslint app/* utils/* database/*",
10
+ "barrels" : "bun run utils/commands/skalfa.ts barrels",
11
+ "skalfa" : "bun run utils/commands/skalfa.ts"
12
+ },
13
+ "dependencies": {
14
+ "@skalfa/skalfa-api-core": "^1.0.2",
15
+ "@skalfa/skalfa-orm" : "^1.0.0",
16
+ "bcrypt" : "^6.0.0",
17
+ "dotenv" : "^17.2.2",
18
+ "elysia" : "latest",
19
+ "knex" : "^3.1.0",
20
+ "pg" : "^8.16.3",
21
+ "nodemailer" : "^7.0.9",
22
+ "tsconfig-paths" : "^4.2.0",
23
+ "validator" : "^13.15.15",
24
+ "commander" : "^12.1.0"
25
+ },
26
+ "devDependencies": {
27
+ "eslint" : "^10.0.0",
28
+ "eslint-config-prettier" : "^10.1.8",
29
+ "@eslint/eslintrc" : "eslint/eslintrc",
30
+ "@typescript-eslint/eslint-plugin" : "^8.55.0",
31
+ "@typescript-eslint/parser" : "^8.55.0",
32
+ "@types/bcrypt" : "^6.0.0",
33
+ "@types/nodemailer" : "^7.0.2",
34
+ "@types/validator" : "^13.15.3",
35
+ "bun-types" : "latest",
36
+ "barrelsby" : "^2.8.1",
37
+ "concurrently" : "^9.2.1"
38
+ }
39
+ }