static data generation with cache

This commit is contained in:
Owen Rees
2023-06-13 20:36:48 +02:00
parent db266e6ef3
commit 112c257321
9 changed files with 117 additions and 118 deletions
+3 -29
View File
@@ -1,44 +1,18 @@
import clientPromise from "@/lib/mongodb"; import clientPromise from "@/lib/mongodb";
import { dateOrderingFrance } from "@/utils/dbDateOrdering";
// TODO collate only the country of France - redundant for now but will be needed when new countries are added
// probably do this by passing the country name as parameter
/** /**
* Tournament data API endpoint * Tournament data API endpoint
* @route /api/tournaments/france * @route /api/tournaments/france
* @internal * @internal
*/ */
// TODO cache this result for 24 hours - using cache()
export async function GET() { export async function GET() {
try { try {
const client = await clientPromise; const client = await clientPromise;
const db = client.db("tournamentsFranceDB"); const db = client.db("tournamentsFranceDB");
/** const data = await dateOrderingFrance(db);
* Converts date from string into a date to allow ordering
*/
// TODO add into a middleware?
const data = await db
.collection("tournaments")
.aggregate([
{
$addFields: {
dateParts: {
$dateFromString: {
dateString: "$date",
format: "%d/%m/%Y",
},
},
},
},
{
$sort: {
dateParts: 1,
},
},
{
$unset: "dateParts",
},
])
.toArray();
return new Response(JSON.stringify(data), { status: 200 }); return new Response(JSON.stringify(data), { status: 200 });
} catch (error) { } catch (error) {
+10 -1
View File
@@ -1,11 +1,20 @@
import Link from "next/link"; import Link from "next/link";
import Image from "next/image";
import Layout from "@/components/Layout"; import Layout from "@/components/Layout";
import bgImage from "@/public/images/map-bg.jpg";
export default function Home() { export default function Home() {
return ( return (
<Layout> <Layout>
<header className="grid h-[calc(100%-153px)] md:h-[calc(100%-173px)] place-items-center"> <header className="grid h-[calc(100%-153px)] md:h-[calc(100%-173px)] place-items-center">
<div className="relative h-full w-full bg-[url('/images/map-bg.jpg')] bg-cover bg-center brightness-[0.2]"></div> <div className="relative h-full w-full brightness-[0.2]">
<Image
src={bgImage}
alt="Background image of France"
fill={true}
style={{ objectFit: "cover" }}
/>
</div>
<div className="absolute text-center"> <div className="absolute text-center">
<h1 className="text-5xl p-5">Echecs France</h1> <h1 className="text-5xl p-5">Echecs France</h1>
<h2 className="text-3xl p-5"> <h2 className="text-3xl p-5">
+20 -7
View File
@@ -1,11 +1,9 @@
import { Tournament } from "@/types"; import clientPromise from "@/lib/mongodb";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import Layout from "@/components/Layout"; import Layout from "@/components/Layout";
import TournamentTable from "@/components/TournamentTable"; import TournamentTable from "@/components/TournamentTable";
import getTournaments from "@/utils/getTournamentData"; import { dateOrderingFrance } from "@/utils/dbDateOrdering";
// TODO can these functions be put into a custom hook?
/** /**
* Imports the tournament map component, ensuring CSR only. * Imports the tournament map component, ensuring CSR only.
* @remarks SSR is not supported by react-leaflet * @remarks SSR is not supported by react-leaflet
@@ -19,17 +17,32 @@ const TournamentMap = dynamic(() => import("@/components/TournamentMap"), {
), ),
}); });
export const revalidate = 86400; // cache for 24 hours
const getTournaments = async () => {
try {
const client = await clientPromise;
const db = client.db("tournamentsFranceDB");
const data = await dateOrderingFrance(db);
return JSON.stringify(data);
} catch (error) {
throw new Error("Error fetching tournament data");
}
};
export default async function Tournaments() { export default async function Tournaments() {
const tournamentData = await getTournaments("france"); const tournamentData = await getTournaments();
return ( return (
<Layout> <Layout>
<main className="grid lg:grid-cols-2"> <main className="grid lg:grid-cols-2">
<div className=""> <div className="">
<TournamentMap tournamentData={tournamentData} /> <TournamentMap tournamentData={JSON.parse(tournamentData)} />
</div> </div>
<div className="bg-white dark:bg-gray-800 lg:overflow-y-auto"> <div className="bg-white dark:bg-gray-800 lg:overflow-y-auto">
<TournamentTable tournamentData={tournamentData} /> <TournamentTable tournamentData={JSON.parse(tournamentData)} />
</div> </div>
</main> </main>
</Layout> </Layout>
+54 -53
View File
@@ -1,54 +1,55 @@
import { Tournament } from "@/types"; // import { Tournament } from "@/types";
import getTournaments from "@/utils/getTournamentData"; // import getTournaments from "@/utils/getTournamentData";
import { createLayerGroups } from "@/utils/layerGroups"; // import { createLayerGroups } from "@/utils/layerGroups";
//
// TODO cy.wrap() for promise resolution // // TODO rewrite this test suite since I no longer use fetch for the map
// TODO DRY - variables and data fetching can be done before tests under the first describe block // // TODO add tests for the API once it is active
describe("Unit tests of utils directory", () => { // describe("Unit tests of utils directory", () => {
describe("Retrieve tournament data from DB", () => { // describe("Retrieve tournament data from DB", () => {
let response: Tournament[]; // let response: Tournament[];
let results: Tournament[]; // let results: Tournament[];
//
describe("France", () => { // describe("France", () => {
before(async () => { // before(async () => {
response = await getTournaments("france"); // response = await getTournaments("france");
results = response.splice(0, 5); // results = response.splice(0, 5);
}); // });
//
it("log first 5 results", () => { // it("log first 5 results", () => {
results.forEach((result) => cy.log(JSON.stringify(result))); // results.forEach((result) => cy.log(JSON.stringify(result)));
}); // });
//
it("check tournament urls are active", () => { // it("check tournament urls are active", () => {
results.forEach((result) => cy.request(result.url)); // results.forEach((result) => cy.request(result.url));
}); // });
}); // });
}); // });
//
describe("Create layer groups for map markers", () => { // describe("Create layer groups for map markers", () => {
let response: Tournament[]; // let response: Tournament[];
let results: Tournament[]; // let results: Tournament[];
describe("France", () => { // describe("France", () => {
const timeControls = [ // const timeControls = [
{ name: "Cadence Lente", colour: "green" }, // { name: "Cadence Lente", colour: "green" },
{ name: "Rapide", colour: "blue" }, // { name: "Rapide", colour: "blue" },
{ name: "Blitz", colour: "yellow" }, // { name: "Blitz", colour: "yellow" },
{ name: "1h KO", colour: "red" }, // { name: "1h KO", colour: "red" },
]; // ];
//
before(async () => { // before(async () => {
response = await getTournaments("france"); // response = await getTournaments("france");
results = response.splice(0, 5); // results = response.splice(0, 5);
}); // });
//
it("generate layer groups", () => { // it("generate layer groups", () => {
const result = timeControls.map((timeControl) => { // const result = timeControls.map((timeControl) => {
return createLayerGroups(timeControl.name, timeControl.colour, { // return createLayerGroups(timeControl.name, timeControl.colour, {
tournamentData: results, // tournamentData: results,
}); // });
}); // });
cy.wrap(result.length).should("be.greaterThan", 0); // cy.wrap(result.length).should("be.greaterThan", 0);
}); // });
}); // });
}); // });
}); // });
-3
View File
@@ -1,7 +1,4 @@
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const dotenvExpand = require("dotenv-expand");
dotenvExpand.expand({ parsed: { ...process.env } });
const nextConfig = {}; const nextConfig = {};
module.exports = nextConfig; module.exports = nextConfig;
-9
View File
@@ -12,7 +12,6 @@
"@types/react": "18.2.6", "@types/react": "18.2.6",
"@types/react-dom": "18.2.4", "@types/react-dom": "18.2.4",
"autoprefixer": "10.4.14", "autoprefixer": "10.4.14",
"dotenv-expand": "^10.0.0",
"eslint": "8.41.0", "eslint": "8.41.0",
"eslint-config-next": "13.4.3", "eslint-config-next": "13.4.3",
"leaflet": "^1.9.4", "leaflet": "^1.9.4",
@@ -3520,14 +3519,6 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/dotenv-expand": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz",
"integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==",
"engines": {
"node": ">=12"
}
},
"node_modules/ecc-jsbn": { "node_modules/ecc-jsbn": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
-1
View File
@@ -17,7 +17,6 @@
"@types/react": "18.2.6", "@types/react": "18.2.6",
"@types/react-dom": "18.2.4", "@types/react-dom": "18.2.4",
"autoprefixer": "10.4.14", "autoprefixer": "10.4.14",
"dotenv-expand": "^10.0.0",
"eslint": "8.41.0", "eslint": "8.41.0",
"eslint-config-next": "13.4.3", "eslint-config-next": "13.4.3",
"leaflet": "^1.9.4", "leaflet": "^1.9.4",
+30
View File
@@ -0,0 +1,30 @@
import { Db } from "mongodb";
/**
* Converts date from string into a date to allow ordering
*/
export const dateOrderingFrance = async (db: Db) => {
return await db
.collection("tournaments")
.aggregate([
{
$addFields: {
dateParts: {
$dateFromString: {
dateString: "$date",
format: "%d/%m/%Y",
},
},
},
},
{
$sort: {
dateParts: 1,
},
},
{
$unset: "dateParts",
},
])
.toArray();
};
-15
View File
@@ -1,15 +0,0 @@
/**
* Retrieves tournament data from /api/tournaments/:country
* @remarks The result is cached for the revalidation period in seconds
*/
export default async function getTournaments(country: string) {
const server = process.env.NEXT_PUBLIC_SITE_URL;
const res = await fetch(`${server}/api/tournaments/${country}`, {
next: { revalidate: 300 },
});
if (res.status !== 200) {
throw new Error("Failed to fetch tournament data");
}
return await res.json();
}