diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/prior_notification/PriorNotificationDocument.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/prior_notification/PriorNotificationDocument.kt index b5dd6f0a53..b85daec526 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/prior_notification/PriorNotificationDocument.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/prior_notification/PriorNotificationDocument.kt @@ -2,7 +2,7 @@ package fr.gouv.cnsp.monitorfish.domain.entities.prior_notification import fr.gouv.cnsp.monitorfish.utils.CustomZonedDateTime -class PriorNotificationDocument( +data class PriorNotificationDocument( val id: String?, val content: ByteArray, val createdAt: CustomZonedDateTime, @@ -11,4 +11,34 @@ class PriorNotificationDocument( val mimeType: String, val reportId: String, val updatedAt: CustomZonedDateTime, -) +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as PriorNotificationDocument + + if (id != other.id) return false + if (!content.contentEquals(other.content)) return false + if (createdAt != other.createdAt) return false + if (fileName != other.fileName) return false + if (isManualPriorNotification != other.isManualPriorNotification) return false + if (mimeType != other.mimeType) return false + if (reportId != other.reportId) return false + if (updatedAt != other.updatedAt) return false + + return true + } + + override fun hashCode(): Int { + var result = id?.hashCode() ?: 0 + result = 31 * result + content.contentHashCode() + result = 31 * result + createdAt.hashCode() + result = 31 * result + fileName.hashCode() + result = 31 * result + isManualPriorNotification.hashCode() + result = 31 * result + mimeType.hashCode() + result = 31 * result + reportId.hashCode() + result = 31 * result + updatedAt.hashCode() + return result + } +} diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/prior_notification/PriorNotificationIdentifier.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/prior_notification/PriorNotificationIdentifier.kt new file mode 100644 index 0000000000..8a05240251 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/prior_notification/PriorNotificationIdentifier.kt @@ -0,0 +1,9 @@ +package fr.gouv.cnsp.monitorfish.domain.entities.prior_notification + +import java.time.ZonedDateTime + +data class PriorNotificationIdentifier( + val reportId: String, + val operationDate: ZonedDateTime, + val isManualPriorNotification: Boolean, +) diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/CreatePriorNotificationUpload.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/CreatePriorNotificationUpload.kt index d151e70edb..d438afbce7 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/CreatePriorNotificationUpload.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/CreatePriorNotificationUpload.kt @@ -2,34 +2,49 @@ package fr.gouv.cnsp.monitorfish.domain.use_cases.prior_notification import fr.gouv.cnsp.monitorfish.config.UseCase import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotificationDocument +import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotificationIdentifier +import fr.gouv.cnsp.monitorfish.domain.repositories.LogbookReportRepository +import fr.gouv.cnsp.monitorfish.domain.repositories.ManualPriorNotificationRepository import fr.gouv.cnsp.monitorfish.domain.repositories.PriorNotificationUploadRepository -import fr.gouv.cnsp.monitorfish.infrastructure.exceptions.BackendRequestErrorCode -import fr.gouv.cnsp.monitorfish.infrastructure.exceptions.BackendRequestException import fr.gouv.cnsp.monitorfish.utils.CustomZonedDateTime -import org.springframework.web.multipart.MultipartFile @UseCase class CreatePriorNotificationUpload( + private val logbookReportRepository: LogbookReportRepository, + private val manualPriorNotificationRepository: ManualPriorNotificationRepository, private val priorNotificationUploadRepository: PriorNotificationUploadRepository, ) { - fun execute(reportId: String, isManualPriorNotification: Boolean, file: MultipartFile) { + fun execute(identifier: PriorNotificationIdentifier, content: ByteArray, fileName: String, mimeType: String) { val createdAt = CustomZonedDateTime.now() - val fileName = file.originalFilename - ?: throw BackendRequestException(BackendRequestErrorCode.MISSING_UPLOADED_FILE_NAME) - val mimeType = file.contentType - ?: throw BackendRequestException(BackendRequestErrorCode.MISSING_UPLOADED_FILE_TYPE) val newPriorNotificationDocument = PriorNotificationDocument( id = null, - content = file.bytes, + content = content, createdAt = createdAt, fileName = fileName, - isManualPriorNotification = isManualPriorNotification, + isManualPriorNotification = identifier.isManualPriorNotification, mimeType = mimeType, - reportId = reportId, + reportId = identifier.reportId, updatedAt = createdAt, ) priorNotificationUploadRepository.save(newPriorNotificationDocument) + + if (identifier.isManualPriorNotification) { + manualPriorNotificationRepository.updateState( + reportId = identifier.reportId, + isBeingSent = false, + isSent = false, + isVerified = false, + ) + } else { + logbookReportRepository.updatePriorNotificationState( + reportId = identifier.reportId, + operationDate = identifier.operationDate, + isBeingSent = false, + isSent = false, + isVerified = false, + ) + } } } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/DeletePriorNotificationUpload.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/DeletePriorNotificationUpload.kt index 364943135b..408bd50be2 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/DeletePriorNotificationUpload.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/DeletePriorNotificationUpload.kt @@ -1,13 +1,35 @@ package fr.gouv.cnsp.monitorfish.domain.use_cases.prior_notification import fr.gouv.cnsp.monitorfish.config.UseCase +import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotificationIdentifier +import fr.gouv.cnsp.monitorfish.domain.repositories.LogbookReportRepository +import fr.gouv.cnsp.monitorfish.domain.repositories.ManualPriorNotificationRepository import fr.gouv.cnsp.monitorfish.domain.repositories.PriorNotificationUploadRepository @UseCase class DeletePriorNotificationUpload( + private val logbookReportRepository: LogbookReportRepository, + private val manualPriorNotificationRepository: ManualPriorNotificationRepository, private val priorNotificationUploadRepository: PriorNotificationUploadRepository, ) { - fun execute(priorNotificationUploadId: String) { + fun execute(identifier: PriorNotificationIdentifier, priorNotificationUploadId: String) { priorNotificationUploadRepository.deleteById(priorNotificationUploadId) + + if (identifier.isManualPriorNotification) { + manualPriorNotificationRepository.updateState( + reportId = identifier.reportId, + isBeingSent = false, + isSent = false, + isVerified = false, + ) + } else { + logbookReportRepository.updatePriorNotificationState( + reportId = identifier.reportId, + operationDate = identifier.operationDate, + isBeingSent = false, + isSent = false, + isVerified = false, + ) + } } } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/PriorNotificationController.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/PriorNotificationController.kt index 2b324321b6..00e73027ea 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/PriorNotificationController.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/PriorNotificationController.kt @@ -1,6 +1,7 @@ package fr.gouv.cnsp.monitorfish.infrastructure.api.bff import fr.gouv.cnsp.monitorfish.domain.entities.facade.SeafrontGroup +import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotificationIdentifier import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotificationState import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.filters.PriorNotificationsFilter import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.sorters.PriorNotificationsSortColumn @@ -9,6 +10,8 @@ import fr.gouv.cnsp.monitorfish.infrastructure.api.input.LogbookPriorNotificatio import fr.gouv.cnsp.monitorfish.infrastructure.api.input.ManualPriorNotificationComputeDataInput import fr.gouv.cnsp.monitorfish.infrastructure.api.input.ManualPriorNotificationFormDataInput import fr.gouv.cnsp.monitorfish.infrastructure.api.outputs.* +import fr.gouv.cnsp.monitorfish.infrastructure.exceptions.BackendRequestErrorCode +import fr.gouv.cnsp.monitorfish.infrastructure.exceptions.BackendRequestException import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.Parameter import io.swagger.v3.oas.annotations.tags.Tag @@ -383,17 +386,25 @@ class PriorNotificationController( @PathParam("Logbook message `reportId`") @PathVariable(name = "reportId") reportId: String, + @Parameter(description = "Operation date (to optimize SQL query via Timescale).") + @RequestParam(name = "operationDate") + operationDate: ZonedDateTime, @Parameter(description = "Is the prior notification manually created?") @RequestParam(name = "isManualPriorNotification") isManualPriorNotification: Boolean, @RequestParam("file") file: MultipartFile, ): ResponseEntity<*> { - if (file.isEmpty) { - return ResponseEntity("Please select a file to upload", HttpStatus.BAD_REQUEST) - } + val content = file.bytes + ?: throw BackendRequestException(BackendRequestErrorCode.EMPTY_UPLOADED_FILE) + val fileName = file.originalFilename + ?: throw BackendRequestException(BackendRequestErrorCode.MISSING_UPLOADED_FILE_NAME) + val mimeType = file.contentType + ?: throw BackendRequestException(BackendRequestErrorCode.MISSING_UPLOADED_FILE_TYPE) + + val identifier = PriorNotificationIdentifier(reportId, operationDate, isManualPriorNotification) - createPriorNotificationUpload.execute(reportId, isManualPriorNotification, file) + createPriorNotificationUpload.execute(identifier, content, fileName, mimeType) return ResponseEntity("File uploaded successfully: " + file.originalFilename, HttpStatus.OK) } @@ -404,11 +415,19 @@ class PriorNotificationController( @PathParam("Logbook message `reportId`") @PathVariable(name = "reportId") reportId: String, + @Parameter(description = "Operation date (to optimize SQL query via Timescale).") + @RequestParam(name = "operationDate") + operationDate: ZonedDateTime, + @Parameter(description = "Is the prior notification manually created?") + @RequestParam(name = "isManualPriorNotification") + isManualPriorNotification: Boolean, @PathParam("Prior notification upload ID") @PathVariable(name = "priorNotificationUploadId") priorNotificationUploadId: String, ): ResponseEntity { - deletePriorNotificationUpload.execute(priorNotificationUploadId) + val identifier = PriorNotificationIdentifier(reportId, operationDate, isManualPriorNotification) + + deletePriorNotificationUpload.execute(identifier, priorNotificationUploadId) return ResponseEntity(HttpStatus.NO_CONTENT) } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/exceptions/BackendRequestErrorCode.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/exceptions/BackendRequestErrorCode.kt index e4e0206a17..8ea5e93243 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/exceptions/BackendRequestErrorCode.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/exceptions/BackendRequestErrorCode.kt @@ -12,7 +12,12 @@ package fr.gouv.cnsp.monitorfish.infrastructure.exceptions * **Don't forget to mirror any update here in the corresponding Frontend enum.** */ enum class BackendRequestErrorCode { + /** The uploaded file is empty. */ + EMPTY_UPLOADED_FILE, + + /** Couldn't extract the uploaded file name. */ MISSING_UPLOADED_FILE_NAME, + + /** Couldn't extract the uploaded file type. */ MISSING_UPLOADED_FILE_TYPE, - WRONG_BODY_PARAMETER_TYPE, } diff --git a/frontend/src/features/PriorNotification/components/LogbookPriorNotificationForm/Form.tsx b/frontend/src/features/PriorNotification/components/LogbookPriorNotificationForm/Form.tsx index f674a68fd4..8558fc177a 100644 --- a/frontend/src/features/PriorNotification/components/LogbookPriorNotificationForm/Form.tsx +++ b/frontend/src/features/PriorNotification/components/LogbookPriorNotificationForm/Form.tsx @@ -81,7 +81,11 @@ export function Form({ detail, initialFormValues }: FormProps) {
- + diff --git a/frontend/src/features/PriorNotification/components/shared/UploadFiles.tsx b/frontend/src/features/PriorNotification/components/shared/UploadFiles.tsx index 1affe5a7ce..ff91b0bde6 100644 --- a/frontend/src/features/PriorNotification/components/shared/UploadFiles.tsx +++ b/frontend/src/features/PriorNotification/components/shared/UploadFiles.tsx @@ -17,13 +17,14 @@ import type { FileType } from 'rsuite/esm/Uploader' type UploadFilesProps = Readonly<{ isManualPriorNotification: boolean + operationDate: string reportId: string }> -export function UploadFiles({ isManualPriorNotification, reportId }: UploadFilesProps) { +export function UploadFiles({ isManualPriorNotification, operationDate, reportId }: UploadFilesProps) { const dispatch = useMainAppDispatch() const headers = useAuthRequestHeaders() - const action = `/bff/v1/prior_notifications/${reportId}/uploads?isManualPriorNotification=${isManualPriorNotification}` + const action = `/bff/v1/prior_notifications/${reportId}/uploads?isManualPriorNotification=${isManualPriorNotification}&operationDate=${operationDate}` const { data: uploads } = useGetPriorNotificationUploadsQuery(reportId) const key = useKey([uploads]) @@ -59,14 +60,18 @@ export function UploadFiles({ isManualPriorNotification, reportId }: UploadFiles const remove = useCallback( async (file: FileType) => { + assertNotNullish(file.fileKey) + await dispatch( priorNotificationApi.endpoints.deletePriorNotificationUpload.initiate({ - priorNotificationUploadId: file.fileKey, + isManualPriorNotification, + operationDate, + priorNotificationUploadId: String(file.fileKey), reportId }) ).unwrap() }, - [dispatch, reportId] + [dispatch, isManualPriorNotification, operationDate, reportId] ) if (!headers || !uploadAsFileTypes) { diff --git a/frontend/src/features/PriorNotification/priorNotificationApi.ts b/frontend/src/features/PriorNotification/priorNotificationApi.ts index cf67cbfb25..75a91b3390 100644 --- a/frontend/src/features/PriorNotification/priorNotificationApi.ts +++ b/frontend/src/features/PriorNotification/priorNotificationApi.ts @@ -57,11 +57,22 @@ export const priorNotificationApi = monitorfishApi.injectEndpoints({ transformErrorResponse: response => new FrontendApiError(CREATE_PRIOR_NOTIFICATION_ERROR_MESSAGE, response) }), - deletePriorNotificationUpload: builder.mutation({ + deletePriorNotificationUpload: builder.mutation< + void, + { + isManualPriorNotification: boolean + operationDate: string + priorNotificationUploadId: string + reportId: string + } + >({ invalidatesTags: [{ type: RtkCacheTagType.PriorNotificationDocuments }], - query: ({ priorNotificationUploadId, reportId }) => ({ + query: ({ isManualPriorNotification, operationDate, priorNotificationUploadId, reportId }) => ({ method: 'DELETE', - url: `/prior_notifications/${reportId}/uploads/${priorNotificationUploadId}` + url: getUrlOrPathWithQueryParams(`/prior_notifications/${reportId}uploads/${priorNotificationUploadId}`, { + isManualPriorNotification, + operationDate + }) }), transformErrorResponse: response => new FrontendApiError(DELETE_PRIOR_NOTIFICATION_UPLOAD_ERROR_MESSAGE, response) }),