♻️ Moving to MagicOnion
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
using System.Globalization;
|
||||
using DysonNetwork.Shared.Cache;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using DysonNetwork.Shared.Services;
|
||||
using MagicOnion.Server;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Caching.Distributed;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using NodaTime;
|
||||
|
||||
@@ -9,11 +11,9 @@ namespace DysonNetwork.Pass.Account;
|
||||
|
||||
public class AccountEventService(
|
||||
AppDatabase db,
|
||||
// WebSocketService ws,
|
||||
// ICacheService cache,
|
||||
// PaymentService payment,
|
||||
ICacheService cache,
|
||||
IStringLocalizer<Localization.AccountEventResource> localizer
|
||||
)
|
||||
) : ServiceBase<IAccountEventService>, IAccountEventService
|
||||
{
|
||||
private static readonly Random Random = new();
|
||||
private const string StatusCacheKey = "AccountStatus_";
|
||||
@@ -21,18 +21,18 @@ public class AccountEventService(
|
||||
public void PurgeStatusCache(Guid userId)
|
||||
{
|
||||
var cacheKey = $"{StatusCacheKey}{userId}";
|
||||
// cache.RemoveAsync(cacheKey);
|
||||
cache.RemoveAsync(cacheKey);
|
||||
}
|
||||
|
||||
public async Task<Status> GetStatus(Guid userId)
|
||||
{
|
||||
var cacheKey = $"{StatusCacheKey}{userId}";
|
||||
// var cachedStatus = await cache.GetAsync<Status>(cacheKey);
|
||||
// if (cachedStatus is not null)
|
||||
// {
|
||||
// cachedStatus!.IsOnline = !cachedStatus.IsInvisible && ws.GetAccountIsConnected(userId);
|
||||
// return cachedStatus;
|
||||
// }
|
||||
var cachedStatus = await cache.GetAsync<Status>(cacheKey);
|
||||
if (cachedStatus is not null)
|
||||
{
|
||||
cachedStatus!.IsOnline = !cachedStatus.IsInvisible /*&& ws.GetAccountIsConnected(userId)*/;
|
||||
return cachedStatus;
|
||||
}
|
||||
|
||||
var now = SystemClock.Instance.GetCurrentInstant();
|
||||
var status = await db.AccountStatuses
|
||||
@@ -45,8 +45,12 @@ public class AccountEventService(
|
||||
if (status is not null)
|
||||
{
|
||||
status.IsOnline = !status.IsInvisible && isOnline;
|
||||
// await cache.SetWithGroupsAsync(cacheKey, status, [$"{AccountService.AccountCachePrefix}{status.AccountId}"],
|
||||
// TimeSpan.FromMinutes(5));
|
||||
await cache.SetWithGroupsAsync(
|
||||
cacheKey,
|
||||
status,
|
||||
[$"{AccountService.AccountCachePrefix}{status.AccountId}"],
|
||||
TimeSpan.FromMinutes(5)
|
||||
);
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -62,7 +66,7 @@ public class AccountEventService(
|
||||
};
|
||||
}
|
||||
|
||||
return new Status
|
||||
return new Status
|
||||
{
|
||||
Attitude = StatusAttitude.Neutral,
|
||||
IsOnline = false,
|
||||
@@ -88,7 +92,7 @@ public class AccountEventService(
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
cacheMissUserIds.Add(userId);
|
||||
cacheMissUserIds.Add(userId);
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -192,27 +196,28 @@ public class AccountEventService(
|
||||
return lastDate < currentDate;
|
||||
}
|
||||
|
||||
public const string CheckInLockKey = "CheckInLock_";
|
||||
private const string CheckInLockKey = "checkin-lock:";
|
||||
|
||||
public async Task<CheckInResult> CheckInDaily(Shared.Models.Account user)
|
||||
{
|
||||
var lockKey = $"{CheckInLockKey}{user.Id}";
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
// var lk = await cache.AcquireLockAsync(lockKey, TimeSpan.FromMinutes(1), TimeSpan.FromMilliseconds(100));
|
||||
|
||||
// if (lk != null)
|
||||
// await lk.ReleaseAsync();
|
||||
var lk = await cache.AcquireLockAsync(lockKey, TimeSpan.FromMinutes(1), TimeSpan.FromMilliseconds(100));
|
||||
|
||||
if (lk != null)
|
||||
await lk.ReleaseAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore errors from this pre-check
|
||||
}
|
||||
|
||||
|
||||
// Now try to acquire the lock properly
|
||||
// await using var lockObj = await cache.AcquireLockAsync(lockKey, TimeSpan.FromMinutes(1), TimeSpan.FromSeconds(5));
|
||||
// if (lockObj is null) throw new InvalidOperationException("Check-in was in progress.");
|
||||
await using var lockObj =
|
||||
await cache.AcquireLockAsync(lockKey, TimeSpan.FromMinutes(1), TimeSpan.FromSeconds(5));
|
||||
if (lockObj is null) throw new InvalidOperationException("Check-in was in progress.");
|
||||
|
||||
var cultureInfo = new CultureInfo(user.Language, false);
|
||||
CultureInfo.CurrentCulture = cultureInfo;
|
||||
@@ -274,12 +279,53 @@ public class AccountEventService(
|
||||
s.SetProperty(b => b.Experience, b => b.Experience + result.RewardExperience)
|
||||
);
|
||||
db.AccountCheckInResults.Add(result);
|
||||
await db.SaveChangesAsync(); // Don't forget to save changes to the database
|
||||
await db.SaveChangesAsync(); // Remember to save changes to the database
|
||||
|
||||
// The lock will be automatically released by the await using statement
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<int> GetCheckInStreak(Shared.Models.Account user)
|
||||
{
|
||||
var today = SystemClock.Instance.GetCurrentInstant().InUtc().Date;
|
||||
var yesterdayEnd = today.PlusDays(-1).AtMidnight().InUtc().ToInstant();
|
||||
var yesterdayStart = today.PlusDays(-1).AtStartOfDayInZone(DateTimeZone.Utc).ToInstant();
|
||||
var tomorrowEnd = today.PlusDays(1).AtMidnight().InUtc().ToInstant();
|
||||
var tomorrowStart = today.PlusDays(1).AtStartOfDayInZone(DateTimeZone.Utc).ToInstant();
|
||||
|
||||
var yesterdayResult = await db.AccountCheckInResults
|
||||
.Where(x => x.AccountId == user.Id)
|
||||
.Where(x => x.CreatedAt >= yesterdayStart)
|
||||
.Where(x => x.CreatedAt < yesterdayEnd)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
var tomorrowResult = await db.AccountCheckInResults
|
||||
.Where(x => x.AccountId == user.Id)
|
||||
.Where(x => x.CreatedAt >= tomorrowStart)
|
||||
.Where(x => x.CreatedAt < tomorrowEnd)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (yesterdayResult is null && tomorrowResult is null)
|
||||
return 1;
|
||||
|
||||
var results = await db.AccountCheckInResults
|
||||
.Where(x => x.AccountId == user.Id)
|
||||
.OrderByDescending(x => x.CreatedAt)
|
||||
.ToListAsync();
|
||||
|
||||
var streak = 0;
|
||||
var day = today;
|
||||
while (results.Any(x =>
|
||||
x.CreatedAt >= day.AtStartOfDayInZone(DateTimeZone.Utc).ToInstant() &&
|
||||
x.CreatedAt < day.AtMidnight().InUtc().ToInstant()))
|
||||
{
|
||||
streak++;
|
||||
day = day.PlusDays(-1);
|
||||
}
|
||||
|
||||
return streak;
|
||||
}
|
||||
|
||||
public async Task<List<DailyEventResponse>> GetEventCalendar(Shared.Models.Account user, int month, int year = 0,
|
||||
bool replaceInvisible = false)
|
||||
{
|
||||
|
@@ -1,61 +0,0 @@
|
||||
using DysonNetwork.Shared.Models;
|
||||
using DysonNetwork.Shared.Protos.Account;
|
||||
using Grpc.Core;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using DysonNetwork.Pass.Auth;
|
||||
|
||||
namespace DysonNetwork.Pass.Account;
|
||||
|
||||
public class AccountGrpcService(AppDatabase db, AuthService auth)
|
||||
: DysonNetwork.Shared.Protos.Account.AccountService.AccountServiceBase
|
||||
{
|
||||
public override async Task<AccountResponse> GetAccount(Empty request, ServerCallContext context)
|
||||
{
|
||||
var account = await GetAccountFromContext(context);
|
||||
return ToAccountResponse(account);
|
||||
}
|
||||
|
||||
public override async Task<AccountResponse> UpdateAccount(UpdateAccountRequest request, ServerCallContext context)
|
||||
{
|
||||
var account = await GetAccountFromContext(context);
|
||||
|
||||
// TODO: implement
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
return ToAccountResponse(account);
|
||||
}
|
||||
|
||||
private async Task<Shared.Models.Account> GetAccountFromContext(ServerCallContext context)
|
||||
{
|
||||
var authorizationHeader = context.RequestHeaders.FirstOrDefault(h => h.Key == "authorization");
|
||||
if (authorizationHeader == null)
|
||||
{
|
||||
throw new RpcException(new Grpc.Core.Status(StatusCode.Unauthenticated, "Missing authorization header."));
|
||||
}
|
||||
|
||||
var token = authorizationHeader.Value.Replace("Bearer ", "");
|
||||
if (!auth.ValidateToken(token, out var sessionId))
|
||||
{
|
||||
throw new RpcException(new Grpc.Core.Status(StatusCode.Unauthenticated, "Invalid token."));
|
||||
}
|
||||
|
||||
var session = await db.AuthSessions.Include(s => s.Account).ThenInclude(a => a.Contacts)
|
||||
.FirstOrDefaultAsync(s => s.Id == sessionId);
|
||||
if (session == null)
|
||||
{
|
||||
throw new RpcException(new Grpc.Core.Status(StatusCode.Unauthenticated, "Session not found."));
|
||||
}
|
||||
|
||||
return session.Account;
|
||||
}
|
||||
|
||||
private AccountResponse ToAccountResponse(Shared.Models.Account account)
|
||||
{
|
||||
// TODO: implement
|
||||
return new AccountResponse
|
||||
{
|
||||
};
|
||||
}
|
||||
}
|
@@ -1,14 +1,16 @@
|
||||
using System.Globalization;
|
||||
using DysonNetwork.Pass.Auth;
|
||||
using DysonNetwork.Pass.Auth.OpenId;
|
||||
using DysonNetwork.Shared.Cache;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using DysonNetwork.Sphere.Auth.OpenId;
|
||||
using DysonNetwork.Shared.Services;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using NodaTime;
|
||||
using OtpNet;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using EFCore.BulkExtensions;
|
||||
using MagicOnion.Server;
|
||||
|
||||
namespace DysonNetwork.Pass.Account;
|
||||
|
||||
@@ -21,7 +23,7 @@ public class AccountService(
|
||||
// IStringLocalizer<NotificationResource> localizer,
|
||||
ICacheService cache,
|
||||
ILogger<AccountService> logger
|
||||
)
|
||||
) : ServiceBase<IAccountService>, IAccountService
|
||||
{
|
||||
public static void SetCultureInfo(Shared.Models.Account account)
|
||||
{
|
||||
|
@@ -1,4 +1,6 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using DysonNetwork.Shared.Services;
|
||||
using MagicOnion.Server;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace DysonNetwork.Pass.Account;
|
||||
@@ -6,7 +8,7 @@ namespace DysonNetwork.Pass.Account;
|
||||
/// <summary>
|
||||
/// Service for handling username generation and validation
|
||||
/// </summary>
|
||||
public class AccountUsernameService(AppDatabase db)
|
||||
public class AccountUsernameService(AppDatabase db) : ServiceBase<IAccountUsernameService>, IAccountUsernameService
|
||||
{
|
||||
private readonly Random _random = new();
|
||||
|
||||
|
@@ -1,13 +1,24 @@
|
||||
using DysonNetwork.Shared.Models;
|
||||
using DysonNetwork.Shared.Services;
|
||||
using MagicOnion.Server;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace DysonNetwork.Pass.Account;
|
||||
|
||||
public class ActionLogService(
|
||||
// GeoIpService geo,
|
||||
// FlushBufferService fbs
|
||||
)
|
||||
public class ActionLogService : ServiceBase<IActionLogService>, IActionLogService
|
||||
{
|
||||
// private readonly GeoIpService _geo;
|
||||
// private readonly FlushBufferService _fbs;
|
||||
|
||||
public ActionLogService(
|
||||
// GeoIpService geo,
|
||||
// FlushBufferService fbs
|
||||
)
|
||||
{
|
||||
// _geo = geo;
|
||||
// _fbs = fbs;
|
||||
}
|
||||
|
||||
public void CreateActionLog(Guid accountId, string action, Dictionary<string, object> meta)
|
||||
{
|
||||
var log = new ActionLog
|
||||
|
@@ -1,30 +0,0 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Pass.Account;
|
||||
|
||||
public enum MagicSpellType
|
||||
{
|
||||
AccountActivation,
|
||||
AccountDeactivation,
|
||||
AccountRemoval,
|
||||
AuthPasswordReset,
|
||||
ContactVerification,
|
||||
}
|
||||
|
||||
[Index(nameof(Spell), IsUnique = true)]
|
||||
public class MagicSpell : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[JsonIgnore] [MaxLength(1024)] public string Spell { get; set; } = null!;
|
||||
public MagicSpellType Type { get; set; }
|
||||
public Instant? ExpiresAt { get; set; }
|
||||
public Instant? AffectedAt { get; set; }
|
||||
[Column(TypeName = "jsonb")] public Dictionary<string, object> Meta { get; set; } = new();
|
||||
|
||||
public Guid? AccountId { get; set; }
|
||||
public Shared.Models.Account? Account { get; set; }
|
||||
}
|
@@ -2,6 +2,8 @@ using System.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.Json;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using DysonNetwork.Shared.Services;
|
||||
using MagicOnion.Server;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using NodaTime;
|
||||
@@ -12,11 +14,9 @@ namespace DysonNetwork.Pass.Account;
|
||||
|
||||
public class MagicSpellService(
|
||||
AppDatabase db,
|
||||
// EmailService email,
|
||||
IConfiguration configuration,
|
||||
ILogger<MagicSpellService> logger
|
||||
// IStringLocalizer<Localization.EmailResource> localizer
|
||||
)
|
||||
) : ServiceBase<IMagicSpellService>, IMagicSpellService
|
||||
{
|
||||
public async Task<MagicSpell> CreateMagicSpell(
|
||||
Shared.Models.Account account,
|
||||
@@ -59,6 +59,17 @@ public class MagicSpellService(
|
||||
return spell;
|
||||
}
|
||||
|
||||
public async Task<MagicSpell?> GetMagicSpellAsync(string token)
|
||||
{
|
||||
var now = SystemClock.Instance.GetCurrentInstant();
|
||||
var spell = await db.MagicSpells
|
||||
.Where(s => s.Spell == token)
|
||||
.Where(s => s.ExpiresAt == null || s.ExpiresAt > now)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
return spell;
|
||||
}
|
||||
|
||||
public async Task NotifyMagicSpell(MagicSpell spell, bool bypassVerify = false)
|
||||
{
|
||||
var contact = await db.AccountContacts
|
||||
@@ -144,8 +155,15 @@ public class MagicSpellService(
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ApplyMagicSpell(MagicSpell spell)
|
||||
public async Task ApplyMagicSpell(string token)
|
||||
{
|
||||
var now = SystemClock.Instance.GetCurrentInstant();
|
||||
var spell = await db.MagicSpells
|
||||
.Where(s => s.Spell == token)
|
||||
.Where(s => s.ExpiresAt == null || s.ExpiresAt > now)
|
||||
.FirstOrDefaultAsync();
|
||||
if (spell is null) throw new ArgumentException("Magic spell not found.");
|
||||
|
||||
switch (spell.Type)
|
||||
{
|
||||
case MagicSpellType.AuthPasswordReset:
|
||||
|
@@ -1,29 +1,29 @@
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using EFCore.BulkExtensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime;
|
||||
using DysonNetwork.Shared.Services;
|
||||
using EFCore.BulkExtensions;
|
||||
using MagicOnion.Server;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace DysonNetwork.Pass.Account;
|
||||
|
||||
public class NotificationService(
|
||||
AppDatabase db
|
||||
// WebSocketService ws,
|
||||
// IHttpClientFactory httpFactory,
|
||||
// IConfiguration config
|
||||
)
|
||||
AppDatabase db,
|
||||
IConfiguration config,
|
||||
IHttpClientFactory httpFactory
|
||||
) : ServiceBase<INotificationService>, INotificationService
|
||||
{
|
||||
// private readonly string _notifyTopic = config["Notifications:Topic"]!;
|
||||
// private readonly Uri _notifyEndpoint = new(config["Notifications:Endpoint"]!);
|
||||
private readonly string _notifyTopic = config["Notifications:Topic"]!;
|
||||
private readonly Uri _notifyEndpoint = new(config["Notifications:Endpoint"]!);
|
||||
|
||||
public async Task UnsubscribePushNotifications(string deviceId)
|
||||
{
|
||||
// await db.NotificationPushSubscriptions
|
||||
// .Where(s => s.DeviceId == deviceId)
|
||||
// .ExecuteDeleteAsync();
|
||||
await db.NotificationPushSubscriptions
|
||||
.Where(s => s.DeviceId == deviceId)
|
||||
.ExecuteDeleteAsync();
|
||||
}
|
||||
|
||||
public async Task<NotificationPushSubscription> SubscribePushNotification(
|
||||
@@ -34,29 +34,29 @@ public class NotificationService(
|
||||
)
|
||||
{
|
||||
var now = SystemClock.Instance.GetCurrentInstant();
|
||||
|
||||
|
||||
// First check if a matching subscription exists
|
||||
// var existingSubscription = await db.NotificationPushSubscriptions
|
||||
// .Where(s => s.AccountId == account.Id)
|
||||
// .Where(s => s.DeviceId == deviceId || s.DeviceToken == deviceToken)
|
||||
// .FirstOrDefaultAsync();
|
||||
var existingSubscription = await db.NotificationPushSubscriptions
|
||||
.Where(s => s.AccountId == account.Id)
|
||||
.Where(s => s.DeviceId == deviceId || s.DeviceToken == deviceToken)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
// if (existingSubscription is not null)
|
||||
// {
|
||||
// // Update the existing subscription directly in the database
|
||||
// await db.NotificationPushSubscriptions
|
||||
// .Where(s => s.Id == existingSubscription.Id)
|
||||
// .ExecuteUpdateAsync(setters => setters
|
||||
// .SetProperty(s => s.DeviceId, deviceId)
|
||||
// .SetProperty(s => s.DeviceToken, deviceToken)
|
||||
// .SetProperty(s => s.UpdatedAt, now));
|
||||
if (existingSubscription is not null)
|
||||
{
|
||||
// Update the existing subscription directly in the database
|
||||
await db.NotificationPushSubscriptions
|
||||
.Where(s => s.Id == existingSubscription.Id)
|
||||
.ExecuteUpdateAsync(setters => setters
|
||||
.SetProperty(s => s.DeviceId, deviceId)
|
||||
.SetProperty(s => s.DeviceToken, deviceToken)
|
||||
.SetProperty(s => s.UpdatedAt, now));
|
||||
|
||||
// // Return the updated subscription
|
||||
// existingSubscription.DeviceId = deviceId;
|
||||
// existingSubscription.DeviceToken = deviceToken;
|
||||
// existingSubscription.UpdatedAt = now;
|
||||
// return existingSubscription;
|
||||
// }
|
||||
// Return the updated subscription
|
||||
existingSubscription.DeviceId = deviceId;
|
||||
existingSubscription.DeviceToken = deviceToken;
|
||||
existingSubscription.UpdatedAt = now;
|
||||
return existingSubscription;
|
||||
}
|
||||
|
||||
var subscription = new NotificationPushSubscription
|
||||
{
|
||||
@@ -102,11 +102,12 @@ public class NotificationService(
|
||||
|
||||
if (save)
|
||||
{
|
||||
// db.Add(notification);
|
||||
// await db.SaveChangesAsync();
|
||||
db.Add(notification);
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
if (!isSilent) Console.WriteLine("Simulating notification delivery."); // _ = DeliveryNotification(notification);
|
||||
if (!isSilent)
|
||||
Console.WriteLine("Simulating notification delivery."); // _ = DeliveryNotification(notification);
|
||||
|
||||
return notification;
|
||||
}
|
||||
@@ -120,11 +121,11 @@ public class NotificationService(
|
||||
// });
|
||||
|
||||
// Pushing the notification
|
||||
// var subscribers = await db.NotificationPushSubscriptions
|
||||
// .Where(s => s.AccountId == notification.AccountId)
|
||||
// .ToListAsync();
|
||||
var subscribers = await db.NotificationPushSubscriptions
|
||||
.Where(s => s.AccountId == notification.AccountId)
|
||||
.ToListAsync();
|
||||
|
||||
// await _PushNotification(notification, subscribers);
|
||||
await _PushNotification(notification, subscribers);
|
||||
}
|
||||
|
||||
public async Task MarkNotificationsViewed(ICollection<Notification> notifications)
|
||||
@@ -174,12 +175,13 @@ public class NotificationService(
|
||||
// });
|
||||
}
|
||||
|
||||
// var subscribers = await db.NotificationPushSubscriptions
|
||||
// .ToListAsync();
|
||||
// await _PushNotification(notification, subscribers);
|
||||
var subscribers = await db.NotificationPushSubscriptions
|
||||
.ToListAsync();
|
||||
await _PushNotification(notification, subscribers);
|
||||
}
|
||||
|
||||
public async Task SendNotificationBatch(Notification notification, List<Shared.Models.Account> accounts, bool save = false)
|
||||
public async Task SendNotificationBatch(Notification notification, List<Shared.Models.Account> accounts,
|
||||
bool save = false)
|
||||
{
|
||||
if (save)
|
||||
{
|
||||
@@ -198,7 +200,7 @@ public class NotificationService(
|
||||
};
|
||||
return newNotification;
|
||||
}).ToList();
|
||||
// await db.BulkInsertAsync(notifications);
|
||||
await db.BulkInsertAsync(notifications);
|
||||
}
|
||||
|
||||
foreach (var account in accounts)
|
||||
@@ -219,93 +221,93 @@ public class NotificationService(
|
||||
// await _PushNotification(notification, subscribers);
|
||||
}
|
||||
|
||||
// private List<Dictionary<string, object>> _BuildNotificationPayload(Notification notification,
|
||||
// IEnumerable<NotificationPushSubscription> subscriptions)
|
||||
// {
|
||||
// var subDict = subscriptions
|
||||
// .GroupBy(x => x.Provider)
|
||||
// .ToDictionary(x => x.Key, x => x.ToList());
|
||||
private List<Dictionary<string, object>> _BuildNotificationPayload(Notification notification,
|
||||
IEnumerable<NotificationPushSubscription> subscriptions)
|
||||
{
|
||||
var subDict = subscriptions
|
||||
.GroupBy(x => x.Provider)
|
||||
.ToDictionary(x => x.Key, x => x.ToList());
|
||||
|
||||
// var notifications = subDict.Select(value =>
|
||||
// {
|
||||
// var platformCode = value.Key switch
|
||||
// {
|
||||
// NotificationPushProvider.Apple => 1,
|
||||
// NotificationPushProvider.Google => 2,
|
||||
// _ => throw new InvalidOperationException($"Unknown push provider: {value.Key}")
|
||||
// };
|
||||
var notifications = subDict.Select(value =>
|
||||
{
|
||||
var platformCode = value.Key switch
|
||||
{
|
||||
NotificationPushProvider.Apple => 1,
|
||||
NotificationPushProvider.Google => 2,
|
||||
_ => throw new InvalidOperationException($"Unknown push provider: {value.Key}")
|
||||
};
|
||||
|
||||
// var tokens = value.Value.Select(x => x.DeviceToken).ToList();
|
||||
// return _BuildNotificationPayload(notification, platformCode, tokens);
|
||||
// }).ToList();
|
||||
var tokens = value.Value.Select(x => x.DeviceToken).ToList();
|
||||
return _BuildNotificationPayload(notification, platformCode, tokens);
|
||||
}).ToList();
|
||||
|
||||
// return notifications.ToList();
|
||||
// }
|
||||
return notifications.ToList();
|
||||
}
|
||||
|
||||
// private Dictionary<string, object> _BuildNotificationPayload(Notification notification, int platformCode,
|
||||
// IEnumerable<string> deviceTokens)
|
||||
// {
|
||||
// var alertDict = new Dictionary<string, object>();
|
||||
// var dict = new Dictionary<string, object>
|
||||
// {
|
||||
// ["notif_id"] = notification.Id.ToString(),
|
||||
// ["apns_id"] = notification.Id.ToString(),
|
||||
// ["topic"] = _notifyTopic,
|
||||
// ["tokens"] = deviceTokens,
|
||||
// ["data"] = new Dictionary<string, object>
|
||||
// {
|
||||
// ["type"] = notification.Topic,
|
||||
// ["meta"] = notification.Meta ?? new Dictionary<string, object>(),
|
||||
// },
|
||||
// ["mutable_content"] = true,
|
||||
// ["priority"] = notification.Priority >= 5 ? "high" : "normal",
|
||||
// };
|
||||
private Dictionary<string, object> _BuildNotificationPayload(Notification notification, int platformCode,
|
||||
IEnumerable<string> deviceTokens)
|
||||
{
|
||||
var alertDict = new Dictionary<string, object>();
|
||||
var dict = new Dictionary<string, object>
|
||||
{
|
||||
["notif_id"] = notification.Id.ToString(),
|
||||
["apns_id"] = notification.Id.ToString(),
|
||||
["topic"] = _notifyTopic,
|
||||
["tokens"] = deviceTokens,
|
||||
["data"] = new Dictionary<string, object>
|
||||
{
|
||||
["type"] = notification.Topic,
|
||||
["meta"] = notification.Meta ?? new Dictionary<string, object>(),
|
||||
},
|
||||
["mutable_content"] = true,
|
||||
["priority"] = notification.Priority >= 5 ? "high" : "normal",
|
||||
};
|
||||
|
||||
// if (!string.IsNullOrWhiteSpace(notification.Title))
|
||||
// {
|
||||
// dict["title"] = notification.Title;
|
||||
// alertDict["title"] = notification.Title;
|
||||
// }
|
||||
if (!string.IsNullOrWhiteSpace(notification.Title))
|
||||
{
|
||||
dict["title"] = notification.Title;
|
||||
alertDict["title"] = notification.Title;
|
||||
}
|
||||
|
||||
// if (!string.IsNullOrWhiteSpace(notification.Content))
|
||||
// {
|
||||
// dict["message"] = notification.Content;
|
||||
// alertDict["body"] = notification.Content;
|
||||
// }
|
||||
if (!string.IsNullOrWhiteSpace(notification.Content))
|
||||
{
|
||||
dict["message"] = notification.Content;
|
||||
alertDict["body"] = notification.Content;
|
||||
}
|
||||
|
||||
// if (!string.IsNullOrWhiteSpace(notification.Subtitle))
|
||||
// {
|
||||
// dict["message"] = $"{notification.Subtitle}\n{dict["message"]}";
|
||||
// alertDict["subtitle"] = notification.Subtitle;
|
||||
// }
|
||||
if (!string.IsNullOrWhiteSpace(notification.Subtitle))
|
||||
{
|
||||
dict["message"] = $"{notification.Subtitle}\n{dict["message"]}";
|
||||
alertDict["subtitle"] = notification.Subtitle;
|
||||
}
|
||||
|
||||
// if (notification.Priority >= 5)
|
||||
// dict["name"] = "default";
|
||||
if (notification.Priority >= 5)
|
||||
dict["name"] = "default";
|
||||
|
||||
// dict["platform"] = platformCode;
|
||||
// dict["alert"] = alertDict;
|
||||
dict["platform"] = platformCode;
|
||||
dict["alert"] = alertDict;
|
||||
|
||||
// return dict;
|
||||
// }
|
||||
return dict;
|
||||
}
|
||||
|
||||
// private async Task _PushNotification(Notification notification,
|
||||
// IEnumerable<NotificationPushSubscription> subscriptions)
|
||||
// {
|
||||
// var subList = subscriptions.ToList();
|
||||
// if (subList.Count == 0) return;
|
||||
private async Task _PushNotification(Notification notification,
|
||||
IEnumerable<NotificationPushSubscription> subscriptions)
|
||||
{
|
||||
var subList = subscriptions.ToList();
|
||||
if (subList.Count == 0) return;
|
||||
|
||||
// var requestDict = new Dictionary<string, object>
|
||||
// {
|
||||
// ["notifications"] = _BuildNotificationPayload(notification, subList)
|
||||
// };
|
||||
var requestDict = new Dictionary<string, object>
|
||||
{
|
||||
["notifications"] = _BuildNotificationPayload(notification, subList)
|
||||
};
|
||||
|
||||
// var client = httpFactory.CreateClient();
|
||||
// client.BaseAddress = _notifyEndpoint;
|
||||
// var request = await client.PostAsync("/push", new StringContent(
|
||||
// JsonSerializer.Serialize(requestDict),
|
||||
// Encoding.UTF8,
|
||||
// "application/json"
|
||||
// ));
|
||||
// request.EnsureSuccessStatusCode();
|
||||
// }
|
||||
var client = httpFactory.CreateClient();
|
||||
client.BaseAddress = _notifyEndpoint;
|
||||
var request = await client.PostAsync("/push", new StringContent(
|
||||
JsonSerializer.Serialize(requestDict),
|
||||
Encoding.UTF8,
|
||||
"application/json"
|
||||
));
|
||||
request.EnsureSuccessStatusCode();
|
||||
}
|
||||
}
|
@@ -1,13 +1,13 @@
|
||||
using DysonNetwork.Shared.Cache;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using DysonNetwork.Shared.Services;
|
||||
using MagicOnion.Server;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Pass.Account;
|
||||
|
||||
public class RelationshipService(
|
||||
AppDatabase db
|
||||
// ICacheService cache
|
||||
)
|
||||
public class RelationshipService(AppDatabase db, ICacheService cache) : ServiceBase<IRelationshipService>, IRelationshipService
|
||||
{
|
||||
private const string UserFriendsCacheKeyPrefix = "accounts:friends:";
|
||||
private const string UserBlockedCacheKeyPrefix = "accounts:blocked:";
|
||||
@@ -150,7 +150,7 @@ public class RelationshipService(
|
||||
db.Update(relationship);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
// await PurgeRelationshipCache(accountId, relatedId);
|
||||
await PurgeRelationshipCache(accountId, relatedId);
|
||||
|
||||
return relationship;
|
||||
}
|
||||
@@ -158,8 +158,7 @@ public class RelationshipService(
|
||||
public async Task<List<Guid>> ListAccountFriends(Shared.Models.Account account)
|
||||
{
|
||||
var cacheKey = $"{UserFriendsCacheKeyPrefix}{account.Id}";
|
||||
// var friends = await cache.GetAsync<List<Guid>>(cacheKey);
|
||||
var friends = new List<Guid>(); // Placeholder
|
||||
var friends = await cache.GetAsync<List<Guid>>(cacheKey);
|
||||
|
||||
if (friends == null)
|
||||
{
|
||||
@@ -169,17 +168,16 @@ public class RelationshipService(
|
||||
.Select(r => r.AccountId)
|
||||
.ToListAsync();
|
||||
|
||||
// await cache.SetAsync(cacheKey, friends, TimeSpan.FromHours(1));
|
||||
await cache.SetAsync(cacheKey, friends, TimeSpan.FromHours(1));
|
||||
}
|
||||
|
||||
return friends ?? [];
|
||||
return friends;
|
||||
}
|
||||
|
||||
public async Task<List<Guid>> ListAccountBlocked(Shared.Models.Account account)
|
||||
{
|
||||
var cacheKey = $"{UserBlockedCacheKeyPrefix}{account.Id}";
|
||||
// var blocked = await cache.GetAsync<List<Guid>>(cacheKey);
|
||||
var blocked = new List<Guid>(); // Placeholder
|
||||
var blocked = await cache.GetAsync<List<Guid>>(cacheKey);
|
||||
|
||||
if (blocked == null)
|
||||
{
|
||||
@@ -189,10 +187,10 @@ public class RelationshipService(
|
||||
.Select(r => r.AccountId)
|
||||
.ToListAsync();
|
||||
|
||||
// await cache.SetAsync(cacheKey, blocked, TimeSpan.FromHours(1));
|
||||
await cache.SetAsync(cacheKey, blocked, TimeSpan.FromHours(1));
|
||||
}
|
||||
|
||||
return blocked ?? [];
|
||||
return blocked;
|
||||
}
|
||||
|
||||
public async Task<bool> HasRelationshipWithStatus(Guid accountId, Guid relatedId,
|
||||
@@ -204,9 +202,9 @@ public class RelationshipService(
|
||||
|
||||
private async Task PurgeRelationshipCache(Guid accountId, Guid relatedId)
|
||||
{
|
||||
// await cache.RemoveAsync($"{UserFriendsCacheKeyPrefix}{accountId}");
|
||||
// await cache.RemoveAsync($"{UserFriendsCacheKeyPrefix}{relatedId}");
|
||||
// await cache.RemoveAsync($"{UserBlockedCacheKeyPrefix}{accountId}");
|
||||
// await cache.RemoveAsync($"{UserBlockedCacheKeyPrefix}{relatedId}");
|
||||
await cache.RemoveAsync($"{UserFriendsCacheKeyPrefix}{accountId}");
|
||||
await cache.RemoveAsync($"{UserFriendsCacheKeyPrefix}{relatedId}");
|
||||
await cache.RemoveAsync($"{UserBlockedCacheKeyPrefix}{accountId}");
|
||||
await cache.RemoveAsync($"{UserBlockedCacheKeyPrefix}{relatedId}");
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user