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,80 @@
|
||||
using Meezi.API.Models.Notifications;
|
||||
using Meezi.Infrastructure.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Meezi.API.Services;
|
||||
|
||||
public interface INotificationInboxService
|
||||
{
|
||||
Task<NotificationListDto> ListAsync(
|
||||
string cafeId,
|
||||
bool unreadOnly,
|
||||
int limit,
|
||||
CancellationToken ct = default);
|
||||
|
||||
Task<int> GetUnreadCountAsync(string cafeId, CancellationToken ct = default);
|
||||
|
||||
Task MarkReadAsync(string cafeId, MarkNotificationsReadRequest request, CancellationToken ct = default);
|
||||
}
|
||||
|
||||
public class NotificationInboxService : INotificationInboxService
|
||||
{
|
||||
private readonly AppDbContext _db;
|
||||
|
||||
public NotificationInboxService(AppDbContext db)
|
||||
{
|
||||
_db = db;
|
||||
}
|
||||
|
||||
public async Task<NotificationListDto> ListAsync(
|
||||
string cafeId,
|
||||
bool unreadOnly,
|
||||
int limit,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
limit = Math.Clamp(limit, 1, 100);
|
||||
var q = _db.CafeNotifications.AsNoTracking().Where(n => n.CafeId == cafeId);
|
||||
if (unreadOnly)
|
||||
q = q.Where(n => !n.IsRead);
|
||||
|
||||
var unread = await q.CountAsync(n => !n.IsRead, ct);
|
||||
var items = await q
|
||||
.OrderByDescending(n => n.CreatedAt)
|
||||
.Take(limit)
|
||||
.Select(n => new CafeNotificationDto(
|
||||
n.Id,
|
||||
n.Type,
|
||||
n.Title,
|
||||
n.Body,
|
||||
n.ReferenceId,
|
||||
n.TableNumber,
|
||||
n.IsRead,
|
||||
n.CreatedAt))
|
||||
.ToListAsync(ct);
|
||||
|
||||
return new NotificationListDto(items, unread);
|
||||
}
|
||||
|
||||
public Task<int> GetUnreadCountAsync(string cafeId, CancellationToken ct = default) =>
|
||||
_db.CafeNotifications.CountAsync(n => n.CafeId == cafeId && !n.IsRead, ct);
|
||||
|
||||
public async Task MarkReadAsync(
|
||||
string cafeId,
|
||||
MarkNotificationsReadRequest request,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var q = _db.CafeNotifications.Where(n => n.CafeId == cafeId && !n.IsRead);
|
||||
if (!request.All && request.Ids is { Count: > 0 })
|
||||
q = q.Where(n => request.Ids.Contains(n.Id));
|
||||
|
||||
var rows = await q.ToListAsync(ct);
|
||||
var now = DateTime.UtcNow;
|
||||
foreach (var row in rows)
|
||||
{
|
||||
row.IsRead = true;
|
||||
row.ReadAt = now;
|
||||
}
|
||||
|
||||
await _db.SaveChangesAsync(ct);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user