475 lines
14 KiB
Plaintext
475 lines
14 KiB
Plaintext
---
|
|
description: RESTful API design, routing patterns, webhooks, and HTTP communication
|
|
globs: routes/*.php, app/Http/Controllers/**/*.php, app/Http/Resources/*.php, app/Http/Requests/*.php
|
|
alwaysApply: false
|
|
---
|
|
# Coolify API & Routing Architecture
|
|
|
|
## Routing Structure
|
|
|
|
Coolify implements **multi-layered routing** with web interfaces, RESTful APIs, webhook endpoints, and real-time communication channels.
|
|
|
|
## Route Files
|
|
|
|
### Core Route Definitions
|
|
- **[routes/web.php](mdc:routes/web.php)** - Web application routes (21KB, 362 lines)
|
|
- **[routes/api.php](mdc:routes/api.php)** - RESTful API endpoints (13KB, 185 lines)
|
|
- **[routes/webhooks.php](mdc:routes/webhooks.php)** - Webhook receivers (815B, 22 lines)
|
|
- **[routes/channels.php](mdc:routes/channels.php)** - WebSocket channel definitions (829B, 33 lines)
|
|
- **[routes/console.php](mdc:routes/console.php)** - Artisan command routes (592B, 20 lines)
|
|
|
|
## Web Application Routing
|
|
|
|
### Authentication Routes
|
|
```php
|
|
// Laravel Fortify authentication
|
|
Route::middleware('guest')->group(function () {
|
|
Route::get('/login', [AuthController::class, 'login']);
|
|
Route::get('/register', [AuthController::class, 'register']);
|
|
Route::get('/forgot-password', [AuthController::class, 'forgotPassword']);
|
|
});
|
|
```
|
|
|
|
### Dashboard & Core Features
|
|
```php
|
|
// Main application routes
|
|
Route::middleware(['auth', 'verified'])->group(function () {
|
|
Route::get('/dashboard', Dashboard::class)->name('dashboard');
|
|
Route::get('/projects', ProjectIndex::class)->name('projects');
|
|
Route::get('/servers', ServerIndex::class)->name('servers');
|
|
Route::get('/teams', TeamIndex::class)->name('teams');
|
|
});
|
|
```
|
|
|
|
### Resource Management Routes
|
|
```php
|
|
// Server management
|
|
Route::prefix('servers')->group(function () {
|
|
Route::get('/{server}', ServerShow::class)->name('server.show');
|
|
Route::get('/{server}/edit', ServerEdit::class)->name('server.edit');
|
|
Route::get('/{server}/logs', ServerLogs::class)->name('server.logs');
|
|
});
|
|
|
|
// Application management
|
|
Route::prefix('applications')->group(function () {
|
|
Route::get('/{application}', ApplicationShow::class)->name('application.show');
|
|
Route::get('/{application}/deployments', ApplicationDeployments::class);
|
|
Route::get('/{application}/environment-variables', ApplicationEnvironmentVariables::class);
|
|
Route::get('/{application}/logs', ApplicationLogs::class);
|
|
});
|
|
```
|
|
|
|
## RESTful API Architecture
|
|
|
|
### API Versioning
|
|
```php
|
|
// API route structure
|
|
Route::prefix('v1')->group(function () {
|
|
// Application endpoints
|
|
Route::apiResource('applications', ApplicationController::class);
|
|
Route::apiResource('servers', ServerController::class);
|
|
Route::apiResource('teams', TeamController::class);
|
|
});
|
|
```
|
|
|
|
### Authentication & Authorization
|
|
```php
|
|
// Sanctum API authentication
|
|
Route::middleware('auth:sanctum')->group(function () {
|
|
Route::get('/user', function (Request $request) {
|
|
return $request->user();
|
|
});
|
|
|
|
// Team-scoped resources
|
|
Route::middleware('team.access')->group(function () {
|
|
Route::apiResource('applications', ApplicationController::class);
|
|
});
|
|
});
|
|
```
|
|
|
|
### Application Management API
|
|
```php
|
|
// Application CRUD operations
|
|
Route::prefix('applications')->group(function () {
|
|
Route::get('/', [ApplicationController::class, 'index']);
|
|
Route::post('/', [ApplicationController::class, 'store']);
|
|
Route::get('/{application}', [ApplicationController::class, 'show']);
|
|
Route::patch('/{application}', [ApplicationController::class, 'update']);
|
|
Route::delete('/{application}', [ApplicationController::class, 'destroy']);
|
|
|
|
// Deployment operations
|
|
Route::post('/{application}/deploy', [ApplicationController::class, 'deploy']);
|
|
Route::post('/{application}/restart', [ApplicationController::class, 'restart']);
|
|
Route::post('/{application}/stop', [ApplicationController::class, 'stop']);
|
|
Route::get('/{application}/logs', [ApplicationController::class, 'logs']);
|
|
});
|
|
```
|
|
|
|
### Server Management API
|
|
```php
|
|
// Server operations
|
|
Route::prefix('servers')->group(function () {
|
|
Route::get('/', [ServerController::class, 'index']);
|
|
Route::post('/', [ServerController::class, 'store']);
|
|
Route::get('/{server}', [ServerController::class, 'show']);
|
|
Route::patch('/{server}', [ServerController::class, 'update']);
|
|
Route::delete('/{server}', [ServerController::class, 'destroy']);
|
|
|
|
// Server actions
|
|
Route::post('/{server}/validate', [ServerController::class, 'validate']);
|
|
Route::get('/{server}/usage', [ServerController::class, 'usage']);
|
|
Route::post('/{server}/cleanup', [ServerController::class, 'cleanup']);
|
|
});
|
|
```
|
|
|
|
### Database Management API
|
|
```php
|
|
// Database operations
|
|
Route::prefix('databases')->group(function () {
|
|
Route::get('/', [DatabaseController::class, 'index']);
|
|
Route::post('/', [DatabaseController::class, 'store']);
|
|
Route::get('/{database}', [DatabaseController::class, 'show']);
|
|
Route::patch('/{database}', [DatabaseController::class, 'update']);
|
|
Route::delete('/{database}', [DatabaseController::class, 'destroy']);
|
|
|
|
// Database actions
|
|
Route::post('/{database}/backup', [DatabaseController::class, 'backup']);
|
|
Route::post('/{database}/restore', [DatabaseController::class, 'restore']);
|
|
Route::get('/{database}/logs', [DatabaseController::class, 'logs']);
|
|
});
|
|
```
|
|
|
|
## Webhook Architecture
|
|
|
|
### Git Integration Webhooks
|
|
```php
|
|
// GitHub webhook endpoints
|
|
Route::post('/webhooks/github/{application}', [GitHubWebhookController::class, 'handle'])
|
|
->name('webhooks.github');
|
|
|
|
// GitLab webhook endpoints
|
|
Route::post('/webhooks/gitlab/{application}', [GitLabWebhookController::class, 'handle'])
|
|
->name('webhooks.gitlab');
|
|
|
|
// Generic Git webhooks
|
|
Route::post('/webhooks/git/{application}', [GitWebhookController::class, 'handle'])
|
|
->name('webhooks.git');
|
|
```
|
|
|
|
### Deployment Webhooks
|
|
```php
|
|
// Deployment status webhooks
|
|
Route::post('/webhooks/deployment/{deployment}/success', [DeploymentWebhookController::class, 'success']);
|
|
Route::post('/webhooks/deployment/{deployment}/failure', [DeploymentWebhookController::class, 'failure']);
|
|
Route::post('/webhooks/deployment/{deployment}/progress', [DeploymentWebhookController::class, 'progress']);
|
|
```
|
|
|
|
### Third-Party Integration Webhooks
|
|
```php
|
|
// Monitoring webhooks
|
|
Route::post('/webhooks/monitoring/{server}', [MonitoringWebhookController::class, 'handle']);
|
|
|
|
// Backup status webhooks
|
|
Route::post('/webhooks/backup/{backup}', [BackupWebhookController::class, 'handle']);
|
|
|
|
// SSL certificate webhooks
|
|
Route::post('/webhooks/ssl/{certificate}', [SslWebhookController::class, 'handle']);
|
|
```
|
|
|
|
## WebSocket Channel Definitions
|
|
|
|
### Real-Time Channels
|
|
```php
|
|
// Private channels for team members
|
|
Broadcast::channel('team.{teamId}', function ($user, $teamId) {
|
|
return $user->teams->contains('id', $teamId);
|
|
});
|
|
|
|
// Application deployment channels
|
|
Broadcast::channel('application.{applicationId}', function ($user, $applicationId) {
|
|
return $user->hasAccessToApplication($applicationId);
|
|
});
|
|
|
|
// Server monitoring channels
|
|
Broadcast::channel('server.{serverId}', function ($user, $serverId) {
|
|
return $user->hasAccessToServer($serverId);
|
|
});
|
|
```
|
|
|
|
### Presence Channels
|
|
```php
|
|
// Team collaboration presence
|
|
Broadcast::channel('team.{teamId}.presence', function ($user, $teamId) {
|
|
if ($user->teams->contains('id', $teamId)) {
|
|
return ['id' => $user->id, 'name' => $user->name];
|
|
}
|
|
});
|
|
```
|
|
|
|
## API Controllers
|
|
|
|
### Location: [app/Http/Controllers/Api/](mdc:app/Http/Controllers)
|
|
|
|
#### Resource Controllers
|
|
```php
|
|
class ApplicationController extends Controller
|
|
{
|
|
public function index(Request $request)
|
|
{
|
|
return ApplicationResource::collection(
|
|
$request->user()->currentTeam->applications()
|
|
->with(['server', 'environment'])
|
|
->paginate()
|
|
);
|
|
}
|
|
|
|
public function store(StoreApplicationRequest $request)
|
|
{
|
|
$application = $request->user()->currentTeam
|
|
->applications()
|
|
->create($request->validated());
|
|
|
|
return new ApplicationResource($application);
|
|
}
|
|
|
|
public function deploy(Application $application)
|
|
{
|
|
$deployment = $application->deploy();
|
|
|
|
return response()->json([
|
|
'message' => 'Deployment started',
|
|
'deployment_id' => $deployment->id
|
|
]);
|
|
}
|
|
}
|
|
```
|
|
|
|
### API Responses & Resources
|
|
```php
|
|
// API Resource classes
|
|
class ApplicationResource extends JsonResource
|
|
{
|
|
public function toArray($request)
|
|
{
|
|
return [
|
|
'id' => $this->id,
|
|
'name' => $this->name,
|
|
'fqdn' => $this->fqdn,
|
|
'status' => $this->status,
|
|
'git_repository' => $this->git_repository,
|
|
'git_branch' => $this->git_branch,
|
|
'created_at' => $this->created_at,
|
|
'updated_at' => $this->updated_at,
|
|
'server' => new ServerResource($this->whenLoaded('server')),
|
|
'environment' => new EnvironmentResource($this->whenLoaded('environment')),
|
|
];
|
|
}
|
|
}
|
|
```
|
|
|
|
## API Authentication
|
|
|
|
### Sanctum Token Authentication
|
|
```php
|
|
// API token generation
|
|
Route::post('/auth/tokens', function (Request $request) {
|
|
$request->validate([
|
|
'name' => 'required|string',
|
|
'abilities' => 'array'
|
|
]);
|
|
|
|
$token = $request->user()->createToken(
|
|
$request->name,
|
|
$request->abilities ?? []
|
|
);
|
|
|
|
return response()->json([
|
|
'token' => $token->plainTextToken,
|
|
'abilities' => $token->accessToken->abilities
|
|
]);
|
|
});
|
|
```
|
|
|
|
### Team-Based Authorization
|
|
```php
|
|
// Team access middleware
|
|
class EnsureTeamAccess
|
|
{
|
|
public function handle($request, Closure $next)
|
|
{
|
|
$teamId = $request->route('team');
|
|
|
|
if (!$request->user()->teams->contains('id', $teamId)) {
|
|
abort(403, 'Access denied to team resources');
|
|
}
|
|
|
|
return $next($request);
|
|
}
|
|
}
|
|
```
|
|
|
|
## Rate Limiting
|
|
|
|
### API Rate Limits
|
|
```php
|
|
// API throttling configuration
|
|
RateLimiter::for('api', function (Request $request) {
|
|
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
|
|
});
|
|
|
|
// Deployment rate limiting
|
|
RateLimiter::for('deployments', function (Request $request) {
|
|
return Limit::perMinute(10)->by($request->user()->id);
|
|
});
|
|
```
|
|
|
|
### Webhook Rate Limiting
|
|
```php
|
|
// Webhook throttling
|
|
RateLimiter::for('webhooks', function (Request $request) {
|
|
return Limit::perMinute(100)->by($request->ip());
|
|
});
|
|
```
|
|
|
|
## Route Model Binding
|
|
|
|
### Custom Route Bindings
|
|
```php
|
|
// Custom model binding for applications
|
|
Route::bind('application', function ($value) {
|
|
return Application::where('uuid', $value)
|
|
->orWhere('id', $value)
|
|
->firstOrFail();
|
|
});
|
|
|
|
// Team-scoped model binding
|
|
Route::bind('team_application', function ($value, $route) {
|
|
$teamId = $route->parameter('team');
|
|
return Application::whereHas('environment.project', function ($query) use ($teamId) {
|
|
$query->where('team_id', $teamId);
|
|
})->findOrFail($value);
|
|
});
|
|
```
|
|
|
|
## API Documentation
|
|
|
|
### OpenAPI Specification
|
|
- **[openapi.json](mdc:openapi.json)** - API documentation (373KB, 8316 lines)
|
|
- **[openapi.yaml](mdc:openapi.yaml)** - YAML format documentation (184KB, 5579 lines)
|
|
|
|
### Documentation Generation
|
|
```php
|
|
// Swagger/OpenAPI annotations
|
|
/**
|
|
* @OA\Get(
|
|
* path="/api/v1/applications",
|
|
* summary="List applications",
|
|
* tags={"Applications"},
|
|
* security={{"bearerAuth":{}}},
|
|
* @OA\Response(
|
|
* response=200,
|
|
* description="List of applications",
|
|
* @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/Application"))
|
|
* )
|
|
* )
|
|
*/
|
|
```
|
|
|
|
## Error Handling
|
|
|
|
### API Error Responses
|
|
```php
|
|
// Standardized error response format
|
|
class ApiExceptionHandler
|
|
{
|
|
public function render($request, Throwable $exception)
|
|
{
|
|
if ($request->expectsJson()) {
|
|
return response()->json([
|
|
'message' => $exception->getMessage(),
|
|
'error_code' => $this->getErrorCode($exception),
|
|
'timestamp' => now()->toISOString()
|
|
], $this->getStatusCode($exception));
|
|
}
|
|
|
|
return parent::render($request, $exception);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Validation Error Handling
|
|
```php
|
|
// Form request validation
|
|
class StoreApplicationRequest extends FormRequest
|
|
{
|
|
public function rules()
|
|
{
|
|
return [
|
|
'name' => 'required|string|max:255',
|
|
'git_repository' => 'required|url',
|
|
'git_branch' => 'required|string',
|
|
'server_id' => 'required|exists:servers,id',
|
|
'environment_id' => 'required|exists:environments,id'
|
|
];
|
|
}
|
|
|
|
public function failedValidation(Validator $validator)
|
|
{
|
|
throw new HttpResponseException(
|
|
response()->json([
|
|
'message' => 'Validation failed',
|
|
'errors' => $validator->errors()
|
|
], 422)
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
## Real-Time API Integration
|
|
|
|
### WebSocket Events
|
|
```php
|
|
// Broadcasting deployment events
|
|
class DeploymentStarted implements ShouldBroadcast
|
|
{
|
|
public $application;
|
|
public $deployment;
|
|
|
|
public function broadcastOn()
|
|
{
|
|
return [
|
|
new PrivateChannel("application.{$this->application->id}"),
|
|
new PrivateChannel("team.{$this->application->team->id}")
|
|
];
|
|
}
|
|
|
|
public function broadcastWith()
|
|
{
|
|
return [
|
|
'deployment_id' => $this->deployment->id,
|
|
'status' => 'started',
|
|
'timestamp' => now()
|
|
];
|
|
}
|
|
}
|
|
```
|
|
|
|
### API Event Streaming
|
|
```php
|
|
// Server-Sent Events for real-time updates
|
|
Route::get('/api/v1/applications/{application}/events', function (Application $application) {
|
|
return response()->stream(function () use ($application) {
|
|
while (true) {
|
|
$events = $application->getRecentEvents();
|
|
foreach ($events as $event) {
|
|
echo "data: " . json_encode($event) . "\n\n";
|
|
}
|
|
usleep(1000000); // 1 second
|
|
}
|
|
}, 200, [
|
|
'Content-Type' => 'text/event-stream',
|
|
'Cache-Control' => 'no-cache',
|
|
]);
|
|
});
|
|
```
|