diff --git a/api/.env.example.ini b/api/.env.example.ini index 265d5ce..04c3c11 100755 --- a/api/.env.example.ini +++ b/api/.env.example.ini @@ -7,6 +7,10 @@ pass = "" vlw = "" battlestation = "" +; Forgejo instance config +[forgejo] +base_url = "" + ; Forgejo language chart endpoints config [about_languages] ; CSV of Forgejo profiles to include public source repositories from diff --git a/api/composer.lock b/api/composer.lock index d46e22b..3887c2d 100755 --- a/api/composer.lock +++ b/api/composer.lock @@ -8,17 +8,11 @@ "packages": [ { "name": "reflect/plugin-rules", - "version": "1.5.0", + "version": "1.5.1", "source": { "type": "git", - "url": "https://github.com/VictorWesterlund/reflect-rules-plugin.git", - "reference": "9c837fd1944133edfed70a63ce8b32bf67f0d94b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/VictorWesterlund/reflect-rules-plugin/zipball/9c837fd1944133edfed70a63ce8b32bf67f0d94b", - "reference": "9c837fd1944133edfed70a63ce8b32bf67f0d94b", - "shasum": "" + "url": "https://codeberg.org/reflect/reflect-rules-plugin", + "reference": "df150f0d860dbc2311e5e2fcb2fac36ee52db56b" }, "type": "library", "autoload": { @@ -37,11 +31,7 @@ } ], "description": "Add request search paramter and request body constraints to an API built with Reflect", - "support": { - "issues": "https://github.com/VictorWesterlund/reflect-rules-plugin/issues", - "source": "https://github.com/VictorWesterlund/reflect-rules-plugin/tree/1.5.0" - }, - "time": "2024-01-17T11:07:44+00:00" + "time": "2024-11-20T10:39:33+00:00" }, { "name": "victorwesterlund/xenum", @@ -121,5 +111,5 @@ "prefer-lowest": false, "platform": [], "platform-dev": [], - "plugin-api-version": "2.0.0" + "plugin-api-version": "2.3.0" } diff --git a/api/endpoints/about/languages/DELETE.php b/api/endpoints/about/languages/DELETE.php new file mode 100644 index 0000000..c7a4dab --- /dev/null +++ b/api/endpoints/about/languages/DELETE.php @@ -0,0 +1,22 @@ +ruleset = new Ruleset(strict: true); + + $this->ruleset->GET([ + (new Rules(PARM_FORCE_RECACHE)) + ->type(Type::BOOLEAN) + ->default(false) + ]); + + $this->ruleset->validate_or_exit(); + } private static function cache_exists(): bool { - return file_exists($_ENV["about_languages"]["cache_file"]); + return file_exists($_ENV["forgejo_languages"]["cache_file"]); } private static function load_cache(): array { - return json_decode(file_get_contents($_GET["about_languages"]["cache_file"])); + return json_decode(file_get_contents($_ENV["forgejo_languages"]["cache_file"]), true); } public function main(): Response { - $bitch = (new Call(Endpoints::ABOUT_LANGUAGES->value))->post(["FUCK YOU"]); - // Return 404 Not Found if response array is empty + // Delete cache file if force flag is set + if ($_GET[PARM_FORCE_RECACHE]) { + (new Call(Endpoints::ABOUT_LANGUAGES->value))->delete(); + } + return self::cache_exists() + // Return languages from cache ? new Response(self::load_cache()) - : $bitch; + // Fetch and return languages (and generate cache file if enabled) + : new Response((new Call(Endpoints::ABOUT_LANGUAGES->value))->post()); } } \ No newline at end of file diff --git a/api/endpoints/about/languages/POST.php b/api/endpoints/about/languages/POST.php index 43c0b71..1b3eb0f 100644 --- a/api/endpoints/about/languages/POST.php +++ b/api/endpoints/about/languages/POST.php @@ -4,10 +4,94 @@ use Reflect\Path; use Reflect\Response; + const ERRNO_CACHE_FAILED = 0; + + const FORGEJO_ENDPOINT_USER = "/api/v1/users/%s"; + const FORGEJO_ENDPOINT_SEARCH = "/api/v1/repos/search?uid=%s"; + class POST_AboutLanguages { + private array $errors = []; + // Tally of all languages used in all configured repositories + private array $languages = []; + public function __construct() {} + // Fetch JSON from URL + private static function fetch_json(string $url): array { + return json_decode(file_get_contents($url), true); + } + + // Fetch JSON from a Forgejo endpoint + private static function fetch_endpoint(string $endpoint): array { + $url = $_ENV["forgejo"]["base_url"] . $endpoint; + return self::fetch_json($url); + } + + // Write $this->languages to a JSON file + private function cache_languages(): int|bool { + $cache_filename = $_ENV["forgejo_languages"]["cache_file"]; + + // Bail out if cache file is not configured + if (empty($cache_filename)) { + return true; + } + + return file_put_contents($cache_filename, json_encode($this->languages)); + } + + // Fetch and add languages to total from a fully-qualified Forgejo URL + private function add_repository_languages(string $url): void { + foreach(self::fetch_json($url) as $language => $bytes) { + // Create key for language if it doesn't exist + if (!array_key_exists($language, $this->languages)) { + $this->languages[$language] = 0; + } + + // Add bytes to language in total + $this->languages[$language] += $bytes; + } + } + + // Tally languages from public repositories for user id + private function add_public_repositores(int $uid): bool { + $resp = self::fetch_endpoint(sprintf(FORGEJO_ENDPOINT_SEARCH, $uid)); + + // Bail out if request failed or if response indicated a problem + if (!$resp or $resp["ok"] === false) { + return false; + } + + // Add langauges for each public repository + foreach ($resp["data"] as $repo) { + $this->add_repository_languages($repo["languages_url"]); + } + + return true; + } + + // Add languages from all public repositories for profiles in config + private function add_repositories_from_config_profiles(): void { + foreach(explode(",", $_ENV["forgejo_languages"]["scan_profiles"]) as $profile) { + // Resolve user data from username + $user = self::fetch_endpoint(sprintf(FORGEJO_ENDPOINT_USER, $profile)); + + if (!$this->add_public_repositores($user["id"])) { + $this->errors[] = $profile; + } + } + } + public function main(): Response { - return new Response("We have a problem with Reflect again"); + $this->add_repositories_from_config_profiles(); + + // Sort langauges bytes tally by largest in descending order + arsort($this->languages); + + // Save languages to cache + if (!$this->cache_languages()) { + $this->errors[] = ERRNO_CACHE_FAILED; + } + + return new Response($this->languages); } } \ No newline at end of file