mirror of
https://github.com/sp-tarkov/forge.git
synced 2025-02-12 20:20:41 -05:00
API Updates
Brings the API in close sync to the rest of the site. - Adds resources for License, UserRole, and ModVersion models - Adds filtering on attribute data - The `includes` data is now disabled by default and available conditionally
This commit is contained in:
parent
65e416e4d9
commit
3a334033fe
@ -7,7 +7,11 @@ use Illuminate\Support\Str;
|
||||
|
||||
class ApiController extends Controller
|
||||
{
|
||||
public static function shouldInclude(string $relationship): bool
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public static function shouldInclude(string|array $relationships): bool
|
||||
{
|
||||
$param = request()->get('include');
|
||||
|
||||
@ -17,6 +21,16 @@ class ApiController extends Controller
|
||||
|
||||
$includeValues = explode(',', Str::lower($param));
|
||||
|
||||
return in_array(Str::lower($relationship), $includeValues);
|
||||
if (is_array($relationships)) {
|
||||
foreach ($relationships as $relationship) {
|
||||
if (in_array(Str::lower($relationship), $includeValues)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array(Str::lower($relationships), $includeValues);
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers\Api\V0;
|
||||
|
||||
use App\Http\Filters\V1\ModFilter;
|
||||
use App\Http\Requests\Api\V0\StoreModRequest;
|
||||
use App\Http\Requests\Api\V0\UpdateModRequest;
|
||||
use App\Http\Resources\Api\V0\ModResource;
|
||||
@ -12,9 +13,9 @@ class ModController extends ApiController
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index()
|
||||
public function index(ModFilter $filters)
|
||||
{
|
||||
return ModResource::collection(Mod::paginate());
|
||||
return ModResource::collection(Mod::filter($filters)->paginate());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers\Api\V0;
|
||||
|
||||
use App\Http\Filters\V1\UserFilter;
|
||||
use App\Http\Requests\Api\V0\StoreUserRequest;
|
||||
use App\Http\Requests\Api\V0\UpdateUserRequest;
|
||||
use App\Http\Resources\Api\V0\UserResource;
|
||||
@ -12,9 +13,9 @@ class UsersController extends ApiController
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index()
|
||||
public function index(UserFilter $filters)
|
||||
{
|
||||
return UserResource::collection(User::paginate());
|
||||
return UserResource::collection(User::filter($filters)->paginate());
|
||||
}
|
||||
|
||||
/**
|
||||
|
116
app/Http/Filters/V1/ModFilter.php
Normal file
116
app/Http/Filters/V1/ModFilter.php
Normal file
@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Filters\V1;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class ModFilter extends QueryFilter
|
||||
{
|
||||
// TODO: Many of these are repeated across UserFilter and ModFilter. Consider refactoring into a shared trait.
|
||||
// Also, consider using common filter types and making the field names dynamic.
|
||||
|
||||
public function name(string $value): Builder
|
||||
{
|
||||
// The API handles the wildcard character as an asterisk (*), but the database uses the percentage sign (%).
|
||||
$like = Str::replace('*', '%', $value);
|
||||
|
||||
return $this->builder->where('name', 'like', $like);
|
||||
}
|
||||
|
||||
public function slug(string $value): Builder
|
||||
{
|
||||
// The API handles the wildcard character as an asterisk (*), but the database uses the percentage sign (%).
|
||||
$like = Str::replace('*', '%', $value);
|
||||
|
||||
return $this->builder->where('slug', 'like', $like);
|
||||
}
|
||||
|
||||
public function teaser(string $value): Builder
|
||||
{
|
||||
// The API handles the wildcard character as an asterisk (*), but the database uses the percentage sign (%).
|
||||
$like = Str::replace('*', '%', $value);
|
||||
|
||||
return $this->builder->where('teaser', 'like', $like);
|
||||
}
|
||||
|
||||
public function source_code_link(string $value): Builder
|
||||
{
|
||||
// The API handles the wildcard character as an asterisk (*), but the database uses the percentage sign (%).
|
||||
$like = Str::replace('*', '%', $value);
|
||||
|
||||
return $this->builder->where('source_code_link', 'like', $like);
|
||||
}
|
||||
|
||||
public function created_at(string $value): Builder
|
||||
{
|
||||
// The API allows for a range of dates to be passed as a comma-separated list.
|
||||
$dates = explode(',', $value);
|
||||
if (count($dates) > 1) {
|
||||
return $this->builder->whereBetween('created_at', $dates);
|
||||
}
|
||||
|
||||
return $this->builder->whereDate('created_at', $value);
|
||||
}
|
||||
|
||||
public function updated_at(string $value): Builder
|
||||
{
|
||||
// The API allows for a range of dates to be passed as a comma-separated list.
|
||||
$dates = explode(',', $value);
|
||||
if (count($dates) > 1) {
|
||||
return $this->builder->whereBetween('updated_at', $dates);
|
||||
}
|
||||
|
||||
return $this->builder->whereDate('updated_at', $value);
|
||||
}
|
||||
|
||||
public function published_at(string $value): Builder
|
||||
{
|
||||
// The API allows for a range of dates to be passed as a comma-separated list.
|
||||
$dates = explode(',', $value);
|
||||
if (count($dates) > 1) {
|
||||
return $this->builder->whereBetween('published_at', $dates);
|
||||
}
|
||||
|
||||
return $this->builder->whereDate('published_at', $value);
|
||||
}
|
||||
|
||||
public function featured(string $value): Builder
|
||||
{
|
||||
// We need to convert the string user input to a boolean, or null if it's not a valid "truthy/falsy" value.
|
||||
$value = filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
|
||||
|
||||
// This column is not nullable.
|
||||
if ($value === null) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
return $this->builder->where('featured', $value);
|
||||
}
|
||||
|
||||
public function contains_ads(string $value): Builder
|
||||
{
|
||||
// We need to convert the string user input to a boolean, or null if it's not a valid "truthy/falsy" value.
|
||||
$value = filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
|
||||
|
||||
// This column is not nullable.
|
||||
if ($value === null) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
return $this->builder->where('contains_ads', $value);
|
||||
}
|
||||
|
||||
public function contains_ai_content(string $value): Builder
|
||||
{
|
||||
// We need to convert the string user input to a boolean, or null if it's not a valid "truthy/falsy" value.
|
||||
$value = filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
|
||||
|
||||
// This column is not nullable.
|
||||
if ($value === null) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
return $this->builder->where('contains_ai_content', $value);
|
||||
}
|
||||
}
|
37
app/Http/Filters/V1/QueryFilter.php
Normal file
37
app/Http/Filters/V1/QueryFilter.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Filters\V1;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
abstract class QueryFilter
|
||||
{
|
||||
protected Builder $builder;
|
||||
|
||||
public function __construct(protected Request $request) {}
|
||||
|
||||
public function apply(Builder $builder): Builder
|
||||
{
|
||||
$this->builder = $builder;
|
||||
|
||||
foreach ($this->request->all() as $attribute => $value) {
|
||||
if (method_exists($this, $attribute)) {
|
||||
$this->$attribute($value);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
protected function filter(array $filters): Builder
|
||||
{
|
||||
foreach ($filters as $attribute => $value) {
|
||||
if (method_exists($this, $attribute)) {
|
||||
$this->$attribute($value);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->builder;
|
||||
}
|
||||
}
|
42
app/Http/Filters/V1/UserFilter.php
Normal file
42
app/Http/Filters/V1/UserFilter.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Filters\V1;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class UserFilter extends QueryFilter
|
||||
{
|
||||
// TODO: Many of these are repeated across UserFilter and ModFilter. Consider refactoring into a shared trait.
|
||||
// Also, consider using common filter types and making the field names dynamic.
|
||||
|
||||
public function name(string $value): Builder
|
||||
{
|
||||
// The API handles the wildcard character as an asterisk (*), but the database uses the percentage sign (%).
|
||||
$like = Str::replace('*', '%', $value);
|
||||
|
||||
return $this->builder->where('name', 'like', $like);
|
||||
}
|
||||
|
||||
public function created_at(string $value): Builder
|
||||
{
|
||||
// The API allows for a range of dates to be passed as a comma-separated list.
|
||||
$dates = explode(',', $value);
|
||||
if (count($dates) > 1) {
|
||||
return $this->builder->whereBetween('created_at', $dates);
|
||||
}
|
||||
|
||||
return $this->builder->whereDate('created_at', $value);
|
||||
}
|
||||
|
||||
public function updated_at(string $value): Builder
|
||||
{
|
||||
// The API allows for a range of dates to be passed as a comma-separated list.
|
||||
$dates = explode(',', $value);
|
||||
if (count($dates) > 1) {
|
||||
return $this->builder->whereBetween('updated_at', $dates);
|
||||
}
|
||||
|
||||
return $this->builder->whereDate('updated_at', $value);
|
||||
}
|
||||
}
|
25
app/Http/Resources/Api/V0/LicenseResource.php
Normal file
25
app/Http/Resources/Api/V0/LicenseResource.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Api\V0;
|
||||
|
||||
use App\Models\License;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
/** @mixin License */
|
||||
class LicenseResource extends JsonResource
|
||||
{
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'type' => 'license',
|
||||
'id' => $this->id,
|
||||
'attributes' => [
|
||||
'name' => $this->name,
|
||||
'link' => $this->link,
|
||||
'created_at' => $this->created_at,
|
||||
'updated_at' => $this->updated_at,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
@ -45,6 +45,19 @@ class ModResource extends JsonResource
|
||||
'self' => $user->profileUrl(),
|
||||
],
|
||||
])->toArray(),
|
||||
'versions' => $this->versions->map(fn ($version) => [
|
||||
'data' => [
|
||||
'type' => 'version',
|
||||
'id' => $version->id,
|
||||
],
|
||||
|
||||
// TODO: The download link to the version can be placed here, but I'd like to track the number of
|
||||
// downloads that are made, so we'll need a new route/feature for that. #35
|
||||
'links' => [
|
||||
'self' => $version->link,
|
||||
],
|
||||
|
||||
])->toArray(),
|
||||
'license' => [
|
||||
[
|
||||
'data' => [
|
||||
@ -54,15 +67,17 @@ class ModResource extends JsonResource
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
'includes' => $this->when(
|
||||
ApiController::shouldInclude('users'),
|
||||
fn () => $this->users->map(fn ($user) => new UserResource($user))
|
||||
ApiController::shouldInclude(['users', 'license', 'versions']),
|
||||
fn () => collect([
|
||||
'users' => $this->users->map(fn ($user) => new UserResource($user)),
|
||||
'license' => new LicenseResource($this->license),
|
||||
'versions' => $this->versions->map(fn ($version) => new ModVersionResource($version)),
|
||||
])
|
||||
->filter(fn ($value, $key) => ApiController::shouldInclude($key))
|
||||
->flatten(1)
|
||||
->values()
|
||||
),
|
||||
|
||||
// TODO: Provide 'included' data for attached 'license':
|
||||
//new LicenseResource($this->license)
|
||||
|
||||
'links' => [
|
||||
'self' => route('mod.show', [
|
||||
'mod' => $this->id,
|
||||
|
54
app/Http/Resources/Api/V0/ModVersionResource.php
Normal file
54
app/Http/Resources/Api/V0/ModVersionResource.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Api\V0;
|
||||
|
||||
use App\Models\ModVersion;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
/** @mixin ModVersion */
|
||||
class ModVersionResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'type' => 'mod_version',
|
||||
'id' => $this->id,
|
||||
'attributes' => [
|
||||
'hub_id' => $this->hub_id,
|
||||
'mod_id' => $this->mod_id,
|
||||
'version' => $this->version,
|
||||
|
||||
// TODO: This should only be visible on the mod version show route(?) which doesn't exist.
|
||||
//'description' => $this->when(
|
||||
// $request->routeIs('api.v0.modversion.show'),
|
||||
// $this->description
|
||||
//),
|
||||
|
||||
// TODO: The download link to the version can be placed here, but I'd like to track the number of
|
||||
// downloads that are made, so we'll need a new route/feature for that. #35
|
||||
'link' => $this->link,
|
||||
|
||||
'spt_version_id' => $this->spt_version_id,
|
||||
'virus_total_link' => $this->virus_total_link,
|
||||
'downloads' => $this->downloads,
|
||||
'created_at' => $this->created_at,
|
||||
'updated_at' => $this->updated_at,
|
||||
'published_at' => $this->published_at,
|
||||
],
|
||||
'relationships' => [
|
||||
'spt_version' => [
|
||||
[
|
||||
'data' => [
|
||||
'type' => 'spt_version',
|
||||
'id' => $this->spt_version_id,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Resources\Api\V0;
|
||||
|
||||
use App\Http\Controllers\Api\V0\ApiController;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
@ -9,9 +10,6 @@ use Illuminate\Http\Resources\Json\JsonResource;
|
||||
/** @mixin User */
|
||||
class UserResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
@ -21,6 +19,7 @@ class UserResource extends JsonResource
|
||||
'name' => $this->name,
|
||||
'user_role_id' => $this->user_role_id,
|
||||
'created_at' => $this->created_at,
|
||||
'updated_at' => $this->updated_at,
|
||||
],
|
||||
'relationships' => [
|
||||
'user_role' => [
|
||||
@ -30,10 +29,10 @@ class UserResource extends JsonResource
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
// TODO: Provide 'included' data for attached 'user_role'
|
||||
//'included' => [new UserRoleResource($this->role)],
|
||||
|
||||
'includes' => $this->when(
|
||||
ApiController::shouldInclude('user_role'),
|
||||
new UserRoleResource($this->role)
|
||||
),
|
||||
'links' => [
|
||||
'self' => $this->profileUrl(),
|
||||
],
|
||||
|
25
app/Http/Resources/Api/V0/UserRoleResource.php
Normal file
25
app/Http/Resources/Api/V0/UserRoleResource.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Api\V0;
|
||||
|
||||
use App\Models\UserRole;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
/** @mixin UserRole */
|
||||
class UserRoleResource extends JsonResource
|
||||
{
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'type' => 'user_role',
|
||||
'id' => $this->id,
|
||||
'attributes' => [
|
||||
'name' => $this->name,
|
||||
'short_name' => $this->short_name,
|
||||
'description' => $this->description,
|
||||
'color_class' => $this->color_class,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
@ -2,8 +2,10 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Http\Filters\V1\QueryFilter;
|
||||
use App\Models\Scopes\DisabledScope;
|
||||
use App\Models\Scopes\PublishedScope;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
@ -144,6 +146,14 @@ class Mod extends Model
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope a query by applying QueryFilter filters.
|
||||
*/
|
||||
public function scopeFilter(Builder $builder, QueryFilter $filters): Builder
|
||||
{
|
||||
return $filters->apply($builder);
|
||||
}
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*/
|
||||
|
@ -2,10 +2,12 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Http\Filters\V1\QueryFilter;
|
||||
use App\Notifications\ResetPassword;
|
||||
use App\Notifications\VerifyEmail;
|
||||
use App\Traits\HasCoverPhoto;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
@ -136,6 +138,14 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
return $this->belongsTo(UserRole::class, 'user_role_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope a query by applying QueryFilter filters.
|
||||
*/
|
||||
public function scopeFilter(Builder $builder, QueryFilter $filters): Builder
|
||||
{
|
||||
return $filters->apply($builder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the disk that profile photos should be stored on.
|
||||
*/
|
||||
|
Loading…
x
Reference in New Issue
Block a user