diff --git a/src/main/java/org/wikitolearn/midtier/course/web/rest/CourseController.java b/src/main/java/org/wikitolearn/midtier/course/web/rest/CourseController.java index d976a30..b2548b6 100644 --- a/src/main/java/org/wikitolearn/midtier/course/web/rest/CourseController.java +++ b/src/main/java/org/wikitolearn/midtier/course/web/rest/CourseController.java @@ -1,196 +1,198 @@ package org.wikitolearn.midtier.course.web.rest; import java.util.Collections; import java.util.Optional; import java.util.stream.Collectors; import javax.validation.Valid; import org.modelmapper.ModelMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import org.wikitolearn.midtier.course.entity.Course; import org.wikitolearn.midtier.course.entity.EntityList; import org.wikitolearn.midtier.course.entity.ErrorJson; import org.wikitolearn.midtier.course.entity.dto.in.AddCourseChaptersDto; -import org.wikitolearn.midtier.course.entity.dto.in.GetCourseDto; -import org.wikitolearn.midtier.course.entity.dto.in.GetCoursesDto; import org.wikitolearn.midtier.course.entity.dto.in.StoreCourseDto; import org.wikitolearn.midtier.course.entity.dto.in.UpdateCourseChaptersDto; import org.wikitolearn.midtier.course.entity.dto.in.UpdateCourseDto; import org.wikitolearn.midtier.course.entity.dto.out.AddedCourseChaptersDto; +import org.wikitolearn.midtier.course.entity.dto.out.GetCourseDto; +import org.wikitolearn.midtier.course.entity.dto.out.GetCoursesDto; import org.wikitolearn.midtier.course.entity.dto.out.StoredOrUpdatedCourseDto; import org.wikitolearn.midtier.course.exception.InvalidResourceCreateException; import org.wikitolearn.midtier.course.service.ChapterService; import org.wikitolearn.midtier.course.service.CourseService; import org.wikitolearn.midtier.course.service.PageService; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import io.swagger.annotations.ApiResponse; import io.swagger.annotations.ApiResponses; import lombok.extern.slf4j.Slf4j; @RestController @Slf4j @RequestMapping(value = "/courses") public class CourseController { @Autowired private ModelMapper modelMapper; @Autowired private CourseService courseService; @Autowired private ChapterService chapterService; @Autowired private PageService pageService; @Autowired private ObjectMapper objectMapper; @ApiResponses({ - @ApiResponse(code = 200, message = "Success", response = GetCoursesDto.class) + @ApiResponse(code = 200, message = "Success", response = GetCoursesDto.class), + @ApiResponse(code = 404, message = "Not Found", response = ErrorJson.class) }) @GetMapping(produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public GetCoursesDto getCourses(@RequestParam(value="page", required=false) Integer page) { MultiValueMap params = new LinkedMultiValueMap<>(); params.add("page", String.valueOf(page)); EntityList courses = courseService.findAll(params); return modelMapper.map(courses, GetCoursesDto.class); } @ApiResponses({ - @ApiResponse(code = 200, message = "Success", response = GetCourseDto.class) + @ApiResponse(code = 200, message = "Success", response = GetCourseDto.class), + @ApiResponse(code = 404, message = "Not Found", response = ErrorJson.class) }) @GetMapping(value = "/{courseId}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public GetCourseDto getCourse(@PathVariable String courseId) throws JsonProcessingException { MultiValueMap chapterParams = new LinkedMultiValueMap<>(); ObjectNode projectionJsonObject = objectMapper.getNodeFactory().objectNode().put("title", 1).put("pages", 1); chapterParams.add("projection", objectMapper.writeValueAsString(projectionJsonObject)); MultiValueMap pagesParams = new LinkedMultiValueMap<>(); projectionJsonObject = objectMapper.getNodeFactory().objectNode().put("title", 1); pagesParams.add("projection", objectMapper.writeValueAsString(projectionJsonObject)); Course course = courseService.find(courseId, null); course.setChapters( Optional .ofNullable(course.getChapters()) .orElseGet(Collections::emptyList) .parallelStream() .map(chapter -> { chapter = chapterService.find(chapter.getId(), chapterParams); chapter.setPages( Optional .ofNullable(chapter.getPages()) .orElseGet(Collections::emptyList) .parallelStream() .map(page -> pageService.find(page.getId(), pagesParams)) .collect(Collectors.toList())); return chapter; }) .collect(Collectors.toList())); return modelMapper.map(course, GetCourseDto.class); } @ApiResponses({ @ApiResponse(code = 201, message = "Created", response = StoredOrUpdatedCourseDto.class), @ApiResponse(code = 401, message = "Unauthorized"), @ApiResponse(code = 403, message = "Forbidden"), @ApiResponse(code = 500, message = "Internal Server Error", response = ErrorJson.class), @ApiResponse(code = 422, message = "Unprocessable Entity") }) @PostMapping(produces = MediaType.APPLICATION_JSON_UTF8_VALUE, consumes = MediaType.APPLICATION_JSON_UTF8_VALUE) @ResponseStatus(HttpStatus.CREATED) public StoredOrUpdatedCourseDto storeCourse(@Valid @RequestBody StoreCourseDto course) throws JsonProcessingException { Course storedCourse = courseService.save(modelMapper.map(course, Course.class)); return modelMapper.map(storedCourse, StoredOrUpdatedCourseDto.class); } @ApiResponses({ @ApiResponse(code = 200, message = "Success", response = StoredOrUpdatedCourseDto.class), @ApiResponse(code = 401, message = "Unauthorized"), @ApiResponse(code = 403, message = "Forbidden"), @ApiResponse(code = 500, message = "Internal Server Error", response = ErrorJson.class), @ApiResponse(code = 422, message = "Unprocessable Entity") }) @PatchMapping(value = "/{courseId}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE, consumes = MediaType.APPLICATION_JSON_UTF8_VALUE) public StoredOrUpdatedCourseDto updateCourse(@PathVariable("courseId") String courseId, @RequestBody UpdateCourseDto course, @RequestHeader("If-Match") String etag) throws JsonProcessingException { Course courseToUpdate = modelMapper.map(course, Course.class); courseToUpdate.setId(courseId); courseToUpdate.setEtag(etag); Course updatedCourse = courseService.update(courseToUpdate); return modelMapper.map(updatedCourse, StoredOrUpdatedCourseDto.class); } @ApiResponses({ @ApiResponse(code = 204, message = "No Content"), @ApiResponse(code = 401, message = "Unauthorized"), @ApiResponse(code = 403, message = "Forbidden"), @ApiResponse(code = 500, message = "Internal Server Error", response = ErrorJson.class) }) @DeleteMapping(value = "/{courseId}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE, consumes = MediaType.APPLICATION_JSON_UTF8_VALUE) @ResponseStatus(HttpStatus.NO_CONTENT) public void deleteCourse(@PathVariable("courseId") String courseId, @RequestHeader("If-Match") String etag) throws JsonProcessingException { Course course = new Course(); course.setId(courseId); course.setEtag(etag); courseService.delete(course); } @ApiResponses({ @ApiResponse(code = 200, message = "Success", response = StoredOrUpdatedCourseDto.class), @ApiResponse(code = 401, message = "Unauthorized"), @ApiResponse(code = 403, message = "Forbidden"), @ApiResponse(code = 500, message = "Internal Server Error", response = ErrorJson.class), @ApiResponse(code = 422, message = "Unprocessable Entity") }) @PatchMapping(value = "/{courseId}/chapters", produces = MediaType.APPLICATION_JSON_UTF8_VALUE, consumes = MediaType.APPLICATION_JSON_UTF8_VALUE) public StoredOrUpdatedCourseDto updateChapters(@PathVariable("courseId") String courseId, @RequestBody UpdateCourseChaptersDto course, @RequestHeader("If-Match") String etag) throws JsonProcessingException, InvalidResourceCreateException { Course courseToUpdate = modelMapper.map(course, Course.class); courseToUpdate.setId(courseId); courseToUpdate.setEtag(etag); Course updatedCourse = courseService.updateChapters(courseToUpdate); return modelMapper.map(updatedCourse, StoredOrUpdatedCourseDto.class); } @ApiResponses({ @ApiResponse(code = 200, message = "Success", response = AddedCourseChaptersDto.class), @ApiResponse(code = 401, message = "Unauthorized"), @ApiResponse(code = 403, message = "Forbidden"), @ApiResponse(code = 500, message = "Internal Server Error", response = ErrorJson.class), @ApiResponse(code = 422, message = "Unprocessable Entity") }) @PostMapping(value = "/{courseId}/chapters", produces = MediaType.APPLICATION_JSON_UTF8_VALUE, consumes = MediaType.APPLICATION_JSON_UTF8_VALUE) public AddedCourseChaptersDto addChapters(@PathVariable("courseId") String courseId, @RequestBody AddCourseChaptersDto course, @RequestHeader("If-Match") String etag) throws JsonProcessingException, InvalidResourceCreateException { Course courseToUpdate = modelMapper.map(course, Course.class); courseToUpdate.setId(courseId); courseToUpdate.setEtag(etag); Course updatedCourse = courseService.addChapters(courseToUpdate); return modelMapper.map(updatedCourse, AddedCourseChaptersDto.class); } } diff --git a/src/main/java/org/wikitolearn/midtier/course/web/rest/HistoryController.java b/src/main/java/org/wikitolearn/midtier/course/web/rest/HistoryController.java index 002a27c..05844b6 100644 --- a/src/main/java/org/wikitolearn/midtier/course/web/rest/HistoryController.java +++ b/src/main/java/org/wikitolearn/midtier/course/web/rest/HistoryController.java @@ -1,156 +1,171 @@ package org.wikitolearn.midtier.course.web.rest; import java.util.Collections; import java.util.Optional; import java.util.stream.Collectors; import org.modelmapper.ModelMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.wikitolearn.midtier.course.entity.Chapter; import org.wikitolearn.midtier.course.entity.Course; import org.wikitolearn.midtier.course.entity.EntityList; +import org.wikitolearn.midtier.course.entity.ErrorJson; import org.wikitolearn.midtier.course.entity.Page; -import org.wikitolearn.midtier.course.entity.dto.in.GetCourseDto; -import org.wikitolearn.midtier.course.entity.dto.in.GetCourseVersionsDto; +import org.wikitolearn.midtier.course.entity.dto.out.GetCourseDto; +import org.wikitolearn.midtier.course.entity.dto.out.GetCourseVersionsDto; import org.wikitolearn.midtier.course.exception.ResourceNotFoundException; import org.wikitolearn.midtier.course.service.ChapterService; import org.wikitolearn.midtier.course.service.CourseService; import org.wikitolearn.midtier.course.service.PageService; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; import lombok.extern.slf4j.Slf4j; @RestController @Slf4j @RequestMapping(value = "/history/courses") public class HistoryController { @Autowired private CourseService courseService; @Autowired private ChapterService chapterService; @Autowired private PageService pageService; @Autowired private ObjectMapper objectMapper; @Autowired private ModelMapper modelMapper; @ApiOperation(value = "getCourseVersion") + @ApiResponses({ + @ApiResponse(code = 200, message = "Success", response = GetCourseDto.class), + @ApiResponse(code = 404, message = "Not Found", response = ErrorJson.class) + }) @GetMapping(value = "/{courseId}/versions/{version}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public GetCourseDto getCourseVersion(@PathVariable(value = "courseId", required = true) String courseId, @PathVariable(value = "version", required = true) Integer version) throws JsonProcessingException { MultiValueMap courseParams = new LinkedMultiValueMap<>(); courseParams.add("version", String.valueOf(version)); MultiValueMap chapterParams = new LinkedMultiValueMap<>(); ObjectNode projectionJsonObject = objectMapper.getNodeFactory().objectNode().put("title", 1).put("pages", 1); chapterParams.add("projection", objectMapper.writeValueAsString(projectionJsonObject)); chapterParams.add("version", ""); MultiValueMap pagesParams = new LinkedMultiValueMap<>(); projectionJsonObject = objectMapper.getNodeFactory().objectNode().put("title", 1); pagesParams.add("projection", objectMapper.writeValueAsString(projectionJsonObject)); pagesParams.add("version", ""); Course course = courseService.find(courseId, courseParams); course.setChapters( Optional .ofNullable(course.getChapters()) .orElseGet(Collections::emptyList) .parallelStream() .map(chapter -> { chapterParams.set("version", String.valueOf(chapter.getVersion())); chapter = chapterService.find(chapter.getId(), chapterParams); chapter.setPages( Optional .ofNullable(chapter.getPages()) .orElseGet(Collections::emptyList) .parallelStream() .map(page -> { pagesParams.set("version", String.valueOf(page.getVersion())); return pageService.find(page.getId(), pagesParams); }) .collect(Collectors.toList())); return chapter; }) .collect(Collectors.toList())); return modelMapper.map(course, GetCourseDto.class); } + @ApiResponses({ + @ApiResponse(code = 200, message = "Success", response = GetCourseVersionsDto.class), + @ApiResponse(code = 404, message = "Not Found", response = ErrorJson.class) + }) @GetMapping(value = "/{courseId}/versions", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public GetCourseVersionsDto getCourseVersions(@PathVariable(value = "courseId", required = true) String courseId, @RequestParam(value="page", required=false) Integer page) { MultiValueMap courseParams = new LinkedMultiValueMap<>(); courseParams.add("page", String.valueOf(page)); courseParams.add("version", "all"); EntityList courseVersions = courseService.getAllCourseVersions(courseId, courseParams); return modelMapper.map(courseVersions, GetCourseVersionsDto.class); } + @ApiResponses({ + @ApiResponse(code = 200, message = "Success", response = Page.class), + @ApiResponse(code = 404, message = "Not Found", response = ErrorJson.class) + }) @GetMapping(value = "/{courseId}/versions/{version}", params = "pageId", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public Page getPage(@PathVariable(value = "courseId", required = true) String courseId, @PathVariable(value = "version", required = true) Integer version, @RequestParam(value = "pageId", required = true) String pageId) throws JsonProcessingException { MultiValueMap courseParams = new LinkedMultiValueMap<>(); courseParams.add("version", String.valueOf(version)); MultiValueMap chapterParams = new LinkedMultiValueMap<>(); ObjectNode projectionJsonObject = objectMapper.getNodeFactory().objectNode().put("title", 1).put("pages", 1); chapterParams.add("projection", objectMapper.writeValueAsString(projectionJsonObject)); chapterParams.add("version", ""); MultiValueMap pageParams = new LinkedMultiValueMap<>(); pageParams.add("version", ""); Course course = courseService.find(courseId, courseParams); Chapter chapterTarget = Optional .ofNullable(course.getChapters()) .orElseGet(Collections::emptyList) .parallelStream() .filter(chapter -> { chapterParams.set("version", String.valueOf(chapter.getVersion())); Chapter enrichedChapter = chapterService.find(chapter.getId(), chapterParams); boolean isPagePresent = Optional.ofNullable(enrichedChapter.getPages()).orElseGet(Collections::emptyList) .parallelStream().filter(page -> pageId.equals(page.getId())).findFirst().isPresent(); if (!isPagePresent) { return false; } chapter.setPages(enrichedChapter.getPages()); return true; }) .findFirst() .orElseThrow(() -> new ResourceNotFoundException()); return Optional.ofNullable(chapterTarget.getPages()).orElseGet(Collections::emptyList).parallelStream() .filter(page -> pageId.equals(page.getId())) .findFirst() .map(page -> { pageParams.set("version", String.valueOf(page.getVersion())); return pageService.find(page.getId(), pageParams); }) .orElseThrow(() -> new ResourceNotFoundException()); } } diff --git a/src/main/java/org/wikitolearn/midtier/course/web/rest/PageController.java b/src/main/java/org/wikitolearn/midtier/course/web/rest/PageController.java index b7eb038..81cb049 100644 --- a/src/main/java/org/wikitolearn/midtier/course/web/rest/PageController.java +++ b/src/main/java/org/wikitolearn/midtier/course/web/rest/PageController.java @@ -1,77 +1,78 @@ package org.wikitolearn.midtier.course.web.rest; import javax.validation.Valid; import org.modelmapper.ModelMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import org.wikitolearn.midtier.course.entity.ErrorJson; import org.wikitolearn.midtier.course.entity.Page; import org.wikitolearn.midtier.course.entity.dto.in.StoreOrUpdatePageDto; import org.wikitolearn.midtier.course.service.PageService; import com.fasterxml.jackson.core.JsonProcessingException; import io.swagger.annotations.ApiResponse; import io.swagger.annotations.ApiResponses; import lombok.extern.slf4j.Slf4j; @RestController @Slf4j @RequestMapping(value = "/pages") public class PageController { @Autowired private PageService pageService; @Autowired ModelMapper modelMapper; @ApiResponses({ @ApiResponse(code = 200, message = "Success"), + @ApiResponse(code = 404, message = "Not Found", response = ErrorJson.class) }) @GetMapping(value = "/{pageId}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public Page getPage(@PathVariable String pageId) { return pageService.find(pageId, null); } @ApiResponses({ @ApiResponse(code = 200, message = "Success", response = Page.class), @ApiResponse(code = 401, message = "Unauthorized"), @ApiResponse(code = 403, message = "Forbidden"), @ApiResponse(code = 500, message = "Internal Server Error", response = ErrorJson.class), @ApiResponse(code = 422, message = "Uprocessable Entity") }) @PatchMapping(value = "/{pageId}", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public Page update(@PathVariable("pageId") String pageId, @Valid @RequestBody StoreOrUpdatePageDto page, @RequestHeader("If-Match") String etag) throws JsonProcessingException { Page pageToUpdate = modelMapper.map(page, Page.class); pageToUpdate.setId(pageId); pageToUpdate.setEtag(etag); return pageService.update(pageToUpdate); } @ApiResponses({ @ApiResponse(code = 204, message = "No Content"), @ApiResponse(code = 401, message = "Unauthorized"), @ApiResponse(code = 403, message = "Forbidden"), @ApiResponse(code = 500, message = "Internal Server Error", response = ErrorJson.class) }) @DeleteMapping(value = "/{pageId}", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) @ResponseStatus(HttpStatus.NO_CONTENT) public void delete(@PathVariable("pageId") String pageId, @RequestHeader("If-Match") String etag) throws JsonProcessingException { Page page = new Page(); page.setId(pageId); page.setEtag(etag); pageService.delete(page, false); } }