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
|
LOG_LEVEL=debug
|
||||||
|
|
||||||
DB_CONNECTION=mysql
|
DB_CONNECTION=mysql
|
||||||
DB_HOST=mysql
|
DB_HOST=127.0.0.1
|
||||||
DB_PORT=3306
|
DB_PORT=33306
|
||||||
DB_DATABASE=testing
|
DB_DATABASE=test
|
||||||
DB_USERNAME=user
|
DB_USERNAME=root
|
||||||
DB_PASSWORD=password
|
DB_PASSWORD=
|
||||||
|
|
||||||
SCOUT_DRIVER=collection
|
SCOUT_DRIVER=collection
|
||||||
FILESYSTEM_DISK=local
|
FILESYSTEM_DISK=local
|
||||||
|
@ -87,8 +87,8 @@ DB_HUB_PASSWORD=
|
|||||||
DB_HUB_CHARSET=utf8mb4
|
DB_HUB_CHARSET=utf8mb4
|
||||||
DB_HUB_COLLATION=utf8mb4_0900_ai_ci
|
DB_HUB_COLLATION=utf8mb4_0900_ai_ci
|
||||||
|
|
||||||
GITEA_DOMAIN=
|
# GitHub Token for fetching release version numbers.
|
||||||
GITEA_TOKEN=
|
GITHUB_TOKEN=
|
||||||
|
|
||||||
# API key for Scribe documentation.
|
# API key for Scribe documentation.
|
||||||
SCRIBE_AUTH_KEY=
|
SCRIBE_AUTH_KEY=
|
||||||
|
@ -43,6 +43,9 @@ MAIL_MAILER=log
|
|||||||
MAIL_FROM_ADDRESS="no-reply@sp-tarkov.com"
|
MAIL_FROM_ADDRESS="no-reply@sp-tarkov.com"
|
||||||
MAIL_FROM_NAME="${APP_NAME}"
|
MAIL_FROM_NAME="${APP_NAME}"
|
||||||
|
|
||||||
|
# GitHub Token for fetching release version numbers.
|
||||||
|
GITHUB_TOKEN=
|
||||||
|
|
||||||
# API key for Scribe documentation.
|
# API key for Scribe documentation.
|
||||||
SCRIBE_AUTH_KEY=
|
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
|
## 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:
|
### Available Services:
|
||||||
|
|
||||||
@ -77,36 +77,3 @@ sail npm run dev
|
|||||||
### More Information
|
### More Information
|
||||||
|
|
||||||
For more information on Laravel development, please refer to the [official documentation](https://laravel.com/docs/11.x/).
|
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"
|
- package-ecosystem: "composer"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "weekly"
|
||||||
time: "15:00" # 10am EST
|
day: "monday"
|
||||||
open-pull-requests-limit: 10
|
time: "10:00"
|
||||||
|
timezone: "America/Toronto"
|
||||||
|
open-pull-requests-limit: 5
|
||||||
target-branch: "develop"
|
target-branch: "develop"
|
||||||
|
versioning-strategy: increase-if-necessary
|
||||||
labels:
|
labels:
|
||||||
- "dependencies"
|
- "dependencies"
|
||||||
assignees:
|
reviewers:
|
||||||
- "Refringe"
|
- "Refringe"
|
||||||
|
|
||||||
# GitHub Actions
|
# GitHub Actions
|
||||||
- package-ecosystem: "github-actions"
|
- package-ecosystem: "github-actions"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "weekly"
|
||||||
time: "15:00" # 10am EST
|
day: "monday"
|
||||||
open-pull-requests-limit: 10
|
time: "10:00"
|
||||||
|
timezone: "America/Toronto"
|
||||||
|
open-pull-requests-limit: 5
|
||||||
target-branch: "develop"
|
target-branch: "develop"
|
||||||
labels:
|
labels:
|
||||||
- "dependencies"
|
- "dependencies"
|
||||||
assignees:
|
reviewers:
|
||||||
- "Refringe"
|
- "Refringe"
|
||||||
|
|
||||||
# npm modules (JavaScript)
|
# npm modules (JavaScript)
|
||||||
- package-ecosystem: "npm"
|
- package-ecosystem: "npm"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "weekly"
|
||||||
time: "15:00" # 10am EST
|
day: "monday"
|
||||||
open-pull-requests-limit: 10
|
time: "10:00"
|
||||||
|
timezone: "America/Toronto"
|
||||||
|
open-pull-requests-limit: 5
|
||||||
target-branch: "develop"
|
target-branch: "develop"
|
||||||
|
versioning-strategy: increase-if-necessary
|
||||||
labels:
|
labels:
|
||||||
- "dependencies"
|
- "dependencies"
|
||||||
assignees:
|
reviewers:
|
||||||
- "Refringe"
|
- "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 ]
|
on: [ push, pull_request ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
laravel-tests:
|
tests:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
services:
|
services:
|
||||||
mysql:
|
mysql:
|
||||||
image: mysql:8.3
|
image: mysql:8.3
|
||||||
|
ports:
|
||||||
|
- 33306:3306
|
||||||
env:
|
env:
|
||||||
MYSQL_DATABASE: testing
|
MYSQL_DATABASE: test
|
||||||
MYSQL_USER: user
|
MYSQL_ALLOW_EMPTY_PASSWORD: yes
|
||||||
MYSQL_PASSWORD: password
|
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||||
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
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup
|
||||||
uses: shivammathur/setup-php@v2
|
uses: ./.github/action/setup
|
||||||
with:
|
with:
|
||||||
php-version: '8.3'
|
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
|
- 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
|
- name: Install NPM dependencies
|
||||||
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
|
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
- name: Build Front-end Assets
|
- name: Build Front-end Assets
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -29,3 +29,4 @@ Homestead.yaml
|
|||||||
npm-debug.log
|
npm-debug.log
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
.scribe
|
.scribe
|
||||||
|
.rector
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Actions\Fortify;
|
namespace App\Actions\Fortify;
|
||||||
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
@ -26,7 +28,7 @@ class CreateNewUser implements CreatesNewUsers
|
|||||||
'terms' => Jetstream::hasTermsAndPrivacyPolicyFeature() ? ['accepted', 'required'] : '',
|
'terms' => Jetstream::hasTermsAndPrivacyPolicyFeature() ? ['accepted', 'required'] : '',
|
||||||
])->validate();
|
])->validate();
|
||||||
|
|
||||||
return User::create([
|
return User::query()->create([
|
||||||
'name' => $input['name'],
|
'name' => $input['name'],
|
||||||
'email' => $input['email'],
|
'email' => $input['email'],
|
||||||
'password' => Hash::make($input['password']),
|
'password' => Hash::make($input['password']),
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Actions\Fortify;
|
namespace App\Actions\Fortify;
|
||||||
|
|
||||||
use Illuminate\Validation\Rules\Password;
|
use Illuminate\Validation\Rules\Password;
|
||||||
@ -8,6 +10,8 @@ trait PasswordValidationRules
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Get the validation rules used to validate passwords.
|
* Get the validation rules used to validate passwords.
|
||||||
|
*
|
||||||
|
* @return array<int, Password|string>
|
||||||
*/
|
*/
|
||||||
protected function passwordRules(): array
|
protected function passwordRules(): array
|
||||||
{
|
{
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Actions\Fortify;
|
namespace App\Actions\Fortify;
|
||||||
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
@ -13,6 +15,8 @@ class ResetUserPassword implements ResetsUserPasswords
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate and reset the user's forgotten password.
|
* Validate and reset the user's forgotten password.
|
||||||
|
*
|
||||||
|
* @param array<string, string> $input
|
||||||
*/
|
*/
|
||||||
public function reset(User $user, array $input): void
|
public function reset(User $user, array $input): void
|
||||||
{
|
{
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Actions\Fortify;
|
namespace App\Actions\Fortify;
|
||||||
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
@ -13,6 +15,8 @@ class UpdateUserPassword implements UpdatesUserPasswords
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate and update the user's password.
|
* Validate and update the user's password.
|
||||||
|
*
|
||||||
|
* @param array<string, mixed> $input
|
||||||
*/
|
*/
|
||||||
public function update(User $user, array $input): void
|
public function update(User $user, array $input): void
|
||||||
{
|
{
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Actions\Fortify;
|
namespace App\Actions\Fortify;
|
||||||
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
use Laravel\Fortify\Contracts\UpdatesUserProfileInformation;
|
use Laravel\Fortify\Contracts\UpdatesUserProfileInformation;
|
||||||
@ -12,6 +13,8 @@ class UpdateUserProfileInformation implements UpdatesUserProfileInformation
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Validate and update the given user's profile information.
|
* Validate and update the given user's profile information.
|
||||||
|
*
|
||||||
|
* @param array<string, mixed> $input
|
||||||
*/
|
*/
|
||||||
public function update(User $user, array $input): void
|
public function update(User $user, array $input): void
|
||||||
{
|
{
|
||||||
@ -30,8 +33,7 @@ class UpdateUserProfileInformation implements UpdatesUserProfileInformation
|
|||||||
$user->updateCoverPhoto($input['cover']);
|
$user->updateCoverPhoto($input['cover']);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($input['email'] !== $user->email &&
|
if ($input['email'] !== $user->email) {
|
||||||
$user instanceof MustVerifyEmail) {
|
|
||||||
$this->updateVerifiedUser($user, $input);
|
$this->updateVerifiedUser($user, $input);
|
||||||
} else {
|
} else {
|
||||||
$user->forceFill([
|
$user->forceFill([
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Actions\Jetstream;
|
namespace App\Actions\Jetstream;
|
||||||
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
use App\Jobs\Import\ImportHubDataJob;
|
use App\Jobs\Import\ImportHubDataJob;
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
use App\Jobs\ResolveDependenciesJob;
|
use App\Jobs\ResolveDependenciesJob;
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\Mod;
|
||||||
|
use App\Models\User;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Support\Facades\Artisan;
|
use Illuminate\Support\Facades\Artisan;
|
||||||
|
|
||||||
@ -15,8 +19,8 @@ class SearchSyncCommand extends Command
|
|||||||
{
|
{
|
||||||
Artisan::call('scout:delete-all-indexes');
|
Artisan::call('scout:delete-all-indexes');
|
||||||
Artisan::call('scout:sync-index-settings');
|
Artisan::call('scout:sync-index-settings');
|
||||||
Artisan::call('scout:import', ['model' => '\App\Models\Mod']);
|
Artisan::call('scout:import', ['model' => Mod::class]);
|
||||||
Artisan::call('scout:import', ['model' => '\App\Models\User']);
|
Artisan::call('scout:import', ['model' => User::class]);
|
||||||
|
|
||||||
$this->info('The search synchronisation jobs have been added to the queue');
|
$this->info('The search synchronisation jobs have been added to the queue');
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
use App\Jobs\SptVersionModCountsJob;
|
use App\Jobs\SptVersionModCountsJob;
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
use App\Jobs\UpdateModDownloadsJob;
|
use App\Jobs\UpdateModDownloadsJob;
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Exceptions;
|
namespace App\Exceptions;
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Exceptions;
|
namespace App\Exceptions;
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
|
@ -1,14 +1,23 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Filament\Resources;
|
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 App\Models\User;
|
||||||
use Filament\Forms;
|
use Filament\Forms\Components\DateTimePicker;
|
||||||
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Forms\Form;
|
use Filament\Forms\Form;
|
||||||
use Filament\Resources\Resource;
|
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 Filament\Tables\Table;
|
||||||
|
use Override;
|
||||||
|
|
||||||
class UserResource extends Resource
|
class UserResource extends Resource
|
||||||
{
|
{
|
||||||
@ -16,46 +25,48 @@ class UserResource extends Resource
|
|||||||
|
|
||||||
protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack';
|
protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack';
|
||||||
|
|
||||||
|
#[Override]
|
||||||
public static function form(Form $form): Form
|
public static function form(Form $form): Form
|
||||||
{
|
{
|
||||||
return $form
|
return $form
|
||||||
->schema([
|
->schema([
|
||||||
Forms\Components\TextInput::make('name')
|
TextInput::make('name')
|
||||||
->required()
|
->required()
|
||||||
->maxLength(255),
|
->maxLength(255),
|
||||||
Forms\Components\TextInput::make('email')
|
TextInput::make('email')
|
||||||
->email()
|
->email()
|
||||||
->required()
|
->required()
|
||||||
->maxLength(255),
|
->maxLength(255),
|
||||||
Forms\Components\DateTimePicker::make('email_verified_at'),
|
DateTimePicker::make('email_verified_at'),
|
||||||
Forms\Components\TextInput::make('password')
|
TextInput::make('password')
|
||||||
->password()
|
->password()
|
||||||
->required()
|
->required()
|
||||||
->maxLength(255),
|
->maxLength(255),
|
||||||
Forms\Components\TextInput::make('user_role_id')
|
TextInput::make('user_role_id')
|
||||||
->numeric(),
|
->numeric(),
|
||||||
Forms\Components\TextInput::make('profile_photo_path')
|
TextInput::make('profile_photo_path')
|
||||||
->maxLength(2048),
|
->maxLength(2048),
|
||||||
Forms\Components\DateTimePicker::make('created_at'),
|
DateTimePicker::make('created_at'),
|
||||||
Forms\Components\DateTimePicker::make('updated_at'),
|
DateTimePicker::make('updated_at'),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
public static function table(Table $table): Table
|
public static function table(Table $table): Table
|
||||||
{
|
{
|
||||||
return $table
|
return $table
|
||||||
->columns([
|
->columns([
|
||||||
Tables\Columns\TextColumn::make('name')
|
TextColumn::make('name')
|
||||||
->searchable(),
|
->searchable(),
|
||||||
Tables\Columns\TextColumn::make('email')
|
TextColumn::make('email')
|
||||||
->searchable(),
|
->searchable(),
|
||||||
Tables\Columns\TextColumn::make('role.name')
|
TextColumn::make('role.name')
|
||||||
->sortable(),
|
->sortable(),
|
||||||
Tables\Columns\TextColumn::make('email_verified_at')
|
TextColumn::make('email_verified_at')
|
||||||
->dateTime()
|
->dateTime()
|
||||||
->sortable()
|
->sortable()
|
||||||
->toggleable(isToggledHiddenByDefault: true),
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
Tables\Columns\TextColumn::make('created_at')
|
TextColumn::make('created_at')
|
||||||
->dateTime()
|
->dateTime()
|
||||||
->sortable()
|
->sortable()
|
||||||
->toggleable(isToggledHiddenByDefault: true),
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
@ -64,15 +75,16 @@ class UserResource extends Resource
|
|||||||
//
|
//
|
||||||
])
|
])
|
||||||
->actions([
|
->actions([
|
||||||
Tables\Actions\EditAction::make(),
|
EditAction::make(),
|
||||||
])
|
])
|
||||||
->bulkActions([
|
->bulkActions([
|
||||||
Tables\Actions\BulkActionGroup::make([
|
BulkActionGroup::make([
|
||||||
Tables\Actions\DeleteBulkAction::make(),
|
DeleteBulkAction::make(),
|
||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
public static function getRelations(): array
|
public static function getRelations(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
@ -80,12 +92,13 @@ class UserResource extends Resource
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
public static function getPages(): array
|
public static function getPages(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'index' => Pages\ListUsers::route('/'),
|
'index' => ListUsers::route('/'),
|
||||||
'create' => Pages\CreateUser::route('/create'),
|
'create' => CreateUser::route('/create'),
|
||||||
'edit' => Pages\EditUser::route('/{record}/edit'),
|
'edit' => EditUser::route('/{record}/edit'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Filament\Resources\UserResource\Pages;
|
namespace App\Filament\Resources\UserResource\Pages;
|
||||||
|
|
||||||
use App\Filament\Resources\UserResource;
|
use App\Filament\Resources\UserResource;
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Filament\Resources\UserResource\Pages;
|
namespace App\Filament\Resources\UserResource\Pages;
|
||||||
|
|
||||||
use App\Filament\Resources\UserResource;
|
use App\Filament\Resources\UserResource;
|
||||||
use Filament\Actions;
|
use Filament\Actions\DeleteAction;
|
||||||
use Filament\Resources\Pages\EditRecord;
|
use Filament\Resources\Pages\EditRecord;
|
||||||
|
|
||||||
class EditUser extends EditRecord
|
class EditUser extends EditRecord
|
||||||
@ -13,7 +15,7 @@ class EditUser extends EditRecord
|
|||||||
protected function getHeaderActions(): array
|
protected function getHeaderActions(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
Actions\DeleteAction::make(),
|
DeleteAction::make(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Filament\Resources\UserResource\Pages;
|
namespace App\Filament\Resources\UserResource\Pages;
|
||||||
|
|
||||||
use App\Filament\Resources\UserResource;
|
use App\Filament\Resources\UserResource;
|
||||||
use Filament\Actions;
|
use Filament\Actions\CreateAction;
|
||||||
use Filament\Resources\Pages\ListRecords;
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
|
||||||
class ListUsers extends ListRecords
|
class ListUsers extends ListRecords
|
||||||
@ -13,7 +15,7 @@ class ListUsers extends ListRecords
|
|||||||
protected function getHeaderActions(): array
|
protected function getHeaderActions(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
Actions\CreateAction::make(),
|
CreateAction::make(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
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' => 'authenticated', 'data' => ['token' => 'YOUR_API_KEY'], 'status' => 200], status: 200, description: 'Authenticated successfully')]
|
||||||
#[Response(['message' => 'invalid credentials', 'status' => 401], status: 401, description: 'Invalid credentials')]
|
#[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.')]
|
#[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);
|
return $this->error(__('invalid credentials'), 401);
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = User::firstWhere('email', $request->email);
|
$user = User::query()->firstWhere('email', $loginUserRequest->email);
|
||||||
$tokenName = $request->token_name ?? __('Dynamic API Token');
|
$tokenName = $loginUserRequest->token_name ?? __('Dynamic API Token');
|
||||||
|
|
||||||
return $this->success(__('authenticated'), [
|
return $this->success(__('authenticated'), [
|
||||||
// Only allowing the 'read' scope to be dynamically created. Can revisit later when writes are possible.
|
// Only allowing the 'read' scope to be dynamically created. Can revisit later when writes are possible.
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Http\Controllers\Api\V0;
|
namespace App\Http\Controllers\Api\V0;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
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,
|
* 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.
|
* 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
|
public static function shouldInclude(string|array $relationships): bool
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$param = request()->get('include');
|
$param = request()->get('include');
|
||||||
} catch (NotFoundExceptionInterface|ContainerExceptionInterface $e) {
|
} catch (NotFoundExceptionInterface|ContainerExceptionInterface) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Http\Controllers\Api\V0;
|
namespace App\Http\Controllers\Api\V0;
|
||||||
|
|
||||||
use App\Http\Filters\V1\ModFilter;
|
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[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('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')]
|
#[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
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Http\Controllers\Api\V0;
|
namespace App\Http\Controllers\Api\V0;
|
||||||
|
|
||||||
use App\Http\Filters\V1\UserFilter;
|
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[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('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')]
|
#[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
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
abstract class Controller
|
abstract class Controller
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Http\Requests\ModRequest;
|
use App\Http\Requests\ModRequest;
|
||||||
@ -19,11 +21,11 @@ class ModController extends Controller
|
|||||||
return view('mod.index');
|
return view('mod.index');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function store(ModRequest $request): ModResource
|
public function store(ModRequest $modRequest): ModResource
|
||||||
{
|
{
|
||||||
$this->authorize('create', Mod::class);
|
$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
|
public function show(int $modId, string $slug): View
|
||||||
@ -37,20 +39,18 @@ class ModController extends Controller
|
|||||||
'users',
|
'users',
|
||||||
])->findOrFail($modId);
|
])->findOrFail($modId);
|
||||||
|
|
||||||
if ($mod->slug !== $slug) {
|
abort_if($mod->slug !== $slug, 404);
|
||||||
abort(404);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->authorize('view', $mod);
|
$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);
|
$this->authorize('update', $mod);
|
||||||
|
|
||||||
$mod->update($request->validated());
|
$mod->update($modRequest->validated());
|
||||||
|
|
||||||
return new ModResource($mod);
|
return new ModResource($mod);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Models\ModVersion;
|
use App\Models\ModVersion;
|
||||||
@ -18,17 +20,13 @@ class ModVersionController extends Controller
|
|||||||
->whereVersion($version)
|
->whereVersion($version)
|
||||||
->firstOrFail();
|
->firstOrFail();
|
||||||
|
|
||||||
if ($modVersion->mod->slug !== $slug) {
|
abort_if($modVersion->mod->slug !== $slug, 404);
|
||||||
abort(404);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->authorize('view', $modVersion);
|
$this->authorize('view', $modVersion);
|
||||||
|
|
||||||
// Rate limit the downloads.
|
// Rate limit the downloads.
|
||||||
$rateKey = 'mod-download:'.($request->user()?->id ?: $request->ip());
|
$rateKey = 'mod-download:'.($request->user()?->id ?: $request->ip());
|
||||||
if (RateLimiter::tooManyAttempts($rateKey, maxAttempts: 5)) { // Max attempts is per minute.
|
abort_if(RateLimiter::tooManyAttempts($rateKey, maxAttempts: 5), 429);
|
||||||
abort(429);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Increment downloads counts in the background.
|
// Increment downloads counts in the background.
|
||||||
defer(fn () => $modVersion->incrementDownloads());
|
defer(fn () => $modVersion->incrementDownloads());
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Models\OAuthConnection;
|
use App\Models\OAuthConnection;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use Exception;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
@ -18,6 +21,8 @@ class SocialiteController extends Controller
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The providers that are supported.
|
* The providers that are supported.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
*/
|
*/
|
||||||
protected array $providers = ['discord'];
|
protected array $providers = ['discord'];
|
||||||
|
|
||||||
@ -50,7 +55,7 @@ class SocialiteController extends Controller
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
$providerUser = Socialite::driver($provider)->user();
|
$providerUser = Socialite::driver($provider)->user();
|
||||||
} catch (\Exception $e) {
|
} catch (Exception) {
|
||||||
return redirect()->route('login')->withErrors('Unable to login using '.$provider.'. Please try again.');
|
return redirect()->route('login')->withErrors('Unable to login using '.$provider.'. Please try again.');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +72,7 @@ class SocialiteController extends Controller
|
|||||||
->whereProviderId($providerUser->getId())
|
->whereProviderId($providerUser->getId())
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
if ($oauthConnection) {
|
if ($oauthConnection !== null) {
|
||||||
$oauthConnection->update([
|
$oauthConnection->update([
|
||||||
'token' => $providerUser->token ?? '',
|
'token' => $providerUser->token ?? '',
|
||||||
'refresh_token' => $providerUser->refreshToken ?? '',
|
'refresh_token' => $providerUser->refreshToken ?? '',
|
||||||
@ -86,6 +91,7 @@ class SocialiteController extends Controller
|
|||||||
while (User::whereName($username.$random)->exists()) {
|
while (User::whereName($username.$random)->exists()) {
|
||||||
$random = '-'.Str::random(5);
|
$random = '-'.Str::random(5);
|
||||||
}
|
}
|
||||||
|
|
||||||
$username .= $random;
|
$username .= $random;
|
||||||
|
|
||||||
// The user has not connected their account with this OAuth provider before, so a new connection needs to be
|
// 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) {
|
return DB::transaction(function () use ($providerUser, $provider, $username) {
|
||||||
|
|
||||||
$user = User::firstOrCreate(['email' => $providerUser->getEmail()], [
|
$user = User::query()->firstOrCreate(['email' => $providerUser->getEmail()], [
|
||||||
'name' => $username,
|
'name' => $username,
|
||||||
'password' => null,
|
'password' => null,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$connection = $user->oAuthConnections()->create([
|
$oAuthConnection = $user->oAuthConnections()->create([
|
||||||
'provider' => $provider,
|
'provider' => $provider,
|
||||||
'provider_id' => $providerUser->getId(),
|
'provider_id' => $providerUser->getId(),
|
||||||
'token' => $providerUser->token ?? '',
|
'token' => $providerUser->token ?? '',
|
||||||
@ -110,7 +116,7 @@ class SocialiteController extends Controller
|
|||||||
'avatar' => $providerUser->getAvatar() ?? '',
|
'avatar' => $providerUser->getAvatar() ?? '',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->updateAvatar($user, $connection->avatar);
|
$this->updateAvatar($user, $oAuthConnection->avatar);
|
||||||
|
|
||||||
return $user;
|
return $user;
|
||||||
});
|
});
|
||||||
@ -125,10 +131,11 @@ class SocialiteController extends Controller
|
|||||||
};
|
};
|
||||||
|
|
||||||
$curl = curl_init();
|
$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_FOLLOWLOCATION, true);
|
||||||
curl_setopt($curl, CURLOPT_URL, $avatarUrl);
|
curl_setopt($curl, CURLOPT_URL, $avatarUrl);
|
||||||
$image = curl_exec($curl);
|
$image = curl_exec($curl);
|
||||||
|
curl_close($curl);
|
||||||
|
|
||||||
if ($image === false) {
|
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($curl));
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
@ -27,14 +29,10 @@ class UserController extends Controller
|
|||||||
->paginate(10)
|
->paginate(10)
|
||||||
->fragment('mods');
|
->fragment('mods');
|
||||||
|
|
||||||
if ($user->slug() !== $username) {
|
abort_if($user->slug() !== $username, 404);
|
||||||
abort(404);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($request->user()?->cannot('view', $user)) {
|
abort_if($request->user()?->cannot('view', $user), 403);
|
||||||
abort(403);
|
|
||||||
}
|
|
||||||
|
|
||||||
return view('user.show', compact('user', 'mods'));
|
return view('user.show', ['user' => $user, 'mods' => $mods]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Http\Filters;
|
namespace App\Http\Filters;
|
||||||
|
|
||||||
use App\Models\Mod;
|
use App\Models\Mod;
|
||||||
@ -10,31 +12,35 @@ class ModFilter
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The query builder instance for the mod model.
|
* The query builder instance for the mod model.
|
||||||
|
*
|
||||||
|
* @var Builder<Mod>
|
||||||
*/
|
*/
|
||||||
protected Builder $builder;
|
protected Builder $builder;
|
||||||
|
|
||||||
/**
|
|
||||||
* The filters to apply.
|
|
||||||
*/
|
|
||||||
protected array $filters;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new ModFilter instance.
|
* Create a new ModFilter instance.
|
||||||
*/
|
*/
|
||||||
public function __construct(array $filters)
|
public function __construct(
|
||||||
{
|
/**
|
||||||
$this->filters = $filters;
|
* The filters to apply to the query.
|
||||||
|
*
|
||||||
|
* @var array<string, mixed>
|
||||||
|
*/
|
||||||
|
protected array $filters
|
||||||
|
) {
|
||||||
$this->builder = $this->baseQuery();
|
$this->builder = $this->baseQuery();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The base query for the mod listing.
|
* The base query for the mod listing.
|
||||||
|
*
|
||||||
|
* @return Builder<Mod>
|
||||||
*/
|
*/
|
||||||
private function baseQuery(): Builder
|
private function baseQuery(): Builder
|
||||||
{
|
{
|
||||||
return Mod::query()
|
return Mod::query()
|
||||||
->select('mods.*')
|
->select('mods.*')
|
||||||
->whereExists(function ($query) {
|
->whereExists(function ($query): void {
|
||||||
$query->select(DB::raw(1))
|
$query->select(DB::raw(1))
|
||||||
->from('mod_versions')
|
->from('mod_versions')
|
||||||
->join('mod_version_spt_version', 'mod_versions.id', '=', 'mod_version_spt_version.mod_version_id')
|
->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.
|
* Filter the results by the given search term.
|
||||||
|
*
|
||||||
|
* @return Builder<Mod>
|
||||||
*/
|
*/
|
||||||
private function query(string $term): Builder
|
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.
|
* Apply the filters to the query.
|
||||||
|
*
|
||||||
|
* @return Builder<Mod>
|
||||||
*/
|
*/
|
||||||
public function apply(): Builder
|
public function apply(): Builder
|
||||||
{
|
{
|
||||||
@ -73,6 +83,8 @@ class ModFilter
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Order the query by the given type.
|
* Order the query by the given type.
|
||||||
|
*
|
||||||
|
* @return Builder<Mod>
|
||||||
*/
|
*/
|
||||||
private function order(string $type): Builder
|
private function order(string $type): Builder
|
||||||
{
|
{
|
||||||
@ -85,6 +97,8 @@ class ModFilter
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter the results by the featured status.
|
* Filter the results by the featured status.
|
||||||
|
*
|
||||||
|
* @return Builder<Mod>
|
||||||
*/
|
*/
|
||||||
private function featured(string $option): Builder
|
private function featured(string $option): Builder
|
||||||
{
|
{
|
||||||
@ -97,10 +111,13 @@ class ModFilter
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter the results to specific SPT versions.
|
* Filter the results to specific SPT versions.
|
||||||
|
*
|
||||||
|
* @param array<int, string> $versions
|
||||||
|
* @return Builder<Mod>
|
||||||
*/
|
*/
|
||||||
private function sptVersions(array $versions): Builder
|
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))
|
$query->select(DB::raw(1))
|
||||||
->from('mod_versions')
|
->from('mod_versions')
|
||||||
->join('mod_version_spt_version', 'mod_versions.id', '=', 'mod_version_spt_version.mod_version_id')
|
->join('mod_version_spt_version', 'mod_versions.id', '=', 'mod_version_spt_version.mod_version_id')
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Http\Filters\V1;
|
namespace App\Http\Filters\V1;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
class ModFilter extends QueryFilter
|
class ModFilter extends QueryFilter
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The sortable fields.
|
* The sortable fields.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
*/
|
*/
|
||||||
protected array $sortable = [
|
protected array $sortable = [
|
||||||
'name',
|
'name',
|
||||||
@ -24,6 +29,8 @@ class ModFilter extends QueryFilter
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter by ID.
|
* Filter by ID.
|
||||||
|
*
|
||||||
|
* @return Builder<Model>
|
||||||
*/
|
*/
|
||||||
public function id(string $value): Builder
|
public function id(string $value): Builder
|
||||||
{
|
{
|
||||||
@ -32,6 +39,8 @@ class ModFilter extends QueryFilter
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter by hub ID.
|
* Filter by hub ID.
|
||||||
|
*
|
||||||
|
* @return Builder<Model>
|
||||||
*/
|
*/
|
||||||
public function hub_id(string $value): Builder
|
public function hub_id(string $value): Builder
|
||||||
{
|
{
|
||||||
@ -40,6 +49,8 @@ class ModFilter extends QueryFilter
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter by name.
|
* Filter by name.
|
||||||
|
*
|
||||||
|
* @return Builder<Model>
|
||||||
*/
|
*/
|
||||||
public function name(string $value): Builder
|
public function name(string $value): Builder
|
||||||
{
|
{
|
||||||
@ -48,6 +59,8 @@ class ModFilter extends QueryFilter
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter by slug.
|
* Filter by slug.
|
||||||
|
*
|
||||||
|
* @return Builder<Model>
|
||||||
*/
|
*/
|
||||||
public function slug(string $value): Builder
|
public function slug(string $value): Builder
|
||||||
{
|
{
|
||||||
@ -56,6 +69,8 @@ class ModFilter extends QueryFilter
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter by teaser.
|
* Filter by teaser.
|
||||||
|
*
|
||||||
|
* @return Builder<Model>
|
||||||
*/
|
*/
|
||||||
public function teaser(string $value): Builder
|
public function teaser(string $value): Builder
|
||||||
{
|
{
|
||||||
@ -64,6 +79,8 @@ class ModFilter extends QueryFilter
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter by source code link.
|
* Filter by source code link.
|
||||||
|
*
|
||||||
|
* @return Builder<Model>
|
||||||
*/
|
*/
|
||||||
public function source_code_link(string $value): Builder
|
public function source_code_link(string $value): Builder
|
||||||
{
|
{
|
||||||
@ -72,6 +89,8 @@ class ModFilter extends QueryFilter
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter by created at date.
|
* Filter by created at date.
|
||||||
|
*
|
||||||
|
* @return Builder<Model>
|
||||||
*/
|
*/
|
||||||
public function created_at(string $value): Builder
|
public function created_at(string $value): Builder
|
||||||
{
|
{
|
||||||
@ -80,6 +99,8 @@ class ModFilter extends QueryFilter
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter by updated at date.
|
* Filter by updated at date.
|
||||||
|
*
|
||||||
|
* @return Builder<Model>
|
||||||
*/
|
*/
|
||||||
public function updated_at(string $value): Builder
|
public function updated_at(string $value): Builder
|
||||||
{
|
{
|
||||||
@ -88,6 +109,8 @@ class ModFilter extends QueryFilter
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter by published at date.
|
* Filter by published at date.
|
||||||
|
*
|
||||||
|
* @return Builder<Model>
|
||||||
*/
|
*/
|
||||||
public function published_at(string $value): Builder
|
public function published_at(string $value): Builder
|
||||||
{
|
{
|
||||||
@ -96,6 +119,8 @@ class ModFilter extends QueryFilter
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter by featured.
|
* Filter by featured.
|
||||||
|
*
|
||||||
|
* @return Builder<Model>
|
||||||
*/
|
*/
|
||||||
public function featured(string $value): Builder
|
public function featured(string $value): Builder
|
||||||
{
|
{
|
||||||
@ -104,6 +129,8 @@ class ModFilter extends QueryFilter
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter by contains ads.
|
* Filter by contains ads.
|
||||||
|
*
|
||||||
|
* @return Builder<Model>
|
||||||
*/
|
*/
|
||||||
public function contains_ads(string $value): Builder
|
public function contains_ads(string $value): Builder
|
||||||
{
|
{
|
||||||
@ -112,6 +139,8 @@ class ModFilter extends QueryFilter
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter by contains AI content.
|
* Filter by contains AI content.
|
||||||
|
*
|
||||||
|
* @return Builder<Model>
|
||||||
*/
|
*/
|
||||||
public function contains_ai_content(string $value): Builder
|
public function contains_ai_content(string $value): Builder
|
||||||
{
|
{
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Http\Filters\V1;
|
namespace App\Http\Filters\V1;
|
||||||
|
|
||||||
use App\Traits\V1\FilterMethods;
|
use App\Traits\V1\FilterMethods;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
abstract class QueryFilter
|
abstract class QueryFilter
|
||||||
@ -15,29 +18,33 @@ abstract class QueryFilter
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The query builder instance.
|
* The query builder instance.
|
||||||
|
*
|
||||||
|
* @var Builder<Model>
|
||||||
*/
|
*/
|
||||||
protected Builder $builder;
|
protected Builder $builder;
|
||||||
|
|
||||||
/**
|
|
||||||
* The request instance.
|
|
||||||
*/
|
|
||||||
protected Request $request;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The sortable fields.
|
* The sortable fields.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
*/
|
*/
|
||||||
protected array $sortable = [];
|
protected array $sortable = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new QueryFilter instance.
|
* Create a new QueryFilter instance.
|
||||||
*/
|
*/
|
||||||
public function __construct(Request $request)
|
public function __construct(
|
||||||
{
|
/**
|
||||||
$this->request = $request;
|
* The request instance.
|
||||||
}
|
*/
|
||||||
|
protected Request $request
|
||||||
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Iterate over each of the filter options and call the appropriate method if it exists.
|
* 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
|
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.
|
* 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
|
public function apply(Builder $builder): Builder
|
||||||
{
|
{
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Http\Filters\V1;
|
namespace App\Http\Filters\V1;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
class UserFilter extends QueryFilter
|
class UserFilter extends QueryFilter
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The sortable fields.
|
* The sortable fields.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
*/
|
*/
|
||||||
protected array $sortable = [
|
protected array $sortable = [
|
||||||
'name',
|
'name',
|
||||||
@ -17,6 +22,8 @@ class UserFilter extends QueryFilter
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter by ID.
|
* Filter by ID.
|
||||||
|
*
|
||||||
|
* @return Builder<Model>
|
||||||
*/
|
*/
|
||||||
public function id(string $value): Builder
|
public function id(string $value): Builder
|
||||||
{
|
{
|
||||||
@ -25,6 +32,8 @@ class UserFilter extends QueryFilter
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter by name.
|
* Filter by name.
|
||||||
|
*
|
||||||
|
* @return Builder<Model>
|
||||||
*/
|
*/
|
||||||
public function name(string $value): Builder
|
public function name(string $value): Builder
|
||||||
{
|
{
|
||||||
@ -33,6 +42,8 @@ class UserFilter extends QueryFilter
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter by created at date.
|
* Filter by created at date.
|
||||||
|
*
|
||||||
|
* @return Builder<Model>
|
||||||
*/
|
*/
|
||||||
public function created_at(string $value): Builder
|
public function created_at(string $value): Builder
|
||||||
{
|
{
|
||||||
@ -41,6 +52,8 @@ class UserFilter extends QueryFilter
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter by updated at date.
|
* Filter by updated at date.
|
||||||
|
*
|
||||||
|
* @return Builder<Model>
|
||||||
*/
|
*/
|
||||||
public function updated_at(string $value): Builder
|
public function updated_at(string $value): Builder
|
||||||
{
|
{
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Http\Requests\Api;
|
namespace App\Http\Requests\Api;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
@ -16,6 +18,8 @@ class LoginUserRequest extends FormRequest
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the validation rules that apply to the request.
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, array<string>>
|
||||||
*/
|
*/
|
||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Http\Requests\Api\V0;
|
namespace App\Http\Requests\Api\V0;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
@ -16,6 +18,8 @@ class StoreModRequest extends FormRequest
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the validation rules that apply to the request.
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, array<string>>
|
||||||
*/
|
*/
|
||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Http\Requests\Api\V0;
|
namespace App\Http\Requests\Api\V0;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
@ -16,6 +18,8 @@ class StoreUserRequest extends FormRequest
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the validation rules that apply to the request.
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, array<string>>
|
||||||
*/
|
*/
|
||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Http\Requests\Api\V0;
|
namespace App\Http\Requests\Api\V0;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
@ -16,6 +18,8 @@ class UpdateModRequest extends FormRequest
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the validation rules that apply to the request.
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, array<string>>
|
||||||
*/
|
*/
|
||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Http\Requests\Api\V0;
|
namespace App\Http\Requests\Api\V0;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
@ -16,6 +18,8 @@ class UpdateUserRequest extends FormRequest
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the validation rules that apply to the request.
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, array<string>>
|
||||||
*/
|
*/
|
||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Http\Requests;
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
@ -8,6 +10,8 @@ class ModRequest extends FormRequest
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Get the validation rules that apply to the request.
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, array<string>>
|
||||||
*/
|
*/
|
||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
|
@ -1,17 +1,23 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Http\Resources\Api\V0;
|
namespace App\Http\Resources\Api\V0;
|
||||||
|
|
||||||
use App\Models\License;
|
use App\Models\License;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Http\Resources\Json\JsonResource;
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
use Override;
|
||||||
|
|
||||||
/** @mixin License */
|
/** @mixin License */
|
||||||
class LicenseResource extends JsonResource
|
class LicenseResource extends JsonResource
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Transform the resource into an array.
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
|
#[Override]
|
||||||
public function toArray(Request $request): array
|
public function toArray(Request $request): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
@ -1,18 +1,26 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Http\Resources\Api\V0;
|
namespace App\Http\Resources\Api\V0;
|
||||||
|
|
||||||
use App\Http\Controllers\Api\V0\ApiController;
|
use App\Http\Controllers\Api\V0\ApiController;
|
||||||
use App\Models\Mod;
|
use App\Models\Mod;
|
||||||
|
use App\Models\ModVersion;
|
||||||
|
use App\Models\User;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Http\Resources\Json\JsonResource;
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
use Override;
|
||||||
|
|
||||||
/** @mixin Mod */
|
/** @mixin Mod */
|
||||||
class ModResource extends JsonResource
|
class ModResource extends JsonResource
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Transform the resource into an array.
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
|
#[Override]
|
||||||
public function toArray(Request $request): array
|
public function toArray(Request $request): array
|
||||||
{
|
{
|
||||||
$this->load(['users', 'versions', 'license']);
|
$this->load(['users', 'versions', 'license']);
|
||||||
@ -39,7 +47,7 @@ class ModResource extends JsonResource
|
|||||||
'published_at' => $this->published_at,
|
'published_at' => $this->published_at,
|
||||||
],
|
],
|
||||||
'relationships' => [
|
'relationships' => [
|
||||||
'users' => $this->users->map(fn ($user) => [
|
'users' => $this->users->map(fn (User $user): array => [
|
||||||
'data' => [
|
'data' => [
|
||||||
'type' => 'user',
|
'type' => 'user',
|
||||||
'id' => $user->id,
|
'id' => $user->id,
|
||||||
@ -48,7 +56,7 @@ class ModResource extends JsonResource
|
|||||||
'self' => $user->profileUrl(),
|
'self' => $user->profileUrl(),
|
||||||
],
|
],
|
||||||
])->toArray(),
|
])->toArray(),
|
||||||
'versions' => $this->versions->map(fn ($version) => [
|
'versions' => $this->versions->map(fn (ModVersion $version): array => [
|
||||||
'data' => [
|
'data' => [
|
||||||
'type' => 'version',
|
'type' => 'version',
|
||||||
'id' => $version->id,
|
'id' => $version->id,
|
||||||
@ -70,11 +78,11 @@ class ModResource extends JsonResource
|
|||||||
'includes' => $this->when(
|
'includes' => $this->when(
|
||||||
ApiController::shouldInclude(['users', 'license', 'versions']),
|
ApiController::shouldInclude(['users', 'license', 'versions']),
|
||||||
fn () => collect([
|
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),
|
'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)
|
->flatten(1)
|
||||||
->values()
|
->values()
|
||||||
),
|
),
|
||||||
|
@ -1,17 +1,23 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Http\Resources\Api\V0;
|
namespace App\Http\Resources\Api\V0;
|
||||||
|
|
||||||
use App\Models\ModVersion;
|
use App\Models\ModVersion;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Http\Resources\Json\JsonResource;
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
use Override;
|
||||||
|
|
||||||
/** @mixin ModVersion */
|
/** @mixin ModVersion */
|
||||||
class ModVersionResource extends JsonResource
|
class ModVersionResource extends JsonResource
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Transform the resource into an array.
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
|
#[Override]
|
||||||
public function toArray(Request $request): array
|
public function toArray(Request $request): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
@ -23,10 +29,10 @@ class ModVersionResource extends JsonResource
|
|||||||
'version' => $this->version,
|
'version' => $this->version,
|
||||||
|
|
||||||
// TODO: This should only be visible on the mod version show route(?) which doesn't exist.
|
// 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'),
|
// $request->routeIs('api.v0.modversion.show'),
|
||||||
// $this->description
|
// $this->description
|
||||||
//),
|
// ),
|
||||||
|
|
||||||
'link' => $this->downloadUrl(absolute: true),
|
'link' => $this->downloadUrl(absolute: true),
|
||||||
'virus_total_link' => $this->virus_total_link,
|
'virus_total_link' => $this->virus_total_link,
|
||||||
|
@ -1,18 +1,24 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Http\Resources\Api\V0;
|
namespace App\Http\Resources\Api\V0;
|
||||||
|
|
||||||
use App\Http\Controllers\Api\V0\ApiController;
|
use App\Http\Controllers\Api\V0\ApiController;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Http\Resources\Json\JsonResource;
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
use Override;
|
||||||
|
|
||||||
/** @mixin User */
|
/** @mixin User */
|
||||||
class UserResource extends JsonResource
|
class UserResource extends JsonResource
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Transform the resource into an array.
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
|
#[Override]
|
||||||
public function toArray(Request $request): array
|
public function toArray(Request $request): array
|
||||||
{
|
{
|
||||||
$this->load('role');
|
$this->load('role');
|
||||||
|
@ -1,17 +1,23 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Http\Resources\Api\V0;
|
namespace App\Http\Resources\Api\V0;
|
||||||
|
|
||||||
use App\Models\UserRole;
|
use App\Models\UserRole;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Http\Resources\Json\JsonResource;
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
use Override;
|
||||||
|
|
||||||
/** @mixin UserRole */
|
/** @mixin UserRole */
|
||||||
class UserRoleResource extends JsonResource
|
class UserRoleResource extends JsonResource
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Transform the resource into an array.
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
|
#[Override]
|
||||||
public function toArray(Request $request): array
|
public function toArray(Request $request): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
@ -1,17 +1,23 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Http\Resources;
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
use App\Models\License;
|
use App\Models\License;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Http\Resources\Json\JsonResource;
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
use Override;
|
||||||
|
|
||||||
/** @mixin License */
|
/** @mixin License */
|
||||||
class LicenseResource extends JsonResource
|
class LicenseResource extends JsonResource
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Transform the resource into an array.
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
|
#[Override]
|
||||||
public function toArray(Request $request): array
|
public function toArray(Request $request): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
@ -1,17 +1,23 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Http\Resources;
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
use App\Models\Mod;
|
use App\Models\Mod;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Http\Resources\Json\JsonResource;
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
use Override;
|
||||||
|
|
||||||
/** @mixin Mod */
|
/** @mixin Mod */
|
||||||
class ModResource extends JsonResource
|
class ModResource extends JsonResource
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Transform the resource into an array.
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
|
#[Override]
|
||||||
public function toArray(Request $request): array
|
public function toArray(Request $request): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
@ -1,17 +1,23 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Http\Resources;
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
use App\Models\ModVersion;
|
use App\Models\ModVersion;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Http\Resources\Json\JsonResource;
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
use Override;
|
||||||
|
|
||||||
/** @mixin ModVersion */
|
/** @mixin ModVersion */
|
||||||
class ModVersionResource extends JsonResource
|
class ModVersionResource extends JsonResource
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Transform the resource into an array.
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
|
#[Override]
|
||||||
public function toArray(Request $request): array
|
public function toArray(Request $request): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
@ -1,17 +1,23 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Http\Resources;
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
use App\Models\SptVersion;
|
use App\Models\SptVersion;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Http\Resources\Json\JsonResource;
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
use Override;
|
||||||
|
|
||||||
/** @mixin SptVersion */
|
/** @mixin SptVersion */
|
||||||
class SptVersionResource extends JsonResource
|
class SptVersionResource extends JsonResource
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Transform the resource into an array.
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
|
#[Override]
|
||||||
public function toArray(Request $request): array
|
public function toArray(Request $request): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Jobs\Import\DataTransferObjects;
|
namespace App\Jobs\Import\DataTransferObjects;
|
||||||
|
|
||||||
class HubUser
|
class HubUser
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Jobs\Import;
|
namespace App\Jobs\Import;
|
||||||
|
|
||||||
use App\Exceptions\InvalidVersionNumberException;
|
use App\Exceptions\InvalidVersionNumberException;
|
||||||
@ -34,7 +36,10 @@ use Throwable;
|
|||||||
|
|
||||||
class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable;
|
||||||
|
use InteractsWithQueue;
|
||||||
|
use Queueable;
|
||||||
|
use SerializesModels;
|
||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
@ -86,7 +91,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
DB::connection('mysql_hub')
|
DB::connection('mysql_hub')
|
||||||
->table('wcf1_user_avatar')
|
->table('wcf1_user_avatar')
|
||||||
->orderBy('avatarID')
|
->orderBy('avatarID')
|
||||||
->chunk(200, function ($avatars) {
|
->chunk(200, function ($avatars): void {
|
||||||
$insertData = [];
|
$insertData = [];
|
||||||
foreach ($avatars as $avatar) {
|
foreach ($avatars as $avatar) {
|
||||||
$insertData[] = [
|
$insertData[] = [
|
||||||
@ -97,7 +102,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($insertData) {
|
if ($insertData !== []) {
|
||||||
DB::table('temp_user_avatar')->insert($insertData);
|
DB::table('temp_user_avatar')->insert($insertData);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -117,7 +122,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
DB::connection('mysql_hub')
|
DB::connection('mysql_hub')
|
||||||
->table('wcf1_user_option_value')
|
->table('wcf1_user_option_value')
|
||||||
->orderBy('userID')
|
->orderBy('userID')
|
||||||
->chunk(200, function ($options) {
|
->chunk(200, function ($options): void {
|
||||||
$insertData = [];
|
$insertData = [];
|
||||||
foreach ($options as $option) {
|
foreach ($options as $option) {
|
||||||
$insertData[] = [
|
$insertData[] = [
|
||||||
@ -126,7 +131,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($insertData) {
|
if ($insertData !== []) {
|
||||||
DB::table('temp_user_options_values')->insert($insertData);
|
DB::table('temp_user_options_values')->insert($insertData);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -146,7 +151,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
DB::connection('mysql_hub')
|
DB::connection('mysql_hub')
|
||||||
->table('filebase1_file_author')
|
->table('filebase1_file_author')
|
||||||
->orderBy('fileID')
|
->orderBy('fileID')
|
||||||
->chunk(200, function ($relationships) {
|
->chunk(200, function ($relationships): void {
|
||||||
$insertData = [];
|
$insertData = [];
|
||||||
foreach ($relationships as $relationship) {
|
foreach ($relationships as $relationship) {
|
||||||
$insertData[] = [
|
$insertData[] = [
|
||||||
@ -155,7 +160,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($insertData) {
|
if ($insertData !== []) {
|
||||||
DB::table('temp_file_author')->insert($insertData);
|
DB::table('temp_file_author')->insert($insertData);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -176,7 +181,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
DB::connection('mysql_hub')
|
DB::connection('mysql_hub')
|
||||||
->table('filebase1_file_option_value')
|
->table('filebase1_file_option_value')
|
||||||
->orderBy('fileID')
|
->orderBy('fileID')
|
||||||
->chunk(200, function ($options) {
|
->chunk(200, function ($options): void {
|
||||||
$insertData = [];
|
$insertData = [];
|
||||||
foreach ($options as $option) {
|
foreach ($options as $option) {
|
||||||
$insertData[] = [
|
$insertData[] = [
|
||||||
@ -186,7 +191,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($insertData) {
|
if ($insertData !== []) {
|
||||||
DB::table('temp_file_option_values')->insert($insertData);
|
DB::table('temp_file_option_values')->insert($insertData);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -208,7 +213,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
DB::connection('mysql_hub')
|
DB::connection('mysql_hub')
|
||||||
->table('filebase1_file_content')
|
->table('filebase1_file_content')
|
||||||
->orderBy('fileID')
|
->orderBy('fileID')
|
||||||
->chunk(200, function ($contents) {
|
->chunk(200, function ($contents): void {
|
||||||
$insertData = [];
|
$insertData = [];
|
||||||
foreach ($contents as $content) {
|
foreach ($contents as $content) {
|
||||||
$insertData[] = [
|
$insertData[] = [
|
||||||
@ -219,7 +224,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($insertData) {
|
if ($insertData !== []) {
|
||||||
DB::table('temp_file_content')->insert($insertData);
|
DB::table('temp_file_content')->insert($insertData);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -240,7 +245,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
->table('wcf1_label_object')
|
->table('wcf1_label_object')
|
||||||
->where('objectTypeID', 387)
|
->where('objectTypeID', 387)
|
||||||
->orderBy('labelID')
|
->orderBy('labelID')
|
||||||
->chunk(200, function ($options) {
|
->chunk(200, function ($options): void {
|
||||||
$insertData = [];
|
$insertData = [];
|
||||||
foreach ($options as $option) {
|
foreach ($options as $option) {
|
||||||
$insertData[] = [
|
$insertData[] = [
|
||||||
@ -249,7 +254,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($insertData) {
|
if ($insertData !== []) {
|
||||||
DB::table('temp_file_version_labels')->insert($insertData);
|
DB::table('temp_file_version_labels')->insert($insertData);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -269,7 +274,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
DB::connection('mysql_hub')
|
DB::connection('mysql_hub')
|
||||||
->table('filebase1_file_version_content')
|
->table('filebase1_file_version_content')
|
||||||
->orderBy('versionID')
|
->orderBy('versionID')
|
||||||
->chunk(200, function ($options) {
|
->chunk(200, function ($options): void {
|
||||||
$insertData = [];
|
$insertData = [];
|
||||||
foreach ($options as $option) {
|
foreach ($options as $option) {
|
||||||
$insertData[] = [
|
$insertData[] = [
|
||||||
@ -278,7 +283,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($insertData) {
|
if ($insertData !== []) {
|
||||||
DB::table('temp_file_version_content')->insert($insertData);
|
DB::table('temp_file_version_content')->insert($insertData);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -297,7 +302,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
->table('wcf1_label')
|
->table('wcf1_label')
|
||||||
->where('groupID', 1)
|
->where('groupID', 1)
|
||||||
->orderBy('labelID')
|
->orderBy('labelID')
|
||||||
->chunk(100, function (Collection $versions) {
|
->chunk(100, function (Collection $versions): void {
|
||||||
$insertData = [];
|
$insertData = [];
|
||||||
foreach ($versions as $version) {
|
foreach ($versions as $version) {
|
||||||
$insertData[] = [
|
$insertData[] = [
|
||||||
@ -307,7 +312,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($insertData) {
|
if ($insertData !== []) {
|
||||||
DB::table('temp_spt_version_tags')->insert($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.
|
// Initialize a cURL handler for downloading mod thumbnails.
|
||||||
$curl = curl_init();
|
$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_FOLLOWLOCATION, true);
|
||||||
|
|
||||||
DB::connection('mysql_hub')
|
DB::connection('mysql_hub')
|
||||||
@ -340,9 +345,10 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
'r.rankTitle',
|
'r.rankTitle',
|
||||||
)
|
)
|
||||||
->leftJoin('wcf1_user_rank as r', 'u.rankID', '=', 'r.rankID')
|
->leftJoin('wcf1_user_rank as r', 'u.rankID', '=', 'r.rankID')
|
||||||
->chunkById(250, function (Collection $users) use ($curl) {
|
->chunkById(250, function (Collection $users) use ($curl): void {
|
||||||
$userData = $bannedUsers = $userRanks = [];
|
$userData = [];
|
||||||
|
$bannedUsers = [];
|
||||||
|
$userRanks = [];
|
||||||
foreach ($users as $user) {
|
foreach ($users as $user) {
|
||||||
$hubUser = new HubUser(
|
$hubUser = new HubUser(
|
||||||
$user->userID,
|
$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.
|
* 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 [
|
return [
|
||||||
'hub_id' => (int) $hubUser->userID,
|
'hub_id' => $hubUser->userID,
|
||||||
'name' => $hubUser->username,
|
'name' => $hubUser->username,
|
||||||
'email' => Str::lower($hubUser->email),
|
'email' => Str::lower($hubUser->email),
|
||||||
'password' => $this->cleanPasswordHash($hubUser->password),
|
'password' => $this->cleanPasswordHash($hubUser->password),
|
||||||
'about' => $this->fetchUserAbout($hubUser->userID),
|
'about' => $this->fetchUserAbout($hubUser->userID),
|
||||||
'profile_photo_path' => $this->fetchUserAvatar($curl, $hubUser),
|
'profile_photo_path' => $this->fetchUserAvatar($curlHandle, $hubUser),
|
||||||
'cover_photo_path' => $this->fetchUserCoverPhoto($curl, $hubUser),
|
'cover_photo_path' => $this->fetchUserCoverPhoto($curlHandle, $hubUser),
|
||||||
'created_at' => $this->cleanRegistrationDate($hubUser->registrationDate),
|
'created_at' => $this->cleanRegistrationDate($hubUser->registrationDate),
|
||||||
'updated_at' => now('UTC')->toDateTimeString(),
|
'updated_at' => now('UTC')->toDateTimeString(),
|
||||||
];
|
];
|
||||||
@ -432,16 +440,16 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
{
|
{
|
||||||
// Alright, hear me out... Shut up.
|
// Alright, hear me out... Shut up.
|
||||||
|
|
||||||
$converter = new HtmlConverter;
|
$htmlConverter = new HtmlConverter;
|
||||||
$clean = Purify::clean($dirty);
|
$clean = Purify::clean($dirty);
|
||||||
|
|
||||||
return $converter->convert($clean);
|
return $htmlConverter->convert($clean);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch the user avatar from the Hub and store it anew.
|
* 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.
|
// Fetch the user's avatar data from the temporary table.
|
||||||
$avatar = DB::table('temp_user_avatar')->where('userID', $hubUser->userID)->first();
|
$avatar = DB::table('temp_user_avatar')->where('userID', $hubUser->userID)->first();
|
||||||
@ -450,18 +458,18 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
$hashShort = substr($avatar->fileHash, 0, 2);
|
$hashShort = substr((string) $avatar->fileHash, 0, 2);
|
||||||
$fileName = $avatar->fileHash.'.'.$avatar->avatarExtension;
|
$fileName = $avatar->fileHash.'.'.$avatar->avatarExtension;
|
||||||
$hubUrl = 'https://hub.sp-tarkov.com/images/avatars/'.$hashShort.'/'.$avatar->avatarID.'-'.$fileName;
|
$hubUrl = 'https://hub.sp-tarkov.com/images/avatars/'.$hashShort.'/'.$avatar->avatarID.'-'.$fileName;
|
||||||
$relativePath = User::profilePhotoStoragePath().'/'.$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.
|
* 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.
|
// Determine the disk to use based on the environment.
|
||||||
$disk = match (config('app.env')) {
|
$disk = match (config('app.env')) {
|
||||||
@ -475,11 +483,11 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Download the image using the cURL handler.
|
// Download the image using the cURL handler.
|
||||||
curl_setopt($curl, CURLOPT_URL, $hubUrl);
|
curl_setopt($curlHandle, CURLOPT_URL, $hubUrl);
|
||||||
$image = curl_exec($curl);
|
$image = curl_exec($curlHandle);
|
||||||
|
|
||||||
if ($image === false) {
|
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 '';
|
return '';
|
||||||
}
|
}
|
||||||
@ -493,9 +501,9 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
/**
|
/**
|
||||||
* Fetch the user avatar from the Hub and store it anew.
|
* 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 '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -504,7 +512,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
$hubUrl = 'https://hub.sp-tarkov.com/images/coverPhotos/'.$hashShort.'/'.$hubUser->userID.'-'.$fileName;
|
$hubUrl = 'https://hub.sp-tarkov.com/images/coverPhotos/'.$hashShort.'/'.$hubUser->userID.'-'.$fileName;
|
||||||
$relativePath = 'user-covers/'.$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.
|
* 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
|
protected function collectBannedUserData(HubUser $hubUser): ?array
|
||||||
{
|
{
|
||||||
if ($hubUser->banned) {
|
if ($hubUser->banned) {
|
||||||
return [
|
return [
|
||||||
'hub_id' => (int) $hubUser->userID,
|
'hub_id' => $hubUser->userID,
|
||||||
'comment' => $hubUser->banReason ?? '',
|
'comment' => $hubUser->banReason ?? '',
|
||||||
'expired_at' => $this->cleanUnbannedAtDate($hubUser->banExpires),
|
'expired_at' => $this->cleanUnbannedAtDate($hubUser->banExpires),
|
||||||
];
|
];
|
||||||
@ -572,7 +582,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
}
|
}
|
||||||
|
|
||||||
return $date->toDateTimeString();
|
return $date->toDateTimeString();
|
||||||
} catch (\Exception $e) {
|
} catch (Exception) {
|
||||||
// If the date is not valid, return null
|
// If the date is not valid, return null
|
||||||
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.
|
* 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
|
protected function collectUserRankData(HubUser $hubUser): ?array
|
||||||
{
|
{
|
||||||
if ($hubUser->rankID && $hubUser->rankTitle) {
|
if ($hubUser->rankID && $hubUser->rankTitle) {
|
||||||
return [
|
return [
|
||||||
'hub_id' => (int) $hubUser->userID,
|
'hub_id' => $hubUser->userID,
|
||||||
'title' => $hubUser->rankTitle,
|
'title' => $hubUser->rankTitle,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -595,10 +607,12 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Insert or update the users in the local database.
|
* Insert or update the users in the local database.
|
||||||
|
*
|
||||||
|
* @param array<array<string, mixed>> $usersData
|
||||||
*/
|
*/
|
||||||
protected function upsertUsers(array $usersData): void
|
protected function upsertUsers(array $usersData): void
|
||||||
{
|
{
|
||||||
if (! empty($usersData)) {
|
if ($usersData !== []) {
|
||||||
DB::table('users')->upsert($usersData, ['hub_id'], [
|
DB::table('users')->upsert($usersData, ['hub_id'], [
|
||||||
'name',
|
'name',
|
||||||
'email',
|
'email',
|
||||||
@ -611,6 +625,8 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch the hub-banned users from the local database and ban them locally.
|
* 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
|
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.
|
* 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
|
protected function handleUserRoles(array $userRanks): void
|
||||||
{
|
{
|
||||||
foreach ($userRanks as $userRank) {
|
foreach ($userRanks as $userRank) {
|
||||||
$roleName = Str::ucfirst(Str::afterLast($userRank['title'], '.'));
|
$roleName = Str::ucfirst(Str::afterLast($userRank['title'], '.'));
|
||||||
$roleData = $this->buildUserRoleData($roleName);
|
$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();
|
$userRole = UserRole::whereName($roleData['name'])->first();
|
||||||
$user = User::whereHubId($userRank['hub_id'])->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.
|
* Build the user role data based on the role name.
|
||||||
|
*
|
||||||
|
* @return array<string, string>
|
||||||
*/
|
*/
|
||||||
protected function buildUserRoleData(string $name): array
|
protected function buildUserRoleData(string $name): array
|
||||||
{
|
{
|
||||||
@ -677,7 +697,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
DB::connection('mysql_hub')
|
DB::connection('mysql_hub')
|
||||||
->table('wcf1_user_follow')
|
->table('wcf1_user_follow')
|
||||||
->select(['followID', 'userID', 'followUserID', 'time'])
|
->select(['followID', 'userID', 'followUserID', 'time'])
|
||||||
->chunkById(100, function (Collection $follows) use (&$followsGroupedByFollower) {
|
->chunkById(100, function (Collection $follows) use (&$followsGroupedByFollower): void {
|
||||||
foreach ($follows as $follow) {
|
foreach ($follows as $follow) {
|
||||||
$followerId = User::whereHubId($follow->userID)->value('id');
|
$followerId = User::whereHubId($follow->userID)->value('id');
|
||||||
$followingId = User::whereHubId($follow->followUserID)->value('id');
|
$followingId = User::whereHubId($follow->followUserID)->value('id');
|
||||||
@ -694,7 +714,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
}, 'followID');
|
}, 'followID');
|
||||||
|
|
||||||
foreach ($followsGroupedByFollower as $followerId => $followings) {
|
foreach ($followsGroupedByFollower as $followerId => $followings) {
|
||||||
$user = User::find($followerId);
|
$user = User::query()->find($followerId);
|
||||||
if ($user) {
|
if ($user) {
|
||||||
$user->following()->sync($followings);
|
$user->following()->sync($followings);
|
||||||
}
|
}
|
||||||
@ -708,7 +728,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
{
|
{
|
||||||
DB::connection('mysql_hub')
|
DB::connection('mysql_hub')
|
||||||
->table('filebase1_license')
|
->table('filebase1_license')
|
||||||
->chunkById(100, function (Collection $licenses) {
|
->chunkById(100, function (Collection $licenses): void {
|
||||||
|
|
||||||
$insertData = [];
|
$insertData = [];
|
||||||
foreach ($licenses as $license) {
|
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']);
|
DB::table('licenses')->upsert($insertData, ['hub_id'], ['name', 'link']);
|
||||||
}
|
}
|
||||||
}, 'licenseID');
|
}, '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
|
protected function importSptVersions(): void
|
||||||
{
|
{
|
||||||
$domain = config('services.gitea.domain');
|
$url = 'https://api.github.com/repos/sp-tarkov/build/releases';
|
||||||
$token = config('services.gitea.token');
|
$token = config('services.github.token');
|
||||||
|
|
||||||
if (empty($domain) || empty($token)) {
|
$ch = curl_init();
|
||||||
return;
|
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) {
|
curl_close($ch);
|
||||||
throw new Exception('JSON Decode Error: '.json_last_error_msg());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (empty($response)) {
|
$response = (array) json_decode($response, true);
|
||||||
throw new Exception('No version data found in the API response.');
|
|
||||||
}
|
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);
|
$latestVersion = $this->getLatestVersion($response);
|
||||||
|
|
||||||
@ -757,8 +795,8 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
'version' => $version['tag_name'],
|
'version' => $version['tag_name'],
|
||||||
'link' => $version['html_url'],
|
'link' => $version['html_url'],
|
||||||
'color_class' => $this->detectVersionColor($version['tag_name'], $latestVersion),
|
'color_class' => $this->detectVersionColor($version['tag_name'], $latestVersion),
|
||||||
'created_at' => Carbon::parse($version['created_at'], 'UTC'),
|
'created_at' => Carbon::parse($version['published_at'], 'UTC'),
|
||||||
'updated_at' => Carbon::parse($version['created_at'], 'UTC'),
|
'updated_at' => Carbon::parse($version['published_at'], 'UTC'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -771,9 +809,9 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
'updated_at' => Carbon::now('UTC'),
|
'updated_at' => Carbon::now('UTC'),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Upsert won't work here. Do it manually. :(
|
// Manually update or create
|
||||||
foreach ($insertData as $data) {
|
foreach ($insertData as $data) {
|
||||||
$existingVersion = SptVersion::where('version', $data['version'])->first();
|
$existingVersion = SptVersion::query()->where('version', $data['version'])->first();
|
||||||
if ($existingVersion) {
|
if ($existingVersion) {
|
||||||
$existingVersion->update([
|
$existingVersion->update([
|
||||||
'link' => $data['link'],
|
'link' => $data['link'],
|
||||||
@ -782,18 +820,20 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
'updated_at' => $data['updated_at'],
|
'updated_at' => $data['updated_at'],
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
SptVersion::create($data);
|
SptVersion::query()->create($data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the latest current version from the response data.
|
* Get the latest current version from the response data.
|
||||||
|
*
|
||||||
|
* @param array<array<string, mixed>> $versions
|
||||||
*/
|
*/
|
||||||
protected function getLatestVersion(array $versions): string
|
protected function getLatestVersion(array $versions): string
|
||||||
{
|
{
|
||||||
$semanticVersions = array_map(
|
$semanticVersions = array_map(
|
||||||
fn ($version) => $this->extractSemanticVersion($version['tag_name']),
|
fn ($version): ?string => $this->extractSemanticVersion($version['tag_name']),
|
||||||
$versions
|
$versions
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -848,7 +888,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
$major = (int) $major;
|
$major = (int) $major;
|
||||||
$minor = (int) $minor;
|
$minor = (int) $minor;
|
||||||
|
|
||||||
if ($major == $currentMajor) {
|
if ($major === $currentMajor) {
|
||||||
$difference = $currentMinor - $minor;
|
$difference = $currentMinor - $minor;
|
||||||
|
|
||||||
return match ($difference) {
|
return match ($difference) {
|
||||||
@ -870,12 +910,12 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
{
|
{
|
||||||
// Initialize a cURL handler for downloading mod thumbnails.
|
// Initialize a cURL handler for downloading mod thumbnails.
|
||||||
$curl = curl_init();
|
$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_FOLLOWLOCATION, true);
|
||||||
|
|
||||||
DB::connection('mysql_hub')
|
DB::connection('mysql_hub')
|
||||||
->table('filebase1_file')
|
->table('filebase1_file')
|
||||||
->chunkById(100, function (Collection $mods) use ($curl) {
|
->chunkById(100, function (Collection $mods) use ($curl): void {
|
||||||
|
|
||||||
foreach ($mods as $mod) {
|
foreach ($mods as $mod) {
|
||||||
// Fetch any additional authors for the mod.
|
// Fetch any additional authors for the mod.
|
||||||
@ -884,7 +924,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
->pluck('userID')
|
->pluck('userID')
|
||||||
->toArray();
|
->toArray();
|
||||||
$modAuthors[] = $mod->userID; // Add the primary author to the list.
|
$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')
|
$modContent = DB::table('temp_file_content')
|
||||||
->where('fileID', $mod->fileID)
|
->where('fileID', $mod->fileID)
|
||||||
@ -927,13 +967,13 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
$modData[] = [
|
$modData[] = [
|
||||||
'hub_id' => (int) $mod->fileID,
|
'hub_id' => (int) $mod->fileID,
|
||||||
'users' => $modAuthors,
|
'users' => $modAuthors,
|
||||||
'name' => $modContent?->subject ?? '',
|
'name' => $modContent->subject ?? '',
|
||||||
'slug' => Str::slug($modContent?->subject ?? ''),
|
'slug' => Str::slug($modContent->subject ?? ''),
|
||||||
'teaser' => Str::limit($modContent?->teaser ?? '', 255),
|
'teaser' => Str::limit($modContent->teaser ?? '', 255),
|
||||||
'description' => $this->cleanHubContent($modContent?->message ?? ''),
|
'description' => $this->cleanHubContent($modContent->message ?? ''),
|
||||||
'thumbnail' => $this->fetchModThumbnail($curl, $mod->fileID, $mod->iconHash, $mod->iconExtension),
|
'thumbnail' => $this->fetchModThumbnail($curl, $mod->fileID, $mod->iconHash, $mod->iconExtension),
|
||||||
'license_id' => License::whereHubId($mod->licenseID)->value('id'),
|
'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,
|
'featured' => (bool) $mod->isFeatured,
|
||||||
'contains_ai_content' => (bool) $optionContainsAi?->contains_ai,
|
'contains_ai_content' => (bool) $optionContainsAi?->contains_ai,
|
||||||
'contains_ads' => (bool) $optionContainsAds?->contains_ads,
|
'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.
|
// Remove the user_id from the mod data before upserting.
|
||||||
$insertModData = array_map(fn ($mod) => Arr::except($mod, 'users'), $modData);
|
$insertModData = array_map(fn ($mod) => Arr::except($mod, 'users'), $modData);
|
||||||
|
|
||||||
Mod::withoutGlobalScopes()->upsert($insertModData, ['hub_id'], [
|
Mod::query()->withoutGlobalScopes()->upsert($insertModData, ['hub_id'], [
|
||||||
'name',
|
'name',
|
||||||
'slug',
|
'slug',
|
||||||
'teaser',
|
'teaser',
|
||||||
@ -978,10 +1018,10 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
/**
|
/**
|
||||||
* Fetch the mod thumbnail from the Hub and store it anew.
|
* 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 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 '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -991,7 +1031,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
$hubUrl = 'https://hub.sp-tarkov.com/files/images/file/'.$hashShort.'/'.$fileName;
|
$hubUrl = 'https://hub.sp-tarkov.com/files/images/file/'.$hashShort.'/'.$fileName;
|
||||||
$relativePath = 'mods/'.$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')
|
DB::connection('mysql_hub')
|
||||||
->table('filebase1_file_version')
|
->table('filebase1_file_version')
|
||||||
->chunkById(500, function (Collection $versions) {
|
->chunkById(500, function (Collection $versions): void {
|
||||||
|
|
||||||
foreach ($versions as $version) {
|
foreach ($versions as $version) {
|
||||||
$versionContent = DB::table('temp_file_version_content')
|
$versionContent = DB::table('temp_file_version_content')
|
||||||
@ -1037,7 +1077,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
$modVersion = new Version($version->versionNumber);
|
$modVersion = new Version($version->versionNumber);
|
||||||
} catch (InvalidVersionNumberException $e) {
|
} catch (InvalidVersionNumberException) {
|
||||||
$modVersion = new Version('0.0.0');
|
$modVersion = new Version('0.0.0');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1052,7 +1092,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
'description' => $this->cleanHubContent($versionContent->description ?? ''),
|
'description' => $this->cleanHubContent($versionContent->description ?? ''),
|
||||||
'link' => $version->downloadURL,
|
'link' => $version->downloadURL,
|
||||||
'spt_version_constraint' => $sptVersionConstraint,
|
'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.
|
'downloads' => max((int) $version->downloads, 0), // At least 0.
|
||||||
'disabled' => (bool) $version->isDisabled,
|
'disabled' => (bool) $version->isDisabled,
|
||||||
'published_at' => $sptVersionConstraint === '0.0.0' ? null : Carbon::parse($version->uploadTime, 'UTC'),
|
'published_at' => $sptVersionConstraint === '0.0.0' ? null : Carbon::parse($version->uploadTime, 'UTC'),
|
||||||
@ -1062,7 +1102,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (! empty($insertData)) {
|
if (! empty($insertData)) {
|
||||||
ModVersion::withoutGlobalScopes()->upsert($insertData, ['hub_id'], [
|
ModVersion::query()->withoutGlobalScopes()->upsert($insertData, ['hub_id'], [
|
||||||
'mod_id',
|
'mod_id',
|
||||||
'version',
|
'version',
|
||||||
'description',
|
'description',
|
||||||
@ -1084,7 +1124,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
*/
|
*/
|
||||||
private function removeDeletedMods(): void
|
private function removeDeletedMods(): void
|
||||||
{
|
{
|
||||||
$mods = Mod::select('hub_id')->get();
|
$mods = Mod::query()->select('hub_id')->get();
|
||||||
foreach ($mods as $mod) {
|
foreach ($mods as $mod) {
|
||||||
if (DB::connection('mysql_hub')->table('filebase1_file')->where('fileID', $mod->hub_id)->doesntExist()) {
|
if (DB::connection('mysql_hub')->table('filebase1_file')->where('fileID', $mod->hub_id)->doesntExist()) {
|
||||||
$mod->delete();
|
$mod->delete();
|
||||||
@ -1095,7 +1135,7 @@ class ImportHubDataJob implements ShouldBeUnique, ShouldQueue
|
|||||||
/**
|
/**
|
||||||
* The job failed to process.
|
* The job failed to process.
|
||||||
*/
|
*/
|
||||||
public function failed(Throwable $exception): void
|
public function failed(Throwable $throwable): void
|
||||||
{
|
{
|
||||||
// Explicitly drop the temporary tables.
|
// Explicitly drop the temporary tables.
|
||||||
DB::unprepared('DROP TEMPORARY TABLE IF EXISTS temp_user_avatar');
|
DB::unprepared('DROP TEMPORARY TABLE IF EXISTS temp_user_avatar');
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
use App\Models\ModVersion;
|
use App\Models\ModVersion;
|
||||||
@ -13,7 +15,10 @@ use Illuminate\Queue\SerializesModels;
|
|||||||
|
|
||||||
class ResolveDependenciesJob implements ShouldBeUnique, ShouldQueue
|
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.
|
* Resolve the SPT versions for each of the mod versions.
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
use App\Models\ModVersion;
|
use App\Models\ModVersion;
|
||||||
@ -13,7 +15,10 @@ use Illuminate\Queue\SerializesModels;
|
|||||||
|
|
||||||
class ResolveSptVersionsJob implements ShouldBeUnique, ShouldQueue
|
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.
|
* Resolve the SPT versions for each of the mod versions.
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
use App\Models\SptVersion;
|
use App\Models\SptVersion;
|
||||||
@ -12,14 +14,17 @@ use Illuminate\Queue\SerializesModels;
|
|||||||
|
|
||||||
class SptVersionModCountsJob implements ShouldBeUnique, ShouldQueue
|
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.
|
* Recalculate the mod counts for each SPT version.
|
||||||
*/
|
*/
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
SptVersion::all()->each(function (SptVersion $sptVersion) {
|
SptVersion::all()->each(function (SptVersion $sptVersion): void {
|
||||||
$sptVersion->updateModCount();
|
$sptVersion->updateModCount();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
use App\Models\Mod;
|
use App\Models\Mod;
|
||||||
@ -11,14 +13,17 @@ use Illuminate\Queue\SerializesModels;
|
|||||||
|
|
||||||
class UpdateModDownloadsJob implements ShouldQueue
|
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.
|
* Recalculate the total download counts for each mod.
|
||||||
*/
|
*/
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
Mod::with('versions')->chunk(100, function ($mods) {
|
Mod::with('versions')->chunk(100, function ($mods): void {
|
||||||
foreach ($mods as $mod) {
|
foreach ($mods as $mod) {
|
||||||
$mod->calculateDownloads();
|
$mod->calculateDownloads();
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Livewire;
|
namespace App\Livewire;
|
||||||
|
|
||||||
use App\Models\Mod;
|
use App\Models\Mod;
|
||||||
@ -19,6 +21,8 @@ class GlobalSearch extends Component
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The search results.
|
* The search results.
|
||||||
|
*
|
||||||
|
* @var array<string, Collection<int, mixed>>
|
||||||
*/
|
*/
|
||||||
#[Locked]
|
#[Locked]
|
||||||
public array $result = [];
|
public array $result = [];
|
||||||
@ -42,6 +46,8 @@ class GlobalSearch extends Component
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the search against each of the searchable models.
|
* Execute the search against each of the searchable models.
|
||||||
|
*
|
||||||
|
* @return array<string, Collection<int, mixed>>
|
||||||
*/
|
*/
|
||||||
protected function executeSearch(string $query): array
|
protected function executeSearch(string $query): array
|
||||||
{
|
{
|
||||||
@ -59,27 +65,37 @@ class GlobalSearch extends Component
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch the user search results.
|
* Fetch the user search results.
|
||||||
|
*
|
||||||
|
* @return Collection<int, mixed>
|
||||||
*/
|
*/
|
||||||
protected function fetchUserResults(string $query): Collection
|
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.
|
* Fetch the mod search results.
|
||||||
|
*
|
||||||
|
* @return Collection<int, mixed>
|
||||||
*/
|
*/
|
||||||
protected function fetchModResults(string $query): Collection
|
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.
|
* Count the total number of results across all models.
|
||||||
|
*
|
||||||
|
* @param array<string, Collection<int, mixed>> $results
|
||||||
*/
|
*/
|
||||||
protected function countTotalResults(array $results): int
|
protected function countTotalResults(array $results): int
|
||||||
{
|
{
|
||||||
return collect($results)->reduce(function (int $carry, Collection $result) {
|
return (int) collect($results)->reduce(fn (int $carry, Collection $result): int => $carry + $result->count(), 0);
|
||||||
return $carry + $result->count();
|
|
||||||
}, 0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Livewire\Mod;
|
namespace App\Livewire\Mod;
|
||||||
|
|
||||||
use App\Http\Filters\ModFilter;
|
use App\Http\Filters\ModFilter;
|
||||||
|
use App\Models\Mod;
|
||||||
use App\Models\SptVersion;
|
use App\Models\SptVersion;
|
||||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||||
use Illuminate\Contracts\View\View;
|
use Illuminate\Contracts\View\View;
|
||||||
@ -42,12 +45,16 @@ class Listing extends Component
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The options that are available for the per page setting.
|
* The options that are available for the per page setting.
|
||||||
|
*
|
||||||
|
* @var array<int>
|
||||||
*/
|
*/
|
||||||
#[Locked]
|
#[Locked]
|
||||||
public array $perPageOptions = [6, 12, 24, 50];
|
public array $perPageOptions = [6, 12, 24, 50];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The SPT versions filter value.
|
* The SPT versions filter value.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
*/
|
*/
|
||||||
#[Session]
|
#[Session]
|
||||||
#[Url]
|
#[Url]
|
||||||
@ -72,15 +79,15 @@ class Listing extends Component
|
|||||||
*/
|
*/
|
||||||
public function mount(): void
|
public function mount(): void
|
||||||
{
|
{
|
||||||
$this->activeSptVersions = $this->activeSptVersions ?? Cache::remember('active-spt-versions', 60 * 60, function () {
|
$this->activeSptVersions ??= Cache::remember('active-spt-versions', 60 * 60, fn (): Collection => SptVersion::getVersionsForLastThreeMinors());
|
||||||
return SptVersion::getVersionsForLastThreeMinors();
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->sptVersions = $this->sptVersions ?? $this->getDefaultSptVersions();
|
$this->sptVersions ??= $this->getDefaultSptVersions();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the default values for the SPT Versions filter.
|
* Get the default values for the SPT Versions filter.
|
||||||
|
*
|
||||||
|
* @return array<int, string>
|
||||||
*/
|
*/
|
||||||
protected function getDefaultSptVersions(): array
|
protected function getDefaultSptVersions(): array
|
||||||
{
|
{
|
||||||
@ -89,12 +96,12 @@ class Listing extends Component
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all patch versions of the latest minor SPT version.
|
* Get all patch versions of the latest minor SPT version.
|
||||||
|
*
|
||||||
|
* @return Collection<int, SptVersion>
|
||||||
*/
|
*/
|
||||||
public function getLatestMinorVersions(): Collection
|
public function getLatestMinorVersions(): Collection
|
||||||
{
|
{
|
||||||
return $this->activeSptVersions->filter(function (SptVersion $sptVersion) {
|
return $this->activeSptVersions->filter(fn (SptVersion $sptVersion): bool => $sptVersion->isLatestMinor());
|
||||||
return $sptVersion->isLatestMinor();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -112,11 +119,11 @@ class Listing extends Component
|
|||||||
'sptVersions' => $this->sptVersions,
|
'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.
|
* 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()) {
|
if ($lengthAwarePaginator->currentPage() > $lengthAwarePaginator->lastPage()) {
|
||||||
$this->redirectRoute('mods', ['page' => $mods->lastPage()]);
|
$this->redirectRoute('mods', ['page' => $lengthAwarePaginator->lastPage()]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,11 +176,11 @@ class Listing extends Component
|
|||||||
if ($this->query !== '') {
|
if ($this->query !== '') {
|
||||||
$count++;
|
$count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->featured !== 'include') {
|
if ($this->featured !== 'include') {
|
||||||
$count++;
|
$count++;
|
||||||
}
|
}
|
||||||
$count += count($this->sptVersions);
|
|
||||||
|
|
||||||
return $count;
|
return $count + count($this->sptVersions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Livewire\Profile;
|
namespace App\Livewire\Profile;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||||
use Illuminate\View\View;
|
use Illuminate\View\View;
|
||||||
use Livewire\Attributes\Locked;
|
use Livewire\Attributes\Locked;
|
||||||
use Livewire\Attributes\On;
|
use Livewire\Attributes\On;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class ManageOAuthConnections extends Component
|
class ManageOauthConnections extends Component
|
||||||
{
|
{
|
||||||
use AuthorizesRequests;
|
use AuthorizesRequests;
|
||||||
|
|
||||||
@ -16,33 +19,31 @@ class ManageOAuthConnections extends Component
|
|||||||
* Store the current user.
|
* Store the current user.
|
||||||
*/
|
*/
|
||||||
#[Locked]
|
#[Locked]
|
||||||
public $user;
|
public User $user;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Controls the confirmation modal visibility.
|
* Controls the confirmation modal visibility.
|
||||||
*/
|
*/
|
||||||
public $confirmingConnectionDeletion = false;
|
public bool $confirmingConnectionDeletion = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the ID of the connection to be deleted.
|
* Stores the ID of the connection to be deleted.
|
||||||
*/
|
*/
|
||||||
#[Locked]
|
#[Locked]
|
||||||
public $selectedConnectionId;
|
public ?string $selectedConnectionId = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the component by loading the user's OAuth connections.
|
* Initializes the component by loading the user's OAuth connections.
|
||||||
*/
|
*/
|
||||||
public function mount(): void
|
public function mount(): void
|
||||||
{
|
{
|
||||||
$this->setName('profile.manage-oauth-connections');
|
|
||||||
|
|
||||||
$this->user = auth()->user();
|
$this->user = auth()->user();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up the deletion confirmation.
|
* Sets up the deletion confirmation.
|
||||||
*/
|
*/
|
||||||
public function confirmConnectionDeletion($connectionId): void
|
public function confirmConnectionDeletion(string $connectionId): void
|
||||||
{
|
{
|
||||||
$this->confirmingConnectionDeletion = true;
|
$this->confirmingConnectionDeletion = true;
|
||||||
$this->selectedConnectionId = $connectionId;
|
$this->selectedConnectionId = $connectionId;
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Livewire\Profile;
|
namespace App\Livewire\Profile;
|
||||||
|
|
||||||
use App\Actions\Fortify\PasswordValidationRules;
|
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.
|
* without needing to provide their current password. This is useful for users that have been created using OAuth.
|
||||||
*/
|
*/
|
||||||
#[Override]
|
#[Override]
|
||||||
public function updatePassword(UpdatesUserPasswords $updater): void
|
public function updatePassword(UpdatesUserPasswords $updatesUserPasswords): void
|
||||||
{
|
{
|
||||||
$this->resetErrorBag();
|
$this->resetErrorBag();
|
||||||
|
|
||||||
$user = Auth::user();
|
$user = Auth::user();
|
||||||
|
|
||||||
if ($user->password !== null) {
|
if ($user->password !== null) {
|
||||||
parent::updatePassword($updater);
|
parent::updatePassword($updatesUserPasswords);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// User has a null password. Allow them to set a new password without their current password.
|
// User has a null password. Allow them to set a new password without their current password.
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Livewire\Profile;
|
namespace App\Livewire\Profile;
|
||||||
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
@ -7,11 +9,14 @@ use Illuminate\Support\Facades\Auth;
|
|||||||
use Laravel\Fortify\Contracts\UpdatesUserProfileInformation;
|
use Laravel\Fortify\Contracts\UpdatesUserProfileInformation;
|
||||||
use Laravel\Jetstream\Http\Livewire\UpdateProfileInformationForm;
|
use Laravel\Jetstream\Http\Livewire\UpdateProfileInformationForm;
|
||||||
use Livewire\Features\SupportRedirects\Redirector;
|
use Livewire\Features\SupportRedirects\Redirector;
|
||||||
|
use Override;
|
||||||
|
|
||||||
class UpdateProfileForm extends UpdateProfileInformationForm
|
class UpdateProfileForm extends UpdateProfileInformationForm
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The new cover photo for the user.
|
* The new cover photo for the user.
|
||||||
|
*
|
||||||
|
* @var string|null
|
||||||
*/
|
*/
|
||||||
public $cover;
|
public $cover;
|
||||||
|
|
||||||
@ -38,6 +43,7 @@ class UpdateProfileForm extends UpdateProfileInformationForm
|
|||||||
/**
|
/**
|
||||||
* Update the user's profile information.
|
* Update the user's profile information.
|
||||||
*/
|
*/
|
||||||
|
#[Override]
|
||||||
public function updateProfileInformation(UpdatesUserProfileInformation $updater): RedirectResponse|Redirector|null
|
public function updateProfileInformation(UpdatesUserProfileInformation $updater): RedirectResponse|Redirector|null
|
||||||
{
|
{
|
||||||
$this->resetErrorBag();
|
$this->resetErrorBag();
|
||||||
@ -51,7 +57,7 @@ class UpdateProfileForm extends UpdateProfileInformationForm
|
|||||||
])) : $this->state
|
])) : $this->state
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isset($this->photo) || isset($this->cover)) {
|
if ($this->photo !== null || $this->cover !== null) {
|
||||||
return redirect()->route('profile.show');
|
return redirect()->route('profile.show');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Livewire\User;
|
namespace App\Livewire\User;
|
||||||
|
|
||||||
use Illuminate\View\View;
|
use Illuminate\View\View;
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Livewire\User;
|
namespace App\Livewire\User;
|
||||||
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
@ -34,18 +36,6 @@ class FollowCard extends Component
|
|||||||
*/
|
*/
|
||||||
public string $dialogTitle;
|
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.
|
* 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.
|
* A collection of user IDs that the auth user follows.
|
||||||
|
*
|
||||||
|
* @var Collection<int, int>
|
||||||
*/
|
*/
|
||||||
#[Locked]
|
#[Locked]
|
||||||
public Collection $authFollowIds;
|
public Collection $authFollowIds;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The profile user's followers (or following).
|
* The profile user's followers (or following).
|
||||||
|
*
|
||||||
|
* @var Collection<int, User>
|
||||||
*/
|
*/
|
||||||
#[Locked]
|
#[Locked]
|
||||||
public Collection $followUsers;
|
public Collection $followUsers;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The events the component should listen for.
|
* The events the component should listen for.
|
||||||
|
*
|
||||||
|
* @var array<string, string>
|
||||||
*/
|
*/
|
||||||
protected $listeners = ['refreshComponent' => '$refresh'];
|
protected $listeners = ['refreshComponent' => '$refresh'];
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Livewire\User;
|
namespace App\Livewire\User;
|
||||||
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
@ -19,6 +21,8 @@ class FollowCards extends Component
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A collection of user IDs that the auth user follows.
|
* A collection of user IDs that the auth user follows.
|
||||||
|
*
|
||||||
|
* @var Collection<int, int>
|
||||||
*/
|
*/
|
||||||
#[Locked]
|
#[Locked]
|
||||||
public Collection $authFollowIds;
|
public Collection $authFollowIds;
|
||||||
|
@ -1,21 +1,40 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Models;
|
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\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
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
|
class License extends Model
|
||||||
{
|
{
|
||||||
|
/** @use HasFactory<LicenseFactory> */
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
use SoftDeletes;
|
use SoftDeletes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The relationship between a license and mod.
|
* The relationship between a license and mod.
|
||||||
*
|
*
|
||||||
* @return HasMany<Mod>
|
* @return HasMany<Mod, $this>
|
||||||
*/
|
*/
|
||||||
public function mods(): HasMany
|
public function mods(): HasMany
|
||||||
{
|
{
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use App\Http\Filters\V1\QueryFilter;
|
use App\Http\Filters\V1\QueryFilter;
|
||||||
use App\Models\Scopes\PublishedScope;
|
use App\Models\Scopes\PublishedScope;
|
||||||
use App\Traits\CanModerate;
|
use App\Traits\CanModerate;
|
||||||
|
use Database\Factories\ModFactory;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
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\HasMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Laravel\Scout\Searchable;
|
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
|
class Mod extends Model
|
||||||
{
|
{
|
||||||
use CanModerate;
|
use CanModerate;
|
||||||
|
/** @use HasFactory<ModFactory> */
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
use Searchable;
|
use Searchable;
|
||||||
use SoftDeletes;
|
use SoftDeletes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Post boot method to configure the model.
|
* Post boot method to configure the model.
|
||||||
*/
|
*/
|
||||||
|
#[Override]
|
||||||
protected static function booted(): void
|
protected static function booted(): void
|
||||||
{
|
{
|
||||||
static::addGlobalScope(new PublishedScope);
|
static::addGlobalScope(new PublishedScope);
|
||||||
@ -60,7 +96,7 @@ class Mod extends Model
|
|||||||
/**
|
/**
|
||||||
* The relationship between a mod and its users.
|
* The relationship between a mod and its users.
|
||||||
*
|
*
|
||||||
* @return BelongsToMany<User>
|
* @return BelongsToMany<User, $this>
|
||||||
*/
|
*/
|
||||||
public function users(): BelongsToMany
|
public function users(): BelongsToMany
|
||||||
{
|
{
|
||||||
@ -70,7 +106,7 @@ class Mod extends Model
|
|||||||
/**
|
/**
|
||||||
* The relationship between a mod and its license.
|
* The relationship between a mod and its license.
|
||||||
*
|
*
|
||||||
* @return BelongsTo<License, Mod>
|
* @return BelongsTo<License, $this>
|
||||||
*/
|
*/
|
||||||
public function license(): BelongsTo
|
public function license(): BelongsTo
|
||||||
{
|
{
|
||||||
@ -80,7 +116,7 @@ class Mod extends Model
|
|||||||
/**
|
/**
|
||||||
* The relationship between a mod and its last updated version.
|
* The relationship between a mod and its last updated version.
|
||||||
*
|
*
|
||||||
* @return HasOne<ModVersion>
|
* @return HasOne<ModVersion, $this>
|
||||||
*/
|
*/
|
||||||
public function latestUpdatedVersion(): HasOne
|
public function latestUpdatedVersion(): HasOne
|
||||||
{
|
{
|
||||||
@ -93,7 +129,7 @@ class Mod extends Model
|
|||||||
/**
|
/**
|
||||||
* The relationship between a mod and its versions.
|
* The relationship between a mod and its versions.
|
||||||
*
|
*
|
||||||
* @return HasMany<ModVersion>
|
* @return HasMany<ModVersion, $this>
|
||||||
*/
|
*/
|
||||||
public function versions(): HasMany
|
public function versions(): HasMany
|
||||||
{
|
{
|
||||||
@ -107,6 +143,8 @@ class Mod extends Model
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The data that is searchable by Scout.
|
* The data that is searchable by Scout.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
public function toSearchableArray(): array
|
public function toSearchableArray(): array
|
||||||
{
|
{
|
||||||
@ -122,9 +160,9 @@ class Mod extends Model
|
|||||||
'description' => $this->description,
|
'description' => $this->description,
|
||||||
'thumbnail' => $this->thumbnail,
|
'thumbnail' => $this->thumbnail,
|
||||||
'featured' => $this->featured,
|
'featured' => $this->featured,
|
||||||
'created_at' => strtotime($this->created_at),
|
'created_at' => $this->created_at->timestamp,
|
||||||
'updated_at' => strtotime($this->updated_at),
|
'updated_at' => $this->updated_at->timestamp,
|
||||||
'published_at' => strtotime($this->published_at),
|
'published_at' => $this->published_at->timestamp,
|
||||||
'latestVersion' => $this->latestVersion->latestSptVersion->version_formatted,
|
'latestVersion' => $this->latestVersion->latestSptVersion->version_formatted,
|
||||||
'latestVersionColorClass' => $this->latestVersion->latestSptVersion->color_class,
|
'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.
|
// Ensure the latest SPT version is within the last three minor versions.
|
||||||
$activeSptVersions = Cache::remember('active-spt-versions', 60 * 60, function () {
|
$activeSptVersions = Cache::remember('active-spt-versions', 60 * 60, fn (): Collection => SptVersion::getVersionsForLastThreeMinors());
|
||||||
return SptVersion::getVersionsForLastThreeMinors();
|
|
||||||
});
|
|
||||||
if (! in_array($this->latestVersion->latestSptVersion->version, $activeSptVersions->pluck('version')->toArray())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// All conditions are met; the mod should be searchable.
|
// 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.
|
* The relationship between a mod and its latest version.
|
||||||
*
|
*
|
||||||
* @return HasOne<ModVersion>
|
* @return HasOne<ModVersion, $this>
|
||||||
*/
|
*/
|
||||||
public function latestVersion(): HasOne
|
public function latestVersion(): HasOne
|
||||||
{
|
{
|
||||||
@ -193,14 +226,14 @@ class Mod extends Model
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Build the URL to the mod's thumbnail.
|
* Build the URL to the mod's thumbnail.
|
||||||
|
*
|
||||||
|
* @return Attribute<string, never>
|
||||||
*/
|
*/
|
||||||
public function thumbnailUrl(): Attribute
|
public function thumbnailUrl(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::get(function (): string {
|
return Attribute::get(fn (): string => $this->thumbnail
|
||||||
return $this->thumbnail
|
? Storage::disk($this->thumbnailDisk())->url($this->thumbnail)
|
||||||
? Storage::disk($this->thumbnailDisk())->url($this->thumbnail)
|
: '');
|
||||||
: '';
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -216,10 +249,13 @@ class Mod extends Model
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Scope a query by applying QueryFilter filters.
|
* 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
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Models;
|
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\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
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
|
class ModDependency extends Model
|
||||||
{
|
{
|
||||||
|
/** @use HasFactory<ModDependencyFactory> */
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The relationship between the mod dependency and the mod version.
|
* The relationship between the mod dependency and the mod version.
|
||||||
*
|
*
|
||||||
* @return BelongsTo<ModVersion, ModDependency>
|
* @return BelongsTo<ModVersion, $this>
|
||||||
*/
|
*/
|
||||||
public function modVersion(): BelongsTo
|
public function modVersion(): BelongsTo
|
||||||
{
|
{
|
||||||
@ -24,7 +43,7 @@ class ModDependency extends Model
|
|||||||
/**
|
/**
|
||||||
* The relationship between the mod dependency and the resolved dependency.
|
* The relationship between the mod dependency and the resolved dependency.
|
||||||
*
|
*
|
||||||
* @return HasMany<ModResolvedDependency>
|
* @return HasMany<ModResolvedDependency, $this>
|
||||||
*/
|
*/
|
||||||
public function resolvedDependencies(): HasMany
|
public function resolvedDependencies(): HasMany
|
||||||
{
|
{
|
||||||
@ -35,7 +54,7 @@ class ModDependency extends Model
|
|||||||
/**
|
/**
|
||||||
* The relationship between the mod dependency and the dependent mod.
|
* The relationship between the mod dependency and the dependent mod.
|
||||||
*
|
*
|
||||||
* @return BelongsTo<Mod, ModDependency>
|
* @return BelongsTo<Mod, $this>
|
||||||
*/
|
*/
|
||||||
public function dependentMod(): BelongsTo
|
public function dependentMod(): BelongsTo
|
||||||
{
|
{
|
||||||
|
@ -1,16 +1,32 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
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
|
class ModResolvedDependency extends Model
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The relationship between the resolved dependency and the mod version.
|
* The relationship between the resolved dependency and the mod version.
|
||||||
*
|
*
|
||||||
* @return BelongsTo<ModVersion, ModResolvedDependency>
|
* @return BelongsTo<ModVersion, $this>
|
||||||
*/
|
*/
|
||||||
public function modVersion(): BelongsTo
|
public function modVersion(): BelongsTo
|
||||||
{
|
{
|
||||||
@ -20,7 +36,7 @@ class ModResolvedDependency extends Model
|
|||||||
/**
|
/**
|
||||||
* The relationship between the resolved dependency and the dependency.
|
* The relationship between the resolved dependency and the dependency.
|
||||||
*
|
*
|
||||||
* @return BelongsTo<ModDependency, ModResolvedDependency>
|
* @return BelongsTo<ModDependency, $this>
|
||||||
*/
|
*/
|
||||||
public function dependency(): BelongsTo
|
public function dependency(): BelongsTo
|
||||||
{
|
{
|
||||||
@ -30,7 +46,7 @@ class ModResolvedDependency extends Model
|
|||||||
/**
|
/**
|
||||||
* The relationship between the resolved dependency and the resolved mod version.
|
* The relationship between the resolved dependency and the resolved mod version.
|
||||||
*
|
*
|
||||||
* @return BelongsTo<ModVersion, ModResolvedDependency>
|
* @return BelongsTo<ModVersion, $this>
|
||||||
*/
|
*/
|
||||||
public function resolvedModVersion(): BelongsTo
|
public function resolvedModVersion(): BelongsTo
|
||||||
{
|
{
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use App\Exceptions\InvalidVersionNumberException;
|
use App\Exceptions\InvalidVersionNumberException;
|
||||||
use App\Models\Scopes\PublishedScope;
|
use App\Models\Scopes\PublishedScope;
|
||||||
use App\Support\Version;
|
use App\Support\Version;
|
||||||
use App\Traits\CanModerate;
|
use App\Traits\CanModerate;
|
||||||
|
use Database\Factories\ModVersionFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
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\HasMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasOneThrough;
|
use Illuminate\Database\Eloquent\Relations\HasOneThrough;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
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
|
class ModVersion extends Model
|
||||||
{
|
{
|
||||||
use CanModerate;
|
use CanModerate;
|
||||||
|
/** @use HasFactory<ModVersionFactory> */
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
use SoftDeletes;
|
use SoftDeletes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the parent mod's updated_at timestamp when the mod version is updated.
|
* Update the parent mod's updated_at timestamp when the mod version is updated.
|
||||||
|
*
|
||||||
|
* @var string[]
|
||||||
*/
|
*/
|
||||||
protected $touches = ['mod'];
|
protected $touches = ['mod'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Post boot method to configure the model.
|
* Post boot method to configure the model.
|
||||||
*/
|
*/
|
||||||
|
#[Override]
|
||||||
protected static function booted(): void
|
protected static function booted(): void
|
||||||
{
|
{
|
||||||
static::addGlobalScope(new PublishedScope);
|
static::addGlobalScope(new PublishedScope);
|
||||||
|
|
||||||
static::saving(function (ModVersion $model) {
|
static::saving(function (ModVersion $modVersion): void {
|
||||||
// Extract the version sections from the version string.
|
// Extract the version sections from the version string.
|
||||||
try {
|
try {
|
||||||
$version = new Version($model->version);
|
$version = new Version($modVersion->version);
|
||||||
|
|
||||||
$model->version_major = $version->getMajor();
|
$modVersion->version_major = $version->getMajor();
|
||||||
$model->version_minor = $version->getMinor();
|
$modVersion->version_minor = $version->getMinor();
|
||||||
$model->version_patch = $version->getPatch();
|
$modVersion->version_patch = $version->getPatch();
|
||||||
$model->version_pre_release = $version->getPreRelease();
|
$modVersion->version_pre_release = $version->getPreRelease();
|
||||||
} catch (InvalidVersionNumberException $e) {
|
} catch (InvalidVersionNumberException) {
|
||||||
$model->version_major = 0;
|
$modVersion->version_major = 0;
|
||||||
$model->version_minor = 0;
|
$modVersion->version_minor = 0;
|
||||||
$model->version_patch = 0;
|
$modVersion->version_patch = 0;
|
||||||
$model->version_pre_release = '';
|
$modVersion->version_pre_release = '';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -53,7 +92,7 @@ class ModVersion extends Model
|
|||||||
/**
|
/**
|
||||||
* The relationship between a mod version and mod.
|
* The relationship between a mod version and mod.
|
||||||
*
|
*
|
||||||
* @return BelongsTo<Mod, ModVersion>
|
* @return BelongsTo<Mod, $this>
|
||||||
*/
|
*/
|
||||||
public function mod(): BelongsTo
|
public function mod(): BelongsTo
|
||||||
{
|
{
|
||||||
@ -63,7 +102,7 @@ class ModVersion extends Model
|
|||||||
/**
|
/**
|
||||||
* The relationship between a mod version and its dependencies.
|
* The relationship between a mod version and its dependencies.
|
||||||
*
|
*
|
||||||
* @return HasMany<ModDependency>
|
* @return HasMany<ModDependency, $this>
|
||||||
*/
|
*/
|
||||||
public function dependencies(): HasMany
|
public function dependencies(): HasMany
|
||||||
{
|
{
|
||||||
@ -74,7 +113,7 @@ class ModVersion extends Model
|
|||||||
/**
|
/**
|
||||||
* The relationship between a mod version and its resolved dependencies.
|
* The relationship between a mod version and its resolved dependencies.
|
||||||
*
|
*
|
||||||
* @return BelongsToMany<ModVersion>
|
* @return BelongsToMany<ModVersion, $this>
|
||||||
*/
|
*/
|
||||||
public function resolvedDependencies(): BelongsToMany
|
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.
|
* 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
|
public function latestResolvedDependencies(): BelongsToMany
|
||||||
{
|
{
|
||||||
return $this->belongsToMany(ModVersion::class, 'mod_resolved_dependencies', 'mod_version_id', 'resolved_mod_version_id')
|
return $this->belongsToMany(ModVersion::class, 'mod_resolved_dependencies', 'mod_version_id', 'resolved_mod_version_id')
|
||||||
->withPivot('dependency_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')
|
$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)');
|
->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.
|
* The relationship between a mod version and its latest SPT version.
|
||||||
*
|
*
|
||||||
* @return HasOneThrough<SptVersion>
|
* @return HasOneThrough<SptVersion, ModVersionSptVersion, $this>
|
||||||
*/
|
*/
|
||||||
public function latestSptVersion(): HasOneThrough
|
public function latestSptVersion(): HasOneThrough
|
||||||
{
|
{
|
||||||
@ -118,7 +157,7 @@ class ModVersion extends Model
|
|||||||
/**
|
/**
|
||||||
* The relationship between a mod version and its SPT versions.
|
* The relationship between a mod version and its SPT versions.
|
||||||
*
|
*
|
||||||
* @return BelongsToMany<SptVersion>
|
* @return BelongsToMany<SptVersion, $this>
|
||||||
*/
|
*/
|
||||||
public function sptVersions(): BelongsToMany
|
public function sptVersions(): BelongsToMany
|
||||||
{
|
{
|
||||||
|
@ -1,9 +1,20 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Relations\Pivot;
|
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
|
class ModVersionSptVersion extends Pivot
|
||||||
{
|
{
|
||||||
public $incrementing = true;
|
public $incrementing = true;
|
||||||
|
@ -1,17 +1,44 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Database\Factories\OAuthConnectionFactory;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
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
|
class OAuthConnection extends Model
|
||||||
{
|
{
|
||||||
|
/** @use HasFactory<OAuthConnectionFactory> */
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
protected $table = 'oauth_connections';
|
protected $table = 'oauth_connections';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The relationship between the OAuth connection and the user.
|
||||||
|
*
|
||||||
|
* @return BelongsTo<User, $this>
|
||||||
|
*/
|
||||||
public function user(): BelongsTo
|
public function user(): BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(User::class);
|
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
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Models\Scopes;
|
namespace App\Models\Scopes;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
@ -10,6 +12,8 @@ class PublishedScope implements Scope
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Apply the scope to a given Eloquent query builder.
|
* Apply the scope to a given Eloquent query builder.
|
||||||
|
*
|
||||||
|
* @param Builder<Model> $builder
|
||||||
*/
|
*/
|
||||||
public function apply(Builder $builder, Model $model): void
|
public function apply(Builder $builder, Model $model): void
|
||||||
{
|
{
|
||||||
|
@ -1,23 +1,52 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use App\Exceptions\InvalidVersionNumberException;
|
use App\Exceptions\InvalidVersionNumberException;
|
||||||
use App\Support\Version;
|
use App\Support\Version;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Database\Factories\SptVersionFactory;
|
||||||
use Illuminate\Database\Eloquent\Collection;
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Illuminate\Support\Facades\Cache;
|
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
|
class SptVersion extends Model
|
||||||
{
|
{
|
||||||
|
/** @use HasFactory<SptVersionFactory> */
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
use SoftDeletes;
|
use SoftDeletes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all versions for the last three minor versions.
|
* Get all versions for the last three minor versions.
|
||||||
|
*
|
||||||
|
* @return Collection<int, $this>
|
||||||
*/
|
*/
|
||||||
public static function getVersionsForLastThreeMinors(): Collection
|
public static function getVersionsForLastThreeMinors(): Collection
|
||||||
{
|
{
|
||||||
@ -28,7 +57,7 @@ class SptVersion extends Model
|
|||||||
$minorVersions = array_column($lastThreeMinorVersions, 'minor');
|
$minorVersions = array_column($lastThreeMinorVersions, 'minor');
|
||||||
|
|
||||||
// Fetch all versions for the last three minor versions with mod count.
|
// 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_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('mod_versions', 'mod_version_spt_version.mod_version_id', '=', 'mod_versions.id')
|
||||||
->join('mods', 'mod_versions.mod_id', '=', 'mods.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).
|
* Get the last three minor versions (major.minor format).
|
||||||
|
*
|
||||||
|
* @return array<int, array{major: int, minor: int}>
|
||||||
*/
|
*/
|
||||||
public static function getLastThreeMinorVersions(): array
|
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')
|
->where('version', '!=', '0.0.0')
|
||||||
->groupBy('version_major', 'version_minor')
|
->groupBy('version_major', 'version_minor')
|
||||||
->orderByDesc('version_major')
|
->orderByDesc('version_major')
|
||||||
->orderByDesc('version_minor')
|
->orderByDesc('version_minor')
|
||||||
->limit(3)
|
->limit(3)
|
||||||
->get()
|
->get()
|
||||||
->map(function (SptVersion $version) {
|
->map(fn (SptVersion $sptVersion): array => [
|
||||||
return [
|
'major' => (int) $sptVersion->version_major,
|
||||||
'major' => (int) $version->version_major,
|
'minor' => (int) $sptVersion->version_minor,
|
||||||
'minor' => (int) $version->version_minor,
|
])
|
||||||
];
|
|
||||||
})
|
|
||||||
->toArray();
|
->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract the version sections from the version string.
|
* 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
|
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.
|
// 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);
|
preg_match('/^(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:-([a-zA-Z0-9]+))?$/', $version, $matches);
|
||||||
|
|
||||||
if (! $matches) {
|
throw_if($matches === [], new InvalidVersionNumberException('Invalid SPT version number: '.$version));
|
||||||
throw new InvalidVersionNumberException('Invalid SPT version number: '.$version);
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'major' => $matches[1] ?? 0,
|
'major' => $matches[1] ?? 0,
|
||||||
@ -92,22 +121,23 @@ class SptVersion extends Model
|
|||||||
/**
|
/**
|
||||||
* Called when the model is booted.
|
* Called when the model is booted.
|
||||||
*/
|
*/
|
||||||
|
#[Override]
|
||||||
protected static function booted(): void
|
protected static function booted(): void
|
||||||
{
|
{
|
||||||
static::saving(function (SptVersion $model) {
|
static::saving(function (SptVersion $sptVersion): void {
|
||||||
// Extract the version sections from the version string.
|
// Extract the version sections from the version string.
|
||||||
try {
|
try {
|
||||||
$version = new Version($model->version);
|
$version = new Version($sptVersion->version);
|
||||||
|
|
||||||
$model->version_major = $version->getMajor();
|
$sptVersion->version_major = $version->getMajor();
|
||||||
$model->version_minor = $version->getMinor();
|
$sptVersion->version_minor = $version->getMinor();
|
||||||
$model->version_patch = $version->getPatch();
|
$sptVersion->version_patch = $version->getPatch();
|
||||||
$model->version_pre_release = $version->getPreRelease();
|
$sptVersion->version_pre_release = $version->getPreRelease();
|
||||||
} catch (InvalidVersionNumberException $e) {
|
} catch (InvalidVersionNumberException) {
|
||||||
$model->version_major = 0;
|
$sptVersion->version_major = 0;
|
||||||
$model->version_minor = 0;
|
$sptVersion->version_minor = 0;
|
||||||
$model->version_patch = 0;
|
$sptVersion->version_patch = 0;
|
||||||
$model->version_pre_release = '';
|
$sptVersion->version_pre_release = '';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -128,7 +158,7 @@ class SptVersion extends Model
|
|||||||
/**
|
/**
|
||||||
* The relationship between an SPT version and mod version.
|
* The relationship between an SPT version and mod version.
|
||||||
*
|
*
|
||||||
* @return BelongsToMany<ModVersion>
|
* @return BelongsToMany<ModVersion, $this>
|
||||||
*/
|
*/
|
||||||
public function modVersions(): BelongsToMany
|
public function modVersions(): BelongsToMany
|
||||||
{
|
{
|
||||||
@ -152,7 +182,7 @@ class SptVersion extends Model
|
|||||||
{
|
{
|
||||||
$latestVersion = self::getLatest();
|
$latestVersion = self::getLatest();
|
||||||
|
|
||||||
if (! $latestVersion) {
|
if (! $latestVersion instanceof \App\Models\SptVersion) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,11 +196,11 @@ class SptVersion extends Model
|
|||||||
*/
|
*/
|
||||||
public static function getLatest(): ?SptVersion
|
public static function getLatest(): ?SptVersion
|
||||||
{
|
{
|
||||||
return Cache::remember('latest_spt_version', 300, function () {
|
return Cache::remember('latest_spt_version', 300, fn () => \App\Models\SptVersion::query()->select(['version', 'version_major', 'version_minor', 'version_patch', 'version_pre_release'])
|
||||||
return SptVersion::select(['version', 'version_major', 'version_minor', 'version_patch', 'version_pre_release'])
|
->orderByDesc('version_major')
|
||||||
->orderByDesc('version')
|
->orderByDesc('version_minor')
|
||||||
->first();
|
->orderByDesc('version_patch')
|
||||||
});
|
->first());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,15 +1,21 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use App\Http\Filters\V1\QueryFilter;
|
use App\Http\Filters\V1\QueryFilter;
|
||||||
use App\Notifications\ResetPassword;
|
use App\Notifications\ResetPassword;
|
||||||
use App\Notifications\VerifyEmail;
|
use App\Notifications\VerifyEmail;
|
||||||
use App\Traits\HasCoverPhoto;
|
use App\Traits\HasCoverPhoto;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Database\Factories\UserFactory;
|
||||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
@ -21,13 +27,39 @@ use Laravel\Jetstream\HasProfilePhoto;
|
|||||||
use Laravel\Sanctum\HasApiTokens;
|
use Laravel\Sanctum\HasApiTokens;
|
||||||
use Laravel\Scout\Searchable;
|
use Laravel\Scout\Searchable;
|
||||||
use Mchev\Banhammer\Traits\Bannable;
|
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
|
class User extends Authenticatable implements MustVerifyEmail
|
||||||
{
|
{
|
||||||
use Bannable;
|
use Bannable;
|
||||||
use HasApiTokens;
|
use HasApiTokens;
|
||||||
use HasCoverPhoto;
|
use HasCoverPhoto;
|
||||||
|
|
||||||
|
/** @use HasFactory<UserFactory> */
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
use HasProfilePhoto;
|
use HasProfilePhoto;
|
||||||
use Notifiable;
|
use Notifiable;
|
||||||
use Searchable;
|
use Searchable;
|
||||||
@ -55,7 +87,7 @@ class User extends Authenticatable implements MustVerifyEmail
|
|||||||
/**
|
/**
|
||||||
* The relationship between a user and their mods.
|
* The relationship between a user and their mods.
|
||||||
*
|
*
|
||||||
* @return BelongsToMany<Mod>
|
* @return BelongsToMany<Mod, $this>
|
||||||
*/
|
*/
|
||||||
public function mods(): BelongsToMany
|
public function mods(): BelongsToMany
|
||||||
{
|
{
|
||||||
@ -65,7 +97,7 @@ class User extends Authenticatable implements MustVerifyEmail
|
|||||||
/**
|
/**
|
||||||
* The relationship between a user and users that follow them.
|
* The relationship between a user and users that follow them.
|
||||||
*
|
*
|
||||||
* @return BelongsToMany<User>
|
* @return BelongsToMany<User, $this>
|
||||||
*/
|
*/
|
||||||
public function followers(): BelongsToMany
|
public function followers(): BelongsToMany
|
||||||
{
|
{
|
||||||
@ -91,7 +123,7 @@ class User extends Authenticatable implements MustVerifyEmail
|
|||||||
/**
|
/**
|
||||||
* The relationship between a user and users they follow.
|
* The relationship between a user and users they follow.
|
||||||
*
|
*
|
||||||
* @return BelongsToMany<User>
|
* @return BelongsToMany<User, $this>
|
||||||
*/
|
*/
|
||||||
public function following(): BelongsToMany
|
public function following(): BelongsToMany
|
||||||
{
|
{
|
||||||
@ -123,6 +155,8 @@ class User extends Authenticatable implements MustVerifyEmail
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The data that is searchable by Scout.
|
* The data that is searchable by Scout.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
public function toSearchableArray(): array
|
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.
|
* 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));
|
$this->notify(new ResetPassword($token));
|
||||||
}
|
}
|
||||||
@ -204,9 +238,9 @@ class User extends Authenticatable implements MustVerifyEmail
|
|||||||
/**
|
/**
|
||||||
* Assign a role to the user.
|
* 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();
|
return $this->save();
|
||||||
}
|
}
|
||||||
@ -214,7 +248,7 @@ class User extends Authenticatable implements MustVerifyEmail
|
|||||||
/**
|
/**
|
||||||
* The relationship between a user and their role.
|
* The relationship between a user and their role.
|
||||||
*
|
*
|
||||||
* @return BelongsTo<UserRole, User>
|
* @return BelongsTo<UserRole, $this>
|
||||||
*/
|
*/
|
||||||
public function role(): BelongsTo
|
public function role(): BelongsTo
|
||||||
{
|
{
|
||||||
@ -223,14 +257,19 @@ class User extends Authenticatable implements MustVerifyEmail
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Scope a query by applying QueryFilter filters.
|
* 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.
|
* The relationship between a user and their OAuth providers.
|
||||||
|
*
|
||||||
|
* @return HasMany<OAuthConnection, $this>
|
||||||
*/
|
*/
|
||||||
public function oAuthConnections(): HasMany
|
public function oAuthConnections(): HasMany
|
||||||
{
|
{
|
||||||
@ -239,6 +278,8 @@ class User extends Authenticatable implements MustVerifyEmail
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle the about default value if empty. Thanks, MySQL!
|
* Handle the about default value if empty. Thanks, MySQL!
|
||||||
|
*
|
||||||
|
* @return Attribute<string[], never>
|
||||||
*/
|
*/
|
||||||
protected function about(): Attribute
|
protected function about(): Attribute
|
||||||
{
|
{
|
||||||
|
@ -1,19 +1,37 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Models;
|
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\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
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
|
class UserRole extends Model
|
||||||
{
|
{
|
||||||
|
/** @use HasFactory<UserRoleFactory> */
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The relationship between a user role and users.
|
* The relationship between a user role and users.
|
||||||
*
|
*
|
||||||
* @return HasMany<User>
|
* @return HasMany<User, $this>
|
||||||
*/
|
*/
|
||||||
public function users(): HasMany
|
public function users(): HasMany
|
||||||
{
|
{
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Notifications;
|
namespace App\Notifications;
|
||||||
|
|
||||||
use Illuminate\Auth\Notifications\ResetPassword as OriginalResetPassword;
|
use Illuminate\Auth\Notifications\ResetPassword as OriginalResetPassword;
|
||||||
@ -20,6 +22,8 @@ class ResetPassword extends OriginalResetPassword implements ShouldQueue
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the array representation of the notification.
|
* Get the array representation of the notification.
|
||||||
|
*
|
||||||
|
* @return array<int, mixed>
|
||||||
*/
|
*/
|
||||||
public function toArray(object $notifiable): array
|
public function toArray(object $notifiable): array
|
||||||
{
|
{
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Notifications;
|
namespace App\Notifications;
|
||||||
|
|
||||||
use Illuminate\Auth\Notifications\VerifyEmail as OriginalVerifyEmail;
|
use Illuminate\Auth\Notifications\VerifyEmail as OriginalVerifyEmail;
|
||||||
@ -15,6 +17,8 @@ class VerifyEmail extends OriginalVerifyEmail implements ShouldQueue
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the array representation of the notification.
|
* Get the array representation of the notification.
|
||||||
|
*
|
||||||
|
* @return array<int, mixed>
|
||||||
*/
|
*/
|
||||||
public function toArray(object $notifiable): array
|
public function toArray(object $notifiable): array
|
||||||
{
|
{
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Observers;
|
namespace App\Observers;
|
||||||
|
|
||||||
use App\Models\ModDependency;
|
use App\Models\ModDependency;
|
||||||
@ -7,12 +9,7 @@ use App\Services\DependencyVersionService;
|
|||||||
|
|
||||||
class ModDependencyObserver
|
class ModDependencyObserver
|
||||||
{
|
{
|
||||||
protected DependencyVersionService $dependencyVersionService;
|
public function __construct(protected DependencyVersionService $dependencyVersionService) {}
|
||||||
|
|
||||||
public function __construct(DependencyVersionService $dependencyVersionService)
|
|
||||||
{
|
|
||||||
$this->dependencyVersionService = $dependencyVersionService;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle the ModDependency "saved" event.
|
* Handle the ModDependency "saved" event.
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Observers;
|
namespace App\Observers;
|
||||||
|
|
||||||
use App\Models\Mod;
|
use App\Models\Mod;
|
||||||
@ -7,13 +9,7 @@ use App\Services\DependencyVersionService;
|
|||||||
|
|
||||||
class ModObserver
|
class ModObserver
|
||||||
{
|
{
|
||||||
protected DependencyVersionService $dependencyVersionService;
|
public function __construct(protected DependencyVersionService $dependencyVersionService) {}
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
DependencyVersionService $dependencyVersionService,
|
|
||||||
) {
|
|
||||||
$this->dependencyVersionService = $dependencyVersionService;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle the Mod "saved" event.
|
* Handle the Mod "saved" event.
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Observers;
|
namespace App\Observers;
|
||||||
|
|
||||||
use App\Models\ModVersion;
|
use App\Models\ModVersion;
|
||||||
@ -8,17 +10,7 @@ use App\Services\SptVersionService;
|
|||||||
|
|
||||||
class ModVersionObserver
|
class ModVersionObserver
|
||||||
{
|
{
|
||||||
protected DependencyVersionService $dependencyVersionService;
|
public function __construct(protected DependencyVersionService $dependencyVersionService, protected SptVersionService $sptVersionService) {}
|
||||||
|
|
||||||
protected SptVersionService $sptVersionService;
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
DependencyVersionService $dependencyVersionService,
|
|
||||||
SptVersionService $sptVersionService,
|
|
||||||
) {
|
|
||||||
$this->dependencyVersionService = $dependencyVersionService;
|
|
||||||
$this->sptVersionService = $sptVersionService;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle the ModVersion "saved" event.
|
* Handle the ModVersion "saved" event.
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Observers;
|
namespace App\Observers;
|
||||||
|
|
||||||
use App\Models\ModVersion;
|
use App\Models\ModVersion;
|
||||||
@ -7,12 +9,7 @@ use App\Services\SptVersionService;
|
|||||||
|
|
||||||
class SptVersionObserver
|
class SptVersionObserver
|
||||||
{
|
{
|
||||||
protected SptVersionService $sptVersionService;
|
public function __construct(protected SptVersionService $sptVersionService) {}
|
||||||
|
|
||||||
public function __construct(SptVersionService $sptVersionService)
|
|
||||||
{
|
|
||||||
$this->sptVersionService = $sptVersionService;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle the SptVersion "saved" event.
|
* Handle the SptVersion "saved" event.
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Policies;
|
namespace App\Policies;
|
||||||
|
|
||||||
use App\Models\Mod;
|
use App\Models\Mod;
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Policies;
|
namespace App\Policies;
|
||||||
|
|
||||||
use App\Models\ModVersion;
|
use App\Models\ModVersion;
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Policies;
|
namespace App\Policies;
|
||||||
|
|
||||||
use App\Models\OAuthConnection;
|
use App\Models\OAuthConnection;
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Policies;
|
namespace App\Policies;
|
||||||
|
|
||||||
use App\Models\User;
|
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