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/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,8 @@
1
+
2
+ export default Object.freeze({
3
+
4
+ LIBRARY: "library",
5
+ COMMUNICATION: "communication",
6
+ SUBSCRIPTION: "subscription"
7
+
8
+ });
@@ -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
+ }