From c19cddceb7a7297640480e21f1084d0cc8f8f2b0 Mon Sep 17 00:00:00 2001 From: Aaron William Po Date: Wed, 26 Apr 2023 08:37:59 -0400 Subject: [PATCH] Create location table for brewery post locations --- package.json | 2 +- src/components/BeerIndex/BeerCard.tsx | 4 +- src/components/BreweryIndex/BreweryCard.tsx | 3 +- src/hooks/useBeerPostComments.ts | 2 + src/pages/breweries/[id].tsx | 6 +- .../migrations/20230426013222_/migration.sql | 41 ++++++++++++ src/prisma/schema.prisma | 16 ++++- src/prisma/seed/create/createNewBeerPosts.ts | 16 +++-- .../seed/create/createNewBreweryPosts.ts | 32 +++------- src/prisma/seed/create/createNewLocations.ts | 63 +++++++++++++++++++ src/prisma/seed/create/createNewUsers.ts | 15 +++++ src/prisma/seed/index.ts | 19 +++++- .../BreweryPost/getAllBreweryPosts.ts | 15 +++-- .../BreweryPost/getBreweryPostById.ts | 14 +++-- .../types/BreweryPostQueryResult.ts | 12 ++-- src/services/User/findUserByEmail.ts | 2 +- src/services/User/findUserByUsername.ts | 2 +- 17 files changed, 209 insertions(+), 55 deletions(-) create mode 100644 src/prisma/migrations/20230426013222_/migration.sql create mode 100644 src/prisma/seed/create/createNewLocations.ts diff --git a/package.json b/package.json index ce99d88..7ecb969 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "start": "next start", "lint": "next lint", "format": "npx prettier . --write", - "seed": "npx ts-node ./src/prisma/seed/index.ts" + "seed": "npx --max-old-space-size=4096 ts-node ./src/prisma/seed/index.ts" }, "dependencies": { "@hapi/iron": "^7.0.1", diff --git a/src/components/BeerIndex/BeerCard.tsx b/src/components/BeerIndex/BeerCard.tsx index 0e331af..5b597e0 100644 --- a/src/components/BeerIndex/BeerCard.tsx +++ b/src/components/BeerIndex/BeerCard.tsx @@ -44,7 +44,9 @@ const BeerCard: FC<{ post: z.infer }> = ({ post }) = {post.abv}% ABV {post.ibu} IBU - liked by {likeCount} users + + liked by {likeCount} user{likeCount === 1 ? '' : 's'} +
{user && } diff --git a/src/components/BreweryIndex/BreweryCard.tsx b/src/components/BreweryIndex/BreweryCard.tsx index b213ba2..fb85024 100644 --- a/src/components/BreweryIndex/BreweryCard.tsx +++ b/src/components/BreweryIndex/BreweryCard.tsx @@ -32,7 +32,8 @@ const BreweryCard: FC<{ brewery: z.infer }> = ({

- located in {brewery.city}, {brewery.stateOrProvince || brewery.country} + located in {brewery.location.city},{' '} + {brewery.location.stateOrProvince || brewery.location.country}

est. {brewery.dateEstablished.getFullYear()} diff --git a/src/hooks/useBeerPostComments.ts b/src/hooks/useBeerPostComments.ts index 3a75e56..b72afea 100644 --- a/src/hooks/useBeerPostComments.ts +++ b/src/hooks/useBeerPostComments.ts @@ -54,6 +54,8 @@ const useBeerPostComments = ({ id, pageSize }: UseBeerPostCommentsProps) => { const isAtEnd = !(size < data?.[0].pageCount!); + console.log(comments); + return { comments, isLoading, diff --git a/src/pages/breweries/[id].tsx b/src/pages/breweries/[id].tsx index 6c4c8cd..354db49 100644 --- a/src/pages/breweries/[id].tsx +++ b/src/pages/breweries/[id].tsx @@ -43,8 +43,8 @@ const BreweryInfoHeader: FC = ({ breweryPost }) => {

{breweryPost.name}

Located in - {` ${breweryPost.city}, ${ - breweryPost.stateOrProvince || breweryPost.country + {` ${breweryPost.location.city}, ${ + breweryPost.location.stateOrProvince || breweryPost.location.country }`}

@@ -130,7 +130,7 @@ const BreweryMap: FC = ({ latitude, longitude }) => { }; const BreweryByIdPage: NextPage = ({ breweryPost }) => { - const [longitude, latitude] = breweryPost.coordinates; + const [longitude, latitude] = breweryPost.location.coordinates; return ( <> diff --git a/src/prisma/migrations/20230426013222_/migration.sql b/src/prisma/migrations/20230426013222_/migration.sql new file mode 100644 index 0000000..eb71625 --- /dev/null +++ b/src/prisma/migrations/20230426013222_/migration.sql @@ -0,0 +1,41 @@ +/* + Warnings: + + - You are about to drop the column `address` on the `BreweryPost` table. All the data in the column will be lost. + - You are about to drop the column `city` on the `BreweryPost` table. All the data in the column will be lost. + - You are about to drop the column `coordinates` on the `BreweryPost` table. All the data in the column will be lost. + - You are about to drop the column `country` on the `BreweryPost` table. All the data in the column will be lost. + - You are about to drop the column `stateOrProvince` on the `BreweryPost` table. All the data in the column will be lost. + - A unique constraint covering the columns `[locationId]` on the table `BreweryPost` will be added. If there are existing duplicate values, this will fail. + - Added the required column `locationId` to the `BreweryPost` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "BreweryPost" DROP COLUMN "address"; +ALTER TABLE "BreweryPost" DROP COLUMN "city"; +ALTER TABLE "BreweryPost" DROP COLUMN "coordinates"; +ALTER TABLE "BreweryPost" DROP COLUMN "country"; +ALTER TABLE "BreweryPost" DROP COLUMN "stateOrProvince"; +ALTER TABLE "BreweryPost" ADD COLUMN "locationId" STRING NOT NULL; + +-- CreateTable +CREATE TABLE "Location" ( + "id" STRING NOT NULL, + "city" STRING NOT NULL, + "stateOrProvince" STRING, + "country" STRING, + "coordinates" FLOAT8[], + "address" STRING NOT NULL, + "postedById" STRING NOT NULL, + + CONSTRAINT "Location_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "BreweryPost_locationId_key" ON "BreweryPost"("locationId"); + +-- AddForeignKey +ALTER TABLE "Location" ADD CONSTRAINT "Location_postedById_fkey" FOREIGN KEY ("postedById") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "BreweryPost" ADD CONSTRAINT "BreweryPost_locationId_fkey" FOREIGN KEY ("locationId") REFERENCES "Location"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/src/prisma/schema.prisma b/src/prisma/schema.prisma index 678e4a8..0c090e2 100644 --- a/src/prisma/schema.prisma +++ b/src/prisma/schema.prisma @@ -30,6 +30,7 @@ model User { BeerImage BeerImage[] BreweryImage BreweryImage[] BreweryPostLike BreweryPostLike[] + Location Location[] } model BeerPost { @@ -93,14 +94,23 @@ model BeerType { beerPosts BeerPost[] } -model BreweryPost { - id String @id @default(uuid()) - name String +model Location { + id String @id @default(uuid()) city String stateOrProvince String? country String? coordinates Float[] address String + postedBy User @relation(fields: [postedById], references: [id], onDelete: Cascade) + postedById String + BreweryPost BreweryPost? +} + +model BreweryPost { + id String @id @default(uuid()) + name String + location Location @relation(fields: [locationId], references: [id]) + locationId String @unique beers BeerPost[] description String createdAt DateTime @default(now()) @db.Timestamptz(3) diff --git a/src/prisma/seed/create/createNewBeerPosts.ts b/src/prisma/seed/create/createNewBeerPosts.ts index 6b37095..bd0b382 100644 --- a/src/prisma/seed/create/createNewBeerPosts.ts +++ b/src/prisma/seed/create/createNewBeerPosts.ts @@ -26,17 +26,23 @@ const createNewBeerPosts = async ({ const beerType = beerTypes[Math.floor(Math.random() * beerTypes.length)]; const breweryPost = breweryPosts[Math.floor(Math.random() * breweryPosts.length)]; const createdAt = faker.date.past(1); + + const abv = Math.floor(Math.random() * (12 - 4) + 4); + const ibu = Math.floor(Math.random() * (60 - 10) + 10); + const name = faker.commerce.productName(); + const description = faker.lorem.lines(20).replace(/(\r\n|\n|\r)/gm, ' '); + beerPostPromises.push( prisma.beerPost.create({ data: { - abv: Math.floor(Math.random() * (12 - 4) + 4), - ibu: Math.floor(Math.random() * (60 - 10) + 10), - name: faker.commerce.productName(), - description: faker.lorem.lines(12).replace(/(\r\n|\n|\r)/gm, ' '), + abv, + ibu, + name, + description, + createdAt, brewery: { connect: { id: breweryPost.id } }, postedBy: { connect: { id: user.id } }, type: { connect: { id: beerType.id } }, - createdAt, }, }), ); diff --git a/src/prisma/seed/create/createNewBreweryPosts.ts b/src/prisma/seed/create/createNewBreweryPosts.ts index 3fb13a1..d4f3687 100644 --- a/src/prisma/seed/create/createNewBreweryPosts.ts +++ b/src/prisma/seed/create/createNewBreweryPosts.ts @@ -1,13 +1,13 @@ // eslint-disable-next-line import/no-extraneous-dependencies import { faker } from '@faker-js/faker'; -import { User } from '@prisma/client'; +import { Location, User } from '@prisma/client'; import DBClient from '../../DBClient'; -import geocode from '../../../config/mapbox/geocoder'; interface CreateNewBreweryPostsArgs { numberOfPosts: number; joinData: { users: User[]; + locations: Location[]; }; } @@ -15,23 +15,17 @@ const createNewBreweryPosts = async ({ numberOfPosts, joinData, }: CreateNewBreweryPostsArgs) => { - const { users } = joinData; + const { users, locations } = joinData; + const prisma = DBClient.instance; const breweryPromises = []; // eslint-disable-next-line no-plusplus for (let i = 0; i < numberOfPosts; i++) { const name = `${faker.commerce.productName()} Brewing Company`; - 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 locationIndex = Math.floor(Math.random() * locations.length); + const location = locations[locationIndex]; + locations.splice(locationIndex, 1); // Remove the location from the array + const description = faker.lorem.lines(20).replace(/(\r\n|\n|\r)/gm, ' '); const user = users[Math.floor(Math.random() * users.length)]; const createdAt = faker.date.past(1); const dateEstablished = faker.date.past(40); @@ -40,17 +34,11 @@ const createNewBreweryPosts = async ({ prisma.breweryPost.create({ data: { name, - - city, - stateOrProvince, - country, - coordinates, - address, - description, - postedBy: { connect: { id: user.id } }, createdAt, dateEstablished, + postedBy: { connect: { id: user.id } }, + location: { connect: { id: location.id } }, }, }), ); diff --git a/src/prisma/seed/create/createNewLocations.ts b/src/prisma/seed/create/createNewLocations.ts new file mode 100644 index 0000000..581ae52 --- /dev/null +++ b/src/prisma/seed/create/createNewLocations.ts @@ -0,0 +1,63 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import { faker } from '@faker-js/faker'; +import { User, Location } from '@prisma/client'; +import { GeocodeFeature } from '@mapbox/mapbox-sdk/services/geocoding'; +import DBClient from '../../DBClient'; +import geocode from '../../../config/mapbox/geocoder'; + +interface CreateNewLocationsArgs { + numberOfLocations: number; + joinData: { + users: User[]; + }; +} + +const createNewLocations = async ({ + numberOfLocations, + joinData, +}: CreateNewLocationsArgs) => { + const prisma = DBClient.instance; + + const locationNames: string[] = []; + + // eslint-disable-next-line no-plusplus + for (let i = 0; i < numberOfLocations; i++) { + locationNames.push(faker.address.cityName()); + } + + const geocodePromises: Promise[] = []; + + locationNames.forEach((locationName) => { + geocodePromises.push(geocode(locationName)); + }); + + const geocodedLocations = await Promise.all(geocodePromises); + + const locationPromises: Promise[] = []; + + geocodedLocations.forEach((geodata) => { + const city = geodata.text; + const user = joinData.users[Math.floor(Math.random() * joinData.users.length)]; + 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; + + locationPromises.push( + prisma.location.create({ + data: { + city, + stateOrProvince, + country, + coordinates, + address, + postedBy: { connect: { id: user.id } }, + }, + }), + ); + }); + + return Promise.all(locationPromises); +}; + +export default createNewLocations; diff --git a/src/prisma/seed/create/createNewUsers.ts b/src/prisma/seed/create/createNewUsers.ts index 62da73b..87a2ac9 100644 --- a/src/prisma/seed/create/createNewUsers.ts +++ b/src/prisma/seed/create/createNewUsers.ts @@ -16,6 +16,9 @@ const createNewUsers = async ({ numberOfUsers }: CreateNewUsersArgs) => { Array.from({ length: numberOfUsers }, () => argon2.hash(faker.internet.password())), ); + const takenEmails: string[] = []; + const takenUsernames: string[] = []; + // eslint-disable-next-line no-plusplus for (let i = 0; i < numberOfUsers; i++) { const randomValue = crypto.randomBytes(10).toString('hex'); @@ -24,6 +27,18 @@ const createNewUsers = async ({ numberOfUsers }: CreateNewUsersArgs) => { const username = `${firstName[0]}.${lastName}.${randomValue}`; const email = faker.internet.email(firstName, randomValue, 'example.com'); + const usernameTaken = takenUsernames.includes(username); + const emailTaken = takenEmails.includes(email); + + if (usernameTaken || emailTaken) { + i -= 1; + // eslint-disable-next-line no-continue + continue; + } + + takenEmails.push(email); + takenUsernames.push(username); + const hash = hashedPasswords[i]; const dateOfBirth = faker.date.birthdate({ mode: 'age', min: 19 }); const createdAt = faker.date.past(1); diff --git a/src/prisma/seed/index.ts b/src/prisma/seed/index.ts index 6f68ffb..85e4c7e 100644 --- a/src/prisma/seed/index.ts +++ b/src/prisma/seed/index.ts @@ -14,6 +14,7 @@ import createNewBreweryPostComments from './create/createNewBreweryPostComments' import createNewBreweryPosts from './create/createNewBreweryPosts'; import createNewUsers from './create/createNewUsers'; import createNewBreweryPostLikes from './create/createNewBreweryPostLikes'; +import createNewLocations from './create/createNewLocations'; (async () => { try { @@ -25,15 +26,25 @@ import createNewBreweryPostLikes from './create/createNewBreweryPostLikes'; logger.info('Database cleared successfully, preparing to seed.'); const users = await createNewUsers({ numberOfUsers: 1000 }); + logger.info('Users created successfully.'); + console.log(users); + + const locations = await createNewLocations({ + numberOfLocations: 1500, + joinData: { users }, + }); + logger.info('Locations created successfully.'); + const [breweryPosts, beerTypes] = await Promise.all([ - createNewBreweryPosts({ numberOfPosts: 30, joinData: { users } }), + createNewBreweryPosts({ numberOfPosts: 1300, joinData: { users, locations } }), createNewBeerTypes({ joinData: { users } }), ]); + logger.info('Brewery posts and beer types created successfully.'); const beerPosts = await createNewBeerPosts({ numberOfPosts: 200, joinData: { breweryPosts, beerTypes, users }, }); - + logger.info('Beer posts created successfully.'); const [ beerPostComments, breweryPostComments, @@ -67,6 +78,10 @@ import createNewBreweryPostLikes from './create/createNewBreweryPostLikes'; }), ]); + logger.info( + 'Beer post comments, brewery post comments, beer post likes, beer images, and brewery images created successfully.', + ); + const end = performance.now(); const timeElapsed = (end - start) / 1000; diff --git a/src/services/BreweryPost/getAllBreweryPosts.ts b/src/services/BreweryPost/getAllBreweryPosts.ts index 678bf40..a1a6b97 100644 --- a/src/services/BreweryPost/getAllBreweryPosts.ts +++ b/src/services/BreweryPost/getAllBreweryPosts.ts @@ -1,5 +1,6 @@ import DBClient from '@/prisma/DBClient'; import BreweryPostQueryResult from '@/services/BreweryPost/types/BreweryPostQueryResult'; + import { z } from 'zod'; const prisma = DBClient.instance; @@ -14,11 +15,15 @@ const getAllBreweryPosts = async (pageNum?: number, pageSize?: number) => { take, select: { id: true, - coordinates: true, - address: true, - city: true, - stateOrProvince: true, - country: true, + location: { + select: { + city: true, + address: true, + coordinates: true, + country: true, + stateOrProvince: true, + }, + }, description: true, name: true, postedBy: { select: { username: true, id: true } }, diff --git a/src/services/BreweryPost/getBreweryPostById.ts b/src/services/BreweryPost/getBreweryPostById.ts index 77d57fd..3bb1ecb 100644 --- a/src/services/BreweryPost/getBreweryPostById.ts +++ b/src/services/BreweryPost/getBreweryPostById.ts @@ -9,11 +9,15 @@ const getBreweryPostById = async (id: string) => { await prisma.breweryPost.findFirst({ select: { id: true, - coordinates: true, - address: true, - city: true, - stateOrProvince: true, - country: true, + location: { + select: { + city: true, + address: true, + coordinates: true, + country: true, + stateOrProvince: true, + }, + }, description: true, name: true, breweryImages: { select: { path: true, caption: true, id: true, alt: true } }, diff --git a/src/services/BreweryPost/types/BreweryPostQueryResult.ts b/src/services/BreweryPost/types/BreweryPostQueryResult.ts index 682bda0..f4a70d0 100644 --- a/src/services/BreweryPost/types/BreweryPostQueryResult.ts +++ b/src/services/BreweryPost/types/BreweryPostQueryResult.ts @@ -4,11 +4,13 @@ const BreweryPostQueryResult = z.object({ id: 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()), + location: z.object({ + city: z.string(), + address: z.string(), + coordinates: z.array(z.number()), + country: z.string().nullable(), + stateOrProvince: z.string().nullable(), + }), postedBy: z.object({ id: z.string(), username: z.string() }), breweryImages: z.array( z.object({ path: z.string(), caption: z.string(), id: z.string(), alt: z.string() }), diff --git a/src/services/User/findUserByEmail.ts b/src/services/User/findUserByEmail.ts index a3ea319..1fa85d4 100644 --- a/src/services/User/findUserByEmail.ts +++ b/src/services/User/findUserByEmail.ts @@ -1,4 +1,4 @@ -import DBClient from '@/prisma/DBClient'; +import DBClient from '../../prisma/DBClient'; const findUserByEmail = async (email: string) => DBClient.instance.user.findFirst({ diff --git a/src/services/User/findUserByUsername.ts b/src/services/User/findUserByUsername.ts index 3b2bfc5..1669e71 100644 --- a/src/services/User/findUserByUsername.ts +++ b/src/services/User/findUserByUsername.ts @@ -1,4 +1,4 @@ -import DBClient from '@/prisma/DBClient'; +import DBClient from '../../prisma/DBClient'; const findUserByUsername = async (username: string) => DBClient.instance.user.findFirst({