Add backend foundation and config-driven workbench
This commit is contained in:
129
backend/internal/httpapi/handlers/auth_handler.go
Normal file
129
backend/internal/httpapi/handlers/auth_handler.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"cmr-backend/internal/apperr"
|
||||
"cmr-backend/internal/httpapi/middleware"
|
||||
"cmr-backend/internal/httpx"
|
||||
"cmr-backend/internal/service"
|
||||
)
|
||||
|
||||
type AuthHandler struct {
|
||||
authService *service.AuthService
|
||||
}
|
||||
|
||||
func NewAuthHandler(authService *service.AuthService) *AuthHandler {
|
||||
return &AuthHandler{authService: authService}
|
||||
}
|
||||
|
||||
func (h *AuthHandler) SendSMSCode(w http.ResponseWriter, r *http.Request) {
|
||||
var req service.SendSMSCodeInput
|
||||
if err := httpx.DecodeJSON(r, &req); err != nil {
|
||||
httpx.WriteError(w, apperr.New(http.StatusBadRequest, "invalid_json", "invalid request body"))
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.authService.SendSMSCode(r.Context(), req)
|
||||
if err != nil {
|
||||
httpx.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) LoginSMS(w http.ResponseWriter, r *http.Request) {
|
||||
var req service.LoginSMSInput
|
||||
if err := httpx.DecodeJSON(r, &req); err != nil {
|
||||
httpx.WriteError(w, apperr.New(http.StatusBadRequest, "invalid_json", "invalid request body"))
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.authService.LoginSMS(r.Context(), req)
|
||||
if err != nil {
|
||||
httpx.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) LoginWechatMini(w http.ResponseWriter, r *http.Request) {
|
||||
var req service.LoginWechatMiniInput
|
||||
if err := httpx.DecodeJSON(r, &req); err != nil {
|
||||
httpx.WriteError(w, apperr.New(http.StatusBadRequest, "invalid_json", "invalid request body"))
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.authService.LoginWechatMini(r.Context(), req)
|
||||
if err != nil {
|
||||
httpx.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) BindMobile(w http.ResponseWriter, r *http.Request) {
|
||||
var req service.BindMobileInput
|
||||
if err := httpx.DecodeJSON(r, &req); err != nil {
|
||||
httpx.WriteError(w, apperr.New(http.StatusBadRequest, "invalid_json", "invalid request body"))
|
||||
return
|
||||
}
|
||||
|
||||
auth := middleware.GetAuthContext(r.Context())
|
||||
if auth == nil {
|
||||
httpx.WriteError(w, apperr.New(http.StatusUnauthorized, "unauthorized", "missing auth context"))
|
||||
return
|
||||
}
|
||||
req.UserID = auth.UserID
|
||||
|
||||
result, err := h.authService.BindMobile(r.Context(), req)
|
||||
if err != nil {
|
||||
httpx.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) Refresh(w http.ResponseWriter, r *http.Request) {
|
||||
var req service.RefreshTokenInput
|
||||
if err := httpx.DecodeJSON(r, &req); err != nil {
|
||||
httpx.WriteError(w, apperr.New(http.StatusBadRequest, "invalid_json", "invalid request body"))
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.authService.Refresh(r.Context(), req)
|
||||
if err != nil {
|
||||
httpx.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) Logout(w http.ResponseWriter, r *http.Request) {
|
||||
var req service.LogoutInput
|
||||
if err := httpx.DecodeJSON(r, &req); err != nil {
|
||||
httpx.WriteError(w, apperr.New(http.StatusBadRequest, "invalid_json", "invalid request body"))
|
||||
return
|
||||
}
|
||||
|
||||
auth := middleware.GetAuthContext(r.Context())
|
||||
if auth != nil && req.UserID == "" {
|
||||
req.UserID = auth.UserID
|
||||
}
|
||||
|
||||
if err := h.authService.Logout(r.Context(), req); err != nil {
|
||||
httpx.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httpx.WriteJSON(w, http.StatusOK, map[string]any{
|
||||
"data": map[string]any{
|
||||
"loggedOut": true,
|
||||
},
|
||||
})
|
||||
}
|
||||
107
backend/internal/httpapi/handlers/config_handler.go
Normal file
107
backend/internal/httpapi/handlers/config_handler.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"cmr-backend/internal/apperr"
|
||||
"cmr-backend/internal/httpx"
|
||||
"cmr-backend/internal/service"
|
||||
)
|
||||
|
||||
type ConfigHandler struct {
|
||||
configService *service.ConfigService
|
||||
}
|
||||
|
||||
func NewConfigHandler(configService *service.ConfigService) *ConfigHandler {
|
||||
return &ConfigHandler{configService: configService}
|
||||
}
|
||||
|
||||
func (h *ConfigHandler) ListLocalFiles(w http.ResponseWriter, r *http.Request) {
|
||||
result, err := h.configService.ListLocalEventFiles()
|
||||
if err != nil {
|
||||
httpx.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
|
||||
}
|
||||
|
||||
func (h *ConfigHandler) ImportLocal(w http.ResponseWriter, r *http.Request) {
|
||||
var req service.ImportLocalEventConfigInput
|
||||
if err := httpx.DecodeJSON(r, &req); err != nil {
|
||||
httpx.WriteError(w, apperr.New(http.StatusBadRequest, "invalid_json", "invalid request body"))
|
||||
return
|
||||
}
|
||||
req.EventPublicID = r.PathValue("eventPublicID")
|
||||
|
||||
result, err := h.configService.ImportLocalEventConfig(r.Context(), req)
|
||||
if err != nil {
|
||||
httpx.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
|
||||
}
|
||||
|
||||
func (h *ConfigHandler) BuildPreview(w http.ResponseWriter, r *http.Request) {
|
||||
var req service.BuildPreviewInput
|
||||
if err := httpx.DecodeJSON(r, &req); err != nil {
|
||||
httpx.WriteError(w, apperr.New(http.StatusBadRequest, "invalid_json", "invalid request body"))
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.configService.BuildPreview(r.Context(), req)
|
||||
if err != nil {
|
||||
httpx.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
|
||||
}
|
||||
|
||||
func (h *ConfigHandler) PublishBuild(w http.ResponseWriter, r *http.Request) {
|
||||
var req service.PublishBuildInput
|
||||
if err := httpx.DecodeJSON(r, &req); err != nil {
|
||||
httpx.WriteError(w, apperr.New(http.StatusBadRequest, "invalid_json", "invalid request body"))
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.configService.PublishBuild(r.Context(), req)
|
||||
if err != nil {
|
||||
httpx.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
|
||||
}
|
||||
|
||||
func (h *ConfigHandler) ListSources(w http.ResponseWriter, r *http.Request) {
|
||||
limit := 20
|
||||
if raw := r.URL.Query().Get("limit"); raw != "" {
|
||||
if parsed, err := strconv.Atoi(raw); err == nil {
|
||||
limit = parsed
|
||||
}
|
||||
}
|
||||
|
||||
result, err := h.configService.ListEventConfigSources(r.Context(), r.PathValue("eventPublicID"), limit)
|
||||
if err != nil {
|
||||
httpx.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
|
||||
}
|
||||
|
||||
func (h *ConfigHandler) GetSource(w http.ResponseWriter, r *http.Request) {
|
||||
result, err := h.configService.GetEventConfigSource(r.Context(), r.PathValue("sourceID"))
|
||||
if err != nil {
|
||||
httpx.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
|
||||
}
|
||||
|
||||
func (h *ConfigHandler) GetBuild(w http.ResponseWriter, r *http.Request) {
|
||||
result, err := h.configService.GetEventConfigBuild(r.Context(), r.PathValue("buildID"))
|
||||
if err != nil {
|
||||
httpx.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
|
||||
}
|
||||
1588
backend/internal/httpapi/handlers/dev_handler.go
Normal file
1588
backend/internal/httpapi/handlers/dev_handler.go
Normal file
File diff suppressed because it is too large
Load Diff
31
backend/internal/httpapi/handlers/entry_handler.go
Normal file
31
backend/internal/httpapi/handlers/entry_handler.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"cmr-backend/internal/httpx"
|
||||
"cmr-backend/internal/service"
|
||||
)
|
||||
|
||||
type EntryHandler struct {
|
||||
entryService *service.EntryService
|
||||
}
|
||||
|
||||
func NewEntryHandler(entryService *service.EntryService) *EntryHandler {
|
||||
return &EntryHandler{entryService: entryService}
|
||||
}
|
||||
|
||||
func (h *EntryHandler) Resolve(w http.ResponseWriter, r *http.Request) {
|
||||
result, err := h.entryService.Resolve(r.Context(), service.ResolveEntryInput{
|
||||
ChannelCode: r.URL.Query().Get("channelCode"),
|
||||
ChannelType: r.URL.Query().Get("channelType"),
|
||||
PlatformAppID: r.URL.Query().Get("platformAppId"),
|
||||
TenantCode: r.URL.Query().Get("tenantCode"),
|
||||
})
|
||||
if err != nil {
|
||||
httpx.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
|
||||
}
|
||||
40
backend/internal/httpapi/handlers/entry_home_handler.go
Normal file
40
backend/internal/httpapi/handlers/entry_home_handler.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"cmr-backend/internal/apperr"
|
||||
"cmr-backend/internal/httpapi/middleware"
|
||||
"cmr-backend/internal/httpx"
|
||||
"cmr-backend/internal/service"
|
||||
)
|
||||
|
||||
type EntryHomeHandler struct {
|
||||
entryHomeService *service.EntryHomeService
|
||||
}
|
||||
|
||||
func NewEntryHomeHandler(entryHomeService *service.EntryHomeService) *EntryHomeHandler {
|
||||
return &EntryHomeHandler{entryHomeService: entryHomeService}
|
||||
}
|
||||
|
||||
func (h *EntryHomeHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
auth := middleware.GetAuthContext(r.Context())
|
||||
if auth == nil {
|
||||
httpx.WriteError(w, apperr.New(http.StatusUnauthorized, "unauthorized", "missing auth context"))
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.entryHomeService.GetEntryHome(r.Context(), service.EntryHomeInput{
|
||||
UserID: auth.UserID,
|
||||
ChannelCode: r.URL.Query().Get("channelCode"),
|
||||
ChannelType: r.URL.Query().Get("channelType"),
|
||||
PlatformAppID: r.URL.Query().Get("platformAppId"),
|
||||
TenantCode: r.URL.Query().Get("tenantCode"),
|
||||
})
|
||||
if err != nil {
|
||||
httpx.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
|
||||
}
|
||||
51
backend/internal/httpapi/handlers/event_handler.go
Normal file
51
backend/internal/httpapi/handlers/event_handler.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"cmr-backend/internal/apperr"
|
||||
"cmr-backend/internal/httpapi/middleware"
|
||||
"cmr-backend/internal/httpx"
|
||||
"cmr-backend/internal/service"
|
||||
)
|
||||
|
||||
type EventHandler struct {
|
||||
eventService *service.EventService
|
||||
}
|
||||
|
||||
func NewEventHandler(eventService *service.EventService) *EventHandler {
|
||||
return &EventHandler{eventService: eventService}
|
||||
}
|
||||
|
||||
func (h *EventHandler) GetDetail(w http.ResponseWriter, r *http.Request) {
|
||||
eventPublicID := r.PathValue("eventPublicID")
|
||||
result, err := h.eventService.GetEventDetail(r.Context(), eventPublicID)
|
||||
if err != nil {
|
||||
httpx.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
|
||||
}
|
||||
|
||||
func (h *EventHandler) Launch(w http.ResponseWriter, r *http.Request) {
|
||||
auth := middleware.GetAuthContext(r.Context())
|
||||
if auth == nil {
|
||||
httpx.WriteError(w, apperr.New(http.StatusUnauthorized, "unauthorized", "missing auth context"))
|
||||
return
|
||||
}
|
||||
|
||||
var req service.LaunchEventInput
|
||||
if err := httpx.DecodeJSON(r, &req); err != nil {
|
||||
httpx.WriteError(w, apperr.New(http.StatusBadRequest, "invalid_json", "invalid request body"))
|
||||
return
|
||||
}
|
||||
req.EventPublicID = r.PathValue("eventPublicID")
|
||||
req.UserID = auth.UserID
|
||||
|
||||
result, err := h.eventService.LaunchEvent(r.Context(), req)
|
||||
if err != nil {
|
||||
httpx.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
|
||||
}
|
||||
37
backend/internal/httpapi/handlers/event_play_handler.go
Normal file
37
backend/internal/httpapi/handlers/event_play_handler.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"cmr-backend/internal/apperr"
|
||||
"cmr-backend/internal/httpapi/middleware"
|
||||
"cmr-backend/internal/httpx"
|
||||
"cmr-backend/internal/service"
|
||||
)
|
||||
|
||||
type EventPlayHandler struct {
|
||||
eventPlayService *service.EventPlayService
|
||||
}
|
||||
|
||||
func NewEventPlayHandler(eventPlayService *service.EventPlayService) *EventPlayHandler {
|
||||
return &EventPlayHandler{eventPlayService: eventPlayService}
|
||||
}
|
||||
|
||||
func (h *EventPlayHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
auth := middleware.GetAuthContext(r.Context())
|
||||
if auth == nil {
|
||||
httpx.WriteError(w, apperr.New(http.StatusUnauthorized, "unauthorized", "missing auth context"))
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.eventPlayService.GetEventPlay(r.Context(), service.EventPlayInput{
|
||||
EventPublicID: r.PathValue("eventPublicID"),
|
||||
UserID: auth.UserID,
|
||||
})
|
||||
if err != nil {
|
||||
httpx.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
|
||||
}
|
||||
21
backend/internal/httpapi/handlers/health_handler.go
Normal file
21
backend/internal/httpapi/handlers/health_handler.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"cmr-backend/internal/httpx"
|
||||
)
|
||||
|
||||
type HealthHandler struct{}
|
||||
|
||||
func NewHealthHandler() *HealthHandler {
|
||||
return &HealthHandler{}
|
||||
}
|
||||
|
||||
func (h *HealthHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
httpx.WriteJSON(w, http.StatusOK, map[string]any{
|
||||
"data": map[string]any{
|
||||
"status": "ok",
|
||||
},
|
||||
})
|
||||
}
|
||||
53
backend/internal/httpapi/handlers/home_handler.go
Normal file
53
backend/internal/httpapi/handlers/home_handler.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"cmr-backend/internal/httpx"
|
||||
"cmr-backend/internal/service"
|
||||
)
|
||||
|
||||
type HomeHandler struct {
|
||||
homeService *service.HomeService
|
||||
}
|
||||
|
||||
func NewHomeHandler(homeService *service.HomeService) *HomeHandler {
|
||||
return &HomeHandler{homeService: homeService}
|
||||
}
|
||||
|
||||
func (h *HomeHandler) GetHome(w http.ResponseWriter, r *http.Request) {
|
||||
result, err := h.homeService.GetHome(r.Context(), buildListCardsInput(r))
|
||||
if err != nil {
|
||||
httpx.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
|
||||
}
|
||||
|
||||
func (h *HomeHandler) GetCards(w http.ResponseWriter, r *http.Request) {
|
||||
result, err := h.homeService.ListCards(r.Context(), buildListCardsInput(r))
|
||||
if err != nil {
|
||||
httpx.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
|
||||
}
|
||||
|
||||
func buildListCardsInput(r *http.Request) service.ListCardsInput {
|
||||
limit := 20
|
||||
if raw := r.URL.Query().Get("limit"); raw != "" {
|
||||
if parsed, err := strconv.Atoi(raw); err == nil {
|
||||
limit = parsed
|
||||
}
|
||||
}
|
||||
|
||||
return service.ListCardsInput{
|
||||
ChannelCode: r.URL.Query().Get("channelCode"),
|
||||
ChannelType: r.URL.Query().Get("channelType"),
|
||||
PlatformAppID: r.URL.Query().Get("platformAppId"),
|
||||
TenantCode: r.URL.Query().Get("tenantCode"),
|
||||
Slot: r.URL.Query().Get("slot"),
|
||||
Limit: limit,
|
||||
}
|
||||
}
|
||||
34
backend/internal/httpapi/handlers/me_handler.go
Normal file
34
backend/internal/httpapi/handlers/me_handler.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"cmr-backend/internal/apperr"
|
||||
"cmr-backend/internal/httpapi/middleware"
|
||||
"cmr-backend/internal/httpx"
|
||||
"cmr-backend/internal/service"
|
||||
)
|
||||
|
||||
type MeHandler struct {
|
||||
meService *service.MeService
|
||||
}
|
||||
|
||||
func NewMeHandler(meService *service.MeService) *MeHandler {
|
||||
return &MeHandler{meService: meService}
|
||||
}
|
||||
|
||||
func (h *MeHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
auth := middleware.GetAuthContext(r.Context())
|
||||
if auth == nil {
|
||||
httpx.WriteError(w, apperr.New(http.StatusUnauthorized, "unauthorized", "missing auth context"))
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.meService.GetMe(r.Context(), auth.UserID)
|
||||
if err != nil {
|
||||
httpx.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": user})
|
||||
}
|
||||
34
backend/internal/httpapi/handlers/profile_handler.go
Normal file
34
backend/internal/httpapi/handlers/profile_handler.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"cmr-backend/internal/apperr"
|
||||
"cmr-backend/internal/httpapi/middleware"
|
||||
"cmr-backend/internal/httpx"
|
||||
"cmr-backend/internal/service"
|
||||
)
|
||||
|
||||
type ProfileHandler struct {
|
||||
profileService *service.ProfileService
|
||||
}
|
||||
|
||||
func NewProfileHandler(profileService *service.ProfileService) *ProfileHandler {
|
||||
return &ProfileHandler{profileService: profileService}
|
||||
}
|
||||
|
||||
func (h *ProfileHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
auth := middleware.GetAuthContext(r.Context())
|
||||
if auth == nil {
|
||||
httpx.WriteError(w, apperr.New(http.StatusUnauthorized, "unauthorized", "missing auth context"))
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.profileService.GetProfile(r.Context(), auth.UserID)
|
||||
if err != nil {
|
||||
httpx.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
|
||||
}
|
||||
58
backend/internal/httpapi/handlers/result_handler.go
Normal file
58
backend/internal/httpapi/handlers/result_handler.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"cmr-backend/internal/apperr"
|
||||
"cmr-backend/internal/httpapi/middleware"
|
||||
"cmr-backend/internal/httpx"
|
||||
"cmr-backend/internal/service"
|
||||
)
|
||||
|
||||
type ResultHandler struct {
|
||||
resultService *service.ResultService
|
||||
}
|
||||
|
||||
func NewResultHandler(resultService *service.ResultService) *ResultHandler {
|
||||
return &ResultHandler{resultService: resultService}
|
||||
}
|
||||
|
||||
func (h *ResultHandler) GetSessionResult(w http.ResponseWriter, r *http.Request) {
|
||||
auth := middleware.GetAuthContext(r.Context())
|
||||
if auth == nil {
|
||||
httpx.WriteError(w, apperr.New(http.StatusUnauthorized, "unauthorized", "missing auth context"))
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.resultService.GetSessionResult(r.Context(), r.PathValue("sessionPublicID"), auth.UserID)
|
||||
if err != nil {
|
||||
httpx.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
|
||||
}
|
||||
|
||||
func (h *ResultHandler) ListMine(w http.ResponseWriter, r *http.Request) {
|
||||
auth := middleware.GetAuthContext(r.Context())
|
||||
if auth == nil {
|
||||
httpx.WriteError(w, apperr.New(http.StatusUnauthorized, "unauthorized", "missing auth context"))
|
||||
return
|
||||
}
|
||||
|
||||
limit := 20
|
||||
if raw := r.URL.Query().Get("limit"); raw != "" {
|
||||
if parsed, err := strconv.Atoi(raw); err == nil {
|
||||
limit = parsed
|
||||
}
|
||||
}
|
||||
|
||||
result, err := h.resultService.ListMyResults(r.Context(), auth.UserID, limit)
|
||||
if err != nil {
|
||||
httpx.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
|
||||
}
|
||||
88
backend/internal/httpapi/handlers/session_handler.go
Normal file
88
backend/internal/httpapi/handlers/session_handler.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"cmr-backend/internal/apperr"
|
||||
"cmr-backend/internal/httpapi/middleware"
|
||||
"cmr-backend/internal/httpx"
|
||||
"cmr-backend/internal/service"
|
||||
)
|
||||
|
||||
type SessionHandler struct {
|
||||
sessionService *service.SessionService
|
||||
}
|
||||
|
||||
func NewSessionHandler(sessionService *service.SessionService) *SessionHandler {
|
||||
return &SessionHandler{sessionService: sessionService}
|
||||
}
|
||||
|
||||
func (h *SessionHandler) GetDetail(w http.ResponseWriter, r *http.Request) {
|
||||
auth := middleware.GetAuthContext(r.Context())
|
||||
if auth == nil {
|
||||
httpx.WriteError(w, apperr.New(http.StatusUnauthorized, "unauthorized", "missing auth context"))
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.sessionService.GetSession(r.Context(), r.PathValue("sessionPublicID"), auth.UserID)
|
||||
if err != nil {
|
||||
httpx.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
|
||||
}
|
||||
|
||||
func (h *SessionHandler) ListMine(w http.ResponseWriter, r *http.Request) {
|
||||
auth := middleware.GetAuthContext(r.Context())
|
||||
if auth == nil {
|
||||
httpx.WriteError(w, apperr.New(http.StatusUnauthorized, "unauthorized", "missing auth context"))
|
||||
return
|
||||
}
|
||||
|
||||
limit := 20
|
||||
if raw := r.URL.Query().Get("limit"); raw != "" {
|
||||
if parsed, err := strconv.Atoi(raw); err == nil {
|
||||
limit = parsed
|
||||
}
|
||||
}
|
||||
|
||||
result, err := h.sessionService.ListMySessions(r.Context(), auth.UserID, limit)
|
||||
if err != nil {
|
||||
httpx.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
|
||||
}
|
||||
|
||||
func (h *SessionHandler) Start(w http.ResponseWriter, r *http.Request) {
|
||||
var req service.SessionActionInput
|
||||
if err := httpx.DecodeJSON(r, &req); err != nil {
|
||||
httpx.WriteError(w, apperr.New(http.StatusBadRequest, "invalid_json", "invalid request body: "+err.Error()))
|
||||
return
|
||||
}
|
||||
req.SessionPublicID = r.PathValue("sessionPublicID")
|
||||
|
||||
result, err := h.sessionService.StartSession(r.Context(), req)
|
||||
if err != nil {
|
||||
httpx.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
|
||||
}
|
||||
|
||||
func (h *SessionHandler) Finish(w http.ResponseWriter, r *http.Request) {
|
||||
var req service.FinishSessionInput
|
||||
if err := httpx.DecodeJSON(r, &req); err != nil {
|
||||
httpx.WriteError(w, apperr.New(http.StatusBadRequest, "invalid_json", "invalid request body: "+err.Error()))
|
||||
return
|
||||
}
|
||||
req.SessionPublicID = r.PathValue("sessionPublicID")
|
||||
|
||||
result, err := h.sessionService.FinishSession(r.Context(), req)
|
||||
if err != nil {
|
||||
httpx.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
|
||||
}
|
||||
50
backend/internal/httpapi/middleware/auth.go
Normal file
50
backend/internal/httpapi/middleware/auth.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"cmr-backend/internal/apperr"
|
||||
"cmr-backend/internal/httpx"
|
||||
"cmr-backend/internal/platform/jwtx"
|
||||
)
|
||||
|
||||
type authContextKey string
|
||||
|
||||
const authKey authContextKey = "auth"
|
||||
|
||||
type AuthContext struct {
|
||||
UserID string
|
||||
UserPublicID string
|
||||
}
|
||||
|
||||
func NewAuthMiddleware(jwtManager *jwtx.Manager) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
authHeader := strings.TrimSpace(r.Header.Get("Authorization"))
|
||||
if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") {
|
||||
httpx.WriteError(w, apperr.New(http.StatusUnauthorized, "unauthorized", "missing bearer token"))
|
||||
return
|
||||
}
|
||||
|
||||
token := strings.TrimSpace(strings.TrimPrefix(authHeader, "Bearer "))
|
||||
claims, err := jwtManager.ParseAccessToken(token)
|
||||
if err != nil {
|
||||
httpx.WriteError(w, apperr.New(http.StatusUnauthorized, "invalid_token", "invalid access token"))
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), authKey, &AuthContext{
|
||||
UserID: claims.UserID,
|
||||
UserPublicID: claims.UserPublicID,
|
||||
})
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func GetAuthContext(ctx context.Context) *AuthContext {
|
||||
auth, _ := ctx.Value(authKey).(*AuthContext)
|
||||
return auth
|
||||
}
|
||||
80
backend/internal/httpapi/router.go
Normal file
80
backend/internal/httpapi/router.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"cmr-backend/internal/httpapi/handlers"
|
||||
"cmr-backend/internal/httpapi/middleware"
|
||||
"cmr-backend/internal/platform/jwtx"
|
||||
"cmr-backend/internal/service"
|
||||
)
|
||||
|
||||
func NewRouter(
|
||||
appEnv string,
|
||||
jwtManager *jwtx.Manager,
|
||||
authService *service.AuthService,
|
||||
entryService *service.EntryService,
|
||||
entryHomeService *service.EntryHomeService,
|
||||
eventService *service.EventService,
|
||||
eventPlayService *service.EventPlayService,
|
||||
configService *service.ConfigService,
|
||||
homeService *service.HomeService,
|
||||
profileService *service.ProfileService,
|
||||
resultService *service.ResultService,
|
||||
sessionService *service.SessionService,
|
||||
devService *service.DevService,
|
||||
meService *service.MeService,
|
||||
) http.Handler {
|
||||
mux := http.NewServeMux()
|
||||
|
||||
healthHandler := handlers.NewHealthHandler()
|
||||
authHandler := handlers.NewAuthHandler(authService)
|
||||
entryHandler := handlers.NewEntryHandler(entryService)
|
||||
entryHomeHandler := handlers.NewEntryHomeHandler(entryHomeService)
|
||||
eventHandler := handlers.NewEventHandler(eventService)
|
||||
eventPlayHandler := handlers.NewEventPlayHandler(eventPlayService)
|
||||
configHandler := handlers.NewConfigHandler(configService)
|
||||
homeHandler := handlers.NewHomeHandler(homeService)
|
||||
profileHandler := handlers.NewProfileHandler(profileService)
|
||||
resultHandler := handlers.NewResultHandler(resultService)
|
||||
sessionHandler := handlers.NewSessionHandler(sessionService)
|
||||
devHandler := handlers.NewDevHandler(devService)
|
||||
meHandler := handlers.NewMeHandler(meService)
|
||||
authMiddleware := middleware.NewAuthMiddleware(jwtManager)
|
||||
|
||||
mux.HandleFunc("GET /healthz", healthHandler.Get)
|
||||
mux.HandleFunc("GET /home", homeHandler.GetHome)
|
||||
mux.HandleFunc("GET /cards", homeHandler.GetCards)
|
||||
mux.HandleFunc("GET /entry/resolve", entryHandler.Resolve)
|
||||
if appEnv != "production" {
|
||||
mux.HandleFunc("GET /dev/workbench", devHandler.Workbench)
|
||||
mux.HandleFunc("POST /dev/bootstrap-demo", devHandler.BootstrapDemo)
|
||||
mux.HandleFunc("GET /dev/config/local-files", configHandler.ListLocalFiles)
|
||||
mux.HandleFunc("POST /dev/events/{eventPublicID}/config-sources/import-local", configHandler.ImportLocal)
|
||||
mux.HandleFunc("POST /dev/config-builds/preview", configHandler.BuildPreview)
|
||||
mux.HandleFunc("POST /dev/config-builds/publish", configHandler.PublishBuild)
|
||||
}
|
||||
mux.Handle("GET /me/entry-home", authMiddleware(http.HandlerFunc(entryHomeHandler.Get)))
|
||||
mux.Handle("GET /me/profile", authMiddleware(http.HandlerFunc(profileHandler.Get)))
|
||||
mux.HandleFunc("GET /events/{eventPublicID}", eventHandler.GetDetail)
|
||||
mux.Handle("GET /events/{eventPublicID}/play", authMiddleware(http.HandlerFunc(eventPlayHandler.Get)))
|
||||
mux.Handle("GET /events/{eventPublicID}/config-sources", authMiddleware(http.HandlerFunc(configHandler.ListSources)))
|
||||
mux.Handle("POST /events/{eventPublicID}/launch", authMiddleware(http.HandlerFunc(eventHandler.Launch)))
|
||||
mux.Handle("GET /config-sources/{sourceID}", authMiddleware(http.HandlerFunc(configHandler.GetSource)))
|
||||
mux.Handle("GET /config-builds/{buildID}", authMiddleware(http.HandlerFunc(configHandler.GetBuild)))
|
||||
mux.Handle("GET /sessions/{sessionPublicID}", authMiddleware(http.HandlerFunc(sessionHandler.GetDetail)))
|
||||
mux.Handle("GET /sessions/{sessionPublicID}/result", authMiddleware(http.HandlerFunc(resultHandler.GetSessionResult)))
|
||||
mux.HandleFunc("POST /sessions/{sessionPublicID}/start", sessionHandler.Start)
|
||||
mux.HandleFunc("POST /sessions/{sessionPublicID}/finish", sessionHandler.Finish)
|
||||
mux.HandleFunc("POST /auth/sms/send", authHandler.SendSMSCode)
|
||||
mux.HandleFunc("POST /auth/login/sms", authHandler.LoginSMS)
|
||||
mux.HandleFunc("POST /auth/login/wechat-mini", authHandler.LoginWechatMini)
|
||||
mux.Handle("POST /auth/bind/mobile", authMiddleware(http.HandlerFunc(authHandler.BindMobile)))
|
||||
mux.HandleFunc("POST /auth/refresh", authHandler.Refresh)
|
||||
mux.HandleFunc("POST /auth/logout", authHandler.Logout)
|
||||
mux.Handle("GET /me", authMiddleware(http.HandlerFunc(meHandler.Get)))
|
||||
mux.Handle("GET /me/sessions", authMiddleware(http.HandlerFunc(sessionHandler.ListMine)))
|
||||
mux.Handle("GET /me/results", authMiddleware(http.HandlerFunc(resultHandler.ListMine)))
|
||||
|
||||
return mux
|
||||
}
|
||||
Reference in New Issue
Block a user