feat(guild): /guild/create + populated /guild/info
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,13 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SVSim.Database;
|
||||
using SVSim.Database.Entities.Guild;
|
||||
using SVSim.Database.Models;
|
||||
using SVSim.Database.Models.Config;
|
||||
using SVSim.Database.Services;
|
||||
using SVSim.Database.Services.Guild;
|
||||
using SVSim.EmulatedEntrypoint.Models.Dtos.Common;
|
||||
using SVSim.EmulatedEntrypoint.Models.Dtos.Common.Guild;
|
||||
using SVSim.EmulatedEntrypoint.Models.Dtos.Guild;
|
||||
using SVSim.EmulatedEntrypoint.Models.Dtos.Requests;
|
||||
|
||||
@@ -14,11 +19,16 @@ public sealed class GuildController : SVSimController
|
||||
{
|
||||
private readonly IGuildService _guild;
|
||||
private readonly IGameConfigService _configs;
|
||||
private readonly SVSimDbContext _db;
|
||||
|
||||
public GuildController(IGuildService guild, IGameConfigService configs)
|
||||
// Wire error code returned when guild operations fail (non-1 result_code).
|
||||
private const int GuildErrorResultCode = 2;
|
||||
|
||||
public GuildController(IGuildService guild, IGameConfigService configs, SVSimDbContext db)
|
||||
{
|
||||
_guild = guild;
|
||||
_configs = configs;
|
||||
_db = db;
|
||||
}
|
||||
|
||||
[HttpPost("info")]
|
||||
@@ -39,8 +49,14 @@ public sealed class GuildController : SVSimController
|
||||
}
|
||||
else
|
||||
{
|
||||
resp.GuildStatus = 2; // JOINING — fully populated path lands in Task 7
|
||||
// Task 7 populates `Guild`, JoinRequestCount, InviteCount.
|
||||
resp.GuildStatus = 2; // JOINING
|
||||
resp.JoinRequestCount = view.JoinRequestCount;
|
||||
resp.InviteCount = view.InviteCount;
|
||||
resp.Guild = new GuildBundle
|
||||
{
|
||||
Detail = ToDetailDto(view.Guild, view.Members.Count),
|
||||
Members = await ToMemberDtoListAsync(view.Members, viewerId, ct),
|
||||
};
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
@@ -48,7 +64,13 @@ public sealed class GuildController : SVSimController
|
||||
// ===== 21 remaining stubs — each returns the response DTO with defaults =====
|
||||
|
||||
[HttpPost("create")]
|
||||
public Task<ActionResult<EmptyResponse>> Create([FromBody] GuildCreateRequest req, CancellationToken ct) => Stub();
|
||||
public async Task<ActionResult<EmptyResponse>> Create([FromBody] GuildCreateRequest req, CancellationToken ct)
|
||||
{
|
||||
if (!TryGetViewerId(out var viewerId)) return Unauthorized();
|
||||
var res = await _guild.CreateAsync(viewerId, new(req.GuildName, req.Activity, req.JoinCondition), ct);
|
||||
if (res.IsOk) return new EmptyResponse();
|
||||
return MapErrorToWire(res);
|
||||
}
|
||||
|
||||
[HttpPost("breakup")]
|
||||
public Task<ActionResult<EmptyResponse>> Breakup([FromBody] BaseRequest _, CancellationToken ct) => Stub();
|
||||
@@ -124,6 +146,68 @@ public sealed class GuildController : SVSimController
|
||||
public Task<ActionResult<GuildChangeRoleResponse>> ChangeRole([FromBody] GuildChangeRoleRequest req, CancellationToken ct)
|
||||
=> Task.FromResult<ActionResult<GuildChangeRoleResponse>>(new GuildChangeRoleResponse { Members = new() });
|
||||
|
||||
// ===== Private helpers =====
|
||||
|
||||
private static GuildDetailDto ToDetailDto(SVSim.Database.Entities.Guild.Guild guild, int memberCount) => new()
|
||||
{
|
||||
GuildId = guild.GuildId,
|
||||
GuildName = guild.Name,
|
||||
Description = guild.Description,
|
||||
GuildEmblemId = guild.EmblemId,
|
||||
JoinCondition = (int)guild.JoinCondition,
|
||||
Activity = (int)guild.Activity,
|
||||
MemberNum = memberCount,
|
||||
LeaderViewerId = guild.LeaderViewerId,
|
||||
// LeaderName will be filled via members list join — leave "" here; caller populates it from member data if needed.
|
||||
LeaderName = "",
|
||||
};
|
||||
|
||||
private async Task<List<GuildMemberInfoDto>> ToMemberDtoListAsync(
|
||||
IReadOnlyList<GuildMember> members,
|
||||
long callerViewerId,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (members.Count == 0) return new();
|
||||
|
||||
var viewerIds = members.Select(m => m.ViewerId).ToList();
|
||||
|
||||
// Batch-load viewer rows with Info + SelectedEmblem + SelectedDegree.
|
||||
var viewers = await _db.Viewers
|
||||
.AsNoTracking()
|
||||
.Include(v => v.Info.SelectedEmblem)
|
||||
.Include(v => v.Info.SelectedDegree)
|
||||
.Where(v => viewerIds.Contains(v.Id))
|
||||
.ToDictionaryAsync(v => v.Id, ct);
|
||||
|
||||
var result = new List<GuildMemberInfoDto>(members.Count);
|
||||
foreach (var m in members)
|
||||
{
|
||||
viewers.TryGetValue(m.ViewerId, out var v);
|
||||
result.Add(ToMemberDto(m, v));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static GuildMemberInfoDto ToMemberDto(GuildMember member, Viewer? viewer) => new()
|
||||
{
|
||||
ViewerId = member.ViewerId,
|
||||
Name = viewer?.DisplayName ?? "",
|
||||
EmblemId = viewer?.Info?.SelectedEmblem?.Id is > 0 ? viewer.Info.SelectedEmblem.Id : 100_000_000L,
|
||||
CountryCode = viewer?.Info?.CountryCode ?? "",
|
||||
Rank = 1, // TODO: populate from actual rank data when rank tracking lands
|
||||
DegreeId = viewer?.Info?.SelectedDegree?.Id ?? 0,
|
||||
IsOfficialMarkDisplayed = viewer?.Info?.IsOfficialMarkDisplayed == true ? 1 : 0,
|
||||
Role = (int)member.Role,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Maps a failed GuildOpResult to the wire error envelope convention used throughout the
|
||||
/// codebase (see CampaignController.Fail(), AchievementController, MissionController).
|
||||
/// Returns HTTP 200 with a JSON body carrying <c>result_code: 2</c>.
|
||||
/// </summary>
|
||||
private ActionResult<EmptyResponse> MapErrorToWire(GuildOpResult res)
|
||||
=> Ok(new { result_code = GuildErrorResultCode });
|
||||
|
||||
private static Task<ActionResult<EmptyResponse>> Stub() =>
|
||||
Task.FromResult<ActionResult<EmptyResponse>>(new EmptyResponse());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user