Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
ivangabriele committed Oct 13, 2023
1 parent 3f979d7 commit 04f023d
Show file tree
Hide file tree
Showing 13 changed files with 187 additions and 26 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package fr.gouv.cacem.monitorenv.domain.use_cases.base

import fr.gouv.cacem.monitorenv.config.UseCase
import fr.gouv.cacem.monitorenv.domain.repositories.IBaseRepository

@UseCase
class DeleteBase(private val baseRepository: IBaseRepository) {
fun execute(baseId: Int) {
baseRepository.deleteById(baseId)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ class ApiAdministrationsController(
fun delete(
@PathParam("Administration ID")
@PathVariable(name = "administrationId")
controlUnitId: Int,
administrationId: Int,
) {
deleteAdministration.execute(controlUnitId)
deleteAdministration.execute(administrationId)
}

@GetMapping("/{administrationId}")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package fr.gouv.cacem.monitorenv.infrastructure.api.endpoints.publicapi

import fr.gouv.cacem.monitorenv.domain.use_cases.base.CreateOrUpdateBase
import fr.gouv.cacem.monitorenv.domain.use_cases.base.DeleteBase
import fr.gouv.cacem.monitorenv.domain.use_cases.base.GetBaseById
import fr.gouv.cacem.monitorenv.domain.use_cases.base.GetBases
import fr.gouv.cacem.monitorenv.infrastructure.api.adapters.publicapi.inputs.CreateOrUpdateBaseDataInput
Expand All @@ -17,6 +18,7 @@ import org.springframework.web.bind.annotation.*
@Tag(name = "Bases", description = "API bases")
class ApiBasesController(
private val createOrUpdateBase: CreateOrUpdateBase,
private val deleteBase: DeleteBase,
private val getBases: GetBases,
private val getBaseById: GetBaseById,
) {
Expand All @@ -33,6 +35,16 @@ class ApiBasesController(
return BaseDataOutput.fromBase(createdBase)
}

@DeleteMapping("/{baseId}")
@Operation(summary = "Delete a base")
fun delete(
@PathParam("Administration ID")
@PathVariable(name = "baseId")
baseId: Int,
) {
deleteBase.execute(baseId)
}

@GetMapping("/{baseId}")
@Operation(summary = "Get a base by its ID")
fun get(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ class JpaBaseRepository(
private val dbBaseRepository: IDBBaseRepository,
) : IBaseRepository {
override fun deleteById(baseId: Int) {
val fullBase = findById(baseId)
// println(fullBase.controlUnitResources)
/*if (fullBase.controlUnitResources.isNotEmpty()) {
throw ForeignKeyConstraintException(
"Cannot delete base (ID=$baseId) due to existing relationships.",
)
}*/

dbBaseRepository.deleteById(baseId)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import fr.gouv.cacem.monitorenv.config.MapperConfiguration
import fr.gouv.cacem.monitorenv.config.WebSecurityConfig
import fr.gouv.cacem.monitorenv.domain.entities.base.BaseEntity
import fr.gouv.cacem.monitorenv.domain.use_cases.base.CreateOrUpdateBase
import fr.gouv.cacem.monitorenv.domain.use_cases.base.DeleteBase
import fr.gouv.cacem.monitorenv.domain.use_cases.base.GetBaseById
import fr.gouv.cacem.monitorenv.domain.use_cases.base.GetBases
import fr.gouv.cacem.monitorenv.domain.use_cases.base.dtos.FullBaseDTO
Expand Down Expand Up @@ -34,6 +35,9 @@ class ApiBasesControllerITests {
@MockBean
private lateinit var createOrUpdateBase: CreateOrUpdateBase

@MockBean
private lateinit var deleteBase: DeleteBase

@MockBean
private lateinit var getBaseById: GetBaseById

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import fr.gouv.cacem.monitorenv.domain.entities.base.BaseEntity
import fr.gouv.cacem.monitorenv.domain.entities.controlUnit.ControlUnitResourceEntity
import fr.gouv.cacem.monitorenv.domain.entities.controlUnit.ControlUnitResourceType
import fr.gouv.cacem.monitorenv.domain.use_cases.base.dtos.FullBaseDTO
import fr.gouv.cacem.monitorenv.infrastructure.database.repositories.exceptions.ForeignKeyConstraintException
import org.assertj.core.api.Assertions
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
Expand All @@ -13,6 +15,16 @@ class JpaBaseRepositoryITests : AbstractDBTests() {
@Autowired
private lateinit var jpaBaseRepository: JpaBaseRepository

@Test
@Transactional
fun `deleteById() should throw the expected exception when the base is linked to some control unit resources`() {
val throwable = Assertions.catchThrowable {
jpaBaseRepository.deleteById(1)
}

assertThat(throwable).isInstanceOf(ForeignKeyConstraintException::class.java)
}

@Test
@Transactional
fun `findAll() should find all bases`() {
Expand Down
22 changes: 21 additions & 1 deletion frontend/src/api/basesAPI.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,42 @@
import { monitorenvPublicApi } from './api'
import { DELETE_GENERIC_ERROR_MESSAGE } from './constants'
import { ApiErrorCode } from './types'
import { FrontendApiError } from '../libs/FrontendApiError'
import { newUserError } from '../libs/UserError'

import type { Base } from '../domain/entities/base'

const DELETE_BASE_ERROR_MESSAGE =
"Cette base est rattachée à des moyens. Veuillez l'en détacher avant de la supprimer ou bien l'archiver."
const GET_BASE_ERROR_MESSAGE = "Nous n'avons pas pu récupérer cette base."
const GET_BASES_ERROR_MESSAGE = "Nous n'avons pas pu récupérer la liste des bases."

export const basesAPI = monitorenvPublicApi.injectEndpoints({
endpoints: builder => ({
createBase: builder.mutation<void, Base.BaseData>({
invalidatesTags: () => [{ type: 'ControlUnits' }, { type: 'Bases' }],
invalidatesTags: () => [{ type: 'Bases' }],
query: newBaseData => ({
body: newBaseData,
method: 'POST',
url: `/v1/bases`
})
}),

deleteBase: builder.mutation<void, number>({
invalidatesTags: () => [{ type: 'Bases' }],
query: baseId => ({
method: 'DELETE',
url: `/v1/bases/${baseId}`
}),
transformErrorResponse: response => {
if (response.data.type === ApiErrorCode.FOREIGN_KEY_CONSTRAINT) {
return newUserError(DELETE_BASE_ERROR_MESSAGE)
}

return new FrontendApiError(DELETE_GENERIC_ERROR_MESSAGE, response)
}
}),

getBase: builder.query<Base.Base, number>({
providesTags: () => [{ type: 'Bases' }],
query: baseId => `/v1/bases/${baseId}`,
Expand Down
1 change: 1 addition & 0 deletions frontend/src/features/BackOffice/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ export enum BackOfficeConfirmationModalActionType {
'ARCHIVE_ADMINISTRATION' = 'ARCHIVE_ADMINISTRATION',
'ARCHIVE_CONTROL_UNIT' = 'ARCHIVE_CONTROL_UNIT',
'DELETE_ADMINISTRATION' = 'DELETE_ADMINISTRATION',
'DELETE_BASE' = 'DELETE_BASE',
'DELETE_CONTROL_UNIT' = 'DELETE_CONTROL_UNIT'
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { FrontendError } from '../../../libs/FrontendError'
import { archiveAdministration } from '../../Administration/useCases/archiveAdministration'
import { deleteAdministration } from '../../Administration/useCases/deleteAdministration'
import { deleteBase } from '../../Base/useCases/deleteBase'
import { archiveControlUnit } from '../../ControlUnit/usesCases/archiveControlUnit'
import { deleteControlUnit } from '../../ControlUnit/usesCases/deleteControlUnit'
import { backOfficeActions } from '../slice'
Expand All @@ -27,6 +28,10 @@ export const handleModalConfirmation = (): AppThunk<void> => async (dispatch, ge
await dispatch(deleteAdministration())
break

case BackOfficeConfirmationModalActionType.DELETE_BASE:
await dispatch(deleteBase())
break

case BackOfficeConfirmationModalActionType.DELETE_CONTROL_UNIT:
await dispatch(deleteControlUnit())
break
Expand Down
9 changes: 6 additions & 3 deletions frontend/src/features/Base/components/BaseTable/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,21 @@ import { DataTable } from '@mtes-mct/monitor-ui'
import { useMemo } from 'react'
import styled from 'styled-components'

import { BASE_TABLE_COLUMNS } from './constants'
import { FilterBar } from './FilterBar'
import { getFilters } from './utils'
import { getBaseTableColumns, getFilters } from './utils'
import { useGetBasesQuery } from '../../../../api/basesAPI'
import { useAppDispatch } from '../../../../hooks/useAppDispatch'
import { useAppSelector } from '../../../../hooks/useAppSelector'
import { NavButton } from '../../../../ui/NavButton'
import { BACK_OFFICE_MENU_PATH, BackOfficeMenuKey } from '../../../BackOfficeMenu/constants'

export function BaseTable() {
const backOfficeBaseList = useAppSelector(store => store.backOfficeBaseList)
const dispatch = useAppDispatch()
const { data: bases } = useGetBasesQuery()

const baseTableColumns = useMemo(() => getBaseTableColumns(dispatch), [dispatch])

const filteredBases = useMemo(() => {
if (!bases) {
return undefined
Expand All @@ -34,7 +37,7 @@ export function BaseTable() {
<NavButton to={`/backoffice${BACK_OFFICE_MENU_PATH[BackOfficeMenuKey.BASE_LIST]}/new`}>Nouvelle base</NavButton>
</ActionGroup>

<DataTable columns={BASE_TABLE_COLUMNS} data={filteredBases} initialSorting={[{ desc: false, id: 'name' }]} />
<DataTable columns={baseTableColumns} data={filteredBases} initialSorting={[{ desc: false, id: 'name' }]} />
</>
)
}
Expand Down
20 changes: 0 additions & 20 deletions frontend/src/features/Base/components/BaseTable/utils.ts

This file was deleted.

62 changes: 62 additions & 0 deletions frontend/src/features/Base/components/BaseTable/utils.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { CustomSearch, IconButton, type Filter, Size, Icon } from '@mtes-mct/monitor-ui'

import { BASE_TABLE_COLUMNS } from './constants'
import { backOfficeActions } from '../../../BackOffice/slice'
import { BackOfficeConfirmationModalActionType } from '../../../BackOffice/types'

import type { FiltersState } from './types'
import type { Base } from '../../../../domain/entities/base'
import type { AppDispatch } from '../../../../store'
import type { CellContext, ColumnDef } from '@tanstack/react-table'

function deleteBase(info: CellContext<Base.Base, unknown>, dispatch: AppDispatch) {
const base = info.getValue<Base.Base>()

dispatch(
backOfficeActions.openConfirmationModal({
actionType: BackOfficeConfirmationModalActionType.DELETE_BASE,
entityId: base.id,
modalProps: {
confirmationButtonLabel: 'Supprimer',
message: `Êtes-vous sûr de vouloir supprimer la base "${base.name}" ?`,
title: `Suppression de la base`
}
})
)
}

export function getBaseTableColumns(dispatch: AppDispatch): Array<ColumnDef<Base.Base>> {
const deleteColumn: ColumnDef<Base.Base> = {
accessorFn: row => row,
cell: info => (
<IconButton
Icon={Icon.Delete}
onClick={() => deleteBase(info, dispatch)}
size={Size.SMALL}
title="Supprimer cette base"
/>
),
enableSorting: false,
header: () => '',
id: 'delete',
size: 44
}

return [...BASE_TABLE_COLUMNS, deleteColumn]
}

export function getFilters(data: Base.Base[], filtersState: FiltersState): Filter<Base.Base>[] {
const customSearch = new CustomSearch(data, ['name'], {
cacheKey: 'BACK_OFFICE_BASE_LIST',
isStrict: true
})
const filters: Array<Filter<Base.Base>> = []

if (filtersState.query && filtersState.query.trim().length > 0) {
const filter: Filter<Base.Base> = () => customSearch.find(filtersState.query as string)

filters.push(filter)
}

return filters
}
43 changes: 43 additions & 0 deletions frontend/src/features/Base/useCases/deleteBase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { THEME, logSoftError } from '@mtes-mct/monitor-ui'

import { basesAPI } from '../../../api/basesAPI'
import { FrontendError } from '../../../libs/FrontendError'
import { isUserError } from '../../../libs/UserError'
import { backOfficeActions } from '../../BackOffice/slice'

import type { AppThunk } from '../../../store'

export const deleteBase = (): AppThunk<Promise<void>> => async (dispatch, getState) => {
const { confirmationModal } = getState().backOffice
if (!confirmationModal) {
throw new FrontendError('`confirmationModal` is undefined.')
}

try {
const { error } = await dispatch(basesAPI.endpoints.deleteBase.initiate(confirmationModal.entityId) as any)
if (error) {
throw error
}
} catch (err) {
if (isUserError(err)) {
dispatch(
backOfficeActions.openDialog({
dialogProps: {
color: THEME.color.maximumRed,
message: err.userMessage,
title: `Suppression impossible`,
titleBackgroundColor: THEME.color.maximumRed
}
})
)

return
}

logSoftError({
message: `An error happened while deleting an base (ID=${confirmationModal.entityId}").`,
originalError: err,
userMessage: "Une erreur est survenue pendant la suppression de l'base."
})
}
}

0 comments on commit 04f023d

Please sign in to comment.