@skalfa/skalfa-api-core 1.0.3 → 1.0.8

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 (284) hide show
  1. package/CONTRIBUTING.md +45 -0
  2. package/LICENSE +21 -0
  3. package/README.md +60 -0
  4. package/dist/auth/auth.d.ts +18 -15
  5. package/dist/auth/auth.js +20 -203
  6. package/dist/auth/auth.js.map +1 -1
  7. package/dist/auth/create-access-token.d.ts +4 -0
  8. package/dist/auth/create-access-token.js +26 -0
  9. package/dist/auth/create-access-token.js.map +1 -0
  10. package/dist/auth/create-user-mail-token.d.ts +4 -0
  11. package/dist/auth/create-user-mail-token.js +19 -0
  12. package/dist/auth/create-user-mail-token.js.map +1 -0
  13. package/dist/auth/helpers/generate-agent-id.d.ts +1 -0
  14. package/dist/auth/helpers/generate-agent-id.js +7 -0
  15. package/dist/auth/helpers/generate-agent-id.js.map +1 -0
  16. package/dist/auth/helpers/get-request-ip.d.ts +1 -0
  17. package/dist/auth/helpers/get-request-ip.js +4 -0
  18. package/dist/auth/helpers/get-request-ip.js.map +1 -0
  19. package/dist/auth/helpers/get-user-permissions.d.ts +1 -0
  20. package/dist/auth/helpers/get-user-permissions.js +9 -0
  21. package/dist/auth/helpers/get-user-permissions.js.map +1 -0
  22. package/dist/auth/helpers/index.d.ts +3 -0
  23. package/dist/auth/helpers/index.js +4 -0
  24. package/dist/auth/helpers/index.js.map +1 -0
  25. package/dist/auth/list-user-sessions.d.ts +1 -0
  26. package/dist/auth/list-user-sessions.js +10 -0
  27. package/dist/auth/list-user-sessions.js.map +1 -0
  28. package/dist/auth/revalidate-user-permissions-by-role.d.ts +1 -0
  29. package/dist/auth/revalidate-user-permissions-by-role.js +12 -0
  30. package/dist/auth/revalidate-user-permissions-by-role.js.map +1 -0
  31. package/dist/auth/revalidate-user-permissions.d.ts +1 -0
  32. package/dist/auth/revalidate-user-permissions.js +21 -0
  33. package/dist/auth/revalidate-user-permissions.js.map +1 -0
  34. package/dist/auth/revoke-access-token.d.ts +1 -0
  35. package/dist/auth/revoke-access-token.js +5 -0
  36. package/dist/auth/revoke-access-token.js.map +1 -0
  37. package/dist/auth/verify-access-token.d.ts +1 -0
  38. package/dist/auth/verify-access-token.js +47 -0
  39. package/dist/auth/verify-access-token.js.map +1 -0
  40. package/dist/auth/verify-user-mail-token.d.ts +1 -0
  41. package/dist/auth/verify-user-mail-token.js +21 -0
  42. package/dist/auth/verify-user-mail-token.js.map +1 -0
  43. package/dist/commands/cli.js +18 -27
  44. package/dist/commands/cli.js.map +1 -1
  45. package/dist/commands/make/basic-controller.js +1 -1
  46. package/dist/commands/make/basic-controller.js.map +1 -1
  47. package/dist/commands/make/basic-migration.d.ts +1 -1
  48. package/dist/commands/make/basic-migration.js +2 -2
  49. package/dist/commands/make/basic-migration.js.map +1 -1
  50. package/dist/commands/make/basic-model.js +1 -1
  51. package/dist/commands/make/basic-model.js.map +1 -1
  52. package/dist/commands/make/basic-seeder.js +1 -1
  53. package/dist/commands/make/basic-seeder.js.map +1 -1
  54. package/dist/commands/make/blueprint.js +1 -1
  55. package/dist/commands/make/blueprint.js.map +1 -1
  56. package/dist/commands/make/da-migration.js +3 -3
  57. package/dist/commands/make/da-migration.js.map +1 -1
  58. package/dist/commands/make/mail.js +2 -2
  59. package/dist/commands/make/mail.js.map +1 -1
  60. package/dist/commands/make/notification.js +1 -1
  61. package/dist/commands/make/notification.js.map +1 -1
  62. package/dist/commands/make/queue.js +1 -1
  63. package/dist/commands/make/queue.js.map +1 -1
  64. package/dist/commands/make/resource.d.ts +2 -0
  65. package/dist/commands/make/resource.js +19 -0
  66. package/dist/commands/make/resource.js.map +1 -0
  67. package/dist/commands/make/skalfa-controller.d.ts +3 -0
  68. package/dist/commands/make/{light-controller.js → skalfa-controller.js} +9 -9
  69. package/dist/commands/make/skalfa-controller.js.map +1 -0
  70. package/dist/commands/make/skalfa-model.d.ts +3 -0
  71. package/dist/commands/make/{light-model.js → skalfa-model.js} +11 -11
  72. package/dist/commands/make/skalfa-model.js.map +1 -0
  73. package/dist/commands/runner/barrels.js.map +1 -1
  74. package/dist/commands/runner/blueprint/controller-generation.js +2 -2
  75. package/dist/commands/runner/blueprint/controller-generation.js.map +1 -1
  76. package/dist/commands/runner/blueprint/documentation-generation.js.map +1 -1
  77. package/dist/commands/runner/blueprint/migration-generation.js +3 -3
  78. package/dist/commands/runner/blueprint/migration-generation.js.map +1 -1
  79. package/dist/commands/runner/blueprint/model-generation.js +2 -2
  80. package/dist/commands/runner/blueprint/model-generation.js.map +1 -1
  81. package/dist/commands/runner/blueprint/runner.js +7 -8
  82. package/dist/commands/runner/blueprint/runner.js.map +1 -1
  83. package/dist/commands/runner/blueprint/seeder-generation.js +3 -3
  84. package/dist/commands/runner/blueprint/seeder-generation.js.map +1 -1
  85. package/dist/commands/runner/da-migration.js +1 -2
  86. package/dist/commands/runner/da-migration.js.map +1 -1
  87. package/dist/commands/runner/generate-docs.d.ts +2 -0
  88. package/dist/commands/runner/generate-docs.js +400 -0
  89. package/dist/commands/runner/generate-docs.js.map +1 -0
  90. package/dist/commands/runner/migration.js +1 -1
  91. package/dist/commands/runner/migration.js.map +1 -1
  92. package/dist/commands/runner/seeder.js +1 -1
  93. package/dist/commands/runner/seeder.js.map +1 -1
  94. package/dist/commands/stubs/index.d.ts +4 -4
  95. package/dist/commands/stubs/index.js +4 -4
  96. package/dist/commands/stubs/index.js.map +1 -1
  97. package/dist/context/context.js +6 -0
  98. package/dist/context/context.js.map +1 -1
  99. package/dist/controller/controller.d.ts +17 -30
  100. package/dist/controller/controller.js +39 -121
  101. package/dist/controller/controller.js.map +1 -1
  102. package/dist/controller/response.d.ts +6 -0
  103. package/dist/controller/response.js +63 -0
  104. package/dist/controller/response.js.map +1 -0
  105. package/dist/controller/storage.d.ts +9 -0
  106. package/dist/controller/storage.js +56 -0
  107. package/dist/controller/storage.js.map +1 -0
  108. package/dist/conversion/conversion.d.ts +3 -0
  109. package/dist/conversion/conversion.js +28 -4
  110. package/dist/conversion/conversion.js.map +1 -1
  111. package/dist/conversion/date.d.ts +1 -0
  112. package/dist/conversion/date.js +77 -0
  113. package/dist/conversion/date.js.map +1 -0
  114. package/dist/index.d.ts +2 -0
  115. package/dist/index.js +2 -0
  116. package/dist/index.js.map +1 -1
  117. package/dist/logger/logger.js +33 -0
  118. package/dist/logger/logger.js.map +1 -1
  119. package/dist/mail/mail.js +6 -6
  120. package/dist/mail/mail.js.map +1 -1
  121. package/dist/middleware/access-log.d.ts +31 -0
  122. package/dist/middleware/access-log.js +13 -0
  123. package/dist/middleware/access-log.js.map +1 -0
  124. package/dist/middleware/auth.d.ts +37 -0
  125. package/dist/middleware/auth.js +16 -0
  126. package/dist/middleware/auth.js.map +1 -0
  127. package/dist/middleware/body-parse.d.ts +35 -0
  128. package/dist/middleware/body-parse.js +87 -0
  129. package/dist/middleware/body-parse.js.map +1 -0
  130. package/dist/middleware/context.d.ts +29 -0
  131. package/dist/middleware/context.js +8 -0
  132. package/dist/middleware/context.js.map +1 -0
  133. package/dist/middleware/cors.d.ts +31 -0
  134. package/dist/middleware/cors.js +27 -0
  135. package/dist/middleware/cors.js.map +1 -0
  136. package/dist/middleware/error-handler.d.ts +33 -0
  137. package/dist/middleware/error-handler.js +17 -0
  138. package/dist/middleware/error-handler.js.map +1 -0
  139. package/dist/middleware/middleware.d.ts +31 -10
  140. package/dist/middleware/middleware.js +41 -209
  141. package/dist/middleware/middleware.js.map +1 -1
  142. package/dist/middleware/private.d.ts +29 -0
  143. package/dist/middleware/private.js +8 -0
  144. package/dist/middleware/private.js.map +1 -0
  145. package/dist/middleware/rate-limiter.d.ts +32 -0
  146. package/dist/middleware/rate-limiter.js +30 -0
  147. package/dist/middleware/rate-limiter.js.map +1 -0
  148. package/dist/notification/index.d.ts +1 -0
  149. package/dist/notification/index.js +2 -0
  150. package/dist/notification/index.js.map +1 -0
  151. package/dist/notification/notification.d.ts +16 -0
  152. package/dist/notification/notification.js +64 -0
  153. package/dist/notification/notification.js.map +1 -0
  154. package/dist/permission/permission.js +9 -0
  155. package/dist/permission/permission.js.map +1 -1
  156. package/dist/registry/registry.d.ts +0 -6
  157. package/dist/registry/registry.js +6 -6
  158. package/dist/registry/registry.js.map +1 -1
  159. package/dist/storage/storage.d.ts +3 -3
  160. package/dist/storage/storage.js.map +1 -1
  161. package/dist/validation/validation.js +43 -51
  162. package/dist/validation/validation.js.map +1 -1
  163. package/package.json +4 -4
  164. package/src/auth/auth.ts +21 -252
  165. package/src/auth/create-access-token.ts +29 -0
  166. package/src/auth/create-user-mail-token.ts +24 -0
  167. package/src/auth/helpers/generate-agent-id.ts +8 -0
  168. package/src/auth/helpers/get-request-ip.ts +3 -0
  169. package/src/auth/helpers/get-user-permissions.ts +15 -0
  170. package/src/auth/helpers/index.ts +3 -0
  171. package/src/auth/list-user-sessions.ts +11 -0
  172. package/src/auth/revalidate-user-permissions-by-role.ts +13 -0
  173. package/src/auth/revalidate-user-permissions.ts +26 -0
  174. package/src/auth/revoke-access-token.ts +5 -0
  175. package/src/auth/verify-access-token.ts +56 -0
  176. package/src/auth/verify-user-mail-token.ts +24 -0
  177. package/src/commands/cli.ts +19 -29
  178. package/src/commands/make/basic-controller.ts +4 -2
  179. package/src/commands/make/basic-migration.ts +5 -3
  180. package/src/commands/make/basic-model.ts +3 -1
  181. package/src/commands/make/basic-seeder.ts +3 -1
  182. package/src/commands/make/blueprint.ts +3 -1
  183. package/src/commands/make/da-migration.ts +6 -5
  184. package/src/commands/make/mail.ts +4 -2
  185. package/src/commands/make/notification.ts +3 -1
  186. package/src/commands/make/queue.ts +3 -1
  187. package/src/commands/make/resource.ts +21 -0
  188. package/src/commands/make/{light-controller.ts → skalfa-controller.ts} +10 -8
  189. package/src/commands/make/{light-model.ts → skalfa-model.ts} +12 -10
  190. package/src/commands/runner/barrels.ts +4 -0
  191. package/src/commands/runner/blueprint/controller-generation.ts +4 -2
  192. package/src/commands/runner/blueprint/documentation-generation.ts +2 -0
  193. package/src/commands/runner/blueprint/migration-generation.ts +5 -3
  194. package/src/commands/runner/blueprint/model-generation.ts +4 -2
  195. package/src/commands/runner/blueprint/runner.ts +15 -8
  196. package/src/commands/runner/blueprint/seeder-generation.ts +5 -3
  197. package/src/commands/runner/da-migration.ts +3 -2
  198. package/src/commands/runner/generate-docs.ts +495 -0
  199. package/src/commands/runner/migration.ts +1 -1
  200. package/src/commands/runner/seeder.ts +1 -1
  201. package/src/commands/stubs/index.ts +4 -4
  202. package/src/context/context.ts +23 -17
  203. package/src/controller/controller.ts +124 -239
  204. package/src/controller/response.ts +78 -0
  205. package/src/controller/storage.ts +78 -0
  206. package/src/conversion/conversion.ts +90 -64
  207. package/src/conversion/date.ts +74 -0
  208. package/src/index.ts +2 -0
  209. package/src/logger/logger.ts +217 -176
  210. package/src/mail/mail.ts +85 -85
  211. package/src/middleware/access-log.ts +15 -0
  212. package/src/middleware/auth.ts +19 -0
  213. package/src/middleware/body-parse.ts +83 -0
  214. package/src/middleware/context.ts +11 -0
  215. package/src/middleware/cors.ts +31 -0
  216. package/src/middleware/error-handler.ts +20 -0
  217. package/src/middleware/middleware.ts +91 -288
  218. package/src/middleware/private.ts +8 -0
  219. package/src/middleware/rate-limiter.ts +41 -0
  220. package/src/notification/index.ts +1 -0
  221. package/src/notification/notification.ts +86 -0
  222. package/src/permission/permission.ts +140 -136
  223. package/src/registry/registry.ts +17 -15
  224. package/src/route/route.ts +11 -11
  225. package/src/storage/storage.ts +104 -106
  226. package/src/validation/validation.ts +322 -346
  227. package/dist/auth.util.d.ts +0 -19
  228. package/dist/auth.util.js +0 -183
  229. package/dist/auth.util.js.map +0 -1
  230. package/dist/commands/make/light-controller.d.ts +0 -3
  231. package/dist/commands/make/light-controller.js.map +0 -1
  232. package/dist/commands/make/light-model.d.ts +0 -3
  233. package/dist/commands/make/light-model.js.map +0 -1
  234. package/dist/context.util.d.ts +0 -7
  235. package/dist/context.util.js +0 -11
  236. package/dist/context.util.js.map +0 -1
  237. package/dist/controller.util.d.ts +0 -118
  238. package/dist/controller.util.js +0 -144
  239. package/dist/controller.util.js.map +0 -1
  240. package/dist/conversion.util.d.ts +0 -8
  241. package/dist/conversion.util.js +0 -52
  242. package/dist/conversion.util.js.map +0 -1
  243. package/dist/db/db.d.ts +0 -84
  244. package/dist/db/db.js +0 -177
  245. package/dist/db/db.js.map +0 -1
  246. package/dist/db/index.d.ts +0 -1
  247. package/dist/db/index.js +0 -2
  248. package/dist/db/index.js.map +0 -1
  249. package/dist/db.util.d.ts +0 -84
  250. package/dist/db.util.js +0 -177
  251. package/dist/db.util.js.map +0 -1
  252. package/dist/logger.util.d.ts +0 -30
  253. package/dist/logger.util.js +0 -126
  254. package/dist/logger.util.js.map +0 -1
  255. package/dist/mail.util.d.ts +0 -21
  256. package/dist/mail.util.js +0 -53
  257. package/dist/mail.util.js.map +0 -1
  258. package/dist/middleware.util.d.ts +0 -263
  259. package/dist/middleware.util.js +0 -233
  260. package/dist/middleware.util.js.map +0 -1
  261. package/dist/model/index.d.ts +0 -3
  262. package/dist/model/index.js +0 -4
  263. package/dist/model/index.js.map +0 -1
  264. package/dist/model/model.d.ts +0 -204
  265. package/dist/model/model.js +0 -1495
  266. package/dist/model/model.js.map +0 -1
  267. package/dist/model.util.d.ts +0 -204
  268. package/dist/model.util.js +0 -1495
  269. package/dist/model.util.js.map +0 -1
  270. package/dist/permission.util.d.ts +0 -38
  271. package/dist/permission.util.js +0 -91
  272. package/dist/permission.util.js.map +0 -1
  273. package/dist/registry.util.d.ts +0 -28
  274. package/dist/registry.util.js +0 -19
  275. package/dist/registry.util.js.map +0 -1
  276. package/dist/route.util.d.ts +0 -1
  277. package/dist/route.util.js +0 -12
  278. package/dist/route.util.js.map +0 -1
  279. package/dist/storage.util.d.ts +0 -56
  280. package/dist/storage.util.js +0 -82
  281. package/dist/storage.util.js.map +0 -1
  282. package/dist/validation.util.d.ts +0 -7
  283. package/dist/validation.util.js +0 -237
  284. package/dist/validation.util.js.map +0 -1
@@ -1,289 +1,92 @@
1
- import { Elysia, status } from 'elysia'
2
- import { auth, context, logger } from '@utils'
3
-
4
- declare module "elysia" {
5
- interface Elysia {
6
- api(
7
- basePath: string,
8
- controller: {
9
- index ?: any
10
- store ?: any
11
- show ?: any
12
- update ?: any
13
- destroy ?: any
14
- }
15
- ): this
16
- }
17
- }
18
-
19
-
20
- const errors = {
21
- unauthorized: {
22
- status: 401,
23
- message: "Unauthorized!"
24
- },
25
- ratelimited: {
26
- status: 429,
27
- message: "Too many requests!"
28
- },
29
- notfound: {
30
- status: 404,
31
- message: "Endpoint not found!"
32
- },
33
- request: {
34
- status: 400,
35
- message: "Bad Request!"
36
- },
37
- error: {
38
- status: 500,
39
- message: "Endpoint not found!"
40
- }
41
- }
42
-
43
-
44
- export const middleware = {
45
- // =============================>
46
- // ## Middleware: Auth hand;er
47
- // =============================>
48
- Auth: (app: Elysia) => app.derive(async ({ request }) => {
49
- const authHeader = request.headers.get('authorization')
50
-
51
- if (!authHeader || !authHeader.startsWith('Bearer ')) return { user: null, permissions: [], token: null }
52
-
53
- const bearer = authHeader.substring(7).trim()
54
- const result = await auth.verifyAccessToken(bearer, request)
55
-
56
- if (!result) return { user: null, permissions: [], token: null };
57
-
58
- return {
59
- user: result.user,
60
- permissions: result.permissions,
61
- token: result.token,
62
- }
63
- }),
64
-
65
-
66
- // =============================>
67
- // ## Middleware: Private handler
68
- // =============================>
69
- Private: (app: Elysia) => app.derive(async ({ user }: Record<string, any> | any) => {
70
- if (!user) {
71
- throw status(errors.unauthorized.status, { message: errors.unauthorized.message })
72
- }
73
- }),
74
-
75
-
76
- // =============================>
77
- // ## Middleware: Cors handler
78
- // =============================>
79
- Cors: (app: Elysia) => app.onRequest(({ request, set }) => {
80
- const origin = request.headers.get('origin') ?? ''
81
- let allowedOrigin: string = '*'
82
-
83
- const originsConf = process.env.APP_CORS_ORIGINS || '*'
84
-
85
- if (originsConf !== '*') {
86
- try {
87
- const allowedOrigins = JSON.parse(originsConf)
88
- if (Array.isArray(allowedOrigins) && allowedOrigins.includes(origin)) {
89
- allowedOrigin = origin || ""
90
- }
91
- } catch (e) {
92
- const em = 'Cors Error: Failed to parse APP_CORS_ORIGINS, fallback to "*"'
93
- logger.error(em, { error: em })
94
- allowedOrigin = ''
95
- }
96
- }
97
-
98
- set.headers['Access-Control-Allow-Origin'] = allowedOrigin
99
- set.headers['Access-Control-Allow-Methods'] = process.env.APP_CORS_METHODS || 'GET, POST, PUT, DELETE, OPTIONS'
100
- set.headers['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept, Authorization, X-Option, x-App'
101
- set.headers['Access-Control-Allow-Credentials'] = 'true'
102
-
103
- if (request.method === 'OPTIONS') {
104
- return new Response(null, { status: 204, })
105
- }
106
- }),
107
-
108
-
109
- // =============================>
110
- // ## Middleware: Rate limiter handler
111
- // =============================>
112
- RateLimiter: (app: Elysia, options?: { windowMs?: number, max?: number }) => app.onRequest(({ request, set, store }) => {
113
- const max = options?.max || ( process.env.APP_RATELIMIT_COUNTDOWN ? Number(process.env.APP_RATE_LIMIT) : 60 )
114
- const windowMs = options?.windowMs || ( process.env.APP_RATELIMIT_COUNTDOWN ? Number(process.env.APP_RATELIMIT_COUNTDOWN) : 60_000 )
115
-
116
- const user = (store as any)?.user
117
- const key = getClientKey(request, user?.id)
118
-
119
- const now = Date.now()
120
- let record = rateLimitStore.get(key)
121
-
122
- if (!record || record.expiresAt < now) {
123
- record = { count: 1, expiresAt: now + windowMs }
124
- rateLimitStore.set(key, record)
125
- } else {
126
- record.count++
127
- }
128
-
129
- set.headers['X-RateLimit-Limit'] = String(max)
130
- set.headers['X-RateLimit-Remaining'] = String(Math.max(0, max - record.count))
131
- set.headers['X-RateLimit-Reset'] = String(record.expiresAt)
132
-
133
- if (record.count > max) throw status(errors.ratelimited.status, { message: errors.ratelimited.message });
134
- }),
135
-
136
-
137
- // =============================>
138
- // ## Middleware: Body parse handler
139
- // =============================>
140
- BodyParse: (app: Elysia) => app.state<{ rawBody?: any }>({}).onRequest(async ({ request, store }) => {
141
- const text = await request.clone().text();
142
-
143
- const contentType = request.headers.get("content-type") || "";
144
- let rawBody: any = {};
145
-
146
- try {
147
- if (contentType.includes("application/json")) {
148
- rawBody = text ? JSON.parse(text) : {};
149
- } else if (contentType.includes("application/x-www-form-urlencoded")) {
150
- const params = new URLSearchParams(text);
151
- for (const [key, value] of params.entries()) bodyParseNestedSet(rawBody, key, value);
152
- } else if (contentType.includes("multipart/form-data")) {
153
- const formData = await request.clone().formData();
154
- for (const [key, value] of formData.entries()) bodyParseNestedSet(rawBody, key, value);
155
- } else {
156
- rawBody = {};
157
- }
158
- } catch (e) {
159
- const em = e instanceof Error ? e.message : String(e)
160
- logger.error(`Body parse error: ${em}`, { error: em })
161
- rawBody = {};
162
- throw status(errors.request.status, { message: errors.request.message })
163
- }
164
-
165
- store.rawBody = rawBody;
166
- }).derive(({ store }) => {
167
- const payload = bodyParseKeyFormat(store.rawBody || {});
168
- return { payload };
169
- }),
170
-
171
-
172
- AccessLog: (app: Elysia) => app.state<{ startedAt?: number }>({}).onRequest(({ store }) => { store.startedAt = Date.now() }).onAfterResponse(({ request, set, store }) => {
173
- const method = request.method
174
- const url = new URL(request.url)
175
- const path = url.pathname
176
- const status = Number(set.status) ?? 200
177
- const latency = Date.now() - (store.startedAt ?? Date.now())
178
- const agent = request.headers.get("user-agent") || 'unknown'
179
- const ip = request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() || request.headers.get('cf-connecting-ip') || 'unknown'
180
-
181
- logger.info(`${method} : ${path} - ${status} - ${latency}ms - ${ip}]`)
182
- logger.access({ method, path, status, latency, ip, agent })
183
- }),
184
-
185
-
186
- // =============================>
187
- // ## Middleware: Error handler
188
- // =============================>
189
- ErrorHandler: (app: Elysia) => app.onError(({ code, set, error, request }) => {
190
- if (code === 'NOT_FOUND') {
191
- set.status = errors.notfound.status
192
- return { message: errors.notfound.message }
193
- }
194
-
195
- if (code === 'INTERNAL_SERVER_ERROR') {
196
- set.status = errors.error.status
197
- const em = error.message
198
- const url = new URL(request.url)
199
- const path = url.pathname
200
-
201
- logger.error(`error: ${em}`, { error: em, reference: path })
202
- return { message: em }
203
- }
204
- }),
205
-
206
- Context: (app: Elysia) => app.derive(async ({ store }) => {
207
- const userId = (store as any)?.user?.id
208
-
209
- return context.run({
210
- user_id: userId,
211
- },() => ({})
212
- )
213
- }),
214
- }
215
-
216
-
217
-
218
- // =============================>
219
- // ## Middleware: Body parse helpers
220
- // =============================>
221
- function bodyParseKeyFormat(input: any): any {
222
- if ( typeof input !== "object" || input === null || input instanceof File ) return input;
223
-
224
- if (Array.isArray(input)) return input.map(bodyParseKeyFormat)
225
-
226
- const result: any = {}
227
- for (const [key, value] of Object.entries(input)) {
228
- if (key.includes(".") || key.includes("[")) {
229
- bodyParseNestedSet(result, key, bodyParseKeyFormat(value))
230
- } else {
231
- result[key] = bodyParseKeyFormat(value)
232
- }
233
- }
234
- return result
235
- }
236
-
237
-
238
- function bodyParseNestedSet(obj: any, path: string, value: any) {
239
- const parts = bodyParsePathFormat(path);
240
- let current = obj;
241
-
242
- for (let i = 0; i < parts.length; i++) {
243
- const key = parts[i];
244
- const isLast = i === parts.length - 1;
245
-
246
- if (isLast) {
247
- current[key] = bodyParseValueFormat(value);
248
- } else {
249
- if (!(key in current)) {
250
- const nextKey = parts[i + 1];
251
- current[key] = isNaN(Number(nextKey)) ? {} : [];
252
- }
253
- current = current[key];
254
- }
255
- }
256
- }
257
-
258
- function bodyParsePathFormat(path: string): string[] {
259
- return path.replace(/\[(\w+)\]/g, ".$1").replace(/^\./, "").split(".");
260
- }
261
-
262
- function bodyParseValueFormat(value: any) {
263
- if (value == "" || value == null || value == "null") return null;
264
- if (typeof value !== "string") return value;
265
- if (value === "true") return true;
266
- if (value === "false") return false;
267
- if (!isNaN(Number(value))) return Number(value);
268
- return value;
269
- }
270
-
271
-
272
-
273
- // =============================>
274
- // ## Middleware: Rate Limiter Helpers
275
- // =============================>
276
- type RateLimitRecord = {
277
- count: number
278
- expiresAt: number
279
- }
280
-
281
- const rateLimitStore = new Map<string, RateLimitRecord>()
282
-
283
- function getClientKey(request: Request, userId?: string | number) {
284
- if (userId) return `user:${userId}`
285
-
286
- const ip = request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() || request.headers.get('cf-connecting-ip') || 'unknown'
287
-
288
- return `ip:${ip}`
1
+ import { Auth } from './auth'
2
+ import { Private } from './private'
3
+ import { Cors } from './cors'
4
+ import { RateLimiter } from './rate-limiter'
5
+ import { BodyParse } from './body-parse'
6
+ import { AccessLog } from './access-log'
7
+ import { ErrorHandler } from './error-handler'
8
+ import { ContextMiddleware } from './context'
9
+
10
+
11
+
12
+ declare module "elysia" {
13
+ interface Elysia {
14
+ api(
15
+ basePath: string,
16
+ controller: {
17
+ index ?: any
18
+ store ?: any
19
+ show ?: any
20
+ update ?: any
21
+ destroy ?: any
22
+ }
23
+ ): this
24
+ }
25
+ }
26
+
27
+ export const errors = {
28
+ unauthorized: {
29
+ status: 401,
30
+ message: "Unauthorized!"
31
+ },
32
+ ratelimited: {
33
+ status: 429,
34
+ message: "Too many requests!"
35
+ },
36
+ notfound: {
37
+ status: 404,
38
+ message: "Endpoint not found!"
39
+ },
40
+ request: {
41
+ status: 400,
42
+ message: "Bad Request!"
43
+ },
44
+ error: {
45
+ status: 500,
46
+ message: "Endpoint not found!"
47
+ }
48
+ } as const;
49
+
50
+
51
+
52
+ export const middleware = {
53
+ // =====================================>
54
+ // ## Middleware: authenticate request token
55
+ // =====================================>
56
+ Auth,
57
+
58
+ // =====================================>
59
+ // ## Middleware: restrict access to private routes
60
+ // =====================================>
61
+ Private,
62
+
63
+ // =====================================>
64
+ // ## Middleware: handle CORS headers
65
+ // =====================================>
66
+ Cors,
67
+
68
+ // =====================================>
69
+ // ## Middleware: rate limit request threshold
70
+ // =====================================>
71
+ RateLimiter,
72
+
73
+ // =====================================>
74
+ // ## Middleware: parse request body formats
75
+ // =====================================>
76
+ BodyParse,
77
+
78
+ // =====================================>
79
+ // ## Middleware: log request access details
80
+ // =====================================>
81
+ AccessLog,
82
+
83
+ // =====================================>
84
+ // ## Middleware: handle global route errors
85
+ // =====================================>
86
+ ErrorHandler,
87
+
88
+ // =====================================>
89
+ // ## Middleware: initialize async local context
90
+ // =====================================>
91
+ Context: ContextMiddleware,
289
92
  }
@@ -0,0 +1,8 @@
1
+ import { Elysia, status } from 'elysia'
2
+ import { errors } from './middleware'
3
+
4
+ export const Private = (app: Elysia) => app.derive(async ({ user }: Record<string, any> | any) => {
5
+ if (!user) {
6
+ throw status(errors.unauthorized.status, { message: errors.unauthorized.message })
7
+ }
8
+ })
@@ -0,0 +1,41 @@
1
+ import { Elysia, status } from 'elysia'
2
+ import { errors } from './middleware'
3
+
4
+ type RateLimitRecord = {
5
+ count: number
6
+ expiresAt: number
7
+ }
8
+
9
+ const rateLimitStore = new Map<string, RateLimitRecord>()
10
+
11
+ function getClientKey(request: Request, userId?: string | number) {
12
+ if (userId) return `user:${userId}`
13
+
14
+ const ip = request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() || request.headers.get('cf-connecting-ip') || 'unknown'
15
+
16
+ return `ip:${ip}`
17
+ }
18
+
19
+ export const RateLimiter = (app: Elysia, options?: { windowMs?: number, max?: number }) => app.onRequest(({ request, set, store }) => {
20
+ const max = options?.max || ( process.env.APP_RATELIMIT_COUNTDOWN ? Number(process.env.APP_RATE_LIMIT) : 60 )
21
+ const windowMs = options?.windowMs || ( process.env.APP_RATELIMIT_COUNTDOWN ? Number(process.env.APP_RATELIMIT_COUNTDOWN) : 60_000 )
22
+
23
+ const user = (store as any)?.user
24
+ const key = getClientKey(request, user?.id)
25
+
26
+ const now = Date.now()
27
+ let record = rateLimitStore.get(key)
28
+
29
+ if (!record || record.expiresAt < now) {
30
+ record = { count: 1, expiresAt: now + windowMs }
31
+ rateLimitStore.set(key, record)
32
+ } else {
33
+ record.count++
34
+ }
35
+
36
+ set.headers['X-RateLimit-Limit'] = String(max)
37
+ set.headers['X-RateLimit-Remaining'] = String(Math.max(0, max - record.count))
38
+ set.headers['X-RateLimit-Reset'] = String(record.expiresAt)
39
+
40
+ if (record.count > max) throw status(errors.ratelimited.status, { message: errors.ratelimited.message });
41
+ })
@@ -0,0 +1 @@
1
+ export * from "./notification";
@@ -0,0 +1,86 @@
1
+ import { db } from "@skalfa/skalfa-orm";
2
+ import { registry } from "../registry";
3
+ import { logger } from "../logger";
4
+
5
+ export type NotificationPayload = {
6
+ title : string;
7
+ body ?: string;
8
+ type ?: string;
9
+ redirect ?: string;
10
+ data ?: Record<string, any>;
11
+ userIds : number[];
12
+ }
13
+
14
+ export type NotificationCancelPayload = {
15
+ id : number;
16
+ userIds : number[];
17
+ }
18
+
19
+ export const notification = {
20
+ // =====================================>
21
+ // ## Notification: send notification
22
+ // =====================================>
23
+ send: async (payload: NotificationPayload) => {
24
+ const userIds = [...new Set(payload.userIds)];
25
+ if (userIds.length === 0) return;
26
+
27
+ try {
28
+ const notificationRecord = await db.transaction(async (trx) => {
29
+ const [n] = await trx('notifications')
30
+ .insert({
31
+ title : payload.title,
32
+ body : payload.body || "",
33
+ type : payload.type || "",
34
+ redirect : payload.redirect || "",
35
+ data : payload.data ?? {},
36
+ })
37
+ .returning('*');
38
+
39
+ await trx('notification_users').insert(
40
+ userIds.map((uid) => ({
41
+ notification_id : n.id,
42
+ user_id : uid
43
+ }))
44
+ );
45
+
46
+ return n;
47
+ });
48
+
49
+ emitNotificationSocket(userIds, notificationRecord);
50
+ } catch (err) {
51
+ const em = err instanceof Error ? err.message : String(err);
52
+ logger.error(`Failed to send notification: ${em}`, { error: em });
53
+ }
54
+ },
55
+
56
+ // =====================================>
57
+ // ## Notification: cancel notification
58
+ // =====================================>
59
+ cancel: async (payload: NotificationCancelPayload) => {
60
+ try {
61
+ await db('notification_users')
62
+ .where("notification_id", payload.id)
63
+ .whereIn("user_id", payload.userIds)
64
+ .update({ canceled_at: new Date() });
65
+
66
+ emitNotificationSocket(payload.userIds, { id: payload.id, type: "cancel" });
67
+ } catch (err) {
68
+ const em = err instanceof Error ? err.message : String(err);
69
+ logger.error(`Failed to cancel notification: ${em}`, { error: em });
70
+ }
71
+ }
72
+ };
73
+
74
+
75
+
76
+ function emitNotificationSocket(userIds: number[], notificationRecord: Record<string, any>) {
77
+ const socket = registry.get("socket");
78
+ if (socket) {
79
+ for (const uid of userIds) {
80
+ socket.room(`user:${uid}`).emit('notification:new', {
81
+ id: notificationRecord.id,
82
+ type: notificationRecord?.type || "send"
83
+ });
84
+ }
85
+ }
86
+ }