Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 62
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
VoteController
0.00% covered (danger)
0.00%
0 / 62
0.00% covered (danger)
0.00%
0 / 4
240
0.00% covered (danger)
0.00%
0 / 1
 vote
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
30
 removeVote
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
30
 clearUserVotes
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
12
 clearAllVotes
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace App\Http\Controllers\Api\v1;
4
5use App\Http\Controllers\Controller;
6use App\Models\Joke;
7use App\Models\Vote;
8use App\Models\User;
9use App\Responses\ApiResponse;
10use Illuminate\Http\Request;
11use Illuminate\Http\JsonResponse;
12use Illuminate\Support\Facades\Gate;
13use Illuminate\Validation\Rule;
14
15/**
16 * VoteController
17 * 
18 * Handles all vote-related API operations including voting on jokes,
19 * removing votes, and administrative vote management. Implements
20 * ownership-based permissions where users can manage their own votes,
21 * while staff+ can manage all votes.
22 * 
23 * @package App\Http\Controllers\Api\v1
24 * @author Ting Liu
25 * @version 1.0.0
26 * @since 2025-10-04
27 * 
28 * @method JsonResponse vote(Request $request, string $jokeId) Vote on a joke (like/dislike)
29 * @method JsonResponse removeVote(string $jokeId) Remove vote from a joke
30 * @method JsonResponse clearUserVotes(string $userId) Clear all votes for a specific user (admin)
31 * @method JsonResponse clearAllVotes() Clear all votes in the system (admin)
32 * 
33 * @see \App\Responses\ApiResponse For standardized JSON responses
34 */
35class VoteController extends Controller
36{
37    /**
38     * Vote on a joke (like or dislike).
39     * 
40     * Allows authenticated users to vote on jokes with a rating of +1 (like)
41     * or -1 (dislike). Users can only vote once per joke, and updating a vote
42     * will replace the previous vote. Implements proper validation and
43     * ownership tracking.
44     * 
45     * @param Request $request The HTTP request containing:
46     *                        - rating (required): Vote rating (+1 for like, -1 for dislike)
47     * @param string $jokeId The joke ID to vote on
48     * 
49     * @return JsonResponse Standardized JSON response containing:
50     *                     - success: boolean indicating operation success
51     *                     - message: Human-readable status message
52     *                     - data: Object containing the vote information
53     * 
54     * @throws \Illuminate\Http\Exceptions\HttpResponseException When joke not found or user lacks vote permissions
55     * @throws \Illuminate\Validation\ValidationException When validation fails
56     * 
57     * @api POST /api/v1/jokes/{joke}/vote
58     * @permission votes.create (User level 100+)
59     */
60    public function vote(Request $request, string $jokeId): JsonResponse
61    {
62        // Check if user can vote (User level 100+)
63        if (!Gate::allows('create', Vote::class)) {
64            return ApiResponse::error(null, 'Unauthorized to vote', 403);
65        }
66
67        $joke = Joke::find($jokeId);
68
69        if (!$joke) {
70            return ApiResponse::error(null, "Joke not found", 404);
71        }
72
73        // Check if user can vote on this specific joke
74        if (!Gate::forUser(request()->user())->allows('voteOnJoke', $joke)) {
75            return ApiResponse::error(null, "Joke not found", 404);
76        }
77
78        $request->validate([
79            'rating' => ['required', 'integer', Rule::in([1, -1])],
80        ]);
81
82        $userId = request()->user()->id;
83
84        // Check if user has already voted on this joke
85        $existingVote = Vote::where('user_id', $userId)
86            ->where('joke_id', $jokeId)
87            ->first();
88
89        if ($existingVote) {
90            // Update existing vote
91            $existingVote->update(['rating' => $request->rating]);
92            $vote = $existingVote;
93            $message = 'Vote updated successfully';
94            $statusCode = 200;
95        } else {
96            // Create new vote
97            $vote = Vote::create([
98                'user_id' => $userId,
99                'joke_id' => $jokeId,
100                'rating' => $request->rating,
101            ]);
102            $message = 'Vote created successfully';
103            $statusCode = 201;
104        }
105
106        $vote->load(['user', 'joke']);
107
108        return ApiResponse::success(['vote' => $vote], $message, $statusCode);
109    }
110
111    /**
112     * Remove vote from a joke.
113     * 
114     * Allows authenticated users to remove their vote from a specific joke.
115     * Users can only remove their own votes, while staff+ can remove any vote.
116     * Implements proper authorization and ownership validation.
117     * 
118     * @param string $jokeId The joke ID to remove vote from
119     * 
120     * @return JsonResponse Standardized JSON response containing:
121     *                     - success: boolean indicating operation success
122     *                     - message: Human-readable status message
123     *                     - data: null (vote is removed)
124     * 
125     * @throws \Illuminate\Http\Exceptions\HttpResponseException When joke not found or user lacks delete permissions
126     * 
127     * @api DELETE /api/v1/jokes/{joke}/vote
128     * @permission votes.delete (User level 100+ for own votes, Staff+ for any vote)
129     */
130    public function removeVote(string $jokeId): JsonResponse
131    {
132        // Check if user can delete votes (User level 100+)
133        if (!Gate::allows('viewAny', Vote::class)) {
134            return ApiResponse::error(null, 'Unauthorized to remove votes', 403);
135        }
136
137        $joke = Joke::find($jokeId);
138
139        if (!$joke) {
140            return ApiResponse::error(null, "Joke not found", 404);
141        }
142
143        $currentUser = request()->user();
144
145        // Find the vote for this joke
146        $vote = Vote::where('joke_id', $jokeId)->first();
147
148        if (!$vote) {
149            return ApiResponse::error(null, "No vote found for this joke", 404);
150        }
151
152        // Check if user can delete this specific vote
153        if (!Gate::allows('delete', $vote)) {
154            return ApiResponse::error(null, "No vote found for this joke", 404);
155        }
156
157        $vote->delete();
158
159        return ApiResponse::success(null, 'Vote removed successfully');
160    }
161
162    /**
163     * Clear all votes for a specific user (admin only).
164     * 
165     * Allows staff+ level users to clear all votes for a specific user.
166     * This is useful for administrative purposes or when cleaning up
167     * user data. Implements proper permission validation.
168     * 
169     * @param string $userId The user ID to clear votes for
170     * 
171     * @return JsonResponse Standardized JSON response containing:
172     *                     - success: boolean indicating operation success
173     *                     - message: Human-readable status message
174     *                     - data: Object containing the count of cleared votes
175     * 
176     * @throws \Illuminate\Http\Exceptions\HttpResponseException When user not found or lacks clear permissions
177     * 
178     * @api DELETE /api/v1/users/{user}/votes
179     * @permission votes.clear-user (Staff level 500+)
180     */
181    public function clearUserVotes(string $userId): JsonResponse
182    {
183        $currentUser = request()->user();
184        $user = User::find($userId);
185
186        if (!$user) {
187            return ApiResponse::error(null, "User not found", 404);
188        }
189
190        // Check if user can clear votes for this user (Staff level 500+)
191        if (!Gate::allows('clearUserVotes', $user)) {
192            return ApiResponse::error(null, 'Unauthorized to clear user votes', 403);
193        }
194
195        $voteCount = Vote::where('user_id', $userId)->count();
196        Vote::where('user_id', $userId)->delete();
197
198        return ApiResponse::success(
199            ['cleared_votes_count' => $voteCount], 
200            "Cleared {$voteCount} votes for user"
201        );
202    }
203
204    /**
205     * Clear all votes in the system (admin only).
206     * 
207     * Allows admin+ level users to clear all votes in the system.
208     * This is a powerful administrative function that should be used
209     * with caution as it affects all users' voting data.
210     * 
211     * @return JsonResponse Standardized JSON response containing:
212     *                     - success: boolean indicating operation success
213     *                     - message: Human-readable status message
214     *                     - data: Object containing the count of cleared votes
215     * 
216     * @throws \Illuminate\Http\Exceptions\HttpResponseException When user lacks clear all permissions
217     * 
218     * @api DELETE /api/v1/votes
219     * @permission votes.clear-all (Admin level 750+)
220     * @warning This action affects all users' voting data
221     */
222    public function clearAllVotes(): JsonResponse
223    {
224        $currentUser = request()->user();
225
226        // Check if user can clear all votes (Admin level 750+)
227        if (!Gate::allows('clearAllVotes', Vote::class)) {
228            return ApiResponse::error(null, 'Unauthorized to clear all votes', 403);
229        }
230
231        $voteCount = Vote::count();
232        Vote::truncate();
233
234        return ApiResponse::success(
235            ['cleared_votes_count' => $voteCount], 
236            "Cleared {$voteCount} votes from the system"
237        );
238    }
239
240}