Skip to content
Snippets Groups Projects
Commit 72ee5111 authored by Nadezhda Zakopailo's avatar Nadezhda Zakopailo
Browse files

cards microservice api fixes, exceptions

parent 1c975118
No related branches found
No related tags found
1 merge request!1Search
Showing
with 164 additions and 25 deletions
......@@ -31,6 +31,11 @@
<version>0.2.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.0.Final</version>
</dependency>
</dependencies>
......
......@@ -2,7 +2,9 @@ package cz.cvut.fel.flashcards.CardsMicroservice.controller;
import cz.cvut.fel.flashcards.CardsMicroservice.entity.CardBox;
import cz.cvut.fel.flashcards.CardsMicroservice.service.CardBoxService;
import cz.cvut.fel.flashcards.CardsMicroservice.util.CardBoxPostDTO;
import cz.cvut.fel.flashcards.CardsMicroservice.util.CardPostDTO;
import jakarta.validation.Valid;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
......@@ -18,7 +20,7 @@ public class CardBoxController {
private final CardBoxService cardBoxService;
@PostMapping
public ResponseEntity<CardBox> createCardBox(@RequestBody CardBox cardBox) {
public ResponseEntity<CardBox> createCardBox(@Valid @RequestBody CardBoxPostDTO cardBox) {
CardBox createdCardBox = cardBoxService.createCardBox(cardBox);
return new ResponseEntity<>(createdCardBox, HttpStatus.CREATED);
}
......@@ -36,7 +38,7 @@ public class CardBoxController {
}
@PutMapping("/{id}")
public ResponseEntity<CardBox> updateCardBox(@PathVariable Long id, @RequestBody CardBox updatedCardBox) {
public ResponseEntity<CardBox> updateCardBox(@PathVariable Long id, @Valid @RequestBody CardBoxPostDTO updatedCardBox) {
CardBox cardBox = cardBoxService.updateCardBox(id, updatedCardBox);
return ResponseEntity.ok(cardBox);
}
......@@ -48,7 +50,7 @@ public class CardBoxController {
}
@PutMapping("/{cardBoxId}/addCard")
public ResponseEntity<CardBox> addCardToCardBox(@PathVariable Long cardBoxId, @RequestBody CardPostDTO postDTO) {
public ResponseEntity<CardBox> addCardToCardBox(@PathVariable Long cardBoxId, @Valid @RequestBody CardPostDTO postDTO) {
CardBox cardBox = cardBoxService.addCardToBox(cardBoxId, postDTO);
return ResponseEntity.ok(cardBox);
}
......
......@@ -2,6 +2,8 @@ package cz.cvut.fel.flashcards.CardsMicroservice.controller;
import cz.cvut.fel.flashcards.CardsMicroservice.entity.Tag;
import cz.cvut.fel.flashcards.CardsMicroservice.service.TagService;
import cz.cvut.fel.flashcards.CardsMicroservice.util.TagPostDTO;
import jakarta.validation.Valid;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
......@@ -17,7 +19,7 @@ public class TagController {
private final TagService tagService;
@PostMapping
public ResponseEntity<Tag> createTag(@RequestBody Tag tag) {
public ResponseEntity<Tag> createTag(@Valid @RequestBody TagPostDTO tag) {
Tag createdTag = tagService.createTag(tag);
return new ResponseEntity<>(createdTag, HttpStatus.CREATED);
}
......@@ -35,7 +37,7 @@ public class TagController {
}
@PutMapping("/{id}")
public ResponseEntity<Tag> updateTag(@PathVariable Long id, @RequestBody Tag updatedTag) {
public ResponseEntity<Tag> updateTag(@PathVariable Long id, @Valid @RequestBody TagPostDTO updatedTag) {
Tag tag = tagService.updateTag(id, updatedTag);
return ResponseEntity.ok(tag);
}
......
package cz.cvut.fel.flashcards.CardsMicroservice.entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
......@@ -18,6 +19,7 @@ public class Card {
private String question;
@ManyToOne
@ManyToOne(fetch = FetchType.LAZY)
@JsonIgnore
private CardBox cardBox;
}
package cz.cvut.fel.flashcards.CardsMicroservice.entity;
import com.fasterxml.jackson.annotation.*;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.*;
import org.hibernate.annotations.ColumnDefault;
import java.util.HashSet;
......@@ -13,6 +12,10 @@ import java.util.Set;
@Entity
@NoArgsConstructor
@AllArgsConstructor
@JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id"
)
public class CardBox {
@Id
......@@ -23,12 +26,17 @@ public class CardBox {
private String title;
@ColumnDefault(value = "false")
@JsonProperty("isPublic")
private boolean isPublic;
@OneToMany
@OneToMany(fetch = FetchType.LAZY)
@JsonManagedReference
@ToString.Exclude
@EqualsAndHashCode.Exclude
private Set<Card> cards = new HashSet<>();
@ManyToMany
@EqualsAndHashCode.Exclude
private Set<Tag> tags = new HashSet<>();
@Column(nullable = false)
......
package cz.cvut.fel.flashcards.CardsMicroservice.entity;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.*;
import java.util.HashSet;
import java.util.Set;
......@@ -12,6 +14,10 @@ import java.util.Set;
@Entity
@NoArgsConstructor
@AllArgsConstructor
@JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id"
)
public class Tag {
@Id
......@@ -24,5 +30,6 @@ public class Tag {
private String color;
@ManyToMany
@EqualsAndHashCode.Exclude
private Set<CardBox> cardBoxes = new HashSet<>();
}
......@@ -18,7 +18,7 @@ public interface CardBoxRepository extends JpaRepository<CardBox, Long> {
"AND cb.isPublic = true")
List<CardBox> searchCardBoxesByName(@Param("name") String name);
@Query("SELECT DISTINCT cb FROM CardBox cb JOIN cb.tags t " +
@Query("SELECT DISTINCT cb FROM CardBox cb LEFT JOIN cb.tags t " +
"WHERE (LOWER(cb.title) LIKE LOWER(CONCAT('%', :keyword, '%')) " +
"OR LOWER(t.tagName) LIKE LOWER(CONCAT('%', :keyword, '%'))) " +
"AND cb.isPublic = true")
......
......@@ -13,4 +13,6 @@ public interface TagRepository extends JpaRepository<Tag, Long> {
"WHERE LOWER(t.tagName) LIKE LOWER(CONCAT('%', :name, '%'))"
)
List<Tag> searchByName(@Param("name") String name);
boolean existsByTagName(String tagName);
}
......@@ -4,6 +4,8 @@ import cz.cvut.fel.flashcards.CardsMicroservice.entity.Card;
import cz.cvut.fel.flashcards.CardsMicroservice.entity.CardBox;
import cz.cvut.fel.flashcards.CardsMicroservice.entity.Tag;
import cz.cvut.fel.flashcards.CardsMicroservice.repository.CardBoxRepository;
import cz.cvut.fel.flashcards.CardsMicroservice.util.CardBoxMapper;
import cz.cvut.fel.flashcards.CardsMicroservice.util.CardBoxPostDTO;
import cz.cvut.fel.flashcards.CardsMicroservice.util.CardPostDTO;
import jakarta.persistence.EntityNotFoundException;
import lombok.AllArgsConstructor;
......@@ -21,8 +23,8 @@ public class CardBoxService {
private final CardService cardService;
private final TagService tagService;
public CardBox createCardBox(CardBox cardBox) {
return cardBoxRepository.save(cardBox);
public CardBox createCardBox(CardBoxPostDTO cardBox) {
return cardBoxRepository.save(CardBoxMapper.getInstance().toCardBox(cardBox));
}
public CardBox getCardBoxById(Long id) {
......@@ -34,13 +36,13 @@ public class CardBoxService {
return cardBoxRepository.findAll();
}
public CardBox updateCardBox(Long id, CardBox updatedCardBox) {
public CardBox updateCardBox(Long id, CardBoxPostDTO updatedCardBox) {
CardBox existingCardBox = getCardBoxById(id);
existingCardBox.setTitle(updatedCardBox.getTitle());
existingCardBox.setPublic(updatedCardBox.isPublic());
existingCardBox.setOwnerId(updatedCardBox.getOwnerId());
existingCardBox.setCards(updatedCardBox.getCards());
existingCardBox.setTags(updatedCardBox.getTags());
// existingCardBox.setCards(updatedCardBox.getCards());
// existingCardBox.setTags(updatedCardBox.getTags());
return cardBoxRepository.save(existingCardBox);
}
......
......@@ -5,6 +5,7 @@ import cz.cvut.fel.flashcards.CardsMicroservice.entity.CardBox;
import cz.cvut.fel.flashcards.CardsMicroservice.repository.CardRepository;
import cz.cvut.fel.flashcards.CardsMicroservice.util.CardMapper;
import cz.cvut.fel.flashcards.CardsMicroservice.util.CardPostDTO;
import jakarta.persistence.EntityNotFoundException;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
......@@ -29,7 +30,7 @@ public class CardService {
public Card getCardById(Long id) {
return cardRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Card not found with id: " + id));
.orElseThrow(() -> new EntityNotFoundException("Card not found with id: " + id));
}
public List<Card> getAllCards() {
......
......@@ -3,6 +3,10 @@ package cz.cvut.fel.flashcards.CardsMicroservice.service;
import cz.cvut.fel.flashcards.CardsMicroservice.entity.CardBox;
import cz.cvut.fel.flashcards.CardsMicroservice.entity.Tag;
import cz.cvut.fel.flashcards.CardsMicroservice.repository.TagRepository;
import cz.cvut.fel.flashcards.CardsMicroservice.util.TagMapper;
import cz.cvut.fel.flashcards.CardsMicroservice.util.TagPostDTO;
import cz.cvut.fel.flashcards.CardsMicroservice.util.exception.DuplicateTagException;
import jakarta.persistence.EntityNotFoundException;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
......@@ -16,20 +20,26 @@ public class TagService {
private final TagRepository tagRepository;
public Tag createTag(Tag tag) {
return tagRepository.save(tag);
public Tag createTag(TagPostDTO tag) {
if (tagRepository.existsByTagName(tag.getTagName())) {
throw new DuplicateTagException("Tag with this name already exists: " + tag.getTagName());
}
return tagRepository.save(TagMapper.getInstance().toTag(tag));
}
public Tag getTagById(Long id) {
return tagRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Tag not found with id: " + id));
.orElseThrow(() -> new EntityNotFoundException("Tag not found with id: " + id));
}
public Tag updateTag(Long id, Tag updatedTag) {
public Tag updateTag(Long id, TagPostDTO updatedTag) {
if (tagRepository.existsByTagName(updatedTag.getTagName())) {
throw new DuplicateTagException("Tag with this name already exists: " + updatedTag.getTagName());
}
Tag existingTag = getTagById(id);
existingTag.setTagName(updatedTag.getTagName());
existingTag.setColor(updatedTag.getColor());
existingTag.setCardBoxes(updatedTag.getCardBoxes());
// existingTag.setCardBoxes(updatedTag.getCardBoxes());
return tagRepository.save(existingTag);
}
......
package cz.cvut.fel.flashcards.CardsMicroservice.util;
import cz.cvut.fel.flashcards.CardsMicroservice.entity.CardBox;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper(componentModel = "spring")
public interface CardBoxMapper {
CardBoxMapper INSTANCE = Mappers.getMapper(CardBoxMapper.class);
CardBox toCardBox(CardBoxPostDTO cardBoxPostDTO);
CardBoxPostDTO toCardBoxPostDTO(CardBox cardBox);
static CardBoxMapper getInstance() { return INSTANCE; }
}
package cz.cvut.fel.flashcards.CardsMicroservice.util;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class CardBoxPostDTO {
@NotBlank(message = "title cannot be blank")
private String title;
@JsonProperty("isPublic")
private boolean isPublic;
@NotNull(message = "owner id cannot be blank")
private Long ownerId;
}
package cz.cvut.fel.flashcards.CardsMicroservice.util;
import cz.cvut.fel.flashcards.CardsMicroservice.entity.Tag;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper(componentModel = "spring")
public interface TagMapper {
TagMapper INSTANCE = Mappers.getMapper(TagMapper.class);
Tag toTag(TagPostDTO tagPostDTO);
TagPostDTO toTagPostDTO(Tag tag);
static TagMapper getInstance() { return INSTANCE; }
}
package cz.cvut.fel.flashcards.CardsMicroservice.util;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class TagPostDTO {
@NotBlank(message = "title cannot be blank")
private String tagName;
private String color;
}
package cz.cvut.fel.flashcards.CardsMicroservice.util.exception;
public class DuplicateTagException extends RuntimeException {
public DuplicateTagException(String message) {
super(message);
}
}
......@@ -3,10 +3,12 @@ package cz.cvut.fel.flashcards.CardsMicroservice.util.exception;
import jakarta.persistence.EntityNotFoundException;
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 {
......@@ -20,4 +22,27 @@ public class GlobalExceptionHandler {
);
return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationException(MethodArgumentNotValidException ex) {
String errorMessage = ex.getBindingResult().getFieldErrors().stream()
.map(fieldError -> fieldError.getField() + ": " + fieldError.getDefaultMessage())
.collect(Collectors.joining("; "));
ErrorResponse response = new ErrorResponse(
HttpStatus.BAD_REQUEST.value(),
"Validation error(s): " + errorMessage,
LocalDateTime.now()
);
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(DuplicateTagException.class)
public ResponseEntity<ErrorResponse> handleDuplicateEmail(DuplicateTagException ex) {
ErrorResponse response = new ErrorResponse(
HttpStatus.CONFLICT.value(),
ex.getMessage(),
LocalDateTime.now()
);
return new ResponseEntity<>(response, HttpStatus.CONFLICT);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment