feat(guild_chat): messages window + system events
- GuildChatService.EmitSystemEventAsync: replaces no-op stub with real chat-row insertion via _msgs.AppendAsync (per-guild monotonic id). - GuildChatService.GetWindowAsync: real implementation — verifies caller membership, delegates to IGuildChatMessageRepository.GetWindowAsync (direction 1=OLD/2=NEW/3=BOTH, limit 50), returns adaptive wait interval (ChatPollActiveSeconds when messages returned, ChatPollIdleSeconds otherwise). - Drop unused repo injections from GuildChatService (guilds, invites, joinRequests). - GuildChatController.Messages: wires the full response — chat_message[], users[] (deduplicated author profiles with Emblem+Degree nav includes), maintenance_card_list (empty until maintenance service lands), wait_interval. - GuildChatPollTests (7 integration tests): fresh-guild CreateGuild event, sequenced Create+Join ordering, direction=NEW filter, direction=BOTH window, users[] deduplication, empty result for viewer-not-in-guild, active vs idle interval comparison. All 7 pass; full suite 1515/1515. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
using SVSim.Database.Entities.Guild;
|
||||
using SVSim.Database.Models.Config;
|
||||
using SVSim.Database.Repositories.Guild;
|
||||
using SVSim.Database.Services;
|
||||
|
||||
@@ -5,31 +7,65 @@ namespace SVSim.Database.Services.Guild;
|
||||
|
||||
public sealed class GuildChatService : IGuildChatService
|
||||
{
|
||||
private readonly IGuildRepository _guilds;
|
||||
private readonly IGuildMemberRepository _members;
|
||||
private readonly IGuildInviteRepository _invites;
|
||||
private readonly IGuildJoinRequestRepository _joinRequests;
|
||||
private readonly IGuildChatMessageRepository _chatMessages;
|
||||
private readonly IGameConfigService _config;
|
||||
private readonly IGuildChatMessageRepository _msgs;
|
||||
private readonly IGameConfigService _cfg;
|
||||
|
||||
public GuildChatService(
|
||||
IGuildRepository guilds,
|
||||
IGuildMemberRepository members,
|
||||
IGuildInviteRepository invites,
|
||||
IGuildJoinRequestRepository joinRequests,
|
||||
IGuildChatMessageRepository chatMessages,
|
||||
IGameConfigService config)
|
||||
IGuildChatMessageRepository msgs,
|
||||
IGameConfigService cfg)
|
||||
{
|
||||
_guilds = guilds;
|
||||
_members = members;
|
||||
_invites = invites;
|
||||
_joinRequests = joinRequests;
|
||||
_chatMessages = chatMessages;
|
||||
_config = config;
|
||||
_msgs = msgs;
|
||||
_cfg = cfg;
|
||||
}
|
||||
|
||||
public Task<ChatWindow> GetWindowAsync(long viewerId, int startMessageId, int direction, int waitIntervalHint, CancellationToken ct = default)
|
||||
=> throw new NotImplementedException();
|
||||
// ── System-event emission ────────────────────────────────────────────────
|
||||
|
||||
public async Task EmitSystemEventAsync(
|
||||
int guildId,
|
||||
long actorViewerId,
|
||||
GuildChatMessageType type,
|
||||
string? body = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var msg = new GuildChatMessage
|
||||
{
|
||||
GuildId = guildId,
|
||||
AuthorViewerId = actorViewerId,
|
||||
MessageType = type,
|
||||
Body = body ?? "",
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
};
|
||||
await _msgs.AppendAsync(msg, ct);
|
||||
}
|
||||
|
||||
// ── Window query ─────────────────────────────────────────────────────────
|
||||
|
||||
public async Task<ChatWindow> GetWindowAsync(
|
||||
long viewerId,
|
||||
int startMessageId,
|
||||
int direction,
|
||||
int waitIntervalHint,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var membership = await _members.GetMembershipAsync(viewerId, ct);
|
||||
if (membership is null)
|
||||
{
|
||||
var idleInterval = _cfg.Get<GuildConfig>().ChatPollIdleSeconds;
|
||||
return new(Array.Empty<GuildChatMessage>(), idleInterval);
|
||||
}
|
||||
|
||||
const int limit = 50;
|
||||
var msgs = await _msgs.GetWindowAsync(membership.GuildId, startMessageId, direction, limit, ct);
|
||||
|
||||
var cfg = _cfg.Get<GuildConfig>();
|
||||
int wait = msgs.Count == 0 ? cfg.ChatPollIdleSeconds : cfg.ChatPollActiveSeconds;
|
||||
return new(msgs, wait);
|
||||
}
|
||||
|
||||
// ── Not-yet-implemented stubs ────────────────────────────────────────────
|
||||
|
||||
public Task<ChatPostResult> PostTextOrStampAsync(long viewerId, int type, string message, CancellationToken ct = default)
|
||||
=> throw new NotImplementedException();
|
||||
@@ -48,8 +84,4 @@ public sealed class GuildChatService : IGuildChatService
|
||||
|
||||
public Task<string?> GetDeckLogAsync(long viewerId, int messageId, CancellationToken ct = default)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
// No-op skeleton — will be wired in Phase 5.
|
||||
public Task EmitSystemEventAsync(int guildId, long actorViewerId, Entities.Guild.GuildChatMessageType type, string? body = null, CancellationToken ct = default)
|
||||
=> Task.CompletedTask;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user