From 8ac5a5b18a13de83ec17824403adddad8d01220a Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Wed, 6 May 2026 22:32:46 +0300 Subject: [PATCH] implement featured post admin endpoints Adds POST /admin/posts/feature, POST /admin/posts/unfeature (both auth-required, admin-checked inside controller via the use case's ForbiddenException), and public GET /posts/featured. Post serialization now includes featureSlot. --- backend/app/Controllers/PostController.php | 81 +++++++++++++++++++++- backend/routes/api.php | 5 ++ 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/backend/app/Controllers/PostController.php b/backend/app/Controllers/PostController.php index a01429c..a70133d 100644 --- a/backend/app/Controllers/PostController.php +++ b/backend/app/Controllers/PostController.php @@ -5,15 +5,20 @@ namespace App\Controllers; use App\Exceptions\BadRequestException; use App\Exceptions\ForbiddenException; use App\Post\Post; +use App\Post\UseCases\ClearFeaturedPost\ClearFeaturedPost; +use App\Post\UseCases\ClearFeaturedPost\ClearFeaturedPostRequest; use App\Post\UseCases\CreatePost\CreatePost; use App\Post\UseCases\CreatePost\CreatePostRequest; use App\Post\UseCases\DeletePost\DeletePost; use App\Post\UseCases\DeletePost\DeletePostRequest; use App\Post\UseCases\GetPost\GetPost; +use App\Post\UseCases\ListFeaturedPosts\ListFeaturedPosts; use App\Post\UseCases\ListRecentPosts\ListRecentPosts; use App\Post\UseCases\ListRecentPosts\ListRecentPostsRequest; use App\Post\UseCases\ListUserPosts\ListUserPosts; use App\Post\UseCases\ListUserPosts\ListUserPostsRequest; +use App\Post\UseCases\SetFeaturedPost\SetFeaturedPost; +use App\Post\UseCases\SetFeaturedPost\SetFeaturedPostRequest; use App\User\User; use App\User\UserRepository; use DomainException; @@ -30,6 +35,9 @@ class PostController private GetPost $getPost, private ListRecentPosts $listRecentPosts, private ListUserPosts $listUserPosts, + private SetFeaturedPost $setFeaturedPost, + private ClearFeaturedPost $clearFeaturedPost, + private ListFeaturedPosts $listFeaturedPosts, private UserRepository $userRepo, ) {} @@ -126,6 +134,75 @@ class PostController ], 201); } + public function listFeatured(Request $request): JsonResponse + { + $posts = $this->listFeaturedPosts->execute(); + + return new JsonResponse([ + 'posts' => array_map( + function (Post $post) { + return $this->serialize($post); + }, + $posts, + ), + ], 200); + } + + public function feature(Request $request): JsonResponse + { + /** @var User $user */ + $user = $request->attributes->get('user'); + try { + $post = $this->setFeaturedPost->execute( + new SetFeaturedPostRequest( + postId: (int) $request->input('postId'), + slot: (int) $request->input('slot'), + requesterIsAdmin: $user->isAdmin(), + ), + ); + } catch (BadRequestException $exception) { + return new JsonResponse( + ['error' => $exception->getMessage()], 400, + ); + } catch (ForbiddenException $exception) { + return new JsonResponse( + ['error' => $exception->getMessage()], 403, + ); + } catch (DomainException $exception) { + return new JsonResponse( + ['error' => $exception->getMessage()], 404, + ); + } + + return new JsonResponse([ + 'post' => $this->serialize($post), + ], 200); + } + + public function unfeature(Request $request): JsonResponse + { + /** @var User $user */ + $user = $request->attributes->get('user'); + try { + $this->clearFeaturedPost->execute( + new ClearFeaturedPostRequest( + postId: (int) $request->input('postId'), + requesterIsAdmin: $user->isAdmin(), + ), + ); + } catch (BadRequestException $exception) { + return new JsonResponse( + ['error' => $exception->getMessage()], 400, + ); + } catch (ForbiddenException $exception) { + return new JsonResponse( + ['error' => $exception->getMessage()], 403, + ); + } + + return new JsonResponse(null, 204); + } + public function delete(Request $request, int $id): JsonResponse { /** @var User $user */ @@ -160,7 +237,8 @@ class PostController * authorDisplayName: string, * title: string, * body: string, - * createdAt: string + * createdAt: string, + * featureSlot: ?int * } */ private function serialize(Post $post): array @@ -176,6 +254,7 @@ class PostController 'title' => $post->getTitle(), 'body' => $post->getBody(), 'createdAt' => $post->getCreatedAt()->format(DATE_ATOM), + 'featureSlot' => $post->getFeatureSlot(), ]; } } diff --git a/backend/routes/api.php b/backend/routes/api.php index f614658..03408cc 100644 --- a/backend/routes/api.php +++ b/backend/routes/api.php @@ -19,6 +19,7 @@ Route::get('/me', [AuthController::class, 'me']) ->middleware(AuthMiddleware::class); Route::get('/posts', [PostController::class, 'recent']); +Route::get('/posts/featured', [PostController::class, 'listFeatured']); Route::get('/posts/{id}', [PostController::class, 'show']) ->whereNumber('id'); Route::post('/posts', [PostController::class, 'create']) @@ -26,6 +27,10 @@ Route::post('/posts', [PostController::class, 'create']) Route::delete('/posts/{id}', [PostController::class, 'delete']) ->whereNumber('id') ->middleware(AuthMiddleware::class); +Route::post('/admin/posts/feature', [PostController::class, 'feature']) + ->middleware(AuthMiddleware::class); +Route::post('/admin/posts/unfeature', [PostController::class, 'unfeature']) + ->middleware(AuthMiddleware::class); Route::get( '/users/{displayName}/posts',