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" ) // LinearIntegrationService implements Linear integration business logic. // Reference: Chatwoot Integrations::LinearController // Linear integration creates and links issues from conversations. type LinearIntegrationService struct { hookRepo *repository.IntegrationHookRepo } // NewLinearIntegrationService creates a new LinearIntegrationService. func NewLinearIntegrationService(hookRepo *repository.IntegrationHookRepo) *LinearIntegrationService { return &LinearIntegrationService{hookRepo: hookRepo} } // Delete removes a Linear integration hook for an account. func (s *LinearIntegrationService) Delete(ctx context.Context, accountID uint) error { hooks, err := s.hookRepo.FindByAccountAndType(ctx, accountID, model.HookTypeLinear) if err != nil || len(hooks) == 0 { return fmt.Errorf("Linear 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 Linear integration: %w", err) } } applogger.L().Infof("Linear integration deleted: account=%d", accountID) return nil } // GetTeams retrieves available Linear teams (proxy to Linear API). // GET /api/v1/accounts/:account_id/integrations/linear/teams func (s *LinearIntegrationService) GetTeams(ctx context.Context, accountID uint) ([]map[string]interface{}, error) { hooks, err := s.hookRepo.FindByAccountAndType(ctx, accountID, model.HookTypeLinear) if err != nil || len(hooks) == 0 { return nil, fmt.Errorf("Linear integration not found for account %d", accountID) } var settings model.LinearSettings if err := json.Unmarshal(hooks[0].Settings, &settings); err != nil { return nil, fmt.Errorf("failed to unmarshal Linear settings: %w", err) } // In production, this would call the Linear API. applogger.L().Infof("Listing Linear teams for account=%d", accountID) return []map[string]interface{}{ {"id": settings.TeamID, "name": settings.TeamName}, }, nil } // GetTeamEntities retrieves entities from a Linear team. // GET /api/v1/accounts/:account_id/integrations/linear/team_entities func (s *LinearIntegrationService) GetTeamEntities(ctx context.Context, accountID uint) ([]map[string]interface{}, error) { hooks, err := s.hookRepo.FindByAccountAndType(ctx, accountID, model.HookTypeLinear) if err != nil || len(hooks) == 0 { return nil, fmt.Errorf("Linear integration not found for account %d", accountID) } applogger.L().Infof("Listing Linear team entities for account=%d", accountID) return []map[string]interface{}{}, nil } // CreateIssueRequest is the DTO for creating a Linear issue. type CreateIssueRequest struct { Title string `json:"title" validate:"required"` Description string `json:"description,omitempty"` TeamID string `json:"team_id,omitempty"` } // CreateIssue creates a Linear issue from a conversation. // POST /api/v1/accounts/:account_id/integrations/linear/create_issue func (s *LinearIntegrationService) CreateIssue(ctx context.Context, accountID uint, req CreateIssueRequest) (map[string]interface{}, error) { hooks, err := s.hookRepo.FindByAccountAndType(ctx, accountID, model.HookTypeLinear) if err != nil || len(hooks) == 0 { return nil, fmt.Errorf("Linear integration not found for account %d", accountID) } applogger.L().Infof("Creating Linear issue for account=%d: title=%s", accountID, req.Title) // In production, this would call the Linear API to create an issue. return map[string]interface{}{ "status": "created", "title": req.Title, "team_id": req.TeamID, }, nil } // LinkIssueRequest is the DTO for linking a Linear issue to a conversation. type LinkIssueRequest struct { IssueID string `json:"issue_id" validate:"required"` ConversationID uint `json:"conversation_id,omitempty"` } // LinkIssue links a Linear issue to a conversation. // POST /api/v1/accounts/:account_id/integrations/linear/link_issue func (s *LinearIntegrationService) LinkIssue(ctx context.Context, accountID uint, req LinkIssueRequest) (map[string]interface{}, error) { hooks, err := s.hookRepo.FindByAccountAndType(ctx, accountID, model.HookTypeLinear) if err != nil || len(hooks) == 0 { return nil, fmt.Errorf("Linear integration not found for account %d", accountID) } applogger.L().Infof("Linking Linear issue %s for account=%d", req.IssueID, accountID) return map[string]interface{}{ "status": "linked", "issue_id": req.IssueID, }, nil } // UnlinkIssueRequest is the DTO for unlinking a Linear issue from a conversation. type UnlinkIssueRequest struct { IssueID string `json:"issue_id" validate:"required"` ConversationID uint `json:"conversation_id,omitempty"` } // UnlinkIssue unlinks a Linear issue from a conversation. // POST /api/v1/accounts/:account_id/integrations/linear/unlink_issue func (s *LinearIntegrationService) UnlinkIssue(ctx context.Context, accountID uint, req UnlinkIssueRequest) (map[string]interface{}, error) { hooks, err := s.hookRepo.FindByAccountAndType(ctx, accountID, model.HookTypeLinear) if err != nil || len(hooks) == 0 { return nil, fmt.Errorf("Linear integration not found for account %d", accountID) } applogger.L().Infof("Unlinking Linear issue %s for account=%d", req.IssueID, accountID) return map[string]interface{}{ "status": "unlinked", "issue_id": req.IssueID, }, nil } // SearchIssue searches Linear issues. // GET /api/v1/accounts/:account_id/integrations/linear/search_issue func (s *LinearIntegrationService) SearchIssue(ctx context.Context, accountID uint, query string) ([]map[string]interface{}, error) { hooks, err := s.hookRepo.FindByAccountAndType(ctx, accountID, model.HookTypeLinear) if err != nil || len(hooks) == 0 { return nil, fmt.Errorf("Linear integration not found for account %d", accountID) } applogger.L().Infof("Searching Linear issues for account=%d, query=%s", accountID, query) return []map[string]interface{}{}, nil } // GetLinkedIssues retrieves Linear issues linked to a conversation. // GET /api/v1/accounts/:account_id/integrations/linear/linked_issues func (s *LinearIntegrationService) GetLinkedIssues(ctx context.Context, accountID uint, conversationID uint) ([]map[string]interface{}, error) { hooks, err := s.hookRepo.FindByAccountAndType(ctx, accountID, model.HookTypeLinear) if err != nil || len(hooks) == 0 { return nil, fmt.Errorf("Linear integration not found for account %d", accountID) } applogger.L().Infof("Listing linked Linear issues for account=%d, conversation=%d", accountID, conversationID) return []map[string]interface{}{}, nil } // ---- Notion Integration Service ---- // NotionIntegrationService implements Notion integration business logic. // Reference: Chatwoot Integrations::NotionController // Notion integration creates notes/pages from conversations. type NotionIntegrationService struct { hookRepo *repository.IntegrationHookRepo } // NewNotionIntegrationService creates a new NotionIntegrationService. func NewNotionIntegrationService(hookRepo *repository.IntegrationHookRepo) *NotionIntegrationService { return &NotionIntegrationService{hookRepo: hookRepo} } // Delete removes a Notion integration hook for an account. func (s *NotionIntegrationService) Delete(ctx context.Context, accountID uint) error { hooks, err := s.hookRepo.FindByAccountAndType(ctx, accountID, model.HookTypeNotion) if err != nil || len(hooks) == 0 { return fmt.Errorf("Notion 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 Notion integration: %w", err) } } applogger.L().Infof("Notion integration deleted: account=%d", accountID) return nil }