110 lines
3.2 KiB
Go
110 lines
3.2 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"chess/internal/server/core"
|
|
"chess/internal/server/storage"
|
|
)
|
|
|
|
// GetGameHistory returns a durable replay even after the live game has been
|
|
// evicted from memory or the server has restarted.
|
|
func (s *Service) GetGameHistory(gameID string) (*core.GameHistoryResponse, error) {
|
|
if s.store == nil {
|
|
return nil, ErrStorageDisabled
|
|
}
|
|
record, moves, err := s.store.GetGameHistory(gameID)
|
|
if err != nil {
|
|
if storage.IsGameNotFound(err) {
|
|
return nil, fmt.Errorf("%w: %s", ErrGameNotFound, gameID)
|
|
}
|
|
if isStorageUnavailable(err) {
|
|
return nil, fmt.Errorf("%w: %v", ErrStorageUnavailable, err)
|
|
}
|
|
return nil, fmt.Errorf("get game history: %w", err)
|
|
}
|
|
|
|
history := &core.GameHistoryResponse{
|
|
GameID: record.GameID,
|
|
InitialFEN: record.InitialFEN,
|
|
Result: record.Result,
|
|
StartTimeUTC: record.StartTimeUTC,
|
|
EndTimeUTC: record.EndTimeUTC,
|
|
Players: playersResponse(*record),
|
|
Moves: make([]core.HistoryMove, 0, len(moves)),
|
|
}
|
|
for _, move := range moves {
|
|
history.Moves = append(history.Moves, core.HistoryMove{
|
|
MoveNumber: move.MoveNumber,
|
|
MoveUCI: move.MoveUCI,
|
|
FENAfterMove: move.FENAfterMove,
|
|
PlayerColor: move.PlayerColor,
|
|
MoveTimeUTC: move.MoveTimeUTC,
|
|
})
|
|
}
|
|
return history, nil
|
|
}
|
|
|
|
// GetUserGames returns a bounded page of games associated either at creation
|
|
// or by a later slot claim.
|
|
func (s *Service) GetUserGames(userID string, limit, offset int) (*core.GameListResponse, error) {
|
|
if s.store == nil {
|
|
return nil, ErrStorageDisabled
|
|
}
|
|
records, err := s.store.QueryGamesForUser(userID, limit+1, offset)
|
|
if err != nil {
|
|
if isStorageUnavailable(err) {
|
|
return nil, fmt.Errorf("%w: %v", ErrStorageUnavailable, err)
|
|
}
|
|
return nil, fmt.Errorf("get user games: %w", err)
|
|
}
|
|
|
|
hasNext := len(records) > limit
|
|
if hasNext {
|
|
records = records[:limit]
|
|
}
|
|
response := &core.GameListResponse{
|
|
Games: make([]core.GameSummary, 0, len(records)),
|
|
Limit: limit,
|
|
Offset: offset,
|
|
}
|
|
if hasNext {
|
|
next := offset + limit
|
|
response.NextOffset = &next
|
|
}
|
|
for _, record := range records {
|
|
response.Games = append(response.Games, core.GameSummary{
|
|
GameID: record.GameID,
|
|
InitialFEN: record.InitialFEN,
|
|
Result: record.Result,
|
|
StartTimeUTC: record.StartTimeUTC,
|
|
EndTimeUTC: record.EndTimeUTC,
|
|
MoveCount: record.MoveCount,
|
|
Players: playersResponse(record.GameRecord),
|
|
})
|
|
}
|
|
return response, nil
|
|
}
|
|
|
|
func isStorageUnavailable(err error) bool {
|
|
return errors.Is(err, storage.ErrStorageDegraded) ||
|
|
errors.Is(err, storage.ErrStoreClosed) ||
|
|
errors.Is(err, storage.ErrWriteQueueFull) ||
|
|
errors.Is(err, context.DeadlineExceeded)
|
|
}
|
|
|
|
func playersResponse(record storage.GameRecord) core.PlayersResponse {
|
|
return core.PlayersResponse{
|
|
White: &core.Player{
|
|
ID: record.WhitePlayerID, Color: core.ColorWhite, Type: core.PlayerType(record.WhiteType),
|
|
Level: record.WhiteLevel, SearchTime: record.WhiteSearchTime, ClaimedBy: record.WhiteClaimedBy,
|
|
},
|
|
Black: &core.Player{
|
|
ID: record.BlackPlayerID, Color: core.ColorBlack, Type: core.PlayerType(record.BlackType),
|
|
Level: record.BlackLevel, SearchTime: record.BlackSearchTime, ClaimedBy: record.BlackClaimedBy,
|
|
},
|
|
}
|
|
}
|