Skip to content
Snippets Groups Projects
Commit 77e1ea3f authored by Dominika Zemanovičová's avatar Dominika Zemanovičová
Browse files

Merge branch 'exercise' into 'main'

Exercise

See merge request !16
parents a80cf535 1e382e21
No related branches found
No related tags found
1 merge request!16Exercise
Pipeline #
Showing
with 850 additions and 0 deletions
package org.fuseri.model.dto.exercise;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public class AnswerCreateDto {
@NotBlank
private String text;
@NotNull
private boolean correct;
@NotBlank
private String questionId;
}
package org.fuseri.model.dto.exercise;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.fuseri.model.dto.common.DomainObjectDto;
import java.util.Objects;
@AllArgsConstructor
@Getter
public class AnswerDto extends DomainObjectDto {
@NotBlank
private String text;
@NotNull
private boolean correct;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AnswerDto answerDto = (AnswerDto) o;
if (correct != answerDto.correct) return false;
return Objects.equals(text, answerDto.text);
}
@Override
public int hashCode() {
int result = text != null ? text.hashCode() : 0;
result = 31 * result + (correct ? 1 : 0);
return result;
}
}
package org.fuseri.model.dto.exercise;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public class AnswerInQuestionCreateDto {
@NotBlank
private String text;
@NotNull
private boolean correct;
}
package org.fuseri.model.dto.exercise;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.List;
@AllArgsConstructor
@Getter
public class AnswersCreateDto {
@NotBlank
private String questionId;
@Valid
private List<AnswerInQuestionCreateDto> answers;
}
package org.fuseri.model.dto.exercise;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.PositiveOrZero;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public class ExerciseCreateDto {
@NotBlank
private String name;
@NotBlank
private String description;
@NotNull
@PositiveOrZero
private int difficulty;
@NotBlank
private String courseId;
}
package org.fuseri.model.dto.exercise;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.PositiveOrZero;
import lombok.Getter;
import lombok.Setter;
import org.fuseri.model.dto.common.DomainObjectDto;
import java.util.Objects;
@Getter
@Setter
public class ExerciseDto extends DomainObjectDto {
@NotBlank
private String name;
@NotBlank
private String description;
@NotNull
@PositiveOrZero
private int difficulty;
@NotBlank
private String courseId;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ExerciseDto that = (ExerciseDto) o;
if (difficulty != that.difficulty) return false;
if (!Objects.equals(name, that.name)) return false;
if (!Objects.equals(description, that.description)) return false;
return Objects.equals(courseId, that.courseId);
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + (description != null ? description.hashCode() : 0);
result = 31 * result + difficulty;
result = 31 * result + (courseId != null ? courseId.hashCode() : 0);
return result;
}
}
package org.fuseri.model.dto.exercise;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.List;
@AllArgsConstructor
@Getter
public class QuestionCreateDto {
@NotBlank
private String text;
@NotBlank
private String exerciseId;
@Valid
private List<AnswerInQuestionCreateDto> answers;
}
package org.fuseri.model.dto.exercise;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
import org.fuseri.model.dto.common.DomainObjectDto;
import java.util.List;
import java.util.Objects;
@Getter
@Setter
public class QuestionDto extends DomainObjectDto {
@NotBlank
private String text;
@NotBlank
private String exerciseId;
@Valid
private List<AnswerDto> answers;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
QuestionDto that = (QuestionDto) o;
if (!Objects.equals(text, that.text)) return false;
if (!Objects.equals(exerciseId, that.exerciseId)) return false;
return Objects.equals(answers, that.answers);
}
@Override
public int hashCode() {
int result = text != null ? text.hashCode() : 0;
result = 31 * result + (exerciseId != null ? exerciseId.hashCode() : 0);
result = 31 * result + (answers != null ? answers.hashCode() : 0);
return result;
}
}
package org.fuseri.model.dto.exercise;
import jakarta.validation.constraints.NotBlank;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class QuestionUpdateDto {
@NotBlank
private String text;
@NotBlank
private String exerciseId;
}
package org.fuseri.moduleexercise.answer;
import lombok.*;
import org.fuseri.moduleexercise.common.DomainObject;
/**
* Represent Answer entity
*/
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Answer extends DomainObject {
private String text;
private boolean correct;
private String questionId;
}
package org.fuseri.moduleexercise.answer;
import jakarta.persistence.EntityNotFoundException;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import org.fuseri.model.dto.exercise.AnswerCreateDto;
import org.fuseri.model.dto.exercise.AnswerDto;
import org.fuseri.model.dto.exercise.AnswersCreateDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;
import java.util.List;
/**
* Represent a REST API controller for answers
* Handle HTTP requests related to answers
*/
@RestController
@RequestMapping("/answers")
public class AnswerController {
private final AnswerFacade facade;
@Autowired
public AnswerController(AnswerFacade facade) {
this.facade = facade;
}
/**
* Retrieve a list of AnswerDto objects which belong to question with questionId
*
* @param questionId the ID of the question for which to retrieve answers
* @return a List of AnswerDto objects
*/
@GetMapping("/{question-id}")
public List<AnswerDto> findAllByQuestionId(@NotBlank @PathVariable("question-id") String questionId) {
return facade.findAllByQuestionId(questionId);
}
/**
* Create a new answer for the given question ID
*
* @param dto the AnswerCreateDto object containing information about the answer to create
* @return an AnswerDto object representing the newly created answer
* @throws ResponseStatusException if the question ID specified in the dto does not exist
*/
@PostMapping
public List<AnswerDto> createMultiple(@Valid @RequestBody AnswersCreateDto dto) {
try {
return facade.createMultiple(dto);
} catch (EntityNotFoundException e) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, e.getMessage());
}
}
/**
* Update an answer
*
* @param id of answer to update
* @param dto dto with updated answer information
* @throws ResponseStatusException if the question id specified in the AnswerCreateDto dto does not exist
*/
@PutMapping("/{id}")
public AnswerDto update(@NotBlank @PathVariable String id, @Valid @RequestBody AnswerCreateDto dto) {
try {
return facade.update(id, dto);
} catch (EntityNotFoundException e) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND,
e.getMessage());
}
}
/**
* Delete answer with the given id
*
* @param id of answer to delete
* @throws ResponseStatusException if answer with specified id does not exist
*/
@DeleteMapping("/{id}")
public void delete(@NotBlank @PathVariable String id) {
try {
facade.delete(id);
} catch (EntityNotFoundException e) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, e.getMessage());
}
}
}
package org.fuseri.moduleexercise.answer;
import org.fuseri.model.dto.exercise.AnswerCreateDto;
import org.fuseri.model.dto.exercise.AnswerDto;
import org.fuseri.model.dto.exercise.AnswersCreateDto;
import org.fuseri.moduleexercise.question.Question;
import org.fuseri.moduleexercise.question.QuestionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
/**
* Represent facade for managing answers
* Provide simplified interface for manipulating with answers
*/
@Service
public class AnswerFacade {
private final AnswerService answerService;
private final QuestionService questionService;
private final AnswerMapper mapper;
/**
* Constructor for AnswerFacade
*
* @param answerService the service responsible for handling answer-related logic
* @param questionService the service responsible for handling question-related logic
* @param mapper the mapper responsible for converting between DTOs and entities
*/
@Autowired
public AnswerFacade(AnswerService answerService, QuestionService questionService, AnswerMapper mapper) {
this.answerService = answerService;
this.questionService = questionService;
this.mapper = mapper;
}
/**
* Retrieve a list of AnswerDto objects which belong to question with questionId
*
* @param questionId the ID of the question for which to retrieve answers
* @return a List of AnswerDto objects
*/
public List<AnswerDto> findAllByQuestionId(String questionId) {
return mapper.toDtoList(answerService.findAllByQuestionId(questionId));
}
/**
* Create a new answer for the given question ID
*
* @param dto the AnswerCreateDto object containing information about the answer to create
* @return an AnswerDto object representing the newly created answer
*/
public List<AnswerDto> createMultiple(@RequestBody AnswersCreateDto dto) {
List<Answer> createdAnswers = new ArrayList<>();
for (var answerDto : dto.getAnswers()) {
Question question;
question = questionService.find(dto.getQuestionId());
Answer answer = mapper.fromCreateDto(answerDto);
answer.setQuestionId(question.getId());
var createdAnswer = answerService.create(answer);
question.getAnswers().add(answer);
createdAnswers.add(createdAnswer);
}
return mapper.toDtoList(createdAnswers);
}
/**
* Update an answer
*
* @param id of answer to update
* @param dto dto with updated answer information
*/
public AnswerDto update(String id, AnswerCreateDto dto) {
var updatedAnswer = mapper.fromCreateDto(dto);
updatedAnswer.setId(id);
answerService.update(updatedAnswer);
Question question;
question = questionService.find(dto.getQuestionId());
var questionAnswers = question.getAnswers();
questionAnswers.removeIf(a -> a.getId().equals(id));
questionAnswers.add(updatedAnswer);
question.setAnswers(questionAnswers);
questionService.update(question);
return mapper.toDto(updatedAnswer);
}
/**
* Delete answer with the given id
*
* @param id of answer to delete
*/
public void delete(String id) {
var answer = answerService.find(id);
Question question;
question = questionService.find(answer.getQuestionId());
var questionAnswers = question.getAnswers();
questionAnswers.removeIf(a -> a.getId().equals(answer.getId()));
question.setAnswers(questionAnswers);
answerService.delete(id);
}
}
package org.fuseri.moduleexercise.answer;
import org.fuseri.model.dto.exercise.AnswerCreateDto;
import org.fuseri.model.dto.exercise.AnswerDto;
import org.fuseri.model.dto.exercise.AnswerInQuestionCreateDto;
import org.fuseri.moduleexercise.common.DomainMapper;
import org.mapstruct.Mapper;
/**
* Mapper between Answers and their corresponding DTOs
*/
@Mapper
public interface AnswerMapper extends DomainMapper<Answer, AnswerDto> {
/**
* Convert DTO of type AnswerCreateDto to Answer
*
* @param dto DTO to be converted
* @return corresponding Answer entity created from DTO
*/
Answer fromCreateDto(AnswerCreateDto dto);
/**
* Convert DTO of type AnswerInQuestionCreateDto to Answer
* @param dto DTO to be converted
* @return corresponding Answer entity created from DTO
*/
Answer fromCreateDto(AnswerInQuestionCreateDto dto);
}
package org.fuseri.moduleexercise.answer;
import org.fuseri.moduleexercise.common.DomainRepository;
import org.springframework.data.repository.query.Param;
import java.util.List;
/**
* A repository interface for managing Answer entities
*/
public interface AnswerRepository extends DomainRepository<Answer, String> {
/**
* Find all answers to a question with the specified ID
*
* @param questionId the ID of the question to find answers for
* @return a list of all answers to the specified question
*/
List<Answer> findByQuestionId(@Param("questionId") String questionId);
}
\ No newline at end of file
package org.fuseri.moduleexercise.answer;
import org.fuseri.moduleexercise.common.DomainRepositoryImpl;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* An implementation of the AnswerRepository interface
* Provides access to Answer entities stored in a data source
*/
@Repository
public class AnswerRepositoryImpl extends DomainRepositoryImpl<Answer> implements AnswerRepository {
/**
* Find all answers to a question with the specified ID
*
* @param questionId the ID of the question to find answers for
* @return a list of all answers to the specified question
*/
@Override
public List<Answer> findByQuestionId(String questionId) {
return getItems()
.stream()
.filter(e -> e.getQuestionId().equals(questionId))
.toList();
}
}
package org.fuseri.moduleexercise.answer;
import jakarta.persistence.EntityNotFoundException;
import lombok.Getter;
import org.fuseri.moduleexercise.common.DomainService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* Represent a service for managing Answer entities
*/
@Service
public class AnswerService extends DomainService<Answer> {
/**
* The repository instance used by this service
*/
@Getter
private final AnswerRepository repository;
/**
* Construct a new instance of AnswerService with the specified repository
*
* @param repository the repository instance to be used by this service
*/
@Autowired
public AnswerService(AnswerRepository repository) {
this.repository = repository;
}
/**
* Retrieve a list of Answer entities with the specified question ID
*
* @param questionId the ID of the question to retrieve answers for
* @return a list of Answer entities with the specified question ID
*/
@Transactional(readOnly = true)
public List<Answer> findAllByQuestionId(String questionId) {
return repository.findByQuestionId(questionId);
}
/**
* Retrieve the Answer entity with the specified id
*
* @param id the id of the Answer entity to retrieve
* @return the Answer entity with the specified id
* @throws EntityNotFoundException if no Answer entity exists with the specified id
*/
@Transactional(readOnly = true)
public Answer find(String id) {
return repository.findById(id)
.orElseThrow(() -> new EntityNotFoundException("Answer '" + id + "' not found."));
}
}
package org.fuseri.moduleexercise.common;
import org.fuseri.model.dto.common.DomainObjectDto;
import org.fuseri.model.dto.common.Result;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.springframework.data.domain.Page;
import java.util.List;
/**
* Base interface for MapStruct Mappers
* It defines methods to convert DTOs to entities and vice versa
*
* @param <T> the type of the entity
* @param <S> the type of the DTO
*/
public interface DomainMapper<T extends DomainObject, S extends DomainObjectDto> {
/**
* Convert DTO to its corresponding entity
*
* @param dto DTO to be converted
* @return corresponding entity created from DTO
*/
T fromDto(S dto);
/**
* Convert entity to its corresponding DTO
*
* @param entity entity to be converted
* @return corresponding DTO created from entity
*/
S toDto(T entity);
/**
* Convert List of entities to List of corresponding DTOs
*
* @param entities entities to be converted
* @return corresponding list of DTO objects
*/
List<S> toDtoList(List<T> entities);
/**
* Convert a {@link org.springframework.data.domain.Page} containing entities to a
* {@link Result} object containing a list of their corresponding DTO objects, along with
* pagination information
*
* @param source the Page of entities to be converted
* @return a Result object containing a list of DTO objects and pagination information
*/
@Mappings({
@Mapping(target = "total", expression = "java(source.getTotalElements())"),
@Mapping(target = "page", expression = "java(source.getNumber())"),
@Mapping(target = "pageSize", expression = "java(source.getSize())"),
@Mapping(target = "items", expression = "java(toDtoList(source.getContent()))")
})
Result<S> toResult(Page<T> source);
}
package org.fuseri.moduleexercise.common;
import jakarta.persistence.Id;
import jakarta.persistence.MappedSuperclass;
import lombok.Getter;
import lombok.Setter;
import java.util.UUID;
/**
* Represent the base class for entities in the module.
*/
@Getter
@Setter
@MappedSuperclass
public abstract class DomainObject {
@Id
private String id = UUID.randomUUID().toString();
}
package org.fuseri.moduleexercise.common;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import java.util.Optional;
/**
* Dummy interface of repository. Later will be replaced by JpaRepository.
*
* @param <T> entity
* @param <ID> entity ID
*/
public interface DomainRepository<T, ID> {
/**
* Save the specified entity
*
* @param entity entity to be saved
* @return created entity
*/
T save(T entity);
/**
* Find entity by ID
*
* @param id ID of entity to be found
* @return {@code Optional} containing the found entity,
* or an empty {@code Optional} if no such entity exists
*/
Optional<T> findById(ID id);
/**
* Retrieve a page of entities according to the specified pagination information
*
* @param pageRequest the pagination information for the query
* @return a page of entities that satisfy the pagination criteria
*/
Page<T> findAll(PageRequest pageRequest);
/**
* Update entity
*
* @param entity entity to update
* @return updated entity
*/
T update(T entity);
/**
* Delete the entity with the specified id
* Note that this does not do cascade deleting.
* We will have cascade deleting with usage of JpaRepository
*
* @param id the id of the entity to be deleted
*/
void deleteById(ID id);
}
\ No newline at end of file
package org.fuseri.moduleexercise.common;
import jakarta.persistence.EntityNotFoundException;
import lombok.Getter;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
/**
* Dummy implementation of repository. Later will be replaced by JpaRepository.
*
* @param <T> entity
*/
public abstract class DomainRepositoryImpl<T extends DomainObject> implements DomainRepository<T, String> {
/**
* Dummy database
*/
@Getter
private final Set<T> items = new HashSet<>();
/**
* Save the specified entity
*
* @param entity entity to be saved
* @return created entity
*/
@Override
public T save(T entity) {
items.add(entity);
return entity;
}
/**
* Find entity by ID
*
* @param id ID of entity to be found
* @return {@code Optional} containing the found entity,
* or an empty {@code Optional} if no such entity exists
*/
@Override
public Optional<T> findById(String id) {
return items.stream()
.filter(e -> e.getId().equals(id))
.findFirst();
}
/**
* Retrieve a page of entities according to the specified pagination information
*
* @param pageRequest the pagination information for the query
* @return a page of entities that satisfy the pagination criteria
*/
@Override
public Page<T> findAll(PageRequest pageRequest) {
int startIndex = pageRequest.getPageNumber() * pageRequest.getPageSize();
List<T> pageEntities = items.stream()
.skip(startIndex)
.limit(pageRequest.getPageSize())
.toList();
return new PageImpl<>(pageEntities, pageRequest, pageEntities.size());
}
/**
* Update entity
*
* @param entity entity to update
* @return updated entity
*/
@Override
public T update(T entity) {
if (entity == null || entity.getId() == null) {
throw new IllegalArgumentException("Entity and its ID can not be null.");
}
var optionalEntity = findById(entity.getId());
if (optionalEntity.isEmpty()) {
throw new EntityNotFoundException("Entity not found with ID: " + entity.getId());
}
T oldEntity = optionalEntity.get();
items.remove(oldEntity);
items.add(entity);
return entity;
}
/**
* Delete the entity with the specified id
* Note that this does not do cascade deleting.
* We will have cascade deleting with usage of JpaRepository
*
* @param id the id of the entity to be deleted
*/
@Override
public void deleteById(String id) {
items.removeIf(e -> e.getId().equals(id));
}
}
\ No newline at end of file
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