package graph // This file will be automatically regenerated based on the schema, any resolver implementations // will be copied through when generating and any unknown code will be moved to the end. import ( "context" "database/sql" "encoding/json" "errors" "fmt" "io/ioutil" "math/rand" "net/http" "time" "git.xenrox.net/~xenrox/10man-api/config" "git.xenrox.net/~xenrox/10man-api/database" "git.xenrox.net/~xenrox/10man-api/functions" "git.xenrox.net/~xenrox/10man-api/graph/generated" "git.xenrox.net/~xenrox/10man-api/graph/model" "git.xenrox.net/~xenrox/10man-api/logic" ) // save current map vote var voteMaps []string var voteStatus string func (r *mutationResolver) CreateUser(ctx context.Context, input model.NewUser) (string, error) { type Elo struct { Elo int `json:"Elo"` } var elo Elo url := "https://faceit.xenrox.net/api/elo/" + input.SteamID resp, err := http.Get(url) if err != nil { return "", err } defer resp.Body.Close() if resp.StatusCode >= 400 { return "", errors.New("wrong steamID64") } body, err := ioutil.ReadAll(resp.Body) if err != nil { return "", err } err = json.Unmarshal(body, &elo) if err != nil { return "", err } // get admin status isAdmin := false for _, adminID := range config.Admins { if input.SteamID == adminID { isAdmin = true break } } query := ` INSERT INTO "User" (steam_id, teamspeak_id, elo, admin, avatar, name) VALUES ($1, $2, $3, $4, $5, $6)` _, err = database.DB.Exec(query, input.SteamID, input.TeamspeakID, elo.Elo, isAdmin, input.Avatar, input.Name) if err != nil { return "", database.CheckErrorCode(err) } return "Created", nil } func (r *mutationResolver) DeleteUser(ctx context.Context, id int) (string, error) { query := `DELETE FROM "User" WHERE id = $1` _, err := database.DB.Exec(query, id) return "Deleted", err } func (r *mutationResolver) UpdateUser(ctx context.Context, id int, input model.UserInput) (*model.User, error) { var user model.User tx, err := database.DB.Begin() if err != nil { return nil, err } defer tx.Rollback() if input.Elo != nil { query := ` UPDATE "User" SET elo = $1 WHERE id = $2` _, err := tx.Exec(query, input.Elo, id) if err != nil { return nil, fmt.Errorf("failed to update elo: %w", err) } } if input.TeamspeakID != nil { query := ` UPDATE "User" SET teamspeak_id = $1 WHERE id = $2` _, err := tx.Exec(query, input.TeamspeakID, id) if err != nil { return nil, fmt.Errorf("failed to update teamspeakID: %w", err) } } if input.Avatar != nil { query := ` UPDATE "User" SET avatar = $1 WHERE id = $2` _, err := tx.Exec(query, input.Avatar, id) if err != nil { return nil, fmt.Errorf("failed to update avatar: %w", err) } } if input.Name != nil { query := ` UPDATE "User" SET name = $1 WHERE id = $2` _, err := tx.Exec(query, input.Name, id) if err != nil { return nil, fmt.Errorf("failed to update name: %w", err) } } query := ` SELECT steam_id, teamspeak_id, elo, admin, avatar, name FROM "User" WHERE id = $1` err = tx.QueryRow(query, id).Scan(&user.SteamID, &user.TeamspeakID, &user.Elo, &user.Admin, &user.Avatar, &user.Name) if err != nil { return nil, err } err = tx.Commit() if err != nil { return nil, err } return &user, nil } func (r *mutationResolver) StartQueue(ctx context.Context, teamspeakID string) (int, error) { players, err := database.PlayersInQueue() if err != nil { return players, err } // if no players are in queue, check if a match is ongoing and queueing up // is forbidden if players == 0 { id := -1 query := ` SELECT id FROM "Match" WHERE status = 'ongoing'` err := database.DB.QueryRow(query).Scan(&id) if err == nil { return players, errors.New("cannot queue, match still ongoing") } else if err != nil && !errors.Is(err, sql.ErrNoRows) { return players, err } } if players == 10 { return players, errors.New("queue is full") } query := ` UPDATE "User" SET queueing = true WHERE teamspeak_id = $1 AND NOT queueing` result, err := database.DB.Exec(query, teamspeakID) if err != nil { return players, fmt.Errorf("QueueFail: %w", err) } count, err := result.RowsAffected() if err != nil { return players, err } // check if user already was in queue or if he does not even exist if count != 1 { queueing := false query = ` SELECT queueing FROM "User" WHERE teamspeak_id = $1` err = database.DB.QueryRow(query, teamspeakID).Scan(&queueing) if err != nil { return players, database.CheckErrorCode(err) } if queueing { return players, errors.New("already in queue") } return players, errors.New("queueing failed") } players++ return players, nil } func (r *mutationResolver) CancelQueue(ctx context.Context, teamspeakID string) (int, error) { query := ` UPDATE "User" SET queueing = false WHERE teamspeak_id = $1 AND queueing` result, err := database.DB.Exec(query, teamspeakID) if err != nil { return -1, err } count, err := result.RowsAffected() if err != nil { return -1, err } if count != 1 { return -1, errors.New("user not in queue") } players, err := database.PlayersInQueue() return players, err } func (r *mutationResolver) CreateMatch(ctx context.Context) (int, error) { query := ` SELECT id, elo FROM "User" WHERE queueing ` rows, err := database.DB.Query(query) if err != nil { return -1, err } defer rows.Close() var players logic.Players index := 0 for rows.Next() { if err := rows.Scan(&players[index][0], &players[index][1]); err != nil { return -1, fmt.Errorf("failed to read players: %w", err) } index++ } if err = rows.Err(); err != nil { return -1, err } // check if 10 players in queue if index != 10 { return -1, errors.New("queue is not full") } tx, err := database.DB.Begin() if err != nil { return -1, err } defer tx.Rollback() team1, team2 := logic.BalanceTeams(players) query = ` INSERT INTO "Team" (p1, p2, p3, p4, p5) VALUES ($1, $2, $3, $4, $5) RETURNING id` err = tx.QueryRow(query, team1.Players[0], team1.Players[1], team1.Players[2], team1.Players[3], team1.Players[4]).Scan(&team1.ID) if err != nil { return -1, err } err = tx.QueryRow(query, team2.Players[0], team2.Players[1], team2.Players[2], team2.Players[3], team2.Players[4]).Scan(&team2.ID) if err != nil { return -1, err } query = ` INSERT INTO "Match" (t1, t2, elo1, elo2) VALUES ($1, $2, $3, $4) RETURNING id` matchID := -1 err = tx.QueryRow(query, team1.ID, team2.ID, team1.Elo, team2.Elo).Scan(&matchID) if err != nil { return -1, err } // clear queueing status query = `UPDATE "User" SET queueing = false where queueing` _, err = tx.Exec(query) if err != nil { return -1, err } err = tx.Commit() if err != nil { return -1, err } // initialize map vote (maps and random voter) voteMaps = config.Maps rand.Seed(time.Now().UTC().UnixNano()) voter := rand.Intn(2) if voter == 0 { voteStatus = "TEAM1" } else { voteStatus = "TEAM2" } return matchID, nil } func (r *mutationResolver) CancelMatch(ctx context.Context) (string, error) { query := ` UPDATE "Match" SET status = 'cancelled' WHERE status = 'ongoing'` result, err := database.DB.Exec(query) if err != nil { return "CancelFail", err } count, err := result.RowsAffected() if err != nil { return "CancelFail", err } if count != 1 { return "CancelFail", errors.New("no match ongoing") } return "CancelSuccess", nil } func (r *mutationResolver) FinishMatch(ctx context.Context, winner string) (string, error) { var team1, team2 logic.Team query := ` SELECT t1, t2, elo1, elo2 FROM "Match" WHERE status = 'ongoing'` err := database.DB.QueryRow(query). Scan(&team1.ID, &team2.ID, &team1.Elo, &team2.Elo) if err != nil { if errors.Is(err, sql.ErrNoRows) { return "FinishFail", errors.New("no match ongoing") } return "FinishFail", err } delta1, delta2, err := logic.EloChange(team1.Elo, team2.Elo, winner) if err != nil { return "FinishFail", err } tx, err := database.DB.Begin() if err != nil { return "FinishFail", err } defer tx.Rollback() query = ` UPDATE "User" SET elo = elo + $1 WHERE id IN (SELECT unnest(array[p1, p2, p3, p4, p5]) FROM "Team" WHERE id = $2)` _, err = tx.Exec(query, delta1, team1.ID) if err != nil { return "FinishFail", err } _, err = tx.Exec(query, delta2, team2.ID) if err != nil { return "FinishFail", err } query = ` UPDATE "Match" SET status = 'finished' WHERE status = 'ongoing'` _, err = tx.Exec(query) if err != nil { return "FinishFail", err } err = tx.Commit() if err != nil { return "FinishFail", err } return "FinishSuccess", nil } func (r *mutationResolver) VetoMap(ctx context.Context, mapArg *string) (*model.MapVote, error) { var mapVote model.MapVote if mapArg == nil { // return all remaining maps mapVote.Maps = voteMaps mapVote.Status = voteStatus return &mapVote, nil } if voteStatus == "DONE" { return nil, errors.New("vote is done") } length := len(voteMaps) // check if voted map is in remaining contains, i := functions.SliceContains(voteMaps, *mapArg) if !contains { return nil, errors.New("no legal vote") } voteMaps[i] = voteMaps[length-1] voteMaps = voteMaps[:length-1] if length == 2 { voteStatus = "DONE" // save in database query := ` UPDATE "Match" SET map = $1 WHERE status = 'ongoing'` _, err := database.DB.Exec(query, voteMaps[0]) if err != nil { return nil, fmt.Errorf("failed to update match map: %w", err) } } else if voteStatus == "TEAM1" { voteStatus = "TEAM2" } else if voteStatus == "TEAM2" { voteStatus = "TEAM1" } mapVote.Maps = voteMaps mapVote.Status = voteStatus return &mapVote, nil } func (r *queryResolver) UserBySteam(ctx context.Context, steamID string) (*model.User, error) { var user model.User user.SteamID = steamID query := ` SELECT id, teamspeak_id, Elo, admin, avatar, name FROM "User" WHERE steam_id = $1` err := database.DB.QueryRow(query, steamID).Scan(&user.ID, &user.TeamspeakID, &user.Elo, &user.Admin, &user.Avatar, &user.Name) if err != nil { return nil, database.CheckErrorCode(err) } return &user, nil } func (r *queryResolver) UserByTs(ctx context.Context, teamspeakID string) (*model.User, error) { var user model.User user.TeamspeakID = teamspeakID query := ` SELECT id, steam_id, Elo, admin, avatar, name FROM "User" WHERE teamspeak_id = $1` err := database.DB.QueryRow(query, teamspeakID).Scan(&user.ID, &user.SteamID, &user.Elo, &user.Admin, &user.Avatar, &user.Name) if err != nil { return nil, database.CheckErrorCode(err) } return &user, nil } func (r *queryResolver) GetTeams(ctx context.Context, id *int) (*model.Teams, error) { var teams model.Teams var team1, team2 []*model.User var err error var rows *sql.Rows tx, err := database.DB.Begin() if err != nil { return nil, database.CheckErrorCode(err) } defer tx.Rollback() query := ` SELECT "User".id, "User".steam_id, "User".teamspeak_id, "User".elo, "User".admin, "User".avatar, "User".name FROM ( SELECT unnest( array[ "Team".p1, "Team".p2, "Team".p3, "Team".p4, "Team".p5] ) AS inner_query, status, "Match".id FROM "Match" LEFT JOIN "Team" ON "Match".%s = "Team".id) AS member LEFT JOIN "User" ON member.inner_query = "User".id` // Team 1 if id != nil { // get match by id query += "\nWHERE member.id = $1" rows, err = tx.Query(fmt.Sprintf(query, "t1"), id) } else { // get ongoing match query += "\nWHERE member.status = 'ongoing'" rows, err = tx.Query(fmt.Sprintf(query, "t1")) } if err != nil { return nil, database.CheckErrorCode(err) } defer rows.Close() for rows.Next() { var user model.User if err := rows.Scan(&user.ID, &user.SteamID, &user.TeamspeakID, &user.Elo, &user.Admin, &user.Avatar, &user.Name); err != nil { return nil, database.CheckErrorCode(err) } team1 = append(team1, &user) } if err = rows.Err(); err != nil { return nil, database.CheckErrorCode(err) } // Team 2 if id != nil { // get match by id rows, err = tx.Query(fmt.Sprintf(query, "t2"), id) } else { // get ongoing match rows, err = tx.Query(fmt.Sprintf(query, "t2")) } if err != nil { return nil, database.CheckErrorCode(err) } defer rows.Close() for rows.Next() { var user model.User if err := rows.Scan(&user.ID, &user.SteamID, &user.TeamspeakID, &user.Elo, &user.Admin, &user.Avatar, &user.Name); err != nil { return nil, database.CheckErrorCode(err) } team2 = append(team2, &user) } if err = rows.Err(); err != nil { return nil, database.CheckErrorCode(err) } teams.Team1, teams.Team2 = team1, team2 // players with highest elo are captains teams.Captain1, teams.Captain2 = team1[1], team2[1] return &teams, nil } // Mutation returns generated.MutationResolver implementation. func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} } // Query returns generated.QueryResolver implementation. func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} } type mutationResolver struct{ *Resolver } type queryResolver struct{ *Resolver }