mirror of
https://gitlab.com/wgp/dougal/software.git
synced 2025-12-06 07:17:07 +00:00
Compare commits
14 Commits
v2
...
175-add-da
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36e7b1fe21 | ||
|
|
e7fa74326d | ||
|
|
83be83e4bd | ||
|
|
81ce6346b9 | ||
|
|
923ff1acea | ||
|
|
8ec479805a | ||
|
|
f10103d396 | ||
|
|
774bde7c00 | ||
|
|
b4569c14df | ||
|
|
54eea62e4a | ||
|
|
69c4f2dd9e | ||
|
|
acc829b978 | ||
|
|
ff4913c0a5 | ||
|
|
51452c978a |
@@ -114,6 +114,9 @@ run $BINDIR/human_exports_qc.py
|
||||
print_log "Export sequence data"
|
||||
run $BINDIR/human_exports_seis.py
|
||||
|
||||
print_log "Process ASAQC queue"
|
||||
run $DOUGAL_ROOT/lib/www/server/queues/asaqc/index.js
|
||||
|
||||
|
||||
rm "$LOCKFILE"
|
||||
print_info "End run"
|
||||
|
||||
@@ -35,7 +35,7 @@ imports:
|
||||
queues:
|
||||
asaqc:
|
||||
request:
|
||||
url: "https://localhost:3077/vt/v1/api/upload-file-encoded"
|
||||
url: "https://api.gateway.equinor.com/vt/v1/api/upload-file-encoded"
|
||||
args:
|
||||
method: POST
|
||||
headers:
|
||||
|
||||
@@ -19,3 +19,79 @@ Created with:
|
||||
```bash
|
||||
SCHEMA_NAME=survey_X EPSG_CODE=XXXXX $DOUGAL_ROOT/sbin/dump_schema.sh
|
||||
```
|
||||
|
||||
## To create a new Dougal database
|
||||
|
||||
Ensure that the following packages are installed:
|
||||
|
||||
* `postgresql*-postgis-utils`
|
||||
* `postgresql*-postgis`
|
||||
* `postgresql*-contrib` # For B-trees
|
||||
|
||||
```bash
|
||||
psql -U postgres <./database-template.sql
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Upgrading PostgreSQL
|
||||
|
||||
The following is based on https://en.opensuse.org/SDB:PostgreSQL#Upgrading_major_PostgreSQL_version
|
||||
|
||||
```bash
|
||||
# The following bash code should be checked and executed
|
||||
# line for line whenever you do an upgrade. The example
|
||||
# shows the upgrade process from an original installation
|
||||
# of version 12 up to version 14.
|
||||
|
||||
# install the new server as well as the required postgresql-contrib packages:
|
||||
zypper in postgresql14-server postgresql14-contrib postgresql12-contrib
|
||||
|
||||
# If not yet done, copy the configuration create a new PostgreSQL configuration directory...
|
||||
mkdir /etc/postgresql
|
||||
# and copy the original file to this global directory
|
||||
cd /srv/pgsql/data
|
||||
for i in pg_hba.conf pg_ident.conf postgresql.conf postgresql.auto.conf ; do cp -a $i /etc/postgresql/$i ; done
|
||||
|
||||
# Now create a new data-directory and initialize it for usage with the new server
|
||||
install -d -m 0700 -o postgres -g postgres /srv/pgsql/data14
|
||||
cd /srv/pgsql/data14
|
||||
sudo -u postgres /usr/lib/postgresql14/bin/initdb .
|
||||
|
||||
# replace the newly generated files by a symlink to the global files.
|
||||
# After doing so, you may check the difference of the created backup files and
|
||||
# the files from the former installation
|
||||
for i in pg_hba.conf pg_ident.conf postgresql.conf postgresql.auto.conf ; do old $i ; ln -s /etc/postgresql/$i .; done
|
||||
|
||||
# Copy over special thesaurus files if some exists.
|
||||
#cp -a /usr/share/postgresql12/tsearch_data/my_thesaurus_german.ths /usr/share/postgresql14/tsearch_data/
|
||||
|
||||
# Now it's time to disable the service...
|
||||
systemctl stop postgresql.service
|
||||
|
||||
# And to start the migration. Please ensure, the directories fit to your upgrade path
|
||||
sudo -u postgres /usr/lib/postgresql14/bin/pg_upgrade --link \
|
||||
--old-bindir="/usr/lib/postgresql12/bin" \
|
||||
--new-bindir="/usr/lib/postgresql14/bin" \
|
||||
--old-datadir="/srv/pgsql/data/" \
|
||||
--new-datadir="/srv/pgsql/data14/"
|
||||
|
||||
# After successfully migrating the data...
|
||||
cd ..
|
||||
# if not already symlinked move the old data to a versioned directory matching
|
||||
# your old installation...
|
||||
mv data data12
|
||||
# and set a symlink to the new data directory
|
||||
ln -sf data14/ data
|
||||
|
||||
# Now start the new service
|
||||
systemctl start postgresql.service
|
||||
|
||||
# If everything has been sucessful, you should uninstall old packages...
|
||||
#zypper rm -u postgresql12 postgresql13
|
||||
# and remove old data directories
|
||||
#rm -rf /srv/pgsql/data_OLD_POSTGRES_VERSION_NUMBER
|
||||
|
||||
# For good measure:
|
||||
sudo -u postgres /usr/lib/postgresql14/bin/vacuumdb --all --analyze-in-stages
|
||||
```
|
||||
|
||||
34
etc/db/upgrades/README.md
Normal file
34
etc/db/upgrades/README.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Database schema upgrades
|
||||
|
||||
When the database schema needs to be upgraded in order to provide new functionality, fix errors, etc., an upgrade script should be added to this directory.
|
||||
|
||||
The script can be SQL (preferred) or anything else (Bash, Python, …) in the event of complex upgrades.
|
||||
|
||||
The script itself should:
|
||||
|
||||
* document what the intended changes are;
|
||||
* contain instructions on how to run it;
|
||||
* make the user aware of any non-obvious side effects; and
|
||||
* say if it is safe to run the script multiple times on the
|
||||
* same schema / database.
|
||||
|
||||
## Naming
|
||||
|
||||
Script files should be named `upgrade-<index>-<commit-id-old>-<commit-id-new>-v<schema-version>.sql`, where:
|
||||
|
||||
* `<index>` is a correlative two-digit index. When reaching 99, existing files will be renamed to a three digit index (001-099) and new files will use three digits.
|
||||
* `<commit-id-old>` is the ID of the Git commit that last introduced a schema change.
|
||||
* `<commit-id-new>` is the ID of the first Git commit expecting the updated schema.
|
||||
* `<schema-version>` is the version of the schema.
|
||||
|
||||
Note: the `<schema-version>` value should be updated with every change and it should be the same as reported by:
|
||||
|
||||
```sql
|
||||
select value->>'db_schema' as db_schema from public.info where key = 'version';
|
||||
```
|
||||
|
||||
If necessary, the wanted schema version must also be updated in `package.json`.
|
||||
|
||||
## Running
|
||||
|
||||
Schema upgrades are always run manually.
|
||||
24
etc/db/upgrades/upgrade09-74b3de5c→83be83e4-v0.1.0.sql
Normal file
24
etc/db/upgrades/upgrade09-74b3de5c→83be83e4-v0.1.0.sql
Normal file
@@ -0,0 +1,24 @@
|
||||
-- Upgrade the database from commit 74b3de5c to commit 83be83e4.
|
||||
--
|
||||
-- NOTE: This upgrade only affects the `public` schema.
|
||||
--
|
||||
-- This inserts a database schema version into the database.
|
||||
-- Note that we are not otherwise changing the schema, so older
|
||||
-- server code will continue to run against this version.
|
||||
--
|
||||
-- ATTENTION!
|
||||
--
|
||||
-- This value should be incremented every time that the database
|
||||
-- schema changes (either `public` or any of the survey schemas)
|
||||
-- and is used by the server at start-up to detect if it is
|
||||
-- running against a compatible schema version.
|
||||
--
|
||||
-- To apply, run as the dougal user:
|
||||
--
|
||||
-- psql < $THIS_FILE
|
||||
--
|
||||
-- NOTE: It can be applied multiple times without ill effect.
|
||||
|
||||
INSERT INTO public.info VALUES ('version', '{"db_schema": "0.1.0"}')
|
||||
ON CONFLICT (key) DO UPDATE
|
||||
SET value = public.info.value || '{"db_schema": "0.1.0"}' WHERE public.info.key = 'version';
|
||||
@@ -4,7 +4,7 @@ const { info } = require('../../../lib/db');
|
||||
module.exports = async function (req, res, next) {
|
||||
|
||||
try {
|
||||
await info.delete(req.params.project, req.params.path);
|
||||
await info.delete(req.params.project, req.params.path, undefined, req.user.role);
|
||||
res.status(204).send();
|
||||
next();
|
||||
} catch (err) {
|
||||
|
||||
@@ -4,7 +4,7 @@ const { info } = require('../../../lib/db');
|
||||
module.exports = async function (req, res, next) {
|
||||
|
||||
try {
|
||||
res.status(200).json(await info.get(req.params.project, req.params.path, req.query));
|
||||
res.status(200).json(await info.get(req.params.project, req.params.path, req.query, req.user.role));
|
||||
} catch (err) {
|
||||
if (err instanceof TypeError) {
|
||||
res.status(404).json(null);
|
||||
|
||||
@@ -6,7 +6,7 @@ module.exports = async function (req, res, next) {
|
||||
try {
|
||||
const payload = req.body;
|
||||
|
||||
await info.post(req.params.project, req.params.path, payload);
|
||||
await info.post(req.params.project, req.params.path, payload, undefined, req.user.role);
|
||||
res.status(201).send();
|
||||
next();
|
||||
} catch (err) {
|
||||
|
||||
@@ -6,7 +6,7 @@ module.exports = async function (req, res, next) {
|
||||
try {
|
||||
const payload = req.body;
|
||||
|
||||
await info.put(req.params.project, req.params.path, payload);
|
||||
await info.put(req.params.project, req.params.path, payload, undefined, req.user.role);
|
||||
res.status(201).send();
|
||||
next();
|
||||
} catch (err) {
|
||||
|
||||
@@ -1,18 +1,28 @@
|
||||
#!/usr/bin/node
|
||||
|
||||
const api = require('./api');
|
||||
const ws = require('./ws');
|
||||
async function main () {
|
||||
// Check that we're running against the correct database version
|
||||
const version = require('./lib/version');
|
||||
console.log("Running version", await version.describe());
|
||||
version.compatible()
|
||||
.then( () => {
|
||||
const api = require('./api');
|
||||
const ws = require('./ws');
|
||||
|
||||
const { fork } = require('child_process');
|
||||
const { fork } = require('child_process');
|
||||
|
||||
// const em = require('./events');
|
||||
const server = api.start(process.env.HTTP_PORT || 3000, process.env.HTTP_PATH);
|
||||
ws.start(server);
|
||||
|
||||
const server = api.start(process.env.HTTP_PORT || 3000, process.env.HTTP_PATH);
|
||||
ws.start(server);
|
||||
const eventManagerPath = [__dirname, "events"].join("/");
|
||||
const eventManager = fork(eventManagerPath, /*{ stdio: 'ignore' }*/);
|
||||
|
||||
const eventManagerPath = [__dirname, "events"].join("/");
|
||||
const eventManager = fork(eventManagerPath, /*{ stdio: 'ignore' }*/);
|
||||
process.on('exit', () => eventManager.kill());
|
||||
})
|
||||
.catch( ({current, wanted}) => {
|
||||
console.error(`Fatal error: incompatible database schema version ${current} (wanted: ${wanted})`);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
process.on('exit', () => eventManager.kill());
|
||||
|
||||
// em.start();
|
||||
main();
|
||||
|
||||
83
lib/www/server/lib/db/info/check-permission.js
Normal file
83
lib/www/server/lib/db/info/check-permission.js
Normal file
@@ -0,0 +1,83 @@
|
||||
/** Check permission to read or write certain keys.
|
||||
*
|
||||
* The global and survey `info` tables can be used to
|
||||
* store and retrieve arbitrary data, but it is also
|
||||
* used by the software, with some keys being reserved
|
||||
* for specific purposes.
|
||||
*
|
||||
* This module lists those keys which are in some way
|
||||
* reserved and reports on who should be allowed what
|
||||
* type of access to them.
|
||||
*/
|
||||
|
||||
|
||||
/** Reserved keys.
|
||||
*
|
||||
* The structure of this dictionary is
|
||||
* object.verb.subject = Boolean.
|
||||
*
|
||||
* The special value `_` is a wildcard
|
||||
* denoting the default condition for
|
||||
* a verb or a subject.
|
||||
*/
|
||||
const dictionary = {
|
||||
version: {
|
||||
// Database or schema version string.
|
||||
// Everyone can read, nobody can alter.
|
||||
get: { _: true },
|
||||
_ : { _: false }
|
||||
},
|
||||
config: {
|
||||
// Configuration (site-wide or survey)
|
||||
// Nobody except admin can access
|
||||
_: { _: false, admin: true }
|
||||
},
|
||||
qc: {
|
||||
// QC results (survey)
|
||||
// Everyone can read, nobody can write
|
||||
get: { _: true },
|
||||
_ : { _: false }
|
||||
},
|
||||
equipment: {
|
||||
// Equipment info (site)
|
||||
// Everyone can read, user + admin can alter
|
||||
get: { _: true },
|
||||
_ : { _: false, user: true, admin: true }
|
||||
},
|
||||
contact: {
|
||||
// Contact details (basically an example entry)
|
||||
// Everyone can read, admin can alter
|
||||
get: { _: true },
|
||||
_ : { _: false, admin: true },
|
||||
}
|
||||
}
|
||||
|
||||
/** Check if access is allowed to an info entry.
|
||||
*
|
||||
* @a key {String} is the object of the action.
|
||||
* @a verb {String} is the action.
|
||||
* @a role {String} is the subject of the action.
|
||||
*
|
||||
* @returns {Boolean} `true` is the action is allowed,
|
||||
* `false` if it is not.
|
||||
*
|
||||
* By default, all actions are allowed on a key that's
|
||||
* not listed in the dictionary. For a key that is listed,
|
||||
* the result for a default action or subject is denoted
|
||||
* by `_`, others are entered explicitly.
|
||||
*
|
||||
*/
|
||||
function checkPermission (key, verb, role) {
|
||||
const entry = dictionary[key]
|
||||
if (entry) {
|
||||
const action = entry[verb] ?? entry._
|
||||
if (action) {
|
||||
return action[role] ?? action._ ?? false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
module.exports = checkPermission;
|
||||
@@ -1,9 +1,15 @@
|
||||
const { setSurvey, transaction } = require('../connection');
|
||||
const checkPermission = require('./check-permission');
|
||||
|
||||
async function del (projectId, path, opts = {}) {
|
||||
async function del (projectId, path, opts = {}, role) {
|
||||
const client = await setSurvey(projectId);
|
||||
const [key, ...jsonpath] = (path||"").split("/").filter(i => i.length);
|
||||
|
||||
if (!checkPermission(key, "delete", role)) {
|
||||
throw {status: 403, message: "Forbidden"};
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const text = jsonpath.length
|
||||
? `
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
const { setSurvey } = require('../connection');
|
||||
const checkPermission = require('./check-permission');
|
||||
|
||||
async function get (projectId, path, opts = {}) {
|
||||
async function get (projectId, path, opts = {}, role) {
|
||||
const client = await setSurvey(projectId);
|
||||
const [key, ...subkey] = path.split("/").filter(i => i.trim().length);
|
||||
|
||||
if (!checkPermission(key, "get", role)) {
|
||||
throw {status: 403, message: "Forbidden"};
|
||||
return;
|
||||
}
|
||||
|
||||
const text = `
|
||||
SELECT value
|
||||
FROM info
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
const { setSurvey, transaction } = require('../connection');
|
||||
const checkPermission = require('./check-permission');
|
||||
|
||||
async function post (projectId, path, payload, opts = {}) {
|
||||
async function post (projectId, path, payload, opts = {}, role) {
|
||||
const client = await setSurvey(projectId);
|
||||
const [key, ...jsonpath] = (path||"").split("/").filter(i => i.length);
|
||||
|
||||
if (!checkPermission(key, "post", role)) {
|
||||
throw {status: 403, message: "Forbidden"};
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const text = jsonpath.length
|
||||
? `
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
const { setSurvey, transaction } = require('../connection');
|
||||
const checkPermission = require('./check-permission');
|
||||
|
||||
async function put (projectId, path, payload, opts = {}) {
|
||||
async function put (projectId, path, payload, opts = {}, role) {
|
||||
const client = await setSurvey(projectId);
|
||||
const [key, ...jsonpath] = (path||"").split("/").filter(i => i.length);
|
||||
|
||||
if (!checkPermission(key, "put", role)) {
|
||||
throw {status: 403, message: "Forbidden"};
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const text = jsonpath.length
|
||||
? `
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
const alert = require("../../../alerts");
|
||||
const configuration = require('../../configuration');
|
||||
|
||||
async function getDistance (client, payload) {
|
||||
@@ -88,6 +89,13 @@ async function getPlanned (client) {
|
||||
|
||||
async function getLineName (client, projectId, payload) {
|
||||
// FIXME TODO Get line name script from configuration
|
||||
// Ref.: https://gitlab.com/wgp/dougal/software/-/issues/129
|
||||
|
||||
// This is to monitor #165
|
||||
// https://gitlab.com/wgp/dougal/software/-/issues/incident/165
|
||||
if (!payload?.line) {
|
||||
alert({function: "getLineName", client, projectId, payload});
|
||||
}
|
||||
|
||||
const planned = await getPlanned(client);
|
||||
const previous = await getSequencesForLine(client, payload.line);
|
||||
|
||||
68
lib/www/server/lib/version.js
Normal file
68
lib/www/server/lib/version.js
Normal file
@@ -0,0 +1,68 @@
|
||||
const semver = require("semver");
|
||||
const { exec } = require("child_process");
|
||||
const pkg = require("../package.json");
|
||||
|
||||
/** Report whether the database schema version is
|
||||
* compatible with the version required by this server.
|
||||
*
|
||||
* The current schema version is retrieved from the
|
||||
* public.info table.
|
||||
*
|
||||
* The wanted version is retrieved from package.json
|
||||
* (config.db_schema).
|
||||
*
|
||||
* @returns true if the versions are compatible,
|
||||
* false otherwise.
|
||||
*/
|
||||
function compatible () {
|
||||
const { info } = require('./db');
|
||||
return new Promise ( async (resolve, reject) => {
|
||||
const current = await info.get(null, "version/db_schema");
|
||||
const wanted = pkg.config.db_schema;
|
||||
if (semver.satisfies(current, wanted)) {
|
||||
resolve({current, wanted});
|
||||
} else {
|
||||
reject({current, wanted});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/** Return software name.
|
||||
*
|
||||
*/
|
||||
function name () {
|
||||
const pkg = require("../package.json");
|
||||
return pkg.name ?? pkg.description ?? "Unknown";
|
||||
}
|
||||
|
||||
/** Return software version, from Git if possible.
|
||||
*
|
||||
*/
|
||||
async function describe () {
|
||||
return new Promise( (resolve, reject) => {
|
||||
const cmd = `git describe || echo git+$(git describe --always);`;
|
||||
exec(cmd, {cwd: __dirname}, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
resolve(stdout.trim());
|
||||
} else {
|
||||
// Most likely not installed from Git, use the
|
||||
// version number in package.json.
|
||||
resolve(pkg.version ?? "Unknown")
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function version () {
|
||||
return pkg.version;
|
||||
}
|
||||
version.compatible = compatible;
|
||||
version.name = name;
|
||||
version.describe = describe;
|
||||
|
||||
module.exports = version;
|
||||
@@ -9,6 +9,16 @@
|
||||
},
|
||||
"author": "Aaltronav s.r.o.",
|
||||
"license": "UNLICENSED",
|
||||
"private": true,
|
||||
"config": {
|
||||
"db_schema": "^0.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"dependencies": {
|
||||
"cookie-parser": "^1.4.5",
|
||||
"express": "^4.17.1",
|
||||
|
||||
Reference in New Issue
Block a user