diff --git a/app/Http/Controllers/ModController.php b/app/Http/Controllers/ModController.php index 22b4922..4016d76 100644 --- a/app/Http/Controllers/ModController.php +++ b/app/Http/Controllers/ModController.php @@ -30,14 +30,12 @@ class ModController extends Controller { $mod = Mod::with([ 'versions', - 'versions.latestSptVersion:id,version,color_class', + 'versions.latestSptVersion', 'versions.latestResolvedDependencies', - 'versions.latestResolvedDependencies.mod:id,name,slug', - 'users:id,name', - 'license:id,name,link', - ]) - ->whereHas('latestVersion') - ->findOrFail($modId); + 'versions.latestResolvedDependencies.mod', + 'license', + 'users', + ])->findOrFail($modId); if ($mod->slug !== $slug) { abort(404); diff --git a/app/Http/Filters/ModFilter.php b/app/Http/Filters/ModFilter.php index bd1ecf6..733b058 100644 --- a/app/Http/Filters/ModFilter.php +++ b/app/Http/Filters/ModFilter.php @@ -3,7 +3,6 @@ namespace App\Http\Filters; use App\Models\Mod; -use App\Models\ModVersion; use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Facades\DB; @@ -17,19 +16,21 @@ class ModFilter protected Builder $builder; /** - * The filter that should be applied to the query. + * The filters to apply. * * @var array */ protected array $filters; /** + * Constructor. + * * @param array $filters */ public function __construct(array $filters) { - $this->builder = $this->baseQuery(); $this->filters = $filters; + $this->builder = $this->baseQuery(); } /** @@ -39,25 +40,31 @@ class ModFilter */ private function baseQuery(): Builder { - // TODO: The latestVersion relationship is sometimes null here and it causes issues. We need to ensure that - // empty relationships are filtered out. Normally we would use the whereHas method, but it causes *major* - // performance issues in this case. We need to find a better solution. + return Mod::query() + ->select('mods.*') + ->whereExists(function ($query) { + $query->select(DB::raw(1)) + ->from('mod_versions') + ->join('mod_version_spt_version', 'mod_versions.id', '=', 'mod_version_spt_version.mod_version_id') + ->join('spt_versions', 'mod_version_spt_version.spt_version_id', '=', 'spt_versions.id') + ->whereColumn('mod_versions.mod_id', 'mods.id') + ->where('spt_versions.version', '!=', '0.0.0'); + }) + ->with([ + 'users:id,name', + 'latestVersion', + 'latestVersion.latestSptVersion', + ]); + } - return Mod::select([ - 'mods.id', - 'mods.name', - 'mods.slug', - 'mods.teaser', - 'mods.thumbnail', - 'mods.featured', - 'mods.downloads', - 'mods.created_at', - ])->with([ - 'users:id,name', - 'latestVersion' => function ($query) { - $query->with('latestSptVersion:id,version,color_class'); - }, - ]); + /** + * Filter the results by the given search term. + * + * @return Builder + */ + private function query(string $term): Builder + { + return $this->builder->whereLike('mods.name', "%{$term}%"); } /** @@ -83,36 +90,11 @@ class ModFilter */ private function order(string $type): Builder { - // We order the "recently updated" mods by the ModVersion's updated_at value. - if ($type === 'updated') { - return $this->builder - ->joinSub( - ModVersion::select('mod_id', DB::raw('MAX(updated_at) as latest_updated_at'))->groupBy('mod_id'), - 'latest_versions', - 'mods.id', - '=', - 'latest_versions.mod_id' - ) - ->orderByDesc('latest_versions.latest_updated_at'); - } - - // By default, we simply order by the column on the mods table/query. - $column = match ($type) { - 'downloaded' => 'downloads', - default => 'created_at', + return match ($type) { + 'updated' => $this->builder->orderByDesc('mods.updated_at'), // TODO: This needs to be updated when a version is updated. + 'downloaded' => $this->builder->orderByDesc('mods.downloads'), + default => $this->builder->orderByDesc('mods.created_at'), }; - - return $this->builder->orderByDesc($column); - } - - /** - * Filter the results by the given search term. - * - * @return Builder - */ - private function query(string $term): Builder - { - return $this->builder->whereLike('name', "%$term%"); } /** @@ -123,8 +105,8 @@ class ModFilter private function featured(string $option): Builder { return match ($option) { - 'exclude' => $this->builder->where('featured', false), - 'only' => $this->builder->where('featured', true), + 'exclude' => $this->builder->where('mods.featured', false), + 'only' => $this->builder->where('mods.featured', true), default => $this->builder, }; } @@ -137,28 +119,14 @@ class ModFilter */ private function sptVersions(array $versions): Builder { - // Parse the versions into major, minor, and patch arrays - $parsedVersions = array_map(fn ($version) => [ - 'major' => (int) explode('.', $version)[0], - 'minor' => (int) (explode('.', $version)[1] ?? 0), - 'patch' => (int) (explode('.', $version)[2] ?? 0), - ], $versions); - - [$majorVersions, $minorVersions, $patchVersions] = array_map('array_unique', [ - array_column($parsedVersions, 'major'), - array_column($parsedVersions, 'minor'), - array_column($parsedVersions, 'patch'), - ]); - - return $this->builder - ->join('mod_versions as mv', 'mods.id', '=', 'mv.mod_id') - ->join('mod_version_spt_version as mvsv', 'mv.id', '=', 'mvsv.mod_version_id') - ->join('spt_versions as sv', 'mvsv.spt_version_id', '=', 'sv.id') - ->whereIn('sv.version_major', $majorVersions) - ->whereIn('sv.version_minor', $minorVersions) - ->whereIn('sv.version_patch', $patchVersions) - ->where('sv.version', '!=', '0.0.0') - ->groupBy('mods.id') - ->distinct(); + return $this->builder->whereExists(function ($query) use ($versions) { + $query->select(DB::raw(1)) + ->from('mod_versions') + ->join('mod_version_spt_version', 'mod_versions.id', '=', 'mod_version_spt_version.mod_version_id') + ->join('spt_versions', 'mod_version_spt_version.spt_version_id', '=', 'spt_versions.id') + ->whereColumn('mod_versions.mod_id', 'mods.id') + ->whereIn('spt_versions.version', $versions) + ->where('spt_versions.version', '!=', '0.0.0'); + }); } } diff --git a/app/Jobs/Import/ImportHubDataJob.php b/app/Jobs/Import/ImportHubDataJob.php index 8be5a29..745a73f 100644 --- a/app/Jobs/Import/ImportHubDataJob.php +++ b/app/Jobs/Import/ImportHubDataJob.php @@ -2,6 +2,7 @@ namespace App\Jobs\Import; +use App\Exceptions\InvalidVersionNumberException; use App\Jobs\Import\DataTransferObjects\HubUser; use App\Models\License; use App\Models\Mod; @@ -9,6 +10,7 @@ use App\Models\ModVersion; use App\Models\SptVersion; use App\Models\User; use App\Models\UserRole; +use App\Support\Version; use Carbon\Carbon; use CurlHandle; use Exception; @@ -969,10 +971,20 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue $sptVersionTemp = DB::table('temp_spt_version_tags')->where('hub_id', $versionLabel->labelID)->value('version'); $sptVersionConstraint = $this->extractSemanticVersion($sptVersionTemp, appendPatch: true) ?? '0.0.0'; + try { + $modVersion = new Version($version->versionNumber); + } catch (InvalidVersionNumberException $e) { + $modVersion = new Version('0.0.0'); + } + $insertData[] = [ 'hub_id' => (int) $version->versionID, 'mod_id' => $modId, - 'version' => $this->extractSemanticVersion($version->versionNumber) ?? '0.0.0', + 'version' => $modVersion, + 'version_major' => $modVersion->getMajor(), + 'version_minor' => $modVersion->getMinor(), + 'version_patch' => $modVersion->getPatch(), + 'version_pre_release' => $modVersion->getPreRelease(), 'description' => $this->cleanHubContent($versionContent->description ?? ''), 'link' => $version->downloadURL, 'spt_version_constraint' => $sptVersionConstraint, diff --git a/app/Livewire/Mod/Listing.php b/app/Livewire/Mod/Listing.php index bc6d1d9..1edf8dd 100644 --- a/app/Livewire/Mod/Listing.php +++ b/app/Livewire/Mod/Listing.php @@ -3,6 +3,7 @@ namespace App\Livewire\Mod; use App\Http\Filters\ModFilter; +use App\Models\Mod; use App\Models\SptVersion; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Contracts\View\View; @@ -95,6 +96,8 @@ class Listing extends Component /** * Check if the current page is greater than the last page. Redirect if it is. + * + * @param LengthAwarePaginator $mods */ private function redirectOutOfBoundsPage(LengthAwarePaginator $mods): void { diff --git a/app/Models/Mod.php b/app/Models/Mod.php index 6e3ce68..75755bc 100644 --- a/app/Models/Mod.php +++ b/app/Models/Mod.php @@ -66,6 +66,19 @@ class Mod extends Model return $this->belongsTo(License::class); } + /** + * The relationship between a mod and its last updated version. + * + * @return HasOne + */ + public function latestUpdatedVersion(): HasOne + { + return $this->versions() + ->one() + ->ofMany('updated_at', 'max') + ->chaperone(); + } + /** * The relationship between a mod and its versions. * @@ -74,21 +87,10 @@ class Mod extends Model public function versions(): HasMany { return $this->hasMany(ModVersion::class) - ->whereHas('latestSptVersion') - ->orderByDesc('version') - ->chaperone(); - } - - /** - * The relationship between a mod and its last updated version. - * - * @return HasOne - */ - public function lastUpdatedVersion(): HasOne - { - return $this->hasOne(ModVersion::class) - ->whereHas('latestSptVersion') - ->orderByDesc('updated_at') + ->orderByDesc('version_major') + ->orderByDesc('version_minor') + ->orderByDesc('version_patch') + ->orderByDesc('version_pre_release') ->chaperone(); } @@ -109,26 +111,11 @@ class Mod extends Model 'created_at' => strtotime($this->created_at), 'updated_at' => strtotime($this->updated_at), 'published_at' => strtotime($this->published_at), - 'latestVersion' => $this->latestVersion()->first()->latestSptVersion()->first()->version_formatted, - 'latestVersionColorClass' => $this->latestVersion()->first()->latestSptVersion()->first()->color_class, + 'latestVersion' => $this->latestVersion->latestSptVersion->version_formatted, + 'latestVersionColorClass' => $this->latestVersion->latestSptVersion->color_class, ]; } - /** - * The relationship to the latest mod version, dictated by the mod version number. - * - * @return HasOne - */ - public function latestVersion(): HasOne - { - return $this->hasOne(ModVersion::class) - ->whereHas('sptVersions') - ->orderByDesc('version') - ->orderByDesc('updated_at') - ->take(1) - ->chaperone(); - } - /** * Determine if the model instance should be searchable. */ @@ -144,16 +131,13 @@ class Mod extends Model return false; } - // Fetch the latest version instance. - $latestVersion = $this->latestVersion()->first(); - // Ensure the mod has a latest version. - if (is_null($latestVersion)) { + if ($this->latestVersion()->doesntExist()) { return false; } // Ensure the latest version has a latest SPT version. - if ($latestVersion->latestSptVersion()->doesntExist()) { + if ($this->latestVersion->latestSptVersion()->doesntExist()) { return false; } @@ -161,7 +145,7 @@ class Mod extends Model $activeSptVersions = Cache::remember('active-spt-versions', 60 * 60, function () { return SptVersion::getVersionsForLastThreeMinors(); }); - if (! in_array($latestVersion->latestSptVersion()->first()->version, $activeSptVersions->pluck('version')->toArray())) { + if (! in_array($this->latestVersion->latestSptVersion->version, $activeSptVersions->pluck('version')->toArray())) { return false; } @@ -169,6 +153,24 @@ class Mod extends Model return true; } + /** + * The relationship between a mod and its latest version. + * + * @return HasOne + */ + public function latestVersion(string $sort = 'version'): HasOne + { + return $this->versions() + ->one() + ->ofMany([ + 'version_major' => 'max', + 'version_minor' => 'max', + 'version_patch' => 'max', + 'version_pre_release' => 'max', + ]) + ->chaperone(); + } + /** * Build the URL to the mod's thumbnail. * diff --git a/app/Models/ModVersion.php b/app/Models/ModVersion.php index abef339..6da4072 100644 --- a/app/Models/ModVersion.php +++ b/app/Models/ModVersion.php @@ -2,14 +2,17 @@ namespace App\Models; +use App\Exceptions\InvalidVersionNumberException; use App\Models\Scopes\DisabledScope; use App\Models\Scopes\PublishedScope; +use App\Support\Version; use Database\Factories\ModFactory; 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\Relations\HasOneThrough; use Illuminate\Database\Eloquent\SoftDeletes; class ModVersion extends Model @@ -25,7 +28,25 @@ class ModVersion extends Model protected static function booted(): void { static::addGlobalScope(new DisabledScope); + static::addGlobalScope(new PublishedScope); + + static::saving(function (ModVersion $model) { + // Extract the version sections from the version string. + try { + $version = new Version($model->version); + + $model->version_major = $version->getMajor(); + $model->version_minor = $version->getMinor(); + $model->version_patch = $version->getPatch(); + $model->version_pre_release = $version->getPreRelease(); + } catch (InvalidVersionNumberException $e) { + $model->version_major = 0; + $model->version_minor = 0; + $model->version_patch = 0; + $model->version_pre_release = ''; + } + }); } /** @@ -79,15 +100,17 @@ class ModVersion extends Model } /** - * The relationship between a mod version and each of its SPT versions' latest version. - * Hint: Be sure to call `->first()` on this to get the actual instance. + * The relationship between a mod version and its latest SPT version. * - * @return BelongsToMany + * @return HasOneThrough */ - public function latestSptVersion(): BelongsToMany + public function latestSptVersion(): HasOneThrough { - return $this->belongsToMany(SptVersion::class, 'mod_version_spt_version') - ->orderBy('version', 'desc') + return $this->hasOneThrough(SptVersion::class, ModVersionSptVersion::class, 'mod_version_id', 'id', 'id', 'spt_version_id') + ->orderByDesc('spt_versions.version_major') + ->orderByDesc('spt_versions.version_minor') + ->orderByDesc('spt_versions.version_patch') + ->orderByDesc('spt_versions.version_pre_release') ->limit(1); } @@ -98,7 +121,11 @@ class ModVersion extends Model */ public function sptVersions(): BelongsToMany { - return $this->belongsToMany(SptVersion::class, 'mod_version_spt_version') - ->orderByDesc('version'); + return $this->belongsToMany(SptVersion::class) + ->using(ModVersionSptVersion::class) + ->orderByDesc('version_major') + ->orderByDesc('version_minor') + ->orderByDesc('version_patch') + ->orderByDesc('version_pre_release'); } } diff --git a/app/Models/ModVersionSptVersion.php b/app/Models/ModVersionSptVersion.php new file mode 100644 index 0000000..355dce7 --- /dev/null +++ b/app/Models/ModVersionSptVersion.php @@ -0,0 +1,10 @@ +toArray(); } - /** - * Called when the model is booted. - */ - protected static function booted(): void - { - // Callback that runs before saving the model. - static::saving(function (SptVersion $model) { - // Extract the version sections from the version string. - if (! empty($model->version)) { - // Default values in case there's an exception. - $model->version_major = 0; - $model->version_minor = 0; - $model->version_patch = 0; - $model->version_pre_release = ''; - - try { - $versionSections = self::extractVersionSections($model->version); - } catch (InvalidVersionNumberException $e) { - return; - } - - $model->version_major = $versionSections['major']; - $model->version_minor = $versionSections['minor']; - $model->version_patch = $versionSections['patch']; - $model->version_pre_release = $versionSections['pre_release']; - } - }); - } - /** * Extract the version sections from the version string. * @@ -126,6 +98,29 @@ class SptVersion extends Model ]; } + /** + * Called when the model is booted. + */ + protected static function booted(): void + { + static::saving(function (SptVersion $model) { + // Extract the version sections from the version string. + try { + $version = new Version($model->version); + + $model->version_major = $version->getMajor(); + $model->version_minor = $version->getMinor(); + $model->version_patch = $version->getPatch(); + $model->version_pre_release = $version->getPreRelease(); + } catch (InvalidVersionNumberException $e) { + $model->version_major = 0; + $model->version_minor = 0; + $model->version_patch = 0; + $model->version_pre_release = ''; + } + }); + } + /** * Update the mod count for this SptVersion. */ @@ -146,7 +141,8 @@ class SptVersion extends Model */ public function modVersions(): BelongsToMany { - return $this->belongsToMany(ModVersion::class, 'mod_version_spt_version'); + return $this->belongsToMany(ModVersion::class) + ->using(ModVersionSptVersion::class); } /** diff --git a/app/Support/Version.php b/app/Support/Version.php new file mode 100644 index 0000000..21c00cb --- /dev/null +++ b/app/Support/Version.php @@ -0,0 +1,74 @@ +version = $version; + $this->parseVersion(); + } + + /** + * Parse the version string into its components. + * + * @throws InvalidVersionNumberException + */ + protected function parseVersion(): void + { + $matches = []; + + // Regex to match semantic versioning, including pre-release identifiers + if (preg_match('/^(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:-([\w.-]+))?$/', $this->version, $matches)) { + $this->major = (int) ($matches[1] ?? 0); + $this->minor = (int) ($matches[2] ?? 0); + $this->patch = (int) ($matches[3] ?? 0); + $this->preRelease = $matches[4] ?? ''; + } else { + throw new InvalidVersionNumberException('Invalid version number: '.$this->version); + } + } + + public function getMajor(): int + { + return $this->major; + } + + public function getMinor(): int + { + return $this->minor; + } + + public function getPatch(): int + { + return $this->patch; + } + + public function getPreRelease(): string + { + return $this->preRelease; + } + + public function __toString(): string + { + return $this->version; + } +} diff --git a/app/View/Components/HomepageMods.php b/app/View/Components/HomepageMods.php index 54f8b3a..7666ee7 100644 --- a/app/View/Components/HomepageMods.php +++ b/app/View/Components/HomepageMods.php @@ -3,10 +3,8 @@ namespace App\View\Components; use App\Models\Mod; -use App\Models\ModVersion; use Illuminate\Contracts\View\View; use Illuminate\Database\Eloquent\Collection; -use Illuminate\Support\Facades\DB; use Illuminate\View\Component; class HomepageMods extends Component @@ -39,13 +37,13 @@ class HomepageMods extends Component */ private function fetchFeaturedMods(): Collection { - return Mod::select(['id', 'name', 'slug', 'teaser', 'thumbnail', 'featured', 'downloads', 'updated_at']) + return Mod::whereFeatured(true) ->with([ - 'latestVersion.latestSptVersion:id,version,color_class', + 'latestVersion', + 'latestVersion.latestSptVersion', 'users:id,name', 'license:id,name,link', ]) - ->whereFeatured(true) ->inRandomOrder() ->limit(6) ->get(); @@ -58,13 +56,13 @@ class HomepageMods extends Component */ private function fetchLatestMods(): Collection { - return Mod::select(['id', 'name', 'slug', 'teaser', 'thumbnail', 'featured', 'created_at', 'downloads', 'updated_at']) + return Mod::orderByDesc('created_at') ->with([ - 'latestVersion.latestSptVersion:id,version,color_class', + 'latestVersion', + 'latestVersion.latestSptVersion', 'users:id,name', 'license:id,name,link', ]) - ->latest() ->limit(6) ->get(); } @@ -76,20 +74,13 @@ class HomepageMods extends Component */ private function fetchUpdatedMods(): Collection { - return Mod::select(['id', 'name', 'slug', 'teaser', 'thumbnail', 'featured', 'downloads', 'updated_at']) + return Mod::orderByDesc('updated_at') ->with([ - 'lastUpdatedVersion.latestSptVersion:id,version,color_class', + 'latestUpdatedVersion', + 'latestUpdatedVersion.latestSptVersion', 'users:id,name', 'license:id,name,link', ]) - ->joinSub( - ModVersion::select('mod_id', DB::raw('MAX(updated_at) as latest_updated_at'))->groupBy('mod_id'), - 'latest_versions', - 'mods.id', - '=', - 'latest_versions.mod_id' - ) - ->orderByDesc('latest_versions.latest_updated_at') ->limit(6) ->get(); } diff --git a/database/factories/ModVersionFactory.php b/database/factories/ModVersionFactory.php index ade126e..100093c 100644 --- a/database/factories/ModVersionFactory.php +++ b/database/factories/ModVersionFactory.php @@ -5,6 +5,7 @@ namespace Database\Factories; use App\Models\Mod; use App\Models\ModVersion; use App\Models\SptVersion; +use App\Support\Version; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Carbon; @@ -17,9 +18,20 @@ class ModVersionFactory extends Factory public function definition(): array { + $versionString = $this->faker->numerify('#.#.#'); + try { + $version = new Version($versionString); + } catch (\Exception $e) { + $version = new Version('0.0.0'); + } + return [ 'mod_id' => Mod::factory(), - 'version' => fake()->numerify('#.#.#'), + 'version' => $versionString, + 'version_major' => $version->getMajor(), + 'version_minor' => $version->getMinor(), + 'version_patch' => $version->getPatch(), + 'version_pre_release' => $version->getPreRelease(), 'description' => fake()->text(), 'link' => fake()->url(), diff --git a/database/factories/SptVersionFactory.php b/database/factories/SptVersionFactory.php index 707ed46..96acc21 100644 --- a/database/factories/SptVersionFactory.php +++ b/database/factories/SptVersionFactory.php @@ -3,6 +3,7 @@ namespace Database\Factories; use App\Models\SptVersion; +use App\Support\Version; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Carbon; @@ -15,8 +16,19 @@ class SptVersionFactory extends Factory public function definition(): array { + $versionString = $this->faker->numerify('#.#.#'); + try { + $version = new Version($versionString); + } catch (\Exception $e) { + $version = new Version('0.0.0'); + } + return [ - 'version' => $this->faker->numerify('#.#.#'), + 'version' => $versionString, + 'version_major' => $version->getMajor(), + 'version_minor' => $version->getMinor(), + 'version_patch' => $version->getPatch(), + 'version_pre_release' => $version->getPreRelease(), 'color_class' => $this->faker->randomElement(['red', 'green', 'emerald', 'lime', 'yellow', 'grey']), 'link' => $this->faker->url, 'created_at' => Carbon::now(), diff --git a/database/migrations/2024_05_15_023430_create_spt_versions_table.php b/database/migrations/2024_05_15_023430_create_spt_versions_table.php index 1c6a0a0..15c9cf8 100644 --- a/database/migrations/2024_05_15_023430_create_spt_versions_table.php +++ b/database/migrations/2024_05_15_023430_create_spt_versions_table.php @@ -15,18 +15,17 @@ return new class extends Migration ->default(null) ->unique(); $table->string('version'); - $table->unsignedInteger('version_major'); - $table->unsignedInteger('version_minor'); - $table->unsignedInteger('version_patch'); - $table->string('version_pre_release'); + $table->unsignedInteger('version_major')->default(0); + $table->unsignedInteger('version_minor')->default(0); + $table->unsignedInteger('version_patch')->default(0); + $table->string('version_pre_release')->default(''); $table->unsignedInteger('mod_count')->default(0); $table->string('link'); $table->string('color_class'); $table->softDeletes(); $table->timestamps(); - $table->index(['version', 'deleted_at', 'id'], 'spt_versions_filtering_index'); - $table->index(['version_major', 'version_minor', 'version_patch', 'version_pre_release', 'deleted_at'], 'spt_versions_lookup_index'); + $table->index(['version', 'version_major', 'version_minor', 'version_patch', 'version_pre_release'], 'spt_versions_lookup_index'); }); } diff --git a/database/migrations/2024_05_15_023705_create_mod_versions_table.php b/database/migrations/2024_05_15_023705_create_mod_versions_table.php index 0301ead..565716c 100644 --- a/database/migrations/2024_05_15_023705_create_mod_versions_table.php +++ b/database/migrations/2024_05_15_023705_create_mod_versions_table.php @@ -20,6 +20,10 @@ return new class extends Migration ->cascadeOnDelete() ->cascadeOnUpdate(); $table->string('version'); + $table->unsignedInteger('version_major')->default(0); + $table->unsignedInteger('version_minor')->default(0); + $table->unsignedInteger('version_patch')->default(0); + $table->string('version_pre_release')->default(''); $table->longText('description'); $table->string('link'); $table->string('spt_version_constraint'); @@ -32,6 +36,7 @@ return new class extends Migration $table->index(['version']); $table->index(['mod_id', 'deleted_at', 'disabled', 'published_at'], 'mod_versions_filtering_index'); + $table->index(['version_major', 'version_minor', 'version_patch', 'version_pre_release'], 'mod_versions_version_components_index'); $table->index(['id', 'deleted_at'], 'mod_versions_id_deleted_at_index'); }); } diff --git a/database/migrations/2024_05_15_023710_create_mod_version_spt_version_table.php b/database/migrations/2024_05_15_023710_create_mod_version_spt_version_table.php index 2848d91..1084253 100644 --- a/database/migrations/2024_05_15_023710_create_mod_version_spt_version_table.php +++ b/database/migrations/2024_05_15_023710_create_mod_version_spt_version_table.php @@ -12,7 +12,6 @@ return new class extends Migration $table->id(); $table->foreignId('mod_version_id')->constrained('mod_versions')->cascadeOnDelete()->cascadeOnUpdate(); $table->foreignId('spt_version_id')->constrained('spt_versions')->cascadeOnDelete()->cascadeOnUpdate(); - $table->timestamps(); $table->index(['mod_version_id', 'spt_version_id'], 'mod_version_spt_version_index'); $table->index(['spt_version_id', 'mod_version_id'], 'spt_version_mod_version_index'); diff --git a/resources/views/components/homepage-mods.blade.php b/resources/views/components/homepage-mods.blade.php index c02ad27..eade9a1 100644 --- a/resources/views/components/homepage-mods.blade.php +++ b/resources/views/components/homepage-mods.blade.php @@ -16,7 +16,7 @@
@foreach ($updated['mods'] as $mod) - + @endforeach
diff --git a/resources/views/components/mod-card.blade.php b/resources/views/components/mod-card.blade.php index 55a12be..3aff1ef 100644 --- a/resources/views/components/mod-card.blade.php +++ b/resources/views/components/mod-card.blade.php @@ -18,8 +18,8 @@

{{ $mod->name }}

- - {{ $version->latestSptVersion->first()->version_formatted }} + + {{ $version->latestSptVersion->version_formatted }}

diff --git a/resources/views/mod/show.blade.php b/resources/views/mod/show.blade.php index 42833be..e6350d5 100644 --- a/resources/views/mod/show.blade.php +++ b/resources/views/mod/show.blade.php @@ -40,8 +40,8 @@

{{ Number::downloads($mod->downloads) }} {{ __('Downloads') }}

- - {{ $mod->latestVersion->latestSptVersion->first()->version_formatted }} {{ __('Compatible') }} + + {{ $mod->latestVersion->latestSptVersion->version_formatted }} {{ __('Compatible') }}

@@ -108,8 +108,8 @@

{{ Number::downloads($version->downloads) }} {{ __('Downloads') }}

- - {{ $version->latestSptVersion->first()->version_formatted }} + + {{ $version->latestSptVersion->version_formatted }} {{__('Virus Total Results')}}
diff --git a/tests/Feature/Mod/ModFilterTest.php b/tests/Feature/Mod/ModFilterTest.php index 6e4d998..a2b5b26 100644 --- a/tests/Feature/Mod/ModFilterTest.php +++ b/tests/Feature/Mod/ModFilterTest.php @@ -99,6 +99,36 @@ it('returns no mods when no SPT versions match', function () { expect($filteredMods)->toBeEmpty(); }); +it('filters mods based on a exact search term', function () { + SptVersion::factory()->create(['version' => '1.0.0']); + + $mod = Mod::factory()->create(['name' => 'BigBrain']); + ModVersion::factory()->recycle($mod)->create(['spt_version_constraint' => '^1.0.0']); + + Mod::factory()->create(['name' => 'SmallFeet']); + ModVersion::factory()->recycle($mod)->create(['spt_version_constraint' => '^1.0.0']); + + $filters = ['query' => 'BigBrain']; + $filteredMods = (new ModFilter($filters))->apply()->get(); + + expect($filteredMods)->toHaveCount(1)->and($filteredMods->first()->id)->toBe($mod->id); +}); + +it('filters mods based featured status', function () { + SptVersion::factory()->create(['version' => '1.0.0']); + + $mod = Mod::factory()->create(['name' => 'BigBrain', 'featured' => true]); + ModVersion::factory()->recycle($mod)->create(['spt_version_constraint' => '^1.0.0']); + + Mod::factory()->create(['name' => 'SmallFeet']); + ModVersion::factory()->recycle($mod)->create(['spt_version_constraint' => '^1.0.0']); + + $filters = ['featured' => true]; + $filteredMods = (new ModFilter($filters))->apply()->get(); + + expect($filteredMods)->toHaveCount(1)->and($filteredMods->first()->id)->toBe($mod->id); +}); + it('filters mods correctly with combined filters', function () { // Create the SPT versions $sptVersion1 = SptVersion::factory()->create(['version' => '1.0.0']); @@ -128,8 +158,7 @@ it('filters mods correctly with combined filters', function () { $filteredMods = (new ModFilter($filters))->apply()->get(); // Assert that only the correct mod is returned - expect($filteredMods)->toHaveCount(1) - ->and($filteredMods->first()->id)->toBe($mod1->id); + expect($filteredMods)->toHaveCount(1)->and($filteredMods->first()->id)->toBe($mod1->id); }); it('handles an empty SPT versions array correctly', function () {