Skip to content

Commit

Permalink
add mongo queue for send verification email
Browse files Browse the repository at this point in the history
  • Loading branch information
saroar committed Nov 1, 2023
1 parent 42046af commit 05297cf
Show file tree
Hide file tree
Showing 11 changed files with 279 additions and 151 deletions.
183 changes: 96 additions & 87 deletions Package.resolved

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ let package = Package(
.package(url: "https://github.com/vapor/fluent-mongo-driver.git", from: "1.1.2"),
.package(url: "https://github.com/vapor/leaf.git", from: "4.2.0"),
.package(url: "https://github.com/OpenKitten/MongoKitten.git", from: "6.7.0"),
.package(url: "https://github.com/orlandos-nl/MongoQueue.git", from: "0.1.0"),
.package(url: "https://github.com/vapor/jwt.git", from: "4.2.0"),
.package(url: "https://github.com/vapor/apns.git", from: "1.0.1"),
.package(url: "https://github.com/pointfreeco/vapor-routing", from: "0.1.2"),
Expand All @@ -36,6 +37,7 @@ let package = Package(
.product(name: "JWT", package: "jwt"),
.product(name: "APNS", package: "apns"),
.product(name: "MongoKitten", package: "MongoKitten"),
.product(name: "MongoQueue", package: "MongoQueue"),
.product(name: "AddaSharedModels", package: "AddaSharedModels"),
.product(name: "QueuesRedisDriver", package: "queues-redis-driver"),
.product(name: "Mailgun", package: "mailgun"),
Expand Down
6 changes: 4 additions & 2 deletions Sources/App/AppExtensions/Mailgun+Domains.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import Vapor

extension MailgunDomain {
static var sandbox: MailgunDomain {
.init( Environment.get("MAILGUN_DOMAIN") ?? "", .us)
.init( "sandbox85d89c55d2ae4a3b8aaeca56b3a025c5.mailgun.org", .us)
}
static var productoin: MailgunDomain { .init("word.justcal.me", .eu)}

static var production: MailgunDomain { .init("addame.com", .eu)}
}

22 changes: 22 additions & 0 deletions Sources/App/AppExtensions/MongoQueue+Application.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Vapor
import MongoQueue

private struct MongoQueueStorageKey: StorageKey {
public typealias Value = MongoQueue
}

extension Application {
public var mongoQueue: MongoQueue {
get {
storage[MongoQueueStorageKey.self]!
}
set {
storage[MongoQueueStorageKey.self] = newValue
}
}

public func initializeMongoQueue() {
self.mongoQueue = MongoQueue(collection: mongoDB["queue"])
}
}

5 changes: 5 additions & 0 deletions Sources/App/AppExtensions/Request+Extension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@

import Vapor
import MongoKitten
import MongoQueue

extension Request {
public var mongoDB: MongoDatabase {
return application.mongoDB.hopped(to: eventLoop)
}

public var mongoQueue: MongoQueue {
return application.mongoQueue
}
}
13 changes: 13 additions & 0 deletions Sources/App/AppExtensions/mongoQueues.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Vapor
import MongoQueue

func mongoQueue(_ app: Application) throws {
app.mongoQueue.registerTask(EmailJobMongoQueue.self, context: app)
do {
try app.mongoQueue.runInBackground()
} catch {
app.logger.info("\(error)")
}

}

60 changes: 60 additions & 0 deletions Sources/App/Jobs/EmailJobMongoQueue.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import Vapor
import Mailgun
import MongoQueue

struct EmailJobMongoQueue: RecurringTask {

var payload: EmailPayload
var vCodeAttempt: VerificationCodeAttempt

// The date this task should be executed at earliest
// We default to one week from 'now'
var initialTaskExecutionDate: Date { Date() }

// We can use any execution context. This context is provided on registration, and the job can have access to it to do 'the work'
// For example, if your job sends email, pass it a handle to the email client
typealias ExecutionContext = Application

// Ensures only one user engagement prompt exists per user
var uniqueTaskKey: String { vCodeAttempt.id!.hexString }

// The amount of time we expect this task to take if it's very slow
// This is optional, and has a sensible default. Note; it will not be the task's actual deadline.
// Instead, the task will be killed if worker executing this task goes offline unexpectedly
// There's a mechanism in MongoQueue to detect a stale or dead task worker
var maxTaskDuration: TimeInterval { 60 }

// This job has a low priority. Some other jobs may be done first if there's a contest for job queue time
var priority: TaskPriority { .higher }

// Execute the task. In our case, we check their last login date
func execute(withContext context: ExecutionContext) async throws {
let mailgunMessage = MailgunTemplateMessage(
from: context.config.noReplyEmail,
to: payload.recipient,
subject: payload.email.subject,
template: payload.email.templateName,
templateData: payload.email.templateData
)

do {
_ = try await context.mailgun().send(mailgunMessage).get()
} catch {
print(error)
}
}

// When to send the next reminder to the user
// If `nil` is returned, this job will never run again
func getNextRecurringTaskDate(_ context: ExecutionContext) async throws -> Date? {
return nil
}

// If we failed to run the job for whatever reason (SMTP issue, database issue or otherwise)
func onExecutionFailure(failureContext: QueuedTaskFailure<ExecutionContext>) async throws -> TaskExecutionFailureAction {
// Retry in 1 hour, `nil` for maxAttempts means we never stop retrying
print(failureContext.error)
return .retryAfter(3600, maxAttempts: nil)
}
}

32 changes: 32 additions & 0 deletions Sources/App/Models/Emails/VerificationEmail.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,35 @@ struct VerificationEmail: Email {
self.verifyUrl = verifyUrl
}
}

struct OTPEmail: Email {

let templateName: String
let name: String
let bodyWithOtp: String
let duration: String
let subject: String

var templateData: [String : String] {
[
"name": name,
"otp": bodyWithOtp,
"duration": duration
]
}

init(
templateName: String = "email_verification",
name: String,
bodyWithOtp: String,
duration: String,
subject: String = "Please verify your email"
) {
self.templateName = templateName
self.name = name
self.bodyWithOtp = bodyWithOtp
self.duration = duration
self.subject = subject

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,67 +19,48 @@ public func authenticationHandler(
}

let email = input.email.lowercased()
let code = String.randomDigits(ofLength: 6)
let message = "\(code)"

switch request.application.environment {
case .production:

try await request
.emailVerifier
.verifyOTPEmail(for: email, msg: message)

let smsAttempt = VerificationCodeAttempt(
email: email,
code: code,
expiresAt: Date().addingTimeInterval(5.0 * 60.0)
)

_ = try await smsAttempt.save(on: request.db).get()
let attemptId = try! smsAttempt.requireID()
return EmailLoginOutput(
email: email,
attemptId: attemptId
)

case .development:

let code = "336699"
// try await request
// .emailVerifier
// .verifyOTPEmail(for: input.email, msg: code)

let smsAttempt = VerificationCodeAttempt(
email: email,
code: code,
expiresAt: Date().addingTimeInterval(5.0 * 60.0)
)
_ = try await smsAttempt.save(on: request.db).get()

let attemptId = try! smsAttempt.requireID()
return EmailLoginOutput(
email: email,
attemptId: attemptId
)

default:

let code = "336699"

let smsAttempt = VerificationCodeAttempt(
email: email,
code: code,
expiresAt: Date().addingTimeInterval(5.0 * 60.0)
)
_ = try await smsAttempt.save(on: request.db).get()

let attemptId = try! smsAttempt.requireID()
return EmailLoginOutput(
email: email,
attemptId: attemptId
)

let randomDigits = request.application.environment
== .development || request.application.environment
== .testing ? "336699" : String.randomDigits(ofLength: 6)

let otp = "\(randomDigits)"
let minutes = 5.0
let expiresAt = Date().addingTimeInterval(minutes * 60.0)

let otpEmail = OTPEmail(
templateName: "addame_otp",
name: "Hello",
bodyWithOtp: otp,
duration: "\(minutes)",
subject: "Your Adda2 OTP Verification Code."
)

let smsAttempt = VerificationCodeAttempt(
email: email,
code: otp,
expiresAt: expiresAt
)

_ = try await smsAttempt.save(on: request.db).get()
let attemptId = try! smsAttempt.requireID()


let emailPayload = EmailPayload(otpEmail, to: email)
let task = EmailJobMongoQueue(payload: emailPayload, vCodeAttempt: smsAttempt)

do {
try await request.mongoQueue.queueTask(task)
} catch {
request.logger.info("\(error)")
}

return EmailLoginOutput(
email: email,
attemptId: attemptId
)


case .verifyEmail(let input):

guard let attempt = try await VerificationCodeAttempt.query(on: request.db)
Expand Down
2 changes: 1 addition & 1 deletion Sources/App/Services/EmailVerifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ struct EmailVerifier {

var verificationEmail = VerificationEmail.init(verifyUrl: msg)
verificationEmail.subject = "Addame2 Verification Code"
verificationEmail.templateName = "email_otp_verification_addame_server"
verificationEmail.templateName = "addame_otp"

try await self.queue.dispatch(
EmailJob.self,
Expand Down
6 changes: 4 additions & 2 deletions Sources/App/configure.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,12 @@ public func configure(_ app: Application) async throws {

// MARK: Mailgun
app.mailgun.configuration = .environment
app.mailgun.defaultDomain = .productoin
app.mailgun.defaultDomain = .production

// MARK: Queues
try queues(app)
// MARK: MongoQueues
app.initializeMongoQueue()
try mongoQueue(app)

// MARK: Services
app.randomGenerators.use(.random)
Expand Down

0 comments on commit 05297cf

Please sign in to comment.