diff --git a/src/components/BeerStyleById/BeerStyleBeerSection.tsx b/src/components/BeerStyleById/BeerStyleBeerSection.tsx index 5cf7d08..c0c4d45 100644 --- a/src/components/BeerStyleById/BeerStyleBeerSection.tsx +++ b/src/components/BeerStyleById/BeerStyleBeerSection.tsx @@ -20,8 +20,8 @@ const BeerStyleBeerSection: FC = ({ beerStyle }) => { }); const { ref: penultimateBeerPostRef } = useInView({ /** - * When the last beer post comes into view, call setSize from useBeerPostsByBrewery to - * load more beer posts. + * When the last beer post comes into view, call setSize from useBeerPostsByBeerStyle + * to load more beer posts. */ onChange: (visible) => { if (!visible || isAtEnd) return; diff --git a/src/hooks/data-fetching/beer-posts/useBeerPostsByBeerStyle.ts b/src/hooks/data-fetching/beer-posts/useBeerPostsByBeerStyle.ts new file mode 100644 index 0000000..05879fc --- /dev/null +++ b/src/hooks/data-fetching/beer-posts/useBeerPostsByBeerStyle.ts @@ -0,0 +1,66 @@ +import BeerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult'; +import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; +import useSWRInfinite from 'swr/infinite'; +import { z } from 'zod'; + +interface UseBeerPostsByBeerStyleParams { + pageSize: number; + beerStyleId: string; +} + +const useBeerPostsByBeerStyle = ({ + pageSize, + beerStyleId, +}: UseBeerPostsByBeerStyleParams) => { + const fetcher = async (url: string) => { + const response = await fetch(url); + if (!response.ok) { + throw new Error(response.statusText); + } + + const json = await response.json(); + const count = response.headers.get('X-Total-Count'); + + const parsed = APIResponseValidationSchema.safeParse(json); + if (!parsed.success) { + throw new Error('API response validation failed'); + } + + const parsedPayload = z.array(BeerPostQueryResult).safeParse(parsed.data.payload); + if (!parsedPayload.success) { + throw new Error('API response validation failed'); + } + + const pageCount = Math.ceil(parseInt(count as string, 10) / pageSize); + return { + beerPosts: parsedPayload.data, + pageCount, + }; + }; + + const { data, error, isLoading, setSize, size } = useSWRInfinite( + (index) => + `/api/beers/styles/${beerStyleId}/beers?page_num=${ + index + 1 + }&page_size=${pageSize}`, + fetcher, + ); + + const beerPosts = data?.flatMap((d) => d.beerPosts) ?? []; + const pageCount = data?.[0].pageCount ?? 0; + const isLoadingMore = size > 0 && data && typeof data[size - 1] === 'undefined'; + const isAtEnd = !(size < data?.[0].pageCount!); + + return { + beerPosts, + pageCount, + size, + setSize, + isLoading, + isLoadingMore, + isAtEnd, + error: error as unknown, + }; +}; + +export default useBeerPostsByBeerStyle; diff --git a/src/hooks/data-fetching/beer-posts/useBeerPostsByBeerStyles.ts b/src/hooks/data-fetching/beer-posts/useBeerPostsByBeerStyles.ts index 05879fc..c11845c 100644 --- a/src/hooks/data-fetching/beer-posts/useBeerPostsByBeerStyles.ts +++ b/src/hooks/data-fetching/beer-posts/useBeerPostsByBeerStyles.ts @@ -46,6 +46,8 @@ const useBeerPostsByBeerStyle = ({ fetcher, ); + console.log(error); + const beerPosts = data?.flatMap((d) => d.beerPosts) ?? []; const pageCount = data?.[0].pageCount ?? 0; const isLoadingMore = size > 0 && data && typeof data[size - 1] === 'undefined'; diff --git a/src/pages/api/beers/styles/[id]/beers/index.ts b/src/pages/api/beers/styles/[id]/beers/index.ts index 30f1156..19d3c86 100644 --- a/src/pages/api/beers/styles/[id]/beers/index.ts +++ b/src/pages/api/beers/styles/[id]/beers/index.ts @@ -1,7 +1,8 @@ import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; import DBClient from '@/prisma/DBClient'; -import BeerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult'; +import getBeerPostsByBeerStyleId from '@/services/BeerPost/getBeerPostsByBeerStyleId'; + import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import { NextApiRequest, NextApiResponse } from 'next'; import { createRouter } from 'next-connect'; @@ -18,30 +19,14 @@ const getAllBeersByBeerStyle = async ( // eslint-disable-next-line @typescript-eslint/naming-convention const { page_size, page_num, id } = req.query; - const beers: z.infer[] = - await DBClient.instance.beerPost.findMany({ - where: { styleId: id }, - take: parseInt(page_size, 10), - skip: parseInt(page_num, 10) * parseInt(page_size, 10), - select: { - id: true, - name: true, - ibu: true, - abv: true, - createdAt: true, - updatedAt: true, - description: true, - postedBy: { select: { username: true, id: true } }, - brewery: { select: { name: true, id: true } }, - style: { select: { name: true, id: true, description: true } }, - beerImages: { select: { alt: true, path: true, caption: true, id: true } }, - }, - }); - - const pageCount = await DBClient.instance.beerPost.count({ - where: { breweryId: id }, + const beers = await getBeerPostsByBeerStyleId({ + pageNum: parseInt(page_num, 10), + pageSize: parseInt(page_size, 10), + styleId: id, }); + const pageCount = await DBClient.instance.beerPost.count({ where: { styleId: id } }); + res.setHeader('X-Total-Count', pageCount); res.status(200).json({ diff --git a/src/services/BeerPost/getBeerPostsByBeerStyleId.ts b/src/services/BeerPost/getBeerPostsByBeerStyleId.ts new file mode 100644 index 0000000..adc1fc7 --- /dev/null +++ b/src/services/BeerPost/getBeerPostsByBeerStyleId.ts @@ -0,0 +1,38 @@ +import DBClient from '@/prisma/DBClient'; +import { z } from 'zod'; +import BeerPostQueryResult from './schema/BeerPostQueryResult'; + +interface GetBeerPostsByBeerStyleIdArgs { + styleId: string; + pageSize: number; + pageNum: number; +} + +const getBeerPostsByBeerStyleId = async ({ + pageNum, + pageSize, + styleId, +}: GetBeerPostsByBeerStyleIdArgs): Promise[]> => { + const beers = await DBClient.instance.beerPost.findMany({ + where: { styleId }, + take: pageSize, + skip: pageNum * pageSize, + select: { + id: true, + name: true, + ibu: true, + abv: true, + createdAt: true, + updatedAt: true, + description: true, + postedBy: { select: { username: true, id: true } }, + brewery: { select: { name: true, id: true } }, + style: { select: { name: true, id: true, description: true } }, + beerImages: { select: { alt: true, path: true, caption: true, id: true } }, + }, + }); + + return beers; +}; + +export default getBeerPostsByBeerStyleId; diff --git a/src/services/BeerPost/getBeerPostsByBreweryId.ts b/src/services/BeerPost/getBeerPostsByBreweryId.ts new file mode 100644 index 0000000..910391b --- /dev/null +++ b/src/services/BeerPost/getBeerPostsByBreweryId.ts @@ -0,0 +1,38 @@ +import DBClient from '@/prisma/DBClient'; +import { z } from 'zod'; +import BeerPostQueryResult from './schema/BeerPostQueryResult'; + +interface GetBeerPostsByBeerStyleIdArgs { + breweryId: string; + pageSize: number; + pageNum: number; +} + +const getBeerPostsByBeerStyleId = async ({ + pageNum, + pageSize, + breweryId, +}: GetBeerPostsByBeerStyleIdArgs): Promise[]> => { + const beers = await DBClient.instance.beerPost.findMany({ + where: { breweryId }, + take: pageSize, + skip: pageNum * pageSize, + select: { + id: true, + name: true, + ibu: true, + abv: true, + createdAt: true, + updatedAt: true, + description: true, + postedBy: { select: { username: true, id: true } }, + brewery: { select: { name: true, id: true } }, + style: { select: { name: true, id: true, description: true } }, + beerImages: { select: { alt: true, path: true, caption: true, id: true } }, + }, + }); + + return beers; +}; + +export default getBeerPostsByBeerStyleId;