gitea plug
This commit is contained in:
1 parent
86508ffcca
commit
dd1afbbeb2
6 files changed
+1650
-30
No files matched your search
@@ -145,6 +145,371 @@ PluginManager::instance()->onApiRoute(function (string $route, string $method) {
|
||||
giteaJson(['ok' => true]);
|
||||
}
|
||||
|
||||
// ── Issues: list ──
|
||||
|
||||
if ($route === '/api/gitea/issues' && $method === 'GET') {
|
||||
$accId = (int)($_GET['account'] ?? 0);
|
||||
$row = giteaGetAccount($accId);
|
||||
if (!$row) giteaJson(['error' => 'account not found'], 404);
|
||||
|
||||
$state = in_array($_GET['state'] ?? 'open', ['open', 'closed', 'all'], true) ? $_GET['state'] : 'open';
|
||||
$q = trim((string)($_GET['q'] ?? ''));
|
||||
$owner = trim((string)($_GET['owner'] ?? ''));
|
||||
$scope = $_GET['scope'] ?? 'all'; // all | assigned | created | mentioned
|
||||
$page = max(1, (int)($_GET['page'] ?? 1));
|
||||
$limit = max(5, min(100, (int)($_GET['limit'] ?? 30)));
|
||||
|
||||
try {
|
||||
$client = giteaOpenClient($row);
|
||||
$issues = $client->searchIssues([
|
||||
'state' => $state,
|
||||
'q' => $q !== '' ? $q : null,
|
||||
'owner' => $owner !== '' ? $owner : null,
|
||||
'type' => 'issues', // exclude PRs
|
||||
'assigned' => $scope === 'assigned' ? true : null,
|
||||
'created' => $scope === 'created' ? true : null,
|
||||
'mentioned' => $scope === 'mentioned' ? true : null,
|
||||
'page' => $page,
|
||||
'limit' => $limit,
|
||||
]);
|
||||
|
||||
// Map to a compact, UI-friendly shape
|
||||
$mapped = array_map(function ($i) {
|
||||
$repoFull = $i['repository']['full_name'] ?? null;
|
||||
if (!$repoFull && isset($i['repository']['owner'], $i['repository']['name'])) {
|
||||
$repoFull = $i['repository']['owner'] . '/' . $i['repository']['name'];
|
||||
}
|
||||
return [
|
||||
'id' => $i['id'] ?? null,
|
||||
'number' => $i['number'] ?? null,
|
||||
'title' => $i['title'] ?? '',
|
||||
'state' => $i['state'] ?? 'open',
|
||||
'created_at' => $i['created_at'] ?? null,
|
||||
'updated_at' => $i['updated_at'] ?? null,
|
||||
'comments_count' => $i['comments'] ?? 0,
|
||||
'html_url' => $i['html_url'] ?? null,
|
||||
'repo' => $repoFull,
|
||||
'author' => $i['user']['login'] ?? null,
|
||||
'labels' => array_map(fn($l) => [
|
||||
'name' => $l['name'] ?? '',
|
||||
'color' => $l['color'] ?? '888888',
|
||||
], $i['labels'] ?? []),
|
||||
'assignees' => array_map(fn($a) => $a['login'] ?? '', $i['assignees'] ?? []),
|
||||
];
|
||||
}, $issues);
|
||||
|
||||
giteaJson([
|
||||
'ok' => true,
|
||||
'issues' => $mapped,
|
||||
'page' => $page,
|
||||
'limit' => $limit,
|
||||
'hasMore' => count($mapped) >= $limit, // heuristic — Gitea doesn't always return total
|
||||
]);
|
||||
} catch (GiteaApiException $e) {
|
||||
giteaJson(['ok' => false, 'error' => "HTTP {$e->statusCode}: " . substr($e->body, 0, 300)], 200);
|
||||
} catch (\Throwable $e) {
|
||||
giteaJson(['ok' => false, 'error' => $e->getMessage()], 200);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Issue detail (with comments) ──
|
||||
|
||||
if (preg_match('#^/api/gitea/issue/(\d+)/([^/]+)/([^/]+)/(\d+)$#', $route, $m) && $method === 'GET') {
|
||||
$accId = (int)$m[1];
|
||||
$owner = urldecode($m[2]);
|
||||
$repo = urldecode($m[3]);
|
||||
$index = (int)$m[4];
|
||||
$row = giteaGetAccount($accId);
|
||||
if (!$row) giteaJson(['error' => 'account not found'], 404);
|
||||
|
||||
try {
|
||||
$client = giteaOpenClient($row);
|
||||
$issue = $client->getIssue($owner, $repo, $index);
|
||||
$comments = $client->listIssueComments($owner, $repo, $index);
|
||||
|
||||
giteaJson([
|
||||
'ok' => true,
|
||||
'issue' => [
|
||||
'id' => $issue['id'] ?? null,
|
||||
'number' => $issue['number'] ?? $index,
|
||||
'title' => $issue['title'] ?? '',
|
||||
'body' => $issue['body'] ?? '',
|
||||
'state' => $issue['state'] ?? 'open',
|
||||
'created_at' => $issue['created_at'] ?? null,
|
||||
'updated_at' => $issue['updated_at'] ?? null,
|
||||
'closed_at' => $issue['closed_at'] ?? null,
|
||||
'due_date' => $issue['due_date'] ?? null,
|
||||
'html_url' => $issue['html_url'] ?? null,
|
||||
'author' => $issue['user']['login'] ?? null,
|
||||
'author_avatar' => $issue['user']['avatar_url'] ?? null,
|
||||
'repo' => "$owner/$repo",
|
||||
'owner' => $owner,
|
||||
'repo_name' => $repo,
|
||||
'labels' => array_map(fn($l) => [
|
||||
'name' => $l['name'] ?? '',
|
||||
'color' => $l['color'] ?? '888888',
|
||||
], $issue['labels'] ?? []),
|
||||
'assignees' => array_map(fn($a) => $a['login'] ?? '', $issue['assignees'] ?? []),
|
||||
'milestone' => $issue['milestone']['title'] ?? null,
|
||||
],
|
||||
'comments' => array_map(fn($c) => [
|
||||
'id' => $c['id'] ?? null,
|
||||
'author' => $c['user']['login'] ?? null,
|
||||
'body' => $c['body'] ?? '',
|
||||
'created_at' => $c['created_at'] ?? null,
|
||||
'updated_at' => $c['updated_at'] ?? null,
|
||||
], $comments),
|
||||
]);
|
||||
} catch (GiteaApiException $e) {
|
||||
giteaJson(['ok' => false, 'error' => "HTTP {$e->statusCode}: " . substr($e->body, 0, 300)], 200);
|
||||
} catch (\Throwable $e) {
|
||||
giteaJson(['ok' => false, 'error' => $e->getMessage()], 200);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Issue write operations ──
|
||||
|
||||
if (preg_match('#^/api/gitea/issue/(\d+)/([^/]+)/([^/]+)/(\d+)/comment$#', $route, $m) && $method === 'POST') {
|
||||
$accId = (int)$m[1]; $owner = urldecode($m[2]); $repo = urldecode($m[3]); $index = (int)$m[4];
|
||||
$row = giteaGetAccount($accId);
|
||||
if (!$row) giteaJson(['error' => 'account not found'], 404);
|
||||
$input = json_decode(file_get_contents('php://input'), true) ?: [];
|
||||
$body = trim((string)($input['body'] ?? ''));
|
||||
if ($body === '') giteaJson(['error' => 'comment body required'], 400);
|
||||
try {
|
||||
$client = giteaOpenClient($row);
|
||||
$created = $client->createIssueComment($owner, $repo, $index, $body);
|
||||
giteaJson([
|
||||
'ok' => true,
|
||||
'comment' => [
|
||||
'id' => $created['id'] ?? null,
|
||||
'author' => $created['user']['login'] ?? null,
|
||||
'body' => $created['body'] ?? '',
|
||||
'created_at' => $created['created_at'] ?? null,
|
||||
],
|
||||
]);
|
||||
} catch (GiteaApiException $e) {
|
||||
giteaJson(['ok' => false, 'error' => "HTTP {$e->statusCode}: " . substr($e->body, 0, 300)], 200);
|
||||
} catch (\Throwable $e) {
|
||||
giteaJson(['ok' => false, 'error' => $e->getMessage()], 200);
|
||||
}
|
||||
}
|
||||
|
||||
if (preg_match('#^/api/gitea/issue/(\d+)/([^/]+)/([^/]+)/(\d+)$#', $route, $m) && $method === 'PATCH') {
|
||||
$accId = (int)$m[1]; $owner = urldecode($m[2]); $repo = urldecode($m[3]); $index = (int)$m[4];
|
||||
$row = giteaGetAccount($accId);
|
||||
if (!$row) giteaJson(['error' => 'account not found'], 404);
|
||||
$input = json_decode(file_get_contents('php://input'), true) ?: [];
|
||||
$changes = [];
|
||||
foreach (['title', 'body', 'state', 'assignees', 'milestone', 'due_date', 'unset_due_date'] as $k) {
|
||||
if (array_key_exists($k, $input)) $changes[$k] = $input[$k];
|
||||
}
|
||||
if (!$changes) giteaJson(['error' => 'no changes'], 400);
|
||||
if (isset($changes['state']) && !in_array($changes['state'], ['open', 'closed'], true)) {
|
||||
giteaJson(['error' => 'invalid state'], 400);
|
||||
}
|
||||
try {
|
||||
$client = giteaOpenClient($row);
|
||||
$updated = $client->editIssue($owner, $repo, $index, $changes);
|
||||
giteaJson([
|
||||
'ok' => true,
|
||||
'issue' => [
|
||||
'title' => $updated['title'] ?? '',
|
||||
'body' => $updated['body'] ?? '',
|
||||
'state' => $updated['state'] ?? 'open',
|
||||
'updated_at' => $updated['updated_at'] ?? null,
|
||||
'closed_at' => $updated['closed_at'] ?? null,
|
||||
],
|
||||
]);
|
||||
} catch (GiteaApiException $e) {
|
||||
giteaJson(['ok' => false, 'error' => "HTTP {$e->statusCode}: " . substr($e->body, 0, 300)], 200);
|
||||
} catch (\Throwable $e) {
|
||||
giteaJson(['ok' => false, 'error' => $e->getMessage()], 200);
|
||||
}
|
||||
}
|
||||
|
||||
if (preg_match('#^/api/gitea/issue/(\d+)/([^/]+)/([^/]+)/(\d+)/labels$#', $route, $m) && $method === 'PUT') {
|
||||
$accId = (int)$m[1]; $owner = urldecode($m[2]); $repo = urldecode($m[3]); $index = (int)$m[4];
|
||||
$row = giteaGetAccount($accId);
|
||||
if (!$row) giteaJson(['error' => 'account not found'], 404);
|
||||
$input = json_decode(file_get_contents('php://input'), true) ?: [];
|
||||
$labels = $input['labels'] ?? [];
|
||||
if (!is_array($labels)) giteaJson(['error' => 'labels array required'], 400);
|
||||
try {
|
||||
$client = giteaOpenClient($row);
|
||||
$resulting = $client->replaceIssueLabels($owner, $repo, $index, $labels);
|
||||
giteaJson([
|
||||
'ok' => true,
|
||||
'labels' => array_map(fn($l) => [
|
||||
'id' => $l['id'] ?? null,
|
||||
'name' => $l['name'] ?? '',
|
||||
'color' => $l['color'] ?? '888888',
|
||||
], $resulting),
|
||||
]);
|
||||
} catch (GiteaApiException $e) {
|
||||
giteaJson(['ok' => false, 'error' => "HTTP {$e->statusCode}: " . substr($e->body, 0, 300)], 200);
|
||||
} catch (\Throwable $e) {
|
||||
giteaJson(['ok' => false, 'error' => $e->getMessage()], 200);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Attachment proxy ──
|
||||
// Gitea attachments live under /attachments/<uuid> on the gitea instance
|
||||
// and require the auth token (or session cookie). The browser can't fetch
|
||||
// them directly (CORS + no token), so hyprcrd proxies them.
|
||||
|
||||
if (preg_match('#^/api/gitea/attachment/(\d+)/(.+)$#', $route, $m) && $method === 'GET') {
|
||||
$accId = (int)$m[1];
|
||||
$path = $m[2]; // e.g. "attachments/abc-123-uuid" or "user/repo/raw/branch/file.png"
|
||||
$row = giteaGetAccount($accId);
|
||||
if (!$row) { http_response_code(404); exit; }
|
||||
|
||||
// Build target URL on the gitea instance
|
||||
$url = rtrim($row['base_url'], '/') . '/' . ltrim($path, '/');
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_TIMEOUT => 30,
|
||||
CURLOPT_SSL_VERIFYPEER => true,
|
||||
CURLOPT_SSL_VERIFYHOST => 2,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Authorization: token ' . giteaDecrypt($row['token']),
|
||||
'User-Agent: hyprcrd-gitea-plugin/1.0',
|
||||
],
|
||||
]);
|
||||
$body = curl_exec($ch);
|
||||
if ($body === false) {
|
||||
$err = curl_error($ch);
|
||||
curl_close($ch);
|
||||
http_response_code(502);
|
||||
header('Content-Type: text/plain');
|
||||
echo "Could not reach gitea: $err";
|
||||
exit;
|
||||
}
|
||||
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE) ?: 'application/octet-stream';
|
||||
curl_close($ch);
|
||||
|
||||
if ($code >= 400) {
|
||||
http_response_code($code);
|
||||
header('Content-Type: text/plain');
|
||||
echo "Gitea returned HTTP $code";
|
||||
exit;
|
||||
}
|
||||
|
||||
// Strip any default headers PHP might emit so we can replace them
|
||||
header_remove('Pragma');
|
||||
header_remove('Expires');
|
||||
header('Content-Type: ' . $contentType);
|
||||
header('Cache-Control: private, max-age=3600');
|
||||
header('Content-Length: ' . strlen($body));
|
||||
echo $body;
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── Create issue ──
|
||||
|
||||
if (preg_match('#^/api/gitea/repo/(\d+)/([^/]+)/([^/]+)/issues$#', $route, $m) && $method === 'POST') {
|
||||
$accId = (int)$m[1]; $owner = urldecode($m[2]); $repo = urldecode($m[3]);
|
||||
$row = giteaGetAccount($accId);
|
||||
if (!$row) giteaJson(['error' => 'account not found'], 404);
|
||||
$input = json_decode(file_get_contents('php://input'), true) ?: [];
|
||||
$title = trim((string)($input['title'] ?? ''));
|
||||
if ($title === '') giteaJson(['error' => 'title is required'], 400);
|
||||
$opts = ['title' => $title];
|
||||
if (array_key_exists('body', $input)) $opts['body'] = (string)$input['body'];
|
||||
if (array_key_exists('labels', $input) && is_array($input['labels'])) {
|
||||
$opts['labels'] = array_map('intval', $input['labels']);
|
||||
}
|
||||
if (array_key_exists('assignees', $input) && is_array($input['assignees'])) {
|
||||
$opts['assignees'] = array_values(array_filter(array_map('strval', $input['assignees'])));
|
||||
}
|
||||
if (!empty($input['milestone'])) $opts['milestone'] = (int)$input['milestone'];
|
||||
if (!empty($input['due_date'])) $opts['due_date'] = (string)$input['due_date'];
|
||||
|
||||
try {
|
||||
$client = giteaOpenClient($row);
|
||||
$created = $client->createIssue($owner, $repo, $opts);
|
||||
giteaJson([
|
||||
'ok' => true,
|
||||
'issue' => [
|
||||
'id' => $created['id'] ?? null,
|
||||
'number' => $created['number'] ?? null,
|
||||
'title' => $created['title'] ?? '',
|
||||
'state' => $created['state'] ?? 'open',
|
||||
'html_url' => $created['html_url'] ?? null,
|
||||
'owner' => $owner,
|
||||
'repo_name' => $repo,
|
||||
],
|
||||
]);
|
||||
} catch (GiteaApiException $e) {
|
||||
giteaJson(['ok' => false, 'error' => "HTTP {$e->statusCode}: " . substr($e->body, 0, 300)], 200);
|
||||
} catch (\Throwable $e) {
|
||||
giteaJson(['ok' => false, 'error' => $e->getMessage()], 200);
|
||||
}
|
||||
}
|
||||
|
||||
// ── User repos (for the repo picker) ──
|
||||
|
||||
if ($route === '/api/gitea/repos' && $method === 'GET') {
|
||||
$accId = (int)($_GET['account'] ?? 0);
|
||||
$row = giteaGetAccount($accId);
|
||||
if (!$row) giteaJson(['error' => 'account not found'], 404);
|
||||
try {
|
||||
$client = giteaOpenClient($row);
|
||||
$repos = $client->listMyRepos(1, 100);
|
||||
$mapped = array_map(fn($r) => [
|
||||
'id' => $r['id'] ?? null,
|
||||
'full_name' => $r['full_name'] ?? null,
|
||||
'name' => $r['name'] ?? null,
|
||||
'owner' => $r['owner']['login'] ?? null,
|
||||
'description' => $r['description'] ?? '',
|
||||
'private' => $r['private'] ?? false,
|
||||
'archived' => $r['archived'] ?? false,
|
||||
'permissions' => $r['permissions'] ?? null,
|
||||
], $repos);
|
||||
// Filter out archived repos and ones without push permission
|
||||
$mapped = array_values(array_filter($mapped, function ($r) {
|
||||
if ($r['archived']) return false;
|
||||
$p = $r['permissions'] ?? null;
|
||||
if ($p && isset($p['push'])) return $p['push'] || $p['admin'] ?? false;
|
||||
return true; // unknown permissions = include
|
||||
}));
|
||||
giteaJson(['ok' => true, 'repos' => $mapped]);
|
||||
} catch (GiteaApiException $e) {
|
||||
giteaJson(['ok' => false, 'error' => "HTTP {$e->statusCode}: " . substr($e->body, 0, 300)], 200);
|
||||
} catch (\Throwable $e) {
|
||||
giteaJson(['ok' => false, 'error' => $e->getMessage()], 200);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Repo metadata ──
|
||||
|
||||
if (preg_match('#^/api/gitea/repo/(\d+)/([^/]+)/([^/]+)/labels$#', $route, $m) && $method === 'GET') {
|
||||
$accId = (int)$m[1]; $owner = urldecode($m[2]); $repo = urldecode($m[3]);
|
||||
$row = giteaGetAccount($accId);
|
||||
if (!$row) giteaJson(['error' => 'account not found'], 404);
|
||||
try {
|
||||
$client = giteaOpenClient($row);
|
||||
$labels = $client->listRepoLabels($owner, $repo);
|
||||
giteaJson([
|
||||
'ok' => true,
|
||||
'labels' => array_map(fn($l) => [
|
||||
'id' => $l['id'] ?? null,
|
||||
'name' => $l['name'] ?? '',
|
||||
'color' => $l['color'] ?? '888888',
|
||||
'description' => $l['description'] ?? '',
|
||||
], $labels),
|
||||
]);
|
||||
} catch (GiteaApiException $e) {
|
||||
giteaJson(['ok' => false, 'error' => "HTTP {$e->statusCode}: " . substr($e->body, 0, 300)], 200);
|
||||
} catch (\Throwable $e) {
|
||||
giteaJson(['ok' => false, 'error' => $e->getMessage()], 200);
|
||||
}
|
||||
}
|
||||
|
||||
if (preg_match('#^/api/gitea/accounts/(\d+)/test$#', $route, $m) && $method === 'POST') {
|
||||
$id = (int)$m[1];
|
||||
$row = giteaGetAccount($id);
|
||||
|
||||
Reference in new issue
Block a user