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

272 lines
8.8 KiB
Go

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