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

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

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