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.

test(api): add Zod response schemas for API contract validation

byarielm.fyi 6004d388 0b8d1dcb

verified
+269
+80
packages/api/src/types/schemas/auth.schema.ts
··· 1 + /** 2 + * Auth Response Schemas 3 + * Validates auth endpoint responses (session, logout, oauth-start, client-metadata, jwks) 4 + */ 5 + 6 + import { z } from 'zod'; 7 + import { BaseSuccessSchema, ErrorResponseSchema } from './common.schema'; 8 + 9 + /** Successful session response */ 10 + export const SessionSuccessSchema = BaseSuccessSchema.extend({ 11 + data: z.object({ 12 + did: z.string().startsWith('did:'), 13 + sessionId: z.string(), 14 + }), 15 + }); 16 + 17 + /** Session error response (401) */ 18 + export const SessionErrorSchema = ErrorResponseSchema; 19 + 20 + /** Successful logout response */ 21 + export const LogoutSuccessSchema = BaseSuccessSchema; 22 + 23 + /** Successful oauth-start response */ 24 + export const OAuthStartSuccessSchema = BaseSuccessSchema.extend({ 25 + data: z.object({ 26 + url: z.string(), 27 + }), 28 + }); 29 + 30 + /** JWK entry in JWKS response */ 31 + export const JwkSchema = z.object({ 32 + kty: z.string(), 33 + x: z.string(), 34 + y: z.string(), 35 + crv: z.string(), 36 + kid: z.string(), 37 + use: z.string(), 38 + alg: z.string(), 39 + }); 40 + 41 + /** JWKS response */ 42 + export const JwksResponseSchema = z.object({ 43 + keys: z.array(JwkSchema).min(1), 44 + }); 45 + 46 + /** Loopback (dev) client metadata */ 47 + export const ClientMetadataLoopbackSchema = z.object({ 48 + client_id: z.string(), 49 + client_name: z.string(), 50 + client_uri: z.string(), 51 + redirect_uris: z.array(z.string()).min(1), 52 + scope: z.string(), 53 + grant_types: z.array(z.string()), 54 + response_types: z.array(z.string()), 55 + application_type: z.literal('web'), 56 + token_endpoint_auth_method: z.literal('none'), 57 + dpop_bound_access_tokens: z.literal(true), 58 + }); 59 + 60 + /** Production client metadata (includes jwks_uri and logo_uri) */ 61 + export const ClientMetadataProductionSchema = z.object({ 62 + client_id: z.string(), 63 + client_name: z.string(), 64 + client_uri: z.string(), 65 + redirect_uris: z.array(z.string()).min(1), 66 + logo_uri: z.string(), 67 + scope: z.string(), 68 + grant_types: z.array(z.string()), 69 + response_types: z.array(z.string()), 70 + application_type: z.literal('web'), 71 + token_endpoint_auth_method: z.literal('private_key_jwt'), 72 + token_endpoint_auth_signing_alg: z.literal('ES256'), 73 + dpop_bound_access_tokens: z.literal(true), 74 + jwks_uri: z.string(), 75 + }); 76 + 77 + /** Client metadata error (missing host) */ 78 + export const ClientMetadataErrorSchema = z.object({ 79 + error: z.string(), 80 + });
+17
packages/api/src/types/schemas/common.schema.ts
··· 1 + /** 2 + * Common Response Schemas 3 + * Shared schema patterns used across all API endpoints 4 + */ 5 + 6 + import { z } from 'zod'; 7 + 8 + /** Standard error response (400, 401, 500) */ 9 + export const ErrorResponseSchema = z.object({ 10 + success: z.literal(false), 11 + error: z.string(), 12 + }); 13 + 14 + /** Base success wrapper — all success responses include `success: true` */ 15 + export const BaseSuccessSchema = z.object({ 16 + success: z.literal(true), 17 + });
+19
packages/api/src/types/schemas/extension.schema.ts
··· 1 + /** 2 + * Extension Response Schemas 3 + * Validates POST /api/extension/import responses 4 + */ 5 + 6 + import { z } from 'zod'; 7 + import { BaseSuccessSchema } from './common.schema'; 8 + 9 + /** Extension import data payload */ 10 + export const ExtensionImportDataSchema = z.object({ 11 + importId: z.string(), 12 + usernameCount: z.number().int().min(1), 13 + redirectUrl: z.string(), 14 + }); 15 + 16 + /** Successful extension import response */ 17 + export const ExtensionImportSuccessSchema = BaseSuccessSchema.extend({ 18 + data: ExtensionImportDataSchema, 19 + });
+36
packages/api/src/types/schemas/follow.schema.ts
··· 1 + /** 2 + * Follow Response Schemas 3 + * Validates POST /api/follow/batch-follow-users and POST /api/follow/check-status responses 4 + */ 5 + 6 + import { z } from 'zod'; 7 + import { BaseSuccessSchema, ErrorResponseSchema } from './common.schema'; 8 + 9 + /** Individual follow result entry */ 10 + export const FollowResultEntrySchema = z.object({ 11 + did: z.string(), 12 + success: z.boolean(), 13 + alreadyFollowing: z.boolean(), 14 + error: z.string().nullable(), 15 + }); 16 + 17 + /** Successful batch follow response */ 18 + export const BatchFollowSuccessSchema = BaseSuccessSchema.extend({ 19 + data: z.object({ 20 + total: z.number().int().min(0), 21 + succeeded: z.number().int().min(0), 22 + failed: z.number().int().min(0), 23 + alreadyFollowing: z.number().int().min(0), 24 + results: z.array(FollowResultEntrySchema), 25 + }), 26 + }); 27 + 28 + /** Successful check-status response */ 29 + export const CheckStatusSuccessSchema = BaseSuccessSchema.extend({ 30 + data: z.object({ 31 + followStatus: z.record(z.string(), z.boolean()), 32 + }), 33 + }); 34 + 35 + /** Validation error response (400) for both endpoints */ 36 + export const FollowErrorSchema = ErrorResponseSchema;
+6
packages/api/src/types/schemas/index.ts
··· 1 + export * from './common.schema'; 2 + export * from './search.schema'; 3 + export * from './follow.schema'; 4 + export * from './results.schema'; 5 + export * from './auth.schema'; 6 + export * from './extension.schema';
+74
packages/api/src/types/schemas/results.schema.ts
··· 1 + /** 2 + * Results Response Schemas 3 + * Validates POST /api/results/save, GET /api/results/uploads, 4 + * and GET /api/results/upload-details responses 5 + */ 6 + 7 + import { z } from 'zod'; 8 + import { BaseSuccessSchema } from './common.schema'; 9 + 10 + /** Successful save response */ 11 + export const SaveResultsSuccessSchema = BaseSuccessSchema.extend({ 12 + uploadId: z.string(), 13 + totalUsers: z.number().int().min(0), 14 + matchedUsers: z.number().int().min(0), 15 + unmatchedUsers: z.number().int().min(0), 16 + message: z.string().optional(), 17 + }); 18 + 19 + /** Single upload entry in uploads list */ 20 + export const UploadEntrySchema = z.object({ 21 + uploadId: z.string(), 22 + sourcePlatform: z.string(), 23 + createdAt: z.string(), 24 + totalUsers: z.number().int().min(0), 25 + matchedUsers: z.number().int().min(0), 26 + unmatchedUsers: z.number().int().min(0), 27 + }); 28 + 29 + /** Successful uploads list response */ 30 + export const UploadsListSuccessSchema = BaseSuccessSchema.extend({ 31 + data: z.object({ 32 + uploads: z.array(UploadEntrySchema), 33 + }), 34 + }); 35 + 36 + /** AT Protocol match in upload details */ 37 + export const UploadDetailMatchSchema = z.object({ 38 + did: z.string(), 39 + handle: z.string(), 40 + displayName: z.string().nullable(), 41 + matchScore: z.number().min(0), 42 + postCount: z.number().nullable(), 43 + followerCount: z.number().nullable(), 44 + foundAt: z.string().nullable(), 45 + dismissed: z.boolean(), 46 + followStatus: z.record(z.string(), z.unknown()), 47 + }); 48 + 49 + /** Grouped result entry in upload details */ 50 + export const UploadDetailResultSchema = z.object({ 51 + sourceUser: z.object({ 52 + username: z.string(), 53 + date: z.string(), 54 + }), 55 + atprotoMatches: z.array(UploadDetailMatchSchema), 56 + }); 57 + 58 + /** Pagination metadata */ 59 + export const PaginationSchema = z.object({ 60 + page: z.number().int().min(1), 61 + pageSize: z.number().int().min(1), 62 + totalPages: z.number().int().min(0), 63 + totalUsers: z.number().int().min(0), 64 + hasNextPage: z.boolean(), 65 + hasPrevPage: z.boolean(), 66 + }); 67 + 68 + /** Successful upload details response */ 69 + export const UploadDetailsSuccessSchema = BaseSuccessSchema.extend({ 70 + data: z.object({ 71 + results: z.array(UploadDetailResultSchema), 72 + pagination: PaginationSchema, 73 + }), 74 + });
+37
packages/api/src/types/schemas/search.schema.ts
··· 1 + /** 2 + * Search Response Schemas 3 + * Validates POST /api/search/batch-search-actors responses 4 + */ 5 + 6 + import { z } from 'zod'; 7 + import { BaseSuccessSchema, ErrorResponseSchema } from './common.schema'; 8 + 9 + /** Individual actor returned in search results */ 10 + export const EnrichedActorSchema = z.object({ 11 + did: z.string().startsWith('did:'), 12 + handle: z.string(), 13 + displayName: z.string().optional(), 14 + avatar: z.string().optional(), 15 + description: z.string().optional(), 16 + matchScore: z.number().min(0).max(100), 17 + postCount: z.number().min(0), 18 + followerCount: z.number().min(0), 19 + followStatus: z.record(z.string(), z.boolean()), 20 + }); 21 + 22 + /** Per-username result entry */ 23 + export const SearchResultEntrySchema = z.object({ 24 + username: z.string(), 25 + actors: z.array(EnrichedActorSchema), 26 + error: z.string().nullable(), 27 + }); 28 + 29 + /** Successful batch search response */ 30 + export const BatchSearchSuccessSchema = BaseSuccessSchema.extend({ 31 + data: z.object({ 32 + results: z.array(SearchResultEntrySchema), 33 + }), 34 + }); 35 + 36 + /** Validation error response (400) */ 37 + export const BatchSearchErrorSchema = ErrorResponseSchema;