mirror of
https://github.com/sp-tarkov/forge.git
synced 2025-02-13 04:30:41 -05:00
Merge branch 'mod-dependencies' into develop
This commit is contained in:
commit
c6fcca6063
10
app/Exceptions/CircularDependencyException.php
Normal file
10
app/Exceptions/CircularDependencyException.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class CircularDependencyException extends Exception
|
||||
{
|
||||
protected $message = 'Circular dependency detected.';
|
||||
}
|
@ -31,20 +31,23 @@ class ModController extends Controller
|
||||
->with([
|
||||
'versions',
|
||||
'versions.sptVersion',
|
||||
'latestVersion',
|
||||
'latestVersion.sptVersion',
|
||||
'versions.dependencies',
|
||||
'versions.dependencies.resolvedVersion',
|
||||
'versions.dependencies.resolvedVersion.mod',
|
||||
'users:id,name',
|
||||
'license:id,name,link',
|
||||
])
|
||||
->find($modId);
|
||||
->findOrFail($modId);
|
||||
|
||||
if (! $mod || $mod->slug !== $slug) {
|
||||
if ($mod->slug !== $slug) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
$this->authorize('view', $mod);
|
||||
|
||||
return view('mod.show', compact('mod'));
|
||||
$latestVersion = $mod->versions->sortByDesc('created_at')->first();
|
||||
|
||||
return view('mod.show', compact(['mod', 'latestVersion']));
|
||||
}
|
||||
|
||||
public function update(ModRequest $request, Mod $mod)
|
||||
|
@ -32,6 +32,7 @@ class ModResource extends JsonResource
|
||||
'contains_ads' => $this->contains_ads,
|
||||
'created_at' => $this->created_at,
|
||||
'updated_at' => $this->updated_at,
|
||||
'published_at' => $this->published_at,
|
||||
],
|
||||
'relationships' => [
|
||||
'users' => [
|
||||
|
@ -20,6 +20,7 @@ class ModResource extends JsonResource
|
||||
'license' => new LicenseResource($this->whenLoaded('license')),
|
||||
'created_at' => $this->created_at,
|
||||
'updated_at' => $this->updated_at,
|
||||
'published_at' => $this->published_at,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ class ModVersionResource extends JsonResource
|
||||
return [
|
||||
'created_at' => $this->created_at,
|
||||
'updated_at' => $this->updated_at,
|
||||
'published_at' => $this->published_at,
|
||||
'id' => $this->id,
|
||||
'version' => $this->version,
|
||||
'description' => $this->description,
|
||||
|
@ -54,6 +54,8 @@ class ImportHubData implements ShouldBeUnique, ShouldQueue
|
||||
|
||||
// Re-sync search.
|
||||
Artisan::call('app:search-sync');
|
||||
|
||||
Artisan::call('cache:clear');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -540,6 +542,7 @@ class ImportHubData implements ShouldBeUnique, ShouldQueue
|
||||
'contains_ai_content' => (bool) $optionContainsAi?->contains_ai,
|
||||
'contains_ads' => (bool) $optionContainsAds?->contains_ads,
|
||||
'disabled' => (bool) $mod->isDisabled,
|
||||
'published_at' => Carbon::parse($mod->time, 'UTC'),
|
||||
'created_at' => Carbon::parse($mod->time, 'UTC'),
|
||||
'updated_at' => Carbon::parse($mod->lastChangeTime, 'UTC'),
|
||||
];
|
||||
@ -549,7 +552,7 @@ class ImportHubData implements ShouldBeUnique, ShouldQueue
|
||||
// Remove the user_id from the mod data before upserting.
|
||||
$insertModData = array_map(fn ($mod) => Arr::except($mod, 'users'), $modData);
|
||||
|
||||
Mod::upsert($insertModData, ['hub_id'], [
|
||||
Mod::withoutGlobalScopes()->upsert($insertModData, ['hub_id'], [
|
||||
'name',
|
||||
'slug',
|
||||
'teaser',
|
||||
@ -561,6 +564,7 @@ class ImportHubData implements ShouldBeUnique, ShouldQueue
|
||||
'contains_ai_content',
|
||||
'contains_ads',
|
||||
'disabled',
|
||||
'published_at',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
]);
|
||||
@ -582,7 +586,7 @@ class ImportHubData implements ShouldBeUnique, ShouldQueue
|
||||
{
|
||||
// Alright, hear me out... Shut up.
|
||||
|
||||
$converter = new HtmlConverter();
|
||||
$converter = new HtmlConverter;
|
||||
$clean = Purify::clean($dirty);
|
||||
|
||||
return $converter->convert($clean);
|
||||
@ -678,13 +682,14 @@ class ImportHubData implements ShouldBeUnique, ShouldQueue
|
||||
'virus_total_link' => $optionVirusTotal?->virus_total_link ?? '',
|
||||
'downloads' => max((int) $version->downloads, 0), // At least 0.
|
||||
'disabled' => (bool) $version->isDisabled,
|
||||
'published_at' => Carbon::parse($version->uploadTime, 'UTC'),
|
||||
'created_at' => Carbon::parse($version->uploadTime, 'UTC'),
|
||||
'updated_at' => Carbon::parse($version->uploadTime, 'UTC'),
|
||||
];
|
||||
}
|
||||
|
||||
if (! empty($insertData)) {
|
||||
ModVersion::upsert($insertData, ['hub_id'], [
|
||||
ModVersion::withoutGlobalScopes()->upsert($insertData, ['hub_id'], [
|
||||
'mod_id',
|
||||
'version',
|
||||
'description',
|
||||
@ -692,6 +697,7 @@ class ImportHubData implements ShouldBeUnique, ShouldQueue
|
||||
'spt_version_id',
|
||||
'virus_total_link',
|
||||
'downloads',
|
||||
'published_at',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
]);
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Scopes\DisabledScope;
|
||||
use App\Models\Scopes\PublishedScope;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
@ -16,6 +17,8 @@ use Illuminate\Support\Str;
|
||||
use Laravel\Scout\Searchable;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string $slug
|
||||
*/
|
||||
class Mod extends Model
|
||||
@ -26,6 +29,8 @@ class Mod extends Model
|
||||
{
|
||||
// Apply the global scope to exclude disabled mods.
|
||||
static::addGlobalScope(new DisabledScope);
|
||||
// Apply the global scope to exclude non-published mods.
|
||||
static::addGlobalScope(new PublishedScope);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -79,6 +84,7 @@ class Mod extends Model
|
||||
'featured' => $this->featured,
|
||||
'created_at' => strtotime($this->created_at),
|
||||
'updated_at' => strtotime($this->updated_at),
|
||||
'published_at' => strtotime($this->published_at),
|
||||
'latestVersion' => $latestVersion?->sptVersion->version,
|
||||
'latestVersionColorClass' => $latestVersion?->sptVersion->color_class,
|
||||
];
|
||||
|
43
app/Models/ModDependency.php
Normal file
43
app/Models/ModDependency.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $mod_version_id
|
||||
* @property int $dependency_mod_id
|
||||
* @property string $version_constraint
|
||||
* @property int|null $resolved_version_id
|
||||
*/
|
||||
class ModDependency extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The relationship between a mod dependency and mod version.
|
||||
*/
|
||||
public function modVersion(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(ModVersion::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* The relationship between a mod dependency and mod.
|
||||
*/
|
||||
public function dependencyMod(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Mod::class, 'dependency_mod_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* The relationship between a mod dependency and resolved mod version.
|
||||
*/
|
||||
public function resolvedVersion(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(ModVersion::class, 'resolved_version_id');
|
||||
}
|
||||
}
|
@ -3,11 +3,18 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Scopes\DisabledScope;
|
||||
use App\Models\Scopes\PublishedScope;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $mod_id
|
||||
* @property string $version
|
||||
*/
|
||||
class ModVersion extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
@ -15,6 +22,7 @@ class ModVersion extends Model
|
||||
protected static function booted(): void
|
||||
{
|
||||
static::addGlobalScope(new DisabledScope);
|
||||
static::addGlobalScope(new PublishedScope);
|
||||
}
|
||||
|
||||
public function mod(): BelongsTo
|
||||
@ -22,6 +30,14 @@ class ModVersion extends Model
|
||||
return $this->belongsTo(Mod::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* The relationship between a mod version and its dependencies.
|
||||
*/
|
||||
public function dependencies(): HasMany
|
||||
{
|
||||
return $this->hasMany(ModDependency::class);
|
||||
}
|
||||
|
||||
public function sptVersion(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(SptVersion::class);
|
||||
|
19
app/Models/Scopes/PublishedScope.php
Normal file
19
app/Models/Scopes/PublishedScope.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Scopes;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Scope;
|
||||
|
||||
class PublishedScope implements Scope
|
||||
{
|
||||
/**
|
||||
* Apply the scope to a given Eloquent query builder.
|
||||
*/
|
||||
public function apply(Builder $builder, Model $model): void
|
||||
{
|
||||
$builder->whereNotNull($model->getTable().'.published_at')
|
||||
->where($model->getTable().'.published_at', '<=', now());
|
||||
}
|
||||
}
|
33
app/Observers/ModDependencyObserver.php
Normal file
33
app/Observers/ModDependencyObserver.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Observers;
|
||||
|
||||
use App\Models\ModDependency;
|
||||
use App\Models\ModVersion;
|
||||
use App\Services\ModVersionService;
|
||||
|
||||
class ModDependencyObserver
|
||||
{
|
||||
protected ModVersionService $modVersionService;
|
||||
|
||||
public function __construct(ModVersionService $modVersionService)
|
||||
{
|
||||
$this->modVersionService = $modVersionService;
|
||||
}
|
||||
|
||||
public function saved(ModDependency $modDependency): void
|
||||
{
|
||||
$modVersion = ModVersion::find($modDependency->mod_version_id);
|
||||
if ($modVersion) {
|
||||
$this->modVersionService->resolveDependencies($modVersion);
|
||||
}
|
||||
}
|
||||
|
||||
public function deleted(ModDependency $modDependency): void
|
||||
{
|
||||
$modVersion = ModVersion::find($modDependency->mod_version_id);
|
||||
if ($modVersion) {
|
||||
$this->modVersionService->resolveDependencies($modVersion);
|
||||
}
|
||||
}
|
||||
}
|
33
app/Observers/ModVersionObserver.php
Normal file
33
app/Observers/ModVersionObserver.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Observers;
|
||||
|
||||
use App\Models\ModDependency;
|
||||
use App\Models\ModVersion;
|
||||
use App\Services\ModVersionService;
|
||||
|
||||
class ModVersionObserver
|
||||
{
|
||||
protected ModVersionService $modVersionService;
|
||||
|
||||
public function __construct(ModVersionService $modVersionService)
|
||||
{
|
||||
$this->modVersionService = $modVersionService;
|
||||
}
|
||||
|
||||
public function saved(ModVersion $modVersion): void
|
||||
{
|
||||
$dependencies = ModDependency::where('resolved_version_id', $modVersion->id)->get();
|
||||
foreach ($dependencies as $dependency) {
|
||||
$this->modVersionService->resolveDependencies($dependency->modVersion);
|
||||
}
|
||||
}
|
||||
|
||||
public function deleted(ModVersion $modVersion): void
|
||||
{
|
||||
$dependencies = ModDependency::where('resolved_version_id', $modVersion->id)->get();
|
||||
foreach ($dependencies as $dependency) {
|
||||
$this->modVersionService->resolveDependencies($dependency->modVersion);
|
||||
}
|
||||
}
|
||||
}
|
@ -2,7 +2,11 @@
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Models\ModDependency;
|
||||
use App\Models\ModVersion;
|
||||
use App\Models\User;
|
||||
use App\Observers\ModDependencyObserver;
|
||||
use App\Observers\ModVersionObserver;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
@ -25,6 +29,10 @@ class AppServiceProvider extends ServiceProvider
|
||||
// Allow mass assignment for all models. Be careful!
|
||||
Model::unguard();
|
||||
|
||||
// Register observers.
|
||||
ModVersion::observe(ModVersionObserver::class);
|
||||
ModDependency::observe(ModDependencyObserver::class);
|
||||
|
||||
// This gate determines who can access the Pulse dashboard.
|
||||
Gate::define('viewPulse', function (User $user) {
|
||||
return $user->isAdmin();
|
||||
|
99
app/Services/ModVersionService.php
Normal file
99
app/Services/ModVersionService.php
Normal file
@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Exceptions\CircularDependencyException;
|
||||
use App\Models\ModDependency;
|
||||
use App\Models\ModVersion;
|
||||
use Composer\Semver\Semver;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class ModVersionService
|
||||
{
|
||||
protected array $visited = [];
|
||||
|
||||
protected array $stack = [];
|
||||
|
||||
/**
|
||||
* Resolve dependencies for the given mod version.
|
||||
*
|
||||
* @throws CircularDependencyException
|
||||
*/
|
||||
public function resolveDependencies(ModVersion $modVersion): array
|
||||
{
|
||||
$resolvedVersions = [];
|
||||
$this->visited = [];
|
||||
$this->stack = [];
|
||||
|
||||
$this->processDependencies($modVersion, $resolvedVersions);
|
||||
|
||||
return $resolvedVersions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a depth-first search to resolve dependencies for the given mod version.
|
||||
*
|
||||
* @throws CircularDependencyException
|
||||
*/
|
||||
protected function processDependencies(ModVersion $modVersion, array &$resolvedVersions): void
|
||||
{
|
||||
if (in_array($modVersion->id, $this->stack)) {
|
||||
throw new CircularDependencyException("Circular dependency detected in ModVersion ID: {$modVersion->id}");
|
||||
}
|
||||
|
||||
if (in_array($modVersion->id, $this->visited)) {
|
||||
return; // Skip already processed versions
|
||||
}
|
||||
|
||||
$this->visited[] = $modVersion->id;
|
||||
$this->stack[] = $modVersion->id;
|
||||
|
||||
/** @var Collection|ModDependency[] $dependencies */
|
||||
$dependencies = $this->getDependencies($modVersion);
|
||||
|
||||
foreach ($dependencies as $dependency) {
|
||||
$resolvedVersionId = $this->resolveVersionIdForDependency($dependency);
|
||||
|
||||
if ($dependency->resolved_version_id !== $resolvedVersionId) {
|
||||
$dependency->updateQuietly(['resolved_version_id' => $resolvedVersionId]);
|
||||
}
|
||||
|
||||
$resolvedVersions[$dependency->id] = $resolvedVersionId ? ModVersion::find($resolvedVersionId) : null;
|
||||
|
||||
if ($resolvedVersionId) {
|
||||
$nextModVersion = ModVersion::find($resolvedVersionId);
|
||||
if ($nextModVersion) {
|
||||
$this->processDependencies($nextModVersion, $resolvedVersions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
array_pop($this->stack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the dependencies for the given mod version.
|
||||
*/
|
||||
protected function getDependencies(ModVersion $modVersion): Collection
|
||||
{
|
||||
return $modVersion->dependencies()->with(['dependencyMod.versions'])->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the latest version ID that satisfies the version constraint on given dependency.
|
||||
*/
|
||||
protected function resolveVersionIdForDependency(ModDependency $dependency): ?int
|
||||
{
|
||||
$mod = $dependency->dependencyMod;
|
||||
|
||||
if (! $mod || $mod->versions->isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$availableVersions = $mod->versions->pluck('id', 'version')->toArray();
|
||||
$satisfyingVersions = Semver::satisfiedBy(array_keys($availableVersions), $dependency->version_constraint);
|
||||
|
||||
// Versions are sorted in descending order by default. Take the first key (the latest version) using `reset()`.
|
||||
return $satisfyingVersions ? $availableVersions[reset($satisfyingVersions)] : null;
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@
|
||||
"ext-curl": "*",
|
||||
"ext-intl": "*",
|
||||
"aws/aws-sdk-php": "^3.314",
|
||||
"composer/semver": "^3.4",
|
||||
"filament/filament": "^3.2",
|
||||
"http-interop/http-factory-guzzle": "^1.2",
|
||||
"laravel/framework": "^11.11",
|
||||
@ -52,7 +53,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"phpstan": [
|
||||
"./vendor/bin/phpstan analyse -c phpstan.neon --debug --memory-limit=2G"
|
||||
"./vendor/bin/phpstan analyse --configuration phpstan.neon --error-format=table --memory-limit=2G"
|
||||
],
|
||||
"post-autoload-dump": [
|
||||
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
|
||||
|
888
composer.lock
generated
888
composer.lock
generated
File diff suppressed because it is too large
Load Diff
25
database/factories/ModDependencyFactory.php
Normal file
25
database/factories/ModDependencyFactory.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\Mod;
|
||||
use App\Models\ModDependency;
|
||||
use App\Models\ModVersion;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class ModDependencyFactory extends Factory
|
||||
{
|
||||
protected $model = ModDependency::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'mod_version_id' => ModVersion::factory(),
|
||||
'dependency_mod_id' => Mod::factory(),
|
||||
'version_constraint' => '^'.$this->faker->numerify('#.#.#'),
|
||||
'created_at' => Carbon::now()->subDays(rand(0, 365))->subHours(rand(0, 23)),
|
||||
'updated_at' => Carbon::now()->subDays(rand(0, 365))->subHours(rand(0, 23)),
|
||||
];
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ namespace Database\Factories;
|
||||
use App\Models\License;
|
||||
use App\Models\Mod;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Str;
|
||||
use Random\RandomException;
|
||||
|
||||
@ -30,12 +31,22 @@ class ModFactory extends Factory
|
||||
'featured' => fake()->boolean(),
|
||||
'contains_ai_content' => fake()->boolean(),
|
||||
'contains_ads' => fake()->boolean(),
|
||||
'disabled' => fake()->boolean(),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
'published_at' => Carbon::now()->subDays(rand(0, 365))->subHours(rand(0, 23)),
|
||||
'created_at' => Carbon::now()->subDays(rand(0, 365))->subHours(rand(0, 23)),
|
||||
'updated_at' => Carbon::now()->subDays(rand(0, 365))->subHours(rand(0, 23)),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that the mod should be disabled.
|
||||
*/
|
||||
public function disabled(): static
|
||||
{
|
||||
return $this->state(fn (array $attributes) => [
|
||||
'disabled' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that the mod should be soft-deleted.
|
||||
*/
|
||||
|
@ -16,15 +16,25 @@ class ModVersionFactory extends Factory
|
||||
{
|
||||
return [
|
||||
'mod_id' => Mod::factory(),
|
||||
'version' => fake()->numerify('1.#.#'),
|
||||
'version' => fake()->numerify('#.#.#'),
|
||||
'description' => fake()->text(),
|
||||
'link' => fake()->url(),
|
||||
'spt_version_id' => SptVersion::factory(),
|
||||
'virus_total_link' => fake()->url(),
|
||||
'downloads' => fake()->randomNumber(),
|
||||
'disabled' => fake()->boolean(),
|
||||
'published_at' => Carbon::now()->subDays(rand(0, 365))->subHours(rand(0, 23)),
|
||||
'created_at' => Carbon::now()->subDays(rand(0, 365))->subHours(rand(0, 23)),
|
||||
'updated_at' => Carbon::now()->subDays(rand(0, 365))->subHours(rand(0, 23)),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that the mod version should be disabled.
|
||||
*/
|
||||
public function disabled(): static
|
||||
{
|
||||
return $this->state(fn (array $attributes) => [
|
||||
'disabled' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ return new class extends Migration
|
||||
$table->boolean('contains_ads')->default(false);
|
||||
$table->boolean('disabled')->default(false);
|
||||
$table->softDeletes();
|
||||
$table->timestamp('published_at')->nullable()->default(null);
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['deleted_at', 'disabled'], 'mods_show_index');
|
||||
|
@ -33,6 +33,7 @@ return new class extends Migration
|
||||
$table->unsignedBigInteger('downloads');
|
||||
$table->boolean('disabled')->default(false);
|
||||
$table->softDeletes();
|
||||
$table->timestamp('published_at')->nullable()->default(null);
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['mod_id', 'deleted_at', 'disabled', 'version'], 'mod_versions_filtering_index');
|
||||
|
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('mod_dependencies', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('mod_version_id')
|
||||
->constrained('mod_versions')
|
||||
->cascadeOnDelete()
|
||||
->cascadeOnUpdate();
|
||||
$table->foreignId('dependency_mod_id')
|
||||
->constrained('mods')
|
||||
->cascadeOnDelete()
|
||||
->cascadeOnUpdate();
|
||||
$table->string('version_constraint'); // e.g., ^1.0.1
|
||||
$table->foreignId('resolved_version_id')
|
||||
->nullable()
|
||||
->constrained('mod_versions')
|
||||
->nullOnDelete()
|
||||
->cascadeOnUpdate();
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['mod_version_id', 'dependency_mod_id', 'version_constraint'], 'mod_dependencies_unique');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('mod_dependencies');
|
||||
}
|
||||
};
|
@ -2,8 +2,10 @@
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Exceptions\CircularDependencyException;
|
||||
use App\Models\License;
|
||||
use App\Models\Mod;
|
||||
use App\Models\ModDependency;
|
||||
use App\Models\ModVersion;
|
||||
use App\Models\SptVersion;
|
||||
use App\Models\User;
|
||||
@ -46,6 +48,31 @@ class DatabaseSeeder extends Seeder
|
||||
}
|
||||
|
||||
// Add 1000 mod versions, assigning them to the mods we just created.
|
||||
ModVersion::factory(1000)->recycle([$mods, $spt_versions])->create();
|
||||
$modVersions = ModVersion::factory(1000)->recycle([$mods, $spt_versions])->create();
|
||||
|
||||
// Add ModDependencies to a subset of ModVersions.
|
||||
foreach ($modVersions as $modVersion) {
|
||||
$hasDependencies = rand(0, 100) < 30; // 30% chance to have dependencies
|
||||
if ($hasDependencies) {
|
||||
$numDependencies = rand(1, 3); // 1 to 3 dependencies
|
||||
$dependencyMods = $mods->random($numDependencies);
|
||||
foreach ($dependencyMods as $dependencyMod) {
|
||||
try {
|
||||
ModDependency::factory()->recycle([$modVersion, $dependencyMod])->create([
|
||||
'version_constraint' => $this->generateVersionConstraint(),
|
||||
]);
|
||||
} catch (CircularDependencyException $e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function generateVersionConstraint(): string
|
||||
{
|
||||
$versionConstraints = ['*', '^1.0.0', '>=2.0.0', '~1.1.0', '>=1.2.0 <2.0.0'];
|
||||
|
||||
return $versionConstraints[array_rand($versionConstraints)];
|
||||
}
|
||||
}
|
||||
|
216
package-lock.json
generated
216
package-lock.json
generated
@ -556,9 +556,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.1.tgz",
|
||||
"integrity": "sha512-lncuC4aHicncmbORnx+dUaAgzee9cm/PbIqgWz1PpXuwc+sa1Ct83tnqUDy/GFKleLiN7ZIeytM6KJ4cAn1SxA==",
|
||||
"version": "4.19.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.19.2.tgz",
|
||||
"integrity": "sha512-OHflWINKtoCFSpm/WmuQaWW4jeX+3Qt3XQDepkkiFTsoxFc5BpF3Z5aDxFZgBqRjO6ATP5+b1iilp4kGIZVWlA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@ -570,9 +570,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.1.tgz",
|
||||
"integrity": "sha512-F/tkdw0WSs4ojqz5Ovrw5r9odqzFjb5LIgHdHZG65dFI1lWTWRVy32KDJLKRISHgJvqUeUhdIvy43fX41znyDg==",
|
||||
"version": "4.19.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.19.2.tgz",
|
||||
"integrity": "sha512-k0OC/b14rNzMLDOE6QMBCjDRm3fQOHAL8Ldc9bxEWvMo4Ty9RY6rWmGetNTWhPo+/+FNd1lsQYRd0/1OSix36A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -584,9 +584,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.1.tgz",
|
||||
"integrity": "sha512-vk+ma8iC1ebje/ahpxpnrfVQJibTMyHdWpOGZ3JpQ7Mgn/3QNHmPq7YwjZbIE7km73dH5M1e6MRRsnEBW7v5CQ==",
|
||||
"version": "4.19.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.19.2.tgz",
|
||||
"integrity": "sha512-IIARRgWCNWMTeQH+kr/gFTHJccKzwEaI0YSvtqkEBPj7AshElFq89TyreKNFAGh5frLfDCbodnq+Ye3dqGKPBw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -598,9 +598,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.1.tgz",
|
||||
"integrity": "sha512-IgpzXKauRe1Tafcej9STjSSuG0Ghu/xGYH+qG6JwsAUxXrnkvNHcq/NL6nz1+jzvWAnQkuAJ4uIwGB48K9OCGA==",
|
||||
"version": "4.19.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.19.2.tgz",
|
||||
"integrity": "sha512-52udDMFDv54BTAdnw+KXNF45QCvcJOcYGl3vQkp4vARyrcdI/cXH8VXTEv/8QWfd6Fru8QQuw1b2uNersXOL0g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -612,9 +612,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.1.tgz",
|
||||
"integrity": "sha512-P9bSiAUnSSM7EmyRK+e5wgpqai86QOSv8BwvkGjLwYuOpaeomiZWifEos517CwbG+aZl1T4clSE1YqqH2JRs+g==",
|
||||
"version": "4.19.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.19.2.tgz",
|
||||
"integrity": "sha512-r+SI2t8srMPYZeoa1w0o/AfoVt9akI1ihgazGYPQGRilVAkuzMGiTtexNZkrPkQsyFrvqq/ni8f3zOnHw4hUbA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@ -626,9 +626,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.1.tgz",
|
||||
"integrity": "sha512-5RnjpACoxtS+aWOI1dURKno11d7krfpGDEn19jI8BuWmSBbUC4ytIADfROM1FZrFhQPSoP+KEa3NlEScznBTyQ==",
|
||||
"version": "4.19.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.19.2.tgz",
|
||||
"integrity": "sha512-+tYiL4QVjtI3KliKBGtUU7yhw0GMcJJuB9mLTCEauHEsqfk49gtUBXGtGP3h1LW8MbaTY6rSFIQV1XOBps1gBA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@ -640,9 +640,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.1.tgz",
|
||||
"integrity": "sha512-8mwmGD668m8WaGbthrEYZ9CBmPug2QPGWxhJxh/vCgBjro5o96gL04WLlg5BA233OCWLqERy4YUzX3bJGXaJgQ==",
|
||||
"version": "4.19.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.19.2.tgz",
|
||||
"integrity": "sha512-OR5DcvZiYN75mXDNQQxlQPTv4D+uNCUsmSCSY2FolLf9W5I4DSoJyg7z9Ea3TjKfhPSGgMJiey1aWvlWuBzMtg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -654,9 +654,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.1.tgz",
|
||||
"integrity": "sha512-dJX9u4r4bqInMGOAQoGYdwDP8lQiisWb9et+T84l2WXk41yEej8v2iGKodmdKimT8cTAYt0jFb+UEBxnPkbXEQ==",
|
||||
"version": "4.19.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.19.2.tgz",
|
||||
"integrity": "sha512-Hw3jSfWdUSauEYFBSFIte6I8m6jOj+3vifLg8EU3lreWulAUpch4JBjDMtlKosrBzkr0kwKgL9iCfjA8L3geoA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -668,9 +668,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.1.tgz",
|
||||
"integrity": "sha512-V72cXdTl4EI0x6FNmho4D502sy7ed+LuVW6Ym8aI6DRQ9hQZdp5sj0a2usYOlqvFBNKQnLQGwmYnujo2HvjCxQ==",
|
||||
"version": "4.19.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.19.2.tgz",
|
||||
"integrity": "sha512-rhjvoPBhBwVnJRq/+hi2Q3EMiVF538/o9dBuj9TVLclo9DuONqt5xfWSaE6MYiFKpo/lFPJ/iSI72rYWw5Hc7w==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@ -682,9 +682,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.1.tgz",
|
||||
"integrity": "sha512-f+pJih7sxoKmbjghrM2RkWo2WHUW8UbfxIQiWo5yeCaCM0TveMEuAzKJte4QskBp1TIinpnRcxkquY+4WuY/tg==",
|
||||
"version": "4.19.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.19.2.tgz",
|
||||
"integrity": "sha512-EAz6vjPwHHs2qOCnpQkw4xs14XJq84I81sDRGPEjKPFVPBw7fwvtwhVjcZR6SLydCv8zNK8YGFblKWd/vRmP8g==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@ -696,9 +696,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.1.tgz",
|
||||
"integrity": "sha512-qb1hMMT3Fr/Qz1OKovCuUM11MUNLUuHeBC2DPPAWUYYUAOFWaxInaTwTQmc7Fl5La7DShTEpmYwgdt2hG+4TEg==",
|
||||
"version": "4.19.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.19.2.tgz",
|
||||
"integrity": "sha512-IJSUX1xb8k/zN9j2I7B5Re6B0NNJDJ1+soezjNojhT8DEVeDNptq2jgycCOpRhyGj0+xBn7Cq+PK7Q+nd2hxLA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@ -710,9 +710,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.1.tgz",
|
||||
"integrity": "sha512-7O5u/p6oKUFYjRbZkL2FLbwsyoJAjyeXHCU3O4ndvzg2OFO2GinFPSJFGbiwFDaCFc+k7gs9CF243PwdPQFh5g==",
|
||||
"version": "4.19.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.19.2.tgz",
|
||||
"integrity": "sha512-OgaToJ8jSxTpgGkZSkwKE+JQGihdcaqnyHEFOSAU45utQ+yLruE1dkonB2SDI8t375wOKgNn8pQvaWY9kPzxDQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -724,9 +724,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.1.tgz",
|
||||
"integrity": "sha512-pDLkYITdYrH/9Cv/Vlj8HppDuLMDUBmgsM0+N+xLtFd18aXgM9Nyqupb/Uw+HeidhfYg2lD6CXvz6CjoVOaKjQ==",
|
||||
"version": "4.19.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.19.2.tgz",
|
||||
"integrity": "sha512-5V3mPpWkB066XZZBgSd1lwozBk7tmOkKtquyCJ6T4LN3mzKENXyBwWNQn8d0Ci81hvlBw5RoFgleVpL6aScLYg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -738,9 +738,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.1.tgz",
|
||||
"integrity": "sha512-W2ZNI323O/8pJdBGil1oCauuCzmVd9lDmWBBqxYZcOqWD6aWqJtVBQ1dFrF4dYpZPks6F+xCZHfzG5hYlSHZ6g==",
|
||||
"version": "4.19.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.19.2.tgz",
|
||||
"integrity": "sha512-ayVstadfLeeXI9zUPiKRVT8qF55hm7hKa+0N1V6Vj+OTNFfKSoUxyZvzVvgtBxqSb5URQ8sK6fhwxr9/MLmxdA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -752,9 +752,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.1.tgz",
|
||||
"integrity": "sha512-ELfEX1/+eGZYMaCIbK4jqLxO1gyTSOIlZr6pbC4SRYFaSIDVKOnZNMdoZ+ON0mrFDp4+H5MhwNC1H/AhE3zQLg==",
|
||||
"version": "4.19.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.19.2.tgz",
|
||||
"integrity": "sha512-Mda7iG4fOLHNsPqjWSjANvNZYoW034yxgrndof0DwCy0D3FvTjeNo+HGE6oGWgvcLZNLlcp0hLEFcRs+UGsMLg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@ -766,9 +766,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.1.tgz",
|
||||
"integrity": "sha512-yjk2MAkQmoaPYCSu35RLJ62+dz358nE83VfTePJRp8CG7aMg25mEJYpXFiD+NcevhX8LxD5OP5tktPXnXN7GDw==",
|
||||
"version": "4.19.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.19.2.tgz",
|
||||
"integrity": "sha512-DPi0ubYhSow/00YqmG1jWm3qt1F8aXziHc/UNy8bo9cpCacqhuWu+iSq/fp2SyEQK7iYTZ60fBU9cat3MXTjIQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -915,9 +915,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz",
|
||||
"integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==",
|
||||
"version": "1.7.3",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.3.tgz",
|
||||
"integrity": "sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -970,9 +970,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.23.2",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.2.tgz",
|
||||
"integrity": "sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==",
|
||||
"version": "4.23.3",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz",
|
||||
"integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@ -990,9 +990,9 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001640",
|
||||
"electron-to-chromium": "^1.4.820",
|
||||
"node-releases": "^2.0.14",
|
||||
"caniuse-lite": "^1.0.30001646",
|
||||
"electron-to-chromium": "^1.5.4",
|
||||
"node-releases": "^2.0.18",
|
||||
"update-browserslist-db": "^1.1.0"
|
||||
},
|
||||
"bin": {
|
||||
@ -1013,9 +1013,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001642",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001642.tgz",
|
||||
"integrity": "sha512-3XQ0DoRgLijXJErLSl+bLnJ+Et4KqV1PY6JJBGAFlsNsz31zeAIncyeZfLCabHK/jtSh+671RM9YMldxjUPZtA==",
|
||||
"version": "1.0.30001646",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001646.tgz",
|
||||
"integrity": "sha512-dRg00gudiBDDTmUhClSdv3hqRfpbOnU28IpI1T6PBTLWa+kOj0681C8uML3PifYfREuBrVjDGhL3adYpBT6spw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@ -1161,9 +1161,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.4.829",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.829.tgz",
|
||||
"integrity": "sha512-5qp1N2POAfW0u1qGAxXEtz6P7bO1m6gpZr5hdf5ve6lxpLM7MpiM4jIPz7xcrNlClQMafbyUDDWjlIQZ1Mw0Rw==",
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.4.tgz",
|
||||
"integrity": "sha512-orzA81VqLyIGUEA77YkVA1D+N+nNfl2isJVjjmOyrlxuooZ19ynb+dOlaDTqd/idKRS9lDCSBmtzM+kyCsMnkA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
@ -1696,9 +1696,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.17",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.17.tgz",
|
||||
"integrity": "sha512-Ww6ZlOiEQfPfXM45v17oabk77Z7mg5bOt7AjDyzy7RjK9OrLrLC8dyZQoAPEOtFX9SaNf1Tdvr5gRJWdTJj7GA==",
|
||||
"version": "2.0.18",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
|
||||
"integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@ -1824,9 +1824,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.39",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz",
|
||||
"integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==",
|
||||
"version": "8.4.40",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.40.tgz",
|
||||
"integrity": "sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@ -1940,21 +1940,27 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-nested": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz",
|
||||
"integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==",
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
|
||||
"integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"postcss-selector-parser": "^6.0.11"
|
||||
"postcss-selector-parser": "^6.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"postcss": "^8.2.14"
|
||||
}
|
||||
@ -2166,9 +2172,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.1.tgz",
|
||||
"integrity": "sha512-Elx2UT8lzxxOXMpy5HWQGZqkrQOtrVDDa/bm9l10+U4rQnVzbL/LgZ4NOM1MPIDyHk69W4InuYDF5dzRh4Kw1A==",
|
||||
"version": "4.19.2",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.19.2.tgz",
|
||||
"integrity": "sha512-6/jgnN1svF9PjNYJ4ya3l+cqutg49vOZ4rVgsDKxdl+5gpGPnByFXWGyfH9YGx9i3nfBwSu1Iyu6vGwFFA0BdQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -2182,22 +2188,22 @@
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.18.1",
|
||||
"@rollup/rollup-android-arm64": "4.18.1",
|
||||
"@rollup/rollup-darwin-arm64": "4.18.1",
|
||||
"@rollup/rollup-darwin-x64": "4.18.1",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.18.1",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.18.1",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.18.1",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.18.1",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.18.1",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.18.1",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.18.1",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.18.1",
|
||||
"@rollup/rollup-linux-x64-musl": "4.18.1",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.18.1",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.18.1",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.18.1",
|
||||
"@rollup/rollup-android-arm-eabi": "4.19.2",
|
||||
"@rollup/rollup-android-arm64": "4.19.2",
|
||||
"@rollup/rollup-darwin-arm64": "4.19.2",
|
||||
"@rollup/rollup-darwin-x64": "4.19.2",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.19.2",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.19.2",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.19.2",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.19.2",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.19.2",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.19.2",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.19.2",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.19.2",
|
||||
"@rollup/rollup-linux-x64-musl": "4.19.2",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.19.2",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.19.2",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.19.2",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
@ -2418,9 +2424,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "3.4.6",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.6.tgz",
|
||||
"integrity": "sha512-1uRHzPB+Vzu57ocybfZ4jh5Q3SdlH7XW23J5sQoM9LhE9eIOlzxer/3XPSsycvih3rboRsvt0QCmzSrqyOYUIA==",
|
||||
"version": "3.4.7",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.7.tgz",
|
||||
"integrity": "sha512-rxWZbe87YJb4OcSopb7up2Ba4U82BoiSGUdoDr3Ydrg9ckxFS/YWsvhN323GMcddgU65QRy7JndC7ahhInhvlQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -2564,9 +2570,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "5.3.4",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.3.4.tgz",
|
||||
"integrity": "sha512-Cw+7zL3ZG9/NZBB8C+8QbQZmR54GwqIz+WMI4b3JgdYJvX+ny9AjJXqkGQlDXSXRP9rP0B4tbciRMOVEKulVOA==",
|
||||
"version": "5.3.5",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.3.5.tgz",
|
||||
"integrity": "sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -2745,9 +2751,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.4.5",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz",
|
||||
"integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz",
|
||||
"integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
|
@ -1 +1 @@
|
||||
function n(){return{collapsedGroups:[],isLoading:!1,selectedRecords:[],shouldCheckUniqueSelection:!0,lastCheckedRecord:null,livewireId:null,init:function(){this.livewireId=this.$root.closest("[wire\\:id]").attributes["wire:id"].value,this.$wire.$on("deselectAllTableRecords",()=>this.deselectAllRecords()),this.$watch("selectedRecords",()=>{if(!this.shouldCheckUniqueSelection){this.shouldCheckUniqueSelection=!0;return}this.selectedRecords=[...new Set(this.selectedRecords)],this.shouldCheckUniqueSelection=!1}),this.$nextTick(()=>this.watchForCheckboxClicks()),Livewire.hook("element.init",({component:e})=>{e.id===this.livewireId&&this.watchForCheckboxClicks()})},mountAction:function(e,t=null){this.$wire.set("selectedTableRecords",this.selectedRecords,!1),this.$wire.mountTableAction(e,t)},mountBulkAction:function(e){this.$wire.set("selectedTableRecords",this.selectedRecords,!1),this.$wire.mountTableBulkAction(e)},toggleSelectRecordsOnPage:function(){let e=this.getRecordsOnPage();if(this.areRecordsSelected(e)){this.deselectRecords(e);return}this.selectRecords(e)},toggleSelectRecordsInGroup:async function(e){if(this.isLoading=!0,this.areRecordsSelected(this.getRecordsInGroupOnPage(e))){this.deselectRecords(await this.$wire.getGroupedSelectableTableRecordKeys(e));return}this.selectRecords(await this.$wire.getGroupedSelectableTableRecordKeys(e)),this.isLoading=!1},getRecordsInGroupOnPage:function(e){let t=[];for(let s of this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[])s.dataset.group===e&&t.push(s.value);return t},getRecordsOnPage:function(){let e=[];for(let t of this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[])e.push(t.value);return e},selectRecords:function(e){for(let t of e)this.isRecordSelected(t)||this.selectedRecords.push(t)},deselectRecords:function(e){for(let t of e){let s=this.selectedRecords.indexOf(t);s!==-1&&this.selectedRecords.splice(s,1)}},selectAllRecords:async function(){this.isLoading=!0,this.selectedRecords=await this.$wire.getAllSelectableTableRecordKeys(),this.isLoading=!1},deselectAllRecords:function(){this.selectedRecords=[]},isRecordSelected:function(e){return this.selectedRecords.includes(e)},areRecordsSelected:function(e){return e.every(t=>this.isRecordSelected(t))},toggleCollapseGroup:function(e){if(this.isGroupCollapsed(e)){this.collapsedGroups.splice(this.collapsedGroups.indexOf(e),1);return}this.collapsedGroups.push(e)},isGroupCollapsed:function(e){return this.collapsedGroups.includes(e)},resetCollapsedGroups:function(){this.collapsedGroups=[]},watchForCheckboxClicks:function(){let e=this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[];for(let t of e)t.removeEventListener("click",this.handleCheckboxClick),t.addEventListener("click",s=>this.handleCheckboxClick(s,t))},handleCheckboxClick:function(e,t){if(!this.lastChecked){this.lastChecked=t;return}if(e.shiftKey){let s=Array.from(this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[]);if(!s.includes(this.lastChecked)){this.lastChecked=t;return}let o=s.indexOf(this.lastChecked),r=s.indexOf(t),l=[o,r].sort((i,d)=>i-d),c=[];for(let i=l[0];i<=l[1];i++)s[i].checked=t.checked,c.push(s[i].value);t.checked?this.selectRecords(c):this.deselectRecords(c)}this.lastChecked=t}}}export{n as default};
|
||||
function n(){return{checkboxClickController:null,collapsedGroups:[],isLoading:!1,selectedRecords:[],shouldCheckUniqueSelection:!0,lastCheckedRecord:null,livewireId:null,init:function(){this.livewireId=this.$root.closest("[wire\\:id]").attributes["wire:id"].value,this.$wire.$on("deselectAllTableRecords",()=>this.deselectAllRecords()),this.$watch("selectedRecords",()=>{if(!this.shouldCheckUniqueSelection){this.shouldCheckUniqueSelection=!0;return}this.selectedRecords=[...new Set(this.selectedRecords)],this.shouldCheckUniqueSelection=!1}),this.$nextTick(()=>this.watchForCheckboxClicks()),Livewire.hook("element.init",({component:e})=>{e.id===this.livewireId&&this.watchForCheckboxClicks()})},mountAction:function(e,t=null){this.$wire.set("selectedTableRecords",this.selectedRecords,!1),this.$wire.mountTableAction(e,t)},mountBulkAction:function(e){this.$wire.set("selectedTableRecords",this.selectedRecords,!1),this.$wire.mountTableBulkAction(e)},toggleSelectRecordsOnPage:function(){let e=this.getRecordsOnPage();if(this.areRecordsSelected(e)){this.deselectRecords(e);return}this.selectRecords(e)},toggleSelectRecordsInGroup:async function(e){if(this.isLoading=!0,this.areRecordsSelected(this.getRecordsInGroupOnPage(e))){this.deselectRecords(await this.$wire.getGroupedSelectableTableRecordKeys(e));return}this.selectRecords(await this.$wire.getGroupedSelectableTableRecordKeys(e)),this.isLoading=!1},getRecordsInGroupOnPage:function(e){let t=[];for(let s of this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[])s.dataset.group===e&&t.push(s.value);return t},getRecordsOnPage:function(){let e=[];for(let t of this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[])e.push(t.value);return e},selectRecords:function(e){for(let t of e)this.isRecordSelected(t)||this.selectedRecords.push(t)},deselectRecords:function(e){for(let t of e){let s=this.selectedRecords.indexOf(t);s!==-1&&this.selectedRecords.splice(s,1)}},selectAllRecords:async function(){this.isLoading=!0,this.selectedRecords=await this.$wire.getAllSelectableTableRecordKeys(),this.isLoading=!1},deselectAllRecords:function(){this.selectedRecords=[]},isRecordSelected:function(e){return this.selectedRecords.includes(e)},areRecordsSelected:function(e){return e.every(t=>this.isRecordSelected(t))},toggleCollapseGroup:function(e){if(this.isGroupCollapsed(e)){this.collapsedGroups.splice(this.collapsedGroups.indexOf(e),1);return}this.collapsedGroups.push(e)},isGroupCollapsed:function(e){return this.collapsedGroups.includes(e)},resetCollapsedGroups:function(){this.collapsedGroups=[]},watchForCheckboxClicks:function(){this.checkboxClickController&&this.checkboxClickController.abort(),this.checkboxClickController=new AbortController;let{signal:e}=this.checkboxClickController;this.$root?.addEventListener("click",t=>t.target?.matches(".fi-ta-record-checkbox")&&this.handleCheckboxClick(t,t.target),{signal:e})},handleCheckboxClick:function(e,t){if(!this.lastChecked){this.lastChecked=t;return}if(e.shiftKey){let s=Array.from(this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[]);if(!s.includes(this.lastChecked)){this.lastChecked=t;return}let o=s.indexOf(this.lastChecked),r=s.indexOf(t),l=[o,r].sort((i,d)=>i-d),c=[];for(let i=l[0];i<=l[1];i++)s[i].checked=t.checked,c.push(s[i].value);t.checked?this.selectRecords(c):this.deselectRecords(c)}this.lastChecked=t}}}export{n as default};
|
||||
|
File diff suppressed because one or more lines are too long
@ -25,15 +25,15 @@
|
||||
<h2 class="pb-1 sm:p-0 text-3xl font-bold text-gray-900 dark:text-white">
|
||||
{{ $mod->name }}
|
||||
<span class="font-light text-nowrap text-gray-700 dark:text-gray-400">
|
||||
{{ $mod->latestVersion->version }}
|
||||
{{ $latestVersion->version }}
|
||||
</span>
|
||||
</h2>
|
||||
<span class="badge-version {{ $mod->latestVersion->sptVersion->color_class }} inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-nowrap">
|
||||
{{ $mod->latestVersion->sptVersion->version }}
|
||||
<span class="badge-version {{ $latestVersion->sptVersion->color_class }} inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-nowrap">
|
||||
{{ $latestVersion->sptVersion->version }}
|
||||
</span>
|
||||
</div>
|
||||
<p>{{ __('Created by') }} {{ $mod->users->pluck('name')->implode(', ') }}</p>
|
||||
<p>{{ $mod->latestVersion->sptVersion->version }} {{ __('Compatible') }}</p>
|
||||
<p>{{ $latestVersion->sptVersion->version }} {{ __('Compatible') }}</p>
|
||||
<p>{{ Number::format($mod->total_downloads) }} {{ __('Downloads') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -94,12 +94,22 @@
|
||||
</span>
|
||||
<a href="{{ $version->virus_total_link }}">{{__('Virus Total Results')}}</a>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-gray-400">
|
||||
<div class="flex items-center justify-between text-gray-600 dark:text-gray-400">
|
||||
<span>{{ __('Created') }} {{ $version->created_at->format("M d, h:m a") }}</span>
|
||||
<span>{{ __('Updated') }} {{ $version->updated_at->format("M d, h:m a") }}</span>
|
||||
</div>
|
||||
@if ($version->dependencies->count())
|
||||
<div class="text-gray-600 dark:text-gray-400">
|
||||
{{ __('Dependencies:') }}
|
||||
@foreach ($version->dependencies as $dependency)
|
||||
<a href="{{ route('mod.show', [$dependency->resolvedVersion->mod->id, $dependency->resolvedVersion->mod->slug]) }}">
|
||||
{{ $dependency->resolvedVersion->mod->name }} ({{ $dependency->resolvedVersion->version }})
|
||||
</a>@if (!$loop->last), @endif
|
||||
@endforeach
|
||||
</div>
|
||||
<div class="pt-3 user-markdown">
|
||||
@endif
|
||||
</div>
|
||||
<div class="p-3 user-markdown">
|
||||
{{-- The description below is safe to write directly because it has been run though HTMLPurifier during the import process. --}}
|
||||
{!! Str::markdown($version->description) !!}
|
||||
</div>
|
||||
@ -118,15 +128,15 @@
|
||||
<div class="col-span-1 flex flex-col gap-6">
|
||||
|
||||
{{-- Main Download Button --}}
|
||||
<a href="{{ $mod->latestVersion->link }}" class="block">
|
||||
<button class="text-lg font-extrabold hover:bg-cyan-400 dark:hover:bg-cyan-600 shadow-md dark:shadow-gray-950 drop-shadow-2xl bg-cyan-500 dark:bg-cyan-700 rounded-xl w-full h-20">{{ __('Download Latest Version') }} ({{ $mod->latestVersion->version }})</button>
|
||||
<a href="{{ $latestVersion->link }}" class="block">
|
||||
<button class="text-lg font-extrabold hover:bg-cyan-400 dark:hover:bg-cyan-600 shadow-md dark:shadow-gray-950 drop-shadow-2xl bg-cyan-500 dark:bg-cyan-700 rounded-xl w-full h-20">{{ __('Download Latest Version') }} ({{ $latestVersion->version }})</button>
|
||||
</a>
|
||||
|
||||
{{-- Additional Mod Details --}}
|
||||
<div class="p-4 sm:p-6 bg-white dark:bg-gray-950 rounded-xl shadow-md dark:shadow-gray-950 drop-shadow-2xl">
|
||||
<h2 class="text-2xl font-bold text-gray-900 dark:text-gray-100">{{ __('Details') }}</h2>
|
||||
<ul role="list" class="divide-y divide-gray-200 dark:divide-gray-800 text-gray-900 dark:text-gray-100">
|
||||
@if($mod->license)
|
||||
@if ($mod->license)
|
||||
<li class="px-4 py-4 sm:px-0">
|
||||
<h3>{{ __('License') }}</h3>
|
||||
<p class="truncate">
|
||||
@ -136,7 +146,7 @@
|
||||
</p>
|
||||
</li>
|
||||
@endif
|
||||
@if($mod->source_code_link)
|
||||
@if ($mod->source_code_link)
|
||||
<li class="px-4 py-4 sm:px-0">
|
||||
<h3>{{ __('Source Code') }}</h3>
|
||||
<p class="truncate">
|
||||
@ -146,17 +156,29 @@
|
||||
</p>
|
||||
</li>
|
||||
@endif
|
||||
@if($mod->latestVersion->virus_total_link)
|
||||
@if ($latestVersion->virus_total_link)
|
||||
<li class="px-4 py-4 sm:px-0">
|
||||
<h3>{{ __('Latest VirusTotal Result') }}</h3>
|
||||
<h3>{{ __('Latest Version VirusTotal Result') }}</h3>
|
||||
<p class="truncate">
|
||||
<a href="{{ $mod->latestVersion->virus_total_link }}" title="{{ $mod->latestVersion->virus_total_link }}" target="_blank">
|
||||
{{ $mod->latestVersion->virus_total_link }}
|
||||
<a href="{{ $latestVersion->virus_total_link }}" title="{{ $latestVersion->virus_total_link }}" target="_blank">
|
||||
{{ $latestVersion->virus_total_link }}
|
||||
</a>
|
||||
</p>
|
||||
</li>
|
||||
@endif
|
||||
@if($mod->contains_ads)
|
||||
@if ($latestVersion->dependencies->count())
|
||||
<li class="px-4 py-4 sm:px-0">
|
||||
<h3>{{ __('Latest Version Dependencies') }}</h3>
|
||||
<p class="truncate">
|
||||
@foreach ($latestVersion->dependencies as $dependency)
|
||||
<a href="{{ route('mod.show', [$dependency->resolvedVersion->mod->id, $dependency->resolvedVersion->mod->slug]) }}">
|
||||
{{ $dependency->resolvedVersion->mod->name }} ({{ $dependency->resolvedVersion->version }})
|
||||
</a><br />
|
||||
@endforeach
|
||||
</p>
|
||||
</li>
|
||||
@endif
|
||||
@if ($mod->contains_ads)
|
||||
<li class="px-4 py-4 sm:px-0 flex flex-row gap-2 items-center">
|
||||
<svg class="grow-0 w-[16px] h-[16px]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M8 15A7 7 0 1 0 8 1a7 7 0 0 0 0 14Zm3.844-8.791a.75.75 0 0 0-1.188-.918l-3.7 4.79-1.649-1.833a.75.75 0 1 0-1.114 1.004l2.25 2.5a.75.75 0 0 0 1.15-.043l4.25-5.5Z" clip-rule="evenodd"/>
|
||||
@ -166,7 +188,7 @@
|
||||
</h3>
|
||||
</li>
|
||||
@endif
|
||||
@if($mod->contains_ai_content)
|
||||
@if ($mod->contains_ai_content)
|
||||
<li class="px-4 py-4 sm:px-0 flex flex-row gap-2 items-center">
|
||||
<svg class="grow-0 w-[16px] h-[16px]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M8 15A7 7 0 1 0 8 1a7 7 0 0 0 0 14Zm3.844-8.791a.75.75 0 0 0-1.188-.918l-3.7 4.79-1.649-1.833a.75.75 0 1 0-1.114 1.004l2.25 2.5a.75.75 0 0 0 1.15-.043l4.25-5.5Z" clip-rule="evenodd"/>
|
||||
|
238
tests/Feature/ModDependencyTest.php
Normal file
238
tests/Feature/ModDependencyTest.php
Normal file
@ -0,0 +1,238 @@
|
||||
<?php
|
||||
|
||||
use App\Exceptions\CircularDependencyException;
|
||||
use App\Models\Mod;
|
||||
use App\Models\ModDependency;
|
||||
use App\Models\ModVersion;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
it('resolves mod version dependency when mod version is created', function () {
|
||||
$modA = Mod::factory()->create(['name' => 'Mod A']);
|
||||
$modB = Mod::factory()->create(['name' => 'Mod B']);
|
||||
|
||||
// Create versions for Mod B
|
||||
ModVersion::factory()->create(['mod_id' => $modB->id, 'version' => '1.0.0']);
|
||||
ModVersion::factory()->create(['mod_id' => $modB->id, 'version' => '1.1.0']);
|
||||
ModVersion::factory()->create(['mod_id' => $modB->id, 'version' => '1.1.1']);
|
||||
ModVersion::factory()->create(['mod_id' => $modB->id, 'version' => '2.0.0']);
|
||||
|
||||
// Create versions for Mod A that depends on Mod B
|
||||
$modAv1 = ModVersion::factory()->create(['mod_id' => $modA->id, 'version' => '1.0.0']);
|
||||
$modDependency = ModDependency::factory()->recycle([$modAv1, $modB])->create([
|
||||
'version_constraint' => '^1.0.0',
|
||||
]);
|
||||
|
||||
$modDependency->refresh();
|
||||
|
||||
expect($modDependency->resolvedVersion->version)->toBe('1.1.1');
|
||||
});
|
||||
|
||||
it('resolves mod version dependency when mod version is updated', function () {
|
||||
$modA = Mod::factory()->create(['name' => 'Mod A']);
|
||||
$modB = Mod::factory()->create(['name' => 'Mod B']);
|
||||
|
||||
// Create versions for Mod B
|
||||
ModVersion::factory()->create(['mod_id' => $modB->id, 'version' => '1.0.0']);
|
||||
ModVersion::factory()->create(['mod_id' => $modB->id, 'version' => '1.1.0']);
|
||||
$modBv3 = ModVersion::factory()->create(['mod_id' => $modB->id, 'version' => '1.1.1']);
|
||||
ModVersion::factory()->create(['mod_id' => $modB->id, 'version' => '2.0.0']);
|
||||
|
||||
// Create versions for Mod A that depends on Mod B
|
||||
$modAv1 = ModVersion::factory()->create(['mod_id' => $modA->id, 'version' => '1.0.0']);
|
||||
$modDependency = ModDependency::factory()->recycle([$modAv1, $modB])->create([
|
||||
'version_constraint' => '^1.0.0',
|
||||
]);
|
||||
|
||||
$modDependency->refresh();
|
||||
expect($modDependency->resolvedVersion->version)->toBe('1.1.1');
|
||||
|
||||
// Update the mod B version
|
||||
$modBv3->update(['version' => '1.1.2']);
|
||||
|
||||
$modDependency->refresh();
|
||||
expect($modDependency->resolvedVersion->version)->toBe('1.1.2');
|
||||
});
|
||||
|
||||
it('resolves mod version dependency when mod version is deleted', function () {
|
||||
$modA = Mod::factory()->create(['name' => 'Mod A']);
|
||||
$modB = Mod::factory()->create(['name' => 'Mod B']);
|
||||
|
||||
// Create versions for Mod B
|
||||
ModVersion::factory()->create(['mod_id' => $modB->id, 'version' => '1.0.0']);
|
||||
ModVersion::factory()->create(['mod_id' => $modB->id, 'version' => '1.1.0']);
|
||||
$modBv3 = ModVersion::factory()->create(['mod_id' => $modB->id, 'version' => '1.1.1']);
|
||||
ModVersion::factory()->create(['mod_id' => $modB->id, 'version' => '2.0.0']);
|
||||
|
||||
// Create versions for Mod A that depends on Mod B
|
||||
$modAv1 = ModVersion::factory()->create(['mod_id' => $modA->id, 'version' => '1.0.0']);
|
||||
$modDependency = ModDependency::factory()->recycle([$modAv1, $modB])->create([
|
||||
'version_constraint' => '^1.0.0',
|
||||
]);
|
||||
|
||||
$modDependency->refresh();
|
||||
expect($modDependency->resolvedVersion->version)->toBe('1.1.1');
|
||||
|
||||
// Update the mod B version
|
||||
$modBv3->delete();
|
||||
|
||||
$modDependency->refresh();
|
||||
expect($modDependency->resolvedVersion->version)->toBe('1.1.0');
|
||||
});
|
||||
|
||||
it('resolves mod version dependency after semantic version constraint is updated', function () {
|
||||
$modA = Mod::factory()->create(['name' => 'Mod A']);
|
||||
$modB = Mod::factory()->create(['name' => 'Mod B']);
|
||||
|
||||
// Create versions for Mod B
|
||||
ModVersion::factory()->create(['mod_id' => $modB->id, 'version' => '1.0.0']);
|
||||
ModVersion::factory()->create(['mod_id' => $modB->id, 'version' => '1.1.0']);
|
||||
ModVersion::factory()->create(['mod_id' => $modB->id, 'version' => '1.1.1']);
|
||||
ModVersion::factory()->create(['mod_id' => $modB->id, 'version' => '2.0.0']);
|
||||
ModVersion::factory()->create(['mod_id' => $modB->id, 'version' => '2.0.1']);
|
||||
|
||||
// Create versions for Mod A that depends on Mod B
|
||||
$modAv1 = ModVersion::factory()->create(['mod_id' => $modA->id, 'version' => '1.0.0']);
|
||||
$modDependency = ModDependency::factory()->recycle([$modAv1, $modB])->create([
|
||||
'version_constraint' => '^1.0.0',
|
||||
]);
|
||||
|
||||
$modDependency->refresh();
|
||||
expect($modDependency->resolvedVersion->version)->toBe('1.1.1');
|
||||
|
||||
// Update the dependency version constraint
|
||||
$modDependency->update(['version_constraint' => '^2.0.0']);
|
||||
|
||||
$modDependency->refresh();
|
||||
expect($modDependency->resolvedVersion->version)->toBe('2.0.1');
|
||||
});
|
||||
|
||||
it('resolves mod version dependency with exact semantic version constraint', function () {
|
||||
$modA = Mod::factory()->create(['name' => 'Mod A']);
|
||||
$modB = Mod::factory()->create(['name' => 'Mod B']);
|
||||
|
||||
// Create versions for Mod B
|
||||
ModVersion::factory()->create(['mod_id' => $modB->id, 'version' => '1.0.0']);
|
||||
ModVersion::factory()->create(['mod_id' => $modB->id, 'version' => '1.1.0']);
|
||||
ModVersion::factory()->create(['mod_id' => $modB->id, 'version' => '1.1.1']);
|
||||
ModVersion::factory()->create(['mod_id' => $modB->id, 'version' => '2.0.0']);
|
||||
|
||||
// Create versions for Mod A that depends on Mod B
|
||||
$modAv1 = ModVersion::factory()->create(['mod_id' => $modA->id, 'version' => '1.0.0']);
|
||||
$modDependency = ModDependency::factory()->recycle([$modAv1, $modB])->create([
|
||||
'version_constraint' => '1.1.0',
|
||||
]);
|
||||
|
||||
$modDependency->refresh();
|
||||
expect($modDependency->resolvedVersion->version)->toBe('1.1.0');
|
||||
});
|
||||
|
||||
it('resolves mod version dependency with complex semantic version constraint', function () {
|
||||
$modA = Mod::factory()->create(['name' => 'Mod A']);
|
||||
$modB = Mod::factory()->create(['name' => 'Mod B']);
|
||||
|
||||
// Create versions for Mod B
|
||||
ModVersion::factory()->create(['mod_id' => $modB->id, 'version' => '1.0.0']);
|
||||
ModVersion::factory()->create(['mod_id' => $modB->id, 'version' => '1.1.0']);
|
||||
ModVersion::factory()->create(['mod_id' => $modB->id, 'version' => '1.1.1']);
|
||||
ModVersion::factory()->create(['mod_id' => $modB->id, 'version' => '1.2.0']);
|
||||
ModVersion::factory()->create(['mod_id' => $modB->id, 'version' => '1.2.1']);
|
||||
ModVersion::factory()->create(['mod_id' => $modB->id, 'version' => '2.0.0']);
|
||||
|
||||
// Create versions for Mod A that depends on Mod B
|
||||
$modAv1 = ModVersion::factory()->create(['mod_id' => $modA->id, 'version' => '1.0.0']);
|
||||
$modDependency = ModDependency::factory()->recycle([$modAv1, $modB])->create([
|
||||
'version_constraint' => '>=1.0.0 <2.0.0',
|
||||
]);
|
||||
|
||||
$modDependency->refresh();
|
||||
expect($modDependency->resolvedVersion->version)->toBe('1.2.1');
|
||||
|
||||
$modDependency->update(['version_constraint' => '1.0.0 || >=1.1.0 <1.2.0']);
|
||||
|
||||
$modDependency->refresh();
|
||||
expect($modDependency->resolvedVersion->version)->toBe('1.1.1');
|
||||
});
|
||||
|
||||
it('resolves null when no mod versions are available', function () {
|
||||
$modA = Mod::factory()->create(['name' => 'Mod A']);
|
||||
$modB = Mod::factory()->create(['name' => 'Mod B']);
|
||||
|
||||
// Create version for Mod A that has no resolvable dependency
|
||||
$modAv1 = ModVersion::factory()->create(['mod_id' => $modA->id, 'version' => '1.0.0']);
|
||||
$modDependency = ModDependency::factory()->recycle([$modAv1, $modB])->create([
|
||||
'version_constraint' => '^1.0.0',
|
||||
]);
|
||||
|
||||
$modDependency->refresh();
|
||||
expect($modDependency->resolved_version_id)->toBeNull();
|
||||
});
|
||||
|
||||
it('resolves null when no mod versions match against semantic version constraint', function () {
|
||||
$modA = Mod::factory()->create(['name' => 'Mod A']);
|
||||
$modB = Mod::factory()->create(['name' => 'Mod B']);
|
||||
|
||||
// Create versions for Mod B
|
||||
ModVersion::factory()->create(['mod_id' => $modB->id, 'version' => '1.0.0']);
|
||||
ModVersion::factory()->create(['mod_id' => $modB->id, 'version' => '1.1.0']);
|
||||
ModVersion::factory()->create(['mod_id' => $modB->id, 'version' => '2.0.0']);
|
||||
|
||||
// Create version for Mod A that has no resolvable dependency
|
||||
$modAv1 = ModVersion::factory()->create(['mod_id' => $modA->id, 'version' => '1.0.0']);
|
||||
$modDependency = ModDependency::factory()->recycle([$modAv1, $modB])->create([
|
||||
'version_constraint' => '~1.2.0',
|
||||
]);
|
||||
|
||||
$modDependency->refresh();
|
||||
expect($modDependency->resolved_version_id)->toBeNull();
|
||||
});
|
||||
|
||||
it('resolves multiple dependencies', function () {
|
||||
$modA = Mod::factory()->create(['name' => 'Mod A']);
|
||||
$modB = Mod::factory()->create(['name' => 'Mod B']);
|
||||
$modC = Mod::factory()->create(['name' => 'Mod C']);
|
||||
|
||||
ModVersion::factory()->create(['mod_id' => $modB->id, 'version' => '1.0.0']);
|
||||
ModVersion::factory()->create(['mod_id' => $modB->id, 'version' => '1.1.0']);
|
||||
ModVersion::factory()->create(['mod_id' => $modB->id, 'version' => '1.1.1']);
|
||||
ModVersion::factory()->create(['mod_id' => $modB->id, 'version' => '2.0.0']);
|
||||
|
||||
ModVersion::factory()->create(['mod_id' => $modC->id, 'version' => '1.0.0']);
|
||||
ModVersion::factory()->create(['mod_id' => $modC->id, 'version' => '1.1.0']);
|
||||
ModVersion::factory()->create(['mod_id' => $modC->id, 'version' => '1.1.1']);
|
||||
ModVersion::factory()->create(['mod_id' => $modC->id, 'version' => '2.0.0']);
|
||||
|
||||
// Creating a version for Mod A that depends on Mod B and Mod C
|
||||
$modAv1 = ModVersion::factory()->create(['mod_id' => $modA->id, 'version' => '1.0.0']);
|
||||
|
||||
$modDependencyB = ModDependency::factory()->recycle([$modAv1, $modB])->create([
|
||||
'version_constraint' => '^1.0.0',
|
||||
]);
|
||||
$modDependencyC = ModDependency::factory()->recycle([$modAv1, $modC])->create([
|
||||
'version_constraint' => '^1.0.0',
|
||||
]);
|
||||
|
||||
$modDependencyB->refresh();
|
||||
expect($modDependencyB->resolvedVersion->version)->toBe('1.1.1');
|
||||
|
||||
$modDependencyC->refresh();
|
||||
expect($modDependencyC->resolvedVersion->version)->toBe('1.1.1');
|
||||
});
|
||||
|
||||
it('throws exception when there is a circular version dependency', function () {
|
||||
$modA = Mod::factory()->create(['name' => 'Mod A']);
|
||||
$modAv1 = ModVersion::factory()->recycle($modA)->create(['version' => '1.0.0']);
|
||||
|
||||
$modB = Mod::factory()->create(['name' => 'Mod B']);
|
||||
$modBv1 = ModVersion::factory()->recycle($modB)->create(['version' => '1.0.0']);
|
||||
|
||||
$modDependencyAtoB = ModDependency::factory()->recycle([$modAv1, $modB])->create([
|
||||
'version_constraint' => '1.0.0',
|
||||
]);
|
||||
|
||||
// Create circular dependencies
|
||||
$modDependencyBtoA = ModDependency::factory()->recycle([$modBv1, $modA])->create([
|
||||
'version_constraint' => '1.0.0',
|
||||
]);
|
||||
})->throws(CircularDependencyException::class);
|
39
tests/Feature/ModVersionTest.php
Normal file
39
tests/Feature/ModVersionTest.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
use App\Models\ModVersion;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
it('includes only published mod versions', function () {
|
||||
$publishedMod = ModVersion::factory()->create([
|
||||
'published_at' => Carbon::now()->subDay(),
|
||||
]);
|
||||
$unpublishedMod = ModVersion::factory()->create([
|
||||
'published_at' => Carbon::now()->addDay(),
|
||||
]);
|
||||
$noPublishedDateMod = ModVersion::factory()->create([
|
||||
'published_at' => null,
|
||||
]);
|
||||
|
||||
$all = ModVersion::withoutGlobalScopes()->get();
|
||||
expect($all)->toHaveCount(3);
|
||||
|
||||
$mods = ModVersion::all();
|
||||
|
||||
expect($mods)->toHaveCount(1)
|
||||
->and($mods->contains($publishedMod))->toBeTrue()
|
||||
->and($mods->contains($unpublishedMod))->toBeFalse()
|
||||
->and($mods->contains($noPublishedDateMod))->toBeFalse();
|
||||
});
|
||||
|
||||
it('handles null published_at as not published', function () {
|
||||
$modWithNoPublishDate = ModVersion::factory()->create([
|
||||
'published_at' => null,
|
||||
]);
|
||||
|
||||
$mods = ModVersion::all();
|
||||
|
||||
expect($mods->contains($modWithNoPublishDate))->toBeFalse();
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user