package com.goldenchart.controller; import com.fasterxml.jackson.databind.ObjectMapper; import com.goldenchart.entity.GcChartSlot; import com.goldenchart.entity.GcChartWorkspace; import com.goldenchart.service.ChartSettingsService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** * 차트 워크스페이스 설정 REST API. * * GET /api/chart/workspace - 전체 설정 로드 (프론트 시작 시 호출) * POST /api/chart/workspace - 전체 설정 저장 * PUT /api/chart/workspace/layout - 레이아웃만 변경 * PUT /api/chart/slot/{index} - 특정 슬롯 저장 */ @RestController @RequestMapping("/chart") @RequiredArgsConstructor @Slf4j public class ChartSettingsController { private final ChartSettingsService settingsService; private final ObjectMapper objectMapper; /** 헤더에서 userId 또는 deviceId 추출 */ private Long userId(Map headers) { String v = headers.get("x-user-id"); return v != null ? Long.parseLong(v) : null; } private String deviceId(Map headers) { return headers.getOrDefault("x-device-id", "anonymous"); } /** * 프론트엔드 시작 시 전체 워크스페이스 로드. * DB에 저장된 설정이 없으면 204 반환 → 프론트는 기본값 사용. */ @GetMapping("/workspace") public ResponseEntity getWorkspace( @RequestHeader Map headers) { GcChartWorkspace ws = settingsService.getWorkspaceWithSlots( userId(headers), deviceId(headers)); if (ws == null) return ResponseEntity.noContent().build(); return ResponseEntity.ok(toDto(ws)); } /** * 전체 워크스페이스 저장 (자동 저장 / 명시 저장). */ @PostMapping("/workspace") public ResponseEntity saveWorkspace( @RequestHeader Map headers, @RequestBody Map body) { Long uid = userId(headers); String did = deviceId(headers); String layoutId = (String) body.getOrDefault("layoutId", "1"); String syncJson = body.containsKey("syncOptions") ? toJson(body.get("syncOptions")) : null; @SuppressWarnings("unchecked") List> slotsData = (List>) body.get("slots"); GcChartWorkspace ws = settingsService.saveAll(uid, did, layoutId, syncJson, slotsData != null ? slotsData : List.of()); return ResponseEntity.ok(toDto(ws)); } /** * 레이아웃만 변경. */ @PutMapping("/workspace/layout") public ResponseEntity updateLayout( @RequestHeader Map headers, @RequestBody Map body) { String layoutId = (String) body.get("layoutId"); String syncJson = body.containsKey("syncOptions") ? toJson(body.get("syncOptions")) : null; GcChartWorkspace ws = settingsService.saveWorkspace( userId(headers), deviceId(headers), layoutId, syncJson); return ResponseEntity.ok(Map.of("layoutId", ws.getLayoutId())); } /** * 특정 슬롯 설정 저장. */ @PutMapping("/slot/{slotIndex}") public ResponseEntity saveSlot( @RequestHeader Map headers, @PathVariable int slotIndex, @RequestBody Map body) { GcChartSlot slot = settingsService.saveSlot( userId(headers), deviceId(headers), slotIndex, body); return ResponseEntity.ok(slotToDto(slot)); } // ── DTO 변환 ──────────────────────────────────────────────────────────────── private Map toDto(GcChartWorkspace ws) { Map dto = new HashMap<>(); dto.put("id", ws.getId()); dto.put("layoutId", ws.getLayoutId()); dto.put("syncOptions", parseJson(ws.getSyncOptionsJson())); dto.put("slots", ws.getSlots().stream() .map(this::slotToDto) .collect(Collectors.toList())); return dto; } private Map slotToDto(GcChartSlot s) { Map dto = new HashMap<>(); dto.put("slotIndex", s.getSlotIndex()); dto.put("symbol", s.getSymbol()); dto.put("timeframe", s.getTimeframe()); dto.put("chartType", s.getChartType()); dto.put("theme", s.getTheme()); dto.put("mode", s.getMode()); dto.put("logScale", s.getLogScale()); dto.put("drawingsLocked", s.getDrawingsLocked()); dto.put("drawingsVisible", s.getDrawingsVisible()); dto.put("indicators", parseJson(s.getIndicatorsJson())); dto.put("drawings", parseJson(s.getDrawingsJson())); dto.put("paneLayout", parseJson(s.getPaneLayoutJson())); dto.put("mainChartStyle", parseJson(s.getMainChartStyleJson())); return dto; } private Object parseJson(String json) { if (json == null) return null; try { return objectMapper.readValue(json, Object.class); } catch (Exception e) { return null; } } private String toJson(Object obj) { try { return objectMapper.writeValueAsString(obj); } catch (Exception e) { return null; } } }