Skip to content

Commit

Permalink
Merge pull request #37 from abinth11/features-students-course-search-…
Browse files Browse the repository at this point in the history
…filter

Features students course search filter
  • Loading branch information
abinth11 authored Jul 25, 2023
2 parents f67f3f0 + 3297524 commit b714981
Show file tree
Hide file tree
Showing 14 changed files with 325 additions and 109 deletions.
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

0 comments on commit b714981

Please sign in to comment.