Skip to content

Commit

Permalink
Release 03-14-2024 (#678)
Browse files Browse the repository at this point in the history
* fix: null check on option links (bloom-housing#3897)

* fix: adjust spacing for preference application checkboxes (bloom-housing#3868)

* feat: limit user names allowed (bloom-housing#3908)

* fix: the Save and Return link should only show after the Review step has been reached (bloom-housing#3901)

* fix: 3898/passwordless schema changes (bloom-housing#3902)

* feat: mfaCode -> singleUseCode

* feat: adding new field to jurisdiction

* fix: call submit directly from onClick (bloom-housing#3907)

* feat: single use code request endpoint (bloom-housing#3915)

* feat: single use code request endpoint

* fix: updating mocks

* fix: update per Em and Emily

* fix: updates after convo with eric

* fix: updates to tests

* fix: export activity interceptor (bloom-housing#3926)

* fix: export activity interceptor

* fix: import path

* fix: improve export logging tests

* fix: tidy up implementation

* fix: commenting for clarity

* fix: user update/create fixes (bloom-housing#3932)

* feat: fixes from hba into core (bloom-housing#3910)

* fix: limit the characters for name on user (#667)

* fix: fixes password out of date error messaging (#669)

* fix: fixes password out of date error messaging

* fix: prod error fixes

* fix: test fix

* fix: public site fix take 2 (#670)

* feat: new endpoint, forgot pwd fix (#671)

* feat: new endpoint, forgot pwd fix

* feat: using new endpoint to public

* fix: update per morgan

* fix: updates to pr

* fix: new test to get us over coverage

* fix: update per morgan

* fix: add all of the jurisdiction data to external (#672)

* fix: add all of the jurisdiction data to external

* fix: use correct field name

* fix: add security around application list (#674)

* fix: add security around application list

* fix: test fixes

* fix: coverage requirement drop

---------

Co-authored-by: Morgan Ludtke <[email protected]>

* fix: load shared helpers tailwind (bloom-housing#3929)

* fix: update to correct juris on change requested (bloom-housing#3934)

* fix: include demographics in partners applicant csv export  (bloom-housing#3933)

* fix: add flag to include demographics for partners

* refactor: change migration folder name

* chore(deps): bump ip from 1.1.5 to 1.1.9 (bloom-housing#3895)

* refactor: uptake and merge card (bloom-housing#3922)

* fix: underscores in translations (bloom-housing#3925)

* fix: limit requestedChangesUser in Listings response (bloom-housing#3921)

* feat: connect up requsted changes user properly

* fix: limit requestedChangesUser in Listings response

bloom-housing#3889

* fix: update api service unit tests

bloom-housing#3889

* fix: add details view unit test

bloom-housing#3889

* fix: send id and name

bloom-housing#3889

* fix: correct inport statement

* fix: addressing comments

bloom-housing#3889

* fix: cleanup swagger changes

bloom-housing#3889

* fix: add missing return statement

bloom-housing#3889

---------

Co-authored-by: Eric McGarry <[email protected]>

* feat: security patch (bloom-housing#3946)

* feat: security patch

* fix: update per eric

* feat: 3909/add redirect url prisma (bloom-housing#3938)

* feat: get email url from getPublicEmailURL

* fix: handle undefined url case and simplify parsing

* fix: use only baseUrl in welcome and password emails

* fix: fix test

* feat: new single use code login endpoint (bloom-housing#3928)

* feat: new single use code login endpoint

* fix: updates per pr comments

* fix: update seeds + fix listing delete (bloom-housing#3940)

* feat: unconfirmed user login error fix (bloom-housing#3949)

* feat: unconfirmed user login error fix

* fix: unconfirmed user attempting to login, public user logging into partner site, seeding es

* updates per cade

* fix: undefined check

* Merge remote-tracking branch 'origin/main' into security-patch-2

* fix: merge mistakes were made

---------

Co-authored-by: Cade Wolcott <[email protected]>

---------

Co-authored-by: Morgan Ludtke <[email protected]>
Co-authored-by: Krzysztof Zięcina <[email protected]>
Co-authored-by: Jared White <[email protected]>
Co-authored-by: cade-exygy <[email protected]>
Co-authored-by: ColinBuyck <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Emily Jablonski <[email protected]>
Co-authored-by: Eric McGarry <[email protected]>
Co-authored-by: Cade Wolcott <[email protected]>
  • Loading branch information
10 people authored Mar 15, 2024
1 parent f878970 commit 7e5d969
Show file tree
Hide file tree
Showing 118 changed files with 3,632 additions and 955 deletions.
2 changes: 1 addition & 1 deletion api/Procfile
Original file line number Diff line number Diff line change
@@ -1 +1 @@
web: yarn start:prod
web: yarn db:migration:run && yarn start:prod
2 changes: 1 addition & 1 deletion api/prisma/migrations/00_init/migration.sql
Original file line number Diff line number Diff line change
Expand Up @@ -1292,4 +1292,4 @@ ALTER TABLE
ADD
CONSTRAINT "FK_87b8888186ca9769c960e926870" FOREIGN KEY ("user_id") REFERENCES "user_accounts"("id") ON
DELETE CASCADE ON
UPDATE CASCADE;
UPDATE CASCADE;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-- AlterTable

ALTER TABLE "user_accounts" RENAME COLUMN "mfa_code" TO "single_use_code";


ALTER TABLE "user_accounts" RENAME COLUMN "mfa_code_updated_at" TO "single_use_code_updated_at";

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "jurisdictions" ADD COLUMN "allow_single_use_code_login" BOOLEAN NOT NULL DEFAULT false;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-- Adds new single-use code translations for emails.

UPDATE translations
SET translations = jsonb_set(translations, '{singleUseCodeEmail}', '{"greeting": "Hi","message": "Use the following code to sign in to your %{jurisdictionName} account. This code will be valid for 5 minutes. Never share this code.","singleUseCode": "%{singleUseCode}"}')
WHERE jurisdiction_id IS NULL
and language = 'en';


UPDATE translations
SET translations = jsonb_set(translations, '{mfaCodeEmail, mfaCode}', '"Your access code is: %{singleUseCode}"')
WHERE language = 'en';

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE "activity_log" ALTER COLUMN "record_id" DROP NOT NULL;
2 changes: 2 additions & 0 deletions api/prisma/migrations/06_requested_user_updates/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AddForeignKey
ALTER TABLE "listings" ADD CONSTRAINT "listings_requested_changes_user_id_fkey" FOREIGN KEY ("requested_changes_user_id") REFERENCES "user_accounts"("id") ON DELETE NO ACTION ON UPDATE NO ACTION;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "jurisdictions" ADD COLUMN "enable_partner_demographics" BOOLEAN NOT NULL DEFAULT false;
Original file line number Diff line number Diff line change
@@ -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;

51 changes: 27 additions & 24 deletions api/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ model ActivityLog {
createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(6)
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamp(6)
module String @db.VarChar
recordId String @map("record_id") @db.Uuid
action String @db.VarChar
metadata Json?
recordId String? @map("record_id") @db.Uuid
userId String? @map("user_id") @db.Uuid
userAccounts UserAccounts? @relation(fields: [userId], references: [id], onUpdate: NoAction)
Expand Down Expand Up @@ -101,7 +101,7 @@ model AmiChart {
@@map("ami_chart")
}

// Note: [birth_month, birth_day, birth_year] formerly max length 8; [phone_number, phone_number_type] formerly max length 16;
// Note: [birth_month, birth_day, birth_year] formerly max length 8; [phone_number, phone_number_type] formerly max length 16;
// [first_name, middle_name, last_name] formerly max length 64
// Note: [first_name, last_name] formerly min length 1
// Note: [email_address] needs to have lower case enforcement and needs isEmail validator
Expand Down Expand Up @@ -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])
Expand All @@ -161,15 +161,15 @@ 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")
}

// Note: [additional_phone_number, additional_phone_number_type, household_size] formerly max length 16;
// Note: [additional_phone_number, additional_phone_number_type, household_size] formerly max length 16;
// [contact_preferences, income] formerly max length 64;
// [app_url] formerly max length 256;
// [app_url] formerly max length 256;
// Note: [contact_preferences] formerly max array length of 8
// Note: [household_member] formerly had max array lenght of 32
// Note: missing virtual [flagged] field
Expand Down Expand Up @@ -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[]
Expand Down Expand Up @@ -284,7 +284,7 @@ model GeneratedListingTranslations {
@@map("generated_listing_translations")
}

// Note: [birth_month, birth_day, birth_year] formerly max length 8;
// Note: [birth_month, birth_day, birth_year] formerly max length 8;
// [phone_number, phone_number_type] formerly max length 16
// [first_name, middle_name, last_name, relationship] formerly max length 64;
// Note: [email_address] formerly enforced lower case
Expand All @@ -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)
Expand All @@ -326,9 +326,11 @@ model Jurisdictions {
emailFromAddress String? @map("email_from_address")
rentalAssistanceDefault String @map("rental_assistance_default")
enablePartnerSettings Boolean @default(false) @map("enable_partner_settings")
enablePartnerDemographics Boolean @default(false) @map("enable_partner_demographics")
enableAccessibilityFeatures Boolean @default(false) @map("enable_accessibility_features")
enableUtilitiesIncluded Boolean @default(false) @map("enable_utilities_included")
enableGeocodingPreferences Boolean @default(false) @map("enable_geocoding_preferences")
allowSingleUseCodeLogin Boolean @default(false) @map("allow_single_use_code_login")
amiChart AmiChart[]
multiselectQuestions MultiselectQuestions[]
listings Listings[]
Expand Down Expand Up @@ -363,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")
}
Expand Down Expand Up @@ -402,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])
Expand All @@ -414,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")
Expand Down Expand Up @@ -565,21 +567,22 @@ model Listings {
requestedChanges String? @map("requested_changes")
requestedChangesDate DateTime? @default(dbgenerated("'1970-01-01 00:00:00-07'::timestamp with time zone")) @map("requested_changes_date") @db.Timestamptz(6)
requestedChangesUserId String? @map("requested_changes_user_id") @db.Uuid
requestedChangesUser UserAccounts? @relation("requested_changes_user", fields: [requestedChangesUserId], references: [id], onDelete: NoAction, onUpdate: NoAction)
@@index([jurisdictionId])
@@map("listings")
}

model MapLayers {
id String @id() @default(dbgenerated("uuid_generate_v4()")) @db.Uuid
id String @id() @default(dbgenerated("uuid_generate_v4()")) @db.Uuid
name String
jurisdictionId String @map("jurisdiction_id")
featureCollection Json @map("feature_collection") @default("{}")
jurisdictionId String @map("jurisdiction_id")
featureCollection Json @default("{}") @map("feature_collection")
@@map("map_layers")
}

// Note: hold over from TypeORM
// Note: hold over from TypeORM
model Migrations {
id Int @id() @default(autoincrement())
timestamp BigInt
Expand Down Expand Up @@ -616,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")
}
Expand Down Expand Up @@ -650,9 +653,9 @@ model Translations {

// Note: [name] formerly max length 256
model UnitAccessibilityPriorityTypes {
id String @id() @default(dbgenerated("uuid_generate_v4()")) @db.Uuid
createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(6)
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamp(6)
id String @id() @default(dbgenerated("uuid_generate_v4()")) @db.Uuid
createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(6)
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamp(6)
name String
units Units[]
unitGroup UnitGroup[]
Expand Down Expand Up @@ -753,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")
}
Expand All @@ -779,8 +782,8 @@ model UserAccounts {
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamp(6)
language LanguagesEnum?
mfaEnabled Boolean @default(false) @map("mfa_enabled")
mfaCode String? @map("mfa_code") @db.VarChar
mfaCodeUpdatedAt DateTime? @map("mfa_code_updated_at") @db.Timestamptz(6)
singleUseCode String? @map("single_use_code") @db.VarChar
singleUseCodeUpdatedAt DateTime? @map("single_use_code_updated_at") @db.Timestamptz(6)
lastLoginAt DateTime @default(now()) @map("last_login_at") @db.Timestamp(6)
failedLoginAttemptsCount Int @default(0) @map("failed_login_attempts_count")
phoneNumberVerified Boolean? @default(false) @map("phone_number_verified")
Expand All @@ -795,6 +798,7 @@ model UserAccounts {
jurisdictions Jurisdictions[]
userPreferences UserPreferences?
userRoles UserRoles?
requestedChangesListings Listings[] @relation("requested_changes_user")
@@map("user_accounts")
}
Expand All @@ -809,7 +813,6 @@ model UserRoles {
@@map("user_roles")
}


// START DETROIT SPECIFIC
model ListingNeighborhoodAmenities {
id String @id() @default(dbgenerated("uuid_generate_v4()")) @db.Uuid
Expand Down
12 changes: 10 additions & 2 deletions api/prisma/seed-dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
8 changes: 8 additions & 0 deletions api/prisma/seed-helpers/application-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const applicationFactory = async (optionalParams?: {
householdMember?: Prisma.HouseholdMemberCreateWithoutApplicationsInput[];
demographics?: Prisma.DemographicsCreateWithoutApplicationsInput;
multiselectQuestions?: Partial<MultiselectQuestions>[];
userId?: string;
}): Promise<Prisma.ApplicationsCreateInput> => {
let preferredUnitTypes: Prisma.UnitTypesCreateNestedManyWithoutApplicationsInput;
if (optionalParams?.unitTypeId) {
Expand Down Expand Up @@ -88,6 +89,13 @@ export const applicationFactory = async (optionalParams?: {
demographics: {
create: demographics,
},
userAccounts: optionalParams?.userId
? {
connect: {
id: optionalParams.userId,
},
}
: undefined,
};
};

Expand Down
Loading

0 comments on commit 7e5d969

Please sign in to comment.