mirror of
https://github.com/serty2005/rmser.git
synced 2026-02-04 19:02:33 -06:00
438 lines
15 KiB
Go
438 lines
15 KiB
Go
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(¤tCount)
|
||
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(¤tOwnerLink).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(¤tOwnerLink).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
|
||
}
|