*/ public function toggleLike(User $user, string $contentType, int $contentId, ?Request $request = null): array { $this->assertSupportedContent($contentType, $contentId); $existing = AcademyLike::query() ->where('user_id', $user->id) ->where('content_type', $contentType) ->where('content_id', $contentId) ->first(); if ($existing) { $existing->delete(); $liked = false; } else { AcademyLike::query()->create([ 'user_id' => $user->id, 'content_type' => $contentType, 'content_id' => $contentId, ]); $liked = true; if ($contentType === AcademyAnalyticsContentType::PROMPT) { $this->analytics->track([ 'event_type' => AcademyAnalyticsEventType::PROMPT_LIKE, 'content_type' => $contentType, 'content_id' => $contentId, ], $user, $request); } } return [ 'liked' => $liked, 'likes_count' => $this->likesCount($contentType, $contentId), ]; } /** * @return array */ public function toggleSave(User $user, string $contentType, int $contentId, ?Request $request = null): array { $content = $this->assertSupportedContent($contentType, $contentId); if ($contentType === AcademyAnalyticsContentType::PROMPT && $content instanceof AcademyPromptTemplate) { $existing = AcademySave::query() ->where('user_id', $user->id) ->where('content_type', $contentType) ->where('content_id', $contentId) ->exists(); if ($existing) { $this->progress->unsavePrompt($user, $content); $saved = false; } else { $this->progress->savePrompt($user, $content); $saved = true; } return [ 'saved' => $saved, 'saves_count' => $this->savesCount($contentType, $contentId), ]; } $existing = AcademySave::query() ->where('user_id', $user->id) ->where('content_type', $contentType) ->where('content_id', $contentId) ->first(); if ($existing) { $existing->delete(); $saved = false; } else { AcademySave::query()->create([ 'user_id' => $user->id, 'content_type' => $contentType, 'content_id' => $contentId, ]); $saved = true; } return [ 'saved' => $saved, 'saves_count' => $this->savesCount($contentType, $contentId), ]; } /** * @return array */ public function getInteractionState(?User $user, string $contentType, int $contentId): array { $this->assertSupportedContent($contentType, $contentId); return [ 'liked' => $user ? AcademyLike::query()->where('user_id', $user->id)->where('content_type', $contentType)->where('content_id', $contentId)->exists() : false, 'saved' => $user ? AcademySave::query()->where('user_id', $user->id)->where('content_type', $contentType)->where('content_id', $contentId)->exists() : false, 'likes_count' => $this->likesCount($contentType, $contentId), 'saves_count' => $this->savesCount($contentType, $contentId), ]; } public function likesCount(string $contentType, int $contentId): int { return AcademyLike::query() ->where('content_type', $contentType) ->where('content_id', $contentId) ->count(); } public function savesCount(string $contentType, int $contentId): int { return AcademySave::query() ->where('content_type', $contentType) ->where('content_id', $contentId) ->count(); } private function assertSupportedContent(string $contentType, int $contentId): mixed { if (! in_array($contentType, [ AcademyAnalyticsContentType::PROMPT, AcademyAnalyticsContentType::LESSON, AcademyAnalyticsContentType::COURSE, AcademyAnalyticsContentType::PROMPT_PACK, AcademyAnalyticsContentType::CHALLENGE, ], true)) { throw new InvalidArgumentException('Unsupported Academy interaction content type.'); } $content = $this->contentResolver->resolve($contentType, $contentId); if ($content === null) { throw new InvalidArgumentException('Unknown Academy interaction content target.'); } return $content; } }