109 lines
4.7 KiB
Java
109 lines
4.7 KiB
Java
package com.hlab.yanalyst.domain.upload;
|
|
|
|
import com.hlab.yanalyst.global.common.ApiResponse;
|
|
import jakarta.servlet.http.HttpSession;
|
|
import jakarta.servlet.http.HttpServletRequest;
|
|
import lombok.RequiredArgsConstructor;
|
|
import org.springframework.http.ResponseEntity;
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
import org.springframework.stereotype.Controller;
|
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
|
import org.springframework.web.bind.annotation.GetMapping;
|
|
import org.springframework.web.bind.annotation.PathVariable;
|
|
import org.springframework.web.bind.annotation.RequestMapping;
|
|
import org.springframework.web.bind.annotation.RequestParam;
|
|
import org.springframework.web.bind.annotation.RequestBody;
|
|
import org.springframework.web.bind.annotation.PostMapping;
|
|
import org.springframework.web.bind.annotation.ResponseBody;
|
|
|
|
import java.net.URI;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.security.MessageDigest;
|
|
import java.util.List;
|
|
import java.util.UUID;
|
|
|
|
@Controller
|
|
@RequestMapping("/api/youtube")
|
|
@RequiredArgsConstructor
|
|
public class YoutubeOAuthController {
|
|
private static final String STATE_SESSION_KEY = "youtube.oauth.state";
|
|
private final YoutubeOAuthService service;
|
|
private final YoutubeOAuthLocalSettings localSettings;
|
|
|
|
@Value("${youtube.oauth.setup-token:}")
|
|
private String setupToken;
|
|
|
|
@GetMapping("/oauth/start")
|
|
public ResponseEntity<Void> start(HttpSession session) {
|
|
String state = UUID.randomUUID().toString();
|
|
session.setAttribute(STATE_SESSION_KEY, state);
|
|
return ResponseEntity.status(302).location(service.authorizationUri(state)).build();
|
|
}
|
|
|
|
@GetMapping("/oauth/callback")
|
|
public ResponseEntity<Void> callback(@RequestParam(required = false) String code,
|
|
@RequestParam(required = false) String state,
|
|
@RequestParam(required = false) String error,
|
|
HttpSession session) {
|
|
String expected = (String) session.getAttribute(STATE_SESSION_KEY);
|
|
session.removeAttribute(STATE_SESSION_KEY);
|
|
if (error != null) return redirect("/upload-studio?youtube=denied");
|
|
if (expected == null || !expected.equals(state)) return redirect("/upload-studio?youtube=invalid-state");
|
|
if (code == null || code.isBlank()) return redirect("/upload-studio?youtube=missing-code");
|
|
try {
|
|
service.connect(code);
|
|
return redirect("/upload-studio?youtube=connected");
|
|
} catch (RuntimeException e) {
|
|
return redirect("/upload-studio?youtube=failed");
|
|
}
|
|
}
|
|
|
|
@GetMapping("/connections")
|
|
@ResponseBody
|
|
public ApiResponse<List<YoutubeOAuthService.ConnectionDto>> connections() {
|
|
return ApiResponse.ok(service.connections());
|
|
}
|
|
|
|
@GetMapping("/settings/status")
|
|
@ResponseBody
|
|
public ApiResponse<SettingsStatus> settingsStatus() {
|
|
YoutubeOAuthLocalSettings.Status status = localSettings.status();
|
|
return ApiResponse.ok(new SettingsStatus(status.configured(), status.redirectUri(),
|
|
setupToken != null && !setupToken.isBlank()));
|
|
}
|
|
|
|
@PostMapping("/settings")
|
|
@ResponseBody
|
|
public ApiResponse<YoutubeOAuthLocalSettings.Status> saveSettings(@RequestBody SettingsRequest body,
|
|
HttpServletRequest request) {
|
|
requireSetupToken(request.getHeader("X-YouTube-Setup-Token"));
|
|
localSettings.save(body.clientId(), body.clientSecret(), body.redirectUri());
|
|
return ApiResponse.ok(localSettings.status());
|
|
}
|
|
|
|
private void requireSetupToken(String provided) {
|
|
if (setupToken == null || setupToken.isBlank()) {
|
|
throw new IllegalStateException("서버에 YOUTUBE_SETUP_TOKEN을 먼저 설정해 주세요.");
|
|
}
|
|
byte[] expected = setupToken.getBytes(StandardCharsets.UTF_8);
|
|
byte[] actual = (provided == null ? "" : provided).getBytes(StandardCharsets.UTF_8);
|
|
if (!MessageDigest.isEqual(expected, actual)) {
|
|
throw new IllegalArgumentException("관리자 설정 토큰이 올바르지 않습니다.");
|
|
}
|
|
}
|
|
|
|
@DeleteMapping("/connections/{id}")
|
|
@ResponseBody
|
|
public ApiResponse<Void> disconnect(@PathVariable Long id) {
|
|
service.disconnect(id);
|
|
return ApiResponse.ok(null);
|
|
}
|
|
|
|
private ResponseEntity<Void> redirect(String location) {
|
|
return ResponseEntity.status(302).location(URI.create(location)).build();
|
|
}
|
|
|
|
public record SettingsRequest(String clientId, String clientSecret, String redirectUri) { }
|
|
public record SettingsStatus(boolean configured, String redirectUri, boolean setupTokenConfigured) { }
|
|
}
|