Files
WebDrop/server/src/WebDrop.Core/Channels/Dtos.cs
T
justmeandClaude Sonnet 5.5 7274390aa1
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/push/web-build Pipeline failed
ci/woodpecker/push/dev-image-api unknown status
perf(host): store avatars as content-addressed files and serve them from disk
Space info now carries avatar_version instead of the inlined thumbnail; the worker fetches the thumbnail once per version. Legacy base64 rows are migrated to files on startup, and the daily backup uploads avatar files.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> [CI]
2026-10-19 14:16:50 +08:00

144 lines
7.2 KiB
C#

using System.Buffers.Binary;
using System.Text.Json.Serialization;
namespace WebDrop.Core;
public sealed class CreateChannelRequest
{
[JsonPropertyName("key_package")] public string? KeyPackage { get; init; }
[JsonPropertyName("join_password")] public string? JoinPassword { get; init; }
[JsonPropertyName("id")] public string? Id { get; init; }
[JsonPropertyName("mode")] public ChannelMode? Mode { get; init; }
[JsonPropertyName("settings")] public ChannelSettings? Settings { get; init; }
[JsonPropertyName("read_hash")] public string? ReadHash { get; init; }
[JsonPropertyName("write_hash")] public string? WriteHash { get; init; }
[JsonPropertyName("owner_hash")] public string? OwnerHash { get; init; }
}
public sealed class CreateShareRequest
{
[JsonPropertyName("id")] public string? Id { get; init; }
[JsonPropertyName("read_hash")] public string? ReadHash { get; init; }
[JsonPropertyName("owner_hash")] public string? OwnerHash { get; init; }
[JsonPropertyName("settings")] public ShareSettings? Settings { get; init; }
}
public sealed class FinishShareRequest
{
[JsonPropertyName("manifest_item_id")] public string? ManifestItemId { get; init; }
}
public sealed class ShareInfo
{
[JsonPropertyName("id")] public required string Id { get; init; }
[JsonPropertyName("state")] public required string State { get; init; }
[JsonPropertyName("expires_at")] public required long ExpiresAt { get; init; }
[JsonPropertyName("created_at")] public required long CreatedAt { get; init; }
[JsonPropertyName("sealed_at")] public long? SealedAt { get; init; }
[JsonPropertyName("manifest_item_id")] public string? ManifestItemId { get; init; }
[JsonPropertyName("settings")] public required ShareSettings Settings { get; init; }
[JsonPropertyName("items")] public required IReadOnlyList<ShareItemInfo> Items { get; init; }
}
public sealed class ShareItemInfo
{
[JsonPropertyName("complete")] public bool Complete { get; init; }
[JsonPropertyName("item_id")] public required string ItemId { get; init; }
[JsonPropertyName("bytes")] public required long Bytes { get; init; }
[JsonPropertyName("frames")] public required int Frames { get; init; }
[JsonPropertyName("max_downloads")] public required long MaxDownloads { get; init; }
[JsonPropertyName("downloads_left")] [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public long? DownloadsLeft { get; init; }
}
public sealed class ChannelInfo
{
// ───── 调用者在这个空间里的能力。前端只读不算:规则只在后端一处
/// <summary>调用者是空间所有者</summary>
[JsonPropertyName("is_owner")] public bool IsOwner { get; init; }
[JsonPropertyName("can_upload")] public bool CanUpload { get; init; }
/// <summary>能重命名频道、管理分组(所有者,或被授予「管理频道和分组」)</summary>
[JsonPropertyName("can_manage_structure")] public bool CanManageStructure { get; init; }
[JsonPropertyName("can_create_channel")] public bool CanCreateChannel { get; init; }
[JsonPropertyName("can_manage_folders")] public bool CanManageFolders { get; init; }
[JsonPropertyName("can_invite")] public bool CanInvite { get; init; }
[JsonPropertyName("can_edit_settings")] public bool CanEditSettings { get; init; }
[JsonPropertyName("can_manage_roles")] public bool CanManageRoles { get; init; }
/// <summary>能移除成员、撤销邀请;具体能移除谁见成员列表里每一项的 can_remove</summary>
[JsonPropertyName("can_remove_members")] public bool CanRemoveMembers { get; init; }
/// <summary>能删除任何人的消息和文件(所有者,或删除策略为 anyone)</summary>
[JsonPropertyName("can_delete_any")] public bool CanDeleteAny { get; init; }
/// <summary>能删除自己发的消息和文件</summary>
[JsonPropertyName("can_delete_own")] public bool CanDeleteOwn { get; init; }
[JsonPropertyName("password_required")] public bool PasswordRequired { get; init; }
[JsonPropertyName("creator_account")] public string? CreatorAccount { get; init; }
/// <summary>头像版本标记,变了就要重新取头像(GET /channels/{id}/avatar);没有头像为 null。头像本体不随空间信息下发。</summary>
[JsonPropertyName("avatar_version")] public string? AvatarVersion { get; init; }
[JsonPropertyName("id")] public required string Id { get; init; }
[JsonPropertyName("mode")] public required ChannelMode Mode { get; init; }
[JsonPropertyName("settings")] public required ChannelSettings Settings { get; init; }
/// <summary>频道成员数(账号),不统计账号设备数。</summary>
[JsonPropertyName("members_used")] public required int MembersUsed { get; init; }
/// <summary>从该 seq 起的续传有效;0 &lt; from &lt; oldest_seq 的续传会得到 410,需从 0 全量同步。</summary>
[JsonPropertyName("oldest_seq")] public required long OldestSeq { get; init; }
[JsonPropertyName("last_seq")] public required long LastSeq { get; init; }
[JsonPropertyName("created_at")] public required long CreatedAt { get; init; }
[JsonPropertyName("expires_at")] public required long ExpiresAt { get; init; }
}
public sealed class JoinResult
{
[JsonPropertyName("joined_at")] public required long JoinedAt { get; init; }
/// <summary>加入后的成员人数,同一账号的其他终端不增加此值。</summary>
[JsonPropertyName("members_used")] public required int MembersUsed { get; init; }
[JsonPropertyName("members_max")] public required int MembersMax { get; init; }
}
public sealed class AppendResult
{
[JsonPropertyName("seq")] public required long Seq { get; init; }
/// <summary>true 为新追加(201),false 为幂等重发(200)。</summary>
[JsonIgnore] public bool Created { get; init; }
}
/// <summary>一条日志记录的线格式:seq(u64) ‖ kind(u8) ‖ body。</summary>
public sealed record LogRecord(long Seq, byte[] Bytes)
{
public static byte[] Encode(long seq, LogKind kind, ReadOnlySpan<byte> body)
{
var bytes = new byte[9 + body.Length];
BinaryPrimitives.WriteInt64BigEndian(bytes, seq);
bytes[8] = (byte)kind;
body.CopyTo(bytes.AsSpan(9));
return bytes;
}
public static byte[] EncodeTombstone(long seq, Id16 itemId, TombstoneReason reason)
{
Span<byte> body = stackalloc byte[Id16.Size + 1];
itemId.WriteTo(body);
body[Id16.Size] = (byte)reason;
return Encode(seq, LogKind.Tombstone, body);
}
public static byte[] EncodeAnnounce(long seq, Id16 itemId, FrameFlags flags, long createdMs)
{
Span<byte> body = stackalloc byte[Id16.Size + 1 + 8];
itemId.WriteTo(body);
body[Id16.Size] = (byte)flags;
BinaryPrimitives.WriteInt64BigEndian(body[(Id16.Size + 1)..], createdMs);
return Encode(seq, LogKind.Announce, body);
}
}
public sealed record LogPage(IReadOnlyList<LogRecord> Records, long LastSeq);
/// <summary>GATED 条目拉取的一个批次:自 FromIndex 起连续的完整封装。</summary>
public sealed record ItemChunk(IReadOnlyList<byte[]> Frames, uint NextIndex, bool Complete);
[JsonSerializable(typeof(ChannelSettings))]
[JsonSerializable(typeof(ShareSettings))]
public sealed partial class CoreJsonContext : JsonSerializerContext;