taleem-kernel 1.0.0 → 1.0.1

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/bin/cli.js ADDED
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "fs";
4
+ import path from "path";
5
+ import { fileURLToPath } from "url";
6
+
7
+ const command = process.argv[2];
8
+
9
+ const kernelSchemaPath = path.resolve(
10
+ path.dirname(fileURLToPath(import.meta.url)),
11
+ "../prisma/schema.prisma"
12
+ );
13
+
14
+ const appSchemaPath = path.resolve(
15
+ process.cwd(),
16
+ "prisma/schema.prisma"
17
+ );
18
+
19
+ if (command === "schema-check") {
20
+ const kernelSchema = fs.readFileSync(kernelSchemaPath, "utf8");
21
+ const appSchema = fs.readFileSync(appSchemaPath, "utf8");
22
+
23
+ if (kernelSchema !== appSchema) {
24
+ console.error("❌ Taleem Prisma schema mismatch.");
25
+ console.error(`Kernel: ${kernelSchemaPath}`);
26
+ console.error(`App: ${appSchemaPath}`);
27
+ console.error("Run: npx taleem-kernel schema-update");
28
+ process.exit(1);
29
+ }
30
+
31
+ console.log("✓ Taleem Prisma schema is up to date.");
32
+ process.exit(0);
33
+ }
34
+
35
+ if (command === "schema-update") {
36
+ fs.copyFileSync(kernelSchemaPath, appSchemaPath);
37
+
38
+ console.log("✓ Taleem Prisma schema updated.");
39
+ console.log(`Source: ${kernelSchemaPath}`);
40
+ console.log(`Target: ${appSchemaPath}`);
41
+ process.exit(0);
42
+ }
43
+
44
+ console.log("Usage: npx taleem-kernel <command>");
45
+ console.log("");
46
+ console.log("Commands:");
47
+ console.log(" schema-check Check Prisma schema");
48
+ console.log(" schema-update Update Prisma schema");
49
+ process.exit(1);
@@ -0,0 +1,20 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { fileURLToPath } from "url";
4
+
5
+ const kernelSchema = fs.readFileSync(
6
+ path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../prisma/schema.prisma"),
7
+ "utf8"
8
+ );
9
+
10
+ const appSchema = fs.readFileSync(
11
+ path.resolve(process.cwd(), "prisma/schema.prisma"),
12
+ "utf8"
13
+ );
14
+
15
+ if (kernelSchema !== appSchema) {
16
+ console.error("❌ Taleem Prisma schema mismatch.");
17
+ process.exit(1);
18
+ }
19
+
20
+ console.log("✓ Taleem Prisma schema is up to date.");
@@ -0,0 +1,2 @@
1
+ fs.copyFileSync(kernelSchemaPath, appSchemaPath);
2
+ console.log("✓ Taleem Prisma schema updated.");
package/package.json CHANGED
@@ -1,15 +1,23 @@
1
1
  {
2
2
  "name": "taleem-kernel",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Taleem data and business-logic kernel. HTTP is only an adapter.",
5
5
  "type": "module",
6
6
  "main": "./src/ServerKernel.js",
7
7
  "exports": {
8
8
  ".": "./src/ServerKernel.js"
9
9
  },
10
+ "bin": {
11
+ "taleem-kernel": "./bin/cli.js"
12
+ },
10
13
  "scripts": {
11
14
  "test": "vitest run"
12
15
  },
16
+ "files": [
17
+ "src",
18
+ "prisma",
19
+ "bin"
20
+ ],
13
21
  "dependencies": {
14
22
  "@prisma/client": "^6.19.3",
15
23
  "bcrypt": "^6.0.0",
package/prisma/dev.db CHANGED
Binary file
@@ -0,0 +1,118 @@
1
+ -- CreateTable
2
+ CREATE TABLE "Course" (
3
+ "slug" TEXT NOT NULL,
4
+ "title" TEXT NOT NULL,
5
+ "description" TEXT,
6
+ "thumbnail" TEXT,
7
+ "access" TEXT NOT NULL DEFAULT 'OPEN',
8
+ "price" INTEGER NOT NULL DEFAULT 0,
9
+ "groupings" TEXT NOT NULL DEFAULT '[]'
10
+ );
11
+
12
+ -- CreateTable
13
+ CREATE TABLE "User" (
14
+ "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
15
+ "email" TEXT NOT NULL,
16
+ "password" TEXT NOT NULL,
17
+ "name" TEXT,
18
+ "role" TEXT NOT NULL DEFAULT 'student',
19
+ "resource" TEXT,
20
+ "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
21
+ );
22
+
23
+ -- CreateTable
24
+ CREATE TABLE "Admin" (
25
+ "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
26
+ "email" TEXT NOT NULL,
27
+ "password" TEXT NOT NULL,
28
+ "courseSlugs" TEXT NOT NULL DEFAULT '[]',
29
+ "role" TEXT NOT NULL DEFAULT 'ADMIN',
30
+ "isActive" BOOLEAN NOT NULL DEFAULT true,
31
+ "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
32
+ "updatedAt" DATETIME NOT NULL
33
+ );
34
+
35
+ -- CreateTable
36
+ CREATE TABLE "Library" (
37
+ "slug" TEXT NOT NULL PRIMARY KEY,
38
+ "title" TEXT NOT NULL,
39
+ "description" TEXT,
40
+ "thumbnail" TEXT,
41
+ "type" TEXT NOT NULL,
42
+ "body" TEXT,
43
+ "courseSlug" TEXT NOT NULL,
44
+ "groupSlug" TEXT NOT NULL,
45
+ "sortOrder" INTEGER NOT NULL DEFAULT 0,
46
+ "allowCommunication" BOOLEAN NOT NULL DEFAULT true,
47
+ "meta" TEXT,
48
+ "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
49
+ "updatedAt" DATETIME NOT NULL
50
+ );
51
+
52
+ -- CreateTable
53
+ CREATE TABLE "Subscription" (
54
+ "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
55
+ "userId" INTEGER NOT NULL,
56
+ "courseSlug" TEXT NOT NULL,
57
+ "startsAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
58
+ "endsAt" DATETIME,
59
+ "amount" INTEGER,
60
+ "cancelledAt" DATETIME,
61
+ CONSTRAINT "Subscription_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
62
+ );
63
+
64
+ -- CreateTable
65
+ CREATE TABLE "Communication" (
66
+ "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
67
+ "userId" INTEGER NOT NULL,
68
+ "librarySlug" TEXT NOT NULL,
69
+ "type" TEXT NOT NULL,
70
+ "meta" TEXT,
71
+ "message" TEXT NOT NULL,
72
+ "authorResponse" TEXT,
73
+ "isPublic" BOOLEAN NOT NULL DEFAULT false,
74
+ "readAt" DATETIME,
75
+ "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
76
+ "updatedAt" DATETIME NOT NULL,
77
+ CONSTRAINT "Communication_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
78
+ );
79
+
80
+ -- CreateTable
81
+ CREATE TABLE "Svg" (
82
+ "slug" TEXT NOT NULL PRIMARY KEY,
83
+ "title" TEXT NOT NULL,
84
+ "body" TEXT NOT NULL,
85
+ "tags" TEXT NOT NULL DEFAULT '[]',
86
+ "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
87
+ "updatedAt" DATETIME NOT NULL
88
+ );
89
+
90
+ -- CreateTable
91
+ CREATE TABLE "Image" (
92
+ "slug" TEXT NOT NULL PRIMARY KEY,
93
+ "title" TEXT NOT NULL,
94
+ "tags" TEXT NOT NULL DEFAULT '[]',
95
+ "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
96
+ "updatedAt" DATETIME NOT NULL
97
+ );
98
+
99
+ -- CreateIndex
100
+ CREATE UNIQUE INDEX "Course_slug_key" ON "Course"("slug");
101
+
102
+ -- CreateIndex
103
+ CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
104
+
105
+ -- CreateIndex
106
+ CREATE UNIQUE INDEX "Admin_email_key" ON "Admin"("email");
107
+
108
+ -- CreateIndex
109
+ CREATE INDEX "Subscription_userId_idx" ON "Subscription"("userId");
110
+
111
+ -- CreateIndex
112
+ CREATE INDEX "Subscription_courseSlug_idx" ON "Subscription"("courseSlug");
113
+
114
+ -- CreateIndex
115
+ CREATE INDEX "Communication_userId_idx" ON "Communication"("userId");
116
+
117
+ -- CreateIndex
118
+ CREATE INDEX "Communication_librarySlug_idx" ON "Communication"("librarySlug");
@@ -0,0 +1,8 @@
1
+ -- CreateTable
2
+ CREATE TABLE "Audio" (
3
+ "slug" TEXT NOT NULL PRIMARY KEY,
4
+ "title" TEXT NOT NULL,
5
+ "tags" TEXT NOT NULL DEFAULT '[]',
6
+ "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
7
+ "updatedAt" DATETIME NOT NULL
8
+ );
@@ -0,0 +1,3 @@
1
+ # Please do not edit this file manually
2
+ # It should be added in your version-control system (e.g., Git)
3
+ provider = "sqlite"
@@ -118,4 +118,31 @@ model Svg {
118
118
 
119
119
  updatedAt DateTime @updatedAt
120
120
 
121
+ }
122
+
123
+ model Image {
124
+
125
+ slug String @id
126
+
127
+ title String
128
+
129
+ tags String @default("[]")
130
+
131
+ createdAt DateTime @default(now())
132
+
133
+ updatedAt DateTime @updatedAt
134
+
135
+ }
136
+ model Audio {
137
+
138
+ slug String @id
139
+
140
+ title String
141
+
142
+ tags String @default("[]")
143
+
144
+ createdAt DateTime @default(now())
145
+
146
+ updatedAt DateTime @updatedAt
147
+
121
148
  }
package/readme.md CHANGED
@@ -1,116 +1,97 @@
1
+ ## Taleem Kernel API
1
2
 
2
- # Taleem Server Kernel Constitution
3
-
4
- ### Purpose
5
-
6
- The **serverKernel** is the product. It contains all business logic and data access. HTTP is only an adapter.
3
+ ```js
4
+ import kernel from "taleem-kernel";
5
+ ```
7
6
 
8
- ## Owns
7
+ `taleem-kernel` is the shared DB/domain layer for Taleem applications. It provides Prisma/SQLite access and Taleem-specific modules.
9
8
 
10
- * Database (Prisma)
11
- * Validation
12
- * Business rules
13
- * Authentication
14
- * Authorization
15
- * Domain errors
9
+ ### Core
16
10
 
17
- ## Never owns
11
+ ```js
12
+ kernel.db // Prisma client
13
+ kernel.config // configuration
14
+ kernel.auth // authentication/JWT
15
+ kernel.communicationPolicy
16
+ kernel.shutdown()
17
+ ```
18
18
 
19
- * Express
20
- * Routes
21
- * `req` / `res`
22
- * HTML pages
23
- * Cookies
24
- * Headers
25
- * Static files
26
- * URL parsing
19
+ ### Modules
20
+
21
+ ```js
22
+ kernel.user
23
+ kernel.admin
24
+ kernel.course
25
+ kernel.library
26
+ kernel.communication
27
+ kernel.subscription
28
+ kernel.svg
29
+ kernel.image
30
+ kernel.audio
31
+ ```
27
32
 
28
- ## Authentication Boundary
33
+ Each module provides simple DB/domain operations such as:
29
34
 
30
- ### HTTP Layer
35
+ ```js
36
+ list()
37
+ get(...)
38
+ create(data)
39
+ update(id, data)
40
+ delete(id)
41
+ ```
31
42
 
32
- Responsible for:
43
+ with additional module-specific methods such as:
33
44
 
34
- * Reading `Authorization` header
35
- * Reading cookies
36
- * Extracting bearer token
37
- * Passing token to kernel
45
+ ```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()
52
+ ```
38
53
 
39
- Never verifies anything.
54
+ ### Schema
40
55
 
41
- ### Kernel
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.
42
57
 
43
- Responsible for:
58
+ ```js
59
+ kernel.auth.authenticate(token)
60
+ kernel.user.login(email, password)
61
+ kernel.course.list()
62
+ kernel.library.get(slug)
63
+ kernel.svg.get(slug)
64
+ ```
44
65
 
45
- * Verifying JWT
46
- * Loading user
47
- * Checking permissions
48
- * Checking subscriptions
49
- * Returning authenticated identity
66
+ `kernel.shutdown()` disconnects Prisma when the application exits.
50
67
 
51
- Never reads headers.
68
+ ## Schema Management
52
69
 
53
- ## Public API Rules
70
+ The kernel provides two CLI commands for maintaining schema compatibility:
54
71
 
55
- Every public method:
72
+ ```bash
73
+ npx taleem-kernel schema-check
74
+ npx taleem-kernel schema-update
75
+ ```
56
76
 
57
- * validates input
58
- * validates authentication (if required)
59
- * enforces authorization
60
- * throws explicit errors
61
- * never silently fails
62
- * never returns ambiguous `null`
77
+ `schema-check` compares the application's `prisma/schema.prisma` with the canonical schema shipped with `taleem-kernel`.
63
78
 
64
- ## Error Rules
79
+ `schema-update` copies the kernel's canonical schema into the application's `prisma/schema.prisma`.
65
80
 
66
- Errors are for developers.
81
+ After updating the schema, run the application's normal Prisma migration/generation process as required.
67
82
 
68
- Every error answers:
83
+ ## Server Dependencies
69
84
 
70
- 1. What failed?
71
- 2. Why?
72
- 3. Which method?
73
- 4. Which object/user/resource?
74
- 5. What should the caller check?
85
+ `taleem-server` should use `taleem-kernel` for all Taleem database access.
75
86
 
76
- Example:
87
+ The server provides the HTTP/API layer on top of the kernel:
77
88
 
78
89
  ```text
79
- AuthenticationError
80
- Method : library.get()
81
- Reason : JWT expired
82
- User : 31
83
- Token : valid format, expired at 2026-07-27T09:10Z
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"
84
95
  ```
85
96
 
86
- ## Story Rule
87
-
88
- Every feature must be explainable as a story.
89
-
90
- ```
91
- HTTP
92
-
93
- Extract token
94
-
95
- Kernel
96
-
97
- Authenticate
98
-
99
- Authorize
100
-
101
- Business logic
102
-
103
- Return result
104
-
105
- HTTP formats response
106
- ```
107
-
108
- No business logic is allowed before entering the kernel.
109
-
110
- ---
111
-
112
- I would add one final principle because it will save you from years of debugging:
113
-
114
- > **The kernel trusts nothing and explains everything.**
115
-
116
- That one sentence should be visible at the top of the `serverKernel` folder. It captures the philosophy you've been moving toward: every input is validated, every decision is explicit, and every failure tells you exactly why it happened.
97
+ The server should **not maintain its own Taleem Prisma models or DB modules**.
@@ -1,9 +1,8 @@
1
- ///home/bilal-tariq/00--TALEEM/taleem-server-prod/src/serverKernel/ServerKernel.js
2
- import { PrismaClient } from "@prisma/client";
1
+ // src/serverKernel/ServerKernel.js
3
2
 
3
+ import { PrismaClient } from "@prisma/client";
4
4
  import Config from "./Config.js";
5
5
  import Auth from "./Auth.js";
6
- import Logger from "./Logger.js";
7
6
  import CommunicationPolicy from "./CommunicationPolicy.js";
8
7
  import User from "./modules/User.js";
9
8
  import Admin from "./modules/Admin.js";
@@ -16,122 +15,25 @@ import Audio from "./modules/Audio.js";
16
15
  import Svg from "./modules/Svg.js";
17
16
 
18
17
  class ServerKernel {
19
-
20
18
  constructor() {
21
-
22
- this.logger = new Logger();
23
-
24
- this.logger.info("========================================");
25
- this.logger.info("Starting Taleem Server Kernel");
26
- this.logger.info("========================================");
27
-
28
- try {
29
-
30
- // --------------------------------------------------
31
- // Core
32
- // --------------------------------------------------
33
-
34
- this.config = this.initialize("Config", () => new Config());
35
-
36
- this.db = this.initialize("Prisma", () => new PrismaClient());
37
-
38
- this.auth = this.initialize("Auth", () => new Auth(this));
39
-
40
- this.communicationPolicy =
41
- this.initialize(
42
- "CommunicationPolicy",
43
- () => new CommunicationPolicy(this)
44
- );
45
-
46
- // --------------------------------------------------
47
- // Modules
48
- // --------------------------------------------------
49
- this.user = this.initialize("User", () => new User(this));
50
- this.admin = this.initialize("Admin", () => new Admin(this));
51
- this.library = this.initialize("Library", () => new Library(this));
52
- this.course = this.initialize("Course", () => new Course(this));
53
- this.image = this.initialize("Image", () => new Image(this));
54
- this.audio = this.initialize("Audio", () => new Audio(this));
55
- this.svg = this.initialize("Svg", () => new Svg(this));
56
- this.communication = this.initialize("Communication", () => new Communication(this));
57
- this.subscription = this.initialize("Subscription", () => new Subscription(this));
58
-
59
- this.logger.info("Server Kernel started successfully.");
60
-
61
- }
62
- catch (error) {
63
-
64
- this.logger.error(error.message);
65
-
66
- throw error;
67
-
68
- }
69
-
70
- }
71
-
72
- initialize(name, factory) {
73
-
74
- this.logger.info(`Initializing ${name}...`);
75
-
76
- try {
77
-
78
- const instance = factory();
79
-
80
- this.logger.info(`${name} initialized.`);
81
-
82
- return instance;
83
-
84
- }
85
- catch (error) {
86
-
87
- throw new Error(
88
- [
89
- "",
90
- "========================================",
91
- "SERVER KERNEL INITIALIZATION FAILED",
92
- "----------------------------------------",
93
- `Component : ${name}`,
94
- `Reason : ${error.message}`,
95
- "",
96
- "Server Kernel startup aborted.",
97
- "========================================"
98
- ].join("\n")
99
- );
100
-
101
- }
102
-
19
+ this.config = new Config();
20
+ this.db = new PrismaClient();
21
+ this.auth = new Auth(this);
22
+ this.communicationPolicy = new CommunicationPolicy(this);
23
+ this.user = new User(this);
24
+ this.admin = new Admin(this);
25
+ this.library = new Library(this);
26
+ this.course = new Course(this);
27
+ this.image = new Image(this);
28
+ this.audio = new Audio(this);
29
+ this.svg = new Svg(this);
30
+ this.communication = new Communication(this);
31
+ this.subscription = new Subscription(this);
103
32
  }
104
33
 
105
34
  async shutdown() {
106
-
107
- this.logger.info("Shutting down Server Kernel...");
108
-
109
- try {
110
-
111
- await this.db.$disconnect();
112
-
113
- this.logger.info("Database disconnected.");
114
-
115
- this.logger.info("Server Kernel shutdown complete.");
116
-
117
- }
118
- catch (error) {
119
-
120
- throw new Error(
121
- [
122
- "",
123
- "========================================",
124
- "SERVER KERNEL SHUTDOWN FAILED",
125
- "----------------------------------------",
126
- `Reason : ${error.message}`,
127
- "========================================"
128
- ].join("\n")
129
- );
130
-
131
- }
132
-
35
+ await this.db.$disconnect();
133
36
  }
134
-
135
37
  }
136
38
 
137
39
  const kernel = new ServerKernel();
@@ -1,3 +1,5 @@
1
+ // src/serverKernel/modules/Admin.js
2
+
1
3
  import bcrypt from "bcrypt";
2
4
 
3
5
  export default class Admin {
@@ -10,15 +12,11 @@ export default class Admin {
10
12
  }
11
13
 
12
14
  async get(email) {
13
- return this.kernel.db.admin.findUnique({
14
- where: { email }
15
- });
15
+ return this.kernel.db.admin.findUnique({ where: { email } });
16
16
  }
17
17
 
18
18
  async login(email, password) {
19
- const admin = await this.kernel.db.admin.findUnique({
20
- where: { email }
21
- });
19
+ const admin = await this.get(email);
22
20
  if (!admin) throw new Error(`Admin.login(): Admin '${email}' not found.`);
23
21
  if (!admin.isActive) throw new Error(`Admin.login(): Admin '${email}' is inactive.`);
24
22
 
@@ -29,29 +27,25 @@ export default class Admin {
29
27
  }
30
28
 
31
29
  async create(data) {
32
- if (data.password) data.password = await bcrypt.hash(data.password, 10);
33
- return this.kernel.db.admin.create({ data });
30
+ const createData = { ...data };
31
+ if (createData.password) createData.password = await bcrypt.hash(createData.password, 10);
32
+ return this.kernel.db.admin.create({ data: createData });
34
33
  }
35
34
 
36
35
  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
- });
36
+ 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 });
42
39
  }
43
40
 
44
41
  async delete(email) {
45
- return this.kernel.db.admin.delete({
46
- where: { email }
47
- });
42
+ return this.kernel.db.admin.delete({ where: { email } });
48
43
  }
49
44
 
50
45
  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
- }
46
+ const admin = await this.get(email);
47
+ if (!admin) return false;
48
+ const courseSlugs = JSON.parse(admin.courseSlugs || "[]");
49
+ return courseSlugs.includes(courseSlug);
50
+ }
57
51
  }
@@ -1,49 +1,27 @@
1
1
  // src/serverKernel/modules/Audio.js
2
2
 
3
3
  export default class Audio {
4
-
5
- constructor(kernel) {
6
- this.kernel = kernel;
7
- }
8
-
9
- // --------------------------------------------------
10
- // Queries
11
- // --------------------------------------------------
4
+ constructor(kernel) { this.kernel = kernel; }
12
5
 
13
6
  async list() {
14
-
15
7
  return this.kernel.db.audio.findMany({
16
-
17
- orderBy: {
18
- createdAt: "desc"
19
- }
20
-
8
+ orderBy: { createdAt: "desc" }
21
9
  });
22
-
23
10
  }
24
11
 
25
- async get(id) {
26
-
27
- return this.kernel.db.audio.findUnique({
28
-
29
- where: { id }
30
-
31
- });
32
-
12
+ async get(slug) {
13
+ return this.kernel.db.audio.findUnique({ where: { slug } });
33
14
  }
34
15
 
35
- // --------------------------------------------------
36
- // Create
37
- // --------------------------------------------------
38
-
39
16
  async create(data) {
17
+ return this.kernel.db.audio.create({ data });
18
+ }
40
19
 
41
- return this.kernel.db.audio.create({
42
-
43
- data
44
-
45
- });
46
-
20
+ async update(slug, data) {
21
+ return this.kernel.db.audio.update({ where: { slug }, data });
47
22
  }
48
23
 
24
+ async delete(slug) {
25
+ return this.kernel.db.audio.delete({ where: { slug } });
26
+ }
49
27
  }
@@ -1,48 +1,27 @@
1
+ // src/serverKernel/modules/Image.js
1
2
 
2
3
  export default class Image {
3
-
4
- constructor(kernel) {
5
- this.kernel = kernel;
6
- }
7
-
8
- // --------------------------------------------------
9
- // Queries
10
- // --------------------------------------------------
4
+ constructor(kernel) { this.kernel = kernel; }
11
5
 
12
6
  async list() {
13
-
14
7
  return this.kernel.db.image.findMany({
15
-
16
- orderBy: {
17
- createdAt: "desc"
18
- }
19
-
8
+ orderBy: { createdAt: "desc" }
20
9
  });
21
-
22
10
  }
23
11
 
24
- async get(id) {
25
-
26
- return this.kernel.db.image.findUnique({
27
-
28
- where: { id }
29
-
30
- });
31
-
12
+ async get(slug) {
13
+ return this.kernel.db.image.findUnique({ where: { slug } });
32
14
  }
33
15
 
34
- // --------------------------------------------------
35
- // Create
36
- // --------------------------------------------------
37
-
38
16
  async create(data) {
17
+ return this.kernel.db.image.create({ data });
18
+ }
39
19
 
40
- return this.kernel.db.image.create({
41
-
42
- data
43
-
44
- });
45
-
20
+ async update(slug, data) {
21
+ return this.kernel.db.image.update({ where: { slug }, data });
46
22
  }
47
23
 
24
+ async delete(slug) {
25
+ return this.kernel.db.image.delete({ where: { slug } });
26
+ }
48
27
  }
@@ -1,11 +1,19 @@
1
+ // src/serverKernel/modules/Svg.js
2
+
1
3
  export default class Svg {
4
+ constructor(kernel) { this.kernel = kernel; }
5
+
6
+ async list() {
7
+ return this.kernel.db.svg.findMany({
8
+ orderBy: { title: "asc" }
9
+ });
10
+ }
2
11
 
3
- constructor(kernel) {
4
- this.kernel = kernel;
12
+ async get(slug) {
13
+ return this.kernel.db.svg.findUnique({ where: { slug } });
5
14
  }
6
15
 
7
16
  async create(data) {
8
-
9
17
  return this.kernel.db.svg.create({
10
18
  data: {
11
19
  slug: data.slug,
@@ -14,29 +22,9 @@ export default class Svg {
14
22
  tags: data.tags ?? "[]"
15
23
  }
16
24
  });
17
-
18
- }
19
-
20
- async get(slug) {
21
-
22
- return this.kernel.db.svg.findUnique({
23
- where: { slug }
24
- });
25
-
26
- }
27
-
28
- async list() {
29
-
30
- return this.kernel.db.svg.findMany({
31
- orderBy: {
32
- title: "asc"
33
- }
34
- });
35
-
36
25
  }
37
26
 
38
27
  async update(slug, data) {
39
-
40
28
  return this.kernel.db.svg.update({
41
29
  where: { slug },
42
30
  data: {
@@ -45,15 +33,9 @@ export default class Svg {
45
33
  tags: data.tags
46
34
  }
47
35
  });
48
-
49
36
  }
50
37
 
51
38
  async delete(slug) {
52
-
53
- return this.kernel.db.svg.delete({
54
- where: { slug }
55
- });
56
-
39
+ return this.kernel.db.svg.delete({ where: { slug } });
57
40
  }
58
-
59
41
  }
@@ -3,115 +3,56 @@
3
3
  import bcrypt from "bcrypt";
4
4
 
5
5
  export default class User {
6
-
7
6
  constructor(kernel) {
8
7
  this.kernel = kernel;
9
8
  }
10
9
 
11
- // --------------------------------------------------
12
10
  // Queries
13
- // --------------------------------------------------
14
11
 
15
12
  async list() {
16
-
17
13
  return this.kernel.db.user.findMany();
18
-
19
14
  }
20
15
 
21
16
  async get(id) {
22
-
23
- return this.kernel.db.user.findUnique({
24
- where: { id }
25
- });
26
-
17
+ return this.kernel.db.user.findUnique({ where: { id } });
27
18
  }
28
19
 
29
20
  async getByEmail(email) {
30
-
31
- return this.kernel.db.user.findUnique({
32
- where: { email }
33
- });
34
-
21
+ return this.kernel.db.user.findUnique({ where: { email } });
35
22
  }
36
23
 
37
24
  async emailToId(email) {
38
-
39
- const user = await this.getByEmail(email);
40
-
41
- if (!user) {
42
- throw new Error(`User '${email}' not found.`);
25
+ const user = await this.getByEmail(email);
26
+ if (!user) throw new Error(`User '${email}' not found.`);
27
+ return user.id;
43
28
  }
44
29
 
45
- return user.id;
46
-
47
- }
48
- // --------------------------------------------------
49
30
  // Authentication
50
- // --------------------------------------------------
51
31
 
52
32
  async register(data) {
53
-
54
33
  const password = await bcrypt.hash(data.password, 10);
55
-
56
- return this.kernel.db.user.create({
57
-
58
- data: {
59
- ...data,
60
- password
61
- }
62
-
63
- });
64
-
34
+ return this.kernel.db.user.create({ data: { ...data, password } });
65
35
  }
66
36
 
67
37
  async login(email, password) {
68
-
69
38
  const user = await this.getByEmail(email);
70
-
71
- if (!user) {
72
- throw new Error(`User.login(): User '${email}' not found.`);
73
- }
39
+ if (!user) throw new Error(`User.login(): User '${email}' not found.`);
74
40
 
75
41
  const ok = await bcrypt.compare(password, user.password);
76
-
77
- if (!ok) {
78
- throw new Error(`User.login(): Invalid password.`);
79
- }
42
+ if (!ok) throw new Error(`User.login(): Invalid password.`);
80
43
 
81
44
  return this.kernel.auth.createUserToken(user);
82
-
83
45
  }
84
46
 
85
- // --------------------------------------------------
86
47
  // CRUD
87
- // --------------------------------------------------
88
48
 
89
49
  async update(id, data) {
90
-
91
- if (data.password) {
92
-
93
- data.password = await bcrypt.hash(data.password, 10);
94
-
95
- }
96
-
97
- return this.kernel.db.user.update({
98
-
99
- where: { id },
100
-
101
- data
102
-
103
- });
104
-
50
+ 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 });
105
53
  }
106
54
 
107
55
  async delete(id) {
108
-
109
- return this.kernel.db.user.delete({
110
-
111
- where: { id }
112
-
113
- });
114
-
56
+ return this.kernel.db.user.delete({ where: { id } });
115
57
  }
116
-
117
58
  }
package/.env DELETED
@@ -1,5 +0,0 @@
1
-
2
- JWT_SECRET=change_this_to_a_long_random_secret
3
- DATABASE_URL="file:./dev.db"
4
-
5
- SERVER_URL=https://taleem.help/api
package/src/Logger.js DELETED
@@ -1,33 +0,0 @@
1
- // src/serverKernel/Logger.js
2
-
3
- export default class Logger {
4
-
5
- info(...args) {
6
-
7
- console.log("[INFO]", ...args);
8
-
9
- }
10
-
11
- warn(...args) {
12
-
13
- console.warn("[WARN]", ...args);
14
-
15
- }
16
-
17
- error(...args) {
18
-
19
- console.error("[ERROR]", ...args);
20
-
21
- }
22
-
23
- debug(...args) {
24
-
25
- if (process.env.NODE_ENV !== "production") {
26
-
27
- console.log("[DEBUG]", ...args);
28
-
29
- }
30
-
31
- }
32
-
33
- }
@@ -1,52 +0,0 @@
1
- import { describe, it, expect, afterAll } from "vitest";
2
- import kernel from "../src/ServerKernel.js";
3
-
4
- const email = "bilal@taleem.help";
5
-
6
- describe("Admin", () => {
7
- it("gets admin by email", async () => {
8
- const admin = await kernel.admin.get(email);
9
-
10
- expect(admin).toBeTruthy();
11
- expect(admin.email).toBe(email);
12
- expect(admin.isActive).toBe(true);
13
- });
14
-
15
- it("lists active admins", async () => {
16
- const admins = await kernel.admin.list({ isActive: true });
17
-
18
- expect(Array.isArray(admins)).toBe(true);
19
- expect(admins.some(admin => admin.email === email)).toBe(true);
20
- });
21
-
22
- it("checks course authorization", async () => {
23
- expect(
24
- await kernel.admin.isAdmin(email, "fbise9math")
25
- ).toBe(true);
26
-
27
- expect(
28
- await kernel.admin.isAdmin(email, "blog")
29
- ).toBe(true);
30
-
31
- expect(
32
- await kernel.admin.isAdmin(email, "fbise9mathQuickRef")
33
- ).toBe(true);
34
-
35
- expect(
36
- await kernel.admin.isAdmin(email, "does-not-exist")
37
- ).toBe(false);
38
- });
39
-
40
- it("returns false for an unknown admin", async () => {
41
- expect(
42
- await kernel.admin.isAdmin(
43
- "unknown@example.com",
44
- "fbise9math"
45
- )
46
- ).toBe(false);
47
- });
48
-
49
- afterAll(async () => {
50
- await kernel.shutdown();
51
- });
52
- });
@@ -1,68 +0,0 @@
1
- import { describe, it, expect, afterAll } from "vitest";
2
- import kernel from "../src/ServerKernel.js";
3
-
4
- describe("Course", () => {
5
- let testSlug;
6
-
7
- it("lists courses", async () => {
8
- const courses = await kernel.course.list();
9
- expect(Array.isArray(courses)).toBe(true);
10
- });
11
-
12
- it("gets a course by slug", async () => {
13
- const courses = await kernel.course.list();
14
- expect(courses.length).toBeGreaterThan(0);
15
-
16
- const course = await kernel.course.get(courses[0].slug);
17
-
18
- expect(course).toBeTruthy();
19
- expect(course.slug).toBe(courses[0].slug);
20
- });
21
-
22
- it("filters courses by access", async () => {
23
- const courses = await kernel.course.list({ access: "OPEN" });
24
-
25
- expect(Array.isArray(courses)).toBe(true);
26
-
27
- for (const course of courses) {
28
- expect(course.access).toBe("OPEN");
29
- }
30
- });
31
-
32
- it("creates, updates and deletes a course", async () => {
33
- testSlug = "__kernel-test-course__";
34
-
35
- await kernel.course.create({
36
- slug: testSlug,
37
- title: "Kernel Test Course",
38
- description: "Temporary test course",
39
- access: "OPEN"
40
- });
41
-
42
- let course = await kernel.course.get(testSlug);
43
- expect(course).toBeTruthy();
44
- expect(course.title).toBe("Kernel Test Course");
45
-
46
- await kernel.course.update(testSlug, {
47
- title: "Updated Kernel Test Course"
48
- });
49
-
50
- course = await kernel.course.get(testSlug);
51
- expect(course.title).toBe("Updated Kernel Test Course");
52
-
53
- await kernel.course.delete(testSlug);
54
-
55
- course = await kernel.course.get(testSlug);
56
- expect(course).toBeNull();
57
-
58
- testSlug = null;
59
- });
60
-
61
- afterAll(async () => {
62
- if (testSlug) {
63
- await kernel.course.delete(testSlug).catch(() => {});
64
- }
65
-
66
- await kernel.shutdown();
67
- });
68
- });
@@ -1,12 +0,0 @@
1
- import { describe, it, expect, afterAll } from "vitest";
2
- import kernel from "../src/ServerKernel.js";
3
-
4
- describe("Taleem Kernel", () => {
5
- it("starts and connects to Prisma", async () => {
6
- await expect(kernel.db.$queryRaw`SELECT 1`).resolves.toBeDefined();
7
- });
8
-
9
- afterAll(async () => {
10
- await kernel.shutdown();
11
- });
12
- });
@@ -1,23 +0,0 @@
1
- import { describe, it, expect, afterAll } from "vitest";
2
- import kernel from "../src/ServerKernel.js";
3
-
4
- describe("Library", () => {
5
- it("lists library content", async () => {
6
- const items = await kernel.library.list();
7
- expect(Array.isArray(items)).toBe(true);
8
- });
9
-
10
- it("gets a library item by slug", async () => {
11
- const items = await kernel.library.list();
12
- expect(items.length).toBeGreaterThan(0);
13
-
14
- const item = await kernel.library.get(items[0].slug);
15
-
16
- expect(item).toBeTruthy();
17
- expect(item.slug).toBe(items[0].slug);
18
- });
19
-
20
- afterAll(async () => {
21
- await kernel.shutdown();
22
- });
23
- });