238 lines
8.3 KiB
Go
238 lines
8.3 KiB
Go
package facebook
|
|
|
|
// Repository provides GORM-based persistence for Facebook and Instagram channel data.
|
|
// Reference: Chatwoot ActiveRecord for ChannelFacebook + ChannelInstagram with callbacks:
|
|
// - ChannelFacebook: before_validation :ensure_valid_page_token, after_destroy :delete_facebook_page
|
|
// - ChannelInstagram: similar lifecycle hooks via connected FB Page
|
|
//
|
|
// Design: Repository pattern abstracts GORM operations, matching gochat's
|
|
// existing repository pattern used by Telegram and other channel providers.
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
channelmodel "github.com/gochat/gochat/internal/model/channel"
|
|
|
|
"gorm.io/gorm"
|
|
applogger "github.com/gochat/gochat/pkg/logger"
|
|
)
|
|
|
|
// Repository handles Facebook/Instagram channel database operations.
|
|
type Repository struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
// NewRepository creates a Facebook/Instagram repository with GORM DB.
|
|
func NewRepository(db *gorm.DB) *Repository {
|
|
return &Repository{db: db}
|
|
}
|
|
|
|
// === Facebook Channel CRUD ===
|
|
|
|
// CreateFacebook persists a new ChannelFacebook record.
|
|
func (r *Repository) CreateFacebook(ctx context.Context, channel *channelmodel.ChannelFacebook) error {
|
|
if err := r.db.WithContext(ctx).Create(channel).Error; err != nil {
|
|
return fmt.Errorf("failed to create ChannelFacebook: %w", err)
|
|
}
|
|
|
|
applogger.L().Info("ChannelFacebook created",
|
|
"id", channel.ID,
|
|
"account_id", channel.AccountID,
|
|
"page_id", channel.PageID,
|
|
"page_name", channel.PageName,
|
|
)
|
|
return nil
|
|
}
|
|
|
|
// UpdateFacebook modifies a ChannelFacebook record.
|
|
func (r *Repository) UpdateFacebook(ctx context.Context, channel *channelmodel.ChannelFacebook) error {
|
|
result := r.db.WithContext(ctx).Save(channel)
|
|
if result.Error != nil {
|
|
return fmt.Errorf("failed to update ChannelFacebook: %w", result.Error)
|
|
}
|
|
|
|
if result.RowsAffected == 0 {
|
|
return fmt.Errorf("ChannelFacebook not found (id=%d)", channel.ID)
|
|
}
|
|
|
|
applogger.L().Info("ChannelFacebook updated", "id", channel.ID)
|
|
return nil
|
|
}
|
|
|
|
// UpdateFacebookPageAccessToken updates only the page_access_token field.
|
|
func (r *Repository) UpdateFacebookPageAccessToken(ctx context.Context, channelID uint, newToken string) error {
|
|
result := r.db.WithContext(ctx).
|
|
Model(&channelmodel.ChannelFacebook{}).
|
|
Where("id = ?", channelID).
|
|
Update("page_access_token", newToken)
|
|
|
|
if result.Error != nil {
|
|
return fmt.Errorf("failed to update page_access_token: %w", result.Error)
|
|
}
|
|
|
|
if result.RowsAffected == 0 {
|
|
return fmt.Errorf("ChannelFacebook not found (id=%d)", channelID)
|
|
}
|
|
|
|
applogger.L().Info("ChannelFacebook page_access_token updated", "id", channelID)
|
|
return nil
|
|
}
|
|
|
|
// GetFacebookByID retrieves a ChannelFacebook by its primary key.
|
|
func (r *Repository) GetFacebookByID(ctx context.Context, id uint) (*channelmodel.ChannelFacebook, error) {
|
|
var channel channelmodel.ChannelFacebook
|
|
if err := r.db.WithContext(ctx).First(&channel, id).Error; err != nil {
|
|
return nil, fmt.Errorf("failed to get ChannelFacebook by id: %w", err)
|
|
}
|
|
return &channel, nil
|
|
}
|
|
|
|
// GetFacebookByPageID retrieves a ChannelFacebook by its Facebook Page ID.
|
|
func (r *Repository) GetFacebookByPageID(ctx context.Context, pageID string) (*channelmodel.ChannelFacebook, error) {
|
|
var channel channelmodel.ChannelFacebook
|
|
if err := r.db.WithContext(ctx).
|
|
Where("page_id = ?", pageID).
|
|
First(&channel).Error; err != nil {
|
|
return nil, fmt.Errorf("failed to get ChannelFacebook by page_id: %w", err)
|
|
}
|
|
return &channel, nil
|
|
}
|
|
|
|
// DeleteFacebook removes a ChannelFacebook record.
|
|
func (r *Repository) DeleteFacebook(ctx context.Context, id uint) error {
|
|
result := r.db.WithContext(ctx).Delete(&channelmodel.ChannelFacebook{}, id)
|
|
if result.Error != nil {
|
|
return fmt.Errorf("failed to delete ChannelFacebook: %w", result.Error)
|
|
}
|
|
|
|
if result.RowsAffected == 0 {
|
|
return fmt.Errorf("ChannelFacebook not found (id=%d)", id)
|
|
}
|
|
|
|
applogger.L().Info("ChannelFacebook deleted", "id", id)
|
|
return nil
|
|
}
|
|
|
|
// === Instagram Channel CRUD ===
|
|
|
|
// CreateInstagram persists a new ChannelInstagram record.
|
|
func (r *Repository) CreateInstagram(ctx context.Context, channel *channelmodel.ChannelInstagram) error {
|
|
if err := r.db.WithContext(ctx).Create(channel).Error; err != nil {
|
|
return fmt.Errorf("failed to create ChannelInstagram: %w", err)
|
|
}
|
|
|
|
applogger.L().Info("ChannelInstagram created",
|
|
"id", channel.ID,
|
|
"account_id", channel.AccountID,
|
|
"ig_account_id", channel.InstagramAccountID,
|
|
"ig_username", channel.InstagramAccountName,
|
|
)
|
|
return nil
|
|
}
|
|
|
|
// UpdateInstagram modifies a ChannelInstagram record.
|
|
func (r *Repository) UpdateInstagram(ctx context.Context, channel *channelmodel.ChannelInstagram) error {
|
|
result := r.db.WithContext(ctx).Save(channel)
|
|
if result.Error != nil {
|
|
return fmt.Errorf("failed to update ChannelInstagram: %w", result.Error)
|
|
}
|
|
|
|
if result.RowsAffected == 0 {
|
|
return fmt.Errorf("ChannelInstagram not found (id=%d)", channel.ID)
|
|
}
|
|
|
|
applogger.L().Info("ChannelInstagram updated", "id", channel.ID)
|
|
return nil
|
|
}
|
|
|
|
// UpdateInstagramPageAccessToken updates only the page_access_token field.
|
|
func (r *Repository) UpdateInstagramPageAccessToken(ctx context.Context, channelID uint, newToken string) error {
|
|
result := r.db.WithContext(ctx).
|
|
Model(&channelmodel.ChannelInstagram{}).
|
|
Where("id = ?", channelID).
|
|
Update("page_access_token", newToken)
|
|
|
|
if result.Error != nil {
|
|
return fmt.Errorf("failed to update page_access_token: %w", result.Error)
|
|
}
|
|
|
|
if result.RowsAffected == 0 {
|
|
return fmt.Errorf("ChannelInstagram not found (id=%d)", channelID)
|
|
}
|
|
|
|
applogger.L().Info("ChannelInstagram page_access_token updated", "id", channelID)
|
|
return nil
|
|
}
|
|
|
|
// GetInstagramByID retrieves a ChannelInstagram by its primary key.
|
|
func (r *Repository) GetInstagramByID(ctx context.Context, id uint) (*channelmodel.ChannelInstagram, error) {
|
|
var channel channelmodel.ChannelInstagram
|
|
if err := r.db.WithContext(ctx).First(&channel, id).Error; err != nil {
|
|
return nil, fmt.Errorf("failed to get ChannelInstagram by id: %w", err)
|
|
}
|
|
return &channel, nil
|
|
}
|
|
|
|
// GetInstagramByAccountID retrieves a ChannelInstagram by Instagram Account ID.
|
|
func (r *Repository) GetInstagramByAccountID(ctx context.Context, igAccountID string) (*channelmodel.ChannelInstagram, error) {
|
|
var channel channelmodel.ChannelInstagram
|
|
if err := r.db.WithContext(ctx).
|
|
Where("instagram_account_id = ?", igAccountID).
|
|
First(&channel).Error; err != nil {
|
|
return nil, fmt.Errorf("failed to get ChannelInstagram by ig_account_id: %w", err)
|
|
}
|
|
return &channel, nil
|
|
}
|
|
|
|
// DeleteInstagram removes a ChannelInstagram record.
|
|
func (r *Repository) DeleteInstagram(ctx context.Context, id uint) error {
|
|
result := r.db.WithContext(ctx).Delete(&channelmodel.ChannelInstagram{}, id)
|
|
if result.Error != nil {
|
|
return fmt.Errorf("failed to delete ChannelInstagram: %w", result.Error)
|
|
}
|
|
|
|
if result.RowsAffected == 0 {
|
|
return fmt.Errorf("ChannelInstagram not found (id=%d)", id)
|
|
}
|
|
|
|
applogger.L().Info("ChannelInstagram deleted", "id", id)
|
|
return nil
|
|
}
|
|
|
|
// === Cross-Channel Queries ===
|
|
|
|
// GetFacebookChannelsByAccount retrieves all ChannelFacebook records for an account.
|
|
func (r *Repository) GetFacebookChannelsByAccount(ctx context.Context, accountID uint) ([]channelmodel.ChannelFacebook, error) {
|
|
var channels []channelmodel.ChannelFacebook
|
|
if err := r.db.WithContext(ctx).
|
|
Where("account_id = ?", accountID).
|
|
Find(&channels).Error; err != nil {
|
|
return nil, fmt.Errorf("failed to list ChannelFacebook by account: %w", err)
|
|
}
|
|
return channels, nil
|
|
}
|
|
|
|
// GetInstagramChannelsByAccount retrieves all ChannelInstagram records for an account.
|
|
func (r *Repository) GetInstagramChannelsByAccount(ctx context.Context, accountID uint) ([]channelmodel.ChannelInstagram, error) {
|
|
var channels []channelmodel.ChannelInstagram
|
|
if err := r.db.WithContext(ctx).
|
|
Where("account_id = ?", accountID).
|
|
Find(&channels).Error; err != nil {
|
|
return nil, fmt.Errorf("failed to list ChannelInstagram by account: %w", err)
|
|
}
|
|
return channels, nil
|
|
}
|
|
|
|
// GetInstagramByConnectedFBPageID retrieves a ChannelInstagram by connected FB Page ID.
|
|
// Used when processing Facebook webhooks that contain Instagram DM events.
|
|
func (r *Repository) GetInstagramByConnectedFBPageID(ctx context.Context, fbPageID string) (*channelmodel.ChannelInstagram, error) {
|
|
var channel channelmodel.ChannelInstagram
|
|
if err := r.db.WithContext(ctx).
|
|
Where("connected_fb_page_id = ?", fbPageID).
|
|
First(&channel).Error; err != nil {
|
|
return nil, fmt.Errorf("failed to get ChannelInstagram by connected_fb_page_id: %w", err)
|
|
}
|
|
return &channel, nil
|
|
}
|