Files
gochat/internal/channel/facebook/repository.go
T
2026-06-04 15:44:48 +08:00

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
}