2020-10-31 17:44:20 -06:00
|
|
|
<?php
|
|
|
|
|
|
2024-03-12 22:39:16 -04:00
|
|
|
namespace App\Http\Controllers\Api\Remote\Backups;
|
2020-10-31 17:44:20 -06:00
|
|
|
|
2025-09-24 13:34:19 +02:00
|
|
|
use App\Exceptions\Http\HttpForbiddenException;
|
2026-01-16 21:52:24 +01:00
|
|
|
use App\Extensions\BackupAdapter\BackupAdapterService;
|
|
|
|
|
use App\Extensions\BackupAdapter\Schemas\S3BackupSchema;
|
2025-09-24 13:34:19 +02:00
|
|
|
use App\Http\Controllers\Controller;
|
|
|
|
|
use App\Models\Backup;
|
2025-09-08 13:12:33 -04:00
|
|
|
use App\Models\Node;
|
2025-09-24 13:34:19 +02:00
|
|
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
2020-10-31 17:44:20 -06:00
|
|
|
use Illuminate\Http\JsonResponse;
|
2025-09-24 13:34:19 +02:00
|
|
|
use Illuminate\Http\Request;
|
2020-11-01 10:11:45 -07:00
|
|
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
2025-09-24 13:34:19 +02:00
|
|
|
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
|
2020-10-31 17:44:20 -06:00
|
|
|
|
|
|
|
|
class BackupRemoteUploadController extends Controller
|
|
|
|
|
{
|
2022-09-25 22:38:49 +02:00
|
|
|
public const DEFAULT_MAX_PART_SIZE = 5 * 1024 * 1024 * 1024;
|
2020-10-31 17:44:20 -06:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* BackupRemoteUploadController constructor.
|
|
|
|
|
*/
|
2026-01-16 21:52:24 +01:00
|
|
|
public function __construct(private BackupAdapterService $backupService) {}
|
2020-10-31 17:44:20 -06:00
|
|
|
|
|
|
|
|
/**
|
2020-11-01 10:11:45 -07:00
|
|
|
* Returns the required presigned urls to upload a backup to S3 cloud storage.
|
2020-10-31 17:44:20 -06:00
|
|
|
*
|
2026-01-16 21:52:24 +01:00
|
|
|
* @throws BadRequestHttpException
|
2025-09-08 13:12:33 -04:00
|
|
|
* @throws ModelNotFoundException
|
2026-01-16 21:52:24 +01:00
|
|
|
* @throws HttpForbiddenException
|
|
|
|
|
* @throws ConflictHttpException
|
2020-10-31 17:44:20 -06:00
|
|
|
*/
|
2022-10-14 10:59:20 -06:00
|
|
|
public function __invoke(Request $request, string $backup): JsonResponse
|
2020-10-31 17:44:20 -06:00
|
|
|
{
|
2025-09-08 13:12:33 -04:00
|
|
|
/** @var Node $node */
|
2024-05-16 11:53:18 +02:00
|
|
|
$node = $request->attributes->get('node');
|
|
|
|
|
|
2020-12-16 14:15:07 -07:00
|
|
|
$size = (int) $request->query('size');
|
2020-11-01 14:46:01 -08:00
|
|
|
if (empty($size)) {
|
|
|
|
|
throw new BadRequestHttpException('A non-empty "size" query parameter must be provided.');
|
2020-10-31 17:44:20 -06:00
|
|
|
}
|
|
|
|
|
|
2026-01-16 21:52:24 +01:00
|
|
|
$backup = Backup::where('uuid', $backup)->firstOrFail();
|
2024-05-16 11:53:18 +02:00
|
|
|
|
|
|
|
|
// Check that the backup is "owned" by the node making the request. This avoids other nodes
|
|
|
|
|
// from messing with backups that they don't own.
|
2026-01-16 21:52:24 +01:00
|
|
|
if ($backup->server->node_id !== $node->id) {
|
2024-05-16 11:53:18 +02:00
|
|
|
throw new HttpForbiddenException('You do not have permission to access that backup.');
|
|
|
|
|
}
|
2020-10-31 17:44:20 -06:00
|
|
|
|
2026-01-16 21:52:24 +01:00
|
|
|
// Prevent backups that have already been completed from trying to be uploaded again.
|
|
|
|
|
if (!is_null($backup->completed_at)) {
|
2020-12-27 11:34:55 -08:00
|
|
|
throw new ConflictHttpException('This backup is already in a completed state.');
|
2020-10-31 17:44:20 -06:00
|
|
|
}
|
|
|
|
|
|
2026-01-16 21:52:24 +01:00
|
|
|
// Ensure we are using the S3 schema.
|
|
|
|
|
$schema = $this->backupService->get(collect($node->backupHosts)->first()->schema);
|
|
|
|
|
if (!$schema || !$schema instanceof S3BackupSchema) {
|
|
|
|
|
throw new BadRequestHttpException('The configured backup schema is not an S3 compatible.');
|
2020-10-31 17:44:20 -06:00
|
|
|
}
|
|
|
|
|
|
2026-01-16 21:52:24 +01:00
|
|
|
return new JsonResponse($schema->getUploadParts($backup, $size));
|
2022-09-25 22:38:49 +02:00
|
|
|
}
|
2020-10-31 17:44:20 -06:00
|
|
|
}
|