Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.f1.backend.domain.admin.app;

import io.f1.backend.domain.admin.dao.AdminRepository;
import io.f1.backend.domain.admin.dto.AdminPrincipal;
import io.f1.backend.domain.admin.entity.Admin;

import lombok.RequiredArgsConstructor;

import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class AdminDetailService implements UserDetailsService {

private final AdminRepository adminRepository;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Admin admin =
adminRepository
.findByUsername(username)
.orElseThrow(
() -> new UsernameNotFoundException("E404007: 존재하지 않는 관리자입니다."));
// 프론트엔드로 내려가지 않는 예외
return new AdminPrincipal(admin);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package io.f1.backend.domain.admin.app.handler;

import com.fasterxml.jackson.databind.ObjectMapper;

import io.f1.backend.domain.admin.dto.AdminLoginFailResponse;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import lombok.RequiredArgsConstructor;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
@RequiredArgsConstructor
public class AdminLoginFailureHandler implements AuthenticationFailureHandler {

private final ObjectMapper objectMapper;

@Override
public void onAuthenticationFailure(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception)
throws IOException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 401
response.setContentType("application/json;charset=UTF-8");

AdminLoginFailResponse errorResponse =
new AdminLoginFailResponse("E401005", "아이디 또는 비밀번호가 일치하지 않습니다.");

response.getWriter().write(objectMapper.writeValueAsString(errorResponse));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package io.f1.backend.domain.admin.app.handler;

import static io.f1.backend.domain.user.constants.SessionKeys.ADMIN;
import static io.f1.backend.global.util.SecurityUtils.getCurrentAdminPrincipal;

import io.f1.backend.domain.admin.dao.AdminRepository;
import io.f1.backend.domain.admin.dto.AdminPrincipal;
import io.f1.backend.domain.admin.entity.Admin;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;

import lombok.RequiredArgsConstructor;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

@Component
@RequiredArgsConstructor
public class AdminLoginSuccessHandler implements AuthenticationSuccessHandler {

private final AdminRepository adminRepository;
private final HttpSession httpSession;

@Override
public void onAuthenticationSuccess(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) {

AdminPrincipal principal = getCurrentAdminPrincipal();
Admin admin =
adminRepository
.findByUsername(principal.getUsername())
.orElseThrow(() -> new RuntimeException("E404007: 존재하지 않는 관리자입니다."));

admin.updateLastLogin(LocalDateTime.now());
adminRepository.save(admin);
httpSession.setAttribute(ADMIN, principal.getAuthenticationAdmin());

response.setStatus(HttpServletResponse.SC_OK); // 200
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.f1.backend.domain.admin.dao;

import io.f1.backend.domain.admin.entity.Admin;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.Optional;

@Repository
public interface AdminRepository extends JpaRepository<Admin, Long> {

Optional<Admin> findByUsername(String username);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.f1.backend.domain.admin.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class AdminLoginFailResponse {
private String code;
private String message;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package io.f1.backend.domain.admin.dto;

import io.f1.backend.domain.admin.entity.Admin;

import lombok.Getter;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.Collections;

@Getter
public class AdminPrincipal implements UserDetails {

public static final String ROLE_ADMIN = "ROLE_ADMIN";
private final AuthenticationAdmin authenticationAdmin;
private final String password;

public AdminPrincipal(Admin admin) {
this.authenticationAdmin = AuthenticationAdmin.from(admin);
this.password = admin.getPassword();
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.singleton(() -> ROLE_ADMIN);
}

@Override
public String getPassword() {
return this.password;
}

@Override
public String getUsername() {
return authenticationAdmin.username();
}

@Override
public boolean isAccountNonExpired() {
return true;
}

@Override
public boolean isAccountNonLocked() {
return true;
}

@Override
public boolean isCredentialsNonExpired() {
return true;
}

@Override
public boolean isEnabled() {
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.f1.backend.domain.admin.dto;

import io.f1.backend.domain.admin.entity.Admin;

public record AuthenticationAdmin(Long adminId, String username) {

public static AuthenticationAdmin from(Admin admin) {
return new AuthenticationAdmin(admin.getId(), admin.getUsername());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

import lombok.Getter;

import java.time.LocalDateTime;

@Entity
@Getter
public class Admin extends BaseEntity {

@Id
Expand All @@ -25,4 +28,8 @@ public class Admin extends BaseEntity {

@Column(nullable = false)
private LocalDateTime lastLogin;

public void updateLastLogin(LocalDateTime lastLogin) {
this.lastLogin = lastLogin;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package io.f1.backend.domain.auth.api;

import static io.f1.backend.global.util.SecurityUtils.getAuthentication;

import io.f1.backend.domain.admin.dto.AdminPrincipal;
import io.f1.backend.domain.auth.dto.CurrentUserAndAdminResponse;
import io.f1.backend.domain.user.dto.UserPrincipal;

import lombok.RequiredArgsConstructor;

import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/auth")
@RequiredArgsConstructor
public class AuthController {

@GetMapping("/me")
public ResponseEntity<?> getCurrentUserOrAdmin() {
Authentication authentication = getAuthentication();
Object principal = authentication.getPrincipal();

if (principal instanceof UserPrincipal userPrincipal) {
return ResponseEntity.ok(CurrentUserAndAdminResponse.from(userPrincipal));
}
return ResponseEntity.ok(CurrentUserAndAdminResponse.from((AdminPrincipal) principal));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.f1.backend.domain.auth.dto;

import io.f1.backend.domain.admin.dto.AdminPrincipal;
import io.f1.backend.domain.user.dto.UserPrincipal;

public record CurrentUserAndAdminResponse(Long id, String name, String role) {

public static CurrentUserAndAdminResponse from(UserPrincipal userPrincipal) {
return new CurrentUserAndAdminResponse(
userPrincipal.getUserId(),
userPrincipal.getUserNickname(),
UserPrincipal.ROLE_USER);
}

public static CurrentUserAndAdminResponse from(AdminPrincipal adminPrincipal) {
return new CurrentUserAndAdminResponse(
adminPrincipal.getAuthenticationAdmin().adminId(),
adminPrincipal.getUsername(),
AdminPrincipal.ROLE_ADMIN);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ public RoomCreateResponse saveRoom(@RequestBody @Valid RoomCreateRequest request
return roomService.saveRoom(request);
}

@PostMapping("/validation")
@PostMapping("/enterRoom")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void validateRoom(@RequestBody @Valid RoomValidationRequest request) {
roomService.validateRoom(request);
public void enterRoom(@RequestBody @Valid RoomValidationRequest request) {
roomService.enterRoom(request);
}

@GetMapping
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package io.f1.backend.domain.game.app;

import io.f1.backend.domain.game.dto.GameStartData;
import io.f1.backend.domain.game.dto.response.GameStartResponse;
import io.f1.backend.domain.game.event.RoomUpdatedEvent;
import io.f1.backend.domain.game.model.GameSetting;
import io.f1.backend.domain.game.model.Player;
import io.f1.backend.domain.game.model.Room;
import io.f1.backend.domain.game.model.RoomState;
import io.f1.backend.domain.game.store.RoomRepository;
import io.f1.backend.domain.quiz.app.QuizService;
import io.f1.backend.domain.quiz.entity.Quiz;

import lombok.RequiredArgsConstructor;

import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;

import java.util.Map;

@Service
@RequiredArgsConstructor
public class GameService {

private final QuizService quizService;
private final RoomRepository roomRepository;
private final ApplicationEventPublisher eventPublisher;

public GameStartData gameStart(Long roomId, Long quizId) {

Room room =
roomRepository
.findRoom(roomId)
.orElseThrow(() -> new IllegalArgumentException("404 존재하지 않는 방입니다."));

if (!validateReadyStatus(room)) {
throw new IllegalArgumentException("E403004 : 레디 상태가 아닙니다.");
}

// 방의 gameSetting에 설정된 퀴즈랑 요청 퀴즈랑 같은지 체크 후 GameSetting에서 라운드 가져오기
Integer round = checkGameSetting(room, quizId);

Quiz quiz = quizService.getQuizWithQuestionsById(quizId);

// 라운드 수만큼 랜덤 Question 추출
GameStartResponse questions = quizService.getRandomQuestionsWithoutAnswer(quizId, round);

// 방 정보 게임 중으로 변경
room.updateRoomState(RoomState.PLAYING);

eventPublisher.publishEvent(new RoomUpdatedEvent(room, quiz));

return new GameStartData(getDestination(roomId), questions);
}

private Integer checkGameSetting(Room room, Long quizId) {

GameSetting gameSetting = room.getGameSetting();

if (!gameSetting.checkQuizId(quizId)) {
throw new IllegalArgumentException("E409002 : 게임 설정이 다릅니다. (게임을 시작할 수 없습니다.)");
}

return gameSetting.getRound();
}

private boolean validateReadyStatus(Room room) {

Map<String, Player> playerSessionMap = room.getPlayerSessionMap();

return playerSessionMap.values().stream().allMatch(Player::isReady);
}

private static String getDestination(Long roomId) {
return "/sub/room/" + roomId;
}
}
Loading