diff --git a/src/routes/api/media/bulk-delete/+server.ts b/src/routes/api/media/bulk-delete/+server.ts index 6ce3c01..52f37a5 100644 --- a/src/routes/api/media/bulk-delete/+server.ts +++ b/src/routes/api/media/bulk-delete/+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, @@ -11,6 +12,20 @@ import { removeMediaUsage, extractMediaIds } from '$lib/server/media-usage.js' import { deleteFile, extractPublicId, isCloudinaryConfigured } from '$lib/server/cloudinary' import { deleteFileLocally } from '$lib/server/local-storage' +// Type for content node structure +interface ContentNode { + type: string + attrs?: Record + content?: ContentNode[] +} + +// Type for gallery item in JSON fields +interface GalleryItem { + id?: number + mediaId?: number + [key: string]: unknown +} + // DELETE /api/media/bulk-delete - Delete multiple media files and clean up references export const DELETE: RequestHandler = async (event) => { // Check authentication @@ -165,7 +180,7 @@ async function cleanupMediaReferences(mediaIds: number[]) { for (const project of projects) { let needsUpdate = false - const updateData: any = {} + const updateData: Prisma.ProjectUpdateInput = {} // Check featured image if (project.featuredImage && urlsToRemove.includes(project.featuredImage)) { @@ -181,9 +196,9 @@ async function cleanupMediaReferences(mediaIds: number[]) { // Check gallery if (project.gallery && Array.isArray(project.gallery)) { - const filteredGallery = project.gallery.filter((item: any) => { - const itemId = typeof item === 'object' ? item.id : parseInt(item) - return !mediaIds.includes(itemId) + const filteredGallery = (project.gallery as GalleryItem[]).filter((item) => { + const itemId = item.id || item.mediaId + return itemId ? !mediaIds.includes(Number(itemId)) : true }) if (filteredGallery.length !== project.gallery.length) { updateData.gallery = filteredGallery.length > 0 ? filteredGallery : null @@ -221,7 +236,7 @@ async function cleanupMediaReferences(mediaIds: number[]) { for (const post of posts) { let needsUpdate = false - const updateData: any = {} + const updateData: Prisma.PostUpdateInput = {} // Check featured image if (post.featuredImage && urlsToRemove.includes(post.featuredImage)) { @@ -231,9 +246,9 @@ async function cleanupMediaReferences(mediaIds: number[]) { // Check attachments if (post.attachments && Array.isArray(post.attachments)) { - const filteredAttachments = post.attachments.filter((item: any) => { - const itemId = typeof item === 'object' ? item.id : parseInt(item) - return !mediaIds.includes(itemId) + const filteredAttachments = (post.attachments as GalleryItem[]).filter((item) => { + const itemId = item.id || item.mediaId + return itemId ? !mediaIds.includes(Number(itemId)) : true }) if (filteredAttachments.length !== post.attachments.length) { updateData.attachments = filteredAttachments.length > 0 ? filteredAttachments : null @@ -263,27 +278,30 @@ async function cleanupMediaReferences(mediaIds: number[]) { /** * Remove media references from rich text content */ -function cleanContentFromMedia(content: any, mediaIds: number[], urlsToRemove: string[]): any { +function cleanContentFromMedia(content: Prisma.JsonValue, mediaIds: number[], urlsToRemove: string[]): Prisma.JsonValue { if (!content || typeof content !== 'object') return content - function cleanNode(node: any): any { + function cleanNode(node: ContentNode | null): ContentNode | null { if (!node) return node // Remove image nodes that reference deleted media if (node.type === 'image' && node.attrs?.src) { - const shouldRemove = urlsToRemove.some((url) => node.attrs.src.includes(url)) + const shouldRemove = urlsToRemove.some((url) => String(node.attrs?.src).includes(url)) if (shouldRemove) { return null // Mark for removal } } // Clean gallery nodes - if (node.type === 'gallery' && node.attrs?.images) { - const filteredImages = node.attrs.images.filter((image: any) => !mediaIds.includes(image.id)) + if (node.type === 'gallery' && node.attrs?.images && Array.isArray(node.attrs.images)) { + const filteredImages = (node.attrs.images as GalleryItem[]).filter((image) => { + const imageId = image.id || image.mediaId + return imageId ? !mediaIds.includes(Number(imageId)) : true + }) if (filteredImages.length === 0) { return null // Remove empty gallery - } else if (filteredImages.length !== node.attrs.images.length) { + } else if (filteredImages.length !== (node.attrs.images as unknown[]).length) { return { ...node, attrs: { @@ -296,7 +314,7 @@ function cleanContentFromMedia(content: any, mediaIds: number[], urlsToRemove: s // Recursively clean child nodes if (node.content) { - const cleanedContent = node.content.map(cleanNode).filter((child: any) => child !== null) + const cleanedContent = node.content.map(cleanNode).filter((child): child is ContentNode => child !== null) return { ...node, diff --git a/src/routes/rss/+server.ts b/src/routes/rss/+server.ts index 3f4fdaa..9f06e58 100644 --- a/src/routes/rss/+server.ts +++ b/src/routes/rss/+server.ts @@ -1,8 +1,32 @@ 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' +// Content node types for TipTap/Edra content +interface TextNode { + type: 'text' + text: string + marks?: unknown[] +} + +interface ParagraphNode { + type: 'paragraph' + content?: (TextNode | ContentNode)[] +} + +interface ContentNode { + type: string + content?: ContentNode[] + attrs?: Record +} + +interface DocContent { + type: 'doc' + content?: ContentNode[] +} + // Helper function to escape XML special characters function escapeXML(str: string): string { if (!str) return '' @@ -16,7 +40,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 '' // Handle legacy content format (if it's just a string) @@ -30,32 +54,33 @@ function convertContentToHTML(content: any): string { } // Helper function to extract text summary from content -function extractTextSummary(content: any, maxLength: number = 300): string { +function extractTextSummary(content: Prisma.JsonValue, maxLength: number = 300): string { if (!content) return '' - + let text = '' - + // Handle string content if (typeof content === 'string') { text = content } // Handle TipTap/Edra format - else if (content.type === 'doc' && content.content && Array.isArray(content.content)) { - text = content.content - .filter((node: any) => node.type === 'paragraph') - .map((node: any) => { + else if (typeof content === 'object' && content !== null && 'type' in content && content.type === 'doc' && 'content' in content && Array.isArray(content.content)) { + const docContent = content as DocContent + text = docContent.content + ?.filter((node): node is ParagraphNode => node.type === 'paragraph') + .map((node) => { if (node.content && Array.isArray(node.content)) { return node.content - .filter((child: any) => child.type === 'text') - .map((child: any) => child.text || '') + .filter((child): child is TextNode => 'type' in child && child.type === 'text') + .map((child) => child.text || '') .join('') } return '' }) - .filter((t: string) => t) - .join(' ') + .filter((t) => t.length > 0) + .join(' ') || '' } - + // Clean up and truncate text = text.replace(/\s+/g, ' ').trim() return text.length > maxLength ? text.substring(0, maxLength) + '...' : text @@ -79,8 +104,8 @@ export const GET: RequestHandler = async (event) => { }) // TODO: Re-enable albums once database schema is updated - const universeAlbums: any[] = [] - const photoAlbums: any[] = [] + const universeAlbums: never[] = [] + const photoAlbums: never[] = [] // Combine all content types const items = [