package services import ( "context" "encoding/base64" "encoding/json" "evening_detective/internal/models" "evening_detective/internal/modules/password" "evening_detective/internal/services/story_service" "evening_detective/proto" "fmt" "net" "net/url" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" ) type Services struct { repository *Repository storyService *story_service.StoryService } func NewServices( repository *Repository, storyService *story_service.StoryService, ) *Services { return &Services{ repository: repository, storyService: storyService, } } func (s *Services) GiveApplications(ctx context.Context, req *proto.GiveApplicationsReq) (*proto.GiveApplicationsRsp, error) { applications := mapProtoApplicationsToApplications(req.Applications) if err := s.repository.GiveApplications(ctx, req.TeamId, applications); err != nil { return nil, status.Errorf(codes.Internal, err.Error()) } return &proto.GiveApplicationsRsp{}, nil } func (s *Services) GetGame(ctx context.Context, _ *proto.GetGameReq) (*proto.GetGameRsp, error) { state, err := s.repository.GetGame(ctx) if err != nil { return nil, status.Errorf(codes.Internal, err.Error()) } return &proto.GetGameRsp{State: state}, nil } func (s *Services) GameStart(ctx context.Context, _ *proto.GameStartReq) (*proto.GameStartRsp, error) { if err := s.repository.GameUpdateState(ctx, "RUN"); err != nil { return nil, status.Errorf(codes.Internal, err.Error()) } return &proto.GameStartRsp{}, nil } func (s *Services) GameStop(ctx context.Context, req *proto.GameStopReq) (*proto.GameStopRsp, error) { if err := s.repository.GameUpdateState(ctx, "STOP"); err != nil { return nil, status.Errorf(codes.Internal, err.Error()) } return &proto.GameStopRsp{}, nil } func (s *Services) AddAction(ctx context.Context, req *proto.AddActionReq) (*proto.AddActionRsp, error) { team, err := s.getTeam(ctx) if err != nil { return nil, err } place := s.storyService.GetPlace(req.Place) actions := []*models.Action{ { Place: place.Code, TeamID: team.ID, Applications: mapStoryApplicationsToApplications(place.Applications), }, } if err := s.repository.AddActions(ctx, team.ID, actions); err != nil { return nil, status.Errorf(codes.Internal, err.Error()) } if err := s.repository.AddApplications(ctx, actions); err != nil { return nil, status.Errorf(codes.Internal, err.Error()) } log(team, "add action", actions) return &proto.AddActionRsp{}, nil } func (s *Services) GetTeam(ctx context.Context, req *proto.GetTeamReq) (*proto.GetTeamRsp, error) { team, err := s.getTeam(ctx) if err != nil { return nil, err } actions, err := s.repository.GetActions(ctx, team.ID) if err != nil { return nil, status.Errorf(codes.Internal, err.Error()) } res := make([]*proto.Action, 0, len(actions)) for _, action := range actions { newAction := mapActionToProtoAction(action) place := s.storyService.GetPlace(action.Place) newAction.Text = place.Text newAction.Name = place.Name newAction.Applications = make([]*proto.Application, 0, len(place.Applications)) for _, application := range place.Applications { newAction.Applications = append(newAction.Applications, mapStoryApplicationToProtoApplication(application)) } res = append(res, newAction) } return &proto.GetTeamRsp{ Name: team.Name, Actions: res, }, err } func (s *Services) GetTeamsCSV(ctx context.Context, req *proto.GetTeamsCSVReq) (*proto.GetTeamsCSVRsp, error) { panic("unimplemented") } func (s *Services) GetTeams(ctx context.Context, _ *proto.GetTeamsReq) (*proto.GetTeamsRsp, error) { teams, err := s.repository.GetTeams(ctx) if err != nil { return nil, status.Errorf(codes.Internal, err.Error()) } res := make([]*proto.TeamAdvanced, 0, len(teams)) for _, team := range teams { newTeam := mapTeamsToTeamAdvanced(team) actions, err := s.repository.GetActions(ctx, team.ID) if err != nil { return nil, err } newTeam.Url, err = getTeamUrl(team) if err != nil { return nil, err } newTeam.SpendTime = int64(len(actions)) applications, err := s.repository.GetApplications(ctx, team.ID, "NEW") if err != nil { return nil, err } newTeam.Applications = mapApplicationsToProtoApplications(applications) res = append(res, newTeam) } return &proto.GetTeamsRsp{Teams: res}, err } func (s *Services) AddTeams(ctx context.Context, req *proto.AddTeamsReq) (*proto.AddTeamsRsp, error) { inTeams := make([]*models.Team, 0, len(req.Teams)) for _, team := range req.Teams { t := mapProtoTeamsToTeam(team) t.Password = password.GenPass(8) inTeams = append(inTeams, t) } teams, err := s.repository.AddTeams(ctx, inTeams) if err != nil { return nil, status.Errorf(codes.Internal, err.Error()) } res := make([]*proto.TeamFull, 0, len(teams)) for _, team := range teams { res = append(res, mapTeamsToTeamFull(team)) } return &proto.AddTeamsRsp{Teams: res}, err } func (s *Services) getTeam(ctx context.Context) (*models.Team, error) { md, ok := metadata.FromIncomingContext(ctx) if !ok { return nil, status.Errorf(codes.Unauthenticated, "error creds") } teamIdArr, ok := md["team-id"] if !ok { return nil, status.Errorf(codes.Unauthenticated, "error creds") } teamId, err := base64.StdEncoding.DecodeString(teamIdArr[0]) if err != nil { return nil, status.Errorf(codes.Unauthenticated, "error creds") } passwordArr, ok := md["password"] if !ok { return nil, status.Errorf(codes.Unauthenticated, "error creds") } password := passwordArr[0] team, err := s.repository.GetTeam(ctx, teamId, password) if err != nil { return nil, status.Errorf(codes.Unauthenticated, err.Error()) } return team, nil } func log(team *models.Team, action string, v any) { vJson, err := json.Marshal(v) if err == nil { fmt.Printf("Team %s: %s %s\n", team.Name, action, string(vJson)) } } func getTeamUrl(team *models.Team) (string, error) { ips, err := getLocalIPs() if err != nil { return "", err } ip := ips[0] u := fmt.Sprintf("http://%s:8100?name=%s&password=%s", ip, url.PathEscape(team.Name), team.Password) return u, nil } func getLocalIPs() ([]string, error) { var ips []string addrs, err := net.InterfaceAddrs() if err != nil { return nil, err } for _, addr := range addrs { ipNet, ok := addr.(*net.IPNet) if !ok { continue } ip := ipNet.IP if ip.IsLoopback() || ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast() { continue } if ipv4 := ip.To4(); ipv4 != nil { ips = append(ips, ipv4.String()) } } return ips, nil }