From 0cfb063c7ced0abad5e1c71672202011cf0e8ba7 Mon Sep 17 00:00:00 2001 From: hpkoh Date: Sun, 30 Oct 2022 21:14:02 +0800 Subject: [PATCH] [questions][chore] update to support location tb --- .../migration.sql | 39 ++++++++++++ apps/portal/prisma/schema.prisma | 51 +++++++++------- .../questions-question-encounter-router.ts | 38 +----------- ...uestions-question-encounter-user-router.ts | 1 - .../questions/questions-question-router.ts | 22 ++++--- apps/portal/src/types/questions.d.ts | 12 +++- .../questions/server/aggregate-encounters.ts | 60 +++++++++++++++---- 7 files changed, 146 insertions(+), 77 deletions(-) create mode 100644 apps/portal/prisma/migrations/20221030121147_use_roles_and_location_table/migration.sql diff --git a/apps/portal/prisma/migrations/20221030121147_use_roles_and_location_table/migration.sql b/apps/portal/prisma/migrations/20221030121147_use_roles_and_location_table/migration.sql new file mode 100644 index 00000000..df5103e4 --- /dev/null +++ b/apps/portal/prisma/migrations/20221030121147_use_roles_and_location_table/migration.sql @@ -0,0 +1,39 @@ +/* + Warnings: + + - You are about to drop the column `location` on the `QuestionsQuestionEncounter` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "QuestionsQuestionEncounter" DROP COLUMN "location", +ADD COLUMN "cityId" TEXT, +ADD COLUMN "countryId" TEXT, +ADD COLUMN "stateId" TEXT, +ALTER COLUMN "companyId" DROP NOT NULL; + +-- CreateIndex +CREATE INDEX "QuestionsAnswer_updatedAt_id_idx" ON "QuestionsAnswer"("updatedAt", "id"); + +-- CreateIndex +CREATE INDEX "QuestionsAnswer_upvotes_id_idx" ON "QuestionsAnswer"("upvotes", "id"); + +-- CreateIndex +CREATE INDEX "QuestionsAnswerComment_updatedAt_id_idx" ON "QuestionsAnswerComment"("updatedAt", "id"); + +-- CreateIndex +CREATE INDEX "QuestionsAnswerComment_upvotes_id_idx" ON "QuestionsAnswerComment"("upvotes", "id"); + +-- CreateIndex +CREATE INDEX "QuestionsQuestionComment_updatedAt_id_idx" ON "QuestionsQuestionComment"("updatedAt", "id"); + +-- CreateIndex +CREATE INDEX "QuestionsQuestionComment_upvotes_id_idx" ON "QuestionsQuestionComment"("upvotes", "id"); + +-- AddForeignKey +ALTER TABLE "QuestionsQuestionEncounter" ADD CONSTRAINT "QuestionsQuestionEncounter_countryId_fkey" FOREIGN KEY ("countryId") REFERENCES "Country"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "QuestionsQuestionEncounter" ADD CONSTRAINT "QuestionsQuestionEncounter_stateId_fkey" FOREIGN KEY ("stateId") REFERENCES "State"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "QuestionsQuestionEncounter" ADD CONSTRAINT "QuestionsQuestionEncounter_cityId_fkey" FOREIGN KEY ("cityId") REFERENCES "City"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/apps/portal/prisma/schema.prisma b/apps/portal/prisma/schema.prisma index 3e28ded6..bbd1d5f3 100644 --- a/apps/portal/prisma/schema.prisma +++ b/apps/portal/prisma/schema.prisma @@ -107,27 +107,30 @@ model Company { } model Country { - id String @id - name String @unique - code String @unique - states State[] + id String @id + name String @unique + code String @unique + states State[] + questionsQuestionEncounters QuestionsQuestionEncounter[] } model State { - id String @id - name String - countryId String - cities City[] - country Country @relation(fields: [countryId], references: [id]) + id String @id + name String + countryId String + cities City[] + country Country @relation(fields: [countryId], references: [id]) + questionsQuestionEncounters QuestionsQuestionEncounter[] @@unique([name, countryId]) } model City { - id String @id - name String - stateId String - state State @relation(fields: [stateId], references: [id]) + id String @id + name String + stateId String + state State @relation(fields: [stateId], references: [id]) + questionsQuestionEncounters QuestionsQuestionEncounter[] @@unique([name, stateId]) } @@ -435,12 +438,12 @@ model QuestionsQuestion { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - user User? @relation(fields: [userId], references: [id], onDelete: SetNull) - encounters QuestionsQuestionEncounter[] - votes QuestionsQuestionVote[] - comments QuestionsQuestionComment[] - answers QuestionsAnswer[] - QuestionsListQuestionEntry QuestionsListQuestionEntry[] + user User? @relation(fields: [userId], references: [id], onDelete: SetNull) + encounters QuestionsQuestionEncounter[] + votes QuestionsQuestionVote[] + comments QuestionsQuestionComment[] + answers QuestionsAnswer[] + questionsListQuestionEntries QuestionsListQuestionEntry[] @@index([lastSeenAt, id]) @@index([upvotes, id]) @@ -450,14 +453,18 @@ model QuestionsQuestionEncounter { id String @id @default(cuid()) questionId String userId String? - // TODO: sync with models (location, role) - companyId String - location String @db.Text + companyId String? + countryId String? + stateId String? + cityId String? role String @db.Text seenAt DateTime createdAt DateTime @default(now()) updatedAt DateTime @updatedAt + country Country? @relation(fields: [countryId], references: [id], onDelete: SetNull) + state State? @relation(fields: [stateId], references: [id], onDelete: SetNull) + city City? @relation(fields: [cityId], references: [id], onDelete: SetNull) company Company? @relation(fields: [companyId], references: [id], onDelete: SetNull) user User? @relation(fields: [userId], references: [id], onDelete: SetNull) question QuestionsQuestion @relation(fields: [questionId], references: [id], onDelete: Cascade) diff --git a/apps/portal/src/server/router/questions/questions-question-encounter-router.ts b/apps/portal/src/server/router/questions/questions-question-encounter-router.ts index 31b11ab4..7b0e7495 100644 --- a/apps/portal/src/server/router/questions/questions-question-encounter-router.ts +++ b/apps/portal/src/server/router/questions/questions-question-encounter-router.ts @@ -2,7 +2,7 @@ import { z } from 'zod'; import { createRouter } from '../context'; -import type { AggregatedQuestionEncounter } from '~/types/questions'; +import { createAggregatedQuestionEncounter } from '~/utils/questions/server/aggregate-encounters'; export const questionsQuestionEncounterRouter = createRouter().query( 'getAggregatedEncounters', @@ -21,41 +21,7 @@ export const questionsQuestionEncounterRouter = createRouter().query( }, }); - const companyCounts: Record = {}; - const locationCounts: Record = {}; - const roleCounts: Record = {}; - - let latestSeenAt = questionEncountersData[0].seenAt; - - for (let i = 0; i < questionEncountersData.length; i++) { - const encounter = questionEncountersData[i]; - - latestSeenAt = - latestSeenAt < encounter.seenAt ? encounter.seenAt : latestSeenAt; - - if (!(encounter.company!.name in companyCounts)) { - companyCounts[encounter.company!.name] = 1; - } - companyCounts[encounter.company!.name] += 1; - - if (!(encounter.location in locationCounts)) { - locationCounts[encounter.location] = 1; - } - locationCounts[encounter.location] += 1; - - if (!(encounter.role in roleCounts)) { - roleCounts[encounter.role] = 1; - } - roleCounts[encounter.role] += 1; - } - - const questionEncounter: AggregatedQuestionEncounter = { - companyCounts, - latestSeenAt, - locationCounts, - roleCounts, - }; - return questionEncounter; + return createAggregatedQuestionEncounter(questionEncountersData); }, }, ); diff --git a/apps/portal/src/server/router/questions/questions-question-encounter-user-router.ts b/apps/portal/src/server/router/questions/questions-question-encounter-user-router.ts index c7b544d7..ede3cf88 100644 --- a/apps/portal/src/server/router/questions/questions-question-encounter-user-router.ts +++ b/apps/portal/src/server/router/questions/questions-question-encounter-user-router.ts @@ -1,7 +1,6 @@ import { z } from 'zod'; import { TRPCError } from '@trpc/server'; -import { createAggregatedQuestionEncounter } from '~/utils/questions/server/aggregate-encounters'; import { createProtectedRouter } from '../context'; diff --git a/apps/portal/src/server/router/questions/questions-question-router.ts b/apps/portal/src/server/router/questions/questions-question-router.ts index cbac65b5..93d95d09 100644 --- a/apps/portal/src/server/router/questions/questions-question-router.ts +++ b/apps/portal/src/server/router/questions/questions-question-router.ts @@ -1,5 +1,6 @@ import { z } from 'zod'; import { QuestionsQuestionType } from '@prisma/client'; +import { JobTitleLabels } from '~/components/shared/JobTitles'; import { TRPCError } from '@trpc/server'; import { createQuestionWithAggregateData } from '~/utils/questions/server/aggregate-encounters'; @@ -11,13 +12,16 @@ import { SortOrder, SortType } from '~/types/questions.d'; export const questionsQuestionRouter = createRouter() .query('getQuestionsByFilter', { input: z.object({ - companyNames: z.string().array(), + companyIds: z.string().array(), cursor: z.string().nullish(), endDate: z.date().default(new Date()), limit: z.number().min(1).default(50), + countryIds: z.string().array(), + cityIds: z.string().array(), + stateIds: z.string().array(), locations: z.string().array(), questionTypes: z.nativeEnum(QuestionsQuestionType).array(), - roles: z.string().array(), + roles: z.nativeEnum(JobTitleLabels).array(), sortOrder: z.nativeEnum(SortOrder), sortType: z.nativeEnum(SortType), startDate: z.date().optional(), @@ -56,7 +60,9 @@ export const questionsQuestionRouter = createRouter() encounters: { select: { company: true, - location: true, + country: true, + city: true, + state: true, role: true, seenAt: true, }, @@ -84,11 +90,11 @@ export const questionsQuestionRouter = createRouter() gte: input.startDate, lte: input.endDate, }, - ...(input.companyNames.length > 0 + ...(input.companyIds.length > 0 ? { company: { - name: { - in: input.companyNames, + id: { + in: input.companyIds, }, }, } @@ -149,7 +155,9 @@ export const questionsQuestionRouter = createRouter() encounters: { select: { company: true, - location: true, + country: true, + city: true, + state: true, role: true, seenAt: true, }, diff --git a/apps/portal/src/types/questions.d.ts b/apps/portal/src/types/questions.d.ts index aea8d31e..8f64f707 100644 --- a/apps/portal/src/types/questions.d.ts +++ b/apps/portal/src/types/questions.d.ts @@ -14,10 +14,20 @@ export type Question = { user: string; }; +export type StateInfo = { + total: number; + cityCounts: Record; +}; + +export type CountryInfo = { + total: number; + stateInfos: Record; +}; + export type AggregatedQuestionEncounter = { companyCounts: Record; latestSeenAt: Date; - locationCounts: Record; + countryCounts: Record; roleCounts: Record; }; diff --git a/apps/portal/src/utils/questions/server/aggregate-encounters.ts b/apps/portal/src/utils/questions/server/aggregate-encounters.ts index a0fdacef..cc698d72 100644 --- a/apps/portal/src/utils/questions/server/aggregate-encounters.ts +++ b/apps/portal/src/utils/questions/server/aggregate-encounters.ts @@ -1,15 +1,20 @@ import type { + City, + State, + Country, Company, QuestionsQuestion, QuestionsQuestionVote, } from '@prisma/client'; import { Vote } from '@prisma/client'; -import type { AggregatedQuestionEncounter, Question } from '~/types/questions'; +import type { AggregatedQuestionEncounter, Question, CountryInfo} from '~/types/questions'; type AggregatableEncounters = Array<{ company: Company | null; - location: string; + city: City | null; + country: Country | null; + state: State | null; role: string; seenAt: Date; }>; @@ -67,8 +72,8 @@ export function createQuestionWithAggregateData( export function createAggregatedQuestionEncounter( encounters: AggregatableEncounters, ): AggregatedQuestionEncounter { + const countryCounts: Record = {}; const companyCounts: Record = {}; - const locationCounts: Record = {}; const roleCounts: Record = {}; let latestSeenAt = encounters[0].seenAt; @@ -77,15 +82,50 @@ export function createAggregatedQuestionEncounter( latestSeenAt = latestSeenAt < encounter.seenAt ? encounter.seenAt : latestSeenAt; - if (!(encounter.company!.name in companyCounts)) { - companyCounts[encounter.company!.name] = 0; + if (encounter.company !== null) { + if (!(encounter.company.name in companyCounts)) { + companyCounts[encounter.company!.name] = 0; + } + companyCounts[encounter.company!.name] += 1; } - companyCounts[encounter.company!.name] += 1; - if (!(encounter.location in locationCounts)) { - locationCounts[encounter.location] = 0; + + if (encounter.country !== null) { + if (!(encounter.country.name in countryCounts)) { + countryCounts[encounter.country.name] = { + total: 0, + stateInfos: {}, + }; + } + const countryInfo = countryCounts[encounter.country.name]; + + countryInfo.total += 1; + + const countryStateInfo = countryInfo.stateInfos; + + if (encounter.state !== null) { + if (!(encounter.state.name in countryStateInfo)) { + countryStateInfo[encounter.state.name] = { + total: 0, + cityCounts: {}, + }; + } + const stateInfo = countryStateInfo[encounter.state.name]; + + stateInfo.total += 1; + + const cityCounts = stateInfo.cityCounts; + + if (encounter.city !== null) { + if (!(encounter.city.name in cityCounts)) { + cityCounts[encounter.city.name] = 0; + } + cityCounts[encounter.city.name] += 1; + + } + } } - locationCounts[encounter.location] += 1; + if (!(encounter.role in roleCounts)) { roleCounts[encounter.role] = 0; @@ -96,7 +136,7 @@ export function createAggregatedQuestionEncounter( return { companyCounts, latestSeenAt, - locationCounts, + countryCounts, roleCounts, }; }