API Feature (#334)

This commit is contained in:
Saeed Vaziry
2024-11-01 16:49:57 +01:00
committed by GitHub
parent da7b24640e
commit 417bf73e44
143 changed files with 36520 additions and 586 deletions

View File

@ -7,9 +7,11 @@
use App\Models\Service;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Spatie\RouteAttributes\Attributes\Post;
class AgentController extends Controller
{
#[Post('api/servers/{server}/agent/{id}', name: 'api.servers.agent')]
public function __invoke(Request $request, Server $server, int $id): JsonResponse
{
$validated = $this->validate($request, [

View File

@ -0,0 +1,97 @@
<?php
namespace App\Http\Controllers\API;
use App\Actions\CronJob\CreateCronJob;
use App\Actions\CronJob\DeleteCronJob;
use App\Http\Controllers\Controller;
use App\Http\Resources\CronJobResource;
use App\Models\CronJob;
use App\Models\Project;
use App\Models\Server;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;
use Knuckles\Scribe\Attributes\BodyParam;
use Knuckles\Scribe\Attributes\Endpoint;
use Knuckles\Scribe\Attributes\Group;
use Knuckles\Scribe\Attributes\Response;
use Knuckles\Scribe\Attributes\ResponseFromApiResource;
use Spatie\RouteAttributes\Attributes\Delete;
use Spatie\RouteAttributes\Attributes\Get;
use Spatie\RouteAttributes\Attributes\Middleware;
use Spatie\RouteAttributes\Attributes\Post;
use Spatie\RouteAttributes\Attributes\Prefix;
#[Prefix('api/projects/{project}/servers/{server}/cron-jobs')]
#[Middleware(['auth:sanctum', 'can-see-project'])]
#[Group(name: 'cron-jobs')]
class CronJobController extends Controller
{
#[Get('/', name: 'api.projects.servers.cron-jobs', middleware: 'ability:read')]
#[Endpoint(title: 'list', description: 'Get all cron jobs.')]
#[ResponseFromApiResource(CronJobResource::class, CronJob::class, collection: true, paginate: 25)]
public function index(Project $project, Server $server): ResourceCollection
{
$this->authorize('viewAny', [CronJob::class, $server]);
$this->validateRoute($project, $server);
return CronJobResource::collection($server->cronJobs()->simplePaginate(25));
}
#[Post('/', name: 'api.projects.servers.cron-jobs.create', middleware: 'ability:write')]
#[Endpoint(title: 'create', description: 'Create a new cron job.')]
#[BodyParam(name: 'command', required: true)]
#[BodyParam(name: 'user', required: true, enum: ['root', 'vito'])]
#[BodyParam(name: 'frequency', description: 'Frequency of the cron job.', required: true, example: '* * * * *')]
#[ResponseFromApiResource(CronJobResource::class, CronJob::class)]
public function create(Request $request, Project $project, Server $server): CronJobResource
{
$this->authorize('create', [CronJob::class, $server]);
$this->validateRoute($project, $server);
$this->validate($request, CreateCronJob::rules($request->all()));
$cronJob = app(CreateCronJob::class)->create($server, $request->all());
return new CronJobResource($cronJob);
}
#[Get('{cronJob}', name: 'api.projects.servers.cron-jobs.show', middleware: 'ability:read')]
#[Endpoint(title: 'show', description: 'Get a cron job by ID.')]
#[ResponseFromApiResource(CronJobResource::class, CronJob::class)]
public function show(Project $project, Server $server, CronJob $cronJob): CronJobResource
{
$this->authorize('view', [$cronJob, $server]);
$this->validateRoute($project, $server, $cronJob);
return new CronJobResource($cronJob);
}
#[Delete('{cronJob}', name: 'api.projects.servers.cron-jobs.delete', middleware: 'ability:write')]
#[Endpoint(title: 'delete', description: 'Delete cron job.')]
#[Response(status: 204)]
public function delete(Project $project, Server $server, CronJob $cronJob)
{
$this->authorize('delete', [$cronJob, $server]);
$this->validateRoute($project, $server, $cronJob);
app(DeleteCronJob::class)->delete($server, $cronJob);
return response()->noContent();
}
private function validateRoute(Project $project, Server $server, ?CronJob $cronJob = null): void
{
if ($project->id !== $server->project_id) {
abort(404, 'Server not found in project');
}
if ($cronJob && $cronJob->server_id !== $server->id) {
abort(404, 'Firewall rule not found in server');
}
}
}

View File

@ -0,0 +1,94 @@
<?php
namespace App\Http\Controllers\API;
use App\Actions\Database\CreateDatabase;
use App\Http\Controllers\Controller;
use App\Http\Resources\DatabaseResource;
use App\Models\Database;
use App\Models\Project;
use App\Models\Server;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;
use Knuckles\Scribe\Attributes\BodyParam;
use Knuckles\Scribe\Attributes\Endpoint;
use Knuckles\Scribe\Attributes\Group;
use Knuckles\Scribe\Attributes\Response;
use Knuckles\Scribe\Attributes\ResponseFromApiResource;
use Spatie\RouteAttributes\Attributes\Delete;
use Spatie\RouteAttributes\Attributes\Get;
use Spatie\RouteAttributes\Attributes\Middleware;
use Spatie\RouteAttributes\Attributes\Post;
use Spatie\RouteAttributes\Attributes\Prefix;
#[Prefix('api/projects/{project}/servers/{server}/databases')]
#[Middleware(['auth:sanctum', 'can-see-project'])]
#[Group(name: 'databases')]
class DatabaseController extends Controller
{
#[Get('/', name: 'api.projects.servers.databases', middleware: 'ability:read')]
#[Endpoint(title: 'list', description: 'Get all databases.')]
#[ResponseFromApiResource(DatabaseResource::class, Database::class, collection: true, paginate: 25)]
public function index(Project $project, Server $server): ResourceCollection
{
$this->authorize('viewAny', [Database::class, $server]);
$this->validateRoute($project, $server);
return DatabaseResource::collection($server->databases()->simplePaginate(25));
}
#[Post('/', name: 'api.projects.servers.databases.create', middleware: 'ability:write')]
#[Endpoint(title: 'create', description: 'Create a new database.')]
#[BodyParam(name: 'name', required: true)]
#[ResponseFromApiResource(DatabaseResource::class, Database::class)]
public function create(Request $request, Project $project, Server $server): DatabaseResource
{
$this->authorize('create', [Database::class, $server]);
$this->validateRoute($project, $server);
$this->validate($request, CreateDatabase::rules($server, $request->input()));
$database = app(CreateDatabase::class)->create($server, $request->all());
return new DatabaseResource($database);
}
#[Get('{database}', name: 'api.projects.servers.databases.show', middleware: 'ability:read')]
#[Endpoint(title: 'show', description: 'Get a database by ID.')]
#[ResponseFromApiResource(DatabaseResource::class, Database::class)]
public function show(Project $project, Server $server, Database $database): DatabaseResource
{
$this->authorize('view', [$database, $server]);
$this->validateRoute($project, $server, $database);
return new DatabaseResource($database);
}
#[Delete('{database}', name: 'api.projects.servers.databases.delete', middleware: 'ability:write')]
#[Endpoint(title: 'delete', description: 'Delete database.')]
#[Response(status: 204)]
public function delete(Project $project, Server $server, Database $database)
{
$this->authorize('delete', [$database, $server]);
$this->validateRoute($project, $server, $database);
$database->delete();
return response()->noContent();
}
private function validateRoute(Project $project, Server $server, ?Database $database = null): void
{
if ($project->id !== $server->project_id) {
abort(404, 'Server not found in project');
}
if ($database && $database->server_id !== $server->id) {
abort(404, 'Database not found in server');
}
}
}

View File

@ -0,0 +1,114 @@
<?php
namespace App\Http\Controllers\API;
use App\Actions\Database\CreateDatabaseUser;
use App\Actions\Database\LinkUser;
use App\Http\Controllers\Controller;
use App\Http\Resources\DatabaseUserResource;
use App\Models\DatabaseUser;
use App\Models\Project;
use App\Models\Server;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;
use Knuckles\Scribe\Attributes\BodyParam;
use Knuckles\Scribe\Attributes\Endpoint;
use Knuckles\Scribe\Attributes\Group;
use Knuckles\Scribe\Attributes\Response;
use Knuckles\Scribe\Attributes\ResponseFromApiResource;
use Spatie\RouteAttributes\Attributes\Delete;
use Spatie\RouteAttributes\Attributes\Get;
use Spatie\RouteAttributes\Attributes\Middleware;
use Spatie\RouteAttributes\Attributes\Post;
use Spatie\RouteAttributes\Attributes\Prefix;
#[Prefix('api/projects/{project}/servers/{server}/database-users')]
#[Middleware(['auth:sanctum', 'can-see-project'])]
#[Group(name: 'database-users')]
class DatabaseUserController extends Controller
{
#[Get('/', name: 'api.projects.servers.database-users', middleware: 'ability:read')]
#[Endpoint(title: 'list', description: 'Get all database users.')]
#[ResponseFromApiResource(DatabaseUserResource::class, DatabaseUser::class, collection: true, paginate: 25)]
public function index(Project $project, Server $server): ResourceCollection
{
$this->authorize('viewAny', [DatabaseUser::class, $server]);
$this->validateRoute($project, $server);
return DatabaseUserResource::collection($server->databaseUsers()->simplePaginate(25));
}
#[Post('/', name: 'api.projects.servers.database-users.create', middleware: 'ability:write')]
#[Endpoint(title: 'create', description: 'Create a new database user.')]
#[BodyParam(name: 'username', required: true)]
#[BodyParam(name: 'password', required: true)]
#[BodyParam(name: 'host', description: 'Host, if it is a remote user.', example: '%')]
#[ResponseFromApiResource(DatabaseUserResource::class, DatabaseUser::class)]
public function create(Request $request, Project $project, Server $server): DatabaseUserResource
{
$this->authorize('create', [DatabaseUser::class, $server]);
$this->validateRoute($project, $server);
$this->validate($request, CreateDatabaseUser::rules($server, $request->input()));
$databaseUser = app(CreateDatabaseUser::class)->create($server, $request->all());
return new DatabaseUserResource($databaseUser);
}
#[Get('{databaseUser}', name: 'api.projects.servers.database-users.show', middleware: 'ability:read')]
#[Endpoint(title: 'show', description: 'Get a database user by ID.')]
#[ResponseFromApiResource(DatabaseUserResource::class, DatabaseUser::class)]
public function show(Project $project, Server $server, DatabaseUser $databaseUser): DatabaseUserResource
{
$this->authorize('view', [$databaseUser, $server]);
$this->validateRoute($project, $server, $databaseUser);
return new DatabaseUserResource($databaseUser);
}
#[Post('{databaseUser}/link', name: 'api.projects.servers.database-users.link', middleware: 'ability:write')]
#[Endpoint(title: 'link', description: 'Link to databases')]
#[BodyParam(name: 'databases', description: 'Array of database names to link to the user.', required: true)]
#[ResponseFromApiResource(DatabaseUserResource::class, DatabaseUser::class)]
public function link(Request $request, Project $project, Server $server, DatabaseUser $databaseUser): DatabaseUserResource
{
$this->authorize('update', [$databaseUser, $server]);
$this->validateRoute($project, $server, $databaseUser);
$this->validate($request, LinkUser::rules($server, $request->all()));
$databaseUser = app(LinkUser::class)->link($databaseUser, $request->all());
return new DatabaseUserResource($databaseUser);
}
#[Delete('{databaseUser}', name: 'api.projects.servers.database-users.delete', middleware: 'ability:write')]
#[Endpoint(title: 'delete', description: 'Delete database user.')]
#[Response(status: 204)]
public function delete(Project $project, Server $server, DatabaseUser $databaseUser)
{
$this->authorize('delete', [$databaseUser, $server]);
$this->validateRoute($project, $server, $databaseUser);
$databaseUser->delete();
return response()->noContent();
}
private function validateRoute(Project $project, Server $server, ?DatabaseUser $databaseUser = null): void
{
if ($project->id !== $server->project_id) {
abort(404, 'Server not found in project');
}
if ($databaseUser && $databaseUser->server_id !== $server->id) {
abort(404, 'Database user not found in server');
}
}
}

View File

@ -0,0 +1,99 @@
<?php
namespace App\Http\Controllers\API;
use App\Actions\FirewallRule\CreateRule;
use App\Actions\FirewallRule\DeleteRule;
use App\Http\Controllers\Controller;
use App\Http\Resources\FirewallRuleResource;
use App\Models\FirewallRule;
use App\Models\Project;
use App\Models\Server;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;
use Knuckles\Scribe\Attributes\BodyParam;
use Knuckles\Scribe\Attributes\Endpoint;
use Knuckles\Scribe\Attributes\Group;
use Knuckles\Scribe\Attributes\Response;
use Knuckles\Scribe\Attributes\ResponseFromApiResource;
use Spatie\RouteAttributes\Attributes\Delete;
use Spatie\RouteAttributes\Attributes\Get;
use Spatie\RouteAttributes\Attributes\Middleware;
use Spatie\RouteAttributes\Attributes\Post;
use Spatie\RouteAttributes\Attributes\Prefix;
#[Prefix('api/projects/{project}/servers/{server}/firewall-rules')]
#[Middleware(['auth:sanctum', 'can-see-project'])]
#[Group(name: 'firewall-rules')]
class FirewallRuleController extends Controller
{
#[Get('/', name: 'api.projects.servers.firewall-rules', middleware: 'ability:read')]
#[Endpoint(title: 'list', description: 'Get all firewall rules.')]
#[ResponseFromApiResource(FirewallRuleResource::class, FirewallRule::class, collection: true, paginate: 25)]
public function index(Project $project, Server $server): ResourceCollection
{
$this->authorize('viewAny', [FirewallRule::class, $server]);
$this->validateRoute($project, $server);
return FirewallRuleResource::collection($server->firewallRules()->simplePaginate(25));
}
#[Post('/', name: 'api.projects.servers.firewall-rules.create', middleware: 'ability:write')]
#[Endpoint(title: 'create', description: 'Create a new firewall rule.')]
#[BodyParam(name: 'type', required: true, enum: ['allow', 'deny'])]
#[BodyParam(name: 'protocol', required: true, enum: ['tcp', 'udp'])]
#[BodyParam(name: 'port', required: true)]
#[BodyParam(name: 'source', required: true)]
#[BodyParam(name: 'mask', description: 'Mask for source IP.', example: '0')]
#[ResponseFromApiResource(FirewallRuleResource::class, FirewallRule::class)]
public function create(Request $request, Project $project, Server $server): FirewallRuleResource
{
$this->authorize('create', [FirewallRule::class, $server]);
$this->validateRoute($project, $server);
$this->validate($request, CreateRule::rules());
$firewallRule = app(CreateRule::class)->create($server, $request->all());
return new FirewallRuleResource($firewallRule);
}
#[Get('{firewallRule}', name: 'api.projects.servers.firewall-rules.show', middleware: 'ability:read')]
#[Endpoint(title: 'show', description: 'Get a firewall rule by ID.')]
#[ResponseFromApiResource(FirewallRuleResource::class, FirewallRule::class)]
public function show(Project $project, Server $server, FirewallRule $firewallRule): FirewallRuleResource
{
$this->authorize('view', [$firewallRule, $server]);
$this->validateRoute($project, $server, $firewallRule);
return new FirewallRuleResource($firewallRule);
}
#[Delete('{firewallRule}', name: 'api.projects.servers.firewall-rules.delete', middleware: 'ability:write')]
#[Endpoint(title: 'delete', description: 'Delete firewall rule.')]
#[Response(status: 204)]
public function delete(Project $project, Server $server, FirewallRule $firewallRule)
{
$this->authorize('delete', [$firewallRule, $server]);
$this->validateRoute($project, $server, $firewallRule);
app(DeleteRule::class)->delete($server, $firewallRule);
return response()->noContent();
}
private function validateRoute(Project $project, Server $server, ?FirewallRule $firewallRule = null): void
{
if ($project->id !== $server->project_id) {
abort(404, 'Server not found in project');
}
if ($firewallRule && $firewallRule->server_id !== $server->id) {
abort(404, 'Firewall rule not found in server');
}
}
}

View File

@ -11,10 +11,12 @@
use App\Notifications\SourceControlDisconnected;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Spatie\RouteAttributes\Attributes\Any;
use Throwable;
class GitHookController extends Controller
{
#[Any('api/git-hooks', name: 'api.git-hooks')]
public function __invoke(Request $request)
{
if (! $request->input('secret')) {

View File

@ -3,9 +3,17 @@
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Knuckles\Scribe\Attributes\Endpoint;
use Knuckles\Scribe\Attributes\Group;
use Knuckles\Scribe\Attributes\Unauthenticated;
use Spatie\RouteAttributes\Attributes\Get;
#[Group(name: 'general')]
class HealthController extends Controller
{
#[Get('api/health', name: 'api.health')]
#[Unauthenticated]
#[Endpoint(title: 'health-check')]
public function __invoke()
{
return response()->json([

View File

@ -0,0 +1,89 @@
<?php
namespace App\Http\Controllers\API;
use App\Actions\Projects\CreateProject;
use App\Actions\Projects\DeleteProject;
use App\Actions\Projects\UpdateProject;
use App\Http\Controllers\Controller;
use App\Http\Resources\ProjectResource;
use App\Models\Project;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;
use Illuminate\Http\Response;
use Knuckles\Scribe\Attributes\BodyParam;
use Knuckles\Scribe\Attributes\Endpoint;
use Knuckles\Scribe\Attributes\Group;
use Knuckles\Scribe\Attributes\ResponseFromApiResource;
use Spatie\RouteAttributes\Attributes\Delete;
use Spatie\RouteAttributes\Attributes\Get;
use Spatie\RouteAttributes\Attributes\Middleware;
use Spatie\RouteAttributes\Attributes\Post;
use Spatie\RouteAttributes\Attributes\Put;
#[Middleware('auth:sanctum')]
#[Group(name: 'projects')]
class ProjectController extends Controller
{
#[Get('api/projects', name: 'api.projects.index', middleware: 'ability:read')]
#[Endpoint(title: 'list', description: 'Get all projects.')]
#[ResponseFromApiResource(ProjectResource::class, Project::class, collection: true, paginate: 25)]
public function index(): ResourceCollection
{
$this->authorize('viewAny', Project::class);
return ProjectResource::collection(Project::all());
}
#[Post('api/projects', name: 'api.projects.create', middleware: 'ability:write')]
#[Endpoint(title: 'create', description: 'Create a new project.')]
#[BodyParam(name: 'name', description: 'The name of the project.', required: true)]
#[ResponseFromApiResource(ProjectResource::class, Project::class)]
public function create(Request $request): ProjectResource
{
$this->authorize('create', Project::class);
$this->validate($request, CreateProject::rules());
$project = app(CreateProject::class)->create(auth()->user(), $request->all());
return new ProjectResource($project);
}
#[Get('api/projects/{project}', name: 'api.projects.show', middleware: 'ability:read')]
#[Endpoint(title: 'show', description: 'Get a project by ID.')]
#[ResponseFromApiResource(ProjectResource::class, Project::class)]
public function show(Project $project): ProjectResource
{
$this->authorize('view', $project);
return new ProjectResource($project);
}
#[Put('api/projects/{project}', name: 'api.projects.update', middleware: 'ability:write')]
#[Endpoint(title: 'update', description: 'Update project.')]
#[BodyParam(name: 'name', description: 'The name of the project.', required: true)]
#[ResponseFromApiResource(ProjectResource::class, Project::class)]
public function update(Request $request, Project $project): ProjectResource
{
$this->authorize('update', $project);
$this->validate($request, UpdateProject::rules($project));
$project = app(UpdateProject::class)->update($project, $request->all());
return new ProjectResource($project);
}
#[Delete('api/projects/{project}', name: 'api.projects.delete', middleware: 'ability:write')]
#[Endpoint(title: 'delete', description: 'Delete project.')]
#[\Knuckles\Scribe\Attributes\Response(status: 204)]
public function delete(Project $project): Response
{
$this->authorize('delete', $project);
app(DeleteProject::class)->delete(auth()->user(), $project);
return response()->noContent();
}
}

View File

@ -0,0 +1,131 @@
<?php
namespace App\Http\Controllers\API;
use App\Actions\Server\CreateServer;
use App\Actions\Server\RebootServer;
use App\Actions\Server\Update;
use App\Enums\Database;
use App\Enums\PHP;
use App\Enums\ServerProvider;
use App\Enums\ServerType;
use App\Enums\Webserver;
use App\Http\Controllers\Controller;
use App\Http\Resources\ServerResource;
use App\Models\Project;
use App\Models\Server;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;
use Knuckles\Scribe\Attributes\BodyParam;
use Knuckles\Scribe\Attributes\Endpoint;
use Knuckles\Scribe\Attributes\Group;
use Knuckles\Scribe\Attributes\Response;
use Knuckles\Scribe\Attributes\ResponseFromApiResource;
use Spatie\RouteAttributes\Attributes\Delete;
use Spatie\RouteAttributes\Attributes\Get;
use Spatie\RouteAttributes\Attributes\Middleware;
use Spatie\RouteAttributes\Attributes\Post;
use Spatie\RouteAttributes\Attributes\Prefix;
#[Prefix('api/projects/{project}/servers')]
#[Middleware(['auth:sanctum', 'can-see-project'])]
#[Group(name: 'servers')]
class ServerController extends Controller
{
#[Get('/', name: 'api.projects.servers', middleware: 'ability:read')]
#[Endpoint(title: 'list', description: 'Get all servers in a project.')]
#[ResponseFromApiResource(ServerResource::class, Server::class, collection: true, paginate: 25)]
public function index(Project $project): ResourceCollection
{
$this->authorize('viewAny', [Server::class, $project]);
return ServerResource::collection($project->servers()->simplePaginate(25));
}
#[Post('/', name: 'api.projects.servers.create', middleware: 'ability:write')]
#[Endpoint(title: 'create', description: 'Create a new server.')]
#[BodyParam(name: 'provider', description: 'The server provider type', required: true)]
#[BodyParam(name: 'server_provider', description: 'If the provider is not custom, the ID of the server provider profile', enum: [ServerProvider::CUSTOM, ServerProvider::HETZNER, ServerProvider::DIGITALOCEAN, ServerProvider::LINODE, ServerProvider::VULTR])]
#[BodyParam(name: 'region', description: 'Provider region if the provider is not custom')]
#[BodyParam(name: 'plan', description: 'Provider plan if the provider is not custom')]
#[BodyParam(name: 'ip', description: 'SSH IP address if the provider is custom')]
#[BodyParam(name: 'port', description: 'SSH Port if the provider is custom')]
#[BodyParam(name: 'name', description: 'The name of the server.', required: true)]
#[BodyParam(name: 'os', description: 'The os of the server', required: true)]
#[BodyParam(name: 'type', description: 'Server type', required: true, enum: [ServerType::REGULAR, ServerType::DATABASE])]
#[BodyParam(name: 'webserver', description: 'Web server', required: true, enum: [Webserver::NONE, Webserver::NGINX])]
#[BodyParam(name: 'database', description: 'Database', required: true, enum: [Database::NONE, Database::MYSQL57, Database::MYSQL80, Database::MARIADB103, Database::MARIADB104, Database::MARIADB103, Database::POSTGRESQL12, Database::POSTGRESQL13, Database::POSTGRESQL14, Database::POSTGRESQL15, Database::POSTGRESQL16], )]
#[BodyParam(name: 'php', description: 'PHP version', required: true, enum: [PHP::V70, PHP::V71, PHP::V72, PHP::V73, PHP::V74, PHP::V80, PHP::V81, PHP::V82, PHP::V83])]
#[ResponseFromApiResource(ServerResource::class, Server::class)]
public function create(Request $request, Project $project): ServerResource
{
$this->authorize('create', [Server::class, $project]);
$this->validate($request, CreateServer::rules($project, $request->input()));
$server = app(CreateServer::class)->create(auth()->user(), $project, $request->all());
return new ServerResource($server);
}
#[Get('{server}', name: 'api.projects.servers.show', middleware: 'ability:read')]
#[Endpoint(title: 'show', description: 'Get a server by ID.')]
#[ResponseFromApiResource(ServerResource::class, Server::class)]
public function show(Project $project, Server $server): ServerResource
{
$this->authorize('view', [$server, $project]);
$this->validateRoute($project, $server);
return new ServerResource($server);
}
#[Post('{server}/reboot', name: 'api.projects.servers.reboot', middleware: 'ability:write')]
#[Endpoint(title: 'reboot', description: 'Reboot a server.')]
#[Response(status: 204)]
public function reboot(Project $project, Server $server)
{
$this->authorize('update', [$server, $project]);
$this->validateRoute($project, $server);
app(RebootServer::class)->reboot($server);
return response()->noContent();
}
#[Post('{server}/upgrade', name: 'api.projects.servers.upgrade', middleware: 'ability:write')]
#[Endpoint(title: 'upgrade', description: 'Upgrade server.')]
#[Response(status: 204)]
public function upgrade(Project $project, Server $server)
{
$this->authorize('update', [$server, $project]);
$this->validateRoute($project, $server);
app(Update::class)->update($server);
return response()->noContent();
}
#[Delete('{server}', name: 'api.projects.servers.delete', middleware: 'ability:write')]
#[Endpoint(title: 'delete', description: 'Delete server.')]
#[Response(status: 204)]
public function delete(Project $project, Server $server)
{
$this->authorize('delete', [$server, $project]);
$this->validateRoute($project, $server);
$server->delete();
return response()->noContent();
}
private function validateRoute(Project $project, Server $server): void
{
if ($project->id !== $server->project_id) {
abort(404, 'Server not found in project');
}
}
}

View File

@ -0,0 +1,116 @@
<?php
namespace App\Http\Controllers\API;
use App\Actions\ServerProvider\CreateServerProvider;
use App\Actions\ServerProvider\DeleteServerProvider;
use App\Actions\ServerProvider\EditServerProvider;
use App\Http\Controllers\Controller;
use App\Http\Resources\ServerProviderResource;
use App\Models\Project;
use App\Models\ServerProvider;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;
use Knuckles\Scribe\Attributes\BodyParam;
use Knuckles\Scribe\Attributes\Endpoint;
use Knuckles\Scribe\Attributes\Group;
use Knuckles\Scribe\Attributes\Response;
use Knuckles\Scribe\Attributes\ResponseFromApiResource;
use Spatie\RouteAttributes\Attributes\Delete;
use Spatie\RouteAttributes\Attributes\Get;
use Spatie\RouteAttributes\Attributes\Middleware;
use Spatie\RouteAttributes\Attributes\Post;
use Spatie\RouteAttributes\Attributes\Prefix;
use Spatie\RouteAttributes\Attributes\Put;
#[Prefix('api/projects/{project}/server-providers')]
#[Middleware(['auth:sanctum', 'can-see-project'])]
#[Group(name: 'server-providers')]
class ServerProviderController extends Controller
{
#[Get('/', name: 'api.projects.server-providers', middleware: 'ability:read')]
#[Endpoint(title: 'list')]
#[ResponseFromApiResource(ServerProviderResource::class, ServerProvider::class, collection: true, paginate: 25)]
public function index(Project $project): ResourceCollection
{
$this->authorize('viewAny', ServerProvider::class);
$serverProviders = ServerProvider::getByProjectId($project->id)->simplePaginate(25);
return ServerProviderResource::collection($serverProviders);
}
#[Post('/', name: 'api.projects.server-providers.create', middleware: 'ability:write')]
#[Endpoint(title: 'create')]
#[BodyParam(name: 'provider', description: 'The provider (aws, linode, hetzner, digitalocean, vultr, ...)', required: true)]
#[BodyParam(name: 'name', description: 'The name of the server provider.', required: true)]
#[BodyParam(name: 'token', description: 'The token if provider requires api token')]
#[BodyParam(name: 'key', description: 'The key if provider requires key')]
#[BodyParam(name: 'secret', description: 'The secret if provider requires key')]
#[ResponseFromApiResource(ServerProviderResource::class, ServerProvider::class)]
public function create(Request $request, Project $project): ServerProviderResource
{
$this->authorize('create', ServerProvider::class);
$this->validate($request, CreateServerProvider::rules($request->all()));
$serverProvider = app(CreateServerProvider::class)->create(auth()->user(), $project, $request->all());
return new ServerProviderResource($serverProvider);
}
#[Get('{serverProvider}', name: 'api.projects.server-providers.show', middleware: 'ability:read')]
#[Endpoint(title: 'show')]
#[ResponseFromApiResource(ServerProviderResource::class, ServerProvider::class)]
public function show(Project $project, ServerProvider $serverProvider)
{
$this->authorize('view', $serverProvider);
$this->validateRoute($project, $serverProvider);
return new ServerProviderResource($serverProvider);
}
#[Put('{serverProvider}', name: 'api.projects.server-providers.update', middleware: 'ability:write')]
#[Endpoint(title: 'update')]
#[BodyParam(name: 'name', description: 'The name of the server provider.', required: true)]
#[BodyParam(name: 'global', description: 'Accessible in all projects', enum: [true, false])]
#[ResponseFromApiResource(ServerProviderResource::class, ServerProvider::class)]
public function update(Request $request, Project $project, ServerProvider $serverProvider)
{
$this->authorize('update', $serverProvider);
$this->validateRoute($project, $serverProvider);
$this->validate($request, EditServerProvider::rules());
$serverProvider = app(EditServerProvider::class)->edit($serverProvider, $project, $request->all());
return new ServerProviderResource($serverProvider);
}
#[Delete('{serverProvider}', name: 'api.projects.server-providers.delete', middleware: 'ability:write')]
#[Endpoint(title: 'delete')]
#[Response(status: 204)]
public function delete(Project $project, ServerProvider $serverProvider)
{
$this->authorize('delete', $serverProvider);
$this->validateRoute($project, $serverProvider);
app(DeleteServerProvider::class)->delete($serverProvider);
return response()->noContent();
}
private function validateRoute(Project $project, ServerProvider $serverProvider): void
{
if (! $serverProvider->project_id) {
return;
}
if ($project->id !== $serverProvider->project_id) {
abort(404, 'Server provider not found in project');
}
}
}

View File

@ -0,0 +1,92 @@
<?php
namespace App\Http\Controllers\API;
use App\Actions\SshKey\CreateSshKey;
use App\Actions\SshKey\DeleteKeyFromServer;
use App\Actions\SshKey\DeployKeyToServer;
use App\Http\Controllers\Controller;
use App\Http\Resources\SshKeyResource;
use App\Models\Project;
use App\Models\Server;
use App\Models\SshKey;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;
use Knuckles\Scribe\Attributes\BodyParam;
use Knuckles\Scribe\Attributes\Endpoint;
use Knuckles\Scribe\Attributes\Group;
use Knuckles\Scribe\Attributes\Response;
use Knuckles\Scribe\Attributes\ResponseFromApiResource;
use Spatie\RouteAttributes\Attributes\Delete;
use Spatie\RouteAttributes\Attributes\Get;
use Spatie\RouteAttributes\Attributes\Middleware;
use Spatie\RouteAttributes\Attributes\Post;
use Spatie\RouteAttributes\Attributes\Prefix;
#[Prefix('api/projects/{project}/servers/{server}/ssh-keys')]
#[Middleware(['auth:sanctum', 'can-see-project'])]
#[Group(name: 'ssh-keys')]
class ServerSSHKeyController extends Controller
{
#[Get('/', name: 'api.projects.servers.ssh-keys', middleware: 'ability:read')]
#[Endpoint(title: 'list', description: 'Get all ssh keys.')]
#[ResponseFromApiResource(SshKeyResource::class, SshKey::class, collection: true, paginate: 25)]
public function index(Project $project, Server $server): ResourceCollection
{
$this->authorize('viewAnyServer', [SshKey::class, $server]);
$this->validateRoute($project, $server);
return SshKeyResource::collection($server->sshKeys()->simplePaginate(25));
}
#[Post('/', name: 'api.projects.servers.ssh-keys.create', middleware: 'ability:write')]
#[Endpoint(title: 'create', description: 'Deploy ssh key to server.')]
#[BodyParam(name: 'key_id', description: 'The ID of the key.')]
#[BodyParam(name: 'name', description: 'Key name, required if key_id is not provided.')]
#[BodyParam(name: 'public_key', description: 'Public Key, required if key_id is not provided.')]
#[ResponseFromApiResource(SshKeyResource::class, SshKey::class)]
public function create(Request $request, Project $project, Server $server): SshKeyResource
{
$this->authorize('create', [SshKey::class, $server]);
$this->validateRoute($project, $server);
$sshKey = null;
if ($request->has('key_id')) {
$this->validate($request, DeployKeyToServer::rules($request->user(), $server));
$sshKey = $request->user()->sshKeys()->findOrFail($request->key_id);
}
if (! $sshKey) {
$this->validate($request, CreateSshKey::rules());
$sshKey = app(CreateSshKey::class)->create($request->user(), $request->all());
}
app(DeployKeyToServer::class)->deploy($server, ['key_id' => $sshKey->id]);
return new SshKeyResource($sshKey);
}
#[Delete('{sshKey}', name: 'api.projects.servers.ssh-keys.delete', middleware: 'ability:write')]
#[Endpoint(title: 'delete', description: 'Delete ssh key from server.')]
#[Response(status: 204)]
public function delete(Project $project, Server $server, SshKey $sshKey)
{
$this->authorize('delete', [$sshKey, $server]);
$this->validateRoute($project, $server);
app(DeleteKeyFromServer::class)->delete($server, $sshKey);
return response()->noContent();
}
private function validateRoute(Project $project, Server $server): void
{
if ($project->id !== $server->project_id) {
abort(404, 'Server not found in project');
}
}
}

View File

@ -0,0 +1,146 @@
<?php
namespace App\Http\Controllers\API;
use App\Actions\Service\Manage;
use App\Actions\Service\Uninstall;
use App\Http\Controllers\Controller;
use App\Http\Resources\ServiceResource;
use App\Models\Project;
use App\Models\Server;
use App\Models\Service;
use Illuminate\Http\Resources\Json\ResourceCollection;
use Knuckles\Scribe\Attributes\Endpoint;
use Knuckles\Scribe\Attributes\Group;
use Knuckles\Scribe\Attributes\Response;
use Knuckles\Scribe\Attributes\ResponseFromApiResource;
use Spatie\RouteAttributes\Attributes\Delete;
use Spatie\RouteAttributes\Attributes\Get;
use Spatie\RouteAttributes\Attributes\Middleware;
use Spatie\RouteAttributes\Attributes\Post;
use Spatie\RouteAttributes\Attributes\Prefix;
#[Prefix('api/projects/{project}/servers/{server}/services')]
#[Middleware(['auth:sanctum', 'can-see-project'])]
#[Group(name: 'services')]
class ServiceController extends Controller
{
#[Get('/', name: 'api.projects.servers.services', middleware: 'ability:read')]
#[Endpoint(title: 'list', description: 'Get all services.')]
#[ResponseFromApiResource(ServiceResource::class, Service::class, collection: true, paginate: 25)]
public function index(Project $project, Server $server): ResourceCollection
{
$this->authorize('viewAny', [Service::class, $server]);
$this->validateRoute($project, $server);
return ServiceResource::collection($server->services()->simplePaginate(25));
}
#[Get('{service}', name: 'api.projects.servers.services.show', middleware: 'ability:read')]
#[Endpoint(title: 'show', description: 'Get a service by ID.')]
#[ResponseFromApiResource(ServiceResource::class, Service::class)]
public function show(Project $project, Server $server, Service $service): ServiceResource
{
$this->authorize('view', [$service, $server]);
$this->validateRoute($project, $server, $service);
return new ServiceResource($service);
}
#[Post('{service}/start', name: 'api.projects.servers.services.start', middleware: 'ability:write')]
#[Endpoint(title: 'start', description: 'Start service.')]
#[Response(status: 204)]
public function start(Project $project, Server $server, Service $service): \Illuminate\Http\Response
{
$this->authorize('update', [$service, $server]);
$this->validateRoute($project, $server, $service);
app(Manage::class)->start($service);
return response()->noContent();
}
#[Post('{service}/stop', name: 'api.projects.servers.services.stop', middleware: 'ability:write')]
#[Endpoint(title: 'stop', description: 'Stop service.')]
#[Response(status: 204)]
public function stop(Project $project, Server $server, Service $service): \Illuminate\Http\Response
{
$this->authorize('update', [$service, $server]);
$this->validateRoute($project, $server, $service);
app(Manage::class)->stop($service);
return response()->noContent();
}
#[Post('{service}/restart', name: 'api.projects.servers.services.restart', middleware: 'ability:write')]
#[Endpoint(title: 'restart', description: 'Restart service.')]
#[Response(status: 204)]
public function restart(Project $project, Server $server, Service $service): \Illuminate\Http\Response
{
$this->authorize('update', [$service, $server]);
$this->validateRoute($project, $server, $service);
app(Manage::class)->restart($service);
return response()->noContent();
}
#[Post('{service}/enable', name: 'api.projects.servers.services.enable', middleware: 'ability:write')]
#[Endpoint(title: 'enable', description: 'Enable service.')]
#[Response(status: 204)]
public function enable(Project $project, Server $server, Service $service): \Illuminate\Http\Response
{
$this->authorize('update', [$service, $server]);
$this->validateRoute($project, $server, $service);
app(Manage::class)->enable($service);
return response()->noContent();
}
#[Post('{service}/disable', name: 'api.projects.servers.services.disable', middleware: 'ability:write')]
#[Endpoint(title: 'disable', description: 'Disable service.')]
#[Response(status: 204)]
public function disable(Project $project, Server $server, Service $service): \Illuminate\Http\Response
{
$this->authorize('update', [$service, $server]);
$this->validateRoute($project, $server, $service);
app(Manage::class)->disable($service);
return response()->noContent();
}
#[Delete('{service}', name: 'api.projects.servers.services.uninstall', middleware: 'ability:write')]
#[Endpoint(title: 'delete', description: 'Delete service.')]
#[Response(status: 204)]
public function uninstall(Project $project, Server $server, Service $service): \Illuminate\Http\Response
{
$this->authorize('delete', [$service, $server]);
$this->validateRoute($project, $server, $service);
app(Uninstall::class)->uninstall($service);
return response()->noContent();
}
private function validateRoute(Project $project, Server $server, ?Service $service = null): void
{
if ($project->id !== $server->project_id) {
abort(404, 'Server not found in project');
}
if ($service && $service->server_id !== $server->id) {
abort(404, 'Service not found in server');
}
}
}

View File

@ -0,0 +1,105 @@
<?php
namespace App\Http\Controllers\API;
use App\Actions\Site\CreateSite;
use App\Enums\SiteType;
use App\Http\Controllers\Controller;
use App\Http\Resources\ServerResource;
use App\Http\Resources\SiteResource;
use App\Models\Project;
use App\Models\Server;
use App\Models\Site;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;
use Knuckles\Scribe\Attributes\BodyParam;
use Knuckles\Scribe\Attributes\Endpoint;
use Knuckles\Scribe\Attributes\Group;
use Knuckles\Scribe\Attributes\Response;
use Knuckles\Scribe\Attributes\ResponseFromApiResource;
use Spatie\RouteAttributes\Attributes\Delete;
use Spatie\RouteAttributes\Attributes\Get;
use Spatie\RouteAttributes\Attributes\Middleware;
use Spatie\RouteAttributes\Attributes\Post;
use Spatie\RouteAttributes\Attributes\Prefix;
#[Prefix('api/projects/{project}/servers/{server}/sites')]
#[Middleware(['auth:sanctum', 'can-see-project'])]
#[Group(name: 'sites')]
class SiteController extends Controller
{
#[Get('/', name: 'api.projects.servers.sites', middleware: 'ability:read')]
#[Endpoint(title: 'list', description: 'Get all sites.')]
#[ResponseFromApiResource(SiteResource::class, Site::class, collection: true, paginate: 25)]
public function index(Project $project, Server $server): ResourceCollection
{
$this->authorize('viewAny', [Site::class, $server]);
$this->validateRoute($project, $server);
return SiteResource::collection($server->sites()->simplePaginate(25));
}
#[Post('/', name: 'api.projects.servers.sites.create', middleware: 'ability:write')]
#[Endpoint(title: 'create', description: 'Create a new site.')]
#[BodyParam(name: 'type', required: true, enum: [SiteType::PHP, SiteType::PHP_BLANK, SiteType::PHPMYADMIN, SiteType::LARAVEL, SiteType::WORDPRESS])]
#[BodyParam(name: 'domain', required: true)]
#[BodyParam(name: 'aliases', type: 'array')]
#[BodyParam(name: 'php_version', description: 'One of the installed PHP Versions', required: true, example: '7.4')]
#[BodyParam(name: 'web_directory', description: 'Required for PHP and Laravel sites', example: 'public')]
#[BodyParam(name: 'source_control', description: 'Source control ID, Required for Sites which support source control')]
#[BodyParam(name: 'repository', description: 'Repository, Required for Sites which support source control', example: 'organization/repository')]
#[BodyParam(name: 'branch', description: 'Branch, Required for Sites which support source control', example: 'main')]
#[BodyParam(name: 'composer', type: 'boolean', description: 'Run composer if site supports composer', example: true)]
#[BodyParam(name: 'version', description: 'Version, if the site type requires a version like PHPMyAdmin', example: '5.2.1')]
#[ResponseFromApiResource(SiteResource::class, Site::class)]
public function create(Request $request, Project $project, Server $server): SiteResource
{
$this->authorize('create', [Site::class, $server]);
$this->validateRoute($project, $server);
$this->validate($request, CreateSite::rules($server, $request->input()));
$site = app(CreateSite::class)->create($server, $request->all());
return new SiteResource($site);
}
#[Get('{site}', name: 'api.projects.servers.sites.show', middleware: 'ability:read')]
#[Endpoint(title: 'show', description: 'Get a site by ID.')]
#[ResponseFromApiResource(SiteResource::class, Site::class)]
public function show(Project $project, Server $server, Site $site): ServerResource
{
$this->authorize('view', [$site, $server]);
$this->validateRoute($project, $server, $site);
return new ServerResource($server);
}
#[Delete('{site}', name: 'api.projects.servers.sites.delete', middleware: 'ability:write')]
#[Endpoint(title: 'delete', description: 'Delete site.')]
#[Response(status: 204)]
public function delete(Project $project, Server $server, Site $site)
{
$this->authorize('delete', [$site, $server]);
$this->validateRoute($project, $server, $site);
$site->delete();
return response()->noContent();
}
private function validateRoute(Project $project, Server $server, ?Site $site = null): void
{
if ($project->id !== $server->project_id) {
abort(404, 'Server not found in project');
}
if ($site && $site->server_id !== $server->id) {
abort(404, 'Site not found in server');
}
}
}

View File

@ -0,0 +1,121 @@
<?php
namespace App\Http\Controllers\API;
use App\Actions\SourceControl\ConnectSourceControl;
use App\Actions\SourceControl\DeleteSourceControl;
use App\Actions\SourceControl\EditSourceControl;
use App\Http\Controllers\Controller;
use App\Http\Resources\SourceControlResource;
use App\Models\Project;
use App\Models\SourceControl;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;
use Knuckles\Scribe\Attributes\BodyParam;
use Knuckles\Scribe\Attributes\Endpoint;
use Knuckles\Scribe\Attributes\Group;
use Knuckles\Scribe\Attributes\Response;
use Knuckles\Scribe\Attributes\ResponseFromApiResource;
use Spatie\RouteAttributes\Attributes\Delete;
use Spatie\RouteAttributes\Attributes\Get;
use Spatie\RouteAttributes\Attributes\Middleware;
use Spatie\RouteAttributes\Attributes\Post;
use Spatie\RouteAttributes\Attributes\Prefix;
use Spatie\RouteAttributes\Attributes\Put;
#[Prefix('api/projects/{project}/source-controls')]
#[Middleware(['auth:sanctum', 'can-see-project'])]
#[Group(name: 'source-controls')]
class SourceControlController extends Controller
{
#[Get('/', name: 'api.projects.source-controls', middleware: 'ability:read')]
#[Endpoint(title: 'list')]
#[ResponseFromApiResource(SourceControlResource::class, SourceControl::class, collection: true, paginate: 25)]
public function index(Project $project): ResourceCollection
{
$this->authorize('viewAny', SourceControl::class);
$sourceControls = SourceControl::getByProjectId($project->id)->simplePaginate(25);
return SourceControlResource::collection($sourceControls);
}
#[Post('/', name: 'api.projects.source-controls.create', middleware: 'ability:write')]
#[Endpoint(title: 'create')]
#[BodyParam(name: 'provider', description: 'The provider', required: true, enum: [\App\Enums\SourceControl::GITLAB, \App\Enums\SourceControl::GITHUB, \App\Enums\SourceControl::BITBUCKET])]
#[BodyParam(name: 'name', description: 'The name of the storage provider.', required: true)]
#[BodyParam(name: 'token', description: 'The token if provider requires api token')]
#[BodyParam(name: 'url', description: 'The URL if the provider is Gitlab and it is self-hosted')]
#[BodyParam(name: 'username', description: 'The username if the provider is Bitbucket')]
#[BodyParam(name: 'password', description: 'The password if the provider is Bitbucket')]
#[ResponseFromApiResource(SourceControlResource::class, SourceControl::class)]
public function create(Request $request, Project $project): SourceControlResource
{
$this->authorize('create', SourceControl::class);
$this->validate($request, ConnectSourceControl::rules($request->all()));
$sourceControl = app(ConnectSourceControl::class)->connect(auth()->user(), $project, $request->all());
return new SourceControlResource($sourceControl);
}
#[Get('{sourceControl}', name: 'api.projects.source-controls.show', middleware: 'ability:read')]
#[Endpoint(title: 'show')]
#[ResponseFromApiResource(SourceControlResource::class, SourceControl::class)]
public function show(Project $project, SourceControl $sourceControl)
{
$this->authorize('view', $sourceControl);
$this->validateRoute($project, $sourceControl);
return new SourceControlResource($sourceControl);
}
#[Put('{sourceControl}', name: 'api.projects.source-controls.update', middleware: 'ability:write')]
#[Endpoint(title: 'update')]
#[BodyParam(name: 'name', description: 'The name of the storage provider.', required: true)]
#[BodyParam(name: 'token', description: 'The token if provider requires api token')]
#[BodyParam(name: 'url', description: 'The URL if the provider is Gitlab and it is self-hosted')]
#[BodyParam(name: 'username', description: 'The username if the provider is Bitbucket')]
#[BodyParam(name: 'password', description: 'The password if the provider is Bitbucket')]
#[BodyParam(name: 'global', description: 'Accessible in all projects', enum: [true, false])]
#[ResponseFromApiResource(SourceControlResource::class, SourceControl::class)]
public function update(Request $request, Project $project, SourceControl $sourceControl)
{
$this->authorize('update', $sourceControl);
$this->validateRoute($project, $sourceControl);
$this->validate($request, EditSourceControl::rules($sourceControl, $request->all()));
$sourceControl = app(EditSourceControl::class)->edit($sourceControl, $project, $request->all());
return new SourceControlResource($sourceControl);
}
#[Delete('{sourceControl}', name: 'api.projects.source-controls.delete', middleware: 'ability:write')]
#[Endpoint(title: 'delete')]
#[Response(status: 204)]
public function delete(Project $project, SourceControl $sourceControl)
{
$this->authorize('delete', $sourceControl);
$this->validateRoute($project, $sourceControl);
app(DeleteSourceControl::class)->delete($sourceControl);
return response()->noContent();
}
private function validateRoute(Project $project, SourceControl $sourceControl): void
{
if (! $sourceControl->project_id) {
return;
}
if ($project->id !== $sourceControl->project_id) {
abort(404, 'Source Control not found in project');
}
}
}

View File

@ -0,0 +1,116 @@
<?php
namespace App\Http\Controllers\API;
use App\Actions\StorageProvider\CreateStorageProvider;
use App\Actions\StorageProvider\DeleteStorageProvider;
use App\Actions\StorageProvider\EditStorageProvider;
use App\Http\Controllers\Controller;
use App\Http\Resources\StorageProviderResource;
use App\Models\Project;
use App\Models\StorageProvider;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;
use Knuckles\Scribe\Attributes\BodyParam;
use Knuckles\Scribe\Attributes\Endpoint;
use Knuckles\Scribe\Attributes\Group;
use Knuckles\Scribe\Attributes\Response;
use Knuckles\Scribe\Attributes\ResponseFromApiResource;
use Spatie\RouteAttributes\Attributes\Delete;
use Spatie\RouteAttributes\Attributes\Get;
use Spatie\RouteAttributes\Attributes\Middleware;
use Spatie\RouteAttributes\Attributes\Post;
use Spatie\RouteAttributes\Attributes\Prefix;
use Spatie\RouteAttributes\Attributes\Put;
#[Prefix('api/projects/{project}/storage-providers')]
#[Middleware(['auth:sanctum', 'can-see-project'])]
#[Group(name: 'storage-providers')]
class StorageProviderController extends Controller
{
#[Get('/', name: 'api.projects.storage-providers', middleware: 'ability:read')]
#[Endpoint(title: 'list')]
#[ResponseFromApiResource(StorageProviderResource::class, StorageProvider::class, collection: true, paginate: 25)]
public function index(Project $project): ResourceCollection
{
$this->authorize('viewAny', StorageProvider::class);
$storageProviders = StorageProvider::getByProjectId($project->id)->simplePaginate(25);
return StorageProviderResource::collection($storageProviders);
}
#[Post('/', name: 'api.projects.storage-providers.create', middleware: 'ability:write')]
#[Endpoint(title: 'create')]
#[BodyParam(name: 'provider', description: 'The provider (aws, linode, hetzner, digitalocean, vultr, ...)', required: true)]
#[BodyParam(name: 'name', description: 'The name of the storage provider.', required: true)]
#[BodyParam(name: 'token', description: 'The token if provider requires api token')]
#[BodyParam(name: 'key', description: 'The key if provider requires key')]
#[BodyParam(name: 'secret', description: 'The secret if provider requires key')]
#[ResponseFromApiResource(StorageProviderResource::class, StorageProvider::class)]
public function create(Request $request, Project $project): StorageProviderResource
{
$this->authorize('create', StorageProvider::class);
$this->validate($request, CreateStorageProvider::rules($request->all()));
$storageProvider = app(CreateStorageProvider::class)->create(auth()->user(), $project, $request->all());
return new StorageProviderResource($storageProvider);
}
#[Get('{storageProvider}', name: 'api.projects.storage-providers.show', middleware: 'ability:read')]
#[Endpoint(title: 'show')]
#[ResponseFromApiResource(StorageProviderResource::class, StorageProvider::class)]
public function show(Project $project, StorageProvider $storageProvider)
{
$this->authorize('view', $storageProvider);
$this->validateRoute($project, $storageProvider);
return new StorageProviderResource($storageProvider);
}
#[Put('{storageProvider}', name: 'api.projects.storage-providers.update', middleware: 'ability:write')]
#[Endpoint(title: 'update')]
#[BodyParam(name: 'name', description: 'The name of the storage provider.', required: true)]
#[BodyParam(name: 'global', description: 'Accessible in all projects', enum: [true, false])]
#[ResponseFromApiResource(StorageProviderResource::class, StorageProvider::class)]
public function update(Request $request, Project $project, StorageProvider $storageProvider)
{
$this->authorize('update', $storageProvider);
$this->validateRoute($project, $storageProvider);
$this->validate($request, EditStorageProvider::rules());
$storageProvider = app(EditStorageProvider::class)->edit($storageProvider, $project, $request->all());
return new StorageProviderResource($storageProvider);
}
#[Delete('{storageProvider}', name: 'api.projects.storage-providers.delete', middleware: 'ability:write')]
#[Endpoint(title: 'delete')]
#[Response(status: 204)]
public function delete(Project $project, StorageProvider $storageProvider)
{
$this->authorize('delete', $storageProvider);
$this->validateRoute($project, $storageProvider);
app(DeleteStorageProvider::class)->delete($storageProvider);
return response()->noContent();
}
private function validateRoute(Project $project, StorageProvider $storageProvider): void
{
if (! $storageProvider->project_id) {
return;
}
if ($project->id !== $storageProvider->project_id) {
abort(404, 'Storage provider not found in project');
}
}
}