Skip to content

Commit

Permalink
Merge branch 'dev' into 1890/character-limit-update
Browse files Browse the repository at this point in the history
  • Loading branch information
dominikx96 committed Oct 5, 2021
2 parents db7db96 + e5b0e25 commit 1d34ac7
Show file tree
Hide file tree
Showing 88 changed files with 3,337 additions and 1,533 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,16 @@ All notable changes to this project will be documented in this file. The format

- Add POST /users/invite endpoint and extend PUT /users/confirm with optional password change ([#1801](https://github.com/bloom-housing/bloom/pull/1801))
- Add `isPartner` filter to GET /user/list endpoint ([#1830](https://github.com/bloom-housing/bloom/pull/1830))
- Changes to applications done through `PUT /applications/:id` are now reflected in AFS ([#1810](https://github.com/bloom-housing/bloom/pull/1810))
- Add logic for connecting newly created user account to existing applications (matching based on applicant.emailAddress) ([#1807](https://github.com/bloom-housing/bloom/pull/1807))
- ** Breaking Change**: Add `jurisdiction` relation to `ReservedCommunitType` entity ([#1889](https://github.com/bloom-housing/bloom/pull/1889))
- Added new userProfile resource and endpoint `PUT /userProfile/:id` suited specifically for users updating their own profiles ([#1862](https://github.com/bloom-housing/bloom/pull/1862))

- Changed:
- ** Breaking Change**: Endpoint `PUT /user/:id` is admin only now, because it allows edits over entire `user` table ([#1862](https://github.com/bloom-housing/bloom/pull/1862))
- Changes to applications done through `PUT /applications/:id` are now reflected in AFS ([#1810](https://github.com/bloom-housing/bloom/pull/1810))
- Adds confirmationCode to applications table ([#1854](https://github.com/bloom-housing/bloom/pull/1854))
- Add various backend filters ([#1884](https://github.com/bloom-housing/bloom/pull/1884))

## Frontend

Expand Down Expand Up @@ -110,6 +118,7 @@ All notable changes to this project will be documented in this file. The format
- Adds `authz.e2e-spec.ts` test cover for preventing user from voluntarily changing his associated `roles` object [#1575](https://github.com/bloom-housing/bloom/pull/1575)
- Adds Jurisdictions to users, listings and translations. The migration script assigns the first alpha sorted jurisdiction to users, so this piece may need to be changed for Detroit, if they have more than Detroit in their DB. [#1776](https://github.com/bloom-housing/bloom/pull/1776)
- Added the optional jurisdiction setting notificationsSignUpURL, which now appears on the home page if set ([#1802](https://github.com/bloom-housing/bloom/pull/1802)) (Emily Jablonski)
- Adds Listings managment validations required for publishing a Listing [#1850](https://github.com/bloom-housing/bloom/pull/1850) (Michał Plebański & Emily Jablonski)

- Changed:

Expand All @@ -135,6 +144,7 @@ All notable changes to this project will be documented in this file. The format
- Fixes flakiness in authz.e2e-spec.ts related to logged in user trying to GET /applications which did not belong to him (sorting of UUID is not deterministic, so the user should fetch by specying a query param userId = self) [#1575](https://github.com/bloom-housing/bloom/pull/1575)
- Fixed ListingsService.retrieve `view` query param not being optional in autogenerated client (it should be) [#1575](https://github.com/bloom-housing/bloom/pull/1575)
- updated DTOs to omit entities and use DTOs for application-method, user-roles, user, listing and units-summary ([#1679](https://github.com/bloom-housing/bloom/pull/1679))
- makes application flagged sets module take applications edits into account (e.g. a leasing agent changes something in the application) ([#1810](https://github.com/bloom-housing/bloom/pull/1810))

### General

Expand Down
6 changes: 3 additions & 3 deletions backend/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@
"@anchan828/nest-sendgrid": "^0.3.25",
"@google-cloud/translate": "^6.2.6",
"@nestjs/cli": "^7.5.1",
"@nestjs/common": "^7.4.4",
"@nestjs/common": "^7.6.18",
"@nestjs/config": "^0.5.0",
"@nestjs/core": "^7.4.4",
"@nestjs/core": "^7.6.18",
"@nestjs/jwt": "^7.1.0",
"@nestjs/passport": "^7.1.0",
"@nestjs/platform-express": "^7.4.4",
"@nestjs/platform-express": "^7.6.18",
"@nestjs/swagger": "4.7.3",
"@nestjs/throttler": "^1.1.2",
"@nestjs/typeorm": "^7.1.0",
Expand Down
215 changes: 215 additions & 0 deletions backend/core/scripts/import-helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
import * as client from "../types/src/backend-swagger"
import axios from "axios"
import {
ListingCreate,
ListingStatus,
serviceOptions,
UnitsSummaryCreate,
UnitCreate,
} from "../types/src/backend-swagger"

// NOTE: This script relies on any logged-in users having permission to create
// listings and properties (defined in backend/core/src/auth/authz_policy.csv)

const preferencesService = new client.PreferencesService()
const listingsService = new client.ListingsService()
const authService = new client.AuthService()
const unitTypesService = new client.UnitTypesService()
const unitAccessibilityPriorityTypesService = new client.UnitAccessibilityPriorityTypesService()
const applicationMethodsService = new client.ApplicationMethodsService()
const reservedCommunityTypesService = new client.ReservedCommunityTypesService()
const jurisdictionService = new client.JurisdictionsService()

// Create these import interfaces to mimic the format defined in backend-swagger.ts, but allow
// certain fields to have a simpler type. For example: allow listing.units.unitType to be a
// string (e.g. "oneBdrm"), and then the importListing function will look up the corresponding
// unitType object by name and use that unitType object to construct the UnitCreate.
export interface ListingImport
extends Omit<ListingCreate, "unitsSummary" | "units" | "reservedCommunityType" | "jurisdiction"> {
unitsSummary?: UnitsSummaryImport[]
units?: UnitImport[]
reservedCommunityTypeName?: string
jurisdictionName?: string
}
export interface UnitsSummaryImport extends Omit<UnitsSummaryCreate, "unitType"> {
unitType?: string
}
export interface UnitImport extends Omit<UnitCreate, "unitType" | "priorityType"> {
priorityType?: string
unitType?: string
}

async function uploadEntity(entityKey, entityService, listing) {
const newRecordsIds = await Promise.all(
listing[entityKey].map(async (obj) => {
try {
const res = await entityService.create({
body: obj,
})
return res
} catch (e) {
console.log(obj)
console.log(e.response.data.message)
process.exit(1)
}
})
)
listing[entityKey] = newRecordsIds
return listing
}

async function uploadListing(listing: ListingCreate) {
try {
return await listingsService.create({
body: listing,
})
} catch (e) {
console.log(listing)
throw new Error(e.response.data.message)
}
}

async function uploadReservedCommunityType(name: string, jurisdictions: client.Jurisdiction[]) {
try {
return await reservedCommunityTypesService.create({
body: { name, jurisdiction: jurisdictions[0] },
})
} catch (e) {
console.log(e.response)
process.exit(1)
}
}

async function getReservedCommunityType(name: string) {
try {
const reservedTypes = await reservedCommunityTypesService.list()
return reservedTypes.filter((reservedType) => reservedType.name === name)[0]
} catch (e) {
console.log(e.response)
process.exit(1)
}
}

function reformatListing(listing, relationsKeys: string[]) {
relationsKeys.forEach((relation) => {
if (!(relation in listing) || listing[relation] === null) {
listing[relation] = []
} else {
// Replace nulls with undefined and remove id
// This is because validation @IsOptional does not allow nulls
const relationArr = listing[relation]
for (const obj of relationArr) {
try {
delete obj["id"]
} catch (e) {
console.error(e)
}
for (const key in obj) {
if (obj[key] === null) {
delete obj[key]
}
}
}
}
})
if (!("status" in listing)) {
listing.status = ListingStatus.active
}
try {
delete listing["id"]
} catch (e) {
console.error(e)
}
return listing
}

const findByName = (list, name: string) => {
return list.find((el) => el.name === name)
}

export async function importListing(
apiUrl: string,
email: string,
password: string,
listing: ListingImport
) {
serviceOptions.axios = axios.create({
baseURL: apiUrl,
timeout: 10000,
})

// Log in to retrieve an access token.
const { accessToken } = await authService.login({
body: {
email: email,
password: password,
},
})

// Update the axios config so future requests include the access token in the header.
serviceOptions.axios = axios.create({
baseURL: apiUrl,
timeout: 10000,
headers: {
Authorization: `Bearer ${accessToken}`,
},
})
const unitTypes = await unitTypesService.list()
const priorityTypes = await unitAccessibilityPriorityTypesService.list()
const jurisdictions = await jurisdictionService.list()

// Tidy a few of the listing's fields.
const relationsKeys = []
listing = reformatListing(listing, relationsKeys)

// Upload new entities.
listing = await uploadEntity("preferences", preferencesService, listing)
listing = await uploadEntity("applicationMethods", applicationMethodsService, listing)

// Look up the reserved community type by name, or create it if it doesn't yet exist.
let reservedCommunityType: client.ReservedCommunityType
if (listing.reservedCommunityTypeName) {
reservedCommunityType = await getReservedCommunityType(listing.reservedCommunityTypeName)
if (!reservedCommunityType) {
reservedCommunityType = await uploadReservedCommunityType(
listing.reservedCommunityTypeName,
jurisdictions
)
}
}

// Construct the units and unitsSummary arrays expected by the backend, by looking up the
// unitTypes and priorityTypes referenced by name.
const unitsCreate: UnitCreate[] = []
listing.units.forEach((unit) => {
const priorityType = findByName(priorityTypes, unit.priorityType)
const unitType = findByName(unitTypes, unit.unitType)
unitsCreate.push({ ...unit, priorityType: priorityType, unitType: unitType })
})
const unitsSummaryCreate: UnitsSummaryCreate[] = []
if (listing.unitsSummary) {
listing.unitsSummary.forEach((summary) => {
const unitType = findByName(unitTypes, summary.unitType)
unitsSummaryCreate.push({ ...summary, unitType: unitType })
})
}

let jurisdiction: client.Jurisdiction = null
if (listing.jurisdictionName) {
jurisdiction = findByName(jurisdictions, listing.jurisdictionName)
}

// Construct the ListingCreate to be sent to the backend. Its structure mostly mimics that of the
// input ListingImport, with the exception of the fields for which we had to look up referenced
// types.
const listingCreate: ListingCreate = {
...listing,
unitsSummary: unitsSummaryCreate,
units: unitsCreate,
reservedCommunityType: reservedCommunityType,
jurisdiction: jurisdiction,
}

// Upload the listing, and then return it.
return await uploadListing(listingCreate)
}
33 changes: 33 additions & 0 deletions backend/core/scripts/import-listing-from-json-file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { importListing, ListingImport } from "./import-helpers"
import fs from "fs"
import { Listing } from "../types/src/backend-swagger"

// Example usage (from within /backend/core):
// $ yarn ts-node scripts/import-listing-from-json-file.ts http://localhost:3100 [email protected]:abcdef scripts/minimal-listing.json

async function main() {
if (process.argv.length < 5) {
console.log(
"usage: yarn ts-node scripts/import-listing-from-json-file.ts api_url email:password input_listing.json"
)
process.exit(1)
}

const [apiUrl, userAndPassword, listingFilePath] = process.argv.slice(2)
const [email, password] = userAndPassword.split(":")

const listing: ListingImport = JSON.parse(fs.readFileSync(listingFilePath, "utf-8"))

let newListing: Listing
try {
newListing = await importListing(apiUrl, email, password, listing)
} catch (e) {
console.log(e)
process.exit(1)
}

console.log(newListing)
console.log("Success! New listing created.")
}

void main()
Loading

0 comments on commit 1d34ac7

Please sign in to comment.