Skip to content

Commit dc691a0

Browse files
Added a new upload endpoint
1 parent 01052a9 commit dc691a0

File tree

8 files changed

+99
-9
lines changed

8 files changed

+99
-9
lines changed

Sources/StreamChat/APIClient/CDNClient/CDNClient.swift

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ public protocol CDNClient {
4040
progress: ((Double) -> Void)?,
4141
completion: @escaping (Result<UploadedFile, Error>) -> Void
4242
)
43+
44+
func uploadAttachment<Payload>(
45+
_ attachment: StreamAttachment<Payload>,
46+
progress: ((Double) -> Void)?,
47+
completion: @escaping (Result<UploadedFile, Error>) -> Void
48+
)
4349
}
4450

4551
public extension CDNClient {
@@ -104,13 +110,52 @@ class StreamCDNClient: CDNClient {
104110
let fileData = try? Data(contentsOf: uploadingState.localFileURL) else {
105111
return completion(.failure(ClientError.AttachmentUploading(id: attachment.id)))
106112
}
113+
let endpoint = Endpoint<FileUploadPayload>.uploadAttachment(with: attachment.id.cid, type: attachment.type)
114+
115+
uploadAttachment(
116+
endpoint: endpoint,
117+
fileData: fileData,
118+
uploadingState: uploadingState,
119+
progress: progress,
120+
completion: completion
121+
)
122+
}
123+
124+
func uploadAttachment<Payload>(
125+
_ attachment: StreamAttachment<Payload>,
126+
progress: ((Double) -> Void)? = nil,
127+
completion: @escaping (Result<UploadedFile, Error>) -> Void
128+
) {
129+
guard
130+
let uploadingState = attachment.uploadingState,
131+
let fileData = try? Data(contentsOf: uploadingState.localFileURL) else {
132+
return completion(.failure(ClientError.Unknown()))
133+
}
134+
135+
let endpoint = Endpoint<FileUploadPayload>.uploadAttachment(type: attachment.type)
136+
137+
uploadAttachment(
138+
endpoint: endpoint,
139+
fileData: fileData,
140+
uploadingState: uploadingState,
141+
progress: progress,
142+
completion: completion
143+
)
144+
}
145+
146+
private func uploadAttachment<ResponsePayload>(
147+
endpoint: Endpoint<ResponsePayload>,
148+
fileData: Data,
149+
uploadingState: AttachmentUploadingState,
150+
progress: ((Double) -> Void)? = nil,
151+
completion: @escaping (Result<UploadedFile, Error>) -> Void
152+
) {
107153
// Encode locally stored attachment into multipart form data
108154
let multipartFormData = MultipartFormData(
109155
fileData,
110156
fileName: uploadingState.localFileURL.lastPathComponent,
111157
mimeType: uploadingState.file.type.mimeType
112158
)
113-
let endpoint = Endpoint<FileUploadPayload>.uploadAttachment(with: attachment.id.cid, type: attachment.type)
114159

115160
encoder.encodeRequest(for: endpoint) { [weak self] (requestResult) in
116161
var urlRequest: URLRequest

Sources/StreamChat/APIClient/Endpoints/AttachmentEndpoints.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,17 @@ import Foundation
77
extension Endpoint {
88
static func uploadAttachment(with cid: ChannelId, type: AttachmentType) -> Endpoint<FileUploadPayload> {
99
.init(
10-
path: .uploadAttachment(channelId: cid.apiPath, type: type == .image ? "image" : "file"),
10+
path: .uploadChannelAttachment(channelId: cid.apiPath, type: type == .image ? "image" : "file"),
11+
method: .post,
12+
queryItems: nil,
13+
requiresConnectionId: false,
14+
body: nil
15+
)
16+
}
17+
18+
static func uploadAttachment(type: AttachmentType) -> Endpoint<FileUploadPayload> {
19+
.init(
20+
path: .uploadAttachment(type == .image ? "image" : "file"),
1121
method: .post,
1222
queryItems: nil,
1323
requiresConnectionId: false,

Sources/StreamChat/APIClient/Endpoints/EndpointPath+OfflineRequest.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ extension EndpointPath {
1111
return true
1212
case .createChannel, .connect, .sync, .users, .guest, .members, .partialMemberUpdate, .search, .devices, .channels, .updateChannel,
1313
.deleteChannel, .channelUpdate, .muteChannel, .showChannel, .truncateChannel, .markChannelRead, .markChannelUnread,
14-
.markAllChannelsRead, .channelEvent, .stopWatchingChannel, .pinnedMessages, .uploadAttachment, .message,
14+
.markAllChannelsRead, .channelEvent, .stopWatchingChannel, .pinnedMessages, .uploadChannelAttachment, .message,
1515
.replies, .reactions, .messageAction, .banMember, .flagUser, .flagMessage, .muteUser, .translateMessage,
1616
.callToken, .createCall, .deleteFile, .deleteImage, .og, .appSettings, .threads, .thread, .markThreadRead, .markThreadUnread,
1717
.polls, .pollsQuery, .poll, .pollOption, .pollOptions, .pollVotes, .pollVoteInMessage, .pollVote,
18-
.unread, .blockUser, .unblockUser, .drafts, .reminders, .reminder, .liveLocations:
18+
.unread, .blockUser, .unblockUser, .drafts, .reminders, .reminder, .liveLocations, .uploadAttachment:
1919
return false
2020
}
2121
}

Sources/StreamChat/APIClient/Endpoints/EndpointPath.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ enum EndpointPath: Codable {
3636
case channelEvent(String)
3737
case stopWatchingChannel(String)
3838
case pinnedMessages(String)
39-
case uploadAttachment(channelId: String, type: String)
39+
case uploadChannelAttachment(channelId: String, type: String)
40+
case uploadAttachment(String)
4041

4142
case sendMessage(ChannelId)
4243
case message(MessageId)
@@ -125,7 +126,8 @@ enum EndpointPath: Codable {
125126
case let .channelEvent(channelId): return "channels/\(channelId)/event"
126127
case let .stopWatchingChannel(channelId): return "channels/\(channelId)/stop-watching"
127128
case let .pinnedMessages(channelId): return "channels/\(channelId)/pinned_messages"
128-
case let .uploadAttachment(channelId, type): return "channels/\(channelId)/\(type)"
129+
case let .uploadChannelAttachment(channelId, type): return "channels/\(channelId)/\(type)"
130+
case let .uploadAttachment(type): return "uploads/\(type)"
129131

130132
case let .sendMessage(channelId): return "channels/\(channelId.apiPath)/message"
131133
case let .message(messageId): return "messages/\(messageId)"

Sources/StreamChat/Models/Attachments/ChatMessageAttachment.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,39 @@
44

55
import Foundation
66

7+
public struct StreamAttachment<Payload> {
8+
/// The attachment type.
9+
public let type: AttachmentType
10+
11+
/// The attachment payload.
12+
public var payload: Payload
13+
14+
/// The downloading state of the attachment.
15+
///
16+
/// Reflects the downloading progress for attachments.
17+
public let downloadingState: AttachmentDownloadingState?
18+
19+
/// The uploading state of the attachment.
20+
///
21+
/// Reflects uploading progress for local attachments that require file uploading.
22+
/// Is `nil` for local attachments that don't need to be uploaded.
23+
///
24+
/// Becomes `nil` when the message with the current attachment is sent.
25+
public let uploadingState: AttachmentUploadingState?
26+
27+
public init(
28+
type: AttachmentType,
29+
payload: Payload,
30+
downloadingState: AttachmentDownloadingState?,
31+
uploadingState: AttachmentUploadingState?
32+
) {
33+
self.type = type
34+
self.payload = payload
35+
self.downloadingState = downloadingState
36+
self.uploadingState = uploadingState
37+
}
38+
}
39+
740
/// A type representing a chat message attachment.
841
/// `ChatMessageAttachment<Payload>` is an immutable snapshot of message attachment at the given time.
942
@dynamicMemberLookup

TestTools/StreamChatTestTools/Extensions/EndpoinPath+Equatable.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ extension EndpointPath: Equatable {
2828
case let (.channelEvent(string1), .channelEvent(string2)): return string1 == string2
2929
case let (.stopWatchingChannel(string1), .stopWatchingChannel(string2)): return string1 == string2
3030
case let (.pinnedMessages(string1), .pinnedMessages(string2)): return string1 == string2
31-
case let (.uploadAttachment(channelId1, type1), .uploadAttachment(channelId2, type2)): return channelId1 == channelId2 &&
31+
case let (.uploadChannelAttachment(channelId1, type1), .uploadChannelAttachment(channelId2, type2)): return channelId1 == channelId2 &&
3232
type1 ==
3333
type2
3434
case let (.sendMessage(channelId1), .sendMessage(channelId2)): return channelId1 == channelId2

Tests/StreamChatTests/APIClient/Endpoints/AttachmentEndpoints_Tests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ final class AttachmentEndpoints_Tests: XCTestCase {
2222

2323
for (type, pathComponent) in testCases {
2424
let expectedEndpoint: Endpoint<FileUploadPayload> = .init(
25-
path: .uploadAttachment(channelId: id.cid.apiPath, type: pathComponent),
25+
path: .uploadChannelAttachment(channelId: id.cid.apiPath, type: pathComponent),
2626
method: .post,
2727
queryItems: nil,
2828
requiresConnectionId: false,

Tests/StreamChatTests/APIClient/Endpoints/EndpointPath_Tests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ final class EndpointPathTests: XCTestCase {
121121
assertResultEncodingAndDecoding(.channelEvent("channel_idq"))
122122
assertResultEncodingAndDecoding(.stopWatchingChannel("channel_idq"))
123123
assertResultEncodingAndDecoding(.pinnedMessages("channel_idq"))
124-
assertResultEncodingAndDecoding(.uploadAttachment(channelId: "channel_id", type: "file"))
124+
assertResultEncodingAndDecoding(.uploadChannelAttachment(channelId: "channel_id", type: "file"))
125125

126126
assertResultEncodingAndDecoding(.sendMessage(ChannelId(type: .messaging, id: "the_id")))
127127
assertResultEncodingAndDecoding(.message("message_idm"))

0 commit comments

Comments
 (0)