Fix delete-script FK error; trigger drift on scope change
- deleteScript: null out deployments.script_id/script_version_id/prior_version_id and deployment_scripts.prior_version_id inside a transaction before the cascade. These are historical audit FKs (NO ACTION), so the cascade from script_versions was blowing up any delete of a promoted script. - Scope endpoint fires runDriftDeployment in the background after any scope mutation so orphan/missing files from the change get reconciled. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -337,7 +337,39 @@ export function deleteScript(scriptId: number): void {
|
|||||||
)
|
)
|
||||||
.get(scriptId) as { id: number } | undefined;
|
.get(scriptId) as { id: number } | undefined;
|
||||||
if (beta) throw new ScriptError(`beta ${beta.id} is still running against this script`, 'has-active-beta');
|
if (beta) throw new ScriptError(`beta ${beta.id} is still running against this script`, 'has-active-beta');
|
||||||
db().prepare('DELETE FROM scripts WHERE id = ?').run(scriptId);
|
|
||||||
|
// Null out deployment-history FKs that would otherwise block the cascade.
|
||||||
|
// deployments.script_id/script_version_id/prior_version_id and
|
||||||
|
// deployment_scripts.prior_version_id are historical audit references
|
||||||
|
// with no ON DELETE action; keep the deployment rows intact but drop
|
||||||
|
// the FK to the script so we can remove it.
|
||||||
|
const handle = db();
|
||||||
|
const tx = handle.transaction((id: number) => {
|
||||||
|
handle
|
||||||
|
.prepare(
|
||||||
|
`UPDATE deployments
|
||||||
|
SET script_version_id = NULL
|
||||||
|
WHERE script_version_id IN (SELECT id FROM script_versions WHERE script_id = ?)`
|
||||||
|
)
|
||||||
|
.run(id);
|
||||||
|
handle
|
||||||
|
.prepare(
|
||||||
|
`UPDATE deployments
|
||||||
|
SET prior_version_id = NULL
|
||||||
|
WHERE prior_version_id IN (SELECT id FROM script_versions WHERE script_id = ?)`
|
||||||
|
)
|
||||||
|
.run(id);
|
||||||
|
handle.prepare('UPDATE deployments SET script_id = NULL WHERE script_id = ?').run(id);
|
||||||
|
handle
|
||||||
|
.prepare(
|
||||||
|
`UPDATE deployment_scripts
|
||||||
|
SET prior_version_id = NULL
|
||||||
|
WHERE prior_version_id IN (SELECT id FROM script_versions WHERE script_id = ?)`
|
||||||
|
)
|
||||||
|
.run(id);
|
||||||
|
handle.prepare('DELETE FROM scripts WHERE id = ?').run(id);
|
||||||
|
});
|
||||||
|
tx(scriptId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Servers that match a script's scope. Empty for unscoped scripts — those are
|
// Servers that match a script's scope. Empty for unscoped scripts — those are
|
||||||
|
|||||||
@@ -1,12 +1,24 @@
|
|||||||
import { json } from '@sveltejs/kit';
|
import { json } from '@sveltejs/kit';
|
||||||
import { getScript, setScope, ScriptError, type ScopeType } from '$lib/server/fleet/scripts.js';
|
import { getScript, setScope, ScriptError, type ScopeType } from '$lib/server/fleet/scripts.js';
|
||||||
import { unsetScopeAndCleanup } from '$lib/server/fleet/beta.js';
|
import { unsetScopeAndCleanup } from '$lib/server/fleet/beta.js';
|
||||||
|
import { runDriftDeployment } from '$lib/server/fleet/drift.js';
|
||||||
|
import { kick } from '$lib/server/pipeline/worker.js';
|
||||||
|
|
||||||
function parseId(raw: string): number | null {
|
function parseId(raw: string): number | null {
|
||||||
const n = Number.parseInt(raw, 10);
|
const n = Number.parseInt(raw, 10);
|
||||||
return Number.isFinite(n) ? n : null;
|
return Number.isFinite(n) ? n : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fire-and-forget: after a scope mutation, reconcile any drift the change may
|
||||||
|
// have introduced (orphan files on servers that no longer match, or missing
|
||||||
|
// files on servers that now do). Runs in the background so the API returns
|
||||||
|
// promptly; the deployment will surface on /deployments if there's real drift.
|
||||||
|
function triggerDrift(): void {
|
||||||
|
void runDriftDeployment({ triggeredBy: 'ui' })
|
||||||
|
.then((result) => { if (result.deploymentId) void kick(); })
|
||||||
|
.catch(() => { /* surfaced on /deployments if it fails */ });
|
||||||
|
}
|
||||||
|
|
||||||
// POST body: { scopeType: 'game' | 'map' | null, scopeValue?: string }
|
// POST body: { scopeType: 'game' | 'map' | null, scopeValue?: string }
|
||||||
// Setting/changing scope on a script with a current_version_id returns a
|
// Setting/changing scope on a script with a current_version_id returns a
|
||||||
// 409 with kind='scope-requires-beta'; unsetting cleans the file off matching
|
// 409 with kind='scope-requires-beta'; unsetting cleans the file off matching
|
||||||
@@ -28,9 +40,11 @@ export async function POST({ params, request }: { params: { id: string }; reques
|
|||||||
try {
|
try {
|
||||||
if (scopeType === null) {
|
if (scopeType === null) {
|
||||||
const cleaned = await unsetScopeAndCleanup(scriptId);
|
const cleaned = await unsetScopeAndCleanup(scriptId);
|
||||||
|
triggerDrift();
|
||||||
return json({ ok: true, cleaned });
|
return json({ ok: true, cleaned });
|
||||||
}
|
}
|
||||||
const updated = setScope({ scriptId, scopeType, scopeValue });
|
const updated = setScope({ scriptId, scopeType, scopeValue });
|
||||||
|
triggerDrift();
|
||||||
return json({ ok: true, script: updated });
|
return json({ ok: true, script: updated });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof ScriptError) {
|
if (err instanceof ScriptError) {
|
||||||
|
|||||||
Reference in New Issue
Block a user