2024-02-16 20:56:38 +00:00
< ? php
namespace App\Http\Controllers\Api ;
2024-10-17 12:56:36 +00:00
use App\Actions\Server\DeleteServer ;
2024-07-23 12:20:53 +00:00
use App\Actions\Server\ValidateServer ;
use App\Enums\ProxyStatus ;
use App\Enums\ProxyTypes ;
2024-02-16 20:56:38 +00:00
use App\Http\Controllers\Controller ;
2026-03-13 15:58:26 +00:00
use App\Jobs\DeleteResourceJob ;
2024-06-21 19:35:02 +00:00
use App\Models\Application ;
2024-07-23 12:20:53 +00:00
use App\Models\PrivateKey ;
2024-06-21 19:35:02 +00:00
use App\Models\Project ;
2024-02-16 20:56:38 +00:00
use App\Models\Server as ModelsServer ;
2026-03-03 10:51:38 +00:00
use App\Rules\ValidServerIp ;
2024-02-16 20:56:38 +00:00
use Illuminate\Http\Request ;
2024-07-09 08:45:10 +00:00
use OpenApi\Attributes as OA ;
2024-07-01 14:26:50 +00:00
use Stringable ;
2024-02-16 20:56:38 +00:00
2024-07-01 14:26:50 +00:00
class ServersController extends Controller
2024-02-16 20:56:38 +00:00
{
2024-07-03 11:13:38 +00:00
private function removeSensitiveDataFromSettings ( $settings )
{
2024-12-09 10:10:35 +00:00
if ( request () -> attributes -> get ( 'can_read_sensitive' , false ) === false ) {
$settings = $settings -> makeHidden ([
'sentinel_token' ,
]);
2024-07-03 11:13:38 +00:00
}
return serializeApiResponse ( $settings );
}
private function removeSensitiveData ( $server )
{
2024-07-04 11:45:06 +00:00
$server -> makeHidden ([
'id' ,
]);
2024-12-09 10:10:35 +00:00
if ( request () -> attributes -> get ( 'can_read_sensitive' , false ) === false ) {
// Do nothing
2024-07-03 11:13:38 +00:00
}
return serializeApiResponse ( $server );
}
2024-07-09 08:45:10 +00:00
#[OA\Get(
summary : 'List' ,
description : 'List all servers.' ,
path : '/servers' ,
2024-09-04 08:09:10 +00:00
operationId : 'list-servers' ,
2024-07-09 08:45:10 +00:00
security : [
[ 'bearerAuth' => []],
],
tags : [ 'Servers' ],
responses : [
new OA\Response (
response : 200 ,
description : 'Get all servers.' ,
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'array' ,
items : new OA\Items ( ref : '#/components/schemas/Server' )
)
),
]),
new OA\Response (
response : 401 ,
ref : '#/components/responses/401' ,
),
new OA\Response (
response : 400 ,
ref : '#/components/responses/400' ,
),
]
)]
2024-02-16 20:56:38 +00:00
public function servers ( Request $request )
{
2024-07-01 14:26:50 +00:00
$teamId = getTeamIdFromToken ();
2024-02-16 20:56:38 +00:00
if ( is_null ( $teamId )) {
2024-07-01 14:26:50 +00:00
return invalidTokenResponse ();
2024-02-16 20:56:38 +00:00
}
2024-07-23 12:20:53 +00:00
$servers = ModelsServer :: whereTeamId ( $teamId ) -> select ( 'id' , 'name' , 'uuid' , 'ip' , 'user' , 'port' , 'description' ) -> get () -> load ([ 'settings' ]) -> map ( function ( $server ) {
2024-02-16 20:56:38 +00:00
$server [ 'is_reachable' ] = $server -> settings -> is_reachable ;
$server [ 'is_usable' ] = $server -> settings -> is_usable ;
2024-06-10 20:43:34 +00:00
2024-02-16 20:56:38 +00:00
return $server ;
});
2024-07-01 14:26:50 +00:00
$servers = $servers -> map ( function ( $server ) {
2024-07-03 11:13:38 +00:00
$settings = $this -> removeSensitiveDataFromSettings ( $server -> settings );
$server = $this -> removeSensitiveData ( $server );
data_set ( $server , 'settings' , $settings );
return $server ;
2024-07-01 14:26:50 +00:00
});
2024-06-10 20:43:34 +00:00
2024-07-03 11:13:38 +00:00
return response () -> json ( $servers );
2024-02-16 20:56:38 +00:00
}
2024-06-10 20:43:34 +00:00
2024-07-09 08:45:10 +00:00
#[OA\Get(
summary : 'Get' ,
description : 'Get server by UUID.' ,
path : '/servers/{uuid}' ,
2024-09-04 08:09:10 +00:00
operationId : 'get-server-by-uuid' ,
2024-07-09 08:45:10 +00:00
security : [
[ 'bearerAuth' => []],
],
tags : [ 'Servers' ],
parameters : [
2024-09-09 16:38:40 +00:00
new OA\Parameter ( name : 'uuid' , in : 'path' , required : true , description : 'Server\'s UUID' , schema : new OA\Schema ( type : 'string' )),
2024-07-09 08:45:10 +00:00
],
responses : [
new OA\Response (
response : 200 ,
description : 'Get server by UUID' ,
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
ref : '#/components/schemas/Server'
)
),
]),
new OA\Response (
response : 401 ,
ref : '#/components/responses/401' ,
),
new OA\Response (
response : 400 ,
ref : '#/components/responses/400' ,
),
new OA\Response (
response : 404 ,
ref : '#/components/responses/404' ,
),
]
)]
2024-02-16 20:56:38 +00:00
public function server_by_uuid ( Request $request )
{
2024-03-05 14:47:43 +00:00
$with_resources = $request -> query ( 'resources' );
2024-07-01 14:26:50 +00:00
$teamId = getTeamIdFromToken ();
2024-02-16 20:56:38 +00:00
if ( is_null ( $teamId )) {
2024-07-01 14:26:50 +00:00
return invalidTokenResponse ();
2024-02-16 20:56:38 +00:00
}
$server = ModelsServer :: whereTeamId ( $teamId ) -> whereUuid ( request () -> uuid ) -> first ();
if ( is_null ( $server )) {
2024-06-26 11:00:36 +00:00
return response () -> json ([ 'message' => 'Server not found.' ], 404 );
2024-02-16 20:56:38 +00:00
}
2024-03-05 14:47:43 +00:00
if ( $with_resources ) {
$server [ 'resources' ] = $server -> definedResources () -> map ( function ( $resource ) {
$payload = [
'id' => $resource -> id ,
'uuid' => $resource -> uuid ,
'name' => $resource -> name ,
'type' => $resource -> type (),
'created_at' => $resource -> created_at ,
'updated_at' => $resource -> updated_at ,
];
2024-12-13 11:03:05 +00:00
$payload [ 'status' ] = $resource -> status ;
2024-06-10 20:43:34 +00:00
2024-03-05 14:47:43 +00:00
return $payload ;
});
} else {
$server -> load ([ 'settings' ]);
}
2024-06-10 20:43:34 +00:00
2024-07-03 11:13:38 +00:00
$settings = $this -> removeSensitiveDataFromSettings ( $server -> settings );
$server = $this -> removeSensitiveData ( $server );
data_set ( $server , 'settings' , $settings );
return response () -> json ( serializeApiResponse ( $server ));
2024-02-16 20:56:38 +00:00
}
2024-06-21 19:35:02 +00:00
2024-07-09 08:45:10 +00:00
#[OA\Get(
summary : 'Resources' ,
description : 'Get resources by server.' ,
path : '/servers/{uuid}/resources' ,
2024-09-04 08:09:10 +00:00
operationId : 'get-resources-by-server-uuid' ,
2024-07-09 08:45:10 +00:00
security : [
[ 'bearerAuth' => []],
],
tags : [ 'Servers' ],
parameters : [
2024-09-09 16:38:40 +00:00
new OA\Parameter ( name : 'uuid' , in : 'path' , required : true , description : 'Server\'s UUID' , schema : new OA\Schema ( type : 'string' )),
2024-07-09 08:45:10 +00:00
],
responses : [
new OA\Response (
response : 200 ,
description : 'Get resources by server' ,
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'array' ,
items : new OA\Items (
type : 'object' ,
properties : [
'id' => [ 'type' => 'integer' ],
'uuid' => [ 'type' => 'string' ],
'name' => [ 'type' => 'string' ],
'type' => [ 'type' => 'string' ],
'created_at' => [ 'type' => 'string' ],
'updated_at' => [ 'type' => 'string' ],
'status' => [ 'type' => 'string' ],
]
)
)),
]),
new OA\Response (
response : 401 ,
ref : '#/components/responses/401' ,
),
new OA\Response (
response : 400 ,
ref : '#/components/responses/400' ,
),
]
)]
2024-07-03 11:13:38 +00:00
public function resources_by_server ( Request $request )
{
$teamId = getTeamIdFromToken ();
if ( is_null ( $teamId )) {
return invalidTokenResponse ();
}
$server = ModelsServer :: whereTeamId ( $teamId ) -> whereUuid ( request () -> uuid ) -> first ();
if ( is_null ( $server )) {
return response () -> json ([ 'message' => 'Server not found.' ], 404 );
}
$server [ 'resources' ] = $server -> definedResources () -> map ( function ( $resource ) {
$payload = [
'id' => $resource -> id ,
'uuid' => $resource -> uuid ,
'name' => $resource -> name ,
'type' => $resource -> type (),
'created_at' => $resource -> created_at ,
'updated_at' => $resource -> updated_at ,
];
2024-12-13 11:03:05 +00:00
$payload [ 'status' ] = $resource -> status ;
2024-07-03 11:13:38 +00:00
return $payload ;
});
$server = $this -> removeSensitiveData ( $server );
return response () -> json ( serializeApiResponse ( data_get ( $server , 'resources' )));
}
2024-07-09 08:45:10 +00:00
#[OA\Get(
summary : 'Domains' ,
description : 'Get domains by server.' ,
path : '/servers/{uuid}/domains' ,
2024-09-04 08:09:10 +00:00
operationId : 'get-domains-by-server-uuid' ,
2024-07-09 08:45:10 +00:00
security : [
[ 'bearerAuth' => []],
],
tags : [ 'Servers' ],
parameters : [
2024-09-09 16:38:40 +00:00
new OA\Parameter ( name : 'uuid' , in : 'path' , required : true , description : 'Server\'s UUID' , schema : new OA\Schema ( type : 'string' )),
2024-07-09 08:45:10 +00:00
],
responses : [
new OA\Response (
response : 200 ,
description : 'Get domains by server' ,
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'array' ,
items : new OA\Items (
type : 'object' ,
properties : [
'ip' => [ 'type' => 'string' ],
'domains' => [ 'type' => 'array' , 'items' => [ 'type' => 'string' ]],
]
)
)),
]),
new OA\Response (
response : 401 ,
ref : '#/components/responses/401' ,
),
new OA\Response (
response : 400 ,
ref : '#/components/responses/400' ,
),
]
)]
2024-07-03 11:13:38 +00:00
public function domains_by_server ( Request $request )
2024-06-21 19:35:02 +00:00
{
2024-07-01 14:26:50 +00:00
$teamId = getTeamIdFromToken ();
2024-06-21 19:35:02 +00:00
if ( is_null ( $teamId )) {
2024-07-01 14:26:50 +00:00
return invalidTokenResponse ();
2024-06-21 19:35:02 +00:00
}
2024-07-01 14:26:50 +00:00
$uuid = $request -> get ( 'uuid' );
2024-06-21 19:35:02 +00:00
if ( $uuid ) {
2026-02-25 10:52:18 +00:00
$application = Application :: ownedByCurrentTeamAPI ( $teamId ) -> where ( 'uuid' , $uuid ) -> first ();
if ( ! $application ) {
return response () -> json ([ 'message' => 'Application not found.' ], 404 );
}
2024-06-21 19:35:02 +00:00
2026-02-25 10:52:18 +00:00
return response () -> json ( serializeApiResponse ( $application -> fqdns ));
2024-06-21 19:35:02 +00:00
}
2025-01-07 14:31:43 +00:00
$projects = Project :: where ( 'team_id' , $teamId ) -> get ();
2024-06-21 19:35:02 +00:00
$domains = collect ();
$applications = $projects -> pluck ( 'applications' ) -> flatten ();
2024-10-01 08:37:40 +00:00
$settings = instanceSettings ();
2024-06-21 19:35:02 +00:00
if ( $applications -> count () > 0 ) {
foreach ( $applications as $application ) {
$ip = $application -> destination -> server -> ip ;
$fqdn = str ( $application -> fqdn ) -> explode ( ',' ) -> map ( function ( $fqdn ) {
2024-07-01 14:26:50 +00:00
$f = str ( $fqdn ) -> replace ( 'http://' , '' ) -> replace ( 'https://' , '' ) -> explode ( '/' );
return str ( str ( $f [ 0 ]) -> explode ( ':' )[ 0 ]);
2025-01-07 14:31:43 +00:00
}) -> filter ( function ( Stringable $fqdn ) {
return $fqdn -> isNotEmpty ();
2024-06-21 19:35:02 +00:00
});
2024-07-01 14:26:50 +00:00
2024-06-21 19:35:02 +00:00
if ( $ip === 'host.docker.internal' ) {
if ( $settings -> public_ipv4 ) {
$domains -> push ([
'domain' => $fqdn ,
'ip' => $settings -> public_ipv4 ,
]);
}
if ( $settings -> public_ipv6 ) {
$domains -> push ([
'domain' => $fqdn ,
'ip' => $settings -> public_ipv6 ,
]);
}
if ( ! $settings -> public_ipv4 && ! $settings -> public_ipv6 ) {
$domains -> push ([
'domain' => $fqdn ,
'ip' => $ip ,
]);
}
} else {
$domains -> push ([
'domain' => $fqdn ,
'ip' => $ip ,
]);
}
}
}
$services = $projects -> pluck ( 'services' ) -> flatten ();
if ( $services -> count () > 0 ) {
foreach ( $services as $service ) {
$service_applications = $service -> applications ;
if ( $service_applications -> count () > 0 ) {
2025-01-07 14:31:43 +00:00
foreach ( $service_applications as $application ) {
$fqdn = str ( $application -> fqdn ) -> explode ( ',' ) -> map ( function ( $fqdn ) {
2024-07-01 14:26:50 +00:00
$f = str ( $fqdn ) -> replace ( 'http://' , '' ) -> replace ( 'https://' , '' ) -> explode ( '/' );
return str ( str ( $f [ 0 ]) -> explode ( ':' )[ 0 ]);
2025-01-07 14:31:43 +00:00
}) -> filter ( function ( Stringable $fqdn ) {
return $fqdn -> isNotEmpty ();
2024-06-21 19:35:02 +00:00
});
if ( $ip === 'host.docker.internal' ) {
if ( $settings -> public_ipv4 ) {
$domains -> push ([
'domain' => $fqdn ,
'ip' => $settings -> public_ipv4 ,
]);
}
if ( $settings -> public_ipv6 ) {
$domains -> push ([
'domain' => $fqdn ,
'ip' => $settings -> public_ipv6 ,
]);
}
if ( ! $settings -> public_ipv4 && ! $settings -> public_ipv6 ) {
$domains -> push ([
'domain' => $fqdn ,
'ip' => $ip ,
]);
}
} else {
$domains -> push ([
'domain' => $fqdn ,
'ip' => $ip ,
]);
}
}
}
}
}
$domains = $domains -> groupBy ( 'ip' ) -> map ( function ( $domain ) {
return $domain -> pluck ( 'domain' ) -> flatten ();
}) -> map ( function ( $domain , $ip ) {
return [
'ip' => $ip ,
'domains' => $domain ,
];
}) -> values ();
2024-07-03 11:13:38 +00:00
return response () -> json ( serializeApiResponse ( $domains ));
2024-06-21 19:35:02 +00:00
}
2024-07-23 12:20:53 +00:00
#[OA\Post(
2024-07-23 12:36:44 +00:00
summary : 'Create' ,
2024-07-23 12:20:53 +00:00
description : 'Create Server.' ,
path : '/servers' ,
2024-09-04 08:09:10 +00:00
operationId : 'create-server' ,
2024-07-23 12:20:53 +00:00
security : [
[ 'bearerAuth' => []],
],
tags : [ 'Servers' ],
requestBody : new OA\RequestBody (
required : true ,
description : 'Server created.' ,
content : new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
properties : [
'name' => [ 'type' => 'string' , 'example' => 'My Server' , 'description' => 'The name of the server.' ],
'description' => [ 'type' => 'string' , 'example' => 'My Server Description' , 'description' => 'The description of the server.' ],
'ip' => [ 'type' => 'string' , 'example' => '127.0.0.1' , 'description' => 'The IP of the server.' ],
'port' => [ 'type' => 'integer' , 'example' => 22 , 'description' => 'The port of the server.' ],
'user' => [ 'type' => 'string' , 'example' => 'root' , 'description' => 'The user of the server.' ],
'private_key_uuid' => [ 'type' => 'string' , 'example' => 'og888os' , 'description' => 'The UUID of the private key.' ],
'is_build_server' => [ 'type' => 'boolean' , 'example' => false , 'description' => 'Is build server.' ],
'instant_validate' => [ 'type' => 'boolean' , 'example' => false , 'description' => 'Instant validate.' ],
2024-11-12 13:30:05 +00:00
'proxy_type' => [ 'type' => 'string' , 'enum' => [ 'traefik' , 'caddy' , 'none' ], 'example' => 'traefik' , 'description' => 'The proxy type.' ],
2024-07-23 12:20:53 +00:00
],
),
),
),
responses : [
new OA\Response (
response : 201 ,
description : 'Server created.' ,
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
properties : [
'uuid' => [ 'type' => 'string' , 'example' => 'og888os' , 'description' => 'The UUID of the server.' ],
]
)
),
]),
new OA\Response (
response : 401 ,
ref : '#/components/responses/401' ,
),
new OA\Response (
response : 400 ,
ref : '#/components/responses/400' ,
),
new OA\Response (
response : 404 ,
ref : '#/components/responses/404' ,
),
2025-10-12 12:20:45 +00:00
new OA\Response (
response : 422 ,
ref : '#/components/responses/422' ,
),
2024-07-23 12:20:53 +00:00
]
)]
public function create_server ( Request $request )
{
2024-11-12 13:30:05 +00:00
$allowedFields = [ 'name' , 'description' , 'ip' , 'port' , 'user' , 'private_key_uuid' , 'is_build_server' , 'instant_validate' , 'proxy_type' ];
2024-07-23 12:20:53 +00:00
$teamId = getTeamIdFromToken ();
if ( is_null ( $teamId )) {
return invalidTokenResponse ();
}
$return = validateIncomingRequest ( $request );
2025-01-07 14:31:43 +00:00
if ( $return instanceof \Illuminate\Http\JsonResponse ) {
2024-07-23 12:20:53 +00:00
return $return ;
}
$validator = customApiValidator ( $request -> all (), [
'name' => 'string|max:255' ,
'description' => 'string|nullable' ,
2026-03-03 10:51:38 +00:00
'ip' => [ 'string' , 'required' , new ValidServerIp ],
'port' => 'integer|nullable|between:1,65535' ,
2024-07-23 12:20:53 +00:00
'private_key_uuid' => 'string|required' ,
2026-03-03 10:51:38 +00:00
'user' => [ 'string' , 'nullable' , 'regex:/^[a-zA-Z0-9_-]+$/' ],
2024-07-23 12:20:53 +00:00
'is_build_server' => 'boolean|nullable' ,
'instant_validate' => 'boolean|nullable' ,
2024-11-12 13:30:05 +00:00
'proxy_type' => 'string|nullable' ,
2024-07-23 12:20:53 +00:00
]);
$extraFields = array_diff ( array_keys ( $request -> all ()), $allowedFields );
2025-01-07 14:31:43 +00:00
if ( $validator -> fails () || ! empty ( $extraFields )) {
2024-07-23 12:20:53 +00:00
$errors = $validator -> errors ();
2025-01-07 14:31:43 +00:00
if ( ! empty ( $extraFields )) {
foreach ( $extraFields as $field ) {
$errors -> add ( $field , 'This field is not allowed.' );
}
2024-07-23 12:20:53 +00:00
}
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => $errors ,
], 422 );
}
if ( ! $request -> name ) {
$request -> offsetSet ( 'name' , generate_random_name ());
}
if ( ! $request -> user ) {
$request -> offsetSet ( 'user' , 'root' );
}
if ( is_null ( $request -> port )) {
$request -> offsetSet ( 'port' , 22 );
}
if ( is_null ( $request -> is_build_server )) {
$request -> offsetSet ( 'is_build_server' , false );
}
if ( is_null ( $request -> instant_validate )) {
$request -> offsetSet ( 'instant_validate' , false );
}
2024-11-12 13:30:05 +00:00
if ( $request -> proxy_type ) {
$validProxyTypes = collect ( ProxyTypes :: cases ()) -> map ( function ( $proxyType ) {
return str ( $proxyType -> value ) -> lower ();
});
if ( ! $validProxyTypes -> contains ( str ( $request -> proxy_type ) -> lower ())) {
return response () -> json ([ 'message' => 'Invalid proxy type.' ], 422 );
}
}
2024-07-23 12:20:53 +00:00
$privateKey = PrivateKey :: whereTeamId ( $teamId ) -> whereUuid ( $request -> private_key_uuid ) -> first ();
if ( ! $privateKey ) {
return response () -> json ([ 'message' => 'Private key not found.' ], 404 );
}
2026-02-12 07:10:59 +00:00
$foundServer = ModelsServer :: whereIp ( $request -> ip ) -> first ();
if ( $foundServer ) {
if ( $foundServer -> team_id === $teamId ) {
return response () -> json ([ 'message' => 'A server with this IP/Domain already exists in your team.' ], 400 );
}
return response () -> json ([ 'message' => 'A server with this IP/Domain is already in use by another team.' ], 400 );
2024-07-23 12:20:53 +00:00
}
2024-11-12 13:30:05 +00:00
$proxyType = $request -> proxy_type ? str ( $request -> proxy_type ) -> upper () : ProxyTypes :: TRAEFIK -> value ;
2024-07-23 12:20:53 +00:00
$server = ModelsServer :: create ([
'name' => $request -> name ,
'description' => $request -> description ,
'ip' => $request -> ip ,
'port' => $request -> port ,
'user' => $request -> user ,
'private_key_id' => $privateKey -> id ,
'team_id' => $teamId ,
]);
2025-01-20 12:56:53 +00:00
$server -> proxy -> set ( 'type' , $proxyType );
$server -> proxy -> set ( 'status' , ProxyStatus :: EXITED -> value );
$server -> save ();
2024-07-23 12:20:53 +00:00
$server -> settings () -> update ([
'is_build_server' => $request -> is_build_server ,
]);
if ( $request -> instant_validate ) {
ValidateServer :: dispatch ( $server );
}
return response () -> json ([
'uuid' => $server -> uuid ,
]) -> setStatusCode ( 201 );
}
#[OA\Patch(
2024-07-23 12:36:44 +00:00
summary : 'Update' ,
2024-07-23 12:20:53 +00:00
description : 'Update Server.' ,
path : '/servers/{uuid}' ,
2024-09-04 08:09:10 +00:00
operationId : 'update-server-by-uuid' ,
2024-07-23 12:20:53 +00:00
security : [
[ 'bearerAuth' => []],
],
tags : [ 'Servers' ],
2024-11-23 01:40:22 +00:00
parameters : [
new OA\Parameter ( name : 'uuid' , in : 'path' , required : true , description : 'Server UUID' , schema : new OA\Schema ( type : 'string' )),
],
2024-07-23 12:20:53 +00:00
requestBody : new OA\RequestBody (
required : true ,
description : 'Server updated.' ,
content : new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
properties : [
'name' => [ 'type' => 'string' , 'description' => 'The name of the server.' ],
'description' => [ 'type' => 'string' , 'description' => 'The description of the server.' ],
'ip' => [ 'type' => 'string' , 'description' => 'The IP of the server.' ],
'port' => [ 'type' => 'integer' , 'description' => 'The port of the server.' ],
'user' => [ 'type' => 'string' , 'description' => 'The user of the server.' ],
'private_key_uuid' => [ 'type' => 'string' , 'description' => 'The UUID of the private key.' ],
'is_build_server' => [ 'type' => 'boolean' , 'description' => 'Is build server.' ],
'instant_validate' => [ 'type' => 'boolean' , 'description' => 'Instant validate.' ],
2024-11-12 13:30:05 +00:00
'proxy_type' => [ 'type' => 'string' , 'enum' => [ 'traefik' , 'caddy' , 'none' ], 'description' => 'The proxy type.' ],
2024-07-23 12:20:53 +00:00
],
),
),
),
responses : [
new OA\Response (
response : 201 ,
description : 'Server updated.' ,
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
2024-11-23 01:17:38 +00:00
ref : '#/components/schemas/Server'
2024-07-23 12:20:53 +00:00
)
),
]),
new OA\Response (
response : 401 ,
ref : '#/components/responses/401' ,
),
new OA\Response (
response : 400 ,
ref : '#/components/responses/400' ,
),
new OA\Response (
response : 404 ,
ref : '#/components/responses/404' ,
),
2025-10-12 12:20:45 +00:00
new OA\Response (
response : 422 ,
ref : '#/components/responses/422' ,
),
2024-07-23 12:20:53 +00:00
]
)]
public function update_server ( Request $request )
{
2024-11-12 13:30:05 +00:00
$allowedFields = [ 'name' , 'description' , 'ip' , 'port' , 'user' , 'private_key_uuid' , 'is_build_server' , 'instant_validate' , 'proxy_type' ];
2024-07-23 12:20:53 +00:00
$teamId = getTeamIdFromToken ();
if ( is_null ( $teamId )) {
return invalidTokenResponse ();
}
$return = validateIncomingRequest ( $request );
2025-01-07 14:31:43 +00:00
if ( $return instanceof \Illuminate\Http\JsonResponse ) {
2024-07-23 12:20:53 +00:00
return $return ;
}
$validator = customApiValidator ( $request -> all (), [
'name' => 'string|max:255|nullable' ,
'description' => 'string|nullable' ,
2026-03-03 10:51:38 +00:00
'ip' => [ 'string' , 'nullable' , new ValidServerIp ],
'port' => 'integer|nullable|between:1,65535' ,
2024-07-23 12:20:53 +00:00
'private_key_uuid' => 'string|nullable' ,
2026-03-03 10:51:38 +00:00
'user' => [ 'string' , 'nullable' , 'regex:/^[a-zA-Z0-9_-]+$/' ],
2024-07-23 12:20:53 +00:00
'is_build_server' => 'boolean|nullable' ,
'instant_validate' => 'boolean|nullable' ,
2024-11-12 13:30:05 +00:00
'proxy_type' => 'string|nullable' ,
2024-07-23 12:20:53 +00:00
]);
$extraFields = array_diff ( array_keys ( $request -> all ()), $allowedFields );
2025-01-07 14:31:43 +00:00
if ( $validator -> fails () || ! empty ( $extraFields )) {
2024-07-23 12:20:53 +00:00
$errors = $validator -> errors ();
2025-01-07 14:31:43 +00:00
if ( ! empty ( $extraFields )) {
foreach ( $extraFields as $field ) {
$errors -> add ( $field , 'This field is not allowed.' );
}
2024-07-23 12:20:53 +00:00
}
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => $errors ,
], 422 );
}
$server = ModelsServer :: whereTeamId ( $teamId ) -> whereUuid ( $request -> uuid ) -> first ();
if ( ! $server ) {
return response () -> json ([ 'message' => 'Server not found.' ], 404 );
}
2024-11-12 13:30:05 +00:00
if ( $request -> proxy_type ) {
$validProxyTypes = collect ( ProxyTypes :: cases ()) -> map ( function ( $proxyType ) {
return str ( $proxyType -> value ) -> lower ();
});
if ( $validProxyTypes -> contains ( str ( $request -> proxy_type ) -> lower ())) {
$server -> changeProxy ( $request -> proxy_type , async : true );
} else {
return response () -> json ([ 'message' => 'Invalid proxy type.' ], 422 );
}
}
2024-07-23 12:20:53 +00:00
$server -> update ( $request -> only ([ 'name' , 'description' , 'ip' , 'port' , 'user' ]));
if ( $request -> is_build_server ) {
$server -> settings () -> update ([
'is_build_server' => $request -> is_build_server ,
]);
}
if ( $request -> instant_validate ) {
ValidateServer :: dispatch ( $server );
}
2024-11-25 12:41:59 +00:00
return response () -> json ([
'uuid' => $server -> uuid ,
]) -> setStatusCode ( 201 );
2024-07-23 12:20:53 +00:00
}
#[OA\Delete(
summary : 'Delete' ,
description : 'Delete server by UUID.' ,
path : '/servers/{uuid}' ,
2024-09-04 08:09:10 +00:00
operationId : 'delete-server-by-uuid' ,
2024-07-23 12:20:53 +00:00
security : [
[ 'bearerAuth' => []],
],
tags : [ 'Servers' ],
parameters : [
new OA\Parameter (
name : 'uuid' ,
in : 'path' ,
description : 'UUID of the server.' ,
required : true ,
schema : new OA\Schema (
type : 'string' ,
)
),
],
responses : [
new OA\Response (
response : 200 ,
description : 'Server deleted.' ,
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
properties : [
'message' => [ 'type' => 'string' , 'example' => 'Server deleted.' ],
]
)
),
]),
new OA\Response (
response : 401 ,
ref : '#/components/responses/401' ,
),
new OA\Response (
response : 400 ,
ref : '#/components/responses/400' ,
),
new OA\Response (
response : 404 ,
ref : '#/components/responses/404' ,
),
2025-10-12 12:20:45 +00:00
new OA\Response (
response : 422 ,
ref : '#/components/responses/422' ,
),
2024-07-23 12:20:53 +00:00
]
)]
public function delete_server ( Request $request )
{
$teamId = getTeamIdFromToken ();
if ( is_null ( $teamId )) {
return invalidTokenResponse ();
}
if ( ! $request -> uuid ) {
return response () -> json ([ 'message' => 'Uuid is required.' ], 422 );
}
$server = ModelsServer :: whereTeamId ( $teamId ) -> whereUuid ( $request -> uuid ) -> first ();
if ( ! $server ) {
return response () -> json ([ 'message' => 'Server not found.' ], 404 );
}
2026-03-13 15:58:26 +00:00
$force = filter_var ( $request -> query ( 'force' , false ), FILTER_VALIDATE_BOOLEAN );
if ( $server -> definedResources () -> count () > 0 && ! $force ) {
return response () -> json ([ 'message' => 'Server has resources. Use ?force=true to delete all resources and the server, or delete resources manually first.' ], 400 );
2024-07-23 12:20:53 +00:00
}
2025-01-20 12:56:53 +00:00
if ( $server -> isLocalhost ()) {
return response () -> json ([ 'message' => 'Local server cannot be deleted.' ], 400 );
}
2026-03-13 15:58:26 +00:00
if ( $force ) {
foreach ( $server -> definedResources () as $resource ) {
DeleteResourceJob :: dispatch ( $resource );
}
}
2024-10-17 12:56:36 +00:00
$server -> delete ();
2025-10-09 14:54:13 +00:00
DeleteServer :: dispatch (
$server -> id ,
false , // Don't delete from Hetzner via API
$server -> hetzner_server_id ,
$server -> cloud_provider_token_id ,
$server -> team_id
);
2024-07-23 12:20:53 +00:00
return response () -> json ([ 'message' => 'Server deleted.' ]);
}
#[OA\Get(
summary : 'Validate' ,
description : 'Validate server by UUID.' ,
path : '/servers/{uuid}/validate' ,
2024-09-04 08:09:10 +00:00
operationId : 'validate-server-by-uuid' ,
2024-07-23 12:20:53 +00:00
security : [
[ 'bearerAuth' => []],
],
tags : [ 'Servers' ],
parameters : [
2024-07-24 19:10:32 +00:00
new OA\Parameter ( name : 'uuid' , in : 'path' , required : true , description : 'Server UUID' , schema : new OA\Schema ( type : 'string' )),
2024-07-23 12:20:53 +00:00
],
responses : [
new OA\Response (
response : 201 ,
description : 'Server validation started.' ,
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
properties : [
'message' => [ 'type' => 'string' , 'example' => 'Validation started.' ],
]
)
),
]),
new OA\Response (
response : 401 ,
ref : '#/components/responses/401' ,
),
new OA\Response (
response : 400 ,
ref : '#/components/responses/400' ,
),
new OA\Response (
response : 404 ,
ref : '#/components/responses/404' ,
),
2025-10-12 12:20:45 +00:00
new OA\Response (
response : 422 ,
ref : '#/components/responses/422' ,
),
2024-07-23 12:20:53 +00:00
]
)]
public function validate_server ( Request $request )
{
$teamId = getTeamIdFromToken ();
if ( is_null ( $teamId )) {
return invalidTokenResponse ();
}
if ( ! $request -> uuid ) {
return response () -> json ([ 'message' => 'Uuid is required.' ], 422 );
}
$server = ModelsServer :: whereTeamId ( $teamId ) -> whereUuid ( $request -> uuid ) -> first ();
if ( ! $server ) {
return response () -> json ([ 'message' => 'Server not found.' ], 404 );
}
ValidateServer :: dispatch ( $server );
2025-04-19 11:17:21 +00:00
return response () -> json ([ 'message' => 'Validation started.' ], 201 );
2024-07-23 12:20:53 +00:00
}
2024-02-16 20:56:38 +00:00
}