Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(templates): add Twilio SMS + Notion template #264

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions notion-twilio/assets/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<title>Get started with your Twilio Functions!</title>

<link
rel="icon"
href="https://twilio-labs.github.io/function-templates/static/v1/favicon.ico"
/>
<link
rel="stylesheet"
href="https://twilio-labs.github.io/function-templates/static/v1/ce-paste-theme.css"
/>

<script
src="https://twilio-labs.github.io/function-templates/static/v1/ce-helpers.js"
defer
></script>
<script>
window.addEventListener('DOMContentLoaded', (_event) => {
inputPrependBaseURL();
});
</script>
</head>
<body>
<div class="page-top">
<header>
<div id="twilio-logo">
<a href="https://www.twilio.com/" target="_blank" rel="noopener">
<svg
class="logo"
data-name="Layer 1"
xmlns="http://www.w3.org/2000/svg"
viewbox="0 0 60 60"
>
<title>Twilio Logo</title>
<path
class="cls-1"
d="M30,15A15,15,0,1,0,45,30,15,15,0,0,0,30,15Zm0,26A11,11,0,1,1,41,30,11,11,0,0,1,30,41Zm6.8-14.7a3.1,3.1,0,1,1-3.1-3.1A3.12,3.12,0,0,1,36.8,26.3Zm0,7.4a3.1,3.1,0,1,1-3.1-3.1A3.12,3.12,0,0,1,36.8,33.7Zm-7.4,0a3.1,3.1,0,1,1-3.1-3.1A3.12,3.12,0,0,1,29.4,33.7Zm0-7.4a3.1,3.1,0,1,1-3.1-3.1A3.12,3.12,0,0,1,29.4,26.3Z"
/>
</svg>
</a>
</div>
<nav>
<span>Your Twilio application</span>
<aside>
<svg
class="icon"
role="img"
aria-hidden="true"
width="100%"
height="100%"
viewBox="0 0 20 20"
aria-labelledby="NewIcon-1577"
>
<path
fill="currentColor"
fill-rule="evenodd"
d="M6.991 7.507c.003-.679 1.021-.675 1.019.004-.012 2.956 1.388 4.41 4.492 4.48.673.016.66 1.021-.013 1.019-2.898-.011-4.327 1.446-4.48 4.506-.033.658-1.01.639-1.018-.02-.03-3.027-1.382-4.49-4.481-4.486-.675 0-.682-1.009-.008-1.019 3.02-.042 4.478-1.452 4.49-4.484zm.505 2.757l-.115.242c-.459.9-1.166 1.558-2.115 1.976l.176.08c.973.465 1.664 1.211 2.083 2.22l.02.05.088-.192c.464-.973 1.173-1.685 2.123-2.124l.039-.018-.118-.05c-.963-.435-1.667-1.117-2.113-2.034l-.068-.15zm10.357-8.12c.174.17.194.434.058.625l-.058.068-1.954 1.905 1.954 1.908a.482.482 0 010 .694.512.512 0 01-.641.056l-.07-.056-1.954-1.908-1.954 1.908a.511.511 0 01-.71 0 .482.482 0 01-.058-.626l.058-.068 1.954-1.908-1.954-1.905a.482.482 0 010-.693.512.512 0 01.64-.057l.07.057 1.954 1.905 1.954-1.905a.511.511 0 01.71 0z"
></path>
</svg>
Live
</aside>
</nav>
</header>
</div>
<main>
<div class="content">
<h1>
<img
src="https://twilio-labs.github.io/function-templates/static/v1/success.svg"
/>
<div>
<p>Welcome!</p>
<p>Your live application with Twilio is ready to use!</p>
</div>
</h1>
<section>
<h2>Get started with your application</h2>
<p>Follow these steps to try out your new app:</p>
<p>
This app is a Twilio Messaging template Function that will populate
a Notion Database. Learn more in the
<a href="https://www.twilio.com/blog/fathers-day-notion-sms">
blog post.
</a>
</p>
</section>
<section>
<!-- APP_INFO_V2 -->
</section>
<section>
<h2>Troubleshooting</h2>
<ul>
<li>
Check the
<a
href="https://www.twilio.com/console/phone-numbers/incoming"
target="_blank"
rel="noopener"
>
phone number configuration
</a>
and make sure the Twilio phone number you want for your app has a
SMS webhook configured to point at the following URL
<form>
<label for="twilio-webhook">Webhook URL</label>
<input
type="text"
id="twilio-webhook"
class="function-root"
readonly="true"
value="/hello-messaging"
/>
</form>
</li>
</ul>
</section>
</div>
</main>
<footer>
<span>We can't wait to see what you build.</span>
</footer>
</body>
</html>
73 changes: 73 additions & 0 deletions notion-twilio/functions/write-to-db.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
const superagent = require('superagent');

function parseBodyToColumns(event, maxColumns) {
try {
return event.Body.trim().split(',').slice(0, maxColumns);
} catch (error) {
throw Error('No message provided. Please include a message.');
}
}

async function sendNotionApiRequest(properties, context) {
try {
superagent
.post(`https://api.notion.com/v1/pages`, {
properties,
parent: {
// eslint-disable-next-line camelcase
database_id: `${context.DATABASE_ID}`,
},
})
.set('Authorization', `Bearer ${context.NOTION_API_KEY}`)
.set('Content-Type', 'application/json')
.set('Notion-Version', '2021-05-13');
} catch (error) {
throw Error(`Error calling the Notion API: ${error}`);
}
}

function zipColumns(columnNames, columns) {
const properties = {};

columns.forEach((columnText, idx) => {
const columnName = columnNames[idx];

properties[columnName] = [
{
text: {
content: columnText,
},
},
];
});

return properties;
}

async function handler(context, event, callback) {
const twiml = new Twilio.twiml.MessagingResponse();

try {
const columnNames = ['Name', 'Where', 'Price'];
const columns = parseBodyToColumns(event, columnNames.length);
const properties = zipColumns(columnNames, columns);

const resp = await sendNotionApiRequest(properties, context);
const columnsSent = Object.keys(properties);
twiml.message(
`Wrote ${columnsSent.length} columns: ${columnsSent.join(
', '
)} to the Notion page!`
);
return callback(null, twiml);
} catch (error) {
twiml.message(`Error: ${error.message}`);
return callback(null, twiml);
}
}

module.exports = {
parseBodyToColumns,
zipColumns,
handler,
};
6 changes: 6 additions & 0 deletions notion-twilio/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"dependencies": {
"superagent": "^6.1.0",
"twilio": "^3.56"
}
}
151 changes: 151 additions & 0 deletions notion-twilio/tests/write-to-db.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
const helpers = require('../../test/test-helper');
const {
parseBodyToColumns,
handler,
zipColumns,
} = require('../functions/write-to-db');
const Twilio = require('twilio');

const textContext = {};

describe('notion-twilio/write-to-db', () => {
beforeAll(() => {
helpers.setup({});
});
afterAll(() => {
helpers.teardown();
});

test('parses the string with 2 elements', (done) => {
const event = { Body: 'hi,hello' };
const columns = parseBodyToColumns(event, 3);
expect(columns).toEqual(['hi', 'hello']);
done();
});

test('parses the string with 3 elements', (done) => {
const event = { Body: 'hi,hello,hey' };
const columns = parseBodyToColumns(event, 3);
expect(columns).toEqual(['hi', 'hello', 'hey']);
done();
});

test('truncates the columns whent there are 4 elements', (done) => {
const event = { Body: 'hi,hello,hey,ahoy' };
const columns = parseBodyToColumns(event, 3);
expect(columns).toEqual(['hi', 'hello', 'hey']);
done();
});

test('combines the column headers and columns as expected with 3 elements', (done) => {
const columnNames = ['Name', 'Where', 'Price'];
const columns = ['hi', 'hello', 'hey'];
const expected = {
Name: [
{
text: {
content: `hi`,
},
},
],
Where: [
{
text: {
content: `hello`,
},
},
],
Price: [
{
text: {
content: `hey`,
},
},
],
};

const actual = zipColumns(columnNames, columns);
expect(actual).toEqual(expected);
done();
});

test('combines the column headers and columns as expected with 2 elements', (done) => {
const columnNames = ['Name', 'Where', 'Price'];
const columns = ['hi', 'hello'];
const expected = {
Name: [
{
text: {
content: `hi`,
},
},
],
Where: [
{
text: {
content: `hello`,
},
},
],
};

const actual = zipColumns(columnNames, columns);
expect(actual).toEqual(expected);
done();
});

test('combines the column headers and columns as expected with 1 element', (done) => {
const columnNames = ['Name', 'Where', 'Price'];
const columns = ['hi'];
const expected = {
Name: [
{
text: {
content: `hi`,
},
},
],
};

const actual = zipColumns(columnNames, columns);
expect(actual).toEqual(expected);
done();
});

test('returns an error if there is no inbound message', (done) => {
const callback = (_err, result) => {
expect(result).toBeInstanceOf(Twilio.twiml.MessagingResponse);
expect(result.toString()).toMatch(
'<Message>Error: No message provided. Please include a message.</Message>'
);
done();
};

const event = {};
handler(textContext, event, callback);
});

test('returns the expected message', (done) => {
const callback = (_err, result) => {
expect(result.toString()).toMatch(
'<Message>Wrote 3 columns: Name, Where, Price to the Notion page!</Message>'
);
done();
};

const event = { Body: 'hi,hello,hey' };
handler(textContext, event, callback);
});

test('ignores any columns after 3', (done) => {
const callback = (_err, result) => {
expect(result.toString()).toMatch(
'<Message>Wrote 3 columns: Name, Where, Price to the Notion page!</Message>'
);
done();
};

const event = { Body: 'hi,hello,hey,ahoy,salutations' };
handler(textContext, event, callback);
});
});
Loading