Files
rmser/internal/infrastructure/repository/account/postgres.go

438 lines
15 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package account
import (
"errors"
"fmt"
"strings"
"time"
"rmser/internal/domain/account"
"github.com/google/uuid"
"gorm.io/gorm"
)
type pgRepository struct {
db *gorm.DB
}
func NewRepository(db *gorm.DB) account.Repository {
return &pgRepository{db: db}
}
// GetOrCreateUser находит пользователя или создает нового
func (r *pgRepository) GetOrCreateUser(telegramID int64, username, first, last string) (*account.User, error) {
var user account.User
err := r.db.Where("telegram_id = ?", telegramID).First(&user).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
newUser := account.User{
TelegramID: telegramID,
Username: username,
FirstName: first,
LastName: last,
}
if err := r.db.Create(&newUser).Error; err != nil {
return nil, err
}
return &newUser, nil
}
return nil, err
}
// Обновляем инфо
if user.Username != username || user.FirstName != first || user.LastName != last {
user.Username = username
user.FirstName = first
user.LastName = last
r.db.Save(&user)
}
return &user, nil
}
func (r *pgRepository) GetUserByTelegramID(telegramID int64) (*account.User, error) {
var user account.User
err := r.db.Where("telegram_id = ?", telegramID).First(&user).Error
if err != nil {
return nil, err
}
return &user, nil
}
func (r *pgRepository) GetUserByID(id uuid.UUID) (*account.User, error) {
var user account.User
err := r.db.Where("id = ?", id).First(&user).Error
if err != nil {
return nil, err
}
return &user, nil
}
// ConnectServer - Основная точка входа для добавления сервера
func (r *pgRepository) ConnectServer(userID uuid.UUID, rawURL, login, encryptedPass, name string) (*account.RMSServer, error) {
cleanURL := strings.TrimRight(strings.ToLower(strings.TrimSpace(rawURL)), "/")
var server account.RMSServer
var created bool
err := r.db.Transaction(func(tx *gorm.DB) error {
err := tx.Where("base_url = ?", cleanURL).First(&server).Error
if err != nil && err != gorm.ErrRecordNotFound {
return err
}
if err == gorm.ErrRecordNotFound {
// --- СЦЕНАРИЙ 1: НОВЫЙ СЕРВЕР (Приветственный бонус) ---
trialDays := 30
welcomeBalance := 10
paidUntil := time.Now().AddDate(0, 0, trialDays)
server = account.RMSServer{
BaseURL: cleanURL,
Name: name,
MaxUsers: 5,
Balance: welcomeBalance,
PaidUntil: &paidUntil,
}
if err := tx.Create(&server).Error; err != nil {
return err
}
created = true
} else {
// --- СЦЕНАРИЙ 2: СУЩЕСТВУЮЩИЙ СЕРВЕР ---
var userCount int64
tx.Model(&account.ServerUser{}).Where("server_id = ?", server.ID).Count(&userCount)
if userCount >= int64(server.MaxUsers) {
var exists int64
tx.Model(&account.ServerUser{}).Where("server_id = ? AND user_id = ?", server.ID, userID).Count(&exists)
if exists == 0 {
return fmt.Errorf("достигнут лимит пользователей на сервере (%d)", server.MaxUsers)
}
}
}
targetRole := account.RoleOperator
if created {
targetRole = account.RoleOwner
}
if err := tx.Model(&account.ServerUser{}).Where("user_id = ?", userID).Update("is_active", false).Error; err != nil {
return err
}
var existingLink account.ServerUser
err = tx.Where("server_id = ? AND user_id = ?", server.ID, userID).First(&existingLink).Error
if err == nil {
existingLink.Login = login
existingLink.EncryptedPassword = encryptedPass
existingLink.IsActive = true
return tx.Save(&existingLink).Error
}
userLink := account.ServerUser{
ServerID: server.ID,
UserID: userID,
Role: targetRole,
IsActive: true,
Login: login,
EncryptedPassword: encryptedPass,
}
return tx.Create(&userLink).Error
})
if err != nil {
return nil, err
}
return &server, nil
}
func (r *pgRepository) SaveServerSettings(server *account.RMSServer) error {
return r.db.Model(server).Updates(map[string]interface{}{
"name": server.Name,
"default_store_id": server.DefaultStoreID,
"root_group_guid": server.RootGroupGUID,
"auto_process": server.AutoProcess,
"max_users": server.MaxUsers,
}).Error
}
func (r *pgRepository) SetActiveServer(userID, serverID uuid.UUID) error {
return r.db.Transaction(func(tx *gorm.DB) error {
// Проверка доступа
var count int64
tx.Model(&account.ServerUser{}).Where("user_id = ? AND server_id = ?", userID, serverID).Count(&count)
if count == 0 {
return errors.New("доступ к серверу запрещен")
}
if err := tx.Model(&account.ServerUser{}).Where("user_id = ?", userID).Update("is_active", false).Error; err != nil {
return err
}
return tx.Model(&account.ServerUser{}).Where("user_id = ? AND server_id = ?", userID, serverID).Update("is_active", true).Error
})
}
func (r *pgRepository) GetActiveServer(userID uuid.UUID) (*account.RMSServer, error) {
var server account.RMSServer
err := r.db.Table("rms_servers").
Select("rms_servers.*").
Joins("JOIN server_users ON server_users.server_id = rms_servers.id").
Where("server_users.user_id = ? AND server_users.is_active = ?", userID, true).
First(&server).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, nil
}
return nil, err
}
return &server, nil
}
// GetActiveConnectionCredentials возвращает креды для подключения.
// Логика:
// 1. Берем личные креды из server_users (если активен)
// 2. Если личных нет (пустой пароль) -> ищем креды Владельца (Owner) этого сервера
func (r *pgRepository) GetActiveConnectionCredentials(userID uuid.UUID) (url, login, passHash string, err error) {
// 1. Получаем связь текущего юзера с активным сервером
type Result struct {
ServerID uuid.UUID
BaseURL string
Login string
EncryptedPassword string
}
var res Result
err = r.db.Table("server_users").
Select("server_users.server_id, rms_servers.base_url, server_users.login, server_users.encrypted_password").
Joins("JOIN rms_servers ON rms_servers.id = server_users.server_id").
Where("server_users.user_id = ? AND server_users.is_active = ?", userID, true).
Scan(&res).Error
if err != nil {
return "", "", "", err
}
if res.ServerID == uuid.Nil {
return "", "", "", errors.New("нет активного сервера")
}
// Если есть личные креды - возвращаем их
if res.Login != "" && res.EncryptedPassword != "" {
return res.BaseURL, res.Login, res.EncryptedPassword, nil
}
// 2. Фоллбэк: ищем креды владельца (OWNER)
var ownerLink account.ServerUser
err = r.db.Where("server_id = ? AND role = ?", res.ServerID, account.RoleOwner).
Order("created_at ASC"). // На случай коллизий, берем старейшего
First(&ownerLink).Error
if err != nil {
return "", "", "", fmt.Errorf("у вас нет учетных данных, а владелец сервера не найден: %w", err)
}
if ownerLink.Login == "" || ownerLink.EncryptedPassword == "" {
return "", "", "", errors.New("у владельца сервера отсутствуют учетные данные")
}
return res.BaseURL, ownerLink.Login, ownerLink.EncryptedPassword, nil
}
func (r *pgRepository) GetAllAvailableServers(userID uuid.UUID) ([]account.RMSServer, error) {
var servers []account.RMSServer
err := r.db.Table("rms_servers").
Select("rms_servers.*").
Joins("JOIN server_users ON server_users.server_id = rms_servers.id").
Where("server_users.user_id = ?", userID).
Find(&servers).Error
return servers, err
}
func (r *pgRepository) DeleteServer(serverID uuid.UUID) error {
// Полное удаление сервера и всех связей
return r.db.Transaction(func(tx *gorm.DB) error {
if err := tx.Where("server_id = ?", serverID).Delete(&account.ServerUser{}).Error; err != nil {
return err
}
if err := tx.Delete(&account.RMSServer{}, serverID).Error; err != nil {
return err
}
return nil
})
}
// --- Управление правами ---
func (r *pgRepository) GetUserRole(userID, serverID uuid.UUID) (account.Role, error) {
var link account.ServerUser
err := r.db.Select("role").Where("user_id = ? AND server_id = ?", userID, serverID).First(&link).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return "", errors.New("access denied")
}
return "", err
}
return link.Role, nil
}
func (r *pgRepository) SetUserRole(serverID, targetUserID uuid.UUID, newRole account.Role) error {
return r.db.Model(&account.ServerUser{}).
Where("server_id = ? AND user_id = ?", serverID, targetUserID).
Update("role", newRole).Error
}
func (r *pgRepository) GetServerUsers(serverID uuid.UUID) ([]account.ServerUser, error) {
var users []account.ServerUser
// Preload User для отображения имен
err := r.db.Preload("User").Where("server_id = ?", serverID).Find(&users).Error
return users, err
}
func (r *pgRepository) AddUserToServer(serverID, userID uuid.UUID, role account.Role) error {
// Проверка лимита перед добавлением
var server account.RMSServer
if err := r.db.First(&server, serverID).Error; err != nil {
return err
}
return r.db.Transaction(func(tx *gorm.DB) error {
// 1. Сначала проверяем, существует ли пользователь на этом сервере
var existingLink account.ServerUser
err := tx.Where("server_id = ? AND user_id = ?", serverID, userID).First(&existingLink).Error
if err == nil {
// --- ПОЛЬЗОВАТЕЛЬ УЖЕ ЕСТЬ ---
// Защита от понижения прав:
// Если текущая роль OWNER или ADMIN, а мы пытаемся поставить OPERATOR (через инвайт),
// то игнорируем смену роли, просто делаем активным.
if (existingLink.Role == account.RoleOwner || existingLink.Role == account.RoleAdmin) && role == account.RoleOperator {
role = existingLink.Role
}
// Обновляем активность и (возможно) роль
return tx.Model(&existingLink).Updates(map[string]interface{}{
"role": role,
"is_active": true,
}).Error
}
// --- ПОЛЬЗОВАТЕЛЬ НОВЫЙ ---
// Проверяем лимит только для новых
var currentCount int64
tx.Model(&account.ServerUser{}).Where("server_id = ?", serverID).Count(&currentCount)
if currentCount >= int64(server.MaxUsers) {
return fmt.Errorf("лимит пользователей (%d) превышен", server.MaxUsers)
}
// Сбрасываем активность на других серверах
if err := tx.Model(&account.ServerUser{}).Where("user_id = ?", userID).Update("is_active", false).Error; err != nil {
return err
}
// Создаем связь
link := account.ServerUser{
ServerID: serverID,
UserID: userID,
Role: role,
IsActive: true,
}
return tx.Create(&link).Error
})
}
func (r *pgRepository) RemoveUserFromServer(serverID, userID uuid.UUID) error {
return r.db.Where("server_id = ? AND user_id = ?", serverID, userID).Delete(&account.ServerUser{}).Error
}
func (r *pgRepository) IncrementInvoiceCount(serverID uuid.UUID) error {
return r.db.Model(&account.RMSServer{}).
Where("id = ?", serverID).
UpdateColumn("invoice_count", gorm.Expr("invoice_count + ?", 1)).Error
}
// --- Super Admin Functions ---
func (r *pgRepository) GetAllServersSystemWide() ([]account.RMSServer, error) {
var servers []account.RMSServer
// Загружаем вместе с владельцем для отображения
err := r.db.Order("name ASC").Find(&servers).Error
return servers, err
}
func (r *pgRepository) TransferOwnership(serverID, newOwnerID uuid.UUID) error {
return r.db.Transaction(func(tx *gorm.DB) error {
// 1. Находим текущего владельца
var currentOwnerLink account.ServerUser
if err := tx.Where("server_id = ? AND role = ?", serverID, account.RoleOwner).First(&currentOwnerLink).Error; err != nil {
return fmt.Errorf("current owner not found: %w", err)
}
// 2. Проверяем, что новый владелец вообще есть на сервере
var newOwnerLink account.ServerUser
if err := tx.Where("server_id = ? AND user_id = ?", serverID, newOwnerID).First(&newOwnerLink).Error; err != nil {
return fmt.Errorf("target user not found on server: %w", err)
}
// 3. Понижаем старого владельца до ADMIN
if err := tx.Model(&currentOwnerLink).Update("role", account.RoleAdmin).Error; err != nil {
return err
}
// 4. Повышаем нового до OWNER
if err := tx.Model(&newOwnerLink).Update("role", account.RoleOwner).Error; err != nil {
return err
}
// УДАЛЕНО: обновление server.owner_id, так как этого поля нет в модели
return nil
})
}
func (r *pgRepository) GetConnectionByID(id uuid.UUID) (*account.ServerUser, error) {
var link account.ServerUser
// Preload нужны, чтобы показать имена в админке
err := r.db.Preload("User").Preload("Server").Where("id = ?", id).First(&link).Error
if err != nil {
return nil, err
}
return &link, nil
}
func (r *pgRepository) GetServerByURL(rawURL string) (*account.RMSServer, error) {
cleanURL := strings.TrimRight(strings.ToLower(strings.TrimSpace(rawURL)), "/")
var server account.RMSServer
err := r.db.Where("base_url = ?", cleanURL).First(&server).Error
if err != nil {
return nil, err
}
return &server, nil
}
func (r *pgRepository) GetServerByID(id uuid.UUID) (*account.RMSServer, error) {
var server account.RMSServer
err := r.db.First(&server, id).Error
if err != nil {
return nil, err
}
return &server, nil
}
// UpdateBalance начисляет пакет или продлевает подписку
func (r *pgRepository) UpdateBalance(serverID uuid.UUID, amountChange int, newPaidUntil *time.Time) error {
return r.db.Model(&account.RMSServer{}).Where("id = ?", serverID).Updates(map[string]interface{}{
"balance": gorm.Expr("balance + ?", amountChange),
"paid_until": newPaidUntil,
}).Error
}
// DecrementBalance списывает 1 единицу при отправке накладной
func (r *pgRepository) DecrementBalance(serverID uuid.UUID) error {
return r.db.Model(&account.RMSServer{}).
Where("id = ? AND balance > 0", serverID).
UpdateColumn("balance", gorm.Expr("balance - ?", 1)).Error
}