Implement JWT session and token

This commit is contained in:
ryan 2026-05-07 15:59:58 +07:00
parent 3721fb3cd7
commit f5d9fe5223
9 changed files with 309 additions and 61 deletions

View File

@ -79,6 +79,14 @@ func (c *Config) GetCustomerJWTExpiresTTL() int {
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 {
return c.Log.LogLevel
}

View File

@ -4,6 +4,12 @@ type Jwt struct {
Token Token `mapstructure:"token"`
RefreshToken RefreshToken `mapstructure:"refresh_token"`
Customer Customer `mapstructure:"customer"`
SelfOrder SelfOrder `mapstructure:"self_order"`
}
type SelfOrder struct {
ExpiresTTL int `mapstructure:"expires-ttl"`
Secret string `mapstructure:"secret"`
}
type Token struct {

View File

@ -13,6 +13,9 @@ jwt:
customer:
expires-ttl: 7776000
secret: "z8d5TlFCT58Q$i0%S^2M&3WtE$PMgd"
self_order:
expires-ttl: 120
secret: "S3lf0rd3r_S3ss10n_S3cr3t_K3y_2024"
postgresql:
host: 62.72.45.250

View File

@ -51,6 +51,8 @@ func (a *App) Initialize(cfg *config.Config) error {
repos.tableRepo,
repos.outletRepo,
repos.userRepo,
cfg.GetSelfOrderJWTSecret(),
cfg.GetSelfOrderJWTExpiresTTL(),
)
a.router = router.NewRouter(
@ -114,6 +116,7 @@ func (a *App) Initialize(cfg *config.Config) error {
services.spinGameService,
middleware.customerAuthMiddleware,
selfOrderHandler,
middleware.selfOrderAuthMiddleware,
)
return nil
@ -448,14 +451,16 @@ func (a *App) initServices(processors *processors, repos *repositories, cfg *con
}
type middlewares struct {
authMiddleware *middleware.AuthMiddleware
customerAuthMiddleware *middleware.CustomerAuthMiddleware
authMiddleware *middleware.AuthMiddleware
customerAuthMiddleware *middleware.CustomerAuthMiddleware
selfOrderAuthMiddleware *middleware.SelfOrderAuthMiddleware
}
func (a *App) initMiddleware(services *services, cfg *config.Config) *middlewares {
return &middlewares{
authMiddleware: middleware.NewAuthMiddleware(services.authService),
customerAuthMiddleware: middleware.NewCustomerAuthMiddleware(cfg.GetCustomerJWTSecret()),
authMiddleware: middleware.NewAuthMiddleware(services.authService),
customerAuthMiddleware: middleware.NewCustomerAuthMiddleware(cfg.GetCustomerJWTSecret()),
selfOrderAuthMiddleware: middleware.NewSelfOrderAuthMiddleware(cfg.GetSelfOrderJWTSecret()),
}
}

View File

@ -4,12 +4,18 @@ import (
"github.com/google/uuid"
)
type SelfOrderMenuRequest struct {
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"`
@ -40,10 +46,8 @@ type SelfOrderMenuVariant struct {
}
type SelfOrderCreateOrderRequest struct {
TableID uuid.UUID `json:"table_id" validate:"required"`
CustomerName string `json:"customer_name" validate:"required"`
Phone *string `json:"phone,omitempty"`
OrderItems []SelfOrderCreateOrderItem `json:"order_items" validate:"required,min=1,dive"`
Phone *string `json:"phone,omitempty"`
OrderItems []SelfOrderCreateOrderItem `json:"order_items" validate:"required,min=1,dive"`
}
type SelfOrderCreateOrderItem struct {
@ -53,10 +57,6 @@ type SelfOrderCreateOrderItem struct {
Notes *string `json:"notes,omitempty"`
}
type SelfOrderListCategoriesRequest struct {
TableID uuid.UUID `form:"table_id" validate:"required"`
}
type SelfOrderCategoryItem struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`

View File

@ -19,12 +19,14 @@ import (
)
type SelfOrderHandler struct {
orderService service.OrderService
categoryService service.CategoryService
productService service.ProductService
tableRepo repository.TableRepositoryInterface
outletRepo processor.OutletRepository
userRepo processor.UserRepository
orderService service.OrderService
categoryService service.CategoryService
productService service.ProductService
tableRepo repository.TableRepositoryInterface
outletRepo processor.OutletRepository
userRepo processor.UserRepository
selfOrderJWTSecret string
selfOrderJWTTTL int
}
func NewSelfOrderHandler(
@ -34,44 +36,120 @@ func NewSelfOrderHandler(
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,
orderService: orderService,
categoryService: categoryService,
productService: productService,
tableRepo: tableRepo,
outletRepo: outletRepo,
userRepo: userRepo,
selfOrderJWTSecret: selfOrderJWTSecret,
selfOrderJWTTTL: selfOrderJWTTTL,
}
}
func (h *SelfOrderHandler) GetMenu(c *gin.Context) {
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.SelfOrderMenuRequest
var req contract.SelfOrderSessionRequest
if err := c.ShouldBindJSON(&req); err != nil {
logger.FromContext(ctx).WithError(err).Error("SelfOrderHandler::GetMenu -> request binding failed")
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::GetMenu")
}), "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::GetMenu")
}), "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::GetMenu")
}), "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{
@ -96,6 +174,8 @@ func (h *SelfOrderHandler) GetMenu(c *gin.Context) {
return
}
_ = customerName
isActive := true
catResp := h.categoryService.ListCategories(ctx, &contract.ListCategoriesRequest{
OrganizationID: &table.OrganizationID,
@ -192,6 +272,14 @@ func (h *SelfOrderHandler) buildMenuResponse(
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")
@ -208,7 +296,7 @@ func (h *SelfOrderHandler) CreateOrder(c *gin.Context) {
return
}
table, err := h.tableRepo.GetByID(ctx, req.TableID)
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{
@ -243,22 +331,27 @@ func (h *SelfOrderHandler) CreateOrder(c *gin.Context) {
})
}
metadata := make(map[string]interface{})
metadata["self_order"] = true
metadata["customer_name"] = req.CustomerName
customerPhone := phone
if req.Phone != nil {
metadata["customer_phone"] = *req.Phone
customerPhone = *req.Phone
}
tableID := req.TableID
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: &tableID,
TableID: &tableIDPtr,
TableNumber: &table.TableName,
OrderType: constants.OrderTypeDineIn,
OrderItems: orderItems,
CustomerName: &req.CustomerName,
CustomerName: &customerName,
Metadata: metadata,
}
@ -276,12 +369,6 @@ func (h *SelfOrderHandler) CreateOrder(c *gin.Context) {
}
func (h *SelfOrderHandler) validateCreateOrderRequest(req *contract.SelfOrderCreateOrderRequest) error {
if req.TableID == uuid.Nil {
return fmt.Errorf("table_id is required")
}
if req.CustomerName == "" {
return fmt.Errorf("customer_name is required")
}
if len(req.OrderItems) == 0 {
return fmt.Errorf("at least one order item is required")
}
@ -299,23 +386,15 @@ func (h *SelfOrderHandler) validateCreateOrderRequest(req *contract.SelfOrderCre
func (h *SelfOrderHandler) ListCategories(c *gin.Context) {
ctx := c.Request.Context()
var req contract.SelfOrderListCategoriesRequest
if err := c.ShouldBindQuery(&req); err != nil {
logger.FromContext(ctx).WithError(err).Error("SelfOrderHandler::ListCategories -> query binding failed")
tableID, _, _, err := h.getSelfOrderContext(c)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error()),
contract.NewResponseError(constants.ValidationErrorCode, constants.RequestEntity, err.Error()),
}), "SelfOrderHandler::ListCategories")
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::ListCategories")
return
}
table, err := h.tableRepo.GetByID(ctx, req.TableID)
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{

View File

@ -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()
}
}

View File

@ -49,9 +49,10 @@ type Router struct {
selfOrderHandler *handler.SelfOrderHandler
authMiddleware *middleware.AuthMiddleware
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, selfOrderHandler *handler.SelfOrderHandler) *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{
config: cfg,
@ -91,6 +92,7 @@ func NewRouter(cfg *config.Config, healthHandler *handler.HealthHandler, authSer
customerAuthMiddleware: customerAuthMiddleware,
productVariantHandler: handler.NewProductVariantHandler(productVariantService, productVariantValidator),
selfOrderHandler: selfOrderHandler,
selfOrderAuthMiddleware: selfOrderAuthMiddleware,
}
}
@ -149,9 +151,15 @@ func (r *Router) addAppRoutes(rg *gin.Engine) {
selfOrder := v1.Group("/self-order")
{
selfOrder.GET("/categories", r.selfOrderHandler.ListCategories)
selfOrder.POST("/menu", r.selfOrderHandler.GetMenu)
selfOrder.POST("/order", r.selfOrderHandler.CreateOrder)
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")

View File

@ -7,6 +7,7 @@ import (
"apskel-pos-be/internal/entities"
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
)
// GenerateCustomerTokens generates access and refresh tokens for customer
@ -85,3 +86,55 @@ func ExtractCustomerIDFromToken(token *jwt.Token) (string, error) {
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
}