Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Features students course search filter #37

Merged
merged 3 commits into from
Jul 25, 2023
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
9 changes: 9 additions & 0 deletions client/src/api/endpoints/course/course.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
getIndividualCourseService,
getRecommendedCoursesService,
getTrendingCoursesService,
searchCourseService,
} from "../../services/course/courseService";
import { getCoursesByInstructorService } from "../../services/course/courseService";
import { PaymentIntent } from "@stripe/stripe-js";
Expand Down Expand Up @@ -50,3 +51,11 @@ export const getTrendingCourses = () => {
export const getCourseByStudent = () => {
return getCourseByStudentService(END_POINTS.GET_COURSE_BY_STUDENT);
};

export const searchCourse = (searchQuery: string, filterQuery: string) => {
return searchCourseService(
END_POINTS.SEARCH_COURSE,
searchQuery,
filterQuery
);
};
51 changes: 31 additions & 20 deletions client/src/api/services/course/courseService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,22 @@ import api from "../../middlewares/protectedInterceptor";
import { PaymentIntent } from "@stripe/stripe-js";
import axiosInstance from "../../middlewares/interceptor";

export const addCourseService = async (endpoint: string, courseInfo: FormData) => {
export const addCourseService = async (
endpoint: string,
courseInfo: FormData
) => {
const response = await api.post(
`${CONSTANTS_COMMON.API_BASE_URL}/${endpoint}`,
courseInfo
);
return response;
};

export const editCourseService = async (endpoint: string,courseId:string, courseInfo: FormData) => {
export const editCourseService = async (
endpoint: string,
courseId: string,
courseInfo: FormData
) => {
const response = await api.put(
`${CONSTANTS_COMMON.API_BASE_URL}/${endpoint}/${courseId}`,
courseInfo
Expand Down Expand Up @@ -44,41 +51,45 @@ export const getIndividualCourseService = async (
};

export const enrollStudentService = async (
endpoint:string,
courseId:string,
paymentInfo?:PaymentIntent,
endpoint: string,
courseId: string,
paymentInfo?: PaymentIntent
) => {
const response = await api.post(
`${CONSTANTS_COMMON.API_BASE_URL}/${endpoint}/${courseId}`,paymentInfo
`${CONSTANTS_COMMON.API_BASE_URL}/${endpoint}/${courseId}`,
paymentInfo
);
return response.data
return response.data;
};

export const getRecommendedCoursesService = async (
endpoint:string
) => {
export const getRecommendedCoursesService = async (endpoint: string) => {
const response = await api.get(
`${CONSTANTS_COMMON.API_BASE_URL}/${endpoint}`
);
return response.data
return response.data;
};

export const getTrendingCoursesService = async (
endpoint:string,
) => {
export const getTrendingCoursesService = async (endpoint: string) => {
const response = await axiosInstance.get(
`${CONSTANTS_COMMON.API_BASE_URL}/${endpoint}`
);
return response.data
return response.data;
};

export const getCourseByStudentService = async (
endpoint:string,
) => {
export const getCourseByStudentService = async (endpoint: string) => {
const response = await api.get(
`${CONSTANTS_COMMON.API_BASE_URL}/${endpoint}`
);
return response.data
return response.data;
};


export const searchCourseService = async (
endpoint: string,
searchQuery: string,
filterQuery: string
) => {
const response = await api.get(
`${CONSTANTS_COMMON.API_BASE_URL}/${endpoint}?search=${searchQuery}&filter=${filterQuery}`
);
return response.data;
};
1 change: 1 addition & 0 deletions client/src/constants/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,6 @@ const END_POINTS = {
GET_MY_STUDENTS:"api/instructors/get-students-by-instructor",
GET_INSTRUCTOR_DETAILS:"api/instructors/get-instructor-details",
EDIT_COURSE:"api/courses/instructors/edit-course",
SEARCH_COURSE:"api/courses/search-course"
}
export default END_POINTS
Binary file added dump.rdb
Binary file not shown.
90 changes: 64 additions & 26 deletions server/src/adapters/controllers/courseController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ import {
} from '../../app/usecases/course/recommendation';
import { editCourseU } from '../../app/usecases/course/editCourse';
import { editLessonsU } from '../../app/usecases/lessons/editLesson';
import { searchCourseU } from '../../app/usecases/course/search';
import { CacheRepositoryInterface } from '@src/app/repositories/cachedRepoInterface';
import { RedisRepositoryImpl } from '@src/frameworks/database/redis/redisCacheRepository';
import { RedisClient } from '@src/app';

const courseController = (
cloudServiceInterface: CloudServiceInterface,
Expand All @@ -58,7 +62,10 @@ const courseController = (
discussionDbRepository: DiscussionDbInterface,
discussionDbRepositoryImpl: DiscussionRepoMongodbInterface,
paymentDbRepository: PaymentInterface,
paymentDbRepositoryImpl: PaymentImplInterface
paymentDbRepositoryImpl: PaymentImplInterface,
cacheDbRepository: CacheRepositoryInterface,
cacheDbRepositoryImpl: RedisRepositoryImpl,
cacheClient: RedisClient
) => {
const dbRepositoryCourse = courseDbRepository(courseDbRepositoryImpl());
const cloudService = cloudServiceInterface(cloudServiceImpl());
Expand All @@ -67,8 +74,10 @@ const courseController = (
const dbRepositoryDiscussion = discussionDbRepository(
discussionDbRepositoryImpl()
);

const dbRepositoryPayment = paymentDbRepository(paymentDbRepositoryImpl());
const dbRepositoryCache = cacheDbRepository(
cacheDbRepositoryImpl(cacheClient)
);

const addCourse = asyncHandler(
async (req: CustomRequest, res: Response, next: NextFunction) => {
Expand All @@ -91,30 +100,34 @@ const courseController = (
}
);

const editCourse = asyncHandler(
async (req: CustomRequest, res: Response) => {
const course: EditCourseInfo = req.body;
const files: Express.Multer.File[] = req.files as Express.Multer.File[];
const instructorId = req.user?.Id;
const courseId: string = req.params.courseId;
const response = await editCourseU(
courseId,
instructorId,
files,
course,
cloudService,
dbRepositoryCourse
);
res.status(200).json({
status: 'success',
message: 'Successfully updated the course',
data: response
});
}
);
const editCourse = asyncHandler(async (req: CustomRequest, res: Response) => {
const course: EditCourseInfo = req.body;
const files: Express.Multer.File[] = req.files as Express.Multer.File[];
const instructorId = req.user?.Id;
const courseId: string = req.params.courseId;
const response = await editCourseU(
courseId,
instructorId,
files,
course,
cloudService,
dbRepositoryCourse
);
res.status(200).json({
status: 'success',
message: 'Successfully updated the course',
data: response
});
});

const getAllCourses = asyncHandler(async (req: Request, res: Response) => {
const courses = await getAllCourseU(cloudService, dbRepositoryCourse);
const cacheOptions = {
key: `all-courses`,
expireTimeSec: 600,
data: JSON.stringify(courses)
};
await dbRepositoryCache.setCache(cacheOptions);
res.status(200).json({
status: 'success',
message: 'Successfully retrieved all courses',
Expand Down Expand Up @@ -155,7 +168,7 @@ const courseController = (
);

const addLesson = asyncHandler(async (req: CustomRequest, res: Response) => {
const instructorId = req.user?.Id
const instructorId = req.user?.Id;
const courseId = req.params.courseId;
const lesson = req.body;
const medias = req.files as Express.Multer.File[];
Expand All @@ -179,7 +192,7 @@ const courseController = (

const editLesson = asyncHandler(async (req: CustomRequest, res: Response) => {
const lesson = req.body;
const lessonId = req.params.lessonId
const lessonId = req.params.lessonId;
const medias = req.files as Express.Multer.File[];
const questions = JSON.parse(lesson.questions);
lesson.questions = questions;
Expand Down Expand Up @@ -382,6 +395,30 @@ const courseController = (
}
);

const searchCourse = asyncHandler(async (req: Request, res: Response) => {
const { search, filter } = req.query as { search: string; filter: string };
const key = search.trim()===""?search:filter
const searchResult = await searchCourseU(
search,
filter,
cloudService,
dbRepositoryCourse
);
if (searchResult.length) {
const cacheOptions = {
key: `${key}`,
expireTimeSec: 600,
data: JSON.stringify(searchResult)
};
await dbRepositoryCache.setCache(cacheOptions);
}
res.status(200).json({
status: 'success',
message: 'Successfully retrieved courses based on the search query',
data: searchResult
});
});

return {
addCourse,
editCourse,
Expand All @@ -402,7 +439,8 @@ const courseController = (
enrollStudent,
getRecommendedCourseByStudentInterest,
getTrendingCourses,
getCourseByStudent
getCourseByStudent,
searchCourse
};
};

Expand Down
4 changes: 2 additions & 2 deletions server/src/app/repositories/cachedRepoInterface.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { RedisRepository} from "../../frameworks/database/redis/cache"
import { RedisRepositoryImpl} from "../../frameworks/database/redis/redisCacheRepository"

export const cacheRepositoryInterface=(repository:ReturnType<RedisRepository>)=>{
export const cacheRepositoryInterface=(repository:ReturnType<RedisRepositoryImpl>)=>{

const setCache = async(cachingOptions:{
key: string;
Expand Down
16 changes: 12 additions & 4 deletions server/src/app/repositories/courseDbRepository.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { CourseRepositoryMongoDbInterface } from '@src/frameworks/database/mongodb/repositories/courseReposMongoDb';
import { AddCourseInfoInterface, EditCourseInfo } from '@src/types/courseInterface';
import {
AddCourseInfoInterface,
EditCourseInfo
} from '@src/types/courseInterface';

export const courseDbRepository = (
repository: ReturnType<CourseRepositoryMongoDbInterface>
) => {
const addCourse = async (courseInfo: AddCourseInfoInterface) =>
await repository.addCourse(courseInfo);

const editCourse = async (courseId:string,editInfo:EditCourseInfo)=> await repository.editCourse(courseId,editInfo)

const editCourse = async (courseId: string, editInfo: EditCourseInfo) =>
await repository.editCourse(courseId, editInfo);

const getAllCourse = async () => await repository.getAllCourse();

Expand Down Expand Up @@ -40,6 +44,9 @@ export const courseDbRepository = (
const getStudentsByCourseForInstructor = async (instructorId: string) =>
await repository.getStudentsByCourseForInstructor(instructorId);

const searchCourse = async (isFree: boolean, searchQuery: string,filterQuery:string) =>
await repository.searchCourse(isFree, searchQuery,filterQuery);

return {
addCourse,
editCourse,
Expand All @@ -53,7 +60,8 @@ export const courseDbRepository = (
getCourseByStudent,
getTotalNumberOfCourses,
getNumberOfCoursesAddedInEachMonth,
getStudentsByCourseForInstructor
getStudentsByCourseForInstructor,
searchCourse
};
};
export type CourseDbRepositoryInterface = typeof courseDbRepository;
49 changes: 49 additions & 0 deletions server/src/app/usecases/course/search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { CourseDbRepositoryInterface } from '../../../app/repositories/courseDbRepository';
import AppError from '../../../utils/appError';
import HttpStatusCodes from '../../../constants/HttpStatusCodes';
import { CourseInterface } from '../../../types/courseInterface';
import { CloudServiceInterface } from '@src/app/services/cloudServiceInterface';

export const searchCourseU = async (
searchQuery: string,
filterQuery: string,
cloudService:ReturnType<CloudServiceInterface>,
courseDbRepository: ReturnType<CourseDbRepositoryInterface>
) => {
if (!searchQuery && !filterQuery) {
throw new AppError(
'Please provide a search or filter query',
HttpStatusCodes.BAD_REQUEST
);
}
let isFree = false
let searchParams: string;

if (searchQuery) {
// Check if the search query has the "free" prefix
const freeRegex = /^free\s/i;
const isFreeMatch = searchQuery.match(freeRegex);
if (isFreeMatch) {
isFree = true;
searchParams = searchQuery.replace(freeRegex, '').trim();
} else {
searchParams = searchQuery;
}
} else {
searchParams = filterQuery;
}

const searchResult= await courseDbRepository.searchCourse(
isFree,
searchParams,
filterQuery
);
await Promise.all(
searchResult.map(async (course) => {
if (course.thumbnail) {
course.thumbnailUrl = await cloudService.getFile(course.thumbnail.key);
}
})
);
return searchResult;
};
Loading
Loading