feat: implement notification system with repository, service, and controller layers
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
using Distribution.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Features.Notifications;
|
||||
|
||||
public class NotificationRepository(AppDBContext context) : INotificationRepository
|
||||
{
|
||||
// ── Inbox ────────────────────────────────────────────────────────────
|
||||
|
||||
public async Task AddAsync(Models.Notification notification) =>
|
||||
await context.Notifications.AddAsync(notification);
|
||||
|
||||
public void Discard(Models.Notification notification)
|
||||
{
|
||||
foreach (var recipient in notification.Recipients)
|
||||
context.Entry(recipient).State = EntityState.Detached;
|
||||
context.Entry(notification).State = EntityState.Detached;
|
||||
}
|
||||
|
||||
private IQueryable<Models.NotificationRecipient> InboxQuery(Guid userId, bool unreadOnly)
|
||||
{
|
||||
var query = context.NotificationRecipients.Where(r => r.FK_UserId == userId && r.IsActive);
|
||||
return unreadOnly ? query.Where(r => !r.IsRead) : query;
|
||||
}
|
||||
|
||||
public Task<int> GetInboxCountAsync(Guid userId, bool unreadOnly) =>
|
||||
InboxQuery(userId, unreadOnly).CountAsync();
|
||||
|
||||
public Task<List<Models.NotificationRecipient>> GetInboxAsync(Guid userId, bool unreadOnly, int page, int pageSize) =>
|
||||
InboxQuery(userId, unreadOnly)
|
||||
.Include(r => r.Notification)
|
||||
.OrderByDescending(r => r.Notification.CreatedAt)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
|
||||
public Task<int> GetUnreadCountAsync(Guid userId) =>
|
||||
InboxQuery(userId, unreadOnly: true).CountAsync();
|
||||
|
||||
public Task<Models.NotificationRecipient?> GetRecipientAsync(Guid userId, Guid recipientId) =>
|
||||
context.NotificationRecipients
|
||||
.FirstOrDefaultAsync(r => r.Id == recipientId && r.FK_UserId == userId && r.IsActive);
|
||||
|
||||
public Task<List<Models.NotificationRecipient>> GetUnreadRecipientsAsync(Guid userId) =>
|
||||
InboxQuery(userId, unreadOnly: true).ToListAsync();
|
||||
|
||||
public void RemoveRecipient(Models.NotificationRecipient recipient) =>
|
||||
context.NotificationRecipients.Remove(recipient);
|
||||
|
||||
// ── Recipient resolution ─────────────────────────────────────────────
|
||||
|
||||
public Task<List<Guid>> FilterActiveUserIdsAsync(IEnumerable<Guid> userIds)
|
||||
{
|
||||
var ids = userIds.ToList();
|
||||
return context.Users
|
||||
.Where(u => u.IsActive && ids.Contains(u.Id))
|
||||
.Select(u => u.Id)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public Task<List<Guid>> GetUserIdsByPermissionAsync(string permissionCode) =>
|
||||
context.Users
|
||||
.Where(u => u.IsActive && context.RolePermissions.Any(rp =>
|
||||
rp.FK_RoleId == u.FK_RoleId && rp.IsActive &&
|
||||
rp.Permission.IsActive && rp.Permission.Code == permissionCode))
|
||||
.Select(u => u.Id)
|
||||
.ToListAsync();
|
||||
|
||||
public async Task<List<Guid>> GetUserIdsByScopeAsync(ScopeKind kind, Guid id)
|
||||
{
|
||||
Guid? regionId = null;
|
||||
Guid? routeId = null;
|
||||
Guid? storeId = null;
|
||||
|
||||
switch (kind)
|
||||
{
|
||||
case ScopeKind.Region:
|
||||
regionId = id;
|
||||
break;
|
||||
case ScopeKind.Route:
|
||||
routeId = id;
|
||||
regionId = await context.Routes.Where(r => r.Id == id)
|
||||
.Select(r => (Guid?)r.FK_RegionId).FirstOrDefaultAsync();
|
||||
break;
|
||||
case ScopeKind.Store:
|
||||
storeId = id;
|
||||
routeId = await context.Stores.Where(s => s.Id == id)
|
||||
.Select(s => (Guid?)s.FK_RouteId).FirstOrDefaultAsync();
|
||||
if (routeId is { } rid)
|
||||
regionId = await context.Routes.Where(r => r.Id == rid)
|
||||
.Select(r => (Guid?)r.FK_RegionId).FirstOrDefaultAsync();
|
||||
break;
|
||||
}
|
||||
|
||||
var userIds = new HashSet<Guid>();
|
||||
|
||||
if (regionId is { } region)
|
||||
userIds.UnionWith(await context.UserRegionScopes
|
||||
.Where(s => s.IsActive && s.FK_RegionId == region)
|
||||
.Select(s => s.FK_UserId).ToListAsync());
|
||||
|
||||
if (routeId is { } route)
|
||||
userIds.UnionWith(await context.UserRouteScopes
|
||||
.Where(s => s.IsActive && s.FK_RouteId == route)
|
||||
.Select(s => s.FK_UserId).ToListAsync());
|
||||
|
||||
if (storeId is { } store)
|
||||
userIds.UnionWith(await context.UserStoreScopes
|
||||
.Where(s => s.IsActive && s.FK_StoreId == store)
|
||||
.Select(s => s.FK_UserId).ToListAsync());
|
||||
|
||||
return userIds.ToList();
|
||||
}
|
||||
|
||||
public Task<Guid?> GetReportsToUserIdAsync(Guid userId) =>
|
||||
context.Users.Where(u => u.Id == userId)
|
||||
.Select(u => u.FK_ReportsToUserId)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
public async Task<Guid?> GetRouteOwnerUserIdAsync(ScopeKind kind, Guid id)
|
||||
{
|
||||
Guid? routeId = kind switch
|
||||
{
|
||||
ScopeKind.Route => id,
|
||||
ScopeKind.Store => await context.Stores.Where(s => s.Id == id)
|
||||
.Select(s => (Guid?)s.FK_RouteId).FirstOrDefaultAsync(),
|
||||
_ => null
|
||||
};
|
||||
if (routeId is not { } rid) return null;
|
||||
|
||||
return await context.Routes.Where(r => r.Id == rid && r.IsActive)
|
||||
.Select(r => r.FK_OwnerUserId)
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
// ── Retention ────────────────────────────────────────────────────────
|
||||
|
||||
public Task<List<Models.NotificationRecipient>> GetExpiredReadRecipientsAsync(DateTime cutoff, int take) =>
|
||||
context.NotificationRecipients
|
||||
.Where(r => r.IsRead && r.ReadAt != null && r.ReadAt < cutoff)
|
||||
.Take(take)
|
||||
.ToListAsync();
|
||||
|
||||
public Task<List<Models.Notification>> GetOrphanNotificationsAsync(int take) =>
|
||||
context.Notifications
|
||||
.Where(n => !n.Recipients.Any())
|
||||
.Take(take)
|
||||
.ToListAsync();
|
||||
|
||||
public void RemoveRecipients(IEnumerable<Models.NotificationRecipient> recipients) =>
|
||||
context.NotificationRecipients.RemoveRange(recipients);
|
||||
|
||||
public void RemoveNotifications(IEnumerable<Models.Notification> notifications) =>
|
||||
context.Notifications.RemoveRange(notifications);
|
||||
}
|
||||
Reference in New Issue
Block a user