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",
|
||||
"mode": "auto",
|
||||
"program": "${workspaceFolder}/",
|
||||
"envFile": "${workspaceFolder}/.env"
|
||||
"envFile": "${workspaceFolder}/.devcontainer/.env"
|
||||
}
|
||||
]
|
||||
}
|
182
api/openapi.yaml
182
api/openapi.yaml
|
@ -20,7 +20,16 @@ paths:
|
|||
post:
|
||||
security: []
|
||||
requestBody:
|
||||
$ref: "#/components/requestBodies/login"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
email:
|
||||
type: string
|
||||
password:
|
||||
type: string
|
||||
required: [email, password]
|
||||
responses:
|
||||
200:
|
||||
$ref: "#/components/responses/userResponse"
|
||||
|
@ -30,7 +39,20 @@ paths:
|
|||
post:
|
||||
security: []
|
||||
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:
|
||||
200:
|
||||
$ref: "#/components/responses/userResponse"
|
||||
|
@ -50,7 +72,6 @@ paths:
|
|||
responses:
|
||||
200:
|
||||
$ref: "#/components/responses/gameListResponse"
|
||||
|
||||
/engine/{uid}:
|
||||
get:
|
||||
operationId: gameEngine
|
||||
|
@ -74,28 +95,19 @@ paths:
|
|||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
requestBody:
|
||||
$ref: "#/components/requestBodies/enterCodeRequest"
|
||||
responses:
|
||||
200:
|
||||
$ref: "#/components/responses/taskResponse"
|
||||
/file/upload:
|
||||
post:
|
||||
operationId: uploadFile
|
||||
security:
|
||||
- cookieAuth: [creator, admin]
|
||||
requestBody:
|
||||
content:
|
||||
multipart/form-data:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
file:
|
||||
code:
|
||||
type: string
|
||||
format: binary
|
||||
required:
|
||||
- code
|
||||
responses:
|
||||
200:
|
||||
$ref: "#/components/responses/uploadResponse"
|
||||
$ref: "#/components/responses/taskResponse"
|
||||
/file/{uid}:
|
||||
get:
|
||||
operationId: getFile
|
||||
|
@ -114,24 +126,46 @@ paths:
|
|||
schema:
|
||||
type: string
|
||||
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:
|
||||
get:
|
||||
operationId: listGamesByAdmin
|
||||
operationId: adminListGames
|
||||
responses:
|
||||
200:
|
||||
$ref: "#/components/responses/gameListResponse"
|
||||
post:
|
||||
operationId: createGame
|
||||
operationId: adminEditGame
|
||||
security:
|
||||
- cookieAuth: [creator, admin]
|
||||
requestBody:
|
||||
$ref: "#/components/requestBodies/gameEditRequest"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/gameEdit"
|
||||
responses:
|
||||
200:
|
||||
$ref: "#/components/responses/gameAdminResponse"
|
||||
/admin/games/{uid}:
|
||||
get:
|
||||
operationId: getGameByAdmin
|
||||
operationId: adminGetGame
|
||||
parameters:
|
||||
- name: uid
|
||||
in: path
|
||||
|
@ -144,22 +178,7 @@ paths:
|
|||
responses:
|
||||
200:
|
||||
$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:
|
||||
schemas:
|
||||
userView:
|
||||
|
@ -254,15 +273,10 @@ components:
|
|||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/codeView"
|
||||
# solutions:
|
||||
# type: array
|
||||
# items:
|
||||
# $ref: '#/components/schemas/solutionView'
|
||||
required:
|
||||
- title
|
||||
- text
|
||||
- codes
|
||||
# - solutions
|
||||
codeView:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -272,15 +286,6 @@ components:
|
|||
type: string
|
||||
required:
|
||||
- description
|
||||
solutionView:
|
||||
type: object
|
||||
properties:
|
||||
text:
|
||||
type: string
|
||||
after:
|
||||
type: integer
|
||||
required:
|
||||
- after
|
||||
gameEdit:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -326,15 +331,10 @@ components:
|
|||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/codeEdit"
|
||||
# solutions:
|
||||
# type: array
|
||||
# items:
|
||||
# $ref: '#/components/schemas/solutionEdit'
|
||||
required:
|
||||
- title
|
||||
- text
|
||||
- codes
|
||||
# - solutions
|
||||
codeEdit:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -348,78 +348,22 @@ components:
|
|||
required:
|
||||
- description
|
||||
- code
|
||||
# solutionEdit:
|
||||
# type: object
|
||||
# properties:
|
||||
# text:
|
||||
# type: string
|
||||
# after:
|
||||
# type: integer
|
||||
# required:
|
||||
# - after
|
||||
# - text
|
||||
gameType:
|
||||
type: string
|
||||
enum:
|
||||
- virtual
|
||||
- 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:
|
||||
userResponse:
|
||||
description: ""
|
||||
content:
|
||||
"application/json":
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/userView"
|
||||
errorResponse:
|
||||
description: ""
|
||||
content:
|
||||
"application/json":
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -431,7 +375,7 @@ components:
|
|||
gameListResponse:
|
||||
description: ""
|
||||
content:
|
||||
"application/json":
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
|
@ -439,25 +383,25 @@ components:
|
|||
gameResponse:
|
||||
description: ""
|
||||
content:
|
||||
"application/json":
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/gameView"
|
||||
gameAdminResponse:
|
||||
description: ""
|
||||
content:
|
||||
"application/json":
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/gameEdit"
|
||||
taskResponse:
|
||||
description: ""
|
||||
content:
|
||||
"application/json":
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/taskView"
|
||||
uploadResponse:
|
||||
description: ""
|
||||
content:
|
||||
"application/json":
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
|
|
123
api/server.go
123
api/server.go
|
@ -22,17 +22,17 @@ import (
|
|||
// ServerInterface represents all server handlers.
|
||||
type ServerInterface interface {
|
||||
|
||||
// (POST /admin/file/upload)
|
||||
AdminUploadFile(ctx echo.Context) error
|
||||
|
||||
// (GET /admin/games)
|
||||
ListGamesByAdmin(ctx echo.Context) error
|
||||
AdminListGames(ctx echo.Context) error
|
||||
|
||||
// (POST /admin/games)
|
||||
CreateGame(ctx echo.Context) error
|
||||
AdminEditGame(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
|
||||
AdminGetGame(ctx echo.Context, uid openapi_types.UUID) error
|
||||
|
||||
// (GET /engine/{uid})
|
||||
GameEngine(ctx echo.Context, uid openapi_types.UUID) error
|
||||
|
@ -40,9 +40,6 @@ type ServerInterface interface {
|
|||
// (POST /engine/{uid}/code)
|
||||
EnterCode(ctx echo.Context, uid openapi_types.UUID) error
|
||||
|
||||
// (POST /file/upload)
|
||||
UploadFile(ctx echo.Context) error
|
||||
|
||||
// (GET /file/{uid})
|
||||
GetFile(ctx echo.Context, uid openapi_types.UUID) error
|
||||
|
||||
|
@ -67,30 +64,41 @@ type ServerInterfaceWrapper struct {
|
|||
Handler ServerInterface
|
||||
}
|
||||
|
||||
// ListGamesByAdmin converts echo context to params.
|
||||
func (w *ServerInterfaceWrapper) ListGamesByAdmin(ctx echo.Context) error {
|
||||
// AdminUploadFile converts echo context to params.
|
||||
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
|
||||
|
||||
ctx.Set(CookieAuthScopes, []string{})
|
||||
|
||||
// Invoke the callback with all the unmarshaled arguments
|
||||
err = w.Handler.ListGamesByAdmin(ctx)
|
||||
err = w.Handler.AdminListGames(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// CreateGame converts echo context to params.
|
||||
func (w *ServerInterfaceWrapper) CreateGame(ctx echo.Context) error {
|
||||
// AdminEditGame converts echo context to params.
|
||||
func (w *ServerInterfaceWrapper) AdminEditGame(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)
|
||||
err = w.Handler.AdminEditGame(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetGameByAdmin converts echo context to params.
|
||||
func (w *ServerInterfaceWrapper) GetGameByAdmin(ctx echo.Context) error {
|
||||
// AdminGetGame converts echo context to params.
|
||||
func (w *ServerInterfaceWrapper) AdminGetGame(ctx echo.Context) error {
|
||||
var err error
|
||||
// ------------- Path parameter "uid" -------------
|
||||
var uid openapi_types.UUID
|
||||
|
@ -103,25 +111,7 @@ func (w *ServerInterfaceWrapper) GetGameByAdmin(ctx echo.Context) error {
|
|||
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)
|
||||
err = w.Handler.AdminGetGame(ctx, uid)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -161,17 +151,6 @@ func (w *ServerInterfaceWrapper) EnterCode(ctx echo.Context) error {
|
|||
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.
|
||||
func (w *ServerInterfaceWrapper) GetFile(ctx echo.Context) error {
|
||||
var err error
|
||||
|
@ -269,13 +248,12 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
|
|||
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.POST(baseURL+"/admin/file/upload", wrapper.AdminUploadFile)
|
||||
router.GET(baseURL+"/admin/games", wrapper.AdminListGames)
|
||||
router.POST(baseURL+"/admin/games", wrapper.AdminEditGame)
|
||||
router.GET(baseURL+"/admin/games/:uid", wrapper.AdminGetGame)
|
||||
router.GET(baseURL+"/engine/:uid", wrapper.GameEngine)
|
||||
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+"/games", wrapper.GetGames)
|
||||
router.GET(baseURL+"/user", wrapper.GetUser)
|
||||
|
@ -288,27 +266,26 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
|
|||
// Base64 encoded, gzipped, json marshaled Swagger object
|
||||
var swaggerSpec = []string{
|
||||
|
||||
"H4sIAAAAAAAC/9RZzW7kNgx+lULt0RvP/px8ywZtUDQo2jTbSzAIFJuZaGNLrkRnMwj87gUl/47lseMa",
|
||||
"2fQUR6JF8uNHmuI8s1hluZIg0bDomWn4pwCDn1UiwC6ARNBnKoFLt0NrsZII0j7yPE9FzFEoGX41StKa",
|
||||
"ie8h4/SUa5WDxuqoWCVAf3GfA4uYQS3kjpVlYLUKDQmLrp3UNqil1O1XiJGVfTHUBZQB2/EMfk4ELrHt",
|
||||
"Jw13LGI/hi0Aods1YX3uiNpU7YT8D0BAxkXqQSJgOTfmm9LJNEzujM4bMyHTsBMGQb+2+e3mB+9uYUBL",
|
||||
"ns0gSCMZDEHoapkFiF0xuZKm8k1rpS+rlfW4LiTCDjQ5moExfDc3EVp5vzsJmFiLnGxiEWNVTpwmmZCL",
|
||||
"vJifFX7NF8LgIsUCITNzLPhbwDfSVoHBteb7MYuQm4fVYaBDnRF+pUWeKp6swKGiEDaT7pTOOLLILQRT",
|
||||
"6UFCc8lCubQ6QHToOEBlUB3T5Inl1NyvxcGBnv0loHXPDMY+QW7DeraWtUfM8OlvMnCgfxKU2G1MwDIT",
|
||||
"vYDlSlQdw7DCUYKY2TlN0q6qHOZ0wFBg6ofVLUxXiyuSKwP2KIy47Z11q1QKXA5iUEvW2oMDctjXaycb",
|
||||
"ICqEx4J2VZkLssicEo0Fp09XLHDfea31sKl1g1jzAu+Vno9wm49DhGMNHCE5xWWp9sqsOlOFxJHtlahy",
|
||||
"wAZn5zEiNARoDezCGjThOkKRJge8dWV+pJtq6on0zBAgPPm5MAbwAWA1VvacoLJ/zOfxWvoyn8fY3em1",
|
||||
"6sxTDzdVYyXkI09FUv+r0uZRwhPepPAIlKBEjRvSmwKCN1NfDbImkV/QksNTfqXOCq1B4oV1yZs+Vux3",
|
||||
"eJqQAS1AxiOdLSFlVujlZnM1HbdVq7QXdoKuzktFT5xaZG84599ErFWe60gHqNpIXyAGqNcQVuZ7mriA",
|
||||
"GYgLLXD/F8FY54t6EHBa4L0Fn9ost0RUto4wA8Z0qlbEeC5+g6pvFvJOWWcdXZn8016mA/YI2ri27f3J",
|
||||
"5mRD4KgcJM8Fi9jHk/cnG3vhwntrRmghDRsW7MBmBdHUdpG/JixidD84J4nPe3tJYQf3rw+bzRhxGrlw",
|
||||
"cNGwbuTKeBSe2Up87gLUTjf241o6A5DwcMBQLjW3fyPrBpJF1/0QXg9ZWm7pjS7A4XMhknIU5nOwKLcg",
|
||||
"51zzDBCobbiuSEKRaynicqx/PQ46Hf9UQ739btCMhZ4CVwX+ldz/X7ML5E5ImCIWmWwF3y6perf+shw4",
|
||||
"F9aXtRHa1BPPN8ubwUy2XAumO5FC6AYY4wB9sfu/iNRTUjsDhKxIUeRcY0gYvEs4emYcpLAH062QXO+9",
|
||||
"F3bvNO+lXh9MZxbmisVpsgRXEH23NBmZ5qgYAd8Z1MCz/lRnOgqDgY4NYMWe45/+6ptkVvvkk0rb1x3R",
|
||||
"+MX1fQt40p2MlQH7tPk4/VJ/etwxMWx+MvCn1B/KWFMvrNiCkuDOL9fxdPNyT3tZtO35rQqc5TjJDez/",
|
||||
"NBhuMVPEMRjzQ3X0UotbG7s/iRy38rKWXBChRsvbCdLR2relqmJAP9Z1q9Api1hI3X+5Lf8NAAD//0cV",
|
||||
"3sY5HAAA",
|
||||
"H4sIAAAAAAAC/8xY32+kNhD+Vyq3j1zY+/HEWxqlUdWoaq+5vkSryIHJxhewqT3OZRXxv1djAwuLWcge",
|
||||
"Su4pGzx4Zr75vmHsZ5aqolQSJBqWPDMNplTSgPsHtFb6c/2EHqRKIkikn7wsc5FyFErGX42S9Myk91Bw",
|
||||
"+lVqVYJG4fdJVeZex20JLGFCImxAsypiBRjDN91Fg1rIDauqiGn4zwoNGUuu/RY7+3XU2Kvbr5Aiq+iF",
|
||||
"DEyqRUkxsYTR/htewGlWCHlUFr9ouGMJ+zneYRT7VRPTzueZOOj5Uhg8yrFAKMycCP4V8I281WBwrfl2",
|
||||
"LCLk5mFxGGhTH0TYqS1zxbMFOGStyOjvndIFR5b4B9EEbZzRXLJYA3pxgGjTcYCqqN6m1Ynj1JSCmmz3",
|
||||
"NgysHwNad8/Iux5C6BdcZktFeyCMkP9WgQP/k6CkfmEClpnoRaxUom6fww5HAjGzNU3WvqvsazpiKDAP",
|
||||
"w+ofTHeLK7KrIvYojLjt7XWrVA5cDmrQWDbeoz1yuNebJFsgaoTHinZVhwvSFt6JRstz4prAbee1XYZt",
|
||||
"rxvUmlu8V3o+wjs9DhFONXCE7BSPk9ors+pMWYkjywtRZY8NPs5DRGgJsAuwC2vUlusARVoNBPvK/Eq3",
|
||||
"3TRQ6ZklQHgKc2EM4D3AGqzcPlEd/1jO4730ZTmPsbszazXKUw839WAl5CPPRdb8q/L2p4QnvMnhEUig",
|
||||
"RI0b8psDQlCprwZZK+QBZFBwkQdjgKfySp1ZrUHipUspKB9n9ic8TdiAFiDTkcmWkDILzHKzuZqPx6pV",
|
||||
"3is7QdfoUtEvTiNysJxkKnkxo3AuqtY8qqvQA6oJMlSIAeoNhHX4gSEuYgZSqwVu/yEYG72oBwGnFu8d",
|
||||
"+DRm+UdEZZcIM2BMp2sljJfiD6jnZiHvlEvW05XJvy0YYuIjaOPHtvcnq5MVgaNKkLwULGEfT96frKj/",
|
||||
"cbx3YcQO0vhO5BD7GdjxVBknDmKrGyZ/z1jC3AHlizP6TTjyE65g8FeVbfcG0cLmKEquMSZCvMs4BmZl",
|
||||
"8trjzK2QXG+Dg19gMu6dAT+sVmPkbe3ivSm/WxqWXPeLcj3kXbWmN2rIWuFsYAwrOlddNOR4cbCDk5lL",
|
||||
"+kBp6CNy4Uk9XphlTpLVsQn1D7nfX4D42YqsOlyGC2hgKbnmBSDQHHZdq46ksNOcb1q7foHaQtQBaOqE",
|
||||
"sn5TYEBuhIQJTAiLc2f44yLSuwPwfOslFzdHt7AcziWCPvNzwStleJzeZp1HQ1dMobuCaimk3ddggkSA",
|
||||
"9TfgzRg0ArJKEfCdQQ286IM9/ZkZ3Hy4L1SNyuGGXzeZ5Vo9uXQD0AGPX/yAdMSHsHuFVEXs0+rj9Ev9",
|
||||
"a9ZOiHGuNkKOy/EvZVyol85sKbWMj88lN+ab0tm0lprhr31jMV0NEV69HOHeR2Ddw1tZnAU42Q3i/zS4",
|
||||
"fWLGpikY81O99bER72LUsBEGPX8PR/m5sXxTZuwWPwRX558wAoeL1m/Xy4/NtYMTyJqasgH92LR9q3OW",
|
||||
"sJhOGdW6+j8AAP//fdZwRK4ZAAA=",
|
||||
}
|
||||
|
||||
// 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.
|
||||
type UserResponse = UserView
|
||||
|
||||
// EnterCodeRequest defines model for enterCodeRequest.
|
||||
type EnterCodeRequest struct {
|
||||
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"`
|
||||
}
|
||||
// AdminUploadFileMultipartBody defines parameters for AdminUploadFile.
|
||||
type AdminUploadFileMultipartBody interface{}
|
||||
|
||||
// EnterCodeJSONBody defines parameters for EnterCode.
|
||||
type EnterCodeJSONBody struct {
|
||||
Code string `json:"code"`
|
||||
}
|
||||
|
||||
// UploadFileMultipartBody defines parameters for UploadFile.
|
||||
type UploadFileMultipartBody interface{}
|
||||
|
||||
// PostUserLoginJSONBody defines parameters for PostUserLogin.
|
||||
type PostUserLoginJSONBody struct {
|
||||
Email string `json:"email"`
|
||||
|
@ -176,18 +154,15 @@ type PostUserRegisterJSONBody struct {
|
|||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
// CreateGameJSONRequestBody defines body for CreateGame for application/json ContentType.
|
||||
type CreateGameJSONRequestBody = GameEdit
|
||||
// AdminUploadFileMultipartRequestBody defines body for AdminUploadFile for multipart/form-data ContentType.
|
||||
type AdminUploadFileMultipartRequestBody AdminUploadFileMultipartBody
|
||||
|
||||
// EditGameJSONRequestBody defines body for EditGame for application/json ContentType.
|
||||
type EditGameJSONRequestBody = GameEdit
|
||||
// AdminEditGameJSONRequestBody defines body for AdminEditGame for application/json ContentType.
|
||||
type AdminEditGameJSONRequestBody = 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.
|
||||
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) {
|
||||
return <Navigate to="/login" state={{ from: location }} replace />
|
||||
}
|
||||
|
||||
if (props.role && !hasRole(props.role)) {
|
||||
return <Navigate to="/" replace />
|
||||
return null
|
||||
}
|
||||
|
||||
return props.children
|
||||
|
|
|
@ -1,3 +1,11 @@
|
|||
:root {
|
||||
--accent-color: #fb923c;
|
||||
--primary-bg: #171E26;
|
||||
--secondary-bg: #26323f;
|
||||
|
||||
accent-color: var(--accent-color);
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
padding-top: 0 !important;
|
||||
padding-bottom: 0 !important;
|
||||
|
@ -21,11 +29,11 @@ body,
|
|||
#container {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
background-color: #171E26;
|
||||
background-color: var(--primary-bg);
|
||||
}
|
||||
|
||||
.ant-layout-header {
|
||||
background-color: #26323f;
|
||||
background-color: var(--secondary-bg);
|
||||
border: 0;
|
||||
border-bottom: 1px solid rgba(154, 197, 247, 0.19);
|
||||
}
|
||||
|
|
|
@ -30,11 +30,7 @@ const Quest = () => {
|
|||
}
|
||||
|
||||
const onFinish = (values) => {
|
||||
let url = '/api/admin/games'
|
||||
if (quest.id) {
|
||||
url = `/api/admin/games/${quest.id}`
|
||||
}
|
||||
ajax(url, {
|
||||
ajax('/api/admin/games', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
|
@ -68,6 +64,9 @@ const Quest = () => {
|
|||
Сохранить квест
|
||||
</Button>
|
||||
</Form.Item>
|
||||
<Form.Item name='id' hidden>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label='Опубликован?' name='visible'>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
|
@ -83,7 +82,7 @@ const Quest = () => {
|
|||
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/admin/file/upload' listType='picture' maxCount={1}>
|
||||
<Button icon={<UploadOutlined />}>Загрузка</Button>
|
||||
</Upload>
|
||||
</Form.Item>
|
||||
|
|
|
@ -12,6 +12,7 @@ const Quests = () => {
|
|||
<Link to="/admin/quests/new">Создать новый квест</Link>
|
||||
<Table
|
||||
dataSource={quests}
|
||||
rowKey={'id'}
|
||||
columns={[
|
||||
{
|
||||
title: 'UUID',
|
||||
|
|
54
main.go
54
main.go
|
@ -1,7 +1,6 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
@ -25,9 +24,7 @@ import (
|
|||
"gitrepo.ru/neonxp/nquest/pkg/service"
|
||||
)
|
||||
|
||||
var (
|
||||
Version = "dev"
|
||||
)
|
||||
var Version = "dev"
|
||||
|
||||
func main() {
|
||||
cfg, err := GetConfig()
|
||||
|
@ -41,22 +38,12 @@ func main() {
|
|||
fmt.Fprintf(os.Stderr, "Error DB connection\n: %s", err)
|
||||
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(
|
||||
&models.User{},
|
||||
&models.Game{},
|
||||
&models.GameCursor{},
|
||||
&models.Task{},
|
||||
&models.Solution{},
|
||||
&models.Code{},
|
||||
&models.File{},
|
||||
); err != nil {
|
||||
|
@ -82,37 +69,6 @@ func main() {
|
|||
}()
|
||||
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()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error loading swagger spec\n: %s", err)
|
||||
|
@ -127,14 +83,6 @@ func main() {
|
|||
session.Middleware(store),
|
||||
middleware.Logger(),
|
||||
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(),
|
||||
echoprometheus.NewMiddleware("nquest"),
|
||||
appmiddleware.User(userService),
|
||||
|
|
|
@ -16,78 +16,18 @@ type Admin struct {
|
|||
GameService *service.Game
|
||||
}
|
||||
|
||||
// (POST /admin/games)
|
||||
func (a *Admin) CreateGame(ctx echo.Context) error {
|
||||
// (POST /games/{uid})
|
||||
func (a *Admin) AdminEditGame(ctx echo.Context) error {
|
||||
user := contextlib.GetUser(ctx)
|
||||
req := &api.GameEditRequest{}
|
||||
req := &api.AdminEditGameJSONRequestBody{}
|
||||
|
||||
if err := ctx.Bind(req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
game := a.mapCreateGameRequest(req, user)
|
||||
|
||||
var err error
|
||||
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)
|
||||
game, err := a.GameService.UpsertGame(ctx.Request().Context(), game)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -125,7 +65,7 @@ func (a *Admin) EditGame(ctx echo.Context, uid uuid.UUID) error {
|
|||
}
|
||||
|
||||
// (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)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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{
|
||||
Model: models.Model{
|
||||
ID: uuid.New(),
|
||||
},
|
||||
ID: id,
|
||||
Visible: req.Visible,
|
||||
Title: req.Title,
|
||||
Description: req.Description,
|
||||
|
@ -219,14 +161,12 @@ func (*Admin) mapCreateGameRequest(req *api.GameEditRequest, user *models.User)
|
|||
IconID: req.Icon,
|
||||
}
|
||||
for order, te := range req.Tasks {
|
||||
if te.Id == nil {
|
||||
u := uuid.New()
|
||||
te.Id = &u
|
||||
id := uuid.New()
|
||||
if te.Id != nil {
|
||||
id = *te.Id
|
||||
}
|
||||
task := &models.Task{
|
||||
Model: models.Model{
|
||||
ID: *te.Id,
|
||||
},
|
||||
ID: id,
|
||||
Title: te.Title,
|
||||
Text: te.Text,
|
||||
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 {
|
||||
if ce.Id == nil {
|
||||
u := uuid.New()
|
||||
ce.Id = &u
|
||||
id := uuid.New()
|
||||
if ce.Id != nil {
|
||||
id = *ce.Id
|
||||
}
|
||||
task.Codes = append(task.Codes, &models.Code{
|
||||
Model: models.Model{
|
||||
ID: *ce.Id,
|
||||
},
|
||||
ID: id,
|
||||
Code: ce.Code,
|
||||
Description: ce.Description,
|
||||
})
|
||||
|
|
|
@ -12,7 +12,7 @@ type File struct {
|
|||
}
|
||||
|
||||
// (POST /file/upload)
|
||||
func (u *File) UploadFile(c echo.Context) error {
|
||||
func (u *File) AdminUploadFile(c echo.Context) error {
|
||||
// user := contextlib.GetUser(c)
|
||||
fh, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
|
|
|
@ -1,10 +1,19 @@
|
|||
package models
|
||||
|
||||
type File struct {
|
||||
Model
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type File struct {
|
||||
ID uuid.UUID `gorm:"primarykey" json:"id"`
|
||||
Filename string
|
||||
ContentType string
|
||||
Size int
|
||||
Body []byte `gorm:"type:bytea"`
|
||||
CreatedAt time.Time `json:"-"`
|
||||
UpdatedAt time.Time `json:"-"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
}
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
package models
|
||||
|
||||
import "github.com/google/uuid"
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Game struct {
|
||||
Model
|
||||
|
||||
ID uuid.UUID `gorm:"primarykey" json:"id"`
|
||||
Visible bool `gorm:"index"`
|
||||
Title string
|
||||
Description string
|
||||
|
@ -14,6 +18,9 @@ type Game struct {
|
|||
Points int
|
||||
Icon *File
|
||||
IconID uuid.UUID
|
||||
CreatedAt time.Time `json:"-"`
|
||||
UpdatedAt time.Time `json:"-"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
import "github.com/google/uuid"
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Task struct {
|
||||
Model
|
||||
ID uuid.UUID `gorm:"primarykey" json:"id"`
|
||||
|
||||
Title string
|
||||
Text string
|
||||
MaxTime int
|
||||
GameID uuid.UUID
|
||||
Solutions []*Solution `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
|
||||
Codes []*Code `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
|
||||
TaskOrder uint
|
||||
}
|
||||
|
||||
type Solution struct {
|
||||
Model
|
||||
|
||||
TaskID uuid.UUID
|
||||
After int
|
||||
Text string
|
||||
}
|
||||
|
||||
type Code struct {
|
||||
Model
|
||||
ID uuid.UUID `gorm:"primarykey" json:"id"`
|
||||
|
||||
TaskID uuid.UUID
|
||||
Code string `gorm:"index"`
|
||||
|
|
|
@ -2,12 +2,19 @@ package models
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var ErrEmptyPassword = errors.New("empty password")
|
||||
|
||||
type User struct {
|
||||
Model
|
||||
ID uuid.UUID `gorm:"primarykey" json:"id"`
|
||||
CreatedAt time.Time `json:"-"`
|
||||
UpdatedAt time.Time `json:"-"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
Username string `gorm:"unique" json:"username"`
|
||||
Email string `gorm:"unique" json:"email"`
|
||||
Password string `json:"-"`
|
||||
|
|
|
@ -137,7 +137,6 @@ func (e *Engine) GetNext(ctx context.Context, gameID uuid.UUID, currentOrder uin
|
|||
var t models.Task
|
||||
err := e.DB.WithContext(ctx).
|
||||
Preload("Codes").
|
||||
Preload("Solutions").
|
||||
Order("task_order ASC").
|
||||
First(&t, `game_id = ? AND task_order > ?`, gameID, currentOrder).
|
||||
Error
|
||||
|
|
|
@ -32,7 +32,7 @@ func (u *File) Upload(
|
|||
}
|
||||
|
||||
file := &models.File{
|
||||
Model: models.Model{ID: uuid.New()},
|
||||
ID: uuid.New(),
|
||||
Filename: filename,
|
||||
ContentType: contentType,
|
||||
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")
|
||||
}).
|
||||
Preload("Tasks.Codes").
|
||||
Preload("Tasks.Solutions").
|
||||
First(g, id).
|
||||
Error
|
||||
}
|
||||
|
@ -67,20 +66,9 @@ func (gs *Game) ListByAuthor(ctx context.Context, author *models.User) ([]*model
|
|||
Error
|
||||
}
|
||||
|
||||
func (gs *Game) CreateGame(ctx context.Context, game *models.Game) (*models.Game, error) {
|
||||
return game, gs.DB.
|
||||
func (gs *Game) UpsertGame(ctx context.Context, game *models.Game) (*models.Game, error) {
|
||||
return game, gs.DB.Debug().
|
||||
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).
|
||||
Error
|
||||
}
|
||||
|
|
|
@ -59,9 +59,7 @@ func (s *User) Register(ctx context.Context, username, email, password, password
|
|||
}
|
||||
|
||||
u := &models.User{
|
||||
Model: models.Model{
|
||||
ID: uuid.New(),
|
||||
},
|
||||
Username: username,
|
||||
Email: normalizer.NewNormalizer().Normalize(email),
|
||||
Password: hex.EncodeToString(hashed),
|
||||
|
|
Loading…
Reference in a new issue