feat(plans): Stage 3b — DB-driven gates for reviews/styling/limits
CI/CD / CI · API (dotnet build + test) (push) Successful in 1m0s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 42s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m5s
CI/CD / CI · Admin Web (tsc) (push) Successful in 38s
CI/CD / CI · Website (tsc) (push) Successful in 45s
CI/CD / CI · Koja (tsc) (push) Successful in 49s
CI/CD / Deploy · all services (push) Successful in 2m51s

Make more plan rules read the admin-editable catalog instead of hardcoded values:
- Review reply gated by the `review_reply` feature (Starter+) — 403 if not in plan.
- Custom menu styling gated by `custom_menu_styling` (Starter+): only blocks an
  actual theme change, so a normal settings save re-sending the current theme is fine.
- Menu categories/items limits now read catalog.GetLimitsAsync (Free categories
  editable; message no longer hardcodes a number).
- Terminals limit reads the catalog (enforcement in TerminalRegistryService +
  the displayed max in TerminalsController).

Remaining (small): menu watermark (Free shows it, `watermark_removed` removes it —
needs the public-menu render), report-history (static ReportPlanGate) and AI-3D
routing — these already enforce the correct matrix values, just not yet editable.

86 tests pass; build clean.
This commit is contained in:
soroush.asadi
2026-06-03 01:40:00 +03:30
parent 8f738f6469
commit 2487f9e30f
5 changed files with 62 additions and 13 deletions
+9 -5
View File
@@ -7,6 +7,7 @@ using Meezi.Core.Constants;
using Meezi.Core.Enums;
using Meezi.Core.Interfaces;
using Meezi.Infrastructure.Data;
using Meezi.Infrastructure.Services.Platform;
using Meezi.Shared;
namespace Meezi.API.Controllers;
@@ -19,24 +20,27 @@ public class MenuController : CafeApiControllerBase
private readonly IValidator<CreateMenuCategoryRequest> _createCategoryValidator;
private readonly IValidator<CreateMenuItemRequest> _createItemValidator;
private readonly AppDbContext _db;
private readonly IPlatformCatalogService _catalog;
private const string CategoryLimitMessage =
"محدودیت دسته‌بندی پلن رایگان (۳ دسته). برای افزودن دسته‌بندی بیشتر، پلن خود را ارتقا دهید.";
"به سقف دسته‌بندی منوی پلن شما رسیدید. برای افزودن دسته‌بندی بیشتر، پلن خود را ارتقا دهید.";
private const string ItemLimitMessage =
"محدودیت آیتم منو پلن رایگان (۳۰ آیتم). برای افزودن آیتم بیشتر، پلن خود را ارتقا دهید.";
"به سقف آیتم منوی پلن شما رسیدید. برای افزودن آیتم بیشتر، پلن خود را ارتقا دهید.";
public MenuController(
IMenuService menuService,
IMenuAi3dGenerationService menuAi3d,
IValidator<CreateMenuCategoryRequest> createCategoryValidator,
IValidator<CreateMenuItemRequest> createItemValidator,
AppDbContext db)
AppDbContext db,
IPlatformCatalogService catalog)
{
_menuService = menuService;
_menuAi3d = menuAi3d;
_createCategoryValidator = createCategoryValidator;
_createItemValidator = createItemValidator;
_db = db;
_catalog = catalog;
}
[HttpGet("categories")]
@@ -59,7 +63,7 @@ public class MenuController : CafeApiControllerBase
if (!validation.IsValid) return BadRequest(ValidationError(validation));
var tier = tenant.PlanTier ?? PlanTier.Free;
var max = PlanLimits.MaxMenuCategories(tier);
var max = (await _catalog.GetLimitsAsync(tier, cancellationToken)).MaxMenuCategories;
if (max != int.MaxValue)
{
var count = await _db.MenuCategories.CountAsync(
@@ -120,7 +124,7 @@ public class MenuController : CafeApiControllerBase
if (!validation.IsValid) return BadRequest(ValidationError(validation));
var tier = tenant.PlanTier ?? PlanTier.Free;
var max = PlanLimits.MaxMenuItems(tier);
var max = (await _catalog.GetLimitsAsync(tier, cancellationToken)).MaxMenuItems;
if (max != int.MaxValue)
{
var count = await _db.MenuItems.CountAsync(