mirror of
https://github.com/sp-tarkov/forge.git
synced 2025-02-12 12:10:41 -05:00
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.
This commit is contained in:
parent
a1504fe622
commit
c1a58e29dd
@ -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);
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -11,11 +11,6 @@ class License extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'link',
|
||||
];
|
||||
|
||||
public function mods(): HasMany
|
||||
{
|
||||
return $this->hasMany(Mod::class);
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -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(),
|
||||
|
@ -1,7 +1,6 @@
|
||||
<?php
|
||||
|
||||
use App\Models\License;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
@ -16,10 +15,6 @@ return new class extends Migration
|
||||
->nullable()
|
||||
->default(null)
|
||||
->unique();
|
||||
$table->foreignIdFor(User::class)
|
||||
->constrained('users')
|
||||
->cascadeOnDelete()
|
||||
->cascadeOnUpdate();
|
||||
$table->string('name');
|
||||
$table->string('slug');
|
||||
$table->string('teaser');
|
||||
|
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('mod_user', function (Blueprint $table) {
|
||||
$table->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');
|
||||
}
|
||||
};
|
@ -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();
|
||||
|
@ -24,7 +24,7 @@
|
||||
{{ $mod->{$versionScope}->sptVersion->version }}
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-sm italic text-slate-600 dark:text-gray-200">By {{ $mod->user->name }}</p>
|
||||
<p class="text-sm italic text-slate-600 dark:text-gray-200">By {{ $mod->users->pluck('name')->implode(', ') }}</p>
|
||||
<p class="mt-2 text-slate-500 dark:text-gray-300">{{ $mod->teaser }}</p>
|
||||
</div>
|
||||
<x-mod-list-stats :mod="$mod" :modVersion="$mod->{$versionScope}"/>
|
||||
|
@ -25,7 +25,7 @@
|
||||
{{ $mod->latestSptVersion->version }}
|
||||
</span>
|
||||
</h2>
|
||||
<p>{{ __('Created by') }} {{ $mod->user->name }}</p>
|
||||
<p>{{ __('Created by') }} {{ $mod->users->pluck('name')->implode(', ') }}</p>
|
||||
<p>{{ $mod->latestSptVersion->sptVersion->version }} {{ __('Compatible') }}</p>
|
||||
<p>{{ $mod->total_downloads }} {{ __('Downloads') }}</p>
|
||||
</div>
|
||||
|
Loading…
x
Reference in New Issue
Block a user