@ainyc/canonry 4.114.1 → 4.115.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/assets/{BacklinksPage-C60ma4Sg.js → BacklinksPage-yUb8oJuq.js} +1 -1
- package/assets/assets/{ChartPrimitives-Blgw1l6J.js → ChartPrimitives-CdQM3YDG.js} +1 -1
- package/assets/assets/ProjectPage-CHj9fvTs.js +7 -0
- package/assets/assets/{RunRow-DgQiXWZl.js → RunRow-BU_x7EcP.js} +1 -1
- package/assets/assets/{RunsPage-C9upHccV.js → RunsPage-CFIBWaO7.js} +1 -1
- package/assets/assets/{SettingsPage-Bz_-_DnJ.js → SettingsPage-PyMryUad.js} +1 -1
- package/assets/assets/{TrafficPage-BhBnQl2N.js → TrafficPage-471ypAdQ.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-DWXqcRNO.js → TrafficSourceDetailPage-CI7TEQge.js} +1 -1
- package/assets/assets/{arrow-left-C6SuuEtD.js → arrow-left-D2N9u-0i.js} +1 -1
- package/assets/assets/{extract-error-message-DPZ5yqzd.js → extract-error-message-CAdsqKp3.js} +1 -1
- package/assets/assets/index-B4R1-8hR.css +1 -0
- package/assets/assets/{index-C3oQZHf8.js → index-CoL5HENE.js} +66 -66
- package/assets/assets/{trash-2-D3Nju7AK.js → trash-2-UgQrN5Np.js} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-VEUBAZRG.js → chunk-6LOKMEL3.js} +101 -0
- package/dist/{chunk-WMWZW34J.js → chunk-B2SNXXAJ.js} +4 -4
- package/dist/{chunk-DUBZH5JW.js → chunk-CW4JISBL.js} +1 -1
- package/dist/{chunk-3VSTTH4M.js → chunk-Z3OGDQHB.js} +489 -244
- package/dist/cli.js +52 -8
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-I5EQ77KZ.js → intelligence-service-4AWB2V7U.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +8 -8
- package/assets/assets/ProjectPage-DfvEEwNU.js +0 -7
- package/assets/assets/index-CReX5Jn9.css +0 -1
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
DiscoveryBuckets,
|
|
21
21
|
DiscoveryCompetitorTypes,
|
|
22
22
|
DiscoverySessionStatuses,
|
|
23
|
+
GA4AiReferralTrafficClasses,
|
|
23
24
|
RunKinds,
|
|
24
25
|
RunStatuses,
|
|
25
26
|
RunTriggers,
|
|
@@ -87,6 +88,7 @@ import {
|
|
|
87
88
|
citationStateToCited,
|
|
88
89
|
citationVisibilityResponseSchema,
|
|
89
90
|
classifyCitedSurface,
|
|
91
|
+
classifyGa4AiReferralTrafficClass,
|
|
90
92
|
classifySkillFile,
|
|
91
93
|
classifySurfaceFromCategory,
|
|
92
94
|
classifyTrafficPath,
|
|
@@ -273,10 +275,10 @@ import {
|
|
|
273
275
|
wordpressSchemaDeployResultDtoSchema,
|
|
274
276
|
wordpressSchemaStatusResultDtoSchema,
|
|
275
277
|
wordpressStatusDtoSchema
|
|
276
|
-
} from "./chunk-
|
|
278
|
+
} from "./chunk-6LOKMEL3.js";
|
|
277
279
|
|
|
278
280
|
// src/intelligence-service.ts
|
|
279
|
-
import { eq as eq38, desc as desc18, asc as asc6, and as
|
|
281
|
+
import { eq as eq38, desc as desc18, asc as asc6, and as and30, ne as ne5, or as or6, inArray as inArray14, gte as gte7, lte as lte4 } from "drizzle-orm";
|
|
280
282
|
|
|
281
283
|
// ../db/src/client.ts
|
|
282
284
|
import { mkdirSync } from "fs";
|
|
@@ -758,6 +760,7 @@ var gaAiReferrals = sqliteTable("ga_ai_referrals", {
|
|
|
758
760
|
date: text("date").notNull(),
|
|
759
761
|
source: text("source").notNull(),
|
|
760
762
|
medium: text("medium").notNull(),
|
|
763
|
+
trafficClass: text("traffic_class").$type().notNull().default("organic"),
|
|
761
764
|
/** Which GA4 dimension produced this row: 'session' | 'first_user' | 'manual_utm' */
|
|
762
765
|
sourceDimension: text("source_dimension").notNull().default("session"),
|
|
763
766
|
/** GA4 default channel group for the session (e.g. 'Referral', 'Organic Social'). */
|
|
@@ -772,6 +775,7 @@ var gaAiReferrals = sqliteTable("ga_ai_referrals", {
|
|
|
772
775
|
index("idx_ga_ai_ref_project_date").on(table.projectId, table.date),
|
|
773
776
|
index("idx_ga_ai_ref_source").on(table.source),
|
|
774
777
|
index("idx_ga_ai_ref_landing_page").on(table.projectId, table.date, table.landingPageNormalized),
|
|
778
|
+
index("idx_ga_ai_ref_traffic_class").on(table.projectId, table.date, table.trafficClass),
|
|
775
779
|
uniqueIndex("idx_ga_ai_ref_unique_v4").on(table.projectId, table.date, table.source, table.medium, table.sourceDimension, table.channelGroup, table.landingPage),
|
|
776
780
|
index("idx_ga_ai_ref_run").on(table.syncRunId)
|
|
777
781
|
]);
|
|
@@ -3482,6 +3486,41 @@ var MIGRATION_VERSIONS = [
|
|
|
3482
3486
|
// post-truncation). Additive + nullable (downgrade-safe).
|
|
3483
3487
|
`ALTER TABLE discovery_sessions ADD COLUMN canonical_count INTEGER`
|
|
3484
3488
|
]
|
|
3489
|
+
},
|
|
3490
|
+
{
|
|
3491
|
+
version: 95,
|
|
3492
|
+
name: "ga-ai-referral-traffic-class",
|
|
3493
|
+
statements: [],
|
|
3494
|
+
run: (tx) => {
|
|
3495
|
+
if (!tableExists(tx, "ga_ai_referrals")) return;
|
|
3496
|
+
if (!columnExists(tx, "ga_ai_referrals", "traffic_class")) {
|
|
3497
|
+
tx.run(sql.raw(`ALTER TABLE ga_ai_referrals ADD COLUMN traffic_class TEXT NOT NULL DEFAULT 'organic'`));
|
|
3498
|
+
}
|
|
3499
|
+
tx.run(sql.raw(`CREATE INDEX IF NOT EXISTS idx_ga_ai_ref_traffic_class ON ga_ai_referrals(project_id, date, traffic_class)`));
|
|
3500
|
+
}
|
|
3501
|
+
},
|
|
3502
|
+
{
|
|
3503
|
+
// v95 added `ga_ai_referrals.traffic_class` with `DEFAULT 'organic'` and did
|
|
3504
|
+
// not classify the rows that already existed. Every historical AI referral
|
|
3505
|
+
// was therefore stamped 'organic', including paid ChatGPT-ads traffic
|
|
3506
|
+
// carrying `medium='cpc'` / `channel_group='Paid Other'`. That silently
|
|
3507
|
+
// reports purchased sessions as organic wins.
|
|
3508
|
+
//
|
|
3509
|
+
// The class is a pure function of columns already stored on each row, so
|
|
3510
|
+
// re-derive it here instead of making an operator delete and re-fetch
|
|
3511
|
+
// identical data from GA4.
|
|
3512
|
+
//
|
|
3513
|
+
// Every row is re-classified, not only the "unclassified" ones: v95's
|
|
3514
|
+
// default is indistinguishable from a genuinely-organic classification, so
|
|
3515
|
+
// there is no way to tell the two apart. `classifyGa4AiReferralTrafficClass`
|
|
3516
|
+
// is pure and deterministic, so re-running it over already-correct rows
|
|
3517
|
+
// writes nothing, which is what makes this version safe to replay.
|
|
3518
|
+
version: 96,
|
|
3519
|
+
name: "ga-ai-referral-traffic-class-backfill",
|
|
3520
|
+
statements: [],
|
|
3521
|
+
run: (tx) => {
|
|
3522
|
+
backfillGaAiReferralTrafficClass(tx);
|
|
3523
|
+
}
|
|
3485
3524
|
}
|
|
3486
3525
|
];
|
|
3487
3526
|
function rebuildBacklinkTableWithSource(tx, table) {
|
|
@@ -3568,6 +3607,26 @@ function tableIsEmpty(db, table) {
|
|
|
3568
3607
|
const rows = db.all(sql.raw(`SELECT COUNT(*) as c FROM ${table}`));
|
|
3569
3608
|
return (rows[0]?.c ?? 0) === 0;
|
|
3570
3609
|
}
|
|
3610
|
+
function backfillGaAiReferralTrafficClass(tx) {
|
|
3611
|
+
if (!tableExists(tx, "ga_ai_referrals")) return 0;
|
|
3612
|
+
if (!columnExists(tx, "ga_ai_referrals", "traffic_class")) return 0;
|
|
3613
|
+
const rows = tx.all(sql.raw(
|
|
3614
|
+
`SELECT id, source, medium, channel_group, landing_page, traffic_class FROM ga_ai_referrals`
|
|
3615
|
+
));
|
|
3616
|
+
let updated = 0;
|
|
3617
|
+
for (const row of rows) {
|
|
3618
|
+
const next = classifyGa4AiReferralTrafficClass({
|
|
3619
|
+
source: row.source,
|
|
3620
|
+
medium: row.medium,
|
|
3621
|
+
channelGroup: row.channel_group,
|
|
3622
|
+
landingPage: row.landing_page
|
|
3623
|
+
});
|
|
3624
|
+
if (next === row.traffic_class) continue;
|
|
3625
|
+
tx.run(sql`UPDATE ga_ai_referrals SET traffic_class = ${next} WHERE id = ${row.id}`);
|
|
3626
|
+
updated += 1;
|
|
3627
|
+
}
|
|
3628
|
+
return updated;
|
|
3629
|
+
}
|
|
3571
3630
|
function hasLegacyQuerySchema(db) {
|
|
3572
3631
|
return tableExists(db, "keywords") || columnExists(db, "query_snapshots", "keyword_id") || columnExists(db, "insights", "keyword");
|
|
3573
3632
|
}
|
|
@@ -6706,7 +6765,7 @@ async function queryRoutes(app, opts) {
|
|
|
6706
6765
|
|
|
6707
6766
|
// ../api-routes/src/competitors.ts
|
|
6708
6767
|
import crypto6 from "crypto";
|
|
6709
|
-
import { eq as eq5 } from "drizzle-orm";
|
|
6768
|
+
import { and as and2, eq as eq5 } from "drizzle-orm";
|
|
6710
6769
|
function normalizeCompetitor(domain) {
|
|
6711
6770
|
const reg = registrableDomain(domain);
|
|
6712
6771
|
if (reg) return reg;
|
|
@@ -6725,11 +6784,14 @@ function normalizeCompetitorList(domains) {
|
|
|
6725
6784
|
}
|
|
6726
6785
|
return result;
|
|
6727
6786
|
}
|
|
6787
|
+
function serializeCompetitor(row) {
|
|
6788
|
+
return { id: row.id, domain: row.domain, createdAt: row.createdAt };
|
|
6789
|
+
}
|
|
6728
6790
|
async function competitorRoutes(app) {
|
|
6729
6791
|
app.get("/projects/:name/competitors", async (request, reply) => {
|
|
6730
6792
|
const project = resolveProject(app.db, request.params.name);
|
|
6731
6793
|
const rows = app.db.select().from(competitors).where(eq5(competitors.projectId, project.id)).all();
|
|
6732
|
-
return reply.send(rows.map(
|
|
6794
|
+
return reply.send(rows.map(serializeCompetitor));
|
|
6733
6795
|
});
|
|
6734
6796
|
app.put("/projects/:name/competitors", async (request, reply) => {
|
|
6735
6797
|
const project = resolveProject(app.db, request.params.name);
|
|
@@ -6759,7 +6821,7 @@ async function competitorRoutes(app) {
|
|
|
6759
6821
|
});
|
|
6760
6822
|
});
|
|
6761
6823
|
const rows = app.db.select().from(competitors).where(eq5(competitors.projectId, project.id)).all();
|
|
6762
|
-
return reply.send(rows.map(
|
|
6824
|
+
return reply.send(rows.map(serializeCompetitor));
|
|
6763
6825
|
});
|
|
6764
6826
|
app.post("/projects/:name/competitors", async (request, reply) => {
|
|
6765
6827
|
const project = resolveProject(app.db, request.params.name);
|
|
@@ -6791,7 +6853,7 @@ async function competitorRoutes(app) {
|
|
|
6791
6853
|
});
|
|
6792
6854
|
});
|
|
6793
6855
|
const rows = app.db.select().from(competitors).where(eq5(competitors.projectId, project.id)).all();
|
|
6794
|
-
return reply.send(rows.map(
|
|
6856
|
+
return reply.send(rows.map(serializeCompetitor));
|
|
6795
6857
|
});
|
|
6796
6858
|
app.delete("/projects/:name/competitors", async (request, reply) => {
|
|
6797
6859
|
const project = resolveProject(app.db, request.params.name);
|
|
@@ -6813,7 +6875,26 @@ async function competitorRoutes(app) {
|
|
|
6813
6875
|
});
|
|
6814
6876
|
});
|
|
6815
6877
|
const rows = app.db.select().from(competitors).where(eq5(competitors.projectId, project.id)).all();
|
|
6816
|
-
return reply.send(rows.map(
|
|
6878
|
+
return reply.send(rows.map(serializeCompetitor));
|
|
6879
|
+
});
|
|
6880
|
+
app.delete("/projects/:name/competitors/:id", async (request, reply) => {
|
|
6881
|
+
const project = resolveProject(app.db, request.params.name);
|
|
6882
|
+
const competitor = app.db.select().from(competitors).where(and2(eq5(competitors.projectId, project.id), eq5(competitors.id, request.params.id))).get();
|
|
6883
|
+
if (!competitor) {
|
|
6884
|
+
throw notFound("Competitor", request.params.id);
|
|
6885
|
+
}
|
|
6886
|
+
app.db.transaction((tx) => {
|
|
6887
|
+
tx.delete(competitors).where(eq5(competitors.id, competitor.id)).run();
|
|
6888
|
+
writeAuditLog(tx, auditFromRequest(request, {
|
|
6889
|
+
projectId: project.id,
|
|
6890
|
+
actor: "api",
|
|
6891
|
+
action: "competitors.deleted",
|
|
6892
|
+
entityType: "competitor",
|
|
6893
|
+
entityId: competitor.id,
|
|
6894
|
+
diff: { deleted: [competitor.domain] }
|
|
6895
|
+
}));
|
|
6896
|
+
});
|
|
6897
|
+
return reply.status(204).send();
|
|
6817
6898
|
});
|
|
6818
6899
|
}
|
|
6819
6900
|
function parseCompetitorBatch(value) {
|
|
@@ -6829,12 +6910,12 @@ function parseCompetitorBatch(value) {
|
|
|
6829
6910
|
|
|
6830
6911
|
// ../api-routes/src/runs.ts
|
|
6831
6912
|
import crypto8 from "crypto";
|
|
6832
|
-
import { and as
|
|
6913
|
+
import { and as and4, eq as eq7, asc, desc, or as or2, sql as sql5 } from "drizzle-orm";
|
|
6833
6914
|
import { gte } from "drizzle-orm";
|
|
6834
6915
|
|
|
6835
6916
|
// ../api-routes/src/run-queue.ts
|
|
6836
6917
|
import crypto7 from "crypto";
|
|
6837
|
-
import { and as
|
|
6918
|
+
import { and as and3, eq as eq6, or } from "drizzle-orm";
|
|
6838
6919
|
function queueRunIfProjectIdle(db, params) {
|
|
6839
6920
|
const createdAt = params.createdAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
6840
6921
|
const kind = params.kind ?? "answer-visibility";
|
|
@@ -6842,7 +6923,7 @@ function queueRunIfProjectIdle(db, params) {
|
|
|
6842
6923
|
const runId = crypto7.randomUUID();
|
|
6843
6924
|
return db.transaction((tx) => {
|
|
6844
6925
|
const activeRun = tx.select().from(runs).where(
|
|
6845
|
-
|
|
6926
|
+
and3(
|
|
6846
6927
|
eq6(runs.projectId, params.projectId),
|
|
6847
6928
|
or(eq6(runs.status, "queued"), eq6(runs.status, "running"))
|
|
6848
6929
|
)
|
|
@@ -6925,7 +7006,7 @@ async function runRoutes(app, opts) {
|
|
|
6925
7006
|
throw validationError("No locations configured for this project");
|
|
6926
7007
|
}
|
|
6927
7008
|
const result = app.db.transaction((tx) => {
|
|
6928
|
-
const activeRun = tx.select({ id: runs.id }).from(runs).where(
|
|
7009
|
+
const activeRun = tx.select({ id: runs.id }).from(runs).where(and4(
|
|
6929
7010
|
eq7(runs.projectId, project.id),
|
|
6930
7011
|
or2(eq7(runs.status, "queued"), eq7(runs.status, "running"))
|
|
6931
7012
|
)).get();
|
|
@@ -6998,15 +7079,15 @@ async function runRoutes(app, opts) {
|
|
|
6998
7079
|
const parsedLimit = parseInt(request.query.limit ?? "", 10);
|
|
6999
7080
|
const limit = Number.isNaN(parsedLimit) || parsedLimit <= 0 ? void 0 : parsedLimit;
|
|
7000
7081
|
const kind = parseListKind(request.query.kind);
|
|
7001
|
-
const where = kind ?
|
|
7082
|
+
const where = kind ? and4(eq7(runs.projectId, project.id), eq7(runs.kind, kind)) : eq7(runs.projectId, project.id);
|
|
7002
7083
|
const rows = limit == null ? app.db.select().from(runs).where(where).orderBy(asc(runs.createdAt)).all() : app.db.select().from(runs).where(where).orderBy(desc(runs.createdAt)).limit(limit).all().reverse();
|
|
7003
7084
|
return reply.send(rows.map(formatRun));
|
|
7004
7085
|
});
|
|
7005
7086
|
app.get("/projects/:name/runs/latest", async (request, reply) => {
|
|
7006
7087
|
const project = resolveProject(app.db, request.params.name);
|
|
7007
|
-
const countRow = app.db.select({ count: sql5`count(*)` }).from(runs).where(
|
|
7088
|
+
const countRow = app.db.select({ count: sql5`count(*)` }).from(runs).where(and4(eq7(runs.projectId, project.id), notProbeRun())).get();
|
|
7008
7089
|
const totalRuns = countRow?.count ?? 0;
|
|
7009
|
-
const latestRun = app.db.select().from(runs).where(
|
|
7090
|
+
const latestRun = app.db.select().from(runs).where(and4(eq7(runs.projectId, project.id), notProbeRun())).orderBy(desc(runs.createdAt), desc(runs.id)).limit(1).get();
|
|
7010
7091
|
if (!latestRun) {
|
|
7011
7092
|
return reply.send({ totalRuns: 0, run: null });
|
|
7012
7093
|
}
|
|
@@ -7025,7 +7106,7 @@ async function runRoutes(app, opts) {
|
|
|
7025
7106
|
if (kind) filters.push(eq7(runs.kind, kind));
|
|
7026
7107
|
const scopedProjectId = request.apiKey?.projectId;
|
|
7027
7108
|
if (scopedProjectId) filters.push(eq7(runs.projectId, scopedProjectId));
|
|
7028
|
-
const rows = app.db.select().from(runs).where(
|
|
7109
|
+
const rows = app.db.select().from(runs).where(and4(...filters)).orderBy(desc(runs.createdAt), desc(runs.id)).limit(limit).all();
|
|
7029
7110
|
return reply.send(rows.map(formatRun));
|
|
7030
7111
|
});
|
|
7031
7112
|
app.post("/runs", async (request, reply) => {
|
|
@@ -7236,7 +7317,7 @@ function loadRunDetail(app, run) {
|
|
|
7236
7317
|
|
|
7237
7318
|
// ../api-routes/src/apply.ts
|
|
7238
7319
|
import crypto10 from "crypto";
|
|
7239
|
-
import { and as
|
|
7320
|
+
import { and as and5, eq as eq8 } from "drizzle-orm";
|
|
7240
7321
|
|
|
7241
7322
|
// ../api-routes/src/schedule-utils.ts
|
|
7242
7323
|
import { CronExpressionParser } from "cron-parser";
|
|
@@ -7679,7 +7760,7 @@ async function applyRoutes(app, opts) {
|
|
|
7679
7760
|
});
|
|
7680
7761
|
const AV_KIND = SchedulableRunKinds["answer-visibility"];
|
|
7681
7762
|
if (resolvedSchedule) {
|
|
7682
|
-
const existingSched = tx.select().from(schedules).where(
|
|
7763
|
+
const existingSched = tx.select().from(schedules).where(and5(eq8(schedules.projectId, projectId), eq8(schedules.kind, AV_KIND))).get();
|
|
7683
7764
|
if (existingSched) {
|
|
7684
7765
|
tx.update(schedules).set({
|
|
7685
7766
|
cronExpr: resolvedSchedule.cronExpr,
|
|
@@ -7705,9 +7786,9 @@ async function applyRoutes(app, opts) {
|
|
|
7705
7786
|
}
|
|
7706
7787
|
scheduleAction = "upsert";
|
|
7707
7788
|
} else if (deleteSchedule) {
|
|
7708
|
-
const existingSched = tx.select().from(schedules).where(
|
|
7789
|
+
const existingSched = tx.select().from(schedules).where(and5(eq8(schedules.projectId, projectId), eq8(schedules.kind, AV_KIND))).get();
|
|
7709
7790
|
if (existingSched) {
|
|
7710
|
-
tx.delete(schedules).where(
|
|
7791
|
+
tx.delete(schedules).where(and5(eq8(schedules.projectId, projectId), eq8(schedules.kind, AV_KIND))).run();
|
|
7711
7792
|
scheduleAction = "delete";
|
|
7712
7793
|
}
|
|
7713
7794
|
}
|
|
@@ -7791,7 +7872,7 @@ function normalizeCompetitorList2(domains) {
|
|
|
7791
7872
|
}
|
|
7792
7873
|
|
|
7793
7874
|
// ../api-routes/src/history.ts
|
|
7794
|
-
import { and as
|
|
7875
|
+
import { and as and6, eq as eq9, desc as desc2, inArray as inArray2 } from "drizzle-orm";
|
|
7795
7876
|
|
|
7796
7877
|
// ../api-routes/src/notification-redaction.ts
|
|
7797
7878
|
var REDACTED_URL = {
|
|
@@ -7849,7 +7930,7 @@ async function historyRoutes(app) {
|
|
|
7849
7930
|
const project = resolveProject(app.db, request.params.name);
|
|
7850
7931
|
const limit = parseInt(request.query.limit ?? "50", 10);
|
|
7851
7932
|
const offset = parseInt(request.query.offset ?? "0", 10);
|
|
7852
|
-
const projectRuns = app.db.select({ id: runs.id }).from(runs).where(
|
|
7933
|
+
const projectRuns = app.db.select({ id: runs.id }).from(runs).where(and6(eq9(runs.projectId, project.id), notProbeRun())).all();
|
|
7853
7934
|
if (projectRuns.length === 0) {
|
|
7854
7935
|
return reply.send({ snapshots: [], total: 0 });
|
|
7855
7936
|
}
|
|
@@ -7898,7 +7979,7 @@ async function historyRoutes(app) {
|
|
|
7898
7979
|
app.get("/projects/:name/timeline", async (request, reply) => {
|
|
7899
7980
|
const project = resolveProject(app.db, request.params.name);
|
|
7900
7981
|
const projectQueries = app.db.select().from(queries).where(eq9(queries.projectId, project.id)).all();
|
|
7901
|
-
const projectRuns = app.db.select().from(runs).where(
|
|
7982
|
+
const projectRuns = app.db.select().from(runs).where(and6(eq9(runs.projectId, project.id), notProbeRun())).orderBy(runs.createdAt).all();
|
|
7902
7983
|
if (projectRuns.length === 0 || projectQueries.length === 0) {
|
|
7903
7984
|
return reply.send([]);
|
|
7904
7985
|
}
|
|
@@ -8109,13 +8190,13 @@ function formatAuditEntry(row) {
|
|
|
8109
8190
|
}
|
|
8110
8191
|
|
|
8111
8192
|
// ../api-routes/src/analytics.ts
|
|
8112
|
-
import { and as
|
|
8193
|
+
import { and as and7, eq as eq10, desc as desc3, inArray as inArray3 } from "drizzle-orm";
|
|
8113
8194
|
async function analyticsRoutes(app) {
|
|
8114
8195
|
app.get("/projects/:name/analytics/metrics", async (request, reply) => {
|
|
8115
8196
|
const project = resolveProject(app.db, request.params.name);
|
|
8116
8197
|
const window = parseWindow(request.query.window);
|
|
8117
8198
|
const cutoff = windowCutoff(window);
|
|
8118
|
-
const projectRuns = app.db.select().from(runs).where(
|
|
8199
|
+
const projectRuns = app.db.select().from(runs).where(and7(eq10(runs.projectId, project.id), eq10(runs.kind, RunKinds["answer-visibility"]), notProbeRun())).orderBy(runs.createdAt).all().filter((r) => r.status === "completed" || r.status === "partial").filter((r) => !cutoff || r.createdAt >= cutoff);
|
|
8119
8200
|
if (projectRuns.length === 0) {
|
|
8120
8201
|
return reply.send({
|
|
8121
8202
|
window,
|
|
@@ -8167,14 +8248,14 @@ async function analyticsRoutes(app) {
|
|
|
8167
8248
|
const project = resolveProject(app.db, request.params.name);
|
|
8168
8249
|
const window = parseWindow(request.query.window);
|
|
8169
8250
|
const cutoff = windowCutoff(window);
|
|
8170
|
-
const completedRuns = app.db.select().from(runs).where(
|
|
8251
|
+
const completedRuns = app.db.select().from(runs).where(and7(eq10(runs.projectId, project.id), eq10(runs.kind, RunKinds["answer-visibility"]), notProbeRun())).orderBy(desc3(runs.createdAt), desc3(runs.id)).all().filter((r) => r.status === "completed" || r.status === "partial");
|
|
8171
8252
|
const latestGroup = groupRunsByCreatedAt(completedRuns)[0] ?? [];
|
|
8172
8253
|
const latestGroupRunIds = latestGroup.map((r) => r.id);
|
|
8173
8254
|
const latestRun = pickGroupRepresentative(latestGroup);
|
|
8174
8255
|
if (!latestRun) {
|
|
8175
8256
|
return reply.send({ cited: [], gap: [], uncited: [], mentionedQueries: [], mentionGap: [], notMentioned: [], runId: "", window });
|
|
8176
8257
|
}
|
|
8177
|
-
const windowRuns = app.db.select().from(runs).where(
|
|
8258
|
+
const windowRuns = app.db.select().from(runs).where(and7(eq10(runs.projectId, project.id), eq10(runs.kind, RunKinds["answer-visibility"]), notProbeRun())).orderBy(runs.createdAt).all().filter((r) => r.status === "completed" || r.status === "partial").filter((r) => !cutoff || r.createdAt >= cutoff);
|
|
8178
8259
|
const windowRunIds = windowRuns.map((r) => r.id);
|
|
8179
8260
|
const runIdToCreatedAt = new Map(windowRuns.map((r) => [r.id, r.createdAt]));
|
|
8180
8261
|
const consistencyMap = /* @__PURE__ */ new Map();
|
|
@@ -8305,7 +8386,7 @@ async function analyticsRoutes(app) {
|
|
|
8305
8386
|
const mapped = surfaceClassFromCompetitorType(row.competitorType);
|
|
8306
8387
|
if (mapped) storedSurfaceClasses.set(normalizeProjectDomain(row.domain), mapped);
|
|
8307
8388
|
}
|
|
8308
|
-
const windowRuns = app.db.select().from(runs).where(
|
|
8389
|
+
const windowRuns = app.db.select().from(runs).where(and7(eq10(runs.projectId, project.id), eq10(runs.kind, RunKinds["answer-visibility"]), notProbeRun())).orderBy(desc3(runs.createdAt), desc3(runs.id)).all().filter((r) => r.status === "completed" || r.status === "partial").filter((r) => !cutoff || r.createdAt >= cutoff);
|
|
8309
8390
|
if (windowRuns.length === 0) {
|
|
8310
8391
|
return reply.send({
|
|
8311
8392
|
overall: [],
|
|
@@ -8570,7 +8651,7 @@ function buildCategoryCounts(counts2) {
|
|
|
8570
8651
|
}
|
|
8571
8652
|
|
|
8572
8653
|
// ../api-routes/src/intelligence.ts
|
|
8573
|
-
import { eq as eq11, desc as desc4, and as
|
|
8654
|
+
import { eq as eq11, desc as desc4, and as and8, inArray as inArray4, like } from "drizzle-orm";
|
|
8574
8655
|
var SEVERITY_RANK = { low: 0, medium: 1, high: 2, critical: 3 };
|
|
8575
8656
|
function severitiesAtOrAbove(min) {
|
|
8576
8657
|
const floor = SEVERITY_RANK[min];
|
|
@@ -8710,7 +8791,7 @@ async function intelligenceRoutes(app) {
|
|
|
8710
8791
|
}
|
|
8711
8792
|
limit = parsed;
|
|
8712
8793
|
}
|
|
8713
|
-
const rows = app.db.select().from(insights).where(conditions.length === 1 ? conditions[0] :
|
|
8794
|
+
const rows = app.db.select().from(insights).where(conditions.length === 1 ? conditions[0] : and8(...conditions)).orderBy(desc4(insights.createdAt)).all();
|
|
8714
8795
|
const showDismissed = request.query.dismissed === "true";
|
|
8715
8796
|
let result = rows.filter((r) => showDismissed || !r.dismissed).map(mapInsightRow);
|
|
8716
8797
|
if (limit !== void 0) result = result.slice(0, limit);
|
|
@@ -8735,7 +8816,7 @@ async function intelligenceRoutes(app) {
|
|
|
8735
8816
|
});
|
|
8736
8817
|
app.get("/projects/:name/health/latest", async (request, reply) => {
|
|
8737
8818
|
const project = resolveProject(app.db, request.params.name);
|
|
8738
|
-
const projectVisRuns = app.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(
|
|
8819
|
+
const projectVisRuns = app.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(and8(
|
|
8739
8820
|
eq11(runs.projectId, project.id),
|
|
8740
8821
|
eq11(runs.kind, RunKinds["answer-visibility"]),
|
|
8741
8822
|
inArray4(runs.status, [RunStatuses.completed, RunStatuses.partial]),
|
|
@@ -8746,7 +8827,7 @@ async function intelligenceRoutes(app) {
|
|
|
8746
8827
|
const latestGroup = groupRunsByCreatedAt(projectVisRuns)[0] ?? [];
|
|
8747
8828
|
const latestGroupRunIds = latestGroup.map((r) => r.id);
|
|
8748
8829
|
if (latestGroupRunIds.length > 0) {
|
|
8749
|
-
const groupRows = app.db.select().from(healthSnapshots).where(
|
|
8830
|
+
const groupRows = app.db.select().from(healthSnapshots).where(and8(
|
|
8750
8831
|
eq11(healthSnapshots.projectId, project.id),
|
|
8751
8832
|
inArray4(healthSnapshots.runId, latestGroupRunIds)
|
|
8752
8833
|
)).all();
|
|
@@ -8770,14 +8851,14 @@ async function intelligenceRoutes(app) {
|
|
|
8770
8851
|
}
|
|
8771
8852
|
|
|
8772
8853
|
// ../api-routes/src/report.ts
|
|
8773
|
-
import { and as
|
|
8854
|
+
import { and as and12, desc as desc7, eq as eq15, gte as gte2, inArray as inArray6, lt, lte, ne as ne2, or as or3, sql as sql7 } from "drizzle-orm";
|
|
8774
8855
|
|
|
8775
8856
|
// ../api-routes/src/content.ts
|
|
8776
8857
|
import crypto11 from "crypto";
|
|
8777
|
-
import { and as
|
|
8858
|
+
import { and as and10, desc as desc6, eq as eq13 } from "drizzle-orm";
|
|
8778
8859
|
|
|
8779
8860
|
// ../api-routes/src/content-data.ts
|
|
8780
|
-
import { and as
|
|
8861
|
+
import { and as and9, eq as eq12, desc as desc5, inArray as inArray5 } from "drizzle-orm";
|
|
8781
8862
|
var RECENT_RUNS_WINDOW = 5;
|
|
8782
8863
|
function loadOrchestratorInput(db, project, locationFilter = void 0) {
|
|
8783
8864
|
const projectId = project.id;
|
|
@@ -8907,7 +8988,7 @@ function listCompetitorDomains(db, projectId) {
|
|
|
8907
8988
|
}
|
|
8908
8989
|
function listRecentAnswerVisibilityRunIds(db, projectId, limit, locationFilter) {
|
|
8909
8990
|
const rows = db.select({ id: runs.id, location: runs.location }).from(runs).where(
|
|
8910
|
-
|
|
8991
|
+
and9(
|
|
8911
8992
|
eq12(runs.projectId, projectId),
|
|
8912
8993
|
eq12(runs.kind, RunKinds["answer-visibility"]),
|
|
8913
8994
|
// Queued/running/failed/cancelled runs may have partial or no
|
|
@@ -9278,7 +9359,7 @@ async function contentRoutes(app, opts = {}) {
|
|
|
9278
9359
|
dismissedAt: now
|
|
9279
9360
|
}
|
|
9280
9361
|
}).run();
|
|
9281
|
-
const row = app.db.select().from(contentTargetDismissals).where(
|
|
9362
|
+
const row = app.db.select().from(contentTargetDismissals).where(and10(
|
|
9282
9363
|
eq13(contentTargetDismissals.projectId, project.id),
|
|
9283
9364
|
eq13(contentTargetDismissals.targetRef, targetRef)
|
|
9284
9365
|
)).get();
|
|
@@ -9288,7 +9369,7 @@ async function contentRoutes(app, opts = {}) {
|
|
|
9288
9369
|
app.delete("/projects/:name/content/dismissals/:targetRef", async (request, reply) => {
|
|
9289
9370
|
const project = resolveProject(app.db, request.params.name);
|
|
9290
9371
|
const { targetRef } = request.params;
|
|
9291
|
-
const result = app.db.delete(contentTargetDismissals).where(
|
|
9372
|
+
const result = app.db.delete(contentTargetDismissals).where(and10(
|
|
9292
9373
|
eq13(contentTargetDismissals.projectId, project.id),
|
|
9293
9374
|
eq13(contentTargetDismissals.targetRef, targetRef)
|
|
9294
9375
|
)).run();
|
|
@@ -9299,7 +9380,7 @@ async function contentRoutes(app, opts = {}) {
|
|
|
9299
9380
|
});
|
|
9300
9381
|
app.get("/projects/:name/content/recommendations/:targetRef/analysis", async (request, reply) => {
|
|
9301
9382
|
const project = resolveProject(app.db, request.params.name);
|
|
9302
|
-
const row = app.db.select().from(recommendationExplanations).where(
|
|
9383
|
+
const row = app.db.select().from(recommendationExplanations).where(and10(
|
|
9303
9384
|
eq13(recommendationExplanations.projectId, project.id),
|
|
9304
9385
|
eq13(recommendationExplanations.targetRef, request.params.targetRef)
|
|
9305
9386
|
)).orderBy(desc6(recommendationExplanations.generatedAt)).limit(1).get();
|
|
@@ -9327,7 +9408,7 @@ async function contentRoutes(app, opts = {}) {
|
|
|
9327
9408
|
throw notFound("contentRecommendation", targetRef);
|
|
9328
9409
|
}
|
|
9329
9410
|
if (!body.forceRefresh) {
|
|
9330
|
-
const cached = app.db.select().from(recommendationExplanations).where(
|
|
9411
|
+
const cached = app.db.select().from(recommendationExplanations).where(and10(
|
|
9331
9412
|
eq13(recommendationExplanations.projectId, project.id),
|
|
9332
9413
|
eq13(recommendationExplanations.targetRef, targetRef)
|
|
9333
9414
|
)).orderBy(desc6(recommendationExplanations.generatedAt)).limit(1).get();
|
|
@@ -9366,7 +9447,7 @@ async function contentRoutes(app, opts = {}) {
|
|
|
9366
9447
|
generatedAt: now
|
|
9367
9448
|
}
|
|
9368
9449
|
}).run();
|
|
9369
|
-
const row = app.db.select().from(recommendationExplanations).where(
|
|
9450
|
+
const row = app.db.select().from(recommendationExplanations).where(and10(
|
|
9370
9451
|
eq13(recommendationExplanations.projectId, project.id),
|
|
9371
9452
|
eq13(recommendationExplanations.targetRef, targetRef),
|
|
9372
9453
|
eq13(recommendationExplanations.promptVersion, result.promptVersion)
|
|
@@ -9450,7 +9531,7 @@ async function contentRoutes(app, opts = {}) {
|
|
|
9450
9531
|
generatedAt: now
|
|
9451
9532
|
}
|
|
9452
9533
|
}).run();
|
|
9453
|
-
const row = app.db.select().from(recommendationBriefs).where(
|
|
9534
|
+
const row = app.db.select().from(recommendationBriefs).where(and10(
|
|
9454
9535
|
eq13(recommendationBriefs.projectId, project.id),
|
|
9455
9536
|
eq13(recommendationBriefs.targetRef, targetRef),
|
|
9456
9537
|
eq13(recommendationBriefs.promptVersion, result.promptVersion)
|
|
@@ -9480,7 +9561,7 @@ function lookupCachedBrief(db, projectId, targetRef, promptVersion) {
|
|
|
9480
9561
|
if (promptVersion !== void 0) {
|
|
9481
9562
|
conditions.push(eq13(recommendationBriefs.promptVersion, promptVersion));
|
|
9482
9563
|
}
|
|
9483
|
-
return db.select().from(recommendationBriefs).where(
|
|
9564
|
+
return db.select().from(recommendationBriefs).where(and10(...conditions)).orderBy(desc6(recommendationBriefs.generatedAt)).limit(1).get();
|
|
9484
9565
|
}
|
|
9485
9566
|
function parseLimitParam(raw) {
|
|
9486
9567
|
if (raw === void 0) return void 0;
|
|
@@ -9506,7 +9587,7 @@ function winnabilityClassRank(winnabilityClass) {
|
|
|
9506
9587
|
}
|
|
9507
9588
|
|
|
9508
9589
|
// ../api-routes/src/gsc-totals.ts
|
|
9509
|
-
import { and as
|
|
9590
|
+
import { and as and11, asc as asc2, eq as eq14, sql as sql6 } from "drizzle-orm";
|
|
9510
9591
|
function readGscDailyTotals(db, projectId, startDate, endDate) {
|
|
9511
9592
|
const rows = db.select({
|
|
9512
9593
|
date: gscDailyTotals.date,
|
|
@@ -9514,7 +9595,7 @@ function readGscDailyTotals(db, projectId, startDate, endDate) {
|
|
|
9514
9595
|
impressions: gscDailyTotals.impressions,
|
|
9515
9596
|
position: gscDailyTotals.position
|
|
9516
9597
|
}).from(gscDailyTotals).where(
|
|
9517
|
-
|
|
9598
|
+
and11(
|
|
9518
9599
|
eq14(gscDailyTotals.projectId, projectId),
|
|
9519
9600
|
sql6`${gscDailyTotals.date} >= ${startDate}`,
|
|
9520
9601
|
sql6`${gscDailyTotals.date} <= ${endDate}`
|
|
@@ -11823,6 +11904,9 @@ function safeNum(value) {
|
|
|
11823
11904
|
}
|
|
11824
11905
|
return 0;
|
|
11825
11906
|
}
|
|
11907
|
+
function isPaidAiTrafficClass(value) {
|
|
11908
|
+
return value === GA4AiReferralTrafficClasses.paid;
|
|
11909
|
+
}
|
|
11826
11910
|
function categorizeQuery(query, projectBrandNames, canonicalDomain) {
|
|
11827
11911
|
return categorizeQueryByIntent(query, buildBrandTokens(canonicalDomain, projectBrandNames));
|
|
11828
11912
|
}
|
|
@@ -11945,7 +12029,7 @@ function buildGscSection(db, projectId, projectBrandNames, canonicalDomain, trac
|
|
|
11945
12029
|
function buildGaSection(db, projectId, windowDays) {
|
|
11946
12030
|
const gaWindowKey = GA_WINDOW_SUMMARY_KEYS[windowDays];
|
|
11947
12031
|
const windowSummary = gaWindowKey ? db.select().from(gaTrafficWindowSummaries).where(
|
|
11948
|
-
|
|
12032
|
+
and12(
|
|
11949
12033
|
eq15(gaTrafficWindowSummaries.projectId, projectId),
|
|
11950
12034
|
eq15(gaTrafficWindowSummaries.windowKey, gaWindowKey)
|
|
11951
12035
|
)
|
|
@@ -11977,14 +12061,40 @@ function buildGaSection(db, projectId, windowDays) {
|
|
|
11977
12061
|
users: data.users,
|
|
11978
12062
|
organicSessions: data.organic
|
|
11979
12063
|
})).sort((a, b) => b.sessions - a.sessions).slice(0, TOP_LANDING_PAGES_LIMIT);
|
|
12064
|
+
const aiConditions = [
|
|
12065
|
+
eq15(gaAiReferrals.projectId, projectId),
|
|
12066
|
+
eq15(gaAiReferrals.sourceDimension, "session")
|
|
12067
|
+
];
|
|
12068
|
+
if (snapshotStartDate && snapshotMaxDate) {
|
|
12069
|
+
aiConditions.push(gte2(gaAiReferrals.date, snapshotStartDate));
|
|
12070
|
+
aiConditions.push(lte(gaAiReferrals.date, snapshotMaxDate));
|
|
12071
|
+
}
|
|
12072
|
+
const aiSessionRows = db.select({
|
|
12073
|
+
trafficClass: gaAiReferrals.trafficClass,
|
|
12074
|
+
channelGroup: gaAiReferrals.channelGroup,
|
|
12075
|
+
sessions: sql7`COALESCE(SUM(${gaAiReferrals.sessions}), 0)`
|
|
12076
|
+
}).from(gaAiReferrals).where(and12(...aiConditions)).groupBy(gaAiReferrals.trafficClass, gaAiReferrals.channelGroup).all();
|
|
12077
|
+
let paidAiSessions = 0;
|
|
12078
|
+
let organicAiSessions = 0;
|
|
12079
|
+
let aiOrganicOverlap = 0;
|
|
12080
|
+
let aiDirectOverlap = 0;
|
|
12081
|
+
for (const row of aiSessionRows) {
|
|
12082
|
+
const sessions = Number(row.sessions ?? 0);
|
|
12083
|
+
if (isPaidAiTrafficClass(row.trafficClass)) paidAiSessions += sessions;
|
|
12084
|
+
else organicAiSessions += sessions;
|
|
12085
|
+
if (row.channelGroup === "Organic Search") aiOrganicOverlap += sessions;
|
|
12086
|
+
if (row.channelGroup === "Direct") aiDirectOverlap += sessions;
|
|
12087
|
+
}
|
|
11980
12088
|
const channelBreakdown = [];
|
|
11981
12089
|
if (totalSessions > 0) {
|
|
11982
|
-
const organic = totalOrganicSessions;
|
|
11983
|
-
const direct = directSessions;
|
|
11984
|
-
const other = Math.max(totalSessions - organic - direct, 0);
|
|
12090
|
+
const organic = Math.max(0, totalOrganicSessions - Math.min(totalOrganicSessions, aiOrganicOverlap));
|
|
12091
|
+
const direct = Math.max(0, directSessions - Math.min(directSessions, aiDirectOverlap));
|
|
12092
|
+
const other = Math.max(totalSessions - organic - direct - paidAiSessions - organicAiSessions, 0);
|
|
11985
12093
|
const buckets = [
|
|
11986
12094
|
{ channel: "Organic Search", sessions: organic },
|
|
11987
12095
|
{ channel: "Direct", sessions: direct },
|
|
12096
|
+
{ channel: "Paid AI", sessions: paidAiSessions },
|
|
12097
|
+
{ channel: "Organic AI referrals", sessions: organicAiSessions },
|
|
11988
12098
|
{ channel: "Other", sessions: other }
|
|
11989
12099
|
];
|
|
11990
12100
|
for (const b of buckets) {
|
|
@@ -12071,15 +12181,34 @@ function buildAiReferrals(db, projectId) {
|
|
|
12071
12181
|
);
|
|
12072
12182
|
let total = 0;
|
|
12073
12183
|
let totalUsers = 0;
|
|
12184
|
+
let paidSessions = 0;
|
|
12185
|
+
let paidUsers = 0;
|
|
12186
|
+
let organicSessions = 0;
|
|
12187
|
+
let organicUsers = 0;
|
|
12074
12188
|
const sourceAgg = /* @__PURE__ */ new Map();
|
|
12075
12189
|
const trendAgg = /* @__PURE__ */ new Map();
|
|
12076
12190
|
const pageAgg = /* @__PURE__ */ new Map();
|
|
12077
12191
|
for (const r of dedupedRows) {
|
|
12078
12192
|
total += r.sessions;
|
|
12079
12193
|
totalUsers += r.users;
|
|
12080
|
-
const
|
|
12194
|
+
const paid = isPaidAiTrafficClass(r.trafficClass);
|
|
12195
|
+
if (paid) {
|
|
12196
|
+
paidSessions += r.sessions;
|
|
12197
|
+
paidUsers += r.users;
|
|
12198
|
+
} else {
|
|
12199
|
+
organicSessions += r.sessions;
|
|
12200
|
+
organicUsers += r.users;
|
|
12201
|
+
}
|
|
12202
|
+
const s = sourceAgg.get(r.source) ?? {
|
|
12203
|
+
sessions: 0,
|
|
12204
|
+
users: 0,
|
|
12205
|
+
paidSessions: 0,
|
|
12206
|
+
organicSessions: 0
|
|
12207
|
+
};
|
|
12081
12208
|
s.sessions += r.sessions;
|
|
12082
12209
|
s.users += r.users;
|
|
12210
|
+
if (paid) s.paidSessions += r.sessions;
|
|
12211
|
+
else s.organicSessions += r.sessions;
|
|
12083
12212
|
sourceAgg.set(r.source, s);
|
|
12084
12213
|
trendAgg.set(r.date, (trendAgg.get(r.date) ?? 0) + r.sessions);
|
|
12085
12214
|
const page = r.landingPageNormalized ?? r.landingPage;
|
|
@@ -12092,11 +12221,23 @@ function buildAiReferrals(db, projectId) {
|
|
|
12092
12221
|
source,
|
|
12093
12222
|
sessions: data.sessions,
|
|
12094
12223
|
users: data.users,
|
|
12224
|
+
paidSessions: data.paidSessions,
|
|
12225
|
+
organicSessions: data.organicSessions,
|
|
12095
12226
|
sharePct: total > 0 ? Math.round(data.sessions / total * 100) : 0
|
|
12096
12227
|
})).sort((a, b) => b.sessions - a.sessions);
|
|
12097
12228
|
const trend = [...trendAgg.entries()].map(([date, sessions]) => ({ date, sessions })).sort((a, b) => a.date.localeCompare(b.date));
|
|
12098
12229
|
const topLandingPages = [...pageAgg.entries()].map(([page, data]) => ({ page, sessions: data.sessions, users: data.users })).sort((a, b) => b.sessions - a.sessions).slice(0, TOP_AI_REFERRAL_PAGES_LIMIT);
|
|
12099
|
-
return {
|
|
12230
|
+
return {
|
|
12231
|
+
totalSessions: total,
|
|
12232
|
+
totalUsers,
|
|
12233
|
+
paidSessions,
|
|
12234
|
+
paidUsers,
|
|
12235
|
+
organicSessions,
|
|
12236
|
+
organicUsers,
|
|
12237
|
+
bySource,
|
|
12238
|
+
trend,
|
|
12239
|
+
topLandingPages
|
|
12240
|
+
};
|
|
12100
12241
|
}
|
|
12101
12242
|
function nonSubresourceReferralPathCondition() {
|
|
12102
12243
|
return sql7`
|
|
@@ -12123,7 +12264,7 @@ function nonSubresourceReferralPathCondition() {
|
|
|
12123
12264
|
}
|
|
12124
12265
|
function buildServerActivity(db, projectId, windowDays) {
|
|
12125
12266
|
const sourceRows = db.select({ id: trafficSources.id }).from(trafficSources).where(
|
|
12126
|
-
|
|
12267
|
+
and12(
|
|
12127
12268
|
eq15(trafficSources.projectId, projectId),
|
|
12128
12269
|
ne2(trafficSources.status, TrafficSourceStatuses.archived)
|
|
12129
12270
|
)
|
|
@@ -12139,7 +12280,7 @@ function buildServerActivity(db, projectId, windowDays) {
|
|
|
12139
12280
|
const trendStart = new Date(trendStartMs).toISOString();
|
|
12140
12281
|
const sumVerifiedCrawlers = (windowStartIso, windowEndIso, exclusiveEnd = false) => Number(
|
|
12141
12282
|
db.select({ total: sql7`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
|
|
12142
|
-
|
|
12283
|
+
and12(
|
|
12143
12284
|
eq15(crawlerEventsHourly.projectId, projectId),
|
|
12144
12285
|
eq15(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
|
|
12145
12286
|
gte2(crawlerEventsHourly.tsHour, windowStartIso),
|
|
@@ -12149,7 +12290,7 @@ function buildServerActivity(db, projectId, windowDays) {
|
|
|
12149
12290
|
);
|
|
12150
12291
|
const sumUnverifiedCrawlers = (windowStartIso, windowEndIso, exclusiveEnd = false) => Number(
|
|
12151
12292
|
db.select({ total: sql7`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
|
|
12152
|
-
|
|
12293
|
+
and12(
|
|
12153
12294
|
eq15(crawlerEventsHourly.projectId, projectId),
|
|
12154
12295
|
ne2(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
|
|
12155
12296
|
gte2(crawlerEventsHourly.tsHour, windowStartIso),
|
|
@@ -12159,7 +12300,7 @@ function buildServerActivity(db, projectId, windowDays) {
|
|
|
12159
12300
|
);
|
|
12160
12301
|
const sumReferrals = (windowStartIso, windowEndIso, exclusiveEnd = false) => Number(
|
|
12161
12302
|
db.select({ total: sql7`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
|
|
12162
|
-
|
|
12303
|
+
and12(
|
|
12163
12304
|
eq15(aiReferralEventsHourly.projectId, projectId),
|
|
12164
12305
|
nonSubresourceReferralPathCondition(),
|
|
12165
12306
|
gte2(aiReferralEventsHourly.tsHour, windowStartIso),
|
|
@@ -12169,7 +12310,7 @@ function buildServerActivity(db, projectId, windowDays) {
|
|
|
12169
12310
|
);
|
|
12170
12311
|
const sumUserFetches = (windowStartIso, windowEndIso, exclusiveEnd = false) => Number(
|
|
12171
12312
|
db.select({ total: sql7`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
|
|
12172
|
-
|
|
12313
|
+
and12(
|
|
12173
12314
|
eq15(aiUserFetchEventsHourly.projectId, projectId),
|
|
12174
12315
|
gte2(aiUserFetchEventsHourly.tsHour, windowStartIso),
|
|
12175
12316
|
exclusiveEnd ? lt(aiUserFetchEventsHourly.tsHour, windowEndIso) : lte(aiUserFetchEventsHourly.tsHour, windowEndIso)
|
|
@@ -12189,7 +12330,7 @@ function buildServerActivity(db, projectId, windowDays) {
|
|
|
12189
12330
|
verificationStatus: crawlerEventsHourly.verificationStatus,
|
|
12190
12331
|
hits: sql7`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
|
|
12191
12332
|
}).from(crawlerEventsHourly).where(
|
|
12192
|
-
|
|
12333
|
+
and12(
|
|
12193
12334
|
eq15(crawlerEventsHourly.projectId, projectId),
|
|
12194
12335
|
gte2(crawlerEventsHourly.tsHour, headlineStart),
|
|
12195
12336
|
lte(crawlerEventsHourly.tsHour, headlineEnd)
|
|
@@ -12199,7 +12340,7 @@ function buildServerActivity(db, projectId, windowDays) {
|
|
|
12199
12340
|
operator: crawlerEventsHourly.operator,
|
|
12200
12341
|
hits: sql7`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
|
|
12201
12342
|
}).from(crawlerEventsHourly).where(
|
|
12202
|
-
|
|
12343
|
+
and12(
|
|
12203
12344
|
eq15(crawlerEventsHourly.projectId, projectId),
|
|
12204
12345
|
eq15(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
|
|
12205
12346
|
gte2(crawlerEventsHourly.tsHour, priorStart),
|
|
@@ -12210,7 +12351,7 @@ function buildServerActivity(db, projectId, windowDays) {
|
|
|
12210
12351
|
operator: aiReferralEventsHourly.operator,
|
|
12211
12352
|
hits: sql7`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`
|
|
12212
12353
|
}).from(aiReferralEventsHourly).where(
|
|
12213
|
-
|
|
12354
|
+
and12(
|
|
12214
12355
|
eq15(aiReferralEventsHourly.projectId, projectId),
|
|
12215
12356
|
nonSubresourceReferralPathCondition(),
|
|
12216
12357
|
gte2(aiReferralEventsHourly.tsHour, headlineStart),
|
|
@@ -12221,7 +12362,7 @@ function buildServerActivity(db, projectId, windowDays) {
|
|
|
12221
12362
|
operator: aiUserFetchEventsHourly.operator,
|
|
12222
12363
|
hits: sql7`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)`
|
|
12223
12364
|
}).from(aiUserFetchEventsHourly).where(
|
|
12224
|
-
|
|
12365
|
+
and12(
|
|
12225
12366
|
eq15(aiUserFetchEventsHourly.projectId, projectId),
|
|
12226
12367
|
gte2(aiUserFetchEventsHourly.tsHour, headlineStart),
|
|
12227
12368
|
lte(aiUserFetchEventsHourly.tsHour, headlineEnd)
|
|
@@ -12265,7 +12406,7 @@ function buildServerActivity(db, projectId, windowDays) {
|
|
|
12265
12406
|
hits: sql7`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`,
|
|
12266
12407
|
operators: sql7`COUNT(DISTINCT ${crawlerEventsHourly.operator})`
|
|
12267
12408
|
}).from(crawlerEventsHourly).where(
|
|
12268
|
-
|
|
12409
|
+
and12(
|
|
12269
12410
|
eq15(crawlerEventsHourly.projectId, projectId),
|
|
12270
12411
|
eq15(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
|
|
12271
12412
|
gte2(crawlerEventsHourly.tsHour, headlineStart),
|
|
@@ -12282,7 +12423,7 @@ function buildServerActivity(db, projectId, windowDays) {
|
|
|
12282
12423
|
arrivals: sql7`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`,
|
|
12283
12424
|
landingPaths: sql7`COUNT(DISTINCT ${aiReferralEventsHourly.landingPathNormalized})`
|
|
12284
12425
|
}).from(aiReferralEventsHourly).where(
|
|
12285
|
-
|
|
12426
|
+
and12(
|
|
12286
12427
|
eq15(aiReferralEventsHourly.projectId, projectId),
|
|
12287
12428
|
nonSubresourceReferralPathCondition(),
|
|
12288
12429
|
gte2(aiReferralEventsHourly.tsHour, headlineStart),
|
|
@@ -12299,7 +12440,7 @@ function buildServerActivity(db, projectId, windowDays) {
|
|
|
12299
12440
|
arrivals: sql7`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`,
|
|
12300
12441
|
products: sql7`COUNT(DISTINCT ${aiReferralEventsHourly.product})`
|
|
12301
12442
|
}).from(aiReferralEventsHourly).where(
|
|
12302
|
-
|
|
12443
|
+
and12(
|
|
12303
12444
|
eq15(aiReferralEventsHourly.projectId, projectId),
|
|
12304
12445
|
nonSubresourceReferralPathCondition(),
|
|
12305
12446
|
gte2(aiReferralEventsHourly.tsHour, headlineStart),
|
|
@@ -12315,7 +12456,7 @@ function buildServerActivity(db, projectId, windowDays) {
|
|
|
12315
12456
|
date: sql7`SUBSTR(${crawlerEventsHourly.tsHour}, 1, 10)`,
|
|
12316
12457
|
hits: sql7`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
|
|
12317
12458
|
}).from(crawlerEventsHourly).where(
|
|
12318
|
-
|
|
12459
|
+
and12(
|
|
12319
12460
|
eq15(crawlerEventsHourly.projectId, projectId),
|
|
12320
12461
|
eq15(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
|
|
12321
12462
|
gte2(crawlerEventsHourly.tsHour, trendStart),
|
|
@@ -12326,7 +12467,7 @@ function buildServerActivity(db, projectId, windowDays) {
|
|
|
12326
12467
|
date: sql7`SUBSTR(${aiReferralEventsHourly.tsHour}, 1, 10)`,
|
|
12327
12468
|
hits: sql7`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`
|
|
12328
12469
|
}).from(aiReferralEventsHourly).where(
|
|
12329
|
-
|
|
12470
|
+
and12(
|
|
12330
12471
|
eq15(aiReferralEventsHourly.projectId, projectId),
|
|
12331
12472
|
nonSubresourceReferralPathCondition(),
|
|
12332
12473
|
gte2(aiReferralEventsHourly.tsHour, trendStart),
|
|
@@ -12337,7 +12478,7 @@ function buildServerActivity(db, projectId, windowDays) {
|
|
|
12337
12478
|
date: sql7`SUBSTR(${aiUserFetchEventsHourly.tsHour}, 1, 10)`,
|
|
12338
12479
|
hits: sql7`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)`
|
|
12339
12480
|
}).from(aiUserFetchEventsHourly).where(
|
|
12340
|
-
|
|
12481
|
+
and12(
|
|
12341
12482
|
eq15(aiUserFetchEventsHourly.projectId, projectId),
|
|
12342
12483
|
gte2(aiUserFetchEventsHourly.tsHour, trendStart),
|
|
12343
12484
|
lte(aiUserFetchEventsHourly.tsHour, headlineEnd)
|
|
@@ -12422,7 +12563,7 @@ function buildIndexingHealth(db, projectId) {
|
|
|
12422
12563
|
return null;
|
|
12423
12564
|
}
|
|
12424
12565
|
function buildCitationsTrend(db, projectId, queryLookup, locationFilter) {
|
|
12425
|
-
const visibilityRuns = db.select().from(runs).where(
|
|
12566
|
+
const visibilityRuns = db.select().from(runs).where(and12(eq15(runs.projectId, projectId), eq15(runs.kind, RunKinds["answer-visibility"]), notProbeRun())).all().filter((r) => locationFilter === void 0 || (r.location ?? null) === locationFilter);
|
|
12426
12567
|
const totalQueries = queryLookup.byId.size;
|
|
12427
12568
|
const points = [];
|
|
12428
12569
|
for (const run of visibilityRuns) {
|
|
@@ -12470,7 +12611,7 @@ function buildCitationsTrend(db, projectId, queryLookup, locationFilter) {
|
|
|
12470
12611
|
}
|
|
12471
12612
|
function buildInsightList(db, projectId, locationFilter) {
|
|
12472
12613
|
const recentRunIds = db.select({ id: runs.id, location: runs.location }).from(runs).where(
|
|
12473
|
-
|
|
12614
|
+
and12(
|
|
12474
12615
|
eq15(runs.projectId, projectId),
|
|
12475
12616
|
eq15(runs.kind, RunKinds["answer-visibility"]),
|
|
12476
12617
|
or3(eq15(runs.status, RunStatuses.completed), eq15(runs.status, RunStatuses.partial)),
|
|
@@ -12478,7 +12619,7 @@ function buildInsightList(db, projectId, locationFilter) {
|
|
|
12478
12619
|
)
|
|
12479
12620
|
).orderBy(desc7(runs.createdAt)).all().filter((r) => locationFilter === void 0 || (r.location ?? null) === locationFilter).slice(0, INSIGHT_LOOKBACK_RUNS).map((r) => r.id);
|
|
12480
12621
|
if (recentRunIds.length === 0) return [];
|
|
12481
|
-
const rows = db.select().from(insights).where(
|
|
12622
|
+
const rows = db.select().from(insights).where(and12(eq15(insights.projectId, projectId), inArray6(insights.runId, recentRunIds))).orderBy(desc7(insights.createdAt)).all();
|
|
12482
12623
|
const severityRank = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
12483
12624
|
const flat = rows.filter((r) => !r.dismissed).map((r) => {
|
|
12484
12625
|
const recommendation = r.recommendation;
|
|
@@ -13030,7 +13171,7 @@ function buildProjectReport(db, projectName, periodDays) {
|
|
|
13030
13171
|
const project = resolveProject(db, projectName);
|
|
13031
13172
|
const queryLookup = loadQueryLookup(db, project.id);
|
|
13032
13173
|
const comparisonWindowDays = reportComparisonWindowDays(periodDays);
|
|
13033
|
-
const allRuns = db.select().from(runs).where(
|
|
13174
|
+
const allRuns = db.select().from(runs).where(and12(eq15(runs.projectId, project.id), notProbeRun())).orderBy(desc7(runs.createdAt), desc7(runs.id)).all();
|
|
13034
13175
|
const visibilityRuns = allRuns.filter((r) => r.kind === RunKinds["answer-visibility"]);
|
|
13035
13176
|
const completedVisRunGroups = groupRunsByCreatedAt(
|
|
13036
13177
|
visibilityRuns.filter((r) => r.status === RunStatuses.completed || r.status === RunStatuses.partial)
|
|
@@ -13266,7 +13407,7 @@ async function reportRoutes(app) {
|
|
|
13266
13407
|
}
|
|
13267
13408
|
|
|
13268
13409
|
// ../api-routes/src/citations.ts
|
|
13269
|
-
import { and as
|
|
13410
|
+
import { and as and13, eq as eq16, inArray as inArray7 } from "drizzle-orm";
|
|
13270
13411
|
async function citationRoutes(app) {
|
|
13271
13412
|
app.get("/projects/:name/citations/visibility", async (request, reply) => {
|
|
13272
13413
|
const project = resolveProject(app.db, request.params.name);
|
|
@@ -13275,7 +13416,7 @@ async function citationRoutes(app) {
|
|
|
13275
13416
|
if (projectQueries.length === 0) {
|
|
13276
13417
|
return reply.send(emptyCitationVisibility("no-queries"));
|
|
13277
13418
|
}
|
|
13278
|
-
const projectRuns = app.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(
|
|
13419
|
+
const projectRuns = app.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(and13(eq16(runs.projectId, project.id), notProbeRun())).all();
|
|
13279
13420
|
if (projectRuns.length === 0) {
|
|
13280
13421
|
return reply.send(emptyCitationVisibility("no-runs-yet"));
|
|
13281
13422
|
}
|
|
@@ -13431,7 +13572,7 @@ function normalizeDomain2(domain) {
|
|
|
13431
13572
|
}
|
|
13432
13573
|
|
|
13433
13574
|
// ../api-routes/src/visibility-stats.ts
|
|
13434
|
-
import { and as
|
|
13575
|
+
import { and as and14, desc as desc8, eq as eq17, inArray as inArray8 } from "drizzle-orm";
|
|
13435
13576
|
function round42(value) {
|
|
13436
13577
|
return Math.round(value * 1e4) / 1e4;
|
|
13437
13578
|
}
|
|
@@ -13583,7 +13724,7 @@ async function visibilityStatsRoutes(app) {
|
|
|
13583
13724
|
lastRuns = n;
|
|
13584
13725
|
}
|
|
13585
13726
|
const projectQueries = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq17(queries.projectId, project.id)).all();
|
|
13586
|
-
let projectRuns = app.db.select({ id: runs.id, createdAt: runs.createdAt, status: runs.status }).from(runs).where(
|
|
13727
|
+
let projectRuns = app.db.select({ id: runs.id, createdAt: runs.createdAt, status: runs.status }).from(runs).where(and14(eq17(runs.projectId, project.id), eq17(runs.kind, RunKinds["answer-visibility"]), notProbeRun())).orderBy(desc8(runs.createdAt)).all().filter((r) => r.status === RunStatuses.completed || r.status === RunStatuses.partial);
|
|
13587
13728
|
if (sinceMs !== null) projectRuns = projectRuns.filter((r) => Date.parse(r.createdAt) >= sinceMs);
|
|
13588
13729
|
if (untilMs !== null) projectRuns = projectRuns.filter((r) => Date.parse(r.createdAt) <= untilMs);
|
|
13589
13730
|
if (lastRuns !== null) projectRuns = projectRuns.slice(0, lastRuns);
|
|
@@ -13653,7 +13794,7 @@ async function visibilityStatsRoutes(app) {
|
|
|
13653
13794
|
}
|
|
13654
13795
|
|
|
13655
13796
|
// ../api-routes/src/composites.ts
|
|
13656
|
-
import { eq as eq18, and as
|
|
13797
|
+
import { eq as eq18, and as and15, desc as desc9, sql as sql8, like as like2, or as or4, inArray as inArray9 } from "drizzle-orm";
|
|
13657
13798
|
var TOP_INSIGHT_LIMIT = 5;
|
|
13658
13799
|
var SEARCH_HIT_HARD_LIMIT = 50;
|
|
13659
13800
|
var SEARCH_SNIPPET_RADIUS = 80;
|
|
@@ -13671,7 +13812,7 @@ async function compositeRoutes(app) {
|
|
|
13671
13812
|
const project = resolveProject(app.db, request.params.name);
|
|
13672
13813
|
const filterLocation = (request.query.location ?? "").trim() || null;
|
|
13673
13814
|
const sinceIso = parseSinceFilter(request.query.since);
|
|
13674
|
-
const allRunsRaw = app.db.select().from(runs).where(
|
|
13815
|
+
const allRunsRaw = app.db.select().from(runs).where(and15(eq18(runs.projectId, project.id), notProbeRun())).orderBy(desc9(runs.createdAt), desc9(runs.id)).all();
|
|
13675
13816
|
const allRuns = allRunsRaw.filter((r) => runMatchesFilters(r, filterLocation, sinceIso));
|
|
13676
13817
|
const totalRuns = allRuns.length;
|
|
13677
13818
|
const visibilityRuns = allRuns.filter((r) => r.kind === RunKinds["answer-visibility"]);
|
|
@@ -13827,7 +13968,7 @@ async function compositeRoutes(app) {
|
|
|
13827
13968
|
rawResponse: querySnapshots.rawResponse,
|
|
13828
13969
|
createdAt: querySnapshots.createdAt
|
|
13829
13970
|
}).from(querySnapshots).innerJoin(queries, eq18(querySnapshots.queryId, queries.id)).where(
|
|
13830
|
-
|
|
13971
|
+
and15(
|
|
13831
13972
|
eq18(queries.projectId, project.id),
|
|
13832
13973
|
or4(
|
|
13833
13974
|
sql8`${querySnapshots.answerText} LIKE ${pattern} ESCAPE '\\'`,
|
|
@@ -13838,7 +13979,7 @@ async function compositeRoutes(app) {
|
|
|
13838
13979
|
)
|
|
13839
13980
|
).orderBy(desc9(querySnapshots.createdAt)).limit(limit + 1).all());
|
|
13840
13981
|
const insightMatches = app.db.select().from(insights).where(
|
|
13841
|
-
|
|
13982
|
+
and15(
|
|
13842
13983
|
eq18(insights.projectId, project.id),
|
|
13843
13984
|
or4(
|
|
13844
13985
|
like2(insights.title, pattern),
|
|
@@ -14055,7 +14196,7 @@ function buildSuggestedQueriesFromGsc(app, projectId, trackedQueries) {
|
|
|
14055
14196
|
// NULLIF guards the degenerate impressions=0 case (SQLite returns NULL,
|
|
14056
14197
|
// which the JS coerces to 0 — caught by the impression floor anyway).
|
|
14057
14198
|
avgPosition: sql8`COALESCE(SUM(${gscSearchData.position} * ${gscSearchData.impressions}) * 1.0 / NULLIF(SUM(${gscSearchData.impressions}), 0), 0)`
|
|
14058
|
-
}).from(gscSearchData).where(
|
|
14199
|
+
}).from(gscSearchData).where(and15(
|
|
14059
14200
|
eq18(gscSearchData.projectId, projectId),
|
|
14060
14201
|
sql8`${gscSearchData.date} >= ${cutoff}`,
|
|
14061
14202
|
sql8`${gscSearchData.impressions} > 0`
|
|
@@ -14570,6 +14711,13 @@ var queryIdParameter = {
|
|
|
14570
14711
|
description: "Query ID.",
|
|
14571
14712
|
schema: stringSchema
|
|
14572
14713
|
};
|
|
14714
|
+
var competitorIdParameter = {
|
|
14715
|
+
name: "id",
|
|
14716
|
+
in: "path",
|
|
14717
|
+
required: true,
|
|
14718
|
+
description: "Competitor ID.",
|
|
14719
|
+
schema: stringSchema
|
|
14720
|
+
};
|
|
14573
14721
|
var notificationIdParameter = {
|
|
14574
14722
|
name: "id",
|
|
14575
14723
|
in: "path",
|
|
@@ -15258,6 +15406,17 @@ var routeCatalog = [
|
|
|
15258
15406
|
400: errorResponse("Invalid competitor delete request.")
|
|
15259
15407
|
}
|
|
15260
15408
|
},
|
|
15409
|
+
{
|
|
15410
|
+
method: "delete",
|
|
15411
|
+
path: "/api/v1/projects/{name}/competitors/{id}",
|
|
15412
|
+
summary: "Delete competitor",
|
|
15413
|
+
tags: ["competitors"],
|
|
15414
|
+
parameters: [nameParameter, competitorIdParameter],
|
|
15415
|
+
responses: {
|
|
15416
|
+
204: { description: "Competitor deleted." },
|
|
15417
|
+
404: errorResponse("Project or competitor not found.")
|
|
15418
|
+
}
|
|
15419
|
+
},
|
|
15261
15420
|
{
|
|
15262
15421
|
method: "post",
|
|
15263
15422
|
path: "/api/v1/projects/{name}/runs",
|
|
@@ -18979,7 +19138,7 @@ async function telemetryRoutes(app, opts) {
|
|
|
18979
19138
|
|
|
18980
19139
|
// ../api-routes/src/schedules.ts
|
|
18981
19140
|
import crypto13 from "crypto";
|
|
18982
|
-
import { and as
|
|
19141
|
+
import { and as and16, eq as eq20 } from "drizzle-orm";
|
|
18983
19142
|
function parseKindParam(raw) {
|
|
18984
19143
|
if (raw === void 0 || raw === null || raw === "") return SchedulableRunKinds["answer-visibility"];
|
|
18985
19144
|
const parsed = schedulableRunKindSchema.safeParse(raw);
|
|
@@ -19051,7 +19210,7 @@ async function scheduleRoutes(app, opts) {
|
|
|
19051
19210
|
}
|
|
19052
19211
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
19053
19212
|
const enabledBool = enabled !== false;
|
|
19054
|
-
const existing = app.db.select().from(schedules).where(
|
|
19213
|
+
const existing = app.db.select().from(schedules).where(and16(eq20(schedules.projectId, project.id), eq20(schedules.kind, kind))).get();
|
|
19055
19214
|
if (existing) {
|
|
19056
19215
|
app.db.update(schedules).set({
|
|
19057
19216
|
cronExpr,
|
|
@@ -19085,13 +19244,13 @@ async function scheduleRoutes(app, opts) {
|
|
|
19085
19244
|
diff: { kind, cronExpr, preset, timezone, providers, sourceId }
|
|
19086
19245
|
});
|
|
19087
19246
|
opts.onScheduleUpdated?.("upsert", project.id, kind);
|
|
19088
|
-
const schedule = app.db.select().from(schedules).where(
|
|
19247
|
+
const schedule = app.db.select().from(schedules).where(and16(eq20(schedules.projectId, project.id), eq20(schedules.kind, kind))).get();
|
|
19089
19248
|
return reply.status(existing ? 200 : 201).send(formatSchedule(schedule));
|
|
19090
19249
|
});
|
|
19091
19250
|
app.get("/projects/:name/schedule", async (request, reply) => {
|
|
19092
19251
|
const project = resolveProject(app.db, request.params.name);
|
|
19093
19252
|
const kind = parseKindParam(request.query?.kind);
|
|
19094
|
-
const schedule = app.db.select().from(schedules).where(
|
|
19253
|
+
const schedule = app.db.select().from(schedules).where(and16(eq20(schedules.projectId, project.id), eq20(schedules.kind, kind))).get();
|
|
19095
19254
|
if (!schedule) {
|
|
19096
19255
|
throw notFound("Schedule", `${request.params.name} (kind=${kind})`);
|
|
19097
19256
|
}
|
|
@@ -19100,7 +19259,7 @@ async function scheduleRoutes(app, opts) {
|
|
|
19100
19259
|
app.delete("/projects/:name/schedule", async (request, reply) => {
|
|
19101
19260
|
const project = resolveProject(app.db, request.params.name);
|
|
19102
19261
|
const kind = parseKindParam(request.query?.kind);
|
|
19103
|
-
const schedule = app.db.select().from(schedules).where(
|
|
19262
|
+
const schedule = app.db.select().from(schedules).where(and16(eq20(schedules.projectId, project.id), eq20(schedules.kind, kind))).get();
|
|
19104
19263
|
if (!schedule) {
|
|
19105
19264
|
throw notFound("Schedule", `${request.params.name} (kind=${kind})`);
|
|
19106
19265
|
}
|
|
@@ -19257,7 +19416,7 @@ function formatNotification(row) {
|
|
|
19257
19416
|
|
|
19258
19417
|
// ../api-routes/src/google.ts
|
|
19259
19418
|
import crypto18 from "crypto";
|
|
19260
|
-
import { eq as eq22, and as
|
|
19419
|
+
import { eq as eq22, and as and17, desc as desc11, sql as sql9, inArray as inArray10 } from "drizzle-orm";
|
|
19261
19420
|
|
|
19262
19421
|
// ../api-routes/src/gbp-summary.ts
|
|
19263
19422
|
function computeMetricTotals(rows) {
|
|
@@ -20383,16 +20542,23 @@ async function fetchAiReferrals(accessToken, propertyId, days) {
|
|
|
20383
20542
|
};
|
|
20384
20543
|
const response = await runReport(accessToken, propertyId, request);
|
|
20385
20544
|
if (!response) break;
|
|
20386
|
-
const pageRows = (response.rows ?? []).map((row) =>
|
|
20387
|
-
|
|
20388
|
-
|
|
20389
|
-
|
|
20390
|
-
|
|
20391
|
-
|
|
20392
|
-
|
|
20393
|
-
|
|
20394
|
-
|
|
20395
|
-
|
|
20545
|
+
const pageRows = (response.rows ?? []).map((row) => {
|
|
20546
|
+
const source = row.dimensionValues[1].value;
|
|
20547
|
+
const medium = row.dimensionValues[2].value;
|
|
20548
|
+
const channelGroup = row.dimensionValues[3]?.value ?? "(not set)";
|
|
20549
|
+
const landingPage = row.dimensionValues[4]?.value ?? "(not set)";
|
|
20550
|
+
return {
|
|
20551
|
+
date: row.dimensionValues[0].value,
|
|
20552
|
+
source,
|
|
20553
|
+
medium,
|
|
20554
|
+
trafficClass: classifyGa4AiReferralTrafficClass({ source, medium, channelGroup, landingPage }),
|
|
20555
|
+
channelGroup,
|
|
20556
|
+
landingPage,
|
|
20557
|
+
sessions: parseInt(row.metricValues[0].value, 10) || 0,
|
|
20558
|
+
users: parseInt(row.metricValues[1].value, 10) || 0,
|
|
20559
|
+
sourceDimension: dimLabel
|
|
20560
|
+
};
|
|
20561
|
+
});
|
|
20396
20562
|
rows.push(...pageRows);
|
|
20397
20563
|
const totalRows = response.rowCount ?? 0;
|
|
20398
20564
|
offset += pageRows.length;
|
|
@@ -21237,7 +21403,7 @@ async function googleRoutes(app, opts) {
|
|
|
21237
21403
|
if (page) conditions.push(sql9`${gscSearchData.page} LIKE ${"%" + escapeLikePattern(page) + "%"} ESCAPE '\\'`);
|
|
21238
21404
|
const limitVal = Math.max(parseInt(limit ?? "500", 10) || 0, 1);
|
|
21239
21405
|
const offsetVal = Math.max(parseInt(offset ?? "0", 10) || 0, 0);
|
|
21240
|
-
const rows = app.db.select().from(gscSearchData).where(
|
|
21406
|
+
const rows = app.db.select().from(gscSearchData).where(and17(...conditions)).orderBy(desc11(gscSearchData.date)).limit(limitVal).offset(offsetVal).all();
|
|
21241
21407
|
return rows.map((r) => ({
|
|
21242
21408
|
date: r.date,
|
|
21243
21409
|
query: r.query,
|
|
@@ -21265,7 +21431,7 @@ async function googleRoutes(app, opts) {
|
|
|
21265
21431
|
date: gscSearchData.date,
|
|
21266
21432
|
clicks: sql9`COALESCE(SUM(${gscSearchData.clicks}), 0)`,
|
|
21267
21433
|
impressions: sql9`COALESCE(SUM(${gscSearchData.impressions}), 0)`
|
|
21268
|
-
}).from(gscSearchData).where(
|
|
21434
|
+
}).from(gscSearchData).where(and17(...conditions)).groupBy(gscSearchData.date).orderBy(gscSearchData.date).all();
|
|
21269
21435
|
const daily = mergeGscDailyTotalsWithFallback(
|
|
21270
21436
|
dailyTotals,
|
|
21271
21437
|
dimensionedRows.map((r) => ({
|
|
@@ -21358,7 +21524,7 @@ async function googleRoutes(app, opts) {
|
|
|
21358
21524
|
const { url, limit } = request.query;
|
|
21359
21525
|
const conditions = [eq22(gscUrlInspections.projectId, project.id)];
|
|
21360
21526
|
if (url) conditions.push(eq22(gscUrlInspections.url, url));
|
|
21361
|
-
const rows = app.db.select().from(gscUrlInspections).where(
|
|
21527
|
+
const rows = app.db.select().from(gscUrlInspections).where(and17(...conditions)).orderBy(desc11(gscUrlInspections.inspectedAt)).limit(parseInt(limit ?? "100", 10)).all();
|
|
21362
21528
|
return rows.map((r) => ({
|
|
21363
21529
|
id: r.id,
|
|
21364
21530
|
url: r.url,
|
|
@@ -21853,7 +22019,7 @@ async function googleRoutes(app, opts) {
|
|
|
21853
22019
|
app.db.transaction((tx) => {
|
|
21854
22020
|
if (switching) clearGbpProjectData(tx, project.id);
|
|
21855
22021
|
for (const remote of remoteLocations) {
|
|
21856
|
-
const existing = tx.select().from(gbpLocations).where(
|
|
22022
|
+
const existing = tx.select().from(gbpLocations).where(and17(eq22(gbpLocations.projectId, project.id), eq22(gbpLocations.locationName, remote.name))).get();
|
|
21857
22023
|
const profile = buildLocationProfileFields(remote);
|
|
21858
22024
|
if (existing) {
|
|
21859
22025
|
tx.update(gbpLocations).set({
|
|
@@ -21950,7 +22116,7 @@ async function googleRoutes(app, opts) {
|
|
|
21950
22116
|
throw validationError(parsed.error.issues[0]?.message ?? "Invalid selection request");
|
|
21951
22117
|
}
|
|
21952
22118
|
const { selected } = parsed.data;
|
|
21953
|
-
const existing = app.db.select().from(gbpLocations).where(
|
|
22119
|
+
const existing = app.db.select().from(gbpLocations).where(and17(eq22(gbpLocations.projectId, project.id), eq22(gbpLocations.locationName, locationName))).get();
|
|
21954
22120
|
if (!existing) throw notFound("GBP location", locationName);
|
|
21955
22121
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
21956
22122
|
app.db.transaction((tx) => {
|
|
@@ -22010,7 +22176,7 @@ async function googleRoutes(app, opts) {
|
|
|
22010
22176
|
const conditions = [eq22(gbpDailyMetrics.projectId, project.id)];
|
|
22011
22177
|
if (request.query.locationName) conditions.push(eq22(gbpDailyMetrics.locationName, request.query.locationName));
|
|
22012
22178
|
if (request.query.metric) conditions.push(eq22(gbpDailyMetrics.metric, request.query.metric));
|
|
22013
|
-
const rows = app.db.select().from(gbpDailyMetrics).where(
|
|
22179
|
+
const rows = app.db.select().from(gbpDailyMetrics).where(and17(...conditions)).orderBy(desc11(gbpDailyMetrics.date)).all();
|
|
22014
22180
|
return {
|
|
22015
22181
|
metrics: rows.map((r) => ({ locationName: r.locationName, date: r.date, metric: r.metric, value: r.value })),
|
|
22016
22182
|
total: rows.length
|
|
@@ -22020,7 +22186,7 @@ async function googleRoutes(app, opts) {
|
|
|
22020
22186
|
const project = resolveProject(app.db, request.params.name);
|
|
22021
22187
|
const conditions = [eq22(gbpKeywordImpressions.projectId, project.id)];
|
|
22022
22188
|
if (request.query.locationName) conditions.push(eq22(gbpKeywordImpressions.locationName, request.query.locationName));
|
|
22023
|
-
const rows = app.db.select().from(gbpKeywordImpressions).where(
|
|
22189
|
+
const rows = app.db.select().from(gbpKeywordImpressions).where(and17(...conditions)).all();
|
|
22024
22190
|
rows.sort((a, b) => (b.valueCount ?? -1) - (a.valueCount ?? -1));
|
|
22025
22191
|
const thresholded = rows.filter((r) => r.valueThreshold !== null).length;
|
|
22026
22192
|
return {
|
|
@@ -22040,7 +22206,7 @@ async function googleRoutes(app, opts) {
|
|
|
22040
22206
|
const project = resolveProject(app.db, request.params.name);
|
|
22041
22207
|
const conditions = [eq22(gbpPlaceActions.projectId, project.id)];
|
|
22042
22208
|
if (request.query.locationName) conditions.push(eq22(gbpPlaceActions.locationName, request.query.locationName));
|
|
22043
|
-
const rows = app.db.select().from(gbpPlaceActions).where(
|
|
22209
|
+
const rows = app.db.select().from(gbpPlaceActions).where(and17(...conditions)).all();
|
|
22044
22210
|
return {
|
|
22045
22211
|
placeActions: rows.map((r) => ({
|
|
22046
22212
|
locationName: r.locationName,
|
|
@@ -22057,7 +22223,7 @@ async function googleRoutes(app, opts) {
|
|
|
22057
22223
|
const project = resolveProject(app.db, request.params.name);
|
|
22058
22224
|
const conditions = [eq22(gbpLodgingSnapshots.projectId, project.id)];
|
|
22059
22225
|
if (request.query.locationName) conditions.push(eq22(gbpLodgingSnapshots.locationName, request.query.locationName));
|
|
22060
|
-
const rows = app.db.select().from(gbpLodgingSnapshots).where(
|
|
22226
|
+
const rows = app.db.select().from(gbpLodgingSnapshots).where(and17(...conditions)).orderBy(desc11(gbpLodgingSnapshots.syncedAt)).all();
|
|
22061
22227
|
const latestByLocation = /* @__PURE__ */ new Map();
|
|
22062
22228
|
for (const row of rows) {
|
|
22063
22229
|
if (!latestByLocation.has(row.locationName)) latestByLocation.set(row.locationName, row);
|
|
@@ -22074,7 +22240,7 @@ async function googleRoutes(app, opts) {
|
|
|
22074
22240
|
const project = resolveProject(app.db, request.params.name);
|
|
22075
22241
|
const conditions = [eq22(gbpAttributesSnapshots.projectId, project.id)];
|
|
22076
22242
|
if (request.query.locationName) conditions.push(eq22(gbpAttributesSnapshots.locationName, request.query.locationName));
|
|
22077
|
-
const rows = app.db.select().from(gbpAttributesSnapshots).where(
|
|
22243
|
+
const rows = app.db.select().from(gbpAttributesSnapshots).where(and17(...conditions)).orderBy(desc11(gbpAttributesSnapshots.syncedAt)).all();
|
|
22078
22244
|
const latestByLocation = /* @__PURE__ */ new Map();
|
|
22079
22245
|
for (const row of rows) {
|
|
22080
22246
|
if (!latestByLocation.has(row.locationName)) latestByLocation.set(row.locationName, row);
|
|
@@ -22094,7 +22260,7 @@ async function googleRoutes(app, opts) {
|
|
|
22094
22260
|
const project = resolveProject(app.db, request.params.name);
|
|
22095
22261
|
const conditions = [eq22(gbpPlaceDetails.projectId, project.id)];
|
|
22096
22262
|
if (request.query.locationName) conditions.push(eq22(gbpPlaceDetails.locationName, request.query.locationName));
|
|
22097
|
-
const rows = app.db.select().from(gbpPlaceDetails).where(
|
|
22263
|
+
const rows = app.db.select().from(gbpPlaceDetails).where(and17(...conditions)).orderBy(desc11(gbpPlaceDetails.syncedAt)).all();
|
|
22098
22264
|
const latestByLocation = /* @__PURE__ */ new Map();
|
|
22099
22265
|
for (const row of rows) {
|
|
22100
22266
|
if (!latestByLocation.has(row.locationName)) latestByLocation.set(row.locationName, row);
|
|
@@ -22113,7 +22279,7 @@ async function googleRoutes(app, opts) {
|
|
|
22113
22279
|
app.get("/projects/:name/gbp/summary", async (request) => {
|
|
22114
22280
|
const project = resolveProject(app.db, request.params.name);
|
|
22115
22281
|
const locationName = request.query.locationName ?? null;
|
|
22116
|
-
const locationNames = locationName ? [locationName] : app.db.select({ n: gbpLocations.locationName }).from(gbpLocations).where(
|
|
22282
|
+
const locationNames = locationName ? [locationName] : app.db.select({ n: gbpLocations.locationName }).from(gbpLocations).where(and17(eq22(gbpLocations.projectId, project.id), eq22(gbpLocations.selected, true))).all().map((r) => r.n);
|
|
22117
22283
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
22118
22284
|
if (locationNames.length === 0) {
|
|
22119
22285
|
return buildGbpSummary({
|
|
@@ -22127,10 +22293,10 @@ async function googleRoutes(app, opts) {
|
|
|
22127
22293
|
locationProfiles: []
|
|
22128
22294
|
});
|
|
22129
22295
|
}
|
|
22130
|
-
const metricRows = app.db.select().from(gbpDailyMetrics).where(
|
|
22131
|
-
const keywordRows = app.db.select().from(gbpKeywordImpressions).where(
|
|
22132
|
-
const placeActionRows = app.db.select().from(gbpPlaceActions).where(
|
|
22133
|
-
const lodgingRows = app.db.select().from(gbpLodgingSnapshots).where(
|
|
22296
|
+
const metricRows = app.db.select().from(gbpDailyMetrics).where(and17(eq22(gbpDailyMetrics.projectId, project.id), inArray10(gbpDailyMetrics.locationName, locationNames))).all();
|
|
22297
|
+
const keywordRows = app.db.select().from(gbpKeywordImpressions).where(and17(eq22(gbpKeywordImpressions.projectId, project.id), inArray10(gbpKeywordImpressions.locationName, locationNames))).all();
|
|
22298
|
+
const placeActionRows = app.db.select().from(gbpPlaceActions).where(and17(eq22(gbpPlaceActions.projectId, project.id), inArray10(gbpPlaceActions.locationName, locationNames))).all();
|
|
22299
|
+
const lodgingRows = app.db.select().from(gbpLodgingSnapshots).where(and17(eq22(gbpLodgingSnapshots.projectId, project.id), inArray10(gbpLodgingSnapshots.locationName, locationNames))).orderBy(desc11(gbpLodgingSnapshots.syncedAt)).all();
|
|
22134
22300
|
const latestLodgingByLocation = /* @__PURE__ */ new Map();
|
|
22135
22301
|
for (const row of lodgingRows) {
|
|
22136
22302
|
if (!latestLodgingByLocation.has(row.locationName)) {
|
|
@@ -22144,7 +22310,7 @@ async function googleRoutes(app, opts) {
|
|
|
22144
22310
|
regularHours: gbpLocations.regularHours,
|
|
22145
22311
|
primaryPhone: gbpLocations.primaryPhone,
|
|
22146
22312
|
openStatus: gbpLocations.openStatus
|
|
22147
|
-
}).from(gbpLocations).where(
|
|
22313
|
+
}).from(gbpLocations).where(and17(eq22(gbpLocations.projectId, project.id), inArray10(gbpLocations.locationName, locationNames))).all();
|
|
22148
22314
|
return buildGbpSummary({
|
|
22149
22315
|
locationName,
|
|
22150
22316
|
locationCount: locationNames.length,
|
|
@@ -22160,7 +22326,7 @@ async function googleRoutes(app, opts) {
|
|
|
22160
22326
|
|
|
22161
22327
|
// ../api-routes/src/ads.ts
|
|
22162
22328
|
import crypto19 from "crypto";
|
|
22163
|
-
import { eq as eq23, and as
|
|
22329
|
+
import { eq as eq23, and as and18, asc as asc3, gte as gte3, lte as lte2, inArray as inArray11 } from "drizzle-orm";
|
|
22164
22330
|
function statusDto(row) {
|
|
22165
22331
|
if (!row) return { connected: false };
|
|
22166
22332
|
return {
|
|
@@ -22276,7 +22442,7 @@ async function adsRoutes(app, opts) {
|
|
|
22276
22442
|
if (!row) {
|
|
22277
22443
|
throw validationError('No ads connection for this project. Run "canonry ads connect" first.');
|
|
22278
22444
|
}
|
|
22279
|
-
const inFlight = app.db.select({ id: runs.id, status: runs.status }).from(runs).where(
|
|
22445
|
+
const inFlight = app.db.select({ id: runs.id, status: runs.status }).from(runs).where(and18(
|
|
22280
22446
|
eq23(runs.projectId, project.id),
|
|
22281
22447
|
eq23(runs.kind, RunKinds["ads-sync"]),
|
|
22282
22448
|
inArray11(runs.status, [RunStatuses.queued, RunStatuses.running])
|
|
@@ -22361,7 +22527,7 @@ async function adsRoutes(app, opts) {
|
|
|
22361
22527
|
if (entityId) conditions.push(eq23(adsInsightsDaily.entityId, entityId));
|
|
22362
22528
|
if (from) conditions.push(gte3(adsInsightsDaily.date, from));
|
|
22363
22529
|
if (to) conditions.push(lte2(adsInsightsDaily.date, to));
|
|
22364
|
-
const rows = app.db.select().from(adsInsightsDaily).where(
|
|
22530
|
+
const rows = app.db.select().from(adsInsightsDaily).where(and18(...conditions)).orderBy(asc3(adsInsightsDaily.date)).all();
|
|
22365
22531
|
const dtoRows = rows.map((row) => ({
|
|
22366
22532
|
level: row.level,
|
|
22367
22533
|
entityId: row.entityId,
|
|
@@ -22383,7 +22549,7 @@ async function adsRoutes(app, opts) {
|
|
|
22383
22549
|
const campaignCount = app.db.select().from(adsCampaigns).where(eq23(adsCampaigns.projectId, project.id)).all().length;
|
|
22384
22550
|
const adGroupCount = app.db.select().from(adsAdGroups).where(eq23(adsAdGroups.projectId, project.id)).all().length;
|
|
22385
22551
|
const adCount = app.db.select().from(adsAds).where(eq23(adsAds.projectId, project.id)).all().length;
|
|
22386
|
-
const campaignInsights = app.db.select().from(adsInsightsDaily).where(
|
|
22552
|
+
const campaignInsights = app.db.select().from(adsInsightsDaily).where(and18(
|
|
22387
22553
|
eq23(adsInsightsDaily.projectId, project.id),
|
|
22388
22554
|
eq23(adsInsightsDaily.level, "campaign")
|
|
22389
22555
|
)).all();
|
|
@@ -22425,7 +22591,7 @@ async function adsRoutes(app, opts) {
|
|
|
22425
22591
|
|
|
22426
22592
|
// ../api-routes/src/bing.ts
|
|
22427
22593
|
import crypto20 from "crypto";
|
|
22428
|
-
import { eq as eq24, and as
|
|
22594
|
+
import { eq as eq24, and as and19, desc as desc12 } from "drizzle-orm";
|
|
22429
22595
|
|
|
22430
22596
|
// ../integration-bing/src/constants.ts
|
|
22431
22597
|
var BING_WMT_API_BASE = "https://ssl.bing.com/webmaster/api.svc/json";
|
|
@@ -22900,7 +23066,7 @@ async function bingRoutes(app, opts) {
|
|
|
22900
23066
|
requireConnectionStore();
|
|
22901
23067
|
const project = resolveProject(app.db, request.params.name);
|
|
22902
23068
|
const { url, limit } = request.query;
|
|
22903
|
-
const whereClause = url ?
|
|
23069
|
+
const whereClause = url ? and19(eq24(bingUrlInspections.projectId, project.id), eq24(bingUrlInspections.url, url)) : eq24(bingUrlInspections.projectId, project.id);
|
|
22904
23070
|
const filtered = app.db.select().from(bingUrlInspections).where(whereClause).orderBy(desc12(bingUrlInspections.inspectedAt)).limit(Math.max(1, Math.min(parseInt(limit ?? "100", 10) || 100, 1e3))).all();
|
|
22905
23071
|
return filtered.map((r) => ({
|
|
22906
23072
|
id: r.id,
|
|
@@ -23132,7 +23298,7 @@ async function bingRoutes(app, opts) {
|
|
|
23132
23298
|
import fs from "fs";
|
|
23133
23299
|
import path from "path";
|
|
23134
23300
|
import os from "os";
|
|
23135
|
-
import { eq as eq25, and as
|
|
23301
|
+
import { eq as eq25, and as and20 } from "drizzle-orm";
|
|
23136
23302
|
function getScreenshotDir() {
|
|
23137
23303
|
return path.join(os.homedir(), ".canonry", "screenshots");
|
|
23138
23304
|
}
|
|
@@ -23207,7 +23373,7 @@ async function cdpRoutes(app, opts) {
|
|
|
23207
23373
|
async (request, reply) => {
|
|
23208
23374
|
const project = resolveProject(app.db, request.params.name);
|
|
23209
23375
|
const { runId } = request.params;
|
|
23210
|
-
const run = app.db.select().from(runs).where(
|
|
23376
|
+
const run = app.db.select().from(runs).where(and20(eq25(runs.id, runId), eq25(runs.projectId, project.id))).get();
|
|
23211
23377
|
if (!run) {
|
|
23212
23378
|
const err = notFound("Run", runId);
|
|
23213
23379
|
return reply.code(err.statusCode).send(err.toJSON());
|
|
@@ -23304,7 +23470,7 @@ async function cdpRoutes(app, opts) {
|
|
|
23304
23470
|
|
|
23305
23471
|
// ../api-routes/src/ga.ts
|
|
23306
23472
|
import crypto21 from "crypto";
|
|
23307
|
-
import { eq as eq26, desc as desc13, and as
|
|
23473
|
+
import { eq as eq26, desc as desc13, and as and21, sql as sql10 } from "drizzle-orm";
|
|
23308
23474
|
function gaLog(level, action, ctx) {
|
|
23309
23475
|
const entry = { ts: (/* @__PURE__ */ new Date()).toISOString(), level, module: "GA4Routes", action, ...ctx };
|
|
23310
23476
|
const stream = level === "error" ? process.stderr : process.stdout;
|
|
@@ -23360,6 +23526,76 @@ function pickWinningDimension(rows, tupleKey) {
|
|
|
23360
23526
|
}
|
|
23361
23527
|
return [...winners.values()].sort((a, b) => (b.sessions ?? 0) - (a.sessions ?? 0));
|
|
23362
23528
|
}
|
|
23529
|
+
function normalizeAiTrafficClass(value) {
|
|
23530
|
+
return value === GA4AiReferralTrafficClasses.paid ? GA4AiReferralTrafficClasses.paid : GA4AiReferralTrafficClasses.organic;
|
|
23531
|
+
}
|
|
23532
|
+
function emptyAiCounts() {
|
|
23533
|
+
return { sessions: 0, users: 0 };
|
|
23534
|
+
}
|
|
23535
|
+
function summarizeAiReferralCounts(rows) {
|
|
23536
|
+
const dedupeGroups = /* @__PURE__ */ new Map();
|
|
23537
|
+
const bySessionChannelGroup = /* @__PURE__ */ new Map();
|
|
23538
|
+
const paidDeduped = emptyAiCounts();
|
|
23539
|
+
const organicDeduped = emptyAiCounts();
|
|
23540
|
+
const paidBySession = emptyAiCounts();
|
|
23541
|
+
const organicBySession = emptyAiCounts();
|
|
23542
|
+
for (const row of rows) {
|
|
23543
|
+
const isPaid = normalizeAiTrafficClass(row.trafficClass) === GA4AiReferralTrafficClasses.paid;
|
|
23544
|
+
const sessions = row.sessions ?? 0;
|
|
23545
|
+
const users = row.users ?? 0;
|
|
23546
|
+
const key = `${row.date}\0${row.source}\0${row.medium}`;
|
|
23547
|
+
let byDimension = dedupeGroups.get(key);
|
|
23548
|
+
if (!byDimension) {
|
|
23549
|
+
byDimension = /* @__PURE__ */ new Map();
|
|
23550
|
+
dedupeGroups.set(key, byDimension);
|
|
23551
|
+
}
|
|
23552
|
+
let dim = byDimension.get(row.sourceDimension);
|
|
23553
|
+
if (!dim) {
|
|
23554
|
+
dim = { paidSessions: 0, organicSessions: 0, paidUsers: 0, organicUsers: 0 };
|
|
23555
|
+
byDimension.set(row.sourceDimension, dim);
|
|
23556
|
+
}
|
|
23557
|
+
if (isPaid) {
|
|
23558
|
+
dim.paidSessions += sessions;
|
|
23559
|
+
dim.paidUsers += users;
|
|
23560
|
+
} else {
|
|
23561
|
+
dim.organicSessions += sessions;
|
|
23562
|
+
dim.organicUsers += users;
|
|
23563
|
+
}
|
|
23564
|
+
if (row.sourceDimension === "session") {
|
|
23565
|
+
bySessionChannelGroup.set(
|
|
23566
|
+
row.channelGroup,
|
|
23567
|
+
(bySessionChannelGroup.get(row.channelGroup) ?? 0) + sessions
|
|
23568
|
+
);
|
|
23569
|
+
const sessionBucket = isPaid ? paidBySession : organicBySession;
|
|
23570
|
+
sessionBucket.sessions += sessions;
|
|
23571
|
+
sessionBucket.users += users;
|
|
23572
|
+
}
|
|
23573
|
+
}
|
|
23574
|
+
for (const byDimension of dedupeGroups.values()) {
|
|
23575
|
+
const dims = [...byDimension.values()];
|
|
23576
|
+
const bestSessions = dims.reduce((best, d) => d.paidSessions + d.organicSessions > best.paidSessions + best.organicSessions ? d : best);
|
|
23577
|
+
const bestUsers = dims.reduce((best, d) => d.paidUsers + d.organicUsers > best.paidUsers + best.organicUsers ? d : best);
|
|
23578
|
+
paidDeduped.sessions += bestSessions.paidSessions;
|
|
23579
|
+
organicDeduped.sessions += bestSessions.organicSessions;
|
|
23580
|
+
paidDeduped.users += bestUsers.paidUsers;
|
|
23581
|
+
organicDeduped.users += bestUsers.organicUsers;
|
|
23582
|
+
}
|
|
23583
|
+
return {
|
|
23584
|
+
paidDeduped,
|
|
23585
|
+
organicDeduped,
|
|
23586
|
+
paidBySession,
|
|
23587
|
+
organicBySession,
|
|
23588
|
+
bySessionChannelGroup,
|
|
23589
|
+
deduped: {
|
|
23590
|
+
sessions: paidDeduped.sessions + organicDeduped.sessions,
|
|
23591
|
+
users: paidDeduped.users + organicDeduped.users
|
|
23592
|
+
},
|
|
23593
|
+
bySession: {
|
|
23594
|
+
sessions: paidBySession.sessions + organicBySession.sessions,
|
|
23595
|
+
users: paidBySession.users + organicBySession.users
|
|
23596
|
+
}
|
|
23597
|
+
};
|
|
23598
|
+
}
|
|
23363
23599
|
async function refreshOAuthTokenIfNeeded(googleStore, authConfig, canonicalDomain, oauthConn) {
|
|
23364
23600
|
const expiresAt = oauthConn.tokenExpiresAt ? new Date(oauthConn.tokenExpiresAt).getTime() : 0;
|
|
23365
23601
|
const fiveMinutes = 5 * 60 * 1e3;
|
|
@@ -23599,7 +23835,7 @@ async function ga4Routes(app, opts) {
|
|
|
23599
23835
|
app.db.transaction((tx) => {
|
|
23600
23836
|
if (syncTraffic) {
|
|
23601
23837
|
tx.delete(gaTrafficSnapshots).where(
|
|
23602
|
-
|
|
23838
|
+
and21(
|
|
23603
23839
|
eq26(gaTrafficSnapshots.projectId, project.id),
|
|
23604
23840
|
sql10`${gaTrafficSnapshots.date} >= ${summary.periodStart}`,
|
|
23605
23841
|
sql10`${gaTrafficSnapshots.date} <= ${summary.periodEnd}`
|
|
@@ -23623,7 +23859,7 @@ async function ga4Routes(app, opts) {
|
|
|
23623
23859
|
}
|
|
23624
23860
|
if (syncAi) {
|
|
23625
23861
|
tx.delete(gaAiReferrals).where(
|
|
23626
|
-
|
|
23862
|
+
and21(
|
|
23627
23863
|
eq26(gaAiReferrals.projectId, project.id),
|
|
23628
23864
|
sql10`${gaAiReferrals.date} >= ${summary.periodStart}`,
|
|
23629
23865
|
sql10`${gaAiReferrals.date} <= ${summary.periodEnd}`
|
|
@@ -23636,6 +23872,12 @@ async function ga4Routes(app, opts) {
|
|
|
23636
23872
|
date: row.date,
|
|
23637
23873
|
source: row.source,
|
|
23638
23874
|
medium: row.medium,
|
|
23875
|
+
trafficClass: row.trafficClass ?? classifyGa4AiReferralTrafficClass({
|
|
23876
|
+
source: row.source,
|
|
23877
|
+
medium: row.medium,
|
|
23878
|
+
channelGroup: row.channelGroup,
|
|
23879
|
+
landingPage: row.landingPage
|
|
23880
|
+
}),
|
|
23639
23881
|
sourceDimension: row.sourceDimension,
|
|
23640
23882
|
channelGroup: row.channelGroup,
|
|
23641
23883
|
landingPage: row.landingPage,
|
|
@@ -23649,7 +23891,7 @@ async function ga4Routes(app, opts) {
|
|
|
23649
23891
|
}
|
|
23650
23892
|
if (syncSocial) {
|
|
23651
23893
|
tx.delete(gaSocialReferrals).where(
|
|
23652
|
-
|
|
23894
|
+
and21(
|
|
23653
23895
|
eq26(gaSocialReferrals.projectId, project.id),
|
|
23654
23896
|
sql10`${gaSocialReferrals.date} >= ${summary.periodStart}`,
|
|
23655
23897
|
sql10`${gaSocialReferrals.date} <= ${summary.periodEnd}`
|
|
@@ -23753,7 +23995,7 @@ async function ga4Routes(app, opts) {
|
|
|
23753
23995
|
totalDirectSessions: gaTrafficWindowSummaries.totalDirectSessions,
|
|
23754
23996
|
totalUsers: gaTrafficWindowSummaries.totalUsers
|
|
23755
23997
|
}).from(gaTrafficWindowSummaries).where(
|
|
23756
|
-
|
|
23998
|
+
and21(
|
|
23757
23999
|
eq26(gaTrafficWindowSummaries.projectId, project.id),
|
|
23758
24000
|
eq26(gaTrafficWindowSummaries.windowKey, window)
|
|
23759
24001
|
)
|
|
@@ -23762,7 +24004,7 @@ async function ga4Routes(app, opts) {
|
|
|
23762
24004
|
totalSessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)`,
|
|
23763
24005
|
totalOrganicSessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)`,
|
|
23764
24006
|
totalUsers: sql10`COALESCE(SUM(${gaTrafficSnapshots.users}), 0)`
|
|
23765
|
-
}).from(gaTrafficSnapshots).where(
|
|
24007
|
+
}).from(gaTrafficSnapshots).where(and21(...snapshotConditions)).get() : null;
|
|
23766
24008
|
const summaryRow = cutoffDate ? windowSummaryRow ?? snapshotTotalsRow : app.db.select({
|
|
23767
24009
|
totalSessions: gaTrafficSummaries.totalSessions,
|
|
23768
24010
|
totalOrganicSessions: gaTrafficSummaries.totalOrganicSessions,
|
|
@@ -23770,7 +24012,7 @@ async function ga4Routes(app, opts) {
|
|
|
23770
24012
|
}).from(gaTrafficSummaries).where(eq26(gaTrafficSummaries.projectId, project.id)).get();
|
|
23771
24013
|
const directTotalRow = windowSummaryRow ? { totalDirectSessions: windowSummaryRow.totalDirectSessions } : app.db.select({
|
|
23772
24014
|
totalDirectSessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)`
|
|
23773
|
-
}).from(gaTrafficSnapshots).where(
|
|
24015
|
+
}).from(gaTrafficSnapshots).where(and21(...snapshotConditions)).get();
|
|
23774
24016
|
const summaryMeta = app.db.select({
|
|
23775
24017
|
periodStart: gaTrafficSummaries.periodStart,
|
|
23776
24018
|
periodEnd: gaTrafficSummaries.periodEnd
|
|
@@ -23781,77 +24023,60 @@ async function ga4Routes(app, opts) {
|
|
|
23781
24023
|
organicSessions: sql10`SUM(${gaTrafficSnapshots.organicSessions})`,
|
|
23782
24024
|
directSessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)`,
|
|
23783
24025
|
users: sql10`SUM(${gaTrafficSnapshots.users})`
|
|
23784
|
-
}).from(gaTrafficSnapshots).where(
|
|
24026
|
+
}).from(gaTrafficSnapshots).where(and21(...snapshotConditions)).groupBy(sql10`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql10`SUM(${gaTrafficSnapshots.sessions}) DESC`).limit(limit).all();
|
|
23785
24027
|
const aiReferralRows = app.db.select({
|
|
23786
24028
|
source: gaAiReferrals.source,
|
|
23787
24029
|
medium: gaAiReferrals.medium,
|
|
24030
|
+
trafficClass: gaAiReferrals.trafficClass,
|
|
23788
24031
|
sourceDimension: gaAiReferrals.sourceDimension,
|
|
23789
24032
|
sessions: sql10`SUM(${gaAiReferrals.sessions})`,
|
|
23790
24033
|
users: sql10`SUM(${gaAiReferrals.users})`
|
|
23791
|
-
}).from(gaAiReferrals).where(
|
|
24034
|
+
}).from(gaAiReferrals).where(and21(...aiConditions)).groupBy(gaAiReferrals.source, gaAiReferrals.medium, gaAiReferrals.trafficClass, gaAiReferrals.sourceDimension).all();
|
|
23792
24035
|
const aiReferralLandingPageRows = app.db.select({
|
|
23793
24036
|
source: gaAiReferrals.source,
|
|
23794
24037
|
medium: gaAiReferrals.medium,
|
|
24038
|
+
trafficClass: gaAiReferrals.trafficClass,
|
|
23795
24039
|
sourceDimension: gaAiReferrals.sourceDimension,
|
|
23796
24040
|
landingPage: sql10`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`,
|
|
23797
24041
|
sessions: sql10`SUM(${gaAiReferrals.sessions})`,
|
|
23798
24042
|
users: sql10`SUM(${gaAiReferrals.users})`
|
|
23799
|
-
}).from(gaAiReferrals).where(
|
|
24043
|
+
}).from(gaAiReferrals).where(and21(...aiConditions)).groupBy(
|
|
23800
24044
|
gaAiReferrals.source,
|
|
23801
24045
|
gaAiReferrals.medium,
|
|
24046
|
+
gaAiReferrals.trafficClass,
|
|
23802
24047
|
gaAiReferrals.sourceDimension,
|
|
23803
24048
|
sql10`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`
|
|
23804
24049
|
).all();
|
|
24050
|
+
const aiRowsForTotals = app.db.select({
|
|
24051
|
+
date: gaAiReferrals.date,
|
|
24052
|
+
source: gaAiReferrals.source,
|
|
24053
|
+
medium: gaAiReferrals.medium,
|
|
24054
|
+
trafficClass: gaAiReferrals.trafficClass,
|
|
24055
|
+
sourceDimension: gaAiReferrals.sourceDimension,
|
|
24056
|
+
channelGroup: gaAiReferrals.channelGroup,
|
|
24057
|
+
sessions: gaAiReferrals.sessions,
|
|
24058
|
+
users: gaAiReferrals.users
|
|
24059
|
+
}).from(gaAiReferrals).where(and21(...aiConditions)).all();
|
|
23805
24060
|
const aiReferrals = pickWinningDimension(
|
|
23806
24061
|
aiReferralRows,
|
|
23807
|
-
(r) => `${r.source}\0${r.medium}`
|
|
24062
|
+
(r) => `${r.source}\0${r.medium}\0${r.trafficClass}`
|
|
23808
24063
|
);
|
|
23809
24064
|
const aiReferralLandingPages = pickWinningDimension(
|
|
23810
24065
|
aiReferralLandingPageRows,
|
|
23811
|
-
(r) => `${r.source}\0${r.medium}\0${r.landingPage}`
|
|
24066
|
+
(r) => `${r.source}\0${r.medium}\0${r.trafficClass}\0${r.landingPage}`
|
|
23812
24067
|
);
|
|
23813
|
-
const
|
|
23814
|
-
sessions: sql10`COALESCE(SUM(max_sessions), 0)`,
|
|
23815
|
-
users: sql10`COALESCE(SUM(max_users), 0)`
|
|
23816
|
-
}).from(
|
|
23817
|
-
sql10`(
|
|
23818
|
-
SELECT date, source, medium,
|
|
23819
|
-
MAX(dimension_sessions) AS max_sessions,
|
|
23820
|
-
MAX(dimension_users) AS max_users
|
|
23821
|
-
FROM (
|
|
23822
|
-
SELECT date, source, medium, source_dimension,
|
|
23823
|
-
SUM(sessions) AS dimension_sessions,
|
|
23824
|
-
SUM(users) AS dimension_users
|
|
23825
|
-
FROM ga_ai_referrals
|
|
23826
|
-
WHERE project_id = ${project.id}${cutoffDate ? sql10` AND date >= ${cutoffDate}` : sql10``}
|
|
23827
|
-
GROUP BY date, source, medium, source_dimension
|
|
23828
|
-
)
|
|
23829
|
-
GROUP BY date, source, medium
|
|
23830
|
-
)`
|
|
23831
|
-
).get();
|
|
23832
|
-
const aiBySessionRows = app.db.select({
|
|
23833
|
-
channelGroup: gaAiReferrals.channelGroup,
|
|
23834
|
-
sessions: sql10`COALESCE(SUM(${gaAiReferrals.sessions}), 0)`,
|
|
23835
|
-
users: sql10`COALESCE(SUM(${gaAiReferrals.users}), 0)`
|
|
23836
|
-
}).from(gaAiReferrals).where(and20(...aiConditions, eq26(gaAiReferrals.sourceDimension, "session"))).groupBy(gaAiReferrals.channelGroup).all();
|
|
23837
|
-
const aiSessionsByChannelGroup = /* @__PURE__ */ new Map();
|
|
23838
|
-
let aiBySessionUsers = 0;
|
|
23839
|
-
for (const row of aiBySessionRows) {
|
|
23840
|
-
aiSessionsByChannelGroup.set(row.channelGroup, row.sessions ?? 0);
|
|
23841
|
-
aiBySessionUsers += row.users ?? 0;
|
|
23842
|
-
}
|
|
23843
|
-
const aiBySessionSessions = [...aiSessionsByChannelGroup.values()].reduce((sum, sessions) => sum + sessions, 0);
|
|
24068
|
+
const aiSummary = summarizeAiReferralCounts(aiRowsForTotals);
|
|
23844
24069
|
const socialReferrals = app.db.select({
|
|
23845
24070
|
source: gaSocialReferrals.source,
|
|
23846
24071
|
medium: gaSocialReferrals.medium,
|
|
23847
24072
|
channelGroup: gaSocialReferrals.channelGroup,
|
|
23848
24073
|
sessions: sql10`SUM(${gaSocialReferrals.sessions})`,
|
|
23849
24074
|
users: sql10`SUM(${gaSocialReferrals.users})`
|
|
23850
|
-
}).from(gaSocialReferrals).where(
|
|
24075
|
+
}).from(gaSocialReferrals).where(and21(...socialConditions)).groupBy(gaSocialReferrals.source, gaSocialReferrals.medium, gaSocialReferrals.channelGroup).orderBy(sql10`SUM(${gaSocialReferrals.sessions}) DESC`).all();
|
|
23851
24076
|
const socialTotals = app.db.select({
|
|
23852
24077
|
sessions: sql10`SUM(${gaSocialReferrals.sessions})`,
|
|
23853
24078
|
users: sql10`SUM(${gaSocialReferrals.users})`
|
|
23854
|
-
}).from(gaSocialReferrals).where(
|
|
24079
|
+
}).from(gaSocialReferrals).where(and21(...socialConditions)).get();
|
|
23855
24080
|
const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq26(gaTrafficSummaries.projectId, project.id)).orderBy(desc13(gaTrafficSummaries.syncedAt)).limit(1).get();
|
|
23856
24081
|
const total = summaryRow?.totalSessions ?? 0;
|
|
23857
24082
|
const totalDirectSessions = directTotalRow?.totalDirectSessions ?? 0;
|
|
@@ -23862,7 +24087,7 @@ async function ga4Routes(app, opts) {
|
|
|
23862
24087
|
organicSessions: totalOrganicSessions,
|
|
23863
24088
|
socialSessions,
|
|
23864
24089
|
directSessions: totalDirectSessions,
|
|
23865
|
-
aiSessionsByChannelGroup
|
|
24090
|
+
aiSessionsByChannelGroup: aiSummary.bySessionChannelGroup
|
|
23866
24091
|
});
|
|
23867
24092
|
return {
|
|
23868
24093
|
totalSessions: total,
|
|
@@ -23879,6 +24104,7 @@ async function ga4Routes(app, opts) {
|
|
|
23879
24104
|
aiReferrals: aiReferrals.map((r) => ({
|
|
23880
24105
|
source: r.source,
|
|
23881
24106
|
medium: r.medium,
|
|
24107
|
+
trafficClass: normalizeAiTrafficClass(r.trafficClass),
|
|
23882
24108
|
sourceDimension: r.sourceDimension,
|
|
23883
24109
|
sessions: r.sessions ?? 0,
|
|
23884
24110
|
users: r.users ?? 0
|
|
@@ -23886,15 +24112,24 @@ async function ga4Routes(app, opts) {
|
|
|
23886
24112
|
aiReferralLandingPages: aiReferralLandingPages.map((r) => ({
|
|
23887
24113
|
source: r.source,
|
|
23888
24114
|
medium: r.medium,
|
|
24115
|
+
trafficClass: normalizeAiTrafficClass(r.trafficClass),
|
|
23889
24116
|
sourceDimension: r.sourceDimension,
|
|
23890
24117
|
landingPage: r.landingPage,
|
|
23891
24118
|
sessions: r.sessions ?? 0,
|
|
23892
24119
|
users: r.users ?? 0
|
|
23893
24120
|
})),
|
|
23894
|
-
aiSessionsDeduped:
|
|
23895
|
-
aiUsersDeduped:
|
|
23896
|
-
|
|
23897
|
-
|
|
24121
|
+
aiSessionsDeduped: aiSummary.deduped.sessions,
|
|
24122
|
+
aiUsersDeduped: aiSummary.deduped.users,
|
|
24123
|
+
paidAiSessionsDeduped: aiSummary.paidDeduped.sessions,
|
|
24124
|
+
paidAiUsersDeduped: aiSummary.paidDeduped.users,
|
|
24125
|
+
organicAiSessionsDeduped: aiSummary.organicDeduped.sessions,
|
|
24126
|
+
organicAiUsersDeduped: aiSummary.organicDeduped.users,
|
|
24127
|
+
aiSessionsBySession: aiSummary.bySession.sessions,
|
|
24128
|
+
aiUsersBySession: aiSummary.bySession.users,
|
|
24129
|
+
paidAiSessionsBySession: aiSummary.paidBySession.sessions,
|
|
24130
|
+
paidAiUsersBySession: aiSummary.paidBySession.users,
|
|
24131
|
+
organicAiSessionsBySession: aiSummary.organicBySession.sessions,
|
|
24132
|
+
organicAiUsersBySession: aiSummary.organicBySession.users,
|
|
23898
24133
|
socialReferrals: socialReferrals.map((r) => ({
|
|
23899
24134
|
source: r.source,
|
|
23900
24135
|
medium: r.medium,
|
|
@@ -23906,16 +24141,24 @@ async function ga4Routes(app, opts) {
|
|
|
23906
24141
|
socialUsers: socialTotals?.users ?? 0,
|
|
23907
24142
|
channelBreakdown,
|
|
23908
24143
|
organicSharePct: total > 0 ? Math.round(totalOrganicSessions / total * 100) : 0,
|
|
23909
|
-
aiSharePct: total > 0 ? Math.round(
|
|
23910
|
-
aiSharePctBySession: total > 0 ? Math.round(
|
|
24144
|
+
aiSharePct: total > 0 ? Math.round(aiSummary.deduped.sessions / total * 100) : 0,
|
|
24145
|
+
aiSharePctBySession: total > 0 ? Math.round(aiSummary.bySession.sessions / total * 100) : 0,
|
|
24146
|
+
paidAiSharePct: total > 0 ? Math.round(aiSummary.paidDeduped.sessions / total * 100) : 0,
|
|
24147
|
+
paidAiSharePctBySession: total > 0 ? Math.round(aiSummary.paidBySession.sessions / total * 100) : 0,
|
|
24148
|
+
organicAiSharePct: total > 0 ? Math.round(aiSummary.organicDeduped.sessions / total * 100) : 0,
|
|
24149
|
+
organicAiSharePctBySession: total > 0 ? Math.round(aiSummary.organicBySession.sessions / total * 100) : 0,
|
|
23911
24150
|
directSharePct: total > 0 ? Math.round(totalDirectSessions / total * 100) : 0,
|
|
23912
24151
|
socialSharePct: total > 0 ? Math.round(socialSessions / total * 100) : 0,
|
|
23913
24152
|
otherSessions: channelBreakdown.other.sessions,
|
|
23914
24153
|
otherSharePct: channelBreakdown.other.sharePct,
|
|
23915
24154
|
otherSharePctDisplay: channelBreakdown.other.sharePctDisplay,
|
|
23916
24155
|
organicSharePctDisplay: formatSharePct(totalOrganicSessions, total),
|
|
23917
|
-
aiSharePctDisplay: formatSharePct(
|
|
23918
|
-
aiSharePctBySessionDisplay: formatSharePct(
|
|
24156
|
+
aiSharePctDisplay: formatSharePct(aiSummary.deduped.sessions, total),
|
|
24157
|
+
aiSharePctBySessionDisplay: formatSharePct(aiSummary.bySession.sessions, total),
|
|
24158
|
+
paidAiSharePctDisplay: formatSharePct(aiSummary.paidDeduped.sessions, total),
|
|
24159
|
+
paidAiSharePctBySessionDisplay: formatSharePct(aiSummary.paidBySession.sessions, total),
|
|
24160
|
+
organicAiSharePctDisplay: formatSharePct(aiSummary.organicDeduped.sessions, total),
|
|
24161
|
+
organicAiSharePctBySessionDisplay: formatSharePct(aiSummary.organicBySession.sessions, total),
|
|
23919
24162
|
directSharePctDisplay: formatSharePct(totalDirectSessions, total),
|
|
23920
24163
|
socialSharePctDisplay: formatSharePct(socialSessions, total),
|
|
23921
24164
|
lastSyncedAt: latestSync?.syncedAt ?? null,
|
|
@@ -23938,14 +24181,16 @@ async function ga4Routes(app, opts) {
|
|
|
23938
24181
|
date: gaAiReferrals.date,
|
|
23939
24182
|
source: gaAiReferrals.source,
|
|
23940
24183
|
medium: gaAiReferrals.medium,
|
|
24184
|
+
trafficClass: gaAiReferrals.trafficClass,
|
|
23941
24185
|
landingPage: sql10`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`,
|
|
23942
24186
|
sourceDimension: gaAiReferrals.sourceDimension,
|
|
23943
24187
|
sessions: sql10`SUM(${gaAiReferrals.sessions})`,
|
|
23944
24188
|
users: sql10`SUM(${gaAiReferrals.users})`
|
|
23945
|
-
}).from(gaAiReferrals).where(
|
|
24189
|
+
}).from(gaAiReferrals).where(and21(...conditions)).groupBy(
|
|
23946
24190
|
gaAiReferrals.date,
|
|
23947
24191
|
gaAiReferrals.source,
|
|
23948
24192
|
gaAiReferrals.medium,
|
|
24193
|
+
gaAiReferrals.trafficClass,
|
|
23949
24194
|
gaAiReferrals.sourceDimension,
|
|
23950
24195
|
sql10`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`
|
|
23951
24196
|
).orderBy(gaAiReferrals.date).all();
|
|
@@ -23964,7 +24209,7 @@ async function ga4Routes(app, opts) {
|
|
|
23964
24209
|
channelGroup: gaSocialReferrals.channelGroup,
|
|
23965
24210
|
sessions: gaSocialReferrals.sessions,
|
|
23966
24211
|
users: gaSocialReferrals.users
|
|
23967
|
-
}).from(gaSocialReferrals).where(
|
|
24212
|
+
}).from(gaSocialReferrals).where(and21(...conditions)).orderBy(gaSocialReferrals.date).all();
|
|
23968
24213
|
return rows;
|
|
23969
24214
|
});
|
|
23970
24215
|
app.get("/projects/:name/ga/social-referral-trend", async (request, _reply) => {
|
|
@@ -23977,7 +24222,7 @@ async function ga4Routes(app, opts) {
|
|
|
23977
24222
|
d.setDate(d.getDate() - n);
|
|
23978
24223
|
return fmt(d);
|
|
23979
24224
|
};
|
|
23980
|
-
const sumSocial = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(
|
|
24225
|
+
const sumSocial = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and21(
|
|
23981
24226
|
eq26(gaSocialReferrals.projectId, project.id),
|
|
23982
24227
|
sql10`${gaSocialReferrals.date} >= ${from}`,
|
|
23983
24228
|
sql10`${gaSocialReferrals.date} < ${to}`
|
|
@@ -23990,7 +24235,7 @@ async function ga4Routes(app, opts) {
|
|
|
23990
24235
|
const sourceCurrent = app.db.select({
|
|
23991
24236
|
source: gaSocialReferrals.source,
|
|
23992
24237
|
sessions: sql10`SUM(${gaSocialReferrals.sessions})`
|
|
23993
|
-
}).from(gaSocialReferrals).where(
|
|
24238
|
+
}).from(gaSocialReferrals).where(and21(
|
|
23994
24239
|
eq26(gaSocialReferrals.projectId, project.id),
|
|
23995
24240
|
sql10`${gaSocialReferrals.date} >= ${daysAgo(7)}`,
|
|
23996
24241
|
sql10`${gaSocialReferrals.date} < ${fmt(today)}`
|
|
@@ -23998,7 +24243,7 @@ async function ga4Routes(app, opts) {
|
|
|
23998
24243
|
const sourcePrev = app.db.select({
|
|
23999
24244
|
source: gaSocialReferrals.source,
|
|
24000
24245
|
sessions: sql10`SUM(${gaSocialReferrals.sessions})`
|
|
24001
|
-
}).from(gaSocialReferrals).where(
|
|
24246
|
+
}).from(gaSocialReferrals).where(and21(
|
|
24002
24247
|
eq26(gaSocialReferrals.projectId, project.id),
|
|
24003
24248
|
sql10`${gaSocialReferrals.date} >= ${daysAgo(14)}`,
|
|
24004
24249
|
sql10`${gaSocialReferrals.date} < ${daysAgo(7)}`
|
|
@@ -24040,16 +24285,16 @@ async function ga4Routes(app, opts) {
|
|
|
24040
24285
|
return fmt(d);
|
|
24041
24286
|
};
|
|
24042
24287
|
const pct = (cur, prev) => prev === 0 ? null : Math.round((cur - prev) / prev * 100);
|
|
24043
|
-
const sumTotal = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)` }).from(gaTrafficSnapshots).where(
|
|
24044
|
-
const sumOrganic = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)` }).from(gaTrafficSnapshots).where(
|
|
24045
|
-
const sumDirect = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)` }).from(gaTrafficSnapshots).where(
|
|
24046
|
-
const sumAi = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(
|
|
24288
|
+
const sumTotal = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)` }).from(gaTrafficSnapshots).where(and21(eq26(gaTrafficSnapshots.projectId, project.id), sql10`${gaTrafficSnapshots.date} >= ${from}`, sql10`${gaTrafficSnapshots.date} < ${to}`)).get();
|
|
24289
|
+
const sumOrganic = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)` }).from(gaTrafficSnapshots).where(and21(eq26(gaTrafficSnapshots.projectId, project.id), sql10`${gaTrafficSnapshots.date} >= ${from}`, sql10`${gaTrafficSnapshots.date} < ${to}`)).get();
|
|
24290
|
+
const sumDirect = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)` }).from(gaTrafficSnapshots).where(and21(eq26(gaTrafficSnapshots.projectId, project.id), sql10`${gaTrafficSnapshots.date} >= ${from}`, sql10`${gaTrafficSnapshots.date} < ${to}`)).get();
|
|
24291
|
+
const sumAi = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and21(
|
|
24047
24292
|
eq26(gaAiReferrals.projectId, project.id),
|
|
24048
24293
|
sql10`${gaAiReferrals.date} >= ${from}`,
|
|
24049
24294
|
sql10`${gaAiReferrals.date} < ${to}`,
|
|
24050
24295
|
eq26(gaAiReferrals.sourceDimension, "session")
|
|
24051
24296
|
)).get();
|
|
24052
|
-
const sumSocial = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(
|
|
24297
|
+
const sumSocial = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and21(eq26(gaSocialReferrals.projectId, project.id), sql10`${gaSocialReferrals.date} >= ${from}`, sql10`${gaSocialReferrals.date} < ${to}`)).get();
|
|
24053
24298
|
const todayStr = fmt(today);
|
|
24054
24299
|
const buildTrend = (sum) => {
|
|
24055
24300
|
const c7 = sum(daysAgo(7), todayStr)?.sessions ?? 0;
|
|
@@ -24058,13 +24303,13 @@ async function ga4Routes(app, opts) {
|
|
|
24058
24303
|
const p30 = sum(daysAgo(60), daysAgo(30))?.sessions ?? 0;
|
|
24059
24304
|
return { sessions7d: c7, sessionsPrev7d: p7, trend7dPct: pct(c7, p7), sessions30d: c30, sessionsPrev30d: p30, trend30dPct: pct(c30, p30) };
|
|
24060
24305
|
};
|
|
24061
|
-
const aiSourceCurrent = app.db.select({ source: gaAiReferrals.source, sessions: sql10`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(
|
|
24306
|
+
const aiSourceCurrent = app.db.select({ source: gaAiReferrals.source, sessions: sql10`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and21(
|
|
24062
24307
|
eq26(gaAiReferrals.projectId, project.id),
|
|
24063
24308
|
sql10`${gaAiReferrals.date} >= ${daysAgo(7)}`,
|
|
24064
24309
|
sql10`${gaAiReferrals.date} < ${todayStr}`,
|
|
24065
24310
|
eq26(gaAiReferrals.sourceDimension, "session")
|
|
24066
24311
|
)).groupBy(gaAiReferrals.source).all();
|
|
24067
|
-
const aiSourcePrev = app.db.select({ source: gaAiReferrals.source, sessions: sql10`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(
|
|
24312
|
+
const aiSourcePrev = app.db.select({ source: gaAiReferrals.source, sessions: sql10`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and21(
|
|
24068
24313
|
eq26(gaAiReferrals.projectId, project.id),
|
|
24069
24314
|
sql10`${gaAiReferrals.date} >= ${daysAgo(14)}`,
|
|
24070
24315
|
sql10`${gaAiReferrals.date} < ${daysAgo(7)}`,
|
|
@@ -24084,8 +24329,8 @@ async function ga4Routes(app, opts) {
|
|
|
24084
24329
|
}
|
|
24085
24330
|
return mover;
|
|
24086
24331
|
};
|
|
24087
|
-
const socialSourceCurrent = app.db.select({ source: gaSocialReferrals.source, sessions: sql10`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(
|
|
24088
|
-
const socialSourcePrev = app.db.select({ source: gaSocialReferrals.source, sessions: sql10`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(
|
|
24332
|
+
const socialSourceCurrent = app.db.select({ source: gaSocialReferrals.source, sessions: sql10`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and21(eq26(gaSocialReferrals.projectId, project.id), sql10`${gaSocialReferrals.date} >= ${daysAgo(7)}`, sql10`${gaSocialReferrals.date} < ${todayStr}`)).groupBy(gaSocialReferrals.source).all();
|
|
24333
|
+
const socialSourcePrev = app.db.select({ source: gaSocialReferrals.source, sessions: sql10`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and21(eq26(gaSocialReferrals.projectId, project.id), sql10`${gaSocialReferrals.date} >= ${daysAgo(14)}`, sql10`${gaSocialReferrals.date} < ${daysAgo(7)}`)).groupBy(gaSocialReferrals.source).all();
|
|
24089
24334
|
return {
|
|
24090
24335
|
total: buildTrend(sumTotal),
|
|
24091
24336
|
organic: buildTrend(sumOrganic),
|
|
@@ -24107,7 +24352,7 @@ async function ga4Routes(app, opts) {
|
|
|
24107
24352
|
sessions: sql10`SUM(${gaTrafficSnapshots.sessions})`,
|
|
24108
24353
|
organicSessions: sql10`SUM(${gaTrafficSnapshots.organicSessions})`,
|
|
24109
24354
|
users: sql10`SUM(${gaTrafficSnapshots.users})`
|
|
24110
|
-
}).from(gaTrafficSnapshots).where(
|
|
24355
|
+
}).from(gaTrafficSnapshots).where(and21(...conditions)).groupBy(gaTrafficSnapshots.date).orderBy(gaTrafficSnapshots.date).all();
|
|
24111
24356
|
return rows.map((r) => ({
|
|
24112
24357
|
date: r.date,
|
|
24113
24358
|
sessions: r.sessions ?? 0,
|
|
@@ -25771,7 +26016,7 @@ async function wordpressRoutes(app, opts) {
|
|
|
25771
26016
|
|
|
25772
26017
|
// ../api-routes/src/backlinks.ts
|
|
25773
26018
|
import crypto23 from "crypto";
|
|
25774
|
-
import { and as
|
|
26019
|
+
import { and as and23, asc as asc4, desc as desc14, eq as eq27, sql as sql11 } from "drizzle-orm";
|
|
25775
26020
|
|
|
25776
26021
|
// ../integration-commoncrawl/src/constants.ts
|
|
25777
26022
|
import os2 from "os";
|
|
@@ -26174,7 +26419,7 @@ function pruneCachedRelease(release, opts = {}) {
|
|
|
26174
26419
|
}
|
|
26175
26420
|
|
|
26176
26421
|
// ../api-routes/src/backlinks-filter.ts
|
|
26177
|
-
import { and as
|
|
26422
|
+
import { and as and22, ne as ne3, notLike } from "drizzle-orm";
|
|
26178
26423
|
var BACKLINK_FILTER_PATTERNS = [
|
|
26179
26424
|
"*.google.com",
|
|
26180
26425
|
"*.googleusercontent.com",
|
|
@@ -26197,7 +26442,7 @@ function backlinkCrawlerExclusionClause() {
|
|
|
26197
26442
|
conditions.push(ne3(backlinkDomains.linkingDomain, pattern));
|
|
26198
26443
|
}
|
|
26199
26444
|
}
|
|
26200
|
-
const combined =
|
|
26445
|
+
const combined = and22(...conditions);
|
|
26201
26446
|
if (!combined) throw new Error("BACKLINK_FILTER_PATTERNS is unexpectedly empty");
|
|
26202
26447
|
return combined;
|
|
26203
26448
|
}
|
|
@@ -26272,7 +26517,7 @@ function latestSummaryForProject(db, projectId, source, release) {
|
|
|
26272
26517
|
eq27(backlinkSummaries.source, source)
|
|
26273
26518
|
];
|
|
26274
26519
|
if (release) conditions.push(eq27(backlinkSummaries.release, release));
|
|
26275
|
-
return db.select().from(backlinkSummaries).where(
|
|
26520
|
+
return db.select().from(backlinkSummaries).where(and23(...conditions)).orderBy(desc14(backlinkSummaries.queriedAt)).limit(1).get();
|
|
26276
26521
|
}
|
|
26277
26522
|
function parseExcludeCrawlers(value) {
|
|
26278
26523
|
if (!value) return false;
|
|
@@ -26280,12 +26525,12 @@ function parseExcludeCrawlers(value) {
|
|
|
26280
26525
|
return lower === "1" || lower === "true" || lower === "yes";
|
|
26281
26526
|
}
|
|
26282
26527
|
function computeFilteredSummary(db, base) {
|
|
26283
|
-
const baseDomainCondition =
|
|
26528
|
+
const baseDomainCondition = and23(
|
|
26284
26529
|
eq27(backlinkDomains.projectId, base.projectId),
|
|
26285
26530
|
eq27(backlinkDomains.source, base.source),
|
|
26286
26531
|
eq27(backlinkDomains.release, base.release)
|
|
26287
26532
|
);
|
|
26288
|
-
const filteredCondition =
|
|
26533
|
+
const filteredCondition = and23(baseDomainCondition, backlinkCrawlerExclusionClause());
|
|
26289
26534
|
const unfilteredAgg = db.select({
|
|
26290
26535
|
count: sql11`count(*)`,
|
|
26291
26536
|
total: sql11`coalesce(sum(${backlinkDomains.numHosts}), 0)`
|
|
@@ -26315,10 +26560,10 @@ function computeFilteredSummary(db, base) {
|
|
|
26315
26560
|
};
|
|
26316
26561
|
}
|
|
26317
26562
|
function buildSourceAvailability(db, projectId, source, connected, excludeCrawlers) {
|
|
26318
|
-
const summary = db.select().from(backlinkSummaries).where(
|
|
26563
|
+
const summary = db.select().from(backlinkSummaries).where(and23(eq27(backlinkSummaries.projectId, projectId), eq27(backlinkSummaries.source, source))).orderBy(desc14(backlinkSummaries.queriedAt)).limit(1).get();
|
|
26319
26564
|
let totalLinkingDomains = summary?.totalLinkingDomains ?? 0;
|
|
26320
26565
|
if (summary && excludeCrawlers) {
|
|
26321
|
-
const filtered = db.select({ count: sql11`count(*)` }).from(backlinkDomains).where(
|
|
26566
|
+
const filtered = db.select({ count: sql11`count(*)` }).from(backlinkDomains).where(and23(
|
|
26322
26567
|
eq27(backlinkDomains.projectId, projectId),
|
|
26323
26568
|
eq27(backlinkDomains.source, source),
|
|
26324
26569
|
eq27(backlinkDomains.release, summary.release),
|
|
@@ -26501,12 +26746,12 @@ async function backlinksRoutes(app, opts) {
|
|
|
26501
26746
|
const limit = Math.min(Math.max(parseInt(request.query.limit ?? "50", 10) || 50, 1), 500);
|
|
26502
26747
|
const offset = Math.max(parseInt(request.query.offset ?? "0", 10) || 0, 0);
|
|
26503
26748
|
const excludeCrawlers = parseExcludeCrawlers(request.query.excludeCrawlers);
|
|
26504
|
-
const baseDomainCondition =
|
|
26749
|
+
const baseDomainCondition = and23(
|
|
26505
26750
|
eq27(backlinkDomains.projectId, project.id),
|
|
26506
26751
|
eq27(backlinkDomains.source, source),
|
|
26507
26752
|
eq27(backlinkDomains.release, targetRelease)
|
|
26508
26753
|
);
|
|
26509
|
-
const domainCondition = excludeCrawlers ?
|
|
26754
|
+
const domainCondition = excludeCrawlers ? and23(baseDomainCondition, backlinkCrawlerExclusionClause()) : baseDomainCondition;
|
|
26510
26755
|
const totalRow = app.db.select({ count: sql11`count(*)` }).from(backlinkDomains).where(domainCondition).get();
|
|
26511
26756
|
const rows = app.db.select({
|
|
26512
26757
|
linkingDomain: backlinkDomains.linkingDomain,
|
|
@@ -26530,7 +26775,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
26530
26775
|
async (request, reply) => {
|
|
26531
26776
|
const project = resolveProject(app.db, request.params.name);
|
|
26532
26777
|
const source = parseSourceParam(request.query.source);
|
|
26533
|
-
const rows = app.db.select().from(backlinkSummaries).where(
|
|
26778
|
+
const rows = app.db.select().from(backlinkSummaries).where(and23(eq27(backlinkSummaries.projectId, project.id), eq27(backlinkSummaries.source, source))).orderBy(asc4(backlinkSummaries.queriedAt)).all();
|
|
26534
26779
|
const response = rows.map((r) => ({
|
|
26535
26780
|
release: r.release,
|
|
26536
26781
|
totalLinkingDomains: r.totalLinkingDomains,
|
|
@@ -26586,7 +26831,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
26586
26831
|
// ../api-routes/src/traffic.ts
|
|
26587
26832
|
import crypto25 from "crypto";
|
|
26588
26833
|
import { Agent as UndiciAgent } from "undici";
|
|
26589
|
-
import { and as
|
|
26834
|
+
import { and as and24, desc as desc15, eq as eq28, gte as gte4, lte as lte3, sql as sql12 } from "drizzle-orm";
|
|
26590
26835
|
|
|
26591
26836
|
// ../integration-cloud-run/src/auth.ts
|
|
26592
26837
|
import crypto24 from "crypto";
|
|
@@ -30445,28 +30690,28 @@ async function runBackfillTask(options) {
|
|
|
30445
30690
|
try {
|
|
30446
30691
|
app.db.transaction((tx) => {
|
|
30447
30692
|
tx.delete(crawlerEventsHourly).where(
|
|
30448
|
-
|
|
30693
|
+
and24(
|
|
30449
30694
|
eq28(crawlerEventsHourly.sourceId, sourceRow.id),
|
|
30450
30695
|
gte4(crawlerEventsHourly.tsHour, windowStartIso),
|
|
30451
30696
|
lte3(crawlerEventsHourly.tsHour, windowEndIso)
|
|
30452
30697
|
)
|
|
30453
30698
|
).run();
|
|
30454
30699
|
tx.delete(aiUserFetchEventsHourly).where(
|
|
30455
|
-
|
|
30700
|
+
and24(
|
|
30456
30701
|
eq28(aiUserFetchEventsHourly.sourceId, sourceRow.id),
|
|
30457
30702
|
gte4(aiUserFetchEventsHourly.tsHour, windowStartIso),
|
|
30458
30703
|
lte3(aiUserFetchEventsHourly.tsHour, windowEndIso)
|
|
30459
30704
|
)
|
|
30460
30705
|
).run();
|
|
30461
30706
|
tx.delete(aiReferralEventsHourly).where(
|
|
30462
|
-
|
|
30707
|
+
and24(
|
|
30463
30708
|
eq28(aiReferralEventsHourly.sourceId, sourceRow.id),
|
|
30464
30709
|
gte4(aiReferralEventsHourly.tsHour, windowStartIso),
|
|
30465
30710
|
lte3(aiReferralEventsHourly.tsHour, windowEndIso)
|
|
30466
30711
|
)
|
|
30467
30712
|
).run();
|
|
30468
30713
|
tx.delete(rawEventSamples).where(
|
|
30469
|
-
|
|
30714
|
+
and24(
|
|
30470
30715
|
eq28(rawEventSamples.sourceId, sourceRow.id),
|
|
30471
30716
|
gte4(rawEventSamples.ts, windowStartIso),
|
|
30472
30717
|
lte3(rawEventSamples.ts, windowEndIso)
|
|
@@ -30849,7 +31094,7 @@ async function trafficRoutes(app, opts) {
|
|
|
30849
31094
|
row = tx.select().from(trafficSources).where(eq28(trafficSources.id, newId)).get();
|
|
30850
31095
|
}
|
|
30851
31096
|
const existingSchedule = tx.select().from(schedules).where(
|
|
30852
|
-
|
|
31097
|
+
and24(
|
|
30853
31098
|
eq28(schedules.projectId, project.id),
|
|
30854
31099
|
eq28(schedules.kind, SchedulableRunKinds["traffic-sync"])
|
|
30855
31100
|
)
|
|
@@ -31571,7 +31816,7 @@ async function trafficRoutes(app, opts) {
|
|
|
31571
31816
|
pathNormalized: crawlerEventsHourly.pathNormalized,
|
|
31572
31817
|
hits: sql12`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
|
|
31573
31818
|
}).from(crawlerEventsHourly).where(
|
|
31574
|
-
|
|
31819
|
+
and24(
|
|
31575
31820
|
eq28(crawlerEventsHourly.sourceId, row.id),
|
|
31576
31821
|
gte4(crawlerEventsHourly.tsHour, since)
|
|
31577
31822
|
)
|
|
@@ -31581,25 +31826,25 @@ async function trafficRoutes(app, opts) {
|
|
|
31581
31826
|
);
|
|
31582
31827
|
const crawlerTotal = crawlerSegments.content + crawlerSegments.sitemap + crawlerSegments.robots + crawlerSegments.asset + crawlerSegments.other;
|
|
31583
31828
|
const aiUserFetchTotals = app.db.select({ total: sql12`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
|
|
31584
|
-
|
|
31829
|
+
and24(
|
|
31585
31830
|
eq28(aiUserFetchEventsHourly.sourceId, row.id),
|
|
31586
31831
|
gte4(aiUserFetchEventsHourly.tsHour, since)
|
|
31587
31832
|
)
|
|
31588
31833
|
).get();
|
|
31589
31834
|
const aiTotals = app.db.select({ total: sql12`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
|
|
31590
|
-
|
|
31835
|
+
and24(
|
|
31591
31836
|
eq28(aiReferralEventsHourly.sourceId, row.id),
|
|
31592
31837
|
gte4(aiReferralEventsHourly.tsHour, since)
|
|
31593
31838
|
)
|
|
31594
31839
|
).get();
|
|
31595
31840
|
const sampleTotals = app.db.select({ total: sql12`COUNT(*)` }).from(rawEventSamples).where(
|
|
31596
|
-
|
|
31841
|
+
and24(
|
|
31597
31842
|
eq28(rawEventSamples.sourceId, row.id),
|
|
31598
31843
|
gte4(rawEventSamples.ts, since)
|
|
31599
31844
|
)
|
|
31600
31845
|
).get();
|
|
31601
31846
|
const latestRun = app.db.select().from(runs).where(
|
|
31602
|
-
|
|
31847
|
+
and24(
|
|
31603
31848
|
eq28(runs.projectId, projectId),
|
|
31604
31849
|
eq28(runs.kind, RunKinds["traffic-sync"]),
|
|
31605
31850
|
eq28(runs.sourceId, row.id)
|
|
@@ -31633,7 +31878,7 @@ async function trafficRoutes(app, opts) {
|
|
|
31633
31878
|
"`advanceToNow` must be `true`. There is no implicit reset."
|
|
31634
31879
|
);
|
|
31635
31880
|
}
|
|
31636
|
-
const sourceRow = app.db.select().from(trafficSources).where(
|
|
31881
|
+
const sourceRow = app.db.select().from(trafficSources).where(and24(eq28(trafficSources.projectId, project.id), eq28(trafficSources.id, request.params.id))).get();
|
|
31637
31882
|
if (!sourceRow) {
|
|
31638
31883
|
throw notFound("traffic source", request.params.id);
|
|
31639
31884
|
}
|
|
@@ -31738,7 +31983,7 @@ async function trafficRoutes(app, opts) {
|
|
|
31738
31983
|
lte3(crawlerEventsHourly.tsHour, untilIso)
|
|
31739
31984
|
];
|
|
31740
31985
|
if (sourceIdParam) crawlerFilters.push(eq28(crawlerEventsHourly.sourceId, sourceIdParam));
|
|
31741
|
-
const crawlerWhere =
|
|
31986
|
+
const crawlerWhere = and24(...crawlerFilters);
|
|
31742
31987
|
const pathTotals = app.db.select({
|
|
31743
31988
|
pathNormalized: crawlerEventsHourly.pathNormalized,
|
|
31744
31989
|
hits: sql12`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
|
|
@@ -31770,7 +32015,7 @@ async function trafficRoutes(app, opts) {
|
|
|
31770
32015
|
lte3(aiUserFetchEventsHourly.tsHour, untilIso)
|
|
31771
32016
|
];
|
|
31772
32017
|
if (sourceIdParam) userFetchFilters.push(eq28(aiUserFetchEventsHourly.sourceId, sourceIdParam));
|
|
31773
|
-
const userFetchWhere =
|
|
32018
|
+
const userFetchWhere = and24(...userFetchFilters);
|
|
31774
32019
|
const total = app.db.select({ total: sql12`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(userFetchWhere).get();
|
|
31775
32020
|
aiUserFetchTotal = Number(total?.total ?? 0);
|
|
31776
32021
|
const rows = app.db.select().from(aiUserFetchEventsHourly).where(userFetchWhere).orderBy(desc15(aiUserFetchEventsHourly.tsHour)).limit(limit).all();
|
|
@@ -31795,7 +32040,7 @@ async function trafficRoutes(app, opts) {
|
|
|
31795
32040
|
lte3(aiReferralEventsHourly.tsHour, untilIso)
|
|
31796
32041
|
];
|
|
31797
32042
|
if (sourceIdParam) aiFilters.push(eq28(aiReferralEventsHourly.sourceId, sourceIdParam));
|
|
31798
|
-
const aiWhere =
|
|
32043
|
+
const aiWhere = and24(...aiFilters);
|
|
31799
32044
|
const total = app.db.select({ total: sql12`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(aiWhere).get();
|
|
31800
32045
|
aiReferralTotal = Number(total?.total ?? 0);
|
|
31801
32046
|
const rows = app.db.select().from(aiReferralEventsHourly).where(aiWhere).orderBy(desc15(aiReferralEventsHourly.tsHour)).limit(limit).all();
|
|
@@ -32002,7 +32247,7 @@ function readInstalledManifest(skillDir) {
|
|
|
32002
32247
|
var AGENT_CHECKS = [skillsInstalledCheck, skillsCurrentCheck];
|
|
32003
32248
|
|
|
32004
32249
|
// ../api-routes/src/doctor/checks/backlinks.ts
|
|
32005
|
-
import { and as
|
|
32250
|
+
import { and as and25, eq as eq29 } from "drizzle-orm";
|
|
32006
32251
|
function skippedNoProject() {
|
|
32007
32252
|
return {
|
|
32008
32253
|
status: CheckStatuses.skipped,
|
|
@@ -32034,7 +32279,7 @@ var BACKLINKS_CHECKS = [
|
|
|
32034
32279
|
details: { commoncrawl: ccConnected, bingWebmaster: bingConnected }
|
|
32035
32280
|
};
|
|
32036
32281
|
}
|
|
32037
|
-
const ccHasData = ccConnected ? !!ctx.db.select({ id: backlinkSummaries.id }).from(backlinkSummaries).where(
|
|
32282
|
+
const ccHasData = ccConnected ? !!ctx.db.select({ id: backlinkSummaries.id }).from(backlinkSummaries).where(and25(
|
|
32038
32283
|
eq29(backlinkSummaries.projectId, ctx.project.id),
|
|
32039
32284
|
eq29(backlinkSummaries.source, BacklinkSources.commoncrawl)
|
|
32040
32285
|
)).limit(1).get() : false;
|
|
@@ -32559,7 +32804,7 @@ var ga4ConnectionCheck = {
|
|
|
32559
32804
|
var GA_AUTH_CHECKS = [ga4ConnectionCheck];
|
|
32560
32805
|
|
|
32561
32806
|
// ../api-routes/src/doctor/checks/gbp-auth.ts
|
|
32562
|
-
import { and as
|
|
32807
|
+
import { and as and26, eq as eq32 } from "drizzle-orm";
|
|
32563
32808
|
var RECENT_SYNC_WARN_DAYS2 = 7;
|
|
32564
32809
|
var RECENT_SYNC_FAIL_DAYS2 = 30;
|
|
32565
32810
|
function skippedNoProject3() {
|
|
@@ -32792,7 +33037,7 @@ var recentSyncCheck = {
|
|
|
32792
33037
|
title: "GBP recent sync",
|
|
32793
33038
|
run: (ctx) => {
|
|
32794
33039
|
if (!ctx.project) return skippedNoProject3();
|
|
32795
|
-
const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(
|
|
33040
|
+
const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(and26(eq32(gbpLocations.projectId, ctx.project.id), eq32(gbpLocations.selected, true))).all();
|
|
32796
33041
|
if (selected.length === 0) {
|
|
32797
33042
|
return {
|
|
32798
33043
|
status: CheckStatuses.skipped,
|
|
@@ -33394,7 +33639,7 @@ var RUNTIME_STATE_CHECKS = [
|
|
|
33394
33639
|
];
|
|
33395
33640
|
|
|
33396
33641
|
// ../api-routes/src/doctor/checks/traffic-source.ts
|
|
33397
|
-
import { and as
|
|
33642
|
+
import { and as and27, eq as eq34, gte as gte5, ne as ne4, sql as sql13 } from "drizzle-orm";
|
|
33398
33643
|
var RECENT_DATA_WARN_DAYS = 7;
|
|
33399
33644
|
var RECENT_DATA_FAIL_DAYS = 30;
|
|
33400
33645
|
function skippedNoProject5() {
|
|
@@ -33408,7 +33653,7 @@ function skippedNoProject5() {
|
|
|
33408
33653
|
function loadProbes(ctx) {
|
|
33409
33654
|
if (!ctx.project) return [];
|
|
33410
33655
|
const rows = ctx.db.select().from(trafficSources).where(
|
|
33411
|
-
|
|
33656
|
+
and27(
|
|
33412
33657
|
eq34(trafficSources.projectId, ctx.project.id),
|
|
33413
33658
|
ne4(trafficSources.status, TrafficSourceStatuses.archived)
|
|
33414
33659
|
)
|
|
@@ -33489,7 +33734,7 @@ var recentDataCheck = {
|
|
|
33489
33734
|
const failCutoff = new Date(now.getTime() - RECENT_DATA_FAIL_DAYS * 24 * 60 * 6e4).toISOString();
|
|
33490
33735
|
const recentCrawlers = Number(
|
|
33491
33736
|
ctx.db.select({ total: sql13`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
|
|
33492
|
-
|
|
33737
|
+
and27(
|
|
33493
33738
|
eq34(crawlerEventsHourly.projectId, ctx.project.id),
|
|
33494
33739
|
gte5(crawlerEventsHourly.tsHour, warnCutoff)
|
|
33495
33740
|
)
|
|
@@ -33497,7 +33742,7 @@ var recentDataCheck = {
|
|
|
33497
33742
|
);
|
|
33498
33743
|
const recentReferrals = Number(
|
|
33499
33744
|
ctx.db.select({ total: sql13`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
|
|
33500
|
-
|
|
33745
|
+
and27(
|
|
33501
33746
|
eq34(aiReferralEventsHourly.projectId, ctx.project.id),
|
|
33502
33747
|
gte5(aiReferralEventsHourly.tsHour, warnCutoff)
|
|
33503
33748
|
)
|
|
@@ -33513,7 +33758,7 @@ var recentDataCheck = {
|
|
|
33513
33758
|
}
|
|
33514
33759
|
const olderCrawlers = Number(
|
|
33515
33760
|
ctx.db.select({ total: sql13`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
|
|
33516
|
-
|
|
33761
|
+
and27(
|
|
33517
33762
|
eq34(crawlerEventsHourly.projectId, ctx.project.id),
|
|
33518
33763
|
gte5(crawlerEventsHourly.tsHour, failCutoff)
|
|
33519
33764
|
)
|
|
@@ -33521,7 +33766,7 @@ var recentDataCheck = {
|
|
|
33521
33766
|
);
|
|
33522
33767
|
const olderReferrals = Number(
|
|
33523
33768
|
ctx.db.select({ total: sql13`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
|
|
33524
|
-
|
|
33769
|
+
and27(
|
|
33525
33770
|
eq34(aiReferralEventsHourly.projectId, ctx.project.id),
|
|
33526
33771
|
gte5(aiReferralEventsHourly.tsHour, failCutoff)
|
|
33527
33772
|
)
|
|
@@ -33938,7 +34183,7 @@ async function doctorRoutes(app, opts) {
|
|
|
33938
34183
|
|
|
33939
34184
|
// ../api-routes/src/discovery/routes.ts
|
|
33940
34185
|
import crypto27 from "crypto";
|
|
33941
|
-
import { and as
|
|
34186
|
+
import { and as and28, desc as desc16, eq as eq35, gte as gte6, inArray as inArray12, isNull, or as or5 } from "drizzle-orm";
|
|
33942
34187
|
var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
|
|
33943
34188
|
async function discoveryRoutes(app, opts) {
|
|
33944
34189
|
app.post("/projects/:name/discover/run", async (request, reply) => {
|
|
@@ -33972,7 +34217,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
33972
34217
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
33973
34218
|
const ageFloorIso = new Date(Date.now() - MAX_INFLIGHT_DISCOVERY_AGE_MS).toISOString();
|
|
33974
34219
|
const decision = app.db.transaction((tx) => {
|
|
33975
|
-
const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(
|
|
34220
|
+
const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and28(
|
|
33976
34221
|
eq35(discoverySessions.projectId, project.id),
|
|
33977
34222
|
eq35(discoverySessions.icpDescription, icpDescription),
|
|
33978
34223
|
// Buyer is part of session identity: it changes the seed prompt's
|
|
@@ -34618,7 +34863,7 @@ function dedupeStrings(input) {
|
|
|
34618
34863
|
|
|
34619
34864
|
// ../api-routes/src/technical-aeo.ts
|
|
34620
34865
|
import crypto29 from "crypto";
|
|
34621
|
-
import { and as
|
|
34866
|
+
import { and as and29, asc as asc5, count, desc as desc17, eq as eq37, inArray as inArray13 } from "drizzle-orm";
|
|
34622
34867
|
var SURFACEABLE_STATUSES = [RunStatuses.completed, RunStatuses.partial];
|
|
34623
34868
|
function emptyScore(projectName) {
|
|
34624
34869
|
return {
|
|
@@ -34650,7 +34895,7 @@ function parsePositiveInt(value, fallback, max) {
|
|
|
34650
34895
|
async function technicalAeoRoutes(app, opts) {
|
|
34651
34896
|
app.get("/projects/:name/technical-aeo", async (request) => {
|
|
34652
34897
|
const project = resolveProject(app.db, request.params.name);
|
|
34653
|
-
const rows = app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq37(siteAuditSnapshots.runId, runs.id)).where(
|
|
34898
|
+
const rows = app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq37(siteAuditSnapshots.runId, runs.id)).where(and29(
|
|
34654
34899
|
eq37(siteAuditSnapshots.projectId, project.id),
|
|
34655
34900
|
eq37(runs.kind, RunKinds["site-audit"]),
|
|
34656
34901
|
inArray13(runs.status, SURFACEABLE_STATUSES),
|
|
@@ -34685,7 +34930,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
34685
34930
|
});
|
|
34686
34931
|
app.get("/projects/:name/technical-aeo/pages", async (request) => {
|
|
34687
34932
|
const project = resolveProject(app.db, request.params.name);
|
|
34688
|
-
const latest = app.db.select({ runId: siteAuditSnapshots.runId, auditedAt: siteAuditSnapshots.auditedAt }).from(siteAuditSnapshots).innerJoin(runs, eq37(siteAuditSnapshots.runId, runs.id)).where(
|
|
34933
|
+
const latest = app.db.select({ runId: siteAuditSnapshots.runId, auditedAt: siteAuditSnapshots.auditedAt }).from(siteAuditSnapshots).innerJoin(runs, eq37(siteAuditSnapshots.runId, runs.id)).where(and29(
|
|
34689
34934
|
eq37(siteAuditSnapshots.projectId, project.id),
|
|
34690
34935
|
eq37(runs.kind, RunKinds["site-audit"]),
|
|
34691
34936
|
inArray13(runs.status, SURFACEABLE_STATUSES),
|
|
@@ -34697,7 +34942,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
34697
34942
|
const statusFilter = request.query.status === "success" || request.query.status === "error" ? request.query.status : null;
|
|
34698
34943
|
const conds = [eq37(siteAuditPages.runId, latest.runId)];
|
|
34699
34944
|
if (statusFilter) conds.push(eq37(siteAuditPages.status, statusFilter));
|
|
34700
|
-
const where =
|
|
34945
|
+
const where = and29(...conds);
|
|
34701
34946
|
const totalRow = app.db.select({ value: count() }).from(siteAuditPages).where(where).get();
|
|
34702
34947
|
const total = totalRow?.value ?? 0;
|
|
34703
34948
|
const limit = parsePositiveInt(request.query.limit, 100, 500);
|
|
@@ -34721,7 +34966,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
34721
34966
|
auditedAt: siteAuditSnapshots.auditedAt,
|
|
34722
34967
|
aggregateScore: siteAuditSnapshots.aggregateScore,
|
|
34723
34968
|
pagesAudited: siteAuditSnapshots.pagesAudited
|
|
34724
|
-
}).from(siteAuditSnapshots).innerJoin(runs, eq37(siteAuditSnapshots.runId, runs.id)).where(
|
|
34969
|
+
}).from(siteAuditSnapshots).innerJoin(runs, eq37(siteAuditSnapshots.runId, runs.id)).where(and29(
|
|
34725
34970
|
eq37(siteAuditSnapshots.projectId, project.id),
|
|
34726
34971
|
eq37(runs.kind, RunKinds["site-audit"]),
|
|
34727
34972
|
inArray13(runs.status, SURFACEABLE_STATUSES),
|
|
@@ -34735,7 +34980,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
34735
34980
|
if (!parsed.success) {
|
|
34736
34981
|
throw validationError(parsed.error.issues[0]?.message ?? "Invalid site-audit request");
|
|
34737
34982
|
}
|
|
34738
|
-
const existing = app.db.select({ id: runs.id, status: runs.status }).from(runs).where(
|
|
34983
|
+
const existing = app.db.select({ id: runs.id, status: runs.status }).from(runs).where(and29(
|
|
34739
34984
|
eq37(runs.projectId, project.id),
|
|
34740
34985
|
eq37(runs.kind, RunKinds["site-audit"]),
|
|
34741
34986
|
inArray13(runs.status, [RunStatuses.queued, RunStatuses.running])
|
|
@@ -35329,7 +35574,7 @@ var IntelligenceService = class {
|
|
|
35329
35574
|
*/
|
|
35330
35575
|
analyzeAndPersist(runId, projectId) {
|
|
35331
35576
|
const recentRuns = this.db.select().from(runs).where(
|
|
35332
|
-
|
|
35577
|
+
and30(
|
|
35333
35578
|
eq38(runs.projectId, projectId),
|
|
35334
35579
|
or6(eq38(runs.status, "completed"), eq38(runs.status, "partial")),
|
|
35335
35580
|
// Defensive: RunCoordinator already skips probes before this is
|
|
@@ -35421,7 +35666,7 @@ var IntelligenceService = class {
|
|
|
35421
35666
|
}
|
|
35422
35667
|
const windowStart = runRow.startedAt ?? runRow.createdAt;
|
|
35423
35668
|
const windowEnd = runRow.finishedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
35424
|
-
const selected = this.db.select().from(gbpLocations).where(
|
|
35669
|
+
const selected = this.db.select().from(gbpLocations).where(and30(
|
|
35425
35670
|
eq38(gbpLocations.projectId, projectId),
|
|
35426
35671
|
eq38(gbpLocations.selected, true),
|
|
35427
35672
|
gte7(gbpLocations.syncedAt, windowStart),
|
|
@@ -35458,12 +35703,12 @@ var IntelligenceService = class {
|
|
|
35458
35703
|
}
|
|
35459
35704
|
/** Build the per-location signal bundle the GBP analyzer consumes. */
|
|
35460
35705
|
buildGbpLocationSignals(projectId, locationName, displayName, fallbackDate) {
|
|
35461
|
-
const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(
|
|
35462
|
-
const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(
|
|
35463
|
-
const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(
|
|
35464
|
-
const ownerRow = this.db.select({ description: gbpLocations.description }).from(gbpLocations).where(
|
|
35706
|
+
const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(and30(eq38(gbpDailyMetrics.projectId, projectId), eq38(gbpDailyMetrics.locationName, locationName))).all();
|
|
35707
|
+
const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(and30(eq38(gbpPlaceActions.projectId, projectId), eq38(gbpPlaceActions.locationName, locationName))).all();
|
|
35708
|
+
const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(and30(eq38(gbpLodgingSnapshots.projectId, projectId), eq38(gbpLodgingSnapshots.locationName, locationName))).orderBy(desc18(gbpLodgingSnapshots.syncedAt)).limit(1).get();
|
|
35709
|
+
const ownerRow = this.db.select({ description: gbpLocations.description }).from(gbpLocations).where(and30(eq38(gbpLocations.projectId, projectId), eq38(gbpLocations.locationName, locationName))).get();
|
|
35465
35710
|
const descriptionMissing = !(ownerRow?.description ?? "").trim();
|
|
35466
|
-
const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(
|
|
35711
|
+
const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(and30(eq38(gbpPlaceDetails.projectId, projectId), eq38(gbpPlaceDetails.locationName, locationName))).orderBy(desc18(gbpPlaceDetails.syncedAt)).limit(1).get();
|
|
35467
35712
|
const placesAmenities = placeRow ? extractPlaceAmenities(placeRow.attributes) : [];
|
|
35468
35713
|
const summary = buildGbpSummary({
|
|
35469
35714
|
locationName,
|
|
@@ -35499,7 +35744,7 @@ var IntelligenceService = class {
|
|
|
35499
35744
|
/** Build the month-over-month keyword series for a location from the
|
|
35500
35745
|
* accumulating gbp_keyword_monthly table (latest complete month vs prior). */
|
|
35501
35746
|
buildGbpKeywordTrend(projectId, locationName) {
|
|
35502
|
-
const rows = this.db.select({ month: gbpKeywordMonthly.month, keyword: gbpKeywordMonthly.keyword, valueCount: gbpKeywordMonthly.valueCount }).from(gbpKeywordMonthly).where(
|
|
35747
|
+
const rows = this.db.select({ month: gbpKeywordMonthly.month, keyword: gbpKeywordMonthly.keyword, valueCount: gbpKeywordMonthly.valueCount }).from(gbpKeywordMonthly).where(and30(eq38(gbpKeywordMonthly.projectId, projectId), eq38(gbpKeywordMonthly.locationName, locationName))).all();
|
|
35503
35748
|
if (rows.length === 0) return { recentMonth: null, priorMonth: null, points: [] };
|
|
35504
35749
|
const months = [...new Set(rows.map((r) => r.month))].sort().reverse();
|
|
35505
35750
|
const recentMonth = months[0] ?? null;
|
|
@@ -35530,7 +35775,7 @@ var IntelligenceService = class {
|
|
|
35530
35775
|
*/
|
|
35531
35776
|
persistGbpInsights(runId, projectId, gbpInsights, coveredLocationNames) {
|
|
35532
35777
|
const covered = new Set(coveredLocationNames);
|
|
35533
|
-
const existing = this.db.select({ id: insights.id, dismissed: insights.dismissed }).from(insights).where(
|
|
35778
|
+
const existing = this.db.select({ id: insights.id, dismissed: insights.dismissed }).from(insights).where(and30(eq38(insights.projectId, projectId), eq38(insights.provider, GBP_INSIGHT_PROVIDER))).all();
|
|
35534
35779
|
const staleIds = [];
|
|
35535
35780
|
const dismissedSlots = /* @__PURE__ */ new Set();
|
|
35536
35781
|
for (const row of existing) {
|
|
@@ -35632,7 +35877,7 @@ var IntelligenceService = class {
|
|
|
35632
35877
|
sinceTimestamp = parsed;
|
|
35633
35878
|
}
|
|
35634
35879
|
const allRuns = this.db.select().from(runs).where(
|
|
35635
|
-
|
|
35880
|
+
and30(
|
|
35636
35881
|
eq38(runs.projectId, project.id),
|
|
35637
35882
|
or6(eq38(runs.status, "completed"), eq38(runs.status, "partial")),
|
|
35638
35883
|
// Backfill must not replay probe runs as if they were real sweeps.
|
|
@@ -35809,7 +36054,7 @@ var IntelligenceService = class {
|
|
|
35809
36054
|
);
|
|
35810
36055
|
const ROWS_PER_GROUP_BUDGET = Math.max(2, locationCount);
|
|
35811
36056
|
const recentRunRows = this.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(
|
|
35812
|
-
|
|
36057
|
+
and30(
|
|
35813
36058
|
eq38(runs.projectId, projectId),
|
|
35814
36059
|
eq38(runs.kind, RunKinds["answer-visibility"]),
|
|
35815
36060
|
or6(eq38(runs.status, "completed"), eq38(runs.status, "partial")),
|
|
@@ -35832,7 +36077,7 @@ var IntelligenceService = class {
|
|
|
35832
36077
|
const haveHistory = recentRunIds.length > 0;
|
|
35833
36078
|
const priorRegressionsByPair = /* @__PURE__ */ new Map();
|
|
35834
36079
|
if (haveHistory) {
|
|
35835
|
-
const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(
|
|
36080
|
+
const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(and30(eq38(insights.type, "regression"), inArray14(insights.runId, recentRunIds))).all();
|
|
35836
36081
|
const regressionGroups = /* @__PURE__ */ new Map();
|
|
35837
36082
|
for (const row of priorRows) {
|
|
35838
36083
|
if (!row.runId) continue;
|