wabe 0.6.9 → 0.6.11

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 (162) hide show
  1. package/README.md +156 -50
  2. package/bucket/b.txt +1 -0
  3. package/dev/index.ts +215 -0
  4. package/dist/authentication/Session.d.ts +4 -1
  5. package/dist/authentication/interface.d.ts +16 -0
  6. package/dist/cron/index.d.ts +0 -1
  7. package/dist/database/DatabaseController.d.ts +41 -13
  8. package/dist/database/interface.d.ts +1 -0
  9. package/dist/email/DevAdapter.d.ts +0 -1
  10. package/dist/email/interface.d.ts +1 -1
  11. package/dist/graphql/resolvers.d.ts +4 -2
  12. package/dist/hooks/index.d.ts +8 -2
  13. package/dist/index.d.ts +0 -1
  14. package/dist/index.js +32144 -32058
  15. package/dist/schema/Schema.d.ts +2 -1
  16. package/dist/server/index.d.ts +4 -2
  17. package/dist/utils/crypto.d.ts +7 -0
  18. package/dist/utils/helper.d.ts +5 -1
  19. package/generated/schema.graphql +22 -14
  20. package/generated/wabe.ts +4 -4
  21. package/package.json +23 -23
  22. package/src/authentication/OTP.test.ts +69 -0
  23. package/src/authentication/OTP.ts +64 -0
  24. package/src/authentication/Session.test.ts +629 -0
  25. package/src/authentication/Session.ts +493 -0
  26. package/src/authentication/defaultAuthentication.ts +209 -0
  27. package/src/authentication/index.ts +3 -0
  28. package/src/authentication/interface.ts +155 -0
  29. package/src/authentication/oauth/GitHub.test.ts +91 -0
  30. package/src/authentication/oauth/GitHub.ts +121 -0
  31. package/src/authentication/oauth/Google.test.ts +91 -0
  32. package/src/authentication/oauth/Google.ts +101 -0
  33. package/src/authentication/oauth/Oauth2Client.test.ts +219 -0
  34. package/src/authentication/oauth/Oauth2Client.ts +135 -0
  35. package/src/authentication/oauth/index.ts +2 -0
  36. package/src/authentication/oauth/utils.test.ts +33 -0
  37. package/src/authentication/oauth/utils.ts +27 -0
  38. package/src/authentication/providers/EmailOTP.test.ts +127 -0
  39. package/src/authentication/providers/EmailOTP.ts +84 -0
  40. package/src/authentication/providers/EmailPassword.test.ts +176 -0
  41. package/src/authentication/providers/EmailPassword.ts +116 -0
  42. package/src/authentication/providers/EmailPasswordSRP.test.ts +208 -0
  43. package/src/authentication/providers/EmailPasswordSRP.ts +179 -0
  44. package/src/authentication/providers/GitHub.ts +24 -0
  45. package/src/authentication/providers/Google.ts +24 -0
  46. package/src/authentication/providers/OAuth.test.ts +185 -0
  47. package/src/authentication/providers/OAuth.ts +106 -0
  48. package/src/authentication/providers/PhonePassword.test.ts +176 -0
  49. package/src/authentication/providers/PhonePassword.ts +115 -0
  50. package/src/authentication/providers/QRCodeOTP.test.ts +77 -0
  51. package/src/authentication/providers/QRCodeOTP.ts +58 -0
  52. package/src/authentication/providers/index.ts +6 -0
  53. package/src/authentication/resolvers/refreshResolver.test.ts +30 -0
  54. package/src/authentication/resolvers/refreshResolver.ts +19 -0
  55. package/src/authentication/resolvers/signInWithResolver.inte.test.ts +59 -0
  56. package/src/authentication/resolvers/signInWithResolver.test.ts +293 -0
  57. package/src/authentication/resolvers/signInWithResolver.ts +92 -0
  58. package/src/authentication/resolvers/signOutResolver.test.ts +38 -0
  59. package/src/authentication/resolvers/signOutResolver.ts +18 -0
  60. package/src/authentication/resolvers/signUpWithResolver.test.ts +180 -0
  61. package/src/authentication/resolvers/signUpWithResolver.ts +65 -0
  62. package/src/authentication/resolvers/verifyChallenge.test.ts +133 -0
  63. package/src/authentication/resolvers/verifyChallenge.ts +62 -0
  64. package/src/authentication/roles.test.ts +49 -0
  65. package/src/authentication/roles.ts +40 -0
  66. package/src/authentication/utils.test.ts +97 -0
  67. package/src/authentication/utils.ts +39 -0
  68. package/src/cache/InMemoryCache.test.ts +62 -0
  69. package/src/cache/InMemoryCache.ts +45 -0
  70. package/src/cron/index.test.ts +17 -0
  71. package/src/cron/index.ts +43 -0
  72. package/src/database/DatabaseController.test.ts +613 -0
  73. package/src/database/DatabaseController.ts +1007 -0
  74. package/src/database/index.test.ts +1372 -0
  75. package/src/database/index.ts +9 -0
  76. package/src/database/interface.ts +302 -0
  77. package/src/email/DevAdapter.ts +7 -0
  78. package/src/email/EmailController.test.ts +29 -0
  79. package/src/email/EmailController.ts +13 -0
  80. package/src/email/index.ts +2 -0
  81. package/src/email/interface.ts +36 -0
  82. package/src/email/templates/sendOtpCode.ts +120 -0
  83. package/src/file/FileController.ts +28 -0
  84. package/src/file/FileDevAdapter.ts +51 -0
  85. package/src/file/hookDeleteFile.ts +25 -0
  86. package/src/file/hookReadFile.ts +66 -0
  87. package/src/file/hookUploadFile.ts +50 -0
  88. package/src/file/index.test.ts +932 -0
  89. package/src/file/index.ts +2 -0
  90. package/src/file/interface.ts +39 -0
  91. package/src/graphql/GraphQLSchema.test.ts +4408 -0
  92. package/src/graphql/GraphQLSchema.ts +880 -0
  93. package/src/graphql/index.ts +2 -0
  94. package/src/graphql/parseGraphqlSchema.ts +85 -0
  95. package/src/graphql/parser.test.ts +203 -0
  96. package/src/graphql/parser.ts +542 -0
  97. package/src/graphql/pointerAndRelationFunction.ts +191 -0
  98. package/src/graphql/resolvers.ts +442 -0
  99. package/src/graphql/tests/aggregation.test.ts +1115 -0
  100. package/src/graphql/tests/e2e.test.ts +590 -0
  101. package/src/graphql/tests/scalars.test.ts +250 -0
  102. package/src/graphql/types.ts +227 -0
  103. package/src/hooks/HookObject.test.ts +122 -0
  104. package/src/hooks/HookObject.ts +165 -0
  105. package/src/hooks/authentication.ts +67 -0
  106. package/src/hooks/createUser.test.ts +77 -0
  107. package/src/hooks/createUser.ts +10 -0
  108. package/src/hooks/defaultFields.test.ts +176 -0
  109. package/src/hooks/defaultFields.ts +32 -0
  110. package/src/hooks/deleteSession.test.ts +181 -0
  111. package/src/hooks/deleteSession.ts +20 -0
  112. package/src/hooks/hashFieldHook.test.ts +152 -0
  113. package/src/hooks/hashFieldHook.ts +89 -0
  114. package/src/hooks/index.test.ts +258 -0
  115. package/src/hooks/index.ts +414 -0
  116. package/src/hooks/permissions.test.ts +412 -0
  117. package/src/hooks/permissions.ts +93 -0
  118. package/src/hooks/protected.test.ts +551 -0
  119. package/src/hooks/protected.ts +60 -0
  120. package/src/hooks/searchableFields.test.ts +147 -0
  121. package/src/hooks/searchableFields.ts +86 -0
  122. package/src/hooks/session.test.ts +134 -0
  123. package/src/hooks/session.ts +76 -0
  124. package/src/hooks/setEmail.test.ts +216 -0
  125. package/src/hooks/setEmail.ts +33 -0
  126. package/src/hooks/setupAcl.test.ts +618 -0
  127. package/src/hooks/setupAcl.ts +25 -0
  128. package/src/index.ts +9 -0
  129. package/src/schema/Schema.test.ts +482 -0
  130. package/src/schema/Schema.ts +757 -0
  131. package/src/schema/defaultResolvers.ts +93 -0
  132. package/src/schema/index.ts +1 -0
  133. package/src/schema/resolvers/meResolver.test.ts +62 -0
  134. package/src/schema/resolvers/meResolver.ts +10 -0
  135. package/src/schema/resolvers/resetPassword.test.ts +341 -0
  136. package/src/schema/resolvers/resetPassword.ts +63 -0
  137. package/src/schema/resolvers/sendEmail.test.ts +118 -0
  138. package/src/schema/resolvers/sendEmail.ts +21 -0
  139. package/src/schema/resolvers/sendOtpCode.test.ts +141 -0
  140. package/src/schema/resolvers/sendOtpCode.ts +52 -0
  141. package/src/security.test.ts +3434 -0
  142. package/src/server/defaultSessionHandler.test.ts +62 -0
  143. package/src/server/defaultSessionHandler.ts +105 -0
  144. package/src/server/generateCodegen.ts +433 -0
  145. package/src/server/index.test.ts +532 -0
  146. package/src/server/index.ts +334 -0
  147. package/src/server/interface.ts +11 -0
  148. package/src/server/routes/authHandler.ts +169 -0
  149. package/src/server/routes/index.ts +39 -0
  150. package/src/utils/crypto.test.ts +41 -0
  151. package/src/utils/crypto.ts +105 -0
  152. package/src/utils/export.ts +11 -0
  153. package/src/utils/helper.ts +204 -0
  154. package/src/utils/index.test.ts +11 -0
  155. package/src/utils/index.ts +189 -0
  156. package/src/utils/preload.ts +8 -0
  157. package/src/utils/testHelper.ts +116 -0
  158. package/tsconfig.json +32 -0
  159. package/bunfig.toml +0 -4
  160. package/dist/ai/index.d.ts +0 -1
  161. package/dist/ai/interface.d.ts +0 -9
  162. /package/dist/server/{defaultHandlers.d.ts → defaultSessionHandler.d.ts} +0 -0
@@ -0,0 +1,9 @@
1
+ import type { WabeTypes } from '../server'
2
+ import type { DatabaseAdapter } from './interface'
3
+
4
+ export interface DatabaseConfig<T extends WabeTypes> {
5
+ adapter: DatabaseAdapter<T>
6
+ }
7
+
8
+ export * from './DatabaseController'
9
+ export * from './interface'
@@ -0,0 +1,302 @@
1
+ import type { WabeContext } from '../server/interface'
2
+ import type { WabeTypes } from '../server'
3
+ import type { SchemaInterface } from '../schema'
4
+
5
+ type IsScalar<T> = T extends string | number | boolean | Date ? true : false
6
+
7
+ type IsArray<T> = T extends Array<any> ? true : false
8
+
9
+ type IsObject<T, K extends WabeTypes> = T extends object
10
+ ? T extends K['types'][keyof K['types']]
11
+ ? false
12
+ : true
13
+ : false
14
+
15
+ type ExtractType<
16
+ T extends WabeTypes,
17
+ ClassName extends keyof T['types'],
18
+ FieldName extends keyof T['types'][ClassName],
19
+ > = T['types'][ClassName][FieldName]
20
+
21
+ type ExtractWhereType<
22
+ T extends WabeTypes,
23
+ ClassName extends keyof T['where'],
24
+ FieldName extends keyof T['where'][ClassName],
25
+ > = T['where'][ClassName][FieldName]
26
+
27
+ type WhereScalar<T> = {
28
+ equalTo?: T
29
+ notEqualTo?: T
30
+ greaterThan?: T
31
+ lessThan?: T
32
+ greaterThanOrEqualTo?: T
33
+ lessThanOrEqualTo?: T
34
+ in?: T[]
35
+ notIn?: T[]
36
+ contains?: T
37
+ notContains?: T
38
+ exists?: boolean
39
+ }
40
+
41
+ type WhereObject<T> = {
42
+ [P in keyof T]: IsScalar<T[P]> extends false ? WhereObject<Partial<T[P]>> : WhereScalar<T[P]>
43
+ }
44
+
45
+ type WhereAggregation<T extends WabeTypes, K = keyof T['where']> = {
46
+ [P in keyof T['where'][K]]: IsScalar<ExtractWhereType<T, K, P>> extends false
47
+ ? WhereObject<Partial<ExtractWhereType<T, K, P>>>
48
+ : WhereScalar<ExtractWhereType<T, K, P>>
49
+ }
50
+
51
+ type WhereConditional<T extends WabeTypes, K = keyof T['where']> = {
52
+ OR?: Array<WhereType<T, K>>
53
+ AND?: Array<WhereType<T, K>>
54
+ }
55
+
56
+ export type WhereType<T extends WabeTypes, K = keyof T['where']> = Partial<WhereAggregation<T, K>> &
57
+ WhereConditional<T, K>
58
+
59
+ type SelectObject<T, K extends WabeTypes, Depth extends number = 3> = {
60
+ [P in keyof T]: IsScalar<T[P]> extends true
61
+ ? boolean
62
+ : IsArray<T[P]> extends true
63
+ ? T[P] extends Array<infer Item>
64
+ ? (Depth extends 0 ? boolean : SelectObject<Partial<Item>, K, Decrement<Depth>>) | boolean
65
+ : boolean
66
+ : IsObject<[P], K> extends true
67
+ ? (Depth extends 0 ? boolean : SelectObject<Partial<T[P]>, K, Decrement<Depth>>) | boolean
68
+ : boolean
69
+ }
70
+
71
+ type Decrement<N extends number> = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10][N]
72
+
73
+ export type SelectType<
74
+ T extends WabeTypes,
75
+ K extends keyof T['types'],
76
+ U extends keyof T['types'][K],
77
+ Depth extends number = 3,
78
+ > = Partial<{
79
+ [P in U]: IsScalar<ExtractType<T, K, P>> extends true
80
+ ? boolean
81
+ : IsArray<ExtractType<T, K, P>> extends true
82
+ ? ExtractType<T, K, P> extends Array<infer Item>
83
+ ? (Depth extends 0 ? boolean : SelectObject<Partial<Item>, T, Decrement<Depth>>) | boolean
84
+ : boolean
85
+ : ExtractType<T, K, P> extends object
86
+ ?
87
+ | (Depth extends 0
88
+ ? boolean
89
+ : SelectObject<Partial<ExtractType<T, K, P>>, T, Decrement<Depth>>)
90
+ | boolean
91
+ : boolean
92
+ }>
93
+
94
+ export type OrderType<
95
+ T extends WabeTypes,
96
+ K extends keyof T['types'],
97
+ U extends keyof T['types'][K],
98
+ > = Record<U, 'ASC' | 'DESC'>
99
+
100
+ export type OutputType<
101
+ T extends WabeTypes,
102
+ K extends keyof T['types'],
103
+ U extends keyof T['types'][K],
104
+ > = (Pick<T['types'][K], U> & { id: string }) | null
105
+
106
+ export interface AdapterOptions {
107
+ databaseUrl: string
108
+ databaseName: string
109
+ }
110
+
111
+ export type MutationData<
112
+ T extends WabeTypes,
113
+ K extends keyof T['types'],
114
+ U extends keyof T['types'][K],
115
+ > = Record<U, any>
116
+
117
+ export interface CountOptions<T extends WabeTypes, K extends keyof T['types']> {
118
+ className: K
119
+ where?: WhereType<T, K>
120
+ context: WabeContext<T>
121
+ }
122
+
123
+ export interface GetObjectOptions<
124
+ T extends WabeTypes,
125
+ K extends keyof T['types'],
126
+ U extends keyof T['types'][K],
127
+ > {
128
+ className: K
129
+ id: string
130
+ where?: WhereType<T, K>
131
+ context: WabeContext<T>
132
+ _skipHooks?: boolean
133
+ select?: SelectType<T, K, U>
134
+ }
135
+
136
+ export interface GetObjectsOptions<
137
+ T extends WabeTypes,
138
+ K extends keyof T['types'],
139
+ U extends keyof T['types'][K],
140
+ W extends keyof T['types'][K],
141
+ > {
142
+ className: K
143
+ where?: WhereType<T, K>
144
+ order?: OrderType<T, K, U>
145
+ offset?: number
146
+ first?: number
147
+ context: WabeContext<T>
148
+ _skipHooks?: boolean
149
+ select?: SelectType<T, K, W>
150
+ }
151
+
152
+ export interface CreateObjectOptions<
153
+ T extends WabeTypes,
154
+ K extends keyof T['types'],
155
+ U extends keyof T['types'][K],
156
+ W extends keyof T['types'][K],
157
+ > {
158
+ className: K
159
+ data: MutationData<T, K, U>
160
+ context: WabeContext<T>
161
+ select?: SelectType<T, K, W>
162
+ }
163
+ export interface CreateObjectsOptions<
164
+ T extends WabeTypes,
165
+ K extends keyof T['types'],
166
+ U extends keyof T['types'][K],
167
+ W extends keyof T['types'][K],
168
+ X extends keyof T['types'][K],
169
+ > {
170
+ className: K
171
+ data: Array<MutationData<T, K, U>>
172
+ offset?: number
173
+ first?: number
174
+ order?: OrderType<T, U, X>
175
+ context: WabeContext<T>
176
+ select?: SelectType<T, K, W>
177
+ }
178
+
179
+ export interface UpdateObjectOptions<
180
+ T extends WabeTypes,
181
+ K extends keyof T['types'],
182
+ U extends keyof T['types'][K],
183
+ W extends keyof T['types'][K],
184
+ > {
185
+ className: K
186
+ id: string
187
+ where?: WhereType<T, K>
188
+ data: MutationData<T, K, U>
189
+ context: WabeContext<T>
190
+ _skipHooks?: boolean
191
+ select?: SelectType<T, K, W>
192
+ }
193
+
194
+ export interface UpdateObjectsOptions<
195
+ T extends WabeTypes,
196
+ K extends keyof T['types'],
197
+ U extends keyof T['types'][K],
198
+ W extends keyof T['types'][K],
199
+ X extends keyof T['types'][K],
200
+ > {
201
+ className: K
202
+ where: WhereType<T, K>
203
+ order?: OrderType<T, K, X>
204
+ data: MutationData<T, K, U>
205
+ offset?: number
206
+ first?: number
207
+ context: WabeContext<T>
208
+ _skipHooks?: boolean
209
+ select?: SelectType<T, K, W>
210
+ }
211
+
212
+ export interface DeleteObjectOptions<
213
+ T extends WabeTypes,
214
+ K extends keyof T['types'],
215
+ U extends keyof T['types'][K],
216
+ > {
217
+ className: K
218
+ id: string
219
+ where?: WhereType<T, K>
220
+ context: WabeContext<T>
221
+ select?: SelectType<T, K, U>
222
+ }
223
+
224
+ export interface DeleteObjectsOptions<
225
+ T extends WabeTypes,
226
+ K extends keyof T['types'],
227
+ U extends keyof T['types'][K],
228
+ W extends keyof T['types'][K],
229
+ > {
230
+ className: K
231
+ where: WhereType<T, K>
232
+ order?: OrderType<T, K, U>
233
+ offset?: number
234
+ first?: number
235
+ context: WabeContext<T>
236
+ select?: SelectType<T, K, W>
237
+ }
238
+
239
+ export interface DatabaseAdapter<T extends WabeTypes> {
240
+ close(): Promise<void>
241
+
242
+ createClassIfNotExist(className: string, schema: SchemaInterface<T>): Promise<any> | any
243
+
244
+ initializeDatabase(schema: SchemaInterface<T>): Promise<void>
245
+ clearDatabase(): Promise<void>
246
+
247
+ count<K extends keyof T['types']>(params: CountOptions<T, K>): Promise<number>
248
+
249
+ getObject<K extends keyof T['types'], U extends keyof T['types'][K]>(
250
+ params: GetObjectOptions<T, K, U>,
251
+ ): Promise<OutputType<T, K, U>>
252
+ getObjects<
253
+ K extends keyof T['types'],
254
+ U extends keyof T['types'][K],
255
+ W extends keyof T['types'][K],
256
+ >(
257
+ params: GetObjectsOptions<T, K, U, W>,
258
+ ): Promise<OutputType<T, K, W>[]>
259
+
260
+ createObject<
261
+ K extends keyof T['types'],
262
+ U extends keyof T['types'][K],
263
+ W extends keyof T['types'][K],
264
+ >(
265
+ params: CreateObjectOptions<T, K, U, W>,
266
+ ): Promise<{ id: string }>
267
+ createObjects<
268
+ K extends keyof T['types'],
269
+ U extends keyof T['types'][K],
270
+ W extends keyof T['types'][K],
271
+ X extends keyof T['types'][K],
272
+ >(
273
+ params: CreateObjectsOptions<T, K, U, W, X>,
274
+ ): Promise<Array<{ id: string }>>
275
+
276
+ updateObject<
277
+ K extends keyof T['types'],
278
+ U extends keyof T['types'][K],
279
+ W extends keyof T['types'][K],
280
+ >(
281
+ params: UpdateObjectOptions<T, K, U, W>,
282
+ ): Promise<{ id: string }>
283
+ updateObjects<
284
+ K extends keyof T['types'],
285
+ U extends keyof T['types'][K],
286
+ W extends keyof T['types'][K],
287
+ X extends keyof T['types'][K],
288
+ >(
289
+ params: UpdateObjectsOptions<T, K, U, W, X>,
290
+ ): Promise<Array<{ id: string }>>
291
+
292
+ deleteObject<K extends keyof T['types'], U extends keyof T['types'][K]>(
293
+ params: DeleteObjectOptions<T, K, U>,
294
+ ): Promise<void>
295
+ deleteObjects<
296
+ K extends keyof T['types'],
297
+ U extends keyof T['types'][K],
298
+ W extends keyof T['types'][K],
299
+ >(
300
+ params: DeleteObjectsOptions<T, K, U, W>,
301
+ ): Promise<void>
302
+ }
@@ -0,0 +1,7 @@
1
+ import type { EmailAdapter } from './interface'
2
+
3
+ export class EmailDevAdapter implements EmailAdapter {
4
+ async send() {
5
+ return '123456'
6
+ }
7
+ }
@@ -0,0 +1,29 @@
1
+ import { describe, expect, it, mock } from 'bun:test'
2
+ import { EmailController } from './EmailController'
3
+
4
+ describe('EmailController', () => {
5
+ it('should send email using correct adapter', async () => {
6
+ const mockSend = mock(() => {})
7
+ const dummyAdapter = {
8
+ send: mockSend,
9
+ }
10
+
11
+ // @ts-expect-error
12
+ const controller = new EmailController(dummyAdapter)
13
+
14
+ await controller.send({
15
+ from: 'from',
16
+ to: ['to'],
17
+ subject: 'subject',
18
+ text: 'text',
19
+ })
20
+
21
+ expect(mockSend).toHaveBeenCalledTimes(1)
22
+ expect(mockSend).toHaveBeenCalledWith({
23
+ from: 'from',
24
+ to: ['to'],
25
+ subject: 'subject',
26
+ text: 'text',
27
+ })
28
+ })
29
+ })
@@ -0,0 +1,13 @@
1
+ import type { EmailAdapter, EmailSendOptions } from './interface'
2
+
3
+ export class EmailController implements EmailAdapter {
4
+ public adapter: EmailAdapter
5
+
6
+ constructor(adapter: EmailAdapter) {
7
+ this.adapter = adapter
8
+ }
9
+
10
+ send(options: EmailSendOptions) {
11
+ return this.adapter.send(options)
12
+ }
13
+ }
@@ -0,0 +1,2 @@
1
+ export * from './interface'
2
+ export * from './DevAdapter'
@@ -0,0 +1,36 @@
1
+ export type HtmlTemplates = {
2
+ sendOTPCode: {
3
+ fn: (options: { otp: string }) => string | Promise<string>
4
+ subject: string
5
+ }
6
+ }
7
+
8
+ export interface EmailSendOptions {
9
+ from: string
10
+ to: Array<string>
11
+ subject: string
12
+ node?: any
13
+ html?: string
14
+ text?: string
15
+ }
16
+
17
+ export interface EmailAdapter {
18
+ /**
19
+ * Send an email using the provided adapter
20
+ * @param options Mail options (expeditor, recipient, subject ...)
21
+ * @return The id of the email sended, throw an error if something wrong
22
+ */
23
+ send(options: EmailSendOptions): Promise<string>
24
+ }
25
+
26
+ /**
27
+ * Configuration for the email in Wabe
28
+ * @property adapter The adapter to use to send emails
29
+ * @property mainEmail The email to use as sender for emails sent by Wabe
30
+ * @property templates The html templates to use for a specific email. If not provided, Wabe will use the default templates
31
+ */
32
+ export interface EmailConfig {
33
+ adapter: EmailAdapter
34
+ mainEmail?: string
35
+ htmlTemplates?: HtmlTemplates
36
+ }
@@ -0,0 +1,120 @@
1
+ export const sendOtpCodeTemplate = (otp: string) => `
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+
5
+ <head>
6
+ <meta charset="UTF-8">
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
+ <meta http-equiv="X-UA-Compatible" content="ie=edge">
9
+ <title>Password Reset</title>
10
+ <style>
11
+ body {
12
+ font-family: 'Arial', sans-serif;
13
+ margin: 0;
14
+ padding: 0;
15
+ background-color: #f4f4f4;
16
+ }
17
+
18
+ .email-container {
19
+ max-width: 600px;
20
+ margin: 0 auto;
21
+ background-color: #ffffff;
22
+ border-radius: 8px;
23
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
24
+ overflow: hidden;
25
+ }
26
+
27
+ .header {
28
+ background-color: #4CAF50;
29
+ padding: 20px;
30
+ text-align: center;
31
+ color: white;
32
+ }
33
+
34
+ .header h1 {
35
+ margin: 0;
36
+ font-size: 24px;
37
+ }
38
+
39
+ .content {
40
+ padding: 20px;
41
+ font-size: 16px;
42
+ line-height: 1.6;
43
+ color: #333333;
44
+ }
45
+
46
+ .content p {
47
+ margin-bottom: 20px;
48
+ }
49
+
50
+ .otp-code {
51
+ display: inline-block;
52
+ padding: 10px 20px;
53
+ background-color: #4CAF50;
54
+ color: white;
55
+ font-size: 18px;
56
+ font-weight: bold;
57
+ border-radius: 5px;
58
+ letter-spacing: 2px;
59
+ text-align: center;
60
+ }
61
+
62
+ .footer {
63
+ background-color: #f4f4f4;
64
+ text-align: center;
65
+ padding: 10px;
66
+ font-size: 14px;
67
+ color: #888888;
68
+ }
69
+
70
+ .footer p {
71
+ margin: 0;
72
+ }
73
+
74
+ /* Responsive Design */
75
+ @media screen and (max-width: 600px) {
76
+ .email-container {
77
+ width: 100%;
78
+ }
79
+
80
+ .header h1 {
81
+ font-size: 20px;
82
+ }
83
+
84
+ .content {
85
+ padding: 15px;
86
+ }
87
+
88
+ .otp-code {
89
+ font-size: 16px;
90
+ padding: 8px 16px;
91
+ }
92
+ }
93
+ </style>
94
+ </head>
95
+
96
+ <body>
97
+ <div class="email-container">
98
+ <!-- Email Header -->
99
+ <div class="header">
100
+ <h1>Confirmation code</h1>
101
+ </div>
102
+
103
+ <!-- Email Content -->
104
+ <div class="content">
105
+ <p>Hello,</p>
106
+ <p>Here is your confirmation code. Please use the OTP code (valid for 5 minutes) below to proceed the action:</p>
107
+ <p class="otp-code">${otp}</p>
108
+ <p>If you did not request this code, please ignore this email.</p>
109
+ <p>Thank you</p>
110
+ </div>
111
+
112
+ <!-- Email Footer -->
113
+ <div class="footer">
114
+ <p>Powered by Wabe</p>
115
+ </div>
116
+ </div>
117
+ </body>
118
+
119
+ </html>
120
+ `
@@ -0,0 +1,28 @@
1
+ import type { Wabe } from '../server'
2
+ import type { DevWabeTypes } from '../utils/helper'
3
+ import type { FileAdapter, ReadFileOptions } from './interface'
4
+
5
+ export class FileController implements FileAdapter {
6
+ public adapter: FileAdapter
7
+ private wabe: Wabe<DevWabeTypes>
8
+
9
+ constructor(adapter: FileAdapter, wabe: Wabe<any>) {
10
+ this.adapter = adapter
11
+ this.wabe = wabe
12
+ }
13
+
14
+ uploadFile(file: File) {
15
+ return this.adapter.uploadFile(file)
16
+ }
17
+
18
+ readFile(fileName: string, options?: ReadFileOptions) {
19
+ return this.adapter.readFile(fileName, {
20
+ ...options,
21
+ port: this.wabe.config.port,
22
+ })
23
+ }
24
+
25
+ deleteFile(fileName: string) {
26
+ return this.adapter.deleteFile(fileName)
27
+ }
28
+ }
@@ -0,0 +1,51 @@
1
+ import { writeFile, mkdir, rm, access, constants } from 'node:fs/promises'
2
+ import path from 'node:path'
3
+ import type { FileAdapter, ReadFileOptions } from '.'
4
+
5
+ export class FileDevAdapter implements FileAdapter {
6
+ private basePath = 'bucket'
7
+ private rootPath = process.cwd()
8
+
9
+ async uploadFile(file: File): Promise<void> {
10
+ const fullPath = path.join(this.rootPath, this.basePath)
11
+
12
+ await mkdir(fullPath, { recursive: true })
13
+
14
+ const fileType = file.type
15
+
16
+ let fileContent: Buffer
17
+
18
+ if (fileType.startsWith('text') || fileType.includes('json')) {
19
+ const textContent = await file.text()
20
+ fileContent = Buffer.from(textContent, 'utf-8')
21
+ } else {
22
+ const arrayBuffer = await file.arrayBuffer()
23
+ fileContent = Buffer.from(arrayBuffer)
24
+ }
25
+
26
+ await writeFile(path.join(fullPath, file.name), fileContent)
27
+ }
28
+
29
+ async readFile(fileName: string, options?: ReadFileOptions): Promise<string | null> {
30
+ const filePath = path.join(this.rootPath, this.basePath, fileName)
31
+
32
+ try {
33
+ await access(filePath, constants.F_OK)
34
+ return `http://127.0.0.1:${options?.port || 3001}/${this.basePath}/${fileName}`
35
+ } catch {
36
+ return null
37
+ }
38
+ }
39
+
40
+ async deleteFile(fileName: string): Promise<void> {
41
+ const filePath = path.join(this.rootPath, this.basePath, fileName)
42
+
43
+ try {
44
+ await access(filePath, constants.F_OK)
45
+
46
+ await rm(filePath)
47
+ } catch {
48
+ // Do nothing
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,25 @@
1
+ import type { HookObject } from '../hooks/HookObject'
2
+
3
+ const deleteFile = async (hookObject: HookObject<any, any>) => {
4
+ const schema = hookObject.context.wabe.config.schema?.classes?.find(
5
+ (currentClass) => currentClass.name === hookObject.className,
6
+ )
7
+
8
+ if (!schema) return
9
+
10
+ await Promise.all(
11
+ Object.entries(schema.fields)
12
+ .filter(([_, value]) => value.type === 'File')
13
+ .map(([fieldName]) => {
14
+ const fileName = hookObject.originalObject?.[fieldName]?.name as string
15
+
16
+ if (!fileName) return Promise.resolve()
17
+
18
+ if (!hookObject.context.wabe.controllers.file) throw new Error('No file adapter found')
19
+
20
+ return hookObject.context.wabe.controllers.file?.deleteFile(fileName)
21
+ }),
22
+ )
23
+ }
24
+
25
+ export const defaultAfterDeleteFile = (hookObject: HookObject<any, any>) => deleteFile(hookObject)
@@ -0,0 +1,66 @@
1
+ import type { HookObject } from '../hooks/HookObject'
2
+
3
+ const getFile = async (hookObject: HookObject<any, any>) => {
4
+ const schema = hookObject.context.wabe.config.schema?.classes?.find(
5
+ (currentClass) => currentClass.name === hookObject.className,
6
+ )
7
+
8
+ if (!schema) return
9
+
10
+ const urlCacheInSeconds = hookObject.context.wabe.config.file?.urlCacheInSeconds || 3600 * 24
11
+
12
+ // After read we get the file URL and we update the field url with an URL.
13
+ // For security purpose we recommend to use a presigned URL
14
+ await Promise.all(
15
+ Object.entries(schema.fields)
16
+ .filter(([_, value]) => value.type === 'File')
17
+ .map(async ([fieldName]) => {
18
+ const fileInfo = hookObject.object?.[fieldName]
19
+
20
+ if (!fileInfo) return
21
+
22
+ const fileName = fileInfo.name as string
23
+
24
+ if (!fileName && fileInfo.url) return fileInfo.url
25
+
26
+ const fileUrlGeneratedAt = new Date(fileInfo.urlGeneratedAt)
27
+
28
+ if (
29
+ fileUrlGeneratedAt &&
30
+ fileUrlGeneratedAt.getTime() + urlCacheInSeconds * 1000 > Date.now()
31
+ )
32
+ return
33
+
34
+ if (!hookObject.context.wabe.controllers.file) throw new Error('No file adapter found')
35
+
36
+ const fileUrlFromBucket = await hookObject.context.wabe.controllers.file?.readFile(fileName)
37
+
38
+ const newUrl = fileUrlFromBucket || fileInfo.url
39
+ const newUrlGeneratedAt = new Date()
40
+
41
+ // Mutate the object returned to the caller so AfterRead effects are visible immediately
42
+ // @ts-expect-error
43
+ hookObject.object[fieldName] = {
44
+ ...fileInfo,
45
+ urlGeneratedAt: newUrlGeneratedAt,
46
+ url: newUrl,
47
+ }
48
+
49
+ return hookObject.context.wabe.controllers.database.updateObject({
50
+ className: hookObject.className,
51
+ context: hookObject.context,
52
+ id: hookObject.object?.id || '',
53
+ data: {
54
+ [fieldName]: {
55
+ ...fileInfo,
56
+ urlGeneratedAt: newUrlGeneratedAt,
57
+ url: newUrl,
58
+ },
59
+ },
60
+ _skipHooks: true,
61
+ })
62
+ }),
63
+ )
64
+ }
65
+
66
+ export const defaultAfterReadFile = (hookObject: HookObject<any, any>) => getFile(hookObject)