Initial commit

This commit is contained in:
Ronald Huynen
2026-03-23 21:37:59 +01:00
commit 2547717edb
2193 changed files with 972171 additions and 0 deletions

View File

@@ -0,0 +1,158 @@
<?php
namespace App\Http\Controllers;
use App\Helpers\ProfileAuthorizationHelper;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log;
use ZipArchive;
class BackupChunkUploadController extends Controller
{
/**
* Receive a single chunk of the backup file.
* POST /posts/backup-upload/chunk
*/
public function uploadChunk(Request $request): JsonResponse
{
$this->authorizeAdminAccess();
$request->validate([
'uploadId' => 'required|string|uuid',
'chunkIndex' => 'required|integer|min:0|max:999',
'totalChunks' => 'required|integer|min:1|max:1000',
'chunk' => 'required|file|max:3072', // 3MB max per chunk
]);
$uploadId = $request->input('uploadId');
$chunkIndex = (int) $request->input('chunkIndex');
$totalChunks = (int) $request->input('totalChunks');
if ($chunkIndex >= $totalChunks) {
return response()->json(['error' => 'Invalid chunk index'], 422);
}
$chunkDir = storage_path("app/temp/chunks/{$uploadId}");
if (!File::isDirectory($chunkDir)) {
File::makeDirectory($chunkDir, 0755, true);
}
$request->file('chunk')->move($chunkDir, "chunk_{$chunkIndex}");
return response()->json([
'success' => true,
'chunkIndex' => $chunkIndex,
'received' => count(File::files($chunkDir)),
'total' => $totalChunks,
]);
}
/**
* Finalize: reassemble chunks into the complete file.
* POST /posts/backup-upload/finalize
*/
public function finalize(Request $request): JsonResponse
{
$this->authorizeAdminAccess();
$request->validate([
'uploadId' => 'required|string|uuid',
'totalChunks' => 'required|integer|min:1|max:1000',
'fileName' => 'required|string|max:255',
]);
$uploadId = $request->input('uploadId');
$totalChunks = (int) $request->input('totalChunks');
$fileName = basename($request->input('fileName'));
$chunkDir = storage_path("app/temp/chunks/{$uploadId}");
$outputDir = storage_path('app/temp');
if (!File::isDirectory($outputDir)) {
File::makeDirectory($outputDir, 0755, true);
}
// Verify all chunks exist
for ($i = 0; $i < $totalChunks; $i++) {
if (!File::exists("{$chunkDir}/chunk_{$i}")) {
return response()->json(['error' => "Missing chunk {$i}"], 422);
}
}
// Reassemble
$ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
if (!in_array($ext, ['zip', 'json'])) {
File::deleteDirectory($chunkDir);
return response()->json(['error' => 'Invalid file type'], 422);
}
$assembledName = 'restore_' . uniqid() . '.' . $ext;
$assembledPath = "{$outputDir}/{$assembledName}";
$output = fopen($assembledPath, 'wb');
for ($i = 0; $i < $totalChunks; $i++) {
$chunkPath = "{$chunkDir}/chunk_{$i}";
$input = fopen($chunkPath, 'rb');
stream_copy_to_stream($input, $output);
fclose($input);
}
fclose($output);
// Clean up chunks
File::deleteDirectory($chunkDir);
// Validate ZIP if applicable
if ($ext === 'zip') {
$zip = new ZipArchive();
if ($zip->open($assembledPath) !== true) {
@unlink($assembledPath);
return response()->json(['error' => 'Invalid ZIP file'], 422);
}
if ($zip->getFromName('backup.json') === false) {
$zip->close();
@unlink($assembledPath);
return response()->json(['error' => 'ZIP does not contain backup.json'], 422);
}
$zip->close();
}
// Store path in session for Livewire to retrieve
session(["backup_restore_file_{$uploadId}" => $assembledPath]);
return response()->json([
'success' => true,
'uploadId' => $uploadId,
]);
}
/**
* Authorization check matching RequiresAdminAuthorization trait logic.
*/
private function authorizeAdminAccess(): void
{
$activeProfileType = session('activeProfileType');
$activeProfileId = session('activeProfileId');
if (!$activeProfileType || !$activeProfileId) {
abort(403, 'No active profile selected');
}
$profile = $activeProfileType::find($activeProfileId);
if (!$profile) {
abort(403, 'Active profile not found');
}
ProfileAuthorizationHelper::authorize($profile);
if ($profile instanceof \App\Models\Admin) {
return;
}
if ($profile instanceof \App\Models\Bank && $profile->level === 0) {
return;
}
abort(403, 'Admin or central bank access required');
}
}