mirror of
https://github.com/dalbodeule/hop-gate.git
synced 2025-12-08 04:45:43 +09:00
build(deps): add ent and x libs dependencies
This commit is contained in:
190
internal/admin/http.go
Normal file
190
internal/admin/http.go
Normal file
@@ -0,0 +1,190 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/dalbodeule/hop-gate/internal/logging"
|
||||
)
|
||||
|
||||
// Handler 는 /api/v1/admin 관리 plane HTTP 엔드포인트를 제공합니다.
|
||||
type Handler struct {
|
||||
Logger logging.Logger
|
||||
AdminAPIKey string
|
||||
Service DomainService
|
||||
}
|
||||
|
||||
// NewHandler 는 새로운 Handler 를 생성합니다.
|
||||
func NewHandler(logger logging.Logger, adminAPIKey string, svc DomainService) *Handler {
|
||||
return &Handler{
|
||||
Logger: logger.With(logging.Fields{"component": "admin_api"}),
|
||||
AdminAPIKey: strings.TrimSpace(adminAPIKey),
|
||||
Service: svc,
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterRoutes 는 전달받은 mux 에 관리 API 라우트를 등록합니다.
|
||||
// - POST /api/v1/admin/domains/register
|
||||
// - POST /api/v1/admin/domains/unregister
|
||||
func (h *Handler) RegisterRoutes(mux *http.ServeMux) {
|
||||
mux.Handle("/api/v1/admin/domains/register", h.authMiddleware(http.HandlerFunc(h.handleDomainRegister)))
|
||||
mux.Handle("/api/v1/admin/domains/unregister", h.authMiddleware(http.HandlerFunc(h.handleDomainUnregister)))
|
||||
}
|
||||
|
||||
// authMiddleware 는 Authorization: Bearer {ADMIN_API_KEY} 헤더를 검증합니다.
|
||||
func (h *Handler) authMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !h.authenticate(r) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"success": false,
|
||||
"error": "unauthorized",
|
||||
})
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handler) authenticate(r *http.Request) bool {
|
||||
if h.AdminAPIKey == "" {
|
||||
// Admin API 키가 설정되지 않았다면 모든 요청을 거부
|
||||
return false
|
||||
}
|
||||
auth := r.Header.Get("Authorization")
|
||||
if auth == "" {
|
||||
return false
|
||||
}
|
||||
const prefix = "Bearer "
|
||||
if !strings.HasPrefix(auth, prefix) {
|
||||
return false
|
||||
}
|
||||
token := strings.TrimSpace(strings.TrimPrefix(auth, prefix))
|
||||
return token == h.AdminAPIKey
|
||||
}
|
||||
|
||||
type domainRegisterRequest struct {
|
||||
Domain string `json:"domain"`
|
||||
Memo string `json:"memo"`
|
||||
}
|
||||
|
||||
type domainRegisterResponse struct {
|
||||
ClientAPIKey string `json:"client_api_key,omitempty"`
|
||||
Success bool `json:"success"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (h *Handler) handleDomainRegister(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
h.writeMethodNotAllowed(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
var req domainRegisterRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
h.Logger.Warn("invalid register request body", logging.Fields{"error": err.Error()})
|
||||
h.writeJSON(w, http.StatusBadRequest, domainRegisterResponse{
|
||||
Success: false,
|
||||
Error: "invalid request body",
|
||||
})
|
||||
return
|
||||
}
|
||||
req.Domain = strings.TrimSpace(req.Domain)
|
||||
|
||||
if req.Domain == "" {
|
||||
h.writeJSON(w, http.StatusBadRequest, domainRegisterResponse{
|
||||
Success: false,
|
||||
Error: "domain is required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
clientKey, err := h.Service.RegisterDomain(r.Context(), req.Domain, req.Memo)
|
||||
if err != nil {
|
||||
h.Logger.Error("failed to register domain", logging.Fields{
|
||||
"domain": req.Domain,
|
||||
"error": err.Error(),
|
||||
})
|
||||
h.writeJSON(w, http.StatusInternalServerError, domainRegisterResponse{
|
||||
Success: false,
|
||||
Error: "internal error",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
h.writeJSON(w, http.StatusOK, domainRegisterResponse{
|
||||
Success: true,
|
||||
ClientAPIKey: clientKey,
|
||||
})
|
||||
}
|
||||
|
||||
type domainUnregisterRequest struct {
|
||||
Domain string `json:"domain"`
|
||||
ClientAPIKey string `json:"client_api_key"`
|
||||
}
|
||||
|
||||
type domainUnregisterResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (h *Handler) handleDomainUnregister(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
h.writeMethodNotAllowed(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
var req domainUnregisterRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
h.Logger.Warn("invalid unregister request body", logging.Fields{"error": err.Error()})
|
||||
h.writeJSON(w, http.StatusBadRequest, domainUnregisterResponse{
|
||||
Success: false,
|
||||
Error: "invalid request body",
|
||||
})
|
||||
return
|
||||
}
|
||||
req.Domain = strings.TrimSpace(req.Domain)
|
||||
req.ClientAPIKey = strings.TrimSpace(req.ClientAPIKey)
|
||||
|
||||
if req.Domain == "" || req.ClientAPIKey == "" {
|
||||
h.writeJSON(w, http.StatusBadRequest, domainUnregisterResponse{
|
||||
Success: false,
|
||||
Error: "domain and client_api_key are required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.Service.UnregisterDomain(r.Context(), req.Domain, req.ClientAPIKey); err != nil {
|
||||
h.Logger.Error("failed to unregister domain", logging.Fields{
|
||||
"domain": req.Domain,
|
||||
"client_api_key": "***",
|
||||
"error": err.Error(),
|
||||
})
|
||||
h.writeJSON(w, http.StatusInternalServerError, domainUnregisterResponse{
|
||||
Success: false,
|
||||
Error: "internal error",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
h.writeJSON(w, http.StatusOK, domainUnregisterResponse{
|
||||
Success: true,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handler) writeMethodNotAllowed(w http.ResponseWriter, r *http.Request) {
|
||||
h.writeJSON(w, http.StatusMethodNotAllowed, map[string]any{
|
||||
"success": false,
|
||||
"error": "method not allowed",
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handler) writeJSON(w http.ResponseWriter, status int, v any) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
if err := json.NewEncoder(w).Encode(v); err != nil {
|
||||
h.Logger.Error("failed to write json response", logging.Fields{"error": err.Error()})
|
||||
}
|
||||
}
|
||||
13
internal/admin/service.go
Normal file
13
internal/admin/service.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package admin
|
||||
|
||||
import "context"
|
||||
|
||||
// DomainService 는 도메인 등록/해제를 담당하는 비즈니스 로직 인터페이스입니다.
|
||||
// 실제 구현에서는 ent.Client(PostgreSQL)를 주입받아 동작하게 됩니다.
|
||||
type DomainService interface {
|
||||
// RegisterDomain 은 새로운 도메인을 등록하고, 해당 도메인을 사용할 클라이언트 API Key(랜덤 64자)를 생성해 반환합니다.
|
||||
RegisterDomain(ctx context.Context, domain, memo string) (clientAPIKey string, err error)
|
||||
|
||||
// UnregisterDomain 은 도메인과 클라이언트 API Key를 함께 받아 등록을 해제합니다.
|
||||
UnregisterDomain(ctx context.Context, domain, clientAPIKey string) error
|
||||
}
|
||||
Reference in New Issue
Block a user