using DorkNet.Models.Notification;
using DorkNet.Server.Data;
using DorkNet.Server.Data.Entities;
namespace DorkNet.Server.Services;
///
/// The REAL channel for event notifications (cheers, friend events, room
/// co-owner changes, …). A notification is a
/// with a discriminator: it persists to the
/// recipient's message/notification feed (so GET api/messages
/// returns it) and is pushed live via MessageReceived with the
/// client's Message DTO shape (RecNet.Runtime KJECOLODAFA:
/// Id/FromPlayerId/SentTime/Type/Data/RoomId/PlayerEventId).
///
/// This replaces the old anti-pattern of pushing {Reason:"…"} blobs
/// on the SubscriptionUpdateProfile ("AccountUpdate") channel — the
/// 2023 client decoded those as its Account DTO, producing a blank account
/// (accountId 0) and "Orphan player account (0) found" log spam, and never
/// rendered the notification. Every field type here matches the client
/// reader (FromPlayerId is Int32; SentTime is an ISO DateTime; Type/RoomId/
/// PlayerEventId are number/nullable-number).
///
public sealed class SystemNotificationService(DorkNetDbContext db, NotificationService notifications)
{
/// RecNet message-type enum (KJECOLODAFA+MEPCALGGMJC),
/// verified from the 2023.03.21 client. Only the values we send are
/// listed; add more as needed.
public static class MessageType
{
public const int TextMessage = 30;
public const int FriendRequestAccepted = 40;
public const int FriendCodeUsed = 41;
public const int PlayerCheer = 50;
public const int PlayerCheerAnonymous = 51;
public const int RoomCoOwnerAdded = 60;
public const int RoomCoOwnerRemoved = 61;
public const int RoomCoOwnerInvited = 62;
public const int CreatorPublishedNewRoom = 70;
public const int PlayerAttendingEvent = 80;
public const int PlayerEventInvitation = 81;
}
/// Persist and push a system notification to
/// .
/// is the actor the client resolves the name/avatar from (0 for a
/// system/anonymous notification). is the
/// type-specific payload string (empty when the Type + FromPlayerId are
/// sufficient, e.g. a cheer). Returns the created message id.
public async Task SendAsync(
long recipientPlayerId,
int type,
long fromPlayerId = 0,
string data = "",
long? roomId = null)
{
if (recipientPlayerId <= 0) return 0;
var msg = new MessageEntity
{
SenderPlayerId = fromPlayerId,
RecipientPlayerId = recipientPlayerId,
Body = data ?? string.Empty,
Type = type,
RoomId = roomId,
SentAt = DateTime.UtcNow,
};
db.Messages.Add(msg);
await db.SaveChangesAsync();
await notifications.NotifyAsync(recipientPlayerId, PushNotificationId.MessageReceived, new
{
Id = msg.Id,
// Client Message DTO reads FromPlayerId as Int32.
FromPlayerId = unchecked((int)fromPlayerId),
SentTime = msg.SentAt,
Type = type,
Data = msg.Body,
RoomId = roomId,
PlayerEventId = (long?)null,
});
return msg.Id;
}
}