From e47de33caf5186e047e2a5dbfcaeaf988306b093 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 6 Jun 2026 15:00:19 +0200 Subject: [PATCH] feat: add Forgejo saas wrapper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirrors the Bitbucket wrapper against the Forgejo REST API v1: token auth headers, repository existence check, repository creation under the authenticated user or an organization. No instance URL is hardcoded — Forgejo is self-hosted, so every operation takes a host parameter. Exposes both ssh_clone_url and https_clone_url (HTTPS needed in CI without SSH host keys). --- src/byteb4rb1e/utils/saas/forgejo.py | 98 ++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 src/byteb4rb1e/utils/saas/forgejo.py diff --git a/src/byteb4rb1e/utils/saas/forgejo.py b/src/byteb4rb1e/utils/saas/forgejo.py new file mode 100644 index 0000000..db28d5d --- /dev/null +++ b/src/byteb4rb1e/utils/saas/forgejo.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +"""Forgejo REST API v1 wrapper. + +Thin layer over http.py for Forgejo-specific operations: + +- Token authentication +- Repository existence checks +- Repository creation under the authenticated user or an organization +- SSH and HTTPS clone URL construction + +Unlike Bitbucket (one global SaaS instance), Forgejo is self-hosted, +so every operation takes a *host* parameter instead of baking any +specific instance in. +""" + +import json +from typing import Any, Dict, Optional + +from byteb4rb1e.utils.http import client as http_client + + +def api_url(host: str) -> str: + """Return the API base URL for a Forgejo instance.""" + return f"https://{host}/api/v1" + + +def http_headers(token: str) -> Dict[str, str]: + """Construct Forgejo API headers with token auth.""" + return { + "Authorization": f"token {token}", + "Accept": "application/json", + "Content-Type": "application/json", + } + + +def repository_exists( + host: str, + owner: str, + repo_slug: str, + token: str, +) -> bool: + """Check whether a repository exists under the owner.""" + url = f"{api_url(host)}/repos/{owner}/{repo_slug}" + resp = http_client.get(url, headers=http_headers(token)) + return bool(resp.status_code == 200) + + +def create_repository( + host: str, + repo_slug: str, + token: str, + org: Optional[str] = None, + description: str = "", + is_private: bool = True, +) -> http_client.HttpResponse: + """Create a new repository on the Forgejo instance. + + When *org* is given the repository is created in that + organization, otherwise under the authenticated user. + + Returns the API response. Caller should check status_code == 201 + for success. + """ + if org: + url = f"{api_url(host)}/orgs/{org}/repos" + else: + url = f"{api_url(host)}/user/repos" + body: Dict[str, Any] = { + "name": repo_slug, + "private": is_private, + "description": description, + } + return http_client.post( + url, + data=json.dumps(body).encode("utf-8"), + headers=http_headers(token), + ) + + +def ssh_clone_url( + host: str, + owner: str, + repo_slug: str, +) -> str: + """Return the SSH clone URL for a Forgejo repository.""" + return f"git@{host}:{owner}/{repo_slug}.git" + + +def https_clone_url( + host: str, + owner: str, + repo_slug: str, +) -> str: + """Return the HTTPS clone URL for a Forgejo repository. + + Preferred in CI environments without SSH host keys. + """ + return f"https://{host}/{owner}/{repo_slug}.git"