Update-Event des Monitorings und PR-Flag bestehender Tickets

update.sh meldet nach dem Neustart ein Event (POST /api/v1/events/system, Admin-Key).
upsertPullRequest markiert bereits vorhandene Tickets als Pull Request, damit sie den
/pulls/-Link bekommen.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
fds2610-bot 2026-10-01 12:48:10 +02:00
parent fb0baff3df
commit 65f6bc6c2e
7 changed files with 86 additions and 1 deletions

6
API.md
View file

@ -354,6 +354,12 @@ Auth: `X-Api-Key`.
Pflichtfelder: `name`, `message`, `severity`. `occurred_at` optional (Default: jetzt).
Response `201` mit dem gespeicherten Event.
### `POST /api/v1/events/system` — Event des Monitorings selbst melden
Wie `POST /api/v1/events`, aber Auth per `X-Admin-Key`. Genutzt von `update.sh`, das nach einem
erfolgreichen Update `name: "Monitor"`, `message: "Update eingespielt (<Commit>)"`,
`severity: "warning"` meldet.
### `GET /api/v1/events` — Event-Liste
Query-Parameter: `limit` (Default `5`, entspricht der im Plan festgelegten Zeilenzahl des

View file

@ -14,6 +14,8 @@ aktualisiert.
fuehrt (per Default) die Testsuiten aus. Kann jederzeit auch manuell aufgerufen werden, siehe
Kommentar am Kopf der Datei fuer die Umgebungsvariablen `UPDATE_REMOTE`, `UPDATE_BRANCH`,
`UPDATE_RUN_TESTS`.
Nach dem Neustart meldet das Skript ein Event "Monitor: Update eingespielt (<Commit>)" per
`POST /api/v1/events/system` (Admin-Key aus der `.env`); schlaegt das fehl, bleibt das Update gueltig.
- **`deploy-webhook.py`** (Projekt-Wurzel) - kleiner HTTP-Server (nur Python-Standardbibliothek,
keine Abhaengigkeiten) auf Basis von `http.server`. Prueft eingehende Codeberg-Webhooks per
HMAC-Signatur (`X-Forgejo-Signature`/`X-Gitea-Signature`, analog zum bestehenden

View file

@ -103,6 +103,9 @@ final class Application
$this->router->add('GET', '/api/v1/events', fn (Request $r, array $p) => $eventController->index($r));
$this->router->add('POST', '/api/v1/events', fn (Request $r, array $p) => $eventController->create($r), 'agent');
// update.sh meldet hier, dass sich das Monitoring selbst aktualisiert hat. Es laeuft auf dem
// Host und kennt nur den Admin-Key aus der .env, keinen Agenten-Key.
$this->router->add('POST', '/api/v1/events/system', fn (Request $r, array $p) => $eventController->create($r), 'admin');
$this->router->add('GET', '/api/v1/systems', fn (Request $r, array $p) => $systemController->index($r));
$this->router->add('POST', '/api/v1/systems/{id}/status', fn (Request $r, array $p) => $systemController->reportStatus($r, $p), 'agent');

View file

@ -185,11 +185,21 @@ final class TicketRepository
return $this->create($data + ['column' => 'fertig', 'source' => 'codeberg', 'is_pull_request' => 1]);
}
return $this->update((int) $existing['id'], [
$updated = $this->update((int) $existing['id'], [
'title' => $data['title'],
'description' => $data['description'] ?? null,
'color' => $data['color'] ?? null,
]);
// Ein Ticket, das vor der PR-Darstellung (z. B. durch einen aelteren Sync) als Issue
// angelegt wurde, behielte sonst is_pull_request = 0 und damit den /issues/-Link.
if (empty($existing['is_pull_request'])) {
$this->db->pdo()->prepare('UPDATE tickets SET is_pull_request = 1 WHERE id = :id')
->execute(['id' => $existing['id']]);
$updated['is_pull_request'] = 1;
}
return $updated;
}
/**

View file

@ -50,6 +50,23 @@ final class ApplicationTest extends TestCase
self::assertSame(401, $response->status);
}
public function testSystemEventNeedsAdminKey(): void
{
$body = ['name' => 'Monitor', 'message' => 'Update eingespielt (abc1234)', 'severity' => 'warning'];
$denied = $this->app->handle(Request::create('POST', '/api/v1/events/system', body: $body));
self::assertSame(401, $denied->status);
$created = $this->app->handle(Request::create(
'POST',
'/api/v1/events/system',
body: $body,
headers: ['X-Admin-Key' => 'admin-secret']
));
self::assertSame(201, $created->status);
self::assertSame('Monitor', $created->body()['event']['name']);
}
public function testFullFlowRegisterAgentThenCreateTicket(): void
{
$registerResponse = $this->app->handle(Request::create(

View file

@ -27,6 +27,16 @@ final class TicketRepositoryTest extends TestCase
self::assertSame('Neues Ticket', $ticket['title']);
}
public function testUpsertPullRequestFlagsExistingIssueTicket(): void
{
$this->repository->create(['title' => 'PR', 'external_ref' => '107', 'repo' => 'a/b', 'source' => 'codeberg']);
$ticket = $this->repository->upsertPullRequest(['external_ref' => '107', 'repo' => 'a/b', 'title' => 'PR neu']);
self::assertSame(1, (int) $ticket['is_pull_request']);
self::assertSame(1, (int) $this->repository->findByExternalRef('107', 'a/b')['is_pull_request']);
}
public function testCreateRejectsUnknownColumn(): void
{
$this->expectException(InvalidArgumentException::class);

View file

@ -97,4 +97,41 @@ fi
log "Starte Dienste neu (api, web, sync)..."
$DOCKER_COMPOSE up -d api web sync
# Meldet das Update als Event (POST /api/v1/events/system, Admin-Key aus der .env). Die API ist
# direkt nach "up -d" evtl. noch nicht bereit, daher mehrere Versuche; ein Fehlschlag bricht das
# (bereits erfolgreiche) Update nicht ab.
report_update_event() {
command -v curl >/dev/null 2>&1 || { log "Hinweis: curl fehlt, Update-Event wird nicht gemeldet."; return 0; }
local env_file="$REPO_DIR/.env" admin_key api_base
env_value() {
[ -f "$env_file" ] || return 0
sed -n "s/^[[:space:]]*$1=//p" "$env_file" | tail -n1 \
| sed -e 's/^"\(.*\)"$/\1/' -e "s/^'\(.*\)'\$/\1/"
}
admin_key="${MONITOR_ADMIN_API_KEY:-$(env_value MONITOR_ADMIN_API_KEY)}"
api_base="${MONITOR_API_BASE:-$(env_value MONITOR_API_BASE)}"
api_base="${api_base:-http://localhost:8091}"
api_base="${api_base%/}"
api_base="${api_base%/api/v1}"
if [ -z "$admin_key" ]; then
log "Hinweis: MONITOR_ADMIN_API_KEY fehlt, Update-Event wird nicht gemeldet."
return 0
fi
local payload i
payload="{\"name\":\"Monitor\",\"message\":\"Update eingespielt ($(git rev-parse --short HEAD))\",\"severity\":\"warning\"}"
for i in 1 2 3 4 5 6 7 8 9 10; do
if curl -sf --max-time 10 -X POST "$api_base/api/v1/events/system" \
-H "Content-Type: application/json" -H "X-Admin-Key: $admin_key" \
-d "$payload" >/dev/null 2>&1; then
log "Update-Event gemeldet."
return 0
fi
sleep 3
done
log "Warnung: Update-Event konnte nicht gemeldet werden (API unter $api_base nicht erreichbar oder Key ungueltig)."
}
report_update_event
log "Update abgeschlossen (${UPDATE_BRANCH} @ $(git rev-parse --short HEAD))."