├── .gitignore ├── Readme.md ├── index.js ├── lib ├── db.js ├── increment-views.js └── verify.js ├── package.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | service-account.json 3 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | Deprecated. 2 | 3 | This project is now a simple serverless function inside `pages/api` in the [main blog repo](https://github.com/rauchg/blog/tree/master/pages/api). 4 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const verify = require('./lib/verify') 2 | const { parse } = require('url') 3 | const increment = require('./lib/increment-views') 4 | 5 | module.exports = async (req, res) => { 6 | const orig = req.headers.origin 7 | if (/https:\/\/(.*\.)?rauchg\.com/.test(orig)) { 8 | res.setHeader('Access-Control-Allow-Origin', orig) 9 | res.setHeader('Access-Control-Allow-Methods', 'GET') 10 | } 11 | 12 | // ensure no duplicate voting 13 | verify(req) 14 | 15 | const { query: { id } } = parse(req.url, true) 16 | 17 | if (!id) { 18 | const err = new Error('Missing `id` parameter with blog post id') 19 | err.statusCode = 400 20 | throw err 21 | } 22 | 23 | const { snapshot } = await increment(id) 24 | return { total: snapshot.val() } 25 | } 26 | -------------------------------------------------------------------------------- /lib/db.js: -------------------------------------------------------------------------------- 1 | const admin = require('firebase-admin') 2 | const { join } = require('path') 3 | const cert = join(__dirname, '../service-account.json') 4 | 5 | admin.initializeApp({ 6 | credential: admin.credential.cert(cert), 7 | databaseURL: "https://rauchg-blog.firebaseio.com" 8 | }); 9 | 10 | module.exports = admin.database() 11 | -------------------------------------------------------------------------------- /lib/increment-views.js: -------------------------------------------------------------------------------- 1 | const db = require('./db') 2 | 3 | module.exports = function incrementViews (id) { 4 | const ref = db.ref('views').child(id) 5 | return ref.transaction(currentViews => { 6 | // if it has never been set it returns null 7 | if (currentViews === null) currentViews = 0 8 | return currentViews + 1 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /lib/verify.js: -------------------------------------------------------------------------------- 1 | const ms = require('ms') 2 | const requestIp = require('request-ip') 3 | 4 | // seen ips in the last hour 5 | let seen = {} 6 | 7 | // reset blacklist every hour 8 | setInterval(() => { seen = {} }, ms('1h')) 9 | 10 | // env 11 | const { NODE_ENV } = process.env 12 | 13 | module.exports = (req) => { 14 | if ('development' === NODE_ENV) { 15 | // ignore limits during development 16 | return 17 | } 18 | 19 | const clientIp = requestIp.getClientIp(req) 20 | seen[clientIp] = seen[clientIp] || 0 21 | if (seen[clientIp] > 10) { 22 | const err = new Error('Too many views per IP') 23 | err.statusCode = 429 24 | throw err 25 | } 26 | seen[clientIp]++ 27 | } 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rauchg-blog-views", 3 | "dependencies": { 4 | "firebase": "^3.6.4", 5 | "firebase-admin": "^4.0.4", 6 | "micro": "^6.1.0", 7 | "ms": "^0.7.2", 8 | "request-ip": "^1.2.3" 9 | }, 10 | "now": { 11 | "files": [ 12 | "service-account.json", 13 | "index.js", 14 | "lib" 15 | ] 16 | }, 17 | "scripts": { 18 | "start": "micro" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/jsonwebtoken@^7.1.33": 6 | version "7.2.0" 7 | resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-7.2.0.tgz#0fed32c8501da80ac9839d2d403a65c83d776ffd" 8 | dependencies: 9 | "@types/node" "*" 10 | 11 | "@types/node@*": 12 | version "6.0.57" 13 | resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.57.tgz#100f9d4390331297bc3b6160ac4805b46de6e752" 14 | 15 | async-to-gen@1.1.4: 16 | version "1.1.4" 17 | resolved "https://registry.yarnpkg.com/async-to-gen/-/async-to-gen-1.1.4.tgz#0baa2a75d5b22b411054d5c85e4a0c06fcf9768d" 18 | dependencies: 19 | babylon "^6.11.4" 20 | magic-string "^0.16.0" 21 | 22 | babylon@^6.11.4: 23 | version "6.14.1" 24 | resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.14.1.tgz#956275fab72753ad9b3435d7afe58f8bf0a29815" 25 | 26 | base64url@2.0.0, base64url@^2.0.0: 27 | version "2.0.0" 28 | resolved "https://registry.yarnpkg.com/base64url/-/base64url-2.0.0.tgz#eac16e03ea1438eff9423d69baa36262ed1f70bb" 29 | 30 | buffer-equal-constant-time@1.0.1: 31 | version "1.0.1" 32 | resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" 33 | 34 | bytes@2.4.0: 35 | version "2.4.0" 36 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.4.0.tgz#7d97196f9d5baf7f6935e25985549edd2a6c2339" 37 | 38 | dom-storage@2.0.2: 39 | version "2.0.2" 40 | resolved "https://registry.yarnpkg.com/dom-storage/-/dom-storage-2.0.2.tgz#ed17cbf68abd10e0aef8182713e297c5e4b500b0" 41 | 42 | ecdsa-sig-formatter@1.0.9: 43 | version "1.0.9" 44 | resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.9.tgz#4bc926274ec3b5abb5016e7e1d60921ac262b2a1" 45 | dependencies: 46 | base64url "^2.0.0" 47 | safe-buffer "^5.0.1" 48 | 49 | faye-websocket@0.9.3: 50 | version "0.9.3" 51 | resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.9.3.tgz#482a505b0df0ae626b969866d3bd740cdb962e83" 52 | dependencies: 53 | websocket-driver ">=0.5.1" 54 | 55 | firebase-admin@^4.0.4: 56 | version "4.0.4" 57 | resolved "https://registry.yarnpkg.com/firebase-admin/-/firebase-admin-4.0.4.tgz#051a5286699837a1eaf9e3eafec99c123131663a" 58 | dependencies: 59 | "@types/jsonwebtoken" "^7.1.33" 60 | faye-websocket "0.9.3" 61 | jsonwebtoken "7.1.9" 62 | 63 | firebase@^3.6.4: 64 | version "3.6.4" 65 | resolved "https://registry.yarnpkg.com/firebase/-/firebase-3.6.4.tgz#5c5b91d20f439b6f0a7aebe1b27c2db1a463e308" 66 | dependencies: 67 | dom-storage "2.0.2" 68 | faye-websocket "0.9.3" 69 | jsonwebtoken "7.1.9" 70 | rsvp "3.2.1" 71 | xmlhttprequest "1.8.0" 72 | 73 | hoek@2.x.x: 74 | version "2.16.3" 75 | resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" 76 | 77 | iconv-lite@0.4.13: 78 | version "0.4.13" 79 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2" 80 | 81 | isemail@1.x.x: 82 | version "1.2.0" 83 | resolved "https://registry.yarnpkg.com/isemail/-/isemail-1.2.0.tgz#be03df8cc3e29de4d2c5df6501263f1fa4595e9a" 84 | 85 | isstream@0.1.2: 86 | version "0.1.2" 87 | resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" 88 | 89 | joi@^6.10.1: 90 | version "6.10.1" 91 | resolved "https://registry.yarnpkg.com/joi/-/joi-6.10.1.tgz#4d50c318079122000fe5f16af1ff8e1917b77e06" 92 | dependencies: 93 | hoek "2.x.x" 94 | isemail "1.x.x" 95 | moment "2.x.x" 96 | topo "1.x.x" 97 | 98 | jsonwebtoken@7.1.9: 99 | version "7.1.9" 100 | resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-7.1.9.tgz#847804e5258bec5a9499a8dc4a5e7a3bae08d58a" 101 | dependencies: 102 | joi "^6.10.1" 103 | jws "^3.1.3" 104 | lodash.once "^4.0.0" 105 | ms "^0.7.1" 106 | xtend "^4.0.1" 107 | 108 | jwa@^1.1.4: 109 | version "1.1.5" 110 | resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.5.tgz#a0552ce0220742cd52e153774a32905c30e756e5" 111 | dependencies: 112 | base64url "2.0.0" 113 | buffer-equal-constant-time "1.0.1" 114 | ecdsa-sig-formatter "1.0.9" 115 | safe-buffer "^5.0.1" 116 | 117 | jws@^3.1.3: 118 | version "3.1.4" 119 | resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.4.tgz#f9e8b9338e8a847277d6444b1464f61880e050a2" 120 | dependencies: 121 | base64url "^2.0.0" 122 | jwa "^1.1.4" 123 | safe-buffer "^5.0.1" 124 | 125 | lodash.once@^4.0.0: 126 | version "4.1.1" 127 | resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" 128 | 129 | magic-string@^0.16.0: 130 | version "0.16.0" 131 | resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.16.0.tgz#970ebb0da7193301285fb1aa650f39bdd81eb45a" 132 | dependencies: 133 | vlq "^0.2.1" 134 | 135 | media-typer@0.3.0: 136 | version "0.3.0" 137 | resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" 138 | 139 | micro@^6.1.0: 140 | version "6.1.0" 141 | resolved "https://registry.yarnpkg.com/micro/-/micro-6.1.0.tgz#c933e1dd90dea56b0517b62b2af00d3f5351d79d" 142 | dependencies: 143 | async-to-gen "1.1.4" 144 | isstream "0.1.2" 145 | media-typer "0.3.0" 146 | minimist "1.2.0" 147 | raw-body "2.1.7" 148 | 149 | minimist@1.2.0: 150 | version "1.2.0" 151 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" 152 | 153 | moment@2.x.x: 154 | version "2.17.1" 155 | resolved "https://registry.yarnpkg.com/moment/-/moment-2.17.1.tgz#fed9506063f36b10f066c8b59a144d7faebe1d82" 156 | 157 | ms@^0.7.1, ms@^0.7.2: 158 | version "0.7.2" 159 | resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765" 160 | 161 | raw-body@2.1.7: 162 | version "2.1.7" 163 | resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.1.7.tgz#adfeace2e4fb3098058014d08c072dcc59758774" 164 | dependencies: 165 | bytes "2.4.0" 166 | iconv-lite "0.4.13" 167 | unpipe "1.0.0" 168 | 169 | request-ip@^1.2.3: 170 | version "1.2.3" 171 | resolved "https://registry.yarnpkg.com/request-ip/-/request-ip-1.2.3.tgz#66988f0e22406ec4af630d19b573fe4b447c3b49" 172 | 173 | rsvp@3.2.1: 174 | version "3.2.1" 175 | resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.2.1.tgz#07cb4a5df25add9e826ebc67dcc9fd89db27d84a" 176 | 177 | safe-buffer@^5.0.1: 178 | version "5.0.1" 179 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7" 180 | 181 | topo@1.x.x: 182 | version "1.1.0" 183 | resolved "https://registry.yarnpkg.com/topo/-/topo-1.1.0.tgz#e9d751615d1bb87dc865db182fa1ca0a5ef536d5" 184 | dependencies: 185 | hoek "2.x.x" 186 | 187 | unpipe@1.0.0: 188 | version "1.0.0" 189 | resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" 190 | 191 | vlq@^0.2.1: 192 | version "0.2.1" 193 | resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.1.tgz#14439d711891e682535467f8587c5630e4222a6c" 194 | 195 | websocket-driver@>=0.5.1: 196 | version "0.6.5" 197 | resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.6.5.tgz#5cb2556ceb85f4373c6d8238aa691c8454e13a36" 198 | dependencies: 199 | websocket-extensions ">=0.1.1" 200 | 201 | websocket-extensions@>=0.1.1: 202 | version "0.1.1" 203 | resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.1.tgz#76899499c184b6ef754377c2dbb0cd6cb55d29e7" 204 | 205 | xmlhttprequest@1.8.0: 206 | version "1.8.0" 207 | resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc" 208 | 209 | xtend@^4.0.1: 210 | version "4.0.1" 211 | resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" 212 | --------------------------------------------------------------------------------