Нормальная загрузка файлов

This commit is contained in:
Александр Кирюхин 2024-04-29 23:01:24 +03:00
parent ffbe8ef217
commit 60af2988ac
No known key found for this signature in database
GPG key ID: 35E33E1AB7776B39
35 changed files with 450 additions and 243 deletions

View file

@ -1,7 +0,0 @@
POSTGRES_HOSTNAME=localhost
POSTGRES_DB=nquest
POSTGRES_USER=nquest
POSTGRES_PASSWORD=nquest
POSTGRES_PORT=5432
SECRET=s3cr3t
LISTEN=:8000

View file

@ -1 +0,0 @@
FROM gitrepo.ru/neonxp/devcontainer:latest

View file

@ -1,26 +0,0 @@
{
"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": {}
}
}

View file

@ -1,25 +0,0 @@
version: '3.8'
volumes:
postgres-data:
services:
app:
build:
context: .
dockerfile: Dockerfile
env_file:
- .env
volumes:
- ../..:/workspaces:cached
- ${SSH_AUTH_SOCK}:/tmp/ssh-agent.socket
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

2
.vscode/launch.json vendored
View file

@ -10,7 +10,7 @@
"request": "launch", "request": "launch",
"mode": "auto", "mode": "auto",
"program": "${workspaceFolder}/", "program": "${workspaceFolder}/",
"envFile": "${workspaceFolder}/.devcontainer/.env" "envFile": "${workspaceFolder}/dev/.env"
} }
] ]
} }

View file

@ -8,8 +8,16 @@ generate:
.PHONY: build-front .PHONY: build-front
build-front: build-front:
cd frontend & npm run cd frontend
npm run build
.PHONY: dev-front .PHONY: dev-front
dev-front: dev-front:
cd frontend; npm run dev cd frontend
npm run dev
.PHONY: deploy
deploy: frontend/dist
docker context use curie
docker compose up -d
docker context use default

View file

@ -1,4 +1,4 @@
package api package api
//go:generate go run github.com/deepmap/oapi-codegen/v2/cmd/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 go run github.com/deepmap/oapi-codegen/v2/cmd/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

@ -128,11 +128,18 @@ paths:
format: binary format: binary
# Admin routes # Admin routes
/admin/file/upload: /admin/file/{quest}/upload:
post: post:
operationId: adminUploadFile operationId: adminUploadFile
security: security:
- cookieAuth: [creator, admin] - cookieAuth: [creator, admin]
parameters:
- name: quest
in: path
required: true
schema:
type: string
format: uuid
requestBody: requestBody:
content: content:
multipart/form-data: multipart/form-data:
@ -145,6 +152,22 @@ paths:
responses: responses:
200: 200:
$ref: "#/components/responses/uploadResponse" $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: /admin/games:
get: get:
operationId: adminListGames operationId: adminListGames
@ -225,6 +248,8 @@ components:
id: id:
type: string type: string
format: uuid format: uuid
visible:
type: boolean
title: title:
type: string type: string
description: description:
@ -316,7 +341,6 @@ components:
- type - type
- tasks - tasks
- points - points
- icon
taskEdit: taskEdit:
type: object type: object
properties: properties:
@ -353,6 +377,20 @@ components:
enum: enum:
- virtual - virtual
- city - city
fileItem:
type: object
properties:
id:
type: string
format: uuid
originalName:
type: string
size:
type: integer
required:
- id
- originalName
- size
responses: responses:
userResponse: userResponse:
description: "" description: ""
@ -410,6 +448,14 @@ components:
format: uuid format: uuid
required: required:
- uuid - uuid
filesListResponse:
description: ""
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/fileItem"
securitySchemes: securitySchemes:
cookieAuth: cookieAuth:
type: apiKey type: apiKey

View file

@ -1,6 +1,6 @@
// Package api provides primitives to interact with the openapi HTTP API. // Package api provides primitives to interact with the openapi HTTP API.
// //
// Code generated by github.com/deepmap/oapi-codegen/v2 version v2.0.0 DO NOT EDIT. // Code generated by github.com/deepmap/oapi-codegen/v2 version v2.1.0 DO NOT EDIT.
package api package api
import ( import (
@ -22,8 +22,11 @@ import (
// ServerInterface represents all server handlers. // ServerInterface represents all server handlers.
type ServerInterface interface { type ServerInterface interface {
// (POST /admin/file/upload) // (GET /admin/file/{quest})
AdminUploadFile(ctx echo.Context) error AdminListFiles(ctx echo.Context, quest openapi_types.UUID) error
// (POST /admin/file/{quest}/upload)
AdminUploadFile(ctx echo.Context, quest openapi_types.UUID) error
// (GET /admin/games) // (GET /admin/games)
AdminListGames(ctx echo.Context) error AdminListGames(ctx echo.Context) error
@ -64,14 +67,39 @@ type ServerInterfaceWrapper struct {
Handler ServerInterface Handler ServerInterface
} }
// AdminUploadFile converts echo context to params. // AdminListFiles converts echo context to params.
func (w *ServerInterfaceWrapper) AdminUploadFile(ctx echo.Context) error { func (w *ServerInterfaceWrapper) AdminListFiles(ctx echo.Context) error {
var err error var err error
// ------------- Path parameter "quest" -------------
var quest openapi_types.UUID
err = runtime.BindStyledParameterWithOptions("simple", "quest", ctx.Param("quest"), &quest, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true})
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter quest: %s", err))
}
ctx.Set(CookieAuthScopes, []string{"creator", "admin"}) ctx.Set(CookieAuthScopes, []string{"creator", "admin"})
// Invoke the callback with all the unmarshaled arguments // Invoke the callback with all the unmarshaled arguments
err = w.Handler.AdminUploadFile(ctx) err = w.Handler.AdminListFiles(ctx, quest)
return err
}
// AdminUploadFile converts echo context to params.
func (w *ServerInterfaceWrapper) AdminUploadFile(ctx echo.Context) error {
var err error
// ------------- Path parameter "quest" -------------
var quest openapi_types.UUID
err = runtime.BindStyledParameterWithOptions("simple", "quest", ctx.Param("quest"), &quest, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true})
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter quest: %s", err))
}
ctx.Set(CookieAuthScopes, []string{"creator", "admin"})
// Invoke the callback with all the unmarshaled arguments
err = w.Handler.AdminUploadFile(ctx, quest)
return err return err
} }
@ -103,7 +131,7 @@ func (w *ServerInterfaceWrapper) AdminGetGame(ctx echo.Context) error {
// ------------- Path parameter "uid" ------------- // ------------- Path parameter "uid" -------------
var uid openapi_types.UUID var uid openapi_types.UUID
err = runtime.BindStyledParameterWithLocation("simple", false, "uid", runtime.ParamLocationPath, ctx.Param("uid"), &uid) err = runtime.BindStyledParameterWithOptions("simple", "uid", ctx.Param("uid"), &uid, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true})
if err != nil { if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter uid: %s", err)) return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter uid: %s", err))
} }
@ -121,7 +149,7 @@ func (w *ServerInterfaceWrapper) GameEngine(ctx echo.Context) error {
// ------------- Path parameter "uid" ------------- // ------------- Path parameter "uid" -------------
var uid openapi_types.UUID var uid openapi_types.UUID
err = runtime.BindStyledParameterWithLocation("simple", false, "uid", runtime.ParamLocationPath, ctx.Param("uid"), &uid) err = runtime.BindStyledParameterWithOptions("simple", "uid", ctx.Param("uid"), &uid, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true})
if err != nil { if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter uid: %s", err)) return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter uid: %s", err))
} }
@ -139,7 +167,7 @@ func (w *ServerInterfaceWrapper) EnterCode(ctx echo.Context) error {
// ------------- Path parameter "uid" ------------- // ------------- Path parameter "uid" -------------
var uid openapi_types.UUID var uid openapi_types.UUID
err = runtime.BindStyledParameterWithLocation("simple", false, "uid", runtime.ParamLocationPath, ctx.Param("uid"), &uid) err = runtime.BindStyledParameterWithOptions("simple", "uid", ctx.Param("uid"), &uid, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true})
if err != nil { if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter uid: %s", err)) return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter uid: %s", err))
} }
@ -157,7 +185,7 @@ func (w *ServerInterfaceWrapper) GetFile(ctx echo.Context) error {
// ------------- Path parameter "uid" ------------- // ------------- Path parameter "uid" -------------
var uid openapi_types.UUID var uid openapi_types.UUID
err = runtime.BindStyledParameterWithLocation("simple", false, "uid", runtime.ParamLocationPath, ctx.Param("uid"), &uid) err = runtime.BindStyledParameterWithOptions("simple", "uid", ctx.Param("uid"), &uid, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true})
if err != nil { if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter uid: %s", err)) return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter uid: %s", err))
} }
@ -248,7 +276,8 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
Handler: si, Handler: si,
} }
router.POST(baseURL+"/admin/file/upload", wrapper.AdminUploadFile) router.GET(baseURL+"/admin/file/:quest", wrapper.AdminListFiles)
router.POST(baseURL+"/admin/file/:quest/upload", wrapper.AdminUploadFile)
router.GET(baseURL+"/admin/games", wrapper.AdminListGames) router.GET(baseURL+"/admin/games", wrapper.AdminListGames)
router.POST(baseURL+"/admin/games", wrapper.AdminEditGame) router.POST(baseURL+"/admin/games", wrapper.AdminEditGame)
router.GET(baseURL+"/admin/games/:uid", wrapper.AdminGetGame) router.GET(baseURL+"/admin/games/:uid", wrapper.AdminGetGame)
@ -266,26 +295,27 @@ 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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",
"sJhOGdW6+j8AAP//fdZwRK4ZAAA=", "cOcQWHfwVgXOApzkeva/610QMlPEMRjzS6X6UIt3NmrYCIOOv+NWfqolfygzdh/feL/OH/c8k16zb3uX",
"n5trox3ImoqyAf1Yl/1CpyxiIY185br8PwAA//96RTU+mhwAAA==",
} }
// GetSwagger returns the content of the embedded swagger specification file // GetSwagger returns the content of the embedded swagger specification file

View file

@ -1,6 +1,6 @@
// Package api provides primitives to interact with the openapi HTTP API. // Package api provides primitives to interact with the openapi HTTP API.
// //
// Code generated by github.com/deepmap/oapi-codegen/v2 version v2.0.0 DO NOT EDIT. // Code generated by github.com/deepmap/oapi-codegen/v2 version v2.1.0 DO NOT EDIT.
package api package api
import ( import (
@ -46,10 +46,17 @@ type CodeView struct {
Description string `json:"description"` Description string `json:"description"`
} }
// FileItem defines model for fileItem.
type FileItem struct {
Id openapi_types.UUID `json:"id"`
OriginalName string `json:"originalName"`
Size int `json:"size"`
}
// GameEdit defines model for gameEdit. // GameEdit defines model for gameEdit.
type GameEdit struct { type GameEdit struct {
Description string `json:"description"` Description string `json:"description"`
Icon openapi_types.UUID `json:"icon"` Icon *openapi_types.UUID `json:"icon,omitempty"`
Id *openapi_types.UUID `json:"id,omitempty"` Id *openapi_types.UUID `json:"id,omitempty"`
Points int `json:"points"` Points int `json:"points"`
Tasks []TaskEdit `json:"tasks"` Tasks []TaskEdit `json:"tasks"`
@ -72,6 +79,7 @@ type GameView struct {
TaskCount int `json:"taskCount"` TaskCount int `json:"taskCount"`
Title string `json:"title"` Title string `json:"title"`
Type GameType `json:"type"` Type GameType `json:"type"`
Visible *bool `json:"visible,omitempty"`
} }
// TaskEdit defines model for taskEdit. // TaskEdit defines model for taskEdit.
@ -115,6 +123,9 @@ type ErrorResponse struct {
Message string `json:"message"` Message string `json:"message"`
} }
// FilesListResponse defines model for filesListResponse.
type FilesListResponse = []FileItem
// GameAdminResponse defines model for gameAdminResponse. // GameAdminResponse defines model for gameAdminResponse.
type GameAdminResponse = GameEdit type GameAdminResponse = GameEdit

15
dev/docker-compose.yml Normal file
View file

@ -0,0 +1,15 @@
version: '3.8'
volumes:
postgres-data:
services:
db:
image: postgres:15-alpine3.17
restart: unless-stopped
volumes:
- postgres-data:/var/lib/postgresql/data
env_file:
- .env
ports:
- 5432:5432

View file

@ -4,6 +4,16 @@ volumes:
postgres-data: postgres-data:
services: services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- 8989:8989
depends_on:
- db
env_file:
- .env
db: db:
image: postgres:15-alpine3.17 image: postgres:15-alpine3.17
restart: unless-stopped restart: unless-stopped
@ -11,5 +21,3 @@ services:
- postgres-data:/var/lib/postgresql/data - postgres-data:/var/lib/postgresql/data
env_file: env_file:
- .env - .env
ports:
- 5432:5432

View file

@ -51,7 +51,13 @@ const router = createBrowserRouter(
<Route <Route
path="/admin/quests/:gameId" path="/admin/quests/:gameId"
element={<Auth role="creator"><EditQuest /></Auth>} element={<Auth role="creator"><EditQuest /></Auth>}
loader={({ params }) => ajax(`/api/admin/games/${params.gameId}`)} loader={async ({ params }) => {
const quest = await ajax(`/api/admin/games/${params.gameId}`)
const files = await ajax(`/api/admin/file/${params.gameId}`)
return { quest, files }
}
}
/> />
<Route <Route
path="/admin/quests" path="/admin/quests"

View file

@ -1,5 +1,5 @@
:root { :root {
--accent-color: #fb923c; --accent-color: #59FBEA;
--primary-bg: #171E26; --primary-bg: #171E26;
--secondary-bg: #26323f; --secondary-bg: #26323f;

View file

@ -18,11 +18,13 @@ ReactDOM.createRoot(document.getElementById('root')).render(
locale={ruRU} locale={ruRU}
theme={{ theme={{
token: { token: {
colorPrimary: '#fb923c', colorTextBase: '#fff',
colorInfo: '#fb923c', colorPrimary: '#59FBEA',
colorButtonText: '#000',
colorInfo: '#59FBEA',
colorSuccess: '#15803d', colorSuccess: '#15803d',
colorBgBase: '#171e26', colorBgBase: '#171e26',
borderRadius: 3, borderRadius: 2,
wireframe: false wireframe: false
}, },
algorithm: darkAlgorithm algorithm: darkAlgorithm

View file

@ -1,14 +1,29 @@
import { useLoaderData, useNavigate } from 'react-router-dom' import { useLoaderData, useNavigate } from 'react-router-dom'
import { Alert, Avatar, Button, Card, Form, Input, InputNumber, Popconfirm, Radio, Switch, Typography, Upload } from 'antd' import { Alert, Avatar, Button, Card, Col, Form, Input, InputNumber, List, Modal, Popconfirm, Radio, Row, Switch, Typography, Upload } from 'antd'
import { UploadOutlined, PlusOutlined, CloseOutlined } from '@ant-design/icons' import { UploadOutlined, PlusOutlined, CloseOutlined } from '@ant-design/icons'
import { ajax } from '../../utils/fetch' import { ajax } from '../../utils/fetch'
import { useState } from 'react' import { useState } from 'react'
import { uuidv4 } from '../../utils/uuid'
import Markdown from 'react-markdown'
const { Title } = Typography const { Title } = Typography
const Quest = () => { const Quest = () => {
let quest = useLoaderData() let { quest, files } = useLoaderData()
const [error, setError] = useState() const [error, setError] = useState()
if (!quest) {
quest = {
type: 'city',
points: 10,
tasks: [],
id: uuidv4(),
visible: false,
title: '',
description: ''
}
}
const [fields, setFields] = useState(quest)
const [preview, setPreview] = useState(false)
const navigate = useNavigate() const navigate = useNavigate()
const normFile = (e) => { const normFile = (e) => {
if (Array.isArray(e)) { if (Array.isArray(e)) {
@ -42,72 +57,114 @@ const Quest = () => {
.catch(({ message }) => setError('Ошибка создания')) .catch(({ message }) => setError('Ошибка создания'))
} }
if (!quest) {
quest = {
type: 'city',
points: 10,
tasks: []
}
}
return ( return (
<> <>
<Title>{quest.title ? (quest.title) : ('Новый квест')}</Title> <Title>{quest.title ? (quest.title) : ('Новый квест')}</Title>
{error ? <Alert type="error" message={error} /> : null} {error ? <Alert type="error" message={error} /> : null}
<Form <Row gutter={8}>
initialValues={quest} <Col xs={24} sm={16} md={16}>
onFinish={onFinish} <Form
{...formItemLayout} initialValues={quest}
> onFinish={onFinish}
<Form.Item wrapperCol={buttonLayout}> {...formItemLayout}
<Button type='primary' htmlType='submit' block> onValuesChange={(_, allFields) => setFields(allFields)}
Сохранить квест >
</Button> <Form.Item wrapperCol={buttonLayout}>
</Form.Item> <Button.Group block>
<Form.Item name='id' hidden> <Button type='primary' htmlType='submit' block>
<Input /> Сохранить квест
</Form.Item> </Button>
<Form.Item label='Опубликован?' name='visible'>
<Switch /> <Button type='default' block onClick={() => setPreview(true)}>
</Form.Item> Предпросмотр
<Form.Item label='Название' name='title'> </Button>
<Input /> </Button.Group>
</Form.Item> </Form.Item>
<Form.Item label='Описание' name='description' help='Поддерживается Markdown'> <Form.Item name='id' hidden>
<Input.TextArea /> <Input />
</Form.Item> </Form.Item>
<Form.Item <Form.Item label='Опубликован?' name='visible'>
name='icon' <Switch />
label='Иконка' </Form.Item>
getValueFromEvent={normFile} <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}
>
{quest.icon ? <Avatar src={`/api/file/${quest.icon}`} /> : null}
<Upload name='file' action={`/api/admin/file/${quest.id}/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>
</Col>
<Col xs={24} sm={8} md={8}>
<Title>Файлы</Title>
<Upload
name='file'
action={`/api/admin/file/${quest.id}/upload`}
listType='picture'
maxCount={10}
itemRender={renderFile}
> >
{quest.icon ? <Avatar src={`/api/file/${quest.icon}`} /> : null}
<Upload name='file' action='/api/admin/file/upload' listType='picture' maxCount={1}>
<Button icon={<UploadOutlined />}>Загрузка</Button> <Button icon={<UploadOutlined />}>Загрузка</Button>
</Upload> </Upload>
</Form.Item> Ранее загруженные файлы:
<Form.Item label='Тип квеста' name='type'> <List dataSource={files} renderItem={renderFileItem} />
<Radio.Group> </Col>
<Radio.Button value='city'>Полевой</Radio.Button> </Row>
<Radio.Button value='virtual'>Виртуальный</Radio.Button> <Modal
</Radio.Group> title="Предпросмотр квеста"
</Form.Item> open={preview}
<Form.Item label='Очков опыта за квест' name='points'> footer={<Button onClick={() => setPreview(false)}>Закрыть</Button>}
<InputNumber /> width={'80%'}
</Form.Item> centered
<Form.List name='tasks'> >
{(tasks, { add, remove }) => ( <List dataSource={fields.tasks} renderItem={(task) => (
<> <List.Item key={task.id}>
{tasks.map(renderTaskForm(remove))} <List.Item.Meta
<Form.Item wrapperCol={buttonLayout}> title={task.title}
<Button type='primary' onClick={() => add()} block> description={
<PlusOutlined/> Добавить уровень <><Card>
</Button> <Markdown>{task.text}</Markdown>
</Form.Item> </Card>
</> Коды:
)} <ul>
</Form.List> {task.codes.map(c => <li key={c.key}>{c.code}</li>)}
</Form> </ul>
</>
}
/>
</List.Item>
)} />
</Modal>
</> </>
) )
} }
@ -187,4 +244,24 @@ const renderCodeForm = remove => code => (
</Card> </Card>
) )
const renderFile = (e, file) => (
<div key={file ? file.uid : null}>
{e}
{file && file.response && file.response.uuid
? <>Код для вставки: <pre>![](/api/file/{file.response.uuid})</pre></>
: null}
</div>
)
const renderFileItem = (file) => (
<List.Item>
<List.Item.Meta
avatar={<Avatar src={`/api/file/${file.id}`} />}
title={file.originalName}
description={<>Код для вставки: <pre>![](/api/file/{file.id})</pre></>}
/>
</List.Item>
)
export default Quest export default Quest

View file

@ -15,15 +15,16 @@ const Quests = () => {
rowKey={'id'} rowKey={'id'}
columns={[ columns={[
{ {
title: 'UUID', title: 'Опубликован?',
dataIndex: 'id', dataIndex: 'visible',
key: 'id', key: 'visible',
render: uid => <Link to={`/admin/quests/${uid}`}>${uid}</Link> render: visible => visible ? 'Да' : 'Нет'
}, },
{ {
title: 'Название', title: 'Название',
dataIndex: 'title', dataIndex: 'title',
key: 'title' key: 'title',
render: (title, q) => <Link to={`/admin/quests/${q.id}`}>{title}</Link>
}, },
{ {
title: 'Тип', title: 'Тип',

View file

@ -0,0 +1,5 @@
export function uuidv4 () {
return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, c =>
(+c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> +c / 4).toString(16)
)
};

View file

@ -62,7 +62,7 @@ const manifest = {
purpose: 'maskable any' purpose: 'maskable any'
} }
], ],
theme_color: '#fb923c', theme_color: '#59FBEA',
background_color: '#171e26', background_color: '#171e26',
display: 'standalone', display: 'standalone',
scope: '/', scope: '/',

25
go.mod
View file

@ -5,21 +5,20 @@ go 1.21.3
require ( require (
github.com/dimuska139/go-email-normalizer v1.2.1 github.com/dimuska139/go-email-normalizer v1.2.1
github.com/getkin/kin-openapi v0.120.0 github.com/getkin/kin-openapi v0.120.0
github.com/google/uuid v1.5.0
github.com/kelseyhightower/envconfig v1.4.0 github.com/kelseyhightower/envconfig v1.4.0
github.com/labstack/echo-contrib v0.15.0 github.com/labstack/echo-contrib v0.15.0
github.com/labstack/echo/v4 v4.11.2 github.com/labstack/echo/v4 v4.11.4
github.com/oapi-codegen/runtime v1.0.0 github.com/oapi-codegen/runtime v1.1.1
golang.org/x/crypto v0.14.0 golang.org/x/crypto v0.17.0
gorm.io/driver/postgres v1.5.3 gorm.io/driver/postgres v1.5.3
gorm.io/gorm v1.25.5 gorm.io/gorm v1.25.5
) )
require ( require (
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/deepmap/oapi-codegen/v2 v2.0.0 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/swag v0.22.4 // indirect github.com/go-openapi/swag v0.22.4 // indirect
github.com/google/uuid v1.3.1 // indirect
github.com/gorilla/context v1.1.1 // indirect github.com/gorilla/context v1.1.1 // indirect
github.com/gorilla/mux v1.8.0 // indirect github.com/gorilla/mux v1.8.0 // indirect
github.com/gorilla/securecookie v1.1.1 // indirect github.com/gorilla/securecookie v1.1.1 // indirect
@ -29,9 +28,6 @@ require (
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/tools v0.12.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )
@ -46,21 +42,22 @@ require (
github.com/jackc/pgx/v5 v5.4.3 github.com/jackc/pgx/v5 v5.4.3
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/labstack/gommon v0.4.0 // indirect github.com/labstack/gommon v0.4.2 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/oapi-codegen/echo-middleware v1.0.1 github.com/oapi-codegen/echo-middleware v1.0.1
github.com/prometheus/client_golang v1.15.0 // indirect github.com/prometheus/client_golang v1.15.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/common v0.42.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect
github.com/spf13/afero v1.11.0
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/wader/gormstore/v2 v2.0.3 github.com/wader/gormstore/v2 v2.0.3
golang.org/x/net v0.17.0 // indirect golang.org/x/net v0.19.0 // indirect
golang.org/x/sys v0.14.0 // indirect golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.13.0 // indirect golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.3.0 // indirect golang.org/x/time v0.5.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect google.golang.org/protobuf v1.31.0 // indirect
) )

57
go.sum
View file

@ -16,8 +16,6 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deepmap/oapi-codegen/v2 v2.0.0 h1:3TS7w3r+XnjKFXcbFbc16pTWzfTy0OLPkCsutEHjWDA=
github.com/deepmap/oapi-codegen/v2 v2.0.0/go.mod h1:7zR+ZL3WzLeCkr2k8oWTxEa0v8y/F25ane0l6A5UjLA=
github.com/dimuska139/go-email-normalizer v1.2.1 h1:pJNZnU7uS9MRoYqpoir05B+bCYXrS9sPGE4G1o9EDA8= github.com/dimuska139/go-email-normalizer v1.2.1 h1:pJNZnU7uS9MRoYqpoir05B+bCYXrS9sPGE4G1o9EDA8=
github.com/dimuska139/go-email-normalizer v1.2.1/go.mod h1:fGPWcd/7PSz9aOHusKVYmDk+oKahH/fZTCQ7tTU7e0Y= github.com/dimuska139/go-email-normalizer v1.2.1/go.mod h1:fGPWcd/7PSz9aOHusKVYmDk+oKahH/fZTCQ7tTU7e0Y=
github.com/getkin/kin-openapi v0.120.0 h1:MqJcNJFrMDFNc07iwE8iFC5eT2k/NPUFDIpNeiZv8Jg= github.com/getkin/kin-openapi v0.120.0 h1:MqJcNJFrMDFNc07iwE8iFC5eT2k/NPUFDIpNeiZv8Jg=
@ -46,8 +44,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
@ -125,10 +123,10 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo-contrib v0.15.0 h1:9K+oRU265y4Mu9zpRDv3X+DGTqUALY6oRHCSZZKCRVU= github.com/labstack/echo-contrib v0.15.0 h1:9K+oRU265y4Mu9zpRDv3X+DGTqUALY6oRHCSZZKCRVU=
github.com/labstack/echo-contrib v0.15.0/go.mod h1:lei+qt5CLB4oa7VHTE0yEfQSEB9XTJI1LUqko9UWvo4= github.com/labstack/echo-contrib v0.15.0/go.mod h1:lei+qt5CLB4oa7VHTE0yEfQSEB9XTJI1LUqko9UWvo4=
github.com/labstack/echo/v4 v4.11.2 h1:T+cTLQxWCDfqDEoydYm5kCobjmHwOwcv4OJAPHilmdE= github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8=
github.com/labstack/echo/v4 v4.11.2/go.mod h1:UcGuQ8V6ZNRmSweBIJkPvGfwCMIlFmiqrPqiEBfPYws= github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8=
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
@ -137,16 +135,14 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
@ -155,8 +151,8 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/oapi-codegen/echo-middleware v1.0.1 h1:edYGScq1phCcuDoz9AqA9eHX+tEI1LNL5PL1lkkQh1k= github.com/oapi-codegen/echo-middleware v1.0.1 h1:edYGScq1phCcuDoz9AqA9eHX+tEI1LNL5PL1lkkQh1k=
github.com/oapi-codegen/echo-middleware v1.0.1/go.mod h1:DBQKRn+D/vfXOFbaX5GRwFttoJY64JH6yu+pdt7wU3o= github.com/oapi-codegen/echo-middleware v1.0.1/go.mod h1:DBQKRn+D/vfXOFbaX5GRwFttoJY64JH6yu+pdt7wU3o=
github.com/oapi-codegen/runtime v1.0.0 h1:P4rqFX5fMFWqRzY9M/3YF9+aPSPPB06IzP2P7oOxrWo= github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro=
github.com/oapi-codegen/runtime v1.0.0/go.mod h1:LmCUMQuPB4M/nLXilQXhHw+BLZdDb18B34OO356yJ/A= github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -182,6 +178,8 @@ github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9Nz
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@ -203,7 +201,6 @@ github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4d
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/wader/gormstore/v2 v2.0.3 h1:/29GWPauY8xZkpLnB8hsp+dZfP3ivA9fiDw1YVNTp6U= github.com/wader/gormstore/v2 v2.0.3 h1:/29GWPauY8xZkpLnB8hsp+dZfP3ivA9fiDw1YVNTp6U=
@ -230,21 +227,19 @@ golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWP
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -260,13 +255,10 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -275,10 +267,10 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
@ -287,8 +279,6 @@ golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss=
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -306,10 +296,7 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -12,6 +12,7 @@ import (
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware" "github.com/labstack/echo/v4/middleware"
oapiMiddleware "github.com/oapi-codegen/echo-middleware" oapiMiddleware "github.com/oapi-codegen/echo-middleware"
"github.com/spf13/afero"
"github.com/wader/gormstore/v2" "github.com/wader/gormstore/v2"
"gorm.io/driver/postgres" "gorm.io/driver/postgres"
"gorm.io/gorm" "gorm.io/gorm"
@ -51,11 +52,14 @@ func main() {
os.Exit(1) os.Exit(1)
} }
storageFs := afero.NewOsFs()
storage := afero.NewBasePathFs(storageFs, "store")
// --[ Services ]-- // --[ Services ]--
userService := service.NewUser(db) userService := service.NewUser(db)
gameService := service.NewGame(db) gameService := service.NewGame(db)
engineService := service.NewEngine(db) engineService := service.NewEngine(db)
uploadService := service.NewFile(db) uploadService := service.NewFile(db, storage)
// --[ HTTP server ]-- // --[ HTTP server ]--

6
package-lock.json generated Normal file
View file

@ -0,0 +1,6 @@
{
"name": "nquest",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}

View file

@ -54,7 +54,7 @@ func (a *Admin) AdminEditGame(ctx echo.Context) error {
return ctx.JSON(http.StatusOK, api.GameAdminResponse{ return ctx.JSON(http.StatusOK, api.GameAdminResponse{
Description: game.Description, Description: game.Description,
Icon: game.IconID, Icon: &game.IconID,
Id: &game.ID, Id: &game.ID,
Points: game.Points, Points: game.Points,
Tasks: tasks, Tasks: tasks,
@ -107,7 +107,7 @@ func (a *Admin) AdminGetGame(ctx echo.Context, uid uuid.UUID) error {
return ctx.JSON(http.StatusOK, api.GameAdminResponse{ return ctx.JSON(http.StatusOK, api.GameAdminResponse{
Description: game.Description, Description: game.Description,
Icon: game.IconID, Icon: &game.IconID,
Id: &game.ID, Id: &game.ID,
Points: game.Points, Points: game.Points,
Tasks: tasks, Tasks: tasks,
@ -135,6 +135,7 @@ func (a *Admin) AdminListGames(ctx echo.Context) error {
TaskCount: len(game.Tasks), TaskCount: len(game.Tasks),
CreatedAt: game.CreatedAt.Format(time.RFC3339), CreatedAt: game.CreatedAt.Format(time.RFC3339),
Icon: game.IconID, Icon: game.IconID,
Visible: &game.Visible,
} }
resp = append(resp, gv) resp = append(resp, gv)
} }
@ -144,21 +145,25 @@ func (a *Admin) AdminListGames(ctx echo.Context) error {
func (*Admin) mapCreateGameRequest(req *api.GameEdit, user *models.User) *models.Game { func (*Admin) mapCreateGameRequest(req *api.GameEdit, user *models.User) *models.Game {
id := uuid.New() id := uuid.New()
icon := uuid.New()
if req.Id != nil { if req.Id != nil {
id = *req.Id id = *req.Id
} }
if req.Icon != nil {
icon = *req.Icon
}
game := &models.Game{ game := &models.Game{
ID: id, ID: id,
Visible: req.Visible, Visible: req.Visible,
Title: req.Title, Title: req.Title,
Description: req.Description, Description: req.Description,
Authors: []*models.User{ Authors: []*models.User{
user, // user,
}, },
Type: api.MapGameType(req.Type), Type: api.MapGameType(req.Type),
Tasks: make([]*models.Task, 0, len(req.Tasks)), Tasks: make([]*models.Task, 0, len(req.Tasks)),
Points: req.Points, Points: req.Points,
IconID: req.Icon, IconID: icon,
} }
for order, te := range req.Tasks { for order, te := range req.Tasks {
id := uuid.New() id := uuid.New()

View file

@ -12,7 +12,7 @@ type File struct {
} }
// (POST /file/upload) // (POST /file/upload)
func (u *File) AdminUploadFile(c echo.Context) error { func (u *File) AdminUploadFile(c echo.Context, quest uuid.UUID) error {
// user := contextlib.GetUser(c) // user := contextlib.GetUser(c)
fh, err := c.FormFile("file") fh, err := c.FormFile("file")
if err != nil { if err != nil {
@ -28,6 +28,7 @@ func (u *File) AdminUploadFile(c echo.Context) error {
fh.Filename, fh.Filename,
fh.Header.Get("Content-Type"), fh.Header.Get("Content-Type"),
int(fh.Size), int(fh.Size),
quest,
fo, fo,
) )
if err != nil { if err != nil {
@ -41,10 +42,28 @@ func (u *File) AdminUploadFile(c echo.Context) error {
// (GET /file/{uid}) // (GET /file/{uid})
func (u *File) GetFile(c echo.Context, uid uuid.UUID) error { func (u *File) GetFile(c echo.Context, uid uuid.UUID) error {
f, err := u.FileService.GetFile(c.Request().Context(), uid) f, rdr, err := u.FileService.GetFile(c.Request().Context(), uid)
if err != nil { if err != nil {
return err return err
} }
return c.Blob(200, f.ContentType, f.Body) return c.Stream(200, f.ContentType, rdr)
}
func (u *File) AdminListFiles(c echo.Context, quest uuid.UUID) error {
fl, err := u.FileService.GetFilesByQuest(c.Request().Context(), quest)
if err != nil {
return err
}
list := make([]api.FileItem, 0, len(fl))
for _, f := range fl {
list = append(list, api.FileItem{
Id: f.ID,
OriginalName: f.Filename,
Size: f.Size,
})
}
return c.JSON(200, list)
} }

View file

@ -12,7 +12,7 @@ type File struct {
Filename string Filename string
ContentType string ContentType string
Size int Size int
Body []byte `gorm:"type:bytea"` QuestID uuid.UUID
CreatedAt time.Time `json:"-"` CreatedAt time.Time `json:"-"`
UpdatedAt time.Time `json:"-"` UpdatedAt time.Time `json:"-"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`

View file

@ -2,20 +2,25 @@ package service
import ( import (
"context" "context"
"fmt"
"io"
"mime/multipart" "mime/multipart"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/spf13/afero"
"gitrepo.ru/neonxp/nquest/pkg/models" "gitrepo.ru/neonxp/nquest/pkg/models"
"gorm.io/gorm" "gorm.io/gorm"
) )
type File struct { type File struct {
DB *gorm.DB DB *gorm.DB
store afero.Fs
} }
func NewFile(db *gorm.DB) *File { func NewFile(db *gorm.DB, store afero.Fs) *File {
return &File{ return &File{
DB: db, DB: db,
store: store,
} }
} }
@ -24,26 +29,47 @@ func (u *File) Upload(
filename string, filename string,
contentType string, contentType string,
size int, size int,
questID uuid.UUID,
r multipart.File, r multipart.File,
) (uuid.UUID, error) { ) (uuid.UUID, error) {
buf := make([]byte, size) defer r.Close()
if _, err := r.Read(buf); err != nil {
return uuid.UUID{}, err
}
file := &models.File{ file := &models.File{
ID: uuid.New(), ID: uuid.New(),
Filename: filename, Filename: filename,
ContentType: contentType, ContentType: contentType,
QuestID: questID,
Size: size, Size: size,
Body: buf, }
if err := u.store.MkdirAll(questID.String(), 0755); err != nil {
return file.ID, err
}
filePath := fmt.Sprintf("%s/%s", questID.String(), filename)
if err := afero.WriteReader(u.store, filePath, r); err != nil {
return file.ID, err
} }
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, error) { func (u *File) GetFile(ctx context.Context, uid uuid.UUID) (*models.File, io.ReadCloser, error) {
f := new(models.File) f := new(models.File)
if err := u.DB.WithContext(ctx).First(f, uid).Error; err != nil {
return nil, nil, err
}
return f, u.DB.WithContext(ctx).First(f, uid).Error 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) {
list := make([]*models.File, 0)
return list, u.DB.WithContext(ctx).Find(&list, `quest_id = ?`, quest.String()).Error
} }

0
store/.gitkeep Normal file
View file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 414 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB