Add backend foundation and config-driven workbench

This commit is contained in:
2026-04-01 15:01:44 +08:00
parent 88b8f05f03
commit 94a1f0ba78
68 changed files with 10833 additions and 0 deletions

View 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,
},
})
}

View 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})
}

File diff suppressed because it is too large Load Diff

View 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})
}

View 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})
}

View 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})
}

View 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})
}

View 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",
},
})
}

View 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,
}
}

View 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})
}

View 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})
}

View 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})
}

View 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})
}