feat(squad): in-memory squad/lobby state + wire squad functions
Build & Deploy / build (push) Successful in 31s

Adds SquadService (singleton, process-local) tracking squads by the
squadId the client shares across party members, plus a user->squad index
so TryGetCompleteSquadInfo (no squadId in request) can resolve the
caller's squad. Members are lazily registered on any squad call.

- TryGetCompleteSquadInfo: returns the caller's real squad.
- SquadMemberReadyForMatch: joins the squad, records readiness/map,
  returns the squad + whether everyone is ready.
- SquadMemberSelectedMap: records the member's selected map.
- DbUserService.FindByIdAsync: resolve display name for squad members.

Groundwork for the station lobby; assumes the client drives squads via
these calls with a shared squadId (to confirm with a 2-client session).
Real-time SignalR push (unknown event names) deferred; the squad is
carried in responses + TryGetCompleteSquadInfo for now.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-14 15:34:43 +02:00
co-authored by Claude Opus 4.8
parent 922deb8397
commit 0588849e4c
6 changed files with 166 additions and 33 deletions
@@ -1,5 +1,8 @@
using System.Text.Json.Serialization;
using Prospect.Server.Api.Services.Auth.Extensions;
using Prospect.Server.Api.Services.CloudScript;
using Prospect.Server.Api.Services.Database;
using Prospect.Server.Api.Services.Squad;
public class TryGetCompleteSquadInfoRequest
{
@@ -51,26 +54,30 @@ public class TryGetCompleteSquadInfoFunction : ICloudScriptFunction<TryGetComple
{
private readonly ILogger<TryGetCompleteSquadInfoFunction> _logger;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly SquadService _squadService;
public TryGetCompleteSquadInfoFunction(ILogger<TryGetCompleteSquadInfoFunction> logger, IHttpContextAccessor httpContextAccessor)
public TryGetCompleteSquadInfoFunction(ILogger<TryGetCompleteSquadInfoFunction> logger, IHttpContextAccessor httpContextAccessor, SquadService squadService)
{
_httpContextAccessor = httpContextAccessor;
_logger = logger;
_squadService = squadService;
}
public async Task<FYPlayFabSquad> ExecuteAsync(TryGetCompleteSquadInfoRequest request)
public Task<FYPlayFabSquad> ExecuteAsync(TryGetCompleteSquadInfoRequest request)
{
var context = _httpContextAccessor.HttpContext;
if (context == null)
{
throw new CloudScriptException("CloudScript was not called within a http request");
}
// var userId = context.User.FindAuthUserId();
return new FYPlayFabSquad
var userId = context.User.FindAuthUserId();
var squad = _squadService.GetByUser(userId);
if (squad == null)
{
// SquadID = "100",
// Members = [],
};
// Not in a squad yet — return an empty squad (same as the previous stub).
return Task.FromResult(new FYPlayFabSquad());
}
_logger.LogInformation("TryGetCompleteSquadInfo: user {User} in squad {Squad} ({Count} member(s))", userId, squad.SquadId, squad.Members.Count);
return Task.FromResult(_squadService.ToClientSquad(squad));
}
}