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: add page views #141

Merged
merged 26 commits into from
Jun 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
6f71367
feat: ignore service-account.json
jsjoeio May 30, 2020
748cd36
feat: add support for netlify functions
jsjoeio May 30, 2020
cbd6440
refactor: get netlify funcs working
jsjoeio May 31, 2020
d7c885e
fix: add page views
jsjoeio May 31, 2020
f49f212
fix: window
jsjoeio May 31, 2020
f880edb
fix again
jsjoeio May 31, 2020
82cfbb0
refactor: update build command
jsjoeio May 31, 2020
ebf57d2
feat: add id to page views
jsjoeio May 31, 2020
af28e2c
feat: add firebase-admin package
jsjoeio May 31, 2020
d5de34d
feat: add webpack node externals
jsjoeio May 31, 2020
537a5dc
feat: get basic page views working with json
jsjoeio May 31, 2020
12e7607
fix: get page views in working state
jsjoeio May 31, 2020
97df04f
feat: add webpack dot env and fix initializeApp
jsjoeio May 31, 2020
b8af970
refactor: move logic into db-admin
jsjoeio May 31, 2020
5ae3ed3
feat: bump h1 to 2.75rem
jsjoeio May 31, 2020
49aa571
feat: add fake total views to post
jsjoeio May 31, 2020
efbd79c
refactor: change date to p
jsjoeio May 31, 2020
660dc1a
wip: add counter
jsjoeio May 31, 2020
1ae1be5
feat: add comma number
jsjoeio Jun 1, 2020
d29d025
feat: use slug in viewCounter
jsjoeio Jun 1, 2020
87a015f
fix: update ViewCounter
jsjoeio Jun 1, 2020
5eb36c7
fix: change posts to articles in search
jsjoeio Jun 1, 2020
0f7dd38
feat: add page views to articles page
jsjoeio Jun 1, 2020
6bc528f
feat: get all views from page views
jsjoeio Jun 1, 2020
c8072b1
fix: clean up Post
jsjoeio Jun 1, 2020
83c0f3a
feat: add open page
jsjoeio Jun 1, 2020
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
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,12 @@ public
.env
.env.development
.env.production

# firebase
service-account.json
serviceAccountKey.json

# netlify funcs
functions-build
# Local Netlify folder
.netlify
9 changes: 9 additions & 0 deletions config/webpack.functions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// required to get firebase-sdk imported into netlify functions
// see: https://github.com/netlify/netlify-lambda/issues/112#issuecomment-488644361
const nodeExternals = require('webpack-node-externals')
const dotEnv = require('dotenv-webpack')

module.exports = {
externals: [nodeExternals()],
plugins: [new dotEnv()],
}
30 changes: 30 additions & 0 deletions functions/increment-views.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { db } from '../src/lib/db-admin'

exports.handler = async (event, context) => {
const { id } = event.queryStringParameters
if (!id) {
return {
statusCode: 400,
body: JSON.stringify({
error: 'Missing "id" query parameter',
}),
}
}

const ref = db.ref('joeprevite-dot-com/views').child(id)
const { snapshot } = await ref.transaction(currentViews => {
if (currentViews === null) {
return 1
}

return currentViews + 1
})

return {
statusCode: 200,
body: JSON.stringify({
pageId: id,
totalViews: snapshot.val(),
}),
}
}
39 changes: 39 additions & 0 deletions functions/page-views.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { db } from '../src/lib/db-admin'

exports.handler = async (event, context) => {
const { id } = event.queryStringParameters

if (!id) {
let totalViews
await db.ref('joeprevite-dot-com/views').once('value', snapshot => {
const views = snapshot.val()
// Grabs all the views for all posts
const allViews = Object.values(views).reduce(
(total, value) => total + value,
)
totalViews = allViews
})
return {
statusCode: 200,
body: JSON.stringify({
totalViews: totalViews || 0,
}),
}
}

const ref = db.ref('joeprevite-dot-com/views').child(id)

let totalViews
await ref.once('value', snapshot => {
const value = snapshot.val()
totalViews = value
})

return {
statusCode: 200,
body: JSON.stringify({
pageId: id,
totalViews: totalViews !== null ? totalViews : 0,
}),
}
}
12 changes: 12 additions & 0 deletions gatsby-config.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
const config = require('./config/website')
const pathPrefix = config.pathPrefix === '/' ? '' : config.pathPrefix
const { createProxyMiddleware } = require('http-proxy-middleware')

require('dotenv').config({
path: `.env.${process.env.NODE_ENV}`,
})

module.exports = {
developMiddleware: app => {
app.use(
'/.netlify/functions/',
createProxyMiddleware({
target: 'http://localhost:9000',
pathRewrite: {
'/.netlify/functions/': '',
},
}),
)
},
pathPrefix: config.pathPrefix,
siteMetadata: {
siteUrl: config.siteUrl + pathPrefix,
Expand Down
2 changes: 2 additions & 0 deletions netlify.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
[build]
command = "yarn build"
functions = "functions-build"
publish = "public"

[[plugins]]
Expand Down
16 changes: 14 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,16 @@
"@mdx-js/mdx": "^1.5.1",
"@mdx-js/react": "^1.5.5",
"@weknow/gatsby-remark-twitter": "^0.2.3",
"comma-number": "^2.0.1",
"core-js": "^3.3.3",
"dotenv": "^8.2.0",
"dotenv-parse-variables": "^0.2.3",
"dotenv-webpack": "^1.8.0",
"emotion": "^10.0.17",
"emotion-server": "^10.0.17",
"emotion-theming": "^10.0.19",
"firebase": "^7.14.6",
"firebase-admin": "^8.12.1",
"formik": "^2.0.8",
"framer-motion": "^1.10.3",
"gatsby": "^2.21.9",
Expand Down Expand Up @@ -60,14 +64,19 @@
"react-share": "^4.0.1",
"react-typography": "^0.16.19",
"serve": "^11.2.0",
"swr": "^0.2.2",
"typescript": "^3.8.3",
"typography": "^0.16.19",
"webpack-node-externals": "^1.7.2",
"yup": "^0.28.0"
},
"devDependencies": {
"babel-preset-gatsby": "^0.2.20",
"dayjs": "^1.8.18",
"http-proxy-middleware": "^1.0.4",
"hygen": "^5.0.3",
"netlify-lambda": "^1.6.3",
"npm-run-all": "^4.1.5",
"open": "^7.0.3",
"prettier": "1.19.1"
},
Expand All @@ -77,10 +86,13 @@
"license": "MIT",
"main": "n/a",
"scripts": {
"build": "GATSBY_EXPERIMENTAL_PAGE_BUILD_ON_DATA_CHANGES=true gatsby build --log-pages",
"build": "GATSBY_EXPERIMENTAL_PAGE_BUILD_ON_DATA_CHANGES=true gatsby build --log-pages && yarn build:lambda",
"build:lambda": "netlify-lambda build --config ./config/webpack.functions.js functions",
"build:local": "gatsby build",
"g:post": "hygen post new",
"dev": "gatsby develop",
"start": "serve public/"
"start": "run-p start:**",
"start:app": "npm run dev",
"start:lambda": "netlify-lambda serve --config ./config/webpack.functions.js functions"
}
}
3 changes: 3 additions & 0 deletions src/components/Layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,9 @@ const getGlobalStyles = theme => {
summary {
display: list-item;
}
small {
color: ${lighten(0.35, theme.colors.black)};
}
`
}

Expand Down
109 changes: 109 additions & 0 deletions src/components/Post.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import React from 'react'
import useSWR from 'swr'
import format from 'comma-number'

import { css } from '@emotion/core'
import { bpMaxSM } from '../lib/breakpoints'
import Link from './Link'
import fetcher from '../lib/fetcher'

function Post({ post }) {
const { data } = useSWR(
`/.netlify/functions/page-views?id=${post.fields.slug}`,
fetcher,
)

const views = data?.totalViews

return (
<div
css={css`
:not(:first-of-type) {
margin-top: 10px;
}
:first-of-type {
margin-top: 10px;
${bpMaxSM} {
margin-top: 10px;
}
}
.gatsby-image-wrapper {
}
${bpMaxSM} {
padding: 20px;
}
display: flex;
flex-direction: column;
`}
>
{/* {post.frontmatter.banner && (
<div
css={css`
padding: 60px 60px 40px 60px;
${bpMaxSM} {
padding: 20px;
}
`}
>
<Link
aria-label={`View ${post.frontmatter.title} article`}
to={`/${post.fields.slug}`}
>
<Img sizes={post.frontmatter.banner.childImageSharp.fluid} />
</Link>
</div>
)} */}
<div
css={css`
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
h2,
small {
margin-top: 30px;
margin-bottom: 10px;
}
small {
margin-left: 10px;
white-space: nowrap;
}
${bpMaxSM} {
flex-wrap: wrap;

small {
margin: 0;
}
}
`}
>
<h2>
<Link
aria-label={`View ${post.frontmatter.title} article`}
to={`/${post.fields.slug}`}
>
{post.frontmatter.title}
</Link>
</h2>
<small>{`${
views !== null && views !== undefined ? format(views) : '–––'
} views`}</small>
</div>
<p
css={css`
margin-top: 10px;
`}
>
{post.excerpt}
</p>{' '}
<Link
to={`/${post.fields.slug}`}
aria-label={`view "${post.frontmatter.title}" article`}
>
Read Article →
</Link>
</div>
)
}

export default Post
44 changes: 44 additions & 0 deletions src/components/ViewCounter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* credit to @leerob who wrote this
* @see https://github.com/leerob/leerob.io/blob/master/components/ViewCounter.js
*/
import React, { useState, useEffect } from 'react'
import format from 'comma-number'

import { loadDb } from '../lib/db'

const ViewCounter = ({ id }) => {
const [views, setViews] = useState('')

useEffect(() => {
const onViews = newViews => setViews(newViews.val())

let db

const fetchData = async () => {
db = await loadDb()
db.child(id).on('value', onViews, error => {
console.error('Error reading value', error)
})
}

fetchData()

return () => {
if (db) {
db.child(id).off('value', onViews)
}
}
}, [id])

useEffect(() => {
const registerView = () =>
fetch(`/.netlify/functions/increment-views?id=${id}`)

registerView()
}, [id])

return <p>{`${views ? format(views) : '–––'} views`}</p>
}

export default ViewCounter
27 changes: 27 additions & 0 deletions src/lib/db-admin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* credit to @leerob who wrote this
* @see https://github.com/leerob/leerob.io/blob/master/lib/db-admin.js
*/
const admin = require('firebase-admin')

try {
admin.initializeApp({
credential: admin.credential.cert({
client_email: process.env.FIREBASE_CLIENT_EMAIL,
private_key: process.env.FIREBASE_PRIVATE_KEY,
project_id: 'website-pageviews-c8d4d',
}),
databaseURL: 'https://website-pageviews-c8d4d.firebaseio.com/',
})
} catch (error) {
/*
* We skip the "already exists" message which is
* not an actual error when we're hot-reloading.
*/
if (!/already exists/u.test(error.message)) {
// eslint-disable-next-line no-console
console.error('Firebase admin initialization error', error.stack)
}
}

export const db = admin.database()
26 changes: 26 additions & 0 deletions src/lib/db.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* credit to @leerob who wrote this
* @see https://github.com/leerob/leerob.io/blob/master/lib/db.js
*/
export async function loadDb() {
const firebase = await import('firebase/app')

await import('firebase/database')

try {
firebase.initializeApp({
databaseURL: 'https://website-pageviews-c8d4d.firebaseio.com/',
})
} catch (error) {
/*
* We skip the "already exists" message which is
* not an actual error when we're hot-reloading.
*/
if (!/already exists/u.test(error.message)) {
// eslint-disable-next-line no-console
console.error('Firebase initialization error', error.stack)
}
}

return firebase.database().ref('joeprevite-dot-com/views')
}
Loading