first commit
This commit is contained in:
288
handlers/stock_handler.go
Normal file
288
handlers/stock_handler.go
Normal file
@@ -0,0 +1,288 @@
|
||||
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})
|
||||
}
|
||||
Reference in New Issue
Block a user