143 рядки
4.1 KiB
Go
143 рядки
4.1 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
)
|
|
|
|
type BotAccessRepository struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
func NewBotAccessRepository(db *gorm.DB) *BotAccessRepository {
|
|
return &BotAccessRepository{db: db}
|
|
}
|
|
|
|
func (r *BotAccessRepository) AllowTelegramID(ctx context.Context, telegramID int64) (bool, error) {
|
|
model := BotAccessModel{TelegramID: &telegramID}
|
|
result := r.db.WithContext(ctx).
|
|
Clauses(clause.OnConflict{
|
|
Columns: []clause.Column{{Name: "telegram_id"}},
|
|
DoNothing: true,
|
|
}).
|
|
Create(&model)
|
|
if result.Error != nil {
|
|
return false, fmt.Errorf("allow telegram id: %w", result.Error)
|
|
}
|
|
return result.RowsAffected > 0, nil
|
|
}
|
|
|
|
func (r *BotAccessRepository) AllowUsername(ctx context.Context, username string) (bool, error) {
|
|
model := BotAccessModel{Username: &username}
|
|
result := r.db.WithContext(ctx).
|
|
Clauses(clause.OnConflict{
|
|
Columns: []clause.Column{{Name: "username"}},
|
|
DoNothing: true,
|
|
}).
|
|
Create(&model)
|
|
if result.Error != nil {
|
|
return false, fmt.Errorf("allow username: %w", result.Error)
|
|
}
|
|
return result.RowsAffected > 0, nil
|
|
}
|
|
|
|
// TelegramIDForUsername returns the bound Telegram ID for a username grant.
|
|
// found is true for a pending grant, where telegramID is zero.
|
|
func (r *BotAccessRepository) TelegramIDForUsername(
|
|
ctx context.Context, username string,
|
|
) (int64, bool, error) {
|
|
var model BotAccessModel
|
|
err := r.db.WithContext(ctx).Where("username = ?", username).First(&model).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return 0, false, nil
|
|
}
|
|
if err != nil {
|
|
return 0, false, fmt.Errorf("get bot access by username: %w", err)
|
|
}
|
|
if model.TelegramID == nil {
|
|
return 0, true, nil
|
|
}
|
|
return *model.TelegramID, true, nil
|
|
}
|
|
|
|
func (r *BotAccessRepository) IsAllowed(ctx context.Context, telegramID int64) (bool, error) {
|
|
var count int64
|
|
err := r.db.WithContext(ctx).
|
|
Model(&BotAccessModel{}).
|
|
Where("telegram_id = ?", telegramID).
|
|
Count(&count).Error
|
|
if err != nil {
|
|
return false, fmt.Errorf("check bot access: %w", err)
|
|
}
|
|
return count > 0, nil
|
|
}
|
|
|
|
// Claim binds a pending username grant to the user who just contacted the bot.
|
|
// If the user already has an ID grant, a duplicate pending row is removed and
|
|
// the stored username is refreshed so /kick can use the current handle.
|
|
func (r *BotAccessRepository) Claim(ctx context.Context, telegramID int64, username string) error {
|
|
if username == "" {
|
|
return nil
|
|
}
|
|
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
var byID BotAccessModel
|
|
err := tx.Where("telegram_id = ?", telegramID).First(&byID).Error
|
|
if err == nil {
|
|
if err := tx.Where("username = ? AND id <> ?", username, byID.ID).
|
|
Delete(&BotAccessModel{}).Error; err != nil {
|
|
return err
|
|
}
|
|
if byID.Username != nil && *byID.Username == username {
|
|
return nil
|
|
}
|
|
return tx.Model(&byID).Update("username", username).Error
|
|
}
|
|
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return err
|
|
}
|
|
|
|
var pending BotAccessModel
|
|
err = tx.Where("username = ?", username).First(&pending).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// A username can be reassigned. Do not transfer a grant that already
|
|
// belongs to another Telegram ID.
|
|
if pending.TelegramID != nil && *pending.TelegramID != telegramID {
|
|
return nil
|
|
}
|
|
return tx.Model(&pending).Update("telegram_id", telegramID).Error
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("claim bot access: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *BotAccessRepository) RevokeTelegramID(ctx context.Context, telegramID int64) (bool, error) {
|
|
result := r.db.WithContext(ctx).
|
|
Where("telegram_id = ?", telegramID).
|
|
Delete(&BotAccessModel{})
|
|
if result.Error != nil {
|
|
return false, fmt.Errorf("revoke telegram id: %w", result.Error)
|
|
}
|
|
return result.RowsAffected > 0, nil
|
|
}
|
|
|
|
func (r *BotAccessRepository) RevokeUsername(ctx context.Context, username string) (bool, error) {
|
|
result := r.db.WithContext(ctx).
|
|
Where("username = ?", username).
|
|
Delete(&BotAccessModel{})
|
|
if result.Error != nil {
|
|
return false, fmt.Errorf("revoke username: %w", result.Error)
|
|
}
|
|
return result.RowsAffected > 0, nil
|
|
}
|