推进活动系统最小成品闭环与游客体验

This commit is contained in:
2026-04-07 19:05:18 +08:00
parent 1a6008449e
commit 6cd16f08dd
102 changed files with 16087 additions and 3556 deletions

View File

@@ -0,0 +1,132 @@
package handlers
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"io"
"net/http"
"os"
"cmr-backend/internal/apperr"
"cmr-backend/internal/httpx"
"cmr-backend/internal/service"
)
type AdminAssetHandler struct {
service *service.AdminAssetService
}
func NewAdminAssetHandler(service *service.AdminAssetService) *AdminAssetHandler {
return &AdminAssetHandler{service: service}
}
func (h *AdminAssetHandler) ListAssets(w http.ResponseWriter, r *http.Request) {
result, err := h.service.ListManagedAssets(r.Context(), parseAdminLimit(r))
if err != nil {
httpx.WriteError(w, err)
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
}
func (h *AdminAssetHandler) GetAsset(w http.ResponseWriter, r *http.Request) {
result, err := h.service.GetManagedAsset(r.Context(), r.PathValue("assetPublicID"))
if err != nil {
httpx.WriteError(w, err)
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
}
func (h *AdminAssetHandler) RegisterLink(w http.ResponseWriter, r *http.Request) {
var req service.RegisterLinkAssetInput
if err := httpx.DecodeJSON(r, &req); err != nil {
httpx.WriteError(w, apperr.New(http.StatusBadRequest, "invalid_json", "invalid request body: "+err.Error()))
return
}
result, err := h.service.RegisterExternalLink(r.Context(), req)
if err != nil {
httpx.WriteError(w, err)
return
}
httpx.WriteJSON(w, http.StatusCreated, map[string]any{"data": result})
}
func (h *AdminAssetHandler) UploadFile(w http.ResponseWriter, r *http.Request) {
if err := r.ParseMultipartForm(64 << 20); err != nil {
httpx.WriteError(w, apperr.New(http.StatusBadRequest, "invalid_multipart", "invalid multipart form: "+err.Error()))
return
}
file, header, err := r.FormFile("file")
if err != nil {
httpx.WriteError(w, apperr.New(http.StatusBadRequest, "file_required", "multipart file field 'file' is required"))
return
}
defer file.Close()
tmpFile, err := os.CreateTemp("", "cmr-upload-*"+header.Filename)
if err != nil {
httpx.WriteError(w, err)
return
}
tmpPath := tmpFile.Name()
defer os.Remove(tmpPath)
hash := sha256.New()
written, err := io.Copy(io.MultiWriter(tmpFile, hash), file)
if err != nil {
tmpFile.Close()
httpx.WriteError(w, err)
return
}
if err := tmpFile.Close(); err != nil {
httpx.WriteError(w, err)
return
}
input := service.UploadAssetFileInput{
AssetType: r.FormValue("assetType"),
AssetCode: r.FormValue("assetCode"),
Version: r.FormValue("version"),
Title: stringPtrOrNil(r.FormValue("title")),
ObjectDir: stringPtrOrNil(r.FormValue("objectDir")),
FileName: header.Filename,
ContentType: header.Header.Get("Content-Type"),
FileSize: written,
Checksum: hex.EncodeToString(hash.Sum(nil)),
TempPath: tmpPath,
Status: r.FormValue("status"),
Metadata: parseMetadataJSON(r.FormValue("metadataJson")),
}
result, err := h.service.UploadAssetFile(r.Context(), input)
if err != nil {
httpx.WriteError(w, err)
return
}
httpx.WriteJSON(w, http.StatusCreated, map[string]any{
"data": result,
"meta": map[string]any{
"uploadedBytes": written,
"checksumSha256": input.Checksum,
},
})
}
func stringPtrOrNil(value string) *string {
if value == "" {
return nil
}
return &value
}
func parseMetadataJSON(raw string) map[string]any {
if raw == "" {
return nil
}
var payload map[string]any
_ = json.Unmarshal([]byte(raw), &payload)
return payload
}

View File

@@ -25,6 +25,15 @@ func (h *AdminProductionHandler) ListPlaces(w http.ResponseWriter, r *http.Reque
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
}
func (h *AdminProductionHandler) ListMapAssets(w http.ResponseWriter, r *http.Request) {
result, err := h.service.ListMapAssets(r.Context(), parseAdminLimit(r))
if err != nil {
httpx.WriteError(w, err)
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
}
func (h *AdminProductionHandler) CreatePlace(w http.ResponseWriter, r *http.Request) {
var req service.CreateAdminPlaceInput
if err := httpx.DecodeJSON(r, &req); err != nil {
@@ -71,6 +80,20 @@ func (h *AdminProductionHandler) GetMapAsset(w http.ResponseWriter, r *http.Requ
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
}
func (h *AdminProductionHandler) UpdateMapAsset(w http.ResponseWriter, r *http.Request) {
var req service.UpdateAdminMapAssetInput
if err := httpx.DecodeJSON(r, &req); err != nil {
httpx.WriteError(w, apperr.New(http.StatusBadRequest, "invalid_json", "invalid request body: "+err.Error()))
return
}
result, err := h.service.UpdateMapAsset(r.Context(), r.PathValue("mapAssetPublicID"), req)
if err != nil {
httpx.WriteError(w, err)
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
}
func (h *AdminProductionHandler) CreateTileRelease(w http.ResponseWriter, r *http.Request) {
var req service.CreateAdminTileReleaseInput
if err := httpx.DecodeJSON(r, &req); err != nil {
@@ -177,6 +200,34 @@ func (h *AdminProductionHandler) CreateRuntimeBinding(w http.ResponseWriter, r *
httpx.WriteJSON(w, http.StatusCreated, map[string]any{"data": result})
}
func (h *AdminProductionHandler) ImportTileRelease(w http.ResponseWriter, r *http.Request) {
var req service.ImportAdminTileReleaseInput
if err := httpx.DecodeJSON(r, &req); err != nil {
httpx.WriteError(w, apperr.New(http.StatusBadRequest, "invalid_json", "invalid request body: "+err.Error()))
return
}
result, err := h.service.ImportTileRelease(r.Context(), req)
if err != nil {
httpx.WriteError(w, err)
return
}
httpx.WriteJSON(w, http.StatusCreated, map[string]any{"data": result})
}
func (h *AdminProductionHandler) ImportCourseSetKMLBatch(w http.ResponseWriter, r *http.Request) {
var req service.ImportAdminCourseSetBatchInput
if err := httpx.DecodeJSON(r, &req); err != nil {
httpx.WriteError(w, apperr.New(http.StatusBadRequest, "invalid_json", "invalid request body: "+err.Error()))
return
}
result, err := h.service.ImportCourseSetKMLBatch(r.Context(), req)
if err != nil {
httpx.WriteError(w, err)
return
}
httpx.WriteJSON(w, http.StatusCreated, map[string]any{"data": result})
}
func (h *AdminProductionHandler) GetRuntimeBinding(w http.ResponseWriter, r *http.Request) {
result, err := h.service.GetRuntimeBinding(r.Context(), r.PathValue("runtimeBindingPublicID"))
if err != nil {

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,41 @@
package handlers
import (
"net/http"
"strconv"
"cmr-backend/internal/httpx"
"cmr-backend/internal/service"
)
type MapExperienceHandler struct {
service *service.MapExperienceService
}
func NewMapExperienceHandler(service *service.MapExperienceService) *MapExperienceHandler {
return &MapExperienceHandler{service: service}
}
func (h *MapExperienceHandler) ListMaps(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.service.ListMaps(r.Context(), service.ListExperienceMapsInput{Limit: limit})
if err != nil {
httpx.WriteError(w, err)
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
}
func (h *MapExperienceHandler) GetMapDetail(w http.ResponseWriter, r *http.Request) {
result, err := h.service.GetMapDetail(r.Context(), r.PathValue("mapAssetPublicID"))
if err != nil {
httpx.WriteError(w, err)
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
}

View File

@@ -0,0 +1,101 @@
package handlers
import (
"net/http"
"cmr-backend/internal/apperr"
"cmr-backend/internal/httpapi/middleware"
"cmr-backend/internal/httpx"
"cmr-backend/internal/service"
)
type OpsAuthHandler struct {
service *service.OpsAuthService
}
func NewOpsAuthHandler(service *service.OpsAuthService) *OpsAuthHandler {
return &OpsAuthHandler{service: service}
}
func (h *OpsAuthHandler) SendSMSCode(w http.ResponseWriter, r *http.Request) {
var req service.OpsSendSMSCodeInput
if err := httpx.DecodeJSON(r, &req); err != nil {
httpx.WriteError(w, apperr.New(http.StatusBadRequest, "invalid_json", "invalid request body"))
return
}
result, err := h.service.SendSMSCode(r.Context(), req)
if err != nil {
httpx.WriteError(w, err)
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
}
func (h *OpsAuthHandler) Register(w http.ResponseWriter, r *http.Request) {
var req service.OpsRegisterInput
if err := httpx.DecodeJSON(r, &req); err != nil {
httpx.WriteError(w, apperr.New(http.StatusBadRequest, "invalid_json", "invalid request body"))
return
}
result, err := h.service.Register(r.Context(), req)
if err != nil {
httpx.WriteError(w, err)
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
}
func (h *OpsAuthHandler) LoginSMS(w http.ResponseWriter, r *http.Request) {
var req service.OpsLoginSMSInput
if err := httpx.DecodeJSON(r, &req); err != nil {
httpx.WriteError(w, apperr.New(http.StatusBadRequest, "invalid_json", "invalid request body"))
return
}
result, err := h.service.LoginSMS(r.Context(), req)
if err != nil {
httpx.WriteError(w, err)
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
}
func (h *OpsAuthHandler) Refresh(w http.ResponseWriter, r *http.Request) {
var req service.OpsRefreshTokenInput
if err := httpx.DecodeJSON(r, &req); err != nil {
httpx.WriteError(w, apperr.New(http.StatusBadRequest, "invalid_json", "invalid request body"))
return
}
result, err := h.service.Refresh(r.Context(), req)
if err != nil {
httpx.WriteError(w, err)
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
}
func (h *OpsAuthHandler) Logout(w http.ResponseWriter, r *http.Request) {
var req service.OpsLogoutInput
if err := httpx.DecodeJSON(r, &req); err != nil {
httpx.WriteError(w, apperr.New(http.StatusBadRequest, "invalid_json", "invalid request body"))
return
}
if err := h.service.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}})
}
func (h *OpsAuthHandler) Me(w http.ResponseWriter, r *http.Request) {
auth := middleware.GetOpsAuthContext(r.Context())
if auth == nil {
httpx.WriteError(w, apperr.New(http.StatusUnauthorized, "unauthorized", "missing ops auth context"))
return
}
result, err := h.service.GetMe(r.Context(), auth.OpsUserID)
if err != nil {
httpx.WriteError(w, err)
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
}

View File

@@ -0,0 +1,25 @@
package handlers
import (
"net/http"
"cmr-backend/internal/httpx"
"cmr-backend/internal/service"
)
type OpsSummaryHandler struct {
service *service.OpsSummaryService
}
func NewOpsSummaryHandler(service *service.OpsSummaryService) *OpsSummaryHandler {
return &OpsSummaryHandler{service: service}
}
func (h *OpsSummaryHandler) GetOverview(w http.ResponseWriter, r *http.Request) {
result, err := h.service.GetOverview(r.Context())
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,78 @@
package handlers
import (
"net/http"
"strconv"
"cmr-backend/internal/apperr"
"cmr-backend/internal/httpx"
"cmr-backend/internal/service"
)
type PublicExperienceHandler struct {
service *service.PublicExperienceService
}
func NewPublicExperienceHandler(service *service.PublicExperienceService) *PublicExperienceHandler {
return &PublicExperienceHandler{service: service}
}
func (h *PublicExperienceHandler) ListMaps(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.service.ListMaps(r.Context(), service.ListExperienceMapsInput{Limit: limit})
if err != nil {
httpx.WriteError(w, err)
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
}
func (h *PublicExperienceHandler) GetMapDetail(w http.ResponseWriter, r *http.Request) {
result, err := h.service.GetMapDetail(r.Context(), r.PathValue("mapAssetPublicID"))
if err != nil {
httpx.WriteError(w, err)
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
}
func (h *PublicExperienceHandler) GetEventDetail(w http.ResponseWriter, r *http.Request) {
result, err := h.service.GetEventDetail(r.Context(), r.PathValue("eventPublicID"))
if err != nil {
httpx.WriteError(w, err)
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
}
func (h *PublicExperienceHandler) GetEventPlay(w http.ResponseWriter, r *http.Request) {
result, err := h.service.GetEventPlay(r.Context(), service.PublicEventPlayInput{
EventPublicID: r.PathValue("eventPublicID"),
})
if err != nil {
httpx.WriteError(w, err)
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": result})
}
func (h *PublicExperienceHandler) Launch(w http.ResponseWriter, r *http.Request) {
var req service.PublicLaunchEventInput
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.service.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,162 @@
package handlers
import (
"context"
"encoding/json"
"fmt"
"net/http"
"sort"
"sync"
"time"
"cmr-backend/internal/apperr"
"cmr-backend/internal/httpx"
)
type RegionOptionsHandler struct {
client *http.Client
mu sync.Mutex
cache []regionProvince
}
type regionProvince struct {
Code string `json:"code"`
Name string `json:"name"`
Cities []regionCity `json:"cities"`
}
type regionCity struct {
Code string `json:"code"`
Name string `json:"name"`
}
type remoteProvince struct {
Code string `json:"code"`
Name string `json:"name"`
}
type remoteCity struct {
Code string `json:"code"`
Name string `json:"name"`
Province string `json:"province"`
}
func NewRegionOptionsHandler() *RegionOptionsHandler {
return &RegionOptionsHandler{
client: &http.Client{Timeout: 12 * time.Second},
}
}
func (h *RegionOptionsHandler) Get(w http.ResponseWriter, r *http.Request) {
items, err := h.load(r.Context())
if err != nil {
httpx.WriteError(w, err)
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"data": items})
}
func (h *RegionOptionsHandler) load(ctx context.Context) ([]regionProvince, error) {
h.mu.Lock()
if len(h.cache) > 0 {
cached := h.cache
h.mu.Unlock()
return cached, nil
}
h.mu.Unlock()
// Data source:
// https://github.com/uiwjs/province-city-china
// Using province + city JSON only, then reducing to the province/city structure
// needed by ops workbench location management.
provinces, err := h.fetchProvinces(ctx, "https://unpkg.com/province-city-china/dist/province.json")
if err != nil {
return nil, err
}
cities, err := h.fetchCities(ctx, "https://unpkg.com/province-city-china/dist/city.json")
if err != nil {
return nil, err
}
cityMap := make(map[string][]regionCity)
for _, item := range cities {
if item.Province == "" || item.Code == "" {
continue
}
fullCode := item.Province + item.Code + "00"
cityMap[item.Province] = append(cityMap[item.Province], regionCity{
Code: fullCode,
Name: item.Name,
})
}
for key := range cityMap {
sort.Slice(cityMap[key], func(i, j int) bool { return cityMap[key][i].Code < cityMap[key][j].Code })
}
items := make([]regionProvince, 0, len(provinces))
for _, item := range provinces {
if len(item.Code) < 2 {
continue
}
provinceCode := item.Code[:2]
province := regionProvince{
Code: item.Code,
Name: item.Name,
}
if entries := cityMap[provinceCode]; len(entries) > 0 {
province.Cities = entries
} else {
// 直辖市 / 特殊地区没有单独的地级市列表时,退化成自身即可。
province.Cities = []regionCity{{
Code: item.Code,
Name: item.Name,
}}
}
items = append(items, province)
}
h.mu.Lock()
h.cache = items
h.mu.Unlock()
return items, nil
}
func (h *RegionOptionsHandler) fetchProvinces(ctx context.Context, url string) ([]remoteProvince, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, apperr.New(http.StatusBadGateway, "region_source_unavailable", "省市数据源不可用")
}
resp, err := h.client.Do(req)
if err != nil {
return nil, apperr.New(http.StatusBadGateway, "region_source_unavailable", "省市数据源不可用")
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, apperr.New(http.StatusBadGateway, "region_source_unavailable", fmt.Sprintf("省级数据拉取失败: %d", resp.StatusCode))
}
var items []remoteProvince
if err := json.NewDecoder(resp.Body).Decode(&items); err != nil {
return nil, apperr.New(http.StatusBadGateway, "region_source_invalid", "省级数据格式无效")
}
return items, nil
}
func (h *RegionOptionsHandler) fetchCities(ctx context.Context, url string) ([]remoteCity, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, apperr.New(http.StatusBadGateway, "region_source_unavailable", "省市数据源不可用")
}
resp, err := h.client.Do(req)
if err != nil {
return nil, apperr.New(http.StatusBadGateway, "region_source_unavailable", "省市数据源不可用")
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, apperr.New(http.StatusBadGateway, "region_source_unavailable", fmt.Sprintf("市级数据拉取失败: %d", resp.StatusCode))
}
var items []remoteCity
if err := json.NewDecoder(resp.Body).Decode(&items); err != nil {
return nil, apperr.New(http.StatusBadGateway, "region_source_invalid", "市级数据格式无效")
}
return items, nil
}

View File

@@ -17,6 +17,7 @@ const authKey authContextKey = "auth"
type AuthContext struct {
UserID string
UserPublicID string
RoleCode string
}
func NewAuthMiddleware(jwtManager *jwtx.Manager) func(http.Handler) http.Handler {
@@ -34,10 +35,15 @@ func NewAuthMiddleware(jwtManager *jwtx.Manager) func(http.Handler) http.Handler
httpx.WriteError(w, apperr.New(http.StatusUnauthorized, "invalid_token", "invalid access token"))
return
}
if claims.ActorType != "" && claims.ActorType != "user" {
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,
RoleCode: claims.RoleCode,
})
next.ServeHTTP(w, r.WithContext(ctx))
})

View File

@@ -0,0 +1,77 @@
package middleware
import (
"context"
"net/http"
"strings"
"cmr-backend/internal/apperr"
"cmr-backend/internal/httpx"
"cmr-backend/internal/platform/jwtx"
)
type opsAuthContextKey string
const opsAuthKey opsAuthContextKey = "ops-auth"
type OpsAuthContext struct {
OpsUserID string
OpsUserPublicID string
RoleCode string
}
func NewOpsAuthMiddleware(jwtManager *jwtx.Manager, appEnv string) func(http.Handler) http.Handler {
devContext := func(r *http.Request) *http.Request {
ctx := context.WithValue(r.Context(), opsAuthKey, &OpsAuthContext{
OpsUserID: "dev-ops-user",
OpsUserPublicID: "ops_dev_console",
RoleCode: "owner",
})
return r.WithContext(ctx)
}
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 ") {
if appEnv != "production" {
next.ServeHTTP(w, devContext(r))
return
}
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 {
if appEnv != "production" {
next.ServeHTTP(w, devContext(r))
return
}
httpx.WriteError(w, apperr.New(http.StatusUnauthorized, "invalid_token", "invalid access token"))
return
}
if claims.ActorType != "ops" {
if appEnv != "production" {
next.ServeHTTP(w, devContext(r))
return
}
httpx.WriteError(w, apperr.New(http.StatusUnauthorized, "invalid_token", "invalid ops access token"))
return
}
ctx := context.WithValue(r.Context(), opsAuthKey, &OpsAuthContext{
OpsUserID: claims.UserID,
OpsUserPublicID: claims.UserPublicID,
RoleCode: claims.RoleCode,
})
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
func GetOpsAuthContext(ctx context.Context) *OpsAuthContext {
auth, _ := ctx.Value(opsAuthKey).(*OpsAuthContext)
return auth
}

View File

@@ -13,16 +13,21 @@ func NewRouter(
appEnv string,
jwtManager *jwtx.Manager,
authService *service.AuthService,
opsAuthService *service.OpsAuthService,
opsSummaryService *service.OpsSummaryService,
entryService *service.EntryService,
entryHomeService *service.EntryHomeService,
adminAssetService *service.AdminAssetService,
adminResourceService *service.AdminResourceService,
adminProductionService *service.AdminProductionService,
adminEventService *service.AdminEventService,
adminPipelineService *service.AdminPipelineService,
eventService *service.EventService,
eventPlayService *service.EventPlayService,
publicExperienceService *service.PublicExperienceService,
configService *service.ConfigService,
homeService *service.HomeService,
mapExperienceService *service.MapExperienceService,
profileService *service.ProfileService,
resultService *service.ResultService,
sessionService *service.SessionService,
@@ -33,27 +38,43 @@ func NewRouter(
healthHandler := handlers.NewHealthHandler()
authHandler := handlers.NewAuthHandler(authService)
opsAuthHandler := handlers.NewOpsAuthHandler(opsAuthService)
opsSummaryHandler := handlers.NewOpsSummaryHandler(opsSummaryService)
regionOptionsHandler := handlers.NewRegionOptionsHandler()
entryHandler := handlers.NewEntryHandler(entryService)
entryHomeHandler := handlers.NewEntryHomeHandler(entryHomeService)
adminAssetHandler := handlers.NewAdminAssetHandler(adminAssetService)
adminResourceHandler := handlers.NewAdminResourceHandler(adminResourceService)
adminProductionHandler := handlers.NewAdminProductionHandler(adminProductionService)
adminEventHandler := handlers.NewAdminEventHandler(adminEventService)
adminPipelineHandler := handlers.NewAdminPipelineHandler(adminPipelineService)
eventHandler := handlers.NewEventHandler(eventService)
eventPlayHandler := handlers.NewEventPlayHandler(eventPlayService)
publicExperienceHandler := handlers.NewPublicExperienceHandler(publicExperienceService)
configHandler := handlers.NewConfigHandler(configService)
homeHandler := handlers.NewHomeHandler(homeService)
mapExperienceHandler := handlers.NewMapExperienceHandler(mapExperienceService)
profileHandler := handlers.NewProfileHandler(profileService)
resultHandler := handlers.NewResultHandler(resultService)
sessionHandler := handlers.NewSessionHandler(sessionService)
devHandler := handlers.NewDevHandler(devService)
opsWorkbenchHandler := handlers.NewOpsWorkbenchHandler()
meHandler := handlers.NewMeHandler(meService)
authMiddleware := middleware.NewAuthMiddleware(jwtManager)
opsAuthMiddleware := middleware.NewOpsAuthMiddleware(jwtManager, appEnv)
mux.HandleFunc("GET /healthz", healthHandler.Get)
mux.HandleFunc("GET /home", homeHandler.GetHome)
mux.HandleFunc("GET /cards", homeHandler.GetCards)
mux.HandleFunc("GET /experience-maps", mapExperienceHandler.ListMaps)
mux.HandleFunc("GET /experience-maps/{mapAssetPublicID}", mapExperienceHandler.GetMapDetail)
mux.HandleFunc("GET /public/experience-maps", publicExperienceHandler.ListMaps)
mux.HandleFunc("GET /public/experience-maps/{mapAssetPublicID}", publicExperienceHandler.GetMapDetail)
mux.HandleFunc("GET /entry/resolve", entryHandler.Resolve)
mux.Handle("GET /admin/assets", authMiddleware(http.HandlerFunc(adminAssetHandler.ListAssets)))
mux.Handle("POST /admin/assets/register-link", authMiddleware(http.HandlerFunc(adminAssetHandler.RegisterLink)))
mux.Handle("POST /admin/assets/upload", authMiddleware(http.HandlerFunc(adminAssetHandler.UploadFile)))
mux.Handle("GET /admin/assets/{assetPublicID}", authMiddleware(http.HandlerFunc(adminAssetHandler.GetAsset)))
mux.Handle("GET /admin/maps", authMiddleware(http.HandlerFunc(adminResourceHandler.ListMaps)))
mux.Handle("POST /admin/maps", authMiddleware(http.HandlerFunc(adminResourceHandler.CreateMap)))
mux.Handle("GET /admin/maps/{mapPublicID}", authMiddleware(http.HandlerFunc(adminResourceHandler.GetMap)))
@@ -61,8 +82,10 @@ func NewRouter(
mux.Handle("GET /admin/places", authMiddleware(http.HandlerFunc(adminProductionHandler.ListPlaces)))
mux.Handle("POST /admin/places", authMiddleware(http.HandlerFunc(adminProductionHandler.CreatePlace)))
mux.Handle("GET /admin/places/{placePublicID}", authMiddleware(http.HandlerFunc(adminProductionHandler.GetPlace)))
mux.Handle("GET /admin/map-assets", authMiddleware(http.HandlerFunc(adminProductionHandler.ListMapAssets)))
mux.Handle("POST /admin/places/{placePublicID}/map-assets", authMiddleware(http.HandlerFunc(adminProductionHandler.CreateMapAsset)))
mux.Handle("GET /admin/map-assets/{mapAssetPublicID}", authMiddleware(http.HandlerFunc(adminProductionHandler.GetMapAsset)))
mux.Handle("PUT /admin/map-assets/{mapAssetPublicID}", authMiddleware(http.HandlerFunc(adminProductionHandler.UpdateMapAsset)))
mux.Handle("POST /admin/map-assets/{mapAssetPublicID}/tile-releases", authMiddleware(http.HandlerFunc(adminProductionHandler.CreateTileRelease)))
mux.Handle("POST /admin/map-assets/{mapAssetPublicID}/course-sets", authMiddleware(http.HandlerFunc(adminProductionHandler.CreateCourseSet)))
mux.Handle("GET /admin/course-sources", authMiddleware(http.HandlerFunc(adminProductionHandler.ListCourseSources)))
@@ -73,6 +96,8 @@ func NewRouter(
mux.Handle("GET /admin/runtime-bindings", authMiddleware(http.HandlerFunc(adminProductionHandler.ListRuntimeBindings)))
mux.Handle("POST /admin/runtime-bindings", authMiddleware(http.HandlerFunc(adminProductionHandler.CreateRuntimeBinding)))
mux.Handle("GET /admin/runtime-bindings/{runtimeBindingPublicID}", authMiddleware(http.HandlerFunc(adminProductionHandler.GetRuntimeBinding)))
mux.Handle("POST /admin/ops/tile-releases/import", authMiddleware(http.HandlerFunc(adminProductionHandler.ImportTileRelease)))
mux.Handle("POST /admin/ops/course-sets/import-kml-batch", authMiddleware(http.HandlerFunc(adminProductionHandler.ImportCourseSetKMLBatch)))
mux.Handle("GET /admin/playfields", authMiddleware(http.HandlerFunc(adminResourceHandler.ListPlayfields)))
mux.Handle("POST /admin/playfields", authMiddleware(http.HandlerFunc(adminResourceHandler.CreatePlayfield)))
mux.Handle("GET /admin/playfields/{playfieldPublicID}", authMiddleware(http.HandlerFunc(adminResourceHandler.GetPlayfield)))
@@ -104,11 +129,13 @@ func NewRouter(
mux.Handle("POST /admin/events/{eventPublicID}/rollback", authMiddleware(http.HandlerFunc(adminPipelineHandler.RollbackRelease)))
if appEnv != "production" {
mux.HandleFunc("GET /dev/workbench", devHandler.Workbench)
mux.HandleFunc("GET /admin/ops-workbench", opsWorkbenchHandler.Get)
mux.HandleFunc("POST /dev/bootstrap-demo", devHandler.BootstrapDemo)
mux.HandleFunc("POST /dev/client-logs", devHandler.CreateClientLog)
mux.HandleFunc("GET /dev/client-logs", devHandler.ListClientLogs)
mux.HandleFunc("DELETE /dev/client-logs", devHandler.ClearClientLogs)
mux.HandleFunc("GET /dev/manifest-summary", devHandler.ManifestSummary)
mux.HandleFunc("GET /dev/demo-assets/manifests/{demoKey}", devHandler.DemoGameManifest)
mux.HandleFunc("GET /dev/demo-assets/presentations/{demoKey}", devHandler.DemoPresentationSchema)
mux.HandleFunc("GET /dev/demo-assets/content-manifests/{demoKey}", devHandler.DemoContentManifest)
mux.HandleFunc("GET /dev/config/local-files", configHandler.ListLocalFiles)
@@ -119,6 +146,9 @@ func NewRouter(
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.HandleFunc("GET /public/events/{eventPublicID}", publicExperienceHandler.GetEventDetail)
mux.HandleFunc("GET /public/events/{eventPublicID}/play", publicExperienceHandler.GetEventPlay)
mux.HandleFunc("POST /public/events/{eventPublicID}/launch", publicExperienceHandler.Launch)
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)))
@@ -131,12 +161,50 @@ func NewRouter(
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.HandleFunc("POST /ops/auth/sms/send", opsAuthHandler.SendSMSCode)
mux.HandleFunc("POST /ops/auth/register", opsAuthHandler.Register)
mux.HandleFunc("POST /ops/auth/login/sms", opsAuthHandler.LoginSMS)
mux.HandleFunc("POST /ops/auth/refresh", opsAuthHandler.Refresh)
mux.HandleFunc("POST /ops/auth/logout", opsAuthHandler.Logout)
mux.Handle("GET /ops/me", opsAuthMiddleware(http.HandlerFunc(opsAuthHandler.Me)))
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)))
mux.Handle("GET /ops/admin/summary", opsAuthMiddleware(http.HandlerFunc(opsSummaryHandler.GetOverview)))
mux.Handle("GET /ops/admin/region-options", opsAuthMiddleware(http.HandlerFunc(regionOptionsHandler.Get)))
mux.Handle("GET /ops/admin/assets", opsAuthMiddleware(http.HandlerFunc(adminAssetHandler.ListAssets)))
mux.Handle("POST /ops/admin/assets/register-link", opsAuthMiddleware(http.HandlerFunc(adminAssetHandler.RegisterLink)))
mux.Handle("POST /ops/admin/assets/upload", opsAuthMiddleware(http.HandlerFunc(adminAssetHandler.UploadFile)))
mux.Handle("GET /ops/admin/assets/{assetPublicID}", opsAuthMiddleware(http.HandlerFunc(adminAssetHandler.GetAsset)))
mux.Handle("GET /ops/admin/places", opsAuthMiddleware(http.HandlerFunc(adminProductionHandler.ListPlaces)))
mux.Handle("POST /ops/admin/places", opsAuthMiddleware(http.HandlerFunc(adminProductionHandler.CreatePlace)))
mux.Handle("GET /ops/admin/places/{placePublicID}", opsAuthMiddleware(http.HandlerFunc(adminProductionHandler.GetPlace)))
mux.Handle("GET /ops/admin/map-assets", opsAuthMiddleware(http.HandlerFunc(adminProductionHandler.ListMapAssets)))
mux.Handle("POST /ops/admin/places/{placePublicID}/map-assets", opsAuthMiddleware(http.HandlerFunc(adminProductionHandler.CreateMapAsset)))
mux.Handle("GET /ops/admin/map-assets/{mapAssetPublicID}", opsAuthMiddleware(http.HandlerFunc(adminProductionHandler.GetMapAsset)))
mux.Handle("PUT /ops/admin/map-assets/{mapAssetPublicID}", opsAuthMiddleware(http.HandlerFunc(adminProductionHandler.UpdateMapAsset)))
mux.Handle("POST /ops/admin/map-assets/{mapAssetPublicID}/tile-releases", opsAuthMiddleware(http.HandlerFunc(adminProductionHandler.CreateTileRelease)))
mux.Handle("POST /ops/admin/ops/tile-releases/import", opsAuthMiddleware(http.HandlerFunc(adminProductionHandler.ImportTileRelease)))
mux.Handle("GET /ops/admin/course-sources", opsAuthMiddleware(http.HandlerFunc(adminProductionHandler.ListCourseSources)))
mux.Handle("GET /ops/admin/course-sources/{sourcePublicID}", opsAuthMiddleware(http.HandlerFunc(adminProductionHandler.GetCourseSource)))
mux.Handle("GET /ops/admin/course-sets/{courseSetPublicID}", opsAuthMiddleware(http.HandlerFunc(adminProductionHandler.GetCourseSet)))
mux.Handle("POST /ops/admin/ops/course-sets/import-kml-batch", opsAuthMiddleware(http.HandlerFunc(adminProductionHandler.ImportCourseSetKMLBatch)))
mux.Handle("GET /ops/admin/events", opsAuthMiddleware(http.HandlerFunc(adminEventHandler.ListEvents)))
mux.Handle("POST /ops/admin/events", opsAuthMiddleware(http.HandlerFunc(adminEventHandler.CreateEvent)))
mux.Handle("GET /ops/admin/events/{eventPublicID}", opsAuthMiddleware(http.HandlerFunc(adminEventHandler.GetEvent)))
mux.Handle("PUT /ops/admin/events/{eventPublicID}", opsAuthMiddleware(http.HandlerFunc(adminEventHandler.UpdateEvent)))
mux.Handle("POST /ops/admin/events/{eventPublicID}/presentations/import", opsAuthMiddleware(http.HandlerFunc(adminEventHandler.ImportPresentation)))
mux.Handle("POST /ops/admin/events/{eventPublicID}/content-bundles/import", opsAuthMiddleware(http.HandlerFunc(adminEventHandler.ImportContentBundle)))
mux.Handle("POST /ops/admin/events/{eventPublicID}/defaults", opsAuthMiddleware(http.HandlerFunc(adminEventHandler.UpdateEventDefaults)))
mux.Handle("GET /ops/admin/events/{eventPublicID}/pipeline", opsAuthMiddleware(http.HandlerFunc(adminPipelineHandler.GetEventPipeline)))
mux.Handle("POST /ops/admin/sources/{sourceID}/build", opsAuthMiddleware(http.HandlerFunc(adminPipelineHandler.BuildSource)))
mux.Handle("GET /ops/admin/builds/{buildID}", opsAuthMiddleware(http.HandlerFunc(adminPipelineHandler.GetBuild)))
mux.Handle("POST /ops/admin/builds/{buildID}/publish", opsAuthMiddleware(http.HandlerFunc(adminPipelineHandler.PublishBuild)))
mux.Handle("GET /ops/admin/releases/{releasePublicID}", opsAuthMiddleware(http.HandlerFunc(adminPipelineHandler.GetRelease)))
mux.Handle("POST /ops/admin/events/{eventPublicID}/rollback", opsAuthMiddleware(http.HandlerFunc(adminPipelineHandler.RollbackRelease)))
return mux
}