QrRapido/Services/UserService.cs

501 lines
19 KiB
C#

using MongoDB.Driver;
using QRRapidoApp.Data;
using QRRapidoApp.Models;
using QRRapidoApp.Models.ViewModels;
using System.Text.Json;
namespace QRRapidoApp.Services
{
public class UserService : IUserService
{
private readonly MongoDbContext _context;
private readonly IConfiguration _config;
private readonly ILogger<UserService> _logger;
public UserService(MongoDbContext context, IConfiguration config, ILogger<UserService> logger)
{
_context = context;
_config = config;
_logger = logger;
}
public async Task<User?> GetUserAsync(string userId)
{
try
{
if (_context.Users == null) return null; // Development mode without MongoDB
User? userData = null;
if (!String.IsNullOrEmpty(userId))
{
userData = await _context.Users.Find(u => u.Id == userId).FirstOrDefaultAsync();
}
return userData ?? new User();
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error getting user {userId}: {ex.Message}");
return null;
}
}
public async Task<User?> GetUserByEmailAsync(string email)
{
try
{
if (_context.Users == null) return null; // Development mode without MongoDB
return await _context.Users.Find(u => u.Email == email).FirstOrDefaultAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error getting user by email {email}: {ex.Message}");
return null;
}
}
public async Task<User?> GetUserByProviderAsync(string provider, string providerId)
{
try
{
return await _context.Users
.Find(u => u.Provider == provider && u.ProviderId == providerId)
.FirstOrDefaultAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error getting user by provider {provider}:{providerId}: {ex.Message}");
return null;
}
}
public async Task<User> CreateUserAsync(string email, string name, string provider, string providerId)
{
var user = new User
{
Email = email,
Name = name,
Provider = provider,
ProviderId = providerId,
CreatedAt = DateTime.UtcNow,
LastLoginAt = DateTime.UtcNow,
PreferredLanguage = "pt-BR",
DailyQRCount = 0,
LastQRDate = DateTime.UtcNow.Date,
TotalQRGenerated = 0
};
await _context.Users.InsertOneAsync(user);
_logger.LogInformation($"Created new user: {email} via {provider}");
return user;
}
public async Task UpdateLastLoginAsync(string userId)
{
try
{
var update = Builders<User>.Update
.Set(u => u.LastLoginAt, DateTime.UtcNow);
await _context.Users.UpdateOneAsync(u => u.Id == userId, update);
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error updating last login for user {userId}: {ex.Message}");
}
}
public async Task<bool> UpdateUserAsync(User user)
{
try
{
var result = await _context.Users.ReplaceOneAsync(u => u.Id == user.Id, user);
return result.ModifiedCount > 0;
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error updating user {user.Id}: {ex.Message}");
return false;
}
}
public async Task<int> GetDailyQRCountAsync(string? userId)
{
if (string.IsNullOrEmpty(userId))
return 0; // Anonymous users tracked separately
try
{
var user = await GetUserAsync(userId);
if (user == null) return 0;
// Reset count if it's a new day
if (user.LastQRDate.Date < DateTime.UtcNow.Date)
{
user.DailyQRCount = 0;
user.LastQRDate = DateTime.UtcNow.Date;
await UpdateUserAsync(user);
}
return user.DailyQRCount;
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error getting daily QR count for user {userId}: {ex.Message}");
return 0;
}
}
public async Task<int> IncrementDailyQRCountAsync(string userId)
{
try
{
var user = await GetUserAsync(userId);
if (user == null) return 0;
// Reset count if it's a new day
if (user.LastQRDate.Date < DateTime.UtcNow.Date)
{
user.DailyQRCount = 1;
user.LastQRDate = DateTime.UtcNow.Date;
}
else
{
user.DailyQRCount++;
}
user.TotalQRGenerated++;
await UpdateUserAsync(user);
// Premium and logged users have unlimited QR codes
return int.MaxValue;
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error incrementing daily QR count for user {userId}: {ex.Message}");
return 0;
}
}
public async Task<int> GetRemainingQRCountAsync(string userId)
{
try
{
var user = await GetUserAsync(userId);
if (user == null) return 0;
// Premium users have unlimited
if (user.IsPremium) return int.MaxValue;
// Logged users (non-premium) have unlimited
return int.MaxValue;
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error getting remaining QR count for user {userId}: {ex.Message}");
return 0;
}
}
public async Task<bool> CanGenerateQRAsync(string? userId, bool isPremium)
{
// Premium users have unlimited QR codes
if (isPremium) return true;
// Logged users (non-premium) have unlimited QR codes
if (!string.IsNullOrEmpty(userId)) return true;
// Anonymous users have 3 QR codes per day
var dailyCount = await GetDailyQRCountAsync(userId);
var limit = 3;
return dailyCount < limit;
}
public async Task SaveQRToHistoryAsync(string? userId, QRGenerationResult qrResult)
{
try
{
var qrHistory = new QRCodeHistory
{
UserId = userId,
Type = qrResult.RequestSettings?.Type ?? "unknown",
Content = qrResult.RequestSettings?.Content ?? "",
QRCodeBase64 = qrResult.QRCodeBase64,
CustomizationSettings = JsonSerializer.Serialize(qrResult.RequestSettings),
CreatedAt = DateTime.UtcNow,
Language = qrResult.RequestSettings?.Language ?? "pt-BR",
Size = qrResult.RequestSettings?.Size ?? 300,
GenerationTimeMs = qrResult.GenerationTimeMs,
FromCache = qrResult.FromCache,
IsActive = true,
LastAccessedAt = DateTime.UtcNow,
TrackingId = qrResult.TrackingId, // Save tracking ID for analytics
IsDynamic = !string.IsNullOrEmpty(qrResult.TrackingId) // Mark as dynamic if tracking is enabled
};
await _context.QRCodeHistory.InsertOneAsync(qrHistory);
// Update user's QR history IDs if logged in
if (!string.IsNullOrEmpty(userId))
{
var update = Builders<User>.Update
.Push(u => u.QRHistoryIds, qrHistory.Id);
await _context.Users.UpdateOneAsync(u => u.Id == userId, update);
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error saving QR to history: {ex.Message}");
}
}
public async Task<List<QRCodeHistory>> GetUserQRHistoryAsync(string userId, int limit = 50)
{
try
{
return await _context.QRCodeHistory
.Find(q => q.UserId == userId && q.IsActive)
.SortByDescending(q => q.CreatedAt)
.Limit(limit)
.ToListAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error getting QR history for user {userId}: {ex.Message}");
return new List<QRCodeHistory>();
}
}
public async Task<QRCodeHistory?> GetQRDataAsync(string qrId)
{
try
{
return await _context.QRCodeHistory
.Find(q => q.Id == qrId && q.IsActive)
.FirstOrDefaultAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error getting QR data {qrId}: {ex.Message}");
return null;
}
}
public async Task<bool> DeleteQRFromHistoryAsync(string userId, string qrId)
{
try
{
// First verify that the QR code belongs to the user
var qrCode = await _context.QRCodeHistory
.Find(q => q.Id == qrId && q.UserId == userId && q.IsActive)
.FirstOrDefaultAsync();
if (qrCode == null)
{
_logger.LogWarning($"QR code not found or doesn't belong to user - QRId: {qrId}, UserId: {userId}");
return false;
}
// Soft delete: mark as inactive instead of permanently deleting
var update = Builders<QRCodeHistory>.Update.Set(q => q.IsActive, false);
var result = await _context.QRCodeHistory.UpdateOneAsync(
q => q.Id == qrId && q.UserId == userId,
update);
return result.ModifiedCount > 0;
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error deleting QR from history - QRId: {qrId}, UserId: {userId}: {ex.Message}");
return false;
}
}
public async Task<int> GetQRCountThisMonthAsync(string userId)
{
try
{
var startOfMonth = new DateTime(DateTime.UtcNow.Year, DateTime.UtcNow.Month, 1);
var endOfMonth = startOfMonth.AddMonths(1);
var count = await _context.QRCodeHistory
.CountDocumentsAsync(q => q.UserId == userId &&
q.CreatedAt >= startOfMonth &&
q.CreatedAt < endOfMonth);
return (int)count;
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error getting monthly QR count for user {userId}: {ex.Message}");
return 0;
}
}
// MÉTODO REMOVIDO: ExtendAdFreeTimeAsync - não é mais necessário
public async Task<string> GetUserEmailAsync(string userId)
{
var user = await GetUserAsync(userId);
return user?.Email ?? string.Empty;
}
public async Task MarkPremiumCancelledAsync(string userId, DateTime cancelledAt)
{
try
{
if (_context.Users == null) return; // Development mode without MongoDB
var update = Builders<User>.Update
.Set(u => u.IsPremium, false)
.Set(u => u.PremiumCancelledAt, cancelledAt)
.Set(u => u.PremiumExpiresAt, null);
await _context.Users.UpdateOneAsync(u => u.Id == userId, update);
_logger.LogInformation($"Marked premium as cancelled for user {userId} at {cancelledAt}");
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error marking premium cancelled for user {userId}: {ex.Message}");
}
}
public async Task<List<User>> GetUsersForHistoryCleanupAsync(DateTime cutoffDate)
{
try
{
if (_context.Users == null) return new List<User>(); // Development mode without MongoDB
return await _context.Users
.Find(u => u.PremiumCancelledAt != null &&
u.PremiumCancelledAt < cutoffDate &&
u.QRHistoryIds.Count > 0)
.ToListAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error getting users for history cleanup: {ex.Message}");
return new List<User>();
}
}
public async Task DeleteUserHistoryAsync(string userId)
{
try
{
if (_context.Users == null || _context.QRCodeHistory == null) return; // Development mode without MongoDB
var user = await GetUserAsync(userId);
if (user?.QRHistoryIds?.Any() == true)
{
// Remover histórico de QR codes
await _context.QRCodeHistory.DeleteManyAsync(qr => user.QRHistoryIds.Contains(qr.Id));
// Limpar lista de histórico do usuário
var update = Builders<User>.Update.Set(u => u.QRHistoryIds, new List<string>());
await _context.Users.UpdateOneAsync(u => u.Id == userId, update);
_logger.LogInformation($"Deleted history for user {userId}");
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error deleting history for user {userId}: {ex.Message}");
}
}
public async Task ActivatePremiumStatus(string userId, string stripeSubscriptionId, DateTime expiryDate)
{
// Verifica se é uma nova assinatura (não renovação)
var user = await GetUserAsync(userId);
var isNewSubscription = user?.StripeSubscriptionId != stripeSubscriptionId;
var updateBuilder = Builders<User>.Update
.Set(u => u.IsPremium, true)
.Set(u => u.StripeSubscriptionId, stripeSubscriptionId)
.Set(u => u.PremiumExpiresAt, expiryDate)
.Unset(u => u.PremiumCancelledAt);
// Se é nova assinatura, atualiza a data de início (para CDC 7 dias)
if (isNewSubscription)
{
updateBuilder = updateBuilder.Set(u => u.SubscriptionStartedAt, DateTime.UtcNow);
}
await _context.Users.UpdateOneAsync(u => u.Id == userId, updateBuilder);
_logger.LogInformation($"Activated premium for user {userId} (new subscription: {isNewSubscription})");
}
public async Task DeactivatePremiumStatus(string stripeSubscriptionId)
{
var update = Builders<User>.Update
.Set(u => u.IsPremium, false)
.Set(u => u.PremiumCancelledAt, DateTime.UtcNow);
await _context.Users.UpdateOneAsync(u => u.StripeSubscriptionId == stripeSubscriptionId, update);
_logger.LogInformation($"Deactivated premium for subscription {stripeSubscriptionId}");
}
public async Task<User?> GetUserByStripeCustomerIdAsync(string customerId)
{
return await _context.Users.Find(u => u.StripeCustomerId == customerId).FirstOrDefaultAsync();
}
public async Task UpdateUserStripeCustomerIdAsync(string userId, string stripeCustomerId)
{
var update = Builders<User>.Update.Set(u => u.StripeCustomerId, stripeCustomerId);
await _context.Users.UpdateOneAsync(u => u.Id == userId, update);
}
/// <summary>
/// Get QR code by tracking ID (for analytics)
/// </summary>
public async Task<QRCodeHistory?> GetQRByTrackingIdAsync(string trackingId)
{
try
{
return await _context.QRCodeHistory
.Find(q => q.TrackingId == trackingId && q.IsActive)
.FirstOrDefaultAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting QR by tracking ID {TrackingId}: {ErrorMessage}", trackingId, ex.Message);
return null;
}
}
/// <summary>
/// Increment scan count for QR code analytics
/// </summary>
public async Task IncrementQRScanCountAsync(string trackingId)
{
try
{
var filter = Builders<QRCodeHistory>.Filter.Eq(q => q.TrackingId, trackingId);
var update = Builders<QRCodeHistory>.Update
.Inc(q => q.ScanCount, 1)
.Set(q => q.LastAccessedAt, DateTime.UtcNow);
var result = await _context.QRCodeHistory.UpdateOneAsync(filter, update);
if (result.ModifiedCount > 0)
{
_logger.LogDebug("QR scan count incremented - TrackingId: {TrackingId}", trackingId);
}
else
{
_logger.LogWarning("Failed to increment scan count - TrackingId not found: {TrackingId}", trackingId);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error incrementing scan count for {TrackingId}: {ErrorMessage}", trackingId, ex.Message);
}
}
}
}