@ainyc/canonry 4.15.2 → 4.17.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/index-4fWsYFLp.css +1 -0
- package/assets/assets/{index-Qq_oMI-C.js → index-C5-Gvl6o.js} +81 -81
- package/assets/index.html +2 -2
- package/dist/{chunk-7SRKUAZO.js → chunk-6TWKC3DP.js} +18 -11
- package/dist/{chunk-MI33SQL6.js → chunk-PAZCY4FF.js} +73 -3
- package/dist/{chunk-ONI3TX2A.js → chunk-Q2OED5JQ.js} +13 -1
- package/dist/{chunk-IVNWS2YU.js → chunk-ZGHD3IAV.js} +223 -139
- package/dist/cli.js +53 -39
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-JYV3CO4H.js → intelligence-service-X3PQLBUV.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +7 -7
- package/assets/assets/index-C1WW21tz.css +0 -1
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
loadConfig,
|
|
6
6
|
loadConfigRaw,
|
|
7
7
|
saveConfigPatch
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-6TWKC3DP.js";
|
|
9
9
|
import {
|
|
10
10
|
DEFAULT_RUN_HISTORY_LIMIT,
|
|
11
11
|
IntelligenceService,
|
|
@@ -66,7 +66,7 @@ import {
|
|
|
66
66
|
schedules,
|
|
67
67
|
trafficSources,
|
|
68
68
|
usageCounters
|
|
69
|
-
} from "./chunk-
|
|
69
|
+
} from "./chunk-PAZCY4FF.js";
|
|
70
70
|
import {
|
|
71
71
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
72
72
|
AGENT_PROVIDER_IDS,
|
|
@@ -81,6 +81,7 @@ import {
|
|
|
81
81
|
RunKinds,
|
|
82
82
|
RunStatuses,
|
|
83
83
|
RunTriggers,
|
|
84
|
+
SchedulableRunKinds,
|
|
84
85
|
TrafficEventConfidences,
|
|
85
86
|
TrafficEventKinds,
|
|
86
87
|
TrafficEvidenceKinds,
|
|
@@ -144,6 +145,7 @@ import {
|
|
|
144
145
|
runInProgress,
|
|
145
146
|
runNotCancellable,
|
|
146
147
|
runTriggerRequestSchema,
|
|
148
|
+
schedulableRunKindSchema,
|
|
147
149
|
scheduleUpsertRequestSchema,
|
|
148
150
|
serializeRunError,
|
|
149
151
|
snapshotRequestSchema,
|
|
@@ -153,7 +155,7 @@ import {
|
|
|
153
155
|
visibilityStateFromAnswerMentioned,
|
|
154
156
|
windowCutoff,
|
|
155
157
|
wordpressEnvSchema
|
|
156
|
-
} from "./chunk-
|
|
158
|
+
} from "./chunk-Q2OED5JQ.js";
|
|
157
159
|
|
|
158
160
|
// src/telemetry.ts
|
|
159
161
|
import crypto from "crypto";
|
|
@@ -1436,7 +1438,7 @@ function loadRunDetail(app, run) {
|
|
|
1436
1438
|
|
|
1437
1439
|
// ../api-routes/src/apply.ts
|
|
1438
1440
|
import crypto10 from "crypto";
|
|
1439
|
-
import { eq as eq8 } from "drizzle-orm";
|
|
1441
|
+
import { and as and2, eq as eq8 } from "drizzle-orm";
|
|
1440
1442
|
|
|
1441
1443
|
// ../api-routes/src/schedule-utils.ts
|
|
1442
1444
|
var DAY_MAP = {
|
|
@@ -1841,8 +1843,9 @@ async function applyRoutes(app, opts) {
|
|
|
1841
1843
|
entityType: "competitor",
|
|
1842
1844
|
diff: { competitors: normalizedCompetitors }
|
|
1843
1845
|
});
|
|
1846
|
+
const AV_KIND = SchedulableRunKinds["answer-visibility"];
|
|
1844
1847
|
if (resolvedSchedule) {
|
|
1845
|
-
const existingSched = tx.select().from(schedules).where(eq8(schedules.projectId, projectId)).get();
|
|
1848
|
+
const existingSched = tx.select().from(schedules).where(and2(eq8(schedules.projectId, projectId), eq8(schedules.kind, AV_KIND))).get();
|
|
1846
1849
|
if (existingSched) {
|
|
1847
1850
|
tx.update(schedules).set({
|
|
1848
1851
|
cronExpr: resolvedSchedule.cronExpr,
|
|
@@ -1856,6 +1859,7 @@ async function applyRoutes(app, opts) {
|
|
|
1856
1859
|
tx.insert(schedules).values({
|
|
1857
1860
|
id: crypto10.randomUUID(),
|
|
1858
1861
|
projectId,
|
|
1862
|
+
kind: AV_KIND,
|
|
1859
1863
|
cronExpr: resolvedSchedule.cronExpr,
|
|
1860
1864
|
preset: resolvedSchedule.preset,
|
|
1861
1865
|
timezone: resolvedSchedule.timezone,
|
|
@@ -1867,9 +1871,9 @@ async function applyRoutes(app, opts) {
|
|
|
1867
1871
|
}
|
|
1868
1872
|
scheduleAction = "upsert";
|
|
1869
1873
|
} else if (deleteSchedule) {
|
|
1870
|
-
const existingSched = tx.select().from(schedules).where(eq8(schedules.projectId, projectId)).get();
|
|
1874
|
+
const existingSched = tx.select().from(schedules).where(and2(eq8(schedules.projectId, projectId), eq8(schedules.kind, AV_KIND))).get();
|
|
1871
1875
|
if (existingSched) {
|
|
1872
|
-
tx.delete(schedules).where(eq8(schedules.projectId, projectId)).run();
|
|
1876
|
+
tx.delete(schedules).where(and2(eq8(schedules.projectId, projectId), eq8(schedules.kind, AV_KIND))).run();
|
|
1873
1877
|
scheduleAction = "delete";
|
|
1874
1878
|
}
|
|
1875
1879
|
}
|
|
@@ -1897,7 +1901,7 @@ async function applyRoutes(app, opts) {
|
|
|
1897
1901
|
}
|
|
1898
1902
|
});
|
|
1899
1903
|
if (scheduleAction) {
|
|
1900
|
-
opts?.onScheduleUpdated?.(scheduleAction, projectId);
|
|
1904
|
+
opts?.onScheduleUpdated?.(scheduleAction, projectId, SchedulableRunKinds["answer-visibility"]);
|
|
1901
1905
|
}
|
|
1902
1906
|
if (!hasNotifications) {
|
|
1903
1907
|
opts?.onProjectUpserted?.(projectId, config.metadata.name);
|
|
@@ -2559,7 +2563,7 @@ function buildCategoryCounts(counts) {
|
|
|
2559
2563
|
}
|
|
2560
2564
|
|
|
2561
2565
|
// ../api-routes/src/intelligence.ts
|
|
2562
|
-
import { eq as eq11, desc as desc4, and as
|
|
2566
|
+
import { eq as eq11, desc as desc4, and as and3 } from "drizzle-orm";
|
|
2563
2567
|
function emptyHealthSnapshot(projectId) {
|
|
2564
2568
|
return {
|
|
2565
2569
|
id: `no-data:${projectId}`,
|
|
@@ -2610,7 +2614,7 @@ async function intelligenceRoutes(app) {
|
|
|
2610
2614
|
if (request.query.runId) {
|
|
2611
2615
|
conditions.push(eq11(insights.runId, request.query.runId));
|
|
2612
2616
|
}
|
|
2613
|
-
const rows = app.db.select().from(insights).where(conditions.length === 1 ? conditions[0] :
|
|
2617
|
+
const rows = app.db.select().from(insights).where(conditions.length === 1 ? conditions[0] : and3(...conditions)).orderBy(desc4(insights.createdAt)).all();
|
|
2614
2618
|
const showDismissed = request.query.dismissed === "true";
|
|
2615
2619
|
const result = rows.filter((r) => showDismissed || !r.dismissed).map(mapInsightRow);
|
|
2616
2620
|
return reply.send(result);
|
|
@@ -2650,7 +2654,7 @@ async function intelligenceRoutes(app) {
|
|
|
2650
2654
|
}
|
|
2651
2655
|
|
|
2652
2656
|
// ../api-routes/src/report.ts
|
|
2653
|
-
import { and as
|
|
2657
|
+
import { and as and5, desc as desc6, eq as eq13, inArray as inArray4, or as or2 } from "drizzle-orm";
|
|
2654
2658
|
|
|
2655
2659
|
// ../api-routes/src/report-renderer.ts
|
|
2656
2660
|
var COLORS = {
|
|
@@ -4725,7 +4729,7 @@ function renderReportHtml(report, opts = {}) {
|
|
|
4725
4729
|
}
|
|
4726
4730
|
|
|
4727
4731
|
// ../api-routes/src/content-data.ts
|
|
4728
|
-
import { and as
|
|
4732
|
+
import { and as and4, eq as eq12, desc as desc5, inArray as inArray3 } from "drizzle-orm";
|
|
4729
4733
|
var RECENT_RUNS_WINDOW = 5;
|
|
4730
4734
|
function loadOrchestratorInput(db, project, locationFilter = void 0) {
|
|
4731
4735
|
const projectId = project.id;
|
|
@@ -4849,7 +4853,7 @@ function listCompetitorDomains(db, projectId) {
|
|
|
4849
4853
|
}
|
|
4850
4854
|
function listRecentAnswerVisibilityRunIds(db, projectId, limit, locationFilter) {
|
|
4851
4855
|
const rows = db.select({ id: runs.id, location: runs.location }).from(runs).where(
|
|
4852
|
-
|
|
4856
|
+
and4(
|
|
4853
4857
|
eq12(runs.projectId, projectId),
|
|
4854
4858
|
eq12(runs.kind, RunKinds["answer-visibility"]),
|
|
4855
4859
|
// Queued/running/failed/cancelled runs may have partial or no
|
|
@@ -5193,7 +5197,7 @@ function buildGscSection(db, projectId, projectDisplayName, canonicalDomain, tra
|
|
|
5193
5197
|
}
|
|
5194
5198
|
function buildGaSection(db, projectId) {
|
|
5195
5199
|
const windowSummary = db.select().from(gaTrafficWindowSummaries).where(
|
|
5196
|
-
|
|
5200
|
+
and5(
|
|
5197
5201
|
eq13(gaTrafficWindowSummaries.projectId, projectId),
|
|
5198
5202
|
eq13(gaTrafficWindowSummaries.windowKey, "30d")
|
|
5199
5203
|
)
|
|
@@ -5376,7 +5380,7 @@ function buildIndexingHealth(db, projectId) {
|
|
|
5376
5380
|
return null;
|
|
5377
5381
|
}
|
|
5378
5382
|
function buildCitationsTrend(db, projectId, queryLookup, locationFilter) {
|
|
5379
|
-
const visibilityRuns = db.select().from(runs).where(
|
|
5383
|
+
const visibilityRuns = db.select().from(runs).where(and5(eq13(runs.projectId, projectId), eq13(runs.kind, RunKinds["answer-visibility"]))).all().filter((r) => locationFilter === void 0 || (r.location ?? null) === locationFilter);
|
|
5380
5384
|
const totalQueries = queryLookup.byId.size;
|
|
5381
5385
|
const points = [];
|
|
5382
5386
|
for (const run of visibilityRuns) {
|
|
@@ -5422,14 +5426,14 @@ function buildCitationsTrend(db, projectId, queryLookup, locationFilter) {
|
|
|
5422
5426
|
}
|
|
5423
5427
|
function buildInsightList(db, projectId, locationFilter) {
|
|
5424
5428
|
const recentRunIds = db.select({ id: runs.id, location: runs.location }).from(runs).where(
|
|
5425
|
-
|
|
5429
|
+
and5(
|
|
5426
5430
|
eq13(runs.projectId, projectId),
|
|
5427
5431
|
eq13(runs.kind, RunKinds["answer-visibility"]),
|
|
5428
5432
|
or2(eq13(runs.status, RunStatuses.completed), eq13(runs.status, RunStatuses.partial))
|
|
5429
5433
|
)
|
|
5430
5434
|
).orderBy(desc6(runs.createdAt)).all().filter((r) => locationFilter === void 0 || (r.location ?? null) === locationFilter).slice(0, INSIGHT_LOOKBACK_RUNS).map((r) => r.id);
|
|
5431
5435
|
if (recentRunIds.length === 0) return [];
|
|
5432
|
-
const rows = db.select().from(insights).where(
|
|
5436
|
+
const rows = db.select().from(insights).where(and5(eq13(insights.projectId, projectId), inArray4(insights.runId, recentRunIds))).orderBy(desc6(insights.createdAt)).all();
|
|
5433
5437
|
const severityRank = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
5434
5438
|
const flat = rows.filter((r) => !r.dismissed).map((r) => {
|
|
5435
5439
|
const recommendation = parseJsonColumn(r.recommendation, null);
|
|
@@ -6351,7 +6355,7 @@ function normalizeDomain2(domain) {
|
|
|
6351
6355
|
}
|
|
6352
6356
|
|
|
6353
6357
|
// ../api-routes/src/composites.ts
|
|
6354
|
-
import { eq as eq15, and as
|
|
6358
|
+
import { eq as eq15, and as and6, desc as desc7, sql as sql3, like, or as or3, inArray as inArray6 } from "drizzle-orm";
|
|
6355
6359
|
var TOP_INSIGHT_LIMIT = 5;
|
|
6356
6360
|
var SEARCH_HIT_HARD_LIMIT = 50;
|
|
6357
6361
|
var SEARCH_SNIPPET_RADIUS = 80;
|
|
@@ -6454,7 +6458,7 @@ async function compositeRoutes(app) {
|
|
|
6454
6458
|
rawResponse: querySnapshots.rawResponse,
|
|
6455
6459
|
createdAt: querySnapshots.createdAt
|
|
6456
6460
|
}).from(querySnapshots).innerJoin(queries, eq15(querySnapshots.queryId, queries.id)).where(
|
|
6457
|
-
|
|
6461
|
+
and6(
|
|
6458
6462
|
eq15(queries.projectId, project.id),
|
|
6459
6463
|
or3(
|
|
6460
6464
|
sql3`${querySnapshots.answerText} LIKE ${pattern} ESCAPE '\\'`,
|
|
@@ -6465,7 +6469,7 @@ async function compositeRoutes(app) {
|
|
|
6465
6469
|
)
|
|
6466
6470
|
).orderBy(desc7(querySnapshots.createdAt)).limit(limit + 1).all();
|
|
6467
6471
|
const insightMatches = app.db.select().from(insights).where(
|
|
6468
|
-
|
|
6472
|
+
and6(
|
|
6469
6473
|
eq15(insights.projectId, project.id),
|
|
6470
6474
|
or3(
|
|
6471
6475
|
like(insights.title, pattern),
|
|
@@ -7037,6 +7041,12 @@ var locationQueryParameter = {
|
|
|
7037
7041
|
description: "Filter by location label. Use an empty value to request locationless results.",
|
|
7038
7042
|
schema: stringSchema
|
|
7039
7043
|
};
|
|
7044
|
+
var scheduleKindQueryParameter = {
|
|
7045
|
+
name: "kind",
|
|
7046
|
+
in: "query",
|
|
7047
|
+
description: 'Schedulable run kind. Defaults to "answer-visibility" for backward compatibility.',
|
|
7048
|
+
schema: { type: "string", enum: ["answer-visibility", "traffic-sync"] }
|
|
7049
|
+
};
|
|
7040
7050
|
var reportAudienceQueryParameter = {
|
|
7041
7051
|
name: "audience",
|
|
7042
7052
|
in: "query",
|
|
@@ -7894,7 +7904,7 @@ var routeCatalog = [
|
|
|
7894
7904
|
path: "/api/v1/projects/{name}/schedule",
|
|
7895
7905
|
summary: "Create or update a schedule",
|
|
7896
7906
|
tags: ["schedules"],
|
|
7897
|
-
parameters: [nameParameter],
|
|
7907
|
+
parameters: [nameParameter, scheduleKindQueryParameter],
|
|
7898
7908
|
requestBody: {
|
|
7899
7909
|
required: true,
|
|
7900
7910
|
content: {
|
|
@@ -7902,11 +7912,13 @@ var routeCatalog = [
|
|
|
7902
7912
|
schema: {
|
|
7903
7913
|
type: "object",
|
|
7904
7914
|
properties: {
|
|
7915
|
+
kind: { type: "string", enum: ["answer-visibility", "traffic-sync"] },
|
|
7905
7916
|
preset: stringSchema,
|
|
7906
7917
|
cron: stringSchema,
|
|
7907
7918
|
timezone: stringSchema,
|
|
7908
7919
|
providers: stringArraySchema,
|
|
7909
|
-
enabled: booleanSchema
|
|
7920
|
+
enabled: booleanSchema,
|
|
7921
|
+
sourceId: stringSchema
|
|
7910
7922
|
}
|
|
7911
7923
|
}
|
|
7912
7924
|
}
|
|
@@ -7914,7 +7926,8 @@ var routeCatalog = [
|
|
|
7914
7926
|
},
|
|
7915
7927
|
responses: {
|
|
7916
7928
|
200: { description: "Schedule updated." },
|
|
7917
|
-
201: { description: "Schedule created." }
|
|
7929
|
+
201: { description: "Schedule created." },
|
|
7930
|
+
400: { description: "Invalid payload (e.g. sourceId missing for kind=traffic-sync, or providers set for kind=traffic-sync)." }
|
|
7918
7931
|
}
|
|
7919
7932
|
},
|
|
7920
7933
|
{
|
|
@@ -7922,7 +7935,7 @@ var routeCatalog = [
|
|
|
7922
7935
|
path: "/api/v1/projects/{name}/schedule",
|
|
7923
7936
|
summary: "Get a schedule",
|
|
7924
7937
|
tags: ["schedules"],
|
|
7925
|
-
parameters: [nameParameter],
|
|
7938
|
+
parameters: [nameParameter, scheduleKindQueryParameter],
|
|
7926
7939
|
responses: {
|
|
7927
7940
|
200: { description: "Schedule returned." },
|
|
7928
7941
|
404: { description: "Schedule not found." }
|
|
@@ -7933,7 +7946,7 @@ var routeCatalog = [
|
|
|
7933
7946
|
path: "/api/v1/projects/{name}/schedule",
|
|
7934
7947
|
summary: "Delete a schedule",
|
|
7935
7948
|
tags: ["schedules"],
|
|
7936
|
-
parameters: [nameParameter],
|
|
7949
|
+
parameters: [nameParameter, scheduleKindQueryParameter],
|
|
7937
7950
|
responses: {
|
|
7938
7951
|
204: { description: "Schedule deleted." },
|
|
7939
7952
|
404: { description: "Schedule not found." }
|
|
@@ -10089,7 +10102,15 @@ async function telemetryRoutes(app, opts) {
|
|
|
10089
10102
|
|
|
10090
10103
|
// ../api-routes/src/schedules.ts
|
|
10091
10104
|
import crypto11 from "crypto";
|
|
10092
|
-
import { eq as eq16 } from "drizzle-orm";
|
|
10105
|
+
import { and as and7, eq as eq16 } from "drizzle-orm";
|
|
10106
|
+
function parseKindParam(raw) {
|
|
10107
|
+
if (raw === void 0 || raw === null || raw === "") return SchedulableRunKinds["answer-visibility"];
|
|
10108
|
+
const parsed = schedulableRunKindSchema.safeParse(raw);
|
|
10109
|
+
if (!parsed.success) {
|
|
10110
|
+
throw validationError(`Invalid kind "${String(raw)}". Must be one of: ${Object.values(SchedulableRunKinds).join(", ")}`);
|
|
10111
|
+
}
|
|
10112
|
+
return parsed.data;
|
|
10113
|
+
}
|
|
10093
10114
|
async function scheduleRoutes(app, opts) {
|
|
10094
10115
|
app.put("/projects/:name/schedule", async (request, reply) => {
|
|
10095
10116
|
const project = resolveProject(app.db, request.params.name);
|
|
@@ -10102,7 +10123,22 @@ async function scheduleRoutes(app, opts) {
|
|
|
10102
10123
|
}))
|
|
10103
10124
|
});
|
|
10104
10125
|
}
|
|
10105
|
-
const
|
|
10126
|
+
const kind = parsedBody.data.kind ?? parseKindParam(request.query?.kind);
|
|
10127
|
+
const { preset, cron: cron2, timezone, providers, enabled, sourceId } = parsedBody.data;
|
|
10128
|
+
if (kind === SchedulableRunKinds["traffic-sync"]) {
|
|
10129
|
+
if (!sourceId) {
|
|
10130
|
+
throw validationError('"sourceId" is required when kind is "traffic-sync"');
|
|
10131
|
+
}
|
|
10132
|
+
const sourceRow = app.db.select().from(trafficSources).where(eq16(trafficSources.id, sourceId)).get();
|
|
10133
|
+
if (!sourceRow || sourceRow.projectId !== project.id) {
|
|
10134
|
+
throw notFound("Traffic source", sourceId);
|
|
10135
|
+
}
|
|
10136
|
+
if (providers && providers.length > 0) {
|
|
10137
|
+
throw validationError('"providers" is not valid for kind "traffic-sync"');
|
|
10138
|
+
}
|
|
10139
|
+
} else if (sourceId) {
|
|
10140
|
+
throw validationError(`"sourceId" is only valid when kind is "traffic-sync"`);
|
|
10141
|
+
}
|
|
10106
10142
|
const validNames = opts.validProviderNames ?? [];
|
|
10107
10143
|
if (validNames.length && providers?.length) {
|
|
10108
10144
|
const invalid = providers.filter((p) => !validNames.includes(p));
|
|
@@ -10132,13 +10168,14 @@ async function scheduleRoutes(app, opts) {
|
|
|
10132
10168
|
}
|
|
10133
10169
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
10134
10170
|
const enabledInt = enabled === false ? 0 : 1;
|
|
10135
|
-
const existing = app.db.select().from(schedules).where(eq16(schedules.projectId, project.id)).get();
|
|
10171
|
+
const existing = app.db.select().from(schedules).where(and7(eq16(schedules.projectId, project.id), eq16(schedules.kind, kind))).get();
|
|
10136
10172
|
if (existing) {
|
|
10137
10173
|
app.db.update(schedules).set({
|
|
10138
10174
|
cronExpr,
|
|
10139
10175
|
preset: preset ?? null,
|
|
10140
10176
|
timezone,
|
|
10141
|
-
providers: JSON.stringify(providers),
|
|
10177
|
+
providers: JSON.stringify(providers ?? []),
|
|
10178
|
+
sourceId: sourceId ?? null,
|
|
10142
10179
|
enabled: enabledInt,
|
|
10143
10180
|
updatedAt: now
|
|
10144
10181
|
}).where(eq16(schedules.id, existing.id)).run();
|
|
@@ -10146,11 +10183,13 @@ async function scheduleRoutes(app, opts) {
|
|
|
10146
10183
|
app.db.insert(schedules).values({
|
|
10147
10184
|
id: crypto11.randomUUID(),
|
|
10148
10185
|
projectId: project.id,
|
|
10186
|
+
kind,
|
|
10149
10187
|
cronExpr,
|
|
10150
10188
|
preset: preset ?? null,
|
|
10151
10189
|
timezone,
|
|
10152
10190
|
enabled: enabledInt,
|
|
10153
|
-
providers: JSON.stringify(providers),
|
|
10191
|
+
providers: JSON.stringify(providers ?? []),
|
|
10192
|
+
sourceId: sourceId ?? null,
|
|
10154
10193
|
createdAt: now,
|
|
10155
10194
|
updatedAt: now
|
|
10156
10195
|
}).run();
|
|
@@ -10160,25 +10199,27 @@ async function scheduleRoutes(app, opts) {
|
|
|
10160
10199
|
actor: "api",
|
|
10161
10200
|
action: existing ? "schedule.updated" : "schedule.created",
|
|
10162
10201
|
entityType: "schedule",
|
|
10163
|
-
diff: { cronExpr, preset, timezone, providers }
|
|
10202
|
+
diff: { kind, cronExpr, preset, timezone, providers, sourceId }
|
|
10164
10203
|
});
|
|
10165
|
-
opts.onScheduleUpdated?.("upsert", project.id);
|
|
10166
|
-
const schedule = app.db.select().from(schedules).where(eq16(schedules.projectId, project.id)).get();
|
|
10204
|
+
opts.onScheduleUpdated?.("upsert", project.id, kind);
|
|
10205
|
+
const schedule = app.db.select().from(schedules).where(and7(eq16(schedules.projectId, project.id), eq16(schedules.kind, kind))).get();
|
|
10167
10206
|
return reply.status(existing ? 200 : 201).send(formatSchedule(schedule));
|
|
10168
10207
|
});
|
|
10169
10208
|
app.get("/projects/:name/schedule", async (request, reply) => {
|
|
10170
10209
|
const project = resolveProject(app.db, request.params.name);
|
|
10171
|
-
const
|
|
10210
|
+
const kind = parseKindParam(request.query?.kind);
|
|
10211
|
+
const schedule = app.db.select().from(schedules).where(and7(eq16(schedules.projectId, project.id), eq16(schedules.kind, kind))).get();
|
|
10172
10212
|
if (!schedule) {
|
|
10173
|
-
throw notFound("Schedule", request.params.name);
|
|
10213
|
+
throw notFound("Schedule", `${request.params.name} (kind=${kind})`);
|
|
10174
10214
|
}
|
|
10175
10215
|
return reply.send(formatSchedule(schedule));
|
|
10176
10216
|
});
|
|
10177
10217
|
app.delete("/projects/:name/schedule", async (request, reply) => {
|
|
10178
10218
|
const project = resolveProject(app.db, request.params.name);
|
|
10179
|
-
const
|
|
10219
|
+
const kind = parseKindParam(request.query?.kind);
|
|
10220
|
+
const schedule = app.db.select().from(schedules).where(and7(eq16(schedules.projectId, project.id), eq16(schedules.kind, kind))).get();
|
|
10180
10221
|
if (!schedule) {
|
|
10181
|
-
throw notFound("Schedule", request.params.name);
|
|
10222
|
+
throw notFound("Schedule", `${request.params.name} (kind=${kind})`);
|
|
10182
10223
|
}
|
|
10183
10224
|
app.db.delete(schedules).where(eq16(schedules.id, schedule.id)).run();
|
|
10184
10225
|
writeAuditLog(app.db, {
|
|
@@ -10186,9 +10227,10 @@ async function scheduleRoutes(app, opts) {
|
|
|
10186
10227
|
actor: "api",
|
|
10187
10228
|
action: "schedule.deleted",
|
|
10188
10229
|
entityType: "schedule",
|
|
10189
|
-
entityId: schedule.id
|
|
10230
|
+
entityId: schedule.id,
|
|
10231
|
+
diff: { kind }
|
|
10190
10232
|
});
|
|
10191
|
-
opts.onScheduleUpdated?.("delete", project.id);
|
|
10233
|
+
opts.onScheduleUpdated?.("delete", project.id, kind);
|
|
10192
10234
|
return reply.status(204).send();
|
|
10193
10235
|
});
|
|
10194
10236
|
}
|
|
@@ -10196,11 +10238,13 @@ function formatSchedule(row) {
|
|
|
10196
10238
|
return {
|
|
10197
10239
|
id: row.id,
|
|
10198
10240
|
projectId: row.projectId,
|
|
10241
|
+
kind: row.kind,
|
|
10199
10242
|
cronExpr: row.cronExpr,
|
|
10200
10243
|
preset: row.preset,
|
|
10201
10244
|
timezone: row.timezone,
|
|
10202
10245
|
enabled: row.enabled === 1,
|
|
10203
10246
|
providers: parseJsonColumn(row.providers, []),
|
|
10247
|
+
sourceId: row.sourceId,
|
|
10204
10248
|
lastRunAt: row.lastRunAt,
|
|
10205
10249
|
nextRunAt: row.nextRunAt,
|
|
10206
10250
|
createdAt: row.createdAt,
|
|
@@ -10329,7 +10373,7 @@ function formatNotification(row) {
|
|
|
10329
10373
|
|
|
10330
10374
|
// ../api-routes/src/google.ts
|
|
10331
10375
|
import crypto14 from "crypto";
|
|
10332
|
-
import { eq as eq18, and as
|
|
10376
|
+
import { eq as eq18, and as and8, desc as desc8, sql as sql4 } from "drizzle-orm";
|
|
10333
10377
|
|
|
10334
10378
|
// ../integration-google/src/constants.ts
|
|
10335
10379
|
var GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
@@ -11544,7 +11588,7 @@ async function googleRoutes(app, opts) {
|
|
|
11544
11588
|
if (endDate) conditions.push(sql4`${gscSearchData.date} <= ${endDate}`);
|
|
11545
11589
|
if (query) conditions.push(sql4`${gscSearchData.query} LIKE ${"%" + query + "%"}`);
|
|
11546
11590
|
if (page) conditions.push(sql4`${gscSearchData.page} LIKE ${"%" + page + "%"}`);
|
|
11547
|
-
const rows = app.db.select().from(gscSearchData).where(
|
|
11591
|
+
const rows = app.db.select().from(gscSearchData).where(and8(...conditions)).orderBy(desc8(gscSearchData.date)).limit(parseInt(limit ?? "500", 10)).all();
|
|
11548
11592
|
return rows.map((r) => ({
|
|
11549
11593
|
date: r.date,
|
|
11550
11594
|
query: r.query,
|
|
@@ -11618,7 +11662,7 @@ async function googleRoutes(app, opts) {
|
|
|
11618
11662
|
const { url, limit } = request.query;
|
|
11619
11663
|
const conditions = [eq18(gscUrlInspections.projectId, project.id)];
|
|
11620
11664
|
if (url) conditions.push(eq18(gscUrlInspections.url, url));
|
|
11621
|
-
const rows = app.db.select().from(gscUrlInspections).where(
|
|
11665
|
+
const rows = app.db.select().from(gscUrlInspections).where(and8(...conditions)).orderBy(desc8(gscUrlInspections.inspectedAt)).limit(parseInt(limit ?? "100", 10)).all();
|
|
11622
11666
|
return rows.map((r) => ({
|
|
11623
11667
|
id: r.id,
|
|
11624
11668
|
url: r.url,
|
|
@@ -11970,7 +12014,7 @@ async function googleRoutes(app, opts) {
|
|
|
11970
12014
|
|
|
11971
12015
|
// ../api-routes/src/bing.ts
|
|
11972
12016
|
import crypto15 from "crypto";
|
|
11973
|
-
import { eq as eq19, and as
|
|
12017
|
+
import { eq as eq19, and as and9, desc as desc9 } from "drizzle-orm";
|
|
11974
12018
|
|
|
11975
12019
|
// ../integration-bing/src/constants.ts
|
|
11976
12020
|
var BING_WMT_API_BASE = "https://ssl.bing.com/webmaster/api.svc/json";
|
|
@@ -12384,7 +12428,7 @@ async function bingRoutes(app, opts) {
|
|
|
12384
12428
|
requireConnectionStore();
|
|
12385
12429
|
const project = resolveProject(app.db, request.params.name);
|
|
12386
12430
|
const { url, limit } = request.query;
|
|
12387
|
-
const whereClause = url ?
|
|
12431
|
+
const whereClause = url ? and9(eq19(bingUrlInspections.projectId, project.id), eq19(bingUrlInspections.url, url)) : eq19(bingUrlInspections.projectId, project.id);
|
|
12388
12432
|
const filtered = app.db.select().from(bingUrlInspections).where(whereClause).orderBy(desc9(bingUrlInspections.inspectedAt)).limit(Math.max(1, Math.min(parseInt(limit ?? "100", 10) || 100, 1e3))).all();
|
|
12389
12433
|
return filtered.map((r) => ({
|
|
12390
12434
|
id: r.id,
|
|
@@ -12616,7 +12660,7 @@ async function bingRoutes(app, opts) {
|
|
|
12616
12660
|
import fs from "fs";
|
|
12617
12661
|
import path from "path";
|
|
12618
12662
|
import os2 from "os";
|
|
12619
|
-
import { eq as eq20, and as
|
|
12663
|
+
import { eq as eq20, and as and10 } from "drizzle-orm";
|
|
12620
12664
|
function getScreenshotDir() {
|
|
12621
12665
|
return path.join(os2.homedir(), ".canonry", "screenshots");
|
|
12622
12666
|
}
|
|
@@ -12689,7 +12733,7 @@ async function cdpRoutes(app, opts) {
|
|
|
12689
12733
|
async (request, reply) => {
|
|
12690
12734
|
const project = resolveProject(app.db, request.params.name);
|
|
12691
12735
|
const { runId } = request.params;
|
|
12692
|
-
const run = app.db.select().from(runs).where(
|
|
12736
|
+
const run = app.db.select().from(runs).where(and10(eq20(runs.id, runId), eq20(runs.projectId, project.id))).get();
|
|
12693
12737
|
if (!run) {
|
|
12694
12738
|
const err = notFound("Run", runId);
|
|
12695
12739
|
return reply.code(err.statusCode).send(err.toJSON());
|
|
@@ -12786,7 +12830,7 @@ async function cdpRoutes(app, opts) {
|
|
|
12786
12830
|
|
|
12787
12831
|
// ../api-routes/src/ga.ts
|
|
12788
12832
|
import crypto16 from "crypto";
|
|
12789
|
-
import { eq as eq21, desc as desc10, and as
|
|
12833
|
+
import { eq as eq21, desc as desc10, and as and11, sql as sql5 } from "drizzle-orm";
|
|
12790
12834
|
function gaLog(level, action, ctx) {
|
|
12791
12835
|
const entry = { ts: (/* @__PURE__ */ new Date()).toISOString(), level, module: "GA4Routes", action, ...ctx };
|
|
12792
12836
|
const stream = level === "error" ? process.stderr : process.stdout;
|
|
@@ -13081,7 +13125,7 @@ async function ga4Routes(app, opts) {
|
|
|
13081
13125
|
app.db.transaction((tx) => {
|
|
13082
13126
|
if (syncTraffic) {
|
|
13083
13127
|
tx.delete(gaTrafficSnapshots).where(
|
|
13084
|
-
|
|
13128
|
+
and11(
|
|
13085
13129
|
eq21(gaTrafficSnapshots.projectId, project.id),
|
|
13086
13130
|
sql5`${gaTrafficSnapshots.date} >= ${summary.periodStart}`,
|
|
13087
13131
|
sql5`${gaTrafficSnapshots.date} <= ${summary.periodEnd}`
|
|
@@ -13105,7 +13149,7 @@ async function ga4Routes(app, opts) {
|
|
|
13105
13149
|
}
|
|
13106
13150
|
if (syncAi) {
|
|
13107
13151
|
tx.delete(gaAiReferrals).where(
|
|
13108
|
-
|
|
13152
|
+
and11(
|
|
13109
13153
|
eq21(gaAiReferrals.projectId, project.id),
|
|
13110
13154
|
sql5`${gaAiReferrals.date} >= ${summary.periodStart}`,
|
|
13111
13155
|
sql5`${gaAiReferrals.date} <= ${summary.periodEnd}`
|
|
@@ -13131,7 +13175,7 @@ async function ga4Routes(app, opts) {
|
|
|
13131
13175
|
}
|
|
13132
13176
|
if (syncSocial) {
|
|
13133
13177
|
tx.delete(gaSocialReferrals).where(
|
|
13134
|
-
|
|
13178
|
+
and11(
|
|
13135
13179
|
eq21(gaSocialReferrals.projectId, project.id),
|
|
13136
13180
|
sql5`${gaSocialReferrals.date} >= ${summary.periodStart}`,
|
|
13137
13181
|
sql5`${gaSocialReferrals.date} <= ${summary.periodEnd}`
|
|
@@ -13235,7 +13279,7 @@ async function ga4Routes(app, opts) {
|
|
|
13235
13279
|
totalDirectSessions: gaTrafficWindowSummaries.totalDirectSessions,
|
|
13236
13280
|
totalUsers: gaTrafficWindowSummaries.totalUsers
|
|
13237
13281
|
}).from(gaTrafficWindowSummaries).where(
|
|
13238
|
-
|
|
13282
|
+
and11(
|
|
13239
13283
|
eq21(gaTrafficWindowSummaries.projectId, project.id),
|
|
13240
13284
|
eq21(gaTrafficWindowSummaries.windowKey, window)
|
|
13241
13285
|
)
|
|
@@ -13244,7 +13288,7 @@ async function ga4Routes(app, opts) {
|
|
|
13244
13288
|
totalSessions: sql5`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)`,
|
|
13245
13289
|
totalOrganicSessions: sql5`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)`,
|
|
13246
13290
|
totalUsers: sql5`COALESCE(SUM(${gaTrafficSnapshots.users}), 0)`
|
|
13247
|
-
}).from(gaTrafficSnapshots).where(
|
|
13291
|
+
}).from(gaTrafficSnapshots).where(and11(...snapshotConditions)).get() : null;
|
|
13248
13292
|
const summaryRow = cutoffDate ? windowSummaryRow ?? snapshotTotalsRow : app.db.select({
|
|
13249
13293
|
totalSessions: gaTrafficSummaries.totalSessions,
|
|
13250
13294
|
totalOrganicSessions: gaTrafficSummaries.totalOrganicSessions,
|
|
@@ -13252,7 +13296,7 @@ async function ga4Routes(app, opts) {
|
|
|
13252
13296
|
}).from(gaTrafficSummaries).where(eq21(gaTrafficSummaries.projectId, project.id)).get();
|
|
13253
13297
|
const directTotalRow = windowSummaryRow ? { totalDirectSessions: windowSummaryRow.totalDirectSessions } : app.db.select({
|
|
13254
13298
|
totalDirectSessions: sql5`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)`
|
|
13255
|
-
}).from(gaTrafficSnapshots).where(
|
|
13299
|
+
}).from(gaTrafficSnapshots).where(and11(...snapshotConditions)).get();
|
|
13256
13300
|
const summaryMeta = app.db.select({
|
|
13257
13301
|
periodStart: gaTrafficSummaries.periodStart,
|
|
13258
13302
|
periodEnd: gaTrafficSummaries.periodEnd
|
|
@@ -13263,14 +13307,14 @@ async function ga4Routes(app, opts) {
|
|
|
13263
13307
|
organicSessions: sql5`SUM(${gaTrafficSnapshots.organicSessions})`,
|
|
13264
13308
|
directSessions: sql5`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)`,
|
|
13265
13309
|
users: sql5`SUM(${gaTrafficSnapshots.users})`
|
|
13266
|
-
}).from(gaTrafficSnapshots).where(
|
|
13310
|
+
}).from(gaTrafficSnapshots).where(and11(...snapshotConditions)).groupBy(sql5`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql5`SUM(${gaTrafficSnapshots.sessions}) DESC`).limit(limit).all();
|
|
13267
13311
|
const aiReferralRows = app.db.select({
|
|
13268
13312
|
source: gaAiReferrals.source,
|
|
13269
13313
|
medium: gaAiReferrals.medium,
|
|
13270
13314
|
sourceDimension: gaAiReferrals.sourceDimension,
|
|
13271
13315
|
sessions: sql5`SUM(${gaAiReferrals.sessions})`,
|
|
13272
13316
|
users: sql5`SUM(${gaAiReferrals.users})`
|
|
13273
|
-
}).from(gaAiReferrals).where(
|
|
13317
|
+
}).from(gaAiReferrals).where(and11(...aiConditions)).groupBy(gaAiReferrals.source, gaAiReferrals.medium, gaAiReferrals.sourceDimension).all();
|
|
13274
13318
|
const aiReferralLandingPageRows = app.db.select({
|
|
13275
13319
|
source: gaAiReferrals.source,
|
|
13276
13320
|
medium: gaAiReferrals.medium,
|
|
@@ -13278,7 +13322,7 @@ async function ga4Routes(app, opts) {
|
|
|
13278
13322
|
landingPage: sql5`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`,
|
|
13279
13323
|
sessions: sql5`SUM(${gaAiReferrals.sessions})`,
|
|
13280
13324
|
users: sql5`SUM(${gaAiReferrals.users})`
|
|
13281
|
-
}).from(gaAiReferrals).where(
|
|
13325
|
+
}).from(gaAiReferrals).where(and11(...aiConditions)).groupBy(
|
|
13282
13326
|
gaAiReferrals.source,
|
|
13283
13327
|
gaAiReferrals.medium,
|
|
13284
13328
|
gaAiReferrals.sourceDimension,
|
|
@@ -13315,7 +13359,7 @@ async function ga4Routes(app, opts) {
|
|
|
13315
13359
|
channelGroup: gaAiReferrals.channelGroup,
|
|
13316
13360
|
sessions: sql5`COALESCE(SUM(${gaAiReferrals.sessions}), 0)`,
|
|
13317
13361
|
users: sql5`COALESCE(SUM(${gaAiReferrals.users}), 0)`
|
|
13318
|
-
}).from(gaAiReferrals).where(
|
|
13362
|
+
}).from(gaAiReferrals).where(and11(...aiConditions, eq21(gaAiReferrals.sourceDimension, "session"))).groupBy(gaAiReferrals.channelGroup).all();
|
|
13319
13363
|
const aiSessionsByChannelGroup = /* @__PURE__ */ new Map();
|
|
13320
13364
|
let aiBySessionUsers = 0;
|
|
13321
13365
|
for (const row of aiBySessionRows) {
|
|
@@ -13329,11 +13373,11 @@ async function ga4Routes(app, opts) {
|
|
|
13329
13373
|
channelGroup: gaSocialReferrals.channelGroup,
|
|
13330
13374
|
sessions: sql5`SUM(${gaSocialReferrals.sessions})`,
|
|
13331
13375
|
users: sql5`SUM(${gaSocialReferrals.users})`
|
|
13332
|
-
}).from(gaSocialReferrals).where(
|
|
13376
|
+
}).from(gaSocialReferrals).where(and11(...socialConditions)).groupBy(gaSocialReferrals.source, gaSocialReferrals.medium, gaSocialReferrals.channelGroup).orderBy(sql5`SUM(${gaSocialReferrals.sessions}) DESC`).all();
|
|
13333
13377
|
const socialTotals = app.db.select({
|
|
13334
13378
|
sessions: sql5`SUM(${gaSocialReferrals.sessions})`,
|
|
13335
13379
|
users: sql5`SUM(${gaSocialReferrals.users})`
|
|
13336
|
-
}).from(gaSocialReferrals).where(
|
|
13380
|
+
}).from(gaSocialReferrals).where(and11(...socialConditions)).get();
|
|
13337
13381
|
const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq21(gaTrafficSummaries.projectId, project.id)).orderBy(desc10(gaTrafficSummaries.syncedAt)).limit(1).get();
|
|
13338
13382
|
const total = summaryRow?.totalSessions ?? 0;
|
|
13339
13383
|
const totalDirectSessions = directTotalRow?.totalDirectSessions ?? 0;
|
|
@@ -13424,7 +13468,7 @@ async function ga4Routes(app, opts) {
|
|
|
13424
13468
|
sourceDimension: gaAiReferrals.sourceDimension,
|
|
13425
13469
|
sessions: sql5`SUM(${gaAiReferrals.sessions})`,
|
|
13426
13470
|
users: sql5`SUM(${gaAiReferrals.users})`
|
|
13427
|
-
}).from(gaAiReferrals).where(
|
|
13471
|
+
}).from(gaAiReferrals).where(and11(...conditions)).groupBy(
|
|
13428
13472
|
gaAiReferrals.date,
|
|
13429
13473
|
gaAiReferrals.source,
|
|
13430
13474
|
gaAiReferrals.medium,
|
|
@@ -13446,7 +13490,7 @@ async function ga4Routes(app, opts) {
|
|
|
13446
13490
|
channelGroup: gaSocialReferrals.channelGroup,
|
|
13447
13491
|
sessions: gaSocialReferrals.sessions,
|
|
13448
13492
|
users: gaSocialReferrals.users
|
|
13449
|
-
}).from(gaSocialReferrals).where(
|
|
13493
|
+
}).from(gaSocialReferrals).where(and11(...conditions)).orderBy(gaSocialReferrals.date).all();
|
|
13450
13494
|
return rows;
|
|
13451
13495
|
});
|
|
13452
13496
|
app.get("/projects/:name/ga/social-referral-trend", async (request, _reply) => {
|
|
@@ -13459,7 +13503,7 @@ async function ga4Routes(app, opts) {
|
|
|
13459
13503
|
d.setDate(d.getDate() - n);
|
|
13460
13504
|
return fmt(d);
|
|
13461
13505
|
};
|
|
13462
|
-
const sumSocial = (from, to) => app.db.select({ sessions: sql5`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(
|
|
13506
|
+
const sumSocial = (from, to) => app.db.select({ sessions: sql5`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and11(
|
|
13463
13507
|
eq21(gaSocialReferrals.projectId, project.id),
|
|
13464
13508
|
sql5`${gaSocialReferrals.date} >= ${from}`,
|
|
13465
13509
|
sql5`${gaSocialReferrals.date} < ${to}`
|
|
@@ -13472,7 +13516,7 @@ async function ga4Routes(app, opts) {
|
|
|
13472
13516
|
const sourceCurrent = app.db.select({
|
|
13473
13517
|
source: gaSocialReferrals.source,
|
|
13474
13518
|
sessions: sql5`SUM(${gaSocialReferrals.sessions})`
|
|
13475
|
-
}).from(gaSocialReferrals).where(
|
|
13519
|
+
}).from(gaSocialReferrals).where(and11(
|
|
13476
13520
|
eq21(gaSocialReferrals.projectId, project.id),
|
|
13477
13521
|
sql5`${gaSocialReferrals.date} >= ${daysAgo2(7)}`,
|
|
13478
13522
|
sql5`${gaSocialReferrals.date} < ${fmt(today)}`
|
|
@@ -13480,7 +13524,7 @@ async function ga4Routes(app, opts) {
|
|
|
13480
13524
|
const sourcePrev = app.db.select({
|
|
13481
13525
|
source: gaSocialReferrals.source,
|
|
13482
13526
|
sessions: sql5`SUM(${gaSocialReferrals.sessions})`
|
|
13483
|
-
}).from(gaSocialReferrals).where(
|
|
13527
|
+
}).from(gaSocialReferrals).where(and11(
|
|
13484
13528
|
eq21(gaSocialReferrals.projectId, project.id),
|
|
13485
13529
|
sql5`${gaSocialReferrals.date} >= ${daysAgo2(14)}`,
|
|
13486
13530
|
sql5`${gaSocialReferrals.date} < ${daysAgo2(7)}`
|
|
@@ -13522,16 +13566,16 @@ async function ga4Routes(app, opts) {
|
|
|
13522
13566
|
return fmt(d);
|
|
13523
13567
|
};
|
|
13524
13568
|
const pct = (cur, prev) => prev === 0 ? null : Math.round((cur - prev) / prev * 100);
|
|
13525
|
-
const sumTotal = (from, to) => app.db.select({ sessions: sql5`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)` }).from(gaTrafficSnapshots).where(
|
|
13526
|
-
const sumOrganic = (from, to) => app.db.select({ sessions: sql5`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)` }).from(gaTrafficSnapshots).where(
|
|
13527
|
-
const sumDirect = (from, to) => app.db.select({ sessions: sql5`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)` }).from(gaTrafficSnapshots).where(
|
|
13528
|
-
const sumAi = (from, to) => app.db.select({ sessions: sql5`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(
|
|
13569
|
+
const sumTotal = (from, to) => app.db.select({ sessions: sql5`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)` }).from(gaTrafficSnapshots).where(and11(eq21(gaTrafficSnapshots.projectId, project.id), sql5`${gaTrafficSnapshots.date} >= ${from}`, sql5`${gaTrafficSnapshots.date} < ${to}`)).get();
|
|
13570
|
+
const sumOrganic = (from, to) => app.db.select({ sessions: sql5`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)` }).from(gaTrafficSnapshots).where(and11(eq21(gaTrafficSnapshots.projectId, project.id), sql5`${gaTrafficSnapshots.date} >= ${from}`, sql5`${gaTrafficSnapshots.date} < ${to}`)).get();
|
|
13571
|
+
const sumDirect = (from, to) => app.db.select({ sessions: sql5`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)` }).from(gaTrafficSnapshots).where(and11(eq21(gaTrafficSnapshots.projectId, project.id), sql5`${gaTrafficSnapshots.date} >= ${from}`, sql5`${gaTrafficSnapshots.date} < ${to}`)).get();
|
|
13572
|
+
const sumAi = (from, to) => app.db.select({ sessions: sql5`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and11(
|
|
13529
13573
|
eq21(gaAiReferrals.projectId, project.id),
|
|
13530
13574
|
sql5`${gaAiReferrals.date} >= ${from}`,
|
|
13531
13575
|
sql5`${gaAiReferrals.date} < ${to}`,
|
|
13532
13576
|
eq21(gaAiReferrals.sourceDimension, "session")
|
|
13533
13577
|
)).get();
|
|
13534
|
-
const sumSocial = (from, to) => app.db.select({ sessions: sql5`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(
|
|
13578
|
+
const sumSocial = (from, to) => app.db.select({ sessions: sql5`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and11(eq21(gaSocialReferrals.projectId, project.id), sql5`${gaSocialReferrals.date} >= ${from}`, sql5`${gaSocialReferrals.date} < ${to}`)).get();
|
|
13535
13579
|
const todayStr = fmt(today);
|
|
13536
13580
|
const buildTrend = (sum) => {
|
|
13537
13581
|
const c7 = sum(daysAgo2(7), todayStr)?.sessions ?? 0;
|
|
@@ -13540,13 +13584,13 @@ async function ga4Routes(app, opts) {
|
|
|
13540
13584
|
const p30 = sum(daysAgo2(60), daysAgo2(30))?.sessions ?? 0;
|
|
13541
13585
|
return { sessions7d: c7, sessionsPrev7d: p7, trend7dPct: pct(c7, p7), sessions30d: c30, sessionsPrev30d: p30, trend30dPct: pct(c30, p30) };
|
|
13542
13586
|
};
|
|
13543
|
-
const aiSourceCurrent = app.db.select({ source: gaAiReferrals.source, sessions: sql5`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(
|
|
13587
|
+
const aiSourceCurrent = app.db.select({ source: gaAiReferrals.source, sessions: sql5`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and11(
|
|
13544
13588
|
eq21(gaAiReferrals.projectId, project.id),
|
|
13545
13589
|
sql5`${gaAiReferrals.date} >= ${daysAgo2(7)}`,
|
|
13546
13590
|
sql5`${gaAiReferrals.date} < ${todayStr}`,
|
|
13547
13591
|
eq21(gaAiReferrals.sourceDimension, "session")
|
|
13548
13592
|
)).groupBy(gaAiReferrals.source).all();
|
|
13549
|
-
const aiSourcePrev = app.db.select({ source: gaAiReferrals.source, sessions: sql5`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(
|
|
13593
|
+
const aiSourcePrev = app.db.select({ source: gaAiReferrals.source, sessions: sql5`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and11(
|
|
13550
13594
|
eq21(gaAiReferrals.projectId, project.id),
|
|
13551
13595
|
sql5`${gaAiReferrals.date} >= ${daysAgo2(14)}`,
|
|
13552
13596
|
sql5`${gaAiReferrals.date} < ${daysAgo2(7)}`,
|
|
@@ -13566,8 +13610,8 @@ async function ga4Routes(app, opts) {
|
|
|
13566
13610
|
}
|
|
13567
13611
|
return mover;
|
|
13568
13612
|
};
|
|
13569
|
-
const socialSourceCurrent = app.db.select({ source: gaSocialReferrals.source, sessions: sql5`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(
|
|
13570
|
-
const socialSourcePrev = app.db.select({ source: gaSocialReferrals.source, sessions: sql5`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(
|
|
13613
|
+
const socialSourceCurrent = app.db.select({ source: gaSocialReferrals.source, sessions: sql5`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and11(eq21(gaSocialReferrals.projectId, project.id), sql5`${gaSocialReferrals.date} >= ${daysAgo2(7)}`, sql5`${gaSocialReferrals.date} < ${todayStr}`)).groupBy(gaSocialReferrals.source).all();
|
|
13614
|
+
const socialSourcePrev = app.db.select({ source: gaSocialReferrals.source, sessions: sql5`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and11(eq21(gaSocialReferrals.projectId, project.id), sql5`${gaSocialReferrals.date} >= ${daysAgo2(14)}`, sql5`${gaSocialReferrals.date} < ${daysAgo2(7)}`)).groupBy(gaSocialReferrals.source).all();
|
|
13571
13615
|
return {
|
|
13572
13616
|
total: buildTrend(sumTotal),
|
|
13573
13617
|
organic: buildTrend(sumOrganic),
|
|
@@ -13589,7 +13633,7 @@ async function ga4Routes(app, opts) {
|
|
|
13589
13633
|
sessions: sql5`SUM(${gaTrafficSnapshots.sessions})`,
|
|
13590
13634
|
organicSessions: sql5`SUM(${gaTrafficSnapshots.organicSessions})`,
|
|
13591
13635
|
users: sql5`SUM(${gaTrafficSnapshots.users})`
|
|
13592
|
-
}).from(gaTrafficSnapshots).where(
|
|
13636
|
+
}).from(gaTrafficSnapshots).where(and11(...conditions)).groupBy(gaTrafficSnapshots.date).orderBy(gaTrafficSnapshots.date).all();
|
|
13593
13637
|
return rows.map((r) => ({
|
|
13594
13638
|
date: r.date,
|
|
13595
13639
|
sessions: r.sessions ?? 0,
|
|
@@ -15242,7 +15286,7 @@ async function wordpressRoutes(app, opts) {
|
|
|
15242
15286
|
|
|
15243
15287
|
// ../api-routes/src/backlinks.ts
|
|
15244
15288
|
import crypto18 from "crypto";
|
|
15245
|
-
import { and as
|
|
15289
|
+
import { and as and13, asc as asc2, desc as desc11, eq as eq22, sql as sql6 } from "drizzle-orm";
|
|
15246
15290
|
|
|
15247
15291
|
// ../integration-commoncrawl/src/constants.ts
|
|
15248
15292
|
import os3 from "os";
|
|
@@ -15639,7 +15683,7 @@ function pruneCachedRelease(release, opts = {}) {
|
|
|
15639
15683
|
}
|
|
15640
15684
|
|
|
15641
15685
|
// ../api-routes/src/backlinks-filter.ts
|
|
15642
|
-
import { and as
|
|
15686
|
+
import { and as and12, ne, notLike } from "drizzle-orm";
|
|
15643
15687
|
var BACKLINK_FILTER_PATTERNS = [
|
|
15644
15688
|
"*.google.com",
|
|
15645
15689
|
"*.googleusercontent.com",
|
|
@@ -15662,7 +15706,7 @@ function backlinkCrawlerExclusionClause() {
|
|
|
15662
15706
|
conditions.push(ne(backlinkDomains.linkingDomain, pattern));
|
|
15663
15707
|
}
|
|
15664
15708
|
}
|
|
15665
|
-
const combined =
|
|
15709
|
+
const combined = and12(...conditions);
|
|
15666
15710
|
if (!combined) throw new Error("BACKLINK_FILTER_PATTERNS is unexpectedly empty");
|
|
15667
15711
|
return combined;
|
|
15668
15712
|
}
|
|
@@ -15723,7 +15767,7 @@ function mapRunRow(row) {
|
|
|
15723
15767
|
};
|
|
15724
15768
|
}
|
|
15725
15769
|
function latestSummaryForProject(db, projectId, release) {
|
|
15726
|
-
const condition = release ?
|
|
15770
|
+
const condition = release ? and13(eq22(backlinkSummaries.projectId, projectId), eq22(backlinkSummaries.release, release)) : eq22(backlinkSummaries.projectId, projectId);
|
|
15727
15771
|
return db.select().from(backlinkSummaries).where(condition).orderBy(desc11(backlinkSummaries.queriedAt)).limit(1).get();
|
|
15728
15772
|
}
|
|
15729
15773
|
function parseExcludeCrawlers(value) {
|
|
@@ -15732,11 +15776,11 @@ function parseExcludeCrawlers(value) {
|
|
|
15732
15776
|
return lower === "1" || lower === "true" || lower === "yes";
|
|
15733
15777
|
}
|
|
15734
15778
|
function computeFilteredSummary(db, base) {
|
|
15735
|
-
const baseDomainCondition =
|
|
15779
|
+
const baseDomainCondition = and13(
|
|
15736
15780
|
eq22(backlinkDomains.projectId, base.projectId),
|
|
15737
15781
|
eq22(backlinkDomains.release, base.release)
|
|
15738
15782
|
);
|
|
15739
|
-
const filteredCondition =
|
|
15783
|
+
const filteredCondition = and13(baseDomainCondition, backlinkCrawlerExclusionClause());
|
|
15740
15784
|
const unfilteredAgg = db.select({
|
|
15741
15785
|
count: sql6`count(*)`,
|
|
15742
15786
|
total: sql6`coalesce(sum(${backlinkDomains.numHosts}), 0)`
|
|
@@ -15912,11 +15956,11 @@ async function backlinksRoutes(app, opts) {
|
|
|
15912
15956
|
const limit = Math.min(Math.max(parseInt(request.query.limit ?? "50", 10) || 50, 1), 500);
|
|
15913
15957
|
const offset = Math.max(parseInt(request.query.offset ?? "0", 10) || 0, 0);
|
|
15914
15958
|
const excludeCrawlers = parseExcludeCrawlers(request.query.excludeCrawlers);
|
|
15915
|
-
const baseDomainCondition =
|
|
15959
|
+
const baseDomainCondition = and13(
|
|
15916
15960
|
eq22(backlinkDomains.projectId, project.id),
|
|
15917
15961
|
eq22(backlinkDomains.release, targetRelease)
|
|
15918
15962
|
);
|
|
15919
|
-
const domainCondition = excludeCrawlers ?
|
|
15963
|
+
const domainCondition = excludeCrawlers ? and13(baseDomainCondition, backlinkCrawlerExclusionClause()) : baseDomainCondition;
|
|
15920
15964
|
const totalRow = app.db.select({ count: sql6`count(*)` }).from(backlinkDomains).where(domainCondition).get();
|
|
15921
15965
|
const rows = app.db.select({
|
|
15922
15966
|
linkingDomain: backlinkDomains.linkingDomain,
|
|
@@ -15952,7 +15996,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
15952
15996
|
|
|
15953
15997
|
// ../api-routes/src/traffic.ts
|
|
15954
15998
|
import crypto20 from "crypto";
|
|
15955
|
-
import { and as
|
|
15999
|
+
import { and as and14, desc as desc12, eq as eq23, gte, lte, sql as sql7 } from "drizzle-orm";
|
|
15956
16000
|
|
|
15957
16001
|
// ../integration-cloud-run/src/auth.ts
|
|
15958
16002
|
import crypto19 from "crypto";
|
|
@@ -16909,25 +16953,25 @@ async function trafficRoutes(app, opts) {
|
|
|
16909
16953
|
});
|
|
16910
16954
|
function buildSourceDetail(projectId, row, since) {
|
|
16911
16955
|
const crawlerTotals = app.db.select({ total: sql7`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
|
|
16912
|
-
|
|
16956
|
+
and14(
|
|
16913
16957
|
eq23(crawlerEventsHourly.sourceId, row.id),
|
|
16914
16958
|
gte(crawlerEventsHourly.tsHour, since)
|
|
16915
16959
|
)
|
|
16916
16960
|
).get();
|
|
16917
16961
|
const aiTotals = app.db.select({ total: sql7`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
|
|
16918
|
-
|
|
16962
|
+
and14(
|
|
16919
16963
|
eq23(aiReferralEventsHourly.sourceId, row.id),
|
|
16920
16964
|
gte(aiReferralEventsHourly.tsHour, since)
|
|
16921
16965
|
)
|
|
16922
16966
|
).get();
|
|
16923
16967
|
const sampleTotals = app.db.select({ total: sql7`COUNT(*)` }).from(rawEventSamples).where(
|
|
16924
|
-
|
|
16968
|
+
and14(
|
|
16925
16969
|
eq23(rawEventSamples.sourceId, row.id),
|
|
16926
16970
|
gte(rawEventSamples.ts, since)
|
|
16927
16971
|
)
|
|
16928
16972
|
).get();
|
|
16929
16973
|
const latestRun = app.db.select().from(runs).where(
|
|
16930
|
-
|
|
16974
|
+
and14(
|
|
16931
16975
|
eq23(runs.projectId, projectId),
|
|
16932
16976
|
eq23(runs.kind, RunKinds["traffic-sync"]),
|
|
16933
16977
|
eq23(runs.sourceId, row.id)
|
|
@@ -17021,7 +17065,7 @@ async function trafficRoutes(app, opts) {
|
|
|
17021
17065
|
lte(crawlerEventsHourly.tsHour, untilIso)
|
|
17022
17066
|
];
|
|
17023
17067
|
if (sourceIdParam) crawlerFilters.push(eq23(crawlerEventsHourly.sourceId, sourceIdParam));
|
|
17024
|
-
const crawlerWhere =
|
|
17068
|
+
const crawlerWhere = and14(...crawlerFilters);
|
|
17025
17069
|
const total = app.db.select({ total: sql7`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(crawlerWhere).get();
|
|
17026
17070
|
crawlerTotal = Number(total?.total ?? 0);
|
|
17027
17071
|
const rows = app.db.select().from(crawlerEventsHourly).where(crawlerWhere).orderBy(desc12(crawlerEventsHourly.tsHour)).limit(limit).all();
|
|
@@ -17046,7 +17090,7 @@ async function trafficRoutes(app, opts) {
|
|
|
17046
17090
|
lte(aiReferralEventsHourly.tsHour, untilIso)
|
|
17047
17091
|
];
|
|
17048
17092
|
if (sourceIdParam) aiFilters.push(eq23(aiReferralEventsHourly.sourceId, sourceIdParam));
|
|
17049
|
-
const aiWhere =
|
|
17093
|
+
const aiWhere = and14(...aiFilters);
|
|
17050
17094
|
const total = app.db.select({ total: sql7`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(aiWhere).get();
|
|
17051
17095
|
aiReferralTotal = Number(total?.total ?? 0);
|
|
17052
17096
|
const rows = app.db.select().from(aiReferralEventsHourly).where(aiWhere).orderBy(desc12(aiReferralEventsHourly.tsHour)).limit(limit).all();
|
|
@@ -20449,7 +20493,7 @@ import crypto22 from "crypto";
|
|
|
20449
20493
|
import fs7 from "fs";
|
|
20450
20494
|
import path9 from "path";
|
|
20451
20495
|
import os5 from "os";
|
|
20452
|
-
import { and as
|
|
20496
|
+
import { and as and15, eq as eq24, inArray as inArray7, sql as sql8 } from "drizzle-orm";
|
|
20453
20497
|
|
|
20454
20498
|
// src/run-telemetry.ts
|
|
20455
20499
|
import crypto21 from "crypto";
|
|
@@ -20828,7 +20872,7 @@ var JobRunner = class {
|
|
|
20828
20872
|
throw new Error(`Run ${runId} is not executable from status '${existingRun.status}'`);
|
|
20829
20873
|
}
|
|
20830
20874
|
if (existingRun.status === "queued") {
|
|
20831
|
-
this.db.update(runs).set({ status: "running", startedAt: now }).where(
|
|
20875
|
+
this.db.update(runs).set({ status: "running", startedAt: now }).where(and15(eq24(runs.id, runId), eq24(runs.status, "queued"))).run();
|
|
20832
20876
|
}
|
|
20833
20877
|
this.throwIfRunCancelled(runId);
|
|
20834
20878
|
const project = this.db.select().from(projects).where(eq24(projects.id, projectId)).get();
|
|
@@ -21187,7 +21231,7 @@ function buildPhases(input) {
|
|
|
21187
21231
|
|
|
21188
21232
|
// src/gsc-sync.ts
|
|
21189
21233
|
import crypto23 from "crypto";
|
|
21190
|
-
import { eq as eq25, and as
|
|
21234
|
+
import { eq as eq25, and as and16, sql as sql9 } from "drizzle-orm";
|
|
21191
21235
|
var log2 = createLogger("GscSync");
|
|
21192
21236
|
function formatDate3(d) {
|
|
21193
21237
|
return d.toISOString().split("T")[0];
|
|
@@ -21239,7 +21283,7 @@ async function executeGscSync(db, runId, projectId, opts) {
|
|
|
21239
21283
|
});
|
|
21240
21284
|
log2.info("fetch.complete", { runId, projectId, rowCount: rows.length });
|
|
21241
21285
|
db.delete(gscSearchData).where(
|
|
21242
|
-
|
|
21286
|
+
and16(
|
|
21243
21287
|
eq25(gscSearchData.projectId, projectId),
|
|
21244
21288
|
sql9`${gscSearchData.date} >= ${startDate}`,
|
|
21245
21289
|
sql9`${gscSearchData.date} <= ${endDate}`
|
|
@@ -21328,7 +21372,7 @@ async function executeGscSync(db, runId, projectId, opts) {
|
|
|
21328
21372
|
}
|
|
21329
21373
|
}
|
|
21330
21374
|
const snapshotDate = formatDate3(/* @__PURE__ */ new Date());
|
|
21331
|
-
db.delete(gscCoverageSnapshots).where(
|
|
21375
|
+
db.delete(gscCoverageSnapshots).where(and16(eq25(gscCoverageSnapshots.projectId, projectId), eq25(gscCoverageSnapshots.date, snapshotDate))).run();
|
|
21332
21376
|
db.insert(gscCoverageSnapshots).values({
|
|
21333
21377
|
id: crypto23.randomUUID(),
|
|
21334
21378
|
projectId,
|
|
@@ -21351,7 +21395,7 @@ async function executeGscSync(db, runId, projectId, opts) {
|
|
|
21351
21395
|
|
|
21352
21396
|
// src/gsc-inspect-sitemap.ts
|
|
21353
21397
|
import crypto24 from "crypto";
|
|
21354
|
-
import { eq as eq26, and as
|
|
21398
|
+
import { eq as eq26, and as and17 } from "drizzle-orm";
|
|
21355
21399
|
|
|
21356
21400
|
// src/sitemap-parser.ts
|
|
21357
21401
|
var log3 = createLogger("SitemapParser");
|
|
@@ -21567,7 +21611,7 @@ async function executeInspectSitemap(db, runId, projectId, opts) {
|
|
|
21567
21611
|
}
|
|
21568
21612
|
}
|
|
21569
21613
|
const snapshotDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
21570
|
-
db.delete(gscCoverageSnapshots).where(
|
|
21614
|
+
db.delete(gscCoverageSnapshots).where(and17(eq26(gscCoverageSnapshots.projectId, projectId), eq26(gscCoverageSnapshots.date, snapshotDate))).run();
|
|
21571
21615
|
db.insert(gscCoverageSnapshots).values({
|
|
21572
21616
|
id: crypto24.randomUUID(),
|
|
21573
21617
|
projectId,
|
|
@@ -21778,7 +21822,7 @@ async function executeBingInspectSitemap(db, runId, projectId, opts) {
|
|
|
21778
21822
|
// src/commoncrawl-sync.ts
|
|
21779
21823
|
import crypto26 from "crypto";
|
|
21780
21824
|
import path10 from "path";
|
|
21781
|
-
import { and as
|
|
21825
|
+
import { and as and18, eq as eq28, sql as sql10 } from "drizzle-orm";
|
|
21782
21826
|
var log6 = createLogger("CommonCrawlSync");
|
|
21783
21827
|
var INSERT_CHUNK_SIZE = 1e4;
|
|
21784
21828
|
function defaultDeps() {
|
|
@@ -21969,7 +22013,7 @@ function computeSummary(rows) {
|
|
|
21969
22013
|
// src/backlink-extract.ts
|
|
21970
22014
|
import crypto27 from "crypto";
|
|
21971
22015
|
import fs8 from "fs";
|
|
21972
|
-
import { and as
|
|
22016
|
+
import { and as and19, desc as desc14, eq as eq29 } from "drizzle-orm";
|
|
21973
22017
|
var log7 = createLogger("BacklinkExtract");
|
|
21974
22018
|
function defaultDeps2() {
|
|
21975
22019
|
return {
|
|
@@ -22015,7 +22059,7 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
|
|
|
22015
22059
|
const targetDomain = project.canonicalDomain;
|
|
22016
22060
|
db.transaction((tx) => {
|
|
22017
22061
|
tx.delete(backlinkDomains).where(
|
|
22018
|
-
|
|
22062
|
+
and19(eq29(backlinkDomains.projectId, projectId), eq29(backlinkDomains.release, release))
|
|
22019
22063
|
).run();
|
|
22020
22064
|
if (rows.length > 0) {
|
|
22021
22065
|
const values = rows.map((r) => ({
|
|
@@ -22137,8 +22181,11 @@ var ProviderRegistry = class {
|
|
|
22137
22181
|
|
|
22138
22182
|
// src/scheduler.ts
|
|
22139
22183
|
import cron from "node-cron";
|
|
22140
|
-
import { eq as eq30 } from "drizzle-orm";
|
|
22184
|
+
import { and as and20, eq as eq30 } from "drizzle-orm";
|
|
22141
22185
|
var log8 = createLogger("Scheduler");
|
|
22186
|
+
function taskKey(projectId, kind) {
|
|
22187
|
+
return `${projectId}::${kind}`;
|
|
22188
|
+
}
|
|
22142
22189
|
var Scheduler = class {
|
|
22143
22190
|
db;
|
|
22144
22191
|
callbacks;
|
|
@@ -22154,78 +22201,110 @@ var Scheduler = class {
|
|
|
22154
22201
|
const missedRunAt = schedule.nextRunAt;
|
|
22155
22202
|
this.registerCronTask(schedule);
|
|
22156
22203
|
if (missedRunAt && new Date(missedRunAt) < /* @__PURE__ */ new Date()) {
|
|
22157
|
-
log8.info("run.catch-up", { projectId: schedule.projectId, missedRunAt });
|
|
22158
|
-
this.triggerRun(schedule.id, schedule.projectId);
|
|
22204
|
+
log8.info("run.catch-up", { projectId: schedule.projectId, kind: schedule.kind, missedRunAt });
|
|
22205
|
+
this.triggerRun(schedule.id, schedule.projectId, schedule.kind);
|
|
22159
22206
|
}
|
|
22160
22207
|
}
|
|
22161
22208
|
log8.info("started", { scheduleCount: allSchedules.length });
|
|
22162
22209
|
}
|
|
22163
22210
|
/** Stop all cron tasks for graceful shutdown. */
|
|
22164
22211
|
stop() {
|
|
22165
|
-
for (const [
|
|
22166
|
-
this.stopTask(
|
|
22212
|
+
for (const [key, task] of this.tasks) {
|
|
22213
|
+
this.stopTask(key, task, "Stopped");
|
|
22167
22214
|
}
|
|
22168
22215
|
this.tasks.clear();
|
|
22169
22216
|
}
|
|
22170
|
-
/**
|
|
22171
|
-
|
|
22172
|
-
|
|
22217
|
+
/**
|
|
22218
|
+
* Add or update a cron registration at runtime (called when schedule API
|
|
22219
|
+
* is used). Keyed by `(projectId, kind)` so a project's traffic-sync and
|
|
22220
|
+
* answer-visibility schedules can coexist independently.
|
|
22221
|
+
*/
|
|
22222
|
+
upsert(projectId, kind) {
|
|
22223
|
+
const key = taskKey(projectId, kind);
|
|
22224
|
+
const existing = this.tasks.get(key);
|
|
22173
22225
|
if (existing) {
|
|
22174
|
-
this.stopTask(
|
|
22175
|
-
this.tasks.delete(
|
|
22226
|
+
this.stopTask(key, existing, "Stopped");
|
|
22227
|
+
this.tasks.delete(key);
|
|
22176
22228
|
}
|
|
22177
|
-
const schedule = this.db.select().from(schedules).where(eq30(schedules.projectId, projectId)).get();
|
|
22229
|
+
const schedule = this.db.select().from(schedules).where(and20(eq30(schedules.projectId, projectId), eq30(schedules.kind, kind))).get();
|
|
22178
22230
|
if (schedule && schedule.enabled === 1) {
|
|
22179
22231
|
this.registerCronTask(schedule);
|
|
22180
22232
|
}
|
|
22181
22233
|
}
|
|
22182
|
-
/** Remove a cron registration (
|
|
22183
|
-
remove(projectId) {
|
|
22184
|
-
const
|
|
22234
|
+
/** Remove a single cron registration (kind-scoped). */
|
|
22235
|
+
remove(projectId, kind) {
|
|
22236
|
+
const key = taskKey(projectId, kind);
|
|
22237
|
+
const existing = this.tasks.get(key);
|
|
22185
22238
|
if (existing) {
|
|
22186
|
-
this.stopTask(
|
|
22187
|
-
this.tasks.delete(
|
|
22239
|
+
this.stopTask(key, existing, "Removed");
|
|
22240
|
+
this.tasks.delete(key);
|
|
22188
22241
|
}
|
|
22189
22242
|
}
|
|
22190
|
-
|
|
22243
|
+
/** Remove ALL cron registrations for a project (used on project delete). */
|
|
22244
|
+
removeAllForProject(projectId) {
|
|
22245
|
+
for (const kind of Object.values(SchedulableRunKinds)) {
|
|
22246
|
+
this.remove(projectId, kind);
|
|
22247
|
+
}
|
|
22248
|
+
}
|
|
22249
|
+
stopTask(key, task, verb) {
|
|
22191
22250
|
task.stop();
|
|
22192
22251
|
task.destroy();
|
|
22193
|
-
log8.info(`task.${verb.toLowerCase()}`, {
|
|
22252
|
+
log8.info(`task.${verb.toLowerCase()}`, { key });
|
|
22194
22253
|
}
|
|
22195
22254
|
registerCronTask(schedule) {
|
|
22196
22255
|
const { id: scheduleId, projectId, cronExpr, timezone } = schedule;
|
|
22256
|
+
const kind = schedule.kind;
|
|
22197
22257
|
if (!cron.validate(cronExpr)) {
|
|
22198
|
-
log8.error("cron.invalid", { projectId, cronExpr });
|
|
22258
|
+
log8.error("cron.invalid", { projectId, kind, cronExpr });
|
|
22199
22259
|
return;
|
|
22200
22260
|
}
|
|
22201
22261
|
const task = cron.schedule(cronExpr, () => {
|
|
22202
|
-
this.triggerRun(scheduleId, projectId);
|
|
22262
|
+
this.triggerRun(scheduleId, projectId, kind);
|
|
22203
22263
|
}, {
|
|
22204
22264
|
timezone
|
|
22205
22265
|
});
|
|
22206
|
-
this.tasks.set(projectId, task);
|
|
22266
|
+
this.tasks.set(taskKey(projectId, kind), task);
|
|
22207
22267
|
this.db.update(schedules).set({
|
|
22208
22268
|
nextRunAt: task.getNextRun()?.toISOString() ?? null,
|
|
22209
22269
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
22210
22270
|
}).where(eq30(schedules.id, scheduleId)).run();
|
|
22211
22271
|
const label = schedule.preset ?? cronExpr;
|
|
22212
|
-
log8.info("cron.registered", { projectId, schedule: label, timezone });
|
|
22272
|
+
log8.info("cron.registered", { projectId, kind, schedule: label, timezone });
|
|
22213
22273
|
}
|
|
22214
|
-
triggerRun(scheduleId, projectId) {
|
|
22274
|
+
triggerRun(scheduleId, projectId, kind) {
|
|
22215
22275
|
try {
|
|
22216
22276
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
22217
22277
|
const currentSchedule = this.db.select().from(schedules).where(eq30(schedules.id, scheduleId)).get();
|
|
22218
22278
|
if (!currentSchedule || currentSchedule.enabled !== 1) {
|
|
22219
|
-
log8.warn("schedule.stale", { scheduleId, projectId, msg: "schedule no longer exists or is disabled" });
|
|
22220
|
-
this.remove(projectId);
|
|
22279
|
+
log8.warn("schedule.stale", { scheduleId, projectId, kind, msg: "schedule no longer exists or is disabled" });
|
|
22280
|
+
this.remove(projectId, kind);
|
|
22221
22281
|
return;
|
|
22222
22282
|
}
|
|
22223
|
-
const task = this.tasks.get(projectId);
|
|
22283
|
+
const task = this.tasks.get(taskKey(projectId, kind));
|
|
22224
22284
|
const nextRunAt = task?.getNextRun()?.toISOString() ?? null;
|
|
22225
22285
|
const project = this.db.select().from(projects).where(eq30(projects.id, projectId)).get();
|
|
22226
22286
|
if (!project) {
|
|
22227
|
-
log8.error("project.not-found", { projectId, msg: "skipping scheduled run" });
|
|
22228
|
-
this.remove(projectId);
|
|
22287
|
+
log8.error("project.not-found", { projectId, kind, msg: "skipping scheduled run" });
|
|
22288
|
+
this.remove(projectId, kind);
|
|
22289
|
+
return;
|
|
22290
|
+
}
|
|
22291
|
+
if (kind === SchedulableRunKinds["traffic-sync"]) {
|
|
22292
|
+
const sourceId = currentSchedule.sourceId;
|
|
22293
|
+
if (!sourceId) {
|
|
22294
|
+
log8.warn("traffic-sync.missing-source", { scheduleId, projectId });
|
|
22295
|
+
return;
|
|
22296
|
+
}
|
|
22297
|
+
if (!this.callbacks.onTrafficSyncRequested) {
|
|
22298
|
+
log8.warn("traffic-sync.no-callback", { scheduleId, projectId, msg: "host did not register onTrafficSyncRequested" });
|
|
22299
|
+
return;
|
|
22300
|
+
}
|
|
22301
|
+
this.db.update(schedules).set({
|
|
22302
|
+
lastRunAt: now,
|
|
22303
|
+
nextRunAt,
|
|
22304
|
+
updatedAt: now
|
|
22305
|
+
}).where(eq30(schedules.id, currentSchedule.id)).run();
|
|
22306
|
+
log8.info("traffic-sync.triggered", { projectName: project.name, sourceId });
|
|
22307
|
+
this.callbacks.onTrafficSyncRequested(project.name, sourceId);
|
|
22229
22308
|
return;
|
|
22230
22309
|
}
|
|
22231
22310
|
const projectLocations = parseJsonColumn(project.locations, []);
|
|
@@ -22265,13 +22344,13 @@ var Scheduler = class {
|
|
|
22265
22344
|
log8.info("run.triggered", { runId, projectName: project.name, providers: providers ?? "all" });
|
|
22266
22345
|
this.callbacks.onRunCreated(runId, projectId, providers, resolvedLocation);
|
|
22267
22346
|
} catch (err) {
|
|
22268
|
-
log8.error("trigger.error", { scheduleId, projectId, error: err instanceof Error ? err.message : String(err) });
|
|
22347
|
+
log8.error("trigger.error", { scheduleId, projectId, kind, error: err instanceof Error ? err.message : String(err) });
|
|
22269
22348
|
}
|
|
22270
22349
|
}
|
|
22271
22350
|
};
|
|
22272
22351
|
|
|
22273
22352
|
// src/notifier.ts
|
|
22274
|
-
import { eq as eq31, desc as desc15, and as
|
|
22353
|
+
import { eq as eq31, desc as desc15, and as and21, or as or4 } from "drizzle-orm";
|
|
22275
22354
|
import crypto28 from "crypto";
|
|
22276
22355
|
var log9 = createLogger("Notifier");
|
|
22277
22356
|
var Notifier = class {
|
|
@@ -22377,7 +22456,7 @@ var Notifier = class {
|
|
|
22377
22456
|
}
|
|
22378
22457
|
computeTransitions(runId, projectId) {
|
|
22379
22458
|
const recentRuns = this.db.select().from(runs).where(
|
|
22380
|
-
|
|
22459
|
+
and21(
|
|
22381
22460
|
eq31(runs.projectId, projectId),
|
|
22382
22461
|
or4(eq31(runs.status, "completed"), eq31(runs.status, "partial"))
|
|
22383
22462
|
)
|
|
@@ -22863,7 +22942,7 @@ function resolveSessionProviderAndModel(config, opts) {
|
|
|
22863
22942
|
|
|
22864
22943
|
// src/agent/memory-store.ts
|
|
22865
22944
|
import crypto29 from "crypto";
|
|
22866
|
-
import { and as
|
|
22945
|
+
import { and as and22, desc as desc16, eq as eq32, like as like2, sql as sql11 } from "drizzle-orm";
|
|
22867
22946
|
var COMPACTION_KEY_PREFIX = "compaction:";
|
|
22868
22947
|
var COMPACTION_NOTES_PER_SESSION = 3;
|
|
22869
22948
|
function rowToDto2(row) {
|
|
@@ -22908,12 +22987,12 @@ function upsertMemoryEntry(db, args) {
|
|
|
22908
22987
|
updatedAt: now
|
|
22909
22988
|
}
|
|
22910
22989
|
}).run();
|
|
22911
|
-
const row = db.select().from(agentMemory).where(
|
|
22990
|
+
const row = db.select().from(agentMemory).where(and22(eq32(agentMemory.projectId, args.projectId), eq32(agentMemory.key, args.key))).get();
|
|
22912
22991
|
if (!row) throw new Error("memory upsert produced no row");
|
|
22913
22992
|
return rowToDto2(row);
|
|
22914
22993
|
}
|
|
22915
22994
|
function deleteMemoryEntry(db, projectId, key) {
|
|
22916
|
-
const result = db.delete(agentMemory).where(
|
|
22995
|
+
const result = db.delete(agentMemory).where(and22(eq32(agentMemory.projectId, projectId), eq32(agentMemory.key, key))).run();
|
|
22917
22996
|
const changes = result.changes ?? 0;
|
|
22918
22997
|
return changes > 0;
|
|
22919
22998
|
}
|
|
@@ -22942,7 +23021,7 @@ function writeCompactionNote(db, args) {
|
|
|
22942
23021
|
}).run();
|
|
22943
23022
|
const sessionPrefix = `${COMPACTION_KEY_PREFIX}${args.sessionId}:`;
|
|
22944
23023
|
const existing = tx.select({ id: agentMemory.id, updatedAt: agentMemory.updatedAt }).from(agentMemory).where(
|
|
22945
|
-
|
|
23024
|
+
and22(
|
|
22946
23025
|
eq32(agentMemory.projectId, args.projectId),
|
|
22947
23026
|
like2(agentMemory.key, `${sessionPrefix}%`)
|
|
22948
23027
|
)
|
|
@@ -22951,7 +23030,7 @@ function writeCompactionNote(db, args) {
|
|
|
22951
23030
|
if (stale.length > 0) {
|
|
22952
23031
|
tx.delete(agentMemory).where(sql11`${agentMemory.id} IN (${sql11.join(stale.map((s) => sql11`${s}`), sql11`, `)})`).run();
|
|
22953
23032
|
}
|
|
22954
|
-
const row = tx.select().from(agentMemory).where(
|
|
23033
|
+
const row = tx.select().from(agentMemory).where(and22(eq32(agentMemory.projectId, args.projectId), eq32(agentMemory.key, key))).get();
|
|
22955
23034
|
if (row) inserted = rowToDto2(row);
|
|
22956
23035
|
});
|
|
22957
23036
|
if (!inserted) throw new Error("compaction note write produced no row");
|
|
@@ -24618,6 +24697,11 @@ async function createServer(opts) {
|
|
|
24618
24697
|
jobRunner.executeRun(runId, projectId, providers2, location).catch((err) => {
|
|
24619
24698
|
app.log.error({ runId, err }, "Scheduled job runner failed");
|
|
24620
24699
|
});
|
|
24700
|
+
},
|
|
24701
|
+
onTrafficSyncRequested: (projectName, sourceId) => {
|
|
24702
|
+
aeroClient.trafficSync(projectName, sourceId).catch((err) => {
|
|
24703
|
+
app.log.error({ projectName, sourceId, err: err instanceof Error ? err.message : String(err) }, "Scheduled traffic sync failed");
|
|
24704
|
+
});
|
|
24621
24705
|
}
|
|
24622
24706
|
});
|
|
24623
24707
|
const providerSummary = API_ADAPTERS.map((adapter) => ({
|
|
@@ -25149,12 +25233,12 @@ async function createServer(opts) {
|
|
|
25149
25233
|
return null;
|
|
25150
25234
|
}
|
|
25151
25235
|
},
|
|
25152
|
-
onScheduleUpdated: (action, projectId) => {
|
|
25153
|
-
if (action === "upsert") scheduler.upsert(projectId);
|
|
25154
|
-
if (action === "delete") scheduler.remove(projectId);
|
|
25236
|
+
onScheduleUpdated: (action, projectId, kind) => {
|
|
25237
|
+
if (action === "upsert") scheduler.upsert(projectId, kind);
|
|
25238
|
+
if (action === "delete") scheduler.remove(projectId, kind);
|
|
25155
25239
|
},
|
|
25156
25240
|
onProjectDeleted: (projectId) => {
|
|
25157
|
-
scheduler.
|
|
25241
|
+
scheduler.removeAllForProject(projectId);
|
|
25158
25242
|
},
|
|
25159
25243
|
getTelemetryStatus: () => {
|
|
25160
25244
|
const enabled = isTelemetryEnabled();
|