mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-02-16 10:42:08 +00:00
Feat: Implement mapbox for geocoding and location data for brewery posts
This commit is contained in:
1474
package-lock.json
generated
1474
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,7 @@
|
|||||||
"@headlessui/react": "^1.7.13",
|
"@headlessui/react": "^1.7.13",
|
||||||
"@headlessui/tailwindcss": "^0.1.2",
|
"@headlessui/tailwindcss": "^0.1.2",
|
||||||
"@hookform/resolvers": "^3.0.0",
|
"@hookform/resolvers": "^3.0.0",
|
||||||
|
"@mapbox/mapbox-sdk": "^0.15.0",
|
||||||
"@prisma/client": "^4.12.0",
|
"@prisma/client": "^4.12.0",
|
||||||
"@react-email/components": "^0.0.4",
|
"@react-email/components": "^0.0.4",
|
||||||
"@react-email/render": "^0.0.6",
|
"@react-email/render": "^0.0.6",
|
||||||
@@ -26,6 +27,7 @@
|
|||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
"jsonwebtoken": "^9.0.0",
|
"jsonwebtoken": "^9.0.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
"mapbox-gl": "^2.14.1",
|
||||||
"multer": "^2.0.0-rc.4",
|
"multer": "^2.0.0-rc.4",
|
||||||
"multer-storage-cloudinary": "^4.0.0",
|
"multer-storage-cloudinary": "^4.0.0",
|
||||||
"next": "^13.2.4",
|
"next": "^13.2.4",
|
||||||
@@ -41,6 +43,7 @@
|
|||||||
"react-hook-form": "^7.43.9",
|
"react-hook-form": "^7.43.9",
|
||||||
"react-icons": "^4.8.0",
|
"react-icons": "^4.8.0",
|
||||||
"react-intersection-observer": "^9.4.3",
|
"react-intersection-observer": "^9.4.3",
|
||||||
|
"react-map-gl": "^7.0.23",
|
||||||
"react-responsive-carousel": "^3.2.23",
|
"react-responsive-carousel": "^3.2.23",
|
||||||
"sparkpost": "^2.1.4",
|
"sparkpost": "^2.1.4",
|
||||||
"swr": "^2.1.2",
|
"swr": "^2.1.2",
|
||||||
@@ -53,6 +56,7 @@
|
|||||||
"@types/ejs": "^3.1.2",
|
"@types/ejs": "^3.1.2",
|
||||||
"@types/jsonwebtoken": "^9.0.1",
|
"@types/jsonwebtoken": "^9.0.1",
|
||||||
"@types/lodash": "^4.14.192",
|
"@types/lodash": "^4.14.192",
|
||||||
|
"@types/mapbox__mapbox-sdk": "^0.13.4",
|
||||||
"@types/multer": "^1.4.7",
|
"@types/multer": "^1.4.7",
|
||||||
"@types/node": "^18.15.11",
|
"@types/node": "^18.15.11",
|
||||||
"@types/passport-local": "^1.0.35",
|
"@types/passport-local": "^1.0.35",
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ import useGetBeerPostLikeCount from '@/hooks/useBeerPostLikeCount';
|
|||||||
import useTimeDistance from '@/hooks/useTimeDistance';
|
import useTimeDistance from '@/hooks/useTimeDistance';
|
||||||
import BeerPostLikeButton from './BeerPostLikeButton';
|
import BeerPostLikeButton from './BeerPostLikeButton';
|
||||||
|
|
||||||
const BeerInfoHeader: FC<{
|
interface BeerInfoHeaderProps {
|
||||||
beerPost: z.infer<typeof beerPostQueryResult>;
|
beerPost: z.infer<typeof beerPostQueryResult>;
|
||||||
}> = ({ beerPost }) => {
|
}
|
||||||
|
|
||||||
|
const BeerInfoHeader: FC<BeerInfoHeaderProps> = ({ beerPost }) => {
|
||||||
const createdAt = new Date(beerPost.createdAt);
|
const createdAt = new Date(beerPost.createdAt);
|
||||||
const timeDistance = useTimeDistance(createdAt);
|
const timeDistance = useTimeDistance(createdAt);
|
||||||
|
|
||||||
@@ -23,10 +25,11 @@ const BeerInfoHeader: FC<{
|
|||||||
const { likeCount, mutate } = useGetBeerPostLikeCount(beerPost.id);
|
const { likeCount, mutate } = useGetBeerPostLikeCount(beerPost.id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="card flex flex-col justify-center bg-base-300">
|
<article className="card flex flex-col justify-center bg-base-300">
|
||||||
<article className="card-body">
|
<div className="card-body">
|
||||||
<div className="flex justify-between">
|
<header className="flex justify-between">
|
||||||
<header>
|
<div className="space-y-2">
|
||||||
|
<div>
|
||||||
<h1 className="text-2xl font-bold lg:text-4xl">{beerPost.name}</h1>
|
<h1 className="text-2xl font-bold lg:text-4xl">{beerPost.name}</h1>
|
||||||
<h2 className="text-lg font-semibold lg:text-2xl">
|
<h2 className="text-lg font-semibold lg:text-2xl">
|
||||||
by{' '}
|
by{' '}
|
||||||
@@ -37,16 +40,8 @@ const BeerInfoHeader: FC<{
|
|||||||
{beerPost.brewery.name}
|
{beerPost.brewery.name}
|
||||||
</Link>
|
</Link>
|
||||||
</h2>
|
</h2>
|
||||||
</header>
|
|
||||||
{isPostOwner && (
|
|
||||||
<div className="tooltip tooltip-left" data-tip={`Edit '${beerPost.name}'`}>
|
|
||||||
<Link href={`/beers/${beerPost.id}/edit`} className="btn-ghost btn-xs btn">
|
|
||||||
<FaRegEdit className="text-xl" />
|
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
<div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3 className="italic">
|
<h3 className="italic">
|
||||||
{' posted by '}
|
{' posted by '}
|
||||||
<Link href={`/users/${beerPost.postedBy.id}`} className="link-hover link">
|
<Link href={`/users/${beerPost.postedBy.id}`} className="link-hover link">
|
||||||
@@ -61,7 +56,18 @@ const BeerInfoHeader: FC<{
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</h3>
|
</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isPostOwner && (
|
||||||
|
<div className="tooltip tooltip-left" data-tip={`Edit '${beerPost.name}'`}>
|
||||||
|
<Link href={`/beers/${beerPost.id}/edit`} className="btn-ghost btn-xs btn">
|
||||||
|
<FaRegEdit className="text-xl" />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</header>
|
||||||
|
<div className="space-y-2">
|
||||||
<p>{beerPost.description}</p>
|
<p>{beerPost.description}</p>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
@@ -86,11 +92,14 @@ const BeerInfoHeader: FC<{
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="card-actions items-end">
|
<div className="card-actions items-end">
|
||||||
{user && <BeerPostLikeButton beerPostId={beerPost.id} mutateCount={mutate} />}
|
{user && (
|
||||||
|
<BeerPostLikeButton beerPostId={beerPost.id} mutateCount={mutate} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
</main>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -37,16 +37,16 @@ const BeerCard: FC<{ post: z.infer<typeof beerPostQueryResult> }> = ({ post }) =
|
|||||||
</h4>
|
</h4>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="flex items-end justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-md lg:text-xl">{post.type.name}</p>
|
<p className="text-md lg:text-xl">{post.type.name}</p>
|
||||||
<div className="space-x-3">
|
<div className="space-x-3">
|
||||||
<span className="text-sm lg:text-lg">{post.abv}% ABV</span>
|
<span className="text-sm lg:text-lg">{post.abv}% ABV</span>
|
||||||
<span className="text-sm lg:text-lg">{post.ibu} IBU</span>
|
<span className="text-sm lg:text-lg">{post.ibu} IBU</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<span>liked by {likeCount} users</span>
|
<span>liked by {likeCount} users</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
{user && <BeerPostLikeButton beerPostId={post.id} mutateCount={mutate} />}
|
{user && <BeerPostLikeButton beerPostId={post.id} mutateCount={mutate} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ const BreweryCard: FC<{ brewery: z.infer<typeof BreweryPostQueryResult> }> = ({
|
|||||||
</Link>
|
</Link>
|
||||||
</h2>
|
</h2>
|
||||||
<h3 className="text-xl font-normal lg:text-2xl">
|
<h3 className="text-xl font-normal lg:text-2xl">
|
||||||
located in {brewery.location}
|
located in {brewery.city}, {brewery.stateOrProvince || brewery.country}
|
||||||
</h3>
|
</h3>
|
||||||
<h4 className="text-lg lg:text-xl">
|
<h4 className="text-lg lg:text-xl">
|
||||||
est. {brewery.dateEstablished.getFullYear()}
|
est. {brewery.dateEstablished.getFullYear()}
|
||||||
|
|||||||
12
src/config/env/index.ts
vendored
12
src/config/env/index.ts
vendored
@@ -22,6 +22,7 @@ const envSchema = z.object({
|
|||||||
NODE_ENV: z.enum(['development', 'production', 'test']),
|
NODE_ENV: z.enum(['development', 'production', 'test']),
|
||||||
SPARKPOST_API_KEY: z.string(),
|
SPARKPOST_API_KEY: z.string(),
|
||||||
SPARKPOST_SENDER_ADDRESS: z.string().email(),
|
SPARKPOST_SENDER_ADDRESS: z.string().email(),
|
||||||
|
MAPBOX_ACCESS_TOKEN: z.string()
|
||||||
});
|
});
|
||||||
|
|
||||||
const parsed = envSchema.safeParse(env);
|
const parsed = envSchema.safeParse(env);
|
||||||
@@ -145,3 +146,14 @@ export const SPARKPOST_API_KEY = parsed.data.SPARKPOST_API_KEY;
|
|||||||
* @see https://app.sparkpost.com/domains/list/sending
|
* @see https://app.sparkpost.com/domains/list/sending
|
||||||
*/
|
*/
|
||||||
export const SPARKPOST_SENDER_ADDRESS = parsed.data.SPARKPOST_SENDER_ADDRESS;
|
export const SPARKPOST_SENDER_ADDRESS = parsed.data.SPARKPOST_SENDER_ADDRESS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Your Mapbox access token.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* 'pk.abcdefghijklmnopqrstuvwxyz123456';
|
||||||
|
*
|
||||||
|
* @see https://docs.mapbox.com/help/how-mapbox-works/access-tokens/
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const MAPBOX_ACCESS_TOKEN = parsed.data.MAPBOX_ACCESS_TOKEN;
|
||||||
12
src/config/mapbox/geocoder.ts
Normal file
12
src/config/mapbox/geocoder.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import mbxGeocoding from '@mapbox/mapbox-sdk/services/geocoding';
|
||||||
|
|
||||||
|
import { MAPBOX_ACCESS_TOKEN } from '../env';
|
||||||
|
|
||||||
|
const geocoder = mbxGeocoding({ accessToken: MAPBOX_ACCESS_TOKEN });
|
||||||
|
|
||||||
|
const geocode = async (query: string) => {
|
||||||
|
const geoData = await geocoder.forwardGeocode({ query, limit: 1 }).send();
|
||||||
|
return geoData.body.features[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
export default geocode;
|
||||||
@@ -37,7 +37,6 @@ const BeerByIdPage: NextPage<BeerPageProps> = ({ beerPost, beerRecommendations }
|
|||||||
<meta name="description" content={beerPost.description} />
|
<meta name="description" content={beerPost.description} />
|
||||||
</Head>
|
</Head>
|
||||||
<>
|
<>
|
||||||
<div>
|
|
||||||
<Carousel
|
<Carousel
|
||||||
className="w-full"
|
className="w-full"
|
||||||
useKeyboardArrows
|
useKeyboardArrows
|
||||||
@@ -63,7 +62,7 @@ const BeerByIdPage: NextPage<BeerPageProps> = ({ beerPost, beerRecommendations }
|
|||||||
))}
|
))}
|
||||||
</Carousel>
|
</Carousel>
|
||||||
|
|
||||||
<div className="mb-12 mt-10 flex w-full items-center justify-center ">
|
<main className="mb-12 mt-10 flex w-full items-center justify-center">
|
||||||
<div className="w-11/12 space-y-3 xl:w-9/12 2xl:w-8/12">
|
<div className="w-11/12 space-y-3 xl:w-9/12 2xl:w-8/12">
|
||||||
<BeerInfoHeader beerPost={beerPost} />
|
<BeerInfoHeader beerPost={beerPost} />
|
||||||
|
|
||||||
@@ -97,8 +96,7 @@ const BeerByIdPage: NextPage<BeerPageProps> = ({ beerPost, beerRecommendations }
|
|||||||
</Tab.Group>
|
</Tab.Group>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</main>
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,16 +1,176 @@
|
|||||||
import getBreweryPostById from '@/services/BreweryPost/getBreweryPostById';
|
import getBreweryPostById from '@/services/BreweryPost/getBreweryPostById';
|
||||||
import BreweryPostQueryResult from '@/services/BreweryPost/types/BreweryPostQueryResult';
|
import BreweryPostQueryResult from '@/services/BreweryPost/types/BreweryPostQueryResult';
|
||||||
import { GetServerSideProps, NextPage } from 'next';
|
import { GetServerSideProps, NextPage } from 'next';
|
||||||
|
import 'mapbox-gl/dist/mapbox-gl.css';
|
||||||
|
import MapGL, { Marker } from 'react-map-gl';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
import { FC, useContext } from 'react';
|
||||||
|
import Head from 'next/head';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import 'react-responsive-carousel/lib/styles/carousel.min.css'; // requires a loader
|
||||||
|
import { Carousel } from 'react-responsive-carousel';
|
||||||
|
import useGetBreweryPostLikeCount from '@/hooks/useGetBreweryPostLikeCount';
|
||||||
|
import useTimeDistance from '@/hooks/useTimeDistance';
|
||||||
|
import UserContext from '@/contexts/userContext';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { FaRegEdit } from 'react-icons/fa';
|
||||||
|
import format from 'date-fns/format';
|
||||||
|
import BreweryPostLikeButton from '@/components/BreweryIndex/BreweryPostLikeButton';
|
||||||
|
|
||||||
interface BreweryPageProps {
|
interface BreweryPageProps {
|
||||||
breweryPost: z.infer<typeof BreweryPostQueryResult>;
|
breweryPost: z.infer<typeof BreweryPostQueryResult>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface BreweryInfoHeaderProps {
|
||||||
|
breweryPost: z.infer<typeof BreweryPostQueryResult>;
|
||||||
|
}
|
||||||
|
const BreweryInfoHeader: FC<BreweryInfoHeaderProps> = ({ breweryPost }) => {
|
||||||
|
const createdAt = new Date(breweryPost.createdAt);
|
||||||
|
const timeDistance = useTimeDistance(createdAt);
|
||||||
|
|
||||||
|
const { user } = useContext(UserContext);
|
||||||
|
const idMatches = user && breweryPost.postedBy.id === user.id;
|
||||||
|
const isPostOwner = !!(user && idMatches);
|
||||||
|
|
||||||
|
const { likeCount, mutate } = useGetBreweryPostLikeCount(breweryPost.id);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<article className="card flex flex-col justify-center bg-base-300">
|
||||||
|
<div className="card-body">
|
||||||
|
<header className="flex justify-between">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-2xl font-bold lg:text-4xl">{breweryPost.name}</h1>
|
||||||
|
<h2 className="text-lg font-semibold lg:text-2xl">
|
||||||
|
Located in
|
||||||
|
{` ${breweryPost.city}, ${
|
||||||
|
breweryPost.stateOrProvince || breweryPost.country
|
||||||
|
}`}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="italic">
|
||||||
|
{' posted by '}
|
||||||
|
<Link
|
||||||
|
href={`/users/${breweryPost.postedBy.id}`}
|
||||||
|
className="link-hover link"
|
||||||
|
>
|
||||||
|
{`${breweryPost.postedBy.username} `}
|
||||||
|
</Link>
|
||||||
|
{timeDistance && (
|
||||||
|
<span
|
||||||
|
className="tooltip tooltip-right"
|
||||||
|
data-tip={format(createdAt, 'MM/dd/yyyy')}
|
||||||
|
>{`${timeDistance} ago`}</span>
|
||||||
|
)}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{isPostOwner && (
|
||||||
|
<div className="tooltip tooltip-left" data-tip={`Edit '${breweryPost.name}'`}>
|
||||||
|
<Link
|
||||||
|
href={`/breweries/${breweryPost.id}/edit`}
|
||||||
|
className="btn-ghost btn-xs btn"
|
||||||
|
>
|
||||||
|
<FaRegEdit className="text-xl" />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</header>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<p>{breweryPost.description}</p>
|
||||||
|
<div className="flex items-end justify-between">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<div>
|
||||||
|
{(!!likeCount || likeCount === 0) && (
|
||||||
|
<span>
|
||||||
|
Liked by {likeCount} user{likeCount !== 1 && 's'}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="card-actions">
|
||||||
|
{user && (
|
||||||
|
<BreweryPostLikeButton
|
||||||
|
breweryPostId={breweryPost.id}
|
||||||
|
mutateCount={mutate}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface BreweryMapProps {
|
||||||
|
latitude: number;
|
||||||
|
longitude: number;
|
||||||
|
}
|
||||||
|
const BreweryMap: FC<BreweryMapProps> = ({ latitude, longitude }) => {
|
||||||
|
return (
|
||||||
|
<MapGL
|
||||||
|
initialViewState={{
|
||||||
|
latitude,
|
||||||
|
longitude,
|
||||||
|
zoom: 17,
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: 450,
|
||||||
|
}}
|
||||||
|
mapStyle="mapbox://styles/mapbox/streets-v12"
|
||||||
|
mapboxAccessToken={process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN as string}
|
||||||
|
scrollZoom={true}
|
||||||
|
>
|
||||||
|
<Marker latitude={latitude} longitude={longitude} />
|
||||||
|
</MapGL>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const BreweryByIdPage: NextPage<BreweryPageProps> = ({ breweryPost }) => {
|
const BreweryByIdPage: NextPage<BreweryPageProps> = ({ breweryPost }) => {
|
||||||
|
const [longitude, latitude] = breweryPost.coordinates;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1 className="text-3xl font-bold underline">{breweryPost.name}</h1>
|
<Head>
|
||||||
|
<title>{breweryPost.name}</title>
|
||||||
|
<meta name="description" content={breweryPost.description} />
|
||||||
|
</Head>
|
||||||
|
|
||||||
|
<>
|
||||||
|
<Carousel
|
||||||
|
className="w-full"
|
||||||
|
useKeyboardArrows
|
||||||
|
autoPlay
|
||||||
|
interval={10000}
|
||||||
|
infiniteLoop
|
||||||
|
showThumbs={false}
|
||||||
|
>
|
||||||
|
{breweryPost.breweryImages.length
|
||||||
|
? breweryPost.breweryImages.map((image, index) => (
|
||||||
|
<div key={image.id} id={`image-${index}}`} className="w-full">
|
||||||
|
<Image
|
||||||
|
alt={image.alt}
|
||||||
|
src={image.path}
|
||||||
|
height={1080}
|
||||||
|
width={1920}
|
||||||
|
className="h-96 w-full object-cover lg:h-[42rem]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
: Array.from({ length: 1 }).map((_, i) => (
|
||||||
|
<div className="h-96 lg:h-[42rem]" key={i} />
|
||||||
|
))}
|
||||||
|
</Carousel>
|
||||||
|
<div className="mb-12 mt-10 flex w-full items-center justify-center">
|
||||||
|
<div className="w-11/12 space-y-3 xl:w-9/12 2xl:w-8/12">
|
||||||
|
<BreweryInfoHeader breweryPost={breweryPost} />
|
||||||
|
|
||||||
|
<BreweryMap latitude={latitude} longitude={longitude} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
15
src/prisma/migrations/20230424192859_/migration.sql
Normal file
15
src/prisma/migrations/20230424192859_/migration.sql
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `location` on the `BreweryPost` table. All the data in the column will be lost.
|
||||||
|
- Added the required column `address` to the `BreweryPost` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Added the required column `city` to the `BreweryPost` table without a default value. This is not possible if the table is not empty.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "BreweryPost" DROP COLUMN "location";
|
||||||
|
ALTER TABLE "BreweryPost" ADD COLUMN "address" STRING NOT NULL;
|
||||||
|
ALTER TABLE "BreweryPost" ADD COLUMN "city" STRING NOT NULL;
|
||||||
|
ALTER TABLE "BreweryPost" ADD COLUMN "coordinates" FLOAT8[];
|
||||||
|
ALTER TABLE "BreweryPost" ADD COLUMN "country" STRING;
|
||||||
|
ALTER TABLE "BreweryPost" ADD COLUMN "stateOrProvince" STRING;
|
||||||
@@ -96,7 +96,11 @@ model BeerType {
|
|||||||
model BreweryPost {
|
model BreweryPost {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
name String
|
name String
|
||||||
location String
|
city String
|
||||||
|
stateOrProvince String?
|
||||||
|
country String?
|
||||||
|
coordinates Float[]
|
||||||
|
address String
|
||||||
beers BeerPost[]
|
beers BeerPost[]
|
||||||
description String
|
description String
|
||||||
createdAt DateTime @default(now()) @db.Timestamptz(3)
|
createdAt DateTime @default(now()) @db.Timestamptz(3)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import logger from '@/config/pino/logger';
|
import logger from '../../../config/pino/logger';
|
||||||
import cleanDatabase from './cleanDatabase';
|
import cleanDatabase from './cleanDatabase';
|
||||||
|
|
||||||
cleanDatabase().then(() => {
|
cleanDatabase().then(() => {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { faker } from '@faker-js/faker';
|
import { faker } from '@faker-js/faker';
|
||||||
import { User } from '@prisma/client';
|
import { User } from '@prisma/client';
|
||||||
import DBClient from '../../DBClient';
|
import DBClient from '../../DBClient';
|
||||||
|
import geocode from '../../../config/mapbox/geocoder';
|
||||||
|
|
||||||
interface CreateNewBreweryPostsArgs {
|
interface CreateNewBreweryPostsArgs {
|
||||||
numberOfPosts: number;
|
numberOfPosts: number;
|
||||||
@@ -21,6 +22,15 @@ const createNewBreweryPosts = async ({
|
|||||||
for (let i = 0; i < numberOfPosts; i++) {
|
for (let i = 0; i < numberOfPosts; i++) {
|
||||||
const name = `${faker.commerce.productName()} Brewing Company`;
|
const name = `${faker.commerce.productName()} Brewing Company`;
|
||||||
const location = faker.address.cityName();
|
const location = faker.address.cityName();
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
const geodata = await geocode(location);
|
||||||
|
|
||||||
|
const city = geodata.text;
|
||||||
|
const stateOrProvince = geodata.context.find((c) => c.id.startsWith('region'))?.text;
|
||||||
|
const country = geodata.context.find((c) => c.id.startsWith('country'))?.text;
|
||||||
|
const coordinates = geodata.center;
|
||||||
|
const address = geodata.place_name;
|
||||||
const description = faker.lorem.lines(5);
|
const description = faker.lorem.lines(5);
|
||||||
const user = users[Math.floor(Math.random() * users.length)];
|
const user = users[Math.floor(Math.random() * users.length)];
|
||||||
const createdAt = faker.date.past(1);
|
const createdAt = faker.date.past(1);
|
||||||
@@ -30,7 +40,13 @@ const createNewBreweryPosts = async ({
|
|||||||
prisma.breweryPost.create({
|
prisma.breweryPost.create({
|
||||||
data: {
|
data: {
|
||||||
name,
|
name,
|
||||||
location,
|
|
||||||
|
city,
|
||||||
|
stateOrProvince,
|
||||||
|
country,
|
||||||
|
coordinates,
|
||||||
|
address,
|
||||||
|
|
||||||
description,
|
description,
|
||||||
postedBy: { connect: { id: user.id } },
|
postedBy: { connect: { id: user.id } },
|
||||||
createdAt,
|
createdAt,
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import createNewBreweryPostLikes from './create/createNewBreweryPostLikes';
|
|||||||
|
|
||||||
const users = await createNewUsers({ numberOfUsers: 1000 });
|
const users = await createNewUsers({ numberOfUsers: 1000 });
|
||||||
const [breweryPosts, beerTypes] = await Promise.all([
|
const [breweryPosts, beerTypes] = await Promise.all([
|
||||||
createNewBreweryPosts({ numberOfPosts: 100, joinData: { users } }),
|
createNewBreweryPosts({ numberOfPosts: 30, joinData: { users } }),
|
||||||
createNewBeerTypes({ joinData: { users } }),
|
createNewBeerTypes({ joinData: { users } }),
|
||||||
]);
|
]);
|
||||||
const beerPosts = await createNewBeerPosts({
|
const beerPosts = await createNewBeerPosts({
|
||||||
|
|||||||
@@ -14,7 +14,12 @@ const getAllBreweryPosts = async (pageNum?: number, pageSize?: number) => {
|
|||||||
take,
|
take,
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
location: true,
|
coordinates: true,
|
||||||
|
address: true,
|
||||||
|
city: true,
|
||||||
|
stateOrProvince: true,
|
||||||
|
country: true,
|
||||||
|
description: true,
|
||||||
name: true,
|
name: true,
|
||||||
postedBy: { select: { username: true, id: true } },
|
postedBy: { select: { username: true, id: true } },
|
||||||
breweryImages: { select: { path: true, caption: true, id: true, alt: true } },
|
breweryImages: { select: { path: true, caption: true, id: true, alt: true } },
|
||||||
|
|||||||
@@ -9,7 +9,12 @@ const getBreweryPostById = async (id: string) => {
|
|||||||
await prisma.breweryPost.findFirst({
|
await prisma.breweryPost.findFirst({
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
location: true,
|
coordinates: true,
|
||||||
|
address: true,
|
||||||
|
city: true,
|
||||||
|
stateOrProvince: true,
|
||||||
|
country: true,
|
||||||
|
description: true,
|
||||||
name: true,
|
name: true,
|
||||||
breweryImages: { select: { path: true, caption: true, id: true, alt: true } },
|
breweryImages: { select: { path: true, caption: true, id: true, alt: true } },
|
||||||
postedBy: { select: { username: true, id: true } },
|
postedBy: { select: { username: true, id: true } },
|
||||||
|
|||||||
@@ -2,8 +2,13 @@ import { z } from 'zod';
|
|||||||
|
|
||||||
const BreweryPostQueryResult = z.object({
|
const BreweryPostQueryResult = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
location: z.string(),
|
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
|
description: z.string(),
|
||||||
|
address: z.string(),
|
||||||
|
city: z.string(),
|
||||||
|
stateOrProvince: z.string().or(z.null()),
|
||||||
|
coordinates: z.array(z.number()),
|
||||||
|
country: z.string().or(z.null()),
|
||||||
postedBy: z.object({ id: z.string(), username: z.string() }),
|
postedBy: z.object({ id: z.string(), username: z.string() }),
|
||||||
breweryImages: z.array(
|
breweryImages: z.array(
|
||||||
z.object({ path: z.string(), caption: z.string(), id: z.string(), alt: z.string() }),
|
z.object({ path: z.string(), caption: z.string(), id: z.string(), alt: z.string() }),
|
||||||
|
|||||||
Reference in New Issue
Block a user