Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions apps/quick-learn-backend/src/common/services/basic-crud.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,21 @@ export class BasicCrudService<T> {
});
}

/**
* Retrieves a count based on the provided options.
* @param {FindOptionsWhere<T> | FindOptionsWhere<T>[]} [options] - The options for finding the entity.
* @returns {Promise<T>} A promise that resolves with the retrieved entity.
*/
async count(
options?: FindOptionsWhere<T> | FindOptionsWhere<T>[],
relations: string[] = [],
): Promise<number> {
return await this.repository.count({
where: options,
relations: [...relations],
});
}

/**
* Retrieves multiple entities based on the provided options.
* @param {FindOptionsWhere<T> | FindOptionsWhere<T>[]} [options] - The options for finding the entities.
Expand Down
4 changes: 4 additions & 0 deletions apps/quick-learn-backend/src/lang/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,8 @@ export const en = {
successdullyPasswordUpdated: 'Password updated successfully.',
successPreferencesUpdated: 'Email preference updated successfully.',
successPreferences: 'Successfully got user preferences.',

// flagged lesson
invalidLesson: 'Invalid lesson is provided.',
successUnflagLesson: 'Successfully unflagged the lesson.',
};
13 changes: 13 additions & 0 deletions apps/quick-learn-backend/src/routes/lesson/dto/get-lesson.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { IsIn, IsOptional, IsString, ValidateIf } from 'class-validator';

export class GetLessonDto {
@ValidateIf((o) => !o?.flagged || o?.flagged != 'true')
@IsString()
@IsIn(['true', 'false'])
approved?: string;

@IsOptional()
@IsString()
@IsIn(['true', 'false'])
flagged?: string;
}
1 change: 1 addition & 0 deletions apps/quick-learn-backend/src/routes/lesson/dto/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './create-lesson.dto';
export * from './update-lesson.dto';
export * from './get-lesson.dto';
38 changes: 32 additions & 6 deletions apps/quick-learn-backend/src/routes/lesson/lesson.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { LessonService } from './lesson.service';
import { ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger';
import { BasePaginationDto, SuccessResponse } from '@src/common/dto';
import { en } from '@src/lang/en';
import { CreateLessonDto, UpdateLessonDto } from './dto';
import { CreateLessonDto, GetLessonDto, UpdateLessonDto } from './dto';
import { JwtAuthGuard } from '../auth/guards';
import { CurrentUser } from '@src/common/decorators/current-user.decorators';
import { UserEntity } from '@src/entities';
Expand All @@ -25,6 +25,7 @@ import { RolesGuard } from '../auth/guards/roles.guard';
import { Roles } from '@src/common/decorators/roles.decorator';
import { UserTypeId } from '@src/common/enum/user_role.enum';
import { LessonProgressService } from '../lesson-progress/lesson-progress.service';
import { MoreThan } from 'typeorm';

@ApiTags('Lessons')
@Controller({
Expand Down Expand Up @@ -113,12 +114,21 @@ export class LessonController {
*/
async get(
@Param('id') id: string,
@Query('approved') approved: string,
@Query() getLessonDto: GetLessonDto,
): Promise<SuccessResponse> {
const lesson = await this.service.get(
{ id: +id, approved: approved == 'true' },
['created_by_user', 'course'],
);
const conditions = { id: +id };
const relations = ['created_by_user', 'course'];
if (getLessonDto.approved)
conditions['approved'] = getLessonDto.approved == 'true';
if (getLessonDto.flagged) {
conditions['flagged_lesson'] = {
id: MoreThan(0),
};
relations.push('flagged_lesson');
relations.push('flagged_lesson.user');
}

const lesson = await this.service.get(conditions, relations);
if (!lesson) {
throw new BadRequestException(en.lessonNotFound);
}
Expand Down Expand Up @@ -162,6 +172,22 @@ export class LessonController {
return new SuccessResponse(en.approveLesson);
}

@UseGuards(RolesGuard)
@Roles(UserTypeId.SUPER_ADMIN, UserTypeId.ADMIN)
@ApiOperation({ summary: 'Unflag an lessons.' })
@Patch('/:id/unflag')
/**
* Approves an existing lesson.
* @param id The id of the lesson that needs to be approved.
* @param user The user approving the lesson.
* @throws BadRequestException if the lesson doesn't exist
* @returns A promise that resolves to a success response.
*/
async unFlag(@Param('id') id: string): Promise<SuccessResponse> {
await this.service.unFlagLesson(+id);
return new SuccessResponse(en.successUnflagLesson);
}

@ApiOperation({ summary: 'Archive an lessons.' })
@Patch('/:id/archive')
/**
Expand Down
51 changes: 38 additions & 13 deletions apps/quick-learn-backend/src/routes/lesson/lesson.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { en } from '@src/lang/en';
import { UserTypeIdEnum } from '@quick-learn/shared';
import { PaginationDto } from '../users/dto';
import { PaginatedResult } from '@src/common/interfaces';
import { Repository, DataSource, ILike } from 'typeorm';
import { Repository, DataSource, ILike, MoreThan } from 'typeorm';
import Helpers from '@src/common/utils/helper';
import { FileService } from '@src/file/file.service';
import { DailyLessonEnum } from '@src/common/enum/daily_lesson.enum';
Expand All @@ -25,7 +25,7 @@ export class LessonService extends PaginationService<LessonEntity> {
@InjectRepository(LessonTokenEntity)
private readonly LessonTokenRepository: Repository<LessonTokenEntity>,
@InjectRepository(FlaggedLessonEntity)
private flaggedLessonEnity: Repository<FlaggedLessonEntity>,
private flaggedLessonRepository: Repository<FlaggedLessonEntity>,
private readonly courseService: CourseService,
private readonly FileService: FileService,
private readonly dataSource: DataSource,
Expand Down Expand Up @@ -448,13 +448,13 @@ export class LessonService extends PaginationService<LessonEntity> {
}

// Create new flagged lesson entry
const flaggedLesson = this.flaggedLessonEnity.create({
const flaggedLesson = this.flaggedLessonRepository.create({
user_id: lessonToken.user_id,
lesson_id: lessonToken.lesson_id,
course_id: lessonToken.course_id,
});

return await this.flaggedLessonEnity.save(flaggedLesson);
return await this.flaggedLessonRepository.save(flaggedLesson);
}

async findAllFlaggedLesson(page = 1, limit = 10, search = '') {
Expand Down Expand Up @@ -490,35 +490,60 @@ export class LessonService extends PaginationService<LessonEntity> {

// Get both lessons and count in parallel for better performance
const [lessons, total] = await Promise.all([
this.flaggedLessonEnity.find(findOptions),
this.flaggedLessonEnity.count({
this.flaggedLessonRepository.find(findOptions),
this.flaggedLessonRepository.count({
where: findOptions.where,
}),
]);

return {
lessons,
totalCount: total,
items: lessons,
total,
page,
limit,
totalPages: Math.ceil(total / limit),
total_pages: Math.ceil(total / limit),
};
} catch (error) {
console.error('Error querying flagged lessons:', error);
throw error;
}
}

async unFlagLesson(id: number): Promise<void> {
const isValid = await this.flaggedLessonRepository.find({
where: { lesson_id: id },
});

if (!isValid) throw new BadRequestException(en.invalidLesson);

await this.flaggedLessonRepository.delete({ lesson_id: id });
}

async getUnApprovedLessonCount() {
return await this.repository.count({
where: {
return await this.count(
{
archived: false,
approved: false,
course: {
archived: false,
},
},
relations: ['course'],
});
['course'],
);
}

async getFlaggedLessonCount() {
return await this.count(
{
archived: false,
flagged_lesson: {
id: MoreThan(0),
},
course: {
archived: false,
},
},
['flagged_lesson'],
);
}
}
10 changes: 7 additions & 3 deletions apps/quick-learn-backend/src/routes/metadata/metadata.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,15 @@ export class MetadataService {

return metadata;
}

async getLessonMetaData() {
const unapprovedLesson =
await this.lessonService.getUnApprovedLessonCount();
const [unapproved_lessons, flagged_lessons] = await Promise.all([
await this.lessonService.getUnApprovedLessonCount(),
await this.lessonService.getFlaggedLessonCount(),
]);
return {
unapprovedLessons: unapprovedLesson,
unapproved_lessons,
flagged_lessons,
};
}
}
26 changes: 24 additions & 2 deletions apps/quick-learn-frontend/src/apiServices/lessonsService.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
TFlaggedLesson,
TLesson,
TUserDailyProgress,
} from '@src/shared/types/contentRepository';
Expand All @@ -13,6 +14,7 @@ import {
TDailyLessonResponse,
UserLessonProgress,
} from '@src/shared/types/LessonProgressTypes';
import { PaginateWrapper } from '@src/shared/types/utilTypes';

export const getArchivedLessons = async (): Promise<
AxiosSuccessResponse<TLesson[]>
Expand Down Expand Up @@ -42,6 +44,15 @@ export const getLessonDetails = async (
return response.data;
};

export const getFlaggedLessonDetails = async (
id: string,
): Promise<AxiosSuccessResponse<TLesson>> => {
const response = await axiosInstance.get<AxiosSuccessResponse<TLesson>>(
ContentRepositoryApiEnum.LESSON + `/${id}?flagged=true`,
);
return response.data;
};

export const approveLesson = async (
id: string,
): Promise<AxiosSuccessResponse> => {
Expand Down Expand Up @@ -85,8 +96,10 @@ export const getFlaggedLessons = async (
page = 1,
limit = 10,
search = '',
): Promise<AxiosSuccessResponse> => {
const response = await axiosInstance.get<AxiosSuccessResponse>(
): Promise<AxiosSuccessResponse<PaginateWrapper<TFlaggedLesson[]>>> => {
const response = await axiosInstance.get<
AxiosSuccessResponse<PaginateWrapper<TFlaggedLesson[]>>
>(
`${ContentRepositoryApiEnum.GET_FLAGGED_LESSON}?page=${page}&limit=${limit}&q=${search}`,
);
return response.data;
Expand Down Expand Up @@ -192,3 +205,12 @@ export const flagLesson = async (
);
return response.data;
};

export const markLessonAsUnFlagged = async (
id: string,
): Promise<AxiosSuccessResponse> => {
const response = await axiosInstance.patch<AxiosSuccessResponse>(
ContentRepositoryApiEnum.LESSON + `/${id}/unflag`,
);
return response.data;
};
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ const LessonDetails = () => {
? defaultLinks
: [
...defaultLinks,
{ name: lesson.name, link: `${RouteEnum.APPROVALS}/${lesson.id}` },
{ name: lesson.course.name, link: '#', disabled: true },
{ name: lesson.name, link: `${RouteEnum.FLAGGED}/${lesson.id}` },
],
[lesson],
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const ApprovalList = () => {
if (!isLoading) {
dispatch(
updateSystemPreferencesData({
unapprovedLessons: lessons?.length ?? 0,
unapproved_lessons: lessons?.length ?? 0,
}),
);
}
Expand Down
Loading