@reddoorla/maintenance 0.54.0 → 0.54.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/bin.js +1 -276
- package/dist/cli/bin.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/bin.js
CHANGED
|
@@ -2444,255 +2444,6 @@ var init_client2 = __esm({
|
|
|
2444
2444
|
}
|
|
2445
2445
|
});
|
|
2446
2446
|
|
|
2447
|
-
// src/forms/types.ts
|
|
2448
|
-
var SUBMISSION_FORM_TYPES;
|
|
2449
|
-
var init_types = __esm({
|
|
2450
|
-
"src/forms/types.ts"() {
|
|
2451
|
-
"use strict";
|
|
2452
|
-
SUBMISSION_FORM_TYPES = [
|
|
2453
|
-
"contact",
|
|
2454
|
-
"inquiry",
|
|
2455
|
-
"newsletter",
|
|
2456
|
-
"rsvp",
|
|
2457
|
-
"reserve"
|
|
2458
|
-
];
|
|
2459
|
-
}
|
|
2460
|
-
});
|
|
2461
|
-
|
|
2462
|
-
// src/reports/submission-row.ts
|
|
2463
|
-
function toFormType(raw) {
|
|
2464
|
-
if (raw && SUBMISSION_FORM_TYPES.includes(raw)) return raw;
|
|
2465
|
-
if (raw)
|
|
2466
|
-
console.warn(`[submissions] unknown Form type ${JSON.stringify(raw)} \u2014 treating as contact`);
|
|
2467
|
-
return "contact";
|
|
2468
|
-
}
|
|
2469
|
-
function toStatus(raw) {
|
|
2470
|
-
if (raw && SUBMISSION_STATUSES.includes(raw))
|
|
2471
|
-
return raw;
|
|
2472
|
-
return "new";
|
|
2473
|
-
}
|
|
2474
|
-
function toNotifyStatus(raw) {
|
|
2475
|
-
if (raw && NOTIFY_STATUSES.includes(raw)) return raw;
|
|
2476
|
-
return "skipped";
|
|
2477
|
-
}
|
|
2478
|
-
var SUBMISSION_STATUSES, NOTIFY_STATUSES;
|
|
2479
|
-
var init_submission_row = __esm({
|
|
2480
|
-
"src/reports/submission-row.ts"() {
|
|
2481
|
-
"use strict";
|
|
2482
|
-
init_types();
|
|
2483
|
-
SUBMISSION_STATUSES = ["new", "read", "archived", "spam"];
|
|
2484
|
-
NOTIFY_STATUSES = ["sent", "failed", "skipped"];
|
|
2485
|
-
}
|
|
2486
|
-
});
|
|
2487
|
-
|
|
2488
|
-
// src/reports/airtable/submissions.ts
|
|
2489
|
-
function mapRow3(rec) {
|
|
2490
|
-
const f = rec.fields;
|
|
2491
|
-
const linkSites = f["Site"] ?? [];
|
|
2492
|
-
return {
|
|
2493
|
-
id: rec.id,
|
|
2494
|
-
submissionId: typeof f["Submission ID"] === "number" ? f["Submission ID"] : null,
|
|
2495
|
-
siteId: linkSites[0] ?? "",
|
|
2496
|
-
formType: toFormType(f["Form type"]),
|
|
2497
|
-
name: String(f["Name"] ?? ""),
|
|
2498
|
-
email: String(f["Email"] ?? ""),
|
|
2499
|
-
phone: f["Phone"] ?? null,
|
|
2500
|
-
message: f["Message"] ?? null,
|
|
2501
|
-
extraFields: f["Extra fields"] ?? null,
|
|
2502
|
-
sourceUrl: f["Source URL"] ?? null,
|
|
2503
|
-
utm: f["UTM"] ?? null,
|
|
2504
|
-
submittedAt: f["Submitted at"] ?? null,
|
|
2505
|
-
status: toStatus(f["Status"]),
|
|
2506
|
-
notifyStatus: toNotifyStatus(f["Notify status"]),
|
|
2507
|
-
resendMessageId: f["Resend message ID"] ?? null
|
|
2508
|
-
};
|
|
2509
|
-
}
|
|
2510
|
-
var SUBMISSIONS_TABLE;
|
|
2511
|
-
var init_submissions = __esm({
|
|
2512
|
-
"src/reports/airtable/submissions.ts"() {
|
|
2513
|
-
"use strict";
|
|
2514
|
-
init_reports();
|
|
2515
|
-
init_submission_row();
|
|
2516
|
-
SUBMISSIONS_TABLE = "Submissions";
|
|
2517
|
-
}
|
|
2518
|
-
});
|
|
2519
|
-
|
|
2520
|
-
// src/reports/airtable/screenouts.ts
|
|
2521
|
-
function num(v) {
|
|
2522
|
-
return typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
2523
|
-
}
|
|
2524
|
-
function siteIdOf(fields) {
|
|
2525
|
-
const link = fields["Site"];
|
|
2526
|
-
return link?.[0] ?? "";
|
|
2527
|
-
}
|
|
2528
|
-
async function listScreenOutsSince(base, sinceDate) {
|
|
2529
|
-
const out = /* @__PURE__ */ new Map();
|
|
2530
|
-
await base(SCREENOUTS_TABLE).select({ filterByFormula: `{Date} >= ${JSON.stringify(sinceDate)}`, pageSize: 100 }).eachPage((records, fetchNextPage) => {
|
|
2531
|
-
for (const rec of records) {
|
|
2532
|
-
const f = rec.fields;
|
|
2533
|
-
const date = typeof f["Date"] === "string" ? f["Date"] : "";
|
|
2534
|
-
if (date < sinceDate) continue;
|
|
2535
|
-
const siteId = siteIdOf(f);
|
|
2536
|
-
if (!siteId) continue;
|
|
2537
|
-
const cur = out.get(siteId) ?? { honeypot: 0, tooFast: 0, markedSpam: 0 };
|
|
2538
|
-
cur.honeypot += num(f["Honeypot"]);
|
|
2539
|
-
cur.tooFast += num(f["Too-fast"]);
|
|
2540
|
-
cur.markedSpam += num(f["Marked spam"]);
|
|
2541
|
-
out.set(siteId, cur);
|
|
2542
|
-
}
|
|
2543
|
-
fetchNextPage();
|
|
2544
|
-
});
|
|
2545
|
-
return out;
|
|
2546
|
-
}
|
|
2547
|
-
var SCREENOUTS_TABLE;
|
|
2548
|
-
var init_screenouts = __esm({
|
|
2549
|
-
"src/reports/airtable/screenouts.ts"() {
|
|
2550
|
-
"use strict";
|
|
2551
|
-
SCREENOUTS_TABLE = "Spam Screenouts";
|
|
2552
|
-
}
|
|
2553
|
-
});
|
|
2554
|
-
|
|
2555
|
-
// src/db/submissions.ts
|
|
2556
|
-
import { sql } from "kysely";
|
|
2557
|
-
async function backfillSubmission(db, row) {
|
|
2558
|
-
await db.insertInto("submissions").values({
|
|
2559
|
-
id: row.id,
|
|
2560
|
-
submission_id: row.submissionId,
|
|
2561
|
-
site_id: row.siteId,
|
|
2562
|
-
form_type: row.formType,
|
|
2563
|
-
name: row.name,
|
|
2564
|
-
email: row.email,
|
|
2565
|
-
phone: row.phone,
|
|
2566
|
-
message: row.message,
|
|
2567
|
-
extra_fields: row.extraFields,
|
|
2568
|
-
source_url: row.sourceUrl,
|
|
2569
|
-
utm: row.utm,
|
|
2570
|
-
submitted_at: row.submittedAt,
|
|
2571
|
-
status: row.status,
|
|
2572
|
-
notify_status: row.notifyStatus,
|
|
2573
|
-
resend_message_id: row.resendMessageId
|
|
2574
|
-
}).onConflict((oc) => oc.column("id").doNothing()).execute();
|
|
2575
|
-
}
|
|
2576
|
-
var init_submissions2 = __esm({
|
|
2577
|
-
"src/db/submissions.ts"() {
|
|
2578
|
-
"use strict";
|
|
2579
|
-
init_submission_row();
|
|
2580
|
-
}
|
|
2581
|
-
});
|
|
2582
|
-
|
|
2583
|
-
// src/db/screenouts.ts
|
|
2584
|
-
import { sql as sql2 } from "kysely";
|
|
2585
|
-
async function listScreenOutsSince2(db, sinceDate) {
|
|
2586
|
-
const rows = await db.selectFrom("spam_screenouts").select((eb) => [
|
|
2587
|
-
"site_id",
|
|
2588
|
-
eb.fn.sum("honeypot").as("honeypot"),
|
|
2589
|
-
eb.fn.sum("too_fast").as("too_fast"),
|
|
2590
|
-
eb.fn.sum("marked_spam").as("marked_spam")
|
|
2591
|
-
]).where("date", ">=", sinceDate).groupBy("site_id").execute();
|
|
2592
|
-
const out = /* @__PURE__ */ new Map();
|
|
2593
|
-
for (const r of rows) {
|
|
2594
|
-
out.set(r.site_id, {
|
|
2595
|
-
honeypot: Number(r.honeypot) || 0,
|
|
2596
|
-
tooFast: Number(r.too_fast) || 0,
|
|
2597
|
-
markedSpam: Number(r.marked_spam) || 0
|
|
2598
|
-
});
|
|
2599
|
-
}
|
|
2600
|
-
return out;
|
|
2601
|
-
}
|
|
2602
|
-
async function backfillScreenoutBucket(db, b) {
|
|
2603
|
-
await sql2`
|
|
2604
|
-
INSERT INTO spam_screenouts (site_id, date, honeypot, too_fast, marked_spam)
|
|
2605
|
-
VALUES (${b.siteId}, ${b.date}, ${b.honeypot}, ${b.tooFast}, ${b.markedSpam})
|
|
2606
|
-
ON CONFLICT (site_id, date) DO UPDATE SET
|
|
2607
|
-
honeypot = excluded.honeypot,
|
|
2608
|
-
too_fast = excluded.too_fast,
|
|
2609
|
-
marked_spam = excluded.marked_spam
|
|
2610
|
-
`.execute(db);
|
|
2611
|
-
}
|
|
2612
|
-
var init_screenouts2 = __esm({
|
|
2613
|
-
"src/db/screenouts.ts"() {
|
|
2614
|
-
"use strict";
|
|
2615
|
-
}
|
|
2616
|
-
});
|
|
2617
|
-
|
|
2618
|
-
// src/db/backfill.ts
|
|
2619
|
-
var backfill_exports = {};
|
|
2620
|
-
__export(backfill_exports, {
|
|
2621
|
-
backfillScreenouts: () => backfillScreenouts,
|
|
2622
|
-
backfillSubmissions: () => backfillSubmissions,
|
|
2623
|
-
reconcile: () => reconcile
|
|
2624
|
-
});
|
|
2625
|
-
import { sql as sql3 } from "kysely";
|
|
2626
|
-
async function backfillSubmissions(base, db) {
|
|
2627
|
-
const rows = [];
|
|
2628
|
-
await base(SUBMISSIONS_TABLE).select({ pageSize: 100 }).eachPage((records, fetchNextPage) => {
|
|
2629
|
-
for (const rec of records) rows.push(mapRow3({ id: rec.id, fields: rec.fields }));
|
|
2630
|
-
fetchNextPage();
|
|
2631
|
-
});
|
|
2632
|
-
for (const row of rows) await backfillSubmission(db, row);
|
|
2633
|
-
return rows.length;
|
|
2634
|
-
}
|
|
2635
|
-
function num2(v) {
|
|
2636
|
-
return typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
2637
|
-
}
|
|
2638
|
-
async function backfillScreenouts(base, db) {
|
|
2639
|
-
const agg = /* @__PURE__ */ new Map();
|
|
2640
|
-
await base(SCREENOUTS_TABLE).select({ pageSize: 100 }).eachPage((records, fetchNextPage) => {
|
|
2641
|
-
for (const rec of records) {
|
|
2642
|
-
const f = rec.fields;
|
|
2643
|
-
const siteId = f["Site"]?.[0] ?? "";
|
|
2644
|
-
const date = typeof f["Date"] === "string" ? f["Date"] : "";
|
|
2645
|
-
if (!siteId || !date) continue;
|
|
2646
|
-
const key = `${siteId} ${date}`;
|
|
2647
|
-
const cur = agg.get(key) ?? { siteId, date, honeypot: 0, tooFast: 0, markedSpam: 0 };
|
|
2648
|
-
cur.honeypot += num2(f["Honeypot"]);
|
|
2649
|
-
cur.tooFast += num2(f["Too-fast"]);
|
|
2650
|
-
cur.markedSpam += num2(f["Marked spam"]);
|
|
2651
|
-
agg.set(key, cur);
|
|
2652
|
-
}
|
|
2653
|
-
fetchNextPage();
|
|
2654
|
-
});
|
|
2655
|
-
for (const bucket of agg.values()) await backfillScreenoutBucket(db, bucket);
|
|
2656
|
-
return agg.size;
|
|
2657
|
-
}
|
|
2658
|
-
async function reconcile(base, db) {
|
|
2659
|
-
let airtableSubs = 0;
|
|
2660
|
-
await base(SUBMISSIONS_TABLE).select({ pageSize: 100, fields: [] }).eachPage((records, fetchNextPage) => {
|
|
2661
|
-
airtableSubs += records.length;
|
|
2662
|
-
fetchNextPage();
|
|
2663
|
-
});
|
|
2664
|
-
const libCountRow = await sql3`SELECT COUNT(*) AS n FROM submissions`.execute(db);
|
|
2665
|
-
const libsqlSubs = Number(libCountRow.rows[0]?.n ?? 0);
|
|
2666
|
-
const aMap = await listScreenOutsSince(base, "0001-01-01");
|
|
2667
|
-
const lMap = await listScreenOutsSince2(db, "0001-01-01");
|
|
2668
|
-
const sumOf = (m) => {
|
|
2669
|
-
const t = { honeypot: 0, tooFast: 0, markedSpam: 0 };
|
|
2670
|
-
for (const v of m.values()) {
|
|
2671
|
-
t.honeypot += v.honeypot;
|
|
2672
|
-
t.tooFast += v.tooFast;
|
|
2673
|
-
t.markedSpam += v.markedSpam;
|
|
2674
|
-
}
|
|
2675
|
-
return t;
|
|
2676
|
-
};
|
|
2677
|
-
const aScreen = sumOf(aMap);
|
|
2678
|
-
const lScreen = sumOf(lMap);
|
|
2679
|
-
const ok = airtableSubs === libsqlSubs && aScreen.honeypot === lScreen.honeypot && aScreen.tooFast === lScreen.tooFast && aScreen.markedSpam === lScreen.markedSpam;
|
|
2680
|
-
return {
|
|
2681
|
-
ok,
|
|
2682
|
-
submissions: { airtable: airtableSubs, libsql: libsqlSubs },
|
|
2683
|
-
screenouts: { airtable: aScreen, libsql: lScreen }
|
|
2684
|
-
};
|
|
2685
|
-
}
|
|
2686
|
-
var init_backfill = __esm({
|
|
2687
|
-
"src/db/backfill.ts"() {
|
|
2688
|
-
"use strict";
|
|
2689
|
-
init_submissions();
|
|
2690
|
-
init_screenouts();
|
|
2691
|
-
init_submissions2();
|
|
2692
|
-
init_screenouts2();
|
|
2693
|
-
}
|
|
2694
|
-
});
|
|
2695
|
-
|
|
2696
2447
|
// src/cli/bin.ts
|
|
2697
2448
|
init_credentials();
|
|
2698
2449
|
import { dirname as dirname9 } from "path";
|
|
@@ -7870,34 +7621,8 @@ async function runDbCommand(action, opts) {
|
|
|
7870
7621
|
code: 0
|
|
7871
7622
|
};
|
|
7872
7623
|
}
|
|
7873
|
-
if (action === "backfill") {
|
|
7874
|
-
const { openDb: openDb2, readDbConfig: readDbConfig2 } = await Promise.resolve().then(() => (init_client2(), client_exports2));
|
|
7875
|
-
const cfg = opts.url ? { url: opts.url } : readDbConfig2();
|
|
7876
|
-
const { openBase: openBase2, readAirtableConfig: readAirtableConfig2 } = await Promise.resolve().then(() => (init_client(), client_exports));
|
|
7877
|
-
const { backfillSubmissions: backfillSubmissions2, backfillScreenouts: backfillScreenouts2 } = await Promise.resolve().then(() => (init_backfill(), backfill_exports));
|
|
7878
|
-
const base = openBase2(readAirtableConfig2());
|
|
7879
|
-
const db = await openDb2(cfg);
|
|
7880
|
-
const subs = await backfillSubmissions2(base, db);
|
|
7881
|
-
const buckets = await backfillScreenouts2(base, db);
|
|
7882
|
-
return { output: `Backfilled ${subs} submissions, ${buckets} screen-out buckets.`, code: 0 };
|
|
7883
|
-
}
|
|
7884
|
-
if (action === "reconcile") {
|
|
7885
|
-
const { openDb: openDb2, readDbConfig: readDbConfig2 } = await Promise.resolve().then(() => (init_client2(), client_exports2));
|
|
7886
|
-
const cfg = opts.url ? { url: opts.url } : readDbConfig2();
|
|
7887
|
-
const { openBase: openBase2, readAirtableConfig: readAirtableConfig2 } = await Promise.resolve().then(() => (init_client(), client_exports));
|
|
7888
|
-
const { reconcile: reconcile2 } = await Promise.resolve().then(() => (init_backfill(), backfill_exports));
|
|
7889
|
-
const base = openBase2(readAirtableConfig2());
|
|
7890
|
-
const db = await openDb2(cfg);
|
|
7891
|
-
const r = await reconcile2(base, db);
|
|
7892
|
-
const lines = [
|
|
7893
|
-
`submissions: airtable=${r.submissions.airtable} libsql=${r.submissions.libsql}`,
|
|
7894
|
-
`screenouts: airtable=${JSON.stringify(r.screenouts.airtable)} libsql=${JSON.stringify(r.screenouts.libsql)}`,
|
|
7895
|
-
r.ok ? "OK \u2014 parity confirmed." : "MISMATCH \u2014 do not cut over."
|
|
7896
|
-
];
|
|
7897
|
-
return { output: lines.join("\n"), code: r.ok ? 0 : 1 };
|
|
7898
|
-
}
|
|
7899
7624
|
return {
|
|
7900
|
-
output: `unknown db action '${action}'. Use: migrate
|
|
7625
|
+
output: `unknown db action '${action}'. Use: migrate.`,
|
|
7901
7626
|
code: 1
|
|
7902
7627
|
};
|
|
7903
7628
|
}
|