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:
gamer147
2026-06-27 14:44:09 -04:00
parent 484b51086a
commit 358f43aa7a
3 changed files with 479 additions and 28 deletions

View File

@@ -1,6 +1,9 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using SVSim.Database;
using SVSim.Database.Services.Guild;
using SVSim.EmulatedEntrypoint.Models.Dtos.Common;
using SVSim.EmulatedEntrypoint.Models.Dtos.Common.Guild;
using SVSim.EmulatedEntrypoint.Models.Dtos.GuildChat;
using SVSim.EmulatedEntrypoint.Models.Dtos.Requests;
@@ -11,18 +14,76 @@ namespace SVSim.EmulatedEntrypoint.Controllers;
public sealed class GuildChatController : SVSimController
{
private readonly IGuildChatService _chat;
private readonly SVSimDbContext _db;
public GuildChatController(IGuildChatService chat) => _chat = chat;
public GuildChatController(IGuildChatService chat, SVSimDbContext db)
{
_chat = chat;
_db = db;
}
[HttpPost("messages")]
public Task<ActionResult<GuildChatMessagesResponse>> Messages([FromBody] GuildChatMessagesRequest req, CancellationToken ct)
=> Task.FromResult<ActionResult<GuildChatMessagesResponse>>(new GuildChatMessagesResponse
public async Task<ActionResult<GuildChatMessagesResponse>> Messages(
[FromBody] GuildChatMessagesRequest req,
CancellationToken ct)
{
if (!TryGetViewerId(out var viewerId)) return Unauthorized();
var window = await _chat.GetWindowAsync(
viewerId,
(int)req.StartMessageId,
req.Direction,
req.WaitInterval,
ct);
// Build chat_message list
var chatMessages = window.Messages.Select(m => new ChatMessageDto
{
ViewerId = m.AuthorViewerId,
MessageId = m.MessageId,
MessageType = (int)m.MessageType,
CreateTime = new DateTimeOffset(m.CreatedAt, TimeSpan.Zero).ToUnixTimeSeconds(),
Message = m.Body,
// deck / replay / room remain null until Task 17
}).ToList();
// Build deduplicated users[] catalog from message authors
var authorIds = window.Messages
.Select(m => m.AuthorViewerId)
.Distinct()
.ToList();
var viewers = authorIds.Count == 0
? new Dictionary<long, SVSim.Database.Models.Viewer>()
: await _db.Viewers
.AsNoTracking()
.Include(v => v.Info.SelectedEmblem)
.Include(v => v.Info.SelectedDegree)
.Where(v => authorIds.Contains(v.Id))
.ToDictionaryAsync(v => v.Id, ct);
var users = authorIds.Select(vid =>
{
viewers.TryGetValue(vid, out var v);
return new ChatUserDto
{
ViewerId = vid,
Name = v?.DisplayName ?? "",
EmblemId = v?.Info?.SelectedEmblem?.Id is > 0 ? v.Info.SelectedEmblem.Id : 100_000_000L,
CountryCode = v?.Info?.CountryCode ?? "",
Rank = 1, // TODO: real rank when rank tracking lands
DegreeId = v?.Info?.SelectedDegree?.Id ?? 0,
};
}).ToList();
return new GuildChatMessagesResponse
{
MaintenanceCardList = new(),
Users = new(),
ChatMessage = new(),
WaitInterval = 10,
});
Users = users,
ChatMessage = chatMessages,
WaitInterval = window.WaitIntervalSeconds,
};
}
[HttpPost("post")]
public Task<ActionResult<GuildChatPostResponse>> Post([FromBody] GuildChatPostRequest req, CancellationToken ct)