mirror of
https://gitlab.com/wgp/dougal/software.git
synced 2025-12-06 10:27:09 +00:00
Add database functions for project creation.
Instead of storing the project configuration in a YAML file under `etc/surveys/`, this is now stored in public.projects.meta. NOTE: as of this commit, the runner scripts (`bin/*.py`) are not aware of this change and they will keep looking for project info under `etc/surveys`. This means that projects created directly in the database will be invisible to Dougal until the runner scripts are changed accordingly.
This commit is contained in:
149
lib/www/server/lib/db/project/create.js
Normal file
149
lib/www/server/lib/db/project/create.js
Normal file
@@ -0,0 +1,149 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs').promises;
|
||||
const cfg = require('DOUGAL_ROOT/lib/config');
|
||||
const { setSurvey, pool } = require('../connection');
|
||||
const get = require('./get');
|
||||
const { INFO, DEBUG, WARNING, ERROR } = require('DOUGAL_ROOT/debug')(__filename);
|
||||
|
||||
|
||||
function checkSyntax (value, type = "project") {
|
||||
|
||||
switch (type) {
|
||||
case "project":
|
||||
var requiredFields = {
|
||||
id: "string",
|
||||
name: "string",
|
||||
epsg: "number",
|
||||
binning: function (value) { return checkSyntax (value, "binning"); }
|
||||
};
|
||||
break;
|
||||
case "binning":
|
||||
var requiredFields = {
|
||||
theta: "number",
|
||||
I_inc: "number",
|
||||
J_inc: "number",
|
||||
I_width: "number",
|
||||
J_width: "number",
|
||||
origin: function (value) { return checkSyntax (value, "origin"); }
|
||||
}
|
||||
break
|
||||
case "origin":
|
||||
var requiredFields = {
|
||||
easting: "number",
|
||||
northing: "number",
|
||||
I: "number",
|
||||
J: "number"
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return typeof type == "function"
|
||||
? type(value)
|
||||
: typeof value == type;
|
||||
}
|
||||
|
||||
// return Object.entries(requiredFields).every( ([field, test]) => {
|
||||
// return value.hasOwnProperty(field) && checkSyntax(value[field], test);
|
||||
// });
|
||||
|
||||
for (const [field, test] of Object.entries(requiredFields)) {
|
||||
if (!value.hasOwnProperty(field)) {
|
||||
return `Missing required property: ${field}`;
|
||||
}
|
||||
const res = checkSyntax(value[field], test);
|
||||
if (res !== true) {
|
||||
return res === false ? `Syntax error on "${field}"` : res;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
async function applySchemaTemplate (projectDefinition) {
|
||||
const templatePath = path.resolve(cfg.DOUGAL_ROOT, "etc/db/schema-template.sql");
|
||||
const text = await fs.readFile(templatePath, "utf-8");
|
||||
return text.replace(/_SURVEY__TEMPLATE_/g, projectDefinition.schema).replace(/_EPSG__CODE_/g, projectDefinition.epsg);
|
||||
}
|
||||
|
||||
async function surveyIds () {
|
||||
const res = await pool.query("SELECT schema FROM public.projects;");
|
||||
if (res.rows?.length) {
|
||||
return res.rows.map( s => Number(s.schema.replace(/^survey_/, "")) ).sort((a, b) => a-b);
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
async function nextSurveyId () {
|
||||
const ids = await surveyIds();
|
||||
if (ids.length) {
|
||||
return ids.pop() + 1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
async function idExists (id) {
|
||||
const surveys = await get();
|
||||
return surveys.includes(s => s.pid.toLowerCase() == id.toLowerCase());
|
||||
}
|
||||
|
||||
async function createProjectSchema (projectDefinition) {
|
||||
const sql = await applySchemaTemplate(projectDefinition);
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
await client.query(sql);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
client.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
async function addProjectToList (projectDefinition) {
|
||||
const sql = `
|
||||
INSERT INTO public.projects (pid, name, schema, meta) VALUES (LOWER($1), $2, $3, $4);
|
||||
`;
|
||||
const values = [ projectDefinition.id, projectDefinition.name, projectDefinition.schema, projectDefinition ];
|
||||
try {
|
||||
return await pool.query(sql, values);
|
||||
} catch (err) {
|
||||
if (err.code == "23505") {
|
||||
if (err.constraint == "projects_name_key") {
|
||||
throw { message: "A project with this name already exists" }
|
||||
}
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function create (projectDefinition) {
|
||||
|
||||
const syntaxOk = checkSyntax(projectDefinition);
|
||||
if (syntaxOk !== true) {
|
||||
throw { status: 400, message: syntaxOk };
|
||||
} else if (await idExists(projectDefinition.id)) {
|
||||
throw { status: 409 }
|
||||
} else {
|
||||
try {
|
||||
console.log("create");
|
||||
const survey_id = await nextSurveyId();
|
||||
console.log("survey id", survey_id);
|
||||
projectDefinition.schema = `survey_${survey_id}`;
|
||||
projectDefinition.archived = projectDefinition.archived ?? false;
|
||||
await createProjectSchema(projectDefinition)
|
||||
await addProjectToList(projectDefinition);
|
||||
} catch (err) {
|
||||
DEBUG(err);
|
||||
throw { status: 500, message: err.message ?? "Failed to create database for new project", detail: err.detail }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
checkSyntax,
|
||||
create
|
||||
};
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
const { setSurvey, pool } = require('../connection');
|
||||
const { create } = require('./create');
|
||||
|
||||
/*
|
||||
* Creating a new project consists of these steps:
|
||||
*
|
||||
* - Check that the payload is well formed and includes all required items.
|
||||
* If not, return 400.
|
||||
* - Check if the id already exists. If it does, return 409.
|
||||
* - Figure out what the next schema name is going to be (survey_XXX).
|
||||
* - Read the SQL template from $DOUGAL_ROOT/etc/db/schema-template.sql and
|
||||
* replace the `_SURVEY__TEMPLATE_` and `_EPSG__CODE_` placeholders with
|
||||
* appropriate values.
|
||||
* - Apply the resulting SQL.
|
||||
* - Add the appropriate entry into public.projects
|
||||
* - Add the survey definition details (the request's payload) into the
|
||||
* database (or create a YAML file under $DOUGAL_ROOT/etc/surveys?)
|
||||
* - Return a 201 with the survey definition as the payload.
|
||||
*/
|
||||
|
||||
async function post (payload) {
|
||||
try {
|
||||
return await create(payload);
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = post;
|
||||
|
||||
Reference in New Issue
Block a user