email sending via sendgrid

This commit is contained in:
Matthias Kretschmann 2019-07-05 15:30:19 +02:00
parent acb7ae4d35
commit cee49978c4
Signed by: m
GPG Key ID: 606EEEF3C479A91F
7 changed files with 260 additions and 23 deletions

View File

@ -1,22 +1,66 @@
import React, { PureComponent } from 'react'
import React, { PureComponent, ChangeEvent } from 'react'
import axios from 'axios'
import { Logger } from '@oceanprotocol/squid'
import Modal from '../../atoms/Modal'
import styles from './Report.module.scss'
import Button from '../../atoms/Button'
import Input from '../../atoms/Form/Input'
import Form from '../../atoms/Form/Form'
import { serviceUri } from '../../../config'
export default class Report extends PureComponent<
{ did: string; title: string },
{ isModalOpen: boolean }
{ isModalOpen: boolean; comment: string; error?: string }
> {
public state = {
isModalOpen: false
isModalOpen: false,
comment: ''
}
public toggleModal = () => {
// for canceling axios requests
public signal = axios.CancelToken.source()
public componentWillUnmount() {
this.signal.cancel()
}
private inputChange = (event: ChangeEvent<HTMLInputElement>) => {
this.setState({
comment: event.target.value
})
}
private toggleModal = () => {
this.setState({ isModalOpen: !this.state.isModalOpen })
}
private sendEmail = async (event: Event) => {
event.preventDefault()
const msg = {
to: 'test@example.com',
from: 'test@example.com',
subject: `[Report] ${this.props.title}`,
html: `<p>The following data set was reported:</p><p><strong>${this.props.title}</strong><br /><a href="https://commons.oceanprotocol.com/asset/${this.props.did}"><code>${this.props.did}</code></a></p><blockquote><p>${this.state.comment}</p></blockquote>`
}
try {
const response = await axios({
method: 'POST',
headers: { 'Content-Type': 'application/json' },
url: `${serviceUri}/api/v1/report`,
data: { msg },
cancelToken: this.signal.token
})
return response.data.result
} catch (error) {
!axios.isCancel(error) &&
this.setState({ error: error.message }) &&
Logger.error(error.message)
}
}
public render() {
return (
<div className={styles.actions}>
@ -46,8 +90,17 @@ export default class Report extends PureComponent<
label="Comment"
help="Briefly describe what is wrong with this asset."
required
value={this.state.comment}
onChange={this.inputChange}
rows={2}
/>
<Button primary>Report Data Set</Button>
<Button
primary
onClick={(e: Event) => this.sendEmail(e)}
disabled={this.state.comment === ''}
>
Report Data Set
</Button>
</Form>
</div>
</Modal>

118
server/README.md Normal file
View File

@ -0,0 +1,118 @@
[![banner](https://raw.githubusercontent.com/oceanprotocol/art/master/github/repo-banner%402x.png)](https://oceanprotocol.com)
<h1 align="center">Commons: Server</h1>
This folder contains server component written in TypeScript using [Express](https://expressjs.com). The server provides various microservices.
- [Get Started](#Get-Started)
- [✨ API Documentation](#-API-Documentation)
- [Url Checker](#Url-Checker)
- [Report](#Report)
- [🎁 Contributing](#-Contributing)
- [🏛 License](#-License)
## Get Started
To spin up the server in a watch mode for local development, execute:
```bash
npm install
npm start
```
## ✨ API Documentation
### Url Checker
Url Checker returns size and additional information about requested file. This service is used as a solution to frontend CORS restrictions.
**Endpoint:** POST `/api/v1/urlcheck`
**Request Parameters**
```json
{
"url": "https://oceanprotocol.com/tech-whitepaper.pdf"
}
```
**Response: Success**
```json
{
"status": "success",
"result": {
"found": true,
"contentLength": "2989228",
"contentType": "application/pdf"
}
}
```
**Response: Error**
```json
{
"status": "error",
"message": null
}
```
### Report
Report endpoints sends an email via SendGrid. Requires `SENDGRID_API_KEY` set as environment variable.
**Endpoint:** POST `/api/v1/report`
**Request Parameters**
```json
{
"msg": {
"to": "test@example.com",
"from": "test@example.com",
"subject": "My Subject",
"text": "Text",
"html": "<strong>HTML</strong>"
}
}
```
**Response: Success**
```json
{
"status": "success"
}
```
**Response: Error**
```json
{
"status": "error",
"message": "Error message"
}
```
## 🎁 Contributing
See the page titled "[Ways to Contribute](https://docs.oceanprotocol.com/concepts/contributing/)" in the Ocean Protocol documentation.
## 🏛 License
```text
Copyright 2019 Ocean Protocol Foundation Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
```

View File

@ -351,6 +351,34 @@
"@types/yargs": "^12.0.9"
}
},
"@sendgrid/client": {
"version": "6.4.0",
"resolved": "https://registry.npmjs.org/@sendgrid/client/-/client-6.4.0.tgz",
"integrity": "sha512-GcO+hKXMQiwN0xMGfPITArlj4Nab1vZsrsRLmsJlcXGZV1V1zQC6XuAWJv6MGDd0hr/jKaXmCJ1XMYkxIRQHFw==",
"requires": {
"@sendgrid/helpers": "^6.4.0",
"@types/request": "^2.0.3",
"request": "^2.88.0"
}
},
"@sendgrid/helpers": {
"version": "6.4.0",
"resolved": "https://registry.npmjs.org/@sendgrid/helpers/-/helpers-6.4.0.tgz",
"integrity": "sha512-1dDDXauArHyxwTKFFfWvQpsijmwalyLgwoQJ3FRCssFq1RfqYDgFhRg0Xs3v/IXS2jkKWePSWiPORSR4Sysdpw==",
"requires": {
"chalk": "^2.0.1",
"deepmerge": "^2.1.1"
}
},
"@sendgrid/mail": {
"version": "6.4.0",
"resolved": "https://registry.npmjs.org/@sendgrid/mail/-/mail-6.4.0.tgz",
"integrity": "sha512-pVzbqbxhZ4FUN6iSIksRLtyXRPurrcee1i0noPDStDCLlHVwUR+TofeeKIFWGpIvbbk5UR6S6iV/U5ie8Kdblw==",
"requires": {
"@sendgrid/client": "^6.4.0",
"@sendgrid/helpers": "^6.4.0"
}
},
"@types/babel__core": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.2.tgz",
@ -405,8 +433,7 @@
"@types/caseless": {
"version": "0.12.2",
"resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz",
"integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==",
"dev": true
"integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w=="
},
"@types/compression": {
"version": "0.0.36",
@ -463,7 +490,6 @@
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-2.2.1.tgz",
"integrity": "sha512-JAMFhOaHIciYVh8fb5/83nmuO/AHwmto+Hq7a9y8FzLDcC1KCU344XDOMEmahnrTFlHjgh4L0WJFczNIX2GxnQ==",
"dev": true,
"requires": {
"@types/node": "*"
}
@ -526,8 +552,7 @@
"@types/node": {
"version": "11.13.15",
"resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.15.tgz",
"integrity": "sha512-x6ypl5Uzly+j23hbxmMzf12Eb4lOhIEqQz0HuczpTUa1KIx1GpbN/o4E3aAED20UoEsdK0wvyY8QcffuWSLDkw==",
"dev": true
"integrity": "sha512-x6ypl5Uzly+j23hbxmMzf12Eb4lOhIEqQz0HuczpTUa1KIx1GpbN/o4E3aAED20UoEsdK0wvyY8QcffuWSLDkw=="
},
"@types/range-parser": {
"version": "1.2.3",
@ -539,7 +564,6 @@
"version": "2.48.1",
"resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.1.tgz",
"integrity": "sha512-ZgEZ1TiD+KGA9LiAAPPJL68Id2UWfeSO62ijSXZjFJArVV+2pKcsVHmrcu+1oiE3q6eDGiFiSolRc4JHoerBBg==",
"dev": true,
"requires": {
"@types/caseless": "*",
"@types/form-data": "*",
@ -585,8 +609,7 @@
"@types/tough-cookie": {
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.5.tgz",
"integrity": "sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg==",
"dev": true
"integrity": "sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg=="
},
"@types/yargs": {
"version": "12.0.12",
@ -681,7 +704,6 @@
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "^1.9.0"
}
@ -1117,7 +1139,6 @@
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
@ -1241,7 +1262,6 @@
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"requires": {
"color-name": "1.1.3"
}
@ -1249,8 +1269,7 @@
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
"dev": true
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
},
"combined-stream": {
"version": "1.0.8",
@ -1504,6 +1523,11 @@
"integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
"dev": true
},
"deepmerge": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz",
"integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA=="
},
"define-properties": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
@ -1681,8 +1705,7 @@
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
"dev": true
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
},
"escodegen": {
"version": "1.11.1",
@ -2800,8 +2823,7 @@
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
"dev": true
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
},
"has-symbols": {
"version": "1.0.0",
@ -5537,7 +5559,6 @@
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}

View File

@ -13,6 +13,7 @@
"coverage": "cat coverage/lcov.info | codacy-coverage --token 8801f827fe1144ffa85cd7da94f2bbf7"
},
"dependencies": {
"@sendgrid/mail": "^6.4.0",
"body-parser": "^1.18.3",
"compression": "^1.7.4",
"debug": "^4.1.1",

View File

@ -0,0 +1,34 @@
import { Router, Request, Response } from 'express'
import SendgridMail from '@sendgrid/mail'
SendgridMail.setApiKey(process.env.SENDGRID_API_KEY)
export class ReportRouter {
public router: Router
public constructor() {
this.router = Router()
}
public async sendEmail(req: Request, res: Response) {
if (!req.body.msg) {
return res.send({ status: 'error', message: 'missing message' })
}
try {
await SendgridMail.send(req.body.msg)
return res.send({ status: 'success' })
} catch (error) {
res.send(`${error.code} - ${error.message}`)
}
}
public init() {
this.router.post('/', this.sendEmail)
}
}
const reportRoutes = new ReportRouter()
reportRoutes.init()
export default reportRoutes.router

View File

@ -7,6 +7,7 @@ import pkg from '../../package.json'
// routes
import UrlCheckRouter from './routes/UrlCheckRouter'
import ReportRouter from './routes/ReportRouter'
// config
import config from './config/config'
@ -62,6 +63,7 @@ app.get('/', (req, res) => {
)
})
app.use('/api/v1/urlcheck', UrlCheckRouter)
app.use('/api/v1/report', ReportRouter)
/// catch 404
app.use((req, res) => {

View File

@ -25,6 +25,14 @@ describe('POST /api/v1/urlcheck', () => {
})
})
describe('POST /api/v1/report', () => {
it('responds with error message when message is missing', async () => {
const response = await request(server).post('/api/v1/report')
const text = await JSON.parse(response.text)
expect(text.message).toBe('missing message')
})
})
describe('Errors', () => {
it('responds with 404 on unknown path', async () => {
const response = await request(server).post('/whatever')