Files
Gnuboard7/modules/_bundled/sirsoft-board/src/Http/Controllers/User/ReactionController.php
T
chym1217 4060a91d28 feat(board): 게시글 반응(추천/비추천) 백엔드
DB 스키마 + 백엔드 로직 + API 레이어를 구현.

- board_reaction_types/board_reactions 테이블 신규, boards.use_reaction·
 active_reaction_types, board_posts.reaction_counts 컬럼 추가
- ReactionType(다국어 JSON+폴백)/Reaction 모델, Repository 2종(인터페이스 주입)
- ReactionService: 등록/전환/해제 통합, DB::transaction 원자 카운트,
 use_reaction·비활성유형·본인글·slug 스코프 차단, before/after_react 훅
- POST boards/{slug}/posts/{postId}/react(회원) + GET admin/reaction-types
- ReactPostRequest + AvailableReactionType Rule, PostResource/BoardResource 확장
- 반응 활동 로그(add/change/remove) + ko/en 다국어 + API 레퍼런스 문서
- 테스트 30건(Unit/Feature, 87 assertions) green, 마이그레이션 왕복 안전성 검증

프론트엔드 화면·ja 언어팩·CHANGELOG·버전 bump 는 후속 단계(4~5)에서 진행.
2026-08-07 17:00:41 +09:00

78 lines
2.8 KiB
PHP

<?php
namespace Modules\Sirsoft\Board\Http\Controllers\User;
use App\Http\Controllers\Api\Base\AuthBaseController;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Auth;
use Modules\Sirsoft\Board\Exceptions\PostNotFoundException;
use Modules\Sirsoft\Board\Exceptions\ReactionNotAllowedException;
use Modules\Sirsoft\Board\Http\Requests\User\ReactPostRequest;
use Modules\Sirsoft\Board\Services\BoardService;
use Modules\Sirsoft\Board\Services\ReactionService;
/**
* 사용자용 반응 컨트롤러
*
* 게시글 반응(추천/비추천) 등록·전환·해제를 하나의 엔드포인트로 처리합니다.
*/
class ReactionController extends AuthBaseController
{
/**
* ReactionController 생성자
*
* @param ReactionService $reactionService 반응 서비스
* @param BoardService $boardService 게시판 서비스 (slug → 게시판 해석)
*/
public function __construct(
private ReactionService $reactionService,
private BoardService $boardService,
) {
parent::__construct();
}
/**
* 게시글에 반응합니다 (등록/전환/해제 통합).
*
* @param ReactPostRequest $request 반응 요청 (reaction_type_id 검증)
* @param string $slug 게시판 slug
* @param int $postId 게시글 ID
* @return JsonResponse 반응 결과 응답
*/
public function react(ReactPostRequest $request, string $slug, int $postId): JsonResponse
{
try {
$board = $this->boardService->getBoardBySlug($slug, checkScope: false);
if (! $board) {
return $this->notFound('sirsoft-board::messages.boards.error_404');
}
$result = $this->reactionService->react(
(int) Auth::id(),
$board,
$postId,
(int) $request->validated('reaction_type_id'),
);
$messageKey = match ($result['action']) {
'change' => 'sirsoft-board::messages.reaction.change_success',
'remove' => 'sirsoft-board::messages.reaction.remove_success',
default => 'sirsoft-board::messages.reaction.add_success',
};
return $this->success($messageKey, [
'action' => $result['action'],
'my_reaction_type_id' => $result['reaction_type_id'],
'reaction_counts' => $result['reaction_counts'],
]);
} catch (PostNotFoundException $e) {
return $this->notFound('sirsoft-board::messages.post.not_found');
} catch (ReactionNotAllowedException $e) {
return $this->error($e->getMessage(), 422, ['code' => 'reaction_not_allowed']);
} catch (\Exception $e) {
return $this->error('sirsoft-board::messages.reaction.failed', 500, $e->getMessage());
}
}
}