Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4b82d14
Student db connection, most likely needs more work
constantine0621 Jun 22, 2026
f31a53e
fixes for Student
constantine0621 Jun 23, 2026
2c79f7f
fixes for Student
constantine0621 Jun 24, 2026
d16ea98
adjusted getStudentById and corresponding test
constantine0621 Jun 24, 2026
90ef981
changes to DTOs and Mapper to prepare for Edit
constantine0621 Jun 24, 2026
57be802
added soft delete logic to Student
constantine0621 Jun 25, 2026
6cf8c31
Merge branch 'main' into Feature--Delete-for-Student
constantine0621 Jun 29, 2026
91613b1
Merge branch 'main' into Feature--Student-db-connection
constantine0621 Jun 29, 2026
8bbfef0
added Validator interface, created StudentValidator and StudentWebFac…
constantine0621 Jun 29, 2026
f7ef3a8
Merge branch 'enhancement/80-student-facade-layer' into Feature--Stud…
constantine0621 Jun 29, 2026
312f559
fixed leftover update from merge conflict
constantine0621 Jun 29, 2026
7d85e69
removed toInternalDto
constantine0621 Jun 29, 2026
a0f44fc
Merge branch 'enhancement/80-student-facade-layer' into Feature--Stud…
constantine0621 Jun 29, 2026
4f6a1cb
changed Student Mapper mappings and Repository queries to adjust for …
constantine0621 Jun 30, 2026
89356e3
Merge branch 'main' into Feature--Student-db-connection
constantine0621 Jun 30, 2026
a39fd3d
adjusted StudentWebFacadeTest test mock data
constantine0621 Jun 30, 2026
60ed996
adjusted StudentServiceTest repository calls
constantine0621 Jun 30, 2026
a212419
Merge branch 'main' into enhancement/80-student-facade-layer
constantine0621 Jun 30, 2026
213145b
Merge branch 'enhancement/80-student-facade-layer' into Feature--Stud…
constantine0621 Jun 30, 2026
c164387
got rid of internal Dtos and responseDto validations, removed @NotNul…
constantine0621 Jun 30, 2026
a4e5d03
Merge branch 'enhancement/80-student-facade-layer' into Feature--Stud…
constantine0621 Jun 30, 2026
545c212
Added StudentMapperTest, changed StudentValidator to accept StudentRe…
constantine0621 Jul 1, 2026
ac847c1
Merge branch 'enhancement/80-student-facade-layer' into Feature--Stud…
constantine0621 Jul 1, 2026
f1abca7
Updated Mapper and Service tests after merge
constantine0621 Jul 1, 2026
b653d38
Merge branch 'main' into enhancement/80-student-facade-layer
constantine0621 Jul 1, 2026
d8a5f0d
Merge branch 'enhancement/80-student-facade-layer' into Feature--Stud…
constantine0621 Jul 1, 2026
cdaf707
Merge branch 'Feature--Student-db-connection' into Feature--Delete-fo…
constantine0621 Jul 4, 2026
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
3 changes: 3 additions & 0 deletions src/main/java/org/unilab/uniplan/student/Student.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.SoftDelete;
import org.hibernate.annotations.SoftDeleteType;
import org.unilab.uniplan.common.model.Person;
import org.unilab.uniplan.course.Course;

@Entity
@Table(name = "student")
@SoftDelete(strategy = SoftDeleteType.DELETED, columnName = "is_deleted")
@Getter
@Setter
@NoArgsConstructor
Expand Down
49 changes: 13 additions & 36 deletions src/main/java/org/unilab/uniplan/student/StudentController.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import jakarta.validation.constraints.NotNull;
import java.util.List;
import java.util.UUID;
import jakarta.validation.constraints.Size;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
Expand All @@ -16,10 +15,7 @@
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.unilab.uniplan.student.dto.StudentCourseMajorDto;
import org.unilab.uniplan.student.dto.StudentDto;
import org.unilab.uniplan.student.dto.StudentRequestDto;
import org.unilab.uniplan.student.dto.StudentResponseDto;

Expand All @@ -29,54 +25,35 @@
@Tag(name = "Students", description = "Manage students, including faculty numbers and enrollment in course")
public class StudentController {

private final StudentService studentService;
private final StudentMapper studentMapper;
private final StudentWebFacade studentWebFacade;

@PostMapping
public ResponseEntity<StudentResponseDto> createStudent(@RequestBody @NotNull
@Valid final StudentRequestDto studentRequestDTO) {
final StudentDto studentDTO = studentMapper.toInternalDto(studentRequestDTO);
studentService.createStudent(studentDTO);
return ResponseEntity.status(HttpStatus.CREATED)
.body(studentMapper.toResponseDto(studentDTO));
public ResponseEntity<Void> createStudent(@RequestBody @NotNull @Valid final StudentRequestDto studentRequestDTO) {
studentWebFacade.createStudent(studentRequestDTO);
return ResponseEntity.status(HttpStatus.CREATED).build();
}

@GetMapping("/{id}")
public ResponseEntity<StudentResponseDto> getStudent(@PathVariable
@NotNull final UUID id) {
final StudentResponseDto studentResponseDTO = studentMapper.toResponseDto(studentService.findStudentById(
id));

return ResponseEntity.ok(studentResponseDTO);
public ResponseEntity<StudentResponseDto> getStudentById(@PathVariable final UUID id) {
return ResponseEntity.ok(studentWebFacade.getStudentById(id));
}

@GetMapping
public List<StudentResponseDto> getAllStudents() {
return studentMapper.toResponseDtoList(studentService.findAll());
}

@GetMapping("/student-course-major/getStudentCourseMajorInfo")
public List<StudentCourseMajorDto> getStudentCourseMajorInfo(@RequestParam(required = false) @Size(max = 100) final String firstName,
@RequestParam(required = false) @Size(max = 100) final String lastName,
@RequestParam(required = false) final String facultyNumber,
@RequestParam(required = false) @Size(max = 200) final String majorName){
return studentService.findStudentCourseMajorInfo(firstName, lastName, facultyNumber, majorName);
public ResponseEntity<List<StudentResponseDto>> getAllStudents() {
return ResponseEntity.ok(studentWebFacade.getAllStudents());
}

@PutMapping("/{id}")
public ResponseEntity<StudentResponseDto> updateStudent(@PathVariable
@NotNull final UUID id,
public ResponseEntity<Void> updateStudent(@PathVariable final UUID id,
@RequestBody
@NotNull @Valid final StudentRequestDto studentRequestDTO) {
final StudentDto studentDTO = studentMapper.toInternalDto(studentRequestDTO);
studentService.updateStudent(id, studentDTO);
return ResponseEntity.ok(studentMapper.toResponseDto(studentDTO));
studentWebFacade.updateStudent(id, studentRequestDTO);
return ResponseEntity.noContent().build();
}

@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteStudent(@PathVariable
@NotNull final UUID id) {
studentService.deleteStudent(id);
public ResponseEntity<Void> deleteStudent(@PathVariable final UUID id) {
studentWebFacade.deleteStudent(id);
return ResponseEntity.noContent().build();
}
}
32 changes: 17 additions & 15 deletions src/main/java/org/unilab/uniplan/student/StudentMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,31 @@
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import org.unilab.uniplan.student.dto.StudentDto;
import org.unilab.uniplan.student.dto.StudentRequestDto;
import org.unilab.uniplan.student.dto.StudentResponseDto;

@Mapper
public interface StudentMapper {

@Mapping(source = "courseId", target = "course.id")
Student toEntity(StudentDto studentDto);

@Mapping(source = "course.id", target = "courseId")
StudentDto toDto(Student student);

@Mapping(target = "id", ignore = true)
@Mapping(source = "courseId", target = "course.id")
void updateEntityFromDto(StudentDto studentDto, @MappingTarget Student student);
Student toEntity(StudentRequestDto requestDto);

@Mapping(target = "id", ignore = true)
StudentDto toInternalDto(StudentRequestDto student);

@Mapping(source = "courseId", target = "courseId")
StudentResponseDto toResponseDto(StudentDto studentDto);

List<StudentResponseDto> toResponseDtoList(List<StudentDto> students);

@Mapping(target = "course", ignore = true)
void updateEntity(StudentRequestDto requestDto, @MappingTarget Student student);

@Mapping(target = "name", expression = "java(toFullName(student.getFirstName(), student.getLastName()))")
@Mapping(source = "course.major.id", target = "majorId")
@Mapping(source = "course.major.majorName", target = "majorName")
@Mapping(source = "course.courseType", target="courseType")
@Mapping(source = "course.courseSubtype", target = "courseSubtype")
@Mapping(source = "course.courseYear", target = "courseYear")
StudentResponseDto toResponseDto(Student student);

List<StudentResponseDto> toResponseDtoList(List<Student> students);

default String toFullName(String firstName, String lastName) {
return firstName + " " + lastName;
}
}
34 changes: 14 additions & 20 deletions src/main/java/org/unilab/uniplan/student/StudentRepository.java
Original file line number Diff line number Diff line change
@@ -1,33 +1,27 @@
package org.unilab.uniplan.student;

import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import org.unilab.uniplan.student.dto.StudentCourseMajorDto;

@Repository
public interface StudentRepository extends JpaRepository<Student, UUID> {
@Query("""
select new org.unilab.uniplan.student.dto.StudentCourseMajorDto(
s.id, s.firstName, s.lastName, s.facultyNumber,
c.id, c.courseType, c.courseSubtype, c.courseYear,
m.id, m.majorName
)
from Student s
join s.course c
join c.major m
where (:firstName is null or lower(s.firstName) like lower(concat('%', :firstName, '%')))
and (:lastName is null or lower(s.lastName) like lower(concat('%', :lastName, '%')))
and (:facultyNumber is null or s.facultyNumber like concat('%', :facultyNumber, '%'))
and (:majorName is null or lower(m.majorName) like lower(concat('%', :majorName, '%')))
""")
List<StudentCourseMajorDto> searchStudents(
@Param("firstName") String firstName,
@Param("lastName") String lastName,
@Param("facultyNumber") String facultyNumber,
@Param("majorName") String majorName
);
SELECT s FROM Student s
JOIN FETCH s.course c
JOIN FETCH c.major m
""")
List<Student> findAllWithCourseAndMajor();

@Query("""
SELECT s FROM Student s
JOIN FETCH s.course c
JOIN FETCH c.major m
WHERE s.id = :id
""")
Optional<Student> findByIdWithCourseAndMajor(@Param("id") UUID id);
}
67 changes: 15 additions & 52 deletions src/main/java/org/unilab/uniplan/student/StudentService.java
Original file line number Diff line number Diff line change
@@ -1,72 +1,35 @@
package org.unilab.uniplan.student;

import static org.unilab.uniplan.utils.ErrorConstants.STUDENT_NOT_FOUND;

import jakarta.transaction.Transactional;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.unilab.uniplan.exception.ResourceNotFoundException;
import org.unilab.uniplan.student.dto.StudentCourseMajorDto;
import org.unilab.uniplan.student.dto.StudentDto;
import org.unilab.uniplan.common.model.BaseService;

@Service
@RequiredArgsConstructor
public class StudentService {
public class StudentService implements BaseService<Student> {

private final StudentRepository studentRepository;
private final StudentMapper studentMapper;

@Transactional
public StudentDto createStudent(final StudentDto studentDTO) {
final Student student = studentMapper.toEntity(studentDTO);
return saveEntityAndConvertToDto(student);
}

public StudentDto findStudentById(final UUID id) {
return studentRepository.findById(id)
.map(studentMapper::toDto)
.orElseThrow(() -> new ResourceNotFoundException(STUDENT_NOT_FOUND.getMessage(
String.valueOf(id))));
}

public List<StudentDto> findAll() {
return studentRepository.findAll()
.stream().map(studentMapper::toDto).toList();
}

@Transactional
public StudentDto updateStudent(final UUID id, final StudentDto studentDTO) {
return studentRepository.findById(id)
.map(existingStudent -> updateEntityAndConvertToDto(
studentDTO,
existingStudent))
.orElseThrow(() -> new ResourceNotFoundException(STUDENT_NOT_FOUND.getMessage(
String.valueOf(id))));
}

@Transactional
public void deleteStudent(final UUID id) {
final Student student = studentRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException(
STUDENT_NOT_FOUND.getMessage(String.valueOf(id))));
studentRepository.delete(student);
@Override
public void save(final Student entity) {
studentRepository.save(entity);
}

public List<StudentCourseMajorDto> findStudentCourseMajorInfo(final String firstName, final String lastName,
final String facultyNumber, final String majorName){
return studentRepository.searchStudents(firstName, lastName, facultyNumber, majorName);
@Override
public List<Student> getAll() {
return studentRepository.findAllWithCourseAndMajor();
}

private StudentDto updateEntityAndConvertToDto(final StudentDto dto,
final Student entity) {
studentMapper.updateEntityFromDto(dto, entity);
return saveEntityAndConvertToDto(entity);
@Override
public Optional<Student> getById(final UUID id) {
return studentRepository.findByIdWithCourseAndMajor(id);
}

private StudentDto saveEntityAndConvertToDto(final Student entity) {
final Student savedEntity = studentRepository.save(entity);
return studentMapper.toDto(savedEntity);
@Override
public void delete(final Student entity) {
studentRepository.delete(entity);
}
}
24 changes: 24 additions & 0 deletions src/main/java/org/unilab/uniplan/student/StudentValidator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.unilab.uniplan.student;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.unilab.uniplan.course.CourseRepository;
import org.unilab.uniplan.exception.ResourceNotFoundException;
import org.unilab.uniplan.student.dto.StudentRequestDto;

import java.util.UUID;

import static org.unilab.uniplan.utils.ErrorConstants.COURSE_NOT_FOUND;

@Component
@RequiredArgsConstructor
public class StudentValidator{
private final CourseRepository courseRepository;

public void validate(final StudentRequestDto request) {
UUID id = request.courseId();
if (!courseRepository.existsById(id)){
throw new ResourceNotFoundException(COURSE_NOT_FOUND.getMessage(id.toString()));
}
}
}
66 changes: 66 additions & 0 deletions src/main/java/org/unilab/uniplan/student/StudentWebFacade.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package org.unilab.uniplan.student;

import org.springframework.transaction.annotation.Transactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.unilab.uniplan.exception.ResourceNotFoundException;
import org.unilab.uniplan.student.dto.StudentRequestDto;
import org.unilab.uniplan.student.dto.StudentResponseDto;
import java.util.List;
import java.util.UUID;

import static org.unilab.uniplan.utils.ErrorConstants.STUDENT_NOT_FOUND;

@Component
@Slf4j
@RequiredArgsConstructor
public class StudentWebFacade {

private final StudentMapper studentMapper;
private final StudentService studentService;
private final StudentValidator studentValidator;

@Transactional
public void createStudent(final StudentRequestDto request){
studentValidator.validate(request);
final Student student = studentMapper.toEntity(request);
studentService.save(student);
log.info("created student with ID: {}", student.getId());
}

@Transactional(readOnly = true)
public List<StudentResponseDto> getAllStudents(){
return studentMapper.toResponseDtoList(studentService.getAll());
}

@Transactional(readOnly = true)
public StudentResponseDto getStudentById(final UUID id){
final Student student = getStudentOrThrow(id);
return studentMapper.toResponseDto(student);
}

@Transactional
public void updateStudent(final UUID id,
final StudentRequestDto request){
studentValidator.validate(request);
final Student student = getStudentOrThrow(id);
studentMapper.updateEntity(request, student);
studentService.save(student);
log.info("updated student with ID: {}", student.getId());
}

@Transactional
public void deleteStudent(final UUID id){
final Student student = getStudentOrThrow(id);
studentService.delete(student);
log.info("deleted student with ID: {}", id);
}

private Student getStudentOrThrow(final UUID id){
return studentService.getById(id)
.orElseThrow(() -> new ResourceNotFoundException(
STUDENT_NOT_FOUND.getMessage(String.valueOf(id))
));
}
}
Loading
Loading