All files / services pro.validators.ts

100% Statements 33/33
100% Branches 54/54
100% Functions 11/11
100% Lines 31/31

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132              6x                             13x 11x 2x   9x 1x   8x 1x         22x 3x             19x 2x                 20x 186x 186x 5x 1x   7x 1x                     20x 2x                 20x         2x         21x 3x         21x         2x             16x         2x                 17x         2x      
// Pro Validation Helpers
import { ValidationError } from "../lib/errors";
import { PRO_STATUS, TEAM_SIZES, RUSH_ORDER_PREMIUMS } from "../db/schema";
import { isValidEnum } from "../lib/utils";
import type { CreateProInput, UpdateProInput } from "./pro.service";
 
// Fields that require array validation
const ARRAY_FIELDS: Array<{ field: keyof CreateProInput; name: string }> = [
	{ field: "businessTypesSecondary", name: "Business types secondary" },
	{ field: "timelineCapabilities", name: "Timeline capabilities" },
	{ field: "customerSegmentsSecondary", name: "Customer segments secondary" },
	{ field: "projectScaleIds", name: "Project scale IDs" },
	{ field: "serviceCategoryIds", name: "Service category IDs" },
	{ field: "materialTagIds", name: "Material tag IDs" },
	{ field: "brandsWorkWith", name: "Brands work with" },
	{ field: "brandsOfficialPartner", name: "Brands official partner" },
	{ field: "serviceAreaIds", name: "Service area IDs" },
	{ field: "languagesSpoken", name: "Languages spoken" },
];
 
export function validateBusinessName(name: string | undefined): void {
	// Allow empty string for auto-created profiles (name set during onboarding)
	if (name === "") return;
	if (!name?.trim()) {
		throw new ValidationError("Business name is required");
	}
	if (name.length < 2) {
		throw new ValidationError("Business name must be at least 2 characters");
	}
	if (name.length > 100) {
		throw new ValidationError("Business name must be less than 100 characters");
	}
}
 
export function validateStatus(status: string | undefined): void {
	if (status && !isValidEnum(status, PRO_STATUS)) {
		throw new ValidationError(
			`Invalid status. Must be one of: ${PRO_STATUS.join(", ")}`,
		);
	}
}
 
export function validateTeamSize(teamSize: string | undefined): void {
	if (teamSize && !isValidEnum(teamSize, TEAM_SIZES)) {
		throw new ValidationError(
			`Invalid teamSize. Must be one of: ${TEAM_SIZES.join(", ")}`,
		);
	}
}
 
export function validateArrayFields(
	input: CreateProInput | UpdateProInput,
): void {
	for (const { field, name } of ARRAY_FIELDS) {
		const value = input[field];
		if (value !== undefined && value !== null) {
			if (!Array.isArray(value)) {
				throw new ValidationError(`${name} must be an array`);
			}
			if (!value.every((item) => typeof item === "string")) {
				throw new ValidationError(`${name} must be an array of strings`);
			}
		}
	}
}
 
export function validatePrimarySecondaryOverlap(
	primaryId: string | undefined | null,
	secondaryIds: string[] | undefined | null,
	fieldName: string,
): void {
	if (primaryId && secondaryIds?.includes(primaryId)) {
		throw new ValidationError(
			`Primary ${fieldName} cannot also be a secondary ${fieldName}`,
		);
	}
}
 
export function validateYearsInBusiness(
	years: number | undefined | null,
): void {
	if (
		years !== undefined &&
		years !== null &&
		(typeof years !== "number" || years < 0)
	) {
		throw new ValidationError("Years in business must be a positive number");
	}
}
 
export function validateEmail(email: string | undefined): void {
	if (email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
		throw new ValidationError("Invalid email format");
	}
}
 
export function validateProfileFields(fields: unknown): void {
	if (
		fields !== undefined &&
		fields !== null &&
		(typeof fields !== "object" || Array.isArray(fields))
	) {
		throw new ValidationError("Profile fields must be an object");
	}
}
 
export function validateRushOrderPremium(
	premium: string | undefined | null,
): void {
	if (
		premium !== undefined &&
		premium !== null &&
		!isValidEnum(premium, RUSH_ORDER_PREMIUMS)
	) {
		throw new ValidationError(
			`Invalid rush order premium. Must be one of: ${RUSH_ORDER_PREMIUMS.join(", ")}`,
		);
	}
}
 
export function validateAcceptsRushOrders(
	accepts: boolean | undefined | null,
): void {
	if (
		accepts !== undefined &&
		accepts !== null &&
		typeof accepts !== "boolean"
	) {
		throw new ValidationError("acceptsRushOrders must be a boolean");
	}
}