From c1e4126048a975c2c6fcd3ef080f072b572f0449 Mon Sep 17 00:00:00 2001 From: antoineludeau <52679050+antoineludeau@users.noreply.github.com> Date: Fri, 6 Oct 2023 16:35:09 +0200 Subject: [PATCH] Added patch routes and schemas for address, common toponym and district --- lib/api/address/routes.js | 27 ++++++++++++ lib/api/address/schema.js | 24 ++++++++++- lib/api/address/utils.js | 6 ++- lib/api/common-toponym/routes.js | 27 ++++++++++++ lib/api/common-toponym/schema.js | 14 +++++++ lib/api/common-toponym/utils.js | 6 ++- lib/api/consumers/api-consumer.js | 68 ++++++++++++++++++++++++++----- lib/api/district/routes.js | 27 ++++++++++++ lib/api/district/schema.js | 12 ++++++ lib/api/district/utils.js | 6 ++- lib/api/helper.js | 15 ++++++- 11 files changed, 214 insertions(+), 18 deletions(-) diff --git a/lib/api/address/routes.js b/lib/api/address/routes.js index 6e62970f..8bb0b2ab 100644 --- a/lib/api/address/routes.js +++ b/lib/api/address/routes.js @@ -71,6 +71,33 @@ app.route('/') res.send(response) }) + .patch(auth, analyticsMiddleware, async (req, res) => { + let response + try { + const addresses = req.body + const statusID = nanoid() + + await apiQueue.add( + {dataType: 'address', jobType: 'patch', data: addresses, statusID}, + {jobId: statusID, removeOnComplete: true} + ) + response = { + date: new Date(), + status: 'success', + message: `Check the status of your request : ${BAN_API_URL}/job-status/${statusID}`, + response: {statusID}, + } + } catch (error) { + response = { + date: new Date(), + status: 'error', + message: error, + response: {}, + } + } + + res.send(response) + }) app.route('/:addressID') .get(analyticsMiddleware, async (req, res) => { diff --git a/lib/api/address/schema.js b/lib/api/address/schema.js index fd42c312..aae7fe61 100644 --- a/lib/api/address/schema.js +++ b/lib/api/address/schema.js @@ -19,10 +19,32 @@ export const banAddressSchema = object({ districtID: banID.required(), number: number().positive().integer().required(), suffix: string().trim(), - labels: array().of(labelSchema).default(null).nullable(), + labels: array().of(labelSchema), certified: boolean(), positions: array().of(positionSchema).required(), updateDate: date().required(), meta: metaSchema }) +export const banAddressSchemaForPatch = object({ + id: banID.required(), + mainCommonToponymID: banID, + secondaryCommonToponymIDs: array().of(banID), + districtID: banID, + number: number().positive().integer(), + suffix: string().trim(), + labels: array().of(labelSchema), + certified: boolean().default(false), + positions: array().of(positionSchema), + updateDate: date(), + meta: metaSchema +}) + +export const addressDefaultOptionalValues = { + secondaryCommonToponymIDs: [], + suffix: '', + labels: [], + certified: false, + meta: {}, +} + diff --git a/lib/api/address/utils.js b/lib/api/address/utils.js index 9eb8ee34..70929945 100644 --- a/lib/api/address/utils.js +++ b/lib/api/address/utils.js @@ -1,7 +1,7 @@ import {checkDataFormat, dataValidationReportFrom, checkIdsIsUniq, checkIdsIsVacant, checkIdsIsAvailable, checkDataShema, checkIdsShema, checkIfCommonToponymsExist, checkIfDistrictsExist} from '../helper.js' import {banID} from '../schema.js' import {getAddresses, getAllAddressIDsFromDistrict, getAllAddressIDsOutsideDistrict} from './models.js' -import {banAddressSchema} from './schema.js' +import {banAddressSchema, banAddressSchemaForPatch} from './schema.js' const getExistingAddressIDs = async addressIDs => { const existingAddresses = await getAddresses(addressIDs) @@ -24,6 +24,7 @@ export const checkAddressesIDsRequest = async (addressIDs, actionType, defaultRe ) break case 'update': + case 'patch': report = ( checkIdsIsUniq('Shared IDs in request', addressIDs) || await checkIdsIsAvailable('Some unknown IDs', addressIDs, getExistingAddressIDs) @@ -49,13 +50,14 @@ export const checkAddressesRequest = async (addresses, actionType) => { switch (actionType) { case 'insert': case 'update': + case 'patch': report = checkDataFormat( `The request require an Array of address but receive ${typeof addresses}`, 'No address send to job', addresses ) || await checkAddressesIDsRequest(addresses.map(address => address.id), actionType, false) - || await checkDataShema('Invalid format', addresses, banAddressSchema) + || await checkDataShema('Invalid format', addresses, actionType === 'patch' ? banAddressSchemaForPatch : banAddressSchema) || await checkIfCommonToponymsExist(addresses.reduce((acc, {mainCommonToponymID, secondaryCommonToponymIDs}) => { const ids = [mainCommonToponymID] if (secondaryCommonToponymIDs) { diff --git a/lib/api/common-toponym/routes.js b/lib/api/common-toponym/routes.js index 8056e7c1..4354474b 100644 --- a/lib/api/common-toponym/routes.js +++ b/lib/api/common-toponym/routes.js @@ -71,6 +71,33 @@ app.route('/') res.send(response) }) + .patch(auth, analyticsMiddleware, async (req, res) => { + let response + try { + const commonToponyms = req.body + const statusID = nanoid() + + await apiQueue.add( + {dataType: 'commonToponym', jobType: 'patch', data: commonToponyms, statusID}, + {jobId: statusID, removeOnComplete: true} + ) + response = { + date: new Date(), + status: 'success', + message: `Check the status of your request : ${BAN_API_URL}/job-status/${statusID}`, + response: {statusID}, + } + } catch (error) { + response = { + date: new Date(), + status: 'error', + message: error, + response: {}, + } + } + + res.send(response) + }) app.route('/:commonToponymID') .get(analyticsMiddleware, async (req, res) => { diff --git a/lib/api/common-toponym/schema.js b/lib/api/common-toponym/schema.js index 053e2372..3764bd0d 100644 --- a/lib/api/common-toponym/schema.js +++ b/lib/api/common-toponym/schema.js @@ -13,3 +13,17 @@ export const banCommonToponymSchema = object({ updateDate: date().required(), meta: metaSchema }) + +export const banCommonToponymSchemaForPatch = object({ + id: banID.required(), + districtID: banID, + labels: array().of(labelSchema), + geometry: geometrySchema, + updateDate: date(), + meta: metaSchema +}) + +export const commonToponymDefaultOptionalValues = { + geometry: {}, + meta: {}, +} diff --git a/lib/api/common-toponym/utils.js b/lib/api/common-toponym/utils.js index 7cbc59c0..89f840cd 100644 --- a/lib/api/common-toponym/utils.js +++ b/lib/api/common-toponym/utils.js @@ -1,7 +1,7 @@ import {checkDataFormat, dataValidationReportFrom, checkIdsIsUniq, checkIdsIsVacant, checkIdsIsAvailable, checkDataShema, checkIdsShema, checkIfDistrictsExist} from '../helper.js' import {banID} from '../schema.js' import {getCommonToponyms, getAllCommonToponymIDsFromDistrict, getAllCommonToponymIDsOutsideDistrict} from './models.js' -import {banCommonToponymSchema} from './schema.js' +import {banCommonToponymSchema, banCommonToponymSchemaForPatch} from './schema.js' const getExistingCommonToponymIDs = async commonToponymIDs => { const existingCommonToponyms = await getCommonToponyms(commonToponymIDs) @@ -24,6 +24,7 @@ export const checkCommonToponymsIDsRequest = async (commonToponymIDs, actionType ) break case 'update': + case 'patch': report = ( checkIdsIsUniq('Shared IDs in request', commonToponymIDs) || await checkIdsIsAvailable('Some unknown IDs', commonToponymIDs, getExistingCommonToponymIDs) @@ -49,13 +50,14 @@ export const checkCommonToponymsRequest = async (commonToponyms, actionType) => switch (actionType) { case 'insert': case 'update': + case 'patch': report = checkDataFormat( `The request require an Array of common toponym but receive ${typeof commonToponyms}`, 'No common toponym send to job', commonToponyms ) || await checkCommonToponymsIDsRequest(commonToponyms.map(commonToponym => commonToponym.id), actionType, false) - || await checkDataShema('Invalid common toponym format', commonToponyms, banCommonToponymSchema) + || await checkDataShema('Invalid common toponym format', commonToponyms, actionType === 'patch' ? banCommonToponymSchemaForPatch : banCommonToponymSchema) || await checkIfDistrictsExist(commonToponyms.map(({districtID}) => districtID)) || dataValidationReportFrom(true) break diff --git a/lib/api/consumers/api-consumer.js b/lib/api/consumers/api-consumer.js index 9892e512..5a0803f1 100644 --- a/lib/api/consumers/api-consumer.js +++ b/lib/api/consumers/api-consumer.js @@ -6,7 +6,10 @@ import {setCommonToponyms, updateCommonToponyms, deleteCommonToponyms, getAllDis import {checkCommonToponymsRequest, checkCommonToponymsIDsRequest} from '../common-toponym/utils.js' import {setDistricts, updateDistricts, deleteDistricts} from '../district/models.js' import {checkDistrictsRequest, checkDistrictsIDsRequest} from '../district/utils.js' -import {dataValidationReportFrom, addOrUpdateJob} from '../helper.js' +import {dataValidationReportFrom, formatObjectWithDefaults, addOrUpdateJob} from '../helper.js' +import {addressDefaultOptionalValues} from '../address/schema.js' +import {commonToponymDefaultOptionalValues} from '../common-toponym/schema.js' +import {districtDefaultOptionalValues} from '../district/schema.js' const exportToExploitationDBQueue = queue('export-to-exploitation-db') const exportToExploitationDBJobDelay = process.env.EXPORT_TO_EXPLOITATION_DB_JOB_DELAY || 10_000 @@ -60,6 +63,7 @@ const addressConsumer = async (jobType, payload, statusID) => { switch (jobType) { case 'insert': case 'update': + case 'patch': return checkAddressesRequest(payload, jobType) case 'delete': return checkAddressesIDsRequest(payload, jobType) @@ -72,10 +76,23 @@ const addressConsumer = async (jobType, payload, statusID) => { const addressesCount = payload.length if (requestDataValidationReport.isValid) { switch (jobType) { - case 'insert': - await setAddresses(payload) + case 'insert': { + const formattedAddresses = payload.map(address => ( + formatObjectWithDefaults(address, addressDefaultOptionalValues) + )) + await setAddresses(formattedAddresses) break - case 'update': + } + + case 'update': { + const formattedAddresses = payload.map(address => ( + formatObjectWithDefaults(address, addressDefaultOptionalValues) + )) + await updateAddresses(formattedAddresses) + break + } + + case 'patch': await updateAddresses(payload) break case 'delete': @@ -108,6 +125,7 @@ const commonToponymConsumer = async (jobType, payload, statusID) => { switch (jobType) { case 'insert': case 'update': + case 'patch': return checkCommonToponymsRequest(payload, jobType) case 'delete': return checkCommonToponymsIDsRequest(payload, jobType) @@ -120,10 +138,23 @@ const commonToponymConsumer = async (jobType, payload, statusID) => { const commonToponymsCount = payload.length if (requestDataValidationReport.isValid) { switch (jobType) { - case 'insert': - await setCommonToponyms(payload) + case 'insert': { + const formattedCommonToponyms = payload.map(commonToponym => ( + formatObjectWithDefaults(commonToponym, commonToponymDefaultOptionalValues) + )) + await setCommonToponyms(formattedCommonToponyms) break - case 'update': + } + + case 'update': { + const formattedCommonToponyms = payload.map(commonToponym => ( + formatObjectWithDefaults(commonToponym, commonToponymDefaultOptionalValues) + )) + await updateCommonToponyms(formattedCommonToponyms) + break + } + + case 'patch': await updateCommonToponyms(payload) break case 'delete': @@ -156,6 +187,7 @@ const districtConsumer = async (jobType, payload, statusID) => { switch (jobType) { case 'insert': case 'update': + case 'patch': return checkDistrictsRequest(payload, jobType) case 'delete': return checkDistrictsIDsRequest(payload, jobType) @@ -168,10 +200,23 @@ const districtConsumer = async (jobType, payload, statusID) => { const districtsCount = payload.length if (requestDataValidationReport.isValid) { switch (jobType) { - case 'insert': - await setDistricts(payload) + case 'insert': { + const formattedDistricts = payload.map(district => ( + formatObjectWithDefaults(district, districtDefaultOptionalValues) + )) + await setDistricts(formattedDistricts) break - case 'update': + } + + case 'update': { + const formattedDistricts = payload.map(district => ( + formatObjectWithDefaults(district, districtDefaultOptionalValues) + )) + await updateDistricts(formattedDistricts) + break + } + + case 'patch': await updateDistricts(payload) break case 'delete': @@ -205,6 +250,7 @@ export const extractRelatedDistrictIDs = async (dataType, jobType, payload) => { switch (jobType) { case 'insert': case 'update': + case 'patch': return getAllDistrictIDsFromAddresses(payload.map(({id}) => id)) case 'delete': return getAllDistrictIDsFromAddresses(payload) @@ -217,6 +263,7 @@ export const extractRelatedDistrictIDs = async (dataType, jobType, payload) => { switch (jobType) { case 'insert': case 'update': + case 'patch': return getAllDistrictIDsFromCommonToponyms(payload.map(({id}) => id)) case 'delete': return getAllDistrictIDsFromCommonToponyms(payload) @@ -229,6 +276,7 @@ export const extractRelatedDistrictIDs = async (dataType, jobType, payload) => { switch (jobType) { case 'insert': case 'update': + case 'patch': return payload.map(({id}) => id) case 'delete': return payload diff --git a/lib/api/district/routes.js b/lib/api/district/routes.js index bc63b853..407360bc 100644 --- a/lib/api/district/routes.js +++ b/lib/api/district/routes.js @@ -70,6 +70,33 @@ app.route('/') res.send(response) }) + .patch(auth, analyticsMiddleware, async (req, res) => { + let response + try { + const districts = req.body + const statusID = nanoid() + + await apiQueue.add( + {dataType: 'district', jobType: 'patch', data: districts, statusID}, + {jobId: statusID, removeOnComplete: true} + ) + response = { + date: new Date(), + status: 'success', + message: `Check the status of your request : ${BAN_API_URL}/job-status/${statusID}`, + response: {statusID}, + } + } catch (error) { + response = { + date: new Date(), + status: 'error', + message: error, + response: {}, + } + } + + res.send(response) + }) app.route('/:districtID') .get(analyticsMiddleware, async (req, res) => { diff --git a/lib/api/district/schema.js b/lib/api/district/schema.js index 6a0bdf78..b71052f6 100644 --- a/lib/api/district/schema.js +++ b/lib/api/district/schema.js @@ -16,3 +16,15 @@ export const banDistrictSchema = object({ config: configSchema, meta: metaSchema }) + +export const banDistrictSchemaForPatch = object({ + id: banID.required(), + labels: array().of(labelSchema), + updateDate: date(), + config: configSchema, + meta: metaSchema +}) + +export const districtDefaultOptionalValues = { + config: {}, +} diff --git a/lib/api/district/utils.js b/lib/api/district/utils.js index ab244099..031ba64e 100644 --- a/lib/api/district/utils.js +++ b/lib/api/district/utils.js @@ -1,7 +1,7 @@ import {checkDataFormat, dataValidationReportFrom, checkIdsIsUniq, checkIdsIsVacant, checkIdsIsAvailable, checkDataShema, checkIdsShema} from '../helper.js' import {banID} from '../schema.js' import {getDistricts} from './models.js' -import {banDistrictSchema} from './schema.js' +import {banDistrictSchema, banDistrictSchemaForPatch} from './schema.js' const getExistingDistrictIDs = async districtIDs => { const existingDistricts = await getDistricts(districtIDs) @@ -24,6 +24,7 @@ export const checkDistrictsIDsRequest = async (districtIDs, actionType, defaultR ) break case 'update': + case 'patch': report = ( checkIdsIsUniq('Shared IDs in request', districtIDs) || await checkIdsIsAvailable('Some unknown IDs', districtIDs, getExistingDistrictIDs) @@ -49,13 +50,14 @@ export const checkDistrictsRequest = async (districts, actionType) => { switch (actionType) { case 'insert': case 'update': + case 'patch': report = checkDataFormat( `The request require an Array of district but receive ${typeof districts}`, 'No district send to job', districts ) || await checkDistrictsIDsRequest(districts.map(district => district.id), actionType, false) - || await checkDataShema('Invalid format', districts, banDistrictSchema) + || await checkDataShema('Invalid format', districts, actionType === 'patch' ? banDistrictSchemaForPatch : banDistrictSchema) || dataValidationReportFrom(true) break default: diff --git a/lib/api/helper.js b/lib/api/helper.js index cd3328de..1041e470 100644 --- a/lib/api/helper.js +++ b/lib/api/helper.js @@ -77,7 +77,7 @@ export const checkIdsShema = async (err = 'Invalid IDs format', ids, schema) => const dataSchemaValidation = async (data, schema) => { try { - await schema.validate(data, {abortEarly: false}) + await schema.validate(data, {abortEarly: false, stripUnknown: true}) } catch (error) { return dataValidationReportFrom(false, `Invalid data format (id: ${data.id})`, error.errors) } @@ -132,3 +132,16 @@ export const addOrUpdateJob = async (queue, jobId, data, delay) => { console.error(error) } } + +export const formatObjectWithDefaults = (inputObject, defaultValues) => { + const formattedObject = {...inputObject} + for (const key in defaultValues) { + // Check if the key is missing in the inputObject + if (!(key in formattedObject)) { + // If the key is missing, add it with the default value + formattedObject[key] = defaultValues[key] + } + } + + return formattedObject +}