openvibers/openvibe.reviews Commits
A source item taken down at its source is 410 through /api/v1/items/:id
Changes
@@@ -21,7 +21,7 @@* POST /entities/:ref/split reviews.entity.split { note? }* POST /trust reviews.entity.manage { scope, scope_id, key, value, note? }* GET /items?resolution=queue reviews.entity.resolve items waiting for an editor-- * GET /items/:id reviews.entity.resolve++ * GET /items/:id reviews.entity.resolve 410 item.removed once its source took it down* POST /items/:id/resolution reviews.entity.resolve { entity, add_alias? } | { ignore: true, note? } editor* POST /signals/import reviews.signal.import { source_item_id } fetch the item from Sources and apply it* POST /sources/sync reviews.signal.import pull the next pages of review items from Sources@@@ -104,6 +104,8 @@ function createApi({ svc, viewers, platform, sync, config, log = console }) {router.get('/items/:id', guard('reviews.entity.resolve'), R((req) => {const row = svc.item(req.params.id);if (!row) throw new svc.ReviewsError(404, 'item.not_found', 'Reviews has not read that item');++ // Taken down at its source: gone, like a deleted entity (its record stays for the audit).++ if (row.state !== 'active') throw new svc.ReviewsError(410, 'item.removed', 'Its source removed this item');return { item: svc.itemView(row) };}));router.post('/items/:id/resolution', guard('reviews.entity.resolve'), R((req) => {nnew file mode 100644@@@ -0,0 +1,54 @@++'use strict';++/**++ * Source items taken down at their source answer like the other taken-down surfaces: GET++ * /api/v1/items/:id is 410 item.removed (as a deleted entity is 410), for every caller, and the++ * body carries none of the item's content.++ */++const assert = require('assert');++const {++ boot, req, suite, productItem, importItem, deliver, sourcesEvent, serviceToken, editorToken, readerToken,++} = require('./helpers');++++const t = suite('takedown');++++t('a removed source item is 410 through /api/v1/items/:id, whoever asks', async () => {++ const h = await boot();++ try {++ h.sources.addSource({ key: 'shop-example', name: 'Shop example', homepage_url: 'https://shop.example/', category: 'reviews' });++ const it = productItem({ title: 'Taken Down Widget', url: 'https://shop.example/p/taken-down' });++ await importItem(h, it);++ const resolver = serviceToken({ client: 'tools', cap: ['reviews.entity.resolve'] });++ const callers = [{}, { token: readerToken() }, { token: editorToken() }, { token: resolver }];++ for (const c of callers) {++ const r = await req(h, 'GET', `/api/v1/items/${it.id}`, c);++ assert.strictEqual(r.status, 200, r.text);++ assert.strictEqual(r.json.item.title, 'Taken Down Widget');++ }++++ const removed = h.sources.remove(it.id, 'takedown');++ const d = await deliver(h, sourcesEvent('sources.item.removed', removed, { reason: 'takedown' }));++ assert.strictEqual(d.status, 200, d.text);++ for (const c of callers) {++ const r = await req(h, 'GET', `/api/v1/items/${it.id}`, c);++ assert.strictEqual(r.status, 410, r.text);++ assert.strictEqual(r.json.code, 'item.removed');++ assert.ok(!r.text.includes('Taken Down Widget') && !r.text.includes('shop.example/p/taken-down'), 'no content of the removed item');++ }++ } finally { await h.stop(); }++});++++t('an item Reviews first heard of as removed is 410 too; an unknown one stays 404', async () => {++ const h = await boot();++ try {++ const it = productItem({ title: 'Never Seen', url: 'https://shop.example/p/never-seen' });++ h.sources.put(it);++ const removed = h.sources.remove(it.id, 'takedown');++ const out = await importItem(h, removed);++ assert.strictEqual(out.outcome, 'removed:unknown');++ let r = await req(h, 'GET', `/api/v1/items/${it.id}`, { token: editorToken() });++ assert.strictEqual(r.status, 410, r.text);++ assert.strictEqual(r.json.code, 'item.removed');++ r = await req(h, 'GET', '/api/v1/items/itm_01J8Z6Q3KX0000000000000009');++ assert.strictEqual(r.status, 404);++ } finally { await h.stop(); }++});
@@ -21,7 +21,7 @@
@@ -21,7 +21,7 @@
* POST /entities/:ref/split reviews.entity.split { note? }
* POST /entities/:ref/split reviews.entity.split { note? }
* POST /trust reviews.entity.manage { scope, scope_id, key, value, note? }
* POST /trust reviews.entity.manage { scope, scope_id, key, value, note? }
* GET /items?resolution=queue reviews.entity.resolve items waiting for an editor
* GET /items?resolution=queue reviews.entity.resolve items waiting for an editor
- * GET /items/:id reviews.entity.resolve
+ * GET /items/:id reviews.entity.resolve 410 item.removed once its source took it down
* POST /items/:id/resolution reviews.entity.resolve { entity, add_alias? } | { ignore: true, note? } editor
* POST /items/:id/resolution reviews.entity.resolve { entity, add_alias? } | { ignore: true, note? } editor
* POST /signals/import reviews.signal.import { source_item_id } fetch the item from Sources and apply it
* POST /signals/import reviews.signal.import { source_item_id } fetch the item from Sources and apply it
* POST /sources/sync reviews.signal.import pull the next pages of review items from Sources
* POST /sources/sync reviews.signal.import pull the next pages of review items from Sources
@@ -104,6 +104,8 @@ function createApi({ svc, viewers, platform, sync, config, log = console }) {
@@ -104,6 +104,8 @@ function createApi({ svc, viewers, platform, sync, config, log = console }) {
router.get('/items/:id', guard('reviews.entity.resolve'), R((req) => {
router.get('/items/:id', guard('reviews.entity.resolve'), R((req) => {
const row = svc.item(req.params.id);
const row = svc.item(req.params.id);
if (!row) throw new svc.ReviewsError(404, 'item.not_found', 'Reviews has not read that item');
if (!row) throw new svc.ReviewsError(404, 'item.not_found', 'Reviews has not read that item');
+ // Taken down at its source: gone, like a deleted entity (its record stays for the audit).
+ if (row.state !== 'active') throw new svc.ReviewsError(410, 'item.removed', 'Its source removed this item');
return { item: svc.itemView(row) };
return { item: svc.itemView(row) };
}));
}));
router.post('/items/:id/resolution', guard('reviews.entity.resolve'), R((req) => {
router.post('/items/:id/resolution', guard('reviews.entity.resolve'), R((req) => {
new file mode 100644
new file mode 100644
@@ -0,0 +1,54 @@
@@ -0,0 +1,54 @@
+'use strict';
+/**
+ * Source items taken down at their source answer like the other taken-down surfaces: GET
+ * /api/v1/items/:id is 410 item.removed (as a deleted entity is 410), for every caller, and the
+ * body carries none of the item's content.
+ */
+const assert = require('assert');
+const {
+ boot, req, suite, productItem, importItem, deliver, sourcesEvent, serviceToken, editorToken, readerToken,
+} = require('./helpers');
+
+const t = suite('takedown');
+
+t('a removed source item is 410 through /api/v1/items/:id, whoever asks', async () => {
+ const h = await boot();
+ try {
+ h.sources.addSource({ key: 'shop-example', name: 'Shop example', homepage_url: 'https://shop.example/', category: 'reviews' });
+ const it = productItem({ title: 'Taken Down Widget', url: 'https://shop.example/p/taken-down' });
+ await importItem(h, it);
+ const resolver = serviceToken({ client: 'tools', cap: ['reviews.entity.resolve'] });
+ const callers = [{}, { token: readerToken() }, { token: editorToken() }, { token: resolver }];
+ for (const c of callers) {
+ const r = await req(h, 'GET', `/api/v1/items/${it.id}`, c);
+ assert.strictEqual(r.status, 200, r.text);
+ assert.strictEqual(r.json.item.title, 'Taken Down Widget');
+ }
+
+ const removed = h.sources.remove(it.id, 'takedown');
+ const d = await deliver(h, sourcesEvent('sources.item.removed', removed, { reason: 'takedown' }));
+ assert.strictEqual(d.status, 200, d.text);
+ for (const c of callers) {
+ const r = await req(h, 'GET', `/api/v1/items/${it.id}`, c);
+ assert.strictEqual(r.status, 410, r.text);
+ assert.strictEqual(r.json.code, 'item.removed');
+ assert.ok(!r.text.includes('Taken Down Widget') && !r.text.includes('shop.example/p/taken-down'), 'no content of the removed item');
+ }
+ } finally { await h.stop(); }
+});
+
+t('an item Reviews first heard of as removed is 410 too; an unknown one stays 404', async () => {
+ const h = await boot();
+ try {
+ const it = productItem({ title: 'Never Seen', url: 'https://shop.example/p/never-seen' });
+ h.sources.put(it);
+ const removed = h.sources.remove(it.id, 'takedown');
+ const out = await importItem(h, removed);
+ assert.strictEqual(out.outcome, 'removed:unknown');
+ let r = await req(h, 'GET', `/api/v1/items/${it.id}`, { token: editorToken() });
+ assert.strictEqual(r.status, 410, r.text);
+ assert.strictEqual(r.json.code, 'item.removed');
+ r = await req(h, 'GET', '/api/v1/items/itm_01J8Z6Q3KX0000000000000009');
+ assert.strictEqual(r.status, 404);
+ } finally { await h.stop(); }
+});