devcontainer :)
This commit is contained in:
parent
fac2df0bc1
commit
7618d85264
27 changed files with 313 additions and 434 deletions
1
.devcontainer/Dockerfile
Normal file
1
.devcontainer/Dockerfile
Normal file
|
@ -0,0 +1 @@
|
||||||
|
FROM gitrepo.ru/neonxp/devcontainer:latest
|
26
.devcontainer/devcontainer.json
Normal file
26
.devcontainer/devcontainer.json
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"name": "Go",
|
||||||
|
"dockerComposeFile": "./docker-compose.yml",
|
||||||
|
"service": "app",
|
||||||
|
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
|
||||||
|
"forwardPorts": [5432, 5173, 8000],
|
||||||
|
"remoteUser": "vscode",
|
||||||
|
"customizations": {
|
||||||
|
"vscode": {
|
||||||
|
"extensions": [
|
||||||
|
"ms-vscode.makefile-tools",
|
||||||
|
"redhat.vscode-yaml",
|
||||||
|
"humao.rest-client",
|
||||||
|
"mtxr.sqltools",
|
||||||
|
"mtxr.sqltools-driver-pg",
|
||||||
|
"codezombiech.gitignore",
|
||||||
|
"ms-azuretools.vscode-docker"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"postCreateCommand": "go mod download",
|
||||||
|
"features": {
|
||||||
|
"https://gitrepo.ru/api/packages/NeonXP/generic/features/latest/devcontainer-feature-common-utils.tgz": {},
|
||||||
|
"https://gitrepo.ru/api/packages/NeonXP/generic/features/latest/devcontainer-feature-go.tgz": {}
|
||||||
|
}
|
||||||
|
}
|
24
.devcontainer/docker-compose.yml
Normal file
24
.devcontainer/docker-compose.yml
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres-data:
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
volumes:
|
||||||
|
- ../..:/workspaces:cached
|
||||||
|
command: sleep infinity
|
||||||
|
network_mode: service:db
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: postgres:15-alpine3.17
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- postgres-data:/var/lib/postgresql/data
|
||||||
|
env_file:
|
||||||
|
- .env
|
12
.github/dependabot.yml
vendored
Normal file
12
.github/dependabot.yml
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
# To get started with Dependabot version updates, you'll need to specify which
|
||||||
|
# package ecosystems to update and where the package manifests are located.
|
||||||
|
# Please see the documentation for more information:
|
||||||
|
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||||
|
# https://containers.dev/guide/dependabot
|
||||||
|
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "devcontainers"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
|
@ -10,7 +10,7 @@
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"mode": "auto",
|
"mode": "auto",
|
||||||
"program": "${workspaceFolder}/",
|
"program": "${workspaceFolder}/",
|
||||||
"envFile": "${workspaceFolder}/.env"
|
"envFile": "${workspaceFolder}/.devcontainer/.env"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
190
api/openapi.yaml
190
api/openapi.yaml
|
@ -20,7 +20,16 @@ paths:
|
||||||
post:
|
post:
|
||||||
security: []
|
security: []
|
||||||
requestBody:
|
requestBody:
|
||||||
$ref: "#/components/requestBodies/login"
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
required: [email, password]
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
$ref: "#/components/responses/userResponse"
|
$ref: "#/components/responses/userResponse"
|
||||||
|
@ -30,7 +39,20 @@ paths:
|
||||||
post:
|
post:
|
||||||
security: []
|
security: []
|
||||||
requestBody:
|
requestBody:
|
||||||
$ref: "#/components/requestBodies/register"
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
password2:
|
||||||
|
type: string
|
||||||
|
required: [username, email, password, password2]
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
$ref: "#/components/responses/userResponse"
|
$ref: "#/components/responses/userResponse"
|
||||||
|
@ -50,7 +72,6 @@ paths:
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
$ref: "#/components/responses/gameListResponse"
|
$ref: "#/components/responses/gameListResponse"
|
||||||
|
|
||||||
/engine/{uid}:
|
/engine/{uid}:
|
||||||
get:
|
get:
|
||||||
operationId: gameEngine
|
operationId: gameEngine
|
||||||
|
@ -75,27 +96,18 @@ paths:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
requestBody:
|
requestBody:
|
||||||
$ref: "#/components/requestBodies/enterCodeRequest"
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- code
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
$ref: "#/components/responses/taskResponse"
|
$ref: "#/components/responses/taskResponse"
|
||||||
/file/upload:
|
|
||||||
post:
|
|
||||||
operationId: uploadFile
|
|
||||||
security:
|
|
||||||
- cookieAuth: [creator, admin]
|
|
||||||
requestBody:
|
|
||||||
content:
|
|
||||||
multipart/form-data:
|
|
||||||
schema:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
file:
|
|
||||||
type: string
|
|
||||||
format: binary
|
|
||||||
responses:
|
|
||||||
200:
|
|
||||||
$ref: "#/components/responses/uploadResponse"
|
|
||||||
/file/{uid}:
|
/file/{uid}:
|
||||||
get:
|
get:
|
||||||
operationId: getFile
|
operationId: getFile
|
||||||
|
@ -114,24 +126,46 @@ paths:
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
format: binary
|
format: binary
|
||||||
|
|
||||||
|
# Admin routes
|
||||||
|
/admin/file/upload:
|
||||||
|
post:
|
||||||
|
operationId: adminUploadFile
|
||||||
|
security:
|
||||||
|
- cookieAuth: [creator, admin]
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
multipart/form-data:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
file:
|
||||||
|
type: string
|
||||||
|
format: binary
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
$ref: "#/components/responses/uploadResponse"
|
||||||
/admin/games:
|
/admin/games:
|
||||||
get:
|
get:
|
||||||
operationId: listGamesByAdmin
|
operationId: adminListGames
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
$ref: "#/components/responses/gameListResponse"
|
$ref: "#/components/responses/gameListResponse"
|
||||||
post:
|
post:
|
||||||
operationId: createGame
|
operationId: adminEditGame
|
||||||
security:
|
security:
|
||||||
- cookieAuth: [creator, admin]
|
- cookieAuth: [creator, admin]
|
||||||
requestBody:
|
requestBody:
|
||||||
$ref: "#/components/requestBodies/gameEditRequest"
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/gameEdit"
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
$ref: "#/components/responses/gameAdminResponse"
|
$ref: "#/components/responses/gameAdminResponse"
|
||||||
/admin/games/{uid}:
|
/admin/games/{uid}:
|
||||||
get:
|
get:
|
||||||
operationId: getGameByAdmin
|
operationId: adminGetGame
|
||||||
parameters:
|
parameters:
|
||||||
- name: uid
|
- name: uid
|
||||||
in: path
|
in: path
|
||||||
|
@ -144,22 +178,7 @@ paths:
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
$ref: "#/components/responses/gameAdminResponse"
|
$ref: "#/components/responses/gameAdminResponse"
|
||||||
post:
|
|
||||||
operationId: editGame
|
|
||||||
parameters:
|
|
||||||
- name: uid
|
|
||||||
in: path
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
format: uuid
|
|
||||||
security:
|
|
||||||
- cookieAuth: [creator, admin]
|
|
||||||
requestBody:
|
|
||||||
$ref: "#/components/requestBodies/gameEditRequest"
|
|
||||||
responses:
|
|
||||||
200:
|
|
||||||
$ref: "#/components/responses/gameAdminResponse"
|
|
||||||
components:
|
components:
|
||||||
schemas:
|
schemas:
|
||||||
userView:
|
userView:
|
||||||
|
@ -254,15 +273,10 @@ components:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: "#/components/schemas/codeView"
|
$ref: "#/components/schemas/codeView"
|
||||||
# solutions:
|
|
||||||
# type: array
|
|
||||||
# items:
|
|
||||||
# $ref: '#/components/schemas/solutionView'
|
|
||||||
required:
|
required:
|
||||||
- title
|
- title
|
||||||
- text
|
- text
|
||||||
- codes
|
- codes
|
||||||
# - solutions
|
|
||||||
codeView:
|
codeView:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -272,15 +286,6 @@ components:
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
- description
|
- description
|
||||||
solutionView:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
text:
|
|
||||||
type: string
|
|
||||||
after:
|
|
||||||
type: integer
|
|
||||||
required:
|
|
||||||
- after
|
|
||||||
gameEdit:
|
gameEdit:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -326,15 +331,10 @@ components:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: "#/components/schemas/codeEdit"
|
$ref: "#/components/schemas/codeEdit"
|
||||||
# solutions:
|
|
||||||
# type: array
|
|
||||||
# items:
|
|
||||||
# $ref: '#/components/schemas/solutionEdit'
|
|
||||||
required:
|
required:
|
||||||
- title
|
- title
|
||||||
- text
|
- text
|
||||||
- codes
|
- codes
|
||||||
# - solutions
|
|
||||||
codeEdit:
|
codeEdit:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -348,78 +348,22 @@ components:
|
||||||
required:
|
required:
|
||||||
- description
|
- description
|
||||||
- code
|
- code
|
||||||
# solutionEdit:
|
|
||||||
# type: object
|
|
||||||
# properties:
|
|
||||||
# text:
|
|
||||||
# type: string
|
|
||||||
# after:
|
|
||||||
# type: integer
|
|
||||||
# required:
|
|
||||||
# - after
|
|
||||||
# - text
|
|
||||||
gameType:
|
gameType:
|
||||||
type: string
|
type: string
|
||||||
enum:
|
enum:
|
||||||
- virtual
|
- virtual
|
||||||
- city
|
- city
|
||||||
requestBodies:
|
|
||||||
login:
|
|
||||||
required: true
|
|
||||||
content:
|
|
||||||
"application/json":
|
|
||||||
schema:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
email:
|
|
||||||
type: string
|
|
||||||
password:
|
|
||||||
type: string
|
|
||||||
required: [email, password]
|
|
||||||
register:
|
|
||||||
required: true
|
|
||||||
content:
|
|
||||||
"application/json":
|
|
||||||
schema:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
username:
|
|
||||||
type: string
|
|
||||||
email:
|
|
||||||
type: string
|
|
||||||
password:
|
|
||||||
type: string
|
|
||||||
password2:
|
|
||||||
type: string
|
|
||||||
required: [username, email, password, password2]
|
|
||||||
gameEditRequest:
|
|
||||||
required: true
|
|
||||||
content:
|
|
||||||
"application/json":
|
|
||||||
schema:
|
|
||||||
$ref: "#/components/schemas/gameEdit"
|
|
||||||
enterCodeRequest:
|
|
||||||
required: true
|
|
||||||
content:
|
|
||||||
"application/json":
|
|
||||||
schema:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
code:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- 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:
|
||||||
|
@ -431,7 +375,7 @@ components:
|
||||||
gameListResponse:
|
gameListResponse:
|
||||||
description: ""
|
description: ""
|
||||||
content:
|
content:
|
||||||
"application/json":
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
|
@ -439,25 +383,25 @@ components:
|
||||||
gameResponse:
|
gameResponse:
|
||||||
description: ""
|
description: ""
|
||||||
content:
|
content:
|
||||||
"application/json":
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/gameView"
|
$ref: "#/components/schemas/gameView"
|
||||||
gameAdminResponse:
|
gameAdminResponse:
|
||||||
description: ""
|
description: ""
|
||||||
content:
|
content:
|
||||||
"application/json":
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/gameEdit"
|
$ref: "#/components/schemas/gameEdit"
|
||||||
taskResponse:
|
taskResponse:
|
||||||
description: ""
|
description: ""
|
||||||
content:
|
content:
|
||||||
"application/json":
|
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:
|
||||||
|
|
123
api/server.go
123
api/server.go
|
@ -22,17 +22,17 @@ import (
|
||||||
// ServerInterface represents all server handlers.
|
// ServerInterface represents all server handlers.
|
||||||
type ServerInterface interface {
|
type ServerInterface interface {
|
||||||
|
|
||||||
|
// (POST /admin/file/upload)
|
||||||
|
AdminUploadFile(ctx echo.Context) error
|
||||||
|
|
||||||
// (GET /admin/games)
|
// (GET /admin/games)
|
||||||
ListGamesByAdmin(ctx echo.Context) error
|
AdminListGames(ctx echo.Context) error
|
||||||
|
|
||||||
// (POST /admin/games)
|
// (POST /admin/games)
|
||||||
CreateGame(ctx echo.Context) error
|
AdminEditGame(ctx echo.Context) error
|
||||||
|
|
||||||
// (GET /admin/games/{uid})
|
// (GET /admin/games/{uid})
|
||||||
GetGameByAdmin(ctx echo.Context, uid openapi_types.UUID) error
|
AdminGetGame(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
|
||||||
|
@ -40,9 +40,6 @@ type ServerInterface interface {
|
||||||
// (POST /engine/{uid}/code)
|
// (POST /engine/{uid}/code)
|
||||||
EnterCode(ctx echo.Context, uid openapi_types.UUID) error
|
EnterCode(ctx echo.Context, uid openapi_types.UUID) error
|
||||||
|
|
||||||
// (POST /file/upload)
|
|
||||||
UploadFile(ctx echo.Context) error
|
|
||||||
|
|
||||||
// (GET /file/{uid})
|
// (GET /file/{uid})
|
||||||
GetFile(ctx echo.Context, uid openapi_types.UUID) error
|
GetFile(ctx echo.Context, uid openapi_types.UUID) error
|
||||||
|
|
||||||
|
@ -67,30 +64,41 @@ type ServerInterfaceWrapper struct {
|
||||||
Handler ServerInterface
|
Handler ServerInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListGamesByAdmin converts echo context to params.
|
// AdminUploadFile converts echo context to params.
|
||||||
func (w *ServerInterfaceWrapper) ListGamesByAdmin(ctx echo.Context) error {
|
func (w *ServerInterfaceWrapper) AdminUploadFile(ctx echo.Context) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
ctx.Set(CookieAuthScopes, []string{"creator", "admin"})
|
||||||
|
|
||||||
|
// Invoke the callback with all the unmarshaled arguments
|
||||||
|
err = w.Handler.AdminUploadFile(ctx)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdminListGames converts echo context to params.
|
||||||
|
func (w *ServerInterfaceWrapper) AdminListGames(ctx echo.Context) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
ctx.Set(CookieAuthScopes, []string{})
|
ctx.Set(CookieAuthScopes, []string{})
|
||||||
|
|
||||||
// Invoke the callback with all the unmarshaled arguments
|
// Invoke the callback with all the unmarshaled arguments
|
||||||
err = w.Handler.ListGamesByAdmin(ctx)
|
err = w.Handler.AdminListGames(ctx)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateGame converts echo context to params.
|
// AdminEditGame converts echo context to params.
|
||||||
func (w *ServerInterfaceWrapper) CreateGame(ctx echo.Context) error {
|
func (w *ServerInterfaceWrapper) AdminEditGame(ctx echo.Context) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
ctx.Set(CookieAuthScopes, []string{"creator", "admin"})
|
ctx.Set(CookieAuthScopes, []string{"creator", "admin"})
|
||||||
|
|
||||||
// Invoke the callback with all the unmarshaled arguments
|
// Invoke the callback with all the unmarshaled arguments
|
||||||
err = w.Handler.CreateGame(ctx)
|
err = w.Handler.AdminEditGame(ctx)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetGameByAdmin converts echo context to params.
|
// AdminGetGame converts echo context to params.
|
||||||
func (w *ServerInterfaceWrapper) GetGameByAdmin(ctx echo.Context) error {
|
func (w *ServerInterfaceWrapper) AdminGetGame(ctx echo.Context) error {
|
||||||
var err error
|
var err error
|
||||||
// ------------- Path parameter "uid" -------------
|
// ------------- Path parameter "uid" -------------
|
||||||
var uid openapi_types.UUID
|
var uid openapi_types.UUID
|
||||||
|
@ -103,25 +111,7 @@ func (w *ServerInterfaceWrapper) GetGameByAdmin(ctx echo.Context) error {
|
||||||
ctx.Set(CookieAuthScopes, []string{"creator", "admin"})
|
ctx.Set(CookieAuthScopes, []string{"creator", "admin"})
|
||||||
|
|
||||||
// Invoke the callback with all the unmarshaled arguments
|
// Invoke the callback with all the unmarshaled arguments
|
||||||
err = w.Handler.GetGameByAdmin(ctx, uid)
|
err = w.Handler.AdminGetGame(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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,17 +151,6 @@ func (w *ServerInterfaceWrapper) EnterCode(ctx echo.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// UploadFile converts echo context to params.
|
|
||||||
func (w *ServerInterfaceWrapper) UploadFile(ctx echo.Context) error {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
ctx.Set(CookieAuthScopes, []string{"creator", "admin"})
|
|
||||||
|
|
||||||
// Invoke the callback with all the unmarshaled arguments
|
|
||||||
err = w.Handler.UploadFile(ctx)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFile converts echo context to params.
|
// GetFile converts echo context to params.
|
||||||
func (w *ServerInterfaceWrapper) GetFile(ctx echo.Context) error {
|
func (w *ServerInterfaceWrapper) GetFile(ctx echo.Context) error {
|
||||||
var err error
|
var err error
|
||||||
|
@ -269,13 +248,12 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
|
||||||
Handler: si,
|
Handler: si,
|
||||||
}
|
}
|
||||||
|
|
||||||
router.GET(baseURL+"/admin/games", wrapper.ListGamesByAdmin)
|
router.POST(baseURL+"/admin/file/upload", wrapper.AdminUploadFile)
|
||||||
router.POST(baseURL+"/admin/games", wrapper.CreateGame)
|
router.GET(baseURL+"/admin/games", wrapper.AdminListGames)
|
||||||
router.GET(baseURL+"/admin/games/:uid", wrapper.GetGameByAdmin)
|
router.POST(baseURL+"/admin/games", wrapper.AdminEditGame)
|
||||||
router.POST(baseURL+"/admin/games/:uid", wrapper.EditGame)
|
router.GET(baseURL+"/admin/games/:uid", wrapper.AdminGetGame)
|
||||||
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.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.GET(baseURL+"/user", wrapper.GetUser)
|
router.GET(baseURL+"/user", wrapper.GetUser)
|
||||||
|
@ -288,27 +266,26 @@ 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{
|
||||||
|
|
||||||
"H4sIAAAAAAAC/9RZzW7kNgx+lULt0RvP/px8ywZtUDQo2jTbSzAIFJuZaGNLrkRnMwj87gUl/47lseMa",
|
"H4sIAAAAAAAC/8xY32+kNhD+Vyq3j1zY+/HEWxqlUdWoaq+5vkSryIHJxhewqT3OZRXxv1djAwuLWcge",
|
||||||
"2fQUR6JF8uNHmuI8s1hluZIg0bDomWn4pwCDn1UiwC6ARNBnKoFLt0NrsZII0j7yPE9FzFEoGX41StKa",
|
"Su4pGzx4Zr75vmHsZ5aqolQSJBqWPDMNplTSgPsHtFb6c/2EHqRKIkikn7wsc5FyFErGX42S9Myk91Bw",
|
||||||
"ie8h4/SUa5WDxuqoWCVAf3GfA4uYQS3kjpVlYLUKDQmLrp3UNqil1O1XiJGVfTHUBZQB2/EMfk4ELrHt",
|
"+lVqVYJG4fdJVeZex20JLGFCImxAsypiBRjDN91Fg1rIDauqiGn4zwoNGUuu/RY7+3XU2Kvbr5Aiq+iF",
|
||||||
"Jw13LGI/hi0Aods1YX3uiNpU7YT8D0BAxkXqQSJgOTfmm9LJNEzujM4bMyHTsBMGQb+2+e3mB+9uYUBL",
|
"DEyqRUkxsYTR/htewGlWCHlUFr9ouGMJ+zneYRT7VRPTzueZOOj5Uhg8yrFAKMycCP4V8I281WBwrfl2",
|
||||||
"ns0gSCMZDEHoapkFiF0xuZKm8k1rpS+rlfW4LiTCDjQ5moExfDc3EVp5vzsJmFiLnGxiEWNVTpwmmZCL",
|
"LCLk5mFxGGhTH0TYqS1zxbMFOGStyOjvndIFR5b4B9EEbZzRXLJYA3pxgGjTcYCqqN6m1Ynj1JSCmmz3",
|
||||||
"vJifFX7NF8LgIsUCITNzLPhbwDfSVoHBteb7MYuQm4fVYaBDnRF+pUWeKp6swKGiEDaT7pTOOLLILQRT",
|
"NgysHwNad8/Iux5C6BdcZktFeyCMkP9WgQP/k6CkfmEClpnoRaxUom6fww5HAjGzNU3WvqvsazpiKDAP",
|
||||||
"6UFCc8lCubQ6QHToOEBlUB3T5Inl1NyvxcGBnv0loHXPDMY+QW7DeraWtUfM8OlvMnCgfxKU2G1MwDIT",
|
"w+ofTHeLK7KrIvYojLjt7XWrVA5cDmrQWDbeoz1yuNebJFsgaoTHinZVhwvSFt6JRstz4prAbee1XYZt",
|
||||||
"vYDlSlQdw7DCUYKY2TlN0q6qHOZ0wFBg6ofVLUxXiyuSKwP2KIy47Z11q1QKXA5iUEvW2oMDctjXaycb",
|
"rxvUmlu8V3o+wjs9DhFONXCE7BSPk9ors+pMWYkjywtRZY8NPs5DRGgJsAuwC2vUlusARVoNBPvK/Eq3",
|
||||||
"ICqEx4J2VZkLssicEo0Fp09XLHDfea31sKl1g1jzAu+Vno9wm49DhGMNHCE5xWWp9sqsOlOFxJHtlahy",
|
"3TRQ6ZklQHgKc2EM4D3AGqzcPlEd/1jO4730ZTmPsbszazXKUw839WAl5CPPRdb8q/L2p4QnvMnhEUig",
|
||||||
"wAZn5zEiNARoDezCGjThOkKRJge8dWV+pJtq6on0zBAgPPm5MAbwAWA1VvacoLJ/zOfxWvoyn8fY3em1",
|
"RI0b8psDQlCprwZZK+QBZFBwkQdjgKfySp1ZrUHipUspKB9n9ic8TdiAFiDTkcmWkDILzHKzuZqPx6pV",
|
||||||
"6sxTDzdVYyXkI09FUv+r0uZRwhPepPAIlKBEjRvSmwKCN1NfDbImkV/QksNTfqXOCq1B4oV1yZs+Vux3",
|
"3is7QdfoUtEvTiNysJxkKnkxo3AuqtY8qqvQA6oJMlSIAeoNhHX4gSEuYgZSqwVu/yEYG72oBwGnFu8d",
|
||||||
"eJqQAS1AxiOdLSFlVujlZnM1HbdVq7QXdoKuzktFT5xaZG84599ErFWe60gHqNpIXyAGqNcQVuZ7mriA",
|
"+DRm+UdEZZcIM2BMp2sljJfiD6jnZiHvlEvW05XJvy0YYuIjaOPHtvcnq5MVgaNKkLwULGEfT96frKj/",
|
||||||
"GYgLLXD/F8FY54t6EHBa4L0Fn9ost0RUto4wA8Z0qlbEeC5+g6pvFvJOWWcdXZn8016mA/YI2ri27f3J",
|
"cbx3YcQO0vhO5BD7GdjxVBknDmKrGyZ/z1jC3AHlizP6TTjyE65g8FeVbfcG0cLmKEquMSZCvMs4BmZl",
|
||||||
"5mRD4KgcJM8Fi9jHk/cnG3vhwntrRmghDRsW7MBmBdHUdpG/JixidD84J4nPe3tJYQf3rw+bzRhxGrlw",
|
"8trjzK2QXG+Dg19gMu6dAT+sVmPkbe3ivSm/WxqWXPeLcj3kXbWmN2rIWuFsYAwrOlddNOR4cbCDk5lL",
|
||||||
"cNGwbuTKeBSe2Up87gLUTjf241o6A5DwcMBQLjW3fyPrBpJF1/0QXg9ZWm7pjS7A4XMhknIU5nOwKLcg",
|
"+kBp6CNy4Uk9XphlTpLVsQn1D7nfX4D42YqsOlyGC2hgKbnmBSDQHHZdq46ksNOcb1q7foHaQtQBaOqE",
|
||||||
"51zzDBCobbiuSEKRaynicqx/PQ46Hf9UQ739btCMhZ4CVwX+ldz/X7ML5E5ImCIWmWwF3y6perf+shw4",
|
"sn5TYEBuhIQJTAiLc2f44yLSuwPwfOslFzdHt7AcziWCPvNzwStleJzeZp1HQ1dMobuCaimk3ddggkSA",
|
||||||
"F9aXtRHa1BPPN8ubwUy2XAumO5FC6AYY4wB9sfu/iNRTUjsDhKxIUeRcY0gYvEs4emYcpLAH062QXO+9",
|
"9TfgzRg0ArJKEfCdQQ286IM9/ZkZ3Hy4L1SNyuGGXzeZ5Vo9uXQD0AGPX/yAdMSHsHuFVEXs0+rj9Ev9",
|
||||||
"F3bvNO+lXh9MZxbmisVpsgRXEH23NBmZ5qgYAd8Z1MCz/lRnOgqDgY4NYMWe45/+6ptkVvvkk0rb1x3R",
|
"a9ZOiHGuNkKOy/EvZVyol85sKbWMj88lN+ab0tm0lprhr31jMV0NEV69HOHeR2Ddw1tZnAU42Q3i/zS4",
|
||||||
"+MX1fQt40p2MlQH7tPk4/VJ/etwxMWx+MvCn1B/KWFMvrNiCkuDOL9fxdPNyT3tZtO35rQqc5TjJDez/",
|
"fWLGpikY81O99bER72LUsBEGPX8PR/m5sXxTZuwWPwRX558wAoeL1m/Xy4/NtYMTyJqasgH92LR9q3OW",
|
||||||
"NBhuMVPEMRjzQ3X0UotbG7s/iRy38rKWXBChRsvbCdLR2relqmJAP9Z1q9Api1hI3X+5Lf8NAAD//0cV",
|
"sJhOGdW6+j8AAP//fdZwRK4ZAAA=",
|
||||||
"3sY5HAAA",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSwagger returns the content of the embedded swagger specification file
|
// GetSwagger returns the content of the embedded swagger specification file
|
||||||
|
|
37
api/types.go
37
api/types.go
|
@ -132,36 +132,14 @@ type UploadResponse struct {
|
||||||
// UserResponse defines model for userResponse.
|
// UserResponse defines model for userResponse.
|
||||||
type UserResponse = UserView
|
type UserResponse = UserView
|
||||||
|
|
||||||
// EnterCodeRequest defines model for enterCodeRequest.
|
// AdminUploadFileMultipartBody defines parameters for AdminUploadFile.
|
||||||
type EnterCodeRequest struct {
|
type AdminUploadFileMultipartBody interface{}
|
||||||
Code string `json:"code"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GameEditRequest defines model for gameEditRequest.
|
|
||||||
type GameEditRequest = GameEdit
|
|
||||||
|
|
||||||
// Login defines model for login.
|
|
||||||
type Login struct {
|
|
||||||
Email string `json:"email"`
|
|
||||||
Password string `json:"password"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register defines model for register.
|
|
||||||
type Register struct {
|
|
||||||
Email string `json:"email"`
|
|
||||||
Password string `json:"password"`
|
|
||||||
Password2 string `json:"password2"`
|
|
||||||
Username string `json:"username"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// EnterCodeJSONBody defines parameters for EnterCode.
|
// EnterCodeJSONBody defines parameters for EnterCode.
|
||||||
type EnterCodeJSONBody struct {
|
type EnterCodeJSONBody struct {
|
||||||
Code string `json:"code"`
|
Code string `json:"code"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UploadFileMultipartBody defines parameters for UploadFile.
|
|
||||||
type UploadFileMultipartBody interface{}
|
|
||||||
|
|
||||||
// PostUserLoginJSONBody defines parameters for PostUserLogin.
|
// PostUserLoginJSONBody defines parameters for PostUserLogin.
|
||||||
type PostUserLoginJSONBody struct {
|
type PostUserLoginJSONBody struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
|
@ -176,18 +154,15 @@ type PostUserRegisterJSONBody struct {
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateGameJSONRequestBody defines body for CreateGame for application/json ContentType.
|
// AdminUploadFileMultipartRequestBody defines body for AdminUploadFile for multipart/form-data ContentType.
|
||||||
type CreateGameJSONRequestBody = GameEdit
|
type AdminUploadFileMultipartRequestBody AdminUploadFileMultipartBody
|
||||||
|
|
||||||
// EditGameJSONRequestBody defines body for EditGame for application/json ContentType.
|
// AdminEditGameJSONRequestBody defines body for AdminEditGame for application/json ContentType.
|
||||||
type EditGameJSONRequestBody = GameEdit
|
type AdminEditGameJSONRequestBody = GameEdit
|
||||||
|
|
||||||
// EnterCodeJSONRequestBody defines body for EnterCode for application/json ContentType.
|
// EnterCodeJSONRequestBody defines body for EnterCode for application/json ContentType.
|
||||||
type EnterCodeJSONRequestBody EnterCodeJSONBody
|
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
|
||||||
|
|
||||||
|
|
41
auth.go
Normal file
41
auth.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/getkin/kin-openapi/openapi3filter"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
oapiMiddleware "github.com/oapi-codegen/echo-middleware"
|
||||||
|
appmiddleware "gitrepo.ru/neonxp/nquest/pkg/contextlib"
|
||||||
|
|
||||||
|
"gitrepo.ru/neonxp/nquest/pkg/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
var authFunc = func(ctx context.Context, ai *openapi3filter.AuthenticationInput) error {
|
||||||
|
echoCtx := ctx.Value(oapiMiddleware.EchoContextKey).(echo.Context)
|
||||||
|
user := appmiddleware.GetUser(echoCtx)
|
||||||
|
if user != nil {
|
||||||
|
if len(ai.Scopes) > 0 {
|
||||||
|
for _, v := range ai.Scopes {
|
||||||
|
switch v {
|
||||||
|
case "user":
|
||||||
|
return nil
|
||||||
|
case "creator":
|
||||||
|
if user.HasRole(models.RoleCreator) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
case "admin":
|
||||||
|
if user.HasRole(models.RoleAdmin) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return echo.ErrForbidden
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return echo.ErrForbidden
|
||||||
|
}
|
|
@ -77,9 +77,8 @@ function Auth (props) {
|
||||||
if (!user && !baseUser) {
|
if (!user && !baseUser) {
|
||||||
return <Navigate to="/login" state={{ from: location }} replace />
|
return <Navigate to="/login" state={{ from: location }} replace />
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.role && !hasRole(props.role)) {
|
if (props.role && !hasRole(props.role)) {
|
||||||
return <Navigate to="/" replace />
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return props.children
|
return props.children
|
||||||
|
|
|
@ -1,3 +1,11 @@
|
||||||
|
:root {
|
||||||
|
--accent-color: #fb923c;
|
||||||
|
--primary-bg: #171E26;
|
||||||
|
--secondary-bg: #26323f;
|
||||||
|
|
||||||
|
accent-color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
.navbar-brand {
|
.navbar-brand {
|
||||||
padding-top: 0 !important;
|
padding-top: 0 !important;
|
||||||
padding-bottom: 0 !important;
|
padding-bottom: 0 !important;
|
||||||
|
@ -21,11 +29,11 @@ body,
|
||||||
#container {
|
#container {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
background-color: #171E26;
|
background-color: var(--primary-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-layout-header {
|
.ant-layout-header {
|
||||||
background-color: #26323f;
|
background-color: var(--secondary-bg);
|
||||||
border: 0;
|
border: 0;
|
||||||
border-bottom: 1px solid rgba(154, 197, 247, 0.19);
|
border-bottom: 1px solid rgba(154, 197, 247, 0.19);
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,11 +30,7 @@ const Quest = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const onFinish = (values) => {
|
const onFinish = (values) => {
|
||||||
let url = '/api/admin/games'
|
ajax('/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',
|
||||||
|
@ -68,6 +64,9 @@ const Quest = () => {
|
||||||
Сохранить квест
|
Сохранить квест
|
||||||
</Button>
|
</Button>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item name='id' hidden>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
<Form.Item label='Опубликован?' name='visible'>
|
<Form.Item label='Опубликован?' name='visible'>
|
||||||
<Switch />
|
<Switch />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
@ -83,7 +82,7 @@ const Quest = () => {
|
||||||
getValueFromEvent={normFile}
|
getValueFromEvent={normFile}
|
||||||
>
|
>
|
||||||
{quest.icon ? <Avatar src={`/api/file/${quest.icon}`} /> : null}
|
{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/admin/file/upload' listType='picture' maxCount={1}>
|
||||||
<Button icon={<UploadOutlined />}>Загрузка</Button>
|
<Button icon={<UploadOutlined />}>Загрузка</Button>
|
||||||
</Upload>
|
</Upload>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
|
@ -12,6 +12,7 @@ const Quests = () => {
|
||||||
<Link to="/admin/quests/new">Создать новый квест</Link>
|
<Link to="/admin/quests/new">Создать новый квест</Link>
|
||||||
<Table
|
<Table
|
||||||
dataSource={quests}
|
dataSource={quests}
|
||||||
|
rowKey={'id'}
|
||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
title: 'UUID',
|
title: 'UUID',
|
||||||
|
|
54
main.go
54
main.go
|
@ -1,7 +1,6 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
@ -25,9 +24,7 @@ import (
|
||||||
"gitrepo.ru/neonxp/nquest/pkg/service"
|
"gitrepo.ru/neonxp/nquest/pkg/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var Version = "dev"
|
||||||
Version = "dev"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
cfg, err := GetConfig()
|
cfg, err := GetConfig()
|
||||||
|
@ -41,22 +38,12 @@ func main() {
|
||||||
fmt.Fprintf(os.Stderr, "Error DB connection\n: %s", err)
|
fmt.Fprintf(os.Stderr, "Error DB connection\n: %s", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
// db.Use(prometheus.New(prometheus.Config{
|
|
||||||
// DBName: "db1", // use `DBName` as metrics label
|
|
||||||
// RefreshInterval: 15, // Refresh metrics interval (default 15 seconds)
|
|
||||||
// MetricsCollector: []prometheus.MetricsCollector{
|
|
||||||
// &prometheus.MySQL{
|
|
||||||
// VariableNames: []string{"Threads_running"},
|
|
||||||
// },
|
|
||||||
// }, // user defined metrics
|
|
||||||
// }))
|
|
||||||
|
|
||||||
if err := db.AutoMigrate(
|
if err := db.AutoMigrate(
|
||||||
&models.User{},
|
&models.User{},
|
||||||
&models.Game{},
|
&models.Game{},
|
||||||
&models.GameCursor{},
|
&models.GameCursor{},
|
||||||
&models.Task{},
|
&models.Task{},
|
||||||
&models.Solution{},
|
|
||||||
&models.Code{},
|
&models.Code{},
|
||||||
&models.File{},
|
&models.File{},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
|
@ -82,37 +69,6 @@ func main() {
|
||||||
}()
|
}()
|
||||||
go store.PeriodicCleanup(12*time.Hour, quit)
|
go store.PeriodicCleanup(12*time.Hour, quit)
|
||||||
|
|
||||||
// userMW := appmiddleware.User(models.RoleUser, userService)
|
|
||||||
|
|
||||||
authFunc := func(ctx context.Context, ai *openapi3filter.AuthenticationInput) error {
|
|
||||||
echoCtx := ctx.Value(oapiMiddleware.EchoContextKey).(echo.Context)
|
|
||||||
user := appmiddleware.GetUser(echoCtx)
|
|
||||||
if user != nil {
|
|
||||||
if len(ai.Scopes) > 0 {
|
|
||||||
for _, v := range ai.Scopes {
|
|
||||||
switch v {
|
|
||||||
case "user":
|
|
||||||
return nil
|
|
||||||
case "creator":
|
|
||||||
if user.HasRole(models.RoleCreator) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
case "admin":
|
|
||||||
if user.HasRole(models.RoleAdmin) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return echo.ErrForbidden
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return echo.ErrForbidden
|
|
||||||
}
|
|
||||||
|
|
||||||
swagger, err := api.GetSwagger()
|
swagger, err := api.GetSwagger()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Error loading swagger spec\n: %s", err)
|
fmt.Fprintf(os.Stderr, "Error loading swagger spec\n: %s", err)
|
||||||
|
@ -127,14 +83,6 @@ func main() {
|
||||||
session.Middleware(store),
|
session.Middleware(store),
|
||||||
middleware.Logger(),
|
middleware.Logger(),
|
||||||
middleware.RateLimiter(middleware.NewRateLimiterMemoryStore(20)),
|
middleware.RateLimiter(middleware.NewRateLimiterMemoryStore(20)),
|
||||||
// middleware.CSRFWithConfig(middleware.CSRFConfig{
|
|
||||||
// TokenLookup: "cookie:_csrf",
|
|
||||||
// CookiePath: "/",
|
|
||||||
// // CookieDomain: "nquest.ru",
|
|
||||||
// // CookieSecure: true,
|
|
||||||
// CookieHTTPOnly: true,
|
|
||||||
// CookieSameSite: http.SameSiteStrictMode,
|
|
||||||
// }),
|
|
||||||
middleware.Gzip(),
|
middleware.Gzip(),
|
||||||
echoprometheus.NewMiddleware("nquest"),
|
echoprometheus.NewMiddleware("nquest"),
|
||||||
appmiddleware.User(userService),
|
appmiddleware.User(userService),
|
||||||
|
|
|
@ -16,78 +16,18 @@ type Admin struct {
|
||||||
GameService *service.Game
|
GameService *service.Game
|
||||||
}
|
}
|
||||||
|
|
||||||
// (POST /admin/games)
|
// (POST /games/{uid})
|
||||||
func (a *Admin) CreateGame(ctx echo.Context) error {
|
func (a *Admin) AdminEditGame(ctx echo.Context) error {
|
||||||
user := contextlib.GetUser(ctx)
|
user := contextlib.GetUser(ctx)
|
||||||
req := &api.GameEditRequest{}
|
req := &api.AdminEditGameJSONRequestBody{}
|
||||||
|
|
||||||
if err := ctx.Bind(req); err != nil {
|
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.UpsertGame(ctx.Request().Context(), game)
|
||||||
game, err = a.GameService.CreateGame(ctx.Request().Context(), game)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks := make([]api.TaskEdit, 0, len(game.Tasks))
|
|
||||||
|
|
||||||
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,
|
|
||||||
Icon: game.IconID,
|
|
||||||
Id: &game.ID,
|
|
||||||
Points: game.Points,
|
|
||||||
Tasks: tasks,
|
|
||||||
Title: game.Title,
|
|
||||||
Type: api.MapGameTypeReverse(game.Type),
|
|
||||||
Visible: game.Visible,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// (POST /games/{uid})
|
|
||||||
func (a *Admin) EditGame(ctx echo.Context, uid uuid.UUID) error {
|
|
||||||
user := contextlib.GetUser(ctx)
|
|
||||||
req := &api.GameEditRequest{}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
game = a.mapCreateGameRequest(req, user)
|
|
||||||
|
|
||||||
game, err = a.GameService.UpdateGame(ctx.Request().Context(), uid, game)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -125,7 +65,7 @@ func (a *Admin) EditGame(ctx echo.Context, uid uuid.UUID) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// (GET /games/{uid})
|
// (GET /games/{uid})
|
||||||
func (a *Admin) GetGameByAdmin(ctx echo.Context, uid uuid.UUID) error {
|
func (a *Admin) AdminGetGame(ctx echo.Context, uid uuid.UUID) error {
|
||||||
user := contextlib.GetUser(ctx)
|
user := contextlib.GetUser(ctx)
|
||||||
|
|
||||||
game, err := a.GameService.GetByID(ctx.Request().Context(), uid)
|
game, err := a.GameService.GetByID(ctx.Request().Context(), uid)
|
||||||
|
@ -177,7 +117,7 @@ func (a *Admin) GetGameByAdmin(ctx echo.Context, uid uuid.UUID) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Admin) ListGamesByAdmin(ctx echo.Context) error {
|
func (a *Admin) AdminListGames(ctx echo.Context) error {
|
||||||
user := contextlib.GetUser(ctx)
|
user := contextlib.GetUser(ctx)
|
||||||
|
|
||||||
games, err := a.GameService.ListByAuthor(ctx.Request().Context(), user)
|
games, err := a.GameService.ListByAuthor(ctx.Request().Context(), user)
|
||||||
|
@ -202,11 +142,13 @@ func (a *Admin) ListGamesByAdmin(ctx echo.Context) error {
|
||||||
return ctx.JSON(http.StatusOK, resp)
|
return ctx.JSON(http.StatusOK, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*Admin) mapCreateGameRequest(req *api.GameEditRequest, user *models.User) *models.Game {
|
func (*Admin) mapCreateGameRequest(req *api.GameEdit, user *models.User) *models.Game {
|
||||||
|
id := uuid.New()
|
||||||
|
if req.Id != nil {
|
||||||
|
id = *req.Id
|
||||||
|
}
|
||||||
game := &models.Game{
|
game := &models.Game{
|
||||||
Model: models.Model{
|
ID: id,
|
||||||
ID: uuid.New(),
|
|
||||||
},
|
|
||||||
Visible: req.Visible,
|
Visible: req.Visible,
|
||||||
Title: req.Title,
|
Title: req.Title,
|
||||||
Description: req.Description,
|
Description: req.Description,
|
||||||
|
@ -219,14 +161,12 @@ 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 {
|
id := uuid.New()
|
||||||
u := uuid.New()
|
if te.Id != nil {
|
||||||
te.Id = &u
|
id = *te.Id
|
||||||
}
|
}
|
||||||
task := &models.Task{
|
task := &models.Task{
|
||||||
Model: models.Model{
|
ID: id,
|
||||||
ID: *te.Id,
|
|
||||||
},
|
|
||||||
Title: te.Title,
|
Title: te.Title,
|
||||||
Text: te.Text,
|
Text: te.Text,
|
||||||
Codes: make([]*models.Code, 0, len(te.Codes)),
|
Codes: make([]*models.Code, 0, len(te.Codes)),
|
||||||
|
@ -234,14 +174,12 @@ func (*Admin) mapCreateGameRequest(req *api.GameEditRequest, user *models.User)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ce := range te.Codes {
|
for _, ce := range te.Codes {
|
||||||
if ce.Id == nil {
|
id := uuid.New()
|
||||||
u := uuid.New()
|
if ce.Id != nil {
|
||||||
ce.Id = &u
|
id = *ce.Id
|
||||||
}
|
}
|
||||||
task.Codes = append(task.Codes, &models.Code{
|
task.Codes = append(task.Codes, &models.Code{
|
||||||
Model: models.Model{
|
ID: id,
|
||||||
ID: *ce.Id,
|
|
||||||
},
|
|
||||||
Code: ce.Code,
|
Code: ce.Code,
|
||||||
Description: ce.Description,
|
Description: ce.Description,
|
||||||
})
|
})
|
||||||
|
|
|
@ -12,7 +12,7 @@ type File struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// (POST /file/upload)
|
// (POST /file/upload)
|
||||||
func (u *File) UploadFile(c echo.Context) error {
|
func (u *File) AdminUploadFile(c echo.Context) error {
|
||||||
// user := contextlib.GetUser(c)
|
// user := contextlib.GetUser(c)
|
||||||
fh, err := c.FormFile("file")
|
fh, err := c.FormFile("file")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,10 +1,19 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
type File struct {
|
import (
|
||||||
Model
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type File struct {
|
||||||
|
ID uuid.UUID `gorm:"primarykey" json:"id"`
|
||||||
Filename string
|
Filename string
|
||||||
ContentType string
|
ContentType string
|
||||||
Size int
|
Size int
|
||||||
Body []byte `gorm:"type:bytea"`
|
Body []byte `gorm:"type:bytea"`
|
||||||
|
CreatedAt time.Time `json:"-"`
|
||||||
|
UpdatedAt time.Time `json:"-"`
|
||||||
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
import "github.com/google/uuid"
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
type Game struct {
|
type Game struct {
|
||||||
Model
|
ID uuid.UUID `gorm:"primarykey" json:"id"`
|
||||||
|
Visible bool `gorm:"index"`
|
||||||
Visible bool `gorm:"index"`
|
|
||||||
Title string
|
Title string
|
||||||
Description string
|
Description string
|
||||||
Tasks []*Task `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
|
Tasks []*Task `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
|
||||||
|
@ -14,6 +18,9 @@ type Game struct {
|
||||||
Points int
|
Points int
|
||||||
Icon *File
|
Icon *File
|
||||||
IconID uuid.UUID
|
IconID uuid.UUID
|
||||||
|
CreatedAt time.Time `json:"-"`
|
||||||
|
UpdatedAt time.Time `json:"-"`
|
||||||
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GameType int
|
type GameType int
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
package models
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Model struct {
|
|
||||||
ID uuid.UUID `gorm:"primarykey" json:"id"`
|
|
||||||
CreatedAt time.Time `json:"-"`
|
|
||||||
UpdatedAt time.Time `json:"-"`
|
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
|
||||||
}
|
|
|
@ -1,29 +1,22 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
import "github.com/google/uuid"
|
import (
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
type Task struct {
|
type Task struct {
|
||||||
Model
|
ID uuid.UUID `gorm:"primarykey" json:"id"`
|
||||||
|
|
||||||
Title string
|
Title string
|
||||||
Text string
|
Text string
|
||||||
MaxTime int
|
MaxTime int
|
||||||
GameID uuid.UUID
|
GameID uuid.UUID
|
||||||
Solutions []*Solution `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
|
Codes []*Code `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
|
||||||
Codes []*Code `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
|
|
||||||
TaskOrder uint
|
TaskOrder uint
|
||||||
}
|
}
|
||||||
|
|
||||||
type Solution struct {
|
|
||||||
Model
|
|
||||||
|
|
||||||
TaskID uuid.UUID
|
|
||||||
After int
|
|
||||||
Text string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Code struct {
|
type Code struct {
|
||||||
Model
|
ID uuid.UUID `gorm:"primarykey" json:"id"`
|
||||||
|
|
||||||
TaskID uuid.UUID
|
TaskID uuid.UUID
|
||||||
Code string `gorm:"index"`
|
Code string `gorm:"index"`
|
||||||
|
|
|
@ -2,15 +2,22 @@ package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrEmptyPassword = errors.New("empty password")
|
var ErrEmptyPassword = errors.New("empty password")
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
Model
|
ID uuid.UUID `gorm:"primarykey" json:"id"`
|
||||||
Username string `gorm:"unique" json:"username"`
|
CreatedAt time.Time `json:"-"`
|
||||||
Email string `gorm:"unique" json:"email"`
|
UpdatedAt time.Time `json:"-"`
|
||||||
Password string `json:"-"`
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||||
|
Username string `gorm:"unique" json:"username"`
|
||||||
|
Email string `gorm:"unique" json:"email"`
|
||||||
|
Password string `json:"-"`
|
||||||
Experience int
|
Experience int
|
||||||
Games []*GameCursor
|
Games []*GameCursor
|
||||||
Role UserRole
|
Role UserRole
|
||||||
|
|
|
@ -137,7 +137,6 @@ func (e *Engine) GetNext(ctx context.Context, gameID uuid.UUID, currentOrder uin
|
||||||
var t models.Task
|
var t models.Task
|
||||||
err := e.DB.WithContext(ctx).
|
err := e.DB.WithContext(ctx).
|
||||||
Preload("Codes").
|
Preload("Codes").
|
||||||
Preload("Solutions").
|
|
||||||
Order("task_order ASC").
|
Order("task_order ASC").
|
||||||
First(&t, `game_id = ? AND task_order > ?`, gameID, currentOrder).
|
First(&t, `game_id = ? AND task_order > ?`, gameID, currentOrder).
|
||||||
Error
|
Error
|
||||||
|
|
|
@ -32,7 +32,7 @@ func (u *File) Upload(
|
||||||
}
|
}
|
||||||
|
|
||||||
file := &models.File{
|
file := &models.File{
|
||||||
Model: models.Model{ID: uuid.New()},
|
ID: uuid.New(),
|
||||||
Filename: filename,
|
Filename: filename,
|
||||||
ContentType: contentType,
|
ContentType: contentType,
|
||||||
Size: size,
|
Size: size,
|
||||||
|
|
|
@ -28,7 +28,6 @@ func (gs *Game) GetByID(ctx context.Context, id uuid.UUID) (*models.Game, error)
|
||||||
return db.Order("tasks.task_order ASC")
|
return db.Order("tasks.task_order ASC")
|
||||||
}).
|
}).
|
||||||
Preload("Tasks.Codes").
|
Preload("Tasks.Codes").
|
||||||
Preload("Tasks.Solutions").
|
|
||||||
First(g, id).
|
First(g, id).
|
||||||
Error
|
Error
|
||||||
}
|
}
|
||||||
|
@ -67,20 +66,9 @@ func (gs *Game) ListByAuthor(ctx context.Context, author *models.User) ([]*model
|
||||||
Error
|
Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gs *Game) CreateGame(ctx context.Context, game *models.Game) (*models.Game, error) {
|
func (gs *Game) UpsertGame(ctx context.Context, game *models.Game) (*models.Game, error) {
|
||||||
return game, gs.DB.
|
return game, gs.DB.Debug().
|
||||||
Session(&gorm.Session{FullSaveAssociations: true}).
|
Session(&gorm.Session{FullSaveAssociations: true}).
|
||||||
Create(game).
|
|
||||||
Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gs *Game) UpdateGame(ctx context.Context, uid uuid.UUID, game *models.Game) (*models.Game, error) {
|
|
||||||
game.ID = uid
|
|
||||||
|
|
||||||
db := gs.DB
|
|
||||||
|
|
||||||
return game, db.Debug().
|
|
||||||
Session(&gorm.Session{FullSaveAssociations: true}).Omit("created_at").
|
|
||||||
Save(game).
|
Save(game).
|
||||||
Error
|
Error
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,9 +59,7 @@ func (s *User) Register(ctx context.Context, username, email, password, password
|
||||||
}
|
}
|
||||||
|
|
||||||
u := &models.User{
|
u := &models.User{
|
||||||
Model: models.Model{
|
ID: uuid.New(),
|
||||||
ID: uuid.New(),
|
|
||||||
},
|
|
||||||
Username: username,
|
Username: username,
|
||||||
Email: normalizer.NewNormalizer().Normalize(email),
|
Email: normalizer.NewNormalizer().Normalize(email),
|
||||||
Password: hex.EncodeToString(hashed),
|
Password: hex.EncodeToString(hashed),
|
||||||
|
|
Loading…
Reference in a new issue