Files
SkinbaseNova/app/Services/Academy/AcademyInteractionService.php

171 lines
5.5 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services\Academy;
use App\Models\AcademyLike;
use App\Models\AcademyPromptTemplate;
use App\Models\AcademySave;
use App\Models\User;
use App\Support\AcademyAnalytics\AcademyAnalyticsContentType;
use App\Support\AcademyAnalytics\AcademyAnalyticsEventType;
use Illuminate\Http\Request;
use InvalidArgumentException;
final class AcademyInteractionService
{
public function __construct(
private readonly AcademyAnalyticsContentResolver $contentResolver,
private readonly AcademyAnalyticsService $analytics,
private readonly AcademyProgressService $progress,
) {
}
/**
* @return array<string, int|bool>
*/
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<string, int|bool>
*/
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<string, int|bool>
*/
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;
}
}