gitea
This commit is contained in:
1 parent
cc97754ae6
commit
86508ffcca
13 files changed
+945
-35
No files matched your search
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Gitea Plugin: backend.
|
||||
*
|
||||
* Replaces the standalone Python `gitea-issue-client` tool. MVP scope:
|
||||
* accounts CRUD + connection test. Issue browsing/extraction comes once
|
||||
* the connection layer is verified. See TODO.md for the deferred items.
|
||||
*
|
||||
* Routes:
|
||||
* GET /api/gitea/accounts → list accounts (no tokens)
|
||||
* POST /api/gitea/accounts → create account
|
||||
* PATCH /api/gitea/accounts/{id} → update (omit token to keep)
|
||||
* DELETE /api/gitea/accounts/{id} → remove
|
||||
* POST /api/gitea/accounts/{id}/test → verify token via /api/v1/user
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/GiteaClient.php';
|
||||
|
||||
// ── Schema migration ──────────────────────────────────────────────
|
||||
|
||||
(function () {
|
||||
$db = Database::connect();
|
||||
$db->exec("
|
||||
CREATE TABLE IF NOT EXISTS gitea_accounts (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
base_url TEXT NOT NULL,
|
||||
token TEXT NOT NULL,
|
||||
position INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
");
|
||||
})();
|
||||
|
||||
// ── Encryption helpers (mirrors email plugin / CalDavSync) ────────
|
||||
|
||||
function giteaEncKey(): string {
|
||||
$db = Database::connect();
|
||||
$vk = $db->query("SELECT value FROM settings WHERE key = 'vault_key'")->fetchColumn();
|
||||
if ($vk) return substr(hash('sha256', $vk), 0, 32);
|
||||
$hash = $db->query("SELECT value FROM settings WHERE key = 'password_hash'")->fetchColumn();
|
||||
return substr(hash('sha256', $hash ?: 'hyprcrd-default-key'), 0, 32);
|
||||
}
|
||||
|
||||
function giteaEncrypt(string $plain): string {
|
||||
$key = giteaEncKey();
|
||||
$iv = random_bytes(16);
|
||||
$cipher = openssl_encrypt($plain, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
|
||||
return base64_encode($iv . $cipher);
|
||||
}
|
||||
|
||||
function giteaDecrypt(string $encoded): string {
|
||||
$key = giteaEncKey();
|
||||
$raw = base64_decode($encoded);
|
||||
if (!$raw || strlen($raw) < 17) return '';
|
||||
$iv = substr($raw, 0, 16);
|
||||
$cipher = substr($raw, 16);
|
||||
return openssl_decrypt($cipher, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv) ?: '';
|
||||
}
|
||||
|
||||
function giteaJson($data, int $code = 200): void {
|
||||
http_response_code($code);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($data);
|
||||
exit;
|
||||
}
|
||||
|
||||
function giteaGetAccount(int $id): ?array {
|
||||
$db = Database::connect();
|
||||
$stmt = $db->prepare('SELECT * FROM gitea_accounts WHERE id = :id');
|
||||
$stmt->execute(['id' => $id]);
|
||||
return $stmt->fetch() ?: null;
|
||||
}
|
||||
|
||||
function giteaOpenClient(array $row): GiteaClient {
|
||||
return new GiteaClient($row['base_url'], giteaDecrypt($row['token']));
|
||||
}
|
||||
|
||||
// ── Route registration ────────────────────────────────────────────
|
||||
|
||||
PluginManager::instance()->onApiRoute(function (string $route, string $method) {
|
||||
|
||||
// ── Accounts CRUD ──
|
||||
|
||||
if ($route === '/api/gitea/accounts' && $method === 'GET') {
|
||||
$db = Database::connect();
|
||||
$rows = $db->query('SELECT id, name, base_url, position, created_at FROM gitea_accounts ORDER BY position ASC, id ASC')->fetchAll();
|
||||
giteaJson($rows);
|
||||
}
|
||||
|
||||
if ($route === '/api/gitea/accounts' && $method === 'POST') {
|
||||
$input = json_decode(file_get_contents('php://input'), true) ?: [];
|
||||
$name = trim((string)($input['name'] ?? ''));
|
||||
$baseUrl = trim((string)($input['base_url'] ?? ''));
|
||||
$token = (string)($input['token'] ?? '');
|
||||
if ($name === '' || $baseUrl === '' || $token === '') {
|
||||
giteaJson(['error' => 'name, base_url, and token are required'], 400);
|
||||
}
|
||||
// Normalize base URL: strip trailing slash, accept with/without scheme
|
||||
if (!preg_match('#^https?://#i', $baseUrl)) {
|
||||
$baseUrl = 'https://' . $baseUrl;
|
||||
}
|
||||
$baseUrl = rtrim($baseUrl, '/');
|
||||
$db = Database::connect();
|
||||
$stmt = $db->prepare('
|
||||
INSERT INTO gitea_accounts (name, base_url, token)
|
||||
VALUES (:n, :u, :t)
|
||||
');
|
||||
$stmt->execute([
|
||||
'n' => $name,
|
||||
'u' => $baseUrl,
|
||||
't' => giteaEncrypt($token),
|
||||
]);
|
||||
giteaJson(['ok' => true, 'id' => (int)$db->lastInsertId()]);
|
||||
}
|
||||
|
||||
if (preg_match('#^/api/gitea/accounts/(\d+)$#', $route, $m) && $method === 'PATCH') {
|
||||
$id = (int)$m[1];
|
||||
$row = giteaGetAccount($id);
|
||||
if (!$row) giteaJson(['error' => 'account not found'], 404);
|
||||
$input = json_decode(file_get_contents('php://input'), true) ?: [];
|
||||
$fields = []; $params = ['id' => $id];
|
||||
if (array_key_exists('name', $input)) {
|
||||
$fields[] = 'name = :name'; $params['name'] = trim((string)$input['name']);
|
||||
}
|
||||
if (array_key_exists('base_url', $input)) {
|
||||
$u = trim((string)$input['base_url']);
|
||||
if (!preg_match('#^https?://#i', $u)) $u = 'https://' . $u;
|
||||
$fields[] = 'base_url = :base_url'; $params['base_url'] = rtrim($u, '/');
|
||||
}
|
||||
if (!empty($input['token'])) {
|
||||
$fields[] = 'token = :token'; $params['token'] = giteaEncrypt($input['token']);
|
||||
}
|
||||
if (!$fields) giteaJson(['ok' => true]);
|
||||
$db = Database::connect();
|
||||
$db->prepare('UPDATE gitea_accounts SET ' . implode(', ', $fields) . ' WHERE id = :id')->execute($params);
|
||||
giteaJson(['ok' => true]);
|
||||
}
|
||||
|
||||
if (preg_match('#^/api/gitea/accounts/(\d+)$#', $route, $m) && $method === 'DELETE') {
|
||||
$id = (int)$m[1];
|
||||
$db = Database::connect();
|
||||
$db->prepare('DELETE FROM gitea_accounts WHERE id = :id')->execute(['id' => $id]);
|
||||
giteaJson(['ok' => true]);
|
||||
}
|
||||
|
||||
if (preg_match('#^/api/gitea/accounts/(\d+)/test$#', $route, $m) && $method === 'POST') {
|
||||
$id = (int)$m[1];
|
||||
$row = giteaGetAccount($id);
|
||||
if (!$row) giteaJson(['error' => 'account not found'], 404);
|
||||
try {
|
||||
$client = giteaOpenClient($row);
|
||||
$user = $client->testConnection();
|
||||
giteaJson([
|
||||
'ok' => true,
|
||||
'user' => [
|
||||
'login' => $user['login'] ?? null,
|
||||
'full_name' => $user['full_name'] ?? null,
|
||||
'email' => $user['email'] ?? null,
|
||||
'avatar_url' => $user['avatar_url'] ?? null,
|
||||
],
|
||||
'base_url' => $row['base_url'],
|
||||
]);
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user