Files
SkinbaseNova/app/Http/Controllers/Api/ArtworkInteractionController.php
Gregor Klevze 90f244f264 feat: artwork share system with modal, native Web Share API, and tracking
- Add ArtworkShareModal with glassmorphism UI (Facebook, X, Pinterest, Email, Copy Link, Embed Code)
- Add ArtworkShareButton with lazy-loaded modal and native share fallback
- Add useWebShare hook abstracting navigator.share with AbortError handling
- Add ShareToast auto-dismissing notification component
- Add share() endpoint to ArtworkInteractionController (POST /api/artworks/{id}/share)
- Add artwork_shares migration for Phase 2 share tracking
- Refactor ArtworkActionBar to use new ArtworkShareButton component
2026-02-28 15:29:45 +01:00

224 lines
7.2 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Services\FollowService;
use App\Services\UserStatsService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
final class ArtworkInteractionController extends Controller
{
public function favorite(Request $request, int $artworkId): JsonResponse
{
$state = $request->boolean('state', true);
$this->toggleSimple(
request: $request,
table: 'artwork_favourites',
keyColumns: ['user_id', 'artwork_id'],
keyValues: ['user_id' => (int) $request->user()->id, 'artwork_id' => $artworkId],
insertPayload: ['created_at' => now(), 'updated_at' => now()],
requiredTable: 'artwork_favourites'
);
$this->syncArtworkStats($artworkId);
// Update creator's favorites_received_count
$creatorId = (int) DB::table('artworks')->where('id', $artworkId)->value('user_id');
if ($creatorId) {
$svc = app(UserStatsService::class);
if ($state) {
$svc->incrementFavoritesReceived($creatorId);
$svc->setLastActiveAt((int) $request->user()->id);
// Record activity event (new favourite only)
try {
\App\Models\ActivityEvent::record(
actorId: (int) $request->user()->id,
type: \App\Models\ActivityEvent::TYPE_FAVORITE,
targetType: \App\Models\ActivityEvent::TARGET_ARTWORK,
targetId: $artworkId,
);
} catch (\Throwable) {}
} else {
$svc->decrementFavoritesReceived($creatorId);
}
}
return response()->json($this->statusPayload((int) $request->user()->id, $artworkId));
}
public function like(Request $request, int $artworkId): JsonResponse
{
$this->toggleSimple(
request: $request,
table: 'artwork_likes',
keyColumns: ['user_id', 'artwork_id'],
keyValues: ['user_id' => (int) $request->user()->id, 'artwork_id' => $artworkId],
insertPayload: ['created_at' => now(), 'updated_at' => now()],
requiredTable: 'artwork_likes'
);
$this->syncArtworkStats($artworkId);
return response()->json($this->statusPayload((int) $request->user()->id, $artworkId));
}
public function report(Request $request, int $artworkId): JsonResponse
{
if (! Schema::hasTable('artwork_reports')) {
return response()->json(['message' => 'Reporting unavailable'], 422);
}
$data = $request->validate([
'reason' => ['nullable', 'string', 'max:1000'],
]);
DB::table('artwork_reports')->updateOrInsert(
[
'artwork_id' => $artworkId,
'reporter_user_id' => (int) $request->user()->id,
],
[
'reason' => trim((string) ($data['reason'] ?? '')) ?: null,
'reported_at' => now(),
'created_at' => now(),
'updated_at' => now(),
]
);
return response()->json(['ok' => true, 'reported' => true]);
}
public function follow(Request $request, int $userId): JsonResponse
{
$actorId = (int) $request->user()->id;
if ($actorId === $userId) {
return response()->json(['message' => 'Cannot follow yourself'], 422);
}
$svc = app(FollowService::class);
$state = $request->boolean('state', true);
if ($state) {
$svc->follow($actorId, $userId);
} else {
$svc->unfollow($actorId, $userId);
}
return response()->json([
'ok' => true,
'is_following' => $state,
'followers_count' => $svc->followersCount($userId),
]);
}
/**
* POST /api/artworks/{id}/share — record a share event (Phase 2 tracking).
*/
public function share(Request $request, int $artworkId): JsonResponse
{
$data = $request->validate([
'platform' => ['required', 'string', 'in:facebook,twitter,pinterest,email,copy,embed'],
]);
if (Schema::hasTable('artwork_shares')) {
DB::table('artwork_shares')->insert([
'artwork_id' => $artworkId,
'user_id' => $request->user()?->id,
'platform' => $data['platform'],
'created_at' => now(),
]);
}
return response()->json(['ok' => true]);
}
private function toggleSimple(
Request $request,
string $table,
array $keyColumns,
array $keyValues,
array $insertPayload,
string $requiredTable
): void {
if (! Schema::hasTable($requiredTable)) {
abort(422, 'Interaction unavailable');
}
$state = $request->boolean('state', true);
$query = DB::table($table);
foreach ($keyColumns as $column) {
$query->where($column, $keyValues[$column]);
}
if ($state) {
if (! $query->exists()) {
DB::table($table)->insert(array_merge($keyValues, $insertPayload));
}
} else {
$query->delete();
}
}
private function syncArtworkStats(int $artworkId): void
{
if (! Schema::hasTable('artwork_stats')) {
return;
}
$favorites = Schema::hasTable('artwork_favourites')
? (int) DB::table('artwork_favourites')->where('artwork_id', $artworkId)->count()
: 0;
$likes = Schema::hasTable('artwork_likes')
? (int) DB::table('artwork_likes')->where('artwork_id', $artworkId)->count()
: 0;
DB::table('artwork_stats')->updateOrInsert(
['artwork_id' => $artworkId],
[
'favorites' => $favorites,
'rating_count' => $likes,
]
);
}
private function statusPayload(int $viewerId, int $artworkId): array
{
$isFavorited = Schema::hasTable('artwork_favourites')
? DB::table('artwork_favourites')->where('user_id', $viewerId)->where('artwork_id', $artworkId)->exists()
: false;
$isLiked = Schema::hasTable('artwork_likes')
? DB::table('artwork_likes')->where('user_id', $viewerId)->where('artwork_id', $artworkId)->exists()
: false;
$favorites = Schema::hasTable('artwork_favourites')
? (int) DB::table('artwork_favourites')->where('artwork_id', $artworkId)->count()
: 0;
$likes = Schema::hasTable('artwork_likes')
? (int) DB::table('artwork_likes')->where('artwork_id', $artworkId)->count()
: 0;
return [
'ok' => true,
'is_favorited' => $isFavorited,
'is_liked' => $isLiked,
'stats' => [
'favorites' => $favorites,
'likes' => $likes,
],
];
}
}