'datetime', ]; } public function photo(): BelongsTo { return $this->belongsTo(Photo::class); } public static function mark(Photo $photo, string $guard, int $actorId): void { static::query()->firstOrCreate( [ 'photo_id' => $photo->id, 'actor_guard' => $guard, 'actor_id' => $actorId, ], ['read_at' => now()], ); } /** * @param list $photoIds * @return list */ public static function readPhotoIds(string $guard, int $actorId, array $photoIds): array { if ($photoIds === []) { return []; } return static::query() ->where('actor_guard', $guard) ->where('actor_id', $actorId) ->whereIn('photo_id', $photoIds) ->pluck('photo_id') ->map(static fn ($id) => (int) $id) ->all(); } public static function applyFilter(Builder|Relation $q, string $guard, int $actorId, string $photoIdColumn, mixed $read): void { $flag = self::parseReadFilter($read); if ($flag === null) { return; } $exists = function ($sub) use ($guard, $actorId, $photoIdColumn) { $sub->from('photo_reads') ->whereColumn('photo_reads.photo_id', $photoIdColumn) ->where('photo_reads.actor_guard', $guard) ->where('photo_reads.actor_id', $actorId); }; if ($flag) { $q->whereExists($exists); } else { $q->whereNotExists($exists); } } public static function parseReadFilter(mixed $raw): ?bool { if ($raw === null) { return null; } $value = is_string($raw) ? strtolower(trim($raw)) : $raw; if ($value === '' || $value === 'all') { return null; } if ($value === 0 || $value === '0' || $value === 'unread') { return false; } if ($value === 1 || $value === '1' || $value === 'read') { return true; } return null; } }