diff --git a/.devcontainer/.env b/.devcontainer/.env deleted file mode 100644 index 7544bf3..0000000 --- a/.devcontainer/.env +++ /dev/null @@ -1,7 +0,0 @@ -POSTGRES_HOSTNAME=localhost -POSTGRES_DB=nquest -POSTGRES_USER=nquest -POSTGRES_PASSWORD=nquest -POSTGRES_PORT=5432 -SECRET=s3cr3t -LISTEN=:8000 \ No newline at end of file diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index a02e4da..0000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1 +0,0 @@ -FROM gitrepo.ru/neonxp/devcontainer:latest diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index e1e7f8d..0000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -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": {} - } -} diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml deleted file mode 100644 index 13eccb0..0000000 --- a/.devcontainer/docker-compose.yml +++ /dev/null @@ -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 diff --git a/.vscode/launch.json b/.vscode/launch.json index aaded20..ac66d2c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,7 +10,7 @@ "request": "launch", "mode": "auto", "program": "${workspaceFolder}/", - "envFile": "${workspaceFolder}/.devcontainer/.env" + "envFile": "${workspaceFolder}/dev/.env" } ] } \ No newline at end of file diff --git a/Makefile b/Makefile index 13900f6..3c3c1a6 100644 --- a/Makefile +++ b/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 diff --git a/api/doc.go b/api/doc.go index 07f36fe..6299a0a 100644 --- a/api/doc.go +++ b/api/doc.go @@ -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 diff --git a/api/openapi.yaml b/api/openapi.yaml index acdf281..5076286 100644 --- a/api/openapi.yaml +++ b/api/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 diff --git a/api/server.go b/api/server.go index 64948a3..d95adfc 100644 --- a/api/server.go +++ b/api/server.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 ( @@ -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 diff --git a/api/types.go b/api/types.go index 03ef2b4..4f602f0 100644 --- a/api/types.go +++ b/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 diff --git a/dev/docker-compose.yml b/dev/docker-compose.yml new file mode 100644 index 0000000..dd69a3f --- /dev/null +++ b/dev/docker-compose.yml @@ -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 diff --git a/docker-compose.yml b/docker-compose.yml index dd69a3f..785209e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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 diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 5f3bf13..572cafa 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -51,7 +51,13 @@ const router = createBrowserRouter( } - 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 } + } + } /> { - 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,72 +57,114 @@ const Quest = () => { .catch(({ message }) => setError('Ошибка создания')) } - if (!quest) { - quest = { - type: 'city', - points: 10, - tasks: [] - } - } - return ( <> {quest.title ? (quest.title) : ('Новый квест')} {error ? : null} -
- - - - - - - - - - - - - - + + setFields(allFields)} + > + + + + + + + + + + + + + + + + + + + {quest.icon ? : null} + + + + + + + Полевой + Виртуальный + + + + + + + {(tasks, { add, remove }) => ( + <> + {tasks.map(renderTaskForm(remove))} + + + + + )} + + + + + Файлы + - {quest.icon ? : null} - -
- - - Полевой - Виртуальный - - - - - - - {(tasks, { add, remove }) => ( - <> - {tasks.map(renderTaskForm(remove))} - - - - - )} - - + Ранее загруженные файлы: + + + + setPreview(false)}>Закрыть} + width={'80%'} + centered + > + ( + + + {task.text} + + Коды: +
    + {task.codes.map(c =>
  • {c.code}
  • )} +
+ + } + /> +
+ )} /> +
) } @@ -187,4 +244,24 @@ const renderCodeForm = remove => code => ( ) +const renderFile = (e, file) => ( +
+ {e} + {file && file.response && file.response.uuid + ? <>Код для вставки:
![](/api/file/{file.response.uuid})
+ : null} +
+) + +const renderFileItem = (file) => ( + + } + title={file.originalName} + description={<>Код для вставки:
![](/api/file/{file.id})
} + /> +
+ +) + export default Quest diff --git a/frontend/src/pages/admin/Quests.jsx b/frontend/src/pages/admin/Quests.jsx index 37ef9f6..0c310a3 100644 --- a/frontend/src/pages/admin/Quests.jsx +++ b/frontend/src/pages/admin/Quests.jsx @@ -15,15 +15,16 @@ const Quests = () => { rowKey={'id'} columns={[ { - title: 'UUID', - dataIndex: 'id', - key: 'id', - render: uid => ${uid} + title: 'Опубликован?', + dataIndex: 'visible', + key: 'visible', + render: visible => visible ? 'Да' : 'Нет' }, { title: 'Название', dataIndex: 'title', - key: 'title' + key: 'title', + render: (title, q) => {title} }, { title: 'Тип', diff --git a/frontend/src/utils/uuid.js b/frontend/src/utils/uuid.js new file mode 100644 index 0000000..d876381 --- /dev/null +++ b/frontend/src/utils/uuid.js @@ -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) + ) +}; diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 70ea720..c1d78c6 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -62,7 +62,7 @@ const manifest = { purpose: 'maskable any' } ], - theme_color: '#fb923c', + theme_color: '#59FBEA', background_color: '#171e26', display: 'standalone', scope: '/', diff --git a/go.mod b/go.mod index 5c41630..0e381e0 100644 --- a/go.mod +++ b/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 ) diff --git a/go.sum b/go.sum index 764f1cc..fc34ab6 100644 --- a/go.sum +++ b/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= diff --git a/main.go b/main.go index a6c1b14..40c246d 100644 --- a/main.go +++ b/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 ]-- diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..3fdad5c --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "nquest", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/pkg/controller/admin.go b/pkg/controller/admin.go index b849ae0..c68eb9b 100644 --- a/pkg/controller/admin.go +++ b/pkg/controller/admin.go @@ -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, + Authors: []*models.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() diff --git a/pkg/controller/file.go b/pkg/controller/file.go index 939e88c..531ae12 100644 --- a/pkg/controller/file.go +++ b/pkg/controller/file.go @@ -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) } diff --git a/pkg/models/file.go b/pkg/models/file.go index b413ee5..bbf347e 100644 --- a/pkg/models/file.go +++ b/pkg/models/file.go @@ -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:"-"` diff --git a/pkg/service/file.go b/pkg/service/file.go index 833ad5b..ef976ca 100644 --- a/pkg/service/file.go +++ b/pkg/service/file.go @@ -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 + 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, + 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 } diff --git a/store/.gitkeep b/store/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/store/d2c3f25b-9f35-4ea7-8039-69a0f7e6c5ac/logo.jpeg b/store/d2c3f25b-9f35-4ea7-8039-69a0f7e6c5ac/logo.jpeg new file mode 100644 index 0000000..6f9c35e Binary files /dev/null and b/store/d2c3f25b-9f35-4ea7-8039-69a0f7e6c5ac/logo.jpeg differ diff --git a/store/d2c3f25b-9f35-4ea7-8039-69a0f7e6c5ac/logo.png b/store/d2c3f25b-9f35-4ea7-8039-69a0f7e6c5ac/logo.png new file mode 100644 index 0000000..e580341 Binary files /dev/null and b/store/d2c3f25b-9f35-4ea7-8039-69a0f7e6c5ac/logo.png differ diff --git a/store/d2c3f25b-9f35-4ea7-8039-69a0f7e6c5ac/logo.svg b/store/d2c3f25b-9f35-4ea7-8039-69a0f7e6c5ac/logo.svg new file mode 100644 index 0000000..7d1d640 --- /dev/null +++ b/store/d2c3f25b-9f35-4ea7-8039-69a0f7e6c5ac/logo.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + NXP + diff --git a/store/d2c3f25b-9f35-4ea7-8039-69a0f7e6c5ac/logo512.png b/store/d2c3f25b-9f35-4ea7-8039-69a0f7e6c5ac/logo512.png new file mode 100644 index 0000000..92d074b Binary files /dev/null and b/store/d2c3f25b-9f35-4ea7-8039-69a0f7e6c5ac/logo512.png differ diff --git a/store/d2c3f25b-9f35-4ea7-8039-69a0f7e6c5ac/Снимок экрана_20240228_222848.png b/store/d2c3f25b-9f35-4ea7-8039-69a0f7e6c5ac/Снимок экрана_20240228_222848.png new file mode 100644 index 0000000..5b0d4c7 Binary files /dev/null and b/store/d2c3f25b-9f35-4ea7-8039-69a0f7e6c5ac/Снимок экрана_20240228_222848.png differ diff --git a/store/d2c3f25b-9f35-4ea7-8039-69a0f7e6c5ac/Снимок экрана_20240228_223157.png b/store/d2c3f25b-9f35-4ea7-8039-69a0f7e6c5ac/Снимок экрана_20240228_223157.png new file mode 100644 index 0000000..b8a4488 Binary files /dev/null and b/store/d2c3f25b-9f35-4ea7-8039-69a0f7e6c5ac/Снимок экрана_20240228_223157.png differ diff --git a/store/d2c3f25b-9f35-4ea7-8039-69a0f7e6c5ac/Снимок экрана_20240229_041441.png b/store/d2c3f25b-9f35-4ea7-8039-69a0f7e6c5ac/Снимок экрана_20240229_041441.png new file mode 100644 index 0000000..f297d13 Binary files /dev/null and b/store/d2c3f25b-9f35-4ea7-8039-69a0f7e6c5ac/Снимок экрана_20240229_041441.png differ