feat: add 3 components: course, lesson, section
This commit is contained in:
@@ -1,40 +1,69 @@
|
||||
package aplp.backend.lms.common.api;
|
||||
|
||||
import aplp.backend.core.common.exception.ErrorCode;
|
||||
import aplp.backend.core.common.exception.ResourceNotFoundException;
|
||||
import aplp.backend.core.common.response.ErrorResponse;
|
||||
import aplp.backend.core.common.response.ApiResponse;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(ResourceNotFoundException.class)
|
||||
public ResponseEntity<ErrorResponse> handleNotFound(
|
||||
public ResponseEntity<ApiResponse<?>> handleNotFound(
|
||||
ResourceNotFoundException ex
|
||||
) {
|
||||
return ResponseEntity
|
||||
.status(HttpStatus.NOT_FOUND)
|
||||
.body(new ErrorResponse(
|
||||
HttpStatus.NOT_FOUND.value(),
|
||||
ex.getMessage(),
|
||||
LocalDateTime.now()
|
||||
.body(ApiResponse.error(
|
||||
HttpStatus.NOT_FOUND.name(),
|
||||
ex.getMessage()
|
||||
));
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseEntity<ErrorResponse> handleException(
|
||||
public ResponseEntity<ApiResponse<?>> handleException(
|
||||
Exception ex
|
||||
) {
|
||||
return ResponseEntity
|
||||
.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(new ErrorResponse(
|
||||
HttpStatus.INTERNAL_SERVER_ERROR.value(),
|
||||
"Internal server error",
|
||||
LocalDateTime.now()
|
||||
.body(ApiResponse.error(
|
||||
ErrorCode.INTERNAL_ERROR.name(),
|
||||
"Internal server error"
|
||||
));
|
||||
}
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ResponseEntity<ApiResponse<?>> handleValidation(
|
||||
MethodArgumentNotValidException ex
|
||||
) {
|
||||
String message = ex.getBindingResult().getFieldErrors().stream()
|
||||
.map(e -> e.getField() + ": " + e.getDefaultMessage())
|
||||
.sorted()
|
||||
.collect(Collectors.joining("; "));
|
||||
return ResponseEntity
|
||||
.badRequest()
|
||||
.body(ApiResponse.error(
|
||||
ErrorCode.VALIDATION_FAILED.name(),
|
||||
message
|
||||
));
|
||||
}
|
||||
|
||||
@ExceptionHandler(DataIntegrityViolationException.class)
|
||||
public ResponseEntity<ApiResponse<?>> handleConflict(
|
||||
DataIntegrityViolationException ex
|
||||
) {
|
||||
return ResponseEntity
|
||||
.status(HttpStatus.CONFLICT)
|
||||
.body(ApiResponse.error(
|
||||
HttpStatus.CONFLICT.name(),
|
||||
"Duplicate or invalid data: " + ex.getMostSpecificCause().getMessage()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +1,51 @@
|
||||
package aplp.backend.lms.course.api.controller;
|
||||
|
||||
import aplp.backend.core.common.response.ApiResponse;
|
||||
import aplp.backend.lms.course.application.dtos.CourseReqDto;
|
||||
import aplp.backend.lms.course.application.dtos.CourseResDto;
|
||||
import aplp.backend.lms.course.application.services.CourseService;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/courses")
|
||||
@RequiredArgsConstructor
|
||||
public class CourseController {
|
||||
|
||||
private final CourseService courseService;
|
||||
|
||||
public CourseController(CourseService courseService) {
|
||||
this.courseService = courseService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public List<CourseResDto> getAll() {
|
||||
return courseService.getAll();
|
||||
public ResponseEntity<ApiResponse<List<CourseResDto>>> getAll() {
|
||||
return ResponseEntity.ok(ApiResponse.ok(courseService.getAll()));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public CourseResDto getById(@PathVariable Long id) {
|
||||
return courseService.getById(id);
|
||||
public ResponseEntity<ApiResponse<CourseResDto>> getById(@PathVariable Long id) {
|
||||
return ResponseEntity.ok(ApiResponse.ok(courseService.getById(id)));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public CourseResDto create(@Valid @RequestBody CourseReqDto request) {
|
||||
return courseService.create(request);
|
||||
public ResponseEntity<ApiResponse<CourseResDto>> create(@Valid @RequestBody CourseReqDto request) {
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(ApiResponse.ok(courseService.create(request)));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public CourseResDto update(
|
||||
public ResponseEntity<ApiResponse<CourseResDto>> update(
|
||||
@PathVariable Long id,
|
||||
@RequestBody CourseReqDto request) {
|
||||
return courseService.update(id, request);
|
||||
return ResponseEntity.ok(ApiResponse.ok(courseService.update(id, request)));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
public void delete(@PathVariable Long id) {
|
||||
public ResponseEntity<ApiResponse<Void>> delete(@PathVariable Long id) {
|
||||
courseService.delete(id);
|
||||
return ResponseEntity.ok(ApiResponse.ok("Deleted successfully", null));
|
||||
}
|
||||
}
|
||||
@@ -4,15 +4,19 @@ import aplp.backend.lms.course.application.dtos.CourseReqDto;
|
||||
import aplp.backend.lms.course.application.dtos.CourseResDto;
|
||||
import aplp.backend.lms.course.domain.entities.Course;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.MappingTarget;
|
||||
|
||||
@Mapper(componentModel = "spring")
|
||||
public interface CourseMapper {
|
||||
|
||||
@Mapping(target = "isPublished", source = "published")
|
||||
CourseResDto toResponse(Course course);
|
||||
|
||||
@Mapping(target = "published", source = "isPublished")
|
||||
Course toEntity(CourseReqDto request);
|
||||
|
||||
@Mapping(target = "published", source = "isPublished")
|
||||
void updateEntity(
|
||||
CourseReqDto request,
|
||||
@MappingTarget Course course
|
||||
|
||||
@@ -6,18 +6,21 @@ import aplp.backend.lms.course.application.dtos.CourseResDto;
|
||||
import aplp.backend.lms.course.application.mappers.CourseMapper;
|
||||
import aplp.backend.lms.course.domain.entities.Course;
|
||||
import aplp.backend.lms.course.domain.repositories.CourseRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class CourseService {
|
||||
|
||||
private final CourseRepository courseRepository;
|
||||
private final CourseMapper courseMapper;
|
||||
|
||||
public CourseService(CourseRepository courseRepository, CourseMapper courseMapper) {
|
||||
this.courseRepository = courseRepository;
|
||||
this.courseMapper = courseMapper;
|
||||
}
|
||||
|
||||
public List<CourseResDto> getAll() {
|
||||
return courseRepository.findAll()
|
||||
.stream()
|
||||
@@ -47,6 +50,8 @@ public class CourseService {
|
||||
}
|
||||
|
||||
public void delete(Long id) {
|
||||
courseRepository.findById(id)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Course not found"));
|
||||
courseRepository.deleteById(id);
|
||||
}
|
||||
}
|
||||
|
||||
+4
-2
@@ -2,17 +2,19 @@ package aplp.backend.lms.course.infrastructure.persistence;
|
||||
|
||||
import aplp.backend.lms.course.domain.entities.Course;
|
||||
import aplp.backend.lms.course.domain.repositories.CourseRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
@RequiredArgsConstructor
|
||||
public class CourseRepositoryImpl implements CourseRepository {
|
||||
private final CourseJpaRepository repository;
|
||||
|
||||
public CourseRepositoryImpl(CourseJpaRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Course> findById(Long id) {
|
||||
return repository.findById(id);
|
||||
|
||||
@@ -1,48 +1,51 @@
|
||||
package aplp.backend.lms.lesson.api.controller;
|
||||
|
||||
import aplp.backend.core.common.response.ApiResponse;
|
||||
import aplp.backend.lms.lesson.application.dtos.LessonReqDto;
|
||||
import aplp.backend.lms.lesson.application.dtos.LessonResDto;
|
||||
import aplp.backend.lms.lesson.application.services.LessonService;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/lessons")
|
||||
@RequiredArgsConstructor
|
||||
public class LessonController {
|
||||
|
||||
private final LessonService lessonService;
|
||||
|
||||
public LessonController(LessonService lessonService) {
|
||||
this.lessonService = lessonService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public List<LessonResDto> getAll() {
|
||||
return lessonService.getAll();
|
||||
public ResponseEntity<ApiResponse<List<LessonResDto>>> getAll() {
|
||||
return ResponseEntity.ok(ApiResponse.ok(lessonService.getAll()));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public LessonResDto getById(@PathVariable Long id) {
|
||||
return lessonService.getById(id);
|
||||
public ResponseEntity<ApiResponse<LessonResDto>> getById(@PathVariable Long id) {
|
||||
return ResponseEntity.ok(ApiResponse.ok(lessonService.getById(id)));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public LessonResDto create(@Valid @RequestBody LessonReqDto request) {
|
||||
return lessonService.create(request);
|
||||
public ResponseEntity<ApiResponse<LessonResDto>> create(@Valid @RequestBody LessonReqDto request) {
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(ApiResponse.ok(lessonService.create(request)));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public LessonResDto update(
|
||||
public ResponseEntity<ApiResponse<LessonResDto>> update(
|
||||
@PathVariable Long id,
|
||||
@Valid @RequestBody LessonReqDto request) {
|
||||
return lessonService.update(id, request);
|
||||
return ResponseEntity.ok(ApiResponse.ok(lessonService.update(id, request)));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
public void delete(@PathVariable Long id) {
|
||||
public ResponseEntity<ApiResponse<Void>> delete(@PathVariable Long id) {
|
||||
lessonService.delete(id);
|
||||
return ResponseEntity.ok(ApiResponse.ok("Deleted successfully", null));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,11 +12,14 @@ import org.mapstruct.MappingTarget;
|
||||
public interface LessonMapper {
|
||||
|
||||
@Mapping(source = "course.id", target = "courseId")
|
||||
@Mapping(target = "isPublished", source = "published")
|
||||
LessonResDto toResponse(Lesson lesson);
|
||||
|
||||
@Mapping(source = "courseId", target = "course")
|
||||
@Mapping(target = "published", source = "isPublished")
|
||||
Lesson toEntity(LessonReqDto request);
|
||||
|
||||
@Mapping(target = "published", source = "isPublished")
|
||||
void updateEntity(
|
||||
LessonReqDto request,
|
||||
@MappingTarget Lesson lesson
|
||||
|
||||
@@ -6,18 +6,21 @@ import aplp.backend.lms.lesson.application.dtos.LessonResDto;
|
||||
import aplp.backend.lms.lesson.application.mappers.LessonMapper;
|
||||
import aplp.backend.lms.lesson.domain.entities.Lesson;
|
||||
import aplp.backend.lms.lesson.domain.repositories.LessonRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class LessonService {
|
||||
|
||||
private final LessonRepository lessonRepository;
|
||||
private final LessonMapper lessonMapper;
|
||||
|
||||
public LessonService(LessonRepository lessonRepository, LessonMapper lessonMapper) {
|
||||
this.lessonRepository = lessonRepository;
|
||||
this.lessonMapper = lessonMapper;
|
||||
}
|
||||
|
||||
public List<LessonResDto> getAll() {
|
||||
return lessonRepository.findAll()
|
||||
.stream()
|
||||
@@ -47,6 +50,8 @@ public class LessonService {
|
||||
}
|
||||
|
||||
public void delete(Long id) {
|
||||
lessonRepository.findById(id)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Lesson not found"));
|
||||
lessonRepository.deleteById(id);
|
||||
}
|
||||
}
|
||||
|
||||
+4
-2
@@ -2,17 +2,19 @@ package aplp.backend.lms.lesson.infrastructure.persistence;
|
||||
|
||||
import aplp.backend.lms.lesson.domain.entities.Lesson;
|
||||
import aplp.backend.lms.lesson.domain.repositories.LessonRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
@RequiredArgsConstructor
|
||||
public class LessonRepositoryImpl implements LessonRepository {
|
||||
private final LessonJpaRepository repository;
|
||||
|
||||
public LessonRepositoryImpl(LessonJpaRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Lesson> findById(Long id) {
|
||||
return repository.findById(id);
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package aplp.backend.lms.section.api.controller;
|
||||
|
||||
import aplp.backend.core.common.response.ApiResponse;
|
||||
import aplp.backend.lms.section.application.dtos.SectionReqDto;
|
||||
import aplp.backend.lms.section.application.dtos.SectionResDto;
|
||||
import aplp.backend.lms.section.application.services.SectionService;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/sections")
|
||||
public class SectionController {
|
||||
|
||||
private final SectionService sectionService;
|
||||
|
||||
public SectionController(SectionService sectionService) {
|
||||
this.sectionService = sectionService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<ApiResponse<List<SectionResDto>>> getAll() {
|
||||
return ResponseEntity.ok(ApiResponse.ok(sectionService.getAll()));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<ApiResponse<SectionResDto>> getById(@PathVariable Long id) {
|
||||
return ResponseEntity.ok(ApiResponse.ok(sectionService.getById(id)));
|
||||
}
|
||||
|
||||
@GetMapping("/by-lesson/{lessonId}")
|
||||
public ResponseEntity<ApiResponse<List<SectionResDto>>> getByLessonId(@PathVariable Long lessonId) {
|
||||
return ResponseEntity.ok(ApiResponse.ok(sectionService.getByLessonId(lessonId)));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<ApiResponse<SectionResDto>> create(@Valid @RequestBody SectionReqDto request) {
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(ApiResponse.ok(sectionService.create(request)));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<ApiResponse<SectionResDto>> update(
|
||||
@PathVariable Long id,
|
||||
@Valid @RequestBody SectionReqDto request) {
|
||||
return ResponseEntity.ok(ApiResponse.ok(sectionService.update(id, request)));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<ApiResponse<Void>> delete(@PathVariable Long id) {
|
||||
sectionService.delete(id);
|
||||
return ResponseEntity.ok(ApiResponse.ok("Deleted successfully", null));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package aplp.backend.lms.section.application.dtos;
|
||||
|
||||
import aplp.backend.lms.section.domain.enums.SectionType;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
public record SectionReqDto(
|
||||
@NotNull(message = "Lesson ID is required")
|
||||
Long lessonId,
|
||||
|
||||
@NotBlank(message = "Title is required")
|
||||
@Size(max = 255, message = "Title must not exceed 255 characters")
|
||||
String title,
|
||||
|
||||
@NotNull(message = "Type is required")
|
||||
SectionType type,
|
||||
|
||||
@NotNull(message = "Position is required")
|
||||
@Min(value = 1, message = "Position must be at least 1")
|
||||
Integer position,
|
||||
|
||||
String content,
|
||||
|
||||
Integer durationSeconds,
|
||||
|
||||
Boolean isFreePreview
|
||||
) {}
|
||||
@@ -0,0 +1,34 @@
|
||||
package aplp.backend.lms.section.application.dtos;
|
||||
|
||||
import aplp.backend.lms.section.domain.entities.Section;
|
||||
import aplp.backend.lms.section.domain.enums.SectionType;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public record SectionResDto(
|
||||
Long id,
|
||||
Long lessonId,
|
||||
String title,
|
||||
SectionType type,
|
||||
int position,
|
||||
String content,
|
||||
int durationSeconds,
|
||||
boolean isFreePreview,
|
||||
LocalDateTime createdAt,
|
||||
LocalDateTime updatedAt
|
||||
) {
|
||||
public static SectionResDto from(Section section) {
|
||||
return new SectionResDto(
|
||||
section.getId(),
|
||||
section.getLesson().getId(),
|
||||
section.getTitle(),
|
||||
section.getType(),
|
||||
section.getPosition(),
|
||||
section.getContent(),
|
||||
section.getDurationSeconds(),
|
||||
section.isFreePreview(),
|
||||
section.getCreatedAt(),
|
||||
section.getUpdatedAt()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package aplp.backend.lms.section.application.mappers;
|
||||
|
||||
import aplp.backend.lms.lesson.domain.entities.Lesson;
|
||||
import aplp.backend.lms.section.application.dtos.SectionReqDto;
|
||||
import aplp.backend.lms.section.application.dtos.SectionResDto;
|
||||
import aplp.backend.lms.section.domain.entities.Section;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.MappingTarget;
|
||||
|
||||
@Mapper(componentModel = "spring")
|
||||
public interface SectionMapper {
|
||||
|
||||
@Mapping(source = "lesson.id", target = "lessonId")
|
||||
SectionResDto toResponse(Section section);
|
||||
|
||||
@Mapping(source = "lessonId", target = "lesson")
|
||||
Section toEntity(SectionReqDto request);
|
||||
|
||||
void updateEntity(
|
||||
SectionReqDto request,
|
||||
@MappingTarget Section section
|
||||
);
|
||||
|
||||
default Lesson mapLesson(Long lessonId) {
|
||||
if (lessonId == null) return null;
|
||||
Lesson lesson = new Lesson();
|
||||
lesson.setId(lessonId);
|
||||
return lesson;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package aplp.backend.lms.section.application.services;
|
||||
|
||||
import aplp.backend.core.common.exception.ResourceNotFoundException;
|
||||
import aplp.backend.lms.section.application.dtos.SectionReqDto;
|
||||
import aplp.backend.lms.section.application.dtos.SectionResDto;
|
||||
import aplp.backend.lms.section.application.mappers.SectionMapper;
|
||||
import aplp.backend.lms.section.domain.entities.Section;
|
||||
import aplp.backend.lms.section.domain.repositories.SectionRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class SectionService {
|
||||
|
||||
private final SectionRepository sectionRepository;
|
||||
private final SectionMapper sectionMapper;
|
||||
|
||||
public SectionService(SectionRepository sectionRepository, SectionMapper sectionMapper) {
|
||||
this.sectionRepository = sectionRepository;
|
||||
this.sectionMapper = sectionMapper;
|
||||
}
|
||||
|
||||
public List<SectionResDto> getAll() {
|
||||
return sectionRepository.findAll()
|
||||
.stream()
|
||||
.map(SectionResDto::from)
|
||||
.toList();
|
||||
}
|
||||
|
||||
public SectionResDto getById(Long id) {
|
||||
Section section = sectionRepository.findById(id)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Section not found"));
|
||||
return SectionResDto.from(section);
|
||||
}
|
||||
|
||||
public List<SectionResDto> getByLessonId(Long lessonId) {
|
||||
return sectionRepository.findByLessonId(lessonId)
|
||||
.stream()
|
||||
.map(SectionResDto::from)
|
||||
.toList();
|
||||
}
|
||||
|
||||
public SectionResDto create(SectionReqDto request) {
|
||||
Section section = sectionMapper.toEntity(request);
|
||||
return sectionMapper.toResponse(sectionRepository.save(section));
|
||||
}
|
||||
|
||||
public SectionResDto update(Long id, SectionReqDto request) {
|
||||
Section section = sectionRepository.findById(id)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Section not found"));
|
||||
sectionMapper.updateEntity(request, section);
|
||||
return sectionMapper.toResponse(sectionRepository.save(section));
|
||||
}
|
||||
|
||||
public void delete(Long id) {
|
||||
sectionRepository.findById(id)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Section not found"));
|
||||
sectionRepository.deleteById(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package aplp.backend.lms.section.domain.entities;
|
||||
|
||||
import aplp.backend.core.common.entity.BaseEntity;
|
||||
import aplp.backend.lms.lesson.domain.entities.Lesson;
|
||||
import aplp.backend.lms.section.domain.enums.SectionType;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.hibernate.annotations.JdbcTypeCode;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
|
||||
@Entity
|
||||
@Table(name = "sections")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class Section extends BaseEntity {
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "lesson_id", nullable = false)
|
||||
private Lesson lesson;
|
||||
|
||||
@Column(nullable = false, length = 255)
|
||||
private String title;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false)
|
||||
private SectionType type;
|
||||
|
||||
@Column(nullable = false)
|
||||
private int position;
|
||||
|
||||
@JdbcTypeCode(SqlTypes.JSON)
|
||||
@Column(columnDefinition = "jsonb")
|
||||
private String content;
|
||||
|
||||
@Column(name = "duration_seconds")
|
||||
private int durationSeconds;
|
||||
|
||||
@Column(name = "is_free_preview", nullable = false)
|
||||
private boolean isFreePreview;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package aplp.backend.lms.section.domain.enums;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
public enum SectionType {
|
||||
TEXT("text"),
|
||||
VIDEO("video"),
|
||||
QUIZ("quiz"),
|
||||
EMBED("embed");
|
||||
|
||||
private final String value;
|
||||
|
||||
SectionType(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@JsonValue
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package aplp.backend.lms.section.domain.repositories;
|
||||
|
||||
import aplp.backend.lms.section.domain.entities.Section;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface SectionRepository {
|
||||
|
||||
Optional<Section> findById(Long id);
|
||||
|
||||
List<Section> findAll();
|
||||
|
||||
List<Section> findByLessonId(Long lessonId);
|
||||
|
||||
Section save(Section section);
|
||||
|
||||
void deleteById(Long id);
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package aplp.backend.lms.section.infrastructure.persistence;
|
||||
|
||||
import aplp.backend.lms.section.domain.entities.Section;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface SectionJpaRepository extends JpaRepository<Section, Long> {
|
||||
List<Section> findByLessonIdOrderByPositionAsc(Long lessonId);
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package aplp.backend.lms.section.infrastructure.persistence;
|
||||
|
||||
import aplp.backend.lms.section.domain.entities.Section;
|
||||
import aplp.backend.lms.section.domain.repositories.SectionRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public class SectionRepositoryImpl implements SectionRepository {
|
||||
private final SectionJpaRepository repository;
|
||||
|
||||
public SectionRepositoryImpl(SectionJpaRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Section> findById(Long id) {
|
||||
return repository.findById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Section> findAll() {
|
||||
return repository.findAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Section> findByLessonId(Long lessonId) {
|
||||
return repository.findByLessonIdOrderByPositionAsc(lessonId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Section save(Section section) {
|
||||
return repository.save(section);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteById(Long id) {
|
||||
repository.deleteById(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
-- Flyway Migration V3: Update courses table
|
||||
-- Add course_code, slug, image, is_published
|
||||
|
||||
ALTER TABLE courses ADD COLUMN course_code VARCHAR(50);
|
||||
ALTER TABLE courses ADD COLUMN slug VARCHAR(255);
|
||||
ALTER TABLE courses ADD COLUMN image VARCHAR(500);
|
||||
ALTER TABLE courses ADD COLUMN is_published BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
ALTER TABLE courses ADD CONSTRAINT uq_course_code UNIQUE (course_code);
|
||||
ALTER TABLE courses ADD CONSTRAINT uq_course_slug UNIQUE (slug);
|
||||
|
||||
UPDATE courses SET slug = LOWER(REPLACE(REPLACE(TRIM(title), ' ', '-'), '.', ''));
|
||||
UPDATE courses SET course_code = 'CRS-' || id;
|
||||
|
||||
ALTER TABLE courses ALTER COLUMN course_code SET NOT NULL;
|
||||
ALTER TABLE courses ALTER COLUMN slug SET NOT NULL;
|
||||
@@ -0,0 +1,26 @@
|
||||
-- Flyway Migration V4: Update lessons table
|
||||
-- Add course_id FK, slug, position, is_published; remove lesson_code
|
||||
|
||||
ALTER TABLE lessons ADD COLUMN course_id BIGINT;
|
||||
ALTER TABLE lessons ADD COLUMN slug VARCHAR(255);
|
||||
ALTER TABLE lessons ADD COLUMN position INT;
|
||||
ALTER TABLE lessons ADD COLUMN is_published BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
ALTER TABLE lessons RENAME COLUMN lesson_name TO title;
|
||||
|
||||
UPDATE lessons SET slug = LOWER(REPLACE(REPLACE(TRIM(title), ' ', '-'), '.', ''));
|
||||
|
||||
ALTER TABLE lessons ALTER COLUMN course_id SET NOT NULL;
|
||||
ALTER TABLE lessons ALTER COLUMN slug SET NOT NULL;
|
||||
ALTER TABLE lessons ALTER COLUMN position SET NOT NULL;
|
||||
|
||||
ALTER TABLE lessons ADD CONSTRAINT fk_lesson_course
|
||||
FOREIGN KEY (course_id) REFERENCES courses(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE lessons ADD CONSTRAINT uq_course_lesson_position UNIQUE (course_id, position);
|
||||
ALTER TABLE lessons ADD CONSTRAINT uq_course_lesson_slug UNIQUE (course_id, slug);
|
||||
|
||||
ALTER TABLE lessons DROP CONSTRAINT IF EXISTS lessons_lesson_code_key;
|
||||
ALTER TABLE lessons DROP COLUMN lesson_code;
|
||||
|
||||
CREATE INDEX idx_lessons_course_id ON lessons(course_id);
|
||||
@@ -0,0 +1,23 @@
|
||||
-- Flyway Migration V5: Create sections table
|
||||
|
||||
CREATE TYPE section_type AS ENUM ('text', 'video', 'quiz', 'embed');
|
||||
|
||||
CREATE TABLE sections (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
lesson_id BIGINT NOT NULL REFERENCES lessons(id) ON DELETE CASCADE,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
type section_type NOT NULL DEFAULT 'text',
|
||||
position INT NOT NULL,
|
||||
content JSONB,
|
||||
duration_seconds INT DEFAULT 0,
|
||||
is_free_preview BOOLEAN NOT NULL DEFAULT false,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
created_by VARCHAR(255),
|
||||
updated_by VARCHAR(255),
|
||||
deleted_at TIMESTAMPTZ,
|
||||
deleted_by VARCHAR(255),
|
||||
CONSTRAINT uq_lesson_section_position UNIQUE (lesson_id, position)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_sections_lesson_id ON sections(lesson_id);
|
||||
Reference in New Issue
Block a user