diff --git a/.env.example b/.env.example index 927e07c..6695bd3 100644 --- a/.env.example +++ b/.env.example @@ -4,7 +4,6 @@ APP_KEY= APP_DEBUG=false APP_URL= APP_TIMEZONE=Europe/Paris -APP_LOCALE=en UPLOAD_MAX_FILESIZE=1G UPLOAD_MAX_FILES=1000 diff --git a/app/Console/Commands/CreateUser.php b/app/Console/Commands/CreateUser.php index a96ca15..3a79a95 100644 --- a/app/Console/Commands/CreateUser.php +++ b/app/Console/Commands/CreateUser.php @@ -2,6 +2,7 @@ namespace App\Console\Commands; +use App\Models\User; use Exception; use Illuminate\Console\Command; use Illuminate\Support\Facades\Hash; @@ -42,8 +43,8 @@ class CreateUser extends Command goto login; } - // Checking login unicity - if (Storage::disk('users')->exists($login.'.json')) { + $existing = User::find($login); + if (! empty($existing) && $existing->count() > 0) { $this->error('User "'.$login.'" already exists'); unset($login); goto login; @@ -53,18 +54,17 @@ class CreateUser extends Command // Asking for user's password $password = $this->secret('Enter the user\'s password'); - if (! preg_match('~^.{4,100}$i~', $password)) { - $this->error('Invalid password format. Must contains between 5 and 100 chars'); + if (! preg_match('~^[^\s]{5,100}$~', $password)) { + $this->error('Invalid password format. Must contains between 5 and 100 chars without space'); unset($password); goto password; } try { - Storage::disk('users')->put($login.'.json', json_encode([ + User::create([ 'username' => $login, - 'password' => Hash::make($password), - 'bundles' => [] - ])); + 'password' => Hash::make($password) + ]); $this->info('User has been created'); } diff --git a/app/Helpers/User.php b/app/Helpers/Auth.php similarity index 72% rename from app/Helpers/User.php rename to app/Helpers/Auth.php index 5eba397..50a1dec 100644 --- a/app/Helpers/User.php +++ b/app/Helpers/Auth.php @@ -5,8 +5,9 @@ namespace App\Helpers; use Exception; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Storage; +use App\Models\User; -class User { +class Auth { static function isLogged():Bool { // Checking credentials auth @@ -27,7 +28,7 @@ class User { $user = self::getUserDetails($username); // Checking password - if (true !== Hash::check($password, $user['password'])) { + if (true !== Hash::check($password, $user->password)) { throw new Exception('Invalid password'); } @@ -41,30 +42,19 @@ class User { } } - static function getLoggedUserDetails():Array { + static function getLoggedUserDetails():User { if (self::isLogged()) { return self::getUserDetails(session()->get('username')); } throw new UnauthenticatedUser('User is not logged in'); } - static function getUserDetails(String $username):Array { - - // Checking user existence - if (Storage::disk('users')->missing($username.'.json')) { + static function getUserDetails(String $username):User { + $user = User::find($username); + if (empty($user)) { throw new Exception('No such user'); } - // Getting user.json - if (! $json = Storage::disk('users')->get($username.'.json')) { - throw new Exception('Could not fetch user details'); - } - - // Decoding JSON - if (! $user = json_decode($json, true)) { - throw new Exception('Cannot decode JSON file'); - } - return $user; } diff --git a/app/Helpers/Upload.php b/app/Helpers/Upload.php index e3af5c0..1fb13f7 100644 --- a/app/Helpers/Upload.php +++ b/app/Helpers/Upload.php @@ -70,7 +70,7 @@ class Upload { return $metadata; } - public static function humanFilesize(Float $size, Int $precision = 2):Int { + public static function humanFilesize(Float $size, Int $precision = 2):String { if ($size > 0) { $size = (int) $size; $base = log($size) / log(1024); diff --git a/app/Http/Controllers/BundleController.php b/app/Http/Controllers/BundleController.php index bd0168c..7e07874 100644 --- a/app/Http/Controllers/BundleController.php +++ b/app/Http/Controllers/BundleController.php @@ -4,31 +4,20 @@ namespace App\Http\Controllers; use ZipArchive; use Exception; -use Carbon\Carbon; use App\Helpers\Upload; +use App\Models\Bundle; use Illuminate\Http\Request; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; - +use App\Http\Resources\BundleResource; class BundleController extends Controller { // The bundle content preview - public function previewBundle(Request $request, $bundleId) { - - // Getting bundle metadata - abort_if(! $metadata = Upload::getMetadata($bundleId), 404); - - // Handling dates as Carbon - Carbon::setLocale(config('app.locale')); - $metadata['created_at_carbon'] = Carbon::createFromTimestamp($metadata['created_at']); - $metadata['expires_at_carbon'] = Carbon::createFromTimestamp($metadata['expires_at']); - + public function previewBundle(Request $request, Bundle $bundle) { return view('download', [ - 'bundleId' => $bundleId, - 'metadata' => $metadata, - 'auth' => $metadata['preview_token'] + 'bundle' => new BundleResource($bundle) ]); } @@ -36,23 +25,18 @@ class BundleController extends Controller // The download method // - the bundle // - or just one file - public function downloadZip(Request $request, $bundleId) { - - // Getting bundle metadata - abort_if(! $metadata = Upload::getMetadata($bundleId), 404); + public function downloadZip(Request $request, Bundle $bundle) { try { // Download of the full bundle // We must create a Zip archive - Upload::setMetadata($bundleId, [ - 'downloads' => $metadata['downloads'] + 1 - ]); + $bundle->downloads ++; + $bundle->save(); - $filename = config('filesystems.disks.uploads.root').'/'.$metadata['bundle_id'].'/bundle.zip'; - if (1 == 1 || ! file_exists($filename)) { - // Timestamped filename + + $filename = Storage::disk('uploads')->path('').'/'.$bundle->slug.'/bundle.zip'; + if (! file_exists($filename)) { $bundlezip = fopen($filename, 'w'); - //chmod($filename, 0600); // Creating the archive $zip = new ZipArchive; @@ -61,14 +45,14 @@ class BundleController extends Controller } // Setting password when required - if (! empty($metadata['password'])) { - $zip->setPassword($metadata['password']); + if (! empty($bundle->password)) { + $zip->setPassword($bundle->password); } // Adding the files into the Zip with their real names - foreach ($metadata['files'] as $k => $file) { - if (file_exists(config('filesystems.disks.uploads.root').'/'.$file['fullpath'])) { - $name = $file['original']; + foreach ($bundle->files as $k => $file) { + if (file_exists(config('filesystems.disks.uploads.root').'/'.$file->fullpath)) { + $name = $file->original; // If a file in the archive has the same name if (false !== $zip->locateName($name)) { @@ -89,9 +73,9 @@ class BundleController extends Controller $name = $newname; } // Finally adding files - $zip->addFile(config('filesystems.disks.uploads.root').'/'.$file['fullpath'], $name); + $zip->addFile(config('filesystems.disks.uploads.root').'/'.$file->fullpath, $name); - if (! empty($metadata['password'])) { + if (! empty($bundle->password)) { $zip->setEncryptionIndex($k, ZipArchive::EM_AES_256); } } @@ -116,16 +100,17 @@ class BundleController extends Controller $limit_rate = $filesize; } + // Flushing everything + flush(); + // Let's download now header('Content-Type: application/octet-stream'); - header('Content-Disposition: attachment; filename="'.Str::slug($metadata['title']).'-'.time().'.zip'.'"'); + header('Content-Disposition: attachment; filename="'.Str::slug($bundle->title).'-'.time().'.zip'.'"'); header('Cache-Control: no-cache, must-revalidate'); header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); header('Content-Length: '.$filesize); - flush(); - - + // Downloading $fh = fopen($filename, 'rb'); while (! feof($fh)) { echo fread($fh, round($limit_rate)); diff --git a/app/Http/Controllers/UploadController.php b/app/Http/Controllers/UploadController.php index cdaaf44..3ae0843 100644 --- a/app/Http/Controllers/UploadController.php +++ b/app/Http/Controllers/UploadController.php @@ -8,44 +8,44 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\Session; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; +use App\Http\Resources\BundleResource; +use App\Http\Resources\FileResource; + +use App\Models\Bundle; +use App\Models\File; class UploadController extends Controller { - public function createBundle(Request $request, String $bundleId = null) { - $metadata = Upload::getMetadata($bundleId); - - abort_if(empty($metadata), 404); - + public function createBundle(Request $request, Bundle $bundle) { return view('upload', [ - 'metadata' => $metadata ?? null, + 'bundle' => $bundle->toArray(), 'baseUrl' => config('app.url') ]); } - public function getMetadata(Request $request, String $bundleId) { - return response()->json(Upload::getMetadata($bundleId)); - } - // The upload form - public function storeBundle(Request $request, String $bundleId) { + public function storeBundle(Request $request, Bundle $bundle) { - $metadata = [ - 'expiry' => $request->expiry ?? null, - 'password' => $request->password ?? null, - 'title' => $request->title ?? null, - 'description' => $request->description ?? null, - 'max_downloads' => $request->max_downloads ?? 0 - ]; + try { + $bundle->update([ + 'expiry' => $request->expiry ?? null, + 'password' => $request->password ?? null, + 'title' => $request->title ?? null, + 'description' => $request->description ?? null, + 'max_downloads' => $request->max_downloads ?? 0 + ]); - $metadata = Upload::setMetaData($bundleId, $metadata); - - // Creating the bundle folder - Storage::disk('uploads')->makeDirectory($bundleId); - - return response()->json($metadata); + return response()->json(new BundleResource($bundle)); + } + catch (Exception $e) { + return response()->json([ + 'result' => false, + 'message' => $e->getMessage() + ], 500); + } } - public function uploadFile(Request $request, String $bundleId) { + public function uploadFile(Request $request, Bundle $bundle) { // Validating form data $request->validate([ @@ -59,22 +59,23 @@ class UploadController extends Controller // Moving file to final destination try { - $fullpath = $request->file('file')->storeAs( - $bundleId, $filename, 'uploads' - ); - - $size = Storage::disk('uploads')->size($fullpath); + $size = $request->file->getSize(); if (config('sharing.upload_prevent_duplicates', true) === true && $size < Upload::humanReadableToBytes(config('sharing.hash_maxfilesize', '1G'))) { - $hash = sha1_file(Storage::disk('uploads')->path($fullpath)); - if (Upload::isDuplicateFile($bundleId, $hash)) { - Storage::disk('uploads')->delete($fullpath); + $hash = sha1_file($request->file->getPathname()); + + $existing = $bundle->files->whereNotNull('hash')->where('hash', $hash)->count(); + if (! empty($existing) && $existing > 0) { throw new Exception(__('app.duplicate-file')); } } + $fullpath = $request->file('file')->storeAs( + $bundle->slug, $filename, 'uploads' + ); // Generating file metadata - $file = [ + $file = new File([ 'uuid' => $request->uuid, + 'bundle_slug' => $bundle->slug, 'original' => $original, 'filesize' => $size, 'fullpath' => $fullpath, @@ -82,78 +83,72 @@ class UploadController extends Controller 'created_at' => time(), 'status' => true, 'hash' => $hash ?? null - ]; - - $metadata = Upload::addFileMetaData($bundleId, $file); - - return response()->json([ - 'result' => true, - 'file' => $file ]); + $file->save(); + + return response()->json(new FileResource($file)); } catch (Exception $e) { return response()->json([ 'result' => false, - 'error' => $e->getMessage(), - 'file' => $e->getFile(), - 'line' => $e->getLine() + 'message' => $e->getMessage() ], 500); } } - public function deleteFile(Request $request, String $bundleId) { + public function deleteFile(Request $request, Bundle $bundle) { $request->validate([ 'uuid' => 'required|uuid' ]); try { - $metadata = Upload::deleteFile($bundleId, $request->uuid); - return response()->json($metadata); + // Getting file model + $file = File::findOrFail($request->uuid); + + // Physically deleting the file + if (! Storage::disk('uploads')->delete($file->fullpath)) { + throw new Exception('Cannot delete file from disk'); + } + + // Destroying the model + $file->delete(); + + return response()->json(new BundleResource($bundle)); } catch (Exception $e) { return response()->json([ 'result' => false, - 'error' => $e->getMessage(), - 'file' => $e->getFile(), - 'line' => $e->getLine() + 'message' => $e->getMessage() ], 500); } } - public function completeBundle(Request $request, String $bundleId) { - - $metadata = Upload::getMetadata($bundleId); + public function completeBundle(Request $request, Bundle $bundle) { // Processing size - if (! empty($metadata['files'])) { - $size = 0; - foreach ($metadata['files'] as $f) { - $size += $f['filesize']; - } + $size = 0; + foreach ($bundle->files as $f) { + $size += $f['filesize']; } // Saving metadata try { - $preview_token = substr(sha1(uniqid('dbdl', true)), 0, rand(10, 15)); + $bundle->completed = true; + $bundle->expires_at = time()+$bundle->expiry; + $bundle->fullsize = $size; + $bundle->preview_link = route('bundle.preview', ['bundle' => $bundle, 'auth' => $bundle->preview_token]); + $bundle->download_link = route('bundle.zip.download', ['bundle' => $bundle, 'auth' => $bundle->preview_token]); + $bundle->deletion_link = route('upload.bundle.delete', ['bundle' => $bundle]); + $bundle->save(); - $metadata = Upload::setMetadata($bundleId, [ - 'completed' => true, - 'expires_at' => time()+$metadata['expiry'], - 'fullsize' => $size, - 'preview_token' => $preview_token, - 'preview_link' => route('bundle.preview', ['bundle' => $bundleId, 'auth' => $preview_token]), - 'download_link' => route('bundle.zip.download', ['bundle' => $bundleId, 'auth' => $preview_token]), - 'deletion_link' => route('upload.bundle.delete', ['bundle' => $bundleId]) - ]); - - return response()->json($metadata); + return response()->json(new BundleResource($bundle)); } catch (\Exception $e) { return response()->json([ 'result' => false, - 'error' => $e->getMessage() + 'message' => $e->getMessage() ], 500); } } @@ -164,23 +159,25 @@ class UploadController extends Controller * We invalidate the expiry date and let the CRON * task do the hard work */ - public function deleteBundle(Request $request, $bundleId) { + public function deleteBundle(Request $request, Bundle $bundle) { - // Tries to get the metadata file - $metadata = Upload::getMetadata($bundleId); - - // Forcing file to expire - $metadata['expires_at'] = time() - (3600 * 24 * 30); - - // Rewriting the metadata file try { - $metadata = Upload::setMetadata($bundleId, $metadata); - return response()->json($metadata); + // Forcing bundle to expire + $bundle->expires_at = time() - (3600 * 24 * 30); + $bundle->save(); + + // Then deleting file models + foreach ($bundle->files as $f) { + $f->forceDelete(); + } + + return response()->json(new BundleResource($bundle)); } catch (Exception $e) { return response()->json([ - 'success' => false - ]); + 'success' => false, + 'message' => $e->getMessage() + ], 500); } } diff --git a/app/Http/Controllers/WebController.php b/app/Http/Controllers/WebController.php index f1d8db7..2fe9f00 100644 --- a/app/Http/Controllers/WebController.php +++ b/app/Http/Controllers/WebController.php @@ -1,16 +1,26 @@ bundles; + if (! empty($bundles) && $bundles->count() > 0) { + $bundles = BundleResource::collection($bundles) ; + } + } + return view('homepage', [ + 'bundles' => $bundles ?? [] + ]); } public function login() { @@ -26,7 +36,7 @@ class WebController extends Controller ]); try { - if (true === User::loginUser($request->login, $request->password)) { + if (true === Auth::loginUser($request->login, $request->password)) { return response()->json([ 'result' => true, ]); @@ -35,63 +45,55 @@ class WebController extends Controller catch (Exception $e) { return response()->json([ 'result' => false, - 'error' => 'Authentication failed, please try again.' + 'message' => 'Authentication failed, please try again.' ], 403); } // This should never happen return response()->json([ 'result' => false, - 'error' => 'Unexpected error' - ]); + 'message' => 'Unexpected error' + ], 500); } function newBundle(Request $request) { // Aborting if request is not AJAX abort_if(! $request->ajax(), 403); - $request->validate([ - 'bundle_id' => 'required', - 'owner_token' => 'required' - ]); - - $owner = null; - if (User::isLogged()) { - $user = User::getLoggedUserDetails(); - $owner = $user['username']; - - // If bundle dimension is not initialized - if (empty($user['bundles']) || ! is_array($user['bundles'])) { - $user['bundles'] = []; - } - - array_push($user['bundles'], $request->bundle_id); - User::setUserDetails($user['username'], $user); + if (Auth::isLogged()) { + $user = Auth::getLoggedUserDetails(); } - $metadata = [ - 'owner' => $owner, - 'created_at' => time(), - 'completed' => false, - 'expiry' => config('sharing.default-expiry', 86400), - 'expires_at' => null, - 'password' => null, - 'bundle_id' => $request->bundle_id, - 'owner_token' => $request->owner_token, - 'preview_token' => null, - 'fullsize' => 0, - 'files' => [], - 'title' => null, - 'description' => null, - 'max_downloads' => 0, - 'downloads' => 0 - ]; + try { + $bundle = new Bundle([ + 'user_username' => $user->username ?? null, + 'created_at' => time(), + 'completed' => false, + 'expiry' => config('sharing.default-expiry', 86400), + 'expires_at' => null, + 'password' => null, + 'slug' => substr(sha1(uniqid('slug_', true)), 0, rand(35, 40)), + 'owner_token' => substr(sha1(uniqid('preview_', true)), 0, 15), + 'preview_token' => substr(sha1(uniqid('preview_', true)), 0, 15), + 'fullsize' => 0, + 'title' => null, + 'description' => null, + 'max_downloads' => 0, + 'downloads' => 0 + ]); + $bundle->save(); - if (Upload::setMetadata($metadata['bundle_id'], $metadata)) { - return response()->json($metadata); + return response()->json([ + 'result' => true, + 'redirect' => route('upload.create.show', ['bundle' => $bundle->slug]), + 'bundle' => new BundleResource($bundle) + ]); } - else { - abort(500); + catch (Exception $e) { + return response()->json([ + 'result' => false, + 'message' => $e->getMessage() + ], 500); } } } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index cf3dcd2..305137c 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -21,6 +21,7 @@ class Kernel extends HttpKernel \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, \App\Http\Middleware\TrimStrings::class, \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, + \App\Http\Middleware\Localisation::class ]; /** diff --git a/app/Http/Middleware/GuestAccess.php b/app/Http/Middleware/GuestAccess.php index df4fdd9..557f0aa 100644 --- a/app/Http/Middleware/GuestAccess.php +++ b/app/Http/Middleware/GuestAccess.php @@ -6,6 +6,8 @@ use Closure; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response; use App\Helpers\Upload; +use App\Models\Bundle; +use Carbon\Carbon; class GuestAccess { @@ -17,27 +19,23 @@ class GuestAccess public function handle(Request $request, Closure $next): Response { // Aborting if Bundle ID is not present - abort_if(empty($request->route()->parameter('bundle')), 403); + abort_if(empty($request->route()->parameter('bundle')), 404); + $bundle = $request->route()->parameters()['bundle']; + abort_if(! is_a($bundle, Bundle::class), 404); + // Aborting if Auth token is not provided abort_if(empty($request->auth), 403); - // Getting metadata - $metadata = Upload::getMetadata($request->route()->parameter('bundle')); - - // Aborting if metadata are empty - abort_if(empty($metadata), 404); - // Aborting if auth_token is different from URL param - abort_if($metadata['preview_token'] !== $request->auth, 403); + abort_if($bundle->preview_token !== $request->auth, 403); - // Checking bundle expiration - abort_if($metadata['expires_at'] < time(), 404); + // Aborting if bundle expired + abort_if($bundle->expires_at->isBefore(Carbon::now()), 404); - // If there is no file into the bundle (should never happen but ...) - abort_if(count($metadata['files']) == 0, 404); - - abort_if(($metadata['max_downloads'] ?? 0) > 0 && $metadata['downloads'] >= $metadata['max_downloads'], 404); + // Aborting if max download is reached + abort_if( ($bundle->max_downloads ?? 0) > 0 && $bundle->downloads >= $bundle->max_downloads, 404); + // Else resuming return $next($request); } } diff --git a/app/Http/Middleware/Localisation.php b/app/Http/Middleware/Localisation.php new file mode 100644 index 0000000..6ac639e --- /dev/null +++ b/app/Http/Middleware/Localisation.php @@ -0,0 +1,34 @@ +header('accept-language'); + if (! empty($locales)) { + if (preg_match_all('~([a-z]{2})[-|_]?~', $locales, $matches) && ! empty($matches[1])) { + $locales = array_unique($matches[1]); + + foreach ($locales as $l) { + if (in_array($l, config('app.supported_locales'))) { + App::setLocale($l); + break; + } + } + } + } + return $next($request); + } +} diff --git a/app/Http/Middleware/OwnerAccess.php b/app/Http/Middleware/OwnerAccess.php index 20b6ef1..d2e37bf 100644 --- a/app/Http/Middleware/OwnerAccess.php +++ b/app/Http/Middleware/OwnerAccess.php @@ -6,6 +6,7 @@ use Closure; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response; use App\Helpers\Upload; +use App\Models\Bundle; class OwnerAccess { @@ -21,6 +22,8 @@ class OwnerAccess // Aborting if Bundle ID is not present abort_if(empty($request->route()->parameter('bundle')), 403); + $bundle = $request->route()->parameters()['bundle']; + abort_if(! is_a($bundle, Bundle::class), 404); // Aborting if auth is not present $auth = null; @@ -30,16 +33,11 @@ class OwnerAccess else if (! empty($request->auth)) { $auth = $request->auth; } + // Aborting if no auth token provided abort_if(empty($auth), 403); - // Getting metadata - $metadata = Upload::getMetadata($request->route()->parameter('bundle')); - - // Aborting if metadata are empty - abort_if(empty($metadata), 404); - - // Aborting if auth_token is different from URL param - abort_if($metadata['owner_token'] !== $auth, 403); + // Aborting if owner token is wrong + abort_if($bundle->owner_token !== $auth, 403); return $next($request); } diff --git a/app/Http/Middleware/UploadAccess.php b/app/Http/Middleware/UploadAccess.php index 4eddef8..0cee04e 100644 --- a/app/Http/Middleware/UploadAccess.php +++ b/app/Http/Middleware/UploadAccess.php @@ -6,7 +6,7 @@ use Closure; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response; use App\Helpers\Upload; -use App\Helpers\User; +use App\Helpers\Auth; use Illuminate\Support\Facades\Storage; class UploadAccess @@ -26,7 +26,7 @@ class UploadAccess } // Checking credentials auth - if (User::isLogged()) { + if (Auth::isLogged()) { return $next($request); } diff --git a/app/Http/Resources/BundleResource.php b/app/Http/Resources/BundleResource.php new file mode 100644 index 0000000..24b8356 --- /dev/null +++ b/app/Http/Resources/BundleResource.php @@ -0,0 +1,55 @@ + + */ + public function toArray(Request $request): array + { + /** + Do not return private data on the preview page + */ + $full = false; + $middleware = Route::current()->gatherMiddleware('access.guest'); + foreach ($middleware as $m) { + if ($m === 'access.owner') { + $full = true; + } + } + + $response = [ + 'created_at' => $this->created_at, + 'completed' => (bool)$this->completed, + 'expiry' => (int)$this->expiry, + 'expires_at' => $this->expires_at, + 'slug' => $this->slug, + 'fullsize' => (int)$this->fullsize, + 'title' => $this->title, + 'description' => $this->description, + 'max_downloads' => (int)$this->max_downloads, + 'downloads' => (int)$this->downloads, + 'files' => FileResource::collection($this->files), + 'preview_link' => $this->preview_link, + 'download_link' => $this->download_link, + 'password' => $this->when($full === true, $this->password), + 'owner_token' => $this->when($full === true, $this->owner_token), + 'preview_token' => $this->when($full === true, $this->preview_token), + 'deletion_link' => $this->when($full === true, $this->deletion_link), + 'user' => $this->when($full === true, new UserResource($this->user)) + ]; + + return $response; + } +} diff --git a/app/Http/Resources/FileResource.php b/app/Http/Resources/FileResource.php new file mode 100644 index 0000000..9401b29 --- /dev/null +++ b/app/Http/Resources/FileResource.php @@ -0,0 +1,29 @@ + + */ + public function toArray(Request $request): array + { + return [ + 'uuid' => $this->uuid, + 'bundle_slug' => $this->bundle_slug, + 'original' => $this->original, + 'filesize' => (int)$this->filesize, + 'fullpath' => $this->fullpath, + 'filename' => $this->filename, + 'created_at' => $this->created_at, + 'status' => $this->status, + 'hash' => $this->hash + ]; + } +} diff --git a/app/Http/Resources/UserResource.php b/app/Http/Resources/UserResource.php new file mode 100644 index 0000000..6c1bf97 --- /dev/null +++ b/app/Http/Resources/UserResource.php @@ -0,0 +1,21 @@ + + */ + public function toArray(Request $request): array + { + return [ + 'username' => $this->username + ]; + } +} diff --git a/app/Models/Bundle.php b/app/Models/Bundle.php new file mode 100644 index 0000000..6d92d28 --- /dev/null +++ b/app/Models/Bundle.php @@ -0,0 +1,73 @@ + 'datetime:Y-m-d', + ]; + + public function getKeyName() { + return 'slug'; + } + + public function getIncrementing() { + return false; + } + + public static function schema(Blueprint $table) { + $table->string('slug'); + $table->string('title')->nullable(); + $table->longText('description')->nullable(); + $table->string('password')->nullable(); + $table->string('owner_token'); + $table->string('preview_token'); + $table->integer('fullsize')->default(0); + $table->integer('max_downloads')->nullable(); + $table->integer('downloads')->default(0); + $table->boolean('completed')->default(false); + $table->integer('expiry')->default(0); + $table->timestamp('expires_at')->nullable(); + $table->string('preview_link')->nullable(); + $table->string('download_link')->nullable(); + $table->string('deletion_link')->nullable(); + $table->string('user_username')->nullable(); + } + + public function files() { + return $this->hasMany(File::class); + } + + public function user() { + return $this->belongsTo(User::class); + } +} diff --git a/app/Models/File.php b/app/Models/File.php new file mode 100644 index 0000000..26e815f --- /dev/null +++ b/app/Models/File.php @@ -0,0 +1,53 @@ +string('uuid'); + $table->string('original')->nullable(); + $table->string('filename')->nullable(); + $table->string('status')->nullable(); + $table->string('hash')->nullable(); + $table->longText('fullpath')->nullable(); + $table->boolean('filesize')->nullable(); + $table->string('bundle_slug'); + } + + public function bundle() { + return $this->belongsTo(Bundle::class); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 23b4063..4f0325f 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -2,15 +2,16 @@ namespace App\Models; -// use Illuminate\Contracts\Auth\MustVerifyEmail; -use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; -use Illuminate\Notifications\Notifiable; -use Laravel\Sanctum\HasApiTokens; +use \Orbit\Concerns\Orbital; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Hash; +use Illuminate\Database\Eloquent\Casts\Attribute; +use Illuminate\Database\Eloquent\Model; class User extends Authenticatable { - use HasApiTokens, HasFactory, Notifiable; + use Orbital; /** * The attributes that are mass assignable. @@ -18,8 +19,7 @@ class User extends Authenticatable * @var array */ protected $fillable = [ - 'name', - 'email', + 'username', 'password', ]; @@ -30,15 +30,30 @@ class User extends Authenticatable */ protected $hidden = [ 'password', - 'remember_token', ]; - /** - * The attributes that should be cast. - * - * @var array - */ - protected $casts = [ - 'email_verified_at' => 'datetime', - ]; + + public $incrementing = false; + + + public function getKeyName() + { + return 'username'; + } + + public function getIncrementing() + { + return false; + } + + public static function schema(Blueprint $table) + { + $table->string('username'); + $table->string('password'); + } + + public function bundles() { + return $this->hasMany(Bundle::class); + } + } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 452e6b6..016ca6c 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -19,6 +19,6 @@ class AppServiceProvider extends ServiceProvider */ public function boot(): void { - // + } } diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 1cf5f15..132daff 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -24,6 +24,7 @@ class RouteServiceProvider extends ServiceProvider */ public function boot(): void { + RateLimiter::for('api', function (Request $request) { return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip()); }); diff --git a/config/app.php b/config/app.php index d398db1..dbb4b31 100644 --- a/config/app.php +++ b/config/app.php @@ -98,6 +98,18 @@ return [ 'fallback_locale' => 'en', + /* + |-------------------------------------------------------------------------- + | Application supported locales + |-------------------------------------------------------------------------- + | + | List of all the supported locales of this application + | + */ + 'supported_locales' => [ + 'en', 'fr' + ], + /* |-------------------------------------------------------------------------- | Faker Locale diff --git a/config/orbit.php b/config/orbit.php new file mode 100644 index 0000000..492fa5f --- /dev/null +++ b/config/orbit.php @@ -0,0 +1,18 @@ + env('ORBIT_DEFAULT_DRIVER', 'json'), + + 'drivers' => [ + 'md' => \Orbit\Drivers\Markdown::class, + 'json' => \Orbit\Drivers\Json::class, + 'yaml' => \Orbit\Drivers\Yaml::class, + ], + + 'paths' => [ + 'content' => base_path('content'), + 'cache' => storage_path('framework/cache/orbit'), + ], + +]; diff --git a/lang/en/app.php b/lang/en/app.php index b0f7cdf..b7cdbec 100644 --- a/lang/en/app.php +++ b/lang/en/app.php @@ -61,6 +61,7 @@ return [ 'created-at' => 'Created', 'fullsize' => 'Total', 'max-downloads' => 'Max downloads', + 'current-downloads' => 'Downloads', 'create-new-upload' => 'Create a new upload bundle', 'page-not-found' => 'Page not found', 'permission-denied' => 'Permission denied', @@ -81,5 +82,8 @@ return [ 'password' => 'Password', 'do-login' => 'Login now', 'pending' => 'Drafts', - 'duplicate-file' => 'This file already exists in the bundle' + 'duplicate-file' => 'This file already exists in the bundle', + 'unexpected-error' => 'An unexpected error has occurred', + 'login-to-get-bundles' => 'to get your bundles', + 'you-are-logged-in' => 'You are logged in as ":username"' ]; diff --git a/lang/fr/app.php b/lang/fr/app.php index 374b7de..95e2465 100644 --- a/lang/fr/app.php +++ b/lang/fr/app.php @@ -61,6 +61,7 @@ return [ 'created-at' => 'Créé', 'fullsize' => 'Total', 'max-downloads' => 'Téléchargements maximum', + 'current-downloads' => 'Téléchargements', 'create-new-upload' => 'Créer une nouvelle archive', 'page-not-found' => 'Page non trouvée', 'permission-denied' => 'Permission refusée', @@ -79,7 +80,10 @@ return [ 'authentication' => 'Authentification', 'login' => 'Identifiant', 'password' => 'Mot de passe', - 'do-login' => 'S\'authentifier', + 'do-login' => 'Authentifiez-vous', 'pending' => 'Brouillons', - 'duplicate-file' => 'Ce fichier existe déjà dans l\'archive' + 'duplicate-file' => 'Ce fichier existe déjà dans l\'archive', + 'unexpected-error' => 'Une erreur inattendue est survenue', + 'to-get-bundles' => 'pour accéder à vos archives', + 'you-are-logged-in' => 'Vous êtes connecté(e) en tant que ":username"' ]; diff --git a/resources/js/app.js b/resources/js/app.js index 8a4ffc4..4a7a2a0 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -6,6 +6,8 @@ import axios from 'axios'; window.axios = axios; import moment from 'moment'; +import 'moment/locale/fr'; +moment.locale('fr'); window.moment = moment; moment().format(); diff --git a/resources/views/download.blade.php b/resources/views/download.blade.php index 535e0d7..4e09ada 100644 --- a/resources/views/download.blade.php +++ b/resources/views/download.blade.php @@ -4,14 +4,13 @@ @push('scripts')