From b905de435d7bdadedf8872db9a8c95b59eb0201b Mon Sep 17 00:00:00 2001 From: Lauris Skraucis Date: Fri, 31 May 2024 14:06:29 +0200 Subject: [PATCH] refactor: format v2 API managed user timeZone (#15273) --- .../lib/inputs/capitalize-timezone.spec.ts | 61 +++++++++++++++++++ .../v2/src/lib/inputs/capitalize-timezone.ts | 28 +++++++++ .../users/inputs/create-managed-user.input.ts | 2 + 3 files changed, 91 insertions(+) create mode 100644 apps/api/v2/src/lib/inputs/capitalize-timezone.spec.ts create mode 100644 apps/api/v2/src/lib/inputs/capitalize-timezone.ts diff --git a/apps/api/v2/src/lib/inputs/capitalize-timezone.spec.ts b/apps/api/v2/src/lib/inputs/capitalize-timezone.spec.ts new file mode 100644 index 0000000000..4c4ce6b90f --- /dev/null +++ b/apps/api/v2/src/lib/inputs/capitalize-timezone.spec.ts @@ -0,0 +1,61 @@ +import { plainToClass } from "class-transformer"; +import { IsOptional, IsString } from "class-validator"; + +import { CapitalizeTimeZone } from "./capitalize-timezone"; + +class TestDto { + @IsOptional() + @IsString() + @CapitalizeTimeZone() + timeZone?: string; +} + +describe("CapitalizeTimeZone", () => { + it("should capitalize single part time zone correctly", () => { + const input = { timeZone: "egypt" }; + const output = plainToClass(TestDto, input); + expect(output.timeZone).toBe("Egypt"); + }); + + it("should capitalize one-part time zone correctly", () => { + const input = { timeZone: "europe/rome" }; + const output = plainToClass(TestDto, input); + expect(output.timeZone).toBe("Europe/Rome"); + }); + + it("should capitalize multi-part time zone correctly", () => { + const input = { timeZone: "america/new_york" }; + const output = plainToClass(TestDto, input); + expect(output.timeZone).toBe("America/New_York"); + }); + + it("should capitalize complex time zone correctly", () => { + const input = { timeZone: "europe/isle_of_man" }; + const output = plainToClass(TestDto, input); + expect(output.timeZone).toBe("Europe/Isle_Of_Man"); + }); + + it("should handle already capitalized time zones correctly", () => { + const input = { timeZone: "Asia/Tokyo" }; + const output = plainToClass(TestDto, input); + expect(output.timeZone).toBe("Asia/Tokyo"); + }); + + it("should handle missing time zone correctly", () => { + const input = {}; + const output = plainToClass(TestDto, input); + expect(output.timeZone).toBeUndefined(); + }); + + it("should capitalize EST at the end of the string", () => { + const input = { email: "test@example.com", timeZone: "utc/est" }; + const output = plainToClass(TestDto, input); + expect(output.timeZone).toBe("UTC/EST"); + }); + + it("should capitalize UTC when surrounded by non-alphabetical characters", () => { + const input = { email: "test@example.com", timeZone: "utc/gmt+3_est" }; + const output = plainToClass(TestDto, input); + expect(output.timeZone).toBe("UTC/GMT+3_EST"); + }); +}); diff --git a/apps/api/v2/src/lib/inputs/capitalize-timezone.ts b/apps/api/v2/src/lib/inputs/capitalize-timezone.ts new file mode 100644 index 0000000000..c94a9dc4c4 --- /dev/null +++ b/apps/api/v2/src/lib/inputs/capitalize-timezone.ts @@ -0,0 +1,28 @@ +import { Transform } from "class-transformer"; + +export function CapitalizeTimeZone(): PropertyDecorator { + return Transform(({ value }) => { + if (typeof value === "string") { + const parts = value.split("/"); + const normalizedParts = parts.map((part) => + part + .split("_") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) + .join("_") + ); + let normalizedTimeZone = normalizedParts.join("/"); + + // note(Lauris): regex matching GMT, EST, UTC at the start, end, or surrounded by non-letters and capitalizing them + const specialCases = ["GMT", "EST", "UTC"]; + specialCases.forEach((specialCase) => { + const regex = new RegExp(`(^|[^a-zA-Z])(${specialCase})([^a-zA-Z]|$)`, "gi"); + normalizedTimeZone = normalizedTimeZone.replace(regex, (match, p1, p2, p3) => { + return `${p1}${specialCase}${p3}`; + }); + }); + + return normalizedTimeZone; + } + return value; + }); +} diff --git a/apps/api/v2/src/modules/users/inputs/create-managed-user.input.ts b/apps/api/v2/src/modules/users/inputs/create-managed-user.input.ts index 8d20908957..755088e6a8 100644 --- a/apps/api/v2/src/modules/users/inputs/create-managed-user.input.ts +++ b/apps/api/v2/src/modules/users/inputs/create-managed-user.input.ts @@ -1,3 +1,4 @@ +import { CapitalizeTimeZone } from "@/lib/inputs/capitalize-timezone"; import { ApiProperty } from "@nestjs/swagger"; import { IsOptional, IsTimeZone, IsString } from "class-validator"; @@ -26,6 +27,7 @@ export class CreateManagedUserInput { @IsTimeZone() @IsOptional() + @CapitalizeTimeZone() @ApiProperty({ example: "America/New_York" }) timeZone?: string; }