mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-02-16 10:42:08 +00:00
Feat: add user posts functionality for account page
This commit is contained in:
82
src/components/Account/BeerPostsByUser.tsx
Normal file
82
src/components/Account/BeerPostsByUser.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import UserContext from '@/contexts/UserContext';
|
||||
import useBeerPostsByUser from '@/hooks/data-fetching/beer-posts/useBeerPostsByUser';
|
||||
import { FC, useContext, MutableRefObject, useRef } from 'react';
|
||||
import { FaArrowUp } from 'react-icons/fa';
|
||||
import { useInView } from 'react-intersection-observer';
|
||||
import BeerCard from '../BeerIndex/BeerCard';
|
||||
import LoadingCard from '../ui/LoadingCard';
|
||||
import Spinner from '../ui/Spinner';
|
||||
|
||||
const BeerPostsByUser: FC = () => {
|
||||
const { user } = useContext(UserContext);
|
||||
const pageRef: MutableRefObject<HTMLDivElement | null> = useRef(null);
|
||||
const PAGE_SIZE = 2;
|
||||
const { beerPosts, setSize, size, isLoading, isLoadingMore, isAtEnd } =
|
||||
useBeerPostsByUser({ pageSize: PAGE_SIZE, userId: user!.id });
|
||||
const { ref: lastBeerPostRef } = useInView({
|
||||
onChange: (visible) => {
|
||||
if (!visible || isAtEnd) return;
|
||||
setSize(size + 1);
|
||||
},
|
||||
});
|
||||
return (
|
||||
<div className="mt-4" ref={pageRef}>
|
||||
<div className="grid gap-6 xl:grid-cols-2">
|
||||
{!!beerPosts.length && !isLoading && (
|
||||
<>
|
||||
{beerPosts.map((beerPost, i) => {
|
||||
return (
|
||||
<div
|
||||
key={beerPost.id}
|
||||
ref={beerPosts.length === i + 1 ? lastBeerPostRef : undefined}
|
||||
>
|
||||
<BeerCard post={beerPost} />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
{(isLoading || isLoadingMore) && (
|
||||
<>
|
||||
{Array.from({ length: PAGE_SIZE }, (_, i) => (
|
||||
<LoadingCard key={i} />
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{(isLoading || isLoadingMore) && (
|
||||
<div className="flex h-32 w-full items-center justify-center">
|
||||
<Spinner size="sm" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!!beerPosts.length && isAtEnd && !isLoading && (
|
||||
<div className="flex h-20 items-center justify-center text-center">
|
||||
<div className="tooltip tooltip-bottom" data-tip="Scroll back to top of page.">
|
||||
<button
|
||||
type="button"
|
||||
className="btn-ghost btn-sm btn"
|
||||
aria-label="Scroll back to top of page."
|
||||
onClick={() => {
|
||||
pageRef.current?.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FaArrowUp />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!beerPosts.length && !isLoading && (
|
||||
<div className="flex h-24 w-full items-center justify-center">
|
||||
<p className="text-lg font-bold">No posts yet.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BeerPostsByUser;
|
||||
84
src/components/Account/BreweryPostsByUser.tsx
Normal file
84
src/components/Account/BreweryPostsByUser.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import UserContext from '@/contexts/UserContext';
|
||||
import { FC, useContext, MutableRefObject, useRef } from 'react';
|
||||
import { FaArrowUp } from 'react-icons/fa';
|
||||
import { useInView } from 'react-intersection-observer';
|
||||
import useBreweryPostsByUser from '@/hooks/data-fetching/brewery-posts/useBreweryPostsByUser';
|
||||
import LoadingCard from '../ui/LoadingCard';
|
||||
import Spinner from '../ui/Spinner';
|
||||
import BreweryCard from '../BreweryIndex/BreweryCard';
|
||||
|
||||
const BreweryPostsByUser: FC = () => {
|
||||
const { user } = useContext(UserContext);
|
||||
const pageRef: MutableRefObject<HTMLDivElement | null> = useRef(null);
|
||||
const PAGE_SIZE = 2;
|
||||
const { breweryPosts, setSize, size, isLoading, isLoadingMore, isAtEnd } =
|
||||
useBreweryPostsByUser({ pageSize: PAGE_SIZE, userId: user!.id });
|
||||
|
||||
const { ref: lastBreweryPostRef } = useInView({
|
||||
onChange: (visible) => {
|
||||
if (!visible || isAtEnd) return;
|
||||
setSize(size + 1);
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="mt-4" ref={pageRef}>
|
||||
<div className="grid gap-6 xl:grid-cols-2">
|
||||
{!!breweryPosts.length && !isLoading && (
|
||||
<>
|
||||
{breweryPosts.map((breweryPost, i) => {
|
||||
return (
|
||||
<div
|
||||
key={breweryPost.id}
|
||||
ref={breweryPosts.length === i + 1 ? lastBreweryPostRef : undefined}
|
||||
>
|
||||
<BreweryCard brewery={breweryPost} />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
{isLoadingMore && (
|
||||
<>
|
||||
{Array.from({ length: PAGE_SIZE }, (_, i) => (
|
||||
<LoadingCard key={i} />
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{(isLoading || isLoadingMore) && (
|
||||
<div className="flex h-32 w-full items-center justify-center">
|
||||
<Spinner size="sm" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!!breweryPosts.length && isAtEnd && !isLoading && (
|
||||
<div className="flex h-20 items-center justify-center text-center">
|
||||
<div className="tooltip tooltip-bottom" data-tip="Scroll back to top of page.">
|
||||
<button
|
||||
type="button"
|
||||
className="btn-ghost btn-sm btn"
|
||||
aria-label="Scroll back to top of page."
|
||||
onClick={() => {
|
||||
pageRef.current?.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FaArrowUp />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!breweryPosts.length && !isLoading && (
|
||||
<div className="flex h-24 w-full items-center justify-center">
|
||||
<p className="text-lg font-bold">No posts yet.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BreweryPostsByUser;
|
||||
31
src/components/Account/UserPosts.tsx
Normal file
31
src/components/Account/UserPosts.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Tab } from '@headlessui/react';
|
||||
import { FC } from 'react';
|
||||
import BeerPostsByUser from './BeerPostsByUser';
|
||||
import BreweryPostsByUser from './BreweryPostsByUser';
|
||||
|
||||
const UserPosts: FC = () => {
|
||||
return (
|
||||
<div className="mt-4">
|
||||
<div>
|
||||
<Tab.Group>
|
||||
<Tab.List className="tabs tabs-boxed items-center justify-center rounded-2xl">
|
||||
<Tab className="tab tab-xl w-1/2 uppercase ui-selected:tab-active">Beers</Tab>
|
||||
<Tab className="tab tab-xl w-1/2 uppercase ui-selected:tab-active">
|
||||
Breweries
|
||||
</Tab>
|
||||
</Tab.List>
|
||||
<Tab.Panels>
|
||||
<Tab.Panel>
|
||||
<BeerPostsByUser />
|
||||
</Tab.Panel>
|
||||
<Tab.Panel>
|
||||
<BreweryPostsByUser />
|
||||
</Tab.Panel>
|
||||
</Tab.Panels>
|
||||
</Tab.Group>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserPosts;
|
||||
62
src/hooks/data-fetching/beer-posts/useBeerPostsByUser.ts
Normal file
62
src/hooks/data-fetching/beer-posts/useBeerPostsByUser.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import BeerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult';
|
||||
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
|
||||
import useSWRInfinite from 'swr/infinite';
|
||||
import { z } from 'zod';
|
||||
|
||||
interface UseBeerPostsByUserParams {
|
||||
pageSize: number;
|
||||
userId: string;
|
||||
}
|
||||
|
||||
const useBeerPostsByUser = ({ pageSize, userId }: UseBeerPostsByUserParams) => {
|
||||
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/users/${userId}/posts/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 useBeerPostsByUser;
|
||||
@@ -0,0 +1,60 @@
|
||||
import BreweryPostQueryResult from '@/services/BreweryPost/schema/BreweryPostQueryResult';
|
||||
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
|
||||
import useSWRInfinite from 'swr/infinite';
|
||||
import { z } from 'zod';
|
||||
|
||||
interface UseBreweryPostsByUserParams {
|
||||
pageSize: number;
|
||||
userId: string;
|
||||
}
|
||||
|
||||
const useBreweryPostsByUser = ({ pageSize, userId }: UseBreweryPostsByUserParams) => {
|
||||
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(BreweryPostQueryResult).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 { breweryPosts: parsedPayload.data, pageCount };
|
||||
};
|
||||
|
||||
const { data, error, isLoading, setSize, size } = useSWRInfinite(
|
||||
(index) =>
|
||||
`/api/users/${userId}/posts/breweries?page_num=${index + 1}&page_size=${pageSize}`,
|
||||
fetcher,
|
||||
{ parallel: true },
|
||||
);
|
||||
|
||||
const breweryPosts = data?.flatMap((d) => d.breweryPosts) ?? [];
|
||||
const pageCount = data?.[0].pageCount ?? 0;
|
||||
const isLoadingMore = size > 0 && data && typeof data[size - 1] === 'undefined';
|
||||
const isAtEnd = !(size < data?.[0].pageCount!);
|
||||
|
||||
return {
|
||||
breweryPosts,
|
||||
pageCount,
|
||||
size,
|
||||
setSize,
|
||||
isLoading,
|
||||
isLoadingMore,
|
||||
isAtEnd,
|
||||
error: error as unknown,
|
||||
};
|
||||
};
|
||||
|
||||
export default useBreweryPostsByUser;
|
||||
@@ -10,6 +10,7 @@ import Security from '@/components/Account/Security';
|
||||
import DeleteAccount from '@/components/Account/DeleteAccount';
|
||||
import accountPageReducer from '@/reducers/accountPageReducer';
|
||||
import UserAvatar from '@/components/Account/UserAvatar';
|
||||
import UserPosts from '@/components/Account/UserPosts';
|
||||
|
||||
const AccountPage: NextPage = () => {
|
||||
const { user } = useContext(UserContext);
|
||||
@@ -32,9 +33,11 @@ const AccountPage: NextPage = () => {
|
||||
/>
|
||||
</Head>
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="m-12 flex w-11/12 flex-col items-center justify-center space-y-3 lg:w-7/12">
|
||||
<div className="m-12 flex w-11/12 flex-col items-center justify-center space-y-3 lg:w-8/12">
|
||||
<div className="flex flex-col items-center space-y-3">
|
||||
<UserAvatar user={user} />
|
||||
<div className="h-32">
|
||||
<UserAvatar user={user} />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center space-y-1">
|
||||
<p className="text-3xl font-bold">Hello, {user!.username}!</p>
|
||||
@@ -58,7 +61,9 @@ const AccountPage: NextPage = () => {
|
||||
<Security pageState={pageState} dispatch={dispatch} />
|
||||
<DeleteAccount pageState={pageState} dispatch={dispatch} />
|
||||
</Tab.Panel>
|
||||
<Tab.Panel>Your posts!</Tab.Panel>
|
||||
<Tab.Panel className="h-full space-y-5">
|
||||
<UserPosts />
|
||||
</Tab.Panel>
|
||||
</Tab.Panels>
|
||||
</Tab.Group>
|
||||
</div>
|
||||
|
||||
58
src/pages/api/users/[id]/posts/beers.ts
Normal file
58
src/pages/api/users/[id]/posts/beers.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { createRouter } from 'next-connect';
|
||||
import { z } from 'zod';
|
||||
|
||||
import validateRequest from '@/config/nextConnect/middleware/validateRequest';
|
||||
import DBClient from '@/prisma/DBClient';
|
||||
import getBeerPostsByPostedById from '@/services/BeerPost/getBeerPostsByPostedById';
|
||||
import PaginatedQueryResponseSchema from '@/services/schema/PaginatedQueryResponseSchema';
|
||||
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
|
||||
|
||||
interface GetBeerPostsRequest extends NextApiRequest {
|
||||
query: {
|
||||
page_num: string;
|
||||
page_size: string;
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
|
||||
const getBeerPostsByUserId = async (
|
||||
req: GetBeerPostsRequest,
|
||||
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
|
||||
) => {
|
||||
const pageNum = parseInt(req.query.page_num, 10);
|
||||
const pageSize = parseInt(req.query.page_size, 10);
|
||||
|
||||
const { id } = req.query;
|
||||
|
||||
const beerPosts = await getBeerPostsByPostedById({ pageNum, pageSize, postedById: id });
|
||||
|
||||
const beerPostCount = await DBClient.instance.beerPost.count({
|
||||
where: { postedBy: { id } },
|
||||
});
|
||||
|
||||
res.setHeader('X-Total-Count', beerPostCount);
|
||||
|
||||
res.status(200).json({
|
||||
message: `Beer posts by user ${id} fetched successfully`,
|
||||
statusCode: 200,
|
||||
payload: beerPosts,
|
||||
success: true,
|
||||
});
|
||||
};
|
||||
|
||||
const router = createRouter<
|
||||
GetBeerPostsRequest,
|
||||
NextApiResponse<z.infer<typeof APIResponseValidationSchema>>
|
||||
>();
|
||||
|
||||
router.get(
|
||||
validateRequest({
|
||||
querySchema: PaginatedQueryResponseSchema.extend({ id: z.string().cuid() }),
|
||||
}),
|
||||
getBeerPostsByUserId,
|
||||
);
|
||||
|
||||
const handler = router.handler();
|
||||
|
||||
export default handler;
|
||||
62
src/pages/api/users/[id]/posts/breweries.ts
Normal file
62
src/pages/api/users/[id]/posts/breweries.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { createRouter } from 'next-connect';
|
||||
import { z } from 'zod';
|
||||
|
||||
import validateRequest from '@/config/nextConnect/middleware/validateRequest';
|
||||
import DBClient from '@/prisma/DBClient';
|
||||
import getAllBreweryPostsByPostedById from '@/services/BreweryPost/getAllBreweryPostsByPostedById';
|
||||
import PaginatedQueryResponseSchema from '@/services/schema/PaginatedQueryResponseSchema';
|
||||
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
|
||||
|
||||
interface GetBreweryPostsRequest extends NextApiRequest {
|
||||
query: {
|
||||
page_num: string;
|
||||
page_size: string;
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
|
||||
const getBreweryPostsByUserId = async (
|
||||
req: GetBreweryPostsRequest,
|
||||
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
|
||||
) => {
|
||||
const pageNum = parseInt(req.query.page_num, 10);
|
||||
const pageSize = parseInt(req.query.page_size, 10);
|
||||
|
||||
const { id } = req.query;
|
||||
|
||||
const breweryPosts = await getAllBreweryPostsByPostedById({
|
||||
pageNum,
|
||||
pageSize,
|
||||
postedById: id,
|
||||
});
|
||||
|
||||
const breweryPostCount = await DBClient.instance.breweryPost.count({
|
||||
where: { postedBy: { id } },
|
||||
});
|
||||
|
||||
res.setHeader('X-Total-Count', breweryPostCount);
|
||||
|
||||
res.status(200).json({
|
||||
message: `Brewery posts by user ${id} fetched successfully`,
|
||||
statusCode: 200,
|
||||
payload: breweryPosts,
|
||||
success: true,
|
||||
});
|
||||
};
|
||||
|
||||
const router = createRouter<
|
||||
GetBreweryPostsRequest,
|
||||
NextApiResponse<z.infer<typeof APIResponseValidationSchema>>
|
||||
>();
|
||||
|
||||
router.get(
|
||||
validateRequest({
|
||||
querySchema: PaginatedQueryResponseSchema.extend({ id: z.string().cuid() }),
|
||||
}),
|
||||
getBreweryPostsByUserId,
|
||||
);
|
||||
|
||||
const handler = router.handler();
|
||||
|
||||
export default handler;
|
||||
47
src/services/BeerPost/getBeerPostsByPostedById.ts
Normal file
47
src/services/BeerPost/getBeerPostsByPostedById.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import DBClient from '@/prisma/DBClient';
|
||||
import { z } from 'zod';
|
||||
import BeerPostQueryResult from './schema/BeerPostQueryResult';
|
||||
|
||||
interface GetBeerPostsByBeerStyleIdArgs {
|
||||
postedById: string;
|
||||
pageSize: number;
|
||||
pageNum: number;
|
||||
}
|
||||
|
||||
const getBeerPostsByPostedById = async ({
|
||||
pageNum,
|
||||
pageSize,
|
||||
postedById,
|
||||
}: GetBeerPostsByBeerStyleIdArgs): Promise<z.infer<typeof BeerPostQueryResult>[]> => {
|
||||
const beers = await DBClient.instance.beerPost.findMany({
|
||||
where: { postedBy: { id: postedById } },
|
||||
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,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return beers;
|
||||
};
|
||||
|
||||
export default getBeerPostsByPostedById;
|
||||
58
src/services/BreweryPost/getAllBreweryPostsByPostedById.ts
Normal file
58
src/services/BreweryPost/getAllBreweryPostsByPostedById.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import DBClient from '@/prisma/DBClient';
|
||||
import BreweryPostQueryResult from '@/services/BreweryPost/schema/BreweryPostQueryResult';
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
const prisma = DBClient.instance;
|
||||
|
||||
const getAllBreweryPostsByPostedById = async ({
|
||||
pageNum,
|
||||
pageSize,
|
||||
postedById,
|
||||
}: {
|
||||
pageNum: number;
|
||||
pageSize: number;
|
||||
postedById: string;
|
||||
}): Promise<z.infer<typeof BreweryPostQueryResult>[]> => {
|
||||
const breweryPosts = await prisma.breweryPost.findMany({
|
||||
where: { postedBy: { id: postedById } },
|
||||
take: pageSize,
|
||||
skip: (pageNum - 1) * pageSize,
|
||||
select: {
|
||||
id: true,
|
||||
location: {
|
||||
select: {
|
||||
city: true,
|
||||
address: true,
|
||||
coordinates: true,
|
||||
country: true,
|
||||
stateOrProvince: true,
|
||||
},
|
||||
},
|
||||
description: true,
|
||||
name: true,
|
||||
postedBy: { select: { username: true, id: true } },
|
||||
breweryImages: {
|
||||
select: {
|
||||
path: true,
|
||||
caption: true,
|
||||
id: true,
|
||||
alt: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
},
|
||||
},
|
||||
createdAt: true,
|
||||
dateEstablished: true,
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
|
||||
/**
|
||||
* Prisma does not support tuples, so we have to typecast the coordinates field to
|
||||
* [number, number] in order to satisfy the zod schema.
|
||||
*/
|
||||
return breweryPosts as Awaited<ReturnType<typeof getAllBreweryPostsByPostedById>>;
|
||||
};
|
||||
|
||||
export default getAllBreweryPostsByPostedById;
|
||||
Reference in New Issue
Block a user