diff --git a/package.json b/package.json index cd49f74d..2ec0c8e0 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,7 @@ "@types/passport-local": "^1.0.33", "@types/supertest": "^2.0.10", "@types/validator": "^13.7.1", + "@types/ws": "^8.5.2", "@typescript-eslint/eslint-plugin": "^4.10.0", "@typescript-eslint/parser": "^4.10.0", "eslint": "^7.15.0", diff --git a/src/entities/dto/create-iot-device.dto.ts b/src/entities/dto/create-iot-device.dto.ts index 6db9ae3d..2f66a9fe 100644 --- a/src/entities/dto/create-iot-device.dto.ts +++ b/src/entities/dto/create-iot-device.dto.ts @@ -16,6 +16,8 @@ import { IoTDeviceType } from "@enum/device-type.enum"; import { CreateLoRaWANSettingsDto } from "./create-lorawan-settings.dto"; import { CreateSigFoxSettingsDto } from "./create-sigfox-settings.dto"; +import { IsMetadataJson } from "@helpers/is-metadata-json.validator"; +import { nameof } from "@helpers/type-helper"; export class CreateIoTDeviceDto { @ApiProperty({ required: true }) @@ -60,6 +62,7 @@ export class CreateIoTDeviceDto { @ApiProperty({ required: false }) @IsOptional() + @IsMetadataJson(nameof("metadata")) metadata?: JSON; @ApiProperty({ required: false }) diff --git a/src/entities/enum/error-codes.enum.ts b/src/entities/enum/error-codes.enum.ts index ddeaedb4..8598d96d 100644 --- a/src/entities/enum/error-codes.enum.ts +++ b/src/entities/enum/error-codes.enum.ts @@ -42,4 +42,6 @@ export enum ErrorCodes { ApplicationDoesNotExist = "MESSAGE.APPLICATION-DOES-NOT-EXIST", FailedToCreateOrUpdateIotDevice = "MESSAGE.FAILED-TO-CREATE-OR-UPDATE-IOT-DEVICE", DeviceModelDoesNotExist = "MESSAGE.DEVICE-MODEL-DOES-NOT-EXIST", + InvalidKeyInKeyValuePair = "MESSAGE.INVALID-KEY-IN-KEY-VALUE-PAIR", + InvalidValueInKeyValuePair = "MESSAGE.INVALID-VALUE-IN-KEY-VALUE-PAIR", } diff --git a/src/helpers/is-metadata-json.validator.ts b/src/helpers/is-metadata-json.validator.ts new file mode 100644 index 00000000..1c1e80e9 --- /dev/null +++ b/src/helpers/is-metadata-json.validator.ts @@ -0,0 +1,57 @@ +import { ErrorCodes } from "@enum/error-codes.enum"; +import { + registerDecorator, + ValidationArguments, + ValidationOptions, + ValidatorConstraint, + ValidatorConstraintInterface, +} from "class-validator"; + +@ValidatorConstraint({ name: "isMetadataJson", async: false }) +export class IsMetadataJsonConstraint implements ValidatorConstraintInterface { + private message = ""; + + public validate(value: string, args: ValidationArguments): boolean { + const [propertyName] = args.constraints; + this.message = `${propertyName} must be a valid list of metadata keys and values`; + + if (typeof value !== "string") { + return false; + } + try { + const json = JSON.parse(value) as Record; + + for (const key of Object.keys(json)) { + if (typeof key !== "string" || key.trim() === "") { + this.message = ErrorCodes.InvalidKeyInKeyValuePair; + return false; + } + if (typeof json[key] !== "string" || json[key].trim() === "") { + this.message = ErrorCodes.InvalidValueInKeyValuePair; + return false; + } + } + } catch (error) { + return false; + } + + return true; + } + + public defaultMessage(_args: ValidationArguments): string { + return this.message; + } +} + +export function IsMetadataJson(property: string, validationOptions?: ValidationOptions) { + return function (object: unknown, propertyName: string): void { + registerDecorator({ + name: "isMetadataJson", + target: object.constructor, + propertyName: propertyName, + constraints: [property], + options: validationOptions, + validator: IsMetadataJsonConstraint, + }); + }; +}