feat(tasks): Aufgabe als Git-Issue anlegen, Zustand wandert mit
Die Zuordnung stand im Schema laengst – sourceIssue, sourceUrl und sourceRepositoryId samt Eindeutigkeit je Repository, gefuellt von der Uebernahme aus Issues seit v0.9.0. Es fehlte die Gegenrichtung. Ein Knopf im Aufgabendialog legt das Issue an; Nummer und Adresse landen in denselben Feldern, der Dublettenschutz gilt also fuer beide Wege. In den Rumpf kommt ein Rueckverweis auf das Projekt. Ausdruecklich per Knopf und nie nebenbei: was in ein fremdes System geschrieben wird, soll jemand gewollt haben. Ist die Aufgabe erst verknuepft, wandert der Zustand allerdings mit – erledigt schliesst das Issue, ein Zurueckziehen oeffnet es wieder. Fehlertolerant: ein nicht erreichbares Git-System darf das Abhaken nicht verhindern. Die Zustaende passen nicht aufeinander, und das bleibt so. Nebula kennt vier, ein Issue zwei; nach aussen geht nur die Grenze um ERLEDIGT. Ein Rundlauf verliert damit IN ARBEIT und BLOCKIERT. Labels waeren die Alternative, wuerden aber den Issue-Tracker mit Zustaenden fuellen, die dort niemand pflegt. Kein Widerspruch zu "keine eigenen Git-Operationen": Issues laufen ueber die HTTP-API der Provider. Dafuer gibt es jetzt sendJson neben getJson – durch dieselbe SSRF-Schleuse, aber ohne Umleitungen zu folgen. Ein 307 auf eine interne Adresse naehme den Rumpf und den Token im Header mit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
import type { TaskStatus } from "@prisma/client";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { clientForConnection } from "@/lib/git";
|
||||
|
||||
/**
|
||||
* Die Bruecke zwischen Nebula-Aufgabe und Git-Issue.
|
||||
*
|
||||
* Die Zustaende passen nicht aufeinander und das bleibt so: Nebula kennt
|
||||
* OFFEN, IN ARBEIT, BLOCKIERT und ERLEDIGT, ein Issue nur offen oder
|
||||
* geschlossen. Nach aussen geht deshalb nur die Grenze zwischen "erledigt"
|
||||
* und "nicht erledigt"; ein Rundlauf verliert IN ARBEIT und BLOCKIERT.
|
||||
*
|
||||
* Die Alternative waeren Labels wie "nebula:doing" – die wuerden den
|
||||
* Issue-Tracker aber mit Zustaenden fuellen, die dort niemand pflegt. Die
|
||||
* feine Abstufung bleibt in Nebula.
|
||||
*/
|
||||
|
||||
/** Ist der Aufgabenzustand einer, bei dem das Issue offen sein soll? */
|
||||
export function issueShouldBeOpen(status: TaskStatus): boolean {
|
||||
return status !== "DONE";
|
||||
}
|
||||
|
||||
/** Der Aufgabenzustand, der zu einem Issue-Zustand passt. */
|
||||
export function statusForIssueState(open: boolean, current: TaskStatus): TaskStatus {
|
||||
if (open) {
|
||||
// Ein wieder geoeffnetes Issue macht aus ERLEDIGT wieder OFFEN. Stand
|
||||
// die Aufgabe ohnehin nicht auf ERLEDIGT, bleibt ihr feinerer Zustand
|
||||
// stehen – IN ARBEIT ist mehr Information als OFFEN.
|
||||
return current === "DONE" ? "TODO" : current;
|
||||
}
|
||||
return "DONE";
|
||||
}
|
||||
|
||||
/**
|
||||
* Traegt den Zustand einer Aufgabe an ihrem Issue nach.
|
||||
*
|
||||
* Bewusst fehlertolerant: schlaegt der Aufruf fehl, ist die Aufgabe in
|
||||
* Nebula trotzdem umgezogen. Ein nicht erreichbares Git-System darf das
|
||||
* Abhaken nicht verhindern – der naechste Wechsel versucht es erneut.
|
||||
*
|
||||
* Gibt zurueck, ob es geklappt hat; null heisst "nichts zu tun".
|
||||
*/
|
||||
export async function mirrorStatusToIssue(
|
||||
taskId: string,
|
||||
status: TaskStatus,
|
||||
): Promise<boolean | null> {
|
||||
const task = await prisma.task.findUnique({
|
||||
where: { id: taskId },
|
||||
select: { sourceIssue: true, sourceRepositoryId: true },
|
||||
});
|
||||
if (!task?.sourceIssue || !task.sourceRepositoryId) return null;
|
||||
|
||||
const repository = await prisma.repository.findUnique({
|
||||
where: { id: task.sourceRepositoryId },
|
||||
include: { connection: true },
|
||||
});
|
||||
if (!repository?.connection || !repository.repoOwner || !repository.repoName) return null;
|
||||
|
||||
try {
|
||||
const client = clientForConnection(repository.connection);
|
||||
await client.setIssueState(
|
||||
{ owner: repository.repoOwner, name: repository.repoName },
|
||||
task.sourceIssue,
|
||||
issueShouldBeOpen(status),
|
||||
);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user