Refactor event middleware and db code to use new tables

This commit is contained in:
D. Berge
2022-02-27 18:13:25 +01:00
parent d0da1b005b
commit 950582a5c6
14 changed files with 155 additions and 448 deletions

View File

@@ -144,12 +144,11 @@ app.map({
'-/:sequence/': { // NOTE: We need to avoid conflict with the next endpoint ☹
get: [ mw.event.sequence.get ],
},
':type/': {
':id/': {
// get: [ mw.event.get ],
put: [ mw.auth.access.write, mw.event.put ],
delete: [mw.auth.access.write, mw.event.delete ]
}
':id/': {
get: [ mw.event.get ],
put: [ mw.auth.access.write, mw.event.put ],
patch: [ mw.auth.access.write, mw.event.patch ],
delete: [mw.auth.access.write, mw.event.delete ]
},
},
'/project/:project/label/': {

View File

@@ -4,26 +4,7 @@ const { event } = require('../../../lib/db');
module.exports = async function (req, res, next) {
try {
const payload = Object.assign({}, req.body);
if (req.params.type && req.params.id) {
payload.type = req.params.type;
payload.id = req.params.id;
}
if (req.params.labels) {
payload.labels = req.params.labels.split(";");
}
if (!req.meta.isLabel) {
// User is requesting that we delete the whole event,
// not just labels
// FIXME NOTE Removal of labels would be best done via
// a PUT request.
delete payload.labels
}
await event.del(req.params.project, payload, req.query);
await event.del(req.params.project, req.params.id);
res.status(204).send();
next();
} catch (err) {

View File

@@ -0,0 +1,14 @@
const { event } = require('../../../../lib/db');
const json = async function (req, res, next) {
try {
const response = await event.get(req.params.project, req.params.id);
res.status(200).send(response);
next();
} catch (err) {
next(err);
}
};
module.exports = json;

View File

@@ -1,9 +1,11 @@
module.exports = {
list: require('./list'),
sequence: require('./sequence'),
get: require('./get'),
post: require('./post'),
put: require('./put'),
patch: require('./patch'),
delete: require('./delete'),
cache: require('./cache')
}

View File

@@ -0,0 +1,16 @@
const { event } = require('../../../lib/db');
module.exports = async function (req, res, next) {
try {
const payload = req.body;
await event.patch(req.params.project, req.params.id, payload, req.query);
res.status(201).send();
next();
} catch (err) {
next(err);
}
};

View File

@@ -6,27 +6,6 @@ module.exports = async function (req, res, next) {
try {
const payload = req.body;
if (req.params.type) {
payload.type = req.params.type;
}
if (payload.type == "timed") {
if (!payload.tstamp) {
payload.tstamp = (new Date).toISOString();
}
delete payload.sequence;
delete payload.point;
} else if (payload.type == "sequence") {
delete payload.tstamp;
}
if (req.params.tstamp) {
payload.tstamp = req.params.tstamp;
} else if (req.params.sequence && req.params.shot) {
payload.sequence = req.params.sequence;
payload.point = req.params.shot;
}
await event.post(req.params.project, payload, req.query);
res.status(201).send();
next();

View File

@@ -6,28 +6,7 @@ module.exports = async function (req, res, next) {
try {
const payload = req.body;
if (req.params.type) {
payload.type = req.params.type;
}
if (payload.type == "timed") {
if (!payload.tstamp) {
payload.tstamp = (new Date).toISOString();
}
delete payload.sequence;
delete payload.point;
} else if (payload.type == "sequence") {
delete payload.tstamp;
}
if (req.params.tstamp) {
payload.tstamp = req.params.tstamp;
} else if (req.params.sequence && req.params.shot) {
payload.sequence = req.params.sequence;
payload.point = req.params.shot;
}
await event.put(req.params.project, payload, req.query);
await event.put(req.params.project, req.params.id, payload, req.query);
res.status(201).send();
next();
} catch (err) {

View File

@@ -1,127 +1,32 @@
const { setSurvey, transaction } = require('../connection');
async function deleteTimedEventLabel (label, eventId, client) {
const text = `
DELETE
FROM events_timed_labels
WHERE label = $1 AND id = $2;
`;
// console.log("deleteTimedEventLabel", label, eventId);
return await client.query(text, [label, eventId]);
}
async function deleteSeqLabel (label, eventId, client) {
const text = `
DELETE
FROM events_seq_labels
WHERE label = $1 AND id = $2;
`;
// console.log("deleteSeqLabel", label, eventId);
return await client.query(text, [label, eventId]);
}
async function deleteTimedEvent (eventId, client) {
const text = `
DELETE
FROM events_timed
WHERE id = $1;
`;
// console.log("deleteTimedEvent", eventId);
return await client.query(text, [eventId]);
}
async function deleteSeqEvent (eventId, client) {
const text = `
DELETE
FROM events_seq
WHERE id = $1;
`;
// console.log("deleteSeqEvent", eventId);
return await client.query(text, [eventId]);
}
/**
* Delete events from the database.
*
* Events may have the following forms:
*
* Delete event associated with a time:
*
* {
* type: "timed",
* id: …,
* }
*
* Delete the listed labels associated with a
* time event, but not the event itself.
*
* {
* type: "timed",
* id: …,
* labels: [ "…", "…", … ]
* }
*
* Delete event associated with a shotpoint:
*
* {
* type: "sequence",
* id: …,
* }
*
* Delete only the labels, not the event itself:
*
* {
* type: "sequence",
* id: …,
* labels: [ "…", "…", … ]
* }
*
* Delete an event from the database.
*/
async function del (projectId, payload, opts = {}) {
// console.log("delete event", projectId, payload);
async function del (projectId, eventId) {
if (!projectId || !eventId) {
throw {status: 400, message: "Invalid request" };
return;
}
const client = await setSurvey(projectId);
await transaction.begin(client);
try {
if (!Array.isArray(payload)) {
payload = [payload];
}
// console.log("Payload", payload);
for (const event of payload) {
// console.log("Event", event);
if (event.type && event.id) {
const eventId = event.id;
if (event.labels) {
const handler = event.type == "timed"
? deleteTimedEventLabel
: deleteSeqLabel;
const text = `
DELETE
FROM event_log
WHERE id = $1;
`;
for (const label of event.labels) {
await handler(label, eventId, client);
}
} else {
if (event.type == "timed") {
await deleteTimedEvent(eventId, client);
} else {
await deleteSeqEvent(eventId, client);
}
}
} else {
throw { status: 400, message: "Unrecognised event kind" };
}
}
transaction.commit(client);
await client.query(text, [eventId]);
} catch (err) {
transaction.rollback(client)
throw err;
} finally {
client.release();
}
return;
}
module.exports = del;

View File

@@ -0,0 +1,36 @@
const { setSurvey } = require('../connection');
const { replaceMarkers } = require('../../utils');
function parseValidity (row) {
if (row.validity) {
const rx = /^(.)("([\d :.+-]+)")?,("([\d :.+-]+)")?([\]\)])$/;
const m = row.validity.match(rx);
row.validity = [ m[1], m[3], m[5], m[6] ];
}
return row;
}
/**
* Get an event from the database, with full history.
*/
async function get (projectId, eventId) {
if (!projectId || !eventId) {
throw {status: 400, message: "Invalid request" };
return;
}
const client = await setSurvey(projectId);
const text = `
SELECT *
FROM event_log_full
WHERE id = $1;
`;
const res = await client.query(text, [eventId]);
client.release();
return res.rows.map(i => replaceMarkers(i) && parseValidity(i));
}
module.exports = get;

View File

@@ -4,5 +4,6 @@ module.exports = {
get: require('./get'),
post: require('./post'),
put: require('./put'),
patch: require('./patch'),
del: require('./delete')
}

View File

@@ -1,21 +1,5 @@
const { setSurvey } = require('../connection');
const { geometryAsString } = require('../../utils');
function replaceMarkers (item, opts={}) {
const textkey = opts.text || "remarks";
const text = item[textkey];
if (text && typeof text === "string") {
item[textkey] = text
.replace(/@POS(ITION)?@/g, geometryAsString(item, opts) || "(position unknown)")
.replace(/@DMS@/g, geometryAsString(item, {...opts, dms:true}) || "(position unknown)")
}
return item;
}
const { replaceMarkers } = require('../../utils');
async function list (projectId, opts = {}) {
const client = await setSurvey(projectId);
@@ -38,7 +22,7 @@ async function list (projectId, opts = {}) {
const text = `
SELECT *
FROM events e
FROM event_log e
WHERE
${filter[0]}
ORDER BY ${sortKey} ${sortDir};

View File

@@ -0,0 +1,34 @@
const { setSurvey, transaction } = require('../connection');
async function patch (projectId, eventId, payload, opts = {}) {
const p = payload; // Shorter
const client = await setSurvey(projectId);
try {
// The order of attributes in an object is not defined, so
// in theory we could get a different order if we made separate
// calls to Object.keys() and Object.values(), unlikely as that
// might be.
let k = [];
let v = [];
Object.entries(p).forEach(i => { k.push(i[0]); v.push(i[1]); });
const text = `
UPDATE event_log
SET
${k.map((k, i) => `${k} = $${i+2}`).join(",\n")}
WHERE id = $1;
`;
const values = [ eventId, ...v ];
await client.query(text, values);
} catch (err) {
throw err;
} finally {
client.release();
}
return;
}
module.exports = patch;

View File

@@ -1,137 +1,26 @@
const { setSurvey, transaction } = require('../connection');
async function insertSequenceEvent(event, client) {
const text = `
INSERT INTO events_seq (remarks, point, sequence)
VALUES ($1, $2, $3)
RETURNING id;
`;
try {
if (!("remarks" in event) || event.remarks === null) {
event.remarks = "";
}
const res = await client.query(text, [event.remarks, event.point, event.sequence]);
event.type = "sequence";
event.id = res.rows[0].id;
} catch (err) {
throw err;
}
return event;
}
async function insertSequenceEventLabels(event, client) {
if (event.type && event.type != "sequence") {
return;
}
const text = `
INSERT INTO events_seq_labels (id, label)
SELECT $1, name
FROM unnest($2::text[]) l (name)
INNER JOIN labels USING (name)
WHERE (data->'model'->'user')::boolean IS true
ON CONFLICT ON CONSTRAINT events_seq_labels_pkey DO NOTHING;
`;
// console.log("insertSequenceEventLabels", text, event);
return await client.query(text, [event.id, event.labels]);
}
async function insertTimedEvent(event, client) {
const text = `
INSERT INTO events_timed (remarks, tstamp)
VALUES ($1, $2)
RETURNING id;
`;
try {
const res = await client.query(text, [event.remarks, event.tstamp]);
event.type = "timed";
event.id = res.rows[0].id;
} catch (err) {
throw err;
}
return event;
}
async function insertTimedEventLabels(event, client) {
if (event.type && event.type != "timed") {
return;
}
const text = `
INSERT INTO events_timed_labels (id, label)
SELECT $1, name
FROM unnest($2::text[]) l (name)
INNER JOIN labels USING (name)
WHERE (data->'model'->'user')::boolean IS true
`;
// console.log("insertTimedEventLabels", text, event);
return await client.query(text, [event.id, event.labels]);
}
/**
* Inserts events into the database.
*
* Events may have the following forms:
*
* Event associated with a time:
*
* {
* remarks: "…",
* tstamp: "…",
* labels: [ "…", "…", … ]
* }
*
* Events associated with a shotpoint:
*
* {
* remarks: "…",
* shotNumber: 0000,
* sequence: 000,
* labels: [ "…", "…", … ]
* }
*
* In both cases, either remarks or labels may be omitted.
*/
async function post (projectId, payload, opts = {}) {
// console.log("post event", projectId, payload);
const p = payload; // Shorter
const client = await setSurvey(projectId);
await transaction.begin(client);
try {
if (!Array.isArray(payload)) {
payload = [payload];
}
// console.log("Payload", payload);
for (const event of payload) {
// console.log("Event", event);
if (event.sequence && event.point) {
// A shot event
// console.log("Shot event");
await insertSequenceEvent(event, client);
await insertSequenceEventLabels(event, client);
} else if (event.tstamp) {
// A timed event
const text = `
INSERT
INTO event_log (tstamp, sequence, point, remarks, labels)
VALUES ($1, $2, $3, $4, $5);
`;
const values = [ p.tstamp, p.sequence, p.point, p.remarks, p.labels ];
// console.log("Timed event");
await insertTimedEvent(event, client);
await insertTimedEventLabels(event, client);
}
}
transaction.commit(client);
await client.query(text, values);
} catch (err) {
transaction.rollback(client)
throw err;
} finally {
client.release();
}
return;
}
module.exports = post;

View File

@@ -1,143 +1,31 @@
const { setSurvey, transaction } = require('../connection');
async function updateTimedEvent (event, client) {
const text = `
UPDATE events_timed
SET
remarks = COALESCE($2, remarks),
tstamp = COALESCE($3, tstamp)
WHERE id = $1;
`
return await client.query(text, [event.id, event.remarks, event.tstamp]);
}
async function updateSeqEvent (event, client) {
const text = `
UPDATE events_seq
SET
remarks = COALESCE($2, remarks),
sequence = COALESCE($3, sequence),
point = COALESCE($4, point)
WHERE id = $1;
`
return await client.query(text, [event.id, event.remarks, event.sequence, event.point]);
}
async function updateTimedEventLabels (event, client) {
if (!event.labels) {
return
}
await client.query("DELETE FROM events_timed_labels WHERE id = $1;", [event.id]);
const text = `
INSERT INTO events_timed_labels (id, label)
SELECT $1, label FROM unnest($2::text[]) t (label);
`;
return client.query(text, [event.id, event.labels]);
}
async function updateSeqEventLabels (event, client) {
if (!event.labels) {
return
}
await client.query("DELETE FROM events_seq_labels WHERE id = $1;", [event.id]);
const text = `
INSERT INTO events_seq_labels (id, label)
SELECT $1, label FROM unnest($2::text[]) t (label)
ON CONFLICT ON CONSTRAINT events_seq_labels_pkey DO NOTHING;
`;
return client.query(text, [event.id, event.labels]);
}
/**
* Updates events in the database.
*
* Events may have the following forms:
*
* Event associated with a time:
*
* {
* type: "timed",
* id: 0,
* remarks: "…",
* tstamp: "…",
* labels: [ "…", "…", … ]
* }
*
* Events associated with a sequence / shotpoint:
*
* {
* type: "seq"
* remarks: "…",
* point: 0000,
* sequence: 000,
* labels: [ "…", "…", … ]
* }
*
* In both cases, either remarks or labels may be omitted.
*
* Labels associated with a sequence / shotpoint (without event):
*
* {
* type: "shot_labels",
* point: 0000,
* sequence: 000,
* labels: [ "…", "…", … ] // Sets these labels
* }
*
* {
* type: "shot_labels",
* point: 0000,
* sequence: 000,
* labels: {
* add: […], // Adds these labels
* remove: […] // Removes these labels
* }
* }
*
*/
async function put (projectId, payload, opts = {}) {
// console.log("put event", projectId, payload);
async function put (projectId, eventId, payload, opts = {}) {
const p = payload; // Shorter
const client = await setSurvey(projectId);
await transaction.begin(client);
try {
if (!Array.isArray(payload)) {
payload = [payload];
}
for (const event of payload) {
// console.log("Event", event);
const text = `
UPDATE event_log
SET
tstamp = $1,
sequence = $2,
point = $3,
remarks = $4,
labels = $5
WHERE id = $6;
`;
const values = [ p.tstamp, p.sequence, p.point, p.remarks, p.labels, eventId ];
switch (event.type) {
case "timed":
await updateTimedEvent(event, client);
await updateTimedEventLabels(event, client)
break;
case "sequence":
await updateSeqEvent(event, client);
await updateSeqEventLabels(event, client);
break;
default:
// Error?
}
}
transaction.commit(client);
await client.query(text, values);
} catch (err) {
transaction.rollback(client)
throw err;
} finally {
client.release();
}
return;
}
module.exports = put;