자산 현황 및 자동매매 페이지 제거:
Some checks failed
Build Push and Restart Compose / deploy (push) Failing after 11m20s
Some checks failed
Build Push and Restart Compose / deploy (push) Failing after 11m20s
- `/templates/pages/asset.html`, `/templates/pages/autotrade.html` HTML 템플릿 삭제. - `/static/js/asset.js`, `/static/js/autotrade.js` 클라이언트 스크립트 제거. - 관련 함수 및 초기화 로직 삭제 (자산 조회 및 자동매매 기능 비활성화).
This commit is contained in:
@@ -1,8 +1,6 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
"stocksearch/config"
|
||||
"stocksearch/middleware"
|
||||
@@ -12,40 +10,11 @@ import (
|
||||
// AuthHandler 로그인/로그아웃 핸들러
|
||||
type AuthHandler struct {
|
||||
sessionSvc *services.SessionService
|
||||
loginTmpl *template.Template
|
||||
}
|
||||
|
||||
// NewAuthHandler 인증 핸들러 초기화
|
||||
func NewAuthHandler(sessionSvc *services.SessionService) *AuthHandler {
|
||||
tmpl, err := template.ParseFiles("templates/pages/login.html")
|
||||
if err != nil {
|
||||
log.Fatalf("로그인 템플릿 파싱 실패: %v", err)
|
||||
}
|
||||
return &AuthHandler{
|
||||
sessionSvc: sessionSvc,
|
||||
loginTmpl: tmpl,
|
||||
}
|
||||
}
|
||||
|
||||
// LoginPage GET /login — 로그인 폼 렌더링
|
||||
func (h *AuthHandler) LoginPage(w http.ResponseWriter, r *http.Request) {
|
||||
// 이미 로그인된 경우 메인으로 리다이렉트
|
||||
if cookie, err := r.Cookie(middleware.SessionCookieName); err == nil {
|
||||
if h.sessionSvc.Validate(cookie.Value) {
|
||||
http.Redirect(w, r, "/", http.StatusFound)
|
||||
return
|
||||
}
|
||||
}
|
||||
// next 파라미터가 없으면 로그인 후 메인 페이지로
|
||||
next := r.URL.Query().Get("next")
|
||||
if next == "" {
|
||||
next = "/"
|
||||
}
|
||||
data := map[string]string{
|
||||
"Next": next,
|
||||
"Error": "",
|
||||
}
|
||||
h.renderLogin(w, data)
|
||||
return &AuthHandler{sessionSvc: sessionSvc}
|
||||
}
|
||||
|
||||
// Login POST /login — ID/PW 검증 후 세션 발급
|
||||
@@ -64,12 +33,9 @@ func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// ID/PW 검증
|
||||
if id != config.App.AdminID || password != config.App.AdminPassword {
|
||||
data := map[string]string{
|
||||
"Next": next,
|
||||
"Error": "아이디 또는 비밀번호가 올바르지 않습니다.",
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
h.renderLogin(w, data)
|
||||
_, _ = w.Write([]byte(`{"error":"아이디 또는 비밀번호가 올바르지 않습니다."}`))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -99,13 +65,12 @@ func (h *AuthHandler) CheckSession(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
// Logout POST /logout — 세션 삭제 후 /login 리다이렉트
|
||||
// Logout POST /logout — 세션 삭제
|
||||
func (h *AuthHandler) Logout(w http.ResponseWriter, r *http.Request) {
|
||||
if cookie, err := r.Cookie(middleware.SessionCookieName); err == nil {
|
||||
h.sessionSvc.Delete(cookie.Value)
|
||||
}
|
||||
|
||||
// 쿠키 만료 처리
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: middleware.SessionCookieName,
|
||||
Value: "",
|
||||
@@ -114,14 +79,5 @@ func (h *AuthHandler) Logout(w http.ResponseWriter, r *http.Request) {
|
||||
MaxAge: -1,
|
||||
})
|
||||
|
||||
http.Redirect(w, r, "/", http.StatusFound)
|
||||
}
|
||||
|
||||
// renderLogin 로그인 템플릿 렌더링 헬퍼
|
||||
func (h *AuthHandler) renderLogin(w http.ResponseWriter, data interface{}) {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
if err := h.loginTmpl.ExecuteTemplate(w, "login.html", data); err != nil {
|
||||
log.Printf("로그인 템플릿 렌더링 실패: %v", err)
|
||||
http.Error(w, "페이지를 표시할 수 없습니다.", http.StatusInternalServerError)
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
@@ -92,6 +92,11 @@ func (h *AutoTradeHandler) GetPositions(w http.ResponseWriter, r *http.Request)
|
||||
jsonResponse(w, h.svc.GetPositions())
|
||||
}
|
||||
|
||||
// GetTrades GET /api/autotrade/trades — 종료된 거래 내역
|
||||
func (h *AutoTradeHandler) GetTrades(w http.ResponseWriter, r *http.Request) {
|
||||
jsonResponse(w, h.svc.GetTrades(100))
|
||||
}
|
||||
|
||||
// GetLogs GET /api/autotrade/logs — 최근 로그 (?level=action 이면 debug 제외)
|
||||
func (h *AutoTradeHandler) GetLogs(w http.ResponseWriter, r *http.Request) {
|
||||
logs := h.svc.GetLogs()
|
||||
|
||||
@@ -1,213 +0,0 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
"stocksearch/middleware"
|
||||
"stocksearch/services"
|
||||
)
|
||||
|
||||
// PageHandler HTML 페이지 렌더링 핸들러
|
||||
type PageHandler struct {
|
||||
// 페이지별 독립 템플릿 세트 (content 블록 충돌 방지)
|
||||
pageTemplates map[string]*template.Template
|
||||
stockService *services.StockService
|
||||
}
|
||||
|
||||
// NewPageHandler 페이지별 템플릿을 분리 파싱하여 핸들러 초기화
|
||||
func NewPageHandler() *PageHandler {
|
||||
base := "templates/layout/base.html"
|
||||
pages := []string{"index.html", "stock_detail.html", "theme.html", "kospi200.html", "asset.html", "autotrade.html"}
|
||||
|
||||
fns := templateFuncs()
|
||||
tmplMap := make(map[string]*template.Template, len(pages))
|
||||
for _, page := range pages {
|
||||
tmplMap[page] = template.Must(
|
||||
template.New("").Funcs(fns).ParseFiles(base, "templates/pages/"+page),
|
||||
)
|
||||
}
|
||||
return &PageHandler{
|
||||
pageTemplates: tmplMap,
|
||||
stockService: services.GetStockService(),
|
||||
}
|
||||
}
|
||||
|
||||
// IndexPage GET / - 메인 페이지
|
||||
func (h *PageHandler) IndexPage(w http.ResponseWriter, r *http.Request) {
|
||||
data := withLoggedIn(r, map[string]interface{}{
|
||||
"Title": "주식 시세",
|
||||
"ActiveMenu": "시세",
|
||||
})
|
||||
h.render(w, "index.html", data)
|
||||
}
|
||||
|
||||
// ThemePage GET /theme - 테마 분석 페이지
|
||||
func (h *PageHandler) ThemePage(w http.ResponseWriter, r *http.Request) {
|
||||
data := withLoggedIn(r, map[string]interface{}{
|
||||
"Title": "테마 분석 - 주식 시세",
|
||||
"ActiveMenu": "테마",
|
||||
})
|
||||
h.render(w, "theme.html", data)
|
||||
}
|
||||
|
||||
// Kospi200Page GET /kospi200 - 코스피200 종목 페이지
|
||||
func (h *PageHandler) Kospi200Page(w http.ResponseWriter, r *http.Request) {
|
||||
data := withLoggedIn(r, map[string]interface{}{
|
||||
"Title": "코스피200 - 주식 시세",
|
||||
"ActiveMenu": "코스피200",
|
||||
})
|
||||
h.render(w, "kospi200.html", data)
|
||||
}
|
||||
|
||||
// AssetPage GET /asset - 자산 현황 페이지
|
||||
func (h *PageHandler) AssetPage(w http.ResponseWriter, r *http.Request) {
|
||||
data := withLoggedIn(r, map[string]interface{}{
|
||||
"Title": "자산 현황 - 주식 시세",
|
||||
"ActiveMenu": "자산",
|
||||
})
|
||||
h.render(w, "asset.html", data)
|
||||
}
|
||||
|
||||
// AutoTradePage GET /autotrade - 자동매매 페이지
|
||||
func (h *PageHandler) AutoTradePage(w http.ResponseWriter, r *http.Request) {
|
||||
data := withLoggedIn(r, map[string]interface{}{
|
||||
"Title": "자동매매 - 주식 시세",
|
||||
"ActiveMenu": "자동매매",
|
||||
})
|
||||
h.render(w, "autotrade.html", data)
|
||||
}
|
||||
|
||||
// StockDetailPage GET /stock/{code} - 종목 상세 페이지
|
||||
func (h *PageHandler) StockDetailPage(w http.ResponseWriter, r *http.Request) {
|
||||
code := r.PathValue("code")
|
||||
|
||||
price, err := h.stockService.GetCurrentPrice(code)
|
||||
if err != nil {
|
||||
log.Printf("현재가 조회 실패 [%s]: %v", code, err)
|
||||
http.Error(w, "종목 정보를 불러올 수 없습니다.", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
data := withLoggedIn(r, map[string]interface{}{
|
||||
"Title": price.Name + " - 주식 시세",
|
||||
"Stock": price,
|
||||
"ActiveMenu": "",
|
||||
})
|
||||
|
||||
h.render(w, "stock_detail.html", data)
|
||||
}
|
||||
|
||||
// withLoggedIn 템플릿 데이터에 LoggedIn 필드 추가
|
||||
func withLoggedIn(r *http.Request, data map[string]interface{}) map[string]interface{} {
|
||||
data["LoggedIn"] = middleware.IsLoggedIn(r)
|
||||
return data
|
||||
}
|
||||
|
||||
// render 템플릿 렌더링 헬퍼
|
||||
func (h *PageHandler) render(w http.ResponseWriter, name string, data interface{}) {
|
||||
tmpl, ok := h.pageTemplates[name]
|
||||
if !ok {
|
||||
http.Error(w, "페이지를 찾을 수 없습니다.", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
if err := tmpl.ExecuteTemplate(w, "base.html", data); err != nil {
|
||||
log.Printf("템플릿 렌더링 실패 [%s]: %v", name, err)
|
||||
http.Error(w, "페이지를 표시할 수 없습니다.", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// templateFuncs 템플릿에서 사용할 커스텀 함수
|
||||
func templateFuncs() template.FuncMap {
|
||||
return template.FuncMap{
|
||||
// 가격 포맷 (천 단위 콤마)
|
||||
"formatPrice": func(n int64) string {
|
||||
return formatNumber(n)
|
||||
},
|
||||
// 등락률 부호 포함 문자열
|
||||
"formatRate": func(f float64) string {
|
||||
if f >= 0 {
|
||||
return "+" + formatFloat(f) + "%"
|
||||
}
|
||||
return formatFloat(f) + "%"
|
||||
},
|
||||
// 등락에 따른 CSS 색상 클래스
|
||||
"priceClass": func(f float64) string {
|
||||
if f > 0 {
|
||||
return "text-red-500" // 상승: 빨강 (한국 관행)
|
||||
} else if f < 0 {
|
||||
return "text-blue-500" // 하락: 파랑 (한국 관행)
|
||||
}
|
||||
return "text-gray-500"
|
||||
},
|
||||
// 체결강도 CSS 클래스 (100 기준)
|
||||
"cntrClass": func(f float64) string {
|
||||
if f > 100 {
|
||||
return "text-red-500"
|
||||
} else if f < 100 && f > 0 {
|
||||
return "text-blue-500"
|
||||
}
|
||||
return "text-gray-400"
|
||||
},
|
||||
// 체결강도 포맷
|
||||
"formatCntr": func(f float64) string {
|
||||
return formatFloat(f)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func formatNumber(n int64) string {
|
||||
s := ""
|
||||
negative := n < 0
|
||||
if negative {
|
||||
n = -n
|
||||
}
|
||||
for i, c := range reverse(itoa(n)) {
|
||||
if i > 0 && i%3 == 0 {
|
||||
s = "," + s
|
||||
}
|
||||
s = string(c) + s
|
||||
}
|
||||
if negative {
|
||||
s = "-" + s
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func formatFloat(f float64) string {
|
||||
if f < 0 {
|
||||
f = -f
|
||||
}
|
||||
// 소수점 2자리
|
||||
result := ""
|
||||
intPart := int64(f)
|
||||
fracPart := int64((f - float64(intPart)) * 100)
|
||||
result = itoa(intPart)
|
||||
if fracPart < 10 {
|
||||
result += ".0" + itoa(fracPart)
|
||||
} else {
|
||||
result += "." + itoa(fracPart)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func itoa(n int64) string {
|
||||
if n == 0 {
|
||||
return "0"
|
||||
}
|
||||
s := ""
|
||||
for n > 0 {
|
||||
s = string(rune('0'+n%10)) + s
|
||||
n /= 10
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func reverse(s string) string {
|
||||
r := []rune(s)
|
||||
for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 {
|
||||
r[i], r[j] = r[j], r[i]
|
||||
}
|
||||
return string(r)
|
||||
}
|
||||
Reference in New Issue
Block a user