@orjok/commons 1.0.2
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/README.md +204 -0
- package/dist/amplify.cjs +129 -0
- package/dist/amplify.cjs.map +1 -0
- package/dist/amplify.d.cts +40 -0
- package/dist/amplify.d.ts +40 -0
- package/dist/amplify.js +129 -0
- package/dist/amplify.js.map +1 -0
- package/dist/client-BaOC79Nc.d.cts +775 -0
- package/dist/client-Bm9aiyTx.d.ts +775 -0
- package/dist/hooks.cjs +16 -0
- package/dist/hooks.cjs.map +1 -0
- package/dist/hooks.d.cts +8 -0
- package/dist/hooks.d.ts +8 -0
- package/dist/hooks.js +16 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.cjs +1723 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +105 -0
- package/dist/index.d.ts +105 -0
- package/dist/index.js +1723 -0
- package/dist/index.js.map +1 -0
- package/dist/storage-DLqdJgQt.d.cts +72 -0
- package/dist/storage-DLqdJgQt.d.ts +72 -0
- package/dist/testing.cjs +97 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +52 -0
- package/dist/testing.d.ts +52 -0
- package/dist/testing.js +97 -0
- package/dist/testing.js.map +1 -0
- package/package.json +104 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1723 @@
|
|
|
1
|
+
// src/services/user.service.ts
|
|
2
|
+
var UserService = class {
|
|
3
|
+
constructor(network) {
|
|
4
|
+
this.network = network;
|
|
5
|
+
}
|
|
6
|
+
async get(id) {
|
|
7
|
+
const result = await this.network.query(
|
|
8
|
+
`query GetUser($id: String!) {
|
|
9
|
+
getUser(id: $id) {
|
|
10
|
+
id fullName avatarUrl avatarConfig tier subscriptionExpiresAt
|
|
11
|
+
aiEvaluationRemaining questionCount packCount contestCount
|
|
12
|
+
EngineeringRating MedicalRating VarsityRating BCSRating
|
|
13
|
+
sscOverallRating sscPhysicsRating sscChemistryRating sscMathRating sscBiologyRating
|
|
14
|
+
hscOverallRating hscPhysicsRating hscChemistryRating hscMathRating hscBiologyRating
|
|
15
|
+
bcsOverallRating
|
|
16
|
+
}
|
|
17
|
+
}`,
|
|
18
|
+
{ id }
|
|
19
|
+
);
|
|
20
|
+
return result.data.getUser;
|
|
21
|
+
}
|
|
22
|
+
async updateAvatar(input) {
|
|
23
|
+
const result = await this.network.mutate(
|
|
24
|
+
`mutation UpdateUser($input: UpdateUserInput!) {
|
|
25
|
+
updateUser(input: $input) {
|
|
26
|
+
id fullName avatarUrl avatarConfig
|
|
27
|
+
}
|
|
28
|
+
}`,
|
|
29
|
+
{ input: { id: input.userId, avatarUrl: input.avatarUrl, avatarConfig: input.avatarConfig } }
|
|
30
|
+
);
|
|
31
|
+
return result.data.updateUser;
|
|
32
|
+
}
|
|
33
|
+
async getQuestions(userId, nextToken) {
|
|
34
|
+
const result = await this.network.query(
|
|
35
|
+
`query ListUserQuestions($owner: ID!, $nextToken: String, $sortDirection: ModelSortDirection) {
|
|
36
|
+
listQuestionObjectByOwnerAndCreatedAt(owner: $owner, sortDirection: $sortDirection, nextToken: $nextToken) {
|
|
37
|
+
items {
|
|
38
|
+
id question imageUrl language level difficulty type voteCount
|
|
39
|
+
verificationStatus owner packId createdAt
|
|
40
|
+
options { items { id content } }
|
|
41
|
+
tags { items { id tagId } }
|
|
42
|
+
}
|
|
43
|
+
nextToken
|
|
44
|
+
}
|
|
45
|
+
}`,
|
|
46
|
+
{ owner: userId, nextToken, sortDirection: "DESC" }
|
|
47
|
+
);
|
|
48
|
+
return result.data.listQuestionObjectByOwnerAndCreatedAt;
|
|
49
|
+
}
|
|
50
|
+
async getPacks(userId, nextToken) {
|
|
51
|
+
const result = await this.network.query(
|
|
52
|
+
`query ListUserPacks($owner: ID!, $nextToken: String, $sortDirection: ModelSortDirection) {
|
|
53
|
+
listPackByOwnerAndCreatedAt(owner: $owner, sortDirection: $sortDirection, nextToken: $nextToken) {
|
|
54
|
+
items {
|
|
55
|
+
id name language level difficulty questionCount automaticNumbering
|
|
56
|
+
owner subjectId chapterId topicId verificationStatus createdAt
|
|
57
|
+
tags { items { id tagId } }
|
|
58
|
+
}
|
|
59
|
+
nextToken
|
|
60
|
+
}
|
|
61
|
+
}`,
|
|
62
|
+
{ owner: userId, nextToken, sortDirection: "DESC" }
|
|
63
|
+
);
|
|
64
|
+
return result.data.listPackByOwnerAndCreatedAt;
|
|
65
|
+
}
|
|
66
|
+
async getCourses(userId) {
|
|
67
|
+
const result = await this.network.query(
|
|
68
|
+
`query ListUserCourses($owner: ID!, $sortDirection: ModelSortDirection) {
|
|
69
|
+
listCourseByOwnerAndCreatedAt(owner: $owner, sortDirection: $sortDirection) {
|
|
70
|
+
items {
|
|
71
|
+
id name description imageUrl videoCount fileCount price totalEnrolled owner createdAt
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}`,
|
|
75
|
+
{ owner: userId, sortDirection: "DESC" }
|
|
76
|
+
);
|
|
77
|
+
return result.data.listCourseByOwnerAndCreatedAt.items;
|
|
78
|
+
}
|
|
79
|
+
async getEnrolledCourses(userId) {
|
|
80
|
+
const result = await this.network.query(
|
|
81
|
+
`query ListEnrolledCourses($userId: ID!, $sortDirection: ModelSortDirection) {
|
|
82
|
+
listCourseEnrolledByUserIdAndCreatedAt(userId: $userId, sortDirection: $sortDirection) {
|
|
83
|
+
items {
|
|
84
|
+
id courseId userId score rank createdAt
|
|
85
|
+
course { id name description imageUrl price totalEnrolled }
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}`,
|
|
89
|
+
{ userId, sortDirection: "DESC" }
|
|
90
|
+
);
|
|
91
|
+
return result.data.listCourseEnrolledByUserIdAndCreatedAt.items;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// src/services/question.service.ts
|
|
96
|
+
var QuestionService = class {
|
|
97
|
+
constructor(network) {
|
|
98
|
+
this.network = network;
|
|
99
|
+
}
|
|
100
|
+
async create(input) {
|
|
101
|
+
const result = await this.network.mutate(
|
|
102
|
+
`mutation CreateQuestion($question: String!, $answer: String!, $options: String!, $language: String!, $level: String!, $difficulty: String!, $type: String, $explanation: String, $tags: String, $imagePayload: String, $optionsImagesPayload: String, $packId: ID, $order: Int, $contestId: ID, $subjectId: ID, $chapterId: ID, $topicId: ID, $extra: String, $markingInstructions: String) {
|
|
103
|
+
createQuestion(question: $question, answer: $answer, options: $options, language: $language, level: $level, difficulty: $difficulty, type: $type, explanation: $explanation, tags: $tags, imagePayload: $imagePayload, optionsImagesPayload: $optionsImagesPayload, packId: $packId, order: $order, contestId: $contestId, subjectId: $subjectId, chapterId: $chapterId, topicId: $topicId, extra: $extra, markingInstructions: $markingInstructions) {
|
|
104
|
+
status questionId
|
|
105
|
+
}
|
|
106
|
+
}`,
|
|
107
|
+
input
|
|
108
|
+
);
|
|
109
|
+
return result.data.createQuestion;
|
|
110
|
+
}
|
|
111
|
+
async get(id) {
|
|
112
|
+
const result = await this.network.query(
|
|
113
|
+
`query GetQuestion($id: String!) {
|
|
114
|
+
getQuestionObject(id: $id) {
|
|
115
|
+
id question imageUrl language level difficulty type voteCount
|
|
116
|
+
verificationStatus owner packId subjectId chapterId topicId order createdAt
|
|
117
|
+
options { items { id content } }
|
|
118
|
+
tags { items { id tagId } }
|
|
119
|
+
}
|
|
120
|
+
}`,
|
|
121
|
+
{ id }
|
|
122
|
+
);
|
|
123
|
+
return result.data.getQuestionObject;
|
|
124
|
+
}
|
|
125
|
+
async getFull(id) {
|
|
126
|
+
const result = await this.network.query(
|
|
127
|
+
`query GetFullQuestion($id: String!) {
|
|
128
|
+
getQuestionObject(id: $id) {
|
|
129
|
+
id question answer explanation extra imageUrl language level difficulty type
|
|
130
|
+
markingInstructions voteCount verificationStatus owner packId
|
|
131
|
+
subjectId chapterId topicId order createdAt
|
|
132
|
+
options { items { id content } }
|
|
133
|
+
tags { items { id tagId } }
|
|
134
|
+
user { id fullName avatarUrl }
|
|
135
|
+
}
|
|
136
|
+
}`,
|
|
137
|
+
{ id }
|
|
138
|
+
);
|
|
139
|
+
return result.data.getQuestionObject;
|
|
140
|
+
}
|
|
141
|
+
async getAnswer(id) {
|
|
142
|
+
const result = await this.network.query(
|
|
143
|
+
`query GetAnswer($id: String!) {
|
|
144
|
+
getQuestionObject(id: $id) { answer }
|
|
145
|
+
}`,
|
|
146
|
+
{ id }
|
|
147
|
+
);
|
|
148
|
+
if (!result.data.getQuestionObject) return null;
|
|
149
|
+
return { id, content: result.data.getQuestionObject.answer };
|
|
150
|
+
}
|
|
151
|
+
async getQuestionId(packId, order) {
|
|
152
|
+
const result = await this.network.query(
|
|
153
|
+
`query GetQuestionId($packId: ID!, $order: ModelIntKeyConditionInput) {
|
|
154
|
+
listQuestionObjectByPackIdAndOrder(packId: $packId, order: $order, limit: 1) {
|
|
155
|
+
items { id }
|
|
156
|
+
}
|
|
157
|
+
}`,
|
|
158
|
+
{ packId, order: { eq: order } }
|
|
159
|
+
);
|
|
160
|
+
return result.data.listQuestionObjectByPackIdAndOrder.items[0]?.id ?? null;
|
|
161
|
+
}
|
|
162
|
+
async update(id, fields) {
|
|
163
|
+
const result = await this.network.mutate(
|
|
164
|
+
`mutation UpdateQuestion($input: UpdateQuestionObjectInput!) {
|
|
165
|
+
updateQuestionObject(input: $input) {
|
|
166
|
+
id question answer explanation extra imageUrl language level difficulty type
|
|
167
|
+
markingInstructions voteCount owner packId subjectId chapterId topicId order createdAt
|
|
168
|
+
options { items { id content } }
|
|
169
|
+
}
|
|
170
|
+
}`,
|
|
171
|
+
{ input: { id, ...fields } }
|
|
172
|
+
);
|
|
173
|
+
return result.data.updateQuestionObject;
|
|
174
|
+
}
|
|
175
|
+
async delete(id) {
|
|
176
|
+
const result = await this.network.mutate(
|
|
177
|
+
`mutation DeleteQuestion($input: DeleteQuestionObjectInput!) {
|
|
178
|
+
deleteQuestionObject(input: $input) { id }
|
|
179
|
+
}`,
|
|
180
|
+
{ input: { id } }
|
|
181
|
+
);
|
|
182
|
+
return result.data.deleteQuestionObject;
|
|
183
|
+
}
|
|
184
|
+
async vote(input) {
|
|
185
|
+
const result = await this.network.mutate(
|
|
186
|
+
`mutation Vote($questionId: ID!, $type: String!, $modelType: String!) {
|
|
187
|
+
vote(questionId: $questionId, type: $type, modelType: $modelType) {
|
|
188
|
+
status id
|
|
189
|
+
}
|
|
190
|
+
}`,
|
|
191
|
+
input
|
|
192
|
+
);
|
|
193
|
+
return result.data.vote;
|
|
194
|
+
}
|
|
195
|
+
async upVote(questionId) {
|
|
196
|
+
return this.vote({ questionId, type: "upvote", modelType: "question" });
|
|
197
|
+
}
|
|
198
|
+
async downVote(questionId) {
|
|
199
|
+
return this.vote({ questionId, type: "downvote", modelType: "question" });
|
|
200
|
+
}
|
|
201
|
+
async verify(id, userId) {
|
|
202
|
+
const result = await this.network.mutate(
|
|
203
|
+
`mutation VerifyQuestion($input: CreateVerificationInput!) {
|
|
204
|
+
createVerification(input: $input) { id verifiedObjectId type userId }
|
|
205
|
+
}`,
|
|
206
|
+
{ input: { verifiedObjectId: id, type: "question", userId } }
|
|
207
|
+
);
|
|
208
|
+
return result.data.createVerification;
|
|
209
|
+
}
|
|
210
|
+
async report(id, reason) {
|
|
211
|
+
const result = await this.network.mutate(
|
|
212
|
+
`mutation ReportQuestion($input: CreateQuestionReportInput!) {
|
|
213
|
+
createQuestionReport(input: $input) { id }
|
|
214
|
+
}`,
|
|
215
|
+
{ input: { questionId: id, reason } }
|
|
216
|
+
);
|
|
217
|
+
return result.data.createQuestionReport;
|
|
218
|
+
}
|
|
219
|
+
async addTags(input) {
|
|
220
|
+
const result = await this.network.mutate(
|
|
221
|
+
`mutation AddQuestionTag($tags: String!, $packId: ID, $questionId: ID) {
|
|
222
|
+
addQuestionTag(tags: $tags, packId: $packId, questionId: $questionId) {
|
|
223
|
+
status
|
|
224
|
+
}
|
|
225
|
+
}`,
|
|
226
|
+
input
|
|
227
|
+
);
|
|
228
|
+
return result.data.addQuestionTag;
|
|
229
|
+
}
|
|
230
|
+
async listBySubject(input) {
|
|
231
|
+
const variables = {
|
|
232
|
+
subjectId: input.id,
|
|
233
|
+
sortDirection: input.sortDirection ?? "DESC",
|
|
234
|
+
limit: input.limit ?? 20,
|
|
235
|
+
nextToken: input.nextToken
|
|
236
|
+
};
|
|
237
|
+
const result = await this.network.query(
|
|
238
|
+
`query ListQuestionsBySubject($subjectId: String!, $sortDirection: ModelSortDirection, $limit: Int, $nextToken: String) {
|
|
239
|
+
listQuestionObjectBySubjectIdAndCreatedAt(subjectId: $subjectId, sortDirection: $sortDirection, limit: $limit, nextToken: $nextToken) {
|
|
240
|
+
items {
|
|
241
|
+
id question imageUrl language level difficulty type voteCount
|
|
242
|
+
verificationStatus owner packId createdAt
|
|
243
|
+
options { items { id content } }
|
|
244
|
+
}
|
|
245
|
+
nextToken
|
|
246
|
+
}
|
|
247
|
+
}`,
|
|
248
|
+
variables
|
|
249
|
+
);
|
|
250
|
+
return result.data.listQuestionObjectBySubjectIdAndCreatedAt;
|
|
251
|
+
}
|
|
252
|
+
async listByChapter(input) {
|
|
253
|
+
const variables = {
|
|
254
|
+
chapterId: input.id,
|
|
255
|
+
sortDirection: input.sortDirection ?? "DESC",
|
|
256
|
+
limit: input.limit ?? 20,
|
|
257
|
+
nextToken: input.nextToken
|
|
258
|
+
};
|
|
259
|
+
const result = await this.network.query(
|
|
260
|
+
`query ListQuestionsByChapter($chapterId: String!, $sortDirection: ModelSortDirection, $limit: Int, $nextToken: String) {
|
|
261
|
+
listQuestionObjectByChapterIdAndCreatedAt(chapterId: $chapterId, sortDirection: $sortDirection, limit: $limit, nextToken: $nextToken) {
|
|
262
|
+
items {
|
|
263
|
+
id question imageUrl language level difficulty type voteCount
|
|
264
|
+
verificationStatus owner packId createdAt
|
|
265
|
+
options { items { id content } }
|
|
266
|
+
}
|
|
267
|
+
nextToken
|
|
268
|
+
}
|
|
269
|
+
}`,
|
|
270
|
+
variables
|
|
271
|
+
);
|
|
272
|
+
return result.data.listQuestionObjectByChapterIdAndCreatedAt;
|
|
273
|
+
}
|
|
274
|
+
async listByTopic(input) {
|
|
275
|
+
const variables = {
|
|
276
|
+
topicId: input.id,
|
|
277
|
+
sortDirection: input.sortDirection ?? "DESC",
|
|
278
|
+
limit: input.limit ?? 20,
|
|
279
|
+
nextToken: input.nextToken
|
|
280
|
+
};
|
|
281
|
+
const result = await this.network.query(
|
|
282
|
+
`query ListQuestionsByTopic($topicId: String!, $sortDirection: ModelSortDirection, $limit: Int, $nextToken: String) {
|
|
283
|
+
listQuestionObjectByTopicIdAndCreatedAt(topicId: $topicId, sortDirection: $sortDirection, limit: $limit, nextToken: $nextToken) {
|
|
284
|
+
items {
|
|
285
|
+
id question imageUrl language level difficulty type voteCount
|
|
286
|
+
verificationStatus owner packId createdAt
|
|
287
|
+
options { items { id content } }
|
|
288
|
+
}
|
|
289
|
+
nextToken
|
|
290
|
+
}
|
|
291
|
+
}`,
|
|
292
|
+
variables
|
|
293
|
+
);
|
|
294
|
+
return result.data.listQuestionObjectByTopicIdAndCreatedAt;
|
|
295
|
+
}
|
|
296
|
+
async listByRandomHash(input) {
|
|
297
|
+
const compositeKey = [input.language, input.difficulty].filter(Boolean).join("_");
|
|
298
|
+
const variables = {
|
|
299
|
+
subjectId: input.id,
|
|
300
|
+
sortDirection: input.sortDirection ?? "ASC",
|
|
301
|
+
limit: input.limit ?? 1,
|
|
302
|
+
language_difficulty_randomHash: input.randomHashFilter ? { beginsWith: compositeKey, ...input.randomHashFilter } : { beginsWith: compositeKey }
|
|
303
|
+
};
|
|
304
|
+
const result = await this.network.query(
|
|
305
|
+
`query ListByRandomHash($subjectId: String!, $language_difficulty_randomHash: ModelStringKeyConditionInput, $sortDirection: ModelSortDirection, $limit: Int) {
|
|
306
|
+
listQuestionObjectBySubjectIdAndLanguage_difficulty_randomHash(subjectId: $subjectId, language_difficulty_randomHash: $language_difficulty_randomHash, sortDirection: $sortDirection, limit: $limit) {
|
|
307
|
+
items {
|
|
308
|
+
id question imageUrl language level difficulty type voteCount
|
|
309
|
+
verificationStatus owner packId subjectId chapterId topicId createdAt
|
|
310
|
+
options { items { id content } }
|
|
311
|
+
}
|
|
312
|
+
nextToken
|
|
313
|
+
}
|
|
314
|
+
}`,
|
|
315
|
+
variables
|
|
316
|
+
);
|
|
317
|
+
return result.data.listQuestionObjectBySubjectIdAndLanguage_difficulty_randomHash;
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
// src/services/pack.service.ts
|
|
322
|
+
var PackService = class {
|
|
323
|
+
constructor(network) {
|
|
324
|
+
this.network = network;
|
|
325
|
+
}
|
|
326
|
+
async create(input) {
|
|
327
|
+
const result = await this.network.mutate(
|
|
328
|
+
`mutation CreatePack($name: String!, $level: String!, $language: String!, $difficulty: String!, $automaticNumbering: Boolean, $subjectId: ID, $chapterId: ID, $topicId: ID) {
|
|
329
|
+
createPackObject(name: $name, level: $level, language: $language, difficulty: $difficulty, automaticNumbering: $automaticNumbering, subjectId: $subjectId, chapterId: $chapterId, topicId: $topicId) {
|
|
330
|
+
status id
|
|
331
|
+
}
|
|
332
|
+
}`,
|
|
333
|
+
input
|
|
334
|
+
);
|
|
335
|
+
return result.data.createPackObject;
|
|
336
|
+
}
|
|
337
|
+
async get(id) {
|
|
338
|
+
const result = await this.network.query(
|
|
339
|
+
`query GetPack($id: ID!) {
|
|
340
|
+
getPack(id: $id) {
|
|
341
|
+
id name language level difficulty questionCount automaticNumbering
|
|
342
|
+
owner subjectId chapterId topicId packGroupId verificationStatus createdAt
|
|
343
|
+
tags { items { id tagId } }
|
|
344
|
+
user { id fullName avatarUrl }
|
|
345
|
+
}
|
|
346
|
+
}`,
|
|
347
|
+
{ id }
|
|
348
|
+
);
|
|
349
|
+
return result.data.getPack;
|
|
350
|
+
}
|
|
351
|
+
async getFull(id) {
|
|
352
|
+
const result = await this.network.query(
|
|
353
|
+
`query GetFullPack($id: ID!) {
|
|
354
|
+
getPack(id: $id) {
|
|
355
|
+
id name language level difficulty questionCount automaticNumbering
|
|
356
|
+
owner subjectId chapterId topicId packGroupId verificationStatus createdAt
|
|
357
|
+
tags { items { id tagId } }
|
|
358
|
+
user { id fullName avatarUrl }
|
|
359
|
+
questions(sortDirection: ASC, limit: 200) {
|
|
360
|
+
items {
|
|
361
|
+
id question answer explanation extra imageUrl language level difficulty type
|
|
362
|
+
markingInstructions voteCount owner order createdAt
|
|
363
|
+
options { items { id content } }
|
|
364
|
+
tags { items { id tagId } }
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}`,
|
|
369
|
+
{ id }
|
|
370
|
+
);
|
|
371
|
+
return result.data.getPack;
|
|
372
|
+
}
|
|
373
|
+
async update(id, fields) {
|
|
374
|
+
const result = await this.network.mutate(
|
|
375
|
+
`mutation UpdatePack($input: UpdatePackInput!) {
|
|
376
|
+
updatePack(input: $input) {
|
|
377
|
+
id name language level difficulty questionCount automaticNumbering
|
|
378
|
+
owner subjectId chapterId topicId verificationStatus createdAt
|
|
379
|
+
}
|
|
380
|
+
}`,
|
|
381
|
+
{ input: { id, ...fields } }
|
|
382
|
+
);
|
|
383
|
+
return result.data.updatePack;
|
|
384
|
+
}
|
|
385
|
+
async delete(id) {
|
|
386
|
+
const result = await this.network.mutate(
|
|
387
|
+
`mutation DeletePack($input: DeletePackInput!) {
|
|
388
|
+
deletePack(input: $input) { id }
|
|
389
|
+
}`,
|
|
390
|
+
{ input: { id } }
|
|
391
|
+
);
|
|
392
|
+
return result.data.deletePack;
|
|
393
|
+
}
|
|
394
|
+
async verify(id, userId) {
|
|
395
|
+
const result = await this.network.mutate(
|
|
396
|
+
`mutation VerifyPack($input: CreateVerificationInput!) {
|
|
397
|
+
createVerification(input: $input) { id verifiedObjectId type userId }
|
|
398
|
+
}`,
|
|
399
|
+
{ input: { verifiedObjectId: id, type: "pack", userId } }
|
|
400
|
+
);
|
|
401
|
+
return result.data.createVerification;
|
|
402
|
+
}
|
|
403
|
+
async report(id, reason) {
|
|
404
|
+
const result = await this.network.mutate(
|
|
405
|
+
`mutation ReportPack($input: CreatePackReportInput!) {
|
|
406
|
+
createPackReport(input: $input) { id }
|
|
407
|
+
}`,
|
|
408
|
+
{ input: { packId: id, reason } }
|
|
409
|
+
);
|
|
410
|
+
return result.data.createPackReport;
|
|
411
|
+
}
|
|
412
|
+
async submitExam(packId, selectedOptions) {
|
|
413
|
+
const result = await this.network.mutate(
|
|
414
|
+
`mutation SubmitPackExam($packId: ID!, $selectedOptions: String!) {
|
|
415
|
+
submitPackExam(packId: $packId, selectedOptions: $selectedOptions) {
|
|
416
|
+
status id
|
|
417
|
+
}
|
|
418
|
+
}`,
|
|
419
|
+
{ packId, selectedOptions: JSON.stringify(selectedOptions) }
|
|
420
|
+
);
|
|
421
|
+
return result.data.submitPackExam;
|
|
422
|
+
}
|
|
423
|
+
async getExams(userId, nextToken) {
|
|
424
|
+
const result = await this.network.query(
|
|
425
|
+
`query ListPackExams($owner: ID!, $sortDirection: ModelSortDirection, $nextToken: String) {
|
|
426
|
+
listPackExamResultsByOwnerAndCreatedAt(owner: $owner, sortDirection: $sortDirection, nextToken: $nextToken) {
|
|
427
|
+
items {
|
|
428
|
+
id packId selectedOptions aiEvaluations owner createdAt
|
|
429
|
+
pack { id name language level difficulty questionCount }
|
|
430
|
+
}
|
|
431
|
+
nextToken
|
|
432
|
+
}
|
|
433
|
+
}`,
|
|
434
|
+
{ owner: userId, sortDirection: "DESC", nextToken }
|
|
435
|
+
);
|
|
436
|
+
return result.data.listPackExamResultsByOwnerAndCreatedAt;
|
|
437
|
+
}
|
|
438
|
+
async getExamResult(resultId) {
|
|
439
|
+
const result = await this.network.query(
|
|
440
|
+
`query GetPackExamResult($id: ID!) {
|
|
441
|
+
getPackExamResults(id: $id) {
|
|
442
|
+
id packId selectedOptions aiEvaluations owner createdAt
|
|
443
|
+
pack { id name language level difficulty questionCount }
|
|
444
|
+
}
|
|
445
|
+
}`,
|
|
446
|
+
{ id: resultId }
|
|
447
|
+
);
|
|
448
|
+
return result.data.getPackExamResults;
|
|
449
|
+
}
|
|
450
|
+
async listBySubject(input) {
|
|
451
|
+
const result = await this.network.query(
|
|
452
|
+
`query ListPacksBySubject($subjectId: String!, $sortDirection: ModelSortDirection, $limit: Int, $nextToken: String) {
|
|
453
|
+
listPackBySubjectIdAndCreatedAt(subjectId: $subjectId, sortDirection: $sortDirection, limit: $limit, nextToken: $nextToken) {
|
|
454
|
+
items {
|
|
455
|
+
id name language level difficulty questionCount owner createdAt verificationStatus
|
|
456
|
+
tags { items { id tagId } }
|
|
457
|
+
}
|
|
458
|
+
nextToken
|
|
459
|
+
}
|
|
460
|
+
}`,
|
|
461
|
+
{ subjectId: input.id, sortDirection: "DESC", limit: input.limit ?? 20, nextToken: input.nextToken }
|
|
462
|
+
);
|
|
463
|
+
return result.data.listPackBySubjectIdAndCreatedAt;
|
|
464
|
+
}
|
|
465
|
+
async listByChapter(input) {
|
|
466
|
+
const result = await this.network.query(
|
|
467
|
+
`query ListPacksByChapter($chapterId: String!, $sortDirection: ModelSortDirection, $limit: Int, $nextToken: String) {
|
|
468
|
+
listPackByChapterIdAndCreatedAt(chapterId: $chapterId, sortDirection: $sortDirection, limit: $limit, nextToken: $nextToken) {
|
|
469
|
+
items {
|
|
470
|
+
id name language level difficulty questionCount owner createdAt verificationStatus
|
|
471
|
+
tags { items { id tagId } }
|
|
472
|
+
}
|
|
473
|
+
nextToken
|
|
474
|
+
}
|
|
475
|
+
}`,
|
|
476
|
+
{ chapterId: input.id, sortDirection: "DESC", limit: input.limit ?? 20, nextToken: input.nextToken }
|
|
477
|
+
);
|
|
478
|
+
return result.data.listPackByChapterIdAndCreatedAt;
|
|
479
|
+
}
|
|
480
|
+
async listByTopic(input) {
|
|
481
|
+
const result = await this.network.query(
|
|
482
|
+
`query ListPacksByTopic($topicId: String!, $sortDirection: ModelSortDirection, $limit: Int, $nextToken: String) {
|
|
483
|
+
listPackByTopicIdAndCreatedAt(topicId: $topicId, sortDirection: $sortDirection, limit: $limit, nextToken: $nextToken) {
|
|
484
|
+
items {
|
|
485
|
+
id name language level difficulty questionCount owner createdAt verificationStatus
|
|
486
|
+
tags { items { id tagId } }
|
|
487
|
+
}
|
|
488
|
+
nextToken
|
|
489
|
+
}
|
|
490
|
+
}`,
|
|
491
|
+
{ topicId: input.id, sortDirection: "DESC", limit: input.limit ?? 20, nextToken: input.nextToken }
|
|
492
|
+
);
|
|
493
|
+
return result.data.listPackByTopicIdAndCreatedAt;
|
|
494
|
+
}
|
|
495
|
+
async listGroups(type, level) {
|
|
496
|
+
const queryField = level ? "listPackGroupByTypeLevelAndOrder" : "listPackGroupByTypeAndOrder";
|
|
497
|
+
const variables = level ? { typeLevel: `${type}_${level}`, sortDirection: "ASC" } : { type, sortDirection: "ASC" };
|
|
498
|
+
const result = await this.network.query(
|
|
499
|
+
level ? `query ListPackGroups($typeLevel: String!, $sortDirection: ModelSortDirection) {
|
|
500
|
+
listPackGroupByTypeLevelAndOrder(typeLevel: $typeLevel, sortDirection: $sortDirection) {
|
|
501
|
+
items { id name description imageUrl type level typeLevel packCount order }
|
|
502
|
+
}
|
|
503
|
+
}` : `query ListPackGroups($type: String!, $sortDirection: ModelSortDirection) {
|
|
504
|
+
listPackGroupByTypeAndOrder(type: $type, sortDirection: $sortDirection) {
|
|
505
|
+
items { id name description imageUrl type level typeLevel packCount order }
|
|
506
|
+
}
|
|
507
|
+
}`,
|
|
508
|
+
variables
|
|
509
|
+
);
|
|
510
|
+
return result.data[queryField].items;
|
|
511
|
+
}
|
|
512
|
+
async getGroup(id) {
|
|
513
|
+
const result = await this.network.query(
|
|
514
|
+
`query GetPackGroup($id: ID!) {
|
|
515
|
+
getPackGroup(id: $id) {
|
|
516
|
+
id name description imageUrl type level typeLevel packCount order
|
|
517
|
+
packs { items { id name language level difficulty questionCount owner createdAt verificationStatus } }
|
|
518
|
+
}
|
|
519
|
+
}`,
|
|
520
|
+
{ id }
|
|
521
|
+
);
|
|
522
|
+
return result.data.getPackGroup;
|
|
523
|
+
}
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
// src/services/contest.service.ts
|
|
527
|
+
var ContestService = class {
|
|
528
|
+
constructor(network) {
|
|
529
|
+
this.network = network;
|
|
530
|
+
}
|
|
531
|
+
async create(input) {
|
|
532
|
+
const result = await this.network.mutate(
|
|
533
|
+
`mutation MakeContest($name: String!, $description: String!, $level: String!, $language: String!, $startTime: AWSDateTime!, $endTime: AWSDateTime!, $subject: String!, $isRated: Boolean!, $questions: String!, $maximumParticipants: Int!, $solutionVideoUrl: String, $imageUrl: String) {
|
|
534
|
+
makeContest(name: $name, description: $description, level: $level, language: $language, startTime: $startTime, endTime: $endTime, subject: $subject, isRated: $isRated, questions: $questions, maximumParticipants: $maximumParticipants, solutionVideoUrl: $solutionVideoUrl, imageUrl: $imageUrl) {
|
|
535
|
+
status id
|
|
536
|
+
}
|
|
537
|
+
}`,
|
|
538
|
+
input
|
|
539
|
+
);
|
|
540
|
+
return result.data.makeContest;
|
|
541
|
+
}
|
|
542
|
+
async get(id) {
|
|
543
|
+
const result = await this.network.query(
|
|
544
|
+
`query GetContest($id: ID!) {
|
|
545
|
+
getContest(id: $id) {
|
|
546
|
+
id name description startTime endTime level language owner isRated subject
|
|
547
|
+
totalQuestions resultsPublished totalParticipants maximumParticipants
|
|
548
|
+
rankings globalPk solutionVideoUrl imageUrl
|
|
549
|
+
}
|
|
550
|
+
}`,
|
|
551
|
+
{ id }
|
|
552
|
+
);
|
|
553
|
+
return result.data.getContest;
|
|
554
|
+
}
|
|
555
|
+
async getName(id) {
|
|
556
|
+
const result = await this.network.query(
|
|
557
|
+
`query GetContestName($id: ID!) { getContest(id: $id) { name } }`,
|
|
558
|
+
{ id }
|
|
559
|
+
);
|
|
560
|
+
return result.data.getContest?.name ?? null;
|
|
561
|
+
}
|
|
562
|
+
async list(nextToken, level) {
|
|
563
|
+
const variables = level ? { level, sortDirection: "DESC", nextToken } : { globalPk: "ALL", sortDirection: "DESC", nextToken };
|
|
564
|
+
const result = await this.network.query(
|
|
565
|
+
level ? `query ListContests($level: String!, $sortDirection: ModelSortDirection, $nextToken: String) {
|
|
566
|
+
listContestByLevelAndStartTime(level: $level, sortDirection: $sortDirection, nextToken: $nextToken) {
|
|
567
|
+
items { id name description startTime endTime level language owner isRated subject totalQuestions resultsPublished totalParticipants maximumParticipants solutionVideoUrl imageUrl }
|
|
568
|
+
nextToken
|
|
569
|
+
}
|
|
570
|
+
}` : `query ListContests($globalPk: String!, $sortDirection: ModelSortDirection, $nextToken: String) {
|
|
571
|
+
listContestByGlobalPkAndStartTime(globalPk: $globalPk, sortDirection: $sortDirection, nextToken: $nextToken) {
|
|
572
|
+
items { id name description startTime endTime level language owner isRated subject totalQuestions resultsPublished totalParticipants maximumParticipants solutionVideoUrl imageUrl }
|
|
573
|
+
nextToken
|
|
574
|
+
}
|
|
575
|
+
}`,
|
|
576
|
+
variables
|
|
577
|
+
);
|
|
578
|
+
const key = level ? "listContestByLevelAndStartTime" : "listContestByGlobalPkAndStartTime";
|
|
579
|
+
return result.data[key];
|
|
580
|
+
}
|
|
581
|
+
async participate(contestId) {
|
|
582
|
+
const result = await this.network.mutate(
|
|
583
|
+
`mutation ParticipateContest($contestId: ID!) {
|
|
584
|
+
participateContest(contestId: $contestId) { status id }
|
|
585
|
+
}`,
|
|
586
|
+
{ contestId }
|
|
587
|
+
);
|
|
588
|
+
return result.data.participateContest;
|
|
589
|
+
}
|
|
590
|
+
async checkParticipation(contestId) {
|
|
591
|
+
const result = await this.network.query(
|
|
592
|
+
`query CheckParticipation($contestId: ID!) {
|
|
593
|
+
checkUserParticipation(contestId: $contestId) {
|
|
594
|
+
status participated selectedOptions score rank beforeRating afterRating
|
|
595
|
+
}
|
|
596
|
+
}`,
|
|
597
|
+
{ contestId }
|
|
598
|
+
);
|
|
599
|
+
return result.data.checkUserParticipation;
|
|
600
|
+
}
|
|
601
|
+
async selectOption(contestId, optionId, order) {
|
|
602
|
+
const result = await this.network.mutate(
|
|
603
|
+
`mutation SelectContestOption($contestId: ID!, $optionId: String!, $order: Int!) {
|
|
604
|
+
selectContestOption(contestId: $contestId, optionId: $optionId, order: $order) { status }
|
|
605
|
+
}`,
|
|
606
|
+
{ contestId, optionId, order }
|
|
607
|
+
);
|
|
608
|
+
return result.data.selectContestOption;
|
|
609
|
+
}
|
|
610
|
+
async getQuestions(contestId) {
|
|
611
|
+
const result = await this.network.query(
|
|
612
|
+
`query GetContestQuestions($contestId: ID!) {
|
|
613
|
+
getContestQuestions(contestId: $contestId) { status questions }
|
|
614
|
+
}`,
|
|
615
|
+
{ contestId }
|
|
616
|
+
);
|
|
617
|
+
return result.data.getContestQuestions;
|
|
618
|
+
}
|
|
619
|
+
async getAnswers(contestId) {
|
|
620
|
+
const result = await this.network.query(
|
|
621
|
+
`query GetContestAnswers($contestId: ID!) {
|
|
622
|
+
getContestAnswers(contestId: $contestId) { status answers }
|
|
623
|
+
}`,
|
|
624
|
+
{ contestId }
|
|
625
|
+
);
|
|
626
|
+
return result.data.getContestAnswers;
|
|
627
|
+
}
|
|
628
|
+
async publish(contestId) {
|
|
629
|
+
const result = await this.network.mutate(
|
|
630
|
+
`mutation PublishResults($contestId: ID!) {
|
|
631
|
+
publishContestResults(contestId: $contestId) { status }
|
|
632
|
+
}`,
|
|
633
|
+
{ contestId }
|
|
634
|
+
);
|
|
635
|
+
return result.data.publishContestResults;
|
|
636
|
+
}
|
|
637
|
+
async getLeaderboard(contestId, nextToken) {
|
|
638
|
+
const result = await this.network.query(
|
|
639
|
+
`query GetLeaderboard($contestId: ID!, $sortDirection: ModelSortDirection, $nextToken: String) {
|
|
640
|
+
listContestParticipantByContestIdAndRank(contestId: $contestId, sortDirection: $sortDirection, nextToken: $nextToken) {
|
|
641
|
+
items { id contestId userId rank score afterRating beforeRating createdAt user { id fullName avatarUrl } }
|
|
642
|
+
nextToken
|
|
643
|
+
}
|
|
644
|
+
}`,
|
|
645
|
+
{ contestId, sortDirection: "ASC", nextToken }
|
|
646
|
+
);
|
|
647
|
+
const data = result.data.listContestParticipantByContestIdAndRank;
|
|
648
|
+
return {
|
|
649
|
+
status: "success",
|
|
650
|
+
leaderboard: JSON.stringify(data.items),
|
|
651
|
+
nextToken: data.nextToken
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
async getUserResults(userId, nextToken) {
|
|
655
|
+
const result = await this.network.query(
|
|
656
|
+
`query GetUserResults($userId: ID!, $sortDirection: ModelSortDirection, $nextToken: String) {
|
|
657
|
+
listContestParticipantByUserIdAndCreatedAt(userId: $userId, sortDirection: $sortDirection, nextToken: $nextToken) {
|
|
658
|
+
items { id contestId userId rank score afterRating beforeRating selectedOptions createdAt contest { id name subject level startTime } }
|
|
659
|
+
nextToken
|
|
660
|
+
}
|
|
661
|
+
}`,
|
|
662
|
+
{ userId, sortDirection: "DESC", nextToken }
|
|
663
|
+
);
|
|
664
|
+
return result.data.listContestParticipantByUserIdAndCreatedAt;
|
|
665
|
+
}
|
|
666
|
+
async updateSolutionVideo(contestId, url) {
|
|
667
|
+
const result = await this.network.mutate(
|
|
668
|
+
`mutation UpdateSolutionVideo($input: UpdateContestInput!) {
|
|
669
|
+
updateContest(input: $input) { id solutionVideoUrl }
|
|
670
|
+
}`,
|
|
671
|
+
{ input: { id: contestId, solutionVideoUrl: url } }
|
|
672
|
+
);
|
|
673
|
+
return { status: result.data.updateContest ? "success" : "error" };
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
|
|
677
|
+
// src/services/course.service.ts
|
|
678
|
+
var CourseService = class {
|
|
679
|
+
constructor(network) {
|
|
680
|
+
this.network = network;
|
|
681
|
+
}
|
|
682
|
+
async create(input) {
|
|
683
|
+
const result = await this.network.mutate(
|
|
684
|
+
`mutation MakeCourse($name: String!, $description: String!, $price: Int!, $imageUrl: String) {
|
|
685
|
+
makeCourse(name: $name, description: $description, price: $price, imageUrl: $imageUrl) { status courseId }
|
|
686
|
+
}`,
|
|
687
|
+
input
|
|
688
|
+
);
|
|
689
|
+
return result.data.makeCourse;
|
|
690
|
+
}
|
|
691
|
+
async get(id) {
|
|
692
|
+
const result = await this.network.query(
|
|
693
|
+
`query GetCourse($id: ID!) {
|
|
694
|
+
getCourse(id: $id) { id name description imageUrl videoCount fileCount price totalEnrolled owner createdAt }
|
|
695
|
+
}`,
|
|
696
|
+
{ id }
|
|
697
|
+
);
|
|
698
|
+
return result.data.getCourse;
|
|
699
|
+
}
|
|
700
|
+
async getItems(courseId) {
|
|
701
|
+
const result = await this.network.query(
|
|
702
|
+
`query ListCourseItems($courseId: ID!, $sortDirection: ModelSortDirection) {
|
|
703
|
+
listCourseItemsByCourseIdAndOrder(courseId: $courseId, sortDirection: $sortDirection) {
|
|
704
|
+
items { id type name courseId parentId order owner }
|
|
705
|
+
}
|
|
706
|
+
}`,
|
|
707
|
+
{ courseId, sortDirection: "ASC" }
|
|
708
|
+
);
|
|
709
|
+
return result.data.listCourseItemsByCourseIdAndOrder.items;
|
|
710
|
+
}
|
|
711
|
+
async createItem(input) {
|
|
712
|
+
const result = await this.network.mutate(
|
|
713
|
+
`mutation CreateCourseItem($courseId: ID!, $name: String!, $order: Int!, $type: CourseItemsType, $parentId: ID, $id: ID) {
|
|
714
|
+
createCourseItem(courseId: $courseId, name: $name, order: $order, type: $type, parentId: $parentId, id: $id) { status id }
|
|
715
|
+
}`,
|
|
716
|
+
input
|
|
717
|
+
);
|
|
718
|
+
return result.data.createCourseItem;
|
|
719
|
+
}
|
|
720
|
+
async updateItem(id, name, order) {
|
|
721
|
+
const result = await this.network.mutate(
|
|
722
|
+
`mutation UpdateCourseItem($input: UpdateCourseItemsInput!) {
|
|
723
|
+
updateCourseItems(input: $input) { id type name courseId parentId order owner }
|
|
724
|
+
}`,
|
|
725
|
+
{ input: { id, name, order } }
|
|
726
|
+
);
|
|
727
|
+
return result.data.updateCourseItems;
|
|
728
|
+
}
|
|
729
|
+
async deleteItem(input) {
|
|
730
|
+
const result = await this.network.mutate(
|
|
731
|
+
`mutation DeleteCourseItem($id: ID!, $fileType: String!, $courseId: ID!, $parentId: ID) {
|
|
732
|
+
deleteCourseItem(id: $id, fileType: $fileType, courseId: $courseId, parentId: $parentId) { status }
|
|
733
|
+
}`,
|
|
734
|
+
input
|
|
735
|
+
);
|
|
736
|
+
return result.data.deleteCourseItem;
|
|
737
|
+
}
|
|
738
|
+
async enroll(input) {
|
|
739
|
+
const result = await this.network.mutate(
|
|
740
|
+
`mutation EnrollStudent($courseId: ID!, $userId: ID!) {
|
|
741
|
+
enrollStudent(courseId: $courseId, userId: $userId) { status id }
|
|
742
|
+
}`,
|
|
743
|
+
input
|
|
744
|
+
);
|
|
745
|
+
return result.data.enrollStudent;
|
|
746
|
+
}
|
|
747
|
+
async getEnrolledStudent(courseId, userId) {
|
|
748
|
+
const result = await this.network.query(
|
|
749
|
+
`query GetEnrolledStudent($courseId: ID!, $filter: ModelCourseEnrolledFilterInput) {
|
|
750
|
+
listCourseEnrolledByCourseIdAndCreatedAt(courseId: $courseId, filter: $filter) {
|
|
751
|
+
items { id courseId userId owner score rank createdAt }
|
|
752
|
+
}
|
|
753
|
+
}`,
|
|
754
|
+
{ courseId, filter: { userId: { eq: userId } } }
|
|
755
|
+
);
|
|
756
|
+
return result.data.listCourseEnrolledByCourseIdAndCreatedAt.items[0] ?? null;
|
|
757
|
+
}
|
|
758
|
+
async getEnrolledStudents(courseId, nextToken) {
|
|
759
|
+
const result = await this.network.query(
|
|
760
|
+
`query ListEnrolledStudents($courseId: ID!, $sortDirection: ModelSortDirection, $nextToken: String) {
|
|
761
|
+
listCourseEnrolledByCourseIdAndCreatedAt(courseId: $courseId, sortDirection: $sortDirection, nextToken: $nextToken) {
|
|
762
|
+
items { id courseId userId owner score rank createdAt user { id fullName avatarUrl } }
|
|
763
|
+
nextToken
|
|
764
|
+
}
|
|
765
|
+
}`,
|
|
766
|
+
{ courseId, sortDirection: "DESC", nextToken }
|
|
767
|
+
);
|
|
768
|
+
return result.data.listCourseEnrolledByCourseIdAndCreatedAt;
|
|
769
|
+
}
|
|
770
|
+
async sendEnrollmentRequest(input) {
|
|
771
|
+
const result = await this.network.mutate(
|
|
772
|
+
`mutation SendEnrollmentRequest($input: CreateCourseEnrollmentRequestInput!) {
|
|
773
|
+
createCourseEnrollmentRequest(input: $input) { id }
|
|
774
|
+
}`,
|
|
775
|
+
{ input: { courseId: input.courseId, name: input.name, phone: input.phone, transactionID: input.transactionId, coupon: input.couponId } }
|
|
776
|
+
);
|
|
777
|
+
return result.data.createCourseEnrollmentRequest;
|
|
778
|
+
}
|
|
779
|
+
async getSignedURL(input) {
|
|
780
|
+
const result = await this.network.query(
|
|
781
|
+
`query GetR2SignedURL($courseId: ID!, $fileName: String!, $fileType: String!, $parentId: ID) {
|
|
782
|
+
getR2SignedURL(courseId: $courseId, fileName: $fileName, fileType: $fileType, parentId: $parentId) { status signedURL }
|
|
783
|
+
}`,
|
|
784
|
+
input
|
|
785
|
+
);
|
|
786
|
+
return result.data.getR2SignedURL;
|
|
787
|
+
}
|
|
788
|
+
async getSignedFileURL(input) {
|
|
789
|
+
const result = await this.network.query(
|
|
790
|
+
`query GetR2SignedFileURL($courseId: ID!, $itemId: ID!, $key: ID!) {
|
|
791
|
+
getR2SignedFileURL(courseId: $courseId, itemId: $itemId, key: $key) { status signedURL }
|
|
792
|
+
}`,
|
|
793
|
+
input
|
|
794
|
+
);
|
|
795
|
+
return result.data.getR2SignedFileURL;
|
|
796
|
+
}
|
|
797
|
+
async createVideo(input) {
|
|
798
|
+
const result = await this.network.query(
|
|
799
|
+
`query CreateCourseVideo($courseId: ID!, $fileName: String!, $fileType: String!) {
|
|
800
|
+
createCourseVideo(courseId: $courseId, fileName: $fileName, fileType: $fileType) { status videoId }
|
|
801
|
+
}`,
|
|
802
|
+
input
|
|
803
|
+
);
|
|
804
|
+
return result.data.createCourseVideo;
|
|
805
|
+
}
|
|
806
|
+
async uploadVideoToBunny(input) {
|
|
807
|
+
const result = await this.network.mutate(
|
|
808
|
+
`mutation UploadR2VideoToBunny($key: String!, $videoId: String!, $fileType: String!) {
|
|
809
|
+
uploadR2VideoToBunny(key: $key, videoId: $videoId, fileType: $fileType) { status }
|
|
810
|
+
}`,
|
|
811
|
+
input
|
|
812
|
+
);
|
|
813
|
+
return result.data.uploadR2VideoToBunny;
|
|
814
|
+
}
|
|
815
|
+
async getLiveExams(courseId, nextToken) {
|
|
816
|
+
const result = await this.network.query(
|
|
817
|
+
`query ListLiveExams($courseId: ID!, $sortDirection: ModelSortDirection, $nextToken: String) {
|
|
818
|
+
listCourseLiveExamByCourseIdAndStartTime(courseId: $courseId, sortDirection: $sortDirection, nextToken: $nextToken) {
|
|
819
|
+
items { id courseId name startTime endTime owner totalParticipants resultsPublished }
|
|
820
|
+
nextToken
|
|
821
|
+
}
|
|
822
|
+
}`,
|
|
823
|
+
{ courseId, sortDirection: "DESC", nextToken }
|
|
824
|
+
);
|
|
825
|
+
return result.data.listCourseLiveExamByCourseIdAndStartTime;
|
|
826
|
+
}
|
|
827
|
+
async createLiveExam(input) {
|
|
828
|
+
const result = await this.network.mutate(
|
|
829
|
+
`mutation MakeCourseLiveExam($courseId: ID!, $name: String!, $questions: String!, $startTime: AWSDateTime!, $endTime: AWSDateTime!) {
|
|
830
|
+
makeCourseLiveExam(courseId: $courseId, name: $name, questions: $questions, startTime: $startTime, endTime: $endTime) { status id }
|
|
831
|
+
}`,
|
|
832
|
+
input
|
|
833
|
+
);
|
|
834
|
+
return result.data.makeCourseLiveExam;
|
|
835
|
+
}
|
|
836
|
+
async getLiveExamQuestions(examId) {
|
|
837
|
+
const result = await this.network.query(
|
|
838
|
+
`query GetLiveExamQuestions($examId: ID!) {
|
|
839
|
+
getCourseLiveExamQuestions(examId: $examId) { status name startTime endTime questions selectedOptions }
|
|
840
|
+
}`,
|
|
841
|
+
{ examId }
|
|
842
|
+
);
|
|
843
|
+
return result.data.getCourseLiveExamQuestions;
|
|
844
|
+
}
|
|
845
|
+
async participateLiveExam(examId) {
|
|
846
|
+
const result = await this.network.mutate(
|
|
847
|
+
`mutation ParticipateLiveExam($examId: ID!) {
|
|
848
|
+
participateCourseLiveExam(examId: $examId) { status }
|
|
849
|
+
}`,
|
|
850
|
+
{ examId }
|
|
851
|
+
);
|
|
852
|
+
return result.data.participateCourseLiveExam;
|
|
853
|
+
}
|
|
854
|
+
async selectLiveExamOption(examId, optionId, order) {
|
|
855
|
+
const result = await this.network.mutate(
|
|
856
|
+
`mutation SelectLiveExamOption($examId: ID!, $optionId: ID!, $order: Int!) {
|
|
857
|
+
selectCourseLiveExamOption(examId: $examId, optionId: $optionId, order: $order) { status }
|
|
858
|
+
}`,
|
|
859
|
+
{ examId, optionId, order }
|
|
860
|
+
);
|
|
861
|
+
return result.data.selectCourseLiveExamOption;
|
|
862
|
+
}
|
|
863
|
+
async getLiveExamLeaderboard(examId, nextToken) {
|
|
864
|
+
const result = await this.network.query(
|
|
865
|
+
`query GetLiveExamLeaderboard($examId: ID!, $nextToken: String) {
|
|
866
|
+
getCourseLiveExamLeaderboard(examId: $examId, nextToken: $nextToken) { status leaderboard nextToken totalParticipants }
|
|
867
|
+
}`,
|
|
868
|
+
{ examId, nextToken }
|
|
869
|
+
);
|
|
870
|
+
return result.data.getCourseLiveExamLeaderboard;
|
|
871
|
+
}
|
|
872
|
+
async publishLiveExamResults(examId) {
|
|
873
|
+
const result = await this.network.mutate(
|
|
874
|
+
`mutation PublishLiveExamResults($examId: ID!) {
|
|
875
|
+
publishCourseLiveExamResults(examId: $examId) { status }
|
|
876
|
+
}`,
|
|
877
|
+
{ examId }
|
|
878
|
+
);
|
|
879
|
+
return result.data.publishCourseLiveExamResults;
|
|
880
|
+
}
|
|
881
|
+
};
|
|
882
|
+
|
|
883
|
+
// src/services/curriculum.service.ts
|
|
884
|
+
var CurriculumService = class {
|
|
885
|
+
constructor(network) {
|
|
886
|
+
this.network = network;
|
|
887
|
+
}
|
|
888
|
+
async createSubject(name, level) {
|
|
889
|
+
const result = await this.network.mutate(
|
|
890
|
+
`mutation MakeSubject($name: String!, $level: String!) {
|
|
891
|
+
makeSubject(name: $name, level: $level) { status id }
|
|
892
|
+
}`,
|
|
893
|
+
{ name, level }
|
|
894
|
+
);
|
|
895
|
+
return result.data.makeSubject;
|
|
896
|
+
}
|
|
897
|
+
async createChapter(name, subjectId, order) {
|
|
898
|
+
const result = await this.network.mutate(
|
|
899
|
+
`mutation MakeChapter($name: String!, $subjectId: ID!, $order: Int!) {
|
|
900
|
+
makeChapter(name: $name, subjectId: $subjectId, order: $order) { status id }
|
|
901
|
+
}`,
|
|
902
|
+
{ name, subjectId, order }
|
|
903
|
+
);
|
|
904
|
+
return result.data.makeChapter;
|
|
905
|
+
}
|
|
906
|
+
async createTopic(name, chapterId) {
|
|
907
|
+
const result = await this.network.mutate(
|
|
908
|
+
`mutation MakeTopic($name: String!, $chapterId: ID!) {
|
|
909
|
+
makeTopic(name: $name, chapterId: $chapterId) { status id }
|
|
910
|
+
}`,
|
|
911
|
+
{ name, chapterId }
|
|
912
|
+
);
|
|
913
|
+
return result.data.makeTopic;
|
|
914
|
+
}
|
|
915
|
+
async getSubjects(level) {
|
|
916
|
+
const result = await this.network.query(
|
|
917
|
+
`query ListSubjects($level: String!) {
|
|
918
|
+
listSubjectByLevel(level: $level) {
|
|
919
|
+
items { id name level questionCount }
|
|
920
|
+
}
|
|
921
|
+
}`,
|
|
922
|
+
{ level }
|
|
923
|
+
);
|
|
924
|
+
return result.data.listSubjectByLevel.items;
|
|
925
|
+
}
|
|
926
|
+
async getChapters(subjectId) {
|
|
927
|
+
const result = await this.network.query(
|
|
928
|
+
`query ListChapters($subjectId: ID!, $sortDirection: ModelSortDirection) {
|
|
929
|
+
listChapterBySubjectIdAndOrder(subjectId: $subjectId, sortDirection: $sortDirection) {
|
|
930
|
+
items { id name order subjectId questionCount }
|
|
931
|
+
}
|
|
932
|
+
}`,
|
|
933
|
+
{ subjectId, sortDirection: "ASC" }
|
|
934
|
+
);
|
|
935
|
+
return result.data.listChapterBySubjectIdAndOrder.items;
|
|
936
|
+
}
|
|
937
|
+
async getTopics(chapterId) {
|
|
938
|
+
const result = await this.network.query(
|
|
939
|
+
`query ListTopics($chapterId: ID!) {
|
|
940
|
+
listTopicByChapterId(chapterId: $chapterId) {
|
|
941
|
+
items { id name chapterId questionCount }
|
|
942
|
+
}
|
|
943
|
+
}`,
|
|
944
|
+
{ chapterId }
|
|
945
|
+
);
|
|
946
|
+
return result.data.listTopicByChapterId.items;
|
|
947
|
+
}
|
|
948
|
+
};
|
|
949
|
+
|
|
950
|
+
// src/services/media.service.ts
|
|
951
|
+
var MediaService = class {
|
|
952
|
+
constructor(network, storage) {
|
|
953
|
+
this.network = network;
|
|
954
|
+
this.storage = storage;
|
|
955
|
+
}
|
|
956
|
+
async uploadImage(questionId, image) {
|
|
957
|
+
const result = await this.network.mutate(
|
|
958
|
+
`mutation UploadImage($questionId: ID!, $image: String!) {
|
|
959
|
+
uploadImage(questionId: $questionId, image: $image) { status imageUrl }
|
|
960
|
+
}`,
|
|
961
|
+
{ questionId, image }
|
|
962
|
+
);
|
|
963
|
+
return result.data.uploadImage;
|
|
964
|
+
}
|
|
965
|
+
async deleteImage(imageUrl) {
|
|
966
|
+
const result = await this.network.mutate(
|
|
967
|
+
`mutation DeleteImage($imageUrl: ID!) {
|
|
968
|
+
deleteImage(imageUrl: $imageUrl) { status }
|
|
969
|
+
}`,
|
|
970
|
+
{ imageUrl }
|
|
971
|
+
);
|
|
972
|
+
return result.data.deleteImage;
|
|
973
|
+
}
|
|
974
|
+
async getSignedURL(input) {
|
|
975
|
+
const result = await this.network.query(
|
|
976
|
+
`query GetR2SignedURL($courseId: ID!, $fileName: String!, $fileType: String!, $parentId: ID) {
|
|
977
|
+
getR2SignedURL(courseId: $courseId, fileName: $fileName, fileType: $fileType, parentId: $parentId) { status signedURL }
|
|
978
|
+
}`,
|
|
979
|
+
input
|
|
980
|
+
);
|
|
981
|
+
return result.data.getR2SignedURL;
|
|
982
|
+
}
|
|
983
|
+
async getSignedFileURL(input) {
|
|
984
|
+
const result = await this.network.query(
|
|
985
|
+
`query GetR2SignedFileURL($courseId: ID!, $itemId: ID!, $key: ID!) {
|
|
986
|
+
getR2SignedFileURL(courseId: $courseId, itemId: $itemId, key: $key) { status signedURL }
|
|
987
|
+
}`,
|
|
988
|
+
input
|
|
989
|
+
);
|
|
990
|
+
return result.data.getR2SignedFileURL;
|
|
991
|
+
}
|
|
992
|
+
async getFileUrl(path) {
|
|
993
|
+
if (!this.storage) throw new Error("StorageProvider not configured");
|
|
994
|
+
return this.storage.getFileUrl(path);
|
|
995
|
+
}
|
|
996
|
+
async uploadFile(path, data, options) {
|
|
997
|
+
if (!this.storage) throw new Error("StorageProvider not configured");
|
|
998
|
+
return this.storage.uploadFile(path, data, options);
|
|
999
|
+
}
|
|
1000
|
+
async createVideo(input) {
|
|
1001
|
+
const result = await this.network.query(
|
|
1002
|
+
`query CreateCourseVideo($courseId: ID!, $fileName: String!, $fileType: String!) {
|
|
1003
|
+
createCourseVideo(courseId: $courseId, fileName: $fileName, fileType: $fileType) { status videoId }
|
|
1004
|
+
}`,
|
|
1005
|
+
input
|
|
1006
|
+
);
|
|
1007
|
+
return result.data.createCourseVideo;
|
|
1008
|
+
}
|
|
1009
|
+
async uploadVideoToBunny(input) {
|
|
1010
|
+
const result = await this.network.mutate(
|
|
1011
|
+
`mutation UploadR2VideoToBunny($key: String!, $videoId: String!, $fileType: String!) {
|
|
1012
|
+
uploadR2VideoToBunny(key: $key, videoId: $videoId, fileType: $fileType) { status }
|
|
1013
|
+
}`,
|
|
1014
|
+
input
|
|
1015
|
+
);
|
|
1016
|
+
return result.data.uploadR2VideoToBunny;
|
|
1017
|
+
}
|
|
1018
|
+
};
|
|
1019
|
+
|
|
1020
|
+
// src/services/progress.service.ts
|
|
1021
|
+
var ProgressService = class {
|
|
1022
|
+
constructor(network) {
|
|
1023
|
+
this.network = network;
|
|
1024
|
+
}
|
|
1025
|
+
async track(input) {
|
|
1026
|
+
const result = await this.network.mutate(
|
|
1027
|
+
`mutation TrackProgress($questionId: ID!, $isCorrect: Boolean!, $subjectId: ID, $chapterId: ID, $topicId: ID) {
|
|
1028
|
+
trackPracticeProgress(questionId: $questionId, isCorrect: $isCorrect, subjectId: $subjectId, chapterId: $chapterId, topicId: $topicId) { status }
|
|
1029
|
+
}`,
|
|
1030
|
+
input
|
|
1031
|
+
);
|
|
1032
|
+
return result.data.trackPracticeProgress;
|
|
1033
|
+
}
|
|
1034
|
+
async getSubjectProgress(userId) {
|
|
1035
|
+
const result = await this.network.query(
|
|
1036
|
+
`query ListSubjectProgress($userId: String!) {
|
|
1037
|
+
listUserSubjectProgressByUserIdAndSubjectId(userId: $userId) {
|
|
1038
|
+
items { id userId subjectId practicedCount mistakeCount correctedCount accuracyScore }
|
|
1039
|
+
}
|
|
1040
|
+
}`,
|
|
1041
|
+
{ userId }
|
|
1042
|
+
);
|
|
1043
|
+
return result.data.listUserSubjectProgressByUserIdAndSubjectId.items;
|
|
1044
|
+
}
|
|
1045
|
+
async getChapterProgress(userId) {
|
|
1046
|
+
const result = await this.network.query(
|
|
1047
|
+
`query ListChapterProgress($userId: String!) {
|
|
1048
|
+
listUserChapterProgressByUserIdAndChapterId(userId: $userId) {
|
|
1049
|
+
items { id userId chapterId practicedCount mistakeCount correctedCount accuracyScore }
|
|
1050
|
+
}
|
|
1051
|
+
}`,
|
|
1052
|
+
{ userId }
|
|
1053
|
+
);
|
|
1054
|
+
return result.data.listUserChapterProgressByUserIdAndChapterId.items;
|
|
1055
|
+
}
|
|
1056
|
+
async getTopicProgress(userId) {
|
|
1057
|
+
const result = await this.network.query(
|
|
1058
|
+
`query ListTopicProgress($userId: String!) {
|
|
1059
|
+
listUserTopicProgressByUserIdAndTopicId(userId: $userId) {
|
|
1060
|
+
items { id userId topicId practicedCount mistakeCount correctedCount accuracyScore }
|
|
1061
|
+
}
|
|
1062
|
+
}`,
|
|
1063
|
+
{ userId }
|
|
1064
|
+
);
|
|
1065
|
+
return result.data.listUserTopicProgressByUserIdAndTopicId.items;
|
|
1066
|
+
}
|
|
1067
|
+
async getTopicMetrics(userId) {
|
|
1068
|
+
const result = await this.network.query(
|
|
1069
|
+
`query ListTopicMetrics($filter: ModelUserTopicMetricFilterInput) {
|
|
1070
|
+
listUserTopicMetrics(filter: $filter) {
|
|
1071
|
+
items { id userId topicId totalAttempted totalCorrect accuracy }
|
|
1072
|
+
}
|
|
1073
|
+
}`,
|
|
1074
|
+
{ filter: { userId: { eq: userId } } }
|
|
1075
|
+
);
|
|
1076
|
+
return result.data.listUserTopicMetrics.items;
|
|
1077
|
+
}
|
|
1078
|
+
async getMistakenQuestions(userId) {
|
|
1079
|
+
const result = await this.network.query(
|
|
1080
|
+
`query ListMistakes($userId: String!, $filter: ModelQuestionTrackingFilterInput) {
|
|
1081
|
+
listQuestionTrackingByUserIdAndSubjectId(userId: $userId, filter: $filter) {
|
|
1082
|
+
items { id userId questionId subjectId chapterId topicId status attempts }
|
|
1083
|
+
}
|
|
1084
|
+
}`,
|
|
1085
|
+
{ userId, filter: { status: { eq: "MISTAKE" } } }
|
|
1086
|
+
);
|
|
1087
|
+
return result.data.listQuestionTrackingByUserIdAndSubjectId.items;
|
|
1088
|
+
}
|
|
1089
|
+
};
|
|
1090
|
+
|
|
1091
|
+
// src/services/ai.service.ts
|
|
1092
|
+
var AIService = class {
|
|
1093
|
+
constructor(network) {
|
|
1094
|
+
this.network = network;
|
|
1095
|
+
}
|
|
1096
|
+
async evaluateWrittenAnswer(input) {
|
|
1097
|
+
const result = await this.network.query(
|
|
1098
|
+
`query EvaluateWrittenAnswer($question: String!, $userAnswer: String!, $correctAnswer: String!, $markingInstructions: String, $topicId: String, $topicName: String, $questionImageUrl: String) {
|
|
1099
|
+
evaluateWrittenAnswer(question: $question, userAnswer: $userAnswer, correctAnswer: $correctAnswer, markingInstructions: $markingInstructions, topicId: $topicId, topicName: $topicName, questionImageUrl: $questionImageUrl) {
|
|
1100
|
+
score maxScore feedback isCorrect markingExplanation improvementFeedback subquestions markLocation
|
|
1101
|
+
}
|
|
1102
|
+
}`,
|
|
1103
|
+
input
|
|
1104
|
+
);
|
|
1105
|
+
return result.data.evaluateWrittenAnswer;
|
|
1106
|
+
}
|
|
1107
|
+
};
|
|
1108
|
+
|
|
1109
|
+
// src/services/news.service.ts
|
|
1110
|
+
var NewsService = class {
|
|
1111
|
+
constructor(network) {
|
|
1112
|
+
this.network = network;
|
|
1113
|
+
}
|
|
1114
|
+
async create(title, content, image, category, excerpt) {
|
|
1115
|
+
const result = await this.network.mutate(
|
|
1116
|
+
`mutation CreateNews($input: CreateNewsArticleInput!) {
|
|
1117
|
+
createNewsArticle(input: $input) { id title content image category excerpt createdAt updatedAt }
|
|
1118
|
+
}`,
|
|
1119
|
+
{ input: { title, content, image, category, excerpt } }
|
|
1120
|
+
);
|
|
1121
|
+
return result.data.createNewsArticle;
|
|
1122
|
+
}
|
|
1123
|
+
async list(nextToken) {
|
|
1124
|
+
const result = await this.network.query(
|
|
1125
|
+
`query ListNews($nextToken: String) {
|
|
1126
|
+
listNewsArticles(nextToken: $nextToken) {
|
|
1127
|
+
items { id title content image category excerpt createdAt updatedAt }
|
|
1128
|
+
nextToken
|
|
1129
|
+
}
|
|
1130
|
+
}`,
|
|
1131
|
+
{ nextToken }
|
|
1132
|
+
);
|
|
1133
|
+
return result.data.listNewsArticles;
|
|
1134
|
+
}
|
|
1135
|
+
async get(id) {
|
|
1136
|
+
const result = await this.network.query(
|
|
1137
|
+
`query GetNews($id: ID!) {
|
|
1138
|
+
getNewsArticle(id: $id) { id title content image category excerpt createdAt updatedAt }
|
|
1139
|
+
}`,
|
|
1140
|
+
{ id }
|
|
1141
|
+
);
|
|
1142
|
+
return result.data.getNewsArticle;
|
|
1143
|
+
}
|
|
1144
|
+
};
|
|
1145
|
+
|
|
1146
|
+
// src/client.ts
|
|
1147
|
+
var OrjokClient = class {
|
|
1148
|
+
constructor(config) {
|
|
1149
|
+
this.auth = config.authProvider;
|
|
1150
|
+
this.users = new UserService(config.networkProvider);
|
|
1151
|
+
this.questions = new QuestionService(config.networkProvider);
|
|
1152
|
+
this.packs = new PackService(config.networkProvider);
|
|
1153
|
+
this.contests = new ContestService(config.networkProvider);
|
|
1154
|
+
this.courses = new CourseService(config.networkProvider);
|
|
1155
|
+
this.curriculum = new CurriculumService(config.networkProvider);
|
|
1156
|
+
this.media = new MediaService(config.networkProvider, config.storageProvider);
|
|
1157
|
+
this.progress = new ProgressService(config.networkProvider);
|
|
1158
|
+
this.ai = new AIService(config.networkProvider);
|
|
1159
|
+
this.news = new NewsService(config.networkProvider);
|
|
1160
|
+
}
|
|
1161
|
+
};
|
|
1162
|
+
function createOrjokClient(config) {
|
|
1163
|
+
return new OrjokClient(config);
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
// src/logic/elo.ts
|
|
1167
|
+
var TIERS = [
|
|
1168
|
+
{ name: "Iron", minRating: 0, maxRating: 799, color: "#6B7280" },
|
|
1169
|
+
{ name: "Bronze", minRating: 800, maxRating: 999, color: "#CD7F32" },
|
|
1170
|
+
{ name: "Silver", minRating: 1e3, maxRating: 1199, color: "#C0C0C0" },
|
|
1171
|
+
{ name: "Gold", minRating: 1200, maxRating: 1399, color: "#FFD700" },
|
|
1172
|
+
{ name: "Platinum", minRating: 1400, maxRating: 1599, color: "#00CED1" },
|
|
1173
|
+
{ name: "Diamond", minRating: 1600, maxRating: 1799, color: "#B9F2FF" },
|
|
1174
|
+
{ name: "Master", minRating: 1800, maxRating: 1999, color: "#9B59B6" },
|
|
1175
|
+
{ name: "Grandmaster", minRating: 2e3, maxRating: 2399, color: "#E74C3C" },
|
|
1176
|
+
{ name: "Challenger", minRating: 2400, maxRating: Infinity, color: "#F1C40F" }
|
|
1177
|
+
];
|
|
1178
|
+
function getEloTier(rating) {
|
|
1179
|
+
return TIERS.find((t) => rating >= t.minRating && rating <= t.maxRating) ?? TIERS[0];
|
|
1180
|
+
}
|
|
1181
|
+
function getEloProgress(rating) {
|
|
1182
|
+
const tier = getEloTier(rating);
|
|
1183
|
+
if (tier.maxRating === Infinity) return 100;
|
|
1184
|
+
const range = tier.maxRating - tier.minRating;
|
|
1185
|
+
return Math.round((rating - tier.minRating) / range * 100);
|
|
1186
|
+
}
|
|
1187
|
+
function getAllTiers() {
|
|
1188
|
+
return [...TIERS];
|
|
1189
|
+
}
|
|
1190
|
+
function getRatingForSubject(user, subject) {
|
|
1191
|
+
const key = subject;
|
|
1192
|
+
return user[key] ?? 1e3;
|
|
1193
|
+
}
|
|
1194
|
+
function computeRatingDelta(beforeRating, afterRating) {
|
|
1195
|
+
if (beforeRating == null || afterRating == null) return 0;
|
|
1196
|
+
return afterRating - beforeRating;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
// src/logic/exam.ts
|
|
1200
|
+
function filterQuestionsByMode(questions, mode) {
|
|
1201
|
+
if (mode === "BOTH") return questions;
|
|
1202
|
+
return questions.filter((q) => q.type === mode || !q.type && mode === "MCQ");
|
|
1203
|
+
}
|
|
1204
|
+
function calculateExamTime(questionCount, secondsPerQuestion = 60) {
|
|
1205
|
+
return questionCount * secondsPerQuestion;
|
|
1206
|
+
}
|
|
1207
|
+
function formatExamTime(totalSeconds) {
|
|
1208
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
1209
|
+
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
1210
|
+
const seconds = totalSeconds % 60;
|
|
1211
|
+
if (hours > 0) {
|
|
1212
|
+
return `${hours}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
|
|
1213
|
+
}
|
|
1214
|
+
return `${minutes}:${String(seconds).padStart(2, "0")}`;
|
|
1215
|
+
}
|
|
1216
|
+
function scoreExam(selectedOptions, correctAnswers) {
|
|
1217
|
+
let correct = 0;
|
|
1218
|
+
let incorrect = 0;
|
|
1219
|
+
let unanswered = 0;
|
|
1220
|
+
for (let i = 0; i < correctAnswers.length; i++) {
|
|
1221
|
+
const selected = selectedOptions[i];
|
|
1222
|
+
if (!selected) {
|
|
1223
|
+
unanswered++;
|
|
1224
|
+
} else if (selected === correctAnswers[i]) {
|
|
1225
|
+
correct++;
|
|
1226
|
+
} else {
|
|
1227
|
+
incorrect++;
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
return { correct, incorrect, unanswered, total: correctAnswers.length };
|
|
1231
|
+
}
|
|
1232
|
+
function isImageAnswer(answer) {
|
|
1233
|
+
if (!answer) return false;
|
|
1234
|
+
return answer.startsWith("image::") || answer.startsWith("images::");
|
|
1235
|
+
}
|
|
1236
|
+
function parseImageKeys(answer) {
|
|
1237
|
+
if (answer.startsWith("images::")) {
|
|
1238
|
+
return answer.slice("images::".length).split(",").filter(Boolean);
|
|
1239
|
+
}
|
|
1240
|
+
if (answer.startsWith("image::")) {
|
|
1241
|
+
const key = answer.slice("image::".length);
|
|
1242
|
+
return key ? [key] : [];
|
|
1243
|
+
}
|
|
1244
|
+
return [];
|
|
1245
|
+
}
|
|
1246
|
+
function buildImageAnswer(keys) {
|
|
1247
|
+
if (keys.length === 0) return "";
|
|
1248
|
+
if (keys.length === 1) return `image::${keys[0]}`;
|
|
1249
|
+
return `images::${keys.join(",")}`;
|
|
1250
|
+
}
|
|
1251
|
+
function normalizeDifficulty(raw) {
|
|
1252
|
+
const trimmed = raw.trim();
|
|
1253
|
+
if (!trimmed) return void 0;
|
|
1254
|
+
const capitalized = trimmed.charAt(0).toUpperCase() + trimmed.slice(1).toLowerCase();
|
|
1255
|
+
if (capitalized === "Easy" || capitalized === "Medium" || capitalized === "Hard") {
|
|
1256
|
+
return capitalized;
|
|
1257
|
+
}
|
|
1258
|
+
return void 0;
|
|
1259
|
+
}
|
|
1260
|
+
function detectQuestionType(option2, option3, option4) {
|
|
1261
|
+
if (!option2.trim() && !option3.trim() && !option4.trim()) {
|
|
1262
|
+
return "WRITTEN";
|
|
1263
|
+
}
|
|
1264
|
+
return "MCQ";
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
// src/logic/import-export.ts
|
|
1268
|
+
var MAX_QUESTIONS = 200;
|
|
1269
|
+
function parseFields(parts) {
|
|
1270
|
+
let extra = "";
|
|
1271
|
+
let difficulty;
|
|
1272
|
+
let subjectId = "";
|
|
1273
|
+
let chapterId = "";
|
|
1274
|
+
let topicId = "";
|
|
1275
|
+
let markingInstructions = "";
|
|
1276
|
+
let id = "";
|
|
1277
|
+
let imageUrl = "";
|
|
1278
|
+
let answerImageUrl = "";
|
|
1279
|
+
let option2ImageUrl = "";
|
|
1280
|
+
let option3ImageUrl = "";
|
|
1281
|
+
let option4ImageUrl = "";
|
|
1282
|
+
if (parts.length >= 13) {
|
|
1283
|
+
extra = parts[7]?.trim() ?? "";
|
|
1284
|
+
const rawDiff = parts[8]?.trim() ?? "";
|
|
1285
|
+
if (rawDiff) {
|
|
1286
|
+
const cap = rawDiff.charAt(0).toUpperCase() + rawDiff.slice(1).toLowerCase();
|
|
1287
|
+
if (cap === "Easy" || cap === "Medium" || cap === "Hard") difficulty = cap;
|
|
1288
|
+
}
|
|
1289
|
+
subjectId = parts[9]?.trim() ?? "";
|
|
1290
|
+
chapterId = parts[10]?.trim() ?? "";
|
|
1291
|
+
topicId = parts[11]?.trim() ?? "";
|
|
1292
|
+
markingInstructions = parts[12]?.trim() ?? "";
|
|
1293
|
+
if (parts.length > 13) id = parts[13]?.trim() ?? "";
|
|
1294
|
+
if (parts.length > 14) imageUrl = parts[14]?.trim() ?? "";
|
|
1295
|
+
if (parts.length > 15) answerImageUrl = parts[15]?.trim() ?? "";
|
|
1296
|
+
if (parts.length > 16) option2ImageUrl = parts[16]?.trim() ?? "";
|
|
1297
|
+
if (parts.length > 17) option3ImageUrl = parts[17]?.trim() ?? "";
|
|
1298
|
+
if (parts.length > 18) option4ImageUrl = parts[18]?.trim() ?? "";
|
|
1299
|
+
} else if (parts.length === 8) {
|
|
1300
|
+
extra = parts[7]?.trim() ?? "";
|
|
1301
|
+
} else if (parts.length === 9) {
|
|
1302
|
+
extra = parts[7]?.trim() ?? "";
|
|
1303
|
+
const rawDiff = parts[8]?.trim() ?? "";
|
|
1304
|
+
if (rawDiff) {
|
|
1305
|
+
const cap = rawDiff.charAt(0).toUpperCase() + rawDiff.slice(1).toLowerCase();
|
|
1306
|
+
if (cap === "Easy" || cap === "Medium" || cap === "Hard") difficulty = cap;
|
|
1307
|
+
}
|
|
1308
|
+
} else if (parts.length === 10) {
|
|
1309
|
+
subjectId = parts[7]?.trim() ?? "";
|
|
1310
|
+
chapterId = parts[8]?.trim() ?? "";
|
|
1311
|
+
topicId = parts[9]?.trim() ?? "";
|
|
1312
|
+
} else if (parts.length === 11) {
|
|
1313
|
+
extra = parts[7]?.trim() ?? "";
|
|
1314
|
+
subjectId = parts[8]?.trim() ?? "";
|
|
1315
|
+
chapterId = parts[9]?.trim() ?? "";
|
|
1316
|
+
topicId = parts[10]?.trim() ?? "";
|
|
1317
|
+
} else if (parts.length === 12) {
|
|
1318
|
+
extra = parts[7]?.trim() ?? "";
|
|
1319
|
+
const rawDiff = parts[8]?.trim() ?? "";
|
|
1320
|
+
if (rawDiff) {
|
|
1321
|
+
const cap = rawDiff.charAt(0).toUpperCase() + rawDiff.slice(1).toLowerCase();
|
|
1322
|
+
if (cap === "Easy" || cap === "Medium" || cap === "Hard") difficulty = cap;
|
|
1323
|
+
}
|
|
1324
|
+
subjectId = parts[9]?.trim() ?? "";
|
|
1325
|
+
chapterId = parts[10]?.trim() ?? "";
|
|
1326
|
+
topicId = parts[11]?.trim() ?? "";
|
|
1327
|
+
}
|
|
1328
|
+
return {
|
|
1329
|
+
id: id || void 0,
|
|
1330
|
+
imageUrl: imageUrl || void 0,
|
|
1331
|
+
extra: extra || void 0,
|
|
1332
|
+
difficulty,
|
|
1333
|
+
subjectId: subjectId || void 0,
|
|
1334
|
+
chapterId: chapterId || void 0,
|
|
1335
|
+
topicId: topicId || void 0,
|
|
1336
|
+
markingInstructions: markingInstructions || void 0,
|
|
1337
|
+
answerImageUrl: answerImageUrl || void 0,
|
|
1338
|
+
option2ImageUrl: option2ImageUrl || void 0,
|
|
1339
|
+
option3ImageUrl: option3ImageUrl || void 0,
|
|
1340
|
+
option4ImageUrl: option4ImageUrl || void 0
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
function buildQuestion(parts) {
|
|
1344
|
+
const tags = parts.length >= 7 && parts[6] ? parts[6].split(",").map((t) => t.trim()).filter(Boolean) : [];
|
|
1345
|
+
const opt2 = parts[2]?.trim() ?? "";
|
|
1346
|
+
const opt3 = parts[3]?.trim() ?? "";
|
|
1347
|
+
const opt4 = parts[4]?.trim() ?? "";
|
|
1348
|
+
const isWritten = parts.length < 6 || !opt2 && !opt3 && !opt4;
|
|
1349
|
+
const extra = parseFields(parts);
|
|
1350
|
+
return {
|
|
1351
|
+
question: parts[0]?.trim() ?? "",
|
|
1352
|
+
answer: parts[1]?.trim() ?? "",
|
|
1353
|
+
option2: opt2,
|
|
1354
|
+
option3: opt3,
|
|
1355
|
+
option4: opt4,
|
|
1356
|
+
explanation: parts[5]?.trim() ?? "",
|
|
1357
|
+
tags,
|
|
1358
|
+
type: isWritten ? "WRITTEN" : "MCQ",
|
|
1359
|
+
...extra
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1362
|
+
function parseTextQuestions(text, keyword, minParts = 2) {
|
|
1363
|
+
const lines = text.split("\n");
|
|
1364
|
+
const questions = [];
|
|
1365
|
+
const errors = [];
|
|
1366
|
+
if (lines.length > MAX_QUESTIONS) {
|
|
1367
|
+
errors.push(`Can't have more than ${MAX_QUESTIONS} questions/lines`);
|
|
1368
|
+
return { questions, errors };
|
|
1369
|
+
}
|
|
1370
|
+
for (let idx = 0; idx < lines.length; idx++) {
|
|
1371
|
+
const line = lines[idx];
|
|
1372
|
+
const parts = line.split(keyword);
|
|
1373
|
+
if (parts.length === 1 && (parts[0] === "\r" || parts[0] === "\n" || parts[0] === "")) {
|
|
1374
|
+
continue;
|
|
1375
|
+
}
|
|
1376
|
+
if (parts.length < minParts || parts.length > 19) {
|
|
1377
|
+
errors.push(`Line ${idx + 1} doesn't have between ${minParts} and 19 parts`);
|
|
1378
|
+
continue;
|
|
1379
|
+
}
|
|
1380
|
+
questions.push(buildQuestion(parts));
|
|
1381
|
+
}
|
|
1382
|
+
return { questions, errors };
|
|
1383
|
+
}
|
|
1384
|
+
function parseCsvQuestions(rows) {
|
|
1385
|
+
const questions = [];
|
|
1386
|
+
const errors = [];
|
|
1387
|
+
const dataRows = rows.slice(1);
|
|
1388
|
+
if (dataRows.length > MAX_QUESTIONS) {
|
|
1389
|
+
errors.push(`Can't have more than ${MAX_QUESTIONS} questions/lines`);
|
|
1390
|
+
return { questions, errors };
|
|
1391
|
+
}
|
|
1392
|
+
for (let idx = 0; idx < dataRows.length; idx++) {
|
|
1393
|
+
const line = dataRows[idx];
|
|
1394
|
+
if (line.length < 2 || line.length > 19) {
|
|
1395
|
+
errors.push(`Line ${idx + 2} doesn't have between 2 and 19 parts`);
|
|
1396
|
+
continue;
|
|
1397
|
+
}
|
|
1398
|
+
questions.push(buildQuestion(line));
|
|
1399
|
+
}
|
|
1400
|
+
return { questions, errors };
|
|
1401
|
+
}
|
|
1402
|
+
function formatQuestionsToText(questions, keyword, packSubjectId, packChapterId, packTopicId) {
|
|
1403
|
+
const lines = questions.map((q) => {
|
|
1404
|
+
const activeSubject = q.subjectId || packSubjectId;
|
|
1405
|
+
const activeChapter = q.chapterId || packChapterId;
|
|
1406
|
+
const activeTopic = q.topicId || packTopicId;
|
|
1407
|
+
const subjectSlug = activeSubject ? activeSubject.split("::")[0]?.trim() ?? "" : "";
|
|
1408
|
+
const chapterSlug = activeChapter ? activeChapter.split("::")[0]?.trim() ?? "" : "";
|
|
1409
|
+
const topicSlug = activeTopic ? activeTopic.split("::")[0]?.trim() ?? "" : "";
|
|
1410
|
+
const fields = [
|
|
1411
|
+
q.question,
|
|
1412
|
+
q.answer,
|
|
1413
|
+
q.type === "WRITTEN" ? "" : q.option2,
|
|
1414
|
+
q.type === "WRITTEN" ? "" : q.option3,
|
|
1415
|
+
q.type === "WRITTEN" ? "" : q.option4,
|
|
1416
|
+
q.explanation,
|
|
1417
|
+
q.tags.join(", "),
|
|
1418
|
+
q.extra ?? "",
|
|
1419
|
+
q.difficulty ?? "",
|
|
1420
|
+
subjectSlug,
|
|
1421
|
+
chapterSlug,
|
|
1422
|
+
topicSlug,
|
|
1423
|
+
q.markingInstructions ?? "",
|
|
1424
|
+
q.id ?? "",
|
|
1425
|
+
q.imageUrl ?? "",
|
|
1426
|
+
q.answerImageUrl ?? "",
|
|
1427
|
+
q.option2ImageUrl ?? "",
|
|
1428
|
+
q.option3ImageUrl ?? "",
|
|
1429
|
+
q.option4ImageUrl ?? ""
|
|
1430
|
+
];
|
|
1431
|
+
return fields.map((f) => (f || "").trim()).join(keyword);
|
|
1432
|
+
});
|
|
1433
|
+
return lines.join("\n");
|
|
1434
|
+
}
|
|
1435
|
+
function formatQuestionsToCsv(questions, packSubjectId, packChapterId, packTopicId) {
|
|
1436
|
+
const escapeCSV = (value) => `"${(value || "").replace(/"/g, '""')}"`;
|
|
1437
|
+
const header = "Question,Answer,Option2,Option3,Option4,Explanation,Tags,Extra,Difficulty,Subject,Chapter,Topic,MarkingInstructions,Id,ImageUrl,AnswerImageUrl,Option2ImageUrl,Option3ImageUrl,Option4ImageUrl";
|
|
1438
|
+
const rows = questions.map((q) => {
|
|
1439
|
+
const activeSubject = q.subjectId || packSubjectId;
|
|
1440
|
+
const activeChapter = q.chapterId || packChapterId;
|
|
1441
|
+
const activeTopic = q.topicId || packTopicId;
|
|
1442
|
+
const subjectSlug = activeSubject ? activeSubject.split("::")[0]?.trim() ?? "" : "";
|
|
1443
|
+
const chapterSlug = activeChapter ? activeChapter.split("::")[0]?.trim() ?? "" : "";
|
|
1444
|
+
const topicSlug = activeTopic ? activeTopic.split("::")[0]?.trim() ?? "" : "";
|
|
1445
|
+
return [
|
|
1446
|
+
escapeCSV(q.question),
|
|
1447
|
+
escapeCSV(q.answer),
|
|
1448
|
+
escapeCSV(q.type === "WRITTEN" ? "" : q.option2),
|
|
1449
|
+
escapeCSV(q.type === "WRITTEN" ? "" : q.option3),
|
|
1450
|
+
escapeCSV(q.type === "WRITTEN" ? "" : q.option4),
|
|
1451
|
+
escapeCSV(q.explanation),
|
|
1452
|
+
escapeCSV(q.tags.join(", ")),
|
|
1453
|
+
escapeCSV(q.extra),
|
|
1454
|
+
escapeCSV(q.difficulty),
|
|
1455
|
+
escapeCSV(subjectSlug),
|
|
1456
|
+
escapeCSV(chapterSlug),
|
|
1457
|
+
escapeCSV(topicSlug),
|
|
1458
|
+
escapeCSV(q.markingInstructions),
|
|
1459
|
+
escapeCSV(q.id),
|
|
1460
|
+
escapeCSV(q.imageUrl),
|
|
1461
|
+
escapeCSV(q.answerImageUrl),
|
|
1462
|
+
escapeCSV(q.option2ImageUrl),
|
|
1463
|
+
escapeCSV(q.option3ImageUrl),
|
|
1464
|
+
escapeCSV(q.option4ImageUrl)
|
|
1465
|
+
].join(",");
|
|
1466
|
+
});
|
|
1467
|
+
return `${header}
|
|
1468
|
+
${rows.join("\n")}`;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
// src/logic/media-url.ts
|
|
1472
|
+
function isStorageKey(value) {
|
|
1473
|
+
return !value.startsWith("http://") && !value.startsWith("https://");
|
|
1474
|
+
}
|
|
1475
|
+
var MediaUrlCache = class {
|
|
1476
|
+
constructor(ttlMs = 50 * 60 * 1e3) {
|
|
1477
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
1478
|
+
this.ttlMs = ttlMs;
|
|
1479
|
+
}
|
|
1480
|
+
get(key) {
|
|
1481
|
+
const entry = this.cache.get(key);
|
|
1482
|
+
if (!entry) return void 0;
|
|
1483
|
+
if (Date.now() >= entry.expiresAt) {
|
|
1484
|
+
this.cache.delete(key);
|
|
1485
|
+
return void 0;
|
|
1486
|
+
}
|
|
1487
|
+
return entry.url;
|
|
1488
|
+
}
|
|
1489
|
+
set(key, url) {
|
|
1490
|
+
this.cache.set(key, { url, expiresAt: Date.now() + this.ttlMs });
|
|
1491
|
+
}
|
|
1492
|
+
clear() {
|
|
1493
|
+
this.cache.clear();
|
|
1494
|
+
}
|
|
1495
|
+
};
|
|
1496
|
+
var warnedNoStorage = false;
|
|
1497
|
+
async function resolveMediaUrl(value, storage, cache) {
|
|
1498
|
+
if (!value || value.length === 0) return null;
|
|
1499
|
+
if (!isStorageKey(value)) return value;
|
|
1500
|
+
if (cache) {
|
|
1501
|
+
const cached = cache.get(value);
|
|
1502
|
+
if (cached) return cached;
|
|
1503
|
+
}
|
|
1504
|
+
if (!storage) {
|
|
1505
|
+
if (!warnedNoStorage) {
|
|
1506
|
+
warnedNoStorage = true;
|
|
1507
|
+
console.warn("@orjok/commons: resolveMediaUrl called without a StorageProvider \u2014 returning raw key");
|
|
1508
|
+
}
|
|
1509
|
+
return value;
|
|
1510
|
+
}
|
|
1511
|
+
const { url } = await storage.getFileUrl(value);
|
|
1512
|
+
cache?.set(value, url);
|
|
1513
|
+
return url;
|
|
1514
|
+
}
|
|
1515
|
+
async function resolveMediaUrls(obj, fields, storage, cache) {
|
|
1516
|
+
const copy = { ...obj };
|
|
1517
|
+
await Promise.all(
|
|
1518
|
+
fields.map(async (field) => {
|
|
1519
|
+
const val = copy[field];
|
|
1520
|
+
if (typeof val === "string") {
|
|
1521
|
+
copy[field] = await resolveMediaUrl(val, storage, cache);
|
|
1522
|
+
}
|
|
1523
|
+
})
|
|
1524
|
+
);
|
|
1525
|
+
return copy;
|
|
1526
|
+
}
|
|
1527
|
+
function _resetMediaUrlWarning() {
|
|
1528
|
+
warnedNoStorage = false;
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
// src/logic/practice.ts
|
|
1532
|
+
function aggregateSubjectMetrics(progress) {
|
|
1533
|
+
let totalPracticed = 0;
|
|
1534
|
+
let totalMistakes = 0;
|
|
1535
|
+
let totalCorrected = 0;
|
|
1536
|
+
for (const p of progress) {
|
|
1537
|
+
totalPracticed += p.practicedCount ?? 0;
|
|
1538
|
+
totalMistakes += p.mistakeCount ?? 0;
|
|
1539
|
+
totalCorrected += p.correctedCount ?? 0;
|
|
1540
|
+
}
|
|
1541
|
+
const accuracy = totalPracticed > 0 ? Math.round((totalPracticed - totalMistakes) / totalPracticed * 100) : 0;
|
|
1542
|
+
return { totalPracticed, totalMistakes, totalCorrected, accuracy };
|
|
1543
|
+
}
|
|
1544
|
+
function aggregateChapterMetrics(progress) {
|
|
1545
|
+
let totalPracticed = 0;
|
|
1546
|
+
let totalMistakes = 0;
|
|
1547
|
+
let totalCorrected = 0;
|
|
1548
|
+
for (const p of progress) {
|
|
1549
|
+
totalPracticed += p.practicedCount ?? 0;
|
|
1550
|
+
totalMistakes += p.mistakeCount ?? 0;
|
|
1551
|
+
totalCorrected += p.correctedCount ?? 0;
|
|
1552
|
+
}
|
|
1553
|
+
const accuracy = totalPracticed > 0 ? Math.round((totalPracticed - totalMistakes) / totalPracticed * 100) : 0;
|
|
1554
|
+
return { totalPracticed, totalMistakes, totalCorrected, accuracy };
|
|
1555
|
+
}
|
|
1556
|
+
function aggregateTopicMetrics(progress) {
|
|
1557
|
+
let totalPracticed = 0;
|
|
1558
|
+
let totalMistakes = 0;
|
|
1559
|
+
let totalCorrected = 0;
|
|
1560
|
+
for (const p of progress) {
|
|
1561
|
+
totalPracticed += p.practicedCount ?? 0;
|
|
1562
|
+
totalMistakes += p.mistakeCount ?? 0;
|
|
1563
|
+
totalCorrected += p.correctedCount ?? 0;
|
|
1564
|
+
}
|
|
1565
|
+
const accuracy = totalPracticed > 0 ? Math.round((totalPracticed - totalMistakes) / totalPracticed * 100) : 0;
|
|
1566
|
+
return { totalPracticed, totalMistakes, totalCorrected, accuracy };
|
|
1567
|
+
}
|
|
1568
|
+
function calculateAccuracy(practiced, mistakes) {
|
|
1569
|
+
if (practiced <= 0) return 0;
|
|
1570
|
+
return Math.round((practiced - mistakes) / practiced * 100);
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
// src/utils/shuffle.ts
|
|
1574
|
+
function shuffle(array) {
|
|
1575
|
+
const result = [...array];
|
|
1576
|
+
let currentIndex = result.length;
|
|
1577
|
+
while (currentIndex !== 0) {
|
|
1578
|
+
const randomIndex = Math.floor(Math.random() * currentIndex);
|
|
1579
|
+
currentIndex--;
|
|
1580
|
+
[result[currentIndex], result[randomIndex]] = [result[randomIndex], result[currentIndex]];
|
|
1581
|
+
}
|
|
1582
|
+
return result;
|
|
1583
|
+
}
|
|
1584
|
+
function shuffleInPlace(array) {
|
|
1585
|
+
let currentIndex = array.length;
|
|
1586
|
+
while (currentIndex !== 0) {
|
|
1587
|
+
const randomIndex = Math.floor(Math.random() * currentIndex);
|
|
1588
|
+
currentIndex--;
|
|
1589
|
+
[array[currentIndex], array[randomIndex]] = [array[randomIndex], array[currentIndex]];
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
// src/utils/bangla-numbers.ts
|
|
1594
|
+
var BANGLA_DIGITS = ["\u09E6", "\u09E7", "\u09E8", "\u09E9", "\u09EA", "\u09EB", "\u09EC", "\u09ED", "\u09EE", "\u09EF"];
|
|
1595
|
+
function engToBanglaNumber(num) {
|
|
1596
|
+
return String(num).split("").map((digit) => {
|
|
1597
|
+
const n = parseInt(digit, 10);
|
|
1598
|
+
return !isNaN(n) ? BANGLA_DIGITS[n] : digit;
|
|
1599
|
+
}).join("");
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
// src/utils/format-curriculum.ts
|
|
1603
|
+
function formatCurriculumName(idString, dbName) {
|
|
1604
|
+
if (dbName) return dbName;
|
|
1605
|
+
if (!idString) return "";
|
|
1606
|
+
const namePart = idString.split("::")[0] ?? "";
|
|
1607
|
+
const clean = namePart.replace(/[-_]/g, " ");
|
|
1608
|
+
return clean.split(/\s+/).map((word) => {
|
|
1609
|
+
if (!word) return "";
|
|
1610
|
+
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
1611
|
+
}).join(" ");
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
// src/utils/relative-time.ts
|
|
1615
|
+
function getRelativeTime(startTime, endTime) {
|
|
1616
|
+
const now = /* @__PURE__ */ new Date();
|
|
1617
|
+
const start = new Date(startTime);
|
|
1618
|
+
const end = endTime ? new Date(endTime) : null;
|
|
1619
|
+
if (end && now >= start && now <= end) {
|
|
1620
|
+
return "";
|
|
1621
|
+
}
|
|
1622
|
+
const diffInSeconds = Math.floor((start.getTime() - now.getTime()) / 1e3);
|
|
1623
|
+
const absDiff = Math.abs(diffInSeconds);
|
|
1624
|
+
const minute = 60;
|
|
1625
|
+
const hour = minute * 60;
|
|
1626
|
+
const day = hour * 24;
|
|
1627
|
+
const week = day * 7;
|
|
1628
|
+
const month = day * 30;
|
|
1629
|
+
const year = day * 365;
|
|
1630
|
+
if (diffInSeconds < 0) {
|
|
1631
|
+
if (absDiff < minute) return "just now";
|
|
1632
|
+
if (absDiff < hour) return `${Math.floor(absDiff / minute)} minutes ago`;
|
|
1633
|
+
if (absDiff < day) return `${Math.floor(absDiff / hour)} hours ago`;
|
|
1634
|
+
if (absDiff < week) return `${Math.floor(absDiff / day)} days ago`;
|
|
1635
|
+
if (absDiff < month) return `${Math.floor(absDiff / week)} weeks ago`;
|
|
1636
|
+
if (absDiff < year) return `${Math.floor(absDiff / month)} months ago`;
|
|
1637
|
+
return `${Math.floor(absDiff / year)} years ago`;
|
|
1638
|
+
} else {
|
|
1639
|
+
if (absDiff < minute) return "in a moment";
|
|
1640
|
+
if (absDiff < hour) return `in ${Math.floor(absDiff / minute)} minutes`;
|
|
1641
|
+
if (absDiff < day) return `in ${Math.floor(absDiff / hour)} hours`;
|
|
1642
|
+
if (absDiff < week) return `in ${Math.floor(absDiff / day)} days`;
|
|
1643
|
+
if (absDiff < month) return `in ${Math.floor(absDiff / week)} weeks`;
|
|
1644
|
+
if (absDiff < year) return `in ${Math.floor(absDiff / month)} months`;
|
|
1645
|
+
return `in ${Math.floor(absDiff / year)} years`;
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
// src/utils/latex.ts
|
|
1650
|
+
function processLatexText(str) {
|
|
1651
|
+
if (!str) return str;
|
|
1652
|
+
const regex = /(\$\$(.*?)\$\$)|(\$(.*?)\$)/gs;
|
|
1653
|
+
let lastIndex = 0;
|
|
1654
|
+
let result = "";
|
|
1655
|
+
let match;
|
|
1656
|
+
while ((match = regex.exec(str)) !== null) {
|
|
1657
|
+
const textBefore = str.substring(lastIndex, match.index);
|
|
1658
|
+
result += textBefore.replace(/\\n/g, "\n");
|
|
1659
|
+
const isDisplay = match[1] !== void 0;
|
|
1660
|
+
const mathContent = isDisplay ? match[2] : match[4];
|
|
1661
|
+
const delimiter = isDisplay ? "$$" : "$";
|
|
1662
|
+
const processedMath = mathContent.replace(
|
|
1663
|
+
/(\\text\s*\{[^{}]*\})|([\u0980-\u09FF]+(?:\s+[\u0980-\u09FF]+)*)/g,
|
|
1664
|
+
(_, p1, p2) => {
|
|
1665
|
+
if (p1) return p1;
|
|
1666
|
+
return `\\text{${p2}}`;
|
|
1667
|
+
}
|
|
1668
|
+
);
|
|
1669
|
+
result += delimiter + processedMath + delimiter;
|
|
1670
|
+
lastIndex = regex.lastIndex;
|
|
1671
|
+
}
|
|
1672
|
+
const remainingText = str.substring(lastIndex);
|
|
1673
|
+
result += remainingText.replace(/\\n/g, "\n");
|
|
1674
|
+
return result;
|
|
1675
|
+
}
|
|
1676
|
+
export {
|
|
1677
|
+
AIService,
|
|
1678
|
+
ContestService,
|
|
1679
|
+
CourseService,
|
|
1680
|
+
CurriculumService,
|
|
1681
|
+
MediaService,
|
|
1682
|
+
MediaUrlCache,
|
|
1683
|
+
NewsService,
|
|
1684
|
+
OrjokClient,
|
|
1685
|
+
PackService,
|
|
1686
|
+
ProgressService,
|
|
1687
|
+
QuestionService,
|
|
1688
|
+
UserService,
|
|
1689
|
+
_resetMediaUrlWarning,
|
|
1690
|
+
aggregateChapterMetrics,
|
|
1691
|
+
aggregateSubjectMetrics,
|
|
1692
|
+
aggregateTopicMetrics,
|
|
1693
|
+
buildImageAnswer,
|
|
1694
|
+
calculateAccuracy,
|
|
1695
|
+
calculateExamTime,
|
|
1696
|
+
computeRatingDelta,
|
|
1697
|
+
createOrjokClient,
|
|
1698
|
+
detectQuestionType,
|
|
1699
|
+
engToBanglaNumber,
|
|
1700
|
+
filterQuestionsByMode,
|
|
1701
|
+
formatCurriculumName,
|
|
1702
|
+
formatExamTime,
|
|
1703
|
+
formatQuestionsToCsv,
|
|
1704
|
+
formatQuestionsToText,
|
|
1705
|
+
getAllTiers,
|
|
1706
|
+
getEloProgress,
|
|
1707
|
+
getEloTier,
|
|
1708
|
+
getRatingForSubject,
|
|
1709
|
+
getRelativeTime,
|
|
1710
|
+
isImageAnswer,
|
|
1711
|
+
isStorageKey,
|
|
1712
|
+
normalizeDifficulty,
|
|
1713
|
+
parseCsvQuestions,
|
|
1714
|
+
parseImageKeys,
|
|
1715
|
+
parseTextQuestions,
|
|
1716
|
+
processLatexText,
|
|
1717
|
+
resolveMediaUrl,
|
|
1718
|
+
resolveMediaUrls,
|
|
1719
|
+
scoreExam,
|
|
1720
|
+
shuffle,
|
|
1721
|
+
shuffleInPlace
|
|
1722
|
+
};
|
|
1723
|
+
//# sourceMappingURL=index.js.map
|