Первичная имплементация админки
|
@ -9,4 +9,6 @@ steps:
|
|||
repo: gitrepo.ru/neonxp/nquest
|
||||
tags:
|
||||
- latest
|
||||
- ${CI_COMMIT_SHA}
|
||||
- ${CI_COMMIT_SHA}
|
||||
build_args:
|
||||
- VERSION=${CI_COMMIT_SHA}
|
|
@ -122,8 +122,23 @@ paths:
|
|||
schema:
|
||||
type: string
|
||||
format: binary
|
||||
|
||||
|
||||
/games/{uid}:
|
||||
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/gameResponse"
|
||||
components:
|
||||
schemas:
|
||||
userView:
|
||||
|
@ -218,15 +233,15 @@ components:
|
|||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/codeView'
|
||||
solutions:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/solutionView'
|
||||
# solutions:
|
||||
# type: array
|
||||
# items:
|
||||
# $ref: '#/components/schemas/solutionView'
|
||||
required:
|
||||
- title
|
||||
- text
|
||||
- codes
|
||||
- solutions
|
||||
# - solutions
|
||||
codeView:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -281,15 +296,15 @@ components:
|
|||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/codeEdit'
|
||||
solutions:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/solutionEdit'
|
||||
# solutions:
|
||||
# type: array
|
||||
# items:
|
||||
# $ref: '#/components/schemas/solutionEdit'
|
||||
required:
|
||||
- title
|
||||
- text
|
||||
- codes
|
||||
- solutions
|
||||
# - solutions
|
||||
codeEdit:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -300,16 +315,16 @@ components:
|
|||
required:
|
||||
- description
|
||||
- code
|
||||
solutionEdit:
|
||||
type: object
|
||||
properties:
|
||||
text:
|
||||
type: string
|
||||
after:
|
||||
type: integer
|
||||
required:
|
||||
- after
|
||||
- text
|
||||
# solutionEdit:
|
||||
# type: object
|
||||
# properties:
|
||||
# text:
|
||||
# type: string
|
||||
# after:
|
||||
# type: integer
|
||||
# required:
|
||||
# - after
|
||||
# - text
|
||||
gameType:
|
||||
type: string
|
||||
enum:
|
||||
|
|
|
@ -40,6 +40,9 @@ type ServerInterface interface {
|
|||
// (POST /games)
|
||||
CreateGame(ctx echo.Context) error
|
||||
|
||||
// (POST /games/{uid})
|
||||
EditGame(ctx echo.Context, uid openapi_types.UUID) error
|
||||
|
||||
// (GET /user)
|
||||
GetUser(ctx echo.Context) error
|
||||
|
||||
|
@ -145,6 +148,24 @@ func (w *ServerInterfaceWrapper) CreateGame(ctx echo.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// EditGame converts echo context to params.
|
||||
func (w *ServerInterfaceWrapper) EditGame(ctx echo.Context) error {
|
||||
var err error
|
||||
// ------------- Path parameter "uid" -------------
|
||||
var uid openapi_types.UUID
|
||||
|
||||
err = runtime.BindStyledParameterWithLocation("simple", false, "uid", runtime.ParamLocationPath, ctx.Param("uid"), &uid)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter uid: %s", err))
|
||||
}
|
||||
|
||||
ctx.Set(CookieAuthScopes, []string{"creator", "admin"})
|
||||
|
||||
// Invoke the callback with all the unmarshaled arguments
|
||||
err = w.Handler.EditGame(ctx, uid)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetUser converts echo context to params.
|
||||
func (w *ServerInterfaceWrapper) GetUser(ctx echo.Context) error {
|
||||
var err error
|
||||
|
@ -219,6 +240,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
|
|||
router.GET(baseURL+"/file/:uid", wrapper.GetFile)
|
||||
router.GET(baseURL+"/games", wrapper.GetGames)
|
||||
router.POST(baseURL+"/games", wrapper.CreateGame)
|
||||
router.POST(baseURL+"/games/:uid", wrapper.EditGame)
|
||||
router.GET(baseURL+"/user", wrapper.GetUser)
|
||||
router.POST(baseURL+"/user/login", wrapper.PostUserLogin)
|
||||
router.POST(baseURL+"/user/logout", wrapper.PostUserLogout)
|
||||
|
@ -229,26 +251,26 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
|
|||
// Base64 encoded, gzipped, json marshaled Swagger object
|
||||
var swaggerSpec = []string{
|
||||
|
||||
"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",
|
||||
"Ymo063X9bwAAAP//ZDhhHgMaAAA=",
|
||||
}
|
||||
|
||||
// GetSwagger returns the content of the embedded swagger specification file
|
||||
|
|
31
api/types.go
|
@ -71,33 +71,19 @@ type GameView struct {
|
|||
Type GameType `json:"type"`
|
||||
}
|
||||
|
||||
// SolutionEdit defines model for solutionEdit.
|
||||
type SolutionEdit struct {
|
||||
After int `json:"after"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
// SolutionView defines model for solutionView.
|
||||
type SolutionView struct {
|
||||
After int `json:"after"`
|
||||
Text *string `json:"text,omitempty"`
|
||||
}
|
||||
|
||||
// TaskEdit defines model for taskEdit.
|
||||
type TaskEdit struct {
|
||||
Codes []CodeEdit `json:"codes"`
|
||||
Solutions []SolutionEdit `json:"solutions"`
|
||||
Text string `json:"text"`
|
||||
Title string `json:"title"`
|
||||
Codes []CodeEdit `json:"codes"`
|
||||
Text string `json:"text"`
|
||||
Title string `json:"title"`
|
||||
}
|
||||
|
||||
// TaskView defines model for taskView.
|
||||
type TaskView struct {
|
||||
Codes []CodeView `json:"codes"`
|
||||
Message *TaskViewMessage `json:"message,omitempty"`
|
||||
Solutions []SolutionView `json:"solutions"`
|
||||
Text string `json:"text"`
|
||||
Title string `json:"title"`
|
||||
Codes []CodeView `json:"codes"`
|
||||
Message *TaskViewMessage `json:"message,omitempty"`
|
||||
Text string `json:"text"`
|
||||
Title string `json:"title"`
|
||||
}
|
||||
|
||||
// TaskViewMessage defines model for TaskView.Message.
|
||||
|
@ -195,6 +181,9 @@ type UploadFileMultipartRequestBody UploadFileMultipartBody
|
|||
// CreateGameJSONRequestBody defines body for CreateGame for application/json ContentType.
|
||||
type CreateGameJSONRequestBody = GameEdit
|
||||
|
||||
// EditGameJSONRequestBody defines body for EditGame for application/json ContentType.
|
||||
type EditGameJSONRequestBody = GameEdit
|
||||
|
||||
// PostUserLoginJSONRequestBody defines body for PostUserLogin for application/json ContentType.
|
||||
type PostUserLoginJSONRequestBody PostUserLoginJSONBody
|
||||
|
||||
|
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 9.1 KiB |
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 9 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 76 KiB |
Before Width: | Height: | Size: 3 KiB After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 6.5 KiB |
|
@ -14,6 +14,7 @@ import Engine from './pages/Engine'
|
|||
import Quests from './pages/Quests'
|
||||
import User from './pages/User'
|
||||
import { useRole } from './utils/roles'
|
||||
import Quest from './pages/admin/Quest'
|
||||
|
||||
const router = createBrowserRouter(
|
||||
createRoutesFromElements(
|
||||
|
@ -48,7 +49,7 @@ const router = createBrowserRouter(
|
|||
/>
|
||||
<Route
|
||||
path="quest/new"
|
||||
element={<Auth role="creator"><NoMatch /></Auth>}
|
||||
element={<Auth role="creator"><Quest /></Auth>}
|
||||
// loader={() => ajax(`/api/admin/games`)}
|
||||
/>
|
||||
|
||||
|
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 6.5 KiB |
Before Width: | Height: | Size: 4.3 KiB |
|
@ -2,9 +2,9 @@
|
|||
font-size: 26px;
|
||||
padding-top: 0 !important;
|
||||
padding-bottom: 0 !important;
|
||||
height: 40px;
|
||||
width: 135px;
|
||||
background-image: url("./logo_small.png");
|
||||
height: 48px;
|
||||
width: 54px;
|
||||
background-image: url("./logo.png");
|
||||
background-size: cover;
|
||||
display: inline-block;
|
||||
margin-right: 50px;
|
||||
|
|
|
@ -32,10 +32,10 @@ const Login = () => {
|
|||
|
||||
return (<>
|
||||
<h1>Вход</h1>
|
||||
{error ? <Alert type="error" message={error} /> : null}
|
||||
{error ? <Alert type='error' message={error} /> : null}
|
||||
<Form
|
||||
form={form}
|
||||
name="login"
|
||||
name='login'
|
||||
labelCol={{
|
||||
span: 8
|
||||
}}
|
||||
|
@ -48,8 +48,8 @@ const Login = () => {
|
|||
|
||||
onFinish={onFinish}>
|
||||
<Form.Item
|
||||
label="E-mail"
|
||||
name="email"
|
||||
label='E-mail'
|
||||
name='email'
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
|
@ -62,13 +62,13 @@ const Login = () => {
|
|||
]}
|
||||
>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="name@mail.ru"
|
||||
type='email'
|
||||
placeholder='name@mail.ru'
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Пароль"
|
||||
name="password"
|
||||
label='Пароль'
|
||||
name='password'
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
|
@ -84,7 +84,7 @@ const Login = () => {
|
|||
span: 16
|
||||
}}
|
||||
>
|
||||
<Button type="primary" htmlType="submit">
|
||||
<Button type='primary' htmlType='submit'>
|
||||
Вход
|
||||
</Button>
|
||||
</Form.Item>
|
||||
|
|
|
@ -1,5 +1,177 @@
|
|||
const Quest = () => {
|
||||
import { useLoaderData, useNavigate } from 'react-router-dom'
|
||||
import { Alert, Button, Card, Form, Input, InputNumber, Popconfirm, Radio, Typography, Upload } from 'antd'
|
||||
import { UploadOutlined, PlusOutlined, CloseOutlined } from '@ant-design/icons'
|
||||
import { ajax } from '../../utils/fetch'
|
||||
import { useState } from 'react'
|
||||
|
||||
const { Title } = Typography
|
||||
|
||||
const Quest = () => {
|
||||
let quest = useLoaderData()
|
||||
const [error, setError] = useState()
|
||||
const navigate = useNavigate()
|
||||
const normFile = (e) => {
|
||||
if (Array.isArray(e)) {
|
||||
return e
|
||||
}
|
||||
if (e.file.response) {
|
||||
return e.file.response.uuid
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
const formItemLayout = {
|
||||
labelCol: { span: 6 },
|
||||
wrapperCol: { span: 14 }
|
||||
}
|
||||
const buttonLayout = {
|
||||
offset: 6,
|
||||
span: 14
|
||||
}
|
||||
|
||||
const onFinish = (values) => {
|
||||
ajax('/api/games', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(values)
|
||||
})
|
||||
.then(g => navigate(`/quest/${g.id}/edit`))
|
||||
.catch(({ message }) => setError('Ошибка создания'))
|
||||
}
|
||||
|
||||
if (!quest) {
|
||||
quest = {
|
||||
type: 'city',
|
||||
points: 10,
|
||||
tasks: []
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Title>{quest.title ? (quest.title) : ('Новый квест')}</Title>
|
||||
{error ? <Alert type="error" message={error} /> : null}
|
||||
<Form
|
||||
initialValues={quest}
|
||||
onFinish={onFinish}
|
||||
{...formItemLayout}
|
||||
>
|
||||
<Form.Item wrapperCol={buttonLayout}>
|
||||
<Button type='primary' htmlType='submit' block>
|
||||
Сохранить квест
|
||||
</Button>
|
||||
</Form.Item>
|
||||
<Form.Item label='Название' name='title'>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label='Описание' name='description' help='Поддерживается Markdown'>
|
||||
<Input.TextArea />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name='icon'
|
||||
label='Иконка'
|
||||
getValueFromEvent={normFile}
|
||||
>
|
||||
<Upload name='file' action='/api/file/upload' listType='picture' maxCount={1}>
|
||||
<Button icon={<UploadOutlined />}>Загрузка</Button>
|
||||
</Upload>
|
||||
</Form.Item>
|
||||
<Form.Item label='Тип квеста' name='type'>
|
||||
<Radio.Group>
|
||||
<Radio.Button value='city'>Полевой</Radio.Button>
|
||||
<Radio.Button value='virtual'>Виртуальный</Radio.Button>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
<Form.Item label='Очков опыта за квест' name='points'>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.List name='tasks'>
|
||||
{(tasks, { add, remove }) => (
|
||||
<>
|
||||
{tasks.map(renderTaskForm(remove))}
|
||||
<Form.Item wrapperCol={buttonLayout}>
|
||||
<Button type='primary' onClick={() => add()} block>
|
||||
<PlusOutlined/> Добавить уровень
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
</Form.List>
|
||||
</Form>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/display-name
|
||||
const renderTaskForm = remove => task => (
|
||||
<Card
|
||||
key={task.key}
|
||||
title={`Уровень ${task.key}`}
|
||||
style={{ marginBottom: 8 }}
|
||||
actions={[
|
||||
<Popconfirm
|
||||
key={'removeLevel'}
|
||||
title='Удалить уровень?'
|
||||
onConfirm={() => remove(task.name)}
|
||||
okText='Да'
|
||||
cancelText='Нет'
|
||||
>
|
||||
<Button danger>
|
||||
<CloseOutlined/> Удалить уровень
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
]}
|
||||
>
|
||||
<Form.Item name={[task.name, 'title']} label='Название уровня' help='ВИДНО игрокам'>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name={[task.name, 'text']} label='Текст задания' help='Поддерживается Markdown'>
|
||||
<Input.TextArea />
|
||||
</Form.Item>
|
||||
<Form.List name={[task.name, 'codes']}>
|
||||
{(codes, codesOpts) => (
|
||||
<>
|
||||
{codes.map(renderCodeForm(codesOpts.remove))}
|
||||
<Form.Item wrapperCol={{ offset: 6, span: 14 }}>
|
||||
<Button key='addCode' type='primary' onClick={() => codesOpts.add()} block>
|
||||
<PlusOutlined/> Добавить код
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
</Form.List>
|
||||
</Card>
|
||||
)
|
||||
|
||||
// eslint-disable-next-line react/display-name
|
||||
const renderCodeForm = remove => code => (
|
||||
<Card
|
||||
key={code.key}
|
||||
style={{ marginBottom: 8 }}
|
||||
actions={[
|
||||
<Popconfirm
|
||||
key='delete'
|
||||
title='Удалить код?'
|
||||
onConfirm={() => remove(code.name)}
|
||||
okText='Да'
|
||||
cancelText='Нет'
|
||||
>
|
||||
<Button danger>
|
||||
<CloseOutlined/> Удалить код
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
]}
|
||||
>
|
||||
<Form.Item name={[code.name, 'code']} label='Код'>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name={[code.name, 'description']} label='Описание кода' help='Видно игрокам всегда'>
|
||||
<Input.TextArea />
|
||||
</Form.Item>
|
||||
</Card>
|
||||
)
|
||||
|
||||
export default Quest
|
||||
|
|
|
@ -45,6 +45,35 @@ func (a *Admin) CreateGame(ctx echo.Context) error {
|
|||
})
|
||||
}
|
||||
|
||||
// (POST /games/{uid})
|
||||
func (a *Admin) EditGame(ctx echo.Context, uid uuid.UUID) error {
|
||||
user := contextlib.GetUser(ctx)
|
||||
req := &api.GameEditRequest{}
|
||||
if err := ctx.Bind(req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
game := a.mapCreateGameRequest(req, user)
|
||||
|
||||
var err error
|
||||
game, err = a.GameService.UpdateGame(ctx.Request().Context(), uid, game)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ctx.JSON(http.StatusCreated, api.GameResponse{
|
||||
Authors: make([]api.UserView, 0, len(game.Authors)),
|
||||
CreatedAt: game.CreatedAt.Format(time.RFC3339),
|
||||
Description: game.Description,
|
||||
Icon: game.IconID,
|
||||
Id: game.ID,
|
||||
Points: game.Points,
|
||||
TaskCount: len(game.Tasks),
|
||||
Title: game.Title,
|
||||
Type: api.MapGameTypeReverse(game.Type),
|
||||
})
|
||||
}
|
||||
|
||||
func (*Admin) mapCreateGameRequest(req *api.GameEditRequest, user *models.User) *models.Game {
|
||||
game := &models.Game{
|
||||
Model: models.Model{
|
||||
|
@ -66,22 +95,22 @@ func (*Admin) mapCreateGameRequest(req *api.GameEditRequest, user *models.User)
|
|||
Model: models.Model{
|
||||
ID: uuid.New(),
|
||||
},
|
||||
Title: te.Title,
|
||||
Text: te.Text,
|
||||
MaxTime: 0,
|
||||
Solutions: make([]*models.Solution, 0, len(te.Solutions)),
|
||||
Title: te.Title,
|
||||
Text: te.Text,
|
||||
MaxTime: 0,
|
||||
// Solutions: make([]*models.Solution, 0, len(te.Solutions)),
|
||||
Codes: make([]*models.Code, 0, len(te.Codes)),
|
||||
TaskOrder: uint(order),
|
||||
}
|
||||
for _, s := range te.Solutions {
|
||||
task.Solutions = append(task.Solutions, &models.Solution{
|
||||
Model: models.Model{
|
||||
ID: uuid.New(),
|
||||
},
|
||||
After: s.After,
|
||||
Text: s.Text,
|
||||
})
|
||||
}
|
||||
// for _, s := range te.Solutions {
|
||||
// task.Solutions = append(task.Solutions, &models.Solution{
|
||||
// Model: models.Model{
|
||||
// ID: uuid.New(),
|
||||
// },
|
||||
// After: s.After,
|
||||
// Text: s.Text,
|
||||
// })
|
||||
// }
|
||||
for _, ce := range te.Codes {
|
||||
task.Codes = append(task.Codes, &models.Code{
|
||||
Model: models.Model{
|
||||
|
|
|
@ -75,11 +75,11 @@ func (ec *Engine) EnterCode(c echo.Context, uid uuid.UUID) error {
|
|||
|
||||
func mapCursorToTask(cursor *models.GameCursor, message *api.TaskViewMessage) *api.TaskView {
|
||||
resp := &api.TaskResponse{
|
||||
Message: message,
|
||||
Codes: make([]api.CodeView, 0, len(cursor.Task.Codes)),
|
||||
Solutions: []api.SolutionView{},
|
||||
Text: cursor.Task.Text,
|
||||
Title: cursor.Task.Title,
|
||||
Message: message,
|
||||
Codes: make([]api.CodeView, 0, len(cursor.Task.Codes)),
|
||||
// Solutions: []api.SolutionView{},
|
||||
Text: cursor.Task.Text,
|
||||
Title: cursor.Task.Title,
|
||||
}
|
||||
for _, code := range cursor.Task.Codes {
|
||||
c := api.CodeView{
|
||||
|
|
|
@ -71,3 +71,12 @@ func (gs *Game) CreateGame(ctx context.Context, game *models.Game) (*models.Game
|
|||
Create(game).
|
||||
Error
|
||||
}
|
||||
|
||||
func (gs *Game) UpdateGame(ctx context.Context, uid uuid.UUID, game *models.Game) (*models.Game, error) {
|
||||
game.ID = uid
|
||||
|
||||
return game, gs.DB.
|
||||
Session(&gorm.Session{FullSaveAssociations: true}).
|
||||
Save(game).
|
||||
Error
|
||||
}
|
||||
|
|