openvibers/openvibe.chat Commits
Chat: archiving a room works before migration 0010 runs (it waits behind 0009's contract window) (#86)
Changes
@@@ -67,14 +67,19 @@ function router({ now = () => Date.now() } = {}) {if (!check.valid) return http.sendProblem(res, 400, 'control.bad_request', { detail: JSON.stringify(check.errors).slice(0, 500), ctx });const caller = req.principal.sub;const digest = digestOf(request);-- const seen = await db.get('SELECT digest, response FROM chat_control_log WHERE caller = ? AND idempotency_key = ?', [caller, request.idempotency_key]);++ // chat_control_log (migration 0010) waits behind 0009's contract window (until 0006 is 7 days old). Until it++ // exists, answers are not kept: archive is idempotent by itself (a second one answers changed: false).++ const missing = (err) => /chat_control_log/.test(String(err && err.message)) && /does not exist|no such table/.test(String(err && err.message));++ const seen = await db.get('SELECT digest, response FROM chat_control_log WHERE caller = ? AND idempotency_key = ?', [caller, request.idempotency_key])++ .catch((err) => { if (missing(err)) return null; throw err; });if (seen) {if (seen.digest !== digest) return http.sendProblem(res, 409, 'control.idempotency_conflict', { detail: 'this idempotency_key was used for another request', ctx });return res.json(typeof seen.response === 'string' ? JSON.parse(seen.response) : seen.response);}const answer = await decide(request);await db.run(`INSERT INTO chat_control_log (caller, idempotency_key, owner_subject, digest, response, created_at)-- VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT DO NOTHING`, [caller, request.idempotency_key, request.owner.id, digest, JSON.stringify(answer), now()]);++ VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT DO NOTHING`, [caller, request.idempotency_key, request.owner.id, digest, JSON.stringify(answer), now()])++ .catch((err) => { if (!missing(err)) throw err; });return res.json(answer);} catch (err) { return next(err); }});@@@ -56,6 +56,18 @@ t('only the owner Chat holds; other actions and kinds are refused', async () =>assert.deepStrictEqual([r.status, r.body.code], [400, 'control.bad_request'], 'Chat holds no project resources');});++t('before migration 0010 has run, archive still works (its answers are just not kept)', async () => {++ await h.db.run('INSERT INTO rooms (slug, name, visibility, owner_id, owner_subject) VALUES (?, ?, ?, ?, ?)', ['control-early', 'Early', 'public', owner.id, owner.subject_id]);++ await h.db.run('ALTER TABLE chat_control_log RENAME TO chat_control_log_hidden');++ try {++ const r = await send(req('archive', { owned: { kind: 'chat.room', id: 'control-early' } }));++ assert.deepStrictEqual([r.status, r.body.state, r.body.result], [200, 'done', { archived: true, changed: true }], r.text);++ assert.ok(await archived('control-early'));++ } finally {++ await h.db.run('ALTER TABLE chat_control_log_hidden RENAME TO chat_control_log');++ }++});++t('first-party only, with chat.resource.control', async () => {let r = await send(req('archive', { owned: { kind: 'chat.room', id: 'control-theirs' } }), { token: h.serviceToken(['chat.resource.read'], { sub: 'svc:services' }) });assert.strictEqual(r.status, 403);
@@ -67,14 +67,19 @@ function router({ now = () => Date.now() } = {}) {
@@ -67,14 +67,19 @@ function router({ now = () => Date.now() } = {}) {
if (!check.valid) return http.sendProblem(res, 400, 'control.bad_request', { detail: JSON.stringify(check.errors).slice(0, 500), ctx });
if (!check.valid) return http.sendProblem(res, 400, 'control.bad_request', { detail: JSON.stringify(check.errors).slice(0, 500), ctx });
const caller = req.principal.sub;
const caller = req.principal.sub;
const digest = digestOf(request);
const digest = digestOf(request);
- const seen = await db.get('SELECT digest, response FROM chat_control_log WHERE caller = ? AND idempotency_key = ?', [caller, request.idempotency_key]);
+ // chat_control_log (migration 0010) waits behind 0009's contract window (until 0006 is 7 days old). Until it
+ // exists, answers are not kept: archive is idempotent by itself (a second one answers changed: false).
+ const missing = (err) => /chat_control_log/.test(String(err && err.message)) && /does not exist|no such table/.test(String(err && err.message));
+ const seen = await db.get('SELECT digest, response FROM chat_control_log WHERE caller = ? AND idempotency_key = ?', [caller, request.idempotency_key])
+ .catch((err) => { if (missing(err)) return null; throw err; });
if (seen) {
if (seen) {
if (seen.digest !== digest) return http.sendProblem(res, 409, 'control.idempotency_conflict', { detail: 'this idempotency_key was used for another request', ctx });
if (seen.digest !== digest) return http.sendProblem(res, 409, 'control.idempotency_conflict', { detail: 'this idempotency_key was used for another request', ctx });
return res.json(typeof seen.response === 'string' ? JSON.parse(seen.response) : seen.response);
return res.json(typeof seen.response === 'string' ? JSON.parse(seen.response) : seen.response);
}
}
const answer = await decide(request);
const answer = await decide(request);
await db.run(`INSERT INTO chat_control_log (caller, idempotency_key, owner_subject, digest, response, created_at)
await db.run(`INSERT INTO chat_control_log (caller, idempotency_key, owner_subject, digest, response, created_at)
- VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT DO NOTHING`, [caller, request.idempotency_key, request.owner.id, digest, JSON.stringify(answer), now()]);
+ VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT DO NOTHING`, [caller, request.idempotency_key, request.owner.id, digest, JSON.stringify(answer), now()])
+ .catch((err) => { if (!missing(err)) throw err; });
return res.json(answer);
return res.json(answer);
} catch (err) { return next(err); }
} catch (err) { return next(err); }
});
});
@@ -56,6 +56,18 @@ t('only the owner Chat holds; other actions and kinds are refused', async () =>
@@ -56,6 +56,18 @@ t('only the owner Chat holds; other actions and kinds are refused', async () =>
assert.deepStrictEqual([r.status, r.body.code], [400, 'control.bad_request'], 'Chat holds no project resources');
assert.deepStrictEqual([r.status, r.body.code], [400, 'control.bad_request'], 'Chat holds no project resources');
});
});
+t('before migration 0010 has run, archive still works (its answers are just not kept)', async () => {
+ await h.db.run('INSERT INTO rooms (slug, name, visibility, owner_id, owner_subject) VALUES (?, ?, ?, ?, ?)', ['control-early', 'Early', 'public', owner.id, owner.subject_id]);
+ await h.db.run('ALTER TABLE chat_control_log RENAME TO chat_control_log_hidden');
+ try {
+ const r = await send(req('archive', { owned: { kind: 'chat.room', id: 'control-early' } }));
+ assert.deepStrictEqual([r.status, r.body.state, r.body.result], [200, 'done', { archived: true, changed: true }], r.text);
+ assert.ok(await archived('control-early'));
+ } finally {
+ await h.db.run('ALTER TABLE chat_control_log_hidden RENAME TO chat_control_log');
+ }
+});
+
t('first-party only, with chat.resource.control', async () => {
t('first-party only, with chat.resource.control', async () => {
let r = await send(req('archive', { owned: { kind: 'chat.room', id: 'control-theirs' } }), { token: h.serviceToken(['chat.resource.read'], { sub: 'svc:services' }) });
let r = await send(req('archive', { owned: { kind: 'chat.room', id: 'control-theirs' } }), { token: h.serviceToken(['chat.resource.read'], { sub: 'svc:services' }) });
assert.strictEqual(r.status, 403);
assert.strictEqual(r.status, 403);