diff --git a/.env.example b/.env.example index b90dc4f79..6966ecb4b 100644 --- a/.env.example +++ b/.env.example @@ -253,13 +253,10 @@ MAILGUN_SECRET= MAILGUN_ENDPOINT=api.mailgun.net POSTMARK_TOKEN= DISCORD_BOT_API_TOKEN= +DISCORD_BOT_API_URL= +DISCORD_BOT_API_KEY= DB_UPDATES_DISCORD_CHANNEL= ADMIN_DISCORD_CHANNEL= -SUBMISSIONS_DISCORD_CHANNEL= -WINTER_DISCORD_FORUM_TAG= -SPRING_DISCORD_FORUM_TAG= -SUMMER_DISCORD_FORUM_TAG= -FALL_DISCORD_FORUM_TAG= MAL_CLIENT_ID=null OPENAI_BEARER_TOKEN=null diff --git a/.env.example-sail b/.env.example-sail index 28fbe520e..5e17a84ec 100644 --- a/.env.example-sail +++ b/.env.example-sail @@ -254,13 +254,10 @@ MAILGUN_SECRET= MAILGUN_ENDPOINT=api.mailgun.net POSTMARK_TOKEN= DISCORD_BOT_API_TOKEN= +DISCORD_BOT_API_URL= +DISCORD_BOT_API_KEY= DB_UPDATES_DISCORD_CHANNEL= ADMIN_DISCORD_CHANNEL= -SUBMISSIONS_DISCORD_CHANNEL= -WINTER_DISCORD_FORUM_TAG= -SPRING_DISCORD_FORUM_TAG= -SUMMER_DISCORD_FORUM_TAG= -FALL_DISCORD_FORUM_TAG= MAL_CLIENT_ID=null OPENAI_BEARER_TOKEN=null diff --git a/app/Actions/Discord/DiscordThreadAction.php b/app/Actions/Discord/DiscordThreadAction.php index f7ce15c1e..0f5b09d8c 100644 --- a/app/Actions/Discord/DiscordThreadAction.php +++ b/app/Actions/Discord/DiscordThreadAction.php @@ -4,12 +4,8 @@ namespace App\Actions\Discord; -use App\Constants\Config\ServiceConstants; -use App\Enums\Discord\EmbedColor; -use App\Enums\Models\Wiki\AnimeSeason; -use App\Enums\Models\Wiki\ImageFacet; +use App\Models\Discord\DiscordThread; use App\Models\Wiki\Anime; -use App\Models\Wiki\Image; use Illuminate\Support\Arr; use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\Http; @@ -27,60 +23,27 @@ class DiscordThreadAction */ public function handle(Anime $anime, array $fields): void { - $name = Arr::get($fields, 'name'); + $anime->load(Anime::RELATION_IMAGES); - /** @var \Illuminate\Filesystem\FilesystemAdapter */ - $imageDisk = Storage::disk(Config::get('image.disk')); - - $imagePath = $anime->images()->where(Image::ATTRIBUTE_FACET, ImageFacet::COVER_LARGE)->first()->path; - - $animepage = json_decode(file_get_contents(base_path('composer.json')), true)['homepage'].'anime/'; - $description = '**Synopsis:** '.strip_tags($anime->synopsis)."\n\n".'**Link:** '.$animepage.$anime->slug; - - Http::withToken(Config::get('services.discord.token'), 'Bot') - ->asMultipart() - ->attach('file', file_get_contents($imageDisk->url($imagePath)), 'image.jpg') - ->post("https://discord.com/api/v10/channels/{$this->getDiscordChannel()}/threads", [ - 'payload_json' => json_encode([ - 'name' => $name, - 'applied_tags' => $this->getAppliedTags($anime->season->value), - 'message' => [ - 'embeds' => [ - [ - 'color' => EmbedColor::PURPLE->value, - 'title' => $anime->name, - 'description' => $description, - ] - ], - ] - ]) - ])->throw(); - } - - /** - * Get Discord forum channel the thread will be created to. - * - * @return string - */ - protected function getDiscordChannel(): string - { - return Config::get(ServiceConstants::SUBMISSIONS_DISCORD_CHANNEL_QUALIFIED); - } + $anime->name = Arr::get($fields, 'name'); - /** - * Get the IDs of the tags applied to the thread. - * - * @param int $season - * @return array - */ - protected function getAppliedTags(int $season): array - { - return match ($season) { - AnimeSeason::WINTER->value => [Config::get('services.discord.submissions_forum_tags.winter')], - AnimeSeason::SPRING->value => [Config::get('services.discord.submissions_forum_tags.spring')], - AnimeSeason::SUMMER->value => [Config::get('services.discord.submissions_forum_tags.summer')], - AnimeSeason::FALL->value => [Config::get('services.discord.submissions_forum_tags.fall')], - default => [], - }; + /** @var \Illuminate\Filesystem\FilesystemAdapter */ + $fs = Storage::disk(Config::get('image.disk')); + + $anime->images->each(fn ($image) => Arr::set($image, 'link', $fs->url($image->path))); + + $response = Http::withHeaders(['x-api-key' => Config::get('services.discord.api_key')]) + ->acceptJson() + ->post(Config::get('services.discord.api_url') . '/thread', $anime->toArray()) + ->throw() + ->json(); + + if (Arr::has($response, 'id')) { + DiscordThread::query()->create([ + DiscordThread::ATTRIBUTE_NAME => Arr::get($response, 'name'), + DiscordThread::ATTRIBUTE_ID => intval(Arr::get($response, 'id')), + DiscordThread::ATTRIBUTE_ANIME => $anime->getKey(), + ]); + } } -} \ No newline at end of file +} diff --git a/app/Actions/Discord/DiscordVideoNotificationAction.php b/app/Actions/Discord/DiscordVideoNotificationAction.php new file mode 100644 index 000000000..6d9c217b5 --- /dev/null +++ b/app/Actions/Discord/DiscordVideoNotificationAction.php @@ -0,0 +1,65 @@ + $videos + * @param array $fields + * + * @return void + */ + public function handle(Collection $videos, array $fields): void + { + $type = Arr::get($fields, 'type'); + + /** @var \Illuminate\Filesystem\FilesystemAdapter */ + $fs = Storage::disk(Config::get('image.disk')); + + $newVideos = []; + + foreach ($videos as $video) { + $video + ->load([ + 'animethemeentries.animetheme.anime.discordthread', + 'animethemeentries.animetheme.anime.images', + 'animethemeentries.animetheme.group', + 'animethemeentries.animetheme.song.artists', + ]); + + $theme = $video->animethemeentries->first()->animetheme; + + if ($theme->anime->discordthread === null) continue; + + Arr::set($video, 'source_name', $video->source->localize()); + Arr::set($video, 'overlap_name', $video->overlap->localize()); + Arr::set($theme, 'type_name', $theme->type->localize()); + + $theme->anime->images->each(function (Image $image) use ($fs) { + Arr::set($image, 'link', $fs->url($image->path)); + }); + + $newVideos[] = $video; + } + + Http::withHeaders(['x-api-key' => Config::get('services.discord.api_key')]) + ->post(Config::get('services.discord.api_url') . '/notification', [ + 'type' => $type, + 'videos' => $newVideos, + ]) + ->throw(); + } +} diff --git a/app/Events/Discord/DiscordThread/DiscordThreadDeleted.php b/app/Events/Discord/DiscordThread/DiscordThreadDeleted.php new file mode 100644 index 000000000..5bfb19ca0 --- /dev/null +++ b/app/Events/Discord/DiscordThread/DiscordThreadDeleted.php @@ -0,0 +1,62 @@ + + */ +class DiscordThreadDeleted extends AdminDeletedEvent +{ + /** + * Create a new event instance. + * + * @param DiscordThread $thread + */ + public function __construct(DiscordThread $thread) + { + parent::__construct($thread); + $thread->forceDelete(); + $this->deleteThread(); + } + + /** + * Get the model that has fired this event. + * + * @return DiscordThread + */ + public function getModel(): DiscordThread + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Discord Thread '**{$this->getModel()->getName()}**' has been deleted."; + } + + /** + * Delete the thread on discord. + * + * @return void + */ + protected function deleteThread(): void + { + Http::withHeaders(['x-api-key' => Config::get('services.discord.api_key')]) + ->delete(Config::get('services.discord.api_url') . '/thread', ['id' => $this->getModel()->getKey()]) + ->throw(); + } +} diff --git a/app/Events/Discord/DiscordThread/DiscordThreadUpdated.php b/app/Events/Discord/DiscordThread/DiscordThreadUpdated.php new file mode 100644 index 000000000..934e71520 --- /dev/null +++ b/app/Events/Discord/DiscordThread/DiscordThreadUpdated.php @@ -0,0 +1,62 @@ + + */ +class DiscordThreadUpdated extends AdminUpdatedEvent +{ + /** + * Create a new event instance. + * + * @param DiscordThread $thread + */ + public function __construct(DiscordThread $thread) + { + parent::__construct($thread); + $this->updateThread(); + $this->initializeEmbedFields($thread); + } + + /** + * Get the model that has fired this event. + * + * @return DiscordThread + */ + public function getModel(): DiscordThread + { + return $this->model; + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + return "Discord Thread '**{$this->getModel()->getName()}**' has been updated."; + } + + /** + * Update the thread on discord. + * + * @return void + */ + protected function updateThread(): void + { + Http::withHeaders(['x-api-key' => Config::get('services.discord.api_key')]) + ->put(Config::get('services.discord.api_url') . '/thread', $this->getModel()) + ->throw(); + } +} diff --git a/app/Filament/BulkActions/BaseBulkAction.php b/app/Filament/BulkActions/BaseBulkAction.php new file mode 100644 index 000000000..9844d4a7f --- /dev/null +++ b/app/Filament/BulkActions/BaseBulkAction.php @@ -0,0 +1,36 @@ +action(fn (Collection $records, array $data) => $this->handle($records, $data)); + } + + /** + * Handle the action. + * + * @param Collection $records + * @param array $fields + * @return void + */ + abstract public function handle(Collection $records, array $fields): void; +} diff --git a/app/Filament/BulkActions/Discord/DiscordVideoNotificationBulkAction.php b/app/Filament/BulkActions/Discord/DiscordVideoNotificationBulkAction.php new file mode 100644 index 000000000..9dc28d733 --- /dev/null +++ b/app/Filament/BulkActions/Discord/DiscordVideoNotificationBulkAction.php @@ -0,0 +1,54 @@ + $videos + * @param array $fields + */ + public function handle(Collection $videos, array $fields): void + { + $action = new DiscordVideoNotificationActionAction(); + + $action->handle($videos, $fields); + } + + /** + * Get the form for the action. + * + * @param Form $form + * @return Form + */ + public function getForm(Form $form): ?Form + { + return $form + ->schema([ + Select::make('type') + ->label(__('filament.bulk_actions.discord.notification.type.name')) + ->helperText(__('filament.bulk_actions.discord.notification.type.help')) + ->options([ + 'added' => __('filament.bulk_actions.discord.notification.type.options.added'), + 'updated' => __('filament.bulk_actions.discord.notification.type.options.updated'), + ]) + ->default('added') + ->required() + ->rules(['required']), + ]); + } +} diff --git a/app/Filament/Resources/Discord/DiscordThread.php b/app/Filament/Resources/Discord/DiscordThread.php new file mode 100644 index 000000000..f12c493ea --- /dev/null +++ b/app/Filament/Resources/Discord/DiscordThread.php @@ -0,0 +1,277 @@ +schema([ + TextInput::make(DiscordThreadModel::ATTRIBUTE_ID) + ->label(__('filament.fields.discord_thread.id.name')) + ->helperText(__('filament.fields.discord_thread.id.help')) + ->integer() + ->required() + ->rules(['required', 'integer']), + + TextInput::make(DiscordThreadModel::ATTRIBUTE_NAME) + ->label(__('filament.fields.discord_thread.name.name')) + ->helperText(__('filament.fields.discord_thread.name.help')) + ->required() + ->rules(['required', 'string']), + + Select::make(DiscordThreadModel::ATTRIBUTE_ANIME) + ->label(__('filament.resources.singularLabel.anime')) + ->relationship(DiscordThreadModel::RELATION_ANIME, Anime::ATTRIBUTE_NAME) + ->required() + ->useScout(Anime::class), + ]); + } + + /** + * The index page of the resource. + * + * @param Table $table + * @return Table + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public static function table(Table $table): Table + { + return parent::table($table) + ->columns([ + TextColumn::make(DiscordThreadModel::ATTRIBUTE_ID) + ->label(__('filament.fields.base.id')) + ->sortable(), + + TextColumn::make(DiscordThreadModel::ATTRIBUTE_NAME) + ->label(__('filament.fields.discord_thread.name.name')) + ->sortable() + ->copyableWithMessage() + ->toggleable(), + + TextColumn::make(DiscordThreadModel::RELATION_ANIME.'.'.Anime::ATTRIBUTE_NAME) + ->label(__('filament.resources.singularLabel.anime')) + ->toggleable() + ->urlToRelated(AnimeResource::class, DiscordThreadModel::RELATION_ANIME), + ]) + ->searchable() + ->defaultSort(DiscordThreadModel::ATTRIBUTE_ID, 'desc') + ->filters(static::getFilters()) + ->filtersFormMaxHeight('400px') + ->actions(static::getActions()) + ->bulkActions(static::getBulkActions()); + } + + /** + * Get the infolist available for the resource. + * + * @param Infolist $infolist + * @return Infolist + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public static function infolist(Infolist $infolist): Infolist + { + return $infolist + ->schema([ + Section::make(static::getRecordTitle($infolist->getRecord())) + ->schema([ + TextEntry::make(DiscordThreadModel::ATTRIBUTE_ID) + ->label(__('filament.fields.base.id')), + + TextEntry::make(DiscordThreadModel::ATTRIBUTE_NAME) + ->label(__('filament.fields.discord_thread.name.name')), + + TextEntry::make(DiscordThreadModel::RELATION_ANIME.'.'.Anime::ATTRIBUTE_NAME) + ->label(__('filament.resources.singularLabel.anime')) + ->urlToRelated(AnimeResource::class, DiscordThreadModel::RELATION_ANIME), + ]) + ->columns(3), + ]); + } + + /** + * Get the filters available for the resource. + * + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public static function getFilters(): array + { + return array_merge( + [], + parent::getFilters(), + ); + } + + /** + * Get the actions available for the resource. + * + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public static function getActions(): array + { + return array_merge( + parent::getActions(), + [], + ); + } + + /** + * Get the bulk actions available for the resource. + * + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public static function getBulkActions(): array + { + return array_merge( + parent::getBulkActions(), + [], + ); + } + + /** + * Get the pages available for the resource. + * + * @return array + * + * @noinspection PhpMissingParentCallCommonInspection + */ + public static function getPages(): array + { + return [ + 'index' => ListDiscordThreads::route('/'), + 'create' => CreateDiscordThread::route('/create'), + 'view' => ViewDiscordThread::route('/{record:thread_id}'), + 'edit' => EditDiscordThread::route('/{record:thread_id}/edit'), + ]; + } +} \ No newline at end of file diff --git a/app/Filament/Resources/Discord/DiscordThread/Pages/CreateDiscordThread.php b/app/Filament/Resources/Discord/DiscordThread/Pages/CreateDiscordThread.php new file mode 100644 index 000000000..67b9db62b --- /dev/null +++ b/app/Filament/Resources/Discord/DiscordThread/Pages/CreateDiscordThread.php @@ -0,0 +1,16 @@ +label(__('filament.actions.anime.discord.thread.name')) ->icon('heroicon-o-chat-bubble-left-right') ->requiresConfirmation() - ->authorize('create', Video::class), + ->authorize('create', DiscordThread::class), BackfillAnimeAction::make('backfill-anime') ->label(__('filament.actions.anime.backfill.name')) diff --git a/app/Filament/Resources/Wiki/Anime/Pages/EditAnime.php b/app/Filament/Resources/Wiki/Anime/Pages/EditAnime.php index 76c636e86..ffb5f4dab 100644 --- a/app/Filament/Resources/Wiki/Anime/Pages/EditAnime.php +++ b/app/Filament/Resources/Wiki/Anime/Pages/EditAnime.php @@ -12,10 +12,10 @@ use App\Filament\HeaderActions\Models\Wiki\Anime\BackfillAnimeHeaderAction; use App\Filament\Resources\Wiki\Anime; use App\Filament\Resources\Base\BaseEditResource; +use App\Models\Discord\DiscordThread; use App\Models\Wiki\Anime as AnimeModel; use App\Models\Wiki\ExternalResource; use App\Models\Wiki\Image; -use App\Models\Wiki\Video; use Filament\Actions\ActionGroup; use Filament\Support\Enums\MaxWidth; @@ -70,7 +70,7 @@ protected function getHeaderActions(): array ->label(__('filament.actions.anime.discord.thread.name')) ->icon('heroicon-o-chat-bubble-left-right') ->requiresConfirmation() - ->authorize('create', Video::class), + ->authorize('create', DiscordThread::class), BackfillAnimeHeaderAction::make('backfill-anime') ->label(__('filament.actions.anime.backfill.name')) diff --git a/app/Filament/Resources/Wiki/Video.php b/app/Filament/Resources/Wiki/Video.php index 87c94a9ae..86d140907 100644 --- a/app/Filament/Resources/Wiki/Video.php +++ b/app/Filament/Resources/Wiki/Video.php @@ -9,6 +9,7 @@ use App\Filament\Actions\Models\Wiki\Video\BackfillAudioAction; use App\Filament\Actions\Storage\Wiki\Video\DeleteVideoAction; use App\Filament\Actions\Storage\Wiki\Video\MoveVideoAction; +use App\Filament\BulkActions\Discord\DiscordVideoNotificationBulkAction; use App\Filament\Components\Columns\TextColumn; use App\Filament\Components\Fields\Select; use App\Filament\Components\Filters\NumberFilter; @@ -24,6 +25,7 @@ use App\Filament\TableActions\Repositories\Storage\Wiki\Video\ReconcileVideoTableAction; use App\Filament\TableActions\Storage\Wiki\Video\UploadVideoTableAction; use App\Http\Resources\Wiki\Resource\AudioResource; +use App\Models\Discord\DiscordThread; use App\Models\Wiki\Audio as AudioModel; use App\Models\Wiki\Video as VideoModel; use Filament\Forms\Components\Checkbox; @@ -34,6 +36,7 @@ use Filament\Resources\RelationManagers\RelationGroup; use Filament\Support\Enums\MaxWidth; use Filament\Tables\Actions\ActionGroup; +use Filament\Tables\Actions\BulkActionGroup; use Filament\Tables\Columns\IconColumn; use Filament\Tables\Filters\Filter; use Filament\Tables\Filters\SelectFilter; @@ -408,7 +411,14 @@ public static function getBulkActions(): array { return array_merge( parent::getBulkActions(), - [], + [ + BulkActionGroup::make([ + DiscordVideoNotificationBulkAction::make('discord-notification') + ->label(__('filament.bulk_actions.discord.notification.name')) + ->requiresConfirmation() + ->authorize('create', DiscordThread::class), + ]), + ], ); } diff --git a/app/Models/Discord/DiscordThread.php b/app/Models/Discord/DiscordThread.php new file mode 100644 index 000000000..5b51be242 --- /dev/null +++ b/app/Models/Discord/DiscordThread.php @@ -0,0 +1,96 @@ + + */ + protected $fillable = [ + DiscordThread::ATTRIBUTE_ANIME, + DiscordThread::ATTRIBUTE_ID, + DiscordThread::ATTRIBUTE_NAME, + ]; + + /** + * The table associated with the model. + * + * @var string + */ + protected $table = DiscordThread::TABLE; + + /** + * The primary key associated with the table. + * + * @var string + */ + protected $primaryKey = DiscordThread::ATTRIBUTE_ID; + + /** + * The event map for the model. + * + * Allows for object-based events for native Eloquent events. + * + * @var array + */ + protected $dispatchesEvents = [ + 'deleted' => DiscordThreadDeleted::class, + 'updated' => DiscordThreadUpdated::class, + ]; + + /** + * Get name. + * + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * Get subtitle. + * + * @return string + */ + public function getSubtitle(): string + { + return $this->getKey(); + } + + /** + * Gets the anime that the thread uses. + * + * @return BelongsTo + */ + public function anime(): BelongsTo + { + return $this->belongsTo(Anime::class, DiscordThread::ATTRIBUTE_ANIME); + } +} \ No newline at end of file diff --git a/app/Models/Wiki/Anime.php b/app/Models/Wiki/Anime.php index 0d61a5973..3767e6235 100644 --- a/app/Models/Wiki/Anime.php +++ b/app/Models/Wiki/Anime.php @@ -16,6 +16,7 @@ use App\Http\Resources\Pivot\Wiki\Resource\AnimeSeriesResource; use App\Http\Resources\Pivot\Wiki\Resource\AnimeStudioResource; use App\Models\BaseModel; +use App\Models\Discord\DiscordThread; use App\Models\Wiki\Anime\AnimeSynonym; use App\Models\Wiki\Anime\AnimeTheme; use App\Pivots\Wiki\AnimeImage; @@ -27,6 +28,7 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Support\Collection; use Laravel\Nova\Actions\Actionable; @@ -36,6 +38,7 @@ * @property int $anime_id * @property Collection $animesynonyms * @property Collection $animethemes + * @property DiscordThread|null $discordthread * @property Collection $images * @property AnimeMediaFormat $media_format * @property string $name @@ -198,6 +201,16 @@ public function animesynonyms(): HasMany return $this->hasMany(AnimeSynonym::class, AnimeSynonym::ATTRIBUTE_ANIME); } + /** + * Get the discord thread that the anime owns. + * + * @return HasOne + */ + public function discordthread(): HasOne + { + return $this->hasOne(DiscordThread::class, DiscordThread::ATTRIBUTE_ANIME); + } + /** * Get the series the anime is included in. * diff --git a/app/Policies/Discord/DiscordThreadPolicy.php b/app/Policies/Discord/DiscordThreadPolicy.php new file mode 100644 index 000000000..5c2548276 --- /dev/null +++ b/app/Policies/Discord/DiscordThreadPolicy.php @@ -0,0 +1,102 @@ +can(CrudPermission::VIEW->format(DiscordThread::class)) + : true; + } + + /** + * Determine whether the user can view the model. + * + * @param User|null $user + * @return bool + */ + public function view(?User $user): bool + { + return Filament::isServing() + ? $user !== null && $user->can(CrudPermission::VIEW->format(DiscordThread::class)) + : 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(DiscordThread::class)); + } + + /** + * Determine whether the user can update the model. + * + * @param User $user + * @param DiscordThread $discordThread + * @return bool + */ + public function update(User $user, DiscordThread $discordThread): bool + { + return ! $discordThread->trashed() && $user->can(CrudPermission::UPDATE->format(DiscordThread::class)); + } + + /** + * Determine whether the user can delete the model. + * + * @param User $user + * @return bool + */ + public function delete(User $user): bool + { + return $user->can(CrudPermission::DELETE->format(DiscordThread::class)); + } + + /** + * Determine whether the user can permanently delete the model. + * + * @param User $user + * @return bool + */ + public function forceDelete(User $user): bool + { + return $user->can(CrudPermission::DELETE->format(DiscordThread::class)); + } + + /** + * Determine whether the user can permanently delete any model. + * + * @param User $user + * @return bool + */ + public function forceDeleteAny(User $user): bool + { + return $user->can(CrudPermission::DELETE->format(DiscordThread::class)); + } +} diff --git a/config/services.php b/config/services.php index e175e4ced..6dc8bb0f5 100644 --- a/config/services.php +++ b/config/services.php @@ -35,15 +35,10 @@ 'discord' => [ 'token' => env('DISCORD_BOT_API_TOKEN'), + 'api_url' => env('DISCORD_BOT_API_URL'), + 'api_key' => env('DISCORD_BOT_API_KEY'), 'db_updates_discord_channel' => env('DB_UPDATES_DISCORD_CHANNEL'), 'admin_discord_channel' => env('ADMIN_DISCORD_CHANNEL'), - 'submissions_discord_channel' => env('SUBMISSIONS_DISCORD_CHANNEL'), - 'submissions_forum_tags' => [ - 'winter' => env('WINTER_DISCORD_FORUM_TAG'), - 'spring' => env('SPRING_DISCORD_FORUM_TAG'), - 'summer' => env('SUMMER_DISCORD_FORUM_TAG'), - 'fall' => env('FALL_DISCORD_FORUM_TAG') - ] ], 'mal' => [ diff --git a/database/migrations/2024_05_31_052841_create_discord_threads_table.php b/database/migrations/2024_05_31_052841_create_discord_threads_table.php new file mode 100644 index 000000000..fa0513e69 --- /dev/null +++ b/database/migrations/2024_05_31_052841_create_discord_threads_table.php @@ -0,0 +1,39 @@ +timestamps(6); + $table->softDeletes(BaseModel::ATTRIBUTE_DELETED_AT, 6); + $table->id(DiscordThread::ATTRIBUTE_ID); + $table->string(DiscordThread::ATTRIBUTE_NAME); + + $table->unsignedBigInteger(DiscordThread::ATTRIBUTE_ANIME); + $table->foreign(DiscordThread::ATTRIBUTE_ANIME)->references(Anime::ATTRIBUTE_ID)->on(Anime::TABLE)->cascadeOnDelete(); + }); + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists(DiscordThread::TABLE); + } +}; diff --git a/database/seeders/Auth/Permission/PermissionSeeder.php b/database/seeders/Auth/Permission/PermissionSeeder.php index ab27f01e4..aa70f9897 100644 --- a/database/seeders/Auth/Permission/PermissionSeeder.php +++ b/database/seeders/Auth/Permission/PermissionSeeder.php @@ -14,6 +14,7 @@ use App\Models\Auth\Permission; use App\Models\Auth\Role; use App\Models\Auth\User; +use App\Models\Discord\DiscordThread; use App\Models\Document\Page; use App\Models\List\Playlist; use App\Models\List\Playlist\PlaylistTrack; @@ -61,6 +62,9 @@ public function run(): void $this->registerResource(Role::class, CrudPermission::cases()); $this->registerResource(User::class, $extendedCrudPermissions); + // Discord Resources + $this->registerResource(DiscordThread::class, CrudPermission::cases()); + // List Resources $this->registerResource(Playlist::class, $extendedCrudPermissions); $this->registerResource(PlaylistTrack::class, $extendedCrudPermissions); diff --git a/database/seeders/Auth/Role/AdminSeeder.php b/database/seeders/Auth/Role/AdminSeeder.php index 1e0ad4b36..e4bd90cca 100644 --- a/database/seeders/Auth/Role/AdminSeeder.php +++ b/database/seeders/Auth/Role/AdminSeeder.php @@ -14,6 +14,7 @@ use App\Models\Auth\Permission; use App\Models\Auth\Role; use App\Models\Auth\User; +use App\Models\Discord\DiscordThread; use App\Models\Document\Page; use App\Models\List\Playlist; use App\Models\List\Playlist\PlaylistTrack; @@ -65,6 +66,9 @@ public function run(): void $this->configureResource($role, Role::class, CrudPermission::cases()); $this->configureResource($role, User::class, $extendedCrudPermissions); + // Discord Resources + $this->configureResource($role, DiscordThread::class, CrudPermission::cases()); + // List Resources $this->configureResource($role, Playlist::class, $extendedCrudPermissions); $this->configureResource($role, PlaylistTrack::class, $extendedCrudPermissions); diff --git a/database/seeders/Auth/Role/DeveloperRoleSeeder.php b/database/seeders/Auth/Role/DeveloperRoleSeeder.php index 3988689e3..be682e97d 100644 --- a/database/seeders/Auth/Role/DeveloperRoleSeeder.php +++ b/database/seeders/Auth/Role/DeveloperRoleSeeder.php @@ -8,6 +8,7 @@ use App\Enums\Auth\ExtendedCrudPermission; use App\Enums\Auth\SpecialPermission; use App\Models\Auth\Role; +use App\Models\Discord\DiscordThread; use App\Models\Document\Page; use App\Models\List\Playlist; use App\Models\List\Playlist\PlaylistTrack; @@ -48,6 +49,9 @@ public function run(): void ExtendedCrudPermission::cases(), ); + // Discord Resources + $this->configureResource($role, DiscordThread::class, [CrudPermission::VIEW]); + // List Resources $this->configureResource($role, Playlist::class, $extendedCrudPermissions); $this->configureResource($role, PlaylistTrack::class, $extendedCrudPermissions); diff --git a/database/seeders/Auth/Role/EncoderRoleSeeder.php b/database/seeders/Auth/Role/EncoderRoleSeeder.php index b74b83a8c..33ea4f6c5 100644 --- a/database/seeders/Auth/Role/EncoderRoleSeeder.php +++ b/database/seeders/Auth/Role/EncoderRoleSeeder.php @@ -8,6 +8,7 @@ use App\Enums\Auth\ExtendedCrudPermission; use App\Enums\Auth\SpecialPermission; use App\Models\Auth\Role; +use App\Models\Discord\DiscordThread; use App\Models\Document\Page; use App\Models\List\Playlist; use App\Models\List\Playlist\PlaylistTrack; @@ -48,6 +49,9 @@ public function run(): void ExtendedCrudPermission::cases(), ); + // Discord Resources + $this->configureResource($role, DiscordThread::class, [CrudPermission::CREATE, CrudPermission::UPDATE, CrudPermission::VIEW]); + // List Resources $this->configureResource($role, Playlist::class, $extendedCrudPermissions); $this->configureResource($role, PlaylistTrack::class, $extendedCrudPermissions); diff --git a/database/seeders/Auth/Role/PatronRoleSeeder.php b/database/seeders/Auth/Role/PatronRoleSeeder.php index 6d0cf336d..0be827840 100644 --- a/database/seeders/Auth/Role/PatronRoleSeeder.php +++ b/database/seeders/Auth/Role/PatronRoleSeeder.php @@ -8,6 +8,7 @@ use App\Enums\Auth\ExtendedCrudPermission; use App\Enums\Auth\SpecialPermission; use App\Models\Auth\Role; +use App\Models\Discord\DiscordThread; use App\Models\Document\Page; use App\Models\List\Playlist; use App\Models\List\Playlist\PlaylistTrack; @@ -48,6 +49,9 @@ public function run(): void ExtendedCrudPermission::cases(), ); + // Discord Resources + $this->configureResource($role, DiscordThread::class, [CrudPermission::VIEW]); + // List Resources $this->configureResource($role, Playlist::class, $extendedCrudPermissions); $this->configureResource($role, PlaylistTrack::class, $extendedCrudPermissions); diff --git a/database/seeders/Auth/Role/WikiEditorRoleSeeder.php b/database/seeders/Auth/Role/WikiEditorRoleSeeder.php index 633e7eec3..a16d179f0 100644 --- a/database/seeders/Auth/Role/WikiEditorRoleSeeder.php +++ b/database/seeders/Auth/Role/WikiEditorRoleSeeder.php @@ -8,6 +8,7 @@ use App\Enums\Auth\ExtendedCrudPermission; use App\Enums\Auth\SpecialPermission; use App\Models\Auth\Role; +use App\Models\Discord\DiscordThread; use App\Models\Document\Page; use App\Models\List\Playlist; use App\Models\List\Playlist\PlaylistTrack; @@ -48,6 +49,9 @@ public function run(): void ExtendedCrudPermission::cases(), ); + // Discord Resources + $this->configureResource($role, DiscordThread::class, [CrudPermission::VIEW]); + // List Resources $this->configureResource($role, Playlist::class, $extendedCrudPermissions); $this->configureResource($role, PlaylistTrack::class, $extendedCrudPermissions); diff --git a/database/seeders/Auth/Role/WikiViewerRoleSeeder.php b/database/seeders/Auth/Role/WikiViewerRoleSeeder.php index 0c6495ccd..994cc2293 100644 --- a/database/seeders/Auth/Role/WikiViewerRoleSeeder.php +++ b/database/seeders/Auth/Role/WikiViewerRoleSeeder.php @@ -8,6 +8,7 @@ use App\Enums\Auth\ExtendedCrudPermission; use App\Enums\Auth\SpecialPermission; use App\Models\Auth\Role; +use App\Models\Discord\DiscordThread; use App\Models\Document\Page; use App\Models\List\Playlist; use App\Models\List\Playlist\PlaylistTrack; @@ -48,6 +49,9 @@ public function run(): void ExtendedCrudPermission::cases(), ); + // Discord Resources + $this->configureResource($role, DiscordThread::class, [CrudPermission::VIEW]); + // List Resources $this->configureResource($role, Playlist::class, $extendedCrudPermissions); $this->configureResource($role, PlaylistTrack::class, $extendedCrudPermissions); diff --git a/lang/en/filament.php b/lang/en/filament.php index 6248777fa..d1c4e7cc3 100644 --- a/lang/en/filament.php +++ b/lang/en/filament.php @@ -410,6 +410,19 @@ 'forcedelete' => 'Force Delete Selected', 'restore' => 'Restore Selected', ], + 'discord' => [ + 'notification' => [ + 'name' => 'Create Discord Notification', + 'type' => [ + 'help' => 'Are they new videos or replacement?', + 'name' => 'Type', + 'options' => [ + 'added' => 'Added', + 'updated' => 'Updated', + ], + ], + ], + ], ], 'dashboards' => [ 'icon' => [ @@ -562,6 +575,16 @@ 'timestamps' => 'Timestamps', 'updated_at' => 'Updated At', ], + 'discord_thread' => [ + 'id' => [ + 'help' => 'The thread ID on Discord', + 'name' => 'Thread ID', + ], + 'name' => [ + 'help' => 'The name of the thread on Discord', + 'name' => 'Name', + ], + ], 'dump' => [ 'path' => 'Path', ], @@ -829,6 +852,7 @@ 'group' => [ 'admin' => 'Admin', 'auth' => 'Auth', + 'discord' => 'Discord', 'document' => 'Document', 'list' => 'List', 'wiki' => 'Wiki', @@ -841,6 +865,7 @@ 'announcements' => 'heroicon-o-megaphone', 'artists' => 'heroicon-o-user-circle', 'audios' => 'heroicon-o-speaker-wave', + 'discord_thread' => 'heroicon-o-chat-bubble-left-right', 'dumps' => 'heroicon-o-circle-stack', 'external_resources' => 'heroicon-o-arrow-top-right-on-square', 'features' => 'heroicon-o-cog-6-tooth', @@ -868,6 +893,7 @@ 'announcements' => 'Announcements', 'artists' => 'Artists', 'audios' => 'Audios', + 'discord_threads' => 'Threads', 'dumps' => 'Dumps', 'external_resources' => 'External Resources', 'features' => 'Features', @@ -895,6 +921,7 @@ 'announcement' => 'Announcement', 'artist' => 'Artist', 'audio' => 'Audio', + 'discord_thread' => 'Thread', 'dump' => 'Dump', 'external_resource' => 'External Resource', 'feature' => 'Feature',