taleem-kernel 1.3.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 +3 -3
- package/prisma/dev.db +0 -0
- package/prisma/migrations/20260829212536_init/migration.sql +96 -0
- package/prisma/schema.prisma +48 -26
- package/readme.md +151 -371
- package/src/{ServerKernel.js → index.js} +2 -0
- 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/prisma/content.db +0 -0
|
@@ -8,18 +8,12 @@ export default class Communication {
|
|
|
8
8
|
const where = {};
|
|
9
9
|
|
|
10
10
|
if (filters.courseSlug) {
|
|
11
|
-
|
|
12
|
-
where: { courseSlug: filters.courseSlug },
|
|
13
|
-
select: { slug: true }
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
where.librarySlug = {
|
|
17
|
-
in: items.map(item => item.slug)
|
|
18
|
-
};
|
|
11
|
+
where.library = { courseSlug: filters.courseSlug };
|
|
19
12
|
}
|
|
20
13
|
|
|
21
14
|
if (filters.librarySlug) where.librarySlug = filters.librarySlug;
|
|
22
15
|
if (filters.userId) where.userId = filters.userId;
|
|
16
|
+
if (filters.initiator) where.initiator = filters.initiator;
|
|
23
17
|
|
|
24
18
|
if (filters.unanswered) {
|
|
25
19
|
where.OR = [
|
|
@@ -30,35 +24,20 @@ export default class Communication {
|
|
|
30
24
|
|
|
31
25
|
return this.kernel.db.communication.findMany({
|
|
32
26
|
where,
|
|
33
|
-
include: { user: true },
|
|
27
|
+
include: { user: true, library: true },
|
|
34
28
|
orderBy: { createdAt: "desc" }
|
|
35
29
|
});
|
|
36
30
|
}
|
|
37
31
|
|
|
38
32
|
async get(id) {
|
|
39
|
-
|
|
33
|
+
return this.kernel.db.communication.findUnique({
|
|
40
34
|
where: { id },
|
|
41
|
-
include: { user: true }
|
|
35
|
+
include: { user: true, library: true }
|
|
42
36
|
});
|
|
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
37
|
}
|
|
60
38
|
|
|
61
39
|
async create(data) {
|
|
40
|
+
// throws if librarySlug doesn't resolve to an existing Library row
|
|
62
41
|
return this.kernel.db.communication.create({ data });
|
|
63
42
|
}
|
|
64
43
|
|
|
@@ -76,30 +55,7 @@ export default class Communication {
|
|
|
76
55
|
}
|
|
77
56
|
|
|
78
57
|
async listUnanswered(courseSlug) {
|
|
79
|
-
|
|
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
|
-
}));
|
|
58
|
+
return this.list({ courseSlug, unanswered: true });
|
|
103
59
|
}
|
|
104
60
|
|
|
105
61
|
async countUserOpenQuestions(userId) {
|
package/src/modules/Course.js
CHANGED
|
@@ -4,51 +4,44 @@ export default class Course {
|
|
|
4
4
|
async list(filters = {}) {
|
|
5
5
|
const where = {};
|
|
6
6
|
if (filters.access) where.access = filters.access;
|
|
7
|
+
if (filters.isActive !== undefined) where.isActive = filters.isActive;
|
|
7
8
|
return this.kernel.db.course.findMany({ where });
|
|
8
9
|
}
|
|
10
|
+
|
|
9
11
|
async get(slug) {
|
|
10
12
|
return this.kernel.db.course.findUnique({ where: { slug } });
|
|
11
13
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
slug: group.slug,
|
|
45
|
-
title: group.title,
|
|
46
|
-
thumbnail: group.thumbnail ?? null,
|
|
47
|
-
items: group.items ?? []
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return created;
|
|
14
|
+
|
|
15
|
+
async create(data) {
|
|
16
|
+
return this.kernel.db.course.create({ data });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async update(slug, data) {
|
|
20
|
+
return this.kernel.db.course.update({ where: { slug }, data });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async delete(slug) {
|
|
24
|
+
// throws if any Group or Subscription still references this course
|
|
25
|
+
return this.kernel.db.course.delete({ where: { slug } });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async authorize(userId, courseSlug) {
|
|
29
|
+
const course = await this.get(courseSlug);
|
|
30
|
+
if (!course) throw new Error(`Course "${courseSlug}" not found.`);
|
|
31
|
+
|
|
32
|
+
if (course.access === "OPEN") return true;
|
|
33
|
+
|
|
34
|
+
if (course.access === "MEMBERS") {
|
|
35
|
+
if (!userId) throw new Error("Login required for this course.");
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (course.access === "SUBSCRIPTION") {
|
|
40
|
+
if (!userId) throw new Error("Login required for this course.");
|
|
41
|
+
await this.kernel.subscription.authorize(userId, courseSlug);
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
throw new Error(`Unknown access level "${course.access}".`);
|
|
53
46
|
}
|
|
54
47
|
}
|
|
@@ -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/prisma/content.db
DELETED
|
Binary file
|