clean: remove discord thread features (#1191)

This commit is contained in:
Kyrch
2026-04-26 14:37:24 -03:00
committed by GitHub
parent 71c4f56a9d
commit 0e41771f4c
34 changed files with 15 additions and 976 deletions
@@ -1,72 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Actions\Discord;
use App\Actions\ActionResult;
use App\Enums\Actions\ActionStatus;
use App\Models\Discord\DiscordThread;
use App\Models\Wiki\Anime;
use Exception;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
class DiscordThreadAction
{
/**
* Get the HTTP client for Discord API.
*/
public static function getHttp(): PendingRequest
{
return Http::withHeaders(['x-api-key' => Config::get('services.discord.api_key')])
->baseUrl(Config::get('services.discord.api_url'));
}
/**
* @param array<string, mixed> $fields
*
* @throws Exception
*/
public function handle(Anime $anime, array $fields): ActionResult
{
try {
$response = static::getHttp()
->acceptJson()
->post('/thread', ['name' => Arr::get($fields, 'name'), 'slug' => $anime->slug])
->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(),
]);
}
} catch (Exception $e) {
Log::error($e->getMessage());
throw $e;
}
return new ActionResult(ActionStatus::PASSED);
}
/**
* Get the thread by ID.
*
* @return array<string, mixed>
*/
public function get(string $id): array
{
return static::getHttp()
->acceptJson()
->get('/thread', ['id' => $id])
->throw()
->json();
}
}
@@ -8,12 +8,23 @@ use App\Actions\ActionResult;
use App\Enums\Actions\ActionStatus;
use App\Enums\Actions\Models\Wiki\Video\DiscordNotificationType;
use App\Models\Wiki\Video;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Http;
class DiscordVideoNotificationAction
{
/**
* Get the HTTP client for Discord API.
*/
public static function getHttp(): PendingRequest
{
return Http::withHeaders(['x-api-key' => Config::get('services.discord.api_key')])
->baseUrl(Config::get('services.discord.api_url'));
}
/**
* @param Collection<int, Video> $videos
* @param array<string, mixed> $fields
@@ -22,37 +33,10 @@ class DiscordVideoNotificationAction
{
$type = Arr::get($fields, DiscordNotificationType::getFieldKey());
$newVideos = [];
foreach ($videos as $video) {
$video
->load([
'animethemeentries.animetheme.anime.discordthread',
]);
$anime = $video->animethemeentries->first()->animetheme->anime;
if ($anime->discordthread === null) {
if (Str::length($anime->name) >= 100) {
$anime->name = Str::limit($anime->name, 96, '...');
}
$threadAction = new DiscordThreadAction();
$threadAction->handle($anime, ['name' => $anime->getName()]);
$anime->load('discordthread');
}
$newVideos[] = [
'threadId' => $anime->discordthread->getKey(),
'videoId' => $video->getKey(),
];
}
DiscordThreadAction::getHttp()
static::getHttp()
->post('/notification', [
'type' => $type->value,
'videos' => $newVideos,
'videos' => $videos->map(fn (Video $video): array => ['videoId' => $video->getKey()])->toArray(),
])
->throw();
@@ -1,44 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Actions\Storage\Admin\Dump;
use App\Concerns\Repositories\Admin\ReconcilesDumpRepositories;
use App\Models\Discord\DiscordThread;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
class DumpDiscordAction extends DumpAction
{
use ReconcilesDumpRepositories;
final public const string FILENAME_PREFIX = 'animethemes-db-dump-discord-';
/**
* The list of tables to include in the dump.
*/
public static function allowedTables(): array
{
return [
DiscordThread::TABLE,
];
}
/**
* The temporary path for the database dump.
* Note: The dumper library does not support writing to disk, so we have to write to the local filesystem first.
* Pattern: "animethemes-db-dump-discord-{milliseconds from epoch}.sql".
*/
protected function getDumpFile(): string
{
$filesystem = Storage::disk('local');
return Str::of($filesystem->path(''))
->append(DumpDiscordAction::FILENAME_PREFIX)
->append(strval(Date::now()->valueOf()))
->append('.sql')
->__toString();
}
}
@@ -1,36 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Console\Commands\Storage\Admin;
use App\Actions\Storage\Admin\Dump\DumpDiscordAction;
use Illuminate\Console\Attributes\Description;
use Illuminate\Console\Attributes\Signature;
#[Signature(
'db:dump-discord
{--comments : Write additional information in the MySQL dump such as program version, server version and host}
{--data-only : Dump only the data without the schema in PostgreSQL dump}
{--default-character-set=utf8 : Specify default character set in MySQL dump}
{--extended-insert : Use multiple-row insert syntax in MySQL dump}
{--inserts : Dump data as INSERT commands rather than COPY in PostgreSQL dump}
{--lock-tables : Lock all tables before dumping them to MySQL dump}
{--no-create-info : Turn off CREATE TABLE statements in MySQL dump}
{--quick : Retrieve rows for a table from the server one row at a time in MySQL dump}
{--set-gtid-purged=AUTO : Add SET GTID_PURGED to output in MySQL dump}
{--single-transaction=true : Issue a BEGIN SQL statement before dumping data from server for MySQL dump}
{--skip-column-statistics : Turn off ANALYZE table statements in the MySQL dump}
{--skip-comments : Do not write additional information in the MySQL dump}
{--skip-extended-insert : Turn off extended-insert in MySQL dump}
{--skip-lock-tables : Turn off locking tables before dumping to MySQL dump}
{--skip-quick : Do not retrieve rows for a table from the server one row at a time in MySQL dump}'
)]
#[Description('Produces sanitized database dump, targeting discord-related tables for seeding purposes')]
class DiscordDumpCommand extends DumpCommand
{
protected function action(): DumpDiscordAction
{
return new DumpDiscordAction($this->options());
}
}
-7
View File
@@ -9,7 +9,6 @@ use App\Console\Commands\Repositories\Storage\Admin\DumpReconcileCommand;
use App\Console\Commands\Storage\Admin\AdminDumpCommand;
use App\Console\Commands\Storage\Admin\AuthDumpCommand;
use App\Console\Commands\Storage\Admin\ContentDumpCommand;
use App\Console\Commands\Storage\Admin\DiscordDumpCommand;
use App\Console\Commands\Storage\Admin\DocumentDumpCommand;
use App\Console\Commands\Storage\Admin\DumpPruneCommand;
use App\Console\Commands\Storage\Admin\ListDumpCommand;
@@ -61,12 +60,6 @@ class Kernel extends ConsoleKernel
->storeOutput()
->weeklyOn(Schedule::MONDAY);
$schedule->command(DiscordDumpCommand::class)
->withoutOverlapping()
->runInBackground()
->storeOutput()
->weeklyOn(Schedule::MONDAY);
$schedule->command(DocumentDumpCommand::class)
->withoutOverlapping()
->runInBackground()
@@ -8,7 +8,6 @@ class ServiceConstants
{
final public const string ADMIN_DISCORD_CHANNEL_QUALIFIED = 'services.discord.admin_discord_channel';
final public const string DB_UPDATES_DISCORD_CHANNEL_QUALIFIED = 'services.discord.db_updates_discord_channel';
final public const string SUBMISSIONS_DISCORD_CHANNEL_QUALIFIED = 'services.discord.submissions_discord_channel';
final public const string OPENAI_BEARER_TOKEN = 'services.openai.token';
@@ -1,13 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Events\Discord\DiscordThread;
use App\Events\Base\Admin\AdminDeletedEvent;
use App\Models\Discord\DiscordThread;
/**
* @extends AdminDeletedEvent<DiscordThread>
*/
class DiscordThreadDeleted extends AdminDeletedEvent {}
@@ -1,20 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Events\Discord\DiscordThread;
use App\Events\Base\Admin\AdminUpdatedEvent;
use App\Models\Discord\DiscordThread;
/**
* @extends AdminUpdatedEvent<DiscordThread>
*/
class DiscordThreadUpdated extends AdminUpdatedEvent
{
public function __construct(DiscordThread $thread)
{
parent::__construct($thread);
$this->initializeEmbedFields($thread);
}
}
-3
View File
@@ -20,13 +20,10 @@ class AnimeDeleting extends BaseEvent implements CascadesDeletesEvent
public function cascadeDeletes(): void
{
$anime = $this->getModel()->load([
'discordthread',
Anime::RELATION_SYNONYMS,
Anime::RELATION_VIDEOS,
]);
$anime->discordthread?->delete();
$anime->synonyms->each(function (Synonym $synonym): void {
Synonym::withoutEvents(function () use ($synonym): void {
$synonym->unsearchable();
@@ -1,54 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Filament\Actions\Models\Wiki\Anime;
use App\Actions\Discord\DiscordThreadAction as DiscordThreadActionAction;
use App\Filament\Actions\BaseAction;
use App\Filament\Components\Fields\TextInput;
use App\Models\Discord\DiscordThread;
use App\Models\Wiki\Anime;
use Filament\Schemas\Schema;
use Filament\Support\Icons\Heroicon;
use Illuminate\Support\Facades\Gate;
class DiscordThreadAction extends BaseAction
{
public static function getDefaultName(): ?string
{
return 'discord-thread';
}
protected function setUp(): void
{
parent::setUp();
$this->label(__('filament.actions.anime.discord_thread.name'));
$this->icon(Heroicon::OutlinedChatBubbleLeftRight);
$this->visible(Gate::allows('create', DiscordThread::class));
$this->fillForm(fn (Anime $record): array => ['name' => $record->getName()]);
$this->action(function (Anime $record, array $data): void {
$result = new DiscordThreadActionAction()->handle($record, $data);
if ($result->hasFailed()) {
$this->failedLog($result->getMessage());
}
});
}
public function getSchema(Schema $schema): Schema
{
return $schema
->components([
TextInput::make('name')
->label(__('filament.actions.discord.thread.name'))
->helperText(__('filament.actions.discord.thread.help'))
->required()
->maxlength(100),
]);
}
}
@@ -8,7 +8,6 @@ use App\Actions\Discord\DiscordVideoNotificationAction as DiscordVideoNotificati
use App\Enums\Actions\Models\Wiki\Video\DiscordNotificationType;
use App\Filament\BulkActions\BaseBulkAction;
use App\Filament\Components\Fields\Select;
use App\Models\Discord\DiscordThread;
use App\Models\Wiki\Video;
use Filament\Schemas\Schema;
use Filament\Support\Enums\Width;
@@ -32,7 +31,7 @@ class VideoDiscordNotificationBulkAction extends BaseBulkAction
$this->label(__('filament.bulk_actions.discord.notification.name'));
$this->icon(Heroicon::OutlinedBell);
$this->visible(Gate::allows('create', DiscordThread::class));
$this->visible(Gate::allows('create', Video::class));
}
/**
@@ -1,13 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Discord\DiscordThread\Pages;
use App\Filament\Resources\Base\BaseListResources;
use App\Filament\Resources\Discord\DiscordThreadResource;
class ListDiscordThreads extends BaseListResources
{
protected static string $resource = DiscordThreadResource::class;
}
@@ -1,13 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Discord\DiscordThread\Pages;
use App\Filament\Resources\Base\BaseViewResource;
use App\Filament\Resources\Discord\DiscordThreadResource;
class ViewDiscordThread extends BaseViewResource
{
protected static string $resource = DiscordThreadResource::class;
}
@@ -1,171 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Discord;
use App\Actions\Discord\DiscordThreadAction;
use App\Enums\Filament\NavigationGroup;
use App\Filament\Components\Columns\BelongsToColumn;
use App\Filament\Components\Columns\TextColumn;
use App\Filament\Components\Fields\BelongsTo;
use App\Filament\Components\Fields\TextInput;
use App\Filament\Components\Filters\DateFilter;
use App\Filament\Components\Infolist\BelongsToEntry;
use App\Filament\Components\Infolist\TextEntry;
use App\Filament\Resources\BaseResource;
use App\Filament\Resources\Discord\DiscordThread\Pages\ListDiscordThreads;
use App\Filament\Resources\Discord\DiscordThread\Pages\ViewDiscordThread;
use App\Filament\Resources\Wiki\AnimeResource;
use App\Models\BaseModel;
use App\Models\Discord\DiscordThread;
use Filament\Resources\RelationManagers\RelationGroup;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Components\Utilities\Set;
use Filament\Schemas\Schema;
use Filament\Support\Icons\Heroicon;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
class DiscordThreadResource extends BaseResource
{
/**
* The model the resource corresponds to.
*
* @var class-string<Model>|null
*/
protected static ?string $model = DiscordThread::class;
public static function getModelLabel(): string
{
return __('filament.resources.singularLabel.discord_thread');
}
public static function getPluralModelLabel(): string
{
return __('filament.resources.label.discord_threads');
}
public static function getNavigationGroup(): NavigationGroup
{
return NavigationGroup::DISCORD;
}
public static function getNavigationIcon(): Heroicon
{
return Heroicon::OutlinedChatBubbleLeftRight;
}
public static function getRecordTitleAttribute(): string
{
return DiscordThread::ATTRIBUTE_NAME;
}
public static function getRecordSlug(): string
{
return 'discord-thread';
}
public static function getEloquentQuery(): Builder
{
$query = parent::getEloquentQuery();
// Necessary to prevent lazy loading when loading related resources
return $query->with([DiscordThread::RELATION_ANIME]);
}
public static function form(Schema $schema): Schema
{
return $schema
->components([
TextInput::make(DiscordThread::ATTRIBUTE_ID)
->label(__('filament.fields.discord_thread.id.name'))
->helperText(__('filament.fields.discord_thread.id.help'))
->disabledOn(['edit'])
->formatStateUsing(fn ($state): string => strval($state))
->required()
->live()
->afterStateUpdated(fn (Set $set, string $state): mixed => $set(DiscordThread::ATTRIBUTE_NAME, Arr::get(new DiscordThreadAction()->get($state), 'thread.name'))),
TextInput::make(DiscordThread::ATTRIBUTE_NAME)
->label(__('filament.fields.discord_thread.name.name'))
->helperText(__('filament.fields.discord_thread.name.help'))
->required(),
BelongsTo::make(DiscordThread::ATTRIBUTE_ANIME)
->resource(AnimeResource::class)
->required(),
]);
}
public static function table(Table $table): Table
{
return parent::table($table)
->columns([
TextColumn::make(DiscordThread::ATTRIBUTE_ID)
->label(__('filament.fields.discord_thread.id.name')),
TextColumn::make(DiscordThread::ATTRIBUTE_NAME)
->label(__('filament.fields.discord_thread.name.name'))
->copyableWithMessage()
->searchable(),
BelongsToColumn::make(DiscordThread::RELATION_ANIME, AnimeResource::class),
])
->defaultSort(BaseModel::CREATED_AT, 'desc');
}
public static function infolist(Schema $schema): Schema
{
return $schema
->components([
Section::make(static::getRecordTitle($schema->getRecord()))
->schema([
TextEntry::make(DiscordThread::ATTRIBUTE_ID)
->label(__('filament.fields.discord_thread.id.name')),
TextEntry::make(DiscordThread::ATTRIBUTE_NAME)
->label(__('filament.fields.discord_thread.name.name')),
BelongsToEntry::make(DiscordThread::RELATION_ANIME, AnimeResource::class),
])
->columns(3),
]);
}
/**
* @return array<int, RelationGroup|class-string<\Filament\Resources\RelationManagers\RelationManager>>
*/
public static function getRelations(): array
{
return [
RelationGroup::make(static::getModelLabel(), [
...parent::getBaseRelations(),
]),
];
}
/**
* @return \Filament\Tables\Filters\BaseFilter[]
*/
public static function getFilters(): array
{
return [
DateFilter::make(BaseModel::ATTRIBUTE_CREATED_AT)
->label(__('filament.fields.base.created_at')),
];
}
/**
* @return array<string, \Filament\Resources\Pages\PageRegistration>
*/
public static function getPages(): array
{
return [
'index' => ListDiscordThreads::route('/'),
'view' => ViewDiscordThread::route('/{record:thread_id}'),
];
}
}
@@ -10,7 +10,6 @@ use App\Enums\Models\Wiki\AnimeSeason;
use App\Enums\Models\Wiki\ResourceSite;
use App\Filament\Actions\Models\Wiki\Anime\AttachAnimeResourceAction;
use App\Filament\Actions\Models\Wiki\Anime\BackfillAnimeAction;
use App\Filament\Actions\Models\Wiki\Anime\DiscordThreadAction;
use App\Filament\Components\Columns\TextColumn;
use App\Filament\Components\Fields\Select;
use App\Filament\Components\Fields\Slug;
@@ -260,8 +259,6 @@ class AnimeResource extends BaseResource
];
return [
DiscordThreadAction::make(),
BackfillAnimeAction::make(),
AttachAnimeResourceAction::make(),
-95
View File
@@ -1,95 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Models\Discord;
use App\Events\Discord\DiscordThread\DiscordThreadDeleted;
use App\Events\Discord\DiscordThread\DiscordThreadUpdated;
use App\Models\BaseModel;
use App\Models\Wiki\Anime;
use App\Observers\Discord\DiscordThreadObserver;
use Database\Factories\Discord\DiscordThreadFactory;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Attributes\Table;
use Illuminate\Database\Eloquent\Attributes\WithoutIncrementing;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* @property Anime $anime
* @property int $anime_id
* @property string $thread_id
* @property string $name
*
* @method static DiscordThreadFactory factory(...$parameters)
*/
#[ObservedBy(DiscordThreadObserver::class)]
#[Table(DiscordThread::TABLE, DiscordThread::ATTRIBUTE_ID, 'string')]
#[WithoutIncrementing]
class DiscordThread extends BaseModel
{
use HasFactory;
final public const string TABLE = 'discord_threads';
final public const string ATTRIBUTE_ANIME = 'anime_id';
final public const string ATTRIBUTE_ID = 'thread_id';
final public const string ATTRIBUTE_NAME = 'name';
final public const string RELATION_ANIME = 'anime';
/**
* The event map for the model.
*
* Allows for object-based events for native Eloquent events.
*
* @var array<string, class-string>
*/
protected $dispatchesEvents = [
'deleted' => DiscordThreadDeleted::class,
'updated' => DiscordThreadUpdated::class,
];
/**
* The attributes that are mass assignable.
*
* @var list<string>
*/
protected $fillable = [
DiscordThread::ATTRIBUTE_ANIME,
DiscordThread::ATTRIBUTE_ID,
DiscordThread::ATTRIBUTE_NAME,
];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
DiscordThread::ATTRIBUTE_ANIME => 'int',
DiscordThread::ATTRIBUTE_NAME => 'string',
];
}
public function getName(): string
{
return $this->name;
}
public function getSubtitle(): string
{
return $this->anime->getName();
}
/**
* @return BelongsTo<Anime, $this>
*/
public function anime(): BelongsTo
{
return $this->belongsTo(Anime::class, DiscordThread::ATTRIBUTE_ANIME);
}
}
-11
View File
@@ -21,7 +21,6 @@ use App\Events\Wiki\Anime\AnimeUpdated;
use App\Http\Resources\Pivot\Wiki\Resource\AnimeSeriesJsonResource;
use App\Http\Resources\Pivot\Wiki\Resource\AnimeStudioJsonResource;
use App\Models\BaseModel;
use App\Models\Discord\DiscordThread;
use App\Models\List\External\ExternalEntry;
use App\Models\Wiki\Anime\AnimeSynonym;
use App\Models\Wiki\Anime\AnimeTheme;
@@ -41,7 +40,6 @@ use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Support\Collection;
@@ -53,7 +51,6 @@ use RuntimeException;
/**
* @property int $anime_id
* @property Collection<int, AnimeTheme> $animethemes
* @property DiscordThread|null $discordthread
* @property Collection<int, ExternalEntry> $externalentries
* @property AnimeFormat|null $format
* @property Collection<int, Image> $images
@@ -218,14 +215,6 @@ class Anime extends BaseModel implements Auditable, HasImages, HasResources, Has
return $this->morphMany(Synonym::class, Synonym::RELATION_SYNONYMABLE);
}
/**
* @return HasOne<DiscordThread, $this>
*/
public function discordthread(): HasOne
{
return $this->hasOne(DiscordThread::class, DiscordThread::ATTRIBUTE_ANIME);
}
/**
* @return BelongsToMany<Series, $this, AnimeSeries>
*/
@@ -1,32 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Observers\Discord;
use App\Actions\Discord\DiscordThreadAction;
use App\Models\Discord\DiscordThread;
use Illuminate\Contracts\Events\ShouldHandleEventsAfterCommit;
class DiscordThreadObserver implements ShouldHandleEventsAfterCommit
{
/**
* Handle the DiscordThread "updated" event.
*/
public function updated(DiscordThread $thread): void
{
DiscordThreadAction::getHttp()
->put('/thread', $thread->toArray())
->throw();
}
/**
* Handle the DiscordThread "deleted" event.
*/
public function deleted(DiscordThread $thread): void
{
DiscordThreadAction::getHttp()
->delete('/thread', ['id' => $thread->getKey()])
->throw();
}
}
@@ -1,9 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Policies\Discord;
use App\Policies\BasePolicy;
class DiscordThreadPolicy extends BasePolicy {}
@@ -1,32 +0,0 @@
<?php
declare(strict_types=1);
namespace Database\Factories\Discord;
use App\Models\Discord\DiscordThread;
use Illuminate\Database\Eloquent\Factories\Attributes\UseModel;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @method DiscordThread createOne($attributes = [])
* @method DiscordThread makeOne($attributes = [])
*
* @extends Factory<DiscordThread>
*/
#[UseModel(DiscordThread::class)]
class DiscordThreadFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
DiscordThread::ATTRIBUTE_NAME => fake()->words(3, true),
DiscordThread::ATTRIBUTE_ID => fake()->words(3, true),
];
}
}
@@ -1,27 +0,0 @@
<?php
declare(strict_types=1);
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('discord_threads')) {
Schema::create('discord_threads', function (Blueprint $table) {
$table->timestamps(6);
$table->string('thread_id')->primary();
$table->string('name');
$table->unsignedBigInteger('anime_id');
$table->foreign('anime_id')->references('anime_id')->on('anime');
});
}
}
};
@@ -16,7 +16,6 @@ use App\Models\Auth\Prohibition;
use App\Models\Auth\Role;
use App\Models\Auth\Sanction;
use App\Models\Auth\User;
use App\Models\Discord\DiscordThread;
use App\Models\Document\Page;
use App\Models\List\External\ExternalEntry;
use App\Models\List\ExternalProfile;
@@ -69,9 +68,6 @@ class PermissionSeeder extends Seeder
$this->registerResource(Sanction::class, CrudPermission::cases());
$this->registerResource(User::class, $extendedCrudPermissions);
// Discord Resources
$this->registerResource(DiscordThread::class, CrudPermission::cases());
// List Resources
$this->registerResource(ExternalEntry::class, CrudPermission::cases());
$this->registerResource(ExternalProfile::class, CrudPermission::cases());
@@ -17,7 +17,6 @@ use App\Models\Auth\Prohibition;
use App\Models\Auth\Role;
use App\Models\Auth\Sanction;
use App\Models\Auth\User;
use App\Models\Discord\DiscordThread;
use App\Models\Document\Page;
use App\Models\List\External\ExternalEntry;
use App\Models\List\ExternalProfile;
@@ -77,9 +76,6 @@ class AdminSeeder extends RoleSeeder
$this->configureResource($role, Sanction::class, CrudPermission::cases());
$this->configureResource($role, User::class, $extendedCrudPermissions);
// Discord Resources
$this->configureResource($role, DiscordThread::class, CrudPermission::cases());
// List Resources
$this->configureResource($role, ExternalEntry::class, CrudPermission::cases());
$this->configureResource($role, ExternalProfile::class, CrudPermission::cases());
@@ -9,7 +9,6 @@ use App\Enums\Auth\ExtendedCrudPermission;
use App\Enums\Auth\Role as RoleEnum;
use App\Enums\Auth\SpecialPermission;
use App\Models\Auth\Role;
use App\Models\Discord\DiscordThread;
use App\Models\Document\Page;
use App\Models\List\External\ExternalEntry;
use App\Models\List\ExternalProfile;
@@ -54,9 +53,6 @@ class ContentModeratorRoleSeeder extends RoleSeeder
ExtendedCrudPermission::cases(),
);
// Discord Resources
$this->configureResource($role, DiscordThread::class, [CrudPermission::VIEW]);
// List Resources
$this->configureResource($role, ExternalEntry::class, [CrudPermission::VIEW]);
$this->configureResource($role, ExternalProfile::class, CrudPermission::cases());
@@ -8,7 +8,6 @@ use App\Enums\Auth\CrudPermission;
use App\Enums\Auth\Role as RoleEnum;
use App\Enums\Auth\SpecialPermission;
use App\Models\Auth\Role;
use App\Models\Discord\DiscordThread;
use App\Models\Document\Page;
use App\Models\List\External\ExternalEntry;
use App\Models\List\ExternalProfile;
@@ -48,9 +47,6 @@ class ContributorRoleSeeder extends RoleSeeder
/** @var Role $role */
$role = Role::findOrCreate($roleEnum->value);
// Discord Resources
$this->configureResource($role, DiscordThread::class, [CrudPermission::VIEW]);
// List Resources
$this->configureResource($role, ExternalEntry::class, [CrudPermission::VIEW]);
$this->configureResource($role, ExternalProfile::class, CrudPermission::cases());
@@ -8,7 +8,6 @@ use App\Enums\Auth\CrudPermission;
use App\Enums\Auth\Role as RoleEnum;
use App\Enums\Auth\SpecialPermission;
use App\Models\Auth\Role;
use App\Models\Discord\DiscordThread;
use App\Models\Document\Page;
use App\Models\List\External\ExternalEntry;
use App\Models\List\ExternalProfile;
@@ -48,9 +47,6 @@ class DeveloperRoleSeeder extends RoleSeeder
/** @var Role $role */
$role = Role::findOrCreate($roleEnum->value);
// Discord Resources
$this->configureResource($role, DiscordThread::class, [CrudPermission::VIEW]);
// List Resources
$this->configureResource($role, ExternalEntry::class, [CrudPermission::VIEW]);
$this->configureResource($role, ExternalProfile::class, CrudPermission::cases());
@@ -9,7 +9,6 @@ use App\Enums\Auth\ExtendedCrudPermission;
use App\Enums\Auth\Role as RoleEnum;
use App\Enums\Auth\SpecialPermission;
use App\Models\Auth\Role;
use App\Models\Discord\DiscordThread;
use App\Models\Document\Page;
use App\Models\List\External\ExternalEntry;
use App\Models\List\ExternalProfile;
@@ -54,9 +53,6 @@ class EncoderRoleSeeder extends RoleSeeder
ExtendedCrudPermission::cases(),
);
// Discord Resources
$this->configureResource($role, DiscordThread::class, [CrudPermission::CREATE, CrudPermission::UPDATE, CrudPermission::VIEW]);
// List Resources
$this->configureResource($role, ExternalEntry::class, [CrudPermission::VIEW]);
$this->configureResource($role, ExternalProfile::class, CrudPermission::cases());
@@ -8,7 +8,6 @@ use App\Enums\Auth\CrudPermission;
use App\Enums\Auth\Role as RoleEnum;
use App\Enums\Auth\SpecialPermission;
use App\Models\Auth\Role;
use App\Models\Discord\DiscordThread;
use App\Models\Document\Page;
use App\Models\List\External\ExternalEntry;
use App\Models\List\ExternalProfile;
@@ -48,9 +47,6 @@ class PanelViewerRoleSeeder extends RoleSeeder
/** @var Role $role */
$role = Role::findOrCreate($roleEnum->value);
// Discord Resources
$this->configureResource($role, DiscordThread::class, [CrudPermission::VIEW]);
// List Resources
$this->configureResource($role, ExternalEntry::class, [CrudPermission::VIEW]);
$this->configureResource($role, ExternalProfile::class, CrudPermission::cases());
@@ -8,7 +8,6 @@ use App\Enums\Auth\CrudPermission;
use App\Enums\Auth\Role as RoleEnum;
use App\Enums\Auth\SpecialPermission;
use App\Models\Auth\Role;
use App\Models\Discord\DiscordThread;
use App\Models\Document\Page;
use App\Models\List\External\ExternalEntry;
use App\Models\List\ExternalProfile;
@@ -48,9 +47,6 @@ class PatronRoleSeeder extends RoleSeeder
/** @var Role $role */
$role = Role::findOrCreate($roleEnum->value);
// Discord Resources
$this->configureResource($role, DiscordThread::class, [CrudPermission::VIEW]);
// List Resources
$this->configureResource($role, ExternalEntry::class, [CrudPermission::VIEW]);
$this->configureResource($role, ExternalProfile::class, CrudPermission::cases());
-21
View File
@@ -71,9 +71,6 @@ return [
],
'name' => 'Backfill Anime',
],
'discord_thread' => [
'name' => 'Create Discord Thread',
],
],
'audio' => [
'delete' => [
@@ -100,12 +97,6 @@ return [
'restore' => 'Restore',
'view' => 'View',
],
'discord' => [
'thread' => [
'name' => 'Name',
'help' => 'The name of the thread to be created. Use the default name or a shorter synonym if it exceeds 100 characters.',
],
],
'dump' => [
'dump' => [
'confirmButtonText' => 'Dump',
@@ -691,16 +682,6 @@ return [
'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',
'public' => 'Public',
@@ -1041,7 +1022,6 @@ return [
'announcements' => 'Announcements',
'artists' => 'Artists',
'audios' => 'Audios',
'discord_threads' => 'Threads',
'dumps' => 'Dumps',
'external_entries' => 'External Entries',
'external_profiles' => 'External Profiles',
@@ -1078,7 +1058,6 @@ return [
'announcement' => 'Announcement',
'artist' => 'Artist',
'audio' => 'Audio',
'discord_thread' => 'Thread',
'dump' => 'Dump',
'external_entry' => 'External Entry',
'external_profile' => 'External Profile',
@@ -1,35 +0,0 @@
<?php
declare(strict_types=1);
use App\Console\Commands\Storage\Admin\DiscordDumpCommand;
use App\Constants\Config\DumpConstants;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\Storage;
uses(WithFaker::class);
test('database dump output', function (): void {
Storage::fake('local');
Storage::fake(Config::get(DumpConstants::DISK_QUALIFIED));
Date::setTestNow(fake()->iso8601());
$this->artisan(DiscordDumpCommand::class)
->assertSuccessful()
->expectsOutputToContain('has been created');
});
test('database dump file', function (): void {
$local = Storage::fake('local');
$fs = Storage::fake(Config::get(DumpConstants::DISK_QUALIFIED));
Date::setTestNow(fake()->iso8601());
$this->artisan(DiscordDumpCommand::class)->run();
$this->assertEmpty($local->allFiles());
$this->assertCount(1, $fs->allFiles());
});
@@ -1,116 +0,0 @@
<?php
declare(strict_types=1);
use App\Enums\Auth\CrudPermission;
use App\Enums\Auth\SpecialPermission;
use App\Filament\Actions\Base\CreateAction;
use App\Filament\Actions\Base\DeleteAction;
use App\Filament\Actions\Base\EditAction;
use App\Filament\Resources\Discord\DiscordThreadResource;
use App\Models\Auth\User;
use App\Models\Discord\DiscordThread as DiscordThreadModel;
use App\Models\Wiki\Anime;
use Filament\Actions\Testing\TestAction;
use Livewire\Livewire;
use function Pest\Laravel\actingAs;
use function Pest\Laravel\get;
test('render index page', function (): void {
$user = User::factory()
->withPermissions(
SpecialPermission::VIEW_FILAMENT->value,
CrudPermission::VIEW->format(DiscordThreadModel::class)
)
->createOne();
actingAs($user);
$records = DiscordThreadModel::factory()
->for(Anime::factory())
->count(10)
->create();
get(DiscordThreadResource::getUrl('index'))
->assertSuccessful();
Livewire::test(getIndexPage(DiscordThreadResource::class))
->assertCanSeeTableRecords($records);
});
test('render view page', function (): void {
$user = User::factory()
->withPermissions(
SpecialPermission::VIEW_FILAMENT->value,
CrudPermission::VIEW->format(DiscordThreadModel::class)
)
->createOne();
actingAs($user);
$record = DiscordThreadModel::factory()
->for(Anime::factory())
->createOne();
get(DiscordThreadResource::getUrl('view', ['record' => $record]))
->assertSuccessful();
});
test('mount create action', function (): void {
$user = User::factory()
->withPermissions(
SpecialPermission::VIEW_FILAMENT->value,
CrudPermission::CREATE->format(DiscordThreadModel::class)
)
->createOne();
actingAs($user);
Livewire::test(getIndexPage(DiscordThreadResource::class))
->mountAction(CreateAction::class)
->assertActionMounted(CreateAction::class);
});
test('mount edit action', function (): void {
$user = User::factory()
->withPermissions(
SpecialPermission::VIEW_FILAMENT->value,
CrudPermission::UPDATE->format(DiscordThreadModel::class)
)
->createOne();
actingAs($user);
$record = DiscordThreadModel::factory()
->for(Anime::factory())
->createOne();
Livewire::test(getIndexPage(DiscordThreadResource::class))
->mountAction(TestAction::make(EditAction::getDefaultName())->table($record))
->callMountedAction()
->assertHasNoErrors();
});
test('user cannot create record', function (): void {
Livewire::test(getIndexPage(DiscordThreadResource::class))
->assertActionHidden(CreateAction::class);
});
test('user cannot edit record', function (): void {
$record = DiscordThreadModel::factory()
->for(Anime::factory())
->createOne();
Livewire::test(getIndexPage(DiscordThreadResource::class))
->assertActionDoesNotExist(TestAction::make(EditAction::getDefaultName())->table($record));
});
test('user cannot delete record', function (): void {
$record = DiscordThreadModel::factory()
->for(Anime::factory())
->createOne();
Livewire::test(getIndexPage(DiscordThreadResource::class))
->assertActionDoesNotExist(TestAction::make(DeleteAction::getDefaultName())->table($record));
});
@@ -1,49 +0,0 @@
<?php
declare(strict_types=1);
use App\Constants\FeatureConstants;
use App\Events\Discord\DiscordThread\DiscordThreadDeleted;
use App\Events\Discord\DiscordThread\DiscordThreadUpdated;
use App\Jobs\SendDiscordNotificationJob;
use App\Models\Discord\DiscordThread;
use App\Models\Wiki\Anime;
use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Http;
use Laravel\Pennant\Feature;
test('thread deleted sends discord notification', function (): void {
$thread = DiscordThread::factory()
->for(Anime::factory())
->createOne();
Feature::activate(FeatureConstants::ALLOW_DISCORD_NOTIFICATIONS);
Bus::fake(SendDiscordNotificationJob::class);
Http::fake();
Event::fakeExcept(DiscordThreadDeleted::class);
$thread->delete();
Bus::assertDispatched(SendDiscordNotificationJob::class);
});
test('thread updated sends discord notification', function (): void {
$thread = DiscordThread::factory()
->for(Anime::factory())
->createOne();
Feature::activate(FeatureConstants::ALLOW_DISCORD_NOTIFICATIONS);
Bus::fake(SendDiscordNotificationJob::class);
Http::fake();
Event::fakeExcept(DiscordThreadUpdated::class);
$changes = DiscordThread::factory()
->for(Anime::factory())
->makeOne();
$thread->fill($changes->getAttributes());
$thread->save();
Bus::assertDispatched(SendDiscordNotificationJob::class);
});
@@ -1,35 +0,0 @@
<?php
declare(strict_types=1);
use App\Models\Discord\DiscordThread;
use App\Models\Wiki\Anime;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Foundation\Testing\WithFaker;
uses(WithFaker::class);
test('nameable', function (): void {
$thread = DiscordThread::factory()
->for(Anime::factory())
->createOne();
$this->assertIsString($thread->getName());
});
test('has subtitle', function (): void {
$thread = DiscordThread::factory()
->for(Anime::factory())
->createOne();
$this->assertIsString($thread->getSubtitle());
});
test('anime', function (): void {
$thread = DiscordThread::factory()
->for(Anime::factory())
->createOne();
$this->assertInstanceOf(BelongsTo::class, $thread->anime());
$this->assertInstanceOf(Anime::class, $thread->anime()->first());
});