diff --git a/api/prisma/migrations/08_updating_on_delete_cascades_listings_and_applications/migration.sql b/api/prisma/migrations/08_updating_on_delete_cascades_listings_and_applications/migration.sql new file mode 100644 index 0000000000..1ad8aa2a0e --- /dev/null +++ b/api/prisma/migrations/08_updating_on_delete_cascades_listings_and_applications/migration.sql @@ -0,0 +1,110 @@ +-- DropForeignKey + +ALTER TABLE "application_methods" +DROP CONSTRAINT "application_methods_listing_id_fkey"; + +-- AddForeignKey + +ALTER TABLE "application_methods" ADD CONSTRAINT "application_methods_listing_id_fkey" +FOREIGN KEY ("listing_id") REFERENCES "listings"("id") ON +DELETE CASCADE ON +UPDATE NO ACTION; + +-- DropForeignKey + +ALTER TABLE "applications" +DROP CONSTRAINT "applications_listing_id_fkey"; + +-- AddForeignKey + +ALTER TABLE "applications" ADD CONSTRAINT "applications_listing_id_fkey" +FOREIGN KEY ("listing_id") REFERENCES "listings"("id") ON +DELETE +SET NULL ON +UPDATE NO ACTION; + +-- DropForeignKey + +ALTER TABLE "listing_images" +DROP CONSTRAINT "listing_images_listing_id_fkey"; + +-- AddForeignKey + +ALTER TABLE "listing_images" ADD CONSTRAINT "listing_images_listing_id_fkey" +FOREIGN KEY ("listing_id") REFERENCES "listings"("id") ON +DELETE CASCADE ON +UPDATE NO ACTION; + +-- DropForeignKey + +ALTER TABLE "listing_events" +DROP CONSTRAINT "listing_events_listing_id_fkey"; + +-- AddForeignKey + +ALTER TABLE "listing_events" ADD CONSTRAINT "listing_events_listing_id_fkey" +FOREIGN KEY ("listing_id") REFERENCES "listings"("id") ON +DELETE CASCADE ON +UPDATE NO ACTION; + +-- DropForeignKey + +ALTER TABLE "listing_multiselect_questions" +DROP CONSTRAINT "listing_multiselect_questions_listing_id_fkey"; + +-- AddForeignKey + +ALTER TABLE "listing_multiselect_questions" ADD CONSTRAINT "listing_multiselect_questions_listing_id_fkey" +FOREIGN KEY ("listing_id") REFERENCES "listings"("id") ON +DELETE CASCADE ON +UPDATE NO ACTION; + +-- DropForeignKey + +ALTER TABLE "units_summary" +DROP CONSTRAINT "units_summary_listing_id_fkey"; + +-- AddForeignKey + +ALTER TABLE "units_summary" ADD CONSTRAINT "units_summary_listing_id_fkey" +FOREIGN KEY ("listing_id") REFERENCES "listings"("id") ON +DELETE CASCADE ON +UPDATE NO ACTION; + +-- DropForeignKey + +ALTER TABLE "application_flagged_set" +DROP CONSTRAINT "application_flagged_set_listing_id_fkey"; + +-- AddForeignKey + +ALTER TABLE "application_flagged_set" ADD CONSTRAINT "application_flagged_set_listing_id_fkey" +FOREIGN KEY ("listing_id") REFERENCES "listings"("id") ON +DELETE CASCADE ON +UPDATE NO ACTION; + +-- DropForeignKey + +ALTER TABLE "paper_applications" +DROP CONSTRAINT "paper_applications_application_method_id_fkey"; + +-- AddForeignKey + +ALTER TABLE "paper_applications" ADD CONSTRAINT "paper_applications_application_method_id_fkey" +FOREIGN KEY ("application_method_id") REFERENCES "application_methods"("id") ON +DELETE +SET NULL ON +UPDATE NO ACTION; + +-- DropForeignKey + +ALTER TABLE "household_member" +DROP CONSTRAINT "household_member_application_id_fkey"; + +-- AddForeignKey + +ALTER TABLE "household_member" ADD CONSTRAINT "household_member_application_id_fkey" +FOREIGN KEY ("application_id") REFERENCES "applications"("id") ON +DELETE CASCADE ON +UPDATE NO ACTION; + diff --git a/api/prisma/schema.prisma b/api/prisma/schema.prisma index ba9261fcdc..b0d4442c49 100644 --- a/api/prisma/schema.prisma +++ b/api/prisma/schema.prisma @@ -29,7 +29,7 @@ model ActivityLog { module String @db.VarChar action String @db.VarChar metadata Json? - recordId String? @map("record_id") @db.Uuid + recordId String? @map("record_id") @db.Uuid userId String? @map("user_id") @db.Uuid userAccounts UserAccounts? @relation(fields: [userId], references: [id], onUpdate: NoAction) @@ -143,7 +143,7 @@ model ApplicationFlaggedSet { status FlaggedSetStatusEnum @default(pending) resolvingUserId String? @map("resolving_user_id") @db.Uuid userAccounts UserAccounts? @relation(fields: [resolvingUserId], references: [id], onDelete: NoAction, onUpdate: NoAction) - listings Listings @relation(fields: [listingId], references: [id], onDelete: NoAction, onUpdate: NoAction) + listings Listings @relation(fields: [listingId], references: [id], onDelete: Cascade, onUpdate: NoAction) applications Applications[] @@index([listingId]) @@ -161,7 +161,7 @@ model ApplicationMethods { acceptsPostmarkedApplications Boolean? @map("accepts_postmarked_applications") phoneNumber String? @map("phone_number") listingId String? @map("listing_id") @db.Uuid - listings Listings? @relation(fields: [listingId], references: [id], onDelete: NoAction, onUpdate: NoAction) + listings Listings? @relation(fields: [listingId], references: [id], onDelete: Cascade, onUpdate: NoAction) paperApplications PaperApplications[] @@map("application_methods") @@ -218,7 +218,7 @@ model Applications { applicationsAlternateAddress Address? @relation("applications_alternate_address", fields: [alternateAddressId], references: [id], onDelete: NoAction, onUpdate: NoAction) userAccounts UserAccounts? @relation(fields: [userId], references: [id], onUpdate: NoAction) applicationsMailingAddress Address? @relation("applications_mailing_address", fields: [mailingAddressId], references: [id], onDelete: NoAction, onUpdate: NoAction) - listings Listings? @relation(fields: [listingId], references: [id], onDelete: NoAction, onUpdate: NoAction) + listings Listings? @relation(fields: [listingId], references: [id], onDelete: SetNull, onUpdate: NoAction) demographics Demographics? @relation(fields: [demographicsId], references: [id], onDelete: NoAction, onUpdate: NoAction) preferredUnitTypes UnitTypes[] householdMember HouseholdMember[] @@ -305,7 +305,7 @@ model HouseholdMember { addressId String? @unique() @map("address_id") @db.Uuid workAddressId String? @unique() @map("work_address_id") @db.Uuid applicationId String? @map("application_id") @db.Uuid - applications Applications? @relation(fields: [applicationId], references: [id], onDelete: NoAction, onUpdate: NoAction) + applications Applications? @relation(fields: [applicationId], references: [id], onDelete: Cascade, onUpdate: NoAction) householdMemberAddress Address? @relation("household_member_address", fields: [addressId], references: [id], onDelete: NoAction, onUpdate: NoAction) householdMemberWorkAddress Address? @relation("household_member_work_address", fields: [workAddressId], references: [id], onDelete: NoAction, onUpdate: NoAction) @@ -365,7 +365,7 @@ model ListingEvents { listingId String? @map("listing_id") @db.Uuid fileId String? @map("file_id") @db.Uuid assets Assets? @relation(fields: [fileId], references: [id], onDelete: NoAction, onUpdate: NoAction) - listings Listings? @relation(fields: [listingId], references: [id], onDelete: NoAction, onUpdate: NoAction) + listings Listings? @relation(fields: [listingId], references: [id], onDelete: Cascade, onUpdate: NoAction) @@map("listing_events") } @@ -404,7 +404,7 @@ model ListingImages { listingId String @map("listing_id") @db.Uuid imageId String @map("image_id") @db.Uuid assets Assets @relation(fields: [imageId], references: [id], onDelete: NoAction, onUpdate: NoAction) - listings Listings @relation(fields: [listingId], references: [id], onDelete: NoAction, onUpdate: NoAction) + listings Listings @relation(fields: [listingId], references: [id], onDelete: Cascade, onUpdate: NoAction) @@id([listingId, imageId]) @@index([listingId]) @@ -416,7 +416,7 @@ model ListingMultiselectQuestions { listingId String @map("listing_id") @db.Uuid multiselectQuestionId String @map("multiselect_question_id") @db.Uuid multiselectQuestions MultiselectQuestions @relation(fields: [multiselectQuestionId], references: [id], onDelete: NoAction, onUpdate: NoAction) - listings Listings @relation(fields: [listingId], references: [id], onDelete: NoAction, onUpdate: NoAction) + listings Listings @relation(fields: [listingId], references: [id], onDelete: Cascade, onUpdate: NoAction) @@id([listingId, multiselectQuestionId]) @@map("listing_multiselect_questions") @@ -619,7 +619,7 @@ model PaperApplications { fileId String? @map("file_id") @db.Uuid applicationMethodId String? @map("application_method_id") @db.Uuid assets Assets? @relation(fields: [fileId], references: [id], onDelete: Cascade, onUpdate: NoAction) - applicationMethods ApplicationMethods? @relation(fields: [applicationMethodId], references: [id], onDelete: NoAction, onUpdate: NoAction) + applicationMethods ApplicationMethods? @relation(fields: [applicationMethodId], references: [id], onDelete: SetNull, onUpdate: NoAction) @@map("paper_applications") } @@ -756,7 +756,7 @@ model UnitsSummary { priorityTypeId String? @map("priority_type_id") @db.Uuid unitTypes UnitTypes? @relation(fields: [unitTypeId], references: [id], onDelete: NoAction, onUpdate: NoAction) unitAccessibilityPriorityTypes UnitAccessibilityPriorityTypes? @relation(fields: [priorityTypeId], references: [id], onDelete: NoAction, onUpdate: NoAction) - listings Listings? @relation(fields: [listingId], references: [id], onDelete: NoAction, onUpdate: NoAction) + listings Listings? @relation(fields: [listingId], references: [id], onDelete: Cascade, onUpdate: NoAction) @@map("units_summary") } diff --git a/api/prisma/seed-dev.ts b/api/prisma/seed-dev.ts index 9ee139fcb6..357118d71a 100644 --- a/api/prisma/seed-dev.ts +++ b/api/prisma/seed-dev.ts @@ -100,13 +100,21 @@ export const devSeeding = async ( const listing = await listingFactory(jurisdiction.id, prismaClient, { amiChart: amiChart, - numberOfUnits: index, + numberOfUnits: index + 1, includeBuildingFeatures: index > 1, includeEligibilityRules: index > 2, - status: listingStatusEnumArray[randomInt(listingStatusEnumArray.length)], + status: + index < 4 + ? ListingsStatusEnum.active + : listingStatusEnumArray[ + index - 3 < listingStatusEnumArray.length + ? index - 3 + : randomInt(listingStatusEnumArray.length - 1) + ], multiselectQuestions: index > 0 ? multiselectQuestions.slice(0, index - 1) : [], applications, + digitalApp: !!(index % 2), }); await prismaClient.listings.create({ data: listing, diff --git a/api/prisma/seed-helpers/listing-factory.ts b/api/prisma/seed-helpers/listing-factory.ts index c5778294f1..232e5e05fc 100644 --- a/api/prisma/seed-helpers/listing-factory.ts +++ b/api/prisma/seed-helpers/listing-factory.ts @@ -4,12 +4,25 @@ import { MultiselectQuestions, PrismaClient, ListingsStatusEnum, + ApplicationMethodsTypeEnum, } from '@prisma/client'; +import { randomInt } from 'crypto'; import { randomName } from './word-generator'; import { addressFactory } from './address-factory'; import { unitFactoryMany } from './unit-factory'; import { reservedCommunityTypeFactoryGet } from './reserved-community-type-factory'; +const cloudinaryIds = [ + 'dev/blake-wheeler-zBHU08hdzhY-unsplash_swqash', + 'dev/krzysztof-hepner-V7Q0Oh3Az-c-unsplash_xoj7sr', + 'dev/dillon-kydd-2keCPb73aQY-unsplash_lm7krp', + 'dev/inside_qo9wre', + 'dev/interior_mc9erd', + 'dev/apartment_ez3yyz', + 'dev/trayan-xIOYJSVEZ8c-unsplash_f1axsg', + 'dev/apartment_building_2_b7ujdd', +]; + export const listingFactory = async ( jurisdictionId: string, prismaClient: PrismaClient, @@ -25,6 +38,8 @@ export const listingFactory = async ( applications?: Prisma.ApplicationsCreateInput[]; applicationDueDate?: Date; afsLastRunSetInPast?: boolean; + digitalApp?: boolean; + noImage?: boolean; }, ): Promise => { const previousListing = optionalParams?.listing || {}; @@ -39,6 +54,11 @@ export const listingFactory = async ( prismaClient, jurisdictionId, ); + + const digitalApp = !!optionalParams?.digitalApp + ? optionalParams.digitalApp + : Math.random() < 0.5; + return { createdAt: new Date(), assets: [], @@ -60,11 +80,14 @@ export const listingFactory = async ( listingsApplicationDropOffAddress: { create: addressFactory(), }, - reservedCommunityTypes: { - connect: { - id: reservedCommunityType.id, - }, - }, + reservedCommunityTypes: + Math.random() < 0.5 + ? { + connect: { + id: reservedCommunityType.id, + }, + } + : {}, // For application flagged set tests the date needs to be before the updated timestamp // All others should be a newer timestamp so that they are not picked up by AFS tests afsLastRunAt: optionalParams?.afsLastRunSetInPast @@ -93,7 +116,6 @@ export const listingFactory = async ( ...featuresAndUtilites(), ...buildingFeatures(optionalParams?.includeBuildingFeatures), ...additionalEligibilityRules(optionalParams?.includeEligibilityRules), - ...previousListing, jurisdictions: { connect: { id: jurisdictionId, @@ -105,6 +127,35 @@ export const listingFactory = async ( } : undefined, applicationDueDate: optionalParams?.applicationDueDate ?? undefined, + developer: randomName(), + leasingAgentName: randomName(), + leasingAgentEmail: 'leasing-agent@example.com', + leasingAgentPhone: '515-604-0183', + digitalApplication: digitalApp, + commonDigitalApplication: digitalApp, + paperApplication: Math.random() < 0.5, + referralOpportunity: Math.random() < 0.5, + applicationMethods: digitalApp + ? { + create: { + type: ApplicationMethodsTypeEnum.Internal, + }, + } + : {}, + listingImages: !optionalParams?.noImage + ? { + create: { + ordinal: 0, + assets: { + create: { + label: 'cloudinaryBuilding', + fileId: cloudinaryIds[randomInt(cloudinaryIds.length)], + }, + }, + }, + } + : {}, + ...previousListing, }; }; diff --git a/api/prisma/seed-staging.ts b/api/prisma/seed-staging.ts index d9c3247476..2b68d65297 100644 --- a/api/prisma/seed-staging.ts +++ b/api/prisma/seed-staging.ts @@ -855,7 +855,7 @@ export const stagingSeed = async ( assets: { create: { label: 'cloudinaryBuilding', - fileId: 'dev/blake-wheeler-zBHU08hdzhY-unsplash_swqash ', + fileId: 'dev/blake-wheeler-zBHU08hdzhY-unsplash_swqash', }, }, }, diff --git a/api/test/integration/listing.e2e-spec.ts b/api/test/integration/listing.e2e-spec.ts index c7314e3baa..f7631f8132 100644 --- a/api/test/integration/listing.e2e-spec.ts +++ b/api/test/integration/listing.e2e-spec.ts @@ -569,7 +569,9 @@ describe('Listing Controller Tests', () => { data: jurisdictionFactory(), }); await reservedCommunityTypeFactoryAll(jurisdictionA.id, prisma); - const listingData = await listingFactory(jurisdictionA.id, prisma); + const listingData = await listingFactory(jurisdictionA.id, prisma, { + noImage: true, + }); const listing = await prisma.listings.create({ data: listingData, }); diff --git a/api/test/integration/permission-tests/permission-as-admin.e2e-spec.ts b/api/test/integration/permission-tests/permission-as-admin.e2e-spec.ts index 0faff9fe95..ab23b7f723 100644 --- a/api/test/integration/permission-tests/permission-as-admin.e2e-spec.ts +++ b/api/test/integration/permission-tests/permission-as-admin.e2e-spec.ts @@ -1124,7 +1124,9 @@ describe('Testing Permissioning of endpoints as Admin User', () => { 'permission juris 17', ); await reservedCommunityTypeFactoryAll(jurisdictionA, prisma); - const listingData = await listingFactory(jurisdictionA, prisma); + const listingData = await listingFactory(jurisdictionA, prisma, { + noImage: true, + }); const listing = await prisma.listings.create({ data: listingData, }); diff --git a/api/test/integration/permission-tests/permission-as-juris-admin-correct-juris.e2e-spec.ts b/api/test/integration/permission-tests/permission-as-juris-admin-correct-juris.e2e-spec.ts index f8ed084916..178f3218e4 100644 --- a/api/test/integration/permission-tests/permission-as-juris-admin-correct-juris.e2e-spec.ts +++ b/api/test/integration/permission-tests/permission-as-juris-admin-correct-juris.e2e-spec.ts @@ -1021,7 +1021,9 @@ describe('Testing Permissioning of endpoints as Jurisdictional Admin in the corr }); it('should succeed for delete endpoint & create an activity log entry', async () => { - const listingData = await listingFactory(jurisId, prisma); + const listingData = await listingFactory(jurisId, prisma, { + noImage: true, + }); const listing = await prisma.listings.create({ data: listingData, });