feat(graphql): Me.likes relation (#1103)

This commit is contained in:
Kyrch
2026-02-16 21:29:19 -03:00
committed by GitHub
parent 73ea075f54
commit e4179cf312
14 changed files with 134 additions and 86 deletions
@@ -7,7 +7,6 @@ namespace App\Concerns\Filament\ActionLogs;
use App\Models\Admin\ActionLog;
use Filament\Actions\Action;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Support\Str;
use Throwable;
@@ -46,9 +45,9 @@ trait HasActionLogs
public function updateLog(Model $relatedModel, Model $pivot): void
{
$this->actionLog->update([
ActionLog::ATTRIBUTE_TARGET_TYPE => Relation::getMorphAlias($relatedModel->getMorphClass()),
ActionLog::ATTRIBUTE_TARGET_TYPE => $relatedModel->getMorphClass(),
ActionLog::ATTRIBUTE_TARGET_ID => $relatedModel->getKey(),
ActionLog::ATTRIBUTE_MODEL_TYPE => Relation::getMorphAlias($pivot->getMorphClass()),
ActionLog::ATTRIBUTE_MODEL_TYPE => $pivot->getMorphClass(),
ActionLog::ATTRIBUTE_MODEL_ID => $pivot->getKey(),
]);
}
@@ -14,9 +14,6 @@ trait AggregatesLike
*/
public function likeAggregate(): MorphOne
{
return $this->morphOne(
LikeAggregate::class,
LikeAggregate::ATTRIBUTE_LIKEABLE,
);
return $this->morphOne(LikeAggregate::class, LikeAggregate::ATTRIBUTE_LIKEABLE);
}
}
@@ -16,9 +16,6 @@ trait AggregatesView
*/
public function viewAggregate(): MorphOne
{
return $this->morphOne(
ViewAggregate::class,
ViewAggregate::ATTRIBUTE_VIEWABLE,
);
return $this->morphOne(ViewAggregate::class, ViewAggregate::ATTRIBUTE_VIEWABLE);
}
}
@@ -23,10 +23,9 @@ trait CanCreateAnimeSynonym
Log::info("Creating {$text} for Anime {$anime->getName()}");
AnimeSynonym::query()->create([
$anime->animesynonyms()->create([
AnimeSynonym::ATTRIBUTE_TEXT => $text,
AnimeSynonym::ATTRIBUTE_TYPE => $type,
AnimeSynonym::ATTRIBUTE_ANIME => $anime->getKey(),
]);
}
}
+4 -4
View File
@@ -7,7 +7,6 @@ namespace App\Concerns\Models;
use App\Models\Auth\User;
use App\Models\User\Like;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\Relations\Relation;
trait InteractsWithLikes
{
@@ -21,13 +20,14 @@ trait InteractsWithLikes
return null;
}
return Like::query()->create([
return $this->likes()->create([
Like::ATTRIBUTE_USER => $user->getKey(),
Like::ATTRIBUTE_LIKEABLE_TYPE => Relation::getMorphAlias($this->getMorphClass()),
Like::ATTRIBUTE_LIKEABLE_ID => $this->getKey(),
]);
}
/**
* @return MorphMany<Like, $this>
*/
public function likes(): MorphMany
{
return $this->morphMany(Like::class, Like::RELATION_LIKEABLE);
+3
View File
@@ -9,6 +9,9 @@ use Illuminate\Database\Eloquent\Relations\MorphMany;
trait Submitable
{
/**
* @return MorphMany<Submission, $this>
*/
public function submissions(): MorphMany
{
return $this->morphMany(Submission::class, Submission::RELATION_ACTIONABLE);
@@ -12,7 +12,6 @@ use App\GraphQL\Schema\Fields\Base\CreatedAtField;
use App\GraphQL\Schema\Fields\Base\IdField;
use App\GraphQL\Schema\Fields\Base\UpdatedAtField;
use App\GraphQL\Schema\Fields\Field;
use App\GraphQL\Schema\Fields\Relations\BelongsToManyRelation;
use App\GraphQL\Schema\Fields\Relations\HasManyRelation;
use App\GraphQL\Schema\Fields\Relations\MorphManyRelation;
use App\GraphQL\Schema\Fields\Relations\MorphToManyRelation;
@@ -20,8 +19,8 @@ use App\GraphQL\Schema\Types\Auth\PermissionType;
use App\GraphQL\Schema\Types\Auth\RoleType;
use App\GraphQL\Schema\Types\EloquentType;
use App\GraphQL\Schema\Types\List\PlaylistType;
use App\GraphQL\Schema\Types\User\LikeType;
use App\GraphQL\Schema\Types\User\WatchHistoryType;
use App\GraphQL\Schema\Types\Wiki\Anime\Theme\AnimeThemeEntryType;
use App\GraphQL\Schema\Unions\NotificationUnion;
use App\Models\Auth\User;
@@ -50,11 +49,10 @@ class MeType extends EloquentType
new MorphManyRelation(new NotificationUnion(), User::RELATION_NOTIFICATIONS),
new HasManyRelation(new PlaylistType(), User::RELATION_PLAYLISTS),
new HasManyRelation(new LikeType(), User::RELATION_LIKES),
new HasManyRelation(new WatchHistoryType(), User::RELATION_WATCH_HISTORY),
new MorphToManyRelation($this, new RoleType(), User::RELATION_ROLES),
new MorphToManyRelation($this, new PermissionType(), User::RELATION_PERMISSIONS),
new BelongsToManyRelation($this, new AnimeThemeEntryType(), 'likedentries'),
new BelongsToManyRelation($this, new PlaylistType(), 'likedplaylists'),
new HasManyRelation(new WatchHistoryType(), User::RELATION_WATCH_HISTORY),
];
}
+4 -2
View File
@@ -32,8 +32,10 @@ class LikeType extends EloquentType
new LikeAnimeThemeEntryField(),
new LikePlaylistField(),
new BelongsToRelation(new UserType(), Like::RELATION_USER),
new MorphToRelation(new LikeableUnion(), Like::RELATION_LIKEABLE),
new BelongsToRelation(new UserType(), Like::RELATION_USER)
->nonNullable(),
new MorphToRelation(new LikeableUnion(), Like::RELATION_LIKEABLE)
->nonNullable(),
];
}
}
+18 -19
View File
@@ -18,7 +18,6 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Eloquent\Relations\Pivot;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\Session;
@@ -184,11 +183,11 @@ class ActionLog extends Model implements Nameable
ActionLog::ATTRIBUTE_BATCH_ID => Str::orderedUuid()->__toString(),
ActionLog::ATTRIBUTE_USER => ActionLog::getUserId(),
ActionLog::ATTRIBUTE_NAME => 'Create',
ActionLog::ATTRIBUTE_ACTIONABLE_TYPE => Relation::getMorphAlias($model->getMorphClass()),
ActionLog::ATTRIBUTE_ACTIONABLE_TYPE => $model->getMorphClass(),
ActionLog::ATTRIBUTE_ACTIONABLE_ID => $model->getKey(),
ActionLog::ATTRIBUTE_TARGET_TYPE => Relation::getMorphAlias($model->getMorphClass()),
ActionLog::ATTRIBUTE_TARGET_TYPE => $model->getMorphClass(),
ActionLog::ATTRIBUTE_TARGET_ID => $model->getKey(),
ActionLog::ATTRIBUTE_MODEL_TYPE => Relation::getMorphAlias($model->getMorphClass()),
ActionLog::ATTRIBUTE_MODEL_TYPE => $model->getMorphClass(),
ActionLog::ATTRIBUTE_MODEL_ID => $model->getKey(),
ActionLog::ATTRIBUTE_FIELDS => static::getFields($model->getAttributes(), $model),
ActionLog::ATTRIBUTE_STATUS => ActionLogStatus::FINISHED->value,
@@ -205,11 +204,11 @@ class ActionLog extends Model implements Nameable
ActionLog::ATTRIBUTE_BATCH_ID => Str::orderedUuid()->__toString(),
ActionLog::ATTRIBUTE_USER => ActionLog::getUserId(),
ActionLog::ATTRIBUTE_NAME => 'Update',
ActionLog::ATTRIBUTE_ACTIONABLE_TYPE => Relation::getMorphAlias($model->getMorphClass()),
ActionLog::ATTRIBUTE_ACTIONABLE_TYPE => $model->getMorphClass(),
ActionLog::ATTRIBUTE_ACTIONABLE_ID => $model->getKey(),
ActionLog::ATTRIBUTE_TARGET_TYPE => Relation::getMorphAlias($model->getMorphClass()),
ActionLog::ATTRIBUTE_TARGET_TYPE => $model->getMorphClass(),
ActionLog::ATTRIBUTE_TARGET_ID => $model->getKey(),
ActionLog::ATTRIBUTE_MODEL_TYPE => Relation::getMorphAlias($model->getMorphClass()),
ActionLog::ATTRIBUTE_MODEL_TYPE => $model->getMorphClass(),
ActionLog::ATTRIBUTE_MODEL_ID => $model->getKey(),
ActionLog::ATTRIBUTE_FIELDS => static::getFields($model->getChanges(), $model),
ActionLog::ATTRIBUTE_STATUS => ActionLogStatus::FINISHED->value,
@@ -242,11 +241,11 @@ class ActionLog extends Model implements Nameable
ActionLog::ATTRIBUTE_BATCH_ID => Str::orderedUuid()->__toString(),
ActionLog::ATTRIBUTE_USER => ActionLog::getUserId(),
ActionLog::ATTRIBUTE_NAME => $actionName,
ActionLog::ATTRIBUTE_ACTIONABLE_TYPE => Relation::getMorphAlias($model->getMorphClass()),
ActionLog::ATTRIBUTE_ACTIONABLE_TYPE => $model->getMorphClass(),
ActionLog::ATTRIBUTE_ACTIONABLE_ID => $model->getKey(),
ActionLog::ATTRIBUTE_TARGET_TYPE => Relation::getMorphAlias($model->getMorphClass()),
ActionLog::ATTRIBUTE_TARGET_TYPE => $model->getMorphClass(),
ActionLog::ATTRIBUTE_TARGET_ID => $model->getKey(),
ActionLog::ATTRIBUTE_MODEL_TYPE => Relation::getMorphAlias($model->getMorphClass()),
ActionLog::ATTRIBUTE_MODEL_TYPE => $model->getMorphClass(),
ActionLog::ATTRIBUTE_MODEL_ID => $model->getKey(),
ActionLog::ATTRIBUTE_FIELDS => static::getFields($model->getAttributes(), $model),
ActionLog::ATTRIBUTE_STATUS => ActionLogStatus::FINISHED->value,
@@ -265,11 +264,11 @@ class ActionLog extends Model implements Nameable
ActionLog::ATTRIBUTE_BATCH_ID => Str::orderedUuid()->__toString(),
ActionLog::ATTRIBUTE_USER => ActionLog::getUserId(),
ActionLog::ATTRIBUTE_NAME => $actionName,
ActionLog::ATTRIBUTE_ACTIONABLE_TYPE => Relation::getMorphAlias($related->getMorphClass()),
ActionLog::ATTRIBUTE_ACTIONABLE_TYPE => $related->getMorphClass(),
ActionLog::ATTRIBUTE_ACTIONABLE_ID => $related->getKey(),
ActionLog::ATTRIBUTE_TARGET_TYPE => Relation::getMorphAlias($parent->getMorphClass()),
ActionLog::ATTRIBUTE_TARGET_TYPE => $parent->getMorphClass(),
ActionLog::ATTRIBUTE_TARGET_ID => $parent->getKey(),
ActionLog::ATTRIBUTE_MODEL_TYPE => Relation::getMorphAlias($pivot->getMorphClass()),
ActionLog::ATTRIBUTE_MODEL_TYPE => $pivot->getMorphClass(),
ActionLog::ATTRIBUTE_MODEL_ID => $pivot instanceof Pivot ? null : $pivot->getKey(),
ActionLog::ATTRIBUTE_FIELDS => $data ? static::getFields($data) : static::getFields($pivot->getAttributes(), $pivot),
ActionLog::ATTRIBUTE_STATUS => ActionLogStatus::FINISHED->value,
@@ -286,11 +285,11 @@ class ActionLog extends Model implements Nameable
ActionLog::ATTRIBUTE_BATCH_ID => Str::orderedUuid()->__toString(),
ActionLog::ATTRIBUTE_USER => ActionLog::getUserId(),
ActionLog::ATTRIBUTE_NAME => $actionName,
ActionLog::ATTRIBUTE_ACTIONABLE_TYPE => Relation::getMorphAlias($related->getMorphClass()),
ActionLog::ATTRIBUTE_ACTIONABLE_TYPE => $related->getMorphClass(),
ActionLog::ATTRIBUTE_ACTIONABLE_ID => $related->getKey(),
ActionLog::ATTRIBUTE_TARGET_TYPE => Relation::getMorphAlias($parent->getMorphClass()),
ActionLog::ATTRIBUTE_TARGET_TYPE => $parent->getMorphClass(),
ActionLog::ATTRIBUTE_TARGET_ID => $parent->getKey(),
ActionLog::ATTRIBUTE_MODEL_TYPE => Relation::getMorphAlias($related->getMorphClass()),
ActionLog::ATTRIBUTE_MODEL_TYPE => $related->getMorphClass(),
ActionLog::ATTRIBUTE_MODEL_ID => $related->getKey(),
ActionLog::ATTRIBUTE_FIELDS => static::getFields($action->getData()),
ActionLog::ATTRIBUTE_STATUS => ActionLogStatus::FINISHED->value,
@@ -307,11 +306,11 @@ class ActionLog extends Model implements Nameable
ActionLog::ATTRIBUTE_BATCH_ID => $batchId,
ActionLog::ATTRIBUTE_USER => ActionLog::getUserId(),
ActionLog::ATTRIBUTE_NAME => $action->getLabel(),
ActionLog::ATTRIBUTE_ACTIONABLE_TYPE => Relation::getMorphAlias($model->getMorphClass()),
ActionLog::ATTRIBUTE_ACTIONABLE_TYPE => $model->getMorphClass(),
ActionLog::ATTRIBUTE_ACTIONABLE_ID => $model->getKey(),
ActionLog::ATTRIBUTE_TARGET_TYPE => Relation::getMorphAlias($model->getMorphClass()),
ActionLog::ATTRIBUTE_TARGET_TYPE => $model->getMorphClass(),
ActionLog::ATTRIBUTE_TARGET_ID => $model->getKey(),
ActionLog::ATTRIBUTE_MODEL_TYPE => Relation::getMorphAlias($model->getMorphClass()),
ActionLog::ATTRIBUTE_MODEL_TYPE => $model->getMorphClass(),
ActionLog::ATTRIBUTE_MODEL_ID => $model->getKey(),
ActionLog::ATTRIBUTE_FIELDS => static::getFields($action->getData()),
ActionLog::ATTRIBUTE_STATUS => ActionLogStatus::RUNNING->value,
+8 -39
View File
@@ -21,7 +21,6 @@ use App\Models\User\Like;
use App\Models\User\Notification;
use App\Models\User\Submission;
use App\Models\User\WatchHistory;
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
use Database\Factories\Auth\UserFactory;
use Filament\Facades\Filament;
use Filament\Models\Contracts\FilamentUser;
@@ -31,10 +30,8 @@ use Filament\Panel;
use Illuminate\Contracts\Auth\MustVerifyEmail;
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\MorphMany;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Carbon;
@@ -52,7 +49,8 @@ use Spatie\Permission\Traits\HasRoles;
* @property Carbon|null $email_verified_at
* @property Collection<int, ExternalProfile> $externalprofiles
* @property int $id
* @property Collection<int, Submission> $managedsubmissions
* @property Collection<int, Like> $likes
* @property Collection<int, Submission> $managedSubmissions
* @property string $name
* @property string $password
* @property Collection<int, Playlist> $playlists
@@ -89,7 +87,8 @@ class User extends Authenticatable implements FilamentUser, HasAvatar, HasSubtit
final public const string ATTRIBUTE_TWO_FACTOR_SECRET = 'two_factor_secret';
final public const string RELATION_EXTERNAL_PROFILES = 'externalprofiles';
final public const string RELATION_MANAGED_SUBMISSIONS = 'managedsubmissions';
final public const string RELATION_LIKES = 'likes';
final public const string RELATION_MANAGED_SUBMISSIONS = 'managedSubmissions';
final public const string RELATION_NOTIFICATIONS = 'notifications';
final public const string RELATION_PERMISSIONS = 'permissions';
final public const string RELATION_PROHIBITIONS = 'prohibitions';
@@ -215,41 +214,11 @@ class User extends Authenticatable implements FilamentUser, HasAvatar, HasSubtit
}
/**
* Get the liked entries of the user.
*
* @return BelongsToMany<AnimeThemeEntry, $this>
* @return HasMany<Like, $this>
*/
public function likedentries(): BelongsToMany
public function likes(): HasMany
{
return $this->belongsToMany(
AnimeThemeEntry::class,
Like::TABLE,
Like::ATTRIBUTE_USER,
Like::ATTRIBUTE_LIKEABLE_ID,
null,
AnimeThemeEntry::ATTRIBUTE_ID
)
->wherePivot(Like::ATTRIBUTE_LIKEABLE_TYPE, Relation::getMorphAlias(AnimeThemeEntry::class))
->withTimestamps();
}
/**
* Get the liked playlists of the user.
*
* @return BelongsToMany<Playlist, $this>
*/
public function likedplaylists(): BelongsToMany
{
return $this->belongsToMany(
Playlist::class,
Like::TABLE,
Like::ATTRIBUTE_USER,
Like::ATTRIBUTE_LIKEABLE_ID,
null,
Playlist::ATTRIBUTE_ID
)
->wherePivot(Like::ATTRIBUTE_LIKEABLE_TYPE, Relation::getMorphAlias(Playlist::class))
->withTimestamps();
return $this->hasMany(Like::class, Like::ATTRIBUTE_USER);
}
/**
@@ -263,7 +232,7 @@ class User extends Authenticatable implements FilamentUser, HasAvatar, HasSubtit
/**
* Get the submissions that the admin managed.
*/
public function managedsubmissions(): HasMany
public function managedSubmissions(): HasMany
{
return $this->hasMany(Submission::class, Submission::ATTRIBUTE_MODERATOR);
}
@@ -47,9 +47,6 @@ class PerformanceFactory extends Factory
*/
public function artist(Artist|Membership $artist): static
{
return $this->state([
Performance::ATTRIBUTE_ARTIST_TYPE => Relation::getMorphAlias($artist->getMorphClass()),
Performance::ATTRIBUTE_ARTIST_ID => $artist->getKey(),
]);
return $this->for($artist, Performance::RELATION_ARTIST);
}
}
+65
View File
@@ -3,7 +3,12 @@
declare(strict_types=1);
use App\Models\Auth\User;
use App\Models\List\ExternalProfile;
use App\Models\List\Playlist;
use App\Models\User\Like;
use App\Models\User\Notification as UserNotification;
use App\Models\User\Submission;
use App\Models\User\WatchHistory;
use Illuminate\Auth\Notifications\VerifyEmail;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphMany;
@@ -53,3 +58,63 @@ test('playlists', function () {
$this->assertEquals($playlistCount, $user->playlists()->count());
$this->assertInstanceOf(Playlist::class, $user->playlists()->first());
});
test('external profiles', function () {
$profileCount = fake()->randomDigitNotNull();
$user = User::factory()
->has(ExternalProfile::factory()->count($profileCount))
->createOne();
$this->assertInstanceOf(HasMany::class, $user->externalprofiles());
$this->assertEquals($profileCount, $user->externalprofiles()->count());
$this->assertInstanceOf(ExternalProfile::class, $user->externalprofiles()->first());
});
test('likes', function () {
$likeCount = fake()->randomDigitNotNull();
$user = User::factory()
->has(Like::factory()->count($likeCount)->forEntry())
->createOne();
$this->assertInstanceOf(HasMany::class, $user->likes());
$this->assertEquals($likeCount, $user->likes()->count());
$this->assertInstanceOf(Like::class, $user->likes()->first());
});
test('submissions', function () {
$submissionCount = fake()->randomDigitNotNull();
$user = User::factory()
->has(Submission::factory()->count($submissionCount))
->createOne();
$this->assertInstanceOf(HasMany::class, $user->submissions());
$this->assertEquals($submissionCount, $user->submissions()->count());
$this->assertInstanceOf(Submission::class, $user->submissions()->first());
});
test('notifications', function () {
$notificationCount = fake()->randomDigitNotNull();
$user = User::factory()
->has(UserNotification::factory()->count($notificationCount))
->createOne();
$this->assertInstanceOf(MorphMany::class, $user->notifications());
$this->assertEquals($notificationCount, $user->notifications()->count());
$this->assertInstanceOf(UserNotification::class, $user->notifications()->first());
});
test('watch history', function () {
$historyCount = fake()->randomDigitNotNull();
$user = User::factory()
->has(WatchHistory::factory()->count($historyCount))
->createOne();
$this->assertInstanceOf(HasMany::class, $user->watchHistory());
$this->assertEquals($historyCount, $user->watchHistory()->count());
$this->assertInstanceOf(WatchHistory::class, $user->watchHistory()->first());
});
+11
View File
@@ -6,10 +6,12 @@ use App\Enums\Models\List\PlaylistVisibility;
use App\Models\Auth\User;
use App\Models\List\Playlist;
use App\Models\List\Playlist\PlaylistTrack;
use App\Models\User\Like;
use App\Models\Wiki\Image;
use App\Pivots\Morph\Imageable;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Support\Arr;
@@ -145,3 +147,12 @@ test('tracks', function () {
$this->assertEquals($trackCount, $playlist->tracks()->count());
$this->assertInstanceOf(PlaylistTrack::class, $playlist->tracks()->first());
});
test('likes', function () {
$playlist = Playlist::factory()
->for(Like::factory()->for(User::factory()))
->createOne();
$this->assertInstanceOf(MorphMany::class, $playlist->likes());
$this->assertInstanceOf(Like::class, $playlist->likes()->first());
});
@@ -2,6 +2,8 @@
declare(strict_types=1);
use App\Models\Auth\User;
use App\Models\User\Like;
use App\Models\Wiki\Anime;
use App\Models\Wiki\Anime\AnimeTheme;
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
@@ -11,6 +13,7 @@ use App\Pivots\Morph\Resourceable;
use App\Pivots\Wiki\AnimeThemeEntryVideo;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Foundation\Testing\WithFaker;
use Znck\Eloquent\Relations\BelongsToThrough;
@@ -94,3 +97,12 @@ test('anime', function () {
$this->assertInstanceOf(BelongsToThrough::class, $entry->anime());
$this->assertInstanceOf(Anime::class, $entry->anime()->first());
});
test('likes', function () {
$entry = AnimeThemeEntry::factory()
->for(Like::factory()->for(User::factory()))
->createOne();
$this->assertInstanceOf(MorphMany::class, $entry->likes());
$this->assertInstanceOf(Like::class, $entry->likes()->first());
});