mirror of
https://github.com/axeloz/filesharing.git
synced 2025-05-03 17:03:54 +02:00
Rewrite with Eloquent ORM JSON
This commit is contained in:
parent
52059e110d
commit
51a8831b98
35 changed files with 744 additions and 460 deletions
|
@ -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
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,16 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
use App\Helpers\Upload;
|
||||
use App\Helpers\User;
|
||||
use App\Helpers\Auth;
|
||||
use Exception;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Resources\BundleResource;
|
||||
use App\Models\Bundle;
|
||||
|
||||
class WebController extends Controller
|
||||
{
|
||||
public function homepage()
|
||||
{
|
||||
return view('homepage');
|
||||
// Getting user bundles
|
||||
if (Auth::isLogged()) {
|
||||
$bundles = Auth::getLoggedUserDetails()->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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
34
app/Http/Middleware/Localisation.php
Normal file
34
app/Http/Middleware/Localisation.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
class Localisation
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$locales = $request->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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
55
app/Http/Resources/BundleResource.php
Normal file
55
app/Http/Resources/BundleResource.php
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
use App\Http\Resources\UserResource;
|
||||
use App\Http\Resources\FileResource;
|
||||
|
||||
class BundleResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
29
app/Http/Resources/FileResource.php
Normal file
29
app/Http/Resources/FileResource.php
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class FileResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
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
|
||||
];
|
||||
}
|
||||
}
|
21
app/Http/Resources/UserResource.php
Normal file
21
app/Http/Resources/UserResource.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class UserResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'username' => $this->username
|
||||
];
|
||||
}
|
||||
}
|
73
app/Models/Bundle.php
Normal file
73
app/Models/Bundle.php
Normal file
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use \Orbit\Concerns\Orbital;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
|
||||
class Bundle extends Model
|
||||
{
|
||||
use Orbital;
|
||||
|
||||
public $incrementing = false;
|
||||
|
||||
public $fillable = [
|
||||
'user_username',
|
||||
'created_at',
|
||||
'completed',
|
||||
'expiry',
|
||||
'expires_at',
|
||||
'password' ,
|
||||
'slug',
|
||||
'owner_token',
|
||||
'preview_token',
|
||||
'fullsize',
|
||||
'title',
|
||||
'description',
|
||||
'max_downloads',
|
||||
'downloads',
|
||||
'preview_link',
|
||||
'download_link',
|
||||
'deletion_link'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'expires_at' => '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);
|
||||
}
|
||||
}
|
53
app/Models/File.php
Normal file
53
app/Models/File.php
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use \Orbit\Concerns\Orbital;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
|
||||
class File extends Model
|
||||
{
|
||||
use Orbital;
|
||||
|
||||
public $fillable = [
|
||||
'uuid',
|
||||
'bundle_slug',
|
||||
'original',
|
||||
'filesize',
|
||||
'fullpath',
|
||||
'filename',
|
||||
'created_at',
|
||||
'status',
|
||||
'hash'
|
||||
];
|
||||
|
||||
public $incrementing = false;
|
||||
|
||||
|
||||
public function getKeyName()
|
||||
{
|
||||
return 'uuid';
|
||||
}
|
||||
|
||||
public function getIncrementing()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function schema(Blueprint $table)
|
||||
{
|
||||
$table->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);
|
||||
}
|
||||
}
|
|
@ -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<int, string>
|
||||
*/
|
||||
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<string, string>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,6 +19,6 @@ class AppServiceProvider extends ServiceProvider
|
|||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
18
config/orbit.php
Normal file
18
config/orbit.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'default' => 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'),
|
||||
],
|
||||
|
||||
];
|
|
@ -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"'
|
||||
];
|
||||
|
|
|
@ -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"'
|
||||
];
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -4,14 +4,13 @@
|
|||
|
||||
@push('scripts')
|
||||
<script>
|
||||
let auth = @js($auth);
|
||||
let bundleId = @js($bundleId);
|
||||
let bundle = @js($bundle);
|
||||
let bundle_expires = '{{ __('app.warning-bundle-expiration') }}'
|
||||
let bundle_expired = '{{ __('app.warning-bundle-expired') }}'
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('download', () => ({
|
||||
metadata: @js($metadata),
|
||||
metadata: @js($bundle),
|
||||
created_at: null,
|
||||
expires_at: null,
|
||||
expired: null,
|
||||
|
@ -25,13 +24,10 @@
|
|||
},
|
||||
|
||||
updateTimes: function() {
|
||||
this.created_at = moment.unix(this.metadata.created_at).fromNow()
|
||||
this.created_at = moment(this.metadata.created_at).fromNow()
|
||||
|
||||
if (this.isExpired()) {
|
||||
this.expires_at = bundle_expired
|
||||
}
|
||||
else {
|
||||
this.expires_at = bundle_expires+' '+moment.unix(this.metadata.expires_at).fromNow()
|
||||
if (! this.isExpired()) {
|
||||
this.expires_at =moment(this.metadata.expires_at).fromNow()
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -78,26 +74,50 @@
|
|||
@lang('app.preview-bundle')
|
||||
</h2>
|
||||
|
||||
<div class="flex flex-wrap items-center">
|
||||
<p class="w-6/12 px-1">
|
||||
<div class="flex flex-wrap justify-between items-center text-xs">
|
||||
<p class="w-full px-1">
|
||||
<span class="font-title text-xs text-primary uppercase mr-1">
|
||||
@lang('app.upload-title')
|
||||
</span>
|
||||
<span x-text="metadata.title"></span>
|
||||
</p>
|
||||
<p class="w-4/12 px-1">
|
||||
<p class="w-1/2 px-1 mt-1">
|
||||
<span class="font-title text-xs text-primary uppercase mr-1">
|
||||
@lang('app.created-at')
|
||||
</span>
|
||||
<span x-text="created_at"></span>
|
||||
</p>
|
||||
<p class="w-2/12 px-1">
|
||||
<p class="w-1/2 px-1 mt-1">
|
||||
<span class="font-title text-xs text-primary uppercase mr-1">
|
||||
@lang('app.upload-expiry')
|
||||
</span>
|
||||
<span x-text="expires_at"></span>
|
||||
</p>
|
||||
<p class="w-1/2 px-1 mt-1">
|
||||
<span class="font-title text-xs text-primary uppercase mr-1">
|
||||
@lang('app.fullsize')
|
||||
</span>
|
||||
<span x-text="humanSize(metadata.fullsize)"></span>
|
||||
</p>
|
||||
<p class="w-full px-1" x-show="metadata.description">
|
||||
<p class="w-1/2 px-1 mt-1">
|
||||
<span class="font-title text-xs text-primary uppercase mr-1">
|
||||
@lang('app.max-downloads')
|
||||
</span>
|
||||
<span x-text="metadata.max_downloads > 0 ? metadata.max_download : '∞'"></span>
|
||||
</p>
|
||||
<p class="w-1/2 px-1 mt-1">
|
||||
<span class="font-title text-xs text-primary uppercase mr-1">
|
||||
@lang('app.current-downloads')
|
||||
</span>
|
||||
<span x-text="metadata.downloads"></span>
|
||||
</p>
|
||||
<p class="w-1/2 px-1 mt-1">
|
||||
<span class="font-title text-xs text-primary uppercase mr-1">
|
||||
@lang('app.password')
|
||||
</span>
|
||||
<span x-text="metadata.password ? 'yes': 'no'"></span>
|
||||
</p>
|
||||
<p class="w-full px-1 mt-1" x-show="metadata.description">
|
||||
<span class="font-title text-xs text-primary uppercase mr-1">
|
||||
@lang('app.upload-description')
|
||||
</span>
|
||||
|
@ -121,12 +141,7 @@
|
|||
|
||||
<div class="grid grid-cols-2 gap-10 mt-10 text-center items-center">
|
||||
<div>
|
||||
<p class="font-xs font-medium">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="inline w-4 h-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z" />
|
||||
</svg>
|
||||
<span x-text="expires_at"></span>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
<div>
|
||||
@include('partials.button', [
|
||||
|
|
|
@ -1,19 +1,10 @@
|
|||
@extends('layout')
|
||||
|
||||
@section('content')
|
||||
<div>
|
||||
<div class="relative bg-white border border-primary rounded-lg overflow-hidden">
|
||||
<div class="bg-gradient-to-r from-primary-light to-primary px-2 py-4 text-center">
|
||||
<h1 class="relative font-title font-medium font-body text-4xl text-center text-white uppercase flex items-center">
|
||||
|
||||
<div class="grow text-center">{{ config('app.name') }}</div>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="my-10 text-center text-base font-title uppercase text-primary">
|
||||
<h1 class="text-7xl mb-0 font-black">403</h1>
|
||||
@lang('app.permission-denied')
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-10 text-center text-base font-title uppercase text-primary">
|
||||
<h1 class="text-7xl mb-0 font-black">403</h1>
|
||||
@lang('app.permission-denied')
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
|
|
|
@ -1,19 +1,10 @@
|
|||
@extends('layout')
|
||||
|
||||
@section('content')
|
||||
<div>
|
||||
<div class="relative bg-white border border-primary rounded-lg overflow-hidden">
|
||||
<div class="bg-gradient-to-r from-primary-light to-primary px-2 py-4 text-center">
|
||||
<h1 class="relative font-title font-medium font-body text-4xl text-center text-white uppercase flex items-center">
|
||||
|
||||
<div class="grow text-center">{{ config('app.name') }}</div>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="my-10 text-center text-base font-title uppercase text-primary">
|
||||
<h1 class="text-7xl mb-0 font-black">404</h1>
|
||||
@lang('app.page-not-found')
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-10 text-center text-base font-title uppercase text-primary">
|
||||
<h1 class="text-7xl mb-0 font-black">404</h1>
|
||||
@lang('app.page-not-found')
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
|
|
10
resources/views/errors/500.blade.php
Normal file
10
resources/views/errors/500.blade.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
@extends('layout')
|
||||
|
||||
@section('content')
|
||||
|
||||
<div class="my-10 text-center text-base font-title uppercase text-primary">
|
||||
<h1 class="text-7xl mb-0 font-black">500</h1>
|
||||
@lang('app.unexpected-error')
|
||||
</div>
|
||||
|
||||
@endsection
|
|
@ -1,4 +1,11 @@
|
|||
<footer class="relative mt-5 h-6">
|
||||
@if (App\Helpers\Auth::isLogged())
|
||||
<span class="ml-3 text-xs text-slate-600">
|
||||
@lang('app.you-are-logged-in', [
|
||||
'username' => App\Helpers\Auth::getLoggedUserDetails()['username']
|
||||
])
|
||||
</span>
|
||||
@endif
|
||||
|
||||
<div class="absolute right-0 top-0 text-[.6rem] text-slate-100 text-right px-2 py-1 italic bg-primary rounded-tl-lg">
|
||||
Made with
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
<header class="bg-gradient-to-r from-primary-light to-primary px-2 py-4 text-center">
|
||||
<a href="/">
|
||||
<h1 class="relative font-title font-medium font-body text-4xl text-center text-white uppercase flex items-center">
|
||||
<div class="grow text-center">{{ config('app.name') }}</div>
|
||||
</h1>
|
||||
</a>
|
||||
<header class="relative bg-gradient-to-r from-primary-light to-primary px-2 py-4 text-center">
|
||||
<h1 class="relative font-title font-medium font-body text-4xl text-center text-white uppercase">
|
||||
<div class="grow text-center">
|
||||
<a href="/">
|
||||
{{ config('app.name') }}
|
||||
</a>
|
||||
</div>
|
||||
</h1>
|
||||
|
||||
</header>
|
||||
|
|
|
@ -3,22 +3,29 @@
|
|||
|
||||
@push('scripts')
|
||||
<script>
|
||||
let bundles = @js($bundles)
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('bundle', () => ({
|
||||
bundles: null,
|
||||
bundles: [],
|
||||
pending: [],
|
||||
active: [],
|
||||
expired: [],
|
||||
currentBundle: null,
|
||||
ownerToken: null,
|
||||
|
||||
init: function() {
|
||||
// Getting bundles stored locally
|
||||
bundles = localStorage.getItem('bundles');
|
||||
// And JSON decoding it
|
||||
this.bundles = JSON.parse(bundles)
|
||||
// Generating anonymous owner token
|
||||
this.ownerToken = localStorage.getItem('owner_token')
|
||||
if (this.ownerToken === null) {
|
||||
this.ownerToken = this.generateStr(15)
|
||||
localStorage.setItem('owner_token', this.ownerToken)
|
||||
}
|
||||
|
||||
// Loading existing bundles
|
||||
this.bundles = bundles
|
||||
|
||||
if (this.bundles != null && Object.keys(this.bundles).length > 0) {
|
||||
|
||||
this.bundles.forEach( (bundle) => {
|
||||
if (bundle.title == null || bundle.title == '') {
|
||||
bundle.label = 'untitled'
|
||||
|
@ -27,7 +34,7 @@
|
|||
bundle.label = bundle.title
|
||||
}
|
||||
|
||||
if (bundle.expires_at != null && moment.unix(bundle.expires_at).isBefore(moment())) {
|
||||
if (bundle.expires_at != null && moment(bundle.expires_at).isBefore(moment())) {
|
||||
this.expired.push(bundle)
|
||||
}
|
||||
else if (bundle.completed == true) {
|
||||
|
@ -36,38 +43,20 @@
|
|||
else {
|
||||
this.pending.push(bundle)
|
||||
}
|
||||
bundle.label += ' - {{ __('app.created-at') }} '+moment.unix(bundle.created_at).fromNow()
|
||||
bundle.label += ' - {{ __('app.created-at') }} '+moment(bundle.created_at).fromNow()
|
||||
})
|
||||
}
|
||||
|
||||
// If bundle is empty, initializing it
|
||||
if (this.bundles == null || this.bundles == '') {
|
||||
this.bundles = []
|
||||
}
|
||||
},
|
||||
|
||||
newBundle: function() {
|
||||
// Generating a new bundle key pair
|
||||
const pair = {
|
||||
bundle_id: this.generateStr(30),
|
||||
owner_token: this.generateStr(15),
|
||||
created_at: moment().unix()
|
||||
}
|
||||
this.bundles.unshift(pair)
|
||||
|
||||
// Storing them locally
|
||||
localStorage.setItem('bundles', JSON.stringify(this.bundles))
|
||||
|
||||
axios({
|
||||
url: '/new',
|
||||
method: 'POST',
|
||||
data: {
|
||||
bundle_id: pair.bundle_id,
|
||||
owner_token: pair.owner_token
|
||||
}
|
||||
method: 'POST'
|
||||
})
|
||||
.then( (response) => {
|
||||
window.location.href = '/upload/'+response.data.bundle_id
|
||||
if (response.data.result === true) {
|
||||
window.location.href = response.data.redirect
|
||||
}
|
||||
})
|
||||
.catch( (error) => {
|
||||
//TODO: do something here
|
||||
|
@ -107,46 +96,59 @@
|
|||
@section('content')
|
||||
<div x-data="bundle">
|
||||
<div class="p-5">
|
||||
<h2 class="font-title text-2xl mb-5 text-primary font-medium uppercase flex items-center">
|
||||
<p>@lang('app.existing-bundles')</p>
|
||||
<p class="text-sm bg-primary rounded-full ml-2 text-white px-3" x-text="Object.keys(bundles).length"></p>
|
||||
</h2>
|
||||
|
||||
<span x-show="bundles == null || Object.keys(bundles).length == 0">@lang('app.no-existing-bundle')</span>
|
||||
<select
|
||||
class="w-full py-4 text-slate-700 bg-transparent h-8 p-0 py-1 border-b border-primary-superlight focus:ring-0 invalid:border-b-red-500 invalid:bg-red-50"
|
||||
name="expiry"
|
||||
id="upload-expiry"
|
||||
x-model="currentBundle"
|
||||
x-on:change="redirectToBundle()"
|
||||
x-show="bundles != null && Object.keys(bundles).length > 0"
|
||||
>
|
||||
<option>-</option>
|
||||
<div>
|
||||
<h2 class="font-title text-2xl mb-5 text-primary font-medium uppercase flex items-center">
|
||||
<p>@lang('app.existing-bundles')</p>
|
||||
<p class="text-sm bg-primary rounded-full ml-2 text-white px-3" x-text="Object.keys(bundles).length"></p>
|
||||
</h2>
|
||||
|
||||
<template x-if="Object.keys(pending).length > 0">
|
||||
<optgroup label="{{ __('app.pending') }}">
|
||||
<template x-for="bundle in pending">
|
||||
<option :value="bundle.bundle_id" x-text="bundle.label"></option>
|
||||
@if (App\Helpers\Auth::isLogged())
|
||||
<p class="text-center">
|
||||
<span x-show="bundles == null || Object.keys(bundles).length == 0">@lang('app.no-existing-bundle')</span>
|
||||
</p>
|
||||
|
||||
<select
|
||||
class="w-full py-4 text-slate-700 bg-transparent h-8 p-0 py-1 border-b border-primary-superlight focus:ring-0 invalid:border-b-red-500 invalid:bg-red-50"
|
||||
name="expiry"
|
||||
id="upload-expiry"
|
||||
x-model="currentBundle"
|
||||
x-on:change="redirectToBundle()"
|
||||
x-show="bundles != null && Object.keys(bundles).length > 0"
|
||||
>
|
||||
<option>-</option>
|
||||
|
||||
<template x-if="Object.keys(pending).length > 0">
|
||||
<optgroup label="{{ __('app.pending') }}">
|
||||
<template x-for="bundle in pending">
|
||||
<option :value="bundle.slug" x-text="bundle.label"></option>
|
||||
</template>
|
||||
</optgroup>
|
||||
</template>
|
||||
</optgroup>
|
||||
</template>
|
||||
|
||||
<template x-if="Object.keys(active).length > 0">
|
||||
<optgroup label="{{ __('app.active') }}">
|
||||
<template x-for="bundle in active">
|
||||
<option :value="bundle.bundle_id" x-text="bundle.label"></option>
|
||||
<template x-if="Object.keys(active).length > 0">
|
||||
<optgroup label="{{ __('app.active') }}">
|
||||
<template x-for="bundle in active">
|
||||
<option :value="bundle.slug" x-text="bundle.label"></option>
|
||||
</template>
|
||||
</optgroup>
|
||||
</template>
|
||||
</optgroup>
|
||||
</template>
|
||||
|
||||
<template x-if="Object.keys(expired).length > 0">
|
||||
<optgroup label="{{ __('app.expired') }}">
|
||||
<template x-for="bundle in expired">
|
||||
<option :value="bundle.bundle_id" x-text="bundle.label"></option>
|
||||
<template x-if="Object.keys(expired).length > 0">
|
||||
<optgroup label="{{ __('app.expired') }}">
|
||||
<template x-for="bundle in expired">
|
||||
<option :value="bundle.slug" x-text="bundle.label"></option>
|
||||
</template>
|
||||
</optgroup>
|
||||
</template>
|
||||
</optgroup>
|
||||
</template>
|
||||
</select>
|
||||
</select>
|
||||
@else
|
||||
<p class="text-center">
|
||||
<a href="/login" class="text-primary font-bold hover:underline">@lang('app.do-login')</a>
|
||||
@lang('app.to-get-bundles')
|
||||
</p>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<h2 class="mt-10 font-title text-2xl mb-5 text-primary font-medium uppercase">@lang('app.or-create')</h2>
|
||||
|
||||
|
|
|
@ -43,9 +43,9 @@
|
|||
if (response.data.result == true) {
|
||||
window.location.href = '/'
|
||||
}
|
||||
else {
|
||||
this.error = response.data.error
|
||||
}
|
||||
})
|
||||
.catch( (error) => {
|
||||
this.error = error.response.data.message
|
||||
})
|
||||
}
|
||||
}))
|
||||
|
|
|
@ -3,20 +3,18 @@
|
|||
@section('page_title', __('app.upload-files-title'))
|
||||
|
||||
@push('scripts')
|
||||
|
||||
<script>
|
||||
let baseUrl = @js($baseUrl);
|
||||
let metadata = @js($metadata ?? []);
|
||||
let bundle = @js($bundle);
|
||||
let maxFiles = @js(config('sharing.max_files'));
|
||||
let maxFileSize = @js(Upload::fileMaxSize());
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('upload', () => ({
|
||||
bundle: null,
|
||||
bundleIndex: null,
|
||||
bundles: null,
|
||||
dropzone: null,
|
||||
uploadedFiles: [],
|
||||
metadata: [],
|
||||
completed: false,
|
||||
step: 0,
|
||||
maxFiles: maxFiles,
|
||||
|
@ -43,14 +41,14 @@
|
|||
],
|
||||
|
||||
init: function() {
|
||||
this.metadata = metadata
|
||||
this.bundle = bundle
|
||||
|
||||
if (this.getBundle()) {
|
||||
// Steps router
|
||||
if (this.metadata.completed == true) {
|
||||
if (this.bundle.completed == true) {
|
||||
this.step = 3
|
||||
}
|
||||
else if (this.metadata.title) {
|
||||
else if (this.bundle.title) {
|
||||
this.step = 2
|
||||
this.startDropzone()
|
||||
}
|
||||
|
@ -61,39 +59,6 @@
|
|||
},
|
||||
|
||||
getBundle: function() {
|
||||
// Getting all bundles store in local storage
|
||||
bundles = localStorage.getItem('bundles')
|
||||
|
||||
// If no bundle found, back to homepage
|
||||
if (bundles == null || bundles == '') {
|
||||
window.location.href = '/'
|
||||
return false
|
||||
}
|
||||
|
||||
this.bundles = JSON.parse(bundles)
|
||||
|
||||
// Looking for the current bundle
|
||||
if (this.bundles != null && Object.keys(this.bundles).length > 0) {
|
||||
this.bundles.forEach( (element, index) => {
|
||||
if (element.bundle_id == this.metadata.bundle_id) {
|
||||
//this.bundle = Object.assign(element)
|
||||
//this.bundleIndex = index
|
||||
this.bundle = index
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// If current bundle not found, aborting
|
||||
if (this.bundle == null) {
|
||||
window.location.href = '/'
|
||||
return false
|
||||
}
|
||||
|
||||
if (this.bundles[this.bundle].owner_token != this.metadata.owner_token) {
|
||||
window.location.href = '/'
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
|
||||
|
@ -105,17 +70,17 @@
|
|||
document.getElementById('upload-password').setCustomValidity('')
|
||||
document.getElementById('upload-max-downloads').setCustomValidity('')
|
||||
|
||||
if (this.metadata.title == null || this.metadata.title == '') {
|
||||
if (this.bundle.title == null || this.bundle.title == '') {
|
||||
document.getElementById('upload-title').setCustomValidity('Field is required')
|
||||
errors = true
|
||||
}
|
||||
|
||||
if (this.metadata.expiry == null || this.metadata.expiry == '') {
|
||||
if (this.bundle.expiry == null || this.bundle.expiry == '') {
|
||||
document.getElementById('upload-expiry').setCustomValidity('Field is required')
|
||||
errors = true
|
||||
}
|
||||
|
||||
if (this.metadata.max_downloads < 0 || this.metadata.max_downloads > 999) {
|
||||
if (this.bundle.max_downloads < 0 || this.bundle.max_downloads > 999) {
|
||||
document.getElementById('upload-max-downloads').setCustomValidity('Invalid number of max downloads')
|
||||
errors = true
|
||||
}
|
||||
|
@ -125,20 +90,20 @@
|
|||
}
|
||||
|
||||
axios({
|
||||
url: '/upload/'+this.metadata.bundle_id,
|
||||
url: '/upload/'+this.bundle.slug,
|
||||
method: 'POST',
|
||||
data: {
|
||||
expiry: this.metadata.expiry,
|
||||
title: this.metadata.title,
|
||||
description: this.metadata.description,
|
||||
max_downloads: this.metadata.max_downloads,
|
||||
password: this.metadata.password,
|
||||
auth: this.bundles[this.bundle].owner_token
|
||||
expiry: this.bundle.expiry,
|
||||
title: this.bundle.title,
|
||||
description: this.bundle.description,
|
||||
max_downloads: this.bundle.max_downloads,
|
||||
password: this.bundle.password,
|
||||
auth: this.bundle.owner_token
|
||||
}
|
||||
})
|
||||
.then( (response) => {
|
||||
this.syncData(response.data)
|
||||
window.history.pushState(null, null, baseUrl+'/upload/'+this.metadata.bundle_id);
|
||||
window.history.pushState(null, null, baseUrl+'/upload/'+this.bundle.slug);
|
||||
this.step = 2
|
||||
|
||||
this.startDropzone()
|
||||
|
@ -149,16 +114,16 @@
|
|||
},
|
||||
|
||||
completeStep: function() {
|
||||
if (Object.keys(this.metadata.files).length == 0) {
|
||||
if (Object.keys(this.bundle.files).length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.showModal('{{ __('app.confirm-complete') }}', () => {
|
||||
axios({
|
||||
url: '/upload/'+this.metadata.bundle_id+'/complete',
|
||||
url: '/upload/'+this.bundle.slug+'/complete',
|
||||
method: 'POST',
|
||||
data: {
|
||||
auth: this.bundles[this.bundle].owner_token
|
||||
auth: this.bundle.owner_token
|
||||
}
|
||||
|
||||
})
|
||||
|
@ -183,10 +148,10 @@
|
|||
this.maxFiles = this.maxFiles - this.countFilesOnServer() >= 0 ? this.maxFiles - this.countFilesOnServer() : 0
|
||||
|
||||
this.dropzone = new Dropzone('#upload-frm', {
|
||||
url: '/upload/'+this.metadata.bundle_id+'/file',
|
||||
url: '/upload/'+this.bundle.slug+'/file',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-Upload-Auth': this.bundles[this.bundle].owner_token
|
||||
'X-Upload-Auth': this.bundle.owner_token
|
||||
},
|
||||
createImageThumbnails: false,
|
||||
disablePreviews: true,
|
||||
|
@ -204,7 +169,7 @@
|
|||
this.dropzone.on('addedfile', (file) => {
|
||||
file.uuid = this.uuid()
|
||||
|
||||
this.metadata.files.unshift({
|
||||
this.bundle.files.unshift({
|
||||
uuid: file.uuid,
|
||||
original: file.name,
|
||||
filesize: file.size,
|
||||
|
@ -224,29 +189,29 @@
|
|||
let fileIndex = null
|
||||
|
||||
if (fileIndex = this.findFileIndex(file.uuid)) {
|
||||
this.metadata.files[fileIndex].progress = Math.round(progress)
|
||||
this.bundle.files[fileIndex].progress = Math.round(progress)
|
||||
}
|
||||
})
|
||||
|
||||
this.dropzone.on('error', (file, message) => {
|
||||
let fileIndex = this.findFileIndex(file.uuid)
|
||||
this.metadata.files[fileIndex].status = false
|
||||
this.bundle.files[fileIndex].status = false
|
||||
|
||||
if (message.hasOwnProperty('error')) {
|
||||
this.metadata.files[fileIndex].message = message.error
|
||||
if (message.hasOwnProperty('message')) {
|
||||
this.bundle.files[fileIndex].message = message.message
|
||||
}
|
||||
else {
|
||||
this.metadata.files[fileIndex].message = message
|
||||
this.bundle.files[fileIndex].message = message
|
||||
}
|
||||
})
|
||||
|
||||
this.dropzone.on('complete', (file) => {
|
||||
let fileIndex = this.findFileIndex(file.uuid)
|
||||
this.metadata.files[fileIndex].progress = 0
|
||||
this.bundle.files[fileIndex].progress = 0
|
||||
|
||||
if (file.status == 'success') {
|
||||
this.maxFiles--
|
||||
this.metadata.files[fileIndex].status = true
|
||||
this.bundle.files[fileIndex].status = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -259,11 +224,11 @@
|
|||
let lfile = file
|
||||
|
||||
axios({
|
||||
url: '/upload/'+this.metadata.bundle_id+'/file',
|
||||
url: '/upload/'+this.bundle.slug+'/file',
|
||||
method: 'DELETE',
|
||||
data: {
|
||||
uuid: lfile.uuid,
|
||||
auth: this.bundles[this.bundle].owner_token
|
||||
auth: this.bundle.owner_token
|
||||
}
|
||||
})
|
||||
.then( (response) => {
|
||||
|
@ -277,7 +242,7 @@
|
|||
// File not valid, no need to remove it from server, just locally
|
||||
else if (file.status == false) {
|
||||
let fileIndex = this.findFileIndex(file.uuid)
|
||||
this.metadata.files.splice(fileIndex, 1)
|
||||
this.bundle.files.splice(fileIndex, 1)
|
||||
}
|
||||
// File has not being uploaded, cannot delete file yet
|
||||
else {
|
||||
|
@ -288,16 +253,17 @@
|
|||
deleteBundle: function() {
|
||||
this.showModal('{{ __('app.confirm-delete-bundle') }}', () => {
|
||||
axios({
|
||||
url: '/upload/'+this.metadata.bundle_id+'/delete',
|
||||
url: '/upload/'+this.bundle.slug+'/delete',
|
||||
method: 'DELETE',
|
||||
data: {
|
||||
auth: this.bundles[this.bundle].owner_token
|
||||
auth: this.bundle.owner_token
|
||||
}
|
||||
})
|
||||
.then( (response) => {
|
||||
if (! response.data.success) {
|
||||
this.syncData(response.data)
|
||||
}
|
||||
this.syncData(response.data)
|
||||
})
|
||||
.catch( (error) => {
|
||||
|
||||
})
|
||||
})
|
||||
},
|
||||
|
@ -305,25 +271,23 @@
|
|||
findFile: function(uuid) {
|
||||
let index = this.findFileIndex(uuid)
|
||||
if (index != null) {
|
||||
return this.metadata.files[index]
|
||||
return this.bundle.files[index]
|
||||
}
|
||||
return null
|
||||
},
|
||||
|
||||
findFileIndex: function (uuid) {
|
||||
for (i in this.metadata.files) {
|
||||
if (this.metadata.files[i].uuid == uuid) {
|
||||
for (i in this.bundle.files) {
|
||||
if (this.bundle.files[i].uuid == uuid) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return null
|
||||
},
|
||||
|
||||
syncData: function(metadata) {
|
||||
if (Object.keys(metadata).length > 0) {
|
||||
this.metadata = metadata
|
||||
this.bundles[this.bundle] = metadata
|
||||
localStorage.setItem('bundles', JSON.stringify(this.bundles))
|
||||
syncData: function(bundle) {
|
||||
if (Object.keys(bundle).length > 0) {
|
||||
this.bundle = bundle
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -376,9 +340,9 @@
|
|||
countFilesOnServer: function() {
|
||||
count = 0
|
||||
|
||||
if (this.metadata.hasOwnProperty('files') && Object.keys(this.metadata.files).length > 0) {
|
||||
for (i in this.metadata.files) {
|
||||
if (this.metadata.files[i].status == true) {
|
||||
if (this.bundle.hasOwnProperty('files') && Object.keys(this.bundle.files).length > 0) {
|
||||
for (i in this.bundle.files) {
|
||||
if (this.bundle.files[i].status == true) {
|
||||
count ++
|
||||
}
|
||||
}
|
||||
|
@ -387,11 +351,11 @@
|
|||
},
|
||||
|
||||
isBundleExpired: function() {
|
||||
if (this.metadata.expires_at == null || this.metadata.expires_at == '') {
|
||||
if (this.bundle.expires_at == null || this.bundle.expires_at == '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return moment.unix(this.metadata.expires_at).isBefore(moment())
|
||||
return moment.unix(this.bundle.expires_at).isBefore(moment())
|
||||
}
|
||||
}))
|
||||
})
|
||||
|
@ -457,7 +421,7 @@
|
|||
</p>
|
||||
|
||||
<input
|
||||
x-model="metadata.title"
|
||||
x-model="bundle.title"
|
||||
class="w-full p-0 bg-transparent text-slate-700 h-8 py-1 rounded-none border-b border-purple-300 outline-none invalid:border-b-red-500 invalid:bg-red-50"
|
||||
type="text"
|
||||
name="title"
|
||||
|
@ -471,7 +435,7 @@
|
|||
<span class="font-title uppercase">@lang('app.upload-description')</span>
|
||||
|
||||
<textarea
|
||||
x-model="metadata.description"
|
||||
x-model="bundle.description"
|
||||
maxlength="300"
|
||||
class="w-full p-0 bg-transparent text-slate-700 h-18 py-1 rounded-none border-b border-purple-300 outline-none invalid:border-b-red-500 invalid:bg-red-50"
|
||||
type="text"
|
||||
|
@ -489,7 +453,7 @@
|
|||
</p>
|
||||
|
||||
<select
|
||||
x-model="metadata.expiry""
|
||||
x-model="bundle.expiry""
|
||||
class="w-full text-slate-700 bg-transparent h-8 p-0 py-1 border-b border-primary-superlight focus:ring-0 invalid:border-b-red-500 invalid:bg-red-50"
|
||||
name="expiry"
|
||||
id="upload-expiry"
|
||||
|
@ -508,7 +472,7 @@
|
|||
</p>
|
||||
|
||||
<input
|
||||
x-model="metadata.max_downloads"
|
||||
x-model="bundle.max_downloads"
|
||||
class="w-full p-0 bg-transparent text-slate-700 h-8 py-1 rounded-none border-b border-purple-300 outline-none invalid:border-b-red-500 invalid:bg-red-50"
|
||||
type="number"
|
||||
name="max_downloads"
|
||||
|
@ -523,7 +487,7 @@
|
|||
<span class="font-title uppercase">@lang('app.bundle-password')</span>
|
||||
|
||||
<input
|
||||
x-model="metadata.password"
|
||||
x-model="bundle.password"
|
||||
class="w-full bg-transparent text-slate-700 h-8 p-0 py-1 rounded-none border-b border-primary-superlight outline-none invalid:border-b-red-500 invalid:bg-red-50"
|
||||
placeholder="@lang('app.leave-empty')"
|
||||
type="text"
|
||||
|
@ -581,11 +545,11 @@
|
|||
</div>
|
||||
</h3>
|
||||
|
||||
<span class="text-xs text-slate-400" x-show="Object.keys(metadata.files).length == 0">@lang('app.no-file')</span>
|
||||
<span class="text-xs text-slate-400" x-show="Object.keys(bundle.files).length == 0">@lang('app.no-file')</span>
|
||||
|
||||
{{-- Files list --}}
|
||||
<ul id="output" class="text-xs max-h-32 overflow-y-scroll pb-3" x-show="Object.keys(metadata.files).length > 0">
|
||||
<template x-for="(f, k) in metadata.files" :key="k">
|
||||
<ul id="output" class="text-xs max-h-32 overflow-y-scroll pb-3" x-show="Object.keys(bundle.files).length > 0">
|
||||
<template x-for="(f, k) in bundle.files" :key="k">
|
||||
<li
|
||||
title="{{ __('app.click-to-remove') }}"
|
||||
class="relative flex items-center leading-5 list-inside even:bg-gray-50 rounded px-2 cursor-pointer overflow-hidden"
|
||||
|
@ -663,7 +627,7 @@
|
|||
@lang('app.preview-link')
|
||||
</div>
|
||||
<div class="w-2/3 shadow">
|
||||
<input x-model="metadata.preview_link" class="w-full bg-transparent text-slate-700 h-8 px-2 py-1 rounded-none border border-primary-superlight outline-none" type="text" readonly x-on:click="selectCopy($el)" />
|
||||
<input x-model="bundle.preview_link" class="w-full bg-transparent text-slate-700 h-8 px-2 py-1 rounded-none border border-primary-superlight outline-none" type="text" readonly x-on:click="selectCopy($el)" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -673,7 +637,7 @@
|
|||
@lang('app.direct-link')
|
||||
</div>
|
||||
<div class="w-2/3 shadow">
|
||||
<input x-model="metadata.download_link" class="w-full bg-transparent text-slate-700 h-8 px-2 py-1 rounded-none border border-primary-superlight outline-none" type="text" readonly x-on:click="selectCopy($el)" />
|
||||
<input x-model="bundle.download_link" class="w-full bg-transparent text-slate-700 h-8 px-2 py-1 rounded-none border border-primary-superlight outline-none" type="text" readonly x-on:click="selectCopy($el)" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ use App\Http\Controllers\UploadController;
|
|||
use App\Http\Controllers\BundleController;
|
||||
use App\Http\Middleware\UploadAccess;
|
||||
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Web Routes
|
||||
|
@ -18,31 +19,40 @@ use App\Http\Middleware\UploadAccess;
|
|||
|
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
Public route for login
|
||||
*/
|
||||
Route::get('/login', [WebController::class, 'login']);
|
||||
Route::post('/login', [WebController::class, 'doLogin']);
|
||||
|
||||
/**
|
||||
Upload routes
|
||||
*/
|
||||
Route::middleware(['can.upload'])->group(function() {
|
||||
Route::get('/', [WebController::class, 'homepage'])->name('homepage');
|
||||
Route::post('/new', [WebController::class, 'newBundle'])->name('bundle.new');
|
||||
|
||||
Route::get('/new', [WebController::class, 'newBundle'])->name('bundle.new');
|
||||
|
||||
Route::prefix('/upload/{bundle}')->controller(UploadController::class)->name('upload.')->group(function() {
|
||||
Route::get('/', 'createBundle')->name('create.show');
|
||||
Route::get('/', 'createBundle')->name('create.show');
|
||||
|
||||
Route::middleware(['access.owner'])->group(function() {
|
||||
Route::post('/', 'storeBundle')->name('create.store');
|
||||
Route::get('/metadata', 'getMetadata')->name('metadata.get');
|
||||
Route::post('/file', 'uploadFile')->name('file.store');
|
||||
Route::delete('/file', 'deleteFile')->name('file.delete');
|
||||
Route::post('/complete', 'completeBundle')->name('complete');
|
||||
Route::post('/', 'storeBundle')->name('create.store');
|
||||
Route::get('/metadata', 'getMetadata')->name('metadata.get');
|
||||
Route::post('/file', 'uploadFile')->name('file.store');
|
||||
Route::delete('/file', 'deleteFile')->name('file.delete');
|
||||
Route::post('/complete', 'completeBundle')->name('complete');
|
||||
Route::delete('/delete', 'deleteBundle')->name('bundle.delete');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
Download routes
|
||||
*/
|
||||
Route::middleware(['access.guest'])->prefix('/bundle/{bundle}')->controller(BundleController::class)->name('bundle.')->group(function() {
|
||||
Route::get('/preview', 'previewBundle')->name('preview');
|
||||
Route::post('/zip', 'prepareZip')->name('zip.make');
|
||||
Route::get('/download', 'downloadZip')->name('zip.download');
|
||||
Route::get('/preview', 'previewBundle')->name('preview');
|
||||
Route::post('/zip', 'prepareZip')->name('zip.make');
|
||||
Route::get('/download', 'downloadZip')->name('zip.download');
|
||||
});
|
||||
|
|
Loading…
Add table
Reference in a new issue