This commit is contained in:
Florian Hoss 2023-07-04 11:51:13 +02:00
commit f90fdc0598
99 changed files with 15260 additions and 0 deletions

52
api/middlwares.go Normal file
View file

@ -0,0 +1,52 @@
package api
import (
"cafe/config"
"net/http"
"strings"
"time"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
func myLogger() gin.HandlerFunc {
return func(c *gin.Context) {
if logrus.GetLevel() != logrus.TraceLevel {
return
}
reqUri := c.Request.RequestURI
if strings.Contains(reqUri, "/storage") {
return
}
startTime := time.Now()
c.Next()
endTime := time.Now()
latencyTime := endTime.Sub(startTime)
logrus.WithFields(logrus.Fields{
"status": http.StatusText(c.Writer.Status()),
"latency": latencyTime,
"client": c.ClientIP(),
"method": c.Request.Method,
}).Trace(reqUri)
}
}
func authHeader() gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Set("Remote-Groups", c.Request.Header.Get("Remote-Groups"))
c.Writer.Header().Set("Remote-Name", c.Request.Header.Get("Remote-Name"))
}
}
func (a *Api) SetMiddlewares() {
a.Router.Use(myLogger())
a.Router.Use(gin.Recovery())
a.Router.Use(cors.Default())
_ = a.Router.SetTrustedProxies(nil)
a.Router.MaxMultipartMemory = 8 << 20 // 8 MiB
logrus.WithFields(logrus.Fields{
"allowedOrigins": config.Cafe.AllowedHosts,
}).Debug("Middlewares set")
}

62
api/router.go Normal file
View file

@ -0,0 +1,62 @@
package api
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
func (a *Api) SetupRouter() {
api := a.Router.Group("/api")
{
tableGroup := api.Group("/tables")
{
tableGroup.GET("", a.getTables)
tableGroup.POST("", a.createTable)
tableGroup.DELETE("", a.deleteTable)
}
orderGroup := api.Group("/orders")
{
orderGroup.GET("", a.getOrders)
orderGroup.POST("", a.createOrder)
orderGroup.DELETE("", a.deleteOrder)
orderGroup.PUT("", a.updateOrder)
orderGroup.GET("/ws", a.serveWs)
orderItemGroup := orderGroup.Group("/items")
{
orderItemGroup.GET("", a.getOrderItems)
orderItemGroup.POST("", a.createOrderItem)
orderItemGroup.PUT("", a.updateOrderItem)
orderItemGroup.DELETE("/:id", a.deleteOrderItem)
}
}
billGroup := api.Group("/bills")
{
billGroup.GET("", a.getBills)
billGroup.POST("", a.createBill)
billGroup.DELETE("/:id", a.deleteBill)
billItemGroup := billGroup.Group("/items")
{
billItemGroup.GET("", a.getBillItems)
}
}
userGroup := api.Group("/users")
{
userGroup.GET("/:username", a.getUser)
userGroup.PUT("", a.updateUser)
}
health := api.Group("/health")
{
health.Use(authHeader())
health.GET("", func(c *gin.Context) {
c.Status(http.StatusOK)
})
}
}
a.Router.NoRoute(func(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", nil)
})
logrus.WithField("amount", len(a.Router.Routes())).Debug("Routes initialized")
}

112
api/routesBill.go Normal file
View file

@ -0,0 +1,112 @@
package api
import (
"cafe/service"
"cafe/types"
"net/http"
"strconv"
"strings"
"github.com/gin-gonic/gin"
)
// @Schemes
// @Summary get all bills
// @Description gets all bills as array
// @Tags bills
// @Produce json
// @Param year query int true "year"
// @Param month query int true "month (1-12)"
// @Param day query int true "day (1-31)"
// @Success 200 {array} service.Bill
// @Router /bills [get]
func (a *Api) getBills(c *gin.Context) {
year, presentYear := c.GetQuery("year")
month, presentMonth := c.GetQuery("month")
day, presentDay := c.GetQuery("day")
if !presentYear || !presentMonth || !presentDay {
c.JSON(http.StatusBadRequest, errorResponse{types.MissingInformation.String()})
return
}
bills, err := service.GetAllBills(year, month, day)
if err != nil {
c.JSON(http.StatusBadRequest, errorResponse{err.Error()})
} else {
c.JSON(http.StatusOK, bills)
}
}
// @Schemes
// @Summary get all billItems
// @Description gets all billItems for bill
// @Tags bills
// @Produce json
// @Param bill query int true "Bill ID"
// @Success 200 {array} service.BillItem
// @Router /bills/items [get]
func (a *Api) getBillItems(c *gin.Context) {
bill, err := service.DoesBillExist(c.Query("bill"))
if err != nil {
c.JSON(http.StatusNotFound, errorResponse{err.Error()})
return
}
billItems, err := service.GetAllBillItems(bill.ID)
if err != nil {
c.JSON(http.StatusNotFound, errorResponse{err.Error()})
} else {
c.JSON(http.StatusOK, billItems)
}
}
// @Schemes
// @Summary create new bill
// @Description creates a new bill and returns it
// @Tags bills
// @Produce json
// @Param table query int true "Table ID"
// @Param filter query string false "filter"
// @Success 201 {object} service.Bill
// @Failure 404 "Not Found"
// @Failure 500 {object} errorResponse
// @Router /bills [post]
func (a *Api) createBill(c *gin.Context) {
table, tableErr := strconv.ParseUint(c.Query("table"), 10, 64)
if tableErr != nil {
c.JSON(http.StatusBadRequest, errorResponse{types.MissingInformation.String()})
return
}
stringFiler, filterPresent := c.GetQuery("filter")
var filter []string
if filterPresent {
filter = strings.Split(stringFiler, ",")
}
bill, err := service.CreateBill(service.GetOrderOptions{TableId: table, Grouped: true, Filter: filter})
if err != nil {
c.JSON(http.StatusInternalServerError, errorResponse{err.Error()})
}
c.JSON(http.StatusCreated, bill)
}
// @Schemes
// @Summary delete a bill
// @Description deletes a bill
// @Tags bills
// @Produce json
// @Param id path int true "Bill ID"
// @Success 200 "OK"
// @Failure 404 "Not Found"
// @Failure 500 {object} errorResponse
// @Router /bills/{id} [delete]
func (a *Api) deleteBill(c *gin.Context) {
id := c.Param("id")
bill, err := service.DoesBillExist(id)
if err != nil {
c.JSON(http.StatusNotFound, errorResponse{err.Error()})
return
}
err = service.DeleteBill(&bill)
if err != nil {
c.JSON(http.StatusInternalServerError, errorResponse{err.Error()})
}
c.Status(http.StatusOK)
}

258
api/routesOrder.go Normal file
View file

@ -0,0 +1,258 @@
package api
import (
"cafe/hub"
"cafe/service"
"cafe/types"
ws "cafe/websocket"
"net/http"
"strconv"
"strings"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"github.com/sirupsen/logrus"
)
// @Schemes
// @Summary get all orders
// @Description gets all orders as array
// @Tags orders
// @Produce json
// @Param table query int false "Table ID"
// @Param grouping query bool false "grouping"
// @Param filter query string false "filter"
// @Success 200 {array} service.Order
// @Router /orders [get]
func (a *Api) getOrders(c *gin.Context) {
table, _ := strconv.ParseUint(c.Query("table"), 10, 64)
grouping, _ := strconv.ParseBool(c.Query("grouping"))
stringFiler, filterPresent := c.GetQuery("filter")
var filter []string
if filterPresent {
filter = strings.Split(stringFiler, ",")
}
options := service.GetOrderOptions{TableId: table, Grouped: grouping, Filter: filter}
var orders []service.Order
if options.TableId == 0 {
orders = service.GetAllActiveOrders()
} else {
orders = service.GetAllOrdersForTable(options)
}
c.JSON(http.StatusOK, orders)
}
// @Schemes
// @Summary create new order
// @Description creates a new order and returns it
// @Tags orders
// @Accept json
// @Produce json
// @Param item query int true "OrderItem ID"
// @Param table query int true "Table ID"
// @Success 201 {object} service.Order
// @Failure 400 {object} errorResponse
// @Failure 500 {object} errorResponse
// @Router /orders [post]
func (a *Api) createOrder(c *gin.Context) {
table, err1 := strconv.ParseUint(c.Query("table"), 10, 64)
item, err2 := strconv.ParseUint(c.Query("item"), 10, 64)
if err1 != nil || err2 != nil {
c.JSON(http.StatusBadRequest, errorResponse{types.MissingInformation.String()})
return
}
order := service.Order{TableID: table, OrderItemID: item, IsServed: false}
err := service.CreateOrder(&order)
if err != nil {
c.JSON(http.StatusInternalServerError, errorResponse{err.Error()})
} else {
c.JSON(http.StatusCreated, order)
}
}
// @Schemes
// @Summary delete an order
// @Description deletes an order from the database
// @Tags orders
// @Produce json
// @Param item query int true "OrderItem ID"
// @Param table query int true "Table ID"
// @Success 200 "OK"
// @Failure 400 {object} errorResponse
// @Failure 500 {object} errorResponse
// @Router /orders [delete]
func (a *Api) deleteOrder(c *gin.Context) {
item := c.Query("item")
table := c.Query("table")
if table == "" || item == "" {
c.JSON(http.StatusBadRequest, errorResponse{types.MissingInformation.String()})
return
}
err := service.DeleteOrder(table, item)
if err != nil {
c.JSON(http.StatusInternalServerError, errorResponse{err.Error()})
} else {
c.Status(http.StatusOK)
}
}
// @Schemes
// @Summary update an order
// @Description updates an order with provided information
// @Tags orders
// @Accept json
// @Produce json
// @Param order body service.Order true "updated Order"
// @Success 200 {object} service.Order
// @Failure 400 {object} errorResponse
// @Failure 404 "Not Found" errorResponse
// @Failure 500 {object} errorResponse
// @Router /orders [put]
func (a *Api) updateOrder(c *gin.Context) {
var newOrder service.Order
err := c.ShouldBindJSON(&newOrder)
if err != nil {
c.JSON(http.StatusBadRequest, errorResponse{types.MissingInformation.String()})
return
}
oldOrder, err := service.DoesOrderExist(strconv.Itoa(int(newOrder.ID)))
if err != nil {
c.JSON(http.StatusNotFound, errorResponse{err.Error()})
return
}
err = service.UpdateOrder(&oldOrder, &newOrder)
if err != nil {
c.JSON(http.StatusInternalServerError, errorResponse{err.Error()})
} else {
c.JSON(http.StatusOK, newOrder)
}
}
func (a *Api) serveWs(c *gin.Context) {
conn, err := ws.Upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
logrus.WithField("error", err).Warning("Cannot upgrade websocket")
return
}
messageChan := make(hub.NotifierChan)
a.Hub.NewClients <- messageChan
defer func() {
a.Hub.ClosingClients <- messageChan
conn.Close()
}()
go ws.ReadPump(conn)
for {
msg, ok := <-messageChan
if !ok {
err := conn.WriteMessage(websocket.CloseMessage, []byte{})
if err != nil {
return
}
return
}
err := conn.WriteJSON(msg)
if err != nil {
return
}
}
}
// @Schemes
// @Summary get all orderItems
// @Description gets all orderItems as array
// @Tags orderItems
// @Produce json
// @Param type query int true "ItemType"
// @Success 200 {array} service.OrderItem
// @Router /orders/items [get]
func (a *Api) getOrderItems(c *gin.Context) {
orderType := c.Query("type")
if orderType == "" {
c.JSON(http.StatusBadRequest, errorResponse{types.MissingInformation.String()})
} else {
c.JSON(http.StatusOK, service.GetOrderItemsForType(orderType))
}
}
// @Schemes
// @Summary create new orderItem
// @Description creates a new orderItem and returns it
// @Tags orderItems
// @Accept json
// @Produce json
// @Param order body service.OrderItem true "OrderItem ID"
// @Success 201 {object} service.OrderItem
// @Failure 400 {object} errorResponse
// @Failure 500 {object} errorResponse
// @Router /orders/items [post]
func (a *Api) createOrderItem(c *gin.Context) {
var orderItem service.OrderItem
err := c.ShouldBindJSON(&orderItem)
if err != nil {
c.JSON(http.StatusBadRequest, errorResponse{types.MissingInformation.String()})
return
}
err = service.CreateOrderItem(&orderItem)
if err != nil {
c.JSON(http.StatusInternalServerError, errorResponse{err.Error()})
} else {
c.JSON(http.StatusCreated, orderItem)
}
}
// @Schemes
// @Summary update a orderItem
// @Description updates a orderItem with provided information
// @Tags orderItems
// @Accept json
// @Produce json
// @Param orderItem body service.OrderItem true "updated OrderItem"
// @Success 200 {object} service.OrderItem
// @Failure 400 {object} errorResponse
// @Failure 404 "Not Found" errorResponse
// @Failure 500 {object} errorResponse
// @Router /orders/items [put]
func (a *Api) updateOrderItem(c *gin.Context) {
var newOrderItem service.OrderItem
err := c.ShouldBindJSON(&newOrderItem)
if err != nil {
c.JSON(http.StatusBadRequest, errorResponse{types.MissingInformation.String()})
return
}
oldOrderItem, err := service.DoesOrderItemExist(strconv.Itoa(int(newOrderItem.ID)))
if err != nil {
c.JSON(http.StatusNotFound, errorResponse{err.Error()})
return
}
err = service.UpdateOrderItem(&oldOrderItem, &newOrderItem)
if err != nil {
c.JSON(http.StatusInternalServerError, errorResponse{err.Error()})
} else {
c.JSON(http.StatusOK, newOrderItem)
}
}
// @Schemes
// @Summary delete an orderItem
// @Description deletes an orderItem from the database
// @Tags orderItems
// @Produce json
// @Param id path int true "OrderItem ID"
// @Success 200 "OK"
// @Failure 404 "Not Found"
// @Failure 500 {object} errorResponse
// @Router /orders/items/{id} [delete]
func (a *Api) deleteOrderItem(c *gin.Context) {
id := c.Param("id")
orderItem, err := service.DoesOrderItemExist(id)
if err != nil {
c.JSON(http.StatusNotFound, errorResponse{err.Error()})
return
}
err = service.DeleteOrderItem(&orderItem)
if err != nil {
c.JSON(http.StatusInternalServerError, errorResponse{err.Error()})
} else {
c.Status(http.StatusOK)
}
}

54
api/routesTable.go Normal file
View file

@ -0,0 +1,54 @@
package api
import (
"cafe/service"
"net/http"
"github.com/gin-gonic/gin"
)
// @Schemes
// @Summary get all active tables
// @Description gets all active tables as array
// @Tags tables
// @Produce json
// @Success 200 {array} service.Table
// @Router /tables [get]
func (a *Api) getTables(c *gin.Context) {
c.JSON(http.StatusOK, service.GetAllTables())
}
// @Schemes
// @Summary create new table
// @Description creates a new table and returns it
// @Tags tables
// @Accept json
// @Produce json
// @Success 201 {object} service.Table "Table has been created"
// @Failure 500 {object} errorResponse "Cannot create table"
// @Router /tables [post]
func (a *Api) createTable(c *gin.Context) {
table, err := service.CreateNewTable()
if err != nil {
c.JSON(http.StatusInternalServerError, errorResponse{err.Error()})
} else {
c.JSON(http.StatusCreated, table)
}
}
// @Schemes
// @Summary delete the latest table
// @Description deletes the latest table from the database
// @Tags tables
// @Produce json
// @Success 200 "OK"
// @Failure 500 {object} errorResponse "Cannot delete table"
// @Router /tables [delete]
func (a *Api) deleteTable(c *gin.Context) {
err := service.DeleteLatestTable()
if err != nil {
c.JSON(http.StatusInternalServerError, errorResponse{err.Error()})
} else {
c.Status(http.StatusOK)
}
}

60
api/routesUser.go Normal file
View file

@ -0,0 +1,60 @@
package api
import (
"cafe/types"
"cafe/user"
"net/http"
"github.com/gin-gonic/gin"
)
// @Schemes
// @Summary get a user
// @Description gets a user
// @Tags users
// @Produce json
// @Param username path string true "Username"
// @Success 200 {object} user.User
// @Failure 500 {object} errorResponse
// @Router /users/{username} [get]
func (a *Api) getUser(c *gin.Context) {
username := c.Param("username")
u, err := user.GetUserOrCreate(username)
if err != nil {
c.JSON(http.StatusBadRequest, errorResponse{err.Error()})
} else {
c.JSON(http.StatusOK, u)
}
}
// @Schemes
// @Summary update a user
// @Description updates a user with provided information
// @Tags users
// @Accept json
// @Produce json
// @Param user body user.User true "updated User"
// @Success 200 {object} user.User
// @Failure 400 {object} errorResponse
// @Failure 404 "Not Found" errorResponse
// @Failure 500 {object} errorResponse
// @Router /users [put]
func (a *Api) updateUser(c *gin.Context) {
var newUser user.User
err := c.ShouldBindJSON(&newUser)
if err != nil {
c.JSON(http.StatusBadRequest, errorResponse{types.MissingInformation.String()})
return
}
oldUser, err := user.DoesUserExist(newUser.Username)
if err != nil {
c.JSON(http.StatusNotFound, errorResponse{err.Error()})
return
}
err = user.UpdateUser(&oldUser, &newUser)
if err != nil {
c.JSON(http.StatusInternalServerError, errorResponse{err.Error()})
} else {
c.JSON(http.StatusOK, newUser)
}
}

26
api/static.go Normal file
View file

@ -0,0 +1,26 @@
package api
import (
"cafe/config"
"os"
"path/filepath"
"strings"
"github.com/gin-contrib/static"
"github.com/sirupsen/logrus"
)
func (a *Api) HandleStaticFiles() {
a.Router.LoadHTMLFiles(config.TemplatesDir + "index.html")
a.serveFoldersInTemplates()
}
func (a *Api) serveFoldersInTemplates() {
_ = filepath.WalkDir(config.TemplatesDir, func(path string, info os.DirEntry, err error) error {
if info.IsDir() && info.Name() != strings.TrimSuffix(config.TemplatesDir, "/") {
a.Router.Use(static.Serve("/"+info.Name(), static.LocalFile(config.TemplatesDir+info.Name(), false)))
logrus.WithField("folder", info.Name()).Debug("Serve static folder")
}
return err
})
}

31
api/swagger.go Normal file
View file

@ -0,0 +1,31 @@
package api
import (
"cafe/config"
"cafe/docs"
"net/http"
"net/url"
"os"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
)
func (a *Api) SetupSwagger() {
if config.Cafe.Swagger {
docs.SwaggerInfo.Title = "Cafe"
docs.SwaggerInfo.Description = "This is the backend of a cafe"
docs.SwaggerInfo.Version = os.Getenv("VERSION")
docs.SwaggerInfo.BasePath = "/api"
parsed, _ := url.Parse(config.Cafe.AllowedHosts[0])
docs.SwaggerInfo.Host = parsed.Host
a.Router.GET("/swagger", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "/swagger/index.html")
})
a.Router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
logrus.WithField("url", config.Cafe.AllowedHosts[0]+"/swagger").Info("Swagger running")
}
}

16
api/types.go Normal file
View file

@ -0,0 +1,16 @@
package api
import (
"cafe/hub"
"github.com/gin-gonic/gin"
)
type Api struct {
Router *gin.Engine
Hub hub.Hub
}
type errorResponse struct {
Error string `json:"error" validate:"required"`
}