upload file into request folder and get their thumbnails

This commit is contained in:
Čarodej
2022-02-19 12:32:19 +01:00
parent 5be55b52bd
commit 1107bf66af
25 changed files with 349 additions and 60 deletions

View File

@@ -1,6 +1,6 @@
APP_NAME=Laravel
APP_ENV=local
APP_KEY=base64:MzktLd8L9CrZsaNj5lDDewmUcI9VVQWq5eWMFxU5LvI=
APP_KEY=base64:uM4NZoG/Wgk0ruC0hs4FkYVEChkviOQy8ekOaVpr/pA=
APP_DEBUG=true
APP_URL=http://localhost
APP_DEMO=false

View File

@@ -169,7 +169,7 @@ const actions = {
if (percentCompleted >= 100) commit('PROCESSING_FILE', true)
},
})
.then((response) => {
.then(async (response) => {
resolve(response)
// Proceed if was returned database record
@@ -179,6 +179,11 @@ const actions = {
// Remove first file from file queue
commit('SHIFT_FROM_FILE_QUEUE')
// Refresh request detail to update currentFolder in Vuex
if (router.currentRoute.name === 'RequestUpload' && !getters.currentFolder) {
await dispatch('getUploadRequestDetail')
}
// Check if user is in uploading folder, if yes, than show new file
if (
(!getters.currentFolder && !response.data.data.attributes.parent_id) ||

View File

@@ -8,7 +8,23 @@ const defaultState = {
uploadRequest: undefined,
}
const actions = {}
const actions = {
getUploadRequestDetail: ({ commit, getters }) => {
axios.get(`/api/upload-request/${router.currentRoute.params.token}`)
.then((response) => {
commit('LOADING_STATE', { loading: false, data: [] })
commit('SET_UPLOAD_REQUEST', response.data)
// Set current folder if exist
if (response.data.data.relationships.folder) {
commit('SET_CURRENT_FOLDER', response.data.data.relationships.folder)
}
})
},
}
const mutations = {
SET_UPLOAD_REQUEST(state, payload) {

View File

@@ -189,11 +189,8 @@ export default {
events.$on('context-menu:current-folder', (folder) => (this.item = folder))
events.$on('mobile-context-menu:show', (item) => (this.item = item))
//this.$store.dispatch('getFolder', this.$route.params.id)
this.$store.commit('LOADING_STATE', { loading: false, data: [] })
this.$store.dispatch('getUploadRequestDetail')
axios.get(`/api/upload-request/${this.$router.currentRoute.params.token}`)
.then((response) => this.$store.commit('SET_UPLOAD_REQUEST', response.data))
},
}
</script>

View File

@@ -1,19 +1,24 @@
<?php
// Get avatars and system images
use App\Users\Controllers\GetAvatarController;
use Domain\Settings\Controllers\GetAppImageController;
use Domain\Files\Controllers\FileAccess\GetFileController;
use Domain\Files\Controllers\FileAccess\GetThumbnailController;
use Domain\Files\Controllers\FileAccess\VisitorGetFileController;
use Domain\Files\Controllers\FileAccess\VisitorGetThumbnailController;
use Domain\UploadRequest\Controllers\FileAccess\GetFileFromUploadRequestController;
use Domain\UploadRequest\Controllers\FileAccess\GetThumbnailFromUploadRequestController;
Route::get('/avatars/{avatar}', GetAvatarController::class);
Route::get('/system/{image}', GetAppImageController::class);
// Get Upload request thumbnails and files
Route::get('/thumbnail/{name}/upload-request/{uploadRequest}', GetThumbnailFromUploadRequestController::class);
Route::get('/file/{name}/upload-request/{uploadRequest}', GetFileFromUploadRequestController::class);
// Get public thumbnails and files
Route::get('/thumbnail/{name}/{shared}', VisitorGetThumbnailController::class);
Route::get('/file/{name}/{shared}', VisitorGetFileController::class);
Route::get('/thumbnail/{name}/shared/{shared}', VisitorGetThumbnailController::class);
Route::get('/file/{name}/shared/{shared}', VisitorGetFileController::class);
// User master,editor,visitor access to image thumbnails and file downloads
Route::group(['middleware' => ['auth:sanctum']], function () {

View File

@@ -46,7 +46,7 @@ class VisitorBrowseFolderController
->get();
// Set thumbnail links for public files
$files->map(fn ($file) => $file->setPublicUrl($shared->token));
$files->map(fn ($file) => $file->setSharedPublicUrl($shared->token));
return [
'folders' => new FolderCollection($folders),

View File

@@ -62,7 +62,7 @@ class VisitorSearchFilesAndFoldersController extends Controller
->take(3);
// Map files and set public url for files
$files->map(fn ($file) => $file->setPublicUrl($shared->token));
$files->map(fn ($file) => $file->setSharedPublicUrl($shared->token));
$folders = Folder::search($query)
->constrain($folderConstrain)

View File

@@ -1,8 +1,8 @@
<?php
namespace Domain\Files\Actions;
use App\Users\Models\User;
use Illuminate\Support\Str;
use Domain\Sharing\Models\Share;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
@@ -29,7 +29,7 @@ class UploadFileAction
*/
public function __invoke(
UploadRequest $request,
?Share $shared = null,
?string $userId = null,
) {
$file = $request->file('file');
@@ -62,7 +62,7 @@ class UploadFileAction
$disk_local = Storage::disk('local');
// Get user data
$user = $shared->user ?? Auth::user();
$user = $userId ? User::find($userId) : Auth::user();
// File Info
$fileSize = $disk_local->size("chunks/$chunkName");
@@ -98,7 +98,7 @@ class UploadFileAction
'metadata' => $metadata,
'name' => $request->input('filename'),
'basename' => $fileName,
'author' => $shared ? 'visitor' : 'user',
'author' => $userId ? 'visitor' : 'user',
'filesize' => $fileSize,
'user_id' => $user->id,
]);

View File

@@ -53,7 +53,7 @@ class VisitorGetFileController extends Controller
user_id: $shared->user_id,
);
// Finally download file
// Finally, download file
return ($this->downloadFile)($file, $shared->user_id);
}
}

View File

@@ -22,7 +22,7 @@ class VisitorShowFileController
Gate::authorize('can-view', [$file, $shared]);
// Set access urls
$file->setPublicUrl($shared->token);
$file->setSharedPublicUrl($shared->token);
return response(new FileResource($file), 200);
}

View File

@@ -50,10 +50,10 @@ class VisitorUploadFileController extends Controller
try {
// Return new uploaded file
$file = ($this->uploadFile)($request, $shared);
$file = ($this->uploadFile)($request, $shared->user_id);
// Set public access url
$file->setPublicUrl($shared->token);
$file->setSharedPublicUrl($shared->token);
return response(new FileResource($file), 201);
} catch (InvalidUserActionException $e) {

View File

@@ -43,7 +43,8 @@ class File extends Model
use HasFactory;
use Sortable;
public ?string $public_access = null;
public ?string $sharedAccess = null;
public ?string $uploadRequestAccess = null;
protected $guarded = [
'id',
@@ -78,11 +79,19 @@ class File extends Model
}
/**
* Set routes with public access
* Set shared routes with public access
*/
public function setPublicUrl(string $token)
public function setSharedPublicUrl(string $token)
{
$this->public_access = $token;
$this->sharedAccess = $token;
}
/**
* Set upload request routes with public access
*/
public function setUploadRequestPublicUrl(string $token)
{
$this->uploadRequestAccess = $token;
}
/**
@@ -94,7 +103,7 @@ class File extends Model
$thumbnail_sizes = collect(config('vuefilemanager.image_sizes'))->collapse()->all();
// Generate thumbnail link for external storage service
if ($this->type === 'image' && ! is_storage_driver(['local'])) {
if ($this->type === 'image' && ! is_storage_driver('local')) {
foreach ($thumbnail_sizes as $item) {
$filePath = "files/{$this->user_id}/{$item['name']}-{$this->basename}";
@@ -106,11 +115,19 @@ class File extends Model
// Generate thumbnail link for local storage
if ($this->type === 'image') {
foreach ($thumbnail_sizes as $item) {
$route = route('thumbnail', ['name' => $item['name'] . '-' . $this->basename]);
if ($this->public_access) {
$route .= "/$this->public_access";
// Set shared public url
if ($this->sharedAccess) {
$route .= "/shared/$this->sharedAccess";
}
// Set upload request public url
if ($this->uploadRequestAccess) {
// TODO: review request for s3
$route .= "/upload-request/$this->uploadRequestAccess";
}
$links[$item['name']] = $route;
@@ -147,8 +164,15 @@ class File extends Model
// Get thumbnail from local storage
$route = route('file', ['name' => $this->attributes['basename']]);
if ($this->public_access) {
return "$route/$this->public_access";
// Set shared public url
if ($this->sharedAccess) {
return "$route/$this->sharedAccess";
}
// Set upload request public url
if ($this->uploadRequestAccess) {
// TODO: review request for s3
$route .= "/upload-request/$this->uploadRequestAccess";
}
return $route;

View File

@@ -10,8 +10,6 @@ class FileResource extends JsonResource
/**
* Transform the resource into an array.
*
* TODO: optimize created_at/updated_at conversion because of performance issue
*
* @param \Illuminate\Http\Request $request
* @return array
*/

View File

@@ -60,7 +60,7 @@ class VisitorRenameFileOrFolderController extends Controller
// Set public url
if ($request->input('type') !== 'folder') {
$item->setPublicUrl($shared->token);
$item->setSharedPublicUrl($shared->token);
}
if ($request->input('type') === 'folder') {

View File

@@ -26,7 +26,7 @@ class WebCrawlerOpenGraphController extends Controller
->first();
if ($item->thumbnail) {
$item->setPublicUrl($share->token);
$item->setSharedPublicUrl($share->token);
}
return view('vuefilemanager.crawler.og-view')

View File

@@ -0,0 +1,45 @@
<?php
namespace Domain\UploadRequest\Controllers\FileAccess;
use Domain\Files\Actions\DownloadFileAction;
use Domain\Sharing\Actions\ProtectShareRecordAction;
use Domain\Sharing\Actions\VerifyAccessToItemWithinAction;
use Domain\Traffic\Actions\RecordDownloadAction;
use Domain\UploadRequest\Models\UploadRequest;
use Gate;
use Domain\Files\Models\File;
use Illuminate\Http\Response;
use Domain\Sharing\Models\Share;
use Domain\Files\Resources\FileResource;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
/**
* Get shared file record
*/
class GetFileFromUploadRequestController
{
public function __construct(
private DownloadFileAction $downloadFile,
private RecordDownloadAction $recordDownload,
) {
}
public function __invoke(
string $filename,
UploadRequest $uploadRequest
): BinaryFileResponse {
// Get file
$file = File::where('user_id', $uploadRequest->user_id)
->where('basename', $filename)
->firstOrFail();
// Store user download size
($this->recordDownload)(
file_size: (int) $file->getRawOriginal('filesize'),
user_id: $uploadRequest->user_id,
);
// Finally, download file
return ($this->downloadFile)($file, $uploadRequest->user_id);
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace Domain\UploadRequest\Controllers\FileAccess;
use App\Http\Controllers\Controller;
use Domain\Files\Actions\DownloadThumbnailAction;
use Domain\Traffic\Actions\RecordDownloadAction;
use Domain\UploadRequest\Models\UploadRequest;
use Domain\Files\Models\File;
use Symfony\Component\HttpFoundation\StreamedResponse;
/**
* Get shared file record
*/
class GetThumbnailFromUploadRequestController extends Controller
{
public function __construct(
private DownloadThumbnailAction $downloadThumbnail,
private RecordDownloadAction $recordDownload,
) {
}
public function __invoke(
string $filename,
UploadRequest $uploadRequest
): StreamedResponse {
$originalFileName = substr($filename, 3);
// Get file
$file = File::where('user_id', $uploadRequest->user_id)
->where('basename', $originalFileName)
->firstOrFail();
// Store user download size
($this->recordDownload)(
file_size: (int) $file->getRawOriginal('filesize'),
user_id: $uploadRequest->user_id,
);
// Finally, download thumbnail
return ($this->downloadThumbnail)($filename, $file);
}
}

View File

@@ -2,30 +2,73 @@
namespace Domain\UploadRequest\Controllers;
use DB;
use Domain\Folders\Models\Folder;
use App\Users\Exceptions\InvalidUserActionException;
use Domain\Files\Resources\FileResource;
use Domain\UploadRequest\Models\UploadRequest;
use Illuminate\Http\Request;
use Domain\Files\Actions\UploadFileAction;
use Domain\Folders\Models\Folder;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
use DB;
class UploadFilesForUploadRequestController
{
public function __invoke(Request $request, UploadRequest $uploadRequest)
public function __construct(
private UploadFileAction $uploadFile,
) {}
/**
* @throws FileNotFoundException
*/
public function __invoke(\Domain\Files\Requests\UploadRequest $request, UploadRequest $uploadRequest)
{
// Check if upload request is active
if ($uploadRequest->status !== 'active') {
return response('Gone', 410);
}
// Get upload request root folder query
$folder = Folder::where('id', $uploadRequest->id);
// Create folder if not exist
if ($folder->doesntExist()) {
$timestampName = format_date($uploadRequest->created_at, 'd. M. Y');
DB::table('folders')->insert([
'id' => $uploadRequest->id,
'parent_id' => $uploadRequest->folder_id,
'user_id' => $uploadRequest->user_id,
'name' => "Upload Request from $timestampName",
]);
$this->createFolder($uploadRequest);
}
return response('Done!', 201);
try {
// Set default parent_id for uploaded file
if (is_null($request->input('parent_id'))) {
$request->merge(['parent_id' => $uploadRequest->id]);
}
// Upload file
$file = ($this->uploadFile)($request, $uploadRequest->user_id);
// Set public access url
$file->setUploadRequestPublicUrl($uploadRequest->id);
// Return new uploaded file
return response(new FileResource($file), 201);
} catch (InvalidUserActionException $e) {
return response([
'type' => 'error',
'message' => $e->getMessage(),
], 401);
}
}
/**
* Create root Upload Request folder
*/
private function createFolder(UploadRequest $uploadRequest): void
{
$timestampName = format_date($uploadRequest->created_at, 'd. M. Y');
DB::table('folders')->insert([
'id' => $uploadRequest->id,
'parent_id' => $uploadRequest->folder_id,
'user_id' => $uploadRequest->user_id,
'name' => "Upload Request from $timestampName",
]);
}
}

View File

@@ -3,6 +3,7 @@ namespace Domain\UploadRequest\Models;
use App\Users\Models\User;
use Database\Factories\UploadRequestFactory;
use Domain\Folders\Models\Folder;
use Illuminate\Support\Str;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasOne;
@@ -43,6 +44,11 @@ class UploadRequest extends Model
return $this->hasOne(User::class, 'id', 'user_id');
}
public function folder(): HasOne
{
return $this->hasOne(Folder::class, 'id', 'id');
}
protected static function boot()
{
parent::boot();

View File

@@ -2,6 +2,7 @@
namespace Domain\UploadRequest\Resources;
use Domain\Folders\Resources\FolderResource;
use Illuminate\Http\Resources\Json\JsonResource;
class UploadRequestResource extends JsonResource
@@ -20,6 +21,9 @@ class UploadRequestResource extends JsonResource
'url' => url("/request/$this->id/upload"),
],
'relationships' => [
$this->mergeWhen($this->folder, fn () => [
'folder' => new FolderResource($this->folder),
]),
'user' => [
'data' => [
'id' => $this->user->id,

View File

@@ -160,7 +160,7 @@ class DefaultRestrictionsTest extends TestCase
// 404 but, ok, because there is not stored temporary file in test
$this
->get("file/$file->name/$share->token")
->get("file/$file->name/shared/$share->token")
->assertStatus(404);
}

View File

@@ -199,7 +199,7 @@ class FixedBillingRestrictionsTest extends TestCase
// 404 but, ok, because there is not stored temporary file in test
$this
->get("file/$file->name/$share->token")
->get("file/$file->name/shared/$share->token")
->assertStatus(404);
}

View File

@@ -184,7 +184,7 @@ class MeteredBillingRestrictionsTest extends TestCase
]);
$this
->get("file/$file->name/$share->token")
->get("file/$file->name/shared/$share->token")
->assertStatus(401);
}
@@ -214,7 +214,7 @@ class MeteredBillingRestrictionsTest extends TestCase
// 404 but, ok, because there is not stored temporary file in test
$this
->get("file/$file->name/$share->token")
->get("file/$file->name/shared/$share->token")
->assertStatus(404);
}

View File

@@ -185,7 +185,7 @@ class TrafficTest extends TestCase
'is_protected' => false,
]);
$this->get("/file/$document->name/$share->token")
$this->get("/file/$document->name/shared/$share->token")
->assertStatus(200);
$this->assertDatabaseHas('traffic', [

View File

@@ -2,10 +2,12 @@
namespace Tests\Domain\UploadRequest;
use Domain\Files\Models\File;
use Domain\UploadRequest\Notifications\UploadRequestNotification;
use Domain\UploadRequest\Models\UploadRequest;
use App\Users\Models\User;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Str;
use Storage;
use Tests\TestCase;
use Notification;
@@ -87,6 +89,7 @@ class UploadRequestTest extends TestCase
$uploadRequest = UploadRequest::factory()
->create([
'status' => 'active',
'user_id' => $user->id,
]);
@@ -108,7 +111,8 @@ class UploadRequestTest extends TestCase
$uploadRequest = UploadRequest::factory()
->create([
'user_id' => $user->id,
'status' => 'active',
'user_id' => $user->id,
'created_at' => now(),
]);
@@ -116,20 +120,120 @@ class UploadRequestTest extends TestCase
->create('fake-file.pdf', 12000000, 'application/pdf');
$this
->actingAs($user)
->postJson("/api/upload-request/$uploadRequest->id", [
'filename' => $file->name,
'file' => $file,
'parent_id' => $uploadRequest->id,
'parent_id' => null,
'path' => "/$file->name",
'is_last' => 'true',
])->assertStatus(201);
$this->assertDatabaseHas('folders', [
'id' => $uploadRequest->id,
'name' => 'Upload Request from 01. Jan. 2021',
]);
$this
->assertDatabaseHas('folders', [
'id' => $uploadRequest->id,
'name' => 'Upload Request from 01. Jan. 2021',
])->assertDatabaseHas('files', [
'parent_id' => $uploadRequest->id,
]);
//Storage::assertExists("files/$user->id/$file->basename");
$file = File::first();
Storage::assertExists("files/$user->id/$file->basename");
}
/**
* @test
*/
public function it_upload_file_into_non_active_upload_request()
{
$user = User::factory()
->hasSettings()
->create();
$uploadRequest = UploadRequest::factory()
->create([
'status' => 'expired',
'user_id' => $user->id,
'created_at' => now(),
]);
$file = UploadedFile::fake()
->create('fake-file.pdf', 12000000, 'application/pdf');
$this
->postJson("/api/upload-request/$uploadRequest->id", [
'filename' => $file->name,
'file' => $file,
'parent_id' => null,
'path' => "/$file->name",
'is_last' => 'true',
])->assertStatus(410);
}
/**
* @test
*/
public function it_get_file_from_upload_request_folder()
{
$user = User::factory()
->hasSettings()
->create();
$uploadRequest = UploadRequest::factory()
->create([
'status' => 'active',
'user_id' => $user->id,
'created_at' => now(),
]);
$file = UploadedFile::fake()
->create(Str::random() . '-fake-file.pdf', 1200, 'application/pdf');
Storage::putFileAs("files/$user->id", $file, $file->name);
File::factory()
->create([
'parent_id' => $uploadRequest->id,
'basename' => $file->name,
'user_id' => $user->id,
'name' => 'fake-file.pdf',
]);
$this
->get("/file/$file->name/upload-request/$uploadRequest->id")
->assertOk();
}
/**
* @test
*/
public function it_get_thumbnail_from_upload_request_folder()
{
$user = User::factory()
->hasSettings()
->create();
$uploadRequest = UploadRequest::factory()
->create([
'status' => 'active',
'user_id' => $user->id,
'created_at' => now(),
]);
$thumbnail = UploadedFile::fake()
->image('fake-thumbnail.jpg');
Storage::putFileAs("files/$user->id", $thumbnail, $thumbnail->name);
File::factory()
->create([
'parent_id' => $uploadRequest->id,
'basename' => 'fake-thumbnail.jpg',
'user_id' => $user->id,
]);
$this
->get("/thumbnail/xs-$thumbnail->name/upload-request/$uploadRequest->id")
->assertOk();
}
}