Add notification to friend request

This commit is contained in:
2025-08-19 19:06:08 +08:00
parent 7f7b47fb1c
commit fc6cee17d7
5 changed files with 65 additions and 33 deletions

View File

@@ -1,14 +1,22 @@
using DysonNetwork.Pass.Localization;
using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Cache;
using DysonNetwork.Shared.Proto;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Localization;
using NodaTime; using NodaTime;
namespace DysonNetwork.Pass.Account; namespace DysonNetwork.Pass.Account;
public class RelationshipService(AppDatabase db, ICacheService cache) public class RelationshipService(
AppDatabase db,
ICacheService cache,
PusherService.PusherServiceClient pusher,
IStringLocalizer<NotificationResource> localizer
)
{ {
private const string UserFriendsCacheKeyPrefix = "accounts:friends:"; private const string UserFriendsCacheKeyPrefix = "accounts:friends:";
private const string UserBlockedCacheKeyPrefix = "accounts:blocked:"; private const string UserBlockedCacheKeyPrefix = "accounts:blocked:";
public async Task<bool> HasExistingRelationship(Guid accountId, Guid relatedId) public async Task<bool> HasExistingRelationship(Guid accountId, Guid relatedId)
{ {
var count = await db.AccountRelationships var count = await db.AccountRelationships
@@ -51,7 +59,7 @@ public class RelationshipService(AppDatabase db, ICacheService cache)
db.AccountRelationships.Add(relationship); db.AccountRelationships.Add(relationship);
await db.SaveChangesAsync(); await db.SaveChangesAsync();
await PurgeRelationshipCache(sender.Id, target.Id); await PurgeRelationshipCache(sender.Id, target.Id);
return relationship; return relationship;
@@ -63,16 +71,16 @@ public class RelationshipService(AppDatabase db, ICacheService cache)
return await UpdateRelationship(sender.Id, target.Id, RelationshipStatus.Blocked); return await UpdateRelationship(sender.Id, target.Id, RelationshipStatus.Blocked);
return await CreateRelationship(sender, target, RelationshipStatus.Blocked); return await CreateRelationship(sender, target, RelationshipStatus.Blocked);
} }
public async Task<Relationship> UnblockAccount(Account sender, Account target) public async Task<Relationship> UnblockAccount(Account sender, Account target)
{ {
var relationship = await GetRelationship(sender.Id, target.Id, RelationshipStatus.Blocked); var relationship = await GetRelationship(sender.Id, target.Id, RelationshipStatus.Blocked);
if (relationship is null) throw new ArgumentException("There is no relationship between you and the user."); if (relationship is null) throw new ArgumentException("There is no relationship between you and the user.");
db.Remove(relationship); db.Remove(relationship);
await db.SaveChangesAsync(); await db.SaveChangesAsync();
await PurgeRelationshipCache(sender.Id, target.Id); await PurgeRelationshipCache(sender.Id, target.Id);
return relationship; return relationship;
} }
@@ -92,21 +100,34 @@ public class RelationshipService(AppDatabase db, ICacheService cache)
db.AccountRelationships.Add(relationship); db.AccountRelationships.Add(relationship);
await db.SaveChangesAsync(); await db.SaveChangesAsync();
await pusher.SendPushNotificationToUserAsync(new SendPushNotificationToUserRequest
{
UserId = target.Id.ToString(),
Notification = new PushNotification
{
Topic = "relationships.friends.request",
Title = localizer["FriendRequestTitle", sender.Nick],
Body = localizer["FriendRequestBody"],
ActionUri = "/account/relationships",
IsSavable = true
}
});
return relationship; return relationship;
} }
public async Task DeleteFriendRequest(Guid accountId, Guid relatedId) public async Task DeleteFriendRequest(Guid accountId, Guid relatedId)
{ {
var relationship = await GetRelationship(accountId, relatedId, RelationshipStatus.Pending); var relationship = await GetRelationship(accountId, relatedId, RelationshipStatus.Pending);
if (relationship is null) throw new ArgumentException("Friend request was not found."); if (relationship is null) throw new ArgumentException("Friend request was not found.");
await db.AccountRelationships await db.AccountRelationships
.Where(r => r.AccountId == accountId && r.RelatedId == relatedId && r.Status == RelationshipStatus.Pending) .Where(r => r.AccountId == accountId && r.RelatedId == relatedId && r.Status == RelationshipStatus.Pending)
.ExecuteDeleteAsync(); .ExecuteDeleteAsync();
await PurgeRelationshipCache(relationship.AccountId, relationship.RelatedId); await PurgeRelationshipCache(relationship.AccountId, relationship.RelatedId);
} }
public async Task<Relationship> AcceptFriendRelationship( public async Task<Relationship> AcceptFriendRelationship(
Relationship relationship, Relationship relationship,
RelationshipStatus status = RelationshipStatus.Friends RelationshipStatus status = RelationshipStatus.Friends
@@ -146,9 +167,9 @@ public class RelationshipService(AppDatabase db, ICacheService cache)
relationship.Status = status; relationship.Status = status;
db.Update(relationship); db.Update(relationship);
await db.SaveChangesAsync(); await db.SaveChangesAsync();
await PurgeRelationshipCache(accountId, relatedId); await PurgeRelationshipCache(accountId, relatedId);
return relationship; return relationship;
} }
@@ -161,7 +182,7 @@ public class RelationshipService(AppDatabase db, ICacheService cache)
{ {
var cacheKey = $"{UserFriendsCacheKeyPrefix}{accountId}"; var cacheKey = $"{UserFriendsCacheKeyPrefix}{accountId}";
var friends = await cache.GetAsync<List<Guid>>(cacheKey); var friends = await cache.GetAsync<List<Guid>>(cacheKey);
if (friends == null) if (friends == null)
{ {
friends = await db.AccountRelationships friends = await db.AccountRelationships
@@ -169,23 +190,23 @@ public class RelationshipService(AppDatabase db, ICacheService cache)
.Where(r => r.Status == RelationshipStatus.Friends) .Where(r => r.Status == RelationshipStatus.Friends)
.Select(r => r.AccountId) .Select(r => r.AccountId)
.ToListAsync(); .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(Account account) public async Task<List<Guid>> ListAccountBlocked(Account account)
{ {
return await ListAccountBlocked(account.Id); return await ListAccountBlocked(account.Id);
} }
public async Task<List<Guid>> ListAccountBlocked(Guid accountId) public async Task<List<Guid>> ListAccountBlocked(Guid accountId)
{ {
var cacheKey = $"{UserBlockedCacheKeyPrefix}{accountId}"; var cacheKey = $"{UserBlockedCacheKeyPrefix}{accountId}";
var blocked = await cache.GetAsync<List<Guid>>(cacheKey); var blocked = await cache.GetAsync<List<Guid>>(cacheKey);
if (blocked == null) if (blocked == null)
{ {
blocked = await db.AccountRelationships blocked = await db.AccountRelationships
@@ -193,7 +214,7 @@ public class RelationshipService(AppDatabase db, ICacheService cache)
.Where(r => r.Status == RelationshipStatus.Blocked) .Where(r => r.Status == RelationshipStatus.Blocked)
.Select(r => r.AccountId) .Select(r => r.AccountId)
.ToListAsync(); .ToListAsync();
await cache.SetAsync(cacheKey, blocked, TimeSpan.FromHours(1)); await cache.SetAsync(cacheKey, blocked, TimeSpan.FromHours(1));
} }
@@ -206,7 +227,7 @@ public class RelationshipService(AppDatabase db, ICacheService cache)
var relationship = await GetRelationship(accountId, relatedId, status); var relationship = await GetRelationship(accountId, relatedId, status);
return relationship is not null; return relationship is not null;
} }
private async Task PurgeRelationshipCache(Guid accountId, Guid relatedId) private async Task PurgeRelationshipCache(Guid accountId, Guid relatedId)
{ {
await cache.RemoveAsync($"{UserFriendsCacheKeyPrefix}{accountId}"); await cache.RemoveAsync($"{UserFriendsCacheKeyPrefix}{accountId}");

View File

@@ -136,19 +136,12 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<AdditionalFiles Include="Pages\Emails\AccountDeletionEmail.razor"/> <AdditionalFiles Include="Pages\Emails\AccountDeletionEmail.razor" />
<AdditionalFiles Include="Pages\Emails\ContactVerificationEmail.razor"/> <AdditionalFiles Include="Pages\Emails\ContactVerificationEmail.razor" />
<AdditionalFiles Include="Pages\Emails\EmailLayout.razor"/> <AdditionalFiles Include="Pages\Emails\EmailLayout.razor" />
<AdditionalFiles Include="Pages\Emails\LandingEmail.razor"/> <AdditionalFiles Include="Pages\Emails\LandingEmail.razor" />
<AdditionalFiles Include="Pages\Emails\PasswordResetEmail.razor"/> <AdditionalFiles Include="Pages\Emails\PasswordResetEmail.razor" />
<AdditionalFiles Include="Pages\Emails\VerificationEmail.razor"/> <AdditionalFiles Include="Pages\Emails\VerificationEmail.razor" />
<AdditionalFiles Include="Resources\Localization\AccountEventResource.zh-hans.resx"/>
<AdditionalFiles Include="Resources\Localization\EmailResource.resx"/>
<AdditionalFiles Include="Resources\Localization\EmailResource.zh-hans.resx"/>
<AdditionalFiles Include="Resources\Localization\NotificationResource.resx"/>
<AdditionalFiles Include="Resources\Localization\NotificationResource.zh-hans.resx"/>
<AdditionalFiles Include="Resources\Localization\SharedResource.resx"/>
<AdditionalFiles Include="Resources\Localization\SharedResource.zh-hans.resx"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -86,4 +86,10 @@
<data name="NewLoginBody" xml:space="preserve"> <data name="NewLoginBody" xml:space="preserve">
<value>Your account logged on to a device named {0} at {1}</value> <value>Your account logged on to a device named {0} at {1}</value>
</data> </data>
<data name="FriendRequestTitle" xml:space="preserve">
<value>{0} requested to be your friend</value>
</data>
<data name="FriendRequestBody" xml:space="preserve">
<value>You can go to relationships page and decide accept their request or not.</value>
</data>
</root> </root>

View File

@@ -78,4 +78,10 @@
<data name="NewLoginBody" xml:space="preserve"> <data name="NewLoginBody" xml:space="preserve">
<value>您的帐号在位于 {1} 的设备 {0} 上刚刚登陆了</value> <value>您的帐号在位于 {1} 的设备 {0} 上刚刚登陆了</value>
</data> </data>
<data name="FriendRequestTitle" xml:space="preserve">
<value>{0} 请求成为您的好友</value>
</data>
<data name="FriendRequestBody" xml:space="preserve">
<value>您可以前往人际关系页面来决定时候要接受他们的邀请。</value>
</data>
</root> </root>

View File

@@ -149,8 +149,14 @@
<s:String x:Key="/Default/Environment/AssemblyExplorer/XmlDocument/@EntryValue">&lt;AssemblyExplorer&gt; <s:String x:Key="/Default/Environment/AssemblyExplorer/XmlDocument/@EntryValue">&lt;AssemblyExplorer&gt;
&lt;Assembly Path="/opt/homebrew/Cellar/dotnet/9.0.6/libexec/packs/Microsoft.AspNetCore.App.Ref/9.0.6/ref/net9.0/Microsoft.AspNetCore.RateLimiting.dll" /&gt; &lt;Assembly Path="/opt/homebrew/Cellar/dotnet/9.0.6/libexec/packs/Microsoft.AspNetCore.App.Ref/9.0.6/ref/net9.0/Microsoft.AspNetCore.RateLimiting.dll" /&gt;
&lt;/AssemblyExplorer&gt;</s:String> &lt;/AssemblyExplorer&gt;</s:String>
<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=DysonNetwork_002EPass_002FResources_002FLocalization_002FAccountEventResource/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=DysonNetwork_002EPass_002FResources_002FLocalization_002FAccountEventResource/@EntryIndexedValue">False</s:Boolean>
<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=DysonNetwork_002EPass_002FResources_002FLocalization_002FEmailResource/@EntryIndexedValue">False</s:Boolean>
<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=DysonNetwork_002EPass_002FResources_002FLocalization_002FNotificationResource/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=DysonNetwork_002EPass_002FResources_002FLocalization_002FNotificationResource/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=DysonNetwork_002EPass_002FResources_002FLocalization_002FSharedResource/@EntryIndexedValue">False</s:Boolean>
<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=DysonNetwork_002ESphere_002FLocalization_002FResources_002FAccountEventResource/@EntryIndexedValue">False</s:Boolean> <s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=DysonNetwork_002ESphere_002FLocalization_002FResources_002FAccountEventResource/@EntryIndexedValue">False</s:Boolean>
<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=DysonNetwork_002ESphere_002FLocalization_002FResources_002FAccountEventResource/@EntryIndexRemoved">True</s:Boolean> <s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=DysonNetwork_002ESphere_002FLocalization_002FResources_002FAccountEventResource/@EntryIndexRemoved">True</s:Boolean>
<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=DysonNetwork_002ESphere_002FLocalization_002FResources_002FSharedResource/@EntryIndexedValue">False</s:Boolean> <s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=DysonNetwork_002ESphere_002FLocalization_002FResources_002FSharedResource/@EntryIndexedValue">False</s:Boolean>