diff --git a/.air.toml b/.air.toml deleted file mode 100644 index c546c22..0000000 --- a/.air.toml +++ /dev/null @@ -1,4 +0,0 @@ -[build] -bin = "tmp/cafe-plaetschwiesle" -cmd = "go build -o tmp/cafe-plaetschwiesle cmd/cafe-plaetschwiesle/cafe-plaetschwiesle.go" -exclude_dir = [".gitlab", "docker", "scripts", "web", "storage", "tmp", "docs"] diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 670887b..a2660bc 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,17 +1,9 @@ stages: - - test - build - deploy 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 - - template: Security/SAST.gitlab-ci.yml - -secret_detection: - rules: !reference [.rules:default, rules] - stage: test diff --git a/.gitlab/_common.gitlab-ci.yml b/.gitlab/_common.gitlab-ci.yml index 2cd2c34..fb6d21b 100644 --- a/.gitlab/_common.gitlab-ci.yml +++ b/.gitlab/_common.gitlab-ci.yml @@ -12,13 +12,3 @@ 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 35d56e0..d34188e 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 VERSION=$CI_COMMIT_TAG + --build-arg APP_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 deleted file mode 100644 index 41c6216..0000000 --- a/.gitlab/test.gitlab-ci.yml +++ /dev/null @@ -1,17 +0,0 @@ -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/README.md b/README.md index 218b990..1d55a17 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,76 @@ # Café Plätschwiesle -![Alt vue](https://img.shields.io/badge/Framework-Vue3-informational?logo=vuedotjs&color=4FC08D) -![Alt typescript](https://img.shields.io/badge/Language-Typescript-informational?logo=typescript&color=3178C6) -![Alt go](https://img.shields.io/badge/Language-Go-informational?logo=go&color=00ADD8) +![Alt vue](https://img.shields.io/badge/Framework-Vue3-informational?style=for-the-badge&logo=vuedotjs&color=4FC08D) +![Alt typescript](https://img.shields.io/badge/Language-Typescript-informational?style=for-the-badge&logo=typescript&color=3178C6) +![Alt go](https://img.shields.io/badge/Language-Go-informational?style=for-the-badge&logo=go&color=00ADD8) -[![pipeline status](https://gitlab.unjx.de/flohoss/cafe-plaetschwiesle/badges/main/pipeline.svg)](https://gitlab.unjx.de/flohoss/cafe-plaetschwiesle/-/commits/main) -[![coverage report](https://gitlab.unjx.de/flohoss/cafe-plaetschwiesle/badges/main/coverage.svg)](https://gitlab.unjx.de/flohoss/cafe-plaetschwiesle/-/commits/main) +## docker-compose example + +```yaml +services: + cafe: + image: ghcr.io/flohoss/cafe-plaetschwiesle:latest + container_name: cafe + restart: unless-stopped + environment: + - PUID=1000 + - PGID=1000 + - ALLOWED_HOSTS=http://localhost:5000,https://home.example.com + - SWAGGER=true + - LOG_LEVEL=info # trace,debug,info,warn,error,fatal,panic + volumes: + - ./storage:/app/storage + ports: + - '127.0.0.1:5000:5000' +``` + +## docker-compose example with MariaDB as database + +```yaml +networks: + net: + external: false + +services: + cafe-db: + image: lscr.io/linuxserver/mariadb:latest + container_name: cafe-db + restart: unless-stopped + environment: + - PUID=1000 + - PGID=1000 + - MYSQL_ROOT_PASSWORD=root + - TZ=Europe/Berlin + - MYSQL_DATABASE=db + - MYSQL_USER=user + - MYSQL_PASSWORD=password + volumes: + - ./db:/config + expose: + - 3306 + networks: + - net + + cafe: + image: ghcr.io/flohoss/cafe-plaetschwiesle:latest + container_name: cafe + restart: unless-stopped + depends_on: + - cafe-db + environment: + - PUID=1000 + - PGID=1000 + - ALLOWED_HOSTS=http://localhost:5000,https://home.example.com + - SWAGGER=true + - LOG_LEVEL=info # trace,debug,info,warn,error,fatal,panic + - MYSQL_URL=cafe-db:3306 + - MYSQL_USER=user + - MYSQL_PASSWORD=password + - MYSQL_DATABASE=db + volumes: + - ./storage:/app/storage + ports: + - '127.0.0.1:5000:5000' + networks: + - net +``` diff --git a/api/middlwares.go b/api/middlwares.go new file mode 100644 index 0000000..9cedeca --- /dev/null +++ b/api/middlwares.go @@ -0,0 +1,52 @@ +package api + +import ( + "cafe/config" + "net/http" + "strings" + "time" + + "github.com/gin-contrib/cors" + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" +) + +func myLogger() gin.HandlerFunc { + return func(c *gin.Context) { + if logrus.GetLevel() != logrus.TraceLevel { + return + } + reqUri := c.Request.RequestURI + if strings.Contains(reqUri, "/storage") { + return + } + startTime := time.Now() + c.Next() + endTime := time.Now() + latencyTime := endTime.Sub(startTime) + logrus.WithFields(logrus.Fields{ + "status": http.StatusText(c.Writer.Status()), + "latency": latencyTime, + "client": c.ClientIP(), + "method": c.Request.Method, + }).Trace(reqUri) + } +} + +func authHeader() gin.HandlerFunc { + return func(c *gin.Context) { + c.Writer.Header().Set("Remote-Groups", c.Request.Header.Get("Remote-Groups")) + c.Writer.Header().Set("Remote-Name", c.Request.Header.Get("Remote-Name")) + } +} + +func (a *Api) SetMiddlewares() { + a.Router.Use(myLogger()) + a.Router.Use(gin.Recovery()) + a.Router.Use(cors.Default()) + _ = a.Router.SetTrustedProxies(nil) + a.Router.MaxMultipartMemory = 8 << 20 // 8 MiB + logrus.WithFields(logrus.Fields{ + "allowedOrigins": config.Cafe.AllowedHosts, + }).Debug("Middlewares set") +} diff --git a/api/router.go b/api/router.go new file mode 100644 index 0000000..7b06266 --- /dev/null +++ b/api/router.go @@ -0,0 +1,62 @@ +package api + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" +) + +func (a *Api) SetupRouter() { + api := a.Router.Group("/api") + { + tableGroup := api.Group("/tables") + { + tableGroup.GET("", a.getTables) + tableGroup.POST("", a.createTable) + tableGroup.DELETE("", a.deleteTable) + } + orderGroup := api.Group("/orders") + { + orderGroup.GET("", a.getOrders) + orderGroup.POST("", a.createOrder) + orderGroup.DELETE("", a.deleteOrder) + orderGroup.PUT("", a.updateOrder) + orderGroup.GET("/ws", a.serveWs) + orderItemGroup := orderGroup.Group("/items") + { + orderItemGroup.GET("", a.getOrderItems) + orderItemGroup.POST("", a.createOrderItem) + orderItemGroup.PUT("", a.updateOrderItem) + orderItemGroup.DELETE("/:id", a.deleteOrderItem) + } + } + billGroup := api.Group("/bills") + { + billGroup.GET("", a.getBills) + billGroup.POST("", a.createBill) + billGroup.DELETE("/:id", a.deleteBill) + billItemGroup := billGroup.Group("/items") + { + billItemGroup.GET("", a.getBillItems) + } + } + userGroup := api.Group("/users") + { + userGroup.GET("/:username", a.getUser) + userGroup.PUT("", a.updateUser) + } + health := api.Group("/health") + { + health.Use(authHeader()) + health.GET("", func(c *gin.Context) { + c.Status(http.StatusOK) + }) + } + } + + a.Router.NoRoute(func(c *gin.Context) { + c.HTML(http.StatusOK, "index.html", nil) + }) + logrus.WithField("amount", len(a.Router.Routes())).Debug("Routes initialized") +} diff --git a/api/routesBill.go b/api/routesBill.go new file mode 100644 index 0000000..38d0453 --- /dev/null +++ b/api/routesBill.go @@ -0,0 +1,112 @@ +package api + +import ( + "cafe/service" + "cafe/types" + "net/http" + "strconv" + "strings" + + "github.com/gin-gonic/gin" +) + +// @Schemes +// @Summary get all bills +// @Description gets all bills as array +// @Tags bills +// @Produce json +// @Param year query int true "year" +// @Param month query int true "month (1-12)" +// @Param day query int true "day (1-31)" +// @Success 200 {array} service.Bill +// @Router /bills [get] +func (a *Api) getBills(c *gin.Context) { + year, presentYear := c.GetQuery("year") + month, presentMonth := c.GetQuery("month") + day, presentDay := c.GetQuery("day") + if !presentYear || !presentMonth || !presentDay { + c.JSON(http.StatusBadRequest, errorResponse{types.MissingInformation.String()}) + return + } + bills, err := service.GetAllBills(year, month, day) + if err != nil { + c.JSON(http.StatusBadRequest, errorResponse{err.Error()}) + } else { + c.JSON(http.StatusOK, bills) + } +} + +// @Schemes +// @Summary get all billItems +// @Description gets all billItems for bill +// @Tags bills +// @Produce json +// @Param bill query int true "Bill ID" +// @Success 200 {array} service.BillItem +// @Router /bills/items [get] +func (a *Api) getBillItems(c *gin.Context) { + bill, err := service.DoesBillExist(c.Query("bill")) + if err != nil { + c.JSON(http.StatusNotFound, errorResponse{err.Error()}) + return + } + billItems, err := service.GetAllBillItems(bill.ID) + if err != nil { + c.JSON(http.StatusNotFound, errorResponse{err.Error()}) + } else { + c.JSON(http.StatusOK, billItems) + } +} + +// @Schemes +// @Summary create new bill +// @Description creates a new bill and returns it +// @Tags bills +// @Produce json +// @Param table query int true "Table ID" +// @Param filter query string false "filter" +// @Success 201 {object} service.Bill +// @Failure 404 "Not Found" +// @Failure 500 {object} errorResponse +// @Router /bills [post] +func (a *Api) createBill(c *gin.Context) { + table, tableErr := strconv.ParseUint(c.Query("table"), 10, 64) + if tableErr != nil { + c.JSON(http.StatusBadRequest, errorResponse{types.MissingInformation.String()}) + return + } + stringFiler, filterPresent := c.GetQuery("filter") + var filter []string + if filterPresent { + filter = strings.Split(stringFiler, ",") + } + bill, err := service.CreateBill(service.GetOrderOptions{TableId: table, Grouped: true, Filter: filter}) + if err != nil { + c.JSON(http.StatusInternalServerError, errorResponse{err.Error()}) + } + c.JSON(http.StatusCreated, bill) +} + +// @Schemes +// @Summary delete a bill +// @Description deletes a bill +// @Tags bills +// @Produce json +// @Param id path int true "Bill ID" +// @Success 200 "OK" +// @Failure 404 "Not Found" +// @Failure 500 {object} errorResponse +// @Router /bills/{id} [delete] +func (a *Api) deleteBill(c *gin.Context) { + id := c.Param("id") + bill, err := service.DoesBillExist(id) + if err != nil { + c.JSON(http.StatusNotFound, errorResponse{err.Error()}) + return + } + err = service.DeleteBill(&bill) + if err != nil { + c.JSON(http.StatusInternalServerError, errorResponse{err.Error()}) + } + c.Status(http.StatusOK) +} diff --git a/api/routesOrder.go b/api/routesOrder.go new file mode 100644 index 0000000..80da9bc --- /dev/null +++ b/api/routesOrder.go @@ -0,0 +1,258 @@ +package api + +import ( + "cafe/hub" + "cafe/service" + "cafe/types" + ws "cafe/websocket" + "net/http" + "strconv" + "strings" + + "github.com/gin-gonic/gin" + "github.com/gorilla/websocket" + "github.com/sirupsen/logrus" +) + +// @Schemes +// @Summary get all orders +// @Description gets all orders as array +// @Tags orders +// @Produce json +// @Param table query int false "Table ID" +// @Param grouping query bool false "grouping" +// @Param filter query string false "filter" +// @Success 200 {array} service.Order +// @Router /orders [get] +func (a *Api) getOrders(c *gin.Context) { + table, _ := strconv.ParseUint(c.Query("table"), 10, 64) + grouping, _ := strconv.ParseBool(c.Query("grouping")) + stringFiler, filterPresent := c.GetQuery("filter") + var filter []string + if filterPresent { + filter = strings.Split(stringFiler, ",") + } + options := service.GetOrderOptions{TableId: table, Grouped: grouping, Filter: filter} + var orders []service.Order + if options.TableId == 0 { + orders = service.GetAllActiveOrders() + } else { + orders = service.GetAllOrdersForTable(options) + } + c.JSON(http.StatusOK, orders) +} + +// @Schemes +// @Summary create new order +// @Description creates a new order and returns it +// @Tags orders +// @Accept json +// @Produce json +// @Param item query int true "OrderItem ID" +// @Param table query int true "Table ID" +// @Success 201 {object} service.Order +// @Failure 400 {object} errorResponse +// @Failure 500 {object} errorResponse +// @Router /orders [post] +func (a *Api) createOrder(c *gin.Context) { + table, err1 := strconv.ParseUint(c.Query("table"), 10, 64) + item, err2 := strconv.ParseUint(c.Query("item"), 10, 64) + if err1 != nil || err2 != nil { + c.JSON(http.StatusBadRequest, errorResponse{types.MissingInformation.String()}) + return + } + order := service.Order{TableID: table, OrderItemID: item, IsServed: false} + err := service.CreateOrder(&order) + if err != nil { + c.JSON(http.StatusInternalServerError, errorResponse{err.Error()}) + } else { + c.JSON(http.StatusCreated, order) + } +} + +// @Schemes +// @Summary delete an order +// @Description deletes an order from the database +// @Tags orders +// @Produce json +// @Param item query int true "OrderItem ID" +// @Param table query int true "Table ID" +// @Success 200 "OK" +// @Failure 400 {object} errorResponse +// @Failure 500 {object} errorResponse +// @Router /orders [delete] +func (a *Api) deleteOrder(c *gin.Context) { + item := c.Query("item") + table := c.Query("table") + if table == "" || item == "" { + c.JSON(http.StatusBadRequest, errorResponse{types.MissingInformation.String()}) + return + } + err := service.DeleteOrder(table, item) + if err != nil { + c.JSON(http.StatusInternalServerError, errorResponse{err.Error()}) + } else { + c.Status(http.StatusOK) + } +} + +// @Schemes +// @Summary update an order +// @Description updates an order with provided information +// @Tags orders +// @Accept json +// @Produce json +// @Param order body service.Order true "updated Order" +// @Success 200 {object} service.Order +// @Failure 400 {object} errorResponse +// @Failure 404 "Not Found" errorResponse +// @Failure 500 {object} errorResponse +// @Router /orders [put] +func (a *Api) updateOrder(c *gin.Context) { + var newOrder service.Order + err := c.ShouldBindJSON(&newOrder) + if err != nil { + c.JSON(http.StatusBadRequest, errorResponse{types.MissingInformation.String()}) + return + } + oldOrder, err := service.DoesOrderExist(strconv.Itoa(int(newOrder.ID))) + if err != nil { + c.JSON(http.StatusNotFound, errorResponse{err.Error()}) + return + } + err = service.UpdateOrder(&oldOrder, &newOrder) + if err != nil { + c.JSON(http.StatusInternalServerError, errorResponse{err.Error()}) + } else { + c.JSON(http.StatusOK, newOrder) + } +} + +func (a *Api) serveWs(c *gin.Context) { + conn, err := ws.Upgrader.Upgrade(c.Writer, c.Request, nil) + if err != nil { + logrus.WithField("error", err).Warning("Cannot upgrade websocket") + return + } + messageChan := make(hub.NotifierChan) + a.Hub.NewClients <- messageChan + defer func() { + a.Hub.ClosingClients <- messageChan + conn.Close() + }() + go ws.ReadPump(conn) + for { + msg, ok := <-messageChan + if !ok { + err := conn.WriteMessage(websocket.CloseMessage, []byte{}) + if err != nil { + return + } + return + } + err := conn.WriteJSON(msg) + if err != nil { + return + } + } +} + +// @Schemes +// @Summary get all orderItems +// @Description gets all orderItems as array +// @Tags orderItems +// @Produce json +// @Param type query int true "ItemType" +// @Success 200 {array} service.OrderItem +// @Router /orders/items [get] +func (a *Api) getOrderItems(c *gin.Context) { + orderType := c.Query("type") + if orderType == "" { + c.JSON(http.StatusBadRequest, errorResponse{types.MissingInformation.String()}) + } else { + c.JSON(http.StatusOK, service.GetOrderItemsForType(orderType)) + } +} + +// @Schemes +// @Summary create new orderItem +// @Description creates a new orderItem and returns it +// @Tags orderItems +// @Accept json +// @Produce json +// @Param order body service.OrderItem true "OrderItem ID" +// @Success 201 {object} service.OrderItem +// @Failure 400 {object} errorResponse +// @Failure 500 {object} errorResponse +// @Router /orders/items [post] +func (a *Api) createOrderItem(c *gin.Context) { + var orderItem service.OrderItem + err := c.ShouldBindJSON(&orderItem) + if err != nil { + c.JSON(http.StatusBadRequest, errorResponse{types.MissingInformation.String()}) + return + } + err = service.CreateOrderItem(&orderItem) + if err != nil { + c.JSON(http.StatusInternalServerError, errorResponse{err.Error()}) + } else { + c.JSON(http.StatusCreated, orderItem) + } +} + +// @Schemes +// @Summary update a orderItem +// @Description updates a orderItem with provided information +// @Tags orderItems +// @Accept json +// @Produce json +// @Param orderItem body service.OrderItem true "updated OrderItem" +// @Success 200 {object} service.OrderItem +// @Failure 400 {object} errorResponse +// @Failure 404 "Not Found" errorResponse +// @Failure 500 {object} errorResponse +// @Router /orders/items [put] +func (a *Api) updateOrderItem(c *gin.Context) { + var newOrderItem service.OrderItem + err := c.ShouldBindJSON(&newOrderItem) + if err != nil { + c.JSON(http.StatusBadRequest, errorResponse{types.MissingInformation.String()}) + return + } + oldOrderItem, err := service.DoesOrderItemExist(strconv.Itoa(int(newOrderItem.ID))) + if err != nil { + c.JSON(http.StatusNotFound, errorResponse{err.Error()}) + return + } + err = service.UpdateOrderItem(&oldOrderItem, &newOrderItem) + if err != nil { + c.JSON(http.StatusInternalServerError, errorResponse{err.Error()}) + } else { + c.JSON(http.StatusOK, newOrderItem) + } +} + +// @Schemes +// @Summary delete an orderItem +// @Description deletes an orderItem from the database +// @Tags orderItems +// @Produce json +// @Param id path int true "OrderItem ID" +// @Success 200 "OK" +// @Failure 404 "Not Found" +// @Failure 500 {object} errorResponse +// @Router /orders/items/{id} [delete] +func (a *Api) deleteOrderItem(c *gin.Context) { + id := c.Param("id") + orderItem, err := service.DoesOrderItemExist(id) + if err != nil { + c.JSON(http.StatusNotFound, errorResponse{err.Error()}) + return + } + err = service.DeleteOrderItem(&orderItem) + if err != nil { + c.JSON(http.StatusInternalServerError, errorResponse{err.Error()}) + } else { + c.Status(http.StatusOK) + } +} diff --git a/api/routesTable.go b/api/routesTable.go new file mode 100644 index 0000000..6706dcf --- /dev/null +++ b/api/routesTable.go @@ -0,0 +1,54 @@ +package api + +import ( + "cafe/service" + "net/http" + + "github.com/gin-gonic/gin" +) + +// @Schemes +// @Summary get all active tables +// @Description gets all active tables as array +// @Tags tables +// @Produce json +// @Success 200 {array} service.Table +// @Router /tables [get] +func (a *Api) getTables(c *gin.Context) { + c.JSON(http.StatusOK, service.GetAllTables()) +} + +// @Schemes +// @Summary create new table +// @Description creates a new table and returns it +// @Tags tables +// @Accept json +// @Produce json +// @Success 201 {object} service.Table "Table has been created" +// @Failure 500 {object} errorResponse "Cannot create table" +// @Router /tables [post] +func (a *Api) createTable(c *gin.Context) { + table, err := service.CreateNewTable() + if err != nil { + c.JSON(http.StatusInternalServerError, errorResponse{err.Error()}) + } else { + c.JSON(http.StatusCreated, table) + } +} + +// @Schemes +// @Summary delete the latest table +// @Description deletes the latest table from the database +// @Tags tables +// @Produce json +// @Success 200 "OK" +// @Failure 500 {object} errorResponse "Cannot delete table" +// @Router /tables [delete] +func (a *Api) deleteTable(c *gin.Context) { + err := service.DeleteLatestTable() + if err != nil { + c.JSON(http.StatusInternalServerError, errorResponse{err.Error()}) + } else { + c.Status(http.StatusOK) + } +} diff --git a/api/routesUser.go b/api/routesUser.go new file mode 100644 index 0000000..79265a4 --- /dev/null +++ b/api/routesUser.go @@ -0,0 +1,60 @@ +package api + +import ( + "cafe/types" + "cafe/user" + "net/http" + + "github.com/gin-gonic/gin" +) + +// @Schemes +// @Summary get a user +// @Description gets a user +// @Tags users +// @Produce json +// @Param username path string true "Username" +// @Success 200 {object} user.User +// @Failure 500 {object} errorResponse +// @Router /users/{username} [get] +func (a *Api) getUser(c *gin.Context) { + username := c.Param("username") + u, err := user.GetUserOrCreate(username) + if err != nil { + c.JSON(http.StatusBadRequest, errorResponse{err.Error()}) + } else { + c.JSON(http.StatusOK, u) + } +} + +// @Schemes +// @Summary update a user +// @Description updates a user with provided information +// @Tags users +// @Accept json +// @Produce json +// @Param user body user.User true "updated User" +// @Success 200 {object} user.User +// @Failure 400 {object} errorResponse +// @Failure 404 "Not Found" errorResponse +// @Failure 500 {object} errorResponse +// @Router /users [put] +func (a *Api) updateUser(c *gin.Context) { + var newUser user.User + err := c.ShouldBindJSON(&newUser) + if err != nil { + c.JSON(http.StatusBadRequest, errorResponse{types.MissingInformation.String()}) + return + } + oldUser, err := user.DoesUserExist(newUser.Username) + if err != nil { + c.JSON(http.StatusNotFound, errorResponse{err.Error()}) + return + } + err = user.UpdateUser(&oldUser, &newUser) + if err != nil { + c.JSON(http.StatusInternalServerError, errorResponse{err.Error()}) + } else { + c.JSON(http.StatusOK, newUser) + } +} diff --git a/api/static.go b/api/static.go new file mode 100644 index 0000000..0e4ba35 --- /dev/null +++ b/api/static.go @@ -0,0 +1,26 @@ +package api + +import ( + "cafe/config" + "os" + "path/filepath" + "strings" + + "github.com/gin-contrib/static" + "github.com/sirupsen/logrus" +) + +func (a *Api) HandleStaticFiles() { + a.Router.LoadHTMLFiles(config.TemplatesDir + "index.html") + a.serveFoldersInTemplates() +} + +func (a *Api) serveFoldersInTemplates() { + _ = filepath.WalkDir(config.TemplatesDir, func(path string, info os.DirEntry, err error) error { + if info.IsDir() && info.Name() != strings.TrimSuffix(config.TemplatesDir, "/") { + a.Router.Use(static.Serve("/"+info.Name(), static.LocalFile(config.TemplatesDir+info.Name(), false))) + logrus.WithField("folder", info.Name()).Debug("Serve static folder") + } + return err + }) +} diff --git a/api/swagger.go b/api/swagger.go new file mode 100644 index 0000000..947416d --- /dev/null +++ b/api/swagger.go @@ -0,0 +1,31 @@ +package api + +import ( + "cafe/config" + "cafe/docs" + "net/http" + "net/url" + "os" + + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + swaggerFiles "github.com/swaggo/files" + ginSwagger "github.com/swaggo/gin-swagger" +) + +func (a *Api) SetupSwagger() { + if config.Cafe.Swagger { + docs.SwaggerInfo.Title = "Cafe" + docs.SwaggerInfo.Description = "This is the backend of a cafe" + docs.SwaggerInfo.Version = os.Getenv("VERSION") + docs.SwaggerInfo.BasePath = "/api" + parsed, _ := url.Parse(config.Cafe.AllowedHosts[0]) + docs.SwaggerInfo.Host = parsed.Host + + a.Router.GET("/swagger", func(c *gin.Context) { + c.Redirect(http.StatusMovedPermanently, "/swagger/index.html") + }) + a.Router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) + logrus.WithField("url", config.Cafe.AllowedHosts[0]+"/swagger").Info("Swagger running") + } +} diff --git a/api/types.go b/api/types.go new file mode 100644 index 0000000..616b354 --- /dev/null +++ b/api/types.go @@ -0,0 +1,16 @@ +package api + +import ( + "cafe/hub" + + "github.com/gin-gonic/gin" +) + +type Api struct { + Router *gin.Engine + Hub hub.Hub +} + +type errorResponse struct { + Error string `json:"error" validate:"required"` +} diff --git a/cmd/cafe-plaetschwiesle/cafe-plaetschwiesle.go b/cmd/cafe-plaetschwiesle/cafe-plaetschwiesle.go deleted file mode 100644 index 21752a5..0000000 --- a/cmd/cafe-plaetschwiesle/cafe-plaetschwiesle.go +++ /dev/null @@ -1,30 +0,0 @@ -package main - -import ( - "fmt" - "log" - "net/http" - - "gitlab.unjx.de/flohoss/cafe-plaetschwiesle/internal/controller" - "gitlab.unjx.de/flohoss/cafe-plaetschwiesle/internal/env" - "gitlab.unjx.de/flohoss/cafe-plaetschwiesle/internal/logging" - "gitlab.unjx.de/flohoss/cafe-plaetschwiesle/internal/router" - "go.uber.org/zap" -) - -func main() { - env, err := env.Parse() - if err != nil { - log.Fatal(err) - } - zap.ReplaceGlobals(logging.CreateLogger(env.LogLevel)) - - r := router.InitRouter() - c := controller.NewController(env) - router.SetupRoutes(r, c, env) - - zap.L().Info("starting server", zap.String("url", fmt.Sprintf("http://localhost:%d", env.Port)), zap.String("version", env.Version)) - if err := r.Start(fmt.Sprintf(":%d", env.Port)); err != http.ErrServerClosed { - zap.L().Fatal("cannot start server", zap.Error(err)) - } -} diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..570fb9e --- /dev/null +++ b/config.toml @@ -0,0 +1,10 @@ +ALLOWED_HOSTS = "https://cafe.test" +LOG_LEVEL = "info" +PORT = 5000 +SWAGGER = true + +[MYSQL] +DATABASE = "" +PASSWORD = "" +URL = "" +USER = "" diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..390bf58 --- /dev/null +++ b/config/config.go @@ -0,0 +1,72 @@ +package config + +import ( + "cafe/database" + "strings" + + "github.com/mitchellh/mapstructure" + "github.com/sirupsen/logrus" + "github.com/spf13/viper" + "github.com/unjx-de/go-folder" +) + +const StorageDir = "storage/" +const TemplatesDir = "templates/" + +type Config struct { + Port int + AllowedHosts []string `mapstructure:"ALLOWED_HOSTS"` + Swagger bool + Bookmarks bool + LogLevel string `mapstructure:"LOG_LEVEL"` + Database database.MySQL `mapstructure:"MYSQL"` +} + +var Cafe = Config{} + +func configLogger() { + logrus.SetFormatter(&logrus.TextFormatter{TimestampFormat: "2006/01/02 15:04:05", FullTimestamp: true}) +} + +func readConfig() { + viper.AddConfigPath(".") + viper.SetConfigName("config") + viper.SetConfigType("toml") + err := viper.ReadInConfig() + if err != nil { + logrus.WithField("error", err).Fatal("Failed opening config file") + } + viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + viper.AutomaticEnv() + err = viper.Unmarshal(&Cafe, viper.DecodeHook(mapstructure.StringToSliceHookFunc(","))) + if err != nil { + logrus.WithField("error", err).Fatal("Failed reading environment variables") + } + logrus.WithField("file", viper.ConfigFileUsed()).Info("Initializing configuration") +} + +func setLogLevel() { + logLevel, err := logrus.ParseLevel(Cafe.LogLevel) + if err != nil { + logrus.SetLevel(logrus.InfoLevel) + } else { + logrus.SetLevel(logLevel) + } + logrus.WithField("logLevel", logLevel.String()).Debug("Log level set") +} + +func createFolderStructure() { + folders := []string{StorageDir, TemplatesDir} + err := folder.CreateFolders(folders, 0755) + if err != nil { + logrus.WithField("error", err).Fatal("Failed creating folders") + } + logrus.WithField("folders", folders).Debug("Folders created") +} + +func init() { + configLogger() + readConfig() + setLogLevel() + createFolderStructure() +} diff --git a/database/database.go b/database/database.go new file mode 100644 index 0000000..fe9bae0 --- /dev/null +++ b/database/database.go @@ -0,0 +1,80 @@ +package database + +import ( + "fmt" + "net" + "time" + + "github.com/sirupsen/logrus" + "gorm.io/driver/mysql" + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +type MySQL struct { + Url string + User string + Password string + Database string + ORM *gorm.DB +} + +func (config *MySQL) MigrateHelper(i interface{}, name string) { + err := config.ORM.AutoMigrate(i) + if err != nil { + logrus.WithField("error", err).Fatalf("Failed to migrate %s", name) + } +} + +func (config *MySQL) tryDbConnection() { + i := 1 + total := 20 + for i <= total { + ln, err := net.DialTimeout("tcp", config.Url, 1*time.Second) + if err != nil { + if i == total { + logrus.WithField("attempt", i).Fatal("Failed connecting to database") + } + logrus.WithField("attempt", i).Warning("Connecting to database") + time.Sleep(2 * time.Second) + i++ + } else { + _ = ln.Close() + logrus.WithField("attempt", i).Info("Connected to database") + i = total + 1 + } + } +} + +func (config *MySQL) initializeMySql() { + var err error + config.tryDbConnection() + dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", + config.User, + config.Password, + config.Url, + config.Database, + ) + config.ORM, err = gorm.Open(mysql.Open(dsn), &gorm.Config{PrepareStmt: true}) + if err != nil { + logrus.WithField("error", err).Fatal("Failed to open database") + } +} + +func (config *MySQL) initializeSqLite(storageDir string) { + var err error + absPath := storageDir + "db.sqlite" + config.ORM, err = gorm.Open(sqlite.Open(absPath), &gorm.Config{PrepareStmt: true}) + if err != nil { + logrus.WithField("error", err).Fatal("Failed to open database") + } +} + +func (config *MySQL) Initialize(storageDir string) { + if config.Url == "" { + config.initializeSqLite(storageDir) + } else { + config.initializeMySql() + } + logrus.WithField("dialect", config.ORM.Dialector.Name()).Debug("Database initialized") +} diff --git a/docker-compose.yml b/docker-compose.yml index 962ba90..a2ec2e3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -102,7 +102,7 @@ services: networks: - net volumes: - - ./web:/app/ + - ./frontend:/app/ cafe-backend: build: @@ -111,11 +111,10 @@ services: args: - GOLANG_VERSION=${GOLANG_VERSION} container_name: cafe-backend - command: air -c .air.toml + entrypoint: air --build.exclude_dir "node_modules,frontend,static,docs,storage,tmp,dist" environment: - PUID=1000 - PGID=1000 - - SWAGGER_HOST=https://cafe.test labels: - 'traefik.enable=true' - 'traefik.http.routers.backend.rule=Host(`cafe.test`) && PathPrefix(`/api`)' @@ -123,7 +122,7 @@ services: - 'traefik.http.routers.backend.tls=true' - 'traefik.http.routers.backend.middlewares=authelia@docker' expose: - - 8080 + - 5000 networks: - net volumes: diff --git a/docker/Dockerfile b/docker/Dockerfile index 74adbbc..830235b 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -14,17 +14,17 @@ RUN go mod download COPY . . RUN ./swagger.sh init -RUN go build -ldflags="-s -w" cmd/cafe-plaetschwiesle/cafe-plaetschwiesle.go +RUN go build -ldflags="-s -w" FROM node:${NODE_VERSION}-alpine AS nodeBuilder WORKDIR /app -COPY ./web/package.json . -COPY ./web/yarn.lock . +COPY ./frontend/package.json . +COPY ./frontend/yarn.lock . RUN yarn install --frozen-lockfile COPY --from=goBuilder /app/docs/swagger.json ../docs/swagger.json -COPY ./web/ . +COPY ./frontend/ . RUN yarn run types:openapi RUN yarn run build @@ -39,8 +39,9 @@ WORKDIR /app COPY ./scripts/entrypoint.sh . COPY --from=logo /logo.txt . -COPY --from=nodeBuilder /app/dist/ ./web/ -COPY --from=goBuilder /app/cafe-plaetschwiesle . +COPY --from=nodeBuilder /app/dist/ ./templates/ +COPY --from=goBuilder /app/cafe . +COPY config.toml . ARG VERSION ENV VERSION=$VERSION diff --git a/docker/Dockerfile.dev b/docker/Dockerfile.dev index e2d1a6e..5aa8461 100644 --- a/docker/Dockerfile.dev +++ b/docker/Dockerfile.dev @@ -8,7 +8,8 @@ COPY ./go.sum . RUN go mod download RUN go install github.com/cosmtrek/air@latest -COPY ./.air.toml . ENV VERSION=v0.0.0-DEV ENV BUILD_TIME=2023-06-01T08:07:43.454Z + +CMD ["air"] diff --git a/web/.dockerignore b/frontend/.dockerignore similarity index 100% rename from web/.dockerignore rename to frontend/.dockerignore diff --git a/web/.gitignore b/frontend/.gitignore similarity index 100% rename from web/.gitignore rename to frontend/.gitignore diff --git a/web/README.md b/frontend/README.md similarity index 96% rename from web/README.md rename to frontend/README.md index 8fe4eab..fb1f6eb 100644 --- a/web/README.md +++ b/frontend/README.md @@ -1,35 +1,29 @@ -# web +# frontend ## Project setup - ``` npm install ``` ### Compiles and hot-reloads for development - ``` npm run serve ``` ### Compiles and minifies for production - ``` npm run build ``` ### Run your unit tests - ``` npm run test:unit ``` ### Lints and fixes files - ``` npm run lint ``` ### Customize configuration - See [Configuration Reference](https://cli.vuejs.org/config/). diff --git a/web/babel.config.js b/frontend/babel.config.js similarity index 100% rename from web/babel.config.js rename to frontend/babel.config.js diff --git a/web/package.json b/frontend/package.json similarity index 99% rename from web/package.json rename to frontend/package.json index 35106ea..7be8523 100644 --- a/web/package.json +++ b/frontend/package.json @@ -1,5 +1,5 @@ { - "name": "web", + "name": "frontend", "version": "0.1.0", "private": true, "scripts": { diff --git a/web/public/favicon/android-chrome-192x192.png b/frontend/public/favicon/android-chrome-192x192.png similarity index 100% rename from web/public/favicon/android-chrome-192x192.png rename to frontend/public/favicon/android-chrome-192x192.png diff --git a/web/public/favicon/android-chrome-512x512.png b/frontend/public/favicon/android-chrome-512x512.png similarity index 100% rename from web/public/favicon/android-chrome-512x512.png rename to frontend/public/favicon/android-chrome-512x512.png diff --git a/web/public/favicon/apple-touch-icon.png b/frontend/public/favicon/apple-touch-icon.png similarity index 100% rename from web/public/favicon/apple-touch-icon.png rename to frontend/public/favicon/apple-touch-icon.png diff --git a/web/public/favicon/browserconfig.xml b/frontend/public/favicon/browserconfig.xml similarity index 100% rename from web/public/favicon/browserconfig.xml rename to frontend/public/favicon/browserconfig.xml diff --git a/web/public/favicon/favicon-16x16.png b/frontend/public/favicon/favicon-16x16.png similarity index 100% rename from web/public/favicon/favicon-16x16.png rename to frontend/public/favicon/favicon-16x16.png diff --git a/web/public/favicon/favicon-32x32.png b/frontend/public/favicon/favicon-32x32.png similarity index 100% rename from web/public/favicon/favicon-32x32.png rename to frontend/public/favicon/favicon-32x32.png diff --git a/web/public/favicon/favicon.ico b/frontend/public/favicon/favicon.ico similarity index 100% rename from web/public/favicon/favicon.ico rename to frontend/public/favicon/favicon.ico diff --git a/web/public/favicon/mstile-150x150.png b/frontend/public/favicon/mstile-150x150.png similarity index 100% rename from web/public/favicon/mstile-150x150.png rename to frontend/public/favicon/mstile-150x150.png diff --git a/web/public/favicon/safari-pinned-tab.svg b/frontend/public/favicon/safari-pinned-tab.svg similarity index 100% rename from web/public/favicon/safari-pinned-tab.svg rename to frontend/public/favicon/safari-pinned-tab.svg diff --git a/web/public/favicon/site.webmanifest b/frontend/public/favicon/site.webmanifest similarity index 100% rename from web/public/favicon/site.webmanifest rename to frontend/public/favicon/site.webmanifest diff --git a/web/public/index.html b/frontend/public/index.html similarity index 100% rename from web/public/index.html rename to frontend/public/index.html diff --git a/web/src/App.vue b/frontend/src/App.vue similarity index 100% rename from web/src/App.vue rename to frontend/src/App.vue diff --git a/web/src/assets/fonts/roboto.ttf b/frontend/src/assets/fonts/roboto.ttf similarity index 100% rename from web/src/assets/fonts/roboto.ttf rename to frontend/src/assets/fonts/roboto.ttf diff --git a/web/src/assets/logos/logo.png b/frontend/src/assets/logos/logo.png similarity index 100% rename from web/src/assets/logos/logo.png rename to frontend/src/assets/logos/logo.png diff --git a/web/src/assets/logos/logo_white.png b/frontend/src/assets/logos/logo_white.png similarity index 100% rename from web/src/assets/logos/logo_white.png rename to frontend/src/assets/logos/logo_white.png diff --git a/web/src/components/Bills/BillModal.vue b/frontend/src/components/Bills/BillModal.vue similarity index 93% rename from web/src/components/Bills/BillModal.vue rename to frontend/src/components/Bills/BillModal.vue index 4c53a61..8a8d388 100644 --- a/web/src/components/Bills/BillModal.vue +++ b/frontend/src/components/Bills/BillModal.vue @@ -31,7 +31,7 @@ diff --git a/web/src/components/Tables/TableOverview.vue b/frontend/src/components/Tables/TableOverview.vue similarity index 87% rename from web/src/components/Tables/TableOverview.vue rename to frontend/src/components/Tables/TableOverview.vue index 9eeec8b..d2febf0 100644 --- a/web/src/components/Tables/TableOverview.vue +++ b/frontend/src/components/Tables/TableOverview.vue @@ -3,13 +3,8 @@
- - + +
@@ -61,7 +56,7 @@ import { computed, defineComponent, provide, ref } from "vue"; import BaseCard from "@/components/UI/BaseCard.vue"; import { useStore } from "vuex"; -import { OrdersService, controller_Order, controller_OrderItem, controller_ItemType } from "@/services/openapi"; +import { OrdersService, service_Order, service_OrderItem, types_ItemType } from "@/services/openapi"; import BottomNavigation from "@/components/UI/BottomNavigation.vue"; import Button from "primevue/button"; import { convertToEur } from "@/utils"; @@ -87,7 +82,7 @@ export default defineComponent({ const total = ref(0); const orderItems = computed(() => store.getters.getOrderItems); const options = ref(); - const orders = ref([]); + const orders = ref([]); store.dispatch("getAllOrderItems"); @@ -115,13 +110,13 @@ export default defineComponent({ total.value = temp; } - function addBeverage(itemType: controller_ItemType[]) { + function addBeverage(itemType: types_ItemType[]) { newOrderModal.value = true; options.value = []; itemType.forEach((type) => { options.value = options.value.concat(orderItems.value.get(type)); }); - options.value.sort((a: controller_OrderItem, b: controller_OrderItem) => { + options.value.sort((a: service_OrderItem, b: service_OrderItem) => { const x = a.description.toLowerCase(); const y = b.description.toLowerCase(); if (x < y) return -1; @@ -147,7 +142,7 @@ export default defineComponent({ total, convertToEur, addBeverage, - controller_ItemType, + types_ItemType, postOrder, orders, getData, diff --git a/web/src/components/UI/BaseCard.vue b/frontend/src/components/UI/BaseCard.vue similarity index 100% rename from web/src/components/UI/BaseCard.vue rename to frontend/src/components/UI/BaseCard.vue diff --git a/web/src/components/UI/BaseItem.vue b/frontend/src/components/UI/BaseItem.vue similarity index 100% rename from web/src/components/UI/BaseItem.vue rename to frontend/src/components/UI/BaseItem.vue diff --git a/web/src/components/UI/BaseToolbar.vue b/frontend/src/components/UI/BaseToolbar.vue similarity index 100% rename from web/src/components/UI/BaseToolbar.vue rename to frontend/src/components/UI/BaseToolbar.vue diff --git a/web/src/components/UI/BottomNavigation.vue b/frontend/src/components/UI/BottomNavigation.vue similarity index 100% rename from web/src/components/UI/BottomNavigation.vue rename to frontend/src/components/UI/BottomNavigation.vue diff --git a/web/src/components/UI/SmallCard.vue b/frontend/src/components/UI/SmallCard.vue similarity index 100% rename from web/src/components/UI/SmallCard.vue rename to frontend/src/components/UI/SmallCard.vue diff --git a/web/src/components/UI/TheBadge.vue b/frontend/src/components/UI/TheBadge.vue similarity index 100% rename from web/src/components/UI/TheBadge.vue rename to frontend/src/components/UI/TheBadge.vue diff --git a/web/src/components/UI/TheNavigation.vue b/frontend/src/components/UI/TheNavigation.vue similarity index 86% rename from web/src/components/UI/TheNavigation.vue rename to frontend/src/components/UI/TheNavigation.vue index 0cc63fb..476d0c0 100644 --- a/web/src/components/UI/TheNavigation.vue +++ b/frontend/src/components/UI/TheNavigation.vue @@ -24,7 +24,7 @@ import Menubar from "primevue/menubar"; import { useStore } from "vuex"; import Button from "primevue/button"; import { useRoute } from "vue-router"; -import { TablesService, controller_ItemType } from "@/services/openapi"; +import { TablesService, types_ItemType } from "@/services/openapi"; import { detailedItemTypeString, errorToast } from "@/utils"; import { useToast } from "primevue/usetoast"; import { visible } from "@/keys"; @@ -84,9 +84,9 @@ export default defineComponent({ label: "Artikel", icon: "pi pi-fw pi-shopping-cart", items: [ - { label: detailedItemTypeString(controller_ItemType.Food), icon: "pi pi-fw pi-shopping-cart", to: "/items/" + controller_ItemType.Food }, - { label: detailedItemTypeString(controller_ItemType.ColdDrink), icon: "pi pi-fw pi-shopping-cart", to: "/items/" + controller_ItemType.ColdDrink }, - { label: detailedItemTypeString(controller_ItemType.HotDrink), icon: "pi pi-fw pi-shopping-cart", to: "/items/" + controller_ItemType.HotDrink }, + { label: detailedItemTypeString(types_ItemType.Food), icon: "pi pi-fw pi-shopping-cart", to: "/items/" + types_ItemType.Food }, + { label: detailedItemTypeString(types_ItemType.ColdDrink), icon: "pi pi-fw pi-shopping-cart", to: "/items/" + types_ItemType.ColdDrink }, + { label: detailedItemTypeString(types_ItemType.HotDrink), icon: "pi pi-fw pi-shopping-cart", to: "/items/" + types_ItemType.HotDrink }, ], visible: () => editor.value, }, @@ -103,7 +103,6 @@ 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/components/UI/WaveSpinner.vue b/frontend/src/components/UI/WaveSpinner.vue similarity index 100% rename from web/src/components/UI/WaveSpinner.vue rename to frontend/src/components/UI/WaveSpinner.vue diff --git a/web/src/components/User/UserSettings.vue b/frontend/src/components/User/UserSettings.vue similarity index 91% rename from web/src/components/User/UserSettings.vue rename to frontend/src/components/User/UserSettings.vue index 7b4ab66..a22bd9b 100644 --- a/web/src/components/User/UserSettings.vue +++ b/frontend/src/components/User/UserSettings.vue @@ -16,7 +16,7 @@ import { computed, defineComponent, inject, ref } from "vue"; import Sidebar from "primevue/sidebar"; import { visible } from "@/keys"; -import { controller_User, UsersService } from "@/services/openapi"; +import { user_User, UsersService } from "@/services/openapi"; import InputSwitch from "primevue/inputswitch"; import { useStore } from "vuex"; import { errorToast } from "@/utils"; @@ -30,7 +30,7 @@ export default defineComponent({ const toast = useToast(); const isLoading = ref(false); const isVisible = inject(visible, ref(false)); - const user = computed(() => store.getters.getUser); + const user = computed(() => store.getters.getUser); function updateUser() { isLoading.value = true; diff --git a/web/src/keys.ts b/frontend/src/keys.ts similarity index 100% rename from web/src/keys.ts rename to frontend/src/keys.ts diff --git a/web/src/main.ts b/frontend/src/main.ts similarity index 97% rename from web/src/main.ts rename to frontend/src/main.ts index bb9db5c..0020657 100644 --- a/web/src/main.ts +++ b/frontend/src/main.ts @@ -1,4 +1,4 @@ -import { createApp, version } from "vue"; +import { createApp } from "vue"; import { OpenAPI, UsersService } from "@/services/openapi"; import App from "./App.vue"; import router from "./router"; @@ -18,6 +18,7 @@ import "primeicons/primeicons.css"; import "primeflex/primeflex.css"; export const API_ENDPOINT_URL = window.origin + "/api"; +export const WEBSOCKET_ENDPOINT_URL = API_ENDPOINT_URL.replace("http", "ws") + "/orders/ws"; OpenAPI.BASE = API_ENDPOINT_URL; async function getHealth() { @@ -26,7 +27,6 @@ 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/router/index.ts b/frontend/src/router/index.ts similarity index 100% rename from web/src/router/index.ts rename to frontend/src/router/index.ts diff --git a/web/src/shims-vue.d.ts b/frontend/src/shims-vue.d.ts similarity index 100% rename from web/src/shims-vue.d.ts rename to frontend/src/shims-vue.d.ts diff --git a/web/src/store/index.ts b/frontend/src/store/index.ts similarity index 69% rename from web/src/store/index.ts rename to frontend/src/store/index.ts index 861806e..08f2cd8 100644 --- a/web/src/store/index.ts +++ b/frontend/src/store/index.ts @@ -1,12 +1,11 @@ import { createStore } from "vuex"; import tableStore from "@/store/tables"; import orderItemStore from "@/store/orderItems"; -import { controller_User } from "@/services/openapi"; +import { user_User } from "@/services/openapi"; interface AppStateModel { - user: controller_User; + user: user_User; groups: string[]; - version: string; } export default createStore({ state: { @@ -16,7 +15,6 @@ export default createStore({ show_hot_drinks: true, }, groups: [""], - version: "", }, getters: { getUser(state: AppStateModel) { @@ -28,20 +26,14 @@ export default createStore({ getUsername(state: AppStateModel) { return state.user.username; }, - getVersion(state: AppStateModel) { - return state.version; - }, }, mutations: { - setUser(state: AppStateModel, _user: controller_User) { + setUser(state: AppStateModel, _user: user_User) { state.user = _user; }, setGroups(state: AppStateModel, groups: string[]) { state.groups = groups; }, - setVersion(state: AppStateModel, version: string) { - state.version = version; - }, }, actions: {}, modules: { diff --git a/web/src/store/orderItems/index.ts b/frontend/src/store/orderItems/index.ts similarity index 52% rename from web/src/store/orderItems/index.ts rename to frontend/src/store/orderItems/index.ts index 0cfa31c..f46fefb 100644 --- a/web/src/store/orderItems/index.ts +++ b/frontend/src/store/orderItems/index.ts @@ -1,17 +1,17 @@ -import { OrderItemsService, controller_OrderItem, controller_ItemType } from "@/services/openapi"; +import { OrderItemsService, service_OrderItem, types_ItemType } from "@/services/openapi"; interface AppStateModel { - orderItems: Map; + orderItems: Map; } interface mutationOrderItems { - orderItems: controller_OrderItem[]; - orderType: controller_ItemType; + orderItems: service_OrderItem[]; + orderType: types_ItemType; } const orderItemStore = { state: () => ({ - orderItems: new Map(), + orderItems: new Map(), }), getters: { getOrderItems(state: AppStateModel) { @@ -22,52 +22,52 @@ const orderItemStore = { setOrderItems(state: AppStateModel, payload: mutationOrderItems) { state.orderItems.set(payload.orderType, payload.orderItems); }, - pushOrderItem(state: AppStateModel, orderItem: controller_OrderItem) { + pushOrderItem(state: AppStateModel, orderItem: service_OrderItem) { const tempOrderItems = state.orderItems.get(orderItem.item_type); tempOrderItems && tempOrderItems.push(orderItem); }, - filterOrderItem(state: AppStateModel, orderItem: controller_OrderItem) { + filterOrderItem(state: AppStateModel, orderItem: service_OrderItem) { const tempOrderItems = state.orderItems.get(orderItem.item_type); tempOrderItems && state.orderItems.set( orderItem.item_type, - tempOrderItems.filter((origItem: controller_OrderItem) => origItem.id !== orderItem.id) + tempOrderItems.filter((origItem: service_OrderItem) => origItem.id !== orderItem.id) ); }, - putOrderItem(state: AppStateModel, orderItem: controller_OrderItem) { + putOrderItem(state: AppStateModel, orderItem: service_OrderItem) { const tempOrderItems = state.orderItems.get(orderItem.item_type); tempOrderItems && state.orderItems.set( orderItem.item_type, - tempOrderItems.map((origItem: controller_OrderItem) => (origItem.id === orderItem.id ? orderItem : origItem)) + tempOrderItems.map((origItem: service_OrderItem) => (origItem.id === orderItem.id ? orderItem : origItem)) ); }, }, actions: { // eslint-disable-next-line async getAllOrderItems(context: any) { - await context.dispatch("getOrderItems", controller_ItemType.Food); - await context.dispatch("getOrderItems", controller_ItemType.ColdDrink); - await context.dispatch("getOrderItems", controller_ItemType.HotDrink); + await context.dispatch("getOrderItems", types_ItemType.Food); + await context.dispatch("getOrderItems", types_ItemType.ColdDrink); + await context.dispatch("getOrderItems", types_ItemType.HotDrink); }, // eslint-disable-next-line - async getOrderItems(context: any, orderType: controller_ItemType) { + async getOrderItems(context: any, orderType: types_ItemType) { const orderTypeArray = context.getters.getOrderItems; if (!orderTypeArray.get(orderType)) { - const orderItems: controller_OrderItem[] | null = await OrderItemsService.getOrdersItems(orderType); + const orderItems: service_OrderItem[] | null = await OrderItemsService.getOrdersItems(orderType); context.commit("setOrderItems", { orderItems, orderType }); } }, // eslint-disable-next-line - addOrderItem(context: any, orderItem: controller_OrderItem) { + addOrderItem(context: any, orderItem: service_OrderItem) { context.commit("pushOrderItem", orderItem); }, // eslint-disable-next-line - deleteOrderItem(context: any, orderItem: controller_OrderItem) { + deleteOrderItem(context: any, orderItem: service_OrderItem) { context.commit("filterOrderItem", orderItem); }, // eslint-disable-next-line - updateOrderItem(context: any, orderItem: controller_OrderItem) { + updateOrderItem(context: any, orderItem: service_OrderItem) { context.commit("putOrderItem", orderItem); }, }, diff --git a/web/src/store/tables/index.ts b/frontend/src/store/tables/index.ts similarity index 75% rename from web/src/store/tables/index.ts rename to frontend/src/store/tables/index.ts index b3fcd00..031d243 100644 --- a/web/src/store/tables/index.ts +++ b/frontend/src/store/tables/index.ts @@ -1,7 +1,7 @@ -import { controller_Table, TablesService } from "@/services/openapi"; +import { service_Table, TablesService } from "@/services/openapi"; interface AppStateModel { - tables: controller_Table[] | null; + tables: service_Table[] | null; } const tableStore = { @@ -17,13 +17,13 @@ const tableStore = { }, }, mutations: { - setTables(state: AppStateModel, tables: controller_Table[]) { + setTables(state: AppStateModel, tables: service_Table[]) { state.tables = tables; }, popTables(state: AppStateModel) { state.tables && state.tables.pop(); }, - pushTable(state: AppStateModel, table: controller_Table) { + pushTable(state: AppStateModel, table: service_Table) { state.tables && state.tables.push(table); }, }, @@ -38,7 +38,7 @@ const tableStore = { context.commit("popTables"); }, // eslint-disable-next-line - addTable(context: any, table: controller_Table) { + addTable(context: any, table: service_Table) { context.commit("pushTable", table); }, }, diff --git a/web/src/utils.ts b/frontend/src/utils.ts similarity index 63% rename from web/src/utils.ts rename to frontend/src/utils.ts index a500c10..7c38943 100644 --- a/web/src/utils.ts +++ b/frontend/src/utils.ts @@ -1,44 +1,44 @@ -import { controller_Bill, controller_Order, controller_ItemType } from "@/services/openapi"; +import { service_Bill, service_Order, types_ItemType } from "@/services/openapi"; export function convertToEur(value: number | undefined) { const temp: number = value ? value : 0; return temp.toLocaleString("de-DE", { style: "currency", currency: "EUR" }); } -export function detailedItemTypeString(type: controller_ItemType | undefined) { +export function detailedItemTypeString(type: types_ItemType | undefined) { switch (type) { - case controller_ItemType.Food: + case types_ItemType.Food: return "Speisen"; - case controller_ItemType.ColdDrink: + case types_ItemType.ColdDrink: return "Kaltgetränke"; default: return "Heiß/Eiskaffee"; } } -export function generalItemTypeString(type: controller_ItemType[]) { - if (type.includes(controller_ItemType.Food)) { +export function generalItemTypeString(type: types_ItemType[]) { + if (type.includes(types_ItemType.Food)) { return "Speisen"; } else { return "Getränke"; } } -export function detailedItemTypeIcon(type: controller_ItemType | undefined) { +export function detailedItemTypeIcon(type: types_ItemType | undefined) { switch (type) { - case controller_ItemType.Food: + case types_ItemType.Food: return "fa-cheese"; - case controller_ItemType.ColdDrink: + case types_ItemType.ColdDrink: return "fa-champagne-glasses"; - case controller_ItemType.HotDrink: + case types_ItemType.HotDrink: return "fa-mug-hot"; default: return ""; } } -export function generalItemTypeIcon(type: controller_ItemType[]) { - if (type.includes(controller_ItemType.Food)) { +export function generalItemTypeIcon(type: types_ItemType[]) { + if (type.includes(types_ItemType.Food)) { return "fa-cheese"; } else { return "fa-champagne-glasses"; @@ -47,7 +47,7 @@ export function generalItemTypeIcon(type: controller_ItemType[]) { export interface WebSocketMsg { type: NotifierType; - payload: controller_Order[]; + payload: service_Order[]; } export enum NotifierType { @@ -75,4 +75,4 @@ export function lessThan15SecondsAgo(updated_at: number | undefined) { return moment().diff(updated, "seconds") < 15; } -export const emptyBill: controller_Bill = { table_id: 0, total: 0 }; +export const emptyBill: service_Bill = { table_id: 0, total: 0 }; diff --git a/web/src/views/Bills.vue b/frontend/src/views/Bills.vue similarity index 95% rename from web/src/views/Bills.vue rename to frontend/src/views/Bills.vue index 26dad42..f0152e2 100644 --- a/web/src/views/Bills.vue +++ b/frontend/src/views/Bills.vue @@ -59,7 +59,7 @@ import { defineComponent, ref, watch } from "vue"; import BaseCard from "@/components/UI/BaseCard.vue"; import Calendar from "primevue/calendar"; -import { BillsService, controller_Bill } from "@/services/openapi"; +import { BillsService, service_Bill } from "@/services/openapi"; import Sidebar from "primevue/sidebar"; import BillModal from "@/components/Bills/BillModal.vue"; import { convertToEur, emptyBill, errorToast } from "@/utils"; @@ -80,11 +80,11 @@ export default defineComponent({ const confirm = useConfirm(); const toast = useToast(); const today = ref(new Date()); - const bills = ref([]); + const bills = ref([]); const isLoading = ref(false); const isDisabled = ref(false); const billModal = ref(false); - const bill = ref({ ...emptyBill }); + const bill = ref({ ...emptyBill }); const filters = ref({ global: { value: null, matchMode: FilterMatchMode.CONTAINS }, }); @@ -104,7 +104,7 @@ export default defineComponent({ function openBill(billId: number) { if (isDisabled.value) return; - const temp: controller_Bill | undefined = bills.value.find((bill) => bill.id === billId); + const temp: service_Bill | undefined = bills.value.find((bill) => bill.id === billId); temp && (bill.value = temp); billModal.value = true; } diff --git a/web/src/views/Checkout.vue b/frontend/src/views/Checkout.vue similarity index 96% rename from web/src/views/Checkout.vue rename to frontend/src/views/Checkout.vue index 81d2cb0..6b09703 100644 --- a/web/src/views/Checkout.vue +++ b/frontend/src/views/Checkout.vue @@ -55,7 +55,7 @@