Skip to content

Commit

Permalink
Created sms messenger
Browse files Browse the repository at this point in the history
  • Loading branch information
davidNicolas-cecs committed Jul 26, 2023
1 parent 74ae4db commit c48382e
Show file tree
Hide file tree
Showing 4 changed files with 224 additions and 0 deletions.
83 changes: 83 additions & 0 deletions swift/send-message/Messenger.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import Foundation
//an enum of the implementations we provide, anything else is rejected
enum MessageType: String {
case sms = "sms"
case email = "email"
case twitter = "twitter"
case discord = "discord"
case none
}

//error types which we want to define, handy for making sure all implementations provide the same errors
enum MessengerError: Error {
case validationError(error: String) //the message request
case providerError(error: String) //what if things appear in good order to us but discord is down? This error type signifies that there is a downstream error.
case misconfigurationError(error: String)// something is wrong with config like the API token for discord being wrong etc
}

//this is our interface for users to interact with us, input to this example function must conform to this
struct Message {
var type: MessageType
var recipient: String
var content: String
var subject: String?
}

protocol Messenger {
//make sure this is synchronous and you handle error handling
func sendMessage(messageRequest: Message) async -> Error?
}

func main(req: RequestValue, res: RequestResponse) async throws -> RequestResponse {

guard !req.payload.isEmpty,
let data = req.payload.data(using: .utf8),
let payload: [String: Any?] = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
let typeString = payload["type"] as? String,
let recipient = payload["recipient"] as? String,
let content = payload["content"] as? String else {
return res.json(data: ["success": false, "message" :"Misconfigurtion Error: Invalid payload."])
}

let type:MessageType = MessageType(rawValue: typeString) ?? .none
let messenger: Messenger

//initialize messenger with environment variables
do{
switch type{
case .sms:
messenger = try SMSMessenger(req.variables)
case .email:
messenger = try EmailMessenger(req.variables)
case .twitter:
messenger = try TwitterMessenger(req.variables)
case .discord:
messenger = try DiscordMessenger(req.variables)
default:
return res.json(data: ["success": false, "message" :"Misconfigurtion Error: Invalid Type"])
}
} catch{
return res.json(data: ["success": false, "message" :"Misconfigurtion Error: Missing environment variables."])
}

let subject = payload["subject"] as? String
let messageRequest = Message(type: type, recipient: recipient, content: content, subject: subject)

let result = await messenger.sendMessage(messageRequest: messageRequest)

if result == nil{
return res.json(data: ["success": true])
} else {
let messengerError = result as? MessengerError
switch messengerError{
case let .validationError(error):
return res.json(data: ["success": false, "message" :"Validation Error: \(error)"])
case let .providerError(error):
return res.json(data: ["success": false, "message" :"Provider Error: \(error)"])
case let .misconfigurationError(error):
return res.json(data: ["success": false, "message" :"Misconfigurtion Error: \(error)"])
default:
return res.json(data: ["success": false, "message" :"Unknown Error"])
}
}
}
54 changes: 54 additions & 0 deletions swift/send-message/message-types/email/Sources/email_message.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import AsyncHTTPClient
import Foundation
import NIO

class EmailMessenger: Messenger{

private let mailgunAPIKey: String
private let mailgunDomain: String
private let httpClient: HTTPClient

init(){
mailgunAPIKey = ProcessInfo.processInfo.environment["MAILGUN_API_KEY"]!
mailgunDomain = ProcessInfo.processInfo.environment["MAILGUN_DOMAIN"]!
self.httpClient = HTTPClient(eventLoopGroupProvider: .createNew)
}

public func sendMessage(messageRequest: Message) async -> Error? {

do{

let targetURL:String = "https://api.mailgun.net/v3/\(mailgunDomain)/messages"
let auth:String = "api:\(mailgunAPIKey)".data(using: .utf8)!.base64EncodedString()

let httpClient = HTTPClient(eventLoopGroupProvider: .createNew)
var request = HTTPClientRequest(url: targetURL)
request.method = .POST
request.headers.add(name: "Content-Type", value: "application/x-www-form-urlencoded") //same as sms
request.headers.add(name: "Authorization", value: "Basic \(auth)")

let bodyString: String = "from=me@samples.mailgun.org&to=\(messageRequest.recipient)&subject=\(messageRequest.subject ?? "")&text=\(messageRequest.content)"
let bodyData:Data = Data(bodyString.utf8)
request.body = .bytes(ByteBuffer(bytes: bodyData))

let response = try await httpClient.execute(request, timeout: .seconds(30))

if response.status == .ok {
try await httpClient.shutdown()
return nil
} else {
try await httpClient.shutdown()
return MessengerError.misconfigurationError(error: "\(response.status)")
}
}catch{
return MessengerError.misconfigurationError(error: "Error with credentials: \(error)")
}

}
}






20 changes: 20 additions & 0 deletions swift/send-message/message-types/sms/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// swift-tools-version:5.8
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "SMS",
dependencies: [
.package(url: "https://github.com/swift-server/async-http-client.git", from: "1.9.0"),
],
targets: [
.executableTarget(
name: "SMS",
dependencies: [
.product(name: "AsyncHTTPClient", package: "async-http-client"),
],
path: "Sources"
)
]
)
67 changes: 67 additions & 0 deletions swift/send-message/message-types/sms/Sources/sms_message.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import AsyncHTTPClient
import Foundation
import NIO
import NIOFoundationCompat

class SMSMessenger: Messenger{
private let authToken: String
private let accountSid: String
private let twilioNumber: String
private let httpClient: HTTPClient

init(_ env_vars: [String: String]) throws{
guard let accountSid = env_vars["TWILIO_ACCOUNT_SID"],
let authToken = env_vars["TWILIO_AUTH_TOKEN"],
let twilioNumber = env_vars["TWILIO_SENDER"] else {
throw MessengerError.misconfigurationError(error: "Missing environment variables.")
}
self.authToken = authToken
self.accountSid = accountSid
self.twilioNumber = twilioNumber
self.httpClient = HTTPClient(eventLoopGroupProvider: .createNew)

}
/*
Creates an HTTPClientRequest to send an SMS message via Twilio API.
*/
private func createRequest(message: Message) async throws-> HTTPClientRequest {
let targetURL: String = "https://api.twilio.com/2010-04-01/Accounts/\(accountSid)/Messages"
let credentials: String = "\(accountSid):\(authToken)"
var request = HTTPClientRequest(url: targetURL)
request.method = .POST
request.headers.add(name: "Content-Type", value: "application/x-www-form-urlencoded")
request.headers.add(name:"Authorization", value: "Basic \(Data(credentials.utf8).base64EncodedString())") // credentials
let bodyString: String = "From=\(twilioNumber)&Body=\(message.content)&To=\(message.recipient)"
let bodyData = Data(bodyString.utf8)
request.body = .bytes(ByteBuffer(bytes: bodyData))
return request
}
/*
Sends an SMS message using the Twilio API and handles potential errors during the process.
*/
public func sendMessage(messageRequest: Message) async -> Error?{

do{
let request = try await createRequest(message: messageRequest)
let response: HTTPClientResponse = try await httpClient.execute(request, timeout: .seconds(30))
//messege created successfully
switch response.status.code{
case 201:
return nil
case 400:
return MessengerError.misconfigurationError(error: "Bad request in request construction")
case 401:
return MessengerError.validationError(error: "Unauthorized credentials, Ensure credentials are correct")
case 404:
return MessengerError.providerError(error: "Message couldnt be found")
case 500,503,429:
return MessengerError.providerError(error: "Connection timed out, try again later")
default:
return MessengerError.providerError(error: "Error: \(response.status)")
}
}
catch{
return MessengerError.providerError(error: "Connection error: \(error)")
}
}
}

0 comments on commit c48382e

Please sign in to comment.