559 lines
16 KiB
JavaScript
559 lines
16 KiB
JavaScript
import pkg from '@jlinc/core';
|
|
const { JlincAgreement, JlincAudit } = pkg;
|
|
import { getPool } from "../../../db/index.js";
|
|
import { entity } from "./entity.js";
|
|
import { putQueue } from "../../../common/queue.js";
|
|
import { cacheAgreementMarkdown } from "../../../util/cacheAgreement.js";
|
|
|
|
async function getAgreement(client, userId, id, key) {
|
|
const whereClause = id ? `a.agreement_id_uuid = $1` : `a.agreement_id_uuid = (SELECT agreement_uuid FROM agreement_content WHERE lower(key) = lower($1))`;
|
|
const whereValue = id ? id : key;
|
|
const sql = `
|
|
SELECT
|
|
JSON_BUILD_OBJECT(
|
|
'version', a.version,
|
|
'@context', a.context,
|
|
'parent', a.parent,
|
|
'references', (
|
|
SELECT JSON_AGG(r.uri ORDER BY r.uri)
|
|
FROM reference r
|
|
INNER JOIN agreement_reference ar
|
|
ON r.id = ar.reference_id
|
|
AND ar.agreement_id = a.id
|
|
AND (
|
|
(r.user_id = a.user_id AND ar.user_id = a.user_id)
|
|
OR
|
|
(r.user_id IS NULL AND ar.user_id IS NULL AND a.user_id IS NULL)
|
|
)
|
|
),
|
|
'agreementId', a.agreement_id_uuid,
|
|
'created', a.created,
|
|
'ids', (
|
|
SELECT JSON_AGG(ari.required_id)
|
|
FROM agreement_required_id ari
|
|
WHERE ari.agreement_id = a.id
|
|
),
|
|
'purposes', (
|
|
SELECT JSON_AGG(p.value ORDER BY p.value)
|
|
FROM purpose p
|
|
INNER JOIN agreement_purpose ap
|
|
ON p.id = ap.purpose_id
|
|
AND ap.agreement_id = a.id
|
|
AND (
|
|
(p.user_id = a.user_id AND ap.user_id = a.user_id)
|
|
OR
|
|
(p.user_id IS NULL AND ap.user_id IS NULL AND a.user_id IS NULL)
|
|
)
|
|
),
|
|
'prohibitions', (
|
|
SELECT JSON_AGG(c.value ORDER BY c.value)
|
|
FROM prohibition c
|
|
INNER JOIN agreement_prohibition ac
|
|
ON c.id = ac.prohibition_id
|
|
AND ac.agreement_id = a.id
|
|
AND (
|
|
(c.user_id = ac.user_id AND ac.user_id = a.user_id)
|
|
OR
|
|
(c.user_id IS NULL AND ac.user_id IS NULL AND a.user_id IS NULL)
|
|
)
|
|
),
|
|
'validRoles', (
|
|
SELECT JSON_AGG(r.value ORDER BY r.value)
|
|
FROM role r
|
|
INNER JOIN agreement_role ar
|
|
ON r.id = ar.role_id
|
|
AND ar.agreement_id = a.id
|
|
AND (
|
|
(r.user_id = ar.user_id AND ar.user_id = a.user_id)
|
|
OR
|
|
(r.user_id IS NULL AND ar.user_id IS NULL AND a.user_id IS NULL)
|
|
)
|
|
)
|
|
) AS record
|
|
FROM agreement a
|
|
WHERE ${whereClause}
|
|
AND (
|
|
(
|
|
a.user_id = $2
|
|
OR a.user_id IS NULL
|
|
)
|
|
OR a.public IS TRUE
|
|
)
|
|
`
|
|
const res = await client.query(sql, [
|
|
whereValue,
|
|
userId,
|
|
]);
|
|
if (res.rows.length > 0 && res.rows[0].record) {
|
|
const ret = res.rows[0].record;
|
|
if (!ret.ids) ret.ids = [];
|
|
if (!ret.purposes) ret.purposes = [];
|
|
if (!ret.prohibitions) ret.prohibitions = [];
|
|
if (!ret.validRoles) ret.validRoles = [];
|
|
if (ret.version == 1) {
|
|
delete ret.references;
|
|
} else {
|
|
delete ret["@context"];
|
|
delete ret.parent;
|
|
ret.permitted = ret.purposes;
|
|
delete ret.purposes;
|
|
ret.prohibited = ret.prohibitions;
|
|
delete ret.prohibitions;
|
|
}
|
|
return ret;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
async function getSignatures(client, userId, id) {
|
|
const sql = `
|
|
SELECT
|
|
JSON_AGG(
|
|
JSON_BUILD_OBJECT(
|
|
'version', s.version,
|
|
'id', s.signer_id,
|
|
'signedOn', s.signed_on,
|
|
'type', s.type,
|
|
'jws', s.jws,
|
|
'role', (
|
|
SELECT r.value
|
|
FROM role r
|
|
WHERE r.id = s.role_id
|
|
AND r.user_id = $2
|
|
)
|
|
)
|
|
) AS records
|
|
FROM signature s
|
|
INNER JOIN agreement a ON s.agreement_id = a.id
|
|
WHERE a.agreement_id_uuid = $1
|
|
AND a.user_id = $2;
|
|
`
|
|
const res = await client.query(sql, [
|
|
id,
|
|
userId,
|
|
]);
|
|
if (res.rows.length > 0 && res.rows[0].records) {
|
|
return res.rows[0].records;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
async function get(input, userId) {
|
|
let response = {
|
|
success: false,
|
|
error: 'Unknown error',
|
|
};
|
|
const client = await getPool();
|
|
try {
|
|
const existingAgreement = await getAgreement(client, userId, input.agreementId, input.key)
|
|
if (!existingAgreement) {
|
|
response.error = 'agreement not found'
|
|
} else {
|
|
const data = {
|
|
agreement: existingAgreement
|
|
}
|
|
if (input.includeSignatures) {
|
|
data.signatures = await getSignatures(client, userId, input.agreementId)
|
|
for (let x = 0; x < data.signatures.length; x++) {
|
|
if (data.signatures[x].role === null) {
|
|
delete data.signatures[x].role;
|
|
}
|
|
}
|
|
}
|
|
response = {
|
|
message: `retrieved: ${input.agreementId}`,
|
|
data,
|
|
}
|
|
}
|
|
} catch(e) {
|
|
console.error(e)
|
|
} finally {
|
|
await client.release();
|
|
}
|
|
return response;
|
|
}
|
|
|
|
async function save(client, userId, agreement, publicAgreement) {
|
|
await client.query(`BEGIN`);
|
|
const isPublic = publicAgreement ? true : false;
|
|
const res = await client.query(`
|
|
INSERT INTO agreement (
|
|
user_id,
|
|
version,
|
|
parent,
|
|
agreement_id_uuid,
|
|
context,
|
|
public,
|
|
created,
|
|
created_as_ts
|
|
) VALUES (
|
|
$1,
|
|
$2,
|
|
$3,
|
|
$4,
|
|
$5,
|
|
$6,
|
|
$7,
|
|
$8
|
|
) RETURNING id;
|
|
`, [
|
|
userId,
|
|
agreement.version,
|
|
agreement.parent,
|
|
agreement.agreementId,
|
|
agreement["@context"],
|
|
isPublic,
|
|
agreement.created,
|
|
new Date(agreement.created).toISOString(),
|
|
]);
|
|
for (const id of agreement.ids) {
|
|
await client.query(`
|
|
INSERT INTO agreement_required_id (
|
|
user_id,
|
|
agreement_id,
|
|
required_id
|
|
) VALUES (
|
|
$1,
|
|
$2,
|
|
$3
|
|
);
|
|
`, [
|
|
userId,
|
|
res.rows[0].id,
|
|
id,
|
|
]);
|
|
}
|
|
if (agreement.references) {
|
|
for (const reference of agreement.references) {
|
|
await client.query(`
|
|
INSERT INTO reference (
|
|
user_id,
|
|
uri
|
|
) VALUES (
|
|
$1,
|
|
$2
|
|
) ON CONFLICT DO NOTHING;
|
|
`, [
|
|
userId,
|
|
reference,
|
|
]);
|
|
await client.query(`
|
|
INSERT INTO agreement_reference (
|
|
user_id,
|
|
agreement_id,
|
|
reference_id
|
|
) VALUES (
|
|
$1,
|
|
$2,
|
|
(
|
|
SELECT id
|
|
FROM reference
|
|
WHERE uri = $3
|
|
AND user_id ${userId ? `= $1` : `IS NULL`}
|
|
)
|
|
);
|
|
`, [
|
|
userId,
|
|
res.rows[0].id,
|
|
reference,
|
|
]);
|
|
}
|
|
}
|
|
const purposes = agreement.version == 1 ? agreement.purposes : agreement.permitted;
|
|
for (const purpose of purposes) {
|
|
await client.query(`
|
|
INSERT INTO purpose (
|
|
user_id,
|
|
value
|
|
) VALUES (
|
|
$1,
|
|
$2
|
|
) ON CONFLICT DO NOTHING;
|
|
`, [
|
|
userId,
|
|
purpose,
|
|
]);
|
|
await client.query(`
|
|
INSERT INTO agreement_purpose (
|
|
user_id,
|
|
agreement_id,
|
|
purpose_id
|
|
) VALUES (
|
|
$1,
|
|
$2,
|
|
(
|
|
SELECT id
|
|
FROM purpose
|
|
WHERE value = $3
|
|
AND user_id ${userId ? `= $1` : `IS NULL`}
|
|
)
|
|
);
|
|
`, [
|
|
userId,
|
|
res.rows[0].id,
|
|
purpose,
|
|
]);
|
|
}
|
|
const prohibitions = agreement.version == 1 ? agreement.prohibitions : agreement.prohibited;
|
|
for (const prohibition of prohibitions) {
|
|
await client.query(`
|
|
INSERT INTO prohibition (
|
|
user_id,
|
|
value
|
|
) VALUES (
|
|
$1,
|
|
$2
|
|
) ON CONFLICT DO NOTHING;
|
|
`, [
|
|
userId,
|
|
prohibition,
|
|
]);
|
|
await client.query(`
|
|
INSERT INTO agreement_prohibition (
|
|
user_id,
|
|
agreement_id,
|
|
prohibition_id
|
|
) VALUES (
|
|
$1,
|
|
$2,
|
|
(
|
|
SELECT id
|
|
FROM prohibition
|
|
WHERE value = $3
|
|
AND user_id ${userId ? `= $1` : `IS NULL`}
|
|
)
|
|
);
|
|
`, [
|
|
userId,
|
|
res.rows[0].id,
|
|
prohibition,
|
|
]);
|
|
}
|
|
for (const role of agreement.validRoles) {
|
|
await client.query(`
|
|
INSERT INTO role (
|
|
user_id,
|
|
value
|
|
) VALUES (
|
|
$1,
|
|
$2
|
|
) ON CONFLICT DO NOTHING;
|
|
`, [
|
|
userId,
|
|
role,
|
|
]);
|
|
await client.query(`
|
|
INSERT INTO agreement_role (
|
|
user_id,
|
|
agreement_id,
|
|
role_id
|
|
) VALUES (
|
|
$1,
|
|
$2,
|
|
(
|
|
SELECT id
|
|
FROM role
|
|
WHERE value = $3
|
|
AND user_id ${userId ? `= $1` : `IS NULL`}
|
|
)
|
|
);
|
|
`, [
|
|
userId,
|
|
res.rows[0].id,
|
|
role,
|
|
]);
|
|
}
|
|
await client.query('COMMIT');
|
|
}
|
|
|
|
async function saveSignatures(client, userId, agreementId, signatures) {
|
|
await client.query(`BEGIN`);
|
|
for (const signature of signatures) {
|
|
await client.query(`
|
|
INSERT INTO signature (
|
|
user_id,
|
|
version,
|
|
signer_id,
|
|
signed_on,
|
|
type,
|
|
jws,
|
|
role_id,
|
|
agreement_id,
|
|
event_id,
|
|
signed_on_as_ts
|
|
) VALUES (
|
|
$1,
|
|
$2,
|
|
$3,
|
|
$4,
|
|
$5,
|
|
$6,
|
|
(
|
|
SELECT id
|
|
FROM role
|
|
WHERE value = $7
|
|
AND user_id = $1
|
|
),
|
|
(
|
|
SELECT id
|
|
FROM agreement
|
|
WHERE agreement_id_uuid = $8
|
|
AND user_id = $1
|
|
),
|
|
$9,
|
|
$10
|
|
) ON CONFLICT DO NOTHING;
|
|
`, [
|
|
userId,
|
|
signature.version,
|
|
signature.id,
|
|
signature.signedOn,
|
|
signature.type,
|
|
signature.jws,
|
|
signature.role,
|
|
agreementId,
|
|
null,
|
|
new Date(signature.signedOn).toISOString(),
|
|
]);
|
|
}
|
|
await client.query('COMMIT');
|
|
}
|
|
|
|
async function create(input, userId, _client, uuid) {
|
|
let response = {
|
|
success: false,
|
|
error: 'Unknown error',
|
|
};
|
|
const client = _client || await getPool();
|
|
try {
|
|
input.didDocs = [];
|
|
for (const shortName of input.shortNames) {
|
|
input.didDocs.push((await entity.getEntity(client, userId, shortName)).didDoc)
|
|
}
|
|
delete input.shortNames;
|
|
if (input.caveats) {
|
|
input.prohibitions = input.caveats;
|
|
delete input.caveats;
|
|
}
|
|
const agreement = await JlincAgreement.create(input);
|
|
if (uuid) {
|
|
agreement.agreementId = uuid;
|
|
}
|
|
await save(client, userId, agreement, input.public);
|
|
for (const uri of (agreement.references || [])) {
|
|
await cacheAgreementMarkdown({ userId, agreementUuid: agreement.agreementId, uri }, client);
|
|
}
|
|
response = {
|
|
message: `created and saved: ${agreement.agreementId}`,
|
|
data: agreement,
|
|
}
|
|
} catch(e) {
|
|
console.error(e)
|
|
} finally {
|
|
if (!_client)
|
|
await client.release();
|
|
}
|
|
return response;
|
|
}
|
|
|
|
async function process(input, userId, _client, _agreement) {
|
|
let response = {
|
|
success: false,
|
|
error: 'Unknown error',
|
|
};
|
|
const client = _client || await getPool();
|
|
try {
|
|
const existingAgreement = _agreement || await getAgreement(client, userId, input.agreementId)
|
|
if (!existingAgreement) {
|
|
throw new Error('agreement does not exist')
|
|
}
|
|
const inputEntity = input.shortName ? await entity.getEntity(client, userId, input.shortName) : null;
|
|
const didDoc = inputEntity ? inputEntity.didDoc : input.didDoc;
|
|
const signingKey = inputEntity ? inputEntity.controlPrivateKeyB64U : input.signingKey;
|
|
const signingPublicKey = inputEntity ? inputEntity.didDoc.verificationMethod[0].key : input.signingPublicKey;
|
|
const signingInput = {
|
|
agreement: existingAgreement,
|
|
didDoc,
|
|
signingKey,
|
|
signingPublicKey,
|
|
role: input.role,
|
|
}
|
|
const agreementData = await JlincAgreement.sign(signingInput);
|
|
await saveSignatures(client, userId, existingAgreement.agreementId, agreementData.signatures);
|
|
const audit = await JlincAudit.create(agreementData);
|
|
const auditInput = {
|
|
audit,
|
|
didDoc,
|
|
signingKey,
|
|
signingPublicKey,
|
|
}
|
|
const auditData = await JlincAudit.sign(auditInput);
|
|
response = {
|
|
message: `signed and saved: ${agreementData?.agreement?.agreementId}`,
|
|
data: {
|
|
auditData,
|
|
},
|
|
}
|
|
if (input.archive) {
|
|
await putQueue(
|
|
client,
|
|
'audit',
|
|
`${input.archive.url}/api/v1/audit/put`,
|
|
{
|
|
'Authorization': `Bearer ${input.archive.key}`,
|
|
},
|
|
response.data.auditData,
|
|
)
|
|
}
|
|
} catch(e) {
|
|
console.error(e)
|
|
} finally {
|
|
if (!_client)
|
|
await client.release();
|
|
}
|
|
return response;
|
|
}
|
|
|
|
async function produce(input, userId) {
|
|
let response = {
|
|
success: false,
|
|
error: 'Unknown error',
|
|
};
|
|
const client = await getPool();
|
|
try {
|
|
const created = (await create(input.data, userId, client)).data;
|
|
const processed = (await process(
|
|
{
|
|
agreementId: created.agreementId,
|
|
shortName: input.shortName,
|
|
role: input.role,
|
|
archive: input.archive,
|
|
},
|
|
userId,
|
|
client,
|
|
created,
|
|
)).data;
|
|
response = {
|
|
message: `created and processed: ${created.agreementId}`,
|
|
data: {
|
|
created,
|
|
processed,
|
|
},
|
|
}
|
|
} catch(e) {
|
|
console.error(e)
|
|
} finally {
|
|
await client.release();
|
|
}
|
|
return response;
|
|
}
|
|
|
|
export const agreement = {
|
|
getAgreement,
|
|
getSignatures,
|
|
get,
|
|
create,
|
|
process,
|
|
produce,
|
|
}
|