diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7c98de1..670887b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -6,6 +6,7 @@ stages: include: - local: .gitlab/_common.gitlab-ci.yml - local: .gitlab/_rules.gitlab-ci.yml + - local: /.gitlab/test.gitlab-ci.yml - local: /.gitlab/build.gitlab-ci.yml - local: /.gitlab/deploy.gitlab-ci.yml - template: Jobs/Secret-Detection.gitlab-ci.yml diff --git a/.gitlab/_common.gitlab-ci.yml b/.gitlab/_common.gitlab-ci.yml index fb6d21b..2cd2c34 100644 --- a/.gitlab/_common.gitlab-ci.yml +++ b/.gitlab/_common.gitlab-ci.yml @@ -12,3 +12,13 @@ image: docker:$DOCKER_VERSION-git .login_registry: before_script: - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY + +.go-cache: + variables: + GOPATH: $CI_PROJECT_DIR/.go + before_script: + - mkdir -p .go + - export PATH=$PATH:$GOROOT/bin:$GOPATH/bin + cache: + paths: + - .go/pkg/mod/ diff --git a/.gitlab/build.gitlab-ci.yml b/.gitlab/build.gitlab-ci.yml index d34188e..35d56e0 100644 --- a/.gitlab/build.gitlab-ci.yml +++ b/.gitlab/build.gitlab-ci.yml @@ -15,7 +15,7 @@ build_release: --build-arg GOLANG_VERSION=$GOLANG_VERSION --build-arg NODE_VERSION=$NODE_VERSION --build-arg ALPINE_VERSION=$ALPINE_VERSION - --build-arg APP_VERSION=$CI_COMMIT_TAG + --build-arg VERSION=$CI_COMMIT_TAG --build-arg BUILD_TIME=$CI_JOB_STARTED_AT --tag $CURRENT_IMAGE --tag $LATEST_IMAGE diff --git a/.gitlab/test.gitlab-ci.yml b/.gitlab/test.gitlab-ci.yml new file mode 100644 index 0000000..41c6216 --- /dev/null +++ b/.gitlab/test.gitlab-ci.yml @@ -0,0 +1,17 @@ +unit_tests: + rules: !reference [.rules:default, rules] + stage: test + image: golang:$GOLANG_VERSION-alpine + extends: + - .go-cache + script: + - ./scripts/swagger.sh install + - ./scripts/swagger.sh init + - go install gotest.tools/gotestsum@latest + - gotestsum --junitfile report.xml --format testname -- ./... -coverprofile=profile.cov + - go tool cover -func profile.cov + coverage: '/\(statements\)(?:\s+)?(\d+(?:\.\d+)?%)/' + artifacts: + when: always + reports: + junit: report.xml diff --git a/docker-compose.yml b/docker-compose.yml index e796d39..962ba90 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -115,6 +115,7 @@ services: environment: - PUID=1000 - PGID=1000 + - SWAGGER_HOST=https://cafe.test labels: - 'traefik.enable=true' - 'traefik.http.routers.backend.rule=Host(`cafe.test`) && PathPrefix(`/api`)' diff --git a/docker/Dockerfile b/docker/Dockerfile index 130bcfb..74adbbc 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -39,9 +39,8 @@ WORKDIR /app COPY ./scripts/entrypoint.sh . COPY --from=logo /logo.txt . -COPY --from=nodeBuilder /app/dist/ ./templates/ +COPY --from=nodeBuilder /app/dist/ ./web/ COPY --from=goBuilder /app/cafe-plaetschwiesle . -COPY config.toml . ARG VERSION ENV VERSION=$VERSION diff --git a/internal/env/env.go b/internal/env/env.go index 6d09c8e..bc9f229 100644 --- a/internal/env/env.go +++ b/internal/env/env.go @@ -14,8 +14,8 @@ type Config struct { TimeZone string `env:"TZ" envDefault:"Etc/UTC" validate:"timezone"` Port int `env:"PORT" envDefault:"8080" validate:"min=1024,max=49151"` LogLevel string `env:"LOG_LEVEL" envDefault:"info" validate:"oneof=debug info warn error panic fatal"` - Version string `env:"APP_VERSION" envDefault:"v0.0.0"` - SwaggerHost string `env:"SWAGGER_HOST" envDefault:"https://cafe.test"` + Version string `env:"VERSION" envDefault:"v0.0.0"` + SwaggerHost string `env:"SWAGGER_HOST"` DB_Host string `env:"DB_HOST"` DB_User string `env:"DB_USER"` DB_Password string `env:"DB_PASSWORD"` diff --git a/internal/router/middleware.go b/internal/router/middleware.go index 475bc03..2831f0f 100644 --- a/internal/router/middleware.go +++ b/internal/router/middleware.go @@ -4,6 +4,13 @@ import ( "github.com/labstack/echo/v4" ) +func longCacheLifetime(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + c.Response().Header().Set(echo.HeaderCacheControl, "public, max-age=31536000") + return next(c) + } +} + func authHeader(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { c.Response().Header().Set("Remote-Groups", c.Request().Header.Get("Remote-Groups")) diff --git a/internal/router/router.go b/internal/router/router.go index 173e5e1..2ca4409 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -29,11 +29,22 @@ func InitRouter() *echo.Echo { e.Pre(middleware.RemoveTrailingSlash()) e.Validator = &CustomValidator{Validator: newValidator()} + e.Renderer = initTemplates() return e } func SetupRoutes(e *echo.Echo, ctrl *controller.Controller, env *env.Config) { + favicon := e.Group("/favicon", longCacheLifetime) + favicon.Static("/", "web/favicon") + fonts := e.Group("/fonts", longCacheLifetime) + fonts.Static("/", "web/fonts") + img := e.Group("/img", longCacheLifetime) + img.Static("/", "web/img") + + e.Static("/css", "web/css") + e.Static("/js", "web/js") + api := e.Group("/api") { tableGroup := api.Group("/tables") @@ -75,13 +86,13 @@ func SetupRoutes(e *echo.Echo, ctrl *controller.Controller, env *env.Config) { health := api.Group("/health", authHeader) { health.GET("", func(ctx echo.Context) error { - return ctx.String(http.StatusOK, ".") + return ctx.String(http.StatusOK, env.Version) }) } if env.SwaggerHost != "" { - docs.SwaggerInfo.Title = "Cafe" - docs.SwaggerInfo.Description = "This is the backend of a cafe" + docs.SwaggerInfo.Title = "Café Plätschwiesle" + docs.SwaggerInfo.Description = "This is the backend of Café Plätschwiesle" docs.SwaggerInfo.Version = env.Version docs.SwaggerInfo.BasePath = "/api" parsed, _ := url.Parse(env.SwaggerHost) @@ -90,5 +101,12 @@ func SetupRoutes(e *echo.Echo, ctrl *controller.Controller, env *env.Config) { api.GET("/swagger/*", echoSwagger.WrapHandler) zap.L().Info("swagger running", zap.String("url", env.SwaggerHost+"/api/swagger/index.html")) } + + e.GET("/robots.txt", func(ctx echo.Context) error { + return ctx.String(http.StatusOK, "User-agent: *\nDisallow: /") + }) + e.RouteNotFound("*", func(c echo.Context) error { + return c.Render(http.StatusOK, "index.html", nil) + }) } } diff --git a/internal/router/templates.go b/internal/router/templates.go new file mode 100644 index 0000000..b99a22e --- /dev/null +++ b/internal/router/templates.go @@ -0,0 +1,22 @@ +package router + +import ( + "html/template" + "io" + + "github.com/labstack/echo/v4" +) + +type Template struct { + templates *template.Template +} + +func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error { + return t.templates.ExecuteTemplate(w, name, data) +} + +func initTemplates() *Template { + return &Template{ + templates: template.Must(template.ParseGlob("web/*.html")), + } +} diff --git a/web/src/components/UI/TheNavigation.vue b/web/src/components/UI/TheNavigation.vue index e784958..0cc63fb 100644 --- a/web/src/components/UI/TheNavigation.vue +++ b/web/src/components/UI/TheNavigation.vue @@ -103,6 +103,7 @@ export default defineComponent({ visible: () => maker.value, }, { label: "Abmelden", icon: "pi pi-fw pi-power-off", command: () => emit("logout") }, + { label: store.getters.getVersion, icon: "pi pi-fw pi-github", disabled: true }, ], }, ]); diff --git a/web/src/main.ts b/web/src/main.ts index 62086ae..bb9db5c 100644 --- a/web/src/main.ts +++ b/web/src/main.ts @@ -1,4 +1,4 @@ -import { createApp } from "vue"; +import { createApp, version } from "vue"; import { OpenAPI, UsersService } from "@/services/openapi"; import App from "./App.vue"; import router from "./router"; @@ -26,6 +26,7 @@ async function getHealth() { store.commit("setGroups", groups); const user = await UsersService.getUsers(response.headers.get("remote-name") || "Benutzer"); store.commit("setUser", user); + store.commit("setVersion", await response.text()); } getHealth().then(() => { diff --git a/web/src/store/index.ts b/web/src/store/index.ts index 28b8201..861806e 100644 --- a/web/src/store/index.ts +++ b/web/src/store/index.ts @@ -6,6 +6,7 @@ import { controller_User } from "@/services/openapi"; interface AppStateModel { user: controller_User; groups: string[]; + version: string; } export default createStore({ state: { @@ -15,6 +16,7 @@ export default createStore({ show_hot_drinks: true, }, groups: [""], + version: "", }, getters: { getUser(state: AppStateModel) { @@ -26,6 +28,9 @@ export default createStore({ getUsername(state: AppStateModel) { return state.user.username; }, + getVersion(state: AppStateModel) { + return state.version; + }, }, mutations: { setUser(state: AppStateModel, _user: controller_User) { @@ -34,6 +39,9 @@ export default createStore({ setGroups(state: AppStateModel, groups: string[]) { state.groups = groups; }, + setVersion(state: AppStateModel, version: string) { + state.version = version; + }, }, actions: {}, modules: {