gc-guide-api/src/main/java/com/gcsc/guide/auth/AuthController.java
htlee 9db7b8bfb4 feat: 관리자/활동/이슈 API 전체 구현
- Entity: LoginHistory, PageView, Issue, IssueComment 추가
- Repository: 각 엔티티별 JpaRepository 추가
- Service: UserService, RoleService, ActivityService, IssueService
- Admin API: 사용자 관리 7개, 롤/권한 관리 7개, 통계 1개 엔드포인트
- Activity API: 페이지뷰 기록, 로그인 이력 조회
- Issue API: CRUD + 코멘트, 프로젝트/위치/Gitea 링크 지원
- Exception: GlobalExceptionHandler, ResourceNotFoundException, BusinessException
- AuthController: 로그인 시 LoginHistory 기록 추가
- Dockerfile 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 21:13:14 +09:00

107 lines
3.8 KiB
Java

package com.gcsc.guide.auth;
import com.gcsc.guide.dto.AuthResponse;
import com.gcsc.guide.dto.GoogleLoginRequest;
import com.gcsc.guide.dto.UserResponse;
import com.gcsc.guide.entity.User;
import com.gcsc.guide.repository.UserRepository;
import com.gcsc.guide.service.ActivityService;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;
@Slf4j
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthController {
private static final String AUTO_ADMIN_EMAIL = "htlee@gcsc.co.kr";
private final GoogleTokenVerifier googleTokenVerifier;
private final JwtTokenProvider jwtTokenProvider;
private final UserRepository userRepository;
private final ActivityService activityService;
/**
* Google ID Token으로 로그인/회원가입 처리 후 JWT 발급
*/
@PostMapping("/google")
public ResponseEntity<AuthResponse> googleLogin(
@Valid @RequestBody GoogleLoginRequest request,
HttpServletRequest httpRequest) {
GoogleIdToken.Payload payload = googleTokenVerifier.verify(request.idToken());
if (payload == null) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "유효하지 않은 Google 토큰입니다");
}
String email = payload.getEmail();
String name = (String) payload.get("name");
String avatarUrl = (String) payload.get("picture");
userRepository.findByEmail(email)
.ifPresentOrElse(
existingUser -> {
existingUser.updateProfile(name, avatarUrl);
existingUser.updateLastLogin();
userRepository.save(existingUser);
},
() -> createNewUser(email, name, avatarUrl)
);
User userWithRoles = userRepository.findByEmailWithRoles(email)
.orElseThrow();
activityService.recordLogin(
userWithRoles.getId(),
httpRequest.getRemoteAddr(),
httpRequest.getHeader("User-Agent"));
String token = jwtTokenProvider.generateToken(
userWithRoles.getId(), userWithRoles.getEmail(), userWithRoles.isAdmin());
return ResponseEntity.ok(new AuthResponse(token, UserResponse.from(userWithRoles)));
}
/**
* 현재 인증된 사용자 정보 조회
*/
@GetMapping("/me")
public ResponseEntity<UserResponse> getCurrentUser(Authentication authentication) {
Long userId = (Long) authentication.getPrincipal();
User user = userRepository.findByIdWithRoles(userId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "사용자를 찾을 수 없습니다"));
return ResponseEntity.ok(UserResponse.from(user));
}
/**
* 로그아웃 (Stateless JWT이므로 서버 측 처리 없음, 프론트에서 토큰 삭제)
*/
@PostMapping("/logout")
public ResponseEntity<Void> logout() {
return ResponseEntity.noContent().build();
}
private User createNewUser(String email, String name, String avatarUrl) {
User newUser = new User(email, name, avatarUrl);
if (AUTO_ADMIN_EMAIL.equals(email)) {
newUser.activate();
newUser.grantAdmin();
log.info("관리자 자동 승인: {}", email);
}
newUser.updateLastLogin();
return userRepository.save(newUser);
}
}