← back to Rentv Adintel

src/adapters/manual-review.js

71 lines

'use strict';
/**
 * Manual-review-only adapter (spec §6, §9, §10).
 *
 * The safe fallback the spec MANDATES for sites that do not permit automation
 * (§9: "Do not assume any listed site permits automation. Validate first, and
 * fall back to manual review or user upload.").
 *
 * This adapter NEVER fetches anything: discover() yields nothing and fetch()
 * refuses. Instead it exposes queueForReview(), which records a
 * manual_review_items row (review_type 'SOURCE') so a human can inspect the
 * public page in a normal browser and decide what, if anything, to store.
 */

const { AdvertiserSourceAdapter } = require('./base');

class ManualReviewAdapter extends AdvertiserSourceAdapter {
  constructor(policy) {
    // Force the access method to manual_review_only so it can never be run
    // through the automated fetch path even if a caller mis-configures it.
    const safePolicy = Object.assign({}, policy, {
      accessMethod: 'manual_review_only',
      allowsAutomatedAccess: false,
    });
    super(safePolicy);
  }

  // eslint-disable-next-line require-yield
  async *discover(_cursor) {
    // Intentionally yields nothing — no automated discovery.
    return;
  }

  async fetch(_item) {
    throw new Error(
      'ManualReviewAdapter.fetch: this source is manual_review_only and MUST NOT be fetched automatically (§6/§9)'
    );
  }

  /**
   * queueForReview(pool, { sourceKey, url, title, discoveryQuery })
   * Inserts a manual_review_items row of review_type 'SOURCE'. `pool` is a
   * pg Pool or any object with a .query() method.
   * Returns the new row id.
   */
  static async queueForReview(pool, { sourceKey, url, title, discoveryQuery } = {}) {
    if (!pool || typeof pool.query !== 'function') {
      throw new Error('queueForReview: a pg pool/client with .query() is required');
    }
    if (!url) throw new Error('queueForReview: url is required');

    const payload = {
      sourceKey: sourceKey || null,
      url,
      title: title || null,
      note: 'Queued for human review — source not permitted for automation (§9).',
    };

    const res = await pool.query(
      `INSERT INTO manual_review_items
         (review_type, payload, status, discovery_query, discovered_at)
       VALUES ('SOURCE', $1::jsonb, 'PENDING', $2, now())
       RETURNING id`,
      [JSON.stringify(payload), discoveryQuery || null]
    );
    return res.rows[0].id;
  }
}

module.exports = { ManualReviewAdapter };