diff --git a/Caddyfile b/Caddyfile new file mode 100644 index 0000000..e8c3eaf --- /dev/null +++ b/Caddyfile @@ -0,0 +1,19 @@ +{ + debug +} + +http:// { + handle_path /file/* { + root * /app/store + file_server + } + handle /api* { + reverse_proxy http://app:8000 + } + handle { + root * /app/frontend/dist + file_server + try_files {path} /index.html + encode gzip zstd + } +} \ No newline at end of file diff --git a/api/openapi.yaml b/api/openapi.yaml index d91fe5a..fae1052 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -377,6 +377,19 @@ paths: responses: 200: $ref: '#/components/responses/taskResponse' + /file/{uid}: + get: + operationId: getFile + parameters: + - in: path + name: uid + required: true + schema: + format: uuid + type: string + response: + 307: + description: redirect /games: get: responses: diff --git a/api/parts/game.yaml b/api/parts/game.yaml index 9629fa7..61a9adc 100644 --- a/api/parts/game.yaml +++ b/api/parts/game.yaml @@ -40,4 +40,17 @@ paths: responses: 200: $ref: "#/components/responses/taskResponse" + /file/{uid}: + get: + operationId: getFile + parameters: + - name: uid + in: path + required: true + schema: + type: string + format: uuid + response: + 307: + description: redirect diff --git a/api/server.go b/api/server.go index 3b1054e..52b980a 100644 --- a/api/server.go +++ b/api/server.go @@ -43,6 +43,9 @@ type ServerInterface interface { // (POST /engine/{uid}/code) EnterCode(ctx echo.Context, uid openapi_types.UUID) error + // (GET /file/{uid}) + GetFile(ctx echo.Context, uid openapi_types.UUID) error + // (GET /games) GetGames(ctx echo.Context) error @@ -176,6 +179,24 @@ func (w *ServerInterfaceWrapper) EnterCode(ctx echo.Context) error { return err } +// GetFile converts echo context to params. +func (w *ServerInterfaceWrapper) GetFile(ctx echo.Context) error { + var err error + // ------------- Path parameter "uid" ------------- + var uid openapi_types.UUID + + err = runtime.BindStyledParameterWithOptions("simple", "uid", ctx.Param("uid"), &uid, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter uid: %s", err)) + } + + ctx.Set(CookieAuthScopes, []string{}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetFile(ctx, uid) + return err +} + // GetGames converts echo context to params. func (w *ServerInterfaceWrapper) GetGames(ctx echo.Context) error { var err error @@ -262,6 +283,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.GET(baseURL+"/admin/games/:uid", wrapper.AdminGetGame) router.GET(baseURL+"/engine/:uid", wrapper.GameEngine) router.POST(baseURL+"/engine/:uid/code", wrapper.EnterCode) + router.GET(baseURL+"/file/:uid", wrapper.GetFile) router.GET(baseURL+"/games", wrapper.GetGames) router.GET(baseURL+"/user", wrapper.GetUser) router.POST(baseURL+"/user/login", wrapper.PostUserLogin) @@ -273,26 +295,27 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "0I910y50yiIW0sJWrst/AwAA//8rjKvDLRwAAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/Dockerfile b/build/api/Dockerfile similarity index 97% rename from Dockerfile rename to build/api/Dockerfile index 324c107..e252b98 100644 --- a/Dockerfile +++ b/build/api/Dockerfile @@ -14,7 +14,6 @@ RUN apk update --no-cache && apk add --no-cache tzdata COPY go.mod go.sum ./ RUN go mod download && go mod verify COPY . . -COPY --from=frontend /app/dist /app/frontend/dist RUN go build -ldflags="-X gitrepo.ru/neonxp/nquest.Version=${VERSION}" -o nquest *.go # Runtime container @@ -24,6 +23,7 @@ RUN apk update --no-cache && apk add --no-cache ca-certificates COPY --from=backend /usr/share/zoneinfo/Europe/Moscow /usr/share/zoneinfo/Europe/Moscow COPY --from=backend /app/nquest /app/nquest +COPY --from=frontend /app/dist /app/frontend/dist ENV TZ Europe/Moscow diff --git a/build/caddy/Dockerfile b/build/caddy/Dockerfile new file mode 100644 index 0000000..f7c7841 --- /dev/null +++ b/build/caddy/Dockerfile @@ -0,0 +1,3 @@ +FROM caddy:2.8-alpine + +COPY Caddyfile /etc/caddy/Caddyfile \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 7458ce3..41bd55e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,19 +3,30 @@ version: '3.8' volumes: postgres-data: storage: + frontend: services: + caddy: + build: + context: . + dockerfile: build/caddy/Dockerfile + volumes: + - frontend:/app/frontend/dist:ro + - storage:/app/store:ro + ports: + - 8989:80 + depends_on: + - app app: build: context: . - dockerfile: Dockerfile - ports: - - 8989:8989 + dockerfile: build/api/Dockerfile depends_on: - db env_file: - .env volumes: + - frontend:/app/frontend/dist:rw - storage:/app/store:rw db: image: postgres:15-alpine3.17 diff --git a/embeds.go b/embeds.go deleted file mode 100644 index e695a92..0000000 --- a/embeds.go +++ /dev/null @@ -1,17 +0,0 @@ -package main - -import ( - "embed" - - "github.com/labstack/echo/v4" -) - -var ( - //go:embed all:frontend/dist - dist embed.FS - //go:embed frontend/dist/index.html - indexHTML embed.FS - - distDirFS = echo.MustSubFS(dist, "frontend/dist") - distIndexHtml = echo.MustSubFS(indexHTML, "frontend/dist") -) diff --git a/frontend/src/components/Layout.jsx b/frontend/src/components/Layout.jsx index 4d4f60e..dc490a1 100644 --- a/frontend/src/components/Layout.jsx +++ b/frontend/src/components/Layout.jsx @@ -4,7 +4,7 @@ import { UserProvider } from '../store/user' import { ajax } from '../utils/fetch' import { Layout, Menu } from 'antd' import { MenuOutlined } from '@ant-design/icons' -import { Content, Header } from 'antd/es/layout/layout' +import { Content, Footer, Header } from 'antd/es/layout/layout' import { useRole } from '../utils/roles' const AppLayout = () => { @@ -80,9 +80,12 @@ const AppLayout = () => { style={{ width: '100%' }} /> - + + ) } diff --git a/frontend/src/pages/Index.jsx b/frontend/src/pages/Index.jsx index d671bbc..96415e4 100644 --- a/frontend/src/pages/Index.jsx +++ b/frontend/src/pages/Index.jsx @@ -8,12 +8,16 @@ const Index = () => { const { user } = UserProvider.useContainer() const navigate = useNavigate() return (<> - NQuest + <img src="/assets/logo.png" />uest Привет! Это платформа для ARG игр. - Если ты попал сюда случайно, то скорее всего, для тебя здесь нет ничего интересного. + Если ты попал сюда случайно, то скорее всего, для тебя здесь нет ничего интересного.
А если ты знаешь зачем пришёл, то добро пожаловать!
+ + Телеграм-канал с новостями проекта: nquest_ru, рекомендую подписаться.
+ Там будут как roadmap проекта, так и анонсы новых квестов. +
{!user ? ( diff --git a/frontend/src/pages/Quests.jsx b/frontend/src/pages/Quests.jsx index 6154656..b1c9c4f 100644 --- a/frontend/src/pages/Quests.jsx +++ b/frontend/src/pages/Quests.jsx @@ -38,16 +38,14 @@ const renderItem = (user, navigate, item) => { , {item.taskCount} ур - , - <>{moment(item.createdAt).fromNow()}, - <>Автор {item.authors.map(a => a.username)} + ] let questAction = (Необходимо войти) if (user) { questAction = (user.games.find(x => x.id === item.id) - ? Вы уже прошли этот квест + ? Вы уже прошли этот квест : ) } diff --git a/main.go b/main.go index 36954b2..1f44d24 100644 --- a/main.go +++ b/main.go @@ -130,9 +130,6 @@ func main() { // --[ System ]-- e.GET("/metrics", echoprometheus.NewHandler()) - e.StaticFS("/file", afero.NewIOFS(storage)) - e.StaticFS("/*", distDirFS) - e.Logger.Debugf("backend version %s", Version) e.Logger.Fatal(e.Start(cfg.Listen)) } diff --git a/pkg/controller/file.go b/pkg/controller/file.go index cb77e2c..1ef54b9 100644 --- a/pkg/controller/file.go +++ b/pkg/controller/file.go @@ -1,6 +1,8 @@ package controller import ( + "fmt" + "github.com/google/uuid" "github.com/labstack/echo/v4" "gitrepo.ru/neonxp/nquest/api" @@ -57,3 +59,12 @@ func (u *File) AdminListFiles(c echo.Context, quest uuid.UUID) error { return c.JSON(200, list) } + +func (u *File) GetFile(c echo.Context, uid uuid.UUID) error { + f, err := u.FileService.GetFilesByID(c.Request().Context(), uid) + if err != nil { + return err + } + + return c.Redirect(307, fmt.Sprintf(`/file/%s/%s`, f.QuestID, f.Filename)) +} diff --git a/pkg/service/file.go b/pkg/service/file.go index b851f4d..1f1136b 100644 --- a/pkg/service/file.go +++ b/pkg/service/file.go @@ -57,3 +57,9 @@ func (u *File) GetFilesByQuest(ctx context.Context, quest uuid.UUID) ([]*models. return list, u.DB.WithContext(ctx).Find(&list, `quest_id = ?`, quest.String()).Error } + +func (u *File) GetFilesByID(ctx context.Context, fileID uuid.UUID) (*models.File, error) { + file := &models.File{} + + return file, u.DB.WithContext(ctx).First(file, fileID).Error +}