diff --git a/review/src/main/kotlin/br/all/application/question/delete/DeleteQuestionPresenter.kt b/review/src/main/kotlin/br/all/application/question/delete/DeleteQuestionPresenter.kt new file mode 100644 index 00000000..2e5fbf96 --- /dev/null +++ b/review/src/main/kotlin/br/all/application/question/delete/DeleteQuestionPresenter.kt @@ -0,0 +1,5 @@ +package br.all.application.question.delete + +import br.all.domain.shared.presenter.GenericPresenter + +interface DeleteQuestionPresenter : GenericPresenter \ No newline at end of file diff --git a/review/src/main/kotlin/br/all/application/question/delete/DeleteQuestionService.kt b/review/src/main/kotlin/br/all/application/question/delete/DeleteQuestionService.kt new file mode 100644 index 00000000..1f8fff68 --- /dev/null +++ b/review/src/main/kotlin/br/all/application/question/delete/DeleteQuestionService.kt @@ -0,0 +1,22 @@ +package br.all.application.question.delete + +import io.swagger.v3.oas.annotations.media.Schema +import java.util.UUID + +interface DeleteQuestionService { + fun delete(presenter: DeleteQuestionPresenter, request: RequestModel) + + data class RequestModel( + val userId: UUID, + val systematicStudyId: UUID, + val questionId: UUID, + ) + + @Schema(name = "DeleteQuestionResponseModel") + data class ResponseModel( + val userId: UUID, + val systematicStudyId: UUID, + val questionId: UUID, + val affectedStudyReviewIds: List + ) +} \ No newline at end of file diff --git a/review/src/main/kotlin/br/all/application/question/delete/DeleteQuestionServiceImpl.kt b/review/src/main/kotlin/br/all/application/question/delete/DeleteQuestionServiceImpl.kt new file mode 100644 index 00000000..e31fc281 --- /dev/null +++ b/review/src/main/kotlin/br/all/application/question/delete/DeleteQuestionServiceImpl.kt @@ -0,0 +1,66 @@ +package br.all.application.question.delete + +import br.all.application.question.repository.QuestionRepository +import br.all.application.review.repository.SystematicStudyRepository +import br.all.application.review.repository.fromDto +import br.all.application.shared.presenter.prepareIfFailsPreconditions +import br.all.application.study.repository.StudyReviewRepository +import br.all.application.user.CredentialsService +import br.all.domain.model.question.QuestionId +import br.all.domain.model.review.SystematicStudy +import org.springframework.stereotype.Service + +@Service +class DeleteQuestionServiceImpl( + private val systematicStudyRepository: SystematicStudyRepository, + private val questionRepository: QuestionRepository, + private val credentialsService: CredentialsService, + private val studyReviewRepository: StudyReviewRepository, +) : DeleteQuestionService { + override fun delete(presenter: DeleteQuestionPresenter, request: DeleteQuestionService.RequestModel) { + val systematicStudyId = request.systematicStudyId + val userId = request.userId + + val user = credentialsService.loadCredentials(userId)?.toUser() + val systematicStudyDto = systematicStudyRepository.findById(systematicStudyId) + val systematicStudy = systematicStudyDto?.let { SystematicStudy.fromDto(it) } + + presenter.prepareIfFailsPreconditions(user, systematicStudy) + if (presenter.isDone()) return + + val questionId = QuestionId(request.questionId) + val question = questionRepository.findById(systematicStudyId, questionId.value) + if (question == null) { + presenter.prepareFailView(NoSuchElementException("Question with id ${questionId.value} not found for study $systematicStudyId.")) + return + } + + val affectedStudyReviewIds = mutableListOf() + + try { + questionRepository.deleteById(systematicStudyId, questionId.value) + + val studyReviews = studyReviewRepository.findAllFromReview(systematicStudyId) + studyReviews.forEach { review -> + val updatedFormAnswers = review.formAnswers.filterNot { it.key == questionId.value } + val updatedRobAnswers = review.robAnswers.filterNot { it.key == questionId.value } + + if (updatedFormAnswers != review.formAnswers || updatedRobAnswers != review.robAnswers) { + val updatedReview = review.copy( + formAnswers = updatedFormAnswers, + robAnswers = updatedRobAnswers, + ) + + studyReviewRepository.saveOrUpdate(updatedReview) + affectedStudyReviewIds.add(review.studyReviewId) + } + } + + } catch (e: Exception) { + presenter.prepareFailView(e) + return + } + + presenter.prepareSuccessView(DeleteQuestionService.ResponseModel(userId, systematicStudyId, question.questionId, affectedStudyReviewIds)) + } +} \ No newline at end of file diff --git a/review/src/main/kotlin/br/all/application/question/repository/QuestionRepository.kt b/review/src/main/kotlin/br/all/application/question/repository/QuestionRepository.kt index 457af8af..d3e7568e 100644 --- a/review/src/main/kotlin/br/all/application/question/repository/QuestionRepository.kt +++ b/review/src/main/kotlin/br/all/application/question/repository/QuestionRepository.kt @@ -7,4 +7,5 @@ interface QuestionRepository { fun createOrUpdate(dto: QuestionDto) fun findById(systematicStudyId: UUID, id: UUID): QuestionDto? fun findAllBySystematicStudyId(systematicStudyId: UUID, context: QuestionContextEnum? = null): List + fun deleteById(systematicStudyId: UUID, id: UUID) } \ No newline at end of file diff --git a/review/src/main/kotlin/br/all/application/study/find/presenter/FindAllIncludedStudyReviewsPresenter.kt b/review/src/main/kotlin/br/all/application/study/find/presenter/FindAllIncludedStudyReviewsPresenter.kt new file mode 100644 index 00000000..e94dffd8 --- /dev/null +++ b/review/src/main/kotlin/br/all/application/study/find/presenter/FindAllIncludedStudyReviewsPresenter.kt @@ -0,0 +1,6 @@ +package br.all.application.study.find.presenter + +import br.all.application.study.find.service.FindAllIncludedStudyReviewsService.ResponseModel +import br.all.domain.shared.presenter.GenericPresenter + +interface FindAllIncludedStudyReviewsPresenter : GenericPresenter \ No newline at end of file diff --git a/review/src/main/kotlin/br/all/application/study/find/presenter/FindAllStudyReviewsByAdvancedSearchPresenter.kt b/review/src/main/kotlin/br/all/application/study/find/presenter/FindAllStudyReviewsByAdvancedSearchPresenter.kt new file mode 100644 index 00000000..4cbaa361 --- /dev/null +++ b/review/src/main/kotlin/br/all/application/study/find/presenter/FindAllStudyReviewsByAdvancedSearchPresenter.kt @@ -0,0 +1,6 @@ +package br.all.application.study.find.presenter + +import br.all.application.study.find.service.FindAllStudyReviewsByAdvancedSearchService.ResponseModel +import br.all.domain.shared.presenter.GenericPresenter + +interface FindAllStudyReviewsByAdvancedSearchPresenter : GenericPresenter diff --git a/review/src/main/kotlin/br/all/application/study/find/service/FindAllIncludedStudyReviewsService.kt b/review/src/main/kotlin/br/all/application/study/find/service/FindAllIncludedStudyReviewsService.kt new file mode 100644 index 00000000..8a5044d2 --- /dev/null +++ b/review/src/main/kotlin/br/all/application/study/find/service/FindAllIncludedStudyReviewsService.kt @@ -0,0 +1,30 @@ +package br.all.application.study.find.service + +import br.all.application.study.find.presenter.FindAllIncludedStudyReviewsPresenter +import br.all.application.study.repository.StudyReviewDto +import io.swagger.v3.oas.annotations.media.Schema +import java.util.UUID + +interface FindAllIncludedStudyReviewsService { + + fun findAllIncluded(presenter: FindAllIncludedStudyReviewsPresenter, request: RequestModel) + + data class RequestModel( + val userId: UUID, + val systematicStudyId: UUID, + val page: Int = 0, + val pageSize: Int = 20, + val sort: String = "id,asc" + ) + + @Schema(name = "FindAllIncludedStudyReviewsResponseModel") + data class ResponseModel( + val userId: UUID, + val systematicStudyId: UUID, + val studyReviews: List, + val page: Int, + val size: Int, + val totalElements: Long, + val totalPages: Int + ) +} \ No newline at end of file diff --git a/review/src/main/kotlin/br/all/application/study/find/service/FindAllIncludedStudyReviewsServiceImpl.kt b/review/src/main/kotlin/br/all/application/study/find/service/FindAllIncludedStudyReviewsServiceImpl.kt new file mode 100644 index 00000000..38642610 --- /dev/null +++ b/review/src/main/kotlin/br/all/application/study/find/service/FindAllIncludedStudyReviewsServiceImpl.kt @@ -0,0 +1,70 @@ +package br.all.application.study.find.service + +import br.all.application.review.repository.SystematicStudyRepository +import br.all.application.review.repository.fromDto +import br.all.application.shared.presenter.prepareIfFailsPreconditions +import br.all.application.study.find.presenter.FindAllIncludedStudyReviewsPresenter +import br.all.application.study.repository.StudyReviewRepository +import br.all.application.user.CredentialsService +import br.all.domain.model.review.SystematicStudy +import br.all.domain.model.study.SelectionStatus +import org.springframework.data.domain.PageRequest +import org.springframework.data.domain.Sort + +class FindAllIncludedStudyReviewsServiceImpl( + private val systematicStudyRepository: SystematicStudyRepository, + private val studyReviewRepository: StudyReviewRepository, + private val credentialsService: CredentialsService, +) : FindAllIncludedStudyReviewsService { + + private fun parseSortParameter(sortParam: String): Sort { + val parts = sortParam.split(",") + val property = parts[0] + val direction = if (parts.size > 1 && parts[1].equals("desc", ignoreCase = true)) { + Sort.Direction.DESC + } else { + Sort.Direction.ASC + } + return Sort.by(direction, property) + } + + override fun findAllIncluded( + presenter: FindAllIncludedStudyReviewsPresenter, + request: FindAllIncludedStudyReviewsService.RequestModel + ) { + val user = credentialsService.loadCredentials(request.userId)?.toUser() + + val systematicStudyDto = systematicStudyRepository.findById(request.systematicStudyId) + val systematicStudy = systematicStudyDto?.let { SystematicStudy.fromDto(it) } + + presenter.prepareIfFailsPreconditions(user, systematicStudy) + + if (presenter.isDone()) return + + val sort = parseSortParameter(request.sort) + val pageable = PageRequest.of( + request.page, + request.pageSize, + sort + ) + + val studyReviewsPage = studyReviewRepository.findAllBySystematicStudyIdAndSelectionStatusPaged( + request.systematicStudyId, + SelectionStatus.INCLUDED, + pageable + ) + + presenter.prepareSuccessView( + FindAllIncludedStudyReviewsService.ResponseModel( + userId = request.userId, + systematicStudyId = request.systematicStudyId, + studyReviews = studyReviewsPage.content, + page = pageable.pageNumber, + size = pageable.pageSize, + totalElements = studyReviewsPage.totalElements, + totalPages = studyReviewsPage.totalPages + ) + ) + } + +} \ No newline at end of file diff --git a/review/src/main/kotlin/br/all/application/study/find/service/FindAllStudyReviewsByAdvancedSearchService.kt b/review/src/main/kotlin/br/all/application/study/find/service/FindAllStudyReviewsByAdvancedSearchService.kt new file mode 100644 index 00000000..16406898 --- /dev/null +++ b/review/src/main/kotlin/br/all/application/study/find/service/FindAllStudyReviewsByAdvancedSearchService.kt @@ -0,0 +1,40 @@ +package br.all.application.study.find.service + +import br.all.application.study.find.presenter.FindAllStudyReviewsByAdvancedSearchPresenter +import br.all.application.study.repository.StudyReviewDto +import io.swagger.v3.oas.annotations.media.Schema +import java.util.* + +interface FindAllStudyReviewsByAdvancedSearchService { + + fun findAllByAdvancedSearch(presenter: FindAllStudyReviewsByAdvancedSearchPresenter, request: RequestModel) + + data class RequestModel( + val userId: UUID, + val systematicStudyId: UUID, + val id: Long? = null, + val studyReviewId: String? = null, + val title: String? = null, + val authors: String? = null, + val venue: String? = null, + val year: Int? = null, + val selectionStatus: String? = null, + val extractionStatus: String? = null, + val score: Double? = null, + val readingPriority: Int? = null, + val page: Int = 0, + val pageSize: Int = 20, + val sort: String = "id,asc" + ) + + @Schema(name = "FindAllStudyReviewsByAdvancedSearchResponseModel") + data class ResponseModel( + val userId: UUID, + val systematicStudyId: UUID, + val studyReviews: List, + val page: Int, + val size: Int, + val totalElements: Long, + val totalPages: Int + ) +} diff --git a/review/src/main/kotlin/br/all/application/study/find/service/FindAllStudyReviewsByAdvancedSearchServiceImpl.kt b/review/src/main/kotlin/br/all/application/study/find/service/FindAllStudyReviewsByAdvancedSearchServiceImpl.kt new file mode 100644 index 00000000..9bbfd490 --- /dev/null +++ b/review/src/main/kotlin/br/all/application/study/find/service/FindAllStudyReviewsByAdvancedSearchServiceImpl.kt @@ -0,0 +1,73 @@ +package br.all.application.study.find.service + +import br.all.application.review.repository.SystematicStudyRepository +import br.all.application.review.repository.fromDto +import br.all.application.shared.presenter.prepareIfFailsPreconditions +import br.all.application.study.find.presenter.FindAllStudyReviewsByAdvancedSearchPresenter +import br.all.application.study.repository.StudyReviewRepository +import br.all.application.user.CredentialsService +import br.all.domain.model.review.SystematicStudy +import org.springframework.data.domain.PageRequest +import org.springframework.data.domain.Sort + +class FindAllStudyReviewsByAdvancedSearchServiceImpl( + private val systematicStudyRepository: SystematicStudyRepository, + private val studyReviewRepository: StudyReviewRepository, + private val credentialsService: CredentialsService +) : FindAllStudyReviewsByAdvancedSearchService { + + private fun parseSortParameter(sortParam: String): Sort { + val parts = sortParam.split(",") + val property = parts[0] + val direction = if (parts.size > 1 && parts[1].equals("desc", ignoreCase = true)) { + Sort.Direction.DESC + } else Sort.Direction.ASC + return Sort.by(direction, property) + } + + override fun findAllByAdvancedSearch( + presenter: FindAllStudyReviewsByAdvancedSearchPresenter, + request: FindAllStudyReviewsByAdvancedSearchService.RequestModel + ) { + val user = credentialsService.loadCredentials(request.userId)?.toUser() + val systematicStudyDto = systematicStudyRepository.findById(request.systematicStudyId) + val systematicStudy = systematicStudyDto?.let { SystematicStudy.fromDto(it) } + + presenter.prepareIfFailsPreconditions(user, systematicStudy) + if (presenter.isDone()) return + + val sort = parseSortParameter(request.sort) + val pageable = PageRequest.of(request.page, request.pageSize, sort) + + val filters = mapOf( + "id" to request.id, + "studyReviewId" to request.studyReviewId, + "title" to request.title, + "authors" to request.authors, + "venue" to request.venue, + "year" to request.year, + "selectionStatus" to request.selectionStatus, + "extractionStatus" to request.extractionStatus, + "score" to request.score, + "readingPriority" to request.readingPriority + ) + + val pageResult = studyReviewRepository.findAllByAdvancedSearch( + reviewId = request.systematicStudyId, + filters = filters, + pageable = pageable + ) + + presenter.prepareSuccessView( + FindAllStudyReviewsByAdvancedSearchService.ResponseModel( + userId = request.userId, + systematicStudyId = request.systematicStudyId, + studyReviews = pageResult.content, + page = pageable.pageNumber, + size = pageable.pageSize, + totalElements = pageResult.totalElements, + totalPages = pageResult.totalPages + ) + ) + } +} diff --git a/review/src/main/kotlin/br/all/application/study/find/service/FindAllStudyReviewsBySessionService.kt b/review/src/main/kotlin/br/all/application/study/find/service/FindAllStudyReviewsBySessionService.kt index 200aeece..156c46cc 100644 --- a/review/src/main/kotlin/br/all/application/study/find/service/FindAllStudyReviewsBySessionService.kt +++ b/review/src/main/kotlin/br/all/application/study/find/service/FindAllStudyReviewsBySessionService.kt @@ -13,12 +13,20 @@ interface FindAllStudyReviewsBySessionService { val userId: UUID, val systematicStudyId: UUID, val searchSessionId: UUID, + val page: Int = 0, + val pageSize: Int = 20, + val sort: String = "id,asc" ) + @Schema(name = "FindAllStudyReviewsBySessionResponseModel") data class ResponseModel( val userId: UUID, val systematicStudyId: UUID, val searchSessionId: UUID, - val studyReviews: List + val studyReviews: List, + val page: Int, + val size: Int, + val totalElements: Long, + val totalPages: Int ) } \ No newline at end of file diff --git a/review/src/main/kotlin/br/all/application/study/find/service/FindAllStudyReviewsBySessionServiceImpl.kt b/review/src/main/kotlin/br/all/application/study/find/service/FindAllStudyReviewsBySessionServiceImpl.kt index afcc5867..f85c142a 100644 --- a/review/src/main/kotlin/br/all/application/study/find/service/FindAllStudyReviewsBySessionServiceImpl.kt +++ b/review/src/main/kotlin/br/all/application/study/find/service/FindAllStudyReviewsBySessionServiceImpl.kt @@ -7,6 +7,8 @@ import br.all.application.study.find.presenter.FindAllStudyReviewsBySessionPrese import br.all.application.study.repository.StudyReviewRepository import br.all.application.user.CredentialsService import br.all.domain.model.review.SystematicStudy +import org.springframework.data.domain.PageRequest +import org.springframework.data.domain.Sort class FindAllStudyReviewsBySessionServiceImpl ( private val systematicStudyRepository: SystematicStudyRepository, @@ -14,6 +16,17 @@ class FindAllStudyReviewsBySessionServiceImpl ( private val credentialsService: CredentialsService, ) : FindAllStudyReviewsBySessionService { + private fun parseSortParameter(sortParam: String): Sort { + val parts = sortParam.split(",") + val property = parts[0] + val direction = if (parts.size > 1 && parts[1].equals("desc", ignoreCase = true)) { + Sort.Direction.DESC + } else { + Sort.Direction.ASC + } + return Sort.by(direction, property) + } + override fun findAllBySearchSession( presenter: FindAllStudyReviewsBySessionPresenter, request: FindAllStudyReviewsBySessionService.RequestModel @@ -27,11 +40,29 @@ class FindAllStudyReviewsBySessionServiceImpl ( if (presenter.isDone()) return - val studyReviews = studyReviewRepository.findAllBySession(request.systematicStudyId, request.searchSessionId) + val sort = parseSortParameter(request.sort) + val pageable = PageRequest.of( + request.page, + request.pageSize, + sort + ) + + val studyReviewsPage = studyReviewRepository.findAllBySessionPaged( + request.systematicStudyId, + request.searchSessionId, + pageable + ) + presenter.prepareSuccessView( FindAllStudyReviewsBySessionService.ResponseModel( - request.userId, request.systematicStudyId, - request.searchSessionId, studyReviews + userId = request.userId, + systematicStudyId = request.systematicStudyId, + searchSessionId = request.searchSessionId, + studyReviews = studyReviewsPage.content, + page = pageable.pageNumber, + size = pageable.pageSize, + totalElements = studyReviewsPage.totalElements, + totalPages = studyReviewsPage.totalPages ) ) } diff --git a/review/src/main/kotlin/br/all/application/study/repository/StudyReviewRepository.kt b/review/src/main/kotlin/br/all/application/study/repository/StudyReviewRepository.kt index 6978afce..f0f15de2 100644 --- a/review/src/main/kotlin/br/all/application/study/repository/StudyReviewRepository.kt +++ b/review/src/main/kotlin/br/all/application/study/repository/StudyReviewRepository.kt @@ -1,5 +1,6 @@ package br.all.application.study.repository +import br.all.domain.model.study.SelectionStatus import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable import java.util.UUID @@ -11,10 +12,28 @@ interface StudyReviewRepository { fun findAllFromReviewPaged(reviewId: UUID, pageable: Pageable = Pageable.unpaged()): Page fun findAllBySource(reviewId: UUID, source: String): List fun findAllBySession(reviewId: UUID, searchSessionId: UUID): List - fun findById(reviewId: UUID, studyId: Long) : StudyReviewDto? + fun findAllBySessionPaged( + reviewId: UUID, + searchSessionId: UUID, + pageable: Pageable = Pageable.unpaged() + ): Page + + fun findAllBySystematicStudyIdAndSelectionStatusPaged( + reviewId: UUID, + status: SelectionStatus, + pageable: Pageable = Pageable.unpaged() + ): Page + + fun findById(reviewId: UUID, studyId: Long): StudyReviewDto? fun updateSelectionStatus(reviewId: UUID, studyId: Long, attributeName: String, newStatus: Any) fun saveOrUpdateBatch(dtos: List) fun findAllQuestionAnswers(reviewId: UUID, questionId: UUID): List + + fun findAllByAdvancedSearch( + reviewId: UUID, + filters: Map, + pageable: Pageable + ): Page } data class AnswerDto( diff --git a/review/src/main/kotlin/br/all/infrastructure/question/QuestionRepositoryImpl.kt b/review/src/main/kotlin/br/all/infrastructure/question/QuestionRepositoryImpl.kt index 751edb8f..849ac1dd 100644 --- a/review/src/main/kotlin/br/all/infrastructure/question/QuestionRepositoryImpl.kt +++ b/review/src/main/kotlin/br/all/infrastructure/question/QuestionRepositoryImpl.kt @@ -29,4 +29,8 @@ class QuestionRepositoryImpl(private val repository: MongoQuestionRepository) : return filteredQuestions.map { it.toDto() } } + + override fun deleteById(systematicStudyId: UUID, id: UUID) { + repository.deleteById(id) + } } \ No newline at end of file diff --git a/review/src/main/kotlin/br/all/infrastructure/study/MongoStudyReviewRepository.kt b/review/src/main/kotlin/br/all/infrastructure/study/MongoStudyReviewRepository.kt index c6d8893b..66c23cd6 100644 --- a/review/src/main/kotlin/br/all/infrastructure/study/MongoStudyReviewRepository.kt +++ b/review/src/main/kotlin/br/all/infrastructure/study/MongoStudyReviewRepository.kt @@ -1,6 +1,7 @@ package br.all.infrastructure.study import br.all.application.study.repository.AnswerDto +import br.all.domain.model.study.SelectionStatus import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable import org.springframework.data.mongodb.repository.Aggregation @@ -21,6 +22,14 @@ interface MongoStudyReviewRepository : MongoRepository + fun findAllById_SystematicStudyIdAndSearchSessionId(reviewID: UUID, searchSessionId: UUID, pageable: Pageable): Page + + fun findAllById_SystematicStudyIdAndSelectionStatus( + reviewID: UUID, + selectionStatus: SelectionStatus, + pageable: Pageable + ): Page + @Update("{ '\$set' : { ?1 : ?2 } }") fun findAndUpdateAttributeById(id: StudyReviewId, attributeName:String, newStatus: Any) diff --git a/review/src/main/kotlin/br/all/infrastructure/study/StudyReviewRepositoryImpl.kt b/review/src/main/kotlin/br/all/infrastructure/study/StudyReviewRepositoryImpl.kt index 838f133c..5eda830d 100644 --- a/review/src/main/kotlin/br/all/infrastructure/study/StudyReviewRepositoryImpl.kt +++ b/review/src/main/kotlin/br/all/infrastructure/study/StudyReviewRepositoryImpl.kt @@ -3,15 +3,21 @@ package br.all.infrastructure.study import br.all.application.study.repository.AnswerDto import br.all.application.study.repository.StudyReviewDto import br.all.application.study.repository.StudyReviewRepository +import br.all.domain.model.study.SelectionStatus import br.all.infrastructure.shared.toNullable import org.springframework.data.domain.Page import org.springframework.data.domain.PageImpl import org.springframework.data.domain.Pageable +import org.springframework.data.mongodb.core.MongoTemplate +import org.springframework.data.mongodb.core.query.Criteria +import org.springframework.data.mongodb.core.query.Query import org.springframework.stereotype.Repository import java.util.* @Repository -open class StudyReviewRepositoryImpl(private val repository: MongoStudyReviewRepository) : StudyReviewRepository { +open class StudyReviewRepositoryImpl(private val repository: MongoStudyReviewRepository, + private val mongoTemplate: MongoTemplate +) : StudyReviewRepository { override fun saveOrUpdate(dto: StudyReviewDto): StudyReviewDocument = repository.save(dto.toDocument()) override fun findById(reviewId: UUID, studyId: Long) = @@ -32,6 +38,27 @@ open class StudyReviewRepositoryImpl(private val repository: MongoStudyReviewRe override fun findAllBySession(reviewId: UUID, searchSessionId: UUID): List = repository.findAllById_SystematicStudyIdAndSearchSessionId(reviewId, searchSessionId).map { it.toDto() } + override fun findAllBySessionPaged( + reviewId: UUID, + searchSessionId: UUID, + pageable: Pageable + ): Page { + val documentsPage = repository.findAllById_SystematicStudyIdAndSearchSessionId(reviewId, searchSessionId, pageable) + return documentsPage.map { it.toDto() } + } + + override fun findAllBySystematicStudyIdAndSelectionStatusPaged( + reviewId: UUID, + status: SelectionStatus, + pageable: Pageable + ): Page { + val documentsPage = repository.findAllById_SystematicStudyIdAndSelectionStatus( + reviewId, + status, + pageable + ) + return documentsPage.map { it.toDto() } + } override fun updateSelectionStatus(reviewId: UUID, studyId: Long, attributeName: String, newStatus: Any) { repository.findAndUpdateAttributeById(StudyReviewId(reviewId, studyId), attributeName, newStatus) @@ -50,5 +77,53 @@ open class StudyReviewRepositoryImpl(private val repository: MongoStudyReviewRe answer = infraDto.answer ) } + + override fun findAllByAdvancedSearch( + reviewId: UUID, + filters: Map, + pageable: Pageable + ): Page { + val criteria = mutableListOf() + criteria += Criteria.where("_id.systematicStudyId").`is`(reviewId) + + filters["id"]?.let { criteria += Criteria.where("_id.studyReviewId").`is`(it) } + filters["studyReviewId"]?.let { + criteria += Criteria.where("studyReviewId").regex(".*${Regex.escape(it as String)}.*", "i") + } + filters["title"]?.let { + criteria += Criteria.where("title").regex(".*${Regex.escape(it as String)}.*", "i") + } + filters["authors"]?.let { + criteria += Criteria.where("authors").regex(".*${Regex.escape(it as String)}.*", "i") + } + filters["venue"]?.let { + criteria += Criteria.where("venue").regex(".*${Regex.escape(it as String)}.*", "i") + } + filters["year"]?.let { + criteria += Criteria.where("year").`is`(it) + } + filters["selectionStatus"]?.let { + criteria += Criteria.where("selectionStatus").`is`(it) + } + filters["extractionStatus"]?.let { + criteria += Criteria.where("extractionStatus").`is`(it) + } + filters["score"]?.let { + criteria += Criteria.where("score").`is`(it) + } + filters["readingPriority"]?.let { + criteria += Criteria.where("readingPriority").`is`(it) + } + + val query = Query().addCriteria(Criteria().andOperator(*criteria.toTypedArray())) + query.with(pageable) + + val total = mongoTemplate.count(query, StudyReviewDocument::class.java) + val documents = mongoTemplate.find(query, StudyReviewDocument::class.java) + + val content = documents.map { it.toDto() } + + return PageImpl(content, pageable, total) + } } diff --git a/web/src/main/kotlin/br/all/question/controller/ExtractionQuestionController.kt b/web/src/main/kotlin/br/all/question/controller/ExtractionQuestionController.kt index 69ee16bb..72eb9490 100644 --- a/web/src/main/kotlin/br/all/question/controller/ExtractionQuestionController.kt +++ b/web/src/main/kotlin/br/all/question/controller/ExtractionQuestionController.kt @@ -3,12 +3,14 @@ package br.all.question.controller import br.all.application.question.create.CreateQuestionService import br.all.application.question.create.CreateQuestionService.QuestionType.* import br.all.application.question.create.CreateQuestionService.RequestModel +import br.all.application.question.delete.DeleteQuestionService import br.all.application.question.find.FindQuestionService import br.all.application.question.findAll.FindAllBySystematicStudyIdService import br.all.application.question.update.services.UpdateQuestionService import br.all.application.question.findAll.FindAllBySystematicStudyIdService.RequestModel as FindAllRequest import br.all.question.presenter.extraction.RestfulFindExtractionQuestionPresenter import br.all.question.presenter.extraction.RestfulCreateExtractionQuestionPresenter +import br.all.question.presenter.extraction.RestfulDeleteExtractionQuestionPresenter import br.all.question.presenter.extraction.RestfulFindAllExtractionQuestionPresenter import br.all.question.presenter.extraction.RestfulUpdateExtractionQuestionPresenter import br.all.question.requests.PutRequest @@ -33,6 +35,7 @@ class ExtractionQuestionController( val findOneService: FindQuestionService, val findAllService: FindAllBySystematicStudyIdService, val updateQuestionService: UpdateQuestionService, + val deleteQuestionService: DeleteQuestionService, val linksFactory: LinksFactory ) { data class TextualRequest(val code: String, val description: String) @@ -364,4 +367,47 @@ class ExtractionQuestionController( updateQuestionService.update(presenter, requestModel) return presenter.responseEntity ?: ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR) } + + @DeleteMapping("/{questionId}") + @Operation(summary = "Delete a extraction question by id and remove all its answers from Study Reviews") + @ApiResponses( + value = [ + ApiResponse( + responseCode = "200", + description = "Success deleting an extraction question by id and removing all its answers from Study Reviews", + content = [Content( + mediaType = "application/json", + schema = Schema(implementation = DeleteQuestionService.ResponseModel::class) + )] + ), + ApiResponse( + responseCode = "404", + description = "Fail deleting an extraction question by id and removing all its answers from Study Reviews - not found", + content = [Content(schema = Schema(hidden = true))] + ), + ApiResponse( + responseCode = "401", + description = "Fail deleting an extraction question by id and removing all its answers from Study Reviews - unauthenticated user", + content = [Content(schema = Schema(hidden = true))] + ), + ApiResponse( + responseCode = "403", + description = "Fail deleting an extraction question by id and removing all its answers from Study Reviews - unauthorized user", + content = [Content(schema = Schema(hidden = true))] + ), + ] + ) + fun deleteQuestion( + @PathVariable systematicStudyId: UUID, + @PathVariable questionId: UUID + ): ResponseEntity<*> { + val presenter = RestfulDeleteExtractionQuestionPresenter(linksFactory) + val request = DeleteQuestionService.RequestModel( + authenticationInfoService.getAuthenticatedUserId(), + systematicStudyId, + questionId + ) + deleteQuestionService.delete(presenter, request) + return presenter.responseEntity ?: ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR) + } } \ No newline at end of file diff --git a/web/src/main/kotlin/br/all/question/controller/RiskOfBiasQuestionController.kt b/web/src/main/kotlin/br/all/question/controller/RiskOfBiasQuestionController.kt index 41499029..da07f7ce 100644 --- a/web/src/main/kotlin/br/all/question/controller/RiskOfBiasQuestionController.kt +++ b/web/src/main/kotlin/br/all/question/controller/RiskOfBiasQuestionController.kt @@ -3,10 +3,12 @@ package br.all.question.controller import br.all.application.question.create.CreateQuestionService import br.all.application.question.create.CreateQuestionService.QuestionType.* import br.all.application.question.create.CreateQuestionService.RequestModel +import br.all.application.question.delete.DeleteQuestionService import br.all.application.question.find.FindQuestionService import br.all.application.question.findAll.FindAllBySystematicStudyIdService import br.all.application.question.update.services.UpdateQuestionService import br.all.question.presenter.riskOfBias.RestfulCreateRoBQuestionPresenter +import br.all.question.presenter.riskOfBias.RestfulDeleteRoBQuestionPresenter import br.all.question.presenter.riskOfBias.RestfulFindAllRoBQuestionPresenter import br.all.question.presenter.riskOfBias.RestfulFindRoBQuestionPresenter import br.all.question.presenter.riskOfBias.RestfulUpdateRoBQuestionPresenter @@ -32,6 +34,7 @@ class RiskOfBiasQuestionController( val findOneService: FindQuestionService, val findAllService: FindAllBySystematicStudyIdService, val updateQuestionService: UpdateQuestionService, + val deleteQuestionService: DeleteQuestionService, val linksFactory: LinksFactory ) { data class TextualRequest(val code: String, val description: String) @@ -360,4 +363,47 @@ class RiskOfBiasQuestionController( updateQuestionService.update(presenter, requestModel) return presenter.responseEntity ?: ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR) } + + @DeleteMapping("/{questionId}") + @Operation(summary = "Delete a extraction question by id and remove all its answers from Study Reviews") + @ApiResponses( + value = [ + ApiResponse( + responseCode = "200", + description = "Success deleting an extraction question by id and removing all its answers from Study Reviews", + content = [Content( + mediaType = "application/json", + schema = Schema(implementation = DeleteQuestionService.ResponseModel::class) + )] + ), + ApiResponse( + responseCode = "404", + description = "Fail deleting an extraction question by id and removing all its answers from Study Reviews - not found", + content = [Content(schema = Schema(hidden = true))] + ), + ApiResponse( + responseCode = "401", + description = "Fail deleting an extraction question by id and removing all its answers from Study Reviews - unauthenticated user", + content = [Content(schema = Schema(hidden = true))] + ), + ApiResponse( + responseCode = "403", + description = "Fail deleting an extraction question by id and removing all its answers from Study Reviews - unauthorized user", + content = [Content(schema = Schema(hidden = true))] + ), + ] + ) + fun deleteQuestion( + @PathVariable systematicStudyId: UUID, + @PathVariable questionId: UUID + ): ResponseEntity<*> { + val presenter = RestfulDeleteRoBQuestionPresenter(linksFactory) + val request = DeleteQuestionService.RequestModel( + authenticationInfoService.getAuthenticatedUserId(), + systematicStudyId, + questionId + ) + deleteQuestionService.delete(presenter, request) + return presenter.responseEntity ?: ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR) + } } diff --git a/web/src/main/kotlin/br/all/question/presenter/extraction/RestfulDeleteExtractionQuestionPresenter.kt b/web/src/main/kotlin/br/all/question/presenter/extraction/RestfulDeleteExtractionQuestionPresenter.kt new file mode 100644 index 00000000..2c41edeb --- /dev/null +++ b/web/src/main/kotlin/br/all/question/presenter/extraction/RestfulDeleteExtractionQuestionPresenter.kt @@ -0,0 +1,35 @@ +package br.all.question.presenter.extraction + +import br.all.application.question.delete.DeleteQuestionPresenter +import br.all.application.question.delete.DeleteQuestionService +import br.all.shared.error.createErrorResponseFrom +import br.all.utils.LinksFactory +import org.springframework.hateoas.RepresentationModel +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import java.util.UUID + +class RestfulDeleteExtractionQuestionPresenter( + private val linksFactory: LinksFactory, +) : DeleteQuestionPresenter { + var responseEntity: ResponseEntity<*>? = null + + override fun prepareSuccessView(response: DeleteQuestionService.ResponseModel) { + val restfulResponse = ViewModel( + response.userId, response.systematicStudyId, response.questionId, response.affectedStudyReviewIds + ) + + responseEntity = ResponseEntity.status(HttpStatus.OK).body(restfulResponse) + } + + override fun prepareFailView(throwable: Throwable) = run { responseEntity = createErrorResponseFrom(throwable) } + + override fun isDone() = responseEntity != null + + private data class ViewModel( + val userId: UUID, + val systematicStudyId: UUID, + val questionId: UUID, + val affectedStudyReviewIds: List + ): RepresentationModel() +} \ No newline at end of file diff --git a/web/src/main/kotlin/br/all/question/presenter/riskOfBias/RestfulDeleteRoBQuestionPresenter.kt b/web/src/main/kotlin/br/all/question/presenter/riskOfBias/RestfulDeleteRoBQuestionPresenter.kt new file mode 100644 index 00000000..54ac165d --- /dev/null +++ b/web/src/main/kotlin/br/all/question/presenter/riskOfBias/RestfulDeleteRoBQuestionPresenter.kt @@ -0,0 +1,35 @@ +package br.all.question.presenter.riskOfBias + +import br.all.application.question.delete.DeleteQuestionPresenter +import br.all.application.question.delete.DeleteQuestionService +import br.all.shared.error.createErrorResponseFrom +import br.all.utils.LinksFactory +import org.springframework.hateoas.RepresentationModel +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import java.util.UUID + +class RestfulDeleteRoBQuestionPresenter( + private val linksFactory: LinksFactory, +) : DeleteQuestionPresenter { + var responseEntity: ResponseEntity<*>? = null + + override fun prepareSuccessView(response: DeleteQuestionService.ResponseModel) { + val restfulResponse = ViewModel( + response.userId, response.systematicStudyId, response.questionId, response.affectedStudyReviewIds + ) + + responseEntity = ResponseEntity.status(HttpStatus.OK).body(restfulResponse) + } + + override fun prepareFailView(throwable: Throwable) = run { responseEntity = createErrorResponseFrom(throwable) } + + override fun isDone() = responseEntity != null + + private data class ViewModel( + val userId: UUID, + val systematicStudyId: UUID, + val questionId: UUID, + val affectedStudyReviewIds: List + ): RepresentationModel() +} \ No newline at end of file diff --git a/web/src/main/kotlin/br/all/study/controller/StudyReviewController.kt b/web/src/main/kotlin/br/all/study/controller/StudyReviewController.kt index 3ccce027..a582ab0e 100644 --- a/web/src/main/kotlin/br/all/study/controller/StudyReviewController.kt +++ b/web/src/main/kotlin/br/all/study/controller/StudyReviewController.kt @@ -24,6 +24,9 @@ import br.all.application.study.find.service.FindStudyReviewService.RequestModel import br.all.application.study.update.interfaces.BatchAnswerQuestionService import br.all.study.presenter.RestfulBatchAnswerQuestionPresenter import br.all.study.requests.PatchBatchAnswerQuestionStudyReviewRequest +import io.swagger.v3.oas.annotations.Parameter +import io.swagger.v3.oas.annotations.Parameters +import io.swagger.v3.oas.annotations.media.ExampleObject @RestController @RequestMapping("/api/v1/systematic-study/{systematicStudy}") @@ -34,6 +37,8 @@ class StudyReviewController( private val findAllBySourceService: FindAllStudyReviewsBySourceService, private val findAllBySessionService: FindAllStudyReviewsBySessionService, private val findAllByAuthorService: FindAllStudyReviewsByAuthorService, + private val findAllIncludedStudyReviewsService: FindAllIncludedStudyReviewsService, + private val findAllStudyReviewsByAdvancedSearchService: FindAllStudyReviewsByAdvancedSearchService, private val findOneService: FindStudyReviewService, private val updateSelectionService: UpdateStudyReviewSelectionService, private val updateExtractionService: UpdateStudyReviewExtractionService, @@ -43,7 +48,6 @@ class StudyReviewController( private val batchAnswerQuestionService: BatchAnswerQuestionService, private val authenticationInfoService: AuthenticationInfoService, private val linksFactory: LinksFactory - ) { @PostMapping("/study-review") @@ -105,6 +109,37 @@ class StudyReviewController( return presenter.responseEntity ?: ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR) } + @GetMapping("/study-review/selection-included") + @Operation(summary = "Get all existing studies of a systematic review with included selection status") + @ApiResponses( + value = [ + ApiResponse( + responseCode = "200", + description = "Success getting studies of a systematic review with included selection status, either found all studies or found none", + content = [Content( + mediaType = "application/json", + schema = Schema(implementation = FindAllStudyReviewsService.ResponseModel::class) + )] + ), + ApiResponse(responseCode = "401", description = "Fail getting all study reviews with included selection status - unauthenticated user", + content = [Content(schema = Schema(hidden = true))]), + ApiResponse(responseCode = "403", description = "Fail getting all study reviews with included selection status - unauthorized user", + content = [Content(schema = Schema(hidden = true))]), + ] + ) + fun findAllIncludedStudyReviews( + @PathVariable systematicStudy: UUID, + @RequestParam("page", required = false, defaultValue = "0") page: Int, + @RequestParam("size", required = false, defaultValue = "20") size: Int, + @RequestParam("sort", required = false, defaultValue = "id,asc") sort: String, + ): ResponseEntity<*> { + val presenter = RestfulFindAllIncludedStudyReviewsPresenter(linksFactory) + val userId = authenticationInfoService.getAuthenticatedUserId() + val request = FindAllIncludedStudyReviewsService.RequestModel(userId, systematicStudy, page, size, sort) + findAllIncludedStudyReviewsService.findAllIncluded(presenter, request) + return presenter.responseEntity ?: ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR) + } + @GetMapping("/search-source/{searchSource}") @Operation(summary = "Get all existing studies of a systematic review search source") @ApiResponses( @@ -156,10 +191,20 @@ class StudyReviewController( fun findAllStudyReviewsBySession( @PathVariable systematicStudy: UUID, @PathVariable searchSessionId: UUID, + @RequestParam("page", required = false, defaultValue = "0") page: Int, + @RequestParam("size", required = false, defaultValue = "20") size: Int, + @RequestParam("sort", required = false, defaultValue = "id,asc") sort: String ): ResponseEntity<*> { val presenter = RestfulFindAllStudyReviewsBySessionPresenter(linksFactory) val userId = authenticationInfoService.getAuthenticatedUserId() - val request = FindAllStudyReviewsBySessionService.RequestModel(userId, systematicStudy, searchSessionId) + val request = FindAllStudyReviewsBySessionService.RequestModel( + userId = userId, + systematicStudyId = systematicStudy, + searchSessionId = searchSessionId, + page = page, + pageSize = size, + sort = sort + ) findAllBySessionService.findAllBySearchSession(presenter, request) return presenter.responseEntity ?: ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR) } @@ -485,4 +530,105 @@ class StudyReviewController( removeCriteriaService.removeCriteria(presenter, request) return presenter.responseEntity ?: ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR) } + + @GetMapping("/study-review/search") + @Operation( + summary = "Advanced search for study reviews within a systematic study", + description = """ + Performs a flexible multi-criteria search across study reviews belonging to a given systematic study. + You can filter by fields such as id, studyReviewId, title, authors, venue, year, selectionStatus, + extractionStatus, score, and readingPriority. Supports pagination, sorting, and partial (case-insensitive) + text matches for textual fields. + """ + ) + @ApiResponses( + value = [ + ApiResponse( + responseCode = "200", + description = "Successfully executed the advanced search for study reviews. The response may include zero or more results.", + content = [Content( + mediaType = "application/json", + schema = Schema(implementation = FindAllStudyReviewsByAdvancedSearchService.ResponseModel::class), + examples = [ + ExampleObject( + name = "ComplexSearchExample", + summary = "Example of a multi-criteria advanced search", + description = "This example demonstrates how to combine multiple filters and sorting options.", + value = "GET /api/v1/systematic-study/7b8e22cc-4e09-4d4f-86bb-0a1f908d2f64/study-review/search?title=deep%20learning&authors=Goodfellow&venue=IEEE&year=2021&selectionStatus=INCLUDED&extractionStatus=PENDING&readingPriority=2&page=0&size=10&sort=year,desc" + ) + ] + )] + ), + ApiResponse( + responseCode = "401", + description = "Unauthorized – the user must be authenticated to perform this search.", + content = [Content(schema = Schema(hidden = true))] + ), + ApiResponse( + responseCode = "403", + description = "Forbidden – the authenticated user does not have permission to access this systematic study.", + content = [Content(schema = Schema(hidden = true))] + ), + ApiResponse( + responseCode = "500", + description = "Internal server error – unexpected failure during search execution.", + content = [Content(schema = Schema(hidden = true))] + ) + ] + ) + @Parameters( + Parameter(name = "id", description = "Internal numeric ID of the study review", example = "15"), + Parameter(name = "studyReviewId", description = "External study review identifier (partial match allowed)", example = "S123"), + Parameter(name = "title", description = "Partial title match (case-insensitive)", example = "deep learning"), + Parameter(name = "authors", description = "Partial author name match (case-insensitive)", example = "Goodfellow"), + Parameter(name = "venue", description = "Conference or journal name match (case-insensitive)", example = "IEEE"), + Parameter(name = "year", description = "Exact publication year", example = "2021"), + Parameter(name = "selectionStatus", description = "Exact selection status filter", example = "INCLUDED"), + Parameter(name = "extractionStatus", description = "Exact extraction status filter", example = "PENDING"), + Parameter(name = "score", description = "Exact numeric score filter", example = "0.85"), + Parameter(name = "readingPriority", description = "Exact reading priority filter", example = "2"), + Parameter(name = "page", description = "Page index (0-based, default = 0)", example = "0"), + Parameter(name = "size", description = "Number of elements per page (default = 20)", example = "20"), + Parameter(name = "sort", description = "Sorting criteria in the format `,` (default = id,asc)", example = "year,desc") + ) + fun searchStudyReviews( + @PathVariable systematicStudy: UUID, + @RequestParam(required = false) id: Long?, + @RequestParam(required = false) studyReviewId: String?, + @RequestParam(required = false) title: String?, + @RequestParam(required = false) authors: String?, + @RequestParam(required = false) venue: String?, + @RequestParam(required = false) year: Int?, + @RequestParam(required = false) selectionStatus: String?, + @RequestParam(required = false) extractionStatus: String?, + @RequestParam(required = false) score: Double?, + @RequestParam(required = false) readingPriority: Int?, + @RequestParam(required = false, defaultValue = "0") page: Int, + @RequestParam(required = false, defaultValue = "20") size: Int, + @RequestParam(required = false, defaultValue = "id,asc") sort: String, + ): ResponseEntity<*> { + val presenter = RestfulFindAllStudyReviewsByAdvancedSearchPresenter(linksFactory) + val userId = authenticationInfoService.getAuthenticatedUserId() + + val request = FindAllStudyReviewsByAdvancedSearchService.RequestModel( + userId = userId, + systematicStudyId = systematicStudy, + id = id, + studyReviewId = studyReviewId, + title = title, + authors = authors, + venue = venue, + year = year, + selectionStatus = selectionStatus, + extractionStatus = extractionStatus, + score = score, + readingPriority = readingPriority, + page = page, + pageSize = size, + sort = sort + ) + + findAllStudyReviewsByAdvancedSearchService.findAllByAdvancedSearch(presenter, request) + return presenter.responseEntity ?: ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR) + } } \ No newline at end of file diff --git a/web/src/main/kotlin/br/all/study/controller/StudyReviewServicesConfiguration.kt b/web/src/main/kotlin/br/all/study/controller/StudyReviewServicesConfiguration.kt index 54d9e3f2..e5f9eb20 100644 --- a/web/src/main/kotlin/br/all/study/controller/StudyReviewServicesConfiguration.kt +++ b/web/src/main/kotlin/br/all/study/controller/StudyReviewServicesConfiguration.kt @@ -66,6 +66,16 @@ class StudyReviewServicesConfiguration { ) = FindAllStudyReviewsBySessionServiceImpl( systematicStudyRepository, studyReviewRepository, credentialsService ) + + @Bean + fun findAllIncludedStudyReviewsService( + systematicStudyRepository: SystematicStudyRepository, + studyReviewRepository: StudyReviewRepository, + credentialsService: CredentialsService, + ) = FindAllIncludedStudyReviewsServiceImpl( + systematicStudyRepository, studyReviewRepository, credentialsService + ) + @Bean fun findReviewService( systematicStudyRepository: SystematicStudyRepository, @@ -149,4 +159,15 @@ class StudyReviewServicesConfiguration { systematicStudyRepository, credentialsService ) + + @Bean + fun findAllStudyReviewsByAdvancedSearchService( + systematicStudyRepository: SystematicStudyRepository, + studyReviewRepository: StudyReviewRepository, + credentialsService: CredentialsService + ) = FindAllStudyReviewsByAdvancedSearchServiceImpl( + systematicStudyRepository, + studyReviewRepository, + credentialsService + ) } \ No newline at end of file diff --git a/web/src/main/kotlin/br/all/study/presenter/RestfulFindAllIncludedStudyReviewsPresenter.kt b/web/src/main/kotlin/br/all/study/presenter/RestfulFindAllIncludedStudyReviewsPresenter.kt new file mode 100644 index 00000000..e17fdba7 --- /dev/null +++ b/web/src/main/kotlin/br/all/study/presenter/RestfulFindAllIncludedStudyReviewsPresenter.kt @@ -0,0 +1,90 @@ +package br.all.study.presenter + +import br.all.application.study.find.presenter.FindAllIncludedStudyReviewsPresenter +import br.all.application.study.find.service.FindAllIncludedStudyReviewsService +import br.all.application.study.repository.StudyReviewDto +import br.all.shared.error.createErrorResponseFrom +import br.all.utils.LinksFactory +import org.springframework.hateoas.RepresentationModel +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import java.util.* + +class RestfulFindAllIncludedStudyReviewsPresenter ( + private val linksFactory: LinksFactory +): FindAllIncludedStudyReviewsPresenter { + + var responseEntity: ResponseEntity<*>? = null + + override fun prepareSuccessView(response: FindAllIncludedStudyReviewsService.ResponseModel) { + val ( + _, + systematicStudyId, + studyReviews, + page, + size, + totalElements, + totalPages + ) = response + + val restfulResponse = ViewModel( + systematicStudyId = systematicStudyId, + size = studyReviews.size, + studyReviews = studyReviews, + page = page, + totalElements = totalElements, + totalPages = totalPages + ) + + val selfRef = linksFactory.findAllIncludedStudies( + systematicStudyId, + page, + size + ) + + if (totalPages > 0) { + restfulResponse.add(linksFactory.findAllIncludedStudiesFirstPage( + systematicStudyId, + size + )) + restfulResponse.add(linksFactory.findAllIncludedStudiesLastPage( + systematicStudyId, + totalPages, + size + )) + + if (page < totalPages - 1) { + restfulResponse.add(linksFactory.findAllIncludedStudiesNextPage( + systematicStudyId, + page, + size + )) + } + + if (page > 0) { + restfulResponse.add(linksFactory.findAllIncludedStudiesPrevPage( + systematicStudyId, + page, + size + )) + } + } + + restfulResponse.add(selfRef) + + responseEntity = ResponseEntity.status(HttpStatus.OK).body(restfulResponse) + } + + override fun prepareFailView(throwable: Throwable) = run { responseEntity = createErrorResponseFrom(throwable) } + + override fun isDone() = responseEntity != null + + private data class ViewModel( + val systematicStudyId: UUID, + val size: Int, + val studyReviews: List, + val page: Int, + val totalElements: Long, + val totalPages: Int + ) : RepresentationModel() +} \ No newline at end of file diff --git a/web/src/main/kotlin/br/all/study/presenter/RestfulFindAllStudyReviewsByAdvancedSearchPresenter.kt b/web/src/main/kotlin/br/all/study/presenter/RestfulFindAllStudyReviewsByAdvancedSearchPresenter.kt new file mode 100644 index 00000000..540a4706 --- /dev/null +++ b/web/src/main/kotlin/br/all/study/presenter/RestfulFindAllStudyReviewsByAdvancedSearchPresenter.kt @@ -0,0 +1,50 @@ +package br.all.study.presenter + +import br.all.application.study.find.presenter.FindAllStudyReviewsByAdvancedSearchPresenter +import br.all.application.study.find.service.FindAllStudyReviewsByAdvancedSearchService +import br.all.application.study.repository.StudyReviewDto +import br.all.shared.error.createErrorResponseFrom +import br.all.utils.LinksFactory +import org.springframework.hateoas.RepresentationModel +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import java.util.* + +class RestfulFindAllStudyReviewsByAdvancedSearchPresenter( + private val linksFactory: LinksFactory +) : FindAllStudyReviewsByAdvancedSearchPresenter { + + var responseEntity: ResponseEntity<*>? = null + + override fun prepareSuccessView(response: FindAllStudyReviewsByAdvancedSearchService.ResponseModel) { + val restfulResponse = ViewModel( + systematicStudyId = response.systematicStudyId, + size = response.studyReviews.size, + studyReviews = response.studyReviews, + page = response.page, + totalElements = response.totalElements, + totalPages = response.totalPages + ) + + restfulResponse.add( + linksFactory.findAllIncludedStudies(response.systematicStudyId, response.page, response.size) + ) + + responseEntity = ResponseEntity.status(HttpStatus.OK).body(restfulResponse) + } + + override fun prepareFailView(throwable: Throwable) { + responseEntity = createErrorResponseFrom(throwable) + } + + override fun isDone() = responseEntity != null + + private data class ViewModel( + val systematicStudyId: UUID, + val size: Int, + val studyReviews: List, + val page: Int, + val totalElements: Long, + val totalPages: Int + ) : RepresentationModel() +} diff --git a/web/src/main/kotlin/br/all/study/presenter/RestfulFindAllStudyReviewsBySessionPresenter.kt b/web/src/main/kotlin/br/all/study/presenter/RestfulFindAllStudyReviewsBySessionPresenter.kt index f02fbf82..7c49a580 100644 --- a/web/src/main/kotlin/br/all/study/presenter/RestfulFindAllStudyReviewsBySessionPresenter.kt +++ b/web/src/main/kotlin/br/all/study/presenter/RestfulFindAllStudyReviewsBySessionPresenter.kt @@ -18,8 +18,67 @@ class RestfulFindAllStudyReviewsBySessionPresenter ( var responseEntity: ResponseEntity<*>? = null override fun prepareSuccessView(response: FindAllStudyReviewsBySessionService.ResponseModel) { - val (_, systematicStudyId, searchSessionId, studyReviews) = response - val restfulResponse = ViewModel(systematicStudyId, searchSessionId, studyReviews.size, studyReviews) + val ( + _, + systematicStudyId, + searchSessionId, + studyReviews, + page, + size, + totalElements, + totalPages + ) = response + + val restfulResponse = ViewModel( + systematicStudyId = systematicStudyId, + searchSessionId = searchSessionId, + size = studyReviews.size, + studyReviews = studyReviews, + page = page, + totalElements = totalElements, + totalPages = totalPages + ) + + val selfRef = linksFactory.findAllStudiesBySession( + systematicStudyId, + searchSessionId, + page, + size + ) + + if (totalPages > 0) { + restfulResponse.add(linksFactory.findAllStudiesBySessionFirstPage( + systematicStudyId, + searchSessionId, + size + )) + restfulResponse.add(linksFactory.findAllStudiesBySessionLastPage( + systematicStudyId, + searchSessionId, + totalPages, + size + )) + + if (page < totalPages - 1) { + restfulResponse.add(linksFactory.findAllStudiesBySessionNextPage( + systematicStudyId, + searchSessionId, + page, + size + )) + } + + if (page > 0) { + restfulResponse.add(linksFactory.findAllStudiesBySessionPrevPage( + systematicStudyId, + searchSessionId, + page, + size + )) + } + } + + restfulResponse.add(selfRef) responseEntity = ResponseEntity.status(HttpStatus.OK).body(restfulResponse) } @@ -33,5 +92,8 @@ class RestfulFindAllStudyReviewsBySessionPresenter ( val searchSessionId: UUID, val size: Int, val studyReviews: List, + val page: Int, + val totalElements: Long, + val totalPages: Int ) : RepresentationModel() } \ No newline at end of file diff --git a/web/src/main/kotlin/br/all/utils/LinksFactory.kt b/web/src/main/kotlin/br/all/utils/LinksFactory.kt index ea7d1b7f..996b486a 100644 --- a/web/src/main/kotlin/br/all/utils/LinksFactory.kt +++ b/web/src/main/kotlin/br/all/utils/LinksFactory.kt @@ -208,27 +208,89 @@ class LinksFactory { fun findAllStudies(systematicStudyId: UUID, page: Int = 0, size: Int = 20, sort: String = "id,asc"): Link = linkTo { findAllStudyReviews(systematicStudyId, page, size, sort) }.withRel("find-all-studies").withType("GET") - - fun findAllStudiesFirstPage(systematicStudyId: UUID, size: Int = 20, sort: String = "id,asc"): Link = + + fun findAllStudiesFirstPage(systematicStudyId: UUID, size: Int = 20, sort: String = "id,asc"): Link = findAllStudies(systematicStudyId, 0, size, sort).withRel("first").withType("GET") - - fun findAllStudiesLastPage(systematicStudyId: UUID, totalPages: Int, size: Int = 20, sort: String = "id,asc"): Link = + + fun findAllStudiesLastPage(systematicStudyId: UUID, totalPages: Int, size: Int = 20, sort: String = "id,asc"): Link = findAllStudies(systematicStudyId, totalPages - 1, size, sort).withRel("last").withType("GET") - - fun findAllStudiesNextPage(systematicStudyId: UUID, currentPage: Int, size: Int = 20, sort: String = "id,asc"): Link = + + fun findAllStudiesNextPage(systematicStudyId: UUID, currentPage: Int, size: Int = 20, sort: String = "id,asc"): Link = findAllStudies(systematicStudyId, currentPage + 1, size, sort).withRel("next").withType("GET") - - fun findAllStudiesPrevPage(systematicStudyId: UUID, currentPage: Int, size: Int = 20, sort: String = "id,asc"): Link = + + fun findAllStudiesPrevPage(systematicStudyId: UUID, currentPage: Int, size: Int = 20, sort: String = "id,asc"): Link = findAllStudies(systematicStudyId, currentPage - 1, size, sort).withRel("prev").withType("GET") fun findAllStudiesBySource(systematicStudyId: UUID, source: String): Link = linkTo { findAllStudyReviewsBySource(systematicStudyId, source) }.withRel("find-all-studies-by-source").withType("GET") - fun findAllStudiesBySession(systematicStudyId: UUID, searchSessionId: UUID): Link = linkTo { - findAllStudyReviewsBySession(systematicStudyId, searchSessionId) + fun findAllIncludedStudies(systematicStudyId: UUID, page: Int = 0, size: Int = 20, sort: String = "id,asc"): Link = linkTo { + findAllStudyReviews(systematicStudyId, page, size, sort) + }.withRel("find-all-studies").withType("GET") + + fun findAllIncludedStudiesFirstPage(systematicStudyId: UUID, size: Int = 20, sort: String = "id,asc"): Link = + findAllStudies(systematicStudyId, 0, size, sort).withRel("first").withType("GET") + + fun findAllIncludedStudiesLastPage(systematicStudyId: UUID, totalPages: Int, size: Int = 20, sort: String = "id,asc"): Link = + findAllStudies(systematicStudyId, totalPages - 1, size, sort).withRel("last").withType("GET") + + fun findAllIncludedStudiesNextPage(systematicStudyId: UUID, currentPage: Int, size: Int = 20, sort: String = "id,asc"): Link = + findAllStudies(systematicStudyId, currentPage + 1, size, sort).withRel("next").withType("GET") + + fun findAllIncludedStudiesPrevPage(systematicStudyId: UUID, currentPage: Int, size: Int = 20, sort: String = "id,asc"): Link = + findAllStudies(systematicStudyId, currentPage - 1, size, sort).withRel("prev").withType("GET") + + fun findAllIncludedStudiesBySource(systematicStudyId: UUID, source: String): Link = linkTo { + findAllStudyReviewsBySource(systematicStudyId, source) + }.withRel("find-all-studies-by-source").withType("GET") + + fun findAllStudiesBySession( + systematicStudyId: UUID, + searchSessionId: UUID, + page: Int = 0, + size: Int = 20, + sort: String = "id,asc" + ): Link = linkTo { + findAllStudyReviewsBySession(systematicStudyId, searchSessionId, page, size, sort) }.withRel("find-all-studies-by-session").withType("GET") + fun findAllStudiesBySessionFirstPage( + systematicStudyId: UUID, + searchSessionId: UUID, + size: Int = 20, + sort: String = "id,asc" + ): Link = + findAllStudiesBySession(systematicStudyId, searchSessionId, 0, size, sort).withRel("first").withType("GET") + + fun findAllStudiesBySessionLastPage( + systematicStudyId: UUID, + searchSessionId: UUID, + totalPages: Int, + size: Int = 20, + sort: String = "id,asc" + ): Link = + findAllStudiesBySession(systematicStudyId, searchSessionId, totalPages - 1, size, sort).withRel("last").withType("GET") + + fun findAllStudiesBySessionNextPage( + systematicStudyId: UUID, + searchSessionId: UUID, + currentPage: Int, + size: Int = 20, + sort: String = "id,asc" + ): Link = + findAllStudiesBySession(systematicStudyId, searchSessionId, currentPage + 1, size, sort).withRel("next").withType("GET") + + fun findAllStudiesBySessionPrevPage( + systematicStudyId: UUID, + searchSessionId: UUID, + currentPage: Int, + size: Int = 20, + sort: String = "id,asc" + ): Link = + findAllStudiesBySession(systematicStudyId, searchSessionId, currentPage - 1, size, sort).withRel("prev").withType("GET") + + fun updateStudySelectionStatus(systematicStudyId: UUID): Link = linkTo { updateStudyReviewSelectionStatus( systematicStudyId,