using SurrealDb.Net; using TarninaHub.Application.Abstractions; using TarninaHub.Application.Dtos; namespace TarninaHub.Infrastructure.Persistence; public sealed class NotificationService : INotificationService { private const string SelectFields = "type::string(id) AS id, type, payload, read_at, created_at"; private static readonly string[] StaffRoles = ["operator", "manager", "admin"]; private readonly ISurrealDbClient _client; public NotificationService(ISurrealDbClient client) { _client = client; } public async Task NotifyStaffAsync( string type, IReadOnlyDictionary payload, CancellationToken cancellationToken) { var response = await _client.RawQuery( "SELECT type::string(id) AS id FROM user WHERE role IN $roles;", new Dictionary { ["roles"] = StaffRoles }, cancellationToken); response.EnsureAllOks(); var rows = response.GetValue>(0) ?? []; foreach (var row in rows) { await NotifyUserAsync(row.Id, type, payload, cancellationToken); } } public async Task NotifyUserAsync( string userId, string type, IReadOnlyDictionary payload, CancellationToken cancellationToken) { var notificationId = Guid.NewGuid().ToString("N"); var (table, id) = SurrealRecordId.Split(userId); var response = await _client.RawQuery( "CREATE type::record('notification', $id) SET recipient = type::record($table, $recipientId), " + "type = $type, payload = $payload;", new Dictionary { ["id"] = notificationId, ["table"] = table, ["recipientId"] = id, ["type"] = type, ["payload"] = payload, }, cancellationToken); response.EnsureAllOks(); } public async Task> ListForUserAsync( string userId, CancellationToken cancellationToken) { var (table, id) = SurrealRecordId.Split(userId); var response = await _client.RawQuery( $"SELECT {SelectFields} FROM notification WHERE recipient = type::record($table, $id) " + "ORDER BY created_at DESC;", new Dictionary { ["table"] = table, ["id"] = id }, cancellationToken); response.EnsureAllOks(); var rows = response.GetValue>(0) ?? []; return rows.Select(Map).ToList(); } public async Task MarkReadAsync( string notificationId, string userId, CancellationToken cancellationToken) { var (table, id) = SurrealRecordId.Split(notificationId); var (userTable, userIdPart) = SurrealRecordId.Split(userId); var response = await _client.RawQuery( "UPDATE type::record($table, $id) SET read_at = time::now() " + "WHERE recipient = type::record($userTable, $userId); " + $"SELECT {SelectFields} FROM type::record($table, $id) LIMIT 1;", new Dictionary { ["table"] = table, ["id"] = id, ["userTable"] = userTable, ["userId"] = userIdPart, }, cancellationToken); response.EnsureAllOks(); var rows = response.GetValue>(1) ?? []; return rows.Count > 0 ? Map(rows[0]) : null; } private static NotificationResponse Map(NotificationRow row) => new( row.Id, row.Type, row.Payload, row.ReadAt, row.CreatedAt); }