фиксы и статистика игроков
This commit is contained in:
parent
1ad012b8fa
commit
392b6df0d5
15 changed files with 425 additions and 28 deletions
7
.env
Normal file
7
.env
Normal file
|
@ -0,0 +1,7 @@
|
|||
POSTGRES_HOSTNAME=db
|
||||
POSTGRES_DB=nquest
|
||||
POSTGRES_USER=nquest
|
||||
POSTGRES_PASSWORD=nquest
|
||||
POSTGRES_PORT=5432
|
||||
LISTEN=:8000
|
||||
SECRET=d51a5056-c8ad-4c2a-939e-c60cff9c1214
|
|
@ -1,8 +1,7 @@
|
|||
{
|
||||
debug
|
||||
}
|
||||
|
||||
http:// {
|
||||
header /* {
|
||||
Cache-Control: no-cache, no-store, must-revalidate
|
||||
}
|
||||
handle_path /file/* {
|
||||
root * /app/store
|
||||
file_server
|
||||
|
|
|
@ -66,6 +66,19 @@ components:
|
|||
schema:
|
||||
$ref: '#/components/schemas/userView'
|
||||
description: ""
|
||||
userShortResponse:
|
||||
content:
|
||||
application/json:
|
||||
$ref: '#/components/schemas/userShortView'
|
||||
description: ""
|
||||
usersListResponse:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/components/schemas/userShortView'
|
||||
type: array
|
||||
description: ""
|
||||
schemas:
|
||||
codeEdit:
|
||||
properties:
|
||||
|
@ -213,6 +226,37 @@ components:
|
|||
- text
|
||||
- codes
|
||||
type: object
|
||||
userShortView:
|
||||
properties:
|
||||
expToCurrentLevel:
|
||||
type: integer
|
||||
expToNextLevel:
|
||||
type: integer
|
||||
experience:
|
||||
type: integer
|
||||
games:
|
||||
items:
|
||||
$ref: '#/components/schemas/gameView'
|
||||
type: array
|
||||
gamesFinished:
|
||||
type: integer
|
||||
gamesStarted:
|
||||
type: integer
|
||||
id:
|
||||
format: uuid
|
||||
type: string
|
||||
level:
|
||||
type: integer
|
||||
username:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- username
|
||||
- experience
|
||||
- level
|
||||
- gamesStarted
|
||||
- gamesFinished
|
||||
type: object
|
||||
userView:
|
||||
properties:
|
||||
email:
|
||||
|
@ -457,6 +501,26 @@ paths:
|
|||
400:
|
||||
$ref: '#/components/responses/errorResponse'
|
||||
security: []
|
||||
/users:
|
||||
get:
|
||||
responses:
|
||||
200:
|
||||
$ref: '#/components/responses/usersListResponse'
|
||||
description: users list
|
||||
/users/{uid}:
|
||||
get:
|
||||
operationId: getUserInfo
|
||||
parameters:
|
||||
- in: path
|
||||
name: uid
|
||||
required: true
|
||||
schema:
|
||||
format: uuid
|
||||
type: string
|
||||
responses:
|
||||
200:
|
||||
$ref: '#/components/responses/userShortResponse'
|
||||
description: users list
|
||||
security:
|
||||
- cookieAuth: []
|
||||
servers:
|
||||
|
|
|
@ -63,4 +63,17 @@ components:
|
|||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/fileItem"
|
||||
$ref: "#/components/schemas/fileItem"
|
||||
usersListResponse:
|
||||
description: ""
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/userShortView"
|
||||
userShortResponse:
|
||||
description: ""
|
||||
content:
|
||||
application/json:
|
||||
$ref: "#/components/schemas/userShortView"
|
|
@ -1,5 +1,36 @@
|
|||
components:
|
||||
schemas:
|
||||
userShortView:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
username:
|
||||
type: string
|
||||
experience:
|
||||
type: integer
|
||||
level:
|
||||
type: integer
|
||||
gamesStarted:
|
||||
type: integer
|
||||
gamesFinished:
|
||||
type: integer
|
||||
expToCurrentLevel:
|
||||
type: integer
|
||||
expToNextLevel:
|
||||
type: integer
|
||||
games:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/gameView"
|
||||
required:
|
||||
- id
|
||||
- username
|
||||
- experience
|
||||
- level
|
||||
- gamesStarted
|
||||
- gamesFinished
|
||||
userView:
|
||||
type: object
|
||||
properties:
|
||||
|
|
|
@ -55,3 +55,23 @@ paths:
|
|||
description: "success logout"
|
||||
400:
|
||||
$ref: "#/components/responses/errorResponse"
|
||||
/users:
|
||||
get:
|
||||
responses:
|
||||
200:
|
||||
$ref: "#/components/responses/usersListResponse"
|
||||
description: "users list"
|
||||
/users/{uid}:
|
||||
get:
|
||||
operationId: getUserInfo
|
||||
parameters:
|
||||
- name: uid
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
200:
|
||||
$ref: "#/components/responses/userShortResponse"
|
||||
description: "users list"
|
|
@ -60,6 +60,12 @@ type ServerInterface interface {
|
|||
|
||||
// (POST /user/register)
|
||||
PostUserRegister(ctx echo.Context) error
|
||||
|
||||
// (GET /users)
|
||||
GetUsers(ctx echo.Context) error
|
||||
|
||||
// (GET /users/{uid})
|
||||
GetUserInfo(ctx echo.Context, uid openapi_types.UUID) error
|
||||
}
|
||||
|
||||
// ServerInterfaceWrapper converts echo contexts to parameters.
|
||||
|
@ -248,6 +254,35 @@ func (w *ServerInterfaceWrapper) PostUserRegister(ctx echo.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// GetUsers converts echo context to params.
|
||||
func (w *ServerInterfaceWrapper) GetUsers(ctx echo.Context) error {
|
||||
var err error
|
||||
|
||||
ctx.Set(CookieAuthScopes, []string{})
|
||||
|
||||
// Invoke the callback with all the unmarshaled arguments
|
||||
err = w.Handler.GetUsers(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetUserInfo converts echo context to params.
|
||||
func (w *ServerInterfaceWrapper) GetUserInfo(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.GetUserInfo(ctx, uid)
|
||||
return err
|
||||
}
|
||||
|
||||
// This is a simple interface which specifies echo.Route addition functions which
|
||||
// are present on both echo.Echo and echo.Group, since we want to allow using
|
||||
// either of them for path registration
|
||||
|
@ -289,33 +324,36 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
|
|||
router.POST(baseURL+"/user/login", wrapper.PostUserLogin)
|
||||
router.POST(baseURL+"/user/logout", wrapper.PostUserLogout)
|
||||
router.POST(baseURL+"/user/register", wrapper.PostUserRegister)
|
||||
router.GET(baseURL+"/users", wrapper.GetUsers)
|
||||
router.GET(baseURL+"/users/:uid", wrapper.GetUserInfo)
|
||||
|
||||
}
|
||||
|
||||
// Base64 encoded, gzipped, json marshaled Swagger object
|
||||
var swaggerSpec = []string{
|
||||
|
||||
"H4sIAAAAAAAC/8xY30/kthP/V77yt48p2TtOqpQ3iig6FZ3aK9cXtEImGRYfiR3sCccW5X+vxk6yycb5",
|
||||
"wZJy98SSTObHZz4znvEzi1WWKwkSDYuemQaTK2nA/gNaK/25ekIPYiURJNJPnuepiDkKJcOvRkl6ZuI7",
|
||||
"yDj9yrXKQaNwemKV2M9xmwOLmJAIG9CsDFgGxvBN+6VBLeSGlWXANDwUQkPCoiunYie/Dmp5dfMVYmQl",
|
||||
"fZCAibXIyScWMdJ/K1IwF8LgQVEIhMwG8JOGWxax/4c7sEInZkIy8REhI3OVT1xrvh1yacMzOEkyIQ9y",
|
||||
"acwT0nyWCByz/N9iQRb+FvBtLhbIzf3iMJBS54TfaJGniicL0LooREJ/b5XOOLLIPQgmmGyF5vK3MKAX",
|
||||
"B4iUDgNUBpWapnQtp6aKuo52T6Hn/SGgWWN90AL7wsaylH+lx0hT4j0js2IJmNJiIyRPP/HM75MR/3g7",
|
||||
"5B4MVntHWfWpD5qmHfS8nsxQ7F5MxjUz/FyJ6njpnwBUrWZ2gyFp1+L2G0zAUGDqR9c9mG5dlyRXBuxR",
|
||||
"GHHT0XWjVApc9hJSS9bWu+yqLNdBNkAMZeuy8hNkkTntGguesoDFAretz3ahNR23l2Re4J3S86HddYU+",
|
||||
"tLEGjpCc4GEF/8Z0OlWFxIHX34EjLogxelQBtb1vYx40ufQRpykJbwecn/+m03vyPzMxCE9+hgzBPoaU",
|
||||
"VRZUQQwFPtz6Xxb4EPFb82ldlOr+uhpGhXzkqUjqf1Xa/JTwhNcpPALVLrHmmuymgOAt4tfiNhuypsZ7",
|
||||
"kEHGRer1AZ7yS3VaaA0SL2xI3sqyYp/gaUIGtAAZD2wDhJRZYNicTdh02Fet0k7aCbq6KhX94jTDe9NJ",
|
||||
"otJ/0vsI34gHVRY6QNVO+hLRQ72GsHLfM2UGzEBcaIHbvwjGul7UvYCTAu8s+DQHukdEZRsIM2BMq2dF",
|
||||
"jOfid6gGeyFvlQ3W0ZXJPwswxMRH0MbNle+OVkcrOwrlIHkuWMSOj94draj7cbyzboQWUrtQhc8PpKKk",
|
||||
"xxuwxUFstdPux4RFzG5QtMz8Rhue1aJ5Bgh04l1VQZDmXQgPlVO7BKAuIGgNzVMz6Tro7sfvV6shkjZy",
|
||||
"YX8FbWfBOtvG/6pPsXJNX3jQCd0qY6tZmSGUvlghwuktYbJqflXJdm9tyYoURc41hqTm54SjZ7OiIDuW",
|
||||
"boTkeutdEzx71MuTtLcTvipDTRcbJ+55Xakvdra3x9ugRxhAx/q56zDDiVnm3qE8NKDulcjrExA+FyKZ",
|
||||
"6B/nUMMyXRaO6m/cOxYDBuRGSJjAhLA4s4I/LiKdGyPHt05wYb32+8vhTCLoUzekvVGEh9XbrLuMeZck",
|
||||
"BxalD2l3+IyTCHD2UbMgg0j8ePVL73aDaUiErmefFgqySFMb03i7rlrEco2aTNpZcsTiFzdrHnCMta8L",
|
||||
"y4B9WB1Pf9S95W+5GKZqI+RwMf2hjHX1wootxfXhTSTnxnxTOpmuhHqObr5YrCr6CK9ejnCnha87eKsC",
|
||||
"ZwFOcj3/P/Tpb4o4BmP+V6k+1OOdjxo2wqDj77iXn2vJ78qM3cv33rfzlzXPntbYbVv5sbk2Oj+sqaUa",
|
||||
"0I910y50yiIW0sJWrst/AwAA//8rjKvDLRwAAA==",
|
||||
"H4sIAAAAAAAC/9RZX2+cOBD/KiffPXJh+0c6ad9yURpFF1V3TXov0SpyYLLrBmximzR7Ed/9NDawsNjA",
|
||||
"Epq2T93CMH9+85uxZ/JMIpFmggPXiiyfiQSVCa7A/AekFPJT+QQfRIJr4Bp/0ixLWEQ1Ezz8ogTHZyra",
|
||||
"QErxVyZFBlIzqycSsflcbzMgS8K4hjVIUgQkBaXouvlSacn4mhRFQCQ85ExCTJbXVsVOfhVU8uL2C0Sa",
|
||||
"FPhBDCqSLEOfyJKg/juWgLpgSk+KgmlITQC/SbgjS/JruAMrtGIqRBPnGlI0V/pEpaRbn0trmsJxnDI+",
|
||||
"yaU+T1Dzacx0n+VviwVa+JfB17FYaKruZ4cBlVon3EbzLBE0noHWec5i/PdOyJRqsrQPggEmG6Gx/M0V",
|
||||
"yNkBQqW9ACmQlxshDyHKkD2jrzTqs/mNC3XPjTEMLYLSSN3GTH0NNbgq83sKHe+nEMgY6xIoMC9MbHP5",
|
||||
"VziM1O2uY2RULAERkq0Zp8lHmrp9Uuw/52mxB4PR3lJWfuqCpm6NHa8HMxTZF4NxjQw/E6w8arunIXYu",
|
||||
"NZrPKG3b/T6VA6KZTtzo2gfDbfwK5YqAPDLFblu6boVIgPJOQirJynqbXaXlKsgaCF+2rko/geep1S51",
|
||||
"ThMSkIjpbeOzXWj16dNJMs31Rkh1UKtwd4mARBKohvhYTyv4V6bTici59rz+DhyxQfTRowyo6X0T86DO",
|
||||
"pYs4dUk4O+D4/Ned3pH/kYnR8ORmiA/2PqSMsqAMwhe4v/UfFriP+I27elWU4v6mvJgz/kgTFlf/FUn9",
|
||||
"k8OTvkngEbB2kTU3aDcBDc4ifiluoyFrXwc6uMFTdiVOcimB6wvjvbOIjNhHeBqQAcmAR54hCEFRM9yx",
|
||||
"S00fGGdqA3GPsUtNpfZJjCR44g8YoeXu493F8lq8BVVlYc/l/SB9qfVkNaUscdLrp8z3y1MlRdKqaISu",
|
||||
"argCf1EcVZ2V+oIsmyx4st1NRAf1CsLSfccwFRAFUS6Z3l4ijFUrFPcMjnO9MeDjFd8+wi5lAiEKlGoc",
|
||||
"R0tCM/YXlNMB43fCBGs7EeH/5KCwyTyCVHZkeHO0OFqYW24GnGaMLMm7ozdHCzzYqN4YN0IDqdkbhM8P",
|
||||
"qKLAx2swfQ/Zasac85gsiVkU4Fj0gSUm4IxKmoIGvMxcl0Gg5l0ID6VTuwRomUPQmJaGxo1V0F4DvV0s",
|
||||
"fCSt5cLupqWZBeNsE//rLsWKFX7hQCe0E7upZqF8KH02QojTa8Jk1Pwp4u3evJrmiWYZlTpENb/HVDsW",
|
||||
"CBhky9It41RunROgY11weJL2Vh8vylDdxfqJe1ZV6sHOdtZVJugeBuCN7cx2GH9i5lmvFVMDam/+Xp6A",
|
||||
"8Dln8UD/OIMKluGysFR/5d4xGzDA14zDACaIxakR/HERaS1GLd9awYXVRsddDqdcgzyx9+9XinBavY1a",
|
||||
"U43bf00sShfS9vDpJxHo0UfNjAxC8XeLPzqLKyIhZrK6+zRQ4HmSmJj623XZIuZr1GjS3CV7LH62d80J",
|
||||
"x1hzK14E5P3i3fBH7T9mNVwME7Fm3F9MfwtlXL0wYnNx3T+JZFSpr0LGw5VQ3aPrL2arii7Ci8MRbrXw",
|
||||
"VQtvketRgKNcx//3XfqrPIpAqV9K1VM93vkoYc2Utvzt9/JTJfldmbF7+db5dvyw5pjTartNKz8F19RQ",
|
||||
"/1GTG5DytTw1fHSg5XOcJn/YC0j3b4CFDdF/K1uhJQXysYollwlZkhDH4GJV/B8AAP//bSQu1mogAAA=",
|
||||
}
|
||||
|
||||
// GetSwagger returns the content of the embedded swagger specification file
|
||||
|
|
19
api/types.go
19
api/types.go
|
@ -101,6 +101,19 @@ type TaskView struct {
|
|||
// TaskViewMessage defines model for TaskView.Message.
|
||||
type TaskViewMessage string
|
||||
|
||||
// UserShortView defines model for userShortView.
|
||||
type UserShortView struct {
|
||||
ExpToCurrentLevel *int `json:"expToCurrentLevel,omitempty"`
|
||||
ExpToNextLevel *int `json:"expToNextLevel,omitempty"`
|
||||
Experience int `json:"experience"`
|
||||
Games *[]GameView `json:"games,omitempty"`
|
||||
GamesFinished int `json:"gamesFinished"`
|
||||
GamesStarted int `json:"gamesStarted"`
|
||||
Id openapi_types.UUID `json:"id"`
|
||||
Level int `json:"level"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
// UserView defines model for userView.
|
||||
type UserView struct {
|
||||
Email string `json:"email"`
|
||||
|
@ -143,6 +156,12 @@ type UploadResponse struct {
|
|||
// UserResponse defines model for userResponse.
|
||||
type UserResponse = UserView
|
||||
|
||||
// UserShortResponse defines model for userShortResponse.
|
||||
type UserShortResponse interface{}
|
||||
|
||||
// UsersListResponse defines model for usersListResponse.
|
||||
type UsersListResponse = []UserShortView
|
||||
|
||||
// AdminUploadFileMultipartBody defines parameters for AdminUploadFile.
|
||||
type AdminUploadFileMultipartBody interface{}
|
||||
|
||||
|
|
|
@ -16,6 +16,8 @@ import User from './pages/User'
|
|||
import { useRole } from './utils/roles'
|
||||
import EditQuest from './pages/admin/Quest'
|
||||
import AdminQuest from './pages/admin/Quests'
|
||||
import Users from './pages/Users'
|
||||
import UserView from './pages/UserView'
|
||||
|
||||
const router = createBrowserRouter(
|
||||
createRoutesFromElements(
|
||||
|
@ -35,6 +37,18 @@ const router = createBrowserRouter(
|
|||
element={<Auth><Quests /></Auth>}
|
||||
loader={() => ajax('/api/games').catch(x => { console.log(x); return null })}
|
||||
/>
|
||||
<Route
|
||||
id="users"
|
||||
path="/users"
|
||||
element={<Auth><Users /></Auth>}
|
||||
loader={() => ajax('/api/users').catch(x => { console.log(x); return null })}
|
||||
/>
|
||||
<Route
|
||||
id="user_view"
|
||||
path="/users/:userId"
|
||||
element={<Auth><UserView /></Auth>}
|
||||
loader={({ params }) => ajax(`/api/users/${params.userId}`).catch(x => { console.log(x); return null })}
|
||||
/>
|
||||
<Route path="me" element={<User />} />
|
||||
<Route path="login" element={<Login />} />
|
||||
<Route path="register" element={<Register />} />
|
||||
|
|
|
@ -35,6 +35,11 @@ const AppLayout = () => {
|
|||
label: 'Квесты',
|
||||
link: '/quests'
|
||||
},
|
||||
{
|
||||
key: 'users',
|
||||
label: 'Игроки',
|
||||
link: '/users'
|
||||
},
|
||||
{
|
||||
key: 'me',
|
||||
label: `${user.username} [${user.level}]`,
|
||||
|
|
34
frontend/src/pages/UserView.jsx
Normal file
34
frontend/src/pages/UserView.jsx
Normal file
|
@ -0,0 +1,34 @@
|
|||
import { Avatar, Popover, Progress, Space, Typography } from 'antd'
|
||||
import Markdown from 'react-markdown'
|
||||
import { useLoaderData } from 'react-router-dom'
|
||||
|
||||
const { Paragraph } = Typography
|
||||
|
||||
const UserView = () => {
|
||||
const user = useLoaderData()
|
||||
if (user == null) {
|
||||
return (<Space>Загрузка...</Space>)
|
||||
}
|
||||
|
||||
return (<>
|
||||
<h1>{user.username}</h1>
|
||||
<Paragraph>Уровень: {user.level} ур</Paragraph>
|
||||
<Paragraph>Очков опыта: {user.experience} ОО</Paragraph>
|
||||
<Paragraph>Следующий уровень: {user.expToNextLevel} ОО</Paragraph>
|
||||
<Progress
|
||||
value={user.experience}
|
||||
percent={((user.experience - user.expToCurrentLevel) / (user.expToNextLevel - user.expToCurrentLevel) * 100)}
|
||||
size="small"
|
||||
showInfo={false}
|
||||
/>
|
||||
{user.games.map(item => <Popover
|
||||
key={item.id}
|
||||
title={item.title}
|
||||
content={<Markdown>{item.description}</Markdown>}
|
||||
>
|
||||
<Avatar size={64} style={{ marginRight: 4, marginBottom: 4 }} key={item.id} src={`/api/file/${item.icon}`} />
|
||||
</Popover>)}
|
||||
</>)
|
||||
}
|
||||
|
||||
export default UserView
|
44
frontend/src/pages/Users.jsx
Normal file
44
frontend/src/pages/Users.jsx
Normal file
|
@ -0,0 +1,44 @@
|
|||
import { Table, Typography } from 'antd'
|
||||
import { Link, useLoaderData } from 'react-router-dom'
|
||||
|
||||
const { Title } = Typography
|
||||
|
||||
const Users = () => {
|
||||
const users = useLoaderData()
|
||||
|
||||
return (
|
||||
<>
|
||||
<Title>Статистика игроков</Title>
|
||||
<Table
|
||||
dataSource={users}
|
||||
rowKey={'id'}
|
||||
columns={[
|
||||
{
|
||||
title: 'Ник в игре',
|
||||
dataIndex: 'username',
|
||||
key: 'username',
|
||||
render: (username, q) => <Link to={`/users/${q.id}`}>{username}</Link>
|
||||
},
|
||||
{
|
||||
title: 'ОО',
|
||||
dataIndex: 'experience',
|
||||
key: 'experience'
|
||||
},
|
||||
{
|
||||
title: 'Уровень',
|
||||
dataIndex: 'level',
|
||||
key: 'level'
|
||||
},
|
||||
{
|
||||
title: 'Квесты начатые / Пройденные',
|
||||
dataIndex: 'gamesStarted',
|
||||
key: 'gamesStarted',
|
||||
render: (gamesStarted, q) => (<>{gamesStarted} / {q.gamesFinished}</>)
|
||||
}
|
||||
]}
|
||||
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default Users
|
|
@ -11,6 +11,7 @@ const { Title } = Typography
|
|||
const Quest = () => {
|
||||
let { quest, files } = useLoaderData()
|
||||
const [error, setError] = useState()
|
||||
const [info, setInfo] = useState()
|
||||
if (!quest) {
|
||||
quest = {
|
||||
type: 'city',
|
||||
|
@ -45,6 +46,21 @@ const Quest = () => {
|
|||
}
|
||||
|
||||
const onFinish = (values) => {
|
||||
values.tasks = values.tasks.map(task => {
|
||||
if (!task.id) {
|
||||
task.id = uuidv4()
|
||||
}
|
||||
task.codes = task.codes.map(code => {
|
||||
if (!code.id) {
|
||||
code.id = uuidv4()
|
||||
}
|
||||
|
||||
return code
|
||||
})
|
||||
|
||||
return task
|
||||
})
|
||||
console.log(values)
|
||||
ajax('/api/admin/games', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
|
@ -53,14 +69,24 @@ const Quest = () => {
|
|||
},
|
||||
body: JSON.stringify(values)
|
||||
})
|
||||
.then(g => navigate(`/admin/quests/${g.id}/`))
|
||||
.catch(({ message }) => setError('Ошибка создания'))
|
||||
.then(g => {
|
||||
navigate(`/admin/quests/${g.id}/`)
|
||||
setError('')
|
||||
setInfo('Сохранено')
|
||||
|
||||
return true
|
||||
})
|
||||
.catch(({ message }) => {
|
||||
setInfo('')
|
||||
setError('Ошибка создания:' + message)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Title>{quest.title ? (quest.title) : ('Новый квест')}</Title>
|
||||
{error ? <Alert type="error" message={error} /> : null}
|
||||
{info ? <Alert type="info" message={info} /> : null}
|
||||
<Row gutter={8}>
|
||||
<Col xs={24} sm={16} md={16}>
|
||||
<Form
|
||||
|
@ -146,6 +172,7 @@ const Quest = () => {
|
|||
footer={<Button onClick={() => setPreview(false)}>Закрыть</Button>}
|
||||
width={'80%'}
|
||||
centered
|
||||
onCancel={() => setPreview(false)}
|
||||
>
|
||||
<List dataSource={fields.tasks} renderItem={(task) => (
|
||||
<List.Item key={task.id}>
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/labstack/echo-contrib/session"
|
||||
"github.com/labstack/echo/v4"
|
||||
|
@ -94,6 +96,74 @@ func (u *User) GetUser(c echo.Context) error {
|
|||
return mapUser(c, user)
|
||||
}
|
||||
|
||||
// (GET /users)
|
||||
func (u *User) GetUsers(c echo.Context) error {
|
||||
list, err := u.UserService.List(c.Request().Context())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result := make(api.UsersListResponse, 0, len(list))
|
||||
|
||||
for _, u := range list {
|
||||
level := utils.ExpToLevel(u.Experience)
|
||||
finished := 0
|
||||
for _, g := range u.Games {
|
||||
if g.Finish {
|
||||
finished++
|
||||
}
|
||||
}
|
||||
result = append(result, api.UserShortView{
|
||||
Experience: u.Experience,
|
||||
GamesFinished: finished,
|
||||
GamesStarted: len(u.Games),
|
||||
Id: u.ID,
|
||||
Level: level,
|
||||
Username: u.Username,
|
||||
})
|
||||
}
|
||||
|
||||
return c.JSON(200, result)
|
||||
}
|
||||
|
||||
// (GET /users/{uid})
|
||||
func (s *User) GetUserInfo(c echo.Context, uid uuid.UUID) error {
|
||||
user, err := s.UserService.GetUserByID(c.Request().Context(), uid)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return echo.ErrNotFound
|
||||
}
|
||||
|
||||
level := utils.ExpToLevel(user.Experience)
|
||||
expToCur := utils.LevelToExp(level)
|
||||
expToNext := utils.LevelToExp(level + 1)
|
||||
|
||||
games := make([]api.GameView, 0, len(user.Games))
|
||||
for _, gc := range user.Games {
|
||||
games = append(games, api.GameView{
|
||||
Id: gc.GameID,
|
||||
Title: gc.Game.Title,
|
||||
Description: gc.Game.Description,
|
||||
Type: api.MapGameTypeReverse(gc.Game.Type),
|
||||
Icon: gc.Game.IconID,
|
||||
})
|
||||
}
|
||||
|
||||
res := &api.UserShortView{
|
||||
ExpToCurrentLevel: &expToCur,
|
||||
ExpToNextLevel: &expToNext,
|
||||
Experience: user.Experience,
|
||||
Games: &games,
|
||||
GamesFinished: 0,
|
||||
GamesStarted: 0,
|
||||
Id: user.ID,
|
||||
Level: level,
|
||||
Username: user.Username,
|
||||
}
|
||||
|
||||
return c.JSON(200, res)
|
||||
}
|
||||
|
||||
func setUser(c echo.Context, user *models.User) error {
|
||||
sess, err := session.Get("session", c)
|
||||
if err != nil {
|
||||
|
@ -134,6 +204,7 @@ func mapUser(c echo.Context, user *models.User) error {
|
|||
Title: gc.Game.Title,
|
||||
Description: gc.Game.Description,
|
||||
Type: api.MapGameTypeReverse(gc.Game.Type),
|
||||
Icon: gc.Game.IconID,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -135,6 +135,17 @@ func (s *User) GetUser(c echo.Context) *models.User {
|
|||
return user
|
||||
}
|
||||
|
||||
func (s *User) List(ctx context.Context) ([]models.User, error) {
|
||||
res := make([]models.User, 0)
|
||||
|
||||
return res, s.DB.WithContext(ctx).
|
||||
Preload("Games").
|
||||
Order("experience DESC").
|
||||
Limit(100).
|
||||
Find(&res).
|
||||
Error
|
||||
}
|
||||
|
||||
func (s *User) Update(ctx context.Context, user *models.User) error {
|
||||
return s.DB.WithContext(ctx).Session(&gorm.Session{FullSaveAssociations: true}).Save(user).Error
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue