├── docker ├── db │ ├── Dockerfile │ ├── .env │ └── init │ │ └── init_db.sql └── srcds │ └── Dockerfile ├── backend ├── sqlc │ ├── generate.go │ ├── players_schema.sql │ ├── teams_schema.sql │ ├── users_schema.sql │ ├── mapstats_query.sql │ ├── users_query.sql │ ├── gameserver_schema.sql │ ├── players_query.sql │ ├── playerstats_query.sql │ ├── teams_query.sql │ ├── mapstats_schema.sql │ ├── gameserver_query.sql │ ├── matches_schema.sql │ ├── matches_query.sql │ ├── sqlc.yaml │ └── playerstats_schema.sql ├── service │ ├── jwt │ │ ├── jwt_mock.go │ │ ├── jwt_test.go │ │ ├── jwt.go │ │ └── mock │ │ │ └── jwt_mock.go │ ├── password_hash │ │ ├── hash_mock.go │ │ ├── hash.go │ │ └── mock │ │ │ └── hash_mock.go │ └── uuid │ │ └── uuid.go ├── gateway │ ├── database │ │ ├── mock.go │ │ ├── idstostr.go │ │ ├── mysql │ │ │ ├── players │ │ │ │ ├── generated │ │ │ │ │ ├── models.go │ │ │ │ │ ├── db.go │ │ │ │ │ └── players_query.sql.go │ │ │ │ └── players.go │ │ │ ├── teams │ │ │ │ └── generated │ │ │ │ │ ├── models.go │ │ │ │ │ └── db.go │ │ │ ├── users │ │ │ │ ├── generated │ │ │ │ │ ├── models.go │ │ │ │ │ ├── db.go │ │ │ │ │ └── users_query.sql.go │ │ │ │ └── users.go │ │ │ ├── gameservers │ │ │ │ └── generated │ │ │ │ │ ├── models.go │ │ │ │ │ ├── db.go │ │ │ │ │ └── gameserver_query.sql.go │ │ │ ├── mapstats │ │ │ │ ├── generated │ │ │ │ │ ├── models.go │ │ │ │ │ ├── db.go │ │ │ │ │ └── mapstats_query.sql.go │ │ │ │ └── mapstats.go │ │ │ ├── matches │ │ │ │ └── generated │ │ │ │ │ ├── models.go │ │ │ │ │ └── db.go │ │ │ ├── playerstats │ │ │ │ └── generated │ │ │ │ │ ├── db.go │ │ │ │ │ └── models.go │ │ │ └── connector │ │ │ │ ├── connector.go │ │ │ │ ├── connector_tx.go │ │ │ │ ├── repository.go │ │ │ │ └── repository_tx.go │ │ ├── errors.go │ │ ├── context.go │ │ ├── errors_test.go │ │ └── models.go │ └── srcds │ │ ├── rcon │ │ ├── rcon_mock.go │ │ ├── rcon.go │ │ └── rcon_mock │ │ │ └── mock_rcon.go │ │ └── query │ │ ├── query_mock.go │ │ ├── query.go │ │ └── query_mock │ │ └── mock_query.go ├── Dockerfile ├── tools │ └── tools.go ├── entity │ ├── server_status.go │ ├── jwt.go │ ├── match_status.go │ ├── server_status_string.go │ ├── match_status_string.go │ └── entity.go ├── cmd │ ├── di │ │ ├── must.go │ │ ├── get5.go │ │ ├── di.go │ │ └── gql.go │ └── main.go ├── presenter │ └── gin │ │ └── jwt.go ├── graph │ ├── resolver.go │ ├── dataloaders │ │ ├── teams_by_teamids.go │ │ ├── teams_by_userid.go │ │ ├── matches_by_userid.go │ │ ├── players_by_teamid.go │ │ ├── mapstats_by_matchid.go │ │ ├── servers_by_userid.go │ │ ├── playerstats_by_mapstatid.go │ │ └── dataloaders.go │ ├── qls │ │ └── schema.graphqls │ ├── converter.go │ └── model │ │ └── models_gen.go ├── .testcoverage.yml ├── usecase │ ├── usecase_mock.go │ ├── validate_jwt.go │ ├── playerstat.go │ ├── get5.go │ ├── validate_jwt_test.go │ ├── mock │ │ ├── get5.go │ │ ├── validate_jwt.go │ │ ├── playerstat.go │ │ ├── player.go │ │ ├── mapstats.go │ │ ├── user.go │ │ ├── match.go │ │ ├── gameserver.go │ │ └── team.go │ ├── player.go │ ├── mapstats.go │ ├── match.go │ ├── gameserver.go │ ├── user.go │ └── team.go ├── controller │ ├── get5 │ │ ├── auth.go │ │ └── loader.go │ └── gin │ │ ├── db.go │ │ ├── login.go │ │ └── register.go ├── cfg │ └── cfg.go ├── g5ctx │ ├── g5ctx.go │ └── g5ctx_test.go ├── .air.toml ├── go.mod └── gqlgen.yml ├── .gitattributes ├── screenshots ├── Match.PNG └── Matches.PNG ├── .github ├── workflows │ ├── go_build.yaml │ ├── go_test.yaml │ └── go_coverage.yaml └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── PATCHNOTE.MD ├── .gitignore ├── LICENSE ├── docker-compose.yaml ├── makefile └── README.md /docker/db/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mysql:8.0 2 | ENV LANG ja_JP.UTF-8 -------------------------------------------------------------------------------- /backend/sqlc/generate.go: -------------------------------------------------------------------------------- 1 | package sqlc 2 | 3 | //go:generate sqlc generate 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /screenshots/Match.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowingSPDG/get5loader/HEAD/screenshots/Match.PNG -------------------------------------------------------------------------------- /screenshots/Matches.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlowingSPDG/get5loader/HEAD/screenshots/Matches.PNG -------------------------------------------------------------------------------- /backend/service/jwt/jwt_mock.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | //go:generate mockgen -source=jwt.go -destination mock/jwt_mock.go 4 | -------------------------------------------------------------------------------- /docker/db/.env: -------------------------------------------------------------------------------- 1 | MYSQL_DATABASE=get5_go 2 | MYSQL_USER=test_user 3 | MYSQL_PASSWORD=password 4 | MYSQL_ROOT_PASSWORD=root_password -------------------------------------------------------------------------------- /backend/gateway/database/mock.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | //go:generate mockgen -source=./database.go -destination ./mock/mock_gen.go 4 | -------------------------------------------------------------------------------- /backend/service/password_hash/hash_mock.go: -------------------------------------------------------------------------------- 1 | package hash 2 | 3 | //go:generate mockgen -source=hash.go -destination=mock/hash_mock.go 4 | -------------------------------------------------------------------------------- /backend/gateway/srcds/rcon/rcon_mock.go: -------------------------------------------------------------------------------- 1 | package rcon 2 | 3 | //go:generate mockgen -source=./rcon.go -destination=./rcon_mock/mock_rcon.go 4 | -------------------------------------------------------------------------------- /backend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.21 2 | WORKDIR /go/src 3 | 4 | RUN go install github.com/cosmtrek/air@v1.44.0 5 | 6 | CMD ["air", "-c", ".air.toml"] -------------------------------------------------------------------------------- /backend/gateway/srcds/query/query_mock.go: -------------------------------------------------------------------------------- 1 | package query 2 | 3 | //go:generate mockgen -source=./query.go -destination=./query_mock/mock_query.go 4 | -------------------------------------------------------------------------------- /backend/tools/tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | // +build tools 3 | 4 | package tools 5 | 6 | import ( 7 | _ "github.com/99designs/gqlgen/graphql/introspection" 8 | ) 9 | -------------------------------------------------------------------------------- /backend/entity/server_status.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | //go:generate stringer -type=SERVER_STATUS 4 | type SERVER_STATUS int 5 | 6 | const ( 7 | SERVER_STATUS_UNKNOWN SERVER_STATUS = iota 8 | SERVER_STATUS_STANDBY 9 | SERVER_STATUS_INUSE 10 | ) 11 | -------------------------------------------------------------------------------- /backend/entity/jwt.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import "github.com/golang-jwt/jwt/v5" 4 | 5 | type TokenUser struct { 6 | UserID UserID `json:"userid"` 7 | SteamID SteamID `json:"steamid"` 8 | Admin bool `json:"admin"` 9 | jwt.RegisteredClaims 10 | } 11 | -------------------------------------------------------------------------------- /backend/sqlc/players_schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `players` ( 2 | `id` VARCHAR(36) NOT NULL PRIMARY KEY, 3 | `team_id` VARCHAR(36) NOT NULL, 4 | `steam_id` BIGINT UNSIGNED NOT NULL, 5 | `name` varchar(40) NOT NULL, 6 | FOREIGN KEY (`team_id`) REFERENCES teams(`id`) 7 | ); -------------------------------------------------------------------------------- /backend/gateway/database/idstostr.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | type ID interface { 4 | ~string 5 | } 6 | 7 | func IDsToString[I ID](ids []I) []string { 8 | ret := make([]string, 0, len(ids)) 9 | for _, v := range ids { 10 | ret = append(ret, string(v)) 11 | } 12 | return ret 13 | } 14 | -------------------------------------------------------------------------------- /backend/entity/match_status.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | //go:generate stringer -type=MATCH_STATUS 4 | type MATCH_STATUS int 5 | 6 | const ( 7 | MATCH_STATUS_UNKNOWN MATCH_STATUS = iota 8 | MATCH_STATUS_PENDING 9 | MATCH_STATUS_LIVE 10 | MATCH_STATUS_FINISHED 11 | MATCH_STATUS_CANCELLED 12 | ) 13 | -------------------------------------------------------------------------------- /backend/gateway/database/mysql/players/generated/models.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.20.0 4 | 5 | package players_gen 6 | 7 | import () 8 | 9 | type Player struct { 10 | ID string 11 | TeamID string 12 | SteamID uint64 13 | Name string 14 | } 15 | -------------------------------------------------------------------------------- /docker/srcds/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM flowingspdg/csgo-get5-docker-dev:latest 2 | 3 | ENV GOTV_ENABLE 1 4 | ENV PORT 27015 5 | ENV GOTV_PORT 2720 6 | ENV GOTV_DELAY 5 7 | ENV PASSWORD local 8 | ENV RCON_PASSWORD localpassword 9 | ENV GOTV_PASSWORD gotvpass 10 | 11 | EXPOSE 27015-27020/udp 12 | 13 | CMD ["bash", "server-launch.sh"] -------------------------------------------------------------------------------- /backend/sqlc/teams_schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `teams` ( 2 | `id` VARCHAR(36) NOT NULL PRIMARY KEY, 3 | `user_id` VARCHAR(36) NOT NULL, 4 | `name` varchar(40) NOT NULL, 5 | `flag` varchar(4) NOT NULL, 6 | `logo` varchar(10) NOT NULL, 7 | `tag` varchar(40) NOT NULL, 8 | `public_team` BOOLEAN DEFAULT FALSE, 9 | FOREIGN KEY (`user_id`) REFERENCES users(`id`) 10 | ); -------------------------------------------------------------------------------- /.github/workflows/go_build.yaml: -------------------------------------------------------------------------------- 1 | name: Go Build 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | 12 | - name: Set up Go 13 | uses: actions/setup-go@v3 14 | with: 15 | go-version: '1.20' 16 | 17 | - name: Build 18 | run: cd backend && go build -v ./... -------------------------------------------------------------------------------- /backend/service/uuid/uuid.go: -------------------------------------------------------------------------------- 1 | package uuid 2 | 3 | import "github.com/google/uuid" 4 | 5 | type UUIDGenerator interface { 6 | Generate() string 7 | } 8 | 9 | type uuidGenerator struct { 10 | } 11 | 12 | func NewUUIDGenerator() UUIDGenerator { 13 | return &uuidGenerator{} 14 | } 15 | 16 | func (u *uuidGenerator) Generate() string { 17 | id := uuid.New() 18 | return id.String() 19 | } 20 | -------------------------------------------------------------------------------- /.github/workflows/go_test.yaml: -------------------------------------------------------------------------------- 1 | name: Go Test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | 12 | - name: Set up Go 13 | uses: actions/setup-go@v3 14 | with: 15 | go-version: '1.20' 16 | 17 | - name: Test 18 | run: cd backend && go test -v -count=1 -v ./... 19 | -------------------------------------------------------------------------------- /backend/sqlc/users_schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `users` ( 2 | `id` VARCHAR(36) NOT NULL PRIMARY KEY, 3 | `steam_id` BIGINT UNSIGNED NOT NULL UNIQUE, 4 | `name` varchar(40) NOT NULL, 5 | `admin` BOOLEAN NOT NULL DEFAULT FALSE, 6 | `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, 7 | `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 8 | `password_hash` BLOB NOT NULL 9 | ); -------------------------------------------------------------------------------- /backend/gateway/database/mysql/teams/generated/models.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.20.0 4 | 5 | package teams_gen 6 | 7 | import ( 8 | "database/sql" 9 | ) 10 | 11 | type Team struct { 12 | ID string 13 | UserID string 14 | Name string 15 | Flag string 16 | Logo string 17 | Tag string 18 | PublicTeam sql.NullBool 19 | } 20 | -------------------------------------------------------------------------------- /backend/gateway/database/mysql/users/generated/models.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.20.0 4 | 5 | package users_gen 6 | 7 | import ( 8 | "time" 9 | ) 10 | 11 | type User struct { 12 | ID string 13 | SteamID uint64 14 | Name string 15 | Admin bool 16 | CreatedAt time.Time 17 | UpdatedAt time.Time 18 | PasswordHash []byte 19 | } 20 | -------------------------------------------------------------------------------- /backend/gateway/database/mysql/gameservers/generated/models.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.20.0 4 | 5 | package gameservers_gen 6 | 7 | import () 8 | 9 | type GameServer struct { 10 | ID string 11 | UserID string 12 | Ip string 13 | Port uint32 14 | RconPassword string 15 | DisplayName string 16 | IsPublic bool 17 | Status int32 18 | } 19 | -------------------------------------------------------------------------------- /backend/sqlc/mapstats_query.sql: -------------------------------------------------------------------------------- 1 | -- name: GetMapStat :one 2 | SELECT * FROM map_stats 3 | WHERE id = ? LIMIT 1; 4 | 5 | -- name: GetMapStatsByMatches :many 6 | SELECT * FROM map_stats 7 | WHERE match_id IN (sqlc.slice('match_ids')); 8 | 9 | -- name: GetMapStatsByMatch :many 10 | SELECT * FROM map_stats 11 | WHERE match_id = ?; 12 | 13 | -- name: GetMapStatsByMatchAndMap :one 14 | SELECT * FROM map_stats 15 | WHERE match_id = ? AND map_number = ? LIMIT 1; -------------------------------------------------------------------------------- /backend/sqlc/users_query.sql: -------------------------------------------------------------------------------- 1 | -- name: GetUser :one 2 | SELECT * FROM users 3 | WHERE id = ? LIMIT 1; 4 | 5 | -- name: GetUsers :many 6 | SELECT * FROM users 7 | WHERE id IN (sqlc.slice('user_ids')); 8 | 9 | -- name: GetUserBySteamID :one 10 | SELECT * FROM users 11 | WHERE steam_id = ? LIMIT 1; 12 | 13 | -- name: CreateUser :execresult 14 | INSERT INTO users ( 15 | id ,steam_id, name, admin, password_hash 16 | ) VALUES ( 17 | ?, ?, ?, ?, ? 18 | ); -------------------------------------------------------------------------------- /PATCHNOTE.MD: -------------------------------------------------------------------------------- 1 | ### Patch Notes and ROADMAPS 2 | 3 | ROADMAP OF get5-web-go (temporary,Roadmap and update details may heavily change) 4 | - v0.2.0(ETA. Summer 2023) 5 | - v0.1.0(Jan.7 2020) : Initial Release 6 | - v0.0.1(Dec.29 2019) : Experimental Beta. Most functions wont work yet 7 | 8 | ### PatchNote : 9 | - v0.1.2 Replaced mux by Gin. Better logging system, more detail configs. 10 | - v0.1.1 Several Fixes,Improved design 11 | - v0.1.0 Supported full-compatibility with get5-web DB. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | .vscode/settings.json 17 | config.ini 18 | get5-web-go 19 | __debug_bin 20 | .vscode/launch.json 21 | backend/build/* 22 | 23 | .DS_Store 24 | backend/tmp/* -------------------------------------------------------------------------------- /backend/gateway/database/mysql/mapstats/generated/models.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.20.0 4 | 5 | package mapstats_gen 6 | 7 | import ( 8 | "database/sql" 9 | ) 10 | 11 | type MapStat struct { 12 | ID string 13 | MatchID string 14 | MapNumber uint32 15 | MapName string 16 | StartTime sql.NullTime 17 | EndTime sql.NullTime 18 | Winner sql.NullString 19 | Team1Score uint32 20 | Team2Score uint32 21 | Forfeit sql.NullBool 22 | } 23 | -------------------------------------------------------------------------------- /backend/sqlc/gameserver_schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `game_servers` ( 2 | `id` VARCHAR(36) NOT NULL PRIMARY KEY, 3 | `user_id` VARCHAR(36) NOT NULL, 4 | `ip` VARCHAR(16) NOT NULL, 5 | `port` SMALLINT UNSIGNED NOT NULL DEFAULT 27015, 6 | `rcon_password` VARCHAR(32) NOT NULL, 7 | `display_name` VARCHAR(32) NOT NULL, 8 | `is_public` BOOLEAN NOT NULL DEFAULT FALSE, 9 | `status` TINYINT NOT NULL DEFAULT 0, 10 | FOREIGN KEY (`user_id`) REFERENCES users(`id`), 11 | UNIQUE KEY `unique_ip_port` (`ip`, `port`), 12 | CHECK(is_ipv4(ip)) 13 | ); -------------------------------------------------------------------------------- /docker/db/init/init_db.sql: -------------------------------------------------------------------------------- 1 | SET CHARSET utf8mb4; 2 | DROP DATABASE IF EXISTS get5_go; 3 | CREATE DATABASE IF NOT EXISTS get5_go DEFAULT CHARACTER SET utf8mb4; 4 | 5 | use get5_go; 6 | BEGIN; 7 | SOURCE /docker-init-sqlc-definitions.d/users_schema.sql; 8 | SOURCE /docker-init-sqlc-definitions.d/gameserver_schema.sql; 9 | SOURCE /docker-init-sqlc-definitions.d/teams_schema.sql; 10 | SOURCE /docker-init-sqlc-definitions.d/matches_schema.sql; 11 | SOURCE /docker-init-sqlc-definitions.d/mapstats_schema.sql; 12 | SOURCE /docker-init-sqlc-definitions.d/players_schema.sql; 13 | COMMIT; 14 | -------------------------------------------------------------------------------- /backend/sqlc/players_query.sql: -------------------------------------------------------------------------------- 1 | -- name: GetPlayer :one 2 | SELECT * FROM players 3 | WHERE id = ? LIMIT 1; 4 | 5 | -- name: GetPlayerBySteamID :one 6 | SELECT * FROM players 7 | WHERE steam_id = ? LIMIT 1; 8 | 9 | -- name: GetPlayersByTeam :many 10 | SELECT * FROM players 11 | WHERE team_id = ?; 12 | 13 | -- name: GetPlayersByTeams :many 14 | SELECT * FROM players 15 | WHERE team_id IN (sqlc.slice('team_ids')); 16 | 17 | -- name: AddPlayer :execresult 18 | INSERT INTO players ( 19 | id, 20 | team_id, 21 | steam_id, 22 | name 23 | ) VALUES ( 24 | ?, 25 | ?, 26 | ?, 27 | ? 28 | ); -------------------------------------------------------------------------------- /backend/cmd/di/must.go: -------------------------------------------------------------------------------- 1 | package di 2 | 3 | import ( 4 | "fmt" 5 | 6 | config "github.com/FlowingSPDG/get5loader/backend/cfg" 7 | "github.com/FlowingSPDG/get5loader/backend/gateway/database" 8 | mysqlconnector "github.com/FlowingSPDG/get5loader/backend/gateway/database/mysql/connector" 9 | ) 10 | 11 | func mustGetWriteConnector(cfg config.Config) database.DBConnector { 12 | dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?parseTime=true&loc=Local&charset=utf8mb4", cfg.DBWriteUser, cfg.DBWritePass, cfg.DBWriteHost, cfg.DBWritePort, cfg.DBWriteName) 13 | return mysqlconnector.NewMysqlConnector(dsn) 14 | } 15 | -------------------------------------------------------------------------------- /backend/gateway/database/errors.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | ErrNotFound = errors.New("specified resource not found") 9 | ErrInternal = errors.New("internal error") 10 | ) 11 | 12 | func IsNotFound(err error) bool { 13 | return errors.Is(err, ErrNotFound) 14 | } 15 | 16 | func IsInternal(err error) bool { 17 | return errors.Is(err, ErrInternal) 18 | } 19 | 20 | func NewNotFoundError(err error) error { 21 | return errors.Join((err), ErrNotFound) 22 | } 23 | 24 | func NewInternalError(err error) error { 25 | return errors.Join(err, ErrInternal) 26 | } 27 | -------------------------------------------------------------------------------- /backend/presenter/gin/jwt.go: -------------------------------------------------------------------------------- 1 | package gin_presenter 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | type JWTPresenter interface { 10 | Handle(c *gin.Context, token string) 11 | } 12 | 13 | type jwtPresenter struct{} 14 | 15 | func NewJWTPresenter() JWTPresenter { 16 | return &jwtPresenter{} 17 | } 18 | 19 | type jwtResponse struct { 20 | Token string `json:"token"` 21 | } 22 | 23 | // Handle implements JWTPresenter. 24 | func (jp *jwtPresenter) Handle(c *gin.Context, token string) { 25 | c.JSON(http.StatusOK, &jwtResponse{ 26 | Token: token, 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /backend/sqlc/playerstats_query.sql: -------------------------------------------------------------------------------- 1 | -- name: GetPlayerStats :one 2 | SELECT * FROM player_stats 3 | WHERE id = ? LIMIT 1; 4 | 5 | -- name: GetPlayerStatsByMatch :many 6 | SELECT * FROM player_stats 7 | WHERE match_id = ?; 8 | 9 | -- name: GetPlayerStatsByMapStats :many 10 | SELECT * FROM player_stats 11 | WHERE map_id IN (sqlc.slice('map_ids')); 12 | 13 | -- name: GetPlayerStatsByMapStat :many 14 | SELECT * FROM player_stats 15 | WHERE map_id = ?; 16 | 17 | -- name: GetPlayerStatsByTeam :many 18 | SELECT * FROM player_stats 19 | WHERE team_id = ?; 20 | 21 | -- name: GetPlayerStatsBySteamID :many 22 | SELECT * FROM player_stats 23 | WHERE steam_id = ?; -------------------------------------------------------------------------------- /backend/sqlc/teams_query.sql: -------------------------------------------------------------------------------- 1 | -- name: GetTeam :one 2 | SELECT * FROM teams 3 | WHERE id = ? LIMIT 1; 4 | 5 | -- name: GetTeams :many 6 | SELECT * FROM teams 7 | WHERE id IN (sqlc.slice('ids')); 8 | 9 | -- name: GetTeamByUserID :many 10 | SELECT * FROM teams 11 | WHERE user_id = ?; 12 | 13 | -- name: GetTeamsByUsers :many 14 | SELECT * FROM teams 15 | WHERE user_id IN (sqlc.slice('user_ids')); 16 | 17 | -- name: GetPublicTeams :many 18 | SELECT * FROM teams 19 | WHERE public_team = TRUE; 20 | 21 | -- name: AddTeam :execresult 22 | INSERT INTO teams ( 23 | id, user_id, name, flag, logo, tag, public_team 24 | ) VALUES ( 25 | ?, ?, ?, ?, ?, ?, ? 26 | ); -------------------------------------------------------------------------------- /backend/sqlc/mapstats_schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `map_stats` ( 2 | `id` VARCHAR(36) NOT NULL PRIMARY KEY, 3 | `match_id` VARCHAR(36) NOT NULL, 4 | `map_number` TINYINT UNSIGNED NOT NULL, 5 | `map_name` varchar(64) NOT NULL, 6 | `start_time` datetime DEFAULT NULL, 7 | `end_time` datetime DEFAULT NULL, 8 | `winner` VARCHAR(36) DEFAULT NULL, 9 | `team1_score` TINYINT UNSIGNED NOT NULL, 10 | `team2_score` TINYINT UNSIGNED NOT NULL, 11 | `forfeit` BOOLEAN DEFAULT NULL, 12 | FOREIGN KEY (`match_id`) REFERENCES matches(`id`), 13 | FOREIGN KEY (`winner`) REFERENCES teams(`id`), 14 | UNIQUE KEY `match_id_map_number` (`match_id`, `map_number`) 15 | ); -------------------------------------------------------------------------------- /backend/graph/resolver.go: -------------------------------------------------------------------------------- 1 | package graph 2 | 3 | import ( 4 | "github.com/FlowingSPDG/get5loader/backend/graph/dataloaders" 5 | "github.com/FlowingSPDG/get5loader/backend/usecase" 6 | ) 7 | 8 | // This file will not be regenerated automatically. 9 | // 10 | // It serves as dependency injection for your app, add any dependencies you require here. 11 | 12 | type Resolver struct { 13 | GameServerUsecase usecase.GameServer 14 | UserUsecase usecase.User 15 | MatchUsecase usecase.Match 16 | MapstatUsecase usecase.Mapstat 17 | TeamUsecase usecase.Team 18 | PlayerUsecase usecase.Player 19 | DataLoader *dataloaders.Loaders 20 | } 21 | -------------------------------------------------------------------------------- /backend/gateway/database/mysql/matches/generated/models.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.20.0 4 | 5 | package matches_gen 6 | 7 | import ( 8 | "database/sql" 9 | ) 10 | 11 | type Match struct { 12 | ID string 13 | UserID string 14 | ServerID string 15 | Team1ID string 16 | Team2ID string 17 | Winner sql.NullString 18 | Cancelled bool 19 | StartTime sql.NullTime 20 | EndTime sql.NullTime 21 | MaxMaps int32 22 | Title string 23 | SkipVeto bool 24 | ApiKey string 25 | Team1Score uint32 26 | Team2Score uint32 27 | Forfeit sql.NullBool 28 | Status int32 29 | } 30 | -------------------------------------------------------------------------------- /backend/.testcoverage.yml: -------------------------------------------------------------------------------- 1 | # (mandatory) 2 | # Path to coverprofile file (output of `go test -coverprofile` command) 3 | profile: backend/cover.out 4 | 5 | # (optional) 6 | # When specified reported file paths will not contain local prefix in the output 7 | local-prefix: "github.com/FlowingSPDG/get5loader" 8 | 9 | # Holds coverage thresholds percentages, values should be in range [0-100] 10 | threshold: 11 | # (optional; default 0) 12 | # The minimum coverage that each file should have 13 | file: 0 14 | 15 | # (optional; default 0) 16 | # The minimum coverage that each package should have 17 | package: 0 18 | 19 | # (optional; default 0) 20 | # The minimum total coverage project should have 21 | total: 0 -------------------------------------------------------------------------------- /backend/sqlc/gameserver_query.sql: -------------------------------------------------------------------------------- 1 | -- name: GetGameServer :one 2 | SELECT * FROM game_servers 3 | WHERE id = ? LIMIT 1; 4 | 5 | -- name: AddGameServer :execresult 6 | INSERT INTO game_servers ( 7 | id, user_id, ip, port, rcon_password, display_name, is_public 8 | ) VALUES ( 9 | ?, ?, ?, ?, ?, ?, ? 10 | ); 11 | 12 | -- name: GetGameServersByUser :many 13 | SELECT * FROM game_servers 14 | WHERE user_id = ?; 15 | 16 | -- name: GetGameServersByUsers :many 17 | SELECT * FROM game_servers 18 | WHERE user_id IN (sqlc.slice('user_ids')); 19 | 20 | -- name: GetPublicGameServers :many 21 | SELECT * FROM game_servers 22 | WHERE is_public = TRUE ; 23 | 24 | -- name: DeleteGameServer :execresult 25 | DELETE FROM game_servers 26 | WHERE id = ?; -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[Feature Request]" 5 | labels: enhancement 6 | assignees: FlowingSPDG 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /backend/usecase/usecase_mock.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | //go:generate mockgen -source=match.go -destination=mock/match.go 4 | //go:generate mockgen -source=gameserver.go -destination=mock/gameserver.go 5 | //go:generate mockgen -source=get5.go -destination=mock/get5.go 6 | //go:generate mockgen -source=mapstats.go -destination=mock/mapstats.go 7 | //go:generate mockgen -source=match.go -destination=mock/match.go 8 | //go:generate mockgen -source=player.go -destination=mock/player.go 9 | //go:generate mockgen -source=playerstat.go -destination=mock/playerstat.go 10 | //go:generate mockgen -source=team.go -destination=mock/team.go 11 | //go:generate mockgen -source=user.go -destination=mock/user.go 12 | //go:generate mockgen -source=validate_jwt.go -destination=mock/validate_jwt.go 13 | -------------------------------------------------------------------------------- /backend/gateway/database/mysql/teams/generated/db.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.20.0 4 | 5 | package teams_gen 6 | 7 | import ( 8 | "context" 9 | "database/sql" 10 | ) 11 | 12 | type DBTX interface { 13 | ExecContext(context.Context, string, ...interface{}) (sql.Result, error) 14 | PrepareContext(context.Context, string) (*sql.Stmt, error) 15 | QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) 16 | QueryRowContext(context.Context, string, ...interface{}) *sql.Row 17 | } 18 | 19 | func New(db DBTX) *Queries { 20 | return &Queries{db: db} 21 | } 22 | 23 | type Queries struct { 24 | db DBTX 25 | } 26 | 27 | func (q *Queries) WithTx(tx *sql.Tx) *Queries { 28 | return &Queries{ 29 | db: tx, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /backend/gateway/database/mysql/users/generated/db.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.20.0 4 | 5 | package users_gen 6 | 7 | import ( 8 | "context" 9 | "database/sql" 10 | ) 11 | 12 | type DBTX interface { 13 | ExecContext(context.Context, string, ...interface{}) (sql.Result, error) 14 | PrepareContext(context.Context, string) (*sql.Stmt, error) 15 | QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) 16 | QueryRowContext(context.Context, string, ...interface{}) *sql.Row 17 | } 18 | 19 | func New(db DBTX) *Queries { 20 | return &Queries{db: db} 21 | } 22 | 23 | type Queries struct { 24 | db DBTX 25 | } 26 | 27 | func (q *Queries) WithTx(tx *sql.Tx) *Queries { 28 | return &Queries{ 29 | db: tx, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /backend/gateway/database/mysql/mapstats/generated/db.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.20.0 4 | 5 | package mapstats_gen 6 | 7 | import ( 8 | "context" 9 | "database/sql" 10 | ) 11 | 12 | type DBTX interface { 13 | ExecContext(context.Context, string, ...interface{}) (sql.Result, error) 14 | PrepareContext(context.Context, string) (*sql.Stmt, error) 15 | QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) 16 | QueryRowContext(context.Context, string, ...interface{}) *sql.Row 17 | } 18 | 19 | func New(db DBTX) *Queries { 20 | return &Queries{db: db} 21 | } 22 | 23 | type Queries struct { 24 | db DBTX 25 | } 26 | 27 | func (q *Queries) WithTx(tx *sql.Tx) *Queries { 28 | return &Queries{ 29 | db: tx, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /backend/gateway/database/mysql/matches/generated/db.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.20.0 4 | 5 | package matches_gen 6 | 7 | import ( 8 | "context" 9 | "database/sql" 10 | ) 11 | 12 | type DBTX interface { 13 | ExecContext(context.Context, string, ...interface{}) (sql.Result, error) 14 | PrepareContext(context.Context, string) (*sql.Stmt, error) 15 | QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) 16 | QueryRowContext(context.Context, string, ...interface{}) *sql.Row 17 | } 18 | 19 | func New(db DBTX) *Queries { 20 | return &Queries{db: db} 21 | } 22 | 23 | type Queries struct { 24 | db DBTX 25 | } 26 | 27 | func (q *Queries) WithTx(tx *sql.Tx) *Queries { 28 | return &Queries{ 29 | db: tx, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /backend/gateway/database/mysql/players/generated/db.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.20.0 4 | 5 | package players_gen 6 | 7 | import ( 8 | "context" 9 | "database/sql" 10 | ) 11 | 12 | type DBTX interface { 13 | ExecContext(context.Context, string, ...interface{}) (sql.Result, error) 14 | PrepareContext(context.Context, string) (*sql.Stmt, error) 15 | QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) 16 | QueryRowContext(context.Context, string, ...interface{}) *sql.Row 17 | } 18 | 19 | func New(db DBTX) *Queries { 20 | return &Queries{db: db} 21 | } 22 | 23 | type Queries struct { 24 | db DBTX 25 | } 26 | 27 | func (q *Queries) WithTx(tx *sql.Tx) *Queries { 28 | return &Queries{ 29 | db: tx, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /backend/gateway/database/mysql/gameservers/generated/db.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.20.0 4 | 5 | package gameservers_gen 6 | 7 | import ( 8 | "context" 9 | "database/sql" 10 | ) 11 | 12 | type DBTX interface { 13 | ExecContext(context.Context, string, ...interface{}) (sql.Result, error) 14 | PrepareContext(context.Context, string) (*sql.Stmt, error) 15 | QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) 16 | QueryRowContext(context.Context, string, ...interface{}) *sql.Row 17 | } 18 | 19 | func New(db DBTX) *Queries { 20 | return &Queries{db: db} 21 | } 22 | 23 | type Queries struct { 24 | db DBTX 25 | } 26 | 27 | func (q *Queries) WithTx(tx *sql.Tx) *Queries { 28 | return &Queries{ 29 | db: tx, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /backend/gateway/database/mysql/playerstats/generated/db.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.20.0 4 | 5 | package playerstats_gen 6 | 7 | import ( 8 | "context" 9 | "database/sql" 10 | ) 11 | 12 | type DBTX interface { 13 | ExecContext(context.Context, string, ...interface{}) (sql.Result, error) 14 | PrepareContext(context.Context, string) (*sql.Stmt, error) 15 | QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) 16 | QueryRowContext(context.Context, string, ...interface{}) *sql.Row 17 | } 18 | 19 | func New(db DBTX) *Queries { 20 | return &Queries{db: db} 21 | } 22 | 23 | type Queries struct { 24 | db DBTX 25 | } 26 | 27 | func (q *Queries) WithTx(tx *sql.Tx) *Queries { 28 | return &Queries{ 29 | db: tx, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /backend/service/password_hash/hash.go: -------------------------------------------------------------------------------- 1 | package hash 2 | 3 | import "golang.org/x/crypto/bcrypt" 4 | 5 | type PasswordHasher interface { 6 | Hash(password string) ([]byte, error) 7 | Compare(hash []byte, password string) error 8 | } 9 | 10 | type passwordHasher struct { 11 | cost int 12 | } 13 | 14 | func NewPasswordHasher(cost int) PasswordHasher { 15 | return &passwordHasher{ 16 | cost: cost, 17 | } 18 | } 19 | 20 | // Compare implements PasswordHasher. 21 | func (ph *passwordHasher) Compare(hash []byte, password string) error { 22 | return bcrypt.CompareHashAndPassword(hash, []byte(password)) 23 | } 24 | 25 | // Hash implements PasswordHasher. 26 | func (ph *passwordHasher) Hash(password string) ([]byte, error) { 27 | return bcrypt.GenerateFromPassword([]byte(password), ph.cost) 28 | } 29 | -------------------------------------------------------------------------------- /backend/usecase/validate_jwt.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "github.com/FlowingSPDG/get5loader/backend/entity" 5 | "github.com/FlowingSPDG/get5loader/backend/service/jwt" 6 | ) 7 | 8 | // ValidateJWT is interface for validating jwt token. 9 | type ValidateJWT interface { 10 | Validate(token string) (*entity.TokenUser, error) 11 | } 12 | 13 | type validateJWT struct { 14 | jwtService jwt.JWTService 15 | } 16 | 17 | func (vj *validateJWT) Validate(token string) (*entity.TokenUser, error) { 18 | tokenUser, err := vj.jwtService.ValidateJWT(token) 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | return tokenUser, nil 24 | } 25 | 26 | func NewValidateJWT( 27 | jwtService jwt.JWTService, 28 | ) ValidateJWT { 29 | return &validateJWT{ 30 | jwtService: jwtService, 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /backend/gateway/srcds/rcon/rcon.go: -------------------------------------------------------------------------------- 1 | package rcon 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/FlowingSPDG/go-steam" 7 | ) 8 | 9 | type RCONAdapter interface { 10 | Execute(ip string, port int, password string, cmd string) (string, error) 11 | } 12 | 13 | type rconAdapter struct { 14 | } 15 | 16 | func NewRCONAdapter() RCONAdapter { 17 | return &rconAdapter{} 18 | } 19 | 20 | func (r *rconAdapter) Execute(ip string, port int, password string, cmd string) (string, error) { 21 | ipport := fmt.Sprintf("%s:%d", ip, port) 22 | connectOptions := &steam.ConnectOptions{RCONPassword: password} 23 | rcon, err := steam.Connect(ipport, connectOptions) 24 | if err != nil { 25 | return "", err 26 | } 27 | defer rcon.Close() 28 | 29 | resp, err := rcon.Send(cmd) 30 | if err != nil { 31 | return "", err 32 | } 33 | return resp, nil 34 | } 35 | -------------------------------------------------------------------------------- /backend/controller/get5/auth.go: -------------------------------------------------------------------------------- 1 | package get5 2 | 3 | import ( 4 | "context" 5 | 6 | got5 "github.com/FlowingSPDG/Got5" 7 | ) 8 | 9 | type authController struct { 10 | } 11 | 12 | func NewGot5AuthController() got5.Auth { 13 | return &authController{} 14 | } 15 | 16 | // CheckDemoAuth implements controller.Auth. 17 | func (ac *authController) CheckDemoAuth(ctx context.Context, mid string, filename string, mapNumber int, serverID string, auth string) error { 18 | return nil 19 | } 20 | 21 | // EventAuth implements controller.Auth. 22 | func (ac *authController) EventAuth(ctx context.Context, serverID string, auth string) error { 23 | // TODO: 認証を実行するusecaseを呼び出す 24 | return nil 25 | } 26 | 27 | // MatchAuth implements controller.Auth. 28 | func (ac *authController) MatchAuth(ctx context.Context, mid string, auth string) error { 29 | // TODO: 認証を実行するusecaseを呼び出す 30 | return nil 31 | } 32 | -------------------------------------------------------------------------------- /backend/entity/server_status_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=SERVER_STATUS"; DO NOT EDIT. 2 | 3 | package entity 4 | 5 | import "strconv" 6 | 7 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[SERVER_STATUS_UNKNOWN-0] 12 | _ = x[SERVER_STATUS_STANDBY-1] 13 | _ = x[SERVER_STATUS_INUSE-2] 14 | } 15 | 16 | const _SERVER_STATUS_name = "SERVER_STATUS_UNKNOWNSERVER_STATUS_STANDBYSERVER_STATUS_INUSE" 17 | 18 | var _SERVER_STATUS_index = [...]uint8{0, 21, 42, 61} 19 | 20 | func (i SERVER_STATUS) String() string { 21 | if i < 0 || i >= SERVER_STATUS(len(_SERVER_STATUS_index)-1) { 22 | return "SERVER_STATUS(" + strconv.FormatInt(int64(i), 10) + ")" 23 | } 24 | return _SERVER_STATUS_name[_SERVER_STATUS_index[i]:_SERVER_STATUS_index[i+1]] 25 | } 26 | -------------------------------------------------------------------------------- /backend/gateway/srcds/query/query.go: -------------------------------------------------------------------------------- 1 | package query 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/rumblefrog/go-a2s" 8 | ) 9 | 10 | type queryAdapter struct { 11 | timeout time.Duration 12 | } 13 | 14 | type QueryAdapter interface { 15 | QueryPlayer(ip string, port int, password string) (uint8, error) 16 | } 17 | 18 | func NewQueryAdapter(timeout time.Duration) QueryAdapter { 19 | return &queryAdapter{ 20 | timeout: timeout, 21 | } 22 | } 23 | 24 | func (q *queryAdapter) QueryPlayer(ip string, port int, password string) (uint8, error) { 25 | ipport := fmt.Sprintf("%s:%d", ip, port) 26 | client, err := a2s.NewClient( 27 | ipport, 28 | a2s.TimeoutOption(q.timeout), 29 | ) 30 | if err != nil { 31 | return 0, err 32 | } 33 | defer client.Close() 34 | 35 | player, err := client.QueryPlayer() 36 | if err != nil { 37 | return 0, err 38 | } 39 | return player.Count, nil 40 | } 41 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report and describe bug 4 | title: "[BUG] " 5 | labels: bug 6 | assignees: FlowingSPDG 7 | 8 | --- 9 | 10 | **Where bugs related** 11 | API / Front(Vue) / Database(DB) / Game 12 | 13 | **Describe the bug** 14 | A clear and concise description of what the bug is. 15 | 16 | **To Reproduce** 17 | Steps to reproduce the behavior: 18 | 1. Go to '...' 19 | 2. Click on '....' 20 | 3. Scroll down to '....' 21 | 4. See error 22 | 23 | **Expected behavior** 24 | A clear and concise description of what you expected to happen. 25 | 26 | **Screenshots** 27 | If applicable, add screenshots to help explain your problem. 28 | 29 | **Desktop (please complete the following information):** 30 | - Server OS: [e.g. iOS] 31 | - Go version: [e.g. 1.13.5] 32 | - Browser [e.g. chrome, safari] 33 | - Version [e.g. 22] 34 | 35 | **Additional context** 36 | Add any other context about the problem here. 37 | -------------------------------------------------------------------------------- /backend/gateway/database/mysql/connector/connector.go: -------------------------------------------------------------------------------- 1 | package mysqlconnector 2 | 3 | import ( 4 | "database/sql" 5 | 6 | "github.com/FlowingSPDG/get5loader/backend/gateway/database" 7 | 8 | _ "github.com/go-sql-driver/mysql" // mysql driver 9 | ) 10 | 11 | type mysqlConnector struct { 12 | dsn string 13 | db *sql.DB 14 | } 15 | 16 | func NewMysqlConnector(dsn string) database.DBConnector { 17 | return &mysqlConnector{dsn: dsn} 18 | } 19 | 20 | func (mc *mysqlConnector) Open() error { 21 | // すでに接続済みの場合は何もしない 22 | if mc.db != nil { 23 | return nil 24 | } 25 | 26 | db, err := sql.Open("mysql", mc.dsn) 27 | if err != nil { 28 | return err 29 | } 30 | mc.db = db 31 | 32 | return nil 33 | } 34 | 35 | func (mc *mysqlConnector) GetConnection() *sql.DB { 36 | return mc.db 37 | } 38 | 39 | func (mc *mysqlConnector) Close() error { 40 | if err := mc.db.Close(); err != nil { 41 | return err 42 | } 43 | mc.db = nil 44 | return nil 45 | } 46 | -------------------------------------------------------------------------------- /backend/cmd/di/get5.go: -------------------------------------------------------------------------------- 1 | package di 2 | 3 | import ( 4 | got5 "github.com/FlowingSPDG/Got5" 5 | config "github.com/FlowingSPDG/get5loader/backend/cfg" 6 | "github.com/FlowingSPDG/get5loader/backend/controller/get5" 7 | mysqlconnector "github.com/FlowingSPDG/get5loader/backend/gateway/database/mysql/connector" 8 | "github.com/FlowingSPDG/get5loader/backend/service/uuid" 9 | ) 10 | 11 | func InitializeGet5EventController() got5.EventHandler { 12 | return get5.NewGot5EventController() 13 | } 14 | 15 | func InitializeGet5AuthController() got5.Auth { 16 | return get5.NewGot5AuthController() 17 | } 18 | 19 | func InitializeGet5matchLoaderController(cfg config.Config) got5.MatchLoader { 20 | uuidGenerator := uuid.NewUUIDGenerator() 21 | mysqlConnector := mustGetWriteConnector(cfg) 22 | mysqlUsersRepositoryConnector := mysqlconnector.NewMySQLRepositoryConnector(uuidGenerator, mysqlConnector) 23 | return get5.NewGot5MatchLoader(mysqlUsersRepositoryConnector) 24 | } 25 | -------------------------------------------------------------------------------- /backend/entity/match_status_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=MATCH_STATUS"; DO NOT EDIT. 2 | 3 | package entity 4 | 5 | import "strconv" 6 | 7 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[MATCH_STATUS_UNKNOWN-0] 12 | _ = x[MATCH_STATUS_PENDING-1] 13 | _ = x[MATCH_STATUS_LIVE-2] 14 | _ = x[MATCH_STATUS_FINISHED-3] 15 | _ = x[MATCH_STATUS_CANCELLED-4] 16 | } 17 | 18 | const _MATCH_STATUS_name = "MATCH_STATUS_UNKNOWNMATCH_STATUS_PENDINGMATCH_STATUS_LIVEMATCH_STATUS_FINISHEDMATCH_STATUS_CANCELLED" 19 | 20 | var _MATCH_STATUS_index = [...]uint8{0, 20, 40, 57, 78, 100} 21 | 22 | func (i MATCH_STATUS) String() string { 23 | if i < 0 || i >= MATCH_STATUS(len(_MATCH_STATUS_index)-1) { 24 | return "MATCH_STATUS(" + strconv.FormatInt(int64(i), 10) + ")" 25 | } 26 | return _MATCH_STATUS_name[_MATCH_STATUS_index[i]:_MATCH_STATUS_index[i+1]] 27 | } 28 | -------------------------------------------------------------------------------- /backend/sqlc/matches_schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `matches` ( 2 | `id` VARCHAR(36) NOT NULL PRIMARY KEY, 3 | `user_id` VARCHAR(36) NOT NULL, 4 | `server_id` VARCHAR(36) NOT NULL, 5 | `team1_id` VARCHAR(36) NOT NULL, 6 | `team2_id` VARCHAR(36) NOT NULL, 7 | `winner` VARCHAR(36) DEFAULT NULL, 8 | `cancelled` BOOLEAN NOT NULL DEFAULT FALSE, 9 | `start_time` datetime DEFAULT NULL, 10 | `end_time` datetime DEFAULT NULL, 11 | `max_maps` TINYINT NOT NULL, 12 | `title` varchar(60) NOT NULL, 13 | `skip_veto` BOOLEAN NOT NULL, 14 | `api_key` varchar(32) NOT NULL, 15 | `team1_score` TINYINT UNSIGNED NOT NULL DEFAULT 0, 16 | `team2_score` TINYINT UNSIGNED NOT NULL DEFAULT 0, 17 | `forfeit` BOOLEAN DEFAULT NULL, 18 | `status` TINYINT NOT NULL, 19 | FOREIGN KEY (`user_id`) REFERENCES users(`id`), 20 | FOREIGN KEY (`server_id`) REFERENCES game_servers(`id`), 21 | FOREIGN KEY (`team1_id`) REFERENCES teams(`id`), 22 | FOREIGN KEY (`team2_id`) REFERENCES teams(`id`), 23 | FOREIGN KEY (`winner`) REFERENCES teams(`id`) 24 | ); -------------------------------------------------------------------------------- /backend/gateway/database/context.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | var ( 10 | // RepositoryConnectorKey is a key to store repositoryConnector in gin.Context. 11 | RepositoryConnectorKey = "repositoryConnector" 12 | ) 13 | 14 | // GetConnectionFromGinContext gin.ContextからRepositoryConnectorを取得する 15 | func GetConnectionFromGinContext(c *gin.Context) RepositoryConnector { 16 | return c.MustGet(RepositoryConnectorKey).(RepositoryConnector) 17 | } 18 | 19 | // SetConnectionForGinContext gin.ContextにRepositoryConnectorを設定する 20 | func SetConnectionForGinContext(c *gin.Context, repositoryConnector RepositoryConnector) { 21 | c.Set(RepositoryConnectorKey, repositoryConnector) 22 | } 23 | 24 | func SetConnection(ctx context.Context, repositoryConnector RepositoryConnector) context.Context { 25 | return context.WithValue(ctx, RepositoryConnectorKey, repositoryConnector) 26 | } 27 | 28 | func GetConnection(ctx context.Context) RepositoryConnector { 29 | return ctx.Value(RepositoryConnectorKey).(RepositoryConnector) 30 | } 31 | -------------------------------------------------------------------------------- /backend/service/jwt/jwt_test.go: -------------------------------------------------------------------------------- 1 | package jwt_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/FlowingSPDG/get5loader/backend/entity" 7 | "github.com/FlowingSPDG/get5loader/backend/service/jwt" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestIssueJWT(t *testing.T) { 12 | tt := []struct { 13 | name string 14 | input *entity.User 15 | expected *entity.TokenUser 16 | }{ 17 | { 18 | name: "success", 19 | input: &entity.User{ 20 | ID: "1", 21 | SteamID: 76561198072054549, 22 | Admin: true, 23 | }, 24 | expected: &entity.TokenUser{ 25 | UserID: "1", 26 | SteamID: 76561198072054549, 27 | Admin: true, 28 | }, 29 | }, 30 | } 31 | 32 | for _, tc := range tt { 33 | t.Run(tc.name, func(t *testing.T) { 34 | j := jwt.NewJWTGateway([]byte("test")) 35 | actual, err := j.IssueJWT(tc.input.ID, tc.input.SteamID, tc.input.Admin) 36 | assert.NoError(t, err) 37 | 38 | token, err := j.ValidateJWT(actual) 39 | assert.NoError(t, err) 40 | 41 | assert.Equal(t, tc.expected, token) 42 | }) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /backend/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | route "github.com/FlowingSPDG/Got5/route/gin" 8 | config "github.com/FlowingSPDG/get5loader/backend/cfg" 9 | "github.com/FlowingSPDG/get5loader/backend/cmd/di" 10 | "github.com/gin-gonic/gin" 11 | ) 12 | 13 | func main() { 14 | r := gin.Default() 15 | 16 | cfg := config.GetConfig() 17 | 18 | v1 := r.Group("/api/v1") 19 | v1.Use(di.InitializeDatabaseConnectionMiddleware(cfg).Handle) 20 | v1.POST("/login", di.InitializeUserLoginController(cfg).Handle) 21 | v1.POST("/register", di.InitializeUserRegisterController(cfg).Handle) 22 | 23 | v1.POST("/query", di.InitializeGraphQLHandler(cfg)) 24 | v1.GET("/playground", di.InitializePlaygroundHandler()) 25 | 26 | g5 := v1.Group("/get5") 27 | ec := di.InitializeGet5EventController() 28 | ac := di.InitializeGet5AuthController() 29 | ml := di.InitializeGet5matchLoaderController(cfg) 30 | route.SetupEventHandlers(ec, ac, g5) 31 | route.SetupMatchLoadHandler(ml, ac, g5) 32 | 33 | addr := fmt.Sprintf(":%d", cfg.Port) 34 | log.Panicf("Failed to listen port %v", r.Run(addr)) 35 | } 36 | -------------------------------------------------------------------------------- /backend/gateway/database/mysql/playerstats/generated/models.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.20.0 4 | 5 | package playerstats_gen 6 | 7 | import () 8 | 9 | type PlayerStat struct { 10 | ID string 11 | MatchID string 12 | MapID string 13 | TeamID string 14 | SteamID uint64 15 | Name string 16 | Kills int32 17 | Deaths int32 18 | Roundsplayed uint32 19 | Assists int32 20 | FlashbangAssists uint32 21 | Teamkills uint32 22 | Suicides uint32 23 | HeadshotKills uint32 24 | Damage uint32 25 | BombPlants uint32 26 | BombDefuses uint32 27 | V1 uint32 28 | V2 uint32 29 | V3 uint32 30 | V4 uint32 31 | V5 uint32 32 | K1 uint32 33 | K2 uint32 34 | K3 uint32 35 | K4 uint32 36 | K5 uint32 37 | FirstdeathCt uint32 38 | FirstdeathT uint32 39 | FirstkillCt uint32 40 | FirstkillT uint32 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Shugo Kawamura 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /backend/controller/gin/db.go: -------------------------------------------------------------------------------- 1 | package gin_controller 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | 8 | "github.com/FlowingSPDG/get5loader/backend/gateway/database" 9 | "github.com/FlowingSPDG/get5loader/backend/service/uuid" 10 | ) 11 | 12 | type DatabaseConnectionMiddleware interface { 13 | Handle(c *gin.Context) 14 | } 15 | 16 | type databaseConnectionMiddleware struct { 17 | uuidGenerator uuid.UUIDGenerator 18 | connector database.RepositoryConnector 19 | } 20 | 21 | func NewDatabaseConnectionMiddleware( 22 | uuidGenerator uuid.UUIDGenerator, 23 | connector database.RepositoryConnector, 24 | ) DatabaseConnectionMiddleware { 25 | return &databaseConnectionMiddleware{ 26 | uuidGenerator: uuidGenerator, 27 | connector: connector, 28 | } 29 | } 30 | 31 | func (d *databaseConnectionMiddleware) Handle(c *gin.Context) { 32 | if err := d.connector.Open(); err != nil { 33 | c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{ 34 | "error": "internal server error", 35 | }) 36 | return 37 | } 38 | defer d.connector.Close() 39 | 40 | database.SetConnectionForGinContext(c, d.connector) 41 | 42 | c.Next() 43 | } 44 | -------------------------------------------------------------------------------- /backend/usecase/playerstat.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/FlowingSPDG/get5loader/backend/entity" 7 | "github.com/FlowingSPDG/get5loader/backend/gateway/database" 8 | ) 9 | 10 | type PlayerStat interface { 11 | BatchGetPlayerStatsByMapstat(ctx context.Context, mapstatIDs []entity.MapStatsID) (map[entity.MapStatsID][]*entity.PlayerStat, error) 12 | } 13 | 14 | type playerStat struct { 15 | } 16 | 17 | func NewPlayerStat() PlayerStat { 18 | return &playerStat{} 19 | } 20 | 21 | // BatchGetPlayerStatsByMapstat implements PlayerStat. 22 | func (ps *playerStat) BatchGetPlayerStatsByMapstat(ctx context.Context, mapstatIDs []entity.MapStatsID) (map[entity.MapStatsID][]*entity.PlayerStat, error) { 23 | repositoryConnector := database.GetConnection(ctx) 24 | 25 | playerStatRepository := repositoryConnector.GetPlayerStatRepository() 26 | 27 | playerStats, err := playerStatRepository.GetPlayerStatsByMapstats(ctx, mapstatIDs) 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | ret := make(map[entity.MapStatsID][]*entity.PlayerStat, len(mapstatIDs)) 33 | for teamID, players := range playerStats { 34 | ret[teamID] = convertPlayerStats(players) 35 | } 36 | 37 | return ret, nil 38 | } 39 | -------------------------------------------------------------------------------- /backend/service/jwt/jwt.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import ( 4 | "github.com/golang-jwt/jwt/v5" 5 | 6 | "github.com/FlowingSPDG/get5loader/backend/entity" 7 | ) 8 | 9 | type JWTService interface { 10 | IssueJWT(userID entity.UserID, steamID entity.SteamID, admin bool) (string, error) 11 | ValidateJWT(token string) (*entity.TokenUser, error) 12 | } 13 | 14 | type jwtService struct { 15 | key []byte 16 | } 17 | 18 | func NewJWTGateway(key []byte) JWTService { 19 | return &jwtService{ 20 | key: key, 21 | } 22 | } 23 | 24 | func (j *jwtService) IssueJWT(userID entity.UserID, steamID entity.SteamID, admin bool) (string, error) { 25 | token := jwt.NewWithClaims(jwt.GetSigningMethod("HS256"), &entity.TokenUser{ 26 | UserID: userID, 27 | SteamID: steamID, 28 | Admin: admin, 29 | }) 30 | 31 | signed, err := token.SignedString(j.key) 32 | if err != nil { 33 | return "", err 34 | } 35 | 36 | return signed, nil 37 | } 38 | 39 | func (j *jwtService) ValidateJWT(token string) (*entity.TokenUser, error) { 40 | claims := &entity.TokenUser{} 41 | _, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (any, error) { 42 | return j.key, nil 43 | }) 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | return claims, nil 49 | } 50 | -------------------------------------------------------------------------------- /backend/cfg/cfg.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/caarlos0/env/v9" 5 | ) 6 | 7 | // Config Configration Struct for config.ini 8 | type Config struct { 9 | Port int `env:"PORT" envDefault:"8080"` 10 | SteamAPIKey string `env:"STEAM_API_KEY"` 11 | DefaultPage string `env:"DEFAULT_PAGE"` 12 | 13 | // Database writing 14 | DBWriteHost string `env:"DB_WRITE_HOST,required"` 15 | DBWritePort int `env:"DB_WRITE_PORT" envDefault:"3306,required"` 16 | DBWriteUser string `env:"DB_WRITE_USER,required"` 17 | DBWritePass string `env:"DB_WRITE_PASS,unset,required"` 18 | DBWriteName string `env:"DB_WRITE_NAME,required"` 19 | 20 | // Database reading 21 | DBReadHost string `env:"DB_READ_HOST"` 22 | DBReadPort int `env:"DB_READ_PORT" envDefault:"3306"` 23 | DBReadUser string `env:"DB_READ_USER"` 24 | DBReadPass string `env:"DB_READ_PASS,unset"` 25 | DBReadName string `env:"DB_READ_NAME"` 26 | 27 | // UserMaxResources UserMaxResources 28 | SecretKey string `env:"SECRET_KEY,unset,required"` 29 | } 30 | 31 | /* 32 | type UserMaxResources struct { 33 | Servers uint16 34 | Teams uint16 35 | Matches uint16 36 | } 37 | */ 38 | 39 | func GetConfig() Config { 40 | cfg := Config{} 41 | if err := env.Parse(&cfg); err != nil { 42 | panic(err) 43 | } 44 | return cfg 45 | } 46 | -------------------------------------------------------------------------------- /backend/sqlc/matches_query.sql: -------------------------------------------------------------------------------- 1 | -- name: GetMatch :one 2 | SELECT * FROM matches 3 | WHERE id = ? LIMIT 1; 4 | 5 | -- name: AddMatch :execresult 6 | INSERT INTO matches ( 7 | id, user_id, server_id, team1_id, team2_id, start_time, end_time, max_maps, title, skip_veto, api_key, status 8 | ) VALUES ( 9 | ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? 10 | ); 11 | 12 | -- name: GetMatchesByUser :many 13 | SELECT * FROM matches 14 | WHERE user_id = ?; 15 | 16 | -- name: GetMatchesByUsers :many 17 | SELECT * FROM matches 18 | WHERE user_id IN (sqlc.slice('user_ids')); 19 | 20 | -- name: GetMatchesByTeam :many 21 | SELECT * FROM matches 22 | WHERE team1_id = ? OR team2_id = ?; 23 | 24 | -- name: GetMatchesByWinner :many 25 | SELECT * FROM matches 26 | WHERE winner = ?; 27 | 28 | -- name: UpdateTeam1Score :execresult 29 | UPDATE matches 30 | SET team1_score = ? 31 | WHERE id = ?; 32 | 33 | -- name: UpdateTeam2Score :execresult 34 | UPDATE matches 35 | SET team2_score = ? 36 | WHERE id = ?; 37 | 38 | -- name: UpdateMatchWinner :execresult 39 | UPDATE matches 40 | SET winner = ?, end_time = ?, forfeit = ?, team1_score = ?, team2_score = ? 41 | WHERE id = ?; 42 | 43 | -- name: CancelMatch :execresult 44 | UPDATE matches 45 | SET status = 4 46 | WHERE id = ?; 47 | 48 | -- name: StartMatch :execresult 49 | UPDATE matches 50 | SET start_time = ?, status = 2 51 | WHERE id = ?; -------------------------------------------------------------------------------- /backend/sqlc/sqlc.yaml: -------------------------------------------------------------------------------- 1 | version: 1 2 | packages: 3 | - path: "../gateway/database/mysql/users/generated" 4 | name: "users_gen" 5 | engine: "mysql" 6 | schema: "users_schema.sql" 7 | queries: "users_query.sql" 8 | - path: "../gateway/database/mysql/gameservers/generated" 9 | name: "gameservers_gen" 10 | engine: "mysql" 11 | schema: "gameserver_schema.sql" 12 | queries: "gameserver_query.sql" 13 | - path: "../gateway/database/mysql/matches/generated" 14 | name: "matches_gen" 15 | engine: "mysql" 16 | schema: "matches_schema.sql" 17 | queries: "matches_query.sql" 18 | - path: "../gateway/database/mysql/mapstats/generated" 19 | name: "mapstats_gen" 20 | engine: "mysql" 21 | schema: "mapstats_schema.sql" 22 | queries: "mapstats_query.sql" 23 | - path: "../gateway/database/mysql/players/generated" 24 | name: "players_gen" 25 | engine: "mysql" 26 | schema: "players_schema.sql" 27 | queries: "players_query.sql" 28 | - path: "../gateway/database/mysql/teams/generated" 29 | name: "teams_gen" 30 | engine: "mysql" 31 | schema: "teams_schema.sql" 32 | queries: "teams_query.sql" 33 | - path: "../gateway/database/mysql/playerstats/generated" 34 | name: "playerstats_gen" 35 | engine: "mysql" 36 | schema: "playerstats_schema.sql" 37 | queries: "playerstats_query.sql" -------------------------------------------------------------------------------- /.github/workflows/go_coverage.yaml: -------------------------------------------------------------------------------- 1 | name: Go test coverage check 2 | on: pull_request 3 | jobs: 4 | test: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v3 8 | with: 9 | ref: ${{ github.event.pull_request.head.sha }} 10 | - uses: actions/setup-go@v3 11 | with: 12 | go-version: 1.21.0 13 | - name: run coverage 14 | run: | 15 | make coverage 16 | - name: Find existing comment 17 | id: find_comment 18 | uses: peter-evans/find-comment@v2 19 | with: 20 | issue-number: ${{ github.event.number }} 21 | body-includes: "## Test Coverage Report" 22 | - name: Delete existing comment 23 | if: steps.find_comment.outputs.comment-id 24 | run: | 25 | curl \ 26 | -X DELETE \ 27 | -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ 28 | -H "Accept: application/vnd.github+json" \ 29 | "https://api.github.com/repos/${{ github.repository }}/issues/comments/${{ steps.find_comment.outputs.comment-id }}" 30 | - name: Comment coverage on PR 31 | uses: peter-evans/create-or-update-comment@v3 32 | with: 33 | token: ${{ secrets.GITHUB_TOKEN }} 34 | issue-number: ${{ github.event.number }} 35 | body-path: 'backend/coverage.txt' 36 | -------------------------------------------------------------------------------- /backend/graph/dataloaders/teams_by_teamids.go: -------------------------------------------------------------------------------- 1 | package dataloaders 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | 7 | "github.com/FlowingSPDG/get5loader/backend/entity" 8 | "github.com/FlowingSPDG/get5loader/backend/usecase" 9 | "github.com/graph-gophers/dataloader/v7" 10 | ) 11 | 12 | type teamsLoader struct { 13 | team usecase.Team 14 | } 15 | 16 | func (u *teamsLoader) BatchGetTeams(ctx context.Context, IDs []entity.TeamID) []*dataloader.Result[*entity.Team] { 17 | // 引数と戻り値のスライスlenは等しくする 18 | results := make([]*dataloader.Result[*entity.Team], len(IDs)) 19 | for i := range results { 20 | results[i] = &dataloader.Result[*entity.Team]{ 21 | Error: errors.New("not found"), 22 | } 23 | } 24 | 25 | // 検索条件であるIDが、引数でもらったIDsスライスの何番目のインデックスに格納されていたのか検索できるようにmap化する 26 | indexs := make(map[entity.TeamID]int, len(IDs)) 27 | for i, ID := range IDs { 28 | indexs[ID] = i 29 | } 30 | 31 | userTeams, err := u.team.BatchGetTeams(ctx, IDs) 32 | 33 | // 取得結果を、戻り値resultの中の適切な場所に格納する 34 | for _, team := range userTeams { 35 | var result *dataloader.Result[*entity.Team] 36 | if err != nil { 37 | result = &dataloader.Result[*entity.Team]{ 38 | Error: err, 39 | } 40 | continue 41 | } 42 | result = &dataloader.Result[*entity.Team]{ 43 | Error: nil, 44 | Data: team, 45 | } 46 | results[indexs[team.ID]] = result 47 | } 48 | return results 49 | } 50 | -------------------------------------------------------------------------------- /backend/graph/dataloaders/teams_by_userid.go: -------------------------------------------------------------------------------- 1 | package dataloaders 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/FlowingSPDG/get5loader/backend/entity" 7 | "github.com/FlowingSPDG/get5loader/backend/usecase" 8 | "github.com/graph-gophers/dataloader/v7" 9 | ) 10 | 11 | type userTeamLoader struct { 12 | team usecase.Team 13 | } 14 | 15 | func (u *userTeamLoader) BatchGetTeams(ctx context.Context, IDs []entity.UserID) []*dataloader.Result[[]*entity.Team] { 16 | // 引数と戻り値のスライスlenは等しくする 17 | results := make([]*dataloader.Result[[]*entity.Team], len(IDs)) 18 | for i := range results { 19 | results[i] = &dataloader.Result[[]*entity.Team]{ 20 | Data: []*entity.Team{}, 21 | } 22 | } 23 | 24 | // 検索条件であるIDが、引数でもらったIDsスライスの何番目のインデックスに格納されていたのか検索できるようにmap化する 25 | indexs := make(map[entity.UserID]int, len(IDs)) 26 | for i, ID := range IDs { 27 | indexs[ID] = i 28 | } 29 | 30 | userTeams, err := u.team.BatchGetTeamsByUsers(ctx, IDs) 31 | 32 | // 取得結果を、戻り値resultの中の適切な場所に格納する 33 | for userID, teams := range userTeams { 34 | var result *dataloader.Result[[]*entity.Team] 35 | if err != nil { 36 | result = &dataloader.Result[[]*entity.Team]{ 37 | Error: err, 38 | } 39 | continue 40 | } 41 | result = &dataloader.Result[[]*entity.Team]{ 42 | Error: nil, 43 | Data: teams, 44 | } 45 | results[indexs[userID]] = result 46 | } 47 | return results 48 | } 49 | -------------------------------------------------------------------------------- /backend/graph/dataloaders/matches_by_userid.go: -------------------------------------------------------------------------------- 1 | package dataloaders 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/FlowingSPDG/get5loader/backend/entity" 7 | "github.com/FlowingSPDG/get5loader/backend/usecase" 8 | "github.com/graph-gophers/dataloader/v7" 9 | ) 10 | 11 | type userMatchLoader struct { 12 | match usecase.Match 13 | } 14 | 15 | func (u *userMatchLoader) BatchGetMatches(ctx context.Context, IDs []entity.UserID) []*dataloader.Result[[]*entity.Match] { 16 | // 引数と戻り値のスライスlenは等しくする 17 | results := make([]*dataloader.Result[[]*entity.Match], len(IDs)) 18 | for i := range results { 19 | results[i] = &dataloader.Result[[]*entity.Match]{ 20 | Data: []*entity.Match{}, 21 | } 22 | } 23 | 24 | // 検索条件であるIDが、引数でもらったIDsスライスの何番目のインデックスに格納されていたのか検索できるようにmap化する 25 | indexs := make(map[entity.UserID]int, len(IDs)) 26 | for i, ID := range IDs { 27 | indexs[ID] = i 28 | } 29 | 30 | userMatches, err := u.match.BatchGetMatchesByUser(ctx, IDs) 31 | 32 | // 取得結果を、戻り値resultの中の適切な場所に格納する 33 | for userID, matches := range userMatches { 34 | var result *dataloader.Result[[]*entity.Match] 35 | if err != nil { 36 | result = &dataloader.Result[[]*entity.Match]{ 37 | Error: err, 38 | } 39 | continue 40 | } 41 | result = &dataloader.Result[[]*entity.Match]{ 42 | Error: nil, 43 | Data: matches, 44 | } 45 | results[indexs[userID]] = result 46 | } 47 | return results 48 | } 49 | -------------------------------------------------------------------------------- /backend/graph/dataloaders/players_by_teamid.go: -------------------------------------------------------------------------------- 1 | package dataloaders 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/graph-gophers/dataloader/v7" 7 | 8 | "github.com/FlowingSPDG/get5loader/backend/entity" 9 | "github.com/FlowingSPDG/get5loader/backend/usecase" 10 | ) 11 | 12 | type teamPlayersLoader struct { 13 | player usecase.Player 14 | } 15 | 16 | func (t *teamPlayersLoader) BatchGetPlayers(ctx context.Context, IDs []entity.TeamID) []*dataloader.Result[[]*entity.Player] { 17 | // 引数と戻り値のスライスlenは等しくする 18 | results := make([]*dataloader.Result[[]*entity.Player], len(IDs)) 19 | for i := range results { 20 | results[i] = &dataloader.Result[[]*entity.Player]{ 21 | Data: []*entity.Player{}, 22 | } 23 | } 24 | 25 | // 検索条件であるIDが、引数でもらったIDsスライスの何番目のインデックスに格納されていたのか検索できるようにmap化する 26 | indexs := make(map[entity.TeamID]int, len(IDs)) 27 | for i, ID := range IDs { 28 | indexs[ID] = i 29 | } 30 | 31 | teamPlayers, err := t.player.BatchGetPlayersByTeam(ctx, IDs) 32 | 33 | // 取得結果を、戻り値resultの中の適切な場所に格納する 34 | for teamID, players := range teamPlayers { 35 | var result *dataloader.Result[[]*entity.Player] 36 | if err != nil { 37 | result = &dataloader.Result[[]*entity.Player]{ 38 | Error: err, 39 | } 40 | continue 41 | } 42 | result = &dataloader.Result[[]*entity.Player]{ 43 | Error: nil, 44 | Data: players, 45 | } 46 | results[indexs[teamID]] = result 47 | } 48 | 49 | return results 50 | } 51 | -------------------------------------------------------------------------------- /backend/graph/dataloaders/mapstats_by_matchid.go: -------------------------------------------------------------------------------- 1 | package dataloaders 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/graph-gophers/dataloader/v7" 7 | 8 | "github.com/FlowingSPDG/get5loader/backend/entity" 9 | "github.com/FlowingSPDG/get5loader/backend/usecase" 10 | ) 11 | 12 | type matchMapstat struct { 13 | mapstat usecase.Mapstat 14 | } 15 | 16 | func (m *matchMapstat) BatchGetMapStats(ctx context.Context, IDs []entity.MatchID) []*dataloader.Result[[]*entity.MapStat] { 17 | // 引数と戻り値のスライスlenは等しくする 18 | results := make([]*dataloader.Result[[]*entity.MapStat], len(IDs)) 19 | for i := range results { 20 | results[i] = &dataloader.Result[[]*entity.MapStat]{ 21 | Data: []*entity.MapStat{}, 22 | } 23 | } 24 | 25 | // 検索条件であるIDが、引数でもらったIDsスライスの何番目のインデックスに格納されていたのか検索できるようにmap化する 26 | indexs := make(map[entity.MatchID]int, len(IDs)) 27 | for i, ID := range IDs { 28 | indexs[ID] = i 29 | } 30 | 31 | matchMapstats, err := m.mapstat.BatchGetMapstatsByMatch(ctx, IDs) 32 | 33 | // 取得結果を、戻り値resultの中の適切な場所に格納する 34 | for matchID, mapstats := range matchMapstats { 35 | var result *dataloader.Result[[]*entity.MapStat] 36 | if err != nil { 37 | result = &dataloader.Result[[]*entity.MapStat]{ 38 | Error: err, 39 | } 40 | continue 41 | } 42 | result = &dataloader.Result[[]*entity.MapStat]{ 43 | Error: nil, 44 | Data: mapstats, 45 | } 46 | results[indexs[matchID]] = result 47 | } 48 | return results 49 | } 50 | -------------------------------------------------------------------------------- /backend/graph/dataloaders/servers_by_userid.go: -------------------------------------------------------------------------------- 1 | package dataloaders 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/FlowingSPDG/get5loader/backend/entity" 7 | "github.com/FlowingSPDG/get5loader/backend/usecase" 8 | "github.com/graph-gophers/dataloader/v7" 9 | ) 10 | 11 | type userServersLoader struct { 12 | server usecase.GameServer 13 | } 14 | 15 | func (u *userServersLoader) BatchGetServers(ctx context.Context, IDs []entity.UserID) []*dataloader.Result[[]*entity.GameServer] { 16 | // 引数と戻り値のスライスlenは等しくする 17 | results := make([]*dataloader.Result[[]*entity.GameServer], len(IDs)) 18 | for i := range results { 19 | results[i] = &dataloader.Result[[]*entity.GameServer]{ 20 | Data: []*entity.GameServer{}, 21 | } 22 | } 23 | 24 | // 検索条件であるIDが、引数でもらったIDsスライスの何番目のインデックスに格納されていたのか検索できるようにmap化する 25 | indexs := make(map[entity.UserID]int, len(IDs)) 26 | for i, ID := range IDs { 27 | indexs[ID] = i 28 | } 29 | 30 | userServers, err := u.server.BatchGetGameServersByUser(ctx, IDs) 31 | 32 | // 取得結果を、戻り値resultの中の適切な場所に格納する 33 | for userID, servers := range userServers { 34 | var result *dataloader.Result[[]*entity.GameServer] 35 | if err != nil { 36 | result = &dataloader.Result[[]*entity.GameServer]{ 37 | Error: err, 38 | } 39 | continue 40 | } 41 | result = &dataloader.Result[[]*entity.GameServer]{ 42 | Error: nil, 43 | Data: servers, 44 | } 45 | results[indexs[userID]] = result 46 | } 47 | return results 48 | } 49 | -------------------------------------------------------------------------------- /backend/controller/gin/login.go: -------------------------------------------------------------------------------- 1 | package gin_controller 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | 8 | "github.com/FlowingSPDG/get5loader/backend/entity" 9 | gin_presenter "github.com/FlowingSPDG/get5loader/backend/presenter/gin" 10 | "github.com/FlowingSPDG/get5loader/backend/usecase" 11 | ) 12 | 13 | type UserLoginController interface { 14 | Handle(c *gin.Context) 15 | } 16 | 17 | type userLoginController struct { 18 | uc usecase.User 19 | presenter gin_presenter.JWTPresenter 20 | } 21 | 22 | func NewUserLoginController( 23 | uc usecase.User, 24 | presenter gin_presenter.JWTPresenter, 25 | ) UserLoginController { 26 | return &userLoginController{ 27 | uc: uc, 28 | presenter: presenter, 29 | } 30 | } 31 | 32 | type userLoginRequest struct { 33 | SteamID entity.SteamID `json:"steamid"` 34 | Password string `json:"password"` 35 | } 36 | 37 | // Handle implements UserLoginController. 38 | func (ulc *userLoginController) Handle(c *gin.Context) { 39 | var req userLoginRequest 40 | if err := c.ShouldBindJSON(&req); err != nil { 41 | c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{ 42 | "error": "invalid request", 43 | }) 44 | return 45 | } 46 | 47 | jwt, err := ulc.uc.IssueJWTBySteamID(c, req.SteamID, req.Password) 48 | if err != nil { 49 | c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ 50 | "error": "unauthorized", 51 | }) 52 | return 53 | } 54 | 55 | ulc.presenter.Handle(c, jwt) 56 | } 57 | -------------------------------------------------------------------------------- /backend/graph/dataloaders/playerstats_by_mapstatid.go: -------------------------------------------------------------------------------- 1 | package dataloaders 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/graph-gophers/dataloader/v7" 7 | 8 | "github.com/FlowingSPDG/get5loader/backend/entity" 9 | "github.com/FlowingSPDG/get5loader/backend/usecase" 10 | ) 11 | 12 | type mapstatPlayerStats struct { 13 | playerstat usecase.PlayerStat 14 | } 15 | 16 | func (m *mapstatPlayerStats) BatchGetMapstats(ctx context.Context, IDs []entity.MapStatsID) []*dataloader.Result[[]*entity.PlayerStat] { 17 | // 引数と戻り値のスライスlenは等しくする 18 | results := make([]*dataloader.Result[[]*entity.PlayerStat], len(IDs)) 19 | for i := range results { 20 | results[i] = &dataloader.Result[[]*entity.PlayerStat]{ 21 | Data: []*entity.PlayerStat{}, 22 | } 23 | } 24 | 25 | // 検索条件であるIDが、引数でもらったIDsスライスの何番目のインデックスに格納されていたのか検索できるようにmap化する 26 | indexs := make(map[entity.MapStatsID]int, len(IDs)) 27 | for i, ID := range IDs { 28 | indexs[ID] = i 29 | } 30 | 31 | matchMapstats, err := m.playerstat.BatchGetPlayerStatsByMapstat(ctx, IDs) 32 | 33 | // 取得結果を、戻り値resultの中の適切な場所に格納する 34 | for matchID, mapstats := range matchMapstats { 35 | var result *dataloader.Result[[]*entity.PlayerStat] 36 | if err != nil { 37 | result = &dataloader.Result[[]*entity.PlayerStat]{ 38 | Error: err, 39 | } 40 | continue 41 | } 42 | result = &dataloader.Result[[]*entity.PlayerStat]{ 43 | Error: nil, 44 | Data: mapstats, 45 | } 46 | results[indexs[matchID]] = result 47 | } 48 | return results 49 | } 50 | -------------------------------------------------------------------------------- /backend/usecase/get5.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/FlowingSPDG/get5loader/backend/entity" 7 | "github.com/FlowingSPDG/get5loader/backend/gateway/database" 8 | ) 9 | 10 | type Get5 interface { 11 | GetMatch(ctx context.Context, matchID entity.MatchID) (*entity.Get5Match, error) 12 | } 13 | 14 | type get5 struct { 15 | } 16 | 17 | func NewGet5() Get5 { 18 | return &get5{} 19 | } 20 | 21 | // GetMatch implements Get5. 22 | func (g *get5) GetMatch(ctx context.Context, matchID entity.MatchID) (*entity.Get5Match, error) { 23 | repositoryConnector := database.GetConnection(ctx) 24 | 25 | matchRepository := repositoryConnector.GetMatchesRepository() 26 | teamRepository := repositoryConnector.GetTeamsRepository() 27 | playerRepository := repositoryConnector.GetPlayersRepository() 28 | 29 | match, err := matchRepository.GetMatch(ctx, matchID) 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | team1, err := teamRepository.GetTeam(ctx, match.Team1ID) 35 | if err != nil { 36 | return nil, err 37 | } 38 | team1players, err := playerRepository.GetPlayersByTeam(ctx, match.Team1ID) 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | team2, err := teamRepository.GetTeam(ctx, match.Team2ID) 44 | if err != nil { 45 | return nil, err 46 | } 47 | team2players, err := playerRepository.GetPlayersByTeam(ctx, match.Team2ID) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | return convertGet5Match(match, team1, team2, team1players, team2players), nil 53 | } 54 | -------------------------------------------------------------------------------- /backend/gateway/database/mysql/connector/connector_tx.go: -------------------------------------------------------------------------------- 1 | package mysqlconnector 2 | 3 | import ( 4 | "database/sql" 5 | 6 | "github.com/FlowingSPDG/get5loader/backend/gateway/database" 7 | ) 8 | 9 | type mysqlConnectorWithTx struct { 10 | mysqlConnector 11 | tx *sql.Tx // since mysqlConnectorWithTx only stores one transaction, only up to one transaction can be used at a time. 12 | } 13 | 14 | func NewMysqlConnectorWithTx(dsn string) database.DBConnectorWithTx { 15 | return &mysqlConnectorWithTx{mysqlConnector: mysqlConnector{dsn: dsn}} 16 | } 17 | 18 | func (mctx *mysqlConnectorWithTx) Open() error { 19 | return mctx.mysqlConnector.Open() 20 | } 21 | 22 | func (mctx *mysqlConnectorWithTx) GetConnection() *sql.DB { 23 | return mctx.mysqlConnector.GetConnection() 24 | } 25 | 26 | // BeginTx implements database.DBConnectorWithTx. 27 | func (mctx *mysqlConnectorWithTx) BeginTx() error { 28 | tx, err := mctx.db.Begin() 29 | if err != nil { 30 | return err 31 | } 32 | mctx.tx = tx 33 | return nil 34 | } 35 | 36 | // GetTx implements database.DBConnectorWithTx. 37 | func (mctx *mysqlConnectorWithTx) GetTx() *sql.Tx { 38 | return mctx.tx 39 | } 40 | 41 | func (mctx *mysqlConnectorWithTx) Close() error { 42 | return mctx.mysqlConnector.Close() 43 | } 44 | 45 | // Commit implements database.DBConnectorWithTx. 46 | func (mctx *mysqlConnectorWithTx) Commit() error { 47 | return mctx.tx.Commit() 48 | } 49 | 50 | // Rollback implements database.DBConnectorWithTx. 51 | func (mctx *mysqlConnectorWithTx) Rollback() error { 52 | return mctx.tx.Rollback() 53 | } 54 | -------------------------------------------------------------------------------- /backend/controller/gin/register.go: -------------------------------------------------------------------------------- 1 | package gin_controller 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | 8 | "github.com/FlowingSPDG/get5loader/backend/entity" 9 | gin_presenter "github.com/FlowingSPDG/get5loader/backend/presenter/gin" 10 | "github.com/FlowingSPDG/get5loader/backend/usecase" 11 | ) 12 | 13 | type UserRegisterController interface { 14 | Handle(c *gin.Context) 15 | } 16 | 17 | type userRegisterController struct { 18 | uc usecase.User 19 | presenter gin_presenter.JWTPresenter 20 | } 21 | 22 | func NewUserRegisterController( 23 | uc usecase.User, 24 | presenter gin_presenter.JWTPresenter, 25 | ) UserRegisterController { 26 | return &userRegisterController{ 27 | uc: uc, 28 | presenter: presenter, 29 | } 30 | } 31 | 32 | type userRegisterRequest struct { 33 | Name string `json:"name"` 34 | SteamID entity.SteamID `json:"steamid"` 35 | Password string `json:"password"` 36 | } 37 | 38 | // Handle implements UserLoginController. 39 | func (urc *userRegisterController) Handle(c *gin.Context) { 40 | var req userRegisterRequest 41 | if err := c.ShouldBindJSON(&req); err != nil { 42 | c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{ 43 | "error": "invalid request", 44 | }) 45 | return 46 | } 47 | 48 | jwt, err := urc.uc.Register(c, req.SteamID, req.Name, false, req.Password) 49 | if err != nil { 50 | c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ 51 | "error": "unauthorized" + err.Error(), 52 | }) 53 | return 54 | } 55 | 56 | urc.presenter.Handle(c, jwt) 57 | } 58 | -------------------------------------------------------------------------------- /backend/sqlc/playerstats_schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `player_stats` ( 2 | `id` VARCHAR(36) NOT NULL PRIMARY KEY, 3 | `match_id` VARCHAR(36) NOT NULL, 4 | `map_id` VARCHAR(36) NOT NULL, 5 | `team_id` VARCHAR(36) NOT NULL, 6 | `steam_id` BIGINT UNSIGNED NOT NULL, 7 | `name` varchar(40) NOT NULL, 8 | `kills` SMALLINT NOT NULL, 9 | `deaths` int(11) NOT NULL, 10 | `roundsplayed` TINYINT UNSIGNED NOT NULL, 11 | `assists` TINYINT NOT NULL, 12 | `flashbang_assists` TINYINT UNSIGNED NOT NULL, 13 | `teamkills` TINYINT UNSIGNED NOT NULL, 14 | `suicides` TINYINT UNSIGNED NOT NULL, 15 | `headshot_kills` TINYINT UNSIGNED NOT NULL, 16 | `damage` MEDIUMINT UNSIGNED NOT NULL, 17 | `bomb_plants` TINYINT UNSIGNED NOT NULL, 18 | `bomb_defuses` TINYINT UNSIGNED NOT NULL, 19 | `v1` TINYINT UNSIGNED NOT NULL, 20 | `v2` TINYINT UNSIGNED NOT NULL, 21 | `v3` TINYINT UNSIGNED NOT NULL, 22 | `v4` TINYINT UNSIGNED NOT NULL, 23 | `v5` TINYINT UNSIGNED NOT NULL, 24 | `k1` TINYINT UNSIGNED NOT NULL, 25 | `k2` TINYINT UNSIGNED NOT NULL, 26 | `k3` TINYINT UNSIGNED NOT NULL, 27 | `k4` TINYINT UNSIGNED NOT NULL, 28 | `k5` TINYINT UNSIGNED NOT NULL, 29 | `firstdeath_Ct` TINYINT UNSIGNED NOT NULL, 30 | `firstdeath_t` TINYINT UNSIGNED NOT NULL, 31 | `firstkill_ct` TINYINT UNSIGNED NOT NULL, 32 | `firstkill_t` TINYINT UNSIGNED NOT NULL, 33 | FOREIGN KEY (`match_id`) REFERENCES matches(`id`), 34 | FOREIGN KEY (`map_id`) REFERENCES map_stats(`id`), 35 | FOREIGN KEY (`team_id`) REFERENCES teams(`id`), 36 | UNIQUE KEY `unique_player_stats` (`match_id`, `map_id`, `team_id`, `steam_id`) 37 | ); -------------------------------------------------------------------------------- /backend/usecase/validate_jwt_test.go: -------------------------------------------------------------------------------- 1 | package usecase_test 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "testing" 7 | 8 | "github.com/FlowingSPDG/get5loader/backend/entity" 9 | "github.com/FlowingSPDG/get5loader/backend/g5ctx" 10 | mock_jwt "github.com/FlowingSPDG/get5loader/backend/service/jwt/mock" 11 | "github.com/FlowingSPDG/get5loader/backend/usecase" 12 | "github.com/stretchr/testify/assert" 13 | "go.uber.org/mock/gomock" 14 | ) 15 | 16 | func TestValidateJWT(t *testing.T) { 17 | tt := []struct { 18 | name string 19 | input string 20 | expected *entity.TokenUser 21 | err error 22 | }{ 23 | { 24 | name: "success", 25 | input: "test", 26 | expected: &entity.TokenUser{ 27 | UserID: "1", 28 | SteamID: 76561198072054549, 29 | Admin: true, 30 | }, 31 | err: nil, 32 | }, 33 | { 34 | name: "invalid token", 35 | input: "invalid", 36 | expected: nil, 37 | err: errors.New("invalid token"), 38 | }, 39 | } 40 | 41 | for _, tc := range tt { 42 | t.Run(tc.name, func(t *testing.T) { 43 | ctx := context.Background() 44 | ctx = g5ctx.SetOperation(ctx, g5ctx.OperationTypeUser) 45 | 46 | // mock connectorの作成 47 | ctrl := gomock.NewController(t) 48 | defer ctrl.Finish() 49 | 50 | // mock JWTServiceの作成 51 | mockJwtService := mock_jwt.NewMockJWTService(ctrl) 52 | mockJwtService.EXPECT().ValidateJWT(tc.input).Return(tc.expected, tc.err) 53 | 54 | uc := usecase.NewValidateJWT(mockJwtService) 55 | actual, err := uc.Validate(tc.input) 56 | assert.Equal(t, tc.expected, actual) 57 | assert.Equal(t, tc.err, err) 58 | }) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /backend/g5ctx/g5ctx.go: -------------------------------------------------------------------------------- 1 | package g5ctx 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | 7 | "github.com/FlowingSPDG/get5loader/backend/entity" 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | var ( 12 | ErrContextValueNotFound = errors.New("context value not found") 13 | ) 14 | 15 | type ctxKey string 16 | 17 | var ( 18 | operatorKey = "operation" 19 | userKey = "user" 20 | ) 21 | 22 | type OperationType int 23 | 24 | const ( 25 | OperationTypeUnknown OperationType = iota 26 | OperationTypeSystem 27 | OperationTypeUser 28 | ) 29 | 30 | // SetOperation sets operator to context. 31 | func SetOperation(ctx context.Context, op OperationType) context.Context { 32 | return context.WithValue(ctx, operatorKey, op) 33 | } 34 | 35 | func SetOperationGinContext(ctx *gin.Context, op OperationType) { 36 | ctx.Set(operatorKey, op) 37 | } 38 | 39 | // GetOperation gets operator from context. 40 | func GetOperation(ctx context.Context) (OperationType, error) { 41 | op, ok := ctx.Value(operatorKey).(OperationType) 42 | if !ok { 43 | return OperationTypeUnknown, ErrContextValueNotFound 44 | } 45 | return op, nil 46 | } 47 | 48 | func SetUserToken(ctx context.Context, user *entity.TokenUser) context.Context { 49 | return context.WithValue(ctx, userKey, user) 50 | } 51 | 52 | func SetUserTokenGinContext(ctx *gin.Context, user *entity.TokenUser) { 53 | ctx.Set(userKey, user) 54 | } 55 | 56 | // GetUser gets user from context. 57 | func GetUserToken(ctx context.Context) (*entity.TokenUser, error) { 58 | user, ok := ctx.Value(userKey).(*entity.TokenUser) 59 | if !ok { 60 | return nil, ErrContextValueNotFound 61 | } 62 | return user, nil 63 | } 64 | -------------------------------------------------------------------------------- /backend/usecase/mock/get5.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: get5.go 3 | 4 | // Package mock_usecase is a generated GoMock package. 5 | package mock_usecase 6 | 7 | import ( 8 | context "context" 9 | reflect "reflect" 10 | 11 | entity "github.com/FlowingSPDG/get5loader/backend/entity" 12 | gomock "go.uber.org/mock/gomock" 13 | ) 14 | 15 | // MockGet5 is a mock of Get5 interface. 16 | type MockGet5 struct { 17 | ctrl *gomock.Controller 18 | recorder *MockGet5MockRecorder 19 | } 20 | 21 | // MockGet5MockRecorder is the mock recorder for MockGet5. 22 | type MockGet5MockRecorder struct { 23 | mock *MockGet5 24 | } 25 | 26 | // NewMockGet5 creates a new mock instance. 27 | func NewMockGet5(ctrl *gomock.Controller) *MockGet5 { 28 | mock := &MockGet5{ctrl: ctrl} 29 | mock.recorder = &MockGet5MockRecorder{mock} 30 | return mock 31 | } 32 | 33 | // EXPECT returns an object that allows the caller to indicate expected use. 34 | func (m *MockGet5) EXPECT() *MockGet5MockRecorder { 35 | return m.recorder 36 | } 37 | 38 | // GetMatch mocks base method. 39 | func (m *MockGet5) GetMatch(ctx context.Context, matchID entity.MatchID) (*entity.Get5Match, error) { 40 | m.ctrl.T.Helper() 41 | ret := m.ctrl.Call(m, "GetMatch", ctx, matchID) 42 | ret0, _ := ret[0].(*entity.Get5Match) 43 | ret1, _ := ret[1].(error) 44 | return ret0, ret1 45 | } 46 | 47 | // GetMatch indicates an expected call of GetMatch. 48 | func (mr *MockGet5MockRecorder) GetMatch(ctx, matchID interface{}) *gomock.Call { 49 | mr.mock.ctrl.T.Helper() 50 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMatch", reflect.TypeOf((*MockGet5)(nil).GetMatch), ctx, matchID) 51 | } 52 | -------------------------------------------------------------------------------- /backend/usecase/player.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/FlowingSPDG/get5loader/backend/entity" 7 | "github.com/FlowingSPDG/get5loader/backend/gateway/database" 8 | ) 9 | 10 | type Player interface { 11 | GetPlayersByTeam(ctx context.Context, teamID entity.TeamID) ([]*entity.Player, error) 12 | // BATCH 13 | BatchGetPlayersByTeam(ctx context.Context, teamIDs []entity.TeamID) (map[entity.TeamID][]*entity.Player, error) 14 | } 15 | 16 | type player struct { 17 | } 18 | 19 | func NewPlayer() Player { 20 | return &player{} 21 | } 22 | 23 | // GetPlayersByTeam implements Player. 24 | func (p *player) GetPlayersByTeam(ctx context.Context, teamID entity.TeamID) ([]*entity.Player, error) { 25 | repositoryConnector := database.GetConnection(ctx) 26 | 27 | playerRepository := repositoryConnector.GetPlayersRepository() 28 | 29 | players, err := playerRepository.GetPlayersByTeam(ctx, teamID) 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | return convertPlayers(players), nil 35 | } 36 | 37 | // BatchGetPlayersByTeam implements Player. 38 | func (p *player) BatchGetPlayersByTeam(ctx context.Context, teamIDs []entity.TeamID) (map[entity.TeamID][]*entity.Player, error) { 39 | repositoryConnector := database.GetConnection(ctx) 40 | 41 | playerRepository := repositoryConnector.GetPlayersRepository() 42 | 43 | teamPlayers, err := playerRepository.GetPlayersByTeams(ctx, teamIDs) 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | ret := make(map[entity.TeamID][]*entity.Player, len(teamIDs)) 49 | for teamID, players := range teamPlayers { 50 | ret[teamID] = convertPlayers(players) 51 | } 52 | 53 | return ret, nil 54 | } 55 | -------------------------------------------------------------------------------- /backend/gateway/srcds/rcon/rcon_mock/mock_rcon.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: ./rcon.go 3 | 4 | // Package mock_rcon is a generated GoMock package. 5 | package mock_rcon 6 | 7 | import ( 8 | reflect "reflect" 9 | 10 | gomock "go.uber.org/mock/gomock" 11 | ) 12 | 13 | // MockRCONAdapter is a mock of RCONAdapter interface. 14 | type MockRCONAdapter struct { 15 | ctrl *gomock.Controller 16 | recorder *MockRCONAdapterMockRecorder 17 | } 18 | 19 | // MockRCONAdapterMockRecorder is the mock recorder for MockRCONAdapter. 20 | type MockRCONAdapterMockRecorder struct { 21 | mock *MockRCONAdapter 22 | } 23 | 24 | // NewMockRCONAdapter creates a new mock instance. 25 | func NewMockRCONAdapter(ctrl *gomock.Controller) *MockRCONAdapter { 26 | mock := &MockRCONAdapter{ctrl: ctrl} 27 | mock.recorder = &MockRCONAdapterMockRecorder{mock} 28 | return mock 29 | } 30 | 31 | // EXPECT returns an object that allows the caller to indicate expected use. 32 | func (m *MockRCONAdapter) EXPECT() *MockRCONAdapterMockRecorder { 33 | return m.recorder 34 | } 35 | 36 | // Execute mocks base method. 37 | func (m *MockRCONAdapter) Execute(ip string, port int, password, cmd string) (string, error) { 38 | m.ctrl.T.Helper() 39 | ret := m.ctrl.Call(m, "Execute", ip, port, password, cmd) 40 | ret0, _ := ret[0].(string) 41 | ret1, _ := ret[1].(error) 42 | return ret0, ret1 43 | } 44 | 45 | // Execute indicates an expected call of Execute. 46 | func (mr *MockRCONAdapterMockRecorder) Execute(ip, port, password, cmd interface{}) *gomock.Call { 47 | mr.mock.ctrl.T.Helper() 48 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Execute", reflect.TypeOf((*MockRCONAdapter)(nil).Execute), ip, port, password, cmd) 49 | } 50 | -------------------------------------------------------------------------------- /backend/usecase/mock/validate_jwt.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: validate_jwt.go 3 | 4 | // Package mock_usecase is a generated GoMock package. 5 | package mock_usecase 6 | 7 | import ( 8 | reflect "reflect" 9 | 10 | entity "github.com/FlowingSPDG/get5loader/backend/entity" 11 | gomock "go.uber.org/mock/gomock" 12 | ) 13 | 14 | // MockValidateJWT is a mock of ValidateJWT interface. 15 | type MockValidateJWT struct { 16 | ctrl *gomock.Controller 17 | recorder *MockValidateJWTMockRecorder 18 | } 19 | 20 | // MockValidateJWTMockRecorder is the mock recorder for MockValidateJWT. 21 | type MockValidateJWTMockRecorder struct { 22 | mock *MockValidateJWT 23 | } 24 | 25 | // NewMockValidateJWT creates a new mock instance. 26 | func NewMockValidateJWT(ctrl *gomock.Controller) *MockValidateJWT { 27 | mock := &MockValidateJWT{ctrl: ctrl} 28 | mock.recorder = &MockValidateJWTMockRecorder{mock} 29 | return mock 30 | } 31 | 32 | // EXPECT returns an object that allows the caller to indicate expected use. 33 | func (m *MockValidateJWT) EXPECT() *MockValidateJWTMockRecorder { 34 | return m.recorder 35 | } 36 | 37 | // Validate mocks base method. 38 | func (m *MockValidateJWT) Validate(token string) (*entity.TokenUser, error) { 39 | m.ctrl.T.Helper() 40 | ret := m.ctrl.Call(m, "Validate", token) 41 | ret0, _ := ret[0].(*entity.TokenUser) 42 | ret1, _ := ret[1].(error) 43 | return ret0, ret1 44 | } 45 | 46 | // Validate indicates an expected call of Validate. 47 | func (mr *MockValidateJWTMockRecorder) Validate(token interface{}) *gomock.Call { 48 | mr.mock.ctrl.T.Helper() 49 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Validate", reflect.TypeOf((*MockValidateJWT)(nil).Validate), token) 50 | } 51 | -------------------------------------------------------------------------------- /backend/gateway/srcds/query/query_mock/mock_query.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: ./query.go 3 | 4 | // Package mock_query is a generated GoMock package. 5 | package mock_query 6 | 7 | import ( 8 | reflect "reflect" 9 | 10 | gomock "go.uber.org/mock/gomock" 11 | ) 12 | 13 | // MockQueryAdapter is a mock of QueryAdapter interface. 14 | type MockQueryAdapter struct { 15 | ctrl *gomock.Controller 16 | recorder *MockQueryAdapterMockRecorder 17 | } 18 | 19 | // MockQueryAdapterMockRecorder is the mock recorder for MockQueryAdapter. 20 | type MockQueryAdapterMockRecorder struct { 21 | mock *MockQueryAdapter 22 | } 23 | 24 | // NewMockQueryAdapter creates a new mock instance. 25 | func NewMockQueryAdapter(ctrl *gomock.Controller) *MockQueryAdapter { 26 | mock := &MockQueryAdapter{ctrl: ctrl} 27 | mock.recorder = &MockQueryAdapterMockRecorder{mock} 28 | return mock 29 | } 30 | 31 | // EXPECT returns an object that allows the caller to indicate expected use. 32 | func (m *MockQueryAdapter) EXPECT() *MockQueryAdapterMockRecorder { 33 | return m.recorder 34 | } 35 | 36 | // QueryPlayer mocks base method. 37 | func (m *MockQueryAdapter) QueryPlayer(ip string, port int, password string) (uint8, error) { 38 | m.ctrl.T.Helper() 39 | ret := m.ctrl.Call(m, "QueryPlayer", ip, port, password) 40 | ret0, _ := ret[0].(uint8) 41 | ret1, _ := ret[1].(error) 42 | return ret0, ret1 43 | } 44 | 45 | // QueryPlayer indicates an expected call of QueryPlayer. 46 | func (mr *MockQueryAdapterMockRecorder) QueryPlayer(ip, port, password interface{}) *gomock.Call { 47 | mr.mock.ctrl.T.Helper() 48 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueryPlayer", reflect.TypeOf((*MockQueryAdapter)(nil).QueryPlayer), ip, port, password) 49 | } 50 | -------------------------------------------------------------------------------- /backend/.air.toml: -------------------------------------------------------------------------------- 1 | # Config file for [Air](https://github.com/cosmtrek/air) in TOML format 2 | 3 | # Working directory 4 | # . or absolute path, please note that the directories following must be under root. 5 | root = "." 6 | tmp_dir = "tmp" 7 | 8 | [build] 9 | # Just plain old shell command. You could use `make` as well. 10 | cmd = "go build -o ./tmp/main ./cmd/main.go" 11 | # Binary file yields from `cmd`. 12 | bin = "tmp/main" 13 | # Customize binary, can setup environment variables when run your app. 14 | full_bin = "APP_ENV=dev APP_USER=air ./tmp/main" 15 | # Watch these filename extensions. 16 | include_ext = ["go", "tpl", "tmpl", "html"] 17 | # Ignore these filename extensions or directories. 18 | exclude_dir = ["assets", "tmp", "vendor", "frontend/node_modules"] 19 | # Watch these directories if you specified. 20 | include_dir = [] 21 | # Exclude files. 22 | exclude_file = [] 23 | # Exclude specific regular expressions. 24 | exclude_regex = ["_test.go"] 25 | # Exclude unchanged files. 26 | exclude_unchanged = true 27 | # Follow symlink for directories 28 | follow_symlink = true 29 | # This log file places in your tmp_dir. 30 | log = "air.log" 31 | # It's not necessary to trigger build each time file changes if it's too frequent. 32 | delay = 1000 # ms 33 | # Stop running old binary when build errors occur. 34 | stop_on_error = true 35 | # Send Interrupt signal before killing process (windows does not support this feature) 36 | send_interrupt = false 37 | # Delay after sending Interrupt signal 38 | kill_delay = 500 # ms 39 | 40 | [log] 41 | # Show log time 42 | time = false 43 | 44 | [color] 45 | # Customize each part's color. If no color found, use the raw app log. 46 | main = "magenta" 47 | watcher = "cyan" 48 | build = "yellow" 49 | runner = "green" 50 | 51 | [misc] 52 | # Delete tmp directory on exit 53 | clean_on_exit = true -------------------------------------------------------------------------------- /backend/cmd/di/di.go: -------------------------------------------------------------------------------- 1 | package di 2 | 3 | import ( 4 | "golang.org/x/crypto/bcrypt" 5 | 6 | config "github.com/FlowingSPDG/get5loader/backend/cfg" 7 | gin_controller "github.com/FlowingSPDG/get5loader/backend/controller/gin" 8 | mysqlconnector "github.com/FlowingSPDG/get5loader/backend/gateway/database/mysql/connector" 9 | gin_presenter "github.com/FlowingSPDG/get5loader/backend/presenter/gin" 10 | "github.com/FlowingSPDG/get5loader/backend/service/jwt" 11 | hash "github.com/FlowingSPDG/get5loader/backend/service/password_hash" 12 | "github.com/FlowingSPDG/get5loader/backend/service/uuid" 13 | "github.com/FlowingSPDG/get5loader/backend/usecase" 14 | ) 15 | 16 | func InitializeUserLoginController(cfg config.Config) gin_controller.UserLoginController { 17 | jwtService := jwt.NewJWTGateway([]byte(cfg.SecretKey)) 18 | passwordHasher := hash.NewPasswordHasher(bcrypt.DefaultCost) 19 | uc := usecase.NewUser(jwtService, passwordHasher) 20 | presenter := gin_presenter.NewJWTPresenter() 21 | return gin_controller.NewUserLoginController(uc, presenter) 22 | } 23 | 24 | func InitializeUserRegisterController(cfg config.Config) gin_controller.UserRegisterController { 25 | jwtService := jwt.NewJWTGateway([]byte(cfg.SecretKey)) 26 | passwordHasher := hash.NewPasswordHasher(bcrypt.DefaultCost) 27 | uc := usecase.NewUser(jwtService, passwordHasher) 28 | presenter := gin_presenter.NewJWTPresenter() 29 | return gin_controller.NewUserRegisterController(uc, presenter) 30 | } 31 | 32 | func InitializeDatabaseConnectionMiddleware(cfg config.Config) gin_controller.DatabaseConnectionMiddleware { 33 | uuidGenerator := uuid.NewUUIDGenerator() 34 | mysqlConnector := mustGetWriteConnector(cfg) 35 | mysqlUsersRepositoryConnector := mysqlconnector.NewMySQLRepositoryConnector(uuidGenerator, mysqlConnector) 36 | return gin_controller.NewDatabaseConnectionMiddleware(uuidGenerator, mysqlUsersRepositoryConnector) 37 | } 38 | -------------------------------------------------------------------------------- /backend/usecase/mock/playerstat.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: playerstat.go 3 | 4 | // Package mock_usecase is a generated GoMock package. 5 | package mock_usecase 6 | 7 | import ( 8 | context "context" 9 | reflect "reflect" 10 | 11 | entity "github.com/FlowingSPDG/get5loader/backend/entity" 12 | gomock "go.uber.org/mock/gomock" 13 | ) 14 | 15 | // MockPlayerStat is a mock of PlayerStat interface. 16 | type MockPlayerStat struct { 17 | ctrl *gomock.Controller 18 | recorder *MockPlayerStatMockRecorder 19 | } 20 | 21 | // MockPlayerStatMockRecorder is the mock recorder for MockPlayerStat. 22 | type MockPlayerStatMockRecorder struct { 23 | mock *MockPlayerStat 24 | } 25 | 26 | // NewMockPlayerStat creates a new mock instance. 27 | func NewMockPlayerStat(ctrl *gomock.Controller) *MockPlayerStat { 28 | mock := &MockPlayerStat{ctrl: ctrl} 29 | mock.recorder = &MockPlayerStatMockRecorder{mock} 30 | return mock 31 | } 32 | 33 | // EXPECT returns an object that allows the caller to indicate expected use. 34 | func (m *MockPlayerStat) EXPECT() *MockPlayerStatMockRecorder { 35 | return m.recorder 36 | } 37 | 38 | // BatchGetPlayerStatsByMapstat mocks base method. 39 | func (m *MockPlayerStat) BatchGetPlayerStatsByMapstat(ctx context.Context, mapstatIDs []entity.MapStatsID) (map[entity.MapStatsID][]*entity.PlayerStat, error) { 40 | m.ctrl.T.Helper() 41 | ret := m.ctrl.Call(m, "BatchGetPlayerStatsByMapstat", ctx, mapstatIDs) 42 | ret0, _ := ret[0].(map[entity.MapStatsID][]*entity.PlayerStat) 43 | ret1, _ := ret[1].(error) 44 | return ret0, ret1 45 | } 46 | 47 | // BatchGetPlayerStatsByMapstat indicates an expected call of BatchGetPlayerStatsByMapstat. 48 | func (mr *MockPlayerStatMockRecorder) BatchGetPlayerStatsByMapstat(ctx, mapstatIDs interface{}) *gomock.Call { 49 | mr.mock.ctrl.T.Helper() 50 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BatchGetPlayerStatsByMapstat", reflect.TypeOf((*MockPlayerStat)(nil).BatchGetPlayerStatsByMapstat), ctx, mapstatIDs) 51 | } 52 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | go: 5 | container_name: go 6 | build: 7 | context: ./backend 8 | dockerfile: Dockerfile 9 | tty: true 10 | ports: 11 | - 8080:8080 12 | depends_on: 13 | - db 14 | volumes: 15 | - type: bind 16 | source: ./backend 17 | target: /go/src 18 | environment: 19 | - "GO111MODULES=on" 20 | - "PORT=8080" 21 | 22 | - "DB_WRITE_HOST=db" 23 | - "DB_WRITE_PORT=3306" 24 | - "DB_WRITE_USER=root" 25 | - "DB_WRITE_PASS=root_password" 26 | - "DB_WRITE_NAME=get5_go" 27 | 28 | - "DB_READ_HOST=db" 29 | - "DB_READ_PORT=3306" 30 | - "DB_READ_USER=root" 31 | - "DB_READ_PASS=root_password" 32 | 33 | - "SECRET_KEY=get5_secret_key" 34 | networks: 35 | - backend_network 36 | 37 | db: 38 | container_name: db 39 | build: 40 | context: ./docker/db 41 | dockerfile: Dockerfile 42 | tty: true 43 | platform: linux/x86_64 44 | ports: 45 | - 3306:3306 46 | env_file: 47 | - ./docker/db/.env 48 | volumes: 49 | - type: volume 50 | source: mysql_test_volume 51 | target: /var/lib/mysql 52 | - type: bind 53 | source: ./docker/db/init 54 | target: /docker-entrypoint-initdb.d 55 | - type: bind 56 | source: ./backend/sqlc 57 | target: /docker-init-sqlc-definitions.d 58 | networks: 59 | - backend_network 60 | 61 | srcds: 62 | profiles: 63 | - test_srcds 64 | container_name: srcds 65 | build: 66 | context: ./docker/srcds 67 | dockerfile: Dockerfile 68 | tty: true 69 | stdin_open: true 70 | platform: linux/x86_64 71 | ports: 72 | - 27015:27015 73 | - 27015:27015/udp 74 | volumes: 75 | - type: bind 76 | source: ./docker/srcds 77 | target: /home/steam 78 | networks: 79 | - backend_network 80 | 81 | volumes: 82 | mysql_test_volume: 83 | name: mysql_test_volume 84 | 85 | networks: 86 | backend_network: 87 | external: false -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | # Go 2 | GOCMD=go 3 | GOBUILD=$(GOCMD) build 4 | GOCLEAN=$(GOCMD) clean 5 | GOTEST=$(GOCMD) test 6 | GOGET=$(GOCMD) get 7 | GOINSTALL=$(GOCMD) install 8 | BINARY_NAME=get5 9 | DIST_DIR=build 10 | SERVER_DIR=bacjebd 11 | WEB_DIR=web 12 | OS_Linux=linux 13 | ARCH_AMD64=amd64 14 | 15 | .DEFAULT_GOAL := build-backend 16 | 17 | test: 18 | cd ./backend && $(GOTEST) -v ./... 19 | clean: 20 | @$(GOCLEAN) 21 | -@$(RM) $(DIST_DIR)/* 22 | deps: deps-web 23 | deps-web: 24 | @cd ./front && yarn 25 | up: 26 | docker compose up -d --build 27 | up-srcds: 28 | docker compose --profile test_srcds up -d --build 29 | down: 30 | docker compose down 31 | down-srcds: 32 | docker compose --profile test_srcds down 33 | generate: 34 | @cd ./backend && \ 35 | $(GOINSTALL) golang.org/x/tools/cmd/stringer@v0.11.1 && \ 36 | $(GOINSTALL) github.com/sqlc-dev/sqlc/cmd/sqlc@latest && \ 37 | go generate ./... 38 | gql-generate: 39 | @cd backend && \ 40 | $(GOINSTALL) github.com/99designs/gqlgen@latest && \ 41 | gqlgen generate 42 | # compile for go 43 | build-backend: build-prepare 44 | cd ./backend/cmd && \ 45 | gox \ 46 | -os="$(OS_Linux)" \ 47 | -arch="$(ARCH_AMD64)" \ 48 | --output "../$(DIST_DIR)/$(BINARY_NAME)_{{.OS}}_{{.Arch}}/$(BINARY_NAME)" 49 | build-prepare: clean generate 50 | @$(GOINSTALL) github.com/mitchellh/gox@latest 51 | -@$(RM) ./$(DIST_DIR)/*/static 52 | build-front: deps-web 53 | @cd ./front && yarn build 54 | coverage: 55 | cd backend && \ 56 | go test -v -count=1 -covermode=count -coverprofile=coverage.out ./... | tee test_output.txt && \ 57 | go tool cover -func=coverage.out | awk '/total:/ {print "| **" $$1 "** | **" $$3 "** |"}' | tee coverage.txt && \ 58 | cat test_output.txt | grep 'ok.*coverage' | awk '{sub("github.com/FlowingSPDG/get5loader/", "", $$2); print "| " $$2 " | " $$5 " |"}' | tee -a coverage.txt && \ 59 | echo "## Test Coverage Report" > coverage_with_header.txt && \ 60 | echo "| Package | Coverage |" >> coverage_with_header.txt && \ 61 | echo "|-------------------|----------|" >> coverage_with_header.txt && \ 62 | cat coverage.txt >> coverage_with_header.txt && \ 63 | mv coverage_with_header.txt coverage.txt && \ 64 | cat coverage.txt -------------------------------------------------------------------------------- /backend/service/password_hash/mock/hash_mock.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: hash.go 3 | 4 | // Package mock_hash is a generated GoMock package. 5 | package mock_hash 6 | 7 | import ( 8 | reflect "reflect" 9 | 10 | gomock "go.uber.org/mock/gomock" 11 | ) 12 | 13 | // MockPasswordHasher is a mock of PasswordHasher interface. 14 | type MockPasswordHasher struct { 15 | ctrl *gomock.Controller 16 | recorder *MockPasswordHasherMockRecorder 17 | } 18 | 19 | // MockPasswordHasherMockRecorder is the mock recorder for MockPasswordHasher. 20 | type MockPasswordHasherMockRecorder struct { 21 | mock *MockPasswordHasher 22 | } 23 | 24 | // NewMockPasswordHasher creates a new mock instance. 25 | func NewMockPasswordHasher(ctrl *gomock.Controller) *MockPasswordHasher { 26 | mock := &MockPasswordHasher{ctrl: ctrl} 27 | mock.recorder = &MockPasswordHasherMockRecorder{mock} 28 | return mock 29 | } 30 | 31 | // EXPECT returns an object that allows the caller to indicate expected use. 32 | func (m *MockPasswordHasher) EXPECT() *MockPasswordHasherMockRecorder { 33 | return m.recorder 34 | } 35 | 36 | // Compare mocks base method. 37 | func (m *MockPasswordHasher) Compare(hash []byte, password string) error { 38 | m.ctrl.T.Helper() 39 | ret := m.ctrl.Call(m, "Compare", hash, password) 40 | ret0, _ := ret[0].(error) 41 | return ret0 42 | } 43 | 44 | // Compare indicates an expected call of Compare. 45 | func (mr *MockPasswordHasherMockRecorder) Compare(hash, password interface{}) *gomock.Call { 46 | mr.mock.ctrl.T.Helper() 47 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Compare", reflect.TypeOf((*MockPasswordHasher)(nil).Compare), hash, password) 48 | } 49 | 50 | // Hash mocks base method. 51 | func (m *MockPasswordHasher) Hash(password string) ([]byte, error) { 52 | m.ctrl.T.Helper() 53 | ret := m.ctrl.Call(m, "Hash", password) 54 | ret0, _ := ret[0].([]byte) 55 | ret1, _ := ret[1].(error) 56 | return ret0, ret1 57 | } 58 | 59 | // Hash indicates an expected call of Hash. 60 | func (mr *MockPasswordHasherMockRecorder) Hash(password interface{}) *gomock.Call { 61 | mr.mock.ctrl.T.Helper() 62 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Hash", reflect.TypeOf((*MockPasswordHasher)(nil).Hash), password) 63 | } 64 | -------------------------------------------------------------------------------- /backend/gateway/database/errors_test.go: -------------------------------------------------------------------------------- 1 | package database_test 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | 9 | "github.com/FlowingSPDG/get5loader/backend/gateway/database" 10 | ) 11 | 12 | func TestErrors(t *testing.T) { 13 | tc := []struct { 14 | name string 15 | input error 16 | method func(error) bool 17 | expected bool 18 | }{ 19 | // default errors 20 | { 21 | name: "ErrNotFound", 22 | method: database.IsNotFound, 23 | input: database.ErrNotFound, 24 | expected: true, 25 | }, 26 | { 27 | name: "ErrInteral", 28 | method: database.IsInternal, 29 | input: database.ErrInternal, 30 | expected: true, 31 | }, 32 | 33 | // wrapped errors 34 | { 35 | name: "wrapped ErrNotFound", 36 | method: database.IsNotFound, 37 | input: database.NewNotFoundError(errors.New("Not Found Error")), 38 | expected: true, 39 | }, 40 | { 41 | name: "wrapped ErrInternal", 42 | method: database.IsInternal, 43 | input: database.NewInternalError(errors.New("Internal Error")), 44 | expected: true, 45 | }, 46 | 47 | // wrapped wrapped errors 48 | { 49 | name: "wrapped wrapped ErrNotFound", 50 | method: database.IsNotFound, 51 | input: database.NewNotFoundError(errors.New("Not Found Error")), 52 | expected: true, 53 | }, 54 | { 55 | name: "wrapped wrapped ErrInternal", 56 | method: database.IsInternal, 57 | input: errors.Join(errors.New("User not found"), database.ErrInternal), 58 | expected: true, 59 | }, 60 | 61 | // Different errors 62 | { 63 | name: "different error", 64 | method: database.IsNotFound, 65 | input: errors.New("Unknown error!"), 66 | expected: false, 67 | }, 68 | 69 | // wrapped different errors 70 | { 71 | name: "wrapped different error", 72 | method: database.IsNotFound, 73 | input: database.ErrInternal, 74 | expected: false, 75 | }, 76 | { 77 | name: "wrapped different error", 78 | method: database.IsInternal, 79 | input: database.ErrNotFound, 80 | expected: false, 81 | }, 82 | } 83 | 84 | for _, c := range tc { 85 | assert.Equal(t, c.expected, c.method(c.input), c.name) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /backend/g5ctx/g5ctx_test.go: -------------------------------------------------------------------------------- 1 | package g5ctx_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/FlowingSPDG/get5loader/backend/entity" 8 | "github.com/FlowingSPDG/get5loader/backend/g5ctx" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestSetOperation(t *testing.T) { 13 | tc := []struct { 14 | name string 15 | set bool 16 | operation g5ctx.OperationType 17 | err error 18 | }{ 19 | { 20 | name: "system", 21 | set: true, 22 | operation: g5ctx.OperationTypeSystem, 23 | err: nil, 24 | }, 25 | { 26 | name: "user", 27 | set: true, 28 | operation: g5ctx.OperationTypeUser, 29 | err: nil, 30 | }, 31 | { 32 | name: "not set", 33 | set: false, 34 | operation: g5ctx.OperationTypeUnknown, 35 | err: g5ctx.ErrContextValueNotFound, 36 | }, 37 | } 38 | 39 | for _, c := range tc { 40 | t.Run(c.name, func(t *testing.T) { 41 | ctx := context.Background() 42 | if c.set { 43 | ctx = g5ctx.SetOperation(ctx, c.operation) 44 | } 45 | op, err := g5ctx.GetOperation(ctx) 46 | assert.Equal(t, c.operation, op) 47 | assert.Equal(t, c.err, err) 48 | }) 49 | } 50 | } 51 | 52 | func TestSetUser(t *testing.T) { 53 | tc := []struct { 54 | name string 55 | set bool 56 | tokenUser *entity.TokenUser 57 | err error 58 | }{ 59 | { 60 | name: "set tokenUser 1", 61 | set: true, 62 | tokenUser: &entity.TokenUser{ 63 | UserID: "1", 64 | SteamID: 76561198072054549, 65 | Admin: true, 66 | }, 67 | err: nil, 68 | }, 69 | { 70 | name: "set userID 2", 71 | set: true, 72 | tokenUser: &entity.TokenUser{ 73 | UserID: "2", 74 | SteamID: 76561198072054549, 75 | Admin: false, 76 | }, 77 | err: nil, 78 | }, 79 | { 80 | name: "not set", 81 | set: false, 82 | tokenUser: nil, 83 | err: g5ctx.ErrContextValueNotFound, 84 | }, 85 | } 86 | 87 | for _, c := range tc { 88 | t.Run(c.name, func(t *testing.T) { 89 | ctx := context.Background() 90 | if c.set { 91 | ctx = g5ctx.SetUserToken(ctx, c.tokenUser) 92 | } 93 | user, err := g5ctx.GetUserToken(ctx) 94 | assert.Equal(t, c.tokenUser, user) 95 | assert.Equal(t, c.err, err) 96 | }) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /backend/service/jwt/mock/jwt_mock.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: jwt.go 3 | 4 | // Package mock_jwt is a generated GoMock package. 5 | package mock_jwt 6 | 7 | import ( 8 | reflect "reflect" 9 | 10 | entity "github.com/FlowingSPDG/get5loader/backend/entity" 11 | gomock "go.uber.org/mock/gomock" 12 | ) 13 | 14 | // MockJWTService is a mock of JWTService interface. 15 | type MockJWTService struct { 16 | ctrl *gomock.Controller 17 | recorder *MockJWTServiceMockRecorder 18 | } 19 | 20 | // MockJWTServiceMockRecorder is the mock recorder for MockJWTService. 21 | type MockJWTServiceMockRecorder struct { 22 | mock *MockJWTService 23 | } 24 | 25 | // NewMockJWTService creates a new mock instance. 26 | func NewMockJWTService(ctrl *gomock.Controller) *MockJWTService { 27 | mock := &MockJWTService{ctrl: ctrl} 28 | mock.recorder = &MockJWTServiceMockRecorder{mock} 29 | return mock 30 | } 31 | 32 | // EXPECT returns an object that allows the caller to indicate expected use. 33 | func (m *MockJWTService) EXPECT() *MockJWTServiceMockRecorder { 34 | return m.recorder 35 | } 36 | 37 | // IssueJWT mocks base method. 38 | func (m *MockJWTService) IssueJWT(userID entity.UserID, steamID entity.SteamID, admin bool) (string, error) { 39 | m.ctrl.T.Helper() 40 | ret := m.ctrl.Call(m, "IssueJWT", userID, steamID, admin) 41 | ret0, _ := ret[0].(string) 42 | ret1, _ := ret[1].(error) 43 | return ret0, ret1 44 | } 45 | 46 | // IssueJWT indicates an expected call of IssueJWT. 47 | func (mr *MockJWTServiceMockRecorder) IssueJWT(userID, steamID, admin interface{}) *gomock.Call { 48 | mr.mock.ctrl.T.Helper() 49 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IssueJWT", reflect.TypeOf((*MockJWTService)(nil).IssueJWT), userID, steamID, admin) 50 | } 51 | 52 | // ValidateJWT mocks base method. 53 | func (m *MockJWTService) ValidateJWT(token string) (*entity.TokenUser, error) { 54 | m.ctrl.T.Helper() 55 | ret := m.ctrl.Call(m, "ValidateJWT", token) 56 | ret0, _ := ret[0].(*entity.TokenUser) 57 | ret1, _ := ret[1].(error) 58 | return ret0, ret1 59 | } 60 | 61 | // ValidateJWT indicates an expected call of ValidateJWT. 62 | func (mr *MockJWTServiceMockRecorder) ValidateJWT(token interface{}) *gomock.Call { 63 | mr.mock.ctrl.T.Helper() 64 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateJWT", reflect.TypeOf((*MockJWTService)(nil).ValidateJWT), token) 65 | } 66 | -------------------------------------------------------------------------------- /backend/usecase/mapstats.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/FlowingSPDG/get5loader/backend/entity" 7 | "github.com/FlowingSPDG/get5loader/backend/gateway/database" 8 | ) 9 | 10 | type Mapstat interface { 11 | GetMapStat(ctx context.Context, id entity.MapStatsID) (*entity.MapStat, error) 12 | GetMapStatsByMatch(ctx context.Context, matchID entity.MatchID) ([]*entity.MapStat, error) 13 | // GetMapStatsByTeam(ctx context.Context, teamID entity.TeamID) ([]*entity.MapStat, error) 14 | 15 | // BATCH 16 | BatchGetMapstatsByMatch(ctx context.Context, matchIDs []entity.MatchID) (map[entity.MatchID][]*entity.MapStat, error) 17 | } 18 | 19 | type mapstat struct { 20 | } 21 | 22 | func NewMapStats() Mapstat { 23 | return &mapstat{} 24 | } 25 | 26 | // GetMapStats implements Mapstats. 27 | func (m *mapstat) GetMapStat(ctx context.Context, id entity.MapStatsID) (*entity.MapStat, error) { 28 | repositoryConnector := database.GetConnection(ctx) 29 | 30 | MapStatRepository := repositoryConnector.GetMapStatRepository() 31 | 32 | mapstats, err := MapStatRepository.GetMapStat(ctx, id) 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | return convertMapstat(mapstats), nil 38 | } 39 | 40 | // GetMapStatsByMatch implements Mapstats. 41 | func (m *mapstat) GetMapStatsByMatch(ctx context.Context, matchID entity.MatchID) ([]*entity.MapStat, error) { 42 | repositoryConnector := database.GetConnection(ctx) 43 | 44 | MapStatRepository := repositoryConnector.GetMapStatRepository() 45 | 46 | mapstats, err := MapStatRepository.GetMapStatsByMatch(ctx, matchID) 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | ret := make([]*entity.MapStat, 0, len(mapstats)) 52 | for _, mapstat := range mapstats { 53 | ret = append(ret, convertMapstat(mapstat)) 54 | } 55 | 56 | return ret, nil 57 | } 58 | 59 | // BatchGetMapstatsByMatch implements Mapstat. 60 | func (m *mapstat) BatchGetMapstatsByMatch(ctx context.Context, matchIDs []entity.MatchID) (map[entity.MatchID][]*entity.MapStat, error) { 61 | repositoryConnector := database.GetConnection(ctx) 62 | 63 | mapStatRepository := repositoryConnector.GetMapStatRepository() 64 | 65 | mapstats, err := mapStatRepository.GetMapStatsByMatches(ctx, matchIDs) 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | ret := make(map[entity.MatchID][]*entity.MapStat, len(matchIDs)) 71 | // nilが渡されるのを防ぐため、空のスライスを生成する 72 | for matchID, mapstats := range mapstats { 73 | ret[matchID] = convertMapstats(mapstats) 74 | } 75 | return ret, nil 76 | } 77 | -------------------------------------------------------------------------------- /backend/usecase/mock/player.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: player.go 3 | 4 | // Package mock_usecase is a generated GoMock package. 5 | package mock_usecase 6 | 7 | import ( 8 | context "context" 9 | reflect "reflect" 10 | 11 | entity "github.com/FlowingSPDG/get5loader/backend/entity" 12 | gomock "go.uber.org/mock/gomock" 13 | ) 14 | 15 | // MockPlayer is a mock of Player interface. 16 | type MockPlayer struct { 17 | ctrl *gomock.Controller 18 | recorder *MockPlayerMockRecorder 19 | } 20 | 21 | // MockPlayerMockRecorder is the mock recorder for MockPlayer. 22 | type MockPlayerMockRecorder struct { 23 | mock *MockPlayer 24 | } 25 | 26 | // NewMockPlayer creates a new mock instance. 27 | func NewMockPlayer(ctrl *gomock.Controller) *MockPlayer { 28 | mock := &MockPlayer{ctrl: ctrl} 29 | mock.recorder = &MockPlayerMockRecorder{mock} 30 | return mock 31 | } 32 | 33 | // EXPECT returns an object that allows the caller to indicate expected use. 34 | func (m *MockPlayer) EXPECT() *MockPlayerMockRecorder { 35 | return m.recorder 36 | } 37 | 38 | // BatchGetPlayersByTeam mocks base method. 39 | func (m *MockPlayer) BatchGetPlayersByTeam(ctx context.Context, teamIDs []entity.TeamID) (map[entity.TeamID][]*entity.Player, error) { 40 | m.ctrl.T.Helper() 41 | ret := m.ctrl.Call(m, "BatchGetPlayersByTeam", ctx, teamIDs) 42 | ret0, _ := ret[0].(map[entity.TeamID][]*entity.Player) 43 | ret1, _ := ret[1].(error) 44 | return ret0, ret1 45 | } 46 | 47 | // BatchGetPlayersByTeam indicates an expected call of BatchGetPlayersByTeam. 48 | func (mr *MockPlayerMockRecorder) BatchGetPlayersByTeam(ctx, teamIDs interface{}) *gomock.Call { 49 | mr.mock.ctrl.T.Helper() 50 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BatchGetPlayersByTeam", reflect.TypeOf((*MockPlayer)(nil).BatchGetPlayersByTeam), ctx, teamIDs) 51 | } 52 | 53 | // GetPlayersByTeam mocks base method. 54 | func (m *MockPlayer) GetPlayersByTeam(ctx context.Context, teamID entity.TeamID) ([]*entity.Player, error) { 55 | m.ctrl.T.Helper() 56 | ret := m.ctrl.Call(m, "GetPlayersByTeam", ctx, teamID) 57 | ret0, _ := ret[0].([]*entity.Player) 58 | ret1, _ := ret[1].(error) 59 | return ret0, ret1 60 | } 61 | 62 | // GetPlayersByTeam indicates an expected call of GetPlayersByTeam. 63 | func (mr *MockPlayerMockRecorder) GetPlayersByTeam(ctx, teamID interface{}) *gomock.Call { 64 | mr.mock.ctrl.T.Helper() 65 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPlayersByTeam", reflect.TypeOf((*MockPlayer)(nil).GetPlayersByTeam), ctx, teamID) 66 | } 67 | -------------------------------------------------------------------------------- /backend/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/FlowingSPDG/get5loader/backend 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/99designs/gqlgen v0.17.36 7 | github.com/FlowingSPDG/Got5 v0.0.0-20230812211625-979f385a7b08 8 | github.com/FlowingSPDG/go-steam v0.0.0-20200304111708-e30ea2f91a83 9 | github.com/caarlos0/env/v9 v9.0.0 10 | github.com/gin-gonic/gin v1.9.1 11 | github.com/go-sql-driver/mysql v1.7.1 12 | github.com/golang-jwt/jwt/v5 v5.0.0 13 | github.com/google/uuid v1.3.0 14 | github.com/graph-gophers/dataloader/v7 v7.1.0 15 | github.com/rumblefrog/go-a2s v1.0.2 16 | github.com/stretchr/testify v1.8.4 17 | github.com/vektah/gqlparser/v2 v2.5.8 18 | go.uber.org/mock v0.2.0 19 | golang.org/x/crypto v0.12.0 20 | ) 21 | 22 | require ( 23 | github.com/agnivade/levenshtein v1.1.1 // indirect 24 | github.com/bytedance/sonic v1.9.1 // indirect 25 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 26 | github.com/davecgh/go-spew v1.1.1 // indirect 27 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect 28 | github.com/gin-contrib/sse v0.1.0 // indirect 29 | github.com/go-playground/locales v0.14.1 // indirect 30 | github.com/go-playground/universal-translator v0.18.1 // indirect 31 | github.com/go-playground/validator/v10 v10.14.0 // indirect 32 | github.com/goccy/go-json v0.10.2 // indirect 33 | github.com/gorilla/websocket v1.5.0 // indirect 34 | github.com/hashicorp/golang-lru/v2 v2.0.3 // indirect 35 | github.com/json-iterator/go v1.1.12 // indirect 36 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect 37 | github.com/kr/pretty v0.3.1 // indirect 38 | github.com/leodido/go-urn v1.2.4 // indirect 39 | github.com/mattn/go-isatty v0.0.19 // indirect 40 | github.com/mitchellh/mapstructure v1.5.0 // indirect 41 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 42 | github.com/modern-go/reflect2 v1.0.2 // indirect 43 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect 44 | github.com/pmezard/go-difflib v1.0.0 // indirect 45 | github.com/sirupsen/logrus v1.9.0 // indirect 46 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 47 | github.com/ugorji/go/codec v1.2.11 // indirect 48 | golang.org/x/arch v0.3.0 // indirect 49 | golang.org/x/net v0.12.0 // indirect 50 | golang.org/x/sys v0.11.0 // indirect 51 | golang.org/x/text v0.12.0 // indirect 52 | golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect 53 | google.golang.org/protobuf v1.30.0 // indirect 54 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 55 | gopkg.in/yaml.v3 v3.0.1 // indirect 56 | ) 57 | -------------------------------------------------------------------------------- /backend/gateway/database/models.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/FlowingSPDG/get5loader/backend/entity" 7 | ) 8 | 9 | type User struct { 10 | ID entity.UserID 11 | SteamID entity.SteamID 12 | Name string 13 | Admin bool 14 | Hash []byte 15 | CreatedAt time.Time 16 | UpdatedAt time.Time 17 | } 18 | 19 | type GameServer struct { 20 | UserID entity.UserID 21 | ID entity.GameServerID 22 | Ip string 23 | Port uint32 24 | RCONPassword string 25 | DisplayName string 26 | IsPublic bool 27 | Status entity.SERVER_STATUS 28 | } 29 | 30 | type Match struct { 31 | ID entity.MatchID 32 | UserID entity.UserID 33 | ServerID entity.GameServerID 34 | Team1ID entity.TeamID 35 | Team2ID entity.TeamID 36 | Winner entity.TeamID 37 | StartTime *time.Time 38 | EndTime *time.Time 39 | MaxMaps int32 40 | Title string 41 | SkipVeto bool 42 | APIKey string 43 | Team1Score uint32 44 | Team2Score uint32 45 | Forfeit *bool 46 | Status entity.MATCH_STATUS 47 | } 48 | 49 | type MapStat struct { 50 | ID entity.MapStatsID 51 | MatchID entity.MatchID 52 | MapNumber uint32 53 | MapName string 54 | StartTime *time.Time 55 | EndTime *time.Time 56 | Winner *entity.TeamID 57 | Team1Score uint32 58 | Team2Score uint32 59 | } 60 | 61 | type PlayerStat struct { 62 | ID entity.PlayerStatsID 63 | MatchID entity.MatchID 64 | MapID entity.MapStatsID 65 | TeamID entity.TeamID 66 | SteamID entity.SteamID 67 | Name string 68 | 69 | Kills int32 70 | Assists int32 71 | Deaths int32 72 | 73 | RoundsPlayed uint32 74 | FlashbangAssists uint32 75 | Suicides uint32 76 | HeadShotKills uint32 77 | Damage uint32 78 | BombPlants uint32 79 | BombDefuses uint32 80 | 81 | V1 uint32 82 | V2 uint32 83 | V3 uint32 84 | V4 uint32 85 | V5 uint32 86 | K1 uint32 87 | K2 uint32 88 | K3 uint32 89 | K4 uint32 90 | K5 uint32 91 | 92 | FirstDeathCT uint32 93 | FirstDeathT uint32 94 | FirstKillCT uint32 95 | FirstKillT uint32 96 | } 97 | 98 | type Team struct { 99 | ID entity.TeamID 100 | UserID entity.UserID 101 | Name string 102 | Flag string 103 | Tag string 104 | Logo string 105 | Public bool 106 | } 107 | 108 | type Player struct { 109 | ID entity.PlayerID 110 | TeamID entity.TeamID 111 | SteamID entity.SteamID 112 | Name string 113 | } 114 | -------------------------------------------------------------------------------- /backend/graph/dataloaders/dataloaders.go: -------------------------------------------------------------------------------- 1 | package dataloaders 2 | 3 | import ( 4 | "github.com/graph-gophers/dataloader/v7" 5 | 6 | "github.com/FlowingSPDG/get5loader/backend/entity" 7 | "github.com/FlowingSPDG/get5loader/backend/usecase" 8 | ) 9 | 10 | type Loaders struct { 11 | PlayersByTeamID dataloader.Interface[entity.TeamID, []*entity.Player] 12 | MapStatsByMatchID dataloader.Interface[entity.MatchID, []*entity.MapStat] 13 | PlayerStatsByMapStatID dataloader.Interface[entity.MapStatsID, []*entity.PlayerStat] 14 | TeamsByTeamID dataloader.Interface[entity.TeamID, *entity.Team] 15 | TeamByUserID dataloader.Interface[entity.UserID, []*entity.Team] 16 | ServersByUserID dataloader.Interface[entity.UserID, []*entity.GameServer] 17 | MatchByUserID dataloader.Interface[entity.UserID, []*entity.Match] 18 | } 19 | 20 | func (l *Loaders) ClearAll() { 21 | l.PlayersByTeamID.ClearAll() 22 | l.MapStatsByMatchID.ClearAll() 23 | l.PlayerStatsByMapStatID.ClearAll() 24 | l.TeamsByTeamID.ClearAll() 25 | l.TeamByUserID.ClearAll() 26 | l.ServersByUserID.ClearAll() 27 | l.MatchByUserID.ClearAll() 28 | } 29 | 30 | func NewLoaders( 31 | player usecase.Player, 32 | match usecase.Match, 33 | team usecase.Team, 34 | mapstats usecase.Mapstat, 35 | playerstats usecase.PlayerStat, 36 | server usecase.GameServer, 37 | ) *Loaders { 38 | teamPlayersLoader := &teamPlayersLoader{player: player} 39 | matchMapstatLoader := &matchMapstat{mapstat: mapstats} 40 | mapStatPlayerstatsLoader := &mapstatPlayerStats{playerstat: playerstats} 41 | teamsLoader := &teamsLoader{team: team} 42 | userTeamLoader := &userTeamLoader{team: team} 43 | userServersLoader := &userServersLoader{server: server} 44 | userMatchLoader := &userMatchLoader{match: match} 45 | return &Loaders{ 46 | PlayersByTeamID: dataloader.NewBatchedLoader[entity.TeamID, []*entity.Player](teamPlayersLoader.BatchGetPlayers), 47 | MapStatsByMatchID: dataloader.NewBatchedLoader[entity.MatchID, []*entity.MapStat](matchMapstatLoader.BatchGetMapStats), 48 | PlayerStatsByMapStatID: dataloader.NewBatchedLoader[entity.MapStatsID, []*entity.PlayerStat](mapStatPlayerstatsLoader.BatchGetMapstats), 49 | TeamsByTeamID: dataloader.NewBatchedLoader[entity.TeamID, *entity.Team](teamsLoader.BatchGetTeams), 50 | TeamByUserID: dataloader.NewBatchedLoader[entity.UserID, []*entity.Team](userTeamLoader.BatchGetTeams), 51 | ServersByUserID: dataloader.NewBatchedLoader[entity.UserID, []*entity.GameServer](userServersLoader.BatchGetServers), 52 | MatchByUserID: dataloader.NewBatchedLoader[entity.UserID, []*entity.Match](userMatchLoader.BatchGetMatches), 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /backend/cmd/di/gql.go: -------------------------------------------------------------------------------- 1 | package di 2 | 3 | import ( 4 | "net/http" 5 | "strings" 6 | 7 | "github.com/99designs/gqlgen/graphql/handler" 8 | "github.com/99designs/gqlgen/graphql/playground" 9 | "github.com/gin-gonic/gin" 10 | "golang.org/x/crypto/bcrypt" 11 | 12 | config "github.com/FlowingSPDG/get5loader/backend/cfg" 13 | "github.com/FlowingSPDG/get5loader/backend/g5ctx" 14 | "github.com/FlowingSPDG/get5loader/backend/graph" 15 | "github.com/FlowingSPDG/get5loader/backend/graph/dataloaders" 16 | "github.com/FlowingSPDG/get5loader/backend/service/jwt" 17 | hash "github.com/FlowingSPDG/get5loader/backend/service/password_hash" 18 | "github.com/FlowingSPDG/get5loader/backend/usecase" 19 | ) 20 | 21 | func InitializeGraphQLHandler(cfg config.Config) gin.HandlerFunc { 22 | // dependencies 23 | jwtService := jwt.NewJWTGateway([]byte(cfg.SecretKey)) 24 | passwordHasher := hash.NewPasswordHasher(bcrypt.DefaultCost) 25 | 26 | // usecases 27 | userUc := usecase.NewUser(jwtService, passwordHasher) 28 | teamUc := usecase.NewTeam() 29 | playerUc := usecase.NewPlayer() 30 | gameServerUc := usecase.NewGameServer() 31 | matchUc := usecase.NewMatch() 32 | mapStatUc := usecase.NewMapStats() 33 | playerStatUc := usecase.NewPlayerStat() 34 | 35 | // dataloader 36 | dl := dataloaders.NewLoaders(playerUc, matchUc, teamUc, mapStatUc, playerStatUc, gameServerUc) 37 | 38 | // handler 39 | h := handler.NewDefaultServer(graph.NewExecutableSchema(graph.Config{Resolvers: &graph.Resolver{ 40 | GameServerUsecase: gameServerUc, 41 | UserUsecase: userUc, 42 | MatchUsecase: matchUc, 43 | MapstatUsecase: mapStatUc, 44 | TeamUsecase: teamUc, 45 | PlayerUsecase: playerUc, 46 | DataLoader: dl, 47 | }})) 48 | 49 | // middleware 50 | validateJWTusecase := usecase.NewValidateJWT(jwtService) 51 | return func(c *gin.Context) { 52 | authorization := c.GetHeader("Authorization") 53 | authorization, _ = strings.CutPrefix(authorization, "Bearer ") 54 | token, err := validateJWTusecase.Validate(authorization) 55 | if err != nil { 56 | c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ 57 | "error": "unauthorized", 58 | }) 59 | return 60 | } 61 | 62 | // DataLoaderのキャッシュをクリアする 63 | dl.ClearAll() 64 | 65 | // contextにuserIDを入れる 66 | g5ctx.SetUserTokenGinContext(c, token) 67 | g5ctx.SetOperationGinContext(c, g5ctx.OperationTypeUser) 68 | c.Request = c.Request.WithContext(c) 69 | 70 | // serve 71 | h.ServeHTTP(c.Writer, c.Request) 72 | } 73 | } 74 | 75 | // Defining the Playground handler 76 | func InitializePlaygroundHandler() gin.HandlerFunc { 77 | h := playground.Handler("GraphQL", "/query") 78 | 79 | return func(c *gin.Context) { 80 | h.ServeHTTP(c.Writer, c.Request) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Go Build](https://github.com/FlowingSPDG/get5loader/actions/workflows/go_build.yaml/badge.svg)](https://github.com/FlowingSPDG/get5loader/actions/workflows/go_build.yaml) 2 | [![Go Test](https://github.com/FlowingSPDG/get5loader/actions/workflows/go_test.yaml/badge.svg)](https://github.com/FlowingSPDG/get5loader/actions/workflows/go_test.yaml) 3 | [![Downloads](https://img.shields.io/github/downloads/flowingspdg/get5-web-go/total?style=flat-square)](https://github.com/FlowingSPDG/get5loader/releases) 4 | [![LICENSE](https://img.shields.io/github/license/flowingspdg/get5-web-go?style=flat-square)](https://github.com/FlowingSPDG/get5loader/blob/master/LICENSE) 5 | 6 | get5loader 7 | =========================== 8 | **Status: UNDER DEVELOPMENT** 9 | 10 | ## Author 11 | Shugo Kawamura 12 | Github : [**FlowingSPDG**](http://github.com/FlowingSPDG) 13 | Twitter : [**@FlowingSPDG**](http://twitter.com/FlowingSPDG) 14 | 15 | ## About 16 | This is match management system for [get5](https://github.com/splewis/get5). 17 | Inspired by [get5-web](https://github.com/splewis/get5-web). 18 | 19 | 20 | ## How to use 21 | 1. Login by your SteamID. 22 | 2. Register your CS:GO servers on the "Add a server" section. 23 | 3. Register teams on the "Create a Team" section with steamids. 24 | 4. Go to the "Create a Match" page. 25 | 26 | API Server will send rcon command to load match config( ``get5_loadmatch_url /api/v1/match//config`` ) Then game server loads match and wait for players. 27 | 28 | ## ScreenShots 29 | ![Matches](/screenshots/Matches.PNG?raw=true "Matches list page") 30 | ![Match Stats Page](/screenshots/Match.PNG?raw=true "Match Stats Page") 31 | 32 | ## Requirements 33 | - Open HTTP access to access API. 34 | - Setup environment variables. 35 | - Setup database. 36 | 37 | ## Requirements(Developers) 38 | - Docker 39 | - Go v1.21 40 | - NodeJS and Yarn(Volta) 41 | - MySQL DB 42 | - CSGO Server with GET5 v0.15.0 [GET5](https://github.com/splewis/get5/releases) 43 | - Steam WebAPI Token for handling Steam-Login. ([here](https://steamcommunity.com/dev/apikey)) 44 | 45 | ## Setup(Developers) 46 | - ``git clone https://github.com/FlowingSPDG/get5loader.git $GOPATH/src/github.com/FlowingSPDG/get5loader`` (you can fork your own) 47 | - ``cd $GOPATH/src/github.com/FlowingSPDG/get5loader && make deps`` 48 | - You're good to Go! edit each `.go` files to fix/add something nice! 49 | - You can test your server by ``go run ./cmd/main.go``,and build them by ``make``.You may get binary files in ``./build``. 50 | 51 | ## Release 52 | I'm [releasing](https://github.com/FlowingSPDG/get5loader/releases) compiled-files for people who feel lazy to build for each major update. 53 | 54 | ## Deploy and Launch 55 | - Setup environment variables 56 | - Start your compiled binary 57 | - Now it's up! 58 | 59 | ## License 60 | ・[MIT](https://github.com/FlowingSPDG/get5loader/blob/master/LICENSE) 61 | -------------------------------------------------------------------------------- /backend/entity/entity.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import "time" 4 | 5 | type ( 6 | UserID string 7 | GameServerID string 8 | MatchID string 9 | TeamID string 10 | MapStatsID string 11 | PlayerID string 12 | PlayerStatsID string 13 | SteamID uint64 // SteamID64. Note: some database drivers may not support uint64. 14 | ) 15 | 16 | type User struct { 17 | ID UserID 18 | SteamID SteamID 19 | Name string 20 | Admin bool 21 | Hash []byte 22 | CreatedAt time.Time 23 | UpdatedAt time.Time 24 | } 25 | 26 | type GameServer struct { 27 | UserID UserID 28 | ID GameServerID 29 | Ip string 30 | Port uint32 31 | RCONPassword string 32 | DisplayName string 33 | IsPublic bool 34 | Status SERVER_STATUS 35 | } 36 | 37 | type Match struct { 38 | ID MatchID 39 | UserID UserID 40 | Team1ID TeamID 41 | Team2ID TeamID 42 | Winner TeamID // 0 for not decided yet 43 | StartTime *time.Time 44 | EndTime *time.Time 45 | MaxMaps int32 46 | Title string 47 | SkipVeto bool 48 | APIKey string 49 | Team1Score uint32 50 | Team2Score uint32 51 | Forfeit *bool 52 | Status MATCH_STATUS 53 | } 54 | 55 | type Get5Match struct { 56 | ID MatchID 57 | Team1 Get5Team 58 | Team2 Get5Team 59 | Winner TeamID // 0 for not decided yet 60 | MaxMaps int32 61 | Title string 62 | SkipVeto bool 63 | APIKey string 64 | } 65 | 66 | type MapStat struct { 67 | ID MapStatsID 68 | MatchID MatchID 69 | MapNumber uint32 70 | MapName string 71 | StartTime *time.Time 72 | EndTime *time.Time 73 | Winner *TeamID 74 | Team1Score uint32 75 | Team2Score uint32 76 | } 77 | 78 | type PlayerStat struct { 79 | ID PlayerStatsID 80 | MatchID MatchID 81 | MapID MapStatsID 82 | TeamID TeamID 83 | SteamID SteamID 84 | Name string 85 | 86 | Kills int32 87 | Assists int32 88 | Deaths int32 89 | 90 | RoundsPlayed uint32 91 | FlashbangAssists uint32 92 | Suicides uint32 93 | HeadShotKills uint32 94 | Damage uint32 95 | BombPlants uint32 96 | BombDefuses uint32 97 | 98 | V1 uint32 99 | V2 uint32 100 | V3 uint32 101 | V4 uint32 102 | V5 uint32 103 | K1 uint32 104 | K2 uint32 105 | K3 uint32 106 | K4 uint32 107 | K5 uint32 108 | 109 | FirstDeathCT uint32 110 | FirstDeathT uint32 111 | FirstKillCT uint32 112 | FirstKillT uint32 113 | } 114 | 115 | type Team struct { 116 | ID TeamID 117 | UserID UserID 118 | Name string 119 | Flag string 120 | Tag string 121 | Logo string 122 | Public bool 123 | } 124 | 125 | type Get5Team struct { 126 | ID TeamID 127 | Name string 128 | Flag string 129 | Tag string 130 | Logo string 131 | Players []*Player 132 | } 133 | 134 | type Player struct { 135 | ID PlayerID 136 | TeamID TeamID 137 | SteamID SteamID 138 | Name string 139 | } 140 | -------------------------------------------------------------------------------- /backend/gateway/database/mysql/users/users.go: -------------------------------------------------------------------------------- 1 | package users 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "errors" 7 | 8 | "github.com/FlowingSPDG/get5loader/backend/entity" 9 | "github.com/FlowingSPDG/get5loader/backend/gateway/database" 10 | users_gen "github.com/FlowingSPDG/get5loader/backend/gateway/database/mysql/users/generated" 11 | "github.com/FlowingSPDG/get5loader/backend/service/uuid" 12 | ) 13 | 14 | type usersRepositry struct { 15 | uuidGenerator uuid.UUIDGenerator 16 | queries *users_gen.Queries 17 | } 18 | 19 | func NewUsersRepositry(uuidGenerator uuid.UUIDGenerator, db *sql.DB) database.UsersRepositry { 20 | queries := users_gen.New(db) 21 | return &usersRepositry{ 22 | uuidGenerator: uuidGenerator, 23 | queries: queries, 24 | } 25 | } 26 | 27 | func NewUsersRepositryWithTx(uuidGenerator uuid.UUIDGenerator, db *sql.DB, tx *sql.Tx) database.UsersRepositry { 28 | queries := users_gen.New(db).WithTx(tx) 29 | return &usersRepositry{ 30 | uuidGenerator: uuidGenerator, 31 | queries: queries, 32 | } 33 | } 34 | 35 | // CreateUser implements database.UsersRepositry. 36 | func (ur *usersRepositry) CreateUser(ctx context.Context, steamID entity.SteamID, name string, admin bool, hash []byte) (entity.UserID, error) { 37 | id := ur.uuidGenerator.Generate() 38 | if _, err := ur.queries.CreateUser(ctx, users_gen.CreateUserParams{ 39 | ID: id, 40 | SteamID: uint64(steamID), 41 | Name: name, 42 | Admin: admin, 43 | PasswordHash: hash, 44 | }); err != nil { 45 | return "", database.NewInternalError(err) 46 | } 47 | 48 | return entity.UserID(id), nil 49 | } 50 | 51 | // GetUser implements database.UsersRepositry. 52 | func (ur *usersRepositry) GetUser(ctx context.Context, id entity.UserID) (*database.User, error) { 53 | user, err := ur.queries.GetUser(ctx, string(id)) 54 | if err != nil { 55 | if errors.Is(err, sql.ErrNoRows) { 56 | return nil, database.NewNotFoundError(nil) 57 | } 58 | return nil, database.NewInternalError(err) 59 | } 60 | return &database.User{ 61 | ID: entity.UserID(user.ID), 62 | SteamID: entity.SteamID(user.SteamID), 63 | Name: user.Name, 64 | Admin: user.Admin, 65 | Hash: user.PasswordHash, 66 | CreatedAt: user.CreatedAt, 67 | UpdatedAt: user.UpdatedAt, 68 | }, nil 69 | } 70 | 71 | // GetUserBySteamID implements database.UsersRepositry. 72 | func (ur *usersRepositry) GetUserBySteamID(ctx context.Context, steamID entity.SteamID) (*database.User, error) { 73 | user, err := ur.queries.GetUserBySteamID(ctx, uint64(steamID)) 74 | if err != nil { 75 | if errors.Is(err, sql.ErrNoRows) { 76 | return nil, database.NewNotFoundError(nil) 77 | } 78 | return nil, database.NewInternalError(err) 79 | } 80 | return &database.User{ 81 | ID: entity.UserID(user.ID), 82 | SteamID: entity.SteamID(user.SteamID), 83 | Name: user.Name, 84 | Admin: user.Admin, 85 | Hash: user.PasswordHash, 86 | CreatedAt: user.CreatedAt, 87 | UpdatedAt: user.UpdatedAt, 88 | }, nil 89 | } 90 | -------------------------------------------------------------------------------- /backend/usecase/match.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/FlowingSPDG/get5loader/backend/entity" 7 | "github.com/FlowingSPDG/get5loader/backend/gateway/database" 8 | ) 9 | 10 | type Match interface { 11 | CreateMatch(ctx context.Context, userID entity.UserID, serverID entity.GameServerID, team1ID entity.TeamID, team2ID entity.TeamID, maxMaps int, title string) (*entity.Match, error) 12 | GetMatch(ctx context.Context, matchID entity.MatchID) (*entity.Match, error) 13 | GetMatchesByUser(ctx context.Context, userID entity.UserID) ([]*entity.Match, error) 14 | // BATCH 15 | BatchGetMatchesByUser(ctx context.Context, userIDs []entity.UserID) (map[entity.UserID][]*entity.Match, error) 16 | } 17 | 18 | type match struct { 19 | } 20 | 21 | func NewMatch() Match { 22 | return &match{} 23 | } 24 | 25 | func (gm *match) GetMatch(ctx context.Context, matchID entity.MatchID) (*entity.Match, error) { 26 | // TODO: publicでない場合の認証処理の追加 27 | repositoryConnector := database.GetConnection(ctx) 28 | 29 | matchRepository := repositoryConnector.GetMatchesRepository() 30 | 31 | match, err := matchRepository.GetMatch(ctx, matchID) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | return convertMatch(match), nil 37 | } 38 | 39 | // CreateMatch implements Match. 40 | func (gm *match) CreateMatch(ctx context.Context, userID entity.UserID, serverID entity.GameServerID, team1ID entity.TeamID, team2ID entity.TeamID, maxMaps int, title string) (*entity.Match, error) { 41 | repositoryConnector := database.GetConnection(ctx) 42 | 43 | matchRepository := repositoryConnector.GetMatchesRepository() 44 | 45 | mID, err := matchRepository.AddMatch(ctx, userID, serverID, team1ID, team2ID, int32(maxMaps), title, false, "") 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | m, err := matchRepository.GetMatch(ctx, mID) 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | match := convertMatch(m) 56 | 57 | return match, nil 58 | } 59 | 60 | // GetMatchesByUser implements Match. 61 | func (gm *match) GetMatchesByUser(ctx context.Context, userID entity.UserID) ([]*entity.Match, error) { 62 | // TODO: publicでない場合の認証処理の追加 63 | repositoryConnector := database.GetConnection(ctx) 64 | 65 | matchRepository := repositoryConnector.GetMatchesRepository() 66 | 67 | matches, err := matchRepository.GetMatchesByUser(ctx, userID) 68 | if err != nil { 69 | return nil, err 70 | } 71 | 72 | return convertMatches(matches), nil 73 | } 74 | 75 | // GetMatchesByUsers implements Match. 76 | func (*match) BatchGetMatchesByUser(ctx context.Context, userIDs []entity.UserID) (map[entity.UserID][]*entity.Match, error) { 77 | // TODO: publicでない場合の認証処理の追加 78 | repositoryConnector := database.GetConnection(ctx) 79 | 80 | matchRepository := repositoryConnector.GetMatchesRepository() 81 | 82 | matches, err := matchRepository.GetMatchesByUsers(ctx, userIDs) 83 | if err != nil { 84 | return nil, err 85 | } 86 | 87 | ret := make(map[entity.UserID][]*entity.Match, len(userIDs)) 88 | for userID, matches := range matches { 89 | ret[userID] = convertMatches(matches) 90 | } 91 | 92 | return ret, nil 93 | } 94 | -------------------------------------------------------------------------------- /backend/usecase/mock/mapstats.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: mapstats.go 3 | 4 | // Package mock_usecase is a generated GoMock package. 5 | package mock_usecase 6 | 7 | import ( 8 | context "context" 9 | reflect "reflect" 10 | 11 | entity "github.com/FlowingSPDG/get5loader/backend/entity" 12 | gomock "go.uber.org/mock/gomock" 13 | ) 14 | 15 | // MockMapstat is a mock of Mapstat interface. 16 | type MockMapstat struct { 17 | ctrl *gomock.Controller 18 | recorder *MockMapstatMockRecorder 19 | } 20 | 21 | // MockMapstatMockRecorder is the mock recorder for MockMapstat. 22 | type MockMapstatMockRecorder struct { 23 | mock *MockMapstat 24 | } 25 | 26 | // NewMockMapstat creates a new mock instance. 27 | func NewMockMapstat(ctrl *gomock.Controller) *MockMapstat { 28 | mock := &MockMapstat{ctrl: ctrl} 29 | mock.recorder = &MockMapstatMockRecorder{mock} 30 | return mock 31 | } 32 | 33 | // EXPECT returns an object that allows the caller to indicate expected use. 34 | func (m *MockMapstat) EXPECT() *MockMapstatMockRecorder { 35 | return m.recorder 36 | } 37 | 38 | // BatchGetMapstatsByMatch mocks base method. 39 | func (m *MockMapstat) BatchGetMapstatsByMatch(ctx context.Context, matchIDs []entity.MatchID) (map[entity.MatchID][]*entity.MapStat, error) { 40 | m.ctrl.T.Helper() 41 | ret := m.ctrl.Call(m, "BatchGetMapstatsByMatch", ctx, matchIDs) 42 | ret0, _ := ret[0].(map[entity.MatchID][]*entity.MapStat) 43 | ret1, _ := ret[1].(error) 44 | return ret0, ret1 45 | } 46 | 47 | // BatchGetMapstatsByMatch indicates an expected call of BatchGetMapstatsByMatch. 48 | func (mr *MockMapstatMockRecorder) BatchGetMapstatsByMatch(ctx, matchIDs interface{}) *gomock.Call { 49 | mr.mock.ctrl.T.Helper() 50 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BatchGetMapstatsByMatch", reflect.TypeOf((*MockMapstat)(nil).BatchGetMapstatsByMatch), ctx, matchIDs) 51 | } 52 | 53 | // GetMapStat mocks base method. 54 | func (m *MockMapstat) GetMapStat(ctx context.Context, id entity.MapStatsID) (*entity.MapStat, error) { 55 | m.ctrl.T.Helper() 56 | ret := m.ctrl.Call(m, "GetMapStat", ctx, id) 57 | ret0, _ := ret[0].(*entity.MapStat) 58 | ret1, _ := ret[1].(error) 59 | return ret0, ret1 60 | } 61 | 62 | // GetMapStat indicates an expected call of GetMapStat. 63 | func (mr *MockMapstatMockRecorder) GetMapStat(ctx, id interface{}) *gomock.Call { 64 | mr.mock.ctrl.T.Helper() 65 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMapStat", reflect.TypeOf((*MockMapstat)(nil).GetMapStat), ctx, id) 66 | } 67 | 68 | // GetMapStatsByMatch mocks base method. 69 | func (m *MockMapstat) GetMapStatsByMatch(ctx context.Context, matchID entity.MatchID) ([]*entity.MapStat, error) { 70 | m.ctrl.T.Helper() 71 | ret := m.ctrl.Call(m, "GetMapStatsByMatch", ctx, matchID) 72 | ret0, _ := ret[0].([]*entity.MapStat) 73 | ret1, _ := ret[1].(error) 74 | return ret0, ret1 75 | } 76 | 77 | // GetMapStatsByMatch indicates an expected call of GetMapStatsByMatch. 78 | func (mr *MockMapstatMockRecorder) GetMapStatsByMatch(ctx, matchID interface{}) *gomock.Call { 79 | mr.mock.ctrl.T.Helper() 80 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMapStatsByMatch", reflect.TypeOf((*MockMapstat)(nil).GetMapStatsByMatch), ctx, matchID) 81 | } 82 | -------------------------------------------------------------------------------- /backend/controller/get5/loader.go: -------------------------------------------------------------------------------- 1 | package get5 2 | 3 | import ( 4 | "context" 5 | "strconv" 6 | 7 | got5 "github.com/FlowingSPDG/Got5" 8 | 9 | "github.com/FlowingSPDG/get5loader/backend/entity" 10 | "github.com/FlowingSPDG/get5loader/backend/gateway/database" 11 | "github.com/FlowingSPDG/get5loader/backend/usecase" 12 | ) 13 | 14 | type matchLoader struct { 15 | repositoryConnector database.RepositoryConnector 16 | } 17 | 18 | func NewGot5MatchLoader(repositoryConnector database.RepositoryConnector) got5.MatchLoader { 19 | return &matchLoader{ 20 | repositoryConnector: repositoryConnector, 21 | } 22 | } 23 | 24 | type match struct { 25 | match *entity.Get5Match 26 | } 27 | 28 | func (m *match) ToG5Format() got5.Match { 29 | team1Players := map[string]string{} 30 | for _, player := range m.match.Team1.Players { 31 | team1Players[strconv.Itoa(int(player.SteamID))] = player.Name 32 | } 33 | team2Players := map[string]string{} 34 | for _, player := range m.match.Team2.Players { 35 | team2Players[strconv.Itoa(int(player.SteamID))] = player.Name 36 | } 37 | 38 | return got5.Match{ 39 | MatchTitle: m.match.Title, 40 | MatchID: string(m.match.ID), 41 | ClinchSeries: false, 42 | NumMaps: int(m.match.MaxMaps), 43 | Scrim: false, 44 | Wingman: false, 45 | PlayersPerTeam: 5, 46 | CoachesPerTeam: 0, 47 | CoachesMustReady: false, 48 | MinPlayersToReady: 5, 49 | MinSpectatorsToReady: 0, 50 | SkipVeto: m.match.SkipVeto, 51 | VetoFirst: "random", 52 | VetoMode: "", 53 | SideType: "standard", 54 | MapSides: []string{}, 55 | Spectators: got5.Spectators{}, 56 | Maplist: []string{ 57 | "de_inferno", 58 | "de_mirage", 59 | "de_nuke", 60 | "de_overpass", 61 | "de_vertigo", 62 | "de_ancient", 63 | "de_anubis", 64 | }, 65 | FavoredPercentageTeam1: 0, 66 | FavoredPercentageText: "", 67 | Team1: got5.Team{ 68 | ID: string(m.match.Team1.ID), 69 | Players: team1Players, 70 | Coaches: map[string]string{}, 71 | Name: m.match.Team1.Name, 72 | Tag: m.match.Team1.Tag, 73 | Flag: m.match.Team1.Flag, 74 | Logo: m.match.Team1.Logo, 75 | SeriesScore: 0, 76 | MatchText: "", 77 | FromFile: "", 78 | }, 79 | Team2: got5.Team{ 80 | ID: string(m.match.Team2.ID), 81 | Players: team2Players, 82 | Coaches: map[string]string{}, 83 | Name: m.match.Team2.Name, 84 | Tag: m.match.Team2.Tag, 85 | Flag: m.match.Team2.Flag, 86 | Logo: m.match.Team2.Logo, 87 | SeriesScore: 0, 88 | MatchText: "", 89 | FromFile: "", 90 | }, 91 | Cvars: map[string]string{}, 92 | } 93 | } 94 | 95 | // Load implements got5.MatchLoader. 96 | func (ml *matchLoader) Load(ctx context.Context, mid string) (got5.G5Match, error) { 97 | uc := usecase.NewGet5() 98 | m, err := uc.GetMatch(ctx, entity.MatchID(mid)) 99 | if err != nil { 100 | return nil, err 101 | } 102 | return &match{ 103 | match: m, 104 | }, nil 105 | 106 | } 107 | -------------------------------------------------------------------------------- /backend/gateway/database/mysql/users/generated/users_query.sql.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.20.0 4 | // source: users_query.sql 5 | 6 | package users_gen 7 | 8 | import ( 9 | "context" 10 | "database/sql" 11 | "strings" 12 | ) 13 | 14 | const createUser = `-- name: CreateUser :execresult 15 | INSERT INTO users ( 16 | id ,steam_id, name, admin, password_hash 17 | ) VALUES ( 18 | ?, ?, ?, ?, ? 19 | ) 20 | ` 21 | 22 | type CreateUserParams struct { 23 | ID string 24 | SteamID uint64 25 | Name string 26 | Admin bool 27 | PasswordHash []byte 28 | } 29 | 30 | func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (sql.Result, error) { 31 | return q.db.ExecContext(ctx, createUser, 32 | arg.ID, 33 | arg.SteamID, 34 | arg.Name, 35 | arg.Admin, 36 | arg.PasswordHash, 37 | ) 38 | } 39 | 40 | const getUser = `-- name: GetUser :one 41 | SELECT id, steam_id, name, admin, created_at, updated_at, password_hash FROM users 42 | WHERE id = ? LIMIT 1 43 | ` 44 | 45 | func (q *Queries) GetUser(ctx context.Context, id string) (User, error) { 46 | row := q.db.QueryRowContext(ctx, getUser, id) 47 | var i User 48 | err := row.Scan( 49 | &i.ID, 50 | &i.SteamID, 51 | &i.Name, 52 | &i.Admin, 53 | &i.CreatedAt, 54 | &i.UpdatedAt, 55 | &i.PasswordHash, 56 | ) 57 | return i, err 58 | } 59 | 60 | const getUserBySteamID = `-- name: GetUserBySteamID :one 61 | SELECT id, steam_id, name, admin, created_at, updated_at, password_hash FROM users 62 | WHERE steam_id = ? LIMIT 1 63 | ` 64 | 65 | func (q *Queries) GetUserBySteamID(ctx context.Context, steamID uint64) (User, error) { 66 | row := q.db.QueryRowContext(ctx, getUserBySteamID, steamID) 67 | var i User 68 | err := row.Scan( 69 | &i.ID, 70 | &i.SteamID, 71 | &i.Name, 72 | &i.Admin, 73 | &i.CreatedAt, 74 | &i.UpdatedAt, 75 | &i.PasswordHash, 76 | ) 77 | return i, err 78 | } 79 | 80 | const getUsers = `-- name: GetUsers :many 81 | SELECT id, steam_id, name, admin, created_at, updated_at, password_hash FROM users 82 | WHERE id IN (/*SLICE:user_ids*/?) 83 | ` 84 | 85 | func (q *Queries) GetUsers(ctx context.Context, userIds []string) ([]User, error) { 86 | query := getUsers 87 | var queryParams []interface{} 88 | if len(userIds) > 0 { 89 | for _, v := range userIds { 90 | queryParams = append(queryParams, v) 91 | } 92 | query = strings.Replace(query, "/*SLICE:user_ids*/?", strings.Repeat(",?", len(userIds))[1:], 1) 93 | } else { 94 | query = strings.Replace(query, "/*SLICE:user_ids*/?", "NULL", 1) 95 | } 96 | rows, err := q.db.QueryContext(ctx, query, queryParams...) 97 | if err != nil { 98 | return nil, err 99 | } 100 | defer rows.Close() 101 | var items []User 102 | for rows.Next() { 103 | var i User 104 | if err := rows.Scan( 105 | &i.ID, 106 | &i.SteamID, 107 | &i.Name, 108 | &i.Admin, 109 | &i.CreatedAt, 110 | &i.UpdatedAt, 111 | &i.PasswordHash, 112 | ); err != nil { 113 | return nil, err 114 | } 115 | items = append(items, i) 116 | } 117 | if err := rows.Close(); err != nil { 118 | return nil, err 119 | } 120 | if err := rows.Err(); err != nil { 121 | return nil, err 122 | } 123 | return items, nil 124 | } 125 | -------------------------------------------------------------------------------- /backend/gateway/database/mysql/connector/repository.go: -------------------------------------------------------------------------------- 1 | package mysqlconnector 2 | 3 | import ( 4 | "github.com/FlowingSPDG/get5loader/backend/gateway/database" 5 | "github.com/FlowingSPDG/get5loader/backend/gateway/database/mysql/gameservers" 6 | "github.com/FlowingSPDG/get5loader/backend/gateway/database/mysql/mapstats" 7 | "github.com/FlowingSPDG/get5loader/backend/gateway/database/mysql/matches" 8 | "github.com/FlowingSPDG/get5loader/backend/gateway/database/mysql/players" 9 | "github.com/FlowingSPDG/get5loader/backend/gateway/database/mysql/playerstats" 10 | "github.com/FlowingSPDG/get5loader/backend/gateway/database/mysql/teams" 11 | "github.com/FlowingSPDG/get5loader/backend/gateway/database/mysql/users" 12 | "github.com/FlowingSPDG/get5loader/backend/service/uuid" 13 | ) 14 | 15 | type mysqlRepositoryConnector struct { 16 | uuidGenerator uuid.UUIDGenerator 17 | connector database.DBConnector 18 | } 19 | 20 | func NewMySQLRepositoryConnector(uuidGenerator uuid.UUIDGenerator, connector database.DBConnector) database.RepositoryConnector { 21 | return &mysqlRepositoryConnector{ 22 | uuidGenerator: uuidGenerator, 23 | connector: connector, 24 | } 25 | } 26 | 27 | // Close implements database.RepositoryConnector. 28 | func (mrc *mysqlRepositoryConnector) Close() error { 29 | return mrc.connector.Close() 30 | } 31 | 32 | func (mrc *mysqlRepositoryConnector) Open() error { 33 | if err := mrc.connector.Open(); err != nil { 34 | return err 35 | } 36 | return nil 37 | } 38 | 39 | // OpenGameServersRepository implements database.RepositoryConnector. 40 | func (mrc *mysqlRepositoryConnector) GetGameServersRepository() database.GameServersRepository { 41 | conn := mrc.connector.GetConnection() 42 | return gameservers.NewGameServerRepository(mrc.uuidGenerator, conn) 43 | } 44 | 45 | // OpenMapStatRepository implements database.RepositoryConnector. 46 | func (mrc *mysqlRepositoryConnector) GetMapStatRepository() database.MapStatRepository { 47 | conn := mrc.connector.GetConnection() 48 | return mapstats.NewMapStatRepository(mrc.uuidGenerator, conn) 49 | } 50 | 51 | // OpenMatchesRepository implements database.RepositoryConnector. 52 | func (mrc *mysqlRepositoryConnector) GetMatchesRepository() database.MatchesRepository { 53 | conn := mrc.connector.GetConnection() 54 | return matches.NewMatchRepository(mrc.uuidGenerator, conn) 55 | } 56 | 57 | // OpenPlayerStatRepository implements database.RepositoryConnector. 58 | func (mrc *mysqlRepositoryConnector) GetPlayerStatRepository() database.PlayerStatRepository { 59 | conn := mrc.connector.GetConnection() 60 | return playerstats.NewPlayerStatRepository(mrc.uuidGenerator, conn) 61 | } 62 | 63 | // OpenPlayersRepository implements database.RepositoryConnector. 64 | func (mrc *mysqlRepositoryConnector) GetPlayersRepository() database.PlayersRepository { 65 | conn := mrc.connector.GetConnection() 66 | return players.NewPlayersRepository(mrc.uuidGenerator, conn) 67 | } 68 | 69 | // OpenTeamsRepository implements database.RepositoryConnector. 70 | func (mrc *mysqlRepositoryConnector) GetTeamsRepository() database.TeamsRepository { 71 | conn := mrc.connector.GetConnection() 72 | return teams.NewTeamsRepository(mrc.uuidGenerator, conn) 73 | } 74 | 75 | // OpenUserRepository implements database.RepositoryConnector. 76 | func (mrc *mysqlRepositoryConnector) GetUserRepository() database.UsersRepositry { 77 | conn := mrc.connector.GetConnection() 78 | return users.NewUsersRepositry(mrc.uuidGenerator, conn) 79 | } 80 | -------------------------------------------------------------------------------- /backend/usecase/mock/user.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: user.go 3 | 4 | // Package mock_usecase is a generated GoMock package. 5 | package mock_usecase 6 | 7 | import ( 8 | context "context" 9 | reflect "reflect" 10 | 11 | entity "github.com/FlowingSPDG/get5loader/backend/entity" 12 | gomock "go.uber.org/mock/gomock" 13 | ) 14 | 15 | // MockUser is a mock of User interface. 16 | type MockUser struct { 17 | ctrl *gomock.Controller 18 | recorder *MockUserMockRecorder 19 | } 20 | 21 | // MockUserMockRecorder is the mock recorder for MockUser. 22 | type MockUserMockRecorder struct { 23 | mock *MockUser 24 | } 25 | 26 | // NewMockUser creates a new mock instance. 27 | func NewMockUser(ctrl *gomock.Controller) *MockUser { 28 | mock := &MockUser{ctrl: ctrl} 29 | mock.recorder = &MockUserMockRecorder{mock} 30 | return mock 31 | } 32 | 33 | // EXPECT returns an object that allows the caller to indicate expected use. 34 | func (m *MockUser) EXPECT() *MockUserMockRecorder { 35 | return m.recorder 36 | } 37 | 38 | // GetUser mocks base method. 39 | func (m *MockUser) GetUser(ctx context.Context, id entity.UserID) (*entity.User, error) { 40 | m.ctrl.T.Helper() 41 | ret := m.ctrl.Call(m, "GetUser", ctx, id) 42 | ret0, _ := ret[0].(*entity.User) 43 | ret1, _ := ret[1].(error) 44 | return ret0, ret1 45 | } 46 | 47 | // GetUser indicates an expected call of GetUser. 48 | func (mr *MockUserMockRecorder) GetUser(ctx, id interface{}) *gomock.Call { 49 | mr.mock.ctrl.T.Helper() 50 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUser", reflect.TypeOf((*MockUser)(nil).GetUser), ctx, id) 51 | } 52 | 53 | // IssueJWT mocks base method. 54 | func (m *MockUser) IssueJWT(ctx context.Context, userID entity.UserID, password string) (string, error) { 55 | m.ctrl.T.Helper() 56 | ret := m.ctrl.Call(m, "IssueJWT", ctx, userID, password) 57 | ret0, _ := ret[0].(string) 58 | ret1, _ := ret[1].(error) 59 | return ret0, ret1 60 | } 61 | 62 | // IssueJWT indicates an expected call of IssueJWT. 63 | func (mr *MockUserMockRecorder) IssueJWT(ctx, userID, password interface{}) *gomock.Call { 64 | mr.mock.ctrl.T.Helper() 65 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IssueJWT", reflect.TypeOf((*MockUser)(nil).IssueJWT), ctx, userID, password) 66 | } 67 | 68 | // IssueJWTBySteamID mocks base method. 69 | func (m *MockUser) IssueJWTBySteamID(ctx context.Context, steamID entity.SteamID, password string) (string, error) { 70 | m.ctrl.T.Helper() 71 | ret := m.ctrl.Call(m, "IssueJWTBySteamID", ctx, steamID, password) 72 | ret0, _ := ret[0].(string) 73 | ret1, _ := ret[1].(error) 74 | return ret0, ret1 75 | } 76 | 77 | // IssueJWTBySteamID indicates an expected call of IssueJWTBySteamID. 78 | func (mr *MockUserMockRecorder) IssueJWTBySteamID(ctx, steamID, password interface{}) *gomock.Call { 79 | mr.mock.ctrl.T.Helper() 80 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IssueJWTBySteamID", reflect.TypeOf((*MockUser)(nil).IssueJWTBySteamID), ctx, steamID, password) 81 | } 82 | 83 | // Register mocks base method. 84 | func (m *MockUser) Register(ctx context.Context, steamID entity.SteamID, name string, admin bool, password string) (string, error) { 85 | m.ctrl.T.Helper() 86 | ret := m.ctrl.Call(m, "Register", ctx, steamID, name, admin, password) 87 | ret0, _ := ret[0].(string) 88 | ret1, _ := ret[1].(error) 89 | return ret0, ret1 90 | } 91 | 92 | // Register indicates an expected call of Register. 93 | func (mr *MockUserMockRecorder) Register(ctx, steamID, name, admin, password interface{}) *gomock.Call { 94 | mr.mock.ctrl.T.Helper() 95 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Register", reflect.TypeOf((*MockUser)(nil).Register), ctx, steamID, name, admin, password) 96 | } 97 | -------------------------------------------------------------------------------- /backend/gateway/database/mysql/players/generated/players_query.sql.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.20.0 4 | // source: players_query.sql 5 | 6 | package players_gen 7 | 8 | import ( 9 | "context" 10 | "database/sql" 11 | "strings" 12 | ) 13 | 14 | const addPlayer = `-- name: AddPlayer :execresult 15 | INSERT INTO players ( 16 | id, 17 | team_id, 18 | steam_id, 19 | name 20 | ) VALUES ( 21 | ?, 22 | ?, 23 | ?, 24 | ? 25 | ) 26 | ` 27 | 28 | type AddPlayerParams struct { 29 | ID string 30 | TeamID string 31 | SteamID uint64 32 | Name string 33 | } 34 | 35 | func (q *Queries) AddPlayer(ctx context.Context, arg AddPlayerParams) (sql.Result, error) { 36 | return q.db.ExecContext(ctx, addPlayer, 37 | arg.ID, 38 | arg.TeamID, 39 | arg.SteamID, 40 | arg.Name, 41 | ) 42 | } 43 | 44 | const getPlayer = `-- name: GetPlayer :one 45 | SELECT id, team_id, steam_id, name FROM players 46 | WHERE id = ? LIMIT 1 47 | ` 48 | 49 | func (q *Queries) GetPlayer(ctx context.Context, id string) (Player, error) { 50 | row := q.db.QueryRowContext(ctx, getPlayer, id) 51 | var i Player 52 | err := row.Scan( 53 | &i.ID, 54 | &i.TeamID, 55 | &i.SteamID, 56 | &i.Name, 57 | ) 58 | return i, err 59 | } 60 | 61 | const getPlayerBySteamID = `-- name: GetPlayerBySteamID :one 62 | SELECT id, team_id, steam_id, name FROM players 63 | WHERE steam_id = ? LIMIT 1 64 | ` 65 | 66 | func (q *Queries) GetPlayerBySteamID(ctx context.Context, steamID uint64) (Player, error) { 67 | row := q.db.QueryRowContext(ctx, getPlayerBySteamID, steamID) 68 | var i Player 69 | err := row.Scan( 70 | &i.ID, 71 | &i.TeamID, 72 | &i.SteamID, 73 | &i.Name, 74 | ) 75 | return i, err 76 | } 77 | 78 | const getPlayersByTeam = `-- name: GetPlayersByTeam :many 79 | SELECT id, team_id, steam_id, name FROM players 80 | WHERE team_id = ? 81 | ` 82 | 83 | func (q *Queries) GetPlayersByTeam(ctx context.Context, teamID string) ([]Player, error) { 84 | rows, err := q.db.QueryContext(ctx, getPlayersByTeam, teamID) 85 | if err != nil { 86 | return nil, err 87 | } 88 | defer rows.Close() 89 | var items []Player 90 | for rows.Next() { 91 | var i Player 92 | if err := rows.Scan( 93 | &i.ID, 94 | &i.TeamID, 95 | &i.SteamID, 96 | &i.Name, 97 | ); err != nil { 98 | return nil, err 99 | } 100 | items = append(items, i) 101 | } 102 | if err := rows.Close(); err != nil { 103 | return nil, err 104 | } 105 | if err := rows.Err(); err != nil { 106 | return nil, err 107 | } 108 | return items, nil 109 | } 110 | 111 | const getPlayersByTeams = `-- name: GetPlayersByTeams :many 112 | SELECT id, team_id, steam_id, name FROM players 113 | WHERE team_id IN (/*SLICE:team_ids*/?) 114 | ` 115 | 116 | func (q *Queries) GetPlayersByTeams(ctx context.Context, teamIds []string) ([]Player, error) { 117 | query := getPlayersByTeams 118 | var queryParams []interface{} 119 | if len(teamIds) > 0 { 120 | for _, v := range teamIds { 121 | queryParams = append(queryParams, v) 122 | } 123 | query = strings.Replace(query, "/*SLICE:team_ids*/?", strings.Repeat(",?", len(teamIds))[1:], 1) 124 | } else { 125 | query = strings.Replace(query, "/*SLICE:team_ids*/?", "NULL", 1) 126 | } 127 | rows, err := q.db.QueryContext(ctx, query, queryParams...) 128 | if err != nil { 129 | return nil, err 130 | } 131 | defer rows.Close() 132 | var items []Player 133 | for rows.Next() { 134 | var i Player 135 | if err := rows.Scan( 136 | &i.ID, 137 | &i.TeamID, 138 | &i.SteamID, 139 | &i.Name, 140 | ); err != nil { 141 | return nil, err 142 | } 143 | items = append(items, i) 144 | } 145 | if err := rows.Close(); err != nil { 146 | return nil, err 147 | } 148 | if err := rows.Err(); err != nil { 149 | return nil, err 150 | } 151 | return items, nil 152 | } 153 | -------------------------------------------------------------------------------- /backend/usecase/gameserver.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/FlowingSPDG/get5loader/backend/entity" 7 | "github.com/FlowingSPDG/get5loader/backend/gateway/database" 8 | ) 9 | 10 | type GameServer interface { 11 | // GetPublicServers returns public game servers. 12 | GetPublicServers(ctx context.Context) ([]*entity.GameServer, error) 13 | // GetGameServer returns a game server. 14 | GetGameServer(ctx context.Context, id entity.GameServerID) (*entity.GameServer, error) 15 | // GetGameServersByUser returns game servers owned by a user. 16 | // AddGameServer adds a game server. 17 | AddGameServer(ctx context.Context, userID entity.UserID, ip string, port uint32, rconPassword string, name string, isPublic bool) (*entity.GameServer, error) 18 | // DeleteGameServer deletes a game server. 19 | DeleteGameServer(ctx context.Context, id entity.GameServerID) error 20 | 21 | BatchGetGameServersByUser(ctx context.Context, userIDs []entity.UserID) (map[entity.UserID][]*entity.GameServer, error) 22 | } 23 | 24 | type gameServer struct { 25 | } 26 | 27 | func NewGameServer() GameServer { 28 | return &gameServer{} 29 | } 30 | 31 | // GetPublicServers implements GameServer. 32 | func (gs *gameServer) GetPublicServers(ctx context.Context) ([]*entity.GameServer, error) { 33 | repositoryConnector := database.GetConnection(ctx) 34 | repository := repositoryConnector.GetGameServersRepository() 35 | 36 | gameServers, err := repository.GetPublicGameServers(ctx) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | return convertGameServers(gameServers), nil 42 | } 43 | 44 | // AddGameServer implements GameServer. 45 | func (gs *gameServer) AddGameServer(ctx context.Context, userID entity.UserID, ip string, port uint32, rconPassword string, name string, isPublic bool) (*entity.GameServer, error) { 46 | repositoryConnector := database.GetConnection(ctx) 47 | repository := repositoryConnector.GetGameServersRepository() 48 | 49 | gameServerID, err := repository.AddGameServer(ctx, userID, ip, port, rconPassword, name, isPublic) 50 | if err != nil { 51 | return nil, err 52 | } 53 | 54 | gameServer, err := repository.GetGameServer(ctx, gameServerID) 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | return convertGameServer(gameServer), nil 60 | } 61 | 62 | // DeleteGameServer implements GameServer. 63 | func (gs *gameServer) DeleteGameServer(ctx context.Context, id entity.GameServerID) error { 64 | panic("unimplemented") 65 | } 66 | 67 | // GetGameServer implements GameServer. 68 | func (gs *gameServer) GetGameServer(ctx context.Context, id entity.GameServerID) (*entity.GameServer, error) { 69 | panic("unimplemented") 70 | } 71 | 72 | // GetGameServersByUser implements GameServer. 73 | func (gs *gameServer) GetGameServersByUser(ctx context.Context, userID entity.UserID) ([]*entity.GameServer, error) { 74 | repositoryConnector := database.GetConnection(ctx) 75 | 76 | repository := repositoryConnector.GetGameServersRepository() 77 | 78 | gss, err := repository.GetGameServersByUser(ctx, userID) 79 | if err != nil { 80 | return nil, err 81 | } 82 | 83 | return convertGameServers(gss), nil 84 | } 85 | 86 | // BatchGetGameServersByUser implements GameServer. 87 | func (gs *gameServer) BatchGetGameServersByUser(ctx context.Context, userIDs []entity.UserID) (map[entity.UserID][]*entity.GameServer, error) { 88 | repositoryConnector := database.GetConnection(ctx) 89 | 90 | repository := repositoryConnector.GetGameServersRepository() 91 | 92 | gss, err := repository.GetGameServersByUsers(ctx, userIDs) 93 | if err != nil { 94 | return nil, err 95 | } 96 | 97 | ret := make(map[entity.UserID][]*entity.GameServer, len(userIDs)) 98 | // nilが渡されるのを防ぐため、空のスライスを生成する 99 | for userID, gss := range gss { 100 | ret[userID] = convertGameServers(gss) 101 | } 102 | 103 | return ret, nil 104 | } 105 | -------------------------------------------------------------------------------- /backend/usecase/mock/match.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: match.go 3 | 4 | // Package mock_usecase is a generated GoMock package. 5 | package mock_usecase 6 | 7 | import ( 8 | context "context" 9 | reflect "reflect" 10 | 11 | entity "github.com/FlowingSPDG/get5loader/backend/entity" 12 | gomock "go.uber.org/mock/gomock" 13 | ) 14 | 15 | // MockMatch is a mock of Match interface. 16 | type MockMatch struct { 17 | ctrl *gomock.Controller 18 | recorder *MockMatchMockRecorder 19 | } 20 | 21 | // MockMatchMockRecorder is the mock recorder for MockMatch. 22 | type MockMatchMockRecorder struct { 23 | mock *MockMatch 24 | } 25 | 26 | // NewMockMatch creates a new mock instance. 27 | func NewMockMatch(ctrl *gomock.Controller) *MockMatch { 28 | mock := &MockMatch{ctrl: ctrl} 29 | mock.recorder = &MockMatchMockRecorder{mock} 30 | return mock 31 | } 32 | 33 | // EXPECT returns an object that allows the caller to indicate expected use. 34 | func (m *MockMatch) EXPECT() *MockMatchMockRecorder { 35 | return m.recorder 36 | } 37 | 38 | // BatchGetMatchesByUser mocks base method. 39 | func (m *MockMatch) BatchGetMatchesByUser(ctx context.Context, userIDs []entity.UserID) (map[entity.UserID][]*entity.Match, error) { 40 | m.ctrl.T.Helper() 41 | ret := m.ctrl.Call(m, "BatchGetMatchesByUser", ctx, userIDs) 42 | ret0, _ := ret[0].(map[entity.UserID][]*entity.Match) 43 | ret1, _ := ret[1].(error) 44 | return ret0, ret1 45 | } 46 | 47 | // BatchGetMatchesByUser indicates an expected call of BatchGetMatchesByUser. 48 | func (mr *MockMatchMockRecorder) BatchGetMatchesByUser(ctx, userIDs interface{}) *gomock.Call { 49 | mr.mock.ctrl.T.Helper() 50 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BatchGetMatchesByUser", reflect.TypeOf((*MockMatch)(nil).BatchGetMatchesByUser), ctx, userIDs) 51 | } 52 | 53 | // CreateMatch mocks base method. 54 | func (m *MockMatch) CreateMatch(ctx context.Context, userID entity.UserID, serverID entity.GameServerID, team1ID, team2ID entity.TeamID, maxMaps int, title string) (*entity.Match, error) { 55 | m.ctrl.T.Helper() 56 | ret := m.ctrl.Call(m, "CreateMatch", ctx, userID, serverID, team1ID, team2ID, maxMaps, title) 57 | ret0, _ := ret[0].(*entity.Match) 58 | ret1, _ := ret[1].(error) 59 | return ret0, ret1 60 | } 61 | 62 | // CreateMatch indicates an expected call of CreateMatch. 63 | func (mr *MockMatchMockRecorder) CreateMatch(ctx, userID, serverID, team1ID, team2ID, maxMaps, title interface{}) *gomock.Call { 64 | mr.mock.ctrl.T.Helper() 65 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateMatch", reflect.TypeOf((*MockMatch)(nil).CreateMatch), ctx, userID, serverID, team1ID, team2ID, maxMaps, title) 66 | } 67 | 68 | // GetMatch mocks base method. 69 | func (m *MockMatch) GetMatch(ctx context.Context, matchID entity.MatchID) (*entity.Match, error) { 70 | m.ctrl.T.Helper() 71 | ret := m.ctrl.Call(m, "GetMatch", ctx, matchID) 72 | ret0, _ := ret[0].(*entity.Match) 73 | ret1, _ := ret[1].(error) 74 | return ret0, ret1 75 | } 76 | 77 | // GetMatch indicates an expected call of GetMatch. 78 | func (mr *MockMatchMockRecorder) GetMatch(ctx, matchID interface{}) *gomock.Call { 79 | mr.mock.ctrl.T.Helper() 80 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMatch", reflect.TypeOf((*MockMatch)(nil).GetMatch), ctx, matchID) 81 | } 82 | 83 | // GetMatchesByUser mocks base method. 84 | func (m *MockMatch) GetMatchesByUser(ctx context.Context, userID entity.UserID) ([]*entity.Match, error) { 85 | m.ctrl.T.Helper() 86 | ret := m.ctrl.Call(m, "GetMatchesByUser", ctx, userID) 87 | ret0, _ := ret[0].([]*entity.Match) 88 | ret1, _ := ret[1].(error) 89 | return ret0, ret1 90 | } 91 | 92 | // GetMatchesByUser indicates an expected call of GetMatchesByUser. 93 | func (mr *MockMatchMockRecorder) GetMatchesByUser(ctx, userID interface{}) *gomock.Call { 94 | mr.mock.ctrl.T.Helper() 95 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMatchesByUser", reflect.TypeOf((*MockMatch)(nil).GetMatchesByUser), ctx, userID) 96 | } 97 | -------------------------------------------------------------------------------- /backend/usecase/user.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | 7 | "github.com/FlowingSPDG/get5loader/backend/entity" 8 | "github.com/FlowingSPDG/get5loader/backend/gateway/database" 9 | "github.com/FlowingSPDG/get5loader/backend/service/jwt" 10 | hash "github.com/FlowingSPDG/get5loader/backend/service/password_hash" 11 | ) 12 | 13 | type User interface { 14 | GetUser(ctx context.Context, id entity.UserID) (*entity.User, error) 15 | Register(ctx context.Context, steamID entity.SteamID, name string, admin bool, password string) (jwt string, err error) 16 | IssueJWT(ctx context.Context, userID entity.UserID, password string) (jwt string, err error) 17 | IssueJWTBySteamID(ctx context.Context, steamID entity.SteamID, password string) (jwt string, err error) 18 | } 19 | 20 | type user struct { 21 | jwtService jwt.JWTService 22 | passwordHasher hash.PasswordHasher 23 | } 24 | 25 | func NewUser(jwtService jwt.JWTService, passwordHasher hash.PasswordHasher) User { 26 | return &user{ 27 | jwtService: jwtService, 28 | passwordHasher: passwordHasher, 29 | } 30 | } 31 | 32 | func (u *user) GetUser(ctx context.Context, id entity.UserID) (*entity.User, error) { 33 | repositoryConnector := database.GetConnection(ctx) 34 | 35 | userRepository := repositoryConnector.GetUserRepository() 36 | 37 | // ユーザーを取得 38 | user, err := userRepository.GetUser(ctx, id) 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | return convertUser(user), nil 44 | } 45 | 46 | // Handle implements UserRegister. 47 | func (u *user) Register(ctx context.Context, steamID entity.SteamID, name string, admin bool, password string) (string, error) { 48 | repositoryConnector := database.GetConnection(ctx) 49 | 50 | repository := repositoryConnector.GetUserRepository() 51 | _, err := repository.GetUserBySteamID(ctx, steamID) 52 | if err == nil { 53 | return "", errors.New("user already exists") 54 | } else if !database.IsNotFound(err) { 55 | return "", err 56 | } 57 | 58 | hash, err := u.passwordHasher.Hash(password) 59 | if err != nil { 60 | return "", err 61 | } 62 | 63 | if _, err := repository.CreateUser(ctx, steamID, name, admin, hash); err != nil { 64 | return "", err 65 | } 66 | 67 | user, err := repository.GetUserBySteamID(ctx, steamID) 68 | if err != nil { 69 | return "", err 70 | } 71 | 72 | signed, err := u.jwtService.IssueJWT(user.ID, user.SteamID, user.Admin) 73 | if err != nil { 74 | return "", err 75 | } 76 | 77 | return signed, nil 78 | } 79 | 80 | // HandleLoginSteamID implements UserLoginUsecase. 81 | func (u *user) IssueJWT(ctx context.Context, userID entity.UserID, password string) (string, error) { 82 | // TODO: エラーハンドリング 83 | repositoryConnector := database.GetConnection(ctx) 84 | 85 | repository := repositoryConnector.GetUserRepository() 86 | user, err := repository.GetUser(ctx, userID) 87 | if err != nil { 88 | return "", err 89 | } 90 | 91 | if err := u.passwordHasher.Compare(user.Hash, password); err != nil { 92 | return "", err 93 | } 94 | 95 | signed, err := u.jwtService.IssueJWT(user.ID, user.SteamID, user.Admin) 96 | if err != nil { 97 | return "", err 98 | } 99 | 100 | return signed, nil 101 | } 102 | 103 | // HandleLoginSteamID implements UserLoginUsecase. 104 | func (u *user) IssueJWTBySteamID(ctx context.Context, steamID entity.SteamID, password string) (string, error) { 105 | // TODO: エラーハンドリング 106 | repositoryConnector := database.GetConnection(ctx) 107 | 108 | repository := repositoryConnector.GetUserRepository() 109 | user, err := repository.GetUserBySteamID(ctx, steamID) 110 | if err != nil { 111 | return "", err 112 | } 113 | 114 | if err := u.passwordHasher.Compare(user.Hash, password); err != nil { 115 | return "", err 116 | } 117 | 118 | signed, err := u.jwtService.IssueJWT(user.ID, user.SteamID, user.Admin) 119 | if err != nil { 120 | return "", err 121 | } 122 | 123 | return signed, nil 124 | } 125 | -------------------------------------------------------------------------------- /backend/gateway/database/mysql/players/players.go: -------------------------------------------------------------------------------- 1 | package players 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "errors" 7 | "fmt" 8 | 9 | "github.com/FlowingSPDG/get5loader/backend/entity" 10 | "github.com/FlowingSPDG/get5loader/backend/gateway/database" 11 | players_gen "github.com/FlowingSPDG/get5loader/backend/gateway/database/mysql/players/generated" 12 | "github.com/FlowingSPDG/get5loader/backend/service/uuid" 13 | ) 14 | 15 | type playersRepository struct { 16 | uuidGenerator uuid.UUIDGenerator 17 | queries *players_gen.Queries 18 | } 19 | 20 | func NewPlayersRepository(uuidGenerator uuid.UUIDGenerator, db *sql.DB) database.PlayersRepository { 21 | queries := players_gen.New(db) 22 | return &playersRepository{ 23 | uuidGenerator: uuidGenerator, 24 | queries: queries, 25 | } 26 | } 27 | 28 | func NewPlayersRepositoryWithTx(uuidGenerator uuid.UUIDGenerator, db *sql.DB, tx *sql.Tx) database.PlayersRepository { 29 | queries := players_gen.New(db).WithTx(tx) 30 | return &playersRepository{ 31 | uuidGenerator: uuidGenerator, 32 | queries: queries, 33 | } 34 | } 35 | 36 | // AddPlayer implements database.PlayersRepository. 37 | func (pr *playersRepository) AddPlayer(ctx context.Context, teamID entity.TeamID, steamID entity.SteamID, name string) (entity.PlayerID, error) { 38 | id := pr.uuidGenerator.Generate() 39 | if _, err := pr.queries.AddPlayer(ctx, players_gen.AddPlayerParams{ 40 | ID: id, 41 | TeamID: string(teamID), 42 | SteamID: uint64(steamID), 43 | Name: name, 44 | }); err != nil { 45 | return "", database.NewInternalError(err) 46 | } 47 | 48 | return entity.PlayerID(id), nil 49 | } 50 | 51 | // GetPlayer implements database.PlayersRepository. 52 | func (pr *playersRepository) GetPlayer(ctx context.Context, id entity.PlayerID) (*database.Player, error) { 53 | res, err := pr.queries.GetPlayer(ctx, string(id)) 54 | if err != nil { 55 | if errors.Is(err, sql.ErrNoRows) { 56 | return nil, database.NewNotFoundError(nil) 57 | } 58 | return nil, database.NewInternalError(err) 59 | } 60 | 61 | return &database.Player{ 62 | ID: entity.PlayerID(res.ID), 63 | TeamID: entity.TeamID(res.TeamID), 64 | SteamID: entity.SteamID(res.SteamID), 65 | Name: res.Name, 66 | }, nil 67 | } 68 | 69 | // GetPlayersByTeam implements database.PlayersRepository. 70 | func (pr *playersRepository) GetPlayersByTeam(ctx context.Context, teamID entity.TeamID) ([]*database.Player, error) { 71 | fmt.Println("GetPlayersByTeam") 72 | res, err := pr.queries.GetPlayersByTeam(ctx, string(teamID)) 73 | if err != nil { 74 | if errors.Is(err, sql.ErrNoRows) { 75 | return []*database.Player{}, nil 76 | } 77 | return nil, database.NewInternalError(err) 78 | } 79 | 80 | players := make([]*database.Player, 0, len(res)) 81 | for _, p := range res { 82 | players = append(players, &database.Player{ 83 | ID: entity.PlayerID(p.ID), 84 | TeamID: entity.TeamID(p.TeamID), 85 | SteamID: entity.SteamID(p.SteamID), 86 | Name: p.Name, 87 | }) 88 | } 89 | 90 | return players, nil 91 | } 92 | 93 | // GetPlayersByTeams implements database.PlayersRepository. 94 | func (pr *playersRepository) GetPlayersByTeams(ctx context.Context, teamIDs []entity.TeamID) (map[entity.TeamID][]*database.Player, error) { 95 | ids := database.IDsToString(teamIDs) 96 | players, err := pr.queries.GetPlayersByTeams(ctx, ids) 97 | if err != nil { 98 | return nil, database.NewInternalError(err) 99 | } 100 | 101 | ret := make(map[entity.TeamID][]*database.Player, len(teamIDs)) 102 | // nilが渡されるのを防ぐため、空のスライスを生成する 103 | for _, teamID := range teamIDs { 104 | ret[teamID] = make([]*database.Player, 0) 105 | } 106 | 107 | for _, p := range players { 108 | ret[entity.TeamID(p.TeamID)] = append(ret[entity.TeamID(p.TeamID)], &database.Player{ 109 | ID: entity.PlayerID(p.ID), 110 | TeamID: entity.TeamID(p.TeamID), 111 | SteamID: entity.SteamID(p.SteamID), 112 | Name: p.Name, 113 | }) 114 | } 115 | return ret, nil 116 | } 117 | -------------------------------------------------------------------------------- /backend/graph/qls/schema.graphqls: -------------------------------------------------------------------------------- 1 | scalar DateTime 2 | scalar SteamID 3 | 4 | # User Operations 5 | type User { 6 | id: ID! 7 | steamId: SteamID! 8 | name: String! 9 | admin: Boolean! 10 | gameservers: [GameServer!]! 11 | teams: [Team!]! 12 | matches: [Match!]! 13 | } 14 | 15 | input NewUser { 16 | steamId: SteamID! 17 | name: String! 18 | password: String! 19 | admin: Boolean! 20 | } 21 | 22 | input UserLoginSteamID { 23 | steamId: SteamID! 24 | password: String! 25 | } 26 | 27 | input UserLoginID { 28 | ID: ID! 29 | password: String! 30 | } 31 | 32 | type LoginToken { 33 | token: String! 34 | } 35 | 36 | # Team 37 | type Team { 38 | id: ID! 39 | userId: ID! 40 | name: String! 41 | flag: String! 42 | tag: String! 43 | logo: String! 44 | public: Boolean! 45 | players: [Player!]! 46 | } 47 | 48 | input NewTeam { 49 | name: String! 50 | flag: String! 51 | tag: String! 52 | logo: String! 53 | public: Boolean! 54 | players: [NewPlayerForTeam!] 55 | } 56 | 57 | # Player 58 | type Player { 59 | id: ID! 60 | teamId: ID! 61 | steamId: SteamID! 62 | name: String! 63 | } 64 | 65 | input NewPlayer { 66 | steamId: SteamID! 67 | name: String! 68 | teamid: ID! 69 | } 70 | 71 | input NewPlayerForTeam { 72 | steamId: SteamID! 73 | name: String! 74 | } 75 | 76 | # GameServer operations 77 | type GameServer { 78 | id: ID! 79 | Ip: String! 80 | port: Int! 81 | name: String! 82 | public: Boolean! 83 | } 84 | 85 | input NewGameServer { 86 | Ip: String! 87 | port: Int! 88 | name: String! 89 | RconPassword: String! 90 | public: Boolean! 91 | } 92 | 93 | type Query { 94 | # User op 95 | getUser(id: ID!): User! 96 | getMe: User! 97 | 98 | # Team op 99 | getTeam(id: ID!): Team! 100 | getTeamsByUser: [Team!]! 101 | 102 | # Match op 103 | getMatch(id: ID!): Match! 104 | getMatchesByUser(id: ID!): [Match!]! 105 | getMatchesByMe: [Match!]! 106 | 107 | # gameserver op 108 | getServer(id: ID!): GameServer! 109 | getPublicServers: [GameServer!]! 110 | } 111 | 112 | type Mutation { 113 | # Team op 114 | registerTeam(input: NewTeam!): Team! 115 | 116 | # Match op 117 | createMatch(input: NewMatch!): Match! 118 | 119 | # gameserver op 120 | addServer(input: NewGameServer!): GameServer! 121 | } 122 | 123 | # Match 124 | type Match { 125 | id: ID! 126 | userId: ID! 127 | team1: Team! 128 | team1Id: ID! 129 | team2: Team! 130 | team2Id: ID! 131 | winner: ID! 132 | startedAt: DateTime 133 | endedAt: DateTime 134 | maxMaps: Int! 135 | title: String! 136 | skipVeto: Boolean! 137 | team1Score: Int! 138 | team2Score: Int! 139 | forfeit: Boolean 140 | mapStats: [MapStats!]! 141 | } 142 | 143 | input NewMatch { 144 | team1: ID! 145 | team2: ID! 146 | serverID: ID! 147 | maxMaps: Int! 148 | title: String! 149 | skipVeto: Boolean! 150 | } 151 | 152 | # MapStats 153 | type MapStats { 154 | id: ID! 155 | matchId: ID! 156 | mapNumber: Int! 157 | mapName: String! 158 | startedAt: DateTime 159 | endedAt: DateTime 160 | winner: ID 161 | team1score: Int! 162 | team2score: Int! 163 | playerstats: [PlayerStats!]! 164 | } 165 | # PlayerStats 166 | type PlayerStats { 167 | id: ID! 168 | matchId: ID! 169 | mapstatsId: ID! 170 | steamId: SteamID! 171 | name: String! 172 | 173 | kills: Int! 174 | assists: Int! 175 | deaths: Int! 176 | 177 | roundsPlayed: Int! 178 | flashBangAssists: Int! 179 | suicides: Int! 180 | headshotKills: Int! 181 | damage: Int! 182 | bombPlants: Int! 183 | bombDefuses: Int! 184 | 185 | v1: Int! 186 | v2: Int! 187 | v3: Int! 188 | v4: Int! 189 | v5: Int! 190 | 191 | k1: Int! 192 | k2: Int! 193 | k3: Int! 194 | k4: Int! 195 | k5: Int! 196 | 197 | firstDeathT: Int! 198 | firstDeathCT: Int! 199 | firstKillT: Int! 200 | firstKillCT: Int! 201 | } -------------------------------------------------------------------------------- /backend/gqlgen.yml: -------------------------------------------------------------------------------- 1 | # Where are all the schema files located? globs are supported eg src/**/*.graphqls 2 | schema: 3 | - graph/qls/schema.graphqls 4 | 5 | # Where should the generated server code go? 6 | exec: 7 | filename: graph/generated.go 8 | package: graph 9 | 10 | # Uncomment to enable federation 11 | # federation: 12 | # filename: graph/federation.go 13 | # package: graph 14 | 15 | # Where should any generated models go? 16 | model: 17 | filename: graph/model/models_gen.go 18 | package: model 19 | 20 | # Where should the resolver implementations go? 21 | resolver: 22 | layout: follow-schema 23 | dir: graph 24 | package: graph 25 | filename_template: "{name}.resolvers.go" 26 | # Optional: turn on to not generate template comments above resolvers 27 | # omit_template_comment: false 28 | 29 | # Optional: turn on use ` + "`" + `gqlgen:"fieldName"` + "`" + ` tags in your models 30 | # struct_tag: json 31 | 32 | # Optional: turn on to use []Thing instead of []*Thing 33 | # omit_slice_element_pointers: false 34 | 35 | # Optional: turn on to omit Is() methods to interface and unions 36 | # omit_interface_checks : true 37 | 38 | # Optional: turn on to skip generation of ComplexityRoot struct content and Complexity function 39 | # omit_complexity: false 40 | 41 | # Optional: turn on to not generate any file notice comments in generated files 42 | # omit_gqlgen_file_notice: false 43 | 44 | # Optional: turn on to exclude the gqlgen version in the generated file notice. No effect if `omit_gqlgen_file_notice` is true. 45 | # omit_gqlgen_version_in_file_notice: false 46 | 47 | # Optional: turn off to make struct-type struct fields not use pointers 48 | # e.g. type Thing struct { FieldA OtherThing } instead of { FieldA *OtherThing } 49 | # struct_fields_always_pointers: true 50 | 51 | # Optional: turn off to make resolvers return values instead of pointers for structs 52 | # resolvers_always_return_pointers: true 53 | 54 | # Optional: turn on to return pointers instead of values in unmarshalInput 55 | # return_pointers_in_unmarshalinput: false 56 | 57 | # Optional: wrap nullable input fields with Omittable 58 | # nullable_input_omittable: true 59 | 60 | # Optional: set to speed up generation time by not performing a final validation pass. 61 | # skip_validation: true 62 | 63 | # Optional: set to skip running `go mod tidy` when generating server code 64 | # skip_mod_tidy: true 65 | 66 | # gqlgen will search for any type names in the schema in these go packages 67 | # if they match it will use them, otherwise it will generate them. 68 | autobind: 69 | # - "github.com/FlowingSPDG/get5loader/backend/graph/model" 70 | 71 | # This section declares type mapping between the GraphQL and go type systems 72 | # 73 | # The first line in each type will be used as defaults for resolver arguments and 74 | # modelgen, the others will be allowed when binding to fields. Configure them to 75 | # your liking 76 | models: 77 | ID: 78 | model: 79 | - github.com/99designs/gqlgen/graphql.ID 80 | - github.com/99designs/gqlgen/graphql.Int 81 | - github.com/99designs/gqlgen/graphql.Int64 82 | - github.com/99designs/gqlgen/graphql.Int32 83 | Int: 84 | model: 85 | - github.com/99designs/gqlgen/graphql.Int 86 | - github.com/99designs/gqlgen/graphql.Int64 87 | - github.com/99designs/gqlgen/graphql.Int32 88 | 89 | DateTime: 90 | model: 91 | - github.com/99designs/gqlgen/graphql.Time 92 | SteamID: 93 | model: 94 | - github.com/99designs/gqlgen/graphql.Uint64 95 | User: 96 | fields: 97 | gameservers: 98 | resolver: true 99 | teams: 100 | resolver: true 101 | matches: 102 | resolver: true 103 | Team: 104 | fields: 105 | players: 106 | resolver: true 107 | Match: 108 | fields: 109 | team1: 110 | resolver: true 111 | team2: 112 | resolver: true 113 | mapStats: 114 | resolver: true 115 | MapStats: 116 | fields: 117 | playerstats: 118 | resolver: true -------------------------------------------------------------------------------- /backend/gateway/database/mysql/mapstats/generated/mapstats_query.sql.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.20.0 4 | // source: mapstats_query.sql 5 | 6 | package mapstats_gen 7 | 8 | import ( 9 | "context" 10 | "strings" 11 | ) 12 | 13 | const getMapStat = `-- name: GetMapStat :one 14 | SELECT id, match_id, map_number, map_name, start_time, end_time, winner, team1_score, team2_score, forfeit FROM map_stats 15 | WHERE id = ? LIMIT 1 16 | ` 17 | 18 | func (q *Queries) GetMapStat(ctx context.Context, id string) (MapStat, error) { 19 | row := q.db.QueryRowContext(ctx, getMapStat, id) 20 | var i MapStat 21 | err := row.Scan( 22 | &i.ID, 23 | &i.MatchID, 24 | &i.MapNumber, 25 | &i.MapName, 26 | &i.StartTime, 27 | &i.EndTime, 28 | &i.Winner, 29 | &i.Team1Score, 30 | &i.Team2Score, 31 | &i.Forfeit, 32 | ) 33 | return i, err 34 | } 35 | 36 | const getMapStatsByMatch = `-- name: GetMapStatsByMatch :many 37 | SELECT id, match_id, map_number, map_name, start_time, end_time, winner, team1_score, team2_score, forfeit FROM map_stats 38 | WHERE match_id = ? 39 | ` 40 | 41 | func (q *Queries) GetMapStatsByMatch(ctx context.Context, matchID string) ([]MapStat, error) { 42 | rows, err := q.db.QueryContext(ctx, getMapStatsByMatch, matchID) 43 | if err != nil { 44 | return nil, err 45 | } 46 | defer rows.Close() 47 | var items []MapStat 48 | for rows.Next() { 49 | var i MapStat 50 | if err := rows.Scan( 51 | &i.ID, 52 | &i.MatchID, 53 | &i.MapNumber, 54 | &i.MapName, 55 | &i.StartTime, 56 | &i.EndTime, 57 | &i.Winner, 58 | &i.Team1Score, 59 | &i.Team2Score, 60 | &i.Forfeit, 61 | ); err != nil { 62 | return nil, err 63 | } 64 | items = append(items, i) 65 | } 66 | if err := rows.Close(); err != nil { 67 | return nil, err 68 | } 69 | if err := rows.Err(); err != nil { 70 | return nil, err 71 | } 72 | return items, nil 73 | } 74 | 75 | const getMapStatsByMatchAndMap = `-- name: GetMapStatsByMatchAndMap :one 76 | SELECT id, match_id, map_number, map_name, start_time, end_time, winner, team1_score, team2_score, forfeit FROM map_stats 77 | WHERE match_id = ? AND map_number = ? LIMIT 1 78 | ` 79 | 80 | type GetMapStatsByMatchAndMapParams struct { 81 | MatchID string 82 | MapNumber uint32 83 | } 84 | 85 | func (q *Queries) GetMapStatsByMatchAndMap(ctx context.Context, arg GetMapStatsByMatchAndMapParams) (MapStat, error) { 86 | row := q.db.QueryRowContext(ctx, getMapStatsByMatchAndMap, arg.MatchID, arg.MapNumber) 87 | var i MapStat 88 | err := row.Scan( 89 | &i.ID, 90 | &i.MatchID, 91 | &i.MapNumber, 92 | &i.MapName, 93 | &i.StartTime, 94 | &i.EndTime, 95 | &i.Winner, 96 | &i.Team1Score, 97 | &i.Team2Score, 98 | &i.Forfeit, 99 | ) 100 | return i, err 101 | } 102 | 103 | const getMapStatsByMatches = `-- name: GetMapStatsByMatches :many 104 | SELECT id, match_id, map_number, map_name, start_time, end_time, winner, team1_score, team2_score, forfeit FROM map_stats 105 | WHERE match_id IN (/*SLICE:match_ids*/?) 106 | ` 107 | 108 | func (q *Queries) GetMapStatsByMatches(ctx context.Context, matchIds []string) ([]MapStat, error) { 109 | query := getMapStatsByMatches 110 | var queryParams []interface{} 111 | if len(matchIds) > 0 { 112 | for _, v := range matchIds { 113 | queryParams = append(queryParams, v) 114 | } 115 | query = strings.Replace(query, "/*SLICE:match_ids*/?", strings.Repeat(",?", len(matchIds))[1:], 1) 116 | } else { 117 | query = strings.Replace(query, "/*SLICE:match_ids*/?", "NULL", 1) 118 | } 119 | rows, err := q.db.QueryContext(ctx, query, queryParams...) 120 | if err != nil { 121 | return nil, err 122 | } 123 | defer rows.Close() 124 | var items []MapStat 125 | for rows.Next() { 126 | var i MapStat 127 | if err := rows.Scan( 128 | &i.ID, 129 | &i.MatchID, 130 | &i.MapNumber, 131 | &i.MapName, 132 | &i.StartTime, 133 | &i.EndTime, 134 | &i.Winner, 135 | &i.Team1Score, 136 | &i.Team2Score, 137 | &i.Forfeit, 138 | ); err != nil { 139 | return nil, err 140 | } 141 | items = append(items, i) 142 | } 143 | if err := rows.Close(); err != nil { 144 | return nil, err 145 | } 146 | if err := rows.Err(); err != nil { 147 | return nil, err 148 | } 149 | return items, nil 150 | } 151 | -------------------------------------------------------------------------------- /backend/gateway/database/mysql/connector/repository_tx.go: -------------------------------------------------------------------------------- 1 | package mysqlconnector 2 | 3 | import ( 4 | "github.com/FlowingSPDG/get5loader/backend/gateway/database" 5 | "github.com/FlowingSPDG/get5loader/backend/gateway/database/mysql/gameservers" 6 | "github.com/FlowingSPDG/get5loader/backend/gateway/database/mysql/mapstats" 7 | "github.com/FlowingSPDG/get5loader/backend/gateway/database/mysql/matches" 8 | "github.com/FlowingSPDG/get5loader/backend/gateway/database/mysql/players" 9 | "github.com/FlowingSPDG/get5loader/backend/gateway/database/mysql/playerstats" 10 | "github.com/FlowingSPDG/get5loader/backend/gateway/database/mysql/teams" 11 | "github.com/FlowingSPDG/get5loader/backend/gateway/database/mysql/users" 12 | "github.com/FlowingSPDG/get5loader/backend/service/uuid" 13 | ) 14 | 15 | type mysqlRepositoryConnectorWithTx struct { 16 | uuidGenerator uuid.UUIDGenerator 17 | connector database.DBConnectorWithTx 18 | } 19 | 20 | func NewMySQLRepositoryConnectorWithTx(uuidGenerator uuid.UUIDGenerator, connector database.DBConnectorWithTx) database.RepositoryConnectorWithTx { 21 | return &mysqlRepositoryConnectorWithTx{ 22 | uuidGenerator: uuidGenerator, 23 | connector: connector, 24 | } 25 | } 26 | 27 | func (mrctx *mysqlRepositoryConnectorWithTx) Open() error { 28 | if err := mrctx.connector.Open(); err != nil { 29 | return err 30 | } 31 | 32 | if err := mrctx.connector.BeginTx(); err != nil { 33 | return err 34 | } 35 | 36 | return nil 37 | } 38 | 39 | // Close implements database.RepositoryConnector. 40 | func (mrctx *mysqlRepositoryConnectorWithTx) Close() error { 41 | return mrctx.connector.Close() 42 | } 43 | 44 | // Commit implements database.RepositoryConnectorWithTx. 45 | func (mrctx *mysqlRepositoryConnectorWithTx) Commit() error { 46 | tx := mrctx.connector.GetTx() 47 | return tx.Commit() 48 | } 49 | 50 | // Rollback implements database.RepositoryConnectorWithTx. 51 | func (mrctx *mysqlRepositoryConnectorWithTx) Rollback() error { 52 | tx := mrctx.connector.GetTx() 53 | return tx.Rollback() 54 | } 55 | 56 | // OpenGameServersRepository implements database.RepositoryConnector. 57 | func (mrctx *mysqlRepositoryConnectorWithTx) GetGameServersRepository() database.GameServersRepository { 58 | conn := mrctx.connector.GetConnection() 59 | tx := mrctx.connector.GetTx() 60 | return gameservers.NewGameServerRepositoryWithTx(mrctx.uuidGenerator, conn, tx) 61 | } 62 | 63 | // OpenMapStatRepository implements database.RepositoryConnector. 64 | func (mrctx *mysqlRepositoryConnectorWithTx) GetMapStatRepository() database.MapStatRepository { 65 | conn := mrctx.connector.GetConnection() 66 | tx := mrctx.connector.GetTx() 67 | return mapstats.NewMapStatRepositoryWithTx(mrctx.uuidGenerator, conn, tx) 68 | } 69 | 70 | // OpenMatchesRepository implements database.RepositoryConnector. 71 | func (mrctx *mysqlRepositoryConnectorWithTx) GetMatchesRepository() database.MatchesRepository { 72 | conn := mrctx.connector.GetConnection() 73 | tx := mrctx.connector.GetTx() 74 | return matches.NewMatchRepositoryWithTx(mrctx.uuidGenerator, conn, tx) 75 | } 76 | 77 | // OpenPlayerStatRepository implements database.RepositoryConnector. 78 | func (mrctx *mysqlRepositoryConnectorWithTx) GetPlayerStatRepository() database.PlayerStatRepository { 79 | conn := mrctx.connector.GetConnection() 80 | tx := mrctx.connector.GetTx() 81 | return playerstats.NewPlayerStatRepositoryWithTx(mrctx.uuidGenerator, conn, tx) 82 | } 83 | 84 | // OpenPlayersRepository implements database.RepositoryConnector. 85 | func (mrctx *mysqlRepositoryConnectorWithTx) GetPlayersRepository() database.PlayersRepository { 86 | conn := mrctx.connector.GetConnection() 87 | tx := mrctx.connector.GetTx() 88 | return players.NewPlayersRepositoryWithTx(mrctx.uuidGenerator, conn, tx) 89 | } 90 | 91 | // OpenTeamsRepository implements database.RepositoryConnector. 92 | func (mrctx *mysqlRepositoryConnectorWithTx) GetTeamsRepository() database.TeamsRepository { 93 | conn := mrctx.connector.GetConnection() 94 | tx := mrctx.connector.GetTx() 95 | return teams.NewTeamsRepositoryWithTx(mrctx.uuidGenerator, conn, tx) 96 | } 97 | 98 | // OpenUserRepository implements database.RepositoryConnector. 99 | func (mrctx *mysqlRepositoryConnectorWithTx) GetUserRepository() database.UsersRepositry { 100 | conn := mrctx.connector.GetConnection() 101 | tx := mrctx.connector.GetTx() 102 | return users.NewUsersRepositryWithTx(mrctx.uuidGenerator, conn, tx) 103 | } 104 | -------------------------------------------------------------------------------- /backend/usecase/mock/gameserver.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: gameserver.go 3 | 4 | // Package mock_usecase is a generated GoMock package. 5 | package mock_usecase 6 | 7 | import ( 8 | context "context" 9 | reflect "reflect" 10 | 11 | entity "github.com/FlowingSPDG/get5loader/backend/entity" 12 | gomock "go.uber.org/mock/gomock" 13 | ) 14 | 15 | // MockGameServer is a mock of GameServer interface. 16 | type MockGameServer struct { 17 | ctrl *gomock.Controller 18 | recorder *MockGameServerMockRecorder 19 | } 20 | 21 | // MockGameServerMockRecorder is the mock recorder for MockGameServer. 22 | type MockGameServerMockRecorder struct { 23 | mock *MockGameServer 24 | } 25 | 26 | // NewMockGameServer creates a new mock instance. 27 | func NewMockGameServer(ctrl *gomock.Controller) *MockGameServer { 28 | mock := &MockGameServer{ctrl: ctrl} 29 | mock.recorder = &MockGameServerMockRecorder{mock} 30 | return mock 31 | } 32 | 33 | // EXPECT returns an object that allows the caller to indicate expected use. 34 | func (m *MockGameServer) EXPECT() *MockGameServerMockRecorder { 35 | return m.recorder 36 | } 37 | 38 | // AddGameServer mocks base method. 39 | func (m *MockGameServer) AddGameServer(ctx context.Context, userID entity.UserID, ip string, port uint32, rconPassword, name string, isPublic bool) (*entity.GameServer, error) { 40 | m.ctrl.T.Helper() 41 | ret := m.ctrl.Call(m, "AddGameServer", ctx, userID, ip, port, rconPassword, name, isPublic) 42 | ret0, _ := ret[0].(*entity.GameServer) 43 | ret1, _ := ret[1].(error) 44 | return ret0, ret1 45 | } 46 | 47 | // AddGameServer indicates an expected call of AddGameServer. 48 | func (mr *MockGameServerMockRecorder) AddGameServer(ctx, userID, ip, port, rconPassword, name, isPublic interface{}) *gomock.Call { 49 | mr.mock.ctrl.T.Helper() 50 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddGameServer", reflect.TypeOf((*MockGameServer)(nil).AddGameServer), ctx, userID, ip, port, rconPassword, name, isPublic) 51 | } 52 | 53 | // BatchGetGameServersByUser mocks base method. 54 | func (m *MockGameServer) BatchGetGameServersByUser(ctx context.Context, userIDs []entity.UserID) (map[entity.UserID][]*entity.GameServer, error) { 55 | m.ctrl.T.Helper() 56 | ret := m.ctrl.Call(m, "BatchGetGameServersByUser", ctx, userIDs) 57 | ret0, _ := ret[0].(map[entity.UserID][]*entity.GameServer) 58 | ret1, _ := ret[1].(error) 59 | return ret0, ret1 60 | } 61 | 62 | // BatchGetGameServersByUser indicates an expected call of BatchGetGameServersByUser. 63 | func (mr *MockGameServerMockRecorder) BatchGetGameServersByUser(ctx, userIDs interface{}) *gomock.Call { 64 | mr.mock.ctrl.T.Helper() 65 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BatchGetGameServersByUser", reflect.TypeOf((*MockGameServer)(nil).BatchGetGameServersByUser), ctx, userIDs) 66 | } 67 | 68 | // DeleteGameServer mocks base method. 69 | func (m *MockGameServer) DeleteGameServer(ctx context.Context, id entity.GameServerID) error { 70 | m.ctrl.T.Helper() 71 | ret := m.ctrl.Call(m, "DeleteGameServer", ctx, id) 72 | ret0, _ := ret[0].(error) 73 | return ret0 74 | } 75 | 76 | // DeleteGameServer indicates an expected call of DeleteGameServer. 77 | func (mr *MockGameServerMockRecorder) DeleteGameServer(ctx, id interface{}) *gomock.Call { 78 | mr.mock.ctrl.T.Helper() 79 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteGameServer", reflect.TypeOf((*MockGameServer)(nil).DeleteGameServer), ctx, id) 80 | } 81 | 82 | // GetGameServer mocks base method. 83 | func (m *MockGameServer) GetGameServer(ctx context.Context, id entity.GameServerID) (*entity.GameServer, error) { 84 | m.ctrl.T.Helper() 85 | ret := m.ctrl.Call(m, "GetGameServer", ctx, id) 86 | ret0, _ := ret[0].(*entity.GameServer) 87 | ret1, _ := ret[1].(error) 88 | return ret0, ret1 89 | } 90 | 91 | // GetGameServer indicates an expected call of GetGameServer. 92 | func (mr *MockGameServerMockRecorder) GetGameServer(ctx, id interface{}) *gomock.Call { 93 | mr.mock.ctrl.T.Helper() 94 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGameServer", reflect.TypeOf((*MockGameServer)(nil).GetGameServer), ctx, id) 95 | } 96 | 97 | // GetPublicServers mocks base method. 98 | func (m *MockGameServer) GetPublicServers(ctx context.Context) ([]*entity.GameServer, error) { 99 | m.ctrl.T.Helper() 100 | ret := m.ctrl.Call(m, "GetPublicServers", ctx) 101 | ret0, _ := ret[0].([]*entity.GameServer) 102 | ret1, _ := ret[1].(error) 103 | return ret0, ret1 104 | } 105 | 106 | // GetPublicServers indicates an expected call of GetPublicServers. 107 | func (mr *MockGameServerMockRecorder) GetPublicServers(ctx interface{}) *gomock.Call { 108 | mr.mock.ctrl.T.Helper() 109 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPublicServers", reflect.TypeOf((*MockGameServer)(nil).GetPublicServers), ctx) 110 | } 111 | -------------------------------------------------------------------------------- /backend/usecase/team.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/FlowingSPDG/get5loader/backend/entity" 7 | "github.com/FlowingSPDG/get5loader/backend/gateway/database" 8 | ) 9 | 10 | type InputPlayers struct { 11 | SteamID entity.SteamID 12 | Name string 13 | } 14 | 15 | type RegisterTeamInput struct { 16 | UserID entity.UserID 17 | Name string 18 | Flag string 19 | Tag string 20 | Logo string 21 | PublicTeam bool 22 | Players []InputPlayers 23 | } 24 | 25 | type Team interface { 26 | RegisterTeam(ctx context.Context, input RegisterTeamInput) (*entity.Team, error) 27 | GetTeam(ctx context.Context, id entity.TeamID) (*entity.Team, error) 28 | GetTeamsByMatch(ctx context.Context, matchID entity.MatchID) (*entity.Team, *entity.Team, error) 29 | GetTeamsByUser(ctx context.Context, userID entity.UserID) ([]*entity.Team, error) 30 | // BATCH 31 | BatchGetTeamsByUsers(ctx context.Context, matchIDs []entity.UserID) (map[entity.UserID][]*entity.Team, error) 32 | BatchGetTeams(ctx context.Context, teamIDs []entity.TeamID) ([]*entity.Team, error) 33 | } 34 | 35 | type team struct { 36 | } 37 | 38 | func NewTeam() Team { 39 | return &team{} 40 | } 41 | 42 | func (t *team) RegisterTeam(ctx context.Context, input RegisterTeamInput) (*entity.Team, error) { 43 | repositoryConnector := database.GetConnection(ctx) 44 | 45 | teamRepository := repositoryConnector.GetTeamsRepository() 46 | playerRepository := repositoryConnector.GetPlayersRepository() 47 | 48 | teamID, err := teamRepository.AddTeam(ctx, input.UserID, input.Name, input.Tag, input.Flag, input.Logo, input.PublicTeam) 49 | if err != nil { 50 | return nil, err 51 | } 52 | // TODO: Batch addする 53 | for _, player := range input.Players { 54 | playerRepository.AddPlayer(ctx, teamID, player.SteamID, player.Name) 55 | } 56 | 57 | team, err := teamRepository.GetTeam(ctx, teamID) 58 | if err != nil { 59 | return nil, err 60 | } 61 | return convertTeam(team), nil 62 | } 63 | 64 | // GetTeam implements Team. 65 | func (t *team) GetTeam(ctx context.Context, id entity.TeamID) (*entity.Team, error) { 66 | repositoryConnector := database.GetConnection(ctx) 67 | 68 | teamsRepository := repositoryConnector.GetTeamsRepository() 69 | 70 | team, err := teamsRepository.GetTeam(ctx, id) 71 | if err != nil { 72 | return nil, err 73 | } 74 | 75 | return convertTeam(team), nil 76 | } 77 | 78 | // GetTeamsByMatch implements Team. 79 | func (t *team) GetTeamsByMatch(ctx context.Context, matchID entity.MatchID) (*entity.Team, *entity.Team, error) { 80 | repositoryConnector := database.GetConnection(ctx) 81 | 82 | matchRepository := repositoryConnector.GetMatchesRepository() 83 | teamRepository := repositoryConnector.GetTeamsRepository() 84 | 85 | match, err := matchRepository.GetMatch(ctx, matchID) 86 | if err != nil { 87 | return nil, nil, err 88 | } 89 | team1, err := teamRepository.GetTeam(ctx, match.Team1ID) 90 | if err != nil { 91 | return nil, nil, err 92 | } 93 | team2, err := teamRepository.GetTeam(ctx, match.Team2ID) 94 | if err != nil { 95 | return nil, nil, err 96 | } 97 | 98 | return convertTeam(team1), convertTeam(team2), nil 99 | } 100 | 101 | // GetTeamsByUser implements Team. 102 | func (t *team) GetTeamsByUser(ctx context.Context, userID entity.UserID) ([]*entity.Team, error) { 103 | repositoryConnector := database.GetConnection(ctx) 104 | 105 | teamRepository := repositoryConnector.GetTeamsRepository() 106 | 107 | teams, err := teamRepository.GetTeamsByUser(ctx, userID) 108 | if err != nil { 109 | return nil, err 110 | } 111 | 112 | ret := make([]*entity.Team, 0, len(teams)) 113 | for _, team := range teams { 114 | ret = append(ret, convertTeam(team)) 115 | } 116 | return ret, nil 117 | } 118 | 119 | // BatchGetTeamsByMatch implements Team. 120 | func (t *team) BatchGetTeamsByUsers(ctx context.Context, matchIDs []entity.UserID) (map[entity.UserID][]*entity.Team, error) { 121 | repositoryConnector := database.GetConnection(ctx) 122 | 123 | teamRepository := repositoryConnector.GetTeamsRepository() 124 | 125 | teams, err := teamRepository.GetTeamsByUsers(ctx, matchIDs) 126 | if err != nil { 127 | return nil, err 128 | } 129 | 130 | ret := make(map[entity.UserID][]*entity.Team, len(matchIDs)) 131 | for userID, teams := range teams { 132 | ret[userID] = convertTeams(teams) 133 | } 134 | return ret, nil 135 | } 136 | 137 | // BatchGetTeams implements Team. 138 | func (t *team) BatchGetTeams(ctx context.Context, teamIDs []entity.TeamID) ([]*entity.Team, error) { 139 | repositoryConnector := database.GetConnection(ctx) 140 | 141 | teamRepository := repositoryConnector.GetTeamsRepository() 142 | 143 | teams, err := teamRepository.GetTeams(ctx, teamIDs) 144 | if err != nil { 145 | return nil, err 146 | } 147 | 148 | return convertTeams(teams), nil 149 | } 150 | -------------------------------------------------------------------------------- /backend/gateway/database/mysql/mapstats/mapstats.go: -------------------------------------------------------------------------------- 1 | package mapstats 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "errors" 7 | 8 | "github.com/FlowingSPDG/get5loader/backend/entity" 9 | "github.com/FlowingSPDG/get5loader/backend/gateway/database" 10 | mapstats_gen "github.com/FlowingSPDG/get5loader/backend/gateway/database/mysql/mapstats/generated" 11 | "github.com/FlowingSPDG/get5loader/backend/service/uuid" 12 | ) 13 | 14 | type MapStatRepository struct { 15 | uuidGenerator uuid.UUIDGenerator 16 | queries *mapstats_gen.Queries 17 | } 18 | 19 | func NewMapStatRepository(uuidGenerator uuid.UUIDGenerator, db *sql.DB) database.MapStatRepository { 20 | queries := mapstats_gen.New(db) 21 | return &MapStatRepository{ 22 | uuidGenerator: uuidGenerator, 23 | queries: queries, 24 | } 25 | } 26 | 27 | func NewMapStatRepositoryWithTx(uuidGenerator uuid.UUIDGenerator, db *sql.DB, tx *sql.Tx) database.MapStatRepository { 28 | queries := mapstats_gen.New(db).WithTx(tx) 29 | return &MapStatRepository{ 30 | uuidGenerator: uuidGenerator, 31 | queries: queries, 32 | } 33 | } 34 | 35 | // GetMapStats implements database.MapStatRepository. 36 | func (msr *MapStatRepository) GetMapStat(ctx context.Context, id entity.MapStatsID) (*database.MapStat, error) { 37 | res, err := msr.queries.GetMapStat(ctx, string(id)) 38 | if err != nil { 39 | if errors.Is(err, sql.ErrNoRows) { 40 | return nil, database.NewNotFoundError(nil) 41 | } 42 | return nil, database.NewInternalError(err) 43 | } 44 | 45 | winner := entity.TeamID(res.Winner.String) 46 | 47 | return &database.MapStat{ 48 | ID: entity.MapStatsID(res.ID), 49 | MatchID: entity.MatchID(res.MatchID), 50 | MapNumber: res.MapNumber, 51 | MapName: res.MapName, 52 | StartTime: &res.StartTime.Time, 53 | EndTime: &res.EndTime.Time, 54 | Winner: &winner, 55 | Team1Score: res.Team1Score, 56 | Team2Score: res.Team2Score, 57 | }, nil 58 | } 59 | 60 | // GetMapStatsByMatch implements database.MapStatRepository. 61 | func (msr *MapStatRepository) GetMapStatsByMatch(ctx context.Context, matchID entity.MatchID) ([]*database.MapStat, error) { 62 | res, err := msr.queries.GetMapStatsByMatch(ctx, string(matchID)) 63 | if err != nil { 64 | if errors.Is(err, sql.ErrNoRows) { 65 | return []*database.MapStat{}, nil 66 | } 67 | return nil, database.NewInternalError(err) 68 | } 69 | 70 | mapStats := make([]*database.MapStat, 0, len(res)) 71 | for _, m := range res { 72 | winner := entity.TeamID(m.Winner.String) 73 | mapStats = append(mapStats, &database.MapStat{ 74 | ID: entity.MapStatsID(m.ID), 75 | MatchID: entity.MatchID(m.MatchID), 76 | MapNumber: m.MapNumber, 77 | MapName: m.MapName, 78 | StartTime: &m.StartTime.Time, 79 | EndTime: &m.EndTime.Time, 80 | Winner: &winner, 81 | Team1Score: m.Team1Score, 82 | Team2Score: m.Team2Score, 83 | }) 84 | } 85 | 86 | return mapStats, nil 87 | } 88 | 89 | // GetMapStatsByMatches implements database.MapStatRepository. 90 | func (msr *MapStatRepository) GetMapStatsByMatches(ctx context.Context, matchIDs []entity.MatchID) (map[entity.MatchID][]*database.MapStat, error) { 91 | ids := database.IDsToString(matchIDs) 92 | mapstats, err := msr.queries.GetMapStatsByMatches(ctx, ids) 93 | if err != nil { 94 | return nil, database.NewInternalError(err) 95 | } 96 | 97 | ret := make(map[entity.MatchID][]*database.MapStat, len(matchIDs)) 98 | // nilが渡されるのを防ぐため、空のスライスを生成する 99 | for _, matchID := range matchIDs { 100 | ret[matchID] = make([]*database.MapStat, 0) 101 | } 102 | 103 | for _, m := range mapstats { 104 | winner := entity.TeamID(m.Winner.String) 105 | ret[entity.MatchID(m.MatchID)] = append(ret[entity.MatchID(m.MatchID)], &database.MapStat{ 106 | ID: entity.MapStatsID(m.ID), 107 | MatchID: entity.MatchID(m.MatchID), 108 | MapNumber: m.MapNumber, 109 | MapName: m.MapName, 110 | StartTime: &m.StartTime.Time, 111 | EndTime: &m.EndTime.Time, 112 | Winner: &winner, 113 | Team1Score: m.Team1Score, 114 | Team2Score: m.Team2Score, 115 | }) 116 | } 117 | return ret, nil 118 | } 119 | 120 | // GetMapStatsByMatchAndMap implements database.MapStatRepository. 121 | func (msr *MapStatRepository) GetMapStatsByMatchAndMap(ctx context.Context, matchID entity.MatchID, mapNumber uint32) (*database.MapStat, error) { 122 | res, err := msr.queries.GetMapStatsByMatchAndMap(ctx, mapstats_gen.GetMapStatsByMatchAndMapParams{ 123 | MatchID: string(matchID), 124 | MapNumber: mapNumber, 125 | }) 126 | if err != nil { 127 | if errors.Is(err, sql.ErrNoRows) { 128 | return nil, database.NewNotFoundError(nil) 129 | } 130 | return nil, database.NewInternalError(err) 131 | } 132 | 133 | winner := entity.TeamID(res.Winner.String) 134 | 135 | return &database.MapStat{ 136 | ID: entity.MapStatsID(res.ID), 137 | MatchID: entity.MatchID(res.MatchID), 138 | MapNumber: res.MapNumber, 139 | MapName: res.MapName, 140 | StartTime: &res.StartTime.Time, 141 | EndTime: &res.EndTime.Time, 142 | Winner: &winner, 143 | Team1Score: res.Team1Score, 144 | Team2Score: res.Team2Score, 145 | }, nil 146 | } 147 | -------------------------------------------------------------------------------- /backend/usecase/mock/team.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: team.go 3 | 4 | // Package mock_usecase is a generated GoMock package. 5 | package mock_usecase 6 | 7 | import ( 8 | context "context" 9 | reflect "reflect" 10 | 11 | entity "github.com/FlowingSPDG/get5loader/backend/entity" 12 | usecase "github.com/FlowingSPDG/get5loader/backend/usecase" 13 | gomock "go.uber.org/mock/gomock" 14 | ) 15 | 16 | // MockTeam is a mock of Team interface. 17 | type MockTeam struct { 18 | ctrl *gomock.Controller 19 | recorder *MockTeamMockRecorder 20 | } 21 | 22 | // MockTeamMockRecorder is the mock recorder for MockTeam. 23 | type MockTeamMockRecorder struct { 24 | mock *MockTeam 25 | } 26 | 27 | // NewMockTeam creates a new mock instance. 28 | func NewMockTeam(ctrl *gomock.Controller) *MockTeam { 29 | mock := &MockTeam{ctrl: ctrl} 30 | mock.recorder = &MockTeamMockRecorder{mock} 31 | return mock 32 | } 33 | 34 | // EXPECT returns an object that allows the caller to indicate expected use. 35 | func (m *MockTeam) EXPECT() *MockTeamMockRecorder { 36 | return m.recorder 37 | } 38 | 39 | // BatchGetTeams mocks base method. 40 | func (m *MockTeam) BatchGetTeams(ctx context.Context, teamIDs []entity.TeamID) (map[entity.TeamID]*entity.Team, error) { 41 | m.ctrl.T.Helper() 42 | ret := m.ctrl.Call(m, "BatchGetTeams", ctx, teamIDs) 43 | ret0, _ := ret[0].(map[entity.TeamID]*entity.Team) 44 | ret1, _ := ret[1].(error) 45 | return ret0, ret1 46 | } 47 | 48 | // BatchGetTeams indicates an expected call of BatchGetTeams. 49 | func (mr *MockTeamMockRecorder) BatchGetTeams(ctx, teamIDs interface{}) *gomock.Call { 50 | mr.mock.ctrl.T.Helper() 51 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BatchGetTeams", reflect.TypeOf((*MockTeam)(nil).BatchGetTeams), ctx, teamIDs) 52 | } 53 | 54 | // BatchGetTeamsByUsers mocks base method. 55 | func (m *MockTeam) BatchGetTeamsByUsers(ctx context.Context, matchIDs []entity.UserID) (map[entity.UserID][]*entity.Team, error) { 56 | m.ctrl.T.Helper() 57 | ret := m.ctrl.Call(m, "BatchGetTeamsByUsers", ctx, matchIDs) 58 | ret0, _ := ret[0].(map[entity.UserID][]*entity.Team) 59 | ret1, _ := ret[1].(error) 60 | return ret0, ret1 61 | } 62 | 63 | // BatchGetTeamsByUsers indicates an expected call of BatchGetTeamsByUsers. 64 | func (mr *MockTeamMockRecorder) BatchGetTeamsByUsers(ctx, matchIDs interface{}) *gomock.Call { 65 | mr.mock.ctrl.T.Helper() 66 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BatchGetTeamsByUsers", reflect.TypeOf((*MockTeam)(nil).BatchGetTeamsByUsers), ctx, matchIDs) 67 | } 68 | 69 | // GetTeam mocks base method. 70 | func (m *MockTeam) GetTeam(ctx context.Context, id entity.TeamID) (*entity.Team, error) { 71 | m.ctrl.T.Helper() 72 | ret := m.ctrl.Call(m, "GetTeam", ctx, id) 73 | ret0, _ := ret[0].(*entity.Team) 74 | ret1, _ := ret[1].(error) 75 | return ret0, ret1 76 | } 77 | 78 | // GetTeam indicates an expected call of GetTeam. 79 | func (mr *MockTeamMockRecorder) GetTeam(ctx, id interface{}) *gomock.Call { 80 | mr.mock.ctrl.T.Helper() 81 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTeam", reflect.TypeOf((*MockTeam)(nil).GetTeam), ctx, id) 82 | } 83 | 84 | // GetTeamsByMatch mocks base method. 85 | func (m *MockTeam) GetTeamsByMatch(ctx context.Context, matchID entity.MatchID) (*entity.Team, *entity.Team, error) { 86 | m.ctrl.T.Helper() 87 | ret := m.ctrl.Call(m, "GetTeamsByMatch", ctx, matchID) 88 | ret0, _ := ret[0].(*entity.Team) 89 | ret1, _ := ret[1].(*entity.Team) 90 | ret2, _ := ret[2].(error) 91 | return ret0, ret1, ret2 92 | } 93 | 94 | // GetTeamsByMatch indicates an expected call of GetTeamsByMatch. 95 | func (mr *MockTeamMockRecorder) GetTeamsByMatch(ctx, matchID interface{}) *gomock.Call { 96 | mr.mock.ctrl.T.Helper() 97 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTeamsByMatch", reflect.TypeOf((*MockTeam)(nil).GetTeamsByMatch), ctx, matchID) 98 | } 99 | 100 | // GetTeamsByUser mocks base method. 101 | func (m *MockTeam) GetTeamsByUser(ctx context.Context, userID entity.UserID) ([]*entity.Team, error) { 102 | m.ctrl.T.Helper() 103 | ret := m.ctrl.Call(m, "GetTeamsByUser", ctx, userID) 104 | ret0, _ := ret[0].([]*entity.Team) 105 | ret1, _ := ret[1].(error) 106 | return ret0, ret1 107 | } 108 | 109 | // GetTeamsByUser indicates an expected call of GetTeamsByUser. 110 | func (mr *MockTeamMockRecorder) GetTeamsByUser(ctx, userID interface{}) *gomock.Call { 111 | mr.mock.ctrl.T.Helper() 112 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTeamsByUser", reflect.TypeOf((*MockTeam)(nil).GetTeamsByUser), ctx, userID) 113 | } 114 | 115 | // RegisterTeam mocks base method. 116 | func (m *MockTeam) RegisterTeam(ctx context.Context, input usecase.RegisterTeamInput) (*entity.Team, error) { 117 | m.ctrl.T.Helper() 118 | ret := m.ctrl.Call(m, "RegisterTeam", ctx, input) 119 | ret0, _ := ret[0].(*entity.Team) 120 | ret1, _ := ret[1].(error) 121 | return ret0, ret1 122 | } 123 | 124 | // RegisterTeam indicates an expected call of RegisterTeam. 125 | func (mr *MockTeamMockRecorder) RegisterTeam(ctx, input interface{}) *gomock.Call { 126 | mr.mock.ctrl.T.Helper() 127 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterTeam", reflect.TypeOf((*MockTeam)(nil).RegisterTeam), ctx, input) 128 | } 129 | -------------------------------------------------------------------------------- /backend/gateway/database/mysql/gameservers/generated/gameserver_query.sql.go: -------------------------------------------------------------------------------- 1 | // Code generated by sqlc. DO NOT EDIT. 2 | // versions: 3 | // sqlc v1.20.0 4 | // source: gameserver_query.sql 5 | 6 | package gameservers_gen 7 | 8 | import ( 9 | "context" 10 | "database/sql" 11 | "strings" 12 | ) 13 | 14 | const addGameServer = `-- name: AddGameServer :execresult 15 | INSERT INTO game_servers ( 16 | id, user_id, ip, port, rcon_password, display_name, is_public 17 | ) VALUES ( 18 | ?, ?, ?, ?, ?, ?, ? 19 | ) 20 | ` 21 | 22 | type AddGameServerParams struct { 23 | ID string 24 | UserID string 25 | Ip string 26 | Port uint32 27 | RconPassword string 28 | DisplayName string 29 | IsPublic bool 30 | } 31 | 32 | func (q *Queries) AddGameServer(ctx context.Context, arg AddGameServerParams) (sql.Result, error) { 33 | return q.db.ExecContext(ctx, addGameServer, 34 | arg.ID, 35 | arg.UserID, 36 | arg.Ip, 37 | arg.Port, 38 | arg.RconPassword, 39 | arg.DisplayName, 40 | arg.IsPublic, 41 | ) 42 | } 43 | 44 | const deleteGameServer = `-- name: DeleteGameServer :execresult 45 | DELETE FROM game_servers 46 | WHERE id = ? 47 | ` 48 | 49 | func (q *Queries) DeleteGameServer(ctx context.Context, id string) (sql.Result, error) { 50 | return q.db.ExecContext(ctx, deleteGameServer, id) 51 | } 52 | 53 | const getGameServer = `-- name: GetGameServer :one 54 | SELECT id, user_id, ip, port, rcon_password, display_name, is_public, status FROM game_servers 55 | WHERE id = ? LIMIT 1 56 | ` 57 | 58 | func (q *Queries) GetGameServer(ctx context.Context, id string) (GameServer, error) { 59 | row := q.db.QueryRowContext(ctx, getGameServer, id) 60 | var i GameServer 61 | err := row.Scan( 62 | &i.ID, 63 | &i.UserID, 64 | &i.Ip, 65 | &i.Port, 66 | &i.RconPassword, 67 | &i.DisplayName, 68 | &i.IsPublic, 69 | &i.Status, 70 | ) 71 | return i, err 72 | } 73 | 74 | const getGameServersByUser = `-- name: GetGameServersByUser :many 75 | SELECT id, user_id, ip, port, rcon_password, display_name, is_public, status FROM game_servers 76 | WHERE user_id = ? 77 | ` 78 | 79 | func (q *Queries) GetGameServersByUser(ctx context.Context, userID string) ([]GameServer, error) { 80 | rows, err := q.db.QueryContext(ctx, getGameServersByUser, userID) 81 | if err != nil { 82 | return nil, err 83 | } 84 | defer rows.Close() 85 | var items []GameServer 86 | for rows.Next() { 87 | var i GameServer 88 | if err := rows.Scan( 89 | &i.ID, 90 | &i.UserID, 91 | &i.Ip, 92 | &i.Port, 93 | &i.RconPassword, 94 | &i.DisplayName, 95 | &i.IsPublic, 96 | &i.Status, 97 | ); err != nil { 98 | return nil, err 99 | } 100 | items = append(items, i) 101 | } 102 | if err := rows.Close(); err != nil { 103 | return nil, err 104 | } 105 | if err := rows.Err(); err != nil { 106 | return nil, err 107 | } 108 | return items, nil 109 | } 110 | 111 | const getGameServersByUsers = `-- name: GetGameServersByUsers :many 112 | SELECT id, user_id, ip, port, rcon_password, display_name, is_public, status FROM game_servers 113 | WHERE user_id IN (/*SLICE:user_ids*/?) 114 | ` 115 | 116 | func (q *Queries) GetGameServersByUsers(ctx context.Context, userIds []string) ([]GameServer, error) { 117 | query := getGameServersByUsers 118 | var queryParams []interface{} 119 | if len(userIds) > 0 { 120 | for _, v := range userIds { 121 | queryParams = append(queryParams, v) 122 | } 123 | query = strings.Replace(query, "/*SLICE:user_ids*/?", strings.Repeat(",?", len(userIds))[1:], 1) 124 | } else { 125 | query = strings.Replace(query, "/*SLICE:user_ids*/?", "NULL", 1) 126 | } 127 | rows, err := q.db.QueryContext(ctx, query, queryParams...) 128 | if err != nil { 129 | return nil, err 130 | } 131 | defer rows.Close() 132 | var items []GameServer 133 | for rows.Next() { 134 | var i GameServer 135 | if err := rows.Scan( 136 | &i.ID, 137 | &i.UserID, 138 | &i.Ip, 139 | &i.Port, 140 | &i.RconPassword, 141 | &i.DisplayName, 142 | &i.IsPublic, 143 | &i.Status, 144 | ); err != nil { 145 | return nil, err 146 | } 147 | items = append(items, i) 148 | } 149 | if err := rows.Close(); err != nil { 150 | return nil, err 151 | } 152 | if err := rows.Err(); err != nil { 153 | return nil, err 154 | } 155 | return items, nil 156 | } 157 | 158 | const getPublicGameServers = `-- name: GetPublicGameServers :many 159 | SELECT id, user_id, ip, port, rcon_password, display_name, is_public, status FROM game_servers 160 | WHERE is_public = TRUE 161 | ` 162 | 163 | func (q *Queries) GetPublicGameServers(ctx context.Context) ([]GameServer, error) { 164 | rows, err := q.db.QueryContext(ctx, getPublicGameServers) 165 | if err != nil { 166 | return nil, err 167 | } 168 | defer rows.Close() 169 | var items []GameServer 170 | for rows.Next() { 171 | var i GameServer 172 | if err := rows.Scan( 173 | &i.ID, 174 | &i.UserID, 175 | &i.Ip, 176 | &i.Port, 177 | &i.RconPassword, 178 | &i.DisplayName, 179 | &i.IsPublic, 180 | &i.Status, 181 | ); err != nil { 182 | return nil, err 183 | } 184 | items = append(items, i) 185 | } 186 | if err := rows.Close(); err != nil { 187 | return nil, err 188 | } 189 | if err := rows.Err(); err != nil { 190 | return nil, err 191 | } 192 | return items, nil 193 | } 194 | -------------------------------------------------------------------------------- /backend/graph/converter.go: -------------------------------------------------------------------------------- 1 | package graph 2 | 3 | import ( 4 | "github.com/FlowingSPDG/get5loader/backend/entity" 5 | "github.com/FlowingSPDG/get5loader/backend/graph/model" 6 | ) 7 | 8 | // entity をmodelに変換する処理 9 | 10 | func convertGameServer(gs *entity.GameServer) *model.GameServer { 11 | return &model.GameServer{ 12 | ID: string(gs.ID), 13 | IP: gs.Ip, 14 | Port: int(gs.Port), 15 | Name: gs.DisplayName, 16 | Public: gs.IsPublic, 17 | } 18 | } 19 | 20 | func convertGameServers(gss []*entity.GameServer) []*model.GameServer { 21 | ret := make([]*model.GameServer, 0, len(gss)) 22 | for _, gs := range gss { 23 | ret = append(ret, convertGameServer(gs)) 24 | } 25 | return ret 26 | } 27 | 28 | func convertTeam(team *entity.Team) *model.Team { 29 | return &model.Team{ 30 | ID: string(team.ID), 31 | UserID: "", 32 | Name: team.Name, 33 | Flag: team.Flag, 34 | Tag: team.Tag, 35 | Logo: team.Logo, 36 | Public: team.Public, 37 | } 38 | } 39 | 40 | func convertTeams(teams []*entity.Team) []*model.Team { 41 | ret := make([]*model.Team, 0, len(teams)) 42 | for _, team := range teams { 43 | ret = append(ret, convertTeam(team)) 44 | } 45 | return ret 46 | } 47 | 48 | func convertPlayer(player *entity.Player) *model.Player { 49 | return &model.Player{ 50 | ID: string(player.ID), 51 | TeamID: string(player.TeamID), 52 | SteamID: uint64(player.SteamID), 53 | Name: player.Name, 54 | } 55 | } 56 | 57 | func convertPlayers(players []*entity.Player) []*model.Player { 58 | ret := make([]*model.Player, 0, len(players)) 59 | for _, player := range players { 60 | ret = append(ret, convertPlayer(player)) 61 | } 62 | return ret 63 | } 64 | 65 | func convertMatch(match *entity.Match) *model.Match { 66 | return &model.Match{ 67 | ID: string(match.ID), 68 | UserID: string(match.UserID), 69 | Team1: &model.Team{}, 70 | Team1Id: string(match.Team1ID), 71 | Team2: &model.Team{}, 72 | Team2Id: string(match.Team2ID), 73 | Winner: string(match.Winner), 74 | StartedAt: match.StartTime, 75 | EndedAt: match.EndTime, 76 | MaxMaps: int(match.MaxMaps), 77 | Title: match.Title, 78 | SkipVeto: match.SkipVeto, 79 | Team1Score: int(match.Team1Score), 80 | Team2Score: int(match.Team2Score), 81 | Forfeit: match.Forfeit, 82 | MapStats: []*model.MapStats{}, 83 | } 84 | } 85 | 86 | func convertMatches(matches []*entity.Match) []*model.Match { 87 | ret := make([]*model.Match, 0, len(matches)) 88 | for _, match := range matches { 89 | ret = append(ret, convertMatch(match)) 90 | } 91 | return ret 92 | } 93 | 94 | func convertMapstat(mapstat *entity.MapStat) *model.MapStats { 95 | return &model.MapStats{ 96 | ID: string(mapstat.ID), 97 | MatchID: string(mapstat.MatchID), 98 | MapNumber: int(mapstat.MapNumber), 99 | MapName: mapstat.MapName, 100 | StartedAt: mapstat.StartTime, 101 | EndedAt: mapstat.EndTime, 102 | Winner: (*string)(mapstat.Winner), 103 | Team1score: int(mapstat.Team1Score), 104 | Team2score: int(mapstat.Team2Score), 105 | } 106 | } 107 | 108 | func convertMapStats(mapstats []*entity.MapStat) []*model.MapStats { 109 | ret := make([]*model.MapStats, 0, len(mapstats)) 110 | for _, mapstat := range mapstats { 111 | ret = append(ret, convertMapstat(mapstat)) 112 | } 113 | return ret 114 | } 115 | 116 | func convertPlayerstat(playerstat *entity.PlayerStat) *model.PlayerStats { 117 | return &model.PlayerStats{ 118 | ID: string(playerstat.ID), 119 | MatchID: string(playerstat.MatchID), 120 | MapstatsID: string(playerstat.MapID), 121 | SteamID: uint64(playerstat.SteamID), 122 | Name: playerstat.Name, 123 | Kills: int(playerstat.Kills), 124 | Assists: int(playerstat.Assists), 125 | Deaths: int(playerstat.Deaths), 126 | RoundsPlayed: int(playerstat.RoundsPlayed), 127 | FlashBangAssists: int(playerstat.FlashbangAssists), 128 | Suicides: int(playerstat.Suicides), 129 | HeadshotKills: int(playerstat.HeadShotKills), 130 | Damage: int(playerstat.Damage), 131 | BombPlants: int(playerstat.BombPlants), 132 | BombDefuses: int(playerstat.BombDefuses), 133 | V1: int(playerstat.V1), 134 | V2: int(playerstat.V2), 135 | V3: int(playerstat.V3), 136 | V4: int(playerstat.V4), 137 | V5: int(playerstat.V5), 138 | K1: int(playerstat.K1), 139 | K2: int(playerstat.K2), 140 | K3: int(playerstat.K3), 141 | K4: int(playerstat.K4), 142 | K5: int(playerstat.K5), 143 | FirstDeathT: int(playerstat.FirstDeathT), 144 | FirstDeathCt: int(playerstat.FirstDeathCT), 145 | FirstKillT: int(playerstat.FirstKillT), 146 | FirstKillCt: int(playerstat.FirstKillCT), 147 | } 148 | } 149 | 150 | func convertPlayerstats(playerstats []*entity.PlayerStat) []*model.PlayerStats { 151 | ret := make([]*model.PlayerStats, 0, len(playerstats)) 152 | for _, playerstat := range playerstats { 153 | ret = append(ret, convertPlayerstat(playerstat)) 154 | } 155 | return ret 156 | } 157 | 158 | func convertUser(user *entity.User) *model.User { 159 | return &model.User{ 160 | ID: string(user.ID), 161 | SteamID: uint64(user.SteamID), 162 | Name: user.Name, 163 | Admin: false, 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /backend/graph/model/models_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by github.com/99designs/gqlgen, DO NOT EDIT. 2 | 3 | package model 4 | 5 | import ( 6 | "time" 7 | ) 8 | 9 | type GameServer struct { 10 | ID string `json:"id"` 11 | IP string `json:"Ip"` 12 | Port int `json:"port"` 13 | Name string `json:"name"` 14 | Public bool `json:"public"` 15 | } 16 | 17 | type LoginToken struct { 18 | Token string `json:"token"` 19 | } 20 | 21 | type MapStats struct { 22 | ID string `json:"id"` 23 | MatchID string `json:"matchId"` 24 | MapNumber int `json:"mapNumber"` 25 | MapName string `json:"mapName"` 26 | StartedAt *time.Time `json:"startedAt,omitempty"` 27 | EndedAt *time.Time `json:"endedAt,omitempty"` 28 | Winner *string `json:"winner,omitempty"` 29 | Team1score int `json:"team1score"` 30 | Team2score int `json:"team2score"` 31 | Playerstats []*PlayerStats `json:"playerstats"` 32 | } 33 | 34 | type Match struct { 35 | ID string `json:"id"` 36 | UserID string `json:"userId"` 37 | Team1 *Team `json:"team1"` 38 | Team1Id string `json:"team1Id"` 39 | Team2 *Team `json:"team2"` 40 | Team2Id string `json:"team2Id"` 41 | Winner string `json:"winner"` 42 | StartedAt *time.Time `json:"startedAt,omitempty"` 43 | EndedAt *time.Time `json:"endedAt,omitempty"` 44 | MaxMaps int `json:"maxMaps"` 45 | Title string `json:"title"` 46 | SkipVeto bool `json:"skipVeto"` 47 | Team1Score int `json:"team1Score"` 48 | Team2Score int `json:"team2Score"` 49 | Forfeit *bool `json:"forfeit,omitempty"` 50 | MapStats []*MapStats `json:"mapStats"` 51 | } 52 | 53 | type NewGameServer struct { 54 | IP string `json:"Ip"` 55 | Port int `json:"port"` 56 | Name string `json:"name"` 57 | RconPassword string `json:"RconPassword"` 58 | Public bool `json:"public"` 59 | } 60 | 61 | type NewMatch struct { 62 | Team1 string `json:"team1"` 63 | Team2 string `json:"team2"` 64 | ServerID string `json:"serverID"` 65 | MaxMaps int `json:"maxMaps"` 66 | Title string `json:"title"` 67 | SkipVeto bool `json:"skipVeto"` 68 | } 69 | 70 | type NewPlayer struct { 71 | SteamID uint64 `json:"steamId"` 72 | Name string `json:"name"` 73 | Teamid string `json:"teamid"` 74 | } 75 | 76 | type NewPlayerForTeam struct { 77 | SteamID uint64 `json:"steamId"` 78 | Name string `json:"name"` 79 | } 80 | 81 | type NewTeam struct { 82 | Name string `json:"name"` 83 | Flag string `json:"flag"` 84 | Tag string `json:"tag"` 85 | Logo string `json:"logo"` 86 | Public bool `json:"public"` 87 | Players []*NewPlayerForTeam `json:"players,omitempty"` 88 | } 89 | 90 | type NewUser struct { 91 | SteamID uint64 `json:"steamId"` 92 | Name string `json:"name"` 93 | Password string `json:"password"` 94 | Admin bool `json:"admin"` 95 | } 96 | 97 | type Player struct { 98 | ID string `json:"id"` 99 | TeamID string `json:"teamId"` 100 | SteamID uint64 `json:"steamId"` 101 | Name string `json:"name"` 102 | } 103 | 104 | type PlayerStats struct { 105 | ID string `json:"id"` 106 | MatchID string `json:"matchId"` 107 | MapstatsID string `json:"mapstatsId"` 108 | SteamID uint64 `json:"steamId"` 109 | Name string `json:"name"` 110 | Kills int `json:"kills"` 111 | Assists int `json:"assists"` 112 | Deaths int `json:"deaths"` 113 | RoundsPlayed int `json:"roundsPlayed"` 114 | FlashBangAssists int `json:"flashBangAssists"` 115 | Suicides int `json:"suicides"` 116 | HeadshotKills int `json:"headshotKills"` 117 | Damage int `json:"damage"` 118 | BombPlants int `json:"bombPlants"` 119 | BombDefuses int `json:"bombDefuses"` 120 | V1 int `json:"v1"` 121 | V2 int `json:"v2"` 122 | V3 int `json:"v3"` 123 | V4 int `json:"v4"` 124 | V5 int `json:"v5"` 125 | K1 int `json:"k1"` 126 | K2 int `json:"k2"` 127 | K3 int `json:"k3"` 128 | K4 int `json:"k4"` 129 | K5 int `json:"k5"` 130 | FirstDeathT int `json:"firstDeathT"` 131 | FirstDeathCt int `json:"firstDeathCT"` 132 | FirstKillT int `json:"firstKillT"` 133 | FirstKillCt int `json:"firstKillCT"` 134 | } 135 | 136 | type Team struct { 137 | ID string `json:"id"` 138 | UserID string `json:"userId"` 139 | Name string `json:"name"` 140 | Flag string `json:"flag"` 141 | Tag string `json:"tag"` 142 | Logo string `json:"logo"` 143 | Public bool `json:"public"` 144 | Players []*Player `json:"players"` 145 | } 146 | 147 | type User struct { 148 | ID string `json:"id"` 149 | SteamID uint64 `json:"steamId"` 150 | Name string `json:"name"` 151 | Admin bool `json:"admin"` 152 | Gameservers []*GameServer `json:"gameservers"` 153 | Teams []*Team `json:"teams"` 154 | Matches []*Match `json:"matches"` 155 | } 156 | 157 | type UserLoginID struct { 158 | ID string `json:"ID"` 159 | Password string `json:"password"` 160 | } 161 | 162 | type UserLoginSteamID struct { 163 | SteamID uint64 `json:"steamId"` 164 | Password string `json:"password"` 165 | } 166 | --------------------------------------------------------------------------------