* * This file is part of a Moko Consulting project. * * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION * DEFGROUP: MokoStandards.Enterprise.Platform * INGROUP: MokoStandards.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /lib/Enterprise/PlatformAdapterFactory.php * BRIEF: Factory for creating platform-specific GitPlatformAdapter instances */ declare(strict_types=1); namespace MokoEnterprise; use RuntimeException; /** * Factory for creating GitPlatformAdapter instances. * * Reads GIT_PLATFORM env var (default: 'github') and constructs * the appropriate adapter with correct base URL, auth scheme, and token. * * Usage: * ```php * $config = Config::load(); * $adapter = PlatformAdapterFactory::create($config); * $repos = $adapter->listOrgRepos('mokoconsulting-tech'); * ``` * * @package MokoStandards\Enterprise * @version 04.06.10 * * @since 04.00.00 */ class PlatformAdapterFactory { /** * Create a GitPlatformAdapter based on configuration. * * @param Config $config Configuration instance * @param string|null $platformOverride Force a specific platform ('github' or 'gitea') * @return GitPlatformAdapter The constructed adapter * @throws RuntimeException If the platform is not supported or token is missing */ public static function create(Config $config, ?string $platformOverride = null): GitPlatformAdapter { $platform = $platformOverride ?? $config->getString('platform', 'gitea'); return match ($platform) { 'github' => self::createGitHubAdapter($config), 'gitea' => self::createMokoGiteaAdapter($config), default => throw new RuntimeException("Unsupported git platform: {$platform}. Use 'github' or 'gitea'."), }; } /** * Create a GitHubAdapter with configured ApiClient. * * @param Config $config Configuration instance * @return GitHubAdapter Configured GitHub adapter * @throws RuntimeException If GitHub token is not available */ private static function createGitHubAdapter(Config $config): GitHubAdapter { $token = $config->getString('github.token', ''); if (empty($token)) { throw new RuntimeException( 'GitHub token not found. Set GH_TOKEN, GITHUB_TOKEN, or authenticate with `gh auth login`.' ); } $apiClient = new ApiClient( baseUrl: 'https://git.mokoconsulting.tech/api/v1', authToken: $token, maxRequestsPerHour: $config->getInt('github.rate_limit', 5000), maxRetries: $config->getInt('github.max_retries', 3), authScheme: 'Bearer' ); return new GitHubAdapter($apiClient); } /** * Create a MokoGiteaAdapter with configured ApiClient. * * @param Config $config Configuration instance * @return MokoGiteaAdapter Configured Gitea adapter * @throws RuntimeException If Gitea token is not available */ private static function createMokoGiteaAdapter(Config $config): MokoGiteaAdapter { $token = $config->getString('gitea.token', ''); if (empty($token)) { throw new RuntimeException( 'Gitea token not found. Set GA_TOKEN environment variable.' ); } $giteaUrl = $config->getString('gitea.url', 'https://git.mokoconsulting.tech'); $apiBaseUrl = rtrim($giteaUrl, '/') . '/api/v1'; $apiClient = new ApiClient( baseUrl: $apiBaseUrl, authToken: $token, maxRequestsPerHour: $config->getInt('gitea.rate_limit', 5000), maxRetries: $config->getInt('gitea.max_retries', 3), authScheme: 'token' ); return new MokoGiteaAdapter($apiClient, $apiBaseUrl); } /** * Create adapters for both platforms (useful during migration). * * @param Config $config Configuration instance * @return array{github: GitHubAdapter, gitea: MokoGiteaAdapter} Both adapters * @throws RuntimeException If either token is missing */ public static function createBoth(Config $config): array { return [ 'github' => self::createGitHubAdapter($config), 'gitea' => self::createMokoGiteaAdapter($config), ]; } /** * Sync a file between Gitea (primary) and GitHub (mirror) for a given repo. * * Reads the file from Gitea and pushes it to GitHub, ensuring both platforms * serve identical content. Commonly used for updates.xml sync after releases. * * @param Config $config Configuration instance * @param string $repo Repository name * @param string $branch Branch to sync (default: 'main') * @param string $filePath Path to the file (default: 'updates.xml') * @return bool True if sync succeeded or file was already identical * @throws RuntimeException If either platform is unreachable */ public static function syncUpdatesBetweenPlatforms( Config $config, string $repo, string $branch = 'main', string $filePath = 'updates.xml' ): bool { $adapters = self::createBoth($config); $giteaOrg = $config->getString('gitea.organization', 'mokoconsulting-tech'); $githubOrg = $config->getString('github.organization', 'mokoconsulting-tech'); // Read from Gitea (primary) try { $giteaFile = $adapters['gitea']->getFileContents($giteaOrg, $repo, $filePath, $branch); } catch (\Exception $e) { throw new RuntimeException("Failed to read {$filePath} from Gitea ({$giteaOrg}/{$repo}): " . $e->getMessage()); } $giteaContent = base64_decode($giteaFile['content'] ?? ''); if (empty($giteaContent)) { return false; } // Read from GitHub (mirror) to check if update is needed $githubSha = null; try { $githubFile = $adapters['github']->getFileContents($githubOrg, $repo, $filePath, $branch); $githubContent = base64_decode($githubFile['content'] ?? ''); $githubSha = $githubFile['sha'] ?? null; if ($githubContent === $giteaContent) { return true; } } catch (\Exception $e) { $adapters['github']->getApiClient()->resetCircuitBreaker(); } $adapters['github']->createOrUpdateFile( $githubOrg, $repo, $filePath, $giteaContent, "chore(sync): sync {$filePath} from Gitea primary", $githubSha, $branch ); return true; } }