Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into release/2023-12-05
Browse files Browse the repository at this point in the history
  • Loading branch information
emilyjablonski committed Dec 6, 2023
2 parents 23ae6ed + 7bf05c1 commit 3f41e70
Show file tree
Hide file tree
Showing 131 changed files with 5,178 additions and 3,715 deletions.
15 changes: 9 additions & 6 deletions backend/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"@anchan828/nest-sendgrid": "^0.3.25",
"@aws-sdk/client-s3": "^3.326.0",
"@aws-sdk/s3-request-presigner": "^3.327.0",
"@google-cloud/translate": "^6.2.6",
"@google-cloud/translate": "^7.2.1",
"@nestjs/axios": "^2.0.0",
"@nestjs/cli": "^9.5.0",
"@nestjs/common": "^9.4.2",
Expand All @@ -60,9 +60,12 @@
"@nestjs/swagger": "^6.3.0",
"@nestjs/throttler": "^4.0.0",
"@nestjs/typeorm": "^9.0.1",
"@turf/buffer": "6.5.0",
"@turf/helpers": "6.5.0",
"@turf/boolean-point-in-polygon": "6.5.0",
"@types/cache-manager": "^3.4.0",
"async-retry": "^1.3.1",
"axios": "0.21.2",
"axios": "0.21.3",
"cache-manager": "^3.4.0",
"casbin": "5.13.0",
"class-transformer": "0.3.1",
Expand All @@ -82,17 +85,17 @@
"nanoid": "^3.1.12",
"nestjs-twilio": "^4.1.1",
"nestjs-typeorm-paginate": "^4.0.3",
"newrelic": "7.5.1",
"newrelic": "11.4.0",
"node-polyglot": "^2.4.0",
"passport": "^0.6.0",
"passport-custom": "^1.1.1",
"passport-jwt": "^4.0.0",
"passport-jwt": "^4.0.1",
"passport-local": "^1.0.0",
"pg": "^8.4.1",
"pg": "^8.11.3",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.5.4",
"swagger-ui-express": "^4.1.4",
"swagger-ui-express": "^4.2.0",
"ts-node": "10.8.0",
"twilio": "^3.71.3",
"typeorm": "0.3.12",
Expand Down
3 changes: 2 additions & 1 deletion backend/core/src/applications/applications.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { CsvBuilder } from "./services/csv-builder.service"
import { ApplicationCsvExporterService } from "./services/application-csv-exporter.service"
import { EmailModule } from "../email/email.module"
import { ActivityLogModule } from "../activity-log/activity-log.module"
import { GeocodingService } from "./services/geocoding.service"

@Module({
imports: [
Expand All @@ -28,7 +29,7 @@ import { ActivityLogModule } from "../activity-log/activity-log.module"
EmailModule,
ScheduleModule.forRoot(),
],
providers: [ApplicationsService, CsvBuilder, ApplicationCsvExporterService],
providers: [ApplicationsService, CsvBuilder, ApplicationCsvExporterService, GeocodingService],
exports: [ApplicationsService],
controllers: [ApplicationsController, ApplicationsSubmissionController],
})
Expand Down
11 changes: 11 additions & 0 deletions backend/core/src/applications/services/applications.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { Listing } from "../../listings/entities/listing.entity"
import { ApplicationCsvExporterService } from "./application-csv-exporter.service"
import { User } from "../../auth/entities/user.entity"
import { StatusDto } from "../../shared/dto/status.dto"
import { GeocodingService } from "./geocoding.service"

@Injectable({ scope: Scope.REQUEST })
export class ApplicationsService {
Expand All @@ -38,6 +39,7 @@ export class ApplicationsService {
private readonly listingsService: ListingsService,
private readonly emailService: EmailService,
private readonly applicationCsvExporter: ApplicationCsvExporterService,
private readonly geocodingService: GeocodingService,
@InjectRepository(Application) private readonly repository: Repository<Application>,
@InjectRepository(Listing) private readonly listingsRepository: Repository<Listing>
) {}
Expand Down Expand Up @@ -421,6 +423,15 @@ export class ApplicationsService {
if (application.applicant.emailAddress && shouldSendConfirmation) {
await this.emailService.confirmation(listing, application, applicationCreateDto.appUrl)
}

// Calculate geocoding preferences after save and email sent
if (listing.jurisdiction?.enableGeocodingPreferences) {
try {
void this.geocodingService.validateGeocodingPreferences(application, listing)
} catch (e) {
console.warn("error while validating geocoding preferences")
}
}
return application
}

Expand Down
167 changes: 167 additions & 0 deletions backend/core/src/applications/services/geocoding.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import { Test, TestingModule } from "@nestjs/testing"
import { GeocodingService } from "./geocoding.service"
import { Address } from "../../shared/entities/address.entity"
import { getRepositoryToken } from "@nestjs/typeorm"
import { Application } from "../entities/application.entity"
import { ValidationMethod } from "../../multiselect-question/types/validation-method-enum"
import { Listing } from "../../listings/entities/listing.entity"
import { InputType } from "../../shared/types/input-type"

describe("GeocodingService", () => {
let service: GeocodingService
const applicationRepoUpdate = jest.fn()
const mockApplicationRepo = {
createQueryBuilder: jest.fn(),
update: applicationRepoUpdate,
}
const date = new Date()
const listingAddress: Address = {
id: "id",
createdAt: date,
updatedAt: date,
city: "Washington",
county: null,
state: "DC",
street: "1600 Pennsylvania Avenue",
street2: null,
zipCode: "20500",
latitude: 38.8977,
longitude: -77.0365,
}

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
GeocodingService,
{
provide: getRepositoryToken(Application),
useValue: mockApplicationRepo,
},
],
}).compile()

service = await module.resolve(GeocodingService)
})

describe("verifyRadius", () => {
it("should return 'unknown' if lat and long not there", () => {
expect(
service.verifyRadius(
{
...listingAddress,
latitude: null,
longitude: null,
},
5,
listingAddress
)
).toBe("unknown")
})
it("should return 'true' if within radius", () => {
expect(
service.verifyRadius(
{
...listingAddress,
latitude: 38.89485,
longitude: -77.04251,
},
5,
listingAddress
)
).toBe("true")
})
it("should return 'false' if not within radius", () => {
expect(
service.verifyRadius(
{
...listingAddress,
latitude: 39.284205,
longitude: -76.621698,
},
5,
listingAddress
)
).toBe("false")
})
it("should return 'true' if same lat long", () => {
expect(
service.verifyRadius(
{
...listingAddress,
},
5,
listingAddress
)
).toBe("true")
})
})
describe("validateRadiusPreferences", () => {
const listing = {
buildingAddress: listingAddress,
listingMultiselectQuestions: [
{
multiselectQuestion: {
options: [
{
text: "Geocoding option by radius",
collectAddress: true,
radiusSize: 5,
validationMethod: ValidationMethod.radius,
},
],
},
},
],
}
const preferenceAddress = { ...listingAddress, latitude: 38.89485, longitude: -77.04251 }
const application = {
id: "applicationId",
preferences: [
{
key: "Geocoding preference",
options: [
{
key: "Geocoding option by radius",
checked: true,
extraData: [
{
type: InputType.address,
value: preferenceAddress,
},
],
},
],
},
],
}
it("should save the validated value as extraData", async () => {
await service.validateRadiusPreferences(
(application as unknown) as Application,
listing as Listing
)
expect(applicationRepoUpdate).toBeCalledWith(
{ id: "applicationId" },
{
preferences: expect.arrayContaining([
expect.objectContaining({
key: "Geocoding preference",
options: [
{
checked: true,
extraData: [
{
type: "address",
value: preferenceAddress,
},
{ key: "geocodingVerified", type: "text", value: "true" },
],
key: "Geocoding option by radius",
},
],
}),
]),
}
)
})
})
})
101 changes: 101 additions & 0 deletions backend/core/src/applications/services/geocoding.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { point } from "@turf/helpers"
import buffer from "@turf/buffer"
import booleanPointInPolygon from "@turf/boolean-point-in-polygon"
import { InjectRepository } from "@nestjs/typeorm"
import { Repository } from "typeorm"
import { Address } from "../../shared/entities/address.entity"
import { Application } from "../entities/application.entity"
import { Listing } from "../../listings/entities/listing.entity"
import { ValidationMethod } from "../../multiselect-question/types/validation-method-enum"
import { MultiselectOption } from "../../multiselect-question/types/multiselect-option"
import { ApplicationMultiselectQuestion } from "../entities/application-multiselect-question.entity"
import { ApplicationMultiselectQuestionOption } from "../types/application-multiselect-question-option"
import { InputType } from "../../shared/types/input-type"
import { GeocodingValues } from "../../shared/types/geocoding-values"

export class GeocodingService {
constructor(
@InjectRepository(Application) private readonly repository: Repository<Application>
) {}

public async validateGeocodingPreferences(application: Application, listing: Listing) {
await this.validateRadiusPreferences(application, listing)
}

verifyRadius(
preferenceAddress: Address,
radius: number,
listingAddress: Address
): GeocodingValues {
try {
if (preferenceAddress.latitude && preferenceAddress.longitude) {
const preferencePoint = point([
Number.parseFloat(preferenceAddress.longitude.toString()),
Number.parseFloat(preferenceAddress.latitude.toString()),
])
const listingPoint = point([
Number.parseFloat(listingAddress.longitude.toString()),
Number.parseFloat(listingAddress.latitude.toString()),
])
const calculatedBuffer = buffer(listingPoint.geometry, radius, { units: "miles" })
return booleanPointInPolygon(preferencePoint, calculatedBuffer)
? GeocodingValues.true
: GeocodingValues.false
}
} catch (e) {
console.log("error happened while calculating radius")
}
return GeocodingValues.unknown
}

public async validateRadiusPreferences(application: Application, listing: Listing) {
// Get all radius preferences from the listing
const radiusPreferenceOptions: MultiselectOption[] = listing.listingMultiselectQuestions.reduce(
(options, multiselectQuestion) => {
const newOptions = multiselectQuestion.multiselectQuestion.options?.filter(
(option) => option.validationMethod === ValidationMethod.radius
)
return [...options, ...newOptions]
},
[]
)
// If there are any radius preferences do the calculation and save the new preferences
if (radiusPreferenceOptions.length) {
const preferences: ApplicationMultiselectQuestion[] = application.preferences.map(
(preference) => {
const newPreferenceOptions: ApplicationMultiselectQuestionOption[] = preference.options.map(
(option) => {
const addressData = option.extraData.find((data) => data.type === InputType.address)
if (option.checked && addressData) {
const foundOption = radiusPreferenceOptions.find(
(preferenceOption) => preferenceOption.text === option.key
)
if (foundOption) {
const geocodingVerified = this.verifyRadius(
addressData.value as Address,
foundOption.radiusSize,
listing.buildingAddress
)
return {
...option,
extraData: [
...option.extraData,
{
key: "geocodingVerified",
type: InputType.text,
value: geocodingVerified,
},
],
}
}
}
return option
}
)
return { ...preference, options: newPreferenceOptions }
}
)
await this.repository.update({ id: application.id }, { preferences: preferences })
}
}
}
5 changes: 5 additions & 0 deletions backend/core/src/shared/types/geocoding-values.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum GeocodingValues {
true = "true",
false = "false",
unknown = "unknown",
}
2 changes: 1 addition & 1 deletion doorway-ui-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@
"typescript": "^4.9.4"
},
"dependencies": {
"@bloom-housing/ui-components": "12.0.26",
"@bloom-housing/ui-components": "12.1.0",
"@fortawesome/fontawesome-svg-core": "^6.1.1",
"@fortawesome/free-regular-svg-icons": "^6.1.1",
"@fortawesome/free-solid-svg-icons": "^6.1.1",
Expand Down
1 change: 0 additions & 1 deletion doorway-ui-components/src/actions/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ export interface ButtonProps extends AppearanceProps {
transition?: boolean
type?: "button" | "submit" | "reset"
unstyled?: boolean

isActive?: boolean
index?: number
label?: string
Expand Down
3 changes: 2 additions & 1 deletion shared-helpers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"dependencies": {
"@bloom-housing/backend-core": "^7.13.0",
"@bloom-housing/doorway-ui-components": "^1.0.0",
"@bloom-housing/ui-components": "12.0.26",
"@bloom-housing/ui-components": "12.1.0",
"@bloom-housing/ui-seeds": "1.12.1",
"axios-cookiejar-support": "4.0.6",
"tough-cookie": "4.1.3"
},
Expand Down
Loading

0 comments on commit 3f41e70

Please sign in to comment.