This commit is contained in:
2026-08-12 23:38:59 +07:00
parent 9d7ad0759e
commit 4060d99bbe
21 changed files with 1074 additions and 0 deletions
@@ -0,0 +1,12 @@
package aplp.backend.lms;
import org.springframework.boot.SpringApplication;
@org.springframework.boot.autoconfigure.SpringBootApplication
public class LmsApplication {
public static void main(String[] args) {
SpringApplication.run(LmsApplication.class, args);
}
}
@@ -0,0 +1,40 @@
package aplp.backend.lms.api.common.exception;
import aplp.backend.core.common.exception.ResourceNotFoundException;
import aplp.backend.core.common.response.ErrorResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.time.LocalDateTime;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(
ResourceNotFoundException ex
) {
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(new ErrorResponse(
HttpStatus.NOT_FOUND.value(),
ex.getMessage(),
LocalDateTime.now()
));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(
Exception ex
) {
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ErrorResponse(
HttpStatus.INTERNAL_SERVER_ERROR.value(),
"Internal server error",
LocalDateTime.now()
));
}
}
@@ -0,0 +1,48 @@
package aplp.backend.lms.api.controllers;
import aplp.backend.lms.application.dtos.CourseReqDto;
import aplp.backend.lms.application.dtos.CourseResDto;
import aplp.backend.lms.application.services.CourseService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/courses")
@RequiredArgsConstructor
public class CourseController {
private final CourseService courseService;
@GetMapping
public List<CourseResDto> getAll() {
return courseService.getAll();
}
@GetMapping("/{id}")
public CourseResDto getById(@PathVariable Long id) {
return courseService.getById(id);
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public CourseResDto create(@Valid @RequestBody CourseReqDto request) {
return courseService.create(request);
}
@PutMapping("/{id}")
public CourseResDto update(
@PathVariable Long id,
@RequestBody CourseReqDto request) {
return courseService.update(id, request);
}
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void delete(@PathVariable Long id) {
courseService.delete(id);
}
}
@@ -0,0 +1,6 @@
package aplp.backend.lms.application.dtos;
public record CourseReqDto(
String title,
String description
) {}
@@ -0,0 +1,17 @@
package aplp.backend.lms.application.dtos;
import aplp.backend.lms.domain.entities.Course;
public record CourseResDto(
Long id,
String title,
String description
) {
public static CourseResDto from(Course course) {
return new CourseResDto(
course.getId(),
course.getTitle(),
course.getDescription()
);
}
}
@@ -0,0 +1,20 @@
package aplp.backend.lms.application.mappers;
import aplp.backend.lms.application.dtos.CourseReqDto;
import aplp.backend.lms.application.dtos.CourseResDto;
import aplp.backend.lms.domain.entities.Course;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
@Mapper(componentModel = "spring")
public interface CourseMapper {
CourseResDto toResponse(Course course);
Course toEntity(CourseReqDto request);
void updateEntity(
CourseReqDto request,
@MappingTarget Course course
);
}
@@ -0,0 +1,52 @@
package aplp.backend.lms.application.services;
import aplp.backend.core.common.exception.ResourceNotFoundException;
import aplp.backend.lms.application.dtos.CourseReqDto;
import aplp.backend.lms.application.dtos.CourseResDto;
import aplp.backend.lms.application.mappers.CourseMapper;
import aplp.backend.lms.domain.entities.Course;
import aplp.backend.lms.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 List<CourseResDto> getAll() {
return courseRepository.findAll()
.stream()
.map(CourseResDto::from)
.toList();
}
public CourseResDto getById(Long id) {
Course course = courseRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Course not found"));
return CourseResDto.from(course);
}
public CourseResDto create(CourseReqDto request) {
Course course = courseMapper.toEntity(request);
return courseMapper.toResponse(courseRepository.save(course));
}
public CourseResDto update(Long id, CourseReqDto request) {
Course course = courseRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Course not found"));
courseMapper.updateEntity(request, course);
return courseMapper.toResponse(courseRepository.save(course));
}
public void delete(Long id) {
courseRepository.deleteById(id);
}
}
@@ -0,0 +1,24 @@
package aplp.backend.lms.domain.entities;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Entity
@Table(name = "courses")
@Getter
@Setter
@NoArgsConstructor
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 200)
private String title;
@Column(length = 1000)
private String description;
}
@@ -0,0 +1,19 @@
package aplp.backend.lms.domain.repositories;
import aplp.backend.lms.domain.entities.Course;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface CourseRepository {
Optional<Course> findById(Long id);
List<Course> findAll();
Course save(Course course);
void deleteById(Long id);
}
@@ -0,0 +1,28 @@
package aplp.backend.lms.infrastructure.config;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
import io.swagger.v3.oas.annotations.info.Info;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.security.SecurityScheme;
import org.springframework.context.annotation.Configuration;
@Configuration
@OpenAPIDefinition(
info = @Info(
title = "APLP LMS API",
version = "v1",
description = "APLP Learning Management System API"
),
security = {
@SecurityRequirement(name = "bearerAuth")
}
)
@SecurityScheme(
name = "bearerAuth",
type = SecuritySchemeType.HTTP,
bearerFormat = "JWT",
scheme = "bearer"
)
public class OpenApiConfig {
}
@@ -0,0 +1,25 @@
package aplp.backend.lms.infrastructure.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(
HttpSecurity http
) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.anyRequest().permitAll()
);
return http.build();
}
}
@@ -0,0 +1,9 @@
package aplp.backend.lms.infrastructure.persistence.repositories;
import aplp.backend.lms.domain.entities.Course;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface CourseJpaRepository extends JpaRepository<Course, Long> {
}
@@ -0,0 +1,35 @@
package aplp.backend.lms.infrastructure.persistence.repositories;
import aplp.backend.lms.domain.entities.Course;
import aplp.backend.lms.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;
@Override
public Optional<Course> findById(Long id) {
return repository.findById(id);
}
@Override
public List<Course> findAll() {
return repository.findAll();
}
@Override
public Course save(Course course) {
return repository.save(course);
}
@Override
public void deleteById(Long id) {
repository.deleteById(id);
}
}
+27
View File
@@ -0,0 +1,27 @@
spring:
application:
name: lms
datasource:
url: jdbc:postgresql://localhost:5432/aplp
username: postgres
password: Pa55w0rd
jpa:
hibernate:
ddl-auto: update
properties:
hibernate:
default_schema: lms
format_sql: true
show-sql: true
# flyway:
# enabled: true
# default-schema: lms
# schemas: lms
# locations: classpath:db/migration
springdoc:
swagger-ui:
path: /swagger-ui