diff --git a/.gitignore b/.gitignore index 3027a08d..f461b506 100644 --- a/.gitignore +++ b/.gitignore @@ -68,4 +68,6 @@ typings/ /toolbox.dev/data # Migration data backup -db-migrations/migrations/data-backup \ No newline at end of file +db-migrations/migrations/data-backup +# Ignorer tout le contenu du dossier db-migrations/data/ +db-migrations/data \ No newline at end of file diff --git a/db-migrations/migrations/20240808162138-init-certificate-table.cjs b/db-migrations/migrations/20240808162138-init-certificate-table.cjs new file mode 100644 index 00000000..fb852784 --- /dev/null +++ b/db-migrations/migrations/20240808162138-init-certificate-table.cjs @@ -0,0 +1,84 @@ +'use strict' + +require('dotenv').config() + +const {POSTGRES_BAN_USER} = process.env + +/** @type {import('sequelize-cli').Migration} */ +module.exports = { + async up(queryInterface, Sequelize) { + try { + // Create ban schema if not exists + await queryInterface.sequelize.query('CREATE SCHEMA IF NOT EXISTS ban;') + + // Grant permissions to ban user on schema ban + await queryInterface.sequelize.query( + `GRANT USAGE ON SCHEMA ban TO "${POSTGRES_BAN_USER}";` + ) + + // Create Certificate Table if not exists + await queryInterface.createTable( + 'certificate', + { + id: { + type: Sequelize.UUID, + defaultValue: Sequelize.UUIDV4, + allowNull: false, + primaryKey: true, + }, + // eslint-disable-next-line camelcase + address_id: { + type: Sequelize.UUID, + allowNull: false, + references: { + model: { + tableName: 'address', + schema: 'ban', + }, + key: 'id', + }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE', + }, + // eslint-disable-next-line camelcase + full_address: { + type: Sequelize.JSONB, + allowNull: false, + }, + // eslint-disable-next-line camelcase + cadastre_ids: { + type: Sequelize.ARRAY(Sequelize.STRING), + allowNull: true, + }, + createdAt: { + type: Sequelize.DATE, + defaultValue: Sequelize.NOW, + }, + }, + { + schema: 'ban', + timestamps: false, + ifNotExists: true, + } + ) + + // Grant permissions to ban user on the certificate table + await queryInterface.sequelize.query( + `GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ban.certificate TO "${POSTGRES_BAN_USER}";` + ) + } catch (error) { + console.error(error) + } + }, + + async down(queryInterface) { + try { + // Drop the Certificate table + await queryInterface.sequelize.query( + 'DROP TABLE IF EXISTS ban.certificate CASCADE;' + ) + } catch (error) { + console.error(error) + } + }, +} diff --git a/lib/api/certificate/models.js b/lib/api/certificate/models.js new file mode 100644 index 00000000..af5a43a6 --- /dev/null +++ b/lib/api/certificate/models.js @@ -0,0 +1,56 @@ +import {Certificate, sequelize} from '../../util/sequelize.js' + +const getDataForCertificateQuery = ` + SELECT + a.id as "addressID", + a.number as "addressNumber", + a.suffix as "addressSuffix", + ct.labels[1]->>'value' as "commonToponymDefaultLabel", + d.labels[1]->>'value' as "districtDefaultLabel", + d.meta->'insee'->>'cog' as "districtCog", + d.config as "districtConfig", + a.meta->'cadastre'->'ids' as "cadastreIDs", + a.certified, + a."isActive" + FROM + "ban"."address" AS a + JOIN + "ban"."district" AS d ON a."districtID" = d.id + LEFT JOIN + "ban"."common_toponym" AS ct ON ct.id = a."mainCommonToponymID" + WHERE + a.id = :addressId + and a.certified=true + and a."isActive"=true + and jsonb_array_length(a.meta->'cadastre'->'ids') > 0 + +` + +export const getCertificate = certificateID => Certificate.findByPk(certificateID, {raw: true}) + +export const getCertificates = certificateIDs => Certificate.findAll({ + where: {id: certificateIDs}, + raw: true +}) + +export const getCertificatesByAddress = addressID => Certificate.findAll({ + where: {address_id: addressID}, // eslint-disable-line camelcase + raw: true +}) + +export const setCertificate = async certificate => Certificate.create(certificate) + +export const getDataForCertificate = async addressId => { + try { + const [data] = await sequelize.query(getDataForCertificateQuery, { + replacements: {addressId}, + raw: true, + }) + + console.log(`Data for certificate: ${JSON.stringify(data)}`) + return data[0] + } catch (error) { + console.error(`Error executing query: ${error.message}`) + throw error + } +} diff --git a/lib/api/certificate/routes.js b/lib/api/certificate/routes.js new file mode 100644 index 00000000..f430ec18 --- /dev/null +++ b/lib/api/certificate/routes.js @@ -0,0 +1,58 @@ +import 'dotenv/config.js' // eslint-disable-line import/no-unassigned-import +import express from 'express' +import auth from '../../middleware/auth.js' +import { + getCertificate, + setCertificate, + getDataForCertificate +} from './models.js' +import {formatDataForCertificate} from './utils.js' + +const app = new express.Router() +app.use(express.json()) + +app.get('/:id', async (req, res) => { + const {id} = req.params + try { + const certificate = await getCertificate(id) + if (certificate) { + res.status(200).json(certificate) + } else { + res.status(404).json({message: 'Certificate not found'}) + } + } catch (error) { + console.error(`Error retrieving certificate: ${error.message}`) + res.status(500).json({message: 'Internal server error'}) + } +}) + +app.post('/', auth, async (req, res) => { + try { + const {addressID} = req.body + + if (!addressID) { + return res.status(400).json({message: 'addressID is required'}) + } + + const data = await getDataForCertificate(addressID) + + if (!data) { + return res.status(400).json({message: 'Address is not certified, not active, or has no parcels.'}) + } + + const {districtConfig} = data + if (!districtConfig.certificate) { + return res.status(400).json({message: 'District has not activated the certificate config.'}) + } + + const certificate = await formatDataForCertificate(data) + const newCertificate = await setCertificate(certificate) + + res.status(201).json(newCertificate) + } catch (error) { + console.error(`Error creating certificate: ${error.message}`) + res.status(500).json({message: 'Internal server error'}) + } +}) + +export default app diff --git a/lib/api/certificate/utils.js b/lib/api/certificate/utils.js new file mode 100644 index 00000000..cefbced5 --- /dev/null +++ b/lib/api/certificate/utils.js @@ -0,0 +1,15 @@ +export const formatDataForCertificate = data => { + const fullAddress = { + number: data.addressNumber, + commonToponymDefaultLabel: data.commonToponymDefaultLabel, + suffix: data.addressSuffix, + districtDefaultLabel: data.districtDefaultLabel, + cog: data.districtCog, + } + + return { + address_id: data.addressID, // eslint-disable-line camelcase + full_address: fullAddress, // eslint-disable-line camelcase + cadastre_ids: data.cadastreIDs, // eslint-disable-line camelcase + } +} diff --git a/lib/api/district/schema.js b/lib/api/district/schema.js index 6bea9052..c4c0954a 100644 --- a/lib/api/district/schema.js +++ b/lib/api/district/schema.js @@ -1,8 +1,10 @@ -import {object, string, array, date, bool} from 'yup' +import {object, string, array, date} from 'yup' import {banID, labelSchema, balSchema} from '../schema.js' +const certificateSchema = object({}).noUnknown() + const configSchema = object({ - useBanId: bool() + certificate: certificateSchema, }).noUnknown() const inseeSchema = object({ diff --git a/lib/api/routes.js b/lib/api/routes.js index 2ac47ff5..2824849d 100644 --- a/lib/api/routes.js +++ b/lib/api/routes.js @@ -6,6 +6,7 @@ import commonToponymRoutes from './common-toponym/routes.js' import districtRoutes from './district/routes.js' import statusRoutes from './job-status/routes.js' import banIdRoutes from './ban-id/routes.js' +import certificatRoutes from './certificate/routes.js' const app = new express.Router() @@ -14,5 +15,6 @@ app.use('/common-toponym', commonToponymRoutes) app.use('/district', districtRoutes) app.use('/job-status', statusRoutes) app.use('/ban-id', banIdRoutes) +app.use('/certificate', certificatRoutes) export default app diff --git a/lib/util/sequelize.js b/lib/util/sequelize.js index f3946e92..ae08517f 100644 --- a/lib/util/sequelize.js +++ b/lib/util/sequelize.js @@ -211,6 +211,40 @@ export const JobStatus = sequelize.define('JobStatus', { tableName: 'job_status' }) +export const Certificate = sequelize.define('Certificate', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + address_id: { // eslint-disable-line camelcase + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'address', + key: 'id', + }, + }, + // eslint-disable-next-line camelcase + full_address: { + type: DataTypes.JSONB, + allowNull: false, + }, + // eslint-disable-next-line camelcase + cadastre_ids: { + type: DataTypes.ARRAY(DataTypes.STRING), + allowNull: true, + }, + createdAt: { + type: DataTypes.DATE, + defaultValue: Sequelize.NOW, + } +}, { + schema: 'ban', + tableName: 'certificate', + timestamps: false, +}) + export const init = async () => { try { await sequelize.authenticate()