diff --git a/app/Console/Commands/ImportWoltlabData.php b/app/Console/Commands/ImportWoltlabData.php index 6585731..bf94538 100644 --- a/app/Console/Commands/ImportWoltlabData.php +++ b/app/Console/Commands/ImportWoltlabData.php @@ -223,6 +223,7 @@ class ImportWoltlabData extends Command 'source_code_link' => $this->fetchSourceLinkValue($modOptions), 'featured' => $mod->isFeatured, 'contains_ai_content' => $this->fetchContainsAiContentValue($modOptions), + 'contains_ads' => $this->fetchContainsAdsValue($modOptions), 'disabled' => $mod->isDisabled, 'created_at' => Carbon::parse($mod->time, 'UTC'), 'updated_at' => Carbon::parse($mod->lastChangeTime, 'UTC'), @@ -305,6 +306,18 @@ class ImportWoltlabData extends Command return false; } + protected function fetchContainsAdsValue(array $options): bool + { + // Iterate over the options and find the 'optionID' of 3. That record will contain the Ad flag. + foreach ($options as $option) { + if ($option->optionID == 3) { + return (bool) $option->optionValue; + } + } + + return false; + } + protected function fetchModThumbnail($command, &$curl, string $fileID, string $thumbnailHash, string $thumbnailExtension): string { if (empty($fileID) || empty($thumbnailHash) || empty($thumbnailExtension)) { @@ -400,6 +413,7 @@ class ImportWoltlabData extends Command 'spt_version_id' => SptVersion::whereHubId($versionLabel)->value('id'), 'virus_total_link' => $this->fetchVirusTotalLink($modOptions), 'downloads' => (int) $version->downloads, + 'disabled' => (bool) $version->isDisabled, 'created_at' => Carbon::parse($version->uploadTime, 'UTC'), 'updated_at' => Carbon::parse($version->uploadTime, 'UTC'), ]; @@ -424,14 +438,19 @@ class ImportWoltlabData extends Command // in the 'optionValue' column. The 'optionID' of 6 should take precedence over 1. If neither are found, return // an empty string. foreach ($options as $option) { - if ($option->optionID == 5 && ! empty($option->optionValue)) { + if ($option->optionID == 6 && ! empty($option->optionValue)) { return $option->optionValue; } - if ($option->optionID == 1 && ! empty($option->optionValue)) { + if ($option->optionID == 2 && ! empty($option->optionValue)) { return $option->optionValue; } } return ''; } + + protected function updateDisabledPropertty(): void + { + $this->output->newLine(); + } } diff --git a/app/Http/Controllers/ModController.php b/app/Http/Controllers/ModController.php index 890b074..1cad7c3 100644 --- a/app/Http/Controllers/ModController.php +++ b/app/Http/Controllers/ModController.php @@ -27,10 +27,11 @@ class ModController extends Controller public function show(int $modId, string $slug) { - $mod = Mod::select(['id', 'user_id', 'name', 'slug', 'teaser', 'thumbnail', 'featured']) + $mod = Mod::select() ->withLatestSptVersion() ->withTotalDownloads() ->with('user:id,name') + ->with('license:id,name,link') ->find($modId); if (! $mod || $mod->slug !== $slug) { diff --git a/app/Http/Resources/ModResource.php b/app/Http/Resources/ModResource.php index 6da33ac..f61b2c6 100644 --- a/app/Http/Resources/ModResource.php +++ b/app/Http/Resources/ModResource.php @@ -5,6 +5,7 @@ namespace App\Http\Resources; use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\JsonResource; +/** @mixin \App\Models\Mod */ class ModResource extends JsonResource { public function toArray(Request $request): array diff --git a/app/Models/Mod.php b/app/Models/Mod.php index 3f99849..32a5aa0 100644 --- a/app/Models/Mod.php +++ b/app/Models/Mod.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Models\Scopes\DisabledScope; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; @@ -11,6 +12,9 @@ use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Facades\DB; use Illuminate\Support\Str; +/** + * @property string $slug + */ class Mod extends Model { use HasFactory, SoftDeletes; @@ -24,6 +28,11 @@ class Mod extends Model 'source_code_link', ]; + protected static function booted(): void + { + static::addGlobalScope(new DisabledScope); + } + protected function slug(): Attribute { return Attribute::make( @@ -49,7 +58,7 @@ class Mod extends Model public function scopeWithTotalDownloads($query) { - $query->addSelect(['total_downloads' => ModVersion::selectRaw('SUM(downloads) AS total_downloads') + return $query->addSelect(['total_downloads' => ModVersion::selectRaw('SUM(downloads) AS total_downloads') ->whereColumn('mod_id', 'mods.id'), ]); } @@ -73,6 +82,7 @@ class Mod extends Model ->orderByDesc(DB::raw('naturalSort(version)')) ->take(1), ]) + ->havingNotNull('latest_spt_version_id') ->with(['latestSptVersion', 'latestSptVersion.sptVersion']); } @@ -95,6 +105,7 @@ class Mod extends Model ->orderByDesc('updated_at') ->take(1) ) + ->havingNotNull('last_updated_spt_version_id') ->with(['lastUpdatedVersion', 'lastUpdatedVersion.sptVersion']); } } diff --git a/app/Models/ModVersion.php b/app/Models/ModVersion.php index 52e62b0..3ec3b8f 100644 --- a/app/Models/ModVersion.php +++ b/app/Models/ModVersion.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Models\Scopes\DisabledScope; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -20,6 +21,11 @@ class ModVersion extends Model 'downloads', ]; + protected static function booted(): void + { + static::addGlobalScope(new DisabledScope); + } + public function mod(): BelongsTo { return $this->belongsTo(Mod::class); diff --git a/app/Models/Scopes/DisabledScope.php b/app/Models/Scopes/DisabledScope.php new file mode 100644 index 0000000..517d3ba --- /dev/null +++ b/app/Models/Scopes/DisabledScope.php @@ -0,0 +1,18 @@ +where($model->getTable().'.disabled', false); + } +} diff --git a/app/Nova/Resource.php b/app/Nova/Resource.php index 03277df..206170f 100644 --- a/app/Nova/Resource.php +++ b/app/Nova/Resource.php @@ -10,7 +10,6 @@ abstract class Resource extends NovaResource /** * Build an "index" query for the given resource. * - * @param \Laravel\Nova\Http\Requests\NovaRequest $request * @param \Illuminate\Database\Eloquent\Builder $query * @return \Illuminate\Database\Eloquent\Builder */ @@ -22,7 +21,6 @@ abstract class Resource extends NovaResource /** * Build a Scout search query for the given resource. * - * @param \Laravel\Nova\Http\Requests\NovaRequest $request * @param \Laravel\Scout\Builder $query * @return \Laravel\Scout\Builder */ @@ -34,7 +32,6 @@ abstract class Resource extends NovaResource /** * Build a "detail" query for the given resource. * - * @param \Laravel\Nova\Http\Requests\NovaRequest $request * @param \Illuminate\Database\Eloquent\Builder $query * @return \Illuminate\Database\Eloquent\Builder */ @@ -48,7 +45,6 @@ abstract class Resource extends NovaResource * * This query determines which instances of the model may be attached to other resources. * - * @param \Laravel\Nova\Http\Requests\NovaRequest $request * @param \Illuminate\Database\Eloquent\Builder $query * @return \Illuminate\Database\Eloquent\Builder */ diff --git a/app/Policies/ModPolicy.php b/app/Policies/ModPolicy.php index c806b37..0d18b6a 100644 --- a/app/Policies/ModPolicy.php +++ b/app/Policies/ModPolicy.php @@ -12,7 +12,7 @@ class ModPolicy */ public function viewAny(User $user): bool { - // + return false; } /** @@ -29,7 +29,7 @@ class ModPolicy */ public function create(User $user): bool { - // + return false; } /** @@ -37,7 +37,7 @@ class ModPolicy */ public function update(User $user, Mod $mod): bool { - // + return false; } /** @@ -45,7 +45,7 @@ class ModPolicy */ public function delete(User $user, Mod $mod): bool { - // + return false; } /** @@ -53,7 +53,7 @@ class ModPolicy */ public function restore(User $user, Mod $mod): bool { - // + return false; } /** @@ -61,6 +61,6 @@ class ModPolicy */ public function forceDelete(User $user, Mod $mod): bool { - // + return false; } } diff --git a/composer.json b/composer.json index 3aa5b0c..325045c 100644 --- a/composer.json +++ b/composer.json @@ -9,18 +9,18 @@ "license": "MIT", "require": { "php": "^8.3", + "ext-curl": "*", "laravel/framework": "^11.0", "laravel/jetstream": "^5.1", "laravel/nova": "^4.0", "laravel/pulse": "^1.1", "laravel/sanctum": "^4.0", + "laravel/scout": "^10.9", "laravel/tinker": "^2.9", - "livewire/livewire": "^3.0", - "ext-curl": "*" + "livewire/livewire": "^3.0" }, "require-dev": { "barryvdh/laravel-debugbar": "^3.13", - "deployer/deployer": "^7.4", "fakerphp/faker": "^1.23", "larastan/larastan": "^2.9", "laravel/pint": "^1.13", diff --git a/composer.lock b/composer.lock index b26dbb2..f14cf82 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "40b3d2220c6f07a6c8182c49f5174d16", + "content-hash": "a7c13b1f59edf063a95ea214b1c73553", "packages": [ { "name": "bacon/bacon-qr-code", @@ -2170,6 +2170,84 @@ }, "time": "2024-04-10T19:39:58+00:00" }, + { + "name": "laravel/scout", + "version": "v10.9.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/scout.git", + "reference": "7bac13a61f1670b4314a65a13b8b12c6575270c8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/scout/zipball/7bac13a61f1670b4314a65a13b8b12c6575270c8", + "reference": "7bac13a61f1670b4314a65a13b8b12c6575270c8", + "shasum": "" + }, + "require": { + "illuminate/bus": "^9.0|^10.0|^11.0", + "illuminate/contracts": "^9.0|^10.0|^11.0", + "illuminate/database": "^9.0|^10.0|^11.0", + "illuminate/http": "^9.0|^10.0|^11.0", + "illuminate/pagination": "^9.0|^10.0|^11.0", + "illuminate/queue": "^9.0|^10.0|^11.0", + "illuminate/support": "^9.0|^10.0|^11.0", + "php": "^8.0", + "symfony/console": "^6.0|^7.0" + }, + "require-dev": { + "algolia/algoliasearch-client-php": "^3.2", + "meilisearch/meilisearch-php": "^1.0", + "mockery/mockery": "^1.0", + "orchestra/testbench": "^7.31|^8.11|^9.0", + "php-http/guzzle7-adapter": "^1.0", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.3|^10.4", + "typesense/typesense-php": "^4.9.3" + }, + "suggest": { + "algolia/algoliasearch-client-php": "Required to use the Algolia engine (^3.2).", + "meilisearch/meilisearch-php": "Required to use the Meilisearch engine (^1.0).", + "typesense/typesense-php": "Required to use the Typesense engine (^4.9)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Scout\\ScoutServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Scout\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel Scout provides a driver based solution to searching your Eloquent models.", + "keywords": [ + "algolia", + "laravel", + "search" + ], + "support": { + "issues": "https://github.com/laravel/scout/issues", + "source": "https://github.com/laravel/scout" + }, + "time": "2024-05-07T14:16:56+00:00" + }, { "name": "laravel/serializable-closure", "version": "v1.3.3", @@ -7236,49 +7314,6 @@ ], "time": "2024-04-12T11:20:37+00:00" }, - { - "name": "deployer/deployer", - "version": "v7.4.0", - "source": { - "type": "git", - "url": "https://github.com/deployphp/deployer.git", - "reference": "b438dc22545ab2ecc67d79c80c7a79c156de3599" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/deployphp/deployer/zipball/b438dc22545ab2ecc67d79c80c7a79c156de3599", - "reference": "b438dc22545ab2ecc67d79c80c7a79c156de3599", - "shasum": "" - }, - "bin": [ - "dep" - ], - "type": "library", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Anton Medvedev", - "email": "anton@medv.io" - } - ], - "description": "Deployment Tool", - "homepage": "https://deployer.org", - "support": { - "docs": "https://deployer.org/docs", - "issues": "https://github.com/deployphp/deployer/issues", - "source": "https://github.com/deployphp/deployer" - }, - "funding": [ - { - "url": "https://github.com/sponsors/antonmedv", - "type": "github" - } - ], - "time": "2024-04-17T20:55:49+00:00" - }, { "name": "fakerphp/faker", "version": "v1.23.1", diff --git a/config/scout.php b/config/scout.php new file mode 100644 index 0000000..ac1a88e --- /dev/null +++ b/config/scout.php @@ -0,0 +1,202 @@ + env('SCOUT_DRIVER', 'algolia'), + + /* + |-------------------------------------------------------------------------- + | Index Prefix + |-------------------------------------------------------------------------- + | + | Here you may specify a prefix that will be applied to all search index + | names used by Scout. This prefix may be useful if you have multiple + | "tenants" or applications sharing the same search infrastructure. + | + */ + + 'prefix' => env('SCOUT_PREFIX', ''), + + /* + |-------------------------------------------------------------------------- + | Queue Data Syncing + |-------------------------------------------------------------------------- + | + | This option allows you to control if the operations that sync your data + | with your search engines are queued. When this is set to "true" then + | all automatic data syncing will get queued for better performance. + | + */ + + 'queue' => env('SCOUT_QUEUE', false), + + /* + |-------------------------------------------------------------------------- + | Database Transactions + |-------------------------------------------------------------------------- + | + | This configuration option determines if your data will only be synced + | with your search indexes after every open database transaction has + | been committed, thus preventing any discarded data from syncing. + | + */ + + 'after_commit' => false, + + /* + |-------------------------------------------------------------------------- + | Chunk Sizes + |-------------------------------------------------------------------------- + | + | These options allow you to control the maximum chunk size when you are + | mass importing data into the search engine. This allows you to fine + | tune each of these chunk sizes based on the power of the servers. + | + */ + + 'chunk' => [ + 'searchable' => 500, + 'unsearchable' => 500, + ], + + /* + |-------------------------------------------------------------------------- + | Soft Deletes + |-------------------------------------------------------------------------- + | + | This option allows to control whether to keep soft deleted records in + | the search indexes. Maintaining soft deleted records can be useful + | if your application still needs to search for the records later. + | + */ + + 'soft_delete' => false, + + /* + |-------------------------------------------------------------------------- + | Identify User + |-------------------------------------------------------------------------- + | + | This option allows you to control whether to notify the search engine + | of the user performing the search. This is sometimes useful if the + | engine supports any analytics based on this application's users. + | + | Supported engines: "algolia" + | + */ + + 'identify' => env('SCOUT_IDENTIFY', false), + + /* + |-------------------------------------------------------------------------- + | Algolia Configuration + |-------------------------------------------------------------------------- + | + | Here you may configure your Algolia settings. Algolia is a cloud hosted + | search engine which works great with Scout out of the box. Just plug + | in your application ID and admin API key to get started searching. + | + */ + + 'algolia' => [ + 'id' => env('ALGOLIA_APP_ID', ''), + 'secret' => env('ALGOLIA_SECRET', ''), + ], + + /* + |-------------------------------------------------------------------------- + | Meilisearch Configuration + |-------------------------------------------------------------------------- + | + | Here you may configure your Meilisearch settings. Meilisearch is an open + | source search engine with minimal configuration. Below, you can state + | the host and key information for your own Meilisearch installation. + | + | See: https://www.meilisearch.com/docs/learn/configuration/instance_options#all-instance-options + | + */ + + 'meilisearch' => [ + 'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'), + 'key' => env('MEILISEARCH_KEY'), + 'index-settings' => [ + // 'users' => [ + // 'filterableAttributes'=> ['id', 'name', 'email'], + // ], + ], + ], + + /* + |-------------------------------------------------------------------------- + | Typesense Configuration + |-------------------------------------------------------------------------- + | + | Here you may configure your Typesense settings. Typesense is an open + | source search engine using minimal configuration. Below, you will + | state the host, key, and schema configuration for the instance. + | + */ + + 'typesense' => [ + 'client-settings' => [ + 'api_key' => env('TYPESENSE_API_KEY', 'xyz'), + 'nodes' => [ + [ + 'host' => env('TYPESENSE_HOST', 'localhost'), + 'port' => env('TYPESENSE_PORT', '8108'), + 'path' => env('TYPESENSE_PATH', ''), + 'protocol' => env('TYPESENSE_PROTOCOL', 'http'), + ], + ], + 'nearest_node' => [ + 'host' => env('TYPESENSE_HOST', 'localhost'), + 'port' => env('TYPESENSE_PORT', '8108'), + 'path' => env('TYPESENSE_PATH', ''), + 'protocol' => env('TYPESENSE_PROTOCOL', 'http'), + ], + 'connection_timeout_seconds' => env('TYPESENSE_CONNECTION_TIMEOUT_SECONDS', 2), + 'healthcheck_interval_seconds' => env('TYPESENSE_HEALTHCHECK_INTERVAL_SECONDS', 30), + 'num_retries' => env('TYPESENSE_NUM_RETRIES', 3), + 'retry_interval_seconds' => env('TYPESENSE_RETRY_INTERVAL_SECONDS', 1), + ], + 'model-settings' => [ + // User::class => [ + // 'collection-schema' => [ + // 'fields' => [ + // [ + // 'name' => 'id', + // 'type' => 'string', + // ], + // [ + // 'name' => 'name', + // 'type' => 'string', + // ], + // [ + // 'name' => 'created_at', + // 'type' => 'int64', + // ], + // ], + // 'default_sorting_field' => 'created_at', + // ], + // 'search-parameters' => [ + // 'query_by' => 'name' + // ], + // ], + ], + ], + +]; diff --git a/database/factories/ModFactory.php b/database/factories/ModFactory.php index 44d3a56..261b70a 100644 --- a/database/factories/ModFactory.php +++ b/database/factories/ModFactory.php @@ -26,6 +26,8 @@ class ModFactory extends Factory 'source_code_link' => $this->faker->url(), 'featured' => $this->faker->boolean, 'contains_ai_content' => $this->faker->boolean, + 'contains_ads' => $this->faker->boolean, + 'disabled' => $this->faker->boolean, 'created_at' => now(), 'updated_at' => now(), ]; diff --git a/database/factories/ModVersionFactory.php b/database/factories/ModVersionFactory.php index cbefc16..36f32a9 100644 --- a/database/factories/ModVersionFactory.php +++ b/database/factories/ModVersionFactory.php @@ -22,6 +22,7 @@ class ModVersionFactory extends Factory 'spt_version_id' => SptVersion::factory(), 'virus_total_link' => $this->faker->url(), 'downloads' => $this->faker->randomNumber(), + 'disabled' => $this->faker->boolean, 'created_at' => Carbon::now(), 'updated_at' => Carbon::now(), ]; diff --git a/database/factories/SptVersionFactory.php b/database/factories/SptVersionFactory.php index 6e17ca0..e25d046 100644 --- a/database/factories/SptVersionFactory.php +++ b/database/factories/SptVersionFactory.php @@ -14,7 +14,7 @@ class SptVersionFactory extends Factory { return [ 'version' => $this->faker->numerify('1.#.#'), - 'color_class' => $this->faker->randomElement(['green', 'yellow', 'red', 'gray']), + 'color_class' => $this->faker->randomElement(['red', 'green', 'emerald', 'lime', 'yellow', 'grey']), 'created_at' => Carbon::now(), 'updated_at' => Carbon::now(), ]; diff --git a/database/migrations/2024_05_15_022710_create_mods_table.php b/database/migrations/2024_05_15_022710_create_mods_table.php index fbb6f05..d05658d 100644 --- a/database/migrations/2024_05_15_022710_create_mods_table.php +++ b/database/migrations/2024_05_15_022710_create_mods_table.php @@ -23,6 +23,7 @@ return new class extends Migration $table->string('source_code_link'); $table->boolean('featured')->default(false); $table->boolean('contains_ai_content')->default(false); + $table->boolean('contains_ads')->default(false); $table->boolean('disabled')->default(false); $table->softDeletes(); $table->timestamps(); diff --git a/database/migrations/2024_05_15_023705_create_mod_versions_table.php b/database/migrations/2024_05_15_023705_create_mod_versions_table.php index ef83e58..13ed6dc 100644 --- a/database/migrations/2024_05_15_023705_create_mod_versions_table.php +++ b/database/migrations/2024_05_15_023705_create_mod_versions_table.php @@ -20,6 +20,7 @@ return new class extends Migration $table->foreignIdFor(SptVersion::class)->constrained('spt_versions'); $table->string('virus_total_link'); $table->unsignedBigInteger('downloads'); + $table->boolean('disabled')->default(false); $table->softDeletes(); $table->timestamps(); }); diff --git a/deploy.yaml b/deploy.yaml deleted file mode 100644 index 8dc59d8..0000000 --- a/deploy.yaml +++ /dev/null @@ -1,24 +0,0 @@ -import: - - recipe/laravel.php - -config: - application: 'Forge' - repository: 'https://github.com/SPT-AKI/forge.git' - keep_releases: 5 - -hosts: - forge.sp-tarkov.com: - remote_user: deployer - deploy_path: '~/Forge' - branch: 'main' - env: - environment: production - labels: - env: prod - -tasks: - build: - - run: uptime - -after: - deploy:failed: deploy:unlock diff --git a/resources/views/components/mod-list.blade.php b/resources/views/components/mod-list.blade.php index a2c84e5..c7c4ae5 100644 --- a/resources/views/components/mod-list.blade.php +++ b/resources/views/components/mod-list.blade.php @@ -16,6 +16,9 @@
+ @if(is_null($mod->{$versionScope})) + @dd($mod) + @endif

{{ $mod->name }}

{{ $mod->{$versionScope}->sptVersion->version }} @@ -24,7 +27,7 @@

By {{ $mod->user->name }}

{{ $mod->teaser }}

- +
diff --git a/resources/views/mod/show.blade.php b/resources/views/mod/show.blade.php index 2fc5dea..49710e6 100644 --- a/resources/views/mod/show.blade.php +++ b/resources/views/mod/show.blade.php @@ -36,9 +36,47 @@ -

{{ __('Details') }}

- +
+

{{ __('Details') }}

+ +
+