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