Нормальная загрузка файлов
|
@ -1,7 +0,0 @@
|
|||
POSTGRES_HOSTNAME=localhost
|
||||
POSTGRES_DB=nquest
|
||||
POSTGRES_USER=nquest
|
||||
POSTGRES_PASSWORD=nquest
|
||||
POSTGRES_PORT=5432
|
||||
SECRET=s3cr3t
|
||||
LISTEN=:8000
|
|
@ -1 +0,0 @@
|
|||
FROM gitrepo.ru/neonxp/devcontainer:latest
|
|
@ -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": {}
|
||||
}
|
||||
}
|
|
@ -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
|
@ -10,7 +10,7 @@
|
|||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "${workspaceFolder}/",
|
||||
"envFile": "${workspaceFolder}/.devcontainer/.env"
|
||||
"envFile": "${workspaceFolder}/dev/.env"
|
||||
}
|
||||
]
|
||||
}
|
12
Makefile
|
@ -8,8 +8,16 @@ generate:
|
|||
|
||||
.PHONY: build-front
|
||||
build-front:
|
||||
cd frontend & npm run
|
||||
cd frontend
|
||||
npm run build
|
||||
|
||||
.PHONY: 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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
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 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 server,spec -package api -o ./server.go ./openapi.yaml
|
||||
//go:generate oapi-codegen -generate types -package api -o ./types.go ./openapi.yaml
|
||||
|
|
|
@ -128,11 +128,18 @@ paths:
|
|||
format: binary
|
||||
|
||||
# Admin routes
|
||||
/admin/file/upload:
|
||||
/admin/file/{quest}/upload:
|
||||
post:
|
||||
operationId: adminUploadFile
|
||||
security:
|
||||
- cookieAuth: [creator, admin]
|
||||
parameters:
|
||||
- name: quest
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
requestBody:
|
||||
content:
|
||||
multipart/form-data:
|
||||
|
@ -145,6 +152,22 @@ paths:
|
|||
responses:
|
||||
200:
|
||||
$ref: "#/components/responses/uploadResponse"
|
||||
/admin/file/{quest}:
|
||||
get:
|
||||
operationId: adminListFiles
|
||||
security:
|
||||
- cookieAuth: [creator, admin]
|
||||
parameters:
|
||||
- name: quest
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
200:
|
||||
$ref: "#/components/responses/filesListResponse"
|
||||
|
||||
/admin/games:
|
||||
get:
|
||||
operationId: adminListGames
|
||||
|
@ -225,6 +248,8 @@ components:
|
|||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
visible:
|
||||
type: boolean
|
||||
title:
|
||||
type: string
|
||||
description:
|
||||
|
@ -316,7 +341,6 @@ components:
|
|||
- type
|
||||
- tasks
|
||||
- points
|
||||
- icon
|
||||
taskEdit:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -353,6 +377,20 @@ components:
|
|||
enum:
|
||||
- virtual
|
||||
- city
|
||||
fileItem:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
originalName:
|
||||
type: string
|
||||
size:
|
||||
type: integer
|
||||
required:
|
||||
- id
|
||||
- originalName
|
||||
- size
|
||||
responses:
|
||||
userResponse:
|
||||
description: ""
|
||||
|
@ -410,6 +448,14 @@ components:
|
|||
format: uuid
|
||||
required:
|
||||
- uuid
|
||||
filesListResponse:
|
||||
description: ""
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/fileItem"
|
||||
securitySchemes:
|
||||
cookieAuth:
|
||||
type: apiKey
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// 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
|
||||
|
||||
import (
|
||||
|
@ -22,8 +22,11 @@ import (
|
|||
// ServerInterface represents all server handlers.
|
||||
type ServerInterface interface {
|
||||
|
||||
// (POST /admin/file/upload)
|
||||
AdminUploadFile(ctx echo.Context) error
|
||||
// (GET /admin/file/{quest})
|
||||
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)
|
||||
AdminListGames(ctx echo.Context) error
|
||||
|
@ -64,14 +67,39 @@ type ServerInterfaceWrapper struct {
|
|||
Handler ServerInterface
|
||||
}
|
||||
|
||||
// AdminUploadFile converts echo context to params.
|
||||
func (w *ServerInterfaceWrapper) AdminUploadFile(ctx echo.Context) error {
|
||||
// AdminListFiles converts echo context to params.
|
||||
func (w *ServerInterfaceWrapper) AdminListFiles(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)
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -103,7 +131,7 @@ func (w *ServerInterfaceWrapper) AdminGetGame(ctx echo.Context) error {
|
|||
// ------------- Path parameter "uid" -------------
|
||||
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 {
|
||||
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" -------------
|
||||
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 {
|
||||
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" -------------
|
||||
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 {
|
||||
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" -------------
|
||||
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 {
|
||||
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,
|
||||
}
|
||||
|
||||
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.POST(baseURL+"/admin/games", wrapper.AdminEditGame)
|
||||
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
|
||||
var swaggerSpec = []string{
|
||||
|
||||
"H4sIAAAAAAAC/8xY32+kNhD+Vyq3j1zY+/HEWxqlUdWoaq+5vkSryIHJxhewqT3OZRXxv1djAwuLWcge",
|
||||
"Su4pGzx4Zr75vmHsZ5aqolQSJBqWPDMNplTSgPsHtFb6c/2EHqRKIkikn7wsc5FyFErGX42S9Myk91Bw",
|
||||
"+lVqVYJG4fdJVeZex20JLGFCImxAsypiBRjDN91Fg1rIDauqiGn4zwoNGUuu/RY7+3XU2Kvbr5Aiq+iF",
|
||||
"DEyqRUkxsYTR/htewGlWCHlUFr9ouGMJ+zneYRT7VRPTzueZOOj5Uhg8yrFAKMycCP4V8I281WBwrfl2",
|
||||
"LCLk5mFxGGhTH0TYqS1zxbMFOGStyOjvndIFR5b4B9EEbZzRXLJYA3pxgGjTcYCqqN6m1Ynj1JSCmmz3",
|
||||
"NgysHwNad8/Iux5C6BdcZktFeyCMkP9WgQP/k6CkfmEClpnoRaxUom6fww5HAjGzNU3WvqvsazpiKDAP",
|
||||
"w+ofTHeLK7KrIvYojLjt7XWrVA5cDmrQWDbeoz1yuNebJFsgaoTHinZVhwvSFt6JRstz4prAbee1XYZt",
|
||||
"rxvUmlu8V3o+wjs9DhFONXCE7BSPk9ors+pMWYkjywtRZY8NPs5DRGgJsAuwC2vUlusARVoNBPvK/Eq3",
|
||||
"3TRQ6ZklQHgKc2EM4D3AGqzcPlEd/1jO4730ZTmPsbszazXKUw839WAl5CPPRdb8q/L2p4QnvMnhEUig",
|
||||
"RI0b8psDQlCprwZZK+QBZFBwkQdjgKfySp1ZrUHipUspKB9n9ic8TdiAFiDTkcmWkDILzHKzuZqPx6pV",
|
||||
"3is7QdfoUtEvTiNysJxkKnkxo3AuqtY8qqvQA6oJMlSIAeoNhHX4gSEuYgZSqwVu/yEYG72oBwGnFu8d",
|
||||
"+DRm+UdEZZcIM2BMp2sljJfiD6jnZiHvlEvW05XJvy0YYuIjaOPHtvcnq5MVgaNKkLwULGEfT96frKj/",
|
||||
"cbx3YcQO0vhO5BD7GdjxVBknDmKrGyZ/z1jC3AHlizP6TTjyE65g8FeVbfcG0cLmKEquMSZCvMs4BmZl",
|
||||
"8trjzK2QXG+Dg19gMu6dAT+sVmPkbe3ivSm/WxqWXPeLcj3kXbWmN2rIWuFsYAwrOlddNOR4cbCDk5lL",
|
||||
"+kBp6CNy4Uk9XphlTpLVsQn1D7nfX4D42YqsOlyGC2hgKbnmBSDQHHZdq46ksNOcb1q7foHaQtQBaOqE",
|
||||
"sn5TYEBuhIQJTAiLc2f44yLSuwPwfOslFzdHt7AcziWCPvNzwStleJzeZp1HQ1dMobuCaimk3ddggkSA",
|
||||
"9TfgzRg0ArJKEfCdQQ286IM9/ZkZ3Hy4L1SNyuGGXzeZ5Vo9uXQD0AGPX/yAdMSHsHuFVEXs0+rj9Ev9",
|
||||
"a9ZOiHGuNkKOy/EvZVyol85sKbWMj88lN+ab0tm0lprhr31jMV0NEV69HOHeR2Ddw1tZnAU42Q3i/zS4",
|
||||
"fWLGpikY81O99bER72LUsBEGPX8PR/m5sXxTZuwWPwRX558wAoeL1m/Xy4/NtYMTyJqasgH92LR9q3OW",
|
||||
"sJhOGdW6+j8AAP//fdZwRK4ZAAA=",
|
||||
"H4sIAAAAAAAC/8xZX2/cNgz/KoO2Rze+/nnyWxdkQbGg2Lp0L8EhUGzmosaWHIlOcwv83QdKts8+y39y",
|
||||
"da99is+mKPLHHylSeWaxynIlQaJh0TPTYHIlDdgfoLXSn6o39CJWEkEiPfI8T0XMUSgZfjFK0jsT30HG",
|
||||
"6SnXKgeNwumJVWKX4zYHFjEhETagWRmwDIzhm/ZHg1rIDSvLgGl4KISGhEVXTsVOfh3U8urmC8TISlqQ",
|
||||
"gIm1yMkmFjHSfytSMBfC4EFeCITMOvCbhlsWsV/DHVihEzMhbfEBIaPtKpu41nw7ZNKGZ/A+yYQ8yKQx",
|
||||
"S0jzWSJwbOfviwXt8K+Ar3OxQG7uF4eBlDoj/JsWeap4sgCti0Ik9PdW6Ywji9yLYILJVmgufwsDenGA",
|
||||
"SOkwQGVQqWlS13JqKqlrb/cUer4fAlpbZ+C27kPoPljPlrJ2xAzf/k0t6O0/y+mAKS02QvL0I8/85hrx",
|
||||
"n7eU7hlqtXeUVUt9Vjd1o2f1ZChj92HSr5nu50pU51D/qKC0NrMrEUm7WrhfiQKGAlM/uu7FdI27JLky",
|
||||
"YI/CiJuOrhulUuCyF5Bast492KO0XV472QAxFK3Lyk6QRea0ayx4SqkhcNtatnOtKc29IPMC75SeD+2u",
|
||||
"fPShjTVwhOQ9HlYZjkynU1VIHPj8AzjinBijR+VQ2/o25kETywpLH3+azPDWyPk0aE4GDw1mxgfhyU+U",
|
||||
"IfT3AKuxsnqCyv4hn4fPhZf5PET9Vitbp6W6v676ViEfeSqS+qdKm0cJT3idwiNQ9hJvrmnfFBC8aXw0",
|
||||
"yJos70EGGRep1wZ4yi/VaaE1SLywLnlzy4p9hKcJGdACZDwwOBBSZoG+dDZX02FbtUo7YSfo6rxU9MSp",
|
||||
"3feGk0Sl/6z3FYdGPKii0AGqNtIXiB7qNYSV+Z6GNGAG4kIL3P5DMNb5ou4FvC/wzoJPLaN7RVS2jjAD",
|
||||
"xrSqVsR4Lv6EagYQ8lZZZx1dmfy7AENMfARtXAv6+mR1srLNUA6S54JF7O3J65MV1T+Od9aM0EJqZ6/w",
|
||||
"+YFUlPR6AzY5iK22Mf6QsIjZYYvmnj9oGLRaNM8Agc68q8oJ0rxz4aEyahcA1AUErf56qn1dB91R+s1q",
|
||||
"NUTSRi7sT6vtKFhj2/hf9SlWrmmFB53QTT02m5UZQumzFSKcjgmTVfO7SrZ7E05WpChyrjEkNa8Sjp4h",
|
||||
"jJzs7HQjJNdb70ThGbleHqS98fGbItRUsXHinteZ+mJjeyO/dXqEAXSin7sKMxyYZa4oykMd6t6efHsA",
|
||||
"wudCJBP14xxqWKbTwlH9yLVjMWBAboSECUwIizMr+PMi0rlccnzrOBfWdwL+dDiTCPrUNWlH8vCwfJt1",
|
||||
"0eG7TvVdQpVLIe0On3ESAc4+ar4TgwZAVjECvjKogWddsKePmd6Vmj2hKlTGC35VZJYr9bSl7UZHdvzs",
|
||||
"utUDDsL23WQZsHert9OLuv9SaJkYpmoj5HA6/qWMNfXCii2VLcOzTM6N+ap0Mp1LdSferFgsr/oIr16O",
|
||||
"cOcQWHfwVgXOApzkeva/610QMlPEMRjzS6X6UIt3NmrYCIOOv+NWfqolfygzdh/feL/OH/c8k16zb3uX",
|
||||
"n5trox3ImoqyAf1Yl/1CpyxiIY185br8PwAA//96RTU+mhwAAA==",
|
||||
}
|
||||
|
||||
// GetSwagger returns the content of the embedded swagger specification file
|
||||
|
|
15
api/types.go
|
@ -1,6 +1,6 @@
|
|||
// 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
|
||||
|
||||
import (
|
||||
|
@ -46,10 +46,17 @@ type CodeView struct {
|
|||
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.
|
||||
type GameEdit struct {
|
||||
Description string `json:"description"`
|
||||
Icon openapi_types.UUID `json:"icon"`
|
||||
Icon *openapi_types.UUID `json:"icon,omitempty"`
|
||||
Id *openapi_types.UUID `json:"id,omitempty"`
|
||||
Points int `json:"points"`
|
||||
Tasks []TaskEdit `json:"tasks"`
|
||||
|
@ -72,6 +79,7 @@ type GameView struct {
|
|||
TaskCount int `json:"taskCount"`
|
||||
Title string `json:"title"`
|
||||
Type GameType `json:"type"`
|
||||
Visible *bool `json:"visible,omitempty"`
|
||||
}
|
||||
|
||||
// TaskEdit defines model for taskEdit.
|
||||
|
@ -115,6 +123,9 @@ type ErrorResponse struct {
|
|||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// FilesListResponse defines model for filesListResponse.
|
||||
type FilesListResponse = []FileItem
|
||||
|
||||
// GameAdminResponse defines model for gameAdminResponse.
|
||||
type GameAdminResponse = GameEdit
|
||||
|
||||
|
|
15
dev/docker-compose.yml
Normal 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
|
|
@ -4,6 +4,16 @@ volumes:
|
|||
postgres-data:
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- 8989:8989
|
||||
depends_on:
|
||||
- db
|
||||
env_file:
|
||||
- .env
|
||||
db:
|
||||
image: postgres:15-alpine3.17
|
||||
restart: unless-stopped
|
||||
|
@ -11,5 +21,3 @@ services:
|
|||
- postgres-data:/var/lib/postgresql/data
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
|
|
@ -51,7 +51,13 @@ const router = createBrowserRouter(
|
|||
<Route
|
||||
path="/admin/quests/:gameId"
|
||||
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
|
||||
path="/admin/quests"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
:root {
|
||||
--accent-color: #fb923c;
|
||||
--accent-color: #59FBEA;
|
||||
--primary-bg: #171E26;
|
||||
--secondary-bg: #26323f;
|
||||
|
||||
|
|
|
@ -18,11 +18,13 @@ ReactDOM.createRoot(document.getElementById('root')).render(
|
|||
locale={ruRU}
|
||||
theme={{
|
||||
token: {
|
||||
colorPrimary: '#fb923c',
|
||||
colorInfo: '#fb923c',
|
||||
colorTextBase: '#fff',
|
||||
colorPrimary: '#59FBEA',
|
||||
colorButtonText: '#000',
|
||||
colorInfo: '#59FBEA',
|
||||
colorSuccess: '#15803d',
|
||||
colorBgBase: '#171e26',
|
||||
borderRadius: 3,
|
||||
borderRadius: 2,
|
||||
wireframe: false
|
||||
},
|
||||
algorithm: darkAlgorithm
|
||||
|
|
|
@ -1,14 +1,29 @@
|
|||
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 { ajax } from '../../utils/fetch'
|
||||
import { useState } from 'react'
|
||||
import { uuidv4 } from '../../utils/uuid'
|
||||
import Markdown from 'react-markdown'
|
||||
|
||||
const { Title } = Typography
|
||||
|
||||
const Quest = () => {
|
||||
let quest = useLoaderData()
|
||||
let { quest, files } = useLoaderData()
|
||||
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 normFile = (e) => {
|
||||
if (Array.isArray(e)) {
|
||||
|
@ -42,27 +57,28 @@ const Quest = () => {
|
|||
.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}
|
||||
<Row gutter={8}>
|
||||
<Col xs={24} sm={16} md={16}>
|
||||
<Form
|
||||
initialValues={quest}
|
||||
onFinish={onFinish}
|
||||
{...formItemLayout}
|
||||
onValuesChange={(_, allFields) => setFields(allFields)}
|
||||
>
|
||||
<Form.Item wrapperCol={buttonLayout}>
|
||||
<Button.Group block>
|
||||
<Button type='primary' htmlType='submit' block>
|
||||
Сохранить квест
|
||||
</Button>
|
||||
|
||||
<Button type='default' block onClick={() => setPreview(true)}>
|
||||
Предпросмотр
|
||||
</Button>
|
||||
</Button.Group>
|
||||
</Form.Item>
|
||||
<Form.Item name='id' hidden>
|
||||
<Input />
|
||||
|
@ -82,7 +98,7 @@ const Quest = () => {
|
|||
getValueFromEvent={normFile}
|
||||
>
|
||||
{quest.icon ? <Avatar src={`/api/file/${quest.icon}`} /> : null}
|
||||
<Upload name='file' action='/api/admin/file/upload' listType='picture' maxCount={1}>
|
||||
<Upload name='file' action={`/api/admin/file/${quest.id}/upload`} listType='picture' maxCount={1}>
|
||||
<Button icon={<UploadOutlined />}>Загрузка</Button>
|
||||
</Upload>
|
||||
</Form.Item>
|
||||
|
@ -108,6 +124,47 @@ const Quest = () => {
|
|||
)}
|
||||
</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}
|
||||
>
|
||||
<Button icon={<UploadOutlined />}>Загрузка</Button>
|
||||
</Upload>
|
||||
Ранее загруженные файлы:
|
||||
<List dataSource={files} renderItem={renderFileItem} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Modal
|
||||
title="Предпросмотр квеста"
|
||||
open={preview}
|
||||
footer={<Button onClick={() => setPreview(false)}>Закрыть</Button>}
|
||||
width={'80%'}
|
||||
centered
|
||||
>
|
||||
<List dataSource={fields.tasks} renderItem={(task) => (
|
||||
<List.Item key={task.id}>
|
||||
<List.Item.Meta
|
||||
title={task.title}
|
||||
description={
|
||||
<><Card>
|
||||
<Markdown>{task.text}</Markdown>
|
||||
</Card>
|
||||
Коды:
|
||||
<ul>
|
||||
{task.codes.map(c => <li key={c.key}>{c.code}</li>)}
|
||||
</ul>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</List.Item>
|
||||
)} />
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -187,4 +244,24 @@ const renderCodeForm = remove => code => (
|
|||
</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
|
||||
|
|
|
@ -15,15 +15,16 @@ const Quests = () => {
|
|||
rowKey={'id'}
|
||||
columns={[
|
||||
{
|
||||
title: 'UUID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
render: uid => <Link to={`/admin/quests/${uid}`}>${uid}</Link>
|
||||
title: 'Опубликован?',
|
||||
dataIndex: 'visible',
|
||||
key: 'visible',
|
||||
render: visible => visible ? 'Да' : 'Нет'
|
||||
},
|
||||
{
|
||||
title: 'Название',
|
||||
dataIndex: 'title',
|
||||
key: 'title'
|
||||
key: 'title',
|
||||
render: (title, q) => <Link to={`/admin/quests/${q.id}`}>{title}</Link>
|
||||
},
|
||||
{
|
||||
title: 'Тип',
|
||||
|
|
5
frontend/src/utils/uuid.js
Normal 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)
|
||||
)
|
||||
};
|
|
@ -62,7 +62,7 @@ const manifest = {
|
|||
purpose: 'maskable any'
|
||||
}
|
||||
],
|
||||
theme_color: '#fb923c',
|
||||
theme_color: '#59FBEA',
|
||||
background_color: '#171e26',
|
||||
display: 'standalone',
|
||||
scope: '/',
|
||||
|
|
25
go.mod
|
@ -5,21 +5,20 @@ go 1.21.3
|
|||
require (
|
||||
github.com/dimuska139/go-email-normalizer v1.2.1
|
||||
github.com/getkin/kin-openapi v0.120.0
|
||||
github.com/google/uuid v1.5.0
|
||||
github.com/kelseyhightower/envconfig v1.4.0
|
||||
github.com/labstack/echo-contrib v0.15.0
|
||||
github.com/labstack/echo/v4 v4.11.2
|
||||
github.com/oapi-codegen/runtime v1.0.0
|
||||
golang.org/x/crypto v0.14.0
|
||||
github.com/labstack/echo/v4 v4.11.4
|
||||
github.com/oapi-codegen/runtime v1.1.1
|
||||
golang.org/x/crypto v0.17.0
|
||||
gorm.io/driver/postgres v1.5.3
|
||||
gorm.io/gorm v1.25.5
|
||||
)
|
||||
|
||||
require (
|
||||
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/swag v0.22.4 // indirect
|
||||
github.com/google/uuid v1.3.1 // indirect
|
||||
github.com/gorilla/context v1.1.1 // indirect
|
||||
github.com/gorilla/mux v1.8.0 // 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/perimeterx/marshmallow v1.1.5 // 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
|
||||
)
|
||||
|
||||
|
@ -46,21 +42,22 @@ require (
|
|||
github.com/jackc/pgx/v5 v5.4.3
|
||||
github.com/jinzhu/inflection v1.0.0 // 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-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/oapi-codegen/echo-middleware v1.0.1
|
||||
github.com/prometheus/client_golang v1.15.0 // indirect
|
||||
github.com/prometheus/client_model v0.3.0 // indirect
|
||||
github.com/prometheus/common v0.42.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/fasttemplate v1.2.2 // indirect
|
||||
github.com/wader/gormstore/v2 v2.0.3
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
golang.org/x/sys v0.14.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/net v0.19.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
)
|
||||
|
|
57
go.sum
|
@ -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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
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/go.mod h1:fGPWcd/7PSz9aOHusKVYmDk+oKahH/fZTCQ7tTU7e0Y=
|
||||
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/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
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.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||
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/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
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/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/v4 v4.11.2 h1:T+cTLQxWCDfqDEoydYm5kCobjmHwOwcv4OJAPHilmdE=
|
||||
github.com/labstack/echo/v4 v4.11.2/go.mod h1:UcGuQ8V6ZNRmSweBIJkPvGfwCMIlFmiqrPqiEBfPYws=
|
||||
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
|
||||
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
|
||||
github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8=
|
||||
github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8=
|
||||
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
||||
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.1.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/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.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
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-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.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.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
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/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
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/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/runtime v1.0.0 h1:P4rqFX5fMFWqRzY9M/3YF9+aPSPPB06IzP2P7oOxrWo=
|
||||
github.com/oapi-codegen/runtime v1.0.0/go.mod h1:LmCUMQuPB4M/nLXilQXhHw+BLZdDb18B34OO356yJ/A=
|
||||
github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro=
|
||||
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/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
|
||||
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/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
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/stretchr/objx v0.1.0/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/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
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/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
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-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.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||
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/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.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-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-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-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
||||
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-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
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-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-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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
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.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.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
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-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
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-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.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-20190513163551-3ee3066db522/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/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.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-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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
6
main.go
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/labstack/echo/v4"
|
||||
"github.com/labstack/echo/v4/middleware"
|
||||
oapiMiddleware "github.com/oapi-codegen/echo-middleware"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/wader/gormstore/v2"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
|
@ -51,11 +52,14 @@ func main() {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
storageFs := afero.NewOsFs()
|
||||
storage := afero.NewBasePathFs(storageFs, "store")
|
||||
|
||||
// --[ Services ]--
|
||||
userService := service.NewUser(db)
|
||||
gameService := service.NewGame(db)
|
||||
engineService := service.NewEngine(db)
|
||||
uploadService := service.NewFile(db)
|
||||
uploadService := service.NewFile(db, storage)
|
||||
|
||||
// --[ HTTP server ]--
|
||||
|
||||
|
|
6
package-lock.json
generated
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "nquest",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {}
|
||||
}
|
|
@ -54,7 +54,7 @@ func (a *Admin) AdminEditGame(ctx echo.Context) error {
|
|||
|
||||
return ctx.JSON(http.StatusOK, api.GameAdminResponse{
|
||||
Description: game.Description,
|
||||
Icon: game.IconID,
|
||||
Icon: &game.IconID,
|
||||
Id: &game.ID,
|
||||
Points: game.Points,
|
||||
Tasks: tasks,
|
||||
|
@ -107,7 +107,7 @@ func (a *Admin) AdminGetGame(ctx echo.Context, uid uuid.UUID) error {
|
|||
|
||||
return ctx.JSON(http.StatusOK, api.GameAdminResponse{
|
||||
Description: game.Description,
|
||||
Icon: game.IconID,
|
||||
Icon: &game.IconID,
|
||||
Id: &game.ID,
|
||||
Points: game.Points,
|
||||
Tasks: tasks,
|
||||
|
@ -135,6 +135,7 @@ func (a *Admin) AdminListGames(ctx echo.Context) error {
|
|||
TaskCount: len(game.Tasks),
|
||||
CreatedAt: game.CreatedAt.Format(time.RFC3339),
|
||||
Icon: game.IconID,
|
||||
Visible: &game.Visible,
|
||||
}
|
||||
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 {
|
||||
id := uuid.New()
|
||||
icon := uuid.New()
|
||||
if req.Id != nil {
|
||||
id = *req.Id
|
||||
}
|
||||
if req.Icon != nil {
|
||||
icon = *req.Icon
|
||||
}
|
||||
game := &models.Game{
|
||||
ID: id,
|
||||
Visible: req.Visible,
|
||||
Title: req.Title,
|
||||
Description: req.Description,
|
||||
Authors: []*models.User{
|
||||
user,
|
||||
// user,
|
||||
},
|
||||
Type: api.MapGameType(req.Type),
|
||||
Tasks: make([]*models.Task, 0, len(req.Tasks)),
|
||||
Points: req.Points,
|
||||
IconID: req.Icon,
|
||||
IconID: icon,
|
||||
}
|
||||
for order, te := range req.Tasks {
|
||||
id := uuid.New()
|
||||
|
|
|
@ -12,7 +12,7 @@ type File struct {
|
|||
}
|
||||
|
||||
// (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)
|
||||
fh, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
|
@ -28,6 +28,7 @@ func (u *File) AdminUploadFile(c echo.Context) error {
|
|||
fh.Filename,
|
||||
fh.Header.Get("Content-Type"),
|
||||
int(fh.Size),
|
||||
quest,
|
||||
fo,
|
||||
)
|
||||
if err != nil {
|
||||
|
@ -41,10 +42,28 @@ func (u *File) AdminUploadFile(c echo.Context) error {
|
|||
|
||||
// (GET /file/{uid})
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ type File struct {
|
|||
Filename string
|
||||
ContentType string
|
||||
Size int
|
||||
Body []byte `gorm:"type:bytea"`
|
||||
QuestID uuid.UUID
|
||||
CreatedAt time.Time `json:"-"`
|
||||
UpdatedAt time.Time `json:"-"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
|
|
|
@ -2,20 +2,25 @@ package service
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/spf13/afero"
|
||||
"gitrepo.ru/neonxp/nquest/pkg/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type File struct {
|
||||
DB *gorm.DB
|
||||
store afero.Fs
|
||||
}
|
||||
|
||||
func NewFile(db *gorm.DB) *File {
|
||||
func NewFile(db *gorm.DB, store afero.Fs) *File {
|
||||
return &File{
|
||||
DB: db,
|
||||
store: store,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,26 +29,47 @@ func (u *File) Upload(
|
|||
filename string,
|
||||
contentType string,
|
||||
size int,
|
||||
questID uuid.UUID,
|
||||
r multipart.File,
|
||||
) (uuid.UUID, error) {
|
||||
buf := make([]byte, size)
|
||||
if _, err := r.Read(buf); err != nil {
|
||||
return uuid.UUID{}, err
|
||||
}
|
||||
|
||||
defer r.Close()
|
||||
file := &models.File{
|
||||
ID: uuid.New(),
|
||||
Filename: filename,
|
||||
ContentType: contentType,
|
||||
QuestID: questID,
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
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
BIN
store/d2c3f25b-9f35-4ea7-8039-69a0f7e6c5ac/logo.jpeg
Normal file
After Width: | Height: | Size: 9.3 KiB |
BIN
store/d2c3f25b-9f35-4ea7-8039-69a0f7e6c5ac/logo.png
Normal file
After Width: | Height: | Size: 218 KiB |
13
store/d2c3f25b-9f35-4ea7-8039-69a0f7e6c5ac/logo.svg
Normal file
After Width: | Height: | Size: 414 KiB |
BIN
store/d2c3f25b-9f35-4ea7-8039-69a0f7e6c5ac/logo512.png
Normal file
After Width: | Height: | Size: 104 KiB |
After Width: | Height: | Size: 3.9 MiB |
After Width: | Height: | Size: 3.7 MiB |
After Width: | Height: | Size: 12 KiB |