| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189 |
- package service
- import (
- "crypto/rand"
- "encoding/json"
- "fmt"
- "math/big"
- "net/http"
- "strings"
- "cmr-backend/internal/apperr"
- )
- const (
- AssignmentModeManual = "manual"
- AssignmentModeRandom = "random"
- AssignmentModeServerAssigned = "server-assigned"
- )
- type CourseVariantView struct {
- ID string `json:"id"`
- Name string `json:"name"`
- Description *string `json:"description,omitempty"`
- RouteCode *string `json:"routeCode,omitempty"`
- Selectable bool `json:"selectable"`
- }
- type VariantBindingView struct {
- ID string `json:"id"`
- Name string `json:"name"`
- RouteCode *string `json:"routeCode,omitempty"`
- AssignmentMode string `json:"assignmentMode"`
- }
- type VariantPlan struct {
- AssignmentMode *string
- CourseVariants []CourseVariantView
- }
- func resolveVariantPlan(payloadJSON *string) VariantPlan {
- if payloadJSON == nil || strings.TrimSpace(*payloadJSON) == "" {
- return VariantPlan{}
- }
- var payload map[string]any
- if err := json.Unmarshal([]byte(*payloadJSON), &payload); err != nil {
- return VariantPlan{}
- }
- play, _ := payload["play"].(map[string]any)
- if len(play) == 0 {
- return VariantPlan{}
- }
- result := VariantPlan{}
- if rawMode, ok := play["assignmentMode"].(string); ok {
- if normalized := normalizeAssignmentMode(rawMode); normalized != nil {
- result.AssignmentMode = normalized
- }
- }
- rawVariants, _ := play["courseVariants"].([]any)
- if len(rawVariants) == 0 {
- return result
- }
- for _, raw := range rawVariants {
- item, ok := raw.(map[string]any)
- if !ok {
- continue
- }
- id, _ := item["id"].(string)
- name, _ := item["name"].(string)
- id = strings.TrimSpace(id)
- name = strings.TrimSpace(name)
- if id == "" || name == "" {
- continue
- }
- var description *string
- if value, ok := item["description"].(string); ok && strings.TrimSpace(value) != "" {
- trimmed := strings.TrimSpace(value)
- description = &trimmed
- }
- var routeCode *string
- if value, ok := item["routeCode"].(string); ok && strings.TrimSpace(value) != "" {
- trimmed := strings.TrimSpace(value)
- routeCode = &trimmed
- }
- selectable := true
- if value, ok := item["selectable"].(bool); ok {
- selectable = value
- }
- result.CourseVariants = append(result.CourseVariants, CourseVariantView{
- ID: id,
- Name: name,
- Description: description,
- RouteCode: routeCode,
- Selectable: selectable,
- })
- }
- return result
- }
- func resolveLaunchVariant(plan VariantPlan, requestedVariantID string) (*VariantBindingView, error) {
- requestedVariantID = strings.TrimSpace(requestedVariantID)
- if len(plan.CourseVariants) == 0 {
- return nil, nil
- }
- mode := AssignmentModeManual
- if plan.AssignmentMode != nil {
- mode = *plan.AssignmentMode
- }
- if requestedVariantID != "" {
- for _, item := range plan.CourseVariants {
- if item.ID == requestedVariantID {
- if !item.Selectable && mode == AssignmentModeManual {
- return nil, apperr.New(http.StatusBadRequest, "variant_not_selectable", "requested variant is not selectable")
- }
- return &VariantBindingView{
- ID: item.ID,
- Name: item.Name,
- RouteCode: item.RouteCode,
- AssignmentMode: mode,
- }, nil
- }
- }
- return nil, apperr.New(http.StatusBadRequest, "variant_not_found", "requested variant does not exist")
- }
- selected, err := selectDefaultVariant(plan.CourseVariants, mode)
- if err != nil {
- return nil, err
- }
- return &VariantBindingView{
- ID: selected.ID,
- Name: selected.Name,
- RouteCode: selected.RouteCode,
- AssignmentMode: mode,
- }, nil
- }
- func normalizeAssignmentMode(value string) *string {
- switch strings.TrimSpace(value) {
- case AssignmentModeManual:
- mode := AssignmentModeManual
- return &mode
- case AssignmentModeRandom:
- mode := AssignmentModeRandom
- return &mode
- case AssignmentModeServerAssigned:
- mode := AssignmentModeServerAssigned
- return &mode
- default:
- return nil
- }
- }
- func selectDefaultVariant(items []CourseVariantView, mode string) (*CourseVariantView, error) {
- candidates := make([]CourseVariantView, 0, len(items))
- for _, item := range items {
- if item.Selectable {
- candidates = append(candidates, item)
- }
- }
- if len(candidates) == 0 {
- candidates = append(candidates, items...)
- }
- if len(candidates) == 0 {
- return nil, apperr.New(http.StatusBadRequest, "variant_not_found", "course variants are empty")
- }
- switch mode {
- case AssignmentModeRandom:
- index, err := rand.Int(rand.Reader, big.NewInt(int64(len(candidates))))
- if err != nil {
- return nil, apperr.New(http.StatusInternalServerError, "variant_select_failed", fmt.Sprintf("failed to select random variant: %v", err))
- }
- selected := candidates[int(index.Int64())]
- return &selected, nil
- case AssignmentModeServerAssigned, AssignmentModeManual:
- fallthrough
- default:
- selected := candidates[0]
- return &selected, nil
- }
- }
|