396 lines
16 KiB
C#
396 lines
16 KiB
C#
using Managing.Application.Abstractions.Repositories;
|
|
using Managing.Application.Abstractions.Services;
|
|
using Managing.Application.Abstractions.Shared;
|
|
using Managing.Domain.Accounts;
|
|
using Managing.Domain.Users;
|
|
using Managing.Infrastructure.Databases.PostgreSql.Entities;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Logging;
|
|
using static Managing.Common.Enums;
|
|
|
|
namespace Managing.Infrastructure.Databases.PostgreSql;
|
|
|
|
public class PostgreSqlUserRepository : BaseRepositoryWithLogging, IUserRepository
|
|
{
|
|
private readonly ICacheService _cacheService;
|
|
|
|
public PostgreSqlUserRepository(ManagingDbContext context, ILogger<SqlQueryLogger> logger,
|
|
SentrySqlMonitoringService sentryMonitoringService, ICacheService cacheService)
|
|
: base(context, logger, sentryMonitoringService)
|
|
{
|
|
_cacheService = cacheService;
|
|
}
|
|
|
|
public async Task<User?> GetUserByAgentNameAsync(string agentName)
|
|
{
|
|
return await ExecuteWithLoggingAsync(async () =>
|
|
{
|
|
// Check cache first for frequently accessed users
|
|
var cacheKey = $"user_agent_{agentName}";
|
|
var cachedUser = _cacheService.GetValue<User>(cacheKey);
|
|
if (cachedUser != null)
|
|
{
|
|
return cachedUser;
|
|
}
|
|
|
|
try
|
|
{
|
|
await PostgreSqlConnectionHelper.EnsureConnectionOpenAsync(_context);
|
|
|
|
// Optimized query with explicit SELECT to avoid loading unnecessary data
|
|
var userEntity = await _context.Users
|
|
.AsNoTracking()
|
|
.Where(u => u.AgentName == agentName)
|
|
.Select(u => new UserEntity
|
|
{
|
|
Id = u.Id,
|
|
Name = u.Name,
|
|
AgentName = u.AgentName,
|
|
AvatarUrl = u.AvatarUrl,
|
|
TelegramChannel = u.TelegramChannel,
|
|
OwnerWalletAddress = u.OwnerWalletAddress
|
|
})
|
|
.FirstOrDefaultAsync()
|
|
.ConfigureAwait(false);
|
|
|
|
if (userEntity == null)
|
|
return null;
|
|
|
|
var user = PostgreSqlMappers.Map(userEntity);
|
|
|
|
// Cache user for 5 minutes since user data doesn't change frequently
|
|
_cacheService.SaveValue(cacheKey, user, TimeSpan.FromMinutes(5));
|
|
|
|
return user;
|
|
}
|
|
finally
|
|
{
|
|
await PostgreSqlConnectionHelper.SafeCloseConnectionAsync(_context);
|
|
}
|
|
}, nameof(GetUserByAgentNameAsync), ("agentName", agentName));
|
|
}
|
|
|
|
public async Task<User?> GetUserByIdAsync(int userId)
|
|
{
|
|
return await ExecuteWithLoggingAsync(async () =>
|
|
{
|
|
// Check cache first for frequently accessed users
|
|
var cacheKey = $"user_id_{userId}";
|
|
var cachedUser = _cacheService.GetValue<User>(cacheKey);
|
|
if (cachedUser != null)
|
|
{
|
|
return cachedUser;
|
|
}
|
|
|
|
try
|
|
{
|
|
await PostgreSqlConnectionHelper.EnsureConnectionOpenAsync(_context);
|
|
|
|
// Optimized query with explicit SELECT to avoid loading unnecessary data
|
|
var userEntity = await _context.Users
|
|
.AsNoTracking()
|
|
.Where(u => u.Id == userId)
|
|
.Select(u => new UserEntity
|
|
{
|
|
Id = u.Id,
|
|
Name = u.Name,
|
|
AgentName = u.AgentName,
|
|
AvatarUrl = u.AvatarUrl,
|
|
TelegramChannel = u.TelegramChannel,
|
|
OwnerWalletAddress = u.OwnerWalletAddress
|
|
})
|
|
.FirstOrDefaultAsync()
|
|
.ConfigureAwait(false);
|
|
|
|
if (userEntity == null)
|
|
return null;
|
|
|
|
var user = PostgreSqlMappers.Map(userEntity);
|
|
|
|
// Cache user for 5 minutes since user data doesn't change frequently
|
|
_cacheService.SaveValue(cacheKey, user, TimeSpan.FromMinutes(5));
|
|
|
|
return user;
|
|
}
|
|
finally
|
|
{
|
|
await PostgreSqlConnectionHelper.SafeCloseConnectionAsync(_context);
|
|
}
|
|
}, nameof(GetUserByIdAsync), ("userId", userId));
|
|
}
|
|
|
|
public async Task<User> GetUserByNameAsync(string name, bool fetchAccounts = false)
|
|
{
|
|
return await ExecuteWithLoggingAsync(async () =>
|
|
{
|
|
try
|
|
{
|
|
await PostgreSqlConnectionHelper.EnsureConnectionOpenAsync(_context);
|
|
|
|
User user;
|
|
|
|
if (fetchAccounts)
|
|
{
|
|
// Fetch user with accounts in a single query
|
|
var userEntity = await _context.Users
|
|
.AsNoTracking()
|
|
.Include(u => u.Accounts)
|
|
.Where(u => u.Name == name)
|
|
.FirstOrDefaultAsync()
|
|
.ConfigureAwait(false);
|
|
|
|
if (userEntity == null)
|
|
throw new InvalidOperationException($"User with name '{name}' not found");
|
|
|
|
user = PostgreSqlMappers.Map(userEntity);
|
|
|
|
// Map accounts using the existing mapper
|
|
if (userEntity.Accounts != null)
|
|
{
|
|
user.Accounts = userEntity.Accounts.Select(PostgreSqlMappers.Map).ToList();
|
|
}
|
|
else
|
|
{
|
|
user.Accounts = new List<Account>();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Optimized query with explicit SELECT to avoid loading unnecessary data
|
|
var userEntity = await _context.Users
|
|
.AsNoTracking()
|
|
.Where(u => u.Name == name)
|
|
.Select(u => new UserEntity
|
|
{
|
|
Id = u.Id,
|
|
Name = u.Name,
|
|
AgentName = u.AgentName,
|
|
AvatarUrl = u.AvatarUrl,
|
|
TelegramChannel = u.TelegramChannel
|
|
})
|
|
.FirstOrDefaultAsync()
|
|
.ConfigureAwait(false);
|
|
|
|
if (userEntity == null)
|
|
throw new InvalidOperationException($"User with name '{name}' not found");
|
|
|
|
user = PostgreSqlMappers.Map(userEntity);
|
|
user.Accounts = new List<Account>(); // Initialize empty list
|
|
}
|
|
|
|
return user;
|
|
}
|
|
finally
|
|
{
|
|
await PostgreSqlConnectionHelper.SafeCloseConnectionAsync(_context);
|
|
}
|
|
}, nameof(GetUserByNameAsync), ("name", name), ("fetchAccounts", fetchAccounts));
|
|
}
|
|
|
|
public async Task<IEnumerable<User>> GetAllUsersAsync()
|
|
{
|
|
return await ExecuteWithLoggingAsync(async () =>
|
|
{
|
|
// Check cache first for all users
|
|
var cacheKey = "all_users";
|
|
var cachedUsers = _cacheService.GetValue<List<User>>(cacheKey);
|
|
if (cachedUsers != null)
|
|
{
|
|
return cachedUsers;
|
|
}
|
|
|
|
try
|
|
{
|
|
await PostgreSqlConnectionHelper.EnsureConnectionOpenAsync(_context);
|
|
|
|
// Optimized query with explicit SELECT to avoid loading unnecessary data
|
|
var userEntities = await _context.Users
|
|
.AsNoTracking()
|
|
.Select(u => new UserEntity
|
|
{
|
|
Id = u.Id,
|
|
Name = u.Name,
|
|
AgentName = u.AgentName,
|
|
AvatarUrl = u.AvatarUrl,
|
|
TelegramChannel = u.TelegramChannel,
|
|
OwnerWalletAddress = u.OwnerWalletAddress
|
|
})
|
|
.ToListAsync()
|
|
.ConfigureAwait(false);
|
|
|
|
var users = userEntities.Select(PostgreSqlMappers.Map).ToList();
|
|
|
|
// Cache all users for 10 minutes since this data changes infrequently
|
|
_cacheService.SaveValue(cacheKey, users, TimeSpan.FromMinutes(10));
|
|
|
|
return users;
|
|
}
|
|
finally
|
|
{
|
|
// Always ensure the connection is closed after the operation
|
|
await PostgreSqlConnectionHelper.SafeCloseConnectionAsync(_context);
|
|
}
|
|
}, nameof(GetAllUsersAsync));
|
|
}
|
|
|
|
public async Task SaveOrUpdateUserAsync(User user)
|
|
{
|
|
await ExecuteWithLoggingAsync(async () =>
|
|
{
|
|
try
|
|
{
|
|
var existingUser = await _context.Users
|
|
.AsTracking()
|
|
.FirstOrDefaultAsync(u => u.Name == user.Name)
|
|
.ConfigureAwait(false);
|
|
|
|
string? oldAgentName = null;
|
|
if (existingUser != null)
|
|
{
|
|
// Capture old AgentName before updating for cache invalidation
|
|
oldAgentName = existingUser.AgentName;
|
|
|
|
// Update existing user
|
|
existingUser.AgentName = user.AgentName;
|
|
existingUser.AvatarUrl = user.AvatarUrl;
|
|
existingUser.TelegramChannel = user.TelegramChannel;
|
|
existingUser.OwnerWalletAddress = user.OwnerWalletAddress;
|
|
existingUser.IsAdmin = user.IsAdmin;
|
|
|
|
_context.Users.Update(existingUser);
|
|
|
|
// Update the user object with the existing user's ID
|
|
user.Id = existingUser.Id;
|
|
}
|
|
else
|
|
{
|
|
// Insert new user
|
|
var userEntity = PostgreSqlMappers.Map(user);
|
|
_context.Users.Add(userEntity);
|
|
|
|
// Update the user object with the database-generated ID after save
|
|
await _context.SaveChangesAsync().ConfigureAwait(false);
|
|
user.Id = userEntity.Id;
|
|
|
|
// Cache the new user
|
|
var newUserNameCacheKey = $"user_name_{user.Name}";
|
|
var newUserAgentCacheKey = $"user_agent_{user.AgentName}";
|
|
_cacheService.SaveValue(newUserNameCacheKey, user, TimeSpan.FromMinutes(5));
|
|
if (!string.IsNullOrEmpty(user.AgentName))
|
|
{
|
|
_cacheService.SaveValue(newUserAgentCacheKey, user, TimeSpan.FromMinutes(5));
|
|
}
|
|
|
|
// Invalidate all users cache since we added a new user
|
|
_cacheService.RemoveValue("all_users");
|
|
return; // Exit early since we already saved
|
|
}
|
|
|
|
await _context.SaveChangesAsync().ConfigureAwait(false);
|
|
|
|
// Invalidate cache for updated user - handle both old and new AgentName
|
|
var nameCacheKey = $"user_name_{user.Name}";
|
|
var nameWithAccountsCacheKey = $"user_name_with_accounts_{user.Name}";
|
|
var idCacheKey = $"user_id_{user.Id}";
|
|
_cacheService.RemoveValue(nameCacheKey);
|
|
_cacheService.RemoveValue(nameWithAccountsCacheKey);
|
|
_cacheService.RemoveValue(idCacheKey);
|
|
|
|
// Invalidate old AgentName cache if it existed
|
|
if (!string.IsNullOrEmpty(oldAgentName))
|
|
{
|
|
var oldAgentCacheKey = $"user_agent_{oldAgentName}";
|
|
_cacheService.RemoveValue(oldAgentCacheKey);
|
|
}
|
|
|
|
// Invalidate new AgentName cache if it exists
|
|
if (!string.IsNullOrEmpty(user.AgentName))
|
|
{
|
|
var newAgentCacheKey = $"user_agent_{user.AgentName}";
|
|
_cacheService.RemoveValue(newAgentCacheKey);
|
|
}
|
|
|
|
// Invalidate all users cache since we updated a user
|
|
_cacheService.RemoveValue("all_users");
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Console.WriteLine(e);
|
|
throw new Exception("Cannot save or update user");
|
|
}
|
|
}, nameof(SaveOrUpdateUserAsync), ("userName", user.Name), ("userId", user.Id));
|
|
}
|
|
|
|
public async Task<(IEnumerable<User> Users, int TotalCount)> GetUsersPaginatedAsync(int page, int pageSize, UserSortableColumn sortBy, string sortOrder, UsersFilter filter)
|
|
{
|
|
return await ExecuteWithLoggingAsync(async () =>
|
|
{
|
|
try
|
|
{
|
|
await PostgreSqlConnectionHelper.EnsureConnectionOpenAsync(_context);
|
|
|
|
var query = _context.Users.AsNoTracking();
|
|
|
|
// Apply filters
|
|
if (!string.IsNullOrWhiteSpace(filter.UserNameContains))
|
|
{
|
|
query = query.Where(u => EF.Functions.ILike(u.Name, $"%{filter.UserNameContains.Trim()}%"));
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(filter.OwnerAddressContains))
|
|
{
|
|
query = query.Where(u => EF.Functions.ILike(u.OwnerWalletAddress, $"%{filter.OwnerAddressContains.Trim()}%"));
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(filter.AgentNameContains))
|
|
{
|
|
query = query.Where(u => EF.Functions.ILike(u.AgentName, $"%{filter.AgentNameContains.Trim()}%"));
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(filter.TelegramChannelContains))
|
|
{
|
|
query = query.Where(u => EF.Functions.ILike(u.TelegramChannel, $"%{filter.TelegramChannelContains.Trim()}%"));
|
|
}
|
|
|
|
// Get total count for pagination
|
|
var totalCount = await query.CountAsync().ConfigureAwait(false);
|
|
|
|
// Apply sorting
|
|
query = sortBy switch
|
|
{
|
|
UserSortableColumn.Id => sortOrder.ToLower() == "desc" ? query.OrderByDescending(u => u.Id) : query.OrderBy(u => u.Id),
|
|
UserSortableColumn.Name => sortOrder.ToLower() == "desc" ? query.OrderByDescending(u => u.Name) : query.OrderBy(u => u.Name),
|
|
UserSortableColumn.OwnerWalletAddress => sortOrder.ToLower() == "desc" ? query.OrderByDescending(u => u.OwnerWalletAddress) : query.OrderBy(u => u.OwnerWalletAddress),
|
|
UserSortableColumn.AgentName => sortOrder.ToLower() == "desc" ? query.OrderByDescending(u => u.AgentName) : query.OrderBy(u => u.AgentName),
|
|
_ => query.OrderBy(u => u.Id) // Default sorting
|
|
};
|
|
|
|
// Apply pagination
|
|
var users = await query
|
|
.Skip((page - 1) * pageSize)
|
|
.Take(pageSize)
|
|
.Select(u => new UserEntity
|
|
{
|
|
Id = u.Id,
|
|
Name = u.Name,
|
|
AgentName = u.AgentName,
|
|
AvatarUrl = u.AvatarUrl,
|
|
TelegramChannel = u.TelegramChannel,
|
|
OwnerWalletAddress = u.OwnerWalletAddress,
|
|
IsAdmin = u.IsAdmin,
|
|
LastConnectionDate = u.LastConnectionDate
|
|
})
|
|
.ToListAsync()
|
|
.ConfigureAwait(false);
|
|
|
|
var domainUsers = users.Select(PostgreSqlMappers.Map).ToList();
|
|
|
|
return (domainUsers, totalCount);
|
|
}
|
|
finally
|
|
{
|
|
await PostgreSqlConnectionHelper.SafeCloseConnectionAsync(_context);
|
|
}
|
|
}, nameof(GetUsersPaginatedAsync), ("page", page), ("pageSize", pageSize), ("sortBy", sortBy), ("sortOrder", sortOrder));
|
|
}
|
|
} |