Skip to content
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
.cache/
public
.env
functions/*
# Local Netlify folder
.netlify
178 changes: 178 additions & 0 deletions functions/airtable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
const Airtable = require('airtable');
const axios = require('axios');

const ERROR_MSGS = {
UNSUPPORTED_METHOD: 'Unsupported method',
UNKNOWN_ERROR: 'Server Error',
};

exports.handler = async event => {
try {
const atClient = _configureAirtable();
const atService = AirtableService(atClient);
switch (event.httpMethod) {
case 'GET':
return await retrieveAttendees(atService, event);
case 'POST':
return await insertAttendee(atService, event);
case 'DELETE':
return await removeAttendee(atService, event);
default:
callback(Error({ message: ERROR_MSGS.UNSUPPORTED_METHOD }), {
statusCode: 405,
body: ERROR_MSGS.UNSUPPORTED_METHOD,
});
}
} catch (e) {
callback(Error(e), {
statusCode: 500,
body: ERROR_MSGS.UNKNOWN_ERROR,
});
}
};

async function retrieveAttendees(Client, event) {
let attendees;
const { eventId, username } = event.queryStringParameters;
if (eventId && username) {
attendees = await Client.getSingleAttendee({ eventId, username });
} else if (eventId) {
attendees = await Client.listAttendees({ eventId });
} else {
throw new Error('Missing parameters');
}
return {
statusCode: 200,
body: JSON.stringify(attendees),
};
}

async function insertAttendee(Client, event) {
if (event.httpMethod !== 'POST') {
return callback(Error({ message: ERROR_MSGS.UNSUPPORTED_METHOD }), {
statusCode: 405,
body: ERROR_MSGS.UNSUPPORTED_METHOD,
});
}
const userDetails = await axios({
method: 'GET',
url: 'https://api.github.com/user',
headers: {
Accept: 'application/vnd.github.v3+json',
Authorization: event.headers.authorization,
},
});
const {
data: { login, name },
} = userDetails;
const { eventId } = JSON.parse(event.body);
const userRecord = await Client.getSingleAttendee({
eventId,
username: login,
});
if (userRecord && userRecord.id) {
return {
statusCode: 409,
body: `You are already signed up!`,
};
}
await Client.insertAttendee({ eventId, name, login });
return {
statusCode: 200,
body: JSON.stringify({ name, eventId }),
};
}

async function removeAttendee(Client, event) {
const { eventId } = JSON.parse(event.body);
if (!eventId) {
throw new Error('Missing Parameters: eventId');
}
const userDetails = await axios({
method: 'GET',
url: 'https://api.github.com/user',
headers: {
Accept: 'application/vnd.github.v3+json',
Authorization: event.headers.authorization,
},
});
const {
data: { login },
} = userDetails;
const userRecord = await Client.getSingleAttendee({
eventId,
username: login,
});
if (!userRecord) {
return {
statusCode: 404,
body: 'User not found',
};
}
const { id } = userRecord;
await Client.removeAttendee({ id });
return { statusCode: 200 };
}

/****** UTILS ******/

function _configureAirtable() {
if (!process.env.AIRTABLE_BASE_ID)
throw new Error('must set process.env.AIRTABLE_BASE_ID');
if (!process.env.AIRTABLE_API_KEY)
throw new Error('must set process.env.AIRTABLE_API_KEY');
Airtable.configure({ apiKey: process.env.AIRTABLE_API_KEY });
return Airtable.base(process.env.AIRTABLE_BASE_ID)('Attendees');
}

function AirtableService(client) {
return {
async getSingleAttendee({ eventId, username }) {
let attendees = [];
await client
.select({
filterByFormula: `AND(
SEARCH("${eventId}",{Event ID}),
SEARCH("${username}",{Github Username})
)`,
})
.eachPage((records, fetchNextPage) => {
records.forEach(function(record) {
attendees.push({ ...record.fields, id: record.id });
});
fetchNextPage();
});
return attendees[0]; // there should only be one
},
async listAttendees({ eventId }) {
let attendees = [];
await client
.select({
filterByFormula: `SEARCH("${eventId}",{Event ID})`,
})
.eachPage((records, fetchNextPage) => {
records.forEach(function(record) {
attendees.push(record.fields);
});
fetchNextPage();
});
return attendees;
},
insertAttendee({ name, login, eventId }) {
return client.create([
{
fields: {
Name: name,
'Github Username': login,
'Event ID': eventId,
Type: 'Attendee',
'Created Date': new Date().toISOString(),
},
},
]);
},
removeAttendee({ id }) {
return client.destroy([id]);
},
};
}
46 changes: 46 additions & 0 deletions functions/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const axios = require('axios');
const qs = require('query-string');

exports.handler = async event => {
try {
return await _retrieveToken(event);
} catch (e) {
let body = `Server Error`;
if (e.message) {
body = `${body} - ${e.message}`;
}
return {
statusCode: 500,
body,
};
}
};

async function _retrieveToken(event) {
if (!process.env.RK_RSVP_CLIENT_ID)
throw new Error('OAuth Client ID is not set');
if (!process.env.RK_RSVP_CLIENT_SECRET)
throw new Error('OAuth Client Secret is not set');
const { code, state } = event.queryStringParameters;
if (!code || !state) {
throw new Error('Missing parameters');
}
const parameters = qs.stringify({
code,
client_id: process.env.RK_RSVP_CLIENT_ID,
client_secret: process.env.RK_RSVP_CLIENT_SECRET,
state,
});
const res = await axios({
method: 'post',
url: `https://github.com/login/oauth/access_token?${parameters}`,
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
});
return {
statusCode: 200,
body: JSON.stringify(res.data),
};
}
6 changes: 6 additions & 0 deletions gatsby-browser.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { AuthProvider } from './src/context/auth';

export const wrapRootElement = ({ element }) => (
<AuthProvider>{element}</AuthProvider>
);

// https://twitter.com/EphemeralCircle/status/1190670453221842944?s=20
// our own @thchia actually also pointed this out as well
Expand Down
12 changes: 0 additions & 12 deletions gatsby-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,17 +129,5 @@ module.exports = {
fetchOptions: {},
},
},
{
resolve: `gatsby-source-airtable`,
options: {
apiKey: process.env.AIRTABLE_API_KEY,
tables: [
{
baseId: process.env.AIRTABLE_BASE_ID,
tableName: 'Attendees',
},
],
},
},
],
};
52 changes: 29 additions & 23 deletions gatsby-node.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,34 @@
const path = require('path');
const { createFilePath } = require('gatsby-source-filesystem');
const axios = require('axios')
const axios = require('axios');

// exports.sourceNodes = async ({ actions, reporter, createContentDigest }) => {
// const { createNode } = actions
// const host = process.env.NODE_ENV === 'production' ? `https://reactknowledgeable.org` : ``
// const data = await axios.get(`${host}/.netlify/functions/airtable`)
// if (data.status >= 200 && data.status < 300) {
// data.data.forEach(datum => createNode({
// ...datum,
// id: `${datum.Name}-${datum["Created Date"]}`,
// parent: null,
// children: [],
// internal: {
// type: "RKAttendee",
// contentDigest: createContentDigest(datum)
// }
// }))
// reporter.success("Retrieved attendee data")
// } else {
// reporter.error("Error encountered retrieving attendees")
// }
// return
// }
exports.sourceNodes = async ({ actions, reporter, createContentDigest }) => {
const { createNode } = actions;
const host = `https://reactknowledgeable.org`;
// const host =
// process.env.NODE_ENV === 'production'
// ? `https://reactknowledgeable.org`
// : ``;
const data = await axios.get(`${host}/.netlify/functions/airtable`);
if (data.status >= 200 && data.status < 300) {
data.data.forEach(datum =>
createNode({
...datum,
id: `${datum.Name}-${datum['Created Date']}`,
parent: null,
children: [],
internal: {
type: 'RKAttendee',
contentDigest: createContentDigest(datum),
},
})
);
reporter.success('Retrieved attendee data');
} else {
reporter.error('Error encountered retrieving attendees');
}
return;
};

exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions;
Expand Down Expand Up @@ -102,7 +108,7 @@ exports.createPages = ({ graphql, actions }) => {
});
});
meetups.forEach(meetup => {
const id = meetup.fields.slug.replace(/[^\d]+/g, "") // to remove everything except the numbers
const id = meetup.fields.slug.replace(/[^\d]+/g, ''); // to remove everything except the numbers
createPage({
path: `${meetup.fields.slug}`,
component: meetupTemplate,
Expand Down
4 changes: 4 additions & 0 deletions netlify.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[build]
publish = "public"
command = "yarn build"
functions = "functions"
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"dependencies": {
"@raae/gatsby-remark-oembed": "^0.1.1",
"@reach/router": "^1.2.1",
"airtable": "^0.8.1",
"axios": "^0.19.0",
"classnames": "^2.2.6",
"gatsby": "^2.17.4",
Expand Down Expand Up @@ -40,8 +41,7 @@
"scripts": {
"develop": "gatsby develop",
"start": "npm run develop",
"build": "yarn run functions && gatsby build",
"functions": "cd src/functions && yarn run build"
"build": "GATSBY_URL=$DEPLOY_PRIME_URL gatsby build"
},
"resolutions": {
"gatsby-plugin-favicon/favicons-webpack-plugin/favicons/sharp": "^0.23.1"
Expand Down
16 changes: 8 additions & 8 deletions prettier.config.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
module.exports = {
singleQuote: true,
trailingComma: 'es5',
bracketSpacing: true,
jsxBracketSameLine: false,
printWidth: 80,
parser: 'babel-flow',
}
singleQuote: true,
trailingComma: 'es5',
bracketSpacing: true,
jsxBracketSameLine: false,
printWidth: 80,
parser: 'babel-flow',
semi: true,
}
9 changes: 3 additions & 6 deletions src/components/Participants/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,13 @@ const getAvatarProps = username => ({

const Participants = ({ rawParticipants }) => {
const participants = new Set();
rawParticipants.map(({ node: { data: { Github_Username: username } } }) => {
rawParticipants.forEach(({ node: { Github_Username: username } }) => {
// dedupe
participants.add(username ? username.toLowerCase() : 'react-knowledgeable');
});
const numberOfSecretParticipants = rawParticipants.filter(
({
node: {
data: { Github_Username: username },
},
}) => !username || username === 'react-knowledgeable'
({ node: { Github_Username: username } }) =>
!username || username === 'react-knowledgeable'
).length;

return participants.size > 0 ? (
Expand Down
Binary file added src/components/RSVP/GitHub-Mark-Light-64px.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading