Migrate fully to sqlite based backend
This commit is contained in:
@@ -37,3 +37,7 @@ next-env.d.ts
|
||||
|
||||
# fetchable assets
|
||||
public/photos
|
||||
|
||||
# sqlite cache
|
||||
main.db-shm
|
||||
main.db-wal
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import { NumistaType } from "@/types/numista";
|
||||
import { readFile, readdir } from "fs/promises";
|
||||
import path from "path";
|
||||
|
||||
export const getCountryBanknotes = async (country: string) => {
|
||||
try {
|
||||
const banknoteIds = await readdir(path.join("./public/banknotes", country));
|
||||
|
||||
const banknotes: NumistaType[] = [];
|
||||
|
||||
for (const banknoteId of banknoteIds) {
|
||||
const banknote = await readFile(
|
||||
path.join("./public/banknotes", country, banknoteId),
|
||||
{ encoding: "utf-8" },
|
||||
);
|
||||
|
||||
banknotes.push(JSON.parse(banknote));
|
||||
}
|
||||
|
||||
return banknotes;
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
@@ -1,24 +0,0 @@
|
||||
import { NumistaType } from "@/types/numista";
|
||||
import { readFile, readdir } from "fs/promises";
|
||||
import path from "path";
|
||||
|
||||
export const getCountryCoins = async (country: string) => {
|
||||
try {
|
||||
const coinIds = await readdir(path.join("./public/coins", country));
|
||||
|
||||
const coins: NumistaType[] = [];
|
||||
|
||||
for (const coinId of coinIds) {
|
||||
const coin = await readFile(
|
||||
path.join("./public/coins", country, coinId),
|
||||
{ encoding: "utf-8" },
|
||||
);
|
||||
|
||||
coins.push(JSON.parse(coin));
|
||||
}
|
||||
|
||||
return coins;
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
import { prisma } from "@/api";
|
||||
|
||||
export const getCountryCounts = async (category?: "coins" | "banknotes") => {
|
||||
const countryBanknoteCounts =
|
||||
category !== "coins"
|
||||
? await prisma.numistaType.groupBy({
|
||||
by: ["issuer_code"],
|
||||
_sum: {
|
||||
count: true,
|
||||
},
|
||||
_count: true,
|
||||
where: {
|
||||
category: {
|
||||
equals: "banknote",
|
||||
},
|
||||
},
|
||||
})
|
||||
: [];
|
||||
|
||||
const countryCoinCounts =
|
||||
category !== "banknotes"
|
||||
? await prisma.numistaType.groupBy({
|
||||
by: ["issuer_code"],
|
||||
_sum: {
|
||||
count: true,
|
||||
},
|
||||
_count: true,
|
||||
where: {
|
||||
category: {
|
||||
not: "banknote",
|
||||
},
|
||||
},
|
||||
})
|
||||
: [];
|
||||
|
||||
const countries = await prisma.issuer.findMany({
|
||||
orderBy: {
|
||||
name: "asc",
|
||||
},
|
||||
});
|
||||
|
||||
const map = countries.map((country) => {
|
||||
const banknoteCounts = countryBanknoteCounts.find(
|
||||
({ issuer_code }) => issuer_code === country.code,
|
||||
);
|
||||
const coinCounts = countryCoinCounts.find(
|
||||
({ issuer_code }) => issuer_code === country.code,
|
||||
);
|
||||
|
||||
return {
|
||||
...country,
|
||||
...(banknoteCounts && {
|
||||
banknotes: {
|
||||
sum: banknoteCounts?._sum?.count,
|
||||
count: banknoteCounts?._count,
|
||||
},
|
||||
}),
|
||||
...(coinCounts && {
|
||||
coins: {
|
||||
sum: coinCounts?._sum?.count,
|
||||
count: coinCounts?._count,
|
||||
},
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
return map;
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
export const prisma = new PrismaClient();
|
||||
@@ -0,0 +1,14 @@
|
||||
import { NumistaType } from "@/types/numista";
|
||||
import { prisma } from "@/api";
|
||||
import { include } from "./relations";
|
||||
|
||||
export const getType = async (id: number) => {
|
||||
const type = await prisma.numistaType.findUnique({
|
||||
include,
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
return type as unknown as NumistaType | null;
|
||||
};
|
||||
@@ -0,0 +1,165 @@
|
||||
import { NumistaCategory, NumistaType } from "@/types/numista";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { prisma } from "@/api";
|
||||
import { include } from "./relations";
|
||||
|
||||
type Props = {
|
||||
search?: string;
|
||||
category?: Exclude<NumistaCategory, "exonumia">;
|
||||
country?: string;
|
||||
currency?: string;
|
||||
faceValue?: number;
|
||||
yearRange?: [number, number];
|
||||
special?: "yes" | "no" | "both";
|
||||
};
|
||||
|
||||
export const getTypes = async ({
|
||||
search,
|
||||
category,
|
||||
country,
|
||||
currency,
|
||||
faceValue,
|
||||
yearRange,
|
||||
special,
|
||||
}: Props) => {
|
||||
const AND: Prisma.NumistaTypeWhereInput[] = [];
|
||||
const OR: Prisma.NumistaTypeWhereInput[] = [];
|
||||
|
||||
if (category === "banknote") {
|
||||
AND.push({
|
||||
category,
|
||||
});
|
||||
}
|
||||
|
||||
if (category === "coin") {
|
||||
AND.push({
|
||||
category: {
|
||||
not: "banknote",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (country) {
|
||||
AND.push({
|
||||
issuer: {
|
||||
OR: [
|
||||
{
|
||||
code: country,
|
||||
},
|
||||
{
|
||||
name: country,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (currency) {
|
||||
AND.push({
|
||||
value: {
|
||||
currency: {
|
||||
OR: [{ name: currency }, { full_name: currency }],
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (faceValue) {
|
||||
AND.push({
|
||||
value: {
|
||||
numeric_value: {
|
||||
equals: faceValue,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (yearRange?.length === 2) {
|
||||
AND.push({
|
||||
AND: {
|
||||
min_year: {
|
||||
lte: yearRange[0],
|
||||
},
|
||||
max_year: {
|
||||
gte: yearRange[1],
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (special === "yes") {
|
||||
AND.push({
|
||||
NOT: {
|
||||
type: {
|
||||
contains: "Standard",
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (special === "no") {
|
||||
AND.push({
|
||||
type: {
|
||||
contains: "Standard",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (search) {
|
||||
OR.push({ title: { contains: search } });
|
||||
OR.push({
|
||||
issuer: {
|
||||
OR: [
|
||||
{
|
||||
code: country,
|
||||
},
|
||||
{
|
||||
name: country,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
OR.push({
|
||||
value: {
|
||||
currency: {
|
||||
OR: [{ name: currency }, { full_name: currency }],
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (AND.length) {
|
||||
OR.push({ AND });
|
||||
}
|
||||
|
||||
const types = await prisma.numistaType.findMany({
|
||||
include,
|
||||
...(OR.length > 0
|
||||
? {
|
||||
where: {
|
||||
OR,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
orderBy: [
|
||||
{
|
||||
issuer: {
|
||||
name: "asc",
|
||||
},
|
||||
},
|
||||
{
|
||||
min_year: "asc",
|
||||
},
|
||||
{
|
||||
max_year: "asc",
|
||||
},
|
||||
{
|
||||
value: {
|
||||
numeric_value: "asc",
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return types as unknown as NumistaType[];
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Prisma } from "@prisma/client";
|
||||
|
||||
export const include: Prisma.NumistaTypeInclude = {
|
||||
issuer: true,
|
||||
obverse: true,
|
||||
reverse: true,
|
||||
edge: true,
|
||||
value: {
|
||||
include: {
|
||||
currency: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
import { getCountryCounts } from "@/api/countries/get-country-counts";
|
||||
import type { NextApiHandler } from "next";
|
||||
|
||||
const handler: NextApiHandler = async (_req, res) => {
|
||||
try {
|
||||
const countryCounts = await getCountryCounts();
|
||||
|
||||
res.status(200).json(countryCounts);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
res.status(500).json({
|
||||
message: `Something went wrong`,
|
||||
error: String(error),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default handler;
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { NextApiHandler } from "next";
|
||||
import { readFile, readdir } from "fs/promises";
|
||||
import { readFile } from "fs/promises";
|
||||
import path from "path";
|
||||
import { spawn } from "child_process";
|
||||
import { getType } from "@/api/types/get-type";
|
||||
|
||||
const asyncSpawn = (command: string, args: string[]) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -22,19 +23,7 @@ const asyncSpawn = (command: string, args: string[]) => {
|
||||
});
|
||||
};
|
||||
|
||||
const getCoinPath = async (id: string) => {
|
||||
const countries = await readdir("./public/coins");
|
||||
for (const country of countries) {
|
||||
const coins = await readdir(path.join("./public/coins", country));
|
||||
for (const coin of coins) {
|
||||
if (coin === id + ".json") {
|
||||
return path.join("./public/coins", country, coin);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getPreviousResult = async (id: string) => {
|
||||
const getPreviousResult = async (id: number) => {
|
||||
try {
|
||||
return await readFile(path.join("./public/photos/joined", id + ".jpg"));
|
||||
} catch {
|
||||
@@ -42,35 +31,51 @@ const getPreviousResult = async (id: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
const generateImage = async (id: string) => {
|
||||
const generateImage = async (id: number) => {
|
||||
const previousResult = await getPreviousResult(id);
|
||||
|
||||
if (previousResult) {
|
||||
return previousResult;
|
||||
}
|
||||
|
||||
const coinPath = await getCoinPath(id);
|
||||
const coin = await getType(id);
|
||||
|
||||
if (!coinPath) {
|
||||
if (!coin) {
|
||||
return;
|
||||
}
|
||||
|
||||
const coin = JSON.parse(await readFile(coinPath, { encoding: "utf-8" }));
|
||||
if (coin.obverse?.picture) {
|
||||
await asyncSpawn("convert", [
|
||||
path.join("./public", coin.obverse.picture),
|
||||
"-resize",
|
||||
"256x256!",
|
||||
path.join("./public/photos/joined", id + "-obverse.jpg"),
|
||||
]);
|
||||
} else {
|
||||
await asyncSpawn("convert", [
|
||||
"-size",
|
||||
"512x64",
|
||||
"xc:" + coin.color,
|
||||
path.join("./public/photos/joined", id + "-edge.jpg"),
|
||||
]);
|
||||
}
|
||||
|
||||
await asyncSpawn("convert", [
|
||||
path.join("./public", coin.obverse.picture),
|
||||
"-resize",
|
||||
"256x256!",
|
||||
path.join("./public/photos/joined", id + "-obverse.jpg"),
|
||||
]);
|
||||
|
||||
await asyncSpawn("convert", [
|
||||
path.join("./public", coin.reverse.picture),
|
||||
"-resize",
|
||||
"256x256!",
|
||||
...(coin.orientation === "coin" ? ["-rotate", "180"] : []),
|
||||
path.join("./public/photos/joined", id + "-reverse.jpg"),
|
||||
]);
|
||||
if (coin.reverse?.picture) {
|
||||
await asyncSpawn("convert", [
|
||||
path.join("./public", coin.reverse.picture),
|
||||
"-resize",
|
||||
"256x256!",
|
||||
...(coin.orientation === "coin" ? ["-rotate", "180"] : []),
|
||||
path.join("./public/photos/joined", id + "-reverse.jpg"),
|
||||
]);
|
||||
} else {
|
||||
await asyncSpawn("convert", [
|
||||
"-size",
|
||||
"512x64",
|
||||
"xc:" + coin.color,
|
||||
path.join("./public/photos/joined", id + "-edge.jpg"),
|
||||
]);
|
||||
}
|
||||
|
||||
if (coin.edge?.picture) {
|
||||
await asyncSpawn("convert", [
|
||||
@@ -108,7 +113,7 @@ const generateImage = async (id: string) => {
|
||||
};
|
||||
|
||||
const handler: NextApiHandler = async ({ query }, res) => {
|
||||
if (typeof query.id !== "string") {
|
||||
if (!Number(query.id)) {
|
||||
res.status(400).json({
|
||||
message: `Invalid id ${JSON.stringify(query.id)}`,
|
||||
});
|
||||
@@ -116,7 +121,7 @@ const handler: NextApiHandler = async ({ query }, res) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const image = await generateImage(query.id);
|
||||
const image = await generateImage(Number(query.id));
|
||||
|
||||
if (!image) {
|
||||
res.status(404).json({
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import { getTypes } from "@/api/types/get-types";
|
||||
import type { NextApiHandler } from "next";
|
||||
|
||||
const handler: NextApiHandler = async ({ query }, res) => {
|
||||
if (Array.isArray(query.search)) {
|
||||
res.status(400).json({
|
||||
message: `Invalid search ${JSON.stringify(query.search)}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(query.currency)) {
|
||||
res.status(400).json({
|
||||
message: `Invalid currency ${JSON.stringify(query.currency)}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (
|
||||
Array.isArray(query.faceValue) ||
|
||||
(query.faceValue && !Number(query.faceValue))
|
||||
) {
|
||||
res.status(400).json({
|
||||
message: `Invalid faceValue ${JSON.stringify(query.faceValue)}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (
|
||||
Array.isArray(query.category) ||
|
||||
(query.category && !["coin", "banknote"].includes(query.category))
|
||||
) {
|
||||
res.status(400).json({
|
||||
message: `Invalid category ${JSON.stringify(query.category)}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (
|
||||
Array.isArray(query.special) ||
|
||||
(query.special && !["yes", "no", "both"].includes(query.special))
|
||||
) {
|
||||
res.status(400).json({
|
||||
message: `Invalid special filter ${JSON.stringify(query.special)}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!Array.isArray(query.yearRange) ||
|
||||
query.yearRange.length !== 2 ||
|
||||
query.yearRange.some((year) => !Number(year))
|
||||
) {
|
||||
res.status(400).json({
|
||||
message: `Invalid year range ${JSON.stringify(query.yearRange)}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const coins = await getTypes({
|
||||
search: query.search,
|
||||
category: query.category as "coin" | "banknote" | undefined,
|
||||
faceValue: Number(query.faceValue),
|
||||
currency: query.currency,
|
||||
special: query.special as "yes" | "no" | "both" | undefined,
|
||||
yearRange: query.yearRange.map(Number) as [number, number],
|
||||
});
|
||||
|
||||
if (!coins.length) {
|
||||
return res.status(404).json(coins);
|
||||
}
|
||||
|
||||
res.status(200).json(coins);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
res.status(500).json({
|
||||
message: `Something went wrong`,
|
||||
error: String(error),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default handler;
|
||||
@@ -1,12 +1,10 @@
|
||||
import Head from "next/head";
|
||||
import { GetStaticPaths, GetStaticProps, NextPage } from "next";
|
||||
import { NumistaType } from "@/types/numista";
|
||||
import { readdir } from "fs/promises";
|
||||
import { Showcase } from "@/components/showcase";
|
||||
import { Header } from "@/components/header";
|
||||
import { getCountryBanknotes } from "@/api/banknotes/get-country-banknotes";
|
||||
import { getCountryCoins } from "@/api/coins/get-country-coins";
|
||||
import { sortTypes } from "@/core/utils/numista";
|
||||
import { getTypes } from "@/api/types/get-types";
|
||||
import { getCountryCounts } from "@/api/countries/get-country-counts";
|
||||
|
||||
type Props = {
|
||||
country: string;
|
||||
@@ -18,10 +16,13 @@ type Props = {
|
||||
const BanknotesPage: NextPage<Props> = ({ banknotes, ...props }) => (
|
||||
<>
|
||||
<Head>
|
||||
<title>Banknotes</title>
|
||||
<title>{props.country + " - Banknotes"}</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="An exhibition of my father's banknote collection"
|
||||
content={
|
||||
"An exhibition of my father's collection of banknotes from " +
|
||||
props.country
|
||||
}
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
@@ -43,9 +44,15 @@ export const getStaticProps: GetStaticProps<Props> = async ({ params }) => {
|
||||
};
|
||||
}
|
||||
|
||||
const coins = await getCountryCoins(params.country);
|
||||
const banknotes = await getCountryBanknotes(params.country);
|
||||
const coins = await getTypes({
|
||||
category: "coin",
|
||||
country: params.country,
|
||||
});
|
||||
|
||||
const banknotes = await getTypes({
|
||||
category: "banknote",
|
||||
country: params.country,
|
||||
});
|
||||
return {
|
||||
props: {
|
||||
country: params.country,
|
||||
@@ -54,17 +61,19 @@ export const getStaticProps: GetStaticProps<Props> = async ({ params }) => {
|
||||
(total, banknote) => total + (banknote.count ?? 1),
|
||||
0,
|
||||
),
|
||||
banknotes: sortTypes(banknotes),
|
||||
banknotes: banknotes,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getStaticPaths: GetStaticPaths = async () => {
|
||||
const countries = await readdir("./public/banknotes");
|
||||
const countries = await getCountryCounts("banknotes");
|
||||
|
||||
return {
|
||||
fallback: false,
|
||||
paths: countries.map((country) => ({ params: { country } })),
|
||||
paths: countries
|
||||
.filter((country) => country.banknotes?.count)
|
||||
.map((country) => ({ params: { country: country.name } })),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import Head from "next/head";
|
||||
import { GetStaticPaths, GetStaticProps, NextPage } from "next";
|
||||
import { NumistaType } from "@/types/numista";
|
||||
import { readdir } from "fs/promises";
|
||||
import { Showcase } from "@/components/showcase";
|
||||
import { Header } from "@/components/header";
|
||||
import { getCountryBanknotes } from "@/api/banknotes/get-country-banknotes";
|
||||
import { getCountryCoins } from "@/api/coins/get-country-coins";
|
||||
import { sortTypes } from "@/core/utils/numista";
|
||||
import { getTypes } from "@/api/types/get-types";
|
||||
import { getCountryCounts } from "@/api/countries/get-country-counts";
|
||||
|
||||
type Props = {
|
||||
country: string;
|
||||
@@ -18,10 +16,13 @@ type Props = {
|
||||
const CoinsPage: NextPage<Props> = ({ coins, ...props }) => (
|
||||
<>
|
||||
<Head>
|
||||
<title>Coins</title>
|
||||
<title>{props.country + " - Coins"}</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="An exhibition of my father's coin collection"
|
||||
content={
|
||||
"An exhibition of my father's collection of coins from " +
|
||||
props.country
|
||||
}
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
@@ -43,8 +44,15 @@ export const getStaticProps: GetStaticProps<Props> = async ({ params }) => {
|
||||
};
|
||||
}
|
||||
|
||||
const coins = await getCountryCoins(params.country);
|
||||
const banknotes = await getCountryBanknotes(params.country);
|
||||
const coins = await getTypes({
|
||||
category: "coin",
|
||||
country: params.country,
|
||||
});
|
||||
|
||||
const banknotes = await getTypes({
|
||||
category: "banknote",
|
||||
country: params.country,
|
||||
});
|
||||
|
||||
return {
|
||||
props: {
|
||||
@@ -54,17 +62,19 @@ export const getStaticProps: GetStaticProps<Props> = async ({ params }) => {
|
||||
0,
|
||||
),
|
||||
coinCount: coins.reduce((total, coin) => total + (coin.count ?? 1), 0),
|
||||
coins: sortTypes(coins),
|
||||
coins,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getStaticPaths: GetStaticPaths = async () => {
|
||||
const countries = await readdir("./public/coins");
|
||||
const countries = await getCountryCounts("coins");
|
||||
|
||||
return {
|
||||
fallback: false,
|
||||
paths: countries.map((country) => ({ params: { country } })),
|
||||
paths: countries
|
||||
.filter((country) => country.coins?.count)
|
||||
.map((country) => ({ params: { country: country.name } })),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
+18
-45
@@ -1,26 +1,12 @@
|
||||
import Head from "next/head";
|
||||
import { GetStaticProps, NextPage } from "next";
|
||||
import { readdir } from "fs/promises";
|
||||
import { useMemo } from "react";
|
||||
import { getCountryCoins } from "@/api/coins/get-country-coins";
|
||||
import { getCountryBanknotes } from "@/api/banknotes/get-country-banknotes";
|
||||
import { getCountryCounts } from "@/api/countries/get-country-counts";
|
||||
|
||||
type Props = {
|
||||
countsByCountry: Record<
|
||||
string,
|
||||
{
|
||||
coinCount: number;
|
||||
banknoteCount: number;
|
||||
}
|
||||
>;
|
||||
countryCounts: Awaited<ReturnType<typeof getCountryCounts>>;
|
||||
};
|
||||
|
||||
const CountriesPage: NextPage<Props> = ({ countsByCountry }) => {
|
||||
const countries = useMemo(
|
||||
() => Object.keys(countsByCountry).sort(),
|
||||
[countsByCountry],
|
||||
);
|
||||
|
||||
const CountriesPage: NextPage<Props> = ({ countryCounts }) => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
@@ -40,9 +26,11 @@ const CountriesPage: NextPage<Props> = ({ countsByCountry }) => {
|
||||
padding: "32px 16px",
|
||||
}}
|
||||
>
|
||||
{countries.map((country) => (
|
||||
<div key={country} style={{ maxWidth: 640, margin: "auto" }}>
|
||||
<b style={{ textAlign: "center", display: "block" }}>{country}</b>
|
||||
{countryCounts.map((country) => (
|
||||
<div key={country.code} style={{ maxWidth: 640, margin: "auto" }}>
|
||||
<b style={{ textAlign: "center", display: "block" }}>
|
||||
{country.name}
|
||||
</b>
|
||||
|
||||
<div
|
||||
style={{
|
||||
@@ -54,13 +42,13 @@ const CountriesPage: NextPage<Props> = ({ countsByCountry }) => {
|
||||
width: "min(640px, 95vw)",
|
||||
}}
|
||||
>
|
||||
{countsByCountry[country].coinCount > 0 ? (
|
||||
{country.coins?.count ? (
|
||||
<>
|
||||
<a href={"/pile/" + country}>
|
||||
{"pile (" + countsByCountry[country].coinCount + ")"}
|
||||
<a href={"/pile/" + country.name}>
|
||||
{"pile (" + country.coins.sum + ")"}
|
||||
</a>
|
||||
<a href={"/coins/" + country}>
|
||||
{"coins (" + countsByCountry[country].coinCount + ")"}
|
||||
<a href={"/coins/" + country.name}>
|
||||
{"coins (" + country.coins.count + ")"}
|
||||
</a>
|
||||
</>
|
||||
) : (
|
||||
@@ -70,9 +58,9 @@ const CountriesPage: NextPage<Props> = ({ countsByCountry }) => {
|
||||
</>
|
||||
)}
|
||||
|
||||
{countsByCountry[country].banknoteCount > 0 ? (
|
||||
<a href={"/banknotes/" + country}>
|
||||
{"banknotes (" + countsByCountry[country].banknoteCount + ")"}
|
||||
{country.banknotes?.count ? (
|
||||
<a href={"/banknotes/" + country.name}>
|
||||
{"banknotes (" + country.banknotes.count + ")"}
|
||||
</a>
|
||||
) : (
|
||||
<div />
|
||||
@@ -86,26 +74,11 @@ const CountriesPage: NextPage<Props> = ({ countsByCountry }) => {
|
||||
};
|
||||
|
||||
export const getStaticProps: GetStaticProps<Props> = async () => {
|
||||
const coinCountries = await readdir("./public/coins");
|
||||
const banknoteCountries = await readdir("./public/banknotes");
|
||||
const countsByCountry: Props["countsByCountry"] = {};
|
||||
|
||||
for (const country of new Set([...coinCountries, ...banknoteCountries])) {
|
||||
countsByCountry[country] = {
|
||||
coinCount: (await getCountryCoins(country)).reduce(
|
||||
(total, coin) => total + (coin.count ?? 1),
|
||||
0,
|
||||
),
|
||||
banknoteCount: (await getCountryBanknotes(country)).reduce(
|
||||
(total, banknote) => total + (banknote.count ?? 1),
|
||||
0,
|
||||
),
|
||||
};
|
||||
}
|
||||
const countryCounts = await getCountryCounts();
|
||||
|
||||
return {
|
||||
props: {
|
||||
countsByCountry,
|
||||
countryCounts,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -2,11 +2,9 @@ import Head from "next/head";
|
||||
import { Pile } from "@/components/pile";
|
||||
import { GetStaticPaths, GetStaticProps, NextPage } from "next";
|
||||
import { NumistaType } from "@/types/numista";
|
||||
import { readdir } from "fs/promises";
|
||||
import { Header } from "@/components/header";
|
||||
import { getCountryBanknotes } from "@/api/banknotes/get-country-banknotes";
|
||||
import { getCountryCoins } from "@/api/coins/get-country-coins";
|
||||
import { sortTypes } from "@/core/utils/numista";
|
||||
import { getCountryCounts } from "@/api/countries/get-country-counts";
|
||||
import { getTypes } from "@/api/types/get-types";
|
||||
|
||||
type Props = {
|
||||
country: string;
|
||||
@@ -43,8 +41,15 @@ export const getStaticProps: GetStaticProps<Props> = async ({ params }) => {
|
||||
};
|
||||
}
|
||||
|
||||
const coins = await getCountryCoins(params.country);
|
||||
const banknotes = await getCountryBanknotes(params.country);
|
||||
const coins = await getTypes({
|
||||
category: "coin",
|
||||
country: params.country,
|
||||
});
|
||||
|
||||
const banknotes = await getTypes({
|
||||
category: "banknote",
|
||||
country: params.country,
|
||||
});
|
||||
|
||||
return {
|
||||
props: {
|
||||
@@ -54,17 +59,19 @@ export const getStaticProps: GetStaticProps<Props> = async ({ params }) => {
|
||||
0,
|
||||
),
|
||||
coinCount: coins.reduce((total, coin) => total + (coin.count ?? 1), 0),
|
||||
coins: sortTypes(coins),
|
||||
coins,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getStaticPaths: GetStaticPaths = async () => {
|
||||
const countries = await readdir("./public/coins");
|
||||
const countries = await getCountryCounts("coins");
|
||||
|
||||
return {
|
||||
fallback: false,
|
||||
paths: countries.map((country) => ({ params: { country } })),
|
||||
paths: countries
|
||||
.filter((country) => country.coins?.count)
|
||||
.map((country) => ({ params: { country: country.name } })),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user