fixed issues and added route params mapper

This commit is contained in:
pushrbx 2023-01-21 23:24:11 +00:00
parent 49ebc8f581
commit 413df48fdc
32 changed files with 326 additions and 298 deletions

View File

@ -28,8 +28,6 @@ class Anime extends JikanApiSearchableModel
'mal_id','url','title','title_english','title_japanese','title_synonyms', 'titles', 'images', 'type','source','episodes','status','airing','aired','duration','rating','score','scored_by','rank','popularity','members','favorites','synopsis','background','premiered','broadcast','related','producers','licensors','studios','genres', 'explicit_genres', 'themes', 'demographics', 'opening_themes','ending_themes'
];
protected ?string $displayNameFieldName = "title";
/**
* The accessors to append to the model's array form.
*
@ -53,6 +51,12 @@ class Anime extends JikanApiSearchableModel
'_id', 'premiered', 'opening_themes', 'ending_themes', 'request_hash', 'expiresAt'
];
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
$this->displayNameFieldName = "title";
}
public function setSeasonAttribute($value)
{
$this->attributes['season'] = $this->getSeasonAttribute();

View File

@ -29,8 +29,6 @@ class Character extends JikanApiSearchableModel
*/
protected $appends = ['images', 'favorites'];
protected ?string $displayNameFieldName = "name";
/**
* The table associated with the model.
*
@ -47,6 +45,12 @@ class Character extends JikanApiSearchableModel
'_id', 'trailer_url', 'premiered', 'opening_themes', 'ending_themes', 'images', 'member_favorites'
];
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
$this->displayNameFieldName = "name";
}
/** @noinspection PhpUnused */
public function getFavoritesAttribute()
{

View File

@ -29,8 +29,6 @@ class Club extends JikanApiSearchableModel
*/
protected $appends = ['images'];
protected ?string $displayNameFieldName = "title";
/**
* The table associated with the model.
*
@ -47,6 +45,12 @@ class Club extends JikanApiSearchableModel
'_id', 'request_hash', 'expiresAt', 'images'
];
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
$this->displayNameFieldName = "title";
}
/** @noinspection PhpUnused */
public function filterByCategory(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, ClubCategoryEnum $value): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
{

View File

@ -18,7 +18,7 @@ trait HasRequestFingerprint
public static function fromRequest(Request $request): ?static
{
$result = app(DataFromSomethingResolver::class)
->withoutMagicalCreation()->execute(self::class, $request);
->withoutMagicalCreation()->execute(static::class, $request);
$result->fingerprint = HttpHelper::resolveRequestFingerprint($request);
return $result;
}

View File

@ -6,7 +6,7 @@ use Illuminate\Support\Env;
trait ScraperCacheTtl
{
protected static function cacheTtl(): int
public static function cacheTtl(): int
{
return (int) Env::get('CACHE_DEFAULT_EXPIRE');
}

View File

@ -4,7 +4,9 @@ namespace App\Contracts;
use App\Anime;
use App\Enums\AnimeScheduleFilterEnum;
use App\Enums\AnimeTypeEnum;
use Illuminate\Contracts\Database\Query\Builder as EloquentBuilder;
use Illuminate\Support\Carbon;
use \Laravel\Scout\Builder as ScoutBuilder;
/**
@ -29,4 +31,6 @@ interface AnimeRepository extends Repository
bool $kids = false,
bool $sfw = false
): EloquentBuilder;
public function getAiredBetween(Carbon $from, Carbon $to, ?AnimeTypeEnum $type = null): EloquentBuilder;
}

View File

@ -0,0 +1,25 @@
<?php
namespace App\DataPipes;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Spatie\LaravelData\Support\DataClass;
class MapRouteParametersDataPipe implements \Spatie\LaravelData\DataPipes\DataPipe
{
public function handle(mixed $payload, DataClass $class, Collection $properties): Collection
{
if ($payload instanceof Request) {
foreach ($class->properties as $dataProperty) {
$routeParamVal = $payload->route($dataProperty->inputMappedName ?? $dataProperty->name);
if (!is_null($routeParamVal)) {
$properties->put($dataProperty->name, $routeParamVal);
}
}
}
return $properties;
}
}

View File

@ -3,7 +3,7 @@
namespace App\Dto;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Spatie\LaravelData\Attributes\Validation\Min;
use Spatie\LaravelData\Attributes\Validation\Numeric;
use Spatie\LaravelData\Attributes\Validation\Required;
@ -12,18 +12,6 @@ use Spatie\LaravelData\Attributes\Validation\Required;
*/
final class AnimeEpisodeLookupCommand extends LookupDataCommand
{
#[Numeric, Required]
#[Numeric, Required, Min(1)]
public int $episodeId;
/** @noinspection PhpUnused */
public static function fromMultiple(Request $request, int $id, int $episodeId): ?self
{
/**
* @var AnimeEpisodeLookupCommand $data
*/
$data = self::fromRequestAndKey($request, $id);
$data->episodeId = $episodeId;
return $data;
}
}

View File

@ -3,6 +3,7 @@
namespace App\Dto;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* @extends LookupDataCommand<JsonResponse>

View File

@ -4,13 +4,22 @@ namespace App\Dto;
use App\Concerns\HasRequestFingerprint;
use App\Contracts\DataRequest;
use App\DataPipes\MapRouteParametersDataPipe;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Http\Resources\Json\ResourceCollection;
use Illuminate\Http\Response;
use Illuminate\Support\Collection;
use Spatie\LaravelData\Attributes\Validation\Min;
use Spatie\LaravelData\Attributes\Validation\Numeric;
use Spatie\LaravelData\Attributes\Validation\Required;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\DataPipeline;
use Spatie\LaravelData\DataPipes\AuthorizedDataPipe;
use Spatie\LaravelData\DataPipes\CastPropertiesDataPipe;
use Spatie\LaravelData\DataPipes\DefaultValuesDataPipe;
use Spatie\LaravelData\DataPipes\MapPropertiesDataPipe;
use Spatie\LaravelData\DataPipes\ValidatePropertiesDataPipe;
/**
* Base class for all requests/commands which are for looking up things by id.
@ -21,14 +30,18 @@ abstract class LookupDataCommand extends Data implements DataRequest
{
use HasRequestFingerprint;
#[Numeric, Required]
#[Numeric, Required, Min(1)]
public int $id;
/** @noinspection PhpUnused */
public static function fromRequestAndKey(Request $request, int $id): self
public static function pipeline(): DataPipeline
{
$data = static::fromRequest($request);
$data->id = $id;
return $data;
return DataPipeline::create()
->into(static::class)
->through(AuthorizedDataPipe::class)
->through(MapPropertiesDataPipe::class)
->through(MapRouteParametersDataPipe::class) // if a payload is a request object, we map route params
->through(ValidatePropertiesDataPipe::class)
->through(DefaultValuesDataPipe::class)
->through(CastPropertiesDataPipe::class);
}
}

View File

@ -9,6 +9,7 @@ use App\Contracts\DataRequest;
use App\Enums\AnimeScheduleFilterEnum;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Env;
use Spatie\Enum\Laravel\Rules\EnumRule;
use Spatie\LaravelData\Attributes\Validation\BooleanType;
use Spatie\LaravelData\Attributes\Validation\IntegerType;
@ -17,6 +18,7 @@ use Spatie\LaravelData\Attributes\Validation\Nullable;
use Spatie\LaravelData\Attributes\Validation\Numeric;
use Spatie\LaravelData\Attributes\WithCast;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\Optional;
/**
* @implements DataRequest<JsonResponse>
@ -26,16 +28,16 @@ final class QueryAnimeSchedulesCommand extends Data implements DataRequest
use HasRequestFingerprint;
#[Numeric, Min(1)]
public int $page = 1;
public int|Optional $page = 1;
#[IntegerType, Min(1), Nullable]
public ?int $limit;
#[IntegerType, Min(1)]
public int|Optional $limit;
#[BooleanType]
public bool $kids = false;
public bool|Optional $kids = false;
#[BooleanType]
public bool $sfw = false;
public bool|Optional $sfw = false;
#[WithCast(EnumCast::class, AnimeScheduleFilterEnum::class)]
public ?AnimeScheduleFilterEnum $filter;
@ -59,6 +61,10 @@ final class QueryAnimeSchedulesCommand extends Data implements DataRequest
$data->filter = AnimeScheduleFilterEnum::from($day);
}
if ($data->limit == Optional::create()) {
$data->limit = Env::get("MAX_RESULTS_PER_PAGE", 25);
}
return $data;
}
}

View File

@ -0,0 +1,77 @@
<?php
namespace App\Dto;
use App\Casts\EnumCast;
use App\Concerns\HasRequestFingerprint;
use App\Contracts\DataRequest;
use App\Enums\AnimeSeasonEnum;
use App\Enums\AnimeTypeEnum;
use App\Http\HttpHelper;
use App\Http\Resources\V4\AnimeCollection;
use Illuminate\Http\Request;
use Illuminate\Support\Env;
use Spatie\Enum\Laravel\Rules\EnumRule;
use Spatie\LaravelData\Attributes\Validation\Between;
use Spatie\LaravelData\Attributes\Validation\Min;
use Spatie\LaravelData\Attributes\Validation\Numeric;
use Spatie\LaravelData\Attributes\Validation\Required;
use Spatie\LaravelData\Attributes\WithCast;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\Optional;
use Spatie\LaravelData\Resolvers\DataFromSomethingResolver;
/**
* @implements DataRequest<AnimeCollection>
*/
final class QueryAnimeSeasonCommand extends Data implements DataRequest
{
use HasRequestFingerprint;
#[Required, Between(1000, 2999)]
public int $year;
#[WithCast(EnumCast::class, AnimeSeasonEnum::class)]
public AnimeSeasonEnum $season;
#[WithCast(EnumCast::class, AnimeTypeEnum::class)]
public AnimeTypeEnum|Optional $filter;
#[Numeric, Min(1)]
public int|Optional $page = 1;
#[Numeric, Min(1)]
public int|Optional $limit;
public static function rules(...$args): array
{
return [
"season" => [new EnumRule(AnimeSeasonEnum::class), new Required()],
"filter" => [new EnumRule(AnimeTypeEnum::class)]
];
}
public static function messages(...$args): array
{
return [
"season.enum" => "Invalid season supplied."
];
}
/** @noinspection PhpUnused */
public static function fromRequestAndRequired(Request $request, int $year, AnimeSeasonEnum $season): self
{
/**
* @var QueryAnimeSeasonCommand $data
*/
$data = app(DataFromSomethingResolver::class)
->withoutMagicalCreation()->execute(self::class, ["request" => $request, "year" => $year, "season" => $season->value]);
$data->fingerprint = HttpHelper::resolveRequestFingerprint($request);
if ($data->limit == Optional::create()) {
$data->limit = Env::get("MAX_RESULTS_PER_PAGE", 30);
}
return $data;
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace App\Enums;
use Spatie\Enum\Laravel\Enum;
/**
* @method static self summer()
* @method static self spring()
* @method static self winter()
* @method static self fall()
*/
final class AnimeSeasonEnum extends Enum
{
protected static function labels(): array
{
$labels = [];
foreach (self::values() as $value) {
$labels[$value] = ucfirst($value);
}
return $labels;
}
}

View File

@ -9,7 +9,6 @@ use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Http\Resources\Json\ResourceCollection;
use Illuminate\Http\Response;
use Illuminate\Support\Collection;
use Spatie\LaravelData\Data;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
@ -17,7 +16,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
* @template TResponse of ResourceCollection|JsonResource|Response
* @implements RequestHandler<TRequest, TResponse>
*/
abstract class ItemLookupHandler extends Data implements RequestHandler
abstract class ItemLookupHandler implements RequestHandler
{
public function __construct(protected readonly CachedScraperService $scraperService)
{

View File

@ -55,9 +55,8 @@ class AnimeController extends Controller
* ),
* )
*/
public function full(Request $request, int $id)
public function full(AnimeFullLookupCommand $command)
{
$command = AnimeFullLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -90,9 +89,8 @@ class AnimeController extends Controller
* ),
* )
*/
public function main(Request $request, int $id)
public function main(AnimeLookupCommand $command)
{
$command = AnimeLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -122,9 +120,8 @@ class AnimeController extends Controller
* ),
* )
*/
public function characters(Request $request, int $id)
public function characters(AnimeCharactersLookupCommand $command)
{
$command = AnimeCharactersLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -154,9 +151,8 @@ class AnimeController extends Controller
* ),
* )
*/
public function staff(Request $request, int $id)
public function staff(AnimeStaffLookupCommand $command)
{
$command = AnimeStaffLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -261,9 +257,8 @@ class AnimeController extends Controller
* }
* )
*/
public function episodes(Request $request, int $id)
public function episodes(AnimeEpisodesLookupCommand $command)
{
$command = AnimeEpisodesLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -303,9 +298,8 @@ class AnimeController extends Controller
* ),
* )
*/
public function episode(Request $request, int $id, int $episodeId)
public function episode(AnimeEpisodeLookupCommand $command)
{
$command = AnimeEpisodeLookupCommand::from($request, $id, $episodeId);
return $this->mediator->send($command);
}
@ -349,9 +343,8 @@ class AnimeController extends Controller
* }
* )
*/
public function news(Request $request, int $id)
public function news(AnimeNewsLookupCommand $command)
{
$command = AnimeNewsLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -389,9 +382,8 @@ class AnimeController extends Controller
* ),
* )
*/
public function forum(Request $request, int $id)
public function forum(AnimeForumLookupCommand $command)
{
$command = AnimeForumLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -421,9 +413,8 @@ class AnimeController extends Controller
* ),
* )
*/
public function videos(Request $request, int $id)
public function videos(AnimeVideosLookupCommand $command)
{
$command = AnimeVideosLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -498,9 +489,8 @@ class AnimeController extends Controller
* }
* )
*/
public function videosEpisodes(Request $request, int $id)
public function videosEpisodes(AnimeVideosEpisodesLookupCommand $command)
{
$command = AnimeVideosEpisodesLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -532,9 +522,8 @@ class AnimeController extends Controller
* )
*
*/
public function pictures(Request $request, int $id)
public function pictures(AnimePicturesLookupCommand $command)
{
$command = AnimePicturesLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -564,9 +553,8 @@ class AnimeController extends Controller
* ),
* )
*/
public function stats(Request $request, int $id)
public function stats(AnimeStatsLookupCommand $command)
{
$command = AnimeStatsLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -596,9 +584,8 @@ class AnimeController extends Controller
* ),
* )
*/
public function moreInfo(Request $request, int $id)
public function moreInfo(AnimeMoreInfoLookupCommand $command)
{
$command = AnimeMoreInfoLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -628,9 +615,8 @@ class AnimeController extends Controller
* ),
* )
*/
public function recommendations(Request $request, int $id)
public function recommendations(AnimeRecommendationsLookupCommand $command)
{
$command = AnimeRecommendationsLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -662,9 +648,8 @@ class AnimeController extends Controller
* ),
* )
*/
public function userupdates(Request $request, int $id)
public function userupdates(AnimeUserUpdatesLookupCommand $command)
{
$command = AnimeUserUpdatesLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -696,9 +681,8 @@ class AnimeController extends Controller
* ),
* )
*/
public function reviews(Request $request, int $id)
public function reviews(AnimeReviewsLookupCommand $command)
{
$command = AnimeReviewsLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -737,9 +721,8 @@ class AnimeController extends Controller
* ),
* )
*/
public function relations(Request $request, int $id)
public function relations(AnimeRelationsLookupCommand $command)
{
$command = AnimeRelationsLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -769,9 +752,8 @@ class AnimeController extends Controller
* ),
* )
*/
public function themes(Request $request, int $id)
public function themes(AnimeThemesLookupCommand $command)
{
$command = AnimeThemesLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -801,9 +783,8 @@ class AnimeController extends Controller
* ),
* )
*/
public function external(Request $request, int $id)
public function external(AnimeExternalLookupCommand $command)
{
$command = AnimeExternalLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -833,9 +814,8 @@ class AnimeController extends Controller
* ),
* )
*/
public function streaming(Request $request, int $id)
public function streaming(AnimeStreamingLookupCommand $command)
{
$command = AnimeStreamingLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
}

View File

@ -57,9 +57,8 @@ class CharacterController extends Controller
* ),
* )
*/
public function full(Request $request, int $id)
public function full(CharacterFullLookupCommand $command)
{
$command = CharacterFullLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -92,9 +91,8 @@ class CharacterController extends Controller
* ),
* )
*/
public function main(Request $request, int $id)
public function main(CharacterLookupCommand $command)
{
$command = CharacterLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -122,9 +120,8 @@ class CharacterController extends Controller
* ),
* )
*/
public function anime(Request $request, int $id)
public function anime(CharacterAnimeLookupCommand $command)
{
$command = CharacterAnimeLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -153,9 +150,8 @@ class CharacterController extends Controller
* ),
* )
*/
public function manga(Request $request, int $id)
public function manga(CharacterMangaLookupCommand $command)
{
$command = CharacterMangaLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -183,9 +179,8 @@ class CharacterController extends Controller
* ),
* )
*/
public function voices(Request $request, int $id)
public function voices(CharacterVoicesLookupCommand $command)
{
$command = CharacterVoicesLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -240,9 +235,8 @@ class CharacterController extends Controller
* )
* )
*/
public function pictures(Request $request, int $id)
public function pictures(CharacterPicturesLookupCommand $command)
{
$command = CharacterPicturesLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
}

View File

@ -51,9 +51,8 @@ class ClubController extends Controller
* ),
* )
*/
public function main(Request $request, int $id)
public function main(ClubLookupCommand $command)
{
$command = ClubLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -103,9 +102,8 @@ class ClubController extends Controller
* ),
* ),
*/
public function members(Request $request, int $id)
public function members(ClubMembersLookupCommand $command)
{
$command = ClubMembersLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -135,9 +133,8 @@ class ClubController extends Controller
* ),
* )
*/
public function staff(Request $request, int $id)
public function staff(ClubStaffLookupCommand $command)
{
$command = ClubStaffLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -167,9 +164,8 @@ class ClubController extends Controller
* ),
* )
*/
public function relations(Request $request, int $id)
public function relations(ClubRelationLookupCommand $command)
{
$command = ClubRelationLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
}

View File

@ -1,21 +0,0 @@
<?php
namespace App\Http\Controllers\V4DB;
use App\Http\Controllers\V4DB\Traits\JikanApiQueryBuilder;
use App\Providers\SearchQueryBuilderProvider;
use Illuminate\Http\Request;
use Jikan\MyAnimeList\MalClient;
abstract class ControllerWithQueryBuilderProvider extends Controller
{
use JikanApiQueryBuilder;
private SearchQueryBuilderProvider $searchQueryBuilderProvider;
public function __construct(Request $request, MalClient $jikan, SearchQueryBuilderProvider $searchQueryBuilderProvider)
{
parent::__construct($request, $jikan);
$this->searchQueryBuilderProvider = $searchQueryBuilderProvider;
}
}

View File

@ -49,9 +49,8 @@ class MangaController extends Controller
* ),
* )
*/
public function full(Request $request, int $id)
public function full(MangaFullLookupCommand $command)
{
$command = MangaFullLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -85,9 +84,8 @@ class MangaController extends Controller
* ),
* )
*/
public function main(Request $request, int $id)
public function main(MangaLookupCommand $command)
{
$command = MangaLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -117,9 +115,8 @@ class MangaController extends Controller
* ),
* )
*/
public function characters(Request $request, int $id)
public function characters(MangaCharactersLookupCommand $command)
{
$command = MangaCharactersLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -161,9 +158,8 @@ class MangaController extends Controller
* }
* )
*/
public function news(Request $request, int $id)
public function news(MangaNewsLookupCommand $command)
{
$command = MangaNewsLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -201,9 +197,8 @@ class MangaController extends Controller
* ),
* )
*/
public function forum(Request $request, int $id)
public function forum(MangaForumLookupCommand $command)
{
$command = MangaForumLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -246,9 +241,8 @@ class MangaController extends Controller
* )
* )
*/
public function pictures(Request $request, int $id)
public function pictures(MangaPicturesLookupCommand $command)
{
$command = MangaPicturesLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -278,9 +272,8 @@ class MangaController extends Controller
* ),
* )
*/
public function stats(Request $request, int $id)
public function stats(MangaStatsLookupCommand $command)
{
$command = MangaStatsLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -310,9 +303,8 @@ class MangaController extends Controller
* ),
* )
*/
public function moreInfo(Request $request, int $id)
public function moreInfo(MangaMoreInfoLookupCommand $command)
{
$command = MangaMoreInfoLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -342,9 +334,8 @@ class MangaController extends Controller
* ),
* )
*/
public function recommendations(Request $request, int $id)
public function recommendations(MangaRecommendationsLookupCommand $command)
{
$command = MangaRecommendationsLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -376,9 +367,8 @@ class MangaController extends Controller
* ),
* )
*/
public function userupdates(Request $request, int $id)
public function userupdates(MangaUserUpdatesLookupCommand $command)
{
$command = MangaUserUpdatesLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -411,9 +401,8 @@ class MangaController extends Controller
* )
* @throws \Exception
*/
public function reviews(Request $request, int $id)
public function reviews(MangaReviewsLookupCommand $command)
{
$command = MangaReviewsLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -450,9 +439,8 @@ class MangaController extends Controller
* ),
* )
*/
public function relations(Request $request, int $id)
public function relations(MangaRelationsLookupCommand $command)
{
$command = MangaRelationsLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -482,9 +470,8 @@ class MangaController extends Controller
* ),
* )
*/
public function external(Request $request, int $id)
public function external(MangaExternalLookupCommand $command)
{
$command = MangaExternalLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
}

View File

@ -42,9 +42,8 @@ class PersonController extends Controller
* ),
* )
*/
public function full(Request $request, int $id)
public function full(PersonFullLookupCommand $command)
{
$command = PersonFullLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -78,9 +77,8 @@ class PersonController extends Controller
* ),
* )
*/
public function main(Request $request, int $id)
public function main(PersonLookupCommand $command)
{
$command = PersonLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -108,9 +106,8 @@ class PersonController extends Controller
* ),
* )
*/
public function anime(Request $request, int $id)
public function anime(PersonAnimeLookupCommand $command)
{
$command = PersonAnimeLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -138,9 +135,8 @@ class PersonController extends Controller
* ),
* )
*/
public function voices(Request $request, int $id)
public function voices(PersonVoicesLookupCommand $command)
{
$command = PersonVoicesLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -168,9 +164,8 @@ class PersonController extends Controller
* ),
* )
*/
public function manga(Request $request, int $id)
public function manga(PersonMangaLookupCommand $command)
{
$command = PersonMangaLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -214,9 +209,8 @@ class PersonController extends Controller
* )
* )
*/
public function pictures(Request $request, int $id)
public function pictures(PersonPicturesLookupCommand $command)
{
$command = PersonPicturesLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
}

View File

@ -38,9 +38,8 @@ class ProducerController extends Controller
* ),
* )
*/
public function main(Request $request, int $id)
public function main(ProducerLookupCommand $command)
{
$command = ProducerLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -73,9 +72,8 @@ class ProducerController extends Controller
* ),
* )
*/
public function full(Request $request, int $id)
public function full(ProducerFullLookupCommand $command)
{
$command = ProducerFullLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
@ -105,9 +103,8 @@ class ProducerController extends Controller
* ),
* )
*/
public function external(Request $request, int $id)
public function external(ProducerExternalLookupCommand $command)
{
$command = ProducerExternalLookupCommand::from($request, $id);
return $this->mediator->send($command);
}
}

View File

@ -517,9 +517,9 @@ class SearchController extends Controller
*
*
*/
public function userById(Request $request, int $id)
public function userById(UserByIdLookupCommand $command)
{
return $this->mediator->send(UserByIdLookupCommand::from($request, $id));
return $this->mediator->send($command);
}
/**

View File

@ -22,7 +22,7 @@ final class ResponseJikanCacheFlags
*/
return $this
->header("X-Request-Fingerprint", $cacheKey)
->setTtl(self::cacheTtl())
->setTtl(ResponseJikanCacheFlags::cacheTtl())
->setExpires(Carbon::createFromTimestamp($scraperResults->expiry()))
->setLastModified(Carbon::createFromTimestamp($scraperResults->lastModified()));
};

View File

@ -31,8 +31,6 @@ class Magazine extends JikanApiSearchableModel
*/
protected $table = 'magazines';
protected ?string $displayNameFieldName = "name";
/**
* The attributes excluded from the model's JSON form.
*
@ -42,6 +40,12 @@ class Magazine extends JikanApiSearchableModel
'_id', 'expiresAt'
];
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
$this->displayNameFieldName = "name";
}
/**
* @return array
*/

View File

@ -35,8 +35,6 @@ class Manga extends JikanApiSearchableModel
*/
protected $appends = [];
protected ?string $displayNameFieldName = "title";
/**
* The table associated with the model.
*
@ -53,6 +51,12 @@ class Manga extends JikanApiSearchableModel
'_id', 'expiresAt', 'request_hash'
];
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
$this->displayNameFieldName = "title";
}
/** @noinspection PhpUnused */
public function filterByStartDate(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, CarbonImmutable $date): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
{

View File

@ -2,6 +2,7 @@
namespace App;
use App\Concerns\FilteredByLetter;
use Jikan\Jikan;
use Jikan\Request\Person\PersonRequest;
use function Symfony\Component\Translation\t;
@ -9,7 +10,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
class Person extends JikanApiSearchableModel
{
use HasFactory;
use HasFactory, FilteredByLetter;
protected array $filters = ["order_by", "sort"];
/**
@ -44,6 +45,12 @@ class Person extends JikanApiSearchableModel
'_id', 'images', 'member_favorites'
];
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
$this->displayNameFieldName = "name";
}
public function getFavoritesAttribute()
{
return $this->attributes['member_favorites'];

View File

@ -26,8 +26,6 @@ class Producers extends JikanApiSearchableModel
*/
protected $table = 'producers';
protected ?string $displayNameFieldName = "titles.0.title";
/**
* The attributes excluded from the model's JSON form.
*
@ -37,6 +35,12 @@ class Producers extends JikanApiSearchableModel
'_id', 'request_hash', 'expiresAt'
];
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
$this->displayNameFieldName = "titles.0.title";
}
public static function scrape(int $id)
{
$data = app('JikanParser')->getProducer(new ProducerRequest($id));

View File

@ -26,8 +26,6 @@ class Profile extends JikanApiSearchableModel
*/
protected $table = 'users';
protected ?string $displayNameFieldName = "username";
/**
* The attributes excluded from the model's JSON form.
*
@ -37,6 +35,12 @@ class Profile extends JikanApiSearchableModel
'_id',
];
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
$this->displayNameFieldName = "username";
}
public static function scrape(string $username)
{
$data = app('JikanParser')->getUserProfile(new UserProfileRequest($username));

View File

@ -47,14 +47,17 @@ use App\Services\DefaultScoutSearchService;
use App\Services\ElasticScoutSearchService;
use App\Services\EloquentBuilderPaginatorService;
use App\Services\MongoSearchService;
use App\Services\QueryBuilderPaginatorService;
use App\Services\ScoutBuilderPaginatorService;
use App\Services\ScoutSearchService;
use App\Services\SearchEngineSearchService;
use App\Services\SearchService;
use App\Services\TypeSenseScoutSearchService;
use App\Support\DefaultMediator;
use App\Support\JikanUnitOfWork;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Http\JsonResponse;
use Laravel\Lumen\Application;
use Illuminate\Http\Response;
use Illuminate\Support\Env;
use Illuminate\Support\ServiceProvider;
@ -87,90 +90,36 @@ class AppServiceProvider extends ServiceProvider
*/
public function register(): void
{
// $this->app->singleton(ScoutSearchService::class, function($app) {
// $scoutDriver = $this->getSearchIndexDriver($app);
//
// return match ($scoutDriver) {
// "typesense" => new TypeSenseScoutSearchService(),
// "Matchish\ScoutElasticSearch\Engines\ElasticSearchEngine" => new ElasticScoutSearchService(),
// default => new DefaultScoutSearchService()
// };
// });
// todo: remove SearchQueryBuilders
$queryBuilders = [
AnimeSearchQueryBuilder::class,
MangaSearchQueryBuilder::class,
ClubSearchQueryBuilder::class,
CharacterSearchQueryBuilder::class,
PeopleSearchQueryBuilder::class,
TopAnimeQueryBuilder::class,
TopMangaQueryBuilder::class
];
foreach($queryBuilders as $queryBuilderClass) {
$this->app->singleton($queryBuilderClass,
$this->getQueryBuilderFactory($queryBuilderClass)
);
}
$simpleQueryBuilderAbstracts = [];
$simpleQueryBuilders = [
[
"name" => "GenreAnime",
"identifier" => "genre_anime",
"modelClass" => GenreAnime::class
],
[
"name" => "GenreManga",
"identifier" => "genre_manga",
"modelClass" => GenreManga::class
],
[
"name" => "Producers",
"identifier" => "producers",
"modelClass" => Producers::class,
"orderByFields" => ["mal_id", "count", "favorites", "established"]
],
[
"name" => "Magazine",
"identifier" => "magazine",
"modelClass" => Magazine::class
]
];
foreach ($simpleQueryBuilders as $simpleQueryBuilder) {
$abstractName = SimpleSearchQueryBuilder::class . $simpleQueryBuilder["name"];
$simpleQueryBuilderAbstracts[] = $abstractName;
$this->app->singleton($abstractName, function($app) use($simpleQueryBuilder) {
$searchIndexesEnabled = $this->getSearchIndexesEnabledConfig($app);
$ctorArgs = [
$simpleQueryBuilder["identifier"],
$simpleQueryBuilder["modelClass"],
$searchIndexesEnabled,
$app->make(ScoutSearchService::class)
];
if (array_key_exists("orderByFields", $simpleQueryBuilder)) {
$ctorArgs[] = $simpleQueryBuilder["orderByFields"];
}
return $this->simpleSearchQueryBuilderClassReflection->newInstanceArgs($ctorArgs);
});
}
$this->app->tag(array_merge($queryBuilders, $simpleQueryBuilderAbstracts), "searchQueryBuilders");
$this->app->singleton(SearchQueryBuilderProvider::class, function($app) {
return new SearchQueryBuilderProvider($app->tagged("searchQueryBuilders"));
});
// new stuff
$this->app->singleton(CachedScraperService::class, DefaultCachedScraperService::class);
if ($this->getSearchIndexesEnabledConfig($this->app)) {
$this->app->bind(QueryBuilderPaginatorService::class, ScoutBuilderPaginatorService::class);
} else {
$this->app->bind(QueryBuilderPaginatorService::class, EloquentBuilderPaginatorService::class);
}
$this->registerModelRepositories();
$this->registerRequestHandlers();
}
private function getSearchService(Repository $repository): SearchService
{
if ($this->getSearchIndexesEnabledConfig($this->app)) {
$scoutDriver = static::getSearchIndexDriver($this->app);
$serviceClass = match ($scoutDriver) {
"typesense" => TypeSenseScoutSearchService::class,
"Matchish\ScoutElasticSearch\Engines\ElasticSearchEngine" => ElasticScoutSearchService::class,
default => DefaultScoutSearchService::class
};
$scoutSearchService = new $serviceClass($repository);
$result = new SearchEngineSearchService($scoutSearchService, $repository);
}
else {
$result = new MongoSearchService($repository);
}
return $result;
}
private function registerModelRepositories()
{
// note: We deliberately not included here any of the GenreRepository implementations.
@ -214,7 +163,6 @@ class AppServiceProvider extends ServiceProvider
$this->app->when(DefaultMediator::class)
->needs(RequestHandler::class)
->give(function (Application $app) {
$searchIndexesEnabled = $this->getSearchIndexesEnabledConfig($app);
/**
* @var UnitOfWork $unitOfWorkInstance
*/
@ -231,10 +179,10 @@ class AppServiceProvider extends ServiceProvider
$requestHandlers = [];
foreach ($searchRequestHandlersDescriptors as $handlerClass => $repositoryInstance) {
$requestHandlers[] = $app->make($handlerClass, [
$app->make(DefaultQueryBuilderService::class, [
static::makeSearchService($app, $searchIndexesEnabled, $repositoryInstance),
$app->make($searchIndexesEnabled ? ScoutBuilderPaginatorService::class : EloquentBuilderPaginatorService::class)
])
"queryBuilderService" => new DefaultQueryBuilderService(
$this->getSearchService($repositoryInstance),
$app->make(QueryBuilderPaginatorService::class)
)
]);
}
@ -260,7 +208,7 @@ class AppServiceProvider extends ServiceProvider
];
foreach ($requestHandlersWithOnlyRepositoryDependency as $handlerClass => $repositoryInstance) {
$requestHandlers[] = $app->make($handlerClass, [$repositoryInstance]);
$requestHandlers[] = $app->make($handlerClass, ["repository" => $repositoryInstance]);
}
// request handlers which are fetching data through the jikan library from MAL, and caching the result.
@ -328,9 +276,10 @@ class AppServiceProvider extends ServiceProvider
foreach ($requestHandlersWithScraperService as $handlerClass => $repositoryInstance) {
$jikan = $app->make(MalClient::class);
$serializer = $app->make("SerializerV4");
$scraperService = $app->make(DefaultCachedScraperService::class,
["repository" => $repositoryInstance, "jikan" => $jikan, "serializer" => $serializer]);
$requestHandlers[] = $app->make($handlerClass, [
$app->make(DefaultCachedScraperService::class,
[$repositoryInstance, $jikan, $serializer])
"scraperService" => $scraperService
]);
}
@ -352,37 +301,6 @@ class AppServiceProvider extends ServiceProvider
});
}
/**
* Creates a search service instance.
* Search service knows how to do a full-text search on the database query builder instance.
* @throws BindingResolutionException
*/
private static function makeSearchService(Application $app, bool $searchIndexesEnabled, Repository $repositoryInstance)
{
return $searchIndexesEnabled ? $app->make( SearchEngineSearchService::class, [
static::makeScoutSearchService($app, $repositoryInstance), $repositoryInstance
]) : $app->make(MongoSearchService::class, [$repositoryInstance]);
}
/**
* Creates a scout search service instance.
* Scout search service knows about the configured search engine's implementation details.
* E.g. per search request configuration.
* @throws BindingResolutionException
*/
private static function makeScoutSearchService(Application $app, Repository $repositoryInstance)
{
// todo: cache result
$scoutDriver = static::getSearchIndexDriver($app);
$serviceClass = match ($scoutDriver) {
"typesense" => TypeSenseScoutSearchService::class,
"Matchish\ScoutElasticSearch\Engines\ElasticSearchEngine" => ElasticScoutSearchService::class,
default => DefaultScoutSearchService::class
};
return $app->make($serviceClass, [$repositoryInstance]);
}
/**
* @throws \ReflectionException
* @throws BindingResolutionException
@ -395,6 +313,7 @@ class AppServiceProvider extends ServiceProvider
->each(fn ($class, $macro) => Collection::macro($macro, app($class)()));
Response::macro("addJikanCacheFlags", app(ResponseJikanCacheFlags::class)());
JsonResponse::macro("addJikanCacheFlags", app(ResponseJikanCacheFlags::class)());
ScoutBuilder::mixin(new ScoutBuilderMixin());
}

View File

@ -10,6 +10,7 @@ use App\Enums\AnimeScheduleFilterEnum;
use App\Enums\AnimeStatusEnum;
use App\Enums\AnimeTypeEnum;
use Illuminate\Contracts\Database\Query\Builder as EloquentBuilder;
use Illuminate\Support\Carbon;
use Jikan\Helper\Constants;
use Laravel\Scout\Builder as ScoutBuilder;
@ -101,4 +102,18 @@ final class DefaultAnimeRepository extends DatabaseRepository implements AnimeRe
return $queryable;
}
public function getAiredBetween(Carbon $from, Carbon $to, ?AnimeTypeEnum $type = null): EloquentBuilder
{
$queryable = $this->queryable(true)->where("aired.from", [
$from->toAtomString(),
$to->modify("last day of this month")->toAtomString()
]);
if (!is_null($type)) {
$queryable = $queryable->where("type", $type->label);
}
return $queryable->orderBy("members", "desc");
}
}

View File

@ -6,12 +6,9 @@ use App\Contracts\Repository;
final class SearchEngineSearchService extends SearchServiceBase
{
/**
* @throws \Exception
*/
public function __construct(
private readonly ScoutSearchService $scoutSearchService,
protected readonly Repository $repository
Repository $repository
)
{
parent::__construct($repository);

View File

@ -9,9 +9,6 @@ abstract class SearchServiceBase implements SearchService
{
protected Collection $filterParameters;
/**
* @throws \Exception
*/
public function __construct(protected readonly Repository $repository)
{
}