From 4258e0727d7b985bc5e5ba1fe5ea1f37a81f4b9e Mon Sep 17 00:00:00 2001 From: paranarimasu <33796518+paranarimasu@users.noreply.github.com> Date: Sat, 30 Jul 2022 21:14:29 -0500 Subject: [PATCH 01/24] Seed audios (#426) * feat: initial implementation of audio resource * chore: bump dependencies --- .env.example | 18 + .../Balance/ReconcileBalanceRepositories.php} | 26 +- .../Balance/ReconcileBalanceResults.php | 26 + .../ReconcileTransactionRepositories.php | 93 ++ .../ReconcileTransactionResults.php | 26 + .../Repositories/ReconcileRepositories.php | 148 ++ app/Actions/Repositories/ReconcileResults.php | 109 ++ .../Wiki/Audio/ReconcileAudioRepositories.php | 96 ++ .../Wiki/Audio/ReconcileAudioResults.php | 26 + .../Video/ReconcileVideoRepositories.php} | 26 +- .../Wiki/Video/ReconcileVideoResults.php | 26 + app/Actions/Wiki/Video/BackfillAudio.php | 186 +++ .../ReconcilesTransactionRepositories.php | 45 - .../Repositories/ReconcilesRepositories.php | 341 ----- .../Balance/BalanceReconcileCommand.php | 114 +- .../Billing/ServiceReconcileCommand.php | 16 +- .../TransactionReconcileCommand.php | 114 +- .../Wiki/Audio/AudioReconcileCommand.php | 65 + .../Wiki/Video/VideoReconcileCommand.php | 120 +- .../Commands/Wiki/WikiDatabaseDumpCommand.php | 2 + app/Constants/Config/FlagConstants.php | 4 + .../Repositories/RepositoryInterface.php | 2 + app/Enums/Models/Wiki/VideoSource.php | 19 + app/Events/Wiki/Audio/AudioCreated.php | 46 + app/Events/Wiki/Audio/AudioDeleted.php | 69 + app/Events/Wiki/Audio/AudioRestored.php | 46 + app/Events/Wiki/Audio/AudioUpdated.php | 47 + .../Flags/FlagsAllowAudioStreamsField.php | 22 + .../Field/Wiki/Audio/AudioBasenameField.php | 53 + .../Field/Wiki/Audio/AudioFilenameField.php | 39 + .../Api/Field/Wiki/Audio/AudioLinkField.php | 22 + .../Field/Wiki/Audio/AudioMimeTypeField.php | 39 + .../Api/Field/Wiki/Audio/AudioPathField.php | 39 + .../Api/Field/Wiki/Audio/AudioSizeField.php | 39 + .../Field/Wiki/Video/VideoAudioIdField.php | 73 + .../Api/Query/Wiki/Audio/AudioReadQuery.php | 63 + .../Api/Query/Wiki/Audio/AudioWriteQuery.php | 50 + app/Http/Api/Schema/Config/FlagsSchema.php | 2 + app/Http/Api/Schema/Wiki/AudioSchema.php | 77 + app/Http/Api/Schema/Wiki/VideoSchema.php | 3 + .../Controllers/Api/Wiki/AudioController.php | 124 ++ app/Http/Controllers/Wiki/AudioController.php | 51 + app/Http/Controllers/Wiki/VideoController.php | 16 +- app/Http/Kernel.php | 2 + .../Middleware/IsAudioStreamingAllowed.php | 36 + .../Api/Wiki/Audio/AudioDestroyRequest.php | 37 + .../Wiki/Audio/AudioForceDeleteRequest.php | 37 + .../Api/Wiki/Audio/AudioIndexRequest.php | 37 + .../Api/Wiki/Audio/AudioRestoreRequest.php | 37 + .../Api/Wiki/Audio/AudioShowRequest.php | 37 + .../Api/Wiki/Audio/AudioStoreRequest.php | 37 + .../Api/Wiki/Audio/AudioUpdateRequest.php | 37 + .../Config/Resource/FlagsResource.php | 4 + .../Wiki/Collection/AudioCollection.php | 36 + .../Resources/Wiki/Resource/AudioResource.php | 99 ++ .../Resources/Wiki/Resource/VideoResource.php | 1 + app/Models/Wiki/Anime/AnimeTheme.php | 1 + app/Models/Wiki/Audio.php | 129 ++ app/Models/Wiki/Video.php | 44 + .../Wiki/Video/BackfillVideoAction.php | 140 ++ app/Nova/Lenses/Audio/AudioVideoLens.php | 114 ++ app/Nova/Lenses/Video/VideoAudioLens.php | 159 +++ app/Nova/Resources/Wiki/Audio.php | 179 +++ app/Nova/Resources/Wiki/Video.php | 39 + app/Pipes/Wiki/Video/BackfillAudio.php | 82 ++ app/Policies/Wiki/AudioPolicy.php | 104 ++ app/Providers/RouteServiceProvider.php | 5 + .../Billing/DigitalOceanBalanceRepository.php | 52 + .../DigitalOceanTransactionRepository.php | 82 +- .../DigitalOcean/DigitalOceanRepository.php | 79 ++ .../Billing/DigitalOceanBalanceRepository.php | 2 + .../DigitalOceanTransactionRepository.php | 2 + .../Eloquent/EloquentRepository.php | 3 + .../Eloquent/Wiki/AudioRepository.php | 59 + .../Eloquent/Wiki/VideoRepository.php | 2 + .../Billing/DigitalOceanBalanceRepository.php | 120 -- .../StorageRepository.php} | 78 +- .../Storage/Wiki/AudioRepository.php | 57 + .../Storage/Wiki/VideoRepository.php | 57 + .../Api/Schema/Wiki/VideoSchema.php | 2 + composer.json | 1 + composer.lock | 1243 +++++++++++++---- config/app.php | 4 + config/audio.php | 36 + config/filesystems.php | 25 + config/flags.php | 15 +- config/laravel-ffmpeg.php | 23 + config/video.php | 12 + database/factories/Wiki/AudioFactory.php | 43 + .../2022_07_05_200425_create_audio_table.php | 43 + ...7_16_214509_add_video_belongs_to_audio.php | 41 + database/seeders/AudioSeeder.php | 36 + database/seeders/BackfillAudioSeeder.php | 30 + database/seeders/DatabaseSeeder.php | 1 + .../seeders/DigitalOceanTransactionSeeder.php | 108 +- database/seeders/PermissionSeeder.php | 7 +- database/seeders/VideoSeeder.php | 108 +- phpstan.neon | 1 + public/vendor/horizon/app.js | 2 +- public/vendor/horizon/mix-manifest.json | 2 +- public/vendor/nova/app.js | 2 +- public/vendor/nova/app.js.map | 2 +- public/vendor/nova/mix-manifest.json | 2 +- public/vendor/telescope/app.js | 2 +- public/vendor/telescope/mix-manifest.json | 2 +- resources/views/layouts/app.blade.php | 6 +- routes/audio.php | 10 + routes/video.php | 7 +- .../Commands/Billing/BalanceReconcileTest.php | 10 +- .../Billing/TransactionReconcileTest.php | 8 +- .../Wiki/Audio/AudioReconcileTest.php | 105 ++ .../Wiki/{ => Video}/VideoReconcileTest.php | 12 +- tests/Feature/Events/Wiki/AudioTest.php | 124 ++ .../Http/Api/Wiki/Audio/AudioDestroyTest.php | 52 + .../Api/Wiki/Audio/AudioForceDeleteTest.php | 52 + .../Http/Api/Wiki/Audio/AudioIndexTest.php | 452 ++++++ .../Http/Api/Wiki/Audio/AudioRestoreTest.php | 56 + .../Http/Api/Wiki/Audio/AudioShowTest.php | 147 ++ .../Http/Api/Wiki/Audio/AudioStoreTest.php | 74 + .../Http/Api/Wiki/Audio/AudioUpdateTest.php | 55 + .../Http/Api/Wiki/Video/VideoIndexTest.php | 2 + .../Http/Api/Wiki/Video/VideoShowTest.php | 2 + tests/Feature/Http/Wiki/AudioTest.php | 110 ++ tests/Feature/Jobs/Wiki/AudioTest.php | 87 ++ tests/Unit/Models/Wiki/AudioTest.php | 82 ++ tests/Unit/Models/Wiki/VideoTest.php | 110 ++ 126 files changed, 6465 insertions(+), 1485 deletions(-) rename app/{Concerns/Repositories/Billing/ReconcilesBalanceRepositories.php => Actions/Repositories/Billing/Balance/ReconcileBalanceRepositories.php} (76%) create mode 100644 app/Actions/Repositories/Billing/Balance/ReconcileBalanceResults.php create mode 100644 app/Actions/Repositories/Billing/Transaction/ReconcileTransactionRepositories.php create mode 100644 app/Actions/Repositories/Billing/Transaction/ReconcileTransactionResults.php create mode 100644 app/Actions/Repositories/ReconcileRepositories.php create mode 100644 app/Actions/Repositories/ReconcileResults.php create mode 100644 app/Actions/Repositories/Wiki/Audio/ReconcileAudioRepositories.php create mode 100644 app/Actions/Repositories/Wiki/Audio/ReconcileAudioResults.php rename app/{Concerns/Repositories/Wiki/ReconcilesVideoRepositories.php => Actions/Repositories/Wiki/Video/ReconcileVideoRepositories.php} (73%) create mode 100644 app/Actions/Repositories/Wiki/Video/ReconcileVideoResults.php create mode 100644 app/Actions/Wiki/Video/BackfillAudio.php delete mode 100644 app/Concerns/Repositories/Billing/ReconcilesTransactionRepositories.php delete mode 100644 app/Concerns/Repositories/ReconcilesRepositories.php create mode 100644 app/Console/Commands/Wiki/Audio/AudioReconcileCommand.php create mode 100644 app/Events/Wiki/Audio/AudioCreated.php create mode 100644 app/Events/Wiki/Audio/AudioDeleted.php create mode 100644 app/Events/Wiki/Audio/AudioRestored.php create mode 100644 app/Events/Wiki/Audio/AudioUpdated.php create mode 100644 app/Http/Api/Field/Config/Flags/FlagsAllowAudioStreamsField.php create mode 100644 app/Http/Api/Field/Wiki/Audio/AudioBasenameField.php create mode 100644 app/Http/Api/Field/Wiki/Audio/AudioFilenameField.php create mode 100644 app/Http/Api/Field/Wiki/Audio/AudioLinkField.php create mode 100644 app/Http/Api/Field/Wiki/Audio/AudioMimeTypeField.php create mode 100644 app/Http/Api/Field/Wiki/Audio/AudioPathField.php create mode 100644 app/Http/Api/Field/Wiki/Audio/AudioSizeField.php create mode 100644 app/Http/Api/Field/Wiki/Video/VideoAudioIdField.php create mode 100644 app/Http/Api/Query/Wiki/Audio/AudioReadQuery.php create mode 100644 app/Http/Api/Query/Wiki/Audio/AudioWriteQuery.php create mode 100644 app/Http/Api/Schema/Wiki/AudioSchema.php create mode 100644 app/Http/Controllers/Api/Wiki/AudioController.php create mode 100644 app/Http/Controllers/Wiki/AudioController.php create mode 100644 app/Http/Middleware/IsAudioStreamingAllowed.php create mode 100644 app/Http/Requests/Api/Wiki/Audio/AudioDestroyRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Audio/AudioForceDeleteRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Audio/AudioIndexRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Audio/AudioRestoreRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Audio/AudioShowRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Audio/AudioStoreRequest.php create mode 100644 app/Http/Requests/Api/Wiki/Audio/AudioUpdateRequest.php create mode 100644 app/Http/Resources/Wiki/Collection/AudioCollection.php create mode 100644 app/Http/Resources/Wiki/Resource/AudioResource.php create mode 100644 app/Models/Wiki/Audio.php create mode 100644 app/Nova/Actions/Wiki/Video/BackfillVideoAction.php create mode 100644 app/Nova/Lenses/Audio/AudioVideoLens.php create mode 100644 app/Nova/Lenses/Video/VideoAudioLens.php create mode 100644 app/Nova/Resources/Wiki/Audio.php create mode 100644 app/Pipes/Wiki/Video/BackfillAudio.php create mode 100644 app/Policies/Wiki/AudioPolicy.php create mode 100644 app/Repositories/DigitalOcean/Billing/DigitalOceanBalanceRepository.php rename app/Repositories/{Service => }/DigitalOcean/Billing/DigitalOceanTransactionRepository.php (51%) create mode 100644 app/Repositories/DigitalOcean/DigitalOceanRepository.php create mode 100644 app/Repositories/Eloquent/Wiki/AudioRepository.php delete mode 100644 app/Repositories/Service/DigitalOcean/Billing/DigitalOceanBalanceRepository.php rename app/Repositories/{Service/DigitalOcean/VideoRepository.php => Storage/StorageRepository.php} (51%) create mode 100644 app/Repositories/Storage/Wiki/AudioRepository.php create mode 100644 app/Repositories/Storage/Wiki/VideoRepository.php create mode 100644 config/audio.php create mode 100644 config/laravel-ffmpeg.php create mode 100644 database/factories/Wiki/AudioFactory.php create mode 100644 database/migrations/2022_07_05_200425_create_audio_table.php create mode 100644 database/migrations/2022_07_16_214509_add_video_belongs_to_audio.php create mode 100644 database/seeders/AudioSeeder.php create mode 100644 database/seeders/BackfillAudioSeeder.php create mode 100644 routes/audio.php create mode 100644 tests/Feature/Console/Commands/Wiki/Audio/AudioReconcileTest.php rename tests/Feature/Console/Commands/Wiki/{ => Video}/VideoReconcileTest.php (89%) create mode 100644 tests/Feature/Events/Wiki/AudioTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Audio/AudioDestroyTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Audio/AudioForceDeleteTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Audio/AudioIndexTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Audio/AudioRestoreTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Audio/AudioShowTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Audio/AudioStoreTest.php create mode 100644 tests/Feature/Http/Api/Wiki/Audio/AudioUpdateTest.php create mode 100644 tests/Feature/Http/Wiki/AudioTest.php create mode 100644 tests/Feature/Jobs/Wiki/AudioTest.php create mode 100644 tests/Unit/Models/Wiki/AudioTest.php diff --git a/.env.example b/.env.example index f15feaec8..4690cffe9 100644 --- a/.env.example +++ b/.env.example @@ -10,6 +10,12 @@ APP_URL=http://localhost ASSET_URL=null APP_KEY= +# audio +AUDIO_DISK=audios +AUDIO_DISK_ROOT= +AUDIO_PATH=/audio +AUDIO_URL= + # audit AUDITING_ENABLED=true @@ -99,8 +105,18 @@ VIDEO_STREAM_READS= VIDEO_DISABLE_ASSERTS= VIDEO_VISIBILITY= +AUDIO_ACCESS_KEY_ID= +AUDIO_SECRET_ACCESS_KEY= +AUDIO_DEFAULT_REGION= +AUDIO_ENDPOINT= +AUDIO_BUCKET= +AUDIO_STREAM_READS= +AUDIO_DISABLE_ASSERTS= +AUDIO_VISIBILITY= + # flags ALLOW_VIDEO_STREAMS=false +ALLOW_AUDIO_STREAMS=false ALLOW_DISCORD_NOTIFICATIONS=false ALLOW_VIEW_RECORDING=false @@ -189,6 +205,8 @@ TELESCOPE_DRIVER=database TELESCOPE_ENABLED=true # video +VIDEO_DISK=videos +VIDEO_DISK_ROOT= VIDEO_PATH=/video VIDEO_URL= diff --git a/app/Concerns/Repositories/Billing/ReconcilesBalanceRepositories.php b/app/Actions/Repositories/Billing/Balance/ReconcileBalanceRepositories.php similarity index 76% rename from app/Concerns/Repositories/Billing/ReconcilesBalanceRepositories.php rename to app/Actions/Repositories/Billing/Balance/ReconcileBalanceRepositories.php index 4fa217482..ab32e6770 100644 --- a/app/Concerns/Repositories/Billing/ReconcilesBalanceRepositories.php +++ b/app/Actions/Repositories/Billing/Balance/ReconcileBalanceRepositories.php @@ -2,9 +2,10 @@ declare(strict_types=1); -namespace App\Concerns\Repositories\Billing; +namespace App\Actions\Repositories\Billing\Balance; -use App\Concerns\Repositories\ReconcilesRepositories; +use App\Actions\Repositories\ReconcileRepositories; +use App\Actions\Repositories\ReconcileResults; use App\Enums\Http\Api\Filter\AllowedDateFormat; use App\Models\Billing\Balance; use Closure; @@ -12,12 +13,12 @@ use Illuminate\Support\Collection; /** - * Trait ReconcilesBalanceRepositories. + * Class ReconcileBalanceRepositories. + * + * @extends ReconcileRepositories */ -trait ReconcilesBalanceRepositories +class ReconcileBalanceRepositories extends ReconcileRepositories { - use ReconcilesRepositories; - /** * The columns used for create and delete set operations. * @@ -85,4 +86,17 @@ protected function resolveUpdatedModel(Collection $sourceModels, Model $destinat fn (Balance $balance) => $balance->date->format(AllowedDateFormat::YM) === $formattedDestinationDate ); } + + /** + * Get reconciliation results. + * + * @param Collection $created + * @param Collection $deleted + * @param Collection $updated + * @return ReconcileResults + */ + protected function getResults(Collection $created, Collection $deleted, Collection $updated): ReconcileResults + { + return new ReconcileBalanceResults($created, $deleted, $updated); + } } diff --git a/app/Actions/Repositories/Billing/Balance/ReconcileBalanceResults.php b/app/Actions/Repositories/Billing/Balance/ReconcileBalanceResults.php new file mode 100644 index 000000000..fd30022a6 --- /dev/null +++ b/app/Actions/Repositories/Billing/Balance/ReconcileBalanceResults.php @@ -0,0 +1,26 @@ + + */ +class ReconcileBalanceResults extends ReconcileResults +{ + /** + * Get the model of the reconciliation results. + * + * @return class-string + */ + protected function model(): string + { + return Balance::class; + } +} diff --git a/app/Actions/Repositories/Billing/Transaction/ReconcileTransactionRepositories.php b/app/Actions/Repositories/Billing/Transaction/ReconcileTransactionRepositories.php new file mode 100644 index 000000000..7d72d656a --- /dev/null +++ b/app/Actions/Repositories/Billing/Transaction/ReconcileTransactionRepositories.php @@ -0,0 +1,93 @@ + + */ +class ReconcileTransactionRepositories extends ReconcileRepositories +{ + /** + * The columns used for create and delete set operations. + * + * @return string[] + */ + protected function columnsForCreateDelete(): array + { + return [ + Transaction::ATTRIBUTE_AMOUNT, + Transaction::ATTRIBUTE_DATE, + Transaction::ATTRIBUTE_EXTERNAL_ID, + Transaction::ATTRIBUTE_ID, + Transaction::ATTRIBUTE_SERVICE, + ]; + } + + /** + * Callback for create and delete set operation item comparison. + * + * @return Closure + */ + protected function diffCallbackForCreateDelete(): Closure + { + return fn (Transaction $first, Transaction $second) => [$first->external_id, $first->date->format(AllowedDateFormat::YMD), $first->amount] + <=> [$second->external_id, $second->date->format(AllowedDateFormat::YMD), $second->amount]; + } + + /** + * The columns used for update set operation. + * + * @return string[] + */ + protected function columnsForUpdate(): array + { + return ['*']; + } + + /** + * Callback for update set operation item comparison. + * + * @return Closure + */ + protected function diffCallbackForUpdate(): Closure + { + return fn () => 0; + } + + /** + * Get source model that has been updated for destination model. + * + * @param Collection $sourceModels + * @param Model $destinationModel + * @return Model|null + */ + protected function resolveUpdatedModel(Collection $sourceModels, Model $destinationModel): ?Model + { + return null; + } + + /** + * Get reconciliation results. + * + * @param Collection $created + * @param Collection $deleted + * @param Collection $updated + * @return ReconcileResults + */ + protected function getResults(Collection $created, Collection $deleted, Collection $updated): ReconcileResults + { + return new ReconcileTransactionResults($created, $deleted, $updated); + } +} diff --git a/app/Actions/Repositories/Billing/Transaction/ReconcileTransactionResults.php b/app/Actions/Repositories/Billing/Transaction/ReconcileTransactionResults.php new file mode 100644 index 000000000..31d5fcf7b --- /dev/null +++ b/app/Actions/Repositories/Billing/Transaction/ReconcileTransactionResults.php @@ -0,0 +1,26 @@ + + */ +class ReconcileTransactionResults extends ReconcileResults +{ + /** + * Get the model of the reconciliation results. + * + * @return class-string + */ + protected function model(): string + { + return Transaction::class; + } +} diff --git a/app/Actions/Repositories/ReconcileRepositories.php b/app/Actions/Repositories/ReconcileRepositories.php new file mode 100644 index 000000000..4d484e416 --- /dev/null +++ b/app/Actions/Repositories/ReconcileRepositories.php @@ -0,0 +1,148 @@ + $source + * @param RepositoryInterface $destination + * @return ReconcileResults + */ + public function reconcileRepositories(RepositoryInterface $source, RepositoryInterface $destination): ReconcileResults + { + $sourceModels = $source->get(); + + $destinationModels = $destination->get($this->columnsForCreateDelete()); + + $created = $this->createModelsFromSource($destination, $sourceModels, $destinationModels); + + $deleted = $this->deleteModelsFromDestination($destination, $sourceModels, $destinationModels); + + $destinationModels = $destination->get($this->columnsForUpdate()); + + $updated = $this->updateDestinationModels($destination, $sourceModels, $destinationModels); + + return $this->getResults($created, $deleted, $updated); + } + + /** + * The columns used for create and delete set operations. + * + * @return string[] + */ + abstract protected function columnsForCreateDelete(): array; + + /** + * Callback for create and delete set operation item comparison. + * + * @return Closure + */ + abstract protected function diffCallbackForCreateDelete(): Closure; + + /** + * Create models that exist in source but not in destination. + * + * @param RepositoryInterface $destination + * @param Collection $sourceModels + * @param Collection $destinationModels + * @return Collection + */ + protected function createModelsFromSource( + RepositoryInterface $destination, + Collection $sourceModels, + Collection $destinationModels + ): Collection { + $createModels = $sourceModels->diffUsing($destinationModels, $this->diffCallbackForCreateDelete()); + + return $createModels->each(fn (Model $createModel) => $destination->save($createModel)); + } + + /** + * Delete models that exist in destination but not in source. + * + * @param RepositoryInterface $destination + * @param Collection $sourceModels + * @param Collection $destinationModels + * @return Collection + */ + protected function deleteModelsFromDestination( + RepositoryInterface $destination, + Collection $sourceModels, + Collection $destinationModels + ): Collection { + $deleteModels = $destinationModels->diffUsing($sourceModels, $this->diffCallbackForCreateDelete()); + + return $deleteModels->each(fn (Model $deleteModel) => $destination->delete($deleteModel)); + } + + /** + * The columns used for update set operation. + * + * @return string[] + */ + abstract protected function columnsForUpdate(): array; + + /** + * Callback for update set operation item comparison. + * + * @return Closure + */ + abstract protected function diffCallbackForUpdate(): Closure; + + /** + * Get source model that has been updated for destination model. + * + * @param Collection $sourceModels + * @param Model $destinationModel + * @return Model|null + */ + abstract protected function resolveUpdatedModel(Collection $sourceModels, Model $destinationModel): ?Model; + + /** + * Update destination models that have changed in source. + * + * @param RepositoryInterface $destination + * @param Collection $sourceModels + * @param Collection $destinationModels + * @return Collection + */ + protected function updateDestinationModels( + RepositoryInterface $destination, + Collection $sourceModels, + Collection $destinationModels + ): Collection { + $updatedModels = $destinationModels->diffUsing($sourceModels, $this->diffCallbackForUpdate()); + + return $updatedModels->each(function (Model $updatedModel) use ($sourceModels, $destination) { + $sourceModel = $this->resolveUpdatedModel($sourceModels, $updatedModel); + if ($sourceModel !== null) { + $destination->update($updatedModel, $sourceModel->toArray()); + } + }); + } + + /** + * Get reconciliation results. + * + * @param Collection $created + * @param Collection $deleted + * @param Collection $updated + * @return ReconcileResults + */ + abstract protected function getResults(Collection $created, Collection $deleted, Collection $updated): ReconcileResults; +} diff --git a/app/Actions/Repositories/ReconcileResults.php b/app/Actions/Repositories/ReconcileResults.php new file mode 100644 index 000000000..ba4cea211 --- /dev/null +++ b/app/Actions/Repositories/ReconcileResults.php @@ -0,0 +1,109 @@ +created; + } + + /** + * Determines if any successful changes were made during reconciliation. + * + * @return bool + */ + protected function hasChanges(): bool + { + return $this->created->isNotEmpty() || $this->deleted->isNotEmpty() || $this->updated->isNotEmpty(); + } + + /** + * Write reconcile results to log. + * + * @return void + */ + public function toLog(): void + { + $this->created->each(fn (BaseModel $model) => Log::info("{$this->label()} '{$model->getName()}' created")); + $this->deleted->each(fn (BaseModel $model) => Log::info("{$this->label()} '{$model->getName()}' deleted")); + $this->updated->each(fn (BaseModel $model) => Log::info("{$this->label()} '{$model->getName()}' updated")); + + if ($this->hasChanges()) { + Log::info("{$this->created->count()} {$this->label($this->created)} created, {$this->deleted->count()} {$this->label($this->deleted)} deleted, {$this->updated->count()} {$this->label($this->updated)} updated"); + } else { + Log::info("No {$this->label($this->created)} created or deleted or updated"); + } + } + + /** + * Write reconcile results to console output. + * + * @param Command $command + * @return void + */ + public function toConsole(Command $command): void + { + $this->created->each(fn (BaseModel $model) => $command->info("{$this->label()} '{$model->getName()}' created")); + $this->deleted->each(fn (BaseModel $model) => $command->info("{$this->label()} '{$model->getName()}' deleted")); + $this->updated->each(fn (BaseModel $model) => $command->info("{$this->label()} '{$model->getName()}' updated")); + + if ($this->hasChanges()) { + $command->info("{$this->created->count()} {$this->label($this->created)} created, {$this->deleted->count()} {$this->label($this->deleted)} deleted, {$this->updated->count()} {$this->label($this->updated)} updated"); + } else { + $command->info("No {$this->label($this->created)} created or deleted or updated"); + } + } + + /** + * Get the model of the reconciliation results. + * + * @return class-string + */ + abstract protected function model(): string; + + /** + * Get the user-friendly label for the model class name. + * + * @param int|array|Countable $models + * @return string + */ + protected function label(int|array|Countable $models = 1): string + { + return Str::plural(class_basename($this->model()), $models); + } +} diff --git a/app/Actions/Repositories/Wiki/Audio/ReconcileAudioRepositories.php b/app/Actions/Repositories/Wiki/Audio/ReconcileAudioRepositories.php new file mode 100644 index 000000000..4097c3635 --- /dev/null +++ b/app/Actions/Repositories/Wiki/Audio/ReconcileAudioRepositories.php @@ -0,0 +1,96 @@ + + */ +class ReconcileAudioRepositories extends ReconcileRepositories +{ + /** + * The columns used for create and delete set operations. + * + * @return string[] + */ + protected function columnsForCreateDelete(): array + { + return [ + Audio::ATTRIBUTE_BASENAME, + Audio::ATTRIBUTE_ID, + ]; + } + + /** + * Callback for create and delete set operation item comparison. + * + * @return Closure + */ + protected function diffCallbackForCreateDelete(): Closure + { + return fn (Audio $first, Audio $second) => $first->basename <=> $second->basename; + } + + /** + * The columns used for update set operation. + * + * @return string[] + */ + protected function columnsForUpdate(): array + { + return [ + Audio::ATTRIBUTE_BASENAME, + Audio::ATTRIBUTE_ID, + Audio::ATTRIBUTE_PATH, + Audio::ATTRIBUTE_SIZE, + ]; + } + + /** + * Callback for update set operation item comparison. + * + * @return Closure + */ + protected function diffCallbackForUpdate(): Closure + { + return fn (Audio $first, Audio $second) => [$first->basename, $first->path, $first->size] <=> [$second->basename, $second->path, $second->size]; + } + + /** + * Get source model that has been updated for destination model. + * + * @param Collection $sourceModels + * @param Model $destinationModel + * @return Model|null + */ + protected function resolveUpdatedModel(Collection $sourceModels, Model $destinationModel): ?Model + { + return $sourceModels->firstWhere( + Audio::ATTRIBUTE_BASENAME, + $destinationModel->getAttribute(Audio::ATTRIBUTE_BASENAME) + ); + } + + /** + * Get reconciliation results. + * + * @param Collection $created + * @param Collection $deleted + * @param Collection $updated + * @return ReconcileResults + */ + protected function getResults(Collection $created, Collection $deleted, Collection $updated): ReconcileResults + { + return new ReconcileAudioResults($created, $deleted, $updated); + } +} diff --git a/app/Actions/Repositories/Wiki/Audio/ReconcileAudioResults.php b/app/Actions/Repositories/Wiki/Audio/ReconcileAudioResults.php new file mode 100644 index 000000000..3adfb6911 --- /dev/null +++ b/app/Actions/Repositories/Wiki/Audio/ReconcileAudioResults.php @@ -0,0 +1,26 @@ + + */ +class ReconcileAudioResults extends ReconcileResults +{ + /** + * Get the model of the reconciliation results. + * + * @return class-string