@orjok/commons 1.0.2 → 1.2.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/dist/index.cjs CHANGED
@@ -1,4 +1,38 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/services/user.service.ts
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+
26
+
27
+
28
+
29
+
30
+
31
+
32
+
33
+ var _chunkODXD2WJNcjs = require('./chunk-ODXD2WJN.cjs');
34
+
35
+ // src/services/user.service.ts
2
36
  var UserService = class {
3
37
  constructor(network) {
4
38
  this.network = network;
@@ -8,11 +42,12 @@ var UserService = class {
8
42
  `query GetUser($id: String!) {
9
43
  getUser(id: $id) {
10
44
  id fullName avatarUrl avatarConfig tier subscriptionExpiresAt
11
- aiEvaluationRemaining questionCount packCount contestCount
45
+ aiEvaluationRemaining aiQuestionGenRemaining questionCount packCount contestCount
12
46
  EngineeringRating MedicalRating VarsityRating BCSRating
13
47
  sscOverallRating sscPhysicsRating sscChemistryRating sscMathRating sscBiologyRating
14
48
  hscOverallRating hscPhysicsRating hscChemistryRating hscMathRating hscBiologyRating
15
49
  bcsOverallRating
50
+ xp userLevel
16
51
  }
17
52
  }`,
18
53
  { id }
@@ -20,20 +55,23 @@ var UserService = class {
20
55
  return result.data.getUser;
21
56
  }
22
57
  async updateAvatar(input) {
58
+ const updateInput = { id: input.userId };
59
+ if (input.avatarUrl !== void 0) updateInput.avatarUrl = input.avatarUrl;
60
+ if (input.avatarConfig !== void 0) updateInput.avatarConfig = input.avatarConfig;
23
61
  const result = await this.network.mutate(
24
62
  `mutation UpdateUser($input: UpdateUserInput!) {
25
63
  updateUser(input: $input) {
26
64
  id fullName avatarUrl avatarConfig
27
65
  }
28
66
  }`,
29
- { input: { id: input.userId, avatarUrl: input.avatarUrl, avatarConfig: input.avatarConfig } }
67
+ { input: updateInput }
30
68
  );
31
69
  return result.data.updateUser;
32
70
  }
33
- async getQuestions(userId, nextToken) {
71
+ async getQuestions(userId, nextToken, limit = 10) {
34
72
  const result = await this.network.query(
35
- `query ListUserQuestions($owner: ID!, $nextToken: String, $sortDirection: ModelSortDirection) {
36
- listQuestionObjectByOwnerAndCreatedAt(owner: $owner, sortDirection: $sortDirection, nextToken: $nextToken) {
73
+ `query ListUserQuestions($owner: ID!, $limit: Int, $nextToken: String, $sortDirection: ModelSortDirection) {
74
+ listQuestionObjectByOwnerAndCreatedAt(owner: $owner, limit: $limit, sortDirection: $sortDirection, nextToken: $nextToken) {
37
75
  items {
38
76
  id question imageUrl language level difficulty type voteCount
39
77
  verificationStatus owner packId createdAt
@@ -43,14 +81,14 @@ var UserService = class {
43
81
  nextToken
44
82
  }
45
83
  }`,
46
- { owner: userId, nextToken, sortDirection: "DESC" }
84
+ { owner: userId, limit, nextToken, sortDirection: "DESC" }
47
85
  );
48
86
  return result.data.listQuestionObjectByOwnerAndCreatedAt;
49
87
  }
50
- async getPacks(userId, nextToken) {
88
+ async getPacks(userId, nextToken, limit = 10) {
51
89
  const result = await this.network.query(
52
- `query ListUserPacks($owner: ID!, $nextToken: String, $sortDirection: ModelSortDirection) {
53
- listPackByOwnerAndCreatedAt(owner: $owner, sortDirection: $sortDirection, nextToken: $nextToken) {
90
+ `query ListUserPacks($owner: ID!, $limit: Int, $nextToken: String, $sortDirection: ModelSortDirection) {
91
+ listPackByOwnerAndCreatedAt(owner: $owner, limit: $limit, sortDirection: $sortDirection, nextToken: $nextToken) {
54
92
  items {
55
93
  id name language level difficulty questionCount automaticNumbering
56
94
  owner subjectId chapterId topicId verificationStatus createdAt
@@ -59,7 +97,7 @@ var UserService = class {
59
97
  nextToken
60
98
  }
61
99
  }`,
62
- { owner: userId, nextToken, sortDirection: "DESC" }
100
+ { owner: userId, limit, nextToken, sortDirection: "DESC" }
63
101
  );
64
102
  return result.data.listPackByOwnerAndCreatedAt;
65
103
  }
@@ -90,8 +128,59 @@ var UserService = class {
90
128
  );
91
129
  return result.data.listCourseEnrolledByUserIdAndCreatedAt.items;
92
130
  }
131
+ async updateProfile(input) {
132
+ const result = await this.network.mutate(
133
+ `mutation UpdateUserProfile($input: UpdateUserInput!) {
134
+ updateUser(input: $input) {
135
+ id fullName avatarUrl avatarConfig tier subscriptionExpiresAt
136
+ aiEvaluationRemaining aiQuestionGenRemaining questionCount packCount contestCount
137
+ xp userLevel
138
+ }
139
+ }`,
140
+ { input }
141
+ );
142
+ return result.data.updateUser;
143
+ }
144
+ async list(input = {}) {
145
+ const result = await this.network.query(
146
+ `query ListUsers($limit: Int, $nextToken: String, $filter: ModelUserFilterInput) {
147
+ listUsers(limit: $limit, nextToken: $nextToken, filter: $filter) {
148
+ items {
149
+ id fullName avatarUrl avatarConfig tier subscriptionExpiresAt
150
+ aiEvaluationRemaining aiQuestionGenRemaining questionCount packCount contestCount
151
+ xp userLevel
152
+ }
153
+ nextToken
154
+ }
155
+ }`,
156
+ { limit: _nullishCoalesce(input.limit, () => ( 20)), nextToken: input.nextToken, filter: input.filter }
157
+ );
158
+ return result.data.listUsers;
159
+ }
93
160
  };
94
161
 
162
+ // src/utils/pagination.ts
163
+ async function paginateWithAccumulator(fetchPage, targetLimit = 12, initialNextToken, maxIterations = 10) {
164
+ const accumulated = [];
165
+ let currentNextToken = initialNextToken;
166
+ let iterations = 0;
167
+ while (accumulated.length < targetLimit && iterations < maxIterations) {
168
+ iterations++;
169
+ const remaining = targetLimit - accumulated.length;
170
+ const response = await fetchPage(remaining, currentNextToken);
171
+ const items = _optionalChain([response, 'optionalAccess', _2 => _2.items]) || [];
172
+ accumulated.push(...items);
173
+ currentNextToken = _optionalChain([response, 'optionalAccess', _3 => _3.nextToken]);
174
+ if (!currentNextToken || accumulated.length >= targetLimit) {
175
+ break;
176
+ }
177
+ }
178
+ return {
179
+ items: accumulated.slice(0, targetLimit),
180
+ nextToken: _nullishCoalesce(currentNextToken, () => ( null))
181
+ };
182
+ }
183
+
95
184
  // src/services/question.service.ts
96
185
  var QuestionService = class {
97
186
  constructor(network) {
@@ -99,8 +188,8 @@ var QuestionService = class {
99
188
  }
100
189
  async create(input) {
101
190
  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) {
191
+ `mutation CreateQuestion($question: String!, $answer: String!, $options: String!, $language: String!, $level: String!, $publicExam: 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) {
192
+ createQuestion(question: $question, answer: $answer, options: $options, language: $language, level: $level, publicExam: $publicExam, 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
193
  status questionId
105
194
  }
106
195
  }`,
@@ -112,7 +201,7 @@ var QuestionService = class {
112
201
  const result = await this.network.query(
113
202
  `query GetQuestion($id: String!) {
114
203
  getQuestionObject(id: $id) {
115
- id question imageUrl language level difficulty type voteCount
204
+ id question imageUrl language level publicExam difficulty type voteCount
116
205
  verificationStatus owner packId subjectId chapterId topicId order createdAt
117
206
  options { items { id content } }
118
207
  tags { items { id tagId } }
@@ -126,7 +215,7 @@ var QuestionService = class {
126
215
  const result = await this.network.query(
127
216
  `query GetFullQuestion($id: String!) {
128
217
  getQuestionObject(id: $id) {
129
- id question answer explanation extra imageUrl language level difficulty type
218
+ id question answer explanation extra imageUrl language level publicExam difficulty type
130
219
  markingInstructions voteCount verificationStatus owner packId
131
220
  subjectId chapterId topicId order createdAt
132
221
  options { items { id content } }
@@ -157,13 +246,30 @@ var QuestionService = class {
157
246
  }`,
158
247
  { packId, order: { eq: order } }
159
248
  );
160
- return _nullishCoalesce(_optionalChain([result, 'access', _2 => _2.data, 'access', _3 => _3.listQuestionObjectByPackIdAndOrder, 'access', _4 => _4.items, 'access', _5 => _5[0], 'optionalAccess', _6 => _6.id]), () => ( null));
249
+ return _nullishCoalesce(_optionalChain([result, 'access', _4 => _4.data, 'access', _5 => _5.listQuestionObjectByPackIdAndOrder, 'access', _6 => _6.items, 'access', _7 => _7[0], 'optionalAccess', _8 => _8.id]), () => ( null));
250
+ }
251
+ async listByPack(packId, limit = 200, sortDirection = "ASC", nextToken) {
252
+ const result = await this.network.query(
253
+ `query ListQuestionsByPack($packId: ID!, $sortDirection: ModelSortDirection, $limit: Int, $nextToken: String) {
254
+ listQuestionObjectByPackIdAndOrder(packId: $packId, sortDirection: $sortDirection, limit: $limit, nextToken: $nextToken) {
255
+ items {
256
+ id question answer explanation extra imageUrl language level publicExam difficulty type
257
+ markingInstructions voteCount owner packId subjectId chapterId topicId order createdAt
258
+ options { items { id content } }
259
+ tags { items { id tagId } }
260
+ }
261
+ nextToken
262
+ }
263
+ }`,
264
+ { packId, sortDirection, limit, nextToken }
265
+ );
266
+ return result.data.listQuestionObjectByPackIdAndOrder;
161
267
  }
162
268
  async update(id, fields) {
163
269
  const result = await this.network.mutate(
164
270
  `mutation UpdateQuestion($input: UpdateQuestionObjectInput!) {
165
271
  updateQuestionObject(input: $input) {
166
- id question answer explanation extra imageUrl language level difficulty type
272
+ id question answer explanation extra imageUrl language level publicExam difficulty type
167
273
  markingInstructions voteCount owner packId subjectId chapterId topicId order createdAt
168
274
  options { items { id content } }
169
275
  }
@@ -172,6 +278,41 @@ var QuestionService = class {
172
278
  );
173
279
  return result.data.updateQuestionObject;
174
280
  }
281
+ async updateOption(id, content) {
282
+ const result = await this.network.mutate(
283
+ `mutation UpdateOption($input: UpdateOptionInput!) {
284
+ updateOption(input: $input) {
285
+ id
286
+ content
287
+ }
288
+ }`,
289
+ { input: { id, content } }
290
+ );
291
+ return result.data.updateOption;
292
+ }
293
+ async createOption(input) {
294
+ const result = await this.network.mutate(
295
+ `mutation CreateOption($input: CreateOptionInput!) {
296
+ createOption(input: $input) {
297
+ id
298
+ content
299
+ }
300
+ }`,
301
+ { input }
302
+ );
303
+ return result.data.createOption;
304
+ }
305
+ async deleteOption(id) {
306
+ const result = await this.network.mutate(
307
+ `mutation DeleteOption($input: DeleteOptionInput!) {
308
+ deleteOption(input: $input) {
309
+ id
310
+ }
311
+ }`,
312
+ { input: { id } }
313
+ );
314
+ return result.data.deleteOption;
315
+ }
175
316
  async delete(id) {
176
317
  const result = await this.network.mutate(
177
318
  `mutation DeleteQuestion($input: DeleteQuestionObjectInput!) {
@@ -228,70 +369,288 @@ var QuestionService = class {
228
369
  return result.data.addQuestionTag;
229
370
  }
230
371
  async listBySubject(input) {
231
- const variables = {
232
- subjectId: input.id,
233
- sortDirection: _nullishCoalesce(input.sortDirection, () => ( "DESC")),
234
- limit: _nullishCoalesce(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
372
+ const hasLanguage = Boolean(input.language && input.language !== "All" && input.language !== "Any");
373
+ const hasDifficulty = Boolean(input.difficulty && input.difficulty !== "All");
374
+ const filter = {};
375
+ if (input.publicExam) {
376
+ filter.publicExam = { eq: input.publicExam };
377
+ }
378
+ if (input.questionType && input.questionType !== "Both") {
379
+ filter.type = { eq: input.questionType };
380
+ }
381
+ const targetLimit = _nullishCoalesce(input.limit, () => ( 12));
382
+ const executeQuery = async (pageLimit, nextToken) => {
383
+ if (hasLanguage) {
384
+ const rangeKey = hasDifficulty ? `${input.language}#${input.difficulty}#` : `${input.language}#`;
385
+ const variables = {
386
+ subjectId: input.id,
387
+ language_difficulty_createdAt: { beginsWith: rangeKey },
388
+ sortDirection: _nullishCoalesce(input.sortDirection, () => ( "DESC")),
389
+ limit: pageLimit,
390
+ nextToken,
391
+ ...Object.keys(filter).length > 0 ? { filter } : {}
392
+ };
393
+ const result = await this.network.query(
394
+ `query ListQuestionsBySubjectAndLangDiff($subjectId: String!, $language_difficulty_createdAt: ModelStringKeyConditionInput, $sortDirection: ModelSortDirection, $limit: Int, $nextToken: String, $filter: ModelQuestionObjectFilterInput) {
395
+ listQuestionObjectBySubjectIdAndLanguage_difficulty_createdAt(subjectId: $subjectId, language_difficulty_createdAt: $language_difficulty_createdAt, sortDirection: $sortDirection, limit: $limit, nextToken: $nextToken, filter: $filter) {
396
+ items {
397
+ id question imageUrl language level publicExam difficulty type voteCount
398
+ verificationStatus owner packId subjectId chapterId topicId createdAt
399
+ options { items { id content } }
400
+ }
401
+ nextToken
402
+ }
403
+ }`,
404
+ variables
405
+ );
406
+ return result.data.listQuestionObjectBySubjectIdAndLanguage_difficulty_createdAt;
407
+ } else {
408
+ const queryFilter = { ...filter };
409
+ if (hasDifficulty) {
410
+ queryFilter.difficulty = { eq: input.difficulty };
246
411
  }
247
- }`,
248
- variables
249
- );
250
- return result.data.listQuestionObjectBySubjectIdAndCreatedAt;
412
+ const variables = {
413
+ subjectId: input.id,
414
+ sortDirection: _nullishCoalesce(input.sortDirection, () => ( "DESC")),
415
+ limit: pageLimit,
416
+ nextToken,
417
+ ...Object.keys(queryFilter).length > 0 ? { filter: queryFilter } : {}
418
+ };
419
+ const result = await this.network.query(
420
+ `query ListQuestionsBySubject($subjectId: String!, $sortDirection: ModelSortDirection, $limit: Int, $nextToken: String, $filter: ModelQuestionObjectFilterInput) {
421
+ listQuestionObjectBySubjectIdAndCreatedAt(subjectId: $subjectId, sortDirection: $sortDirection, limit: $limit, nextToken: $nextToken, filter: $filter) {
422
+ items {
423
+ id question imageUrl language level publicExam difficulty type voteCount
424
+ verificationStatus owner packId subjectId chapterId topicId createdAt
425
+ options { items { id content } }
426
+ }
427
+ nextToken
428
+ }
429
+ }`,
430
+ variables
431
+ );
432
+ return result.data.listQuestionObjectBySubjectIdAndCreatedAt;
433
+ }
434
+ };
435
+ const hasPostFilter = hasLanguage ? Object.keys(filter).length > 0 : Object.keys(filter).length > 0 || hasDifficulty;
436
+ if (!hasPostFilter) {
437
+ return executeQuery(targetLimit, input.nextToken);
438
+ }
439
+ return paginateWithAccumulator(executeQuery, targetLimit, input.nextToken);
251
440
  }
252
441
  async listByChapter(input) {
253
- const variables = {
254
- chapterId: input.id,
255
- sortDirection: _nullishCoalesce(input.sortDirection, () => ( "DESC")),
256
- limit: _nullishCoalesce(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
442
+ const hasLanguage = Boolean(input.language && input.language !== "All" && input.language !== "Any");
443
+ const hasDifficulty = Boolean(input.difficulty && input.difficulty !== "All");
444
+ const filter = {};
445
+ if (input.publicExam) {
446
+ filter.publicExam = { eq: input.publicExam };
447
+ }
448
+ if (input.questionType && input.questionType !== "Both") {
449
+ filter.type = { eq: input.questionType };
450
+ }
451
+ const targetLimit = _nullishCoalesce(input.limit, () => ( 12));
452
+ const executeQuery = async (pageLimit, nextToken) => {
453
+ if (hasLanguage) {
454
+ const rangeKey = hasDifficulty ? `${input.language}#${input.difficulty}#` : `${input.language}#`;
455
+ const variables = {
456
+ chapterId: input.id,
457
+ language_difficulty_createdAt: { beginsWith: rangeKey },
458
+ sortDirection: _nullishCoalesce(input.sortDirection, () => ( "DESC")),
459
+ limit: pageLimit,
460
+ nextToken,
461
+ ...Object.keys(filter).length > 0 ? { filter } : {}
462
+ };
463
+ const result = await this.network.query(
464
+ `query ListQuestionsByChapterAndLangDiff($chapterId: String!, $language_difficulty_createdAt: ModelStringKeyConditionInput, $sortDirection: ModelSortDirection, $limit: Int, $nextToken: String, $filter: ModelQuestionObjectFilterInput) {
465
+ listQuestionObjectByChapterIdAndLanguage_difficulty_createdAt(chapterId: $chapterId, language_difficulty_createdAt: $language_difficulty_createdAt, sortDirection: $sortDirection, limit: $limit, nextToken: $nextToken, filter: $filter) {
466
+ items {
467
+ id question imageUrl language level publicExam difficulty type voteCount
468
+ verificationStatus owner packId subjectId chapterId topicId createdAt
469
+ options { items { id content } }
470
+ }
471
+ nextToken
472
+ }
473
+ }`,
474
+ variables
475
+ );
476
+ return result.data.listQuestionObjectByChapterIdAndLanguage_difficulty_createdAt;
477
+ } else {
478
+ const queryFilter = { ...filter };
479
+ if (hasDifficulty) {
480
+ queryFilter.difficulty = { eq: input.difficulty };
268
481
  }
269
- }`,
270
- variables
271
- );
272
- return result.data.listQuestionObjectByChapterIdAndCreatedAt;
482
+ const variables = {
483
+ chapterId: input.id,
484
+ sortDirection: _nullishCoalesce(input.sortDirection, () => ( "DESC")),
485
+ limit: pageLimit,
486
+ nextToken,
487
+ ...Object.keys(queryFilter).length > 0 ? { filter: queryFilter } : {}
488
+ };
489
+ const result = await this.network.query(
490
+ `query ListQuestionsByChapter($chapterId: String!, $sortDirection: ModelSortDirection, $limit: Int, $nextToken: String, $filter: ModelQuestionObjectFilterInput) {
491
+ listQuestionObjectByChapterIdAndCreatedAt(chapterId: $chapterId, sortDirection: $sortDirection, limit: $limit, nextToken: $nextToken, filter: $filter) {
492
+ items {
493
+ id question imageUrl language level publicExam difficulty type voteCount
494
+ verificationStatus owner packId subjectId chapterId topicId createdAt
495
+ options { items { id content } }
496
+ }
497
+ nextToken
498
+ }
499
+ }`,
500
+ variables
501
+ );
502
+ return result.data.listQuestionObjectByChapterIdAndCreatedAt;
503
+ }
504
+ };
505
+ const hasPostFilter = hasLanguage ? Object.keys(filter).length > 0 : Object.keys(filter).length > 0 || hasDifficulty;
506
+ if (!hasPostFilter) {
507
+ return executeQuery(targetLimit, input.nextToken);
508
+ }
509
+ return paginateWithAccumulator(executeQuery, targetLimit, input.nextToken);
273
510
  }
274
511
  async listByTopic(input) {
275
- const variables = {
276
- topicId: input.id,
277
- sortDirection: _nullishCoalesce(input.sortDirection, () => ( "DESC")),
278
- limit: _nullishCoalesce(input.limit, () => ( 20)),
279
- nextToken: input.nextToken
512
+ const hasLanguage = Boolean(input.language && input.language !== "All" && input.language !== "Any");
513
+ const hasDifficulty = Boolean(input.difficulty && input.difficulty !== "All");
514
+ const filter = {};
515
+ if (input.publicExam) {
516
+ filter.publicExam = { eq: input.publicExam };
517
+ }
518
+ if (input.questionType && input.questionType !== "Both") {
519
+ filter.type = { eq: input.questionType };
520
+ }
521
+ const targetLimit = _nullishCoalesce(input.limit, () => ( 12));
522
+ const executeQuery = async (pageLimit, nextToken) => {
523
+ if (hasLanguage) {
524
+ const rangeKey = hasDifficulty ? `${input.language}#${input.difficulty}#` : `${input.language}#`;
525
+ const variables = {
526
+ topicId: input.id,
527
+ language_difficulty_createdAt: { beginsWith: rangeKey },
528
+ sortDirection: _nullishCoalesce(input.sortDirection, () => ( "DESC")),
529
+ limit: pageLimit,
530
+ nextToken,
531
+ ...Object.keys(filter).length > 0 ? { filter } : {}
532
+ };
533
+ const result = await this.network.query(
534
+ `query ListQuestionsByTopicAndLangDiff($topicId: String!, $language_difficulty_createdAt: ModelStringKeyConditionInput, $sortDirection: ModelSortDirection, $limit: Int, $nextToken: String, $filter: ModelQuestionObjectFilterInput) {
535
+ listQuestionObjectByTopicIdAndLanguage_difficulty_createdAt(topicId: $topicId, language_difficulty_createdAt: $language_difficulty_createdAt, sortDirection: $sortDirection, limit: $limit, nextToken: $nextToken, filter: $filter) {
536
+ items {
537
+ id question imageUrl language level publicExam difficulty type voteCount
538
+ verificationStatus owner packId subjectId chapterId topicId createdAt
539
+ options { items { id content } }
540
+ }
541
+ nextToken
542
+ }
543
+ }`,
544
+ variables
545
+ );
546
+ return result.data.listQuestionObjectByTopicIdAndLanguage_difficulty_createdAt;
547
+ } else {
548
+ const queryFilter = { ...filter };
549
+ if (hasDifficulty) {
550
+ queryFilter.difficulty = { eq: input.difficulty };
551
+ }
552
+ const variables = {
553
+ topicId: input.id,
554
+ sortDirection: _nullishCoalesce(input.sortDirection, () => ( "DESC")),
555
+ limit: pageLimit,
556
+ nextToken,
557
+ ...Object.keys(queryFilter).length > 0 ? { filter: queryFilter } : {}
558
+ };
559
+ const result = await this.network.query(
560
+ `query ListQuestionsByTopic($topicId: String!, $sortDirection: ModelSortDirection, $limit: Int, $nextToken: String, $filter: ModelQuestionObjectFilterInput) {
561
+ listQuestionObjectByTopicIdAndCreatedAt(topicId: $topicId, sortDirection: $sortDirection, limit: $limit, nextToken: $nextToken, filter: $filter) {
562
+ items {
563
+ id question imageUrl language level publicExam difficulty type voteCount
564
+ verificationStatus owner packId subjectId chapterId topicId createdAt
565
+ options { items { id content } }
566
+ }
567
+ nextToken
568
+ }
569
+ }`,
570
+ variables
571
+ );
572
+ return result.data.listQuestionObjectByTopicIdAndCreatedAt;
573
+ }
280
574
  };
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 } }
575
+ const hasPostFilter = hasLanguage ? Object.keys(filter).length > 0 : Object.keys(filter).length > 0 || hasDifficulty;
576
+ if (!hasPostFilter) {
577
+ return executeQuery(targetLimit, input.nextToken);
578
+ }
579
+ return paginateWithAccumulator(executeQuery, targetLimit, input.nextToken);
580
+ }
581
+ async listByPublicExam(input) {
582
+ const hasLanguage = Boolean(input.language && input.language !== "All" && input.language !== "Any");
583
+ const hasDifficulty = Boolean(input.difficulty && input.difficulty !== "All");
584
+ const filter = {};
585
+ if (input.questionType && input.questionType !== "Both") {
586
+ filter.type = { eq: input.questionType };
587
+ }
588
+ const targetLimit = _nullishCoalesce(input.limit, () => ( 12));
589
+ const executeQuery = async (pageLimit, nextToken) => {
590
+ if (hasLanguage) {
591
+ const rangeKey = hasDifficulty ? `${input.language}#${input.difficulty}#` : `${input.language}#`;
592
+ const variables2 = {
593
+ publicExam: input.publicExam,
594
+ language_difficulty_createdAt: { beginsWith: rangeKey },
595
+ sortDirection: _nullishCoalesce(input.sortDirection, () => ( "DESC")),
596
+ limit: pageLimit,
597
+ nextToken,
598
+ ...Object.keys(filter).length > 0 ? { filter } : {}
599
+ };
600
+ try {
601
+ const result2 = await this.network.query(
602
+ `query ListQuestionsByPublicExamAndLangDiff($publicExam: String!, $language_difficulty_createdAt: ModelStringKeyConditionInput, $sortDirection: ModelSortDirection, $limit: Int, $nextToken: String, $filter: ModelQuestionObjectFilterInput) {
603
+ listQuestionObjectByPublicExamAndLanguage_difficulty_createdAt(publicExam: $publicExam, language_difficulty_createdAt: $language_difficulty_createdAt, sortDirection: $sortDirection, limit: $limit, nextToken: $nextToken, filter: $filter) {
604
+ items {
605
+ id question imageUrl language level publicExam difficulty type voteCount
606
+ verificationStatus owner packId subjectId chapterId topicId createdAt
607
+ options { items { id content } }
608
+ }
609
+ nextToken
610
+ }
611
+ }`,
612
+ variables2
613
+ );
614
+ if (_optionalChain([result2, 'access', _9 => _9.data, 'optionalAccess', _10 => _10.listQuestionObjectByPublicExamAndLanguage_difficulty_createdAt])) {
615
+ return result2.data.listQuestionObjectByPublicExamAndLanguage_difficulty_createdAt;
288
616
  }
289
- nextToken
617
+ } catch (e2) {
290
618
  }
291
- }`,
292
- variables
293
- );
294
- return result.data.listQuestionObjectByTopicIdAndCreatedAt;
619
+ }
620
+ const fallbackFilter = { ...filter };
621
+ if (hasLanguage) {
622
+ fallbackFilter.language = { eq: input.language };
623
+ }
624
+ if (hasDifficulty) {
625
+ fallbackFilter.difficulty = { eq: input.difficulty };
626
+ }
627
+ const variables = {
628
+ publicExam: input.publicExam,
629
+ sortDirection: _nullishCoalesce(input.sortDirection, () => ( "DESC")),
630
+ limit: pageLimit,
631
+ nextToken,
632
+ ...Object.keys(fallbackFilter).length > 0 ? { filter: fallbackFilter } : {}
633
+ };
634
+ const result = await this.network.query(
635
+ `query ListQuestionsByPublicExam($publicExam: String!, $sortDirection: ModelSortDirection, $limit: Int, $nextToken: String, $filter: ModelQuestionObjectFilterInput) {
636
+ listQuestionObjectByPublicExamAndCreatedAt(publicExam: $publicExam, sortDirection: $sortDirection, limit: $limit, nextToken: $nextToken, filter: $filter) {
637
+ items {
638
+ id question imageUrl language level publicExam difficulty type voteCount
639
+ verificationStatus owner packId subjectId chapterId topicId createdAt
640
+ options { items { id content } }
641
+ }
642
+ nextToken
643
+ }
644
+ }`,
645
+ variables
646
+ );
647
+ return result.data.listQuestionObjectByPublicExamAndCreatedAt;
648
+ };
649
+ const hasPostFilter = hasLanguage ? Object.keys(filter).length > 0 : Object.keys(filter).length > 0 || hasLanguage || hasDifficulty;
650
+ if (!hasPostFilter) {
651
+ return executeQuery(targetLimit, input.nextToken);
652
+ }
653
+ return paginateWithAccumulator(executeQuery, targetLimit, input.nextToken);
295
654
  }
296
655
  async listByRandomHash(input) {
297
656
  const compositeKey = [input.language, input.difficulty].filter(Boolean).join("_");
@@ -305,7 +664,7 @@ var QuestionService = class {
305
664
  `query ListByRandomHash($subjectId: String!, $language_difficulty_randomHash: ModelStringKeyConditionInput, $sortDirection: ModelSortDirection, $limit: Int) {
306
665
  listQuestionObjectBySubjectIdAndLanguage_difficulty_randomHash(subjectId: $subjectId, language_difficulty_randomHash: $language_difficulty_randomHash, sortDirection: $sortDirection, limit: $limit) {
307
666
  items {
308
- id question imageUrl language level difficulty type voteCount
667
+ id question imageUrl language level publicExam difficulty type voteCount
309
668
  verificationStatus owner packId subjectId chapterId topicId createdAt
310
669
  options { items { id content } }
311
670
  }
@@ -316,6 +675,76 @@ var QuestionService = class {
316
675
  );
317
676
  return result.data.listQuestionObjectBySubjectIdAndLanguage_difficulty_randomHash;
318
677
  }
678
+ async listByLevel(input) {
679
+ const hasLanguage = Boolean(input.language && input.language !== "All" && input.language !== "Any");
680
+ const hasDifficulty = Boolean(input.difficulty && input.difficulty !== "All");
681
+ const filter = {};
682
+ if (input.publicExam) {
683
+ filter.publicExam = { eq: input.publicExam };
684
+ }
685
+ if (input.questionType && input.questionType !== "Both") {
686
+ filter.type = { eq: input.questionType };
687
+ }
688
+ const targetLimit = _nullishCoalesce(input.limit, () => ( 12));
689
+ const executeQuery = async (pageLimit, nextToken) => {
690
+ if (hasLanguage) {
691
+ const rangeKey = hasDifficulty ? `${input.language}#${input.difficulty}#` : `${input.language}#`;
692
+ const variables = {
693
+ level: input.level,
694
+ language_difficulty_createdAt: { beginsWith: rangeKey },
695
+ sortDirection: _nullishCoalesce(input.sortDirection, () => ( "DESC")),
696
+ limit: pageLimit,
697
+ nextToken,
698
+ ...Object.keys(filter).length > 0 ? { filter } : {}
699
+ };
700
+ const result = await this.network.query(
701
+ `query ListQuestionsByLevelAndLangDiff($level: String!, $language_difficulty_createdAt: ModelStringKeyConditionInput, $sortDirection: ModelSortDirection, $limit: Int, $nextToken: String, $filter: ModelQuestionObjectFilterInput) {
702
+ listQuestionObjectByLevelAndLanguage_difficulty_createdAt(level: $level, language_difficulty_createdAt: $language_difficulty_createdAt, sortDirection: $sortDirection, limit: $limit, nextToken: $nextToken, filter: $filter) {
703
+ items {
704
+ id question imageUrl language level publicExam difficulty type voteCount
705
+ verificationStatus owner packId subjectId chapterId topicId createdAt
706
+ options { items { id content } }
707
+ }
708
+ nextToken
709
+ }
710
+ }`,
711
+ variables
712
+ );
713
+ return result.data.listQuestionObjectByLevelAndLanguage_difficulty_createdAt;
714
+ } else {
715
+ const queryFilter = { ...filter };
716
+ if (hasDifficulty) {
717
+ queryFilter.difficulty = { eq: input.difficulty };
718
+ }
719
+ const variables = {
720
+ level: input.level,
721
+ sortDirection: _nullishCoalesce(input.sortDirection, () => ( "DESC")),
722
+ limit: pageLimit,
723
+ nextToken,
724
+ ...Object.keys(queryFilter).length > 0 ? { filter: queryFilter } : {}
725
+ };
726
+ const result = await this.network.query(
727
+ `query ListQuestionsByLevel($level: String!, $sortDirection: ModelSortDirection, $limit: Int, $nextToken: String, $filter: ModelQuestionObjectFilterInput) {
728
+ listQuestionObjectByLevelAndCreatedAt(level: $level, sortDirection: $sortDirection, limit: $limit, nextToken: $nextToken, filter: $filter) {
729
+ items {
730
+ id question imageUrl language level publicExam difficulty type voteCount
731
+ verificationStatus owner packId subjectId chapterId topicId createdAt
732
+ options { items { id content } }
733
+ }
734
+ nextToken
735
+ }
736
+ }`,
737
+ variables
738
+ );
739
+ return result.data.listQuestionObjectByLevelAndCreatedAt;
740
+ }
741
+ };
742
+ const hasPostFilter = hasLanguage ? Object.keys(filter).length > 0 : Object.keys(filter).length > 0 || hasDifficulty;
743
+ if (!hasPostFilter) {
744
+ return executeQuery(targetLimit, input.nextToken);
745
+ }
746
+ return paginateWithAccumulator(executeQuery, targetLimit, input.nextToken);
747
+ }
319
748
  };
320
749
 
321
750
  // src/services/pack.service.ts
@@ -325,8 +754,8 @@ var PackService = class {
325
754
  }
326
755
  async create(input) {
327
756
  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) {
757
+ `mutation CreatePack($name: String!, $level: String!, $publicExam: String, $language: String!, $difficulty: String!, $automaticNumbering: Boolean, $subjectId: ID, $chapterId: ID, $topicId: ID) {
758
+ createPackObject(name: $name, level: $level, publicExam: $publicExam, language: $language, difficulty: $difficulty, automaticNumbering: $automaticNumbering, subjectId: $subjectId, chapterId: $chapterId, topicId: $topicId) {
330
759
  status id
331
760
  }
332
761
  }`,
@@ -338,7 +767,7 @@ var PackService = class {
338
767
  const result = await this.network.query(
339
768
  `query GetPack($id: ID!) {
340
769
  getPack(id: $id) {
341
- id name language level difficulty questionCount automaticNumbering
770
+ id name language level publicExam difficulty questionCount automaticNumbering
342
771
  owner subjectId chapterId topicId packGroupId verificationStatus createdAt
343
772
  tags { items { id tagId } }
344
773
  user { id fullName avatarUrl }
@@ -352,13 +781,13 @@ var PackService = class {
352
781
  const result = await this.network.query(
353
782
  `query GetFullPack($id: ID!) {
354
783
  getPack(id: $id) {
355
- id name language level difficulty questionCount automaticNumbering
784
+ id name language level publicExam difficulty questionCount automaticNumbering
356
785
  owner subjectId chapterId topicId packGroupId verificationStatus createdAt
357
786
  tags { items { id tagId } }
358
787
  user { id fullName avatarUrl }
359
788
  questions(sortDirection: ASC, limit: 200) {
360
789
  items {
361
- id question answer explanation extra imageUrl language level difficulty type
790
+ id question answer explanation extra imageUrl language level publicExam difficulty type
362
791
  markingInstructions voteCount owner order createdAt
363
792
  options { items { id content } }
364
793
  tags { items { id tagId } }
@@ -368,13 +797,33 @@ var PackService = class {
368
797
  }`,
369
798
  { id }
370
799
  );
371
- return result.data.getPack;
800
+ const pack = result.data.getPack;
801
+ if (pack && pack.questions && Array.isArray(pack.questions.items)) {
802
+ pack.questions.items.sort((a, b) => {
803
+ const isWrittenA = a.type === "WRITTEN" ? 1 : 0;
804
+ const isWrittenB = b.type === "WRITTEN" ? 1 : 0;
805
+ if (isWrittenA !== isWrittenB) {
806
+ return isWrittenA - isWrittenB;
807
+ }
808
+ const orderA = typeof a.order === "number" ? a.order : a.order ? Number(a.order) : 0;
809
+ const orderB = typeof b.order === "number" ? b.order : b.order ? Number(b.order) : 0;
810
+ if (orderA !== orderB) {
811
+ if (orderA === 0) return 1;
812
+ if (orderB === 0) return -1;
813
+ return orderA - orderB;
814
+ }
815
+ const timeA = a.createdAt ? new Date(a.createdAt).getTime() : 0;
816
+ const timeB = b.createdAt ? new Date(b.createdAt).getTime() : 0;
817
+ return timeA - timeB;
818
+ });
819
+ }
820
+ return pack;
372
821
  }
373
822
  async update(id, fields) {
374
823
  const result = await this.network.mutate(
375
824
  `mutation UpdatePack($input: UpdatePackInput!) {
376
825
  updatePack(input: $input) {
377
- id name language level difficulty questionCount automaticNumbering
826
+ id name language level publicExam difficulty questionCount automaticNumbering
378
827
  owner subjectId chapterId topicId verificationStatus createdAt
379
828
  }
380
829
  }`,
@@ -384,12 +833,15 @@ var PackService = class {
384
833
  }
385
834
  async delete(id) {
386
835
  const result = await this.network.mutate(
387
- `mutation DeletePack($input: DeletePackInput!) {
388
- deletePack(input: $input) { id }
836
+ `mutation DeletePackCascade($packId: ID!) {
837
+ deletePackCascade(packId: $packId) { status }
389
838
  }`,
390
- { input: { id } }
839
+ { packId: id }
391
840
  );
392
- return result.data.deletePack;
841
+ if (_optionalChain([result, 'access', _11 => _11.data, 'access', _12 => _12.deletePackCascade, 'optionalAccess', _13 => _13.status]) === "success") {
842
+ return { id };
843
+ }
844
+ return null;
393
845
  }
394
846
  async verify(id, userId) {
395
847
  const result = await this.network.mutate(
@@ -409,7 +861,27 @@ var PackService = class {
409
861
  );
410
862
  return result.data.createPackReport;
411
863
  }
412
- async submitExam(packId, selectedOptions) {
864
+ async submitExam(packId, selectedOptions, options) {
865
+ if (_optionalChain([options, 'optionalAccess', _14 => _14.functionUrl])) {
866
+ try {
867
+ const headers = { "Content-Type": "application/json" };
868
+ if (options.authToken) {
869
+ headers["Authorization"] = `Bearer ${options.authToken}`;
870
+ }
871
+ const response = await fetch(options.functionUrl, {
872
+ method: "POST",
873
+ headers,
874
+ body: JSON.stringify({ packId, selectedOptions: JSON.stringify(selectedOptions) })
875
+ });
876
+ if (response.ok) {
877
+ const data = await response.json();
878
+ return data;
879
+ }
880
+ console.warn(`[PackService] Function URL failed with status ${response.status}, falling back to GraphQL...`);
881
+ } catch (err) {
882
+ console.warn("[PackService] Function URL fetch error, falling back to GraphQL...", err);
883
+ }
884
+ }
413
885
  const result = await this.network.mutate(
414
886
  `mutation SubmitPackExam($packId: ID!, $selectedOptions: String!) {
415
887
  submitPackExam(packId: $packId, selectedOptions: $selectedOptions) {
@@ -426,7 +898,7 @@ var PackService = class {
426
898
  listPackExamResultsByOwnerAndCreatedAt(owner: $owner, sortDirection: $sortDirection, nextToken: $nextToken) {
427
899
  items {
428
900
  id packId selectedOptions aiEvaluations owner createdAt
429
- pack { id name language level difficulty questionCount }
901
+ pack { id name language level publicExam difficulty questionCount }
430
902
  }
431
903
  nextToken
432
904
  }
@@ -440,7 +912,7 @@ var PackService = class {
440
912
  `query GetPackExamResult($id: ID!) {
441
913
  getPackExamResults(id: $id) {
442
914
  id packId selectedOptions aiEvaluations owner createdAt
443
- pack { id name language level difficulty questionCount }
915
+ pack { id name language level publicExam difficulty questionCount }
444
916
  }
445
917
  }`,
446
918
  { id: resultId }
@@ -448,79 +920,462 @@ var PackService = class {
448
920
  return result.data.getPackExamResults;
449
921
  }
450
922
  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
923
+ const hasLanguage = Boolean(input.language && input.language !== "All" && input.language !== "Any");
924
+ const hasDifficulty = Boolean(input.difficulty && input.difficulty !== "All");
925
+ const filter = {};
926
+ if (input.publicExam) {
927
+ filter.publicExam = { eq: input.publicExam };
928
+ }
929
+ const targetLimit = _nullishCoalesce(input.limit, () => ( 12));
930
+ const executeQuery = async (pageLimit, nextToken) => {
931
+ if (hasLanguage) {
932
+ const rangeKey = hasDifficulty ? `${input.language}#${input.difficulty}#` : `${input.language}#`;
933
+ const variables = {
934
+ subjectId: input.id,
935
+ language_difficulty_createdAt: { beginsWith: rangeKey },
936
+ sortDirection: _nullishCoalesce(input.sortDirection, () => ( "DESC")),
937
+ limit: pageLimit,
938
+ nextToken,
939
+ ...Object.keys(filter).length > 0 ? { filter } : {}
940
+ };
941
+ const result = await this.network.query(
942
+ `query ListPacksBySubjectAndLangDiff($subjectId: String!, $language_difficulty_createdAt: ModelStringKeyConditionInput, $sortDirection: ModelSortDirection, $limit: Int, $nextToken: String, $filter: ModelPackFilterInput) {
943
+ listPackBySubjectIdAndLanguage_difficulty_createdAt(subjectId: $subjectId, language_difficulty_createdAt: $language_difficulty_createdAt, sortDirection: $sortDirection, limit: $limit, nextToken: $nextToken, filter: $filter) {
944
+ items {
945
+ id name language level publicExam difficulty questionCount owner createdAt verificationStatus subjectId chapterId topicId
946
+ tags { items { id tagId } }
947
+ }
948
+ nextToken
949
+ }
950
+ }`,
951
+ variables
952
+ );
953
+ return result.data.listPackBySubjectIdAndLanguage_difficulty_createdAt;
954
+ } else {
955
+ const queryFilter = { ...filter };
956
+ if (hasDifficulty) {
957
+ queryFilter.difficulty = { eq: input.difficulty };
459
958
  }
460
- }`,
461
- { subjectId: input.id, sortDirection: "DESC", limit: _nullishCoalesce(input.limit, () => ( 20)), nextToken: input.nextToken }
462
- );
463
- return result.data.listPackBySubjectIdAndCreatedAt;
959
+ const variables = {
960
+ subjectId: input.id,
961
+ sortDirection: _nullishCoalesce(input.sortDirection, () => ( "DESC")),
962
+ limit: pageLimit,
963
+ nextToken,
964
+ ...Object.keys(queryFilter).length > 0 ? { filter: queryFilter } : {}
965
+ };
966
+ const result = await this.network.query(
967
+ `query ListPacksBySubject($subjectId: String!, $sortDirection: ModelSortDirection, $limit: Int, $nextToken: String, $filter: ModelPackFilterInput) {
968
+ listPackBySubjectIdAndCreatedAt(subjectId: $subjectId, sortDirection: $sortDirection, limit: $limit, nextToken: $nextToken, filter: $filter) {
969
+ items {
970
+ id name language level publicExam difficulty questionCount owner createdAt verificationStatus subjectId chapterId topicId
971
+ tags { items { id tagId } }
972
+ }
973
+ nextToken
974
+ }
975
+ }`,
976
+ variables
977
+ );
978
+ return result.data.listPackBySubjectIdAndCreatedAt;
979
+ }
980
+ };
981
+ const hasPostFilter = hasLanguage ? Object.keys(filter).length > 0 : Object.keys(filter).length > 0 || hasDifficulty;
982
+ if (!hasPostFilter) {
983
+ return executeQuery(targetLimit, input.nextToken);
984
+ }
985
+ return paginateWithAccumulator(executeQuery, targetLimit, input.nextToken);
464
986
  }
465
987
  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
988
+ const hasLanguage = Boolean(input.language && input.language !== "All" && input.language !== "Any");
989
+ const hasDifficulty = Boolean(input.difficulty && input.difficulty !== "All");
990
+ const filter = {};
991
+ if (input.publicExam) {
992
+ filter.publicExam = { eq: input.publicExam };
993
+ }
994
+ const targetLimit = _nullishCoalesce(input.limit, () => ( 12));
995
+ const executeQuery = async (pageLimit, nextToken) => {
996
+ if (hasLanguage) {
997
+ const rangeKey = hasDifficulty ? `${input.language}#${input.difficulty}#` : `${input.language}#`;
998
+ const variables = {
999
+ chapterId: input.id,
1000
+ language_difficulty_createdAt: { beginsWith: rangeKey },
1001
+ sortDirection: _nullishCoalesce(input.sortDirection, () => ( "DESC")),
1002
+ limit: pageLimit,
1003
+ nextToken,
1004
+ ...Object.keys(filter).length > 0 ? { filter } : {}
1005
+ };
1006
+ const result = await this.network.query(
1007
+ `query ListPacksByChapterAndLangDiff($chapterId: String!, $language_difficulty_createdAt: ModelStringKeyConditionInput, $sortDirection: ModelSortDirection, $limit: Int, $nextToken: String, $filter: ModelPackFilterInput) {
1008
+ listPackByChapterIdAndLanguage_difficulty_createdAt(chapterId: $chapterId, language_difficulty_createdAt: $language_difficulty_createdAt, sortDirection: $sortDirection, limit: $limit, nextToken: $nextToken, filter: $filter) {
1009
+ items {
1010
+ id name language level publicExam difficulty questionCount owner createdAt verificationStatus subjectId chapterId topicId
1011
+ tags { items { id tagId } }
1012
+ }
1013
+ nextToken
1014
+ }
1015
+ }`,
1016
+ variables
1017
+ );
1018
+ return result.data.listPackByChapterIdAndLanguage_difficulty_createdAt;
1019
+ } else {
1020
+ const queryFilter = { ...filter };
1021
+ if (hasDifficulty) {
1022
+ queryFilter.difficulty = { eq: input.difficulty };
474
1023
  }
475
- }`,
476
- { chapterId: input.id, sortDirection: "DESC", limit: _nullishCoalesce(input.limit, () => ( 20)), nextToken: input.nextToken }
477
- );
478
- return result.data.listPackByChapterIdAndCreatedAt;
1024
+ const variables = {
1025
+ chapterId: input.id,
1026
+ sortDirection: _nullishCoalesce(input.sortDirection, () => ( "DESC")),
1027
+ limit: pageLimit,
1028
+ nextToken,
1029
+ ...Object.keys(queryFilter).length > 0 ? { filter: queryFilter } : {}
1030
+ };
1031
+ const result = await this.network.query(
1032
+ `query ListPacksByChapter($chapterId: String!, $sortDirection: ModelSortDirection, $limit: Int, $nextToken: String, $filter: ModelPackFilterInput) {
1033
+ listPackByChapterIdAndCreatedAt(chapterId: $chapterId, sortDirection: $sortDirection, limit: $limit, nextToken: $nextToken, filter: $filter) {
1034
+ items {
1035
+ id name language level publicExam difficulty questionCount owner createdAt verificationStatus subjectId chapterId topicId
1036
+ tags { items { id tagId } }
1037
+ }
1038
+ nextToken
1039
+ }
1040
+ }`,
1041
+ variables
1042
+ );
1043
+ return result.data.listPackByChapterIdAndCreatedAt;
1044
+ }
1045
+ };
1046
+ const hasPostFilter = hasLanguage ? Object.keys(filter).length > 0 : Object.keys(filter).length > 0 || hasDifficulty;
1047
+ if (!hasPostFilter) {
1048
+ return executeQuery(targetLimit, input.nextToken);
1049
+ }
1050
+ return paginateWithAccumulator(executeQuery, targetLimit, input.nextToken);
479
1051
  }
480
1052
  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 } }
1053
+ const hasLanguage = Boolean(input.language && input.language !== "All" && input.language !== "Any");
1054
+ const hasDifficulty = Boolean(input.difficulty && input.difficulty !== "All");
1055
+ const filter = {};
1056
+ if (input.publicExam) {
1057
+ filter.publicExam = { eq: input.publicExam };
1058
+ }
1059
+ const targetLimit = _nullishCoalesce(input.limit, () => ( 12));
1060
+ const executeQuery = async (pageLimit, nextToken) => {
1061
+ if (hasLanguage) {
1062
+ const rangeKey = hasDifficulty ? `${input.language}#${input.difficulty}#` : `${input.language}#`;
1063
+ const variables = {
1064
+ topicId: input.id,
1065
+ language_difficulty_createdAt: { beginsWith: rangeKey },
1066
+ sortDirection: _nullishCoalesce(input.sortDirection, () => ( "DESC")),
1067
+ limit: pageLimit,
1068
+ nextToken,
1069
+ ...Object.keys(filter).length > 0 ? { filter } : {}
1070
+ };
1071
+ const result = await this.network.query(
1072
+ `query ListPacksByTopicAndLangDiff($topicId: String!, $language_difficulty_createdAt: ModelStringKeyConditionInput, $sortDirection: ModelSortDirection, $limit: Int, $nextToken: String, $filter: ModelPackFilterInput) {
1073
+ listPackByTopicIdAndLanguage_difficulty_createdAt(topicId: $topicId, language_difficulty_createdAt: $language_difficulty_createdAt, sortDirection: $sortDirection, limit: $limit, nextToken: $nextToken, filter: $filter) {
1074
+ items {
1075
+ id name language level publicExam difficulty questionCount owner createdAt verificationStatus subjectId chapterId topicId
1076
+ tags { items { id tagId } }
1077
+ }
1078
+ nextToken
1079
+ }
1080
+ }`,
1081
+ variables
1082
+ );
1083
+ return result.data.listPackByTopicIdAndLanguage_difficulty_createdAt;
1084
+ } else {
1085
+ const queryFilter = { ...filter };
1086
+ if (hasDifficulty) {
1087
+ queryFilter.difficulty = { eq: input.difficulty };
1088
+ }
1089
+ const variables = {
1090
+ topicId: input.id,
1091
+ sortDirection: _nullishCoalesce(input.sortDirection, () => ( "DESC")),
1092
+ limit: pageLimit,
1093
+ nextToken,
1094
+ ...Object.keys(queryFilter).length > 0 ? { filter: queryFilter } : {}
1095
+ };
1096
+ const result = await this.network.query(
1097
+ `query ListPacksByTopic($topicId: String!, $sortDirection: ModelSortDirection, $limit: Int, $nextToken: String, $filter: ModelPackFilterInput) {
1098
+ listPackByTopicIdAndCreatedAt(topicId: $topicId, sortDirection: $sortDirection, limit: $limit, nextToken: $nextToken, filter: $filter) {
1099
+ items {
1100
+ id name language level publicExam difficulty questionCount owner createdAt verificationStatus subjectId chapterId topicId
1101
+ tags { items { id tagId } }
1102
+ }
1103
+ nextToken
1104
+ }
1105
+ }`,
1106
+ variables
1107
+ );
1108
+ return result.data.listPackByTopicIdAndCreatedAt;
1109
+ }
1110
+ };
1111
+ const hasPostFilter = hasLanguage ? Object.keys(filter).length > 0 : Object.keys(filter).length > 0 || hasDifficulty;
1112
+ if (!hasPostFilter) {
1113
+ return executeQuery(targetLimit, input.nextToken);
1114
+ }
1115
+ return paginateWithAccumulator(executeQuery, targetLimit, input.nextToken);
1116
+ }
1117
+ async listByPublicExam(input) {
1118
+ const hasLanguage = Boolean(input.language && input.language !== "All" && input.language !== "Any");
1119
+ const hasDifficulty = Boolean(input.difficulty && input.difficulty !== "All");
1120
+ const filter = {};
1121
+ const targetLimit = _nullishCoalesce(input.limit, () => ( 12));
1122
+ const executeQuery = async (pageLimit, nextToken) => {
1123
+ if (hasLanguage) {
1124
+ const rangeKey = hasDifficulty ? `${input.language}#${input.difficulty}#` : `${input.language}#`;
1125
+ const variables = {
1126
+ publicExam: input.publicExam,
1127
+ language_difficulty_createdAt: { beginsWith: rangeKey },
1128
+ sortDirection: _nullishCoalesce(input.sortDirection, () => ( "DESC")),
1129
+ limit: pageLimit,
1130
+ nextToken,
1131
+ ...Object.keys(filter).length > 0 ? { filter } : {}
1132
+ };
1133
+ try {
1134
+ const result2 = await this.network.query(
1135
+ `query ListPacksByPublicExamAndLangDiff($publicExam: String!, $language_difficulty_createdAt: ModelStringKeyConditionInput, $sortDirection: ModelSortDirection, $limit: Int, $nextToken: String, $filter: ModelPackFilterInput) {
1136
+ listPackByPublicExamAndLanguage_difficulty_createdAt(publicExam: $publicExam, language_difficulty_createdAt: $language_difficulty_createdAt, sortDirection: $sortDirection, limit: $limit, nextToken: $nextToken, filter: $filter) {
1137
+ items {
1138
+ id name language level publicExam difficulty questionCount owner createdAt verificationStatus subjectId chapterId topicId
1139
+ tags { items { id tagId } }
1140
+ }
1141
+ nextToken
1142
+ }
1143
+ }`,
1144
+ variables
1145
+ );
1146
+ if (_optionalChain([result2, 'access', _15 => _15.data, 'optionalAccess', _16 => _16.listPackByPublicExamAndLanguage_difficulty_createdAt])) {
1147
+ return result2.data.listPackByPublicExamAndLanguage_difficulty_createdAt;
487
1148
  }
488
- nextToken
1149
+ } catch (e3) {
489
1150
  }
490
- }`,
491
- { topicId: input.id, sortDirection: "DESC", limit: _nullishCoalesce(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 }
1151
+ }
1152
+ const fallbackFilter = { ...filter };
1153
+ if (hasLanguage) {
1154
+ fallbackFilter.language = { eq: input.language };
1155
+ }
1156
+ if (hasDifficulty) {
1157
+ fallbackFilter.difficulty = { eq: input.difficulty };
1158
+ }
1159
+ try {
1160
+ const variables = {
1161
+ publicExam: input.publicExam,
1162
+ sortDirection: _nullishCoalesce(input.sortDirection, () => ( "DESC")),
1163
+ limit: pageLimit,
1164
+ nextToken,
1165
+ ...Object.keys(fallbackFilter).length > 0 ? { filter: fallbackFilter } : {}
1166
+ };
1167
+ const result2 = await this.network.query(
1168
+ `query ListPacksByPublicExam($publicExam: String!, $sortDirection: ModelSortDirection, $limit: Int, $nextToken: String, $filter: ModelPackFilterInput) {
1169
+ listPackByPublicExamAndCreatedAt(publicExam: $publicExam, sortDirection: $sortDirection, limit: $limit, nextToken: $nextToken, filter: $filter) {
1170
+ items {
1171
+ id name language level publicExam difficulty questionCount owner createdAt verificationStatus subjectId chapterId topicId
1172
+ tags { items { id tagId } }
1173
+ }
1174
+ nextToken
1175
+ }
1176
+ }`,
1177
+ variables
1178
+ );
1179
+ if (_optionalChain([result2, 'access', _17 => _17.data, 'optionalAccess', _18 => _18.listPackByPublicExamAndCreatedAt])) {
1180
+ return result2.data.listPackByPublicExamAndCreatedAt;
1181
+ }
1182
+ } catch (e4) {
1183
+ }
1184
+ const scanFilter = {
1185
+ publicExam: { eq: input.publicExam },
1186
+ ...fallbackFilter
1187
+ };
1188
+ const result = await this.network.query(
1189
+ `query ListPacksFallback($filter: ModelPackFilterInput, $limit: Int, $nextToken: String) {
1190
+ listPacks(filter: $filter, limit: $limit, nextToken: $nextToken) {
1191
+ items {
1192
+ id name language level publicExam difficulty questionCount owner createdAt verificationStatus subjectId chapterId topicId
1193
+ tags { items { id tagId } }
1194
+ }
1195
+ nextToken
1196
+ }
1197
+ }`,
1198
+ { filter: scanFilter, limit: pageLimit, nextToken }
1199
+ );
1200
+ return result.data.listPacks;
1201
+ };
1202
+ const hasPostFilter = hasLanguage ? Object.keys(filter).length > 0 : Object.keys(filter).length > 0 || hasLanguage || hasDifficulty;
1203
+ if (!hasPostFilter) {
1204
+ return executeQuery(targetLimit, input.nextToken);
1205
+ }
1206
+ return paginateWithAccumulator(executeQuery, targetLimit, input.nextToken);
1207
+ }
1208
+ async listByLevel(input) {
1209
+ const hasLanguage = Boolean(input.language && input.language !== "All" && input.language !== "Any");
1210
+ const hasDifficulty = Boolean(input.difficulty && input.difficulty !== "All");
1211
+ const filter = {};
1212
+ if (input.publicExam) {
1213
+ filter.publicExam = { eq: input.publicExam };
1214
+ }
1215
+ const targetLimit = _nullishCoalesce(input.limit, () => ( 12));
1216
+ const executeQuery = async (pageLimit, nextToken) => {
1217
+ if (hasLanguage) {
1218
+ const rangeKey = hasDifficulty ? `${input.language}#${input.difficulty}#` : `${input.language}#`;
1219
+ const variables = {
1220
+ level: input.level,
1221
+ language_difficulty_createdAt: { beginsWith: rangeKey },
1222
+ sortDirection: _nullishCoalesce(input.sortDirection, () => ( "DESC")),
1223
+ limit: pageLimit,
1224
+ nextToken,
1225
+ ...Object.keys(filter).length > 0 ? { filter } : {}
1226
+ };
1227
+ const result = await this.network.query(
1228
+ `query ListPacksByLevelAndLangDiff($level: String!, $language_difficulty_createdAt: ModelStringKeyConditionInput, $sortDirection: ModelSortDirection, $limit: Int, $nextToken: String, $filter: ModelPackFilterInput) {
1229
+ listPackByLevelAndLanguage_difficulty_createdAt(level: $level, language_difficulty_createdAt: $language_difficulty_createdAt, sortDirection: $sortDirection, limit: $limit, nextToken: $nextToken, filter: $filter) {
1230
+ items {
1231
+ id name language level publicExam difficulty questionCount owner createdAt verificationStatus subjectId chapterId topicId
1232
+ tags { items { id tagId } }
1233
+ }
1234
+ nextToken
502
1235
  }
503
- }` : `query ListPackGroups($type: String!, $sortDirection: ModelSortDirection) {
504
- listPackGroupByTypeAndOrder(type: $type, sortDirection: $sortDirection) {
505
- items { id name description imageUrl type level typeLevel packCount order }
1236
+ }`,
1237
+ variables
1238
+ );
1239
+ return result.data.listPackByLevelAndLanguage_difficulty_createdAt;
1240
+ } else {
1241
+ const queryFilter = { ...filter };
1242
+ if (hasDifficulty) {
1243
+ queryFilter.difficulty = { eq: input.difficulty };
1244
+ }
1245
+ const variables = {
1246
+ level: input.level,
1247
+ sortDirection: _nullishCoalesce(input.sortDirection, () => ( "DESC")),
1248
+ limit: pageLimit,
1249
+ nextToken,
1250
+ ...Object.keys(queryFilter).length > 0 ? { filter: queryFilter } : {}
1251
+ };
1252
+ const result = await this.network.query(
1253
+ `query ListPacksByLevel($level: String!, $sortDirection: ModelSortDirection, $limit: Int, $nextToken: String, $filter: ModelPackFilterInput) {
1254
+ listPackByLevelAndCreatedAt(level: $level, sortDirection: $sortDirection, limit: $limit, nextToken: $nextToken, filter: $filter) {
1255
+ items {
1256
+ id name language level publicExam difficulty questionCount owner createdAt verificationStatus subjectId chapterId topicId
1257
+ tags { items { id tagId } }
1258
+ }
1259
+ nextToken
506
1260
  }
507
1261
  }`,
508
- variables
1262
+ variables
1263
+ );
1264
+ return result.data.listPackByLevelAndCreatedAt;
1265
+ }
1266
+ };
1267
+ const hasPostFilter = hasLanguage ? Object.keys(filter).length > 0 : Object.keys(filter).length > 0 || hasDifficulty;
1268
+ if (!hasPostFilter) {
1269
+ return executeQuery(targetLimit, input.nextToken);
1270
+ }
1271
+ return paginateWithAccumulator(executeQuery, targetLimit, input.nextToken);
1272
+ }
1273
+ async createGroup(input) {
1274
+ const typeLevel = input.type && input.level ? `${input.type}_${input.level}` : void 0;
1275
+ const mutationInput = {
1276
+ name: input.name,
1277
+ description: input.description,
1278
+ imageUrl: input.imageUrl,
1279
+ type: input.type,
1280
+ level: input.level,
1281
+ typeLevel,
1282
+ order: input.order
1283
+ };
1284
+ const result = await this.network.mutate(
1285
+ `mutation CreatePackGroup($input: CreatePackGroupInput!) {
1286
+ createPackGroup(input: $input) {
1287
+ id name description imageUrl type level typeLevel packCount order
1288
+ }
1289
+ }`,
1290
+ { input: mutationInput }
509
1291
  );
510
- return result.data[queryField].items;
1292
+ const created = result.data.createPackGroup;
1293
+ if (created && input.packIds && input.packIds.length > 0) {
1294
+ await Promise.all(
1295
+ input.packIds.map((pId) => this.update(pId, { packGroupId: created.id }))
1296
+ );
1297
+ }
1298
+ return created;
1299
+ }
1300
+ async deleteGroup(id) {
1301
+ const result = await this.network.mutate(
1302
+ `mutation DeletePackGroup($input: DeletePackGroupInput!) {
1303
+ deletePackGroup(input: $input) {
1304
+ id
1305
+ }
1306
+ }`,
1307
+ { input: { id } }
1308
+ );
1309
+ return result.data.deletePackGroup;
1310
+ }
1311
+ async listGroups(typeOrInput, level) {
1312
+ let type;
1313
+ let targetLevel;
1314
+ let limit;
1315
+ let nextToken;
1316
+ let sortDirection = "ASC";
1317
+ const isLegacyCall = typeof typeOrInput === "string";
1318
+ if (isLegacyCall) {
1319
+ type = typeOrInput;
1320
+ targetLevel = level;
1321
+ } else {
1322
+ type = _nullishCoalesce(typeOrInput.type, () => ( "QB"));
1323
+ targetLevel = typeOrInput.level;
1324
+ limit = typeOrInput.limit;
1325
+ nextToken = typeOrInput.nextToken;
1326
+ sortDirection = _nullishCoalesce(typeOrInput.sortDirection, () => ( "ASC"));
1327
+ }
1328
+ const queryField = targetLevel ? "listPackGroupByTypeLevelAndOrder" : "listPackGroupByTypeAndOrder";
1329
+ const variables = isLegacyCall ? targetLevel ? { typeLevel: `${type}_${targetLevel}`, sortDirection } : { type, sortDirection } : targetLevel ? { typeLevel: `${type}_${targetLevel}`, sortDirection, limit: _nullishCoalesce(limit, () => ( 20)), nextToken } : { type, sortDirection, limit: _nullishCoalesce(limit, () => ( 20)), nextToken };
1330
+ const queryString = isLegacyCall ? targetLevel ? `query ListPackGroups($typeLevel: String!, $sortDirection: ModelSortDirection) {
1331
+ listPackGroupByTypeLevelAndOrder(typeLevel: $typeLevel, sortDirection: $sortDirection) {
1332
+ items { id name description imageUrl type level typeLevel packCount order }
1333
+ }
1334
+ }` : `query ListPackGroups($type: String!, $sortDirection: ModelSortDirection) {
1335
+ listPackGroupByTypeAndOrder(type: $type, sortDirection: $sortDirection) {
1336
+ items { id name description imageUrl type level typeLevel packCount order }
1337
+ }
1338
+ }` : targetLevel ? `query ListPackGroupsByLevel($typeLevel: String!, $sortDirection: ModelSortDirection, $limit: Int, $nextToken: String) {
1339
+ listPackGroupByTypeLevelAndOrder(typeLevel: $typeLevel, sortDirection: $sortDirection, limit: $limit, nextToken: $nextToken) {
1340
+ items { id name description imageUrl type level typeLevel packCount order }
1341
+ nextToken
1342
+ }
1343
+ }` : `query ListPackGroupsByType($type: String!, $sortDirection: ModelSortDirection, $limit: Int, $nextToken: String) {
1344
+ listPackGroupByTypeAndOrder(type: $type, sortDirection: $sortDirection, limit: $limit, nextToken: $nextToken) {
1345
+ items { id name description imageUrl type level typeLevel packCount order }
1346
+ nextToken
1347
+ }
1348
+ }`;
1349
+ const result = await this.network.query(queryString, variables);
1350
+ const paginated = result.data[queryField];
1351
+ return isLegacyCall ? paginated.items : paginated;
511
1352
  }
512
1353
  async getGroup(id) {
513
1354
  const result = await this.network.query(
514
1355
  `query GetPackGroup($id: ID!) {
515
1356
  getPackGroup(id: $id) {
516
1357
  id name description imageUrl type level typeLevel packCount order
517
- packs { items { id name language level difficulty questionCount owner createdAt verificationStatus } }
1358
+ packs { items { id name language level publicExam difficulty questionCount owner createdAt verificationStatus } }
518
1359
  }
519
1360
  }`,
520
1361
  { id }
521
1362
  );
522
1363
  return result.data.getPackGroup;
523
1364
  }
1365
+ async list(input) {
1366
+ const result = await this.network.query(
1367
+ `query ListPacks($limit: Int, $nextToken: String) {
1368
+ listPacks(limit: $limit, nextToken: $nextToken) {
1369
+ items {
1370
+ id name language level publicExam difficulty questionCount owner createdAt verificationStatus packGroupId
1371
+ }
1372
+ nextToken
1373
+ }
1374
+ }`,
1375
+ { limit: _nullishCoalesce(_optionalChain([input, 'optionalAccess', _19 => _19.limit]), () => ( 20)), nextToken: _optionalChain([input, 'optionalAccess', _20 => _20.nextToken]) }
1376
+ );
1377
+ return result.data.listPacks;
1378
+ }
524
1379
  };
525
1380
 
526
1381
  // src/services/contest.service.ts
@@ -557,7 +1412,7 @@ var ContestService = class {
557
1412
  `query GetContestName($id: ID!) { getContest(id: $id) { name } }`,
558
1413
  { id }
559
1414
  );
560
- return _nullishCoalesce(_optionalChain([result, 'access', _7 => _7.data, 'access', _8 => _8.getContest, 'optionalAccess', _9 => _9.name]), () => ( null));
1415
+ return _nullishCoalesce(_optionalChain([result, 'access', _21 => _21.data, 'access', _22 => _22.getContest, 'optionalAccess', _23 => _23.name]), () => ( null));
561
1416
  }
562
1417
  async list(nextToken, level) {
563
1418
  const variables = level ? { level, sortDirection: "DESC", nextToken } : { globalPk: "ALL", sortDirection: "DESC", nextToken };
@@ -878,6 +1733,28 @@ var CourseService = class {
878
1733
  );
879
1734
  return result.data.publishCourseLiveExamResults;
880
1735
  }
1736
+ async uploadCourseItem(input) {
1737
+ const signed = await this.getSignedURL({
1738
+ courseId: input.courseId,
1739
+ fileName: input.fileName,
1740
+ fileType: input.fileType,
1741
+ parentId: _nullishCoalesce(input.parentId, () => ( void 0))
1742
+ });
1743
+ if (signed && signed.signedURL && input.fileData) {
1744
+ await fetch(signed.signedURL, {
1745
+ method: "PUT",
1746
+ body: input.fileData,
1747
+ headers: { "Content-Type": input.fileType }
1748
+ });
1749
+ }
1750
+ return this.createItem({
1751
+ courseId: input.courseId,
1752
+ name: input.itemName || input.fileName,
1753
+ order: input.itemOrder,
1754
+ type: "file",
1755
+ parentId: _nullishCoalesce(input.parentId, () => ( void 0))
1756
+ });
1757
+ }
881
1758
  };
882
1759
 
883
1760
  // src/services/curriculum.service.ts
@@ -945,6 +1822,67 @@ var CurriculumService = class {
945
1822
  );
946
1823
  return result.data.listTopicByChapterId.items;
947
1824
  }
1825
+ async recalculateQuestionCounts() {
1826
+ let nextToken = null;
1827
+ const counts = {};
1828
+ do {
1829
+ const res = await this.network.query(
1830
+ `query ListQuestionsForRecalc($nextToken: String) {
1831
+ listQuestionObjects(limit: 1000, nextToken: $nextToken) {
1832
+ items { id subjectId chapterId topicId }
1833
+ nextToken
1834
+ }
1835
+ }`,
1836
+ { nextToken }
1837
+ );
1838
+ const items = _optionalChain([res, 'access', _24 => _24.data, 'optionalAccess', _25 => _25.listQuestionObjects, 'optionalAccess', _26 => _26.items]) || [];
1839
+ for (const q of items) {
1840
+ if (q.subjectId) counts[q.subjectId] = (counts[q.subjectId] || 0) + 1;
1841
+ }
1842
+ nextToken = _nullishCoalesce(_optionalChain([res, 'access', _27 => _27.data, 'optionalAccess', _28 => _28.listQuestionObjects, 'optionalAccess', _29 => _29.nextToken]), () => ( null));
1843
+ } while (nextToken);
1844
+ const [hscSubjects, sscSubjects] = await Promise.all([
1845
+ this.getSubjects("HSC").catch(() => []),
1846
+ this.getSubjects("SSC").catch(() => [])
1847
+ ]);
1848
+ const allSubjects = [...hscSubjects, ...sscSubjects];
1849
+ for (const s of allSubjects) {
1850
+ await this.network.mutate(
1851
+ `mutation UpdateSubjectCount($input: UpdateSubjectInput!) {
1852
+ updateSubject(input: $input) { id }
1853
+ }`,
1854
+ { input: { id: s.id, questionCount: counts[s.id] || 0 } }
1855
+ );
1856
+ }
1857
+ }
1858
+ async recalculateCreatorStats() {
1859
+ let nextToken = null;
1860
+ const userQuestionCounts = {};
1861
+ do {
1862
+ const res = await this.network.query(
1863
+ `query ListQuestionsForCreatorStats($nextToken: String) {
1864
+ listQuestionObjects(limit: 1000, nextToken: $nextToken) {
1865
+ items { id owner }
1866
+ nextToken
1867
+ }
1868
+ }`,
1869
+ { nextToken }
1870
+ );
1871
+ const items = _optionalChain([res, 'access', _30 => _30.data, 'optionalAccess', _31 => _31.listQuestionObjects, 'optionalAccess', _32 => _32.items]) || [];
1872
+ for (const q of items) {
1873
+ if (q.owner) userQuestionCounts[q.owner] = (userQuestionCounts[q.owner] || 0) + 1;
1874
+ }
1875
+ nextToken = _nullishCoalesce(_optionalChain([res, 'access', _33 => _33.data, 'optionalAccess', _34 => _34.listQuestionObjects, 'optionalAccess', _35 => _35.nextToken]), () => ( null));
1876
+ } while (nextToken);
1877
+ for (const [userId, count] of Object.entries(userQuestionCounts)) {
1878
+ await this.network.mutate(
1879
+ `mutation UpdateCreatorStats($input: UpdateUserInput!) {
1880
+ updateUser(input: $input) { id }
1881
+ }`,
1882
+ { input: { id: userId, questionCount: count } }
1883
+ );
1884
+ }
1885
+ }
948
1886
  };
949
1887
 
950
1888
  // src/services/media.service.ts
@@ -954,13 +1892,34 @@ var MediaService = class {
954
1892
  this.storage = storage;
955
1893
  }
956
1894
  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;
1895
+ const payloadLength = image ? image.length : 0;
1896
+ console.log(`[MediaService.uploadImage] Initiating upload mutation: questionId="${questionId}", payloadLength=${payloadLength} chars`);
1897
+ try {
1898
+ const result = await this.network.mutate(
1899
+ `mutation UploadImage($questionId: ID!, $image: String!) {
1900
+ uploadImage(questionId: $questionId, image: $image) { status imageUrl }
1901
+ }`,
1902
+ { questionId, image }
1903
+ );
1904
+ if (result.errors && result.errors.length > 0) {
1905
+ console.error(
1906
+ `[MediaService.uploadImage] GraphQL errors returned for questionId "${questionId}":`,
1907
+ JSON.stringify(result.errors, null, 2)
1908
+ );
1909
+ }
1910
+ const response = _optionalChain([result, 'access', _36 => _36.data, 'optionalAccess', _37 => _37.uploadImage]);
1911
+ if (!response) {
1912
+ console.error(`[MediaService.uploadImage] No uploadImage object returned in GraphQL response for questionId "${questionId}". Full result:`, result);
1913
+ } else if (response.status === "Error" || response.status === "error" || !response.imageUrl) {
1914
+ console.error(`[MediaService.uploadImage] Backend returned error status or missing imageUrl for questionId "${questionId}":`, response);
1915
+ } else {
1916
+ console.log(`[MediaService.uploadImage] Upload mutation success for questionId "${questionId}": imageUrl="${response.imageUrl}"`);
1917
+ }
1918
+ return response;
1919
+ } catch (err) {
1920
+ console.error(`[MediaService.uploadImage] Network/GraphQL mutation failed for questionId "${questionId}":`, err);
1921
+ throw err;
1922
+ }
964
1923
  }
965
1924
  async deleteImage(imageUrl) {
966
1925
  const result = await this.network.mutate(
@@ -1023,123 +1982,423 @@ var ProgressService = class {
1023
1982
  this.network = network;
1024
1983
  }
1025
1984
  async track(input) {
1985
+ const variables = {
1986
+ questionId: input.questionId,
1987
+ isCorrect: input.isCorrect,
1988
+ subjectId: _nullishCoalesce(input.subjectId, () => ( null)),
1989
+ chapterId: _nullishCoalesce(input.chapterId, () => ( null)),
1990
+ topicId: _nullishCoalesce(input.topicId, () => ( null))
1991
+ };
1026
1992
  const result = await this.network.mutate(
1027
1993
  `mutation TrackProgress($questionId: ID!, $isCorrect: Boolean!, $subjectId: ID, $chapterId: ID, $topicId: ID) {
1028
1994
  trackPracticeProgress(questionId: $questionId, isCorrect: $isCorrect, subjectId: $subjectId, chapterId: $chapterId, topicId: $topicId) { status }
1029
1995
  }`,
1030
- input
1996
+ variables
1031
1997
  );
1032
- return result.data.trackPracticeProgress;
1998
+ return _optionalChain([result, 'access', _38 => _38.data, 'optionalAccess', _39 => _39.trackPracticeProgress]) || { status: "success" };
1033
1999
  }
1034
2000
  async getSubjectProgress(userId) {
2001
+ try {
2002
+ const result = await this.network.query(
2003
+ `query ListSubjectProgress($userId: String!) {
2004
+ listUserSubjectProgressByUserIdAndSubjectId(userId: $userId) {
2005
+ items { id userId subjectId practicedCount mistakeCount correctedCount accuracyScore }
2006
+ }
2007
+ }`,
2008
+ { userId }
2009
+ );
2010
+ return _optionalChain([result, 'access', _40 => _40.data, 'optionalAccess', _41 => _41.listUserSubjectProgressByUserIdAndSubjectId, 'optionalAccess', _42 => _42.items]) || _optionalChain([result, 'access', _43 => _43.data, 'optionalAccess', _44 => _44.listUserSubjectProgressesByUserIdAndSubjectId, 'optionalAccess', _45 => _45.items]) || [];
2011
+ } catch (e) {
2012
+ console.error("Error fetching subject progress:", e);
2013
+ return [];
2014
+ }
2015
+ }
2016
+ async getChapterProgress(userId) {
2017
+ try {
2018
+ const result = await this.network.query(
2019
+ `query ListChapterProgress($userId: String!) {
2020
+ listUserChapterProgressByUserIdAndChapterId(userId: $userId) {
2021
+ items { id userId chapterId practicedCount mistakeCount correctedCount accuracyScore }
2022
+ }
2023
+ }`,
2024
+ { userId }
2025
+ );
2026
+ return _optionalChain([result, 'access', _46 => _46.data, 'optionalAccess', _47 => _47.listUserChapterProgressByUserIdAndChapterId, 'optionalAccess', _48 => _48.items]) || _optionalChain([result, 'access', _49 => _49.data, 'optionalAccess', _50 => _50.listUserChapterProgressesByUserIdAndChapterId, 'optionalAccess', _51 => _51.items]) || [];
2027
+ } catch (e) {
2028
+ console.error("Error fetching chapter progress:", e);
2029
+ return [];
2030
+ }
2031
+ }
2032
+ async getTopicProgress(userId) {
2033
+ try {
2034
+ const result = await this.network.query(
2035
+ `query ListTopicProgress($userId: String!) {
2036
+ listUserTopicProgressByUserIdAndTopicId(userId: $userId) {
2037
+ items { id userId topicId practicedCount mistakeCount correctedCount accuracyScore }
2038
+ }
2039
+ }`,
2040
+ { userId }
2041
+ );
2042
+ return _optionalChain([result, 'access', _52 => _52.data, 'optionalAccess', _53 => _53.listUserTopicProgressByUserIdAndTopicId, 'optionalAccess', _54 => _54.items]) || _optionalChain([result, 'access', _55 => _55.data, 'optionalAccess', _56 => _56.listUserTopicProgressesByUserIdAndTopicId, 'optionalAccess', _57 => _57.items]) || [];
2043
+ } catch (e) {
2044
+ console.error("Error fetching topic progress:", e);
2045
+ return [];
2046
+ }
2047
+ }
2048
+ async getTopicMetrics(userId) {
2049
+ try {
2050
+ const result = await this.network.query(
2051
+ `query ListTopicMetrics($filter: ModelUserTopicMetricFilterInput) {
2052
+ listUserTopicMetrics(filter: $filter) {
2053
+ items { id userId topicId totalAttempted totalCorrect accuracy }
2054
+ }
2055
+ }`,
2056
+ { filter: { userId: { eq: userId } } }
2057
+ );
2058
+ return _optionalChain([result, 'access', _58 => _58.data, 'optionalAccess', _59 => _59.listUserTopicMetrics, 'optionalAccess', _60 => _60.items]) || [];
2059
+ } catch (e) {
2060
+ console.error("Error fetching topic metrics:", e);
2061
+ return [];
2062
+ }
2063
+ }
2064
+ async getMistakenQuestions(userId, status = "MISTAKE") {
2065
+ try {
2066
+ const result = await this.network.query(
2067
+ `query ListMistakes($userId: String!, $filter: ModelQuestionTrackingFilterInput) {
2068
+ listQuestionTrackingByUserIdAndQuestionId(userId: $userId, filter: $filter) {
2069
+ items { id userId questionId subjectId chapterId topicId status attempts }
2070
+ }
2071
+ }`,
2072
+ { userId, filter: { status: { eq: status } } }
2073
+ );
2074
+ return _optionalChain([result, 'access', _61 => _61.data, 'optionalAccess', _62 => _62.listQuestionTrackingByUserIdAndQuestionId, 'optionalAccess', _63 => _63.items]) || _optionalChain([result, 'access', _64 => _64.data, 'optionalAccess', _65 => _65.listQuestionTrackingsByUserIdAndQuestionId, 'optionalAccess', _66 => _66.items]) || _optionalChain([result, 'access', _67 => _67.data, 'optionalAccess', _68 => _68.listQuestionTrackings, 'optionalAccess', _69 => _69.items]) || [];
2075
+ } catch (e) {
2076
+ console.error("Error fetching mistaken questions:", e);
2077
+ return [];
2078
+ }
2079
+ }
2080
+ async getCorrectedQuestions(userId) {
2081
+ return this.getMistakenQuestions(userId, "CORRECTED");
2082
+ }
2083
+ };
2084
+
2085
+ // src/services/ai.service.ts
2086
+ var AIService = class {
2087
+ constructor(network) {
2088
+ this.network = network;
2089
+ }
2090
+ async evaluateWrittenAnswer(input, options) {
2091
+ if (_optionalChain([options, 'optionalAccess', _70 => _70.functionUrl])) {
2092
+ try {
2093
+ const headers = { "Content-Type": "application/json" };
2094
+ if (options.authToken) {
2095
+ headers["Authorization"] = `Bearer ${options.authToken}`;
2096
+ }
2097
+ const response = await fetch(options.functionUrl, {
2098
+ method: "POST",
2099
+ headers,
2100
+ body: JSON.stringify(input)
2101
+ });
2102
+ if (response.ok) {
2103
+ const data = await response.json();
2104
+ return data;
2105
+ }
2106
+ console.warn(`[AIService] Function URL failed with status ${response.status}, falling back to GraphQL...`);
2107
+ } catch (err) {
2108
+ console.warn("[AIService] Function URL fetch error, falling back to GraphQL...", err);
2109
+ }
2110
+ }
1035
2111
  const result = await this.network.query(
1036
- `query ListSubjectProgress($userId: String!) {
1037
- listUserSubjectProgressByUserIdAndSubjectId(userId: $userId) {
1038
- items { id userId subjectId practicedCount mistakeCount correctedCount accuracyScore }
2112
+ `query EvaluateWrittenAnswer($question: String!, $userAnswer: String!, $correctAnswer: String!, $markingInstructions: String, $topicId: String, $topicName: String, $questionImageUrl: String) {
2113
+ evaluateWrittenAnswer(question: $question, userAnswer: $userAnswer, correctAnswer: $correctAnswer, markingInstructions: $markingInstructions, topicId: $topicId, topicName: $topicName, questionImageUrl: $questionImageUrl) {
2114
+ score maxScore feedback isCorrect markingExplanation improvementFeedback subquestions markLocation
1039
2115
  }
1040
2116
  }`,
1041
- { userId }
2117
+ input
1042
2118
  );
1043
- return result.data.listUserSubjectProgressByUserIdAndSubjectId.items;
2119
+ return result.data.evaluateWrittenAnswer;
1044
2120
  }
1045
- async getChapterProgress(userId) {
2121
+ };
2122
+
2123
+ // src/services/news.service.ts
2124
+ var NewsService = class {
2125
+ constructor(network) {
2126
+ this.network = network;
2127
+ }
2128
+ async create(title, content, image, category, excerpt) {
2129
+ const result = await this.network.mutate(
2130
+ `mutation CreateNews($input: CreateNewsArticleInput!) {
2131
+ createNewsArticle(input: $input) { id title content image category excerpt createdAt updatedAt }
2132
+ }`,
2133
+ { input: { title, content, image, category, excerpt } }
2134
+ );
2135
+ return result.data.createNewsArticle;
2136
+ }
2137
+ async list(nextToken) {
1046
2138
  const result = await this.network.query(
1047
- `query ListChapterProgress($userId: String!) {
1048
- listUserChapterProgressByUserIdAndChapterId(userId: $userId) {
1049
- items { id userId chapterId practicedCount mistakeCount correctedCount accuracyScore }
2139
+ `query ListNews($nextToken: String) {
2140
+ listNewsArticles(nextToken: $nextToken) {
2141
+ items { id title content image category excerpt createdAt updatedAt }
2142
+ nextToken
1050
2143
  }
1051
2144
  }`,
1052
- { userId }
2145
+ { nextToken }
1053
2146
  );
1054
- return result.data.listUserChapterProgressByUserIdAndChapterId.items;
2147
+ return result.data.listNewsArticles;
1055
2148
  }
1056
- async getTopicProgress(userId) {
2149
+ async get(id) {
1057
2150
  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
- }
2151
+ `query GetNews($id: ID!) {
2152
+ getNewsArticle(id: $id) { id title content image category excerpt createdAt updatedAt }
1062
2153
  }`,
1063
- { userId }
2154
+ { id }
1064
2155
  );
1065
- return result.data.listUserTopicProgressByUserIdAndTopicId.items;
2156
+ return result.data.getNewsArticle;
1066
2157
  }
1067
- async getTopicMetrics(userId) {
2158
+ };
2159
+
2160
+ // src/services/notification.service.ts
2161
+ var NotificationService = class {
2162
+ constructor(network) {
2163
+ this.network = network;
2164
+ }
2165
+ /**
2166
+ * List broadcast announcements (ordered by category and createdAt).
2167
+ */
2168
+ async listAnnouncements(limit = 20, nextToken) {
1068
2169
  const result = await this.network.query(
1069
- `query ListTopicMetrics($filter: ModelUserTopicMetricFilterInput) {
1070
- listUserTopicMetrics(filter: $filter) {
1071
- items { id userId topicId totalAttempted totalCorrect accuracy }
2170
+ `query ListAnnouncements($limit: Int, $nextToken: String) {
2171
+ listAnnouncements(limit: $limit, nextToken: $nextToken) {
2172
+ items {
2173
+ id title message senderType senderName senderAvatar category actionUrl priority createdAt updatedAt
2174
+ }
2175
+ nextToken
1072
2176
  }
1073
2177
  }`,
1074
- { filter: { userId: { eq: userId } } }
2178
+ { limit, nextToken }
1075
2179
  );
1076
- return result.data.listUserTopicMetrics.items;
2180
+ return _nullishCoalesce(result.data.listAnnouncements, () => ( { items: [], nextToken: null }));
1077
2181
  }
1078
- async getMistakenQuestions(userId) {
2182
+ /**
2183
+ * List announcements by category.
2184
+ */
2185
+ async listAnnouncementsByCategory(category = "ANNOUNCEMENT", limit = 20, nextToken) {
1079
2186
  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 }
2187
+ `query ListAnnouncementsByCategory($category: String!, $limit: Int, $nextToken: String, $sortDirection: ModelSortDirection) {
2188
+ listAnnouncementByCategoryAndCreatedAt(category: $category, limit: $limit, nextToken: $nextToken, sortDirection: $sortDirection) {
2189
+ items {
2190
+ id title message senderType senderName senderAvatar category actionUrl priority createdAt updatedAt
2191
+ }
2192
+ nextToken
1083
2193
  }
1084
2194
  }`,
1085
- { userId, filter: { status: { eq: "MISTAKE" } } }
2195
+ { category, limit, nextToken, sortDirection: "DESC" }
1086
2196
  );
1087
- return result.data.listQuestionTrackingByUserIdAndSubjectId.items;
2197
+ return _nullishCoalesce(result.data.listAnnouncementByCategoryAndCreatedAt, () => ( {
2198
+ items: [],
2199
+ nextToken: null
2200
+ }));
1088
2201
  }
1089
- };
1090
-
1091
- // src/services/ai.service.ts
1092
- var AIService = class {
1093
- constructor(network) {
1094
- this.network = network;
2202
+ /**
2203
+ * Create an announcement (Admins only).
2204
+ */
2205
+ async createAnnouncement(input) {
2206
+ const result = await this.network.mutate(
2207
+ `mutation CreateAnnouncement($input: CreateAnnouncementInput!) {
2208
+ createAnnouncement(input: $input) {
2209
+ id title message senderType senderName senderAvatar category actionUrl priority createdAt updatedAt
2210
+ }
2211
+ }`,
2212
+ {
2213
+ input: {
2214
+ ...input,
2215
+ senderType: input.senderType || "ORJOK",
2216
+ category: input.category || "ANNOUNCEMENT",
2217
+ priority: input.priority || "NORMAL"
2218
+ }
2219
+ }
2220
+ );
2221
+ return result.data.createAnnouncement;
1095
2222
  }
1096
- async evaluateWrittenAnswer(input) {
2223
+ /**
2224
+ * Delete an announcement (Admins only).
2225
+ */
2226
+ async deleteAnnouncement(id) {
2227
+ const result = await this.network.mutate(
2228
+ `mutation DeleteAnnouncement($input: DeleteAnnouncementInput!) {
2229
+ deleteAnnouncement(input: $input) { id }
2230
+ }`,
2231
+ { input: { id } }
2232
+ );
2233
+ return Boolean(_optionalChain([result, 'access', _71 => _71.data, 'access', _72 => _72.deleteAnnouncement, 'optionalAccess', _73 => _73.id]));
2234
+ }
2235
+ /**
2236
+ * List user notifications for a specific user ID.
2237
+ */
2238
+ async listUserNotifications(userId, limit = 20, nextToken) {
1097
2239
  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
2240
+ `query ListUserNotifications($userId: ID!, $limit: Int, $nextToken: String, $sortDirection: ModelSortDirection) {
2241
+ listUserNotificationByUserIdAndCreatedAt(userId: $userId, limit: $limit, nextToken: $nextToken, sortDirection: $sortDirection) {
2242
+ items {
2243
+ id userId title message type senderType senderName senderAvatar actionUrl isRead readAt metadata createdAt updatedAt
2244
+ }
2245
+ nextToken
2246
+ }
2247
+ }`,
2248
+ { userId, limit, nextToken, sortDirection: "DESC" }
2249
+ );
2250
+ return _nullishCoalesce(result.data.listUserNotificationByUserIdAndCreatedAt, () => ( {
2251
+ items: [],
2252
+ nextToken: null
2253
+ }));
2254
+ }
2255
+ /**
2256
+ * Create a user notification (Level up, direct message, system alert).
2257
+ */
2258
+ async createUserNotification(input) {
2259
+ const result = await this.network.mutate(
2260
+ `mutation CreateUserNotification($input: CreateUserNotificationInput!) {
2261
+ createUserNotification(input: $input) {
2262
+ id userId title message type senderType senderName senderAvatar actionUrl isRead readAt metadata createdAt updatedAt
2263
+ }
2264
+ }`,
2265
+ {
2266
+ input: {
2267
+ ...input,
2268
+ isRead: false
2269
+ }
2270
+ }
2271
+ );
2272
+ return result.data.createUserNotification;
2273
+ }
2274
+ /**
2275
+ * Mark a personal notification as read.
2276
+ */
2277
+ async markNotificationAsRead(id) {
2278
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2279
+ const result = await this.network.mutate(
2280
+ `mutation UpdateUserNotification($input: UpdateUserNotificationInput!) {
2281
+ updateUserNotification(input: $input) {
2282
+ id isRead readAt
1101
2283
  }
1102
2284
  }`,
1103
- input
2285
+ {
2286
+ input: {
2287
+ id,
2288
+ isRead: true,
2289
+ readAt: now
2290
+ }
2291
+ }
2292
+ );
2293
+ return result.data.updateUserNotification;
2294
+ }
2295
+ /**
2296
+ * Delete a personal user notification.
2297
+ */
2298
+ async deleteUserNotification(id) {
2299
+ const result = await this.network.mutate(
2300
+ `mutation DeleteUserNotification($input: DeleteUserNotificationInput!) {
2301
+ deleteUserNotification(input: $input) { id }
2302
+ }`,
2303
+ { input: { id } }
2304
+ );
2305
+ return Boolean(_optionalChain([result, 'access', _74 => _74.data, 'access', _75 => _75.deleteUserNotification, 'optionalAccess', _76 => _76.id]));
2306
+ }
2307
+ /**
2308
+ * Update the user's lastReadAnnouncementAt timestamp in DynamoDB.
2309
+ */
2310
+ async updateLastReadAnnouncement(userId, timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
2311
+ const result = await this.network.mutate(
2312
+ `mutation UpdateUserLastRead($input: UpdateUserInput!) {
2313
+ updateUser(input: $input) { id lastReadAnnouncementAt }
2314
+ }`,
2315
+ { input: { id: userId, lastReadAnnouncementAt: timestamp } }
1104
2316
  );
1105
- return result.data.evaluateWrittenAnswer;
2317
+ return Boolean(_optionalChain([result, 'access', _77 => _77.data, 'access', _78 => _78.updateUser, 'optionalAccess', _79 => _79.id]));
1106
2318
  }
1107
2319
  };
1108
2320
 
1109
- // src/services/news.service.ts
1110
- var NewsService = class {
2321
+ // src/services/subscription.service.ts
2322
+ var SubscriptionService = class {
1111
2323
  constructor(network) {
1112
2324
  this.network = network;
1113
2325
  }
1114
- async create(title, content, image, category, excerpt) {
2326
+ async createRequest(input) {
1115
2327
  const result = await this.network.mutate(
1116
- `mutation CreateNews($input: CreateNewsArticleInput!) {
1117
- createNewsArticle(input: $input) { id title content image category excerpt createdAt updatedAt }
2328
+ `mutation CreateSubscriptionRequest($input: CreateSubscriptionRequestInput!) {
2329
+ createSubscriptionRequest(input: $input) {
2330
+ id userId userFullName userEmail itemType itemId itemTitle amount billingCycle paymentMethod senderPhone contactPhone trxId status aiEvals aiQuestions adminNotes processedAt processedBy createdAt updatedAt
2331
+ }
1118
2332
  }`,
1119
- { input: { title, content, image, category, excerpt } }
2333
+ { input }
1120
2334
  );
1121
- return result.data.createNewsArticle;
2335
+ return result.data.createSubscriptionRequest;
1122
2336
  }
1123
- async list(nextToken) {
2337
+ async getRequest(id) {
1124
2338
  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
2339
+ `query GetSubscriptionRequest($id: ID!) {
2340
+ getSubscriptionRequest(id: $id) {
2341
+ id userId userFullName userEmail itemType itemId itemTitle amount billingCycle paymentMethod senderPhone contactPhone trxId status aiEvals aiQuestions adminNotes processedAt processedBy createdAt updatedAt
1129
2342
  }
1130
2343
  }`,
1131
- { nextToken }
2344
+ { id }
1132
2345
  );
1133
- return result.data.listNewsArticles;
2346
+ return result.data.getSubscriptionRequest;
1134
2347
  }
1135
- async get(id) {
2348
+ async listRequests(input = {}) {
2349
+ const limit = _nullishCoalesce(input.limit, () => ( 20));
2350
+ const sortDirection = _nullishCoalesce(input.sortDirection, () => ( "DESC"));
2351
+ if (input.status) {
2352
+ const result2 = await this.network.query(
2353
+ `query ListSubscriptionRequestsByStatus($status: String!, $sortDirection: ModelSortDirection, $limit: Int, $nextToken: String) {
2354
+ listSubscriptionRequestByStatusAndCreatedAt(status: $status, sortDirection: $sortDirection, limit: $limit, nextToken: $nextToken) {
2355
+ items {
2356
+ id userId userFullName userEmail itemType itemId itemTitle amount billingCycle paymentMethod senderPhone contactPhone trxId status aiEvals aiQuestions adminNotes processedAt processedBy createdAt updatedAt
2357
+ }
2358
+ nextToken
2359
+ }
2360
+ }`,
2361
+ { status: input.status, sortDirection, limit, nextToken: input.nextToken }
2362
+ );
2363
+ return result2.data.listSubscriptionRequestByStatusAndCreatedAt;
2364
+ }
2365
+ if (input.userId) {
2366
+ const result2 = await this.network.query(
2367
+ `query ListSubscriptionRequestsByUser($userId: String!, $sortDirection: ModelSortDirection, $limit: Int, $nextToken: String) {
2368
+ listSubscriptionRequestByUserIdAndCreatedAt(userId: $userId, sortDirection: $sortDirection, limit: $limit, nextToken: $nextToken) {
2369
+ items {
2370
+ id userId userFullName userEmail itemType itemId itemTitle amount billingCycle paymentMethod senderPhone contactPhone trxId status aiEvals aiQuestions adminNotes processedAt processedBy createdAt updatedAt
2371
+ }
2372
+ nextToken
2373
+ }
2374
+ }`,
2375
+ { userId: input.userId, sortDirection, limit, nextToken: input.nextToken }
2376
+ );
2377
+ return result2.data.listSubscriptionRequestByUserIdAndCreatedAt;
2378
+ }
1136
2379
  const result = await this.network.query(
1137
- `query GetNews($id: ID!) {
1138
- getNewsArticle(id: $id) { id title content image category excerpt createdAt updatedAt }
2380
+ `query ListAllSubscriptionRequests($limit: Int, $nextToken: String, $filter: ModelSubscriptionRequestFilterInput) {
2381
+ listSubscriptionRequests(limit: $limit, nextToken: $nextToken, filter: $filter) {
2382
+ items {
2383
+ id userId userFullName userEmail itemType itemId itemTitle amount billingCycle paymentMethod senderPhone contactPhone trxId status aiEvals aiQuestions adminNotes processedAt processedBy createdAt updatedAt
2384
+ }
2385
+ nextToken
2386
+ }
1139
2387
  }`,
1140
- { id }
2388
+ { limit, nextToken: input.nextToken, filter: input.filter }
1141
2389
  );
1142
- return result.data.getNewsArticle;
2390
+ return result.data.listSubscriptionRequests;
2391
+ }
2392
+ async updateRequest(input) {
2393
+ const result = await this.network.mutate(
2394
+ `mutation UpdateSubscriptionRequest($input: UpdateSubscriptionRequestInput!) {
2395
+ updateSubscriptionRequest(input: $input) {
2396
+ id userId userFullName userEmail itemType itemId itemTitle amount billingCycle paymentMethod senderPhone contactPhone trxId status aiEvals aiQuestions adminNotes processedAt processedBy createdAt updatedAt
2397
+ }
2398
+ }`,
2399
+ { input }
2400
+ );
2401
+ return result.data.updateSubscriptionRequest;
1143
2402
  }
1144
2403
  };
1145
2404
 
@@ -1147,6 +2406,8 @@ var NewsService = class {
1147
2406
  var OrjokClient = class {
1148
2407
  constructor(config) {
1149
2408
  this.auth = config.authProvider;
2409
+ this.network = config.networkProvider;
2410
+ this.storage = config.storageProvider;
1150
2411
  this.users = new UserService(config.networkProvider);
1151
2412
  this.questions = new QuestionService(config.networkProvider);
1152
2413
  this.packs = new PackService(config.networkProvider);
@@ -1157,12 +2418,218 @@ var OrjokClient = class {
1157
2418
  this.progress = new ProgressService(config.networkProvider);
1158
2419
  this.ai = new AIService(config.networkProvider);
1159
2420
  this.news = new NewsService(config.networkProvider);
2421
+ this.notifications = new NotificationService(config.networkProvider);
2422
+ this.subscriptions = new SubscriptionService(config.networkProvider);
1160
2423
  }
1161
2424
  };
1162
2425
  function createOrjokClient(config) {
1163
2426
  return new OrjokClient(config);
1164
2427
  }
1165
2428
 
2429
+ // src/logic/avatar.ts
2430
+ var HAIR_COLORS = [
2431
+ { name: "Original", filter: "none", hex: "#e2e8f0", brightness: 1 },
2432
+ { name: "White", filter: "grayscale(100%) brightness(500%)", hex: "#ffffff", brightness: 2 },
2433
+ { name: "Black", filter: "grayscale(100%) brightness(40%)", hex: "#1a202c", brightness: 0.15 },
2434
+ { name: "Brown", filter: "sepia(100%) saturate(300%) hue-rotate(330deg) brightness(80%)", hex: "#7b3f00", brightness: 0.35 },
2435
+ { name: "Blonde", filter: "sepia(100%) saturate(400%) hue-rotate(20deg) brightness(130%)", hex: "#e6c27a", brightness: 0.8 },
2436
+ { name: "Red", filter: "sepia(100%) saturate(500%) hue-rotate(320deg) brightness(90%)", hex: "#9b2c2c", brightness: 0.4 },
2437
+ { name: "Blue", filter: "sepia(100%) saturate(500%) hue-rotate(180deg) brightness(90%)", hex: "#2b6cb0", brightness: 0.4 },
2438
+ { name: "Green", filter: "sepia(100%) saturate(400%) hue-rotate(80deg) brightness(90%)", hex: "#2f855a", brightness: 0.45 }
2439
+ ];
2440
+ var CLOTHES_COLORS = [
2441
+ { name: "Original", filter: "none", hex: "#e2e8f0", brightness: 1 },
2442
+ { name: "White", filter: "grayscale(100%) brightness(500%)", hex: "#ffffff", brightness: 2 },
2443
+ { name: "Black", filter: "grayscale(100%) brightness(30%)", hex: "#1a202c", brightness: 0.12 },
2444
+ { name: "Red", filter: "sepia(100%) saturate(500%) hue-rotate(320deg)", hex: "#c53030", brightness: 0.45 },
2445
+ { name: "Orange", filter: "sepia(100%) saturate(500%) hue-rotate(350deg)", hex: "#dd6b20", brightness: 0.55 },
2446
+ { name: "Yellow", filter: "sepia(100%) saturate(500%) hue-rotate(20deg) brightness(120%)", hex: "#d69e2e", brightness: 0.8 },
2447
+ { name: "Green", filter: "sepia(100%) saturate(500%) hue-rotate(80deg)", hex: "#38a169", brightness: 0.55 },
2448
+ { name: "Blue", filter: "sepia(100%) saturate(500%) hue-rotate(180deg)", hex: "#3182ce", brightness: 0.45 },
2449
+ { name: "Purple", filter: "sepia(100%) saturate(500%) hue-rotate(240deg)", hex: "#805ad5", brightness: 0.45 },
2450
+ { name: "Pink", filter: "sepia(100%) saturate(400%) hue-rotate(290deg)", hex: "#d53f8c", brightness: 0.55 }
2451
+ ];
2452
+ var COLOR_HUES = {
2453
+ Original: 0,
2454
+ Red: 0,
2455
+ Orange: 24,
2456
+ Yellow: 45,
2457
+ Green: 140,
2458
+ Blue: 210,
2459
+ Purple: 270,
2460
+ Pink: 320,
2461
+ Brown: 25,
2462
+ Blonde: 45
2463
+ };
2464
+ var RENDER_ORDER = [
2465
+ "Background",
2466
+ "Hair Back",
2467
+ "Clothes Back",
2468
+ "Skin Color Body",
2469
+ "Clothes Front",
2470
+ "Skin Color Head",
2471
+ "Accessories",
2472
+ "Facial Expression",
2473
+ "Beard",
2474
+ "Hair Front",
2475
+ "Eyewear",
2476
+ "Headwears"
2477
+ ];
2478
+ var INITIAL_AVATAR_DATA = {
2479
+ Background: [],
2480
+ Hair: [],
2481
+ "Skin Color": [],
2482
+ Clothes: [],
2483
+ Accessories: [],
2484
+ Headwears: [],
2485
+ Eyewear: [],
2486
+ "Facial Expression": [],
2487
+ Beard: []
2488
+ };
2489
+ function getActualOptionFileName(category, baseName, config, selections) {
2490
+ if (baseName === "None") return null;
2491
+ const items = config[category] || [];
2492
+ const matched = items.filter((item) => item.base_name === baseName);
2493
+ if (matched.length === 0) return null;
2494
+ for (const item of matched) {
2495
+ if (item.conditions && item.conditions.length > 0) {
2496
+ const allMet = item.conditions.every((cond) => {
2497
+ return Object.values(selections).some(
2498
+ (sel) => sel && (sel.includes(cond) || cond.includes(sel))
2499
+ );
2500
+ });
2501
+ if (allMet) return item;
2502
+ }
2503
+ }
2504
+ return matched.find((item) => !item.conditions || item.conditions.length === 0) || matched[0] || null;
2505
+ }
2506
+ function parseAvatarConfig(configStr) {
2507
+ if (!configStr) {
2508
+ return { selections: {}, colors: {} };
2509
+ }
2510
+ try {
2511
+ const parsed = JSON.parse(configStr);
2512
+ return {
2513
+ selections: parsed.selections || {},
2514
+ colors: parsed.colors || {},
2515
+ gender: parsed.gender
2516
+ };
2517
+ } catch (e5) {
2518
+ return { selections: {}, colors: {} };
2519
+ }
2520
+ }
2521
+ function parseAvatarConfigWithDefaults(configStr, seed) {
2522
+ const parsed = parseAvatarConfig(configStr);
2523
+ const hasSelections = Object.keys(parsed.selections).length > 0;
2524
+ if (!hasSelections) {
2525
+ return generateRandomAvatarState({ seed });
2526
+ }
2527
+ return {
2528
+ selections: { ...parsed.selections },
2529
+ colors: { ...parsed.colors },
2530
+ gender: parsed.gender
2531
+ };
2532
+ }
2533
+ function serializeAvatarConfig(state) {
2534
+ return JSON.stringify(state);
2535
+ }
2536
+ function createSeededRandom(seedStr) {
2537
+ let h = 1779033703 ^ seedStr.length;
2538
+ for (let i = 0; i < seedStr.length; i++) {
2539
+ h = Math.imul(h ^ seedStr.charCodeAt(i), 3432918353);
2540
+ h = h << 13 | h >>> 19;
2541
+ }
2542
+ return () => {
2543
+ h = Math.imul(h ^ h >>> 16, 2246822507);
2544
+ h = Math.imul(h ^ h >>> 13, 3266489909);
2545
+ return ((h ^= h >>> 16) >>> 0) / 4294967296;
2546
+ };
2547
+ }
2548
+ function pickRandom(arr, fallback, rng = Math.random) {
2549
+ if (arr.length === 0) return fallback;
2550
+ const item = arr[Math.floor(rng() * arr.length)];
2551
+ return item !== void 0 ? item : fallback;
2552
+ }
2553
+ function generateRandomAvatarState(options) {
2554
+ const rng = _optionalChain([options, 'optionalAccess', _80 => _80.seed]) ? createSeededRandom(options.seed) : Math.random;
2555
+ const gender = _optionalChain([options, 'optionalAccess', _81 => _81.gender]) || (rng() > 0.5 ? "Male" : "Female");
2556
+ const backgrounds = ["Solid", "3 Stripes"];
2557
+ const skinColors = [
2558
+ "Light Peach",
2559
+ "Warm Peach",
2560
+ "Golden Tan",
2561
+ "Sand Beige",
2562
+ "Warm Brown",
2563
+ "Deep Brown"
2564
+ ];
2565
+ const maleHairs = ["Clean", "Mid", "Curled", "Bald"];
2566
+ const femaleHairs = ["Bun", "Long", "Mid", "Short"];
2567
+ const clothesList = ["Tshirt", "Hoodie", "Turtleneck"];
2568
+ const expressions = ["Normal", "Happy", "Joyful", "Playful", "Surprised"];
2569
+ const eywears = ["None", "None", "None", "None", "Normal Glasses"];
2570
+ const headwears = ["None", "None", "None", "None", "Beanie", "HeadPhones"];
2571
+ const hairList = gender === "Male" ? maleHairs : femaleHairs;
2572
+ const bg = pickRandom(backgrounds, "Solid", rng);
2573
+ const skin = pickRandom(skinColors, "Light Peach", rng);
2574
+ const hair = pickRandom(hairList, gender === "Male" ? "Clean" : "Short", rng);
2575
+ const clothes = pickRandom(clothesList, "Tshirt", rng);
2576
+ const expression = pickRandom(expressions, "Normal", rng);
2577
+ const eyewear = pickRandom(eywears, "None", rng);
2578
+ const headwear = pickRandom(headwears, "None", rng);
2579
+ const hairColorPool = [
2580
+ "Black",
2581
+ "Black",
2582
+ "Brown",
2583
+ "Brown",
2584
+ "Blonde",
2585
+ "White",
2586
+ "Red",
2587
+ "Blue"
2588
+ ];
2589
+ const hairColor = pickRandom(hairColorPool, "Black", rng);
2590
+ const clothesColorPool = [
2591
+ "Blue",
2592
+ "Red",
2593
+ "Green",
2594
+ "Orange",
2595
+ "Purple",
2596
+ "Black",
2597
+ "Yellow",
2598
+ "Pink"
2599
+ ];
2600
+ const clothesColor = pickRandom(clothesColorPool, "Blue", rng);
2601
+ const bgColorPool = ["Blue", "Green", "Purple", "Orange", "Red", "Pink", "Original"];
2602
+ const bgColor = pickRandom(bgColorPool, "Blue", rng);
2603
+ return {
2604
+ selections: {
2605
+ Background: bg,
2606
+ "Hair Back": hair,
2607
+ "Skin Color": skin,
2608
+ "Clothes Back": clothes,
2609
+ "Clothes Front": clothes,
2610
+ Accessories: "None",
2611
+ Headwears: headwear,
2612
+ Eyewear: eyewear,
2613
+ "Facial Expression": expression,
2614
+ Beard: "None",
2615
+ "Hair Front": hair
2616
+ },
2617
+ colors: {
2618
+ "Background Color": bgColor,
2619
+ "Hair Color": hairColor,
2620
+ "Clothes Color": clothesColor,
2621
+ "Accessory Color": "Original",
2622
+ "Headwears Color": "Original",
2623
+ "Eyewear Color": "Original",
2624
+ "Facial Expression Color": "Original"
2625
+ },
2626
+ gender
2627
+ };
2628
+ }
2629
+ function generateRandomAvatarConfig(options) {
2630
+ return JSON.stringify(generateRandomAvatarState(options));
2631
+ }
2632
+
1166
2633
  // src/logic/elo.ts
1167
2634
  var TIERS = [
1168
2635
  { name: "Iron", minRating: 0, maxRating: 799, color: "#6B7280" },
@@ -1263,6 +2730,246 @@ function detectQuestionType(option2, option3, option4) {
1263
2730
  }
1264
2731
  return "MCQ";
1265
2732
  }
2733
+ function isCorrectAnswer(selected, correct) {
2734
+ if (!selected || !correct) return false;
2735
+ const selTrim = String(selected).trim();
2736
+ const corrTrim = String(correct).trim();
2737
+ if (selTrim === corrTrim) return true;
2738
+ const selNum = Number(selTrim);
2739
+ const corrNum = Number(corrTrim);
2740
+ if (!isNaN(selNum) && !isNaN(corrNum) && selNum === corrNum) {
2741
+ return true;
2742
+ }
2743
+ return false;
2744
+ }
2745
+ function calculateDefaultExamMinutes(mode, questions) {
2746
+ const mcqCount = questions.filter((q) => q.type !== "WRITTEN").length;
2747
+ const writtenCount = questions.filter((q) => q.type === "WRITTEN").length;
2748
+ if (mode === "MCQ") return Math.max(1, mcqCount * 1);
2749
+ if (mode === "WRITTEN") return Math.max(1, writtenCount * 10);
2750
+ return Math.max(1, mcqCount * 1 + writtenCount * 10);
2751
+ }
2752
+ function reorderObjectKeys(obj, oldIndex, newIndex, length) {
2753
+ const arr = [];
2754
+ for (let i = 0; i < length; i++) {
2755
+ arr.push(obj[i]);
2756
+ }
2757
+ const [movedItem] = arr.splice(oldIndex, 1);
2758
+ arr.splice(newIndex, 0, movedItem);
2759
+ const newObj = {};
2760
+ arr.forEach((item, idx) => {
2761
+ if (item !== void 0) {
2762
+ newObj[idx] = item;
2763
+ }
2764
+ });
2765
+ return newObj;
2766
+ }
2767
+
2768
+ // src/logic/home.ts
2769
+ function mapCognitoLevelToStandardLevel(cognitoLevel) {
2770
+ if (!cognitoLevel) return "HSC+Admission";
2771
+ const normalized = cognitoLevel.trim().toLowerCase();
2772
+ if (normalized === "class_9" || normalized === "class_10" || normalized === "ssc") {
2773
+ return "SSC";
2774
+ }
2775
+ if (normalized === "class_11" || normalized === "class_12" || normalized === "admission" || normalized === "hsc" || normalized === "hsc+admission") {
2776
+ return "HSC+Admission";
2777
+ }
2778
+ if (normalized === "bcs" || normalized === "job") {
2779
+ return "BCS";
2780
+ }
2781
+ return "HSC+Admission";
2782
+ }
2783
+ function getTimeOfDayGreeting(customHour, lang = "en") {
2784
+ const hour = customHour !== void 0 ? customHour : (/* @__PURE__ */ new Date()).getHours();
2785
+ if (hour >= 5 && hour < 12) {
2786
+ return {
2787
+ greeting: lang === "bn" ? "\u09B6\u09C1\u09AD \u09B8\u0995\u09BE\u09B2" : "Good morning",
2788
+ period: "morning"
2789
+ };
2790
+ }
2791
+ if (hour >= 12 && hour < 17) {
2792
+ return {
2793
+ greeting: lang === "bn" ? "\u09B6\u09C1\u09AD \u09A6\u09C1\u09AA\u09C1\u09B0" : "Good afternoon",
2794
+ period: "afternoon"
2795
+ };
2796
+ }
2797
+ if (hour >= 17 && hour < 21) {
2798
+ return {
2799
+ greeting: lang === "bn" ? "\u09B6\u09C1\u09AD \u09B8\u09A8\u09CD\u09A7\u09CD\u09AF\u09BE" : "Good evening",
2800
+ period: "evening"
2801
+ };
2802
+ }
2803
+ return {
2804
+ greeting: lang === "bn" ? "\u09B6\u09C1\u09AD \u09B0\u09BE\u09A4\u09CD\u09B0\u09BF" : "Good evening",
2805
+ period: "night"
2806
+ };
2807
+ }
2808
+ function getRecommendedSubjectsForLevel(level, lang = "en") {
2809
+ const isBn = lang === "bn";
2810
+ const normalizedLevel = mapCognitoLevelToStandardLevel(level);
2811
+ if (normalizedLevel === "SSC") {
2812
+ return [
2813
+ {
2814
+ id: "ssc-physics",
2815
+ name: isBn ? "\u09AA\u09A6\u09BE\u09B0\u09CD\u09A5\u09AC\u09BF\u099C\u09CD\u099E\u09BE\u09A8" : "Physics",
2816
+ iconKey: "atom",
2817
+ colorClass: "from-blue-500 to-indigo-600"
2818
+ },
2819
+ {
2820
+ id: "ssc-chemistry",
2821
+ name: isBn ? "\u09B0\u09B8\u09BE\u09DF\u09A8" : "Chemistry",
2822
+ iconKey: "flask",
2823
+ colorClass: "from-emerald-500 to-teal-600"
2824
+ },
2825
+ {
2826
+ id: "ssc-higher-math",
2827
+ name: isBn ? "\u0989\u099A\u09CD\u099A\u09A4\u09B0 \u0997\u09A3\u09BF\u09A4" : "Higher Math",
2828
+ iconKey: "math",
2829
+ colorClass: "from-amber-500 to-orange-600"
2830
+ },
2831
+ {
2832
+ id: "ssc-general-math",
2833
+ name: isBn ? "\u09B8\u09BE\u09A7\u09BE\u09B0\u09A3 \u0997\u09A3\u09BF\u09A4" : "General Math",
2834
+ iconKey: "calculator",
2835
+ colorClass: "from-violet-500 to-purple-600"
2836
+ },
2837
+ {
2838
+ id: "ssc-biology",
2839
+ name: isBn ? "\u099C\u09C0\u09AC\u09AC\u09BF\u099C\u09CD\u099E\u09BE\u09A8" : "Biology",
2840
+ iconKey: "dna",
2841
+ colorClass: "from-rose-500 to-pink-600"
2842
+ },
2843
+ {
2844
+ id: "ssc-bangla",
2845
+ name: isBn ? "\u09AC\u09BE\u0982\u09B2\u09BE" : "Bangla",
2846
+ iconKey: "book",
2847
+ colorClass: "from-cyan-500 to-blue-600"
2848
+ }
2849
+ ];
2850
+ }
2851
+ if (normalizedLevel === "BCS") {
2852
+ return [
2853
+ {
2854
+ id: "bcs-bangladesh-affairs",
2855
+ name: isBn ? "\u09AC\u09BE\u0982\u09B2\u09BE\u09A6\u09C7\u09B6 \u09AC\u09BF\u09B7\u09DF\u09BE\u09AC\u09B2\u09C0" : "Bangladesh Affairs",
2856
+ iconKey: "monument",
2857
+ colorClass: "from-emerald-500 to-green-600"
2858
+ },
2859
+ {
2860
+ id: "bcs-international-affairs",
2861
+ name: isBn ? "\u0986\u09A8\u09CD\u09A4\u09B0\u09CD\u099C\u09BE\u09A4\u09BF\u0995 \u09AC\u09BF\u09B7\u09DF\u09BE\u09AC\u09B2\u09C0" : "International Affairs",
2862
+ iconKey: "world",
2863
+ colorClass: "from-blue-500 to-cyan-600"
2864
+ },
2865
+ {
2866
+ id: "bcs-bangla",
2867
+ name: isBn ? "\u09AC\u09BE\u0982\u09B2\u09BE \u09AD\u09BE\u09B7\u09BE \u0993 \u09B8\u09BE\u09B9\u09BF\u09A4\u09CD\u09AF" : "Bangla Literature",
2868
+ iconKey: "book",
2869
+ colorClass: "from-amber-500 to-orange-600"
2870
+ },
2871
+ {
2872
+ id: "bcs-english",
2873
+ name: isBn ? "\u0987\u0982\u09B0\u09C7\u099C\u09BF \u09AD\u09BE\u09B7\u09BE \u0993 \u09B8\u09BE\u09B9\u09BF\u09A4\u09CD\u09AF" : "English Language",
2874
+ iconKey: "language",
2875
+ colorClass: "from-violet-500 to-purple-600"
2876
+ },
2877
+ {
2878
+ id: "bcs-math-mental-ability",
2879
+ name: isBn ? "\u0997\u09BE\u09A3\u09BF\u09A4\u09BF\u0995 \u0993 \u09AE\u09BE\u09A8\u09B8\u09BF\u0995 \u09A6\u0995\u09CD\u09B7\u09A4\u09BE" : "Math & Mental Ability",
2880
+ iconKey: "brain",
2881
+ colorClass: "from-pink-500 to-rose-600"
2882
+ },
2883
+ {
2884
+ id: "bcs-general-science",
2885
+ name: isBn ? "\u09B8\u09BE\u09A7\u09BE\u09B0\u09A3 \u09AC\u09BF\u099C\u09CD\u099E\u09BE\u09A8 \u0993 \u09AA\u09CD\u09B0\u09AF\u09C1\u0995\u09CD\u09A4\u09BF" : "General Science",
2886
+ iconKey: "atom",
2887
+ colorClass: "from-teal-500 to-emerald-600"
2888
+ }
2889
+ ];
2890
+ }
2891
+ return [
2892
+ {
2893
+ id: "hsc-physics",
2894
+ name: isBn ? "\u09AA\u09A6\u09BE\u09B0\u09CD\u09A5\u09AC\u09BF\u099C\u09CD\u099E\u09BE\u09A8" : "Physics",
2895
+ iconKey: "atom",
2896
+ colorClass: "from-blue-500 to-indigo-600"
2897
+ },
2898
+ {
2899
+ id: "hsc-chemistry",
2900
+ name: isBn ? "\u09B0\u09B8\u09BE\u09DF\u09A8" : "Chemistry",
2901
+ iconKey: "flask",
2902
+ colorClass: "from-emerald-500 to-teal-600"
2903
+ },
2904
+ {
2905
+ id: "hsc-higher-math",
2906
+ name: isBn ? "\u0989\u099A\u09CD\u099A\u09A4\u09B0 \u0997\u09A3\u09BF\u09A4" : "Higher Math",
2907
+ iconKey: "math",
2908
+ colorClass: "from-amber-500 to-orange-600"
2909
+ },
2910
+ {
2911
+ id: "hsc-biology",
2912
+ name: isBn ? "\u099C\u09C0\u09AC\u09AC\u09BF\u099C\u09CD\u099E\u09BE\u09A8" : "Biology",
2913
+ iconKey: "dna",
2914
+ colorClass: "from-rose-500 to-pink-600"
2915
+ },
2916
+ {
2917
+ id: "hsc-ict",
2918
+ name: isBn ? "\u09A4\u09A5\u09CD\u09AF \u0993 \u09AF\u09CB\u0997\u09BE\u09AF\u09CB\u0997 \u09AA\u09CD\u09B0\u09AF\u09C1\u0995\u09CD\u09A4\u09BF" : "ICT",
2919
+ iconKey: "device-laptop",
2920
+ colorClass: "from-cyan-500 to-sky-600"
2921
+ },
2922
+ {
2923
+ id: "hsc-bangla",
2924
+ name: isBn ? "\u09AC\u09BE\u0982\u09B2\u09BE" : "Bangla",
2925
+ iconKey: "book",
2926
+ colorClass: "from-violet-500 to-purple-600"
2927
+ }
2928
+ ];
2929
+ }
2930
+ function calculateStreakFromDates(activityDates) {
2931
+ if (!activityDates || activityDates.length === 0) {
2932
+ return { streak: 0, isActiveToday: false };
2933
+ }
2934
+ const uniqueDateStrings = Array.from(
2935
+ new Set(
2936
+ activityDates.map((d) => {
2937
+ try {
2938
+ return new Date(d).toISOString().split("T")[0];
2939
+ } catch (e6) {
2940
+ return null;
2941
+ }
2942
+ }).filter((d) => Boolean(d))
2943
+ )
2944
+ ).sort((a, b) => a > b ? -1 : 1);
2945
+ if (uniqueDateStrings.length === 0) {
2946
+ return { streak: 0, isActiveToday: false };
2947
+ }
2948
+ const todayStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2949
+ const yesterdayDate = /* @__PURE__ */ new Date();
2950
+ yesterdayDate.setDate(yesterdayDate.getDate() - 1);
2951
+ const yesterdayStr = yesterdayDate.toISOString().split("T")[0];
2952
+ const latestDate = uniqueDateStrings[0];
2953
+ if (!latestDate) {
2954
+ return { streak: 0, isActiveToday: false };
2955
+ }
2956
+ const isActiveToday = latestDate === todayStr;
2957
+ if (latestDate !== todayStr && latestDate !== yesterdayStr) {
2958
+ return { streak: 0, isActiveToday: false };
2959
+ }
2960
+ let streak = 0;
2961
+ const checkDate = new Date(latestDate);
2962
+ for (const dateStr of uniqueDateStrings) {
2963
+ const expectedStr = checkDate.toISOString().split("T")[0];
2964
+ if (dateStr === expectedStr) {
2965
+ streak += 1;
2966
+ checkDate.setDate(checkDate.getDate() - 1);
2967
+ } else {
2968
+ break;
2969
+ }
2970
+ }
2971
+ return { streak, isActiveToday };
2972
+ }
1266
2973
 
1267
2974
  // src/logic/import-export.ts
1268
2975
  var MAX_QUESTIONS = 200;
@@ -1280,50 +2987,50 @@ function parseFields(parts) {
1280
2987
  let option3ImageUrl = "";
1281
2988
  let option4ImageUrl = "";
1282
2989
  if (parts.length >= 13) {
1283
- extra = _nullishCoalesce(_optionalChain([parts, 'access', _10 => _10[7], 'optionalAccess', _11 => _11.trim, 'call', _12 => _12()]), () => ( ""));
1284
- const rawDiff = _nullishCoalesce(_optionalChain([parts, 'access', _13 => _13[8], 'optionalAccess', _14 => _14.trim, 'call', _15 => _15()]), () => ( ""));
2990
+ extra = _nullishCoalesce(_optionalChain([parts, 'access', _82 => _82[7], 'optionalAccess', _83 => _83.trim, 'call', _84 => _84()]), () => ( ""));
2991
+ const rawDiff = _nullishCoalesce(_optionalChain([parts, 'access', _85 => _85[8], 'optionalAccess', _86 => _86.trim, 'call', _87 => _87()]), () => ( ""));
1285
2992
  if (rawDiff) {
1286
2993
  const cap = rawDiff.charAt(0).toUpperCase() + rawDiff.slice(1).toLowerCase();
1287
2994
  if (cap === "Easy" || cap === "Medium" || cap === "Hard") difficulty = cap;
1288
2995
  }
1289
- subjectId = _nullishCoalesce(_optionalChain([parts, 'access', _16 => _16[9], 'optionalAccess', _17 => _17.trim, 'call', _18 => _18()]), () => ( ""));
1290
- chapterId = _nullishCoalesce(_optionalChain([parts, 'access', _19 => _19[10], 'optionalAccess', _20 => _20.trim, 'call', _21 => _21()]), () => ( ""));
1291
- topicId = _nullishCoalesce(_optionalChain([parts, 'access', _22 => _22[11], 'optionalAccess', _23 => _23.trim, 'call', _24 => _24()]), () => ( ""));
1292
- markingInstructions = _nullishCoalesce(_optionalChain([parts, 'access', _25 => _25[12], 'optionalAccess', _26 => _26.trim, 'call', _27 => _27()]), () => ( ""));
1293
- if (parts.length > 13) id = _nullishCoalesce(_optionalChain([parts, 'access', _28 => _28[13], 'optionalAccess', _29 => _29.trim, 'call', _30 => _30()]), () => ( ""));
1294
- if (parts.length > 14) imageUrl = _nullishCoalesce(_optionalChain([parts, 'access', _31 => _31[14], 'optionalAccess', _32 => _32.trim, 'call', _33 => _33()]), () => ( ""));
1295
- if (parts.length > 15) answerImageUrl = _nullishCoalesce(_optionalChain([parts, 'access', _34 => _34[15], 'optionalAccess', _35 => _35.trim, 'call', _36 => _36()]), () => ( ""));
1296
- if (parts.length > 16) option2ImageUrl = _nullishCoalesce(_optionalChain([parts, 'access', _37 => _37[16], 'optionalAccess', _38 => _38.trim, 'call', _39 => _39()]), () => ( ""));
1297
- if (parts.length > 17) option3ImageUrl = _nullishCoalesce(_optionalChain([parts, 'access', _40 => _40[17], 'optionalAccess', _41 => _41.trim, 'call', _42 => _42()]), () => ( ""));
1298
- if (parts.length > 18) option4ImageUrl = _nullishCoalesce(_optionalChain([parts, 'access', _43 => _43[18], 'optionalAccess', _44 => _44.trim, 'call', _45 => _45()]), () => ( ""));
2996
+ subjectId = _nullishCoalesce(_optionalChain([parts, 'access', _88 => _88[9], 'optionalAccess', _89 => _89.trim, 'call', _90 => _90()]), () => ( ""));
2997
+ chapterId = _nullishCoalesce(_optionalChain([parts, 'access', _91 => _91[10], 'optionalAccess', _92 => _92.trim, 'call', _93 => _93()]), () => ( ""));
2998
+ topicId = _nullishCoalesce(_optionalChain([parts, 'access', _94 => _94[11], 'optionalAccess', _95 => _95.trim, 'call', _96 => _96()]), () => ( ""));
2999
+ markingInstructions = _nullishCoalesce(_optionalChain([parts, 'access', _97 => _97[12], 'optionalAccess', _98 => _98.trim, 'call', _99 => _99()]), () => ( ""));
3000
+ if (parts.length > 13) id = _nullishCoalesce(_optionalChain([parts, 'access', _100 => _100[13], 'optionalAccess', _101 => _101.trim, 'call', _102 => _102()]), () => ( ""));
3001
+ if (parts.length > 14) imageUrl = _nullishCoalesce(_optionalChain([parts, 'access', _103 => _103[14], 'optionalAccess', _104 => _104.trim, 'call', _105 => _105()]), () => ( ""));
3002
+ if (parts.length > 15) answerImageUrl = _nullishCoalesce(_optionalChain([parts, 'access', _106 => _106[15], 'optionalAccess', _107 => _107.trim, 'call', _108 => _108()]), () => ( ""));
3003
+ if (parts.length > 16) option2ImageUrl = _nullishCoalesce(_optionalChain([parts, 'access', _109 => _109[16], 'optionalAccess', _110 => _110.trim, 'call', _111 => _111()]), () => ( ""));
3004
+ if (parts.length > 17) option3ImageUrl = _nullishCoalesce(_optionalChain([parts, 'access', _112 => _112[17], 'optionalAccess', _113 => _113.trim, 'call', _114 => _114()]), () => ( ""));
3005
+ if (parts.length > 18) option4ImageUrl = _nullishCoalesce(_optionalChain([parts, 'access', _115 => _115[18], 'optionalAccess', _116 => _116.trim, 'call', _117 => _117()]), () => ( ""));
1299
3006
  } else if (parts.length === 8) {
1300
- extra = _nullishCoalesce(_optionalChain([parts, 'access', _46 => _46[7], 'optionalAccess', _47 => _47.trim, 'call', _48 => _48()]), () => ( ""));
3007
+ extra = _nullishCoalesce(_optionalChain([parts, 'access', _118 => _118[7], 'optionalAccess', _119 => _119.trim, 'call', _120 => _120()]), () => ( ""));
1301
3008
  } else if (parts.length === 9) {
1302
- extra = _nullishCoalesce(_optionalChain([parts, 'access', _49 => _49[7], 'optionalAccess', _50 => _50.trim, 'call', _51 => _51()]), () => ( ""));
1303
- const rawDiff = _nullishCoalesce(_optionalChain([parts, 'access', _52 => _52[8], 'optionalAccess', _53 => _53.trim, 'call', _54 => _54()]), () => ( ""));
3009
+ extra = _nullishCoalesce(_optionalChain([parts, 'access', _121 => _121[7], 'optionalAccess', _122 => _122.trim, 'call', _123 => _123()]), () => ( ""));
3010
+ const rawDiff = _nullishCoalesce(_optionalChain([parts, 'access', _124 => _124[8], 'optionalAccess', _125 => _125.trim, 'call', _126 => _126()]), () => ( ""));
1304
3011
  if (rawDiff) {
1305
3012
  const cap = rawDiff.charAt(0).toUpperCase() + rawDiff.slice(1).toLowerCase();
1306
3013
  if (cap === "Easy" || cap === "Medium" || cap === "Hard") difficulty = cap;
1307
3014
  }
1308
3015
  } else if (parts.length === 10) {
1309
- subjectId = _nullishCoalesce(_optionalChain([parts, 'access', _55 => _55[7], 'optionalAccess', _56 => _56.trim, 'call', _57 => _57()]), () => ( ""));
1310
- chapterId = _nullishCoalesce(_optionalChain([parts, 'access', _58 => _58[8], 'optionalAccess', _59 => _59.trim, 'call', _60 => _60()]), () => ( ""));
1311
- topicId = _nullishCoalesce(_optionalChain([parts, 'access', _61 => _61[9], 'optionalAccess', _62 => _62.trim, 'call', _63 => _63()]), () => ( ""));
3016
+ subjectId = _nullishCoalesce(_optionalChain([parts, 'access', _127 => _127[7], 'optionalAccess', _128 => _128.trim, 'call', _129 => _129()]), () => ( ""));
3017
+ chapterId = _nullishCoalesce(_optionalChain([parts, 'access', _130 => _130[8], 'optionalAccess', _131 => _131.trim, 'call', _132 => _132()]), () => ( ""));
3018
+ topicId = _nullishCoalesce(_optionalChain([parts, 'access', _133 => _133[9], 'optionalAccess', _134 => _134.trim, 'call', _135 => _135()]), () => ( ""));
1312
3019
  } else if (parts.length === 11) {
1313
- extra = _nullishCoalesce(_optionalChain([parts, 'access', _64 => _64[7], 'optionalAccess', _65 => _65.trim, 'call', _66 => _66()]), () => ( ""));
1314
- subjectId = _nullishCoalesce(_optionalChain([parts, 'access', _67 => _67[8], 'optionalAccess', _68 => _68.trim, 'call', _69 => _69()]), () => ( ""));
1315
- chapterId = _nullishCoalesce(_optionalChain([parts, 'access', _70 => _70[9], 'optionalAccess', _71 => _71.trim, 'call', _72 => _72()]), () => ( ""));
1316
- topicId = _nullishCoalesce(_optionalChain([parts, 'access', _73 => _73[10], 'optionalAccess', _74 => _74.trim, 'call', _75 => _75()]), () => ( ""));
3020
+ extra = _nullishCoalesce(_optionalChain([parts, 'access', _136 => _136[7], 'optionalAccess', _137 => _137.trim, 'call', _138 => _138()]), () => ( ""));
3021
+ subjectId = _nullishCoalesce(_optionalChain([parts, 'access', _139 => _139[8], 'optionalAccess', _140 => _140.trim, 'call', _141 => _141()]), () => ( ""));
3022
+ chapterId = _nullishCoalesce(_optionalChain([parts, 'access', _142 => _142[9], 'optionalAccess', _143 => _143.trim, 'call', _144 => _144()]), () => ( ""));
3023
+ topicId = _nullishCoalesce(_optionalChain([parts, 'access', _145 => _145[10], 'optionalAccess', _146 => _146.trim, 'call', _147 => _147()]), () => ( ""));
1317
3024
  } else if (parts.length === 12) {
1318
- extra = _nullishCoalesce(_optionalChain([parts, 'access', _76 => _76[7], 'optionalAccess', _77 => _77.trim, 'call', _78 => _78()]), () => ( ""));
1319
- const rawDiff = _nullishCoalesce(_optionalChain([parts, 'access', _79 => _79[8], 'optionalAccess', _80 => _80.trim, 'call', _81 => _81()]), () => ( ""));
3025
+ extra = _nullishCoalesce(_optionalChain([parts, 'access', _148 => _148[7], 'optionalAccess', _149 => _149.trim, 'call', _150 => _150()]), () => ( ""));
3026
+ const rawDiff = _nullishCoalesce(_optionalChain([parts, 'access', _151 => _151[8], 'optionalAccess', _152 => _152.trim, 'call', _153 => _153()]), () => ( ""));
1320
3027
  if (rawDiff) {
1321
3028
  const cap = rawDiff.charAt(0).toUpperCase() + rawDiff.slice(1).toLowerCase();
1322
3029
  if (cap === "Easy" || cap === "Medium" || cap === "Hard") difficulty = cap;
1323
3030
  }
1324
- subjectId = _nullishCoalesce(_optionalChain([parts, 'access', _82 => _82[9], 'optionalAccess', _83 => _83.trim, 'call', _84 => _84()]), () => ( ""));
1325
- chapterId = _nullishCoalesce(_optionalChain([parts, 'access', _85 => _85[10], 'optionalAccess', _86 => _86.trim, 'call', _87 => _87()]), () => ( ""));
1326
- topicId = _nullishCoalesce(_optionalChain([parts, 'access', _88 => _88[11], 'optionalAccess', _89 => _89.trim, 'call', _90 => _90()]), () => ( ""));
3031
+ subjectId = _nullishCoalesce(_optionalChain([parts, 'access', _154 => _154[9], 'optionalAccess', _155 => _155.trim, 'call', _156 => _156()]), () => ( ""));
3032
+ chapterId = _nullishCoalesce(_optionalChain([parts, 'access', _157 => _157[10], 'optionalAccess', _158 => _158.trim, 'call', _159 => _159()]), () => ( ""));
3033
+ topicId = _nullishCoalesce(_optionalChain([parts, 'access', _160 => _160[11], 'optionalAccess', _161 => _161.trim, 'call', _162 => _162()]), () => ( ""));
1327
3034
  }
1328
3035
  return {
1329
3036
  id: id || void 0,
@@ -1342,18 +3049,18 @@ function parseFields(parts) {
1342
3049
  }
1343
3050
  function buildQuestion(parts) {
1344
3051
  const tags = parts.length >= 7 && parts[6] ? parts[6].split(",").map((t) => t.trim()).filter(Boolean) : [];
1345
- const opt2 = _nullishCoalesce(_optionalChain([parts, 'access', _91 => _91[2], 'optionalAccess', _92 => _92.trim, 'call', _93 => _93()]), () => ( ""));
1346
- const opt3 = _nullishCoalesce(_optionalChain([parts, 'access', _94 => _94[3], 'optionalAccess', _95 => _95.trim, 'call', _96 => _96()]), () => ( ""));
1347
- const opt4 = _nullishCoalesce(_optionalChain([parts, 'access', _97 => _97[4], 'optionalAccess', _98 => _98.trim, 'call', _99 => _99()]), () => ( ""));
3052
+ const opt2 = _nullishCoalesce(_optionalChain([parts, 'access', _163 => _163[2], 'optionalAccess', _164 => _164.trim, 'call', _165 => _165()]), () => ( ""));
3053
+ const opt3 = _nullishCoalesce(_optionalChain([parts, 'access', _166 => _166[3], 'optionalAccess', _167 => _167.trim, 'call', _168 => _168()]), () => ( ""));
3054
+ const opt4 = _nullishCoalesce(_optionalChain([parts, 'access', _169 => _169[4], 'optionalAccess', _170 => _170.trim, 'call', _171 => _171()]), () => ( ""));
1348
3055
  const isWritten = parts.length < 6 || !opt2 && !opt3 && !opt4;
1349
3056
  const extra = parseFields(parts);
1350
3057
  return {
1351
- question: _nullishCoalesce(_optionalChain([parts, 'access', _100 => _100[0], 'optionalAccess', _101 => _101.trim, 'call', _102 => _102()]), () => ( "")),
1352
- answer: _nullishCoalesce(_optionalChain([parts, 'access', _103 => _103[1], 'optionalAccess', _104 => _104.trim, 'call', _105 => _105()]), () => ( "")),
3058
+ question: _nullishCoalesce(_optionalChain([parts, 'access', _172 => _172[0], 'optionalAccess', _173 => _173.trim, 'call', _174 => _174()]), () => ( "")),
3059
+ answer: _nullishCoalesce(_optionalChain([parts, 'access', _175 => _175[1], 'optionalAccess', _176 => _176.trim, 'call', _177 => _177()]), () => ( "")),
1353
3060
  option2: opt2,
1354
3061
  option3: opt3,
1355
3062
  option4: opt4,
1356
- explanation: _nullishCoalesce(_optionalChain([parts, 'access', _106 => _106[5], 'optionalAccess', _107 => _107.trim, 'call', _108 => _108()]), () => ( "")),
3063
+ explanation: _nullishCoalesce(_optionalChain([parts, 'access', _178 => _178[5], 'optionalAccess', _179 => _179.trim, 'call', _180 => _180()]), () => ( "")),
1357
3064
  tags,
1358
3065
  type: isWritten ? "WRITTEN" : "MCQ",
1359
3066
  ...extra
@@ -1404,9 +3111,9 @@ function formatQuestionsToText(questions, keyword, packSubjectId, packChapterId,
1404
3111
  const activeSubject = q.subjectId || packSubjectId;
1405
3112
  const activeChapter = q.chapterId || packChapterId;
1406
3113
  const activeTopic = q.topicId || packTopicId;
1407
- const subjectSlug = activeSubject ? _nullishCoalesce(_optionalChain([activeSubject, 'access', _109 => _109.split, 'call', _110 => _110("::"), 'access', _111 => _111[0], 'optionalAccess', _112 => _112.trim, 'call', _113 => _113()]), () => ( "")) : "";
1408
- const chapterSlug = activeChapter ? _nullishCoalesce(_optionalChain([activeChapter, 'access', _114 => _114.split, 'call', _115 => _115("::"), 'access', _116 => _116[0], 'optionalAccess', _117 => _117.trim, 'call', _118 => _118()]), () => ( "")) : "";
1409
- const topicSlug = activeTopic ? _nullishCoalesce(_optionalChain([activeTopic, 'access', _119 => _119.split, 'call', _120 => _120("::"), 'access', _121 => _121[0], 'optionalAccess', _122 => _122.trim, 'call', _123 => _123()]), () => ( "")) : "";
3114
+ const subjectSlug = activeSubject ? _nullishCoalesce(_optionalChain([activeSubject, 'access', _181 => _181.split, 'call', _182 => _182("::"), 'access', _183 => _183[0], 'optionalAccess', _184 => _184.trim, 'call', _185 => _185()]), () => ( "")) : "";
3115
+ const chapterSlug = activeChapter ? _nullishCoalesce(_optionalChain([activeChapter, 'access', _186 => _186.split, 'call', _187 => _187("::"), 'access', _188 => _188[0], 'optionalAccess', _189 => _189.trim, 'call', _190 => _190()]), () => ( "")) : "";
3116
+ const topicSlug = activeTopic ? _nullishCoalesce(_optionalChain([activeTopic, 'access', _191 => _191.split, 'call', _192 => _192("::"), 'access', _193 => _193[0], 'optionalAccess', _194 => _194.trim, 'call', _195 => _195()]), () => ( "")) : "";
1410
3117
  const fields = [
1411
3118
  q.question,
1412
3119
  q.answer,
@@ -1439,9 +3146,9 @@ function formatQuestionsToCsv(questions, packSubjectId, packChapterId, packTopic
1439
3146
  const activeSubject = q.subjectId || packSubjectId;
1440
3147
  const activeChapter = q.chapterId || packChapterId;
1441
3148
  const activeTopic = q.topicId || packTopicId;
1442
- const subjectSlug = activeSubject ? _nullishCoalesce(_optionalChain([activeSubject, 'access', _124 => _124.split, 'call', _125 => _125("::"), 'access', _126 => _126[0], 'optionalAccess', _127 => _127.trim, 'call', _128 => _128()]), () => ( "")) : "";
1443
- const chapterSlug = activeChapter ? _nullishCoalesce(_optionalChain([activeChapter, 'access', _129 => _129.split, 'call', _130 => _130("::"), 'access', _131 => _131[0], 'optionalAccess', _132 => _132.trim, 'call', _133 => _133()]), () => ( "")) : "";
1444
- const topicSlug = activeTopic ? _nullishCoalesce(_optionalChain([activeTopic, 'access', _134 => _134.split, 'call', _135 => _135("::"), 'access', _136 => _136[0], 'optionalAccess', _137 => _137.trim, 'call', _138 => _138()]), () => ( "")) : "";
3149
+ const subjectSlug = activeSubject ? _nullishCoalesce(_optionalChain([activeSubject, 'access', _196 => _196.split, 'call', _197 => _197("::"), 'access', _198 => _198[0], 'optionalAccess', _199 => _199.trim, 'call', _200 => _200()]), () => ( "")) : "";
3150
+ const chapterSlug = activeChapter ? _nullishCoalesce(_optionalChain([activeChapter, 'access', _201 => _201.split, 'call', _202 => _202("::"), 'access', _203 => _203[0], 'optionalAccess', _204 => _204.trim, 'call', _205 => _205()]), () => ( "")) : "";
3151
+ const topicSlug = activeTopic ? _nullishCoalesce(_optionalChain([activeTopic, 'access', _206 => _206.split, 'call', _207 => _207("::"), 'access', _208 => _208[0], 'optionalAccess', _209 => _209.trim, 'call', _210 => _210()]), () => ( "")) : "";
1445
3152
  return [
1446
3153
  escapeCSV(q.question),
1447
3154
  escapeCSV(q.answer),
@@ -1468,126 +3175,281 @@ function formatQuestionsToCsv(questions, packSubjectId, packChapterId, packTopic
1468
3175
  ${rows.join("\n")}`;
1469
3176
  }
1470
3177
 
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;
3178
+ // src/logic/subscription.ts
3179
+ var MANUAL_PAYMENT_CONFIG = {
3180
+ phoneNumber: "01323567688",
3181
+ accountType: "Personal",
3182
+ methods: ["BKASH", "ROCKET"]
3183
+ };
3184
+ var SUBSCRIPTION_PLANS = {
3185
+ FREE: {
3186
+ id: "FREE",
3187
+ tier: "FREE",
3188
+ titleKey: "upgrade.freeTitle",
3189
+ descKey: "upgrade.freeDesc",
3190
+ pricing: {
3191
+ monthly: 0,
3192
+ quarterly: 0,
3193
+ biannually: 0,
3194
+ annually: 0
3195
+ },
3196
+ aiQuotas: {
3197
+ monthly: { aiEvals: 0, aiQuestions: 0 },
3198
+ quarterly: { aiEvals: 0, aiQuestions: 0 },
3199
+ biannually: { aiEvals: 0, aiQuestions: 0 },
3200
+ annually: { aiEvals: 0, aiQuestions: 0 }
1486
3201
  }
1487
- return entry.url;
3202
+ },
3203
+ PRO: {
3204
+ id: "PRO",
3205
+ tier: "PRO",
3206
+ titleKey: "upgrade.proTitle",
3207
+ descKey: "upgrade.proDesc",
3208
+ pricing: {
3209
+ monthly: 149,
3210
+ quarterly: 399,
3211
+ biannually: 699,
3212
+ annually: 999
3213
+ },
3214
+ aiQuotas: {
3215
+ monthly: { aiEvals: 0, aiQuestions: 0 },
3216
+ quarterly: { aiEvals: 0, aiQuestions: 0 },
3217
+ biannually: { aiEvals: 0, aiQuestions: 0 },
3218
+ annually: { aiEvals: 0, aiQuestions: 0 }
3219
+ }
3220
+ },
3221
+ PRO_AI: {
3222
+ id: "PRO_AI",
3223
+ tier: "PRO_AI",
3224
+ titleKey: "upgrade.aiTitle",
3225
+ descKey: "upgrade.aiDesc",
3226
+ pricing: {
3227
+ monthly: 349,
3228
+ quarterly: 899,
3229
+ biannually: 1599,
3230
+ annually: 2499
3231
+ },
3232
+ aiQuotas: {
3233
+ monthly: { aiEvals: 100, aiQuestions: 450 },
3234
+ quarterly: { aiEvals: 280, aiQuestions: 1100 },
3235
+ biannually: { aiEvals: 500, aiQuestions: 2e3 },
3236
+ annually: { aiEvals: 800, aiQuestions: 3e3 }
3237
+ }
3238
+ }
3239
+ };
3240
+ var AI_EVAL_BOOSTER_PACKS = [
3241
+ {
3242
+ id: "eval_booster_starter",
3243
+ type: "AI_EVAL",
3244
+ amount: 40,
3245
+ price: 69,
3246
+ titleKey: "upgrade.boosters.evalStarterTitle",
3247
+ descKey: "upgrade.boosters.evalStarterDesc"
3248
+ },
3249
+ {
3250
+ id: "eval_booster_standard",
3251
+ type: "AI_EVAL",
3252
+ amount: 100,
3253
+ price: 159,
3254
+ titleKey: "upgrade.boosters.evalStandardTitle",
3255
+ descKey: "upgrade.boosters.evalStandardDesc",
3256
+ badgeKey: "upgrade.popular"
3257
+ },
3258
+ {
3259
+ id: "eval_booster_mega",
3260
+ type: "AI_EVAL",
3261
+ amount: 220,
3262
+ price: 349,
3263
+ titleKey: "upgrade.boosters.evalMegaTitle",
3264
+ descKey: "upgrade.boosters.evalMegaDesc",
3265
+ badgeKey: "upgrade.bestValue"
1488
3266
  }
1489
- set(key, url) {
1490
- this.cache.set(key, { url, expiresAt: Date.now() + this.ttlMs });
3267
+ ];
3268
+ var AI_QUESTION_BOOSTER_PACKS = [
3269
+ {
3270
+ id: "question_booster_drill",
3271
+ type: "AI_QUESTION",
3272
+ amount: 100,
3273
+ price: 39,
3274
+ titleKey: "upgrade.boosters.questionDrillTitle",
3275
+ descKey: "upgrade.boosters.questionDrillDesc"
3276
+ },
3277
+ {
3278
+ id: "question_booster_chapter",
3279
+ type: "AI_QUESTION",
3280
+ amount: 250,
3281
+ price: 99,
3282
+ titleKey: "upgrade.boosters.questionChapterTitle",
3283
+ descKey: "upgrade.boosters.questionChapterDesc",
3284
+ badgeKey: "upgrade.popular"
3285
+ },
3286
+ {
3287
+ id: "question_booster_exam",
3288
+ type: "AI_QUESTION",
3289
+ amount: 700,
3290
+ price: 249,
3291
+ titleKey: "upgrade.boosters.questionExamTitle",
3292
+ descKey: "upgrade.boosters.questionExamDesc",
3293
+ badgeKey: "upgrade.bestValue"
1491
3294
  }
1492
- clear() {
1493
- this.cache.clear();
3295
+ ];
3296
+ function getCycleMultiplier(cycle) {
3297
+ switch (cycle) {
3298
+ case "quarterly":
3299
+ return 3;
3300
+ case "biannually":
3301
+ return 6;
3302
+ case "annually":
3303
+ return 12;
3304
+ case "monthly":
3305
+ default:
3306
+ return 1;
1494
3307
  }
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
- _optionalChain([cache, 'optionalAccess', _139 => _139.set, 'call', _140 => _140(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 += _nullishCoalesce(p.practicedCount, () => ( 0));
1538
- totalMistakes += _nullishCoalesce(p.mistakeCount, () => ( 0));
1539
- totalCorrected += _nullishCoalesce(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 += _nullishCoalesce(p.practicedCount, () => ( 0));
1550
- totalMistakes += _nullishCoalesce(p.mistakeCount, () => ( 0));
1551
- totalCorrected += _nullishCoalesce(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 += _nullishCoalesce(p.practicedCount, () => ( 0));
1562
- totalMistakes += _nullishCoalesce(p.mistakeCount, () => ( 0));
1563
- totalCorrected += _nullishCoalesce(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]];
3308
+ }
3309
+ function getPlanOriginalPrice(tier, cycle) {
3310
+ const normalizedTier = tier === "AI_ANNUAL" || tier === "AI_MONTHLY" ? "PRO_AI" : tier;
3311
+ const plan = SUBSCRIPTION_PLANS[normalizedTier] || SUBSCRIPTION_PLANS.FREE;
3312
+ const monthlyPrice = _nullishCoalesce(plan.pricing.monthly, () => ( 0));
3313
+ return monthlyPrice * getCycleMultiplier(cycle);
3314
+ }
3315
+ function getPlanPrice(tier, cycle) {
3316
+ const normalizedTier = tier === "AI_ANNUAL" || tier === "AI_MONTHLY" ? "PRO_AI" : tier;
3317
+ const plan = SUBSCRIPTION_PLANS[normalizedTier] || SUBSCRIPTION_PLANS.FREE;
3318
+ return _nullishCoalesce(plan.pricing[cycle], () => ( 0));
3319
+ }
3320
+ function getPlanMonthlyEquivalentPrice(tier, cycle) {
3321
+ const normalizedTier = tier === "AI_ANNUAL" || tier === "AI_MONTHLY" ? "PRO_AI" : tier;
3322
+ const plan = SUBSCRIPTION_PLANS[normalizedTier] || SUBSCRIPTION_PLANS.FREE;
3323
+ const cyclePrice = _nullishCoalesce(plan.pricing[cycle], () => ( 0));
3324
+ const multiplier = getCycleMultiplier(cycle);
3325
+ return Math.round(cyclePrice / multiplier);
3326
+ }
3327
+ function getPlanDiscountPercent(cycle) {
3328
+ switch (cycle) {
3329
+ case "quarterly":
3330
+ return 10;
3331
+ case "biannually":
3332
+ return 22;
3333
+ case "annually":
3334
+ return 44;
3335
+ case "monthly":
3336
+ default:
3337
+ return 0;
1581
3338
  }
1582
- return result;
1583
3339
  }
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]];
3340
+ function getPlanAiQuotas(tier, cycle) {
3341
+ const normalizedTier = tier === "AI_ANNUAL" || tier === "AI_MONTHLY" ? "PRO_AI" : tier;
3342
+ const plan = SUBSCRIPTION_PLANS[normalizedTier] || SUBSCRIPTION_PLANS.FREE;
3343
+ return _nullishCoalesce(plan.aiQuotas[cycle], () => ( { aiEvals: 0, aiQuestions: 0 }));
3344
+ }
3345
+ function isValidBangladeshiPhone(phone) {
3346
+ if (!phone) return false;
3347
+ const cleaned = phone.replace(/[\s\-()]/g, "");
3348
+ const regex = /^(?:\+?88)?01[3-9]\d{8}$/;
3349
+ return regex.test(cleaned);
3350
+ }
3351
+ function isValidTrxId(trxId) {
3352
+ if (!trxId) return false;
3353
+ const cleaned = trxId.trim();
3354
+ return cleaned.length >= 4 && cleaned.length <= 40;
3355
+ }
3356
+ function hasProAccess(tier, expiresAt) {
3357
+ if (!tier) return false;
3358
+ const upper = tier.toUpperCase();
3359
+ const isTierPro = upper === "PRO" || upper === "PRO_AI";
3360
+ if (!isTierPro) return false;
3361
+ if (!expiresAt) return true;
3362
+ const expTime = new Date(expiresAt).getTime();
3363
+ if (isNaN(expTime)) return true;
3364
+ return expTime > Date.now();
3365
+ }
3366
+ function hasAiTierAccess(tier, expiresAt) {
3367
+ if (!tier) return false;
3368
+ const upper = tier.toUpperCase();
3369
+ const isTierAi = upper === "PRO_AI";
3370
+ if (!isTierAi) return false;
3371
+ if (!expiresAt) return true;
3372
+ const expTime = new Date(expiresAt).getTime();
3373
+ if (isNaN(expTime)) return true;
3374
+ return expTime > Date.now();
3375
+ }
3376
+ var FREE_DAILY_PRACTICE_LIMIT = 30;
3377
+ function canExportPack(tier, expiresAt, isCreator) {
3378
+ return Boolean(isCreator || hasProAccess(tier, expiresAt));
3379
+ }
3380
+ function canAccessMistakeVault(tier, expiresAt, isCreator) {
3381
+ return Boolean(isCreator || hasProAccess(tier, expiresAt));
3382
+ }
3383
+ function canWatchContestSolution(tier, expiresAt, isCreator) {
3384
+ return Boolean(isCreator || hasProAccess(tier, expiresAt));
3385
+ }
3386
+ function shouldTrackPracticeProgress(tier, expiresAt, isCreator) {
3387
+ return Boolean(isCreator || hasProAccess(tier, expiresAt));
3388
+ }
3389
+ function getDailyPracticeLimit(tier, expiresAt) {
3390
+ if (hasProAccess(tier, expiresAt)) return null;
3391
+ return FREE_DAILY_PRACTICE_LIMIT;
3392
+ }
3393
+ function checkDailyPracticeLimit(dailyCount, lastPracticeDate, tier, expiresAt, todayStr) {
3394
+ if (hasProAccess(tier, expiresAt)) {
3395
+ return {
3396
+ allowed: true,
3397
+ remaining: Infinity,
3398
+ currentCount: dailyCount,
3399
+ isUnlimited: true
3400
+ };
3401
+ }
3402
+ const today = todayStr || (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
3403
+ const isSameDay = lastPracticeDate ? lastPracticeDate.startsWith(today) : false;
3404
+ const currentCount = isSameDay ? Number.isFinite(dailyCount) ? dailyCount : 0 : 0;
3405
+ const remaining = Math.max(0, FREE_DAILY_PRACTICE_LIMIT - currentCount);
3406
+ const allowed = currentCount < FREE_DAILY_PRACTICE_LIMIT;
3407
+ return {
3408
+ allowed,
3409
+ remaining,
3410
+ currentCount,
3411
+ isUnlimited: false
3412
+ };
3413
+ }
3414
+ function hasSufficientAiQuestionQuota(quota, requested) {
3415
+ if (requested <= 0) return false;
3416
+ return (_nullishCoalesce(quota, () => ( 0))) >= requested;
3417
+ }
3418
+ function hasSufficientAiEvalQuota(quota) {
3419
+ return (_nullishCoalesce(quota, () => ( 0))) > 0;
3420
+ }
3421
+
3422
+ // src/navigation.ts
3423
+ function getActiveTab(pathname) {
3424
+ const normalized = pathname.toLowerCase();
3425
+ if (normalized.startsWith("/contests") || normalized.startsWith("/contest")) {
3426
+ return "contests";
3427
+ }
3428
+ if (normalized.startsWith("/courses") || normalized.startsWith("/course") || normalized.startsWith("/exams")) {
3429
+ return "courses";
3430
+ }
3431
+ if (normalized.startsWith("/profile") || normalized.startsWith("/dashboard") || normalized.startsWith("/edit-profile")) {
3432
+ return "profile";
3433
+ }
3434
+ if (normalized.startsWith("/learn") || normalized.startsWith("/practice") || normalized.startsWith("/questions") || normalized.startsWith("/question") || normalized.startsWith("/bank") || normalized.startsWith("/question-bank") || normalized.startsWith("/packs") || normalized.startsWith("/pack") || normalized.startsWith("/taxonomy") || normalized.startsWith("/explore")) {
3435
+ return "learn";
1590
3436
  }
3437
+ return "home";
3438
+ }
3439
+ function getCanonicalPath(pathname) {
3440
+ const normalized = pathname.toLowerCase();
3441
+ if (normalized === "/explore/questions") return "/learn/questions";
3442
+ if (normalized === "/explore/packs") return "/learn/packs";
3443
+ if (normalized === "/question-bank") return "/learn/bank";
3444
+ if (normalized === "/contest/all") return "/contests";
3445
+ if (normalized === "/dashboard") return "/profile";
3446
+ if (normalized === "/edit-profile") return "/profile/edit";
3447
+ return pathname;
3448
+ }
3449
+ function isAuthGatedRoute(pathname) {
3450
+ const normalized = pathname.toLowerCase();
3451
+ const isProtectedPath = normalized.includes("/exam") || normalized.includes("/enroll") || normalized.startsWith("/profile") || normalized.startsWith("/dashboard") || normalized.startsWith("/edit-profile") || normalized.startsWith("/avatar-creator") || normalized.startsWith("/practice");
3452
+ return isProtectedPath;
1591
3453
  }
1592
3454
 
1593
3455
  // src/utils/bangla-numbers.ts
@@ -1600,15 +3462,51 @@ function engToBanglaNumber(num) {
1600
3462
  }
1601
3463
 
1602
3464
  // src/utils/format-curriculum.ts
1603
- function formatCurriculumName(idString, dbName) {
1604
- if (dbName) return dbName;
3465
+ function extractBilingualName(name, language) {
3466
+ if (!name) return "";
3467
+ const trimmed = name.trim();
3468
+ const match = trimmed.match(/^(.*?)\s*[\(\[(]\s*([^\)\])]+)\s*[\)\])]\s*$/);
3469
+ if (!match) {
3470
+ return trimmed;
3471
+ }
3472
+ const part1 = _optionalChain([match, 'access', _211 => _211[1], 'optionalAccess', _212 => _212.trim, 'call', _213 => _213()]) || "";
3473
+ const part2 = _optionalChain([match, 'access', _214 => _214[2], 'optionalAccess', _215 => _215.trim, 'call', _216 => _216()]) || "";
3474
+ if (!part1 && !part2) return trimmed;
3475
+ if (!part1) return part2;
3476
+ if (!part2) return part1;
3477
+ const hasBengali = (text) => /[\u0980-\u09FF]/.test(text);
3478
+ const isPart1Bengali = hasBengali(part1);
3479
+ const isPart2Bengali = hasBengali(part2);
3480
+ let banglaPart = part1;
3481
+ let englishPart = part2;
3482
+ if (!isPart1Bengali && isPart2Bengali) {
3483
+ banglaPart = part2;
3484
+ englishPart = part1;
3485
+ } else {
3486
+ banglaPart = part1;
3487
+ englishPart = part2;
3488
+ }
3489
+ const lang = _optionalChain([language, 'optionalAccess', _217 => _217.toLowerCase, 'call', _218 => _218()]);
3490
+ if (lang === "en") {
3491
+ return englishPart || banglaPart || trimmed;
3492
+ }
3493
+ if (lang === "bn") {
3494
+ return banglaPart || englishPart || trimmed;
3495
+ }
3496
+ return trimmed;
3497
+ }
3498
+ function formatCurriculumName(idString, dbName, language) {
3499
+ if (dbName) {
3500
+ return extractBilingualName(dbName, language);
3501
+ }
1605
3502
  if (!idString) return "";
1606
3503
  const namePart = _nullishCoalesce(idString.split("::")[0], () => ( ""));
1607
3504
  const clean = namePart.replace(/[-_]/g, " ");
1608
- return clean.split(/\s+/).map((word) => {
3505
+ const formatted = clean.split(/\s+/).map((word) => {
1609
3506
  if (!word) return "";
1610
3507
  return word.charAt(0).toUpperCase() + word.slice(1);
1611
3508
  }).join(" ");
3509
+ return extractBilingualName(formatted, language);
1612
3510
  }
1613
3511
 
1614
3512
  // src/utils/relative-time.ts
@@ -1674,6 +3572,199 @@ function processLatexText(str) {
1674
3572
  return result;
1675
3573
  }
1676
3574
 
3575
+ // src/utils/time.ts
3576
+ function convertToBangladeshTime(isoString, locale = "en-GB") {
3577
+ const options = {
3578
+ year: "numeric",
3579
+ month: "long",
3580
+ day: "numeric",
3581
+ hour: "numeric",
3582
+ minute: "numeric",
3583
+ hour12: true
3584
+ };
3585
+ const date = new Date(isoString);
3586
+ const resolvedLocale = locale === "bn" ? "bn-BD" : locale;
3587
+ return date.toLocaleString(resolvedLocale, {
3588
+ ...options,
3589
+ timeZone: "Asia/Dhaka"
3590
+ });
3591
+ }
3592
+ function getDhakaNow() {
3593
+ const now = /* @__PURE__ */ new Date();
3594
+ const parts = new Intl.DateTimeFormat("en-CA", {
3595
+ timeZone: "Asia/Dhaka",
3596
+ year: "numeric",
3597
+ month: "2-digit",
3598
+ day: "2-digit",
3599
+ hour: "2-digit",
3600
+ minute: "2-digit",
3601
+ second: "2-digit",
3602
+ hour12: false
3603
+ }).formatToParts(now);
3604
+ const get = (type) => _nullishCoalesce(_optionalChain([parts, 'access', _219 => _219.find, 'call', _220 => _220((p) => p.type === type), 'optionalAccess', _221 => _221.value]), () => ( "00"));
3605
+ return /* @__PURE__ */ new Date(`${get("year")}-${get("month")}-${get("day")}T${get("hour")}:${get("minute")}:${get("second")}`);
3606
+ }
3607
+ function calculateTimeLeft(endTime) {
3608
+ const target = new Date(endTime).getTime();
3609
+ const now = (/* @__PURE__ */ new Date()).getTime();
3610
+ const difference = target - now;
3611
+ if (difference <= 0 || isNaN(difference)) {
3612
+ return {
3613
+ days: 0,
3614
+ hours: 0,
3615
+ minutes: 0,
3616
+ seconds: 0,
3617
+ isEnded: true
3618
+ };
3619
+ }
3620
+ return {
3621
+ days: Math.floor(difference / (1e3 * 60 * 60 * 24)),
3622
+ hours: Math.floor(difference / (1e3 * 60 * 60) % 24),
3623
+ minutes: Math.floor(difference / 1e3 / 60 % 60),
3624
+ seconds: Math.floor(difference / 1e3 % 60),
3625
+ isEnded: false
3626
+ };
3627
+ }
3628
+ function formatDate(date, locale = "en-GB") {
3629
+ const d = typeof date === "string" ? new Date(date) : date;
3630
+ return d.toLocaleDateString(locale, {
3631
+ day: "2-digit",
3632
+ month: "short",
3633
+ year: "numeric"
3634
+ });
3635
+ }
3636
+
3637
+ // src/utils/currency.ts
3638
+ function formatBDT(amount) {
3639
+ return amount.toLocaleString("en-US");
3640
+ }
3641
+
3642
+ // src/utils/url.ts
3643
+ function getYouTubeId(url) {
3644
+ if (!url) return null;
3645
+ const regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|[?&]v=)([^#&?]*).*/;
3646
+ const match = url.match(regExp);
3647
+ return match && _optionalChain([match, 'access', _222 => _222[2], 'optionalAccess', _223 => _223.length]) === 11 ? match[2] : null;
3648
+ }
3649
+
3650
+ // src/utils/file.ts
3651
+ function sanitizeFileName(name) {
3652
+ return name.replace(/[\s,]+/g, "_");
3653
+ }
3654
+
3655
+ // src/utils/guards.ts
3656
+ function isQuestionLevel(value) {
3657
+ return [
3658
+ "One",
3659
+ "Two",
3660
+ "Three",
3661
+ "Four",
3662
+ "Five",
3663
+ "Six",
3664
+ "Seven",
3665
+ "Eight",
3666
+ "SSC",
3667
+ "HSC",
3668
+ "Admission",
3669
+ "HSC+Admission",
3670
+ "BCS"
3671
+ ].includes(value);
3672
+ }
3673
+ function isLanguage(value) {
3674
+ return ["Bangla", "English", "Any"].includes(value);
3675
+ }
3676
+ function isDifficulty(value) {
3677
+ return ["Easy", "Medium", "Hard"].includes(value);
3678
+ }
3679
+ function normalizeLevel(value) {
3680
+ if (!value) return "";
3681
+ const clean = value.trim().replace(/\s+/g, "+");
3682
+ const upper = clean.toUpperCase();
3683
+ if (upper === "SSC" || upper.includes("CLASS_9") || upper.includes("CLASS_10")) return "SSC";
3684
+ if (upper === "HSC" || upper === "ADMISSION" || upper === "HSC+ADMISSION" || upper.includes("CLASS_11") || upper.includes("CLASS_12")) return "HSC+Admission";
3685
+ if (upper === "BCS") return "BCS";
3686
+ return clean;
3687
+ }
3688
+
3689
+
3690
+
3691
+
3692
+
3693
+
3694
+
3695
+
3696
+
3697
+
3698
+
3699
+
3700
+
3701
+
3702
+
3703
+
3704
+
3705
+
3706
+
3707
+
3708
+
3709
+
3710
+
3711
+
3712
+
3713
+
3714
+
3715
+
3716
+
3717
+
3718
+
3719
+
3720
+
3721
+
3722
+
3723
+
3724
+
3725
+
3726
+
3727
+
3728
+
3729
+
3730
+
3731
+
3732
+
3733
+
3734
+
3735
+
3736
+
3737
+
3738
+
3739
+
3740
+
3741
+
3742
+
3743
+
3744
+
3745
+
3746
+
3747
+
3748
+
3749
+
3750
+
3751
+
3752
+
3753
+
3754
+
3755
+
3756
+
3757
+
3758
+
3759
+
3760
+
3761
+
3762
+
3763
+
3764
+
3765
+
3766
+
3767
+
1677
3768
 
1678
3769
 
1679
3770
 
@@ -1719,5 +3810,5 @@ function processLatexText(str) {
1719
3810
 
1720
3811
 
1721
3812
 
1722
- exports.AIService = AIService; exports.ContestService = ContestService; exports.CourseService = CourseService; exports.CurriculumService = CurriculumService; exports.MediaService = MediaService; exports.MediaUrlCache = MediaUrlCache; exports.NewsService = NewsService; exports.OrjokClient = OrjokClient; exports.PackService = PackService; exports.ProgressService = ProgressService; exports.QuestionService = QuestionService; exports.UserService = UserService; exports._resetMediaUrlWarning = _resetMediaUrlWarning; exports.aggregateChapterMetrics = aggregateChapterMetrics; exports.aggregateSubjectMetrics = aggregateSubjectMetrics; exports.aggregateTopicMetrics = aggregateTopicMetrics; exports.buildImageAnswer = buildImageAnswer; exports.calculateAccuracy = calculateAccuracy; exports.calculateExamTime = calculateExamTime; exports.computeRatingDelta = computeRatingDelta; exports.createOrjokClient = createOrjokClient; exports.detectQuestionType = detectQuestionType; exports.engToBanglaNumber = engToBanglaNumber; exports.filterQuestionsByMode = filterQuestionsByMode; exports.formatCurriculumName = formatCurriculumName; exports.formatExamTime = formatExamTime; exports.formatQuestionsToCsv = formatQuestionsToCsv; exports.formatQuestionsToText = formatQuestionsToText; exports.getAllTiers = getAllTiers; exports.getEloProgress = getEloProgress; exports.getEloTier = getEloTier; exports.getRatingForSubject = getRatingForSubject; exports.getRelativeTime = getRelativeTime; exports.isImageAnswer = isImageAnswer; exports.isStorageKey = isStorageKey; exports.normalizeDifficulty = normalizeDifficulty; exports.parseCsvQuestions = parseCsvQuestions; exports.parseImageKeys = parseImageKeys; exports.parseTextQuestions = parseTextQuestions; exports.processLatexText = processLatexText; exports.resolveMediaUrl = resolveMediaUrl; exports.resolveMediaUrls = resolveMediaUrls; exports.scoreExam = scoreExam; exports.shuffle = shuffle; exports.shuffleInPlace = shuffleInPlace;
3813
+ exports.AIService = AIService; exports.AI_EVAL_BOOSTER_PACKS = AI_EVAL_BOOSTER_PACKS; exports.AI_QUESTION_BOOSTER_PACKS = AI_QUESTION_BOOSTER_PACKS; exports.CLOTHES_COLORS = CLOTHES_COLORS; exports.COLOR_HUES = COLOR_HUES; exports.ContestService = ContestService; exports.CourseService = CourseService; exports.CurriculumService = CurriculumService; exports.FREE_DAILY_PRACTICE_LIMIT = FREE_DAILY_PRACTICE_LIMIT; exports.HAIR_COLORS = HAIR_COLORS; exports.INITIAL_AVATAR_DATA = INITIAL_AVATAR_DATA; exports.MANUAL_PAYMENT_CONFIG = MANUAL_PAYMENT_CONFIG; exports.MediaService = MediaService; exports.MediaUrlCache = _chunkODXD2WJNcjs.MediaUrlCache; exports.NewsService = NewsService; exports.NotificationService = NotificationService; exports.OrjokClient = OrjokClient; exports.PackService = PackService; exports.ProgressService = ProgressService; exports.QuestionService = QuestionService; exports.RENDER_ORDER = RENDER_ORDER; exports.SUBSCRIPTION_PLANS = SUBSCRIPTION_PLANS; exports.SubscriptionService = SubscriptionService; exports.UserService = UserService; exports.XP_REWARDS = _chunkODXD2WJNcjs.XP_REWARDS; exports.XP_TIERS = _chunkODXD2WJNcjs.XP_TIERS; exports._resetMediaUrlWarning = _chunkODXD2WJNcjs._resetMediaUrlWarning; exports.aggregateChapterMetrics = _chunkODXD2WJNcjs.aggregateChapterMetrics; exports.aggregateSubjectMetrics = _chunkODXD2WJNcjs.aggregateSubjectMetrics; exports.aggregateTopicMetrics = _chunkODXD2WJNcjs.aggregateTopicMetrics; exports.buildImageAnswer = buildImageAnswer; exports.calculateAccuracy = _chunkODXD2WJNcjs.calculateAccuracy; exports.calculateDefaultExamMinutes = calculateDefaultExamMinutes; exports.calculateExamTime = calculateExamTime; exports.calculateStreakFromDates = calculateStreakFromDates; exports.calculateTimeLeft = calculateTimeLeft; exports.calculateUnreadCount = _chunkODXD2WJNcjs.calculateUnreadCount; exports.calculateXpReward = _chunkODXD2WJNcjs.calculateXpReward; exports.canAccessMistakeVault = canAccessMistakeVault; exports.canExportPack = canExportPack; exports.canWatchContestSolution = canWatchContestSolution; exports.checkDailyPracticeLimit = checkDailyPracticeLimit; exports.computeRatingDelta = computeRatingDelta; exports.convertToBangladeshTime = convertToBangladeshTime; exports.createOrjokClient = createOrjokClient; exports.detectQuestionType = detectQuestionType; exports.difficultyOptions = _chunkODXD2WJNcjs.difficultyOptions; exports.engToBanglaNumber = engToBanglaNumber; exports.extractBilingualName = extractBilingualName; exports.filterQuestionsByMode = filterQuestionsByMode; exports.formatBDT = formatBDT; exports.formatCurriculumName = formatCurriculumName; exports.formatDate = formatDate; exports.formatExamTime = formatExamTime; exports.formatLevelUpNotification = _chunkODXD2WJNcjs.formatLevelUpNotification; exports.formatQuestionsToCsv = formatQuestionsToCsv; exports.formatQuestionsToText = formatQuestionsToText; exports.generateRandomAvatarConfig = generateRandomAvatarConfig; exports.generateRandomAvatarState = generateRandomAvatarState; exports.getActiveTab = getActiveTab; exports.getActualOptionFileName = getActualOptionFileName; exports.getAllTiers = getAllTiers; exports.getAllXpTiers = _chunkODXD2WJNcjs.getAllXpTiers; exports.getCanonicalPath = getCanonicalPath; exports.getCycleMultiplier = getCycleMultiplier; exports.getDailyPracticeLimit = getDailyPracticeLimit; exports.getDhakaNow = getDhakaNow; exports.getEloProgress = getEloProgress; exports.getEloTier = getEloTier; exports.getLevelFromXp = _chunkODXD2WJNcjs.getLevelFromXp; exports.getPlanAiQuotas = getPlanAiQuotas; exports.getPlanDiscountPercent = getPlanDiscountPercent; exports.getPlanMonthlyEquivalentPrice = getPlanMonthlyEquivalentPrice; exports.getPlanOriginalPrice = getPlanOriginalPrice; exports.getPlanPrice = getPlanPrice; exports.getPublicExamsByLevel = _chunkODXD2WJNcjs.getPublicExamsByLevel; exports.getRatingForSubject = getRatingForSubject; exports.getRecommendedSubjectsForLevel = getRecommendedSubjectsForLevel; exports.getRelativeTime = getRelativeTime; exports.getTimeOfDayGreeting = getTimeOfDayGreeting; exports.getXpForLevel = _chunkODXD2WJNcjs.getXpForLevel; exports.getXpProgress = _chunkODXD2WJNcjs.getXpProgress; exports.getXpRequiredForNextLevel = _chunkODXD2WJNcjs.getXpRequiredForNextLevel; exports.getXpTier = _chunkODXD2WJNcjs.getXpTier; exports.getYouTubeId = getYouTubeId; exports.hasAiTierAccess = hasAiTierAccess; exports.hasProAccess = hasProAccess; exports.hasSufficientAiEvalQuota = hasSufficientAiEvalQuota; exports.hasSufficientAiQuestionQuota = hasSufficientAiQuestionQuota; exports.isAuthGatedRoute = isAuthGatedRoute; exports.isCorrectAnswer = isCorrectAnswer; exports.isDifficulty = isDifficulty; exports.isDifficultyMatch = _chunkODXD2WJNcjs.isDifficultyMatch; exports.isImageAnswer = isImageAnswer; exports.isLanguage = isLanguage; exports.isLanguageMatch = _chunkODXD2WJNcjs.isLanguageMatch; exports.isQuestionLevel = isQuestionLevel; exports.isStorageKey = _chunkODXD2WJNcjs.isStorageKey; exports.isValidBangladeshiPhone = isValidBangladeshiPhone; exports.isValidMCQ = _chunkODXD2WJNcjs.isValidMCQ; exports.isValidTrxId = isValidTrxId; exports.languageOptions = _chunkODXD2WJNcjs.languageOptions; exports.levelOptions = _chunkODXD2WJNcjs.levelOptions; exports.mapCognitoLevelToStandardLevel = mapCognitoLevelToStandardLevel; exports.mergeNotificationTimeline = _chunkODXD2WJNcjs.mergeNotificationTimeline; exports.normalizeDifficulty = normalizeDifficulty; exports.normalizeLevel = normalizeLevel; exports.paginateWithAccumulator = paginateWithAccumulator; exports.parseAvatarConfig = parseAvatarConfig; exports.parseAvatarConfigWithDefaults = parseAvatarConfigWithDefaults; exports.parseCsvQuestions = parseCsvQuestions; exports.parseImageKeys = parseImageKeys; exports.parseTextQuestions = parseTextQuestions; exports.processLatexText = processLatexText; exports.publicExamOptions = _chunkODXD2WJNcjs.publicExamOptions; exports.reorderObjectKeys = reorderObjectKeys; exports.resolveMediaUrl = _chunkODXD2WJNcjs.resolveMediaUrl; exports.resolveMediaUrls = _chunkODXD2WJNcjs.resolveMediaUrls; exports.sanitizeFileName = sanitizeFileName; exports.scoreExam = scoreExam; exports.serializeAvatarConfig = serializeAvatarConfig; exports.shouldTrackPracticeProgress = shouldTrackPracticeProgress; exports.shuffle = _chunkODXD2WJNcjs.shuffle; exports.shuffleInPlace = _chunkODXD2WJNcjs.shuffleInPlace;
1723
3814
  //# sourceMappingURL=index.cjs.map