-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
6042486
commit d8e7918
Showing
16 changed files
with
410 additions
and
14,723 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,9 @@ node_modules | |
.pnp | ||
.pnp.js | ||
|
||
# docs | ||
docs | ||
|
||
# testing | ||
coverage | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,5 @@ | ||
node_modules | ||
build | ||
.turbo | ||
dist | ||
build | ||
node_modules | ||
.env* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
REDIS_URL | ||
RABBITMQ_URL | ||
|
||
ACCESS_TOKEN | ||
PORT=3000 | ||
|
||
EMAIL_ID | ||
GMAIL_API_CLIENT_ID | ||
GMAIL_API_CLIENT_SECRET | ||
GMAIL_API_REFRESH_TOKEN | ||
GMAIL_EMAIL_ADDRESS | ||
|
||
GMAIL_API_ACCESS_TOKEN |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import Mail from 'nodemailer/lib/mailer'; | ||
|
||
import nodemailer from 'nodemailer'; | ||
|
||
export const createTransport = (): Mail => { | ||
return nodemailer.createTransport({ | ||
host: 'smtp.gmail.com', | ||
port: 587, | ||
secure: false, | ||
auth: { | ||
type: 'OAuth2', | ||
user: process.env.GMAIL_EMAIL_ADDRESS, | ||
clientId: process.env.GMAIL_API_CLIENT_ID, | ||
clientSecret: process.env.GMAIL_API_CLIENT_SECRET, | ||
refreshToken: process.env.GMAIL_API_REFRESH_TOKEN, | ||
}, | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import Redis from 'ioredis'; | ||
|
||
// Right now the entries do not have a TTL, so they will remain in Redis indefinitely. | ||
// This is not a problem for this application, but it is something to keep in mind. | ||
// Ideally, we would want to set a TTL for each entry, so that they are automatically removed after a certain amount of time. | ||
// The application sending the email job should check whether the email has been sent or failed and update in its database. | ||
|
||
export const createRedisClient = (): Redis => { | ||
return new Redis(process.env.REDIS_URL as string, { | ||
maxRetriesPerRequest: null, | ||
keyPrefix: 'core-mailer:', | ||
}); | ||
}; |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { UUID } from 'node:crypto'; | ||
|
||
import { Mail } from '../models/Mail'; | ||
import { MailService } from '../services/MailService'; | ||
|
||
// Add email to the queue. This will be picked up by the worker and sent. | ||
export const sendEmailController: any = async ( | ||
name: string, | ||
to: string, | ||
subject: string, | ||
text: string, | ||
html: string, | ||
) => { | ||
try { | ||
const mail: Mail = new Mail(process.env.EMAIL_ID as string, name, to, subject, text, html); | ||
|
||
const mailService: MailService = new MailService(); | ||
await mailService.initialize(); | ||
|
||
await mailService.publish(mail); | ||
|
||
return mail.jobId; | ||
} catch (error) { | ||
console.error(error); | ||
} | ||
}; | ||
|
||
// Check the status of an email job. | ||
export const fetchEmailJobStatus: any = async (jobId: UUID) => { | ||
try { | ||
const mailService: MailService = new MailService(); | ||
await mailService.initialize(); | ||
|
||
return await mailService.fetchJobStatus(jobId); | ||
} catch (error) { | ||
console.error(error); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,47 +1,92 @@ | ||
import express, { Request, Response } from 'express'; | ||
import dotenv from 'dotenv'; | ||
import { sendMail } from './controller/mail'; | ||
import { UUID } from 'node:crypto'; | ||
|
||
import express, { Express, Request, Response } from 'express'; | ||
|
||
import { fetchEmailJobStatus, sendEmailController } from './controllers/MailController'; | ||
import { MailService } from './services/MailService'; | ||
import { authorize } from './middlewares/auth'; | ||
|
||
const bodyParser = require('body-parser'); | ||
const cors = require('cors'); | ||
|
||
dotenv.config(); | ||
|
||
const port = process.env.PORT || 80; | ||
|
||
const app = express(); | ||
app.use( | ||
cors({ | ||
origin: '*', | ||
}), | ||
); | ||
const app: Express = express(); | ||
|
||
app.use(cors({ origin: '*' })); | ||
|
||
app.use(bodyParser.json()); | ||
|
||
app.get('/health', (req: Request, res: Response) => { | ||
const healthcheck: any = { | ||
resource: 'Techno Mailer Server', | ||
resource: 'Core Mailer', | ||
uptime: process.uptime(), | ||
responseTime: process.hrtime(), | ||
message: 'OK', | ||
timestamp: Date.now(), | ||
}; | ||
try { | ||
res.send(healthcheck); | ||
return res.send(healthcheck); | ||
} catch (error) { | ||
healthcheck.message = error; | ||
res.status(503).send(); | ||
return res.status(503).send(); | ||
} | ||
}); | ||
|
||
app.post('/send-mail', async (req: Request, res: Response) => { | ||
const { email, subject, text } = req.body; | ||
// Add email to queue | ||
app.post('/mail', authorize, async (req: Request, res: Response) => { | ||
try { | ||
await sendMail(email, subject, text); | ||
res.status(200).send('Email sent successfully'); | ||
const { name, to, subject, text, html } = req.body; | ||
|
||
if (!name || !to || !subject || !text || !html) | ||
return res.status(400).send({ message: 'Missing required fields' }); | ||
|
||
const jobId: UUID = await sendEmailController(name, to, subject, text, html); | ||
|
||
return res.status(200).send({ | ||
jobId: jobId, | ||
}); | ||
} catch (error) { | ||
res.status(500).send('Email failed to send'); | ||
console.error(error); | ||
return res.status(500).send({ message: 'Internal Server Error' }); | ||
} | ||
}); | ||
|
||
// Fetch email status | ||
app.get('/mail', authorize, async (req: Request, res: Response) => { | ||
try { | ||
const { jobId } = req.query; | ||
|
||
if (!jobId) return res.status(400).send({ message: 'Missing required fields' }); | ||
|
||
const status = await fetchEmailJobStatus(jobId as UUID); | ||
|
||
return res.status(200).send({ | ||
status: status, | ||
}); | ||
} catch (error) { | ||
console.error(error); | ||
return res.status(500).send({ message: 'Internal Server Error' }); | ||
} | ||
}); | ||
|
||
app.listen(port, () => { | ||
console.log(`Server is running at http://localhost:${port}`); | ||
console.log(`Core Mailer is running on ${port}`); | ||
}); | ||
|
||
// Check queue for new emails and send them | ||
async function startMailSub() { | ||
try { | ||
const rmqInstance: MailService = new MailService(); | ||
await rmqInstance.initialize(); | ||
await rmqInstance.subscribe(); | ||
console.log('Subscribed to RabbitMQ email queue'); | ||
} catch (error) { | ||
console.error('Failed to subscribe to RabbitMQ email queue: ', error); | ||
} | ||
} | ||
|
||
startMailSub(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { NextFunction, Request, Response } from 'express'; | ||
|
||
export const authorize = (req: Request, res: Response, next: NextFunction) => { | ||
try { | ||
const ACCESS_TOKEN = process.env.ACCESS_TOKEN; | ||
|
||
if (!ACCESS_TOKEN) { | ||
console.error('Access token not found on the server'); | ||
return res.status(500).json({ message: 'Internal Server Error' }); | ||
} | ||
|
||
const authHeader = req.headers['authorization']; | ||
const token = authHeader && authHeader.split(' ')[1]; | ||
|
||
if (!token) return res.status(401).json({ message: 'Unauthorized' }); | ||
|
||
if (token !== ACCESS_TOKEN) return res.status(403).json({ message: 'Unauthorized' }); | ||
|
||
next(); | ||
} catch (error) { | ||
console.error(error); | ||
return res.status(500).json({ message: 'Internal Server Error' }); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { randomUUID, UUID } from 'node:crypto'; | ||
|
||
export class Mail { | ||
readonly jobId: UUID; | ||
accessor from: string; | ||
accessor name: string; | ||
accessor to: string; | ||
accessor subject: string; | ||
accessor text: string; | ||
accessor html: string; | ||
|
||
constructor( | ||
from: string, | ||
name: string, | ||
to: string, | ||
subject: string, | ||
text: string, | ||
html: string, | ||
jobId: UUID = randomUUID(), | ||
) { | ||
this.jobId = jobId; | ||
this.from = from; | ||
this.name = name; | ||
this.to = to; | ||
this.subject = subject; | ||
this.text = text; | ||
this.html = html; | ||
} | ||
|
||
toString(): string { | ||
return `{ | ||
jobId: "${this.jobId}", | ||
from: "${this.from}", | ||
name: "${this.name}", | ||
to: "${this.to}", | ||
subject: "${this.subject}", | ||
text: "${this.text}", | ||
html: "${this.html}" | ||
}`; | ||
} | ||
} | ||
|
||
export function parseMail(str: string): Mail { | ||
const parsed = JSON.parse(str.replace(/(\w+):/g, '"$1":')); | ||
return new Mail( | ||
parsed.from, | ||
parsed.name, | ||
parsed.to, | ||
parsed.subject, | ||
parsed.text, | ||
parsed.html, | ||
parsed.jobId, | ||
); | ||
} |
Oops, something went wrong.