feat: migrating theme groups to an appropriate table (#611) (#659)

This commit is contained in:
Kyrch
2024-04-28 16:23:25 -03:00
committed by GitHub
parent 99153cbf5f
commit e472a1d3bd
36 changed files with 1627 additions and 256 deletions
+68
View File
@@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace App\Events\Wiki\Group;
use App\Contracts\Events\UpdateRelatedIndicesEvent;
use App\Events\Base\Wiki\WikiCreatedEvent;
use App\Models\Wiki\Anime\AnimeTheme;
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
use App\Models\Wiki\Group;
use App\Models\Wiki\Video;
/**
* Class GroupCreated.
*
* @extends WikiCreatedEvent<Group>
*/
class GroupCreated extends WikiCreatedEvent implements UpdateRelatedIndicesEvent
{
/**
* Create a new event instance.
*
* @param Group $group
*/
public function __construct(Group $group)
{
parent::__construct($group);
}
/**
* Get the model that has fired this event.
*
* @return Group
*/
public function getModel(): Group
{
return $this->model;
}
/**
* Get the description for the Discord message payload.
*
* @return string
*/
protected function getDiscordMessageDescription(): string
{
return "Group '**{$this->getModel()->getName()}**' has been created.";
}
/**
* Perform updates on related indices.
*
* @return void
*/
public function updateRelatedIndices(): void
{
$group = $this->getModel()->load(Group::RELATION_VIDEOS);
$group->animethemes->each(function (AnimeTheme $theme) {
$theme->searchable();
$theme->animethemeentries->each(function (AnimeThemeEntry $entry) {
$entry->searchable();
$entry->videos->each(fn (Video $video) => $video->searchable());
});
});
}
}
+91
View File
@@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
namespace App\Events\Wiki\Group;
use App\Contracts\Events\UpdateRelatedIndicesEvent;
use App\Events\Base\Wiki\WikiDeletedEvent;
use App\Models\Wiki\Anime\AnimeTheme;
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
use App\Models\Wiki\Group;
use App\Models\Wiki\Video;
use App\Nova\Resources\Wiki\Group as GroupResource;
/**
* Class GroupDeleted.
*
* @extends WikiDeletedEvent<Group>
*/
class GroupDeleted extends WikiDeletedEvent implements UpdateRelatedIndicesEvent
{
/**
* Create a new event instance.
*
* @param Group $group
*/
public function __construct(Group $group)
{
parent::__construct($group);
}
/**
* Get the model that has fired this event.
*
* @return Group
*/
public function getModel(): Group
{
return $this->model;
}
/**
* Get the description for the Discord message payload.
*
* @return string
*/
protected function getDiscordMessageDescription(): string
{
return "Group '**{$this->getModel()->getName()}**' has been deleted.";
}
/**
* Get the message for the nova notification.
*
* @return string
*/
protected function getNotificationMessage(): string
{
return "Group '{$this->getModel()->getName()}' has been deleted. It will be automatically pruned in one week. Please review.";
}
/**
* Get the URL for the nova notification.
*
* @return string
*/
protected function getNovaNotificationUrl(): string
{
$uriKey = GroupResource::uriKey();
return "/resources/$uriKey/{$this->getModel()->getKey()}";
}
/**
* Perform updates on related indices.
*
* @return void
*/
public function updateRelatedIndices(): void
{
$group = $this->getModel()->load(Group::RELATION_VIDEOS);
$group->animethemes->each(function (AnimeTheme $theme) {
$theme->searchable();
$theme->animethemeentries->each(function (AnimeThemeEntry $entry) {
$entry->searchable();
$entry->videos->each(fn (Video $video) => $video->searchable());
});
});
}
}
+64
View File
@@ -0,0 +1,64 @@
<?php
declare(strict_types=1);
namespace App\Events\Wiki\Group;
use App\Contracts\Events\UpdateRelatedIndicesEvent;
use App\Events\BaseEvent;
use App\Models\Wiki\Anime\AnimeTheme;
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
use App\Models\Wiki\Group;
use App\Models\Wiki\Video;
/**
* Class GroupDeleting.
*
* @extends BaseEvent<Group>
*/
class GroupDeleting extends BaseEvent implements UpdateRelatedIndicesEvent
{
/**
* Create a new event instance.
*
* @param Group $group
*/
public function __construct(Group $group)
{
parent::__construct($group);
}
/**
* Get the model that has fired this event.
*
* @return Group
*/
public function getModel(): Group
{
return $this->model;
}
/**
* Perform cascading deletes.
*
* @return void
*/
public function updateRelatedIndices(): void
{
$group = $this->getModel()->load(Group::RELATION_VIDEOS);
if ($group->isForceDeleting()) {
$group->animethemes->each(function (AnimeTheme $theme) {
AnimeTheme::withoutEvents(function () use ($theme) {
$theme->theme_group()->dissociate();
$theme->save();
});
$theme->searchable();
$theme->animethemeentries->each(function (AnimeThemeEntry $entry) {
$entry->searchable();
$entry->videos->each(fn (Video $video) => $video->searchable());
});
});
}
}
}
+68
View File
@@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace App\Events\Wiki\Group;
use App\Contracts\Events\UpdateRelatedIndicesEvent;
use App\Events\Base\Wiki\WikiRestoredEvent;
use App\Models\Wiki\Anime\AnimeTheme;
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
use App\Models\Wiki\Group;
use App\Models\Wiki\Video;
/**
* Class GroupRestored.
*
* @extends WikiRestoredEvent<Group>
*/
class GroupRestored extends WikiRestoredEvent implements UpdateRelatedIndicesEvent
{
/**
* Create a new event instance.
*
* @param Group $group
*/
public function __construct(Group $group)
{
parent::__construct($group);
}
/**
* Get the model that has fired this event.
*
* @return Group
*/
public function getModel(): Group
{
return $this->model;
}
/**
* Get the description for the Discord message payload.
*
* @return string
*/
protected function getDiscordMessageDescription(): string
{
return "Group '**{$this->getModel()->getName()}**' has been restored.";
}
/**
* Perform cascading deletes.
*
* @return void
*/
public function updateRelatedIndices(): void
{
$group = $this->getModel()->load(Group::RELATION_VIDEOS);
$group->animethemes->each(function (AnimeTheme $theme) {
$theme->searchable();
$theme->animethemeentries->each(function (AnimeThemeEntry $entry) {
$entry->searchable();
$entry->videos->each(fn (Video $video) => $video->searchable());
});
});
}
}
+69
View File
@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
namespace App\Events\Wiki\Group;
use App\Contracts\Events\UpdateRelatedIndicesEvent;
use App\Events\Base\Wiki\WikiUpdatedEvent;
use App\Models\Wiki\Anime\AnimeTheme;
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
use App\Models\Wiki\Group;
use App\Models\Wiki\Video;
/**
* Class GroupUpdated.
*
* @extends WikiUpdatedEvent<Group>
*/
class GroupUpdated extends WikiUpdatedEvent implements UpdateRelatedIndicesEvent
{
/**
* Create a new event instance.
*
* @param Group $group
*/
public function __construct(Group $group)
{
parent::__construct($group);
$this->initializeEmbedFields($group);
}
/**
* Get the model that has fired this event.
*
* @return Group
*/
public function getModel(): Group
{
return $this->model;
}
/**
* Get the description for the Discord message payload.
*
* @return string
*/
protected function getDiscordMessageDescription(): string
{
return "Group '**{$this->getModel()->getName()}**' has been updated.";
}
/**
* Perform updates on related indices.
*
* @return void
*/
public function updateRelatedIndices(): void
{
$group = $this->getModel()->load(Group::RELATION_VIDEOS);
$group->animethemes->each(function (AnimeTheme $theme) {
$theme->searchable();
$theme->animethemeentries->each(function (AnimeThemeEntry $entry) {
$entry->searchable();
$entry->videos->each(fn (Video $video) => $video->searchable());
});
});
}
}
@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace App\Http\Api\Field\Wiki\Anime\Theme;
use App\Contracts\Http\Api\Field\CreatableField;
use App\Contracts\Http\Api\Field\SelectableField;
use App\Http\Api\Field\Field;
use App\Http\Api\Query\Query;
use App\Http\Api\Schema\Schema;
use App\Models\Wiki\Anime\AnimeTheme;
use App\Models\Wiki\Group;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
/**
* Class ThemeGroupIdField.
*/
class ThemeGroupIdField extends Field implements CreatableField, SelectableField
{
/**
* Create a new field instance.
*
* @param Schema $schema
*/
public function __construct(Schema $schema)
{
parent::__construct($schema, AnimeTheme::ATTRIBUTE_THEME_GROUP);
}
/**
* Set the creation validation rules for the field.
*
* @param Request $request
* @return array
*/
public function getCreationRules(Request $request): array
{
return [
'required',
'integer',
Rule::exists(Group::class, Group::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 anime relation.
return true;
}
}
@@ -12,7 +12,7 @@ use App\Models\Wiki\Anime\AnimeTheme;
use Illuminate\Http\Request;
/**
* Class ThemeGroupField.
* Class ThemeSlugField.
*/
class ThemeSlugField extends StringField implements CreatableField, UpdatableField
{
@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace App\Http\Api\Field\Wiki\Group;
use App\Contracts\Http\Api\Field\CreatableField;
use App\Contracts\Http\Api\Field\UpdatableField;
use App\Http\Api\Field\StringField;
use App\Http\Api\Schema\Schema;
use App\Models\Wiki\Group;
use Illuminate\Http\Request;
/**
* Class GroupNameField.
*/
class GroupNameField extends StringField implements CreatableField, UpdatableField
{
/**
* Create a new field instance.
*
* @param Schema $schema
*/
public function __construct(Schema $schema)
{
parent::__construct($schema, Group::ATTRIBUTE_NAME);
}
/**
* Set the creation validation rules for the field.
*
* @param Request $request
* @return array
*/
public function getCreationRules(Request $request): array
{
return [
'required',
'string',
'max:192',
];
}
/**
* Set the update validation rules for the field.
*
* @param Request $request
* @return array
*/
public function getUpdateRules(Request $request): array
{
return [
'sometimes',
'required',
'string',
'max:192',
];
}
}
@@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
namespace App\Http\Api\Field\Wiki\Group;
use App\Contracts\Http\Api\Field\CreatableField;
use App\Contracts\Http\Api\Field\UpdatableField;
use App\Http\Api\Field\StringField;
use App\Http\Api\Schema\Schema;
use App\Models\Wiki\Group;
use Illuminate\Http\Request;
/**
* Class GroupSlugField.
*/
class GroupSlugField extends StringField implements CreatableField, UpdatableField
{
/**
* Create a new field instance.
*
* @param Schema $schema
*/
public function __construct(Schema $schema)
{
parent::__construct($schema, Group::ATTRIBUTE_SLUG);
}
/**
* Set the creation validation rules for the field.
*
* @param Request $request
* @return array
*/
public function getCreationRules(Request $request): array
{
return [
'required',
'string',
'max:192',
'alpha_dash',
];
}
/**
* Set the update validation rules for the field.
*
* @param Request $request
* @return array
*/
public function getUpdateRules(Request $request): array
{
return [
'sometimes',
'required',
'string',
'max:192',
'alpha_dash',
];
}
}
@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace App\Http\Api\Field\Wiki\Group;
use App\Contracts\Http\Api\Field\CreatableField;
use App\Contracts\Http\Api\Field\UpdatableField;
use App\Http\Api\Field\StringField;
use App\Http\Api\Schema\Schema;
use App\Models\Wiki\Group;
use Illuminate\Http\Request;
/**
* Class GroupVideoFilenameField.
*/
class GroupVideoFilenameField extends StringField implements CreatableField, UpdatableField
{
/**
* Create a new field instance.
*
* @param Schema $schema
*/
public function __construct(Schema $schema)
{
parent::__construct($schema, Group::ATTRIBUTE_VIDEO_FILENAME);
}
/**
* Set the creation validation rules for the field.
*
* @param Request $request
* @return array
*/
public function getCreationRules(Request $request): array
{
return [
'required',
'string',
'max:192',
];
}
/**
* Set the update validation rules for the field.
*
* @param Request $request
* @return array
*/
public function getUpdateRules(Request $request): array
{
return [
'sometimes',
'required',
'string',
'max:192',
];
}
}
@@ -9,6 +9,7 @@ use App\Http\Api\Field\Base\IdField;
use App\Http\Api\Field\Field;
use App\Http\Api\Field\Wiki\Anime\Theme\ThemeAnimeIdField;
use App\Http\Api\Field\Wiki\Anime\Theme\ThemeGroupField;
use App\Http\Api\Field\Wiki\Anime\Theme\ThemeGroupIdField;
use App\Http\Api\Field\Wiki\Anime\Theme\ThemeSequenceField;
use App\Http\Api\Field\Wiki\Anime\Theme\ThemeSlugField;
use App\Http\Api\Field\Wiki\Anime\Theme\ThemeSongIdField;
@@ -19,6 +20,7 @@ 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\AudioSchema;
use App\Http\Api\Schema\Wiki\GroupSchema;
use App\Http\Api\Schema\Wiki\ImageSchema;
use App\Http\Api\Schema\Wiki\SongSchema;
use App\Http\Api\Schema\Wiki\VideoSchema;
@@ -58,6 +60,7 @@ class ThemeSchema extends EloquentSchema implements SearchableSchema
new AllowedInclude(new AnimeSchema(), AnimeTheme::RELATION_ANIME),
new AllowedInclude(new ArtistSchema(), AnimeTheme::RELATION_ARTISTS),
new AllowedInclude(new EntrySchema(), AnimeTheme::RELATION_ENTRIES),
new AllowedInclude(new GroupSchema(), AnimeTheme::RELATION_GROUP),
new AllowedInclude(new ImageSchema(), AnimeTheme::RELATION_IMAGES),
new AllowedInclude(new SongSchema(), AnimeTheme::RELATION_SONG),
new AllowedInclude(new VideoSchema(), AnimeTheme::RELATION_VIDEOS),
@@ -83,6 +86,7 @@ class ThemeSchema extends EloquentSchema implements SearchableSchema
new ThemeSlugField($this),
new ThemeTypeField($this),
new ThemeAnimeIdField($this),
new ThemeGroupIdField($this),
new ThemeSongIdField($this),
],
);
+1
View File
@@ -63,6 +63,7 @@ class AnimeSchema extends EloquentSchema implements InteractsWithPivots, Searcha
return [
new AllowedInclude(new ArtistSchema(), Anime::RELATION_ARTISTS),
new AllowedInclude(new AudioSchema(), Anime::RELATION_AUDIO),
new AllowedInclude(new GroupSchema(), Anime::RELATION_GROUPS),
new AllowedInclude(new EntrySchema(), Anime::RELATION_ENTRIES),
new AllowedInclude(new ExternalResourceSchema(), Anime::RELATION_RESOURCES),
new AllowedInclude(new ImageSchema(), Anime::RELATION_IMAGES),
+67
View File
@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
namespace App\Http\Api\Schema\Wiki;
use App\Contracts\Http\Api\Schema\SearchableSchema;
use App\Http\Api\Field\Base\IdField;
use App\Http\Api\Field\Field;
use App\Http\Api\Field\Wiki\Group\GroupNameField;
use App\Http\Api\Field\Wiki\Group\GroupSlugField;
use App\Http\Api\Field\Wiki\Group\GroupVideoFilenameField;
use App\Http\Api\Include\AllowedInclude;
use App\Http\Api\Schema\EloquentSchema;
use App\Http\Api\Schema\Wiki\Anime\ThemeSchema;
use App\Http\Api\Schema\Wiki\AnimeSchema;
use App\Http\Api\Schema\Wiki\VideoSchema;
use App\Http\Resources\Wiki\Resource\GroupResource;
use App\Models\Wiki\Group;
/**
* Class GroupSchema.
*/
class GroupSchema extends EloquentSchema implements SearchableSchema
{
/**
* Get the type of the resource.
*
* @return string
*/
public function type(): string
{
return GroupResource::$wrap;
}
/**
* Get the allowed includes.
*
* @return AllowedInclude[]
*/
public function allowedIncludes(): array
{
return [
new AllowedInclude(new AnimeSchema(), Group::RELATION_ANIME),
new AllowedInclude(new ThemeSchema(), Group::RELATION_THEMES),
new AllowedInclude(new VideoSchema(), Group::RELATION_VIDEOS),
];
}
/**
* Get the direct fields of the resource.
*
* @return Field[]
*/
public function fields(): array
{
return array_merge(
parent::fields(),
[
new IdField($this, Group::ATTRIBUTE_ID),
new GroupNameField($this),
new GroupSlugField($this),
new GroupVideoFilenameField($this),
],
);
}
}
@@ -0,0 +1,145 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api\Wiki;
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\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\Wiki\Collection\GroupCollection;
use App\Http\Resources\Wiki\Resource\GroupResource;
use App\Models\Wiki\Group;
use Illuminate\Http\JsonResponse;
/**
* Class GroupController.
*/
class GroupController extends BaseController
{
/**
* Create a new controller instance.
*/
public function __construct()
{
parent::__construct(Group::class, 'group');
}
/**
* Display a listing of the resource.
*
* @param IndexRequest $request
* @param IndexAction $action
* @return GroupCollection
*/
public function index(IndexRequest $request, IndexAction $action): GroupCollection
{
$query = new Query($request->validated());
$groups = $query->hasSearchCriteria()
? $action->search($query, $request->schema())
: $action->index(Group::query(), $query, $request->schema());
return new GroupCollection($groups, $query);
}
/**
* Store a newly created resource.
*
* @param StoreRequest $request
* @param StoreAction $action
* @return GroupResource
*/
public function store(StoreRequest $request, StoreAction $action): GroupResource
{
$group = $action->store(Group::query(), $request->validated());
return new GroupResource($group, new Query());
}
/**
* Display the specified resource.
*
* @param ShowRequest $request
* @param Group $group
* @param ShowAction $action
* @return GroupResource
*/
public function show(ShowRequest $request, Group $group, ShowAction $action): GroupResource
{
$query = new Query($request->validated());
$show = $action->show($group, $query, $request->schema());
return new GroupResource($show, $query);
}
/**
* Update the specified resource.
*
* @param UpdateRequest $request
* @param Group $group
* @param UpdateAction $action
* @return GroupResource
*/
public function update(UpdateRequest $request, Group $group, UpdateAction $action): GroupResource
{
$updated = $action->update($group, $request->validated());
return new GroupResource($updated, new Query());
}
/**
* Remove the specified resource.
*
* @param Group $group
* @param DestroyAction $action
* @return GroupResource
*/
public function destroy(Group $group, DestroyAction $action): GroupResource
{
$deleted = $action->destroy($group);
return new GroupResource($deleted, new Query());
}
/**
* Restore the specified resource.
*
* @param Group $group
* @param RestoreAction $action
* @return GroupResource
*/
public function restore(Group $group, RestoreAction $action): GroupResource
{
$restored = $action->restore($group);
return new GroupResource($restored, new Query());
}
/**
* Hard-delete the specified resource.
*
* @param Group $group
* @param ForceDeleteAction $action
* @return JsonResponse
*/
public function forceDelete(Group $group, ForceDeleteAction $action): JsonResponse
{
$message = $action->forceDelete($group);
return new JsonResponse([
'message' => $message,
]);
}
}
@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace App\Http\Resources\Wiki\Collection;
use App\Http\Resources\BaseCollection;
use App\Http\Resources\Wiki\Resource\GroupResource;
use App\Models\Wiki\Group;
use Illuminate\Http\Request;
/**
* Class GroupCollection.
*/
class GroupCollection extends BaseCollection
{
/**
* The "data" wrapper that should be applied.
*
* @var string|null
*/
public static $wrap = 'groups';
/**
* Transform the resource into a JSON array.
*
* @param Request $request
* @return array
*
* @noinspection PhpMissingParentCallCommonInspection
*/
public function toArray(Request $request): array
{
return $this->collection->map(fn (Group $group) => new GroupResource($group, $this->query))->all();
}
}
@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace App\Http\Resources\Wiki\Resource;
use App\Http\Api\Schema\Schema;
use App\Http\Api\Schema\Wiki\GroupSchema;
use App\Http\Resources\BaseResource;
/**
* Class GroupResource.
*/
class GroupResource extends BaseResource
{
/**
* The "data" wrapper that should be applied.
*
* @var string|null
*/
public static $wrap = 'group';
/**
* Get the resource schema.
*
* @return Schema
*/
protected function schema(): Schema
{
return new GroupSchema();
}
}
+1
View File
@@ -67,6 +67,7 @@ class Anime extends BaseModel
final public const RELATION_ARTISTS = 'animethemes.song.artists';
final public const RELATION_AUDIO = 'animethemes.animethemeentries.videos.audio';
final public const RELATION_ENTRIES = 'animethemes.animethemeentries';
final public const RELATION_GROUPS = 'animethemes.theme_group';
final public const RELATION_IMAGES = 'images';
final public const RELATION_RESOURCES = 'resources';
final public const RELATION_SCRIPTS = 'animethemes.animethemeentries.videos.videoscript';
+16
View File
@@ -13,6 +13,7 @@ use App\Events\Wiki\Anime\Theme\ThemeUpdated;
use App\Models\BaseModel;
use App\Models\Wiki\Anime;
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
use App\Models\Wiki\Group;
use App\Models\Wiki\Song;
use Database\Factories\Wiki\Anime\AnimeThemeFactory;
use Elastic\ScoutDriverPlus\Searchable;
@@ -29,6 +30,8 @@ use Laravel\Nova\Actions\Actionable;
* @property int $anime_id
* @property Collection<int, AnimeThemeEntry> $animethemeentries
* @property string|null $group
* @property Group|null $theme_group
* @property int|null $group_id
* @property int|null $sequence
* @property string $slug
* @property Song|null $song
@@ -51,12 +54,14 @@ class AnimeTheme extends BaseModel
final public const ATTRIBUTE_SEQUENCE = 'sequence';
final public const ATTRIBUTE_SLUG = 'slug';
final public const ATTRIBUTE_SONG = 'song_id';
final public const ATTRIBUTE_THEME_GROUP = 'group_id';
final public const ATTRIBUTE_TYPE = 'type';
final public const RELATION_ANIME = 'anime';
final public const RELATION_ARTISTS = 'song.artists';
final public const RELATION_AUDIO = 'animethemeentries.videos.audio';
final public const RELATION_ENTRIES = 'animethemeentries';
final public const RELATION_GROUP = 'theme_group';
final public const RELATION_IMAGES = 'anime.images';
final public const RELATION_SONG = 'song';
final public const RELATION_SYNONYMS = 'anime.animesynonyms';
@@ -70,6 +75,7 @@ class AnimeTheme extends BaseModel
protected $fillable = [
AnimeTheme::ATTRIBUTE_ANIME,
AnimeTheme::ATTRIBUTE_GROUP,
AnimeTheme::ATTRIBUTE_THEME_GROUP,
AnimeTheme::ATTRIBUTE_SEQUENCE,
AnimeTheme::ATTRIBUTE_SLUG,
AnimeTheme::ATTRIBUTE_SONG,
@@ -165,6 +171,16 @@ class AnimeTheme extends BaseModel
return $this->belongsTo(Anime::class, AnimeTheme::ATTRIBUTE_ANIME);
}
/**
* Gets the group that the theme uses.
*
* @return BelongsTo
*/
public function theme_group(): BelongsTo
{
return $this->belongsTo(Group::class, AnimeTheme::ATTRIBUTE_THEME_GROUP);
}
/**
* Gets the song that the theme uses.
*
+106
View File
@@ -0,0 +1,106 @@
<?php
declare(strict_types=1);
namespace App\Models\Wiki;
use App\Events\Wiki\Group\GroupCreated;
use App\Events\Wiki\Group\GroupDeleted;
use App\Events\Wiki\Group\GroupDeleting;
use App\Events\Wiki\Group\GroupRestored;
use App\Events\Wiki\Group\GroupUpdated;
use App\Models\BaseModel;
use App\Models\Wiki\Anime\AnimeTheme;
use Database\Factories\Wiki\GroupFactory;
use Elastic\ScoutDriverPlus\Searchable;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Collection;
use Laravel\Nova\Actions\Actionable;
/**
* Class Group.
*
* @property Collection<int, AnimeTheme> $animethemes
* @property int $group_id
* @property string $name
* @property string $slug
* @property string|null $video_filename
*
* @method static GroupFactory factory(...$parameters)
*/
class Group extends BaseModel
{
use Actionable;
use Searchable;
final public const TABLE = 'groups';
final public const ATTRIBUTE_ID = 'group_id';
final public const ATTRIBUTE_NAME = 'name';
final public const ATTRIBUTE_SLUG = 'slug';
final public const ATTRIBUTE_VIDEO_FILENAME = 'video_filename';
final public const RELATION_ANIME = 'animethemes.anime';
final public const RELATION_THEMES = 'animethemes';
final public const RELATION_VIDEOS = 'animethemes.animethemeentries.videos';
/**
* The attributes that are mass assignable.
*
* @var string[]
*/
protected $fillable = [
Group::ATTRIBUTE_NAME,
Group::ATTRIBUTE_SLUG,
Group::ATTRIBUTE_VIDEO_FILENAME,
];
/**
* The event map for the model.
*
* Allows for object-based events for native Eloquent events.
*
* @var array
*/
protected $dispatchesEvents = [
'created' => GroupCreated::class,
'deleted' => GroupDeleted::class,
'deleting' => GroupDeleting::class,
'restored' => GroupRestored::class,
'updated' => GroupUpdated::class,
];
/**
* The table associated with the model.
*
* @var string
*/
protected $table = Group::TABLE;
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = Group::ATTRIBUTE_ID;
/**
* Get name.
*
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* Get the themes for the group.
*
* @return HasMany
*/
public function animethemes(): HasMany
{
return $this->hasMany(AnimeTheme::class, AnimeTheme::ATTRIBUTE_THEME_GROUP);
}
}
+10
View File
@@ -9,6 +9,7 @@ use App\Models\Wiki\Anime\AnimeTheme;
use App\Nova\Resources\BaseResource;
use App\Nova\Resources\Wiki\Anime;
use App\Nova\Resources\Wiki\Anime\Theme\Entry;
use App\Nova\Resources\Wiki\Group;
use App\Nova\Resources\Wiki\Song;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Str;
@@ -232,6 +233,15 @@ class Theme extends BaseResource
}
),
BelongsTo::make(__('nova.resources.singularLabel.group'), AnimeTheme::RELATION_GROUP, Group::class)
->sortable()
->filterable()
->searchable()
->withSubtitles()
->nullable()
->showCreateRelationButton()
->showOnPreview(),
BelongsTo::make(__('nova.resources.singularLabel.song'), AnimeTheme::RELATION_SONG, Song::class)
->sortable()
->filterable()
+249
View File
@@ -0,0 +1,249 @@
<?php
declare(strict_types=1);
namespace App\Nova\Resources\Wiki;
use App\Models\Wiki\Anime\AnimeTheme;
use App\Models\Wiki\Group as GroupModel;
use App\Nova\Resources\BaseResource;
use App\Nova\Resources\Wiki\Anime\Theme;
use Exception;
use Illuminate\Database\Eloquent\Builder;
use Laravel\Nova\Fields\HasMany;
use Laravel\Nova\Fields\ID;
use Laravel\Nova\Fields\Text;
use Laravel\Nova\Http\Requests\NovaRequest;
use Laravel\Nova\Panel;
use Laravel\Nova\Query\Search\Column;
/**
* Class Group.
*/
class Group extends BaseResource
{
/**
* The model the resource corresponds to.
*
* @var string
*/
public static string $model = GroupModel::class;
/**
* The single value that should be used to represent the resource when being displayed.
*
* @var string
*/
public static $title = GroupModel::ATTRIBUTE_NAME;
/**
* Get the search result subtitle for the resource.
*
* @return string|null
*
* @noinspection PhpMissingParentCallCommonInspection
*/
public function subtitle(): ?string
{
$group = $this->model();
if ($group instanceof GroupModel) {
$theme = $group->animethemes->first();
if ($theme instanceof AnimeTheme) {
return $theme->anime->getName();
}
}
return null;
}
/**
* 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 relatableQuery(NovaRequest $request, $query): Builder
{
return $query->with(GroupModel::RELATION_ANIME);
}
/**
* Build an "index" query for the given resource.
*
* @param NovaRequest $request
* @param Builder $query
* @return Builder
*
* @noinspection PhpMissingParentCallCommonInspection
*/
public static function indexQuery(NovaRequest $request, $query): Builder
{
return $query->with(GroupModel::RELATION_ANIME);
}
/**
* The logical group associated with the resource.
*
* @return string
*
* @noinspection PhpMissingParentCallCommonInspection
*/
public static function group(): string
{
return __('nova.resources.group.wiki');
}
/**
* Get the displayable label of the resource.
*
* @return string
*
* @noinspection PhpMissingParentCallCommonInspection
*/
public static function label(): string
{
return __('nova.resources.label.groups');
}
/**
* Get the displayable singular label of the resource.
*
* @return string
*
* @noinspection PhpMissingParentCallCommonInspection
*/
public static function singularLabel(): string
{
return __('nova.resources.singularLabel.group');
}
/**
* Get the URI key for the resource.
*
* @return string
*
* @noinspection PhpMissingParentCallCommonInspection
*/
public static function uriKey(): string
{
return 'groups';
}
/**
* Get the searchable columns for the resource.
*
* @return array
*
* @noinspection PhpMissingParentCallCommonInspection
*/
public static function searchableColumns(): array
{
return [
new Column(GroupModel::ATTRIBUTE_NAME),
];
}
/**
* Determine if this resource uses Laravel Scout.
*
* @return bool
*
* @noinspection PhpMissingParentCallCommonInspection
*/
public static function usesScout(): bool
{
return false;
}
/**
* 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'), GroupModel::ATTRIBUTE_ID)
->sortable()
->showOnPreview()
->showWhenPeeking(),
Text::make(__('nova.fields.group.name.name'), GroupModel::ATTRIBUTE_NAME)
->sortable()
->copyable()
->required()
->rules(['required', 'max:192'])
->help(__('nova.fields.group.name.help'))
->showOnPreview()
->filterable()
->maxlength(192)
->enforceMaxlength()
->showWhenPeeking(),
Text::make(__('nova.fields.group.slug.name'), GroupModel::ATTRIBUTE_SLUG)
->sortable()
->copyable()
->required()
->rules(['required', 'max:192'])
->help(__('nova.fields.group.slug.help'))
->showOnPreview()
->filterable()
->maxlength(192)
->enforceMaxlength()
->showWhenPeeking(),
Text::make(__('nova.fields.group.video_filename.name'), GroupModel::ATTRIBUTE_VIDEO_FILENAME)
->sortable()
->copyable()
->nullable()
->rules(['nullable', 'max:192'])
->help(__('nova.fields.group.video_filename.help'))
->showOnPreview()
->filterable()
->maxlength(192)
->enforceMaxlength()
->showWhenPeeking(),
HasMany::make(__('nova.resources.label.anime_themes'), GroupModel::RELATION_THEMES, Theme::class),
Panel::make(__('nova.fields.base.timestamps'), $this->timestamps())
->collapsable(),
];
}
/**
* Get the actions available for the resource.
*
* @param NovaRequest $request
* @return array
*/
public function actions(NovaRequest $request): array
{
return array_merge(
parent::actions($request),
[]
);
}
/**
* Get the lenses available for the resource.
*
* @param NovaRequest $request
* @return array
*/
public function lenses(NovaRequest $request): array
{
return array_merge(
parent::lenses($request),
[]
);
}
}
+117
View File
@@ -0,0 +1,117 @@
<?php
declare(strict_types=1);
namespace App\Policies\Wiki;
use App\Enums\Auth\CrudPermission;
use App\Enums\Auth\ExtendedCrudPermission;
use App\Models\Auth\User;
use App\Models\Wiki\Group;
use Illuminate\Auth\Access\HandlesAuthorization;
use Laravel\Nova\Nova;
/**
* Class GroupPolicy.
*/
class GroupPolicy
{
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(Group::class)),
fn (): bool => true
);
}
/**
* Determine whether the user can view the model.
*
* @param User|null $user
* @return bool
*/
public function view(?User $user): bool
{
return Nova::whenServing(
fn (): bool => $user !== null && $user->can(CrudPermission::VIEW->format(Group::class)),
fn (): bool => true
);
}
/**
* Determine whether the user can create models.
*
* @param User $user
* @return bool
*/
public function create(User $user): bool
{
return $user->can(CrudPermission::CREATE->format(Group::class));
}
/**
* Determine whether the user can update the model.
*
* @param User $user
* @param Group $group
* @return bool
*/
public function update(User $user, Group $group): bool
{
return ! $group->trashed() && $user->can(CrudPermission::UPDATE->format(Group::class));
}
/**
* Determine whether the user can delete the model.
*
* @param User $user
* @param Group $group
* @return bool
*/
public function delete(User $user, Group $group): bool
{
return ! $group->trashed() && $user->can(CrudPermission::DELETE->format(Group::class));
}
/**
* Determine whether the user can restore the model.
*
* @param User $user
* @param Group $group
* @return bool
*/
public function restore(User $user, Group $group): bool
{
return $group->trashed() && $user->can(ExtendedCrudPermission::RESTORE->format(Group::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(Group::class));
}
/**
* Determine whether the user can add a theme to the group.
*
* @param User $user
* @return bool
*/
public function addAnimeTheme(User $user): bool
{
return $user->can(CrudPermission::UPDATE->format(Group::class));
}
}
+41
View File
@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Database\Factories\Wiki;
use App\Models\Wiki\Group;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
/**
* Class GroupFactory.
*
* @method Group createOne($attributes = [])
* @method Group makeOne($attributes = [])
*
* @extends Factory<Group>
*/
class GroupFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var class-string<Group>
*/
protected $model = Group::class;
/**
* Define the model's default state.
*
* @return array
*/
public function definition(): array
{
return [
Group::ATTRIBUTE_NAME => fake()->words(3, true),
Group::ATTRIBUTE_SLUG => Str::slug(fake()->text(191), '_'),
Group::ATTRIBUTE_VIDEO_FILENAME => fake()->words(3, true),
];
}
}
@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
use App\Models\BaseModel;
use App\Models\Wiki\Group;
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(Group::TABLE)) {
Schema::create(Group::TABLE, function (Blueprint $table) {
$table->id(Group::ATTRIBUTE_ID);
$table->timestamps(6);
$table->softDeletes(BaseModel::ATTRIBUTE_DELETED_AT, 6);
$table->string(Group::ATTRIBUTE_NAME);
$table->string(Group::ATTRIBUTE_SLUG);
$table->string(Group::ATTRIBUTE_VIDEO_FILENAME)->nullable();
});
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists(Group::TABLE);
}
};
@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
use App\Models\Wiki\Anime\AnimeTheme;
use App\Models\Wiki\Group;
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::hasColumn(AnimeTheme::TABLE, AnimeTheme::ATTRIBUTE_THEME_GROUP)) {
Schema::table(AnimeTheme::TABLE, function (Blueprint $table) {
$table->unsignedBigInteger(AnimeTheme::ATTRIBUTE_THEME_GROUP)->nullable();
$table->foreign(AnimeTheme::ATTRIBUTE_THEME_GROUP)->references(Group::ATTRIBUTE_ID)->on(Group::TABLE)->nullOnDelete();
});
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
if (Schema::hasColumn(AnimeTheme::TABLE, AnimeTheme::ATTRIBUTE_THEME_GROUP)) {
Schema::table(AnimeTheme::TABLE, function (Blueprint $table) {
$table->dropConstrainedForeignId(AnimeTheme::ATTRIBUTE_THEME_GROUP);
});
}
}
};
@@ -24,6 +24,7 @@ use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
use App\Models\Wiki\Artist;
use App\Models\Wiki\Audio;
use App\Models\Wiki\ExternalResource;
use App\Models\Wiki\Group;
use App\Models\Wiki\Image;
use App\Models\Wiki\Series;
use App\Models\Wiki\Song;
@@ -71,6 +72,7 @@ class PermissionSeeder extends Seeder
$this->registerResource(AnimeThemeEntry::class, $extendedCrudPermissions);
$this->registerResource(Artist::class, $extendedCrudPermissions);
$this->registerResource(Audio::class, $extendedCrudPermissions);
$this->registerResource(Group::class, $extendedCrudPermissions);
$this->registerResource(ExternalResource::class, $extendedCrudPermissions);
$this->registerResource(Image::class, $extendedCrudPermissions);
$this->registerResource(Page::class, $extendedCrudPermissions);
@@ -24,6 +24,7 @@ use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
use App\Models\Wiki\Artist;
use App\Models\Wiki\Audio;
use App\Models\Wiki\ExternalResource;
use App\Models\Wiki\Group;
use App\Models\Wiki\Image;
use App\Models\Wiki\Series;
use App\Models\Wiki\Song;
@@ -75,6 +76,7 @@ class AdminSeeder extends RoleSeeder
$this->configureResource($role, AnimeThemeEntry::class, $extendedCrudPermissions);
$this->configureResource($role, Artist::class, $extendedCrudPermissions);
$this->configureResource($role, Audio::class, $extendedCrudPermissions);
$this->configureResource($role, Group::class, $extendedCrudPermissions);
$this->configureResource($role, ExternalResource::class, $extendedCrudPermissions);
$this->configureResource($role, Image::class, $extendedCrudPermissions);
$this->configureResource($role, Page::class, $extendedCrudPermissions);
@@ -18,6 +18,7 @@ use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
use App\Models\Wiki\Artist;
use App\Models\Wiki\Audio;
use App\Models\Wiki\ExternalResource;
use App\Models\Wiki\Group;
use App\Models\Wiki\Image;
use App\Models\Wiki\Series;
use App\Models\Wiki\Song;
@@ -58,6 +59,7 @@ class DeveloperRoleSeeder extends RoleSeeder
$this->configureResource($role, AnimeThemeEntry::class, [CrudPermission::VIEW]);
$this->configureResource($role, Artist::class, [CrudPermission::VIEW]);
$this->configureResource($role, Audio::class, [CrudPermission::VIEW]);
$this->configureResource($role, Group::class, [CrudPermission::VIEW]);
$this->configureResource($role, ExternalResource::class, [CrudPermission::VIEW]);
$this->configureResource($role, Image::class, [CrudPermission::VIEW]);
$this->configureResource($role, Page::class, [CrudPermission::VIEW]);
@@ -18,6 +18,7 @@ use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
use App\Models\Wiki\Artist;
use App\Models\Wiki\Audio;
use App\Models\Wiki\ExternalResource;
use App\Models\Wiki\Group;
use App\Models\Wiki\Image;
use App\Models\Wiki\Series;
use App\Models\Wiki\Song;
@@ -65,6 +66,7 @@ class EncoderRoleSeeder extends RoleSeeder
$this->configureResource($role, AnimeThemeEntry::class, $extendedCrudPermissions);
$this->configureResource($role, Artist::class, $extendedCrudPermissions);
$this->configureResource($role, Audio::class, $extendedCrudPermissions);
$this->configureResource($role, Group::class, $extendedCrudPermissions);
$this->configureResource($role, ExternalResource::class, $extendedCrudPermissions);
$this->configureResource($role, Image::class, $extendedCrudPermissions);
$this->configureResource($role, Page::class, $extendedCrudPermissions);
@@ -18,6 +18,7 @@ use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
use App\Models\Wiki\Artist;
use App\Models\Wiki\Audio;
use App\Models\Wiki\ExternalResource;
use App\Models\Wiki\Group;
use App\Models\Wiki\Image;
use App\Models\Wiki\Series;
use App\Models\Wiki\Song;
@@ -58,6 +59,7 @@ class PatronRoleSeeder extends RoleSeeder
$this->configureResource($role, AnimeThemeEntry::class, [CrudPermission::VIEW]);
$this->configureResource($role, Artist::class, [CrudPermission::VIEW]);
$this->configureResource($role, Audio::class, [CrudPermission::VIEW]);
$this->configureResource($role, Group::class, [CrudPermission::VIEW]);
$this->configureResource($role, ExternalResource::class, [CrudPermission::VIEW]);
$this->configureResource($role, Image::class, [CrudPermission::VIEW]);
$this->configureResource($role, Page::class, [CrudPermission::VIEW]);
@@ -18,6 +18,7 @@ use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
use App\Models\Wiki\Artist;
use App\Models\Wiki\Audio;
use App\Models\Wiki\ExternalResource;
use App\Models\Wiki\Group;
use App\Models\Wiki\Image;
use App\Models\Wiki\Series;
use App\Models\Wiki\Song;
@@ -65,6 +66,7 @@ class WikiEditorRoleSeeder extends RoleSeeder
$this->configureResource($role, AnimeThemeEntry::class, $extendedCrudPermissions);
$this->configureResource($role, Artist::class, $extendedCrudPermissions);
$this->configureResource($role, Audio::class, [CrudPermission::VIEW, CrudPermission::UPDATE]);
$this->configureResource($role, Group::class, $extendedCrudPermissions);
$this->configureResource($role, ExternalResource::class, $extendedCrudPermissions);
$this->configureResource($role, Image::class, $extendedCrudPermissions);
$this->configureResource($role, Page::class, $extendedCrudPermissions);
@@ -18,6 +18,7 @@ use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
use App\Models\Wiki\Artist;
use App\Models\Wiki\Audio;
use App\Models\Wiki\ExternalResource;
use App\Models\Wiki\Group;
use App\Models\Wiki\Image;
use App\Models\Wiki\Series;
use App\Models\Wiki\Song;
@@ -58,6 +59,7 @@ class WikiViewerRoleSeeder extends RoleSeeder
$this->configureResource($role, AnimeThemeEntry::class, [CrudPermission::VIEW]);
$this->configureResource($role, Artist::class, [CrudPermission::VIEW]);
$this->configureResource($role, Audio::class, [CrudPermission::VIEW]);
$this->configureResource($role, Group::class, [CrudPermission::VIEW]);
$this->configureResource($role, ExternalResource::class, [CrudPermission::VIEW]);
$this->configureResource($role, Image::class, [CrudPermission::VIEW]);
$this->configureResource($role, Page::class, [CrudPermission::VIEW]);
@@ -0,0 +1,98 @@
<?php
declare(strict_types=1);
namespace Database\Seeders\Wiki\Group;
use App\Models\Wiki\Anime\AnimeTheme;
use App\Models\Wiki\Group;
use Illuminate\Database\Seeder;
/**
* Class GroupMigratingSeeder.
*/
class GroupMigratingSeeder extends Seeder
{
/**
* Seed the application's database.
*
* @return void
*/
public function run(): void
{
$this->englishVersionGroup();
$this->tvVersionGroup();
$this->bdVersionGroup();
$this->koreanVersionGroup();
$this->hdRemasterGroup();
$this->gintamaYorinukiGroup();
}
protected function englishVersionGroup()
{
$dubbedGroupThemes = AnimeTheme::query()
->where(AnimeTheme::ATTRIBUTE_GROUP, 'Dubbed Version')
->orWhere(AnimeTheme::ATTRIBUTE_GROUP, 'Dubbed Version - Funimation')
->orWhere(AnimeTheme::ATTRIBUTE_GROUP, 'English Version')
->get();
$dubbedGroup = Group::query()->where(Group::ATTRIBUTE_NAME, 'English Version')->get();
$dubbedGroupThemes->each(fn (AnimeTheme $theme) => $theme->theme_group()->associate($dubbedGroup)->save());
}
protected function tvVersionGroup()
{
$tvVersionThemes = AnimeTheme::query()
->where(AnimeTheme::ATTRIBUTE_GROUP, 'Original Broadcast Version')
->orWhere(AnimeTheme::ATTRIBUTE_GROUP, 'Original Japanese Version')
->orWhere(AnimeTheme::ATTRIBUTE_GROUP, 'Japanese Terrestrial Broadcast')
->orWhere(AnimeTheme::ATTRIBUTE_GROUP, '2005 Japanese Terrestrial Broadcast')
->orWhere(AnimeTheme::ATTRIBUTE_GROUP, 'TV version')
->orWhere(AnimeTheme::ATTRIBUTE_GROUP, 'TV Broadcast')
->get();
$tvVersionGroup = Group::query()->where(Group::ATTRIBUTE_NAME, 'TV Version')->get();
$tvVersionThemes->each(fn (AnimeTheme $theme) => $theme->theme_group()->associate($tvVersionGroup)->save());
}
protected function bdVersionGroup()
{
$bdVersionThemes = AnimeTheme::query()
->where(AnimeTheme::ATTRIBUTE_GROUP, 'BD version')
->get();
$bdVersionGroup = Group::query()->where(Group::ATTRIBUTE_NAME, 'BD Version')->get();
$bdVersionThemes->each(fn (AnimeTheme $theme) => $theme->theme_group()->associate($bdVersionGroup)->save());
}
protected function koreanVersionGroup()
{
$koreanVersionThemes = AnimeTheme::query()
->where(AnimeTheme::ATTRIBUTE_GROUP, 'Korean Version')
->get();
$koreanVersionGroup = Group::query()->where(Group::ATTRIBUTE_NAME, 'Korean Version')->get();
$koreanVersionThemes->each(fn (AnimeTheme $theme) => $theme->theme_group()->associate($koreanVersionGroup)->save());
}
protected function hdRemasterGroup()
{
$hdRemasterThemes = AnimeTheme::query()
->where(AnimeTheme::ATTRIBUTE_GROUP, 'HD Remaster')
->get();
$hdRemasterGroup = Group::query()->where(Group::ATTRIBUTE_NAME, 'HD Remaster')->get();
$hdRemasterThemes->each(fn (AnimeTheme $theme) => $theme->theme_group()->associate($hdRemasterGroup)->save());
}
protected function gintamaYorinukiGroup()
{
$gintamaYorinukiThemes = AnimeTheme::query()
->where(AnimeTheme::ATTRIBUTE_GROUP, 'Yorinuki Gintama-san')
->get();
$gintamaYorinukiGroup = Group::query()->where(Group::ATTRIBUTE_NAME, 'Yorinuki Gintama-san')->get();
$gintamaYorinukiThemes->each(fn (AnimeTheme $theme) => $theme->theme_group()->associate($gintamaYorinukiGroup)->save());
}
}
@@ -1,255 +0,0 @@
<?php
declare(strict_types=1);
namespace Database\Seeders\Wiki\Image;
use App\Enums\Models\Wiki\ImageFacet;
use App\Models\Wiki\Anime;
use App\Models\Wiki\Artist;
use App\Models\Wiki\Image;
use App\Models\Wiki\Studio;
use Exception;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Seeder;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
class MoveImageToAppropriateFolderSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$this->grillFacetImageSeeder();
//$this->animeImageSeeder();
$this->artistImageSeeder();
//$this->studioImageSeeder();
}
protected function grillFacetImageSeeder(): void
{
try {
DB::beginTransaction();
$chunkSize = 100;
/** @var FilesystemAdapter $fs */
$fs = Storage::disk(Config::get('image.disk'));
$images = Image::query()->where(Image::ATTRIBUTE_FACET, ImageFacet::GRILL)->get();
foreach ($images->chunk($chunkSize) as $chunk) {
foreach ($chunk as $image) {
if ($image instanceof Image) {
$oldPath = $image->path;
$newPath = "grill/{$image->path}";
$fs->move($oldPath, $newPath);
$image->update([
Image::ATTRIBUTE_PATH => $newPath,
]);
DB::commit();
echo $oldPath . ' moved to ' . $newPath . "\n";
}
}
sleep(5);
}
echo "grill facet images done\n";
} catch (Exception $e) {
echo 'error ' . $e->getMessage() . "\n";
DB::rollBack();
throw $e;
}
}
protected function animeImageSeeder(): void
{
try {
DB::beginTransaction();
$chunkSize = 100;
/** @var FilesystemAdapter $fs */
$fs = Storage::disk(Config::get('image.disk'));
$animes = Anime::query()->where(Anime::ATTRIBUTE_ID, '>', 0)->whereHas(Image::TABLE, function (Builder $query) {
$query->where(Image::ATTRIBUTE_PATH, 'not like', '%/%');
})->get();
foreach ($animes->chunk($chunkSize) as $chunk) {
foreach ($chunk as $anime) {
if ($anime instanceof Anime) {
$images = $anime->images()->get();
if (count($images) === 0) continue;
$largeCover = $images->where(Image::ATTRIBUTE_FACET, ImageFacet::COVER_LARGE)->first();
$smallCover = $images->where(Image::ATTRIBUTE_FACET, ImageFacet::COVER_SMALL)->first();
if (!empty($largeCover)) {
$largeCoverOldPath = $largeCover->path;
$largeCoverNewPath = "anime/large-cover/{$largeCover->path}";
$fs->move($largeCoverOldPath, $largeCoverNewPath);
$largeCover->update([
Image::ATTRIBUTE_PATH => $largeCoverNewPath,
]);
echo $largeCoverOldPath . ' moved to ' . $largeCoverNewPath . "\n";
}
if (!empty($smallCover)) {
$smallCoverOldPath = $smallCover->path;
$smallCoverNewPath = "anime/small-cover/{$smallCover->path}";
$fs->move($smallCoverOldPath, $smallCoverNewPath);
$smallCover->update([
Image::ATTRIBUTE_PATH => $smallCoverNewPath,
]);
echo $smallCoverOldPath . ' moved to ' . $smallCoverNewPath . "\n";
}
DB::commit();
}
}
sleep(5);
}
echo "anime images done\n";
} catch (Exception $e) {
echo 'error ' . $e->getMessage() . "\n";
DB::rollBack();
throw $e;
}
}
protected function artistImageSeeder(): void
{
try {
DB::beginTransaction();
$chunkSize = 100;
/** @var FilesystemAdapter $fs */
$fs = Storage::disk(Config::get('image.disk'));
$artists = Artist::query()->where(Artist::ATTRIBUTE_ID, '>', 0)->whereHas(Image::TABLE, function (Builder $query) {
$query->where(Image::ATTRIBUTE_PATH, 'not like', '%/%');
})->get();
foreach ($artists->chunk($chunkSize) as $chunk) {
foreach ($chunk as $artist) {
if ($artist instanceof Artist) {
$images = $artist->images()->get();
if (count($images) === 0) continue;
$largeCover = $images->where(Image::ATTRIBUTE_FACET, ImageFacet::COVER_LARGE)->first();
$smallCover = $images->where(Image::ATTRIBUTE_FACET, ImageFacet::COVER_SMALL)->first();
if (!empty($largeCover)) {
$largeCoverOldPath = $largeCover->path;
$largeCoverNewPath = "artist/large-cover/{$largeCover->path}";
$fs->move($largeCoverOldPath, $largeCoverNewPath);
$largeCover->update([
Image::ATTRIBUTE_PATH => $largeCoverNewPath,
]);
echo $largeCoverOldPath . ' moved to ' . $largeCoverNewPath . "\n";
}
if (!empty($smallCover)) {
$smallCoverOldPath = $smallCover->path;
$smallCoverNewPath = "artist/small-cover/{$smallCover->path}";
$fs->move($smallCoverOldPath, $smallCoverNewPath);
$smallCover->update([
Image::ATTRIBUTE_PATH => $smallCoverNewPath,
]);
echo $smallCoverOldPath . ' moved to ' . $smallCoverNewPath . "\n";
}
DB::commit();
}
}
sleep(5);
}
echo "artist images done\n";
} catch (Exception $e) {
echo 'error ' . $e->getMessage() . "\n";
DB::rollBack();
throw $e;
}
}
protected function studioImageSeeder(): void
{
try {
DB::beginTransaction();
$chunkSize = 100;
/** @var FilesystemAdapter $fs */
$fs = Storage::disk(Config::get('image.disk'));
$studios = Studio::query()->where(Studio::ATTRIBUTE_ID, '>', 0)->whereHas(Image::TABLE, function (Builder $query) {
$query->where(Image::ATTRIBUTE_PATH, 'not like', '%/%');
})->get();
foreach ($studios->chunk($chunkSize) as $chunk) {
foreach ($chunk as $studio) {
if ($studio instanceof Studio) {
$images = $studio->images()->get();
if (count($images) === 0) continue;
$largeCover = $images->where(Image::ATTRIBUTE_FACET, ImageFacet::COVER_LARGE)->first();
if (!empty($largeCover)) {
$oldPath = $largeCover->path;
$newPath = "studio/large-cover/{$largeCover->path}";
$fs->move($oldPath, $newPath);
$largeCover->update([
Image::ATTRIBUTE_PATH => $newPath,
]);
DB::commit();
echo $oldPath . ' moved to ' . $newPath . "\n";
}
}
}
sleep(5);
}
echo "studio images done\n";
} catch (Exception $e) {
echo 'error ' . $e->getMessage() . "\n";
DB::rollBack();
throw $e;
}
}
}
+15
View File
@@ -567,6 +567,20 @@ return [
'name' => 'Start At',
],
],
'group' => [
'name' => [
'name' => 'Name',
'help' => 'The name of the group.',
],
'slug' => [
'name' => 'Slug',
'help' => 'The slug that will be appended to the slug of the theme that has the group.',
],
'video_filename' => [
'name' => 'Video Filename',
'help' => 'The filename that will be appended to the filaname of the video that is related to the group.',
],
],
'image' => [
'facet' => [
'help' => 'The page component that the image is intended for. Example: Is this a small cover image or a large cover image?',
@@ -890,6 +904,7 @@ return [
'external_resource' => 'External Resource',
'feature' => 'Feature',
'featured_theme' => 'Featured Theme',
'group' => 'Group',
'image' => 'Image',
'page' => 'Page',
'permission' => 'Permission',
+2
View File
@@ -38,6 +38,7 @@ use App\Http\Controllers\Api\Wiki\AnimeController;
use App\Http\Controllers\Api\Wiki\ArtistController;
use App\Http\Controllers\Api\Wiki\AudioController;
use App\Http\Controllers\Api\Wiki\ExternalResourceController;
use App\Http\Controllers\Api\Wiki\GroupController;
use App\Http\Controllers\Api\Wiki\ImageController;
use App\Http\Controllers\Api\Wiki\SeriesController;
use App\Http\Controllers\Api\Wiki\SongController;
@@ -273,6 +274,7 @@ apiEditablePivotResource('artistsong', 'artist', 'song', ArtistSongController::c
apiResource('anime', AnimeController::class);
apiResource('artist', ArtistController::class);
apiResource('audio', AudioController::class);
apiResource('group', GroupController::class);
apiResource('image', ImageController::class);
apiResource('resource', ExternalResourceController::class);
Route::get('search', [SearchController::class, 'show'])->name('search.show');