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
This commit is contained in:
Justin Edmund 2025-11-23 05:14:19 -08:00
parent aab78f3909
commit f6737ee19c
4 changed files with 88 additions and 14 deletions

View file

@ -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) {

View file

@ -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<any>(event.request)
const body = await parseRequestBody<ProjectCreateBody>(event.request)
if (!body) {
return errorResponse('Invalid request body', 400)
}

View file

@ -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
}

View file

@ -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