├── .gitignore ├── README.md ├── _site-config.js ├── content ├── 01-01-2017 │ └── index.md ├── 01-02-2017 │ └── index.md ├── 01-03-2017 │ └── index.md ├── functions │ └── wow │ │ └── index.md ├── tester.md ├── typeography.md └── what.md ├── data ├── examples.json └── tutorials.json ├── functions ├── add-example.js ├── add-tutorial.js ├── package.json └── utils │ ├── createPullRequest.js │ └── sanitize.js ├── gatsby-browser.js ├── gatsby-config.js ├── gatsby-node.js ├── netlify.toml ├── package-lock.json ├── package.json ├── plugins ├── gatsby-better-postcss │ ├── gatsby-node.js │ ├── index.js │ └── package.json └── gatsby-route-plugin │ ├── gatsby-browser.js │ ├── index.js │ └── package.json ├── postcss.config.js ├── src ├── _variables.js ├── analytics.js ├── components │ ├── Button │ │ ├── Button.css │ │ └── index.js │ ├── Card │ │ ├── Card.css │ │ └── index.js │ ├── Copy │ │ ├── Copy.css │ │ └── index.js │ ├── Disqus │ │ └── Disqus.js │ ├── FieldSet │ │ ├── Fieldset.css │ │ └── index.js │ ├── Form │ │ ├── AutoForm.js │ │ └── index.js │ ├── GithubCorner │ │ ├── GithubCorner.css │ │ └── index.js │ ├── Icon │ │ ├── Icon.css │ │ ├── index.js │ │ └── utils │ │ │ └── addSvgToDom.js │ ├── Input │ │ ├── Input.css │ │ ├── index.js │ │ └── utils │ │ │ └── validation.js │ ├── Logo │ │ ├── Logo.css │ │ └── index.js │ ├── Modal │ │ ├── Modal.css │ │ └── index.js │ ├── PostListing │ │ └── PostListing.js │ ├── PostTags │ │ └── PostTags.js │ ├── SEO │ │ └── SEO.js │ ├── SearchBox │ │ ├── SearchBox.css │ │ └── index.js │ └── TextArea │ │ ├── TextArea.css │ │ └── index.js ├── favicon.png ├── fragments │ ├── .gitkeep │ ├── Grid │ │ ├── Grid.css │ │ └── index.js │ └── Sidebar │ │ ├── Sidebar.css │ │ └── index.js ├── icons │ ├── github.svg │ ├── settings.svg │ ├── sprite.js │ └── sprite.svg ├── layouts │ ├── Base │ │ ├── Base.css │ │ └── index.js │ ├── Default │ │ ├── Default.css │ │ └── index.js │ ├── index.css │ └── index.global.css ├── pages │ ├── 404.js │ ├── Home.css │ ├── about.js │ ├── add-example │ │ ├── Add.css │ │ └── index.js │ ├── add-tutorial │ │ ├── Add.css │ │ └── index.js │ ├── admin │ │ ├── Admin.css │ │ ├── examples │ │ │ └── index.js │ │ └── tutorials │ │ │ └── index.js │ ├── directory.js │ ├── examples │ │ ├── Examples.css │ │ └── index.js │ ├── index.js │ └── tutorials │ │ ├── Tutorials.css │ │ └── index.js ├── templates │ ├── Category.js │ ├── Post.css │ ├── Post.js │ └── Tag.js └── utils │ └── data.js └── static ├── favicon.ico ├── logos ├── logo-1024.png └── logo-48.png └── robots.txt /.gitignore: -------------------------------------------------------------------------------- 1 | ### Node ### 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | *.pid.lock 12 | .DS_Store 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # node-waf configuration 27 | .lock-wscript 28 | 29 | # Compiled binary addons (http://nodejs.org/api/addons.html) 30 | build/Release 31 | 32 | # Dependency directories 33 | node_modules 34 | jspm_packages 35 | 36 | # Optional npm cache directory 37 | .npm 38 | 39 | # Optional eslint cache 40 | .eslintcache 41 | 42 | # Optional REPL history 43 | .node_repl_history 44 | 45 | # Output of 'npm pack' 46 | *.tgz 47 | 48 | # Yarn Integrity file 49 | .yarn-integrity 50 | 51 | 52 | # Build Files 53 | misc 54 | public/ 55 | .cache/ 56 | 57 | # Gatsby context 58 | .gatsby-context.js 59 | 60 | # Bundle stats 61 | bundle-stats.json 62 | 63 | # Netlify files 64 | functions-build 65 | 66 | # Local Netlify folder 67 | .netlify -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
146 | 147 | 148 | ## Youtube 149 | 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /content/what.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "what" 3 | cover: "https://unsplash.it/1152/300/?random?BirchintheRoses" 4 | date: "01/03/2017" 5 | category: "tech" 6 | tags: 7 | - tag 8 | --- 9 | 10 | # What page 11 | 12 | ⊂◉‿◉つ 13 | -------------------------------------------------------------------------------- /data/tutorials.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "Netlify Lambda Functions tutorial", 4 | "description": "How to use Netlify Lambda Functions and add dynamic processing to JAMstack sites", 5 | "url": "https://flaviocopes.com/netlify-functions/", 6 | "date": "Dec 28, 2018" 7 | }, 8 | { 9 | "title": "Write and Deploy Your First Serverless Function in 10 Minutes, or Less", 10 | "description": "I heard about Netlify supports lambda functions, and decided to give it a try. Surprisingly, the process was super simple, and my serverless function was up and running within minutes!", 11 | "url": "https://codeburst.io/write-and-deploy-your-first-serverless-function-within-10-minutes-or-less-d7552fcd6550", 12 | "date": "Jan 2, 2019" 13 | }, 14 | { 15 | "title": "Building Serverless CRUD apps with Netlify Functions & FaunaDB", 16 | "description": "This tutorial demonstrates how to build a CRUD backend using Netlify serverless functions and FaunaDB as the datastore.", 17 | "url": "https://www.netlify.com/blog/2018/07/09/building-serverless-crud-apps-with-netlify-functions--faunadb/", 18 | "date": "July 7, 2018" 19 | }, 20 | { 21 | "title": "Build and Deploy a Serverless Function to Netlify", 22 | "description": "Recently, Netlify has become one of the most popular hosts in Web Development. Netlify offers an incredible variety of features like serverless (lambda) functions", 23 | "url": "https://scotch.io/tutorials/build-and-deploy-a-serverless-function-to-netlify", 24 | "date": "October 31, 2018" 25 | }, 26 | { 27 | "title": "Building a Lambda Function with Netlify", 28 | "description": "Process payments via Stripe + serverless functions", 29 | "url": "https://macarthur.me/posts/building-a-lambda-function-with-netlify", 30 | "date": "February 12, 2018" 31 | }, 32 | { 33 | "title": "Forms, Auth and Serverless Functions on Gatsby and Netlify", 34 | "description": "Authenicated functions and Gatsby", 35 | "url": "https://css-tricks.com/forms-auth-and-serverless-functions-on-gatsby-and-netlify/", 36 | "date": "May 31, 2018" 37 | }, 38 | { 39 | "title": "Netlify Lambdas - As simple as possible", 40 | "description": "A minimal approach to successfully deploy an AWS Lambda on Netlify", 41 | "url": "https://luetkemj.github.io/180505/netlify-lambdas-as-simple-as-possible", 42 | "date": "May 5, 2018" 43 | }, 44 | { 45 | "title": "How to submit forms on a static website with VueJS and Netlify functions", 46 | "description": "How you could submit forms, like a contact form, on that serverless architecture", 47 | "url": "https://medium.com/@marcmintel/how-to-submit-forms-on-a-static-website-with-vuejs-and-netlify-functions-b901f40f0627", 48 | "date": "Sep 12, 2018" 49 | }, 50 | { 51 | "title": "Building a Serverless Comment System with Netlify Functions, Storyblok and Vue.js", 52 | "description": "Build a Serverless comment system powered by Netlify Functions and we’ll use the headless CMS Storyblok as a database", 53 | "url": "https://markus.oberlehner.net/blog/building-a-serverless-comment-system-with-netlify-functions-storyblok-and-vue/", 54 | "date": "August 5, 2018" 55 | }, 56 | { 57 | "title": "Deploy a fullstack Apollo app with Netlify", 58 | "description": "How to run your API using Netlify Functions on AWS Lambda", 59 | "url": "https://blog.apollographql.com/deploy-a-fullstack-apollo-app-with-netlify-45a7dfd51b0b", 60 | "date": "Sep 13, 2018" 61 | }, 62 | { 63 | "title": "Telegram Bot on Netlify Functions", 64 | "description": "Building a simple telegram echo bot built using golang and Netlify Functions", 65 | "url": "https://zede.solutions/blog/telegram-bot-on-netlify-functions/", 66 | "date": "April 7, 2018" 67 | }, 68 | { 69 | "title": "Get Data from MongoDB with Netlify Functions Lambda", 70 | "description": "Learn how to query a database instance and return data without a backend server", 71 | "url": "https://www.edwardbeazer.com/get-data-from-mongodb-with-netlify-functions-lambda/", 72 | "date": "July 31, 2018" 73 | }, 74 | { 75 | "title": "Express.js on Netlify", 76 | "description": "How to run express via Netlify functions", 77 | "url": "https://blog.neverendingqs.com/2018/09/08/expressjs-on-netlify.html", 78 | "date": "Sep 8, 2018" 79 | }, 80 | { 81 | "title": "Sending WebSub notifications from static sites using Netlify functions", 82 | "description": "How to send notifications through protocols like WebSub with serverless functions", 83 | "url": "https://matienzo.org/2018/websub-static/", 84 | "date": "February 13, 2018" 85 | }, 86 | { 87 | "title": "Making the static dynamic: Instagram Importer", 88 | "description": "Automatically rebuild static sites when a new instagram post is found", 89 | "url": "https://www.trysmudford.com/blog/making-the-static-dynamic-instagram-importer/", 90 | "date": "February 12, 2018" 91 | }, 92 | { 93 | "title": "Dynamic Static Sites with Netlify Functions & iOS Shortcuts", 94 | "description": "Share data from iOS Shortcuts to my Netlify site", 95 | "url": "https://bryanlrobinson.com/blog/2018/11/12/ios-shortcuts-pushing-data-to-netlify-static-site/", 96 | "date": "Nov 12, 2018" 97 | }, 98 | { 99 | "title": "Syndicating Content with Netlify functions", 100 | "description": "How to automatically publish content from a static site on Twitter, using Eleventy and Netlify's lambda functions", 101 | "url": "https://mxb.at/blog/syndicating-content-to-twitter-with-netlify-functions/", 102 | "date": "Jan 06, 2019" 103 | }, 104 | { 105 | "title": "Adding Emotional Tone Analysis to Your Contact Form", 106 | "description": "How to use IBM Watson Tone Analyzer & Netlify functions for sentiment analysis on forms", 107 | "url": "https://www.raymondcamden.com/2019/01/18/adding-emotional-tone-analysis-to-your-contact-form", 108 | "date": "Jan 18, 2019" 109 | }, 110 | { 111 | "title": "Building a Serverless JAMStack app with FaunaDB Cloud: Part 1", 112 | "description": "How to use FaunaDB & Netlify functions & identity for CRUD applications", 113 | "url": "https://fauna.com/blog/building-a-serverless-jamstack-app-with-faunadb-cloud-part-1", 114 | "date": "Jan 23, 2019" 115 | }, 116 | { 117 | "title": "Debugging Netlify Function Errors with Sentry", 118 | "description": "How to set up, debug and monitor Netlify Functions with Sentry.io", 119 | "url": "https://httptoolkit.tech/blog/netlify-function-error-reporting-with-sentry/", 120 | "date": "Jan 23, 2019" 121 | }, 122 | { 123 | "title": "How to send text messages from your static site using Netlify, Twilio and serverless functions", 124 | "description": "In this article, I want to share how you can create and deploy a scalable static site on Netlify and how you can use serverless functions to send text messages using Twilio.", 125 | "url": "https://dev.to/twilio/how-to-send-text-messages-from-your-static-site-using-netlify-twilio-and-serverless-functions-24ci/", 126 | "date": "April 01, 2019" 127 | }, 128 | { 129 | "title": "Circumventing CORS with Netlify Functions & Nodejs", 130 | "description": "I needed to consume a third-party API that hadn’t been properly configured with the appropriate CORS headers. Rather than track down the API author, I decided to take this as an opportunity to experiment with a serverless pattern", 131 | "url": "https://medium.com/@kamry.bowman/circumventing-cors-with-netlify-functions-nodejs-65aa6ec69a65", 132 | "date": "April 19, 2019" 133 | }, 134 | { 135 | "title": "CSSTricks: Netlify Functions for Sending Emails", 136 | "description": "Let's say you're rocking a JAMstack-style site (no server-side languages in use), but you want to do something rather dynamic like send an email. Not a problem! That's the whole point of JAMstack.", 137 | "url": "https://css-tricks.com/netlify-functions-for-sending-emails/", 138 | "date": "April 23, 2019" 139 | }, 140 | { 141 | "title": "Handling Static Forms, Auth & Serverless Functions with Gatsby on Netlify", 142 | "description": "Supercharge your static site with forms, password-protected authentication, and AWS Lambda functions.", 143 | "url": "https://snipcart.com/blog/static-forms-serverless-gatsby-netlify", 144 | "date": "April 25, 2019" 145 | }, 146 | { 147 | "title": "How to use Netlify Functions in Elm", 148 | "description": "This worked example creates a simple Netlify Function and integrates it with an Elm application.", 149 | "url": "https://www.freecodecamp.org/news/how-to-use-netlify-functions-in-elm/", 150 | "date": "August 28, 2019" 151 | }, 152 | { 153 | "title": "Netlify Dev + Serverless Functions + MailChimp Subscribe Form Tutorial", 154 | "description": "Create a working serverless function hosted on Netlify, automatically built and deployed every time you push to git, that you can use on your static site to add subscriber emails directly to MailChimp", 155 | "url": "https://medium.com/@mattdgregg/netlify-dev-serverless-functions-mailchimp-subscribe-form-tutorial-28ffaa51ba99", 156 | "date": "September 10, 2019" 157 | }, 158 | { 159 | "title": "Serverless Mailing Lists - Unlimited Email Sign-Ups for Free", 160 | "description": "Create a simple serverless function to collect email from your users for free without external tools", 161 | "url": "https://medium.com/better-programming/unlimited-email-signups-for-free-for-indie-hackers-403c20261880", 162 | "date": "November 6, 2019" 163 | }, 164 | { 165 | "title": "Super simple start to serverless", 166 | "description": "Easily create server code without worrying about managing servers yourself", 167 | "url": "https://kentcdodds.com/blog/super-simple-start-to-serverless", 168 | "date": "December 7, 2019" 169 | }, 170 | { 171 | "title": "Submitting sitemaps on deploy with Netlify", 172 | "url": "https://atymic.dev/tips/netlify-submit-sitemaps/", 173 | "date": "2020-01-14", 174 | "description": "There's plenty of static site generators out there, and most support generating sitemaps. To make sure google indexes your pages as quickly as possible, it's a good idea to ping them with your sitemap when it's updated.\n\nNetlify provides some hooks which can be used to run functions once the deploy is complete. This allows us to ping google when production is deployed." 175 | } 176 | ] -------------------------------------------------------------------------------- /functions/add-example.js: -------------------------------------------------------------------------------- 1 | const url = require('url') 2 | const gitUrlParse = require('git-url-parse') 3 | const createPullRequest = require('./utils/createPullRequest') 4 | const sanitize = require('./utils/sanitize') 5 | const Octokit = require('@octokit/rest').plugin(createPullRequest) 6 | 7 | const { REPOSITORY_URL } = process.env 8 | 9 | const octokit = new Octokit() 10 | octokit.authenticate({ 11 | type: 'oauth', 12 | token: process.env.GITHUB_TOKEN 13 | }) 14 | 15 | const FILE_TO_CHANGE = 'data/examples.json' 16 | 17 | /* export our lambda function as named "handler" export */ 18 | exports.handler = async (event, context) => { 19 | const parsed = gitUrlParse('https://github.com/netlify-labs/functions-site') 20 | const repo = parsed.name 21 | const owner = parsed.owner 22 | const body = JSON.parse(event.body) 23 | 24 | if (!repo || !owner) { 25 | return { 26 | statusCode: 401, 27 | body: JSON.stringify({ 28 | data: 'process.env.REPOSITORY_URL malformed' 29 | }) 30 | } 31 | } 32 | 33 | if (!body || !body.name) { 34 | return { 35 | statusCode: 401, 36 | body: JSON.stringify({ 37 | data: 'Request malformed' 38 | }) 39 | } 40 | } 41 | 42 | const cleanName = sanitize(body.name) 43 | console.log('cleanName', cleanName) 44 | if (!cleanName) { 45 | return { 46 | statusCode: 401, 47 | body: JSON.stringify({ 48 | data: 'Request malformed. Bad data has been passed in' 49 | }) 50 | } 51 | } 52 | 53 | // Get repo file contents 54 | let result 55 | try { 56 | result = await octokit.repos.getContents({ 57 | owner, 58 | repo, 59 | path: FILE_TO_CHANGE 60 | }) 61 | } catch (err) { 62 | console.log('octokit.repos.getContents err', err) 63 | return { 64 | statusCode: 400, 65 | body: JSON.stringify({ 66 | error: `${err.message}` 67 | }) 68 | } 69 | } 70 | 71 | if (typeof result.data === 'undefined') { 72 | // createFile(octokit, config, file, content) 73 | // throw file doesnt exist 74 | return { 75 | statusCode: 400, 76 | body: JSON.stringify({ 77 | error: `No ${FILE_TO_CHANGE} found` 78 | }) 79 | } 80 | } 81 | 82 | // content will be base64 encoded 83 | const content = Buffer.from(result.data.content, 'base64').toString() 84 | 85 | const allData = parseFile(FILE_TO_CHANGE, content) 86 | console.log('allData.length', allData.length) 87 | 88 | if (alreadyHasUri(body, allData)) { 89 | console.log(`${body.url} already is in the list!`) 90 | return { 91 | statusCode: 422, 92 | body: JSON.stringify({ 93 | message: `${body.url} already is in the list!` 94 | }) 95 | } 96 | } 97 | 98 | const newData = allData.concat(body) 99 | const newContent = JSON.stringify(newData, null, 2) 100 | 101 | let response = {} 102 | try { 103 | response = await octokit.createPullRequest({ 104 | owner, 105 | repo, 106 | title: `Add example ${body.url}`, 107 | body: `Add ${body.name} at ${body.url}`, 108 | base: 'master', /* optional: defaults to default branch */ 109 | head: `pull-request-branch-name-${new Date().getTime()}`, 110 | changes: { 111 | files: { 112 | [`${FILE_TO_CHANGE}`]: newContent, 113 | }, 114 | commit: `updating ${FILE_TO_CHANGE}` 115 | } 116 | }) 117 | } catch (err) { 118 | if (err.status === 422) { 119 | console.log('BRANCH ALREADY EXISTS!') 120 | return { 121 | statusCode: 400, 122 | body: JSON.stringify({ 123 | error: `BRANCH ALREADY EXISTS!` 124 | }) 125 | } 126 | } 127 | } 128 | console.log('data', response.data) 129 | return { 130 | statusCode: 200, 131 | body: JSON.stringify({ 132 | message: `pr created!`, 133 | url: response.data.html_url 134 | }) 135 | } 136 | } 137 | 138 | /** 139 | * Check if array already has URL 140 | * @return {Boolean} 141 | */ 142 | function alreadyHasUri(newItem, allData) { 143 | return allData.some((item) => { 144 | if (!item.url || !newItem.url) { 145 | return false 146 | } 147 | return niceUrl(item.url) === niceUrl(newItem.url) 148 | }) 149 | } 150 | 151 | function niceUrl(href) { 152 | const { host, pathname } = url.parse(href) 153 | return `${host}${pathname}` 154 | } 155 | 156 | /** 157 | * Stringify to JSON maybe. 158 | * 159 | * @param {string} file 160 | * @param {string} content 161 | * 162 | * @return {string} 163 | */ 164 | function parseFile(file, content) { 165 | if (file.indexOf('.json') !== -1) { 166 | return JSON.parse(content) 167 | } 168 | 169 | return content 170 | } 171 | -------------------------------------------------------------------------------- /functions/add-tutorial.js: -------------------------------------------------------------------------------- 1 | const url = require('url') 2 | const gitUrlParse = require('git-url-parse') 3 | const createPullRequest = require('./utils/createPullRequest') 4 | const sanitizeTitle = require('./utils/sanitize') 5 | const Octokit = require('@octokit/rest').plugin(createPullRequest) 6 | 7 | const { REPOSITORY_URL } = process.env 8 | 9 | const octokit = new Octokit() 10 | octokit.authenticate({ 11 | type: 'oauth', 12 | token: process.env.GITHUB_TOKEN 13 | }) 14 | 15 | const FILE_TO_CHANGE = 'data/tutorials.json' 16 | 17 | /* export our lambda function as named "handler" export */ 18 | exports.handler = async (event, context) => { 19 | const parsed = gitUrlParse('https://github.com/netlify-labs/functions-site') 20 | const repo = parsed.name 21 | const owner = parsed.owner 22 | const body = JSON.parse(event.body) 23 | 24 | if (!repo || !owner) { 25 | return { 26 | statusCode: 401, 27 | body: JSON.stringify({ 28 | data: 'process.env.REPOSITORY_URL malformed' 29 | }) 30 | } 31 | } 32 | 33 | if (!body || !body.title) { 34 | return { 35 | statusCode: 401, 36 | body: JSON.stringify({ 37 | data: 'Request malformed. Missing Title' 38 | }) 39 | } 40 | } 41 | 42 | const cleanTitle = sanitizeTitle(body.title) 43 | console.log('cleanTitle', cleanTitle) 44 | if (!cleanTitle) { 45 | return { 46 | statusCode: 401, 47 | body: JSON.stringify({ 48 | data: 'Request malformed. Bad data has been passed in' 49 | }) 50 | } 51 | } 52 | 53 | // Get repo file contents 54 | let result 55 | try { 56 | result = await octokit.repos.getContents({ 57 | owner, 58 | repo, 59 | path: FILE_TO_CHANGE 60 | }) 61 | } catch (err) { 62 | console.log('octokit.repos.getContents err', err) 63 | return { 64 | statusCode: 400, 65 | body: JSON.stringify({ 66 | error: `${err.message}` 67 | }) 68 | } 69 | } 70 | 71 | if (typeof result.data === 'undefined') { 72 | // createFile(octokit, config, file, content) 73 | // throw file doesnt exist 74 | return { 75 | statusCode: 400, 76 | body: JSON.stringify({ 77 | error: `No ${FILE_TO_CHANGE} found` 78 | }) 79 | } 80 | } 81 | 82 | // content will be base64 encoded 83 | const content = Buffer.from(result.data.content, 'base64').toString() 84 | 85 | const allData = parseFile(FILE_TO_CHANGE, content) 86 | console.log('allData.length', allData.length) 87 | 88 | if (alreadyHasUri(body, allData)) { 89 | console.log(`${body.url} already is in the list!`) 90 | return { 91 | statusCode: 422, 92 | body: JSON.stringify({ 93 | message: `${body.url} already is in the list!` 94 | }) 95 | } 96 | } 97 | 98 | const newData = allData.concat(body) 99 | const newContent = JSON.stringify(newData, null, 2) 100 | 101 | let response = {} 102 | try { 103 | response = await octokit.createPullRequest({ 104 | owner, 105 | repo, 106 | title: `Add tutorial ${body.url}`, 107 | body: `Add ${body.title} at ${body.url}`, 108 | base: 'master', /* optional: defaults to default branch */ 109 | head: `pull-request-branch-name-${new Date().getTime()}`, 110 | changes: { 111 | files: { 112 | [`${FILE_TO_CHANGE}`]: newContent, 113 | }, 114 | commit: `updating ${FILE_TO_CHANGE}` 115 | } 116 | }) 117 | } catch (err) { 118 | if (err.status === 422) { 119 | console.log('BRANCH ALREADY EXISTS!') 120 | return { 121 | statusCode: 400, 122 | body: JSON.stringify({ 123 | error: `BRANCH ALREADY EXISTS!` 124 | }) 125 | } 126 | } 127 | } 128 | console.log('data', response.data) 129 | return { 130 | statusCode: 200, 131 | body: JSON.stringify({ 132 | message: `pr created!`, 133 | url: response.data.html_url 134 | }) 135 | } 136 | } 137 | 138 | /** 139 | * Check if array already has URL 140 | * @return {Boolean} 141 | */ 142 | function alreadyHasUri(newItem, allData) { 143 | return allData.some((item) => { 144 | if (!item.url || !newItem.url) { 145 | return false 146 | } 147 | return niceUrl(item.url) === niceUrl(newItem.url) 148 | }) 149 | } 150 | 151 | function niceUrl(href) { 152 | const { host, pathname } = url.parse(href) 153 | return `${host}${pathname}` 154 | } 155 | 156 | /** 157 | * Stringify to JSON maybe. 158 | * 159 | * @param {string} file 160 | * @param {string} content 161 | * 162 | * @return {string} 163 | */ 164 | function parseFile(file, content) { 165 | if (file.indexOf('.json') !== -1) { 166 | return JSON.parse(content) 167 | } 168 | 169 | return content 170 | } 171 | -------------------------------------------------------------------------------- /functions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "add-example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "add-example.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "MIT", 11 | "dependencies": { 12 | "@octokit/rest": "^16.3.0", 13 | "git-url-parse": "^11.1.2" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /functions/utils/createPullRequest.js: -------------------------------------------------------------------------------- 1 | module.exports = octokitCreatePullRequest 2 | 3 | function octokitCreatePullRequest (octokit) { 4 | octokit.createPullRequest = createPullRequest.bind(null, octokit) 5 | } 6 | 7 | async function createPullRequest (octokit, { owner, repo, title, body, base, head, changes }) { 8 | let response 9 | 10 | if (!base) { 11 | response = await octokit.repos.get({ owner, repo }) 12 | base = response.data.default_branch 13 | } 14 | 15 | response = await octokit.repos.listCommits({ 16 | owner, 17 | repo, 18 | sha: base, 19 | per_page: 1 20 | }) 21 | let latestCommitSha = response.data[0].sha 22 | const treeSha = response.data[0].commit.tree.sha 23 | 24 | response = await octokit.git.createTree({ 25 | owner, 26 | repo, 27 | base_tree: treeSha, 28 | tree: Object.keys(changes.files).map(path => { 29 | return { 30 | path, 31 | mode: '100644', 32 | content: changes.files[path] 33 | } 34 | }) 35 | }) 36 | const newTreeSha = response.data.sha 37 | 38 | response = await octokit.git.createCommit({ 39 | owner, 40 | repo, 41 | message: changes.commit, 42 | tree: newTreeSha, 43 | parents: [latestCommitSha] 44 | }) 45 | latestCommitSha = response.data.sha 46 | 47 | await octokit.git.createRef({ 48 | owner, 49 | repo, 50 | sha: latestCommitSha, 51 | ref: `refs/heads/${head}` 52 | }) 53 | 54 | response = await octokit.pulls.create({ 55 | owner, 56 | repo, 57 | head, 58 | base, 59 | title, 60 | body 61 | }) 62 | return response 63 | } 64 | -------------------------------------------------------------------------------- /functions/utils/sanitize.js: -------------------------------------------------------------------------------- 1 | 2 | // Add -1); waitfor delay '0:0:9.456' -- at 3 | // Add -1;select pg_sleep(14.184); -- at 4 | // Add 1'||DBMS_PIPE.RECEIVE_MESSAGE(CHR(98)||CHR(98)||CHR(98),4.728)||' at 5 | // Add '.print(md5(31337)).' at 6 | module.exports = function isClean(str) { 7 | const value = str || '' 8 | if (value.length < 3) { 9 | return false 10 | } 11 | const matches = value.match(/\.\.\/|bxss\.me|\|\|\);|';|0:0:|\)\)|\(\(|\(\),|\.xml|\/\/\/|\.\.%/g) 12 | if (matches) { 13 | return false 14 | } 15 | return true 16 | } 17 | -------------------------------------------------------------------------------- /gatsby-browser.js: -------------------------------------------------------------------------------- 1 | const analytics = require('./src/analytics').default 2 | 3 | exports.onRouteUpdate = ({ location }) => { 4 | console.log('new pathname', location.pathname) 5 | console.log('analytics', analytics) 6 | } -------------------------------------------------------------------------------- /gatsby-config.js: -------------------------------------------------------------------------------- 1 | const urljoin = require('url-join') 2 | const config = require('./_site-config') 3 | const isDevelopment = process.env.NODE_ENV !== `production` 4 | 5 | /* hot module reloading for CSS variables */ 6 | const postcssFile = require.resolve("./postcss.config.js") 7 | const postcssPlugins = (webpackInstance) => { 8 | const varFile = require.resolve("./src/_variables.js") 9 | const varFileContents = () => { 10 | webpackInstance.addDependency(varFile) 11 | delete require.cache[varFile] 12 | return require(varFile) 13 | } 14 | webpackInstance.addDependency(postcssFile) 15 | delete require.cache[postcssFile] 16 | return require(postcssFile)({}, varFileContents()) 17 | } 18 | 19 | module.exports = { 20 | pathPrefix: config.pathPrefix, 21 | siteMetadata: { 22 | siteUrl: urljoin(config.siteUrl, config.pathPrefix), 23 | rssMetadata: { 24 | site_url: urljoin(config.siteUrl, config.pathPrefix), 25 | feed_url: urljoin(config.siteUrl, config.pathPrefix, config.siteRss), 26 | title: config.siteTitle, 27 | description: config.siteDescription, 28 | image_url: `${urljoin(config.siteUrl, config.pathPrefix)}/logos/logo-512.png`, 29 | author: config.userName, 30 | copyright: config.copyright 31 | } 32 | }, 33 | plugins: [ 34 | { 35 | resolve: 'gatsby-route-plugin', 36 | options: { 37 | debug: true 38 | } 39 | }, 40 | { 41 | resolve: 'gatsby-plugin-analytics', 42 | options: { 43 | debug: true 44 | } 45 | }, 46 | 'gatsby-plugin-react-helmet', 47 | 'gatsby-plugin-lodash', 48 | // https://github.com/gatsbyjs/gatsby/pull/8496/files 49 | // { 50 | // resolve: `gatsby-plugin-react-css-modules`, 51 | // options: { 52 | // generateScopedName: isDevelopment ? `[name]--[local]--[hash:base64:5]` : `[hash:base64:5]`, 53 | // }, 54 | // }, 55 | { 56 | resolve: 'gatsby-better-postcss', 57 | options: { 58 | cssMatch: 'hi', 59 | postCssPlugins: postcssPlugins, 60 | }, 61 | }, 62 | { 63 | resolve: 'gatsby-source-filesystem', 64 | options: { 65 | name: 'assets', 66 | path: `${__dirname}/static/` 67 | } 68 | }, 69 | { 70 | resolve: 'gatsby-source-filesystem', 71 | options: { 72 | name: 'posts', 73 | path: `${__dirname}/content/` 74 | } 75 | }, 76 | { 77 | resolve: 'gatsby-transformer-remark', 78 | options: { 79 | plugins: [ 80 | { 81 | resolve: 'gatsby-remark-images', 82 | options: { 83 | maxWidth: 690 84 | } 85 | }, 86 | { 87 | resolve: 'gatsby-remark-responsive-iframe' 88 | }, 89 | 'gatsby-remark-prismjs', 90 | 'gatsby-remark-copy-linked-files', 91 | 'gatsby-remark-autolink-headers' 92 | ] 93 | } 94 | }, 95 | { 96 | resolve: 'gatsby-plugin-google-analytics', 97 | options: { 98 | trackingId: config.googleAnalyticsID 99 | } 100 | }, 101 | { 102 | resolve: 'gatsby-plugin-nprogress', 103 | options: { 104 | color: config.themeColor 105 | } 106 | }, 107 | 'gatsby-plugin-sharp', 108 | 'gatsby-plugin-catch-links', 109 | 'gatsby-plugin-twitter', 110 | 'gatsby-plugin-sitemap', 111 | { 112 | resolve: 'gatsby-plugin-manifest', 113 | options: { 114 | name: config.siteTitle, 115 | short_name: config.siteTitleShort, 116 | description: config.siteDescription, 117 | start_url: config.pathPrefix, 118 | background_color: config.backgroundColor, 119 | theme_color: config.themeColor, 120 | display: 'minimal-ui', 121 | icons: [ 122 | { 123 | src: '/logos/logo-192x192.png', 124 | sizes: '192x192', 125 | type: 'image/png' 126 | }, 127 | { 128 | src: '/logos/logo-512x512.png', 129 | sizes: '512x512', 130 | type: 'image/png' 131 | } 132 | ] 133 | } 134 | }, 135 | // 'gatsby-plugin-offline', 136 | { 137 | resolve: 'gatsby-plugin-feed', 138 | options: { 139 | setup(ref) { 140 | const ret = ref.query.site.siteMetadata.rssMetadata 141 | ret.allMarkdownRemark = ref.query.allMarkdownRemark 142 | ret.generator = 'GatsbyJS Material Starter' 143 | return ret 144 | }, 145 | query: ` 146 | { 147 | site { 148 | siteMetadata { 149 | rssMetadata { 150 | site_url 151 | feed_url 152 | title 153 | description 154 | image_url 155 | author 156 | copyright 157 | } 158 | } 159 | } 160 | } 161 | `, 162 | feeds: [ 163 | { 164 | serialize(ctx) { 165 | const { rssMetadata } = ctx.query.site.siteMetadata 166 | return ctx.query.allMarkdownRemark.edges.map(edge => ({ 167 | categories: edge.node.frontmatter.tags, 168 | date: edge.node.fields.date, 169 | title: edge.node.frontmatter.title, 170 | description: edge.node.excerpt, 171 | author: rssMetadata.author, 172 | url: rssMetadata.site_url + edge.node.fields.slug, 173 | guid: rssMetadata.site_url + edge.node.fields.slug, 174 | custom_elements: [{ 'content:encoded': edge.node.html }] 175 | })) 176 | }, 177 | query: ` 178 | { 179 | allMarkdownRemark( 180 | limit: 1000, 181 | sort: { order: DESC, fields: [fields___date] }, 182 | ) { 183 | edges { 184 | node { 185 | excerpt 186 | html 187 | timeToRead 188 | fields { 189 | slug 190 | date 191 | } 192 | frontmatter { 193 | title 194 | cover 195 | date 196 | category 197 | tags 198 | } 199 | } 200 | } 201 | } 202 | } 203 | `, 204 | output: config.siteRss 205 | } 206 | ] 207 | } 208 | } 209 | ] 210 | } 211 | -------------------------------------------------------------------------------- /gatsby-node.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const _ = require('lodash') 3 | const moment = require('moment') 4 | const siteConfig = require('./_site-config') 5 | const { createFilePath } = require(`gatsby-source-filesystem`) 6 | 7 | const postNodes = [] 8 | 9 | // Fetching remote data https://github.com/gatsbyjs/gatsby/blob/5a08b7640c2db2c65696be56d81afee83b2ca9ec/examples/using-unstructured-data/gatsby-node.js 10 | // Big example https://github.com/gatsbyjs/gatsby/blob/master/www/gatsby-node.js 11 | 12 | function addSiblingNodes(createNodeField) { 13 | postNodes.sort( 14 | ({ frontmatter: { date: date1 } }, { frontmatter: { date: date2 } }) => { 15 | const dateA = moment(date1, siteConfig.dateFromFormat) 16 | const dateB = moment(date2, siteConfig.dateFromFormat) 17 | 18 | if (dateA.isBefore(dateB)) return 1 19 | 20 | if (dateB.isBefore(dateA)) return -1 21 | 22 | return 0 23 | } 24 | ) 25 | for (let i = 0; i < postNodes.length; i += 1) { 26 | const nextID = i + 1 < postNodes.length ? i + 1 : 0 27 | const prevID = i - 1 > 0 ? i - 1 : postNodes.length - 1 28 | const currNode = postNodes[i] 29 | const nextNode = postNodes[nextID] 30 | const prevNode = postNodes[prevID] 31 | createNodeField({ 32 | node: currNode, 33 | name: 'nextTitle', 34 | value: nextNode.frontmatter.title 35 | }) 36 | createNodeField({ 37 | node: currNode, 38 | name: 'nextSlug', 39 | value: nextNode.fields.slug 40 | }) 41 | createNodeField({ 42 | node: currNode, 43 | name: 'prevTitle', 44 | value: prevNode.frontmatter.title 45 | }) 46 | createNodeField({ 47 | node: currNode, 48 | name: 'prevSlug', 49 | value: prevNode.fields.slug 50 | }) 51 | } 52 | } 53 | 54 | exports.onCreateNode = ({ node, actions, getNode }) => { 55 | const { createNodeField } = actions 56 | let slug 57 | 58 | if (node.internal.type === `MarkdownRemark`) { 59 | const fileNode = getNode(node.parent) 60 | console.log(`\n`, fileNode.relativePath) 61 | const test = createFilePath({ node, getNode, basePath: `pages` }) 62 | console.log('test', test) 63 | } 64 | if (node.internal.type === 'MarkdownRemark') { 65 | const fileNode = getNode(node.parent) 66 | const parsedFilePath = path.parse(fileNode.relativePath) 67 | if ( 68 | Object.prototype.hasOwnProperty.call(node, 'frontmatter') && 69 | Object.prototype.hasOwnProperty.call(node.frontmatter, 'title') 70 | ) { 71 | console.log('Set title as slug', node.frontmatter.title) 72 | slug = `/${_.kebabCase(node.frontmatter.title)}` 73 | } else if (parsedFilePath.name !== 'index' && parsedFilePath.dir !== '') { 74 | slug = `/${parsedFilePath.dir}/${parsedFilePath.name}/` 75 | } else if (parsedFilePath.dir === '') { 76 | slug = `/${parsedFilePath.name}/` 77 | } else { 78 | slug = `/${parsedFilePath.dir}/` 79 | } 80 | 81 | if (Object.prototype.hasOwnProperty.call(node, 'frontmatter')) { 82 | if (Object.prototype.hasOwnProperty.call(node.frontmatter, 'slug')) { 83 | slug = `/${_.kebabCase(node.frontmatter.slug)}` 84 | } 85 | 86 | if (Object.prototype.hasOwnProperty.call(node.frontmatter, 'date')) { 87 | const date = moment(node.frontmatter.date, siteConfig.dateFromFormat) 88 | if (!date.isValid) { console.warn(`WARNING: Invalid date.`, node.frontmatter) } 89 | 90 | createNodeField({ 91 | node, 92 | name: 'date', 93 | value: date.toISOString() 94 | }) 95 | } 96 | } 97 | createNodeField({ node, name: 'slug', value: slug }) 98 | postNodes.push(node) 99 | } 100 | } 101 | 102 | exports.setFieldsOnGraphQLNodeType = ({ type, actions }) => { 103 | const { name } = type 104 | const { createNodeField } = actions 105 | if (name === 'MarkdownRemark') { 106 | addSiblingNodes(createNodeField) 107 | } 108 | } 109 | 110 | exports.createPages = ({ graphql, actions }) => { 111 | const { createPage } = actions 112 | 113 | return new Promise((resolve, reject) => { 114 | const postPage = path.resolve('src/templates/Post.js') 115 | const tagPage = path.resolve('src/templates/Tag.js') 116 | const categoryPage = path.resolve('src/templates/Category.js') 117 | resolve( 118 | graphql( 119 | ` 120 | { 121 | allMarkdownRemark { 122 | edges { 123 | node { 124 | frontmatter { 125 | tags 126 | category 127 | } 128 | fields { 129 | slug 130 | } 131 | } 132 | } 133 | } 134 | } 135 | ` 136 | ).then(result => { 137 | if (result.errors) { 138 | /* eslint no-console: "off" */ 139 | console.log(result.errors) 140 | reject(result.errors) 141 | } 142 | 143 | const tagSet = new Set() 144 | const categorySet = new Set() 145 | result.data.allMarkdownRemark.edges.forEach(edge => { 146 | if (edge.node.frontmatter.tags) { 147 | edge.node.frontmatter.tags.forEach(tag => { 148 | tagSet.add(tag) 149 | }) 150 | } 151 | 152 | if (edge.node.frontmatter.category) { 153 | categorySet.add(edge.node.frontmatter.category) 154 | } 155 | 156 | createPage({ 157 | path: edge.node.fields.slug, 158 | component: postPage, 159 | context: { 160 | slug: edge.node.fields.slug 161 | } 162 | }) 163 | }) 164 | 165 | const tagList = Array.from(tagSet) 166 | tagList.forEach(tag => { 167 | createPage({ 168 | path: `/tags/${_.kebabCase(tag)}/`, 169 | component: tagPage, 170 | context: { 171 | tag 172 | } 173 | }) 174 | }) 175 | 176 | const categoryList = Array.from(categorySet) 177 | categoryList.forEach(category => { 178 | createPage({ 179 | path: `/categories/${_.kebabCase(category)}/`, 180 | component: categoryPage, 181 | context: { 182 | category 183 | } 184 | }) 185 | }) 186 | }) 187 | ) 188 | }) 189 | } 190 | 191 | /* Shrink CSS class names in prod */ 192 | const cssLoaderRe = /\/css-loader\//; 193 | const targetFile = '.css'; 194 | 195 | const processRule = rule => { 196 | // console.log('rule', rule) 197 | if (rule.oneOf) { 198 | return { 199 | ...rule, 200 | oneOf: rule.oneOf.map(processRule), 201 | }; 202 | } 203 | 204 | if (!rule.test.test(targetFile)) { 205 | return rule; 206 | } 207 | 208 | if (Array.isArray(rule.use)) { 209 | return { 210 | ...rule, 211 | use: rule.use.map(use => { 212 | if (!cssLoaderRe.test(use.loader)) { 213 | return use; 214 | } 215 | // console.log('use', use) 216 | // Adjust css-loader options 217 | return { 218 | ...use, 219 | options: { 220 | ...use.options, 221 | localIdentName: 222 | process.env.NODE_ENV === 'production' 223 | ? '[hash:base64:5]' 224 | : '[name]_[local]_[hash:base64:4]', 225 | }, 226 | }; 227 | }), 228 | }; 229 | } 230 | 231 | return rule; 232 | }; 233 | 234 | exports.onCreateWebpackConfig = ({ getConfig, actions }) => { 235 | const config = getConfig(); 236 | 237 | const newConfig = { 238 | ...config, 239 | module: { 240 | ...config.module, 241 | rules: config.module.rules.map(processRule), 242 | }, 243 | }; 244 | actions.replaceWebpackConfig(newConfig); 245 | }; 246 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | publish = "public" 3 | command = "npm run build" 4 | functions = "functions" 5 | 6 | # Redirects 7 | [[redirects]] 8 | from = "/add" 9 | to = "/add-example" 10 | status = 301 11 | force = true 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "netlify-functions", 3 | "description": "Netlify functions site", 4 | "version": "0.0.1", 5 | "private": true, 6 | "keywords": [ 7 | "gatsby" 8 | ], 9 | "author": "David WellsWe’re launching 🚀 AWS AppSync as a new service for preview later today! Here are some of its features! @apatel72001 #reInvent pic.twitter.com/fG9thG6sAa
— AWS re:Invent (@AWSreInvent) November 28, 2017
11 | Oops! The page you are looking for has been removed or relocated. 12 |
13 | 14 |Go Back
15 | 16 |13 | hehehehehe 14 |
15 |201 | Thanks for your submission! 202 |
203 |204 | 205 | {response.url} 206 | 207 |
208 | {/* 209 |210 | 213 |
214 | */} 215 | 216 | ) 217 | } 218 | return ( 219 |185 | Thanks for your submission! 186 |
187 |188 | 189 | {response.url} 190 | 191 |
192 | 193 | ) 194 | } 195 | return ( 196 |23 | When you build and deploy sites on netlify, you can interact with third-party services and APIs with javascript places directly in your site. For example, you can have a script that sends event data to google analytics, or adds someone to a mailchimp list, or sends a request to a zapier webhook. 24 |
25 |26 | But what if you want to write scripts with something other than javascript? Or if you want to do more complex things like resize images or query a database? Or if you have sensitive information in your script such as API tokens that you don’t want embedded on your site and visible to all? Or if there is no service or API that does what you need? 27 |
28 |29 | That’s where Netlify Functions come in. 30 |
31 |33 | Functions are scripts that you write and deploy with Netlify. The function’s code is hidden from the public, but you can interact with it just like any other API service. Just as with your site code, Netlify takes care of deploying your scripts into functions. 34 |
35 |37 | This is useful for developers who want to add more functionality to their sites and don’t want to or can’t rely entirely on third-party APIs, or want to use a language other than JavaScript, or don’t want to expose their scripts to site visitors. 38 |
39 |41 | Despite all the benefits of serverless sites, there were many things you couldn’t do with just client-side JavaScript: 42 |
43 |52 | You would want to use Functions if you want to deploy scripts that can be run on-demand and return results just like an API, that run on high-powered servers for low latency, that can be written in Go or JavaScript, and that keeps the underlying code (and any secrets inside) hidden from the world. 53 |
54 |