diff --git a/src/main/java/org/unilab/uniplan/building/Building.java b/src/main/java/org/unilab/uniplan/building/Building.java new file mode 100644 index 00000000..78300f42 --- /dev/null +++ b/src/main/java/org/unilab/uniplan/building/Building.java @@ -0,0 +1,42 @@ +package org.unilab.uniplan.building; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.unilab.uniplan.common.model.BaseEntity; +import org.unilab.uniplan.room.Room; +import org.unilab.uniplan.university.University; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Table(name = "building") +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class Building extends BaseEntity { + + @Column(name = "name", nullable = false, length = 50) + private String name; + + @Column(name = "address", nullable = false, length = 100) + private String address; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "university_id", referencedColumnName = "id") + private University university; + + @OneToMany(mappedBy = "building") + private List rooms = new ArrayList<>(); +} diff --git a/src/main/java/org/unilab/uniplan/category/Category.java b/src/main/java/org/unilab/uniplan/category/Category.java index 437268f9..ff0ca278 100644 --- a/src/main/java/org/unilab/uniplan/category/Category.java +++ b/src/main/java/org/unilab/uniplan/category/Category.java @@ -19,6 +19,10 @@ public class Category extends BaseEntity { @Column(name = "room_type", nullable = false, length = 50) private String roomType; + @Column(name = "capacity", nullable = false) private short capacity; + + @Column(name = "description", nullable = true, length = 100) + private String description; } \ No newline at end of file diff --git a/src/main/java/org/unilab/uniplan/category/dto/CategoryDto.java b/src/main/java/org/unilab/uniplan/category/dto/CategoryDto.java index 1ad4a517..6dd33327 100644 --- a/src/main/java/org/unilab/uniplan/category/dto/CategoryDto.java +++ b/src/main/java/org/unilab/uniplan/category/dto/CategoryDto.java @@ -14,7 +14,10 @@ public record CategoryDto( String roomType, @Positive - short capacity + short capacity, + + @Size(min = 1, max = 100, message = "Description must be between 1 and 100 characters") + String description ) { } diff --git a/src/main/java/org/unilab/uniplan/category/dto/CategoryRequestDto.java b/src/main/java/org/unilab/uniplan/category/dto/CategoryRequestDto.java index 204b0dbc..ac71e16a 100644 --- a/src/main/java/org/unilab/uniplan/category/dto/CategoryRequestDto.java +++ b/src/main/java/org/unilab/uniplan/category/dto/CategoryRequestDto.java @@ -1,5 +1,6 @@ package org.unilab.uniplan.category.dto; +import jakarta.persistence.Column; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; import jakarta.validation.constraints.Size; @@ -11,7 +12,10 @@ public record CategoryRequestDto( String roomType, @Positive - short capacity + short capacity, + + @Size(min = 1, max = 100, message = "Description must be between 1 and 100 characters") + String description ) { } diff --git a/src/main/java/org/unilab/uniplan/category/dto/CategoryResponseDto.java b/src/main/java/org/unilab/uniplan/category/dto/CategoryResponseDto.java index 7fc7a1a7..3c8d6658 100644 --- a/src/main/java/org/unilab/uniplan/category/dto/CategoryResponseDto.java +++ b/src/main/java/org/unilab/uniplan/category/dto/CategoryResponseDto.java @@ -14,7 +14,10 @@ public record CategoryResponseDto( String roomType, @Positive - short capacity + short capacity, + + @Size(min = 1, max = 100, message = "Description must be between 1 and 100 characters") + String description ) { } diff --git a/src/main/java/org/unilab/uniplan/room/CustomRoomRepository.java b/src/main/java/org/unilab/uniplan/room/CustomRoomRepository.java new file mode 100644 index 00000000..cb5d43a4 --- /dev/null +++ b/src/main/java/org/unilab/uniplan/room/CustomRoomRepository.java @@ -0,0 +1,14 @@ +package org.unilab.uniplan.room; + +import org.springframework.data.repository.query.Param; +import org.unilab.uniplan.room.dto.RoomResponseDto; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public interface CustomRoomRepository { + + List findAllRoomResponses(); + + Optional findRoomResponseById(UUID id); +} diff --git a/src/main/java/org/unilab/uniplan/room/CustomRoomRepositoryImpl.java b/src/main/java/org/unilab/uniplan/room/CustomRoomRepositoryImpl.java new file mode 100644 index 00000000..255a57ff --- /dev/null +++ b/src/main/java/org/unilab/uniplan/room/CustomRoomRepositoryImpl.java @@ -0,0 +1,81 @@ +package org.unilab.uniplan.room; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import org.hibernate.query.common.JoinType; +import org.hibernate.query.criteria.HibernateCriteriaBuilder; +import org.hibernate.query.criteria.JpaCriteriaQuery; +import org.hibernate.query.criteria.JpaEntityJoin; +import org.hibernate.query.criteria.JpaRoot; +import org.springframework.data.repository.query.Param; +import org.unilab.uniplan.category.Category; +import org.unilab.uniplan.room.dto.RoomResponseDto; +import org.unilab.uniplan.roomcategory.RoomCategory; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public class CustomRoomRepositoryImpl implements CustomRoomRepository { + + @PersistenceContext + private EntityManager entityManager; + + @Override + public List findAllRoomResponses() { + HibernateCriteriaBuilder cb = (HibernateCriteriaBuilder) entityManager.getCriteriaBuilder(); + + JpaCriteriaQuery query = cb.createQuery(RoomResponseDto.class); + + JpaRoot room = query.from(Room.class); + + JpaEntityJoin roomCategory = + room.join(RoomCategory.class, JoinType.LEFT); + roomCategory.on(cb.equal(roomCategory.get("room"), room)); + + JpaEntityJoin category = + roomCategory.join(Category.class, JoinType.LEFT); + category.on(cb.equal(category, roomCategory.get("category"))); + + query.select(cb.construct( + RoomResponseDto.class, + room.get("id"), + room.get("faculty").get("id"), + room.get("roomNumber"), + room.get("availableSeats"), + category.get("id"), + room.get("building").get("id") + )); + + return entityManager.createQuery(query).getResultList(); + } + + @Override + public Optional findRoomResponseById(UUID id) { + HibernateCriteriaBuilder cb = (HibernateCriteriaBuilder) entityManager.getCriteriaBuilder(); + + JpaCriteriaQuery query = cb.createQuery(RoomResponseDto.class); + + JpaRoot room = query.from(Room.class); + JpaEntityJoin roomCategory = + room.join(RoomCategory.class, JoinType.LEFT); + roomCategory.on(cb.equal(roomCategory.get("room"), room)); + + JpaEntityJoin category = + roomCategory.join(Category.class, JoinType.LEFT); + category.on(cb.equal(category, roomCategory.get("category"))); + + query.select(cb.construct( + RoomResponseDto.class, + room.get("id"), + room.get("faculty").get("id"), + room.get("roomNumber"), + room.get("availableSeats"), + category.get("id"), + room.get("building").get("id") + )).where(cb.equal(room.get("id"), id)); + + return entityManager.createQuery(query) + .getResultStream() + .findFirst(); + } +} \ No newline at end of file diff --git a/src/main/java/org/unilab/uniplan/room/Room.java b/src/main/java/org/unilab/uniplan/room/Room.java index afd41137..19be519a 100644 --- a/src/main/java/org/unilab/uniplan/room/Room.java +++ b/src/main/java/org/unilab/uniplan/room/Room.java @@ -4,11 +4,13 @@ import jakarta.persistence.Entity; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToOne; import jakarta.persistence.Table; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.unilab.uniplan.building.Building; import org.unilab.uniplan.common.model.BaseEntity; import org.unilab.uniplan.faculty.Faculty; @@ -21,9 +23,16 @@ public class Room extends BaseEntity { @ManyToOne - @JoinColumn(name = "faculty_id", referencedColumnName = "id", nullable = false) + @JoinColumn(name = "faculty_id", referencedColumnName = "id", nullable = true) private Faculty faculty; + @Column(name = "room_number", nullable = false, length = 50) private String roomNumber; + @Column(name = "available_seats", nullable = false) + private int availableSeats; + + @ManyToOne + @JoinColumn(name = "building_id") + private Building building; } \ No newline at end of file diff --git a/src/main/java/org/unilab/uniplan/room/RoomController.java b/src/main/java/org/unilab/uniplan/room/RoomController.java index 20b3f1a6..55206cf6 100644 --- a/src/main/java/org/unilab/uniplan/room/RoomController.java +++ b/src/main/java/org/unilab/uniplan/room/RoomController.java @@ -1,7 +1,5 @@ package org.unilab.uniplan.room; -import static org.springframework.http.ResponseEntity.ok; - import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; @@ -18,7 +16,6 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import org.unilab.uniplan.room.dto.RoomDto; import org.unilab.uniplan.room.dto.RoomRequestDto; import org.unilab.uniplan.room.dto.RoomResponseDto; @@ -28,39 +25,34 @@ @Tag(name = "Rooms", description = "Manage classrooms and lecture halls, including room numbers and assigned faculties") public class RoomController { - private final RoomService roomService; - private final RoomMapper roomMapper; + private final RoomWebFacade roomWebFacade; @PostMapping - public ResponseEntity createRoom(@Valid @NotNull @RequestBody final RoomRequestDto roomRequestDto) { - final RoomDto roomDto = roomService.createRoom(roomMapper.toInternalDto(roomRequestDto)); - - return new ResponseEntity<>(roomMapper.toResponseDto(roomDto), HttpStatus.CREATED); + public ResponseEntity createRoom(@Valid @NotNull @RequestBody final RoomRequestDto roomRequestDto) { + roomWebFacade.createRoom(roomRequestDto); + return ResponseEntity.status(HttpStatus.CREATED).build(); } @GetMapping - public List getAllRooms() { - return roomMapper.toResponseDtoList(roomService.getAllRooms()); + public ResponseEntity> getAllRooms() { + return ResponseEntity.ok(roomWebFacade.getAllRooms()); } @GetMapping("/{id}") public ResponseEntity getRoomById(@PathVariable final UUID id) { - final RoomDto roomDto = roomService.getRoomById(id); - - return ok(roomMapper.toResponseDto(roomDto)); + return ResponseEntity.ok(roomWebFacade.getRoomById(id)); } @PutMapping("/{id}") public ResponseEntity updateRoom(@PathVariable final UUID id, @Valid @NotNull @RequestBody final RoomRequestDto roomRequestDto) { - final RoomDto internalDto = roomMapper.toInternalDto(roomRequestDto); - - return ok(roomMapper.toResponseDto(roomService.updateRoom(id, internalDto))); + roomWebFacade.updateRoom(id, roomRequestDto); + return ResponseEntity.noContent().build(); } @DeleteMapping("/{id}") public ResponseEntity deleteRoom(@PathVariable final UUID id) { - roomService.deleteRoom(id); + roomWebFacade.deleteRoom(id); return ResponseEntity.noContent().build(); } diff --git a/src/main/java/org/unilab/uniplan/room/RoomMapper.java b/src/main/java/org/unilab/uniplan/room/RoomMapper.java index a551abaf..b122ca9d 100644 --- a/src/main/java/org/unilab/uniplan/room/RoomMapper.java +++ b/src/main/java/org/unilab/uniplan/room/RoomMapper.java @@ -4,29 +4,21 @@ import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingTarget; -import org.unilab.uniplan.room.dto.RoomDto; import org.unilab.uniplan.room.dto.RoomRequestDto; import org.unilab.uniplan.room.dto.RoomResponseDto; @Mapper public interface RoomMapper { - @Mapping(source = "faculty.id", target = "facultyId") - RoomDto toDto(final Room room); + @Mapping(target = "faculty", ignore = true) + Room toEntity(final RoomRequestDto roomRequestDto); - @Mapping(source = "facultyId", target = "faculty.id") - Room toEntity(final RoomDto roomDto); + @Mapping(target = "facultyId", source = "faculty.id") + RoomResponseDto toResponseDto(Room room); - @Mapping(target = "id", ignore = true) - RoomDto toInternalDto(final RoomRequestDto roomRequestDto); - - RoomResponseDto toResponseDto(final RoomDto roomDto); - - List toDtoList(final List rooms); - - List toResponseDtoList(final List rooms); + List toResponseDtoList(final List rooms); - @Mapping(source = "roomDto.facultyId", target = "faculty.id") + @Mapping(target = "faculty", ignore = true) @Mapping(target = "id", ignore = true) - void updateEntityFromDto(final RoomDto roomDto, @MappingTarget final Room room); + void updateEntityFromDto(final RoomRequestDto roomRequestDto, @MappingTarget final Room room); } diff --git a/src/main/java/org/unilab/uniplan/room/RoomRepository.java b/src/main/java/org/unilab/uniplan/room/RoomRepository.java index 0597aa71..268d42de 100644 --- a/src/main/java/org/unilab/uniplan/room/RoomRepository.java +++ b/src/main/java/org/unilab/uniplan/room/RoomRepository.java @@ -5,6 +5,5 @@ import org.springframework.stereotype.Repository; @Repository -public interface RoomRepository extends JpaRepository { - +public interface RoomRepository extends JpaRepository, CustomRoomRepository { } \ No newline at end of file diff --git a/src/main/java/org/unilab/uniplan/room/RoomService.java b/src/main/java/org/unilab/uniplan/room/RoomService.java index 5dbd29ac..62b4acb9 100644 --- a/src/main/java/org/unilab/uniplan/room/RoomService.java +++ b/src/main/java/org/unilab/uniplan/room/RoomService.java @@ -1,65 +1,61 @@ package org.unilab.uniplan.room; -import static org.unilab.uniplan.utils.ErrorConstants.ROOM_NOT_FOUND; - import java.util.List; +import java.util.Optional; import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; +import org.unilab.uniplan.common.model.BaseService; import org.unilab.uniplan.exception.ResourceNotFoundException; -import org.unilab.uniplan.room.dto.RoomDto; +import org.unilab.uniplan.faculty.Faculty; +import org.unilab.uniplan.faculty.FacultyRepository; +import org.unilab.uniplan.room.dto.RoomResponseDto; @Service @RequiredArgsConstructor -public class RoomService { +public class RoomService implements BaseService { private final RoomRepository roomRepository; - private final RoomMapper roomMapper; + private final FacultyRepository facultyRepository; + + public void setFaculty(Room room, UUID facultyId) { + if (facultyId == null) { + room.setFaculty(null); + return; + } - @Transactional - public RoomDto createRoom(final RoomDto roomDto) { - final Room room = roomMapper.toEntity(roomDto); + //TODO: Change to FacultyNotFoundException after pr is approved + Faculty faculty = facultyRepository.findById(facultyId) + .orElseThrow(() -> new ResourceNotFoundException("Faculty not found")); - return saveEntityAndConvertToDto(room); + room.setFaculty(faculty); } - public List getAllRooms() { - return roomMapper.toDtoList(roomRepository.findAll()); + @Override + public void save(final Room room) { + roomRepository.save(room); } - public RoomDto getRoomById(final UUID id) { - return roomRepository.findById(id) - .map(roomMapper::toDto) - .orElseThrow(() -> new ResourceNotFoundException(ROOM_NOT_FOUND.getMessage( - String.valueOf(id)))); + @Override + public List getAll() { + return roomRepository.findAll(); } - @Transactional - public RoomDto updateRoom(final UUID id, final RoomDto roomDto) { - return roomRepository.findById(id) - .map(existingRoom -> updateEntityAndConvertToDto( - roomDto, - existingRoom)).orElseThrow(() -> new ResourceNotFoundException( - ROOM_NOT_FOUND.getMessage(String.valueOf(id)))); + @Override + public Optional getById(final UUID id) { + return roomRepository.findById(id); } - @Transactional - public void deleteRoom(final UUID id) { - final Room room = roomRepository.findById(id) - .orElseThrow(() -> new ResourceNotFoundException( - ROOM_NOT_FOUND.getMessage(String.valueOf(id)))); + @Override + public void delete(final Room room) { roomRepository.delete(room); } - private RoomDto updateEntityAndConvertToDto(final RoomDto dto, - final Room entity) { - roomMapper.updateEntityFromDto(dto, entity); - return saveEntityAndConvertToDto(entity); + public List getAllRoomResponses() { + return roomRepository.findAllRoomResponses(); } - private RoomDto saveEntityAndConvertToDto(final Room entity) { - final Room savedEntity = roomRepository.save(entity); - return roomMapper.toDto(savedEntity); + public Optional getRoomResponseById(final UUID id) { + return roomRepository.findRoomResponseById(id); } } diff --git a/src/main/java/org/unilab/uniplan/room/RoomWebFacade.java b/src/main/java/org/unilab/uniplan/room/RoomWebFacade.java new file mode 100644 index 00000000..37c91b6d --- /dev/null +++ b/src/main/java/org/unilab/uniplan/room/RoomWebFacade.java @@ -0,0 +1,67 @@ +package org.unilab.uniplan.room; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; +import org.unilab.uniplan.exception.ResourceNotFoundException; +import org.unilab.uniplan.room.dto.RoomRequestDto; +import org.unilab.uniplan.room.dto.RoomResponseDto; +import java.util.List; +import java.util.UUID; +import static org.unilab.uniplan.utils.ErrorConstants.ROOM_NOT_FOUND; + +@Component +@Slf4j +@RequiredArgsConstructor +public class RoomWebFacade { + + private final RoomMapper roomMapper; + private final RoomService roomService; + + private Room getRoomOrThrow(final UUID id) { + return roomService.getById(id) + .orElseThrow(() -> new ResourceNotFoundException(ROOM_NOT_FOUND.getMessage( + String.valueOf(id)))); + } + + @Transactional + public void createRoom(RoomRequestDto roomRequestDto) { + Room room = roomMapper.toEntity(roomRequestDto); + + roomService.setFaculty(room, roomRequestDto.facultyId()); + + roomService.save(room); + log.info("created room {} with ID: {}", + room.getRoomNumber(), + room.getId()); + } + + @Transactional(readOnly = true) + public List getAllRooms() { + return roomService.getAllRoomResponses(); + } + + @Transactional + public void deleteRoom(final UUID id) { + final Room room = getRoomOrThrow(id); + roomService.delete(room); + log.info("deleted room with id {}", id); + } + + @Transactional(readOnly = true) + public RoomResponseDto getRoomById(final UUID id) { + return roomService.getRoomResponseById(id) + .orElseThrow(() -> new ResourceNotFoundException(ROOM_NOT_FOUND.getMessage( + String.valueOf(id)))); + } + + @Transactional + public void updateRoom(final UUID id, final RoomRequestDto roomRequestDto) { + final Room room = getRoomOrThrow(id); + roomMapper.updateEntityFromDto(roomRequestDto, room); + roomService.setFaculty(room, roomRequestDto.facultyId()); + roomService.save(room); + log.info("updated room with id {}", id); + } +} diff --git a/src/main/java/org/unilab/uniplan/room/dto/RoomDto.java b/src/main/java/org/unilab/uniplan/room/dto/RoomDto.java deleted file mode 100644 index 0acf52e4..00000000 --- a/src/main/java/org/unilab/uniplan/room/dto/RoomDto.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.unilab.uniplan.room.dto; - -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; -import java.util.UUID; - -public record RoomDto( - - UUID id, - - @NotNull(message = "Faculty ID cannot be null") - UUID facultyId, - - @NotNull(message = "Room number cannot be null") - @Size(max = 50, message = "Room number cannot exceed 50 characters") - String roomNumber -) { - -} diff --git a/src/main/java/org/unilab/uniplan/room/dto/RoomRequestDto.java b/src/main/java/org/unilab/uniplan/room/dto/RoomRequestDto.java index 9cbe739f..8a92a801 100644 --- a/src/main/java/org/unilab/uniplan/room/dto/RoomRequestDto.java +++ b/src/main/java/org/unilab/uniplan/room/dto/RoomRequestDto.java @@ -1,17 +1,22 @@ package org.unilab.uniplan.room.dto; import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; import jakarta.validation.constraints.Size; import java.util.UUID; public record RoomRequestDto( - @NotNull(message = "Faculty ID cannot be null") UUID facultyId, @NotNull(message = "Room number cannot be null") @Size(max = 50, message = "Room number cannot exceed 50 characters") - String roomNumber + String roomNumber, + + @NotNull(message="Available seats cannot be null") + @PositiveOrZero + int availableSeats ) { } diff --git a/src/main/java/org/unilab/uniplan/room/dto/RoomResponseDto.java b/src/main/java/org/unilab/uniplan/room/dto/RoomResponseDto.java index b5cf53fe..6f8d209c 100644 --- a/src/main/java/org/unilab/uniplan/room/dto/RoomResponseDto.java +++ b/src/main/java/org/unilab/uniplan/room/dto/RoomResponseDto.java @@ -1,19 +1,18 @@ package org.unilab.uniplan.room.dto; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; import java.util.UUID; public record RoomResponseDto( UUID id, - @NotNull(message = "Faculty ID cannot be null") UUID facultyId, - @NotNull(message = "Room number cannot be null") - @Size(max = 50, message = "Room number cannot exceed 50 characters") - String roomNumber -) { + String roomNumber, -} + int availableSeats, + + UUID categoryId, + + UUID buildingId +) { } diff --git a/src/main/java/org/unilab/uniplan/roomcategory/RoomCategoryRepository.java b/src/main/java/org/unilab/uniplan/roomcategory/RoomCategoryRepository.java index 8f8a1f2c..00957015 100644 --- a/src/main/java/org/unilab/uniplan/roomcategory/RoomCategoryRepository.java +++ b/src/main/java/org/unilab/uniplan/roomcategory/RoomCategoryRepository.java @@ -1,9 +1,11 @@ package org.unilab.uniplan.roomcategory; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; +import java.util.List; +import java.util.Optional; @Repository public interface RoomCategoryRepository extends JpaRepository { - } \ No newline at end of file diff --git a/src/main/java/org/unilab/uniplan/university/University.java b/src/main/java/org/unilab/uniplan/university/University.java index e110fd80..b2078532 100644 --- a/src/main/java/org/unilab/uniplan/university/University.java +++ b/src/main/java/org/unilab/uniplan/university/University.java @@ -2,12 +2,16 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.unilab.uniplan.building.Building; import org.unilab.uniplan.common.model.BaseEntity; +import java.util.ArrayList; +import java.util.List; @Entity @Table(name = "university") @@ -19,13 +23,19 @@ public class University extends BaseEntity { @Column(name = "uni_name", nullable = false) private String uniName; + @Column(name = "location", length = 500) private String location; + @Column(name = "established_year") private short establishedYear; + @Column(name = "accreditation", length = 200) private String accreditation; + @Column(name = "website", length = 2048) private String website; + @OneToMany(mappedBy="university") + private List buildings= new ArrayList<>(); } \ No newline at end of file diff --git a/src/main/resources/db/changelog/20260723130700-add-available-seats-to-room.yaml b/src/main/resources/db/changelog/20260723130700-add-available-seats-to-room.yaml new file mode 100644 index 00000000..abf0c0bf --- /dev/null +++ b/src/main/resources/db/changelog/20260723130700-add-available-seats-to-room.yaml @@ -0,0 +1,27 @@ +databaseChangeLog: + - changeSet: + id: 202607231307000-1 + author: pipetkova19 + changes: + - addColumn: + tableName: room + columns: + - column: + name: available_seats + type: int + defaultValueNumeric: "0" + constraints: + nullable: false + - sql: + sql: > + ALTER TABLE room + ADD CONSTRAINT chk_room_available_seats_positive + CHECK (available_seats >= 0); + rollback: + - sql: + sql: > + ALTER TABLE room + DROP CONSTRAINT chk_room_available_seats_positive; + - dropColumn: + tableName: room + columnName: available_seats diff --git a/src/main/resources/db/changelog/20260723134100-drop-not-null-constraint-faculty-room.yaml b/src/main/resources/db/changelog/20260723134100-drop-not-null-constraint-faculty-room.yaml new file mode 100644 index 00000000..a73412c6 --- /dev/null +++ b/src/main/resources/db/changelog/20260723134100-drop-not-null-constraint-faculty-room.yaml @@ -0,0 +1,14 @@ +databaseChangeLog: + - changeSet: + id: 20260723134100-1 + author: pipetkova19 + changes: + - dropNotNullConstraint: + tableName: room + columnName: faculty_id + columnDataType: uuid + rollback: + - addNotNullConstraint: + tableName: room + columnName: faculty_id + columnDataType: uuid \ No newline at end of file diff --git a/src/main/resources/db/changelog/20260723141400-add-description-column-to-category.yaml b/src/main/resources/db/changelog/20260723141400-add-description-column-to-category.yaml new file mode 100644 index 00000000..2933b279 --- /dev/null +++ b/src/main/resources/db/changelog/20260723141400-add-description-column-to-category.yaml @@ -0,0 +1,16 @@ +databaseChangeLog: + - changeSet: + id: 20260723141400-1 + author: pipetkova19 + changes: + - addColumn: + tableName: category + columns: + - column: + name: description + type: varchar(100) + defaultValue: "-" + rollback: + - dropColumn: + tableName: category + columnName: description \ No newline at end of file diff --git a/src/main/resources/db/changelog/20260723144301-create-building-table.yaml b/src/main/resources/db/changelog/20260723144301-create-building-table.yaml new file mode 100644 index 00000000..e0aa5dcb --- /dev/null +++ b/src/main/resources/db/changelog/20260723144301-create-building-table.yaml @@ -0,0 +1,47 @@ +databaseChangeLog: + - changeSet: + id: 20260723144300-1 + author: pipetkova19 + changes: + - createTable: + tableName: building + columns: + - column: + name: id + type: UUID + constraints: + nullable: false + primaryKey: true + primaryKeyName: pk_building + - column: + name: name + type: varchar(50) + constraints: + nullable: false + - column: + name: address + type: varchar(100) + constraints: + nullable: false + - column: + name: university_id + type: UUID + - column: + constraints: + nullable: false + name: created_at + type: DATETIME + - column: + name: updated_at + type: DATETIME + + - changeSet: + id: 20260723144300-2 + author: pipetkova19 + changes: + - addForeignKeyConstraint: + baseTableName: building + baseColumnNames: university_id + constraintName: FK_BUILDING_ON_UNIVERSITY + referencedTableName: university + referencedColumnNames: id \ No newline at end of file diff --git a/src/main/resources/db/changelog/20260728113500-add-building-column-to-room.yaml b/src/main/resources/db/changelog/20260728113500-add-building-column-to-room.yaml new file mode 100644 index 00000000..ce9e48a3 --- /dev/null +++ b/src/main/resources/db/changelog/20260728113500-add-building-column-to-room.yaml @@ -0,0 +1,15 @@ +databaseChangeLog: + - changeSet: + id: 20260728113500-1 + author: pipetkova19 + changes: + - addColumn: + tableName: room + columns: + - column: + name: building_id + type: uuid + rollback: + - dropColumn: + tableName: building + columnName: room_id diff --git a/src/main/resources/db/changelog/db.changelog-master.yaml b/src/main/resources/db/changelog/db.changelog-master.yaml index 10432196..a9e98dc5 100644 --- a/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/src/main/resources/db/changelog/db.changelog-master.yaml @@ -13,3 +13,13 @@ databaseChangeLog: file: db/changelog/20260626144400-drop-column-deleted-at-major.yaml - include: file: db/changelog/20260626144500-add-column-is-deleted-to-course.yaml + - include: + file: db/changelog/20260723130700-add-available-seats-to-room.yaml + - include: + file: db/changelog/20260723134100-drop-not-null-constraint-faculty-room.yaml + - include: + file: db/changelog/20260723141400-add-description-column-to-category.yaml + - include: + file: db/changelog/20260723144301-create-building-table.yaml + - include: + file: db/changelog/20260728113500-add-building-column-to-room.yaml diff --git a/src/test/java/org/unilab/uniplan/faculty/FacultyMapperTest.java b/src/test/java/org/unilab/uniplan/faculty/FacultyMapperTest.java index 7873a7e1..f76b0566 100644 --- a/src/test/java/org/unilab/uniplan/faculty/FacultyMapperTest.java +++ b/src/test/java/org/unilab/uniplan/faculty/FacultyMapperTest.java @@ -31,7 +31,8 @@ void setUp() { "Burgas", (short) 1999, "accreditation", - "www.pu.com" + "www.pu.com", + null ); university.setId(universityId); diff --git a/src/test/java/org/unilab/uniplan/room/RoomMapperTest.java b/src/test/java/org/unilab/uniplan/room/RoomMapperTest.java new file mode 100644 index 00000000..29c15770 --- /dev/null +++ b/src/test/java/org/unilab/uniplan/room/RoomMapperTest.java @@ -0,0 +1,97 @@ +package org.unilab.uniplan.room; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.unilab.uniplan.room.dto.RoomRequestDto; +import org.unilab.uniplan.room.dto.RoomResponseDto; +import org.unilab.uniplan.university.University; +import java.util.List; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + + +public class RoomMapperTest { + + private final RoomMapper roomMapper = new RoomMapperImpl(); + private Room room1; + private Room room2; + private RoomRequestDto roomRequestDto; + private RoomResponseDto roomResponseDto1; + private RoomResponseDto roomResponseDto2; + + private String roomNumber1; + + @BeforeEach + void setUp() { + University university = new University("Sofia University", + "Sofia, Bulgaria", + (short) 1888, + "NEAA", + "https://uni-sofia.bg", + null + ); + roomNumber1 = "111"; + String roomNumber2 = "222"; + room1 = new Room(null, roomNumber1, 20, null); + room1.setId(UUID.randomUUID()); + room2 = new Room(null, roomNumber2, 20, null); + room2.setId(UUID.randomUUID()); + roomRequestDto = new RoomRequestDto(null, roomNumber1, 20); + roomResponseDto1 = new RoomResponseDto(room1.getId(), null, roomNumber1,20,null, null); + roomResponseDto2 = new RoomResponseDto(room2.getId(), null, roomNumber2,20,null, null); + + } + + @Test + void toEntity_shouldMapAllFieldsAndIgnoreId_whenRequestDtoIsValid() { + final Room result = roomMapper.toEntity(roomRequestDto); + assertThat(result.getRoomNumber()).isEqualTo(roomNumber1); + assertThat(result.getId()).isNull(); + } + + @Test + void toResponseDto_shouldMapAllFields_whenFacultyIsValid() { + RoomResponseDto result = roomMapper.toResponseDto(room1); + assertThat(result.id()).isEqualTo(room1.getId()); + assertThat(result.roomNumber()).isEqualTo(roomNumber1); + } + + @Test + void toResponseDtoList_shouldMapAllElements_whenListIsNotEmpty() { + List rooms = List.of(room1, room2); + List results = roomMapper.toResponseDtoList(rooms); + + assertThat(results.size()).isEqualTo(2); + assertThat(results.get(0)).isEqualTo(roomResponseDto1); + assertThat(results.get(1)).isEqualTo(roomResponseDto2); + } + + @Test + void toResponseDtoList_shouldReturnEmptyList_whenListIsEmpty() { + List rooms = List.of(); + List results = roomMapper.toResponseDtoList(rooms); + + assertThat(results).isEmpty(); + } + + @Test + void updateEntity_shouldUpdateAllFields_whenRequestDtoIsValid() { + roomMapper.updateEntityFromDto(roomRequestDto, room2); + assertThat(room2.getRoomNumber()).isEqualTo(roomNumber1); + } + + @Test + void updateEntity_shouldNotChangeId_whenUpdating() { + UUID id = room2.getId(); + roomMapper.updateEntityFromDto(roomRequestDto, room2); + assertThat(room2.getId()).isEqualTo(id); + } + + @Test + void toResponseDto_shouldSetCategoryIdToNull_whenMappingFromRoomEntity() { + RoomResponseDto result = roomMapper.toResponseDto(room1); + + assertThat(result.categoryId()).isNull(); + } +} \ No newline at end of file diff --git a/src/test/java/org/unilab/uniplan/room/RoomServiceTest.java b/src/test/java/org/unilab/uniplan/room/RoomServiceTest.java index 26d1d931..58e3736f 100644 --- a/src/test/java/org/unilab/uniplan/room/RoomServiceTest.java +++ b/src/test/java/org/unilab/uniplan/room/RoomServiceTest.java @@ -1,10 +1,6 @@ package org.unilab.uniplan.room; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -17,114 +13,59 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.unilab.uniplan.exception.ResourceNotFoundException; -import org.unilab.uniplan.room.dto.RoomDto; @ExtendWith(MockitoExtension.class) class RoomServiceTest { @Mock private RoomRepository roomRepository; - - @Mock - private RoomMapper roomMapper; - @InjectMocks private RoomService roomService; - + private Room room; private UUID id; - private UUID facultyId; - private RoomDto dto; - private Room entity; @BeforeEach void setUp() { id = UUID.randomUUID(); - facultyId = UUID.randomUUID(); - dto = new RoomDto(id, facultyId, "101"); - entity = new Room(); - } - - @Test - void testCreateRoomShouldSaveAndReturnDto() { - when(roomMapper.toEntity(dto)).thenReturn(entity); - when(roomRepository.save(entity)).thenReturn(entity); - when(roomMapper.toDto(entity)).thenReturn(dto); - - RoomDto result = roomService.createRoom(dto); - - assertEquals(dto, result); - } - - @Test - void testGetAllRoomsShouldReturnListOfRoomDtos() { - List entities = List.of(entity); - List dtos = List.of(dto); - - when(roomRepository.findAll()).thenReturn(entities); - when(roomMapper.toDtoList(entities)).thenReturn(dtos); - - List result = roomService.getAllRooms(); - - assertEquals(dtos, result); - } - - @Test - void testGetRoomByIdShouldReturnRoomDtoIfFound() { - when(roomRepository.findById(id)).thenReturn(Optional.of(entity)); - when(roomMapper.toDto(entity)).thenReturn(dto); - - RoomDto result = roomService.getRoomById(id); - - assertEquals(dto, result); + room = new Room(); } @Test - void testGetRoomByIdShouldReturnEmptyOptionalIfRoomNotFound() { - when(roomRepository.findById(id)).thenReturn(Optional.empty()); + void save_shouldSaveEntity() { + when(roomRepository.save(room)).thenReturn(room); - ResourceNotFoundException exception = assertThrows(ResourceNotFoundException.class, () -> roomService.getRoomById(id)); + roomService.save(room); - assertTrue(exception.getMessage().contains(String.valueOf(id))); + verify(roomRepository).save(room); } @Test - void testUpdateRoomShouldUpdateAndReturnDtoIfFound() { - when(roomRepository.findById(id)).thenReturn(Optional.of(entity)); - doAnswer(invocation -> null).when(roomMapper).updateEntityFromDto(dto, entity); - when(roomRepository.save(entity)).thenReturn(entity); - when(roomMapper.toDto(entity)).thenReturn(dto); + void findAll_shouldReturnListOfEntities() { + List rooms = List.of(room); + when(roomRepository.findAll()).thenReturn(List.of(room)); - RoomDto result = roomService.updateRoom(id, dto); + List result = roomService.getAll(); - assertEquals(dto, result); + verify(roomRepository).findAll(); + assertEquals(result, rooms); } @Test - void testUpdateRoomShouldReturnEmptyOptionalIfNotFound() { - when(roomRepository.findById(id)).thenReturn(Optional.empty()); + void findById_shouldReturnEntity_whenRoomExists() { + Optional expected = Optional.of(room); + when(roomRepository.findById(id)).thenReturn(expected); - ResourceNotFoundException exception = assertThrows(ResourceNotFoundException.class, () -> roomService.updateRoom(id, dto)); + Optional result = roomService.getById(id); - assertTrue(exception.getMessage().contains(String.valueOf(id))); + verify(roomRepository).findById(id); + assertEquals(expected, result); } @Test - void testDeleteRoomShouldDeleteRoomIfFound() { - when(roomRepository.findById(id)).thenReturn(Optional.of(entity)); - doAnswer(invocation -> null).when(roomRepository).delete(entity); + void delete_shouldDeleteEntity_whenFacultyExists() { + roomService.delete(room); - assertDoesNotThrow(() -> roomService.deleteRoom(id)); - verify(roomRepository).delete(entity); + verify(roomRepository).delete(room); } - @Test - void testDeleteRoomShouldThrowIfNotFound() { - when(roomRepository.findById(id)).thenReturn(Optional.empty()); - - ResourceNotFoundException exception = assertThrows(ResourceNotFoundException.class, () -> - roomService.deleteRoom(id)); - - assertTrue(exception.getMessage().contains(String.valueOf(id))); - } } diff --git a/src/test/java/org/unilab/uniplan/room/RoomWebFacadeTest.java b/src/test/java/org/unilab/uniplan/room/RoomWebFacadeTest.java new file mode 100644 index 00000000..72d0ce32 --- /dev/null +++ b/src/test/java/org/unilab/uniplan/room/RoomWebFacadeTest.java @@ -0,0 +1,129 @@ +package org.unilab.uniplan.room; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.unilab.uniplan.exception.ResourceNotFoundException; +import org.unilab.uniplan.faculty.Faculty; +import org.unilab.uniplan.room.dto.RoomRequestDto; +import org.unilab.uniplan.room.dto.RoomResponseDto; +import org.unilab.uniplan.university.University; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class RoomWebFacadeTest { + @Mock + private RoomMapper roomMapper; + @Mock + private RoomService roomService; + @InjectMocks + private RoomWebFacade roomWebFacade; + private RoomRequestDto requestDto; + private Room entity; + private RoomResponseDto responseDto; + private UUID id; +private UUID categoryId; + + @BeforeEach + void setUp() { + id = UUID.randomUUID(); + UUID facultyId = UUID.randomUUID(); + categoryId = UUID.randomUUID(); + University university = new University("Sofia University", + "Sofia, Bulgaria", + (short) 1888, + "NEAA", + "https://uni-sofia.bg", null + ); + Faculty faculty = new Faculty(university, "FMI", "Faculty Name"); + faculty.setId(facultyId); + String roomNumber = "111"; + requestDto = new RoomRequestDto(facultyId, "222", 20); + entity = new Room(faculty, roomNumber, 20, null); + entity.setId(id); + responseDto = new RoomResponseDto(id, facultyId, roomNumber, 20, categoryId, null); + } + + @Test + void createRoom_shouldSaveRoom_whenRequestIsValid() { + when(roomMapper.toEntity(requestDto)).thenReturn(entity); + + roomWebFacade.createRoom(requestDto); + + final var inOrder = inOrder(roomMapper, roomService); + inOrder.verify(roomMapper).toEntity(requestDto); + inOrder.verify(roomService).save(entity); + } + + @Test + void getAllRooms_shouldReturnListOfResponseDtos() { + List rooms = List.of(responseDto); + when(roomService.getAllRoomResponses()).thenReturn(rooms); + + List results = roomWebFacade.getAllRooms(); + + assertEquals(rooms, results); + assertThat(results.getFirst().categoryId()).isEqualTo(categoryId); + verify(roomService).getAllRoomResponses(); + } + + @Test + void getRoomById_shouldReturnResponseDto_whenRoomExists() { + when(roomService.getRoomResponseById(id)).thenReturn(Optional.of(responseDto)); + + RoomResponseDto result = roomWebFacade.getRoomById(id); + + assertEquals(result, responseDto); + verify(roomService).getRoomResponseById(id); + } + + @Test + void deleteRoom_shouldDeleteRoom_whenRoomExists() { + when(roomService.getById(id)).thenReturn(Optional.of(entity)); + + roomWebFacade.deleteRoom(entity.getId()); + + verify(roomService).delete(entity); + } + + @Test + void deleteRoom_shouldThrowResourceNotFoundException_whenRoomNotFound() { + when(roomService.getById(id)).thenReturn(Optional.empty()); + + assertThatThrownBy(() -> roomWebFacade.deleteRoom(id)) + .isInstanceOf(ResourceNotFoundException.class) + .hasMessageContaining(id.toString()); + } + + @Test + void updateRoom_shouldUpdateRoom_whenRoomExists() { + when(roomService.getById(id)).thenReturn(Optional.of(entity)); + + roomWebFacade.updateRoom(id, requestDto); + + verify(roomMapper).updateEntityFromDto(requestDto, entity); + verify(roomService).save(entity); + } + + @Test + void updateRoom_shouldThrowResourceNotFoundException_whenRoomNotFound() { + when(roomService.getById(id)).thenReturn(Optional.empty()); + + assertThatThrownBy(() -> roomWebFacade.updateRoom(id, requestDto)) + .isInstanceOf(ResourceNotFoundException.class) + .hasMessageContaining(id.toString()); + } +} diff --git a/src/test/java/org/unilab/uniplan/university/UniversityMapperTest.java b/src/test/java/org/unilab/uniplan/university/UniversityMapperTest.java index fba6840f..457dd52c 100644 --- a/src/test/java/org/unilab/uniplan/university/UniversityMapperTest.java +++ b/src/test/java/org/unilab/uniplan/university/UniversityMapperTest.java @@ -27,10 +27,10 @@ void setUp() { "New Name", "New Location", (short) 2000, "New Acc", "https://new.bg" ); university = new University( - "Sofia University", "Sofia, Bulgaria", (short) 1888, "NEAA", "https://uni-sofia.bg" + "Sofia University", "Sofia, Bulgaria", (short) 1888, "NEAA", "https://uni-sofia.bg", null ); university2 = new University( - "Plovdiv University", "Plovdiv", (short) 1961, "NEAA", "https://uni-plovdiv.bg" + "Plovdiv University", "Plovdiv", (short) 1961, "NEAA", "https://uni-plovdiv.bg", null ); id = UUID.randomUUID(); }