2026-03-07 07:50:24 +07:00

331 lines
9.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package services
import (
"context"
"encoding/base64"
"encoding/json"
"evening_detective/internal/models"
"evening_detective/internal/modules/password"
"evening_detective/internal/services/db"
"evening_detective/internal/services/link"
"evening_detective/internal/services/pdf"
"evening_detective/internal/services/story_service"
"evening_detective/proto"
"fmt"
"time"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
type Services struct {
dbService db.IDBService
storyService *story_service.StoryService
linkService link.ILinkService
passwordGenerator password.IPasswordGenerator
pdfGenerator pdf.IPDFGenerator
clientHost string
}
func NewServices(
dbService db.IDBService,
storyService *story_service.StoryService,
linkService link.ILinkService,
passwordGenerator password.IPasswordGenerator,
pdfGenerator pdf.IPDFGenerator,
clientHost string,
) *Services {
return &Services{
dbService: dbService,
storyService: storyService,
linkService: linkService,
passwordGenerator: passwordGenerator,
pdfGenerator: pdfGenerator,
clientHost: clientHost,
}
}
func (s *Services) GiveApplications(ctx context.Context, req *proto.GiveApplicationsReq) (*proto.GiveApplicationsRsp, error) {
applications := mapProtoApplicationsToApplications(req.Applications)
if err := s.dbService.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) {
game, err := s.dbService.GetGame(ctx)
if err != nil {
return nil, status.Errorf(codes.Internal, err.Error())
}
return &proto.GetGameRsp{
State: game.State,
StartAt: game.StartTime,
EndAt: game.EndTime,
}, nil
}
func (s *Services) GameStart(ctx context.Context, _ *proto.GameStartReq) (*proto.GameStartRsp, error) {
if err := s.dbService.UpdateGameState(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.dbService.UpdateGameState(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.dbService.AddActions(ctx, team.ID, actions); err != nil {
return nil, status.Errorf(codes.Internal, err.Error())
}
currentApplications, err := s.dbService.GetApplications(ctx, team.ID)
if err != nil {
return nil, err
}
newApplications := make([]*models.Application, 0, len(actions))
for _, action := range actions {
for _, actionApplication := range action.Applications {
f := false
for _, currentApplication := range currentApplications {
if currentApplication.Name == actionApplication.Name {
f = true
}
}
if !f {
newApplications = append(newApplications, actionApplication)
}
}
}
if err := s.dbService.AddApplications(ctx, team.ID, newApplications); err != nil {
return nil, status.Errorf(codes.Internal, err.Error())
}
addLog(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.dbService.GetActions(ctx, team.ID)
if err != nil {
return nil, status.Errorf(codes.Internal, err.Error())
}
res := make([]*proto.Action, 0, len(actions))
for _, place := range s.getPlaces(actions) {
newAction := mapPlaceToProtoAction(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) getPlaces(actions []*models.Action) []*story_service.Place {
places := []*story_service.Place{}
m := map[string]any{}
for _, action := range actions {
place := s.storyService.GetPlace(action.Place)
_, ok := m[place.Code]
if place.Hidden && !ok {
place = &story_service.Place{
Code: place.Code,
Name: "Не найдено",
Text: "Такой точки не существует.",
}
}
places = append(places, place)
for _, door := range place.Doors {
m[door.Code] = struct{}{}
}
}
return places
}
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.dbService.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.dbService.GetActions(ctx, team.ID)
if err != nil {
return nil, err
}
newTeam.Url = s.linkService.GetTeamClientLink(s.clientHost, team.Name, team.Password)
newTeam.SpendTime = int64(len(actions))
currentApplications, err := s.dbService.GetApplicationsByState(ctx, team.ID, "NEW")
if err != nil {
return nil, err
}
newTeam.Applications = mapApplicationsToProtoApplications(currentApplications)
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 = s.passwordGenerator.GeneratePassword(8)
inTeams = append(inTeams, t)
}
teams, err := s.dbService.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}, nil
}
func (s *Services) DownloadTeamsQrCodesFile(ctx context.Context, req *proto.DownloadTeamsQrCodesFileReq) (*proto.DownloadTeamsQrCodesFileRsp, error) {
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
teams, err := s.dbService.GetTeams(ctx)
if err != nil {
return nil, err
}
for _, team := range teams {
team.Link = s.linkService.GetTeamClientLink(s.clientHost, team.Name, team.Password)
}
b, err := s.pdfGenerator.CreateTeamsPDF(teams)
if err != nil {
return nil, err
}
return &proto.DownloadTeamsQrCodesFileRsp{Result: b}, nil
}
func (s *Services) UpdateNode(ctx context.Context, req *proto.UpdateNodeReq) (*proto.UpdateNodeRsp, error) {
applications := make([]*story_service.GraphApplication, 0, len(req.Node.Applications))
for _, application := range req.Node.Applications {
applications = append(
applications,
&story_service.GraphApplication{
Name: application.Name,
},
)
}
node := &story_service.GraphNode{
Code: req.Node.Code,
Name: req.Node.Name,
Text: req.Node.Text,
Applications: applications,
}
if err := s.storyService.UpdatePlace(ctx, req.Code, node); err != nil {
return nil, err
}
return &proto.UpdateNodeRsp{}, nil
}
func (s *Services) GetGraph(ctx context.Context, req *proto.GetGraphReq) (*proto.GetGraphRsp, error) {
graph := s.storyService.GetGraph(ctx)
nodes := make([]*proto.GraphNode, 0, len(graph.Nodes))
for _, node := range graph.Nodes {
applications := make([]*proto.GraphApplication, 0, len(node.Applications))
for _, application := range node.Applications {
applications = append(
applications,
&proto.GraphApplication{
Name: application.Name,
},
)
}
nodes = append(nodes, &proto.GraphNode{
Code: node.Code,
Name: node.Name,
Text: node.Text,
Applications: applications,
})
}
edges := make([]*proto.GetGraphRsp_Edge, 0, len(graph.Edges))
for _, edge := range graph.Edges {
edges = append(edges, &proto.GetGraphRsp_Edge{
From: edge.From,
To: edge.To,
Arrows: "to",
Type: edge.Type,
})
}
return &proto.GetGraphRsp{
Nodes: nodes,
Edges: edges,
CountNodes: int32(len(nodes)),
CountEdges: int32(len(edges)),
}, nil
}
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.dbService.GetTeam(ctx, teamId, password)
if err != nil {
return nil, status.Errorf(codes.Unauthenticated, err.Error())
}
return team, nil
}
func addLog(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))
}
}