mirror of
https://github.com/DorkSquadRR/DorkNet.git
synced 2026-10-25 23:18:44 -07:00
feat: Refactor notification handling to use SystemNotificationService; remove outdated NotificationService references and update related logic
This commit is contained in:
@@ -0,0 +1,62 @@
|
|||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Text.Json;
|
||||||
|
using DorkNet.Server.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace DorkNet.Server.Tests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event notifications (cheers) now go through the REAL Message channel
|
||||||
|
/// (persisted MessageEntity + MessageReceived push with the client's
|
||||||
|
/// Message DTO shape), instead of the old blank-account misuse of
|
||||||
|
/// SubscriptionUpdateProfile. A cheer must land in the recipient's message
|
||||||
|
/// feed as a PlayerCheer (Type 50) from the cheerer.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class SystemNotificationTests : IClassFixture<DorkNetServerFactory>
|
||||||
|
{
|
||||||
|
private const int PlayerCheerType = 50;
|
||||||
|
|
||||||
|
private readonly DorkNetServerFactory _factory;
|
||||||
|
|
||||||
|
public SystemNotificationTests(DorkNetServerFactory factory) => _factory = factory;
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Cheering_a_player_creates_a_PlayerCheer_message_notification()
|
||||||
|
{
|
||||||
|
using var setup = ApiClient();
|
||||||
|
var cheerer = await GameClientSessionFactory.CreateAsync(setup, _factory.ApexDomain);
|
||||||
|
var target = await GameClientSessionFactory.CreateAsync(setup, _factory.ApexDomain);
|
||||||
|
|
||||||
|
using var client = ApiClient(cheerer);
|
||||||
|
using var resp = await client.PostAsync("/api/PlayerCheer/v1/create",
|
||||||
|
new FormUrlEncodedContent(new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["TargetAccountId"] = target.PlayerId.ToString(),
|
||||||
|
["Type"] = "0",
|
||||||
|
}));
|
||||||
|
resp.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
await using var scope = _factory.Services.CreateAsyncScope();
|
||||||
|
var db = scope.ServiceProvider.GetRequiredService<DorkNetDbContext>();
|
||||||
|
var msg = await db.Messages.FirstOrDefaultAsync(m =>
|
||||||
|
m.RecipientPlayerId == target.PlayerId && m.Type == PlayerCheerType);
|
||||||
|
|
||||||
|
Assert.NotNull(msg);
|
||||||
|
Assert.Equal(cheerer.PlayerId, msg!.SenderPlayerId);
|
||||||
|
// No orphan/blank-account row was written anywhere in the flow.
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpClient ApiClient(GameClientSession? session = null)
|
||||||
|
{
|
||||||
|
var client = _factory.CreateClient(new() { AllowAutoRedirect = false });
|
||||||
|
client.BaseAddress = new Uri($"http://api.{_factory.ApexDomain}");
|
||||||
|
client.Timeout = TimeSpan.FromSeconds(30);
|
||||||
|
client.DefaultRequestHeaders.Accept.ParseAdd("application/json");
|
||||||
|
client.DefaultRequestHeaders.UserAgent.ParseAdd("RecRoom/2023.03.21");
|
||||||
|
client.DefaultRequestHeaders.TryAddWithoutValidation("X-DorkNet-Version", "march_2023_03_21");
|
||||||
|
if (session is not null)
|
||||||
|
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", session.AccessToken);
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,7 +28,6 @@ namespace DorkNet.Server.Controllers.API.Photos;
|
|||||||
public class PhotosController(
|
public class PhotosController(
|
||||||
DorkNetDbContext db,
|
DorkNetDbContext db,
|
||||||
PlayerPresenceService presence,
|
PlayerPresenceService presence,
|
||||||
NotificationService notifications,
|
|
||||||
LevelService level,
|
LevelService level,
|
||||||
DomainConfig domain) : ControllerBase
|
DomainConfig domain) : ControllerBase
|
||||||
{
|
{
|
||||||
@@ -83,12 +82,11 @@ public class PhotosController(
|
|||||||
// Posting a photo gives a small XP bump — encourages camera use.
|
// Posting a photo gives a small XP bump — encourages camera use.
|
||||||
await level.AwardXpAsync(me, LevelService.InventionSavedXp, $"photo_posted:{photo.Id}");
|
await level.AwardXpAsync(me, LevelService.InventionSavedXp, $"photo_posted:{photo.Id}");
|
||||||
|
|
||||||
// Push to tagged players so their "Photos of me" feed refreshes.
|
// No real-time push for photo tags: there is no matching client
|
||||||
foreach (var taggedId in ParseTagged(photo.TaggedPlayerIdsCsv))
|
// message type, and the old SubscriptionUpdateProfile
|
||||||
await notifications.NotifyAsync(taggedId,
|
// {Reason:"TaggedInPhoto"} blob decoded to a blank account
|
||||||
PushNotificationId.SubscriptionUpdateProfile,
|
// ("Orphan player account (0)") without refreshing anything. The
|
||||||
new { Reason = "TaggedInPhoto", PhotoId = photo.Id, From = me });
|
// "Photos of me" feed refreshes on the tagged player's next fetch.
|
||||||
|
|
||||||
return Ok(ToDto(photo));
|
return Ok(ToDto(photo));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,9 +215,9 @@ public class PhotosController(
|
|||||||
{
|
{
|
||||||
await level.AwardXpAsync(photo.UploaderPlayerId,
|
await level.AwardXpAsync(photo.UploaderPlayerId,
|
||||||
LevelService.CheerReceivedXp, $"photo_cheer:{id}");
|
LevelService.CheerReceivedXp, $"photo_cheer:{id}");
|
||||||
await notifications.NotifyAsync(photo.UploaderPlayerId,
|
// No real-time push: no client message type for photo cheers, and
|
||||||
PushNotificationId.SubscriptionUpdateProfile,
|
// the old SubscriptionUpdateProfile blob only spawned a blank
|
||||||
new { Reason = "PhotoCheer", PhotoId = id, From = me });
|
// "Orphan player account (0)". Count refreshes on next fetch.
|
||||||
}
|
}
|
||||||
return Ok(new { cheered = true, count = photo.CheerCount });
|
return Ok(new { cheered = true, count = photo.CheerCount });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ namespace DorkNet.Server.Controllers.API.PlayerCheer;
|
|||||||
[Authorize]
|
[Authorize]
|
||||||
public class PlayerCheerController(
|
public class PlayerCheerController(
|
||||||
DorkNetDbContext db,
|
DorkNetDbContext db,
|
||||||
NotificationService notifications,
|
SystemNotificationService systemNotifications,
|
||||||
LevelService level) : ControllerBase
|
LevelService level) : ControllerBase
|
||||||
{
|
{
|
||||||
private long Me => this.RequireCurrentPlayerId();
|
private long Me => this.RequireCurrentPlayerId();
|
||||||
@@ -54,9 +54,12 @@ public class PlayerCheerController(
|
|||||||
});
|
});
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
await level.AwardXpAsync(target, LevelService.CheerReceivedXp, $"cheer_from:{Me}");
|
await level.AwardXpAsync(target, LevelService.CheerReceivedXp, $"cheer_from:{Me}");
|
||||||
await notifications.NotifyAsync(target,
|
// Real notification: a PlayerCheer message the client renders as
|
||||||
PushNotificationId.SubscriptionUpdateProfile,
|
// "X cheered you" (resolved from FromPlayerId), persisted to the
|
||||||
new { Reason = "CheerReceived", From = Me, Type = type });
|
// recipient's notification feed. Replaces the old blank-account
|
||||||
|
// SubscriptionUpdateProfile misuse.
|
||||||
|
await systemNotifications.SendAsync(target,
|
||||||
|
SystemNotificationService.MessageType.PlayerCheer, fromPlayerId: Me);
|
||||||
return Ok(new { success = true, error = "" });
|
return Ok(new { success = true, error = "" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ namespace DorkNet.Server.Controllers.API.PlayerState;
|
|||||||
[ApiController]
|
[ApiController]
|
||||||
public class PlayerStateController(
|
public class PlayerStateController(
|
||||||
DorkNetDbContext db,
|
DorkNetDbContext db,
|
||||||
NotificationService notifications,
|
SystemNotificationService systemNotifications,
|
||||||
LevelService level) : ControllerBase
|
LevelService level) : ControllerBase
|
||||||
{
|
{
|
||||||
// RecNet.Progressions.GetProgressionById / GetMyProgression — real
|
// RecNet.Progressions.GetProgressionById / GetMyProgression — real
|
||||||
@@ -150,9 +150,10 @@ public class PlayerStateController(
|
|||||||
// Reward the cheered player with a small XP bump.
|
// Reward the cheered player with a small XP bump.
|
||||||
await level.AwardXpAsync(playerId, LevelService.CheerReceivedXp, $"cheer_from:{me}");
|
await level.AwardXpAsync(playerId, LevelService.CheerReceivedXp, $"cheer_from:{me}");
|
||||||
|
|
||||||
await notifications.NotifyAsync(playerId,
|
// Real PlayerCheer notification (persisted + pushed as a Message),
|
||||||
PushNotificationId.SubscriptionUpdateProfile,
|
// replacing the old blank-account SubscriptionUpdateProfile misuse.
|
||||||
new { Reason = "CheerReceived", From = me, Type = type });
|
await systemNotifications.SendAsync(playerId,
|
||||||
|
SystemNotificationService.MessageType.PlayerCheer, fromPlayerId: me);
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,8 +20,7 @@ namespace DorkNet.Server.Controllers.API.PlayerSubscriptions;
|
|||||||
[Route("api/[controller]/v1")]
|
[Route("api/[controller]/v1")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public class PlayerSubscriptionsController(
|
public class PlayerSubscriptionsController(
|
||||||
DorkNetDbContext db,
|
DorkNetDbContext db) : ControllerBase
|
||||||
NotificationService notifications) : ControllerBase
|
|
||||||
{
|
{
|
||||||
private long Me => this.RequireCurrentPlayerId();
|
private long Me => this.RequireCurrentPlayerId();
|
||||||
|
|
||||||
@@ -61,11 +60,10 @@ public class PlayerSubscriptionsController(
|
|||||||
});
|
});
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
// Tell the target their subscriber count changed so the watch
|
// No real-time push: no client message type for new subscribers, and
|
||||||
// refreshes their profile page if open.
|
// the old SubscriptionUpdateProfile {Reason:"NewSubscriber"} blob
|
||||||
await notifications.NotifyAsync(targetId,
|
// decoded to a blank account ("Orphan player account (0)") without
|
||||||
PushNotificationId.SubscriptionUpdateProfile,
|
// refreshing anything. The subscriber count refreshes on next fetch.
|
||||||
new { Reason = "NewSubscriber", From = Me });
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user