Add backend foundation and config-driven workbench
This commit is contained in:
94
backend/internal/service/result_service.go
Normal file
94
backend/internal/service/result_service.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"cmr-backend/internal/apperr"
|
||||
"cmr-backend/internal/store/postgres"
|
||||
)
|
||||
|
||||
type ResultService struct {
|
||||
store *postgres.Store
|
||||
}
|
||||
|
||||
type SessionResultView struct {
|
||||
Session EntrySessionSummary `json:"session"`
|
||||
Result ResultSummaryPayload `json:"result"`
|
||||
}
|
||||
|
||||
type ResultSummaryPayload struct {
|
||||
Status string `json:"status"`
|
||||
FinalDurationSec *int `json:"finalDurationSec,omitempty"`
|
||||
FinalScore *int `json:"finalScore,omitempty"`
|
||||
CompletedControls *int `json:"completedControls,omitempty"`
|
||||
TotalControls *int `json:"totalControls,omitempty"`
|
||||
DistanceMeters *float64 `json:"distanceMeters,omitempty"`
|
||||
AverageSpeedKmh *float64 `json:"averageSpeedKmh,omitempty"`
|
||||
MaxHeartRateBpm *int `json:"maxHeartRateBpm,omitempty"`
|
||||
Summary map[string]any `json:"summary,omitempty"`
|
||||
}
|
||||
|
||||
func NewResultService(store *postgres.Store) *ResultService {
|
||||
return &ResultService{store: store}
|
||||
}
|
||||
|
||||
func (s *ResultService) GetSessionResult(ctx context.Context, sessionPublicID, userID string) (*SessionResultView, error) {
|
||||
record, err := s.store.GetSessionResultByPublicID(ctx, sessionPublicID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if record == nil {
|
||||
return nil, apperr.New(http.StatusNotFound, "session_not_found", "session not found")
|
||||
}
|
||||
if userID != "" && record.UserID != userID {
|
||||
return nil, apperr.New(http.StatusForbidden, "session_forbidden", "session does not belong to current user")
|
||||
}
|
||||
return buildSessionResultView(record), nil
|
||||
}
|
||||
|
||||
func (s *ResultService) ListMyResults(ctx context.Context, userID string, limit int) ([]SessionResultView, error) {
|
||||
if userID == "" {
|
||||
return nil, apperr.New(http.StatusUnauthorized, "unauthorized", "user is required")
|
||||
}
|
||||
|
||||
records, err := s.store.ListSessionResultsByUserID(ctx, userID, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results := make([]SessionResultView, 0, len(records))
|
||||
for i := range records {
|
||||
results = append(results, *buildSessionResultView(&records[i]))
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func buildSessionResultView(record *postgres.SessionResultRecord) *SessionResultView {
|
||||
view := &SessionResultView{
|
||||
Session: buildEntrySessionSummary(&record.Session),
|
||||
Result: ResultSummaryPayload{
|
||||
Status: record.Status,
|
||||
},
|
||||
}
|
||||
|
||||
if record.Result != nil {
|
||||
view.Result.Status = record.Result.ResultStatus
|
||||
view.Result.FinalDurationSec = record.Result.FinalDurationSec
|
||||
view.Result.FinalScore = record.Result.FinalScore
|
||||
view.Result.CompletedControls = record.Result.CompletedControls
|
||||
view.Result.TotalControls = record.Result.TotalControls
|
||||
view.Result.DistanceMeters = record.Result.DistanceMeters
|
||||
view.Result.AverageSpeedKmh = record.Result.AverageSpeedKmh
|
||||
view.Result.MaxHeartRateBpm = record.Result.MaxHeartRateBpm
|
||||
if record.Result.SummaryJSON != "" {
|
||||
summary := map[string]any{}
|
||||
if err := json.Unmarshal([]byte(record.Result.SummaryJSON), &summary); err == nil && len(summary) > 0 {
|
||||
view.Result.Summary = summary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return view
|
||||
}
|
||||
Reference in New Issue
Block a user