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
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.backend.elearning.configuration;

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver;

import java.util.List;
import java.util.Locale;

@Configuration
public class AppLocaleResolver extends AcceptHeaderLocaleResolver implements WebMvcConfigurer {

private static final List<Locale> SUPPORTED = List.of(Locale.ENGLISH, new Locale("vi"));

@Override
public Locale resolveLocale(HttpServletRequest request) {
String header = request.getHeader("Accept-Language");
if (header == null || header.isBlank()) return Locale.ENGLISH;
Locale matched = Locale.lookup(Locale.LanguageRange.parse(header), SUPPORTED);
return matched != null ? matched : Locale.ENGLISH;
}

@Bean
public MessageSource messageSource() {
var rs = new ResourceBundleMessageSource();
rs.setBasenames("messages/messages"); // -> resources/messages/messages*.properties
rs.setDefaultEncoding("UTF-8");
rs.setUseCodeAsDefaultMessage(true);
rs.setFallbackToSystemLocale(false);
return rs;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import com.backend.elearning.security.JWTUtil;
import com.backend.elearning.utils.Constants;
import com.backend.elearning.utils.EmailEncryptionUtil;
import com.backend.elearning.utils.RandomString;
import jakarta.mail.MessagingException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
Expand All @@ -26,7 +25,6 @@
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
Expand Down Expand Up @@ -80,7 +78,7 @@ public AuthenticationVm login(AuthenticationPostVm request) {
Optional<User> user = userRepository.findByEmail(request.email());
Optional<Student> student = studentRepository.findByEmail(request.email());
if (!user.isPresent() && !student.isPresent()) {
throw new BadRequestException("Email or password is not corrected");
throw new BadRequestException(Constants.ERROR_CODE.AUTH_ERROR);
}
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,13 @@ public ResponseEntity<PageableData<CourseVM>> getPageableCourse (
return ResponseEntity.ok().body(pageableCourses);
}

@GetMapping("/courses/category/{categoryId}")
@GetMapping("/courses/category/{categoryName}")
public ResponseEntity<List<CourseListGetVM>> getPageableCourse (
@PathVariable("categoryId") Integer categoryId
@PathVariable("categoryName") String categoryName,
@RequestParam(value = "pageNum", defaultValue = Constants.PageableConstant.DEFAULT_PAGE_NUMBER) int pageNum,
@RequestParam(value = "pageSize", defaultValue = Constants.PageableConstant.DEFAULT_PAGE_SIZE) int pageSize
) {
List<CourseListGetVM> courseListGetVMS = courseService.getCoursesByCategoryId(categoryId);
List<CourseListGetVM> courseListGetVMS = courseService.getCoursesByCategory(categoryName, pageNum, pageSize);
return ResponseEntity.ok().body(courseListGetVMS);
}

Expand All @@ -62,21 +64,6 @@ public ResponseEntity<PageableData<CourseListGetVM>> getCoursesByMultiQueryWithP
return ResponseEntity.ok().body(pageableCourses);
}

// @GetMapping("/courses/search")
// public ResponseEntity<List<CourseListGetVM>> getCoursesByMultiQuery (
// @RequestParam(value = "pageNum", defaultValue = Constants.PageableConstant.DEFAULT_PAGE_NUMBER, required = false) int pageNum,
// @RequestParam(value = "pageSize", defaultValue = Constants.PageableConstant.DEFAULT_PAGE_SIZE, required = false) int pageSize,
// @RequestParam(value = "keyword", required = false) String keyword,
// @RequestParam(value = "ratingStar", required = false) Float rating,
// @RequestParam(value = "level", required = false) String[] level,
// @RequestParam(value = "free", required = false) Boolean[] free,
// @RequestParam(value = "categoryName", required = false) String categoryName,
// @RequestParam(value = "topicId", required = false) Integer topicId
// ) {
// List<CourseListGetVM> coursesByMultiQueryReturnList = courseService.getCoursesByMultiQueryReturnList(pageNum, pageSize, keyword, rating, level, free, categoryName, topicId);
// return ResponseEntity.ok().body(coursesByMultiQueryReturnList);
// }

@PostMapping("/admin/courses")
@ApiResponses(value = {
@ApiResponse(responseCode = "201", description = "Created", content =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,6 @@ List<Course> findByMultiQuery(
AND (:freeFlags IS NULL OR c.free IN :freeFlags)
AND (:categoryName IS NULL OR cat.name = :categoryName OR p.name = :categoryName)
AND (:topicId IS NULL OR t.id = :topicId)
AND c.status = 'PUBLISHED'
AND (
:ratingStar IS NULL OR
(SELECT COALESCE(AVG(r.ratingStar), 0)
Expand All @@ -292,7 +291,6 @@ SELECT COUNT(c)
AND (:freeFlags IS NULL OR c.free IN :freeFlags)
AND (:categoryName IS NULL OR cat.name = :categoryName OR p.name = :categoryName)
AND (:topicId IS NULL OR t.id = :topicId)
AND c.status = 'PUBLISHED'
AND (
:ratingStar IS NULL OR
(SELECT COALESCE(AVG(r.ratingStar), 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,11 @@ PageableData<CourseListGetVM> getCoursesByMultiQuery(int pageNum,
Integer topicId
);


List<CourseListGetVM> getCoursesByMultiQueryReturnList(int pageNum,
int pageSize,
String title,
Float rating,
String[] level,
Boolean[] free,
String categoryName,
Integer topicId
);

List<CourseListGetVM> getCoursesByCategoryId(Integer categoryId);

void delete(Long id);

void updateStatusCourse(CourseStatusPostVM courseStatusPostVM, Long courseId);

List<CourseAssignPromotion> getByPromotionId(Long promotionId);

List<CourseListGetVM> getCoursesByCategory(String categoryName, int pageNum, int pageSize);
}
Original file line number Diff line number Diff line change
Expand Up @@ -356,83 +356,15 @@ public PageableData<CourseListGetVM> getCoursesByMultiQuery(int pageNum,
Pageable pageable = PageRequest.of(pageNum, pageSize);
Page<Course> coursePage = courseRepository.findByMultiQueryWithKeyword(pageable, title, rating, level, free, categoryName, topicId) ;
List<Course> courses = coursePage.getContent();
List<CourseListGetVM> courseListGetVMS = courses.stream().map(course -> {
course = courseRepository.findByIdWithPromotions(course).orElseThrow(() -> new NotFoundException(Constants.ERROR_CODE.COURSE_NOT_FOUND));
List<Review> reviews = course.getReviews();
int ratingCount = reviews.size();
Double averageRating = reviews.stream().map(review -> review.getRatingStar()).mapToDouble(Integer::doubleValue).average().orElse(0.0);
double roundedAverageRating = Math.round(averageRating * 10) / 10.0;
AtomicInteger totalCurriculumCourse = new AtomicInteger();
AtomicInteger totalDurationCourse = new AtomicInteger();
List<Section> sections = sectionService.findByCourseId(course.getId());
sections.forEach(section -> {
List<Lecture> lectures = section.getLectures();
List<Quiz> quizzes = section.getQuizzes();
totalCurriculumCourse.addAndGet(lectures.size());
totalCurriculumCourse.addAndGet(quizzes.size());
int totalSeconds = lectures.stream()
.mapToInt(lecture -> lecture.getDuration())
.sum();
totalDurationCourse.addAndGet(totalSeconds);
});
String formattedHours = convertSeconds(totalDurationCourse.get());


Set<Promotion> promotions = course.getPromotions();
Long discountedPrice = getDiscountedPriceByCourse(promotions, course);
return CourseListGetVM.fromModel(course, formattedHours, totalCurriculumCourse.get(), roundedAverageRating, ratingCount, discountedPrice);
}).toList();
List<CourseListGetVM> courseListGetVMS = courses.stream().map(course -> getCourseListGetVMById(course.getId())).toList();
return new PageableData(
pageNum,
pageSize,
coursePage.getTotalElements(),coursePage.getTotalPages()
,
coursePage.getTotalElements(),coursePage.getTotalPages(),
courseListGetVMS
);
}

@Override
public List<CourseListGetVM> getCoursesByMultiQueryReturnList(int pageNum, int pageSize, String title, Float rating, String[] level, Boolean[] free, String categoryName, Integer topicId) {
log.info("received pageNum: {}, pageSize: {}, title: {}, rating: {}, level: {}, free: {}, categoryName: {}, " +
"topicId: {}", pageNum, pageSize, title, rating, level, free, categoryName, topicId);
List<Course> courses = title != null ? courseRepository.findByMultiQueryWithKeyword(title, rating, level, free, categoryName, topicId) :
courseRepository.findByMultiQuery(rating, level, free, categoryName, topicId);
List<CourseListGetVM> courseListGetVMS = courses.stream().map(course -> {
course = courseRepository.findByIdWithPromotions(course).orElseThrow(() -> new NotFoundException(Constants.ERROR_CODE.COURSE_NOT_FOUND));
List<Review> reviews = course.getReviews();
int ratingCount = reviews.size();
Double averageRating = reviews.stream().map(review -> review.getRatingStar()).mapToDouble(Integer::doubleValue).average().orElse(0.0);
double roundedAverageRating = Math.round(averageRating * 10) / 10.0;
AtomicInteger totalCurriculumCourse = new AtomicInteger();
AtomicInteger totalDurationCourse = new AtomicInteger();
List<Section> sections = sectionService.findByCourseId(course.getId());
sections.forEach(section -> {
List<Lecture> lectures = section.getLectures();
List<Quiz> quizzes = section.getQuizzes();
totalCurriculumCourse.addAndGet(lectures.size());
totalCurriculumCourse.addAndGet(quizzes.size());
int totalSeconds = lectures.stream()
.mapToInt(lecture -> lecture.getDuration())
.sum();
totalDurationCourse.addAndGet(totalSeconds);

});
Set<Promotion> promotions = course.getPromotions();
Long discountedPrice = getDiscountedPriceByCourse(promotions, course);
String formattedHours = convertSeconds(totalDurationCourse.get());
return CourseListGetVM.fromModel(course, formattedHours, totalCurriculumCourse.get(), roundedAverageRating, ratingCount, discountedPrice);
}).toList();
return courseListGetVMS;
}

@Override
public List<CourseListGetVM> getCoursesByCategoryId(Integer categoryId) {
log.info("received categoryId: {}", categoryId);
List<Course> courses = courseRepository.findByCategoryIdWithStatus(categoryId);
List<CourseListGetVM> courseListGetVMS = courses.stream().map(course -> getCourseListGetVMById(course.getId())).toList();
return courseListGetVMS;
}

@Override
public void delete(Long id) {
log.info("received courseId: {}", id);
Expand Down Expand Up @@ -485,6 +417,15 @@ public List<CourseAssignPromotion> getByPromotionId(Long promotionId) {
return courseAssignPromotions;
}

@Override
public List<CourseListGetVM> getCoursesByCategory(String categoryName, int pageNum, int pageSize) {
Pageable pageable = PageRequest.of(pageNum, pageSize);
Page<Course> coursePage = courseRepository.findByMultiQueryWithKeyword(pageable, null, null, null, null, categoryName, null) ;
List<Course> courses = coursePage.getContent();
List<CourseListGetVM> courseListGetVMS = courses.stream().map(course -> getCourseListGetVMById(course.getId())).toList();
return courseListGetVMS;
}

private String convertSeconds(int seconds) {
if (seconds < 3600) {
int minutes = seconds / 60;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.backend.elearning.exception;

import com.backend.elearning.utils.Constants;
import com.backend.elearning.utils.MessageUtil;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
Expand Down Expand Up @@ -40,7 +42,7 @@ public ResponseEntity<ErrorVm> handleBadRequestException(BadRequestException ex,

@ExceptionHandler(BadCredentialsException.class)
public ResponseEntity<ErrorVm> handleBadRequestException(BadCredentialsException ex, WebRequest request) {
String message = "Email hoặc mật khẩu không chính xác";
String message = Constants.ERROR_CODE.AUTH_ERROR;
ErrorVm errorVm = new ErrorVm(HttpStatus.BAD_REQUEST.toString(), HttpStatus.BAD_REQUEST.getReasonPhrase(), message);
return ResponseEntity.badRequest().body(errorVm);
}
Expand All @@ -67,8 +69,8 @@ protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotV
.stream()
.map(error -> error.getField() + " " + error.getDefaultMessage())
.toList();

ErrorVm errorVm = new ErrorVm(HttpStatus.BAD_REQUEST.toString(), HttpStatus.BAD_REQUEST.getReasonPhrase(), "Request information is not valid", errors);
String detail = Constants.ERROR_CODE.REQUEST_NOT_VALID;
ErrorVm errorVm = new ErrorVm(HttpStatus.BAD_REQUEST.toString(), HttpStatus.BAD_REQUEST.getReasonPhrase(), detail, errors);
return ResponseEntity.badRequest().body(errorVm);
}

Expand All @@ -79,7 +81,8 @@ public ResponseEntity<Object> handleConstraintViolation(ConstraintViolationExcep
errors.add(violation.getRootBeanClass().getName() + " " +
violation.getPropertyPath() + ": " + violation.getMessage());
}
ErrorVm errorVm = new ErrorVm(HttpStatus.BAD_REQUEST.toString(), HttpStatus.BAD_REQUEST.getReasonPhrase(), "Request information is not valid", errors);
String detail = Constants.ERROR_CODE.REQUEST_NOT_VALID;
ErrorVm errorVm = new ErrorVm(HttpStatus.BAD_REQUEST.toString(), HttpStatus.BAD_REQUEST.getReasonPhrase(), detail, errors);
return ResponseEntity.badRequest().body(errorVm);
}

Expand All @@ -93,25 +96,23 @@ private String getServletPath(WebRequest webRequest) {
public ResponseEntity<ErrorVm> handleHttpMessageNotReadable(HttpMessageNotReadableException ex) {
Throwable cause = ex.getCause();

// Handle Enum Deserialization specifically
if (cause instanceof InvalidFormatException ife && ife.getTargetType().isEnum()) {
String field = ife.getPath().isEmpty() ? "unknown" : ife.getPath().get(0).getFieldName();
String rejectedValue = String.valueOf(ife.getValue());
String enumName = ife.getTargetType().getSimpleName();
Object[] allowed = ife.getTargetType().getEnumConstants();

List<String> fieldErrors = new ArrayList<>();
fieldErrors.add(
String.format("Field '%s': invalid value '%s'. Expected one of: %s",
field,
rejectedValue,
String.join(", ", java.util.Arrays.stream(allowed).map(Object::toString).toList()))
);
String message = MessageUtil.getMessage(Constants.ERROR_CODE.FIELD_INVALID_VALUE,field,
rejectedValue,
String.join(", ", java.util.Arrays.stream(allowed).map(Object::toString).toList()));
fieldErrors.add(message);

String detail = MessageUtil.getMessage(Constants.ERROR_CODE.ENUM_VALUE_NOT_VALID,field);
ErrorVm error = new ErrorVm(
String.valueOf(HttpStatus.BAD_REQUEST.value()),
HttpStatus.BAD_REQUEST.getReasonPhrase(),
"Invalid enum value for field '" + field + "'",
detail,
fieldErrors
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

public class BadRequestException extends RuntimeException{
private String message;


public BadRequestException(String errorCode, Object... var2) {
this.message = MessageUtil.getMessage(errorCode, var2);
}
Expand Down
14 changes: 5 additions & 9 deletions src/main/java/com/backend/elearning/utils/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ public final class AUTH {
}

public final class ERROR_CODE {

public static final String REQUEST_NOT_VALID = "REQUEST_NOT_VALID";
public static final String USER_NOT_FOUND = "USER_NOT_FOUND";
public static final String CART_NOT_FOUND = "CART_NOT_FOUND";

public static final String ORDER_NOT_FOUND = "ORDER_NOT_FOUND";
public static final String CATEGORY_NOT_FOUND = "CATEGORY_NOT_FOUND";
public static final String QUESTION_NOT_FOUND = "QUESTION_NOT_FOUND";
Expand All @@ -23,7 +22,6 @@ public final class ERROR_CODE {
public static final String COURSE_HAD_CART = "COURSE_HAD_CART";
public static final String COURSE_HAD_BEEN_BOUGHT = "COURSE_HAD_BEEN_BOUGHT";
public static final String NOTE_NOT_FOUND = "NOTE_NOT_FOUND";

public static final String LECTURE_NOT_FOUND = "LECTURE_NOT_FOUND";
public static final String SECTION_NOT_FOUND = "SECTION_NOT_FOUND";
public static final String QUIZ_NOT_FOUND = "QUIZ_NOT_FOUND";
Expand All @@ -35,22 +33,20 @@ public final class ERROR_CODE {
public static final String TOPIC_NAME_DUPLICATED = "TOPIC_NAME_DUPLICATED";
public static final String COURSE_TITLE_DUPLICATED = "COURSE_TITLE_DUPLICATED";
public static final String USER_EMAIL_DUPLICATED = "USER_EMAIL_DUPLICATED";

public static final String STUDENT_EMAIL_DUPLICATED = "STUDENT_EMAIL_DUPLICATED";

public static final String COUPON_CODE_DUPLICATED = "COUPON_CODE_DUPLICATED";

public static final String LECTURE_SECTION_NOT_SAME = "LECTURE_SECTION_NOT_SAME";

public static final String COUPON_NOT_FOUND = "COUPON_NOT_FOUND";
public static final String COUPON_IS_EXPIRED = "COUPON_IS_EXPIRED";

public static final String AUTH_ERROR = "AUTH_ERROR";
public static final String FIELD_INVALID_VALUE = "FIELD_INVALID_VALUE";
public static final String ENUM_VALUE_NOT_VALID = "ENUM_VALUE_NOT_VALID";


}

public final class PageableConstant {
public static final String DEFAULT_PAGE_SIZE = "5";
public static final String DEFAULT_PAGE_SIZE = "10";

public static final String DEFAULT_COURSE_PAGE_SIZE = "5";
public static final String DEFAULT_PAGE_SIZE_REVIEW = "4";
Expand Down
Loading
Loading