taleem-kernel 1.2.0 → 1.5.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/bin/schema-check.js +21 -10
- package/bin/schema-update.js +1 -0
- package/package.json +4 -4
- package/prisma/dev.db +0 -0
- package/prisma/migrations/20260824182316_add_library_status/migration.sql +24 -0
- package/prisma/migrations/20260829212536_init/migration.sql +96 -0
- package/prisma/schema.prisma +82 -57
- package/readme.md +244 -56
- package/src/{ServerKernel.js → index.js} +4 -2
- package/src/modules/Admin.js +73 -11
- package/src/modules/Communication.js +7 -51
- package/src/modules/Course.js +34 -41
- package/src/modules/Group.js +48 -0
- package/src/modules/Library.js +20 -8
- package/src/modules/Subscription.js +12 -61
- package/src/modules/Svg.js +3 -21
- package/src/modules/User.js +50 -7
- package/src/utils/JWT.js +31 -0
- package/prisma/content.db +0 -0
- package/src/Auth.js +0 -105
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export default class Group {
|
|
2
|
+
constructor(kernel) { this.kernel = kernel; }
|
|
3
|
+
|
|
4
|
+
async list(filters = {}) {
|
|
5
|
+
const where = {};
|
|
6
|
+
if (filters.courseSlug) where.courseSlug = filters.courseSlug;
|
|
7
|
+
|
|
8
|
+
return this.kernel.db.group.findMany({
|
|
9
|
+
where,
|
|
10
|
+
orderBy: { id: "asc" }
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async listByCourse(courseSlug) {
|
|
15
|
+
return this.list({ courseSlug });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async get(courseSlug, slug) {
|
|
19
|
+
return this.kernel.db.group.findUnique({
|
|
20
|
+
where: {
|
|
21
|
+
courseSlug_slug: { courseSlug, slug }
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async create(data) {
|
|
27
|
+
// throws if data.courseSlug doesn't resolve to an existing Course
|
|
28
|
+
return this.kernel.db.group.create({ data });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async update(courseSlug, slug, data) {
|
|
32
|
+
return this.kernel.db.group.update({
|
|
33
|
+
where: {
|
|
34
|
+
courseSlug_slug: { courseSlug, slug }
|
|
35
|
+
},
|
|
36
|
+
data
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async delete(courseSlug, slug) {
|
|
41
|
+
// throws if any Library rows still reference this group (Restrict)
|
|
42
|
+
return this.kernel.db.group.delete({
|
|
43
|
+
where: {
|
|
44
|
+
courseSlug_slug: { courseSlug, slug }
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
package/src/modules/Library.js
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
|
+
const PUBLIC_STATUS = "PUBLISHED";
|
|
2
|
+
|
|
1
3
|
export default class Library {
|
|
2
4
|
constructor(kernel) { this.kernel = kernel; }
|
|
3
5
|
|
|
4
|
-
async list(filters = {}) {
|
|
6
|
+
async list(filters = {}, { includeUnpublished = false } = {}) {
|
|
5
7
|
const where = {};
|
|
6
8
|
if (filters.type) where.type = filters.type;
|
|
7
9
|
if (filters.courseSlug) where.courseSlug = filters.courseSlug;
|
|
8
10
|
if (filters.groupSlug) where.groupSlug = filters.groupSlug;
|
|
9
11
|
|
|
12
|
+
if (!includeUnpublished) where.status = PUBLIC_STATUS;
|
|
13
|
+
else if (filters.status) where.status = filters.status;
|
|
14
|
+
|
|
10
15
|
return this.kernel.db.library.findMany({
|
|
11
16
|
where,
|
|
12
17
|
select: {
|
|
@@ -15,6 +20,7 @@ export default class Library {
|
|
|
15
20
|
description: true,
|
|
16
21
|
thumbnail: true,
|
|
17
22
|
type: true,
|
|
23
|
+
status: true,
|
|
18
24
|
courseSlug: true,
|
|
19
25
|
groupSlug: true,
|
|
20
26
|
sortOrder: true,
|
|
@@ -26,17 +32,20 @@ export default class Library {
|
|
|
26
32
|
});
|
|
27
33
|
}
|
|
28
34
|
|
|
29
|
-
async listByCourse(courseSlug) {
|
|
30
|
-
return this.list({ courseSlug });
|
|
35
|
+
async listByCourse(courseSlug, opts) {
|
|
36
|
+
return this.list({ courseSlug }, opts);
|
|
31
37
|
}
|
|
32
38
|
|
|
33
|
-
async listByGroup(courseSlug, groupSlug) {
|
|
34
|
-
return this.list({ courseSlug, groupSlug });
|
|
39
|
+
async listByGroup(courseSlug, groupSlug, opts) {
|
|
40
|
+
return this.list({ courseSlug, groupSlug }, opts);
|
|
35
41
|
}
|
|
36
42
|
|
|
37
|
-
async get(slug) {
|
|
43
|
+
async get(slug, { includeUnpublished = false } = {}) {
|
|
38
44
|
return this.kernel.db.library.findUnique({
|
|
39
|
-
where: {
|
|
45
|
+
where: {
|
|
46
|
+
slug,
|
|
47
|
+
...(includeUnpublished ? {} : { status: PUBLIC_STATUS })
|
|
48
|
+
}
|
|
40
49
|
});
|
|
41
50
|
}
|
|
42
51
|
|
|
@@ -52,11 +61,13 @@ export default class Library {
|
|
|
52
61
|
}
|
|
53
62
|
|
|
54
63
|
async delete(slug) {
|
|
64
|
+
// throws if Communication rows still reference this slug (Restrict)
|
|
55
65
|
return this.kernel.db.library.delete({
|
|
56
66
|
where: { slug }
|
|
57
67
|
});
|
|
58
68
|
}
|
|
59
|
-
|
|
69
|
+
|
|
70
|
+
async createFromSlot(slug, courseSlug, groupSlug, type) {
|
|
60
71
|
return this.create({
|
|
61
72
|
slug,
|
|
62
73
|
courseSlug,
|
|
@@ -65,4 +76,5 @@ export default class Library {
|
|
|
65
76
|
title: slug
|
|
66
77
|
});
|
|
67
78
|
}
|
|
79
|
+
|
|
68
80
|
}
|
|
@@ -1,97 +1,48 @@
|
|
|
1
|
-
// src/serverKernel/modules/Subscription.js
|
|
2
|
-
|
|
3
1
|
export default class Subscription {
|
|
4
|
-
|
|
5
|
-
constructor(kernel) {
|
|
6
|
-
this.kernel = kernel;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
// --------------------------------------------------
|
|
10
|
-
// Queries
|
|
11
|
-
// --------------------------------------------------
|
|
2
|
+
constructor(kernel) { this.kernel = kernel; }
|
|
12
3
|
|
|
13
4
|
async list(filters = {}) {
|
|
14
|
-
|
|
15
5
|
const where = {};
|
|
16
|
-
|
|
17
|
-
if (filters.
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
if (filters.courseId) {
|
|
22
|
-
where.courseId = filters.courseId;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return this.kernel.db.subscription.findMany({
|
|
26
|
-
where
|
|
27
|
-
});
|
|
28
|
-
|
|
6
|
+
if (filters.userId) where.userId = filters.userId;
|
|
7
|
+
if (filters.courseSlug) where.courseSlug = filters.courseSlug;
|
|
8
|
+
return this.kernel.db.subscription.findMany({ where });
|
|
29
9
|
}
|
|
30
10
|
|
|
31
11
|
async get(id) {
|
|
32
|
-
|
|
33
|
-
return this.kernel.db.subscription.findUnique({
|
|
34
|
-
where: { id }
|
|
35
|
-
});
|
|
36
|
-
|
|
12
|
+
return this.kernel.db.subscription.findUnique({ where: { id } });
|
|
37
13
|
}
|
|
38
14
|
|
|
39
|
-
// --------------------------------------------------
|
|
40
|
-
// CRUD
|
|
41
|
-
// --------------------------------------------------
|
|
42
|
-
|
|
43
15
|
async create(data) {
|
|
44
|
-
|
|
45
|
-
return this.kernel.db.subscription.create({
|
|
46
|
-
data
|
|
47
|
-
});
|
|
48
|
-
|
|
16
|
+
// throws if courseSlug doesn't resolve to an existing Course
|
|
17
|
+
return this.kernel.db.subscription.create({ data });
|
|
49
18
|
}
|
|
50
19
|
|
|
51
20
|
async update(id, data) {
|
|
52
|
-
|
|
53
|
-
return this.kernel.db.subscription.update({
|
|
54
|
-
where: { id },
|
|
55
|
-
data
|
|
56
|
-
});
|
|
57
|
-
|
|
21
|
+
return this.kernel.db.subscription.update({ where: { id }, data });
|
|
58
22
|
}
|
|
59
23
|
|
|
60
24
|
async delete(id) {
|
|
61
|
-
|
|
62
|
-
return this.kernel.db.subscription.delete({
|
|
63
|
-
where: { id }
|
|
64
|
-
});
|
|
65
|
-
|
|
25
|
+
return this.kernel.db.subscription.delete({ where: { id } });
|
|
66
26
|
}
|
|
67
27
|
|
|
68
|
-
|
|
69
|
-
// Utilities
|
|
70
|
-
// --------------------------------------------------
|
|
71
|
-
|
|
72
|
-
async authorize(userId, courseId) {
|
|
73
|
-
debugger;
|
|
28
|
+
async authorize(userId, courseSlug) {
|
|
74
29
|
const now = new Date();
|
|
75
30
|
|
|
76
31
|
const subscription = await this.kernel.db.subscription.findFirst({
|
|
77
|
-
|
|
78
32
|
where: {
|
|
79
33
|
userId,
|
|
80
|
-
|
|
34
|
+
courseSlug,
|
|
81
35
|
startsAt: { lte: now },
|
|
82
36
|
endsAt: { gte: now }
|
|
83
37
|
}
|
|
84
|
-
|
|
85
38
|
});
|
|
86
39
|
|
|
87
40
|
if (!subscription) {
|
|
88
41
|
throw new Error(
|
|
89
|
-
`User "${userId}" does not have an active subscription for course "${
|
|
42
|
+
`User "${userId}" does not have an active subscription for course "${courseSlug}".`
|
|
90
43
|
);
|
|
91
44
|
}
|
|
92
45
|
|
|
93
46
|
return subscription;
|
|
94
|
-
|
|
95
47
|
}
|
|
96
|
-
|
|
97
48
|
}
|
package/src/modules/Svg.js
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
// src/serverKernel/modules/Svg.js
|
|
2
|
-
|
|
3
1
|
export default class Svg {
|
|
4
2
|
constructor(kernel) { this.kernel = kernel; }
|
|
5
3
|
|
|
6
4
|
async list() {
|
|
7
|
-
return this.kernel.db.svg.findMany({
|
|
8
|
-
orderBy: { title: "asc" }
|
|
9
|
-
});
|
|
5
|
+
return this.kernel.db.svg.findMany({ orderBy: { title: "asc" } });
|
|
10
6
|
}
|
|
11
7
|
|
|
12
8
|
async get(slug) {
|
|
@@ -14,25 +10,11 @@ export default class Svg {
|
|
|
14
10
|
}
|
|
15
11
|
|
|
16
12
|
async create(data) {
|
|
17
|
-
return this.kernel.db.svg.create({
|
|
18
|
-
data: {
|
|
19
|
-
slug: data.slug,
|
|
20
|
-
title: data.title,
|
|
21
|
-
body: data.body,
|
|
22
|
-
tags: data.tags ?? "[]"
|
|
23
|
-
}
|
|
24
|
-
});
|
|
13
|
+
return this.kernel.db.svg.create({ data });
|
|
25
14
|
}
|
|
26
15
|
|
|
27
16
|
async update(slug, data) {
|
|
28
|
-
return this.kernel.db.svg.update({
|
|
29
|
-
where: { slug },
|
|
30
|
-
data: {
|
|
31
|
-
title: data.title,
|
|
32
|
-
body: data.body,
|
|
33
|
-
tags: data.tags
|
|
34
|
-
}
|
|
35
|
-
});
|
|
17
|
+
return this.kernel.db.svg.update({ where: { slug }, data });
|
|
36
18
|
}
|
|
37
19
|
|
|
38
20
|
async delete(slug) {
|
package/src/modules/User.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
52
|
-
|
|
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({
|
|
97
|
+
return this.kernel.db.user.delete({
|
|
98
|
+
where: { id }
|
|
99
|
+
});
|
|
57
100
|
}
|
|
58
101
|
}
|
package/src/utils/JWT.js
ADDED
|
@@ -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/prisma/content.db
DELETED
|
Binary file
|
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
|
-
}
|