Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5d9fe5223 | ||
|
|
3721fb3cd7 | ||
|
|
2d6df8e4c6 | ||
|
|
2c76962959 |
@@ -79,6 +79,14 @@ func (c *Config) GetCustomerJWTExpiresTTL() int {
|
|||||||
return c.Jwt.Customer.ExpiresTTL
|
return c.Jwt.Customer.ExpiresTTL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Config) GetSelfOrderJWTSecret() string {
|
||||||
|
return c.Jwt.SelfOrder.Secret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) GetSelfOrderJWTExpiresTTL() int {
|
||||||
|
return c.Jwt.SelfOrder.ExpiresTTL
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Config) LogLevel() string {
|
func (c *Config) LogLevel() string {
|
||||||
return c.Log.LogLevel
|
return c.Log.LogLevel
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,12 @@ type Jwt struct {
|
|||||||
Token Token `mapstructure:"token"`
|
Token Token `mapstructure:"token"`
|
||||||
RefreshToken RefreshToken `mapstructure:"refresh_token"`
|
RefreshToken RefreshToken `mapstructure:"refresh_token"`
|
||||||
Customer Customer `mapstructure:"customer"`
|
Customer Customer `mapstructure:"customer"`
|
||||||
|
SelfOrder SelfOrder `mapstructure:"self_order"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SelfOrder struct {
|
||||||
|
ExpiresTTL int `mapstructure:"expires-ttl"`
|
||||||
|
Secret string `mapstructure:"secret"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Token struct {
|
type Token struct {
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ jwt:
|
|||||||
customer:
|
customer:
|
||||||
expires-ttl: 7776000
|
expires-ttl: 7776000
|
||||||
secret: "z8d5TlFCT58Q$i0%S^2M&3WtE$PMgd"
|
secret: "z8d5TlFCT58Q$i0%S^2M&3WtE$PMgd"
|
||||||
|
self_order:
|
||||||
|
expires-ttl: 120
|
||||||
|
secret: "S3lf0rd3r_S3ss10n_S3cr3t_K3y_2024"
|
||||||
|
|
||||||
postgresql:
|
postgresql:
|
||||||
host: 62.72.45.250
|
host: 62.72.45.250
|
||||||
|
|||||||
+18
-4
@@ -44,6 +44,16 @@ func (a *App) Initialize(cfg *config.Config) error {
|
|||||||
validators := a.initValidators()
|
validators := a.initValidators()
|
||||||
middleware := a.initMiddleware(services, cfg)
|
middleware := a.initMiddleware(services, cfg)
|
||||||
healthHandler := handler.NewHealthHandler()
|
healthHandler := handler.NewHealthHandler()
|
||||||
|
selfOrderHandler := handler.NewSelfOrderHandler(
|
||||||
|
services.orderService,
|
||||||
|
services.categoryService,
|
||||||
|
services.productService,
|
||||||
|
repos.tableRepo,
|
||||||
|
repos.outletRepo,
|
||||||
|
repos.userRepo,
|
||||||
|
cfg.GetSelfOrderJWTSecret(),
|
||||||
|
cfg.GetSelfOrderJWTExpiresTTL(),
|
||||||
|
)
|
||||||
|
|
||||||
a.router = router.NewRouter(
|
a.router = router.NewRouter(
|
||||||
cfg,
|
cfg,
|
||||||
@@ -105,6 +115,8 @@ func (a *App) Initialize(cfg *config.Config) error {
|
|||||||
services.customerPointsService,
|
services.customerPointsService,
|
||||||
services.spinGameService,
|
services.spinGameService,
|
||||||
middleware.customerAuthMiddleware,
|
middleware.customerAuthMiddleware,
|
||||||
|
selfOrderHandler,
|
||||||
|
middleware.selfOrderAuthMiddleware,
|
||||||
)
|
)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -439,14 +451,16 @@ func (a *App) initServices(processors *processors, repos *repositories, cfg *con
|
|||||||
}
|
}
|
||||||
|
|
||||||
type middlewares struct {
|
type middlewares struct {
|
||||||
authMiddleware *middleware.AuthMiddleware
|
authMiddleware *middleware.AuthMiddleware
|
||||||
customerAuthMiddleware *middleware.CustomerAuthMiddleware
|
customerAuthMiddleware *middleware.CustomerAuthMiddleware
|
||||||
|
selfOrderAuthMiddleware *middleware.SelfOrderAuthMiddleware
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) initMiddleware(services *services, cfg *config.Config) *middlewares {
|
func (a *App) initMiddleware(services *services, cfg *config.Config) *middlewares {
|
||||||
return &middlewares{
|
return &middlewares{
|
||||||
authMiddleware: middleware.NewAuthMiddleware(services.authService),
|
authMiddleware: middleware.NewAuthMiddleware(services.authService),
|
||||||
customerAuthMiddleware: middleware.NewCustomerAuthMiddleware(cfg.GetCustomerJWTSecret()),
|
customerAuthMiddleware: middleware.NewCustomerAuthMiddleware(cfg.GetCustomerJWTSecret()),
|
||||||
|
selfOrderAuthMiddleware: middleware.NewSelfOrderAuthMiddleware(cfg.GetSelfOrderJWTSecret()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package contract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SelfOrderSessionRequest struct {
|
||||||
|
TableID uuid.UUID `json:"table_id" validate:"required"`
|
||||||
|
CustomerName string `json:"customer_name" validate:"required"`
|
||||||
|
Phone *string `json:"phone,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SelfOrderSessionResponse struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
ExpiresAt int64 `json:"expires_at"`
|
||||||
|
TableID uuid.UUID `json:"table_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SelfOrderMenuResponse struct {
|
||||||
|
OutletName string `json:"outlet_name"`
|
||||||
|
TableName string `json:"table_name"`
|
||||||
|
Categories []SelfOrderMenuCategory `json:"categories"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SelfOrderMenuCategory struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description *string `json:"description,omitempty"`
|
||||||
|
Order int `json:"order"`
|
||||||
|
Products []SelfOrderMenuItem `json:"products"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SelfOrderMenuItem struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description *string `json:"description,omitempty"`
|
||||||
|
Price float64 `json:"price"`
|
||||||
|
ImageURL *string `json:"image_url,omitempty"`
|
||||||
|
Variants []SelfOrderMenuVariant `json:"variants,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SelfOrderMenuVariant struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
PriceModifier float64 `json:"price_modifier"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SelfOrderCreateOrderRequest struct {
|
||||||
|
Phone *string `json:"phone,omitempty"`
|
||||||
|
OrderItems []SelfOrderCreateOrderItem `json:"order_items" validate:"required,min=1,dive"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SelfOrderCreateOrderItem struct {
|
||||||
|
ProductID uuid.UUID `json:"product_id" validate:"required"`
|
||||||
|
ProductVariantID *uuid.UUID `json:"product_variant_id,omitempty"`
|
||||||
|
Quantity int `json:"quantity" validate:"required,min=1"`
|
||||||
|
Notes *string `json:"notes,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SelfOrderCategoryItem struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description *string `json:"description,omitempty"`
|
||||||
|
Order int `json:"order"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SelfOrderListCategoriesResponse struct {
|
||||||
|
Categories []SelfOrderCategoryItem `json:"categories"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,449 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apskel-pos-be/internal/constants"
|
||||||
|
"apskel-pos-be/internal/contract"
|
||||||
|
"apskel-pos-be/internal/entities"
|
||||||
|
"apskel-pos-be/internal/logger"
|
||||||
|
"apskel-pos-be/internal/models"
|
||||||
|
"apskel-pos-be/internal/processor"
|
||||||
|
"apskel-pos-be/internal/repository"
|
||||||
|
"apskel-pos-be/internal/service"
|
||||||
|
"apskel-pos-be/internal/transformer"
|
||||||
|
"apskel-pos-be/internal/util"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SelfOrderHandler struct {
|
||||||
|
orderService service.OrderService
|
||||||
|
categoryService service.CategoryService
|
||||||
|
productService service.ProductService
|
||||||
|
tableRepo repository.TableRepositoryInterface
|
||||||
|
outletRepo processor.OutletRepository
|
||||||
|
userRepo processor.UserRepository
|
||||||
|
selfOrderJWTSecret string
|
||||||
|
selfOrderJWTTTL int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSelfOrderHandler(
|
||||||
|
orderService service.OrderService,
|
||||||
|
categoryService service.CategoryService,
|
||||||
|
productService service.ProductService,
|
||||||
|
tableRepo repository.TableRepositoryInterface,
|
||||||
|
outletRepo processor.OutletRepository,
|
||||||
|
userRepo processor.UserRepository,
|
||||||
|
selfOrderJWTSecret string,
|
||||||
|
selfOrderJWTTTL int,
|
||||||
|
) *SelfOrderHandler {
|
||||||
|
return &SelfOrderHandler{
|
||||||
|
orderService: orderService,
|
||||||
|
categoryService: categoryService,
|
||||||
|
productService: productService,
|
||||||
|
tableRepo: tableRepo,
|
||||||
|
outletRepo: outletRepo,
|
||||||
|
userRepo: userRepo,
|
||||||
|
selfOrderJWTSecret: selfOrderJWTSecret,
|
||||||
|
selfOrderJWTTTL: selfOrderJWTTTL,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SelfOrderHandler) getSelfOrderContext(c *gin.Context) (uuid.UUID, string, string, error) {
|
||||||
|
tableIDStr, _ := c.Get("self_order_table_id")
|
||||||
|
customerName, _ := c.Get("self_order_customer_name")
|
||||||
|
phoneStr, _ := c.Get("self_order_phone")
|
||||||
|
|
||||||
|
tableIDStrTyped, ok := tableIDStr.(string)
|
||||||
|
if !ok || tableIDStrTyped == "" {
|
||||||
|
return uuid.Nil, "", "", fmt.Errorf("table_id not found in context")
|
||||||
|
}
|
||||||
|
tableID, err := uuid.Parse(tableIDStrTyped)
|
||||||
|
if err != nil {
|
||||||
|
return uuid.Nil, "", "", fmt.Errorf("invalid table_id in token")
|
||||||
|
}
|
||||||
|
|
||||||
|
nameTyped, ok := customerName.(string)
|
||||||
|
if !ok || nameTyped == "" {
|
||||||
|
return uuid.Nil, "", "", fmt.Errorf("customer_name not found in context")
|
||||||
|
}
|
||||||
|
|
||||||
|
phone, _ := phoneStr.(string)
|
||||||
|
|
||||||
|
return tableID, nameTyped, phone, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SelfOrderHandler) CreateSession(c *gin.Context) {
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
|
||||||
|
var req contract.SelfOrderSessionRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
logger.FromContext(ctx).WithError(err).Error("SelfOrderHandler::CreateSession -> request binding failed")
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||||
|
contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error()),
|
||||||
|
}), "SelfOrderHandler::CreateSession")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.TableID == uuid.Nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||||
|
contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, "table_id is required"),
|
||||||
|
}), "SelfOrderHandler::CreateSession")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.CustomerName == "" {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||||
|
contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, "customer_name is required"),
|
||||||
|
}), "SelfOrderHandler::CreateSession")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
table, err := h.tableRepo.GetByID(ctx, req.TableID)
|
||||||
|
if err != nil {
|
||||||
|
logger.FromContext(ctx).WithError(err).Error("SelfOrderHandler::CreateSession -> table not found")
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||||
|
contract.NewResponseError(constants.NotFoundErrorCode, constants.TableEntity, "table not found"),
|
||||||
|
}), "SelfOrderHandler::CreateSession")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !table.IsActive {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||||
|
contract.NewResponseError(constants.ValidationErrorCode, constants.TableEntity, "table is not active"),
|
||||||
|
}), "SelfOrderHandler::CreateSession")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
phone := ""
|
||||||
|
if req.Phone != nil {
|
||||||
|
phone = *req.Phone
|
||||||
|
}
|
||||||
|
|
||||||
|
token, expiresAt, err := util.GenerateSelfOrderSessionToken(req.TableID, req.CustomerName, phone, h.selfOrderJWTSecret, h.selfOrderJWTTTL)
|
||||||
|
if err != nil {
|
||||||
|
logger.FromContext(ctx).WithError(err).Error("SelfOrderHandler::CreateSession -> failed to generate token")
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||||
|
contract.NewResponseError(constants.InternalServerErrorCode, constants.OrderServiceEntity, "failed to create session"),
|
||||||
|
}), "SelfOrderHandler::CreateSession")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(&contract.SelfOrderSessionResponse{
|
||||||
|
Token: token,
|
||||||
|
ExpiresAt: expiresAt,
|
||||||
|
TableID: req.TableID,
|
||||||
|
}), "SelfOrderHandler::CreateSession")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SelfOrderHandler) GetMenu(c *gin.Context) {
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
|
||||||
|
tableID, customerName, _, err := h.getSelfOrderContext(c)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||||
|
contract.NewResponseError(constants.ValidationErrorCode, constants.RequestEntity, err.Error()),
|
||||||
|
}), "SelfOrderHandler::GetMenu")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
table, err := h.tableRepo.GetByID(ctx, tableID)
|
||||||
|
if err != nil {
|
||||||
|
logger.FromContext(ctx).WithError(err).Error("SelfOrderHandler::GetMenu -> table not found")
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||||
|
contract.NewResponseError(constants.NotFoundErrorCode, constants.TableEntity, "table not found"),
|
||||||
|
}), "SelfOrderHandler::GetMenu")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !table.IsActive {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||||
|
contract.NewResponseError(constants.ValidationErrorCode, constants.TableEntity, "table is not active"),
|
||||||
|
}), "SelfOrderHandler::GetMenu")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
outlet, err := h.outletRepo.GetByID(ctx, table.OutletID)
|
||||||
|
if err != nil {
|
||||||
|
logger.FromContext(ctx).WithError(err).Error("SelfOrderHandler::GetMenu -> outlet not found")
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||||
|
contract.NewResponseError(constants.NotFoundErrorCode, constants.OrderServiceEntity, "outlet not found"),
|
||||||
|
}), "SelfOrderHandler::GetMenu")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = customerName
|
||||||
|
|
||||||
|
isActive := true
|
||||||
|
catResp := h.categoryService.ListCategories(ctx, &contract.ListCategoriesRequest{
|
||||||
|
OrganizationID: &table.OrganizationID,
|
||||||
|
Page: 1,
|
||||||
|
Limit: 100,
|
||||||
|
})
|
||||||
|
if catResp.HasErrors() {
|
||||||
|
logger.FromContext(ctx).WithError(catResp.GetErrors()[0]).Error("SelfOrderHandler::GetMenu -> failed to list categories")
|
||||||
|
util.HandleResponse(c.Writer, c.Request, catResp, "SelfOrderHandler::GetMenu")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
prodResp := h.productService.ListProducts(ctx, &contract.ListProductsRequest{
|
||||||
|
OrganizationID: &table.OrganizationID,
|
||||||
|
IsActive: &isActive,
|
||||||
|
Page: 1,
|
||||||
|
Limit: 1000,
|
||||||
|
})
|
||||||
|
if prodResp.HasErrors() {
|
||||||
|
logger.FromContext(ctx).WithError(prodResp.GetErrors()[0]).Error("SelfOrderHandler::GetMenu -> failed to list products")
|
||||||
|
util.HandleResponse(c.Writer, c.Request, prodResp, "SelfOrderHandler::GetMenu")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
catList, ok := catResp.Data.(*contract.ListCategoriesResponse)
|
||||||
|
if !ok {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||||
|
contract.NewResponseError(constants.InternalServerErrorCode, constants.CategoryServiceEntity, "unexpected categories response type"),
|
||||||
|
}), "SelfOrderHandler::GetMenu")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
prodList, ok := prodResp.Data.(*contract.ListProductsResponse)
|
||||||
|
if !ok {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||||
|
contract.NewResponseError(constants.InternalServerErrorCode, constants.ProductServiceEntity, "unexpected products response type"),
|
||||||
|
}), "SelfOrderHandler::GetMenu")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
menu := h.buildMenuResponse(outlet, table, catList.Categories, prodList.Products)
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(menu), "SelfOrderHandler::GetMenu")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SelfOrderHandler) buildMenuResponse(
|
||||||
|
outlet *entities.Outlet,
|
||||||
|
table *entities.Table,
|
||||||
|
categories []contract.CategoryResponse,
|
||||||
|
products []contract.ProductResponse,
|
||||||
|
) *contract.SelfOrderMenuResponse {
|
||||||
|
productMap := make(map[uuid.UUID][]contract.ProductResponse)
|
||||||
|
for _, p := range products {
|
||||||
|
productMap[p.CategoryID] = append(productMap[p.CategoryID], p)
|
||||||
|
}
|
||||||
|
|
||||||
|
menuCategories := make([]contract.SelfOrderMenuCategory, 0, len(categories))
|
||||||
|
for _, cat := range categories {
|
||||||
|
menuItems := make([]contract.SelfOrderMenuItem, 0)
|
||||||
|
if prods, ok := productMap[cat.ID]; ok {
|
||||||
|
for _, p := range prods {
|
||||||
|
item := contract.SelfOrderMenuItem{
|
||||||
|
ID: p.ID,
|
||||||
|
Name: p.Name,
|
||||||
|
Description: p.Description,
|
||||||
|
Price: p.Price,
|
||||||
|
ImageURL: p.ImageURL,
|
||||||
|
}
|
||||||
|
for _, v := range p.Variants {
|
||||||
|
item.Variants = append(item.Variants, contract.SelfOrderMenuVariant{
|
||||||
|
ID: v.ID,
|
||||||
|
Name: v.Name,
|
||||||
|
PriceModifier: v.PriceModifier,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
menuItems = append(menuItems, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
menuCategories = append(menuCategories, contract.SelfOrderMenuCategory{
|
||||||
|
ID: cat.ID,
|
||||||
|
Name: cat.Name,
|
||||||
|
Description: cat.Description,
|
||||||
|
Order: cat.Order,
|
||||||
|
Products: menuItems,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return &contract.SelfOrderMenuResponse{
|
||||||
|
OutletName: outlet.Name,
|
||||||
|
TableName: table.TableName,
|
||||||
|
Categories: menuCategories,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SelfOrderHandler) CreateOrder(c *gin.Context) {
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
|
||||||
|
tableID, customerName, phone, err := h.getSelfOrderContext(c)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||||
|
contract.NewResponseError(constants.ValidationErrorCode, constants.RequestEntity, err.Error()),
|
||||||
|
}), "SelfOrderHandler::CreateOrder")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req contract.SelfOrderCreateOrderRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
logger.FromContext(ctx).WithError(err).Error("SelfOrderHandler::CreateOrder -> request binding failed")
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||||
|
contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error()),
|
||||||
|
}), "SelfOrderHandler::CreateOrder")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.validateCreateOrderRequest(&req); err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||||
|
contract.NewResponseError(constants.ValidationErrorCode, constants.RequestEntity, err.Error()),
|
||||||
|
}), "SelfOrderHandler::CreateOrder")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
table, err := h.tableRepo.GetByID(ctx, tableID)
|
||||||
|
if err != nil {
|
||||||
|
logger.FromContext(ctx).WithError(err).Error("SelfOrderHandler::CreateOrder -> table not found")
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||||
|
contract.NewResponseError(constants.NotFoundErrorCode, constants.TableEntity, "table not found"),
|
||||||
|
}), "SelfOrderHandler::CreateOrder")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !table.IsActive || !table.IsAvailable() {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||||
|
contract.NewResponseError(constants.ValidationErrorCode, constants.TableEntity, "table is not available for ordering"),
|
||||||
|
}), "SelfOrderHandler::CreateOrder")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userID, err := h.resolveOrgUser(ctx, table.OrganizationID)
|
||||||
|
if err != nil {
|
||||||
|
logger.FromContext(ctx).WithError(err).Error("SelfOrderHandler::CreateOrder -> failed to resolve org user")
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||||
|
contract.NewResponseError(constants.InternalServerErrorCode, constants.OrderServiceEntity, "failed to create self-order"),
|
||||||
|
}), "SelfOrderHandler::CreateOrder")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
orderItems := make([]models.CreateOrderItemRequest, 0, len(req.OrderItems))
|
||||||
|
for _, item := range req.OrderItems {
|
||||||
|
orderItems = append(orderItems, models.CreateOrderItemRequest{
|
||||||
|
ProductID: item.ProductID,
|
||||||
|
ProductVariantID: item.ProductVariantID,
|
||||||
|
Quantity: item.Quantity,
|
||||||
|
Notes: item.Notes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
customerPhone := phone
|
||||||
|
if req.Phone != nil {
|
||||||
|
customerPhone = *req.Phone
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata := make(map[string]interface{})
|
||||||
|
metadata["self_order"] = true
|
||||||
|
metadata["customer_name"] = customerName
|
||||||
|
if customerPhone != "" {
|
||||||
|
metadata["customer_phone"] = customerPhone
|
||||||
|
}
|
||||||
|
|
||||||
|
tableIDPtr := tableID
|
||||||
|
modelReq := &models.CreateOrderRequest{
|
||||||
|
OutletID: table.OutletID,
|
||||||
|
UserID: userID,
|
||||||
|
TableID: &tableIDPtr,
|
||||||
|
TableNumber: &table.TableName,
|
||||||
|
OrderType: constants.OrderTypeDineIn,
|
||||||
|
OrderItems: orderItems,
|
||||||
|
CustomerName: &customerName,
|
||||||
|
Metadata: metadata,
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := h.orderService.CreateOrder(ctx, modelReq, table.OrganizationID)
|
||||||
|
if err != nil {
|
||||||
|
logger.FromContext(ctx).WithError(err).Error("SelfOrderHandler::CreateOrder -> failed to create order")
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||||
|
contract.NewResponseError(constants.InternalServerErrorCode, constants.OrderServiceEntity, err.Error()),
|
||||||
|
}), "SelfOrderHandler::CreateOrder")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
contractResp := transformer.OrderModelToContract(response)
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(contractResp), "SelfOrderHandler::CreateOrder")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SelfOrderHandler) validateCreateOrderRequest(req *contract.SelfOrderCreateOrderRequest) error {
|
||||||
|
if len(req.OrderItems) == 0 {
|
||||||
|
return fmt.Errorf("at least one order item is required")
|
||||||
|
}
|
||||||
|
for i, item := range req.OrderItems {
|
||||||
|
if item.ProductID == uuid.Nil {
|
||||||
|
return fmt.Errorf("product_id is required for item %d", i+1)
|
||||||
|
}
|
||||||
|
if item.Quantity <= 0 {
|
||||||
|
return fmt.Errorf("quantity must be greater than zero for item %d", i+1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SelfOrderHandler) ListCategories(c *gin.Context) {
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
|
||||||
|
tableID, _, _, err := h.getSelfOrderContext(c)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||||
|
contract.NewResponseError(constants.ValidationErrorCode, constants.RequestEntity, err.Error()),
|
||||||
|
}), "SelfOrderHandler::ListCategories")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
table, err := h.tableRepo.GetByID(ctx, tableID)
|
||||||
|
if err != nil {
|
||||||
|
logger.FromContext(ctx).WithError(err).Error("SelfOrderHandler::ListCategories -> table not found")
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||||
|
contract.NewResponseError(constants.NotFoundErrorCode, constants.TableEntity, "table not found"),
|
||||||
|
}), "SelfOrderHandler::ListCategories")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
catResp := h.categoryService.ListCategories(ctx, &contract.ListCategoriesRequest{
|
||||||
|
OrganizationID: &table.OrganizationID,
|
||||||
|
Page: 1,
|
||||||
|
Limit: 100,
|
||||||
|
})
|
||||||
|
if catResp.HasErrors() {
|
||||||
|
logger.FromContext(ctx).WithError(catResp.GetErrors()[0]).Error("SelfOrderHandler::ListCategories -> failed to list categories")
|
||||||
|
util.HandleResponse(c.Writer, c.Request, catResp, "SelfOrderHandler::ListCategories")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
catList, ok := catResp.Data.(*contract.ListCategoriesResponse)
|
||||||
|
if !ok {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||||
|
contract.NewResponseError(constants.InternalServerErrorCode, constants.CategoryServiceEntity, "unexpected categories response type"),
|
||||||
|
}), "SelfOrderHandler::ListCategories")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
items := make([]contract.SelfOrderCategoryItem, 0, len(catList.Categories))
|
||||||
|
for _, cat := range catList.Categories {
|
||||||
|
items = append(items, contract.SelfOrderCategoryItem{
|
||||||
|
ID: cat.ID,
|
||||||
|
Name: cat.Name,
|
||||||
|
Description: cat.Description,
|
||||||
|
Order: cat.Order,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(&contract.SelfOrderListCategoriesResponse{
|
||||||
|
Categories: items,
|
||||||
|
}), "SelfOrderHandler::ListCategories")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SelfOrderHandler) resolveOrgUser(ctx context.Context, organizationID uuid.UUID) (uuid.UUID, error) {
|
||||||
|
users, err := h.userRepo.GetByOrganizationID(ctx, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return uuid.Nil, fmt.Errorf("failed to get users for organization: %w", err)
|
||||||
|
}
|
||||||
|
if len(users) == 0 {
|
||||||
|
return uuid.Nil, fmt.Errorf("no users found for organization")
|
||||||
|
}
|
||||||
|
return users[0].ID, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"apskel-pos-be/internal/constants"
|
||||||
|
"apskel-pos-be/internal/contract"
|
||||||
|
"apskel-pos-be/internal/util"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SelfOrderAuthMiddleware struct {
|
||||||
|
selfOrderJWTSecret string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSelfOrderAuthMiddleware(selfOrderJWTSecret string) *SelfOrderAuthMiddleware {
|
||||||
|
return &SelfOrderAuthMiddleware{
|
||||||
|
selfOrderJWTSecret: selfOrderJWTSecret,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SelfOrderAuthMiddleware) ValidateSelfOrderToken() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
authHeader := c.GetHeader("Authorization")
|
||||||
|
if authHeader == "" {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||||
|
contract.NewResponseError(constants.ValidationErrorCode, constants.AuthHandlerEntity, "Authorization header is required"),
|
||||||
|
}), "SelfOrderAuthMiddleware::ValidateSelfOrderToken")
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(authHeader, "Bearer ") {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||||
|
contract.NewResponseError(constants.ValidationErrorCode, constants.AuthHandlerEntity, "Invalid authorization header format"),
|
||||||
|
}), "SelfOrderAuthMiddleware::ValidateSelfOrderToken")
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
|
||||||
|
if tokenString == "" {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||||
|
contract.NewResponseError(constants.ValidationErrorCode, constants.AuthHandlerEntity, "Token is required"),
|
||||||
|
}), "SelfOrderAuthMiddleware::ValidateSelfOrderToken")
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
claims, err := util.ValidateSelfOrderToken(tokenString, m.selfOrderJWTSecret)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||||
|
contract.NewResponseError(constants.ValidationErrorCode, constants.AuthHandlerEntity, "Invalid token: "+err.Error()),
|
||||||
|
}), "SelfOrderAuthMiddleware::ValidateSelfOrderToken")
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tableID, ok := claims["table_id"].(string)
|
||||||
|
if !ok || tableID == "" {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||||
|
contract.NewResponseError(constants.ValidationErrorCode, constants.AuthHandlerEntity, "table_id not found in token"),
|
||||||
|
}), "SelfOrderAuthMiddleware::ValidateSelfOrderToken")
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
customerName, ok := claims["customer_name"].(string)
|
||||||
|
if !ok || customerName == "" {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||||
|
contract.NewResponseError(constants.ValidationErrorCode, constants.AuthHandlerEntity, "customer_name not found in token"),
|
||||||
|
}), "SelfOrderAuthMiddleware::ValidateSelfOrderToken")
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
phone, _ := claims["phone"].(string)
|
||||||
|
|
||||||
|
c.Set("self_order_table_id", tableID)
|
||||||
|
c.Set("self_order_customer_name", customerName)
|
||||||
|
c.Set("self_order_phone", phone)
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -46,11 +46,13 @@ type Router struct {
|
|||||||
customerAuthHandler *handler.CustomerAuthHandler
|
customerAuthHandler *handler.CustomerAuthHandler
|
||||||
customerPointsHandler *handler.CustomerPointsHandler
|
customerPointsHandler *handler.CustomerPointsHandler
|
||||||
spinGameHandler *handler.SpinGameHandler
|
spinGameHandler *handler.SpinGameHandler
|
||||||
|
selfOrderHandler *handler.SelfOrderHandler
|
||||||
authMiddleware *middleware.AuthMiddleware
|
authMiddleware *middleware.AuthMiddleware
|
||||||
customerAuthMiddleware *middleware.CustomerAuthMiddleware
|
customerAuthMiddleware *middleware.CustomerAuthMiddleware
|
||||||
|
selfOrderAuthMiddleware *middleware.SelfOrderAuthMiddleware
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRouter(cfg *config.Config, healthHandler *handler.HealthHandler, authService service.AuthService, authMiddleware *middleware.AuthMiddleware, userService *service.UserServiceImpl, userValidator *validator.UserValidatorImpl, organizationService service.OrganizationService, organizationValidator validator.OrganizationValidator, outletService service.OutletService, outletValidator validator.OutletValidator, outletSettingService service.OutletSettingService, categoryService service.CategoryService, categoryValidator validator.CategoryValidator, productService service.ProductService, productValidator validator.ProductValidator, productVariantService service.ProductVariantService, productVariantValidator validator.ProductVariantValidator, inventoryService service.InventoryService, inventoryValidator validator.InventoryValidator, orderService service.OrderService, orderValidator validator.OrderValidator, fileService service.FileService, fileValidator validator.FileValidator, customerService service.CustomerService, customerValidator validator.CustomerValidator, paymentMethodService service.PaymentMethodService, paymentMethodValidator validator.PaymentMethodValidator, analyticsService *service.AnalyticsServiceImpl, reportService service.ReportService, tableService *service.TableServiceImpl, tableValidator *validator.TableValidator, unitService handler.UnitService, ingredientService handler.IngredientService, productRecipeService service.ProductRecipeService, vendorService service.VendorService, vendorValidator validator.VendorValidator, purchaseOrderService service.PurchaseOrderService, purchaseOrderValidator validator.PurchaseOrderValidator, unitConverterService service.IngredientUnitConverterService, unitConverterValidator validator.IngredientUnitConverterValidator, chartOfAccountTypeService service.ChartOfAccountTypeService, chartOfAccountTypeValidator validator.ChartOfAccountTypeValidator, chartOfAccountService service.ChartOfAccountService, chartOfAccountValidator validator.ChartOfAccountValidator, accountService service.AccountService, accountValidator validator.AccountValidator, orderIngredientTransactionService service.OrderIngredientTransactionService, orderIngredientTransactionValidator validator.OrderIngredientTransactionValidator, gamificationService service.GamificationService, gamificationValidator validator.GamificationValidator, rewardService service.RewardService, rewardValidator validator.RewardValidator, campaignService service.CampaignService, campaignValidator validator.CampaignValidator, customerAuthService service.CustomerAuthService, customerAuthValidator validator.CustomerAuthValidator, customerPointsService service.CustomerPointsService, spinGameService service.SpinGameService, customerAuthMiddleware *middleware.CustomerAuthMiddleware) *Router {
|
func NewRouter(cfg *config.Config, healthHandler *handler.HealthHandler, authService service.AuthService, authMiddleware *middleware.AuthMiddleware, userService *service.UserServiceImpl, userValidator *validator.UserValidatorImpl, organizationService service.OrganizationService, organizationValidator validator.OrganizationValidator, outletService service.OutletService, outletValidator validator.OutletValidator, outletSettingService service.OutletSettingService, categoryService service.CategoryService, categoryValidator validator.CategoryValidator, productService service.ProductService, productValidator validator.ProductValidator, productVariantService service.ProductVariantService, productVariantValidator validator.ProductVariantValidator, inventoryService service.InventoryService, inventoryValidator validator.InventoryValidator, orderService service.OrderService, orderValidator validator.OrderValidator, fileService service.FileService, fileValidator validator.FileValidator, customerService service.CustomerService, customerValidator validator.CustomerValidator, paymentMethodService service.PaymentMethodService, paymentMethodValidator validator.PaymentMethodValidator, analyticsService *service.AnalyticsServiceImpl, reportService service.ReportService, tableService *service.TableServiceImpl, tableValidator *validator.TableValidator, unitService handler.UnitService, ingredientService handler.IngredientService, productRecipeService service.ProductRecipeService, vendorService service.VendorService, vendorValidator validator.VendorValidator, purchaseOrderService service.PurchaseOrderService, purchaseOrderValidator validator.PurchaseOrderValidator, unitConverterService service.IngredientUnitConverterService, unitConverterValidator validator.IngredientUnitConverterValidator, chartOfAccountTypeService service.ChartOfAccountTypeService, chartOfAccountTypeValidator validator.ChartOfAccountTypeValidator, chartOfAccountService service.ChartOfAccountService, chartOfAccountValidator validator.ChartOfAccountValidator, accountService service.AccountService, accountValidator validator.AccountValidator, orderIngredientTransactionService service.OrderIngredientTransactionService, orderIngredientTransactionValidator validator.OrderIngredientTransactionValidator, gamificationService service.GamificationService, gamificationValidator validator.GamificationValidator, rewardService service.RewardService, rewardValidator validator.RewardValidator, campaignService service.CampaignService, campaignValidator validator.CampaignValidator, customerAuthService service.CustomerAuthService, customerAuthValidator validator.CustomerAuthValidator, customerPointsService service.CustomerPointsService, spinGameService service.SpinGameService, customerAuthMiddleware *middleware.CustomerAuthMiddleware, selfOrderHandler *handler.SelfOrderHandler, selfOrderAuthMiddleware *middleware.SelfOrderAuthMiddleware) *Router {
|
||||||
|
|
||||||
return &Router{
|
return &Router{
|
||||||
config: cfg,
|
config: cfg,
|
||||||
@@ -89,6 +91,8 @@ func NewRouter(cfg *config.Config, healthHandler *handler.HealthHandler, authSer
|
|||||||
authMiddleware: authMiddleware,
|
authMiddleware: authMiddleware,
|
||||||
customerAuthMiddleware: customerAuthMiddleware,
|
customerAuthMiddleware: customerAuthMiddleware,
|
||||||
productVariantHandler: handler.NewProductVariantHandler(productVariantService, productVariantValidator),
|
productVariantHandler: handler.NewProductVariantHandler(productVariantService, productVariantValidator),
|
||||||
|
selfOrderHandler: selfOrderHandler,
|
||||||
|
selfOrderAuthMiddleware: selfOrderAuthMiddleware,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,6 +149,19 @@ func (r *Router) addAppRoutes(rg *gin.Engine) {
|
|||||||
customer.POST("/spin", r.spinGameHandler.PlaySpinGame)
|
customer.POST("/spin", r.spinGameHandler.PlaySpinGame)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
selfOrder := v1.Group("/self-order")
|
||||||
|
{
|
||||||
|
selfOrder.POST("/session", r.selfOrderHandler.CreateSession)
|
||||||
|
}
|
||||||
|
|
||||||
|
selfOrderProtected := v1.Group("/self-order")
|
||||||
|
selfOrderProtected.Use(r.selfOrderAuthMiddleware.ValidateSelfOrderToken())
|
||||||
|
{
|
||||||
|
selfOrderProtected.GET("/menu", r.selfOrderHandler.GetMenu)
|
||||||
|
selfOrderProtected.GET("/categories", r.selfOrderHandler.ListCategories)
|
||||||
|
selfOrderProtected.POST("/order", r.selfOrderHandler.CreateOrder)
|
||||||
|
}
|
||||||
|
|
||||||
organizations := v1.Group("/organizations")
|
organizations := v1.Group("/organizations")
|
||||||
{
|
{
|
||||||
organizations.POST("", r.organizationHandler.CreateOrganization)
|
organizations.POST("", r.organizationHandler.CreateOrganization)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"apskel-pos-be/internal/entities"
|
"apskel-pos-be/internal/entities"
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GenerateCustomerTokens generates access and refresh tokens for customer
|
// GenerateCustomerTokens generates access and refresh tokens for customer
|
||||||
@@ -85,3 +86,55 @@ func ExtractCustomerIDFromToken(token *jwt.Token) (string, error) {
|
|||||||
|
|
||||||
return customerID, nil
|
return customerID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GenerateSelfOrderSessionToken(tableID uuid.UUID, customerName string, phone string, secret string, ttlMinutes int) (string, int64, error) {
|
||||||
|
now := time.Now()
|
||||||
|
expiresAt := now.Add(time.Duration(ttlMinutes) * time.Minute)
|
||||||
|
|
||||||
|
claims := jwt.MapClaims{
|
||||||
|
"table_id": tableID.String(),
|
||||||
|
"customer_name": customerName,
|
||||||
|
"phone": phone,
|
||||||
|
"session_id": uuid.New().String(),
|
||||||
|
"type": "self_order_access",
|
||||||
|
"iat": now.Unix(),
|
||||||
|
"exp": expiresAt.Unix(),
|
||||||
|
}
|
||||||
|
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
|
tokenString, err := token.SignedString([]byte(secret))
|
||||||
|
if err != nil {
|
||||||
|
return "", 0, fmt.Errorf("failed to generate self-order session token: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokenString, expiresAt.Unix(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidateSelfOrderToken(tokenString string, secret string) (jwt.MapClaims, error) {
|
||||||
|
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||||
|
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||||
|
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||||
|
}
|
||||||
|
return []byte(secret), nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse self-order token: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !token.Valid {
|
||||||
|
return nil, fmt.Errorf("invalid self-order token")
|
||||||
|
}
|
||||||
|
|
||||||
|
claims, ok := token.Claims.(jwt.MapClaims)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid self-order token claims")
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenType, ok := claims["type"].(string)
|
||||||
|
if !ok || tokenType != "self_order_access" {
|
||||||
|
return nil, fmt.Errorf("invalid self-order token type")
|
||||||
|
}
|
||||||
|
|
||||||
|
return claims, nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user