diff --git a/app/Http/Controllers/Api/GithubController.php b/app/Http/Controllers/Api/GithubController.php new file mode 100644 index 000000000..e37ec45dc --- /dev/null +++ b/app/Http/Controllers/Api/GithubController.php @@ -0,0 +1,320 @@ + []], + ], + tags: ['GitHub Apps'], + requestBody: new OA\RequestBody( + description: 'GitHub app creation payload.', + required: true, + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'object', + properties: [ + 'name' => ['type' => 'string', 'description' => 'Name of the GitHub app.'], + 'organization' => ['type' => 'string', 'nullable' => true, 'description' => 'Organization to associate the app with.'], + 'api_url' => ['type' => 'string', 'description' => 'API URL for the GitHub app.'], + 'html_url' => ['type' => 'string', 'description' => 'HTML URL for the GitHub app.'], + 'custom_user' => ['type' => 'string', 'description' => 'Custom user for the app.'], + 'custom_port' => ['type' => 'integer', 'description' => 'Custom port for the app.'], + 'is_system_wide' => ['type' => 'boolean', 'description' => 'Is this app system-wide.'], + ], + required: ['name', 'api_url', 'html_url', 'custom_user', 'custom_port'], + ), + ), + ], + ), + responses: [ + new OA\Response( + response: 201, + description: 'GitHub app created successfully.', + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'object', + properties: [ + 'id' => ['type' => 'integer'], + 'uuid' => ['type' => 'string'], + 'name' => ['type' => 'string'], + 'organization' => ['type' => 'string', 'nullable' => true], + 'api_url' => ['type' => 'string'], + 'html_url' => ['type' => 'string'], + 'custom_user' => ['type' => 'string'], + 'custom_port' => ['type' => 'integer'], + 'team_id' => ['type' => 'integer'], + ] + ) + ), + ] + ), + new OA\Response( + response: 400, + ref: '#/components/responses/400', + ), + new OA\Response( + response: 401, + ref: '#/components/responses/401', + ), + ] + )] + + public function createGitHubApp(Request $request){ + // Extract the team ID from the current session or token + $teamId = currentTeam()->id; + + try { + + + $validator = customApiValidator($request->all(), [ + 'name' => 'required|string|max:255', + 'organization' => 'nullable|string|max:255', + 'api_url' => 'required|string|url', + 'html_url' => 'required|string|url', + 'custom_user' => 'required|string|max:255', + 'custom_port' => 'required|integer', + 'is_system_wide' => 'required|boolean', + + ]); + + // If validation fails, return a 400 error with validation messages + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 400); + } + + // Prepare the payload for creating the GitHub app + $payload = [ + 'name' => $request->input('name'), + 'organization' => $request->input('organization'), + 'api_url' => $request->input('api_url'), + 'html_url' => $request->input('html_url'), + 'custom_user' => $request->input('custom_user'), + 'custom_port' => $request->input('custom_port'), + 'team_id' => $teamId, + ]; + + // If running in cloud environment, include 'is_system_wide' + if (isCloud()) { + $payload['is_system_wide'] = $request->input('is_system_wide'); + } + + // Create the GitHub app in the database + $githubApp = GithubApp::create($payload); + + // Return the newly created GitHub app with a 201 status + return response()->json([ + 'message' => 'GitHub app created successfully.', + 'data' => $githubApp, + ], 201); + + } catch (\Throwable $e) { + // Handle any errors that occur during the process + return response()->json([ + 'message' => 'An error occurred while creating the GitHub app.', + 'error' => $e->getMessage(), + ], 500); + } + } + + #[OA\Get( + path: '/github-apps/{github_app_id}/repositories', + summary: 'Load Repositories for a GitHub App', + description: 'Fetch repositories from GitHub for a given GitHub app.', + operationId: 'load-repositories', + tags: ['GitHub Repositories'], + security: [ + ['bearerAuth' => []], + ], + parameters: [ + new OA\Parameter( + name: 'github_app_id', + in: 'path', + required: true, + schema: new OA\Schema(type: 'integer'), + description: 'GitHub App ID' + ) + ], + responses: [ + new OA\Response( + response: 200, + description: 'Repositories loaded successfully.', + content: new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'object', + properties: [ + 'repositories' => new OA\Items( + type: 'array', + items: new OA\Schema(type: 'object') + ) + ] + ) + ) + ), + new OA\Response( + response: 400, + ref: '#/components/responses/400', + ), + new OA\Response( + response: 401, + ref: '#/components/responses/401', + ) + ] + )] + public function loadRepositories($github_app_id) + { + try { + // Fetch GitHub app by ID + $githubApp = GithubApp::findOrFail($github_app_id); + $token = generate_github_installation_token($githubApp); + $repositories = collect(); + $page = 1; + + // Fetch repositories in a loop to handle pagination + do { + $response = Http::withToken($token)->get("{$githubApp->api_url}/installation/repositories", [ + 'per_page' => 100, + 'page' => $page + ]); + + if ($response->status() !== 200) { + return response()->json([ + 'message' => $response->json()['message'], + ], $response->status()); + } + + $json = $response->json(); + $repositories = $repositories->concat($json['repositories']); + $page++; + } while (count($json['repositories']) > 0); + + return response()->json([ + 'message' => 'Repositories loaded successfully.', + 'repositories' => $repositories->sortBy('name')->values(), + ], 200); + } catch (\Throwable $e) { + return response()->json([ + 'message' => 'An error occurred while loading repositories.', + 'error' => $e->getMessage(), + ], 500); + } + } + + #[OA\Get( + path: '/github-apps/{github_app_id}/repositories/{owner}/{repo}/branches', + summary: 'Load Branches for a GitHub Repository', + description: 'Fetch branches from GitHub for a given repository.', + operationId: 'load-branches', + tags: ['GitHub Branches'], + security: [ + ['bearerAuth' => []], + ], + parameters: [ + new OA\Parameter( + name: 'github_app_id', + in: 'path', + required: true, + schema: new OA\Schema(type: 'integer'), + description: 'GitHub App ID' + ), + new OA\Parameter( + name: 'owner', + in: 'path', + required: true, + schema: new OA\Schema(type: 'string'), + description: 'Repository owner' + ), + new OA\Parameter( + name: 'repo', + in: 'path', + required: true, + schema: new OA\Schema(type: 'string'), + description: 'Repository name' + ), + ], + responses: [ + new OA\Response( + response: 200, + description: 'Branches loaded successfully.', + content: new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'object', + properties: [ + 'branches' => new OA\Items( + type: 'array', + items: new OA\Schema(type: 'object') + ) + ] + ) + ) + ), + new OA\Response( + response: 400, + ref: '#/components/responses/400', + ), + new OA\Response( + response: 401, + ref: '#/components/responses/401', + ), + ] + )] + public function loadBranches($github_app_id, $owner, $repo) + { + try { + // Fetch the GitHub App + $githubApp = GithubApp::findOrFail($github_app_id); + $token = generate_github_installation_token($githubApp); + + // API call to GitHub to load branches + $response = Http::withToken($token)->get("{$githubApp->api_url}/repos/{$owner}/{$repo}/branches"); + + // Handle the response from GitHub API + if ($response->status() !== 200) { + return response()->json([ + 'message' => 'Error loading branches from GitHub.', + 'error' => $response->json('message') + ], $response->status()); + } + + $branches = $response->json(); + + return response()->json([ + 'message' => 'Branches loaded successfully.', + 'branches' => $branches, + ], 200); + + } catch (\Throwable $e) { + return response()->json([ + 'message' => 'An error occurred while loading branches.', + 'error' => $e->getMessage(), + ], 500); + } + } + + +} \ No newline at end of file diff --git a/routes/api.php b/routes/api.php index 066a33c4e..9b6a29f3b 100644 --- a/routes/api.php +++ b/routes/api.php @@ -10,6 +10,7 @@ use App\Http\Controllers\Api\SecurityController; use App\Http\Controllers\Api\ServersController; use App\Http\Controllers\Api\ServicesController; use App\Http\Controllers\Api\TeamController; +use App\Http\Controllers\Api\GithubController; use App\Http\Middleware\ApiAllowed; use App\Http\Middleware\IgnoreReadOnlyApiToken; use App\Http\Middleware\OnlyRootApiToken; @@ -122,7 +123,10 @@ Route::group([ Route::patch('/services/{uuid}/envs/bulk', [ServicesController::class, 'create_bulk_envs'])->middleware([IgnoreReadOnlyApiToken::class]); Route::patch('/services/{uuid}/envs', [ServicesController::class, 'update_env_by_uuid'])->middleware([IgnoreReadOnlyApiToken::class]); Route::delete('/services/{uuid}/envs/{env_uuid}', [ServicesController::class, 'delete_env_by_uuid'])->middleware([IgnoreReadOnlyApiToken::class]); - + Route::post('/github-apps', [GithubController::class, 'createGitHubApp'])->middleware([IgnoreReadOnlyApiToken::class]); + Route::get('/github-apps/{github_app_id}/repositories', [GithubController::class, 'loadRepositories'])->middleware([IgnoreReadOnlyApiToken::class]); + Route::get('/github-apps/{github_app_id}/repositories/{owner}/{repo}/branches', [GithubController::class, 'loadBranches'])->middleware([IgnoreReadOnlyApiToken::class]); + Route::match(['get', 'post'], '/services/{uuid}/start', [ServicesController::class, 'action_deploy'])->middleware([IgnoreReadOnlyApiToken::class]); Route::match(['get', 'post'], '/services/{uuid}/restart', [ServicesController::class, 'action_restart'])->middleware([IgnoreReadOnlyApiToken::class]); Route::match(['get', 'post'], '/services/{uuid}/stop', [ServicesController::class, 'action_stop'])->middleware([IgnoreReadOnlyApiToken::class]);