From f6737ee19cd5e64f3fcb12da9e8a67c869769437 Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Sun, 23 Nov 2025 05:14:19 -0800 Subject: [PATCH] fix: replace remaining any types in API routes - add AlbumPhoto, BlockContent, AppleMusicData types - add ProjectCreateBody interface for request validation - use Prisma.ProjectWhereInput for query filters - use Prisma.JsonValue for JSON fields - add proper type guards for content validation --- src/routes/api/lastfm/+server.ts | 30 +++++++++++++++++++++++++++--- src/routes/api/projects/+server.ts | 27 +++++++++++++++++++++++++-- src/routes/api/universe/+server.ts | 19 +++++++++++++++---- src/routes/rss/universe/+server.ts | 26 +++++++++++++++++++++----- 4 files changed, 88 insertions(+), 14 deletions(-) diff --git a/src/routes/api/lastfm/+server.ts b/src/routes/api/lastfm/+server.ts index bd153fa..dbaa843 100644 --- a/src/routes/api/lastfm/+server.ts +++ b/src/routes/api/lastfm/+server.ts @@ -19,6 +19,30 @@ interface TrackPlayInfo { durationMs?: number } +// Type for Apple Music data +interface AppleMusicTrack { + name: string + previewUrl?: string + durationMs?: number +} + +interface AppleMusicData { + tracks?: AppleMusicTrack[] + artwork?: { + url: string + width: number + height: number + bgColor?: string + textColor1?: string + textColor2?: string + textColor3?: string + textColor4?: string + } + previewUrl?: string + appleMusicUrl?: string + releaseDate?: string +} + let recentTracks: TrackPlayInfo[] = [] export const GET: RequestHandler = async ({ url }) => { @@ -310,7 +334,7 @@ function transformImages(images: LastfmImage[]): AlbumImages { return transformedImages } -function checkNowPlaying(album: Album, appleMusicData: any): Album { +function checkNowPlaying(album: Album, appleMusicData: AppleMusicData | null): Album { // Don't override if already marked as now playing by Last.fm if (album.isNowPlaying) { return album @@ -324,8 +348,8 @@ function checkNowPlaying(album: Album, appleMusicData: any): Album { if (trackInfo.albumName !== album.name) continue // Find the track duration from Apple Music data - const trackData = appleMusicData.tracks?.find( - (t: any) => t.name.toLowerCase() === trackInfo.trackName.toLowerCase() + const trackData = appleMusicData?.tracks?.find( + (t) => t.name.toLowerCase() === trackInfo.trackName.toLowerCase() ) if (trackData?.durationMs) { diff --git a/src/routes/api/projects/+server.ts b/src/routes/api/projects/+server.ts index b2d3f9e..41725a4 100644 --- a/src/routes/api/projects/+server.ts +++ b/src/routes/api/projects/+server.ts @@ -1,4 +1,5 @@ import type { RequestHandler } from './$types' +import type { Prisma } from '@prisma/client' import { prisma } from '$lib/server/database' import { jsonResponse, @@ -16,6 +17,28 @@ import { type MediaUsageReference } from '$lib/server/media-usage.js' +// Type for project creation request body +interface ProjectCreateBody { + title: string + subtitle?: string + description?: string + year: number + client?: string + role?: string + featuredImage?: string + logoUrl?: string + gallery?: Prisma.JsonValue + externalUrl?: string + caseStudyContent?: Prisma.JsonValue + backgroundColor?: string + highlightColor?: string + projectType?: string + displayOrder?: number + status?: string + password?: string | null + slug?: string +} + // GET /api/projects - List all projects export const GET: RequestHandler = async (event) => { try { @@ -33,7 +56,7 @@ export const GET: RequestHandler = async (event) => { event.url.searchParams.get('includePasswordProtected') === 'true' // Build where clause - const where: any = {} + const where: Prisma.ProjectWhereInput = {} if (status) { where.status = status @@ -90,7 +113,7 @@ export const POST: RequestHandler = async (event) => { } try { - const body = await parseRequestBody(event.request) + const body = await parseRequestBody(event.request) if (!body) { return errorResponse('Invalid request body', 400) } diff --git a/src/routes/api/universe/+server.ts b/src/routes/api/universe/+server.ts index 7bec52b..4216b36 100644 --- a/src/routes/api/universe/+server.ts +++ b/src/routes/api/universe/+server.ts @@ -1,20 +1,31 @@ import type { RequestHandler } from './$types' +import type { Prisma } from '@prisma/client' import { prisma } from '$lib/server/database' import { jsonResponse, errorResponse } from '$lib/server/api-utils' import { logger } from '$lib/server/logger' +// Type for photo in album +interface AlbumPhoto { + id: number + url: string + thumbnailUrl: string | null + photoCaption: string | null + width: number | null + height: number | null +} + export interface UniverseItem { id: number type: 'post' | 'album' slug: string title?: string - content?: any + content?: Prisma.JsonValue publishedAt: string createdAt: string // Post-specific fields postType?: string - attachments?: any + attachments?: Prisma.JsonValue featuredImage?: string // Album-specific fields @@ -22,8 +33,8 @@ export interface UniverseItem { location?: string date?: string photosCount?: number - coverPhoto?: any - photos?: any[] + coverPhoto?: AlbumPhoto + photos?: AlbumPhoto[] hasContent?: boolean } diff --git a/src/routes/rss/universe/+server.ts b/src/routes/rss/universe/+server.ts index 1768b48..aa50c3e 100644 --- a/src/routes/rss/universe/+server.ts +++ b/src/routes/rss/universe/+server.ts @@ -1,8 +1,17 @@ import type { RequestHandler } from './$types' +import type { Prisma } from '@prisma/client' import { prisma } from '$lib/server/database' import { logger } from '$lib/server/logger' import { renderEdraContent } from '$lib/utils/content' +// Type for legacy block content format +interface BlockContent { + blocks: Array<{ + type: string + content?: string + }> +} + // Helper function to escape XML special characters function escapeXML(str: string): string { if (!str) return '' @@ -16,7 +25,7 @@ function escapeXML(str: string): string { // Helper function to convert content to HTML for full content // Uses the same rendering logic as the website for consistency -function convertContentToHTML(content: any): string { +function convertContentToHTML(content: Prisma.JsonValue): string { if (!content) return '' // Use the existing renderEdraContent function which properly handles TipTap marks @@ -25,12 +34,19 @@ function convertContentToHTML(content: any): string { } // Helper function to extract text summary from content -function extractTextSummary(content: any, maxLength: number = 300): string { - if (!content || !content.blocks) return '' +function extractTextSummary(content: Prisma.JsonValue, maxLength: number = 300): string { + if (!content) return '' + + // Type guard for block content + const isBlockContent = (val: unknown): val is BlockContent => { + return typeof val === 'object' && val !== null && 'blocks' in val && Array.isArray((val as BlockContent).blocks) + } + + if (!isBlockContent(content)) return '' const text = content.blocks - .filter((block: any) => block.type === 'paragraph' && block.content) - .map((block: any) => block.content) + .filter((block) => block.type === 'paragraph' && block.content) + .map((block) => block.content || '') .join(' ') return text.length > maxLength ? text.substring(0, maxLength) + '...' : text