// Package server exposes the scoreboard Store over gRPC and HTTP/JSON. package server import ( "context" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" pb "git.mkz.me/mycroft/root-me-api/gen/scoreboard/v1" "git.mkz.me/mycroft/root-me-api/internal/scoreboard" ) // GRPCServer implements pb.ScoreboardServiceServer over a Store. type GRPCServer struct { pb.UnimplementedScoreboardServiceServer Store *scoreboard.Store } // NewGRPC returns a gRPC server backed by the given store. func NewGRPC(store *scoreboard.Store) *GRPCServer { return &GRPCServer{Store: store} } func (s *GRPCServer) ListScoreboard(_ context.Context, req *pb.ListScoreboardRequest) (*pb.ListScoreboardResponse, error) { if !s.Store.Ready() { return nil, status.Error(codes.Unavailable, "scoreboard not loaded yet") } entries, fetchedAt := s.Store.List(int(req.GetOffset()), int(req.GetLimit())) resp := &pb.ListScoreboardResponse{ Total: int32(s.Store.Len()), FetchedAt: fetchedAt.Unix(), } resp.Entries = make([]*pb.Entry, len(entries)) for i, e := range entries { resp.Entries[i] = toProto(e) } return resp, nil } func (s *GRPCServer) GetByRank(_ context.Context, req *pb.GetByRankRequest) (*pb.Entry, error) { if !s.Store.Ready() { return nil, status.Error(codes.Unavailable, "scoreboard not loaded yet") } e, ok := s.Store.ByRank(int(req.GetRank())) if !ok { return nil, status.Errorf(codes.NotFound, "no entry at rank %d", req.GetRank()) } return toProto(e), nil } func (s *GRPCServer) GetByUsername(_ context.Context, req *pb.GetByUsernameRequest) (*pb.Entry, error) { if !s.Store.Ready() { return nil, status.Error(codes.Unavailable, "scoreboard not loaded yet") } if req.GetUsername() == "" { return nil, status.Error(codes.InvalidArgument, "username is required") } e, ok := s.Store.ByUsername(req.GetUsername()) if !ok { return nil, status.Errorf(codes.NotFound, "no entry for user %q", req.GetUsername()) } return toProto(e), nil } func toProto(e scoreboard.Entry) *pb.Entry { return &pb.Entry{ Rank: int32(e.Rank), Username: e.Username, ProfilePath: e.ProfilePath, Country: e.Country, Grade: e.Grade, Score: int32(e.Score), } }