diff --git a/app/Http/Controllers/Api/MediaController.php b/app/Http/Controllers/Api/MediaController.php index 992e3de..5d9580d 100644 --- a/app/Http/Controllers/Api/MediaController.php +++ b/app/Http/Controllers/Api/MediaController.php @@ -8,6 +8,7 @@ use App\Http\Requests\MediaRequest; use App\Models\Media; use Illuminate\Http\Request; use Illuminate\Support\Carbon; +use Illuminate\Support\Facades\Log; use Laravel\Sanctum\PersonalAccessToken; class MediaController extends ApiController @@ -119,19 +120,36 @@ class MediaController extends ApiController if (MediaConductor::updatable($medium) === true) { $file = $request->file('file'); if ($file !== null) { - if ($file->getSize() > Media::maxUploadSize()) { - return $this->respondTooLarge(); + if ($file->isValid() !== true) { + switch ($file->getError()) { + case UPLOAD_ERR_INI_SIZE: + case UPLOAD_ERR_FORM_SIZE: + return $this->respondTooLarge(); + case UPLOAD_ERR_PARTIAL: + return $this->respondWithErrors(['file' => 'The file upload was interrupted.']); + default: + return $this->respondWithErrors(['file' => 'An error occurred uploading the file to the server.']); + } } - if ($medium->updateFile($file) === false) { + if ($file->getSize() > Media::getMaxUploadSize()) { + return $this->respondTooLarge(); + } + } + + $medium->update($request->all()); + + if ($file !== null) { + try { + $medium->updateWithUploadedFile($file); + } catch (\Exception $e) { return $this->respondWithErrors( - ['file' => 'The file could not be stored on the server'], + ['file' => $e->getMessage()], HttpResponseCodes::HTTP_INTERNAL_SERVER_ERROR ); } - }//end if + } - $medium->update($request->all()); return $this->respondAsResource(MediaConductor::model($request, $medium)); }//end if diff --git a/app/Jobs/StoreUploadedFileJob.php b/app/Jobs/StoreUploadedFileJob.php index 6f9f724..10591f4 100644 --- a/app/Jobs/StoreUploadedFileJob.php +++ b/app/Jobs/StoreUploadedFileJob.php @@ -44,11 +44,12 @@ class StoreUploadedFileJob implements ShouldQueue */ protected $replaceExisting; + /** * Create a new job instance. * - * @param Media $media The media model. - * @param string $filePath The uploaded file. + * @param Media $media The media model. + * @param string $filePath The uploaded file. * @param boolean $replaceExisting Replace existing files. * @return void */ @@ -74,31 +75,30 @@ class StoreUploadedFileJob implements ShouldQueue $this->media->save(); if (strlen($this->uploadedFilePath) > 0) { - if (Storage::disk($storageDisk)->exists($fileName) == false || $this->replaceExisting == true) { + if (Storage::disk($storageDisk)->exists($fileName) === false || $this->replaceExisting === true) { Storage::disk($storageDisk)->putFileAs('/', new SplFileInfo($this->uploadedFilePath), $fileName); Log::info("uploading file {$storageDisk} / {$fileName} / {$this->uploadedFilePath}"); } else { Log::info("file {$fileName} already exists in {$storageDisk} / {$this->uploadedFilePath}. Not replacing file and using local {$fileName} for variants."); } } else { - if (Storage::disk($storageDisk)->exists($fileName) == true) { + if (Storage::disk($storageDisk)->exists($fileName) === true) { Log::info("file {$fileName} already exists in {$storageDisk} / {$this->uploadedFilePath}. No local {$fileName} for variants, downloading from CDN."); $readStream = Storage::disk($storageDisk)->readStream($fileName); $tempFilePath = tempnam(sys_get_temp_dir(), 'download-'); $writeStream = fopen($tempFilePath, 'w'); - while (!feof($readStream)) { + while (feof($readStream) !== true) { fwrite($writeStream, fread($readStream, 8192)); } fclose($readStream); fclose($writeStream); $this->uploadedFilePath = $tempFilePath; - } else { $errorStr = "cannot upload file {$storageDisk} / {$fileName} / {$this->uploadedFilePath} as temp file is empty"; Log::info($errorStr); throw new \Exception($errorStr); } - } + }//end if if (strpos($this->media->mime_type, 'image/') === 0) { $this->media->status = "Optimizing image"; @@ -160,7 +160,7 @@ class StoreUploadedFileJob implements ShouldQueue }//end if } else { Log::info("variant {$variantName} already exists for file {$fileName}"); - } + }//end if }//end foreach // Set missing variants to the largest available variant diff --git a/app/Models/Media.php b/app/Models/Media.php index 57f3e6d..3840b02 100644 --- a/app/Models/Media.php +++ b/app/Models/Media.php @@ -206,7 +206,7 @@ class Media extends Model */ public function getUrlAttribute() { - if(isset($this->attributes['name'])) { + if (isset($this->attributes['name']) === true) { $url = config("filesystems.disks.$this->storage.url"); return "$url/$this->name"; } @@ -247,6 +247,28 @@ class Media extends Model * @return null|Media The result or null if not successful. */ public static function createFromUploadedFile(Request $request, UploadedFile $file) + { + $request->merge([ + 'title' => $request->get('title', ''), + 'name' => '', + 'size' => 0, + 'mime_type' => '', + 'status' => '', + ]); + + $mediaItem = $request->user()->media()->create($request->all()); + $mediaItem->updateWithUploadedFile($file); + + return $mediaItem; + } + + /** + * Update Media with UploadedFile data. + * + * @param Illuminate\Http\UploadedFile $file The file. + * @return null|Media The media item. + */ + public function updateWithUploadedFile(UploadedFile $file) { if ($file === null || $file->isValid() !== true) { throw new \Exception('The file is invalid.', self::INVALID_FILE_ERROR); @@ -261,34 +283,40 @@ class Media extends Model throw new \Exception('The file name already exists in storage.', self::FILE_NAME_EXISTS_ERROR); } - $request->merge([ - 'title' => $request->get('title', $name), - 'name' => $name, - 'size' => $file->getSize(), - 'mime_type' => $file->getMimeType(), - 'status' => 'Processing media', - ]); + // remove file if there is an existing entry in this medium item + if (strlen($this->name) > 0 && strlen($this->storage) > 0) { + Storage::disk($this->storage)->delete($this->name); + foreach ($this->variants as $variantName => $fileName) { + Storage::disk($this->storage)->delete($fileName); + } - $mediaItem = $request->user()->media()->create($request->all()); - - try { - $temporaryFilePath = tempnam(sys_get_temp_dir(), 'upload'); - $temporaryDirectoryPath = dirname($temporaryFilePath); - $file->move($temporaryDirectoryPath, basename($temporaryFilePath)); - } catch (\Exception $e) { - throw new \Exception('Could not temporarily store file. ' . $e->getMessage(), self::TEMP_FILE_ERROR); + $this->name = ''; + $this->variants = []; } + if (strlen($this->title) === 0) { + $this->title = $name; + } + + $this->name = $name; + $this->size = $file->getSize(); + $this->mime_type = $file->getMimeType(); + $this->status = 'Processing media'; + $this->save(); + + $temporaryFilePath = tempnam(sys_get_temp_dir(), 'upload'); + copy($file->path(), $temporaryFilePath); + try { - StoreUploadedFileJob::dispatch($mediaItem, $temporaryFilePath)->onQueue('media'); + StoreUploadedFileJob::dispatch($this, $temporaryFilePath)->onQueue('media'); } catch (\Exception $e) { - $mediaItem->delete(); - $mediaItem = null; + $this->status = 'Error'; + $this->save(); throw $e; }//end try - return $mediaItem; + return $this; } /** diff --git a/composer.lock b/composer.lock index 0dc22dd..883e296 100644 --- a/composer.lock +++ b/composer.lock @@ -62,16 +62,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.263.14", + "version": "3.268.16", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "7a6a43fad8899e3be3c46471fa3802331620e36b" + "reference": "b59134c9ca64dcb9de6f7dbbcb9d5a75ed665a98" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/7a6a43fad8899e3be3c46471fa3802331620e36b", - "reference": "7a6a43fad8899e3be3c46471fa3802331620e36b", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/b59134c9ca64dcb9de6f7dbbcb9d5a75ed665a98", + "reference": "b59134c9ca64dcb9de6f7dbbcb9d5a75ed665a98", "shasum": "" }, "require": { @@ -151,9 +151,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.263.14" + "source": "https://github.com/aws/aws-sdk-php/tree/3.268.16" }, - "time": "2023-04-20T18:21:44+00:00" + "time": "2023-04-21T21:37:05+00:00" }, { "name": "brick/math", @@ -9508,5 +9508,5 @@ "php": "^8.0.2" }, "platform-dev": [], - "plugin-api-version": "2.1.0" + "plugin-api-version": "2.3.0" } diff --git a/config/filesystems.php b/config/filesystems.php index 4b8e02e..5f66356 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -31,10 +31,7 @@ return [ 'disks' => [ 'local' => [ 'driver' => 'local', - 'root' => storage_path('app/uploads'), - 'throw' => false, - 'url' => env('STORAGE_LOCAL_URL'), - 'public' => true, + 'root' => storage_path('app'), ], 'cdn' => [ diff --git a/package-lock.json b/package-lock.json index 5cf87f7..f6909f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -475,9 +475,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.38.0.tgz", - "integrity": "sha512-IoD2MfUnOV58ghIHCiil01PcohxjbYR/qCxsoC+xNgUwh1EY8jOOrYmu3d3a71+tJJ23uscEV4X2HJWMsPJu4g==", + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.39.0.tgz", + "integrity": "sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -729,9 +729,9 @@ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" }, "node_modules/@types/node": { - "version": "18.15.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.12.tgz", - "integrity": "sha512-Wha1UwsB3CYdqUm2PPzh/1gujGCNtWVUYF0mB00fJFoR4gTyWTDPjSm+zBF787Ahw8vSGgBja90MkgFwvB86Dg==" + "version": "18.15.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.13.tgz", + "integrity": "sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==" }, "node_modules/@types/semver": { "version": "7.3.13", @@ -1607,9 +1607,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001480", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001480.tgz", - "integrity": "sha512-q7cpoPPvZYgtyC4VaBSN0Bt+PJ4c4EYRf0DrduInOz2SkFpHD5p3LnvEpqBp7UnJn+8x1Ogl1s38saUxe+ihQQ==", + "version": "1.0.30001481", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001481.tgz", + "integrity": "sha512-KCqHwRnaa1InZBtqXzP98LPg0ajCVujMKjqKDhZEthIpAsJl/YEIa3YvXjGXPVqzZVguccuu7ga9KOE1J9rKPQ==", "funding": [ { "type": "opencollective", @@ -1936,9 +1936,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.368", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.368.tgz", - "integrity": "sha512-e2aeCAixCj9M7nJxdB/wDjO6mbYX+lJJxSJCXDzlr5YPGYVofuJwGN9nKg2o6wWInjX6XmxRinn3AeJMK81ltw==", + "version": "1.4.369", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.369.tgz", + "integrity": "sha512-LfxbHXdA/S+qyoTEA4EbhxGjrxx7WK2h6yb5K2v0UCOufUKX+VZaHbl3svlzZfv9sGseym/g3Ne4DpsgRULmqg==", "peer": true }, "node_modules/emoji-regex": { @@ -2032,15 +2032,15 @@ } }, "node_modules/eslint": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.38.0.tgz", - "integrity": "sha512-pIdsD2jwlUGf/U38Jv97t8lq6HpaU/G9NKbYmpWpZGw3LdTNhZLbJePqxOXGB5+JEKfOPU/XLxYxFh03nr1KTg==", + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.39.0.tgz", + "integrity": "sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", "@eslint/eslintrc": "^2.0.2", - "@eslint/js": "8.38.0", + "@eslint/js": "8.39.0", "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -2050,7 +2050,7 @@ "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", + "eslint-scope": "^7.2.0", "eslint-visitor-keys": "^3.4.0", "espree": "^9.5.1", "esquery": "^1.4.2", @@ -3473,9 +3473,9 @@ } }, "node_modules/rollup": { - "version": "3.20.6", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.20.6.tgz", - "integrity": "sha512-2yEB3nQXp/tBQDN0hJScJQheXdvU2wFhh6ld7K/aiZ1vYcak6N/BKjY1QrU6BvO2JWYS8bEs14FRaxXosxy2zw==", + "version": "3.20.7", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.20.7.tgz", + "integrity": "sha512-P7E2zezKSLhWnTz46XxjSmInrbOCiul1yf+kJccMxT56vxjHwCbDfoLbiqFgu+WQoo9ij2PkraYaBstgB2prBA==", "bin": { "rollup": "dist/bin/rollup" }, diff --git a/public/uploadabiwAz b/public/uploadabiwAz new file mode 100644 index 0000000..00313cc Binary files /dev/null and b/public/uploadabiwAz differ diff --git a/resources/css/utils.scss b/resources/css/utils.scss index 1c796ce..dda1119 100644 --- a/resources/css/utils.scss +++ b/resources/css/utils.scss @@ -114,7 +114,7 @@ } .flex-row-reverse { - flex-direction: row-reverse; + flex-direction: row-reverse !important; } .flex-column { diff --git a/resources/js/components/SMButton.vue b/resources/js/components/SMButton.vue index 69ac127..4b6bbf7 100644 --- a/resources/js/components/SMButton.vue +++ b/resources/js/components/SMButton.vue @@ -8,6 +8,7 @@ props.size, { 'button-block': block }, { 'button-dropdown': dropdown }, + { 'button-loading': loading }, ]" ref="buttonRef" :style="{ minWidth: minWidth }" @@ -146,8 +147,10 @@ if (props.form !== undefined) { watch( () => props.form.loading(), (newValue) => { - loading.value = newValue; disabled.value = newValue; + if (buttonType === "submit") { + loading.value = newValue; + } } ); } @@ -265,7 +268,7 @@ const handleClickItem = (item: string) => { &:disabled, &.primary:disabled { - background-color: var(--base-color-dark); + background-color: var(--base-color-dark) !important; box-shadow: none; } diff --git a/resources/js/components/SMImage.vue b/resources/js/components/SMImage.vue new file mode 100644 index 0000000..b0327c0 --- /dev/null +++ b/resources/js/components/SMImage.vue @@ -0,0 +1,58 @@ + + + + + diff --git a/resources/js/components/SMLoading.vue b/resources/js/components/SMLoading.vue index 791e0d7..0967e99 100644 --- a/resources/js/components/SMLoading.vue +++ b/resources/js/components/SMLoading.vue @@ -1,6 +1,9 @@ @@ -13,14 +16,54 @@ const props = defineProps({ default: false, required: false, }, + text: { + type: String, + default: "", + required: false, + }, + overlay: { + type: Boolean, + default: false, + required: false, + }, }); diff --git a/resources/js/components/SMTable.vue b/resources/js/components/SMTable.vue index e9e746a..3cb075a 100644 --- a/resources/js/components/SMTable.vue +++ b/resources/js/components/SMTable.vue @@ -17,17 +17,11 @@ :data-title="header['text']" :key="`item-row-${index}-${header['value']}`"> @@ -57,6 +51,22 @@ const slots = useSlots(); const handleRowClick = (item) => { emits("rowClick", item); }; + +const getItemValue = (data: unknown, key: string): string => { + if (typeof data === "object" && data !== null) { + return key.split(".").reduce((item, key) => item[key], data); + } + + return ""; +}; + +const hasClassLong = (text: unknown): boolean => { + if (typeof text == "string") { + return text.length >= 35; + } + + return false; +}; diff --git a/resources/js/views/dashboard/MediaList.vue b/resources/js/views/dashboard/MediaList.vue index 008d0cc..2c27278 100644 --- a/resources/js/views/dashboard/MediaList.vue +++ b/resources/js/views/dashboard/MediaList.vue @@ -39,6 +39,11 @@ +