mirror of
https://github.com/sp-tarkov/forge.git
synced 2025-02-13 04:30:41 -05:00
Merge remote-tracking branch 'upstream/develop' into impl/mod-card-moderation-options
This commit is contained in:
commit
c930a4d14f
10
.env.ci
10
.env.ci
@ -9,11 +9,11 @@ LOG_DEPRECATIONS_CHANNEL=null
|
||||
LOG_LEVEL=debug
|
||||
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=mysql
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=testing
|
||||
DB_USERNAME=user
|
||||
DB_PASSWORD=password
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=33306
|
||||
DB_DATABASE=test
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=
|
||||
|
||||
SCOUT_DRIVER=collection
|
||||
FILESYSTEM_DISK=local
|
||||
|
@ -87,8 +87,8 @@ DB_HUB_PASSWORD=
|
||||
DB_HUB_CHARSET=utf8mb4
|
||||
DB_HUB_COLLATION=utf8mb4_0900_ai_ci
|
||||
|
||||
GITEA_DOMAIN=
|
||||
GITEA_TOKEN=
|
||||
# GitHub Token for fetching release version numbers.
|
||||
GITHUB_TOKEN=
|
||||
|
||||
# API key for Scribe documentation.
|
||||
SCRIBE_AUTH_KEY=
|
||||
|
@ -43,6 +43,9 @@ MAIL_MAILER=log
|
||||
MAIL_FROM_ADDRESS="no-reply@sp-tarkov.com"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
|
||||
# GitHub Token for fetching release version numbers.
|
||||
GITHUB_TOKEN=
|
||||
|
||||
# API key for Scribe documentation.
|
||||
SCRIBE_AUTH_KEY=
|
||||
|
||||
|
8
.github/CODE_OF_CONDUCT.md
vendored
Normal file
8
.github/CODE_OF_CONDUCT.md
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# Code of Conduct
|
||||
|
||||
The Forge development code of conduct is derived from the Ruby code of conduct. Any violations of the code of conduct may be reported to Refringe at me@refringe.com.
|
||||
|
||||
- Participants will be tolerant of opposing views.
|
||||
- Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.
|
||||
- When interpreting the words and actions of others, participants should always assume good intentions.
|
||||
- Behavior that can be reasonably considered harassment will not be tolerated.
|
38
.github/CONTRIBUTING.md
vendored
Normal file
38
.github/CONTRIBUTING.md
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
# Contributing
|
||||
|
||||
## Development Discussion
|
||||
|
||||
*__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.
|
||||
|
||||
## Pull Request Guidelines
|
||||
|
||||
- **Keep Them Small**
|
||||
If you're fixing a bug, try to keep the changes to the bug fix only. If you're adding a feature, try to keep the changes to the feature only. This will make it easier to review and merge your changes.
|
||||
- **Perform a Self-Review**
|
||||
Before submitting your changes, review your own code. This will help you catch any mistakes you may have made.
|
||||
- **Remove Noise**
|
||||
Remove any unnecessary changes to white space, code style formatting, or some text change that has no impact related to the intention of the PR.
|
||||
- **Create a Meaningful Title**
|
||||
When creating a PR, make sure the title is meaningful and describes the changes you've made.
|
||||
- **Write Detailed Commit Messages**
|
||||
Bring out your table manners, speak the Queen's English and be on your best behaviour.
|
||||
|
||||
## Style Guide
|
||||
|
||||
Forge follows the PSR-2 coding standard and the PSR-4 autoloading standard. We use an automated Laravel Pint action to enforce the coding standard, though it's suggested to run your code changes through Pint before contributing. This can be done by configuring your IDE to format with Pint on save, or manually by running the following command:
|
||||
|
||||
```
|
||||
./vendor/bin/sail pint
|
||||
```
|
||||
|
||||
### Tests
|
||||
|
||||
We have a number of tests that are run automatically when you submit a pull request. You can run these tests locally by running `php artisan test`. If you're adding a new feature or fixing a bug, please add tests to cover your changes so that we can ensure they don't break in the future. We use the [Pest PHP testing framework](https://pestphp.com).
|
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
github: [ refringe ]
|
||||
patreon: sptarkov
|
||||
ko_fi: refringe
|
35
.github/README.md
vendored
35
.github/README.md
vendored
@ -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://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.
|
||||
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.
|
||||
|
||||
### Available Services:
|
||||
|
||||
@ -77,36 +77,3 @@ sail npm run dev
|
||||
### More Information
|
||||
|
||||
For more information on Laravel development, please refer to the [official documentation](https://laravel.com/docs/11.x/).
|
||||
|
||||
## Development Discussion
|
||||
|
||||
*__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.
|
||||
|
||||
## Coding Style
|
||||
|
||||
Forge follows the PSR-2 coding standard and the PSR-4 autoloading standard. We use an automated Laravel Pint action to enforce the coding standard, though it's suggested to run your code changes through Pint before contributing. This can be done by configuring your IDE to format with Pint on save, or manually by running the following command:
|
||||
|
||||
```
|
||||
./vendor/bin/sail pint
|
||||
```
|
||||
|
||||
## Security Vulnerabilities
|
||||
|
||||
If you discover a security vulnerability within Forge, please email Refringe at me@refringe.com. All security vulnerabilities will be promptly addressed.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
The Forge development code of conduct is derived from the Ruby code of conduct. Any violations of the code of conduct may be reported to Refringe at me@refringe.com.
|
||||
|
||||
- Participants will be tolerant of opposing views.
|
||||
- Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.
|
||||
- When interpreting the words and actions of others, participants should always assume good intentions.
|
||||
- Behavior that can be reasonably considered harassment will not be tolerated.
|
||||
|
3
.github/SECURITY.md
vendored
Normal file
3
.github/SECURITY.md
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# Security Policy
|
||||
|
||||
If you discover a security vulnerability within Forge, please email Refringe at me@refringe.com. All security vulnerabilities will be promptly addressed.
|
86
.github/action/setup/action.yml
vendored
Normal file
86
.github/action/setup/action.yml
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
name: Setup
|
||||
description: Setup and Cache PHP, Composer, and NPM.
|
||||
|
||||
inputs:
|
||||
php-version:
|
||||
description: PHP version(s) to use.
|
||||
required: true
|
||||
php-extensions:
|
||||
description: PHP extensions to install.
|
||||
required: false
|
||||
default: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, gd, redis, igbinary, msgpack, memcached, gmp, :php-psr
|
||||
redis-options:
|
||||
description: Redis options to compile with.
|
||||
required: false
|
||||
default: --enable-redis --enable-redis-igbinary --enable-redis-msgpack --enable-redis-lzf --with-liblzf --enable-redis-zstd --with-libzstd --enable-redis-lz4 --with-liblz4
|
||||
redis-libs:
|
||||
description: Redis libraries to install.
|
||||
required: false
|
||||
default: liblz4-dev, liblzf-dev, libzstd-dev
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Get PHP Extension Cache Hash
|
||||
id: php-ext-cache-hash
|
||||
env:
|
||||
PHP_EXTENSIONS: ${{ inputs.php-extensions }}
|
||||
REDIS_OPTIONS: ${{ inputs.redis-options }}
|
||||
REDIS_LIBS: ${{ inputs.redis-libs }}
|
||||
run: |
|
||||
concat_values="${PHP_EXTENSIONS}${REDIS_OPTIONS}${REDIS_LIBS}"
|
||||
echo "hash=$(echo $concat_values | md5sum | awk '{print $1}')" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Setup Cache Environment
|
||||
id: php-ext-cache
|
||||
uses: shivammathur/cache-extensions@v1
|
||||
if: inputs.php-extensions != '' && inputs.redis-options != '' && inputs.redis-libs != ''
|
||||
with:
|
||||
php-version: ${{ inputs.php-version }}
|
||||
extensions: ${{ inputs.php-extensions }}
|
||||
key: ${{ runner.os }}-php-ext-${{ steps.php-ext-cache-hash.outputs.hash }}
|
||||
|
||||
- name: Cache PHP Extensions
|
||||
uses: actions/cache@v4
|
||||
if: inputs.php-extensions != '' && inputs.redis-options != '' && inputs.redis-libs != ''
|
||||
with:
|
||||
path: ${{ steps.php-ext-cache.outputs.dir }}
|
||||
key: ${{ steps.php-ext-cache.outputs.key }}
|
||||
restore-keys: ${{ steps.php-ext-cache.outputs.key }}
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ inputs.php-version }}
|
||||
extensions: ${{ inputs.php-extensions }}
|
||||
ini-values: error_reporting=E_ALL
|
||||
tools: composer:v2
|
||||
coverage: none
|
||||
env:
|
||||
REDIS_CONFIGURE_OPTS: ${{ inputs.redis-options }}
|
||||
REDIS_LIBS: ${{ inputs.redis-libs }}
|
||||
|
||||
- name: Get Composer Cache Directory
|
||||
id: composer-cache
|
||||
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Cache Composer Dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: ${{ runner.os }}-composer-
|
||||
|
||||
- name: Get NPM Cache Directory
|
||||
id: npm-cache
|
||||
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Cache NPM Dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.npm-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: ${{ runner.os }}-node-
|
32
.github/dependabot.yml
vendored
32
.github/dependabot.yml
vendored
@ -4,37 +4,45 @@ updates:
|
||||
- package-ecosystem: "composer"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
time: "15:00" # 10am EST
|
||||
open-pull-requests-limit: 10
|
||||
interval: "weekly"
|
||||
day: "monday"
|
||||
time: "10:00"
|
||||
timezone: "America/Toronto"
|
||||
open-pull-requests-limit: 5
|
||||
target-branch: "develop"
|
||||
versioning-strategy: increase-if-necessary
|
||||
labels:
|
||||
- "dependencies"
|
||||
assignees:
|
||||
reviewers:
|
||||
- "Refringe"
|
||||
|
||||
# GitHub Actions
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
time: "15:00" # 10am EST
|
||||
open-pull-requests-limit: 10
|
||||
interval: "weekly"
|
||||
day: "monday"
|
||||
time: "10:00"
|
||||
timezone: "America/Toronto"
|
||||
open-pull-requests-limit: 5
|
||||
target-branch: "develop"
|
||||
labels:
|
||||
- "dependencies"
|
||||
assignees:
|
||||
reviewers:
|
||||
- "Refringe"
|
||||
|
||||
# npm modules (JavaScript)
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
time: "15:00" # 10am EST
|
||||
open-pull-requests-limit: 10
|
||||
interval: "weekly"
|
||||
day: "monday"
|
||||
time: "10:00"
|
||||
timezone: "America/Toronto"
|
||||
open-pull-requests-limit: 5
|
||||
target-branch: "develop"
|
||||
versioning-strategy: increase-if-necessary
|
||||
labels:
|
||||
- "dependencies"
|
||||
assignees:
|
||||
reviewers:
|
||||
- "Refringe"
|
||||
|
20
.github/workflows/analyze.yml
vendored
Normal file
20
.github/workflows/analyze.yml
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
name: Analyze
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and analyze
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: SonarSource/sonarqube-scan-action@v4
|
||||
env:
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
|
21
.github/workflows/larastan.yaml
vendored
Normal file
21
.github/workflows/larastan.yaml
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
name: Larastan
|
||||
|
||||
on: [ push, pull_request ]
|
||||
|
||||
jobs:
|
||||
larastan:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/action/setup
|
||||
with:
|
||||
php-version: 8.3
|
||||
|
||||
- name: Install Composer Dependencies
|
||||
run: composer install -q --no-interaction --no-progress --prefer-dist --optimize-autoloader
|
||||
|
||||
- name: Execute Code Static Analysis with Larastan
|
||||
run: ./vendor/bin/phpstan analyse -c ./phpstan.neon --no-progress --error-format=github
|
33
.github/workflows/pint.yaml
vendored
Normal file
33
.github/workflows/pint.yaml
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
name: Pint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
pint:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/action/setup
|
||||
with:
|
||||
php-version: 8.3
|
||||
|
||||
- name: Install Composer Dependencies
|
||||
run: composer install -q --no-interaction --no-progress --prefer-dist --optimize-autoloader
|
||||
|
||||
- name: Run Pint Code Style Fixer
|
||||
run: ./vendor/bin/pint
|
||||
|
||||
- uses: stefanzweifel/git-auto-commit-action@v5
|
||||
with:
|
||||
commit_message: Pint PHP Style Fixes [no ci]
|
||||
commit_user_name: Pint Bot
|
||||
skip_fetch: true
|
||||
file_pattern: '*.php'
|
93
.github/workflows/quality.yaml
vendored
93
.github/workflows/quality.yaml
vendored
@ -1,93 +0,0 @@
|
||||
name: Quality
|
||||
|
||||
on: [ push, pull_request ]
|
||||
|
||||
jobs:
|
||||
security-checker:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: The PHP Security Checker
|
||||
uses: symfonycorp/security-checker-action@v5
|
||||
|
||||
larastan:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.3'
|
||||
extensions: mbstring, dom, fileinfo
|
||||
coverage: none
|
||||
|
||||
- name: Get Composer Cache Directory
|
||||
id: composer-cache
|
||||
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache composer dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: ${{ runner.os }}-composer-
|
||||
|
||||
- name: Install Composer Dependencies
|
||||
run: composer install --no-progress --prefer-dist --optimize-autoloader
|
||||
|
||||
- name: Prepare Laravel Environment
|
||||
run: |
|
||||
php -r "file_exists('.env') || copy('.env.ci', '.env');"
|
||||
php artisan key:generate
|
||||
php artisan optimize
|
||||
|
||||
- name: Execute Code Static Analysis with Larastan
|
||||
run: ./vendor/bin/phpstan analyse -c ./phpstan.neon --no-progress --error-format=github
|
||||
|
||||
pint-fixer:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.3'
|
||||
extensions: mbstring, dom, fileinfo
|
||||
coverage: none
|
||||
|
||||
- name: Get Composer Cache Directory
|
||||
id: composer-cache
|
||||
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache composer dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: ${{ runner.os }}-composer-
|
||||
|
||||
- name: Install Composer Dependencies
|
||||
run: composer install --no-progress --prefer-dist --optimize-autoloader
|
||||
|
||||
- name: Prepare Laravel Environment
|
||||
run: |
|
||||
php -r "file_exists('.env') || copy('.env.ci', '.env');"
|
||||
php artisan key:generate
|
||||
php artisan optimize
|
||||
|
||||
- name: Run Pint Code Style Fixer
|
||||
run: ./vendor/bin/pint
|
||||
|
||||
- uses: stefanzweifel/git-auto-commit-action@v5
|
||||
with:
|
||||
commit_message: Pint PHP Style Fixes [no ci]
|
||||
commit_user_name: Pint Bot
|
||||
skip_fetch: true
|
||||
file_pattern: '*.php'
|
38
.github/workflows/rector.yaml
vendored
Normal file
38
.github/workflows/rector.yaml
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
name: Rector
|
||||
|
||||
on: [ push, pull_request ]
|
||||
|
||||
jobs:
|
||||
rector:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/action/setup
|
||||
with:
|
||||
php-version: 8.3
|
||||
|
||||
- name: Install Composer Dependencies
|
||||
run: composer install -q --no-interaction --no-progress --prefer-dist --optimize-autoloader
|
||||
|
||||
- name: Rector Cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .rector/cache
|
||||
key: ${{ runner.os }}-rector-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-rector-
|
||||
|
||||
- run: mkdir -p .rector/cache
|
||||
|
||||
- name: Execute Rector Analysis
|
||||
run: php vendor/bin/rector process --dry-run --config=rector.php
|
||||
|
||||
# Disable automated changes until we have a better understanding of the impact
|
||||
# - uses: stefanzweifel/git-auto-commit-action@v5
|
||||
# with:
|
||||
# commit_message: Rector Fixes [no ci]
|
||||
# commit_user_name: Rector Bot
|
||||
# skip_fetch: true
|
||||
# file_pattern: '*.php'
|
13
.github/workflows/security.yaml
vendored
Normal file
13
.github/workflows/security.yaml
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
name: Security
|
||||
|
||||
on: [ push, pull_request ]
|
||||
|
||||
jobs:
|
||||
security:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: The PHP Security Checker
|
||||
uses: symfonycorp/security-checker-action@v5
|
46
.github/workflows/tests.yaml
vendored
46
.github/workflows/tests.yaml
vendored
@ -3,55 +3,31 @@ name: Tests
|
||||
on: [ push, pull_request ]
|
||||
|
||||
jobs:
|
||||
laravel-tests:
|
||||
tests:
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:8.3
|
||||
ports:
|
||||
- 33306:3306
|
||||
env:
|
||||
MYSQL_DATABASE: testing
|
||||
MYSQL_USER: user
|
||||
MYSQL_PASSWORD: password
|
||||
MYSQL_ROOT_PASSWORD: password
|
||||
options: --health-cmd="mysql -u user -D testing -ppassword -h mysql -e ''" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||
MYSQL_DATABASE: test
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: yes
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
- name: Setup
|
||||
uses: ./.github/action/setup
|
||||
with:
|
||||
php-version: '8.3'
|
||||
extensions: mbstring, dom, fileinfo
|
||||
coverage: none
|
||||
|
||||
- name: Get Composer Cache Directory
|
||||
id: composer-cache
|
||||
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache composer dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: ${{ runner.os }}-composer-
|
||||
php-version: 8.3
|
||||
|
||||
- name: Install Composer Dependencies
|
||||
run: composer install --no-progress --prefer-dist --optimize-autoloader
|
||||
run: composer install -q --no-interaction --no-progress --prefer-dist --optimize-autoloader
|
||||
|
||||
- name: Get NPM Cache Directory
|
||||
id: npm-cache
|
||||
run: echo "NPM_CACHE_DIR=$(npm config get cache)" >> $GITHUB_ENV
|
||||
|
||||
- name: Cache NPM Dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.NPM_CACHE_DIR }}
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: ${{ runner.os }}-node-
|
||||
|
||||
- name: Install npm dependencies
|
||||
- name: Install NPM dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build Front-end Assets
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -29,3 +29,4 @@ Homestead.yaml
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
.scribe
|
||||
.rector
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use App\Models\User;
|
||||
@ -26,7 +28,7 @@ class CreateNewUser implements CreatesNewUsers
|
||||
'terms' => Jetstream::hasTermsAndPrivacyPolicyFeature() ? ['accepted', 'required'] : '',
|
||||
])->validate();
|
||||
|
||||
return User::create([
|
||||
return User::query()->create([
|
||||
'name' => $input['name'],
|
||||
'email' => $input['email'],
|
||||
'password' => Hash::make($input['password']),
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
@ -8,6 +10,8 @@ trait PasswordValidationRules
|
||||
{
|
||||
/**
|
||||
* Get the validation rules used to validate passwords.
|
||||
*
|
||||
* @return array<int, Password|string>
|
||||
*/
|
||||
protected function passwordRules(): array
|
||||
{
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use App\Models\User;
|
||||
@ -13,6 +15,8 @@ class ResetUserPassword implements ResetsUserPasswords
|
||||
|
||||
/**
|
||||
* Validate and reset the user's forgotten password.
|
||||
*
|
||||
* @param array<string, string> $input
|
||||
*/
|
||||
public function reset(User $user, array $input): void
|
||||
{
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use App\Models\User;
|
||||
@ -13,6 +15,8 @@ class UpdateUserPassword implements UpdatesUserPasswords
|
||||
|
||||
/**
|
||||
* Validate and update the user's password.
|
||||
*
|
||||
* @param array<string, mixed> $input
|
||||
*/
|
||||
public function update(User $user, array $input): void
|
||||
{
|
||||
|
@ -1,9 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Laravel\Fortify\Contracts\UpdatesUserProfileInformation;
|
||||
@ -12,6 +13,8 @@ class UpdateUserProfileInformation implements UpdatesUserProfileInformation
|
||||
{
|
||||
/**
|
||||
* Validate and update the given user's profile information.
|
||||
*
|
||||
* @param array<string, mixed> $input
|
||||
*/
|
||||
public function update(User $user, array $input): void
|
||||
{
|
||||
@ -30,8 +33,7 @@ class UpdateUserProfileInformation implements UpdatesUserProfileInformation
|
||||
$user->updateCoverPhoto($input['cover']);
|
||||
}
|
||||
|
||||
if ($input['email'] !== $user->email &&
|
||||
$user instanceof MustVerifyEmail) {
|
||||
if ($input['email'] !== $user->email) {
|
||||
$this->updateVerifiedUser($user, $input);
|
||||
} else {
|
||||
$user->forceFill([
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Actions\Jetstream;
|
||||
|
||||
use App\Models\User;
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Jobs\Import\ImportHubDataJob;
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Jobs\ResolveDependenciesJob;
|
||||
|
@ -1,7 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Mod;
|
||||
use App\Models\User;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
|
||||
@ -15,8 +19,8 @@ class SearchSyncCommand extends Command
|
||||
{
|
||||
Artisan::call('scout:delete-all-indexes');
|
||||
Artisan::call('scout:sync-index-settings');
|
||||
Artisan::call('scout:import', ['model' => '\App\Models\Mod']);
|
||||
Artisan::call('scout:import', ['model' => '\App\Models\User']);
|
||||
Artisan::call('scout:import', ['model' => Mod::class]);
|
||||
Artisan::call('scout:import', ['model' => User::class]);
|
||||
|
||||
$this->info('The search synchronisation jobs have been added to the queue');
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Jobs\SptVersionModCountsJob;
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Jobs\UpdateModDownloadsJob;
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
@ -1,14 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\UserResource\Pages;
|
||||
use App\Filament\Resources\UserResource\Pages\CreateUser;
|
||||
use App\Filament\Resources\UserResource\Pages\EditUser;
|
||||
use App\Filament\Resources\UserResource\Pages\ListUsers;
|
||||
use App\Models\User;
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Actions\BulkActionGroup;
|
||||
use Filament\Tables\Actions\DeleteBulkAction;
|
||||
use Filament\Tables\Actions\EditAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Override;
|
||||
|
||||
class UserResource extends Resource
|
||||
{
|
||||
@ -16,46 +25,48 @@ class UserResource extends Resource
|
||||
|
||||
protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack';
|
||||
|
||||
#[Override]
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('name')
|
||||
TextInput::make('name')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
Forms\Components\TextInput::make('email')
|
||||
TextInput::make('email')
|
||||
->email()
|
||||
->required()
|
||||
->maxLength(255),
|
||||
Forms\Components\DateTimePicker::make('email_verified_at'),
|
||||
Forms\Components\TextInput::make('password')
|
||||
DateTimePicker::make('email_verified_at'),
|
||||
TextInput::make('password')
|
||||
->password()
|
||||
->required()
|
||||
->maxLength(255),
|
||||
Forms\Components\TextInput::make('user_role_id')
|
||||
TextInput::make('user_role_id')
|
||||
->numeric(),
|
||||
Forms\Components\TextInput::make('profile_photo_path')
|
||||
TextInput::make('profile_photo_path')
|
||||
->maxLength(2048),
|
||||
Forms\Components\DateTimePicker::make('created_at'),
|
||||
Forms\Components\DateTimePicker::make('updated_at'),
|
||||
DateTimePicker::make('created_at'),
|
||||
DateTimePicker::make('updated_at'),
|
||||
]);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('name')
|
||||
TextColumn::make('name')
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('email')
|
||||
TextColumn::make('email')
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('role.name')
|
||||
TextColumn::make('role.name')
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('email_verified_at')
|
||||
TextColumn::make('email_verified_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
@ -64,15 +75,16 @@ class UserResource extends Resource
|
||||
//
|
||||
])
|
||||
->actions([
|
||||
Tables\Actions\EditAction::make(),
|
||||
EditAction::make(),
|
||||
])
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkActionGroup::make([
|
||||
Tables\Actions\DeleteBulkAction::make(),
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
@ -80,12 +92,13 @@ class UserResource extends Resource
|
||||
];
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListUsers::route('/'),
|
||||
'create' => Pages\CreateUser::route('/create'),
|
||||
'edit' => Pages\EditUser::route('/{record}/edit'),
|
||||
'index' => ListUsers::route('/'),
|
||||
'create' => CreateUser::route('/create'),
|
||||
'edit' => EditUser::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\UserResource\Pages;
|
||||
|
||||
use App\Filament\Resources\UserResource;
|
||||
|
@ -1,9 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\UserResource\Pages;
|
||||
|
||||
use App\Filament\Resources\UserResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditUser extends EditRecord
|
||||
@ -13,7 +15,7 @@ class EditUser extends EditRecord
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make(),
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\UserResource\Pages;
|
||||
|
||||
use App\Filament\Resources\UserResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListUsers extends ListRecords
|
||||
@ -13,7 +15,7 @@ class ListUsers extends ListRecords
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
@ -34,16 +36,16 @@ class AuthController extends Controller
|
||||
#[Response(['message' => 'authenticated', 'data' => ['token' => 'YOUR_API_KEY'], 'status' => 200], status: 200, description: 'Authenticated successfully')]
|
||||
#[Response(['message' => 'invalid credentials', 'status' => 401], status: 401, description: 'Invalid credentials')]
|
||||
#[ResponseField('token', description: 'The newly created read-only API token to use for future authenticated requests.')]
|
||||
public function login(LoginUserRequest $request): JsonResponse
|
||||
public function login(LoginUserRequest $loginUserRequest): JsonResponse
|
||||
{
|
||||
$request->validated($request->all());
|
||||
$loginUserRequest->validated($loginUserRequest->all());
|
||||
|
||||
if (! Auth::attempt($request->only('email', 'password'))) {
|
||||
if (! Auth::attempt($loginUserRequest->only('email', 'password'))) {
|
||||
return $this->error(__('invalid credentials'), 401);
|
||||
}
|
||||
|
||||
$user = User::firstWhere('email', $request->email);
|
||||
$tokenName = $request->token_name ?? __('Dynamic API Token');
|
||||
$user = User::query()->firstWhere('email', $loginUserRequest->email);
|
||||
$tokenName = $loginUserRequest->token_name ?? __('Dynamic API Token');
|
||||
|
||||
return $this->success(__('authenticated'), [
|
||||
// Only allowing the 'read' scope to be dynamically created. Can revisit later when writes are possible.
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api\V0;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
@ -12,12 +14,14 @@ class ApiController extends Controller
|
||||
/**
|
||||
* Determine if the given relationship should be included in the request. If more than one relationship is provided,
|
||||
* only one needs to be present in the request for this method to return true.
|
||||
*
|
||||
* @param string|array<int, string> $relationships
|
||||
*/
|
||||
public static function shouldInclude(string|array $relationships): bool
|
||||
{
|
||||
try {
|
||||
$param = request()->get('include');
|
||||
} catch (NotFoundExceptionInterface|ContainerExceptionInterface $e) {
|
||||
} catch (NotFoundExceptionInterface|ContainerExceptionInterface) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api\V0;
|
||||
|
||||
use App\Http\Filters\V1\ModFilter;
|
||||
@ -33,9 +35,9 @@ class ModController extends ApiController
|
||||
#[QueryParam('filter[updated_at]', 'string', 'Filter by the `updated_at` attribute. Ranges are possible by separating the dates with a comma.', required: false, example: '2023-12-31,2024-12-31')]
|
||||
#[QueryParam('filter[published_at]', 'string', 'Filter by the `published_at` attribute. Ranges are possible by seperating the dates with a comma.', required: false, example: '2023-12-31,2024-12-31')]
|
||||
#[QueryParam('sort', 'string', 'Sort the results by a comma seperated list of attributes. The default sort direction is ASC, append the attribute name with a minus to sort DESC.', required: false, example: '-featured,name')]
|
||||
public function index(ModFilter $filters): AnonymousResourceCollection
|
||||
public function index(ModFilter $modFilter): AnonymousResourceCollection
|
||||
{
|
||||
return ModResource::collection(Mod::filter($filters)->paginate());
|
||||
return ModResource::collection(Mod::filter($modFilter)->paginate());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api\V0;
|
||||
|
||||
use App\Http\Filters\V1\UserFilter;
|
||||
@ -24,9 +26,9 @@ class UsersController extends ApiController
|
||||
#[QueryParam('filter[created_at]', 'string', 'Filter by the `created_at` attribute. Ranges are possible by separating the dates with a comma.', required: false, example: '2023-12-31,2024-12-31')]
|
||||
#[QueryParam('filter[updated_at]', 'string', 'Filter by the `updated_at` attribute. Ranges are possible by separating the dates with a comma.', required: false, example: '2023-12-31,2024-12-31')]
|
||||
#[QueryParam('sort', 'string', 'Sort the results by a comma seperated list of attributes. The default sort direction is ASC, append the attribute name with a minus to sort DESC.', required: false, example: 'created_at,-name')]
|
||||
public function index(UserFilter $filters): AnonymousResourceCollection
|
||||
public function index(UserFilter $userFilter): AnonymousResourceCollection
|
||||
{
|
||||
return UserResource::collection(User::filter($filters)->paginate());
|
||||
return UserResource::collection(User::filter($userFilter)->paginate());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\ModRequest;
|
||||
@ -19,11 +21,11 @@ class ModController extends Controller
|
||||
return view('mod.index');
|
||||
}
|
||||
|
||||
public function store(ModRequest $request): ModResource
|
||||
public function store(ModRequest $modRequest): ModResource
|
||||
{
|
||||
$this->authorize('create', Mod::class);
|
||||
|
||||
return new ModResource(Mod::create($request->validated()));
|
||||
return new ModResource(Mod::query()->create($modRequest->validated()));
|
||||
}
|
||||
|
||||
public function show(int $modId, string $slug): View
|
||||
@ -37,20 +39,18 @@ class ModController extends Controller
|
||||
'users',
|
||||
])->findOrFail($modId);
|
||||
|
||||
if ($mod->slug !== $slug) {
|
||||
abort(404);
|
||||
}
|
||||
abort_if($mod->slug !== $slug, 404);
|
||||
|
||||
$this->authorize('view', $mod);
|
||||
|
||||
return view('mod.show', compact(['mod']));
|
||||
return view('mod.show', ['mod' => $mod]);
|
||||
}
|
||||
|
||||
public function update(ModRequest $request, Mod $mod): ModResource
|
||||
public function update(ModRequest $modRequest, Mod $mod): ModResource
|
||||
{
|
||||
$this->authorize('update', $mod);
|
||||
|
||||
$mod->update($request->validated());
|
||||
$mod->update($modRequest->validated());
|
||||
|
||||
return new ModResource($mod);
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\ModVersion;
|
||||
@ -18,17 +20,13 @@ class ModVersionController extends Controller
|
||||
->whereVersion($version)
|
||||
->firstOrFail();
|
||||
|
||||
if ($modVersion->mod->slug !== $slug) {
|
||||
abort(404);
|
||||
}
|
||||
abort_if($modVersion->mod->slug !== $slug, 404);
|
||||
|
||||
$this->authorize('view', $modVersion);
|
||||
|
||||
// Rate limit the downloads.
|
||||
$rateKey = 'mod-download:'.($request->user()?->id ?: $request->ip());
|
||||
if (RateLimiter::tooManyAttempts($rateKey, maxAttempts: 5)) { // Max attempts is per minute.
|
||||
abort(429);
|
||||
}
|
||||
abort_if(RateLimiter::tooManyAttempts($rateKey, maxAttempts: 5), 429);
|
||||
|
||||
// Increment downloads counts in the background.
|
||||
defer(fn () => $modVersion->incrementDownloads());
|
||||
|
@ -1,9 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\OAuthConnection;
|
||||
use App\Models\User;
|
||||
use Exception;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
@ -18,6 +21,8 @@ class SocialiteController extends Controller
|
||||
{
|
||||
/**
|
||||
* The providers that are supported.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected array $providers = ['discord'];
|
||||
|
||||
@ -50,7 +55,7 @@ class SocialiteController extends Controller
|
||||
|
||||
try {
|
||||
$providerUser = Socialite::driver($provider)->user();
|
||||
} catch (\Exception $e) {
|
||||
} catch (Exception) {
|
||||
return redirect()->route('login')->withErrors('Unable to login using '.$provider.'. Please try again.');
|
||||
}
|
||||
|
||||
@ -67,7 +72,7 @@ class SocialiteController extends Controller
|
||||
->whereProviderId($providerUser->getId())
|
||||
->first();
|
||||
|
||||
if ($oauthConnection) {
|
||||
if ($oauthConnection !== null) {
|
||||
$oauthConnection->update([
|
||||
'token' => $providerUser->token ?? '',
|
||||
'refresh_token' => $providerUser->refreshToken ?? '',
|
||||
@ -86,6 +91,7 @@ class SocialiteController extends Controller
|
||||
while (User::whereName($username.$random)->exists()) {
|
||||
$random = '-'.Str::random(5);
|
||||
}
|
||||
|
||||
$username .= $random;
|
||||
|
||||
// The user has not connected their account with this OAuth provider before, so a new connection needs to be
|
||||
@ -94,12 +100,12 @@ class SocialiteController extends Controller
|
||||
|
||||
return DB::transaction(function () use ($providerUser, $provider, $username) {
|
||||
|
||||
$user = User::firstOrCreate(['email' => $providerUser->getEmail()], [
|
||||
$user = User::query()->firstOrCreate(['email' => $providerUser->getEmail()], [
|
||||
'name' => $username,
|
||||
'password' => null,
|
||||
]);
|
||||
|
||||
$connection = $user->oAuthConnections()->create([
|
||||
$oAuthConnection = $user->oAuthConnections()->create([
|
||||
'provider' => $provider,
|
||||
'provider_id' => $providerUser->getId(),
|
||||
'token' => $providerUser->token ?? '',
|
||||
@ -110,7 +116,7 @@ class SocialiteController extends Controller
|
||||
'avatar' => $providerUser->getAvatar() ?? '',
|
||||
]);
|
||||
|
||||
$this->updateAvatar($user, $connection->avatar);
|
||||
$this->updateAvatar($user, $oAuthConnection->avatar);
|
||||
|
||||
return $user;
|
||||
});
|
||||
@ -125,10 +131,11 @@ class SocialiteController extends Controller
|
||||
};
|
||||
|
||||
$curl = curl_init();
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($curl, CURLOPT_URL, $avatarUrl);
|
||||
$image = curl_exec($curl);
|
||||
curl_close($curl);
|
||||
|
||||
if ($image === false) {
|
||||
Log::error('There was an error attempting to download the image. cURL error: '.curl_error($curl));
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\User;
|
||||
@ -27,14 +29,10 @@ class UserController extends Controller
|
||||
->paginate(10)
|
||||
->fragment('mods');
|
||||
|
||||
if ($user->slug() !== $username) {
|
||||
abort(404);
|
||||
}
|
||||
abort_if($user->slug() !== $username, 404);
|
||||
|
||||
if ($request->user()?->cannot('view', $user)) {
|
||||
abort(403);
|
||||
}
|
||||
abort_if($request->user()?->cannot('view', $user), 403);
|
||||
|
||||
return view('user.show', compact('user', 'mods'));
|
||||
return view('user.show', ['user' => $user, 'mods' => $mods]);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Filters;
|
||||
|
||||
use App\Models\Mod;
|
||||
@ -10,31 +12,35 @@ class ModFilter
|
||||
{
|
||||
/**
|
||||
* The query builder instance for the mod model.
|
||||
*
|
||||
* @var Builder<Mod>
|
||||
*/
|
||||
protected Builder $builder;
|
||||
|
||||
/**
|
||||
* The filters to apply.
|
||||
*/
|
||||
protected array $filters;
|
||||
|
||||
/**
|
||||
* Create a new ModFilter instance.
|
||||
*/
|
||||
public function __construct(array $filters)
|
||||
{
|
||||
$this->filters = $filters;
|
||||
public function __construct(
|
||||
/**
|
||||
* The filters to apply to the query.
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
protected array $filters
|
||||
) {
|
||||
$this->builder = $this->baseQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* The base query for the mod listing.
|
||||
*
|
||||
* @return Builder<Mod>
|
||||
*/
|
||||
private function baseQuery(): Builder
|
||||
{
|
||||
return Mod::query()
|
||||
->select('mods.*')
|
||||
->whereExists(function ($query) {
|
||||
->whereExists(function ($query): void {
|
||||
$query->select(DB::raw(1))
|
||||
->from('mod_versions')
|
||||
->join('mod_version_spt_version', 'mod_versions.id', '=', 'mod_version_spt_version.mod_version_id')
|
||||
@ -51,14 +57,18 @@ class ModFilter
|
||||
|
||||
/**
|
||||
* Filter the results by the given search term.
|
||||
*
|
||||
* @return Builder<Mod>
|
||||
*/
|
||||
private function query(string $term): Builder
|
||||
{
|
||||
return $this->builder->whereLike('mods.name', "%{$term}%");
|
||||
return $this->builder->whereLike('mods.name', sprintf('%%%s%%', $term));
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the filters to the query.
|
||||
*
|
||||
* @return Builder<Mod>
|
||||
*/
|
||||
public function apply(): Builder
|
||||
{
|
||||
@ -73,6 +83,8 @@ class ModFilter
|
||||
|
||||
/**
|
||||
* Order the query by the given type.
|
||||
*
|
||||
* @return Builder<Mod>
|
||||
*/
|
||||
private function order(string $type): Builder
|
||||
{
|
||||
@ -85,6 +97,8 @@ class ModFilter
|
||||
|
||||
/**
|
||||
* Filter the results by the featured status.
|
||||
*
|
||||
* @return Builder<Mod>
|
||||
*/
|
||||
private function featured(string $option): Builder
|
||||
{
|
||||
@ -97,10 +111,13 @@ class ModFilter
|
||||
|
||||
/**
|
||||
* Filter the results to specific SPT versions.
|
||||
*
|
||||
* @param array<int, string> $versions
|
||||
* @return Builder<Mod>
|
||||
*/
|
||||
private function sptVersions(array $versions): Builder
|
||||
{
|
||||
return $this->builder->whereExists(function ($query) use ($versions) {
|
||||
return $this->builder->whereExists(function ($query) use ($versions): void {
|
||||
$query->select(DB::raw(1))
|
||||
->from('mod_versions')
|
||||
->join('mod_version_spt_version', 'mod_versions.id', '=', 'mod_version_spt_version.mod_version_id')
|
||||
|
@ -1,13 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Filters\V1;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ModFilter extends QueryFilter
|
||||
{
|
||||
/**
|
||||
* The sortable fields.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected array $sortable = [
|
||||
'name',
|
||||
@ -24,6 +29,8 @@ class ModFilter extends QueryFilter
|
||||
|
||||
/**
|
||||
* Filter by ID.
|
||||
*
|
||||
* @return Builder<Model>
|
||||
*/
|
||||
public function id(string $value): Builder
|
||||
{
|
||||
@ -32,6 +39,8 @@ class ModFilter extends QueryFilter
|
||||
|
||||
/**
|
||||
* Filter by hub ID.
|
||||
*
|
||||
* @return Builder<Model>
|
||||
*/
|
||||
public function hub_id(string $value): Builder
|
||||
{
|
||||
@ -40,6 +49,8 @@ class ModFilter extends QueryFilter
|
||||
|
||||
/**
|
||||
* Filter by name.
|
||||
*
|
||||
* @return Builder<Model>
|
||||
*/
|
||||
public function name(string $value): Builder
|
||||
{
|
||||
@ -48,6 +59,8 @@ class ModFilter extends QueryFilter
|
||||
|
||||
/**
|
||||
* Filter by slug.
|
||||
*
|
||||
* @return Builder<Model>
|
||||
*/
|
||||
public function slug(string $value): Builder
|
||||
{
|
||||
@ -56,6 +69,8 @@ class ModFilter extends QueryFilter
|
||||
|
||||
/**
|
||||
* Filter by teaser.
|
||||
*
|
||||
* @return Builder<Model>
|
||||
*/
|
||||
public function teaser(string $value): Builder
|
||||
{
|
||||
@ -64,6 +79,8 @@ class ModFilter extends QueryFilter
|
||||
|
||||
/**
|
||||
* Filter by source code link.
|
||||
*
|
||||
* @return Builder<Model>
|
||||
*/
|
||||
public function source_code_link(string $value): Builder
|
||||
{
|
||||
@ -72,6 +89,8 @@ class ModFilter extends QueryFilter
|
||||
|
||||
/**
|
||||
* Filter by created at date.
|
||||
*
|
||||
* @return Builder<Model>
|
||||
*/
|
||||
public function created_at(string $value): Builder
|
||||
{
|
||||
@ -80,6 +99,8 @@ class ModFilter extends QueryFilter
|
||||
|
||||
/**
|
||||
* Filter by updated at date.
|
||||
*
|
||||
* @return Builder<Model>
|
||||
*/
|
||||
public function updated_at(string $value): Builder
|
||||
{
|
||||
@ -88,6 +109,8 @@ class ModFilter extends QueryFilter
|
||||
|
||||
/**
|
||||
* Filter by published at date.
|
||||
*
|
||||
* @return Builder<Model>
|
||||
*/
|
||||
public function published_at(string $value): Builder
|
||||
{
|
||||
@ -96,6 +119,8 @@ class ModFilter extends QueryFilter
|
||||
|
||||
/**
|
||||
* Filter by featured.
|
||||
*
|
||||
* @return Builder<Model>
|
||||
*/
|
||||
public function featured(string $value): Builder
|
||||
{
|
||||
@ -104,6 +129,8 @@ class ModFilter extends QueryFilter
|
||||
|
||||
/**
|
||||
* Filter by contains ads.
|
||||
*
|
||||
* @return Builder<Model>
|
||||
*/
|
||||
public function contains_ads(string $value): Builder
|
||||
{
|
||||
@ -112,6 +139,8 @@ class ModFilter extends QueryFilter
|
||||
|
||||
/**
|
||||
* Filter by contains AI content.
|
||||
*
|
||||
* @return Builder<Model>
|
||||
*/
|
||||
public function contains_ai_content(string $value): Builder
|
||||
{
|
||||
|
@ -1,9 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Filters\V1;
|
||||
|
||||
use App\Traits\V1\FilterMethods;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
abstract class QueryFilter
|
||||
@ -15,29 +18,33 @@ abstract class QueryFilter
|
||||
|
||||
/**
|
||||
* The query builder instance.
|
||||
*
|
||||
* @var Builder<Model>
|
||||
*/
|
||||
protected Builder $builder;
|
||||
|
||||
/**
|
||||
* The request instance.
|
||||
*/
|
||||
protected Request $request;
|
||||
|
||||
/**
|
||||
* The sortable fields.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected array $sortable = [];
|
||||
|
||||
/**
|
||||
* Create a new QueryFilter instance.
|
||||
*/
|
||||
public function __construct(Request $request)
|
||||
{
|
||||
$this->request = $request;
|
||||
}
|
||||
public function __construct(
|
||||
/**
|
||||
* The request instance.
|
||||
*/
|
||||
protected Request $request
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Iterate over each of the filter options and call the appropriate method if it exists.
|
||||
*
|
||||
* @param array<string, mixed> $filters
|
||||
* @return Builder<Model>
|
||||
*/
|
||||
public function filter(array $filters): Builder
|
||||
{
|
||||
@ -52,6 +59,9 @@ abstract class QueryFilter
|
||||
|
||||
/**
|
||||
* Iterate over all request data and call the appropriate method if it exists.
|
||||
*
|
||||
* @param Builder<Model> $builder
|
||||
* @return Builder<Model>
|
||||
*/
|
||||
public function apply(Builder $builder): Builder
|
||||
{
|
||||
|
@ -1,13 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Filters\V1;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class UserFilter extends QueryFilter
|
||||
{
|
||||
/**
|
||||
* The sortable fields.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected array $sortable = [
|
||||
'name',
|
||||
@ -17,6 +22,8 @@ class UserFilter extends QueryFilter
|
||||
|
||||
/**
|
||||
* Filter by ID.
|
||||
*
|
||||
* @return Builder<Model>
|
||||
*/
|
||||
public function id(string $value): Builder
|
||||
{
|
||||
@ -25,6 +32,8 @@ class UserFilter extends QueryFilter
|
||||
|
||||
/**
|
||||
* Filter by name.
|
||||
*
|
||||
* @return Builder<Model>
|
||||
*/
|
||||
public function name(string $value): Builder
|
||||
{
|
||||
@ -33,6 +42,8 @@ class UserFilter extends QueryFilter
|
||||
|
||||
/**
|
||||
* Filter by created at date.
|
||||
*
|
||||
* @return Builder<Model>
|
||||
*/
|
||||
public function created_at(string $value): Builder
|
||||
{
|
||||
@ -41,6 +52,8 @@ class UserFilter extends QueryFilter
|
||||
|
||||
/**
|
||||
* Filter by updated at date.
|
||||
*
|
||||
* @return Builder<Model>
|
||||
*/
|
||||
public function updated_at(string $value): Builder
|
||||
{
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Api;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
@ -16,6 +18,8 @@ class LoginUserRequest extends FormRequest
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, array<string>>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Api\V0;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
@ -16,6 +18,8 @@ class StoreModRequest extends FormRequest
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, array<string>>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Api\V0;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
@ -16,6 +18,8 @@ class StoreUserRequest extends FormRequest
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, array<string>>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Api\V0;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
@ -16,6 +18,8 @@ class UpdateModRequest extends FormRequest
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, array<string>>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Api\V0;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
@ -16,6 +18,8 @@ class UpdateUserRequest extends FormRequest
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, array<string>>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
@ -8,6 +10,8 @@ class ModRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, array<string>>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
|
@ -1,17 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources\Api\V0;
|
||||
|
||||
use App\Models\License;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Override;
|
||||
|
||||
/** @mixin License */
|
||||
class LicenseResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
#[Override]
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
|
@ -1,18 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources\Api\V0;
|
||||
|
||||
use App\Http\Controllers\Api\V0\ApiController;
|
||||
use App\Models\Mod;
|
||||
use App\Models\ModVersion;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Override;
|
||||
|
||||
/** @mixin Mod */
|
||||
class ModResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
#[Override]
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
$this->load(['users', 'versions', 'license']);
|
||||
@ -39,7 +47,7 @@ class ModResource extends JsonResource
|
||||
'published_at' => $this->published_at,
|
||||
],
|
||||
'relationships' => [
|
||||
'users' => $this->users->map(fn ($user) => [
|
||||
'users' => $this->users->map(fn (User $user): array => [
|
||||
'data' => [
|
||||
'type' => 'user',
|
||||
'id' => $user->id,
|
||||
@ -48,7 +56,7 @@ class ModResource extends JsonResource
|
||||
'self' => $user->profileUrl(),
|
||||
],
|
||||
])->toArray(),
|
||||
'versions' => $this->versions->map(fn ($version) => [
|
||||
'versions' => $this->versions->map(fn (ModVersion $version): array => [
|
||||
'data' => [
|
||||
'type' => 'version',
|
||||
'id' => $version->id,
|
||||
@ -70,11 +78,11 @@ class ModResource extends JsonResource
|
||||
'includes' => $this->when(
|
||||
ApiController::shouldInclude(['users', 'license', 'versions']),
|
||||
fn () => collect([
|
||||
'users' => $this->users->map(fn ($user) => new UserResource($user)),
|
||||
'users' => $this->users->map(fn ($user): UserResource => new UserResource($user)),
|
||||
'license' => new LicenseResource($this->license),
|
||||
'versions' => $this->versions->map(fn ($version) => new ModVersionResource($version)),
|
||||
'versions' => $this->versions->map(fn ($version): ModVersionResource => new ModVersionResource($version)),
|
||||
])
|
||||
->filter(fn ($value, $key) => ApiController::shouldInclude($key))
|
||||
->filter(fn ($value, $key): bool => ApiController::shouldInclude($key))
|
||||
->flatten(1)
|
||||
->values()
|
||||
),
|
||||
|
@ -1,17 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources\Api\V0;
|
||||
|
||||
use App\Models\ModVersion;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Override;
|
||||
|
||||
/** @mixin ModVersion */
|
||||
class ModVersionResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
#[Override]
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
@ -23,10 +29,10 @@ class ModVersionResource extends JsonResource
|
||||
'version' => $this->version,
|
||||
|
||||
// TODO: This should only be visible on the mod version show route(?) which doesn't exist.
|
||||
//'description' => $this->when(
|
||||
// 'description' => $this->when(
|
||||
// $request->routeIs('api.v0.modversion.show'),
|
||||
// $this->description
|
||||
//),
|
||||
// ),
|
||||
|
||||
'link' => $this->downloadUrl(absolute: true),
|
||||
'virus_total_link' => $this->virus_total_link,
|
||||
|
@ -1,18 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources\Api\V0;
|
||||
|
||||
use App\Http\Controllers\Api\V0\ApiController;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Override;
|
||||
|
||||
/** @mixin User */
|
||||
class UserResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
#[Override]
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
$this->load('role');
|
||||
|
@ -1,17 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources\Api\V0;
|
||||
|
||||
use App\Models\UserRole;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Override;
|
||||
|
||||
/** @mixin UserRole */
|
||||
class UserRoleResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
#[Override]
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
|
@ -1,17 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use App\Models\License;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Override;
|
||||
|
||||
/** @mixin License */
|
||||
class LicenseResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
#[Override]
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
|
@ -1,17 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use App\Models\Mod;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Override;
|
||||
|
||||
/** @mixin Mod */
|
||||
class ModResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
#[Override]
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
|
@ -1,17 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use App\Models\ModVersion;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Override;
|
||||
|
||||
/** @mixin ModVersion */
|
||||
class ModVersionResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
#[Override]
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
|
@ -1,17 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use App\Models\SptVersion;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Override;
|
||||
|
||||
/** @mixin SptVersion */
|
||||
class SptVersionResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
#[Override]
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Jobs\Import\DataTransferObjects;
|
||||
|
||||
class HubUser
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Jobs\Import;
|
||||
|
||||
use App\Exceptions\InvalidVersionNumberException;
|
||||
@ -34,7 +36,10 @@ use Throwable;
|
||||
|
||||
class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
use Dispatchable;
|
||||
use InteractsWithQueue;
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
@ -86,7 +91,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
DB::connection('mysql_hub')
|
||||
->table('wcf1_user_avatar')
|
||||
->orderBy('avatarID')
|
||||
->chunk(200, function ($avatars) {
|
||||
->chunk(200, function ($avatars): void {
|
||||
$insertData = [];
|
||||
foreach ($avatars as $avatar) {
|
||||
$insertData[] = [
|
||||
@ -97,7 +102,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
];
|
||||
}
|
||||
|
||||
if ($insertData) {
|
||||
if ($insertData !== []) {
|
||||
DB::table('temp_user_avatar')->insert($insertData);
|
||||
}
|
||||
});
|
||||
@ -117,7 +122,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
DB::connection('mysql_hub')
|
||||
->table('wcf1_user_option_value')
|
||||
->orderBy('userID')
|
||||
->chunk(200, function ($options) {
|
||||
->chunk(200, function ($options): void {
|
||||
$insertData = [];
|
||||
foreach ($options as $option) {
|
||||
$insertData[] = [
|
||||
@ -126,7 +131,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
];
|
||||
}
|
||||
|
||||
if ($insertData) {
|
||||
if ($insertData !== []) {
|
||||
DB::table('temp_user_options_values')->insert($insertData);
|
||||
}
|
||||
});
|
||||
@ -146,7 +151,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
DB::connection('mysql_hub')
|
||||
->table('filebase1_file_author')
|
||||
->orderBy('fileID')
|
||||
->chunk(200, function ($relationships) {
|
||||
->chunk(200, function ($relationships): void {
|
||||
$insertData = [];
|
||||
foreach ($relationships as $relationship) {
|
||||
$insertData[] = [
|
||||
@ -155,7 +160,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
];
|
||||
}
|
||||
|
||||
if ($insertData) {
|
||||
if ($insertData !== []) {
|
||||
DB::table('temp_file_author')->insert($insertData);
|
||||
}
|
||||
});
|
||||
@ -176,7 +181,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
DB::connection('mysql_hub')
|
||||
->table('filebase1_file_option_value')
|
||||
->orderBy('fileID')
|
||||
->chunk(200, function ($options) {
|
||||
->chunk(200, function ($options): void {
|
||||
$insertData = [];
|
||||
foreach ($options as $option) {
|
||||
$insertData[] = [
|
||||
@ -186,7 +191,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
];
|
||||
}
|
||||
|
||||
if ($insertData) {
|
||||
if ($insertData !== []) {
|
||||
DB::table('temp_file_option_values')->insert($insertData);
|
||||
}
|
||||
});
|
||||
@ -208,7 +213,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
DB::connection('mysql_hub')
|
||||
->table('filebase1_file_content')
|
||||
->orderBy('fileID')
|
||||
->chunk(200, function ($contents) {
|
||||
->chunk(200, function ($contents): void {
|
||||
$insertData = [];
|
||||
foreach ($contents as $content) {
|
||||
$insertData[] = [
|
||||
@ -219,7 +224,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
];
|
||||
}
|
||||
|
||||
if ($insertData) {
|
||||
if ($insertData !== []) {
|
||||
DB::table('temp_file_content')->insert($insertData);
|
||||
}
|
||||
});
|
||||
@ -240,7 +245,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
->table('wcf1_label_object')
|
||||
->where('objectTypeID', 387)
|
||||
->orderBy('labelID')
|
||||
->chunk(200, function ($options) {
|
||||
->chunk(200, function ($options): void {
|
||||
$insertData = [];
|
||||
foreach ($options as $option) {
|
||||
$insertData[] = [
|
||||
@ -249,7 +254,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
];
|
||||
}
|
||||
|
||||
if ($insertData) {
|
||||
if ($insertData !== []) {
|
||||
DB::table('temp_file_version_labels')->insert($insertData);
|
||||
}
|
||||
});
|
||||
@ -269,7 +274,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
DB::connection('mysql_hub')
|
||||
->table('filebase1_file_version_content')
|
||||
->orderBy('versionID')
|
||||
->chunk(200, function ($options) {
|
||||
->chunk(200, function ($options): void {
|
||||
$insertData = [];
|
||||
foreach ($options as $option) {
|
||||
$insertData[] = [
|
||||
@ -278,7 +283,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
];
|
||||
}
|
||||
|
||||
if ($insertData) {
|
||||
if ($insertData !== []) {
|
||||
DB::table('temp_file_version_content')->insert($insertData);
|
||||
}
|
||||
});
|
||||
@ -297,7 +302,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
->table('wcf1_label')
|
||||
->where('groupID', 1)
|
||||
->orderBy('labelID')
|
||||
->chunk(100, function (Collection $versions) {
|
||||
->chunk(100, function (Collection $versions): void {
|
||||
$insertData = [];
|
||||
foreach ($versions as $version) {
|
||||
$insertData[] = [
|
||||
@ -307,7 +312,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
];
|
||||
}
|
||||
|
||||
if ($insertData) {
|
||||
if ($insertData !== []) {
|
||||
DB::table('temp_spt_version_tags')->insert($insertData);
|
||||
}
|
||||
});
|
||||
@ -320,7 +325,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
{
|
||||
// Initialize a cURL handler for downloading mod thumbnails.
|
||||
$curl = curl_init();
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
|
||||
|
||||
DB::connection('mysql_hub')
|
||||
@ -340,9 +345,10 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
'r.rankTitle',
|
||||
)
|
||||
->leftJoin('wcf1_user_rank as r', 'u.rankID', '=', 'r.rankID')
|
||||
->chunkById(250, function (Collection $users) use ($curl) {
|
||||
$userData = $bannedUsers = $userRanks = [];
|
||||
|
||||
->chunkById(250, function (Collection $users) use ($curl): void {
|
||||
$userData = [];
|
||||
$bannedUsers = [];
|
||||
$userRanks = [];
|
||||
foreach ($users as $user) {
|
||||
$hubUser = new HubUser(
|
||||
$user->userID,
|
||||
@ -383,17 +389,19 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
|
||||
/**
|
||||
* 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
|
||||
protected function collectUserData(CurlHandle $curlHandle, HubUser $hubUser): array
|
||||
{
|
||||
return [
|
||||
'hub_id' => (int) $hubUser->userID,
|
||||
'hub_id' => $hubUser->userID,
|
||||
'name' => $hubUser->username,
|
||||
'email' => Str::lower($hubUser->email),
|
||||
'password' => $this->cleanPasswordHash($hubUser->password),
|
||||
'about' => $this->fetchUserAbout($hubUser->userID),
|
||||
'profile_photo_path' => $this->fetchUserAvatar($curl, $hubUser),
|
||||
'cover_photo_path' => $this->fetchUserCoverPhoto($curl, $hubUser),
|
||||
'profile_photo_path' => $this->fetchUserAvatar($curlHandle, $hubUser),
|
||||
'cover_photo_path' => $this->fetchUserCoverPhoto($curlHandle, $hubUser),
|
||||
'created_at' => $this->cleanRegistrationDate($hubUser->registrationDate),
|
||||
'updated_at' => now('UTC')->toDateTimeString(),
|
||||
];
|
||||
@ -432,16 +440,16 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
{
|
||||
// Alright, hear me out... Shut up.
|
||||
|
||||
$converter = new HtmlConverter;
|
||||
$htmlConverter = new HtmlConverter;
|
||||
$clean = Purify::clean($dirty);
|
||||
|
||||
return $converter->convert($clean);
|
||||
return $htmlConverter->convert($clean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the user avatar from the Hub and store it anew.
|
||||
*/
|
||||
protected function fetchUserAvatar(CurlHandle $curl, HubUser $hubUser): string
|
||||
protected function fetchUserAvatar(CurlHandle $curlHandle, HubUser $hubUser): string
|
||||
{
|
||||
// Fetch the user's avatar data from the temporary table.
|
||||
$avatar = DB::table('temp_user_avatar')->where('userID', $hubUser->userID)->first();
|
||||
@ -450,18 +458,18 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
return '';
|
||||
}
|
||||
|
||||
$hashShort = substr($avatar->fileHash, 0, 2);
|
||||
$hashShort = substr((string) $avatar->fileHash, 0, 2);
|
||||
$fileName = $avatar->fileHash.'.'.$avatar->avatarExtension;
|
||||
$hubUrl = 'https://hub.sp-tarkov.com/images/avatars/'.$hashShort.'/'.$avatar->avatarID.'-'.$fileName;
|
||||
$relativePath = User::profilePhotoStoragePath().'/'.$fileName;
|
||||
|
||||
return $this->fetchAndStoreImage($curl, $hubUrl, $relativePath);
|
||||
return $this->fetchAndStoreImage($curlHandle, $hubUrl, $relativePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch and store an image from the Hub.
|
||||
*/
|
||||
protected function fetchAndStoreImage(CurlHandle $curl, string $hubUrl, string $relativePath): string
|
||||
protected function fetchAndStoreImage(CurlHandle $curlHandle, string $hubUrl, string $relativePath): string
|
||||
{
|
||||
// Determine the disk to use based on the environment.
|
||||
$disk = match (config('app.env')) {
|
||||
@ -475,11 +483,11 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
}
|
||||
|
||||
// Download the image using the cURL handler.
|
||||
curl_setopt($curl, CURLOPT_URL, $hubUrl);
|
||||
$image = curl_exec($curl);
|
||||
curl_setopt($curlHandle, CURLOPT_URL, $hubUrl);
|
||||
$image = curl_exec($curlHandle);
|
||||
|
||||
if ($image === false) {
|
||||
Log::error('There was an error attempting to download the image. cURL error: '.curl_error($curl));
|
||||
Log::error('There was an error attempting to download the image. cURL error: '.curl_error($curlHandle));
|
||||
|
||||
return '';
|
||||
}
|
||||
@ -493,9 +501,9 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
/**
|
||||
* Fetch the user avatar from the Hub and store it anew.
|
||||
*/
|
||||
protected function fetchUserCoverPhoto(CurlHandle $curl, HubUser $hubUser): string
|
||||
protected function fetchUserCoverPhoto(CurlHandle $curlHandle, HubUser $hubUser): string
|
||||
{
|
||||
if (empty($hubUser->coverPhotoHash) || empty($hubUser->coverPhotoExtension)) {
|
||||
if ($hubUser->coverPhotoHash === null || $hubUser->coverPhotoHash === '' || $hubUser->coverPhotoExtension === null || $hubUser->coverPhotoExtension === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
@ -504,7 +512,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
$hubUrl = 'https://hub.sp-tarkov.com/images/coverPhotos/'.$hashShort.'/'.$hubUser->userID.'-'.$fileName;
|
||||
$relativePath = 'user-covers/'.$fileName;
|
||||
|
||||
return $this->fetchAndStoreImage($curl, $hubUrl, $relativePath);
|
||||
return $this->fetchAndStoreImage($curlHandle, $hubUrl, $relativePath);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -524,12 +532,14 @@ 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(HubUser $hubUser): ?array
|
||||
{
|
||||
if ($hubUser->banned) {
|
||||
return [
|
||||
'hub_id' => (int) $hubUser->userID,
|
||||
'hub_id' => $hubUser->userID,
|
||||
'comment' => $hubUser->banReason ?? '',
|
||||
'expired_at' => $this->cleanUnbannedAtDate($hubUser->banExpires),
|
||||
];
|
||||
@ -572,7 +582,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
}
|
||||
|
||||
return $date->toDateTimeString();
|
||||
} catch (\Exception $e) {
|
||||
} catch (Exception) {
|
||||
// If the date is not valid, return null
|
||||
return null;
|
||||
}
|
||||
@ -580,12 +590,14 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
|
||||
/**
|
||||
* 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 ($hubUser->rankID && $hubUser->rankTitle) {
|
||||
return [
|
||||
'hub_id' => (int) $hubUser->userID,
|
||||
'hub_id' => $hubUser->userID,
|
||||
'title' => $hubUser->rankTitle,
|
||||
];
|
||||
}
|
||||
@ -595,10 +607,12 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
|
||||
/**
|
||||
* Insert or update the users in the local database.
|
||||
*
|
||||
* @param array<array<string, mixed>> $usersData
|
||||
*/
|
||||
protected function upsertUsers(array $usersData): void
|
||||
{
|
||||
if (! empty($usersData)) {
|
||||
if ($usersData !== []) {
|
||||
DB::table('users')->upsert($usersData, ['hub_id'], [
|
||||
'name',
|
||||
'email',
|
||||
@ -611,6 +625,8 @@ 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(array $bannedUsers): void
|
||||
{
|
||||
@ -625,13 +641,15 @@ 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(array $userRanks): void
|
||||
{
|
||||
foreach ($userRanks as $userRank) {
|
||||
$roleName = Str::ucfirst(Str::afterLast($userRank['title'], '.'));
|
||||
$roleData = $this->buildUserRoleData($roleName);
|
||||
UserRole::upsert($roleData, ['name'], ['name', 'short_name', 'description', 'color_class']);
|
||||
UserRole::query()->upsert($roleData, ['name'], ['name', 'short_name', 'description', 'color_class']);
|
||||
|
||||
$userRole = UserRole::whereName($roleData['name'])->first();
|
||||
$user = User::whereHubId($userRank['hub_id'])->first();
|
||||
@ -641,6 +659,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
|
||||
{
|
||||
@ -677,7 +697,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
DB::connection('mysql_hub')
|
||||
->table('wcf1_user_follow')
|
||||
->select(['followID', 'userID', 'followUserID', 'time'])
|
||||
->chunkById(100, function (Collection $follows) use (&$followsGroupedByFollower) {
|
||||
->chunkById(100, function (Collection $follows) use (&$followsGroupedByFollower): void {
|
||||
foreach ($follows as $follow) {
|
||||
$followerId = User::whereHubId($follow->userID)->value('id');
|
||||
$followingId = User::whereHubId($follow->followUserID)->value('id');
|
||||
@ -694,7 +714,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
}, 'followID');
|
||||
|
||||
foreach ($followsGroupedByFollower as $followerId => $followings) {
|
||||
$user = User::find($followerId);
|
||||
$user = User::query()->find($followerId);
|
||||
if ($user) {
|
||||
$user->following()->sync($followings);
|
||||
}
|
||||
@ -708,7 +728,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
{
|
||||
DB::connection('mysql_hub')
|
||||
->table('filebase1_license')
|
||||
->chunkById(100, function (Collection $licenses) {
|
||||
->chunkById(100, function (Collection $licenses): void {
|
||||
|
||||
$insertData = [];
|
||||
foreach ($licenses as $license) {
|
||||
@ -719,35 +739,53 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
];
|
||||
}
|
||||
|
||||
if (! empty($insertData)) {
|
||||
if ($insertData !== []) {
|
||||
DB::table('licenses')->upsert($insertData, ['hub_id'], ['name', 'link']);
|
||||
}
|
||||
}, 'licenseID');
|
||||
}
|
||||
|
||||
/**
|
||||
* Import the SPT versions from the Hub database to the local database.
|
||||
* Import the SPT versions from the public GitHub repo to the local database.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function importSptVersions(): void
|
||||
{
|
||||
$domain = config('services.gitea.domain');
|
||||
$token = config('services.gitea.token');
|
||||
$url = 'https://api.github.com/repos/sp-tarkov/build/releases';
|
||||
$token = config('services.github.token');
|
||||
|
||||
if (empty($domain) || empty($token)) {
|
||||
return;
|
||||
}
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'User-Agent: The Forge (forge.sp-tarkov.com)',
|
||||
'Authorization: token '.$token,
|
||||
]);
|
||||
|
||||
$url = "{$domain}/api/v1/repos/SPT/Stable-releases/releases?draft=false&pre-release=false&token={$token}";
|
||||
$response = curl_exec($ch);
|
||||
|
||||
$response = json_decode(file_get_contents($url), true);
|
||||
throw_if(curl_errno($ch) !== 0, new Exception('cURL Error: '.curl_error($ch)));
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new Exception('JSON Decode Error: '.json_last_error_msg());
|
||||
}
|
||||
curl_close($ch);
|
||||
|
||||
if (empty($response)) {
|
||||
throw new Exception('No version data found in the API response.');
|
||||
}
|
||||
$response = (array) json_decode($response, true);
|
||||
|
||||
throw_if(json_last_error() !== JSON_ERROR_NONE, new Exception('JSON Decode Error: '.json_last_error_msg()));
|
||||
|
||||
throw_if($response === [], new Exception('No version data found in the GitHub API response.'));
|
||||
|
||||
// Filter out drafts and pre-releases.
|
||||
$response = array_filter($response, fn (array $release): bool => ! $release['draft'] && ! $release['prerelease']);
|
||||
|
||||
throw_if($response === [], new Exception('No finalized versions found after filtering drafts and pre-releases.'));
|
||||
|
||||
// Ensure that each of the tag_name values has any 'v' prefix trimmed.
|
||||
$response = array_map(function (array $release) {
|
||||
$release['tag_name'] = Str::of($release['tag_name'])->ltrim('v')->toString();
|
||||
|
||||
return $release;
|
||||
}, $response);
|
||||
|
||||
$latestVersion = $this->getLatestVersion($response);
|
||||
|
||||
@ -757,8 +795,8 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
'version' => $version['tag_name'],
|
||||
'link' => $version['html_url'],
|
||||
'color_class' => $this->detectVersionColor($version['tag_name'], $latestVersion),
|
||||
'created_at' => Carbon::parse($version['created_at'], 'UTC'),
|
||||
'updated_at' => Carbon::parse($version['created_at'], 'UTC'),
|
||||
'created_at' => Carbon::parse($version['published_at'], 'UTC'),
|
||||
'updated_at' => Carbon::parse($version['published_at'], 'UTC'),
|
||||
];
|
||||
}
|
||||
|
||||
@ -771,9 +809,9 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
'updated_at' => Carbon::now('UTC'),
|
||||
];
|
||||
|
||||
// Upsert won't work here. Do it manually. :(
|
||||
// Manually update or create
|
||||
foreach ($insertData as $data) {
|
||||
$existingVersion = SptVersion::where('version', $data['version'])->first();
|
||||
$existingVersion = SptVersion::query()->where('version', $data['version'])->first();
|
||||
if ($existingVersion) {
|
||||
$existingVersion->update([
|
||||
'link' => $data['link'],
|
||||
@ -782,18 +820,20 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
'updated_at' => $data['updated_at'],
|
||||
]);
|
||||
} else {
|
||||
SptVersion::create($data);
|
||||
SptVersion::query()->create($data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latest current version from the response data.
|
||||
*
|
||||
* @param array<array<string, mixed>> $versions
|
||||
*/
|
||||
protected function getLatestVersion(array $versions): string
|
||||
{
|
||||
$semanticVersions = array_map(
|
||||
fn ($version) => $this->extractSemanticVersion($version['tag_name']),
|
||||
fn ($version): ?string => $this->extractSemanticVersion($version['tag_name']),
|
||||
$versions
|
||||
);
|
||||
|
||||
@ -848,7 +888,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
$major = (int) $major;
|
||||
$minor = (int) $minor;
|
||||
|
||||
if ($major == $currentMajor) {
|
||||
if ($major === $currentMajor) {
|
||||
$difference = $currentMinor - $minor;
|
||||
|
||||
return match ($difference) {
|
||||
@ -870,12 +910,12 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
{
|
||||
// Initialize a cURL handler for downloading mod thumbnails.
|
||||
$curl = curl_init();
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
|
||||
|
||||
DB::connection('mysql_hub')
|
||||
->table('filebase1_file')
|
||||
->chunkById(100, function (Collection $mods) use ($curl) {
|
||||
->chunkById(100, function (Collection $mods) use ($curl): void {
|
||||
|
||||
foreach ($mods as $mod) {
|
||||
// Fetch any additional authors for the mod.
|
||||
@ -884,7 +924,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
->pluck('userID')
|
||||
->toArray();
|
||||
$modAuthors[] = $mod->userID; // Add the primary author to the list.
|
||||
$modAuthors = User::whereIn('hub_id', $modAuthors)->pluck('id')->toArray(); // Replace with local IDs.
|
||||
$modAuthors = User::query()->whereIn('hub_id', $modAuthors)->pluck('id')->toArray(); // Replace with local IDs.
|
||||
|
||||
$modContent = DB::table('temp_file_content')
|
||||
->where('fileID', $mod->fileID)
|
||||
@ -927,13 +967,13 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
$modData[] = [
|
||||
'hub_id' => (int) $mod->fileID,
|
||||
'users' => $modAuthors,
|
||||
'name' => $modContent?->subject ?? '',
|
||||
'slug' => Str::slug($modContent?->subject ?? ''),
|
||||
'teaser' => Str::limit($modContent?->teaser ?? '', 255),
|
||||
'description' => $this->cleanHubContent($modContent?->message ?? ''),
|
||||
'name' => $modContent->subject ?? '',
|
||||
'slug' => Str::slug($modContent->subject ?? ''),
|
||||
'teaser' => Str::limit($modContent->teaser ?? '', 255),
|
||||
'description' => $this->cleanHubContent($modContent->message ?? ''),
|
||||
'thumbnail' => $this->fetchModThumbnail($curl, $mod->fileID, $mod->iconHash, $mod->iconExtension),
|
||||
'license_id' => License::whereHubId($mod->licenseID)->value('id'),
|
||||
'source_code_link' => $optionSourceCode?->source_code_link ?? '',
|
||||
'source_code_link' => $optionSourceCode->source_code_link ?? '',
|
||||
'featured' => (bool) $mod->isFeatured,
|
||||
'contains_ai_content' => (bool) $optionContainsAi?->contains_ai,
|
||||
'contains_ads' => (bool) $optionContainsAds?->contains_ads,
|
||||
@ -948,7 +988,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
// Remove the user_id from the mod data before upserting.
|
||||
$insertModData = array_map(fn ($mod) => Arr::except($mod, 'users'), $modData);
|
||||
|
||||
Mod::withoutGlobalScopes()->upsert($insertModData, ['hub_id'], [
|
||||
Mod::query()->withoutGlobalScopes()->upsert($insertModData, ['hub_id'], [
|
||||
'name',
|
||||
'slug',
|
||||
'teaser',
|
||||
@ -978,10 +1018,10 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
/**
|
||||
* Fetch the mod thumbnail from the Hub and store it anew.
|
||||
*/
|
||||
protected function fetchModThumbnail(CurlHandle $curl, string $fileID, string $thumbnailHash, string $thumbnailExtension): string
|
||||
protected function fetchModThumbnail(CurlHandle $curlHandle, string $fileID, string $thumbnailHash, string $thumbnailExtension): string
|
||||
{
|
||||
// If any of the required fields are empty, return an empty string.
|
||||
if (empty($fileID) || empty($thumbnailHash) || empty($thumbnailExtension)) {
|
||||
if ($fileID === '' || $fileID === '0' || ($thumbnailHash === '' || $thumbnailHash === '0') || ($thumbnailExtension === '' || $thumbnailExtension === '0')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
@ -991,7 +1031,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
$hubUrl = 'https://hub.sp-tarkov.com/files/images/file/'.$hashShort.'/'.$fileName;
|
||||
$relativePath = 'mods/'.$fileName;
|
||||
|
||||
return $this->fetchAndStoreImage($curl, $hubUrl, $relativePath);
|
||||
return $this->fetchAndStoreImage($curlHandle, $hubUrl, $relativePath);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1001,7 +1041,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
{
|
||||
DB::connection('mysql_hub')
|
||||
->table('filebase1_file_version')
|
||||
->chunkById(500, function (Collection $versions) {
|
||||
->chunkById(500, function (Collection $versions): void {
|
||||
|
||||
foreach ($versions as $version) {
|
||||
$versionContent = DB::table('temp_file_version_content')
|
||||
@ -1037,7 +1077,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
|
||||
try {
|
||||
$modVersion = new Version($version->versionNumber);
|
||||
} catch (InvalidVersionNumberException $e) {
|
||||
} catch (InvalidVersionNumberException) {
|
||||
$modVersion = new Version('0.0.0');
|
||||
}
|
||||
|
||||
@ -1052,7 +1092,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
'description' => $this->cleanHubContent($versionContent->description ?? ''),
|
||||
'link' => $version->downloadURL,
|
||||
'spt_version_constraint' => $sptVersionConstraint,
|
||||
'virus_total_link' => $optionVirusTotal?->virus_total_link ?? '',
|
||||
'virus_total_link' => $optionVirusTotal->virus_total_link ?? '',
|
||||
'downloads' => max((int) $version->downloads, 0), // At least 0.
|
||||
'disabled' => (bool) $version->isDisabled,
|
||||
'published_at' => $sptVersionConstraint === '0.0.0' ? null : Carbon::parse($version->uploadTime, 'UTC'),
|
||||
@ -1062,7 +1102,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
}
|
||||
|
||||
if (! empty($insertData)) {
|
||||
ModVersion::withoutGlobalScopes()->upsert($insertData, ['hub_id'], [
|
||||
ModVersion::query()->withoutGlobalScopes()->upsert($insertData, ['hub_id'], [
|
||||
'mod_id',
|
||||
'version',
|
||||
'description',
|
||||
@ -1084,7 +1124,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
*/
|
||||
private function removeDeletedMods(): void
|
||||
{
|
||||
$mods = Mod::select('hub_id')->get();
|
||||
$mods = Mod::query()->select('hub_id')->get();
|
||||
foreach ($mods as $mod) {
|
||||
if (DB::connection('mysql_hub')->table('filebase1_file')->where('fileID', $mod->hub_id)->doesntExist()) {
|
||||
$mod->delete();
|
||||
@ -1095,7 +1135,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||
/**
|
||||
* The job failed to process.
|
||||
*/
|
||||
public function failed(Throwable $exception): void
|
||||
public function failed(Throwable $throwable): void
|
||||
{
|
||||
// Explicitly drop the temporary tables.
|
||||
DB::unprepared('DROP TEMPORARY TABLE IF EXISTS temp_user_avatar');
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\ModVersion;
|
||||
@ -13,7 +15,10 @@ use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ResolveDependenciesJob implements ShouldBeUnique, ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
use Dispatchable;
|
||||
use InteractsWithQueue;
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Resolve the SPT versions for each of the mod versions.
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\ModVersion;
|
||||
@ -13,7 +15,10 @@ use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ResolveSptVersionsJob implements ShouldBeUnique, ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
use Dispatchable;
|
||||
use InteractsWithQueue;
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Resolve the SPT versions for each of the mod versions.
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\SptVersion;
|
||||
@ -12,14 +14,17 @@ use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class SptVersionModCountsJob implements ShouldBeUnique, ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
use Dispatchable;
|
||||
use InteractsWithQueue;
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Recalculate the mod counts for each SPT version.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
SptVersion::all()->each(function (SptVersion $sptVersion) {
|
||||
SptVersion::all()->each(function (SptVersion $sptVersion): void {
|
||||
$sptVersion->updateModCount();
|
||||
});
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\Mod;
|
||||
@ -11,14 +13,17 @@ use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class UpdateModDownloadsJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
use Dispatchable;
|
||||
use InteractsWithQueue;
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Recalculate the total download counts for each mod.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
Mod::with('versions')->chunk(100, function ($mods) {
|
||||
Mod::with('versions')->chunk(100, function ($mods): void {
|
||||
foreach ($mods as $mod) {
|
||||
$mod->calculateDownloads();
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\Mod;
|
||||
@ -19,6 +21,8 @@ class GlobalSearch extends Component
|
||||
|
||||
/**
|
||||
* The search results.
|
||||
*
|
||||
* @var array<string, Collection<int, mixed>>
|
||||
*/
|
||||
#[Locked]
|
||||
public array $result = [];
|
||||
@ -42,6 +46,8 @@ class GlobalSearch extends Component
|
||||
|
||||
/**
|
||||
* Execute the search against each of the searchable models.
|
||||
*
|
||||
* @return array<string, Collection<int, mixed>>
|
||||
*/
|
||||
protected function executeSearch(string $query): array
|
||||
{
|
||||
@ -59,27 +65,37 @@ class GlobalSearch extends Component
|
||||
|
||||
/**
|
||||
* Fetch the user search results.
|
||||
*
|
||||
* @return Collection<int, mixed>
|
||||
*/
|
||||
protected function fetchUserResults(string $query): Collection
|
||||
{
|
||||
return collect(User::search($query)->raw()['hits']);
|
||||
/** @var Collection<int, mixed> $searchHits */
|
||||
$searchHits = User::search($query)->raw()['hits'];
|
||||
|
||||
return collect($searchHits);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the mod search results.
|
||||
*
|
||||
* @return Collection<int, mixed>
|
||||
*/
|
||||
protected function fetchModResults(string $query): Collection
|
||||
{
|
||||
return collect(Mod::search($query)->raw()['hits']);
|
||||
/** @var Collection<int, mixed> $searchHits */
|
||||
$searchHits = Mod::search($query)->raw()['hits'];
|
||||
|
||||
return collect($searchHits);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the total number of results across all models.
|
||||
*
|
||||
* @param array<string, Collection<int, mixed>> $results
|
||||
*/
|
||||
protected function countTotalResults(array $results): int
|
||||
{
|
||||
return collect($results)->reduce(function (int $carry, Collection $result) {
|
||||
return $carry + $result->count();
|
||||
}, 0);
|
||||
return (int) collect($results)->reduce(fn (int $carry, Collection $result): int => $carry + $result->count(), 0);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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;
|
||||
@ -42,12 +45,16 @@ class Listing extends Component
|
||||
|
||||
/**
|
||||
* The options that are available for the per page setting.
|
||||
*
|
||||
* @var array<int>
|
||||
*/
|
||||
#[Locked]
|
||||
public array $perPageOptions = [6, 12, 24, 50];
|
||||
|
||||
/**
|
||||
* The SPT versions filter value.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
#[Session]
|
||||
#[Url]
|
||||
@ -72,15 +79,15 @@ class Listing extends Component
|
||||
*/
|
||||
public function mount(): void
|
||||
{
|
||||
$this->activeSptVersions = $this->activeSptVersions ?? Cache::remember('active-spt-versions', 60 * 60, function () {
|
||||
return SptVersion::getVersionsForLastThreeMinors();
|
||||
});
|
||||
$this->activeSptVersions ??= Cache::remember('active-spt-versions', 60 * 60, fn (): Collection => SptVersion::getVersionsForLastThreeMinors());
|
||||
|
||||
$this->sptVersions = $this->sptVersions ?? $this->getDefaultSptVersions();
|
||||
$this->sptVersions ??= $this->getDefaultSptVersions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default values for the SPT Versions filter.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
protected function getDefaultSptVersions(): array
|
||||
{
|
||||
@ -89,12 +96,12 @@ class Listing extends Component
|
||||
|
||||
/**
|
||||
* Get all patch versions of the latest minor SPT version.
|
||||
*
|
||||
* @return Collection<int, SptVersion>
|
||||
*/
|
||||
public function getLatestMinorVersions(): Collection
|
||||
{
|
||||
return $this->activeSptVersions->filter(function (SptVersion $sptVersion) {
|
||||
return $sptVersion->isLatestMinor();
|
||||
});
|
||||
return $this->activeSptVersions->filter(fn (SptVersion $sptVersion): bool => $sptVersion->isLatestMinor());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -112,11 +119,11 @@ class Listing extends Component
|
||||
'sptVersions' => $this->sptVersions,
|
||||
];
|
||||
|
||||
$mods = (new ModFilter($filters))->apply()->paginate($this->perPage);
|
||||
$lengthAwarePaginator = (new ModFilter($filters))->apply()->paginate($this->perPage);
|
||||
|
||||
$this->redirectOutOfBoundsPage($mods);
|
||||
$this->redirectOutOfBoundsPage($lengthAwarePaginator);
|
||||
|
||||
return view('livewire.mod.listing', compact('mods'));
|
||||
return view('livewire.mod.listing', ['mods' => $lengthAwarePaginator]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -139,11 +146,13 @@ class Listing extends Component
|
||||
|
||||
/**
|
||||
* Check if the current page is greater than the last page. Redirect if it is.
|
||||
*
|
||||
* @param LengthAwarePaginator<Mod> $lengthAwarePaginator
|
||||
*/
|
||||
private function redirectOutOfBoundsPage(LengthAwarePaginator $mods): void
|
||||
private function redirectOutOfBoundsPage(LengthAwarePaginator $lengthAwarePaginator): void
|
||||
{
|
||||
if ($mods->currentPage() > $mods->lastPage()) {
|
||||
$this->redirectRoute('mods', ['page' => $mods->lastPage()]);
|
||||
if ($lengthAwarePaginator->currentPage() > $lengthAwarePaginator->lastPage()) {
|
||||
$this->redirectRoute('mods', ['page' => $lengthAwarePaginator->lastPage()]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -167,11 +176,11 @@ class Listing extends Component
|
||||
if ($this->query !== '') {
|
||||
$count++;
|
||||
}
|
||||
|
||||
if ($this->featured !== 'include') {
|
||||
$count++;
|
||||
}
|
||||
$count += count($this->sptVersions);
|
||||
|
||||
return $count;
|
||||
return $count + count($this->sptVersions);
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Livewire\Profile;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\View\View;
|
||||
use Livewire\Attributes\Locked;
|
||||
use Livewire\Attributes\On;
|
||||
use Livewire\Component;
|
||||
|
||||
class ManageOAuthConnections extends Component
|
||||
class ManageOauthConnections extends Component
|
||||
{
|
||||
use AuthorizesRequests;
|
||||
|
||||
@ -16,33 +19,31 @@ class ManageOAuthConnections extends Component
|
||||
* Store the current user.
|
||||
*/
|
||||
#[Locked]
|
||||
public $user;
|
||||
public User $user;
|
||||
|
||||
/**
|
||||
* Controls the confirmation modal visibility.
|
||||
*/
|
||||
public $confirmingConnectionDeletion = false;
|
||||
public bool $confirmingConnectionDeletion = false;
|
||||
|
||||
/**
|
||||
* Stores the ID of the connection to be deleted.
|
||||
*/
|
||||
#[Locked]
|
||||
public $selectedConnectionId;
|
||||
public ?string $selectedConnectionId = null;
|
||||
|
||||
/**
|
||||
* Initializes the component by loading the user's OAuth connections.
|
||||
*/
|
||||
public function mount(): void
|
||||
{
|
||||
$this->setName('profile.manage-oauth-connections');
|
||||
|
||||
$this->user = auth()->user();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the deletion confirmation.
|
||||
*/
|
||||
public function confirmConnectionDeletion($connectionId): void
|
||||
public function confirmConnectionDeletion(string $connectionId): void
|
||||
{
|
||||
$this->confirmingConnectionDeletion = true;
|
||||
$this->selectedConnectionId = $connectionId;
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Livewire\Profile;
|
||||
|
||||
use App\Actions\Fortify\PasswordValidationRules;
|
||||
@ -21,14 +23,14 @@ class UpdatePasswordForm extends JetstreamUpdatePasswordForm
|
||||
* without needing to provide their current password. This is useful for users that have been created using OAuth.
|
||||
*/
|
||||
#[Override]
|
||||
public function updatePassword(UpdatesUserPasswords $updater): void
|
||||
public function updatePassword(UpdatesUserPasswords $updatesUserPasswords): void
|
||||
{
|
||||
$this->resetErrorBag();
|
||||
|
||||
$user = Auth::user();
|
||||
|
||||
if ($user->password !== null) {
|
||||
parent::updatePassword($updater);
|
||||
parent::updatePassword($updatesUserPasswords);
|
||||
} else {
|
||||
|
||||
// User has a null password. Allow them to set a new password without their current password.
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Livewire\Profile;
|
||||
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
@ -7,11 +9,14 @@ use Illuminate\Support\Facades\Auth;
|
||||
use Laravel\Fortify\Contracts\UpdatesUserProfileInformation;
|
||||
use Laravel\Jetstream\Http\Livewire\UpdateProfileInformationForm;
|
||||
use Livewire\Features\SupportRedirects\Redirector;
|
||||
use Override;
|
||||
|
||||
class UpdateProfileForm extends UpdateProfileInformationForm
|
||||
{
|
||||
/**
|
||||
* The new cover photo for the user.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public $cover;
|
||||
|
||||
@ -38,6 +43,7 @@ class UpdateProfileForm extends UpdateProfileInformationForm
|
||||
/**
|
||||
* Update the user's profile information.
|
||||
*/
|
||||
#[Override]
|
||||
public function updateProfileInformation(UpdatesUserProfileInformation $updater): RedirectResponse|Redirector|null
|
||||
{
|
||||
$this->resetErrorBag();
|
||||
@ -51,7 +57,7 @@ class UpdateProfileForm extends UpdateProfileInformationForm
|
||||
])) : $this->state
|
||||
);
|
||||
|
||||
if (isset($this->photo) || isset($this->cover)) {
|
||||
if ($this->photo !== null || $this->cover !== null) {
|
||||
return redirect()->route('profile.show');
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Livewire\User;
|
||||
|
||||
use Illuminate\View\View;
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Livewire\User;
|
||||
|
||||
use App\Models\User;
|
||||
@ -34,18 +36,6 @@ class FollowCard extends Component
|
||||
*/
|
||||
public string $dialogTitle;
|
||||
|
||||
/**
|
||||
* The user data to display in the card.
|
||||
*/
|
||||
#[Locked]
|
||||
public array $display = [];
|
||||
|
||||
/**
|
||||
* The limited user data to display in the card.
|
||||
*/
|
||||
#[Locked]
|
||||
public array $displayLimit = [];
|
||||
|
||||
/**
|
||||
* The maximum number of users to display on the card.
|
||||
*/
|
||||
@ -65,18 +55,24 @@ class FollowCard extends Component
|
||||
|
||||
/**
|
||||
* A collection of user IDs that the auth user follows.
|
||||
*
|
||||
* @var Collection<int, int>
|
||||
*/
|
||||
#[Locked]
|
||||
public Collection $authFollowIds;
|
||||
|
||||
/**
|
||||
* The profile user's followers (or following).
|
||||
*
|
||||
* @var Collection<int, User>
|
||||
*/
|
||||
#[Locked]
|
||||
public Collection $followUsers;
|
||||
|
||||
/**
|
||||
* The events the component should listen for.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $listeners = ['refreshComponent' => '$refresh'];
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Livewire\User;
|
||||
|
||||
use App\Models\User;
|
||||
@ -19,6 +21,8 @@ class FollowCards extends Component
|
||||
|
||||
/**
|
||||
* A collection of user IDs that the auth user follows.
|
||||
*
|
||||
* @var Collection<int, int>
|
||||
*/
|
||||
#[Locked]
|
||||
public Collection $authFollowIds;
|
||||
|
@ -1,21 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Database\Factories\LicenseFactory;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* License Model
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $hub_id
|
||||
* @property string $name
|
||||
* @property string $link
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
* @property Carbon|null $deleted_at
|
||||
* @property-read Collection<int, Mod> $mods
|
||||
*/
|
||||
class License extends Model
|
||||
{
|
||||
/** @use HasFactory<LicenseFactory> */
|
||||
use HasFactory;
|
||||
|
||||
use SoftDeletes;
|
||||
|
||||
/**
|
||||
* The relationship between a license and mod.
|
||||
*
|
||||
* @return HasMany<Mod>
|
||||
* @return HasMany<Mod, $this>
|
||||
*/
|
||||
public function mods(): HasMany
|
||||
{
|
||||
|
@ -1,12 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Http\Filters\V1\QueryFilter;
|
||||
use App\Models\Scopes\PublishedScope;
|
||||
use App\Traits\CanModerate;
|
||||
use Database\Factories\ModFactory;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
@ -14,21 +18,53 @@ 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\Carbon;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Scout\Searchable;
|
||||
use Override;
|
||||
|
||||
/**
|
||||
* Mod Model
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $hub_id
|
||||
* @property string $name
|
||||
* @property string $slug
|
||||
* @property string $teaser
|
||||
* @property string $description
|
||||
* @property string $thumbnail
|
||||
* @property int|null $license_id
|
||||
* @property int $downloads
|
||||
* @property string $source_code_link
|
||||
* @property bool $featured
|
||||
* @property bool $contains_ai_content
|
||||
* @property bool $contains_ads
|
||||
* @property bool $disabled
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
* @property Carbon|null $deleted_at
|
||||
* @property Carbon|null $published_at
|
||||
* @property-read License|null $license
|
||||
* @property-read Collection<int, User> $users
|
||||
* @property-read Collection<int, ModVersion> $versions
|
||||
* @property-read ModVersion|null $latestVersion
|
||||
* @property-read ModVersion|null $latestUpdatedVersion
|
||||
*/
|
||||
class Mod extends Model
|
||||
{
|
||||
use CanModerate;
|
||||
/** @use HasFactory<ModFactory> */
|
||||
use HasFactory;
|
||||
|
||||
use Searchable;
|
||||
use SoftDeletes;
|
||||
|
||||
/**
|
||||
* Post boot method to configure the model.
|
||||
*/
|
||||
#[Override]
|
||||
protected static function booted(): void
|
||||
{
|
||||
static::addGlobalScope(new PublishedScope);
|
||||
@ -60,7 +96,7 @@ class Mod extends Model
|
||||
/**
|
||||
* The relationship between a mod and its users.
|
||||
*
|
||||
* @return BelongsToMany<User>
|
||||
* @return BelongsToMany<User, $this>
|
||||
*/
|
||||
public function users(): BelongsToMany
|
||||
{
|
||||
@ -70,7 +106,7 @@ class Mod extends Model
|
||||
/**
|
||||
* The relationship between a mod and its license.
|
||||
*
|
||||
* @return BelongsTo<License, Mod>
|
||||
* @return BelongsTo<License, $this>
|
||||
*/
|
||||
public function license(): BelongsTo
|
||||
{
|
||||
@ -80,7 +116,7 @@ class Mod extends Model
|
||||
/**
|
||||
* The relationship between a mod and its last updated version.
|
||||
*
|
||||
* @return HasOne<ModVersion>
|
||||
* @return HasOne<ModVersion, $this>
|
||||
*/
|
||||
public function latestUpdatedVersion(): HasOne
|
||||
{
|
||||
@ -93,7 +129,7 @@ class Mod extends Model
|
||||
/**
|
||||
* The relationship between a mod and its versions.
|
||||
*
|
||||
* @return HasMany<ModVersion>
|
||||
* @return HasMany<ModVersion, $this>
|
||||
*/
|
||||
public function versions(): HasMany
|
||||
{
|
||||
@ -107,6 +143,8 @@ class Mod extends Model
|
||||
|
||||
/**
|
||||
* The data that is searchable by Scout.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toSearchableArray(): array
|
||||
{
|
||||
@ -122,9 +160,9 @@ class Mod extends Model
|
||||
'description' => $this->description,
|
||||
'thumbnail' => $this->thumbnail,
|
||||
'featured' => $this->featured,
|
||||
'created_at' => strtotime($this->created_at),
|
||||
'updated_at' => strtotime($this->updated_at),
|
||||
'published_at' => strtotime($this->published_at),
|
||||
'created_at' => $this->created_at->timestamp,
|
||||
'updated_at' => $this->updated_at->timestamp,
|
||||
'published_at' => $this->published_at->timestamp,
|
||||
'latestVersion' => $this->latestVersion->latestSptVersion->version_formatted,
|
||||
'latestVersionColorClass' => $this->latestVersion->latestSptVersion->color_class,
|
||||
];
|
||||
@ -162,21 +200,16 @@ class Mod extends Model
|
||||
}
|
||||
|
||||
// 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($this->latestVersion->latestSptVersion->version, $activeSptVersions->pluck('version')->toArray())) {
|
||||
return false;
|
||||
}
|
||||
$activeSptVersions = Cache::remember('active-spt-versions', 60 * 60, fn (): Collection => SptVersion::getVersionsForLastThreeMinors());
|
||||
|
||||
// All conditions are met; the mod should be searchable.
|
||||
return true;
|
||||
return in_array($this->latestVersion->latestSptVersion->version, $activeSptVersions->pluck('version')->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* The relationship between a mod and its latest version.
|
||||
*
|
||||
* @return HasOne<ModVersion>
|
||||
* @return HasOne<ModVersion, $this>
|
||||
*/
|
||||
public function latestVersion(): HasOne
|
||||
{
|
||||
@ -193,14 +226,14 @@ class Mod extends Model
|
||||
|
||||
/**
|
||||
* Build the URL to the mod's thumbnail.
|
||||
*
|
||||
* @return Attribute<string, never>
|
||||
*/
|
||||
public function thumbnailUrl(): Attribute
|
||||
{
|
||||
return Attribute::get(function (): string {
|
||||
return $this->thumbnail
|
||||
return Attribute::get(fn (): string => $this->thumbnail
|
||||
? Storage::disk($this->thumbnailDisk())->url($this->thumbnail)
|
||||
: '';
|
||||
});
|
||||
: '');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -216,10 +249,13 @@ class Mod extends Model
|
||||
|
||||
/**
|
||||
* Scope a query by applying QueryFilter filters.
|
||||
*
|
||||
* @param Builder<Model> $builder
|
||||
* @return Builder<Model>
|
||||
*/
|
||||
public function scopeFilter(Builder $builder, QueryFilter $filters): Builder
|
||||
public function scopeFilter(Builder $builder, QueryFilter $queryFilter): Builder
|
||||
{
|
||||
return $filters->apply($builder);
|
||||
return $queryFilter->apply($builder);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,20 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Database\Factories\ModDependencyFactory;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
/**
|
||||
* ModDependency Model
|
||||
*
|
||||
* @property int $id
|
||||
* @property int $mod_version_id
|
||||
* @property int $dependent_mod_id
|
||||
* @property string $constraint
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
* @property-read ModVersion $modVersion
|
||||
* @property-read Mod $dependentMod
|
||||
* @property-read Collection<int, ModResolvedDependency> $resolvedDependencies
|
||||
*/
|
||||
class ModDependency extends Model
|
||||
{
|
||||
/** @use HasFactory<ModDependencyFactory> */
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The relationship between the mod dependency and the mod version.
|
||||
*
|
||||
* @return BelongsTo<ModVersion, ModDependency>
|
||||
* @return BelongsTo<ModVersion, $this>
|
||||
*/
|
||||
public function modVersion(): BelongsTo
|
||||
{
|
||||
@ -24,7 +43,7 @@ class ModDependency extends Model
|
||||
/**
|
||||
* The relationship between the mod dependency and the resolved dependency.
|
||||
*
|
||||
* @return HasMany<ModResolvedDependency>
|
||||
* @return HasMany<ModResolvedDependency, $this>
|
||||
*/
|
||||
public function resolvedDependencies(): HasMany
|
||||
{
|
||||
@ -35,7 +54,7 @@ class ModDependency extends Model
|
||||
/**
|
||||
* The relationship between the mod dependency and the dependent mod.
|
||||
*
|
||||
* @return BelongsTo<Mod, ModDependency>
|
||||
* @return BelongsTo<Mod, $this>
|
||||
*/
|
||||
public function dependentMod(): BelongsTo
|
||||
{
|
||||
|
@ -1,16 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* ModResolvedDependency Model
|
||||
*
|
||||
* @property int $id
|
||||
* @property int $mod_version_id
|
||||
* @property int $dependency_id
|
||||
* @property int $resolved_mod_version_id
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
* @property-read ModVersion|null $modVersion
|
||||
* @property-read ModDependency|null $dependency
|
||||
* @property-read ModVersion|null $resolvedModVersion
|
||||
*/
|
||||
class ModResolvedDependency extends Model
|
||||
{
|
||||
/**
|
||||
* The relationship between the resolved dependency and the mod version.
|
||||
*
|
||||
* @return BelongsTo<ModVersion, ModResolvedDependency>
|
||||
* @return BelongsTo<ModVersion, $this>
|
||||
*/
|
||||
public function modVersion(): BelongsTo
|
||||
{
|
||||
@ -20,7 +36,7 @@ class ModResolvedDependency extends Model
|
||||
/**
|
||||
* The relationship between the resolved dependency and the dependency.
|
||||
*
|
||||
* @return BelongsTo<ModDependency, ModResolvedDependency>
|
||||
* @return BelongsTo<ModDependency, $this>
|
||||
*/
|
||||
public function dependency(): BelongsTo
|
||||
{
|
||||
@ -30,7 +46,7 @@ class ModResolvedDependency extends Model
|
||||
/**
|
||||
* The relationship between the resolved dependency and the resolved mod version.
|
||||
*
|
||||
* @return BelongsTo<ModVersion, ModResolvedDependency>
|
||||
* @return BelongsTo<ModVersion, $this>
|
||||
*/
|
||||
public function resolvedModVersion(): BelongsTo
|
||||
{
|
||||
|
@ -1,11 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Exceptions\InvalidVersionNumberException;
|
||||
use App\Models\Scopes\PublishedScope;
|
||||
use App\Support\Version;
|
||||
use App\Traits\CanModerate;
|
||||
use Database\Factories\ModVersionFactory;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
@ -13,39 +17,74 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOneThrough;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Override;
|
||||
|
||||
/**
|
||||
* ModVersion Model
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $hub_id
|
||||
* @property int $mod_id
|
||||
* @property string $version
|
||||
* @property int $version_major
|
||||
* @property int $version_minor
|
||||
* @property int $version_patch
|
||||
* @property string $version_pre_release
|
||||
* @property string $description
|
||||
* @property string $link
|
||||
* @property string $spt_version_constraint
|
||||
* @property string $virus_total_link
|
||||
* @property int $downloads
|
||||
* @property bool $disabled
|
||||
* @property Carbon|null $deleted_at
|
||||
* @property Carbon|null $published_at
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
* @property-read Mod $mod
|
||||
* @property-read Collection<int, ModDependency> $dependencies
|
||||
* @property-read Collection<int, ModVersion> $resolvedDependencies
|
||||
* @property-read Collection<int, ModVersion> $latestResolvedDependencies
|
||||
* @property-read SptVersion|null $latestSptVersion
|
||||
* @property-read Collection<int, SptVersion> $sptVersions
|
||||
*/
|
||||
class ModVersion extends Model
|
||||
{
|
||||
use CanModerate;
|
||||
/** @use HasFactory<ModVersionFactory> */
|
||||
use HasFactory;
|
||||
|
||||
use SoftDeletes;
|
||||
|
||||
/**
|
||||
* Update the parent mod's updated_at timestamp when the mod version is updated.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $touches = ['mod'];
|
||||
|
||||
/**
|
||||
* Post boot method to configure the model.
|
||||
*/
|
||||
#[Override]
|
||||
protected static function booted(): void
|
||||
{
|
||||
static::addGlobalScope(new PublishedScope);
|
||||
|
||||
static::saving(function (ModVersion $model) {
|
||||
static::saving(function (ModVersion $modVersion): void {
|
||||
// Extract the version sections from the version string.
|
||||
try {
|
||||
$version = new Version($model->version);
|
||||
$version = new Version($modVersion->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 = '';
|
||||
$modVersion->version_major = $version->getMajor();
|
||||
$modVersion->version_minor = $version->getMinor();
|
||||
$modVersion->version_patch = $version->getPatch();
|
||||
$modVersion->version_pre_release = $version->getPreRelease();
|
||||
} catch (InvalidVersionNumberException) {
|
||||
$modVersion->version_major = 0;
|
||||
$modVersion->version_minor = 0;
|
||||
$modVersion->version_patch = 0;
|
||||
$modVersion->version_pre_release = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -53,7 +92,7 @@ class ModVersion extends Model
|
||||
/**
|
||||
* The relationship between a mod version and mod.
|
||||
*
|
||||
* @return BelongsTo<Mod, ModVersion>
|
||||
* @return BelongsTo<Mod, $this>
|
||||
*/
|
||||
public function mod(): BelongsTo
|
||||
{
|
||||
@ -63,7 +102,7 @@ class ModVersion extends Model
|
||||
/**
|
||||
* The relationship between a mod version and its dependencies.
|
||||
*
|
||||
* @return HasMany<ModDependency>
|
||||
* @return HasMany<ModDependency, $this>
|
||||
*/
|
||||
public function dependencies(): HasMany
|
||||
{
|
||||
@ -74,7 +113,7 @@ class ModVersion extends Model
|
||||
/**
|
||||
* The relationship between a mod version and its resolved dependencies.
|
||||
*
|
||||
* @return BelongsToMany<ModVersion>
|
||||
* @return BelongsToMany<ModVersion, $this>
|
||||
*/
|
||||
public function resolvedDependencies(): BelongsToMany
|
||||
{
|
||||
@ -86,13 +125,13 @@ class ModVersion extends Model
|
||||
/**
|
||||
* The relationship between a mod version and its each of it's resolved dependencies' latest versions.
|
||||
*
|
||||
* @return BelongsToMany<ModVersion>
|
||||
* @return BelongsToMany<ModVersion, $this>
|
||||
*/
|
||||
public function latestResolvedDependencies(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(ModVersion::class, 'mod_resolved_dependencies', 'mod_version_id', 'resolved_mod_version_id')
|
||||
->withPivot('dependency_id')
|
||||
->join('mod_versions as latest_versions', function ($join) {
|
||||
->join('mod_versions as latest_versions', function ($join): void {
|
||||
$join->on('latest_versions.id', '=', 'mod_versions.id')
|
||||
->whereRaw('latest_versions.version = (SELECT MAX(mv.version) FROM mod_versions mv WHERE mv.mod_id = mod_versions.mod_id)');
|
||||
})
|
||||
@ -103,7 +142,7 @@ class ModVersion extends Model
|
||||
/**
|
||||
* The relationship between a mod version and its latest SPT version.
|
||||
*
|
||||
* @return HasOneThrough<SptVersion>
|
||||
* @return HasOneThrough<SptVersion, ModVersionSptVersion, $this>
|
||||
*/
|
||||
public function latestSptVersion(): HasOneThrough
|
||||
{
|
||||
@ -118,7 +157,7 @@ class ModVersion extends Model
|
||||
/**
|
||||
* The relationship between a mod version and its SPT versions.
|
||||
*
|
||||
* @return BelongsToMany<SptVersion>
|
||||
* @return BelongsToMany<SptVersion, $this>
|
||||
*/
|
||||
public function sptVersions(): BelongsToMany
|
||||
{
|
||||
|
@ -1,9 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Relations\Pivot;
|
||||
|
||||
/**
|
||||
* ModVersionSptVersion Pivot Model
|
||||
*
|
||||
* @property int $id
|
||||
* @property int $mod_version_id
|
||||
* @property int $spt_version_id
|
||||
* @property-read ModVersion $modVersion
|
||||
* @property-read SptVersion $sptVersion
|
||||
*/
|
||||
class ModVersionSptVersion extends Pivot
|
||||
{
|
||||
public $incrementing = true;
|
||||
|
@ -1,17 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Database\Factories\OAuthConnectionFactory;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* OAuthConnection Model
|
||||
*
|
||||
* @property int $id
|
||||
* @property int $user_id
|
||||
* @property string $provider
|
||||
* @property string $provider_id
|
||||
* @property string $token
|
||||
* @property string $refresh_token
|
||||
* @property string $nickname
|
||||
* @property string $name
|
||||
* @property string $email
|
||||
* @property string $avatar
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
* @property-read User $user
|
||||
*/
|
||||
class OAuthConnection extends Model
|
||||
{
|
||||
/** @use HasFactory<OAuthConnectionFactory> */
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'oauth_connections';
|
||||
|
||||
/**
|
||||
* The relationship between the OAuth connection and the user.
|
||||
*
|
||||
* @return BelongsTo<User, $this>
|
||||
*/
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
|
22
app/Models/Scopes/DisabledScope.php
Normal file
22
app/Models/Scopes/DisabledScope.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models\Scopes;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Scope;
|
||||
|
||||
class DisabledScope implements Scope
|
||||
{
|
||||
/**
|
||||
* Apply the scope to a given Eloquent query builder.
|
||||
*
|
||||
* @param Builder<Model> $builder
|
||||
*/
|
||||
public function apply(Builder $builder, Model $model): void
|
||||
{
|
||||
$builder->where($model->getTable().'.disabled', false);
|
||||
}
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models\Scopes;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
@ -10,6 +12,8 @@ class PublishedScope implements Scope
|
||||
{
|
||||
/**
|
||||
* Apply the scope to a given Eloquent query builder.
|
||||
*
|
||||
* @param Builder<Model> $builder
|
||||
*/
|
||||
public function apply(Builder $builder, Model $model): void
|
||||
{
|
||||
|
@ -1,23 +1,52 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Exceptions\InvalidVersionNumberException;
|
||||
use App\Support\Version;
|
||||
use Carbon\Carbon;
|
||||
use Database\Factories\SptVersionFactory;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Override;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* SptVersion Model
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $hub_id
|
||||
* @property string $version
|
||||
* @property int $version_major
|
||||
* @property int $version_minor
|
||||
* @property int $version_patch
|
||||
* @property string $version_pre_release
|
||||
* @property int $mod_count
|
||||
* @property string $link
|
||||
* @property string $color_class
|
||||
* @property Carbon|null $deleted_at
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
* @property-read Collection<int, ModVersion> $modVersions
|
||||
* @property-read string $version_formatted
|
||||
*/
|
||||
class SptVersion extends Model
|
||||
{
|
||||
/** @use HasFactory<SptVersionFactory> */
|
||||
use HasFactory;
|
||||
|
||||
use SoftDeletes;
|
||||
|
||||
/**
|
||||
* Get all versions for the last three minor versions.
|
||||
*
|
||||
* @return Collection<int, $this>
|
||||
*/
|
||||
public static function getVersionsForLastThreeMinors(): Collection
|
||||
{
|
||||
@ -28,7 +57,7 @@ class SptVersion extends Model
|
||||
$minorVersions = array_column($lastThreeMinorVersions, 'minor');
|
||||
|
||||
// Fetch all versions for the last three minor versions with mod count.
|
||||
return self::select(['spt_versions.id', 'spt_versions.version', 'spt_versions.color_class', 'spt_versions.mod_count'])
|
||||
return self::query()->select(['spt_versions.id', 'spt_versions.version', 'spt_versions.color_class', 'spt_versions.mod_count'])
|
||||
->join('mod_version_spt_version', 'spt_versions.id', '=', 'mod_version_spt_version.spt_version_id')
|
||||
->join('mod_versions', 'mod_version_spt_version.mod_version_id', '=', 'mod_versions.id')
|
||||
->join('mods', 'mod_versions.mod_id', '=', 'mods.id')
|
||||
@ -46,29 +75,31 @@ 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
|
||||
{
|
||||
return self::selectRaw('CONCAT(version_major, ".", version_minor) AS minor_version, version_major, version_minor')
|
||||
return self::query()->selectRaw('CONCAT(version_major, ".", version_minor) AS minor_version, version_major, version_minor')
|
||||
->where('version', '!=', '0.0.0')
|
||||
->groupBy('version_major', 'version_minor')
|
||||
->orderByDesc('version_major')
|
||||
->orderByDesc('version_minor')
|
||||
->limit(3)
|
||||
->get()
|
||||
->map(function (SptVersion $version) {
|
||||
return [
|
||||
'major' => (int) $version->version_major,
|
||||
'minor' => (int) $version->version_minor,
|
||||
];
|
||||
})
|
||||
->map(fn (SptVersion $sptVersion): array => [
|
||||
'major' => (int) $sptVersion->version_major,
|
||||
'minor' => (int) $sptVersion->version_minor,
|
||||
])
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the version sections from the version string.
|
||||
*
|
||||
* @throws InvalidVersionNumberException
|
||||
* @return array{major: int, minor: int, patch: int, pre_release: string}
|
||||
*
|
||||
* @throws InvalidVersionNumberException|Throwable
|
||||
*/
|
||||
public static function extractVersionSections(string $version): array
|
||||
{
|
||||
@ -77,9 +108,7 @@ class SptVersion extends Model
|
||||
// Perform the regex match to capture the version sections, including the possible preRelease section.
|
||||
preg_match('/^(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:-([a-zA-Z0-9]+))?$/', $version, $matches);
|
||||
|
||||
if (! $matches) {
|
||||
throw new InvalidVersionNumberException('Invalid SPT version number: '.$version);
|
||||
}
|
||||
throw_if($matches === [], new InvalidVersionNumberException('Invalid SPT version number: '.$version));
|
||||
|
||||
return [
|
||||
'major' => $matches[1] ?? 0,
|
||||
@ -92,22 +121,23 @@ class SptVersion extends Model
|
||||
/**
|
||||
* Called when the model is booted.
|
||||
*/
|
||||
#[Override]
|
||||
protected static function booted(): void
|
||||
{
|
||||
static::saving(function (SptVersion $model) {
|
||||
static::saving(function (SptVersion $sptVersion): void {
|
||||
// Extract the version sections from the version string.
|
||||
try {
|
||||
$version = new Version($model->version);
|
||||
$version = new Version($sptVersion->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 = '';
|
||||
$sptVersion->version_major = $version->getMajor();
|
||||
$sptVersion->version_minor = $version->getMinor();
|
||||
$sptVersion->version_patch = $version->getPatch();
|
||||
$sptVersion->version_pre_release = $version->getPreRelease();
|
||||
} catch (InvalidVersionNumberException) {
|
||||
$sptVersion->version_major = 0;
|
||||
$sptVersion->version_minor = 0;
|
||||
$sptVersion->version_patch = 0;
|
||||
$sptVersion->version_pre_release = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -128,7 +158,7 @@ class SptVersion extends Model
|
||||
/**
|
||||
* The relationship between an SPT version and mod version.
|
||||
*
|
||||
* @return BelongsToMany<ModVersion>
|
||||
* @return BelongsToMany<ModVersion, $this>
|
||||
*/
|
||||
public function modVersions(): BelongsToMany
|
||||
{
|
||||
@ -152,7 +182,7 @@ class SptVersion extends Model
|
||||
{
|
||||
$latestVersion = self::getLatest();
|
||||
|
||||
if (! $latestVersion) {
|
||||
if (! $latestVersion instanceof \App\Models\SptVersion) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -166,11 +196,11 @@ class SptVersion extends Model
|
||||
*/
|
||||
public static function getLatest(): ?SptVersion
|
||||
{
|
||||
return Cache::remember('latest_spt_version', 300, function () {
|
||||
return SptVersion::select(['version', 'version_major', 'version_minor', 'version_patch', 'version_pre_release'])
|
||||
->orderByDesc('version')
|
||||
->first();
|
||||
});
|
||||
return Cache::remember('latest_spt_version', 300, fn () => \App\Models\SptVersion::query()->select(['version', 'version_major', 'version_minor', 'version_patch', 'version_pre_release'])
|
||||
->orderByDesc('version_major')
|
||||
->orderByDesc('version_minor')
|
||||
->orderByDesc('version_patch')
|
||||
->first());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,15 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Http\Filters\V1\QueryFilter;
|
||||
use App\Notifications\ResetPassword;
|
||||
use App\Notifications\VerifyEmail;
|
||||
use App\Traits\HasCoverPhoto;
|
||||
use Carbon\Carbon;
|
||||
use Database\Factories\UserFactory;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
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;
|
||||
@ -21,13 +27,39 @@ use Laravel\Jetstream\HasProfilePhoto;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
use Laravel\Scout\Searchable;
|
||||
use Mchev\Banhammer\Traits\Bannable;
|
||||
use SensitiveParameter;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int|null $hub_id
|
||||
* @property int|null $discord_id
|
||||
* @property string $name
|
||||
* @property string $email
|
||||
* @property Carbon|null $email_verified_at
|
||||
* @property string|null $password
|
||||
* @property string $about
|
||||
* @property int|null $user_role_id
|
||||
* @property string|null $remember_token
|
||||
* @property string|null $profile_photo_path
|
||||
* @property string|null $cover_photo_path
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
* @property-read string $profile_photo_url
|
||||
* @property-read UserRole|null $role
|
||||
* @property-read Collection<int, Mod> $mods
|
||||
* @property-read Collection<int, User> $followers
|
||||
* @property-read Collection<int, User> $following
|
||||
* @property-read Collection<int, OAuthConnection> $oAuthConnections
|
||||
*/
|
||||
class User extends Authenticatable implements MustVerifyEmail
|
||||
{
|
||||
use Bannable;
|
||||
use HasApiTokens;
|
||||
use HasCoverPhoto;
|
||||
|
||||
/** @use HasFactory<UserFactory> */
|
||||
use HasFactory;
|
||||
|
||||
use HasProfilePhoto;
|
||||
use Notifiable;
|
||||
use Searchable;
|
||||
@ -55,7 +87,7 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
/**
|
||||
* The relationship between a user and their mods.
|
||||
*
|
||||
* @return BelongsToMany<Mod>
|
||||
* @return BelongsToMany<Mod, $this>
|
||||
*/
|
||||
public function mods(): BelongsToMany
|
||||
{
|
||||
@ -65,7 +97,7 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
/**
|
||||
* The relationship between a user and users that follow them.
|
||||
*
|
||||
* @return BelongsToMany<User>
|
||||
* @return BelongsToMany<User, $this>
|
||||
*/
|
||||
public function followers(): BelongsToMany
|
||||
{
|
||||
@ -91,7 +123,7 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
/**
|
||||
* The relationship between a user and users they follow.
|
||||
*
|
||||
* @return BelongsToMany<User>
|
||||
* @return BelongsToMany<User, $this>
|
||||
*/
|
||||
public function following(): BelongsToMany
|
||||
{
|
||||
@ -123,6 +155,8 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
|
||||
/**
|
||||
* The data that is searchable by Scout.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toSearchableArray(): array
|
||||
{
|
||||
@ -177,7 +211,7 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
/**
|
||||
* Overwritten to instead use the queued version of the ResetPassword notification.
|
||||
*/
|
||||
public function sendPasswordResetNotification(#[\SensitiveParameter] $token): void
|
||||
public function sendPasswordResetNotification(#[SensitiveParameter] $token): void
|
||||
{
|
||||
$this->notify(new ResetPassword($token));
|
||||
}
|
||||
@ -204,9 +238,9 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
/**
|
||||
* Assign a role to the user.
|
||||
*/
|
||||
public function assignRole(UserRole $role): bool
|
||||
public function assignRole(UserRole $userRole): bool
|
||||
{
|
||||
$this->role()->associate($role);
|
||||
$this->role()->associate($userRole);
|
||||
|
||||
return $this->save();
|
||||
}
|
||||
@ -214,7 +248,7 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
/**
|
||||
* The relationship between a user and their role.
|
||||
*
|
||||
* @return BelongsTo<UserRole, User>
|
||||
* @return BelongsTo<UserRole, $this>
|
||||
*/
|
||||
public function role(): BelongsTo
|
||||
{
|
||||
@ -223,14 +257,19 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
|
||||
/**
|
||||
* Scope a query by applying QueryFilter filters.
|
||||
*
|
||||
* @param Builder<Model> $builder
|
||||
* @return Builder<Model>
|
||||
*/
|
||||
public function scopeFilter(Builder $builder, QueryFilter $filters): Builder
|
||||
public function scopeFilter(Builder $builder, QueryFilter $queryFilter): Builder
|
||||
{
|
||||
return $filters->apply($builder);
|
||||
return $queryFilter->apply($builder);
|
||||
}
|
||||
|
||||
/**
|
||||
* The relationship between a user and their OAuth providers.
|
||||
*
|
||||
* @return HasMany<OAuthConnection, $this>
|
||||
*/
|
||||
public function oAuthConnections(): HasMany
|
||||
{
|
||||
@ -239,6 +278,8 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
|
||||
/**
|
||||
* Handle the about default value if empty. Thanks, MySQL!
|
||||
*
|
||||
* @return Attribute<string[], never>
|
||||
*/
|
||||
protected function about(): Attribute
|
||||
{
|
||||
|
@ -1,19 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Database\Factories\UserRoleFactory;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
/**
|
||||
* UserRole Model
|
||||
*
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string $short_name
|
||||
* @property string $description
|
||||
* @property string $color_class
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
* @property-read Collection<int, User> $users
|
||||
*/
|
||||
class UserRole extends Model
|
||||
{
|
||||
/** @use HasFactory<UserRoleFactory> */
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The relationship between a user role and users.
|
||||
*
|
||||
* @return HasMany<User>
|
||||
* @return HasMany<User, $this>
|
||||
*/
|
||||
public function users(): HasMany
|
||||
{
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use Illuminate\Auth\Notifications\ResetPassword as OriginalResetPassword;
|
||||
@ -20,6 +22,8 @@ class ResetPassword extends OriginalResetPassword implements ShouldQueue
|
||||
|
||||
/**
|
||||
* Get the array representation of the notification.
|
||||
*
|
||||
* @return array<int, mixed>
|
||||
*/
|
||||
public function toArray(object $notifiable): array
|
||||
{
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use Illuminate\Auth\Notifications\VerifyEmail as OriginalVerifyEmail;
|
||||
@ -15,6 +17,8 @@ class VerifyEmail extends OriginalVerifyEmail implements ShouldQueue
|
||||
|
||||
/**
|
||||
* Get the array representation of the notification.
|
||||
*
|
||||
* @return array<int, mixed>
|
||||
*/
|
||||
public function toArray(object $notifiable): array
|
||||
{
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Observers;
|
||||
|
||||
use App\Models\ModDependency;
|
||||
@ -7,12 +9,7 @@ use App\Services\DependencyVersionService;
|
||||
|
||||
class ModDependencyObserver
|
||||
{
|
||||
protected DependencyVersionService $dependencyVersionService;
|
||||
|
||||
public function __construct(DependencyVersionService $dependencyVersionService)
|
||||
{
|
||||
$this->dependencyVersionService = $dependencyVersionService;
|
||||
}
|
||||
public function __construct(protected DependencyVersionService $dependencyVersionService) {}
|
||||
|
||||
/**
|
||||
* Handle the ModDependency "saved" event.
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Observers;
|
||||
|
||||
use App\Models\Mod;
|
||||
@ -7,13 +9,7 @@ use App\Services\DependencyVersionService;
|
||||
|
||||
class ModObserver
|
||||
{
|
||||
protected DependencyVersionService $dependencyVersionService;
|
||||
|
||||
public function __construct(
|
||||
DependencyVersionService $dependencyVersionService,
|
||||
) {
|
||||
$this->dependencyVersionService = $dependencyVersionService;
|
||||
}
|
||||
public function __construct(protected DependencyVersionService $dependencyVersionService) {}
|
||||
|
||||
/**
|
||||
* Handle the Mod "saved" event.
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Observers;
|
||||
|
||||
use App\Models\ModVersion;
|
||||
@ -8,17 +10,7 @@ use App\Services\SptVersionService;
|
||||
|
||||
class ModVersionObserver
|
||||
{
|
||||
protected DependencyVersionService $dependencyVersionService;
|
||||
|
||||
protected SptVersionService $sptVersionService;
|
||||
|
||||
public function __construct(
|
||||
DependencyVersionService $dependencyVersionService,
|
||||
SptVersionService $sptVersionService,
|
||||
) {
|
||||
$this->dependencyVersionService = $dependencyVersionService;
|
||||
$this->sptVersionService = $sptVersionService;
|
||||
}
|
||||
public function __construct(protected DependencyVersionService $dependencyVersionService, protected SptVersionService $sptVersionService) {}
|
||||
|
||||
/**
|
||||
* Handle the ModVersion "saved" event.
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Observers;
|
||||
|
||||
use App\Models\ModVersion;
|
||||
@ -7,12 +9,7 @@ use App\Services\SptVersionService;
|
||||
|
||||
class SptVersionObserver
|
||||
{
|
||||
protected SptVersionService $sptVersionService;
|
||||
|
||||
public function __construct(SptVersionService $sptVersionService)
|
||||
{
|
||||
$this->sptVersionService = $sptVersionService;
|
||||
}
|
||||
public function __construct(protected SptVersionService $sptVersionService) {}
|
||||
|
||||
/**
|
||||
* Handle the SptVersion "saved" event.
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\Mod;
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\ModVersion;
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\OAuthConnection;
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\User;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user