taleem-kernel 1.2.0 → 1.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taleem-kernel",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Taleem data and business-logic kernel. HTTP is only an adapter.",
5
5
  "type": "module",
6
6
  "main": "./src/ServerKernel.js",
@@ -11,7 +11,7 @@
11
11
  "taleem-kernel": "./bin/cli.js"
12
12
  },
13
13
  "scripts": {
14
- "test": "vitest run"
14
+ "test": "vitest run --no-file-parallelism"
15
15
  },
16
16
  "files": [
17
17
  "src",
package/prisma/dev.db CHANGED
Binary file
@@ -0,0 +1,24 @@
1
+ -- RedefineTables
2
+ PRAGMA defer_foreign_keys=ON;
3
+ PRAGMA foreign_keys=OFF;
4
+ CREATE TABLE "new_Library" (
5
+ "slug" TEXT NOT NULL PRIMARY KEY,
6
+ "title" TEXT NOT NULL,
7
+ "description" TEXT,
8
+ "thumbnail" TEXT,
9
+ "type" TEXT NOT NULL,
10
+ "status" TEXT NOT NULL DEFAULT 'DRAFT',
11
+ "body" TEXT,
12
+ "courseSlug" TEXT NOT NULL,
13
+ "groupSlug" TEXT NOT NULL,
14
+ "sortOrder" INTEGER NOT NULL DEFAULT 0,
15
+ "allowCommunication" BOOLEAN NOT NULL DEFAULT true,
16
+ "meta" TEXT,
17
+ "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
18
+ "updatedAt" DATETIME NOT NULL
19
+ );
20
+ INSERT INTO "new_Library" ("allowCommunication", "body", "courseSlug", "createdAt", "description", "groupSlug", "meta", "slug", "sortOrder", "thumbnail", "title", "type", "updatedAt") SELECT "allowCommunication", "body", "courseSlug", "createdAt", "description", "groupSlug", "meta", "slug", "sortOrder", "thumbnail", "title", "type", "updatedAt" FROM "Library";
21
+ DROP TABLE "Library";
22
+ ALTER TABLE "new_Library" RENAME TO "Library";
23
+ PRAGMA foreign_keys=ON;
24
+ PRAGMA defer_foreign_keys=OFF;
@@ -13,6 +13,12 @@ enum ContentType {
13
13
  MCQ
14
14
  }
15
15
 
16
+ enum LibraryStatus {
17
+ DRAFT
18
+ PUBLISHED
19
+ ARCHIVED
20
+ }
21
+
16
22
  enum CourseAccess {
17
23
  OPEN
18
24
  MEMBERS
@@ -25,12 +31,12 @@ enum AdminRole {
25
31
  }
26
32
 
27
33
  model Group {
28
- id Int @id @default(autoincrement())
29
- courseSlug String
30
- slug String
31
- title String
32
- thumbnail String?
33
- items Json
34
+ id Int @id @default(autoincrement())
35
+ courseSlug String
36
+ slug String
37
+ title String
38
+ thumbnail String?
39
+ items Json
34
40
 
35
41
  @@unique([courseSlug, slug])
36
42
  @@index([courseSlug])
@@ -46,15 +52,15 @@ model Course {
46
52
  }
47
53
 
48
54
  model User {
49
- id Int @id @default(autoincrement())
50
- email String @unique
51
- password String
52
- name String?
53
- role String @default("student")
54
- resource String?
55
- createdAt DateTime @default(now())
56
- subscriptions Subscription[]
57
- communications Communication[]
55
+ id Int @id @default(autoincrement())
56
+ email String @unique
57
+ password String
58
+ name String?
59
+ role String @default("student")
60
+ resource String?
61
+ createdAt DateTime @default(now())
62
+ subscriptions Subscription[]
63
+ communications Communication[]
58
64
  }
59
65
 
60
66
  model Admin {
@@ -69,19 +75,20 @@ model Admin {
69
75
  }
70
76
 
71
77
  model Library {
72
- slug String @id
78
+ slug String @id
73
79
  title String
74
80
  description String?
75
81
  thumbnail String?
76
82
  type ContentType
83
+ status LibraryStatus @default(DRAFT)
77
84
  body String?
78
85
  courseSlug String
79
86
  groupSlug String
80
- sortOrder Int @default(0)
81
- allowCommunication Boolean @default(true)
87
+ sortOrder Int @default(0)
88
+ allowCommunication Boolean @default(true)
82
89
  meta String?
83
- createdAt DateTime @default(now())
84
- updatedAt DateTime @updatedAt
90
+ createdAt DateTime @default(now())
91
+ updatedAt DateTime @updatedAt
85
92
  }
86
93
 
87
94
  model Subscription {
@@ -110,50 +117,46 @@ model Communication {
110
117
  readAt DateTime?
111
118
  createdAt DateTime @default(now())
112
119
  updatedAt DateTime @updatedAt
113
- user User @relation(fields: [userId], references: [id])
120
+ user User @relation(fields: [userId], references: [id])
114
121
 
115
122
  @@index([userId])
116
123
  @@index([librarySlug])
117
124
  }
118
- model Svg {
119
125
 
120
- slug String @id
126
+ model Svg {
127
+ slug String @id
121
128
 
122
- title String
129
+ title String
123
130
 
124
- body String
131
+ body String
125
132
 
126
- tags String @default("[]")
133
+ tags String @default("[]")
127
134
 
128
135
  createdAt DateTime @default(now())
129
136
 
130
137
  updatedAt DateTime @updatedAt
131
-
132
138
  }
133
139
 
134
140
  model Image {
141
+ slug String @id
135
142
 
136
- slug String @id
137
-
138
- title String
143
+ title String
139
144
 
140
- tags String @default("[]")
145
+ tags String @default("[]")
141
146
 
142
147
  createdAt DateTime @default(now())
143
148
 
144
149
  updatedAt DateTime @updatedAt
145
-
146
150
  }
147
- model Audio {
148
151
 
149
- slug String @id
152
+ model Audio {
153
+ slug String @id
150
154
 
151
- title String
155
+ title String
152
156
 
153
- tags String @default("[]")
157
+ tags String @default("[]")
154
158
 
155
159
  createdAt DateTime @default(now())
156
160
 
157
161
  updatedAt DateTime @updatedAt
158
-
159
- }
162
+ }
package/readme.md CHANGED
@@ -1,97 +1,505 @@
1
- ## Taleem Kernel API
1
+
2
+
3
+ # Taleem Kernel API
2
4
 
3
5
  ```js
4
6
  import kernel from "taleem-kernel";
5
- ```
7
+ ````
8
+
9
+ `taleem-kernel` is the shared DB/domain layer for Taleem applications.
6
10
 
7
- `taleem-kernel` is the shared DB/domain layer for Taleem applications. It provides Prisma/SQLite access and Taleem-specific modules.
11
+ It owns the canonical Taleem Prisma/SQLite schema and provides the domain modules used by Taleem Server, Taleem Studio, and other Taleem applications.
8
12
 
9
- ### Core
13
+ The kernel is intentionally small: modules expose operations that make sense for Taleem rather than blindly exposing Prisma CRUD.
14
+
15
+ ---
16
+
17
+ ## Core
10
18
 
11
19
  ```js
12
- kernel.db // Prisma client
13
- kernel.config // configuration
14
- kernel.auth // authentication/JWT
20
+ kernel.db
21
+
22
+ kernel.config
23
+
24
+ kernel.jwt
25
+
15
26
  kernel.communicationPolicy
27
+
16
28
  kernel.shutdown()
17
29
  ```
18
30
 
19
- ### Modules
31
+ `kernel.jwt` is the low-level JWT utility used by the User and Admin modules.
32
+
33
+ Applications normally should not create or verify application identity tokens directly through `kernel.jwt`. Instead, use the authentication methods on the appropriate domain module.
34
+
35
+ ---
36
+
37
+ ## Modules
20
38
 
21
39
  ```js
22
40
  kernel.user
41
+
23
42
  kernel.admin
43
+
24
44
  kernel.course
45
+
25
46
  kernel.library
47
+
26
48
  kernel.communication
49
+
27
50
  kernel.subscription
51
+
28
52
  kernel.svg
53
+
29
54
  kernel.image
55
+
30
56
  kernel.audio
31
57
  ```
32
58
 
33
- Each module provides simple DB/domain operations such as:
59
+ ---
60
+
61
+ ## User
62
+
63
+ Users are the normal Taleem application users.
64
+
65
+ User registration and authentication are owned directly by the User module.
66
+
67
+ ### User API
34
68
 
35
69
  ```js
36
- list()
37
- get(...)
38
- create(data)
39
- update(id, data)
40
- delete(id)
70
+ kernel.user.list()
71
+
72
+ kernel.user.get(id)
73
+
74
+ kernel.user.getByEmail(email)
75
+
76
+ kernel.user.emailToId(email)
77
+
78
+ kernel.user.register(data)
79
+
80
+ kernel.user.login(email, password)
81
+
82
+ kernel.user.createToken(user)
83
+
84
+ kernel.user.authenticate(token)
85
+
86
+ kernel.user.update(id, data)
87
+
88
+ kernel.user.delete(id)
41
89
  ```
42
90
 
43
- with additional module-specific methods such as:
91
+ Registration creates a User and hashes the supplied password:
44
92
 
45
93
  ```js
46
- kernel.user.login()
47
- kernel.user.register()
48
- kernel.admin.isAdmin()
49
- kernel.library.listByCourse()
50
- kernel.communication.listUnanswered()
51
- kernel.communication.countUserOpenQuestions()
94
+ const user = await kernel.user.register({
95
+ email: "student@example.com",
96
+ password: "12345678"
97
+ });
52
98
  ```
53
99
 
54
- ### Schema
100
+ Login returns a User JWT:
55
101
 
56
- The kernel owns the **canonical Taleem Prisma/SQLite schema**. Applications using Taleem DB should use the kernel rather than maintaining their own DB layer.
102
+ ```js
103
+ const token = await kernel.user.login(
104
+ "student@example.com",
105
+ "12345678"
106
+ );
107
+ ```
108
+
109
+ The token can then be authenticated:
110
+
111
+ ```js
112
+ const user = await kernel.user.authenticate(token);
113
+ ```
114
+
115
+ User authentication accepts only tokens with:
116
+
117
+ ```js
118
+ {
119
+ type: "user"
120
+ }
121
+ ```
122
+
123
+ The User module creates its own tokens using the kernel JWT utility.
124
+
125
+ ---
126
+
127
+ ## Admin
128
+
129
+ Admins are independent of Course structure.
130
+
131
+ An Admin may be assigned one or more course slugs through `courseSlugs`.
132
+
133
+ Admin accounts are provisioned separately from normal User registration.
134
+
135
+ ### Admin API
136
+
137
+ ```js
138
+ kernel.admin.list()
139
+
140
+ kernel.admin.get(email)
141
+
142
+ kernel.admin.login(email, password)
143
+
144
+ kernel.admin.createToken(admin)
145
+
146
+ kernel.admin.authenticate(token)
147
+
148
+ kernel.admin.create(data)
149
+
150
+ kernel.admin.update(email, data)
151
+
152
+ kernel.admin.delete(email)
153
+
154
+ kernel.admin.isAdmin(email, courseSlug)
155
+ ```
156
+
157
+ Admin login returns an Admin JWT:
158
+
159
+ ```js
160
+ const token = await kernel.admin.login(
161
+ "admin@example.com",
162
+ "12345678"
163
+ );
164
+ ```
165
+
166
+ The token can then be authenticated:
167
+
168
+ ```js
169
+ const admin = await kernel.admin.authenticate(token);
170
+ ```
171
+
172
+ Admin authentication accepts only tokens with:
173
+
174
+ ```js
175
+ {
176
+ type: "admin"
177
+ }
178
+ ```
179
+
180
+ Admin authorization is handled separately:
181
+
182
+ ```js
183
+ const allowed = await kernel.admin.isAdmin(
184
+ admin.email,
185
+ courseSlug
186
+ );
187
+ ```
188
+
189
+ Authentication and authorization are separate concerns.
190
+
191
+ An authenticated Admin is not automatically authorized for every Course.
192
+
193
+ ---
194
+
195
+ ## JWT
196
+
197
+ JWT is the low-level token utility used by the User and Admin modules.
198
+
199
+ ```js
200
+ kernel.jwt.sign(payload)
201
+
202
+ kernel.jwt.verify(token)
203
+ ```
204
+
205
+ User and Admin own their respective identity tokens.
206
+
207
+ Applications should normally use:
208
+
209
+ ```js
210
+ kernel.user.login(...)
211
+ kernel.user.authenticate(...)
212
+
213
+ kernel.admin.login(...)
214
+ kernel.admin.authenticate(...)
215
+ ```
216
+
217
+ rather than calling `kernel.jwt` directly.
218
+
219
+ The important architectural rule is:
220
+
221
+ ```text
222
+ JWT
223
+
224
+ ├── User
225
+ │ ├── login()
226
+ │ ├── createToken()
227
+ │ └── authenticate()
228
+
229
+ └── Admin
230
+ ├── login()
231
+ ├── createToken()
232
+ └── authenticate()
233
+ ```
234
+
235
+ There is no shared identity/authentication module routing between User and Admin.
236
+
237
+ ---
238
+
239
+ ## Course
240
+
241
+ Course is **not a CRUD entity**.
242
+
243
+ A Course originates as a static course artifact (`course.json`). The artifact is the source of truth and is materialized into the database by the kernel.
244
+
245
+ Internally, a Course is represented by two tables:
246
+
247
+ ```text
248
+ Course
249
+ |
250
+ +-- Group
251
+ |
252
+ +-- items[] // library slugs
253
+ ```
254
+
255
+ There are intentionally no Prisma relationships between Course, Group, and Library. Their relationship is maintained through Taleem slugs.
256
+
257
+ ### Course API
57
258
 
58
259
  ```js
59
- kernel.auth.authenticate(token)
60
- kernel.user.login(email, password)
61
260
  kernel.course.list()
261
+
262
+ kernel.course.get(courseSlug)
263
+
264
+ kernel.course.getGroups(courseSlug)
265
+
266
+ kernel.course.getGroup(courseSlug, groupSlug)
267
+
268
+ kernel.course.getGroupItems(courseSlug, groupSlug)
269
+
270
+ kernel.course.seed(course)
271
+
272
+ kernel.course.wipe()
273
+ ```
274
+
275
+ `seed()` materializes a complete course artifact:
276
+
277
+ ```js
278
+ await kernel.course.seed(course);
279
+ ```
280
+
281
+ `wipe()` removes the materialized Course and Group data before rebuilding from the source artifacts:
282
+
283
+ ```js
284
+ await kernel.course.wipe();
285
+ ```
286
+
287
+ There are deliberately no:
288
+
289
+ ```js
290
+ course.create()
291
+
292
+ course.update()
293
+
294
+ course.delete()
295
+ ```
296
+
297
+ because a Course is an artifact, not an independently edited CRUD record.
298
+
299
+ ---
300
+
301
+ ## Library
302
+
303
+ Library contains the actual authored Taleem content.
304
+
305
+ Library items belong logically to a Course and Group through string slugs:
306
+
307
+ ```text
308
+ Library.courseSlug → Course.slug
309
+
310
+ Library.groupSlug → Group.slug
311
+
312
+ Library.slug → Group.items[]
313
+ ```
314
+
315
+ These are application-level relationships, not Prisma foreign keys.
316
+
317
+ ### Library API
318
+
319
+ ```js
320
+ kernel.library.list()
321
+
322
+ kernel.library.listByCourse(courseSlug)
323
+
324
+ kernel.library.listByGroup(courseSlug, groupSlug)
325
+
62
326
  kernel.library.get(slug)
63
- kernel.svg.get(slug)
327
+
328
+ kernel.library.create(data)
329
+
330
+ kernel.library.update(slug, data)
331
+
332
+ kernel.library.delete(slug)
333
+ ```
334
+
335
+ Filtering:
336
+
337
+ ```js
338
+ kernel.library.list({
339
+ type,
340
+ courseSlug,
341
+ groupSlug
342
+ });
343
+ ```
344
+
345
+ `list()` returns the library metadata needed by applications without requiring the full content body.
346
+
347
+ Library items also have a lifecycle status:
348
+
349
+ ```text
350
+ DRAFT
351
+ PUBLISHED
352
+ ARCHIVED
353
+ ```
354
+
355
+ The default status for a new Library item is:
356
+
357
+ ```text
358
+ DRAFT
359
+ ```
360
+
361
+ ---
362
+
363
+ ## Assets
364
+
365
+ SVG, Image, and Audio are independent reusable assets.
366
+
367
+ They are not tied to Courses, Groups, or Library records at the database level.
368
+
369
+ Their metadata is intentionally small:
370
+
371
+ ```text
372
+ slug
373
+
374
+ title
375
+
376
+ tags
377
+ ```
378
+
379
+ Assets can be discovered through tags and referenced by content where appropriate.
380
+
381
+ ```js
382
+ kernel.svg
383
+
384
+ kernel.image
385
+
386
+ kernel.audio
387
+ ```
388
+
389
+ These modules provide the normal asset operations such as:
390
+
391
+ ```js
392
+ list()
393
+
394
+ get()
395
+
396
+ create()
397
+
398
+ update()
399
+
400
+ delete()
64
401
  ```
65
402
 
66
- `kernel.shutdown()` disconnects Prisma when the application exits.
403
+ ---
404
+
405
+ ## Schema
406
+
407
+ The kernel owns the **canonical Taleem Prisma/SQLite schema**.
408
+
409
+ Applications using the Taleem database should use the kernel rather than maintaining their own DB/domain layer.
410
+
411
+ The important architectural rule is:
412
+
413
+ ```text
414
+ Kernel
415
+
416
+
417
+
418
+ Canonical schema + Taleem domain operations
419
+
420
+
421
+
422
+ Applications
423
+ ```
424
+
425
+ Applications should not bypass the kernel for normal Taleem DB operations.
426
+
427
+ ---
67
428
 
68
429
  ## Schema Management
69
430
 
70
- The kernel provides two CLI commands for maintaining schema compatibility:
431
+ The kernel provides CLI commands for maintaining schema compatibility:
71
432
 
72
433
  ```bash
73
434
  npx taleem-kernel schema-check
435
+
74
436
  npx taleem-kernel schema-update
75
437
  ```
76
438
 
77
- `schema-check` compares the application's `prisma/schema.prisma` with the canonical schema shipped with `taleem-kernel`.
439
+ `schema-check` compares the application's Prisma schema with the canonical schema shipped with `taleem-kernel`.
78
440
 
79
- `schema-update` copies the kernel's canonical schema into the application's `prisma/schema.prisma`.
441
+ `schema-update` copies the kernel's canonical schema into the application's Prisma schema.
80
442
 
81
443
  After updating the schema, run the application's normal Prisma migration/generation process as required.
82
444
 
445
+ ---
446
+
83
447
  ## Server Dependencies
84
448
 
85
449
  `taleem-server` should use `taleem-kernel` for all Taleem database access.
86
450
 
87
- The server provides the HTTP/API layer on top of the kernel:
451
+ The server provides the HTTP/API layer on top of the kernel.
88
452
 
89
- ```text
90
- "@prisma/client": "^6.19.3",
91
- "bcrypt": "^6.0.0",
92
- "dotenv": "^17.4.2",
93
- "jsonwebtoken": "^9.0.3",
94
- "zod": "^4.4.3"
453
+ The server should **not maintain its own Taleem Prisma models or duplicate DB modules**.
454
+
455
+ Authentication at the server layer should use the appropriate Kernel identity module:
456
+
457
+ ```js
458
+ const user = await kernel.user.authenticate(token);
95
459
  ```
96
460
 
97
- The server should **not maintain its own Taleem Prisma models or DB modules**.
461
+ or:
462
+
463
+ ```js
464
+ const admin = await kernel.admin.authenticate(token);
465
+ ```
466
+
467
+ Course-level Admin authorization can then be checked separately:
468
+
469
+ ```js
470
+ const allowed = await kernel.admin.isAdmin(
471
+ admin.email,
472
+ courseSlug
473
+ );
474
+ ```
475
+
476
+ ---
477
+
478
+ ## Design Principles
479
+
480
+ The kernel follows a few important Taleem-specific rules:
481
+
482
+ 1. **Course artifacts are the source of truth.**
483
+
484
+ 2. **Course is not generic CRUD.** It is materialized from the course artifact.
485
+
486
+ 3. **Course and Group are internally separate tables but one domain concept.**
487
+
488
+ 4. **Course, Group, and Library are connected by slugs, not Prisma relationships.**
489
+
490
+ 5. **Relational integrity is enforced by the Taleem build/application layer.**
491
+
492
+ 6. **Library contains authored content; Course defines the expected structure and item slugs.**
493
+
494
+ 7. **Library content has an explicit lifecycle: DRAFT, PUBLISHED, and ARCHIVED.**
495
+
496
+ 8. **SVG, Image, and Audio are independent reusable assets.**
497
+
498
+ 9. **User and Admin authentication are separate domain concerns.**
499
+
500
+ 10. **JWT is a low-level utility; User and Admin own their identity token lifecycle.**
501
+
502
+ 11. **The kernel exposes domain operations only where they represent real Taleem behavior.**
503
+
504
+ The goal is a small, stable kernel that owns the Taleem contract without pretending to be a generic CRUD framework.
505
+
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { PrismaClient } from "@prisma/client";
4
4
  import Config from "./Config.js";
5
- import Auth from "./Auth.js";
5
+ // import Auth from "./Auth.js";
6
6
  import CommunicationPolicy from "./CommunicationPolicy.js";
7
7
  import User from "./modules/User.js";
8
8
  import Admin from "./modules/Admin.js";
@@ -18,7 +18,7 @@ class ServerKernel {
18
18
  constructor() {
19
19
  this.config = new Config();
20
20
  this.db = new PrismaClient();
21
- this.auth = new Auth(this);
21
+ // this.auth = new Auth(this);
22
22
  this.communicationPolicy = new CommunicationPolicy(this);
23
23
  this.user = new User(this);
24
24
  this.admin = new Admin(this);
@@ -1,9 +1,13 @@
1
- // src/serverKernel/modules/Admin.js
1
+ // /home/bilal-tariq/00--TALEEM/taleem-kernel/src/modules/Admin.js
2
2
 
3
3
  import bcrypt from "bcrypt";
4
+ import JWT from "../utils/JWT.js";
4
5
 
5
6
  export default class Admin {
6
- constructor(kernel) { this.kernel = kernel; }
7
+ constructor(kernel) {
8
+ this.kernel = kernel;
9
+ this.jwt = new JWT(kernel);
10
+ }
7
11
 
8
12
  async list(filters = {}) {
9
13
  const where = {};
@@ -15,37 +19,95 @@ export default class Admin {
15
19
  return this.kernel.db.admin.findUnique({ where: { email } });
16
20
  }
17
21
 
22
+ // Authentication
23
+
18
24
  async login(email, password) {
19
25
  const admin = await this.get(email);
20
- if (!admin) throw new Error(`Admin.login(): Admin '${email}' not found.`);
21
- if (!admin.isActive) throw new Error(`Admin.login(): Admin '${email}' is inactive.`);
26
+
27
+ if (!admin) {
28
+ throw new Error(`Admin.login(): Admin '${email}' not found.`);
29
+ }
30
+
31
+ if (!admin.isActive) {
32
+ throw new Error(`Admin.login(): Admin '${email}' is inactive.`);
33
+ }
22
34
 
23
35
  const ok = await bcrypt.compare(password, admin.password);
24
- if (!ok) throw new Error(`Admin.login(): Invalid password.`);
25
36
 
26
- return this.kernel.auth.createAdminToken(admin);
37
+ if (!ok) {
38
+ throw new Error(`Admin.login(): Invalid password.`);
39
+ }
40
+
41
+ return this.createToken(admin);
42
+ }
43
+
44
+ async createToken(admin) {
45
+ return this.jwt.sign({
46
+ email: admin.email,
47
+ type: "admin"
48
+ });
49
+ }
50
+
51
+ async authenticate(token) {
52
+ const payload = this.jwt.verify(token);
53
+
54
+ if (payload.type !== "admin") {
55
+ throw new Error("Admin token required.");
56
+ }
57
+
58
+ const admin = await this.get(payload.email);
59
+
60
+ if (!admin) {
61
+ throw new Error("Admin not found.");
62
+ }
63
+
64
+ if (!admin.isActive) {
65
+ throw new Error("Admin is inactive.");
66
+ }
67
+
68
+ return admin;
27
69
  }
28
70
 
71
+ // CRUD
72
+
29
73
  async create(data) {
30
74
  const createData = { ...data };
31
- if (createData.password) createData.password = await bcrypt.hash(createData.password, 10);
32
- return this.kernel.db.admin.create({ data: createData });
75
+
76
+ if (createData.password) {
77
+ createData.password = await bcrypt.hash(createData.password, 10);
78
+ }
79
+
80
+ return this.kernel.db.admin.create({
81
+ data: createData
82
+ });
33
83
  }
34
84
 
35
85
  async update(email, data) {
36
86
  const updateData = { ...data };
37
- if (updateData.password) updateData.password = await bcrypt.hash(updateData.password, 10);
38
- return this.kernel.db.admin.update({ where: { email }, data: updateData });
87
+
88
+ if (updateData.password) {
89
+ updateData.password = await bcrypt.hash(updateData.password, 10);
90
+ }
91
+
92
+ return this.kernel.db.admin.update({
93
+ where: { email },
94
+ data: updateData
95
+ });
39
96
  }
40
97
 
41
98
  async delete(email) {
42
- return this.kernel.db.admin.delete({ where: { email } });
99
+ return this.kernel.db.admin.delete({
100
+ where: { email }
101
+ });
43
102
  }
44
103
 
45
104
  async isAdmin(email, courseSlug) {
46
105
  const admin = await this.get(email);
106
+
47
107
  if (!admin) return false;
108
+
48
109
  const courseSlugs = JSON.parse(admin.courseSlugs || "[]");
110
+
49
111
  return courseSlugs.includes(courseSlug);
50
112
  }
51
113
  }
@@ -1,10 +1,12 @@
1
1
  // src/serverKernel/modules/User.js
2
2
 
3
3
  import bcrypt from "bcrypt";
4
+ import JWT from "../utils/JWT.js";
4
5
 
5
6
  export default class User {
6
7
  constructor(kernel) {
7
8
  this.kernel = kernel;
9
+ this.jwt = new JWT(kernel);
8
10
  }
9
11
 
10
12
  // Queries
@@ -31,28 +33,69 @@ export default class User {
31
33
 
32
34
  async register(data) {
33
35
  const password = await bcrypt.hash(data.password, 10);
34
- return this.kernel.db.user.create({ data: { ...data, password } });
36
+
37
+ return this.kernel.db.user.create({
38
+ data: { ...data, password }
39
+ });
35
40
  }
36
41
 
37
42
  async login(email, password) {
38
43
  const user = await this.getByEmail(email);
39
- if (!user) throw new Error(`User.login(): User '${email}' not found.`);
44
+
45
+ if (!user) {
46
+ throw new Error(`User.login(): User '${email}' not found.`);
47
+ }
40
48
 
41
49
  const ok = await bcrypt.compare(password, user.password);
42
- if (!ok) throw new Error(`User.login(): Invalid password.`);
43
50
 
44
- return this.kernel.auth.createUserToken(user);
51
+ if (!ok) {
52
+ throw new Error(`User.login(): Invalid password.`);
53
+ }
54
+
55
+ return this.createToken(user);
56
+ }
57
+
58
+ async createToken(user) {
59
+ return this.jwt.sign({
60
+ id: user.id,
61
+ type: "user"
62
+ });
63
+ }
64
+
65
+ async authenticate(token) {
66
+ const payload = this.jwt.verify(token);
67
+
68
+ if (payload.type !== "user") {
69
+ throw new Error("User token required.");
70
+ }
71
+
72
+ const user = await this.get(payload.id);
73
+
74
+ if (!user) {
75
+ throw new Error("User not found.");
76
+ }
77
+
78
+ return user;
45
79
  }
46
80
 
47
81
  // CRUD
48
82
 
49
83
  async update(id, data) {
50
84
  const updateData = { ...data };
51
- if (updateData.password) updateData.password = await bcrypt.hash(updateData.password, 10);
52
- return this.kernel.db.user.update({ where: { id }, data: updateData });
85
+
86
+ if (updateData.password) {
87
+ updateData.password = await bcrypt.hash(updateData.password, 10);
88
+ }
89
+
90
+ return this.kernel.db.user.update({
91
+ where: { id },
92
+ data: updateData
93
+ });
53
94
  }
54
95
 
55
96
  async delete(id) {
56
- return this.kernel.db.user.delete({ where: { id } });
97
+ return this.kernel.db.user.delete({
98
+ where: { id }
99
+ });
57
100
  }
58
101
  }
@@ -0,0 +1,31 @@
1
+ // /home/bilal-tariq/00--TALEEM/taleem-kernel/src/utils/JWT.js
2
+
3
+ import jwt from "jsonwebtoken";
4
+
5
+ export default class JWT {
6
+
7
+ constructor(kernel) {
8
+
9
+ this.kernel = kernel;
10
+
11
+ }
12
+
13
+ sign(payload) {
14
+
15
+ return jwt.sign(
16
+ payload,
17
+ this.kernel.config.jwtSecret
18
+ );
19
+
20
+ }
21
+
22
+ verify(token) {
23
+
24
+ return jwt.verify(
25
+ token,
26
+ this.kernel.config.jwtSecret
27
+ );
28
+
29
+ }
30
+
31
+ }
package/src/Auth.js DELETED
@@ -1,105 +0,0 @@
1
- ///home/bilal-tariq/00--TALEEM/taleem-server/src/serverKernel/Auth.js
2
-
3
- import JWT from "./JWT.js";
4
-
5
- export default class Auth {
6
-
7
- constructor(kernel) {
8
- this.kernel = kernel;
9
- this.jwt = new JWT(kernel);
10
- }
11
- // --------------------------------------------------
12
- // Token Creation
13
- // --------------------------------------------------
14
- createUserToken(user) {
15
-
16
- return this.jwt.sign({ id: user.id, type: "user" });
17
-
18
- }
19
-
20
- createAdminToken(admin) {
21
-
22
- return this.jwt.sign({ id: admin.id, type: "admin" });
23
-
24
- }
25
-
26
- // --------------------------------------------------
27
- // Authentication
28
- // --------------------------------------------------
29
-
30
- async authenticate(token) {
31
-
32
- const { id, type } = this.verifyToken(token);
33
-
34
- if (type === "user") return this.authenticateUser(id);
35
-
36
- if (type === "admin") return this.authenticateAdmin(id);
37
-
38
- this.fail("authenticate()", `Unknown identity type '${type}'.`);
39
-
40
- }
41
-
42
- async authenticateUser(id) {
43
-
44
- const user = await this.kernel.user.get(id);
45
-
46
- if (!user)
47
- this.fail(
48
- "authenticateUser()",
49
- `User '${id}' does not exist.`
50
- );
51
-
52
- return user;
53
-
54
- }
55
-
56
- async authenticateAdmin(id) {
57
-
58
- const admin = await this.kernel.admin.get(id);
59
-
60
- if (!admin)
61
- this.fail(
62
- "authenticateAdmin()",
63
- `Admin '${id}' does not exist.`
64
- );
65
-
66
- return admin;
67
-
68
- }
69
-
70
- verifyToken(token) {
71
-
72
- try {
73
-
74
- return this.jwt.verify(token);
75
-
76
- }
77
- catch (error) {
78
-
79
- this.fail("verifyToken()", error.message);
80
-
81
- }
82
-
83
- }
84
-
85
- // --------------------------------------------------
86
- // Helpers
87
- // --------------------------------------------------
88
-
89
- fail(method, reason) {
90
-
91
- throw new Error(
92
- [
93
- "",
94
- "========================================",
95
- "AUTHENTICATION FAILED",
96
- "----------------------------------------",
97
- `Method : Auth.${method}`,
98
- `Reason : ${reason}`,
99
- "========================================"
100
- ].join("\n")
101
- );
102
-
103
- }
104
-
105
- }