Merge remote-tracking branch 'upstream/develop' into update-mod-list-stats

This commit is contained in:
IsWaffle 2024-09-11 14:38:32 -04:00
commit 0190e0431d
15 changed files with 305 additions and 399 deletions

16
.github/README.md vendored
View File

@ -12,7 +12,7 @@ The Forge is a Laravel-based web application that provides a platform for the Si
## Development Environment Setup
We use [Laravel Sail](https://laravel.com/docs/11.x/sail) to mirror the services that are used in our production server in a local development environment. You can see detailed instructions on how to configure the [full development environment](https://github.com/sp-tarkov/forge/wiki/Full-Windows-Dev-Env) or a [lightweight development environment](https://github.com/sp-tarkov/forge/wiki/Light-Windows-Dev-Env) on the project wiki. The full development environment is recommended.
We use [Laravel Sail](https://laravel.com/docs/11.x/sail) to mirror the services that are used in our production server in a local development environment. You can see detailed instructions on how to configure the [full development environment](https://dev.sp-tarkov.com/SPT/forge/wiki/Full-Windows-Dev-Env) or a [lightweight development environment](https://dev.sp-tarkov.com/SPT/forge/wiki/Light-Windows-Dev-Env) on the project wiki. The full development environment is recommended.
### Available Services:
@ -25,13 +25,13 @@ We use [Laravel Sail](https://laravel.com/docs/11.x/sail) to mirror the services
### Notable Routes
| Service | Authentication | Access Via Host |
|----------------------------------|----------------|-----------------------------|
| Service | Authentication | Access Via Host |
|----------------------------------|----------------|----------------------------|
| Laravel Filament Admin Panel | Via User Role | <http://localhost/admin> |
| Redis Queue Management (Horizon) | Via User Role | <http://localhost/horizon> |
| Website Status (Pulse) | Via User Role | <http://localhost/pulse> |
| Meilisearch WebUI | Local Only | <http://localhost:7700> |
| Mailpit WebUI | Local Only | <http://localhost:8025> |
| Meilisearch WebUI | Local Only | <http://localhost:7700> |
| Mailpit WebUI | Local Only | <http://localhost:8025> |
Most of these connection settings should already be configured in the `.env.full` or `.env.light` example files. Simply save one of these (depending on your environment) as `.env` and adjust settings as needed.
@ -80,12 +80,12 @@ For more information on Laravel development, please refer to the [official docum
## Development Discussion
*__Please note__, we are very early in development and will likely not accept work that is not discussed beforehand through the following channels...*
You may propose new features or improvements of existing Forge behavior in [the repository's GitHub discussion board](https://github.com/sp-tarkov/forge/discussions). If you propose a new feature, please be willing to implement at least some of the code that would be needed to complete the feature.
*__Please note__, we are very early in development and will likely not accept work that is not discussed beforehand.*
Informal discussion regarding bugs, new features, and implementation of existing features takes place in the `#website-general` channel of the [Single Player Tarkov Discord server](https://discord.com/invite/Xn9msqQZan). Refringe, the maintainer of Forge, is typically present in the channel on weekdays from 9am-5pm Eastern Time (ET), and sporadically present in the channel at other times.
If you propose a new feature, please be willing to implement at least some of the code that would be needed to complete the feature.
## Which Branch?
The `main` branch is the default branch for Forge. This branch is used for the latest stable release of the site. The `develop` branch is used for the latest development changes. All feature branches should be based on the `develop` branch. All pull requests should target the `develop` branch.

View File

@ -8,7 +8,6 @@ use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Cache;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Session;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
@ -21,41 +20,37 @@ class Index extends Component
* The search query value.
*/
#[Url]
#[Session]
public string $query = '';
/**
* The sort order value.
*/
#[Url]
#[Session]
public string $order = 'created';
/**
* The SPT versions filter value.
*/
#[Url]
#[Session]
public array $sptVersions = [];
/**
* The featured filter value.
*/
#[Url]
#[Session]
public string $featured = 'include';
/**
* The available SPT versions.
*/
public Collection $availableSptVersions;
public Collection $activeSptVersions;
/**
* The component mount method, run only once when the component is mounted.
*/
public function mount(): void
{
$this->availableSptVersions = $this->availableSptVersions ?? Cache::remember('available-spt-versions', 60 * 60, function () {
$this->activeSptVersions = $this->activeSptVersions ?? Cache::remember('active-spt-versions', 60 * 60, function () {
return SptVersion::getVersionsForLastThreeMinors();
});
@ -67,7 +62,7 @@ class Index extends Component
*/
public function getLatestMinorVersions(): Collection
{
return $this->availableSptVersions->filter(function (SptVersion $sptVersion) {
return $this->activeSptVersions->filter(function (SptVersion $sptVersion) {
return $sptVersion->isLatestMinor();
});
}
@ -102,9 +97,6 @@ class Index extends Component
$this->query = '';
$this->sptVersions = $this->getLatestMinorVersions()->pluck('version')->toArray();
$this->featured = 'include';
// Clear local storage
$this->dispatch('clear-filters');
}
/**

View File

@ -14,6 +14,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Laravel\Scout\Searchable;
@ -144,6 +145,14 @@ class Mod extends Model
return false;
}
// Ensure the latest SPT version is within the last three minor versions.
$activeSptVersions = Cache::remember('active-spt-versions', 60 * 60, function () {
return SptVersion::getVersionsForLastThreeMinors();
});
if (! in_array($latestVersion->latestSptVersion()->first()->version, $activeSptVersions->pluck('version')->toArray())) {
return false;
}
// All conditions are met; the mod should be searchable.
return true;
}

View File

@ -11,8 +11,8 @@ use App\Observers\ModDependencyObserver;
use App\Observers\ModObserver;
use App\Observers\ModVersionObserver;
use App\Observers\SptVersionObserver;
use App\Services\LatestSptVersionService;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Number;
use Illuminate\Support\ServiceProvider;
@ -24,9 +24,7 @@ class AppServiceProvider extends ServiceProvider
*/
public function register(): void
{
$this->app->singleton(LatestSptVersionService::class, function ($app) {
return new LatestSptVersionService;
});
//
}
/**
@ -37,18 +35,34 @@ class AppServiceProvider extends ServiceProvider
// Allow mass assignment for all models. Be careful!
Model::unguard();
// Register observers.
Mod::observe(ModObserver::class);
ModVersion::observe(ModVersionObserver::class);
ModDependency::observe(ModDependencyObserver::class);
SptVersion::observe(SptVersionObserver::class);
$this->registerObservers();
$this->registerNumberMacros();
$this->registerCarbonMacros();
// This gate determines who can access the Pulse dashboard.
Gate::define('viewPulse', function (User $user) {
return $user->isAdmin();
});
}
// Register a number macro to format download numbers.
/**
* Register model observers.
*/
private function registerObservers(): void
{
Mod::observe(ModObserver::class);
ModVersion::observe(ModVersionObserver::class);
ModDependency::observe(ModDependencyObserver::class);
SptVersion::observe(SptVersionObserver::class);
}
/**
* Register custom number macros.
*/
private function registerNumberMacros(): void
{
// Format download numbers.
Number::macro('downloads', function (int|float $number) {
return Number::forHumans(
$number,
@ -58,4 +72,22 @@ class AppServiceProvider extends ServiceProvider
);
});
}
/**
* Register custom Carbon macros.
*/
private function registerCarbonMacros(): void
{
// Format dates dynamically based on the time passed.
Carbon::macro('dynamicFormat', function (Carbon $date) {
if ($date->diff(now())->m > 1) {
return $date->format('M jS, Y');
}
if ($date->diff(now())->d === 0) {
return $date->diffForHumans();
}
return $date->format('M jS, g:i A');
});
}
}

View File

@ -88,16 +88,19 @@ class ModListSection extends Component
'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',
],
];
}

View File

@ -24,4 +24,5 @@ return Application::configure(basePath: dirname(__DIR__))
})
->withExceptions(function (Exceptions $exceptions) {
//
})->create();
})
->create();

View File

@ -36,7 +36,6 @@
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.1",
"pestphp/pest": "^2.34",
"pestphp/pest-plugin-stressless": "^2.2",
"spatie/laravel-ignition": "^2.8"
},
"autoload": {

498
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,7 @@
<?php
use Illuminate\Support\Facades\Facade;
return [
/*
@ -123,4 +125,19 @@ return [
'store' => env('APP_MAINTENANCE_STORE', 'database'),
],
/*
|--------------------------------------------------------------------------
| Class Aliases
|--------------------------------------------------------------------------
|
| This array of class aliases will be registered when this application
| is started. However, feel free to register as many as you wish as
| the aliases are "lazy" loaded so they don't hinder performance.
|
*/
'aliases' => Facade::defaultAliases()->merge([
'Carbon' => \Carbon\Carbon::class,
])->toArray(),
];

64
package-lock.json generated
View File

@ -1,5 +1,5 @@
{
"name": "html",
"name": "forge",
"lockfileVersion": 3,
"requires": true,
"packages": {
@ -780,9 +780,9 @@
]
},
"node_modules/@tailwindcss/forms": {
"version": "0.5.8",
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.8.tgz",
"integrity": "sha512-DJs7B7NPD0JH7BVvdHWNviWmunlFhuEkz7FyFxE4japOWYMLl9b1D6+Z9mivJJPWr6AEbmlPqgiFRyLwFB1SgQ==",
"version": "0.5.9",
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.9.tgz",
"integrity": "sha512-tM4XVr2+UVTxXJzey9Twx48c1gcxFStqn1pQz0tRsX8o3DvxhN5oY5pvyAbUx7VTaZxpej4Zzvc6h+1RJBzpIg==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -915,9 +915,9 @@
}
},
"node_modules/axios": {
"version": "1.7.6",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.6.tgz",
"integrity": "sha512-Ekur6XDwhnJ5RgOCaxFnXyqlPALI3rVeukZMwOdfghW7/wGz784BYKiQq+QD8NPcr91KRo30KfHOchyijwWw7g==",
"version": "1.7.7",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
"integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -1013,9 +1013,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001655",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001655.tgz",
"integrity": "sha512-jRGVy3iSGO5Uutn2owlb5gR6qsGngTw9ZTb4ali9f3glshcNmJ2noam4Mo9zia5P9Dk3jNNydy7vQjuE5dQmfg==",
"version": "1.0.30001658",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001658.tgz",
"integrity": "sha512-N2YVqWbJELVdrnsW5p+apoQyYt51aBMSsBZki1XZEfeBCexcM/sf4xiAHcXQBkuOwJBXtWF7aW1sYX6tKebPHw==",
"dev": true,
"funding": [
{
@ -1161,9 +1161,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.5.13",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz",
"integrity": "sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==",
"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==",
"dev": true,
"license": "ISC"
},
@ -1273,9 +1273,9 @@
}
},
"node_modules/follow-redirects": {
"version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"dev": true,
"funding": [
{
@ -1784,9 +1784,9 @@
}
},
"node_modules/picocolors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
"integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
"integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==",
"dev": true,
"license": "ISC"
},
@ -1824,9 +1824,9 @@
}
},
"node_modules/postcss": {
"version": "8.4.41",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz",
"integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==",
"version": "8.4.45",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.45.tgz",
"integrity": "sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q==",
"dev": true,
"funding": [
{
@ -2272,9 +2272,9 @@
}
},
"node_modules/source-map-js": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
@ -2574,14 +2574,14 @@
"license": "MIT"
},
"node_modules/vite": {
"version": "5.4.2",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.2.tgz",
"integrity": "sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA==",
"version": "5.4.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.3.tgz",
"integrity": "sha512-IH+nl64eq9lJjFqU+/yrRnrHPVTlgy42/+IzbOdaFDVlyLgI/wDlf+FCobXLX1cT0X5+7LMyH1mIy2xJdLfo8Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.21.3",
"postcss": "^8.4.41",
"postcss": "^8.4.43",
"rollup": "^4.20.0"
},
"bin": {
@ -2759,9 +2759,9 @@
}
},
"node_modules/yaml": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz",
"integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==",
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz",
"integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==",
"dev": true,
"license": "ISC",
"bin": {

View File

@ -1,4 +1,4 @@
@props(['mod', 'versionScope' => 'latestVersion'])
@props(['mod', 'versionScope' => 'none'])
<a href="/mod/{{ $mod->id }}/{{ $mod->slug }}" class="mod-list-component relative mx-auto w-full max-w-md md:max-w-2xl">
@if ($mod->featured && !request()->routeIs('home'))
@ -18,16 +18,20 @@
<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>
@endif
</div>
<p class="text-sm italic text-slate-600 dark:text-gray-200">
By {{ $mod->users->pluck('name')->implode(', ') }}
</p>
<p class="mt-2 text-slate-500 dark:text-gray-300">{{ Str::limit($mod->teaser, 100) }}</p>
</div>
<x-mod-list-stats :mod="$mod" :modVersion="$mod->{$versionScope}"/>
@if($versionScope !== 'none' && $mod->{$versionScope} !== null)
<x-mod-list-stats :mod="$mod" :modVersion="$mod->{$versionScope}"/>
@endif
</div>
</div>
</div>

View File

@ -1,10 +1,6 @@
@props(['mods', 'versionScope', 'title'])
@props(['mods', 'versionScope', 'title', 'link'])
<div class="mx-auto max-w-7xl px-4 pt-16 sm:px-6 lg:px-8">
{{--
TODO: The button-link should be dynamic based on the versionScope. Eg. Featured `View All` button should take
the user to the mods page with the `featured` query parameter set.
--}}
<x-page-content-title :title="$title" button-text="View All" button-link="/mods" />
<x-page-content-title :title="$title" button-text="View All" :button-link="$link" />
<x-mod-list :mods="$mods" :versionScope="$versionScope" />
</div>

View File

@ -3,5 +3,6 @@
'title' => $section['title'],
'mods' => $section['mods'],
'versionScope' => $section['versionScope'],
'link' => $section['link']
])
@endforeach

View File

@ -53,13 +53,13 @@
<div class="mx-auto grid max-w-7xl grid-cols-2 gap-x-4 px-4 text-sm sm:px-6 md:gap-x-6 lg:px-8">
<div class="grid auto-rows-min grid-cols-1 gap-y-10 md:grid-cols-2 md:gap-x-6">
@php
$totalVersions = count($availableSptVersions);
$totalVersions = count($activeSptVersions);
$half = ceil($totalVersions / 2);
@endphp
<fieldset>
<legend class="block font-medium text-gray-900 dark:text-gray-100">{{ __('SPT Versions') }}</legend>
<div class="space-y-6 pt-6 sm:space-y-4 sm:pt-4">
@foreach ($availableSptVersions as $index => $version)
@foreach ($activeSptVersions as $index => $version)
@if ($index < $half)
<x-filter-checkbox id="sptVersions-{{ $index }}" name="sptVersions" value="{{ $version->version }}">{{ $version->version }}</x-filter-checkbox>
@endif
@ -69,7 +69,7 @@
<fieldset>
<legend class="block font-medium text-gray-900 dark:text-gray-100">&nbsp;</legend>
<div class="space-y-6 pt-6 sm:space-y-4 sm:pt-4">
@foreach ($availableSptVersions as $index => $version)
@foreach ($activeSptVersions as $index => $version)
@if ($index >= $half)
<x-filter-checkbox id="sptVersions-{{ $index }}" name="sptVersions" value="{{ $version->version }}">{{ $version->version }}</x-filter-checkbox>
@endif

View File

@ -114,8 +114,8 @@
<a href="{{ $version->virus_total_link }}">{{__('Virus Total Results')}}</a>
</div>
<div class="flex items-center justify-between text-gray-600 dark:text-gray-400">
<span>{{ __('Created') }} {{ $version->created_at->format("M d, h:m a") }}</span>
<span>{{ __('Updated') }} {{ $version->updated_at->format("M d, h:m a") }}</span>
<span>{{ __('Created') }} {{ Carbon::dynamicFormat($version->created_at) }}</span>
<span>{{ __('Updated') }} {{ Carbon::dynamicFormat($version->updated_at) }}</span>
</div>
{{-- Display latest resolved dependencies --}}