feat(api): .NET 10 multi-tenant REST API
Full backend implementation: - Multi-tenant cafe/restaurant management (menus, orders, tables, staff) - POS order flow with ZarinPal and Snappfood payment integration - OTP authentication via Kavenegar SMS - QR digital menu with public discover/finder endpoints - Customer loyalty, coupons, CRM - PostgreSQL via EF Core, Redis for caching/sessions - Background jobs, webhook handlers - Full migration history Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Meezi.API.Models.Discover;
|
||||
using Meezi.API.Services;
|
||||
using Meezi.Core.Enums;
|
||||
using Meezi.Core.Interfaces;
|
||||
using Meezi.Infrastructure.Data;
|
||||
using Meezi.Infrastructure.Discover;
|
||||
using Meezi.Infrastructure.Services.Platform;
|
||||
using Meezi.Shared;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Meezi.API.Controllers;
|
||||
|
||||
[Authorize]
|
||||
[Route("api/cafes/{cafeId}/discover-profile")]
|
||||
public class CafeDiscoverProfileController : CafeApiControllerBase
|
||||
{
|
||||
private readonly AppDbContext _db;
|
||||
|
||||
public CafeDiscoverProfileController(AppDbContext db) => _db = db;
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Get(string cafeId, ITenantContext tenant, CancellationToken ct)
|
||||
{
|
||||
if (EnsureCafeAccess(cafeId, tenant) is { } denied)
|
||||
return denied;
|
||||
|
||||
var cafe = await _db.Cafes.AsNoTracking()
|
||||
.FirstOrDefaultAsync(c => c.Id == cafeId, cancellationToken: ct);
|
||||
if (cafe is null)
|
||||
return NotFound(new ApiResponse<object>(false, null, new ApiError("NOT_FOUND", "Cafe not found.")));
|
||||
|
||||
var profile = CafeDiscoverProfileSerializer.Deserialize(cafe.DiscoverProfileJson);
|
||||
return Ok(new ApiResponse<CafeDiscoverProfileDto>(true, CafeDiscoverProfileMapping.ToDto(profile)));
|
||||
}
|
||||
|
||||
[HttpPut]
|
||||
public async Task<IActionResult> Put(
|
||||
string cafeId,
|
||||
[FromBody] UpsertCafeDiscoverProfileRequest request,
|
||||
ITenantContext tenant,
|
||||
IPlatformCatalogService catalog,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (EnsureCafeAccess(cafeId, tenant) is { } denied)
|
||||
return denied;
|
||||
|
||||
var planTier = tenant.PlanTier ?? PlanTier.Free;
|
||||
if (!await catalog.IsFeatureEnabledForCafeAsync(cafeId, planTier, "discover_profile", ct))
|
||||
{
|
||||
return StatusCode(
|
||||
StatusCodes.Status403Forbidden,
|
||||
new ApiResponse<object>(
|
||||
false,
|
||||
null,
|
||||
new ApiError("PLAN_FEATURE_DISABLED", "Discover profile is not included in your plan. Upgrade to enable it.")));
|
||||
}
|
||||
|
||||
var cafe = await _db.Cafes.FirstOrDefaultAsync(c => c.Id == cafeId, cancellationToken: ct);
|
||||
if (cafe is null)
|
||||
return NotFound(new ApiResponse<object>(false, null, new ApiError("NOT_FOUND", "Cafe not found.")));
|
||||
|
||||
var profile = CafeDiscoverProfileMapping.FromRequest(request);
|
||||
cafe.DiscoverProfileJson = CafeDiscoverProfileSerializer.Serialize(profile);
|
||||
await _db.SaveChangesAsync(ct);
|
||||
|
||||
return Ok(new ApiResponse<CafeDiscoverProfileDto>(true, CafeDiscoverProfileMapping.ToDto(profile)));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user