feat: make chat-channel and implement WhatsApp bot (#17518)

This commit is contained in:
Haruko386
2026-07-29 18:55:22 +08:00
committed by GitHub
parent d5604f8947
commit c7b1b3a4d2
21 changed files with 1899 additions and 60 deletions

View File

@@ -30,6 +30,7 @@ type ChatChannelService interface {
List(ctx context.Context, tenantID string) ([]*entity.ChatChannelListResponse, error)
GetChatChannel(ctx context.Context, userID, channelID string) (*entity.ChatChannel, common.ErrorCode, error)
UpdateChatChannel(ctx context.Context, userID, channelID string, req map[string]interface{}) (*entity.ChatChannel, common.ErrorCode, error)
GetChatChannelRuntime(ctx context.Context, userID, channelID string) (map[string]any, common.ErrorCode, error)
DeleteChatChannel(ctx context.Context, userID, channelID string) (bool, common.ErrorCode, error)
}
@@ -49,7 +50,7 @@ func NewChatChannel() *ChatChannelHandler {
type CreateChatChannelRequest struct {
Name string `json:"name" binding:"required"`
Channel string `json:"channel" binding:"required"`
Config entity.JSONMap `json:"config" binding:"required"`
Config entity.JSONMap `json:"config"`
ChatID *string `json:"chat_id"`
}
@@ -66,6 +67,9 @@ func (h *ChatChannelHandler) CreateChatChannel(c *gin.Context) {
common.ResponseWithCodeData(c, common.CodeDataError, nil, "Invalid request: "+err.Error())
return
}
if req.Config == nil {
req.Config = entity.JSONMap{}
}
ctx := c.Request.Context()
@@ -167,6 +171,35 @@ func (h *ChatChannelHandler) UpdateChatChannel(c *gin.Context) {
common.SuccessWithData(c, result, "success")
}
// GetChatChannelRuntime returns live runtime metadata for a running chat channel.
func (h *ChatChannelHandler) GetChatChannelRuntime(c *gin.Context) {
user, errorCode, errorMessage := GetUser(c)
if errorCode != common.CodeSuccess {
common.ErrorWithCode(c, errorCode, errorMessage)
return
}
userID := strings.TrimSpace(user.ID)
if userID == "" {
common.ResponseWithCodeData(c, common.CodeArgumentError, nil, "user_id is required")
return
}
channelID := strings.TrimSpace(c.Param("channel_id"))
if channelID == "" {
common.ResponseWithCodeData(c, common.CodeArgumentError, nil, "channel_id is required")
return
}
ctx := c.Request.Context()
result, code, err := h.chatChannelService.GetChatChannelRuntime(ctx, userID, channelID)
if code != common.CodeSuccess || err != nil {
writeChatChannelError(c, code, chatChannelErrMsg(code, err))
return
}
common.SuccessWithData(c, result, "success")
}
// DeleteChatChannel handles DELETE /chat-channels/:channel_id.
func (h *ChatChannelHandler) DeleteChatChannel(c *gin.Context) {
user, errorCode, errorMessage := GetUser(c)

View File

@@ -16,11 +16,12 @@ import (
)
type fakeChatChannelService struct {
createFn func(tenantID, name, channelType string, config entity.JSONMap, chatID *string) (*entity.ChatChannel, error)
listFn func(tenantID string) ([]*entity.ChatChannelListResponse, error)
getFn func(userID, channelID string) (*entity.ChatChannel, common.ErrorCode, error)
updateFn func(userID, channelID string, req map[string]interface{}) (*entity.ChatChannel, common.ErrorCode, error)
deleteFn func(userID, channelID string) (bool, common.ErrorCode, error)
createFn func(tenantID, name, channelType string, config entity.JSONMap, chatID *string) (*entity.ChatChannel, error)
listFn func(tenantID string) ([]*entity.ChatChannelListResponse, error)
getFn func(userID, channelID string) (*entity.ChatChannel, common.ErrorCode, error)
updateFn func(userID, channelID string, req map[string]interface{}) (*entity.ChatChannel, common.ErrorCode, error)
runtimeFn func(userID, channelID string) (map[string]any, common.ErrorCode, error)
deleteFn func(userID, channelID string) (bool, common.ErrorCode, error)
}
func (f fakeChatChannelService) CreateChatChannel(ctx context.Context, tenantID, name, channelType string, config entity.JSONMap, chatID *string) (*entity.ChatChannel, error) {
@@ -51,6 +52,13 @@ func (f fakeChatChannelService) UpdateChatChannel(ctx context.Context, userID, c
return f.updateFn(userID, channelID, req)
}
func (f fakeChatChannelService) GetChatChannelRuntime(ctx context.Context, userID, channelID string) (map[string]any, common.ErrorCode, error) {
if f.runtimeFn == nil {
return nil, common.CodeServerError, errors.New("unexpected GetChatChannelRuntime call")
}
return f.runtimeFn(userID, channelID)
}
func (f fakeChatChannelService) DeleteChatChannel(ctx context.Context, userID, channelID string) (bool, common.ErrorCode, error) {
if f.deleteFn == nil {
return false, common.CodeServerError, errors.New("unexpected DeleteChatChannel call")
@@ -327,3 +335,44 @@ func TestChatChannelHandlerDeleteChatChannelSuccess(t *testing.T) {
t.Fatalf("payload=%v", payload)
}
}
func TestChatChannelHandlerGetRuntimeSuccess(t *testing.T) {
gin.SetMode(gin.TestMode)
var gotUserID, gotChannelID string
h := &ChatChannelHandler{
chatChannelService: fakeChatChannelService{
runtimeFn: func(userID, channelID string) (map[string]any, common.ErrorCode, error) {
gotUserID = userID
gotChannelID = channelID
return map[string]any{"status": "waiting"}, common.CodeSuccess, nil
},
},
}
router := gin.New()
router.GET("/api/v1/chat-channels/:channel_id/runtime", func(c *gin.Context) {
c.Set("user", &entity.User{ID: "tenant-1"})
h.GetChatChannelRuntime(c)
})
resp := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/api/v1/chat-channels/cc-1/runtime", nil)
router.ServeHTTP(resp, req)
if gotUserID != "tenant-1" || gotChannelID != "cc-1" {
t.Fatalf("userID=%q channelID=%q", gotUserID, gotChannelID)
}
var payload map[string]interface{}
if err := json.Unmarshal(resp.Body.Bytes(), &payload); err != nil {
t.Fatalf("unmarshal response: %v", err)
}
if payload["code"] != float64(common.CodeSuccess) {
t.Fatalf("payload=%v", payload)
}
data, _ := payload["data"].(map[string]interface{})
if data["status"] != "waiting" {
t.Fatalf("payload=%v", payload)
}
}