@ainyc/canonry 2.10.3 → 2.12.0
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/assets/assets/{index-VW6VB3cO.js → index-Ckr4V5dK.js} +110 -110
- package/assets/index.html +1 -1
- package/dist/{chunk-FAP76VXF.js → chunk-FCYNFM4B.js} +621 -269
- package/dist/{chunk-Z3BWDCBJ.js → chunk-PLI7EOPM.js} +117 -66
- package/dist/cli.js +116 -10
- package/dist/index.js +2 -2
- package/dist/mcp.js +62 -11
- package/package.json +7 -7
|
@@ -55,7 +55,7 @@ import {
|
|
|
55
55
|
visibilityStateFromAnswerMentioned,
|
|
56
56
|
windowCutoff,
|
|
57
57
|
wordpressEnvSchema
|
|
58
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-PLI7EOPM.js";
|
|
59
59
|
import {
|
|
60
60
|
IntelligenceService,
|
|
61
61
|
agentMemory,
|
|
@@ -173,7 +173,7 @@ import crypto28 from "crypto";
|
|
|
173
173
|
import fs12 from "fs";
|
|
174
174
|
import path14 from "path";
|
|
175
175
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
176
|
-
import { eq as
|
|
176
|
+
import { eq as eq32 } from "drizzle-orm";
|
|
177
177
|
import Fastify from "fastify";
|
|
178
178
|
|
|
179
179
|
// ../api-routes/src/auth.ts
|
|
@@ -2353,8 +2353,330 @@ async function intelligenceRoutes(app) {
|
|
|
2353
2353
|
});
|
|
2354
2354
|
}
|
|
2355
2355
|
|
|
2356
|
+
// ../api-routes/src/composites.ts
|
|
2357
|
+
import { eq as eq12, and as and3, desc as desc5, sql as sql3, like, or as or2 } from "drizzle-orm";
|
|
2358
|
+
var TOP_INSIGHT_LIMIT = 5;
|
|
2359
|
+
var SEARCH_HIT_HARD_LIMIT = 50;
|
|
2360
|
+
var SEARCH_SNIPPET_RADIUS = 80;
|
|
2361
|
+
async function compositeRoutes(app) {
|
|
2362
|
+
app.get("/projects/:name/overview", async (request, reply) => {
|
|
2363
|
+
const project = resolveProject(app.db, request.params.name);
|
|
2364
|
+
const totalRunsRow = app.db.select({ count: sql3`count(*)` }).from(runs).where(eq12(runs.projectId, project.id)).get();
|
|
2365
|
+
const totalRuns = totalRunsRow?.count ?? 0;
|
|
2366
|
+
const recentRuns = app.db.select().from(runs).where(eq12(runs.projectId, project.id)).orderBy(desc5(runs.createdAt)).limit(2).all();
|
|
2367
|
+
const [latestRunRow, previousRunRow] = recentRuns;
|
|
2368
|
+
const latestRun = latestRunRow ? { totalRuns, run: summarizeRun(latestRunRow) } : { totalRuns: 0, run: null };
|
|
2369
|
+
const healthRow = app.db.select().from(healthSnapshots).where(eq12(healthSnapshots.projectId, project.id)).orderBy(desc5(healthSnapshots.createdAt)).limit(1).get();
|
|
2370
|
+
const health = healthRow ? mapHealthRow2(healthRow) : null;
|
|
2371
|
+
const insightRows = app.db.select().from(insights).where(eq12(insights.projectId, project.id)).orderBy(desc5(insights.createdAt)).all();
|
|
2372
|
+
const topInsights = insightRows.filter((row) => !row.dismissed).slice(0, TOP_INSIGHT_LIMIT).map(mapInsightRow2);
|
|
2373
|
+
const { keywordCounts, providers } = summarizeLatestRun(app, latestRunRow ?? null);
|
|
2374
|
+
const transitions = summarizeTransitions(app, latestRunRow ?? null, previousRunRow ?? null);
|
|
2375
|
+
const result = {
|
|
2376
|
+
project: formatProject2(project),
|
|
2377
|
+
latestRun,
|
|
2378
|
+
health,
|
|
2379
|
+
topInsights,
|
|
2380
|
+
keywordCounts,
|
|
2381
|
+
providers,
|
|
2382
|
+
transitions
|
|
2383
|
+
};
|
|
2384
|
+
return reply.send(result);
|
|
2385
|
+
});
|
|
2386
|
+
app.get("/projects/:name/search", async (request, reply) => {
|
|
2387
|
+
const project = resolveProject(app.db, request.params.name);
|
|
2388
|
+
const rawQuery = (request.query.q ?? "").trim();
|
|
2389
|
+
if (rawQuery.length < 2) {
|
|
2390
|
+
throw validationError('"q" must be at least 2 characters');
|
|
2391
|
+
}
|
|
2392
|
+
const limit = clampSearchLimit(request.query.limit);
|
|
2393
|
+
const escaped = escapeLikePattern(rawQuery);
|
|
2394
|
+
const pattern = `%${escaped}%`;
|
|
2395
|
+
const snapshotMatches = app.db.select({
|
|
2396
|
+
id: querySnapshots.id,
|
|
2397
|
+
runId: querySnapshots.runId,
|
|
2398
|
+
keywordId: querySnapshots.keywordId,
|
|
2399
|
+
keywordText: keywords.keyword,
|
|
2400
|
+
provider: querySnapshots.provider,
|
|
2401
|
+
model: querySnapshots.model,
|
|
2402
|
+
citationState: querySnapshots.citationState,
|
|
2403
|
+
answerText: querySnapshots.answerText,
|
|
2404
|
+
citedDomains: querySnapshots.citedDomains,
|
|
2405
|
+
rawResponse: querySnapshots.rawResponse,
|
|
2406
|
+
createdAt: querySnapshots.createdAt
|
|
2407
|
+
}).from(querySnapshots).innerJoin(keywords, eq12(querySnapshots.keywordId, keywords.id)).where(
|
|
2408
|
+
and3(
|
|
2409
|
+
eq12(keywords.projectId, project.id),
|
|
2410
|
+
or2(
|
|
2411
|
+
sql3`${querySnapshots.answerText} LIKE ${pattern} ESCAPE '\\'`,
|
|
2412
|
+
sql3`${querySnapshots.citedDomains} LIKE ${pattern} ESCAPE '\\'`,
|
|
2413
|
+
sql3`${querySnapshots.rawResponse} LIKE ${pattern} ESCAPE '\\'`,
|
|
2414
|
+
like(keywords.keyword, pattern)
|
|
2415
|
+
)
|
|
2416
|
+
)
|
|
2417
|
+
).orderBy(desc5(querySnapshots.createdAt)).limit(limit + 1).all();
|
|
2418
|
+
const insightMatches = app.db.select().from(insights).where(
|
|
2419
|
+
and3(
|
|
2420
|
+
eq12(insights.projectId, project.id),
|
|
2421
|
+
or2(
|
|
2422
|
+
like(insights.title, pattern),
|
|
2423
|
+
like(insights.keyword, pattern),
|
|
2424
|
+
sql3`${insights.recommendation} LIKE ${pattern} ESCAPE '\\'`,
|
|
2425
|
+
sql3`${insights.cause} LIKE ${pattern} ESCAPE '\\'`
|
|
2426
|
+
)
|
|
2427
|
+
)
|
|
2428
|
+
).orderBy(desc5(insights.createdAt)).limit(limit + 1).all();
|
|
2429
|
+
const hits = [];
|
|
2430
|
+
for (const row of snapshotMatches) {
|
|
2431
|
+
hits.push(buildSnapshotHit(row, rawQuery));
|
|
2432
|
+
}
|
|
2433
|
+
for (const row of insightMatches) {
|
|
2434
|
+
hits.push(buildInsightHit(row, rawQuery));
|
|
2435
|
+
}
|
|
2436
|
+
hits.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
2437
|
+
const truncated = hits.length > limit;
|
|
2438
|
+
const trimmed = truncated ? hits.slice(0, limit) : hits;
|
|
2439
|
+
const response = {
|
|
2440
|
+
query: rawQuery,
|
|
2441
|
+
totalHits: trimmed.length,
|
|
2442
|
+
truncated,
|
|
2443
|
+
hits: trimmed
|
|
2444
|
+
};
|
|
2445
|
+
return reply.send(response);
|
|
2446
|
+
});
|
|
2447
|
+
}
|
|
2448
|
+
function clampSearchLimit(raw) {
|
|
2449
|
+
if (!raw) return 25;
|
|
2450
|
+
const parsed = Number.parseInt(raw, 10);
|
|
2451
|
+
if (Number.isNaN(parsed)) return 25;
|
|
2452
|
+
if (parsed < 1) return 1;
|
|
2453
|
+
if (parsed > SEARCH_HIT_HARD_LIMIT) return SEARCH_HIT_HARD_LIMIT;
|
|
2454
|
+
return parsed;
|
|
2455
|
+
}
|
|
2456
|
+
function escapeLikePattern(value) {
|
|
2457
|
+
return value.replace(/[\\%_]/g, (match) => `\\${match}`);
|
|
2458
|
+
}
|
|
2459
|
+
function summarizeRun(run) {
|
|
2460
|
+
return {
|
|
2461
|
+
id: run.id,
|
|
2462
|
+
projectId: run.projectId,
|
|
2463
|
+
kind: run.kind,
|
|
2464
|
+
status: run.status,
|
|
2465
|
+
trigger: run.trigger,
|
|
2466
|
+
location: run.location,
|
|
2467
|
+
startedAt: run.startedAt,
|
|
2468
|
+
finishedAt: run.finishedAt,
|
|
2469
|
+
error: parseRunError(run.error),
|
|
2470
|
+
createdAt: run.createdAt
|
|
2471
|
+
};
|
|
2472
|
+
}
|
|
2473
|
+
function summarizeLatestRun(app, run) {
|
|
2474
|
+
const empty = {
|
|
2475
|
+
keywordCounts: { totalKeywords: 0, citedKeywords: 0, notCitedKeywords: 0, citedRate: 0 },
|
|
2476
|
+
providers: []
|
|
2477
|
+
};
|
|
2478
|
+
if (!run) return empty;
|
|
2479
|
+
const rows = app.db.select({
|
|
2480
|
+
keywordId: querySnapshots.keywordId,
|
|
2481
|
+
provider: querySnapshots.provider,
|
|
2482
|
+
citationState: querySnapshots.citationState
|
|
2483
|
+
}).from(querySnapshots).where(eq12(querySnapshots.runId, run.id)).all();
|
|
2484
|
+
if (rows.length === 0) return empty;
|
|
2485
|
+
const perKeyword = /* @__PURE__ */ new Map();
|
|
2486
|
+
const perProvider = /* @__PURE__ */ new Map();
|
|
2487
|
+
for (const row of rows) {
|
|
2488
|
+
const cited = row.citationState === "cited";
|
|
2489
|
+
if (!perKeyword.has(row.keywordId) || cited) {
|
|
2490
|
+
perKeyword.set(row.keywordId, cited);
|
|
2491
|
+
}
|
|
2492
|
+
const bucket = perProvider.get(row.provider) ?? { cited: 0, total: 0 };
|
|
2493
|
+
bucket.total += 1;
|
|
2494
|
+
if (cited) bucket.cited += 1;
|
|
2495
|
+
perProvider.set(row.provider, bucket);
|
|
2496
|
+
}
|
|
2497
|
+
const totalKeywords = perKeyword.size;
|
|
2498
|
+
let citedKeywords = 0;
|
|
2499
|
+
for (const wasCited of perKeyword.values()) {
|
|
2500
|
+
if (wasCited) citedKeywords += 1;
|
|
2501
|
+
}
|
|
2502
|
+
const notCitedKeywords = totalKeywords - citedKeywords;
|
|
2503
|
+
const citedRate = totalKeywords === 0 ? 0 : Number((citedKeywords / totalKeywords).toFixed(4));
|
|
2504
|
+
const providers = [...perProvider.entries()].map(([provider, { cited, total }]) => ({
|
|
2505
|
+
provider,
|
|
2506
|
+
cited,
|
|
2507
|
+
total,
|
|
2508
|
+
citedRate: total === 0 ? 0 : Number((cited / total).toFixed(4))
|
|
2509
|
+
})).sort((a, b) => a.provider.localeCompare(b.provider));
|
|
2510
|
+
return {
|
|
2511
|
+
keywordCounts: { totalKeywords, citedKeywords, notCitedKeywords, citedRate },
|
|
2512
|
+
providers
|
|
2513
|
+
};
|
|
2514
|
+
}
|
|
2515
|
+
function summarizeTransitions(app, latest, previous) {
|
|
2516
|
+
const empty = { since: null, gained: 0, lost: 0, emerging: 0 };
|
|
2517
|
+
if (!latest || !previous) return empty;
|
|
2518
|
+
const fetchCited = (runId) => {
|
|
2519
|
+
const rows = app.db.select({
|
|
2520
|
+
keywordId: querySnapshots.keywordId,
|
|
2521
|
+
citationState: querySnapshots.citationState
|
|
2522
|
+
}).from(querySnapshots).where(eq12(querySnapshots.runId, runId)).all();
|
|
2523
|
+
const map = /* @__PURE__ */ new Map();
|
|
2524
|
+
for (const row of rows) {
|
|
2525
|
+
const cited = row.citationState === "cited";
|
|
2526
|
+
if (!map.has(row.keywordId) || cited) map.set(row.keywordId, cited);
|
|
2527
|
+
}
|
|
2528
|
+
return map;
|
|
2529
|
+
};
|
|
2530
|
+
const latestMap = fetchCited(latest.id);
|
|
2531
|
+
const previousMap = fetchCited(previous.id);
|
|
2532
|
+
let gained = 0;
|
|
2533
|
+
let lost = 0;
|
|
2534
|
+
let emerging = 0;
|
|
2535
|
+
for (const [keywordId, latestCited] of latestMap) {
|
|
2536
|
+
const previousCited = previousMap.get(keywordId);
|
|
2537
|
+
if (previousCited === void 0) {
|
|
2538
|
+
if (latestCited) emerging += 1;
|
|
2539
|
+
continue;
|
|
2540
|
+
}
|
|
2541
|
+
if (latestCited && !previousCited) gained += 1;
|
|
2542
|
+
else if (!latestCited && previousCited) lost += 1;
|
|
2543
|
+
}
|
|
2544
|
+
return { since: previous.createdAt, gained, lost, emerging };
|
|
2545
|
+
}
|
|
2546
|
+
function mapInsightRow2(r) {
|
|
2547
|
+
return {
|
|
2548
|
+
id: r.id,
|
|
2549
|
+
projectId: r.projectId,
|
|
2550
|
+
runId: r.runId ?? null,
|
|
2551
|
+
type: r.type,
|
|
2552
|
+
severity: r.severity,
|
|
2553
|
+
title: r.title,
|
|
2554
|
+
keyword: r.keyword,
|
|
2555
|
+
provider: r.provider,
|
|
2556
|
+
recommendation: parseJsonColumn(r.recommendation, void 0),
|
|
2557
|
+
cause: parseJsonColumn(r.cause, void 0),
|
|
2558
|
+
dismissed: r.dismissed,
|
|
2559
|
+
createdAt: r.createdAt
|
|
2560
|
+
};
|
|
2561
|
+
}
|
|
2562
|
+
function mapHealthRow2(r) {
|
|
2563
|
+
return {
|
|
2564
|
+
id: r.id,
|
|
2565
|
+
projectId: r.projectId,
|
|
2566
|
+
runId: r.runId ?? null,
|
|
2567
|
+
overallCitedRate: Number(r.overallCitedRate),
|
|
2568
|
+
totalPairs: r.totalPairs,
|
|
2569
|
+
citedPairs: r.citedPairs,
|
|
2570
|
+
providerBreakdown: parseJsonColumn(r.providerBreakdown, {}),
|
|
2571
|
+
createdAt: r.createdAt,
|
|
2572
|
+
status: "ready"
|
|
2573
|
+
};
|
|
2574
|
+
}
|
|
2575
|
+
function formatProject2(row) {
|
|
2576
|
+
return {
|
|
2577
|
+
id: row.id,
|
|
2578
|
+
name: row.name,
|
|
2579
|
+
displayName: row.displayName,
|
|
2580
|
+
canonicalDomain: row.canonicalDomain,
|
|
2581
|
+
ownedDomains: parseJsonColumn(row.ownedDomains, []),
|
|
2582
|
+
country: row.country,
|
|
2583
|
+
language: row.language,
|
|
2584
|
+
tags: parseJsonColumn(row.tags, []),
|
|
2585
|
+
labels: parseJsonColumn(row.labels, {}),
|
|
2586
|
+
locations: parseJsonColumn(row.locations, []),
|
|
2587
|
+
defaultLocation: row.defaultLocation,
|
|
2588
|
+
autoExtractBacklinks: row.autoExtractBacklinks === 1,
|
|
2589
|
+
configSource: row.configSource,
|
|
2590
|
+
configRevision: row.configRevision,
|
|
2591
|
+
createdAt: row.createdAt,
|
|
2592
|
+
updatedAt: row.updatedAt
|
|
2593
|
+
};
|
|
2594
|
+
}
|
|
2595
|
+
function buildSnapshotHit(row, query) {
|
|
2596
|
+
const lower = query.toLowerCase();
|
|
2597
|
+
const keyword = row.keywordText ?? "";
|
|
2598
|
+
const answer = row.answerText ?? "";
|
|
2599
|
+
const cited = row.citedDomains;
|
|
2600
|
+
const raw = row.rawResponse ?? "";
|
|
2601
|
+
let matchedField;
|
|
2602
|
+
let snippet;
|
|
2603
|
+
if (answer.toLowerCase().includes(lower)) {
|
|
2604
|
+
matchedField = "answerText";
|
|
2605
|
+
snippet = makeSnippet(answer, query);
|
|
2606
|
+
} else if (cited.toLowerCase().includes(lower)) {
|
|
2607
|
+
matchedField = "citedDomains";
|
|
2608
|
+
snippet = makeSnippet(cited, query);
|
|
2609
|
+
} else if (raw.toLowerCase().includes(lower)) {
|
|
2610
|
+
matchedField = "searchQueries";
|
|
2611
|
+
snippet = makeSnippet(raw, query);
|
|
2612
|
+
} else {
|
|
2613
|
+
matchedField = "keyword";
|
|
2614
|
+
snippet = keyword;
|
|
2615
|
+
}
|
|
2616
|
+
return {
|
|
2617
|
+
kind: "snapshot",
|
|
2618
|
+
id: row.id,
|
|
2619
|
+
runId: row.runId,
|
|
2620
|
+
keyword,
|
|
2621
|
+
provider: row.provider,
|
|
2622
|
+
model: row.model,
|
|
2623
|
+
citationState: row.citationState,
|
|
2624
|
+
matchedField,
|
|
2625
|
+
snippet,
|
|
2626
|
+
createdAt: row.createdAt
|
|
2627
|
+
};
|
|
2628
|
+
}
|
|
2629
|
+
function buildInsightHit(row, query) {
|
|
2630
|
+
const lower = query.toLowerCase();
|
|
2631
|
+
const recommendation = row.recommendation ?? "";
|
|
2632
|
+
const cause = row.cause ?? "";
|
|
2633
|
+
let matchedField;
|
|
2634
|
+
let snippet;
|
|
2635
|
+
if (row.title.toLowerCase().includes(lower)) {
|
|
2636
|
+
matchedField = "title";
|
|
2637
|
+
snippet = makeSnippet(row.title, query);
|
|
2638
|
+
} else if (row.keyword.toLowerCase().includes(lower)) {
|
|
2639
|
+
matchedField = "keyword";
|
|
2640
|
+
snippet = row.keyword;
|
|
2641
|
+
} else if (recommendation.toLowerCase().includes(lower)) {
|
|
2642
|
+
matchedField = "recommendation";
|
|
2643
|
+
snippet = makeSnippet(recommendation, query);
|
|
2644
|
+
} else {
|
|
2645
|
+
matchedField = "cause";
|
|
2646
|
+
snippet = makeSnippet(cause, query);
|
|
2647
|
+
}
|
|
2648
|
+
return {
|
|
2649
|
+
kind: "insight",
|
|
2650
|
+
id: row.id,
|
|
2651
|
+
runId: row.runId ?? null,
|
|
2652
|
+
type: row.type,
|
|
2653
|
+
severity: row.severity,
|
|
2654
|
+
title: row.title,
|
|
2655
|
+
keyword: row.keyword,
|
|
2656
|
+
provider: row.provider,
|
|
2657
|
+
matchedField,
|
|
2658
|
+
snippet,
|
|
2659
|
+
dismissed: row.dismissed,
|
|
2660
|
+
createdAt: row.createdAt
|
|
2661
|
+
};
|
|
2662
|
+
}
|
|
2663
|
+
function makeSnippet(text, query) {
|
|
2664
|
+
if (!text) return "";
|
|
2665
|
+
const needle = query.toLowerCase();
|
|
2666
|
+
const haystack = text.toLowerCase();
|
|
2667
|
+
const idx = haystack.indexOf(needle);
|
|
2668
|
+
if (idx < 0) {
|
|
2669
|
+
return text.length <= SEARCH_SNIPPET_RADIUS * 2 ? text : `${text.slice(0, SEARCH_SNIPPET_RADIUS * 2)}\u2026`;
|
|
2670
|
+
}
|
|
2671
|
+
const start = Math.max(0, idx - SEARCH_SNIPPET_RADIUS);
|
|
2672
|
+
const end = Math.min(text.length, idx + query.length + SEARCH_SNIPPET_RADIUS);
|
|
2673
|
+
const prefix = start === 0 ? "" : "\u2026";
|
|
2674
|
+
const suffix = end === text.length ? "" : "\u2026";
|
|
2675
|
+
return `${prefix}${text.slice(start, end)}${suffix}`;
|
|
2676
|
+
}
|
|
2677
|
+
|
|
2356
2678
|
// ../api-routes/src/content-data.ts
|
|
2357
|
-
import { and as
|
|
2679
|
+
import { and as and4, eq as eq13, desc as desc6, inArray as inArray3 } from "drizzle-orm";
|
|
2358
2680
|
var RECENT_RUNS_WINDOW = 5;
|
|
2359
2681
|
function loadOrchestratorInput(db, project) {
|
|
2360
2682
|
const projectId = project.id;
|
|
@@ -2400,43 +2722,43 @@ function loadOrchestratorInput(db, project) {
|
|
|
2400
2722
|
};
|
|
2401
2723
|
}
|
|
2402
2724
|
function listKeywords(db, projectId) {
|
|
2403
|
-
const rows = db.select({ text: keywords.keyword }).from(keywords).where(
|
|
2725
|
+
const rows = db.select({ text: keywords.keyword }).from(keywords).where(eq13(keywords.projectId, projectId)).all();
|
|
2404
2726
|
return rows.map((r) => r.text);
|
|
2405
2727
|
}
|
|
2406
2728
|
function listCompetitorDomains(db, projectId) {
|
|
2407
|
-
const rows = db.select({ domain: competitors.domain }).from(competitors).where(
|
|
2729
|
+
const rows = db.select({ domain: competitors.domain }).from(competitors).where(eq13(competitors.projectId, projectId)).all();
|
|
2408
2730
|
return rows.map((r) => r.domain);
|
|
2409
2731
|
}
|
|
2410
2732
|
function listRecentAnswerVisibilityRunIds(db, projectId, limit) {
|
|
2411
2733
|
const rows = db.select({ id: runs.id }).from(runs).where(
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2734
|
+
and4(
|
|
2735
|
+
eq13(runs.projectId, projectId),
|
|
2736
|
+
eq13(runs.kind, RunKinds["answer-visibility"]),
|
|
2415
2737
|
// Queued/running/failed/cancelled runs may have partial or no
|
|
2416
2738
|
// snapshots; including them risks pointing latestRunId at a run with
|
|
2417
2739
|
// no usable evidence.
|
|
2418
2740
|
inArray3(runs.status, [RunStatuses.completed, RunStatuses.partial])
|
|
2419
2741
|
)
|
|
2420
|
-
).orderBy(
|
|
2742
|
+
).orderBy(desc6(runs.createdAt)).limit(limit).all();
|
|
2421
2743
|
return rows.map((r) => r.id);
|
|
2422
2744
|
}
|
|
2423
2745
|
function lookupRunTimestamp(db, runId) {
|
|
2424
|
-
const row = db.select({ createdAt: runs.createdAt }).from(runs).where(
|
|
2746
|
+
const row = db.select({ createdAt: runs.createdAt }).from(runs).where(eq13(runs.id, runId)).get();
|
|
2425
2747
|
return row?.createdAt ?? "";
|
|
2426
2748
|
}
|
|
2427
2749
|
function listGscPagesForProject(db, projectId) {
|
|
2428
|
-
const rows = db.selectDistinct({ page: gscSearchData.page }).from(gscSearchData).where(
|
|
2750
|
+
const rows = db.selectDistinct({ page: gscSearchData.page }).from(gscSearchData).where(eq13(gscSearchData.projectId, projectId)).all();
|
|
2429
2751
|
return rows.map((r) => r.page);
|
|
2430
2752
|
}
|
|
2431
2753
|
function listGa4LandingPagesForProject(db, projectId) {
|
|
2432
|
-
const rows = db.selectDistinct({ landingPage: gaTrafficSnapshots.landingPage }).from(gaTrafficSnapshots).where(
|
|
2754
|
+
const rows = db.selectDistinct({ landingPage: gaTrafficSnapshots.landingPage }).from(gaTrafficSnapshots).where(eq13(gaTrafficSnapshots.projectId, projectId)).all();
|
|
2433
2755
|
return rows.map((r) => r.landingPage);
|
|
2434
2756
|
}
|
|
2435
2757
|
function buildGaTrafficByPage(db, projectId) {
|
|
2436
2758
|
const rows = db.select({
|
|
2437
2759
|
landingPage: gaTrafficSnapshots.landingPage,
|
|
2438
2760
|
sessions: gaTrafficSnapshots.sessions
|
|
2439
|
-
}).from(gaTrafficSnapshots).where(
|
|
2761
|
+
}).from(gaTrafficSnapshots).where(eq13(gaTrafficSnapshots.projectId, projectId)).all();
|
|
2440
2762
|
const map = /* @__PURE__ */ new Map();
|
|
2441
2763
|
for (const row of rows) {
|
|
2442
2764
|
const path15 = extractPath(row.landingPage);
|
|
@@ -2446,14 +2768,14 @@ function buildGaTrafficByPage(db, projectId) {
|
|
|
2446
2768
|
return map;
|
|
2447
2769
|
}
|
|
2448
2770
|
function sumAiReferralSessions(db, projectId) {
|
|
2449
|
-
const rows = db.select({ sessions: gaAiReferrals.sessions }).from(gaAiReferrals).where(
|
|
2771
|
+
const rows = db.select({ sessions: gaAiReferrals.sessions }).from(gaAiReferrals).where(eq13(gaAiReferrals.projectId, projectId)).all();
|
|
2450
2772
|
return rows.reduce((acc, r) => acc + (r.sessions ?? 0), 0);
|
|
2451
2773
|
}
|
|
2452
2774
|
function buildCandidateQueries(opts) {
|
|
2453
2775
|
if (opts.candidateQueryStrings.length === 0 || opts.recentRunIds.length === 0) {
|
|
2454
2776
|
return opts.candidateQueryStrings.map((query) => emptyCandidate(query));
|
|
2455
2777
|
}
|
|
2456
|
-
const keywordRows = opts.db.select({ id: keywords.id, text: keywords.keyword }).from(keywords).where(
|
|
2778
|
+
const keywordRows = opts.db.select({ id: keywords.id, text: keywords.keyword }).from(keywords).where(eq13(keywords.projectId, opts.projectId)).all();
|
|
2457
2779
|
const keywordIdByText = new Map(keywordRows.map((r) => [r.text, r.id]));
|
|
2458
2780
|
const candidateKeywordIds = opts.candidateQueryStrings.map((q) => keywordIdByText.get(q)).filter((id) => Boolean(id));
|
|
2459
2781
|
const snapshotRows = opts.db.select().from(querySnapshots).where(inArray3(querySnapshots.runId, opts.recentRunIds)).all().filter((r) => candidateKeywordIds.includes(r.keywordId));
|
|
@@ -2463,7 +2785,7 @@ function buildCandidateQueries(opts) {
|
|
|
2463
2785
|
list.push(row);
|
|
2464
2786
|
snapshotsByKeyword.set(row.keywordId, list);
|
|
2465
2787
|
}
|
|
2466
|
-
const gscRows = opts.db.select().from(gscSearchData).where(
|
|
2788
|
+
const gscRows = opts.db.select().from(gscSearchData).where(eq13(gscSearchData.projectId, opts.projectId)).all();
|
|
2467
2789
|
const gscByQuery = aggregateGscByQuery(gscRows);
|
|
2468
2790
|
return opts.candidateQueryStrings.map((query) => {
|
|
2469
2791
|
const keywordId = keywordIdByText.get(query);
|
|
@@ -4952,6 +5274,35 @@ var routeCatalog = [
|
|
|
4952
5274
|
404: { description: "Project not found." }
|
|
4953
5275
|
}
|
|
4954
5276
|
},
|
|
5277
|
+
{
|
|
5278
|
+
method: "get",
|
|
5279
|
+
path: "/api/v1/projects/{name}/overview",
|
|
5280
|
+
summary: "Get a composite overview of project health",
|
|
5281
|
+
description: 'Bundles project info, latest run, top undismissed insights, the latest health snapshot, keyword cited rate, per-provider breakdown, and transitions vs. the previous run. Designed for the "how is project X doing?" question so agents can answer in one call.',
|
|
5282
|
+
tags: ["intelligence"],
|
|
5283
|
+
parameters: [nameParameter],
|
|
5284
|
+
responses: {
|
|
5285
|
+
200: { description: "Overview returned." },
|
|
5286
|
+
404: { description: "Project not found." }
|
|
5287
|
+
}
|
|
5288
|
+
},
|
|
5289
|
+
{
|
|
5290
|
+
method: "get",
|
|
5291
|
+
path: "/api/v1/projects/{name}/search",
|
|
5292
|
+
summary: "Search query snapshots and insights for text",
|
|
5293
|
+
description: "Returns the most recent snapshots and insights whose answer text, cited domains, raw response, or insight title/keyword/recommendation/cause matches the query. Use to find anything mentioning a competitor, term, or URL without paginating snapshots.",
|
|
5294
|
+
tags: ["intelligence"],
|
|
5295
|
+
parameters: [
|
|
5296
|
+
nameParameter,
|
|
5297
|
+
{ name: "q", in: "query", required: true, description: "Search term (>= 2 chars).", schema: stringSchema },
|
|
5298
|
+
{ name: "limit", in: "query", description: "Max combined hits (1-50, default 25).", schema: stringSchema }
|
|
5299
|
+
],
|
|
5300
|
+
responses: {
|
|
5301
|
+
200: { description: "Search hits returned." },
|
|
5302
|
+
400: { description: "Query string missing or too short." },
|
|
5303
|
+
404: { description: "Project not found." }
|
|
5304
|
+
}
|
|
5305
|
+
},
|
|
4955
5306
|
{
|
|
4956
5307
|
method: "get",
|
|
4957
5308
|
path: "/api/v1/backlinks/status",
|
|
@@ -5410,7 +5761,7 @@ async function telemetryRoutes(app, opts) {
|
|
|
5410
5761
|
|
|
5411
5762
|
// ../api-routes/src/schedules.ts
|
|
5412
5763
|
import crypto11 from "crypto";
|
|
5413
|
-
import { eq as
|
|
5764
|
+
import { eq as eq14 } from "drizzle-orm";
|
|
5414
5765
|
async function scheduleRoutes(app, opts) {
|
|
5415
5766
|
app.put("/projects/:name/schedule", async (request, reply) => {
|
|
5416
5767
|
const project = resolveProject(app.db, request.params.name);
|
|
@@ -5453,7 +5804,7 @@ async function scheduleRoutes(app, opts) {
|
|
|
5453
5804
|
}
|
|
5454
5805
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5455
5806
|
const enabledInt = enabled === false ? 0 : 1;
|
|
5456
|
-
const existing = app.db.select().from(schedules).where(
|
|
5807
|
+
const existing = app.db.select().from(schedules).where(eq14(schedules.projectId, project.id)).get();
|
|
5457
5808
|
if (existing) {
|
|
5458
5809
|
app.db.update(schedules).set({
|
|
5459
5810
|
cronExpr,
|
|
@@ -5462,7 +5813,7 @@ async function scheduleRoutes(app, opts) {
|
|
|
5462
5813
|
providers: JSON.stringify(providers),
|
|
5463
5814
|
enabled: enabledInt,
|
|
5464
5815
|
updatedAt: now
|
|
5465
|
-
}).where(
|
|
5816
|
+
}).where(eq14(schedules.id, existing.id)).run();
|
|
5466
5817
|
} else {
|
|
5467
5818
|
app.db.insert(schedules).values({
|
|
5468
5819
|
id: crypto11.randomUUID(),
|
|
@@ -5484,12 +5835,12 @@ async function scheduleRoutes(app, opts) {
|
|
|
5484
5835
|
diff: { cronExpr, preset, timezone, providers }
|
|
5485
5836
|
});
|
|
5486
5837
|
opts.onScheduleUpdated?.("upsert", project.id);
|
|
5487
|
-
const schedule = app.db.select().from(schedules).where(
|
|
5838
|
+
const schedule = app.db.select().from(schedules).where(eq14(schedules.projectId, project.id)).get();
|
|
5488
5839
|
return reply.status(existing ? 200 : 201).send(formatSchedule(schedule));
|
|
5489
5840
|
});
|
|
5490
5841
|
app.get("/projects/:name/schedule", async (request, reply) => {
|
|
5491
5842
|
const project = resolveProject(app.db, request.params.name);
|
|
5492
|
-
const schedule = app.db.select().from(schedules).where(
|
|
5843
|
+
const schedule = app.db.select().from(schedules).where(eq14(schedules.projectId, project.id)).get();
|
|
5493
5844
|
if (!schedule) {
|
|
5494
5845
|
throw notFound("Schedule", request.params.name);
|
|
5495
5846
|
}
|
|
@@ -5497,11 +5848,11 @@ async function scheduleRoutes(app, opts) {
|
|
|
5497
5848
|
});
|
|
5498
5849
|
app.delete("/projects/:name/schedule", async (request, reply) => {
|
|
5499
5850
|
const project = resolveProject(app.db, request.params.name);
|
|
5500
|
-
const schedule = app.db.select().from(schedules).where(
|
|
5851
|
+
const schedule = app.db.select().from(schedules).where(eq14(schedules.projectId, project.id)).get();
|
|
5501
5852
|
if (!schedule) {
|
|
5502
5853
|
throw notFound("Schedule", request.params.name);
|
|
5503
5854
|
}
|
|
5504
|
-
app.db.delete(schedules).where(
|
|
5855
|
+
app.db.delete(schedules).where(eq14(schedules.id, schedule.id)).run();
|
|
5505
5856
|
writeAuditLog(app.db, {
|
|
5506
5857
|
projectId: project.id,
|
|
5507
5858
|
actor: "api",
|
|
@@ -5531,7 +5882,7 @@ function formatSchedule(row) {
|
|
|
5531
5882
|
|
|
5532
5883
|
// ../api-routes/src/notifications.ts
|
|
5533
5884
|
import crypto12 from "crypto";
|
|
5534
|
-
import { eq as
|
|
5885
|
+
import { eq as eq15 } from "drizzle-orm";
|
|
5535
5886
|
var VALID_EVENTS = ["citation.lost", "citation.gained", "run.completed", "run.failed", "insight.critical", "insight.high"];
|
|
5536
5887
|
async function notificationRoutes(app) {
|
|
5537
5888
|
app.get("/notifications/events", async (_request, reply) => {
|
|
@@ -5570,22 +5921,22 @@ async function notificationRoutes(app) {
|
|
|
5570
5921
|
diff: { channel, ...redactNotificationUrl(url), events }
|
|
5571
5922
|
});
|
|
5572
5923
|
return reply.status(201).send({
|
|
5573
|
-
...formatNotification(app.db.select().from(notifications).where(
|
|
5924
|
+
...formatNotification(app.db.select().from(notifications).where(eq15(notifications.id, id)).get()),
|
|
5574
5925
|
webhookSecret
|
|
5575
5926
|
});
|
|
5576
5927
|
});
|
|
5577
5928
|
app.get("/projects/:name/notifications", async (request, reply) => {
|
|
5578
5929
|
const project = resolveProject(app.db, request.params.name);
|
|
5579
|
-
const rows = app.db.select().from(notifications).where(
|
|
5930
|
+
const rows = app.db.select().from(notifications).where(eq15(notifications.projectId, project.id)).all();
|
|
5580
5931
|
return reply.send(rows.map(formatNotification));
|
|
5581
5932
|
});
|
|
5582
5933
|
app.delete("/projects/:name/notifications/:id", async (request, reply) => {
|
|
5583
5934
|
const project = resolveProject(app.db, request.params.name);
|
|
5584
|
-
const notification = app.db.select().from(notifications).where(
|
|
5935
|
+
const notification = app.db.select().from(notifications).where(eq15(notifications.id, request.params.id)).get();
|
|
5585
5936
|
if (!notification || notification.projectId !== project.id) {
|
|
5586
5937
|
throw notFound("Notification", request.params.id);
|
|
5587
5938
|
}
|
|
5588
|
-
app.db.delete(notifications).where(
|
|
5939
|
+
app.db.delete(notifications).where(eq15(notifications.id, notification.id)).run();
|
|
5589
5940
|
writeAuditLog(app.db, {
|
|
5590
5941
|
projectId: project.id,
|
|
5591
5942
|
actor: "api",
|
|
@@ -5597,7 +5948,7 @@ async function notificationRoutes(app) {
|
|
|
5597
5948
|
});
|
|
5598
5949
|
app.post("/projects/:name/notifications/:id/test", async (request, reply) => {
|
|
5599
5950
|
const project = resolveProject(app.db, request.params.name);
|
|
5600
|
-
const notification = app.db.select().from(notifications).where(
|
|
5951
|
+
const notification = app.db.select().from(notifications).where(eq15(notifications.id, request.params.id)).get();
|
|
5601
5952
|
if (!notification || notification.projectId !== project.id) {
|
|
5602
5953
|
throw notFound("Notification", request.params.id);
|
|
5603
5954
|
}
|
|
@@ -5650,7 +6001,7 @@ function formatNotification(row) {
|
|
|
5650
6001
|
|
|
5651
6002
|
// ../api-routes/src/google.ts
|
|
5652
6003
|
import crypto14 from "crypto";
|
|
5653
|
-
import { eq as
|
|
6004
|
+
import { eq as eq16, and as and5, desc as desc7, sql as sql4 } from "drizzle-orm";
|
|
5654
6005
|
|
|
5655
6006
|
// ../integration-google/src/constants.ts
|
|
5656
6007
|
var GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
@@ -6754,20 +7105,20 @@ async function googleRoutes(app, opts) {
|
|
|
6754
7105
|
if (opts.onGscSyncRequested) {
|
|
6755
7106
|
opts.onGscSyncRequested(runId, project.id, { days, full });
|
|
6756
7107
|
}
|
|
6757
|
-
const run = app.db.select().from(runs).where(
|
|
7108
|
+
const run = app.db.select().from(runs).where(eq16(runs.id, runId)).get();
|
|
6758
7109
|
return run;
|
|
6759
7110
|
});
|
|
6760
7111
|
app.get("/projects/:name/google/gsc/performance", async (request) => {
|
|
6761
7112
|
const project = resolveProject(app.db, request.params.name);
|
|
6762
7113
|
const { startDate, endDate, query, page, limit } = request.query;
|
|
6763
7114
|
const cutoffDate = !startDate ? windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null : null;
|
|
6764
|
-
const conditions = [
|
|
6765
|
-
if (startDate) conditions.push(
|
|
6766
|
-
else if (cutoffDate) conditions.push(
|
|
6767
|
-
if (endDate) conditions.push(
|
|
6768
|
-
if (query) conditions.push(
|
|
6769
|
-
if (page) conditions.push(
|
|
6770
|
-
const rows = app.db.select().from(gscSearchData).where(
|
|
7115
|
+
const conditions = [eq16(gscSearchData.projectId, project.id)];
|
|
7116
|
+
if (startDate) conditions.push(sql4`${gscSearchData.date} >= ${startDate}`);
|
|
7117
|
+
else if (cutoffDate) conditions.push(sql4`${gscSearchData.date} >= ${cutoffDate}`);
|
|
7118
|
+
if (endDate) conditions.push(sql4`${gscSearchData.date} <= ${endDate}`);
|
|
7119
|
+
if (query) conditions.push(sql4`${gscSearchData.query} LIKE ${"%" + query + "%"}`);
|
|
7120
|
+
if (page) conditions.push(sql4`${gscSearchData.page} LIKE ${"%" + page + "%"}`);
|
|
7121
|
+
const rows = app.db.select().from(gscSearchData).where(and5(...conditions)).orderBy(desc7(gscSearchData.date)).limit(parseInt(limit ?? "500", 10)).all();
|
|
6771
7122
|
return rows.map((r) => ({
|
|
6772
7123
|
date: r.date,
|
|
6773
7124
|
query: r.query,
|
|
@@ -6839,9 +7190,9 @@ async function googleRoutes(app, opts) {
|
|
|
6839
7190
|
app.get("/projects/:name/google/gsc/inspections", async (request) => {
|
|
6840
7191
|
const project = resolveProject(app.db, request.params.name);
|
|
6841
7192
|
const { url, limit } = request.query;
|
|
6842
|
-
const conditions = [
|
|
6843
|
-
if (url) conditions.push(
|
|
6844
|
-
const rows = app.db.select().from(gscUrlInspections).where(
|
|
7193
|
+
const conditions = [eq16(gscUrlInspections.projectId, project.id)];
|
|
7194
|
+
if (url) conditions.push(eq16(gscUrlInspections.url, url));
|
|
7195
|
+
const rows = app.db.select().from(gscUrlInspections).where(and5(...conditions)).orderBy(desc7(gscUrlInspections.inspectedAt)).limit(parseInt(limit ?? "100", 10)).all();
|
|
6845
7196
|
return rows.map((r) => ({
|
|
6846
7197
|
id: r.id,
|
|
6847
7198
|
url: r.url,
|
|
@@ -6860,7 +7211,7 @@ async function googleRoutes(app, opts) {
|
|
|
6860
7211
|
});
|
|
6861
7212
|
app.get("/projects/:name/google/gsc/deindexed", async (request) => {
|
|
6862
7213
|
const project = resolveProject(app.db, request.params.name);
|
|
6863
|
-
const allInspections = app.db.select().from(gscUrlInspections).where(
|
|
7214
|
+
const allInspections = app.db.select().from(gscUrlInspections).where(eq16(gscUrlInspections.projectId, project.id)).orderBy(desc7(gscUrlInspections.inspectedAt)).all();
|
|
6864
7215
|
const byUrl = /* @__PURE__ */ new Map();
|
|
6865
7216
|
for (const row of allInspections) {
|
|
6866
7217
|
const existing = byUrl.get(row.url);
|
|
@@ -6888,7 +7239,7 @@ async function googleRoutes(app, opts) {
|
|
|
6888
7239
|
});
|
|
6889
7240
|
app.get("/projects/:name/google/gsc/coverage", async (request) => {
|
|
6890
7241
|
const project = resolveProject(app.db, request.params.name);
|
|
6891
|
-
const allInspections = app.db.select().from(gscUrlInspections).where(
|
|
7242
|
+
const allInspections = app.db.select().from(gscUrlInspections).where(eq16(gscUrlInspections.projectId, project.id)).orderBy(desc7(gscUrlInspections.inspectedAt)).all();
|
|
6892
7243
|
const canonicalUrl = (url) => url.replace(/^http:\/\//, "https://");
|
|
6893
7244
|
const latestByUrl = /* @__PURE__ */ new Map();
|
|
6894
7245
|
const historyByUrl = /* @__PURE__ */ new Map();
|
|
@@ -6985,7 +7336,7 @@ async function googleRoutes(app, opts) {
|
|
|
6985
7336
|
const project = resolveProject(app.db, request.params.name);
|
|
6986
7337
|
const parsed = parseInt(request.query.limit ?? "90", 10);
|
|
6987
7338
|
const limit = Number.isNaN(parsed) || parsed <= 0 ? 90 : parsed;
|
|
6988
|
-
const rows = app.db.select().from(gscCoverageSnapshots).where(
|
|
7339
|
+
const rows = app.db.select().from(gscCoverageSnapshots).where(eq16(gscCoverageSnapshots.projectId, project.id)).orderBy(desc7(gscCoverageSnapshots.date)).limit(limit).all();
|
|
6989
7340
|
return rows.map((r) => ({
|
|
6990
7341
|
date: r.date,
|
|
6991
7342
|
indexed: r.indexed,
|
|
@@ -7045,7 +7396,7 @@ async function googleRoutes(app, opts) {
|
|
|
7045
7396
|
if (opts.onInspectSitemapRequested) {
|
|
7046
7397
|
opts.onInspectSitemapRequested(runId, project.id, { sitemapUrl });
|
|
7047
7398
|
}
|
|
7048
|
-
const run = app.db.select().from(runs).where(
|
|
7399
|
+
const run = app.db.select().from(runs).where(eq16(runs.id, runId)).get();
|
|
7049
7400
|
return { sitemaps, primarySitemapUrl: sitemapUrl, run };
|
|
7050
7401
|
});
|
|
7051
7402
|
app.post("/projects/:name/google/gsc/inspect-sitemap", async (request) => {
|
|
@@ -7072,7 +7423,7 @@ async function googleRoutes(app, opts) {
|
|
|
7072
7423
|
if (opts.onInspectSitemapRequested) {
|
|
7073
7424
|
opts.onInspectSitemapRequested(runId, project.id, { sitemapUrl: sitemapUrl ?? void 0 });
|
|
7074
7425
|
}
|
|
7075
|
-
const run = app.db.select().from(runs).where(
|
|
7426
|
+
const run = app.db.select().from(runs).where(eq16(runs.id, runId)).get();
|
|
7076
7427
|
return run;
|
|
7077
7428
|
});
|
|
7078
7429
|
app.put("/projects/:name/google/connections/:type/sitemap", async (request) => {
|
|
@@ -7119,7 +7470,7 @@ async function googleRoutes(app, opts) {
|
|
|
7119
7470
|
const { accessToken } = await getValidToken(store, project.canonicalDomain, "gsc", googleClientId, googleClientSecret);
|
|
7120
7471
|
let urlsToNotify = request.body?.urls ?? [];
|
|
7121
7472
|
if (request.body?.allUnindexed) {
|
|
7122
|
-
const allInspections = app.db.select().from(gscUrlInspections).where(
|
|
7473
|
+
const allInspections = app.db.select().from(gscUrlInspections).where(eq16(gscUrlInspections.projectId, project.id)).orderBy(desc7(gscUrlInspections.inspectedAt)).all();
|
|
7123
7474
|
const latestByUrl = /* @__PURE__ */ new Map();
|
|
7124
7475
|
for (const row of allInspections) {
|
|
7125
7476
|
if (!latestByUrl.has(row.url)) {
|
|
@@ -7190,7 +7541,7 @@ async function googleRoutes(app, opts) {
|
|
|
7190
7541
|
|
|
7191
7542
|
// ../api-routes/src/bing.ts
|
|
7192
7543
|
import crypto15 from "crypto";
|
|
7193
|
-
import { eq as
|
|
7544
|
+
import { eq as eq17, and as and6, desc as desc8 } from "drizzle-orm";
|
|
7194
7545
|
|
|
7195
7546
|
// ../integration-bing/src/constants.ts
|
|
7196
7547
|
var BING_WMT_API_BASE = "https://ssl.bing.com/webmaster/api.svc/json";
|
|
@@ -7503,7 +7854,7 @@ async function bingRoutes(app, opts) {
|
|
|
7503
7854
|
const store = requireConnectionStore();
|
|
7504
7855
|
const project = resolveProject(app.db, request.params.name);
|
|
7505
7856
|
requireConnection(store, project.canonicalDomain);
|
|
7506
|
-
const allInspections = app.db.select().from(bingUrlInspections).where(
|
|
7857
|
+
const allInspections = app.db.select().from(bingUrlInspections).where(eq17(bingUrlInspections.projectId, project.id)).orderBy(desc8(bingUrlInspections.inspectedAt)).all();
|
|
7507
7858
|
const latestByUrl = /* @__PURE__ */ new Map();
|
|
7508
7859
|
const definitiveByUrl = /* @__PURE__ */ new Map();
|
|
7509
7860
|
for (const row of allInspections) {
|
|
@@ -7592,7 +7943,7 @@ async function bingRoutes(app, opts) {
|
|
|
7592
7943
|
const project = resolveProject(app.db, request.params.name);
|
|
7593
7944
|
const parsed = parseInt(request.query.limit ?? "90", 10);
|
|
7594
7945
|
const limit = Number.isNaN(parsed) || parsed <= 0 ? 90 : parsed;
|
|
7595
|
-
const rows = app.db.select().from(bingCoverageSnapshots).where(
|
|
7946
|
+
const rows = app.db.select().from(bingCoverageSnapshots).where(eq17(bingCoverageSnapshots.projectId, project.id)).orderBy(desc8(bingCoverageSnapshots.date)).limit(limit).all();
|
|
7596
7947
|
return rows.map((r) => ({
|
|
7597
7948
|
date: r.date,
|
|
7598
7949
|
indexed: r.indexed,
|
|
@@ -7604,8 +7955,8 @@ async function bingRoutes(app, opts) {
|
|
|
7604
7955
|
requireConnectionStore();
|
|
7605
7956
|
const project = resolveProject(app.db, request.params.name);
|
|
7606
7957
|
const { url, limit } = request.query;
|
|
7607
|
-
const whereClause = url ?
|
|
7608
|
-
const filtered = app.db.select().from(bingUrlInspections).where(whereClause).orderBy(
|
|
7958
|
+
const whereClause = url ? and6(eq17(bingUrlInspections.projectId, project.id), eq17(bingUrlInspections.url, url)) : eq17(bingUrlInspections.projectId, project.id);
|
|
7959
|
+
const filtered = app.db.select().from(bingUrlInspections).where(whereClause).orderBy(desc8(bingUrlInspections.inspectedAt)).limit(Math.max(1, Math.min(parseInt(limit ?? "100", 10) || 100, 1e3))).all();
|
|
7609
7960
|
return filtered.map((r) => ({
|
|
7610
7961
|
id: r.id,
|
|
7611
7962
|
url: r.url,
|
|
@@ -7694,7 +8045,7 @@ async function bingRoutes(app, opts) {
|
|
|
7694
8045
|
anchorCount: result.AnchorCount ?? null,
|
|
7695
8046
|
discoveryDate
|
|
7696
8047
|
}).run();
|
|
7697
|
-
app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(
|
|
8048
|
+
app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(eq17(runs.id, runId)).run();
|
|
7698
8049
|
return {
|
|
7699
8050
|
id,
|
|
7700
8051
|
url,
|
|
@@ -7710,7 +8061,7 @@ async function bingRoutes(app, opts) {
|
|
|
7710
8061
|
} catch (e) {
|
|
7711
8062
|
const msg = e instanceof Error ? e.message : String(e);
|
|
7712
8063
|
bingLog("error", "inspect-url.failed", { domain: project.canonicalDomain, url, error: msg });
|
|
7713
|
-
app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
8064
|
+
app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq17(runs.id, runId)).run();
|
|
7714
8065
|
throw e;
|
|
7715
8066
|
}
|
|
7716
8067
|
});
|
|
@@ -7737,7 +8088,7 @@ async function bingRoutes(app, opts) {
|
|
|
7737
8088
|
} else {
|
|
7738
8089
|
bingLog("warn", "inspect-sitemap.no-callback", { domain: project.canonicalDomain, runId });
|
|
7739
8090
|
}
|
|
7740
|
-
const run = app.db.select().from(runs).where(
|
|
8091
|
+
const run = app.db.select().from(runs).where(eq17(runs.id, runId)).get();
|
|
7741
8092
|
return run;
|
|
7742
8093
|
});
|
|
7743
8094
|
app.post("/projects/:name/bing/request-indexing", async (request) => {
|
|
@@ -7749,7 +8100,7 @@ async function bingRoutes(app, opts) {
|
|
|
7749
8100
|
}
|
|
7750
8101
|
let urlsToSubmit = request.body?.urls ?? [];
|
|
7751
8102
|
if (request.body?.allUnindexed) {
|
|
7752
|
-
const allInspections = app.db.select().from(bingUrlInspections).where(
|
|
8103
|
+
const allInspections = app.db.select().from(bingUrlInspections).where(eq17(bingUrlInspections.projectId, project.id)).orderBy(desc8(bingUrlInspections.inspectedAt)).all();
|
|
7753
8104
|
const latestByUrl = /* @__PURE__ */ new Map();
|
|
7754
8105
|
for (const row of allInspections) {
|
|
7755
8106
|
if (!latestByUrl.has(row.url)) {
|
|
@@ -7836,14 +8187,14 @@ async function bingRoutes(app, opts) {
|
|
|
7836
8187
|
import fs from "fs";
|
|
7837
8188
|
import path from "path";
|
|
7838
8189
|
import os from "os";
|
|
7839
|
-
import { eq as
|
|
8190
|
+
import { eq as eq18, and as and7 } from "drizzle-orm";
|
|
7840
8191
|
function getScreenshotDir() {
|
|
7841
8192
|
return path.join(os.homedir(), ".canonry", "screenshots");
|
|
7842
8193
|
}
|
|
7843
8194
|
async function cdpRoutes(app, opts) {
|
|
7844
8195
|
app.get("/screenshots/:snapshotId", async (request, reply) => {
|
|
7845
8196
|
const { snapshotId } = request.params;
|
|
7846
|
-
const snapshot = app.db.select({ screenshotPath: querySnapshots.screenshotPath }).from(querySnapshots).where(
|
|
8197
|
+
const snapshot = app.db.select({ screenshotPath: querySnapshots.screenshotPath }).from(querySnapshots).where(eq18(querySnapshots.id, snapshotId)).get();
|
|
7847
8198
|
if (!snapshot?.screenshotPath) {
|
|
7848
8199
|
const err = notFound("Screenshot", snapshotId);
|
|
7849
8200
|
return reply.code(err.statusCode).send(err.toJSON());
|
|
@@ -7909,7 +8260,7 @@ async function cdpRoutes(app, opts) {
|
|
|
7909
8260
|
async (request, reply) => {
|
|
7910
8261
|
const project = resolveProject(app.db, request.params.name);
|
|
7911
8262
|
const { runId } = request.params;
|
|
7912
|
-
const run = app.db.select().from(runs).where(
|
|
8263
|
+
const run = app.db.select().from(runs).where(and7(eq18(runs.id, runId), eq18(runs.projectId, project.id))).get();
|
|
7913
8264
|
if (!run) {
|
|
7914
8265
|
const err = notFound("Run", runId);
|
|
7915
8266
|
return reply.code(err.statusCode).send(err.toJSON());
|
|
@@ -7922,8 +8273,8 @@ async function cdpRoutes(app, opts) {
|
|
|
7922
8273
|
citedDomains: querySnapshots.citedDomains,
|
|
7923
8274
|
screenshotPath: querySnapshots.screenshotPath,
|
|
7924
8275
|
rawResponse: querySnapshots.rawResponse
|
|
7925
|
-
}).from(querySnapshots).where(
|
|
7926
|
-
const keywordRows = app.db.select({ id: keywords.id, keyword: keywords.keyword }).from(keywords).where(
|
|
8276
|
+
}).from(querySnapshots).where(eq18(querySnapshots.runId, runId)).all();
|
|
8277
|
+
const keywordRows = app.db.select({ id: keywords.id, keyword: keywords.keyword }).from(keywords).where(eq18(keywords.projectId, project.id)).all();
|
|
7927
8278
|
const keywordMap = new Map(keywordRows.map((k) => [k.id, k.keyword]));
|
|
7928
8279
|
const byKeyword = /* @__PURE__ */ new Map();
|
|
7929
8280
|
for (const snap of snapshots) {
|
|
@@ -8006,7 +8357,7 @@ async function cdpRoutes(app, opts) {
|
|
|
8006
8357
|
|
|
8007
8358
|
// ../api-routes/src/ga.ts
|
|
8008
8359
|
import crypto16 from "crypto";
|
|
8009
|
-
import { eq as
|
|
8360
|
+
import { eq as eq19, desc as desc9, and as and8, sql as sql5 } from "drizzle-orm";
|
|
8010
8361
|
function gaLog(level, action, ctx) {
|
|
8011
8362
|
const entry = { ts: (/* @__PURE__ */ new Date()).toISOString(), level, module: "GA4Routes", action, ...ctx };
|
|
8012
8363
|
const stream = level === "error" ? process.stderr : process.stdout;
|
|
@@ -8163,10 +8514,10 @@ async function ga4Routes(app, opts) {
|
|
|
8163
8514
|
if (!saConn && !oauthConn) {
|
|
8164
8515
|
throw notFound("GA4 connection", project.name);
|
|
8165
8516
|
}
|
|
8166
|
-
app.db.delete(gaTrafficSnapshots).where(
|
|
8167
|
-
app.db.delete(gaTrafficSummaries).where(
|
|
8168
|
-
app.db.delete(gaAiReferrals).where(
|
|
8169
|
-
app.db.delete(gaSocialReferrals).where(
|
|
8517
|
+
app.db.delete(gaTrafficSnapshots).where(eq19(gaTrafficSnapshots.projectId, project.id)).run();
|
|
8518
|
+
app.db.delete(gaTrafficSummaries).where(eq19(gaTrafficSummaries.projectId, project.id)).run();
|
|
8519
|
+
app.db.delete(gaAiReferrals).where(eq19(gaAiReferrals.projectId, project.id)).run();
|
|
8520
|
+
app.db.delete(gaSocialReferrals).where(eq19(gaSocialReferrals.projectId, project.id)).run();
|
|
8170
8521
|
const propertyId = saConn?.propertyId ?? oauthConn?.propertyId ?? null;
|
|
8171
8522
|
opts.ga4CredentialStore?.deleteConnection(project.name);
|
|
8172
8523
|
opts.googleConnectionStore?.deleteConnection(project.canonicalDomain, "ga4");
|
|
@@ -8187,7 +8538,7 @@ async function ga4Routes(app, opts) {
|
|
|
8187
8538
|
if (!connected) {
|
|
8188
8539
|
return { connected: false, propertyId: null, clientEmail: null, authMethod: null, lastSyncedAt: null };
|
|
8189
8540
|
}
|
|
8190
|
-
const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(
|
|
8541
|
+
const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq19(gaTrafficSummaries.projectId, project.id)).orderBy(desc9(gaTrafficSummaries.syncedAt)).limit(1).get();
|
|
8191
8542
|
return {
|
|
8192
8543
|
connected: true,
|
|
8193
8544
|
propertyId: saConn?.propertyId ?? oauthConn?.propertyId ?? null,
|
|
@@ -8246,10 +8597,10 @@ async function ga4Routes(app, opts) {
|
|
|
8246
8597
|
app.db.transaction((tx) => {
|
|
8247
8598
|
if (syncTraffic) {
|
|
8248
8599
|
tx.delete(gaTrafficSnapshots).where(
|
|
8249
|
-
|
|
8250
|
-
|
|
8251
|
-
|
|
8252
|
-
|
|
8600
|
+
and8(
|
|
8601
|
+
eq19(gaTrafficSnapshots.projectId, project.id),
|
|
8602
|
+
sql5`${gaTrafficSnapshots.date} >= ${summary.periodStart}`,
|
|
8603
|
+
sql5`${gaTrafficSnapshots.date} <= ${summary.periodEnd}`
|
|
8253
8604
|
)
|
|
8254
8605
|
).run();
|
|
8255
8606
|
for (const row of rows) {
|
|
@@ -8268,10 +8619,10 @@ async function ga4Routes(app, opts) {
|
|
|
8268
8619
|
}
|
|
8269
8620
|
if (syncAi) {
|
|
8270
8621
|
tx.delete(gaAiReferrals).where(
|
|
8271
|
-
|
|
8272
|
-
|
|
8273
|
-
|
|
8274
|
-
|
|
8622
|
+
and8(
|
|
8623
|
+
eq19(gaAiReferrals.projectId, project.id),
|
|
8624
|
+
sql5`${gaAiReferrals.date} >= ${summary.periodStart}`,
|
|
8625
|
+
sql5`${gaAiReferrals.date} <= ${summary.periodEnd}`
|
|
8275
8626
|
)
|
|
8276
8627
|
).run();
|
|
8277
8628
|
for (const row of aiReferrals) {
|
|
@@ -8291,10 +8642,10 @@ async function ga4Routes(app, opts) {
|
|
|
8291
8642
|
}
|
|
8292
8643
|
if (syncSocial) {
|
|
8293
8644
|
tx.delete(gaSocialReferrals).where(
|
|
8294
|
-
|
|
8295
|
-
|
|
8296
|
-
|
|
8297
|
-
|
|
8645
|
+
and8(
|
|
8646
|
+
eq19(gaSocialReferrals.projectId, project.id),
|
|
8647
|
+
sql5`${gaSocialReferrals.date} >= ${summary.periodStart}`,
|
|
8648
|
+
sql5`${gaSocialReferrals.date} <= ${summary.periodEnd}`
|
|
8298
8649
|
)
|
|
8299
8650
|
).run();
|
|
8300
8651
|
for (const row of socialReferrals) {
|
|
@@ -8313,7 +8664,7 @@ async function ga4Routes(app, opts) {
|
|
|
8313
8664
|
}
|
|
8314
8665
|
}
|
|
8315
8666
|
if (syncSummary) {
|
|
8316
|
-
tx.delete(gaTrafficSummaries).where(
|
|
8667
|
+
tx.delete(gaTrafficSummaries).where(eq19(gaTrafficSummaries.projectId, project.id)).run();
|
|
8317
8668
|
tx.insert(gaTrafficSummaries).values({
|
|
8318
8669
|
id: crypto16.randomUUID(),
|
|
8319
8670
|
projectId: project.id,
|
|
@@ -8327,7 +8678,7 @@ async function ga4Routes(app, opts) {
|
|
|
8327
8678
|
}).run();
|
|
8328
8679
|
}
|
|
8329
8680
|
});
|
|
8330
|
-
app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(
|
|
8681
|
+
app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(eq19(runs.id, runId)).run();
|
|
8331
8682
|
const syncedComponents = only ? [only, ...only !== "social" && only !== "ai" && only !== "traffic" ? [] : []] : void 0;
|
|
8332
8683
|
gaLog("info", "sync.complete", {
|
|
8333
8684
|
projectId: project.id,
|
|
@@ -8351,7 +8702,7 @@ async function ga4Routes(app, opts) {
|
|
|
8351
8702
|
} catch (e) {
|
|
8352
8703
|
const msg = e instanceof Error ? e.message : String(e);
|
|
8353
8704
|
gaLog("error", "sync.fetch-failed", { projectId: project.id, runId, error: msg });
|
|
8354
|
-
app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
8705
|
+
app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq19(runs.id, runId)).run();
|
|
8355
8706
|
throw e;
|
|
8356
8707
|
}
|
|
8357
8708
|
});
|
|
@@ -8362,48 +8713,48 @@ async function ga4Routes(app, opts) {
|
|
|
8362
8713
|
const window = parseWindow(request.query.window);
|
|
8363
8714
|
const cutoff = windowCutoff(window);
|
|
8364
8715
|
const cutoffDate = cutoff?.slice(0, 10) ?? null;
|
|
8365
|
-
const snapshotConditions = [
|
|
8366
|
-
if (cutoffDate) snapshotConditions.push(
|
|
8367
|
-
const aiConditions = [
|
|
8368
|
-
if (cutoffDate) aiConditions.push(
|
|
8369
|
-
const socialConditions = [
|
|
8370
|
-
if (cutoffDate) socialConditions.push(
|
|
8716
|
+
const snapshotConditions = [eq19(gaTrafficSnapshots.projectId, project.id)];
|
|
8717
|
+
if (cutoffDate) snapshotConditions.push(sql5`${gaTrafficSnapshots.date} >= ${cutoffDate}`);
|
|
8718
|
+
const aiConditions = [eq19(gaAiReferrals.projectId, project.id)];
|
|
8719
|
+
if (cutoffDate) aiConditions.push(sql5`${gaAiReferrals.date} >= ${cutoffDate}`);
|
|
8720
|
+
const socialConditions = [eq19(gaSocialReferrals.projectId, project.id)];
|
|
8721
|
+
if (cutoffDate) socialConditions.push(sql5`${gaSocialReferrals.date} >= ${cutoffDate}`);
|
|
8371
8722
|
const summaryRow = cutoffDate ? app.db.select({
|
|
8372
|
-
totalSessions:
|
|
8373
|
-
totalOrganicSessions:
|
|
8374
|
-
totalUsers:
|
|
8375
|
-
}).from(gaTrafficSnapshots).where(
|
|
8723
|
+
totalSessions: sql5`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)`,
|
|
8724
|
+
totalOrganicSessions: sql5`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)`,
|
|
8725
|
+
totalUsers: sql5`COALESCE(SUM(${gaTrafficSnapshots.users}), 0)`
|
|
8726
|
+
}).from(gaTrafficSnapshots).where(and8(...snapshotConditions)).get() : app.db.select({
|
|
8376
8727
|
totalSessions: gaTrafficSummaries.totalSessions,
|
|
8377
8728
|
totalOrganicSessions: gaTrafficSummaries.totalOrganicSessions,
|
|
8378
8729
|
totalUsers: gaTrafficSummaries.totalUsers
|
|
8379
|
-
}).from(gaTrafficSummaries).where(
|
|
8730
|
+
}).from(gaTrafficSummaries).where(eq19(gaTrafficSummaries.projectId, project.id)).get();
|
|
8380
8731
|
const summaryMeta = app.db.select({
|
|
8381
8732
|
periodStart: gaTrafficSummaries.periodStart,
|
|
8382
8733
|
periodEnd: gaTrafficSummaries.periodEnd
|
|
8383
|
-
}).from(gaTrafficSummaries).where(
|
|
8734
|
+
}).from(gaTrafficSummaries).where(eq19(gaTrafficSummaries.projectId, project.id)).get();
|
|
8384
8735
|
const rows = app.db.select({
|
|
8385
8736
|
landingPage: gaTrafficSnapshots.landingPage,
|
|
8386
|
-
sessions:
|
|
8387
|
-
organicSessions:
|
|
8388
|
-
users:
|
|
8389
|
-
}).from(gaTrafficSnapshots).where(
|
|
8737
|
+
sessions: sql5`SUM(${gaTrafficSnapshots.sessions})`,
|
|
8738
|
+
organicSessions: sql5`SUM(${gaTrafficSnapshots.organicSessions})`,
|
|
8739
|
+
users: sql5`SUM(${gaTrafficSnapshots.users})`
|
|
8740
|
+
}).from(gaTrafficSnapshots).where(and8(...snapshotConditions)).groupBy(gaTrafficSnapshots.landingPage).orderBy(sql5`SUM(${gaTrafficSnapshots.sessions}) DESC`).limit(limit).all();
|
|
8390
8741
|
const aiReferrals = app.db.select({
|
|
8391
8742
|
source: gaAiReferrals.source,
|
|
8392
8743
|
medium: gaAiReferrals.medium,
|
|
8393
8744
|
sourceDimension: gaAiReferrals.sourceDimension,
|
|
8394
|
-
sessions:
|
|
8395
|
-
users:
|
|
8396
|
-
}).from(gaAiReferrals).where(
|
|
8745
|
+
sessions: sql5`SUM(${gaAiReferrals.sessions})`,
|
|
8746
|
+
users: sql5`SUM(${gaAiReferrals.users})`
|
|
8747
|
+
}).from(gaAiReferrals).where(and8(...aiConditions)).groupBy(gaAiReferrals.source, gaAiReferrals.medium, gaAiReferrals.sourceDimension).orderBy(sql5`SUM(${gaAiReferrals.sessions}) DESC`).all();
|
|
8397
8748
|
const aiDeduped = app.db.select({
|
|
8398
|
-
sessions:
|
|
8399
|
-
users:
|
|
8749
|
+
sessions: sql5`SUM(max_sessions)`,
|
|
8750
|
+
users: sql5`SUM(max_users)`
|
|
8400
8751
|
}).from(
|
|
8401
|
-
|
|
8752
|
+
sql5`(
|
|
8402
8753
|
SELECT date, source, medium,
|
|
8403
8754
|
MAX(sessions) AS max_sessions,
|
|
8404
8755
|
MAX(users) AS max_users
|
|
8405
8756
|
FROM ga_ai_referrals
|
|
8406
|
-
WHERE project_id = ${project.id}${cutoffDate ?
|
|
8757
|
+
WHERE project_id = ${project.id}${cutoffDate ? sql5` AND date >= ${cutoffDate}` : sql5``}
|
|
8407
8758
|
GROUP BY date, source, medium
|
|
8408
8759
|
)`
|
|
8409
8760
|
).get();
|
|
@@ -8411,14 +8762,14 @@ async function ga4Routes(app, opts) {
|
|
|
8411
8762
|
source: gaSocialReferrals.source,
|
|
8412
8763
|
medium: gaSocialReferrals.medium,
|
|
8413
8764
|
channelGroup: gaSocialReferrals.channelGroup,
|
|
8414
|
-
sessions:
|
|
8415
|
-
users:
|
|
8416
|
-
}).from(gaSocialReferrals).where(
|
|
8765
|
+
sessions: sql5`SUM(${gaSocialReferrals.sessions})`,
|
|
8766
|
+
users: sql5`SUM(${gaSocialReferrals.users})`
|
|
8767
|
+
}).from(gaSocialReferrals).where(and8(...socialConditions)).groupBy(gaSocialReferrals.source, gaSocialReferrals.medium, gaSocialReferrals.channelGroup).orderBy(sql5`SUM(${gaSocialReferrals.sessions}) DESC`).all();
|
|
8417
8768
|
const socialTotals = app.db.select({
|
|
8418
|
-
sessions:
|
|
8419
|
-
users:
|
|
8420
|
-
}).from(gaSocialReferrals).where(
|
|
8421
|
-
const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(
|
|
8769
|
+
sessions: sql5`SUM(${gaSocialReferrals.sessions})`,
|
|
8770
|
+
users: sql5`SUM(${gaSocialReferrals.users})`
|
|
8771
|
+
}).from(gaSocialReferrals).where(and8(...socialConditions)).get();
|
|
8772
|
+
const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq19(gaTrafficSummaries.projectId, project.id)).orderBy(desc9(gaTrafficSummaries.syncedAt)).limit(1).get();
|
|
8422
8773
|
const total = summaryRow?.totalSessions ?? 0;
|
|
8423
8774
|
return {
|
|
8424
8775
|
totalSessions: total,
|
|
@@ -8465,8 +8816,8 @@ async function ga4Routes(app, opts) {
|
|
|
8465
8816
|
const project = resolveProject(app.db, request.params.name);
|
|
8466
8817
|
requireGa4Connection(opts, project.name, project.canonicalDomain);
|
|
8467
8818
|
const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
|
|
8468
|
-
const conditions = [
|
|
8469
|
-
if (cutoffDate) conditions.push(
|
|
8819
|
+
const conditions = [eq19(gaAiReferrals.projectId, project.id)];
|
|
8820
|
+
if (cutoffDate) conditions.push(sql5`${gaAiReferrals.date} >= ${cutoffDate}`);
|
|
8470
8821
|
const rows = app.db.select({
|
|
8471
8822
|
date: gaAiReferrals.date,
|
|
8472
8823
|
source: gaAiReferrals.source,
|
|
@@ -8474,15 +8825,15 @@ async function ga4Routes(app, opts) {
|
|
|
8474
8825
|
sourceDimension: gaAiReferrals.sourceDimension,
|
|
8475
8826
|
sessions: gaAiReferrals.sessions,
|
|
8476
8827
|
users: gaAiReferrals.users
|
|
8477
|
-
}).from(gaAiReferrals).where(
|
|
8828
|
+
}).from(gaAiReferrals).where(and8(...conditions)).orderBy(gaAiReferrals.date).all();
|
|
8478
8829
|
return rows;
|
|
8479
8830
|
});
|
|
8480
8831
|
app.get("/projects/:name/ga/social-referral-history", async (request, _reply) => {
|
|
8481
8832
|
const project = resolveProject(app.db, request.params.name);
|
|
8482
8833
|
requireGa4Connection(opts, project.name, project.canonicalDomain);
|
|
8483
8834
|
const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
|
|
8484
|
-
const conditions = [
|
|
8485
|
-
if (cutoffDate) conditions.push(
|
|
8835
|
+
const conditions = [eq19(gaSocialReferrals.projectId, project.id)];
|
|
8836
|
+
if (cutoffDate) conditions.push(sql5`${gaSocialReferrals.date} >= ${cutoffDate}`);
|
|
8486
8837
|
const rows = app.db.select({
|
|
8487
8838
|
date: gaSocialReferrals.date,
|
|
8488
8839
|
source: gaSocialReferrals.source,
|
|
@@ -8490,7 +8841,7 @@ async function ga4Routes(app, opts) {
|
|
|
8490
8841
|
channelGroup: gaSocialReferrals.channelGroup,
|
|
8491
8842
|
sessions: gaSocialReferrals.sessions,
|
|
8492
8843
|
users: gaSocialReferrals.users
|
|
8493
|
-
}).from(gaSocialReferrals).where(
|
|
8844
|
+
}).from(gaSocialReferrals).where(and8(...conditions)).orderBy(gaSocialReferrals.date).all();
|
|
8494
8845
|
return rows;
|
|
8495
8846
|
});
|
|
8496
8847
|
app.get("/projects/:name/ga/social-referral-trend", async (request, _reply) => {
|
|
@@ -8503,10 +8854,10 @@ async function ga4Routes(app, opts) {
|
|
|
8503
8854
|
d.setDate(d.getDate() - n);
|
|
8504
8855
|
return fmt(d);
|
|
8505
8856
|
};
|
|
8506
|
-
const sumSocial = (from, to) => app.db.select({ sessions:
|
|
8507
|
-
|
|
8508
|
-
|
|
8509
|
-
|
|
8857
|
+
const sumSocial = (from, to) => app.db.select({ sessions: sql5`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and8(
|
|
8858
|
+
eq19(gaSocialReferrals.projectId, project.id),
|
|
8859
|
+
sql5`${gaSocialReferrals.date} >= ${from}`,
|
|
8860
|
+
sql5`${gaSocialReferrals.date} < ${to}`
|
|
8510
8861
|
)).get();
|
|
8511
8862
|
const current7d = sumSocial(daysAgo2(7), fmt(today));
|
|
8512
8863
|
const prev7d = sumSocial(daysAgo2(14), daysAgo2(7));
|
|
@@ -8515,19 +8866,19 @@ async function ga4Routes(app, opts) {
|
|
|
8515
8866
|
const pct = (cur, prev) => prev === 0 ? null : Math.round((cur - prev) / prev * 100);
|
|
8516
8867
|
const sourceCurrent = app.db.select({
|
|
8517
8868
|
source: gaSocialReferrals.source,
|
|
8518
|
-
sessions:
|
|
8519
|
-
}).from(gaSocialReferrals).where(
|
|
8520
|
-
|
|
8521
|
-
|
|
8522
|
-
|
|
8869
|
+
sessions: sql5`SUM(${gaSocialReferrals.sessions})`
|
|
8870
|
+
}).from(gaSocialReferrals).where(and8(
|
|
8871
|
+
eq19(gaSocialReferrals.projectId, project.id),
|
|
8872
|
+
sql5`${gaSocialReferrals.date} >= ${daysAgo2(7)}`,
|
|
8873
|
+
sql5`${gaSocialReferrals.date} < ${fmt(today)}`
|
|
8523
8874
|
)).groupBy(gaSocialReferrals.source).all();
|
|
8524
8875
|
const sourcePrev = app.db.select({
|
|
8525
8876
|
source: gaSocialReferrals.source,
|
|
8526
|
-
sessions:
|
|
8527
|
-
}).from(gaSocialReferrals).where(
|
|
8528
|
-
|
|
8529
|
-
|
|
8530
|
-
|
|
8877
|
+
sessions: sql5`SUM(${gaSocialReferrals.sessions})`
|
|
8878
|
+
}).from(gaSocialReferrals).where(and8(
|
|
8879
|
+
eq19(gaSocialReferrals.projectId, project.id),
|
|
8880
|
+
sql5`${gaSocialReferrals.date} >= ${daysAgo2(14)}`,
|
|
8881
|
+
sql5`${gaSocialReferrals.date} < ${daysAgo2(7)}`
|
|
8531
8882
|
)).groupBy(gaSocialReferrals.source).all();
|
|
8532
8883
|
const prevMap = new Map(sourcePrev.map((r) => [r.source, r.sessions]));
|
|
8533
8884
|
let biggestMover = null;
|
|
@@ -8566,15 +8917,15 @@ async function ga4Routes(app, opts) {
|
|
|
8566
8917
|
return fmt(d);
|
|
8567
8918
|
};
|
|
8568
8919
|
const pct = (cur, prev) => prev === 0 ? null : Math.round((cur - prev) / prev * 100);
|
|
8569
|
-
const sumTotal = (from, to) => app.db.select({ sessions:
|
|
8570
|
-
const sumOrganic = (from, to) => app.db.select({ sessions:
|
|
8571
|
-
const sumAi = (from, to) => app.db.select({ sessions:
|
|
8920
|
+
const sumTotal = (from, to) => app.db.select({ sessions: sql5`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)` }).from(gaTrafficSnapshots).where(and8(eq19(gaTrafficSnapshots.projectId, project.id), sql5`${gaTrafficSnapshots.date} >= ${from}`, sql5`${gaTrafficSnapshots.date} < ${to}`)).get();
|
|
8921
|
+
const sumOrganic = (from, to) => app.db.select({ sessions: sql5`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)` }).from(gaTrafficSnapshots).where(and8(eq19(gaTrafficSnapshots.projectId, project.id), sql5`${gaTrafficSnapshots.date} >= ${from}`, sql5`${gaTrafficSnapshots.date} < ${to}`)).get();
|
|
8922
|
+
const sumAi = (from, to) => app.db.select({ sessions: sql5`COALESCE(SUM(max_sessions), 0)` }).from(sql5`(
|
|
8572
8923
|
SELECT date, source, medium, MAX(sessions) AS max_sessions
|
|
8573
8924
|
FROM ga_ai_referrals
|
|
8574
8925
|
WHERE project_id = ${project.id} AND date >= ${from} AND date < ${to}
|
|
8575
8926
|
GROUP BY date, source, medium
|
|
8576
8927
|
)`).get();
|
|
8577
|
-
const sumSocial = (from, to) => app.db.select({ sessions:
|
|
8928
|
+
const sumSocial = (from, to) => app.db.select({ sessions: sql5`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and8(eq19(gaSocialReferrals.projectId, project.id), sql5`${gaSocialReferrals.date} >= ${from}`, sql5`${gaSocialReferrals.date} < ${to}`)).get();
|
|
8578
8929
|
const todayStr = fmt(today);
|
|
8579
8930
|
const buildTrend = (sum) => {
|
|
8580
8931
|
const c7 = sum(daysAgo2(7), todayStr)?.sessions ?? 0;
|
|
@@ -8583,18 +8934,18 @@ async function ga4Routes(app, opts) {
|
|
|
8583
8934
|
const p30 = sum(daysAgo2(60), daysAgo2(30))?.sessions ?? 0;
|
|
8584
8935
|
return { sessions7d: c7, sessionsPrev7d: p7, trend7dPct: pct(c7, p7), sessions30d: c30, sessionsPrev30d: p30, trend30dPct: pct(c30, p30) };
|
|
8585
8936
|
};
|
|
8586
|
-
const aiSourceCurrent = app.db.select({ source:
|
|
8937
|
+
const aiSourceCurrent = app.db.select({ source: sql5`source`, sessions: sql5`COALESCE(SUM(max_sessions), 0)` }).from(sql5`(
|
|
8587
8938
|
SELECT date, source, medium, MAX(sessions) AS max_sessions
|
|
8588
8939
|
FROM ga_ai_referrals
|
|
8589
8940
|
WHERE project_id = ${project.id} AND date >= ${daysAgo2(7)} AND date < ${todayStr}
|
|
8590
8941
|
GROUP BY date, source, medium
|
|
8591
|
-
)`).groupBy(
|
|
8592
|
-
const aiSourcePrev = app.db.select({ source:
|
|
8942
|
+
)`).groupBy(sql5`source`).all();
|
|
8943
|
+
const aiSourcePrev = app.db.select({ source: sql5`source`, sessions: sql5`COALESCE(SUM(max_sessions), 0)` }).from(sql5`(
|
|
8593
8944
|
SELECT date, source, medium, MAX(sessions) AS max_sessions
|
|
8594
8945
|
FROM ga_ai_referrals
|
|
8595
8946
|
WHERE project_id = ${project.id} AND date >= ${daysAgo2(14)} AND date < ${daysAgo2(7)}
|
|
8596
8947
|
GROUP BY date, source, medium
|
|
8597
|
-
)`).groupBy(
|
|
8948
|
+
)`).groupBy(sql5`source`).all();
|
|
8598
8949
|
const findBiggestMover = (current, prev) => {
|
|
8599
8950
|
const prevMap = new Map(prev.map((r) => [r.source, r.sessions]));
|
|
8600
8951
|
let mover = null;
|
|
@@ -8609,8 +8960,8 @@ async function ga4Routes(app, opts) {
|
|
|
8609
8960
|
}
|
|
8610
8961
|
return mover;
|
|
8611
8962
|
};
|
|
8612
|
-
const socialSourceCurrent = app.db.select({ source: gaSocialReferrals.source, sessions:
|
|
8613
|
-
const socialSourcePrev = app.db.select({ source: gaSocialReferrals.source, sessions:
|
|
8963
|
+
const socialSourceCurrent = app.db.select({ source: gaSocialReferrals.source, sessions: sql5`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and8(eq19(gaSocialReferrals.projectId, project.id), sql5`${gaSocialReferrals.date} >= ${daysAgo2(7)}`, sql5`${gaSocialReferrals.date} < ${todayStr}`)).groupBy(gaSocialReferrals.source).all();
|
|
8964
|
+
const socialSourcePrev = app.db.select({ source: gaSocialReferrals.source, sessions: sql5`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and8(eq19(gaSocialReferrals.projectId, project.id), sql5`${gaSocialReferrals.date} >= ${daysAgo2(14)}`, sql5`${gaSocialReferrals.date} < ${daysAgo2(7)}`)).groupBy(gaSocialReferrals.source).all();
|
|
8614
8965
|
return {
|
|
8615
8966
|
total: buildTrend(sumTotal),
|
|
8616
8967
|
organic: buildTrend(sumOrganic),
|
|
@@ -8624,14 +8975,14 @@ async function ga4Routes(app, opts) {
|
|
|
8624
8975
|
const project = resolveProject(app.db, request.params.name);
|
|
8625
8976
|
requireGa4Connection(opts, project.name, project.canonicalDomain);
|
|
8626
8977
|
const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
|
|
8627
|
-
const conditions = [
|
|
8628
|
-
if (cutoffDate) conditions.push(
|
|
8978
|
+
const conditions = [eq19(gaTrafficSnapshots.projectId, project.id)];
|
|
8979
|
+
if (cutoffDate) conditions.push(sql5`${gaTrafficSnapshots.date} >= ${cutoffDate}`);
|
|
8629
8980
|
const rows = app.db.select({
|
|
8630
8981
|
date: gaTrafficSnapshots.date,
|
|
8631
|
-
sessions:
|
|
8632
|
-
organicSessions:
|
|
8633
|
-
users:
|
|
8634
|
-
}).from(gaTrafficSnapshots).where(
|
|
8982
|
+
sessions: sql5`SUM(${gaTrafficSnapshots.sessions})`,
|
|
8983
|
+
organicSessions: sql5`SUM(${gaTrafficSnapshots.organicSessions})`,
|
|
8984
|
+
users: sql5`SUM(${gaTrafficSnapshots.users})`
|
|
8985
|
+
}).from(gaTrafficSnapshots).where(and8(...conditions)).groupBy(gaTrafficSnapshots.date).orderBy(gaTrafficSnapshots.date).all();
|
|
8635
8986
|
return rows.map((r) => ({
|
|
8636
8987
|
date: r.date,
|
|
8637
8988
|
sessions: r.sessions ?? 0,
|
|
@@ -8644,10 +8995,10 @@ async function ga4Routes(app, opts) {
|
|
|
8644
8995
|
requireGa4Connection(opts, project.name, project.canonicalDomain);
|
|
8645
8996
|
const trafficPages = app.db.select({
|
|
8646
8997
|
landingPage: gaTrafficSnapshots.landingPage,
|
|
8647
|
-
sessions:
|
|
8648
|
-
organicSessions:
|
|
8649
|
-
users:
|
|
8650
|
-
}).from(gaTrafficSnapshots).where(
|
|
8998
|
+
sessions: sql5`SUM(${gaTrafficSnapshots.sessions})`,
|
|
8999
|
+
organicSessions: sql5`SUM(${gaTrafficSnapshots.organicSessions})`,
|
|
9000
|
+
users: sql5`SUM(${gaTrafficSnapshots.users})`
|
|
9001
|
+
}).from(gaTrafficSnapshots).where(eq19(gaTrafficSnapshots.projectId, project.id)).groupBy(gaTrafficSnapshots.landingPage).orderBy(sql5`SUM(${gaTrafficSnapshots.sessions}) DESC`).all();
|
|
8651
9002
|
return {
|
|
8652
9003
|
pages: trafficPages.map((r) => ({
|
|
8653
9004
|
landingPage: r.landingPage,
|
|
@@ -10284,7 +10635,7 @@ async function wordpressRoutes(app, opts) {
|
|
|
10284
10635
|
|
|
10285
10636
|
// ../api-routes/src/backlinks.ts
|
|
10286
10637
|
import crypto18 from "crypto";
|
|
10287
|
-
import { and as
|
|
10638
|
+
import { and as and9, asc as asc2, desc as desc10, eq as eq20, sql as sql6 } from "drizzle-orm";
|
|
10288
10639
|
|
|
10289
10640
|
// ../integration-commoncrawl/src/constants.ts
|
|
10290
10641
|
import os2 from "os";
|
|
@@ -10514,7 +10865,7 @@ async function queryBacklinks(opts) {
|
|
|
10514
10865
|
const reversed = opts.targets.map(reverseDomain);
|
|
10515
10866
|
const targetList = reversed.map(quote).join(", ");
|
|
10516
10867
|
const limitClause = opts.limitPerTarget ? `QUALIFY row_number() OVER (PARTITION BY t.target_rev_domain ORDER BY v.num_hosts DESC) <= ${Math.floor(opts.limitPerTarget)}` : "";
|
|
10517
|
-
const
|
|
10868
|
+
const sql11 = `
|
|
10518
10869
|
WITH vertices AS (
|
|
10519
10870
|
SELECT * FROM read_csv(
|
|
10520
10871
|
${quote(opts.vertexPath)},
|
|
@@ -10550,7 +10901,7 @@ async function queryBacklinks(opts) {
|
|
|
10550
10901
|
const conn = await instance.connect();
|
|
10551
10902
|
let rows;
|
|
10552
10903
|
try {
|
|
10553
|
-
const reader = await conn.runAndReadAll(
|
|
10904
|
+
const reader = await conn.runAndReadAll(sql11);
|
|
10554
10905
|
rows = reader.getRowObjects();
|
|
10555
10906
|
} finally {
|
|
10556
10907
|
conn.disconnectSync?.();
|
|
@@ -10682,8 +11033,8 @@ function mapRunRow(row) {
|
|
|
10682
11033
|
};
|
|
10683
11034
|
}
|
|
10684
11035
|
function latestSummaryForProject(db, projectId, release) {
|
|
10685
|
-
const condition = release ?
|
|
10686
|
-
return db.select().from(backlinkSummaries).where(condition).orderBy(
|
|
11036
|
+
const condition = release ? and9(eq20(backlinkSummaries.projectId, projectId), eq20(backlinkSummaries.release, release)) : eq20(backlinkSummaries.projectId, projectId);
|
|
11037
|
+
return db.select().from(backlinkSummaries).where(condition).orderBy(desc10(backlinkSummaries.queriedAt)).limit(1).get();
|
|
10687
11038
|
}
|
|
10688
11039
|
async function backlinksRoutes(app, opts) {
|
|
10689
11040
|
app.get("/backlinks/status", async (_request, reply) => {
|
|
@@ -10712,7 +11063,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
10712
11063
|
"@duckdb/node-api is not installed. Run `canonry backlinks install` to enable the backlinks feature."
|
|
10713
11064
|
);
|
|
10714
11065
|
}
|
|
10715
|
-
const existing = app.db.select().from(ccReleaseSyncs).where(
|
|
11066
|
+
const existing = app.db.select().from(ccReleaseSyncs).where(eq20(ccReleaseSyncs.release, release)).get();
|
|
10716
11067
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
10717
11068
|
if (existing) {
|
|
10718
11069
|
if (NON_TERMINAL_SYNC_STATUSES.has(existing.status)) {
|
|
@@ -10723,9 +11074,9 @@ async function backlinksRoutes(app, opts) {
|
|
|
10723
11074
|
phaseDetail: null,
|
|
10724
11075
|
error: null,
|
|
10725
11076
|
updatedAt: now
|
|
10726
|
-
}).where(
|
|
11077
|
+
}).where(eq20(ccReleaseSyncs.id, existing.id)).run();
|
|
10727
11078
|
opts.onReleaseSyncRequested(existing.id, release);
|
|
10728
|
-
const refreshed = app.db.select().from(ccReleaseSyncs).where(
|
|
11079
|
+
const refreshed = app.db.select().from(ccReleaseSyncs).where(eq20(ccReleaseSyncs.id, existing.id)).get();
|
|
10729
11080
|
return reply.status(200).send(mapSyncRow(refreshed));
|
|
10730
11081
|
}
|
|
10731
11082
|
const id = crypto18.randomUUID();
|
|
@@ -10737,15 +11088,15 @@ async function backlinksRoutes(app, opts) {
|
|
|
10737
11088
|
updatedAt: now
|
|
10738
11089
|
}).run();
|
|
10739
11090
|
opts.onReleaseSyncRequested(id, release);
|
|
10740
|
-
const inserted = app.db.select().from(ccReleaseSyncs).where(
|
|
11091
|
+
const inserted = app.db.select().from(ccReleaseSyncs).where(eq20(ccReleaseSyncs.id, id)).get();
|
|
10741
11092
|
return reply.status(201).send(mapSyncRow(inserted));
|
|
10742
11093
|
});
|
|
10743
11094
|
app.get("/backlinks/syncs/latest", async (_request, reply) => {
|
|
10744
|
-
const row = app.db.select().from(ccReleaseSyncs).orderBy(
|
|
11095
|
+
const row = app.db.select().from(ccReleaseSyncs).orderBy(desc10(ccReleaseSyncs.updatedAt)).limit(1).get();
|
|
10745
11096
|
return reply.send(row ? mapSyncRow(row) : null);
|
|
10746
11097
|
});
|
|
10747
11098
|
app.get("/backlinks/syncs", async (_request, reply) => {
|
|
10748
|
-
const rows = app.db.select().from(ccReleaseSyncs).orderBy(
|
|
11099
|
+
const rows = app.db.select().from(ccReleaseSyncs).orderBy(desc10(ccReleaseSyncs.updatedAt)).all();
|
|
10749
11100
|
return reply.send(rows.map(mapSyncRow));
|
|
10750
11101
|
});
|
|
10751
11102
|
app.get("/backlinks/releases", async (_request, reply) => {
|
|
@@ -10788,7 +11139,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
10788
11139
|
createdAt: now
|
|
10789
11140
|
}).run();
|
|
10790
11141
|
opts.onBacklinkExtractRequested(runId, project.id, release);
|
|
10791
|
-
const run = app.db.select().from(runs).where(
|
|
11142
|
+
const run = app.db.select().from(runs).where(eq20(runs.id, runId)).get();
|
|
10792
11143
|
return reply.status(201).send(mapRunRow(run));
|
|
10793
11144
|
});
|
|
10794
11145
|
app.get(
|
|
@@ -10809,15 +11160,15 @@ async function backlinksRoutes(app, opts) {
|
|
|
10809
11160
|
}
|
|
10810
11161
|
const limit = Math.min(Math.max(parseInt(request.query.limit ?? "50", 10) || 50, 1), 500);
|
|
10811
11162
|
const offset = Math.max(parseInt(request.query.offset ?? "0", 10) || 0, 0);
|
|
10812
|
-
const domainCondition =
|
|
10813
|
-
|
|
10814
|
-
|
|
11163
|
+
const domainCondition = and9(
|
|
11164
|
+
eq20(backlinkDomains.projectId, project.id),
|
|
11165
|
+
eq20(backlinkDomains.release, targetRelease)
|
|
10815
11166
|
);
|
|
10816
|
-
const totalRow = app.db.select({ count:
|
|
11167
|
+
const totalRow = app.db.select({ count: sql6`count(*)` }).from(backlinkDomains).where(domainCondition).get();
|
|
10817
11168
|
const rows = app.db.select({
|
|
10818
11169
|
linkingDomain: backlinkDomains.linkingDomain,
|
|
10819
11170
|
numHosts: backlinkDomains.numHosts
|
|
10820
|
-
}).from(backlinkDomains).where(domainCondition).orderBy(
|
|
11171
|
+
}).from(backlinkDomains).where(domainCondition).orderBy(desc10(backlinkDomains.numHosts)).limit(limit).offset(offset).all();
|
|
10821
11172
|
const response = {
|
|
10822
11173
|
summary: summaryRow ? mapSummaryRow(summaryRow) : null,
|
|
10823
11174
|
total: Number(totalRow?.count ?? 0),
|
|
@@ -10829,7 +11180,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
10829
11180
|
"/projects/:name/backlinks/history",
|
|
10830
11181
|
async (request, reply) => {
|
|
10831
11182
|
const project = resolveProject(app.db, request.params.name);
|
|
10832
|
-
const rows = app.db.select().from(backlinkSummaries).where(
|
|
11183
|
+
const rows = app.db.select().from(backlinkSummaries).where(eq20(backlinkSummaries.projectId, project.id)).orderBy(asc2(backlinkSummaries.queriedAt)).all();
|
|
10833
11184
|
const response = rows.map((r) => ({
|
|
10834
11185
|
release: r.release,
|
|
10835
11186
|
totalLinkingDomains: r.totalLinkingDomains,
|
|
@@ -10902,6 +11253,7 @@ async function apiRoutes(app, opts) {
|
|
|
10902
11253
|
await api.register(historyRoutes);
|
|
10903
11254
|
await api.register(analyticsRoutes);
|
|
10904
11255
|
await api.register(intelligenceRoutes);
|
|
11256
|
+
await api.register(compositeRoutes);
|
|
10905
11257
|
await api.register(contentRoutes);
|
|
10906
11258
|
await api.register(settingsRoutes, {
|
|
10907
11259
|
providerSummary: opts.providerSummary,
|
|
@@ -13424,7 +13776,7 @@ import crypto19 from "crypto";
|
|
|
13424
13776
|
import fs7 from "fs";
|
|
13425
13777
|
import path9 from "path";
|
|
13426
13778
|
import os4 from "os";
|
|
13427
|
-
import { and as
|
|
13779
|
+
import { and as and10, eq as eq21, inArray as inArray4, sql as sql7 } from "drizzle-orm";
|
|
13428
13780
|
|
|
13429
13781
|
// src/citation-utils.ts
|
|
13430
13782
|
function domainMatches(domain, canonicalDomain) {
|
|
@@ -13680,7 +14032,7 @@ var JobRunner = class {
|
|
|
13680
14032
|
if (stale.length === 0) return;
|
|
13681
14033
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
13682
14034
|
for (const run of stale) {
|
|
13683
|
-
this.db.update(runs).set({ status: "failed", finishedAt: now, error: "Server restarted while run was in progress" }).where(
|
|
14035
|
+
this.db.update(runs).set({ status: "failed", finishedAt: now, error: "Server restarted while run was in progress" }).where(eq21(runs.id, run.id)).run();
|
|
13684
14036
|
log.warn("run.recovered-stale", { runId: run.id, previousStatus: run.status });
|
|
13685
14037
|
}
|
|
13686
14038
|
}
|
|
@@ -13708,10 +14060,10 @@ var JobRunner = class {
|
|
|
13708
14060
|
throw new Error(`Run ${runId} is not executable from status '${existingRun.status}'`);
|
|
13709
14061
|
}
|
|
13710
14062
|
if (existingRun.status === "queued") {
|
|
13711
|
-
this.db.update(runs).set({ status: "running", startedAt: now }).where(
|
|
14063
|
+
this.db.update(runs).set({ status: "running", startedAt: now }).where(and10(eq21(runs.id, runId), eq21(runs.status, "queued"))).run();
|
|
13712
14064
|
}
|
|
13713
14065
|
this.throwIfRunCancelled(runId);
|
|
13714
|
-
const project = this.db.select().from(projects).where(
|
|
14066
|
+
const project = this.db.select().from(projects).where(eq21(projects.id, projectId)).get();
|
|
13715
14067
|
if (!project) {
|
|
13716
14068
|
throw new Error(`Project ${projectId} not found`);
|
|
13717
14069
|
}
|
|
@@ -13731,8 +14083,8 @@ var JobRunner = class {
|
|
|
13731
14083
|
throw new Error("No providers configured. Add at least one provider API key.");
|
|
13732
14084
|
}
|
|
13733
14085
|
log.info("run.dispatch", { runId, providerCount: activeProviders.length, providers: activeProviders.map((p) => p.adapter.name) });
|
|
13734
|
-
projectKeywords = this.db.select().from(keywords).where(
|
|
13735
|
-
const projectCompetitors = this.db.select().from(competitors).where(
|
|
14086
|
+
projectKeywords = this.db.select().from(keywords).where(eq21(keywords.projectId, projectId)).all();
|
|
14087
|
+
const projectCompetitors = this.db.select().from(competitors).where(eq21(competitors.projectId, projectId)).all();
|
|
13736
14088
|
const competitorDomains = projectCompetitors.map((c) => c.domain);
|
|
13737
14089
|
const allDomains = effectiveDomains({
|
|
13738
14090
|
canonicalDomain: project.canonicalDomain,
|
|
@@ -13748,7 +14100,7 @@ var JobRunner = class {
|
|
|
13748
14100
|
const todayPeriod = getCurrentUsageDay();
|
|
13749
14101
|
for (const p of activeProviders) {
|
|
13750
14102
|
const providerScope = `${projectId}:${p.adapter.name}`;
|
|
13751
|
-
const providerUsage = this.db.select().from(usageCounters).where(
|
|
14103
|
+
const providerUsage = this.db.select().from(usageCounters).where(eq21(usageCounters.scope, providerScope)).all().filter((r) => r.period === todayPeriod && r.metric === "queries").reduce((sum, r) => sum + r.count, 0);
|
|
13752
14104
|
const limit = p.config.quotaPolicy.maxRequestsPerDay;
|
|
13753
14105
|
if (providerUsage + queriesPerProvider > limit) {
|
|
13754
14106
|
throw new Error(
|
|
@@ -13889,12 +14241,12 @@ var JobRunner = class {
|
|
|
13889
14241
|
const someFailed = providerErrors.size > 0;
|
|
13890
14242
|
if (allFailed) {
|
|
13891
14243
|
const errorDetail = serializeRunError(buildRunErrorFromMessages(providerErrors));
|
|
13892
|
-
this.db.update(runs).set({ status: "failed", finishedAt: (/* @__PURE__ */ new Date()).toISOString(), error: errorDetail }).where(
|
|
14244
|
+
this.db.update(runs).set({ status: "failed", finishedAt: (/* @__PURE__ */ new Date()).toISOString(), error: errorDetail }).where(eq21(runs.id, runId)).run();
|
|
13893
14245
|
} else if (someFailed) {
|
|
13894
14246
|
const errorDetail = serializeRunError(buildRunErrorFromMessages(providerErrors));
|
|
13895
|
-
this.db.update(runs).set({ status: "partial", finishedAt: (/* @__PURE__ */ new Date()).toISOString(), error: errorDetail }).where(
|
|
14247
|
+
this.db.update(runs).set({ status: "partial", finishedAt: (/* @__PURE__ */ new Date()).toISOString(), error: errorDetail }).where(eq21(runs.id, runId)).run();
|
|
13896
14248
|
} else {
|
|
13897
|
-
this.db.update(runs).set({ status: "completed", finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
14249
|
+
this.db.update(runs).set({ status: "completed", finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq21(runs.id, runId)).run();
|
|
13898
14250
|
}
|
|
13899
14251
|
this.flushProviderUsage(projectId, providerDispatchCounts);
|
|
13900
14252
|
const finalStatus = allFailed ? "failed" : someFailed ? "partial" : "completed";
|
|
@@ -13929,7 +14281,7 @@ var JobRunner = class {
|
|
|
13929
14281
|
status: "failed",
|
|
13930
14282
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13931
14283
|
error: errorMessage
|
|
13932
|
-
}).where(
|
|
14284
|
+
}).where(eq21(runs.id, runId)).run();
|
|
13933
14285
|
this.flushProviderUsage(projectId, providerDispatchCounts);
|
|
13934
14286
|
trackEvent("run.completed", {
|
|
13935
14287
|
status: "failed",
|
|
@@ -13958,7 +14310,7 @@ var JobRunner = class {
|
|
|
13958
14310
|
updatedAt: now
|
|
13959
14311
|
}).onConflictDoUpdate({
|
|
13960
14312
|
target: [usageCounters.scope, usageCounters.period, usageCounters.metric],
|
|
13961
|
-
set: { count:
|
|
14313
|
+
set: { count: sql7`${usageCounters.count} + ${count}`, updatedAt: now }
|
|
13962
14314
|
}).run();
|
|
13963
14315
|
}
|
|
13964
14316
|
flushProviderUsage(projectId, providerDispatchCounts) {
|
|
@@ -13972,7 +14324,7 @@ var JobRunner = class {
|
|
|
13972
14324
|
status: runs.status,
|
|
13973
14325
|
finishedAt: runs.finishedAt,
|
|
13974
14326
|
error: runs.error
|
|
13975
|
-
}).from(runs).where(
|
|
14327
|
+
}).from(runs).where(eq21(runs.id, runId)).get();
|
|
13976
14328
|
}
|
|
13977
14329
|
isRunCancelled(runId) {
|
|
13978
14330
|
return this.getRunState(runId)?.status === "cancelled";
|
|
@@ -13988,7 +14340,7 @@ var JobRunner = class {
|
|
|
13988
14340
|
this.db.update(runs).set({
|
|
13989
14341
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13990
14342
|
error: currentRun.error ?? "Cancelled by user"
|
|
13991
|
-
}).where(
|
|
14343
|
+
}).where(eq21(runs.id, runId)).run();
|
|
13992
14344
|
}
|
|
13993
14345
|
trackEvent("run.completed", {
|
|
13994
14346
|
status: "cancelled",
|
|
@@ -14011,7 +14363,7 @@ function getCurrentUsageDay() {
|
|
|
14011
14363
|
|
|
14012
14364
|
// src/gsc-sync.ts
|
|
14013
14365
|
import crypto20 from "crypto";
|
|
14014
|
-
import { eq as
|
|
14366
|
+
import { eq as eq22, and as and11, sql as sql8 } from "drizzle-orm";
|
|
14015
14367
|
var log2 = createLogger("GscSync");
|
|
14016
14368
|
function formatDate2(d) {
|
|
14017
14369
|
return d.toISOString().split("T")[0];
|
|
@@ -14023,13 +14375,13 @@ function daysAgo(n) {
|
|
|
14023
14375
|
}
|
|
14024
14376
|
async function executeGscSync(db, runId, projectId, opts) {
|
|
14025
14377
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
14026
|
-
db.update(runs).set({ status: "running", startedAt: now }).where(
|
|
14378
|
+
db.update(runs).set({ status: "running", startedAt: now }).where(eq22(runs.id, runId)).run();
|
|
14027
14379
|
try {
|
|
14028
14380
|
const { clientId: googleClientId, clientSecret: googleClientSecret } = getGoogleAuthConfig(opts.config);
|
|
14029
14381
|
if (!googleClientId || !googleClientSecret) {
|
|
14030
14382
|
throw new Error("Google OAuth is not configured in the local Canonry config");
|
|
14031
14383
|
}
|
|
14032
|
-
const project = db.select().from(projects).where(
|
|
14384
|
+
const project = db.select().from(projects).where(eq22(projects.id, projectId)).get();
|
|
14033
14385
|
if (!project) {
|
|
14034
14386
|
throw new Error(`Project not found: ${projectId}`);
|
|
14035
14387
|
}
|
|
@@ -14063,10 +14415,10 @@ async function executeGscSync(db, runId, projectId, opts) {
|
|
|
14063
14415
|
});
|
|
14064
14416
|
log2.info("fetch.complete", { runId, projectId, rowCount: rows.length });
|
|
14065
14417
|
db.delete(gscSearchData).where(
|
|
14066
|
-
|
|
14067
|
-
|
|
14068
|
-
|
|
14069
|
-
|
|
14418
|
+
and11(
|
|
14419
|
+
eq22(gscSearchData.projectId, projectId),
|
|
14420
|
+
sql8`${gscSearchData.date} >= ${startDate}`,
|
|
14421
|
+
sql8`${gscSearchData.date} <= ${endDate}`
|
|
14070
14422
|
)
|
|
14071
14423
|
).run();
|
|
14072
14424
|
const batchSize = 500;
|
|
@@ -14131,7 +14483,7 @@ async function executeGscSync(db, runId, projectId, opts) {
|
|
|
14131
14483
|
log2.error("inspect.url-failed", { runId, projectId, url: pageUrl, error: err instanceof Error ? err.message : String(err) });
|
|
14132
14484
|
}
|
|
14133
14485
|
}
|
|
14134
|
-
const allInspections = db.select().from(gscUrlInspections).where(
|
|
14486
|
+
const allInspections = db.select().from(gscUrlInspections).where(eq22(gscUrlInspections.projectId, projectId)).all();
|
|
14135
14487
|
const latestByUrl = /* @__PURE__ */ new Map();
|
|
14136
14488
|
for (const row of allInspections) {
|
|
14137
14489
|
const existing = latestByUrl.get(row.url);
|
|
@@ -14152,7 +14504,7 @@ async function executeGscSync(db, runId, projectId, opts) {
|
|
|
14152
14504
|
}
|
|
14153
14505
|
}
|
|
14154
14506
|
const snapshotDate = formatDate2(/* @__PURE__ */ new Date());
|
|
14155
|
-
db.delete(gscCoverageSnapshots).where(
|
|
14507
|
+
db.delete(gscCoverageSnapshots).where(and11(eq22(gscCoverageSnapshots.projectId, projectId), eq22(gscCoverageSnapshots.date, snapshotDate))).run();
|
|
14156
14508
|
db.insert(gscCoverageSnapshots).values({
|
|
14157
14509
|
id: crypto20.randomUUID(),
|
|
14158
14510
|
projectId,
|
|
@@ -14163,11 +14515,11 @@ async function executeGscSync(db, runId, projectId, opts) {
|
|
|
14163
14515
|
reasonBreakdown: JSON.stringify(reasonCounts),
|
|
14164
14516
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
14165
14517
|
}).run();
|
|
14166
|
-
db.update(runs).set({ status: "completed", finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
14518
|
+
db.update(runs).set({ status: "completed", finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq22(runs.id, runId)).run();
|
|
14167
14519
|
log2.info("sync.completed", { runId, projectId, searchDataRows: rows.length, urlInspections: topPages.length, indexed: snapIndexed, notIndexed: snapNotIndexed });
|
|
14168
14520
|
} catch (err) {
|
|
14169
14521
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
14170
|
-
db.update(runs).set({ status: "failed", error: errorMsg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
14522
|
+
db.update(runs).set({ status: "failed", error: errorMsg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq22(runs.id, runId)).run();
|
|
14171
14523
|
log2.error("sync.failed", { runId, projectId, error: errorMsg });
|
|
14172
14524
|
throw err;
|
|
14173
14525
|
}
|
|
@@ -14175,7 +14527,7 @@ async function executeGscSync(db, runId, projectId, opts) {
|
|
|
14175
14527
|
|
|
14176
14528
|
// src/gsc-inspect-sitemap.ts
|
|
14177
14529
|
import crypto21 from "crypto";
|
|
14178
|
-
import { eq as
|
|
14530
|
+
import { eq as eq23, and as and12 } from "drizzle-orm";
|
|
14179
14531
|
|
|
14180
14532
|
// src/sitemap-parser.ts
|
|
14181
14533
|
var log3 = createLogger("SitemapParser");
|
|
@@ -14296,13 +14648,13 @@ async function parseSitemapRecursive(url, urls, visited, depth, isChild) {
|
|
|
14296
14648
|
var log4 = createLogger("InspectSitemap");
|
|
14297
14649
|
async function executeInspectSitemap(db, runId, projectId, opts) {
|
|
14298
14650
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
14299
|
-
db.update(runs).set({ status: "running", startedAt: now }).where(
|
|
14651
|
+
db.update(runs).set({ status: "running", startedAt: now }).where(eq23(runs.id, runId)).run();
|
|
14300
14652
|
try {
|
|
14301
14653
|
const { clientId: googleClientId, clientSecret: googleClientSecret } = getGoogleAuthConfig(opts.config);
|
|
14302
14654
|
if (!googleClientId || !googleClientSecret) {
|
|
14303
14655
|
throw new Error("Google OAuth is not configured in the local Canonry config");
|
|
14304
14656
|
}
|
|
14305
|
-
const project = db.select().from(projects).where(
|
|
14657
|
+
const project = db.select().from(projects).where(eq23(projects.id, projectId)).get();
|
|
14306
14658
|
if (!project) {
|
|
14307
14659
|
throw new Error(`Project not found: ${projectId}`);
|
|
14308
14660
|
}
|
|
@@ -14370,7 +14722,7 @@ async function executeInspectSitemap(db, runId, projectId, opts) {
|
|
|
14370
14722
|
await new Promise((r) => setTimeout(r, 1e3));
|
|
14371
14723
|
}
|
|
14372
14724
|
}
|
|
14373
|
-
const allInspections = db.select().from(gscUrlInspections).where(
|
|
14725
|
+
const allInspections = db.select().from(gscUrlInspections).where(eq23(gscUrlInspections.projectId, projectId)).all();
|
|
14374
14726
|
const latestByUrl = /* @__PURE__ */ new Map();
|
|
14375
14727
|
for (const row of allInspections) {
|
|
14376
14728
|
const existing = latestByUrl.get(row.url);
|
|
@@ -14391,7 +14743,7 @@ async function executeInspectSitemap(db, runId, projectId, opts) {
|
|
|
14391
14743
|
}
|
|
14392
14744
|
}
|
|
14393
14745
|
const snapshotDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
14394
|
-
db.delete(gscCoverageSnapshots).where(
|
|
14746
|
+
db.delete(gscCoverageSnapshots).where(and12(eq23(gscCoverageSnapshots.projectId, projectId), eq23(gscCoverageSnapshots.date, snapshotDate))).run();
|
|
14395
14747
|
db.insert(gscCoverageSnapshots).values({
|
|
14396
14748
|
id: crypto21.randomUUID(),
|
|
14397
14749
|
projectId,
|
|
@@ -14403,11 +14755,11 @@ async function executeInspectSitemap(db, runId, projectId, opts) {
|
|
|
14403
14755
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
14404
14756
|
}).run();
|
|
14405
14757
|
const status = errors > 0 && inspected > 0 ? "partial" : errors === urls.length ? "failed" : "completed";
|
|
14406
|
-
db.update(runs).set({ status, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
14758
|
+
db.update(runs).set({ status, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq23(runs.id, runId)).run();
|
|
14407
14759
|
log4.info("inspect.completed", { runId, projectId, inspected, errors, total: urls.length, indexed: snapIndexed, notIndexed: snapNotIndexed });
|
|
14408
14760
|
} catch (err) {
|
|
14409
14761
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
14410
|
-
db.update(runs).set({ status: "failed", error: errorMsg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
14762
|
+
db.update(runs).set({ status: "failed", error: errorMsg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq23(runs.id, runId)).run();
|
|
14411
14763
|
log4.error("inspect.failed", { runId, projectId, error: errorMsg });
|
|
14412
14764
|
throw err;
|
|
14413
14765
|
}
|
|
@@ -14415,7 +14767,7 @@ async function executeInspectSitemap(db, runId, projectId, opts) {
|
|
|
14415
14767
|
|
|
14416
14768
|
// src/bing-inspect-sitemap.ts
|
|
14417
14769
|
import crypto22 from "crypto";
|
|
14418
|
-
import { eq as
|
|
14770
|
+
import { eq as eq24, desc as desc11 } from "drizzle-orm";
|
|
14419
14771
|
var log5 = createLogger("BingInspectSitemap");
|
|
14420
14772
|
function parseBingDate2(value) {
|
|
14421
14773
|
if (!value) return null;
|
|
@@ -14433,9 +14785,9 @@ function isBlockingIssueType2(issueType) {
|
|
|
14433
14785
|
}
|
|
14434
14786
|
async function executeBingInspectSitemap(db, runId, projectId, opts) {
|
|
14435
14787
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
14436
|
-
db.update(runs).set({ status: RunStatuses.running, startedAt }).where(
|
|
14788
|
+
db.update(runs).set({ status: RunStatuses.running, startedAt }).where(eq24(runs.id, runId)).run();
|
|
14437
14789
|
try {
|
|
14438
|
-
const project = db.select().from(projects).where(
|
|
14790
|
+
const project = db.select().from(projects).where(eq24(projects.id, projectId)).get();
|
|
14439
14791
|
if (!project) {
|
|
14440
14792
|
throw new Error(`Project not found: ${projectId}`);
|
|
14441
14793
|
}
|
|
@@ -14453,7 +14805,7 @@ async function executeBingInspectSitemap(db, runId, projectId, opts) {
|
|
|
14453
14805
|
if (sitemapUrls.length === 0) {
|
|
14454
14806
|
throw new Error("No URLs found in sitemap");
|
|
14455
14807
|
}
|
|
14456
|
-
const trackedRows = db.select({ url: bingUrlInspections.url }).from(bingUrlInspections).where(
|
|
14808
|
+
const trackedRows = db.select({ url: bingUrlInspections.url }).from(bingUrlInspections).where(eq24(bingUrlInspections.projectId, projectId)).all();
|
|
14457
14809
|
const trackedUrls = new Set(trackedRows.map((r) => r.url));
|
|
14458
14810
|
const discovered = sitemapUrls.filter((u) => !trackedUrls.has(u));
|
|
14459
14811
|
log5.info("sitemap.diff", {
|
|
@@ -14536,7 +14888,7 @@ async function executeBingInspectSitemap(db, runId, projectId, opts) {
|
|
|
14536
14888
|
await new Promise((r) => setTimeout(r, 1e3));
|
|
14537
14889
|
}
|
|
14538
14890
|
}
|
|
14539
|
-
const allInspections = db.select().from(bingUrlInspections).where(
|
|
14891
|
+
const allInspections = db.select().from(bingUrlInspections).where(eq24(bingUrlInspections.projectId, projectId)).orderBy(desc11(bingUrlInspections.inspectedAt)).all();
|
|
14540
14892
|
const latestByUrl = /* @__PURE__ */ new Map();
|
|
14541
14893
|
const definitiveByUrl = /* @__PURE__ */ new Map();
|
|
14542
14894
|
for (const row of allInspections) {
|
|
@@ -14579,7 +14931,7 @@ async function executeBingInspectSitemap(db, runId, projectId, opts) {
|
|
|
14579
14931
|
}
|
|
14580
14932
|
}).run();
|
|
14581
14933
|
const status = errors === sitemapUrls.length ? RunStatuses.failed : errors > 0 ? RunStatuses.partial : RunStatuses.completed;
|
|
14582
|
-
db.update(runs).set({ status, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
14934
|
+
db.update(runs).set({ status, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq24(runs.id, runId)).run();
|
|
14583
14935
|
log5.info("inspect.completed", {
|
|
14584
14936
|
runId,
|
|
14585
14937
|
projectId,
|
|
@@ -14593,7 +14945,7 @@ async function executeBingInspectSitemap(db, runId, projectId, opts) {
|
|
|
14593
14945
|
});
|
|
14594
14946
|
} catch (err) {
|
|
14595
14947
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
14596
|
-
db.update(runs).set({ status: RunStatuses.failed, error: errorMsg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
14948
|
+
db.update(runs).set({ status: RunStatuses.failed, error: errorMsg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq24(runs.id, runId)).run();
|
|
14597
14949
|
log5.error("inspect.failed", { runId, projectId, error: errorMsg });
|
|
14598
14950
|
throw err;
|
|
14599
14951
|
}
|
|
@@ -14602,7 +14954,7 @@ async function executeBingInspectSitemap(db, runId, projectId, opts) {
|
|
|
14602
14954
|
// src/commoncrawl-sync.ts
|
|
14603
14955
|
import crypto23 from "crypto";
|
|
14604
14956
|
import path10 from "path";
|
|
14605
|
-
import { and as
|
|
14957
|
+
import { and as and13, eq as eq25, sql as sql9 } from "drizzle-orm";
|
|
14606
14958
|
var log6 = createLogger("CommonCrawlSync");
|
|
14607
14959
|
var INSERT_CHUNK_SIZE = 1e4;
|
|
14608
14960
|
function defaultDeps() {
|
|
@@ -14628,7 +14980,7 @@ async function executeReleaseSync(db, syncId, opts) {
|
|
|
14628
14980
|
phaseDetail: "downloading vertices + edges",
|
|
14629
14981
|
updatedAt: downloadStartedAt,
|
|
14630
14982
|
error: null
|
|
14631
|
-
}).where(
|
|
14983
|
+
}).where(eq25(ccReleaseSyncs.id, syncId)).run();
|
|
14632
14984
|
const paths = ccReleasePaths(release);
|
|
14633
14985
|
const releaseCacheDir = path10.join(deps.cacheDir, release);
|
|
14634
14986
|
const vertexPath = path10.join(releaseCacheDir, paths.vertexFilename);
|
|
@@ -14651,7 +15003,7 @@ async function executeReleaseSync(db, syncId, opts) {
|
|
|
14651
15003
|
vertexSha256: vertex.sha256,
|
|
14652
15004
|
edgesSha256: edges.sha256,
|
|
14653
15005
|
updatedAt: downloadFinishedAt
|
|
14654
|
-
}).where(
|
|
15006
|
+
}).where(eq25(ccReleaseSyncs.id, syncId)).run();
|
|
14655
15007
|
const allProjects = db.select().from(projects).all();
|
|
14656
15008
|
const targets = Array.from(new Set(allProjects.map((p) => p.canonicalDomain)));
|
|
14657
15009
|
let rows = [];
|
|
@@ -14667,8 +15019,8 @@ async function executeReleaseSync(db, syncId, opts) {
|
|
|
14667
15019
|
}
|
|
14668
15020
|
const queriedAt = deps.now().toISOString();
|
|
14669
15021
|
db.transaction((tx) => {
|
|
14670
|
-
tx.delete(backlinkDomains).where(
|
|
14671
|
-
tx.delete(backlinkSummaries).where(
|
|
15022
|
+
tx.delete(backlinkDomains).where(eq25(backlinkDomains.releaseSyncId, syncId)).run();
|
|
15023
|
+
tx.delete(backlinkSummaries).where(eq25(backlinkSummaries.releaseSyncId, syncId)).run();
|
|
14672
15024
|
const expanded = [];
|
|
14673
15025
|
for (const r of rows) {
|
|
14674
15026
|
const projectIds = projectsByDomain.get(r.targetDomain);
|
|
@@ -14727,7 +15079,7 @@ async function executeReleaseSync(db, syncId, opts) {
|
|
|
14727
15079
|
domainsDiscovered: rows.length,
|
|
14728
15080
|
updatedAt: finishedAt,
|
|
14729
15081
|
error: null
|
|
14730
|
-
}).where(
|
|
15082
|
+
}).where(eq25(ccReleaseSyncs.id, syncId)).run();
|
|
14731
15083
|
log6.info("sync.completed", {
|
|
14732
15084
|
syncId,
|
|
14733
15085
|
release,
|
|
@@ -14757,7 +15109,7 @@ async function executeReleaseSync(db, syncId, opts) {
|
|
|
14757
15109
|
error: errorMsg,
|
|
14758
15110
|
phaseDetail: null,
|
|
14759
15111
|
updatedAt: finishedAt
|
|
14760
|
-
}).where(
|
|
15112
|
+
}).where(eq25(ccReleaseSyncs.id, syncId)).run();
|
|
14761
15113
|
log6.error("sync.failed", { syncId, release, error: errorMsg });
|
|
14762
15114
|
throw err;
|
|
14763
15115
|
}
|
|
@@ -14793,7 +15145,7 @@ function computeSummary(rows) {
|
|
|
14793
15145
|
// src/backlink-extract.ts
|
|
14794
15146
|
import crypto24 from "crypto";
|
|
14795
15147
|
import fs8 from "fs";
|
|
14796
|
-
import { and as
|
|
15148
|
+
import { and as and14, desc as desc12, eq as eq26 } from "drizzle-orm";
|
|
14797
15149
|
var log7 = createLogger("BacklinkExtract");
|
|
14798
15150
|
function defaultDeps2() {
|
|
14799
15151
|
return {
|
|
@@ -14805,13 +15157,13 @@ function defaultDeps2() {
|
|
|
14805
15157
|
async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
|
|
14806
15158
|
const deps = { ...defaultDeps2(), ...opts.deps };
|
|
14807
15159
|
const startedAt = deps.now().toISOString();
|
|
14808
|
-
db.update(runs).set({ status: RunStatuses.running, startedAt }).where(
|
|
15160
|
+
db.update(runs).set({ status: RunStatuses.running, startedAt }).where(eq26(runs.id, runId)).run();
|
|
14809
15161
|
try {
|
|
14810
|
-
const project = db.select().from(projects).where(
|
|
15162
|
+
const project = db.select().from(projects).where(eq26(projects.id, projectId)).get();
|
|
14811
15163
|
if (!project) {
|
|
14812
15164
|
throw new Error(`Project not found: ${projectId}`);
|
|
14813
15165
|
}
|
|
14814
|
-
const sync = opts.release ? db.select().from(ccReleaseSyncs).where(
|
|
15166
|
+
const sync = opts.release ? db.select().from(ccReleaseSyncs).where(eq26(ccReleaseSyncs.release, opts.release)).get() : db.select().from(ccReleaseSyncs).where(eq26(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).orderBy(desc12(ccReleaseSyncs.createdAt)).limit(1).get();
|
|
14815
15167
|
if (!sync) {
|
|
14816
15168
|
throw new Error("No ready release sync available \u2014 run `canonry backlinks sync` first");
|
|
14817
15169
|
}
|
|
@@ -14839,7 +15191,7 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
|
|
|
14839
15191
|
const targetDomain = project.canonicalDomain;
|
|
14840
15192
|
db.transaction((tx) => {
|
|
14841
15193
|
tx.delete(backlinkDomains).where(
|
|
14842
|
-
|
|
15194
|
+
and14(eq26(backlinkDomains.projectId, projectId), eq26(backlinkDomains.release, release))
|
|
14843
15195
|
).run();
|
|
14844
15196
|
if (rows.length > 0) {
|
|
14845
15197
|
const values = rows.map((r) => ({
|
|
@@ -14879,7 +15231,7 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
|
|
|
14879
15231
|
}).run();
|
|
14880
15232
|
});
|
|
14881
15233
|
const finishedAt = deps.now().toISOString();
|
|
14882
|
-
db.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(
|
|
15234
|
+
db.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq26(runs.id, runId)).run();
|
|
14883
15235
|
log7.info("extract.completed", { runId, projectId, release, rows: rows.length });
|
|
14884
15236
|
} catch (err) {
|
|
14885
15237
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
@@ -14888,7 +15240,7 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
|
|
|
14888
15240
|
status: RunStatuses.failed,
|
|
14889
15241
|
error: errorMsg,
|
|
14890
15242
|
finishedAt
|
|
14891
|
-
}).where(
|
|
15243
|
+
}).where(eq26(runs.id, runId)).run();
|
|
14892
15244
|
log7.error("extract.failed", { runId, projectId, error: errorMsg });
|
|
14893
15245
|
throw err;
|
|
14894
15246
|
}
|
|
@@ -14961,7 +15313,7 @@ var ProviderRegistry = class {
|
|
|
14961
15313
|
|
|
14962
15314
|
// src/scheduler.ts
|
|
14963
15315
|
import cron from "node-cron";
|
|
14964
|
-
import { eq as
|
|
15316
|
+
import { eq as eq27 } from "drizzle-orm";
|
|
14965
15317
|
var log8 = createLogger("Scheduler");
|
|
14966
15318
|
var Scheduler = class {
|
|
14967
15319
|
db;
|
|
@@ -14973,7 +15325,7 @@ var Scheduler = class {
|
|
|
14973
15325
|
}
|
|
14974
15326
|
/** Load all enabled schedules from DB and register cron jobs. */
|
|
14975
15327
|
start() {
|
|
14976
|
-
const allSchedules = this.db.select().from(schedules).where(
|
|
15328
|
+
const allSchedules = this.db.select().from(schedules).where(eq27(schedules.enabled, 1)).all();
|
|
14977
15329
|
for (const schedule of allSchedules) {
|
|
14978
15330
|
const missedRunAt = schedule.nextRunAt;
|
|
14979
15331
|
this.registerCronTask(schedule);
|
|
@@ -14998,7 +15350,7 @@ var Scheduler = class {
|
|
|
14998
15350
|
this.stopTask(projectId, existing, "Stopped");
|
|
14999
15351
|
this.tasks.delete(projectId);
|
|
15000
15352
|
}
|
|
15001
|
-
const schedule = this.db.select().from(schedules).where(
|
|
15353
|
+
const schedule = this.db.select().from(schedules).where(eq27(schedules.projectId, projectId)).get();
|
|
15002
15354
|
if (schedule && schedule.enabled === 1) {
|
|
15003
15355
|
this.registerCronTask(schedule);
|
|
15004
15356
|
}
|
|
@@ -15031,14 +15383,14 @@ var Scheduler = class {
|
|
|
15031
15383
|
this.db.update(schedules).set({
|
|
15032
15384
|
nextRunAt: task.getNextRun()?.toISOString() ?? null,
|
|
15033
15385
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
15034
|
-
}).where(
|
|
15386
|
+
}).where(eq27(schedules.id, scheduleId)).run();
|
|
15035
15387
|
const label = schedule.preset ?? cronExpr;
|
|
15036
15388
|
log8.info("cron.registered", { projectId, schedule: label, timezone });
|
|
15037
15389
|
}
|
|
15038
15390
|
triggerRun(scheduleId, projectId) {
|
|
15039
15391
|
try {
|
|
15040
15392
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
15041
|
-
const currentSchedule = this.db.select().from(schedules).where(
|
|
15393
|
+
const currentSchedule = this.db.select().from(schedules).where(eq27(schedules.id, scheduleId)).get();
|
|
15042
15394
|
if (!currentSchedule || currentSchedule.enabled !== 1) {
|
|
15043
15395
|
log8.warn("schedule.stale", { scheduleId, projectId, msg: "schedule no longer exists or is disabled" });
|
|
15044
15396
|
this.remove(projectId);
|
|
@@ -15046,7 +15398,7 @@ var Scheduler = class {
|
|
|
15046
15398
|
}
|
|
15047
15399
|
const task = this.tasks.get(projectId);
|
|
15048
15400
|
const nextRunAt = task?.getNextRun()?.toISOString() ?? null;
|
|
15049
|
-
const project = this.db.select().from(projects).where(
|
|
15401
|
+
const project = this.db.select().from(projects).where(eq27(projects.id, projectId)).get();
|
|
15050
15402
|
if (!project) {
|
|
15051
15403
|
log8.error("project.not-found", { projectId, msg: "skipping scheduled run" });
|
|
15052
15404
|
this.remove(projectId);
|
|
@@ -15075,7 +15427,7 @@ var Scheduler = class {
|
|
|
15075
15427
|
this.db.update(schedules).set({
|
|
15076
15428
|
nextRunAt,
|
|
15077
15429
|
updatedAt: now
|
|
15078
|
-
}).where(
|
|
15430
|
+
}).where(eq27(schedules.id, currentSchedule.id)).run();
|
|
15079
15431
|
return;
|
|
15080
15432
|
}
|
|
15081
15433
|
const runId = queueResult.runId;
|
|
@@ -15083,7 +15435,7 @@ var Scheduler = class {
|
|
|
15083
15435
|
lastRunAt: now,
|
|
15084
15436
|
nextRunAt,
|
|
15085
15437
|
updatedAt: now
|
|
15086
|
-
}).where(
|
|
15438
|
+
}).where(eq27(schedules.id, currentSchedule.id)).run();
|
|
15087
15439
|
const scheduleProviders = parseJsonColumn(currentSchedule.providers, []);
|
|
15088
15440
|
const providers = scheduleProviders.length > 0 ? scheduleProviders : void 0;
|
|
15089
15441
|
log8.info("run.triggered", { runId, projectName: project.name, providers: providers ?? "all" });
|
|
@@ -15095,7 +15447,7 @@ var Scheduler = class {
|
|
|
15095
15447
|
};
|
|
15096
15448
|
|
|
15097
15449
|
// src/notifier.ts
|
|
15098
|
-
import { eq as
|
|
15450
|
+
import { eq as eq28, desc as desc13, and as and15, or as or3 } from "drizzle-orm";
|
|
15099
15451
|
import crypto25 from "crypto";
|
|
15100
15452
|
var log9 = createLogger("Notifier");
|
|
15101
15453
|
var Notifier = class {
|
|
@@ -15108,18 +15460,18 @@ var Notifier = class {
|
|
|
15108
15460
|
/** Called after a run completes (success, partial, or failed). */
|
|
15109
15461
|
async onRunCompleted(runId, projectId) {
|
|
15110
15462
|
log9.info("run.completed", { runId, projectId });
|
|
15111
|
-
const notifs = this.db.select().from(notifications).where(
|
|
15463
|
+
const notifs = this.db.select().from(notifications).where(eq28(notifications.projectId, projectId)).all().filter((n) => n.enabled === 1);
|
|
15112
15464
|
if (notifs.length === 0) {
|
|
15113
15465
|
log9.info("notifications.none-enabled", { projectId });
|
|
15114
15466
|
return;
|
|
15115
15467
|
}
|
|
15116
15468
|
log9.info("notifications.found", { projectId, count: notifs.length });
|
|
15117
|
-
const run = this.db.select().from(runs).where(
|
|
15469
|
+
const run = this.db.select().from(runs).where(eq28(runs.id, runId)).get();
|
|
15118
15470
|
if (!run) {
|
|
15119
15471
|
log9.error("run.not-found", { runId, msg: "skipping notification dispatch" });
|
|
15120
15472
|
return;
|
|
15121
15473
|
}
|
|
15122
|
-
const project = this.db.select().from(projects).where(
|
|
15474
|
+
const project = this.db.select().from(projects).where(eq28(projects.id, projectId)).get();
|
|
15123
15475
|
if (!project) {
|
|
15124
15476
|
log9.error("project.not-found", { projectId, msg: "skipping notification dispatch" });
|
|
15125
15477
|
return;
|
|
@@ -15166,11 +15518,11 @@ var Notifier = class {
|
|
|
15166
15518
|
if (criticalInsights.length > 0) insightEvents.push("insight.critical");
|
|
15167
15519
|
if (highInsights.length > 0) insightEvents.push("insight.high");
|
|
15168
15520
|
if (insightEvents.length === 0) return;
|
|
15169
|
-
const notifs = this.db.select().from(notifications).where(
|
|
15521
|
+
const notifs = this.db.select().from(notifications).where(eq28(notifications.projectId, projectId)).all().filter((n) => n.enabled === 1);
|
|
15170
15522
|
if (notifs.length === 0) return;
|
|
15171
|
-
const run = this.db.select().from(runs).where(
|
|
15523
|
+
const run = this.db.select().from(runs).where(eq28(runs.id, runId)).get();
|
|
15172
15524
|
if (!run) return;
|
|
15173
|
-
const project = this.db.select().from(projects).where(
|
|
15525
|
+
const project = this.db.select().from(projects).where(eq28(projects.id, projectId)).get();
|
|
15174
15526
|
if (!project) return;
|
|
15175
15527
|
for (const notif of notifs) {
|
|
15176
15528
|
const config = parseJsonColumn(notif.config, { url: "", events: [] });
|
|
@@ -15201,11 +15553,11 @@ var Notifier = class {
|
|
|
15201
15553
|
}
|
|
15202
15554
|
computeTransitions(runId, projectId) {
|
|
15203
15555
|
const recentRuns = this.db.select().from(runs).where(
|
|
15204
|
-
|
|
15205
|
-
|
|
15206
|
-
|
|
15556
|
+
and15(
|
|
15557
|
+
eq28(runs.projectId, projectId),
|
|
15558
|
+
or3(eq28(runs.status, "completed"), eq28(runs.status, "partial"))
|
|
15207
15559
|
)
|
|
15208
|
-
).orderBy(
|
|
15560
|
+
).orderBy(desc13(runs.createdAt)).limit(2).all();
|
|
15209
15561
|
if (recentRuns.length < 2) return [];
|
|
15210
15562
|
const currentRunId = recentRuns[0].id;
|
|
15211
15563
|
const previousRunId = recentRuns[1].id;
|
|
@@ -15215,12 +15567,12 @@ var Notifier = class {
|
|
|
15215
15567
|
keyword: keywords.keyword,
|
|
15216
15568
|
provider: querySnapshots.provider,
|
|
15217
15569
|
citationState: querySnapshots.citationState
|
|
15218
|
-
}).from(querySnapshots).leftJoin(keywords,
|
|
15570
|
+
}).from(querySnapshots).leftJoin(keywords, eq28(querySnapshots.keywordId, keywords.id)).where(eq28(querySnapshots.runId, currentRunId)).all();
|
|
15219
15571
|
const previousSnapshots = this.db.select({
|
|
15220
15572
|
keywordId: querySnapshots.keywordId,
|
|
15221
15573
|
provider: querySnapshots.provider,
|
|
15222
15574
|
citationState: querySnapshots.citationState
|
|
15223
|
-
}).from(querySnapshots).where(
|
|
15575
|
+
}).from(querySnapshots).where(eq28(querySnapshots.runId, previousRunId)).all();
|
|
15224
15576
|
const prevMap = /* @__PURE__ */ new Map();
|
|
15225
15577
|
for (const s of previousSnapshots) {
|
|
15226
15578
|
prevMap.set(`${s.keywordId}:${s.provider}`, s.citationState);
|
|
@@ -15337,7 +15689,7 @@ var RunCoordinator = class {
|
|
|
15337
15689
|
|
|
15338
15690
|
// src/agent/session-registry.ts
|
|
15339
15691
|
import crypto27 from "crypto";
|
|
15340
|
-
import { eq as
|
|
15692
|
+
import { eq as eq30 } from "drizzle-orm";
|
|
15341
15693
|
|
|
15342
15694
|
// src/agent/session.ts
|
|
15343
15695
|
import fs11 from "fs";
|
|
@@ -15557,7 +15909,7 @@ import { Type as Type2 } from "@sinclair/typebox";
|
|
|
15557
15909
|
|
|
15558
15910
|
// src/agent/memory-store.ts
|
|
15559
15911
|
import crypto26 from "crypto";
|
|
15560
|
-
import { and as
|
|
15912
|
+
import { and as and16, desc as desc14, eq as eq29, like as like2, sql as sql10 } from "drizzle-orm";
|
|
15561
15913
|
var COMPACTION_KEY_PREFIX = "compaction:";
|
|
15562
15914
|
var COMPACTION_NOTES_PER_SESSION = 3;
|
|
15563
15915
|
function rowToDto(row) {
|
|
@@ -15571,7 +15923,7 @@ function rowToDto(row) {
|
|
|
15571
15923
|
};
|
|
15572
15924
|
}
|
|
15573
15925
|
function listMemoryEntries(db, projectId, opts = {}) {
|
|
15574
|
-
const query = db.select().from(agentMemory).where(
|
|
15926
|
+
const query = db.select().from(agentMemory).where(eq29(agentMemory.projectId, projectId)).orderBy(desc14(agentMemory.updatedAt));
|
|
15575
15927
|
const rows = opts.limit === void 0 ? query.all() : query.limit(opts.limit).all();
|
|
15576
15928
|
return rows.map(rowToDto);
|
|
15577
15929
|
}
|
|
@@ -15602,12 +15954,12 @@ function upsertMemoryEntry(db, args) {
|
|
|
15602
15954
|
updatedAt: now
|
|
15603
15955
|
}
|
|
15604
15956
|
}).run();
|
|
15605
|
-
const row = db.select().from(agentMemory).where(
|
|
15957
|
+
const row = db.select().from(agentMemory).where(and16(eq29(agentMemory.projectId, args.projectId), eq29(agentMemory.key, args.key))).get();
|
|
15606
15958
|
if (!row) throw new Error("memory upsert produced no row");
|
|
15607
15959
|
return rowToDto(row);
|
|
15608
15960
|
}
|
|
15609
15961
|
function deleteMemoryEntry(db, projectId, key) {
|
|
15610
|
-
const result = db.delete(agentMemory).where(
|
|
15962
|
+
const result = db.delete(agentMemory).where(and16(eq29(agentMemory.projectId, projectId), eq29(agentMemory.key, key))).run();
|
|
15611
15963
|
const changes = result.changes ?? 0;
|
|
15612
15964
|
return changes > 0;
|
|
15613
15965
|
}
|
|
@@ -15636,16 +15988,16 @@ function writeCompactionNote(db, args) {
|
|
|
15636
15988
|
}).run();
|
|
15637
15989
|
const sessionPrefix = `${COMPACTION_KEY_PREFIX}${args.sessionId}:`;
|
|
15638
15990
|
const existing = tx.select({ id: agentMemory.id, updatedAt: agentMemory.updatedAt }).from(agentMemory).where(
|
|
15639
|
-
|
|
15640
|
-
|
|
15641
|
-
|
|
15991
|
+
and16(
|
|
15992
|
+
eq29(agentMemory.projectId, args.projectId),
|
|
15993
|
+
like2(agentMemory.key, `${sessionPrefix}%`)
|
|
15642
15994
|
)
|
|
15643
|
-
).orderBy(
|
|
15995
|
+
).orderBy(desc14(agentMemory.updatedAt)).all();
|
|
15644
15996
|
const stale = existing.slice(COMPACTION_NOTES_PER_SESSION).map((r) => r.id);
|
|
15645
15997
|
if (stale.length > 0) {
|
|
15646
|
-
tx.delete(agentMemory).where(
|
|
15998
|
+
tx.delete(agentMemory).where(sql10`${agentMemory.id} IN (${sql10.join(stale.map((s) => sql10`${s}`), sql10`, `)})`).run();
|
|
15647
15999
|
}
|
|
15648
|
-
const row = tx.select().from(agentMemory).where(
|
|
16000
|
+
const row = tx.select().from(agentMemory).where(and16(eq29(agentMemory.projectId, args.projectId), eq29(agentMemory.key, key))).get();
|
|
15649
16001
|
if (row) inserted = rowToDto(row);
|
|
15650
16002
|
});
|
|
15651
16003
|
if (!inserted) throw new Error("compaction note write produced no row");
|
|
@@ -16377,7 +16729,7 @@ var SessionRegistry = class {
|
|
|
16377
16729
|
modelProvider: effectiveProvider,
|
|
16378
16730
|
modelId: effectiveModelId,
|
|
16379
16731
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
16380
|
-
}).where(
|
|
16732
|
+
}).where(eq30(agentSessions.projectId, projectId)).run();
|
|
16381
16733
|
}
|
|
16382
16734
|
const agent2 = createAeroSession({
|
|
16383
16735
|
projectName,
|
|
@@ -16595,7 +16947,7 @@ ${lines.join("\n")}
|
|
|
16595
16947
|
modelProvider: nextProvider,
|
|
16596
16948
|
modelId: nextModelId,
|
|
16597
16949
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
16598
|
-
}).where(
|
|
16950
|
+
}).where(eq30(agentSessions.projectId, projectId)).run();
|
|
16599
16951
|
}
|
|
16600
16952
|
/** Persist a session's transcript back to the DB. Call after any run settles. */
|
|
16601
16953
|
save(projectName) {
|
|
@@ -16757,11 +17109,11 @@ ${lines.join("\n")}
|
|
|
16757
17109
|
return id;
|
|
16758
17110
|
}
|
|
16759
17111
|
tryResolveProjectId(projectName) {
|
|
16760
|
-
const row = this.opts.db.select({ id: projects.id }).from(projects).where(
|
|
17112
|
+
const row = this.opts.db.select({ id: projects.id }).from(projects).where(eq30(projects.name, projectName)).get();
|
|
16761
17113
|
return row?.id;
|
|
16762
17114
|
}
|
|
16763
17115
|
loadRow(projectId) {
|
|
16764
|
-
const row = this.opts.db.select().from(agentSessions).where(
|
|
17116
|
+
const row = this.opts.db.select().from(agentSessions).where(eq30(agentSessions.projectId, projectId)).get();
|
|
16765
17117
|
return row ?? null;
|
|
16766
17118
|
}
|
|
16767
17119
|
insertRow(params) {
|
|
@@ -16780,14 +17132,14 @@ ${lines.join("\n")}
|
|
|
16780
17132
|
}
|
|
16781
17133
|
updateRow(projectId, patch) {
|
|
16782
17134
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
16783
|
-
this.opts.db.update(agentSessions).set({ ...patch, updatedAt: now }).where(
|
|
17135
|
+
this.opts.db.update(agentSessions).set({ ...patch, updatedAt: now }).where(eq30(agentSessions.projectId, projectId)).run();
|
|
16784
17136
|
}
|
|
16785
17137
|
};
|
|
16786
17138
|
|
|
16787
17139
|
// src/agent/agent-routes.ts
|
|
16788
|
-
import { eq as
|
|
17140
|
+
import { eq as eq31 } from "drizzle-orm";
|
|
16789
17141
|
function resolveProject2(db, name) {
|
|
16790
|
-
const row = db.select({ id: projects.id, name: projects.name }).from(projects).where(
|
|
17142
|
+
const row = db.select({ id: projects.id, name: projects.name }).from(projects).where(eq31(projects.name, name)).get();
|
|
16791
17143
|
if (!row) throw notFound("project", name);
|
|
16792
17144
|
return row;
|
|
16793
17145
|
}
|
|
@@ -16796,7 +17148,7 @@ function registerAgentRoutes(app, opts) {
|
|
|
16796
17148
|
"/projects/:name/agent/transcript",
|
|
16797
17149
|
async (request) => {
|
|
16798
17150
|
const project = resolveProject2(opts.db, request.params.name);
|
|
16799
|
-
const row = opts.db.select().from(agentSessions).where(
|
|
17151
|
+
const row = opts.db.select().from(agentSessions).where(eq31(agentSessions.projectId, project.id)).get();
|
|
16800
17152
|
if (!row) {
|
|
16801
17153
|
return { messages: [], modelProvider: null, modelId: null, updatedAt: null };
|
|
16802
17154
|
}
|
|
@@ -16820,7 +17172,7 @@ function registerAgentRoutes(app, opts) {
|
|
|
16820
17172
|
async (request) => {
|
|
16821
17173
|
const project = resolveProject2(opts.db, request.params.name);
|
|
16822
17174
|
opts.sessionRegistry.reset(project.name);
|
|
16823
|
-
opts.db.update(agentSessions).set({ messages: "[]", followUpQueue: "[]", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
17175
|
+
opts.db.update(agentSessions).set({ messages: "[]", followUpQueue: "[]", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq31(agentSessions.projectId, project.id)).run();
|
|
16824
17176
|
return { status: "reset" };
|
|
16825
17177
|
}
|
|
16826
17178
|
);
|
|
@@ -17842,7 +18194,7 @@ async function createServer(opts) {
|
|
|
17842
18194
|
intelligenceService,
|
|
17843
18195
|
(runId, projectId, result) => notifier.dispatchInsightWebhooks(runId, projectId, result),
|
|
17844
18196
|
async ({ runId, projectId, insightCount, criticalOrHigh }) => {
|
|
17845
|
-
const project = opts.db.select({ name: projects.name }).from(projects).where(
|
|
18197
|
+
const project = opts.db.select({ name: projects.name }).from(projects).where(eq32(projects.id, projectId)).get();
|
|
17846
18198
|
if (!project) return;
|
|
17847
18199
|
sessionRegistry.queueFollowUp(project.name, {
|
|
17848
18200
|
role: "user",
|
|
@@ -17982,7 +18334,7 @@ async function createServer(opts) {
|
|
|
17982
18334
|
const apiPrefix = basePath ? `${basePath}api/v1` : "/api/v1";
|
|
17983
18335
|
if (opts.config.apiKey) {
|
|
17984
18336
|
const keyHash = hashApiKey(opts.config.apiKey);
|
|
17985
|
-
const existing = opts.db.select().from(apiKeys).where(
|
|
18337
|
+
const existing = opts.db.select().from(apiKeys).where(eq32(apiKeys.keyHash, keyHash)).get();
|
|
17986
18338
|
if (!existing) {
|
|
17987
18339
|
const prefix = opts.config.apiKey.slice(0, 12);
|
|
17988
18340
|
opts.db.insert(apiKeys).values({
|
|
@@ -18034,7 +18386,7 @@ async function createServer(opts) {
|
|
|
18034
18386
|
};
|
|
18035
18387
|
const getDefaultApiKey = () => {
|
|
18036
18388
|
if (!opts.config.apiKey) return void 0;
|
|
18037
|
-
return opts.db.select().from(apiKeys).where(
|
|
18389
|
+
return opts.db.select().from(apiKeys).where(eq32(apiKeys.keyHash, hashApiKey(opts.config.apiKey))).get();
|
|
18038
18390
|
};
|
|
18039
18391
|
const createPasswordSession = (reply) => {
|
|
18040
18392
|
const key = getDefaultApiKey();
|
|
@@ -18091,12 +18443,12 @@ async function createServer(opts) {
|
|
|
18091
18443
|
return reply.send({ authenticated: true });
|
|
18092
18444
|
}
|
|
18093
18445
|
if (apiKey) {
|
|
18094
|
-
const key = opts.db.select().from(apiKeys).where(
|
|
18446
|
+
const key = opts.db.select().from(apiKeys).where(eq32(apiKeys.keyHash, hashApiKey(apiKey))).get();
|
|
18095
18447
|
if (!key || key.revokedAt) {
|
|
18096
18448
|
const err2 = authInvalid();
|
|
18097
18449
|
return reply.status(err2.statusCode).send(err2.toJSON());
|
|
18098
18450
|
}
|
|
18099
|
-
opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
18451
|
+
opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq32(apiKeys.id, key.id)).run();
|
|
18100
18452
|
const sessionId = createSession(key.id);
|
|
18101
18453
|
reply.header("set-cookie", serializeSessionCookie({
|
|
18102
18454
|
name: SESSION_COOKIE_NAME,
|