package controller import ( "errors" "net/http" "github.com/Caknoooo/go-gin-clean-starter/modules/menu/dto" "github.com/Caknoooo/go-gin-clean-starter/modules/menu/query" "github.com/Caknoooo/go-gin-clean-starter/modules/menu/service" "github.com/Caknoooo/go-gin-clean-starter/pkg/constants" "github.com/Caknoooo/go-gin-clean-starter/pkg/utils" "github.com/Caknoooo/go-pagination" "github.com/gin-gonic/gin" "github.com/samber/do" "gorm.io/gorm" ) type ( MenuController interface { CreateMenu(ctx *gin.Context) GetMenuByID(ctx *gin.Context) GetMenuByName(ctx *gin.Context) GetMenus(ctx *gin.Context) UpdateMenu(ctx *gin.Context) DeleteMenu(ctx *gin.Context) } menuController struct { menuService service.MenuService db *gorm.DB } ) // CreateMenu godoc // @Summary Create a new menu // @Description Create a new menu with the provided information // @Tags Menus // @Accept json // @Produce json // @Param request body dto.MenuCreateRequest true "Menu creation data" // @Success 200 {object} utils.Response // @Failure 400 {object} utils.Response // @Router /menus [post] func (m *menuController) CreateMenu(ctx *gin.Context) { var req dto.MenuCreateRequest if err := ctx.ShouldBind(&req); err != nil { res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_DATA_FROM_BODY, err.Error(), nil) ctx.AbortWithStatusJSON(http.StatusBadRequest, res) return } in := dto.MenuResponse{ Name: req.Name, IconUrl: req.IconUrl, Url: req.Url, Sequence: req.Sequence, Mode: req.Mode, Status: req.Status, } if req.ParentID != nil { in.Parent = &dto.MenuParentResponse{ID: req.ParentID.String()} } res, err := m.menuService.Create(ctx.Request.Context(), in) if err != nil { resp := utils.BuildResponseFailed(dto.MESSAGE_FAILED_CREATE_MENU, err.Error(), nil) ctx.JSON(http.StatusBadRequest, resp) return } resp := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_CREATE_MENU, res) ctx.JSON(http.StatusOK, resp) } // GetMenus godoc // @Summary Get list of menus // @Description Get paginated list of menus with filtering and sorting capabilities // @Tags Menus // @Accept json // @Produce json // @Param name query string false "Filter by name (partial match)" // @Param parent_id query string false "Filter by parent menu ID" // @Param includes query []string false "Include related entities" // @Param page query int false "Page number (default: 1)" // @Param page_size query int false "Page size (default: 10)" // @Success 200 {object} utils.ResponseWithPagination // @Failure 400 {object} utils.Response // @Router /menus [get] func (m *menuController) GetMenus(ctx *gin.Context) { _ = ctx.MustGet("client_id").(string) var filter = &query.MenuFilter{ // ClientID: clientId, Name: ctx.Query("name"), ParentID: ctx.Query("parent_id"), Includes: ctx.QueryArray("includes"), } filter.BindPagination(ctx) ctx.ShouldBindQuery(filter) menus, total, err := pagination.PaginatedQueryWithIncludable[query.M_Menu](m.db, filter) if err != nil { res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_LIST_MENU, err.Error(), nil) ctx.JSON(http.StatusBadRequest, res) return } menuResponses := ToMenuResponses(menus) paginationResponse := pagination.CalculatePagination(filter.Pagination, total) response := pagination.NewPaginatedResponse(http.StatusOK, dto.MESSAGE_SUCCESS_GET_LIST_MENU, menuResponses, paginationResponse) ctx.JSON(http.StatusOK, response) } // GetMenuByID godoc // @Summary Get menu by ID // @Description Get detailed information of a specific menu by its ID // @Tags Menus // @Accept json // @Produce json // @Param id path string true "Menu ID" // @Success 200 {object} utils.Response // @Failure 400 {object} utils.Response // @Failure 404 {object} utils.Response // @Router /menus/{id} [get] func (m *menuController) GetMenuByID(ctx *gin.Context) { id := ctx.Param("id") if id == "" { res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_MENU, "id is required", nil) ctx.JSON(http.StatusBadRequest, res) return } res, err := m.menuService.GetById(ctx.Request.Context(), id) if err != nil { resErr := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_MENU, err.Error(), nil) ctx.JSON(http.StatusBadRequest, resErr) return } resp := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_GET_MENU, res) ctx.JSON(http.StatusOK, resp) } // GetMenuByName godoc // @Summary Get menu by name // @Description Get menu information by its name // @Tags Menus // @Accept json // @Produce json // @Param name query string true "Menu name" // @Success 200 {object} utils.Response // @Failure 400 {object} utils.Response // @Router /menus/by-name [get] func (m *menuController) GetMenuByName(ctx *gin.Context) { name := ctx.Query("name") if name == "" { res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_MENU, "name query is required", nil) ctx.JSON(http.StatusBadRequest, res) return } res, err := m.menuService.GetByName(ctx.Request.Context(), name) if err != nil { resErr := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_MENU, err.Error(), nil) ctx.JSON(http.StatusBadRequest, resErr) return } resp := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_GET_MENU, res) ctx.JSON(http.StatusOK, resp) } // UpdateMenu godoc // @Summary Update menu // @Description Update menu information by ID // @Tags Menus // @Accept json // @Produce json // @Param id path string true "Menu ID" // @Param request body dto.MenuUpdateRequest true "Menu update data" // @Success 200 {object} utils.Response // @Failure 400 {object} utils.Response // @Router /menus/{id} [put] func (m *menuController) UpdateMenu(ctx *gin.Context) { var req dto.MenuUpdateRequest if err := ctx.ShouldBind(&req); err != nil { res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_DATA_FROM_BODY, err.Error(), nil) ctx.AbortWithStatusJSON(http.StatusBadRequest, res) return } menuId := ctx.Param("id") result, err := m.menuService.Update(ctx.Request.Context(), menuId, req) if err != nil { res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_UPDATE_MENU, err.Error(), nil) ctx.JSON(http.StatusBadRequest, res) return } resp := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_UPDATE_MENU, result) ctx.JSON(http.StatusOK, resp) } // DeleteMenu godoc // @Summary Delete menu // @Description Delete a menu by ID // @Tags Menus // @Accept json // @Produce json // @Param id path string true "Menu ID" // @Success 200 {object} utils.Response // @Failure 400 {object} utils.Response // @Failure 404 {object} utils.Response // @Router /menus/{id} [delete] func (m *menuController) DeleteMenu(ctx *gin.Context) { id := ctx.Param("id") if id == "" { res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_DELETE_MENU, "id is required", nil) ctx.JSON(http.StatusBadRequest, res) return } if err := m.menuService.Delete(ctx.Request.Context(), id); err != nil { // if record not found, return 404 if errors.Is(err, gorm.ErrRecordNotFound) { ctx.JSON(http.StatusNotFound, gin.H{"error": "menu not found"}) return } res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_DELETE_MENU, err.Error(), nil) ctx.JSON(http.StatusBadRequest, res) return } resp := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_DELETE_MENU, nil) ctx.JSON(http.StatusOK, resp) } func NewMenuController(i *do.Injector, ms service.MenuService) MenuController { db := do.MustInvokeNamed[*gorm.DB](i, constants.DB) return &menuController{ menuService: ms, db: db, } } // Fungsi transform dari entities ke DTO func ToMenuResponse(menu query.M_Menu) dto.MenuResponse { response := dto.MenuResponse{ ID: menu.ID.String(), Name: menu.Name, IconUrl: menu.IconUrl, Url: menu.Url, Sequence: menu.Sequence, Mode: menu.Mode, Status: menu.Status, } // Handle parent jika ada if menu.Parent != nil { response.Parent = &dto.MenuParentResponse{ ID: menu.Parent.ID.String(), Name: menu.Parent.Name, } } return response } func ToMenuResponses(menus []query.M_Menu) []dto.MenuResponse { responses := make([]dto.MenuResponse, len(menus)) for i, menu := range menus { responses[i] = ToMenuResponse(menu) } return responses }