@skalfa/skalfa-api-core 1.0.2 → 1.0.7

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 (272) 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 +5 -3
  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/{light-controller.js → skalfa-controller.js} +3 -3
  68. package/dist/commands/make/skalfa-controller.js.map +1 -0
  69. package/dist/commands/make/{light-model.js → skalfa-model.js} +4 -4
  70. package/dist/commands/make/skalfa-model.js.map +1 -0
  71. package/dist/commands/runner/barrels.js.map +1 -1
  72. package/dist/commands/runner/blueprint/controller-generation.js.map +1 -1
  73. package/dist/commands/runner/blueprint/documentation-generation.js.map +1 -1
  74. package/dist/commands/runner/blueprint/migration-generation.js +1 -1
  75. package/dist/commands/runner/blueprint/migration-generation.js.map +1 -1
  76. package/dist/commands/runner/blueprint/model-generation.js.map +1 -1
  77. package/dist/commands/runner/blueprint/runner.js +2 -2
  78. package/dist/commands/runner/blueprint/runner.js.map +1 -1
  79. package/dist/commands/runner/blueprint/seeder-generation.js +1 -1
  80. package/dist/commands/runner/blueprint/seeder-generation.js.map +1 -1
  81. package/dist/commands/runner/da-migration.js +1 -2
  82. package/dist/commands/runner/da-migration.js.map +1 -1
  83. package/dist/commands/runner/migration.js +1 -1
  84. package/dist/commands/runner/migration.js.map +1 -1
  85. package/dist/commands/runner/seeder.js +1 -1
  86. package/dist/commands/runner/seeder.js.map +1 -1
  87. package/dist/context/context.js +6 -0
  88. package/dist/context/context.js.map +1 -1
  89. package/dist/controller/controller.d.ts +17 -30
  90. package/dist/controller/controller.js +39 -121
  91. package/dist/controller/controller.js.map +1 -1
  92. package/dist/controller/response.d.ts +6 -0
  93. package/dist/controller/response.js +63 -0
  94. package/dist/controller/response.js.map +1 -0
  95. package/dist/controller/storage.d.ts +9 -0
  96. package/dist/controller/storage.js +56 -0
  97. package/dist/controller/storage.js.map +1 -0
  98. package/dist/conversion/conversion.d.ts +3 -0
  99. package/dist/conversion/conversion.js +28 -4
  100. package/dist/conversion/conversion.js.map +1 -1
  101. package/dist/conversion/date.d.ts +1 -0
  102. package/dist/conversion/date.js +77 -0
  103. package/dist/conversion/date.js.map +1 -0
  104. package/dist/index.d.ts +2 -0
  105. package/dist/index.js +2 -0
  106. package/dist/index.js.map +1 -1
  107. package/dist/logger/logger.js +33 -0
  108. package/dist/logger/logger.js.map +1 -1
  109. package/dist/mail/mail.js +6 -6
  110. package/dist/mail/mail.js.map +1 -1
  111. package/dist/middleware/access-log.d.ts +31 -0
  112. package/dist/middleware/access-log.js +13 -0
  113. package/dist/middleware/access-log.js.map +1 -0
  114. package/dist/middleware/auth.d.ts +37 -0
  115. package/dist/middleware/auth.js +16 -0
  116. package/dist/middleware/auth.js.map +1 -0
  117. package/dist/middleware/body-parse.d.ts +35 -0
  118. package/dist/middleware/body-parse.js +87 -0
  119. package/dist/middleware/body-parse.js.map +1 -0
  120. package/dist/middleware/context.d.ts +29 -0
  121. package/dist/middleware/context.js +8 -0
  122. package/dist/middleware/context.js.map +1 -0
  123. package/dist/middleware/cors.d.ts +31 -0
  124. package/dist/middleware/cors.js +27 -0
  125. package/dist/middleware/cors.js.map +1 -0
  126. package/dist/middleware/error-handler.d.ts +33 -0
  127. package/dist/middleware/error-handler.js +17 -0
  128. package/dist/middleware/error-handler.js.map +1 -0
  129. package/dist/middleware/middleware.d.ts +31 -10
  130. package/dist/middleware/middleware.js +41 -209
  131. package/dist/middleware/middleware.js.map +1 -1
  132. package/dist/middleware/private.d.ts +29 -0
  133. package/dist/middleware/private.js +8 -0
  134. package/dist/middleware/private.js.map +1 -0
  135. package/dist/middleware/rate-limiter.d.ts +32 -0
  136. package/dist/middleware/rate-limiter.js +30 -0
  137. package/dist/middleware/rate-limiter.js.map +1 -0
  138. package/dist/notification/index.d.ts +1 -0
  139. package/dist/notification/index.js +2 -0
  140. package/dist/notification/index.js.map +1 -0
  141. package/dist/notification/notification.d.ts +16 -0
  142. package/dist/notification/notification.js +64 -0
  143. package/dist/notification/notification.js.map +1 -0
  144. package/dist/permission/permission.js +9 -0
  145. package/dist/permission/permission.js.map +1 -1
  146. package/dist/registry/registry.d.ts +0 -6
  147. package/dist/registry/registry.js +6 -6
  148. package/dist/registry/registry.js.map +1 -1
  149. package/dist/storage/storage.d.ts +3 -3
  150. package/dist/storage/storage.js.map +1 -1
  151. package/dist/validation/validation.js +43 -51
  152. package/dist/validation/validation.js.map +1 -1
  153. package/package.json +4 -4
  154. package/src/auth/auth.ts +21 -252
  155. package/src/auth/create-access-token.ts +29 -0
  156. package/src/auth/create-user-mail-token.ts +24 -0
  157. package/src/auth/helpers/generate-agent-id.ts +8 -0
  158. package/src/auth/helpers/get-request-ip.ts +3 -0
  159. package/src/auth/helpers/get-user-permissions.ts +15 -0
  160. package/src/auth/helpers/index.ts +3 -0
  161. package/src/auth/list-user-sessions.ts +11 -0
  162. package/src/auth/revalidate-user-permissions-by-role.ts +13 -0
  163. package/src/auth/revalidate-user-permissions.ts +26 -0
  164. package/src/auth/revoke-access-token.ts +5 -0
  165. package/src/auth/verify-access-token.ts +56 -0
  166. package/src/auth/verify-user-mail-token.ts +24 -0
  167. package/src/commands/cli.ts +5 -3
  168. package/src/commands/make/basic-controller.ts +3 -1
  169. package/src/commands/make/basic-migration.ts +5 -3
  170. package/src/commands/make/basic-model.ts +3 -1
  171. package/src/commands/make/basic-seeder.ts +3 -1
  172. package/src/commands/make/blueprint.ts +3 -1
  173. package/src/commands/make/da-migration.ts +6 -5
  174. package/src/commands/make/mail.ts +4 -2
  175. package/src/commands/make/notification.ts +3 -1
  176. package/src/commands/make/queue.ts +3 -1
  177. package/src/commands/make/resource.ts +21 -0
  178. package/src/commands/make/{light-controller.ts → skalfa-controller.ts} +4 -2
  179. package/src/commands/make/{light-model.ts → skalfa-model.ts} +5 -3
  180. package/src/commands/runner/barrels.ts +4 -0
  181. package/src/commands/runner/blueprint/controller-generation.ts +2 -0
  182. package/src/commands/runner/blueprint/documentation-generation.ts +2 -0
  183. package/src/commands/runner/blueprint/migration-generation.ts +3 -1
  184. package/src/commands/runner/blueprint/model-generation.ts +2 -0
  185. package/src/commands/runner/blueprint/runner.ts +10 -2
  186. package/src/commands/runner/blueprint/seeder-generation.ts +3 -1
  187. package/src/commands/runner/da-migration.ts +3 -2
  188. package/src/commands/runner/migration.ts +1 -1
  189. package/src/commands/runner/seeder.ts +1 -1
  190. package/src/context/context.ts +23 -17
  191. package/src/controller/controller.ts +124 -239
  192. package/src/controller/response.ts +78 -0
  193. package/src/controller/storage.ts +78 -0
  194. package/src/conversion/conversion.ts +90 -64
  195. package/src/conversion/date.ts +74 -0
  196. package/src/index.ts +2 -0
  197. package/src/logger/logger.ts +217 -176
  198. package/src/mail/mail.ts +85 -85
  199. package/src/middleware/access-log.ts +15 -0
  200. package/src/middleware/auth.ts +19 -0
  201. package/src/middleware/body-parse.ts +83 -0
  202. package/src/middleware/context.ts +11 -0
  203. package/src/middleware/cors.ts +31 -0
  204. package/src/middleware/error-handler.ts +20 -0
  205. package/src/middleware/middleware.ts +91 -288
  206. package/src/middleware/private.ts +8 -0
  207. package/src/middleware/rate-limiter.ts +41 -0
  208. package/src/notification/index.ts +1 -0
  209. package/src/notification/notification.ts +86 -0
  210. package/src/permission/permission.ts +140 -136
  211. package/src/registry/registry.ts +17 -15
  212. package/src/route/route.ts +11 -11
  213. package/src/storage/storage.ts +104 -106
  214. package/src/validation/validation.ts +322 -346
  215. package/dist/auth.util.d.ts +0 -19
  216. package/dist/auth.util.js +0 -183
  217. package/dist/auth.util.js.map +0 -1
  218. package/dist/commands/make/light-controller.js.map +0 -1
  219. package/dist/commands/make/light-model.js.map +0 -1
  220. package/dist/context.util.d.ts +0 -7
  221. package/dist/context.util.js +0 -11
  222. package/dist/context.util.js.map +0 -1
  223. package/dist/controller.util.d.ts +0 -118
  224. package/dist/controller.util.js +0 -144
  225. package/dist/controller.util.js.map +0 -1
  226. package/dist/conversion.util.d.ts +0 -8
  227. package/dist/conversion.util.js +0 -52
  228. package/dist/conversion.util.js.map +0 -1
  229. package/dist/db/db.d.ts +0 -84
  230. package/dist/db/db.js +0 -177
  231. package/dist/db/db.js.map +0 -1
  232. package/dist/db/index.d.ts +0 -1
  233. package/dist/db/index.js +0 -2
  234. package/dist/db/index.js.map +0 -1
  235. package/dist/db.util.d.ts +0 -84
  236. package/dist/db.util.js +0 -177
  237. package/dist/db.util.js.map +0 -1
  238. package/dist/logger.util.d.ts +0 -30
  239. package/dist/logger.util.js +0 -126
  240. package/dist/logger.util.js.map +0 -1
  241. package/dist/mail.util.d.ts +0 -21
  242. package/dist/mail.util.js +0 -53
  243. package/dist/mail.util.js.map +0 -1
  244. package/dist/middleware.util.d.ts +0 -263
  245. package/dist/middleware.util.js +0 -233
  246. package/dist/middleware.util.js.map +0 -1
  247. package/dist/model/index.d.ts +0 -3
  248. package/dist/model/index.js +0 -4
  249. package/dist/model/index.js.map +0 -1
  250. package/dist/model/model.d.ts +0 -204
  251. package/dist/model/model.js +0 -1495
  252. package/dist/model/model.js.map +0 -1
  253. package/dist/model.util.d.ts +0 -204
  254. package/dist/model.util.js +0 -1495
  255. package/dist/model.util.js.map +0 -1
  256. package/dist/permission.util.d.ts +0 -38
  257. package/dist/permission.util.js +0 -91
  258. package/dist/permission.util.js.map +0 -1
  259. package/dist/registry.util.d.ts +0 -28
  260. package/dist/registry.util.js +0 -19
  261. package/dist/registry.util.js.map +0 -1
  262. package/dist/route.util.d.ts +0 -1
  263. package/dist/route.util.js +0 -12
  264. package/dist/route.util.js.map +0 -1
  265. package/dist/storage.util.d.ts +0 -56
  266. package/dist/storage.util.js +0 -82
  267. package/dist/storage.util.js.map +0 -1
  268. package/dist/validation.util.d.ts +0 -7
  269. package/dist/validation.util.js +0 -237
  270. package/dist/validation.util.js.map +0 -1
  271. /package/dist/commands/make/{light-controller.d.ts → skalfa-controller.d.ts} +0 -0
  272. /package/dist/commands/make/{light-model.d.ts → skalfa-model.d.ts} +0 -0
@@ -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
+ }