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
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
export default class Communication {
|
|
2
|
+
|
|
3
|
+
constructor(kernel) {
|
|
4
|
+
this.kernel = kernel;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
async list(filters = {}) {
|
|
8
|
+
const where = {};
|
|
9
|
+
|
|
10
|
+
if (filters.courseSlug) {
|
|
11
|
+
const items = await this.kernel.db.library.findMany({
|
|
12
|
+
where: { courseSlug: filters.courseSlug },
|
|
13
|
+
select: { slug: true }
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
where.librarySlug = {
|
|
17
|
+
in: items.map(item => item.slug)
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (filters.librarySlug) where.librarySlug = filters.librarySlug;
|
|
22
|
+
if (filters.userId) where.userId = filters.userId;
|
|
23
|
+
|
|
24
|
+
if (filters.unanswered) {
|
|
25
|
+
where.OR = [
|
|
26
|
+
{ authorResponse: null },
|
|
27
|
+
{ authorResponse: "" }
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return this.kernel.db.communication.findMany({
|
|
32
|
+
where,
|
|
33
|
+
include: { user: true },
|
|
34
|
+
orderBy: { createdAt: "desc" }
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async get(id) {
|
|
39
|
+
const item = await this.kernel.db.communication.findUnique({
|
|
40
|
+
where: { id },
|
|
41
|
+
include: { user: true }
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
if (!item) return null;
|
|
45
|
+
|
|
46
|
+
const library = await this.kernel.db.library.findUnique({
|
|
47
|
+
where: { slug: item.librarySlug },
|
|
48
|
+
select: {
|
|
49
|
+
slug: true,
|
|
50
|
+
title: true,
|
|
51
|
+
courseSlug: true
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
...item,
|
|
57
|
+
library
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async create(data) {
|
|
62
|
+
return this.kernel.db.communication.create({ data });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async update(id, data) {
|
|
66
|
+
return this.kernel.db.communication.update({
|
|
67
|
+
where: { id },
|
|
68
|
+
data
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async delete(id) {
|
|
73
|
+
return this.kernel.db.communication.delete({
|
|
74
|
+
where: { id }
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async listUnanswered(courseSlug) {
|
|
79
|
+
const items = await this.list({
|
|
80
|
+
courseSlug,
|
|
81
|
+
unanswered: true
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const slugs = [...new Set(items.map(item => item.librarySlug))];
|
|
85
|
+
|
|
86
|
+
const libraries = await this.kernel.db.library.findMany({
|
|
87
|
+
where: { slug: { in: slugs } },
|
|
88
|
+
select: {
|
|
89
|
+
slug: true,
|
|
90
|
+
title: true,
|
|
91
|
+
courseSlug: true
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const map = new Map(
|
|
96
|
+
libraries.map(item => [item.slug, item])
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
return items.map(item => ({
|
|
100
|
+
...item,
|
|
101
|
+
library: map.get(item.librarySlug) || null
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async countUserOpenQuestions(userId) {
|
|
106
|
+
return this.kernel.db.communication.count({
|
|
107
|
+
where: {
|
|
108
|
+
userId,
|
|
109
|
+
OR: [
|
|
110
|
+
{ authorResponse: null },
|
|
111
|
+
{ authorResponse: "" }
|
|
112
|
+
]
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export default class Course {
|
|
2
|
+
constructor(kernel) { this.kernel = kernel; }
|
|
3
|
+
|
|
4
|
+
async list(filters = {}) {
|
|
5
|
+
const where = {};
|
|
6
|
+
if (filters.access) where.access = filters.access;
|
|
7
|
+
return this.kernel.db.course.findMany({ where });
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async get(slug) {
|
|
11
|
+
return this.kernel.db.course.findUnique({ where: { slug } });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async create(data) {
|
|
15
|
+
return this.kernel.db.course.create({ data });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async update(slug, data) {
|
|
19
|
+
return this.kernel.db.course.update({ where: { slug }, data });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async delete(slug) {
|
|
23
|
+
return this.kernel.db.course.delete({ where: { slug } });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
|
|
2
|
+
export default class Image {
|
|
3
|
+
|
|
4
|
+
constructor(kernel) {
|
|
5
|
+
this.kernel = kernel;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// --------------------------------------------------
|
|
9
|
+
// Queries
|
|
10
|
+
// --------------------------------------------------
|
|
11
|
+
|
|
12
|
+
async list() {
|
|
13
|
+
|
|
14
|
+
return this.kernel.db.image.findMany({
|
|
15
|
+
|
|
16
|
+
orderBy: {
|
|
17
|
+
createdAt: "desc"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async get(id) {
|
|
25
|
+
|
|
26
|
+
return this.kernel.db.image.findUnique({
|
|
27
|
+
|
|
28
|
+
where: { id }
|
|
29
|
+
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// --------------------------------------------------
|
|
35
|
+
// Create
|
|
36
|
+
// --------------------------------------------------
|
|
37
|
+
|
|
38
|
+
async create(data) {
|
|
39
|
+
|
|
40
|
+
return this.kernel.db.image.create({
|
|
41
|
+
|
|
42
|
+
data
|
|
43
|
+
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export default class Library {
|
|
2
|
+
constructor(kernel) { this.kernel = kernel; }
|
|
3
|
+
|
|
4
|
+
async list(filters = {}) {
|
|
5
|
+
const where = {};
|
|
6
|
+
if (filters.type) where.type = filters.type;
|
|
7
|
+
if (filters.courseSlug) where.courseSlug = filters.courseSlug;
|
|
8
|
+
if (filters.groupSlug) where.groupSlug = filters.groupSlug;
|
|
9
|
+
|
|
10
|
+
return this.kernel.db.library.findMany({
|
|
11
|
+
where,
|
|
12
|
+
select: {
|
|
13
|
+
slug: true,
|
|
14
|
+
title: true,
|
|
15
|
+
description: true,
|
|
16
|
+
thumbnail: true,
|
|
17
|
+
type: true,
|
|
18
|
+
courseSlug: true,
|
|
19
|
+
groupSlug: true,
|
|
20
|
+
sortOrder: true,
|
|
21
|
+
allowCommunication: true,
|
|
22
|
+
createdAt: true,
|
|
23
|
+
updatedAt: true
|
|
24
|
+
},
|
|
25
|
+
orderBy: { sortOrder: "asc" }
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async listByCourse(courseSlug) {
|
|
30
|
+
return this.list({ courseSlug });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async listByGroup(courseSlug, groupSlug) {
|
|
34
|
+
return this.list({ courseSlug, groupSlug });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async get(slug) {
|
|
38
|
+
return this.kernel.db.library.findUnique({
|
|
39
|
+
where: { slug }
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async create(data) {
|
|
44
|
+
return this.kernel.db.library.create({ data });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async update(slug, data) {
|
|
48
|
+
return this.kernel.db.library.update({
|
|
49
|
+
where: { slug },
|
|
50
|
+
data
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async delete(slug) {
|
|
55
|
+
return this.kernel.db.library.delete({
|
|
56
|
+
where: { slug }
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// src/serverKernel/modules/Subscription.js
|
|
2
|
+
|
|
3
|
+
export default class Subscription {
|
|
4
|
+
|
|
5
|
+
constructor(kernel) {
|
|
6
|
+
this.kernel = kernel;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// --------------------------------------------------
|
|
10
|
+
// Queries
|
|
11
|
+
// --------------------------------------------------
|
|
12
|
+
|
|
13
|
+
async list(filters = {}) {
|
|
14
|
+
|
|
15
|
+
const where = {};
|
|
16
|
+
|
|
17
|
+
if (filters.userId) {
|
|
18
|
+
where.userId = filters.userId;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (filters.courseId) {
|
|
22
|
+
where.courseId = filters.courseId;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return this.kernel.db.subscription.findMany({
|
|
26
|
+
where
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async get(id) {
|
|
32
|
+
|
|
33
|
+
return this.kernel.db.subscription.findUnique({
|
|
34
|
+
where: { id }
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// --------------------------------------------------
|
|
40
|
+
// CRUD
|
|
41
|
+
// --------------------------------------------------
|
|
42
|
+
|
|
43
|
+
async create(data) {
|
|
44
|
+
|
|
45
|
+
return this.kernel.db.subscription.create({
|
|
46
|
+
data
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async update(id, data) {
|
|
52
|
+
|
|
53
|
+
return this.kernel.db.subscription.update({
|
|
54
|
+
where: { id },
|
|
55
|
+
data
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async delete(id) {
|
|
61
|
+
|
|
62
|
+
return this.kernel.db.subscription.delete({
|
|
63
|
+
where: { id }
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// --------------------------------------------------
|
|
69
|
+
// Utilities
|
|
70
|
+
// --------------------------------------------------
|
|
71
|
+
|
|
72
|
+
async authorize(userId, courseId) {
|
|
73
|
+
debugger;
|
|
74
|
+
const now = new Date();
|
|
75
|
+
|
|
76
|
+
const subscription = await this.kernel.db.subscription.findFirst({
|
|
77
|
+
|
|
78
|
+
where: {
|
|
79
|
+
userId,
|
|
80
|
+
courseId,
|
|
81
|
+
startsAt: { lte: now },
|
|
82
|
+
endsAt: { gte: now }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
if (!subscription) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`User "${userId}" does not have an active subscription for course "${courseId}".`
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return subscription;
|
|
94
|
+
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export default class Svg {
|
|
2
|
+
|
|
3
|
+
constructor(kernel) {
|
|
4
|
+
this.kernel = kernel;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
async create(data) {
|
|
8
|
+
|
|
9
|
+
return this.kernel.db.svg.create({
|
|
10
|
+
data: {
|
|
11
|
+
slug: data.slug,
|
|
12
|
+
title: data.title,
|
|
13
|
+
body: data.body,
|
|
14
|
+
tags: data.tags ?? "[]"
|
|
15
|
+
}
|
|
16
|
+
});
|
|
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
|
+
}
|
|
37
|
+
|
|
38
|
+
async update(slug, data) {
|
|
39
|
+
|
|
40
|
+
return this.kernel.db.svg.update({
|
|
41
|
+
where: { slug },
|
|
42
|
+
data: {
|
|
43
|
+
title: data.title,
|
|
44
|
+
body: data.body,
|
|
45
|
+
tags: data.tags
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async delete(slug) {
|
|
52
|
+
|
|
53
|
+
return this.kernel.db.svg.delete({
|
|
54
|
+
where: { slug }
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// src/serverKernel/modules/User.js
|
|
2
|
+
|
|
3
|
+
import bcrypt from "bcrypt";
|
|
4
|
+
|
|
5
|
+
export default class User {
|
|
6
|
+
|
|
7
|
+
constructor(kernel) {
|
|
8
|
+
this.kernel = kernel;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// --------------------------------------------------
|
|
12
|
+
// Queries
|
|
13
|
+
// --------------------------------------------------
|
|
14
|
+
|
|
15
|
+
async list() {
|
|
16
|
+
|
|
17
|
+
return this.kernel.db.user.findMany();
|
|
18
|
+
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async get(id) {
|
|
22
|
+
|
|
23
|
+
return this.kernel.db.user.findUnique({
|
|
24
|
+
where: { id }
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async getByEmail(email) {
|
|
30
|
+
|
|
31
|
+
return this.kernel.db.user.findUnique({
|
|
32
|
+
where: { email }
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async emailToId(email) {
|
|
38
|
+
|
|
39
|
+
const user = await this.getByEmail(email);
|
|
40
|
+
|
|
41
|
+
if (!user) {
|
|
42
|
+
throw new Error(`User '${email}' not found.`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return user.id;
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
// --------------------------------------------------
|
|
49
|
+
// Authentication
|
|
50
|
+
// --------------------------------------------------
|
|
51
|
+
|
|
52
|
+
async register(data) {
|
|
53
|
+
|
|
54
|
+
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
|
+
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async login(email, password) {
|
|
68
|
+
|
|
69
|
+
const user = await this.getByEmail(email);
|
|
70
|
+
|
|
71
|
+
if (!user) {
|
|
72
|
+
throw new Error(`User.login(): User '${email}' not found.`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const ok = await bcrypt.compare(password, user.password);
|
|
76
|
+
|
|
77
|
+
if (!ok) {
|
|
78
|
+
throw new Error(`User.login(): Invalid password.`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return this.kernel.auth.createUserToken(user);
|
|
82
|
+
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// --------------------------------------------------
|
|
86
|
+
// CRUD
|
|
87
|
+
// --------------------------------------------------
|
|
88
|
+
|
|
89
|
+
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
|
+
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async delete(id) {
|
|
108
|
+
|
|
109
|
+
return this.kernel.db.user.delete({
|
|
110
|
+
|
|
111
|
+
where: { id }
|
|
112
|
+
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
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
|
+
});
|