mirror of
https://github.com/AnimeThemes/animethemes-server.git
synced 2026-07-11 01:24:46 +02:00
* feat: add featured theme model * style: fix StyleCI findings
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Events\Admin\FeaturedTheme;
|
||||
|
||||
use App\Events\Base\Admin\AdminCreatedEvent;
|
||||
use App\Models\Admin\FeaturedTheme;
|
||||
|
||||
/**
|
||||
* Class FeaturedThemeCreated.
|
||||
*
|
||||
* @extends AdminCreatedEvent<FeaturedTheme>
|
||||
*/
|
||||
class FeaturedThemeCreated extends AdminCreatedEvent
|
||||
{
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param FeaturedTheme $featuredTheme
|
||||
*/
|
||||
public function __construct(FeaturedTheme $featuredTheme)
|
||||
{
|
||||
parent::__construct($featuredTheme);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the model that has fired this event.
|
||||
*
|
||||
* @return FeaturedTheme
|
||||
*/
|
||||
public function getModel(): FeaturedTheme
|
||||
{
|
||||
return $this->model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the description for the Discord message payload.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getDiscordMessageDescription(): string
|
||||
{
|
||||
return "Featured Theme '**{$this->getModel()->getName()}**' has been created.";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Events\Admin\FeaturedTheme;
|
||||
|
||||
use App\Events\Base\Admin\AdminDeletedEvent;
|
||||
use App\Models\Admin\FeaturedTheme;
|
||||
|
||||
/**
|
||||
* Class FeaturedThemeDeleted.
|
||||
*
|
||||
* @extends AdminDeletedEvent<FeaturedTheme>
|
||||
*/
|
||||
class FeaturedThemeDeleted extends AdminDeletedEvent
|
||||
{
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param FeaturedTheme $featuredTheme
|
||||
*/
|
||||
public function __construct(FeaturedTheme $featuredTheme)
|
||||
{
|
||||
parent::__construct($featuredTheme);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the model that has fired this event.
|
||||
*
|
||||
* @return FeaturedTheme
|
||||
*/
|
||||
public function getModel(): FeaturedTheme
|
||||
{
|
||||
return $this->model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the description for the Discord message payload.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getDiscordMessageDescription(): string
|
||||
{
|
||||
return "Featured Theme '**{$this->getModel()->getName()}**' has been deleted.";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Events\Admin\FeaturedTheme;
|
||||
|
||||
use App\Events\Base\Admin\AdminRestoredEvent;
|
||||
use App\Models\Admin\FeaturedTheme;
|
||||
|
||||
/**
|
||||
* Class FeaturedThemeRestored.
|
||||
*
|
||||
* @extends AdminRestoredEvent<FeaturedTheme>
|
||||
*/
|
||||
class FeaturedThemeRestored extends AdminRestoredEvent
|
||||
{
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param FeaturedTheme $featuredTheme
|
||||
*/
|
||||
public function __construct(FeaturedTheme $featuredTheme)
|
||||
{
|
||||
parent::__construct($featuredTheme);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the model that has fired this event.
|
||||
*
|
||||
* @return FeaturedTheme
|
||||
*/
|
||||
public function getModel(): FeaturedTheme
|
||||
{
|
||||
return $this->model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the description for the Discord message payload.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getDiscordMessageDescription(): string
|
||||
{
|
||||
return "Featured Theme '**{$this->getModel()->getName()}**' has been restored.";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Events\Admin\FeaturedTheme;
|
||||
|
||||
use App\Events\Base\Admin\AdminUpdatedEvent;
|
||||
use App\Models\Admin\FeaturedTheme;
|
||||
|
||||
/**
|
||||
* Class FeaturedThemeUpdated.
|
||||
*
|
||||
* @extends AdminUpdatedEvent<FeaturedTheme>
|
||||
*/
|
||||
class FeaturedThemeUpdated extends AdminUpdatedEvent
|
||||
{
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param FeaturedTheme $featuredTheme
|
||||
*/
|
||||
public function __construct(FeaturedTheme $featuredTheme)
|
||||
{
|
||||
parent::__construct($featuredTheme);
|
||||
$this->initializeEmbedFields($featuredTheme);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the model that has fired this event.
|
||||
*
|
||||
* @return FeaturedTheme
|
||||
*/
|
||||
public function getModel(): FeaturedTheme
|
||||
{
|
||||
return $this->model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the description for the Discord message payload.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getDiscordMessageDescription(): string
|
||||
{
|
||||
return "Featured Theme '**{$this->getModel()->getName()}**' has been updated.";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Api\Field\Admin\FeaturedTheme;
|
||||
|
||||
use App\Contracts\Http\Api\Field\CreatableField;
|
||||
use App\Contracts\Http\Api\Field\UpdatableField;
|
||||
use App\Enums\Http\Api\Filter\AllowedDateFormat;
|
||||
use App\Http\Api\Field\DateField;
|
||||
use App\Http\Api\Schema\Schema;
|
||||
use App\Models\Admin\FeaturedTheme;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Class FeaturedThemeEndAtField.
|
||||
*/
|
||||
class FeaturedThemeEndAtField extends DateField implements CreatableField, UpdatableField
|
||||
{
|
||||
/**
|
||||
* Create a new field instance.
|
||||
*
|
||||
* @param Schema $schema
|
||||
*/
|
||||
public function __construct(Schema $schema)
|
||||
{
|
||||
parent::__construct($schema, FeaturedTheme::ATTRIBUTE_END_AT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the creation validation rules for the field.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return array
|
||||
*/
|
||||
public function getCreationRules(Request $request): array
|
||||
{
|
||||
return [
|
||||
'required',
|
||||
Str::of('date_format:')
|
||||
->append(implode(',', AllowedDateFormat::getValues()))
|
||||
->__toString(),
|
||||
Str::of('after:')
|
||||
->append($this->resolveStartAt($request))
|
||||
->__toString(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the update validation rules for the field.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return array
|
||||
*/
|
||||
public function getUpdateRules(Request $request): array
|
||||
{
|
||||
return [
|
||||
'sometimes',
|
||||
'required',
|
||||
Str::of('date_format:')
|
||||
->append(implode(',', AllowedDateFormat::getValues()))
|
||||
->__toString(),
|
||||
Str::of('after:')
|
||||
->append($this->resolveStartAt($request))
|
||||
->__toString(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get dependent start_at field.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return string|null
|
||||
*/
|
||||
private function resolveStartAt(Request $request): ?string
|
||||
{
|
||||
if ($request->has(FeaturedTheme::ATTRIBUTE_START_AT)) {
|
||||
return $request->get(FeaturedTheme::ATTRIBUTE_START_AT);
|
||||
}
|
||||
|
||||
/** @var FeaturedTheme|null $featuredTheme */
|
||||
$featuredTheme = $request->route('featuredtheme');
|
||||
|
||||
return $featuredTheme?->start_at?->format(AllowedDateFormat::YMDHISU);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Api\Field\Admin\FeaturedTheme;
|
||||
|
||||
use App\Contracts\Http\Api\Field\CreatableField;
|
||||
use App\Contracts\Http\Api\Field\SelectableField;
|
||||
use App\Contracts\Http\Api\Field\UpdatableField;
|
||||
use App\Http\Api\Field\Field;
|
||||
use App\Http\Api\Query\Query;
|
||||
use App\Http\Api\Schema\Schema;
|
||||
use App\Models\Admin\FeaturedTheme;
|
||||
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
|
||||
use App\Pivots\Wiki\AnimeThemeEntryVideo;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
/**
|
||||
* Class FeaturedThemeEntryIdField.
|
||||
*/
|
||||
class FeaturedThemeEntryIdField extends Field implements CreatableField, SelectableField, UpdatableField
|
||||
{
|
||||
/**
|
||||
* Create a new field instance.
|
||||
*
|
||||
* @param Schema $schema
|
||||
*/
|
||||
public function __construct(Schema $schema)
|
||||
{
|
||||
parent::__construct($schema, FeaturedTheme::ATTRIBUTE_ENTRY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the creation validation rules for the field.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return array
|
||||
*/
|
||||
public function getCreationRules(Request $request): array
|
||||
{
|
||||
$videoId = $this->resolveVideoId($request);
|
||||
|
||||
return [
|
||||
'sometimes',
|
||||
'required',
|
||||
'integer',
|
||||
Rule::exists(AnimeThemeEntry::TABLE, AnimeThemeEntry::ATTRIBUTE_ID),
|
||||
Rule::when(
|
||||
! empty($videoId),
|
||||
[
|
||||
Rule::exists(AnimeThemeEntryVideo::TABLE, AnimeThemeEntryVideo::ATTRIBUTE_ENTRY)
|
||||
->where(AnimeThemeEntryVideo::ATTRIBUTE_VIDEO, $this->resolveVideoId($request)),
|
||||
]
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the field should be included in the select clause of our query.
|
||||
*
|
||||
* @param Query $query
|
||||
* @param Schema $schema
|
||||
* @return bool
|
||||
*/
|
||||
public function shouldSelect(Query $query, Schema $schema): bool
|
||||
{
|
||||
// Needed to match entry relation.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the update validation rules for the field.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return array
|
||||
*/
|
||||
public function getUpdateRules(Request $request): array
|
||||
{
|
||||
$videoId = $this->resolveVideoId($request);
|
||||
|
||||
return [
|
||||
'sometimes',
|
||||
'required',
|
||||
'integer',
|
||||
Rule::exists(AnimeThemeEntry::TABLE, AnimeThemeEntry::ATTRIBUTE_ID),
|
||||
Rule::when(
|
||||
! empty($videoId),
|
||||
[
|
||||
Rule::exists(AnimeThemeEntryVideo::TABLE, AnimeThemeEntryVideo::ATTRIBUTE_ENTRY)
|
||||
->where(AnimeThemeEntryVideo::ATTRIBUTE_VIDEO, $this->resolveVideoId($request)),
|
||||
]
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get dependent video_id field.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return mixed
|
||||
*/
|
||||
private function resolveVideoId(Request $request): mixed
|
||||
{
|
||||
if ($request->has(FeaturedTheme::ATTRIBUTE_VIDEO)) {
|
||||
return $request->get(FeaturedTheme::ATTRIBUTE_VIDEO);
|
||||
}
|
||||
|
||||
/** @var FeaturedTheme|null $featuredTheme */
|
||||
$featuredTheme = $request->route('featuredtheme');
|
||||
|
||||
return $featuredTheme?->video_id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Api\Field\Admin\FeaturedTheme;
|
||||
|
||||
use App\Contracts\Http\Api\Field\CreatableField;
|
||||
use App\Contracts\Http\Api\Field\UpdatableField;
|
||||
use App\Enums\Http\Api\Filter\AllowedDateFormat;
|
||||
use App\Http\Api\Field\DateField;
|
||||
use App\Http\Api\Schema\Schema;
|
||||
use App\Models\Admin\FeaturedTheme;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Class FeaturedThemeStartAtField.
|
||||
*/
|
||||
class FeaturedThemeStartAtField extends DateField implements CreatableField, UpdatableField
|
||||
{
|
||||
/**
|
||||
* Create a new field instance.
|
||||
*
|
||||
* @param Schema $schema
|
||||
*/
|
||||
public function __construct(Schema $schema)
|
||||
{
|
||||
parent::__construct($schema, FeaturedTheme::ATTRIBUTE_START_AT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the creation validation rules for the field.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return array
|
||||
*/
|
||||
public function getCreationRules(Request $request): array
|
||||
{
|
||||
return [
|
||||
'required',
|
||||
Str::of('date_format:')
|
||||
->append(implode(',', AllowedDateFormat::getValues()))
|
||||
->__toString(),
|
||||
Str::of('before:')
|
||||
->append($this->resolveEndAt($request))
|
||||
->__toString(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the update validation rules for the field.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return array
|
||||
*/
|
||||
public function getUpdateRules(Request $request): array
|
||||
{
|
||||
return [
|
||||
'sometimes',
|
||||
'required',
|
||||
Str::of('date_format:')
|
||||
->append(implode(',', AllowedDateFormat::getValues()))
|
||||
->__toString(),
|
||||
Str::of('before:')
|
||||
->append($this->resolveEndAt($request))
|
||||
->__toString(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get dependent end_at field.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return string|null
|
||||
*/
|
||||
private function resolveEndAt(Request $request): ?string
|
||||
{
|
||||
if ($request->has(FeaturedTheme::ATTRIBUTE_END_AT)) {
|
||||
return $request->get(FeaturedTheme::ATTRIBUTE_END_AT);
|
||||
}
|
||||
|
||||
/** @var FeaturedTheme|null $featuredTheme */
|
||||
$featuredTheme = $request->route('featuredtheme');
|
||||
|
||||
return $featuredTheme?->end_at?->format(AllowedDateFormat::YMDHISU);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Api\Field\Admin\FeaturedTheme;
|
||||
|
||||
use App\Contracts\Http\Api\Field\CreatableField;
|
||||
use App\Contracts\Http\Api\Field\SelectableField;
|
||||
use App\Contracts\Http\Api\Field\UpdatableField;
|
||||
use App\Http\Api\Field\Field;
|
||||
use App\Http\Api\Query\Query;
|
||||
use App\Http\Api\Schema\Schema;
|
||||
use App\Models\Admin\FeaturedTheme;
|
||||
use App\Models\Auth\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
/**
|
||||
* Class FeaturedThemeUserIdField.
|
||||
*/
|
||||
class FeaturedThemeUserIdField extends Field implements CreatableField, SelectableField, UpdatableField
|
||||
{
|
||||
/**
|
||||
* Create a new field instance.
|
||||
*
|
||||
* @param Schema $schema
|
||||
*/
|
||||
public function __construct(Schema $schema)
|
||||
{
|
||||
parent::__construct($schema, FeaturedTheme::ATTRIBUTE_USER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the creation validation rules for the field.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return array
|
||||
*/
|
||||
public function getCreationRules(Request $request): array
|
||||
{
|
||||
return [
|
||||
'sometimes',
|
||||
'required',
|
||||
'integer',
|
||||
Rule::exists(User::TABLE, User::ATTRIBUTE_ID),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the field should be included in the select clause of our query.
|
||||
*
|
||||
* @param Query $query
|
||||
* @param Schema $schema
|
||||
* @return bool
|
||||
*/
|
||||
public function shouldSelect(Query $query, Schema $schema): bool
|
||||
{
|
||||
// Needed to match user relation.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the update validation rules for the field.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return array
|
||||
*/
|
||||
public function getUpdateRules(Request $request): array
|
||||
{
|
||||
return [
|
||||
'sometimes',
|
||||
'required',
|
||||
'integer',
|
||||
Rule::exists(User::TABLE, User::ATTRIBUTE_ID),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Api\Field\Admin\FeaturedTheme;
|
||||
|
||||
use App\Contracts\Http\Api\Field\CreatableField;
|
||||
use App\Contracts\Http\Api\Field\SelectableField;
|
||||
use App\Contracts\Http\Api\Field\UpdatableField;
|
||||
use App\Http\Api\Field\Field;
|
||||
use App\Http\Api\Query\Query;
|
||||
use App\Http\Api\Schema\Schema;
|
||||
use App\Models\Admin\FeaturedTheme;
|
||||
use App\Models\Wiki\Video;
|
||||
use App\Pivots\Wiki\AnimeThemeEntryVideo;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
/**
|
||||
* Class FeaturedThemeVideoIdField.
|
||||
*/
|
||||
class FeaturedThemeVideoIdField extends Field implements CreatableField, SelectableField, UpdatableField
|
||||
{
|
||||
/**
|
||||
* Create a new field instance.
|
||||
*
|
||||
* @param Schema $schema
|
||||
*/
|
||||
public function __construct(Schema $schema)
|
||||
{
|
||||
parent::__construct($schema, FeaturedTheme::ATTRIBUTE_VIDEO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the creation validation rules for the field.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return array
|
||||
*/
|
||||
public function getCreationRules(Request $request): array
|
||||
{
|
||||
$entryId = $this->resolveEntryId($request);
|
||||
|
||||
return [
|
||||
'sometimes',
|
||||
'required',
|
||||
'integer',
|
||||
Rule::exists(Video::TABLE, Video::ATTRIBUTE_ID),
|
||||
Rule::when(
|
||||
! empty($entryId),
|
||||
[
|
||||
Rule::exists(AnimeThemeEntryVideo::TABLE, AnimeThemeEntryVideo::ATTRIBUTE_VIDEO)
|
||||
->where(AnimeThemeEntryVideo::ATTRIBUTE_ENTRY, $this->resolveEntryId($request)),
|
||||
]
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the field should be included in the select clause of our query.
|
||||
*
|
||||
* @param Query $query
|
||||
* @param Schema $schema
|
||||
* @return bool
|
||||
*/
|
||||
public function shouldSelect(Query $query, Schema $schema): bool
|
||||
{
|
||||
// Needed to match video relation.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the update validation rules for the field.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return array
|
||||
*/
|
||||
public function getUpdateRules(Request $request): array
|
||||
{
|
||||
$entryId = $this->resolveEntryId($request);
|
||||
|
||||
return [
|
||||
'sometimes',
|
||||
'required',
|
||||
'integer',
|
||||
Rule::exists(Video::TABLE, Video::ATTRIBUTE_ID),
|
||||
Rule::when(
|
||||
! empty($entryId),
|
||||
[
|
||||
Rule::exists(AnimeThemeEntryVideo::TABLE, AnimeThemeEntryVideo::ATTRIBUTE_VIDEO)
|
||||
->where(AnimeThemeEntryVideo::ATTRIBUTE_ENTRY, $this->resolveEntryId($request)),
|
||||
]
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get dependent entry_id field.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return mixed
|
||||
*/
|
||||
private function resolveEntryId(Request $request): mixed
|
||||
{
|
||||
if ($request->has(FeaturedTheme::ATTRIBUTE_ENTRY)) {
|
||||
return $request->get(FeaturedTheme::ATTRIBUTE_ENTRY);
|
||||
}
|
||||
|
||||
/** @var FeaturedTheme|null $featuredTheme */
|
||||
$featuredTheme = $request->route('featuredtheme');
|
||||
|
||||
return $featuredTheme?->entry_id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Api\Schema\Admin;
|
||||
|
||||
use App\Http\Api\Field\Admin\FeaturedTheme\FeaturedThemeEndAtField;
|
||||
use App\Http\Api\Field\Admin\FeaturedTheme\FeaturedThemeEntryIdField;
|
||||
use App\Http\Api\Field\Admin\FeaturedTheme\FeaturedThemeStartAtField;
|
||||
use App\Http\Api\Field\Admin\FeaturedTheme\FeaturedThemeUserIdField;
|
||||
use App\Http\Api\Field\Admin\FeaturedTheme\FeaturedThemeVideoIdField;
|
||||
use App\Http\Api\Field\Base\IdField;
|
||||
use App\Http\Api\Field\Field;
|
||||
use App\Http\Api\Include\AllowedInclude;
|
||||
use App\Http\Api\Schema\Auth\UserSchema;
|
||||
use App\Http\Api\Schema\EloquentSchema;
|
||||
use App\Http\Api\Schema\Wiki\Anime\Theme\EntrySchema;
|
||||
use App\Http\Api\Schema\Wiki\AnimeSchema;
|
||||
use App\Http\Api\Schema\Wiki\ArtistSchema;
|
||||
use App\Http\Api\Schema\Wiki\ImageSchema;
|
||||
use App\Http\Api\Schema\Wiki\SongSchema;
|
||||
use App\Http\Api\Schema\Wiki\VideoSchema;
|
||||
use App\Http\Resources\Admin\Resource\FeaturedThemeResource;
|
||||
use App\Models\Admin\FeaturedTheme;
|
||||
|
||||
/**
|
||||
* Class FeaturedThemeSchema.
|
||||
*/
|
||||
class FeaturedThemeSchema extends EloquentSchema
|
||||
{
|
||||
/**
|
||||
* Get the type of the resource.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function type(): string
|
||||
{
|
||||
return FeaturedThemeResource::$wrap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the allowed includes.
|
||||
*
|
||||
* @return AllowedInclude[]
|
||||
*/
|
||||
public function allowedIncludes(): array
|
||||
{
|
||||
return [
|
||||
new AllowedInclude(new AnimeSchema(), FeaturedTheme::RELATION_ANIME),
|
||||
new AllowedInclude(new ArtistSchema(), FeaturedTheme::RELATION_ARTISTS),
|
||||
new AllowedInclude(new EntrySchema(), FeaturedTheme::RELATION_ENTRY),
|
||||
new AllowedInclude(new ImageSchema(), FeaturedTheme::RELATION_IMAGES),
|
||||
new AllowedInclude(new SongSchema(), FeaturedTheme::RELATION_SONG),
|
||||
new AllowedInclude(new UserSchema(), FeaturedTheme::RELATION_USER),
|
||||
new AllowedInclude(new VideoSchema(), FeaturedTheme::RELATION_VIDEO),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the direct fields of the resource.
|
||||
*
|
||||
* @return Field[]
|
||||
*/
|
||||
public function fields(): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::fields(),
|
||||
[
|
||||
new IdField($this, FeaturedTheme::ATTRIBUTE_ID),
|
||||
new FeaturedThemeStartAtField($this),
|
||||
new FeaturedThemeEndAtField($this),
|
||||
new FeaturedThemeEntryIdField($this),
|
||||
new FeaturedThemeUserIdField($this),
|
||||
new FeaturedThemeVideoIdField($this),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api\Admin;
|
||||
|
||||
use App\Actions\Http\Api\ShowAction;
|
||||
use App\Contracts\Http\Api\InteractsWithSchema;
|
||||
use App\Enums\Http\Api\Filter\ComparisonOperator;
|
||||
use App\Http\Api\Query\Query;
|
||||
use App\Http\Api\Schema\Admin\FeaturedThemeSchema;
|
||||
use App\Http\Api\Schema\Schema;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Api\ShowRequest;
|
||||
use App\Http\Resources\Admin\Resource\FeaturedThemeResource;
|
||||
use App\Models\Admin\FeaturedTheme;
|
||||
use Illuminate\Support\Facades\Date;
|
||||
|
||||
/**
|
||||
* Class CurrentFeaturedThemeController.
|
||||
*/
|
||||
class CurrentFeaturedThemeController extends Controller implements InteractsWithSchema
|
||||
{
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param ShowRequest $request
|
||||
* @param ShowAction $action
|
||||
* @return FeaturedThemeResource
|
||||
*/
|
||||
public function show(ShowRequest $request, ShowAction $action): FeaturedThemeResource
|
||||
{
|
||||
$query = new Query($request->validated());
|
||||
|
||||
$featuredtheme = FeaturedTheme::query()
|
||||
->whereNotNull(FeaturedTheme::ATTRIBUTE_START_AT)
|
||||
->whereNotNull(FeaturedTheme::ATTRIBUTE_END_AT)
|
||||
->whereDate(FeaturedTheme::ATTRIBUTE_START_AT, ComparisonOperator::LTE, Date::now())
|
||||
->whereDate(FeaturedTheme::ATTRIBUTE_END_AT, ComparisonOperator::GT, Date::now())
|
||||
->firstOrFail();
|
||||
|
||||
$show = $action->show($featuredtheme, $query, $request->schema());
|
||||
|
||||
return new FeaturedThemeResource($show, $query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the underlying schema.
|
||||
*
|
||||
* @return Schema
|
||||
*/
|
||||
public function schema(): Schema
|
||||
{
|
||||
return new FeaturedThemeSchema();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api\Admin;
|
||||
|
||||
use App\Actions\Http\Api\DestroyAction;
|
||||
use App\Actions\Http\Api\ForceDeleteAction;
|
||||
use App\Actions\Http\Api\IndexAction;
|
||||
use App\Actions\Http\Api\RestoreAction;
|
||||
use App\Actions\Http\Api\ShowAction;
|
||||
use App\Actions\Http\Api\StoreAction;
|
||||
use App\Actions\Http\Api\UpdateAction;
|
||||
use App\Enums\Http\Api\Filter\ComparisonOperator;
|
||||
use App\Http\Api\Query\Query;
|
||||
use App\Http\Controllers\Api\BaseController;
|
||||
use App\Http\Requests\Api\IndexRequest;
|
||||
use App\Http\Requests\Api\ShowRequest;
|
||||
use App\Http\Requests\Api\StoreRequest;
|
||||
use App\Http\Requests\Api\UpdateRequest;
|
||||
use App\Http\Resources\Admin\Collection\FeaturedThemeCollection;
|
||||
use App\Http\Resources\Admin\Resource\FeaturedThemeResource;
|
||||
use App\Models\Admin\FeaturedTheme;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Date;
|
||||
|
||||
/**
|
||||
* Class FeaturedThemeController.
|
||||
*/
|
||||
class FeaturedThemeController extends BaseController
|
||||
{
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(FeaturedTheme::class, 'featuredtheme');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @param IndexRequest $request
|
||||
* @param IndexAction $action
|
||||
* @return FeaturedThemeCollection
|
||||
*/
|
||||
public function index(IndexRequest $request, IndexAction $action): FeaturedThemeCollection
|
||||
{
|
||||
$query = new Query($request->validated());
|
||||
|
||||
$builder = FeaturedTheme::query()
|
||||
->whereNotNull(FeaturedTheme::ATTRIBUTE_START_AT)
|
||||
->whereDate(FeaturedTheme::ATTRIBUTE_START_AT, ComparisonOperator::LTE, Date::now());
|
||||
|
||||
$featuredthemes = $action->index($builder, $query, $request->schema());
|
||||
|
||||
return new FeaturedThemeCollection($featuredthemes, $query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource.
|
||||
*
|
||||
* @param StoreRequest $request
|
||||
* @param StoreAction $action
|
||||
* @return FeaturedThemeResource
|
||||
*/
|
||||
public function store(StoreRequest $request, StoreAction $action): FeaturedThemeResource
|
||||
{
|
||||
$featuredtheme = $action->store(FeaturedTheme::query(), $request->validated());
|
||||
|
||||
return new FeaturedThemeResource($featuredtheme, new Query());
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param ShowRequest $request
|
||||
* @param FeaturedTheme $featuredtheme
|
||||
* @param ShowAction $action
|
||||
* @return FeaturedThemeResource
|
||||
*/
|
||||
public function show(ShowRequest $request, FeaturedTheme $featuredtheme, ShowAction $action): FeaturedThemeResource
|
||||
{
|
||||
$query = new Query($request->validated());
|
||||
|
||||
$show = $action->show($featuredtheme, $query, $request->schema());
|
||||
|
||||
return new FeaturedThemeResource($show, $query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource.
|
||||
*
|
||||
* @param UpdateRequest $request
|
||||
* @param FeaturedTheme $featuredtheme
|
||||
* @param UpdateAction $action
|
||||
* @return FeaturedThemeResource
|
||||
*/
|
||||
public function update(UpdateRequest $request, FeaturedTheme $featuredtheme, UpdateAction $action): FeaturedThemeResource
|
||||
{
|
||||
$updated = $action->update($featuredtheme, $request->validated());
|
||||
|
||||
return new FeaturedThemeResource($updated, new Query());
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource.
|
||||
*
|
||||
* @param FeaturedTheme $featuredtheme
|
||||
* @param DestroyAction $action
|
||||
* @return FeaturedThemeResource
|
||||
*/
|
||||
public function destroy(FeaturedTheme $featuredtheme, DestroyAction $action): FeaturedThemeResource
|
||||
{
|
||||
$deleted = $action->destroy($featuredtheme);
|
||||
|
||||
return new FeaturedThemeResource($deleted, new Query());
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the specified resource.
|
||||
*
|
||||
* @param FeaturedTheme $featuredtheme
|
||||
* @param RestoreAction $action
|
||||
* @return FeaturedThemeResource
|
||||
*/
|
||||
public function restore(FeaturedTheme $featuredtheme, RestoreAction $action): FeaturedThemeResource
|
||||
{
|
||||
$restored = $action->restore($featuredtheme);
|
||||
|
||||
return new FeaturedThemeResource($restored, new Query());
|
||||
}
|
||||
|
||||
/**
|
||||
* Hard-delete the specified resource.
|
||||
*
|
||||
* @param FeaturedTheme $featuredtheme
|
||||
* @param ForceDeleteAction $action
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function forceDelete(FeaturedTheme $featuredtheme, ForceDeleteAction $action): JsonResponse
|
||||
{
|
||||
$message = $action->forceDelete($featuredtheme);
|
||||
|
||||
return new JsonResponse([
|
||||
'message' => $message,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources\Admin\Collection;
|
||||
|
||||
use App\Http\Resources\Admin\Resource\FeaturedThemeResource;
|
||||
use App\Http\Resources\BaseCollection;
|
||||
use App\Models\Admin\FeaturedTheme;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* Class FeaturedThemeCollection.
|
||||
*/
|
||||
class FeaturedThemeCollection extends BaseCollection
|
||||
{
|
||||
/**
|
||||
* The "data" wrapper that should be applied.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public static $wrap = 'featuredthemes';
|
||||
|
||||
/**
|
||||
* Transform the resource collection into an array.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return array
|
||||
*
|
||||
* @noinspection PhpMissingParentCallCommonInspection
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return $this->collection->map(
|
||||
fn (FeaturedTheme $featuredTheme) => new FeaturedThemeResource($featuredTheme, $this->query)
|
||||
)->all();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources\Admin\Resource;
|
||||
|
||||
use App\Http\Api\Schema\Admin\FeaturedThemeSchema;
|
||||
use App\Http\Api\Schema\Schema;
|
||||
use App\Http\Resources\BaseResource;
|
||||
|
||||
/**
|
||||
* Class FeaturedThemeResource.
|
||||
*/
|
||||
class FeaturedThemeResource extends BaseResource
|
||||
{
|
||||
/**
|
||||
* The "data" wrapper that should be applied.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public static $wrap = 'featuredtheme';
|
||||
|
||||
/**
|
||||
* Get the resource schema.
|
||||
*
|
||||
* @return Schema
|
||||
*/
|
||||
protected function schema(): Schema
|
||||
{
|
||||
return new FeaturedThemeSchema();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models\Admin;
|
||||
|
||||
use App\Enums\Http\Api\Filter\AllowedDateFormat;
|
||||
use App\Events\Admin\FeaturedTheme\FeaturedThemeCreated;
|
||||
use App\Events\Admin\FeaturedTheme\FeaturedThemeDeleted;
|
||||
use App\Events\Admin\FeaturedTheme\FeaturedThemeRestored;
|
||||
use App\Events\Admin\FeaturedTheme\FeaturedThemeUpdated;
|
||||
use App\Models\Auth\User;
|
||||
use App\Models\BaseModel;
|
||||
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
|
||||
use App\Models\Wiki\Video;
|
||||
use Database\Factories\Admin\FeaturedThemeFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Nova\Actions\Actionable;
|
||||
|
||||
/**
|
||||
* Class FeaturedTheme.
|
||||
*
|
||||
* @property Carbon|null $end_at
|
||||
* @property AnimeThemeEntry|null $entry
|
||||
* @property int $entry_id
|
||||
* @property int $feature_id
|
||||
* @property Carbon|null $start_at
|
||||
* @property User|null $user
|
||||
* @property int|null $user_id
|
||||
* @property Video|null $video
|
||||
* @property int|null $video_id
|
||||
*
|
||||
* @method static FeaturedThemeFactory factory(...$parameters)
|
||||
*/
|
||||
class FeaturedTheme extends BaseModel
|
||||
{
|
||||
use Actionable;
|
||||
|
||||
final public const TABLE = 'featured_themes';
|
||||
|
||||
final public const ATTRIBUTE_END_AT = 'end_at';
|
||||
final public const ATTRIBUTE_ENTRY = 'entry_id';
|
||||
final public const ATTRIBUTE_ID = 'featured_theme_id';
|
||||
final public const ATTRIBUTE_START_AT = 'start_at';
|
||||
final public const ATTRIBUTE_USER = 'user_id';
|
||||
final public const ATTRIBUTE_VIDEO = 'video_id';
|
||||
|
||||
final public const RELATION_ANIME = 'animethemeentry.animetheme.anime';
|
||||
final public const RELATION_ARTISTS = 'animethemeentry.animetheme.song.artists';
|
||||
final public const RELATION_ENTRY = 'animethemeentry';
|
||||
final public const RELATION_IMAGES = 'animethemeentry.animetheme.anime.images';
|
||||
final public const RELATION_SONG = 'animethemeentry.animetheme.song';
|
||||
final public const RELATION_USER = 'user';
|
||||
final public const RELATION_VIDEO = 'video';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $fillable = [
|
||||
FeaturedTheme::ATTRIBUTE_END_AT,
|
||||
FeaturedTheme::ATTRIBUTE_ENTRY,
|
||||
FeaturedTheme::ATTRIBUTE_START_AT,
|
||||
FeaturedTheme::ATTRIBUTE_USER,
|
||||
FeaturedTheme::ATTRIBUTE_VIDEO,
|
||||
];
|
||||
|
||||
/**
|
||||
* The event map for the model.
|
||||
*
|
||||
* Allows for object-based events for native Eloquent events.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dispatchesEvents = [
|
||||
'created' => FeaturedThemeCreated::class,
|
||||
'deleted' => FeaturedThemeDeleted::class,
|
||||
'restored' => FeaturedThemeRestored::class,
|
||||
'updated' => FeaturedThemeUpdated::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = FeaturedTheme::TABLE;
|
||||
|
||||
/**
|
||||
* The primary key associated with the table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = FeaturedTheme::ATTRIBUTE_ID;
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
FeaturedTheme::ATTRIBUTE_END_AT => 'datetime',
|
||||
FeaturedTheme::ATTRIBUTE_START_AT => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return Str::of($this->start_at->format(AllowedDateFormat::YMD))
|
||||
->append(' - ')
|
||||
->append($this->end_at->format(AllowedDateFormat::YMD))
|
||||
->__toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user that recommended the featured theme.
|
||||
*
|
||||
* @return BelongsTo
|
||||
*/
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, FeaturedTheme::ATTRIBUTE_USER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entry for the featured video.
|
||||
*
|
||||
* @return BelongsTo
|
||||
*/
|
||||
public function animethemeentry(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(AnimeThemeEntry::class, FeaturedTheme::ATTRIBUTE_ENTRY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the video to feature.
|
||||
*
|
||||
* @return BelongsTo
|
||||
*/
|
||||
public function video(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Video::class, FeaturedTheme::ATTRIBUTE_VIDEO);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Nova\Resources\Admin;
|
||||
|
||||
use App\Enums\Http\Api\Filter\AllowedDateFormat;
|
||||
use App\Models\Admin\FeaturedTheme as FeaturedThemeModel;
|
||||
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
|
||||
use App\Models\Wiki\Video as VideoModel;
|
||||
use App\Nova\Resources\Auth\User;
|
||||
use App\Nova\Resources\BaseResource;
|
||||
use App\Nova\Resources\Wiki\Anime\Theme\Entry;
|
||||
use App\Nova\Resources\Wiki\Video;
|
||||
use Exception;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Nova\Fields\BelongsTo;
|
||||
use Laravel\Nova\Fields\Date;
|
||||
use Laravel\Nova\Fields\FormData;
|
||||
use Laravel\Nova\Fields\ID;
|
||||
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||
use Laravel\Nova\Panel;
|
||||
use Laravel\Nova\Query\Search\SearchableRelation;
|
||||
|
||||
/**
|
||||
* Class FeaturedTheme.
|
||||
*/
|
||||
class FeaturedTheme extends BaseResource
|
||||
{
|
||||
/**
|
||||
* The model the resource corresponds to.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static string $model = FeaturedThemeModel::class;
|
||||
|
||||
/**
|
||||
* Get the value that should be displayed to represent the resource.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function title(): string
|
||||
{
|
||||
$featuredTheme = $this->model();
|
||||
if ($featuredTheme instanceof FeaturedThemeModel) {
|
||||
return $featuredTheme->getName();
|
||||
}
|
||||
|
||||
return parent::title();
|
||||
}
|
||||
|
||||
/**
|
||||
* The logical group associated with the resource.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @noinspection PhpMissingParentCallCommonInspection
|
||||
*/
|
||||
public static function group(): string
|
||||
{
|
||||
return __('nova.resources.group.admin');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the displayable label of the resource.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @noinspection PhpMissingParentCallCommonInspection
|
||||
*/
|
||||
public static function label(): string
|
||||
{
|
||||
return __('nova.resources.label.featured_themes');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the displayable singular label of the resource.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @noinspection PhpMissingParentCallCommonInspection
|
||||
*/
|
||||
public static function singularLabel(): string
|
||||
{
|
||||
return __('nova.resources.singularLabel.featured_theme');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the searchable columns for the resource.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @noinspection PhpMissingParentCallCommonInspection
|
||||
*/
|
||||
public static function searchableColumns(): array
|
||||
{
|
||||
return [
|
||||
new SearchableRelation(FeaturedThemeModel::RELATION_VIDEO, VideoModel::ATTRIBUTE_FILENAME),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the resource should be globally searchable.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public static $globallySearchable = false;
|
||||
|
||||
/**
|
||||
* Determine if this resource uses Laravel Scout.
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @noinspection PhpMissingParentCallCommonInspection
|
||||
*/
|
||||
public static function usesScout(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a "relatable" query for the given resource.
|
||||
*
|
||||
* This query determines which instances of the model may be attached to other resources.
|
||||
*
|
||||
* @param NovaRequest $request
|
||||
* @param Builder $query
|
||||
* @return Builder
|
||||
*
|
||||
* @noinspection PhpMissingParentCallCommonInspection
|
||||
*/
|
||||
public static function indexQuery(NovaRequest $request, $query): Builder
|
||||
{
|
||||
return $query->with([
|
||||
FeaturedThemeModel::RELATION_ENTRY,
|
||||
FeaturedThemeModel::RELATION_USER,
|
||||
FeaturedThemeModel::RELATION_VIDEO,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fields displayed by the resource.
|
||||
*
|
||||
* @param NovaRequest $request
|
||||
* @return array
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function fields(NovaRequest $request): array
|
||||
{
|
||||
return [
|
||||
ID::make(__('nova.fields.base.id'), FeaturedThemeModel::ATTRIBUTE_ID)
|
||||
->sortable()
|
||||
->showOnPreview()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Date::make(__('nova.fields.featured_theme.start_at.name'), FeaturedThemeModel::ATTRIBUTE_START_AT)
|
||||
->sortable()
|
||||
->required()
|
||||
->rules([
|
||||
'required',
|
||||
Str::of('date_format:')
|
||||
->append(implode(',', AllowedDateFormat::getValues()))
|
||||
->__toString(),
|
||||
Str::of('before:')
|
||||
->append(FeaturedThemeModel::ATTRIBUTE_END_AT)
|
||||
->__toString(),
|
||||
])
|
||||
->help(__('nova.fields.featured_theme.start_at.help'))
|
||||
->showOnPreview()
|
||||
->filterable()
|
||||
->showWhenPeeking(),
|
||||
|
||||
Date::make(__('nova.fields.featured_theme.end_at.name'), FeaturedThemeModel::ATTRIBUTE_END_AT)
|
||||
->sortable()
|
||||
->required()
|
||||
->rules([
|
||||
'required',
|
||||
Str::of('date_format:')
|
||||
->append(implode(',', AllowedDateFormat::getValues()))
|
||||
->__toString(),
|
||||
Str::of('after:')
|
||||
->append(FeaturedThemeModel::ATTRIBUTE_START_AT)
|
||||
->__toString(),
|
||||
])
|
||||
->help(__('nova.fields.featured_theme.end_at.help'))
|
||||
->showOnPreview()
|
||||
->filterable()
|
||||
->showWhenPeeking(),
|
||||
|
||||
BelongsTo::make(__('nova.resources.singularLabel.video'), FeaturedThemeModel::RELATION_VIDEO, Video::class)
|
||||
->sortable()
|
||||
->filterable()
|
||||
->searchable()
|
||||
->nullable()
|
||||
->showOnPreview()
|
||||
->dependsOn(
|
||||
[FeaturedThemeModel::RELATION_ENTRY],
|
||||
function (BelongsTo $field, NovaRequest $novaRequest, FormData $formData) {
|
||||
if ($formData->offsetExists(FeaturedThemeModel::RELATION_ENTRY)) {
|
||||
$field->relatableQueryUsing(function (NovaRequest $relatableRequest, Builder $query) use ($formData) {
|
||||
return $query->whereHas(VideoModel::RELATION_ANIMETHEMEENTRIES, function (Builder $relationBuilder) use ($formData) {
|
||||
$relationBuilder->where(AnimeThemeEntry::ATTRIBUTE_ID, $formData->offsetGet(FeaturedThemeModel::RELATION_ENTRY));
|
||||
});
|
||||
});
|
||||
} else {
|
||||
$field->relatableQueryUsing(null);
|
||||
}
|
||||
}
|
||||
),
|
||||
|
||||
BelongsTo::make(__('nova.resources.singularLabel.anime_theme_entry'), FeaturedThemeModel::RELATION_ENTRY, Entry::class)
|
||||
->sortable()
|
||||
->filterable()
|
||||
->searchable()
|
||||
->nullable()
|
||||
->showOnPreview()
|
||||
->dependsOn(
|
||||
[FeaturedThemeModel::RELATION_VIDEO],
|
||||
function (BelongsTo $field, NovaRequest $novaRequest, FormData $formData) {
|
||||
if ($formData->offsetExists(FeaturedThemeModel::RELATION_VIDEO)) {
|
||||
$field->relatableQueryUsing(function (NovaRequest $relatableRequest, Builder $query) use ($formData) {
|
||||
return $query->whereHas(AnimeThemeEntry::RELATION_VIDEOS, function (Builder $relationBuilder) use ($formData) {
|
||||
$relationBuilder->where(VideoModel::ATTRIBUTE_ID, $formData->offsetGet(FeaturedThemeModel::RELATION_VIDEO));
|
||||
});
|
||||
});
|
||||
} else {
|
||||
$field->relatableQueryUsing(null);
|
||||
}
|
||||
}
|
||||
),
|
||||
|
||||
BelongsTo::make(__('nova.resources.singularLabel.user'), FeaturedThemeModel::RELATION_USER, User::class)
|
||||
->sortable()
|
||||
->filterable()
|
||||
->searchable()
|
||||
->nullable()
|
||||
->showOnPreview(),
|
||||
|
||||
Panel::make(__('nova.fields.base.timestamps'), $this->timestamps())
|
||||
->collapsable(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Policies\Admin;
|
||||
|
||||
use App\Enums\Auth\CrudPermission;
|
||||
use App\Enums\Auth\ExtendedCrudPermission;
|
||||
use App\Models\Admin\FeaturedTheme;
|
||||
use App\Models\Auth\User;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
use Illuminate\Support\Facades\Date;
|
||||
use Laravel\Nova\Nova;
|
||||
|
||||
/**
|
||||
* Class FeaturedThemePolicy.
|
||||
*/
|
||||
class FeaturedThemePolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
/**
|
||||
* Determine whether the user can view any models.
|
||||
*
|
||||
* @param User|null $user
|
||||
* @return bool
|
||||
*/
|
||||
public function viewAny(?User $user): bool
|
||||
{
|
||||
return Nova::whenServing(
|
||||
fn (): bool => $user !== null && $user->can(CrudPermission::VIEW()->format(FeaturedTheme::class)),
|
||||
fn (): bool => true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can view the model.
|
||||
*
|
||||
* @param User|null $user
|
||||
* @param FeaturedTheme $featuredtheme
|
||||
* @return bool
|
||||
*/
|
||||
public function view(?User $user, FeaturedTheme $featuredtheme): bool
|
||||
{
|
||||
return Nova::whenServing(
|
||||
fn (): bool => $user !== null && $user->can(CrudPermission::VIEW()->format(FeaturedTheme::class)),
|
||||
fn (): bool => $featuredtheme->start_at->isBefore(Date::now())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can create models.
|
||||
*
|
||||
* @param User $user
|
||||
* @return bool
|
||||
*/
|
||||
public function create(User $user): bool
|
||||
{
|
||||
return $user->can(CrudPermission::CREATE()->format(FeaturedTheme::class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can update the model.
|
||||
*
|
||||
* @param User $user
|
||||
* @param FeaturedTheme $featuredtheme
|
||||
* @return bool
|
||||
*/
|
||||
public function update(User $user, FeaturedTheme $featuredtheme): bool
|
||||
{
|
||||
return ! $featuredtheme->trashed() && $user->can(CrudPermission::UPDATE()->format(FeaturedTheme::class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can delete the model.
|
||||
*
|
||||
* @param User $user
|
||||
* @param FeaturedTheme $featuredtheme
|
||||
* @return bool
|
||||
*/
|
||||
public function delete(User $user, FeaturedTheme $featuredtheme): bool
|
||||
{
|
||||
return ! $featuredtheme->trashed() && $user->can(CrudPermission::DELETE()->format(FeaturedTheme::class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can restore the model.
|
||||
*
|
||||
* @param User $user
|
||||
* @param FeaturedTheme $featuredtheme
|
||||
* @return bool
|
||||
*/
|
||||
public function restore(User $user, FeaturedTheme $featuredtheme): bool
|
||||
{
|
||||
return $featuredtheme->trashed() && $user->can(ExtendedCrudPermission::RESTORE()->format(FeaturedTheme::class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can permanently delete the model.
|
||||
*
|
||||
* @param User $user
|
||||
* @return bool
|
||||
*/
|
||||
public function forceDelete(User $user): bool
|
||||
{
|
||||
return $user->can(ExtendedCrudPermission::FORCE_DELETE()->format(FeaturedTheme::class));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Database\Factories\Admin;
|
||||
|
||||
use App\Enums\Http\Api\Filter\AllowedDateFormat;
|
||||
use App\Models\Admin\FeaturedTheme;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* Class FeaturedThemeFactory.
|
||||
*
|
||||
* @method FeaturedTheme createOne($attributes = [])
|
||||
* @method FeaturedTheme makeOne($attributes = [])
|
||||
*
|
||||
* @extends Factory<FeaturedTheme>
|
||||
*/
|
||||
class FeaturedThemeFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* The name of the factory's corresponding model.
|
||||
*
|
||||
* @var class-string<FeaturedTheme>
|
||||
*/
|
||||
protected $model = FeaturedTheme::class;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
FeaturedTheme::ATTRIBUTE_START_AT => fake()->dateTimeBetween()->format(AllowedDateFormat::YMDHISU),
|
||||
FeaturedTheme::ATTRIBUTE_END_AT => fake()->dateTimeBetween('+1 day', '+30 years')->format(AllowedDateFormat::YMDHISU),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Models\Admin\Feature;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Models\Admin\FeaturedTheme;
|
||||
use App\Models\Auth\User;
|
||||
use App\Models\BaseModel;
|
||||
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
|
||||
use App\Models\Wiki\Video;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
if (! Schema::hasTable(FeaturedTheme::TABLE)) {
|
||||
Schema::create(FeaturedTheme::TABLE, function (Blueprint $table) {
|
||||
$table->id(FeaturedTheme::ATTRIBUTE_ID);
|
||||
$table->timestamps(6);
|
||||
$table->softDeletes(BaseModel::ATTRIBUTE_DELETED_AT, 6);
|
||||
$table->timestamp(FeaturedTheme::ATTRIBUTE_START_AT, 6)->nullable();
|
||||
$table->timestamp(FeaturedTheme::ATTRIBUTE_END_AT, 6)->nullable();
|
||||
|
||||
$table->unsignedBigInteger(FeaturedTheme::ATTRIBUTE_USER)->nullable();
|
||||
$table->foreign(FeaturedTheme::ATTRIBUTE_USER)->references(User::ATTRIBUTE_ID)->on(User::TABLE)->nullOnDelete();
|
||||
|
||||
$table->unsignedBigInteger(FeaturedTheme::ATTRIBUTE_ENTRY)->nullable();
|
||||
$table->foreign(FeaturedTheme::ATTRIBUTE_ENTRY)->references(AnimeThemeEntry::ATTRIBUTE_ID)->on(AnimeThemeEntry::TABLE)->nullOnDelete();
|
||||
|
||||
$table->unsignedBigInteger(FeaturedTheme::ATTRIBUTE_VIDEO)->nullable();
|
||||
$table->foreign(FeaturedTheme::ATTRIBUTE_VIDEO)->references(Video::ATTRIBUTE_ID)->on(Video::TABLE)->nullOnDelete();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists(FeaturedTheme::TABLE);
|
||||
}
|
||||
};
|
||||
@@ -10,6 +10,7 @@ use App\Enums\Auth\SpecialPermission;
|
||||
use App\Models\Admin\Announcement;
|
||||
use App\Models\Admin\Dump;
|
||||
use App\Models\Admin\Feature;
|
||||
use App\Models\Admin\FeaturedTheme;
|
||||
use App\Models\Admin\Setting;
|
||||
use App\Models\Auth\Permission;
|
||||
use App\Models\Auth\Role;
|
||||
@@ -51,6 +52,7 @@ class PermissionSeeder extends Seeder
|
||||
$this->registerResource(Dump::class, ExtendedCrudPermission::getInstances());
|
||||
$this->registerResource(Setting::class, CrudPermission::getInstances());
|
||||
$this->registerResource(Feature::class, CrudPermission::getInstances());
|
||||
$this->registerResource(FeaturedTheme::class, ExtendedCrudPermission::getInstances());
|
||||
|
||||
// Auth Resources
|
||||
$this->registerResource(Permission::class, [CrudPermission::VIEW()]);
|
||||
|
||||
@@ -10,6 +10,7 @@ use App\Enums\Auth\SpecialPermission;
|
||||
use App\Models\Admin\Announcement;
|
||||
use App\Models\Admin\Dump;
|
||||
use App\Models\Admin\Feature;
|
||||
use App\Models\Admin\FeaturedTheme;
|
||||
use App\Models\Admin\Setting;
|
||||
use App\Models\Auth\Permission;
|
||||
use App\Models\Auth\Role;
|
||||
@@ -53,6 +54,7 @@ class AdminSeeder extends RoleSeeder
|
||||
$this->configureResource($role, Dump::class, ExtendedCrudPermission::getInstances());
|
||||
$this->configureResource($role, Feature::class, [CrudPermission::VIEW(), CrudPermission::UPDATE()]);
|
||||
$this->configureResource($role, Setting::class, CrudPermission::getInstances());
|
||||
$this->configureResource($role, FeaturedTheme::class, ExtendedCrudPermission::getInstances());
|
||||
|
||||
// Auth Resources
|
||||
$this->configureResource($role, Permission::class, [CrudPermission::VIEW()]);
|
||||
|
||||
@@ -502,6 +502,16 @@ return [
|
||||
'name' => 'Value',
|
||||
],
|
||||
],
|
||||
'featured_theme' => [
|
||||
'end_at' => [
|
||||
'help' => 'The datetime that the featured theme should stop being featured.',
|
||||
'name' => 'End At',
|
||||
],
|
||||
'start_at' => [
|
||||
'help' => 'The datetime that the featured theme should start being featured.',
|
||||
'name' => 'Start At',
|
||||
],
|
||||
],
|
||||
'image' => [
|
||||
'facet' => [
|
||||
'help' => 'The page component that the image is intended for. Example: Is this a small cover image or a large cover image?',
|
||||
@@ -812,6 +822,7 @@ return [
|
||||
'dumps' => 'Dumps',
|
||||
'external_resources' => 'External Resources',
|
||||
'features' => 'Features',
|
||||
'featured_themes' => 'Featured Themes',
|
||||
'groups' => 'Groups',
|
||||
'images' => 'Images',
|
||||
'members' => 'Members',
|
||||
@@ -841,6 +852,7 @@ return [
|
||||
'dump' => 'Dump',
|
||||
'external_resource' => 'External Resource',
|
||||
'feature' => 'Feature',
|
||||
'featured_theme' => 'Featured Theme',
|
||||
'image' => 'Image',
|
||||
'page' => 'Page',
|
||||
'permission' => 'Permission',
|
||||
|
||||
@@ -22,6 +22,7 @@ parameters:
|
||||
- '#Strict comparison using === between class-string<Laravel\\Nova\\Resource> and null will always evaluate to false.#'
|
||||
- '#Call to an undefined method ProtoneMedia\\LaravelFFMpeg\\Drivers\\PHPFFMpeg::export\(\).#'
|
||||
- '#Call to an undefined method ProtoneMedia\\LaravelFFMpeg\\Drivers\\PHPFFMpeg::getProcessOutput\(\).#'
|
||||
- '#Call to an undefined method Database\\Factories.*::trashed\(\).#'
|
||||
-
|
||||
message: '#Method App\\Rules\\Wiki\\Submission\\SubmissionRule::#'
|
||||
path: app/Rules/Wiki/Submission/SubmissionRule.php
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Http\Controllers\Api\Admin\AnnouncementController;
|
||||
use App\Http\Controllers\Api\Admin\CurrentFeaturedThemeController;
|
||||
use App\Http\Controllers\Api\Admin\DumpController;
|
||||
use App\Http\Controllers\Api\Admin\FeatureController;
|
||||
use App\Http\Controllers\Api\Admin\FeaturedThemeController;
|
||||
use App\Http\Controllers\Api\Auth\User\Me\List\MyPlaylistController;
|
||||
use App\Http\Controllers\Api\Auth\User\Me\MyController;
|
||||
use App\Http\Controllers\Api\Billing\BalanceController;
|
||||
@@ -222,6 +224,9 @@ if (! function_exists('apiPivotResourceUri')) {
|
||||
// Admin Routes
|
||||
apiResource('announcement', AnnouncementController::class);
|
||||
apiResource('dump', DumpController::class);
|
||||
apiResource('featuredtheme', FeaturedThemeController::class);
|
||||
Route::get('current/featuredtheme', [CurrentFeaturedThemeController::class, 'show'])
|
||||
->name('featuredtheme.current.show');
|
||||
Route::apiResource('feature', FeatureController::class)
|
||||
->only(['index', 'show', 'update']);
|
||||
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature\Events\Admin;
|
||||
|
||||
use App\Events\Admin\FeaturedTheme\FeaturedThemeCreated;
|
||||
use App\Events\Admin\FeaturedTheme\FeaturedThemeDeleted;
|
||||
use App\Events\Admin\FeaturedTheme\FeaturedThemeRestored;
|
||||
use App\Events\Admin\FeaturedTheme\FeaturedThemeUpdated;
|
||||
use App\Models\Admin\FeaturedTheme;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* Class FeaturedThemeTest.
|
||||
*/
|
||||
class FeaturedThemeTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* When a Featured Theme is created, a FeaturedThemeCreated event shall be dispatched.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testFeaturedThemeCreatedEventDispatched(): void
|
||||
{
|
||||
FeaturedTheme::factory()->create();
|
||||
|
||||
Event::assertDispatched(FeaturedThemeCreated::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* When a Featured Theme is deleted, a FeaturedThemeDeleted event shall be dispatched.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testFeaturedThemeDeletedEventDispatched(): void
|
||||
{
|
||||
$featuredTheme = FeaturedTheme::factory()->create();
|
||||
|
||||
$featuredTheme->delete();
|
||||
|
||||
Event::assertDispatched(FeaturedThemeDeleted::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* When a Featured Theme is restored, a FeaturedThemeRestored event shall be dispatched.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testFeaturedThemeRestoredEventDispatched(): void
|
||||
{
|
||||
$featuredTheme = FeaturedTheme::factory()->createOne();
|
||||
|
||||
$featuredTheme->restore();
|
||||
|
||||
Event::assertDispatched(FeaturedThemeRestored::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* When a FeaturedTheme is restored, a FeaturedThemeUpdated event shall not be dispatched.
|
||||
* Note: This is a customization that overrides default framework behavior.
|
||||
* An updated event is fired on restore.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testFeaturedThemeRestoresQuietly(): void
|
||||
{
|
||||
$featuredTheme = FeaturedTheme::factory()->createOne();
|
||||
|
||||
$featuredTheme->restore();
|
||||
|
||||
Event::assertNotDispatched(FeaturedThemeUpdated::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* When a Featured Theme is updated, a FeaturedThemeUpdated event shall be dispatched.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testFeaturedThemeUpdatedEventDispatched(): void
|
||||
{
|
||||
$featuredTheme = FeaturedTheme::factory()->createOne();
|
||||
$changes = FeaturedTheme::factory()->makeOne();
|
||||
|
||||
$featuredTheme->fill($changes->getAttributes());
|
||||
$featuredTheme->save();
|
||||
|
||||
Event::assertDispatched(FeaturedThemeUpdated::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* The FeaturedThemeUpdated event shall contain embed fields.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testFeaturedThemeUpdatedEventEmbedFields(): void
|
||||
{
|
||||
$featuredTheme = FeaturedTheme::factory()->createOne();
|
||||
$changes = FeaturedTheme::factory()->makeOne();
|
||||
|
||||
$featuredTheme->fill($changes->getAttributes());
|
||||
$featuredTheme->save();
|
||||
|
||||
Event::assertDispatched(FeaturedThemeUpdated::class, function (FeaturedThemeUpdated $event) {
|
||||
$message = $event->getDiscordMessage();
|
||||
|
||||
return ! empty(Arr::get($message->embed, 'fields'));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature\Http\Api\Admin\FeaturedTheme;
|
||||
|
||||
use App\Http\Api\Field\Field;
|
||||
use App\Http\Api\Include\AllowedInclude;
|
||||
use App\Http\Api\Parser\FieldParser;
|
||||
use App\Http\Api\Parser\IncludeParser;
|
||||
use App\Http\Api\Query\Query;
|
||||
use App\Http\Api\Schema\Admin\FeaturedThemeSchema;
|
||||
use App\Http\Resources\Admin\Resource\FeaturedThemeResource;
|
||||
use App\Models\Admin\FeaturedTheme;
|
||||
use App\Models\Auth\User;
|
||||
use App\Models\Wiki\Anime;
|
||||
use App\Models\Wiki\Anime\AnimeTheme;
|
||||
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
|
||||
use App\Models\Wiki\Artist;
|
||||
use App\Models\Wiki\Image;
|
||||
use App\Models\Wiki\Song;
|
||||
use App\Models\Wiki\Video;
|
||||
use Illuminate\Foundation\Testing\WithFaker;
|
||||
use Illuminate\Support\Collection;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* Class CurrentFeaturedThemeShowTest.
|
||||
*/
|
||||
class CurrentFeaturedThemeShowTest extends TestCase
|
||||
{
|
||||
use WithFaker;
|
||||
|
||||
/**
|
||||
* The Current Featured Theme Show Endpoint shall return a Not Found exception if there are no featured themes.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testNotFoundIfNoFeaturedThemes(): void
|
||||
{
|
||||
$response = $this->get(route('api.featuredtheme.current.show'));
|
||||
|
||||
$response->assertNotFound();
|
||||
}
|
||||
|
||||
/**
|
||||
* The Current Featured Theme Show Endpoint shall return a Not Found exception if the featured theme has no start date.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testNotFoundIfThemeStartAtNull(): void
|
||||
{
|
||||
FeaturedTheme::factory()->create([
|
||||
FeaturedTheme::ATTRIBUTE_START_AT => null,
|
||||
]);
|
||||
|
||||
$response = $this->get(route('api.featuredtheme.current.show'));
|
||||
|
||||
$response->assertNotFound();
|
||||
}
|
||||
|
||||
/**
|
||||
* The Current Featured Theme Show Endpoint shall return a Not Found exception if the featured theme has no end date.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testNotFoundIfThemeEndAtNull(): void
|
||||
{
|
||||
FeaturedTheme::factory()->create([
|
||||
FeaturedTheme::ATTRIBUTE_END_AT => null,
|
||||
]);
|
||||
|
||||
$response = $this->get(route('api.featuredtheme.current.show'));
|
||||
|
||||
$response->assertNotFound();
|
||||
}
|
||||
|
||||
/**
|
||||
* The Current Featured Theme Show Endpoint shall return a Not Found exception if the featured theme starts after today.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testNotFoundIfThemeStartAtAfterNow(): void
|
||||
{
|
||||
FeaturedTheme::factory()->create([
|
||||
FeaturedTheme::ATTRIBUTE_START_AT => $this->faker->dateTimeBetween('+1 day', '+30 years'),
|
||||
]);
|
||||
|
||||
$response = $this->get(route('api.featuredtheme.current.show'));
|
||||
|
||||
$response->assertNotFound();
|
||||
}
|
||||
|
||||
/**
|
||||
* The Current Featured Theme Show Endpoint shall return a Not Found exception if the featured theme ends before today.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testNotFoundIfThemeEndAtBeforeNow(): void
|
||||
{
|
||||
FeaturedTheme::factory()->create([
|
||||
FeaturedTheme::ATTRIBUTE_END_AT => $this->faker->dateTimeBetween(),
|
||||
]);
|
||||
|
||||
$response = $this->get(route('api.featuredtheme.current.show'));
|
||||
|
||||
$response->assertNotFound();
|
||||
}
|
||||
|
||||
/**
|
||||
* By default, the Current Featured Theme Show Endpoint shall return a Featured Theme Resource.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testDefault(): void
|
||||
{
|
||||
Collection::times($this->faker->randomDigitNotNull(), function () {
|
||||
FeaturedTheme::factory()->create([
|
||||
FeaturedTheme::ATTRIBUTE_START_AT => null,
|
||||
]);
|
||||
});
|
||||
|
||||
Collection::times($this->faker->randomDigitNotNull(), function () {
|
||||
FeaturedTheme::factory()->create([
|
||||
FeaturedTheme::ATTRIBUTE_END_AT => null,
|
||||
]);
|
||||
});
|
||||
|
||||
Collection::times($this->faker->randomDigitNotNull(), function () {
|
||||
FeaturedTheme::factory()->create([
|
||||
FeaturedTheme::ATTRIBUTE_START_AT => $this->faker->dateTimeBetween('+1 day', '+30 years'),
|
||||
]);
|
||||
});
|
||||
|
||||
Collection::times($this->faker->randomDigitNotNull(), function () {
|
||||
FeaturedTheme::factory()->create([
|
||||
FeaturedTheme::ATTRIBUTE_END_AT => $this->faker->dateTimeBetween('-30 years', '-1 day'),
|
||||
]);
|
||||
});
|
||||
|
||||
$currentTheme = FeaturedTheme::factory()->create();
|
||||
|
||||
$response = $this->get(route('api.featuredtheme.current.show'));
|
||||
|
||||
$response->assertJson(
|
||||
json_decode(
|
||||
json_encode(
|
||||
(new FeaturedThemeResource($currentTheme, new Query()))
|
||||
->response()
|
||||
->getData()
|
||||
),
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Current Featured Theme Show Endpoint shall return a Not Found exception if the featured theme is soft deleted.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testSoftDelete(): void
|
||||
{
|
||||
FeaturedTheme::factory()->trashed()->create();
|
||||
|
||||
$response = $this->get(route('api.featuredtheme.current.show'));
|
||||
|
||||
$response->assertNotFound();
|
||||
}
|
||||
|
||||
/**
|
||||
* The Featured Theme Show Endpoint shall allow inclusion of related resources.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testAllowedIncludePaths(): void
|
||||
{
|
||||
$schema = new FeaturedThemeSchema();
|
||||
|
||||
$allowedIncludes = collect($schema->allowedIncludes());
|
||||
|
||||
$selectedIncludes = $allowedIncludes->random($this->faker->numberBetween(1, $allowedIncludes->count()));
|
||||
|
||||
$includedPaths = $selectedIncludes->map(fn (AllowedInclude $include) => $include->path());
|
||||
|
||||
$parameters = [
|
||||
IncludeParser::param() => $includedPaths->join(','),
|
||||
];
|
||||
|
||||
$currentTheme = FeaturedTheme::factory()
|
||||
->for(
|
||||
AnimeThemeEntry::factory()
|
||||
->for(
|
||||
AnimeTheme::factory()
|
||||
->for(Anime::factory()->has(Image::factory()->count($this->faker->randomDigitNotNull())))
|
||||
->for(Song::factory()->has(Artist::factory()->count($this->faker->randomDigitNotNull())))
|
||||
)
|
||||
)
|
||||
->for(Video::factory())
|
||||
->for(User::factory())
|
||||
->createOne();
|
||||
|
||||
$response = $this->get(route('api.featuredtheme.current.show', $parameters));
|
||||
|
||||
$response->assertJson(
|
||||
json_decode(
|
||||
json_encode(
|
||||
(new FeaturedThemeResource($currentTheme, new Query($parameters)))
|
||||
->response()
|
||||
->getData()
|
||||
),
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Current Featured Theme Show Endpoint shall implement sparse fieldsets.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testSparseFieldsets(): void
|
||||
{
|
||||
$schema = new FeaturedThemeSchema();
|
||||
|
||||
$fields = collect($schema->fields());
|
||||
|
||||
$includedFields = $fields->random($this->faker->numberBetween(1, $fields->count()));
|
||||
|
||||
$parameters = [
|
||||
FieldParser::param() => [
|
||||
FeaturedThemeResource::$wrap => $includedFields->map(fn (Field $field) => $field->getKey())->join(','),
|
||||
],
|
||||
];
|
||||
|
||||
$currentTheme = FeaturedTheme::factory()->create();
|
||||
|
||||
$response = $this->get(route('api.featuredtheme.current.show', $parameters));
|
||||
|
||||
$response->assertJson(
|
||||
json_decode(
|
||||
json_encode(
|
||||
(new FeaturedThemeResource($currentTheme, new Query($parameters)))
|
||||
->response()
|
||||
->getData()
|
||||
),
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature\Http\Api\Admin\FeaturedTheme;
|
||||
|
||||
use App\Enums\Auth\CrudPermission;
|
||||
use App\Models\Admin\FeaturedTheme;
|
||||
use App\Models\Auth\User;
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* Class FeaturedThemeDestroyTest.
|
||||
*/
|
||||
class FeaturedThemeDestroyTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* The Featured Theme Destroy Endpoint shall be protected by sanctum.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testProtected(): void
|
||||
{
|
||||
$featuredTheme = FeaturedTheme::factory()->createOne();
|
||||
|
||||
$response = $this->delete(route('api.featuredtheme.destroy', ['featuredtheme' => $featuredTheme]));
|
||||
|
||||
$response->assertUnauthorized();
|
||||
}
|
||||
|
||||
/**
|
||||
* The Featured Theme Destroy Endpoint shall forbid users without the delete featured theme permission.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testForbidden(): void
|
||||
{
|
||||
$featuredTheme = FeaturedTheme::factory()->createOne();
|
||||
|
||||
$user = User::factory()->createOne();
|
||||
|
||||
Sanctum::actingAs($user);
|
||||
|
||||
$response = $this->delete(route('api.featuredtheme.destroy', ['featuredtheme' => $featuredTheme]));
|
||||
|
||||
$response->assertForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* The Featured Theme Destroy Endpoint shall forbid users from updating a featured theme that is trashed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testTrashed(): void
|
||||
{
|
||||
$featuredTheme = FeaturedTheme::factory()->trashed()->createOne();
|
||||
|
||||
$user = User::factory()->withPermissions(CrudPermission::DELETE()->format(FeaturedTheme::class))->createOne();
|
||||
|
||||
Sanctum::actingAs($user);
|
||||
|
||||
$response = $this->delete(route('api.featuredtheme.destroy', ['featuredtheme' => $featuredTheme]));
|
||||
|
||||
$response->assertNotFound();
|
||||
}
|
||||
|
||||
/**
|
||||
* The FeaturedTheme Destroy Endpoint shall delete the featured theme.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testDeleted(): void
|
||||
{
|
||||
$featuredTheme = FeaturedTheme::factory()->createOne();
|
||||
|
||||
$user = User::factory()->withPermissions(CrudPermission::DELETE()->format(FeaturedTheme::class))->createOne();
|
||||
|
||||
Sanctum::actingAs($user);
|
||||
|
||||
$response = $this->delete(route('api.featuredtheme.destroy', ['featuredtheme' => $featuredTheme]));
|
||||
|
||||
$response->assertOk();
|
||||
static::assertSoftDeleted($featuredTheme);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature\Http\Api\Admin\FeaturedTheme;
|
||||
|
||||
use App\Enums\Auth\ExtendedCrudPermission;
|
||||
use App\Models\Admin\FeaturedTheme;
|
||||
use App\Models\Auth\User;
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* Class FeaturedThemeForceDeleteTest.
|
||||
*/
|
||||
class FeaturedThemeForceDeleteTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* The Featured Theme Force Delete Endpoint shall be protected by sanctum.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testProtected(): void
|
||||
{
|
||||
$featuredTheme = FeaturedTheme::factory()->createOne();
|
||||
|
||||
$response = $this->delete(route('api.featuredtheme.forceDelete', ['featuredtheme' => $featuredTheme]));
|
||||
|
||||
$response->assertUnauthorized();
|
||||
}
|
||||
|
||||
/**
|
||||
* The Featured Theme Force Delete Endpoint shall forbid users without the force delete featured theme permission.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testForbidden(): void
|
||||
{
|
||||
$featuredTheme = FeaturedTheme::factory()->createOne();
|
||||
|
||||
$user = User::factory()->createOne();
|
||||
|
||||
Sanctum::actingAs($user);
|
||||
|
||||
$response = $this->delete(route('api.featuredtheme.forceDelete', ['featuredtheme' => $featuredTheme]));
|
||||
|
||||
$response->assertForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* The Featured Theme Force Delete Endpoint shall force delete the featured theme.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testDeleted(): void
|
||||
{
|
||||
$featuredTheme = FeaturedTheme::factory()->createOne();
|
||||
|
||||
$user = User::factory()->withPermissions(ExtendedCrudPermission::FORCE_DELETE()->format(FeaturedTheme::class))->createOne();
|
||||
|
||||
Sanctum::actingAs($user);
|
||||
|
||||
$response = $this->delete(route('api.featuredtheme.forceDelete', ['featuredtheme' => $featuredTheme]));
|
||||
|
||||
$response->assertOk();
|
||||
static::assertModelMissing($featuredTheme);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,466 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature\Http\Api\Admin\FeaturedTheme;
|
||||
|
||||
use App\Concerns\Actions\Http\Api\SortsModels;
|
||||
use App\Contracts\Http\Api\Field\SortableField;
|
||||
use App\Enums\Http\Api\Filter\TrashedStatus;
|
||||
use App\Enums\Http\Api\Sort\Direction;
|
||||
use App\Http\Api\Criteria\Filter\TrashedCriteria;
|
||||
use App\Http\Api\Criteria\Paging\Criteria;
|
||||
use App\Http\Api\Criteria\Paging\OffsetCriteria;
|
||||
use App\Http\Api\Field\Field;
|
||||
use App\Http\Api\Include\AllowedInclude;
|
||||
use App\Http\Api\Parser\FieldParser;
|
||||
use App\Http\Api\Parser\FilterParser;
|
||||
use App\Http\Api\Parser\IncludeParser;
|
||||
use App\Http\Api\Parser\PagingParser;
|
||||
use App\Http\Api\Parser\SortParser;
|
||||
use App\Http\Api\Query\Query;
|
||||
use App\Http\Api\Schema\Admin\FeaturedThemeSchema;
|
||||
use App\Http\Api\Sort\Sort;
|
||||
use App\Http\Resources\Admin\Collection\FeaturedThemeCollection;
|
||||
use App\Http\Resources\Admin\Resource\FeaturedThemeResource;
|
||||
use App\Models\Admin\FeaturedTheme;
|
||||
use App\Models\Auth\User;
|
||||
use App\Models\BaseModel;
|
||||
use App\Models\Wiki\Anime;
|
||||
use App\Models\Wiki\Anime\AnimeTheme;
|
||||
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
|
||||
use App\Models\Wiki\Artist;
|
||||
use App\Models\Wiki\Image;
|
||||
use App\Models\Wiki\Song;
|
||||
use App\Models\Wiki\Video;
|
||||
use Illuminate\Foundation\Testing\WithFaker;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* Class FeaturedThemeIndexTest.
|
||||
*/
|
||||
class FeaturedThemeIndexTest extends TestCase
|
||||
{
|
||||
use SortsModels;
|
||||
use WithFaker;
|
||||
|
||||
/**
|
||||
* By default, the Featured Theme Index Endpoint shall return a collection of Featured Theme Resources.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testDefault(): void
|
||||
{
|
||||
$publicCount = $this->faker->randomDigitNotNull();
|
||||
|
||||
$featuredThemes = FeaturedTheme::factory()->count($publicCount)->create();
|
||||
|
||||
Collection::times($this->faker->randomDigitNotNull(), function () {
|
||||
FeaturedTheme::factory()->create([
|
||||
FeaturedTheme::ATTRIBUTE_START_AT => $this->faker->dateTimeBetween('+1 day', '+30 years'),
|
||||
]);
|
||||
});
|
||||
|
||||
Collection::times($this->faker->randomDigitNotNull(), function () {
|
||||
FeaturedTheme::factory()->create([
|
||||
FeaturedTheme::ATTRIBUTE_START_AT => null,
|
||||
]);
|
||||
});
|
||||
|
||||
$response = $this->get(route('api.featuredtheme.index'));
|
||||
|
||||
$response->assertJsonCount($publicCount, FeaturedThemeCollection::$wrap);
|
||||
|
||||
$response->assertJson(
|
||||
json_decode(
|
||||
json_encode(
|
||||
(new FeaturedThemeCollection($featuredThemes, new Query()))
|
||||
->response()
|
||||
->getData()
|
||||
),
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Featured Theme Index Endpoint shall be paginated.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testPaginated(): void
|
||||
{
|
||||
FeaturedTheme::factory()->count($this->faker->randomDigitNotNull())->create();
|
||||
|
||||
$response = $this->get(route('api.featuredtheme.index'));
|
||||
|
||||
$response->assertJsonStructure([
|
||||
FeaturedThemeCollection::$wrap,
|
||||
'links',
|
||||
'meta',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Featured Theme Index Endpoint shall allow inclusion of related resources.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testAllowedIncludePaths(): void
|
||||
{
|
||||
$schema = new FeaturedThemeSchema();
|
||||
|
||||
$allowedIncludes = collect($schema->allowedIncludes());
|
||||
|
||||
$selectedIncludes = $allowedIncludes->random($this->faker->numberBetween(1, $allowedIncludes->count()));
|
||||
|
||||
$includedPaths = $selectedIncludes->map(fn (AllowedInclude $include) => $include->path());
|
||||
|
||||
$parameters = [
|
||||
IncludeParser::param() => $includedPaths->join(','),
|
||||
];
|
||||
|
||||
FeaturedTheme::factory()
|
||||
->for(
|
||||
AnimeThemeEntry::factory()
|
||||
->for(
|
||||
AnimeTheme::factory()
|
||||
->for(Anime::factory()->has(Image::factory()->count($this->faker->randomDigitNotNull())))
|
||||
->for(Song::factory()->has(Artist::factory()->count($this->faker->randomDigitNotNull())))
|
||||
)
|
||||
)
|
||||
->for(Video::factory())
|
||||
->for(User::factory())
|
||||
->count($this->faker->randomDigitNotNull())
|
||||
->create();
|
||||
|
||||
$featuredThemes = FeaturedTheme::with($includedPaths->all())->get();
|
||||
|
||||
$response = $this->get(route('api.featuredtheme.index', $parameters));
|
||||
|
||||
$response->assertJson(
|
||||
json_decode(
|
||||
json_encode(
|
||||
(new FeaturedThemeCollection($featuredThemes, new Query($parameters)))
|
||||
->response()
|
||||
->getData()
|
||||
),
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Featured Theme Index Endpoint shall implement sparse fieldsets.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testSparseFieldsets(): void
|
||||
{
|
||||
$schema = new FeaturedThemeSchema();
|
||||
|
||||
$fields = collect($schema->fields());
|
||||
|
||||
$includedFields = $fields->random($this->faker->numberBetween(1, $fields->count()));
|
||||
|
||||
$parameters = [
|
||||
FieldParser::param() => [
|
||||
FeaturedThemeResource::$wrap => $includedFields->map(fn (Field $field) => $field->getKey())->join(','),
|
||||
],
|
||||
];
|
||||
|
||||
$featuredThemes = FeaturedTheme::factory()->count($this->faker->randomDigitNotNull())->create();
|
||||
|
||||
$response = $this->get(route('api.featuredtheme.index', $parameters));
|
||||
|
||||
$response->assertJson(
|
||||
json_decode(
|
||||
json_encode(
|
||||
(new FeaturedThemeCollection($featuredThemes, new Query($parameters)))
|
||||
->response()
|
||||
->getData()
|
||||
),
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Featured Theme Index Endpoint shall support sorting resources.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testSorts(): void
|
||||
{
|
||||
$schema = new FeaturedThemeSchema();
|
||||
|
||||
/** @var Sort $sort */
|
||||
$sort = collect($schema->fields())
|
||||
->filter(fn (Field $field) => $field instanceof SortableField)
|
||||
->map(fn (SortableField $field) => $field->getSort())
|
||||
->random();
|
||||
|
||||
$parameters = [
|
||||
SortParser::param() => $sort->format(Direction::getRandomInstance()),
|
||||
];
|
||||
|
||||
$query = new Query($parameters);
|
||||
|
||||
FeaturedTheme::factory()->count($this->faker->randomDigitNotNull())->create();
|
||||
|
||||
$response = $this->get(route('api.featuredtheme.index', $parameters));
|
||||
|
||||
$featuredThemes = $this->sort(FeaturedTheme::query(), $query, $schema)->get();
|
||||
|
||||
$response->assertJson(
|
||||
json_decode(
|
||||
json_encode(
|
||||
(new FeaturedThemeCollection($featuredThemes, $query))
|
||||
->response()
|
||||
->getData()
|
||||
),
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Featured Theme Index Endpoint shall support filtering by created_at.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCreatedAtFilter(): void
|
||||
{
|
||||
$createdFilter = $this->faker->date();
|
||||
$excludedDate = $this->faker->date();
|
||||
|
||||
$parameters = [
|
||||
FilterParser::param() => [
|
||||
BaseModel::ATTRIBUTE_CREATED_AT => $createdFilter,
|
||||
],
|
||||
PagingParser::param() => [
|
||||
OffsetCriteria::SIZE_PARAM => Criteria::MAX_RESULTS,
|
||||
],
|
||||
];
|
||||
|
||||
Carbon::withTestNow($createdFilter, function () {
|
||||
FeaturedTheme::factory()->count($this->faker->randomDigitNotNull())->create();
|
||||
});
|
||||
|
||||
Carbon::withTestNow($excludedDate, function () {
|
||||
FeaturedTheme::factory()->count($this->faker->randomDigitNotNull())->create();
|
||||
});
|
||||
|
||||
$featuredTheme = FeaturedTheme::query()->where(BaseModel::ATTRIBUTE_CREATED_AT, $createdFilter)->get();
|
||||
|
||||
$response = $this->get(route('api.featuredtheme.index', $parameters));
|
||||
|
||||
$response->assertJson(
|
||||
json_decode(
|
||||
json_encode(
|
||||
(new FeaturedThemeCollection($featuredTheme, new Query($parameters)))
|
||||
->response()
|
||||
->getData()
|
||||
),
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Featured Theme Index Endpoint shall support filtering by updated_at.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testUpdatedAtFilter(): void
|
||||
{
|
||||
$updatedFilter = $this->faker->date();
|
||||
$excludedDate = $this->faker->date();
|
||||
|
||||
$parameters = [
|
||||
FilterParser::param() => [
|
||||
BaseModel::ATTRIBUTE_UPDATED_AT => $updatedFilter,
|
||||
],
|
||||
PagingParser::param() => [
|
||||
OffsetCriteria::SIZE_PARAM => Criteria::MAX_RESULTS,
|
||||
],
|
||||
];
|
||||
|
||||
Carbon::withTestNow($updatedFilter, function () {
|
||||
FeaturedTheme::factory()->count($this->faker->randomDigitNotNull())->create();
|
||||
});
|
||||
|
||||
Carbon::withTestNow($excludedDate, function () {
|
||||
FeaturedTheme::factory()->count($this->faker->randomDigitNotNull())->create();
|
||||
});
|
||||
|
||||
$featuredTheme = FeaturedTheme::query()->where(BaseModel::ATTRIBUTE_UPDATED_AT, $updatedFilter)->get();
|
||||
|
||||
$response = $this->get(route('api.featuredtheme.index', $parameters));
|
||||
|
||||
$response->assertJson(
|
||||
json_decode(
|
||||
json_encode(
|
||||
(new FeaturedThemeCollection($featuredTheme, new Query($parameters)))
|
||||
->response()
|
||||
->getData()
|
||||
),
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Featured Theme Index Endpoint shall support filtering by trashed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testWithoutTrashedFilter(): void
|
||||
{
|
||||
$parameters = [
|
||||
FilterParser::param() => [
|
||||
TrashedCriteria::PARAM_VALUE => TrashedStatus::WITHOUT,
|
||||
],
|
||||
PagingParser::param() => [
|
||||
OffsetCriteria::SIZE_PARAM => Criteria::MAX_RESULTS,
|
||||
],
|
||||
];
|
||||
|
||||
FeaturedTheme::factory()->count($this->faker->randomDigitNotNull())->create();
|
||||
|
||||
FeaturedTheme::factory()->trashed()->count($this->faker->randomDigitNotNull())->create();
|
||||
|
||||
$featuredTheme = FeaturedTheme::withoutTrashed()->get();
|
||||
|
||||
$response = $this->get(route('api.featuredtheme.index', $parameters));
|
||||
|
||||
$response->assertJson(
|
||||
json_decode(
|
||||
json_encode(
|
||||
(new FeaturedThemeCollection($featuredTheme, new Query($parameters)))
|
||||
->response()
|
||||
->getData()
|
||||
),
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Featured Theme Index Endpoint shall support filtering by trashed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testWithTrashedFilter(): void
|
||||
{
|
||||
$parameters = [
|
||||
FilterParser::param() => [
|
||||
TrashedCriteria::PARAM_VALUE => TrashedStatus::WITH,
|
||||
],
|
||||
PagingParser::param() => [
|
||||
OffsetCriteria::SIZE_PARAM => Criteria::MAX_RESULTS,
|
||||
],
|
||||
];
|
||||
|
||||
FeaturedTheme::factory()->count($this->faker->randomDigitNotNull())->create();
|
||||
|
||||
FeaturedTheme::factory()->trashed()->count($this->faker->randomDigitNotNull())->create();
|
||||
|
||||
$featuredTheme = FeaturedTheme::withTrashed()->get();
|
||||
|
||||
$response = $this->get(route('api.featuredtheme.index', $parameters));
|
||||
|
||||
$response->assertJson(
|
||||
json_decode(
|
||||
json_encode(
|
||||
(new FeaturedThemeCollection($featuredTheme, new Query($parameters)))
|
||||
->response()
|
||||
->getData()
|
||||
),
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Featured Theme Index Endpoint shall support filtering by trashed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testOnlyTrashedFilter(): void
|
||||
{
|
||||
$parameters = [
|
||||
FilterParser::param() => [
|
||||
TrashedCriteria::PARAM_VALUE => TrashedStatus::ONLY,
|
||||
],
|
||||
PagingParser::param() => [
|
||||
OffsetCriteria::SIZE_PARAM => Criteria::MAX_RESULTS,
|
||||
],
|
||||
];
|
||||
|
||||
FeaturedTheme::factory()->count($this->faker->randomDigitNotNull())->create();
|
||||
|
||||
FeaturedTheme::factory()->trashed()->count($this->faker->randomDigitNotNull())->create();
|
||||
|
||||
$featuredTheme = FeaturedTheme::onlyTrashed()->get();
|
||||
|
||||
$response = $this->get(route('api.featuredtheme.index', $parameters));
|
||||
|
||||
$response->assertJson(
|
||||
json_decode(
|
||||
json_encode(
|
||||
(new FeaturedThemeCollection($featuredTheme, new Query($parameters)))
|
||||
->response()
|
||||
->getData()
|
||||
),
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Featured Theme Index Endpoint shall support filtering by deleted_at.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testDeletedAtFilter(): void
|
||||
{
|
||||
$deletedFilter = $this->faker->date();
|
||||
$excludedDate = $this->faker->date();
|
||||
|
||||
$parameters = [
|
||||
FilterParser::param() => [
|
||||
BaseModel::ATTRIBUTE_DELETED_AT => $deletedFilter,
|
||||
TrashedCriteria::PARAM_VALUE => TrashedStatus::WITH,
|
||||
],
|
||||
PagingParser::param() => [
|
||||
OffsetCriteria::SIZE_PARAM => Criteria::MAX_RESULTS,
|
||||
],
|
||||
];
|
||||
|
||||
Carbon::withTestNow($deletedFilter, function () {
|
||||
FeaturedTheme::factory()->trashed()->count($this->faker->randomDigitNotNull())->create();
|
||||
});
|
||||
|
||||
Carbon::withTestNow($excludedDate, function () {
|
||||
FeaturedTheme::factory()->trashed()->count($this->faker->randomDigitNotNull())->create();
|
||||
});
|
||||
|
||||
$featuredTheme = FeaturedTheme::withTrashed()->where(BaseModel::ATTRIBUTE_DELETED_AT, $deletedFilter)->get();
|
||||
|
||||
$response = $this->get(route('api.featuredtheme.index', $parameters));
|
||||
|
||||
$response->assertJson(
|
||||
json_decode(
|
||||
json_encode(
|
||||
(new FeaturedThemeCollection($featuredTheme, new Query($parameters)))
|
||||
->response()
|
||||
->getData()
|
||||
),
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature\Http\Api\Admin\FeaturedTheme;
|
||||
|
||||
use App\Enums\Auth\ExtendedCrudPermission;
|
||||
use App\Models\Admin\FeaturedTheme;
|
||||
use App\Models\Auth\User;
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* Class FeaturedThemeRestoreTest.
|
||||
*/
|
||||
class FeaturedThemeRestoreTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* The Featured Theme Restore Endpoint shall be protected by sanctum.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testProtected(): void
|
||||
{
|
||||
$featuredTheme = FeaturedTheme::factory()->trashed()->createOne();
|
||||
|
||||
$response = $this->patch(route('api.featuredtheme.restore', ['featuredtheme' => $featuredTheme]));
|
||||
|
||||
$response->assertUnauthorized();
|
||||
}
|
||||
|
||||
/**
|
||||
* The Featured Theme Restore Endpoint shall forbid users without the restore featured theme permission.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testForbidden(): void
|
||||
{
|
||||
$featuredTheme = FeaturedTheme::factory()->trashed()->createOne();
|
||||
|
||||
$user = User::factory()->createOne();
|
||||
|
||||
Sanctum::actingAs($user);
|
||||
|
||||
$response = $this->patch(route('api.featuredtheme.restore', ['featuredtheme' => $featuredTheme]));
|
||||
|
||||
$response->assertForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* The Featured Theme Restore Endpoint shall forbid users from restoring a featured theme that isn't trashed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testTrashed(): void
|
||||
{
|
||||
$featuredTheme = FeaturedTheme::factory()->createOne();
|
||||
|
||||
$user = User::factory()->withPermissions(ExtendedCrudPermission::RESTORE()->format(FeaturedTheme::class))->createOne();
|
||||
|
||||
Sanctum::actingAs($user);
|
||||
|
||||
$response = $this->patch(route('api.featuredtheme.restore', ['featuredtheme' => $featuredTheme]));
|
||||
|
||||
$response->assertForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* The Featured Theme Restore Endpoint shall restore the featured theme.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testRestored(): void
|
||||
{
|
||||
$featuredTheme = FeaturedTheme::factory()->trashed()->createOne();
|
||||
|
||||
$user = User::factory()->withPermissions(ExtendedCrudPermission::RESTORE()->format(FeaturedTheme::class))->createOne();
|
||||
|
||||
Sanctum::actingAs($user);
|
||||
|
||||
$response = $this->patch(route('api.featuredtheme.restore', ['featuredtheme' => $featuredTheme]));
|
||||
|
||||
$response->assertOk();
|
||||
static::assertNotSoftDeleted($featuredTheme);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature\Http\Api\Admin\FeaturedTheme;
|
||||
|
||||
use App\Http\Api\Field\Field;
|
||||
use App\Http\Api\Include\AllowedInclude;
|
||||
use App\Http\Api\Parser\FieldParser;
|
||||
use App\Http\Api\Parser\IncludeParser;
|
||||
use App\Http\Api\Query\Query;
|
||||
use App\Http\Api\Schema\Admin\FeaturedThemeSchema;
|
||||
use App\Http\Resources\Admin\Resource\FeaturedThemeResource;
|
||||
use App\Models\Admin\FeaturedTheme;
|
||||
use App\Models\Auth\User;
|
||||
use App\Models\Wiki\Anime;
|
||||
use App\Models\Wiki\Anime\AnimeTheme;
|
||||
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
|
||||
use App\Models\Wiki\Artist;
|
||||
use App\Models\Wiki\Image;
|
||||
use App\Models\Wiki\Song;
|
||||
use App\Models\Wiki\Video;
|
||||
use Illuminate\Foundation\Testing\WithFaker;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* Class FeaturedThemeShowTest.
|
||||
*/
|
||||
class FeaturedThemeShowTest extends TestCase
|
||||
{
|
||||
use WithFaker;
|
||||
|
||||
/**
|
||||
* The Featured Theme Show Endpoint shall forbid the user from viewing a featured theme whose start date is in the future.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testForbiddenIfFutureStartDate(): void
|
||||
{
|
||||
$featuredTheme = FeaturedTheme::factory()->create([
|
||||
FeaturedTheme::ATTRIBUTE_START_AT => $this->faker->dateTimeBetween('+1 day', '+30 years'),
|
||||
]);
|
||||
|
||||
$response = $this->get(route('api.featuredtheme.show', ['featuredtheme' => $featuredTheme]));
|
||||
|
||||
$response->assertForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* By default, the Featured Theme Show Endpoint shall return a Featured Theme Resource.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testDefault(): void
|
||||
{
|
||||
$featuredTheme = FeaturedTheme::factory()->create();
|
||||
|
||||
$response = $this->get(route('api.featuredtheme.show', ['featuredtheme' => $featuredTheme]));
|
||||
|
||||
$response->assertJson(
|
||||
json_decode(
|
||||
json_encode(
|
||||
(new FeaturedThemeResource($featuredTheme, new Query()))
|
||||
->response()
|
||||
->getData()
|
||||
),
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Featured Theme Show Endpoint shall return a Featured Theme Resource for soft deleted featured themes.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testSoftDelete(): void
|
||||
{
|
||||
$featuredTheme = FeaturedTheme::factory()->createOne();
|
||||
|
||||
$featuredTheme->delete();
|
||||
|
||||
$featuredTheme->unsetRelations();
|
||||
|
||||
$response = $this->get(route('api.featuredtheme.show', ['featuredtheme' => $featuredTheme]));
|
||||
|
||||
$response->assertJson(
|
||||
json_decode(
|
||||
json_encode(
|
||||
(new FeaturedThemeResource($featuredTheme, new Query()))
|
||||
->response()
|
||||
->getData()
|
||||
),
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Featured Theme Show Endpoint shall allow inclusion of related resources.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testAllowedIncludePaths(): void
|
||||
{
|
||||
$schema = new FeaturedThemeSchema();
|
||||
|
||||
$allowedIncludes = collect($schema->allowedIncludes());
|
||||
|
||||
$selectedIncludes = $allowedIncludes->random($this->faker->numberBetween(1, $allowedIncludes->count()));
|
||||
|
||||
$includedPaths = $selectedIncludes->map(fn (AllowedInclude $include) => $include->path());
|
||||
|
||||
$parameters = [
|
||||
IncludeParser::param() => $includedPaths->join(','),
|
||||
];
|
||||
|
||||
$featuredTheme = FeaturedTheme::factory()
|
||||
->for(
|
||||
AnimeThemeEntry::factory()
|
||||
->for(
|
||||
AnimeTheme::factory()
|
||||
->for(Anime::factory()->has(Image::factory()->count($this->faker->randomDigitNotNull())))
|
||||
->for(Song::factory()->has(Artist::factory()->count($this->faker->randomDigitNotNull())))
|
||||
)
|
||||
)
|
||||
->for(Video::factory())
|
||||
->for(User::factory())
|
||||
->createOne();
|
||||
|
||||
$response = $this->get(route('api.featuredtheme.show', ['featuredtheme' => $featuredTheme] + $parameters));
|
||||
|
||||
$response->assertJson(
|
||||
json_decode(
|
||||
json_encode(
|
||||
(new FeaturedThemeResource($featuredTheme, new Query($parameters)))
|
||||
->response()
|
||||
->getData()
|
||||
),
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Featured Theme Show Endpoint shall implement sparse fieldsets.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testSparseFieldsets(): void
|
||||
{
|
||||
$schema = new FeaturedThemeSchema();
|
||||
|
||||
$fields = collect($schema->fields());
|
||||
|
||||
$includedFields = $fields->random($this->faker->numberBetween(1, $fields->count()));
|
||||
|
||||
$parameters = [
|
||||
FieldParser::param() => [
|
||||
FeaturedThemeResource::$wrap => $includedFields->map(fn (Field $field) => $field->getKey())->join(','),
|
||||
],
|
||||
];
|
||||
|
||||
$featuredTheme = FeaturedTheme::factory()->create();
|
||||
|
||||
$response = $this->get(route('api.featuredtheme.show', ['featuredtheme' => $featuredTheme] + $parameters));
|
||||
|
||||
$response->assertJson(
|
||||
json_decode(
|
||||
json_encode(
|
||||
(new FeaturedThemeResource($featuredTheme, new Query($parameters)))
|
||||
->response()
|
||||
->getData()
|
||||
),
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature\Http\Api\Admin\FeaturedTheme;
|
||||
|
||||
use App\Enums\Auth\CrudPermission;
|
||||
use App\Enums\Http\Api\Filter\AllowedDateFormat;
|
||||
use App\Models\Admin\FeaturedTheme;
|
||||
use App\Models\Auth\User;
|
||||
use App\Models\Wiki\Anime;
|
||||
use App\Models\Wiki\Anime\AnimeTheme;
|
||||
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
|
||||
use App\Models\Wiki\Video;
|
||||
use App\Pivots\Wiki\AnimeThemeEntryVideo;
|
||||
use Illuminate\Foundation\Testing\WithFaker;
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* Class FeaturedThemeStoreTest.
|
||||
*/
|
||||
class FeaturedThemeStoreTest extends TestCase
|
||||
{
|
||||
use WithFaker;
|
||||
|
||||
/**
|
||||
* The Featured Theme Store Endpoint shall be protected by sanctum.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testProtected(): void
|
||||
{
|
||||
$featuredTheme = FeaturedTheme::factory()->makeOne();
|
||||
|
||||
$response = $this->post(route('api.featuredtheme.store', $featuredTheme->toArray()));
|
||||
|
||||
$response->assertUnauthorized();
|
||||
}
|
||||
|
||||
/**
|
||||
* The Featured Theme Store Endpoint shall forbid users without the create featured theme permission.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testForbidden(): void
|
||||
{
|
||||
$featuredTheme = FeaturedTheme::factory()->makeOne();
|
||||
|
||||
$user = User::factory()->createOne();
|
||||
|
||||
Sanctum::actingAs($user);
|
||||
|
||||
$response = $this->post(route('api.featuredtheme.store', $featuredTheme->toArray()));
|
||||
|
||||
$response->assertForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* The Featured Theme Store Endpoint shall require the end_at and start_at fields.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testRequiredFields(): void
|
||||
{
|
||||
$user = User::factory()->withPermissions(CrudPermission::CREATE()->format(FeaturedTheme::class))->createOne();
|
||||
|
||||
Sanctum::actingAs($user);
|
||||
|
||||
$response = $this->post(route('api.featuredtheme.store'));
|
||||
|
||||
$response->assertJsonValidationErrors([
|
||||
FeaturedTheme::ATTRIBUTE_END_AT,
|
||||
FeaturedTheme::ATTRIBUTE_START_AT,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Featured Theme Store Endpoint shall require the start_at field to be before the end_at field and vice versa.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testStartAtBeforeEndDate(): void
|
||||
{
|
||||
$parameters = FeaturedTheme::factory()->raw([
|
||||
FeaturedTheme::ATTRIBUTE_START_AT => $this->faker->dateTimeBetween('+1 day', '+30 years')->format(AllowedDateFormat::YMDHISU),
|
||||
FeaturedTheme::ATTRIBUTE_END_AT => $this->faker->dateTimeBetween('-30 years', '-1 day')->format(AllowedDateFormat::YMDHISU),
|
||||
]);
|
||||
|
||||
$user = User::factory()->withPermissions(CrudPermission::CREATE()->format(FeaturedTheme::class))->createOne();
|
||||
|
||||
Sanctum::actingAs($user);
|
||||
|
||||
$response = $this->post(route('api.featuredtheme.store', $parameters));
|
||||
|
||||
$response->assertJsonValidationErrors([
|
||||
FeaturedTheme::ATTRIBUTE_START_AT,
|
||||
FeaturedTheme::ATTRIBUTE_END_AT,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Featured Theme Store Endpoint shall require the entry and video to have an association.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testAnimeThemeEntryVideoExists(): void
|
||||
{
|
||||
$entry = AnimeThemeEntry::factory()
|
||||
->for(AnimeTheme::factory()->for(Anime::factory()))
|
||||
->create();
|
||||
|
||||
$video = Video::factory()->create();
|
||||
|
||||
$parameters = FeaturedTheme::factory()->raw([
|
||||
FeaturedTheme::ATTRIBUTE_ENTRY => $entry->getKey(),
|
||||
FeaturedTheme::ATTRIBUTE_VIDEO => $video->getKey(),
|
||||
]);
|
||||
|
||||
$user = User::factory()->withPermissions(CrudPermission::CREATE()->format(FeaturedTheme::class))->createOne();
|
||||
|
||||
Sanctum::actingAs($user);
|
||||
|
||||
$response = $this->post(route('api.featuredtheme.store', $parameters));
|
||||
|
||||
$response->assertJsonValidationErrors([
|
||||
FeaturedTheme::ATTRIBUTE_ENTRY,
|
||||
FeaturedTheme::ATTRIBUTE_VIDEO,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Featured Theme Store Endpoint shall create a featured theme.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCreate(): void
|
||||
{
|
||||
$entryVideo = AnimeThemeEntryVideo::factory()
|
||||
->for(AnimeThemeEntry::factory()->for(AnimeTheme::factory()->for(Anime::factory())))
|
||||
->for(Video::factory())
|
||||
->createOne();
|
||||
|
||||
$parameters = FeaturedTheme::factory()->raw([
|
||||
FeaturedTheme::ATTRIBUTE_ENTRY => $entryVideo->entry_id,
|
||||
FeaturedTheme::ATTRIBUTE_VIDEO => $entryVideo->video_id,
|
||||
]);
|
||||
|
||||
$user = User::factory()->withPermissions(CrudPermission::CREATE()->format(FeaturedTheme::class))->createOne();
|
||||
|
||||
Sanctum::actingAs($user);
|
||||
|
||||
$response = $this->post(route('api.featuredtheme.store', $parameters));
|
||||
|
||||
$response->assertCreated();
|
||||
static::assertDatabaseCount(FeaturedTheme::TABLE, 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature\Http\Api\Admin\FeaturedTheme;
|
||||
|
||||
use App\Enums\Auth\CrudPermission;
|
||||
use App\Enums\Http\Api\Filter\AllowedDateFormat;
|
||||
use App\Models\Admin\FeaturedTheme;
|
||||
use App\Models\Auth\User;
|
||||
use App\Models\Wiki\Anime;
|
||||
use App\Models\Wiki\Anime\AnimeTheme;
|
||||
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
|
||||
use App\Models\Wiki\Video;
|
||||
use App\Pivots\Wiki\AnimeThemeEntryVideo;
|
||||
use Illuminate\Foundation\Testing\WithFaker;
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* Class FeaturedThemeUpdateTest.
|
||||
*/
|
||||
class FeaturedThemeUpdateTest extends TestCase
|
||||
{
|
||||
use WithFaker;
|
||||
|
||||
/**
|
||||
* The Featured Theme Update Endpoint shall be protected by sanctum.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testProtected(): void
|
||||
{
|
||||
$featuredTheme = FeaturedTheme::factory()->createOne();
|
||||
|
||||
$parameters = FeaturedTheme::factory()->raw();
|
||||
|
||||
$response = $this->put(route('api.featuredtheme.update', ['featuredtheme' => $featuredTheme] + $parameters));
|
||||
|
||||
$response->assertUnauthorized();
|
||||
}
|
||||
|
||||
/**
|
||||
* The Featured Theme Update Endpoint shall forbid users without the update featured theme permission.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testForbidden(): void
|
||||
{
|
||||
$featuredTheme = FeaturedTheme::factory()->createOne();
|
||||
|
||||
$parameters = FeaturedTheme::factory()->raw();
|
||||
|
||||
$user = User::factory()->createOne();
|
||||
|
||||
Sanctum::actingAs($user);
|
||||
|
||||
$response = $this->put(route('api.featuredtheme.update', ['featuredtheme' => $featuredTheme] + $parameters));
|
||||
|
||||
$response->assertForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* The Featured Theme Update Endpoint shall forbid users from updating a featured theme that is trashed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testTrashed(): void
|
||||
{
|
||||
$featuredTheme = FeaturedTheme::factory()->trashed()->createOne();
|
||||
|
||||
$parameters = FeaturedTheme::factory()->raw();
|
||||
|
||||
$user = User::factory()->withPermissions(CrudPermission::UPDATE()->format(FeaturedTheme::class))->createOne();
|
||||
|
||||
Sanctum::actingAs($user);
|
||||
|
||||
$response = $this->put(route('api.featuredtheme.update', ['featuredtheme' => $featuredTheme] + $parameters));
|
||||
|
||||
$response->assertForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* The Featured Update Store Endpoint shall require the start_at field to be before the end_at field and vice versa.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testStartAtBeforeEndDate(): void
|
||||
{
|
||||
$featuredTheme = FeaturedTheme::factory()->createOne();
|
||||
|
||||
$parameters = FeaturedTheme::factory()->raw([
|
||||
FeaturedTheme::ATTRIBUTE_START_AT => $this->faker->dateTimeBetween('+1 day', '+30 years')->format(AllowedDateFormat::YMDHISU),
|
||||
FeaturedTheme::ATTRIBUTE_END_AT => $this->faker->dateTimeBetween('-30 years', '-1 day')->format(AllowedDateFormat::YMDHISU),
|
||||
]);
|
||||
|
||||
$user = User::factory()->withPermissions(CrudPermission::UPDATE()->format(FeaturedTheme::class))->createOne();
|
||||
|
||||
Sanctum::actingAs($user);
|
||||
|
||||
$response = $this->put(route('api.featuredtheme.update', ['featuredtheme' => $featuredTheme] + $parameters));
|
||||
|
||||
$response->assertJsonValidationErrors([
|
||||
FeaturedTheme::ATTRIBUTE_START_AT,
|
||||
FeaturedTheme::ATTRIBUTE_END_AT,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Featured Theme Update Endpoint shall require the entry and video to have an association.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testAnimeThemeEntryVideoExists(): void
|
||||
{
|
||||
$featuredTheme = FeaturedTheme::factory()->createOne();
|
||||
|
||||
$entry = AnimeThemeEntry::factory()
|
||||
->for(AnimeTheme::factory()->for(Anime::factory()))
|
||||
->create();
|
||||
|
||||
$video = Video::factory()->create();
|
||||
|
||||
$parameters = FeaturedTheme::factory()->raw([
|
||||
FeaturedTheme::ATTRIBUTE_ENTRY => $entry->getKey(),
|
||||
FeaturedTheme::ATTRIBUTE_VIDEO => $video->getKey(),
|
||||
]);
|
||||
|
||||
$user = User::factory()->withPermissions(CrudPermission::UPDATE()->format(FeaturedTheme::class))->createOne();
|
||||
|
||||
Sanctum::actingAs($user);
|
||||
|
||||
$response = $this->put(route('api.featuredtheme.update', ['featuredtheme' => $featuredTheme] + $parameters));
|
||||
|
||||
$response->assertJsonValidationErrors([
|
||||
FeaturedTheme::ATTRIBUTE_ENTRY,
|
||||
FeaturedTheme::ATTRIBUTE_VIDEO,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Featured Theme Update Endpoint shall update a featured theme.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testUpdate(): void
|
||||
{
|
||||
$featuredTheme = FeaturedTheme::factory()->createOne();
|
||||
|
||||
$entryVideo = AnimeThemeEntryVideo::factory()
|
||||
->for(AnimeThemeEntry::factory()->for(AnimeTheme::factory()->for(Anime::factory())))
|
||||
->for(Video::factory())
|
||||
->createOne();
|
||||
|
||||
$parameters = FeaturedTheme::factory()->raw([
|
||||
FeaturedTheme::ATTRIBUTE_ENTRY => $entryVideo->entry_id,
|
||||
FeaturedTheme::ATTRIBUTE_VIDEO => $entryVideo->video_id,
|
||||
]);
|
||||
|
||||
$user = User::factory()->withPermissions(CrudPermission::UPDATE()->format(FeaturedTheme::class))->createOne();
|
||||
|
||||
Sanctum::actingAs($user);
|
||||
|
||||
$response = $this->put(route('api.featuredtheme.update', ['featuredtheme' => $featuredTheme] + $parameters));
|
||||
|
||||
$response->assertOk();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature\Jobs\Admin;
|
||||
|
||||
use App\Constants\FeatureConstants;
|
||||
use App\Events\Admin\FeaturedTheme\FeaturedThemeCreated;
|
||||
use App\Events\Admin\FeaturedTheme\FeaturedThemeDeleted;
|
||||
use App\Events\Admin\FeaturedTheme\FeaturedThemeRestored;
|
||||
use App\Events\Admin\FeaturedTheme\FeaturedThemeUpdated;
|
||||
use App\Jobs\SendDiscordNotificationJob;
|
||||
use App\Models\Admin\FeaturedTheme;
|
||||
use Illuminate\Support\Facades\Bus;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Laravel\Pennant\Feature;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* Class FeaturedThemeTest.
|
||||
*/
|
||||
class FeaturedThemeTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* When a featured theme is created, a SendDiscordNotification job shall be dispatched.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testFeaturedThemeCreatedSendsDiscordNotification(): void
|
||||
{
|
||||
Feature::activate(FeatureConstants::ALLOW_DISCORD_NOTIFICATIONS);
|
||||
Bus::fake(SendDiscordNotificationJob::class);
|
||||
Event::fakeExcept(FeaturedThemeCreated::class);
|
||||
|
||||
FeaturedTheme::factory()->createOne();
|
||||
|
||||
Bus::assertDispatched(SendDiscordNotificationJob::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* When a featured theme is deleted, a SendDiscordNotification job shall be dispatched.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testFeaturedThemeDeletedSendsDiscordNotification(): void
|
||||
{
|
||||
$featuredTheme = FeaturedTheme::factory()->createOne();
|
||||
|
||||
Feature::activate(FeatureConstants::ALLOW_DISCORD_NOTIFICATIONS);
|
||||
Bus::fake(SendDiscordNotificationJob::class);
|
||||
Event::fakeExcept(FeaturedThemeDeleted::class);
|
||||
|
||||
$featuredTheme->delete();
|
||||
|
||||
Bus::assertDispatched(SendDiscordNotificationJob::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* When a featured theme is restored, a SendDiscordNotification job shall be dispatched.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testFeaturedThemeRestoredSendsDiscordNotification(): void
|
||||
{
|
||||
$featuredTheme = FeaturedTheme::factory()->createOne();
|
||||
|
||||
Feature::activate(FeatureConstants::ALLOW_DISCORD_NOTIFICATIONS);
|
||||
Bus::fake(SendDiscordNotificationJob::class);
|
||||
Event::fakeExcept(FeaturedThemeRestored::class);
|
||||
|
||||
$featuredTheme->restore();
|
||||
|
||||
Bus::assertDispatched(SendDiscordNotificationJob::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* When a featured theme is updated, a SendDiscordNotification job shall be dispatched.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testFeaturedThemeUpdatedSendsDiscordNotification(): void
|
||||
{
|
||||
$featuredTheme = FeaturedTheme::factory()->createOne();
|
||||
|
||||
Feature::activate(FeatureConstants::ALLOW_DISCORD_NOTIFICATIONS);
|
||||
Bus::fake(SendDiscordNotificationJob::class);
|
||||
Event::fakeExcept(FeaturedThemeUpdated::class);
|
||||
|
||||
$changes = FeaturedTheme::factory()->makeOne();
|
||||
|
||||
$featuredTheme->fill($changes->getAttributes());
|
||||
$featuredTheme->save();
|
||||
|
||||
Bus::assertDispatched(SendDiscordNotificationJob::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Models\Admin;
|
||||
|
||||
use App\Models\Admin\FeaturedTheme;
|
||||
use App\Models\Auth\User;
|
||||
use App\Models\Wiki\Anime;
|
||||
use App\Models\Wiki\Anime\AnimeTheme;
|
||||
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
|
||||
use App\Models\Wiki\Video;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* Class FeaturedThemeTest.
|
||||
*/
|
||||
class FeaturedThemeTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* Featured Themes shall be nameable.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testNameable(): void
|
||||
{
|
||||
$featuredTheme = FeaturedTheme::factory()->createOne();
|
||||
|
||||
static::assertIsString($featuredTheme->getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Featured Themes shall cast the end_at attribute to datetime.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCastsEndAt(): void
|
||||
{
|
||||
$featuredTheme = FeaturedTheme::factory()->createOne();
|
||||
|
||||
static::assertInstanceOf(Carbon::class, $featuredTheme->end_at);
|
||||
}
|
||||
|
||||
/**
|
||||
* Featured Themes shall cast the start_at attribute to datetime.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCastsStartAt(): void
|
||||
{
|
||||
$featuredTheme = FeaturedTheme::factory()->createOne();
|
||||
|
||||
static::assertInstanceOf(Carbon::class, $featuredTheme->start_at);
|
||||
}
|
||||
|
||||
/**
|
||||
* Featured themes shall belong to a User.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testUser(): void
|
||||
{
|
||||
$featuredTheme = FeaturedTheme::factory()
|
||||
->for(User::factory())
|
||||
->createOne();
|
||||
|
||||
static::assertInstanceOf(BelongsTo::class, $featuredTheme->user());
|
||||
static::assertInstanceOf(User::class, $featuredTheme->user()->first());
|
||||
}
|
||||
|
||||
/**
|
||||
* Featured themes shall belong to a Video.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testVideo(): void
|
||||
{
|
||||
$featuredTheme = FeaturedTheme::factory()
|
||||
->for(Video::factory())
|
||||
->createOne();
|
||||
|
||||
static::assertInstanceOf(BelongsTo::class, $featuredTheme->video());
|
||||
static::assertInstanceOf(Video::class, $featuredTheme->video()->first());
|
||||
}
|
||||
|
||||
/**
|
||||
* Featured themes shall belong to an Entry.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testEntry(): void
|
||||
{
|
||||
$featuredTheme = FeaturedTheme::factory()
|
||||
->for(AnimeThemeEntry::factory()->for(AnimeTheme::factory()->for(Anime::factory())))
|
||||
->createOne();
|
||||
|
||||
static::assertInstanceOf(BelongsTo::class, $featuredTheme->animethemeentry());
|
||||
static::assertInstanceOf(AnimeThemeEntry::class, $featuredTheme->animethemeentry()->first());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user