diff --git a/services/listings/__tests__/lib/url_helper.test.ts b/services/listings/__tests__/lib/url_helper.test.ts new file mode 100644 index 0000000000..3196c829a3 --- /dev/null +++ b/services/listings/__tests__/lib/url_helper.test.ts @@ -0,0 +1,28 @@ +import { formatUrlSlug, listingUrlSlug } from "../../src/lib/url_helper" +import { Listing } from "@bloom-housing/core" +import triton from "../../listings/triton.json" + +describe("formatUrlSlug", () => { + test("reformats strings properly", () => { + expect(formatUrlSlug("snake_case")).toEqual("snake_case") + expect(formatUrlSlug("SnakeCase")).toEqual("snake_case") + expect(formatUrlSlug("Mix of spaces_and-hyphens")).toEqual("mix_of_spaces_and_hyphens") + expect(formatUrlSlug("Lots@of&weird spaces&^&!@^*&AND OTHER_CHARS")).toEqual( + "lots_of_weird_spaces_and_other_chars" + ) + }) + + test("with an empty string", () => { + expect(formatUrlSlug("")).toEqual("") + }) +}) + +describe("listingUrlSlug", () => { + // Force cast to listing - should we add a dependency to `listingsLoader` instead? + const listing = (triton as unknown) as Listing + + test("Generates a URL slug for a Listing", () => { + const slug = listingUrlSlug(listing) + expect(slug).toEqual("the_triton_55_triton_park_lane_foster_city_ca") + }) +}) diff --git a/services/listings/src/index.ts b/services/listings/src/index.ts index c3617a18bc..4ea9173060 100644 --- a/services/listings/src/index.ts +++ b/services/listings/src/index.ts @@ -5,6 +5,7 @@ import jp from "jsonpath" import { Listing } from "@bloom-housing/core" import listingsLoader from "./lib/listings_loader" import { transformUnits } from "./lib/unit_transformations" +import { listingUrlSlug } from "./lib/url_helper" import { amiCharts } from "./lib/ami_charts" dotenv.config({ path: ".env" }) @@ -29,6 +30,7 @@ app.use(async ctx => { // Transform all the listings listings.forEach(listing => { listing.unitsSummarized = transformUnits(listing.units, amiCharts) + listing.urlSlug = listingUrlSlug(listing) }) const data = { diff --git a/services/listings/src/lib/url_helper.ts b/services/listings/src/lib/url_helper.ts new file mode 100644 index 0000000000..3979d2ef33 --- /dev/null +++ b/services/listings/src/lib/url_helper.ts @@ -0,0 +1,30 @@ +/** + * Formats the input string as a URL slug. + * This includes the following transformations: + * - All lowercase + * - Remove special characters + * - snake_case + * @param input + */ +import { Listing } from "@bloom-housing/core" + +export const formatUrlSlug = (input: string): string => { + return ( + ( + (input || "") + // Divide into words based on upper case letters followed by lower case letters + .match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]+|[0-9]+/g) || [] + ) + + .join("_") + .toLowerCase() + ) +} + +export const listingUrlSlug = (listing: Listing): string => { + const { + name, + buildingAddress: { city, street, state } + } = listing + return formatUrlSlug([name, street, city, state].join(" ")) +} diff --git a/shared/core/src/listings.ts b/shared/core/src/listings.ts index 7416b30793..92685b2178 100644 --- a/shared/core/src/listings.ts +++ b/shared/core/src/listings.ts @@ -58,6 +58,7 @@ export interface Listing { unitsAvailable: number unitAmenities: string unitsSummarized?: UnitsSummarized + urlSlug?: string waitlistCurrentSize: number waitlistMaxSize: number yearBuilt: number