Skip to content

Commit

Permalink
Merge pull request #24 from betagouv/maud/grist_type_checking
Browse files Browse the repository at this point in the history
Data fetching and prerendering changes
  • Loading branch information
jillro authored Oct 24, 2024
2 parents eeed97f + b1a6e51 commit a6e3522
Show file tree
Hide file tree
Showing 9 changed files with 102 additions and 75 deletions.
14 changes: 4 additions & 10 deletions src/app/(container)/article/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,16 @@ import { MDXRemote } from "next-mdx-remote/rsc";
import { fr } from "@codegouvfr/react-dsfr";
import { getArticles } from "@/data/articles";
import Breadcrumb from "@codegouvfr/react-dsfr/Breadcrumb";
import { notFound } from "next/navigation";

export async function generateStaticParams() {
const records = await getArticles();

return records.map(({ slug }) => ({
slug,
}));
}
export const dynamic = "error";
export const dynamicParams = true;

async function getArticle(slug: string) {
const articles = await getArticles();
const article = articles.find(({ slug: _slug }) => _slug === slug);

if (!article) {
throw new Error(`Article not found: ${slug}`);
}
if (!article) return notFound();

return article;
}
Expand Down
18 changes: 10 additions & 8 deletions src/app/(container)/medicament/[CIS]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ import {
SubstanceNom,
} from "@/db/pdbmMySQL/types";
import { getAtcLabels } from "@/data/atc";
import { notFound } from "next/navigation";

export const dynamic = "error";
export const dynamicParams = true;

export async function generateMetadata(
{ params: { CIS } }: { params: { CIS: string } },
Expand All @@ -50,18 +54,16 @@ export async function generateMetadata(
};
}

export async function generateStaticParams(): Promise<{ CIS: string }[]> {
return liste_CIS_MVP.map((CIS) => ({
CIS,
}));
}

const getSpecialite = cache(async (CIS: string) => {
const specialite: Specialite = await pdbmMySQL
if (!liste_CIS_MVP.includes(CIS)) notFound();

const specialite: Specialite | undefined = await pdbmMySQL
.selectFrom("Specialite")
.where("SpecId", "=", CIS)
.selectAll()
.executeTakeFirstOrThrow();
.executeTakeFirst();

if (!specialite) return notFound();

const elements: SpecElement[] = await pdbmMySQL
.selectFrom("Element")
Expand Down
23 changes: 12 additions & 11 deletions src/app/(container)/parcourir/pathologies/[letter]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,8 @@ import { pdbmMySQL } from "@/db/pdbmMySQL";
import { notFound } from "next/navigation";
import Breadcrumb from "@codegouvfr/react-dsfr/Breadcrumb";

export async function generateStaticParams(): Promise<{ letter: string }[]> {
return pdbmMySQL
.selectFrom("Patho")
.select(({ fn, val }) =>
fn<string>("substr", ["NomPatho", val(1), val(1)]).as("letter"),
)
.orderBy("letter")
.groupBy("letter")
.execute();
}
export const dynamic = "error";
export const dynamicParams = true;

async function getPathologyPage(letter: string): Promise<Patho[]> {
return pdbmMySQL
Expand All @@ -25,7 +17,16 @@ async function getPathologyPage(letter: string): Promise<Patho[]> {
}

async function getLetters(): Promise<string[]> {
return (await generateStaticParams()).map((r) => r.letter);
return (
await pdbmMySQL
.selectFrom("Patho")
.select(({ fn, val }) =>
fn<string>("substr", ["NomPatho", val(1), val(1)]).as("letter"),
)
.orderBy("letter")
.groupBy("letter")
.execute()
).map((r) => r.letter);
}

export default async function Page({
Expand Down
7 changes: 3 additions & 4 deletions src/app/(container)/pathologie/[code]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ import { MedGroupSpecListList } from "@/components/MedGroupSpecList";
import Breadcrumb from "@codegouvfr/react-dsfr/Breadcrumb";
import { getPathologyDefinition } from "@/data/pathologies";

export async function generateStaticParams(): Promise<{ code: string }[]> {
return pdbmMySQL.selectFrom("Patho").select("codePatho as code").execute();
}
export const dynamic = "error";
export const dynamicParams = true;

async function getPatho(code: string): Promise<Patho> {
const patho = await pdbmMySQL
Expand All @@ -20,7 +19,7 @@ async function getPatho(code: string): Promise<Patho> {
.where("codePatho", "=", code)
.executeTakeFirst();

if (!patho) notFound();
if (!patho) return notFound();

return patho;
}
Expand Down
20 changes: 7 additions & 13 deletions src/app/(container)/substance/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,19 @@ import { pdbmMySQL } from "@/db/pdbmMySQL";
import { formatSpecName, groupSpecialites } from "@/displayUtils";
import liste_CIS_MVP from "@/liste_CIS_MVP.json";
import { Specialite, SubstanceNom } from "@/db/pdbmMySQL/types";
import { notFound } from "next/navigation";

export async function generateStaticParams(): Promise<{ id: string }[]> {
return (
await pdbmMySQL
.selectFrom("Subs_Nom")
.leftJoin("Composant", "Subs_Nom.SubsId", "Composant.SubsId")
.where("Composant.SpecId", "in", liste_CIS_MVP)
.select("Subs_Nom.NomId as id")
.distinct()
.execute()
).map(({ id }) => ({ id: id.trim() }));
}
export const dynamic = "error";
export const dynamicParams = true;

async function getSubstance(id: string) {
const substance: SubstanceNom = await pdbmMySQL
const substance: SubstanceNom | undefined = await pdbmMySQL
.selectFrom("Subs_Nom")
.where("NomId", "=", id)
.selectAll()
.executeTakeFirstOrThrow();
.executeTakeFirst();

if (!substance) return notFound();

const specialites: Specialite[] = await pdbmMySQL
.selectFrom("Specialite")
Expand Down
27 changes: 11 additions & 16 deletions src/data/articles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,21 @@ import "server-only";
import { getGristTableData } from "@/data/grist";
import slugify from "slugify";

type ArticleRecord = {
Titre: string;
Source: string;
Contenu: string;
Theme: string;
};

export async function getArticles() {
const records = (await getGristTableData("Articles")) as {
id: number;
fields: ArticleRecord;
}[];
const records = await getGristTableData("Articles", [
"Titre",
"Source",
"Contenu",
"Theme",
]);

return records.map(({ fields }) => {
return {
slug: slugify(fields.Titre, { lower: true, strict: true }),
title: fields.Titre,
source: fields.Source,
content: fields.Contenu,
category: fields.Theme,
slug: slugify(fields.Titre as string, { lower: true, strict: true }),
title: fields.Titre as string,
source: fields.Source as string,
content: fields.Contenu as string,
category: fields.Theme as string,
};
});
}
22 changes: 16 additions & 6 deletions src/data/atc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@ import atcOfficialLabels from "@/data/ATC 2024 02 15.json";
import { getGristTableData } from "@/data/grist";

async function getAtcLabel1(code: string): Promise<string> {
const data = await getGristTableData("Table_Niveau_1");
const data = await getGristTableData("Table_Niveau_1", [
"Lettre_1_ATC_1",
"Libelles_niveau_1",
"Definition_Classe",
]);
data[0].fields;
const record = data.find(
(record: any) => record.fields.Lettre_1_ATC_1 === code.slice(0, 1),
(record) => record.fields.Lettre_1_ATC_1 === code.slice(0, 1),
);
if (!record) {
throw new Error(`ATC code not found: ${code.slice(0, 1)}`);
Expand All @@ -15,17 +20,22 @@ async function getAtcLabel1(code: string): Promise<string> {
}

async function getAtcLabel2(code: string): Promise<string> {
const atcData = await getGristTableData("Table_Niveau_2");
const atcData = await getGristTableData("Table_Niveau_2", [
"Libelles_niveau_2",
"Lettre_2_ATC2",
]);
const record = atcData.find(
(record: any) => record.fields.Lettre_2_ATC2 === code.slice(0, 3),
(record) => record.fields.Lettre_2_ATC2 === code.slice(0, 3),
);
if (!record) {
throw new Error(`ATC code not found: ${code.slice(0, 3)}`);
}

const libeleId = record.fields.Libelles_niveau_2;
const libeleData = await getGristTableData("Intitules_possibles");
const libeleRecord = libeleData.find((record: any) => record.id === libeleId);
const libeleData = await getGristTableData("Intitules_possibles", [
"Libelles_niveau_2",
]);
const libeleRecord = libeleData.find((record) => record.id === libeleId);

if (!libeleRecord) {
throw new Error(`ATC code not found: ${code.slice(0, 3)}`);
Expand Down
39 changes: 34 additions & 5 deletions src/data/grist.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,45 @@
import "server-only";
import { unstable_cache } from "next/cache";

export const getGristTableData = unstable_cache(async function (
function matchesFields<F extends string[]>(
record: Record<string, string | number>,
fields: F,
): record is Record<F[number], string | number> {
return fields.every((key) => key in record);
}

export const getGristTableData = async function <F extends string>(
tableId: string,
): Promise<{ id: number; fields: Record<string, string | number> }[]> {
fields: F[],
): Promise<{ id: number; fields: Record<F, string | number> }[]> {
const response = await fetch(
`https://grist.numerique.gouv.fr/api/docs/${process.env.GRIST_DOC_ID}/tables/${tableId}/records`,
{
headers: {
Authorization: `Bearer ${process.env.GRIST_API_KEY}`,
Accept: "application/json",
},
},
);
return (await response.json()).records;
});

const body = await response.json();
if ("error" in body) {
throw Error(`Grist error: ${body.error}`);
}

const data = body.records as {
id: number;
fields: Record<string, string>;
}[];

if (!data) {
throw Error(`No Grist data for table ${tableId}.`);
}

if (data.length && !matchesFields(data[0].fields, fields)) {
throw Error(
`Grist data for table ${tableId} does not match expected fields.`,
);
}

return data;
};
7 changes: 5 additions & 2 deletions src/data/pathologies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ import { getGristTableData } from "@/data/grist";
export async function getPathologyDefinition(
code: `${number}`,
): Promise<string> {
const data = await getGristTableData("Pathologies");
const data = await getGristTableData("Pathologies", [
"codePatho",
"Definition_pathologie",
]);
const record = data.find(
(record: any) => record.fields.codePatho === Number(code),
(record) => record.fields.codePatho === Number(code),
);
if (!record) {
throw new Error(`Pathology code not found: ${code}`);
Expand Down

0 comments on commit a6e3522

Please sign in to comment.