Files
meezi/src/Meezi.API/Services/PlanLimitChecker.cs
T

141 lines
5.9 KiB
C#
Raw Normal View History

2026-05-27 21:33:48 +03:30
using Meezi.Infrastructure.Services.Platform;
using Meezi.Core.Enums;
using Meezi.Core.Interfaces;
using Meezi.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using StackExchange.Redis;
namespace Meezi.API.Services;
public interface IPlanLimitChecker
{
Task<(bool Allowed, string? ErrorCode, string? Message)> CheckAsync(
HttpContext context,
ITenantContext tenant,
CancellationToken cancellationToken = default);
}
public class PlanLimitChecker : IPlanLimitChecker
{
private readonly AppDbContext _db;
private readonly IConnectionMultiplexer _redis;
private readonly IPlatformCatalogService _platformCatalog;
public PlanLimitChecker(
AppDbContext db,
IConnectionMultiplexer redis,
IPlatformCatalogService platformCatalog)
{
_db = db;
_redis = redis;
_platformCatalog = platformCatalog;
}
public async Task<(bool Allowed, string? ErrorCode, string? Message)> CheckAsync(
HttpContext context,
ITenantContext tenant,
CancellationToken cancellationToken = default)
{
if (tenant.IsSystemAdmin || !tenant.IsAuthenticated || tenant.PlanTier is null || string.IsNullOrEmpty(tenant.CafeId))
return (true, null, null);
var method = context.Request.Method;
var path = context.Request.Path.Value ?? string.Empty;
if (method != HttpMethods.Post)
return (true, null, null);
var cafeId = tenant.CafeId;
var tier = tenant.PlanTier.Value;
var ordersPath = $"/api/cafes/{cafeId}/orders";
if (method == HttpMethods.Post &&
path.StartsWith(ordersPath, StringComparison.OrdinalIgnoreCase) &&
(path.Equals(ordersPath, StringComparison.OrdinalIgnoreCase) ||
path.Equals($"{ordersPath}/", StringComparison.OrdinalIgnoreCase)))
{
var limits = await _platformCatalog.GetLimitsAsync(tier, cancellationToken);
var maxOrders = limits.MaxOrdersPerDay;
if (maxOrders == int.MaxValue)
return (true, null, null);
var todayStart = DateTime.UtcNow.Date;
var count = await _db.Orders
.CountAsync(o => o.CafeId == cafeId && o.CreatedAt >= todayStart, cancellationToken);
if (count >= maxOrders)
return (false, "PLAN_LIMIT_REACHED", "Daily order limit reached for your plan. Please upgrade.");
}
var customersPath = $"/api/cafes/{cafeId}/customers";
if (path.StartsWith(customersPath, StringComparison.OrdinalIgnoreCase) &&
(path.Equals(customersPath, StringComparison.OrdinalIgnoreCase) ||
path.Equals($"{customersPath}/", StringComparison.OrdinalIgnoreCase)))
{
var limitsCustomers = await _platformCatalog.GetLimitsAsync(tier, cancellationToken);
var maxCustomers = limitsCustomers.MaxCustomers;
if (maxCustomers == int.MaxValue)
return (true, null, null);
var count = await _db.Customers
.CountAsync(c => c.CafeId == cafeId, cancellationToken);
if (count >= maxCustomers)
return (false, "PLAN_LIMIT_REACHED", "Customer limit reached for your plan. Please upgrade.");
}
var branchesPath = $"/api/cafes/{cafeId}/branches";
if (path.StartsWith(branchesPath, StringComparison.OrdinalIgnoreCase) &&
(path.Equals(branchesPath, StringComparison.OrdinalIgnoreCase) ||
path.Equals($"{branchesPath}/", StringComparison.OrdinalIgnoreCase)))
{
var limitsBranches = await _platformCatalog.GetLimitsAsync(tier, cancellationToken);
var maxBranches = limitsBranches.MaxBranches;
if (maxBranches == int.MaxValue)
return (true, null, null);
var branchCount = await _db.Branches.CountAsync(b => b.CafeId == cafeId, cancellationToken);
if (branchCount >= maxBranches)
return (false, "PLAN_LIMIT_REACHED", "Branch limit reached for your plan. Please upgrade.");
}
var tablesPath = $"/api/cafes/{cafeId}/tables";
if (path.StartsWith(tablesPath, StringComparison.OrdinalIgnoreCase) &&
(path.Equals(tablesPath, StringComparison.OrdinalIgnoreCase) ||
path.Equals($"{tablesPath}/", StringComparison.OrdinalIgnoreCase)))
{
var limitsTables = await _platformCatalog.GetLimitsAsync(tier, cancellationToken);
var maxTables = limitsTables.MaxTables;
if (maxTables != int.MaxValue)
{
var tableCount = await _db.Tables.CountAsync(t => t.CafeId == cafeId, cancellationToken);
if (tableCount >= maxTables)
return (false, "PLAN_LIMIT_REACHED", "Table limit reached for your plan. Please upgrade.");
}
}
2026-05-27 21:33:48 +03:30
var smsCampaignPath = $"/api/cafes/{cafeId}/sms/campaign";
if (path.Equals(smsCampaignPath, StringComparison.OrdinalIgnoreCase) ||
path.Equals($"{smsCampaignPath}/", StringComparison.OrdinalIgnoreCase))
{
var limitsSms = await _platformCatalog.GetLimitsAsync(tier, cancellationToken);
var maxSms = limitsSms.MaxSmsPerMonth;
if (maxSms == 0)
return (false, "PLAN_LIMIT_REACHED", "SMS is not available on the Free plan. Please upgrade.");
if (maxSms == int.MaxValue)
return (true, null, null);
var monthKey = $"sms:usage:{cafeId}:{DateTime.UtcNow:yyyy-MM}";
var redis = _redis.GetDatabase();
var used = await redis.StringGetAsync(monthKey);
var usedCount = used.HasValue ? (int)used : 0;
if (usedCount >= maxSms)
return (false, "PLAN_LIMIT_REACHED", "Monthly SMS limit reached for your plan. Please upgrade.");
}
return (true, null, null);
}
}