63 lines
2.5 KiB
JavaScript
63 lines
2.5 KiB
JavaScript
/// <reference path="../pb_data/types.d.ts" />
|
|
//
|
|
// Adds `trip_watchlist` — the per-seat trip-monitoring feature. Each
|
|
// active seat can pin a small, tier-capped number of specific already-
|
|
// analyzed competitor trips (WATCHLIST_CAP_PER_SEAT in
|
|
// services/watchlist_service.py — 1 per seat at Analyse). The nightly
|
|
// n8n cron re-scrapes exactly these URLs instead of each competitor's
|
|
// root website, replacing an unbounded, low-signal daily scrape (a
|
|
// homepage `is_generic_homepage()` already flags as non-trip-specific)
|
|
// with a small, predictable, and meaningful one — every resulting
|
|
// price/product alert is about a trip a PM explicitly chose to track.
|
|
//
|
|
// Reuses the exact same confirmed_url / per-url content-hash caching
|
|
// already built for manual trip-intent analysis this session — no
|
|
// schema change needed on products/scrape_runs/competitors, and no
|
|
// jobs.py/analysis_service.py code change either. See
|
|
// n8n/workflows/daily_scrape_cron.json for the cron-side wiring.
|
|
|
|
migrate((app) => {
|
|
const tenants = app.findCollectionByNameOrId('tenants');
|
|
const tenantSeats = app.findCollectionByNameOrId('tenant_seats');
|
|
const competitors = app.findCollectionByNameOrId('competitors');
|
|
|
|
const rel = (name, collection, opts = {}) => ({
|
|
name, type: 'relation', collectionId: collection.id,
|
|
cascadeDelete: false, minSelect: 0, maxSelect: 1, ...opts,
|
|
});
|
|
const text = (name, opts = {}) => ({ name, type: 'text', ...opts });
|
|
const autodate = (name, onCreate, onUpdate = false) => ({
|
|
name, type: 'autodate', onCreate, onUpdate,
|
|
});
|
|
|
|
const collection = new Collection({
|
|
type: 'base',
|
|
name: 'trip_watchlist',
|
|
fields: [
|
|
rel('tenant_id', tenants, { required: true }),
|
|
rel('seat_id', tenantSeats, { required: true }),
|
|
rel('competitor_id', competitors, { required: true }),
|
|
text('url', { required: true }),
|
|
text('trip_name'),
|
|
autodate('added_at', true),
|
|
],
|
|
indexes: [
|
|
'CREATE INDEX idx_trip_watchlist_seat ON trip_watchlist (seat_id)',
|
|
'CREATE INDEX idx_trip_watchlist_tenant ON trip_watchlist (tenant_id)',
|
|
],
|
|
// Same access model as every other tenant-scoped collection — Flask
|
|
// is the only direct PocketBase client, enforcing tenant_id/seat_id
|
|
// scoping itself.
|
|
listRule: null,
|
|
viewRule: null,
|
|
createRule: null,
|
|
updateRule: null,
|
|
deleteRule: null,
|
|
});
|
|
|
|
return app.save(collection);
|
|
}, (app) => {
|
|
const collection = app.findCollectionByNameOrId('trip_watchlist');
|
|
if (collection) app.delete(collection);
|
|
});
|