Skip to content

Commit

Permalink
feat: start api refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
henb13 committed Oct 30, 2023
1 parent ba34af5 commit 8676fa9
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 52 deletions.
2 changes: 1 addition & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"web-vitals": "^3.5.0"
},
"scripts": {
"start": "vite",
"start": "vite --open",
"build": "tsc && vite build",
"serve": "vite preview",
"test": "vitest",
Expand Down
3 changes: 3 additions & 0 deletions server/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ SPOTIFY_CLIENT_SECRET=YOUR_SECRET
SUPABASE_PROJECT_URL=YOUR_SUPABASE_PROJECT_URL
SUPABASE_API_KEY=YOUR_SUPABASE_API_KEY

SUPABASE_PROJECT_URL_DEV=YOUR_SUPABASE_DEV_PROJECT_URL
SUPABASE_API_KEY_DEV=YOUR_SUPABASE_DEV_API_KEY

PORT=3000

# How often to compare with Spotify API in minutes
Expand Down
88 changes: 52 additions & 36 deletions server/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,61 +2,77 @@ const express = require("express");
const router = express.Router();
const DB = require("../db/db");
const pool = require("../db/connect");
const { mockResponse } = require("./__mocks__/mockResponse");
const NodeCache = require("node-cache");

router.use(express.json());
require("dotenv").config();

let missingEpisodesCache;
let shortenedEpisodesCache;
let lastCheckedCache;
const CRON_INTERVAL = parseInt(process.env.CRON_INTERVAL, 10) ?? 30;

const { CRON_INTERVAL, USE_MOCK_DATA, NODE_ENV } = process.env;
const isDev = NODE_ENV === "development";
const cache = new NodeCache({ stdTTL: CRON_INTERVAL * 60 });

const KEYS = {
missingEpisodes: "missing-episodes",
shortenedEpisodes: "shortened-episodes",
lastChecked: "last-checked",
};

//TODO: Change when client in prod
const allowOrigin = isDev ? "http://localhost:3000" : "https://not-yet-prod-domain.com";
const allowOrigin =
process.env.NODE_ENV === "development"
? "http://localhost:3000"
: "https://not-yet-prod-domain.com";

router.get("/api/episodes", async (_, res) => {
if (isDev && USE_MOCK_DATA === "true") {
return res.json(mockResponse);
}

const timeSinceLastCheckedDbInMins =
lastCheckedCache && (Date.now() - lastCheckedCache) / 1000 / 60;
console.info("request fired");

const cacheTimeLeftSecs = Math.floor((CRON_INTERVAL - timeSinceLastCheckedDbInMins) * 60);
const buffer = 120;
const maxAgeMs = Math.min(
cache.getTtl(KEYS.missingEpisodes),
cache.getTtl(KEYS.shortenedEpisodes)
);
const maxAge = maxAgeMs ? Math.round((new Date(maxAgeMs).getTime() - Date.now()) / 1000) : 0;

res.header({
"cache-control": `no-transform, max-age=${cacheTimeLeftSecs + buffer || 1}`,
"cache-control": `no-transform, max-age=${maxAge}`,
"Access-Control-Allow-Origin": allowOrigin,
});

if (
!missingEpisodesCache ||
!shortenedEpisodesCache ||
timeSinceLastCheckedDbInMins > CRON_INTERVAL
) {
await (async () => {
const client = await pool.connect();
const db = DB(client);
try {
missingEpisodesCache = await db.getMissingEpisodes();
shortenedEpisodesCache = await db.getShortenedEpisodes();
lastCheckedCache = await db.getLastChecked();
console.info("db queried and cache updated");
} finally {
client.release();
const missingCacheExists = cache.has(KEYS.missingEpisodes);
const shortenedCacheExists = cache.has(KEYS.shortenedEpisodes);
const lastCheckedExists = cache.has(KEYS.lastChecked);

if (!missingCacheExists || !shortenedCacheExists || !lastCheckedExists) {
const client = await pool.connect();
const db = DB(client);

try {
if (!missingCacheExists) {
const missingEpisodes = await db.getMissingEpisodes();
cache.set(KEYS.missingEpisodes, missingEpisodes);
}
})().catch((err) => console.error(err.message));

if (!shortenedCacheExists) {
const shortenedEpisodes = await db.getshortenedEpisodes();
cache.set(KEYS.shortenedEpisodes, shortenedEpisodes);
}

if (!lastCheckedExists) {
const lastChecked = await db.getlastChecked();
cache.set(KEYS.lastChecked, lastChecked);
}

console.info("db queried and cache updated");
} catch (error) {
console.error(error.message);
} finally {
client.release();
}
}

console.info("request fired");
res.json({
missingEpisodes: missingEpisodesCache,
shortenedEpisodes: shortenedEpisodesCache,
lastCheckedInMs: lastCheckedCache,
missingEpisodes: cache.get(KEYS.missingEpisodes),
shortenedEpisodes: cache.get(KEYS.shortenedEpisodes),
lastCheckedInMs: cache.get(KEYS.lastChecked),
});
});

Expand Down
25 changes: 10 additions & 15 deletions server/lib/getSpotifyEpisodes.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
/* eslint-disable no-undef */
const SpotifyWebApi = require("spotify-web-api-node");
require("dotenv").config();
const initializeSpotifyClient = require("./spotify-client");

const JRE_SHOW_ID = "4rOoJ6Egrf8K2IrywzwOMk";

async function getSpotifyEpisodes() {
try {
const spotifyApi = new SpotifyWebApi({
clientId: process.env.SPOTIFY_CLIENT_ID,
clientSecret: process.env.SPOTIFY_CLIENT_SECRET,
});
let spotifyClient;

const tokenData = await spotifyApi.clientCredentialsGrant();
console.info(`The access token expires in ${tokenData.body["expires_in"]}`);
spotifyApi.setAccessToken(tokenData.body["access_token"]);
async function getSpotifyEpisodes() {
if (!spotifyClient) {
spotifyClient = await initializeSpotifyClient();
}

try {
const spotifyEpisodes = [];
const episodes = await spotifyApi.getShowEpisodes(JRE_SHOW_ID, {
const episodes = await spotifyClient.getShowEpisodes(JRE_SHOW_ID, {
market: "US",
limit: 50,
offset: spotifyEpisodes.length,
Expand All @@ -33,7 +28,7 @@ async function getSpotifyEpisodes() {
const totalEpisodes = episodes.body.total;

while (spotifyEpisodes.length < totalEpisodes) {
const episodes = await spotifyApi.getShowEpisodes(JRE_SHOW_ID, {
const episodes = await spotifyClient.getShowEpisodes(JRE_SHOW_ID, {
market: "US",
limit: 50,
offset: spotifyEpisodes.length,
Expand All @@ -50,7 +45,7 @@ async function getSpotifyEpisodes() {

return spotifyEpisodes;
} catch (err) {
console.error(err.message);
console.error("something went wrong fetching from Spotify: ", err.message);
}
}

Expand Down
33 changes: 33 additions & 0 deletions server/lib/spotify-client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const SpotifyWebApi = require("spotify-web-api-node");
require("dotenv").config();

async function initializeSpotifyClient() {
try {
const spotifyClient = new SpotifyWebApi({
clientId: process.env.SPOTIFY_CLIENT_ID,
clientSecret: process.env.SPOTIFY_CLIENT_SECRET,
});

const data = await spotifyClient.clientCredentialsGrant();
const accessToken = data.body["access_token"];
const refreshToken = data.body["refresh_token"];
const expiresIn = data.body["expires_in"];

spotifyClient.setAccessToken(accessToken);
spotifyClient.setRefreshToken(refreshToken);

setInterval(async () => {
const data = await spotifyClient.refreshAccessToken();
const accessToken = data.body["access_token"];
spotifyClient.setAccessToken(accessToken);

console.log("The access token has been refreshed!");
}, expiresIn - 60 * 1000);

spotifyClient.setAccessToken(data.body["access_token"]);
} catch (error) {
return null;
}
}

module.exports = { initializeSpotifyClient };
20 changes: 20 additions & 0 deletions server/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"express-rate-limit": "^7.1.3",
"express-slow-down": "^2.0.0",
"helmet": "^7.0.0",
"node-cache": "^5.1.2",
"node-schedule": "^2.1.1",
"npm-run-all": "^4.1.5",
"pg": "^8.11.3",
Expand Down

0 comments on commit 8676fa9

Please sign in to comment.