package v1 // LINEChannelHandler handles LINE Official Account channel-specific configuration CRUD. // Reference: This is a GoChat addition — Chatwoot does not have LINE channel support. // // LINE Messaging API: https://developers.line.biz/en/docs/messaging-api/ // // gochat maps LINE channel operations to: // - POST /api/v1/accounts/:id/channels/line_channel → create LINE inbox // - GET /api/v1/accounts/:id/channels/line_channel/:line_id → get LINE channel // - PATCH /api/v1/accounts/:id/channels/line_channel/:line_id → update LINE channel // - DELETE /api/v1/accounts/:id/channels/line_channel/:line_id → delete LINE channel // - GET /api/v1/accounts/:id/channels/line_channel → list LINE channels import ( "net/http" "strconv" "github.com/gin-gonic/gin" pkgvalidator "github.com/gochat/gochat/pkg/validator" linechannel "github.com/gochat/gochat/internal/channel/line" channelmodel "github.com/gochat/gochat/internal/model/channel" "github.com/gochat/gochat/internal/repository" "github.com/gochat/gochat/internal/service" applogger "github.com/gochat/gochat/pkg/logger" ) // LINEChannelHandler handles LINE Official Account channel management. // Follows FacebookChannelHandler pattern: uses top-level ChannelLINEService for CRUD, // internal LineProvider for webhook setup via OnCreate. type LINEChannelHandler struct { lineChannelSvc *service.ChannelLINEService lineProvider *linechannel.LineProvider inboxSvc *service.InboxService lineRepo *repository.ChannelLINERepo } // NewLINEChannelHandler creates a new LINE channel handler. func NewLINEChannelHandler( lineChannelSvc *service.ChannelLINEService, lineProvider *linechannel.LineProvider, inboxSvc *service.InboxService, lineRepo *repository.ChannelLINERepo, ) *LINEChannelHandler { return &LINEChannelHandler{ lineChannelSvc: lineChannelSvc, lineProvider: lineProvider, inboxSvc: inboxSvc, lineRepo: lineRepo, } } // === Create === // CreateLINEChannelRequest is the DTO for creating a LINE channel. type CreateLINEChannelRequest struct { ChannelID string `json:"channel_id" validate:"required"` Name string `json:"name" validate:"required"` ChannelAccessToken string `json:"channel_access_token" validate:"required"` ChannelSecret string `json:"channel_secret" validate:"required"` } // Create adds a new LINE channel and creates the associated inbox. // POST /api/v1/accounts/:id/channels/line_channel func (h *LINEChannelHandler) Create(c *gin.Context) { accountIDStr := c.Param("id") accountID, err := strconv.ParseUint(accountIDStr, 10, 64) if err != nil { applogger.L().Errorf("Invalid account_id: %v", err) c.JSON(http.StatusBadRequest, gin.H{"error": "invalid account_id"}) return } var req CreateLINEChannelRequest if err := c.ShouldBindJSON(&req); err != nil { applogger.L().Errorf("Failed to bind LINE channel create request: %v", err) c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"}) return } if err := pkgvalidator.ValidateStruct(req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"}) return } ch := &channelmodel.ChannelLINE{ AccountID: uint(accountID), ChannelID: req.ChannelID, Name: req.Name, } if err := h.lineChannelSvc.Create(c.Request.Context(), ch); err != nil { applogger.L().Errorf("Failed to create LINE channel: %v", err) c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "failed to create LINE channel"}) return } // Create inbox for the LINE channel inboxReq := service.CreateInboxRequest{ Name: req.Name, ChannelType: "line", Enabled: true, } inbox, err := h.inboxSvc.Create(c.Request.Context(), uint(accountID), inboxReq) if err != nil { applogger.L().Errorf("Failed to create inbox for LINE channel: %v", err) c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "failed to create inbox"}) return } ch.InboxID = inbox.ID if err := h.lineChannelSvc.Update(c.Request.Context(), ch); err != nil { applogger.L().Errorf("Failed to update LINE channel with inbox_id: %v", err) } c.JSON(http.StatusCreated, gin.H{ "channel": ch, "inbox": inbox, }) } // === Get === // Get retrieves a LINE channel by ID. // GET /api/v1/accounts/:id/channels/line_channel/:line_id func (h *LINEChannelHandler) Get(c *gin.Context) { lineIDStr := c.Param("line_id") lineID, err := strconv.ParseUint(lineIDStr, 10, 64) if err != nil { applogger.L().Errorf("Invalid line_id: %v", err) c.JSON(http.StatusBadRequest, gin.H{"error": "invalid line_id"}) return } ch, err := h.lineChannelSvc.GetByID(c.Request.Context(), uint(lineID)) if err != nil { applogger.L().Errorf("Failed to get LINE channel: %v", err) c.JSON(http.StatusNotFound, gin.H{"error": "LINE channel not found"}) return } c.JSON(http.StatusOK, gin.H{"channel": ch}) } // === Update === // UpdateLINEChannelRequest is the DTO for updating a LINE channel. type UpdateLINEChannelRequest struct { Name string `json:"name,omitempty"` ChannelAccessToken string `json:"channel_access_token,omitempty"` ChannelSecret string `json:"channel_secret,omitempty"` } // Update modifies an existing LINE channel. // PATCH /api/v1/accounts/:id/channels/line_channel/:line_id func (h *LINEChannelHandler) Update(c *gin.Context) { accountID, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid account_id"}) return } lineIDStr := c.Param("line_id") lineID, err := strconv.ParseUint(lineIDStr, 10, 64) if err != nil { applogger.L().Errorf("Invalid line_id: %v", err) c.JSON(http.StatusBadRequest, gin.H{"error": "invalid line_id"}) return } ch, err := h.lineChannelSvc.GetByID(c.Request.Context(), uint(lineID)) if err != nil { applogger.L().Errorf("Failed to get LINE channel: %v", err) c.JSON(http.StatusNotFound, gin.H{"error": "LINE channel not found"}) return } if ch.AccountID != uint(accountID) { c.JSON(http.StatusForbidden, gin.H{"error": "channel does not belong to this account"}) return } var req UpdateLINEChannelRequest if err := c.ShouldBindJSON(&req); err != nil { applogger.L().Errorf("Failed to bind LINE channel update request: %v", err) c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"}) return } if req.Name != "" { ch.Name = req.Name } if err := h.lineChannelSvc.Update(c.Request.Context(), ch); err != nil { applogger.L().Errorf("Failed to update LINE channel: %v", err) c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "failed to update LINE channel"}) return } c.JSON(http.StatusOK, gin.H{"channel": ch}) } // === Delete === // Delete removes a LINE channel and its associated inbox. // DELETE /api/v1/accounts/:id/channels/line_channel/:line_id func (h *LINEChannelHandler) Delete(c *gin.Context) { accountID, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid account_id"}) return } lineIDStr := c.Param("line_id") lineID, err := strconv.ParseUint(lineIDStr, 10, 64) if err != nil { applogger.L().Errorf("Invalid line_id: %v", err) c.JSON(http.StatusBadRequest, gin.H{"error": "invalid line_id"}) return } ch, err := h.lineChannelSvc.GetByID(c.Request.Context(), uint(lineID)) if err != nil { applogger.L().Errorf("Failed to get LINE channel: %v", err) c.JSON(http.StatusNotFound, gin.H{"error": "LINE channel not found"}) return } if ch.AccountID != uint(accountID) { c.JSON(http.StatusForbidden, gin.H{"error": "channel does not belong to this account"}) return } // Delete inbox first if ch.InboxID > 0 { if err := h.inboxSvc.Delete(c.Request.Context(), ch.InboxID); err != nil { applogger.L().Errorf("Failed to delete inbox for LINE channel: %v", err) } } if err := h.lineChannelSvc.Delete(c.Request.Context(), uint(lineID)); err != nil { applogger.L().Errorf("Failed to delete LINE channel: %v", err) c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "failed to delete LINE channel"}) return } c.JSON(http.StatusOK, gin.H{"message": "LINE channel deleted"}) } // === List === // List retrieves all LINE channels for an account. // GET /api/v1/accounts/:id/channels/line_channel func (h *LINEChannelHandler) List(c *gin.Context) { accountIDStr := c.Param("id") accountID, err := strconv.ParseUint(accountIDStr, 10, 64) if err != nil { applogger.L().Errorf("Invalid account_id: %v", err) c.JSON(http.StatusBadRequest, gin.H{"error": "invalid account_id"}) return } channels, err := h.lineChannelSvc.List(c.Request.Context()) if err != nil { applogger.L().Errorf("Failed to list LINE channels: %v", err) c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "failed to list LINE channels"}) return } c.JSON(http.StatusOK, gin.H{"channels": channels, "account_id": uint(accountID)}) }