Adds the presigned-upload plumbing Outline's own web client uses for any file upload, not just avatars: attachments.create requests an upload target (server-assigned key, ACL, a short-lived signed form), then a direct multipart POST to that target (uploadUrl/form) actually uploads the bytes — confirmed against a live server's own request/ response shapes pulled from network capture. MultipartFormDataBuilder is a pure, fully-tested function (no network) building that S3-style presigned-POST body. uploadAttachmentFile deliberately sends no Bearer/CSRF header — the presigned form's `sig` field is what authorizes that specific request, same as an S3 presigned POST; flagged as best-effort pending live confirmation, cheap to add if the server turns out to also want one. UpdateUserAvatarRequest (users.update, avatar only) needed a custom encode(to:) — Swift's synthesized Encodable omits nil Optional keys via encodeIfPresent, but removing an avatar needs a literal "avatarUrl": null in the body, not the key missing. Confirmed the request shape (id + avatarUrl) from the captured request's Content-Length matching that shape and no shorter alternative. 6 new tests (multipart body structure/field ordering, explicit-null encoding, create/upload/update round trip) — 63/63 passing.
27 lines
1.1 KiB
Swift
27 lines
1.1 KiB
Swift
import Foundation
|
|
|
|
/// One uploaded file — created via a two-step presigned upload
|
|
/// (`attachments.create` for the upload target, then a direct POST to
|
|
/// `uploadUrl`/`form`). Confirmed live against a self-hosted instance's
|
|
/// local-storage backend; `size` comes back as a string there, not a
|
|
/// number — same "don't trust the vendored spec's implied types" lesson as
|
|
/// everywhere else this codebase has hit it.
|
|
public struct OutlineAttachment: Decodable, Identifiable, Sendable {
|
|
public let id: String
|
|
public let documentId: String?
|
|
public let contentType: String?
|
|
public let name: String?
|
|
public let url: String?
|
|
public let size: String?
|
|
}
|
|
|
|
/// `attachments.create`'s response — everything needed to perform the
|
|
/// actual upload. `form` fields (Content-Type, key, acl, sig, `_csrf`, …)
|
|
/// must all be included as their own multipart parts, in the same request
|
|
/// as the file itself, POSTed to `uploadUrl`.
|
|
public struct CreateAttachmentResult: Decodable, Sendable {
|
|
public let attachment: OutlineAttachment
|
|
public let uploadUrl: String
|
|
public let form: [String: String]
|
|
}
|