Skip to content

Commit

Permalink
feat(api,dashboard): passages migration cherry picked (#493)
Browse files Browse the repository at this point in the history
* feat(api): add migrating field to prevent double migration

* api: add passage model/controller/api

* dashboard: handle new passage ongoing

* migration: first step

* can edit passage

* reception ongoing

* reception ongoing

* good passage management

* delete stale comments / update reports to remove passages from each

* fix tests

* migrate passages in stats

* chore: comment inclusive to `isBetween` from dayjs

* chore: rename validateOrganisationEncryption to validateEncryptionAndMigrations

* chore: clean

* fix: up filename

* Update api/src/controllers/migration.js

Co-authored-by: Raphaël Huchet <rap2hpoutre@users.noreply.github.com>

* Update api/src/controllers/migration.js

Co-authored-by: Raphaël Huchet <rap2hpoutre@users.noreply.github.com>

* Update dashboard/src/components/Passage.js

Co-authored-by: Raphaël Huchet <rap2hpoutre@users.noreply.github.com>

* Update api/src/db/migrations/2022-03-14_add_passage_table.js

Co-authored-by: Raphaël Huchet <rap2hpoutre@users.noreply.github.com>

* fix: move middlewarable code to middleware

* dix: grammar

* fix: set anonymous passage time to start of day

Co-authored-by: Raphaël Huchet <raph@selego.co>
Co-authored-by: Raphaël Huchet <rap2hpoutre@users.noreply.github.com>
  • Loading branch information
3 people authored Mar 22, 2022
1 parent d976261 commit 32b940e
Show file tree
Hide file tree
Showing 46 changed files with 943 additions and 357 deletions.
6 changes: 3 additions & 3 deletions api/src/controllers/action.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const passport = require("passport");
const { Op } = require("sequelize");
const { catchErrors } = require("../errors");
const validateUser = require("../middleware/validateUser");
const validateOrganisationEncryption = require("../middleware/validateOrganisationEncryption");
const validateEncryptionAndMigrations = require("../middleware/validateEncryptionAndMigrations");
const Action = require("../models/action");
const { looseUuidRegex, positiveIntegerRegex } = require("../utils");

Expand All @@ -18,7 +18,7 @@ router.post(
"/",
passport.authenticate("user", { session: false }),
validateUser(["admin", "normal"]),
validateOrganisationEncryption,
validateEncryptionAndMigrations,
catchErrors(async (req, res, next) => {
try {
z.enum(STATUS).parse(req.body.status);
Expand Down Expand Up @@ -126,7 +126,7 @@ router.put(
"/:_id",
passport.authenticate("user", { session: false }),
validateUser(["admin", "normal"]),
validateOrganisationEncryption,
validateEncryptionAndMigrations,
catchErrors(async (req, res, next) => {
try {
z.string().regex(looseUuidRegex).parse(req.params._id);
Expand Down
4 changes: 2 additions & 2 deletions api/src/controllers/category.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const sequelize = require("../db/sequelize");
const { catchErrors } = require("../errors");
const Organisation = require("../models/organisation");
const Action = require("../models/action");
const validateOrganisationEncryption = require("../middleware/validateOrganisationEncryption");
const validateEncryptionAndMigrations = require("../middleware/validateEncryptionAndMigrations");
const { capture } = require("../sentry");
const validateUser = require("../middleware/validateUser");
const { looseUuidRegex } = require("../utils");
Expand All @@ -15,7 +15,7 @@ router.put(
"/",
passport.authenticate("user", { session: false }),
validateUser("admin"),
validateOrganisationEncryption,
validateEncryptionAndMigrations,
catchErrors(async (req, res, next) => {
try {
z.array(
Expand Down
6 changes: 3 additions & 3 deletions api/src/controllers/comment.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ const { Op } = require("sequelize");
const { z } = require("zod");
const { catchErrors } = require("../errors");
const Comment = require("../models/comment");
const validateOrganisationEncryption = require("../middleware/validateOrganisationEncryption");
const validateEncryptionAndMigrations = require("../middleware/validateEncryptionAndMigrations");
const validateUser = require("../middleware/validateUser");
const { looseUuidRegex, positiveIntegerRegex } = require("../utils");

router.post(
"/",
passport.authenticate("user", { session: false }),
validateUser(["admin", "normal"]),
validateOrganisationEncryption,
validateEncryptionAndMigrations,
catchErrors(async (req, res, next) => {
try {
z.string().parse(req.body.encrypted);
Expand Down Expand Up @@ -85,7 +85,7 @@ router.put(
"/:_id",
passport.authenticate("user", { session: false }),
validateUser(["admin", "normal"]),
validateOrganisationEncryption,
validateEncryptionAndMigrations,
catchErrors(async (req, res, next) => {
try {
z.string().regex(looseUuidRegex).parse(req.params._id);
Expand Down
6 changes: 6 additions & 0 deletions api/src/controllers/encrypt.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const Place = require("../models/place");
const RelPersonPlace = require("../models/relPersonPlace");
const Action = require("../models/action");
const Comment = require("../models/comment");
const Passage = require("../models/passage");
const Territory = require("../models/territory");
const Report = require("../models/report");
const TerritoryObservation = require("../models/territoryObservation");
Expand Down Expand Up @@ -63,6 +64,7 @@ router.post(
actions = [],
persons = [],
comments = [],
passages = [],
territories = [],
observations = [],
places = [],
Expand All @@ -88,6 +90,10 @@ router.post(
await Comment.update({ encrypted, encryptedEntityKey }, { where: { _id }, transaction: tx });
}

for (let { encrypted, encryptedEntityKey, _id } of passages) {
await Passage.update({ encrypted, encryptedEntityKey }, { where: { _id }, transaction: tx });
}

for (let { encrypted, encryptedEntityKey, _id } of territories) {
await Territory.update({ encrypted, encryptedEntityKey }, { where: { _id }, transaction: tx });
}
Expand Down
78 changes: 70 additions & 8 deletions api/src/controllers/migration.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,20 @@ const { z } = require("zod");
const sequelize = require("../db/sequelize");
const { catchErrors } = require("../errors");
const Organisation = require("../models/organisation");
const validateOrganisationEncryption = require("../middleware/validateOrganisationEncryption");
const Passage = require("../models/passage");
const Comment = require("../models/comment");
const Report = require("../models/report");
const validateEncryptionAndMigrations = require("../middleware/validateEncryptionAndMigrations");
const { looseUuidRegex } = require("../utils");
const { capture } = require("../sentry");
const validateUser = require("../middleware/validateUser");

router.put(
"/:migrationName",
passport.authenticate("user", { session: false }),
validateOrganisationEncryption,
catchErrors(async (req, res) => {
validateEncryptionAndMigrations,
validateUser(["admin", "normal"]),
catchErrors(async (req, res, next) => {
try {
z.literal("admin").parse(req.user.role);
z.string().regex(looseUuidRegex).parse(req.user.organisation);
Expand All @@ -24,30 +29,87 @@ router.put(

const organisation = await Organisation.findOne({ where: { _id: req.user.organisation } });
if (!organisation) return res.status(404).send({ ok: false, error: "Not Found" });
organisation.set({ migrating: true });
await organisation.save();

try {
await sequelize.transaction(async (tx) => {
// Each migration has its own "if". This is an example.
if (req.params.migrationName === "passages-from-comments-to-table") {
try {
z.array(z.string().regex(looseUuidRegex)).parse(req.body.commentIds);
z.array(z.string().regex(looseUuidRegex)).parse(req.body.commentIdsToDelete);
z.array(
z.object({
encrypted: z.string(),
encryptedEntityKey: z.string(),
})
).parse(req.body.newPassages);
z.array(
z.object({
_id: z.string().regex(looseUuidRegex),
encrypted: z.string(),
encryptedEntityKey: z.string(),
})
).parse(req.body.reportsToMigrate);
} catch (e) {
return res.status(400).send({ ok: false, error: "Invalid request" });
const error = new Error(`Invalid request in report creation: ${e}`);
error.status = 400;
throw error;
}
for (const _id of actions) {
for (const passage of req.body.newPassages) {
await Passage.create({
encrypted: passage.encrypted,
encryptedEntityKey: passage.encryptedEntityKey,
organisation: req.user.organisation,
});
}
for (const _id of req.body.commentIdsToDelete) {
const comment = await Comment.findOne({ where: { _id, organisation: req.user.organisation }, transaction: tx });
if (comment) await comment.destroy();
}
for (const { _id, encrypted, encryptedEntityKey } of req.body.reportsToMigrate) {
const report = await Report.findOne({ where: { _id, organisation: req.user.organisation }, transaction: tx });
if (report) {
report.set({ encrypted, encryptedEntityKey });
await report.save();
}
}
}

organisation.set({ migrations: [...organisation.migrations, req.params.migrationName] });
organisation.set({
migrations: [...(organisation.migrations || []), req.params.migrationName],
migrating: false,
migrationLastUpdateAt: new Date(),
});
await organisation.save({ transaction: tx });
});
} catch (e) {
capture("error migrating", e);
organisation.set({ migrating: false });
await organisation.save();
throw e;
}
return res.status(200).send({ ok: true });
return res.status(200).send({
ok: true,
organisation: {
_id: organisation._id,
name: organisation.name,
createdAt: organisation.createdAt,
updatedAt: organisation.updatedAt,
categories: organisation.categories,
encryptionEnabled: organisation.encryptionEnabled,
encryptionLastUpdateAt: organisation.encryptionLastUpdateAt,
receptionEnabled: organisation.receptionEnabled,
services: organisation.services,
collaborations: organisation.collaborations,
customFieldsObs: organisation.customFieldsObs,
encryptedVerificationKey: organisation.encryptedVerificationKey,
customFieldsPersonsSocial: organisation.customFieldsPersonsSocial,
customFieldsPersonsMedical: organisation.customFieldsPersonsMedical,
migrations: organisation.migrations,
migrationLastUpdateAt: organisation.migrationLastUpdateAt,
},
});
})
);

Expand Down
3 changes: 3 additions & 0 deletions api/src/controllers/organisation.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const Person = require("../models/person");
const Territory = require("../models/territory");
const Report = require("../models/report");
const Comment = require("../models/comment");
const Passage = require("../models/passage");
const mailservice = require("../utils/mailservice");
const validateUser = require("../middleware/validateUser");
const { looseUuidRegex } = require("../utils");
Expand Down Expand Up @@ -99,6 +100,7 @@ router.get(
const territories = (await Territory.findAll(countQuery)).map((item) => item.toJSON());
const reports = (await Report.findAll(countQuery)).map((item) => item.toJSON());
const comments = (await Comment.findAll(countQuery)).map((item) => item.toJSON());
const passages = (await Passage.findAll(countQuery)).map((item) => item.toJSON());

return res.status(200).send({
ok: true,
Expand All @@ -113,6 +115,7 @@ router.get(
: 0,
reports: reports.find((r) => r.organisation === org._id) ? Number(reports.find((r) => r.organisation === org._id).countByOrg) : 0,
comments: comments.find((r) => r.organisation === org._id) ? Number(comments.find((r) => r.organisation === org._id).countByOrg) : 0,
passages: passages.find((r) => r.organisation === org._id) ? Number(passages.find((r) => r.organisation === org._id).countByOrg) : 0,
};
return {
...org,
Expand Down
150 changes: 150 additions & 0 deletions api/src/controllers/passage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
const express = require("express");
const router = express.Router();
const passport = require("passport");
const { Op } = require("sequelize");
const { z } = require("zod");
const { catchErrors } = require("../errors");
const Passage = require("../models/passage");
const validateEncryptionAndMigrations = require("../middleware/validateEncryptionAndMigrations");
const validateUser = require("../middleware/validateUser");
const { looseUuidRegex, positiveIntegerRegex } = require("../utils");

router.post(
"/",
passport.authenticate("user", { session: false }),
validateUser(["admin", "normal"]),
validateEncryptionAndMigrations,
catchErrors(async (req, res, next) => {
try {
z.string().parse(req.body.encrypted);
z.string().parse(req.body.encryptedEntityKey);
} catch (e) {
const error = new Error(`Invalid request in passage creation: ${e}`);
error.status = 400;
return next(error);
}

const data = await Passage.create(
{
organisation: req.user.organisation,
encrypted: req.body.encrypted,
encryptedEntityKey: req.body.encryptedEntityKey,
},
{ returning: true }
);

return res.status(200).send({
ok: true,
data: {
_id: data._id,
encrypted: data.encrypted,
encryptedEntityKey: data.encryptedEntityKey,
organisation: data.organisation,
createdAt: data.createdAt,
updatedAt: data.updatedAt,
},
});
})
);

router.get(
"/",
passport.authenticate("user", { session: false }),
validateUser(["admin", "normal"]),
catchErrors(async (req, res, next) => {
try {
z.optional(z.string().regex(positiveIntegerRegex)).parse(req.query.limit);
z.optional(z.string().regex(positiveIntegerRegex)).parse(req.query.page);
z.optional(z.string().regex(positiveIntegerRegex)).parse(req.query.lastRefresh);
} catch (e) {
const error = new Error(`Invalid request in passage get: ${e}`);
error.status = 400;
return next(error);
}
const { limit, page, lastRefresh } = req.query;

const query = {
where: { organisation: req.user.organisation },
order: [["createdAt", "DESC"]],
};

const total = await Passage.count(query);
if (limit) query.limit = Number(limit);
if (page) query.offset = Number(page) * limit;
if (lastRefresh) query.where.updatedAt = { [Op.gte]: new Date(Number(lastRefresh)) };

const data = await Passage.findAll({
...query,
attributes: ["_id", "encrypted", "encryptedEntityKey", "organisation", "createdAt", "updatedAt"],
});
return res.status(200).send({ ok: true, data, hasMore: data.length === Number(limit), total });
})
);

router.put(
"/:_id",
passport.authenticate("user", { session: false }),
validateUser(["admin", "normal"]),
validateEncryptionAndMigrations,
catchErrors(async (req, res, next) => {
try {
z.string().regex(looseUuidRegex).parse(req.params._id);
if (req.body.createdAt) z.preprocess((input) => new Date(input), z.date()).parse(req.body.createdAt);
z.string().parse(req.body.encrypted);
z.string().parse(req.body.encryptedEntityKey);
} catch (e) {
const error = new Error(`Invalid request in passage put: ${e}`);
error.status = 400;
return next(error);
}
const query = { where: { _id: req.params._id, organisation: req.user.organisation } };
const passage = await Passage.findOne(query);
if (!passage) return res.status(404).send({ ok: false, error: "Not Found" });

const { encrypted, encryptedEntityKey } = req.body;

const updatePassage = {
encrypted: encrypted,
encryptedEntityKey: encryptedEntityKey,
};

await Passage.update(updatePassage, query, { silent: false });
const newPassage = await Passage.findOne(query);

return res.status(200).send({
ok: true,
data: {
_id: newPassage._id,
encrypted: newPassage.encrypted,
encryptedEntityKey: newPassage.encryptedEntityKey,
organisation: newPassage.organisation,
createdAt: newPassage.createdAt,
updatedAt: newPassage.updatedAt,
},
});
})
);

router.delete(
"/:_id",
passport.authenticate("user", { session: false }),
validateUser(["admin", "normal"]),
catchErrors(async (req, res, next) => {
try {
z.string().regex(looseUuidRegex).parse(req.params._id);
} catch (e) {
const error = new Error(`Invalid request in passage delete: ${e}`);
error.status = 400;
return next(error);
}
const query = { where: { _id: req.params._id, organisation: req.user.organisation } };

const passage = await Passage.findOne(query);
if (!passage) return res.status(200).send({ ok: true });

await passage.destroy();
res.status(200).send({ ok: true });
})
);

module.exports = router;
Loading

0 comments on commit 32b940e

Please sign in to comment.