ATlast — you'll never need to find your favorites on another platform again. Find your favs in the ATmosphere.
atproto
16
fork

Configure Feed

Select the types of activity you want to include in your feed.

replace duplicate validation with Zod schemas

Optimizations #6 & #7:
- #6: verified early exit optimization already implemented in FollowService
- #7: created validation.utils.ts with Zod schemas for array validation
- replaced 3 duplicate validation blocks with reusable Zod schemas
- updated batch-follow-users, check-follow-status, batch-search-actors

byarielm.fyi 093b47d6 0b453335

verified
+93 -35
+6 -11
netlify/functions/batch-follow-users.ts
··· 2 2 import { SessionService } from "./services/SessionService"; 3 3 import { FollowService } from "./services/FollowService"; 4 4 import { MatchRepository } from "./repositories"; 5 - import { successResponse } from "./utils"; 5 + import { successResponse, validateArrayInput, ValidationSchemas } from "./utils"; 6 6 import { withAuthErrorHandling } from "./core/middleware"; 7 - import { ValidationError } from "./core/errors"; 8 7 9 8 const batchFollowHandler: AuthenticatedHandler = async (context) => { 10 9 const body = JSON.parse(context.event.body || "{}"); 11 - const dids: string[] = body.dids || []; 10 + const dids = validateArrayInput<string>( 11 + context.event.body, 12 + "dids", 13 + ValidationSchemas.didsArray, 14 + ); 12 15 const followLexicon: string = body.followLexicon || "app.bsky.graph.follow"; 13 - 14 - if (!Array.isArray(dids) || dids.length === 0) { 15 - throw new ValidationError("dids array is required and must not be empty"); 16 - } 17 - 18 - if (dids.length > 100) { 19 - throw new ValidationError("Maximum 100 DIDs per batch"); 20 - } 21 16 22 17 const { agent } = await SessionService.getAgentForSession( 23 18 context.sessionId,
+6 -13
netlify/functions/batch-search-actors.ts
··· 1 1 import { AuthenticatedHandler } from "./core/types"; 2 2 import { SessionService } from "./services/SessionService"; 3 - import { successResponse } from "./utils"; 3 + import { successResponse, validateArrayInput, ValidationSchemas } from "./utils"; 4 4 import { withAuthErrorHandling } from "./core/middleware"; 5 - import { ValidationError } from "./core/errors"; 6 5 import { normalize } from "./utils/string.utils"; 7 6 import { FollowService } from "./services/FollowService"; 8 7 9 8 const batchSearchHandler: AuthenticatedHandler = async (context) => { 10 9 const body = JSON.parse(context.event.body || "{}"); 11 - const usernames: string[] = body.usernames || []; 12 - 13 - if (!Array.isArray(usernames) || usernames.length === 0) { 14 - throw new ValidationError( 15 - "usernames array is required and must not be empty", 16 - ); 17 - } 18 - 19 - if (usernames.length > 50) { 20 - throw new ValidationError("Maximum 50 usernames per batch"); 21 - } 10 + const usernames = validateArrayInput<string>( 11 + context.event.body, 12 + "usernames", 13 + ValidationSchemas.usernamesArray, 14 + ); 22 15 23 16 const { agent } = await SessionService.getAgentForSession( 24 17 context.sessionId,
+6 -11
netlify/functions/check-follow-status.ts
··· 1 1 import { AuthenticatedHandler } from "./core/types"; 2 2 import { SessionService } from "./services/SessionService"; 3 3 import { FollowService } from "./services/FollowService"; 4 - import { successResponse } from "./utils"; 4 + import { successResponse, validateArrayInput, ValidationSchemas } from "./utils"; 5 5 import { withAuthErrorHandling } from "./core/middleware"; 6 - import { ValidationError } from "./core/errors"; 7 6 8 7 const checkFollowStatusHandler: AuthenticatedHandler = async (context) => { 9 8 const body = JSON.parse(context.event.body || "{}"); 10 - const dids: string[] = body.dids || []; 9 + const dids = validateArrayInput<string>( 10 + context.event.body, 11 + "dids", 12 + ValidationSchemas.didsArray, 13 + ); 11 14 const followLexicon: string = body.followLexicon || "app.bsky.graph.follow"; 12 - 13 - if (!Array.isArray(dids) || dids.length === 0) { 14 - throw new ValidationError("dids array is required and must not be empty"); 15 - } 16 - 17 - if (dids.length > 100) { 18 - throw new ValidationError("Maximum 100 DIDs per batch"); 19 - } 20 15 21 16 const { agent } = await SessionService.getAgentForSession( 22 17 context.sessionId,
+1
netlify/functions/utils/index.ts
··· 1 1 export * from "./response.utils"; 2 2 export * from "./string.utils"; 3 3 export * from "./encryption.utils"; 4 + export * from "./validation.utils";
+74
netlify/functions/utils/validation.utils.ts
··· 1 + import { z } from "zod"; 2 + import { ValidationError } from "../core/errors"; 3 + 4 + /** 5 + * Validation utility schemas using Zod 6 + * Provides type-safe validation with clear error messages 7 + */ 8 + 9 + /** 10 + * Generic array validation schema factory 11 + * @param itemSchema - Zod schema for array items 12 + * @param maxLength - Maximum array length 13 + * @param fieldName - Name of field for error messages 14 + */ 15 + export function createArraySchema<T extends z.ZodTypeAny>( 16 + itemSchema: T, 17 + maxLength: number, 18 + fieldName: string = "items", 19 + ) { 20 + return z 21 + .array(itemSchema) 22 + .min(1, `${fieldName} array is required and must not be empty`) 23 + .max(maxLength, `Maximum ${maxLength} ${fieldName} per batch`); 24 + } 25 + 26 + /** 27 + * Common validation schemas 28 + */ 29 + export const ValidationSchemas = { 30 + // DIDs array (max 100) 31 + didsArray: createArraySchema(z.string(), 100, "DIDs"), 32 + 33 + // Usernames array (max 50) 34 + usernamesArray: createArraySchema(z.string(), 50, "usernames"), 35 + 36 + // Generic string array with custom max 37 + stringArray: (maxLength: number, fieldName: string = "items") => 38 + createArraySchema(z.string(), maxLength, fieldName), 39 + }; 40 + 41 + /** 42 + * Validates input against a Zod schema and throws ValidationError on failure 43 + * @param schema - Zod schema to validate against 44 + * @param data - Data to validate 45 + * @returns Parsed and validated data 46 + * @throws ValidationError if validation fails 47 + */ 48 + export function validateInput<T>(schema: z.ZodSchema<T>, data: unknown): T { 49 + const result = schema.safeParse(data); 50 + 51 + if (!result.success) { 52 + // Extract first error message for cleaner API responses 53 + const firstError = result.error.issues[0]; 54 + const message = firstError.message; 55 + throw new ValidationError(message); 56 + } 57 + 58 + return result.data; 59 + } 60 + 61 + /** 62 + * Parses request body and validates array input 63 + * Common pattern: JSON.parse(body) -> extract array -> validate 64 + */ 65 + export function validateArrayInput<T>( 66 + body: string | null, 67 + fieldName: string, 68 + schema: z.ZodArray<any>, 69 + ): T[] { 70 + const parsed = JSON.parse(body || "{}"); 71 + const data = parsed[fieldName]; 72 + 73 + return validateInput(schema, data); 74 + }