taleem-kernel 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.
- package/.env +5 -0
- package/package.json +30 -0
- package/prisma/content.db +0 -0
- package/prisma/dev.db +0 -0
- package/prisma/schema.prisma +121 -0
- package/readme.md +116 -0
- package/src/Auth.js +105 -0
- package/src/CommunicationPolicy.js +34 -0
- package/src/Config.js +26 -0
- package/src/JWT.js +31 -0
- package/src/Logger.js +33 -0
- package/src/ServerKernel.js +139 -0
- package/src/api.md +584 -0
- package/src/enums/Resources.js +8 -0
- package/src/modules/Admin.js +57 -0
- package/src/modules/Audio.js +49 -0
- package/src/modules/Communication.js +116 -0
- package/src/modules/Course.js +26 -0
- package/src/modules/Image.js +48 -0
- package/src/modules/Library.js +59 -0
- package/src/modules/Subscription.js +97 -0
- package/src/modules/Svg.js +59 -0
- package/src/modules/User.js +117 -0
- package/tests/admin.test.js +52 -0
- package/tests/course.test.js +68 -0
- package/tests/kernel.test.js +12 -0
- package/tests/library.test.js +23 -0
package/src/api.md
ADDED
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
# Taleem Server Kernel API
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
`taleem-server` encapsulates the database, Prisma, authentication, authorization and all business logic behind a single **Server Kernel API**.
|
|
6
|
+
|
|
7
|
+
HTTP routes **must never** access Prisma or the database directly.
|
|
8
|
+
|
|
9
|
+
Instead, every route communicates exclusively with the Server Kernel.
|
|
10
|
+
|
|
11
|
+
This document defines the **public Kernel API** for route authors, tests and future AI agents.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Public Kernel Components
|
|
16
|
+
|
|
17
|
+
The public kernel consists of only three categories:
|
|
18
|
+
|
|
19
|
+
### Resources
|
|
20
|
+
|
|
21
|
+
Business modules that expose the application data.
|
|
22
|
+
|
|
23
|
+
- User
|
|
24
|
+
- Admin
|
|
25
|
+
- Course
|
|
26
|
+
- Library
|
|
27
|
+
- Communication
|
|
28
|
+
- Subscription
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
### Auth
|
|
33
|
+
|
|
34
|
+
Responsible for authentication.
|
|
35
|
+
|
|
36
|
+
Responsibilities:
|
|
37
|
+
|
|
38
|
+
- create JWT tokens
|
|
39
|
+
- authenticate JWT tokens
|
|
40
|
+
- return the authenticated User/Admin
|
|
41
|
+
|
|
42
|
+
Routes never manipulate JWTs directly.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
### Policy
|
|
47
|
+
|
|
48
|
+
Responsible for authorization.
|
|
49
|
+
|
|
50
|
+
Responsibilities:
|
|
51
|
+
|
|
52
|
+
- verify that an authenticated admin has permission to perform an operation on a course resource.
|
|
53
|
+
|
|
54
|
+
Authentication and authorization are intentionally separated.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# Route Utilities
|
|
60
|
+
|
|
61
|
+
Every authenticated route begins by extracting the Bearer token.
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
import { getToken } from "../utils/getToken.js";
|
|
65
|
+
|
|
66
|
+
const token = getToken(req);
|
|
67
|
+
|
|
68
|
+
const identity = await kernel.auth.authenticate(token);
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Implementation:
|
|
72
|
+
|
|
73
|
+
```js
|
|
74
|
+
export function getToken(req) {
|
|
75
|
+
|
|
76
|
+
const header = req.headers.authorization;
|
|
77
|
+
|
|
78
|
+
if (!header?.startsWith("Bearer ")) {
|
|
79
|
+
throw new Error("Missing Bearer token.");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return header.substring(7);
|
|
83
|
+
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
# Route Design Rules
|
|
90
|
+
|
|
91
|
+
A route should only perform orchestration.
|
|
92
|
+
|
|
93
|
+
Typical flow:
|
|
94
|
+
|
|
95
|
+
1. Extract token.
|
|
96
|
+
2. Authenticate identity.
|
|
97
|
+
3. Load required resources.
|
|
98
|
+
4. Perform authorization (admin routes only).
|
|
99
|
+
5. Execute business operation.
|
|
100
|
+
6. Return HTTP response.
|
|
101
|
+
|
|
102
|
+
Business rules belong inside the Server Kernel, not inside the route.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
# Example
|
|
107
|
+
|
|
108
|
+
## User Route
|
|
109
|
+
|
|
110
|
+
```js
|
|
111
|
+
const token = getToken(req);
|
|
112
|
+
|
|
113
|
+
const user = await kernel.auth.authenticate(token);
|
|
114
|
+
|
|
115
|
+
const communication =
|
|
116
|
+
await kernel.communication.create({
|
|
117
|
+
|
|
118
|
+
userId: user.id,
|
|
119
|
+
...req.body
|
|
120
|
+
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
res.status(201).json(communication);
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Admin Route
|
|
129
|
+
|
|
130
|
+
```js
|
|
131
|
+
const token = getToken(req);
|
|
132
|
+
|
|
133
|
+
const admin = await kernel.auth.authenticate(token);
|
|
134
|
+
|
|
135
|
+
const id = await kernel.library.slugToId(req.params.slug);
|
|
136
|
+
|
|
137
|
+
const library = await kernel.library.get(id);
|
|
138
|
+
|
|
139
|
+
await kernel.policy.require(
|
|
140
|
+
admin,
|
|
141
|
+
library.course.id,
|
|
142
|
+
"library"
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
res.json(library);
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Philosophy
|
|
151
|
+
|
|
152
|
+
The Server Kernel is the only API between the HTTP layer and the application.
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
HTTP Route
|
|
156
|
+
│
|
|
157
|
+
▼
|
|
158
|
+
Server Kernel API
|
|
159
|
+
│
|
|
160
|
+
▼
|
|
161
|
+
Business Logic
|
|
162
|
+
│
|
|
163
|
+
▼
|
|
164
|
+
Prisma / Database
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Routes should remain thin, predictable orchestration layers with no business logic and no direct database access.
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
# Authentication
|
|
171
|
+
|
|
172
|
+
Authentication is a two-step process.
|
|
173
|
+
|
|
174
|
+
## Login
|
|
175
|
+
|
|
176
|
+
Authenticate credentials.
|
|
177
|
+
|
|
178
|
+
Returns a JWT.
|
|
179
|
+
|
|
180
|
+
```js
|
|
181
|
+
const token = await kernel.user.login(
|
|
182
|
+
email,
|
|
183
|
+
password
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
const token = await kernel.admin.login(
|
|
187
|
+
email,
|
|
188
|
+
password
|
|
189
|
+
);
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Returns
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
Promise<string>
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Authenticate
|
|
201
|
+
|
|
202
|
+
Authenticate a JWT.
|
|
203
|
+
|
|
204
|
+
Returns the authenticated identity.
|
|
205
|
+
|
|
206
|
+
```js
|
|
207
|
+
const user = await kernel.auth.authenticate(token);
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Returns
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
Promise<User | Admin>
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Example
|
|
217
|
+
|
|
218
|
+
```js
|
|
219
|
+
const token = req.headers.authorization.replace(
|
|
220
|
+
"Bearer ",
|
|
221
|
+
""
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
const user = await kernel.auth.authenticate(token);
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
# Authorization
|
|
230
|
+
|
|
231
|
+
Authorization is performed only after authentication.
|
|
232
|
+
|
|
233
|
+
```js
|
|
234
|
+
await kernel.policy.require(
|
|
235
|
+
admin,
|
|
236
|
+
courseId,
|
|
237
|
+
"library"
|
|
238
|
+
);
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Returns
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
Promise<AdminCoursePolicy>
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Throws if access is denied.
|
|
248
|
+
|
|
249
|
+
Typical admin route
|
|
250
|
+
|
|
251
|
+
```js
|
|
252
|
+
const token = req.headers.authorization.replace(
|
|
253
|
+
"Bearer ",
|
|
254
|
+
""
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
const admin = await kernel.auth.authenticate(token);
|
|
258
|
+
|
|
259
|
+
await kernel.policy.require(
|
|
260
|
+
admin,
|
|
261
|
+
library.course.id,
|
|
262
|
+
"library"
|
|
263
|
+
);
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
# Resources
|
|
269
|
+
|
|
270
|
+
Every resource follows the same pattern.
|
|
271
|
+
|
|
272
|
+
```
|
|
273
|
+
list()
|
|
274
|
+
get()
|
|
275
|
+
|
|
276
|
+
create()
|
|
277
|
+
update()
|
|
278
|
+
delete()
|
|
279
|
+
|
|
280
|
+
slugToId()
|
|
281
|
+
idToSlug()
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Resources may expose additional business methods.
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
# User
|
|
289
|
+
|
|
290
|
+
## Queries
|
|
291
|
+
|
|
292
|
+
```js
|
|
293
|
+
await kernel.user.list();
|
|
294
|
+
|
|
295
|
+
await kernel.user.get(id);
|
|
296
|
+
|
|
297
|
+
await kernel.user.getByEmail(email);
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## Authentication
|
|
301
|
+
|
|
302
|
+
```js
|
|
303
|
+
await kernel.user.register(data);
|
|
304
|
+
|
|
305
|
+
await kernel.user.login(email, password);
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Returns
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
Promise<string> // JWT
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## CRUD
|
|
315
|
+
|
|
316
|
+
```js
|
|
317
|
+
await kernel.user.update(id, data);
|
|
318
|
+
|
|
319
|
+
await kernel.user.delete(id);
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
# Admin
|
|
325
|
+
|
|
326
|
+
## Queries
|
|
327
|
+
|
|
328
|
+
```js
|
|
329
|
+
await kernel.admin.list(filters);
|
|
330
|
+
|
|
331
|
+
await kernel.admin.get(id);
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
## Authentication
|
|
335
|
+
|
|
336
|
+
```js
|
|
337
|
+
await kernel.admin.login(email, password);
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Returns
|
|
341
|
+
|
|
342
|
+
```ts
|
|
343
|
+
Promise<string> // JWT
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## CRUD
|
|
347
|
+
|
|
348
|
+
```js
|
|
349
|
+
await kernel.admin.create(data);
|
|
350
|
+
|
|
351
|
+
await kernel.admin.update(id, data);
|
|
352
|
+
|
|
353
|
+
await kernel.admin.delete(id);
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
358
|
+
# Course
|
|
359
|
+
|
|
360
|
+
## Queries
|
|
361
|
+
|
|
362
|
+
```js
|
|
363
|
+
await kernel.course.list(filters);
|
|
364
|
+
|
|
365
|
+
await kernel.course.get(id);
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
Supported filters
|
|
369
|
+
|
|
370
|
+
```js
|
|
371
|
+
{
|
|
372
|
+
access
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
## CRUD
|
|
377
|
+
|
|
378
|
+
```js
|
|
379
|
+
await kernel.course.create(data);
|
|
380
|
+
|
|
381
|
+
await kernel.course.update(id, data);
|
|
382
|
+
|
|
383
|
+
await kernel.course.delete(id);
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
## Utilities
|
|
387
|
+
|
|
388
|
+
```js
|
|
389
|
+
await kernel.course.slugToId(slug);
|
|
390
|
+
|
|
391
|
+
await kernel.course.idToSlug(id);
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
# Library
|
|
397
|
+
|
|
398
|
+
## Queries
|
|
399
|
+
|
|
400
|
+
```js
|
|
401
|
+
await kernel.library.list(filters);
|
|
402
|
+
|
|
403
|
+
await kernel.library.get(id);
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
Supported filters
|
|
407
|
+
|
|
408
|
+
```js
|
|
409
|
+
{
|
|
410
|
+
course,
|
|
411
|
+
access,
|
|
412
|
+
type
|
|
413
|
+
}
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
## CRUD
|
|
417
|
+
|
|
418
|
+
```js
|
|
419
|
+
await kernel.library.create(data);
|
|
420
|
+
|
|
421
|
+
await kernel.library.update(id, data);
|
|
422
|
+
|
|
423
|
+
await kernel.library.delete(id);
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
## Utilities
|
|
427
|
+
|
|
428
|
+
```js
|
|
429
|
+
await kernel.library.slugToId(slug);
|
|
430
|
+
|
|
431
|
+
await kernel.library.idToSlug(id);
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
---
|
|
435
|
+
|
|
436
|
+
# Communication
|
|
437
|
+
|
|
438
|
+
## Queries
|
|
439
|
+
|
|
440
|
+
```js
|
|
441
|
+
await kernel.communication.list(filters);
|
|
442
|
+
|
|
443
|
+
await kernel.communication.get(id);
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
Supported filters
|
|
447
|
+
|
|
448
|
+
```js
|
|
449
|
+
{
|
|
450
|
+
userId,
|
|
451
|
+
libraryId
|
|
452
|
+
}
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
## CRUD
|
|
456
|
+
|
|
457
|
+
```js
|
|
458
|
+
await kernel.communication.create(data);
|
|
459
|
+
|
|
460
|
+
await kernel.communication.update(id, data);
|
|
461
|
+
|
|
462
|
+
await kernel.communication.delete(id);
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
## Special
|
|
466
|
+
|
|
467
|
+
```js
|
|
468
|
+
await kernel.communication.listUnanswered(admin);
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
Returns all unanswered communications for courses the admin is authorized to manage.
|
|
472
|
+
|
|
473
|
+
---
|
|
474
|
+
|
|
475
|
+
# Subscription
|
|
476
|
+
|
|
477
|
+
## Queries
|
|
478
|
+
|
|
479
|
+
```js
|
|
480
|
+
await kernel.subscription.list(filters);
|
|
481
|
+
|
|
482
|
+
await kernel.subscription.get(id);
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
Supported filters
|
|
486
|
+
|
|
487
|
+
```js
|
|
488
|
+
{
|
|
489
|
+
userId,
|
|
490
|
+
courseId
|
|
491
|
+
}
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
## CRUD
|
|
495
|
+
|
|
496
|
+
```js
|
|
497
|
+
await kernel.subscription.create(data);
|
|
498
|
+
|
|
499
|
+
await kernel.subscription.update(id, data);
|
|
500
|
+
|
|
501
|
+
await kernel.subscription.delete(id);
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
## Authorization
|
|
505
|
+
|
|
506
|
+
```js
|
|
507
|
+
await kernel.subscription.authorize(
|
|
508
|
+
userId,
|
|
509
|
+
courseId
|
|
510
|
+
);
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
Returns the active subscription.
|
|
514
|
+
|
|
515
|
+
Throws if no active subscription exists.
|
|
516
|
+
|
|
517
|
+
---
|
|
518
|
+
|
|
519
|
+
# Design Rules
|
|
520
|
+
|
|
521
|
+
1. Routes only communicate with the Server Kernel.
|
|
522
|
+
|
|
523
|
+
2. Routes never use Prisma.
|
|
524
|
+
|
|
525
|
+
3. Routes never create JWTs.
|
|
526
|
+
|
|
527
|
+
4. Login returns JWT tokens.
|
|
528
|
+
|
|
529
|
+
5. `auth.authenticate()` converts JWT → User/Admin.
|
|
530
|
+
|
|
531
|
+
6. `policy.require()` authorizes an authenticated admin.
|
|
532
|
+
|
|
533
|
+
7. Business rules belong inside resources.
|
|
534
|
+
|
|
535
|
+
8. Routes should remain thin orchestration layers.
|
|
536
|
+
|
|
537
|
+
---
|
|
538
|
+
|
|
539
|
+
# Example
|
|
540
|
+
|
|
541
|
+
## Public user route
|
|
542
|
+
|
|
543
|
+
```js
|
|
544
|
+
const token = req.headers.authorization.replace(
|
|
545
|
+
"Bearer ",
|
|
546
|
+
""
|
|
547
|
+
);
|
|
548
|
+
|
|
549
|
+
const user = await kernel.auth.authenticate(token);
|
|
550
|
+
|
|
551
|
+
const item = await kernel.communication.create({
|
|
552
|
+
...req.body,
|
|
553
|
+
userId: user.id
|
|
554
|
+
});
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
---
|
|
558
|
+
|
|
559
|
+
## Admin route
|
|
560
|
+
|
|
561
|
+
```js
|
|
562
|
+
const token = req.headers.authorization.replace(
|
|
563
|
+
"Bearer ",
|
|
564
|
+
""
|
|
565
|
+
);
|
|
566
|
+
|
|
567
|
+
const admin = await kernel.auth.authenticate(token);
|
|
568
|
+
|
|
569
|
+
const id = await kernel.library.slugToId(req.params.slug);
|
|
570
|
+
|
|
571
|
+
const library = await kernel.library.get(id);
|
|
572
|
+
|
|
573
|
+
await kernel.policy.require(
|
|
574
|
+
admin,
|
|
575
|
+
library.course.id,
|
|
576
|
+
"library"
|
|
577
|
+
);
|
|
578
|
+
|
|
579
|
+
res.json(library);
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
---
|
|
583
|
+
|
|
584
|
+
This document is the canonical contract between the HTTP routes, tests, and the Taleem Server Kernel.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import bcrypt from "bcrypt";
|
|
2
|
+
|
|
3
|
+
export default class Admin {
|
|
4
|
+
constructor(kernel) { this.kernel = kernel; }
|
|
5
|
+
|
|
6
|
+
async list(filters = {}) {
|
|
7
|
+
const where = {};
|
|
8
|
+
if (filters.isActive !== undefined) where.isActive = filters.isActive;
|
|
9
|
+
return this.kernel.db.admin.findMany({ where });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async get(email) {
|
|
13
|
+
return this.kernel.db.admin.findUnique({
|
|
14
|
+
where: { email }
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async login(email, password) {
|
|
19
|
+
const admin = await this.kernel.db.admin.findUnique({
|
|
20
|
+
where: { email }
|
|
21
|
+
});
|
|
22
|
+
if (!admin) throw new Error(`Admin.login(): Admin '${email}' not found.`);
|
|
23
|
+
if (!admin.isActive) throw new Error(`Admin.login(): Admin '${email}' is inactive.`);
|
|
24
|
+
|
|
25
|
+
const ok = await bcrypt.compare(password, admin.password);
|
|
26
|
+
if (!ok) throw new Error(`Admin.login(): Invalid password.`);
|
|
27
|
+
|
|
28
|
+
return this.kernel.auth.createAdminToken(admin);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async create(data) {
|
|
32
|
+
if (data.password) data.password = await bcrypt.hash(data.password, 10);
|
|
33
|
+
return this.kernel.db.admin.create({ data });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async update(email, data) {
|
|
37
|
+
if (data.password) data.password = await bcrypt.hash(data.password, 10);
|
|
38
|
+
return this.kernel.db.admin.update({
|
|
39
|
+
where: { email },
|
|
40
|
+
data
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async delete(email) {
|
|
45
|
+
return this.kernel.db.admin.delete({
|
|
46
|
+
where: { email }
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async isAdmin(email, courseSlug) {
|
|
51
|
+
const admin = await this.get(email);
|
|
52
|
+
if (!admin) return false;
|
|
53
|
+
|
|
54
|
+
const courseSlugs = JSON.parse(admin.courseSlugs || "[]");
|
|
55
|
+
return courseSlugs.includes(courseSlug);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// src/serverKernel/modules/Audio.js
|
|
2
|
+
|
|
3
|
+
export default class Audio {
|
|
4
|
+
|
|
5
|
+
constructor(kernel) {
|
|
6
|
+
this.kernel = kernel;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// --------------------------------------------------
|
|
10
|
+
// Queries
|
|
11
|
+
// --------------------------------------------------
|
|
12
|
+
|
|
13
|
+
async list() {
|
|
14
|
+
|
|
15
|
+
return this.kernel.db.audio.findMany({
|
|
16
|
+
|
|
17
|
+
orderBy: {
|
|
18
|
+
createdAt: "desc"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async get(id) {
|
|
26
|
+
|
|
27
|
+
return this.kernel.db.audio.findUnique({
|
|
28
|
+
|
|
29
|
+
where: { id }
|
|
30
|
+
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// --------------------------------------------------
|
|
36
|
+
// Create
|
|
37
|
+
// --------------------------------------------------
|
|
38
|
+
|
|
39
|
+
async create(data) {
|
|
40
|
+
|
|
41
|
+
return this.kernel.db.audio.create({
|
|
42
|
+
|
|
43
|
+
data
|
|
44
|
+
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
}
|