250 lines
7.7 KiB
C#
250 lines
7.7 KiB
C#
using BCards.Web.Models;
|
|
using BCards.Web.Repositories;
|
|
using System.Text.RegularExpressions;
|
|
using System.Globalization;
|
|
using System.Text;
|
|
using BCards.Web.Utils;
|
|
|
|
namespace BCards.Web.Services;
|
|
|
|
public class UserPageService : IUserPageService
|
|
{
|
|
private readonly IUserPageRepository _userPageRepository;
|
|
private readonly IUserRepository _userRepository;
|
|
private readonly ISubscriptionRepository _subscriptionRepository;
|
|
|
|
public UserPageService(
|
|
IUserPageRepository userPageRepository,
|
|
IUserRepository userRepository,
|
|
ISubscriptionRepository subscriptionRepository)
|
|
{
|
|
_userPageRepository = userPageRepository;
|
|
_userRepository = userRepository;
|
|
_subscriptionRepository = subscriptionRepository;
|
|
}
|
|
|
|
public async Task<UserPage?> GetPageAsync(string category, string slug)
|
|
{
|
|
return await _userPageRepository.GetBySlugAsync(category, slug);
|
|
}
|
|
|
|
public async Task<UserPage?> GetUserPageAsync(string userId)
|
|
{
|
|
return await _userPageRepository.GetByUserIdAsync(userId);
|
|
}
|
|
|
|
public async Task<UserPage?> GetPageByIdAsync(string id)
|
|
{
|
|
return await _userPageRepository.GetByIdAsync(id);
|
|
}
|
|
|
|
public async Task<List<UserPage>> GetUserPagesAsync(string userId)
|
|
{
|
|
return await _userPageRepository.GetByUserIdAllAsync(userId);
|
|
}
|
|
|
|
public async Task<List<UserPage>> GetActivePagesAsync()
|
|
{
|
|
return await _userPageRepository.GetActivePagesAsync();
|
|
}
|
|
|
|
public async Task<UserPage> CreatePageAsync(UserPage userPage)
|
|
{
|
|
userPage.Slug = await GenerateSlugAsync(userPage.Category, userPage.DisplayName);
|
|
return await _userPageRepository.CreateAsync(userPage);
|
|
}
|
|
|
|
public async Task<UserPage> UpdatePageAsync(UserPage userPage)
|
|
{
|
|
return await _userPageRepository.UpdateAsync(userPage);
|
|
}
|
|
|
|
public async Task DeletePageAsync(string id)
|
|
{
|
|
await _userPageRepository.DeleteAsync(id);
|
|
}
|
|
|
|
public async Task<bool> ValidateSlugAsync(string category, string slug, string? excludeId = null)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(slug) || string.IsNullOrWhiteSpace(category))
|
|
return false;
|
|
|
|
if (!IsValidSlugFormat(slug))
|
|
return false;
|
|
|
|
return !await _userPageRepository.SlugExistsAsync(category, slug, excludeId);
|
|
}
|
|
|
|
public async Task<string> GenerateSlugAsync(string category, string name)
|
|
{
|
|
var slug = SlugHelper.CreateSlug(GenerateSlug(name));
|
|
var originalSlug = slug;
|
|
var counter = 1;
|
|
|
|
while (await _userPageRepository.SlugExistsAsync(category, slug))
|
|
{
|
|
slug = $"{originalSlug}-{counter}";
|
|
counter++;
|
|
}
|
|
|
|
return slug;
|
|
}
|
|
|
|
public async Task<bool> CanCreateLinksAsync(string userId, int newLinksCount = 1)
|
|
{
|
|
var userPage = await _userPageRepository.GetByUserIdAsync(userId);
|
|
if (userPage == null) return true; // New page
|
|
|
|
var subscription = await _subscriptionRepository.GetByUserIdAsync(userId);
|
|
var maxLinks = subscription?.MaxLinks ?? 5; // Default free plan
|
|
|
|
if (maxLinks == -1) return true; // Unlimited
|
|
|
|
var currentLinksCount = userPage.Links?.Count(l => l.IsActive) ?? 0;
|
|
return (currentLinksCount + newLinksCount) <= maxLinks;
|
|
}
|
|
|
|
public async Task RecordPageViewAsync(string pageId, string? referrer = null, string? userAgent = null)
|
|
{
|
|
var page = await _userPageRepository.GetByIdAsync(pageId);
|
|
if (page?.PlanLimitations.AllowAnalytics != true) return;
|
|
|
|
var analytics = page.Analytics;
|
|
analytics.TotalViews++;
|
|
analytics.LastViewedAt = DateTime.UtcNow;
|
|
|
|
// Monthly stats
|
|
var monthKey = DateTime.UtcNow.ToString("yyyy-MM");
|
|
if (analytics.MonthlyViews.ContainsKey(monthKey))
|
|
analytics.MonthlyViews[monthKey]++;
|
|
else
|
|
analytics.MonthlyViews[monthKey] = 1;
|
|
|
|
// Referrer stats
|
|
if (!string.IsNullOrEmpty(referrer))
|
|
{
|
|
var domain = ExtractDomain(referrer);
|
|
if (!string.IsNullOrEmpty(domain))
|
|
{
|
|
if (analytics.TopReferrers.ContainsKey(domain))
|
|
analytics.TopReferrers[domain]++;
|
|
else
|
|
analytics.TopReferrers[domain] = 1;
|
|
}
|
|
}
|
|
|
|
// Device stats (simplified)
|
|
if (!string.IsNullOrEmpty(userAgent))
|
|
{
|
|
var deviceType = GetDeviceType(userAgent);
|
|
if (analytics.DeviceStats.ContainsKey(deviceType))
|
|
analytics.DeviceStats[deviceType]++;
|
|
else
|
|
analytics.DeviceStats[deviceType] = 1;
|
|
}
|
|
|
|
await _userPageRepository.UpdateAnalyticsAsync(pageId, analytics);
|
|
}
|
|
|
|
public async Task RecordLinkClickAsync(string pageId, int linkIndex)
|
|
{
|
|
var page = await _userPageRepository.GetByIdAsync(pageId);
|
|
if (page?.PlanLimitations.AllowAnalytics != true) return;
|
|
|
|
if (linkIndex >= 0 && linkIndex < page.Links.Count)
|
|
{
|
|
page.Links[linkIndex].Clicks++;
|
|
}
|
|
|
|
var analytics = page.Analytics;
|
|
analytics.TotalClicks++;
|
|
|
|
// Monthly clicks
|
|
var monthKey = DateTime.UtcNow.ToString("yyyy-MM");
|
|
if (analytics.MonthlyClicks.ContainsKey(monthKey))
|
|
analytics.MonthlyClicks[monthKey]++;
|
|
else
|
|
analytics.MonthlyClicks[monthKey] = 1;
|
|
|
|
await _userPageRepository.UpdateAsync(page);
|
|
}
|
|
|
|
public async Task<List<UserPage>> GetRecentPagesAsync(int limit = 10)
|
|
{
|
|
return await _userPageRepository.GetRecentPagesAsync(limit);
|
|
}
|
|
|
|
public async Task<List<UserPage>> GetPagesByCategoryAsync(string category, int limit = 20)
|
|
{
|
|
return await _userPageRepository.GetByCategoryAsync(category, limit);
|
|
}
|
|
|
|
private static string GenerateSlug(string text)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(text))
|
|
return string.Empty;
|
|
|
|
// Remove acentos
|
|
text = RemoveDiacritics(text);
|
|
|
|
// Converter para minúsculas
|
|
text = text.ToLowerInvariant();
|
|
|
|
// Substituir espaços e caracteres especiais por hífens
|
|
text = Regex.Replace(text, @"[^a-z0-9\s-]", "");
|
|
text = Regex.Replace(text, @"[\s-]+", "-");
|
|
|
|
// Remover hífens do início e fim
|
|
text = text.Trim('-');
|
|
|
|
return text;
|
|
}
|
|
|
|
private static string RemoveDiacritics(string text)
|
|
{
|
|
var normalizedString = text.Normalize(NormalizationForm.FormD);
|
|
var stringBuilder = new StringBuilder();
|
|
|
|
foreach (var c in normalizedString)
|
|
{
|
|
var unicodeCategory = CharUnicodeInfo.GetUnicodeCategory(c);
|
|
if (unicodeCategory != UnicodeCategory.NonSpacingMark)
|
|
{
|
|
stringBuilder.Append(c);
|
|
}
|
|
}
|
|
|
|
return stringBuilder.ToString().Normalize(NormalizationForm.FormC);
|
|
}
|
|
|
|
private static bool IsValidSlugFormat(string slug)
|
|
{
|
|
return Regex.IsMatch(slug, @"^[a-z0-9-]+$") && !slug.StartsWith('-') && !slug.EndsWith('-');
|
|
}
|
|
|
|
private static string ExtractDomain(string url)
|
|
{
|
|
try
|
|
{
|
|
var uri = new Uri(url);
|
|
return uri.Host;
|
|
}
|
|
catch
|
|
{
|
|
return string.Empty;
|
|
}
|
|
}
|
|
|
|
private static string GetDeviceType(string userAgent)
|
|
{
|
|
userAgent = userAgent.ToLowerInvariant();
|
|
|
|
if (userAgent.Contains("mobile") || userAgent.Contains("android") || userAgent.Contains("iphone"))
|
|
return "mobile";
|
|
|
|
if (userAgent.Contains("tablet") || userAgent.Contains("ipad"))
|
|
return "tablet";
|
|
|
|
return "desktop";
|
|
}
|
|
} |