fix: add Node.js to Docker build stage for frontend compilation
- Install Node.js 18.x in Docker build stage - Add MongoDB DataProtection for Swarm compatibility - Enables shared authentication keys across multiple replicas 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
870436c1ab
commit
b54aa295ac
@ -1,326 +1,326 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authentication.Google;
|
||||
using Microsoft.AspNetCore.Authentication.MicrosoftAccount;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using QRRapidoApp.Models.ViewModels;
|
||||
using QRRapidoApp.Services;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
|
||||
namespace QRRapidoApp.Controllers
|
||||
{
|
||||
public class AccountController : Controller
|
||||
{
|
||||
private readonly IUserService _userService;
|
||||
private readonly AdDisplayService _adDisplayService;
|
||||
private readonly ILogger<AccountController> _logger;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly IDataProtector _protector;
|
||||
|
||||
public AccountController(IUserService userService, AdDisplayService adDisplayService,
|
||||
ILogger<AccountController> logger, IConfiguration configuration,
|
||||
IDataProtectionProvider dataProtection)
|
||||
{
|
||||
_userService = userService;
|
||||
_adDisplayService = adDisplayService;
|
||||
_logger = logger;
|
||||
_configuration = configuration;
|
||||
_protector = dataProtection.CreateProtector("OAuth.StateProtection");
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult Login(string returnUrl = "/")
|
||||
{
|
||||
_adDisplayService.SetViewBagAds(ViewBag);
|
||||
ViewBag.ReturnUrl = returnUrl;
|
||||
return View();
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult LoginGoogle(string returnUrl = "/")
|
||||
{
|
||||
var baseUrl = _configuration.GetSection("App:BaseUrl").Value;
|
||||
|
||||
// Criar state com dados criptografados em vez de sessão
|
||||
var stateData = new OAuthStateData
|
||||
{
|
||||
ReturnUrl = returnUrl,
|
||||
Nonce = Guid.NewGuid().ToString(),
|
||||
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
|
||||
};
|
||||
|
||||
var stateJson = JsonSerializer.Serialize(stateData);
|
||||
var protectedState = _protector.Protect(stateJson);
|
||||
var encodedState = Convert.ToBase64String(Encoding.UTF8.GetBytes(protectedState));
|
||||
|
||||
var properties = new AuthenticationProperties
|
||||
{
|
||||
RedirectUri = $"{baseUrl}{Url.Action("GoogleCallback")}",
|
||||
Items = { { "state", encodedState } }
|
||||
};
|
||||
|
||||
return Challenge(properties, GoogleDefaults.AuthenticationScheme);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult LoginMicrosoft(string returnUrl = "/")
|
||||
{
|
||||
var baseUrl = _configuration.GetSection("App:BaseUrl").Value;
|
||||
|
||||
// Mesmo processo para Microsoft
|
||||
var stateData = new OAuthStateData
|
||||
{
|
||||
ReturnUrl = returnUrl,
|
||||
Nonce = Guid.NewGuid().ToString(),
|
||||
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
|
||||
};
|
||||
|
||||
var stateJson = JsonSerializer.Serialize(stateData);
|
||||
var protectedState = _protector.Protect(stateJson);
|
||||
var encodedState = Convert.ToBase64String(Encoding.UTF8.GetBytes(protectedState));
|
||||
|
||||
var redirectUrl = returnUrl == "/"
|
||||
? $"{baseUrl}/Account/MicrosoftCallback"
|
||||
: $"{baseUrl}/Account/MicrosoftCallback";
|
||||
|
||||
var properties = new AuthenticationProperties
|
||||
{
|
||||
RedirectUri = redirectUrl,
|
||||
Items = { { "state", encodedState } }
|
||||
};
|
||||
|
||||
return Challenge(properties, MicrosoftAccountDefaults.AuthenticationScheme);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GoogleCallback(string state = null)
|
||||
{
|
||||
var returnUrl = await HandleExternalLoginCallbackAsync(GoogleDefaults.AuthenticationScheme, state);
|
||||
return Redirect(returnUrl ?? "/");
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> MicrosoftCallback(string state = null)
|
||||
{
|
||||
var returnUrl = await HandleExternalLoginCallbackAsync(MicrosoftAccountDefaults.AuthenticationScheme, state);
|
||||
return Redirect(returnUrl ?? "/");
|
||||
}
|
||||
|
||||
private async Task<string> HandleExternalLoginCallbackAsync(string scheme, string state = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_adDisplayService.SetViewBagAds(ViewBag);
|
||||
|
||||
// Recuperar returnUrl do state em vez da sessão
|
||||
string returnUrl = "/";
|
||||
if (!string.IsNullOrEmpty(state))
|
||||
{
|
||||
try
|
||||
{
|
||||
var decodedState = Encoding.UTF8.GetString(Convert.FromBase64String(state));
|
||||
var unprotectedState = _protector.Unprotect(decodedState);
|
||||
var stateData = JsonSerializer.Deserialize<OAuthStateData>(unprotectedState);
|
||||
|
||||
// Validar timestamp (não mais que 10 minutos)
|
||||
var maxAge = DateTimeOffset.UtcNow.ToUnixTimeSeconds() - (10 * 60);
|
||||
if (stateData.Timestamp > maxAge)
|
||||
{
|
||||
returnUrl = stateData.ReturnUrl ?? "/";
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning($"OAuth state expired for scheme {scheme}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, $"Failed to decode OAuth state for scheme {scheme}");
|
||||
}
|
||||
}
|
||||
|
||||
var result = await HttpContext.AuthenticateAsync(scheme);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
_logger.LogWarning($"External authentication failed for scheme {scheme}");
|
||||
return "/Account/Login";
|
||||
}
|
||||
|
||||
var email = result.Principal?.FindFirst(ClaimTypes.Email)?.Value;
|
||||
var name = result.Principal?.FindFirst(ClaimTypes.Name)?.Value;
|
||||
var providerId = result.Principal?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
|
||||
if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(providerId))
|
||||
{
|
||||
_logger.LogWarning($"Missing required claims from {scheme} authentication");
|
||||
return "/Account/Login";
|
||||
}
|
||||
|
||||
// Find or create user
|
||||
var user = await _userService.GetUserByProviderAsync(scheme, providerId);
|
||||
if (user == null)
|
||||
{
|
||||
user = await _userService.CreateUserAsync(email, name ?? email, scheme, providerId);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _userService.UpdateLastLoginAsync(user.Id);
|
||||
}
|
||||
|
||||
// Create application claims
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
new Claim(ClaimTypes.NameIdentifier, user.Id),
|
||||
new Claim(ClaimTypes.Email, user.Email),
|
||||
new Claim(ClaimTypes.Name, user.Name),
|
||||
new Claim("Provider", user.Provider),
|
||||
new Claim("IsPremium", user.IsPremium.ToString())
|
||||
};
|
||||
|
||||
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
var authProperties = new AuthenticationProperties
|
||||
{
|
||||
IsPersistent = true,
|
||||
ExpiresUtc = DateTimeOffset.UtcNow.AddDays(30)
|
||||
};
|
||||
|
||||
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
|
||||
new ClaimsPrincipal(claimsIdentity), authProperties);
|
||||
|
||||
return returnUrl;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Error in external login callback for {scheme}");
|
||||
return "/Account/Login";
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> Logout()
|
||||
{
|
||||
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
return RedirectToAction("Index", "Home");
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> Profile()
|
||||
{
|
||||
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
if (string.IsNullOrEmpty(userId))
|
||||
{
|
||||
return RedirectToAction("Login");
|
||||
}
|
||||
|
||||
var user = await _userService.GetUserAsync(userId);
|
||||
if (user == null)
|
||||
{
|
||||
return RedirectToAction("Login");
|
||||
}
|
||||
|
||||
ViewBag.QRHistory = await _userService.GetUserQRHistoryAsync(userId, 10);
|
||||
ViewBag.MonthlyQRCount = await _userService.GetQRCountThisMonthAsync(userId);
|
||||
ViewBag.IsPremium = await _adDisplayService.HasValidPremiumSubscription(userId);
|
||||
_adDisplayService.SetViewBagAds(ViewBag);
|
||||
|
||||
return View(user);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> AdFreeStatus()
|
||||
{
|
||||
var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
|
||||
if (string.IsNullOrEmpty(userId))
|
||||
{
|
||||
return Json(new AdFreeStatusViewModel
|
||||
{
|
||||
IsAdFree = false,
|
||||
TimeRemaining = 0,
|
||||
IsPremium = false
|
||||
});
|
||||
}
|
||||
|
||||
var shouldShowAds = await _adDisplayService.ShouldShowAds(userId);
|
||||
var isPremium = await _adDisplayService.HasValidPremiumSubscription(userId);
|
||||
var expiryDate = await _adDisplayService.GetAdFreeExpiryDate(userId);
|
||||
var status = await _adDisplayService.GetAdFreeStatusAsync(userId);
|
||||
|
||||
return Json(new AdFreeStatusViewModel
|
||||
{
|
||||
IsAdFree = !shouldShowAds,
|
||||
TimeRemaining = isPremium ? int.MaxValue : 0,
|
||||
IsPremium = isPremium,
|
||||
ExpiryDate = expiryDate,
|
||||
SessionType = status
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> ExtendAdFreeTime(int minutes)
|
||||
{
|
||||
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
|
||||
// Método removido - sem extensão de tempo ad-free
|
||||
return Json(new { success = false, message = "Feature not available" });
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> History()
|
||||
{
|
||||
_adDisplayService.SetViewBagAds(ViewBag);
|
||||
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
if (string.IsNullOrEmpty(userId))
|
||||
{
|
||||
return RedirectToAction("Login");
|
||||
}
|
||||
|
||||
var history = await _userService.GetUserQRHistoryAsync(userId, 50);
|
||||
return View(history);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> UpdatePreferences(string language)
|
||||
{
|
||||
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
if (string.IsNullOrEmpty(userId))
|
||||
{
|
||||
return Json(new { success = false });
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var user = await _userService.GetUserAsync(userId);
|
||||
if (user != null)
|
||||
{
|
||||
user.PreferredLanguage = language;
|
||||
await _userService.UpdateUserAsync(user);
|
||||
return Json(new { success = true });
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Error updating preferences for user {userId}");
|
||||
}
|
||||
|
||||
return Json(new { success = false });
|
||||
}
|
||||
}
|
||||
|
||||
// Classe para dados do state
|
||||
public class OAuthStateData
|
||||
{
|
||||
public string ReturnUrl { get; set; } = "/";
|
||||
public string Nonce { get; set; } = "";
|
||||
public long Timestamp { get; set; }
|
||||
}
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authentication.Google;
|
||||
using Microsoft.AspNetCore.Authentication.MicrosoftAccount;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using QRRapidoApp.Models.ViewModels;
|
||||
using QRRapidoApp.Services;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
|
||||
namespace QRRapidoApp.Controllers
|
||||
{
|
||||
public class AccountController : Controller
|
||||
{
|
||||
private readonly IUserService _userService;
|
||||
private readonly AdDisplayService _adDisplayService;
|
||||
private readonly ILogger<AccountController> _logger;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly IDataProtector _protector;
|
||||
|
||||
public AccountController(IUserService userService, AdDisplayService adDisplayService,
|
||||
ILogger<AccountController> logger, IConfiguration configuration,
|
||||
IDataProtectionProvider dataProtection)
|
||||
{
|
||||
_userService = userService;
|
||||
_adDisplayService = adDisplayService;
|
||||
_logger = logger;
|
||||
_configuration = configuration;
|
||||
_protector = dataProtection.CreateProtector("OAuth.StateProtection");
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult Login(string returnUrl = "/")
|
||||
{
|
||||
_adDisplayService.SetViewBagAds(ViewBag);
|
||||
ViewBag.ReturnUrl = returnUrl;
|
||||
return View();
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult LoginGoogle(string returnUrl = "/")
|
||||
{
|
||||
var baseUrl = _configuration.GetSection("App:BaseUrl").Value;
|
||||
|
||||
// Criar state com dados criptografados em vez de sessão
|
||||
var stateData = new OAuthStateData
|
||||
{
|
||||
ReturnUrl = returnUrl,
|
||||
Nonce = Guid.NewGuid().ToString(),
|
||||
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
|
||||
};
|
||||
|
||||
var stateJson = JsonSerializer.Serialize(stateData);
|
||||
var protectedState = _protector.Protect(stateJson);
|
||||
var encodedState = Convert.ToBase64String(Encoding.UTF8.GetBytes(protectedState));
|
||||
|
||||
var properties = new AuthenticationProperties
|
||||
{
|
||||
RedirectUri = $"{baseUrl}{Url.Action("GoogleCallback")}",
|
||||
Items = { { "state", encodedState } }
|
||||
};
|
||||
|
||||
return Challenge(properties, GoogleDefaults.AuthenticationScheme);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult LoginMicrosoft(string returnUrl = "/")
|
||||
{
|
||||
var baseUrl = _configuration.GetSection("App:BaseUrl").Value;
|
||||
|
||||
// Mesmo processo para Microsoft
|
||||
var stateData = new OAuthStateData
|
||||
{
|
||||
ReturnUrl = returnUrl,
|
||||
Nonce = Guid.NewGuid().ToString(),
|
||||
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
|
||||
};
|
||||
|
||||
var stateJson = JsonSerializer.Serialize(stateData);
|
||||
var protectedState = _protector.Protect(stateJson);
|
||||
var encodedState = Convert.ToBase64String(Encoding.UTF8.GetBytes(protectedState));
|
||||
|
||||
var redirectUrl = returnUrl == "/"
|
||||
? $"{baseUrl}/Account/MicrosoftCallback"
|
||||
: $"{baseUrl}/Account/MicrosoftCallback";
|
||||
|
||||
var properties = new AuthenticationProperties
|
||||
{
|
||||
RedirectUri = redirectUrl,
|
||||
Items = { { "state", encodedState } }
|
||||
};
|
||||
|
||||
return Challenge(properties, MicrosoftAccountDefaults.AuthenticationScheme);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GoogleCallback(string state = null)
|
||||
{
|
||||
var returnUrl = await HandleExternalLoginCallbackAsync(GoogleDefaults.AuthenticationScheme, state);
|
||||
return Redirect(returnUrl ?? "/");
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> MicrosoftCallback(string state = null)
|
||||
{
|
||||
var returnUrl = await HandleExternalLoginCallbackAsync(MicrosoftAccountDefaults.AuthenticationScheme, state);
|
||||
return Redirect(returnUrl ?? "/");
|
||||
}
|
||||
|
||||
private async Task<string> HandleExternalLoginCallbackAsync(string scheme, string state = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_adDisplayService.SetViewBagAds(ViewBag);
|
||||
|
||||
// Recuperar returnUrl do state em vez da sessão
|
||||
string returnUrl = "/";
|
||||
if (!string.IsNullOrEmpty(state))
|
||||
{
|
||||
try
|
||||
{
|
||||
var decodedState = Encoding.UTF8.GetString(Convert.FromBase64String(state));
|
||||
var unprotectedState = _protector.Unprotect(decodedState);
|
||||
var stateData = JsonSerializer.Deserialize<OAuthStateData>(unprotectedState);
|
||||
|
||||
// Validar timestamp (não mais que 10 minutos)
|
||||
var maxAge = DateTimeOffset.UtcNow.ToUnixTimeSeconds() - (10 * 60);
|
||||
if (stateData.Timestamp > maxAge)
|
||||
{
|
||||
returnUrl = stateData.ReturnUrl ?? "/";
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning($"OAuth state expired for scheme {scheme}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, $"Failed to decode OAuth state for scheme {scheme}");
|
||||
}
|
||||
}
|
||||
|
||||
var result = await HttpContext.AuthenticateAsync(scheme);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
_logger.LogWarning($"External authentication failed for scheme {scheme}");
|
||||
return "/Account/Login";
|
||||
}
|
||||
|
||||
var email = result.Principal?.FindFirst(ClaimTypes.Email)?.Value;
|
||||
var name = result.Principal?.FindFirst(ClaimTypes.Name)?.Value;
|
||||
var providerId = result.Principal?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
|
||||
if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(providerId))
|
||||
{
|
||||
_logger.LogWarning($"Missing required claims from {scheme} authentication");
|
||||
return "/Account/Login";
|
||||
}
|
||||
|
||||
// Find or create user
|
||||
var user = await _userService.GetUserByProviderAsync(scheme, providerId);
|
||||
if (user == null)
|
||||
{
|
||||
user = await _userService.CreateUserAsync(email, name ?? email, scheme, providerId);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _userService.UpdateLastLoginAsync(user.Id);
|
||||
}
|
||||
|
||||
// Create application claims
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
new Claim(ClaimTypes.NameIdentifier, user.Id),
|
||||
new Claim(ClaimTypes.Email, user.Email),
|
||||
new Claim(ClaimTypes.Name, user.Name),
|
||||
new Claim("Provider", user.Provider),
|
||||
new Claim("IsPremium", user.IsPremium.ToString())
|
||||
};
|
||||
|
||||
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
var authProperties = new AuthenticationProperties
|
||||
{
|
||||
IsPersistent = true,
|
||||
ExpiresUtc = DateTimeOffset.UtcNow.AddDays(30)
|
||||
};
|
||||
|
||||
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
|
||||
new ClaimsPrincipal(claimsIdentity), authProperties);
|
||||
|
||||
return returnUrl;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Error in external login callback for {scheme}");
|
||||
return "/Account/Login";
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> Logout()
|
||||
{
|
||||
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
return RedirectToAction("Index", "Home");
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> Profile()
|
||||
{
|
||||
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
if (string.IsNullOrEmpty(userId))
|
||||
{
|
||||
return RedirectToAction("Login");
|
||||
}
|
||||
|
||||
var user = await _userService.GetUserAsync(userId);
|
||||
if (user == null)
|
||||
{
|
||||
return RedirectToAction("Login");
|
||||
}
|
||||
|
||||
ViewBag.QRHistory = await _userService.GetUserQRHistoryAsync(userId, 10);
|
||||
ViewBag.MonthlyQRCount = await _userService.GetQRCountThisMonthAsync(userId);
|
||||
ViewBag.IsPremium = await _adDisplayService.HasValidPremiumSubscription(userId);
|
||||
_adDisplayService.SetViewBagAds(ViewBag);
|
||||
|
||||
return View(user);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> AdFreeStatus()
|
||||
{
|
||||
var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
|
||||
if (string.IsNullOrEmpty(userId))
|
||||
{
|
||||
return Json(new AdFreeStatusViewModel
|
||||
{
|
||||
IsAdFree = false,
|
||||
TimeRemaining = 0,
|
||||
IsPremium = false
|
||||
});
|
||||
}
|
||||
|
||||
var shouldShowAds = await _adDisplayService.ShouldShowAds(userId);
|
||||
var isPremium = await _adDisplayService.HasValidPremiumSubscription(userId);
|
||||
var expiryDate = await _adDisplayService.GetAdFreeExpiryDate(userId);
|
||||
var status = await _adDisplayService.GetAdFreeStatusAsync(userId);
|
||||
|
||||
return Json(new AdFreeStatusViewModel
|
||||
{
|
||||
IsAdFree = !shouldShowAds,
|
||||
TimeRemaining = isPremium ? int.MaxValue : 0,
|
||||
IsPremium = isPremium,
|
||||
ExpiryDate = expiryDate,
|
||||
SessionType = status
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> ExtendAdFreeTime(int minutes)
|
||||
{
|
||||
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
|
||||
// Método removido - sem extensão de tempo ad-free
|
||||
return Json(new { success = false, message = "Feature not available" });
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> History()
|
||||
{
|
||||
_adDisplayService.SetViewBagAds(ViewBag);
|
||||
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
if (string.IsNullOrEmpty(userId))
|
||||
{
|
||||
return RedirectToAction("Login");
|
||||
}
|
||||
|
||||
var history = await _userService.GetUserQRHistoryAsync(userId, 50);
|
||||
return View(history);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> UpdatePreferences(string language)
|
||||
{
|
||||
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
if (string.IsNullOrEmpty(userId))
|
||||
{
|
||||
return Json(new { success = false });
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var user = await _userService.GetUserAsync(userId);
|
||||
if (user != null)
|
||||
{
|
||||
user.PreferredLanguage = language;
|
||||
await _userService.UpdateUserAsync(user);
|
||||
return Json(new { success = true });
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Error updating preferences for user {userId}");
|
||||
}
|
||||
|
||||
return Json(new { success = false });
|
||||
}
|
||||
}
|
||||
|
||||
// Classe para dados do state
|
||||
public class OAuthStateData
|
||||
{
|
||||
public string ReturnUrl { get; set; } = "/";
|
||||
public string Nonce { get; set; } = "";
|
||||
public long Timestamp { get; set; }
|
||||
}
|
||||
}
|
||||
@ -1,306 +1,306 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using QRRapidoApp.Models;
|
||||
using QRRapidoApp.Services;
|
||||
using System.Diagnostics;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
namespace QRRapidoApp.Controllers
|
||||
{
|
||||
public class HomeController : Controller
|
||||
{
|
||||
private readonly ILogger<HomeController> _logger;
|
||||
private readonly AdDisplayService _adDisplayService;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IConfiguration _config;
|
||||
private readonly IStringLocalizer<QRRapidoApp.Resources.SharedResource> _localizer;
|
||||
|
||||
public HomeController(
|
||||
ILogger<HomeController> logger,
|
||||
AdDisplayService adDisplayService,
|
||||
IUserService userService,
|
||||
IConfiguration config,
|
||||
IStringLocalizer<QRRapidoApp.Resources.SharedResource> localizer
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
_adDisplayService = adDisplayService;
|
||||
_userService = userService;
|
||||
_config = config;
|
||||
_localizer = localizer;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Index()
|
||||
{
|
||||
var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
|
||||
ViewBag.ShowAds = await _adDisplayService.ShouldShowAds(userId);
|
||||
ViewBag.IsPremium = await _adDisplayService.HasValidPremiumSubscription(userId ?? "");
|
||||
ViewBag.IsAuthenticated = User.Identity?.IsAuthenticated ?? false;
|
||||
ViewBag.UserName = User.Identity?.Name ?? "";
|
||||
_adDisplayService.SetViewBagAds(ViewBag);
|
||||
|
||||
// SEO and Analytics data
|
||||
ViewBag.Title = _config["App:TaglinePT"];
|
||||
ViewBag.Keywords = _config["SEO:KeywordsPT"];
|
||||
ViewBag.Description = _localizer["QRGenerateDescription"];
|
||||
|
||||
// User stats for logged in users
|
||||
if (!string.IsNullOrEmpty(userId))
|
||||
{
|
||||
ViewBag.DailyQRCount = await _userService.GetDailyQRCountAsync(userId);
|
||||
ViewBag.MonthlyQRCount = await _userService.GetQRCountThisMonthAsync(userId);
|
||||
}
|
||||
|
||||
return View();
|
||||
}
|
||||
|
||||
public IActionResult Privacy()
|
||||
{
|
||||
var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
|
||||
ViewBag.ShowAds = _adDisplayService.ShouldShowAds(userId).Result;
|
||||
ViewBag.IsPremium = _adDisplayService.HasValidPremiumSubscription(userId ?? "").Result;
|
||||
ViewBag.IsAuthenticated = User.Identity?.IsAuthenticated ?? false;
|
||||
ViewBag.UserName = User.Identity?.Name ?? "";
|
||||
|
||||
ViewBag.Title = _localizer["PrivacyPolicyTitle"];
|
||||
ViewBag.Description = _localizer["PrivacyPolicyDescription"];
|
||||
_adDisplayService.SetViewBagAds(ViewBag);
|
||||
|
||||
return View();
|
||||
}
|
||||
|
||||
public IActionResult Terms()
|
||||
{
|
||||
var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
|
||||
ViewBag.ShowAds = _adDisplayService.ShouldShowAds(userId).Result;
|
||||
ViewBag.IsPremium = _adDisplayService.HasValidPremiumSubscription(userId ?? "").Result;
|
||||
ViewBag.IsAuthenticated = User.Identity?.IsAuthenticated ?? false;
|
||||
ViewBag.UserName = User.Identity?.Name ?? "";
|
||||
|
||||
ViewBag.Title = _localizer["TermsOfUseTitle"];
|
||||
ViewBag.Description = _localizer["TermsOfUseDescription"];
|
||||
_adDisplayService.SetViewBagAds(ViewBag);
|
||||
|
||||
return View();
|
||||
}
|
||||
|
||||
public IActionResult About()
|
||||
{
|
||||
var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
|
||||
ViewBag.ShowAds = _adDisplayService.ShouldShowAds(userId).Result;
|
||||
ViewBag.IsPremium = _adDisplayService.HasValidPremiumSubscription(userId ?? "").Result;
|
||||
ViewBag.IsAuthenticated = User.Identity?.IsAuthenticated ?? false;
|
||||
ViewBag.UserName = User.Identity?.Name ?? "";
|
||||
|
||||
ViewBag.Title = _localizer["AboutPageTitle"];
|
||||
ViewBag.Description = _localizer["AboutPageDescription"];
|
||||
_adDisplayService.SetViewBagAds(ViewBag);
|
||||
|
||||
return View();
|
||||
}
|
||||
|
||||
public IActionResult Contact()
|
||||
{
|
||||
var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
|
||||
ViewBag.ShowAds = _adDisplayService.ShouldShowAds(userId).Result;
|
||||
ViewBag.IsPremium = _adDisplayService.HasValidPremiumSubscription(userId ?? "").Result;
|
||||
ViewBag.IsAuthenticated = User.Identity?.IsAuthenticated ?? false;
|
||||
ViewBag.UserName = User.Identity?.Name ?? "";
|
||||
|
||||
ViewBag.Title = _localizer["ContactPageTitle"];
|
||||
ViewBag.Description = _localizer["ContactPageDescription"];
|
||||
_adDisplayService.SetViewBagAds(ViewBag);
|
||||
|
||||
return View();
|
||||
}
|
||||
|
||||
public IActionResult FAQ()
|
||||
{
|
||||
var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
|
||||
ViewBag.ShowAds = _adDisplayService.ShouldShowAds(userId).Result;
|
||||
ViewBag.IsPremium = _adDisplayService.HasValidPremiumSubscription(userId ?? "").Result;
|
||||
ViewBag.IsAuthenticated = User.Identity?.IsAuthenticated ?? false;
|
||||
ViewBag.UserName = User.Identity?.Name ?? "";
|
||||
|
||||
ViewBag.Title = _localizer["FAQPageTitle"];
|
||||
ViewBag.Description = _localizer["FAQPageDescription"];
|
||||
_adDisplayService.SetViewBagAds(ViewBag);
|
||||
|
||||
return View();
|
||||
}
|
||||
|
||||
public IActionResult HowToUse()
|
||||
{
|
||||
var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
|
||||
ViewBag.ShowAds = _adDisplayService.ShouldShowAds(userId).Result;
|
||||
ViewBag.IsPremium = _adDisplayService.HasValidPremiumSubscription(userId ?? "").Result;
|
||||
ViewBag.IsAuthenticated = User.Identity?.IsAuthenticated ?? false;
|
||||
ViewBag.UserName = User.Identity?.Name ?? "";
|
||||
|
||||
ViewBag.Title = _localizer["HowToUsePageTitle"];
|
||||
ViewBag.Description = _localizer["HowToUsePageDescription"];
|
||||
_adDisplayService.SetViewBagAds(ViewBag);
|
||||
|
||||
return View();
|
||||
}
|
||||
|
||||
//[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||
//public IActionResult Error()
|
||||
//{
|
||||
// return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
|
||||
//}
|
||||
|
||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||
public IActionResult Error()
|
||||
{
|
||||
var errorCode = Request.Query["code"].ToString();
|
||||
var errorMessage = "";
|
||||
|
||||
// Interpretar códigos de erro específicos
|
||||
if (errorCode.StartsWith("M.C506"))
|
||||
{
|
||||
errorMessage = "Erro de autenticação. Verifique suas credenciais e tente novamente.";
|
||||
}
|
||||
|
||||
ViewBag.ErrorCode = errorCode;
|
||||
ViewBag.ErrorMessage = errorMessage;
|
||||
|
||||
return View(new ErrorViewModel
|
||||
{
|
||||
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier
|
||||
});
|
||||
}
|
||||
|
||||
// Dynamic QR redirect endpoint
|
||||
[Route("d/{id}")]
|
||||
public async Task<IActionResult> DynamicRedirect(string id)
|
||||
{
|
||||
try
|
||||
{
|
||||
_adDisplayService.SetViewBagAds(ViewBag);
|
||||
// This would lookup the dynamic QR content from cache/database
|
||||
// For now, return a placeholder
|
||||
return Redirect("https://qrrapido.site");
|
||||
}
|
||||
catch
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
// Health check endpoint
|
||||
[Route("health")]
|
||||
public IActionResult Health()
|
||||
{
|
||||
return Ok(new { status = "healthy", timestamp = DateTime.UtcNow });
|
||||
}
|
||||
|
||||
// Sitemap endpoint for SEO
|
||||
[Route("sitemap.xml")]
|
||||
public IActionResult Sitemap()
|
||||
{
|
||||
var sitemap = $@"<?xml version=""1.0"" encoding=""UTF-8""?>
|
||||
<urlset xmlns=""http://www.sitemaps.org/schemas/sitemap/0.9"">
|
||||
<url>
|
||||
<loc>https://qrrapido.site/</loc>
|
||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://qrrapido.site/pt/</loc>
|
||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://qrrapido.site/es/</loc>
|
||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://qrrapido.site/pt-BR/About</loc>
|
||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://qrrapido.site/es-PY/About</loc>
|
||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://qrrapido.site/pt-BR/Contact</loc>
|
||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://qrrapido.site/es-PY/Contact</loc>
|
||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://qrrapido.site/pt-BR/FAQ</loc>
|
||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://qrrapido.site/es-PY/FAQ</loc>
|
||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://qrrapido.site/pt-BR/HowToUse</loc>
|
||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://qrrapido.site/es-PY/HowToUse</loc>
|
||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://qrrapido.site/Premium/Upgrade</loc>
|
||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://qrrapido.site/privacy</loc>
|
||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.5</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://qrrapido.site/terms</loc>
|
||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.5</priority>
|
||||
</url>
|
||||
</urlset>";
|
||||
|
||||
return Content(sitemap, "application/xml");
|
||||
}
|
||||
}
|
||||
|
||||
//public class ErrorViewModel
|
||||
//{
|
||||
// public string? RequestId { get; set; }
|
||||
// public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||
//}
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using QRRapidoApp.Models;
|
||||
using QRRapidoApp.Services;
|
||||
using System.Diagnostics;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
namespace QRRapidoApp.Controllers
|
||||
{
|
||||
public class HomeController : Controller
|
||||
{
|
||||
private readonly ILogger<HomeController> _logger;
|
||||
private readonly AdDisplayService _adDisplayService;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IConfiguration _config;
|
||||
private readonly IStringLocalizer<QRRapidoApp.Resources.SharedResource> _localizer;
|
||||
|
||||
public HomeController(
|
||||
ILogger<HomeController> logger,
|
||||
AdDisplayService adDisplayService,
|
||||
IUserService userService,
|
||||
IConfiguration config,
|
||||
IStringLocalizer<QRRapidoApp.Resources.SharedResource> localizer
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
_adDisplayService = adDisplayService;
|
||||
_userService = userService;
|
||||
_config = config;
|
||||
_localizer = localizer;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Index()
|
||||
{
|
||||
var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
|
||||
ViewBag.ShowAds = await _adDisplayService.ShouldShowAds(userId);
|
||||
ViewBag.IsPremium = await _adDisplayService.HasValidPremiumSubscription(userId ?? "");
|
||||
ViewBag.IsAuthenticated = User.Identity?.IsAuthenticated ?? false;
|
||||
ViewBag.UserName = User.Identity?.Name ?? "";
|
||||
_adDisplayService.SetViewBagAds(ViewBag);
|
||||
|
||||
// SEO and Analytics data
|
||||
ViewBag.Title = _config["App:TaglinePT"];
|
||||
ViewBag.Keywords = _config["SEO:KeywordsPT"];
|
||||
ViewBag.Description = _localizer["QRGenerateDescription"];
|
||||
|
||||
// User stats for logged in users
|
||||
if (!string.IsNullOrEmpty(userId))
|
||||
{
|
||||
ViewBag.DailyQRCount = await _userService.GetDailyQRCountAsync(userId);
|
||||
ViewBag.MonthlyQRCount = await _userService.GetQRCountThisMonthAsync(userId);
|
||||
}
|
||||
|
||||
return View();
|
||||
}
|
||||
|
||||
public IActionResult Privacy()
|
||||
{
|
||||
var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
|
||||
ViewBag.ShowAds = _adDisplayService.ShouldShowAds(userId).Result;
|
||||
ViewBag.IsPremium = _adDisplayService.HasValidPremiumSubscription(userId ?? "").Result;
|
||||
ViewBag.IsAuthenticated = User.Identity?.IsAuthenticated ?? false;
|
||||
ViewBag.UserName = User.Identity?.Name ?? "";
|
||||
|
||||
ViewBag.Title = _localizer["PrivacyPolicyTitle"];
|
||||
ViewBag.Description = _localizer["PrivacyPolicyDescription"];
|
||||
_adDisplayService.SetViewBagAds(ViewBag);
|
||||
|
||||
return View();
|
||||
}
|
||||
|
||||
public IActionResult Terms()
|
||||
{
|
||||
var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
|
||||
ViewBag.ShowAds = _adDisplayService.ShouldShowAds(userId).Result;
|
||||
ViewBag.IsPremium = _adDisplayService.HasValidPremiumSubscription(userId ?? "").Result;
|
||||
ViewBag.IsAuthenticated = User.Identity?.IsAuthenticated ?? false;
|
||||
ViewBag.UserName = User.Identity?.Name ?? "";
|
||||
|
||||
ViewBag.Title = _localizer["TermsOfUseTitle"];
|
||||
ViewBag.Description = _localizer["TermsOfUseDescription"];
|
||||
_adDisplayService.SetViewBagAds(ViewBag);
|
||||
|
||||
return View();
|
||||
}
|
||||
|
||||
public IActionResult About()
|
||||
{
|
||||
var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
|
||||
ViewBag.ShowAds = _adDisplayService.ShouldShowAds(userId).Result;
|
||||
ViewBag.IsPremium = _adDisplayService.HasValidPremiumSubscription(userId ?? "").Result;
|
||||
ViewBag.IsAuthenticated = User.Identity?.IsAuthenticated ?? false;
|
||||
ViewBag.UserName = User.Identity?.Name ?? "";
|
||||
|
||||
ViewBag.Title = _localizer["AboutPageTitle"];
|
||||
ViewBag.Description = _localizer["AboutPageDescription"];
|
||||
_adDisplayService.SetViewBagAds(ViewBag);
|
||||
|
||||
return View();
|
||||
}
|
||||
|
||||
public IActionResult Contact()
|
||||
{
|
||||
var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
|
||||
ViewBag.ShowAds = _adDisplayService.ShouldShowAds(userId).Result;
|
||||
ViewBag.IsPremium = _adDisplayService.HasValidPremiumSubscription(userId ?? "").Result;
|
||||
ViewBag.IsAuthenticated = User.Identity?.IsAuthenticated ?? false;
|
||||
ViewBag.UserName = User.Identity?.Name ?? "";
|
||||
|
||||
ViewBag.Title = _localizer["ContactPageTitle"];
|
||||
ViewBag.Description = _localizer["ContactPageDescription"];
|
||||
_adDisplayService.SetViewBagAds(ViewBag);
|
||||
|
||||
return View();
|
||||
}
|
||||
|
||||
public IActionResult FAQ()
|
||||
{
|
||||
var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
|
||||
ViewBag.ShowAds = _adDisplayService.ShouldShowAds(userId).Result;
|
||||
ViewBag.IsPremium = _adDisplayService.HasValidPremiumSubscription(userId ?? "").Result;
|
||||
ViewBag.IsAuthenticated = User.Identity?.IsAuthenticated ?? false;
|
||||
ViewBag.UserName = User.Identity?.Name ?? "";
|
||||
|
||||
ViewBag.Title = _localizer["FAQPageTitle"];
|
||||
ViewBag.Description = _localizer["FAQPageDescription"];
|
||||
_adDisplayService.SetViewBagAds(ViewBag);
|
||||
|
||||
return View();
|
||||
}
|
||||
|
||||
public IActionResult HowToUse()
|
||||
{
|
||||
var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
|
||||
ViewBag.ShowAds = _adDisplayService.ShouldShowAds(userId).Result;
|
||||
ViewBag.IsPremium = _adDisplayService.HasValidPremiumSubscription(userId ?? "").Result;
|
||||
ViewBag.IsAuthenticated = User.Identity?.IsAuthenticated ?? false;
|
||||
ViewBag.UserName = User.Identity?.Name ?? "";
|
||||
|
||||
ViewBag.Title = _localizer["HowToUsePageTitle"];
|
||||
ViewBag.Description = _localizer["HowToUsePageDescription"];
|
||||
_adDisplayService.SetViewBagAds(ViewBag);
|
||||
|
||||
return View();
|
||||
}
|
||||
|
||||
//[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||
//public IActionResult Error()
|
||||
//{
|
||||
// return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
|
||||
//}
|
||||
|
||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||
public IActionResult Error()
|
||||
{
|
||||
var errorCode = Request.Query["code"].ToString();
|
||||
var errorMessage = "";
|
||||
|
||||
// Interpretar códigos de erro específicos
|
||||
if (errorCode.StartsWith("M.C506"))
|
||||
{
|
||||
errorMessage = "Erro de autenticação. Verifique suas credenciais e tente novamente.";
|
||||
}
|
||||
|
||||
ViewBag.ErrorCode = errorCode;
|
||||
ViewBag.ErrorMessage = errorMessage;
|
||||
|
||||
return View(new ErrorViewModel
|
||||
{
|
||||
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier
|
||||
});
|
||||
}
|
||||
|
||||
// Dynamic QR redirect endpoint
|
||||
[Route("d/{id}")]
|
||||
public async Task<IActionResult> DynamicRedirect(string id)
|
||||
{
|
||||
try
|
||||
{
|
||||
_adDisplayService.SetViewBagAds(ViewBag);
|
||||
// This would lookup the dynamic QR content from cache/database
|
||||
// For now, return a placeholder
|
||||
return Redirect("https://qrrapido.site");
|
||||
}
|
||||
catch
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
// Health check endpoint
|
||||
[Route("health")]
|
||||
public IActionResult Health()
|
||||
{
|
||||
return Ok(new { status = "healthy", timestamp = DateTime.UtcNow });
|
||||
}
|
||||
|
||||
// Sitemap endpoint for SEO
|
||||
[Route("sitemap.xml")]
|
||||
public IActionResult Sitemap()
|
||||
{
|
||||
var sitemap = $@"<?xml version=""1.0"" encoding=""UTF-8""?>
|
||||
<urlset xmlns=""http://www.sitemaps.org/schemas/sitemap/0.9"">
|
||||
<url>
|
||||
<loc>https://qrrapido.site/</loc>
|
||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://qrrapido.site/pt/</loc>
|
||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://qrrapido.site/es/</loc>
|
||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://qrrapido.site/pt-BR/About</loc>
|
||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://qrrapido.site/es-PY/About</loc>
|
||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://qrrapido.site/pt-BR/Contact</loc>
|
||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://qrrapido.site/es-PY/Contact</loc>
|
||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://qrrapido.site/pt-BR/FAQ</loc>
|
||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://qrrapido.site/es-PY/FAQ</loc>
|
||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://qrrapido.site/pt-BR/HowToUse</loc>
|
||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://qrrapido.site/es-PY/HowToUse</loc>
|
||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://qrrapido.site/Premium/Upgrade</loc>
|
||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://qrrapido.site/privacy</loc>
|
||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.5</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://qrrapido.site/terms</loc>
|
||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.5</priority>
|
||||
</url>
|
||||
</urlset>";
|
||||
|
||||
return Content(sitemap, "application/xml");
|
||||
}
|
||||
}
|
||||
|
||||
//public class ErrorViewModel
|
||||
//{
|
||||
// public string? RequestId { get; set; }
|
||||
// public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||
//}
|
||||
}
|
||||
@ -60,9 +60,9 @@ namespace QRRapidoApp.Controllers
|
||||
}
|
||||
|
||||
var countryCode = GetUserCountryCode();
|
||||
if (countryCode != lang && languages.Contains(lang))
|
||||
{
|
||||
countryCode = lang;
|
||||
if (countryCode != lang && languages.Contains(lang))
|
||||
{
|
||||
countryCode = lang;
|
||||
}
|
||||
|
||||
var priceId = plan.PricesByCountry.ContainsKey(countryCode)
|
||||
@ -136,7 +136,7 @@ namespace QRRapidoApp.Controllers
|
||||
private string GetUserCountryCode()
|
||||
{
|
||||
// Check current culture from URL first
|
||||
var culture = HttpContext.Request.RouteValues["culture"]?.ToString() ??
|
||||
var culture = HttpContext.Request.RouteValues["culture"]?.ToString() ??
|
||||
HttpContext.Features.Get<Microsoft.AspNetCore.Localization.IRequestCultureFeature>()?.RequestCulture?.Culture?.Name;
|
||||
|
||||
var countryMap = new Dictionary<string, string>
|
||||
@ -157,7 +157,7 @@ namespace QRRapidoApp.Controllers
|
||||
private string GetUserCountryCodeComplete()
|
||||
{
|
||||
// Check current culture from URL first
|
||||
var culture = HttpContext.Request.RouteValues["culture"]?.ToString() ??
|
||||
var culture = HttpContext.Request.RouteValues["culture"]?.ToString() ??
|
||||
HttpContext.Features.Get<Microsoft.AspNetCore.Localization.IRequestCultureFeature>()?.RequestCulture?.Culture?.Name;
|
||||
|
||||
if (languages.Contains(culture))
|
||||
|
||||
@ -16,17 +16,17 @@ namespace QRRapidoApp.Controllers
|
||||
private readonly IUserService _userService;
|
||||
private readonly AdDisplayService _adService;
|
||||
private readonly ILogger<QRController> _logger;
|
||||
private readonly IStringLocalizer<QRRapidoApp.Resources.SharedResource> _localizer;
|
||||
private readonly AdDisplayService _adDisplayService;
|
||||
|
||||
private readonly IStringLocalizer<QRRapidoApp.Resources.SharedResource> _localizer;
|
||||
private readonly AdDisplayService _adDisplayService;
|
||||
|
||||
public QRController(IQRCodeService qrService, IUserService userService, AdDisplayService adService, ILogger<QRController> logger, IStringLocalizer<QRRapidoApp.Resources.SharedResource> localizer, AdDisplayService adDisplayService)
|
||||
{
|
||||
_qrService = qrService;
|
||||
_userService = userService;
|
||||
_adService = adService;
|
||||
_logger = logger;
|
||||
_localizer = localizer;
|
||||
_adDisplayService = adDisplayService;
|
||||
_localizer = localizer;
|
||||
_adDisplayService = adDisplayService;
|
||||
}
|
||||
|
||||
[HttpPost("GenerateRapid")]
|
||||
@ -36,7 +36,7 @@ namespace QRRapidoApp.Controllers
|
||||
var requestId = Guid.NewGuid().ToString("N")[..8];
|
||||
var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
var isAuthenticated = User?.Identity?.IsAuthenticated ?? false;
|
||||
|
||||
|
||||
using (_logger.BeginScope(new Dictionary<string, object>
|
||||
{
|
||||
["RequestId"] = requestId,
|
||||
@ -468,7 +468,7 @@ namespace QRRapidoApp.Controllers
|
||||
public async Task<IActionResult> GetHistory(int limit = 20)
|
||||
{
|
||||
try
|
||||
{
|
||||
{
|
||||
var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
if (string.IsNullOrEmpty(userId))
|
||||
{
|
||||
@ -489,7 +489,7 @@ namespace QRRapidoApp.Controllers
|
||||
public async Task<IActionResult> GetUserStats()
|
||||
{
|
||||
try
|
||||
{
|
||||
{
|
||||
var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
if (string.IsNullOrEmpty(userId))
|
||||
{
|
||||
|
||||
@ -2,6 +2,10 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||
WORKDIR /src
|
||||
|
||||
# Install Node.js for frontend build
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
|
||||
&& apt-get install -y nodejs
|
||||
|
||||
# Copy csproj and restore as distinct layers
|
||||
COPY *.csproj ./
|
||||
RUN dotnet restore --runtime linux-arm64
|
||||
@ -22,7 +26,7 @@ RUN dotnet publish "QRRapidoApp.csproj" -c Release -o /app/publish \
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
|
||||
WORKDIR /app
|
||||
|
||||
# Install libgdiplus for System.Drawing (se necessário para QR codes)
|
||||
# Install libgdiplus for System.Drawing (se necess<EFBFBD>rio para QR codes)
|
||||
RUN apt-get update && apt-get install -y libgdiplus && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy published app
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
namespace QRRapidoApp.Models
|
||||
{
|
||||
public class ErrorViewModel
|
||||
{
|
||||
public string? RequestId { get; set; }
|
||||
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||
public string? ErrorCode { get; set; }
|
||||
public string? ErrorMessage { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
namespace QRRapidoApp.Models
|
||||
{
|
||||
public class ErrorViewModel
|
||||
{
|
||||
public string? RequestId { get; set; }
|
||||
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||
public string? ErrorCode { get; set; }
|
||||
public string? ErrorMessage { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
{
|
||||
"profiles": {
|
||||
"QRRapidoApp": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"applicationUrl": "https://localhost:52428;http://192.168.0.85:52429;http://localhost:52429"
|
||||
}
|
||||
}
|
||||
{
|
||||
"profiles": {
|
||||
"QRRapidoApp": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"applicationUrl": "https://localhost:52428;http://192.168.0.85:52429;http://localhost:52429"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,30 +1,30 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.13.35818.85
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QRRapidoApp", "QRRapidoApp.csproj", "{8AF92774-40E8-830E-08B3-67F0A0B91DDE}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "RootFolder", "RootFolder", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.github\workflows\deploy.yml = .github\workflows\deploy.yml
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{8AF92774-40E8-830E-08B3-67F0A0B91DDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8AF92774-40E8-830E-08B3-67F0A0B91DDE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8AF92774-40E8-830E-08B3-67F0A0B91DDE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8AF92774-40E8-830E-08B3-67F0A0B91DDE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {9E53D8E2-0957-4925-B347-404E3B14587B}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.13.35818.85
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QRRapidoApp", "QRRapidoApp.csproj", "{8AF92774-40E8-830E-08B3-67F0A0B91DDE}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "RootFolder", "RootFolder", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.github\workflows\deploy.yml = .github\workflows\deploy.yml
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{8AF92774-40E8-830E-08B3-67F0A0B91DDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8AF92774-40E8-830E-08B3-67F0A0B91DDE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8AF92774-40E8-830E-08B3-67F0A0B91DDE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8AF92774-40E8-830E-08B3-67F0A0B91DDE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {9E53D8E2-0957-4925-B347-404E3B14587B}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
1440
Resources/SharedResource.Designer.cs
generated
1440
Resources/SharedResource.Designer.cs
generated
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,429 +1,429 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Tagline" xml:space="preserve">
|
||||
<value>Generate QR codes in seconds!</value>
|
||||
</data>
|
||||
<data name="GenerateQR" xml:space="preserve">
|
||||
<value>Generate QR Code</value>
|
||||
</data>
|
||||
<data name="QRType" xml:space="preserve">
|
||||
<value>QR Code Type</value>
|
||||
</data>
|
||||
<data name="Content" xml:space="preserve">
|
||||
<value>Content</value>
|
||||
</data>
|
||||
<data name="URLType" xml:space="preserve">
|
||||
<value>URL/Link</value>
|
||||
</data>
|
||||
<data name="TextType" xml:space="preserve">
|
||||
<value>Plain Text</value>
|
||||
</data>
|
||||
<data name="WiFiType" xml:space="preserve">
|
||||
<value>WiFi</value>
|
||||
</data>
|
||||
<data name="VCardType" xml:space="preserve">
|
||||
<value>Business Card</value>
|
||||
</data>
|
||||
<data name="SMSType" xml:space="preserve">
|
||||
<value>SMS</value>
|
||||
</data>
|
||||
<data name="EmailType" xml:space="preserve">
|
||||
<value>Email</value>
|
||||
</data>
|
||||
<data name="DynamicType" xml:space="preserve">
|
||||
<value>Dynamic QR (Premium)</value>
|
||||
</data>
|
||||
<data name="QuickStyle" xml:space="preserve">
|
||||
<value>Quick Style</value>
|
||||
</data>
|
||||
<data name="ClassicStyle" xml:space="preserve">
|
||||
<value>Classic</value>
|
||||
</data>
|
||||
<data name="ModernStyle" xml:space="preserve">
|
||||
<value>Modern</value>
|
||||
</data>
|
||||
<data name="ColorfulStyle" xml:space="preserve">
|
||||
<value>Colorful</value>
|
||||
</data>
|
||||
<data name="ContentPlaceholder" xml:space="preserve">
|
||||
<value>Enter your QR code content here...</value>
|
||||
</data>
|
||||
<data name="AdvancedCustomization" xml:space="preserve">
|
||||
<value>Advanced Customization</value>
|
||||
</data>
|
||||
<data name="PrimaryColor" xml:space="preserve">
|
||||
<value>Primary Color</value>
|
||||
</data>
|
||||
<data name="BackgroundColor" xml:space="preserve">
|
||||
<value>Background Color</value>
|
||||
</data>
|
||||
<data name="Size" xml:space="preserve">
|
||||
<value>Size</value>
|
||||
</data>
|
||||
<data name="Margin" xml:space="preserve">
|
||||
<value>Margin</value>
|
||||
</data>
|
||||
<data name="Logo" xml:space="preserve">
|
||||
<value>Logo/Icon</value>
|
||||
</data>
|
||||
<data name="CornerStyle" xml:space="preserve">
|
||||
<value>Corner Style</value>
|
||||
</data>
|
||||
<data name="GenerateRapidly" xml:space="preserve">
|
||||
<value>Generate QR Code Rapidly</value>
|
||||
</data>
|
||||
<data name="Preview" xml:space="preserve">
|
||||
<value>Preview</value>
|
||||
</data>
|
||||
<data name="PreviewPlaceholder" xml:space="preserve">
|
||||
<value>Your QR code will appear here in seconds</value>
|
||||
</data>
|
||||
<data name="UltraFastGeneration" xml:space="preserve">
|
||||
<value>Ultra-fast generation guaranteed</value>
|
||||
</data>
|
||||
<data name="DownloadPNG" xml:space="preserve">
|
||||
<value>Download PNG</value>
|
||||
</data>
|
||||
<data name="DownloadSVG" xml:space="preserve">
|
||||
<value>Download SVG (Vector)</value>
|
||||
</data>
|
||||
<data name="DownloadPDF" xml:space="preserve">
|
||||
<value>Download PDF</value>
|
||||
</data>
|
||||
<data name="SaveToHistory" xml:space="preserve">
|
||||
<value>Save to History</value>
|
||||
</data>
|
||||
<data name="LoginToSave" xml:space="preserve">
|
||||
<value>Login to save to history</value>
|
||||
</data>
|
||||
<data name="PremiumTitle" xml:space="preserve">
|
||||
<value>QR Rapido Premium</value>
|
||||
</data>
|
||||
<data name="SpeedTipsTitle" xml:space="preserve">
|
||||
<value>Tips for Faster QR</value>
|
||||
</data>
|
||||
<data name="SpeedTip1" xml:space="preserve">
|
||||
<value>Short URLs generate faster</value>
|
||||
</data>
|
||||
<data name="SpeedTip2" xml:space="preserve">
|
||||
<value>Less text = higher speed</value>
|
||||
</data>
|
||||
<data name="SpeedTip3" xml:space="preserve">
|
||||
<value>Solid colors optimize the process</value>
|
||||
</data>
|
||||
<data name="SpeedTip4" xml:space="preserve">
|
||||
<value>Smaller sizes speed up downloads</value>
|
||||
</data>
|
||||
<data name="Login" xml:space="preserve">
|
||||
<value>Login</value>
|
||||
</data>
|
||||
<data name="LoginWith" xml:space="preserve">
|
||||
<value>Login with</value>
|
||||
</data>
|
||||
<data name="Google" xml:space="preserve">
|
||||
<value>Google</value>
|
||||
</data>
|
||||
<data name="Microsoft" xml:space="preserve">
|
||||
<value>Microsoft</value>
|
||||
</data>
|
||||
<data name="AdFreeOffer" xml:space="preserve">
|
||||
<value>Unlock all premium features!</value>
|
||||
</data>
|
||||
<data name="SpecialOffer" xml:space="preserve">
|
||||
<value>Premium Offer!</value>
|
||||
</data>
|
||||
<data name="LoginBenefits" xml:space="preserve">
|
||||
<value>Remove ads, access advanced customization and much more for only $12.90/month or $129.00/year. Login and subscribe now!</value>
|
||||
</data>
|
||||
<data name="Privacy" xml:space="preserve">
|
||||
<value>Privacy Policy</value>
|
||||
</data>
|
||||
<data name="BackToGenerator" xml:space="preserve">
|
||||
<value>Back to generator</value>
|
||||
</data>
|
||||
<data name="GeneratedIn" xml:space="preserve">
|
||||
<value>Generated in</value>
|
||||
</data>
|
||||
<data name="Seconds" xml:space="preserve">
|
||||
<value>s</value>
|
||||
</data>
|
||||
<data name="UltraFast" xml:space="preserve">
|
||||
<value>Ultra fast generation!</value>
|
||||
</data>
|
||||
<data name="Fast" xml:space="preserve">
|
||||
<value>Fast generation!</value>
|
||||
</data>
|
||||
<data name="Normal" xml:space="preserve">
|
||||
<value>Normal generation</value>
|
||||
</data>
|
||||
<data name="Error" xml:space="preserve">
|
||||
<value>Generation error. Try again.</value>
|
||||
</data>
|
||||
<data name="Success" xml:space="preserve">
|
||||
<value>QR Code saved to history!</value>
|
||||
</data>
|
||||
<data name="CreateQRCodeQuickly" xml:space="preserve">
|
||||
<value>Create QR Code Quickly</value>
|
||||
</data>
|
||||
<data name="PremiumUserActive" xml:space="preserve">
|
||||
<value>Premium User Active</value>
|
||||
</data>
|
||||
<data name="NoAdsHistoryUnlimitedQR" xml:space="preserve">
|
||||
<value>No Ads • History • Unlimited QR</value>
|
||||
</data>
|
||||
<data name="UnlimitedToday" xml:space="preserve">
|
||||
<value>Unlimited today</value>
|
||||
</data>
|
||||
<data name="QRCodesRemaining" xml:space="preserve">
|
||||
<value>QR codes remaining</value>
|
||||
</data>
|
||||
<data name="QRCodeType" xml:space="preserve">
|
||||
<value>QR Code Type</value>
|
||||
</data>
|
||||
<data name="SelectType" xml:space="preserve">
|
||||
<value>Select type</value>
|
||||
</data>
|
||||
<data name="URLLink" xml:space="preserve">
|
||||
<value>URL/Link</value>
|
||||
</data>
|
||||
<data name="SimpleText" xml:space="preserve">
|
||||
<value>Simple Text</value>
|
||||
</data>
|
||||
<data name="VCard" xml:space="preserve">
|
||||
<value>Business Card</value>
|
||||
</data>
|
||||
<data name="DynamicQRPremium" xml:space="preserve">
|
||||
<value>Dynamic QR (Premium)</value>
|
||||
</data>
|
||||
<data name="EnterQRCodeContent" xml:space="preserve">
|
||||
<value>Enter your QR code content here...</value>
|
||||
</data>
|
||||
<data name="ContentHints" xml:space="preserve">
|
||||
<value>Content hints</value>
|
||||
</data>
|
||||
<data name="Classic" xml:space="preserve">
|
||||
<value>Classic</value>
|
||||
</data>
|
||||
<data name="Modern" xml:space="preserve">
|
||||
<value>Modern</value>
|
||||
</data>
|
||||
<data name="Colorful" xml:space="preserve">
|
||||
<value>Colorful</value>
|
||||
</data>
|
||||
<data name="WPARecommended" xml:space="preserve">
|
||||
<value>Rede WPA (a mais comum)</value>
|
||||
</data>
|
||||
<data name="WEPLegacy" xml:space="preserve">
|
||||
<value>WEP (muito antigo)</value>
|
||||
</data>
|
||||
<data name="OpenNetwork" xml:space="preserve">
|
||||
<value>Sem senha </value>
|
||||
</data>
|
||||
<data name="RequiredContent" xml:space="preserve">
|
||||
<value>Content is required</value>
|
||||
</data>
|
||||
<data name="ContentTooLong" xml:space="preserve">
|
||||
<value>Content too long. Maximum 4000 characters.</value>
|
||||
</data>
|
||||
<data name="PremiumCornerStyleRequired" xml:space="preserve">
|
||||
<value>Custom corner styles are exclusive to Premium plan. Upgrade to use this functionality.</value>
|
||||
</data>
|
||||
<data name="RateLimitReached" xml:space="preserve">
|
||||
<value>QR codes limit reached</value>
|
||||
</data>
|
||||
<data name="PremiumLogoRequired" xml:space="preserve">
|
||||
<value>Custom logo is exclusive to Premium plan. Upgrade to use this functionality.</value>
|
||||
</data>
|
||||
<data name="LogoTooLarge" xml:space="preserve">
|
||||
<value>Logo too large. Maximum 2MB.</value>
|
||||
</data>
|
||||
<data name="InvalidLogoFormat" xml:space="preserve">
|
||||
<value>Invalid format. Use PNG or JPG.</value>
|
||||
</data>
|
||||
<data name="UserProfileTitle" xml:space="preserve">
|
||||
<value>User Profile</value>
|
||||
</data>
|
||||
<data name="HistoryTitle" xml:space="preserve">
|
||||
<value>QR Codes History</value>
|
||||
</data>
|
||||
<data name="ErrorSavingHistory" xml:space="preserve">
|
||||
<value>Error saving to history.</value>
|
||||
</data>
|
||||
<data name="FeatureNotAvailable" xml:space="preserve">
|
||||
<value>Feature not available</value>
|
||||
</data>
|
||||
<data name="ShareError" xml:space="preserve">
|
||||
<value>Error sharing. Try another method.</value>
|
||||
</data>
|
||||
<data name="LinkCopied" xml:space="preserve">
|
||||
<value>Link copied to clipboard!</value>
|
||||
</data>
|
||||
<data name="EnterQRContent" xml:space="preserve">
|
||||
<value>Enter QR code content</value>
|
||||
</data>
|
||||
<data name="ValidationContentMinLength" xml:space="preserve">
|
||||
<value>Content must have at least 3 characters</value>
|
||||
</data>
|
||||
<data name="VCardValidationError" xml:space="preserve">
|
||||
<value>VCard validation error: </value>
|
||||
</data>
|
||||
<data name="FastestGeneratorBrazil" xml:space="preserve">
|
||||
<value>QR Code generated with QR Rapido - the fastest generator in Brazil!</value>
|
||||
</data>
|
||||
<data name="QRGenerateDescription" xml:space="preserve">
|
||||
<value>QR Rapido: Generate QR codes in seconds! Ultra-fast generator. Free, no registration required. 30 days ad-free after login.</value>
|
||||
</data>
|
||||
<data name="LogoNotProvided" xml:space="preserve">
|
||||
<value>Logo not provided</value>
|
||||
</data>
|
||||
<data name="LogoTooSmall" xml:space="preserve">
|
||||
<value>Logo too small. Minimum 32x32 pixels.</value>
|
||||
</data>
|
||||
<data name="InvalidImageFormat" xml:space="preserve">
|
||||
<value>Invalid image format</value>
|
||||
</data>
|
||||
<data name="ErrorProcessingLogo" xml:space="preserve">
|
||||
<value>Error processing logo.</value>
|
||||
</data>
|
||||
<data name="DeleteQRCode" xml:space="preserve">
|
||||
<value>Delete QR Code</value>
|
||||
</data>
|
||||
<data name="ConfirmDeleteTitle" xml:space="preserve">
|
||||
<value>Confirm Deletion</value>
|
||||
</data>
|
||||
<data name="ConfirmDeleteMessage" xml:space="preserve">
|
||||
<value>Are you sure you want to delete this QR code from your history?</value>
|
||||
</data>
|
||||
<data name="Yes" xml:space="preserve">
|
||||
<value>Yes</value>
|
||||
</data>
|
||||
<data name="No" xml:space="preserve">
|
||||
<value>No</value>
|
||||
</data>
|
||||
<data name="QRCodeDeleted" xml:space="preserve">
|
||||
<value>QR Code deleted successfully!</value>
|
||||
</data>
|
||||
<data name="ErrorDeletingQR" xml:space="preserve">
|
||||
<value>Error deleting QR code. Please try again.</value>
|
||||
</data>
|
||||
<data name="Deleting" xml:space="preserve">
|
||||
<value>Deleting</value>
|
||||
</data>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Tagline" xml:space="preserve">
|
||||
<value>Generate QR codes in seconds!</value>
|
||||
</data>
|
||||
<data name="GenerateQR" xml:space="preserve">
|
||||
<value>Generate QR Code</value>
|
||||
</data>
|
||||
<data name="QRType" xml:space="preserve">
|
||||
<value>QR Code Type</value>
|
||||
</data>
|
||||
<data name="Content" xml:space="preserve">
|
||||
<value>Content</value>
|
||||
</data>
|
||||
<data name="URLType" xml:space="preserve">
|
||||
<value>URL/Link</value>
|
||||
</data>
|
||||
<data name="TextType" xml:space="preserve">
|
||||
<value>Plain Text</value>
|
||||
</data>
|
||||
<data name="WiFiType" xml:space="preserve">
|
||||
<value>WiFi</value>
|
||||
</data>
|
||||
<data name="VCardType" xml:space="preserve">
|
||||
<value>Business Card</value>
|
||||
</data>
|
||||
<data name="SMSType" xml:space="preserve">
|
||||
<value>SMS</value>
|
||||
</data>
|
||||
<data name="EmailType" xml:space="preserve">
|
||||
<value>Email</value>
|
||||
</data>
|
||||
<data name="DynamicType" xml:space="preserve">
|
||||
<value>Dynamic QR (Premium)</value>
|
||||
</data>
|
||||
<data name="QuickStyle" xml:space="preserve">
|
||||
<value>Quick Style</value>
|
||||
</data>
|
||||
<data name="ClassicStyle" xml:space="preserve">
|
||||
<value>Classic</value>
|
||||
</data>
|
||||
<data name="ModernStyle" xml:space="preserve">
|
||||
<value>Modern</value>
|
||||
</data>
|
||||
<data name="ColorfulStyle" xml:space="preserve">
|
||||
<value>Colorful</value>
|
||||
</data>
|
||||
<data name="ContentPlaceholder" xml:space="preserve">
|
||||
<value>Enter your QR code content here...</value>
|
||||
</data>
|
||||
<data name="AdvancedCustomization" xml:space="preserve">
|
||||
<value>Advanced Customization</value>
|
||||
</data>
|
||||
<data name="PrimaryColor" xml:space="preserve">
|
||||
<value>Primary Color</value>
|
||||
</data>
|
||||
<data name="BackgroundColor" xml:space="preserve">
|
||||
<value>Background Color</value>
|
||||
</data>
|
||||
<data name="Size" xml:space="preserve">
|
||||
<value>Size</value>
|
||||
</data>
|
||||
<data name="Margin" xml:space="preserve">
|
||||
<value>Margin</value>
|
||||
</data>
|
||||
<data name="Logo" xml:space="preserve">
|
||||
<value>Logo/Icon</value>
|
||||
</data>
|
||||
<data name="CornerStyle" xml:space="preserve">
|
||||
<value>Corner Style</value>
|
||||
</data>
|
||||
<data name="GenerateRapidly" xml:space="preserve">
|
||||
<value>Generate QR Code Rapidly</value>
|
||||
</data>
|
||||
<data name="Preview" xml:space="preserve">
|
||||
<value>Preview</value>
|
||||
</data>
|
||||
<data name="PreviewPlaceholder" xml:space="preserve">
|
||||
<value>Your QR code will appear here in seconds</value>
|
||||
</data>
|
||||
<data name="UltraFastGeneration" xml:space="preserve">
|
||||
<value>Ultra-fast generation guaranteed</value>
|
||||
</data>
|
||||
<data name="DownloadPNG" xml:space="preserve">
|
||||
<value>Download PNG</value>
|
||||
</data>
|
||||
<data name="DownloadSVG" xml:space="preserve">
|
||||
<value>Download SVG (Vector)</value>
|
||||
</data>
|
||||
<data name="DownloadPDF" xml:space="preserve">
|
||||
<value>Download PDF</value>
|
||||
</data>
|
||||
<data name="SaveToHistory" xml:space="preserve">
|
||||
<value>Save to History</value>
|
||||
</data>
|
||||
<data name="LoginToSave" xml:space="preserve">
|
||||
<value>Login to save to history</value>
|
||||
</data>
|
||||
<data name="PremiumTitle" xml:space="preserve">
|
||||
<value>QR Rapido Premium</value>
|
||||
</data>
|
||||
<data name="SpeedTipsTitle" xml:space="preserve">
|
||||
<value>Tips for Faster QR</value>
|
||||
</data>
|
||||
<data name="SpeedTip1" xml:space="preserve">
|
||||
<value>Short URLs generate faster</value>
|
||||
</data>
|
||||
<data name="SpeedTip2" xml:space="preserve">
|
||||
<value>Less text = higher speed</value>
|
||||
</data>
|
||||
<data name="SpeedTip3" xml:space="preserve">
|
||||
<value>Solid colors optimize the process</value>
|
||||
</data>
|
||||
<data name="SpeedTip4" xml:space="preserve">
|
||||
<value>Smaller sizes speed up downloads</value>
|
||||
</data>
|
||||
<data name="Login" xml:space="preserve">
|
||||
<value>Login</value>
|
||||
</data>
|
||||
<data name="LoginWith" xml:space="preserve">
|
||||
<value>Login with</value>
|
||||
</data>
|
||||
<data name="Google" xml:space="preserve">
|
||||
<value>Google</value>
|
||||
</data>
|
||||
<data name="Microsoft" xml:space="preserve">
|
||||
<value>Microsoft</value>
|
||||
</data>
|
||||
<data name="AdFreeOffer" xml:space="preserve">
|
||||
<value>Unlock all premium features!</value>
|
||||
</data>
|
||||
<data name="SpecialOffer" xml:space="preserve">
|
||||
<value>Premium Offer!</value>
|
||||
</data>
|
||||
<data name="LoginBenefits" xml:space="preserve">
|
||||
<value>Remove ads, access advanced customization and much more for only $12.90/month or $129.00/year. Login and subscribe now!</value>
|
||||
</data>
|
||||
<data name="Privacy" xml:space="preserve">
|
||||
<value>Privacy Policy</value>
|
||||
</data>
|
||||
<data name="BackToGenerator" xml:space="preserve">
|
||||
<value>Back to generator</value>
|
||||
</data>
|
||||
<data name="GeneratedIn" xml:space="preserve">
|
||||
<value>Generated in</value>
|
||||
</data>
|
||||
<data name="Seconds" xml:space="preserve">
|
||||
<value>s</value>
|
||||
</data>
|
||||
<data name="UltraFast" xml:space="preserve">
|
||||
<value>Ultra fast generation!</value>
|
||||
</data>
|
||||
<data name="Fast" xml:space="preserve">
|
||||
<value>Fast generation!</value>
|
||||
</data>
|
||||
<data name="Normal" xml:space="preserve">
|
||||
<value>Normal generation</value>
|
||||
</data>
|
||||
<data name="Error" xml:space="preserve">
|
||||
<value>Generation error. Try again.</value>
|
||||
</data>
|
||||
<data name="Success" xml:space="preserve">
|
||||
<value>QR Code saved to history!</value>
|
||||
</data>
|
||||
<data name="CreateQRCodeQuickly" xml:space="preserve">
|
||||
<value>Create QR Code Quickly</value>
|
||||
</data>
|
||||
<data name="PremiumUserActive" xml:space="preserve">
|
||||
<value>Premium User Active</value>
|
||||
</data>
|
||||
<data name="NoAdsHistoryUnlimitedQR" xml:space="preserve">
|
||||
<value>No Ads • History • Unlimited QR</value>
|
||||
</data>
|
||||
<data name="UnlimitedToday" xml:space="preserve">
|
||||
<value>Unlimited today</value>
|
||||
</data>
|
||||
<data name="QRCodesRemaining" xml:space="preserve">
|
||||
<value>QR codes remaining</value>
|
||||
</data>
|
||||
<data name="QRCodeType" xml:space="preserve">
|
||||
<value>QR Code Type</value>
|
||||
</data>
|
||||
<data name="SelectType" xml:space="preserve">
|
||||
<value>Select type</value>
|
||||
</data>
|
||||
<data name="URLLink" xml:space="preserve">
|
||||
<value>URL/Link</value>
|
||||
</data>
|
||||
<data name="SimpleText" xml:space="preserve">
|
||||
<value>Simple Text</value>
|
||||
</data>
|
||||
<data name="VCard" xml:space="preserve">
|
||||
<value>Business Card</value>
|
||||
</data>
|
||||
<data name="DynamicQRPremium" xml:space="preserve">
|
||||
<value>Dynamic QR (Premium)</value>
|
||||
</data>
|
||||
<data name="EnterQRCodeContent" xml:space="preserve">
|
||||
<value>Enter your QR code content here...</value>
|
||||
</data>
|
||||
<data name="ContentHints" xml:space="preserve">
|
||||
<value>Content hints</value>
|
||||
</data>
|
||||
<data name="Classic" xml:space="preserve">
|
||||
<value>Classic</value>
|
||||
</data>
|
||||
<data name="Modern" xml:space="preserve">
|
||||
<value>Modern</value>
|
||||
</data>
|
||||
<data name="Colorful" xml:space="preserve">
|
||||
<value>Colorful</value>
|
||||
</data>
|
||||
<data name="WPARecommended" xml:space="preserve">
|
||||
<value>Rede WPA (a mais comum)</value>
|
||||
</data>
|
||||
<data name="WEPLegacy" xml:space="preserve">
|
||||
<value>WEP (muito antigo)</value>
|
||||
</data>
|
||||
<data name="OpenNetwork" xml:space="preserve">
|
||||
<value>Sem senha </value>
|
||||
</data>
|
||||
<data name="RequiredContent" xml:space="preserve">
|
||||
<value>Content is required</value>
|
||||
</data>
|
||||
<data name="ContentTooLong" xml:space="preserve">
|
||||
<value>Content too long. Maximum 4000 characters.</value>
|
||||
</data>
|
||||
<data name="PremiumCornerStyleRequired" xml:space="preserve">
|
||||
<value>Custom corner styles are exclusive to Premium plan. Upgrade to use this functionality.</value>
|
||||
</data>
|
||||
<data name="RateLimitReached" xml:space="preserve">
|
||||
<value>QR codes limit reached</value>
|
||||
</data>
|
||||
<data name="PremiumLogoRequired" xml:space="preserve">
|
||||
<value>Custom logo is exclusive to Premium plan. Upgrade to use this functionality.</value>
|
||||
</data>
|
||||
<data name="LogoTooLarge" xml:space="preserve">
|
||||
<value>Logo too large. Maximum 2MB.</value>
|
||||
</data>
|
||||
<data name="InvalidLogoFormat" xml:space="preserve">
|
||||
<value>Invalid format. Use PNG or JPG.</value>
|
||||
</data>
|
||||
<data name="UserProfileTitle" xml:space="preserve">
|
||||
<value>User Profile</value>
|
||||
</data>
|
||||
<data name="HistoryTitle" xml:space="preserve">
|
||||
<value>QR Codes History</value>
|
||||
</data>
|
||||
<data name="ErrorSavingHistory" xml:space="preserve">
|
||||
<value>Error saving to history.</value>
|
||||
</data>
|
||||
<data name="FeatureNotAvailable" xml:space="preserve">
|
||||
<value>Feature not available</value>
|
||||
</data>
|
||||
<data name="ShareError" xml:space="preserve">
|
||||
<value>Error sharing. Try another method.</value>
|
||||
</data>
|
||||
<data name="LinkCopied" xml:space="preserve">
|
||||
<value>Link copied to clipboard!</value>
|
||||
</data>
|
||||
<data name="EnterQRContent" xml:space="preserve">
|
||||
<value>Enter QR code content</value>
|
||||
</data>
|
||||
<data name="ValidationContentMinLength" xml:space="preserve">
|
||||
<value>Content must have at least 3 characters</value>
|
||||
</data>
|
||||
<data name="VCardValidationError" xml:space="preserve">
|
||||
<value>VCard validation error: </value>
|
||||
</data>
|
||||
<data name="FastestGeneratorBrazil" xml:space="preserve">
|
||||
<value>QR Code generated with QR Rapido - the fastest generator in Brazil!</value>
|
||||
</data>
|
||||
<data name="QRGenerateDescription" xml:space="preserve">
|
||||
<value>QR Rapido: Generate QR codes in seconds! Ultra-fast generator. Free, no registration required. 30 days ad-free after login.</value>
|
||||
</data>
|
||||
<data name="LogoNotProvided" xml:space="preserve">
|
||||
<value>Logo not provided</value>
|
||||
</data>
|
||||
<data name="LogoTooSmall" xml:space="preserve">
|
||||
<value>Logo too small. Minimum 32x32 pixels.</value>
|
||||
</data>
|
||||
<data name="InvalidImageFormat" xml:space="preserve">
|
||||
<value>Invalid image format</value>
|
||||
</data>
|
||||
<data name="ErrorProcessingLogo" xml:space="preserve">
|
||||
<value>Error processing logo.</value>
|
||||
</data>
|
||||
<data name="DeleteQRCode" xml:space="preserve">
|
||||
<value>Delete QR Code</value>
|
||||
</data>
|
||||
<data name="ConfirmDeleteTitle" xml:space="preserve">
|
||||
<value>Confirm Deletion</value>
|
||||
</data>
|
||||
<data name="ConfirmDeleteMessage" xml:space="preserve">
|
||||
<value>Are you sure you want to delete this QR code from your history?</value>
|
||||
</data>
|
||||
<data name="Yes" xml:space="preserve">
|
||||
<value>Yes</value>
|
||||
</data>
|
||||
<data name="No" xml:space="preserve">
|
||||
<value>No</value>
|
||||
</data>
|
||||
<data name="QRCodeDeleted" xml:space="preserve">
|
||||
<value>QR Code deleted successfully!</value>
|
||||
</data>
|
||||
<data name="ErrorDeletingQR" xml:space="preserve">
|
||||
<value>Error deleting QR code. Please try again.</value>
|
||||
</data>
|
||||
<data name="Deleting" xml:space="preserve">
|
||||
<value>Deleting</value>
|
||||
</data>
|
||||
</root>
|
||||
@ -109,10 +109,10 @@ namespace QRRapidoApp.Services
|
||||
}
|
||||
}
|
||||
|
||||
public void SetViewBagAds(dynamic viewBag)
|
||||
{
|
||||
public void SetViewBagAds(dynamic viewBag)
|
||||
{
|
||||
viewBag.AdSenseTag = _config["AdSense:ClientId"];
|
||||
viewBag.AdSenseEnabled = _config["AdSense:Enabled"]=="True";
|
||||
viewBag.AdSenseEnabled = _config["AdSense:Enabled"]=="True";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -199,13 +199,13 @@ namespace QRRapidoApp.Services.Monitoring
|
||||
try
|
||||
{
|
||||
var command = new BsonDocument("collStats", collectionName);
|
||||
var result = await context.Database!.RunCommandAsync<BsonDocument>(command);
|
||||
|
||||
var result = await context.Database!.RunCommandAsync<BsonDocument>(command);
|
||||
|
||||
var size = GetDoubleValue(result, "size");
|
||||
var totalIndexSize = GetDoubleValue(result, "totalIndexSize");
|
||||
var count = result.GetValue("count", BsonValue.Create(0)).ToInt64();
|
||||
var avgObjSize = GetDoubleValue(result, "avgObjSize");
|
||||
|
||||
var avgObjSize = GetDoubleValue(result, "avgObjSize");
|
||||
|
||||
collectionStats.Add(new CollectionStatistics
|
||||
{
|
||||
Name = collectionName,
|
||||
@ -225,17 +225,17 @@ namespace QRRapidoApp.Services.Monitoring
|
||||
return collectionStats.OrderByDescending(c => c.SizeMB).ToList();
|
||||
}
|
||||
|
||||
private static double GetDoubleValue(BsonDocument document, string fieldName)
|
||||
{
|
||||
var value = document.GetValue(fieldName, BsonValue.Create(0));
|
||||
return value.BsonType switch
|
||||
{
|
||||
BsonType.Double => value.AsDouble,
|
||||
BsonType.Int32 => (double)value.AsInt32,
|
||||
BsonType.Int64 => (double)value.AsInt64,
|
||||
BsonType.Decimal128 => (double)value.AsDecimal128,
|
||||
_ => 0.0
|
||||
};
|
||||
private static double GetDoubleValue(BsonDocument document, string fieldName)
|
||||
{
|
||||
var value = document.GetValue(fieldName, BsonValue.Create(0));
|
||||
return value.BsonType switch
|
||||
{
|
||||
BsonType.Double => value.AsDouble,
|
||||
BsonType.Int32 => (double)value.AsInt32,
|
||||
BsonType.Int64 => (double)value.AsInt64,
|
||||
BsonType.Decimal128 => (double)value.AsDecimal128,
|
||||
_ => 0.0
|
||||
};
|
||||
}
|
||||
|
||||
private bool ShouldMonitorCollection(string collectionName)
|
||||
|
||||
@ -89,20 +89,20 @@ namespace QRRapidoApp.Services
|
||||
break;
|
||||
|
||||
case "invoice.finalized":
|
||||
var invoice = stripeEvent.Data.Object as Invoice;
|
||||
var subscriptionLineItem = invoice.Lines?.Data
|
||||
.FirstOrDefault(line =>
|
||||
!string.IsNullOrEmpty(line.SubscriptionId) ||
|
||||
line.Subscription != null
|
||||
);
|
||||
|
||||
string subscriptionId = null;
|
||||
|
||||
if (subscriptionLineItem != null)
|
||||
{
|
||||
// Tenta obter o ID da assinatura de duas formas diferentes
|
||||
subscriptionId = subscriptionLineItem.SubscriptionId
|
||||
?? subscriptionLineItem.Subscription?.Id;
|
||||
var invoice = stripeEvent.Data.Object as Invoice;
|
||||
var subscriptionLineItem = invoice.Lines?.Data
|
||||
.FirstOrDefault(line =>
|
||||
!string.IsNullOrEmpty(line.SubscriptionId) ||
|
||||
line.Subscription != null
|
||||
);
|
||||
|
||||
string subscriptionId = null;
|
||||
|
||||
if (subscriptionLineItem != null)
|
||||
{
|
||||
// Tenta obter o ID da assinatura de duas formas diferentes
|
||||
subscriptionId = subscriptionLineItem.SubscriptionId
|
||||
?? subscriptionLineItem.Subscription?.Id;
|
||||
}
|
||||
|
||||
if (subscriptionId != null)
|
||||
@ -153,8 +153,8 @@ namespace QRRapidoApp.Services
|
||||
await _userService.UpdateUserStripeCustomerIdAsync(user.Id, subscription.CustomerId);
|
||||
}
|
||||
|
||||
await _userService.ActivatePremiumStatus(userId, subscription.Id, subItem.CurrentPeriodEnd);
|
||||
|
||||
await _userService.ActivatePremiumStatus(userId, subscription.Id, subItem.CurrentPeriodEnd);
|
||||
|
||||
_logger.LogInformation($"Successfully processed premium activation/renewal for user {userId}.");
|
||||
}
|
||||
|
||||
|
||||
@ -1,442 +1,442 @@
|
||||
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
|
||||
};
|
||||
|
||||
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)
|
||||
{
|
||||
var update = Builders<User>.Update
|
||||
.Set(u => u.IsPremium, true)
|
||||
.Set(u => u.StripeSubscriptionId, stripeSubscriptionId)
|
||||
.Set(u => u.PremiumExpiresAt, expiryDate)
|
||||
.Unset(u => u.PremiumCancelledAt);
|
||||
|
||||
await _context.Users.UpdateOneAsync(u => u.Id == userId, update);
|
||||
_logger.LogInformation($"Activated premium for user {userId}");
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
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
|
||||
};
|
||||
|
||||
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)
|
||||
{
|
||||
var update = Builders<User>.Update
|
||||
.Set(u => u.IsPremium, true)
|
||||
.Set(u => u.StripeSubscriptionId, stripeSubscriptionId)
|
||||
.Set(u => u.PremiumExpiresAt, expiryDate)
|
||||
.Unset(u => u.PremiumCancelledAt);
|
||||
|
||||
await _context.Users.UpdateOneAsync(u => u.Id == userId, update);
|
||||
_logger.LogInformation($"Activated premium for user {userId}");
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,43 +1,43 @@
|
||||
@using QRRapidoApp.Models
|
||||
@model ErrorViewModel
|
||||
@{
|
||||
ViewData["Title"] = "Erro";
|
||||
Layout = "~/Views/Shared/_Layout.cshtml";
|
||||
}
|
||||
|
||||
<div class="container mt-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<div class="alert alert-danger">
|
||||
<h1 class="display-4">Erro</h1>
|
||||
<p>Ocorreu um erro durante o processamento da sua solicitação.</p>
|
||||
|
||||
@if (Model?.ShowRequestId == true)
|
||||
{
|
||||
<p>
|
||||
<strong>Request ID:</strong> <code>@Model.RequestId</code>
|
||||
</p>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrEmpty(ViewBag.ErrorCode))
|
||||
{
|
||||
<p>
|
||||
<strong>Código do erro:</strong> <code>@ViewBag.ErrorCode</code>
|
||||
</p>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrEmpty(ViewBag.ErrorMessage))
|
||||
{
|
||||
<p>
|
||||
<strong>Mensagem:</strong> @ViewBag.ErrorMessage
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<a href="/pt-BR" class="btn btn-primary">Voltar ao Início</a>
|
||||
<a href="/pt-BR/Account/Login" class="btn btn-secondary">Tentar Login Novamente</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@using QRRapidoApp.Models
|
||||
@model ErrorViewModel
|
||||
@{
|
||||
ViewData["Title"] = "Erro";
|
||||
Layout = "~/Views/Shared/_Layout.cshtml";
|
||||
}
|
||||
|
||||
<div class="container mt-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<div class="alert alert-danger">
|
||||
<h1 class="display-4">Erro</h1>
|
||||
<p>Ocorreu um erro durante o processamento da sua solicitação.</p>
|
||||
|
||||
@if (Model?.ShowRequestId == true)
|
||||
{
|
||||
<p>
|
||||
<strong>Request ID:</strong> <code>@Model.RequestId</code>
|
||||
</p>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrEmpty(ViewBag.ErrorCode))
|
||||
{
|
||||
<p>
|
||||
<strong>Código do erro:</strong> <code>@ViewBag.ErrorCode</code>
|
||||
</p>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrEmpty(ViewBag.ErrorMessage))
|
||||
{
|
||||
<p>
|
||||
<strong>Mensagem:</strong> @ViewBag.ErrorMessage
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<a href="/pt-BR" class="btn btn-primary">Voltar ao Início</a>
|
||||
<a href="/pt-BR/Account/Login" class="btn btn-secondary">Tentar Login Novamente</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1 +1 @@
|
||||
google.com, pub-3475956393038764, DIRECT, f08c47fec0942fa0
|
||||
google.com, pub-3475956393038764, DIRECT, f08c47fec0942fa0
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user