From a7cb0e9844501dfec04f0515b9914ac541ae4753 Mon Sep 17 00:00:00 2001 From: Nadim Salloum Date: Fri, 25 Jun 2021 17:35:37 +0300 Subject: [PATCH] make documents polymorphic --- app/Http/Controllers/CarController.php | 11 ++++ app/Http/Controllers/ContactController.php | 11 ++++ app/Http/Controllers/DocumentController.php | 18 +++++-- app/Models/Car.php | 8 ++- app/Models/Contact.php | 5 ++ app/Models/Contract.php | 2 +- app/Models/Document.php | 17 ++++--- app/Providers/MorphServiceProvider.php | 32 ++++++++++++ app/Providers/RouteServiceProvider.php | 18 +++---- config/app.php | 3 +- database/factories/DocumentFactory.php | 3 +- ...21_05_10_144114_create_documents_table.php | 5 +- database/seeders/DatabaseSeeder.php | 4 -- public/js/app.js | 51 +++++++++++++++---- resources/js/Components/Documents/Upload.vue | 7 ++- resources/js/Components/Documents/View.vue | 17 ++++--- resources/js/Pages/Cars/Show.vue | 3 ++ resources/js/Pages/Contacts/Show.vue | 3 ++ resources/js/Pages/Contracts/Show.vue | 2 +- routes/web.php | 13 +++-- 20 files changed, 172 insertions(+), 61 deletions(-) create mode 100644 app/Providers/MorphServiceProvider.php diff --git a/app/Http/Controllers/CarController.php b/app/Http/Controllers/CarController.php index 00a9490..7fcbc77 100644 --- a/app/Http/Controllers/CarController.php +++ b/app/Http/Controllers/CarController.php @@ -373,6 +373,17 @@ class CarController extends Controller 'known_damage' => $car->known_damage, 'notes' => $car->notes, 'deleted_at' => $car->deleted_at, + 'documents' => $car->documents()->orderBy('created_at', 'asc')->get() + ->map(function ($document) { + return [ + 'id' => $document->id, + 'name' => $document->name, + 'size' => $document->size, + 'extension' => $document->extension, + 'link' => $document->link, + 'created_at' => $document->created_at, + ]; + }), 'buy_contracts' => $car->buyContracts() ->orderBy('date', 'desc') ->with('contact') diff --git a/app/Http/Controllers/ContactController.php b/app/Http/Controllers/ContactController.php index f7eb015..4c49331 100644 --- a/app/Http/Controllers/ContactController.php +++ b/app/Http/Controllers/ContactController.php @@ -212,6 +212,17 @@ class ContactController extends Controller 'city' => $contact->city, 'country' => $contact->country, 'deleted_at' => $contact->deleted_at, + 'documents' => $contact->documents()->orderBy('created_at', 'asc')->get() + ->map(function ($document) { + return [ + 'id' => $document->id, + 'name' => $document->name, + 'size' => $document->size, + 'extension' => $document->extension, + 'link' => $document->link, + 'created_at' => $document->created_at, + ]; + }), 'buy_contracts' => $contact->buyContracts() ->orderBy('date', 'desc') ->with('car') diff --git a/app/Http/Controllers/DocumentController.php b/app/Http/Controllers/DocumentController.php index dbdddd2..e795b33 100644 --- a/app/Http/Controllers/DocumentController.php +++ b/app/Http/Controllers/DocumentController.php @@ -7,10 +7,11 @@ use App\Models\Document; use Illuminate\Http\Request; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Redirect; +use Illuminate\Database\Eloquent\Relations\Relation; class DocumentController extends Controller { - public function show(Contract $contract, Document $document) + public function show(Document $document) { if (file_exists($document->path)) { header('Content-Disposition: filename="' . $document->name . '"'); @@ -20,8 +21,14 @@ class DocumentController extends Controller abort(404); } - public function store(Request $request, Contract $contract) + public function store(Request $request) { + $class = $request->get('documentable_type'); + $id = $request->get('documentable_id'); + if (!in_array($class, ['contracts', 'cars', 'contacts'])) { + return []; + } + $file = $request->file()['document']; $internalName = date('Y-m-d-H-i-s') . '.' . $file->extension(); $document = Document::create([ @@ -29,9 +36,10 @@ class DocumentController extends Controller 'internal_name' => $internalName, 'size' => $file->getSize(), 'extension' => $file->extension() ?? '', - 'contract_id' => $contract->id, + 'documentable_type' => $class, + 'documentable_id' => $id, ]); - $file->move(public_path("documents/contracts/{$contract->id}/"), $internalName); + $file->move(public_path("documents/{$class}/{$id}/"), $internalName); return [ 'id' => $document->id, @@ -43,7 +51,7 @@ class DocumentController extends Controller ]; } - public function destroy(Request $request, Contract $contract) + public function destroy(Request $request) { $document = Document::find((int)$request->get('id')); diff --git a/app/Models/Car.php b/app/Models/Car.php index 621f62e..5b90d5e 100644 --- a/app/Models/Car.php +++ b/app/Models/Car.php @@ -2,9 +2,8 @@ namespace App\Models; -use App\Enums\ContractType; use Carbon\Carbon; -use Cknow\Money\Money; +use App\Enums\ContractType; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -25,6 +24,11 @@ class Car extends Model 'car_model_id', ]; + public function documents() + { + return $this->morphMany(Document::class, 'documentable'); + } + public function getNameAttribute() { if (!$this->carModel) { diff --git a/app/Models/Contact.php b/app/Models/Contact.php index 693ace2..ab929d0 100644 --- a/app/Models/Contact.php +++ b/app/Models/Contact.php @@ -25,6 +25,11 @@ class Contact extends Model 'notes', ]; + public function documents() + { + return $this->morphMany(Document::class, 'documentable'); + } + public function getNameAttribute() { return implode(' ', array_filter([$this->lastname, $this->firstname])); diff --git a/app/Models/Contract.php b/app/Models/Contract.php index 54ebed4..5ce5968 100644 --- a/app/Models/Contract.php +++ b/app/Models/Contract.php @@ -96,7 +96,7 @@ class Contract extends Model public function documents() { - return $this->hasMany(Document::class); + return $this->morphMany(Document::class, 'documentable'); } public function payments() diff --git a/app/Models/Document.php b/app/Models/Document.php index 4121eb6..353de1b 100644 --- a/app/Models/Document.php +++ b/app/Models/Document.php @@ -15,9 +15,15 @@ class Document extends Model 'internal_name', 'extension', 'size', - 'contract_id', + 'documentable_type', + 'documentable_id', ]; + public function documentable() + { + return $this->morphTo(); + } + public function getCreatedAtAttribute($created_at) { return Carbon::parse($created_at)->format('d.m.Y'); @@ -43,16 +49,11 @@ class Document extends Model public function getLinkAttribute() { - return route('documents.show', [$this->contract->id, $this->id]); + return route('documents.show', $this->id); } public function getPathAttribute() { - return public_path("documents/contracts/{$this->contract->id}/{$this->internal_name}"); - } - - public function contract() - { - return $this->belongsTo(Contract::class)->withTrashed(); + return public_path("documents/{$this->documentable_type}/{$this->documentable->id}/{$this->internal_name}"); } } diff --git a/app/Providers/MorphServiceProvider.php b/app/Providers/MorphServiceProvider.php new file mode 100644 index 0000000..e841f91 --- /dev/null +++ b/app/Providers/MorphServiceProvider.php @@ -0,0 +1,32 @@ + 'App\Models\Contract', + 'cars' => 'App\Models\Car', + 'contacts' => 'App\Models\Contact', + ]); + } + + /** + * Register services. + * + * @return void + */ + public function register() + { + // + } +} diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 2c9cdcc..8cc1366 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -41,15 +41,6 @@ class RouteServiceProvider extends ServiceProvider $this->configureRateLimiting(); $this->routes(function () { - Route::prefix('api') - ->middleware('api') - ->namespace($this->namespace) - ->group(base_path('routes/api.php')); - - Route::middleware('web') - ->namespace($this->namespace) - ->group(base_path('routes/web.php')); - Route::bind('car', function ($value) { if (in_array(Route::currentRouteName(), ['cars.show', 'cars.restore'])) { return Car::withTrashed()->find($value); @@ -70,6 +61,15 @@ class RouteServiceProvider extends ServiceProvider } return Contract::find($value); }); + + Route::prefix('api') + ->middleware('api') + ->namespace($this->namespace) + ->group(base_path('routes/api.php')); + + Route::middleware('web') + ->namespace($this->namespace) + ->group(base_path('routes/web.php')); }); } diff --git a/config/app.php b/config/app.php index 1aef992..96b6c5f 100644 --- a/config/app.php +++ b/config/app.php @@ -171,6 +171,7 @@ return [ */ App\Providers\AppServiceProvider::class, App\Providers\AuthServiceProvider::class, + App\Providers\MorphServiceProvider::class, // App\Providers\BroadcastServiceProvider::class, App\Providers\EventServiceProvider::class, App\Providers\RouteServiceProvider::class, @@ -229,7 +230,7 @@ return [ 'URL' => Illuminate\Support\Facades\URL::class, 'Validator' => Illuminate\Support\Facades\Validator::class, 'View' => Illuminate\Support\Facades\View::class, - 'PDF' => Barryvdh\DomPDF\Facade::class, + 'PDF' => Barryvdh\DomPDF\Facade::class, ], ]; diff --git a/database/factories/DocumentFactory.php b/database/factories/DocumentFactory.php index 023fe67..e2ed364 100644 --- a/database/factories/DocumentFactory.php +++ b/database/factories/DocumentFactory.php @@ -26,7 +26,8 @@ class DocumentFactory extends Factory return [ 'name' => 'Vertrag.pdf', 'internal_name' => '2021-06-11-13:11:12.pdf', - 'contract_id' => $this->faker->numberBetween(1, Contract::count()), + 'documentable_id' => $this->faker->numberBetween(1, Contract::count()), + 'documentable_type' => 'contracts', 'size' => $this->faker->numberBetween(1, 30000), 'extension' => 'pdf', ]; diff --git a/database/migrations/2021_05_10_144114_create_documents_table.php b/database/migrations/2021_05_10_144114_create_documents_table.php index bf64420..6152a75 100644 --- a/database/migrations/2021_05_10_144114_create_documents_table.php +++ b/database/migrations/2021_05_10_144114_create_documents_table.php @@ -19,10 +19,7 @@ class CreateDocumentsTable extends Migration $table->string('internal_name'); $table->integer('size'); $table->string('extension'); - $table->foreignId('contract_id') - ->onUpdate('cascade') - ->onDelete('cascade') - ->constrained('contracts'); + $table->morphs('documentable'); $table->timestamps(); }); } diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index e039ee4..7eb2337 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -73,10 +73,6 @@ class DatabaseSeeder extends Seeder $payments = Payment::factory() ->count(60) ->create(); - - $documents = Document::factory() - ->count(40) - ->create(); } public function getBrands(): array diff --git a/public/js/app.js b/public/js/app.js index 2928e44..5e011dc 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -18486,6 +18486,7 @@ var STATUS_FAILED = 2; /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({ props: { id: Number, + documentable_type: String, documents: Object }, data: function data() { @@ -18517,7 +18518,8 @@ var STATUS_FAILED = 2; // upload data to the server this.currentStatus = STATUS_SAVING; - axios.post(this.route('documents.store', this.id), formData).then(function (response) { + console.log(this.route('documents.store')); + axios.post(this.route('documents.store'), formData).then(function (response) { _this.documents.push(response.data); _this.reset(); @@ -18529,7 +18531,9 @@ var STATUS_FAILED = 2; filesChange: function filesChange(fieldName, fileList) { // handle file changes var formData = new FormData(); - if (!fileList.length) return; // append the files to FormData + if (!fileList.length) return; + formData.append('documentable_type', this.documentable_type); + formData.append('documentable_id', this.id); // append the files to FormData Array.from(Array(fileList.length).keys()).map(function (x) { formData.append(fieldName, fileList[x], fileList[x].name); @@ -18573,6 +18577,7 @@ __webpack_require__.r(__webpack_exports__); props: { initial_documents: Object, id: Number, + documentable_type: String, show_upload: Boolean }, data: function data() { @@ -20737,6 +20742,8 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _Components_Buttons_RestoreButton_vue__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! @/Components/Buttons/RestoreButton.vue */ "./resources/js/Components/Buttons/RestoreButton.vue"); /* harmony import */ var _Components_Contracts_ContractTable_vue__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../../Components/Contracts/ContractTable.vue */ "./resources/js/Components/Contracts/ContractTable.vue"); /* harmony import */ var _Components_SmallTitle_vue__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ../../Components/SmallTitle.vue */ "./resources/js/Components/SmallTitle.vue"); +/* harmony import */ var _Components_Documents_View_vue__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! @/Components/Documents/View.vue */ "./resources/js/Components/Documents/View.vue"); + @@ -20754,7 +20761,8 @@ __webpack_require__.r(__webpack_exports__); DeleteButton: _Components_Buttons_DeleteButton_vue__WEBPACK_IMPORTED_MODULE_4__.default, RestoreButton: _Components_Buttons_RestoreButton_vue__WEBPACK_IMPORTED_MODULE_5__.default, ContractTable: _Components_Contracts_ContractTable_vue__WEBPACK_IMPORTED_MODULE_6__.default, - SmallTitle: _Components_SmallTitle_vue__WEBPACK_IMPORTED_MODULE_7__.default + SmallTitle: _Components_SmallTitle_vue__WEBPACK_IMPORTED_MODULE_7__.default, + DocumentsView: _Components_Documents_View_vue__WEBPACK_IMPORTED_MODULE_8__.default }, props: { car: Object @@ -21276,6 +21284,8 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _Components_Buttons_RestoreButton_vue__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! @/Components/Buttons/RestoreButton.vue */ "./resources/js/Components/Buttons/RestoreButton.vue"); /* harmony import */ var _Components_Contracts_ContractTable_vue__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../../Components/Contracts/ContractTable.vue */ "./resources/js/Components/Contracts/ContractTable.vue"); /* harmony import */ var _Components_SmallTitle_vue__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ../../Components/SmallTitle.vue */ "./resources/js/Components/SmallTitle.vue"); +/* harmony import */ var _Components_Documents_View_vue__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! @/Components/Documents/View.vue */ "./resources/js/Components/Documents/View.vue"); + @@ -21293,7 +21303,8 @@ __webpack_require__.r(__webpack_exports__); DeleteButton: _Components_Buttons_DeleteButton_vue__WEBPACK_IMPORTED_MODULE_4__.default, RestoreButton: _Components_Buttons_RestoreButton_vue__WEBPACK_IMPORTED_MODULE_5__.default, ContractTable: _Components_Contracts_ContractTable_vue__WEBPACK_IMPORTED_MODULE_6__.default, - SmallTitle: _Components_SmallTitle_vue__WEBPACK_IMPORTED_MODULE_7__.default + SmallTitle: _Components_SmallTitle_vue__WEBPACK_IMPORTED_MODULE_7__.default, + DocumentsView: _Components_Documents_View_vue__WEBPACK_IMPORTED_MODULE_8__.default }, props: { contact: Object @@ -23546,7 +23557,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) { var _component_document_upload = (0,vue__WEBPACK_IMPORTED_MODULE_0__.resolveComponent)("document-upload"); - return (0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createBlock)(vue__WEBPACK_IMPORTED_MODULE_0__.Fragment, null, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)(_component_small_title, { + return (0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createBlock)("div", null, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)(_component_small_title, { title: "Dokumente", "class": "mb-3" }), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)("div", _hoisted_1, [((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(true), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createBlock)(vue__WEBPACK_IMPORTED_MODULE_0__.Fragment, null, (0,vue__WEBPACK_IMPORTED_MODULE_0__.renderList)($data.documents, function (document) { @@ -23562,12 +23573,11 @@ function render(_ctx, _cache, $props, $setup, $data, $options) { )), $props.show_upload ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createBlock)(_component_document_upload, { key: 0, id: $props.id, + documentable_type: $props.documentable_type, documents: $data.documents }, null, 8 /* PROPS */ - , ["id", "documents"])) : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)("v-if", true)])], 64 - /* STABLE_FRAGMENT */ - ); + , ["id", "documentable_type", "documents"])) : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)("v-if", true)])]); } /***/ }), @@ -28417,6 +28427,8 @@ function render(_ctx, _cache, $props, $setup, $data, $options) { var _component_contract_table = (0,vue__WEBPACK_IMPORTED_MODULE_0__.resolveComponent)("contract-table"); + var _component_documents_view = (0,vue__WEBPACK_IMPORTED_MODULE_0__.resolveComponent)("documents-view"); + var _component_show_page = (0,vue__WEBPACK_IMPORTED_MODULE_0__.resolveComponent)("show-page"); return (0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createBlock)(_component_show_page, null, { @@ -28478,7 +28490,15 @@ function render(_ctx, _cache, $props, $setup, $data, $options) { title: $props.car.sell_contracts.length > 1 ? $props.car.sell_contracts.length + ' Verkaufsverträge' : 'Verkaufsvertrag' }, null, 8 /* PROPS */ - , ["contracts", "carId", "show_upload", "title"])]; + , ["contracts", "carId", "show_upload", "title"]), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)(_component_documents_view, { + "class": "mt-5", + initial_documents: $props.car.documents, + id: $props.car.id, + documentable_type: "cars", + show_upload: !$props.car.deleted_at + }, null, 8 + /* PROPS */ + , ["initial_documents", "id", "show_upload"])]; }), _: 1 /* STABLE */ @@ -29307,6 +29327,8 @@ function render(_ctx, _cache, $props, $setup, $data, $options) { var _component_contract_table = (0,vue__WEBPACK_IMPORTED_MODULE_0__.resolveComponent)("contract-table"); + var _component_documents_view = (0,vue__WEBPACK_IMPORTED_MODULE_0__.resolveComponent)("documents-view"); + var _component_show_page = (0,vue__WEBPACK_IMPORTED_MODULE_0__.resolveComponent)("show-page"); return (0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createBlock)(_component_show_page, null, { @@ -29368,7 +29390,15 @@ function render(_ctx, _cache, $props, $setup, $data, $options) { title: $props.contact.sell_contracts.length > 1 ? $props.contact.sell_contracts.length + ' Verkaufsverträge' : 'Verkaufsvertrag' }, null, 8 /* PROPS */ - , ["contracts", "contactId", "show_upload", "title"])]; + , ["contracts", "contactId", "show_upload", "title"]), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)(_component_documents_view, { + "class": "mt-5", + initial_documents: $props.contact.documents, + id: $props.contact.id, + documentable_type: "contacts", + show_upload: !$props.contact.deleted_at + }, null, 8 + /* PROPS */ + , ["initial_documents", "id", "show_upload"])]; }), _: 1 /* STABLE */ @@ -30069,6 +30099,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) { , ["payments", "contract"]), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)(_component_documents_view, { initial_documents: $props.contract.documents, id: $props.contract.id, + documentable_type: "contracts", show_upload: !$props.contract.deleted_at }, null, 8 /* PROPS */ diff --git a/resources/js/Components/Documents/Upload.vue b/resources/js/Components/Documents/Upload.vue index f8d0b16..39ba134 100644 --- a/resources/js/Components/Documents/Upload.vue +++ b/resources/js/Components/Documents/Upload.vue @@ -24,6 +24,7 @@ const STATUS_INITIAL = 0; const STATUS_SAVING = 1; const export default { props: { id: Number, + documentable_type: String, documents: Object, }, data() { @@ -53,7 +54,8 @@ export default { save(formData) { // upload data to the server this.currentStatus = STATUS_SAVING; - axios.post(this.route('documents.store', this.id), formData) + console.log(this.route('documents.store')); + axios.post(this.route('documents.store'), formData) .then((response) => { this.documents.push(response.data); this.reset(); @@ -69,6 +71,9 @@ export default { if (!fileList.length) return; + formData.append('documentable_type', this.documentable_type); + formData.append('documentable_id', this.id); + // append the files to FormData Array .from(Array(fileList.length).keys()) diff --git a/resources/js/Components/Documents/View.vue b/resources/js/Components/Documents/View.vue index 201f6f8..3808874 100644 --- a/resources/js/Components/Documents/View.vue +++ b/resources/js/Components/Documents/View.vue @@ -1,11 +1,13 @@