File

apps/recallassess/recallassess-api/src/api/admin/course/config/course-config.service.ts

Extends

ModuleConfigBase

Index

Properties
Methods

Methods

getConfiguration
getConfiguration()
Returns : ModuleConfig
Private Async validateCourseDeletion
validateCourseDeletion(courseId: number)

Validates that a course can be deleted Throws an error if the course has enrolled participants

Parameters :
Name Type Optional
courseId number No
Returns : Promise<void>

Properties

Private prisma
Type : BNestPrismaService
Decorators :
@Inject()
import { ColumnConfig, ColumnType } from "@bish-nest/core/app-config/column-config.class";
import { MediaConfig } from "@bish-nest/core/app-config/media-config.class";
import { ModuleConfig } from "@bish-nest/core/app-config/module-config.class";
import { ModuleConfigBase } from "@bish-nest/core/app-config/module-config-base.class";
import { BNestPrismaService } from "@bish-nest/core/services/database/prisma/prisma.service";
import { BadRequestException, Inject, Injectable } from "@nestjs/common";
import { MediaName, MediaType } from "@prisma/client";
import { CourseAddDto } from "../dto/course-add.dto";
import { CourseDetailDto } from "../dto/course-detail.dto";
import { CourseListDto } from "../dto/course-list.dto";
import { CourseListSimpleDto } from "../dto/course-list-simple.dto";
import { CourseSaveDto } from "../dto/course-save.dto";
import { courseLinks } from "./course-links-config.service";

@Injectable()
export class CourseConfig extends ModuleConfigBase {
  @Inject() private prisma!: BNestPrismaService;
  getConfiguration(): ModuleConfig {
    const columns: ColumnConfig[] = [
      new ColumnConfig({ name: "id", type: ColumnType.Integer }),
      new ColumnConfig({ name: "course_code" }),
      new ColumnConfig({ name: "title" }),
      new ColumnConfig({ name: "course_code_title" }),
      new ColumnConfig({ name: "description" }),
      new ColumnConfig({ name: "short_description" }),
      new ColumnConfig({ name: "category" }),
      new ColumnConfig({ name: "level" }),
      new ColumnConfig({ name: "assessment_id", type: ColumnType.Integer }),
      new ColumnConfig({ name: "knowledge_review_id", type: ColumnType.Integer }),
      new ColumnConfig({ name: "is_published", type: ColumnType.Boolean }),
      new ColumnConfig({ name: "is_featured", type: ColumnType.Boolean }),
      new ColumnConfig({ name: "skip_hundred_dj", type: ColumnType.Boolean }),
      new ColumnConfig({ name: "created_at", type: ColumnType.DateTime }),
      new ColumnConfig({ name: "updated_at", type: ColumnType.DateTime }),
    ];

    const mediaArr: MediaConfig[] = [
      new MediaConfig({
        name: MediaName.COURSE__IMAGE,
        mediaRelationName: "mediaImages",
        mediaRelationObj: {
          mediaImages: {
            where: { media_type: MediaType.IMAGE, media_name: MediaName.COURSE__IMAGE },
          },
        },
        mediaRelationFromMediaTable: "courseMediaImages",
      }),
      new MediaConfig({
        name: "COURSE__DETAIL_PDF" as MediaName,
        mediaRelationName: "courseDetailPdfDocuments",
        mediaRelationObj: {
          courseDetailPdfDocuments: {
            where: { media_type: MediaType.DOCUMENT, media_name: "COURSE__DETAIL_PDF" as MediaName },
          },
        },
        mediaRelationFromMediaTable: "courseDetailPdfDocuments",
        isPublic: true,
        maxFiles: 1,
      }),
      new MediaConfig({
        name: "COURSE__TESTIMONIALS_PDF" as MediaName,
        mediaRelationName: "testimonialsPdfDocuments",
        mediaRelationObj: {
          testimonialsPdfDocuments: {
            where: { media_type: MediaType.DOCUMENT, media_name: "COURSE__TESTIMONIALS_PDF" as MediaName },
          },
        },
        mediaRelationFromMediaTable: "courseTestimonialsPdfDocuments",
        isPublic: true,
        maxFiles: 1,
      }),
      // Note: hundredDjEmail*Documents relations don't exist in Prisma Course model
      // These are handled separately via direct Media queries, not as Prisma includes
      // Keeping them in mediaArr for S3 URL prefix processing, but with empty relationObj
      new MediaConfig({
        name: "COURSE__HUNDRED_DJ_EMAIL1" as MediaName,
        mediaRelationName: "hundredDjEmail1Documents",
        mediaRelationObj: {
          hundredDjEmail1Documents: {
            where: { media_type: MediaType.DOCUMENT, media_name: MediaName.COURSE__HUNDRED_DJ_EMAIL1 },
          },
        },
        mediaRelationFromMediaTable: "courseHundredDjEmail1Documents",
      }),
      new MediaConfig({
        name: "COURSE__HUNDRED_DJ_EMAIL2" as MediaName,
        mediaRelationName: "hundredDjEmail2Documents",
        mediaRelationObj: {
          hundredDjEmail2Documents: {
            where: { media_type: MediaType.DOCUMENT, media_name: MediaName.COURSE__HUNDRED_DJ_EMAIL2 },
          },
        },
        mediaRelationFromMediaTable: "courseHundredDjEmail2Documents",
      }),
      new MediaConfig({
        name: "COURSE__HUNDRED_DJ_EMAIL3" as MediaName,
        mediaRelationName: "hundredDjEmail3Documents",
        mediaRelationObj: {
          hundredDjEmail3Documents: {
            where: { media_type: MediaType.DOCUMENT, media_name: MediaName.COURSE__HUNDRED_DJ_EMAIL3 },
          },
        },
        mediaRelationFromMediaTable: "courseHundredDjEmail3Documents",
      }),
      new MediaConfig({
        name: "COURSE__HUNDRED_DJ_EMAIL4" as MediaName,
        mediaRelationName: "hundredDjEmail4Documents",
        mediaRelationObj: {
          hundredDjEmail4Documents: {
            where: { media_type: MediaType.DOCUMENT, media_name: MediaName.COURSE__HUNDRED_DJ_EMAIL4 },
          },
        },
        mediaRelationFromMediaTable: "courseHundredDjEmail4Documents",
      }),
    ];

    return new ModuleConfig({
      name: "course",
      columns,
      // Allow searching by numeric id in the list keyword box (e.g. "11")
      keywordSearchCols: ["id", "course_code", "title", "description", "short_description", "category"],
      mediaArr,

      relationObjToIncludeForDetail: {
        assessment: true,
        knowledgeReview: true,
        // Note: hundredDjEmail*Documents are handled via mediaArr configuration and loaded separately
        // in CourseService.getDetail() to avoid Prisma relation errors and reduce API calls
        // Note: courseModules and learningGroupParticipants are not included here
        // because counts are calculated efficiently using count queries in CourseService.getDetail()
        userCreatedBy: true,
        userUpdatedBy: true,
      },

      listDto: CourseListDto,
      detailDto: CourseDetailDto,
      addDto: CourseAddDto,
      saveDto: CourseSaveDto,
      simpleDto: CourseListSimpleDto,
      deleteValidation: this.validateCourseDeletion.bind(this),
      links: courseLinks,
    });
  }

  /**
   * Validates that a course can be deleted
   * Throws an error if the course has enrolled participants
   */
  private async validateCourseDeletion(courseId: number): Promise<void> {
    // Check if there are any enrollments for this course
    const enrollmentCount = await this.prisma.client.learningGroupParticipant.count({
      where: {
        course_id: courseId,
      },
    });

    if (enrollmentCount > 0) {
      throw new BadRequestException(
        `Cannot delete course. This course has ${enrollmentCount} enrolled participant(s). Courses with enrollments cannot be deleted.`,
      );
    }
  }
}

results matching ""

    No results matching ""