Add email integration to be able to broadcast emails to all users.

Resolves #388
This commit is contained in:
Kurt Hutten
2021-06-29 06:37:04 +10:00
parent 7ef8d8d1ff
commit 0da15443cb
13 changed files with 173 additions and 5 deletions

View File

@@ -1,5 +1,6 @@
{
"cSpell.words": [
"Hutten"
"Hutten",
"sendmail"
]
}

View File

@@ -17,3 +17,14 @@ CLOUDINARY_API_KEY=476712943135152
# See: https://redwoodjs.com/docs/logger for level options:
# trace | info | debug | warn | error | silent
# LOG_LEVEL=debug
# EMAIL_PASSWORD=abc123
CAD_LAMBDA_BASE_URL="http://localhost:8080"
# sentry
GITHUB_ASSIST_APP_ID=23342
GITHUB_ASSIST_SECRET=abc
GITHUB_ASSIST_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nabcdefg\n-----END RSA PRIVATE KEY-----"

View File

@@ -5,6 +5,10 @@
"dependencies": {
"@redwoodjs/api": "^0.34.1",
"@sentry/node": "^6.5.1",
"cloudinary": "^1.23.0"
"cloudinary": "^1.23.0",
"nodemailer": "^6.6.2"
},
"devDependencies": {
"@types/nodemailer": "^6.4.2"
}
}

View File

@@ -1,4 +1,4 @@
import { createUserInsecure } from 'src/services/users/users.js'
import { createUserInsecure } from 'src/services/users/users'
import { db } from 'src/lib/db'
import { sentryWrapper } from 'src/lib/sentry'
import { enforceAlphaNumeric, generateUniqueString } from 'src/services/helpers'

View File

@@ -0,0 +1,22 @@
export const schema = gql`
type Envelope {
from: String
to: [String!]!
}
type EmailResponse {
accepted: [String!]!
rejected: [String!]!
messageId: String!
envelope: Envelope
}
input Email {
subject: String!
body: String!
}
type Mutation {
sendAllUsersEmail(input: Email!): EmailResponse!
}
`

View File

@@ -121,7 +121,8 @@ export const getCurrentUser = async (decoded, { _token, _type }) => {
* requireAuth({ role: ['editor', 'author'] })
* requireAuth({ role: ['publisher'] })
*/
export const requireAuth = ({ role } = {}) => {
export const requireAuth = ({ role }: {role?: string | string[]} = {}) => {
console.log(context.currentUser)
if (!context.currentUser) {
throw new AuthenticationError("You don't have permission to do that.")
}

View File

@@ -0,0 +1,56 @@
import nodemailer, {SendMailOptions} from 'nodemailer'
interface Args {
to: SendMailOptions['to']
from: SendMailOptions['from']
subject: string
text: string
}
interface SuccessResult {
accepted: string[]
rejected: string[]
envelopeTime: number
messageTime: number
messageSize: number
response: string
envelope: {
from: string | false,
to: string[]
},
messageId: string
}
export function sendMail({to, from, subject, text}: Args): Promise<SuccessResult> {
let transporter = nodemailer.createTransport({
host: 'smtp.mailgun.org',
port: 587,
secure: false,
tls: {
ciphers:'SSLv3'
},
auth: {
user: 'postmaster@mail.cadhub.xyz',
pass: process.env.EMAIL_PASSWORD
}
});
console.log({to, from, subject, text});
const emailPromise = new Promise((resolve, reject) => {
transporter.sendMail({
from,
to,
subject,
text,
}, (error, info) => {
if (error) {
reject(error);
} else {
resolve(info);
}
});
}) as any as Promise<SuccessResult>
return emailPromise
}

View File

@@ -0,0 +1,18 @@
import { requireAuth } from 'src/lib/auth'
import {sendMail} from 'src/lib/sendmail'
import {users} from 'src/services/users/users'
export const sendAllUsersEmail = async ({input: {body, subject}}) => {
requireAuth({ role: 'admin' })
const recipients = (await users()).map(({email}) => email)
const from = {
address:'news@mail.cadhub.xyz',
name: 'CadHub',
}
return sendMail({
to: recipients,
from,
subject,
text: body,
})
}

View File

@@ -16,7 +16,8 @@
'SENTRY_DSN',
'SENTRY_AUTH_TOKEN',
'SENTRY_ORG',
'SENTRY_PROJECT'
'SENTRY_PROJECT',
'EMAIL_PASSWORD'
]
# experimentalFastRefresh = true # this seems to break cascadeStudio
[api]

View File

@@ -62,6 +62,7 @@ const Routes = () => {
<Route path="/admin/subject-access-requests/{id}/edit" page={EditSubjectAccessRequestPage} name="editSubjectAccessRequest" />
<Route path="/admin/subject-access-requests/{id}" page={SubjectAccessRequestPage} name="subjectAccessRequest" />
<Route path="/admin/subject-access-requests" page={SubjectAccessRequestsPage} name="subjectAccessRequests" />
<Route path="/admin/email" page={AdminEmailPage} name="adminEmail" />
</Private>
</Router>
)

View File

@@ -0,0 +1,41 @@
import {useState} from 'react'
import { useMutation } from '@redwoodjs/web'
import { toast, Toaster } from '@redwoodjs/web/toast'
const SEND_EMAIL_MUTATION = gql`
mutation sendEmailMutation($email: Email!) {
sendAllUsersEmail(input: $email) {
accepted
}
}
`
const AdminEmailPage = () => {
const [subject, setSubject] = useState('')
const [body, setBody] = useState('')
const [sendEmailMutation] = useMutation(SEND_EMAIL_MUTATION, {
onCompleted: ({sendAllUsersEmail}) => {
toast.success(`Emails sent, ${sendAllUsersEmail?.accepted.join(', ')}`)
setSubject('')
setBody('')
},
})
const sendEmail = () => sendEmailMutation({ variables: { email: { subject, body } } })
return (
<div className="flex justify-center">
<div className="max-w-7xl pt-8">
<h2 className="" style={{width: '46rem'}}>Email all users</h2>
<label htmlFor="subject">Subject</label>
<input name="subject" className="rounded border border-gray-400 px-2 w-full" value={subject} onChange={({target}) => setSubject(target.value)}/>
<label htmlFor="body">Body</label>
<textarea className="w-full rounded border border-gray-400 p-2" name="text" value={body} onChange={({target}) => setBody(target.value)}></textarea>
<button className="rounded px-2 p-1 mt-4 bg-ch-purple-400 text-indigo-200" onClick={sendEmail}>Send</button>
</div>
<Toaster timeout={1500} />
</div>
)
}
export default AdminEmailPage

View File

@@ -3963,6 +3963,13 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b"
integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==
"@types/nodemailer@^6.4.2":
version "6.4.2"
resolved "https://registry.yarnpkg.com/@types/nodemailer/-/nodemailer-6.4.2.tgz#d8ee254c969e6ad83fb9a0a0df3a817406a3fa3b"
integrity sha512-yhsqg5Xbr8aWdwjFS3QjkniW5/tLpWXtOYQcJdo9qE3DolBxsKzgRCQrteaMY0hos8MklJNSEsMqDpZynGzMNg==
dependencies:
"@types/node" "*"
"@types/normalize-package-data@^2.4.0":
version "2.4.0"
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
@@ -13070,6 +13077,11 @@ node-releases@^1.1.29, node-releases@^1.1.61, node-releases@^1.1.71:
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.73.tgz#dd4e81ddd5277ff846b80b52bb40c49edf7a7b20"
integrity sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==
nodemailer@^6.6.2:
version "6.6.2"
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.6.2.tgz#e184c9ed5bee245a3e0bcabc7255866385757114"
integrity sha512-YSzu7TLbI+bsjCis/TZlAXBoM4y93HhlIgo0P5oiA2ua9Z4k+E2Fod//ybIzdJxOlXGRcHIh/WaeCBehvxZb/Q==
normalize-package-data@^2.3.2, normalize-package-data@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"