Restructure project
This commit is contained in:
parent
e44e7caa11
commit
16b2f17301
46 changed files with 1744 additions and 1265 deletions
4
.air.toml
Normal file
4
.air.toml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
[build]
|
||||||
|
bin = "tmp/cafe-plaetschwiesle"
|
||||||
|
cmd = "go build -o tmp/cafe-plaetschwiesle cmd/cafe-plaetschwiesle/cafe-plaetschwiesle.go"
|
||||||
|
exclude_dir = [".gitlab", "docker", "scripts", "frontend", "storage", "tmp", "docs"]
|
|
@ -1,52 +0,0 @@
|
||||||
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")
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
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")
|
|
||||||
}
|
|
|
@ -1,112 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
|
@ -1,258 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
16
api/types.go
16
api/types.go
|
@ -1,16 +0,0 @@
|
||||||
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"`
|
|
||||||
}
|
|
30
cmd/cafe-plaetschwiesle/cafe-plaetschwiesle.go
Normal file
30
cmd/cafe-plaetschwiesle/cafe-plaetschwiesle.go
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
10
config.toml
10
config.toml
|
@ -1,10 +0,0 @@
|
||||||
ALLOWED_HOSTS = "https://cafe.test"
|
|
||||||
LOG_LEVEL = "info"
|
|
||||||
PORT = 5000
|
|
||||||
SWAGGER = true
|
|
||||||
|
|
||||||
[MYSQL]
|
|
||||||
DATABASE = ""
|
|
||||||
PASSWORD = ""
|
|
||||||
URL = ""
|
|
||||||
USER = ""
|
|
|
@ -1,72 +0,0 @@
|
||||||
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()
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
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")
|
|
||||||
}
|
|
|
@ -111,7 +111,7 @@ services:
|
||||||
args:
|
args:
|
||||||
- GOLANG_VERSION=${GOLANG_VERSION}
|
- GOLANG_VERSION=${GOLANG_VERSION}
|
||||||
container_name: cafe-backend
|
container_name: cafe-backend
|
||||||
entrypoint: air --build.exclude_dir "node_modules,frontend,static,docs,storage,tmp,dist"
|
command: air -c .air.toml
|
||||||
environment:
|
environment:
|
||||||
- PUID=1000
|
- PUID=1000
|
||||||
- PGID=1000
|
- PGID=1000
|
||||||
|
@ -122,7 +122,7 @@ services:
|
||||||
- 'traefik.http.routers.backend.tls=true'
|
- 'traefik.http.routers.backend.tls=true'
|
||||||
- 'traefik.http.routers.backend.middlewares=authelia@docker'
|
- 'traefik.http.routers.backend.middlewares=authelia@docker'
|
||||||
expose:
|
expose:
|
||||||
- 5000
|
- 8080
|
||||||
networks:
|
networks:
|
||||||
- net
|
- net
|
||||||
volumes:
|
volumes:
|
||||||
|
|
|
@ -8,8 +8,7 @@ COPY ./go.sum .
|
||||||
RUN go mod download
|
RUN go mod download
|
||||||
|
|
||||||
RUN go install github.com/cosmtrek/air@latest
|
RUN go install github.com/cosmtrek/air@latest
|
||||||
|
COPY ./.air.toml .
|
||||||
|
|
||||||
ENV VERSION=v0.0.0-DEV
|
ENV VERSION=v0.0.0-DEV
|
||||||
ENV BUILD_TIME=2023-06-01T08:07:43.454Z
|
ENV BUILD_TIME=2023-06-01T08:07:43.454Z
|
||||||
|
|
||||||
CMD ["air"]
|
|
||||||
|
|
|
@ -18,7 +18,6 @@ import "primeicons/primeicons.css";
|
||||||
import "primeflex/primeflex.css";
|
import "primeflex/primeflex.css";
|
||||||
|
|
||||||
export const API_ENDPOINT_URL = window.origin + "/api";
|
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;
|
OpenAPI.BASE = API_ENDPOINT_URL;
|
||||||
|
|
||||||
async function getHealth() {
|
async function getHealth() {
|
||||||
|
|
|
@ -27,7 +27,7 @@ import { computed, defineComponent, onUnmounted, provide, ref } from "vue";
|
||||||
import BaseCard from "@/components/UI/BaseCard.vue";
|
import BaseCard from "@/components/UI/BaseCard.vue";
|
||||||
import { OrdersService, service_Order, types_ItemType, user_User } from "@/services/openapi";
|
import { OrdersService, service_Order, types_ItemType, user_User } from "@/services/openapi";
|
||||||
import { detailedItemTypeIcon, detailedItemTypeString, NotifierType, WebSocketMsg } from "@/utils";
|
import { detailedItemTypeIcon, detailedItemTypeString, NotifierType, WebSocketMsg } from "@/utils";
|
||||||
import { WEBSOCKET_ENDPOINT_URL } from "@/main";
|
import { API_ENDPOINT_URL } from "@/main";
|
||||||
import EmptyView from "@/views/Empty.vue";
|
import EmptyView from "@/views/Empty.vue";
|
||||||
import WaveSpinner from "@/components/UI/WaveSpinner.vue";
|
import WaveSpinner from "@/components/UI/WaveSpinner.vue";
|
||||||
import { disabled, loading } from "@/keys";
|
import { disabled, loading } from "@/keys";
|
||||||
|
@ -68,7 +68,7 @@ export default defineComponent({
|
||||||
return new Map([...temp.entries()].sort());
|
return new Map([...temp.entries()].sort());
|
||||||
});
|
});
|
||||||
const coldOrders = computed(() => orders.value.filter((order) => order.order_item.item_type === types_ItemType.ColdDrink));
|
const coldOrders = computed(() => orders.value.filter((order) => order.order_item.item_type === types_ItemType.ColdDrink));
|
||||||
const ws = ref<WebSocket | null>(null);
|
const sse = ref<EventSource | null>(null);
|
||||||
|
|
||||||
getData();
|
getData();
|
||||||
function getData() {
|
function getData() {
|
||||||
|
@ -77,37 +77,21 @@ export default defineComponent({
|
||||||
.then((res) => (orders.value = res))
|
.then((res) => (orders.value = res))
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
startWebsocket();
|
setupSSE();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
onUnmounted(() => stopWebsocket());
|
onUnmounted(() => sse.value && sse.value.close());
|
||||||
|
addEventListener("beforeunload", () => {
|
||||||
|
sse.value && sse.value.close();
|
||||||
|
});
|
||||||
|
|
||||||
function filterOrder(id: number) {
|
function filterOrder(id: number) {
|
||||||
orders.value = orders.value.filter((old) => old.id !== id);
|
orders.value = orders.value.filter((old) => old.id !== id);
|
||||||
}
|
}
|
||||||
|
|
||||||
function startWebsocket() {
|
function setupSSE() {
|
||||||
ws.value = new WebSocket(WEBSOCKET_ENDPOINT_URL);
|
sse.value = new EventSource(API_ENDPOINT_URL + "/orders/sse?stream=sse");
|
||||||
ws.value.addEventListener("message", parseWebsocket);
|
sse.value.onmessage = (evt: Event) => {
|
||||||
ws.value.addEventListener("error", handleWebsocketError);
|
|
||||||
}
|
|
||||||
|
|
||||||
function stopWebsocket() {
|
|
||||||
if (ws.value) {
|
|
||||||
ws.value.removeEventListener("message", parseWebsocket);
|
|
||||||
ws.value.removeEventListener("error", handleWebsocketError);
|
|
||||||
ws.value.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleWebsocketError() {
|
|
||||||
stopWebsocket();
|
|
||||||
setTimeout(() => {
|
|
||||||
startWebsocket();
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseWebsocket(evt: Event) {
|
|
||||||
isDisabled.value = true;
|
isDisabled.value = true;
|
||||||
const messageEvent = evt as MessageEvent;
|
const messageEvent = evt as MessageEvent;
|
||||||
const webSocketMsg: WebSocketMsg = JSON.parse(messageEvent.data);
|
const webSocketMsg: WebSocketMsg = JSON.parse(messageEvent.data);
|
||||||
|
@ -122,6 +106,7 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
sortOrders();
|
sortOrders();
|
||||||
isDisabled.value = false;
|
isDisabled.value = false;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortOrders() {
|
function sortOrders() {
|
||||||
|
|
61
go.mod
61
go.mod
|
@ -1,70 +1,61 @@
|
||||||
module cafe
|
module gitlab.unjx.de/flohoss/cafe-plaetschwiesle
|
||||||
|
|
||||||
go 1.20
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gin-contrib/cors v1.4.0
|
github.com/caarlos0/env/v8 v8.0.0
|
||||||
github.com/gin-contrib/static v0.0.1
|
github.com/containrrr/shoutrrr v0.7.1
|
||||||
github.com/gin-gonic/gin v1.9.1
|
github.com/go-playground/validator/v10 v10.14.1
|
||||||
github.com/go-playground/assert/v2 v2.2.0
|
github.com/labstack/echo/v4 v4.10.2
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/r3labs/sse/v2 v2.10.0
|
||||||
github.com/mitchellh/mapstructure v1.5.0
|
github.com/stretchr/testify v1.8.4
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/swaggo/echo-swagger v1.4.0
|
||||||
github.com/spf13/viper v1.16.0
|
|
||||||
github.com/swaggo/files v1.0.1
|
|
||||||
github.com/swaggo/gin-swagger v1.6.0
|
|
||||||
github.com/swaggo/swag v1.16.1
|
github.com/swaggo/swag v1.16.1
|
||||||
github.com/unjx-de/go-folder v1.0.7
|
go.uber.org/zap v1.24.0
|
||||||
gorm.io/driver/mysql v1.5.1
|
gorm.io/driver/mysql v1.5.1
|
||||||
gorm.io/driver/sqlite v1.5.2
|
gorm.io/driver/sqlite v1.5.2
|
||||||
gorm.io/gorm v1.25.2
|
gorm.io/gorm v1.25.2
|
||||||
gorm.io/plugin/soft_delete v1.2.1
|
gorm.io/plugin/soft_delete v1.2.1
|
||||||
|
moul.io/zapgorm2 v1.3.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||||
github.com/bytedance/sonic v1.9.2 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
github.com/fatih/color v1.15.0 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
|
||||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||||
github.com/go-openapi/spec v0.20.9 // indirect
|
github.com/go-openapi/spec v0.20.9 // indirect
|
||||||
github.com/go-openapi/swag v0.22.4 // indirect
|
github.com/go-openapi/swag v0.22.4 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.14.1 // indirect
|
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
||||||
github.com/go-sql-driver/mysql v1.7.1 // indirect
|
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||||
github.com/goccy/go-json v0.10.2 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/kr/pretty v0.3.1 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
github.com/labstack/gommon v0.4.0 // indirect
|
||||||
github.com/leodido/go-urn v1.2.4 // indirect
|
github.com/leodido/go-urn v1.2.4 // indirect
|
||||||
github.com/magiconair/properties v1.8.7 // indirect
|
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // 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.19 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.17 // indirect
|
github.com/mattn/go-sqlite3 v1.14.17 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/swaggo/files/v2 v2.0.0 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
github.com/spf13/afero v1.9.5 // indirect
|
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||||
github.com/spf13/cast v1.5.1 // indirect
|
go.uber.org/atomic v1.11.0 // indirect
|
||||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
|
||||||
github.com/subosito/gotenv v1.4.2 // indirect
|
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
|
||||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
|
||||||
golang.org/x/arch v0.3.0 // indirect
|
|
||||||
golang.org/x/crypto v0.10.0 // indirect
|
golang.org/x/crypto v0.10.0 // indirect
|
||||||
golang.org/x/net v0.11.0 // indirect
|
golang.org/x/net v0.11.0 // indirect
|
||||||
golang.org/x/sys v0.9.0 // indirect
|
golang.org/x/sys v0.9.0 // indirect
|
||||||
golang.org/x/text v0.10.0 // indirect
|
golang.org/x/text v0.10.0 // indirect
|
||||||
|
golang.org/x/time v0.3.0 // indirect
|
||||||
golang.org/x/tools v0.10.0 // indirect
|
golang.org/x/tools v0.10.0 // indirect
|
||||||
google.golang.org/protobuf v1.31.0 // indirect
|
google.golang.org/protobuf v1.31.0 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
55
hub/hub.go
55
hub/hub.go
|
@ -1,55 +0,0 @@
|
||||||
package hub
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cafe/service"
|
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
NotifierChan chan service.WebSocketMsg
|
|
||||||
|
|
||||||
Hub struct {
|
|
||||||
Notifier NotifierChan
|
|
||||||
NewClients chan NotifierChan
|
|
||||||
ClosingClients chan NotifierChan
|
|
||||||
clients map[NotifierChan]struct{}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func (h *Hub) Initialize() {
|
|
||||||
h.Notifier = make(NotifierChan)
|
|
||||||
h.NewClients = make(chan NotifierChan)
|
|
||||||
h.ClosingClients = make(chan NotifierChan)
|
|
||||||
h.clients = make(map[NotifierChan]struct{})
|
|
||||||
go h.listen()
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
if msg, ok := <-service.LiveCh; ok {
|
|
||||||
h.Notifier <- msg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Hub) listen() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case s := <-h.NewClients:
|
|
||||||
h.clients[s] = struct{}{}
|
|
||||||
logrus.WithField("openConnections", len(h.clients)).Trace("Websocket connection added")
|
|
||||||
case s := <-h.ClosingClients:
|
|
||||||
delete(h.clients, s)
|
|
||||||
logrus.WithField("openConnections", len(h.clients)).Trace("Websocket connection removed")
|
|
||||||
case event := <-h.Notifier:
|
|
||||||
for client := range h.clients {
|
|
||||||
select {
|
|
||||||
case client <- event:
|
|
||||||
default:
|
|
||||||
close(client)
|
|
||||||
delete(h.clients, client)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +1,11 @@
|
||||||
package service
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cafe/config"
|
|
||||||
"cafe/types"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"gitlab.unjx.de/flohoss/cafe-plaetschwiesle/internal/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -27,18 +27,18 @@ type (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func DoesBillExist(id string) (Bill, error) {
|
func (c *Controller) DoesBillExist(id string) (Bill, error) {
|
||||||
var bill Bill
|
var bill Bill
|
||||||
result := config.Cafe.Database.ORM.Limit(1).Find(&bill, id)
|
result := c.orm.Limit(1).Find(&bill, id)
|
||||||
if result.RowsAffected == 0 {
|
if result.RowsAffected == 0 {
|
||||||
return bill, fmt.Errorf(types.CannotFind.String())
|
return bill, fmt.Errorf(types.CannotFind.String())
|
||||||
}
|
}
|
||||||
return bill, nil
|
return bill, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAllBillItems(billId uint64) ([]BillItem, error) {
|
func (c *Controller) GetAllBillItems(billId uint64) ([]BillItem, error) {
|
||||||
var billItems []BillItem
|
var billItems []BillItem
|
||||||
result := config.Cafe.Database.ORM.Where("bill_id = ?", billId).Find(&billItems)
|
result := c.orm.Where("bill_id = ?", billId).Find(&billItems)
|
||||||
if result.RowsAffected == 0 {
|
if result.RowsAffected == 0 {
|
||||||
return billItems, fmt.Errorf(types.CannotFind.String())
|
return billItems, fmt.Errorf(types.CannotFind.String())
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ func getDate(year string, month string, day string) (time.Time, error) {
|
||||||
return time.Date(yearI, time.Month(monthI), dayI, 0, 0, 0, 0, loc), nil
|
return time.Date(yearI, time.Month(monthI), dayI, 0, 0, 0, 0, loc), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAllBills(year string, month string, day string) ([]Bill, error) {
|
func (c *Controller) GetAllBills(year string, month string, day string) ([]Bill, error) {
|
||||||
var bills []Bill
|
var bills []Bill
|
||||||
today, err := getDate(year, month, day)
|
today, err := getDate(year, month, day)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -73,12 +73,12 @@ func GetAllBills(year string, month string, day string) ([]Bill, error) {
|
||||||
}
|
}
|
||||||
beginningOfDay := today.Unix()
|
beginningOfDay := today.Unix()
|
||||||
endOfDay := today.Add(23 * time.Hour).Add(59 * time.Minute).Add(59 * time.Second).Unix()
|
endOfDay := today.Add(23 * time.Hour).Add(59 * time.Minute).Add(59 * time.Second).Unix()
|
||||||
config.Cafe.Database.ORM.Where("created_at BETWEEN ? AND ?", beginningOfDay, endOfDay).Order("created_at").Find(&bills)
|
c.orm.Where("created_at BETWEEN ? AND ?", beginningOfDay, endOfDay).Order("created_at").Find(&bills)
|
||||||
return bills, nil
|
return bills, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateBill(options GetOrderOptions) (Bill, error) {
|
func (c *Controller) createBill(options GetOrderOptions) (Bill, error) {
|
||||||
orders := GetAllOrdersForTable(options)
|
orders := c.getAllOrdersForTable(options)
|
||||||
var bill Bill
|
var bill Bill
|
||||||
var total float32 = 0
|
var total float32 = 0
|
||||||
for _, order := range orders {
|
for _, order := range orders {
|
||||||
|
@ -86,7 +86,7 @@ func CreateBill(options GetOrderOptions) (Bill, error) {
|
||||||
}
|
}
|
||||||
bill.TableID = options.TableId
|
bill.TableID = options.TableId
|
||||||
bill.Total = total
|
bill.Total = total
|
||||||
err := config.Cafe.Database.ORM.Create(&bill).Error
|
err := c.orm.Create(&bill).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return bill, fmt.Errorf(types.CannotCreate.String())
|
return bill, fmt.Errorf(types.CannotCreate.String())
|
||||||
}
|
}
|
||||||
|
@ -99,26 +99,26 @@ func CreateBill(options GetOrderOptions) (Bill, error) {
|
||||||
Amount: order.OrderCount,
|
Amount: order.OrderCount,
|
||||||
ItemType: order.OrderItem.ItemType,
|
ItemType: order.OrderItem.ItemType,
|
||||||
}
|
}
|
||||||
config.Cafe.Database.ORM.Create(&billItem)
|
c.orm.Create(&billItem)
|
||||||
}
|
}
|
||||||
ordersToDelete := GetAllOrdersForTable(GetOrderOptions{TableId: options.TableId, Grouped: false, Filter: options.Filter})
|
ordersToDelete := c.getAllOrdersForTable(GetOrderOptions{TableId: options.TableId, Grouped: false, Filter: options.Filter})
|
||||||
err = config.Cafe.Database.ORM.Delete(&ordersToDelete).Error
|
err = c.orm.Delete(&ordersToDelete).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return bill, fmt.Errorf(types.CannotDelete.String())
|
return bill, fmt.Errorf(types.CannotDelete.String())
|
||||||
}
|
}
|
||||||
LiveCh <- WebSocketMsg{
|
c.publishMessage(StatusMessage{
|
||||||
Type: types.DeleteAll,
|
Type: types.DeleteAll,
|
||||||
Payload: ordersToDelete,
|
Payload: ordersToDelete,
|
||||||
}
|
})
|
||||||
return bill, nil
|
return bill, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteBill(bill *Bill) error {
|
func (c *Controller) deleteBill(bill *Bill) error {
|
||||||
err := config.Cafe.Database.ORM.Delete(bill).Error
|
err := c.orm.Delete(bill).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf(types.CannotDelete.String())
|
return fmt.Errorf(types.CannotDelete.String())
|
||||||
}
|
}
|
||||||
billItemsToDelete, _ := GetAllBillItems(bill.ID)
|
billItemsToDelete, _ := c.GetAllBillItems(bill.ID)
|
||||||
config.Cafe.Database.ORM.Delete(&billItemsToDelete)
|
c.orm.Delete(&billItemsToDelete)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
34
internal/controller/controller.go
Normal file
34
internal/controller/controller.go
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/r3labs/sse/v2"
|
||||||
|
"gitlab.unjx.de/flohoss/cafe-plaetschwiesle/internal/database"
|
||||||
|
"gitlab.unjx.de/flohoss/cafe-plaetschwiesle/internal/env"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Controller struct {
|
||||||
|
orm *gorm.DB
|
||||||
|
env *env.Config
|
||||||
|
SSE *sse.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewController(env *env.Config) *Controller {
|
||||||
|
db := database.NewDatabaseConnection(&database.Database{
|
||||||
|
Host: env.DB_Host,
|
||||||
|
User: env.DB_User,
|
||||||
|
Password: env.DB_Password,
|
||||||
|
Database: env.DB_Database,
|
||||||
|
})
|
||||||
|
|
||||||
|
db.AutoMigrate(&Table{})
|
||||||
|
db.AutoMigrate(&Order{})
|
||||||
|
db.AutoMigrate(&OrderItem{})
|
||||||
|
db.AutoMigrate(&Bill{})
|
||||||
|
db.AutoMigrate(&BillItem{})
|
||||||
|
db.AutoMigrate(&User{})
|
||||||
|
|
||||||
|
ctrl := Controller{orm: db, env: env, SSE: sse.New()}
|
||||||
|
ctrl.setupEventChannel()
|
||||||
|
return &ctrl
|
||||||
|
}
|
25
internal/controller/events.go
Normal file
25
internal/controller/events.go
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/r3labs/sse/v2"
|
||||||
|
"gitlab.unjx.de/flohoss/cafe-plaetschwiesle/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ServerSideEvent = "sse"
|
||||||
|
|
||||||
|
type StatusMessage struct {
|
||||||
|
Type types.NotifierType `json:"type"`
|
||||||
|
Payload []Order `json:"payload"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) setupEventChannel() {
|
||||||
|
c.SSE.AutoReplay = false
|
||||||
|
c.SSE.CreateStream(ServerSideEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) publishMessage(msg StatusMessage) {
|
||||||
|
json, _ := json.Marshal(msg)
|
||||||
|
c.SSE.Publish(ServerSideEvent, &sse.Event{Data: json})
|
||||||
|
}
|
|
@ -1,11 +1,10 @@
|
||||||
package service
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cafe/config"
|
|
||||||
"cafe/types"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"gitlab.unjx.de/flohoss/cafe-plaetschwiesle/internal/types"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -52,116 +51,116 @@ func (o *Order) AfterDelete(tx *gorm.DB) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func DoesOrderItemExist(id string) (OrderItem, error) {
|
func (c *Controller) doesOrderItemExist(id string) (OrderItem, error) {
|
||||||
var orderItem OrderItem
|
var orderItem OrderItem
|
||||||
result := config.Cafe.Database.ORM.Limit(1).Find(&orderItem, id)
|
result := c.orm.Limit(1).Find(&orderItem, id)
|
||||||
if result.RowsAffected == 0 {
|
if result.RowsAffected == 0 {
|
||||||
return orderItem, fmt.Errorf(types.CannotFind.String())
|
return orderItem, fmt.Errorf(types.CannotFind.String())
|
||||||
}
|
}
|
||||||
return orderItem, nil
|
return orderItem, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func DoesOrderExist(id string) (Order, error) {
|
func (c *Controller) doesOrderExist(id string) (Order, error) {
|
||||||
var order Order
|
var order Order
|
||||||
result := config.Cafe.Database.ORM.Limit(1).Find(&order, id)
|
result := c.orm.Limit(1).Find(&order, id)
|
||||||
if result.RowsAffected == 0 {
|
if result.RowsAffected == 0 {
|
||||||
return order, fmt.Errorf(types.CannotFind.String())
|
return order, fmt.Errorf(types.CannotFind.String())
|
||||||
}
|
}
|
||||||
return order, nil
|
return order, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAllActiveOrders() []Order {
|
func (c *Controller) getAllActiveOrders() []Order {
|
||||||
var orders []Order
|
var orders []Order
|
||||||
config.Cafe.Database.ORM.Model(&Order{}).Joins("OrderItem").Where("is_served = ?", 0).Order("updated_at").Find(&orders)
|
c.orm.Model(&Order{}).Joins("OrderItem").Where("is_served = ?", 0).Order("updated_at").Find(&orders)
|
||||||
return orders
|
return orders
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAllOrdersForTable(options GetOrderOptions) []Order {
|
func (c *Controller) getAllOrdersForTable(options GetOrderOptions) []Order {
|
||||||
var orders []Order
|
var orders []Order
|
||||||
if options.Grouped {
|
if options.Grouped {
|
||||||
if len(options.Filter) == 0 {
|
if len(options.Filter) == 0 {
|
||||||
config.Cafe.Database.ORM.Model(&Order{}).Joins("OrderItem").Select("table_id, order_item_id, sum(price) as total, count(order_item_id) as order_count").Group("order_item_id").Where("table_id = ?", options.TableId).Order("item_type, description").Find(&orders)
|
c.orm.Model(&Order{}).Joins("OrderItem").Select("table_id, order_item_id, sum(price) as total, count(order_item_id) as order_count").Group("order_item_id").Where("table_id = ?", options.TableId).Order("item_type, description").Find(&orders)
|
||||||
} else {
|
} else {
|
||||||
config.Cafe.Database.ORM.Model(&Order{}).Find(&orders, options.Filter).Joins("OrderItem").Select("table_id, order_item_id, sum(price) as total, count(order_item_id) as order_count").Group("order_item_id").Where("table_id = ?", options.TableId).Order("item_type, description").Find(&orders)
|
c.orm.Model(&Order{}).Find(&orders, options.Filter).Joins("OrderItem").Select("table_id, order_item_id, sum(price) as total, count(order_item_id) as order_count").Group("order_item_id").Where("table_id = ?", options.TableId).Order("item_type, description").Find(&orders)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if len(options.Filter) == 0 {
|
if len(options.Filter) == 0 {
|
||||||
config.Cafe.Database.ORM.Model(&Order{}).Joins("OrderItem").Where("table_id = ?", options.TableId).Order("item_type, description").Find(&orders)
|
c.orm.Model(&Order{}).Joins("OrderItem").Where("table_id = ?", options.TableId).Order("item_type, description").Find(&orders)
|
||||||
} else {
|
} else {
|
||||||
config.Cafe.Database.ORM.Model(&Order{}).Find(&orders, options.Filter).Where("table_id = ?", options.TableId).Find(&orders)
|
c.orm.Model(&Order{}).Find(&orders, options.Filter).Where("table_id = ?", options.TableId).Find(&orders)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return orders
|
return orders
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateOrder(order *Order) error {
|
func (c *Controller) createOrder(order *Order) error {
|
||||||
err := config.Cafe.Database.ORM.Create(order).Error
|
err := c.orm.Create(order).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf(types.CannotCreate.String())
|
return fmt.Errorf(types.CannotCreate.String())
|
||||||
}
|
}
|
||||||
config.Cafe.Database.ORM.Model(&Order{}).Joins("OrderItem").First(order)
|
c.orm.Model(&Order{}).Joins("OrderItem").First(order)
|
||||||
LiveCh <- WebSocketMsg{
|
c.publishMessage(StatusMessage{
|
||||||
Type: types.Create,
|
Type: types.Create,
|
||||||
Payload: []Order{*order},
|
Payload: []Order{*order},
|
||||||
}
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateOrder(old *Order, new *Order) error {
|
func (c *Controller) updateOrder(old *Order, new *Order) error {
|
||||||
err := config.Cafe.Database.ORM.First(old).Updates(new).Error
|
err := c.orm.First(old).Updates(new).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf(types.CannotUpdate.String())
|
return fmt.Errorf(types.CannotUpdate.String())
|
||||||
}
|
}
|
||||||
if new.IsServed {
|
if new.IsServed {
|
||||||
LiveCh <- WebSocketMsg{
|
c.publishMessage(StatusMessage{
|
||||||
Type: types.Delete,
|
Type: types.Delete,
|
||||||
Payload: []Order{*new},
|
Payload: []Order{*new},
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteOrder(tableId string, orderItemId string) error {
|
func (c *Controller) deleteOrder(tableId string, orderItemId string) error {
|
||||||
var order Order
|
var order Order
|
||||||
err := config.Cafe.Database.ORM.Where("table_id = ? AND order_item_id = ?", tableId, orderItemId).Last(&order).Error
|
err := c.orm.Where("table_id = ? AND order_item_id = ?", tableId, orderItemId).Last(&order).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf(types.CannotFind.String())
|
return fmt.Errorf(types.CannotFind.String())
|
||||||
}
|
}
|
||||||
err = config.Cafe.Database.ORM.Delete(&order).Error
|
err = c.orm.Delete(&order).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf(types.CannotDelete.String())
|
return fmt.Errorf(types.CannotDelete.String())
|
||||||
}
|
}
|
||||||
LiveCh <- WebSocketMsg{
|
c.publishMessage(StatusMessage{
|
||||||
Type: types.Delete,
|
Type: types.Delete,
|
||||||
Payload: []Order{order},
|
Payload: []Order{order},
|
||||||
}
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetOrderItemsForType(itemType string) []OrderItem {
|
func (c *Controller) getOrderItemsForType(itemType string) []OrderItem {
|
||||||
var orderItems []OrderItem
|
var orderItems []OrderItem
|
||||||
config.Cafe.Database.ORM.Order("description").Where("item_type = ?", types.ParseItemType(itemType)).Find(&orderItems)
|
c.orm.Order("description").Where("item_type = ?", types.ParseItemType(itemType)).Find(&orderItems)
|
||||||
return orderItems
|
return orderItems
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateOrderItem(oderItem *OrderItem) error {
|
func (c *Controller) createOrderItem(oderItem *OrderItem) error {
|
||||||
err := config.Cafe.Database.ORM.Create(oderItem).Error
|
err := c.orm.Create(oderItem).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf(types.CannotCreate.String())
|
return fmt.Errorf(types.CannotCreate.String())
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateOrderItem(old *OrderItem, new *OrderItem) error {
|
func (c *Controller) updateOrderItem(old *OrderItem, new *OrderItem) error {
|
||||||
err := config.Cafe.Database.ORM.First(old).Updates(new).Error
|
err := c.orm.First(old).Updates(new).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf(types.CannotUpdate.String())
|
return fmt.Errorf(types.CannotUpdate.String())
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteOrderItem(oderItem *OrderItem) error {
|
func (c *Controller) deleteOrderItem(oderItem *OrderItem) error {
|
||||||
err := config.Cafe.Database.ORM.Delete(oderItem).Error
|
err := c.orm.Delete(oderItem).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf(types.CannotDelete.String())
|
return fmt.Errorf(types.CannotDelete.String())
|
||||||
}
|
}
|
105
internal/controller/routesBill.go
Normal file
105
internal/controller/routesBill.go
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"gitlab.unjx.de/flohoss/cafe-plaetschwiesle/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// @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} Bill
|
||||||
|
// @Router /bills [get]
|
||||||
|
func (c *Controller) GetBills(ctx echo.Context) error {
|
||||||
|
year := ctx.QueryParam("year")
|
||||||
|
month := ctx.QueryParam("month")
|
||||||
|
day := ctx.QueryParam("day")
|
||||||
|
if year == "" || month == "" || day == "" {
|
||||||
|
return echo.NewHTTPError(http.StatusInternalServerError, types.MissingInformation.String())
|
||||||
|
}
|
||||||
|
bills, err := c.GetAllBills(year, month, day)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
return ctx.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} BillItem
|
||||||
|
// @Router /bills/items [get]
|
||||||
|
func (c *Controller) GetBillItems(ctx echo.Context) error {
|
||||||
|
bill, err := c.DoesBillExist(ctx.QueryParam("bill"))
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusNotFound, err.Error())
|
||||||
|
}
|
||||||
|
billItems, err := c.GetAllBillItems(bill.ID)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
return ctx.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} Bill
|
||||||
|
// @Failure 404 "Not Found"
|
||||||
|
// @Failure 500 "Internal Server Error"
|
||||||
|
// @Router /bills [post]
|
||||||
|
func (c *Controller) CreateBill(ctx echo.Context) error {
|
||||||
|
table, tableErr := strconv.ParseUint(ctx.QueryParam("table"), 10, 64)
|
||||||
|
if tableErr != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, types.MissingInformation.String())
|
||||||
|
}
|
||||||
|
stringFiler := ctx.QueryParam("filter")
|
||||||
|
var filter []string
|
||||||
|
if stringFiler != "" {
|
||||||
|
filter = strings.Split(stringFiler, ",")
|
||||||
|
}
|
||||||
|
bill, err := c.createBill(GetOrderOptions{TableId: table, Grouped: true, Filter: filter})
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
return ctx.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 "Internal Server Error"
|
||||||
|
// @Router /bills/{id} [delete]
|
||||||
|
func (c *Controller) DeleteBill(ctx echo.Context) error {
|
||||||
|
id := ctx.Param("id")
|
||||||
|
bill, err := c.DoesBillExist(id)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusNotFound, err.Error())
|
||||||
|
}
|
||||||
|
err = c.deleteBill(&bill)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
return ctx.NoContent(http.StatusOK)
|
||||||
|
}
|
209
internal/controller/routesOrder.go
Normal file
209
internal/controller/routesOrder.go
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"gitlab.unjx.de/flohoss/cafe-plaetschwiesle/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// @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} Order
|
||||||
|
// @Router /orders [get]
|
||||||
|
func (c *Controller) GetOrders(ctx echo.Context) error {
|
||||||
|
table, _ := strconv.ParseUint(ctx.QueryParam("table"), 10, 64)
|
||||||
|
grouping, _ := strconv.ParseBool(ctx.QueryParam("grouping"))
|
||||||
|
stringFiler := ctx.QueryParam("filter")
|
||||||
|
var filter []string
|
||||||
|
if stringFiler != "" {
|
||||||
|
filter = strings.Split(stringFiler, ",")
|
||||||
|
}
|
||||||
|
options := GetOrderOptions{TableId: table, Grouped: grouping, Filter: filter}
|
||||||
|
var orders []Order
|
||||||
|
if options.TableId == 0 {
|
||||||
|
orders = c.getAllActiveOrders()
|
||||||
|
} else {
|
||||||
|
orders = c.getAllOrdersForTable(options)
|
||||||
|
}
|
||||||
|
return ctx.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} Order
|
||||||
|
// @Failure 400
|
||||||
|
// @Failure 500 "Internal Server Error"
|
||||||
|
// @Router /orders [post]
|
||||||
|
func (c *Controller) CreateOrder(ctx echo.Context) error {
|
||||||
|
table, err1 := strconv.ParseUint(ctx.QueryParam("table"), 10, 64)
|
||||||
|
item, err2 := strconv.ParseUint(ctx.QueryParam("item"), 10, 64)
|
||||||
|
if err1 != nil || err2 != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, types.MissingInformation.String())
|
||||||
|
}
|
||||||
|
order := Order{TableID: table, OrderItemID: item, IsServed: false}
|
||||||
|
err := c.createOrder(&order)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
return ctx.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 "Bad Request"
|
||||||
|
// @Failure 500 "Internal Server Error"
|
||||||
|
// @Router /orders [delete]
|
||||||
|
func (c *Controller) DeleteOrder(ctx echo.Context) error {
|
||||||
|
item := ctx.QueryParam("item")
|
||||||
|
table := ctx.QueryParam("table")
|
||||||
|
if table == "" || item == "" {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, types.MissingInformation.String())
|
||||||
|
}
|
||||||
|
err := c.deleteOrder(table, item)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
return ctx.NoContent(http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Schemes
|
||||||
|
// @Summary update an order
|
||||||
|
// @Description updates an order with provided information
|
||||||
|
// @Tags orders
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param order body Order true "updated Order"
|
||||||
|
// @Success 200 {object} Order
|
||||||
|
// @Failure 400 "Bad Request"
|
||||||
|
// @Failure 404 "Not Found"
|
||||||
|
// @Failure 500 "Internal Server Error"
|
||||||
|
// @Router /orders [put]
|
||||||
|
func (c *Controller) UpdateOrder(ctx echo.Context) error {
|
||||||
|
var newOrder Order
|
||||||
|
err := ctx.Bind(&newOrder)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
|
||||||
|
}
|
||||||
|
oldOrder, err := c.doesOrderExist(strconv.Itoa(int(newOrder.ID)))
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusNotFound, err.Error())
|
||||||
|
}
|
||||||
|
err = c.updateOrder(&oldOrder, &newOrder)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
return ctx.JSON(http.StatusOK, newOrder)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Schemes
|
||||||
|
// @Summary get all orderItems
|
||||||
|
// @Description gets all orderItems as array
|
||||||
|
// @Tags orderItems
|
||||||
|
// @Produce json
|
||||||
|
// @Param type query int true "ItemType"
|
||||||
|
// @Success 200 {array} OrderItem
|
||||||
|
// @Router /orders/items [get]
|
||||||
|
func (c *Controller) GetOrderItems(ctx echo.Context) error {
|
||||||
|
orderType := ctx.QueryParam("type")
|
||||||
|
if orderType == "" {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, types.MissingInformation.String())
|
||||||
|
}
|
||||||
|
return ctx.JSON(http.StatusOK, c.getOrderItemsForType(orderType))
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Schemes
|
||||||
|
// @Summary create new orderItem
|
||||||
|
// @Description creates a new orderItem and returns it
|
||||||
|
// @Tags orderItems
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param order body OrderItem true "OrderItem ID"
|
||||||
|
// @Success 201 {object} OrderItem
|
||||||
|
// @Failure 400 "Bad Request"
|
||||||
|
// @Failure 500 "Internal Server Error"
|
||||||
|
// @Router /orders/items [post]
|
||||||
|
func (c *Controller) CreateOrderItem(ctx echo.Context) error {
|
||||||
|
var orderItem OrderItem
|
||||||
|
err := ctx.Bind(&orderItem)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
|
||||||
|
}
|
||||||
|
err = c.createOrderItem(&orderItem)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
return ctx.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 OrderItem true "updated OrderItem"
|
||||||
|
// @Success 200 {object} OrderItem
|
||||||
|
// @Failure 400 "Bad Request"
|
||||||
|
// @Failure 404 "Not Found"
|
||||||
|
// @Failure 500 "Internal Server Error"
|
||||||
|
// @Router /orders/items [put]
|
||||||
|
func (c *Controller) UpdateOrderItem(ctx echo.Context) error {
|
||||||
|
var newOrderItem OrderItem
|
||||||
|
err := ctx.Bind(&newOrderItem)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
|
||||||
|
}
|
||||||
|
oldOrderItem, err := c.doesOrderItemExist(strconv.Itoa(int(newOrderItem.ID)))
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusNotFound, err.Error())
|
||||||
|
}
|
||||||
|
err = c.updateOrderItem(&oldOrderItem, &newOrderItem)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
return ctx.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 "Internal Server Error"
|
||||||
|
// @Router /orders/items/{id} [delete]
|
||||||
|
func (c *Controller) DeleteOrderItem(ctx echo.Context) error {
|
||||||
|
id := ctx.Param("id")
|
||||||
|
orderItem, err := c.doesOrderItemExist(id)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusNotFound, err.Error())
|
||||||
|
}
|
||||||
|
err = c.deleteOrderItem(&orderItem)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
return ctx.NoContent(http.StatusOK)
|
||||||
|
}
|
51
internal/controller/routesTable.go
Normal file
51
internal/controller/routesTable.go
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
// @Schemes
|
||||||
|
// @Summary get all active tables
|
||||||
|
// @Description gets all active tables as array
|
||||||
|
// @Tags tables
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {array} Table
|
||||||
|
// @Router /tables [get]
|
||||||
|
func (c *Controller) GetTables(ctx echo.Context) error {
|
||||||
|
return ctx.JSON(http.StatusOK, c.GetAllTables())
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Schemes
|
||||||
|
// @Summary create new table
|
||||||
|
// @Description creates a new table and returns it
|
||||||
|
// @Tags tables
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Success 201 {object} Table "Table has been created"
|
||||||
|
// @Failure 500 "Internal Server Error"
|
||||||
|
// @Router /tables [post]
|
||||||
|
func (c *Controller) CreateTable(ctx echo.Context) error {
|
||||||
|
table, err := c.CreateNewTable()
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
return ctx.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 "Internal Server Error"
|
||||||
|
// @Router /tables [delete]
|
||||||
|
func (c *Controller) DeleteTable(ctx echo.Context) error {
|
||||||
|
err := c.DeleteLatestTable()
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
return ctx.NoContent(http.StatusOK)
|
||||||
|
}
|
54
internal/controller/routesUser.go
Normal file
54
internal/controller/routesUser.go
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
// @Schemes
|
||||||
|
// @Summary get a user
|
||||||
|
// @Description gets a user
|
||||||
|
// @Tags users
|
||||||
|
// @Produce json
|
||||||
|
// @Param username path string true "Username"
|
||||||
|
// @Success 200 {object} User
|
||||||
|
// @Failure 500 "Internal Server Error"
|
||||||
|
// @Router /users/{username} [get]
|
||||||
|
func (c *Controller) GetUser(ctx echo.Context) error {
|
||||||
|
username := ctx.Param("username")
|
||||||
|
u, err := c.getUserOrCreate(username)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
|
||||||
|
}
|
||||||
|
return ctx.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 true "updated User"
|
||||||
|
// @Success 200 {object} User
|
||||||
|
// @Failure 400 "Bad Request"
|
||||||
|
// @Failure 404 "Not Found"
|
||||||
|
// @Failure 500 "Internal Server Error"
|
||||||
|
// @Router /users [put]
|
||||||
|
func (c *Controller) UpdateUser(ctx echo.Context) error {
|
||||||
|
var newUser User
|
||||||
|
err := ctx.Bind(&newUser)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
|
||||||
|
}
|
||||||
|
oldUser, err := c.doesUserExist(newUser.Username)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusNotFound, err.Error())
|
||||||
|
}
|
||||||
|
err = c.updateUser(&oldUser, &newUser)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
return ctx.JSON(http.StatusOK, newUser)
|
||||||
|
}
|
|
@ -1,10 +1,9 @@
|
||||||
package service
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cafe/config"
|
|
||||||
"cafe/types"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"gitlab.unjx.de/flohoss/cafe-plaetschwiesle/internal/types"
|
||||||
"gorm.io/plugin/soft_delete"
|
"gorm.io/plugin/soft_delete"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -16,9 +15,9 @@ type Table struct {
|
||||||
IsDeleted soft_delete.DeletedAt `gorm:"softDelete:flag" json:"is_deleted" swaggerignore:"true"`
|
IsDeleted soft_delete.DeletedAt `gorm:"softDelete:flag" json:"is_deleted" swaggerignore:"true"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAllTables() []Table {
|
func (c *Controller) GetAllTables() []Table {
|
||||||
var tables []Table
|
var tables []Table
|
||||||
config.Cafe.Database.ORM.Model(
|
c.orm.Model(
|
||||||
&Table{},
|
&Table{},
|
||||||
).Joins(
|
).Joins(
|
||||||
"left join orders on tables.id = orders.table_id",
|
"left join orders on tables.id = orders.table_id",
|
||||||
|
@ -32,15 +31,15 @@ func GetAllTables() []Table {
|
||||||
return tables
|
return tables
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateNewTable() (Table, error) {
|
func (c *Controller) CreateNewTable() (Table, error) {
|
||||||
var table Table
|
var table Table
|
||||||
var err error
|
var err error
|
||||||
result := config.Cafe.Database.ORM.Unscoped().Where("is_deleted = ?", 1).Limit(1).Find(&table)
|
result := c.orm.Unscoped().Where("is_deleted = ?", 1).Limit(1).Find(&table)
|
||||||
if result.RowsAffected == 0 {
|
if result.RowsAffected == 0 {
|
||||||
err = config.Cafe.Database.ORM.Create(&table).Error
|
err = c.orm.Create(&table).Error
|
||||||
} else {
|
} else {
|
||||||
table.IsDeleted = 0
|
table.IsDeleted = 0
|
||||||
err = config.Cafe.Database.ORM.Unscoped().Save(&table).Error
|
err = c.orm.Unscoped().Save(&table).Error
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return table, fmt.Errorf(types.CannotCreate.String())
|
return table, fmt.Errorf(types.CannotCreate.String())
|
||||||
|
@ -48,9 +47,9 @@ func CreateNewTable() (Table, error) {
|
||||||
return table, nil
|
return table, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteLatestTable() error {
|
func (c *Controller) DeleteLatestTable() error {
|
||||||
var table Table
|
var table Table
|
||||||
err := config.Cafe.Database.ORM.Model(
|
err := c.orm.Model(
|
||||||
&Table{},
|
&Table{},
|
||||||
).Joins(
|
).Joins(
|
||||||
"left join orders on tables.id = orders.table_id",
|
"left join orders on tables.id = orders.table_id",
|
||||||
|
@ -67,7 +66,7 @@ func DeleteLatestTable() error {
|
||||||
if table.OrderCount != 0 {
|
if table.OrderCount != 0 {
|
||||||
return fmt.Errorf(types.StillInUse.String())
|
return fmt.Errorf(types.StillInUse.String())
|
||||||
}
|
}
|
||||||
err = config.Cafe.Database.ORM.Delete(&table).Error
|
err = c.orm.Delete(&table).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf(types.CannotDelete.String())
|
return fmt.Errorf(types.CannotDelete.String())
|
||||||
}
|
}
|
|
@ -1,9 +1,9 @@
|
||||||
package user
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cafe/config"
|
|
||||||
"cafe/types"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"gitlab.unjx.de/flohoss/cafe-plaetschwiesle/internal/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
|
@ -12,26 +12,26 @@ type User struct {
|
||||||
ShowHotDrinks bool `json:"show_hot_drinks" validate:"required"`
|
ShowHotDrinks bool `json:"show_hot_drinks" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func DoesUserExist(username string) (User, error) {
|
func (c *Controller) doesUserExist(username string) (User, error) {
|
||||||
var user User
|
var user User
|
||||||
result := config.Cafe.Database.ORM.Limit(1).Find(&user, "username = ?", username)
|
result := c.orm.Limit(1).Find(&user, "username = ?", username)
|
||||||
if result.RowsAffected == 0 {
|
if result.RowsAffected == 0 {
|
||||||
return user, fmt.Errorf(types.CannotFind.String())
|
return user, fmt.Errorf(types.CannotFind.String())
|
||||||
}
|
}
|
||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetUserOrCreate(username string) (User, error) {
|
func (c *Controller) getUserOrCreate(username string) (User, error) {
|
||||||
var user User
|
var user User
|
||||||
err := config.Cafe.Database.ORM.Where(User{Username: username}).Attrs(User{ShowHotDrinks: true, ShowColdDrinks: true}).FirstOrCreate(&user).Error
|
err := c.orm.Where(User{Username: username}).Attrs(User{ShowHotDrinks: true, ShowColdDrinks: true}).FirstOrCreate(&user).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return user, fmt.Errorf(types.CannotCreate.String())
|
return user, fmt.Errorf(types.CannotCreate.String())
|
||||||
}
|
}
|
||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateUser(old *User, new *User) error {
|
func (c *Controller) updateUser(old *User, new *User) error {
|
||||||
err := config.Cafe.Database.ORM.First(old).Updates(map[string]interface{}{
|
err := c.orm.First(old).Updates(map[string]interface{}{
|
||||||
"Username": new.Username,
|
"Username": new.Username,
|
||||||
"ShowColdDrinks": new.ShowColdDrinks,
|
"ShowColdDrinks": new.ShowColdDrinks,
|
||||||
"ShowHotDrinks": new.ShowHotDrinks}).Error
|
"ShowHotDrinks": new.ShowHotDrinks}).Error
|
81
internal/database/database.go
Normal file
81
internal/database/database.go
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"gorm.io/driver/mysql"
|
||||||
|
"gorm.io/driver/sqlite"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"moul.io/zapgorm2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Database struct {
|
||||||
|
Host string
|
||||||
|
User string
|
||||||
|
Password string
|
||||||
|
Database string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Storage = "storage/"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
os.Mkdir(Storage, os.ModePerm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) tryDbConnection() {
|
||||||
|
i := 1
|
||||||
|
total := 20
|
||||||
|
for i <= total {
|
||||||
|
ln, err := net.DialTimeout("tcp", d.Host, 1*time.Second)
|
||||||
|
if err != nil {
|
||||||
|
if i == total {
|
||||||
|
zap.L().Fatal("Failed connecting to database", zap.Int("attempt", i))
|
||||||
|
}
|
||||||
|
zap.L().Warn("Connecting to database", zap.Int("attempt", i))
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
i++
|
||||||
|
} else {
|
||||||
|
_ = ln.Close()
|
||||||
|
zap.L().Info("Connected to database", zap.Int("attempt", i))
|
||||||
|
i = total + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) initializeMySql(conf *gorm.Config) *gorm.DB {
|
||||||
|
var err error
|
||||||
|
d.tryDbConnection()
|
||||||
|
dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
|
||||||
|
d.User,
|
||||||
|
d.Password,
|
||||||
|
d.Host,
|
||||||
|
d.Database,
|
||||||
|
)
|
||||||
|
orm, err := gorm.Open(mysql.Open(dsn), conf)
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error(err.Error())
|
||||||
|
}
|
||||||
|
return orm
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) initializeSqLite(conf *gorm.Config) *gorm.DB {
|
||||||
|
var err error
|
||||||
|
orm, err := gorm.Open(sqlite.Open(Storage+"db.sqlite?_pragma=foreign_keys(1)"), conf)
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error(err.Error())
|
||||||
|
}
|
||||||
|
return orm
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDatabaseConnection(d *Database) *gorm.DB {
|
||||||
|
logger := zapgorm2.New(zap.L())
|
||||||
|
conf := &gorm.Config{Logger: logger, PrepareStmt: true}
|
||||||
|
if d.Host == "" {
|
||||||
|
return d.initializeSqLite(conf)
|
||||||
|
}
|
||||||
|
return d.initializeMySql(conf)
|
||||||
|
}
|
70
internal/env/env.go
vendored
Normal file
70
internal/env/env.go
vendored
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
package env
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/caarlos0/env/v8"
|
||||||
|
"github.com/containrrr/shoutrrr"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
)
|
||||||
|
|
||||||
|
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"`
|
||||||
|
DB_Host string `env:"DB_HOST"`
|
||||||
|
DB_User string `env:"DB_USER"`
|
||||||
|
DB_Password string `env:"DB_PASSWORD"`
|
||||||
|
DB_Database string `env:"DB_DATABASE"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var errParse = errors.New("error parsing environment variables")
|
||||||
|
|
||||||
|
func Parse() (*Config, error) {
|
||||||
|
cfg := &Config{}
|
||||||
|
if err := env.Parse(cfg); err != nil {
|
||||||
|
return cfg, err
|
||||||
|
}
|
||||||
|
if err := validateContent(cfg); err != nil {
|
||||||
|
return cfg, err
|
||||||
|
}
|
||||||
|
setAllDefaultEnvs(cfg)
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEnvValidator() *validator.Validate {
|
||||||
|
validate := validator.New()
|
||||||
|
validate.RegisterValidation(`shoutrrr`, func(fl validator.FieldLevel) bool {
|
||||||
|
value := fl.Field().Interface().(string)
|
||||||
|
_, err := shoutrrr.CreateSender(value)
|
||||||
|
return err == nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return validate
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateContent(cfg *Config) error {
|
||||||
|
validate := newEnvValidator()
|
||||||
|
err := validate.Struct(cfg)
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := err.(*validator.InvalidValidationError); ok {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
for _, err := range err.(validator.ValidationErrors) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errParse
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setAllDefaultEnvs(cfg *Config) {
|
||||||
|
os.Setenv("TZ", cfg.TimeZone)
|
||||||
|
os.Setenv("PORT", fmt.Sprintf("%d", cfg.Port))
|
||||||
|
os.Setenv("LOG_LEVEL", cfg.LogLevel)
|
||||||
|
}
|
56
internal/env/env_test.go
vendored
Normal file
56
internal/env/env_test.go
vendored
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
package env
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPortParser(t *testing.T) {
|
||||||
|
key := "PORT"
|
||||||
|
var err error
|
||||||
|
defer func() {
|
||||||
|
os.Unsetenv(key)
|
||||||
|
}()
|
||||||
|
|
||||||
|
os.Setenv(key, "1024")
|
||||||
|
_, err = Parse()
|
||||||
|
assert.Equal(t, err, nil, "Parsing should pass")
|
||||||
|
|
||||||
|
os.Setenv(key, "-12")
|
||||||
|
_, err = Parse()
|
||||||
|
assert.Equal(t, err.Error(), "Key: 'Config.Port' Error:Field validation for 'Port' failed on the 'min' tag", "Validation should fail")
|
||||||
|
|
||||||
|
os.Setenv(key, "60000")
|
||||||
|
_, err = Parse()
|
||||||
|
assert.Equal(t, err.Error(), "Key: 'Config.Port' Error:Field validation for 'Port' failed on the 'max' tag", "Validation should fail")
|
||||||
|
|
||||||
|
os.Setenv(key, "abc")
|
||||||
|
_, err = Parse()
|
||||||
|
assert.Equal(t, err.Error(), "env: parse error on field \"Port\" of type \"int\": strconv.ParseInt: parsing \"abc\": invalid syntax", "Parsing should fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTimeZoneParser(t *testing.T) {
|
||||||
|
key := "TZ"
|
||||||
|
var err error
|
||||||
|
defer func() {
|
||||||
|
os.Unsetenv(key)
|
||||||
|
}()
|
||||||
|
|
||||||
|
os.Setenv(key, "Europe/Berlin")
|
||||||
|
_, err = Parse()
|
||||||
|
assert.Equal(t, err, nil, "Parsing should pass")
|
||||||
|
|
||||||
|
os.Setenv(key, "Etc/UTC")
|
||||||
|
_, err = Parse()
|
||||||
|
assert.Equal(t, err, nil, "Parsing should pass")
|
||||||
|
|
||||||
|
os.Setenv(key, "abc")
|
||||||
|
_, err = Parse()
|
||||||
|
assert.Equal(t, err.Error(), "Key: 'Config.TimeZone' Error:Field validation for 'TimeZone' failed on the 'timezone' tag", "Validation should fail")
|
||||||
|
|
||||||
|
os.Setenv(key, "-1")
|
||||||
|
_, err = Parse()
|
||||||
|
assert.Equal(t, err.Error(), "Key: 'Config.TimeZone' Error:Field validation for 'TimeZone' failed on the 'timezone' tag", "Validation should fail")
|
||||||
|
}
|
44
internal/logging/logger_test.go
Normal file
44
internal/logging/logger_test.go
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
package logging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInfoLogger(t *testing.T) {
|
||||||
|
level := "info"
|
||||||
|
log := CreateLogger(level)
|
||||||
|
defer log.Sync()
|
||||||
|
|
||||||
|
assert.NotEmpty(t, log, "Logger should not be nil")
|
||||||
|
assert.Equal(t, log.Level().String(), level, fmt.Sprintf("Level should be %s", level))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWarnLogger(t *testing.T) {
|
||||||
|
level := "warn"
|
||||||
|
log := CreateLogger(level)
|
||||||
|
defer log.Sync()
|
||||||
|
|
||||||
|
assert.NotEmpty(t, log, "Logger should not be nil")
|
||||||
|
assert.Equal(t, log.Level().String(), level, fmt.Sprintf("Level should be %s", level))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDebugLogger(t *testing.T) {
|
||||||
|
level := "debug"
|
||||||
|
log := CreateLogger(level)
|
||||||
|
defer log.Sync()
|
||||||
|
|
||||||
|
assert.NotEmpty(t, log, "Logger should not be nil")
|
||||||
|
assert.Equal(t, log.Level().String(), level, fmt.Sprintf("Level should be %s", level))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInvalidLogger(t *testing.T) {
|
||||||
|
level := "invalid"
|
||||||
|
log := CreateLogger(level)
|
||||||
|
defer log.Sync()
|
||||||
|
|
||||||
|
assert.NotEmpty(t, log, "Logger should not be nil")
|
||||||
|
assert.Equal(t, log.Level().String(), "info", "Level should be info")
|
||||||
|
}
|
33
internal/logging/logging.go
Normal file
33
internal/logging/logging.go
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
package logging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateLogger(logLevel string) *zap.Logger {
|
||||||
|
encoderCfg := zap.NewProductionEncoderConfig()
|
||||||
|
encoderCfg.TimeKey = "time"
|
||||||
|
encoderCfg.EncodeTime = zapcore.TimeEncoderOfLayout(time.StampMilli)
|
||||||
|
|
||||||
|
level := zap.NewAtomicLevelAt(zap.InfoLevel)
|
||||||
|
zapLevel, err := zap.ParseAtomicLevel(logLevel)
|
||||||
|
if err == nil {
|
||||||
|
level = zapLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
config := zap.Config{
|
||||||
|
Level: level,
|
||||||
|
Development: false,
|
||||||
|
DisableCaller: false,
|
||||||
|
DisableStacktrace: false,
|
||||||
|
Sampling: nil,
|
||||||
|
Encoding: "json",
|
||||||
|
EncoderConfig: encoderCfg,
|
||||||
|
OutputPaths: []string{"stdout"},
|
||||||
|
ErrorOutputPaths: []string{"stderr"},
|
||||||
|
}
|
||||||
|
return zap.Must(config.Build())
|
||||||
|
}
|
13
internal/router/middleware.go
Normal file
13
internal/router/middleware.go
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
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"))
|
||||||
|
c.Response().Header().Set("Remote-Name", c.Request().Header.Get("Remote-Name"))
|
||||||
|
return next(c)
|
||||||
|
}
|
||||||
|
}
|
94
internal/router/router.go
Normal file
94
internal/router/router.go
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/labstack/echo/v4/middleware"
|
||||||
|
echoSwagger "github.com/swaggo/echo-swagger"
|
||||||
|
"gitlab.unjx.de/flohoss/cafe-plaetschwiesle/docs"
|
||||||
|
"gitlab.unjx.de/flohoss/cafe-plaetschwiesle/internal/controller"
|
||||||
|
"gitlab.unjx.de/flohoss/cafe-plaetschwiesle/internal/env"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func InitRouter() *echo.Echo {
|
||||||
|
e := echo.New()
|
||||||
|
|
||||||
|
e.HideBanner = true
|
||||||
|
e.HidePort = true
|
||||||
|
|
||||||
|
e.Use(middleware.Recover())
|
||||||
|
e.Use(middleware.GzipWithConfig(middleware.GzipConfig{
|
||||||
|
Skipper: func(c echo.Context) bool {
|
||||||
|
return strings.Contains(c.Request().URL.Path, "swagger")
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
e.Pre(middleware.RemoveTrailingSlash())
|
||||||
|
|
||||||
|
e.Validator = &CustomValidator{Validator: newValidator()}
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetupRoutes(e *echo.Echo, ctrl *controller.Controller, env *env.Config) {
|
||||||
|
api := e.Group("/api")
|
||||||
|
{
|
||||||
|
tableGroup := api.Group("/tables")
|
||||||
|
{
|
||||||
|
tableGroup.GET("", ctrl.GetTables)
|
||||||
|
tableGroup.POST("", ctrl.CreateTable)
|
||||||
|
tableGroup.DELETE("", ctrl.DeleteTable)
|
||||||
|
}
|
||||||
|
orderGroup := api.Group("/orders")
|
||||||
|
{
|
||||||
|
orderGroup.GET("", ctrl.GetOrders)
|
||||||
|
orderGroup.POST("", ctrl.CreateOrder)
|
||||||
|
orderGroup.DELETE("", ctrl.DeleteOrder)
|
||||||
|
orderGroup.PUT("", ctrl.UpdateOrder)
|
||||||
|
orderGroup.GET("/sse", echo.WrapHandler(http.HandlerFunc(ctrl.SSE.ServeHTTP)))
|
||||||
|
orderItemGroup := orderGroup.Group("/items")
|
||||||
|
{
|
||||||
|
orderItemGroup.GET("", ctrl.GetOrderItems)
|
||||||
|
orderItemGroup.POST("", ctrl.CreateOrderItem)
|
||||||
|
orderItemGroup.PUT("", ctrl.UpdateOrderItem)
|
||||||
|
orderItemGroup.DELETE("/:id", ctrl.DeleteOrderItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
billGroup := api.Group("/bills")
|
||||||
|
{
|
||||||
|
billGroup.GET("", ctrl.GetBills)
|
||||||
|
billGroup.POST("", ctrl.CreateBill)
|
||||||
|
billGroup.DELETE("/:id", ctrl.DeleteBill)
|
||||||
|
billItemGroup := billGroup.Group("/items")
|
||||||
|
{
|
||||||
|
billItemGroup.GET("", ctrl.GetBillItems)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
userGroup := api.Group("/users")
|
||||||
|
{
|
||||||
|
userGroup.GET("/:username", ctrl.GetUser)
|
||||||
|
userGroup.PUT("", ctrl.UpdateUser)
|
||||||
|
}
|
||||||
|
health := api.Group("/health", authHeader)
|
||||||
|
{
|
||||||
|
health.GET("", func(ctx echo.Context) error {
|
||||||
|
return ctx.String(http.StatusOK, ".")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if env.SwaggerHost != "" {
|
||||||
|
docs.SwaggerInfo.Title = "Cafe"
|
||||||
|
docs.SwaggerInfo.Description = "This is the backend of a cafe"
|
||||||
|
docs.SwaggerInfo.Version = env.Version
|
||||||
|
docs.SwaggerInfo.BasePath = "/api"
|
||||||
|
parsed, _ := url.Parse(env.SwaggerHost)
|
||||||
|
docs.SwaggerInfo.Host = parsed.Host
|
||||||
|
|
||||||
|
api.GET("/swagger/*", echoSwagger.WrapHandler)
|
||||||
|
zap.L().Info("swagger running", zap.String("url", env.SwaggerHost+"/api/swagger/index.html"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
internal/router/validate.go
Normal file
17
internal/router/validate.go
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CustomValidator struct {
|
||||||
|
Validator *validator.Validate
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cv *CustomValidator) Validate(i interface{}) error {
|
||||||
|
return cv.Validator.Struct(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newValidator() *validator.Validate {
|
||||||
|
return validator.New()
|
||||||
|
}
|
39
main.go
39
main.go
|
@ -1,39 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cafe/api"
|
|
||||||
"cafe/config"
|
|
||||||
"cafe/service"
|
|
||||||
"cafe/user"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
gin.SetMode(gin.ReleaseMode)
|
|
||||||
|
|
||||||
config.Cafe.Database.Initialize(config.StorageDir)
|
|
||||||
config.Cafe.Database.MigrateHelper(service.Table{}, "table")
|
|
||||||
config.Cafe.Database.MigrateHelper(service.Order{}, "order")
|
|
||||||
config.Cafe.Database.MigrateHelper(service.OrderItem{}, "orderItem")
|
|
||||||
config.Cafe.Database.MigrateHelper(service.Bill{}, "bill")
|
|
||||||
config.Cafe.Database.MigrateHelper(service.BillItem{}, "billItem")
|
|
||||||
config.Cafe.Database.MigrateHelper(user.User{}, "user")
|
|
||||||
|
|
||||||
a := api.Api{}
|
|
||||||
service.Initialize()
|
|
||||||
a.Hub.Initialize()
|
|
||||||
|
|
||||||
a.Router = gin.New()
|
|
||||||
a.SetMiddlewares()
|
|
||||||
a.HandleStaticFiles()
|
|
||||||
a.SetupSwagger()
|
|
||||||
a.SetupRouter()
|
|
||||||
logrus.WithField("port", config.Cafe.Port).Info("Server running")
|
|
||||||
err := a.Router.Run(fmt.Sprintf(":%d", config.Cafe.Port))
|
|
||||||
if err != nil {
|
|
||||||
logrus.WithField("error", err).Fatal("Cannot start server")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -7,7 +7,10 @@ case $action in
|
||||||
go install github.com/swaggo/swag/cmd/swag@latest
|
go install github.com/swaggo/swag/cmd/swag@latest
|
||||||
;;
|
;;
|
||||||
"init")
|
"init")
|
||||||
swag init -g api/swagger.go
|
swag init --dir internal/controller -g ../router/router.go -pd
|
||||||
|
;;
|
||||||
|
"format")
|
||||||
|
swag fmt
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
exit 0
|
exit 0
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cafe/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
WebSocketMsg struct {
|
|
||||||
Type types.NotifierType `json:"type"`
|
|
||||||
Payload []Order `json:"payload"`
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
var LiveCh chan WebSocketMsg
|
|
||||||
|
|
||||||
func Initialize() {
|
|
||||||
LiveCh = make(chan WebSocketMsg)
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
<html lang="de">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport"
|
|
||||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
||||||
<title>Test</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div>Working</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,23 +0,0 @@
|
||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/go-playground/assert/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestErrorResponses_String(t *testing.T) {
|
|
||||||
assert.Equal(t, MissingInformation.String(), "fehlende Informationen")
|
|
||||||
assert.Equal(t, CannotCreate.String(), "kann nicht gespeichert werden")
|
|
||||||
assert.Equal(t, CannotUpdate.String(), "kann nicht geändert werden")
|
|
||||||
assert.Equal(t, CannotDelete.String(), "kann nicht gelöscht werden")
|
|
||||||
assert.Equal(t, CannotFind.String(), "kann nicht gefunden werden")
|
|
||||||
assert.Equal(t, StillInUse.String(), "noch in Verwendung")
|
|
||||||
assert.Equal(t, CannotParse.String(), "kann nicht verarbeitet werden")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseItemType(t *testing.T) {
|
|
||||||
assert.Equal(t, ParseItemType("0"), Food)
|
|
||||||
assert.Equal(t, ParseItemType("1"), ColdDrink)
|
|
||||||
assert.Equal(t, ParseItemType("2"), HotDrink)
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
package websocket
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cafe/config"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
|
||||||
)
|
|
||||||
|
|
||||||
func inAllowedHosts(str string) bool {
|
|
||||||
for _, a := range config.Cafe.AllowedHosts {
|
|
||||||
if a == str {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var Upgrader = websocket.Upgrader{
|
|
||||||
CheckOrigin: func(r *http.Request) bool {
|
|
||||||
origin := r.Header.Get("Origin")
|
|
||||||
return inAllowedHosts(origin)
|
|
||||||
},
|
|
||||||
ReadBufferSize: 1024,
|
|
||||||
WriteBufferSize: 1024,
|
|
||||||
}
|
|
||||||
|
|
||||||
func ReadPump(conn *websocket.Conn) {
|
|
||||||
defer conn.Close()
|
|
||||||
for {
|
|
||||||
_, _, err := conn.ReadMessage()
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue