monitor/api/src/Sync/CurlCodebergClient.php
fds2610-bot 1b6873db62 Rote Fahne am PO fuer offene Tickets externer User (#113)
Der Codeberg-Sync speichert den Ersteller (user.login) von Issues und PRs in der neuen
Ticket-Spalte author (Migration 010). Die Buero-Visualisierung zaehlt offene Tickets
(alles ausser Done), deren Ersteller weder fds2610 noch fds2610-bot ist, und zeigt die
Anzahl in einer kleinen roten Fahne auf dem PO-Tisch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 09:04:56 +02:00

163 lines
5 KiB
PHP

<?php
declare(strict_types=1);
namespace Monitor\Api\Sync;
use RuntimeException;
final class CurlCodebergClient implements CodebergClient
{
private const PER_PAGE = 50;
private const MAX_PAGES = 20;
public function __construct(
private readonly string $apiBase,
private readonly ?string $token = null,
) {
}
public function listIssues(string $owner, string $repo, string $state): array
{
$issues = [];
for ($page = 1; $page <= self::MAX_PAGES; $page++) {
$url = sprintf(
'%s/repos/%s/%s/issues?state=%s&type=issues&page=%d&limit=%d',
$this->apiBase,
rawurlencode($owner),
rawurlencode($repo),
rawurlencode($state),
$page,
self::PER_PAGE
);
$pageIssues = $this->fetch($url);
if ($pageIssues === []) {
break;
}
foreach ($pageIssues as $issue) {
$issues[] = [
'number' => (int) $issue['number'],
'title' => (string) $issue['title'],
'body' => isset($issue['body']) ? (string) $issue['body'] : null,
'author' => self::authorLogin($issue),
'labels' => array_map(
static fn (array $label): array => [
'name' => (string) $label['name'],
'color' => (string) $label['color'],
],
$issue['labels'] ?? []
),
];
}
if (count($pageIssues) < self::PER_PAGE) {
break;
}
}
return $issues;
}
public function listOpenPullRequests(string $owner, string $repo): array
{
$pulls = [];
for ($page = 1; $page <= self::MAX_PAGES; $page++) {
$url = sprintf(
'%s/repos/%s/%s/issues?state=open&type=pulls&page=%d&limit=%d',
$this->apiBase,
rawurlencode($owner),
rawurlencode($repo),
$page,
self::PER_PAGE
);
$pagePulls = $this->fetch($url);
foreach ($pagePulls as $pull) {
$pulls[] = [
'number' => (int) $pull['number'],
'title' => (string) ($pull['title'] ?? ''),
'body' => isset($pull['body']) ? (string) $pull['body'] : null,
'author' => self::authorLogin($pull),
];
}
if (count($pagePulls) < self::PER_PAGE) {
break;
}
}
return $pulls;
}
/**
* Issue #113: Login des Kontos, das Issue/PR angelegt hat (Feld "user" der Codeberg-API).
*
* @param array<string, mixed> $item
*/
private static function authorLogin(array $item): ?string
{
$login = is_array($item['user'] ?? null) ? ($item['user']['login'] ?? null) : null;
return is_string($login) && $login !== '' ? $login : null;
}
public function repoPermissions(string $owner, string $repo): array
{
$url = sprintf('%s/repos/%s/%s', $this->apiBase, rawurlencode($owner), rawurlencode($repo));
$data = $this->fetch($url);
$permissions = is_array($data['permissions'] ?? null) ? $data['permissions'] : [];
return ['push' => (bool) ($permissions['push'] ?? false)];
}
public function listWebhooks(string $owner, string $repo): array
{
$url = sprintf('%s/repos/%s/%s/hooks', $this->apiBase, rawurlencode($owner), rawurlencode($repo));
$data = $this->fetch($url);
return array_map(
static fn (array $hook): array => [
'active' => (bool) ($hook['active'] ?? false),
'url' => (string) ($hook['config']['url'] ?? ''),
'content_type' => (string) ($hook['config']['content_type'] ?? ''),
],
$data
);
}
/** @return array<int|string, mixed> */
private function fetch(string $url): array
{
$headers = ['Accept: application/json'];
if ($this->token !== null && $this->token !== '') {
$headers[] = 'Authorization: token ' . $this->token;
}
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_TIMEOUT => 15,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($body === false) {
throw new RuntimeException('Codeberg-API nicht erreichbar: ' . $error);
}
if ($status >= 400) {
throw new CodebergApiException($status, 'Codeberg-API-Fehler (HTTP ' . $status . '): ' . $body);
}
$decoded = json_decode((string) $body, true);
return is_array($decoded) ? $decoded : [];
}
}