Files
gochat/internal/service/slack_integration_service.go
T
2026-06-04 15:44:48 +08:00

147 lines
5.0 KiB
Go

package service
import (
"context"
"encoding/json"
"fmt"
"github.com/gochat/gochat/internal/model"
"github.com/gochat/gochat/internal/repository"
applogger "github.com/gochat/gochat/pkg/logger"
)
// SlackIntegrationService implements Slack integration business logic.
// Reference: Chatwoot Integrations::SlackController + SlackService
// Slack integration sends conversation notifications to a Slack channel and
// supports Slack slash commands for ticket creation.
type SlackIntegrationService struct {
hookRepo *repository.IntegrationHookRepo
}
// NewSlackIntegrationService creates a new SlackIntegrationService.
func NewSlackIntegrationService(hookRepo *repository.IntegrationHookRepo) *SlackIntegrationService {
return &SlackIntegrationService{hookRepo: hookRepo}
}
// CreateSlackRequest is the DTO for creating/updating a Slack integration.
// Reference: Chatwoot SlackController#create — params: {channel_id, channel_name, slack_token}
type CreateSlackRequest struct {
ChannelID string `json:"channel_id" validate:"required"`
ChannelName string `json:"channel_name,omitempty"`
SlackToken string `json:"slack_token,omitempty"`
}
// UpdateSlackRequest is the DTO for updating a Slack integration.
type UpdateSlackRequest struct {
ChannelID string `json:"channel_id,omitempty"`
ChannelName string `json:"channel_name,omitempty"`
SlackToken string `json:"slack_token,omitempty"`
}
// Create creates a Slack integration hook for an account.
func (s *SlackIntegrationService) Create(ctx context.Context, accountID uint, req CreateSlackRequest) (*model.IntegrationHook, error) {
settings := model.SlackSettings{
ChannelID: req.ChannelID,
ChannelName: req.ChannelName,
SlackToken: req.SlackToken,
}
settingsJSON, err := json.Marshal(settings)
if err != nil {
return nil, fmt.Errorf("failed to marshal Slack settings: %w", err)
}
hook := &model.IntegrationHook{
AccountID: accountID,
HookType: model.HookTypeSlack,
Status: model.HookStatusActive,
Settings: settingsJSON,
}
if err := s.hookRepo.Create(ctx, hook); err != nil {
return nil, fmt.Errorf("failed to create Slack integration: %w", err)
}
applogger.L().Infof("Slack integration created: account=%d, channel=%s", accountID, req.ChannelID)
return hook, nil
}
// Update updates a Slack integration hook.
func (s *SlackIntegrationService) Update(ctx context.Context, accountID uint, req UpdateSlackRequest) (*model.IntegrationHook, error) {
hooks, err := s.hookRepo.FindByAccountAndType(ctx, accountID, model.HookTypeSlack)
if err != nil || len(hooks) == 0 {
return nil, fmt.Errorf("Slack integration not found for account %d", accountID)
}
hook := &hooks[0]
// Update settings
var settings model.SlackSettings
if hook.Settings != nil {
if err := json.Unmarshal(hook.Settings, &settings); err != nil {
return nil, fmt.Errorf("failed to unmarshal existing Slack settings: %w", err)
}
}
if req.ChannelID != "" {
settings.ChannelID = req.ChannelID
}
if req.ChannelName != "" {
settings.ChannelName = req.ChannelName
}
if req.SlackToken != "" {
settings.SlackToken = req.SlackToken
}
settingsJSON, err := json.Marshal(settings)
if err != nil {
return nil, fmt.Errorf("failed to marshal Slack settings: %w", err)
}
hook.Settings = settingsJSON
if err := s.hookRepo.Update(ctx, hook); err != nil {
return nil, fmt.Errorf("failed to update Slack integration: %w", err)
}
applogger.L().Infof("Slack integration updated: account=%d", accountID)
return hook, nil
}
// Delete removes a Slack integration hook for an account.
func (s *SlackIntegrationService) Delete(ctx context.Context, accountID uint) error {
hooks, err := s.hookRepo.FindByAccountAndType(ctx, accountID, model.HookTypeSlack)
if err != nil || len(hooks) == 0 {
return fmt.Errorf("Slack integration not found for account %d", accountID)
}
for _, hook := range hooks {
if err := s.hookRepo.Delete(ctx, hook.ID); err != nil {
return fmt.Errorf("failed to delete Slack integration: %w", err)
}
}
applogger.L().Infof("Slack integration deleted: account=%d", accountID)
return nil
}
// ListAllChannels returns available Slack channels (proxy to Slack API).
// GET /api/v1/accounts/:account_id/integrations/slack/list_all_channels
func (s *SlackIntegrationService) ListAllChannels(ctx context.Context, accountID uint) ([]map[string]interface{}, error) {
hooks, err := s.hookRepo.FindByAccountAndType(ctx, accountID, model.HookTypeSlack)
if err != nil || len(hooks) == 0 {
return nil, fmt.Errorf("Slack integration not found for account %d", accountID)
}
var settings model.SlackSettings
if err := json.Unmarshal(hooks[0].Settings, &settings); err != nil {
return nil, fmt.Errorf("failed to unmarshal Slack settings: %w", err)
}
// In production, this would call the Slack API to list channels using settings.SlackToken.
// Production note: when Slack OAuth is wired, this returns real channel list from Slack API.
applogger.L().Infof("Listing Slack channels for account=%d", accountID)
return []map[string]interface{}{
{"id": settings.ChannelID, "name": settings.ChannelName},
}, nil
}