Archived
make webhook
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import {
|
||||
type DatabaseContext,
|
||||
type Job,
|
||||
mapJob,
|
||||
type NewJob,
|
||||
now,
|
||||
type Row,
|
||||
} from "../model.js";
|
||||
|
||||
export class JobRepository {
|
||||
constructor(private readonly context: DatabaseContext) {}
|
||||
|
||||
createCommand(input: NewJob): { job: Job; created: boolean } {
|
||||
return this.context.transaction(() => {
|
||||
const existing = this.byTriggerKey(input.triggerKey);
|
||||
if (existing) return { job: existing, created: false };
|
||||
return { job: this.insert(input), created: true };
|
||||
});
|
||||
}
|
||||
|
||||
createLabel(input: NewJob): Job | undefined {
|
||||
if (!input.triggerLabel)
|
||||
throw new Error("Label job requires triggerLabel");
|
||||
const label = input.triggerLabel;
|
||||
return this.context.transaction(() => {
|
||||
const claim = this.context.db
|
||||
.prepare(`
|
||||
SELECT claimed FROM label_claims
|
||||
WHERE repository_id = $repositoryId AND issue_number = $issueNumber AND label = $label
|
||||
`)
|
||||
.get({
|
||||
$repositoryId: input.repositoryId,
|
||||
$issueNumber: input.issueNumber,
|
||||
$label: label,
|
||||
}) as Row | undefined;
|
||||
if (Number(claim?.claimed || 0) === 1) return undefined;
|
||||
const job = this.insert({
|
||||
...input,
|
||||
triggerKey: `label:${input.repositoryId}:${input.issueNumber}:${label}:${randomUUID()}`,
|
||||
});
|
||||
this.context.db
|
||||
.prepare(`
|
||||
INSERT INTO label_claims(repository_id, issue_number, label, claimed, job_id, updated_at)
|
||||
VALUES ($repositoryId, $issueNumber, $label, 1, $jobId, $now)
|
||||
ON CONFLICT(repository_id, issue_number, label)
|
||||
DO UPDATE SET claimed = 1, job_id = excluded.job_id, updated_at = excluded.updated_at
|
||||
`)
|
||||
.run({
|
||||
$repositoryId: input.repositoryId,
|
||||
$issueNumber: input.issueNumber,
|
||||
$label: label,
|
||||
$jobId: job.id,
|
||||
$now: now(),
|
||||
});
|
||||
return job;
|
||||
});
|
||||
}
|
||||
|
||||
releaseLabel(
|
||||
repositoryId: number,
|
||||
issueNumber: number,
|
||||
label: string,
|
||||
): void {
|
||||
this.context.db
|
||||
.prepare(`
|
||||
INSERT INTO label_claims(repository_id, issue_number, label, claimed, job_id, updated_at)
|
||||
VALUES ($repositoryId, $issueNumber, $label, 0, NULL, $now)
|
||||
ON CONFLICT(repository_id, issue_number, label)
|
||||
DO UPDATE SET claimed = 0, job_id = NULL, updated_at = excluded.updated_at
|
||||
`)
|
||||
.run({
|
||||
$repositoryId: repositoryId,
|
||||
$issueNumber: issueNumber,
|
||||
$label: label,
|
||||
$now: now(),
|
||||
});
|
||||
}
|
||||
|
||||
active(repositoryId: number, issueNumber: number): Job | undefined {
|
||||
const row = this.context.db
|
||||
.prepare(`
|
||||
SELECT * FROM jobs
|
||||
WHERE repository_id = $repositoryId AND issue_number = $issueNumber
|
||||
AND state IN ('admitted', 'queued', 'running', 'publishing')
|
||||
ORDER BY created_at DESC LIMIT 1
|
||||
`)
|
||||
.get({ $repositoryId: repositoryId, $issueNumber: issueNumber }) as
|
||||
| Row
|
||||
| undefined;
|
||||
return row ? mapJob(row) : undefined;
|
||||
}
|
||||
|
||||
latest(repositoryId: number, issueNumber: number): Job | undefined {
|
||||
const row = this.context.db
|
||||
.prepare(`
|
||||
SELECT * FROM jobs WHERE repository_id = $repositoryId AND issue_number = $issueNumber
|
||||
ORDER BY created_at DESC LIMIT 1
|
||||
`)
|
||||
.get({ $repositoryId: repositoryId, $issueNumber: issueNumber }) as
|
||||
| Row
|
||||
| undefined;
|
||||
return row ? mapJob(row) : undefined;
|
||||
}
|
||||
|
||||
control(
|
||||
triggerKey: string,
|
||||
action: "cancel" | "status",
|
||||
repositoryId: number,
|
||||
issueNumber: number,
|
||||
): Job | undefined {
|
||||
return this.context.transaction(() => {
|
||||
const existing = this.context.db
|
||||
.prepare(
|
||||
"SELECT target_job_id FROM command_receipts WHERE trigger_key = $triggerKey",
|
||||
)
|
||||
.get({ $triggerKey: triggerKey }) as Row | undefined;
|
||||
if (existing)
|
||||
return existing.target_job_id === null
|
||||
? undefined
|
||||
: this.get(String(existing.target_job_id));
|
||||
const target =
|
||||
action === "cancel"
|
||||
? this.active(repositoryId, issueNumber)
|
||||
: this.latest(repositoryId, issueNumber);
|
||||
this.context.db
|
||||
.prepare(`
|
||||
INSERT INTO command_receipts(trigger_key, action, repository_id, issue_number, target_job_id, created_at)
|
||||
VALUES ($triggerKey, $action, $repositoryId, $issueNumber, $targetJobId, $now)
|
||||
`)
|
||||
.run({
|
||||
$triggerKey: triggerKey,
|
||||
$action: action,
|
||||
$repositoryId: repositoryId,
|
||||
$issueNumber: issueNumber,
|
||||
$targetJobId: target?.id || null,
|
||||
$now: now(),
|
||||
});
|
||||
if (action === "cancel" && target) {
|
||||
this.context.db
|
||||
.prepare(`
|
||||
UPDATE jobs SET cancel_requested = 1,
|
||||
state = CASE WHEN state IN ('admitted', 'queued') THEN 'cancelled' ELSE state END,
|
||||
updated_at = $now WHERE id = $id
|
||||
`)
|
||||
.run({ $id: target.id, $now: now() });
|
||||
this.context.audit(
|
||||
target.id,
|
||||
"job.cancel-requested",
|
||||
triggerKey,
|
||||
);
|
||||
return this.get(target.id);
|
||||
}
|
||||
return target;
|
||||
});
|
||||
}
|
||||
|
||||
get(id: string): Job | undefined {
|
||||
const row = this.context.db
|
||||
.prepare("SELECT * FROM jobs WHERE id = $id")
|
||||
.get({ $id: id }) as Row | undefined;
|
||||
return row ? mapJob(row) : undefined;
|
||||
}
|
||||
|
||||
private insert(input: NewJob): Job {
|
||||
const id = randomUUID();
|
||||
const time = now();
|
||||
this.context.db
|
||||
.prepare(`
|
||||
INSERT INTO jobs
|
||||
(id, repository_id, issue_number, mode, trigger_kind, trigger_key, trigger_label,
|
||||
actor_id, actor_login, instruction, state, created_at, updated_at)
|
||||
VALUES ($id, $repositoryId, $issueNumber, $mode, $triggerKind, $triggerKey, $triggerLabel,
|
||||
$actorId, $actorLogin, $instruction, 'admitted', $now, $now)
|
||||
`)
|
||||
.run({
|
||||
$id: id,
|
||||
$repositoryId: input.repositoryId,
|
||||
$issueNumber: input.issueNumber,
|
||||
$mode: input.mode,
|
||||
$triggerKind: input.triggerKind,
|
||||
$triggerKey: input.triggerKey,
|
||||
$triggerLabel: input.triggerLabel || null,
|
||||
$actorId: input.actorId,
|
||||
$actorLogin: input.actorLogin,
|
||||
$instruction: input.instruction || "",
|
||||
$now: time,
|
||||
});
|
||||
this.context.db
|
||||
.prepare(`
|
||||
INSERT INTO outbox(id, job_id, kind, status, available_at, created_at, updated_at)
|
||||
VALUES ($id, $jobId, 'claim', 'pending', $now, $now, $now)
|
||||
`)
|
||||
.run({ $id: randomUUID(), $jobId: id, $now: time });
|
||||
this.context.audit(
|
||||
id,
|
||||
"job.admitted",
|
||||
`${input.triggerKind}:${input.actorLogin}`,
|
||||
);
|
||||
const job = this.get(id);
|
||||
if (!job) throw new Error(`Inserted job ${id} was not found`);
|
||||
return job;
|
||||
}
|
||||
|
||||
private byTriggerKey(triggerKey: string): Job | undefined {
|
||||
const row = this.context.db
|
||||
.prepare("SELECT * FROM jobs WHERE trigger_key = $key")
|
||||
.get({ $key: triggerKey }) as Row | undefined;
|
||||
return row ? mapJob(row) : undefined;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user