package service import ( "context" "fmt" channelmodel "github.com/gochat/gochat/internal/model/channel" "github.com/gochat/gochat/internal/repository" applogger "github.com/gochat/gochat/pkg/logger" ) // ChannelFacebookService implements business logic for Facebook channel operations. // Reference: Chatwoot app/controllers/api/v1/accounts/channels/facebook_controller.rb // // Facebook Messenger channels use the Facebook Graph API to send/receive messages // via a connected Facebook Page. The Page access token grants pages_messaging // permission for Messenger interactions. type ChannelFacebookService struct { repo *repository.ChannelFacebookRepo } // NewChannelFacebookService creates a new ChannelFacebook service. func NewChannelFacebookService(repo *repository.ChannelFacebookRepo) *ChannelFacebookService { return &ChannelFacebookService{repo: repo} } // GetByID retrieves a Facebook channel by ID. func (s *ChannelFacebookService) GetByID(ctx context.Context, id uint) (*channelmodel.ChannelFacebook, error) { return s.repo.FindByID(ctx, id) } // GetByInboxID retrieves a Facebook channel by inbox ID. func (s *ChannelFacebookService) GetByInboxID(ctx context.Context, inboxID uint) (*channelmodel.ChannelFacebook, error) { return s.repo.FindByInboxID(ctx, inboxID) } // GetByAccountAndInboxID retrieves a Facebook channel scoped to an account and inbox. func (s *ChannelFacebookService) GetByAccountAndInboxID(ctx context.Context, accountID, inboxID uint) (*channelmodel.ChannelFacebook, error) { return s.repo.FindByAccountAndInboxID(ctx, accountID, inboxID) } // FindByPageID retrieves a Facebook channel by Facebook Page ID. // Used by the webhook handler to route incoming Facebook messages. func (s *ChannelFacebookService) FindByPageID(ctx context.Context, pageID string) (*channelmodel.ChannelFacebook, error) { return s.repo.FindByPageID(ctx, pageID) } // UpdateFacebookChannelRequest is the DTO for updating Facebook channel config. type UpdateFacebookChannelRequest struct { PageName *string `json:"page_name,omitempty"` ReauthorizationRequired *bool `json:"reauthorization_required,omitempty"` } // Update modifies an existing Facebook channel's configurable fields. func (s *ChannelFacebookService) Update(ctx context.Context, accountID, inboxID uint, req UpdateFacebookChannelRequest) (*channelmodel.ChannelFacebook, error) { ch, err := s.repo.FindByAccountAndInboxID(ctx, accountID, inboxID) if err != nil { return nil, fmt.Errorf("facebook channel not found: %w", err) } if req.PageName != nil { ch.PageName = *req.PageName } if req.ReauthorizationRequired != nil { ch.ReauthorizationRequired = *req.ReauthorizationRequired } if err := s.repo.Update(ctx, ch); err != nil { applogger.L().Errorf("Failed to update facebook channel: %v", err) return nil, err } return ch, nil } // MarkReauthorizationRequired flags a Facebook channel as needing token refresh. // Called when the Facebook webhook delivers a token expiration event. func (s *ChannelFacebookService) MarkReauthorizationRequired(ctx context.Context, inboxID uint) error { ch, err := s.repo.FindByInboxID(ctx, inboxID) if err != nil { return fmt.Errorf("facebook channel not found: %w", err) } ch.ReauthorizationRequired = true if err := s.repo.Update(ctx, ch); err != nil { applogger.L().Errorf("Failed to mark facebook channel for reauthorization: %v", err) return err } applogger.L().Infof("Facebook channel marked for reauthorization (inbox_id=%d)", inboxID) return nil } // ListByAccount retrieves all Facebook channels for an account. func (s *ChannelFacebookService) ListByAccount(ctx context.Context, accountID uint) ([]channelmodel.ChannelFacebook, error) { return s.repo.ListByAccount(ctx, accountID) } // Delete soft-deletes a Facebook channel record. // Note: This should be called BEFORE deleting the Inbox (same pattern as Chatwoot's after_destroy). func (s *ChannelFacebookService) Delete(ctx context.Context, id uint) error { if err := s.repo.Delete(ctx, id); err != nil { applogger.L().Errorf("Failed to delete facebook channel: %v", err) return err } applogger.L().Infof("Facebook channel deleted (id=%d)", id) return nil }