Skip to content
Snippets Groups Projects
Commit 3bc05d11 authored by BITARD Michaël's avatar BITARD Michaël
Browse files

refactor(api): utilise effect sur plusieurs routes (!1678)

parent 10daa16f
No related branches found
No related tags found
1 merge request!1678refactor(api): utilise effect sur plusieurs routes
Pipeline #541988 canceled
Showing
with 491 additions and 242 deletions
......@@ -17,11 +17,10 @@ import { z } from 'zod'
import { CaminoError } from 'camino-common/src/zod-tools'
import { Effect, pipe } from 'effect'
export const getUtilisateursByAdministrationId = async (pool: Pool, administrationId: AdministrationId): Promise<AdminUserNotNull[]> => {
const result = await dbQueryAndValidate(getUtilisateursByAdministrationIdDb, { administrationId }, pool, getUtilisateursByAdministrationIdDbValidator)
return result.map(a => ({ ...a, administrationId }))
}
export const getUtilisateursByAdministrationId = (pool: Pool, administrationId: AdministrationId): Effect.Effect<AdminUserNotNull[], CaminoError<EffectDbQueryAndValidateErrors>> =>
effectDbQueryAndValidate(getUtilisateursByAdministrationIdDb, { administrationId }, pool, getUtilisateursByAdministrationIdDbValidator).pipe(
Effect.map(result => result.map(a => ({ ...a, administrationId })))
)
const getUtilisateursByAdministrationIdDbValidator = adminUserNotNullValidator.omit({ administrationId: true })
......@@ -43,9 +42,11 @@ where
and keycloak_id is not null
`
export const getActiviteTypeEmailsByAdministrationId = async (pool: Pool, administrationId: AdministrationId): Promise<AdministrationActiviteTypeEmail[]> => {
return dbQueryAndValidate(getActiviteTypeEmailsByAdministrationIdDb, { administrationId }, pool, administrationActiviteTypeEmailValidator)
}
export const getActiviteTypeEmailsByAdministrationId = (
pool: Pool,
administrationId: AdministrationId
): Effect.Effect<AdministrationActiviteTypeEmail[], CaminoError<EffectDbQueryAndValidateErrors>> =>
effectDbQueryAndValidate(getActiviteTypeEmailsByAdministrationIdDb, { administrationId }, pool, administrationActiviteTypeEmailValidator)
const getActiviteTypeEmailsByAdministrationIdDb = sql<Redefine<IGetActiviteTypeEmailsByAdministrationIdDbQuery, { administrationId: AdministrationId }, AdministrationActiviteTypeEmail>>`
select
......
import { restCall, restNewPostCall, userGenerate } from '../../../tests/_utils/index'
import { restNewCall, restNewPostCall, userGenerate } from '../../../tests/_utils/index'
import { dbManager } from '../../../tests/db-manager'
import { expect, test, describe, afterAll, beforeAll, vi } from 'vitest'
import type { Pool } from 'pg'
......@@ -50,7 +50,7 @@ describe('getAdministrationUtilisateurs', () => {
await userGenerate(dbPool, { role: 'admin', administrationId: 'dea-guyane-01' })
await userGenerate(dbPool, { role: 'admin', administrationId: 'dea-reunion-01' })
const tested = await restCall(dbPool, '/rest/administrations/:administrationId/utilisateurs', { administrationId: 'dea-guyane-01' }, user)
const tested = await restNewCall(dbPool, '/rest/administrations/:administrationId/utilisateurs', { administrationId: 'dea-guyane-01' }, user)
if (lecture) {
expect(tested.statusCode).toBe(HTTP_STATUS.OK)
......@@ -87,7 +87,7 @@ describe('administrationActiviteTypeEmails', () => {
[{ role: 'defaut' }, false],
[undefined, false],
])('utilisateur %s peur gérer les emails associés à un type d’activité: %s', async (user, canEdit) => {
let tested = await restCall(dbPool, '/rest/administrations/:administrationId/activiteTypeEmails', { administrationId: 'dea-guyane-01' }, user)
let tested = await restNewCall(dbPool, '/rest/administrations/:administrationId/activiteTypeEmails', { administrationId: 'dea-guyane-01' }, user)
if (canEdit) {
expect(tested.statusCode).toBe(HTTP_STATUS.OK)
......@@ -96,12 +96,12 @@ describe('administrationActiviteTypeEmails', () => {
const newActiviteTypeEmail: AdministrationActiviteTypeEmail = { activite_type_id: 'gra', email: 'toto@toto.com' }
await restNewPostCall(dbPool, '/rest/administrations/:administrationId/activiteTypeEmails', { administrationId: 'dea-guyane-01' }, user, newActiviteTypeEmail)
tested = await restCall(dbPool, '/rest/administrations/:administrationId/activiteTypeEmails', { administrationId: 'dea-guyane-01' }, user)
tested = await restNewCall(dbPool, '/rest/administrations/:administrationId/activiteTypeEmails', { administrationId: 'dea-guyane-01' }, user)
expect(tested.statusCode).toBe(HTTP_STATUS.OK)
expect(tested.body).toEqual([newActiviteTypeEmail])
await restNewPostCall(dbPool, '/rest/administrations/:administrationId/activiteTypeEmails/delete', { administrationId: 'dea-guyane-01' }, user, newActiviteTypeEmail)
tested = await restCall(dbPool, '/rest/administrations/:administrationId/activiteTypeEmails', { administrationId: 'dea-guyane-01' }, user)
tested = await restNewCall(dbPool, '/rest/administrations/:administrationId/activiteTypeEmails', { administrationId: 'dea-guyane-01' }, user)
expect(tested.statusCode).toBe(HTTP_STATUS.OK)
expect(tested.body).toEqual([])
} else {
......
import { Request as JWTRequest } from 'express-jwt'
import { HTTP_STATUS } from 'camino-common/src/http'
import { CustomResponse } from './express-type'
import { AdminUserNotNull, User } from 'camino-common/src/roles'
import { Pool } from 'pg'
import { administrationIdValidator } from 'camino-common/src/static/administrations'
import { AdminUserNotNull } from 'camino-common/src/roles'
import { canReadAdministrations } from 'camino-common/src/permissions/administrations'
import { deleteAdministrationActiviteTypeEmail, getActiviteTypeEmailsByAdministrationId, getUtilisateursByAdministrationId, insertAdministrationActiviteTypeEmail } from './administrations.queries'
import { AdministrationActiviteTypeEmail } from 'camino-common/src/administrations'
import { CaminoApiError } from '../../types'
import { EffectDbQueryAndValidateErrors } from '../../pg-database'
import { Effect, Match } from 'effect'
import { RestNewPostCall } from '../../server/rest'
import { RestNewGetCall, RestNewPostCall } from '../../server/rest'
export const getAdministrationUtilisateurs =
(pool: Pool) =>
async (req: JWTRequest<User>, res: CustomResponse<AdminUserNotNull[]>): Promise<void> => {
const user = req.auth
const parsed = administrationIdValidator.safeParse(req.params.administrationId)
if (!parsed.success) {
console.warn(`l'administrationId est obligatoire`)
res.sendStatus(HTTP_STATUS.FORBIDDEN)
} else if (!canReadAdministrations(user)) {
res.sendStatus(HTTP_STATUS.FORBIDDEN)
} else {
try {
res.json(await getUtilisateursByAdministrationId(pool, parsed.data))
} catch (e) {
console.error(e)
res.sendStatus(HTTP_STATUS.INTERNAL_SERVER_ERROR)
}
}
}
export const getAdministrationActiviteTypeEmails =
(pool: Pool) =>
async (req: JWTRequest<User>, res: CustomResponse<AdministrationActiviteTypeEmail[]>): Promise<void> => {
const user = req.auth
const parsed = administrationIdValidator.safeParse(req.params.administrationId)
if (!parsed.success) {
console.warn(`l'administrationId est obligatoire`)
res.sendStatus(HTTP_STATUS.FORBIDDEN)
} else if (!canReadAdministrations(user)) {
res.sendStatus(HTTP_STATUS.FORBIDDEN)
} else {
try {
res.json(await getActiviteTypeEmailsByAdministrationId(pool, parsed.data))
} catch (e) {
console.error(e)
const accessInterdit = 'Accès interdit' as const
type GetAdministrationUtilisateursErreurs = EffectDbQueryAndValidateErrors | typeof accessInterdit
export const getAdministrationUtilisateurs: RestNewGetCall<'/rest/administrations/:administrationId/utilisateurs'> = (
rootPipe
): Effect.Effect<AdminUserNotNull[], CaminoApiError<GetAdministrationUtilisateursErreurs>> =>
rootPipe.pipe(
Effect.filterOrFail(
({ user }) => canReadAdministrations(user),
() => ({ message: accessInterdit })
),
Effect.flatMap(({ pool, params }) => getUtilisateursByAdministrationId(pool, params.administrationId)),
Effect.mapError(caminoError =>
Match.value(caminoError.message).pipe(
Match.when('Accès interdit', () => ({ ...caminoError, status: HTTP_STATUS.FORBIDDEN })),
Match.whenOr("Impossible d'exécuter la requête dans la base de données", 'Les données en base ne correspondent pas à ce qui est attendu', () => ({
...caminoError,
status: HTTP_STATUS.INTERNAL_SERVER_ERROR,
})),
Match.exhaustive
)
)
)
res.sendStatus(HTTP_STATUS.INTERNAL_SERVER_ERROR)
}
}
}
type GetAdministrationActiviteTypeEmailsErreurs = EffectDbQueryAndValidateErrors | typeof accessInterdit
export const getAdministrationActiviteTypeEmails: RestNewGetCall<'/rest/administrations/:administrationId/activiteTypeEmails'> = (
rootPipe
): Effect.Effect<AdministrationActiviteTypeEmail[], CaminoApiError<GetAdministrationActiviteTypeEmailsErreurs>> =>
rootPipe.pipe(
Effect.filterOrFail(
({ user }) => canReadAdministrations(user),
() => ({ message: accessInterdit })
),
Effect.flatMap(({ pool, params }) => getActiviteTypeEmailsByAdministrationId(pool, params.administrationId)),
Effect.mapError(caminoError =>
Match.value(caminoError.message).pipe(
Match.when('Accès interdit', () => ({ ...caminoError, status: HTTP_STATUS.FORBIDDEN })),
Match.whenOr("Impossible d'exécuter la requête dans la base de données", 'Les données en base ne correspondent pas à ce qui est attendu', () => ({
...caminoError,
status: HTTP_STATUS.INTERNAL_SERVER_ERROR,
})),
Match.exhaustive
)
)
)
export const addAdministrationActiviteTypeEmails: RestNewPostCall<'/rest/administrations/:administrationId/activiteTypeEmails'> = (
rootPipe
): Effect.Effect<boolean, CaminoApiError<'Accès interdit' | EffectDbQueryAndValidateErrors>> => {
): Effect.Effect<boolean, CaminoApiError<typeof accessInterdit | EffectDbQueryAndValidateErrors>> => {
return rootPipe.pipe(
Effect.filterOrFail(
({ user }) => canReadAdministrations(user),
() => ({ message: 'Accès interdit' as const })
() => ({ message: accessInterdit })
),
Effect.flatMap(({ pool, params, body }) => insertAdministrationActiviteTypeEmail(pool, params.administrationId, body)),
Effect.mapError(caminoError =>
......@@ -83,11 +78,11 @@ export const addAdministrationActiviteTypeEmails: RestNewPostCall<'/rest/adminis
export const deleteAdministrationActiviteTypeEmails: RestNewPostCall<'/rest/administrations/:administrationId/activiteTypeEmails/delete'> = (
rootPipe
): Effect.Effect<boolean, CaminoApiError<'Accès interdit' | EffectDbQueryAndValidateErrors>> => {
): Effect.Effect<boolean, CaminoApiError<typeof accessInterdit | EffectDbQueryAndValidateErrors>> => {
return rootPipe.pipe(
Effect.filterOrFail(
({ user }) => canReadAdministrations(user),
() => ({ message: 'Accès interdit' as const })
() => ({ message: accessInterdit })
),
Effect.flatMap(({ pool, params, body }) => deleteAdministrationActiviteTypeEmail(pool, params.administrationId, body)),
Effect.mapError(caminoError =>
......
......@@ -205,9 +205,8 @@ where
id = $ entreprise_id !
`
export const getEntreprises = async (pool: Pool): Promise<GetEntreprises[]> => {
return dbQueryAndValidate(getEntreprisesDb, undefined, pool, getEntreprisesValidor)
}
export const getEntreprises = (pool: Pool): Effect.Effect<GetEntreprises[], CaminoError<EffectDbQueryAndValidateErrors>> =>
effectDbQueryAndValidate(getEntreprisesDb, undefined, pool, getEntreprisesValidor)
const getEntreprisesDb = sql<Redefine<IGetEntreprisesDbQuery, void, GetEntreprises>>`
select
......
/* eslint-disable sql/no-unsafe-query */
import { entrepriseDocumentIdValidator, EntrepriseDocumentInput, newEntrepriseId, Siren, sirenValidator } from 'camino-common/src/entreprise'
import { dbManager } from '../../../tests/db-manager'
import { restCall, restNewCall, restNewDeleteCall, restNewPostCall, restNewPutCall, restPostCall, userGenerate } from '../../../tests/_utils/index'
import { restCall, restNewCall, restNewDeleteCall, restNewPostCall, restNewPutCall, userGenerate } from '../../../tests/_utils/index'
import { entrepriseUpsert } from '../../database/queries/entreprises'
import { afterAll, beforeAll, describe, test, expect, vi, beforeEach } from 'vitest'
import { userSuper } from '../../database/user-super'
......@@ -109,7 +109,7 @@ describe('fiscalite', () => {
describe('entrepriseCreer', () => {
test('ne peut pas créer une entreprise (utilisateur anonyme)', async () => {
const tested = await restPostCall(dbPool, '/rest/entreprises', {}, undefined, { siren: entreprise.siren })
const tested = await restNewPostCall(dbPool, '/rest/entreprises', {}, undefined, { siren: entreprise.siren })
expect(tested.statusCode).toBe(403)
})
......@@ -118,8 +118,8 @@ describe('entrepriseCreer', () => {
entrepriseFetchMock.mockResolvedValue([entreprise])
entreprisesEtablissementsFetchMock.mockResolvedValue([entrepriseAndEtablissements])
const tested = await restPostCall(dbPool, '/rest/entreprises', {}, userSuper, { siren: entreprise.siren })
expect(tested.statusCode).toBe(204)
const tested = await restNewPostCall(dbPool, '/rest/entreprises', {}, userSuper, { siren: entreprise.siren })
expect(tested.statusCode).toBe(HTTP_STATUS.OK)
})
test("ne peut pas créer une entreprise déjà existante (un utilisateur 'super')", async () => {
......@@ -127,9 +127,9 @@ describe('entrepriseCreer', () => {
const siren = sirenValidator.parse('123456789')
entrepriseFetchMock.mockResolvedValue([{ ...entreprise, siren }])
entreprisesEtablissementsFetchMock.mockResolvedValue([{ ...entrepriseAndEtablissements, siren }])
let tested = await restPostCall(dbPool, '/rest/entreprises', {}, userSuper, { siren })
expect(tested.statusCode).toBe(204)
tested = await restPostCall(dbPool, '/rest/entreprises', {}, userSuper, { siren })
let tested = await restNewPostCall(dbPool, '/rest/entreprises', {}, userSuper, { siren })
expect(tested.statusCode).toBe(HTTP_STATUS.OK)
tested = await restNewPostCall(dbPool, '/rest/entreprises', {}, userSuper, { siren })
expect(tested.statusCode).toBe(400)
})
......@@ -137,7 +137,7 @@ describe('entrepriseCreer', () => {
tokenInitializeMock.mockResolvedValue('token')
entrepriseFetchMock.mockResolvedValue([])
const tested = await restPostCall(dbPool, '/rest/entreprises', {}, userSuper, { siren: 'invalide' as Siren })
const tested = await restNewPostCall(dbPool, '/rest/entreprises', {}, userSuper, { siren: 'invalide' as Siren })
expect(tested.statusCode).toBe(400)
})
})
......@@ -417,7 +417,7 @@ describe('getEntreprises', () => {
id: newEntrepriseId('plop'),
nom: 'Mon Entreprise',
})
const tested = await restCall(dbPool, '/rest/entreprises', {}, { role: 'defaut' })
const tested = await restNewCall(dbPool, '/rest/entreprises', {}, { role: 'defaut' })
expect(tested.statusCode).toBe(HTTP_STATUS.OK)
expect(tested.body).toMatchInlineSnapshot(`
......
......@@ -9,18 +9,7 @@ import { entrepriseGet, entrepriseUpsert } from '../../database/queries/entrepri
import { CustomResponse } from './express-type'
import { isNotNullNorUndefined, isNullOrUndefined } from 'camino-common/src/typescript-tools'
import { anneePrecedente, isAnnee } from 'camino-common/src/date'
import {
entrepriseIdValidator,
EntrepriseType,
sirenValidator,
EntrepriseDocument,
entrepriseDocumentIdValidator,
EntrepriseDocumentId,
newEntrepriseId,
Entreprise,
entrepriseValidator,
EntrepriseId,
} from 'camino-common/src/entreprise'
import { entrepriseIdValidator, EntrepriseType, EntrepriseDocument, entrepriseDocumentIdValidator, EntrepriseDocumentId, newEntrepriseId, EntrepriseId } from 'camino-common/src/entreprise'
import { isSuper, User } from 'camino-common/src/roles'
import { canCreateEntreprise, canEditEntreprise, canSeeEntrepriseDocuments } from 'camino-common/src/permissions/entreprises'
import { emailCheck } from '../../tools/email-check'
......@@ -34,19 +23,19 @@ import {
getLargeobjectIdByEntrepriseDocumentId,
insertEntrepriseDocument,
GetEntrepriseErrors,
GetEntreprises,
} from './entreprises.queries'
import { newEnterpriseDocumentId } from '../../database/models/_format/id-create'
import { NewDownload } from './fichiers'
import Decimal from 'decimal.js'
import { createLargeObject, CreateLargeObjectError } from '../../database/largeobjects'
import { z } from 'zod'
import { getEntrepriseEtablissements } from './entreprises-etablissements.queries'
import { RawLineMatrice, getRawLines } from '../../business/matrices'
import { RestNewDeleteCall, RestNewGetCall, RestNewPostCall, RestNewPutCall } from '../../server/rest'
import { Effect, Match, Option } from 'effect'
import { EffectDbQueryAndValidateErrors } from '../../pg-database'
import { CaminoApiError } from '../../types'
import { CaminoApiError, IEntreprise } from '../../types'
type Reduced = { guyane: true; fiscalite: FiscaliteGuyane } | { guyane: false; fiscalite: FiscaliteFrance }
// VisibleForTesting
......@@ -152,43 +141,73 @@ export const modifierEntreprise: RestNewPutCall<'/rest/entreprises/:entrepriseId
)
)
export const creerEntreprise =
(_pool: Pool) =>
async (req: JWTRequest<User>, res: CustomResponse<void>): Promise<void> => {
const user = req.auth
const siren = sirenValidator.safeParse(req.body.siren)
if (!user) {
res.sendStatus(HTTP_STATUS.FORBIDDEN)
} else if (!siren.success) {
console.warn(`siren '${req.body.siren}' invalide`)
res.sendStatus(HTTP_STATUS.BAD_REQUEST)
} else {
if (!canCreateEntreprise(user)) {
res.sendStatus(HTTP_STATUS.FORBIDDEN)
} else {
try {
const entrepriseOld = await entrepriseGet(newEntrepriseId(`fr-${siren.data}`), { fields: { id: {} } }, user)
const creationEntrepriseImpossible = "Interdiction de créer l'entreprise" as const
const erreurLorsDeLaVerificationDExistenceDeLEntreprise = "Erreur lors de la vérification de l'existence de l'entreprise" as const
const entrepriseDejaExistante = 'Entreprise déjà existante' as const
const erreurRecuperationInsee = "Impossible de récupérer les informations de l'entreprise auprès de l'INSEE" as const
const erreurInsertionEntreprise = "Impossible d'enregistrer l'entreprise" as const
const entrepriseNonTrouvee = "Entreprise non trouvée auprès de l'INSEE" as const
type CreerEntrepriseErrors =
| typeof creationEntrepriseImpossible
| typeof erreurLorsDeLaVerificationDExistenceDeLEntreprise
| typeof entrepriseDejaExistante
| typeof erreurRecuperationInsee
| typeof erreurInsertionEntreprise
| typeof entrepriseNonTrouvee
if (entrepriseOld) {
console.warn(`l'entreprise ${entrepriseOld.nom} existe déjà dans Camino`)
res.sendStatus(HTTP_STATUS.BAD_REQUEST)
} else {
const entrepriseInsee = await apiInseeEntrepriseAndEtablissementsGet(siren.data)
export const creerEntreprise: RestNewPostCall<'/rest/entreprises'> = (rootPipe): Effect.Effect<{ id: EntrepriseId }, CaminoApiError<CreerEntrepriseErrors>> =>
rootPipe.pipe(
Effect.filterOrFail(
({ user }) => canCreateEntreprise(user),
() => ({ message: creationEntrepriseImpossible })
),
Effect.bind('entrepriseAlreadyExists', ({ user, body }) =>
Effect.tryPromise({
try: () => entrepriseGet(newEntrepriseId(`fr-${body.siren}`), { fields: { id: {} } }, user),
catch: e => ({ message: erreurLorsDeLaVerificationDExistenceDeLEntreprise, extra: e }),
})
),
Effect.filterOrFail(
({ entrepriseAlreadyExists }) => isNullOrUndefined(entrepriseAlreadyExists),
() => ({ message: entrepriseDejaExistante })
),
Effect.bind('entrepriseInsee', ({ body }) =>
Effect.tryPromise({
try: () => apiInseeEntrepriseAndEtablissementsGet(body.siren),
catch: e => ({ message: erreurRecuperationInsee, extra: e }),
}).pipe(
Effect.filterOrFail(
(entrepriseInsee): entrepriseInsee is NonNullable<IEntreprise> => isNotNullNorUndefined(entrepriseInsee),
() => ({ message: entrepriseNonTrouvee })
)
)
),
Effect.bind('insertedEntreprise', ({ entrepriseInsee }) =>
Effect.tryPromise({
try: () => entrepriseUpsert(entrepriseInsee),
catch: e => ({ message: erreurInsertionEntreprise, extra: e }),
})
),
Effect.map(({ insertedEntreprise }) => ({ id: insertedEntreprise.id })),
Effect.mapError(caminoError =>
Match.value(caminoError.message).pipe(
Match.when("Interdiction de créer l'entreprise", () => ({ ...caminoError, status: HTTP_STATUS.FORBIDDEN })),
Match.whenOr('Entreprise déjà existante', "Entreprise non trouvée auprès de l'INSEE", () => ({
...caminoError,
status: HTTP_STATUS.BAD_REQUEST,
})),
Match.whenOr(
"Erreur lors de la vérification de l'existence de l'entreprise",
"Impossible de récupérer les informations de l'entreprise auprès de l'INSEE",
"Impossible d'enregistrer l'entreprise",
() => ({ ...caminoError, status: HTTP_STATUS.INTERNAL_SERVER_ERROR })
),
Match.exhaustive
)
)
)
if (!entrepriseInsee) {
res.sendStatus(HTTP_STATUS.NOT_FOUND)
} else {
await entrepriseUpsert(entrepriseInsee)
res.sendStatus(HTTP_STATUS.NO_CONTENT)
}
}
} catch (e) {
console.error(e)
res.sendStatus(HTTP_STATUS.INTERNAL_SERVER_ERROR)
}
}
}
}
type GetEntrepriseRestErrors = GetEntrepriseErrors
export const getEntrepriseRest: RestNewGetCall<'/rest/entreprises/:entrepriseId'> = (rootPipe): Effect.Effect<EntrepriseType, CaminoApiError<GetEntrepriseRestErrors>> =>
rootPipe.pipe(
......@@ -402,10 +421,18 @@ export const entrepriseDocumentDownload: NewDownload = async (params, user, pool
return { loid: entrepriseDocumentLargeObjectId, fileName: entrepriseDocumentId }
}
type GetEntreprisesErrors = EffectDbQueryAndValidateErrors
export const getAllEntreprises =
(pool: Pool) =>
async (_req: JWTRequest<User>, res: CustomResponse<Entreprise[]>): Promise<void> => {
const allEntreprises = await getEntreprises(pool)
res.json(z.array(entrepriseValidator).parse(allEntreprises))
}
export const getAllEntreprises: RestNewGetCall<'/rest/entreprises'> = (rootPipe): Effect.Effect<GetEntreprises[], CaminoApiError<GetEntreprisesErrors>> =>
rootPipe.pipe(
Effect.flatMap(({ pool }) => getEntreprises(pool)),
Effect.mapError(caminoError =>
Match.value(caminoError.message).pipe(
Match.whenOr("Impossible d'exécuter la requête dans la base de données", 'Les données en base ne correspondent pas à ce qui est attendu', () => ({
...caminoError,
status: HTTP_STATUS.INTERNAL_SERVER_ERROR,
})),
Match.exhaustive
)
)
)
......@@ -368,9 +368,9 @@ describe('etapeModifier', () => {
})
test("ne peut pas supprimer un document obligatoire d'une étape qui n'est pas en brouillon (utilisateur super)", async () => {
const titreId = newTitreId('titre-id-1')
const demarcheId = newDemarcheId('demarche-id-1')
const titreEtapeId = newEtapeId('etape-id-adc')
const titreId = newTitreId('titre-id-1-document-obligatoire')
const demarcheId = newDemarcheId('demarche-id-1-document-obligatoire')
const titreEtapeId = newEtapeId('etape-id-adc-document-obligatoire')
await insertTitreGraph({
id: titreId,
nom: 'mon titre simple',
......@@ -390,6 +390,7 @@ describe('etapeModifier', () => {
isBrouillon: ETAPE_IS_NOT_BROUILLON,
statutId: ETAPES_STATUTS.FAIT,
titreDemarcheId: demarcheId,
ordre: 0,
},
{
typeId: ETAPES_TYPES.enregistrementDeLaDemande,
......@@ -398,6 +399,7 @@ describe('etapeModifier', () => {
isBrouillon: ETAPE_IS_NOT_BROUILLON,
statutId: ETAPES_STATUTS.FAIT,
titreDemarcheId: demarcheId,
ordre: 1,
},
{
typeId: ETAPES_TYPES.recevabiliteDeLaDemande,
......@@ -406,6 +408,7 @@ describe('etapeModifier', () => {
isBrouillon: ETAPE_IS_NOT_BROUILLON,
statutId: ETAPES_STATUTS.FAVORABLE,
titreDemarcheId: demarcheId,
ordre: 2,
},
{
typeId: ETAPES_TYPES.avisDesCollectivites,
......@@ -414,6 +417,7 @@ describe('etapeModifier', () => {
isBrouillon: ETAPE_IS_NOT_BROUILLON,
statutId: ETAPES_STATUTS.FAIT,
titreDemarcheId: demarcheId,
ordre: 3,
},
],
},
......
......@@ -30,6 +30,7 @@ import { CaminoError } from 'camino-common/src/zod-tools'
import { Effect, pipe } from 'effect'
import { DATE_DEBUT_PROCEDURE_SPECIFIQUE } from 'camino-common/src/machines'
import { TitreId } from 'camino-common/src/validators/titres'
import { communeIdValidator } from 'camino-common/src/static/communes'
const getEtapeByIdValidator = z.object({
etape_id: etapeIdValidator,
......@@ -38,6 +39,7 @@ const getEtapeByIdValidator = z.object({
geojson4326_perimetre: multiPolygonValidator.nullable(),
sdom_zones: z.array(sdomZoneIdValidator).nullable(),
demarche_id_en_concurrence: demarcheIdValidator.nullable(),
communes: z.array(z.object({ id: communeIdValidator, surface: z.number().optional() })),
})
type GetEtapeByIdValidator = z.infer<typeof getEtapeByIdValidator>
......@@ -55,7 +57,8 @@ select
titre_demarche_id as demarche_id,
ST_AsGeoJSON (geojson4326_perimetre, 40)::json as geojson4326_perimetre,
sdom_zones,
demarche_id_en_concurrence
demarche_id_en_concurrence,
communes
from
titres_etapes
where (id = $ etapeId !
......
......@@ -8,6 +8,7 @@ export interface IGetEtapeByIdDbParams {
/** 'GetEtapeByIdDb' return type */
export interface IGetEtapeByIdDbResult {
communes: Json;
demarche_id: string;
demarche_id_en_concurrence: string | null;
etape_id: string;
......
......@@ -12,6 +12,7 @@ import { getEntreprises } from '../entreprises.queries'
import { EntrepriseId } from 'camino-common/src/entreprise'
import { TitreSlug } from 'camino-common/src/validators/titres'
import { CaminoAnnee, toCaminoAnnee } from 'camino-common/src/date'
import { callAndExit } from '../../../tools/fp-tools'
const titreActiviteContenuFormat = (contenu: IContenu, sections: Section[]) =>
sections.reduce((resSections: Index<IContenuValeur>, section) => {
......@@ -56,7 +57,7 @@ export const titresActivitesFormatTable = async (pool: Pool, activites: ITitreAc
pool,
activites.flatMap(({ titre }) => titre?.communes?.map(({ id }) => id) ?? [])
)
const entreprises = await getEntreprises(pool)
const entreprises = await callAndExit(getEntreprises(pool))
const entreprisesIndex = entreprises.reduce<Record<EntrepriseId, string>>((acc, entreprise) => {
acc[entreprise.id] = entreprise.nom
......
......@@ -18,6 +18,7 @@ import { EntrepriseId } from 'camino-common/src/entreprise'
import { GetEntreprises, getEntreprises } from '../entreprises.queries'
import { ETAPE_IS_NOT_BROUILLON } from 'camino-common/src/etape'
import { TitreSlug } from 'camino-common/src/validators/titres'
import { callAndExit } from '../../../tools/fp-tools'
const etapesDatesStatutsBuild = (titreDemarche: ITitreDemarche) => {
if (isNullOrUndefinedOrEmpty(titreDemarche.etapes)) return null
......@@ -73,7 +74,7 @@ export const titresDemarchesFormatTable = async (pool: Pool, titresDemarches: IT
pool,
titresDemarches.flatMap(titreDemarche => titreDemarche.etapes?.flatMap(etape => etape.communes?.map(({ id }) => id) ?? []) ?? [])
)
const entreprises = await getEntreprises(pool)
const entreprises = await callAndExit(getEntreprises(pool))
const entreprisesIndex = entreprises.reduce<Record<EntrepriseId, GetEntreprises>>((acc, entreprise) => {
acc[entreprise.id] = entreprise
......
......@@ -24,6 +24,7 @@ import { DemarchesTypes } from 'camino-common/src/static/demarchesTypes'
import { GetEntreprises, getEntreprises } from '../entreprises.queries'
import { EntrepriseId } from 'camino-common/src/entreprise'
import { slugify } from 'camino-common/src/strings'
import { callAndExit } from '../../../tools/fp-tools'
const getFacadesMaritimeCell = (secteursMaritime: SecteursMaritimes[], separator: string): string =>
getFacadesComputed(secteursMaritime)
......@@ -55,7 +56,7 @@ export const titresTableFormat = async (pool: Pool, titres: ITitre[]): Promise<R
pool,
titres.flatMap(titre => titre.communes?.map(({ id }) => id) ?? [])
)
const entreprises = await getEntreprises(pool)
const entreprises = await callAndExit(getEntreprises(pool))
const entreprisesIndex = entreprises.reduce<RecordPartial<EntrepriseId, GetEntreprises>>((acc, entreprise) => {
acc[entreprise.id] = entreprise
......@@ -176,7 +177,7 @@ export const titresGeojsonFormat = async (pool: Pool, titres: ITitre[]): Promise
pool,
titres.flatMap(titre => titre.communes?.map(({ id }) => id) ?? [])
)
const entreprises = await getEntreprises(pool)
const entreprises = await callAndExit(getEntreprises(pool))
const entreprisesIndex = entreprises.reduce<Record<EntrepriseId, GetEntreprises>>((acc, entreprise) => {
acc[entreprise.id] = entreprise
......
......@@ -25,6 +25,7 @@ import { FieldsTitre } from '../../database/queries/_options'
import { titresValidator, demarchesValidator, activitesValidator, entreprisesValidator } from '../../business/utils/filters'
import { GetEntreprises, getEntreprises } from './entreprises.queries'
import { EntrepriseId } from 'camino-common/src/entreprise'
import { callAndExit } from '../../tools/fp-tools'
const formatCheck = (formats: string[], format: string) => {
if (!formats.includes(format)) {
......@@ -65,7 +66,7 @@ export const titre =
const titreFormatted = titreFormat(titre)
const communesIndex = await getCommunesIndex(pool, titreFormatted.communes?.map(({ id }) => id) ?? [])
const entreprises = await getEntreprises(pool)
const entreprises = await callAndExit(getEntreprises(pool))
const entreprisesIndex = entreprises.reduce<Record<EntrepriseId, GetEntreprises>>((acc, entreprise) => {
acc[entreprise.id] = entreprise
......
import { userSuper } from '../../database/user-super'
import { dbManager } from '../../../tests/db-manager'
import { restNewPostCall } from '../../../tests/_utils/index'
import { restNewCall, restNewPostCall } from '../../../tests/_utils/index'
import { test, expect, vi, beforeAll, afterAll, describe } from 'vitest'
import type { Pool } from 'pg'
import { HTTP_STATUS } from 'camino-common/src/http'
......@@ -15,11 +15,15 @@ import {
GeojsonImportPointsBody,
} from 'camino-common/src/perimetre'
import { GEO_SYSTEME_IDS } from 'camino-common/src/static/geoSystemes'
import { idGenerate } from '../../database/models/_format/id-create'
import { idGenerate, newDemarcheId, newEtapeId, newTitreId } from '../../database/models/_format/id-create'
import { copyFileSync, mkdirSync, writeFileSync } from 'node:fs'
import { tempDocumentNameValidator } from 'camino-common/src/document'
import { titreSlugValidator } from 'camino-common/src/validators/titres'
import { join } from 'node:path'
import { insertTitreGraph, ITitreInsert } from '../../../tests/integration-test-helper'
import { communeIdValidator } from 'camino-common/src/static/communes'
import { toCaminoDate } from 'camino-common/src/date'
import { ETAPE_IS_NOT_BROUILLON } from 'camino-common/src/etape'
console.info = vi.fn()
console.error = vi.fn()
......@@ -1706,3 +1710,152 @@ Piézomètre A;Point A;1051195.108314365847036;6867800.046355471946299;12.12;rej
expect(tested.body).toMatchSnapshot()
})
})
describe('getPerimetreInfosByEtape', () => {
test('test connecté et non connecté', async () => {
const communeId = communeIdValidator.parse('31200')
const etapeId = newEtapeId('titre-id-demarche-id-dpu')
const titre: ITitreInsert = {
id: newTitreId('titre-id'),
nom: 'mon titre',
typeId: 'arm',
titreStatutId: 'ind',
publicLecture: true,
propsTitreEtapesIds: { titulaires: 'titre-id-demarche-id-dpu', points: 'titre-id-demarche-id-dpu' },
demarches: [
{
id: newDemarcheId('titre-id-demarche-id'),
titreId: newTitreId('titre-id'),
typeId: 'oct',
statutId: 'acc',
publicLecture: true,
etapes: [
{
id: etapeId,
typeId: 'dpu',
ordre: 0,
titreDemarcheId: newDemarcheId('titre-id-demarche-id'),
statutId: 'acc',
date: toCaminoDate('2020-02-02'),
administrationsLocales: ['dea-guyane-01'],
titulaireIds: [],
communes: [{ id: communeId }],
isBrouillon: ETAPE_IS_NOT_BROUILLON,
},
],
},
],
}
await insertTitreGraph(titre)
let tested = await restNewCall(dbPool, '/rest/etapes/:etapeId/geojson', { etapeId: etapeId }, undefined)
expect(tested.body).toMatchInlineSnapshot(`
{
"message": "Il faut être connecté pour utiliser cette route",
"status": 403,
}
`)
tested = await restNewCall(dbPool, '/rest/etapes/:etapeId/geojson', { etapeId: etapeId }, userSuper)
expect(tested.body).toMatchInlineSnapshot(`
{
"communes": [
"31200",
],
"sdomZoneIds": [],
"superposition_alertes": [],
}
`)
})
})
describe('getPerimetreInfosByDemarche', () => {
test('test connecté et non connecté', async () => {
const communeId = communeIdValidator.parse('31200')
const demarcheId = newDemarcheId('titre-id-demarche-id-2')
const etapeId = newEtapeId('titre-id-demarche-id-dpu-2')
const titre: ITitreInsert = {
id: newTitreId('titre-id-2'),
nom: 'mon titre',
typeId: 'arm',
titreStatutId: 'ind',
publicLecture: true,
propsTitreEtapesIds: { titulaires: 'titre-id-demarche-id-dpu', points: 'titre-id-demarche-id-dpu' },
demarches: [
{
id: demarcheId,
titreId: newTitreId('titre-id'),
typeId: 'oct',
statutId: 'acc',
publicLecture: true,
etapes: [
{
id: etapeId,
typeId: 'dpu',
ordre: 0,
titreDemarcheId: demarcheId,
statutId: 'acc',
date: toCaminoDate('2020-02-02'),
administrationsLocales: ['dea-guyane-01'],
titulaireIds: [],
communes: [{ id: communeId }],
isBrouillon: ETAPE_IS_NOT_BROUILLON,
},
],
},
],
}
await insertTitreGraph(titre)
let tested = await restNewCall(dbPool, '/rest/demarches/:demarcheId/geojson', { demarcheId }, undefined)
expect(tested.body).toMatchInlineSnapshot(`
{
"message": "Il faut être connecté pour utiliser cette route",
"status": 403,
}
`)
tested = await restNewCall(dbPool, '/rest/demarches/:demarcheId/geojson', { demarcheId }, userSuper)
expect(tested.body).toMatchInlineSnapshot(`
{
"communes": [
"31200",
],
"sdomZoneIds": [],
"superposition_alertes": [],
}
`)
})
test('démarche sans étape', async () => {
const demarcheId = newDemarcheId('titre-id-demarche-id-3')
const titre: ITitreInsert = {
id: newTitreId('titre-id-3'),
nom: 'mon titre',
typeId: 'arm',
titreStatutId: 'ind',
publicLecture: true,
propsTitreEtapesIds: { titulaires: 'titre-id-demarche-id-dpu', points: 'titre-id-demarche-id-dpu' },
demarches: [
{
id: demarcheId,
titreId: newTitreId('titre-id'),
typeId: 'oct',
statutId: 'acc',
publicLecture: true,
etapes: [],
},
],
}
await insertTitreGraph(titre)
const tested = await restNewCall(dbPool, '/rest/demarches/:demarcheId/geojson', { demarcheId }, userSuper)
expect(tested.body).toMatchInlineSnapshot(`
{
"communes": [],
"sdomZoneIds": [],
"superposition_alertes": [],
}
`)
})
})
import { DemarcheId, demarcheIdOrSlugValidator } from 'camino-common/src/demarche'
import { CaminoRequest, CustomResponse } from './express-type'
import { DemarcheId } from 'camino-common/src/demarche'
import { Pool } from 'pg'
import { pipe, Effect, Match } from 'effect'
import { GeoSystemes } from 'camino-common/src/static/geoSystemes'
......@@ -17,9 +16,8 @@ import {
import { TitreTypeId } from 'camino-common/src/static/titresTypes'
import { getMostRecentEtapeFondamentaleValide } from './titre-heritage'
import { isAdministrationAdmin, isAdministrationEditeur, isDefault, isSuper, User } from 'camino-common/src/roles'
import { getDemarcheByIdOrSlug, getEtapesByDemarcheId } from './demarches.queries'
import { getAdministrationsLocalesByTitreId, getTitreByIdOrSlug, getTitulairesAmodiatairesByTitreId } from './titres.queries'
import { etapeIdOrSlugValidator } from 'camino-common/src/etape'
import { getDemarcheByIdOrSlug, GetDemarcheByIdOrSlugErrors, getEtapesByDemarcheId } from './demarches.queries'
import { getAdministrationsLocalesByTitreId, getTitreByIdOrSlug, GetTitreByIdOrSlugErrors, getTitulairesAmodiatairesByTitreId } from './titres.queries'
import { getEtapeById } from './etapes.queries'
import {
FeatureCollectionPoints,
......@@ -50,90 +48,149 @@ import xlsx from 'xlsx'
import { ZodTypeAny, z } from 'zod'
import { CommuneId } from 'camino-common/src/static/communes'
import { CaminoApiError } from '../../types'
import { EffectDbQueryAndValidateErrors } from '../../pg-database'
import { ZodUnparseable, callAndExit, zodParseEffect, zodParseEffectCallback } from '../../tools/fp-tools'
import { DBNotFound, EffectDbQueryAndValidateErrors } from '../../pg-database'
import { ZodUnparseable, zodParseEffect, zodParseEffectCallback } from '../../tools/fp-tools'
import { CaminoError } from 'camino-common/src/zod-tools'
import { RestNewPostCall } from '../../server/rest'
export const getPerimetreInfos =
(pool: Pool) =>
async (req: CaminoRequest, res: CustomResponse<PerimetreInformations>): Promise<void> => {
const user = req.auth
if (!user) {
res.sendStatus(HTTP_STATUS.FORBIDDEN)
} else {
const etapeIdOrSlugParsed = etapeIdOrSlugValidator.safeParse(req.params.etapeId)
const demarcheIdOrSlugParsed = demarcheIdOrSlugValidator.safeParse(req.params.demarcheId)
if (!etapeIdOrSlugParsed.success && !demarcheIdOrSlugParsed.success) {
res.sendStatus(HTTP_STATUS.BAD_REQUEST)
} else {
try {
let etape: null | { demarche_id: DemarcheId; geojson4326_perimetre: MultiPolygon | null; sdom_zones: SDOMZoneId[]; etape_type_id: EtapeTypeId; communes: CommuneId[] } = null
if (etapeIdOrSlugParsed.success) {
const myEtape = await callAndExit(getEtapeById(pool, etapeIdOrSlugParsed.data))
etape = { demarche_id: myEtape.demarche_id, geojson4326_perimetre: myEtape.geojson4326_perimetre, sdom_zones: myEtape.sdom_zones ?? [], etape_type_id: myEtape.etape_type_id, communes: [] }
} else if (demarcheIdOrSlugParsed.success) {
const demarche = await callAndExit(getDemarcheByIdOrSlug(pool, demarcheIdOrSlugParsed.data))
const etapes = await callAndExit(getEtapesByDemarcheId(pool, demarche.demarche_id))
const mostRecentEtapeFondamentale = getMostRecentEtapeFondamentaleValide([{ ordre: 1, etapes }])
if (isNotNullNorUndefined(mostRecentEtapeFondamentale)) {
etape = {
demarche_id: demarche.demarche_id,
geojson4326_perimetre: mostRecentEtapeFondamentale.geojson4326_perimetre,
sdom_zones: mostRecentEtapeFondamentale.sdom_zones ?? [],
etape_type_id: mostRecentEtapeFondamentale.etape_type_id,
communes: mostRecentEtapeFondamentale.communes.map(({ id }) => id),
}
}
} else {
res.sendStatus(HTTP_STATUS.INTERNAL_SERVER_ERROR)
console.error("cas impossible où ni l'étape id ni la démarche Id n'est chargée")
import { RestNewGetCall, RestNewPostCall } from '../../server/rest'
const userNotConnected = 'Il faut être connecté pour utiliser cette route' as const
type GetPerimetreInfosByEtapeErrors = EffectDbQueryAndValidateErrors | DBNotFound | typeof userNotConnected | GetPerimetreInfosInternalErrors
export const getPerimetreInfosByEtape: RestNewGetCall<'/rest/etapes/:etapeId/geojson'> = (rootPipe): Effect.Effect<PerimetreInformations, CaminoApiError<GetPerimetreInfosByEtapeErrors>> =>
rootPipe.pipe(
Effect.filterOrFail(
({ user }) => isNotNullNorUndefined(user),
() => ({ message: userNotConnected })
),
Effect.bind('etape', ({ pool, params }) =>
getEtapeById(pool, params.etapeId).pipe(
Effect.map(myEtape => {
const etapeReworked: GetPerimetreInfosInternalEtape = {
demarche_id: myEtape.demarche_id,
geojson4326_perimetre: myEtape.geojson4326_perimetre,
sdom_zones: myEtape.sdom_zones ?? [],
etape_type_id: myEtape.etape_type_id,
communes: myEtape.communes.map(({ id }) => id),
}
return etapeReworked
})
)
),
Effect.flatMap(({ pool, user, etape }) => getPerimetreInfosInternal(pool, user, etape)),
Effect.mapError(caminoError =>
Match.value(caminoError.message).pipe(
Match.whenOr('Il faut être connecté pour utiliser cette route', "Droits insuffisant pour lire l'étape", () => ({ ...caminoError, status: HTTP_STATUS.FORBIDDEN })),
Match.whenOr(
'Élément non trouvé dans la base de données',
'Les données en base ne correspondent pas à ce qui est attendu',
"Impossible d'exécuter la requête dans la base de données",
"La démarche n'existe pas",
'titre non trouvé en base',
() => ({
...caminoError,
status: HTTP_STATUS.BAD_REQUEST,
})
),
Match.when("Une erreur est survenue lors de la gestion des droits de l'étape", () => ({
...caminoError,
status: HTTP_STATUS.INTERNAL_SERVER_ERROR,
})),
Match.exhaustive
)
)
)
if (isNullOrUndefined(etape)) {
res.json({
superposition_alertes: [],
sdomZoneIds: [],
communes: [],
})
} else {
const demarche = await callAndExit(getDemarcheByIdOrSlug(pool, etape.demarche_id))
const titre = await callAndExit(getTitreByIdOrSlug(pool, demarche.titre_id))
const administrationsLocales = memoize(() => getAdministrationsLocalesByTitreId(pool, demarche.titre_id))
if (
await canReadEtape(
user,
memoize(() => Promise.resolve(titre.titre_type_id)),
administrationsLocales,
memoize(() => getTitulairesAmodiatairesByTitreId(pool, demarche.titre_id)),
etape.etape_type_id,
{ ...demarche, titre_public_lecture: titre.public_lecture }
)
) {
const superpositionAlertes = await callAndExit(getAlertesSuperposition(etape.geojson4326_perimetre, titre.titre_type_id, titre.titre_slug, user, pool))
type GetPerimetreInfosByDemarcheErrors = EffectDbQueryAndValidateErrors | typeof userNotConnected | GetDemarcheByIdOrSlugErrors | GetPerimetreInfosInternalErrors
export const getPerimetreInfosByDemarche: RestNewGetCall<'/rest/demarches/:demarcheId/geojson'> = (rootPipe): Effect.Effect<PerimetreInformations, CaminoApiError<GetPerimetreInfosByDemarcheErrors>> =>
rootPipe.pipe(
Effect.filterOrFail(
({ user }) => isNotNullNorUndefined(user),
() => ({ message: userNotConnected })
),
Effect.bind('demarche', ({ pool, params }) => getDemarcheByIdOrSlug(pool, params.demarcheId)),
Effect.bind('etapes', ({ pool, demarche }) => getEtapesByDemarcheId(pool, demarche.demarche_id)),
Effect.let('mostRecentEtapeFondamentale', ({ etapes }) => getMostRecentEtapeFondamentaleValide([{ ordre: 1, etapes }])),
Effect.let('etape', ({ mostRecentEtapeFondamentale, demarche }) => {
if (isNullOrUndefined(mostRecentEtapeFondamentale)) {
return null
}
const etapeReworked: GetPerimetreInfosInternalEtape = {
demarche_id: demarche.demarche_id,
geojson4326_perimetre: mostRecentEtapeFondamentale.geojson4326_perimetre,
sdom_zones: mostRecentEtapeFondamentale.sdom_zones ?? [],
etape_type_id: mostRecentEtapeFondamentale.etape_type_id,
communes: mostRecentEtapeFondamentale.communes.map(({ id }) => id),
}
res.json({
superposition_alertes: superpositionAlertes,
sdomZoneIds: etape.sdom_zones,
communes: etape.communes,
})
} else {
res.sendStatus(HTTP_STATUS.FORBIDDEN)
}
return etapeReworked
}),
Effect.flatMap(({ pool, user, etape }) => {
return Match.value(etape).pipe(
Match.when(null, () => {
const emptyResult: PerimetreInformations = {
communes: [],
sdomZoneIds: [],
superposition_alertes: [],
}
} catch (e) {
res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).send(e)
console.error(e)
}
return Effect.succeed(emptyResult)
}),
Match.orElse(myEtape => getPerimetreInfosInternal(pool, user, myEtape))
)
}),
Effect.mapError(caminoError =>
Match.value(caminoError.message).pipe(
Match.whenOr('Il faut être connecté pour utiliser cette route', "Droits insuffisant pour lire l'étape", () => ({ ...caminoError, status: HTTP_STATUS.FORBIDDEN })),
Match.whenOr('Les données en base ne correspondent pas à ce qui est attendu', "Impossible d'exécuter la requête dans la base de données", "La démarche n'existe pas", () => ({
...caminoError,
status: HTTP_STATUS.BAD_REQUEST,
})),
Match.whenOr("Une erreur est survenue lors de la gestion des droits de l'étape", 'titre non trouvé en base', () => ({ ...caminoError, status: HTTP_STATUS.INTERNAL_SERVER_ERROR })),
Match.exhaustive
)
)
)
type GetPerimetreInfosInternalEtape = { demarche_id: DemarcheId; geojson4326_perimetre: MultiPolygon | null; sdom_zones: SDOMZoneId[]; etape_type_id: EtapeTypeId; communes: CommuneId[] }
const erreurLorsDeLaGestionDesDroitsDeLEtape = "Une erreur est survenue lors de la gestion des droits de l'étape" as const
const cantReadEtapeError = "Droits insuffisant pour lire l'étape" as const
type GetPerimetreInfosInternalErrors =
| EffectDbQueryAndValidateErrors
| GetDemarcheByIdOrSlugErrors
| GetTitreByIdOrSlugErrors
| typeof cantReadEtapeError
| typeof erreurLorsDeLaGestionDesDroitsDeLEtape
const getPerimetreInfosInternal = (pool: Pool, user: User, etape: GetPerimetreInfosInternalEtape): Effect.Effect<PerimetreInformations, CaminoError<GetPerimetreInfosInternalErrors>> =>
Effect.Do.pipe(
Effect.bind('demarche', () => getDemarcheByIdOrSlug(pool, etape.demarche_id)),
Effect.bind('titre', ({ demarche }) => getTitreByIdOrSlug(pool, demarche.titre_id)),
Effect.bind('canReadEtape', ({ demarche, titre }) =>
Effect.tryPromise({
try: () =>
canReadEtape(
user,
memoize(() => Promise.resolve(titre.titre_type_id)),
memoize(() => getAdministrationsLocalesByTitreId(pool, demarche.titre_id)),
memoize(() => getTitulairesAmodiatairesByTitreId(pool, demarche.titre_id)),
etape.etape_type_id,
{ ...demarche, titre_public_lecture: titre.public_lecture }
),
catch: e => ({ message: erreurLorsDeLaGestionDesDroitsDeLEtape, extra: e }),
})
),
Effect.filterOrFail(
({ canReadEtape }) => canReadEtape,
() => ({ message: cantReadEtapeError })
),
Effect.bind('superpositionAlertes', ({ titre }) => getAlertesSuperposition(etape.geojson4326_perimetre, titre.titre_type_id, titre.titre_slug, user, pool)),
Effect.map(({ superpositionAlertes }) => {
const response: PerimetreInformations = {
communes: etape.communes,
sdomZoneIds: etape.sdom_zones,
superposition_alertes: superpositionAlertes,
}
}
}
return response
})
)
const shapeValidator = z.array(polygonValidator.or(multiPolygonValidator)).max(1).min(1)
const geojsonValidator = featureCollectionMultipolygonValidator.or(featureCollectionPolygonValidator)
......
......@@ -199,7 +199,7 @@ export const utilisateurs =
const format = searchParams.format
const entreprises = await getEntreprises(pool)
const entreprises = await callAndExit(getEntreprises(pool))
const contenu = tableConvert('utilisateurs', utilisateursFormatTable(utilisateurs, entreprises), format)
return contenu
......
......@@ -314,7 +314,7 @@ const matrices = async (annee: CaminoAnnee, pool: Pool): Promise<void> => {
userSuper
)
const entreprises = await getEntreprises(pool)
const entreprises = await callAndExit(getEntreprises(pool))
const communesIds = titres
.flatMap(({ communes }) => communes?.map(({ id }) => id))
......
......@@ -49,7 +49,7 @@ import { SendFileOptions } from 'express-serve-static-core'
import { activiteDocumentDownload, getActivite, updateActivite, deleteActivite } from '../api/rest/activites'
import { isNotNullNorUndefined, isNullOrUndefined } from 'camino-common/src/typescript-tools'
import { getDemarcheByIdOrSlugApi, demarcheSupprimer, demarcheCreer, getDemarchesEnConcurrence, getResultatEnConcurrence } from '../api/rest/demarches'
import { geojsonImport, geojsonImportPoints, getPerimetreInfos, geojsonImportForages } from '../api/rest/perimetre'
import { geojsonImport, geojsonImportPoints, geojsonImportForages, getPerimetreInfosByDemarche, getPerimetreInfosByEtape } from '../api/rest/perimetre'
import { getDataGouvStats } from '../api/rest/statistiques/datagouv'
import { addAdministrationActiviteTypeEmails, deleteAdministrationActiviteTypeEmails, getAdministrationActiviteTypeEmails, getAdministrationUtilisateurs } from '../api/rest/administrations'
import { titreDemandeCreer } from '../api/rest/titre-demande'
......@@ -219,10 +219,10 @@ const restRouteImplementations: Readonly<{ [key in CaminoRestRoute]: Transform<k
newDeleteCall: deleteEntrepriseDocument,
...CaminoRestRoutes['/rest/entreprises/:entrepriseId/documents/:entrepriseDocumentId'],
},
'/rest/entreprises': { postCall: creerEntreprise, getCall: getAllEntreprises, ...CaminoRestRoutes['/rest/entreprises'] },
'/rest/administrations/:administrationId/utilisateurs': { getCall: getAdministrationUtilisateurs, ...CaminoRestRoutes['/rest/administrations/:administrationId/utilisateurs'] },
'/rest/entreprises': { newPostCall: creerEntreprise, newGetCall: getAllEntreprises, ...CaminoRestRoutes['/rest/entreprises'] },
'/rest/administrations/:administrationId/utilisateurs': { newGetCall: getAdministrationUtilisateurs, ...CaminoRestRoutes['/rest/administrations/:administrationId/utilisateurs'] },
'/rest/administrations/:administrationId/activiteTypeEmails': {
getCall: getAdministrationActiviteTypeEmails,
newGetCall: getAdministrationActiviteTypeEmails,
newPostCall: addAdministrationActiviteTypeEmails,
...CaminoRestRoutes['/rest/administrations/:administrationId/activiteTypeEmails'],
},
......@@ -230,8 +230,8 @@ const restRouteImplementations: Readonly<{ [key in CaminoRestRoute]: Transform<k
newPostCall: deleteAdministrationActiviteTypeEmails,
...CaminoRestRoutes['/rest/administrations/:administrationId/activiteTypeEmails/delete'],
},
'/rest/demarches/:demarcheId/geojson': { getCall: getPerimetreInfos, ...CaminoRestRoutes['/rest/demarches/:demarcheId/geojson'] },
'/rest/etapes/:etapeId/geojson': { getCall: getPerimetreInfos, ...CaminoRestRoutes['/rest/etapes/:etapeId/geojson'] },
'/rest/demarches/:demarcheId/geojson': { newGetCall: getPerimetreInfosByDemarche, ...CaminoRestRoutes['/rest/demarches/:demarcheId/geojson'] },
'/rest/etapes/:etapeId/geojson': { newGetCall: getPerimetreInfosByEtape, ...CaminoRestRoutes['/rest/etapes/:etapeId/geojson'] },
'/rest/etapes/:etapeIdOrSlug': { deleteCall: deleteEtape, getCall: getEtape, ...CaminoRestRoutes['/rest/etapes/:etapeIdOrSlug'] },
'/rest/etapes': { newPostCall: createEtape, newPutCall: updateEtape, ...CaminoRestRoutes['/rest/etapes'] },
'/rest/etapes/:etapeId/depot': { newPutCall: deposeEtape, ...CaminoRestRoutes['/rest/etapes/:etapeId/depot'] },
......
......@@ -17,6 +17,7 @@ import { CommuneId, communeIdValidator } from 'camino-common/src/static/communes
import { idGenerate, newTitreId } from '../../database/models/_format/id-create'
import { RawLineMatrice, getRawLines } from '../../business/matrices'
import { isNotNullNorUndefined, RecordPartial } from 'camino-common/src/typescript-tools'
import { callAndExit } from '../fp-tools'
// Le pool ne doit être qu'aux entrypoints : le daily, le monthly, et l'application.
const pool = new pg.Pool({
host: config().PGHOST,
......@@ -100,7 +101,7 @@ export type BodyMatrice = {
const writeMatricesForTest = async () => {
const user = userSuper
const testBody: BodyMatrice[] = []
const entreprises = await getEntreprises(pool)
const entreprises = await callAndExit(getEntreprises(pool))
const annees = [toCaminoAnnee(2023), toCaminoAnnee(2022)] as const
for (const annee of annees) {
......
......@@ -180,7 +180,11 @@ export const CaminoRestRoutes = {
'/rest/statistiques/dgtm': { params: noParamsValidator, get: { output: statistiquesDGTMValidator } },
'/rest/entreprises/:entrepriseId/fiscalite/:annee': { params: z.object({ entrepriseId: entrepriseIdValidator, annee: caminoAnneeValidator }), get: { output: fiscaliteValidator } },
'/rest/entreprises': { params: noParamsValidator, post: { input: z.object({ siren: sirenValidator }), output: z.void() }, get: { output: z.array(entrepriseValidator) } },
'/rest/entreprises': {
params: noParamsValidator,
newPost: { input: z.object({ siren: sirenValidator }), output: z.object({ id: entrepriseIdValidator }) },
newGet: { output: z.array(entrepriseValidator) },
},
'/rest/entreprises/:entrepriseId': {
params: entrepriseIdParamsValidator,
newGet: { output: entrepriseTypeValidator },
......@@ -195,10 +199,10 @@ export const CaminoRestRoutes = {
params: z.object({ entrepriseId: entrepriseIdValidator, entrepriseDocumentId: entrepriseDocumentIdValidator }),
newDelete: true,
},
'/rest/administrations/:administrationId/utilisateurs': { params: administrationIdParamsValidator, get: { output: z.array(adminUserNotNullValidator) } },
'/rest/administrations/:administrationId/utilisateurs': { params: administrationIdParamsValidator, newGet: { output: z.array(adminUserNotNullValidator) } },
'/rest/administrations/:administrationId/activiteTypeEmails': {
params: administrationIdParamsValidator,
get: { output: z.array(administrationActiviteTypeEmailValidator) },
newGet: { output: z.array(administrationActiviteTypeEmailValidator) },
newPost: { input: administrationActiviteTypeEmailValidator, output: z.boolean() },
},
'/rest/administrations/:administrationId/activiteTypeEmails/delete': {
......@@ -210,10 +214,10 @@ export const CaminoRestRoutes = {
params: z.object({ demarcheId: demarcheIdValidator, date: caminoDateValidator }),
newGet: { output: etapeTypeEtapeStatutWithMainStepValidator, searchParams: z.object({ etapeId: etapeIdValidator.optional() }) },
},
'/rest/demarches/:demarcheId/geojson': { params: z.object({ demarcheId: demarcheIdOrSlugValidator }), get: { output: perimetreInformationsValidator } },
'/rest/demarches/:demarcheId/geojson': { params: z.object({ demarcheId: demarcheIdOrSlugValidator }), newGet: { output: perimetreInformationsValidator } },
'/rest/demarches/:demarcheId/miseEnConcurrence': { params: z.object({ demarcheId: demarcheIdValidator }), newGet: { output: z.array(getDemarcheMiseEnConcurrenceValidator) } },
'/rest/demarches/:demarcheId/resultatMiseEnConcurrence': { params: z.object({ demarcheId: demarcheIdValidator }), newGet: { output: getResultatMiseEnConcurrenceValidator } },
'/rest/etapes/:etapeId/geojson': { params: z.object({ etapeId: etapeIdOrSlugValidator }), get: { output: perimetreInformationsValidator } },
'/rest/etapes/:etapeId/geojson': { params: z.object({ etapeId: etapeIdOrSlugValidator }), newGet: { output: perimetreInformationsValidator } },
'/rest/etapes/:etapeId/etapeDocuments': { params: etapeIdParamsValidator, get: { output: getEtapeDocumentsByEtapeIdValidator } },
'/rest/etapes/:etapeId/etapeAvis': { params: etapeIdParamsValidator, get: { output: getEtapeAvisByEtapeIdValidator } },
'/rest/etapes/:etapeId/entrepriseDocuments': { params: etapeIdParamsValidator, get: { output: z.array(etapeEntrepriseDocumentValidator) } },
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment