mirror of
https://github.com/sp-tarkov/forge.git
synced 2025-02-12 12:10:41 -05:00
Merge remote-tracking branch 'upstream/develop' into user-profile-info
This commit is contained in:
commit
743ed27bd0
@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Jobs\ImportHubDataJob;
|
||||
use App\Jobs\Import\ImportHubDataJob;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ImportHubCommand extends Command
|
||||
|
@ -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);
|
||||
|
@ -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<string, mixed>
|
||||
*/
|
||||
protected array $filters;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array<string, mixed> $filters
|
||||
*/
|
||||
public function __construct(array $filters)
|
||||
{
|
||||
$this->builder = $this->baseQuery();
|
||||
$this->filters = $filters;
|
||||
$this->builder = $this->baseQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -39,21 +40,31 @@ class ModFilter
|
||||
*/
|
||||
private function baseQuery(): Builder
|
||||
{
|
||||
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');
|
||||
},
|
||||
]);
|
||||
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',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the results by the given search term.
|
||||
*
|
||||
* @return Builder<Mod>
|
||||
*/
|
||||
private function query(string $term): Builder
|
||||
{
|
||||
return $this->builder->whereLike('mods.name', "%{$term}%");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -79,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<Mod>
|
||||
*/
|
||||
private function query(string $term): Builder
|
||||
{
|
||||
return $this->builder->whereLike('name', "%$term%");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -119,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,
|
||||
};
|
||||
}
|
||||
@ -133,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');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -2,9 +2,13 @@
|
||||
|
||||
namespace App\Http\Filters\V1;
|
||||
|
||||
use App\Models\Mod;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* @extends QueryFilter<Mod>
|
||||
*/
|
||||
class ModFilter extends QueryFilter
|
||||
{
|
||||
protected array $sortable = [
|
||||
@ -23,6 +27,11 @@ class ModFilter extends QueryFilter
|
||||
// TODO: Many of these are repeated across UserFilter and ModFilter. Consider refactoring into a shared trait.
|
||||
// Also, consider using common filter types and making the field names dynamic.
|
||||
|
||||
/**
|
||||
* Filter by ID.
|
||||
*
|
||||
* @return Builder<Mod>
|
||||
*/
|
||||
public function id(string $value): Builder
|
||||
{
|
||||
$ids = array_map('trim', explode(',', $value));
|
||||
@ -30,6 +39,11 @@ class ModFilter extends QueryFilter
|
||||
return $this->builder->whereIn('id', $ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by hub ID.
|
||||
*
|
||||
* @return Builder<Mod>
|
||||
*/
|
||||
public function hub_id(string $value): Builder
|
||||
{
|
||||
$ids = array_map('trim', explode(',', $value));
|
||||
@ -37,6 +51,11 @@ class ModFilter extends QueryFilter
|
||||
return $this->builder->whereIn('hub_id', $ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by name.
|
||||
*
|
||||
* @return Builder<Mod>
|
||||
*/
|
||||
public function name(string $value): Builder
|
||||
{
|
||||
// The API handles the wildcard character as an asterisk (*), but the database uses the percentage sign (%).
|
||||
@ -45,6 +64,11 @@ class ModFilter extends QueryFilter
|
||||
return $this->builder->where('name', 'like', $like);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by slug.
|
||||
*
|
||||
* @return Builder<Mod>
|
||||
*/
|
||||
public function slug(string $value): Builder
|
||||
{
|
||||
// The API handles the wildcard character as an asterisk (*), but the database uses the percentage sign (%).
|
||||
@ -53,6 +77,11 @@ class ModFilter extends QueryFilter
|
||||
return $this->builder->where('slug', 'like', $like);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by teaser.
|
||||
*
|
||||
* @return Builder<Mod>
|
||||
*/
|
||||
public function teaser(string $value): Builder
|
||||
{
|
||||
// The API handles the wildcard character as an asterisk (*), but the database uses the percentage sign (%).
|
||||
@ -61,6 +90,11 @@ class ModFilter extends QueryFilter
|
||||
return $this->builder->where('teaser', 'like', $like);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by source code link.
|
||||
*
|
||||
* @return Builder<Mod>
|
||||
*/
|
||||
public function source_code_link(string $value): Builder
|
||||
{
|
||||
// The API handles the wildcard character as an asterisk (*), but the database uses the percentage sign (%).
|
||||
@ -69,6 +103,11 @@ class ModFilter extends QueryFilter
|
||||
return $this->builder->where('source_code_link', 'like', $like);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by created at date.
|
||||
*
|
||||
* @return Builder<Mod>
|
||||
*/
|
||||
public function created_at(string $value): Builder
|
||||
{
|
||||
// The API allows for a range of dates to be passed as a comma-separated list.
|
||||
@ -80,6 +119,11 @@ class ModFilter extends QueryFilter
|
||||
return $this->builder->whereDate('created_at', $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by updated at date.
|
||||
*
|
||||
* @return Builder<Mod>
|
||||
*/
|
||||
public function updated_at(string $value): Builder
|
||||
{
|
||||
// The API allows for a range of dates to be passed as a comma-separated list.
|
||||
@ -91,6 +135,11 @@ class ModFilter extends QueryFilter
|
||||
return $this->builder->whereDate('updated_at', $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by published at date.
|
||||
*
|
||||
* @return Builder<Mod>
|
||||
*/
|
||||
public function published_at(string $value): Builder
|
||||
{
|
||||
// The API allows for a range of dates to be passed as a comma-separated list.
|
||||
@ -102,6 +151,11 @@ class ModFilter extends QueryFilter
|
||||
return $this->builder->whereDate('published_at', $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by featured.
|
||||
*
|
||||
* @return Builder<Mod>
|
||||
*/
|
||||
public function featured(string $value): Builder
|
||||
{
|
||||
// We need to convert the string user input to a boolean, or null if it's not a valid "truthy/falsy" value.
|
||||
@ -115,6 +169,11 @@ class ModFilter extends QueryFilter
|
||||
return $this->builder->where('featured', $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by contains ads.
|
||||
*
|
||||
* @return Builder<Mod>
|
||||
*/
|
||||
public function contains_ads(string $value): Builder
|
||||
{
|
||||
// We need to convert the string user input to a boolean, or null if it's not a valid "truthy/falsy" value.
|
||||
@ -128,6 +187,11 @@ class ModFilter extends QueryFilter
|
||||
return $this->builder->where('contains_ads', $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by contains AI content.
|
||||
*
|
||||
* @return Builder<Mod>
|
||||
*/
|
||||
public function contains_ai_content(string $value): Builder
|
||||
{
|
||||
// We need to convert the string user input to a boolean, or null if it's not a valid "truthy/falsy" value.
|
||||
|
@ -3,15 +3,25 @@
|
||||
namespace App\Http\Filters\V1;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* @template TModelClass of Model
|
||||
*/
|
||||
abstract class QueryFilter
|
||||
{
|
||||
/**
|
||||
* The query builder instance.
|
||||
*
|
||||
* @var Builder<TModelClass>
|
||||
*/
|
||||
protected Builder $builder;
|
||||
|
||||
protected Request $request;
|
||||
|
||||
/** @var array<string> */
|
||||
protected array $sortable = [];
|
||||
|
||||
public function __construct(Request $request)
|
||||
@ -19,6 +29,12 @@ abstract class QueryFilter
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the filter to the query builder.
|
||||
*
|
||||
* @param Builder<TModelClass> $builder
|
||||
* @return Builder<TModelClass>
|
||||
*/
|
||||
public function apply(Builder $builder): Builder
|
||||
{
|
||||
$this->builder = $builder;
|
||||
@ -32,17 +48,11 @@ abstract class QueryFilter
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
protected function filter(array $filters): Builder
|
||||
{
|
||||
foreach ($filters as $attribute => $value) {
|
||||
if (method_exists($this, $attribute)) {
|
||||
$this->$attribute($value);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the sort type to the query.
|
||||
*
|
||||
* @return Builder<TModelClass>
|
||||
*/
|
||||
protected function sort(string $values): Builder
|
||||
{
|
||||
$sortables = array_map('trim', explode(',', $values));
|
||||
|
@ -2,11 +2,20 @@
|
||||
|
||||
namespace App\Http\Filters\V1;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* @extends QueryFilter<User>
|
||||
*/
|
||||
class UserFilter extends QueryFilter
|
||||
{
|
||||
/**
|
||||
* The sortable fields.
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
protected array $sortable = [
|
||||
'name',
|
||||
'created_at',
|
||||
@ -16,6 +25,11 @@ class UserFilter extends QueryFilter
|
||||
// TODO: Many of these are repeated across UserFilter and ModFilter. Consider refactoring into a shared trait.
|
||||
// Also, consider using common filter types and making the field names dynamic.
|
||||
|
||||
/**
|
||||
* Filter by ID.
|
||||
*
|
||||
* @return Builder<User>
|
||||
*/
|
||||
public function id(string $value): Builder
|
||||
{
|
||||
$ids = array_map('trim', explode(',', $value));
|
||||
@ -23,6 +37,11 @@ class UserFilter extends QueryFilter
|
||||
return $this->builder->whereIn('id', $ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by name.
|
||||
*
|
||||
* @return Builder<User>
|
||||
*/
|
||||
public function name(string $value): Builder
|
||||
{
|
||||
// The API handles the wildcard character as an asterisk (*), but the database uses the percentage sign (%).
|
||||
@ -31,6 +50,11 @@ class UserFilter extends QueryFilter
|
||||
return $this->builder->where('name', 'like', $like);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by created at date.
|
||||
*
|
||||
* @return Builder<User>
|
||||
*/
|
||||
public function created_at(string $value): Builder
|
||||
{
|
||||
// The API allows for a range of dates to be passed as a comma-separated list.
|
||||
@ -42,6 +66,11 @@ class UserFilter extends QueryFilter
|
||||
return $this->builder->whereDate('created_at', $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by updated at date.
|
||||
*
|
||||
* @return Builder<User>
|
||||
*/
|
||||
public function updated_at(string $value): Builder
|
||||
{
|
||||
// The API allows for a range of dates to be passed as a comma-separated list.
|
||||
|
@ -16,6 +16,8 @@ class LoginUserRequest extends FormRequest
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
|
@ -16,11 +16,11 @@ class StoreModRequest extends FormRequest
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
@ -16,11 +16,11 @@ class StoreUserRequest extends FormRequest
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
@ -16,11 +16,11 @@ class UpdateModRequest extends FormRequest
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
@ -16,11 +16,11 @@ class UpdateUserRequest extends FormRequest
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,11 @@ use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ModRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
@ -18,6 +23,9 @@ class ModRequest extends FormRequest
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
|
@ -9,6 +9,11 @@ use Illuminate\Http\Resources\Json\JsonResource;
|
||||
/** @mixin License */
|
||||
class LicenseResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
|
@ -12,6 +12,8 @@ class ModResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
|
@ -11,6 +11,8 @@ class ModVersionResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
@ -32,7 +34,6 @@ class ModVersionResource extends JsonResource
|
||||
// downloads that are made, so we'll need a new route/feature for that. #35
|
||||
'link' => $this->link,
|
||||
|
||||
'spt_version_id' => $this->spt_version_id,
|
||||
'virus_total_link' => $this->virus_total_link,
|
||||
'downloads' => $this->downloads,
|
||||
'created_at' => $this->created_at,
|
||||
@ -44,7 +45,6 @@ class ModVersionResource extends JsonResource
|
||||
[
|
||||
'data' => [
|
||||
'type' => 'spt_version',
|
||||
'id' => $this->spt_version_id,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
@ -10,6 +10,11 @@ use Illuminate\Http\Resources\Json\JsonResource;
|
||||
/** @mixin User */
|
||||
class UserResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
|
@ -9,6 +9,11 @@ use Illuminate\Http\Resources\Json\JsonResource;
|
||||
/** @mixin UserRole */
|
||||
class UserRoleResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
|
@ -2,12 +2,18 @@
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use App\Models\License;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
/** @mixin \App\Models\License */
|
||||
/** @mixin License */
|
||||
class LicenseResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
|
@ -2,12 +2,18 @@
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use App\Models\Mod;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
/** @mixin \App\Models\Mod */
|
||||
/** @mixin Mod */
|
||||
class ModResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
|
@ -2,12 +2,18 @@
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use App\Models\ModVersion;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
/** @mixin \App\Models\ModVersion */
|
||||
/** @mixin ModVersion */
|
||||
class ModVersionResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
@ -19,10 +25,7 @@ class ModVersionResource extends JsonResource
|
||||
'description' => $this->description,
|
||||
'virus_total_link' => $this->virus_total_link,
|
||||
'downloads' => $this->downloads,
|
||||
|
||||
'mod_id' => $this->mod_id,
|
||||
'spt_version_id' => $this->spt_version_id,
|
||||
|
||||
'mod' => new ModResource($this->whenLoaded('mod')),
|
||||
'sptVersion' => new SptVersionResource($this->whenLoaded('sptVersion')),
|
||||
];
|
||||
|
@ -2,12 +2,18 @@
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use App\Models\SptVersion;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
/** @mixin \App\Models\SptVersion */
|
||||
/** @mixin SptVersion */
|
||||
class SptVersionResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
|
23
app/Jobs/Import/DataTransferObjects/HubUser.php
Normal file
23
app/Jobs/Import/DataTransferObjects/HubUser.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs\Import\DataTransferObjects;
|
||||
|
||||
class HubUser
|
||||
{
|
||||
public function __construct(
|
||||
public int $userID,
|
||||
public string $username,
|
||||
public string $email,
|
||||
public string $password,
|
||||
public string $registrationDate,
|
||||
public ?bool $banned,
|
||||
public ?string $banReason,
|
||||
public ?string $banExpires,
|
||||
public ?string $coverPhotoHash,
|
||||
public ?string $coverPhotoExtension,
|
||||
public ?int $rankID,
|
||||
public ?string $rankTitle,
|
||||
) {
|
||||
//
|
||||
}
|
||||
}
|
@ -1,13 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
namespace App\Jobs\Import;
|
||||
|
||||
use App\Exceptions\InvalidVersionNumberException;
|
||||
use App\Jobs\Import\DataTransferObjects\HubUser;
|
||||
use App\Models\License;
|
||||
use App\Models\Mod;
|
||||
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;
|
||||
@ -306,14 +309,29 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
$userData = $bannedUsers = $userRanks = [];
|
||||
|
||||
foreach ($users as $user) {
|
||||
$userData[] = $this->collectUserData($curl, $user);
|
||||
$hubUser = new HubUser(
|
||||
$user->userID,
|
||||
$user->username,
|
||||
$user->email,
|
||||
$user->password,
|
||||
$user->registrationDate,
|
||||
$user->banned,
|
||||
$user->banReason,
|
||||
$user->banExpires,
|
||||
$user->coverPhotoHash,
|
||||
$user->coverPhotoExtension,
|
||||
$user->rankID,
|
||||
$user->rankTitle
|
||||
);
|
||||
|
||||
$bannedUserData = $this->collectBannedUserData($user);
|
||||
$userData[] = $this->collectUserData($curl, $hubUser);
|
||||
|
||||
$bannedUserData = $this->collectBannedUserData($hubUser);
|
||||
if ($bannedUserData) {
|
||||
$bannedUsers[] = $bannedUserData;
|
||||
}
|
||||
|
||||
$userRankData = $this->collectUserRankData($user);
|
||||
$userRankData = $this->collectUserRankData($hubUser);
|
||||
if ($userRankData) {
|
||||
$userRanks[] = $userRankData;
|
||||
}
|
||||
@ -328,16 +346,21 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
curl_close($curl);
|
||||
}
|
||||
|
||||
protected function collectUserData(CurlHandle $curl, object $user): array
|
||||
/**
|
||||
* Build an array of user data ready to be inserted into the local database.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected function collectUserData(CurlHandle $curl, HubUser $hubUser): array
|
||||
{
|
||||
return [
|
||||
'hub_id' => (int) $user->userID,
|
||||
'name' => $user->username,
|
||||
'email' => Str::lower($user->email),
|
||||
'password' => $this->cleanPasswordHash($user->password),
|
||||
'profile_photo_path' => $this->fetchUserAvatar($curl, $user),
|
||||
'cover_photo_path' => $this->fetchUserCoverPhoto($curl, $user),
|
||||
'created_at' => $this->cleanRegistrationDate($user->registrationDate),
|
||||
'hub_id' => (int) $hubUser->userID,
|
||||
'name' => $hubUser->username,
|
||||
'email' => Str::lower($hubUser->email),
|
||||
'password' => $this->cleanPasswordHash($hubUser->password),
|
||||
'profile_photo_path' => $this->fetchUserAvatar($curl, $hubUser),
|
||||
'cover_photo_path' => $this->fetchUserCoverPhoto($curl, $hubUser),
|
||||
'created_at' => $this->cleanRegistrationDate($hubUser->registrationDate),
|
||||
'updated_at' => now('UTC')->toDateTimeString(),
|
||||
];
|
||||
}
|
||||
@ -358,10 +381,10 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
/**
|
||||
* Fetch the user avatar from the Hub and store it anew.
|
||||
*/
|
||||
protected function fetchUserAvatar(CurlHandle $curl, object $user): string
|
||||
protected function fetchUserAvatar(CurlHandle $curl, HubUser $hubUser): string
|
||||
{
|
||||
// Fetch the user's avatar data from the temporary table.
|
||||
$avatar = DB::table('temp_user_avatar')->where('userID', $user->userID)->first();
|
||||
$avatar = DB::table('temp_user_avatar')->where('userID', $hubUser->userID)->first();
|
||||
|
||||
if (! $avatar) {
|
||||
return '';
|
||||
@ -410,15 +433,15 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
/**
|
||||
* Fetch the user avatar from the Hub and store it anew.
|
||||
*/
|
||||
protected function fetchUserCoverPhoto(CurlHandle $curl, object $user): string
|
||||
protected function fetchUserCoverPhoto(CurlHandle $curl, HubUser $hubUser): string
|
||||
{
|
||||
if (empty($user->coverPhotoHash) || empty($user->coverPhotoExtension)) {
|
||||
if (empty($hubUser->coverPhotoHash) || empty($hubUser->coverPhotoExtension)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$hashShort = substr($user->coverPhotoHash, 0, 2);
|
||||
$fileName = $user->coverPhotoHash.'.'.$user->coverPhotoExtension;
|
||||
$hubUrl = 'https://hub.sp-tarkov.com/images/coverPhotos/'.$hashShort.'/'.$user->userID.'-'.$fileName;
|
||||
$hashShort = substr($hubUser->coverPhotoHash, 0, 2);
|
||||
$fileName = $hubUser->coverPhotoHash.'.'.$hubUser->coverPhotoExtension;
|
||||
$hubUrl = 'https://hub.sp-tarkov.com/images/coverPhotos/'.$hashShort.'/'.$hubUser->userID.'-'.$fileName;
|
||||
$relativePath = 'user-covers/'.$fileName;
|
||||
|
||||
return $this->fetchAndStoreImage($curl, $hubUrl, $relativePath);
|
||||
@ -441,14 +464,16 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
|
||||
/**
|
||||
* Build an array of banned user data ready to be inserted into the local database.
|
||||
*
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
protected function collectBannedUserData($user): ?array
|
||||
protected function collectBannedUserData(HubUser $hubUser): ?array
|
||||
{
|
||||
if ($user->banned) {
|
||||
if ($hubUser->banned) {
|
||||
return [
|
||||
'hub_id' => (int) $user->userID,
|
||||
'comment' => $user->banReason ?? '',
|
||||
'expired_at' => $this->cleanUnbannedAtDate($user->banExpires),
|
||||
'hub_id' => (int) $hubUser->userID,
|
||||
'comment' => $hubUser->banReason ?? '',
|
||||
'expired_at' => $this->cleanUnbannedAtDate($hubUser->banExpires),
|
||||
];
|
||||
}
|
||||
|
||||
@ -495,12 +520,17 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
}
|
||||
}
|
||||
|
||||
protected function collectUserRankData($user): ?array
|
||||
/**
|
||||
* Build an array of user rank data ready to be inserted into the local database.
|
||||
*
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
protected function collectUserRankData(HubUser $hubUser): ?array
|
||||
{
|
||||
if ($user->rankID && $user->rankTitle) {
|
||||
if ($hubUser->rankID && $hubUser->rankTitle) {
|
||||
return [
|
||||
'hub_id' => (int) $user->userID,
|
||||
'title' => $user->rankTitle,
|
||||
'hub_id' => (int) $hubUser->userID,
|
||||
'title' => $hubUser->rankTitle,
|
||||
];
|
||||
}
|
||||
|
||||
@ -509,8 +539,10 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
|
||||
/**
|
||||
* Insert or update the users in the local database.
|
||||
*
|
||||
* @param array<array<string, mixed>> $usersData
|
||||
*/
|
||||
protected function upsertUsers($usersData): void
|
||||
protected function upsertUsers(array $usersData): void
|
||||
{
|
||||
if (! empty($usersData)) {
|
||||
DB::table('users')->upsert($usersData, ['hub_id'], [
|
||||
@ -525,8 +557,10 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
|
||||
/**
|
||||
* Fetch the hub-banned users from the local database and ban them locally.
|
||||
*
|
||||
* @param array<array<string, mixed>> $bannedUsers
|
||||
*/
|
||||
protected function handleBannedUsers($bannedUsers): void
|
||||
protected function handleBannedUsers(array $bannedUsers): void
|
||||
{
|
||||
foreach ($bannedUsers as $bannedUser) {
|
||||
$user = User::whereHubId($bannedUser['hub_id'])->first();
|
||||
@ -539,8 +573,10 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
|
||||
/**
|
||||
* Fetch or create the user ranks in the local database and assign them to the users.
|
||||
*
|
||||
* @param array<array<string, mixed>> $userRanks
|
||||
*/
|
||||
protected function handleUserRoles($userRanks): void
|
||||
protected function handleUserRoles(array $userRanks): void
|
||||
{
|
||||
foreach ($userRanks as $userRank) {
|
||||
$roleName = Str::ucfirst(Str::afterLast($userRank['title'], '.'));
|
||||
@ -555,6 +591,8 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
|
||||
/**
|
||||
* Build the user role data based on the role name.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function buildUserRoleData(string $name): array
|
||||
{
|
||||
@ -672,6 +710,8 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
|
||||
/**
|
||||
* Get the latest current version from the response data.
|
||||
*
|
||||
* @param array<string, array<string, string>> $versions
|
||||
*/
|
||||
protected function getLatestVersion(array $versions): string
|
||||
{
|
||||
@ -931,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,
|
@ -4,6 +4,7 @@ namespace App\Livewire;
|
||||
|
||||
use App\Models\Mod;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\View\View;
|
||||
use Livewire\Component;
|
||||
@ -34,16 +35,18 @@ class GlobalSearch extends Component
|
||||
|
||||
/**
|
||||
* Execute the search against each of the searchable models.
|
||||
*
|
||||
* @return array<string, array<array<string, mixed>>>
|
||||
*/
|
||||
protected function executeSearch(string $query): array
|
||||
{
|
||||
$query = Str::trim($query);
|
||||
$results = ['data' => [], 'total' => 0];
|
||||
|
||||
if (Str::length($query)) {
|
||||
if (Str::length($query) > 0) {
|
||||
$results['data'] = [
|
||||
'user' => collect(User::search($query)->raw()['hits']),
|
||||
'mod' => collect(Mod::search($query)->raw()['hits']),
|
||||
'user' => $this->fetchUserResults($query),
|
||||
'mod' => $this->fetchModResults($query),
|
||||
];
|
||||
$results['total'] = $this->countTotalResults($results['data']);
|
||||
}
|
||||
@ -55,11 +58,39 @@ class GlobalSearch extends Component
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the total number of results across all models.
|
||||
* Fetch the user search results.
|
||||
*
|
||||
* @return Collection<int, array<string, mixed>>
|
||||
*/
|
||||
protected function countTotalResults($results): int
|
||||
protected function fetchUserResults(string $query): Collection
|
||||
{
|
||||
return collect($results)->reduce(function ($carry, $result) {
|
||||
/** @var array<int, array<string, mixed>> $userHits */
|
||||
$userHits = User::search($query)->raw()['hits'];
|
||||
|
||||
return collect($userHits);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the mod search results.
|
||||
*
|
||||
* @return Collection<int, array<string, mixed>>
|
||||
*/
|
||||
protected function fetchModResults(string $query): Collection
|
||||
{
|
||||
/** @var array<int, array<string, mixed>> $modHits */
|
||||
$modHits = Mod::search($query)->raw()['hits'];
|
||||
|
||||
return collect($modHits);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the total number of results across all models.
|
||||
*
|
||||
* @param array<string, Collection<int, array<string, mixed>>> $results
|
||||
*/
|
||||
protected function countTotalResults(array $results): int
|
||||
{
|
||||
return collect($results)->reduce(function (int $carry, Collection $result) {
|
||||
return $carry + $result->count();
|
||||
}, 0);
|
||||
}
|
||||
|
@ -3,7 +3,9 @@
|
||||
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;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
@ -12,7 +14,7 @@ use Livewire\Attributes\Url;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithPagination;
|
||||
|
||||
class Index extends Component
|
||||
class Listing extends Component
|
||||
{
|
||||
use WithPagination;
|
||||
|
||||
@ -30,6 +32,8 @@ class Index extends Component
|
||||
|
||||
/**
|
||||
* The SPT versions filter value.
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
#[Url]
|
||||
public array $sptVersions = [];
|
||||
@ -42,6 +46,8 @@ class Index extends Component
|
||||
|
||||
/**
|
||||
* The available SPT versions.
|
||||
*
|
||||
* @var Collection<int, SptVersion>
|
||||
*/
|
||||
public Collection $activeSptVersions;
|
||||
|
||||
@ -59,6 +65,8 @@ class Index extends Component
|
||||
|
||||
/**
|
||||
* Get all patch versions of the latest minor SPT version.
|
||||
*
|
||||
* @return Collection<int, SptVersion>
|
||||
*/
|
||||
public function getLatestMinorVersions(): Collection
|
||||
{
|
||||
@ -81,12 +89,21 @@ class Index extends Component
|
||||
];
|
||||
$mods = (new ModFilter($filters))->apply()->paginate(16);
|
||||
|
||||
// Check if the current page is greater than the last page. Redirect if it is.
|
||||
$this->redirectOutOfBoundsPage($mods);
|
||||
|
||||
return view('livewire.mod.listing', compact('mods'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current page is greater than the last page. Redirect if it is.
|
||||
*
|
||||
* @param LengthAwarePaginator<Mod> $mods
|
||||
*/
|
||||
private function redirectOutOfBoundsPage(LengthAwarePaginator $mods): void
|
||||
{
|
||||
if ($mods->currentPage() > $mods->lastPage()) {
|
||||
$this->redirectRoute('mods', ['page' => $mods->lastPage()]);
|
||||
}
|
||||
|
||||
return view('livewire.mod.index', compact('mods'));
|
||||
}
|
||||
|
||||
/**
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Database\Factories\LicenseFactory;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
@ -9,10 +10,15 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class License extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
/** @use HasFactory<LicenseFactory> */
|
||||
use HasFactory;
|
||||
|
||||
use SoftDeletes;
|
||||
|
||||
/**
|
||||
* The relationship between a license and mod.
|
||||
*
|
||||
* @return HasMany<Mod>
|
||||
*/
|
||||
public function mods(): HasMany
|
||||
{
|
||||
|
@ -5,6 +5,7 @@ namespace App\Models;
|
||||
use App\Http\Filters\V1\QueryFilter;
|
||||
use App\Models\Scopes\DisabledScope;
|
||||
use App\Models\Scopes\PublishedScope;
|
||||
use Database\Factories\ModFactory;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
@ -19,24 +20,20 @@ use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Scout\Searchable;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string $slug
|
||||
*/
|
||||
class Mod extends Model
|
||||
{
|
||||
use HasFactory, Searchable, SoftDeletes;
|
||||
/** @use HasFactory<ModFactory> */
|
||||
use HasFactory;
|
||||
|
||||
use Searchable;
|
||||
use SoftDeletes;
|
||||
|
||||
/**
|
||||
* Post boot method to configure the model.
|
||||
*/
|
||||
protected static function booted(): void
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
|
||||
@ -51,6 +48,8 @@ class Mod extends Model
|
||||
|
||||
/**
|
||||
* The relationship between a mod and its users.
|
||||
*
|
||||
* @return BelongsToMany<User>
|
||||
*/
|
||||
public function users(): BelongsToMany
|
||||
{
|
||||
@ -59,6 +58,8 @@ class Mod extends Model
|
||||
|
||||
/**
|
||||
* The relationship between a mod and its license.
|
||||
*
|
||||
* @return BelongsTo<License, Mod>
|
||||
*/
|
||||
public function license(): BelongsTo
|
||||
{
|
||||
@ -66,29 +67,37 @@ class Mod extends Model
|
||||
}
|
||||
|
||||
/**
|
||||
* The relationship between a mod and its versions.
|
||||
* The relationship between a mod and its last updated version.
|
||||
*
|
||||
* @return HasOne<ModVersion>
|
||||
*/
|
||||
public function versions(): HasMany
|
||||
public function latestUpdatedVersion(): HasOne
|
||||
{
|
||||
return $this->hasMany(ModVersion::class)
|
||||
->whereHas('latestSptVersion')
|
||||
->orderByDesc('version')
|
||||
return $this->versions()
|
||||
->one()
|
||||
->ofMany('updated_at', 'max')
|
||||
->chaperone();
|
||||
}
|
||||
|
||||
/**
|
||||
* The relationship between a mod and its last updated version.
|
||||
* The relationship between a mod and its versions.
|
||||
*
|
||||
* @return HasMany<ModVersion>
|
||||
*/
|
||||
public function lastUpdatedVersion(): HasOne
|
||||
public function versions(): HasMany
|
||||
{
|
||||
return $this->hasOne(ModVersion::class)
|
||||
->whereHas('latestSptVersion')
|
||||
->orderByDesc('updated_at')
|
||||
return $this->hasMany(ModVersion::class)
|
||||
->orderByDesc('version_major')
|
||||
->orderByDesc('version_minor')
|
||||
->orderByDesc('version_patch')
|
||||
->orderByDesc('version_pre_release')
|
||||
->chaperone();
|
||||
}
|
||||
|
||||
/**
|
||||
* The data that is searchable by Scout.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toSearchableArray(): array
|
||||
{
|
||||
@ -102,24 +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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@ -135,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;
|
||||
}
|
||||
|
||||
@ -152,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;
|
||||
}
|
||||
|
||||
@ -160,8 +153,28 @@ class Mod extends Model
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The relationship between a mod and its latest version.
|
||||
*
|
||||
* @return HasOne<ModVersion>
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @return Attribute<string, never>
|
||||
*/
|
||||
public function thumbnailUrl(): Attribute
|
||||
{
|
||||
@ -185,6 +198,10 @@ class Mod extends Model
|
||||
|
||||
/**
|
||||
* Scope a query by applying QueryFilter filters.
|
||||
*
|
||||
* @param Builder<Mod> $builder
|
||||
* @param QueryFilter<Mod> $filters
|
||||
* @return Builder<Mod>
|
||||
*/
|
||||
public function scopeFilter(Builder $builder, QueryFilter $filters): Builder
|
||||
{
|
||||
@ -201,6 +218,8 @@ class Mod extends Model
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
@ -209,11 +228,16 @@ class Mod extends Model
|
||||
'contains_ai_content' => 'boolean',
|
||||
'contains_ads' => 'boolean',
|
||||
'disabled' => 'boolean',
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
'deleted_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutate the slug attribute to always be lower case on get and slugified on set.
|
||||
*
|
||||
* @return Attribute<string, string>
|
||||
*/
|
||||
protected function slug(): Attribute
|
||||
{
|
||||
|
@ -2,24 +2,21 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Database\Factories\ModDependencyFactory;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $mod_version_id
|
||||
* @property int $dependency_mod_id
|
||||
* @property string $constraint
|
||||
* @property int|null $resolved_version_id
|
||||
*/
|
||||
class ModDependency extends Model
|
||||
{
|
||||
/** @use HasFactory<ModDependencyFactory> */
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The relationship between the mod dependency and the mod version.
|
||||
*
|
||||
* @return BelongsTo<ModVersion, ModDependency>
|
||||
*/
|
||||
public function modVersion(): BelongsTo
|
||||
{
|
||||
@ -28,6 +25,8 @@ class ModDependency extends Model
|
||||
|
||||
/**
|
||||
* The relationship between the mod dependency and the resolved dependency.
|
||||
*
|
||||
* @return HasMany<ModResolvedDependency>
|
||||
*/
|
||||
public function resolvedDependencies(): HasMany
|
||||
{
|
||||
@ -37,6 +36,8 @@ class ModDependency extends Model
|
||||
|
||||
/**
|
||||
* The relationship between the mod dependency and the dependent mod.
|
||||
*
|
||||
* @return BelongsTo<Mod, ModDependency>
|
||||
*/
|
||||
public function dependentMod(): BelongsTo
|
||||
{
|
||||
|
@ -9,6 +9,8 @@ class ModResolvedDependency extends Model
|
||||
{
|
||||
/**
|
||||
* The relationship between the resolved dependency and the mod version.
|
||||
*
|
||||
* @return BelongsTo<ModVersion, ModResolvedDependency>
|
||||
*/
|
||||
public function modVersion(): BelongsTo
|
||||
{
|
||||
@ -17,6 +19,8 @@ class ModResolvedDependency extends Model
|
||||
|
||||
/**
|
||||
* The relationship between the resolved dependency and the dependency.
|
||||
*
|
||||
* @return BelongsTo<ModDependency, ModResolvedDependency>
|
||||
*/
|
||||
public function dependency(): BelongsTo
|
||||
{
|
||||
@ -25,6 +29,8 @@ class ModResolvedDependency extends Model
|
||||
|
||||
/**
|
||||
* The relationship between the resolved dependency and the resolved mod version.
|
||||
*
|
||||
* @return BelongsTo<ModVersion, ModResolvedDependency>
|
||||
*/
|
||||
public function resolvedModVersion(): BelongsTo
|
||||
{
|
||||
|
@ -2,23 +2,25 @@
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $mod_id
|
||||
* @property string $version
|
||||
*/
|
||||
class ModVersion extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
/** @use HasFactory<ModFactory> */
|
||||
use HasFactory;
|
||||
|
||||
use SoftDeletes;
|
||||
|
||||
/**
|
||||
* Post boot method to configure the model.
|
||||
@ -26,11 +28,31 @@ 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 = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The relationship between a mod version and mod.
|
||||
*
|
||||
* @return BelongsTo<Mod, ModVersion>
|
||||
*/
|
||||
public function mod(): BelongsTo
|
||||
{
|
||||
@ -39,6 +61,8 @@ class ModVersion extends Model
|
||||
|
||||
/**
|
||||
* The relationship between a mod version and its dependencies.
|
||||
*
|
||||
* @return HasMany<ModDependency>
|
||||
*/
|
||||
public function dependencies(): HasMany
|
||||
{
|
||||
@ -48,6 +72,8 @@ class ModVersion extends Model
|
||||
|
||||
/**
|
||||
* The relationship between a mod version and its resolved dependencies.
|
||||
*
|
||||
* @return BelongsToMany<ModVersion>
|
||||
*/
|
||||
public function resolvedDependencies(): BelongsToMany
|
||||
{
|
||||
@ -58,6 +84,8 @@ class ModVersion extends Model
|
||||
|
||||
/**
|
||||
* The relationship between a mod version and its each of it's resolved dependencies' latest versions.
|
||||
*
|
||||
* @return BelongsToMany<ModVersion>
|
||||
*/
|
||||
public function latestResolvedDependencies(): BelongsToMany
|
||||
{
|
||||
@ -72,22 +100,32 @@ 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 HasOneThrough<SptVersion>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* The relationship between a mod version and its SPT versions.
|
||||
*
|
||||
* @return BelongsToMany<SptVersion>
|
||||
*/
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
10
app/Models/ModVersionSptVersion.php
Normal file
10
app/Models/ModVersionSptVersion.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Relations\Pivot;
|
||||
|
||||
class ModVersionSptVersion extends Pivot
|
||||
{
|
||||
public $incrementing = true;
|
||||
}
|
@ -6,10 +6,15 @@ use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Scope;
|
||||
|
||||
/**
|
||||
* @template TModelClass of Model
|
||||
*/
|
||||
class DisabledScope implements Scope
|
||||
{
|
||||
/**
|
||||
* Apply the scope to a given Eloquent query builder.
|
||||
*
|
||||
* @param Builder<TModelClass> $builder
|
||||
*/
|
||||
public function apply(Builder $builder, Model $model): void
|
||||
{
|
||||
|
@ -6,10 +6,15 @@ use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Scope;
|
||||
|
||||
/**
|
||||
* @template TModelClass of Model
|
||||
*/
|
||||
class PublishedScope implements Scope
|
||||
{
|
||||
/**
|
||||
* Apply the scope to a given Eloquent query builder.
|
||||
*
|
||||
* @param Builder<TModelClass> $builder
|
||||
*/
|
||||
public function apply(Builder $builder, Model $model): void
|
||||
{
|
||||
|
@ -3,6 +3,8 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\Exceptions\InvalidVersionNumberException;
|
||||
use App\Support\Version;
|
||||
use Database\Factories\SptVersionFactory;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
@ -12,10 +14,15 @@ use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class SptVersion extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
/** @use HasFactory<SptVersionFactory> */
|
||||
use HasFactory;
|
||||
|
||||
use SoftDeletes;
|
||||
|
||||
/**
|
||||
* Get all versions for the last three minor versions.
|
||||
*
|
||||
* @return Collection<int, SptVersion>
|
||||
*/
|
||||
public static function getVersionsForLastThreeMinors(): Collection
|
||||
{
|
||||
@ -44,6 +51,8 @@ class SptVersion extends Model
|
||||
|
||||
/**
|
||||
* Get the last three minor versions (major.minor format).
|
||||
*
|
||||
* @return array<int, array{major: int, minor: int}>
|
||||
*/
|
||||
public static function getLastThreeMinorVersions(): array
|
||||
{
|
||||
@ -54,7 +63,7 @@ class SptVersion extends Model
|
||||
->orderByDesc('version_minor')
|
||||
->limit(3)
|
||||
->get()
|
||||
->map(function ($version) {
|
||||
->map(function (SptVersion $version) {
|
||||
return [
|
||||
'major' => (int) $version->version_major,
|
||||
'minor' => (int) $version->version_minor,
|
||||
@ -63,38 +72,11 @@ class SptVersion extends Model
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the model is booted.
|
||||
*/
|
||||
protected static function booted(): void
|
||||
{
|
||||
// Callback that runs before saving the model.
|
||||
static::saving(function ($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.
|
||||
*
|
||||
* @return array{major: int, minor: int, patch: int, pre_release: string}
|
||||
*
|
||||
* @throws InvalidVersionNumberException
|
||||
*/
|
||||
public static function extractVersionSections(string $version): array
|
||||
@ -116,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.
|
||||
*/
|
||||
@ -131,10 +136,13 @@ class SptVersion extends Model
|
||||
|
||||
/**
|
||||
* The relationship between an SPT version and mod version.
|
||||
*
|
||||
* @return BelongsToMany<ModVersion>
|
||||
*/
|
||||
public function modVersions(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(ModVersion::class, 'mod_version_spt_version');
|
||||
return $this->belongsToMany(ModVersion::class)
|
||||
->using(ModVersionSptVersion::class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -6,6 +6,7 @@ use App\Http\Filters\V1\QueryFilter;
|
||||
use App\Notifications\ResetPassword;
|
||||
use App\Notifications\VerifyEmail;
|
||||
use App\Traits\HasCoverPhoto;
|
||||
use Database\Factories\UserFactory;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
@ -25,7 +26,10 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
use Bannable;
|
||||
use HasApiTokens;
|
||||
use HasCoverPhoto;
|
||||
|
||||
/** @use HasFactory<UserFactory> */
|
||||
use HasFactory;
|
||||
|
||||
use HasProfilePhoto;
|
||||
use Notifiable;
|
||||
use Searchable;
|
||||
@ -44,6 +48,8 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
|
||||
/**
|
||||
* The relationship between a user and their mods.
|
||||
*
|
||||
* @return BelongsToMany<Mod>
|
||||
*/
|
||||
public function mods(): BelongsToMany
|
||||
{
|
||||
@ -97,6 +103,8 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
|
||||
/**
|
||||
* The data that is searchable by Scout.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toSearchableArray(): array
|
||||
{
|
||||
@ -177,6 +185,8 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
|
||||
/**
|
||||
* The relationship between a user and their role.
|
||||
*
|
||||
* @return BelongsTo<UserRole, User>
|
||||
*/
|
||||
public function role(): BelongsTo
|
||||
{
|
||||
@ -185,6 +195,10 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
|
||||
/**
|
||||
* Scope a query by applying QueryFilter filters.
|
||||
*
|
||||
* @param Builder<User> $builder
|
||||
* @param QueryFilter<User> $filters
|
||||
* @return Builder<User>
|
||||
*/
|
||||
public function scopeFilter(Builder $builder, QueryFilter $filters): Builder
|
||||
{
|
||||
@ -201,6 +215,8 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
|
@ -11,8 +11,8 @@ use App\Observers\ModDependencyObserver;
|
||||
use App\Observers\ModObserver;
|
||||
use App\Observers\ModVersionObserver;
|
||||
use App\Observers\SptVersionObserver;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\Number;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
74
app/Support/Version.php
Normal file
74
app/Support/Version.php
Normal file
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use App\Exceptions\InvalidVersionNumberException;
|
||||
|
||||
class Version
|
||||
{
|
||||
protected int $major = 0;
|
||||
|
||||
protected int $minor = 0;
|
||||
|
||||
protected int $patch = 0;
|
||||
|
||||
protected string $preRelease = '';
|
||||
|
||||
protected string $version;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @throws InvalidVersionNumberException
|
||||
*/
|
||||
public function __construct(string $version)
|
||||
{
|
||||
$this->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];
|
||||
$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;
|
||||
}
|
||||
}
|
94
app/View/Components/HomepageMods.php
Normal file
94
app/View/Components/HomepageMods.php
Normal file
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace App\View\Components;
|
||||
|
||||
use App\Models\Mod;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class HomepageMods extends Component
|
||||
{
|
||||
public function render(): View
|
||||
{
|
||||
return view('components.homepage-mods', [
|
||||
'featured' => [
|
||||
'title' => __('Featured Mods'),
|
||||
'mods' => $this->fetchFeaturedMods(),
|
||||
'link' => '/mods?featured=only',
|
||||
],
|
||||
'latest' => [
|
||||
'title' => __('Newest Mods'),
|
||||
'mods' => $this->fetchLatestMods(),
|
||||
'link' => '/mods',
|
||||
],
|
||||
'updated' => [
|
||||
'title' => __('Recently Updated Mods'),
|
||||
'mods' => $this->fetchUpdatedMods(),
|
||||
'link' => '/mods?order=updated',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the featured mods homepage listing.
|
||||
*
|
||||
* @return Collection<int, Mod>
|
||||
*/
|
||||
private function fetchFeaturedMods(): Collection
|
||||
{
|
||||
return Cache::flexible('homepage-featured-mods', [5, 10], function () {
|
||||
return Mod::whereFeatured(true)
|
||||
->with([
|
||||
'latestVersion',
|
||||
'latestVersion.latestSptVersion',
|
||||
'users:id,name',
|
||||
'license:id,name,link',
|
||||
])
|
||||
->inRandomOrder()
|
||||
->limit(6)
|
||||
->get();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the latest mods homepage listing.
|
||||
*
|
||||
* @return Collection<int, Mod>
|
||||
*/
|
||||
private function fetchLatestMods(): Collection
|
||||
{
|
||||
return Cache::flexible('homepage-latest-mods', [5, 10], function () {
|
||||
return Mod::orderByDesc('created_at')
|
||||
->with([
|
||||
'latestVersion',
|
||||
'latestVersion.latestSptVersion',
|
||||
'users:id,name',
|
||||
'license:id,name,link',
|
||||
])
|
||||
->limit(6)
|
||||
->get();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the recently updated mods homepage listing.
|
||||
*
|
||||
* @return Collection<int, Mod>
|
||||
*/
|
||||
private function fetchUpdatedMods(): Collection
|
||||
{
|
||||
return Cache::flexible('homepage-updated-mods', [5, 10], function () {
|
||||
return Mod::orderByDesc('updated_at')
|
||||
->with([
|
||||
'latestUpdatedVersion',
|
||||
'latestUpdatedVersion.latestSptVersion',
|
||||
'users:id,name',
|
||||
'license:id,name,link',
|
||||
])
|
||||
->limit(6)
|
||||
->get();
|
||||
});
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\View\Components;
|
||||
|
||||
use App\Models\Mod;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class ModList extends Component
|
||||
{
|
||||
/**
|
||||
* The mods to display.
|
||||
*
|
||||
* @var Collection<int, Mod>
|
||||
*/
|
||||
public Collection $mods;
|
||||
|
||||
public string $versionScope;
|
||||
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*
|
||||
* @param Collection<int, Mod> $mods
|
||||
*/
|
||||
public function __construct(Collection $mods, string $versionScope)
|
||||
{
|
||||
$this->mods = $mods;
|
||||
$this->versionScope = $versionScope;
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('components.mod-list', [
|
||||
'mods' => $this->mods,
|
||||
'versionScope' => $this->versionScope,
|
||||
]);
|
||||
}
|
||||
}
|
@ -1,142 +0,0 @@
|
||||
<?php
|
||||
|
||||
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 ModListSection extends Component
|
||||
{
|
||||
/**
|
||||
* The featured mods listed on the homepage.
|
||||
*
|
||||
* @var Collection<int, Mod>
|
||||
*/
|
||||
public Collection $modsFeatured;
|
||||
|
||||
/**
|
||||
* The latest mods listed on the homepage.
|
||||
*
|
||||
* @var Collection<int, Mod>
|
||||
*/
|
||||
public Collection $modsLatest;
|
||||
|
||||
/**
|
||||
* The last updated mods listed on the homepage.
|
||||
*
|
||||
* @var Collection<int, Mod>
|
||||
*/
|
||||
public Collection $modsUpdated;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->modsFeatured = $this->fetchFeaturedMods();
|
||||
$this->modsLatest = $this->fetchLatestMods();
|
||||
$this->modsUpdated = $this->fetchUpdatedMods();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the featured mods homepage listing.
|
||||
*
|
||||
* @return Collection<int, Mod>
|
||||
*/
|
||||
private function fetchFeaturedMods(): Collection
|
||||
{
|
||||
return Mod::select(['id', 'name', 'slug', 'teaser', 'thumbnail', 'featured', 'downloads'])
|
||||
->with([
|
||||
'latestVersion',
|
||||
'latestVersion.latestSptVersion:id,version,color_class',
|
||||
'users:id,name',
|
||||
'license:id,name,link',
|
||||
])
|
||||
->whereFeatured(true)
|
||||
->inRandomOrder()
|
||||
->limit(6)
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the latest mods homepage listing.
|
||||
*
|
||||
* @return Collection<int, Mod>
|
||||
*/
|
||||
private function fetchLatestMods(): Collection
|
||||
{
|
||||
return Mod::select(['id', 'name', 'slug', 'teaser', 'thumbnail', 'featured', 'created_at', 'downloads'])
|
||||
->with([
|
||||
'latestVersion',
|
||||
'latestVersion.latestSptVersion:id,version,color_class',
|
||||
'users:id,name',
|
||||
'license:id,name,link',
|
||||
])
|
||||
->latest()
|
||||
->limit(6)
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the recently updated mods homepage listing.
|
||||
*
|
||||
* @return Collection<int, Mod>
|
||||
*/
|
||||
private function fetchUpdatedMods(): Collection
|
||||
{
|
||||
return Mod::select(['id', 'name', 'slug', 'teaser', 'thumbnail', 'featured', 'downloads'])
|
||||
->with([
|
||||
'lastUpdatedVersion',
|
||||
'lastUpdatedVersion.latestSptVersion:id,version,color_class',
|
||||
'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();
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('components.mod-list-section', [
|
||||
'sections' => $this->getSections(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the sections for the homepage mod lists.
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
public function getSections(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'title' => __('Featured Mods'),
|
||||
'mods' => $this->modsFeatured,
|
||||
'versionScope' => 'latestVersion',
|
||||
'link' => '/mods?featured=only',
|
||||
],
|
||||
[
|
||||
'title' => __('Newest Mods'),
|
||||
'mods' => $this->modsLatest,
|
||||
'versionScope' => 'latestVersion',
|
||||
'link' => '/mods',
|
||||
],
|
||||
[
|
||||
'title' => __('Recently Updated Mods'),
|
||||
'mods' => $this->modsUpdated,
|
||||
'versionScope' => 'lastUpdatedVersion',
|
||||
'link' => '/mods?order=updated',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\View\Components;
|
||||
|
||||
use App\Models\Mod;
|
||||
use App\Models\ModVersion;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class ModListStats extends Component
|
||||
{
|
||||
public function __construct(
|
||||
public Mod $mod,
|
||||
public ModVersion $modVersion
|
||||
) {}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('components.mod-list-stats');
|
||||
}
|
||||
}
|
192
composer.lock
generated
192
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "f28826efd68d2b2d99e0a748de6da8b7",
|
||||
"content-hash": "aef706ee9aa7b671ca81c5ced4a7bfb7",
|
||||
"packages": [
|
||||
{
|
||||
"name": "anourvalar/eloquent-serialize",
|
||||
@ -128,16 +128,16 @@
|
||||
},
|
||||
{
|
||||
"name": "aws/aws-sdk-php",
|
||||
"version": "3.321.7",
|
||||
"version": "3.321.11",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/aws/aws-sdk-php.git",
|
||||
"reference": "c64ee32d80ec2ab5d8d6a0b77297c2d69602ef3b"
|
||||
"reference": "bbd357d246350ffcd0dd8df30951d2d46c5ddadb"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/c64ee32d80ec2ab5d8d6a0b77297c2d69602ef3b",
|
||||
"reference": "c64ee32d80ec2ab5d8d6a0b77297c2d69602ef3b",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/bbd357d246350ffcd0dd8df30951d2d46c5ddadb",
|
||||
"reference": "bbd357d246350ffcd0dd8df30951d2d46c5ddadb",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -220,9 +220,9 @@
|
||||
"support": {
|
||||
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
|
||||
"issues": "https://github.com/aws/aws-sdk-php/issues",
|
||||
"source": "https://github.com/aws/aws-sdk-php/tree/3.321.7"
|
||||
"source": "https://github.com/aws/aws-sdk-php/tree/3.321.11"
|
||||
},
|
||||
"time": "2024-09-09T18:09:23+00:00"
|
||||
"time": "2024-09-13T18:05:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "bacon/bacon-qr-code",
|
||||
@ -1437,16 +1437,16 @@
|
||||
},
|
||||
{
|
||||
"name": "filament/actions",
|
||||
"version": "v3.2.110",
|
||||
"version": "v3.2.112",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/actions.git",
|
||||
"reference": "5d6e4fe444f1ef04d373518248a445bbcc3ca272"
|
||||
"reference": "df3310607b49dad302b03516c558c93cb82c5164"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filamentphp/actions/zipball/5d6e4fe444f1ef04d373518248a445bbcc3ca272",
|
||||
"reference": "5d6e4fe444f1ef04d373518248a445bbcc3ca272",
|
||||
"url": "https://api.github.com/repos/filamentphp/actions/zipball/df3310607b49dad302b03516c558c93cb82c5164",
|
||||
"reference": "df3310607b49dad302b03516c558c93cb82c5164",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1486,20 +1486,20 @@
|
||||
"issues": "https://github.com/filamentphp/filament/issues",
|
||||
"source": "https://github.com/filamentphp/filament"
|
||||
},
|
||||
"time": "2024-08-26T07:22:35+00:00"
|
||||
"time": "2024-09-11T08:25:31+00:00"
|
||||
},
|
||||
{
|
||||
"name": "filament/filament",
|
||||
"version": "v3.2.110",
|
||||
"version": "v3.2.112",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/panels.git",
|
||||
"reference": "130636e90e821154e0ce60dcbc7b358d2a1a716f"
|
||||
"reference": "86aa182deceedce5970560c60ceae30c2c40632d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filamentphp/panels/zipball/130636e90e821154e0ce60dcbc7b358d2a1a716f",
|
||||
"reference": "130636e90e821154e0ce60dcbc7b358d2a1a716f",
|
||||
"url": "https://api.github.com/repos/filamentphp/panels/zipball/86aa182deceedce5970560c60ceae30c2c40632d",
|
||||
"reference": "86aa182deceedce5970560c60ceae30c2c40632d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1551,20 +1551,20 @@
|
||||
"issues": "https://github.com/filamentphp/filament/issues",
|
||||
"source": "https://github.com/filamentphp/filament"
|
||||
},
|
||||
"time": "2024-08-30T01:52:09+00:00"
|
||||
"time": "2024-09-11T08:25:51+00:00"
|
||||
},
|
||||
{
|
||||
"name": "filament/forms",
|
||||
"version": "v3.2.110",
|
||||
"version": "v3.2.112",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/forms.git",
|
||||
"reference": "02fe2e211993f6291b719a093ed6f63e17125e9a"
|
||||
"reference": "99d72777f1e6dc5d42d936e7deb53148e4233ec3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filamentphp/forms/zipball/02fe2e211993f6291b719a093ed6f63e17125e9a",
|
||||
"reference": "02fe2e211993f6291b719a093ed6f63e17125e9a",
|
||||
"url": "https://api.github.com/repos/filamentphp/forms/zipball/99d72777f1e6dc5d42d936e7deb53148e4233ec3",
|
||||
"reference": "99d72777f1e6dc5d42d936e7deb53148e4233ec3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1607,20 +1607,20 @@
|
||||
"issues": "https://github.com/filamentphp/filament/issues",
|
||||
"source": "https://github.com/filamentphp/filament"
|
||||
},
|
||||
"time": "2024-08-30T18:04:06+00:00"
|
||||
"time": "2024-09-12T12:27:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "filament/infolists",
|
||||
"version": "v3.2.110",
|
||||
"version": "v3.2.112",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/infolists.git",
|
||||
"reference": "96403f2842e4c485f32110e4456b7a3bbcb1e835"
|
||||
"reference": "e50bd9a5fc623320bd79508e3bfb72ff9e309edf"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filamentphp/infolists/zipball/96403f2842e4c485f32110e4456b7a3bbcb1e835",
|
||||
"reference": "96403f2842e4c485f32110e4456b7a3bbcb1e835",
|
||||
"url": "https://api.github.com/repos/filamentphp/infolists/zipball/e50bd9a5fc623320bd79508e3bfb72ff9e309edf",
|
||||
"reference": "e50bd9a5fc623320bd79508e3bfb72ff9e309edf",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1658,11 +1658,11 @@
|
||||
"issues": "https://github.com/filamentphp/filament/issues",
|
||||
"source": "https://github.com/filamentphp/filament"
|
||||
},
|
||||
"time": "2024-08-14T16:52:44+00:00"
|
||||
"time": "2024-09-11T08:25:25+00:00"
|
||||
},
|
||||
{
|
||||
"name": "filament/notifications",
|
||||
"version": "v3.2.110",
|
||||
"version": "v3.2.112",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/notifications.git",
|
||||
@ -1714,16 +1714,16 @@
|
||||
},
|
||||
{
|
||||
"name": "filament/support",
|
||||
"version": "v3.2.110",
|
||||
"version": "v3.2.112",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/support.git",
|
||||
"reference": "78e25428c754fcbb30c321d5dda439c760de9837"
|
||||
"reference": "d07086506d39f318398c13a0b8d689f75cbc14d6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filamentphp/support/zipball/78e25428c754fcbb30c321d5dda439c760de9837",
|
||||
"reference": "78e25428c754fcbb30c321d5dda439c760de9837",
|
||||
"url": "https://api.github.com/repos/filamentphp/support/zipball/d07086506d39f318398c13a0b8d689f75cbc14d6",
|
||||
"reference": "d07086506d39f318398c13a0b8d689f75cbc14d6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1769,20 +1769,20 @@
|
||||
"issues": "https://github.com/filamentphp/filament/issues",
|
||||
"source": "https://github.com/filamentphp/filament"
|
||||
},
|
||||
"time": "2024-08-26T07:22:57+00:00"
|
||||
"time": "2024-09-11T08:25:46+00:00"
|
||||
},
|
||||
{
|
||||
"name": "filament/tables",
|
||||
"version": "v3.2.110",
|
||||
"version": "v3.2.112",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/tables.git",
|
||||
"reference": "129943d1b4e6c1edeef53e804eb56ef78a932a6c"
|
||||
"reference": "4285a031dd36250a86710631a5b1fea1372097a1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filamentphp/tables/zipball/129943d1b4e6c1edeef53e804eb56ef78a932a6c",
|
||||
"reference": "129943d1b4e6c1edeef53e804eb56ef78a932a6c",
|
||||
"url": "https://api.github.com/repos/filamentphp/tables/zipball/4285a031dd36250a86710631a5b1fea1372097a1",
|
||||
"reference": "4285a031dd36250a86710631a5b1fea1372097a1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1821,11 +1821,11 @@
|
||||
"issues": "https://github.com/filamentphp/filament/issues",
|
||||
"source": "https://github.com/filamentphp/filament"
|
||||
},
|
||||
"time": "2024-08-30T01:52:14+00:00"
|
||||
"time": "2024-09-11T08:25:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "filament/widgets",
|
||||
"version": "v3.2.110",
|
||||
"version": "v3.2.112",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/widgets.git",
|
||||
@ -2471,16 +2471,16 @@
|
||||
},
|
||||
{
|
||||
"name": "kirschbaum-development/eloquent-power-joins",
|
||||
"version": "3.5.7",
|
||||
"version": "3.5.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/kirschbaum-development/eloquent-power-joins.git",
|
||||
"reference": "3f57b398117d97bae4dfd5c37ea0f8f48f296c97"
|
||||
"reference": "397ef08f15ceff48111fd7f57d9f1fd41bf1a453"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/kirschbaum-development/eloquent-power-joins/zipball/3f57b398117d97bae4dfd5c37ea0f8f48f296c97",
|
||||
"reference": "3f57b398117d97bae4dfd5c37ea0f8f48f296c97",
|
||||
"url": "https://api.github.com/repos/kirschbaum-development/eloquent-power-joins/zipball/397ef08f15ceff48111fd7f57d9f1fd41bf1a453",
|
||||
"reference": "397ef08f15ceff48111fd7f57d9f1fd41bf1a453",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2527,22 +2527,22 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/kirschbaum-development/eloquent-power-joins/issues",
|
||||
"source": "https://github.com/kirschbaum-development/eloquent-power-joins/tree/3.5.7"
|
||||
"source": "https://github.com/kirschbaum-development/eloquent-power-joins/tree/3.5.8"
|
||||
},
|
||||
"time": "2024-06-26T13:09:29+00:00"
|
||||
"time": "2024-09-10T10:28:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laminas/laminas-diactoros",
|
||||
"version": "3.3.1",
|
||||
"version": "3.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laminas/laminas-diactoros.git",
|
||||
"reference": "74cfb9a7522ffd2a161d1ebe10db2fc2abb9df45"
|
||||
"reference": "2cce7e77ca4c6c4183e9e8d4edeba483afc14487"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/74cfb9a7522ffd2a161d1ebe10db2fc2abb9df45",
|
||||
"reference": "74cfb9a7522ffd2a161d1ebe10db2fc2abb9df45",
|
||||
"url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/2cce7e77ca4c6c4183e9e8d4edeba483afc14487",
|
||||
"reference": "2cce7e77ca4c6c4183e9e8d4edeba483afc14487",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2551,7 +2551,7 @@
|
||||
"psr/http-message": "^1.1 || ^2.0"
|
||||
},
|
||||
"provide": {
|
||||
"psr/http-factory-implementation": "^1.1 || ^2.0",
|
||||
"psr/http-factory-implementation": "^1.0",
|
||||
"psr/http-message-implementation": "^1.1 || ^2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
@ -2559,12 +2559,12 @@
|
||||
"ext-dom": "*",
|
||||
"ext-gd": "*",
|
||||
"ext-libxml": "*",
|
||||
"http-interop/http-factory-tests": "^0.9.0",
|
||||
"http-interop/http-factory-tests": "^2.2.0",
|
||||
"laminas/laminas-coding-standard": "~2.5.0",
|
||||
"php-http/psr7-integration-tests": "^1.3",
|
||||
"phpunit/phpunit": "^9.6.16",
|
||||
"psalm/plugin-phpunit": "^0.18.4",
|
||||
"vimeo/psalm": "^5.22.1"
|
||||
"php-http/psr7-integration-tests": "^1.4.0",
|
||||
"phpunit/phpunit": "^10.5.31",
|
||||
"psalm/plugin-phpunit": "^0.19.0",
|
||||
"vimeo/psalm": "^5.25.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
@ -2614,7 +2614,7 @@
|
||||
"type": "community_bridge"
|
||||
}
|
||||
],
|
||||
"time": "2024-02-16T16:06:16+00:00"
|
||||
"time": "2024-09-11T00:55:07+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/fortify",
|
||||
@ -2683,16 +2683,16 @@
|
||||
},
|
||||
{
|
||||
"name": "laravel/framework",
|
||||
"version": "v11.22.0",
|
||||
"version": "v11.23.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/framework.git",
|
||||
"reference": "868c75beacc47d0f361b919bbc155c0b619bf3d5"
|
||||
"reference": "16b31ab0e1dad5cb2ed6dcc1818c02f02fc48453"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/868c75beacc47d0f361b919bbc155c0b619bf3d5",
|
||||
"reference": "868c75beacc47d0f361b919bbc155c0b619bf3d5",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/16b31ab0e1dad5cb2ed6dcc1818c02f02fc48453",
|
||||
"reference": "16b31ab0e1dad5cb2ed6dcc1818c02f02fc48453",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2754,6 +2754,7 @@
|
||||
"illuminate/bus": "self.version",
|
||||
"illuminate/cache": "self.version",
|
||||
"illuminate/collections": "self.version",
|
||||
"illuminate/concurrency": "self.version",
|
||||
"illuminate/conditionable": "self.version",
|
||||
"illuminate/config": "self.version",
|
||||
"illuminate/console": "self.version",
|
||||
@ -2796,7 +2797,7 @@
|
||||
"league/flysystem-sftp-v3": "^3.0",
|
||||
"mockery/mockery": "^1.6",
|
||||
"nyholm/psr7": "^1.2",
|
||||
"orchestra/testbench-core": "^9.1.5",
|
||||
"orchestra/testbench-core": "^9.4.0",
|
||||
"pda/pheanstalk": "^5.0",
|
||||
"phpstan/phpstan": "^1.11.5",
|
||||
"phpunit/phpunit": "^10.5|^11.0",
|
||||
@ -2854,6 +2855,7 @@
|
||||
"src/Illuminate/Events/functions.php",
|
||||
"src/Illuminate/Filesystem/functions.php",
|
||||
"src/Illuminate/Foundation/helpers.php",
|
||||
"src/Illuminate/Log/functions.php",
|
||||
"src/Illuminate/Support/helpers.php"
|
||||
],
|
||||
"psr-4": {
|
||||
@ -2885,7 +2887,7 @@
|
||||
"issues": "https://github.com/laravel/framework/issues",
|
||||
"source": "https://github.com/laravel/framework"
|
||||
},
|
||||
"time": "2024-09-03T15:27:15+00:00"
|
||||
"time": "2024-09-13T13:36:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/horizon",
|
||||
@ -3035,16 +3037,16 @@
|
||||
},
|
||||
{
|
||||
"name": "laravel/octane",
|
||||
"version": "v2.5.4",
|
||||
"version": "v2.5.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/octane.git",
|
||||
"reference": "d7b8991270eb57eef83be7de62ba04c1289dd65b"
|
||||
"reference": "a6cb30a609a997386533201344196f01bcd90e2b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/octane/zipball/d7b8991270eb57eef83be7de62ba04c1289dd65b",
|
||||
"reference": "d7b8991270eb57eef83be7de62ba04c1289dd65b",
|
||||
"url": "https://api.github.com/repos/laravel/octane/zipball/a6cb30a609a997386533201344196f01bcd90e2b",
|
||||
"reference": "a6cb30a609a997386533201344196f01bcd90e2b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -3121,7 +3123,7 @@
|
||||
"issues": "https://github.com/laravel/octane/issues",
|
||||
"source": "https://github.com/laravel/octane"
|
||||
},
|
||||
"time": "2024-08-09T12:25:04+00:00"
|
||||
"time": "2024-09-11T20:54:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/prompts",
|
||||
@ -3334,16 +3336,16 @@
|
||||
},
|
||||
{
|
||||
"name": "laravel/scout",
|
||||
"version": "v10.11.2",
|
||||
"version": "v10.11.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/scout.git",
|
||||
"reference": "74f007d0f5b78f589014899094f7ddb4855b771d"
|
||||
"reference": "642b4750127b5242a089571c9314037a7453cc0a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/scout/zipball/74f007d0f5b78f589014899094f7ddb4855b771d",
|
||||
"reference": "74f007d0f5b78f589014899094f7ddb4855b771d",
|
||||
"url": "https://api.github.com/repos/laravel/scout/zipball/642b4750127b5242a089571c9314037a7453cc0a",
|
||||
"reference": "642b4750127b5242a089571c9314037a7453cc0a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -3408,7 +3410,7 @@
|
||||
"issues": "https://github.com/laravel/scout/issues",
|
||||
"source": "https://github.com/laravel/scout"
|
||||
},
|
||||
"time": "2024-09-03T15:30:25+00:00"
|
||||
"time": "2024-09-11T21:32:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/serializable-closure",
|
||||
@ -4532,16 +4534,16 @@
|
||||
},
|
||||
{
|
||||
"name": "meilisearch/meilisearch-php",
|
||||
"version": "v1.10.0",
|
||||
"version": "v1.10.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/meilisearch/meilisearch-php.git",
|
||||
"reference": "110be455194fda42ae3a3b8b6a9243479c3a6d19"
|
||||
"reference": "e3d8a74fdb0c65ecdef6ef9e89c110810c5c1aa0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/meilisearch/meilisearch-php/zipball/110be455194fda42ae3a3b8b6a9243479c3a6d19",
|
||||
"reference": "110be455194fda42ae3a3b8b6a9243479c3a6d19",
|
||||
"url": "https://api.github.com/repos/meilisearch/meilisearch-php/zipball/e3d8a74fdb0c65ecdef6ef9e89c110810c5c1aa0",
|
||||
"reference": "e3d8a74fdb0c65ecdef6ef9e89c110810c5c1aa0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -4593,9 +4595,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/meilisearch/meilisearch-php/issues",
|
||||
"source": "https://github.com/meilisearch/meilisearch-php/tree/v1.10.0"
|
||||
"source": "https://github.com/meilisearch/meilisearch-php/tree/v1.10.1"
|
||||
},
|
||||
"time": "2024-08-26T23:09:54+00:00"
|
||||
"time": "2024-09-15T22:50:45+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mobiledetect/mobiledetectlib",
|
||||
@ -5084,16 +5086,16 @@
|
||||
},
|
||||
{
|
||||
"name": "nikic/php-parser",
|
||||
"version": "v5.1.0",
|
||||
"version": "v5.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nikic/PHP-Parser.git",
|
||||
"reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1"
|
||||
"reference": "23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1",
|
||||
"reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1",
|
||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb",
|
||||
"reference": "23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -5136,9 +5138,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/nikic/PHP-Parser/issues",
|
||||
"source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0"
|
||||
"source": "https://github.com/nikic/PHP-Parser/tree/v5.2.0"
|
||||
},
|
||||
"time": "2024-07-01T20:03:41+00:00"
|
||||
"time": "2024-09-15T16:40:33+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nunomaduro/termwind",
|
||||
@ -5956,16 +5958,16 @@
|
||||
},
|
||||
{
|
||||
"name": "psr/log",
|
||||
"version": "3.0.1",
|
||||
"version": "3.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/log.git",
|
||||
"reference": "79dff0b268932c640297f5208d6298f71855c03e"
|
||||
"reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/log/zipball/79dff0b268932c640297f5208d6298f71855c03e",
|
||||
"reference": "79dff0b268932c640297f5208d6298f71855c03e",
|
||||
"url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
|
||||
"reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -6000,9 +6002,9 @@
|
||||
"psr-3"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/php-fig/log/tree/3.0.1"
|
||||
"source": "https://github.com/php-fig/log/tree/3.0.2"
|
||||
},
|
||||
"time": "2024-08-21T13:31:24+00:00"
|
||||
"time": "2024-09-11T13:17:53+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/simple-cache",
|
||||
@ -9983,16 +9985,16 @@
|
||||
},
|
||||
{
|
||||
"name": "laravel/sail",
|
||||
"version": "v1.31.3",
|
||||
"version": "v1.32.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/sail.git",
|
||||
"reference": "0a7e2891a85eba2d448a9ffc6fc5ce367e924bc1"
|
||||
"reference": "4a7e41d280861ca7e35710cea011a07669b4003b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/sail/zipball/0a7e2891a85eba2d448a9ffc6fc5ce367e924bc1",
|
||||
"reference": "0a7e2891a85eba2d448a9ffc6fc5ce367e924bc1",
|
||||
"url": "https://api.github.com/repos/laravel/sail/zipball/4a7e41d280861ca7e35710cea011a07669b4003b",
|
||||
"reference": "4a7e41d280861ca7e35710cea011a07669b4003b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -10042,7 +10044,7 @@
|
||||
"issues": "https://github.com/laravel/sail/issues",
|
||||
"source": "https://github.com/laravel/sail"
|
||||
},
|
||||
"time": "2024-09-03T20:05:33+00:00"
|
||||
"time": "2024-09-11T20:14:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "maximebf/debugbar",
|
||||
|
@ -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(),
|
||||
|
||||
|
@ -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(),
|
||||
|
@ -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');
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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');
|
||||
});
|
||||
}
|
||||
|
@ -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');
|
||||
|
174
package-lock.json
generated
174
package-lock.json
generated
@ -556,9 +556,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.2.tgz",
|
||||
"integrity": "sha512-fSuPrt0ZO8uXeS+xP3b+yYTCBUd05MoSp2N/MFOgjhhUhMmchXlpTQrTpI8T+YAwAQuK7MafsCOxW7VrPMrJcg==",
|
||||
"version": "4.21.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.3.tgz",
|
||||
"integrity": "sha512-MmKSfaB9GX+zXl6E8z4koOr/xU63AMVleLEa64v7R0QF/ZloMs5vcD1sHgM64GXXS1csaJutG+ddtzcueI/BLg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@ -570,9 +570,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.2.tgz",
|
||||
"integrity": "sha512-xGU5ZQmPlsjQS6tzTTGwMsnKUtu0WVbl0hYpTPauvbRAnmIvpInhJtgjj3mcuJpEiuUw4v1s4BimkdfDWlh7gA==",
|
||||
"version": "4.21.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.3.tgz",
|
||||
"integrity": "sha512-zrt8ecH07PE3sB4jPOggweBjJMzI1JG5xI2DIsUbkA+7K+Gkjys6eV7i9pOenNSDJH3eOr/jLb/PzqtmdwDq5g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -584,9 +584,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.2.tgz",
|
||||
"integrity": "sha512-99AhQ3/ZMxU7jw34Sq8brzXqWH/bMnf7ZVhvLk9QU2cOepbQSVTns6qoErJmSiAvU3InRqC2RRZ5ovh1KN0d0Q==",
|
||||
"version": "4.21.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.3.tgz",
|
||||
"integrity": "sha512-P0UxIOrKNBFTQaXTxOH4RxuEBVCgEA5UTNV6Yz7z9QHnUJ7eLX9reOd/NYMO3+XZO2cco19mXTxDMXxit4R/eQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -598,9 +598,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.2.tgz",
|
||||
"integrity": "sha512-ZbRaUvw2iN/y37x6dY50D8m2BnDbBjlnMPotDi/qITMJ4sIxNY33HArjikDyakhSv0+ybdUxhWxE6kTI4oX26w==",
|
||||
"version": "4.21.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.3.tgz",
|
||||
"integrity": "sha512-L1M0vKGO5ASKntqtsFEjTq/fD91vAqnzeaF6sfNAy55aD+Hi2pBI5DKwCO+UNDQHWsDViJLqshxOahXyLSh3EA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -612,9 +612,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.2.tgz",
|
||||
"integrity": "sha512-ztRJJMiE8nnU1YFcdbd9BcH6bGWG1z+jP+IPW2oDUAPxPjo9dverIOyXz76m6IPA6udEL12reYeLojzW2cYL7w==",
|
||||
"version": "4.21.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.3.tgz",
|
||||
"integrity": "sha512-btVgIsCjuYFKUjopPoWiDqmoUXQDiW2A4C3Mtmp5vACm7/GnyuprqIDPNczeyR5W8rTXEbkmrJux7cJmD99D2g==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@ -626,9 +626,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.2.tgz",
|
||||
"integrity": "sha512-flOcGHDZajGKYpLV0JNc0VFH361M7rnV1ee+NTeC/BQQ1/0pllYcFmxpagltANYt8FYf9+kL6RSk80Ziwyhr7w==",
|
||||
"version": "4.21.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.3.tgz",
|
||||
"integrity": "sha512-zmjbSphplZlau6ZTkxd3+NMtE4UKVy7U4aVFMmHcgO5CUbw17ZP6QCgyxhzGaU/wFFdTfiojjbLG3/0p9HhAqA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@ -640,9 +640,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.2.tgz",
|
||||
"integrity": "sha512-69CF19Kp3TdMopyteO/LJbWufOzqqXzkrv4L2sP8kfMaAQ6iwky7NoXTp7bD6/irKgknDKM0P9E/1l5XxVQAhw==",
|
||||
"version": "4.21.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.3.tgz",
|
||||
"integrity": "sha512-nSZfcZtAnQPRZmUkUQwZq2OjQciR6tEoJaZVFvLHsj0MF6QhNMg0fQ6mUOsiCUpTqxTx0/O6gX0V/nYc7LrgPw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -654,9 +654,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.2.tgz",
|
||||
"integrity": "sha512-48pD/fJkTiHAZTnZwR0VzHrao70/4MlzJrq0ZsILjLW/Ab/1XlVUStYyGt7tdyIiVSlGZbnliqmult/QGA2O2w==",
|
||||
"version": "4.21.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.3.tgz",
|
||||
"integrity": "sha512-MnvSPGO8KJXIMGlQDYfvYS3IosFN2rKsvxRpPO2l2cum+Z3exiExLwVU+GExL96pn8IP+GdH8Tz70EpBhO0sIQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -668,9 +668,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||
"version": "4.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.2.tgz",
|
||||
"integrity": "sha512-cZdyuInj0ofc7mAQpKcPR2a2iu4YM4FQfuUzCVA2u4HI95lCwzjoPtdWjdpDKyHxI0UO82bLDoOaLfpZ/wviyQ==",
|
||||
"version": "4.21.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.3.tgz",
|
||||
"integrity": "sha512-+W+p/9QNDr2vE2AXU0qIy0qQE75E8RTwTwgqS2G5CRQ11vzq0tbnfBd6brWhS9bCRjAjepJe2fvvkvS3dno+iw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@ -682,9 +682,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.2.tgz",
|
||||
"integrity": "sha512-RL56JMT6NwQ0lXIQmMIWr1SW28z4E4pOhRRNqwWZeXpRlykRIlEpSWdsgNWJbYBEWD84eocjSGDu/XxbYeCmwg==",
|
||||
"version": "4.21.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.3.tgz",
|
||||
"integrity": "sha512-yXH6K6KfqGXaxHrtr+Uoy+JpNlUlI46BKVyonGiaD74ravdnF9BUNC+vV+SIuB96hUMGShhKV693rF9QDfO6nQ==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@ -696,9 +696,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.2.tgz",
|
||||
"integrity": "sha512-PMxkrWS9z38bCr3rWvDFVGD6sFeZJw4iQlhrup7ReGmfn7Oukrr/zweLhYX6v2/8J6Cep9IEA/SmjXjCmSbrMQ==",
|
||||
"version": "4.21.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.3.tgz",
|
||||
"integrity": "sha512-R8cwY9wcnApN/KDYWTH4gV/ypvy9yZUHlbJvfaiXSB48JO3KpwSpjOGqO4jnGkLDSk1hgjYkTbTt6Q7uvPf8eg==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@ -710,9 +710,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.2.tgz",
|
||||
"integrity": "sha512-B90tYAUoLhU22olrafY3JQCFLnT3NglazdwkHyxNDYF/zAxJt5fJUB/yBoWFoIQ7SQj+KLe3iL4BhOMa9fzgpw==",
|
||||
"version": "4.21.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.3.tgz",
|
||||
"integrity": "sha512-kZPbX/NOPh0vhS5sI+dR8L1bU2cSO9FgxwM8r7wHzGydzfSjLRCFAT87GR5U9scj2rhzN3JPYVC7NoBbl4FZ0g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -724,9 +724,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.2.tgz",
|
||||
"integrity": "sha512-7twFizNXudESmC9oneLGIUmoHiiLppz/Xs5uJQ4ShvE6234K0VB1/aJYU3f/4g7PhssLGKBVCC37uRkkOi8wjg==",
|
||||
"version": "4.21.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.3.tgz",
|
||||
"integrity": "sha512-S0Yq+xA1VEH66uiMNhijsWAafffydd2X5b77eLHfRmfLsRSpbiAWiRHV6DEpz6aOToPsgid7TI9rGd6zB1rhbg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -738,9 +738,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.2.tgz",
|
||||
"integrity": "sha512-9rRero0E7qTeYf6+rFh3AErTNU1VCQg2mn7CQcI44vNUWM9Ze7MSRS/9RFuSsox+vstRt97+x3sOhEey024FRQ==",
|
||||
"version": "4.21.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.3.tgz",
|
||||
"integrity": "sha512-9isNzeL34yquCPyerog+IMCNxKR8XYmGd0tHSV+OVx0TmE0aJOo9uw4fZfUuk2qxobP5sug6vNdZR6u7Mw7Q+Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -752,9 +752,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.2.tgz",
|
||||
"integrity": "sha512-5rA4vjlqgrpbFVVHX3qkrCo/fZTj1q0Xxpg+Z7yIo3J2AilW7t2+n6Q8Jrx+4MrYpAnjttTYF8rr7bP46BPzRw==",
|
||||
"version": "4.21.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.3.tgz",
|
||||
"integrity": "sha512-nMIdKnfZfzn1Vsk+RuOvl43ONTZXoAPUUxgcU0tXooqg4YrAqzfKzVenqqk2g5efWh46/D28cKFrOzDSW28gTA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@ -766,9 +766,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.2.tgz",
|
||||
"integrity": "sha512-6UUxd0+SKomjdzuAcp+HAmxw1FlGBnl1v2yEPSabtx4lBfdXHDVsW7+lQkgz9cNFJGY3AWR7+V8P5BqkD9L9nA==",
|
||||
"version": "4.21.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.3.tgz",
|
||||
"integrity": "sha512-fOvu7PCQjAj4eWDEuD8Xz5gpzFqXzGlxHZozHP4b9Jxv9APtdxL6STqztDzMLuRXEc4UpXGGhx029Xgm91QBeA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -816,9 +816,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
|
||||
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
|
||||
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@ -1013,9 +1013,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001658",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001658.tgz",
|
||||
"integrity": "sha512-N2YVqWbJELVdrnsW5p+apoQyYt51aBMSsBZki1XZEfeBCexcM/sf4xiAHcXQBkuOwJBXtWF7aW1sYX6tKebPHw==",
|
||||
"version": "1.0.30001660",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001660.tgz",
|
||||
"integrity": "sha512-GacvNTTuATm26qC74pt+ad1fW15mlQ/zuTzzY1ZoIzECTP8HURDfF43kNxPgf7H1jmelCBQTTbBNxdSXOA7Bqg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@ -1161,9 +1161,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.18",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.18.tgz",
|
||||
"integrity": "sha512-1OfuVACu+zKlmjsNdcJuVQuVE61sZOLbNM4JAQ1Rvh6EOj0/EUKhMJjRH73InPlXSh8HIJk1cVZ8pyOV/FMdUQ==",
|
||||
"version": "1.5.23",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.23.tgz",
|
||||
"integrity": "sha512-mBhODedOXg4v5QWwl21DjM5amzjmI1zw9EPrPK/5Wx7C8jt33bpZNrC7OhHUG3pxRtbLpr3W2dXT+Ph1SsfRZA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
@ -1824,9 +1824,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.45",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.45.tgz",
|
||||
"integrity": "sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q==",
|
||||
"version": "8.4.47",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
|
||||
"integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@ -1845,8 +1845,8 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.7",
|
||||
"picocolors": "^1.0.1",
|
||||
"source-map-js": "^1.2.0"
|
||||
"picocolors": "^1.1.0",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
@ -2176,9 +2176,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.21.2",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.2.tgz",
|
||||
"integrity": "sha512-e3TapAgYf9xjdLvKQCkQTnbTKd4a6jwlpQSJJFokHGaX2IVjoEqkIIhiQfqsi0cdwlOD+tQGuOd5AJkc5RngBw==",
|
||||
"version": "4.21.3",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.3.tgz",
|
||||
"integrity": "sha512-7sqRtBNnEbcBtMeRVc6VRsJMmpI+JU1z9VTvW8D4gXIYQFz0aLcsE6rRkyghZkLfEgUZgVvOG7A5CVz/VW5GIA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -2192,22 +2192,22 @@
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.21.2",
|
||||
"@rollup/rollup-android-arm64": "4.21.2",
|
||||
"@rollup/rollup-darwin-arm64": "4.21.2",
|
||||
"@rollup/rollup-darwin-x64": "4.21.2",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.21.2",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.21.2",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.21.2",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.21.2",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.21.2",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.21.2",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.21.2",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.21.2",
|
||||
"@rollup/rollup-linux-x64-musl": "4.21.2",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.21.2",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.21.2",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.21.2",
|
||||
"@rollup/rollup-android-arm-eabi": "4.21.3",
|
||||
"@rollup/rollup-android-arm64": "4.21.3",
|
||||
"@rollup/rollup-darwin-arm64": "4.21.3",
|
||||
"@rollup/rollup-darwin-x64": "4.21.3",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.21.3",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.21.3",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.21.3",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.21.3",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.21.3",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.21.3",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.21.3",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.21.3",
|
||||
"@rollup/rollup-linux-x64-musl": "4.21.3",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.21.3",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.21.3",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.21.3",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
@ -2428,9 +2428,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "3.4.10",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.10.tgz",
|
||||
"integrity": "sha512-KWZkVPm7yJRhdu4SRSl9d4AK2wM3a50UsvgHZO7xY77NQr2V+fIrEuoDGQcbvswWvFGbS2f6e+jC/6WJm1Dl0w==",
|
||||
"version": "3.4.11",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.11.tgz",
|
||||
"integrity": "sha512-qhEuBcLemjSJk5ajccN9xJFtM/h0AVCPaA6C92jNP+M2J8kX+eMJHI7R2HFKUvvAsMpcfLILMCFYSeDwpMmlUg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -2574,9 +2574,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "5.4.3",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.3.tgz",
|
||||
"integrity": "sha512-IH+nl64eq9lJjFqU+/yrRnrHPVTlgy42/+IzbOdaFDVlyLgI/wDlf+FCobXLX1cT0X5+7LMyH1mIy2xJdLfo8Q==",
|
||||
"version": "5.4.5",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.5.tgz",
|
||||
"integrity": "sha512-pXqR0qtb2bTwLkev4SE3r4abCNioP3GkjvIDLlzziPpXtHgiJIjuKl+1GN6ESOT3wMjG3JTeARopj2SwYaHTOA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
22
resources/views/components/homepage-mods.blade.php
Normal file
22
resources/views/components/homepage-mods.blade.php
Normal file
@ -0,0 +1,22 @@
|
||||
<div class="mx-auto max-w-7xl px-4 pt-16 sm:px-6 lg:px-8">
|
||||
<x-page-content-title :title="$featured['title']" :button-text="__('View All')" :button-link="$featured['link']" />
|
||||
<div class="my-8 grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||
@foreach ($featured['mods'] as $mod)
|
||||
<x-mod-card :mod="$mod" :version="$mod->latestVersion" />
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<x-page-content-title :title="$latest['title']" :button-text="__('View All')" :button-link="$latest['link']" />
|
||||
<div class="my-8 grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||
@foreach ($latest['mods'] as $mod)
|
||||
<x-mod-card :mod="$mod" :version="$mod->latestVersion" />
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<x-page-content-title :title="$updated['title']" :button-text="__('View All')" :button-link="$updated['link']" />
|
||||
<div class="my-8 grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||
@foreach ($updated['mods'] as $mod)
|
||||
<x-mod-card :mod="$mod" :version="$mod->latestUpdatedVersion" />
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
@ -1,37 +1,56 @@
|
||||
@props(['mod', 'versionScope' => 'none'])
|
||||
@props(['mod', 'version'])
|
||||
|
||||
<a href="/mod/{{ $mod->id }}/{{ $mod->slug }}" class="mod-list-component relative mx-auto w-full max-w-md md:max-w-2xl">
|
||||
<a href="{{ $mod->detailUrl() }}" class="mod-list-component relative mx-auto w-full max-w-md md:max-w-2xl">
|
||||
@if ($mod->featured && !request()->routeIs('home'))
|
||||
<div class="ribbon z-10">{{ __('Featured!') }}</div>
|
||||
@endif
|
||||
<div class="flex flex-col group h-full w-full bg-white dark:bg-gray-950 rounded-xl shadow-md dark:shadow-gray-950 drop-shadow-2xl overflow-hidden hover:shadow-lg hover:bg-gray-50 dark:hover:bg-black hover:shadow-gray-400 dark:hover:shadow-black transition-all duration-200">
|
||||
<div class="h-auto md:h-full md:flex">
|
||||
<div class="h-auto md:h-full md:shrink-0 overflow-hidden">
|
||||
@if (empty($mod->thumbnail))
|
||||
@empty($mod->thumbnail)
|
||||
<img src="https://placehold.co/450x450/EEE/31343C?font=source-sans-pro&text={{ $mod->name }}" alt="{{ $mod->name }}" class="block dark:hidden h-48 w-full object-cover md:h-full md:w-48 transform group-hover:scale-110 transition-all duration-200">
|
||||
<img src="https://placehold.co/450x450/31343C/EEE?font=source-sans-pro&text={{ $mod->name }}" alt="{{ $mod->name }}" class="hidden dark:block h-48 w-full object-cover md:h-full md:w-48 transform group-hover:scale-110 transition-all duration-200">
|
||||
@else
|
||||
<img src="{{ $mod->thumbnailUrl }}" alt="{{ $mod->name }}" class="h-48 w-full object-cover md:h-full md:w-48 transform group-hover:scale-110 transition-all duration-200">
|
||||
@endif
|
||||
@endempty
|
||||
</div>
|
||||
<div class="flex flex-col w-full justify-between p-5">
|
||||
<div class="pb-3">
|
||||
<div class="flex justify-between items-center space-x-3">
|
||||
<h3 class="block mt-1 text-lg leading-tight font-medium text-black dark:text-white group-hover:underline">{{ $mod->name }}</h3>
|
||||
@if($versionScope !== 'none' && $mod->{$versionScope} !== null && $mod->{$versionScope}->latestSptVersion !== null)
|
||||
<span class="badge-version {{ $mod->{$versionScope}->latestSptVersion->first()->color_class }} inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-nowrap">
|
||||
{{ $mod->{$versionScope}->latestSptVersion->first()->version_formatted }}
|
||||
<span class="badge-version {{ $version->latestSptVersion->color_class }} inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-nowrap">
|
||||
{{ $version->latestSptVersion->version_formatted }}
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
<p class="text-sm italic text-slate-600 dark:text-gray-200">
|
||||
By {{ $mod->users->pluck('name')->implode(', ') }}
|
||||
{{ __('By :authors', ['authors' => $mod->users->pluck('name')->implode(', ')]) }}
|
||||
</p>
|
||||
<p class="mt-2 text-slate-500 dark:text-gray-300">
|
||||
{{ Str::limit($mod->teaser, 100) }}
|
||||
</p>
|
||||
<p class="mt-2 text-slate-500 dark:text-gray-300">{{ Str::limit($mod->teaser, 100) }}</p>
|
||||
</div>
|
||||
@if($versionScope !== 'none' && $mod->{$versionScope} !== null)
|
||||
<x-mod-list-stats :mod="$mod" :modVersion="$mod->{$versionScope}"/>
|
||||
@endif
|
||||
<div class="text-slate-700 dark:text-gray-300 text-sm">
|
||||
<div class="flex items-end w-full text-sm">
|
||||
@if ($mod->updated_at || $mod->created_at)
|
||||
<div class="flex items-end w-full">
|
||||
<div class="flex items-center gap-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5m-9-6h.008v.008H12v-.008ZM12 15h.008v.008H12V15Zm0 2.25h.008v.008H12v-.008ZM9.75 15h.008v.008H9.75V15Zm0 2.25h.008v.008H9.75v-.008ZM7.5 15h.008v.008H7.5V15Zm0 2.25h.008v.008H7.5v-.008Zm6.75-4.5h.008v.008h-.008v-.008Zm0 2.25h.008v.008h-.008V15Zm0 2.25h.008v.008h-.008v-.008Zm2.25-4.5h.008v.008H16.5v-.008Zm0 2.25h.008v.008H16.5V15Z" />
|
||||
</svg>
|
||||
<x-time :datetime="$version->created_at" />
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
<div class="flex justify-end items-center gap-1">
|
||||
<span title="{{ __('Exactly :downloads', ['downloads' => $mod->downloads]) }}">
|
||||
{{ Number::downloads($mod->downloads) }}
|
||||
</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,6 +0,0 @@
|
||||
@props(['mods', 'versionScope', 'title', 'link'])
|
||||
|
||||
<div class="mx-auto max-w-7xl px-4 pt-16 sm:px-6 lg:px-8">
|
||||
<x-page-content-title :title="$title" button-text="View All" :button-link="$link" />
|
||||
<x-mod-list :mods="$mods" :versionScope="$versionScope" />
|
||||
</div>
|
@ -1,8 +0,0 @@
|
||||
@foreach ($sections as $section)
|
||||
@include('components.mod-list-section-partial', [
|
||||
'title' => $section['title'],
|
||||
'mods' => $section['mods'],
|
||||
'versionScope' => $section['versionScope'],
|
||||
'link' => $section['link']
|
||||
])
|
||||
@endforeach
|
@ -1,32 +0,0 @@
|
||||
<p {{ $attributes->class(['text-slate-700 dark:text-gray-300 text-sm']) }}>
|
||||
<div class="flex items-end w-full text-sm">
|
||||
<div class="flex items-end w-full">
|
||||
<div class="flex items-center gap-1">
|
||||
<svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M21 8.5v9.25A3.25 3.25 0 0 1 17.75 21H6.25A3.25 3.25 0 0 1 3 17.75V8.5h18ZM7.25 15a1.25 1.25 0 1 0 0 2.5 1.25 1.25 0 0 0 0-2.5ZM12 15a1.25 1.25 0 1 0 0 2.5 1.25 1.25 0 0 0 0-2.5Zm-4.75-4.5a1.25 1.25 0 1 0 0 2.5 1.25 1.25 0 0 0 0-2.5Zm4.75 0a1.25 1.25 0 1 0 0 2.5 1.25 1.25 0 0 0 0-2.5Zm4.75 0a1.25 1.25 0 1 0 0 2.5 1.25 1.25 0 0 0 0-2.5Zm1-7.5A3.25 3.25 0 0 1 21 6.25V7H3v-.75A3.25 3.25 0 0 1 6.25 3h11.5Z"/>
|
||||
</svg>
|
||||
<span>
|
||||
@if(!is_null($mod->updated_at))
|
||||
<time datetime="{{ $modVersion->updated_at->format('c') }}">
|
||||
{{ Carbon::dynamicFormat($modVersion->updated_at) }}
|
||||
</time>
|
||||
@elseif(!is_null($mod->created_at))
|
||||
<time datetime="{{ $modVersion->created_at->format('c') }}">
|
||||
{{ Carbon::dynamicFormat($modVersion->created_at) }}
|
||||
</time>
|
||||
@else
|
||||
N/A
|
||||
@endif
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end items-center gap-1">
|
||||
<span title="{{ __('Exactly') }} {{ $mod->downloads }}">
|
||||
{{ Number::downloads($mod->downloads) }}
|
||||
</span>
|
||||
<svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.25 20.5h13.498a.75.75 0 0 1 .101 1.493l-.101.007H5.25a.75.75 0 0 1-.102-1.494l.102-.006h13.498H5.25Zm6.633-18.498L12 1.995a1 1 0 0 1 .993.883l.007.117v12.59l3.294-3.293a1 1 0 0 1 1.32-.083l.094.084a1 1 0 0 1 .083 1.32l-.083.094-4.997 4.996a1 1 0 0 1-1.32.084l-.094-.083-5.004-4.997a1 1 0 0 1 1.32-1.498l.094.083L11 15.58V2.995a1 1 0 0 1 .883-.993L12 1.995l-.117.007Z"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
@ -1,9 +0,0 @@
|
||||
@props(['mods', 'versionScope'])
|
||||
|
||||
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||
@foreach ($mods as $mod)
|
||||
@if ($mod->{$versionScope})
|
||||
<x-mod-card :mod="$mod" :versionScope="$versionScope"/>
|
||||
@endif
|
||||
@endforeach
|
||||
</div>
|
@ -2,7 +2,7 @@
|
||||
<div class="min-w-0 flex-1">
|
||||
<h2 class="text-2xl font-bold leading-7 text-gray-900 dark:text-gray-200 sm:truncate sm:text-3xl sm:tracking-tight">{{ __($title) }}</h2>
|
||||
</div>
|
||||
@if (isset($buttonText) && isset($buttonLink))
|
||||
@if ($buttonText && $buttonLink)
|
||||
<div class="mt-4 flex md:ml-4 md:mt-0">
|
||||
<a href="{{ $buttonLink }}">
|
||||
<button type="button">{{ __($buttonText) }}</button>
|
||||
|
5
resources/views/components/time.blade.php
Normal file
5
resources/views/components/time.blade.php
Normal file
@ -0,0 +1,5 @@
|
||||
@props(['datetime'])
|
||||
|
||||
<time datetime="{{ $datetime->format('c') }}">
|
||||
{{ Carbon::dynamicFormat($datetime) }}
|
||||
</time>
|
@ -20,7 +20,7 @@
|
||||
</div>
|
||||
|
||||
<div class="pb-10">
|
||||
<x-mod-list-section/>
|
||||
<x-homepage-mods />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -127,7 +127,7 @@
|
||||
@if ($mods->isNotEmpty())
|
||||
<div class="my-8 grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||
@foreach ($mods as $mod)
|
||||
<x-mod-card :mod="$mod" />
|
||||
<x-mod-card :mod="$mod" :version="$mod->latestVersion" />
|
||||
@endforeach
|
||||
</div>
|
||||
@else
|
@ -1,3 +1,3 @@
|
||||
<x-app-layout>
|
||||
@livewire('mod.index')
|
||||
@livewire('mod.listing')
|
||||
</x-app-layout>
|
||||
|
@ -40,8 +40,8 @@
|
||||
</p>
|
||||
<p title="{{ __('Exactly') }} {{ $mod->downloads }}">{{ Number::downloads($mod->downloads) }} {{ __('Downloads') }}</p>
|
||||
<p class="mt-2">
|
||||
<span class="badge-version {{ $mod->latestVersion->latestSptVersion->first()->color_class }} inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-nowrap">
|
||||
{{ $mod->latestVersion->latestSptVersion->first()->version_formatted }} {{ __('Compatible') }}
|
||||
<span class="badge-version {{ $mod->latestVersion->latestSptVersion->color_class }} inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-nowrap">
|
||||
{{ $mod->latestVersion->latestSptVersion->version_formatted }} {{ __('Compatible') }}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
@ -108,8 +108,8 @@
|
||||
<p class="text-gray-700 dark:text-gray-300" title="{{ __('Exactly') }} {{ $version->downloads }}">{{ Number::downloads($version->downloads) }} {{ __('Downloads') }}</p>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="badge-version {{ $version->latestSptVersion->first()->color_class }} inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-nowrap">
|
||||
{{ $version->latestSptVersion->first()->version_formatted }}
|
||||
<span class="badge-version {{ $version->latestSptVersion->color_class }} inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-nowrap">
|
||||
{{ $version->latestSptVersion->version_formatted }}
|
||||
</span>
|
||||
<a href="{{ $version->virus_total_link }}">{{__('Virus Total Results')}}</a>
|
||||
</div>
|
||||
|
@ -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 () {
|
||||
|
Loading…
x
Reference in New Issue
Block a user