Files
gochat/internal/handler/api/v1/dashboard_app_handler.go
T
2026-06-04 15:44:48 +08:00

300 lines
10 KiB
Go

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