feat(guild): breakup + emblem_list + others_info
Implements GuildService.BreakupAsync (soft-delete guild, hard-delete dependents, clear viewer GuildId pointers). Wires /guild/breakup, /guild/emblem_list, /guild/others_info in GuildController. Fixes ToDetailDto to accept and populate leader_name from the member list. Adds GuildServiceBreakupTests (3 tests: cascade delete, permission guard, leader_name HTTP regression). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -52,10 +52,12 @@ public sealed class GuildController : SVSimController
|
||||
resp.GuildStatus = 2; // JOINING
|
||||
resp.JoinRequestCount = view.JoinRequestCount;
|
||||
resp.InviteCount = view.InviteCount;
|
||||
var memberDtos = await ToMemberDtoListAsync(view.Members, viewerId, ct);
|
||||
var leaderName = memberDtos.FirstOrDefault(m => m.ViewerId == view.Guild.LeaderViewerId)?.Name ?? "";
|
||||
resp.Guild = new GuildBundle
|
||||
{
|
||||
Detail = ToDetailDto(view.Guild, view.Members.Count),
|
||||
Members = await ToMemberDtoListAsync(view.Members, viewerId, ct),
|
||||
Detail = ToDetailDto(view.Guild, view.Members.Count, leaderName),
|
||||
Members = memberDtos,
|
||||
};
|
||||
}
|
||||
return resp;
|
||||
@@ -73,7 +75,12 @@ public sealed class GuildController : SVSimController
|
||||
}
|
||||
|
||||
[HttpPost("breakup")]
|
||||
public Task<ActionResult<EmptyResponse>> Breakup([FromBody] BaseRequest _, CancellationToken ct) => Stub();
|
||||
public async Task<ActionResult<EmptyResponse>> Breakup([FromBody] BaseRequest _, CancellationToken ct)
|
||||
{
|
||||
if (!TryGetViewerId(out var v)) return Unauthorized();
|
||||
var r = await _guild.BreakupAsync(v, ct);
|
||||
return r.IsOk ? new EmptyResponse() : MapErrorToWire(r);
|
||||
}
|
||||
|
||||
[HttpPost("update")]
|
||||
public Task<ActionResult<GuildUpdateResponse>> Update([FromBody] GuildUpdateRequest req, CancellationToken ct)
|
||||
@@ -91,12 +98,36 @@ public sealed class GuildController : SVSimController
|
||||
=> Task.FromResult<ActionResult<GuildSearchGuildResponse>>(new GuildSearchGuildResponse { List = new() });
|
||||
|
||||
[HttpPost("emblem_list")]
|
||||
public Task<ActionResult<GuildEmblemListResponse>> EmblemList([FromBody] BaseRequest _, CancellationToken ct)
|
||||
=> Task.FromResult<ActionResult<GuildEmblemListResponse>>(new GuildEmblemListResponse { EmblemList = new() });
|
||||
public async Task<ActionResult<GuildEmblemListResponse>> EmblemList([FromBody] BaseRequest _, CancellationToken ct)
|
||||
{
|
||||
if (!TryGetViewerId(out var viewerId)) return Unauthorized();
|
||||
var viewer = await _db.Viewers
|
||||
.AsNoTracking()
|
||||
.Include(v => v.Emblems)
|
||||
.FirstOrDefaultAsync(v => v.Id == viewerId, ct);
|
||||
var emblems = viewer?.Emblems ?? new();
|
||||
return new GuildEmblemListResponse
|
||||
{
|
||||
EmblemList = emblems.Select(e => new GuildEmblemEntry { EmblemId = e.Id }).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
[HttpPost("others_info")]
|
||||
public Task<ActionResult<GuildOthersInfoResponse>> OthersInfo([FromBody] GuildOthersInfoRequest req, CancellationToken ct)
|
||||
=> Task.FromResult<ActionResult<GuildOthersInfoResponse>>(new GuildOthersInfoResponse());
|
||||
public async Task<ActionResult<GuildOthersInfoResponse>> OthersInfo([FromBody] GuildOthersInfoRequest req, CancellationToken ct)
|
||||
{
|
||||
if (!TryGetViewerId(out _)) return Unauthorized();
|
||||
var guild = await _guild.GetActiveAsync(req.GuildId, ct);
|
||||
if (guild is null) return new GuildOthersInfoResponse();
|
||||
var memberCount = await _db.GuildMembers.CountAsync(m => m.GuildId == guild.GuildId, ct);
|
||||
var leader = await _db.Viewers.AsNoTracking().FirstOrDefaultAsync(v => v.Id == guild.LeaderViewerId, ct);
|
||||
return new GuildOthersInfoResponse
|
||||
{
|
||||
Guild = new GuildDetailSubTree
|
||||
{
|
||||
Detail = ToDetailDto(guild, memberCount, leader?.DisplayName ?? "")
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[HttpPost("friend_list")]
|
||||
public Task<ActionResult<GuildFriendListResponse>> FriendList([FromBody] BaseRequest _, CancellationToken ct)
|
||||
@@ -148,7 +179,7 @@ public sealed class GuildController : SVSimController
|
||||
|
||||
// ===== Private helpers =====
|
||||
|
||||
private static GuildDetailDto ToDetailDto(SVSim.Database.Entities.Guild.Guild guild, int memberCount) => new()
|
||||
private static GuildDetailDto ToDetailDto(SVSim.Database.Entities.Guild.Guild guild, int memberCount, string leaderName = "") => new()
|
||||
{
|
||||
GuildId = guild.GuildId,
|
||||
GuildName = guild.Name,
|
||||
@@ -158,8 +189,7 @@ public sealed class GuildController : SVSimController
|
||||
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 = "",
|
||||
LeaderName = leaderName,
|
||||
};
|
||||
|
||||
private async Task<List<GuildMemberInfoDto>> ToMemberDtoListAsync(
|
||||
|
||||
Reference in New Issue
Block a user