From c1a58e29dd4733b1a81069f2c08ede59f6d42fe0 Mon Sep 17 00:00:00 2001 From: Refringe Date: Thu, 4 Jul 2024 22:10:48 -0400 Subject: [PATCH] Multiple Mod Authors The mod-to-user relationship has been modified to be a many-to-many relationship. Mods can now have multiple authors, and an author can have multiple mods. --- app/Http/Controllers/ModController.php | 2 +- app/Http/Resources/Api/V0/ModResource.php | 29 +++++++++++-------- app/Http/Resources/ModResource.php | 1 - app/Models/License.php | 5 ---- app/Models/Mod.php | 28 ++++++++++-------- app/Models/ModVersion.php | 9 ------ app/Models/SptVersion.php | 5 ---- app/Models/User.php | 12 ++------ app/Models/UserRole.php | 7 ----- app/Providers/AppServiceProvider.php | 4 +++ app/View/Components/ModListSection.php | 26 ++++++++--------- database/factories/ModFactory.php | 3 +- .../2024_05_15_022710_create_mods_table.php | 5 ---- ...24_05_15_023706_create_mod_users_table.php | 29 +++++++++++++++++++ database/seeders/DatabaseSeeder.php | 7 ++++- resources/views/components/mod-list.blade.php | 2 +- resources/views/mod/show.blade.php | 2 +- 17 files changed, 92 insertions(+), 84 deletions(-) create mode 100644 database/migrations/2024_05_15_023706_create_mod_users_table.php diff --git a/app/Http/Controllers/ModController.php b/app/Http/Controllers/ModController.php index 1cad7c3..3f29240 100644 --- a/app/Http/Controllers/ModController.php +++ b/app/Http/Controllers/ModController.php @@ -30,7 +30,7 @@ class ModController extends Controller $mod = Mod::select() ->withLatestSptVersion() ->withTotalDownloads() - ->with('user:id,name') + ->with('users:id,name') ->with('license:id,name,link') ->find($modId); diff --git a/app/Http/Resources/Api/V0/ModResource.php b/app/Http/Resources/Api/V0/ModResource.php index e73ff0c..746671b 100644 --- a/app/Http/Resources/Api/V0/ModResource.php +++ b/app/Http/Resources/Api/V0/ModResource.php @@ -20,22 +20,27 @@ class ModResource extends JsonResource 'attributes' => [ 'name' => $this->name, 'slug' => $this->slug, + 'teaser' => $this->teaser, 'description' => $this->when( $request->routeIs('api.v0.mods.show'), $this->description ), - 'source_code_link' => $this->source_code_link, - 'user_id' => $this->user_id, 'license_id' => $this->license_id, + 'source_code_link' => $this->source_code_link, + 'featured' => $this->featured, + 'contains_ai_content' => $this->contains_ai_content, + 'contains_ads' => $this->contains_ads, 'created_at' => $this->created_at, + 'updated_at' => $this->updated_at, ], 'relationships' => [ - 'user' => [ - 'data' => [ + 'users' => [ + 'data' => $this->users->map(fn ($user) => [ 'type' => 'user', - 'id' => $this->user_id, - ], - // TODO: Provide 'links.self' to user profile: + 'id' => $user->id, + ])->toArray(), + + // TODO: Provide 'links.self' to user profile //'links' => ['self' => '#'], ], 'license' => [ @@ -45,11 +50,11 @@ class ModResource extends JsonResource ], ], ], - 'included' => [ - new UserResource($this->user), - // TODO: Provide 'included' data for attached 'license': - //new LicenseResource($this->license), - ], + 'included' => $this->users->map(fn ($user) => new UserResource($user)), + + // TODO: Provide 'included' data for attached 'license': + //new LicenseResource($this->license) + 'links' => [ 'self' => route('mod.show', [ 'mod' => $this->id, diff --git a/app/Http/Resources/ModResource.php b/app/Http/Resources/ModResource.php index f61b2c6..0ab080b 100644 --- a/app/Http/Resources/ModResource.php +++ b/app/Http/Resources/ModResource.php @@ -16,7 +16,6 @@ class ModResource extends JsonResource 'slug' => $this->slug, 'description' => $this->description, 'source_code_link' => $this->source_code_link, - 'user_id' => $this->user_id, 'license_id' => $this->license_id, 'license' => new LicenseResource($this->whenLoaded('license')), 'created_at' => $this->created_at, diff --git a/app/Models/License.php b/app/Models/License.php index 2c8363d..d67c71e 100644 --- a/app/Models/License.php +++ b/app/Models/License.php @@ -11,11 +11,6 @@ class License extends Model { use HasFactory, SoftDeletes; - protected $fillable = [ - 'name', - 'link', - ]; - public function mods(): HasMany { return $this->hasMany(Mod::class); diff --git a/app/Models/Mod.php b/app/Models/Mod.php index a6344be..97eed4b 100644 --- a/app/Models/Mod.php +++ b/app/Models/Mod.php @@ -7,6 +7,7 @@ use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Facades\Storage; @@ -20,25 +21,18 @@ class Mod extends Model { use HasFactory, Searchable, SoftDeletes; - protected $fillable = [ - 'user_id', - 'name', - 'slug', - 'teaser', - 'description', - 'license_id', - 'source_code_link', - ]; - protected static function booted(): void { // Apply the global scope to exclude disabled mods. static::addGlobalScope(new DisabledScope); } - public function user(): BelongsTo + /** + * The users that belong to the mod. + */ + public function users(): BelongsToMany { - return $this->belongsTo(User::class); + return $this->belongsToMany(User::class); } public function license(): BelongsTo @@ -155,6 +149,16 @@ class Mod extends Model }; } + protected function casts(): array + { + return [ + 'featured' => 'boolean', + 'contains_ai_content' => 'boolean', + 'contains_ads' => 'boolean', + 'disabled' => 'boolean', + ]; + } + /** * Ensure the slug is always lower case when retrieved and slugified when saved. */ diff --git a/app/Models/ModVersion.php b/app/Models/ModVersion.php index 3ec3b8f..a167039 100644 --- a/app/Models/ModVersion.php +++ b/app/Models/ModVersion.php @@ -12,15 +12,6 @@ class ModVersion extends Model { use HasFactory, SoftDeletes; - protected $fillable = [ - 'mod_id', - 'version', - 'description', - 'spt_version_id', - 'virus_total_link', - 'downloads', - ]; - protected static function booted(): void { static::addGlobalScope(new DisabledScope); diff --git a/app/Models/SptVersion.php b/app/Models/SptVersion.php index f261e43..518a308 100644 --- a/app/Models/SptVersion.php +++ b/app/Models/SptVersion.php @@ -11,11 +11,6 @@ class SptVersion extends Model { use HasFactory, SoftDeletes; - protected $fillable = [ - 'version', - 'color_class', - ]; - public function modVersions(): HasMany { return $this->hasMany(ModVersion::class); diff --git a/app/Models/User.php b/app/Models/User.php index 841a3c8..248fd04 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -5,7 +5,7 @@ namespace App\Models; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; -use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Illuminate\Support\Str; @@ -25,12 +25,6 @@ class User extends Authenticatable implements MustVerifyEmail use Searchable; use TwoFactorAuthenticatable; - protected $fillable = [ - 'name', - 'email', - 'password', - ]; - protected $hidden = [ 'password', 'remember_token', @@ -42,9 +36,9 @@ class User extends Authenticatable implements MustVerifyEmail 'profile_photo_url', ]; - public function mods(): HasMany + public function mods(): BelongsToMany { - return $this->hasMany(Mod::class); + return $this->belongsToMany(Mod::class); } public function toSearchableArray(): array diff --git a/app/Models/UserRole.php b/app/Models/UserRole.php index b496c20..df600ef 100644 --- a/app/Models/UserRole.php +++ b/app/Models/UserRole.php @@ -10,13 +10,6 @@ class UserRole extends Model { use HasFactory; - protected $fillable = [ - 'name', - 'short_name', - 'description', - 'color_class', - ]; - public function users(): HasMany { return $this->hasMany(User::class); diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 9d28d7a..dd2a10a 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -3,6 +3,7 @@ namespace App\Providers; use App\Models\User; +use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Gate; use Illuminate\Support\ServiceProvider; @@ -21,6 +22,9 @@ class AppServiceProvider extends ServiceProvider */ public function boot(): void { + // Allow mass assignment for all models. Be careful! + Model::unguard(); + // This gate determines who can access the Pulse dashboard. Gate::define('viewPulse', function (User $user) { return $user->isAdmin(); diff --git a/app/View/Components/ModListSection.php b/app/View/Components/ModListSection.php index 2332a9a..82335a7 100644 --- a/app/View/Components/ModListSection.php +++ b/app/View/Components/ModListSection.php @@ -24,10 +24,10 @@ class ModListSection extends Component private function fetchFeaturedMods(): Collection { - return Mod::select(['id', 'user_id', 'name', 'slug', 'teaser', 'thumbnail', 'featured']) + return Mod::select(['id', 'name', 'slug', 'teaser', 'thumbnail', 'featured']) ->withLatestSptVersion() ->withTotalDownloads() - ->with('user:id,name') + ->with('users:id,name') ->where('featured', true) ->latest() ->limit(6) @@ -36,10 +36,10 @@ class ModListSection extends Component private function fetchLatestMods(): Collection { - return Mod::select(['id', 'user_id', 'name', 'slug', 'teaser', 'thumbnail', 'featured', 'created_at']) + return Mod::select(['id', 'name', 'slug', 'teaser', 'thumbnail', 'featured', 'created_at']) ->withLatestSptVersion() ->withTotalDownloads() - ->with('user:id,name') + ->with('users:id,name') ->latest() ->limit(6) ->get(); @@ -47,15 +47,22 @@ class ModListSection extends Component private function fetchUpdatedMods(): Collection { - return Mod::select(['id', 'user_id', 'name', 'slug', 'teaser', 'thumbnail', 'featured']) + return Mod::select(['id', 'name', 'slug', 'teaser', 'thumbnail', 'featured']) ->withLastUpdatedVersion() ->withTotalDownloads() - ->with('user:id,name') + ->with('users:id,name') ->latest() ->limit(6) ->get(); } + public function render(): View + { + return view('components.mod-list-section', [ + 'sections' => $this->getSections(), + ]); + } + public function getSections(): array { return [ @@ -76,11 +83,4 @@ class ModListSection extends Component ], ]; } - - public function render(): View - { - return view('components.mod-list-section', [ - 'sections' => $this->getSections(), - ]); - } } diff --git a/database/factories/ModFactory.php b/database/factories/ModFactory.php index 0b8ca64..aef197b 100644 --- a/database/factories/ModFactory.php +++ b/database/factories/ModFactory.php @@ -4,7 +4,6 @@ namespace Database\Factories; use App\Models\License; use App\Models\Mod; -use App\Models\User; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Str; use Random\RandomException; @@ -18,10 +17,10 @@ class ModFactory extends Factory */ public function definition(): array { + $name = fake()->catchPhrase(); return [ - 'user_id' => User::factory(), 'name' => $name, 'slug' => Str::slug($name), 'teaser' => fake()->sentence(), diff --git a/database/migrations/2024_05_15_022710_create_mods_table.php b/database/migrations/2024_05_15_022710_create_mods_table.php index e4b0477..cbe04b6 100644 --- a/database/migrations/2024_05_15_022710_create_mods_table.php +++ b/database/migrations/2024_05_15_022710_create_mods_table.php @@ -1,7 +1,6 @@ nullable() ->default(null) ->unique(); - $table->foreignIdFor(User::class) - ->constrained('users') - ->cascadeOnDelete() - ->cascadeOnUpdate(); $table->string('name'); $table->string('slug'); $table->string('teaser'); diff --git a/database/migrations/2024_05_15_023706_create_mod_users_table.php b/database/migrations/2024_05_15_023706_create_mod_users_table.php new file mode 100644 index 0000000..ad10a91 --- /dev/null +++ b/database/migrations/2024_05_15_023706_create_mod_users_table.php @@ -0,0 +1,29 @@ +id(); + $table->foreignId('mod_id')->constrained()->cascadeOnUpdate()->cascadeOnDelete(); + $table->foreignId('user_id')->constrained()->cascadeOnUpdate()->cascadeOnDelete(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('mod_user'); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 68b0148..264a204 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -38,7 +38,12 @@ class DatabaseSeeder extends Seeder $users = User::factory(100)->create(); // Add 200 mods, assigning them to the users we just created. - $mods = Mod::factory(200)->recycle([$users, $licenses])->create(); + $allUsers = $users->merge([$administrator, $moderator]); + $mods = Mod::factory(200)->recycle([$licenses])->create(); + foreach ($mods as $mod) { + $userIds = $allUsers->random(rand(1, 3))->pluck('id')->toArray(); + $mod->users()->attach($userIds); + } // Add 1000 mod versions, assigning them to the mods we just created. ModVersion::factory(1000)->recycle([$mods, $spt_versions])->create(); diff --git a/resources/views/components/mod-list.blade.php b/resources/views/components/mod-list.blade.php index 6e3c13e..f8ba548 100644 --- a/resources/views/components/mod-list.blade.php +++ b/resources/views/components/mod-list.blade.php @@ -24,7 +24,7 @@ {{ $mod->{$versionScope}->sptVersion->version }} -

By {{ $mod->user->name }}

+

By {{ $mod->users->pluck('name')->implode(', ') }}

{{ $mod->teaser }}

diff --git a/resources/views/mod/show.blade.php b/resources/views/mod/show.blade.php index 06c4eb2..3d8012a 100644 --- a/resources/views/mod/show.blade.php +++ b/resources/views/mod/show.blade.php @@ -25,7 +25,7 @@ {{ $mod->latestSptVersion->version }} -

{{ __('Created by') }} {{ $mod->user->name }}

+

{{ __('Created by') }} {{ $mod->users->pluck('name')->implode(', ') }}

{{ $mod->latestSptVersion->sptVersion->version }} {{ __('Compatible') }}

{{ $mod->total_downloads }} {{ __('Downloads') }}