Remove plugin data query projection
This commit is contained in:
@@ -273,29 +273,6 @@
|
||||
"resultSchemaRef": "schemas/bridge/queries/scum-player-profile.result.schema.json",
|
||||
"sqlRef": "sql/scum-db-v57/users.sql",
|
||||
"pollIntervalSeconds": 3,
|
||||
"rowTarget": {
|
||||
"collection": "scum_users",
|
||||
"upsertKeys": [
|
||||
"steamId"
|
||||
],
|
||||
"writeMode": "merge",
|
||||
"columnMappings": {
|
||||
"userProfileId": "userProfileId",
|
||||
"steamId": "steamId",
|
||||
"gamePlayerId": "gamePlayerId",
|
||||
"displayName": "displayName",
|
||||
"squadId": "squadId",
|
||||
"squadName": "squadName",
|
||||
"famePoints": "famePoints",
|
||||
"normalBalance": "normalBalance",
|
||||
"goldBalance": "goldBalance",
|
||||
"x": "x",
|
||||
"y": "y",
|
||||
"z": "z",
|
||||
"lastLoginTime": "lastLoginTime",
|
||||
"lastSaveTime": "lastSaveTime"
|
||||
}
|
||||
},
|
||||
"maxRows": 500,
|
||||
"timeoutSeconds": 15
|
||||
},
|
||||
@@ -310,25 +287,6 @@
|
||||
"resultSchemaRef": "schemas/bridge/queries/scum-squads.result.schema.json",
|
||||
"sqlRef": "sql/scum-db-v57/squads.sql",
|
||||
"pollIntervalSeconds": 1800,
|
||||
"rowTarget": {
|
||||
"collection": "scum_squads",
|
||||
"upsertKeys": [
|
||||
"squadId"
|
||||
],
|
||||
"writeMode": "replace",
|
||||
"columnMappings": {
|
||||
"squadId": "squadId",
|
||||
"name": "name",
|
||||
"leaderProfileId": "leaderProfileId",
|
||||
"leaderPlayerId": "leaderPlayerId",
|
||||
"memberCount": "memberCount",
|
||||
"score": "score",
|
||||
"memberLimit": "memberLimit",
|
||||
"message": "message",
|
||||
"info": "info",
|
||||
"lastMemberLoginTime": "lastMemberLoginTime"
|
||||
}
|
||||
},
|
||||
"maxRows": 500,
|
||||
"timeoutSeconds": 15
|
||||
},
|
||||
@@ -343,23 +301,6 @@
|
||||
"resultSchemaRef": "schemas/bridge/queries/scum-squad-members.result.schema.json",
|
||||
"sqlRef": "sql/scum-db-v57/squad-members.sql",
|
||||
"pollIntervalSeconds": 1800,
|
||||
"rowTarget": {
|
||||
"collection": "scum_squad_members",
|
||||
"upsertKeys": [
|
||||
"squadId",
|
||||
"steamId"
|
||||
],
|
||||
"writeMode": "replace",
|
||||
"columnMappings": {
|
||||
"squadId": "squadId",
|
||||
"userProfileId": "userProfileId",
|
||||
"gamePlayerId": "gamePlayerId",
|
||||
"steamId": "steamId",
|
||||
"displayName": "displayName",
|
||||
"rank": "rank",
|
||||
"isLeader": "isLeader"
|
||||
}
|
||||
},
|
||||
"maxRows": 500,
|
||||
"timeoutSeconds": 15
|
||||
},
|
||||
@@ -374,24 +315,6 @@
|
||||
"resultSchemaRef": "schemas/bridge/queries/scum-vehicles.result.schema.json",
|
||||
"sqlRef": "sql/scum-db-v57/vehicles.sql",
|
||||
"pollIntervalSeconds": 3,
|
||||
"rowTarget": {
|
||||
"collection": "scum_vehicles",
|
||||
"upsertKeys": [
|
||||
"vehicleId"
|
||||
],
|
||||
"writeMode": "replace",
|
||||
"columnMappings": {
|
||||
"vehicleId": "vehicleId",
|
||||
"entityId": "entityId",
|
||||
"className": "className",
|
||||
"label": "label",
|
||||
"x": "x",
|
||||
"y": "y",
|
||||
"z": "z",
|
||||
"lastAccessTime": "lastAccessTime",
|
||||
"isFunctional": "isFunctional"
|
||||
}
|
||||
},
|
||||
"maxRows": 500,
|
||||
"timeoutSeconds": 15
|
||||
},
|
||||
@@ -406,28 +329,6 @@
|
||||
"resultSchemaRef": "schemas/bridge/queries/scum-flags.result.schema.json",
|
||||
"sqlRef": "sql/scum-db-v57/flags.sql",
|
||||
"pollIntervalSeconds": 1800,
|
||||
"rowTarget": {
|
||||
"collection": "scum_flags",
|
||||
"upsertKeys": [
|
||||
"flagId"
|
||||
],
|
||||
"writeMode": "replace",
|
||||
"columnMappings": {
|
||||
"flagId": "flagId",
|
||||
"entityId": "entityId",
|
||||
"baseId": "baseId",
|
||||
"ownerProfileId": "ownerProfileId",
|
||||
"ownerPlayerId": "ownerPlayerId",
|
||||
"ownerSquadId": "ownerSquadId",
|
||||
"ownerSquadName": "ownerSquadName",
|
||||
"overtakerProfileId": "overtakerProfileId",
|
||||
"overtakeEndTime": "overtakeEndTime",
|
||||
"ownershipConfidence": "ownershipConfidence",
|
||||
"x": "x",
|
||||
"y": "y",
|
||||
"z": "z"
|
||||
}
|
||||
},
|
||||
"maxRows": 500,
|
||||
"timeoutSeconds": 15
|
||||
},
|
||||
@@ -442,27 +343,6 @@
|
||||
"resultSchemaRef": "schemas/bridge/queries/scum-positions.result.schema.json",
|
||||
"sqlRef": "sql/scum-db-v57/map-points.sql",
|
||||
"pollIntervalSeconds": 3,
|
||||
"rowTarget": {
|
||||
"collection": "scum_map_points",
|
||||
"upsertKeys": [
|
||||
"subjectType",
|
||||
"subjectId"
|
||||
],
|
||||
"writeMode": "replace",
|
||||
"columnMappings": {
|
||||
"subjectType": "subjectType",
|
||||
"subjectId": "subjectId",
|
||||
"userProfileId": "userProfileId",
|
||||
"gamePlayerId": "gamePlayerId",
|
||||
"vehicleId": "vehicleId",
|
||||
"entityId": "entityId",
|
||||
"baseId": "baseId",
|
||||
"x": "x",
|
||||
"y": "y",
|
||||
"z": "z",
|
||||
"observedAt": "observedAt"
|
||||
}
|
||||
},
|
||||
"maxRows": 500,
|
||||
"timeoutSeconds": 15
|
||||
},
|
||||
@@ -477,25 +357,6 @@
|
||||
"resultSchemaRef": "schemas/bridge/queries/scum-tasks.result.schema.json",
|
||||
"sqlRef": "sql/scum-db-v57/tasks.sql",
|
||||
"pollIntervalSeconds": 1800,
|
||||
"rowTarget": {
|
||||
"collection": "scum_tasks",
|
||||
"upsertKeys": [
|
||||
"taskRecordId"
|
||||
],
|
||||
"writeMode": "replace",
|
||||
"columnMappings": {
|
||||
"taskRecordId": "taskRecordId",
|
||||
"taskKind": "taskKind",
|
||||
"userProfileId": "userProfileId",
|
||||
"mapId": "mapId",
|
||||
"trackingDataSetId": "trackingDataSetId",
|
||||
"dataAssetPath": "dataAssetPath",
|
||||
"sequenceIndex": "sequenceIndex",
|
||||
"isTracked": "isTracked",
|
||||
"state": "state",
|
||||
"completionDeadline": "completionDeadline"
|
||||
}
|
||||
},
|
||||
"maxRows": 500,
|
||||
"timeoutSeconds": 15
|
||||
},
|
||||
@@ -510,28 +371,6 @@
|
||||
"resultSchemaRef": "schemas/bridge/queries/scum-events.result.schema.json",
|
||||
"sqlRef": "sql/scum-db-v57/events.sql",
|
||||
"pollIntervalSeconds": 1800,
|
||||
"rowTarget": {
|
||||
"collection": "scum_native_event_rounds",
|
||||
"upsertKeys": [
|
||||
"eventRecordId"
|
||||
],
|
||||
"writeMode": "replace",
|
||||
"columnMappings": {
|
||||
"eventRecordId": "eventRecordId",
|
||||
"eventId": "eventId",
|
||||
"roundId": "roundId",
|
||||
"userProfileId": "userProfileId",
|
||||
"startTime": "startTime",
|
||||
"endTime": "endTime",
|
||||
"state": "state",
|
||||
"score": "score",
|
||||
"enemyKills": "enemyKills",
|
||||
"teamKills": "teamKills",
|
||||
"deaths": "deaths",
|
||||
"assists": "assists",
|
||||
"headshots": "headshots"
|
||||
}
|
||||
},
|
||||
"maxRows": 500,
|
||||
"timeoutSeconds": 15
|
||||
},
|
||||
@@ -546,20 +385,6 @@
|
||||
"resultSchemaRef": "schemas/bridge/queries/scum-native-timed-gifts.result.schema.json",
|
||||
"sqlRef": "sql/scum-db-v57/native-timed-gifts.sql",
|
||||
"pollIntervalSeconds": 1800,
|
||||
"rowTarget": {
|
||||
"collection": "scum_timed_gift_events",
|
||||
"upsertKeys": [
|
||||
"timedGiftId"
|
||||
],
|
||||
"writeMode": "replace",
|
||||
"columnMappings": {
|
||||
"timedGiftId": "timedGiftId",
|
||||
"userProfileId": "userProfileId",
|
||||
"mapId": "mapId",
|
||||
"spawnTime": "spawnTime",
|
||||
"spawnAt": "spawnAt"
|
||||
}
|
||||
},
|
||||
"maxRows": 500,
|
||||
"timeoutSeconds": 15
|
||||
}
|
||||
|
||||
@@ -335,23 +335,11 @@
|
||||
"parameterSchemaRef": { "$ref": "#/$defs/relativeJsonRef" },
|
||||
"resultSchemaRef": { "$ref": "#/$defs/relativeJsonRef" },
|
||||
"sqlRef": { "$ref": "#/$defs/relativeSqlRef" },
|
||||
"rowTarget": { "$ref": "#/$defs/pluginDataRowTarget" },
|
||||
"maxRows": { "type": "integer", "minimum": 1, "maximum": 500 },
|
||||
"timeoutSeconds": { "type": "integer", "minimum": 1, "maximum": 60 },
|
||||
"pollIntervalSeconds": { "type": "integer", "minimum": 0, "maximum": 86400 }
|
||||
}
|
||||
},
|
||||
"pluginDataRowTarget": {
|
||||
"type": "object",
|
||||
"required": ["collection", "upsertKeys", "columnMappings", "writeMode"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"collection": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9._-]{0,119}$" },
|
||||
"upsertKeys": { "type": "array", "items": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9._-]{0,79}$" }, "uniqueItems": true, "minItems": 1, "maxItems": 8 },
|
||||
"columnMappings": { "type": "object", "minProperties": 1, "maxProperties": 64, "propertyNames": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9._-]{0,79}$" }, "additionalProperties": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9._-]{0,79}$" } },
|
||||
"writeMode": { "enum": ["merge", "replace"] }
|
||||
}
|
||||
},
|
||||
"gameClientBridgeLogProjection": {
|
||||
"type": "object",
|
||||
"required": ["key", "streamKeys", "steps", "correlationFields", "maxInterveningLines", "target"],
|
||||
|
||||
@@ -685,7 +685,6 @@ export function validateGameClientBridgeCatalog(manifest: unknown): string[] {
|
||||
parameterSchemaRef?: string;
|
||||
resultSchemaRef?: string;
|
||||
sqlRef?: string;
|
||||
rowTarget?: { collection?: string; upsertKeys?: string[]; columnMappings?: Record<string, string>; writeMode?: string };
|
||||
maxRows?: number;
|
||||
timeoutSeconds?: number;
|
||||
pollIntervalSeconds?: number;
|
||||
@@ -817,17 +816,7 @@ export function validateGameClientBridgeCatalog(manifest: unknown): string[] {
|
||||
errors.push(`${location}.${field}: raw host paths and unsafe schema references are not allowed`);
|
||||
}
|
||||
}
|
||||
const projectsRows = queryTemplate.sqlRef !== undefined || queryTemplate.rowTarget !== undefined;
|
||||
if (projectsRows) {
|
||||
if (!queryTemplate.sqlRef || !isSafeRelativeSqlRef(queryTemplate.sqlRef)) errors.push(`${location}.sqlRef: projected queries require a package-relative SQL asset`);
|
||||
const target = queryTemplate.rowTarget;
|
||||
if (!target || !/^[A-Za-z][A-Za-z0-9._-]{0,119}$/.test(target.collection ?? "")) errors.push(`${location}.rowTarget.collection: projected queries require a safe collection`);
|
||||
if (!Array.isArray(target?.upsertKeys) || target.upsertKeys.length === 0 || !target.upsertKeys.every((key) => /^[A-Za-z][A-Za-z0-9._-]{0,79}$/.test(key))) errors.push(`${location}.rowTarget.upsertKeys: projected queries require safe upsert keys`);
|
||||
const mappings = target?.columnMappings;
|
||||
if (!mappings || Array.isArray(mappings) || Object.keys(mappings).length === 0 || !Object.entries(mappings).every(([destination, source]) => /^[A-Za-z][A-Za-z0-9._-]{0,79}$/.test(destination) && typeof source === "string" && /^[A-Za-z][A-Za-z0-9._-]{0,79}$/.test(source))) errors.push(`${location}.rowTarget.columnMappings: projected queries require safe field mappings`);
|
||||
if (mappings && Array.isArray(target?.upsertKeys) && !target.upsertKeys.every((key) => key in mappings)) errors.push(`${location}.rowTarget.upsertKeys: every upsert key must be declared in columnMappings`);
|
||||
if (!new Set(["merge", "replace"]).has(target?.writeMode ?? "")) errors.push(`${location}.rowTarget.writeMode: projected queries require merge or replace`);
|
||||
}
|
||||
if (queryTemplate.sqlRef !== undefined && !isSafeRelativeSqlRef(queryTemplate.sqlRef)) errors.push(`${location}.sqlRef: query templates require a package-relative SQL asset`);
|
||||
if (!Number.isInteger(queryTemplate.maxRows) || (queryTemplate.maxRows ?? 0) < 1 || (queryTemplate.maxRows ?? 0) > 500) {
|
||||
errors.push(`${location}.maxRows: must be an integer between 1 and 500`);
|
||||
}
|
||||
|
||||
@@ -249,14 +249,6 @@ export interface GameClientBridgeQueryTemplateDeclaration {
|
||||
maxRows: number;
|
||||
timeoutSeconds: number;
|
||||
pollIntervalSeconds?: number;
|
||||
rowTarget?: PluginDataRowTargetDeclaration;
|
||||
}
|
||||
|
||||
export interface PluginDataRowTargetDeclaration {
|
||||
collection: string;
|
||||
upsertKeys: string[];
|
||||
columnMappings: Record<string, string>;
|
||||
writeMode: "merge" | "replace";
|
||||
}
|
||||
|
||||
export interface GameClientBridgeLogProjectionStepDeclaration {
|
||||
|
||||
@@ -637,7 +637,6 @@ describe("plugin manifest validation", () => {
|
||||
resultSchemaRef: string;
|
||||
sqlRef: string;
|
||||
pollIntervalSeconds: number;
|
||||
rowTarget: { collection: string; upsertKeys: string[]; writeMode: "merge" | "replace"; columnMappings: Record<string, string> };
|
||||
maxRows: number;
|
||||
timeoutSeconds: number;
|
||||
}>;
|
||||
@@ -673,11 +672,7 @@ describe("plugin manifest validation", () => {
|
||||
expect(template.transportKey).toBe("scum-database");
|
||||
expect(template.targetKey).toBe("scum-database");
|
||||
expect(template.sqlRef).toMatch(/^sql\/scum-db-v57\/.+\.sql$/);
|
||||
expect(template.rowTarget.collection).toMatch(/^scum_/);
|
||||
expect(template.pollIntervalSeconds).toBe(fastTemplates.has(key) ? 3 : 1800);
|
||||
expect(template.rowTarget.writeMode).toBe(key === "scum.player.profile" ? "merge" : "replace");
|
||||
expect(template.rowTarget.upsertKeys.length).toBeGreaterThan(0);
|
||||
expect(template.rowTarget.upsertKeys.every((upsertKey) => upsertKey in template.rowTarget.columnMappings)).toBe(true);
|
||||
expect(fs.existsSync(path.join(pluginDir, template.sqlRef))).toBe(true);
|
||||
expect(JSON.stringify(template).toLowerCase()).not.toMatch(/select\s|from\s|sqlite:|scum\.db|databasepath|hostpath|dsn/);
|
||||
const parameters = JSON.parse(fs.readFileSync(path.join(pluginDir, template.parameterSchemaRef), "utf8"));
|
||||
@@ -687,15 +682,12 @@ describe("plugin manifest validation", () => {
|
||||
expect(parameters).toMatchObject({ type: "object", additionalProperties: false });
|
||||
expect(result).toMatchObject({ type: "object", additionalProperties: false, required: ["rows"] });
|
||||
expect(result.properties.rows.maxItems).toBeLessThanOrEqual(template.maxRows);
|
||||
expect(template.rowTarget.columnMappings).toEqual(Object.fromEntries(expectedColumns.map((column) => [column, column])));
|
||||
expect(Object.keys(result.properties.rows.items.properties).sort()).toEqual([...expectedColumns].sort());
|
||||
expect([...result.properties.rows.items.required].sort()).toEqual([...expectedColumns].sort());
|
||||
for (const column of expectedColumns) {
|
||||
expect(sql).toMatch(new RegExp(`\\bAS\\s+${column}\\b`, "i"));
|
||||
}
|
||||
}
|
||||
expect(templatesByKey.get("scum.player.profile")?.rowTarget.upsertKeys).toEqual(["steamId"]);
|
||||
expect(templatesByKey.get("scum.squad-members")?.rowTarget.upsertKeys).toEqual(["squadId", "steamId"]);
|
||||
const userSQL = fs.readFileSync(path.join(pluginDir, templatesByKey.get("scum.player.profile")!.sqlRef), "utf8");
|
||||
const positionSQL = fs.readFileSync(path.join(pluginDir, templatesByKey.get("scum.positions")!.sqlRef), "utf8");
|
||||
expect(userSQL).toMatch(/FROM user account\s+LEFT JOIN user_profile profile/i);
|
||||
|
||||
Reference in New Issue
Block a user