validate([ 'entity_type' => ['required', 'string', 'in:artwork,story'], 'entity_id' => ['required', 'integer'], 'state' => ['nullable', 'boolean'], ]); $state = array_key_exists('state', $payload) ? (bool) $payload['state'] : ! $request->isMethod('delete'); if ($payload['entity_type'] === 'story') { $story = Story::published()->findOrFail((int) $payload['entity_id']); $result = $this->social->toggleStoryLike($request->user(), $story, $state); return response()->json([ 'ok' => (bool) ($result['ok'] ?? true), 'liked' => (bool) ($result['liked'] ?? false), 'likes_count' => (int) ($result['likes_count'] ?? 0), 'is_liked' => (bool) ($result['liked'] ?? false), 'stats' => [ 'likes' => (int) ($result['likes_count'] ?? 0), ], ]); } $artworkId = (int) $payload['entity_id']; abort_unless(Artwork::public()->published()->whereKey($artworkId)->exists(), 404); return app(ArtworkInteractionController::class)->like( $request->merge(['state' => $state]), $artworkId, ); } public function comments(Request $request): JsonResponse { $payload = $request->validate([ 'entity_type' => ['required', 'string', 'in:artwork,story'], 'entity_id' => ['required', 'integer'], 'content' => [$request->isMethod('get') ? 'nullable' : 'required', 'string', 'min:1', 'max:10000'], 'parent_id' => ['nullable', 'integer'], ]); if ($payload['entity_type'] === 'story') { if ($request->isMethod('get')) { $story = Story::published()->findOrFail((int) $payload['entity_id']); return response()->json( $this->social->listStoryComments($story, $request->user()?->id, (int) $request->query('page', 1), 20) ); } $story = Story::published()->findOrFail((int) $payload['entity_id']); $comment = $this->social->addStoryComment( $request->user(), $story, (string) $payload['content'], isset($payload['parent_id']) ? (int) $payload['parent_id'] : null, ); return response()->json([ 'data' => $this->social->formatComment($comment, (int) $request->user()->id, true), ], 201); } $artworkId = (int) $payload['entity_id']; abort_unless(Artwork::public()->published()->whereKey($artworkId)->exists(), 404); if ($request->isMethod('get')) { return app(ArtworkCommentController::class)->index($request, $artworkId); } return app(ArtworkCommentController::class)->store( $request->merge([ 'content' => $payload['content'], 'parent_id' => $payload['parent_id'] ?? null, ]), $artworkId, ); } public function bookmark(Request $request): JsonResponse { $payload = $request->validate([ 'entity_type' => ['required', 'string', 'in:artwork,story'], 'entity_id' => ['required', 'integer'], 'state' => ['nullable', 'boolean'], ]); $state = array_key_exists('state', $payload) ? (bool) $payload['state'] : ! $request->isMethod('delete'); if ($payload['entity_type'] === 'story') { $story = Story::published()->findOrFail((int) $payload['entity_id']); $result = $this->social->toggleStoryBookmark($request->user(), $story, $state); return response()->json([ 'ok' => (bool) ($result['ok'] ?? true), 'bookmarked' => (bool) ($result['bookmarked'] ?? false), 'bookmarks_count' => (int) ($result['bookmarks_count'] ?? 0), 'is_bookmarked' => (bool) ($result['bookmarked'] ?? false), 'stats' => [ 'bookmarks' => (int) ($result['bookmarks_count'] ?? 0), ], ]); } $artworkId = (int) $payload['entity_id']; abort_unless(Artwork::public()->published()->whereKey($artworkId)->exists(), 404); return app(ArtworkInteractionController::class)->bookmark( $request->merge(['state' => $state]), $artworkId, ); } public function bookmarks(Request $request): JsonResponse { $payload = $request->validate([ 'entity_type' => ['nullable', 'string', 'in:artwork,story'], 'per_page' => ['nullable', 'integer', 'min:1', 'max:50'], ]); $perPage = (int) ($payload['per_page'] ?? 20); $userId = (int) $request->user()->id; $type = $payload['entity_type'] ?? null; $items = collect(); if ($type === null || $type === 'artwork') { $items = $items->concat( Schema::hasTable('artwork_bookmarks') ? DB::table('artwork_bookmarks') ->join('artworks', 'artworks.id', '=', 'artwork_bookmarks.artwork_id') ->where('artwork_bookmarks.user_id', $userId) ->where('artworks.is_public', true) ->where('artworks.is_approved', true) ->select([ 'artwork_bookmarks.created_at as saved_at', 'artworks.id', 'artworks.title', 'artworks.slug', ]) ->latest('artwork_bookmarks.created_at') ->limit($perPage) ->get() ->map(fn ($row) => [ 'type' => 'artwork', 'id' => (int) $row->id, 'title' => (string) $row->title, 'url' => route('art.show', ['id' => (int) $row->id, 'slug' => Str::slug((string) ($row->slug ?: $row->title)) ?: (string) $row->id]), 'saved_at' => Carbon::parse($row->saved_at)->toIso8601String(), ]) : collect() ); } if ($type === null || $type === 'story') { $items = $items->concat( StoryBookmark::query() ->with('story:id,slug,title') ->where('user_id', $userId) ->latest('created_at') ->limit($perPage) ->get() ->filter(fn (StoryBookmark $bookmark) => $bookmark->story !== null) ->map(fn (StoryBookmark $bookmark) => [ 'type' => 'story', 'id' => (int) $bookmark->story->id, 'title' => (string) $bookmark->story->title, 'url' => route('stories.show', ['slug' => $bookmark->story->slug]), 'saved_at' => $bookmark->created_at?->toIso8601String(), ]) ); } return response()->json([ 'data' => $items ->sortByDesc('saved_at') ->take($perPage) ->values() ->all(), ]); } }