ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,57 @@
+import { ReviewSectionName, Button, ReviewFormArea, Conditions, IfReward, Checkbox, Number, DropDown, ReviewContent } from "./ReviewForm.style";
+import { useState } from "react";
+
+const ReviewForm = () => {
+    const [received, setReceived] = useState(false);
+    const [year, setYear] = useState("");
+    const [semester, setSemester] = useState("");
+    const [review, setReview] = useState("");
+
+    const Submission = () => {
+        console.log({ received, year, semester, review });
+    };
+
+    return (
+        <div>
+            <ReviewSectionName>리뷰쓰기</ReviewSectionName>
+            <ReviewFormArea>
+                <Conditions>
+                    <IfReward>
+                        <Checkbox
+                            type="checkbox"
+                            checked={received}
+                            onChange={() => setReceived(!received)}
+                        />
+                        수혜여부
+                    </IfReward>
+
+                    <Number
+                        type="number"
+                        value={year}
+                        onChange={(e) => setYear(e.target.value)}
+                        placeholder="신청 연도"
+                    />
+
+                    <DropDown value={semester} onChange={(e) => setSemester(e.target.value)}>
+                        <option value="">신청 학기</option>
+                        <option value="1학기">1학기</option>
+                        <option value="2학기">2학기</option>
+                    </DropDown>
+                </Conditions>
+
+                <ReviewContent>
+                    <textarea
+                        value={review}
+                        onChange={(e) => setReview(e.target.value)}
+                        placeHolder="장학금 신청 과정이 어떠했나요? &#13;&#10;입금 금액 및 시기, 선발 기준, 팁 등 장학금에 관한 정보와 경험을 자유롭게 남겨주세요."
+                    />
+                </ReviewContent>
+
+                <Button onClick={Submission}>리뷰 작성하기</Button>
+
+            </ReviewFormArea>
+        </div>
+    );
+};
+
+export default ReviewForm;
\ No newline at end of file

리뷰: 안그래도 네이밍컨벤션을 잘 몰라서 이번주에 공부해오기로 했습니다...ㅎㅎ 공부하고 코드 수정해보겠습니다! 두 분 다 리뷰해주셔서 감사합니다:)
@@ -0,0 +1,25 @@
+package nextstep.security.filter;
+
+import jakarta.servlet.http.HttpServletRequest;
+import nextstep.security.authentication.Authentication;
+import nextstep.security.authentication.AuthenticationManager;
+import nextstep.security.context.SecurityContextRepository;
+import nextstep.security.user.UsernamePasswordAuthenticationToken;
+import org.springframework.http.HttpMethod;
+
+import java.util.Map;
+
+public class UserNamePasswordAuthFilter extends AbstractAuthProcessingFilter {
+
+    public UserNamePasswordAuthFilter(AuthenticationManager authenticationManager, SecurityContextRepository securityContextRepository) {
+        super(authenticationManager, securityContextRepository, new String[]{"/login"}, new HttpMethod[]{HttpMethod.POST});
+    }
+
+    public Authentication getAuthentication(HttpServletRequest httpRequest) {
+        Map<String, String[]> parameterMap = httpRequest.getParameterMap();
+        String username = parameterMap.get("username")[0];
+        String password = parameterMap.get("password")[0];
+
+        return UsernamePasswordAuthenticationToken.unAuthorizedToken(username, password);
+    }
+}

리뷰: 미션을 빠르게 잘 수행해주셔서 추가적으로 고민해볼 수 있는 부분을 드립니다!

실제 시큐리티 코드에서 UsernamePasswordAuthenticationFilter의 경우 AbstractAuthenticationProcessingFilter를 상속받고 있습니다. **시큐리티의 인증 관련 필터를 잘 파악해보기 위한 목적**으로 UserNamePasswordAuthFilter와 AbstractAuthenticationProcessingFilter를 구분해보고 리팩터링 해보면 어떨까요?

함께 고민해보면 좋을 내용으로 실제 시큐리티 코드에서는 BasicAuthenticationFilter는 AbstractAuthenticationProcessingFilter를 상속받고 있지 않고 있습니다. 이유에 대해서 고민해보시고 세준님이 구현한다면 이 부분을 합쳐서 구현해볼 것 인지, 아니면 기존처럼 따로 구현해볼 것 인지를 고민해보시고 그 이유도 함께 작성해봐주시면 조금 더 폭넓은 학습을 하는데 도움이 될 것 같습니다 :)
@@ -0,0 +1,25 @@
+package nextstep.security.filter;
+
+import jakarta.servlet.http.HttpServletRequest;
+import nextstep.security.authentication.Authentication;
+import nextstep.security.authentication.AuthenticationManager;
+import nextstep.security.context.SecurityContextRepository;
+import nextstep.security.user.UsernamePasswordAuthenticationToken;
+import org.springframework.http.HttpMethod;
+
+import java.util.Map;
+
+public class UserNamePasswordAuthFilter extends AbstractAuthProcessingFilter {
+
+    public UserNamePasswordAuthFilter(AuthenticationManager authenticationManager, SecurityContextRepository securityContextRepository) {
+        super(authenticationManager, securityContextRepository, new String[]{"/login"}, new HttpMethod[]{HttpMethod.POST});
+    }
+
+    public Authentication getAuthentication(HttpServletRequest httpRequest) {
+        Map<String, String[]> parameterMap = httpRequest.getParameterMap();
+        String username = parameterMap.get("username")[0];
+        String password = parameterMap.get("password")[0];
+
+        return UsernamePasswordAuthenticationToken.unAuthorizedToken(username, password);
+    }
+}

리뷰: 필터마다 제가 시큐리티 코드를 비교해보면서 이해한 것을 정리하였습니다.

시큐리티의 BasicAuthenticationFilter는 모든 요청당 한번 작동합니다.
모든 요청마다 Request에 authRequest 가 있다면 인증을 진행하고 세션에 결과를 저장시켜주는 역할을 하는 것 같습니다. 
만약 요청에 authRequest 가 없거나 작업이 잘 끝난다면 다음 필터로 chain.doFilter(request, response); 체이닝을 해줍니다.

__직접 구현한 필터와 다른 점:__
제가 직접 구현한 필터(BasicAuthenticationFilter)도 OncePerRequestFilter를 사용하지만 내부 작동이 다릅니다.
많은 에러 처리, 로그 처리등도 다르지만, 가장 큰 차이 점은 authRequest 를 가져오고 사용하는 방식이라고 생각이 듭니다.

제가 만든 필터는 __쓰레드 로컬__ 에서 authentication 를 가져와서 존재하면 다음 필터로 체이닝을해줍니다.
만약 쓰레드 로컬에 데이터가 없는 경우에 HttpServletRequest 에서 authRequest 를 가져온 뒤 인증을 시도합니다.
이때 요청에 authRequest 가 존재하지 않으면 에러를 보냅니다.  에러처리 또한 하고 있습니다.

결국 시큐리티의 BasicAuthenticationFilter는 요청에서 authRequest가 있는 경우에만 세션에 등록시켜준다면,
제가 만든 BasicAuthenticationFilter는 쓰레드로컬도 보고 HttpServletRequest 도 보고 없는 경우에 에러도 던져주는 많은 역할(쓰레드 로컬 확인, 요청 확인, 인증 처리, 에러 처리)을 제공합니다.

정리하면 시큐리티에서 BasicAuthenticationFilter에 AbstractAuthenticationProcessingFilter를 사용하지 않은 이유는, 모든 요청에서 단순히 Authorization 헤더만 확인하고 세션에 저장하는 역할만 제공하기 위해서라고 생각이 듭니다.
AbstractAuthenticationProcessingFilter에 있는 다양한 기능과 복잡한 인증 처리들은 필요가 없었던 것 아닐까요?
@@ -0,0 +1,25 @@
+package nextstep.security.filter;
+
+import jakarta.servlet.http.HttpServletRequest;
+import nextstep.security.authentication.Authentication;
+import nextstep.security.authentication.AuthenticationManager;
+import nextstep.security.context.SecurityContextRepository;
+import nextstep.security.user.UsernamePasswordAuthenticationToken;
+import org.springframework.http.HttpMethod;
+
+import java.util.Map;
+
+public class UserNamePasswordAuthFilter extends AbstractAuthProcessingFilter {
+
+    public UserNamePasswordAuthFilter(AuthenticationManager authenticationManager, SecurityContextRepository securityContextRepository) {
+        super(authenticationManager, securityContextRepository, new String[]{"/login"}, new HttpMethod[]{HttpMethod.POST});
+    }
+
+    public Authentication getAuthentication(HttpServletRequest httpRequest) {
+        Map<String, String[]> parameterMap = httpRequest.getParameterMap();
+        String username = parameterMap.get("username")[0];
+        String password = parameterMap.get("password")[0];
+
+        return UsernamePasswordAuthenticationToken.unAuthorizedToken(username, password);
+    }
+}

리뷰: 마지막으로 AbstractAuthenticationProcessingFilter의 코드도 조금 살펴보았는데요.

이 객체의 의의는 많은 기능의 추상화가 아닐까 합니다.
제가 만든 UserNamePasswordAuthFilter를 보면 필터링 해야할지 판단해야하는 역할부터 인증 부분 그리고 세션에 저장하거나 에러를 처리하는 등 다양한 기능을 제공하고 어떤 것은 반복되는 코드들 같아요.

시큐리티에서 AbstractAuthenticationProcessingFilter와 UserNamePasswordAuthFilter로 나눈 것은

UserNamePasswordAuthFilter이나 다른 인증 구현체에서는 Authentication를 제공하는 로직만 담당하게 하고
AbstractAuthenticationProcessingFilter에서는 반복되는 보일러플레이트들만 처리하게 시킨것 같습니다!
@@ -27,6 +27,7 @@ public class RecommendController {
 	private final RecommendService recommendService;
 	private final ReviewService reviewService;
 
+	// 선택한 리뷰 추천하기
 	@PostMapping("/{reviewId}")
 	public ResponseEntity<Long> createRecommend(
 		@AuthenticationPrincipal WebtyUserDetails webtyUserDetails,
@@ -36,6 +37,7 @@ public ResponseEntity<Long> createRecommend(
 		return ResponseEntity.ok(recommendService.createRecommend(webtyUserDetails,reviewId,type));
 	}
 
+	// 선택한 리뷰 추천 취소하기
 	@DeleteMapping("/{reviewId}")
 	public ResponseEntity<Void> deleteRecommend(
 		@AuthenticationPrincipal WebtyUserDetails webtyUserDetails,
@@ -46,7 +48,7 @@ public ResponseEntity<Void> deleteRecommend(
 		return ResponseEntity.ok().build();
 	}
 
-	// 단독 호출 할 일 없을듯
+	// 선택한 리뷰의 추천수 조회 (사용하지 않을 것으로 보임)
 	@GetMapping("/{reviewId}")
 	public ResponseEntity<Map<String,Long>> getRecommendCounts(
 		@PathVariable(value = "reviewId") Long reviewId
@@ -55,6 +57,7 @@ public ResponseEntity<Map<String,Long>> getRecommendCounts(
 		return ResponseEntity.ok(recommendCounts);
 	}
 
+	// 로그인한 사용자 기준) 추천한 리뷰 목록 조회
 	@GetMapping("/user/{userId}")
 	public ResponseEntity<PageDto<ReviewItemResponse>> getUserRecommendReviews(
 		@PathVariable(value = "userId") Long userId,
@@ -63,4 +66,13 @@ public ResponseEntity<PageDto<ReviewItemResponse>> getUserRecommendReviews(
 	){
 		return ResponseEntity.ok(PageMapper.toPageDto(reviewService.getUserRecommendedReviews(userId, page, size)));
 	}
+	
+	// 로그인한 사용자 기준) 선택한 리뷰의 추천 여부 반환
+	@GetMapping("/{reviewId}/recommendation")
+	public ResponseEntity<Map<String, Boolean>> getRecommended(
+		@AuthenticationPrincipal WebtyUserDetails webtyUserDetails,
+		@PathVariable(value="reviewId") Long reviewId
+	){
+		return ResponseEntity.ok(recommendService.isRecommended(webtyUserDetails,reviewId));
+	}
 }

리뷰: // 특정 유저의 추천 리뷰 조회
->
// 로그인한 사용자 기준) 추천한 리뷰 목록 조회
@@ -27,6 +27,7 @@ public class RecommendController {
 	private final RecommendService recommendService;
 	private final ReviewService reviewService;
 
+	// 선택한 리뷰 추천하기
 	@PostMapping("/{reviewId}")
 	public ResponseEntity<Long> createRecommend(
 		@AuthenticationPrincipal WebtyUserDetails webtyUserDetails,
@@ -36,6 +37,7 @@ public ResponseEntity<Long> createRecommend(
 		return ResponseEntity.ok(recommendService.createRecommend(webtyUserDetails,reviewId,type));
 	}
 
+	// 선택한 리뷰 추천 취소하기
 	@DeleteMapping("/{reviewId}")
 	public ResponseEntity<Void> deleteRecommend(
 		@AuthenticationPrincipal WebtyUserDetails webtyUserDetails,
@@ -46,7 +48,7 @@ public ResponseEntity<Void> deleteRecommend(
 		return ResponseEntity.ok().build();
 	}
 
-	// 단독 호출 할 일 없을듯
+	// 선택한 리뷰의 추천수 조회 (사용하지 않을 것으로 보임)
 	@GetMapping("/{reviewId}")
 	public ResponseEntity<Map<String,Long>> getRecommendCounts(
 		@PathVariable(value = "reviewId") Long reviewId
@@ -55,6 +57,7 @@ public ResponseEntity<Map<String,Long>> getRecommendCounts(
 		return ResponseEntity.ok(recommendCounts);
 	}
 
+	// 로그인한 사용자 기준) 추천한 리뷰 목록 조회
 	@GetMapping("/user/{userId}")
 	public ResponseEntity<PageDto<ReviewItemResponse>> getUserRecommendReviews(
 		@PathVariable(value = "userId") Long userId,
@@ -63,4 +66,13 @@ public ResponseEntity<PageDto<ReviewItemResponse>> getUserRecommendReviews(
 	){
 		return ResponseEntity.ok(PageMapper.toPageDto(reviewService.getUserRecommendedReviews(userId, page, size)));
 	}
+	
+	// 로그인한 사용자 기준) 선택한 리뷰의 추천 여부 반환
+	@GetMapping("/{reviewId}/recommendation")
+	public ResponseEntity<Map<String, Boolean>> getRecommended(
+		@AuthenticationPrincipal WebtyUserDetails webtyUserDetails,
+		@PathVariable(value="reviewId") Long reviewId
+	){
+		return ResponseEntity.ok(recommendService.isRecommended(webtyUserDetails,reviewId));
+	}
 }

리뷰: // 특정 리뷰의 로그인된 유저 추천 현황 조회
->
// 로그인한 사용자 기준) 해당 리뷰글의 추천 여부 반환
@@ -8,13 +8,9 @@ namespace util
 	template <typename U>
 	void memory_pool_c::_free(U* obj)
 	{
-		base_node_c* base_obj = dynamic_cast<base_node_c*>(obj);
-		if(nullptr == base_obj)
-		{
-			// error
-			return;
-		}
+		static_assert(std::is_base_of<base_node_c, U>::value, "U must inherit from base_node_c");
 
+		base_node_c* base_obj = static_cast<base_node_c*>(obj);
 		if(0 != base_obj->_grp_name.compare(_grp_name))
 		{
 			// error
@@ -29,18 +25,18 @@ namespace util
 		size_t obj_size = sizeof(U);
 		_mpool.insert(std::make_pair(obj_size, base_obj));
 
-		_mpool_alloc_cnt--;
-		if(_mpool_alloc_cnt < 0)
+		if (_mpool_alloc_cnt < 0)
 		{
 			// weird
 		}
+		_mpool_alloc_cnt--;
 	}
 
 	template <typename U, typename... Args>
-	std::shared_ptr<U> memory_pool_c::alloc(std::string& grp_name, Args... args)
+	std::shared_ptr<U> memory_pool_c::alloc(const std::string& grp_name, Args... args)
 	{
 		// check inheritance
-		static_assert(std::is_base_of<base_node_c, U>::value, "think phrase");
+		static_assert(std::is_base_of<base_node_c, U>::value, "U must be derived from base_node_c");
 
 		base_node_c* base_node = nullptr;
 		size_t obj_size = sizeof(U);
@@ -62,7 +58,7 @@ namespace util
 				}
 
 				// calc position
-				_last_ptr = reinterpret_cast<void*>(reinterpret_cast<uint64_t>(_last_ptr) + adjust_size);
+				_last_ptr = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(_last_ptr) + adjust_size);
 				_mpool_cur_byte += adjust_size;
 			}
 			else

리뷰: crash 를 유발하는 코드는 가급적 넣지 않는게 좋으므로 삭제.
_mpool_alloc_cnt 값은 최소가 0 이므로 조건도 부합하지 않음.
**_mpool_alloc_cnt 타입을 변경하고 하단에서 로그로 체크하는 방향으로 수정.**

unit-test 에서 위와 같은 케이스를 추가해서 테스트 할 예정.
@@ -8,13 +8,9 @@ namespace util
 	template <typename U>
 	void memory_pool_c::_free(U* obj)
 	{
-		base_node_c* base_obj = dynamic_cast<base_node_c*>(obj);
-		if(nullptr == base_obj)
-		{
-			// error
-			return;
-		}
+		static_assert(std::is_base_of<base_node_c, U>::value, "U must inherit from base_node_c");
 
+		base_node_c* base_obj = static_cast<base_node_c*>(obj);
 		if(0 != base_obj->_grp_name.compare(_grp_name))
 		{
 			// error
@@ -29,18 +25,18 @@ namespace util
 		size_t obj_size = sizeof(U);
 		_mpool.insert(std::make_pair(obj_size, base_obj));
 
-		_mpool_alloc_cnt--;
-		if(_mpool_alloc_cnt < 0)
+		if (_mpool_alloc_cnt < 0)
 		{
 			// weird
 		}
+		_mpool_alloc_cnt--;
 	}
 
 	template <typename U, typename... Args>
-	std::shared_ptr<U> memory_pool_c::alloc(std::string& grp_name, Args... args)
+	std::shared_ptr<U> memory_pool_c::alloc(const std::string& grp_name, Args... args)
 	{
 		// check inheritance
-		static_assert(std::is_base_of<base_node_c, U>::value, "think phrase");
+		static_assert(std::is_base_of<base_node_c, U>::value, "U must be derived from base_node_c");
 
 		base_node_c* base_node = nullptr;
 		size_t obj_size = sizeof(U);
@@ -62,7 +58,7 @@ namespace util
 				}
 
 				// calc position
-				_last_ptr = reinterpret_cast<void*>(reinterpret_cast<uint64_t>(_last_ptr) + adjust_size);
+				_last_ptr = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(_last_ptr) + adjust_size);
 				_mpool_cur_byte += adjust_size;
 			}
 			else

리뷰: static_cast 는 nullptr 반환하지 않으므로 체크하는 로직 삭제 필요.
@@ -0,0 +1,22 @@
+package kr.wooco.woocobe.placereview.adapter.out.presistence.entity
+
+import jakarta.persistence.Column
+import jakarta.persistence.Entity
+import jakarta.persistence.Id
+import jakarta.persistence.Table
+import kr.wooco.woocobe.common.infrastructure.storage.Tsid
+import kr.wooco.woocobe.common.infrastructure.storage.entity.BaseTimeEntity
+
+@Entity
+@Table(name = "place_one_line_reviews")
+class PlaceOneLineReviewJpaEntity(
+    @Column(name = "contents")
+    val contents: String,
+    @Column(name = "place_review_id")
+    val placeReviewId: Long,
+    @Column(name = "place_id")
+    val placeId: Long,
+    @Id @Tsid
+    @Column(name = "place_one_line_review_id")
+    override val id: Long = 0L,
+) : BaseTimeEntity()

리뷰: `place_Review_id` -> `place_review_id` 대문자 없애주세유
@@ -25,10 +25,13 @@ repositories {
 
 dependencies {
     implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
-    //implementation 'org.springframework.boot:spring-boot-starter-security'
+    implementation 'org.springframework.boot:spring-boot-starter-security'
     implementation 'org.springframework.boot:spring-boot-starter-web'
     implementation 'org.springframework.boot:spring-boot-starter-webflux'
     implementation 'org.springframework.boot:spring-boot-starter-validation'
+    implementation("io.jsonwebtoken:jjwt-api:0.11.5")
+    runtimeOnly("io.jsonwebtoken:jjwt-impl:0.11.5")
+    runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.11.5")
     annotationProcessor 'org.projectlombok:lombok'
     compileOnly 'org.projectlombok:lombok'
     developmentOnly 'org.springframework.boot:spring-boot-devtools'
@@ -44,6 +47,8 @@ dependencies {
     runtimeOnly 'com.h2database:h2'
     implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
     implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0' //Swagger
+    implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.16.0' // Jackson JSR310
+    compileOnly 'javax.servlet:javax.servlet-api:4.0.1'
 }
 
 tasks.named('test') {

리뷰: 형식은 맞춰주세요.
@@ -0,0 +1,72 @@
+package com.ll.nbe342team8.domain.admin.service;
+
+import com.ll.nbe342team8.domain.admin.dto.AdminQuestionDto;
+import com.ll.nbe342team8.domain.qna.question.entity.Question;
+import com.ll.nbe342team8.domain.qna.question.repository.QuestionRepository;
+import com.ll.nbe342team8.global.exceptions.ServiceException;
+import com.ll.nbe342team8.standard.PageDto.PageDto;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class AdminQuestionService {
+    private final QuestionRepository questionRepository;
+
+    public PageDto<AdminQuestionDto> getQuestionsForAdmin(String keyword, Boolean hasAnswer, Pageable pageable) {
+        Page<Question> questionPage = fetchQuestions(keyword, hasAnswer, pageable);
+        return new PageDto<>(questionPage.map(AdminQuestionDto::new));
+    }
+
+    //검색 조건(키워드, 답변 상태)에 맞는 질문 목록을 조회
+    private Page<Question> fetchQuestions(String keyword, Boolean hasAnswer, Pageable pageable) {
+        if (isBlank(keyword) && hasAnswer == null) {
+            return questionRepository.findAll(pageable);
+        }
+
+        if (!isBlank(keyword)) {
+            return searchByKeywordAndAnswerStatus(keyword.trim(), hasAnswer, pageable);
+        }
+
+        return searchByAnswerStatus(hasAnswer, pageable);
+    }
+
+    //문자열이 비어있는지 확인
+    private boolean isBlank(String str) {
+        return str == null || str.trim().isEmpty();
+    }
+
+    //키워드 및 답변 상태에 따라 질문 검색
+    private Page<Question> searchByKeywordAndAnswerStatus(String keyword, Boolean hasAnswer, Pageable pageable) {
+        if (hasAnswer == null) {
+            return questionRepository.findByTitleContainingOrContentContaining(keyword, keyword, pageable);
+        }
+        return hasAnswer
+                ? questionRepository.findByAnswersIsNotEmptyAndTitleContainingOrContentContaining(keyword, keyword, pageable)
+                : questionRepository.findByAnswersIsEmptyAndTitleContainingOrContentContaining(keyword, keyword, pageable);
+    }
+
+    // 답변 상태에 따라 질문 검색
+    private Page<Question> searchByAnswerStatus(Boolean hasAnswer, Pageable pageable) {
+        return hasAnswer
+                ? questionRepository.findByAnswersIsNotEmpty(pageable)
+                : questionRepository.findByAnswersIsEmpty(pageable);
+    }
+
+    //ID를 기반으로 특정 질문을 조회
+    public Question getQuestionById(Long id) {
+        return questionRepository.findById(id)
+                .orElseThrow(() -> new ServiceException(404, "해당 질문을 찾을 수 없습니다."));
+    }
+
+
+    //관리자 권한으로 질문 삭제
+    @PreAuthorize("hasRole('ADMIN')")
+    public void deleteQuestion(Long id) {
+        Question question = getQuestionById(id);
+        questionRepository.delete(question);
+    }
+}

리뷰: 저번에 라이브러리 설명드렸었죠?
그리고 그게 아니더라도, 다른곳에서 사용할 가능성이 있다면 이 메서드는 여기 있으면 안됩니다.
@@ -0,0 +1,72 @@
+package com.ll.nbe342team8.domain.admin.service;
+
+import com.ll.nbe342team8.domain.admin.dto.AdminQuestionDto;
+import com.ll.nbe342team8.domain.qna.question.entity.Question;
+import com.ll.nbe342team8.domain.qna.question.repository.QuestionRepository;
+import com.ll.nbe342team8.global.exceptions.ServiceException;
+import com.ll.nbe342team8.standard.PageDto.PageDto;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class AdminQuestionService {
+    private final QuestionRepository questionRepository;
+
+    public PageDto<AdminQuestionDto> getQuestionsForAdmin(String keyword, Boolean hasAnswer, Pageable pageable) {
+        Page<Question> questionPage = fetchQuestions(keyword, hasAnswer, pageable);
+        return new PageDto<>(questionPage.map(AdminQuestionDto::new));
+    }
+
+    //검색 조건(키워드, 답변 상태)에 맞는 질문 목록을 조회
+    private Page<Question> fetchQuestions(String keyword, Boolean hasAnswer, Pageable pageable) {
+        if (isBlank(keyword) && hasAnswer == null) {
+            return questionRepository.findAll(pageable);
+        }
+
+        if (!isBlank(keyword)) {
+            return searchByKeywordAndAnswerStatus(keyword.trim(), hasAnswer, pageable);
+        }
+
+        return searchByAnswerStatus(hasAnswer, pageable);
+    }
+
+    //문자열이 비어있는지 확인
+    private boolean isBlank(String str) {
+        return str == null || str.trim().isEmpty();
+    }
+
+    //키워드 및 답변 상태에 따라 질문 검색
+    private Page<Question> searchByKeywordAndAnswerStatus(String keyword, Boolean hasAnswer, Pageable pageable) {
+        if (hasAnswer == null) {
+            return questionRepository.findByTitleContainingOrContentContaining(keyword, keyword, pageable);
+        }
+        return hasAnswer
+                ? questionRepository.findByAnswersIsNotEmptyAndTitleContainingOrContentContaining(keyword, keyword, pageable)
+                : questionRepository.findByAnswersIsEmptyAndTitleContainingOrContentContaining(keyword, keyword, pageable);
+    }
+
+    // 답변 상태에 따라 질문 검색
+    private Page<Question> searchByAnswerStatus(Boolean hasAnswer, Pageable pageable) {
+        return hasAnswer
+                ? questionRepository.findByAnswersIsNotEmpty(pageable)
+                : questionRepository.findByAnswersIsEmpty(pageable);
+    }
+
+    //ID를 기반으로 특정 질문을 조회
+    public Question getQuestionById(Long id) {
+        return questionRepository.findById(id)
+                .orElseThrow(() -> new ServiceException(404, "해당 질문을 찾을 수 없습니다."));
+    }
+
+
+    //관리자 권한으로 질문 삭제
+    @PreAuthorize("hasRole('ADMIN')")
+    public void deleteQuestion(Long id) {
+        Question question = getQuestionById(id);
+        questionRepository.delete(question);
+    }
+}

리뷰: 메서드 이름의 가독성이 정말, 정말 안 좋네요...
아무리 Data JPA의 네이밍 컨벤션이라고 해도, 적당히 자를 필요가 있습니다.
@@ -1,7 +1,10 @@
 package baseball;
 
+import baseball.controller.GameController;
+
 public class Application {
     public static void main(String[] args) {
-        // TODO: 프로그램 구현
+        GameController gameController = new GameController();
+        gameController.gameFlow();
     }
 }

리뷰: 여기 왜 경고 표시가 뜨는걸까?
https://hyeon9mak.github.io/github-no-newline-at-a-end-of-file/