feat(api): delete workspace groups and record who signed a content event
This commit is contained in:
1 parent
55f1b70d89
commit
fc1dc08ebe
11 files changed
+80
-7
No files matched your search
@@ -6,5 +6,5 @@ public static class ApiContract
|
||||
// 去掉设备名与设备清单接口。设备只是账号的属性。
|
||||
// v8: 频道强制登录,去掉未登录访客(allow_guests、/guests、终端席位);便捷分享不受影响。
|
||||
// Keep this aligned with contracts/api-contract.json and docs/openapi.yaml.
|
||||
public const string Version = "8.0.0";
|
||||
public const string Version = "8.1.0";
|
||||
}
|
||||
@@ -42,6 +42,8 @@ public static partial class Endpoints
|
||||
var req = await ReadJson(ctx.Request, ApiJson.Default.WorkspaceNameRequest, ct);
|
||||
return Json(View(svc.RenameWorkspaceGroup(ParseId(id), RequestAuth.FromHeaders(ctx.Request, false), groupId, req.Name)), ApiJson.Default.WorkspaceBody);
|
||||
});
|
||||
api.MapDelete("/spaces/{id}/groups/{groupId}", (string id, string groupId, HttpContext ctx, ChannelService svc) =>
|
||||
Json(View(svc.DeleteWorkspaceGroup(ParseId(id), RequestAuth.FromHeaders(ctx.Request, false), groupId)), ApiJson.Default.WorkspaceBody));
|
||||
api.MapPost("/spaces/{id}/folders", async (string id, HttpContext ctx, ChannelService svc, CancellationToken ct) =>
|
||||
{
|
||||
var req = await ReadJson(ctx.Request, ApiJson.Default.WorkspaceFolderRequest, ct);
|
||||
|
||||
@@ -568,7 +568,7 @@ public sealed partial class ChannelService(
|
||||
{
|
||||
EventId = Codec.Encode(signedEvent.EventIdBytes), Kind = signedEvent.EventKind, FrameCount = signedEvent.FrameCount,
|
||||
ManifestHash = Convert.ToBase64String(signedEvent.ManifestHash).TrimEnd('=').Replace('+', '-').Replace('/', '_'),
|
||||
SignerDeviceId = signedEvent.DeviceId,
|
||||
SignerDeviceId = signedEvent.DeviceId, SignerAccount = signedEvent.Username,
|
||||
Signature = Convert.ToBase64String(signedEvent.Signature).TrimEnd('=').Replace('+', '-').Replace('/', '_'),
|
||||
TargetItemId = signedEvent.TargetItemId,
|
||||
SignerRootPublicKey = signedEvent.RootPublicKey, SignerDevicePublicKey = signedEvent.DevicePublicKey,
|
||||
|
||||
@@ -84,6 +84,13 @@ public sealed partial class ChannelService
|
||||
public WorkspaceState CreateWorkspaceGroup(Id16 id, Credentials creds, string name) => CreateWorkspaceGroupResult(id, creds, name).State;
|
||||
public WorkspaceState RenameWorkspaceGroup(Id16 id, Credentials creds, string groupId, string name) => ChangeWorkspace(id, creds, (_, state, _) =>
|
||||
(state.Groups.Find(g => g.Id == groupId) ?? throw new WebDropException(ErrorCode.NotFound)).Name = WorkspaceName(name));
|
||||
/// <summary>删除分组:组里的频道回到未分组,频道本身和内容都不动</summary>
|
||||
public WorkspaceState DeleteWorkspaceGroup(Id16 id, Credentials creds, string groupId) => ChangeWorkspace(id, creds, (_, state, _) =>
|
||||
{
|
||||
var group = state.Groups.Find(g => g.Id == groupId) ?? throw new WebDropException(ErrorCode.NotFound);
|
||||
foreach (var channel in state.Channels.Where(c => c.GroupId == groupId)) channel.GroupId = null;
|
||||
state.Groups.Remove(group);
|
||||
});
|
||||
public WorkspaceState CreateWorkspaceFolder(Id16 id, Credentials creds, string threadId, string? parentId, string name) => ChangeWorkspace(id, creds, (_, state, _) =>
|
||||
state.Folders.Add(new WorkspaceFolder { Id = Guid.NewGuid().ToString(), ThreadId = threadId, ParentId = parentId, Name = WorkspaceName(name) }));
|
||||
public WorkspaceState RenameWorkspaceFolder(Id16 id, Credentials creds, string folderId, string name) => ChangeWorkspace(id, creds, (_, state, _) =>
|
||||
|
||||
@@ -30,6 +30,8 @@ public sealed class SignedContentEventRecord
|
||||
[JsonPropertyName("frameCount")] public required int FrameCount { get; init; }
|
||||
[JsonPropertyName("manifestHash")] public required string ManifestHash { get; init; }
|
||||
[JsonPropertyName("signerDeviceId")] public required string SignerDeviceId { get; init; }
|
||||
/// <summary>签名者的账号。空间主撤回别人的消息时,签名者不是作者;旧记录没有这个字段,按作者处理</summary>
|
||||
[JsonPropertyName("signerAccount")] public string? SignerAccount { get; init; }
|
||||
[JsonPropertyName("signature")] public required string Signature { get; init; }
|
||||
[JsonPropertyName("targetItemId")] public string? TargetItemId { get; init; }
|
||||
[JsonPropertyName("signerRootPublicKey")] public required string SignerRootPublicKey { get; init; }
|
||||
|
||||
@@ -45,6 +45,26 @@ public sealed class WorkspaceApiTests(ServerFixture server) : IClassFixture<Serv
|
||||
using var loaded = await server.Http.SendAsync(read); Assert.Equal(2, (await ReadJsonAsync(loaded)).GetProperty("structure").GetProperty("channels").GetArrayLength());
|
||||
}
|
||||
[Fact]
|
||||
public async Task DeletingAGroupMovesItsChannelsBackToUngrouped()
|
||||
{
|
||||
var a = await server.CreateAsync();
|
||||
using var createGroup = server.Req(HttpMethod.Post, $"/api/v1/spaces/{a.Id}/groups", a.Owner, JsonBody(new { name = "待删分组" }));
|
||||
using var groupResponse = await server.Http.SendAsync(createGroup);
|
||||
var groupId = (await ReadJsonAsync(groupResponse)).GetProperty("group").GetProperty("id").GetString();
|
||||
using var createChannel = server.Req(HttpMethod.Post, $"/api/v1/spaces/{a.Id}/channels", a.Owner, JsonBody(new { name = "组内频道", groupId }));
|
||||
using var channelResponse = await server.Http.SendAsync(createChannel); Assert.Equal(HttpStatusCode.Created, channelResponse.StatusCode);
|
||||
|
||||
using var delete = server.Req(HttpMethod.Delete, $"/api/v1/spaces/{a.Id}/groups/{groupId}", a.Owner);
|
||||
using var deleted = await server.Http.SendAsync(delete); Assert.Equal(HttpStatusCode.OK, deleted.StatusCode);
|
||||
var structure = (await ReadJsonAsync(deleted)).GetProperty("structure");
|
||||
Assert.DoesNotContain(structure.GetProperty("groups").EnumerateArray(), g => g.GetProperty("id").GetString() == groupId);
|
||||
var moved = structure.GetProperty("channels").EnumerateArray().Single(ch => ch.GetProperty("name").GetString() == "组内频道");
|
||||
Assert.True(!moved.TryGetProperty("groupId", out var remaining) || remaining.ValueKind == System.Text.Json.JsonValueKind.Null);
|
||||
|
||||
using var again = server.Req(HttpMethod.Delete, $"/api/v1/spaces/{a.Id}/groups/{groupId}", a.Owner);
|
||||
using var missing = await server.Http.SendAsync(again); Assert.Equal(HttpStatusCode.NotFound, missing.StatusCode);
|
||||
}
|
||||
[Fact]
|
||||
public async Task AdvertisedDefaultsRespectDeploymentLimitsAndMatchCreation()
|
||||
{
|
||||
await using var isolated = WithSettings(new() { ["WebDrop:Limits:MaxMembers"] = "3", ["WebDrop:Limits:MinTtlSeconds"] = "120", ["WebDrop:Limits:MaxTtlSeconds"] = "3600" });
|
||||
|
||||
Reference in new issue
Block a user