diff --git a/README.md b/README.md index fdf8b6de..f2132fd6 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,6 @@ The [Vercel CLI](https://vercel.com/docs/cli) is not included as a project depen ![Creative Commons Attribution 4.0 International License](https://raw.githubusercontent.com/creativecommons/cc-cert-core/master/images/cc-by-88x31.png "CC BY") -Site content (everything in [`content/notes`](content/notes/)) is published under the [**Creative Commons Attribution 4.0 International License**](LICENSE.md) (CC-BY-4.0), which means that you can copy, redistribute, remix, transform, and build upon the content for any purpose as long as you give appropriate credit. +Site content (everything in [`content/notes`](content/notes/)) is published under the [**Creative Commons Attribution 4.0 International License**](LICENSE) (CC-BY-4.0), which means that you can copy, redistribute, remix, transform, and build upon the content for any purpose as long as you give appropriate credit. All original code in this repository is published under the [**MIT License**](https://opensource.org/licenses/MIT). diff --git a/api/contact.js b/api/contact.js index 7830b9e4..78eb17d0 100644 --- a/api/contact.js +++ b/api/contact.js @@ -16,22 +16,17 @@ const { AIRTABLE_API_KEY, AIRTABLE_BASE } = process.env; const AIRTABLE_API_ENDPOINT = "https://api.airtable.com/v0/"; export default async (req, res) => { - // disable caching on both ends - res.setHeader("Cache-Control", "private, no-cache, no-store, must-revalidate"); - res.setHeader("Expires", 0); - res.setHeader("Pragma", "no-cache"); - - // permissive access control headers - res.setHeader("Access-Control-Allow-Methods", "POST"); - res.setHeader("Access-Control-Allow-Origin", "*"); - try { - // some rudimentary error handling + // permissive access control headers + res.setHeader("Access-Control-Allow-Methods", "POST"); + res.setHeader("Access-Control-Allow-Origin", "*"); + // disable caching on both ends + res.setHeader("Cache-Control", "private, no-cache, no-store, must-revalidate"); + res.setHeader("Expires", 0); + res.setHeader("Pragma", "no-cache"); + if (req.method !== "POST") { - throw new Error(`Method ${req.method} not allowed.`); - } - if (!AIRTABLE_API_KEY || !AIRTABLE_BASE) { - throw new Error("Airtable API credentials aren't set."); + return res.status(405).send(); // 405 Method Not Allowed } const { body } = req; @@ -60,7 +55,7 @@ export default async (req, res) => { } // return in JSON format - res.status(200).json({ success: true }); + return res.status(200).json({ success: true }); } catch (error) { console.error(error); @@ -73,7 +68,8 @@ export default async (req, res) => { await Sentry.flush(2000); } - res.status(400).json({ success: false, message: message }); + // 500 Internal Server Error + return res.status(500).json({ success: false, message: message }); } }; diff --git a/api/hits.js b/api/hits.js index ac5d4028..ed206f83 100644 --- a/api/hits.js +++ b/api/hits.js @@ -15,12 +15,12 @@ const BASE_URL = "https://jarv.is/"; export default async (req, res) => { try { - // some rudimentary error handling + // permissive access control headers + res.setHeader("Access-Control-Allow-Methods", "GET"); + res.setHeader("Access-Control-Allow-Origin", "*"); + if (req.method !== "GET") { - throw new Error(`Method ${req.method} not allowed.`); - } - if (!process.env.FAUNADB_SERVER_SECRET) { - throw new Error("Database credentials aren't set."); + return res.status(405).send(); // 405 Method Not Allowed } const client = new faunadb.Client({ @@ -52,10 +52,7 @@ export default async (req, res) => { res.setHeader("Pragma", "no-cache"); } - res.setHeader("Access-Control-Allow-Methods", "GET"); - res.setHeader("Access-Control-Allow-Origin", "*"); - - res.status(200).json(result); + return res.status(200).json(result); } catch (error) { console.error(error); @@ -65,7 +62,8 @@ export default async (req, res) => { const message = error instanceof Error ? error.message : "Unknown error."; - res.status(400).json({ success: false, message: message }); + // 500 Internal Server Error + return res.status(500).json({ success: false, message: message }); } }; diff --git a/api/projects.js b/api/projects.js index 57dd0b90..4ceddc29 100644 --- a/api/projects.js +++ b/api/projects.js @@ -8,12 +8,12 @@ Sentry.init({ export default async (req, res) => { try { - // some rudimentary error handling + // permissive access control headers + res.setHeader("Access-Control-Allow-Methods", "GET"); + res.setHeader("Access-Control-Allow-Origin", "*"); + if (req.method !== "GET") { - throw new Error(`Method ${req.method} not allowed.`); - } - if (!process.env.GH_PUBLIC_TOKEN) { - throw new Error("GitHub API credentials aren't set."); + return res.status(405).send(); // 405 Method Not Allowed } // allow custom limit, max. 24 results @@ -33,10 +33,8 @@ export default async (req, res) => { // let Vercel edge and browser cache results for 15 mins res.setHeader("Cache-Control", "public, max-age=900, s-maxage=900, stale-while-revalidate"); - res.setHeader("Access-Control-Allow-Methods", "GET"); - res.setHeader("Access-Control-Allow-Origin", "*"); - res.status(200).json(result); + return res.status(200).json(result); } catch (error) { console.error(error); @@ -46,7 +44,8 @@ export default async (req, res) => { const message = error instanceof Error ? error.message : "Unknown error."; - res.status(400).json({ success: false, message: message }); + // 500 Internal Server Error + return res.status(500).json({ success: false, message: message }); } }; diff --git a/api/tracks.js b/api/tracks.js index 5ebfddc6..355be0e0 100644 --- a/api/tracks.js +++ b/api/tracks.js @@ -23,17 +23,18 @@ const TOP_TRACKS_ENDPOINT = "https://api.spotify.com/v1/me/top/tracks?time_range export default async (req, res) => { try { - // some rudimentary error handling + // permissive access control headers + res.setHeader("Access-Control-Allow-Methods", "GET"); + res.setHeader("Access-Control-Allow-Origin", "*"); + if (req.method !== "GET") { - throw new Error(`Method ${req.method} not allowed.`); - } - if (!process.env.SPOTIFY_CLIENT_ID || !process.env.SPOTIFY_CLIENT_SECRET || !process.env.SPOTIFY_REFRESH_TOKEN) { - throw new Error("Spotify API credentials aren't set."); + return res.status(405).send(); // 405 Method Not Allowed } // default to top tracks let response; - // get currently playing track (/music/?now), otherwise top 10 tracks + + // get currently playing track (/api/tracks/?now), otherwise top 10 tracks if (typeof req.query.now !== "undefined") { response = await getNowPlaying(); @@ -46,10 +47,7 @@ export default async (req, res) => { res.setHeader("Cache-Control", "public, max-age=10800, s-maxage=10800, stale-while-revalidate"); } - res.setHeader("Access-Control-Allow-Methods", "GET"); - res.setHeader("Access-Control-Allow-Origin", "*"); - - res.status(200).json(response); + return res.status(200).json(response); } catch (error) { console.error(error); @@ -59,7 +57,8 @@ export default async (req, res) => { const message = error instanceof Error ? error.message : "Unknown error."; - res.status(400).json({ success: false, message: message }); + // 500 Internal Server Error + return res.status(500).json({ success: false, message: message }); } }; @@ -100,7 +99,7 @@ const getNowPlaying = async () => { if (active.is_playing === true && active.item) { return { isPlaying: active.is_playing, - artist: active.item.artists.map((_artist) => _artist.name).join(", "), + artist: active.item.artists.map((artist) => artist.name).join(", "), title: active.item.name, album: active.item.album.name, imageUrl: active.item.album.images ? active.item.album.images[0].url : undefined, @@ -126,7 +125,7 @@ const getTopTracks = async () => { const { items } = await response.json(); const tracks = items.map((track) => ({ - artist: track.artists.map((_artist) => _artist.name).join(", "), + artist: track.artists.map((artist) => artist.name).join(", "), title: track.name, album: track.album.name, imageUrl: track.album.images ? track.album.images[0].url : undefined, diff --git a/assets/js/index.js b/assets/js/index.js index 84ad567d..29e82726 100644 --- a/assets/js/index.js +++ b/assets/js/index.js @@ -1,6 +1,6 @@ import "./src/dark-mode.js"; import "./src/emoji.js"; -import "./src/counter.js"; +import "./src/hits.js"; import "./src/clipboard.js"; import "./src/anchor.js"; import "./src/contact.js"; diff --git a/assets/js/src/counter.js b/assets/js/src/hits.js similarity index 100% rename from assets/js/src/counter.js rename to assets/js/src/hits.js diff --git a/content/privacy/index.md b/content/privacy/index.md index 76ff0f05..f176f39c 100644 --- a/content/privacy/index.md +++ b/content/privacy/index.md @@ -13,7 +13,7 @@ Okay, this is an easy one. 😉 A simple hit counter on each page tallies an aggregate number of pageviews (i.e. `hits = hits + 1`). Individual views and identifying (or non-identifying) details are **never stored or logged**. -The [serverless function](https://github.com/jakejarvis/jarv.is/blob/main/api/hits.js) and [client script](https://github.com/jakejarvis/jarv.is/blob/main/assets/js/src/counter.js) are open source, and [snapshots of the database](https://github.com/jakejarvis/website-stats) are public. +The [serverless function](https://github.com/jakejarvis/jarv.is/blob/main/api/hits.js) and [client script](https://github.com/jakejarvis/jarv.is/blob/main/assets/js/src/hits.js) are open source, and [snapshots of the database](https://github.com/jakejarvis/website-stats) are public. {{< image src="images/fauna_hits.png" alt="The entire database schema." link="/privacy/images/fauna_hits.png" />}}