289 lines
8.4 KiB
Go
289 lines
8.4 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"stocksearch/models"
|
|
"stocksearch/services"
|
|
"strings"
|
|
)
|
|
|
|
// StockHandler JSON REST API 핸들러
|
|
type StockHandler struct {
|
|
stockService *services.StockService
|
|
scannerService *services.ScannerService
|
|
indexService *services.IndexService
|
|
searchService *services.SearchService
|
|
dartService *services.DartService
|
|
newsService *services.NewsService
|
|
themeService *services.ThemeService
|
|
kospi200Service *services.Kospi200Service
|
|
watchlistSvc *services.WatchlistService
|
|
}
|
|
|
|
// NewStockHandler 핸들러 초기화
|
|
func NewStockHandler(watchlistSvc *services.WatchlistService) *StockHandler {
|
|
return &StockHandler{
|
|
stockService: services.GetStockService(),
|
|
scannerService: services.GetScannerService(),
|
|
indexService: services.GetIndexService(),
|
|
searchService: services.GetSearchService(),
|
|
dartService: services.GetDartService(),
|
|
newsService: services.GetNewsService(),
|
|
themeService: services.GetThemeService(),
|
|
kospi200Service: services.GetKospi200Service(),
|
|
watchlistSvc: watchlistSvc,
|
|
}
|
|
}
|
|
|
|
// GetCurrentPrice GET /api/stock/{code} - 주식 현재가 JSON 반환
|
|
func (h *StockHandler) GetCurrentPrice(w http.ResponseWriter, r *http.Request) {
|
|
code := strings.TrimPrefix(r.PathValue("code"), "")
|
|
if code == "" {
|
|
jsonError(w, "종목코드가 필요합니다.", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
price, err := h.stockService.GetCurrentPrice(code)
|
|
if err != nil {
|
|
jsonError(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
jsonOK(w, price)
|
|
}
|
|
|
|
// GetChart GET /api/stock/{code}/chart?period=daily|minute1|minute5 - 차트 데이터 JSON 반환
|
|
func (h *StockHandler) GetDailyChart(w http.ResponseWriter, r *http.Request) {
|
|
code := r.PathValue("code")
|
|
if code == "" {
|
|
jsonError(w, "종목코드가 필요합니다.", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
period := r.URL.Query().Get("period")
|
|
var (
|
|
candles []models.CandleData
|
|
err error
|
|
)
|
|
|
|
switch period {
|
|
case "minute1":
|
|
candles, err = h.stockService.GetMinuteChart(code, 1)
|
|
case "minute5":
|
|
candles, err = h.stockService.GetMinuteChart(code, 5)
|
|
default:
|
|
candles, err = h.stockService.GetDailyChart(code)
|
|
}
|
|
|
|
if err != nil {
|
|
jsonError(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
jsonOK(w, candles)
|
|
}
|
|
|
|
// GetScannerStatus GET /api/scanner/status - 스캐너 활성화 상태 반환
|
|
func (h *StockHandler) GetScannerStatus(w http.ResponseWriter, r *http.Request) {
|
|
jsonOK(w, map[string]bool{"enabled": h.scannerService.IsEnabled()})
|
|
}
|
|
|
|
// ToggleScanner POST /api/scanner/toggle - 스캐너 ON/OFF 토글 후 새 상태 반환
|
|
func (h *StockHandler) ToggleScanner(w http.ResponseWriter, r *http.Request) {
|
|
next := !h.scannerService.IsEnabled()
|
|
h.scannerService.SetEnabled(next)
|
|
jsonOK(w, map[string]bool{"enabled": next})
|
|
}
|
|
|
|
// GetSignals GET /api/signal - 체결강도 상승 감지 시그널 종목 JSON 반환
|
|
func (h *StockHandler) GetSignals(w http.ResponseWriter, r *http.Request) {
|
|
signals := h.scannerService.GetSignals()
|
|
jsonOK(w, signals)
|
|
}
|
|
|
|
// GetWatchlistSignals GET /api/watchlist-signal?codes=005930,000660,... - 관심종목 복합 분석 JSON 반환
|
|
func (h *StockHandler) GetWatchlistSignals(w http.ResponseWriter, r *http.Request) {
|
|
raw := r.URL.Query().Get("codes")
|
|
if raw == "" {
|
|
jsonOK(w, []services.SignalStock{})
|
|
return
|
|
}
|
|
parts := strings.Split(raw, ",")
|
|
// 최대 20개 제한
|
|
codes := make([]string, 0, 20)
|
|
for _, c := range parts {
|
|
c = strings.TrimSpace(c)
|
|
if len(c) == 6 {
|
|
codes = append(codes, c)
|
|
}
|
|
if len(codes) >= 20 {
|
|
break
|
|
}
|
|
}
|
|
if len(codes) == 0 {
|
|
jsonOK(w, []services.SignalStock{})
|
|
return
|
|
}
|
|
signals := h.scannerService.AnalyzeWatchlist(codes)
|
|
if signals == nil {
|
|
signals = []services.SignalStock{}
|
|
}
|
|
jsonOK(w, signals)
|
|
}
|
|
|
|
// Search GET /api/search?q=... - 종목명·코드 검색 결과 JSON 반환
|
|
func (h *StockHandler) Search(w http.ResponseWriter, r *http.Request) {
|
|
q := r.URL.Query().Get("q")
|
|
results := h.searchService.Search(q)
|
|
if results == nil {
|
|
results = []services.StockItem{}
|
|
}
|
|
jsonOK(w, results)
|
|
}
|
|
|
|
// GetIndices GET /api/indices - 코스피·코스닥·다우·나스닥 지수 JSON 반환
|
|
func (h *StockHandler) GetIndices(w http.ResponseWriter, r *http.Request) {
|
|
quotes, err := h.indexService.GetIndices()
|
|
if err != nil {
|
|
jsonError(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
jsonOK(w, quotes)
|
|
}
|
|
|
|
// GetNews GET /api/news?name={stockName} - 종목 관련 최근 뉴스 JSON 반환
|
|
func (h *StockHandler) GetNews(w http.ResponseWriter, r *http.Request) {
|
|
name := r.URL.Query().Get("name")
|
|
if name == "" {
|
|
jsonError(w, "종목명이 필요합니다.", http.StatusBadRequest)
|
|
return
|
|
}
|
|
news, err := h.newsService.GetNews(name)
|
|
if err != nil {
|
|
jsonError(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
jsonOK(w, news)
|
|
}
|
|
|
|
// GetDisclosures GET /api/disclosure?code={stockCode} - DART 최근 공시 JSON 반환
|
|
func (h *StockHandler) GetDisclosures(w http.ResponseWriter, r *http.Request) {
|
|
code := r.URL.Query().Get("code")
|
|
if len(code) != 6 {
|
|
jsonError(w, "올바른 종목코드를 입력해주세요.", http.StatusBadRequest)
|
|
return
|
|
}
|
|
disclosures, err := h.dartService.GetDisclosures(code)
|
|
if err != nil {
|
|
jsonError(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
jsonOK(w, disclosures)
|
|
}
|
|
|
|
// GetKospi200 GET /api/kospi200 - 코스피200 구성종목 JSON 반환
|
|
func (h *StockHandler) GetKospi200(w http.ResponseWriter, r *http.Request) {
|
|
stocks, err := h.kospi200Service.GetStocks()
|
|
if err != nil {
|
|
jsonError(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
jsonOK(w, stocks)
|
|
}
|
|
|
|
// GetThemes GET /api/themes?date=1&sort=3 - 테마그룹 목록 JSON 반환
|
|
// sort: 3=상위등락률(기본), 1=상위기간수익률
|
|
func (h *StockHandler) GetThemes(w http.ResponseWriter, r *http.Request) {
|
|
dateTp := r.URL.Query().Get("date")
|
|
if dateTp == "" {
|
|
dateTp = "1"
|
|
}
|
|
sortTp := r.URL.Query().Get("sort")
|
|
if sortTp == "" {
|
|
sortTp = "3"
|
|
}
|
|
groups, err := h.themeService.GetThemes(dateTp, sortTp)
|
|
if err != nil {
|
|
jsonError(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
jsonOK(w, groups)
|
|
}
|
|
|
|
// GetThemeStocks GET /api/themes/{code}?date=1 - 테마구성종목 JSON 반환
|
|
func (h *StockHandler) GetThemeStocks(w http.ResponseWriter, r *http.Request) {
|
|
code := r.PathValue("code")
|
|
if code == "" {
|
|
jsonError(w, "테마코드가 필요합니다.", http.StatusBadRequest)
|
|
return
|
|
}
|
|
dateTp := r.URL.Query().Get("date")
|
|
if dateTp == "" {
|
|
dateTp = "1"
|
|
}
|
|
detail, err := h.themeService.GetThemeStocks(code, dateTp)
|
|
if err != nil {
|
|
jsonError(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
jsonOK(w, detail)
|
|
}
|
|
|
|
// GetWatchlist GET /api/watchlist — 관심종목 목록 JSON 반환
|
|
func (h *StockHandler) GetWatchlist(w http.ResponseWriter, r *http.Request) {
|
|
list := h.watchlistSvc.GetAll()
|
|
if list == nil {
|
|
list = []services.WatchlistItem{}
|
|
}
|
|
jsonOK(w, list)
|
|
}
|
|
|
|
// AddWatchlist POST /api/watchlist — body: {code, name} 관심종목 추가
|
|
func (h *StockHandler) AddWatchlist(w http.ResponseWriter, r *http.Request) {
|
|
var body struct {
|
|
Code string `json:"code"`
|
|
Name string `json:"name"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
|
jsonError(w, "잘못된 요청입니다.", http.StatusBadRequest)
|
|
return
|
|
}
|
|
if len(body.Code) != 6 {
|
|
jsonError(w, "올바른 종목코드를 입력해주세요.", http.StatusBadRequest)
|
|
return
|
|
}
|
|
if err := h.watchlistSvc.Add(body.Code, body.Name); err != nil {
|
|
jsonError(w, err.Error(), http.StatusConflict)
|
|
return
|
|
}
|
|
jsonOK(w, map[string]bool{"ok": true})
|
|
}
|
|
|
|
// RemoveWatchlist DELETE /api/watchlist/{code} — 관심종목 삭제
|
|
func (h *StockHandler) RemoveWatchlist(w http.ResponseWriter, r *http.Request) {
|
|
code := r.PathValue("code")
|
|
if len(code) != 6 {
|
|
jsonError(w, "올바른 종목코드를 입력해주세요.", http.StatusBadRequest)
|
|
return
|
|
}
|
|
if err := h.watchlistSvc.Remove(code); err != nil {
|
|
jsonError(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
jsonOK(w, map[string]bool{"ok": true})
|
|
}
|
|
|
|
// --- JSON 응답 헬퍼 ---
|
|
|
|
func jsonOK(w http.ResponseWriter, data interface{}) {
|
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
json.NewEncoder(w).Encode(data)
|
|
}
|
|
|
|
func jsonError(w http.ResponseWriter, message string, status int) {
|
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
w.WriteHeader(status)
|
|
json.NewEncoder(w).Encode(map[string]string{"error": message})
|
|
}
|