Почти рабочая админка
This commit is contained in:
parent
10a355d0f1
commit
fac2df0bc1
11 changed files with 478 additions and 204 deletions
129
api/openapi.yaml
129
api/openapi.yaml
|
@ -13,52 +13,44 @@ paths:
|
||||||
get:
|
get:
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
$ref: '#/components/responses/userResponse'
|
$ref: "#/components/responses/userResponse"
|
||||||
403:
|
403:
|
||||||
$ref: '#/components/responses/errorResponse'
|
$ref: "#/components/responses/errorResponse"
|
||||||
/user/login:
|
/user/login:
|
||||||
post:
|
post:
|
||||||
security: []
|
security: []
|
||||||
requestBody:
|
requestBody:
|
||||||
$ref: '#/components/requestBodies/login'
|
$ref: "#/components/requestBodies/login"
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
$ref: '#/components/responses/userResponse'
|
$ref: "#/components/responses/userResponse"
|
||||||
400:
|
400:
|
||||||
$ref: '#/components/responses/errorResponse'
|
$ref: "#/components/responses/errorResponse"
|
||||||
/user/register:
|
/user/register:
|
||||||
post:
|
post:
|
||||||
security: []
|
security: []
|
||||||
requestBody:
|
requestBody:
|
||||||
$ref: '#/components/requestBodies/register'
|
$ref: "#/components/requestBodies/register"
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
$ref: '#/components/responses/userResponse'
|
$ref: "#/components/responses/userResponse"
|
||||||
400:
|
400:
|
||||||
$ref: '#/components/responses/errorResponse'
|
$ref: "#/components/responses/errorResponse"
|
||||||
/user/logout:
|
/user/logout:
|
||||||
post:
|
post:
|
||||||
responses:
|
responses:
|
||||||
204:
|
204:
|
||||||
description: "success logout"
|
description: "success logout"
|
||||||
400:
|
400:
|
||||||
$ref: '#/components/responses/errorResponse'
|
$ref: "#/components/responses/errorResponse"
|
||||||
|
|
||||||
# Game routes
|
# Game routes
|
||||||
/games:
|
/games:
|
||||||
get:
|
get:
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
$ref: '#/components/responses/gameListResponse'
|
$ref: "#/components/responses/gameListResponse"
|
||||||
post:
|
|
||||||
operationId: createGame
|
|
||||||
security:
|
|
||||||
- cookieAuth: [creator, admin]
|
|
||||||
requestBody:
|
|
||||||
$ref: "#/components/requestBodies/gameEditRequest"
|
|
||||||
responses:
|
|
||||||
200:
|
|
||||||
$ref: "#/components/responses/gameResponse"
|
|
||||||
/engine/{uid}:
|
/engine/{uid}:
|
||||||
get:
|
get:
|
||||||
operationId: gameEngine
|
operationId: gameEngine
|
||||||
|
@ -71,7 +63,7 @@ paths:
|
||||||
format: uuid
|
format: uuid
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
$ref: '#/components/responses/taskResponse'
|
$ref: "#/components/responses/taskResponse"
|
||||||
/engine/{uid}/code:
|
/engine/{uid}/code:
|
||||||
post:
|
post:
|
||||||
operationId: enterCode
|
operationId: enterCode
|
||||||
|
@ -86,7 +78,7 @@ paths:
|
||||||
$ref: "#/components/requestBodies/enterCodeRequest"
|
$ref: "#/components/requestBodies/enterCodeRequest"
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
$ref: '#/components/responses/taskResponse'
|
$ref: "#/components/responses/taskResponse"
|
||||||
/file/upload:
|
/file/upload:
|
||||||
post:
|
post:
|
||||||
operationId: uploadFile
|
operationId: uploadFile
|
||||||
|
@ -103,7 +95,7 @@ paths:
|
||||||
format: binary
|
format: binary
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
$ref: '#/components/responses/uploadResponse'
|
$ref: "#/components/responses/uploadResponse"
|
||||||
/file/{uid}:
|
/file/{uid}:
|
||||||
get:
|
get:
|
||||||
operationId: getFile
|
operationId: getFile
|
||||||
|
@ -118,11 +110,40 @@ paths:
|
||||||
200:
|
200:
|
||||||
description: file
|
description: file
|
||||||
content:
|
content:
|
||||||
'application/octet-stream':
|
"application/octet-stream":
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
format: binary
|
format: binary
|
||||||
/games/{uid}:
|
/admin/games:
|
||||||
|
get:
|
||||||
|
operationId: listGamesByAdmin
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
$ref: "#/components/responses/gameListResponse"
|
||||||
|
post:
|
||||||
|
operationId: createGame
|
||||||
|
security:
|
||||||
|
- cookieAuth: [creator, admin]
|
||||||
|
requestBody:
|
||||||
|
$ref: "#/components/requestBodies/gameEditRequest"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
$ref: "#/components/responses/gameAdminResponse"
|
||||||
|
/admin/games/{uid}:
|
||||||
|
get:
|
||||||
|
operationId: getGameByAdmin
|
||||||
|
parameters:
|
||||||
|
- name: uid
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
security:
|
||||||
|
- cookieAuth: [creator, admin]
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
$ref: "#/components/responses/gameAdminResponse"
|
||||||
post:
|
post:
|
||||||
operationId: editGame
|
operationId: editGame
|
||||||
parameters:
|
parameters:
|
||||||
|
@ -138,7 +159,7 @@ paths:
|
||||||
$ref: "#/components/requestBodies/gameEditRequest"
|
$ref: "#/components/requestBodies/gameEditRequest"
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
$ref: "#/components/responses/gameResponse"
|
$ref: "#/components/responses/gameAdminResponse"
|
||||||
components:
|
components:
|
||||||
schemas:
|
schemas:
|
||||||
userView:
|
userView:
|
||||||
|
@ -232,7 +253,7 @@ components:
|
||||||
codes:
|
codes:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/components/schemas/codeView'
|
$ref: "#/components/schemas/codeView"
|
||||||
# solutions:
|
# solutions:
|
||||||
# type: array
|
# type: array
|
||||||
# items:
|
# items:
|
||||||
|
@ -263,6 +284,11 @@ components:
|
||||||
gameEdit:
|
gameEdit:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
visible:
|
||||||
|
type: boolean
|
||||||
title:
|
title:
|
||||||
type: string
|
type: string
|
||||||
description:
|
description:
|
||||||
|
@ -279,6 +305,7 @@ components:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
required:
|
required:
|
||||||
|
- visible
|
||||||
- title
|
- title
|
||||||
- description
|
- description
|
||||||
- type
|
- type
|
||||||
|
@ -288,6 +315,9 @@ components:
|
||||||
taskEdit:
|
taskEdit:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
title:
|
title:
|
||||||
type: string
|
type: string
|
||||||
text:
|
text:
|
||||||
|
@ -295,7 +325,7 @@ components:
|
||||||
codes:
|
codes:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/components/schemas/codeEdit'
|
$ref: "#/components/schemas/codeEdit"
|
||||||
# solutions:
|
# solutions:
|
||||||
# type: array
|
# type: array
|
||||||
# items:
|
# items:
|
||||||
|
@ -308,6 +338,9 @@ components:
|
||||||
codeEdit:
|
codeEdit:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
description:
|
description:
|
||||||
type: string
|
type: string
|
||||||
code:
|
code:
|
||||||
|
@ -334,7 +367,7 @@ components:
|
||||||
login:
|
login:
|
||||||
required: true
|
required: true
|
||||||
content:
|
content:
|
||||||
'application/json':
|
"application/json":
|
||||||
schema:
|
schema:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -346,7 +379,7 @@ components:
|
||||||
register:
|
register:
|
||||||
required: true
|
required: true
|
||||||
content:
|
content:
|
||||||
'application/json':
|
"application/json":
|
||||||
schema:
|
schema:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -362,13 +395,13 @@ components:
|
||||||
gameEditRequest:
|
gameEditRequest:
|
||||||
required: true
|
required: true
|
||||||
content:
|
content:
|
||||||
'application/json':
|
"application/json":
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/gameEdit'
|
$ref: "#/components/schemas/gameEdit"
|
||||||
enterCodeRequest:
|
enterCodeRequest:
|
||||||
required: true
|
required: true
|
||||||
content:
|
content:
|
||||||
'application/json':
|
"application/json":
|
||||||
schema:
|
schema:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -378,15 +411,15 @@ components:
|
||||||
- code
|
- code
|
||||||
responses:
|
responses:
|
||||||
userResponse:
|
userResponse:
|
||||||
description: ''
|
description: ""
|
||||||
content:
|
content:
|
||||||
'application/json':
|
"application/json":
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/userView"
|
$ref: "#/components/schemas/userView"
|
||||||
errorResponse:
|
errorResponse:
|
||||||
description: ''
|
description: ""
|
||||||
content:
|
content:
|
||||||
'application/json':
|
"application/json":
|
||||||
schema:
|
schema:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -396,29 +429,35 @@ components:
|
||||||
type: string
|
type: string
|
||||||
required: [code, message]
|
required: [code, message]
|
||||||
gameListResponse:
|
gameListResponse:
|
||||||
description: ''
|
description: ""
|
||||||
content:
|
content:
|
||||||
'application/json':
|
"application/json":
|
||||||
schema:
|
schema:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: "#/components/schemas/gameView"
|
$ref: "#/components/schemas/gameView"
|
||||||
gameResponse:
|
gameResponse:
|
||||||
description: ''
|
description: ""
|
||||||
content:
|
content:
|
||||||
'application/json':
|
"application/json":
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/gameView"
|
$ref: "#/components/schemas/gameView"
|
||||||
taskResponse:
|
gameAdminResponse:
|
||||||
description: ''
|
description: ""
|
||||||
content:
|
content:
|
||||||
'application/json':
|
"application/json":
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/gameEdit"
|
||||||
|
taskResponse:
|
||||||
|
description: ""
|
||||||
|
content:
|
||||||
|
"application/json":
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/taskView"
|
$ref: "#/components/schemas/taskView"
|
||||||
uploadResponse:
|
uploadResponse:
|
||||||
description: ''
|
description: ""
|
||||||
content:
|
content:
|
||||||
'application/json':
|
"application/json":
|
||||||
schema:
|
schema:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|
152
api/server.go
152
api/server.go
|
@ -22,6 +22,18 @@ import (
|
||||||
// ServerInterface represents all server handlers.
|
// ServerInterface represents all server handlers.
|
||||||
type ServerInterface interface {
|
type ServerInterface interface {
|
||||||
|
|
||||||
|
// (GET /admin/games)
|
||||||
|
ListGamesByAdmin(ctx echo.Context) error
|
||||||
|
|
||||||
|
// (POST /admin/games)
|
||||||
|
CreateGame(ctx echo.Context) error
|
||||||
|
|
||||||
|
// (GET /admin/games/{uid})
|
||||||
|
GetGameByAdmin(ctx echo.Context, uid openapi_types.UUID) error
|
||||||
|
|
||||||
|
// (POST /admin/games/{uid})
|
||||||
|
EditGame(ctx echo.Context, uid openapi_types.UUID) error
|
||||||
|
|
||||||
// (GET /engine/{uid})
|
// (GET /engine/{uid})
|
||||||
GameEngine(ctx echo.Context, uid openapi_types.UUID) error
|
GameEngine(ctx echo.Context, uid openapi_types.UUID) error
|
||||||
|
|
||||||
|
@ -37,12 +49,6 @@ type ServerInterface interface {
|
||||||
// (GET /games)
|
// (GET /games)
|
||||||
GetGames(ctx echo.Context) error
|
GetGames(ctx echo.Context) error
|
||||||
|
|
||||||
// (POST /games)
|
|
||||||
CreateGame(ctx echo.Context) error
|
|
||||||
|
|
||||||
// (POST /games/{uid})
|
|
||||||
EditGame(ctx echo.Context, uid openapi_types.UUID) error
|
|
||||||
|
|
||||||
// (GET /user)
|
// (GET /user)
|
||||||
GetUser(ctx echo.Context) error
|
GetUser(ctx echo.Context) error
|
||||||
|
|
||||||
|
@ -61,6 +67,64 @@ type ServerInterfaceWrapper struct {
|
||||||
Handler ServerInterface
|
Handler ServerInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListGamesByAdmin converts echo context to params.
|
||||||
|
func (w *ServerInterfaceWrapper) ListGamesByAdmin(ctx echo.Context) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
ctx.Set(CookieAuthScopes, []string{})
|
||||||
|
|
||||||
|
// Invoke the callback with all the unmarshaled arguments
|
||||||
|
err = w.Handler.ListGamesByAdmin(ctx)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateGame converts echo context to params.
|
||||||
|
func (w *ServerInterfaceWrapper) CreateGame(ctx echo.Context) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
ctx.Set(CookieAuthScopes, []string{"creator", "admin"})
|
||||||
|
|
||||||
|
// Invoke the callback with all the unmarshaled arguments
|
||||||
|
err = w.Handler.CreateGame(ctx)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGameByAdmin converts echo context to params.
|
||||||
|
func (w *ServerInterfaceWrapper) GetGameByAdmin(ctx echo.Context) error {
|
||||||
|
var err error
|
||||||
|
// ------------- Path parameter "uid" -------------
|
||||||
|
var uid openapi_types.UUID
|
||||||
|
|
||||||
|
err = runtime.BindStyledParameterWithLocation("simple", false, "uid", runtime.ParamLocationPath, ctx.Param("uid"), &uid)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter uid: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Set(CookieAuthScopes, []string{"creator", "admin"})
|
||||||
|
|
||||||
|
// Invoke the callback with all the unmarshaled arguments
|
||||||
|
err = w.Handler.GetGameByAdmin(ctx, uid)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// EditGame converts echo context to params.
|
||||||
|
func (w *ServerInterfaceWrapper) EditGame(ctx echo.Context) error {
|
||||||
|
var err error
|
||||||
|
// ------------- Path parameter "uid" -------------
|
||||||
|
var uid openapi_types.UUID
|
||||||
|
|
||||||
|
err = runtime.BindStyledParameterWithLocation("simple", false, "uid", runtime.ParamLocationPath, ctx.Param("uid"), &uid)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter uid: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Set(CookieAuthScopes, []string{"creator", "admin"})
|
||||||
|
|
||||||
|
// Invoke the callback with all the unmarshaled arguments
|
||||||
|
err = w.Handler.EditGame(ctx, uid)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// GameEngine converts echo context to params.
|
// GameEngine converts echo context to params.
|
||||||
func (w *ServerInterfaceWrapper) GameEngine(ctx echo.Context) error {
|
func (w *ServerInterfaceWrapper) GameEngine(ctx echo.Context) error {
|
||||||
var err error
|
var err error
|
||||||
|
@ -137,35 +201,6 @@ func (w *ServerInterfaceWrapper) GetGames(ctx echo.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateGame converts echo context to params.
|
|
||||||
func (w *ServerInterfaceWrapper) CreateGame(ctx echo.Context) error {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
ctx.Set(CookieAuthScopes, []string{"creator", "admin"})
|
|
||||||
|
|
||||||
// Invoke the callback with all the unmarshaled arguments
|
|
||||||
err = w.Handler.CreateGame(ctx)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// EditGame converts echo context to params.
|
|
||||||
func (w *ServerInterfaceWrapper) EditGame(ctx echo.Context) error {
|
|
||||||
var err error
|
|
||||||
// ------------- Path parameter "uid" -------------
|
|
||||||
var uid openapi_types.UUID
|
|
||||||
|
|
||||||
err = runtime.BindStyledParameterWithLocation("simple", false, "uid", runtime.ParamLocationPath, ctx.Param("uid"), &uid)
|
|
||||||
if err != nil {
|
|
||||||
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter uid: %s", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Set(CookieAuthScopes, []string{"creator", "admin"})
|
|
||||||
|
|
||||||
// Invoke the callback with all the unmarshaled arguments
|
|
||||||
err = w.Handler.EditGame(ctx, uid)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUser converts echo context to params.
|
// GetUser converts echo context to params.
|
||||||
func (w *ServerInterfaceWrapper) GetUser(ctx echo.Context) error {
|
func (w *ServerInterfaceWrapper) GetUser(ctx echo.Context) error {
|
||||||
var err error
|
var err error
|
||||||
|
@ -234,13 +269,15 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
|
||||||
Handler: si,
|
Handler: si,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
router.GET(baseURL+"/admin/games", wrapper.ListGamesByAdmin)
|
||||||
|
router.POST(baseURL+"/admin/games", wrapper.CreateGame)
|
||||||
|
router.GET(baseURL+"/admin/games/:uid", wrapper.GetGameByAdmin)
|
||||||
|
router.POST(baseURL+"/admin/games/:uid", wrapper.EditGame)
|
||||||
router.GET(baseURL+"/engine/:uid", wrapper.GameEngine)
|
router.GET(baseURL+"/engine/:uid", wrapper.GameEngine)
|
||||||
router.POST(baseURL+"/engine/:uid/code", wrapper.EnterCode)
|
router.POST(baseURL+"/engine/:uid/code", wrapper.EnterCode)
|
||||||
router.POST(baseURL+"/file/upload", wrapper.UploadFile)
|
router.POST(baseURL+"/file/upload", wrapper.UploadFile)
|
||||||
router.GET(baseURL+"/file/:uid", wrapper.GetFile)
|
router.GET(baseURL+"/file/:uid", wrapper.GetFile)
|
||||||
router.GET(baseURL+"/games", wrapper.GetGames)
|
router.GET(baseURL+"/games", wrapper.GetGames)
|
||||||
router.POST(baseURL+"/games", wrapper.CreateGame)
|
|
||||||
router.POST(baseURL+"/games/:uid", wrapper.EditGame)
|
|
||||||
router.GET(baseURL+"/user", wrapper.GetUser)
|
router.GET(baseURL+"/user", wrapper.GetUser)
|
||||||
router.POST(baseURL+"/user/login", wrapper.PostUserLogin)
|
router.POST(baseURL+"/user/login", wrapper.PostUserLogin)
|
||||||
router.POST(baseURL+"/user/logout", wrapper.PostUserLogout)
|
router.POST(baseURL+"/user/logout", wrapper.PostUserLogout)
|
||||||
|
@ -251,26 +288,27 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
|
||||||
// Base64 encoded, gzipped, json marshaled Swagger object
|
// Base64 encoded, gzipped, json marshaled Swagger object
|
||||||
var swaggerSpec = []string{
|
var swaggerSpec = []string{
|
||||||
|
|
||||||
"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",
|
||||||
"Ymo063X9bwAAAP//ZDhhHgMaAAA=",
|
"NBhuMVPEMRjzQ3X0UotbG7s/iRy38rKWXBChRsvbCdLR2relqmJAP9Z1q9Api1hI3X+5Lf8NAAD//0cV",
|
||||||
|
"3sY5HAAA",
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSwagger returns the content of the embedded swagger specification file
|
// GetSwagger returns the content of the embedded swagger specification file
|
||||||
|
|
22
api/types.go
22
api/types.go
|
@ -37,6 +37,7 @@ const (
|
||||||
type CodeEdit struct {
|
type CodeEdit struct {
|
||||||
Code string `json:"code"`
|
Code string `json:"code"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
|
Id *openapi_types.UUID `json:"id,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CodeView defines model for codeView.
|
// CodeView defines model for codeView.
|
||||||
|
@ -49,10 +50,12 @@ type CodeView struct {
|
||||||
type GameEdit struct {
|
type GameEdit struct {
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Icon openapi_types.UUID `json:"icon"`
|
Icon openapi_types.UUID `json:"icon"`
|
||||||
|
Id *openapi_types.UUID `json:"id,omitempty"`
|
||||||
Points int `json:"points"`
|
Points int `json:"points"`
|
||||||
Tasks []TaskEdit `json:"tasks"`
|
Tasks []TaskEdit `json:"tasks"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Type GameType `json:"type"`
|
Type GameType `json:"type"`
|
||||||
|
Visible bool `json:"visible"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GameType defines model for gameType.
|
// GameType defines model for gameType.
|
||||||
|
@ -74,6 +77,7 @@ type GameView struct {
|
||||||
// TaskEdit defines model for taskEdit.
|
// TaskEdit defines model for taskEdit.
|
||||||
type TaskEdit struct {
|
type TaskEdit struct {
|
||||||
Codes []CodeEdit `json:"codes"`
|
Codes []CodeEdit `json:"codes"`
|
||||||
|
Id *openapi_types.UUID `json:"id,omitempty"`
|
||||||
Text string `json:"text"`
|
Text string `json:"text"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
}
|
}
|
||||||
|
@ -111,12 +115,12 @@ type ErrorResponse struct {
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GameAdminResponse defines model for gameAdminResponse.
|
||||||
|
type GameAdminResponse = GameEdit
|
||||||
|
|
||||||
// GameListResponse defines model for gameListResponse.
|
// GameListResponse defines model for gameListResponse.
|
||||||
type GameListResponse = []GameView
|
type GameListResponse = []GameView
|
||||||
|
|
||||||
// GameResponse defines model for gameResponse.
|
|
||||||
type GameResponse = GameView
|
|
||||||
|
|
||||||
// TaskResponse defines model for taskResponse.
|
// TaskResponse defines model for taskResponse.
|
||||||
type TaskResponse = TaskView
|
type TaskResponse = TaskView
|
||||||
|
|
||||||
|
@ -172,18 +176,18 @@ type PostUserRegisterJSONBody struct {
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnterCodeJSONRequestBody defines body for EnterCode for application/json ContentType.
|
|
||||||
type EnterCodeJSONRequestBody EnterCodeJSONBody
|
|
||||||
|
|
||||||
// UploadFileMultipartRequestBody defines body for UploadFile for multipart/form-data ContentType.
|
|
||||||
type UploadFileMultipartRequestBody UploadFileMultipartBody
|
|
||||||
|
|
||||||
// CreateGameJSONRequestBody defines body for CreateGame for application/json ContentType.
|
// CreateGameJSONRequestBody defines body for CreateGame for application/json ContentType.
|
||||||
type CreateGameJSONRequestBody = GameEdit
|
type CreateGameJSONRequestBody = GameEdit
|
||||||
|
|
||||||
// EditGameJSONRequestBody defines body for EditGame for application/json ContentType.
|
// EditGameJSONRequestBody defines body for EditGame for application/json ContentType.
|
||||||
type EditGameJSONRequestBody = GameEdit
|
type EditGameJSONRequestBody = GameEdit
|
||||||
|
|
||||||
|
// EnterCodeJSONRequestBody defines body for EnterCode for application/json ContentType.
|
||||||
|
type EnterCodeJSONRequestBody EnterCodeJSONBody
|
||||||
|
|
||||||
|
// UploadFileMultipartRequestBody defines body for UploadFile for multipart/form-data ContentType.
|
||||||
|
type UploadFileMultipartRequestBody UploadFileMultipartBody
|
||||||
|
|
||||||
// PostUserLoginJSONRequestBody defines body for PostUserLogin for application/json ContentType.
|
// PostUserLoginJSONRequestBody defines body for PostUserLogin for application/json ContentType.
|
||||||
type PostUserLoginJSONRequestBody PostUserLoginJSONBody
|
type PostUserLoginJSONRequestBody PostUserLoginJSONBody
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,8 @@ import Engine from './pages/Engine'
|
||||||
import Quests from './pages/Quests'
|
import Quests from './pages/Quests'
|
||||||
import User from './pages/User'
|
import User from './pages/User'
|
||||||
import { useRole } from './utils/roles'
|
import { useRole } from './utils/roles'
|
||||||
import Quest from './pages/admin/Quest'
|
import EditQuest from './pages/admin/Quest'
|
||||||
|
import AdminQuest from './pages/admin/Quests'
|
||||||
|
|
||||||
const router = createBrowserRouter(
|
const router = createBrowserRouter(
|
||||||
createRoutesFromElements(
|
createRoutesFromElements(
|
||||||
|
@ -43,14 +44,19 @@ const router = createBrowserRouter(
|
||||||
loader={({ params }) => ajax(`/api/engine/${params.gameId}`).catch(x => { console.log(x); return null })}
|
loader={({ params }) => ajax(`/api/engine/${params.gameId}`).catch(x => { console.log(x); return null })}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="admin"
|
path="/admin/quests/new"
|
||||||
element={<Auth role="admin"><NoMatch /></Auth>}
|
element={<Auth role="creator"><EditQuest /></Auth>}
|
||||||
// loader={() => ajax(`/api/admin/games`)}
|
// loader={() => ajax(`/api/admin/games`)}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="quest/new"
|
path="/admin/quests/:gameId"
|
||||||
element={<Auth role="creator"><Quest /></Auth>}
|
element={<Auth role="creator"><EditQuest /></Auth>}
|
||||||
// loader={() => ajax(`/api/admin/games`)}
|
loader={({ params }) => ajax(`/api/admin/games/${params.gameId}`)}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/admin/quests"
|
||||||
|
element={<Auth role="creator"><AdminQuest /></Auth>}
|
||||||
|
loader={() => ajax('/api/admin/games')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route path="*" element={<NoMatch />} />
|
<Route path="*" element={<NoMatch />} />
|
||||||
|
|
|
@ -49,9 +49,9 @@ const AppLayout = () => {
|
||||||
|
|
||||||
if (hasRole('creator')) {
|
if (hasRole('creator')) {
|
||||||
items.push({
|
items.push({
|
||||||
key: 'quest/new',
|
key: 'admin/quests',
|
||||||
label: 'Создать квест',
|
label: 'Управление квестами',
|
||||||
link: '/quest/new'
|
link: '/admin/quests'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,9 @@ const Engine = () => {
|
||||||
const { message } = App.useApp()
|
const { message } = App.useApp()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!task) {
|
||||||
|
return
|
||||||
|
}
|
||||||
switch (task.message) {
|
switch (task.message) {
|
||||||
case 'invalid_code':
|
case 'invalid_code':
|
||||||
message.error('Неверный код')
|
message.error('Неверный код')
|
||||||
|
@ -26,7 +29,7 @@ const Engine = () => {
|
||||||
message.success('Код принят, ищите оставшиеся')
|
message.success('Код принят, ищите оставшиеся')
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}, [task.message])
|
}, [task])
|
||||||
|
|
||||||
const [form] = Form.useForm()
|
const [form] = Form.useForm()
|
||||||
const onFinish = ({ code }) => {
|
const onFinish = ({ code }) => {
|
||||||
|
|
|
@ -16,7 +16,7 @@ const Quests = () => {
|
||||||
return (<>
|
return (<>
|
||||||
<Title>Квесты</Title>
|
<Title>Квесты</Title>
|
||||||
{games.map(item => renderItem(user, navigate, item))}
|
{games.map(item => renderItem(user, navigate, item))}
|
||||||
{!games ? (<strong>Квестов пока не анонсировано</strong>) : null}
|
{games.length === 0 ? (<strong>Квестов пока не анонсировано</strong>) : null}
|
||||||
</>)
|
</>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { useLoaderData, useNavigate } from 'react-router-dom'
|
import { useLoaderData, useNavigate } from 'react-router-dom'
|
||||||
import { Alert, Button, Card, Form, Input, InputNumber, Popconfirm, Radio, Typography, Upload } from 'antd'
|
import { Alert, Avatar, Button, Card, Form, Input, InputNumber, Popconfirm, Radio, Switch, Typography, Upload } from 'antd'
|
||||||
import { UploadOutlined, PlusOutlined, CloseOutlined } from '@ant-design/icons'
|
import { UploadOutlined, PlusOutlined, CloseOutlined } from '@ant-design/icons'
|
||||||
import { ajax } from '../../utils/fetch'
|
import { ajax } from '../../utils/fetch'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
@ -30,7 +30,11 @@ const Quest = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const onFinish = (values) => {
|
const onFinish = (values) => {
|
||||||
ajax('/api/games', {
|
let url = '/api/admin/games'
|
||||||
|
if (quest.id) {
|
||||||
|
url = `/api/admin/games/${quest.id}`
|
||||||
|
}
|
||||||
|
ajax(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
Accept: 'application/json',
|
Accept: 'application/json',
|
||||||
|
@ -38,7 +42,7 @@ const Quest = () => {
|
||||||
},
|
},
|
||||||
body: JSON.stringify(values)
|
body: JSON.stringify(values)
|
||||||
})
|
})
|
||||||
.then(g => navigate(`/quest/${g.id}/edit`))
|
.then(g => navigate(`/admin/quests/${g.id}/`))
|
||||||
.catch(({ message }) => setError('Ошибка создания'))
|
.catch(({ message }) => setError('Ошибка создания'))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,6 +68,9 @@ const Quest = () => {
|
||||||
Сохранить квест
|
Сохранить квест
|
||||||
</Button>
|
</Button>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item label='Опубликован?' name='visible'>
|
||||||
|
<Switch />
|
||||||
|
</Form.Item>
|
||||||
<Form.Item label='Название' name='title'>
|
<Form.Item label='Название' name='title'>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
@ -75,6 +82,7 @@ const Quest = () => {
|
||||||
label='Иконка'
|
label='Иконка'
|
||||||
getValueFromEvent={normFile}
|
getValueFromEvent={normFile}
|
||||||
>
|
>
|
||||||
|
{quest.icon ? <Avatar src={`/api/file/${quest.icon}`} /> : null}
|
||||||
<Upload name='file' action='/api/file/upload' listType='picture' maxCount={1}>
|
<Upload name='file' action='/api/file/upload' listType='picture' maxCount={1}>
|
||||||
<Button icon={<UploadOutlined />}>Загрузка</Button>
|
<Button icon={<UploadOutlined />}>Загрузка</Button>
|
||||||
</Upload>
|
</Upload>
|
||||||
|
@ -108,7 +116,7 @@ const Quest = () => {
|
||||||
// eslint-disable-next-line react/display-name
|
// eslint-disable-next-line react/display-name
|
||||||
const renderTaskForm = remove => task => (
|
const renderTaskForm = remove => task => (
|
||||||
<Card
|
<Card
|
||||||
key={task.key}
|
key={task.id}
|
||||||
title={`Уровень ${task.key}`}
|
title={`Уровень ${task.key}`}
|
||||||
style={{ marginBottom: 8 }}
|
style={{ marginBottom: 8 }}
|
||||||
actions={[
|
actions={[
|
||||||
|
@ -125,6 +133,9 @@ const renderTaskForm = remove => task => (
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
<Form.Item name={[task.name, 'id']} hidden>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
<Form.Item name={[task.name, 'title']} label='Название уровня' help='ВИДНО игрокам'>
|
<Form.Item name={[task.name, 'title']} label='Название уровня' help='ВИДНО игрокам'>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
@ -152,19 +163,22 @@ const renderCodeForm = remove => code => (
|
||||||
key={code.key}
|
key={code.key}
|
||||||
style={{ marginBottom: 8 }}
|
style={{ marginBottom: 8 }}
|
||||||
actions={[
|
actions={[
|
||||||
<Popconfirm
|
// <Popconfirm
|
||||||
key='delete'
|
// key='delete'
|
||||||
title='Удалить код?'
|
// title='Удалить код?'
|
||||||
onConfirm={() => remove(code.name)}
|
// onConfirm={() => remove(code.name)}
|
||||||
okText='Да'
|
// okText='Да'
|
||||||
cancelText='Нет'
|
// cancelText='Нет'
|
||||||
>
|
// >
|
||||||
<Button danger>
|
<Button key="delete" danger onClick={() => remove(code.name)}>
|
||||||
<CloseOutlined/> Удалить код
|
<CloseOutlined/> Удалить код
|
||||||
</Button>
|
</Button>
|
||||||
</Popconfirm>
|
// </Popconfirm>
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
<Form.Item name={[code.name, 'id']} hidden>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
<Form.Item name={[code.name, 'code']} label='Код'>
|
<Form.Item name={[code.name, 'code']} label='Код'>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
40
frontend/src/pages/admin/Quests.jsx
Normal file
40
frontend/src/pages/admin/Quests.jsx
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import { Table, Typography } from 'antd'
|
||||||
|
import { Link, useLoaderData } from 'react-router-dom'
|
||||||
|
|
||||||
|
const { Title } = Typography
|
||||||
|
|
||||||
|
const Quests = () => {
|
||||||
|
const quests = useLoaderData()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Title>Управление своими квестами</Title>
|
||||||
|
<Link to="/admin/quests/new">Создать новый квест</Link>
|
||||||
|
<Table
|
||||||
|
dataSource={quests}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
title: 'UUID',
|
||||||
|
dataIndex: 'id',
|
||||||
|
key: 'id',
|
||||||
|
render: uid => <Link to={`/admin/quests/${uid}`}>${uid}</Link>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Название',
|
||||||
|
dataIndex: 'title',
|
||||||
|
key: 'title'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Тип',
|
||||||
|
dataIndex: 'type',
|
||||||
|
key: 'type',
|
||||||
|
render: type => type === 'virtual' ? 'Виртуальный' : 'Полевой'
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Quests
|
|
@ -32,16 +32,32 @@ func (a *Admin) CreateGame(ctx echo.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx.JSON(http.StatusCreated, api.GameResponse{
|
tasks := make([]api.TaskEdit, 0, len(game.Tasks))
|
||||||
Authors: make([]api.UserView, 0, len(game.Authors)),
|
|
||||||
CreatedAt: game.CreatedAt.Format(time.RFC3339),
|
for _, t := range game.Tasks {
|
||||||
|
codes := make([]api.CodeEdit, 0, len(t.Codes))
|
||||||
|
for _, c := range t.Codes {
|
||||||
|
codes = append(codes, api.CodeEdit{
|
||||||
|
Code: c.Code,
|
||||||
|
Description: c.Description,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
tasks = append(tasks, api.TaskEdit{
|
||||||
|
Codes: codes,
|
||||||
|
Text: t.Text,
|
||||||
|
Title: t.Title,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.JSON(http.StatusOK, api.GameAdminResponse{
|
||||||
Description: game.Description,
|
Description: game.Description,
|
||||||
Icon: game.IconID,
|
Icon: game.IconID,
|
||||||
Id: game.ID,
|
Id: &game.ID,
|
||||||
Points: game.Points,
|
Points: game.Points,
|
||||||
TaskCount: len(game.Tasks),
|
Tasks: tasks,
|
||||||
Title: game.Title,
|
Title: game.Title,
|
||||||
Type: api.MapGameTypeReverse(game.Type),
|
Type: api.MapGameTypeReverse(game.Type),
|
||||||
|
Visible: game.Visible,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,37 +65,149 @@ func (a *Admin) CreateGame(ctx echo.Context) error {
|
||||||
func (a *Admin) EditGame(ctx echo.Context, uid uuid.UUID) error {
|
func (a *Admin) EditGame(ctx echo.Context, uid uuid.UUID) error {
|
||||||
user := contextlib.GetUser(ctx)
|
user := contextlib.GetUser(ctx)
|
||||||
req := &api.GameEditRequest{}
|
req := &api.GameEditRequest{}
|
||||||
if err := ctx.Bind(req); err != nil {
|
|
||||||
|
game, err := a.GameService.GetByID(ctx.Request().Context(), uid)
|
||||||
|
if err != nil {
|
||||||
|
return echo.ErrNotFound
|
||||||
|
}
|
||||||
|
if user.Role != models.RoleAdmin {
|
||||||
|
isAuthor := false
|
||||||
|
for _, u := range game.Authors {
|
||||||
|
if u.ID == user.ID {
|
||||||
|
isAuthor = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !isAuthor {
|
||||||
|
return echo.ErrForbidden
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err = ctx.Bind(req); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
game = a.mapCreateGameRequest(req, user)
|
||||||
|
|
||||||
game := a.mapCreateGameRequest(req, user)
|
|
||||||
|
|
||||||
var err error
|
|
||||||
game, err = a.GameService.UpdateGame(ctx.Request().Context(), uid, game)
|
game, err = a.GameService.UpdateGame(ctx.Request().Context(), uid, game)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx.JSON(http.StatusCreated, api.GameResponse{
|
tasks := make([]api.TaskEdit, 0, len(game.Tasks))
|
||||||
Authors: make([]api.UserView, 0, len(game.Authors)),
|
|
||||||
CreatedAt: game.CreatedAt.Format(time.RFC3339),
|
for _, t := range game.Tasks {
|
||||||
|
t := t
|
||||||
|
codes := make([]api.CodeEdit, 0, len(t.Codes))
|
||||||
|
for _, c := range t.Codes {
|
||||||
|
codes = append(codes, api.CodeEdit{
|
||||||
|
Id: &c.ID,
|
||||||
|
Code: c.Code,
|
||||||
|
Description: c.Description,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
tasks = append(tasks, api.TaskEdit{
|
||||||
|
Id: &t.ID,
|
||||||
|
Codes: codes,
|
||||||
|
Text: t.Text,
|
||||||
|
Title: t.Title,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.JSON(http.StatusOK, api.GameAdminResponse{
|
||||||
Description: game.Description,
|
Description: game.Description,
|
||||||
Icon: game.IconID,
|
Icon: game.IconID,
|
||||||
Id: game.ID,
|
Id: &game.ID,
|
||||||
Points: game.Points,
|
Points: game.Points,
|
||||||
TaskCount: len(game.Tasks),
|
Tasks: tasks,
|
||||||
Title: game.Title,
|
Title: game.Title,
|
||||||
Type: api.MapGameTypeReverse(game.Type),
|
Type: api.MapGameTypeReverse(game.Type),
|
||||||
|
Visible: game.Visible,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// (GET /games/{uid})
|
||||||
|
func (a *Admin) GetGameByAdmin(ctx echo.Context, uid uuid.UUID) error {
|
||||||
|
user := contextlib.GetUser(ctx)
|
||||||
|
|
||||||
|
game, err := a.GameService.GetByID(ctx.Request().Context(), uid)
|
||||||
|
if err != nil {
|
||||||
|
return echo.ErrNotFound
|
||||||
|
}
|
||||||
|
if user.Role != models.RoleAdmin {
|
||||||
|
isAuthor := false
|
||||||
|
for _, u := range game.Authors {
|
||||||
|
if u.ID == user.ID {
|
||||||
|
isAuthor = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !isAuthor {
|
||||||
|
return echo.ErrForbidden
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks := make([]api.TaskEdit, 0, len(game.Tasks))
|
||||||
|
|
||||||
|
for _, t := range game.Tasks {
|
||||||
|
t := t
|
||||||
|
codes := make([]api.CodeEdit, 0, len(t.Codes))
|
||||||
|
for _, c := range t.Codes {
|
||||||
|
codes = append(codes, api.CodeEdit{
|
||||||
|
Id: &c.ID,
|
||||||
|
Code: c.Code,
|
||||||
|
Description: c.Description,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
tasks = append(tasks, api.TaskEdit{
|
||||||
|
Id: &t.ID,
|
||||||
|
Codes: codes,
|
||||||
|
Text: t.Text,
|
||||||
|
Title: t.Title,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.JSON(http.StatusOK, api.GameAdminResponse{
|
||||||
|
Description: game.Description,
|
||||||
|
Icon: game.IconID,
|
||||||
|
Id: &game.ID,
|
||||||
|
Points: game.Points,
|
||||||
|
Tasks: tasks,
|
||||||
|
Title: game.Title,
|
||||||
|
Type: api.MapGameTypeReverse(game.Type),
|
||||||
|
Visible: game.Visible,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Admin) ListGamesByAdmin(ctx echo.Context) error {
|
||||||
|
user := contextlib.GetUser(ctx)
|
||||||
|
|
||||||
|
games, err := a.GameService.ListByAuthor(ctx.Request().Context(), user)
|
||||||
|
if err != nil {
|
||||||
|
return echo.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := make(api.GameListResponse, 0, len(games))
|
||||||
|
for _, game := range games {
|
||||||
|
gv := api.GameView{
|
||||||
|
Id: game.ID,
|
||||||
|
Title: game.Title,
|
||||||
|
Type: api.MapGameTypeReverse(game.Type),
|
||||||
|
Points: game.Points,
|
||||||
|
TaskCount: len(game.Tasks),
|
||||||
|
CreatedAt: game.CreatedAt.Format(time.RFC3339),
|
||||||
|
Icon: game.IconID,
|
||||||
|
}
|
||||||
|
resp = append(resp, gv)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.JSON(http.StatusOK, resp)
|
||||||
|
}
|
||||||
|
|
||||||
func (*Admin) mapCreateGameRequest(req *api.GameEditRequest, user *models.User) *models.Game {
|
func (*Admin) mapCreateGameRequest(req *api.GameEditRequest, user *models.User) *models.Game {
|
||||||
game := &models.Game{
|
game := &models.Game{
|
||||||
Model: models.Model{
|
Model: models.Model{
|
||||||
ID: uuid.New(),
|
ID: uuid.New(),
|
||||||
},
|
},
|
||||||
Visible: false,
|
Visible: req.Visible,
|
||||||
Title: req.Title,
|
Title: req.Title,
|
||||||
Description: req.Description,
|
Description: req.Description,
|
||||||
Authors: []*models.User{
|
Authors: []*models.User{
|
||||||
|
@ -91,30 +219,28 @@ func (*Admin) mapCreateGameRequest(req *api.GameEditRequest, user *models.User)
|
||||||
IconID: req.Icon,
|
IconID: req.Icon,
|
||||||
}
|
}
|
||||||
for order, te := range req.Tasks {
|
for order, te := range req.Tasks {
|
||||||
|
if te.Id == nil {
|
||||||
|
u := uuid.New()
|
||||||
|
te.Id = &u
|
||||||
|
}
|
||||||
task := &models.Task{
|
task := &models.Task{
|
||||||
Model: models.Model{
|
Model: models.Model{
|
||||||
ID: uuid.New(),
|
ID: *te.Id,
|
||||||
},
|
},
|
||||||
Title: te.Title,
|
Title: te.Title,
|
||||||
Text: te.Text,
|
Text: te.Text,
|
||||||
MaxTime: 0,
|
|
||||||
// Solutions: make([]*models.Solution, 0, len(te.Solutions)),
|
|
||||||
Codes: make([]*models.Code, 0, len(te.Codes)),
|
Codes: make([]*models.Code, 0, len(te.Codes)),
|
||||||
TaskOrder: uint(order),
|
TaskOrder: uint(order),
|
||||||
}
|
}
|
||||||
// for _, s := range te.Solutions {
|
|
||||||
// task.Solutions = append(task.Solutions, &models.Solution{
|
|
||||||
// Model: models.Model{
|
|
||||||
// ID: uuid.New(),
|
|
||||||
// },
|
|
||||||
// After: s.After,
|
|
||||||
// Text: s.Text,
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
for _, ce := range te.Codes {
|
for _, ce := range te.Codes {
|
||||||
|
if ce.Id == nil {
|
||||||
|
u := uuid.New()
|
||||||
|
ce.Id = &u
|
||||||
|
}
|
||||||
task.Codes = append(task.Codes, &models.Code{
|
task.Codes = append(task.Codes, &models.Code{
|
||||||
Model: models.Model{
|
Model: models.Model{
|
||||||
ID: uuid.New(),
|
ID: *ce.Id,
|
||||||
},
|
},
|
||||||
Code: ce.Code,
|
Code: ce.Code,
|
||||||
Description: ce.Description,
|
Description: ce.Description,
|
||||||
|
|
|
@ -41,8 +41,7 @@ func (gs *Game) List(ctx context.Context) ([]*models.Game, error) {
|
||||||
Order("created_at DESC").
|
Order("created_at DESC").
|
||||||
Preload("Tasks").
|
Preload("Tasks").
|
||||||
Preload("Authors").
|
Preload("Authors").
|
||||||
Find(&games).
|
Find(&games, "visible = true").
|
||||||
Limit(20).
|
|
||||||
Error
|
Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,14 +53,17 @@ func (gs *Game) GetTaskID(ctx context.Context, id uuid.UUID) (*models.Task, erro
|
||||||
|
|
||||||
func (gs *Game) ListByAuthor(ctx context.Context, author *models.User) ([]*models.Game, error) {
|
func (gs *Game) ListByAuthor(ctx context.Context, author *models.User) ([]*models.Game, error) {
|
||||||
games := make([]*models.Game, 0)
|
games := make([]*models.Game, 0)
|
||||||
|
model := gs.DB.
|
||||||
return games, gs.DB.
|
|
||||||
WithContext(ctx).
|
WithContext(ctx).
|
||||||
Model(&models.Game{}).
|
Model(&models.Game{})
|
||||||
Preload("Authors", gs.DB.Where("id = ?", author.ID)).
|
if author.Role == models.RoleCreator {
|
||||||
|
model.Preload("Authors", gs.DB.Where("id = ?", author.ID))
|
||||||
|
}
|
||||||
|
|
||||||
|
return games,
|
||||||
|
model.
|
||||||
Order("created_at DESC").
|
Order("created_at DESC").
|
||||||
Find(&games).
|
Find(&games).
|
||||||
Limit(20).
|
|
||||||
Error
|
Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,8 +77,10 @@ func (gs *Game) CreateGame(ctx context.Context, game *models.Game) (*models.Game
|
||||||
func (gs *Game) UpdateGame(ctx context.Context, uid uuid.UUID, game *models.Game) (*models.Game, error) {
|
func (gs *Game) UpdateGame(ctx context.Context, uid uuid.UUID, game *models.Game) (*models.Game, error) {
|
||||||
game.ID = uid
|
game.ID = uid
|
||||||
|
|
||||||
return game, gs.DB.
|
db := gs.DB
|
||||||
Session(&gorm.Session{FullSaveAssociations: true}).
|
|
||||||
|
return game, db.Debug().
|
||||||
|
Session(&gorm.Session{FullSaveAssociations: true}).Omit("created_at").
|
||||||
Save(game).
|
Save(game).
|
||||||
Error
|
Error
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue