From ec03496d80bd6bb745736b8e8647173be17a28dd Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Fri, 9 Feb 2024 16:48:03 -0500 Subject: [PATCH] feat: join tables for OrgWebsite & OrgSocialMedia (#1081) * schema update: join tables for websites & social media * add "IF NOT EXISTS" to index creation --- .../router/location/query.getById.handler.ts | 10 +- .../location/query.getByOrgId.handler.ts | 10 +- .../misc/query.hasContactInfo.handler.ts | 6 +- .../orgSocialMedia/mutation.update.handler.ts | 2 +- .../query.forContactInfo.handler.ts | 4 +- .../query.forContactInfoEdits.handler.ts | 2 +- .../query.forEditDrawer.handler.ts | 4 +- .../orgWebsite/mutation.create.schema.ts | 2 +- .../query.forContactInfo.handler.ts | 4 +- .../query.forContactInfoEdit.handler.ts | 4 +- packages/api/schemas/create/orgSocialMedia.ts | 2 +- packages/api/schemas/create/orgWebsite.ts | 6 +- packages/api/schemas/selects/org.ts | 7 +- packages/api/selects/global.ts | 2 +- packages/db/lib/idGen.ts | 23 +- .../migration.sql | 273 ++++++++++++++++++ packages/db/prisma/schema.prisma | 99 ++++++- 17 files changed, 411 insertions(+), 49 deletions(-) create mode 100644 packages/db/prisma/migrations/20240209180602_indexing_and_join_tables_for_websites_and_social_media/migration.sql diff --git a/packages/api/router/location/query.getById.handler.ts b/packages/api/router/location/query.getById.handler.ts index 634f741b7e2..f2e03aafe7a 100644 --- a/packages/api/router/location/query.getById.handler.ts +++ b/packages/api/router/location/query.getById.handler.ts @@ -30,14 +30,20 @@ export const getById = async ({ ctx, input }: TRPCHandlerParams) ...select.attributes(), }, emails: { where: { email: globalWhere.isPublic() }, ...select.orgEmail() }, - websites: { where: globalWhere.isPublic(), ...globalSelect.orgWebsite() }, + websites: { + where: { website: globalWhere.isPublic() }, + select: { website: globalSelect.orgWebsite() }, + }, phones: { where: { phone: globalWhere.isPublic() }, ...select.orgPhone() }, photos: { where: globalWhere.isPublic(), ...globalSelect.orgPhoto() }, hours: globalSelect.hours(), reviews: { where: { visible: true, deleted: false }, select: { id: true } }, services: { where: { service: globalWhere.isPublic() }, ...select.service(ctx) }, serviceAreas: globalSelect.serviceArea(), - socialMedia: { where: globalWhere.isPublic(), ...globalSelect.socialMedia() }, + socialMedia: { + where: { socialMedia: globalWhere.isPublic() }, + select: { socialMedia: globalSelect.socialMedia() }, + }, description: globalSelect.freeText(), name: true, street1: true, diff --git a/packages/api/router/location/query.getByOrgId.handler.ts b/packages/api/router/location/query.getByOrgId.handler.ts index d7422c7551b..e9bc30253d2 100644 --- a/packages/api/router/location/query.getByOrgId.handler.ts +++ b/packages/api/router/location/query.getByOrgId.handler.ts @@ -34,14 +34,20 @@ export const getByOrgId = async ({ ctx, input }: TRPCHandlerParams) => { +export const forEditDrawer = async ({ input }: TRPCHandlerParams) => { try { const result = await prisma.orgSocialMedia.findUnique({ where: input, @@ -16,7 +16,7 @@ export const forEditDrawer = async ({ ctx, input }: TRPCHandlerParams diff --git a/packages/api/router/orgWebsite/query.forContactInfo.handler.ts b/packages/api/router/orgWebsite/query.forContactInfo.handler.ts index 4d78ee92bb5..45edbfc9b09 100644 --- a/packages/api/router/orgWebsite/query.forContactInfo.handler.ts +++ b/packages/api/router/orgWebsite/query.forContactInfo.handler.ts @@ -13,13 +13,13 @@ const whereId = (input: TForContactInfoSchema, isSingleLoc?: boolean): Prisma.Or ? { OR: [ { organization: { id: input.parentId, ...isPublic } }, - { orgLocation: { organization: { id: input.parentId, ...isPublic } } }, + { locations: { every: { location: { organization: { id: input.parentId, ...isPublic } } } } }, ], } : { organization: { id: input.parentId, ...isPublic } } } case isIdFor('orgLocation', input.parentId): { - return { orgLocation: { id: input.parentId, ...isPublic } } + return { locations: { some: { location: { id: input.parentId, ...isPublic } } } } } default: { diff --git a/packages/api/router/orgWebsite/query.forContactInfoEdit.handler.ts b/packages/api/router/orgWebsite/query.forContactInfoEdit.handler.ts index 3e30629e3ca..6a7575941de 100644 --- a/packages/api/router/orgWebsite/query.forContactInfoEdit.handler.ts +++ b/packages/api/router/orgWebsite/query.forContactInfoEdit.handler.ts @@ -10,7 +10,7 @@ const whereId = (input: TForContactInfoEditSchema): Prisma.OrgWebsiteWhereInput return { organization: { id: input.parentId } } } case isIdFor('orgLocation', input.parentId): { - return { orgLocation: { id: input.parentId } } + return { locations: { some: { location: { id: input.parentId } } } } } default: { @@ -18,7 +18,7 @@ const whereId = (input: TForContactInfoEditSchema): Prisma.OrgWebsiteWhereInput } } } -export const forContactInfoEdit = async ({ ctx, input }: TRPCHandlerParams) => { +export const forContactInfoEdit = async ({ input }: TRPCHandlerParams) => { try { const result = await prisma.orgWebsite.findMany({ where: { diff --git a/packages/api/schemas/create/orgSocialMedia.ts b/packages/api/schemas/create/orgSocialMedia.ts index e2bcab22213..29fb1ade2a6 100644 --- a/packages/api/schemas/create/orgSocialMedia.ts +++ b/packages/api/schemas/create/orgSocialMedia.ts @@ -25,7 +25,7 @@ export const CreateNestedOrgSocialMediaSchema = z .transform((data) => Prisma.validator< Prisma.Enumerable< - Prisma.OrgSocialMediaCreateManyOrganizationInput | Prisma.OrgSocialMediaCreateManyOrgLocationInput + Prisma.OrgSocialMediaCreateManyOrganizationInput | Prisma.OrgSocialMedia$locationsArgs > >()(data) ) diff --git a/packages/api/schemas/create/orgWebsite.ts b/packages/api/schemas/create/orgWebsite.ts index 47259165c31..d02196363b7 100644 --- a/packages/api/schemas/create/orgWebsite.ts +++ b/packages/api/schemas/create/orgWebsite.ts @@ -32,7 +32,7 @@ export const CreateOrgWebsiteSchema = z orgLocationOnly, description, organization: connectOneId(organizationId), - orgLocation: connectOneId(orgLocationId), + locations: orgLocationId ? { create: { orgLocationId } } : undefined, }) }) @@ -43,9 +43,7 @@ export const CreateNestedOrgWebsiteSchema = z .array() .transform((data) => Prisma.validator< - Prisma.Enumerable< - Prisma.OrgWebsiteCreateManyOrganizationInput | Prisma.OrgWebsiteCreateManyOrgLocationInput - > + Prisma.Enumerable >()(data) ) diff --git a/packages/api/schemas/selects/org.ts b/packages/api/schemas/selects/org.ts index de54c9df60c..53232acedb9 100644 --- a/packages/api/schemas/selects/org.ts +++ b/packages/api/schemas/selects/org.ts @@ -116,7 +116,7 @@ const orgWebsiteInclude = { languages: { select: { language: languageSelect } }, url: true, isPrimary: true, - orgLocationId: true, + locations: { select: { orgLocationId: true } }, orgLocationOnly: true, }, } satisfies Prisma.Organization$websitesArgs @@ -241,14 +241,15 @@ export const orgLocationInclude = (ctx: Omit) => country: countryWithoutGeo, attributes: attributeInclude, emails: orgEmailInclude, - websites: orgWebsiteInclude, + // websites: orgWebsiteInclude, + websites: { select: { website: orgWebsiteInclude } }, phones: phoneSelectPublic, photos: photoSelect, hours: hoursSelect, reviews: reviewIds, services: orgLocationServiceInclude(ctx), serviceAreas: serviceAreaInclude, - socialMedia: orgSocialMediaInclude, + socialMedia: { select: { socialMedia: orgSocialMediaInclude } }, description: freeText, name: true, street1: true, diff --git a/packages/api/selects/global.ts b/packages/api/selects/global.ts index c68b6441c59..89cecb30c54 100644 --- a/packages/api/selects/global.ts +++ b/packages/api/selects/global.ts @@ -100,7 +100,7 @@ export const globalSelect = { languages: { select: { language: this.language() } }, url: true, isPrimary: true, - orgLocationId: true, + locations: { select: { orgLocationId: true } }, orgLocationOnly: true, }, } satisfies Prisma.OrgWebsiteDefaultArgs diff --git a/packages/db/lib/idGen.ts b/packages/db/lib/idGen.ts index 14757323683..0bc575b217e 100644 --- a/packages/db/lib/idGen.ts +++ b/packages/db/lib/idGen.ts @@ -1,6 +1,19 @@ import { type Prisma } from '@prisma/client' import { Ulid } from 'id128' +/** + * Tables that are not in the idPrefix or excludedTables + * + * If the type resolves to `never`, then everything is good. Otherwise, it will resolve to the missing table + * name(s) + * + * This is for utility purposes. + */ +type _TablesNotInIdPrefix = Exclude< + Prisma.ModelName, + Capitalize | (typeof excludedTables)[number] +> + export const idPrefix = { account: 'acct', attribute: 'attr', @@ -114,23 +127,16 @@ const excludedTables = [ 'OrgServiceTag', 'ServiceAreaCountry', 'ServiceAreaDist', - 'OrganizationAttribute', - 'LocationAttribute', - 'ServiceAttribute', - 'ServiceAccessAttribute', 'AttributeToCategory', 'ServiceCategoryDefaultAttribute', 'ServiceTagDefaultAttribute', 'orgsearchresults', - 'attributeandcategory', - 'servicetagcategory', 'VerificationToken', 'TranslationNamespace', 'TranslationKey', 'OutsideAPIService', 'OrganizationPhone', 'OrganizationEmail', - 'UserAttribute', 'AttributeNesting', 'ServiceTagNesting', 'ServiceTagCountry', @@ -139,4 +145,7 @@ const excludedTables = [ 'user_access_token', 'AuditTrail', 'ServiceTagToCategory', + 'OrgLocationSocialMedia', + 'OrgLocationWebsite', + 'OrgServiceWebsite', ] as const // satisfies Prisma.ModelName[] diff --git a/packages/db/prisma/migrations/20240209180602_indexing_and_join_tables_for_websites_and_social_media/migration.sql b/packages/db/prisma/migrations/20240209180602_indexing_and_join_tables_for_websites_and_social_media/migration.sql new file mode 100644 index 00000000000..79eeb1e09b9 --- /dev/null +++ b/packages/db/prisma/migrations/20240209180602_indexing_and_join_tables_for_websites_and_social_media/migration.sql @@ -0,0 +1,273 @@ +/* + Warnings: + + - You are about to drop the column `orgLocationId` on the `OrgSocialMedia` table. All the data in the column will be lost. + - You are about to drop the column `orgLocationId` on the `OrgWebsite` table. All the data in the column will be lost. + */ +-- DropForeignKey +ALTER TABLE "OrgSocialMedia" + DROP CONSTRAINT "OrgSocialMedia_orgLocationId_fkey"; + +-- DropForeignKey +ALTER TABLE "OrgWebsite" + DROP CONSTRAINT "OrgWebsite_orgLocationId_fkey"; + +-- DropIndex +DROP INDEX "OrgLocation_deleted_idx"; + +-- DropIndex +DROP INDEX "OrgLocation_published_deleted_idx"; + +-- DropIndex +DROP INDEX "OrgLocation_published_idx"; + +-- DropIndex +DROP INDEX "OrgService_published_deleted_idx"; + +-- DropIndex +DROP INDEX "OrgSocialMedia_orgLocationId_idx"; + +-- DropIndex +DROP INDEX "OrgWebsite_orgLocationId_idx"; + +-- AlterTable +ALTER TABLE "OrgSocialMedia" + DROP COLUMN "orgLocationId"; + +-- AlterTable +ALTER TABLE "OrgWebsite" + DROP COLUMN "orgLocationId"; + +-- CreateTable +CREATE TABLE "OrgServiceWebsite"( + "orgWebsiteId" text NOT NULL, + "serviceId" text NOT NULL, + "linkedAt" timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "active" boolean NOT NULL DEFAULT TRUE, + CONSTRAINT "OrgServiceWebsite_pkey" PRIMARY KEY ("orgWebsiteId", "serviceId") +); + +-- CreateTable +CREATE TABLE "OrgLocationWebsite"( + "orgLocationId" text NOT NULL, + "orgWebsiteId" text NOT NULL, + "linkedAt" timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "active" boolean NOT NULL DEFAULT TRUE, + CONSTRAINT "OrgLocationWebsite_pkey" PRIMARY KEY ("orgLocationId", "orgWebsiteId") +); + +-- CreateTable +CREATE TABLE "OrgLocationSocialMedia"( + "orgLocationId" text NOT NULL, + "socialMediaId" text NOT NULL, + "linkedAt" timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "active" boolean NOT NULL DEFAULT TRUE, + CONSTRAINT "OrgLocationSocialMedia_pkey" PRIMARY KEY ("orgLocationId", "socialMediaId") +); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "OrgServiceWebsite_serviceId_orgWebsiteId_idx" ON + "OrgServiceWebsite"("serviceId", "orgWebsiteId"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "OrgLocationWebsite_orgWebsiteId_orgLocationId_idx" + ON "OrgLocationWebsite"("orgWebsiteId", "orgLocationId"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS + "OrgLocationSocialMedia_socialMediaId_orgLocationId_idx" ON + "OrgLocationSocialMedia"("socialMediaId", "orgLocationId"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "AssignedRole_roleId_userId_idx" ON + "AssignedRole"("roleId", "userId"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "AttributeSupplement_active_attributeId_idx" ON + "AttributeSupplement"("active", "attributeId"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "FreeText_ns_key_idx" ON "FreeText"("ns", "key"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "ListSharedWith_listId_userId_idx" ON + "ListSharedWith"("listId", "userId"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "LocationPermission_orgLocationId_userId_idx" ON + "LocationPermission"("orgLocationId", "userId"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "LocationPermission_userId_orgLocationId_idx" ON + "LocationPermission"("userId", "orgLocationId"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "OrgEmail_id_published_deleted_idx" ON + "OrgEmail"("id", "published" DESC, "deleted"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "OrgHours_id_active_idx" ON "OrgHours"("id", "active"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "OrgLocation_id_published_deleted_idx" ON + "OrgLocation"("id", "published" DESC, "deleted"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "OrgLocationEmail_orgLocationId_orgEmailId_idx" ON + "OrgLocationEmail"("orgLocationId", "orgEmailId"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "OrgLocationPhone_phoneId_orgLocationId_idx" ON + "OrgLocationPhone"("phoneId", "orgLocationId"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "OrgLocationService_active_serviceId_idx" ON + "OrgLocationService"("active", "serviceId"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "OrgPhone_id_published_deleted_idx" ON + "OrgPhone"("id", "published" DESC, "deleted"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "OrgPhoneLanguage_languageId_orgPhoneId_idx" ON + "OrgPhoneLanguage"("languageId", "orgPhoneId"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "OrgPhoto_id_published_deleted_idx" ON + "OrgPhoto"("id", "published" DESC, "deleted"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "OrgService_organizationId_published_deleted_idx" ON + "OrgService"("organizationId", "published" DESC, "deleted"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "OrgService_id_published_deleted_idx" ON + "OrgService"("id", "published" DESC, "deleted"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "OrgServiceEmail_serviceId_orgEmailId_idx" ON + "OrgServiceEmail"("serviceId", "orgEmailId"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "OrgServicePhone_serviceId_orgPhoneId_idx" ON + "OrgServicePhone"("serviceId", "orgPhoneId"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "OrgSocialMedia_id_published_deleted_idx" ON + "OrgSocialMedia"("id", "published" DESC, "deleted"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "OrgWebsite_id_published_deleted_idx" ON + "OrgWebsite"("id", "published" DESC, "deleted"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "OrgWebsiteLanguage_languageId_orgWebsiteId_idx" ON + "OrgWebsiteLanguage"("languageId", "orgWebsiteId"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "Organization_slug_published_deleted_idx" ON + "Organization"("slug", "published", "deleted"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "Organization_id_published_deleted_idx" ON + "Organization"("id", "published" DESC, "deleted"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "OrganizationEmail_organizationId_orgEmailId_idx" ON + "OrganizationEmail"("organizationId", "orgEmailId"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "OrganizationPermission_organizationId_userId_idx" + ON "OrganizationPermission"("organizationId", "userId"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "OrganizationPermission_userId_organizationId_idx" + ON "OrganizationPermission"("userId", "organizationId"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "OrganizationPhone_phoneId_organizationId_idx" ON + "OrganizationPhone"("phoneId", "organizationId"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "RolePermission_permissionId_roleId_idx" ON + "RolePermission"("permissionId", "roleId"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "SavedOrganization_organizationId_listId_idx" ON + "SavedOrganization"("organizationId", "listId"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "SavedService_serviceId_listId_idx" ON + "SavedService"("serviceId", "listId"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "ServiceArea_active_organizationId_idx" ON + "ServiceArea"("active", "organizationId"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "ServiceArea_active_orgLocationId_idx" ON + "ServiceArea"("active", "orgLocationId"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "ServiceArea_active_orgServiceId_idx" ON + "ServiceArea"("active", "orgServiceId"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "ServiceAreaCountry_active_serviceAreaId_idx" ON + "ServiceAreaCountry"("active", "serviceAreaId"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "ServiceAreaDist_active_serviceAreaId_idx" ON + "ServiceAreaDist"("active", "serviceAreaId"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "ServiceTagToCategory_serviceTagId_categoryId_idx" + ON "ServiceTagToCategory"("serviceTagId", "categoryId"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "UserMail_fromUserId_toUserId_idx" ON + "UserMail"("fromUserId", "toUserId"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "UserPermission_permissionId_userId_idx" ON + "UserPermission"("permissionId", "userId"); + +-- CreateIndex +CREATE INDEX IF NOT EXISTS "UserToOrganization_organizationId_userId_idx" ON + "UserToOrganization"("organizationId", "userId"); + +-- AddForeignKey +ALTER TABLE "OrgServiceWebsite" + ADD CONSTRAINT "OrgServiceWebsite_orgWebsiteId_fkey" FOREIGN KEY + ("orgWebsiteId") REFERENCES "OrgWebsite"("id") ON DELETE CASCADE ON + UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "OrgServiceWebsite" + ADD CONSTRAINT "OrgServiceWebsite_serviceId_fkey" FOREIGN KEY + ("serviceId") REFERENCES "OrgService"("id") ON DELETE CASCADE ON UPDATE + CASCADE; + +-- AddForeignKey +ALTER TABLE "OrgLocationWebsite" + ADD CONSTRAINT "OrgLocationWebsite_orgLocationId_fkey" FOREIGN KEY + ("orgLocationId") REFERENCES "OrgLocation"("id") ON DELETE CASCADE ON + UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "OrgLocationWebsite" + ADD CONSTRAINT "OrgLocationWebsite_orgWebsiteId_fkey" FOREIGN KEY + ("orgWebsiteId") REFERENCES "OrgWebsite"("id") ON DELETE CASCADE ON + UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "OrgLocationSocialMedia" + ADD CONSTRAINT "OrgLocationSocialMedia_orgLocationId_fkey" FOREIGN KEY + ("orgLocationId") REFERENCES "OrgLocation"("id") ON DELETE CASCADE ON + UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "OrgLocationSocialMedia" + ADD CONSTRAINT "OrgLocationSocialMedia_socialMediaId_fkey" FOREIGN KEY + ("socialMediaId") REFERENCES "OrgSocialMedia"("id") ON DELETE CASCADE + ON UPDATE CASCADE; diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma index 4e84577e120..d3f68836eeb 100644 --- a/packages/db/prisma/schema.prisma +++ b/packages/db/prisma/schema.prisma @@ -389,6 +389,7 @@ model UserMail { @@index([toUserId]) @@index([fromUserId]) @@index([responseToId]) + @@index([fromUserId, toUserId]) } /// Main organization definition @@ -449,6 +450,8 @@ model Organization { @@index([name(sort: Asc)]) @@index([published, deleted]) @@index([slug]) + @@index([slug, published, deleted]) + @@index([id, published(sort: Desc), deleted]) } model SlugRedirect { @@ -501,6 +504,7 @@ model OrgEmail { @@index([lastName(sort: Asc), firstName]) @@index([email]) + @@index([id, published(sort: Desc), deleted]) } /// Org phone numbers - can be general or location specific @@ -543,7 +547,9 @@ model OrgPhone { updatedAt DateTime @updatedAt internalNotes InternalNote[] + //@@schema("org") + @@index([id, published(sort: Desc), deleted]) } /// Organization's social media links @@ -558,13 +564,12 @@ model OrgSocialMedia { service SocialMediaService @relation(fields: [serviceId], references: [id], onDelete: Restrict, onUpdate: Cascade) serviceId String - organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade, onUpdate: Cascade) + organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade, onUpdate: Cascade) organizationId String? + locations OrgLocationSocialMedia[] - orgLocation OrgLocation? @relation(fields: [orgLocationId], references: [id], onDelete: SetNull, onUpdate: Cascade) - orgLocationId String? /// associated only with location and not overall organization (for large orgs w/ multiple locations) - orgLocationOnly Boolean @default(false) + orgLocationOnly Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@ -574,7 +579,7 @@ model OrgSocialMedia { @@index([serviceId]) @@index([organizationId]) - @@index([orgLocationId]) + @@index([id, published(sort: Desc), deleted]) } model OrgWebsite { @@ -589,9 +594,9 @@ model OrgWebsite { organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade, onUpdate: Cascade) organizationId String? - orgLocation OrgLocation? @relation(fields: [orgLocationId], references: [id], onDelete: SetNull, onUpdate: Cascade) - orgLocationId String? - orgLocationOnly Boolean @default(false) + services OrgServiceWebsite[] + locations OrgLocationWebsite[] + orgLocationOnly Boolean @default(false) languages OrgWebsiteLanguage[] @@ -602,7 +607,7 @@ model OrgWebsite { //@@schema("org") @@index([organizationId]) - @@index([orgLocationId]) + @@index([id, published(sort: Desc), deleted]) } /// Org location information @@ -646,12 +651,12 @@ model OrgLocation { allowedEditors LocationPermission[] emails OrgLocationEmail[] /// if location specific - websites OrgWebsite[] /// if location specific phones OrgLocationPhone[] /// if location specific photos OrgPhoto[] services OrgLocationService[] serviceAreas ServiceArea? - socialMedia OrgSocialMedia[] /// if location specific + socialMedia OrgLocationSocialMedia[] + websites OrgLocationWebsite[] // Outside API connections outsideApi OutsideAPI[] @@ -672,10 +677,8 @@ model OrgLocation { @@index([orgId]) @@index([countryId]) @@index([govDistId]) - @@index([published, deleted]) @@index([orgId, id, geo]) - @@index([published]) - @@index([deleted]) + @@index([id, published(sort: Desc), deleted]) } /// Organization photos @@ -701,6 +704,7 @@ model OrgPhoto { @@index([orgId]) @@index([orgLocationId]) + @@index([id, published(sort: Desc), deleted]) } /// Organization location/service hours @@ -745,6 +749,7 @@ model OrgHours { @@index([orgLocId]) @@index([orgServiceId]) @@index([organizationId]) + @@index([id, active]) } model FreeText { @@ -769,6 +774,7 @@ model FreeText { @@unique([key, ns]) @@index([key]) + @@index([ns, key]) } /// Organization's offered services - access instructions & details @@ -799,6 +805,7 @@ model OrgService { organizationId String? locations OrgLocationService[] userLists SavedService[] + websites OrgServiceWebsite[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@ -811,7 +818,7 @@ model OrgService { @@index([organizationId(sort: Asc)]) @@index([organizationId, published(sort: Desc), deleted]) - @@index([published, deleted]) + @@index([id, published(sort: Desc), deleted]) } model ServiceArea { @@ -1132,6 +1139,7 @@ model ServiceTagToCategory { active Boolean @default(true) @@id([categoryId, serviceTagId]) + @@index([serviceTagId, categoryId]) } /// Phone number descriptors @@ -1697,6 +1705,7 @@ model RolePermission { //@@schema("relation") @@id([roleId, permissionId]) + @@index([permissionId, roleId]) } /// Assign permissions to user @@ -1712,6 +1721,7 @@ model UserPermission { //@@schema("relation") @@id([userId, permissionId]) + @@index([permissionId, userId]) } /// Associate User to Org - w/ contact info @@ -1735,6 +1745,7 @@ model UserToOrganization { //@@schema("relation") @@id([userId, organizationId]) + @@index([organizationId, userId]) } /// For certain permissions, attach Organization @@ -1757,6 +1768,8 @@ model OrganizationPermission { //@@schema("relation") @@id([userId, permissionId, organizationId]) + @@index([organizationId, userId]) + @@index([userId, organizationId]) } /// For certain permissions, attach Organization @@ -1777,6 +1790,8 @@ model LocationPermission { //@@schema("relation") @@id([userId, permissionId, orgLocationId]) + @@index([orgLocationId, userId]) + @@index([userId, orgLocationId]) } // @@ -1820,6 +1835,7 @@ model ListSharedWith { //@@schema("relation") @@id([userId, listId]) + @@index([listId, userId]) } /// Organizations saved to list @@ -1833,6 +1849,7 @@ model SavedOrganization { //@@schema("relation") @@id([listId, organizationId]) + @@index([organizationId, listId]) } /// Services saved to list @@ -1846,6 +1863,7 @@ model SavedService { //@@schema("relation") @@id([listId, serviceId]) + @@index([serviceId, listId]) } /// Role assigned to user @@ -1861,6 +1879,7 @@ model AssignedRole { //@@schema("relation") @@id([userId, roleId]) + @@index([roleId, userId]) } /// Link community to anon user survey @@ -1911,6 +1930,7 @@ model OrganizationPhone { //@@schema("relation") @@id([organizationId, phoneId]) + @@index([phoneId, organizationId]) } model OrganizationEmail { @@ -1924,6 +1944,7 @@ model OrganizationEmail { //@@schema("relation") @@id([orgEmailId, organizationId]) + @@index([organizationId, orgEmailId]) } /// Link multiple languages to a website entry @@ -1938,6 +1959,7 @@ model OrgWebsiteLanguage { //@@schema("relation") @@id([orgWebsiteId, languageId]) + @@index([languageId, orgWebsiteId]) } /// Link multiple languages to a phone number @@ -1952,6 +1974,7 @@ model OrgPhoneLanguage { //@@schema("relation") @@id([orgPhoneId, languageId]) + @@index([languageId, orgPhoneId]) } /// Link phone numbers to services @@ -1966,6 +1989,7 @@ model OrgServicePhone { //@@schema("relation") @@id([orgPhoneId, serviceId]) + @@index([serviceId, orgPhoneId]) } /// Link emails to services @@ -1980,6 +2004,21 @@ model OrgServiceEmail { //@@schema("relation") @@id([orgEmailId, serviceId]) + @@index([serviceId, orgEmailId]) +} + +/// Link website to service +model OrgServiceWebsite { + website OrgWebsite @relation(fields: [orgWebsiteId], references: [id], onDelete: Cascade, onUpdate: Cascade) + orgWebsiteId String + service OrgService @relation(fields: [serviceId], references: [id], onDelete: Cascade, onUpdate: Cascade) + serviceId String + + linkedAt DateTime @default(now()) + active Boolean @default(true) + + @@id([orgWebsiteId, serviceId]) + @@index([serviceId, orgWebsiteId]) } /// Link emails to locations @@ -1994,6 +2033,7 @@ model OrgLocationEmail { //@@schema("relation") @@id([orgEmailId, orgLocationId]) + @@index([orgLocationId, orgEmailId]) } model OrgLocationPhone { @@ -2007,6 +2047,7 @@ model OrgLocationPhone { //@@schema("relation") @@id([orgLocationId, phoneId]) + @@index([phoneId, orgLocationId]) } /// Link services to organizations @@ -2029,6 +2070,34 @@ model OrgLocationService { @@index([active, serviceId]) } +/// Link website to location +model OrgLocationWebsite { + location OrgLocation @relation(fields: [orgLocationId], references: [id], onDelete: Cascade, onUpdate: Cascade) + orgLocationId String + website OrgWebsite @relation(fields: [orgWebsiteId], references: [id], onDelete: Cascade, onUpdate: Cascade) + orgWebsiteId String + + linkedAt DateTime @default(now()) + active Boolean @default(true) + + @@id([orgLocationId, orgWebsiteId]) + @@index([orgWebsiteId, orgLocationId]) +} + +/// Link Social Media to Location +model OrgLocationSocialMedia { + location OrgLocation @relation(fields: [orgLocationId], references: [id], onDelete: Cascade, onUpdate: Cascade) + orgLocationId String + socialMedia OrgSocialMedia @relation(fields: [socialMediaId], references: [id], onDelete: Cascade, onUpdate: Cascade) + socialMediaId String + + linkedAt DateTime @default(now()) + active Boolean @default(true) + + @@id([orgLocationId, socialMediaId]) + @@index([socialMediaId, orgLocationId]) +} + /// Link tags to services model OrgServiceTag { service OrgService @relation(fields: [serviceId], references: [id], onDelete: Cascade, onUpdate: Cascade)