Skip to content

Commit 99cd1ef

Browse files
committed
Fix all Admin Library modals
1 parent c07190d commit 99cd1ef

File tree

13 files changed

+751
-211
lines changed

13 files changed

+751
-211
lines changed

backend/rest/courseRoutes.ts

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -447,16 +447,12 @@ courseRouter.delete(
447447
);
448448

449449
courseRouter.patch(
450-
"/:unitId/:moduleId/publish",
450+
"/:moduleId/publish",
451451
isAuthorizedByRole(new Set(["Administrator"])),
452-
moduleBelongsToUnitValidator,
453452
async (req, res) => {
454-
const { unitId, moduleId } = req.params;
453+
const { moduleId } = req.params;
455454
try {
456-
const updated = await courseModuleService.publishCourseModule(
457-
unitId,
458-
moduleId,
459-
);
455+
const updated = await courseModuleService.publishCourseModule(moduleId);
460456
res.json(updated);
461457
} catch (e: unknown) {
462458
res.status(500).send(getErrorMessage(e));
@@ -465,16 +461,12 @@ courseRouter.patch(
465461
);
466462

467463
courseRouter.patch(
468-
"/:unitId/:moduleId/unpublish",
464+
"/:moduleId/unpublish",
469465
isAuthorizedByRole(new Set(["Administrator"])),
470-
moduleBelongsToUnitValidator,
471466
async (req, res) => {
472-
const { unitId, moduleId } = req.params;
467+
const { moduleId } = req.params;
473468
try {
474-
const updated = await courseModuleService.unpublishCourseModule(
475-
unitId,
476-
moduleId,
477-
);
469+
const updated = await courseModuleService.unpublishCourseModule(moduleId);
478470
res.json(updated);
479471
} catch (e: unknown) {
480472
res.status(500).send(getErrorMessage(e));

backend/services/implementations/courseModuleService.ts

Lines changed: 9 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* eslint-disable class-methods-use-this */
2-
import mongoose, { ClientSession, Schema, startSession } from "mongoose";
2+
import { ClientSession, Schema, startSession } from "mongoose";
33
import { PDFDocument } from "pdf-lib";
44
import MgCourseModule, {
55
CourseModule,
@@ -10,6 +10,7 @@ import CoursePageModel, {
1010
import MgCourseUnit, { CourseUnit } from "../../models/courseunit.mgmodel";
1111
import {
1212
CourseModuleDTO,
13+
CourseModuleLeanDTO,
1314
CreateCourseModuleDTO,
1415
ModuleStatus,
1516
UpdateCourseModuleDTO,
@@ -276,54 +277,33 @@ class CourseModuleService implements ICourseModuleService {
276277
/**
277278
* Publish a module (Draft → Published or Unpublished → Published).
278279
*/
279-
async publishCourseModule(
280-
courseUnitId: string,
281-
moduleId: string,
282-
): Promise<CourseModuleDTO> {
283-
return this.changeStatus(courseUnitId, moduleId, ModuleStatus.Published);
280+
async publishCourseModule(moduleId: string): Promise<CourseModuleLeanDTO> {
281+
return this.changeStatus(moduleId, ModuleStatus.Published);
284282
}
285283

286284
/**
287285
* Unpublish a module (Published → Unpublished).
288286
*/
289-
async unpublishCourseModule(
290-
courseUnitId: string,
291-
moduleId: string,
292-
): Promise<CourseModuleDTO> {
293-
return this.changeStatus(courseUnitId, moduleId, ModuleStatus.Unpublished);
287+
async unpublishCourseModule(moduleId: string): Promise<CourseModuleLeanDTO> {
288+
return this.changeStatus(moduleId, ModuleStatus.Unpublished);
294289
}
295290

296291
/**
297292
* Internal helper that validates state transitions.
298293
*/
299294
private async changeStatus(
300-
courseUnitId: string,
301295
moduleId: string,
302296
newStatus: ModuleStatus,
303-
): Promise<CourseModuleDTO> {
297+
): Promise<CourseModuleLeanDTO> {
304298
const session = await startSession();
305299
session.startTransaction();
306300
try {
307-
const courseUnit = await MgCourseUnit.findById(courseUnitId).session(
308-
session,
309-
);
310-
const newModuleId = new mongoose.Types.ObjectId(moduleId);
311-
312-
const belongsToUnit = courseUnit?.modules.some((m) =>
313-
(m as unknown as mongoose.Types.ObjectId).equals(newModuleId),
314-
);
315-
if (!courseUnit || !belongsToUnit) {
316-
throw new Error("Module not found in specified unit");
317-
}
318-
319301
const module = await MgCourseModule.findById(moduleId).session(session);
320302
if (!module) {
321303
throw new Error("Module not found");
322304
}
323305

324-
if (
325-
!validTransitions[module.status as ModuleStatus].includes(newStatus)
326-
) {
306+
if (!validTransitions[module.status].includes(newStatus)) {
327307
const msg = `Cannot transition from "${module.status}" to "${newStatus}"`;
328308
throw new Error(msg);
329309
}
@@ -332,11 +312,7 @@ class CourseModuleService implements ICourseModuleService {
332312
await module.save({ session });
333313
await session.commitTransaction();
334314

335-
return {
336-
id: module.id,
337-
title: module.title,
338-
status: module.status as ModuleStatus,
339-
} as CourseModuleDTO;
315+
return module.toObject();
340316
} catch (error) {
341317
await session.abortTransaction();
342318
Logger.error(

backend/services/interfaces/courseModuleService.ts

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ClientSession } from "mongoose";
22
import {
33
CourseModuleDTO,
4+
CourseModuleLeanDTO,
45
CreateCourseModuleDTO,
56
UpdateCourseModuleDTO,
67
} from "../../types/courseTypes";
@@ -67,25 +68,17 @@ interface ICourseModuleService {
6768

6869
/**
6970
* Publish a module (Draft → Published, or Unpublished → Published)
70-
* @param courseUnitId the id of the unit that contains the module
7171
* @param moduleId the id of the module to publish
7272
* @throws Error if the status transition is invalid or module/unit not found
7373
*/
74-
publishCourseModule(
75-
courseUnitId: string,
76-
moduleId: string,
77-
): Promise<CourseModuleDTO>;
74+
publishCourseModule(moduleId: string): Promise<CourseModuleLeanDTO>;
7875

7976
/**
8077
* Unpublish a module (Published → Unpublished)
81-
* @param courseUnitId the id of the unit that contains the module
8278
* @param moduleId the id of the module to unpublish
8379
* @throws Error if the status transition is invalid or module/unit not found
8480
*/
85-
unpublishCourseModule(
86-
courseUnitId: string,
87-
moduleId: string,
88-
): Promise<CourseModuleDTO>;
81+
unpublishCourseModule(moduleId: string): Promise<CourseModuleLeanDTO>;
8982

9083
/**
9184
* Reorder pages within a module

backend/types/courseTypes.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { ObjectId } from "mongoose";
2+
13
export type CourseUnitDTO = {
24
id: string;
35
displayIndex: number;
@@ -25,6 +27,15 @@ export type CourseModuleDTO = {
2527
status: ModuleStatus;
2628
};
2729

30+
export type CourseModuleLeanDTO = {
31+
id: string;
32+
title: string;
33+
imageURL?: string;
34+
pages: ObjectId[];
35+
unitId?: string;
36+
status: ModuleStatus;
37+
};
38+
2839
export type CreateCourseModuleDTO = Pick<CourseModuleDTO, "title">;
2940
export type UpdateCourseModuleDTO = Partial<
3041
Pick<CourseModuleDTO, "title" | "imageURL">

frontend/src/APIClients/CourseAPIClient.ts

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,10 @@ const createModule = async (unitId: string, title: string) => {
102102
}
103103
};
104104

105-
const uploadThumbnail = async (moduleID: string, uploadedImage: FormData) => {
105+
const uploadThumbnail = async (
106+
moduleID: string,
107+
uploadedImage: FormData,
108+
): Promise<string | null> => {
106109
const bearerToken = `Bearer ${getLocalStorageObjProperty(
107110
AUTHENTICATED_USER_KEY,
108111
"accessToken",
@@ -183,6 +186,27 @@ const editModule = async (
183186
}
184187
};
185188

189+
const deleteModule = async (
190+
unitId: string,
191+
moduleId: string,
192+
): Promise<string | null> => {
193+
const bearerToken = `Bearer ${getLocalStorageObjProperty(
194+
AUTHENTICATED_USER_KEY,
195+
"accessToken",
196+
)}`;
197+
try {
198+
const { data } = await baseAPIClient.delete(
199+
`/course/${unitId}/${moduleId}`,
200+
{
201+
headers: { Authorization: bearerToken },
202+
},
203+
);
204+
return data.id;
205+
} catch (error) {
206+
return null;
207+
}
208+
};
209+
186210
const deletePage = async (
187211
moduleId: string,
188212
pageId: string,
@@ -247,6 +271,48 @@ const reorderPages = async (
247271
}
248272
};
249273

274+
const publishModule = async (
275+
moduleId: string,
276+
): Promise<CourseModule | null> => {
277+
const bearerToken = `Bearer ${getLocalStorageObjProperty(
278+
AUTHENTICATED_USER_KEY,
279+
"accessToken",
280+
)}`;
281+
try {
282+
const { data } = await baseAPIClient.patch(
283+
`/course/${moduleId}/publish`,
284+
{},
285+
{
286+
headers: { Authorization: bearerToken },
287+
},
288+
);
289+
return data;
290+
} catch (error) {
291+
return null;
292+
}
293+
};
294+
295+
const unpublishModule = async (
296+
moduleId: string,
297+
): Promise<CourseModule | null> => {
298+
const bearerToken = `Bearer ${getLocalStorageObjProperty(
299+
AUTHENTICATED_USER_KEY,
300+
"accessToken",
301+
)}`;
302+
try {
303+
const { data } = await baseAPIClient.patch(
304+
`/course/${moduleId}/unpublish`,
305+
{},
306+
{
307+
headers: { Authorization: bearerToken },
308+
},
309+
);
310+
return data;
311+
} catch (error) {
312+
return null;
313+
}
314+
};
315+
250316
export default {
251317
getUnits,
252318
createUnit,
@@ -258,7 +324,10 @@ export default {
258324
lessonUpload,
259325
getModuleById,
260326
editModule,
327+
deleteModule,
261328
deletePage,
262329
reorderPages,
263330
reorderModules,
331+
publishModule,
332+
unpublishModule,
264333
};

0 commit comments

Comments
 (0)