package v1 import ( "net/http" "github.com/gin-gonic/gin" "github.com/gochat/gochat/internal/service" applogger "github.com/gochat/gochat/pkg/logger" "github.com/gochat/gochat/pkg/response" pkgvalidator "github.com/gochat/gochat/pkg/validator" ) // DashboardAppHandler handles DashboardApp CRUD endpoints. // Reference: Chatwoot app/controllers/api/v1/accounts/dashboard_apps_controller.rb // Chatwoot routes: resources :dashboard_apps, only: [:index, :show, :create, :update, :destroy] // Chatwoot request: params.require(:dashboard_app).permit(:title, content: [:url, :type]) // Chatwoot response: Jbuilder partial — {id, title, content, created_at} // Chatwoot destroy: head :no_content (204) // GoChat extensions: search, widgets CRUD (not in Chatwoot) type DashboardAppHandler struct { svc *service.DashboardAppService } func NewDashboardAppHandler(svc *service.DashboardAppService) *DashboardAppHandler { return &DashboardAppHandler{svc: svc} } // Create creates a new dashboard app. // POST /api/v1/accounts/:account_id/dashboard_apps // Chatwoot: Current.account.dashboard_apps.create!(permitted_payload.merge(user_id: Current.user.id)) func (h *DashboardAppHandler) Create(c *gin.Context) { accountID := getAccountID(c) if accountID == 0 { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account_id") return } userID := getUserID(c) userIDPtr := &userID var wrapper service.DashboardAppCreateWrapper if err := c.ShouldBindJSON(&wrapper); err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error()) return } req := wrapper.DashboardApp if err := pkgvalidator.ValidateStruct(&req); err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error()) return } app, err := h.svc.Create(c.Request.Context(), accountID, userIDPtr, &req) if err != nil { applogger.L().Errorf("Create dashboard app: %v", err) response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to create dashboard app") return } response.Created(c, app) } // Get retrieves a dashboard app by ID. // GET /api/v1/accounts/:account_id/dashboard_apps/:dashboard_app_id func (h *DashboardAppHandler) Get(c *gin.Context) { id, err := parseUintParam(c, "dashboard_app_id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid dashboard_app_id") return } app, err := h.svc.GetByID(c.Request.Context(), id) if err != nil { applogger.L().Errorf("Get dashboard app: %v", err) response.AbortWithStatusError(c, http.StatusNotFound, response.ErrNotFound, "dashboard app not found") return } response.OK(c, app) } // Update modifies an existing dashboard app. // PUT /api/v1/accounts/:account_id/dashboard_apps/:dashboard_app_id // Chatwoot: @dashboard_app.update!(permitted_payload) func (h *DashboardAppHandler) Update(c *gin.Context) { id, err := parseUintParam(c, "dashboard_app_id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid dashboard_app_id") return } var wrapper service.DashboardAppUpdateWrapper if err := c.ShouldBindJSON(&wrapper); err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error()) return } req := wrapper.DashboardApp app, err := h.svc.Update(c.Request.Context(), id, &req) if err != nil { applogger.L().Errorf("Update dashboard app: %v", err) response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to update dashboard app") return } response.OK(c, app) } // Delete removes a dashboard app. // DELETE /api/v1/accounts/:account_id/dashboard_apps/:dashboard_app_id // Chatwoot: @dashboard_app.destroy!; head :no_content (204) func (h *DashboardAppHandler) Delete(c *gin.Context) { id, err := parseUintParam(c, "dashboard_app_id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid dashboard_app_id") return } if err := h.svc.Delete(c.Request.Context(), id); err != nil { applogger.L().Errorf("Delete dashboard app: %v", err) response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to delete dashboard app") return } response.NoContent(c) } // Patch partially updates a dashboard app (GoChat extension — Chatwoot has no PATCH). // PATCH /api/v1/accounts/:account_id/dashboard_apps/:dashboard_app_id func (h *DashboardAppHandler) Patch(c *gin.Context) { id, err := parseUintParam(c, "dashboard_app_id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid dashboard_app_id") return } var wrapper service.DashboardAppUpdateWrapper if err := c.ShouldBindJSON(&wrapper); err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error()) return } req := wrapper.DashboardApp app, err := h.svc.Update(c.Request.Context(), id, &req) if err != nil { applogger.L().Errorf("Patch dashboard app: %v", err) response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to update dashboard app") return } response.OK(c, app) } // List returns all dashboard apps for an account. // GET /api/v1/accounts/:account_id/dashboard_apps // Chatwoot: json.array! @dashboard_apps — pure JSON array, no meta func (h *DashboardAppHandler) List(c *gin.Context) { accountID := getAccountID(c) if accountID == 0 { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account_id") return } apps, err := h.svc.ListByAccount(c.Request.Context(), accountID) if err != nil { applogger.L().Errorf("List dashboard apps: %v", err) response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to list dashboard apps") return } // Chatwoot returns a pure JSON array (no meta/pagination wrapper) c.JSON(http.StatusOK, apps) } // ========== GoChat Extension Endpoints (not in Chatwoot) ========== // Search searches dashboard apps by title keyword within an account. // GET /api/v1/accounts/:account_id/dashboard_apps/search?q=... func (h *DashboardAppHandler) Search(c *gin.Context) { accountID := getAccountID(c) if accountID == 0 { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account_id") return } query := c.Query("q") if query == "" { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, "search query 'q' is required") return } apps, err := h.svc.Search(c.Request.Context(), accountID, query) if err != nil { applogger.L().Errorf("Search dashboard apps: %v", err) response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to search dashboard apps") return } c.JSON(http.StatusOK, apps) } // GetWidgets returns all widgets in a dashboard app's content. // GET /api/v1/accounts/:account_id/dashboard_apps/:dashboard_app_id/widgets func (h *DashboardAppHandler) GetWidgets(c *gin.Context) { id, err := parseUintParam(c, "dashboard_app_id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid dashboard_app_id") return } widgets, err := h.svc.GetWidgets(c.Request.Context(), id) if err != nil { applogger.L().Errorf("Get widgets: %v", err) response.AbortWithStatusError(c, http.StatusNotFound, response.ErrNotFound, "dashboard app not found") return } response.OK(c, widgets) } // AddWidget adds a widget to a dashboard app's content array. // POST /api/v1/accounts/:account_id/dashboard_apps/:dashboard_app_id/widgets func (h *DashboardAppHandler) AddWidget(c *gin.Context) { id, err := parseUintParam(c, "dashboard_app_id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid dashboard_app_id") return } var req service.AddWidgetRequest if err := c.ShouldBindJSON(&req); err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error()) return } widgets, err := h.svc.AddWidget(c.Request.Context(), id, &req) if err != nil { applogger.L().Errorf("Add widget: %v", err) response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to add widget") return } response.OK(c, widgets) } // RemoveWidget removes a widget from a dashboard app's content array by index. // DELETE /api/v1/accounts/:account_id/dashboard_apps/:dashboard_app_id/widgets/:widget_index func (h *DashboardAppHandler) RemoveWidget(c *gin.Context) { id, err := parseUintParam(c, "dashboard_app_id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid dashboard_app_id") return } widgetIndex, err := parseUintParam(c, "widget_index") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid widget_index") return } widgets, err := h.svc.RemoveWidget(c.Request.Context(), id, int(widgetIndex)) if err != nil { applogger.L().Errorf("Remove widget: %v", err) response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to remove widget") return } response.OK(c, widgets) } // UpdateWidget updates a widget in a dashboard app's content array by index. // PUT /api/v1/accounts/:account_id/dashboard_apps/:dashboard_app_id/widgets/:widget_index // GoChat extension — Chatwoot has no widget sub-resource CRUD. func (h *DashboardAppHandler) UpdateWidget(c *gin.Context) { id, err := parseUintParam(c, "dashboard_app_id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid dashboard_app_id") return } widgetIndex, err := parseUintParam(c, "widget_index") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid widget_index") return } var req service.UpdateWidgetRequest if err := c.ShouldBindJSON(&req); err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error()) return } widgets, err := h.svc.UpdateWidget(c.Request.Context(), id, int(widgetIndex), &req) if err != nil { applogger.L().Errorf("Update widget: %v", err) response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to update widget") return } response.OK(c, widgets) }