Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.f1.backend.domain.admin.api;

import io.f1.backend.domain.admin.app.AdminService;
import io.f1.backend.domain.admin.dto.UserPageResponse;
import io.f1.backend.global.validation.LimitPageSize;

import lombok.RequiredArgsConstructor;

import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/admin")
@RequiredArgsConstructor
public class AdminController {

private final AdminService adminService;

@LimitPageSize
@GetMapping("/users")
public ResponseEntity<UserPageResponse> getUsers(Pageable pageable) {
UserPageResponse response = adminService.getAllUsers(pageable);
return ResponseEntity.ok().body(response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.f1.backend.domain.admin.app;

import static io.f1.backend.domain.admin.mapper.AdminMapper.toUserListPageResponse;

import io.f1.backend.domain.admin.dto.UserPageResponse;
import io.f1.backend.domain.admin.dto.UserResponse;
import io.f1.backend.domain.user.dao.UserRepository;

import lombok.RequiredArgsConstructor;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class AdminService {

private final UserRepository userRepository;

@Transactional(readOnly = true)
public UserPageResponse getAllUsers(Pageable pageable) {
Page<UserResponse> users = userRepository.findAllUsersWithPaging(pageable);
return toUserListPageResponse(users);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.f1.backend.domain.admin.dto;

import java.util.List;

public record UserPageResponse(
int totalPages, int currentPage, int totalElements, List<UserResponse> users) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.f1.backend.domain.admin.dto;

import java.time.LocalDateTime;

public record UserResponse(
Long id, String nickname, LocalDateTime lastLogin, LocalDateTime createdAt) {}
11 changes: 11 additions & 0 deletions backend/src/main/java/io/f1/backend/domain/admin/entity/Admin.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Entity
@Getter
@NoArgsConstructor
public class Admin extends BaseEntity {

@Id
Expand All @@ -32,4 +35,12 @@ public class Admin extends BaseEntity {
public void updateLastLogin(LocalDateTime lastLogin) {
this.lastLogin = lastLogin;
}

@Builder
public Admin(Long id, String username, String password, LocalDateTime lastLogin) {
this.id = id;
this.username = username;
this.password = password;
this.lastLogin = LocalDateTime.now();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.f1.backend.domain.admin.mapper;

import io.f1.backend.domain.admin.dto.UserPageResponse;
import io.f1.backend.domain.admin.dto.UserResponse;

import org.springframework.data.domain.Page;

public class AdminMapper {

private AdminMapper() {}

public static UserPageResponse toUserListPageResponse(Page<UserResponse> userPage) {
int curPage = userPage.getNumber() + 1;

return new UserPageResponse(
userPage.getTotalPages(),
curPage,
userPage.getNumberOfElements(),
userPage.getContent());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,18 @@
import io.f1.backend.domain.question.entity.TextQuestion;
import io.f1.backend.domain.quiz.entity.Quiz;
import io.f1.backend.global.exception.CustomException;
import io.f1.backend.global.exception.errorcode.AuthErrorCode;
import io.f1.backend.global.exception.errorcode.QuestionErrorCode;
import io.f1.backend.global.security.enums.Role;
import io.f1.backend.global.util.SecurityUtils;

import lombok.RequiredArgsConstructor;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Objects;

@Service
@RequiredArgsConstructor
public class QuestionService {
Expand Down Expand Up @@ -47,10 +52,21 @@ public void updateQuestionContent(Long questionId, String content) {
.orElseThrow(
() -> new CustomException(QuestionErrorCode.QUESTION_NOT_FOUND));

verifyUserAuthority(question.getQuiz());

TextQuestion textQuestion = question.getTextQuestion();
textQuestion.changeContent(content);
}

private static void verifyUserAuthority(Quiz quiz) {
if (SecurityUtils.getCurrentUserRole() == Role.ADMIN) {
return;
}
if (!Objects.equals(SecurityUtils.getCurrentUserId(), quiz.getCreator().getId())) {
throw new CustomException(AuthErrorCode.FORBIDDEN);
}
}

@Transactional
public void updateQuestionAnswer(Long questionId, String answer) {

Expand All @@ -62,6 +78,8 @@ public void updateQuestionAnswer(Long questionId, String answer) {
.orElseThrow(
() -> new CustomException(QuestionErrorCode.QUESTION_NOT_FOUND));

verifyUserAuthority(question.getQuiz());

question.changeAnswer(answer);
}

Expand All @@ -74,6 +92,8 @@ public void deleteQuestion(Long questionId) {
.orElseThrow(
() -> new CustomException(QuestionErrorCode.QUESTION_NOT_FOUND));

verifyUserAuthority(question.getQuiz());

questionRepository.delete(question);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
import io.f1.backend.global.exception.CustomException;
import io.f1.backend.global.exception.errorcode.AuthErrorCode;
import io.f1.backend.global.exception.errorcode.QuizErrorCode;
import io.f1.backend.global.exception.errorcode.UserErrorCode;
import io.f1.backend.global.security.enums.Role;
import io.f1.backend.global.util.SecurityUtils;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -37,6 +40,7 @@
import java.nio.file.Paths;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.UUID;

@Slf4j
Expand All @@ -53,7 +57,6 @@ public class QuizService {
private final String DEFAULT = "default";
private static final long MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB

// TODO : 시큐리티 구현 이후 삭제해도 되는 의존성 주입
private final UserRepository userRepository;
private final QuestionService questionService;
private final QuizRepository quizRepository;
Expand All @@ -67,10 +70,13 @@ public QuizCreateResponse saveQuiz(MultipartFile thumbnailFile, QuizCreateReques
thumbnailPath = convertToThumbnailPath(thumbnailFile);
}

// TODO : 시큐리티 구현 이후 삭제 (data.sql로 초기 저장해둔 유저 get), 나중엔 현재 로그인한 유저의 아이디를 받아오도록 수정
User user = userRepository.findById(1L).orElseThrow(RuntimeException::new);
Long creatorId = SecurityUtils.getCurrentUserId();
User creator =
userRepository
.findById(creatorId)
.orElseThrow(() -> new CustomException(UserErrorCode.USER_NOT_FOUND));

Quiz quiz = quizCreateRequestToQuiz(request, thumbnailPath, user);
Quiz quiz = quizCreateRequestToQuiz(request, thumbnailPath, creator);

Quiz savedQuiz = quizRepository.save(quiz);

Expand Down Expand Up @@ -126,22 +132,30 @@ public void deleteQuiz(Long quizId) {
.findById(quizId)
.orElseThrow(() -> new CustomException(QuizErrorCode.QUIZ_NOT_FOUND));

// TODO : util 메서드에서 사용자 ID 꺼내쓰는 식으로 수정하기
if (1L != quiz.getCreator().getId()) {
throw new CustomException(AuthErrorCode.FORBIDDEN);
}
verifyUserAuthority(quiz);

deleteThumbnailFile(quiz.getThumbnailUrl());
quizRepository.deleteById(quizId);
}

private static void verifyUserAuthority(Quiz quiz) {
if (SecurityUtils.getCurrentUserRole() == Role.ADMIN) {
return;
}
if (!Objects.equals(SecurityUtils.getCurrentUserId(), quiz.getCreator().getId())) {
throw new CustomException(AuthErrorCode.FORBIDDEN);
}
}

@Transactional
public void updateQuizTitle(Long quizId, String title) {
Quiz quiz =
quizRepository
.findById(quizId)
.orElseThrow(() -> new CustomException(QuizErrorCode.QUIZ_NOT_FOUND));

verifyUserAuthority(quiz);

validateTitle(title);
quiz.changeTitle(title);
}
Expand All @@ -154,6 +168,8 @@ public void updateQuizDesc(Long quizId, String description) {
.findById(quizId)
.orElseThrow(() -> new CustomException(QuizErrorCode.QUIZ_NOT_FOUND));

verifyUserAuthority(quiz);

validateDesc(description);
quiz.changeDescription(description);
}
Expand All @@ -166,6 +182,8 @@ public void updateThumbnail(Long quizId, MultipartFile thumbnailFile) {
.findById(quizId)
.orElseThrow(() -> new CustomException(QuizErrorCode.QUIZ_NOT_FOUND));

verifyUserAuthority(quiz);

validateImageFile(thumbnailFile);
String newThumbnailPath = convertToThumbnailPath(thumbnailFile);

Expand Down Expand Up @@ -257,8 +275,6 @@ public List<Question> getRandomQuestionsWithoutAnswer(Long quizId, Integer round
.findById(quizId)
.orElseThrow(() -> new NoSuchElementException("존재하지 않는 퀴즈입니다."));

List<Question> randomQuestions = quizRepository.findRandQuestionsByQuizId(quizId, round);

return randomQuestions;
return quizRepository.findRandQuestionsByQuizId(quizId, round);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,18 @@

public class QuizMapper {

// TODO : 이후 파라미터에서 user 삭제하기
public static Quiz quizCreateRequestToQuiz(
QuizCreateRequest quizCreateRequest, String imgUrl, User user) {
QuizCreateRequest quizCreateRequest, String imgUrl, User creator) {

return new Quiz(
quizCreateRequest.getTitle(),
quizCreateRequest.getDescription(),
quizCreateRequest.getQuizType(),
imgUrl,
user // TODO : 이후 creator에 들어갈 User은 현재 로그인 중인 유저를 가져오도록 변경
);
creator);
}

public static QuizCreateResponse quizToQuizCreateResponse(Quiz quiz) {
// TODO : creatorId 넣어주는 부분에서 Getter를 안 쓰고, 현재 로그인한 유저의 id를 담는 식으로 바꿔도 될 듯
return new QuizCreateResponse(
quiz.getId(),
quiz.getTitle(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package io.f1.backend.domain.user.dao;

import io.f1.backend.domain.admin.dto.UserResponse;
import io.f1.backend.domain.user.entity.User;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

import java.util.Optional;
Expand All @@ -13,4 +17,9 @@ public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByProviderAndProviderId(String provider, String providerId);

Boolean existsUserByNickname(String nickname);

@Query(
"SELECT new io.f1.backend.domain.admin.dto.UserResponse(u.id, u.nickname, u.lastLogin,"
+ " u.createdAt)FROM User u ORDER BY u.id")
Page<UserResponse> findAllUsersWithPaging(Pageable pageable);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@

@Configuration
public class CorsConfig {

@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();

config.addAllowedOrigin("http://localhost:3000");
config.addAllowedOrigin("https://brainrace.duckdns.org");

config.addAllowedHeader("*");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import io.f1.backend.domain.user.app.handler.CustomAuthenticationEntryPoint;
import io.f1.backend.domain.user.app.handler.OAuthSuccessHandler;
import io.f1.backend.domain.user.app.handler.UserAndAdminLogoutSuccessHandler;
import io.f1.backend.global.filter.DevTokenAuthFilter;

import lombok.RequiredArgsConstructor;

Expand All @@ -17,6 +18,7 @@
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
Expand All @@ -34,6 +36,8 @@ public class SecurityConfig {
public SecurityFilterChain userFilterChain(HttpSecurity http) throws Exception {
http.csrf(AbstractHttpConfigurer::disable)
.cors(Customizer.withDefaults())
.addFilterBefore(
new DevTokenAuthFilter(), UsernamePasswordAuthenticationFilter.class)
.exceptionHandling(
exception ->
exception.authenticationEntryPoint(customAuthenticationEntryPoint))
Expand Down
Loading