Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jurisdictional Refactor Feature Merge #377

Merged
merged 10 commits into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/cypress_public.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ env:
MAPBOX_TOKEN: ${{ secrets.MAPBOX_TOKEN }}
DISABLE_CORS: "TRUE"
LANGUAGES: en,es,zh,vi,tl
NOTIFICATIONS_SIGN_UP_URL: http://example.com


jobs:
Expand Down
10 changes: 9 additions & 1 deletion backend/core/src/jurisdictions/dto/jurisdiction.dto.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { OmitType } from "@nestjs/swagger"
import { Expose, Type } from "class-transformer"
import { ArrayMaxSize, IsArray, IsString, ValidateNested } from "class-validator"
import { ArrayMaxSize, IsArray, IsBoolean, IsString, ValidateNested } from "class-validator"
import { ValidationsGroupsEnum } from "../../shared/types/validations-groups-enum"
import { Jurisdiction } from "../entities/jurisdiction.entity"
import { IdDto } from "../../shared/dto/id.dto"
Expand All @@ -19,4 +19,12 @@ export class JurisdictionSlimDto extends IdNameDto {
@Expose()
@IsString({ groups: [ValidationsGroupsEnum.default] })
publicUrl: string

@Expose()
@IsBoolean({ groups: [ValidationsGroupsEnum.default] })
enableAccessibilityFeatures: boolean | null

@Expose()
@IsBoolean({ groups: [ValidationsGroupsEnum.default] })
enableUtilitiesIncluded: boolean | null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Expose } from "class-transformer"
import { ApiProperty } from "@nestjs/swagger"
import { IsArray, IsOptional } from "class-validator"
import { ValidationsGroupsEnum } from "../../shared/types/validations-groups-enum"

export class JurisdictionsListParams {
@Expose()
@ApiProperty({
type: Array,
example: ["Bay Area", "Contra Costa"],
required: false,
})
@IsOptional({ groups: [ValidationsGroupsEnum.default] })
@IsArray({ groups: [ValidationsGroupsEnum.default] })
names?: string[]
}
9 changes: 7 additions & 2 deletions backend/core/src/jurisdictions/jurisdictions.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
Param,
Post,
Put,
Query,
UseGuards,
UsePipes,
ValidationPipe,
Expand All @@ -21,6 +22,7 @@ import { JurisdictionDto } from "./dto/jurisdiction.dto"
import { JurisdictionCreateDto } from "./dto/jurisdiction-create.dto"
import { JurisdictionUpdateDto } from "./dto/jurisdiction-update.dto"
import { IdDto } from "../shared/dto/id.dto"
import { JurisdictionsListParams } from "./dto/jurisdictions-list-query-params"

@Controller("jurisdictions")
@ApiTags("jurisdictions")
Expand All @@ -33,8 +35,11 @@ export class JurisdictionsController {

@Get()
@ApiOperation({ summary: "List jurisdictions", operationId: "list" })
async list(): Promise<JurisdictionDto[]> {
return mapTo(JurisdictionDto, await this.jurisdictionsService.list())
async list(
@Query()
queryParams: JurisdictionsListParams
): Promise<JurisdictionDto[]> {
return mapTo(JurisdictionDto, await this.jurisdictionsService.list(queryParams))
}

@Post()
Expand Down
21 changes: 16 additions & 5 deletions backend/core/src/jurisdictions/services/jurisdictions.service.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { NotFoundException } from "@nestjs/common"
import { InjectRepository } from "@nestjs/typeorm"
import { FindOneOptions, Repository } from "typeorm"
import { FindOneOptions, In, Repository } from "typeorm"
import { Jurisdiction } from "../entities/jurisdiction.entity"
import { JurisdictionCreateDto } from "../dto/jurisdiction-create.dto"
import { JurisdictionUpdateDto } from "../dto/jurisdiction-update.dto"
import { assignDefined } from "../../shared/utils/assign-defined"
import { JurisdictionsListParams } from "../dto/jurisdictions-list-query-params"

export class JurisdictionsService {
constructor(
Expand All @@ -18,10 +19,20 @@ export class JurisdictionsService {
},
}

list(): Promise<Jurisdiction[]> {
return this.repository.find({
join: this.joinOptions,
})
async list(queryParams?: JurisdictionsListParams): Promise<Jurisdiction[]> {
const obj = queryParams?.names
? await this.repository.find({
where: { name: In([...queryParams.names]) },
join: this.joinOptions,
})
: await this.repository.find({
join: this.joinOptions,
})

if (!obj) {
throw new NotFoundException()
}
return obj
}

async create(dto: JurisdictionCreateDto): Promise<Jurisdiction> {
Expand Down
126 changes: 126 additions & 0 deletions backend/core/src/migration/1695143897902-add-jurisdictions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { MigrationInterface, QueryRunner } from "typeorm"

export class addJurisdictions1695143897902 implements MigrationInterface {
name = "addJurisdictions1695143897902"

public async up(queryRunner: QueryRunner): Promise<void> {
const baseJurisdiction = (
await queryRunner.query(
`SELECT *
FROM jurisdictions
WHERE name='Bay Area'`
)
)[0]
//add new jurisdictions
const countyArr = ["Contra Costa", "Marin", "Napa", "Santa Clara", "Solano", "Sonoma"]
countyArr.forEach(async (county) => {
const newJuris = { ...baseJurisdiction, name: county }
delete newJuris.id
delete newJuris.created_at
delete newJuris.updated_at
const jurisKeys = Object.keys(newJuris).toString()
const jurisValues = Object.values(newJuris)
await queryRunner.query(
`INSERT INTO jurisdictions (${jurisKeys})
VALUES (${jurisValues.map((_, index) => `$${index+1}`).toString()})`,
jurisValues
)
})

//link existing listings to corresponding jurisdiction
const existingListings = await queryRunner.query(
`SELECT listings.id,county
FROM listings
LEFT JOIN address on listings.building_address_id = address.id`
)
const existingJurisdictions = await queryRunner.query(
`SELECT id,name
FROM jurisdictions`
)

existingListings.forEach(async (listing) => {
const matchingJuris = existingJurisdictions.find((juris) => juris.name === listing.county).id
await queryRunner.query(
`UPDATE listings
SET jurisdiction_id='${matchingJuris}'
WHERE id='${listing.id}'`
)
})

const existingUsersAndRoles = await queryRunner.query(
`SELECT user_id,is_admin,is_jurisdictional_admin,is_partner
FROM user_roles`
)

// Update user information
existingUsersAndRoles.forEach(async (user) => {
// Add all jurisdictions to the admin users
if (user.is_admin) {
existingJurisdictions.forEach(async (jurisdiction) => {
if (jurisdiction.name !== "Bay Area") {
await queryRunner.query(
`INSERT INTO user_accounts_jurisdictions_jurisdictions
(user_accounts_id, jurisdictions_id)
VALUES ($1, $2)`,
[user.user_id, jurisdiction.id]
)
}
})
// Add all jurisdictions tied to listings of partner accounts
} else if (user.is_partner) {
const userListings = await queryRunner.query(
`SELECT listings_id from listings_leasing_agents_user_accounts where user_accounts_id = '${user.user_id}'`
)
userListings.forEach(async (listing) => {
const matchingListing = existingListings.find(
(list) => list.id === listing.listings_id
)
const matchingJuris = existingJurisdictions.find((juris) => juris.name === matchingListing.county).id
await queryRunner.query(
`INSERT INTO user_accounts_jurisdictions_jurisdictions
(user_accounts_id, jurisdictions_id)
VALUES ($1, $2)
ON CONFLICT (user_accounts_id, jurisdictions_id) DO NOTHING
`,
[user.user_id, matchingJuris]
)
})
}
})

//link existing ami charts to new jurisdictions
const amiJurisMap = {
"Marin - HUD": "Marin",
"Napa - HUD": "Napa",
"Contra Costa - HUD": "Contra Costa",
"Santa Clara - HUD": "Santa Clara",
"Solano - HUD": "Solano",
"Sonoma - HUD": "Sonoma",
"Sonoma - Sonoma County State and Local": "Sonoma",
"Contra Costa - CA TCAC": "Contra Costa",
"Marin - CA TCAC": "Marin",
"Napa - CA TCAC": "Napa",
"Santa Clara - CA TCAC": "Santa Clara",
"Solano - CA TCAC": "Solano",
"Sonoma - CA TCAC": "Sonoma",
}
const amiCharts = await queryRunner.query(
`SELECT id,name
FROM ami_chart`
)
amiCharts.forEach(async (ami) => {
const matchingJuris = existingJurisdictions.find(
(juris) => juris.name === amiJurisMap[ami.name]
)?.id
if (matchingJuris) {
await queryRunner.query(
`UPDATE ami_chart
SET jurisdiction_id='${matchingJuris}'
WHERE id='${ami.id}'`
)
}
})
}

public async down(queryRunner: QueryRunner): Promise<void> {}
}
2 changes: 2 additions & 0 deletions backend/core/src/seeder/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { AmiDefaultMissingAMI } from "./seeds/ami-charts/missing-household-ami-l
import { SeederModule } from "./seeder.module"
import { AmiDefaultTriton } from "./seeds/ami-charts/triton-ami-chart"
import { AmiDefaultSanMateo } from "./seeds/ami-charts/default-ami-chart-san-mateo"
import { AmiDefaultDoorway } from "./seeds/ami-charts/default-ami-chart-doorway"
import { makeNewApplication } from "./seeds/applications"
import { UserRoles } from "../auth/entities/user-roles.entity"
import { Jurisdiction } from "../jurisdictions/entities/jurisdiction.entity"
Expand Down Expand Up @@ -88,6 +89,7 @@ const amiSeeds: any[] = [
AmiDefaultTriton,
AmiDefaultSanJose,
AmiDefaultSanMateo,
AmiDefaultDoorway,
]

export function getSeedListingsCount() {
Expand Down
2 changes: 2 additions & 0 deletions backend/core/src/seeder/seeder.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import { AmiDefaultMissingAMI } from "../seeder/seeds/ami-charts/missing-househo
import { AmiDefaultTriton } from "../seeder/seeds/ami-charts/triton-ami-chart"
import { AmiDefaultSanJose } from "../seeder/seeds/ami-charts/default-ami-chart-san-jose"
import { AmiDefaultSanMateo } from "../seeder/seeds/ami-charts/default-ami-chart-san-mateo"
import { AmiDefaultDoorway } from "./seeds/ami-charts/default-ami-chart-doorway"
import { Asset } from "../assets/entities/asset.entity"

@Module({})
Expand Down Expand Up @@ -113,6 +114,7 @@ export class SeederModule {
AmiDefaultTriton,
AmiDefaultSanJose,
AmiDefaultSanMateo,
AmiDefaultDoorway,
],
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { DoorwayJurisdictions } from "../../../../src/shared/types/doorway-jurisdictions"
import { AmiChartDefaultSeed, getDefaultAmiChart } from "./default-ami-chart"

export class AmiDefaultDoorway extends AmiChartDefaultSeed {
async seed() {
const promiseArr = Object.values(DoorwayJurisdictions).map(async (name) => {
const doorwayJurisdiction = await this.jurisdictionRepository.findOneOrFail({
where: { name: name },
})
return await this.amiChartRepository.save({
...getDefaultAmiChart(),
name: `${name} - HUD`,
jurisdiction: doorwayJurisdiction,
})
})

return await Promise.all(promiseArr)
}
}
41 changes: 23 additions & 18 deletions backend/core/src/seeder/seeds/jurisdictions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,30 @@ import { JurisdictionCreateDto } from "../../jurisdictions/dto/jurisdiction-crea
import { Language } from "../../shared/types/language-enum"
import { JurisdictionsService } from "../../jurisdictions/services/jurisdictions.service"
import { UserRoleEnum } from "../../../src/auth/enum/user-role-enum"
import { DoorwayJurisdictions } from "../../../src/shared/types/doorway-jurisdictions"

export const activeJurisdictions: JurisdictionCreateDto[] = [
{
name: "Bay Area",
multiselectQuestions: [],
languages: [Language.en],
publicUrl: "http://localhost:3000",
notificationsSignUpURL: "https://public.govdelivery.com/accounts/CAMTC/signup/36832",
emailFromAddress: "Bay Area: Housing Bay Area <[email protected]>",
rentalAssistanceDefault:
"[Bay Area seed] Housing Choice Vouchers, Section 8 and other valid rental assistance programs will be considered for this property. In the case of a valid rental subsidy, the required minimum income will be based on the portion of the rent that the tenant pays after use of the subsidy.",
enablePartnerSettings: true,
enableAccessibilityFeatures: false,
enableUtilitiesIncluded: true,
enableListingOpportunity: false,
listingApprovalPermissions: [UserRoleEnum.admin],
enableGeocodingPreferences: true,
},
]
export const basicJurisInfo: JurisdictionCreateDto = {
name: "",
multiselectQuestions: [],
languages: [Language.en],
publicUrl: "http://localhost:3000",
notificationsSignUpURL: "https://public.govdelivery.com/accounts/CAMTC/signup/36832",
emailFromAddress: "Bay Area: Housing Bay Area <[email protected]>",
rentalAssistanceDefault:
"[Bay Area seed] Housing Choice Vouchers, Section 8 and other valid rental assistance programs will be considered for this property. In the case of a valid rental subsidy, the required minimum income will be based on the portion of the rent that the tenant pays after use of the subsidy.",
enablePartnerSettings: true,
enableAccessibilityFeatures: false,
enableUtilitiesIncluded: true,
enableListingOpportunity: false,
enableGeocodingPreferences: true,
listingApprovalPermissions: [UserRoleEnum.admin],
}

const activeJurisdictions: JurisdictionCreateDto[] = Object.values(DoorwayJurisdictions).map(
(name) => {
return { ...basicJurisInfo, name: name }
}
)

export async function createJurisdictions(app: INestApplicationContext) {
const jurisdictionService = await app.resolve<JurisdictionsService>(JurisdictionsService)
Expand Down
9 changes: 9 additions & 0 deletions backend/core/src/shared/types/doorway-jurisdictions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export enum DoorwayJurisdictions {
bayArea = "Bay Area",
contraCosta = "Contra Costa",
marin = "Marin",
napa = "Napa",
santaClara = "Santa Clara",
solano = "Solano",
sonoma = "Sonoma",
}
7 changes: 7 additions & 0 deletions build/docker/Dockerfile.sites-generic
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ ARG GTM_KEY
# feature flags
ARG FEATURE_LISTINGS_APPROVAL
ARG SHOW_PROFESSIONAL_PARTNERS
ARG NOTIFICATIONS_SIGN_UP_URL


# The base image will contain all the source code needed for our site
FROM node:18.14-alpine AS base
Expand Down Expand Up @@ -118,6 +120,8 @@ ARG MAPBOX_TOKEN
ARG GTM_KEY
ARG FEATURE_LISTINGS_APPROVAL
ARG SHOW_PROFESSIONAL_PARTNERS
ARG NOTIFICATIONS_SIGN_UP_URL


# We need to have this nested 2 layers deep due to hardcoded paths in package.json
WORKDIR /app/sites/${SITE}
Expand All @@ -138,6 +142,9 @@ ENV GTM_KEY=$GTM_KEY
# feature flags
ENV FEATURE_LISTINGS_APPROVAL=$FEATURE_LISTINGS_APPROVAL
ENV SHOW_PROFESSIONAL_PARTNERS=$SHOW_PROFESSIONAL_PARTNERS
ENV NOTIFICATIONS_SIGN_UP_URL=$NOTIFICATIONS_SIGN_UP_URL



# Build/compile
RUN yarn build
Expand Down
Loading
Loading