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

124 lines
5.1 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.");
}
}
// NOTE: SMS is deliberately NOT plan-gated — marketing SMS is
// bring-your-own-provider (the café's own API key + sender line), so the
// café's provider account is the only limit.
2026-05-27 21:33:48 +03:30
return (true, null, null);
}
}