All files / services/project validation.ts

100% Statements 36/36
100% Branches 50/50
100% Functions 7/7
100% Lines 35/35

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                                    30x 8x   22x 21x 4x   17x 2x                         36x 3x       33x 2x       31x 2x       29x 2x                       31x 8x 3x   6x 2x                     33x         3x   30x 6x 6x 3x                     8x 8x 8x 8x             18x 9x   17x 17x 17x    
// Project Validation Utilities
import {
	PRO_STATUS,
	PROPERTY_TYPES,
	BUDGET_RANGES,
	PROJECT_SCOPES,
} from "../../db/schema";
import { ValidationError } from "../../lib/errors";
import { isValidEnum, isValidEnumArray } from "../../lib/utils";
import type { CreateProjectInput, UpdateProjectInput } from "./types";
 
/**
 * Validate project title
 */
export function validateTitle(
	title: string | undefined,
	required = true,
): void {
	if (required && !title?.trim()) {
		throw new ValidationError("Project title is required");
	}
	if (title !== undefined) {
		if (title.length < 2) {
			throw new ValidationError("Project title must be at least 2 characters");
		}
		if (title.length > 200) {
			throw new ValidationError(
				"Project title must be less than 200 characters",
			);
		}
	}
}
 
/**
 * Validate enum fields (status, propertyType, budgetRange, scope)
 */
export function validateEnumFields(
	input: CreateProjectInput | UpdateProjectInput,
): void {
	if (input.status && !isValidEnum(input.status, PRO_STATUS)) {
		throw new ValidationError(
			`Invalid status. Must be one of: ${PRO_STATUS.join(", ")}`,
		);
	}
	if (input.propertyType && !isValidEnum(input.propertyType, PROPERTY_TYPES)) {
		throw new ValidationError(
			`Invalid propertyType. Must be one of: ${PROPERTY_TYPES.join(", ")}`,
		);
	}
	if (input.budgetRange && !isValidEnum(input.budgetRange, BUDGET_RANGES)) {
		throw new ValidationError(
			`Invalid budgetRange. Must be one of: ${BUDGET_RANGES.join(", ")}`,
		);
	}
	if (input.scope && !isValidEnumArray(input.scope, PROJECT_SCOPES)) {
		throw new ValidationError(
			`Invalid scope. Must be an array containing only: ${PROJECT_SCOPES.join(", ")}`,
		);
	}
}
 
/**
 * Validate array fields (materialTagIds)
 */
export function validateArrayFields(
	input: CreateProjectInput | UpdateProjectInput,
): void {
	if (input.materialTagIds !== undefined && input.materialTagIds !== null) {
		if (!Array.isArray(input.materialTagIds)) {
			throw new ValidationError("materialTagIds must be an array");
		}
		if (!input.materialTagIds.every((id) => typeof id === "string")) {
			throw new ValidationError("All materialTagIds must be strings");
		}
	}
}
 
/**
 * Validate numeric fields (projectAreaSqft, yearCompleted)
 */
export function validateNumericFields(
	input: CreateProjectInput | UpdateProjectInput,
): void {
	if (
		input.projectAreaSqft !== undefined &&
		input.projectAreaSqft !== null &&
		input.projectAreaSqft < 0
	) {
		throw new ValidationError("projectAreaSqft must be a positive number");
	}
	if (input.yearCompleted !== undefined && input.yearCompleted !== null) {
		const currentYear = new Date().getFullYear();
		if (input.yearCompleted < 1900 || input.yearCompleted > currentYear) {
			throw new ValidationError(
				`yearCompleted must be between 1900 and ${currentYear}`,
			);
		}
	}
}
 
/**
 * Validate all project input fields
 */
export function validateProjectInput(input: CreateProjectInput): void {
	validateTitle(input.title, true);
	validateEnumFields(input);
	validateArrayFields(input);
	validateNumericFields(input);
}
 
/**
 * Validate project update input
 */
export function validateUpdateInput(input: UpdateProjectInput): void {
	if (input.title !== undefined) {
		validateTitle(input.title, false);
	}
	validateEnumFields(input);
	validateArrayFields(input);
	validateNumericFields(input);
}