Commit 97b49cf1 authored by Julien Schröter's avatar Julien Schröter Committed by Frederik Wegner

Resolve "Send details about why duel could not be created"

parent c4936092
Pipeline #2260 failed with stages
in 4 minutes and 12 seconds
......@@ -543,25 +543,42 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/duel'
'200':
description: Query was correct but no new duel could not be created. Often due to no intersection in pool selections of players.
'409':
description: Did not create duel since the current user and the challenged user don't have common question pools.
content:
text/plain:
application/json:
schema:
type: string
description: Details on why no duel was created.
type: object
properties:
pools:
type: object
required:
- self
- opponent
properties:
self:
type: array
items:
type: integer
format: uint32
description: ID's of the requesting user's pools.
opponent:
type: array
items:
type: integer
format: uint32
description: ID's of the given user's pools.
'401':
description: Authentication failure.
content:
text/plain:
schema:
type: string
'400':
description: Query was invalid.
'403':
description: There already is a running duel with the current and the challenged user.
'404':
description: User with `id` does not exist.
'500':
description: The server cannot deliver due to an internal error.
'/duel/pool':
get:
summary: Get pools to choose from.
......
......@@ -11,6 +11,7 @@ import (
"firebase.google.com/go/messaging"
"gitlab.akamu.de/akamu/game-server-go/endpoint/pool"
"gitlab.akamu.de/akamu/game-server-go/endpoint/refreshjwt"
"github.com/getsentry/raven-go"
......@@ -22,7 +23,7 @@ import (
var errUserForbidsNotifications = errors.New("user does not want to receive notifications")
func SetupDuelRoutes(group *gin.RouterGroup, repository DuelQuery, titleRepository title.TitleQuery, sessionRepository refreshjwt.RefreshJWTQuery) {
func SetupDuelRoutes(group *gin.RouterGroup, repository DuelQuery, titleRepository title.TitleQuery, sessionRepository refreshjwt.RefreshJWTQuery, poolRepository pool.PoolQuery) {
group.OPTIONS("", func(ctx *gin.Context) {
ctx.Header("Allow", "GET,POST,PATCH,PUT,DELETE")
ctx.Status(200)
......@@ -32,7 +33,7 @@ func SetupDuelRoutes(group *gin.RouterGroup, repository DuelQuery, titleReposito
group.GET("", getDuel(repository))
// Endpoint to create new duel
group.POST("", postDuel(repository, sessionRepository))
group.POST("", postDuel(repository, sessionRepository, poolRepository))
// Endpoint to fetch all playable pools
group.GET("/pool", getPools(repository))
......@@ -96,7 +97,7 @@ func getDuel(repository DuelQuery) gin.HandlerFunc {
}
}
func postDuel(repository DuelQuery, sessionRepository refreshjwt.RefreshJWTQuery) gin.HandlerFunc {
func postDuel(repository DuelQuery, sessionRepository refreshjwt.RefreshJWTQuery, poolRepository pool.PoolQuery) gin.HandlerFunc {
return func(ctx *gin.Context) {
// Fetch challenged user
var challenged struct {
......@@ -123,7 +124,21 @@ func postDuel(repository DuelQuery, sessionRepository refreshjwt.RefreshJWTQuery
case ErrUserNotFound:
ctx.AbortWithError(http.StatusNotFound, err)
case ErrNoCommonPool:
ctx.AbortWithError(http.StatusConflict, err)
// Get pools of user and challenged user for the app
poolsChallenger, errPoolsChallenger := poolRepository.SelectUserPools(challenger)
poolsChallenged, errPoolsChallanged := poolRepository.SelectUserPools(challenged.ID)
if errPoolsChallenger != nil {
raven.CaptureError(errPoolsChallenger, nil)
ctx.AbortWithError(http.StatusConflict, errPoolsChallenger)
return
} else if errPoolsChallanged != nil {
raven.CaptureError(errPoolsChallanged, nil)
ctx.AbortWithError(http.StatusConflict, errPoolsChallanged)
return
}
ctx.AbortWithStatusJSON(http.StatusConflict, gin.H{"pools": gin.H{"self": poolsChallenger, "opponent": poolsChallenged}})
default:
raven.CaptureError(err, nil)
ctx.AbortWithError(http.StatusInternalServerError, err)
......
......@@ -238,6 +238,8 @@ var ErrQuestionNotFound = errors.New("question not found")
var ErrBadRequest = errors.New("The request was either formatted incorrectly or it was not allowed at the currect state of the database.")
const TotalRounds = 2
// Must match the value in endpoints/pool/query.go
const QuestionsPerRound = 2
const queryDuelsByID = "SELECT `duel`.`idduel`, `duel`.`round_current`, `duel`.`round_max`, `duel`.`started_on`, `duel`.`modified_on`, `duel`.`finished_on`, `duel`.`running`, `duel`.`started`, `duel`.`next`, `duel_player_score`.`score`, `participant`.`iduser`, `participant`.`username`, idavatar, avatar.image, avatar.level, title.idtitle, title.name, subject.idsubject, subject.name, subject.shortform, subject.department, subject.description, title.unlock_score, title.unlock_win, `round`.`idround`, `round`.`number`, `round`.`started`, `pool`.`idpool`, `pool`.`name`, `pool`.`shortform`, `pool`.`image`" +
......
......@@ -6,14 +6,17 @@ import (
"fmt"
"gitlab.akamu.de/akamu/game-server-go/dbhandler"
"gitlab.akamu.de/akamu/game-server-go/endpoint/duel"
"gitlab.akamu.de/akamu/game-server-go/schemas"
)
var ErrorInvalidPools = errors.New("The selection of pools was invalid.")
// Must match the value in endpoint/duel/query.go
const QuestionsPerRound = 2
type PoolQuery interface {
Select(userID uint32) ([]schemas.PoolSchema, error)
SelectUserPools(userID uint32) ([]uint32, error)
PatchUserPools(userID uint32, selection []uint32) error
}
......@@ -36,7 +39,7 @@ func (m MySQLPoolQuery) Select(userID uint32) ([]schemas.PoolSchema, error) {
}
// Fetch pools from database
rows, errRows := db.Query(queryString, userID, duel.QuestionsPerRound)
rows, errRows := db.Query(queryString, userID, QuestionsPerRound)
// Check whether query failed
if errRows != nil {
......@@ -76,6 +79,38 @@ func (m MySQLPoolQuery) Select(userID uint32) ([]schemas.PoolSchema, error) {
return pools, nil
}
// Select returns a list of all available playable pools.
func (m MySQLPoolQuery) SelectUserPools(userID uint32) ([]uint32, error) {
db, err := dbhandler.GetDBConnection()
if err != nil {
return nil, fmt.Errorf("failed to get database instance: %v", err)
}
// Fetch pools from database
rows, err := db.Query("SELECT DISTINCT `pool` FROM `user_pool` WHERE `user`=? AND `pool` IN (SELECT `pool` FROM `pool_question` GROUP BY `pool` HAVING COUNT(DISTINCT `question`) >= ?)", userID, QuestionsPerRound)
// Check whether query failed
if err != nil {
return nil, fmt.Errorf("failed fetching pools from database: %v", err)
}
defer rows.Close()
// Generate output
var pools []uint32
for rows.Next() {
var pool uint32
if err := rows.Scan(&pool); err != nil {
return nil, fmt.Errorf("failed aggregating pools from database: %v", err)
}
pools = append(pools, pool)
}
return pools, nil
}
// PatchUserPools insert relations into user_pool for all pools in the array `selection`.
// Relations to pools that are not in `selection` are deleted.
func (m MySQLPoolQuery) PatchUserPools(userID uint32, selection []uint32) error {
......
......@@ -313,7 +313,7 @@ func SetupRoutes(courseRepository course.CourseQuery, flashcardRepository flashc
pool.SetupPoolRoutes(authGroup.Group("/pool"), poolRepository)
friend.SetupFriendRoutes(authGroup.Group("/friend"), friendRepository)
avatar.SetupAvatarRoutes(authGroup.Group("/avatar"), avatarRepository)
duel.SetupDuelRoutes(authGroup.Group("/duel"), duelRepository, titleRepository, refreshjwtRepository)
duel.SetupDuelRoutes(authGroup.Group("/duel"), duelRepository, titleRepository, refreshjwtRepository, poolRepository)
resource.SetupResourceRoutes(authGroup.Group("/resource"), resourceRepository, resourceServerConfig)
refreshjwt.SetupRefreshJWTRoutes(authGroup.Group("/session"), refreshjwtRepository)
......
......@@ -21,6 +21,10 @@ func (p *mockPool) Select(userID uint32) ([]schemas.PoolSchema, error) {
return pools, nil
}
func (*mockPool) SelectUserPools(userId uint32) ([]uint32, error) {
return nil, nil
}
// just to match interface doesnt do anything actually
func (p *mockPool) PatchUserPools(userID uint32, selection []uint32) error {
return nil
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment