Skip to content

Instantly share code, notes, and snippets.

@saroar
Created January 19, 2024 14:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save saroar/393f71f676919a1564d6b46eabb5b8be to your computer and use it in GitHub Desktop.
Save saroar/393f71f676919a1564d6b46eabb5b8be to your computer and use it in GitHub Desktop.
public struct AttachmentS3Client {
public let bucket: String
public let bucketWithEndpoint: String
public let awsS3: S3
private let awsClient: AWSClient
private let logger: Logger
public typealias UploadImageToS3Handler = @Sendable (
UIImage,
String?, // conversationId
String?, // userId
String, // bucket
S3, // awsS3
String // bucketWithEndpoint
) async throws -> String
public let uploadImageToS3: UploadImageToS3Handler
public init(
bucket: String,
endpoint: String,
awsClient: AWSClient,
awsS3: S3,
logger: Logger,
uploadImageToS3: @escaping UploadImageToS3Handler
) {
self.bucket = bucket
self.bucketWithEndpoint = "https://\(bucket).\(endpoint)"
self.awsClient = awsClient
self.awsS3 = awsS3
self.logger = logger
self.uploadImageToS3 = uploadImageToS3
}
}
extension AttachmentS3Client {
static func buildImageKey(conversationId: String? = nil, userId: String? = nil, imageFormat: String ) -> String {
let currentTime = Int64(Date().timeIntervalSince1970 * 1000)
var imageKey = String(format: "%ld", currentTime)
if let conversationId = conversationId {
imageKey = "uploads/images/\(conversationId)/\(imageKey).\(imageFormat)"
} else if let userId = userId {
imageKey = "uploads/images/\(userId)/\(imageKey).\(imageFormat)"
}
return imageKey
}
}
extension AttachmentS3Client {
// upload image to DigitalOcen Spaces
@Sendable
public static func uploadImage(
image: UIImage,
conversationId: String? = nil,
userId: String? = nil,
bucket: String,
awsS3: S3,
bucketWithEndpoint: String
) async throws -> String {
return try await withCheckedThrowingContinuation { continuation in
do {
let (imageData, imageFormat) = try image.compressImage(
compressionQuality: conversationId == nil ? .highest : .medium,
imageType: .jpeg,
passImagesType: .thumbnail
)
let imageKey = AttachmentS3Client.buildImageKey(conversationId: conversationId, userId: userId, imageFormat: imageFormat)
let body = AWSPayload.data(imageData)
// Put an Object
let putObjectRequest = S3.PutObjectRequest(
acl: .publicRead,
body: body,
bucket: bucket,
contentLength: Int64(imageData.count),
key: imageKey
)
let futureOutput = awsS3.putObject(putObjectRequest)
futureOutput.whenSuccess { response in
print(#line, self, response, imageKey)
let finalURL = bucketWithEndpoint + imageKey
continuation.resume(returning: finalURL)
}
futureOutput.whenFailure { error in
continuation.resume(throwing: error)
}
} catch {
continuation.resume(throwing: error)
}
}
}
}
extension AttachmentS3Client {
public static var live: AttachmentS3Client {
// Configure these values appropriately
let bucket = "your-bucket-name"
let endpoint = "your-endpoint"
let client = AWSClient(
credentialProvider: .static(
accessKeyId: EnvironmentKeys.accessKeyId,
secretAccessKey: EnvironmentKeys.secretAccessKey
),
httpClientProvider: .createNew
)
let s3Client = S3(
client: client,
region: .useast1,
endpoint: "https://\(endpoint)"
)
let logger = Logger(label: "com.learnplaygrow.attachmentS3Client")
return AttachmentS3Client(
bucket: bucket,
endpoint: endpoint,
awsClient: client,
awsS3: s3Client,
logger: logger,
uploadImageToS3: uploadImage
)
}
}
public enum AttachmentS3ClientKey: DependencyKey {
public static var liveValue: AttachmentS3Client = AttachmentS3Client.live
}
extension AttachmentS3ClientKey: TestDependencyKey {
public static let testValue = AttachmentS3Client.happyPath
}
extension DependencyValues {
public var attachmentS3Client: AttachmentS3Client {
get { self[AttachmentS3ClientKey.self] }
set { self[AttachmentS3ClientKey.self] = newValue }
}
}
public let logger = Logger(label: "com.learnplaygrow.attachmentS3Client")
// Use case
case .sendButtonTapped:
state.isUploadingImages = true
guard let userID = state.user?.id.hexString else {
logger.debug("Current User is missing")
return .none
}
let images = state.photosSelectorState.images.map { $0.image }
return .run { send in
for image in images {
let imageURLString = try await attachmentS3Client.uploadImageToS3(
image, nil, userID,
attachmentS3Client.bucket,
attachmentS3Client.awsS3,
attachmentS3Client.bucketWithEndpoint
)
// Handle the imageURLString...
}
}
@saroar
Copy link
Author

saroar commented Jan 19, 2024

Mocks

extension AttachmentS3Client {

    static let clientMock = AWSClient(
        credentialProvider: .static(
            accessKeyId: "",
            secretAccessKey: ""
        ),
        httpClientProvider: .createNew
    )

    static let mockS3Client = S3(
        client: clientMock,
        region: .useast1,
        endpoint: ""
    )

    static let mockLogger = Logger(label: "com.learnplaygrow.attachmentS3Client.mock")

    public static let empty = Self(
        bucket: "",
        endpoint: "",
        awsClient: clientMock,
        awsS3: mockS3Client, // You need to provide a mock S3 client
        logger: mockLogger,  // You need to provide a mock logger
        uploadImageToS3: { _, _, _, _, _, _ in "" } // Mock implementation
    )

    public static let happyPath = Self(
        bucket: "mock-bucket",
        endpoint: "mock-endpoint",
        awsClient: clientMock,
        awsS3: mockS3Client, // You need to provide a mock S3 client
        logger: mockLogger,  // You need to provide a mock logger
        uploadImageToS3: { _, _, _, _, _, _ in
            "https://mock-bucket.mock-endpoint/uploads/images/mock-id/mock-id_123456789.jpeg"
        }
    )
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment