Skip to content
Merged

Dev #210

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
08b18be
feat(delete-question): add DeleteQuestion service and presenter inter…
matheusspacifico Oct 18, 2025
8665aaa
refactor(delete-question): remove unused parameter from request model
matheusspacifico Oct 18, 2025
8ed2609
fix(delete-question): wrong generic presenter model
matheusspacifico Oct 18, 2025
f9ce03d
feat(question-repository): add deleteById method
matheusspacifico Oct 18, 2025
e3ab25a
feat(delete-question): implement DeleteQuestion service logic
matheusspacifico Oct 18, 2025
d56bbbd
feat(delete-question): add restful presenters for both rob and extrac…
matheusspacifico Oct 18, 2025
4f4122c
feat(delete-question): add endpoint to delete extraction question by ID
matheusspacifico Oct 18, 2025
3fe270b
feat(delete-question): add endpoint to delete risk of bias question b…
matheusspacifico Oct 18, 2025
b8e2ebf
feat(question-repository): implement deleteById method
matheusspacifico Oct 18, 2025
5fdaddc
docs(delete-question): update documentation to reflect removal of ans…
matheusspacifico Oct 18, 2025
dba5aa1
feat(delete-question): remove associated answers from study reviews o…
matheusspacifico Oct 18, 2025
cfd8f5b
feat(delete-question): track affected study review IDs during questio…
matheusspacifico Oct 18, 2025
39276c5
feat(delete-question): include affected study review IDs in delete re…
matheusspacifico Oct 18, 2025
c8c51f6
docs(delete-question): update descriptions and status codes for quest…
matheusspacifico Oct 18, 2025
15afcb8
feat(study-review): add paginated retrieval for reviews by session ID
matheusspacifico Oct 18, 2025
6f2f7eb
feat(study-review): add pagination support and sorting for reviews by…
matheusspacifico Oct 18, 2025
c0e20ec
Merge branch 'dev' into delete-question
matheusspacifico Oct 18, 2025
59cddf8
Merge pull request #207 from pet-ads/delete-question
matheusspacifico Oct 21, 2025
c50185a
feat(study-review): add service and presenter for retrieving included…
matheusspacifico Oct 21, 2025
55c20c1
feat(study-review): add support for filtering reviews by selection st…
matheusspacifico Oct 21, 2025
86d2443
feat(study-review): implement service for retrieving included study r…
matheusspacifico Oct 21, 2025
b721e71
feat(study-review): add bean configuration for included study reviews…
matheusspacifico Oct 21, 2025
0692976
refactor(study-review): simplify repository methods by removing `sear…
matheusspacifico Oct 21, 2025
596fdb0
refactor(study-review): remove `searchSessionId` from request and res…
matheusspacifico Oct 21, 2025
6bb7493
feat(study-review): add links for paginated navigation of included st…
matheusspacifico Oct 21, 2025
98aa3c8
feat(study-review): add endpoint for retrieving included study review…
matheusspacifico Oct 21, 2025
14eed63
feat: add service and presenter for advanced search of study reviews
matheusspacifico Oct 27, 2025
bbc1f27
feat: add mongodb query for advanced study review search
matheusspacifico Oct 27, 2025
d79d262
feat: implement study review advanced search service
matheusspacifico Oct 27, 2025
a1c3aa2
feat: implement restful presenter for advanced search of study reviews
matheusspacifico Oct 27, 2025
b8b3c38
feat: add bean configuration for advanced search service
matheusspacifico Oct 27, 2025
7ada943
feat: add endpoint for advanced search of study reviews with multi-cr…
matheusspacifico Oct 27, 2025
6bb13d5
Merge pull request #208 from pet-ads/advanced-search
matheusspacifico Oct 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package br.all.application.question.delete

import br.all.domain.shared.presenter.GenericPresenter

interface DeleteQuestionPresenter : GenericPresenter<DeleteQuestionService.ResponseModel>
Original file line number Diff line number Diff line change
@@ -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<Long>
)
}
Original file line number Diff line number Diff line change
@@ -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<Long>()

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))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<QuestionDto>
fun deleteById(systematicStudyId: UUID, id: UUID)
}
Original file line number Diff line number Diff line change
@@ -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<ResponseModel>
Original file line number Diff line number Diff line change
@@ -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<ResponseModel>
Original file line number Diff line number Diff line change
@@ -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<StudyReviewDto>,
val page: Int,
val size: Int,
val totalElements: Long,
val totalPages: Int
)
}
Original file line number Diff line number Diff line change
@@ -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
)
)
}

}
Original file line number Diff line number Diff line change
@@ -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<StudyReviewDto>,
val page: Int,
val size: Int,
val totalElements: Long,
val totalPages: Int
)
}
Original file line number Diff line number Diff line change
@@ -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
)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<StudyReviewDto>
val studyReviews: List<StudyReviewDto>,
val page: Int,
val size: Int,
val totalElements: Long,
val totalPages: Int
)
}
Loading