Фикс ошибок

This commit is contained in:
Александр Кирюхин 2024-05-05 19:42:33 +03:00
parent 7e706a3981
commit a1dc96088c
No known key found for this signature in database
GPG key ID: 35E33E1AB7776B39
34 changed files with 1584 additions and 1180 deletions

View file

@ -1,4 +1,5 @@
package api package api
//go:generate merger -i parts/common.yaml -i parts/game.yaml -i parts/admin.yaml -i parts/user.yaml -i parts/schemas.yaml -i parts/responses.yaml -o openapi.yaml
//go:generate oapi-codegen -generate server,spec -package api -o ./server.go ./openapi.yaml //go:generate oapi-codegen -generate server,spec -package api -o ./server.go ./openapi.yaml
//go:generate oapi-codegen -generate types -package api -o ./types.go ./openapi.yaml //go:generate oapi-codegen -generate types -package api -o ./types.go ./openapi.yaml

View file

@ -1,237 +1,245 @@
openapi: "3.1.0" components:
info:
version: 1.0.0
title: nQuest
servers:
- url: /api
paths:
# User routes
/user:
get:
responses: responses:
200: errorResponse:
$ref: "#/components/responses/userResponse"
403:
$ref: "#/components/responses/errorResponse"
/user/login:
post:
security: []
requestBody:
content: content:
application/json: application/json:
schema: schema:
type: object
properties:
email:
type: string
password:
type: string
required: [email, password]
responses:
200:
$ref: "#/components/responses/userResponse"
400:
$ref: "#/components/responses/errorResponse"
/user/register:
post:
security: []
requestBody:
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"
400:
$ref: "#/components/responses/errorResponse"
/user/logout:
post:
responses:
204:
description: "success logout"
400:
$ref: "#/components/responses/errorResponse"
# Game routes
/games:
get:
responses:
200:
$ref: "#/components/responses/gameListResponse"
/engine/{uid}:
get:
operationId: gameEngine
parameters:
- name: uid
in: path
required: true
schema:
type: string
format: uuid
responses:
200:
$ref: "#/components/responses/taskResponse"
/engine/{uid}/code:
post:
operationId: enterCode
parameters:
- name: uid
in: path
required: true
schema:
type: string
format: uuid
requestBody:
content:
application/json:
schema:
type: object
properties: properties:
code: code:
type: integer
message:
type: string type: string
required: required:
- code - code
responses: - message
200:
$ref: "#/components/responses/taskResponse"
/file/{uid}:
get:
operationId: getFile
parameters:
- name: uid
in: path
required: true
schema:
type: string
format: uuid
responses:
200:
description: file
content:
"application/octet-stream":
schema:
type: string
format: binary
# Admin routes
/admin/file/{quest}/upload:
post:
operationId: adminUploadFile
security:
- cookieAuth: [creator, admin]
parameters:
- name: quest
in: path
required: true
schema:
type: string
format: uuid
requestBody:
content:
multipart/form-data:
schema:
type: object type: object
properties: description: ""
file: filesListResponse:
type: string
format: binary
responses:
200:
$ref: "#/components/responses/uploadResponse"
/admin/file/{quest}:
get:
operationId: adminListFiles
security:
- cookieAuth: [creator, admin]
parameters:
- name: quest
in: path
required: true
schema:
type: string
format: uuid
responses:
200:
$ref: "#/components/responses/filesListResponse"
/admin/games:
get:
operationId: adminListGames
responses:
200:
$ref: "#/components/responses/gameListResponse"
post:
operationId: adminEditGame
security:
- cookieAuth: [creator, admin]
requestBody:
content: content:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/gameEdit" items:
responses: $ref: '#/components/schemas/fileItem'
200: type: array
$ref: "#/components/responses/gameAdminResponse" description: ""
/admin/games/{uid}: gameAdminResponse:
get: content:
operationId: adminGetGame application/json:
parameters:
- name: uid
in: path
required: true
schema: schema:
type: string $ref: '#/components/schemas/gameEdit'
description: ""
gameListResponse:
content:
application/json:
schema:
items:
$ref: '#/components/schemas/gameView'
type: array
description: ""
gameResponse:
content:
application/json:
schema:
$ref: '#/components/schemas/gameView'
description: ""
taskResponse:
content:
application/json:
schema:
$ref: '#/components/schemas/taskView'
description: ""
uploadResponse:
content:
application/json:
schema:
properties:
uuid:
format: uuid format: uuid
security: type: string
- cookieAuth: [creator, admin] required:
responses: - uuid
200:
$ref: "#/components/responses/gameAdminResponse"
components:
schemas:
userView:
type: object type: object
description: ""
userResponse:
content:
application/json:
schema:
$ref: '#/components/schemas/userView'
description: ""
schemas:
codeEdit:
properties:
code:
type: string
description:
type: string
id:
format: uuid
type: string
required:
- code
type: object
codeView:
properties:
code:
type: string
description:
type: string
type: object
fileItem:
properties: properties:
id: id:
type: string
format: uuid format: uuid
username:
type: string type: string
originalName:
type: string
size:
type: integer
required:
- id
- originalName
- size
type: object
gameEdit:
properties:
description:
type: string
icon:
format: uuid
type: string
id:
format: uuid
type: string
points:
type: integer
tasks:
items:
$ref: '#/components/schemas/taskEdit'
type: array
title:
type: string
type:
$ref: '#/components/schemas/gameType'
visible:
type: boolean
required:
- visible
- title
- description
- type
- tasks
- points
type: object
gameType:
enum:
- virtual
- city
type: string
gameView:
properties:
authors:
items:
$ref: '#/components/schemas/userView'
type: array
createdAt:
type: string
description:
type: string
icon:
format: uuid
type: string
id:
format: uuid
type: string
points:
type: integer
taskCount:
type: integer
title:
type: string
type:
$ref: '#/components/schemas/gameType'
visible:
type: boolean
required:
- id
- title
- description
- type
- points
- taskCount
- createdAt
- authors
type: object
taskEdit:
properties:
codes:
items:
$ref: '#/components/schemas/codeEdit'
type: array
id:
format: uuid
type: string
text:
type: string
title:
type: string
required:
- id
- title
- text
- codes
type: object
taskView:
properties:
codes:
items:
$ref: '#/components/schemas/codeView'
type: array
message:
enum:
- ok_code
- invalid_code
- old_code
- next_level
- game_complete
type: string
text:
type: string
title:
type: string
required:
- title
- text
- codes
type: object
userView:
properties:
email: email:
type: string type: string
experience:
type: integer
level:
type: integer
expToCurrentLevel: expToCurrentLevel:
type: integer type: integer
expToNextLevel: expToNextLevel:
type: integer type: integer
experience:
type: integer
games: games:
type: array
items: items:
$ref: "#/components/schemas/gameView" $ref: '#/components/schemas/gameView'
role: type: array
id:
format: uuid
type: string type: string
level:
type: integer
role:
enum: enum:
- user - user
- creator - creator
- admin - admin
type: string
username:
type: string
required: required:
- id - id
- username - username
@ -242,221 +250,201 @@ components:
- expToNextLevel - expToNextLevel
- games - games
- role - role
gameView:
type: object type: object
securitySchemes:
cookieAuth:
in: cookie
name: session
type: apiKey
info:
title: nQuest
version: 1.0.0
openapi: 3.1.0
paths:
/admin/file/{quest}:
get:
operationId: adminListFiles
parameters:
- in: path
name: quest
required: true
schema:
format: uuid
type: string
responses:
200:
$ref: '#/components/responses/filesListResponse'
security:
- cookieAuth:
- creator
- admin
/admin/file/{quest}/upload:
post:
operationId: adminUploadFile
parameters:
- in: path
name: quest
required: true
schema:
format: uuid
type: string
requestBody:
content:
multipart/form-data:
properties: properties:
id: file:
format: binary
type: string type: string
format: uuid schema: null
visible:
type: boolean
title:
type: string
description:
type: string
type:
$ref: "#/components/schemas/gameType"
points:
type: integer
taskCount:
type: integer
createdAt:
type: string
authors:
type: array
items:
$ref: "#/components/schemas/userView"
icon:
type: string
format: uuid
required:
- id
- title
- description
- type
- points
- taskCount
- createdAt
- authors
taskView:
type: object type: object
properties: responses:
message: 200:
type: string $ref: '#/components/responses/uploadResponse'
enum: security:
- ok_code - cookieAuth:
- invalid_code - creator
- old_code - admin
- next_level /admin/games:
- game_complete get:
title: operationId: adminListGames
type: string responses:
text: 200:
type: string $ref: '#/components/responses/gameListResponse'
codes: post:
type: array operationId: adminEditGame
items: requestBody:
$ref: "#/components/schemas/codeView" content:
required: application/json:
- title schema:
- text $ref: '#/components/schemas/gameEdit'
- codes responses:
codeView: 200:
type: object $ref: '#/components/responses/gameAdminResponse'
properties: security:
description: - cookieAuth:
type: string - creator
code: - admin
type: string /admin/games/{uid}:
gameEdit: get:
type: object operationId: adminGetGame
properties: parameters:
id: - in: path
type: string name: uid
required: true
schema:
format: uuid format: uuid
visible:
type: boolean
title:
type: string
description:
type: string
type:
$ref: "#/components/schemas/gameType"
tasks:
type: array
items:
$ref: "#/components/schemas/taskEdit"
points:
type: integer
icon:
type: string type: string
responses:
200:
$ref: '#/components/responses/gameAdminResponse'
security:
- cookieAuth:
- creator
- admin
/engine/{uid}:
get:
operationId: gameEngine
parameters:
- in: path
name: uid
required: true
schema:
format: uuid format: uuid
required: type: string
- visible responses:
- title 200:
- description $ref: '#/components/responses/taskResponse'
- type /engine/{uid}/code:
- tasks post:
- points operationId: enterCode
taskEdit: parameters:
type: object - in: path
name: uid
required: true
schema:
format: uuid
type: string
requestBody:
content:
application/json:
schema:
properties: properties:
id:
type: string
format: uuid
title:
type: string
text:
type: string
codes:
type: array
items:
$ref: "#/components/schemas/codeEdit"
required:
- title
- text
- codes
codeEdit:
type: object
properties:
id:
type: string
format: uuid
description:
type: string
code: code:
type: string type: string
required: required:
- code - code
gameType:
type: string
enum:
- virtual
- city
fileItem:
type: object type: object
properties:
id:
type: string
format: uuid
originalName:
type: string
size:
type: integer
required:
- id
- originalName
- size
responses: responses:
userResponse: 200:
description: "" $ref: '#/components/responses/taskResponse'
/games:
get:
responses:
200:
$ref: '#/components/responses/gameListResponse'
/user:
get:
responses:
200:
$ref: '#/components/responses/userResponse'
403:
$ref: '#/components/responses/errorResponse'
/user/login:
post:
requestBody:
content: content:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/userView"
errorResponse:
description: ""
content:
application/json:
schema:
type: object
properties: properties:
code: email:
type: integer
message:
type: string type: string
required: [code, message] password:
gameListResponse:
description: ""
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/gameView"
gameResponse:
description: ""
content:
application/json:
schema:
$ref: "#/components/schemas/gameView"
gameAdminResponse:
description: ""
content:
application/json:
schema:
$ref: "#/components/schemas/gameEdit"
taskResponse:
description: ""
content:
application/json:
schema:
$ref: "#/components/schemas/taskView"
uploadResponse:
description: ""
content:
application/json:
schema:
type: object
properties:
uuid:
type: string type: string
format: uuid
required: required:
- uuid - email
filesListResponse: - password
description: "" type: object
responses:
200:
$ref: '#/components/responses/userResponse'
400:
$ref: '#/components/responses/errorResponse'
security: []
/user/logout:
post:
responses:
204:
description: success logout
400:
$ref: '#/components/responses/errorResponse'
/user/register:
post:
requestBody:
content: content:
application/json: application/json:
schema: schema:
type: array properties:
items: email:
$ref: "#/components/schemas/fileItem" type: string
securitySchemes: password:
cookieAuth: type: string
type: apiKey password2:
in: cookie type: string
name: session username:
type: string
required:
- username
- email
- password
- password2
type: object
responses:
200:
$ref: '#/components/responses/userResponse'
400:
$ref: '#/components/responses/errorResponse'
security: []
security: security:
- cookieAuth: [] - cookieAuth: []
servers:
- url: /api

74
api/parts/admin.yaml Normal file
View file

@ -0,0 +1,74 @@
paths:
/admin/file/{quest}/upload:
post:
operationId: adminUploadFile
security:
- cookieAuth: [creator, admin]
parameters:
- name: quest
in: path
required: true
schema:
type: string
format: uuid
requestBody:
content:
multipart/form-data:
schema:
type: object
properties:
file:
type: string
format: binary
responses:
200:
$ref: "#/components/responses/uploadResponse"
/admin/file/{quest}:
get:
operationId: adminListFiles
security:
- cookieAuth: [creator, admin]
parameters:
- name: quest
in: path
required: true
schema:
type: string
format: uuid
responses:
200:
$ref: "#/components/responses/filesListResponse"
/admin/games:
get:
operationId: adminListGames
responses:
200:
$ref: "#/components/responses/gameListResponse"
post:
operationId: adminEditGame
security:
- cookieAuth: [creator, admin]
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/gameEdit"
responses:
200:
$ref: "#/components/responses/gameAdminResponse"
/admin/games/{uid}:
get:
operationId: adminGetGame
parameters:
- name: uid
in: path
required: true
schema:
type: string
format: uuid
security:
- cookieAuth: [creator, admin]
responses:
200:
$ref: "#/components/responses/gameAdminResponse"

19
api/parts/common.yaml Normal file
View file

@ -0,0 +1,19 @@
openapi: "3.1.0"
info:
version: 1.0.0
title: nQuest
servers:
- url: /api
components:
securitySchemes:
cookieAuth:
type: apiKey
in: cookie
name: session
security:
- cookieAuth: []

43
api/parts/game.yaml Normal file
View file

@ -0,0 +1,43 @@
paths:
/games:
get:
responses:
200:
$ref: "#/components/responses/gameListResponse"
/engine/{uid}:
get:
operationId: gameEngine
parameters:
- name: uid
in: path
required: true
schema:
type: string
format: uuid
responses:
200:
$ref: "#/components/responses/taskResponse"
/engine/{uid}/code:
post:
operationId: enterCode
parameters:
- name: uid
in: path
required: true
schema:
type: string
format: uuid
requestBody:
content:
application/json:
schema:
type: object
properties:
code:
type: string
required:
- code
responses:
200:
$ref: "#/components/responses/taskResponse"

66
api/parts/responses.yaml Normal file
View file

@ -0,0 +1,66 @@
components:
responses:
userResponse:
description: ""
content:
application/json:
schema:
$ref: "#/components/schemas/userView"
errorResponse:
description: ""
content:
application/json:
schema:
type: object
properties:
code:
type: integer
message:
type: string
required: [code, message]
gameListResponse:
description: ""
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/gameView"
gameResponse:
description: ""
content:
application/json:
schema:
$ref: "#/components/schemas/gameView"
gameAdminResponse:
description: ""
content:
application/json:
schema:
$ref: "#/components/schemas/gameEdit"
taskResponse:
description: ""
content:
application/json:
schema:
$ref: "#/components/schemas/taskView"
uploadResponse:
description: ""
content:
application/json:
schema:
type: object
properties:
uuid:
type: string
format: uuid
required:
- uuid
filesListResponse:
description: ""
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/fileItem"

186
api/parts/schemas.yaml Normal file
View file

@ -0,0 +1,186 @@
components:
schemas:
userView:
type: object
properties:
id:
type: string
format: uuid
username:
type: string
email:
type: string
experience:
type: integer
level:
type: integer
expToCurrentLevel:
type: integer
expToNextLevel:
type: integer
games:
type: array
items:
$ref: "#/components/schemas/gameView"
role:
type: string
enum:
- user
- creator
- admin
required:
- id
- username
- email
- experience
- level
- expToCurrentLevel
- expToNextLevel
- games
- role
gameView:
type: object
properties:
id:
type: string
format: uuid
visible:
type: boolean
title:
type: string
description:
type: string
type:
$ref: "#/components/schemas/gameType"
points:
type: integer
taskCount:
type: integer
createdAt:
type: string
authors:
type: array
items:
$ref: "#/components/schemas/userView"
icon:
type: string
format: uuid
required:
- id
- title
- description
- type
- points
- taskCount
- createdAt
- authors
taskView:
type: object
properties:
message:
type: string
enum:
- ok_code
- invalid_code
- old_code
- next_level
- game_complete
title:
type: string
text:
type: string
codes:
type: array
items:
$ref: "#/components/schemas/codeView"
required:
- title
- text
- codes
codeView:
type: object
properties:
description:
type: string
code:
type: string
gameEdit:
type: object
properties:
id:
type: string
format: uuid
visible:
type: boolean
title:
type: string
description:
type: string
type:
$ref: "#/components/schemas/gameType"
tasks:
type: array
items:
$ref: "#/components/schemas/taskEdit"
points:
type: integer
icon:
type: string
format: uuid
required:
- visible
- title
- description
- type
- tasks
- points
taskEdit:
type: object
properties:
id:
type: string
format: uuid
title:
type: string
text:
type: string
codes:
type: array
items:
$ref: "#/components/schemas/codeEdit"
required:
- id
- title
- text
- codes
codeEdit:
type: object
properties:
id:
type: string
format: uuid
description:
type: string
code:
type: string
required:
- code
gameType:
type: string
enum:
- virtual
- city
fileItem:
type: object
properties:
id:
type: string
format: uuid
originalName:
type: string
size:
type: integer
required:
- id
- originalName
- size

57
api/parts/user.yaml Normal file
View file

@ -0,0 +1,57 @@
paths:
/user:
get:
responses:
200:
$ref: "#/components/responses/userResponse"
403:
$ref: "#/components/responses/errorResponse"
/user/login:
post:
security: []
requestBody:
content:
application/json:
schema:
type: object
properties:
email:
type: string
password:
type: string
required: [email, password]
responses:
200:
$ref: "#/components/responses/userResponse"
400:
$ref: "#/components/responses/errorResponse"
/user/register:
post:
security: []
requestBody:
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"
400:
$ref: "#/components/responses/errorResponse"
/user/logout:
post:
responses:
204:
description: "success logout"
400:
$ref: "#/components/responses/errorResponse"

View file

@ -43,9 +43,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
// (GET /file/{uid})
GetFile(ctx echo.Context, uid openapi_types.UUID) error
// (GET /games) // (GET /games)
GetGames(ctx echo.Context) error GetGames(ctx echo.Context) error
@ -179,24 +176,6 @@ func (w *ServerInterfaceWrapper) EnterCode(ctx echo.Context) error {
return err return err
} }
// GetFile converts echo context to params.
func (w *ServerInterfaceWrapper) GetFile(ctx echo.Context) error {
var err error
// ------------- Path parameter "uid" -------------
var uid openapi_types.UUID
err = runtime.BindStyledParameterWithOptions("simple", "uid", ctx.Param("uid"), &uid, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true})
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter uid: %s", err))
}
ctx.Set(CookieAuthScopes, []string{})
// Invoke the callback with all the unmarshaled arguments
err = w.Handler.GetFile(ctx, uid)
return err
}
// GetGames converts echo context to params. // GetGames converts echo context to params.
func (w *ServerInterfaceWrapper) GetGames(ctx echo.Context) error { func (w *ServerInterfaceWrapper) GetGames(ctx echo.Context) error {
var err error var err error
@ -283,7 +262,6 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
router.GET(baseURL+"/admin/games/:uid", wrapper.AdminGetGame) 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.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)
router.POST(baseURL+"/user/login", wrapper.PostUserLogin) router.POST(baseURL+"/user/login", wrapper.PostUserLogin)
@ -295,27 +273,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{
"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",
"DejHuuwXOmURC2nkK9fl/wEAAP//MtUIV2ocAAA=",
} }
// GetSwagger returns the content of the embedded swagger specification file // GetSwagger returns the content of the embedded swagger specification file

View file

@ -85,7 +85,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"` Id openapi_types.UUID `json:"id"`
Text string `json:"text"` Text string `json:"text"`
Title string `json:"title"` Title string `json:"title"`
} }

View file

@ -29,5 +29,6 @@ module.exports = {
'react' 'react'
], ],
rules: { rules: {
indent: ['error', 4]
} }
} }

View file

@ -117,7 +117,7 @@ const Quest = () => {
{tasks.map(renderTaskForm(remove))} {tasks.map(renderTaskForm(remove))}
<Form.Item wrapperCol={buttonLayout}> <Form.Item wrapperCol={buttonLayout}>
<Button type='primary' onClick={() => add()} block> <Button type='primary' onClick={() => add()} block>
<PlusOutlined/> Добавить уровень <PlusOutlined /> Добавить уровень
</Button> </Button>
</Form.Item> </Form.Item>
</> </>
@ -132,12 +132,12 @@ const Quest = () => {
action={`/api/admin/file/${quest.id}/upload`} action={`/api/admin/file/${quest.id}/upload`}
listType='picture' listType='picture'
maxCount={10} maxCount={10}
itemRender={renderFile} itemRender={(e, file) => renderFile(e, file, quest)}
> >
<Button icon={<UploadOutlined />}>Загрузка</Button> <Button icon={<UploadOutlined />}>Загрузка</Button>
</Upload> </Upload>
Ранее загруженные файлы: Ранее загруженные файлы:
<List dataSource={files} renderItem={renderFileItem} /> <List dataSource={files} renderItem={x => renderFileItem(x, quest)} />
</Col> </Col>
</Row> </Row>
<Modal <Modal
@ -165,8 +165,7 @@ const Quest = () => {
</List.Item> </List.Item>
)} /> )} />
</Modal> </Modal>
</> </>)
)
} }
// eslint-disable-next-line react/display-name // eslint-disable-next-line react/display-name
@ -184,7 +183,7 @@ const renderTaskForm = remove => task => (
cancelText='Нет' cancelText='Нет'
> >
<Button danger> <Button danger>
<CloseOutlined/> Удалить уровень <CloseOutlined /> Удалить уровень
</Button> </Button>
</Popconfirm> </Popconfirm>
]} ]}
@ -204,7 +203,7 @@ const renderTaskForm = remove => task => (
{codes.map(renderCodeForm(codesOpts.remove))} {codes.map(renderCodeForm(codesOpts.remove))}
<Form.Item wrapperCol={{ offset: 6, span: 14 }}> <Form.Item wrapperCol={{ offset: 6, span: 14 }}>
<Button key='addCode' type='primary' onClick={() => codesOpts.add()} block> <Button key='addCode' type='primary' onClick={() => codesOpts.add()} block>
<PlusOutlined/> Добавить код <PlusOutlined /> Добавить код
</Button> </Button>
</Form.Item> </Form.Item>
</> </>
@ -227,7 +226,7 @@ const renderCodeForm = remove => code => (
// cancelText='Нет' // cancelText='Нет'
// > // >
<Button key="delete" danger onClick={() => remove(code.name)}> <Button key="delete" danger onClick={() => remove(code.name)}>
<CloseOutlined/> Удалить код <CloseOutlined /> Удалить код
</Button> </Button>
// </Popconfirm> // </Popconfirm>
]} ]}
@ -244,21 +243,24 @@ const renderCodeForm = remove => code => (
</Card> </Card>
) )
const renderFile = (e, file) => ( const renderFile = (e, file, quest) => {
<div key={file ? file.uid : null}> console.log(file)
return (
<div key={file ? file.uid : null}>
{e} {e}
{file && file.response && file.response.uuid {file && file.response && file.response.uuid
? <>Код для вставки: <pre>![](/api/file/{file.response.uuid})</pre></> ? <>Код для вставки: <pre>![](/file/{quest.id}/{file.originFileObj.name})</pre></>
: null} : null}
</div> </div>
) )
}
const renderFileItem = (file) => ( const renderFileItem = (file, quest) => (
<List.Item> <List.Item>
<List.Item.Meta <List.Item.Meta
avatar={<Avatar src={`/api/file/${file.id}`} />} avatar={<Avatar src={`/file/${quest.id})/${file.originalName}`} />}
title={file.originalName} title={file.originalName}
description={<>Код для вставки: <pre>![](/api/file/{file.id})</pre></>} description={<>Код для вставки: <pre>![](/file/{quest.id}/{file.originalName})</pre></>}
/> />
</List.Item> </List.Item>

View file

@ -8,6 +8,7 @@ const manifest = {
workbox: { workbox: {
cleanupOutdatedCaches: true cleanupOutdatedCaches: true
}, },
base: 'assets',
manifest: { manifest: {
name: 'NQuest', name: 'NQuest',
short_name: 'NQuest', short_name: 'NQuest',
@ -80,6 +81,12 @@ export default defineConfig({
changeOrigin: true, changeOrigin: true,
secure: false, secure: false,
ws: false ws: false
},
'/file': {
target: 'http://localhost:8000',
changeOrigin: true,
secure: false,
ws: false
} }
} }
}, },

View file

@ -127,12 +127,12 @@ func main() {
api.RegisterHandlersWithBaseURL(codegen, handler, "/api") api.RegisterHandlersWithBaseURL(codegen, handler, "/api")
e.FileFS("/", "index.html", distIndexHtml)
e.FileFS("/*", "index.html", distIndexHtml)
e.StaticFS("/", distDirFS)
// --[ System ]-- // --[ System ]--
e.GET("/metrics", echoprometheus.NewHandler()) e.GET("/metrics", echoprometheus.NewHandler())
e.StaticFS("/file", afero.NewIOFS(storage))
e.StaticFS("/*", distDirFS)
e.Logger.Debugf("backend version %s", Version) e.Logger.Debugf("backend version %s", Version)
e.Logger.Fatal(e.Start(cfg.Listen)) e.Logger.Fatal(e.Start(cfg.Listen))
} }

View file

@ -45,7 +45,7 @@ func (a *Admin) AdminEditGame(ctx echo.Context) error {
}) })
} }
tasks = append(tasks, api.TaskEdit{ tasks = append(tasks, api.TaskEdit{
Id: &t.ID, Id: t.ID,
Codes: codes, Codes: codes,
Text: t.Text, Text: t.Text,
Title: t.Title, Title: t.Title,
@ -98,7 +98,7 @@ func (a *Admin) AdminGetGame(ctx echo.Context, uid uuid.UUID) error {
}) })
} }
tasks = append(tasks, api.TaskEdit{ tasks = append(tasks, api.TaskEdit{
Id: &t.ID, Id: t.ID,
Codes: codes, Codes: codes,
Text: t.Text, Text: t.Text,
Title: t.Title, Title: t.Title,
@ -163,12 +163,8 @@ func (*Admin) mapCreateGameRequest(req *api.GameEdit, user *models.User) *models
IconID: req.Icon, IconID: req.Icon,
} }
for order, te := range req.Tasks { for order, te := range req.Tasks {
id := uuid.New()
if te.Id != nil {
id = *te.Id
}
task := &models.Task{ task := &models.Task{
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)),

View file

@ -40,16 +40,6 @@ func (u *File) AdminUploadFile(c echo.Context, quest uuid.UUID) error {
}) })
} }
// (GET /file/{uid})
func (u *File) GetFile(c echo.Context, uid uuid.UUID) error {
f, rdr, err := u.FileService.GetFile(c.Request().Context(), uid)
if err != nil {
return err
}
return c.Stream(200, f.ContentType, rdr)
}
func (u *File) AdminListFiles(c echo.Context, quest uuid.UUID) error { func (u *File) AdminListFiles(c echo.Context, quest uuid.UUID) error {
fl, err := u.FileService.GetFilesByQuest(c.Request().Context(), quest) fl, err := u.FileService.GetFilesByQuest(c.Request().Context(), quest)
if err != nil { if err != nil {

View file

@ -10,6 +10,7 @@ type Task struct {
Title string Title string
Text string Text string
MaxTime int MaxTime int
Game *Game
GameID uuid.UUID GameID uuid.UUID
Codes []*Code `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"` Codes []*Code `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
TaskOrder uint TaskOrder uint

View file

@ -3,7 +3,6 @@ package service
import ( import (
"context" "context"
"fmt" "fmt"
"io"
"mime/multipart" "mime/multipart"
"github.com/google/uuid" "github.com/google/uuid"
@ -53,21 +52,6 @@ func (u *File) Upload(
return file.ID, u.DB.WithContext(ctx).Create(file).Error return file.ID, u.DB.WithContext(ctx).Create(file).Error
} }
func (u *File) GetFile(ctx context.Context, uid uuid.UUID) (*models.File, io.ReadCloser, error) {
f := new(models.File)
if err := u.DB.WithContext(ctx).First(f, uid).Error; err != nil {
return nil, nil, err
}
filePath := fmt.Sprintf("%s/%s", f.QuestID.String(), f.Filename)
file, err := u.store.Open(filePath)
if err != nil {
return nil, nil, err
}
return f, file, nil
}
func (u *File) GetFilesByQuest(ctx context.Context, quest uuid.UUID) ([]*models.File, error) { func (u *File) GetFilesByQuest(ctx context.Context, quest uuid.UUID) ([]*models.File, error) {
list := make([]*models.File, 0) list := make([]*models.File, 0)

View file

@ -68,7 +68,14 @@ func (gs *Game) ListByAuthor(ctx context.Context, author *models.User) ([]*model
} }
func (gs *Game) UpsertGame(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.Debug().
ids := []uuid.UUID{}
for _, t := range game.Tasks {
ids = append(ids, t.ID)
}
gs.DB.Delete([]models.Task{}, `game_id = ? and id not in (?)`, game.ID, ids)
err := gs.DB.
Session(&gorm.Session{FullSaveAssociations: true}). Session(&gorm.Session{FullSaveAssociations: true}).
Clauses(clause.OnConflict{ Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "id"}}, Columns: []clause.Column{{Name: "id"}},
@ -76,6 +83,11 @@ func (gs *Game) UpsertGame(ctx context.Context, game *models.Game) (*models.Game
"title", "description", "title", "description",
}), }),
}). }).
Create(&game). Create(game).
Error Error
if err != nil {
return nil, err
}
return game, gs.DB.Save(game).Error
} }