fix: replace any types in API routes with proper Prisma types
- add ContentNode, GalleryItem, TextNode, ParagraphNode, DocContent types - use Prisma.JsonValue for JSON column content - use Prisma.ProjectUpdateInput and Prisma.PostUpdateInput for update payloads - improve type guards for content filtering - replace any[] with never[] for empty placeholder arrays
This commit is contained in:
parent
056e8927ee
commit
aab78f3909
2 changed files with 73 additions and 30 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
import type { RequestHandler } from './$types'
|
import type { RequestHandler } from './$types'
|
||||||
|
import type { Prisma } from '@prisma/client'
|
||||||
import { prisma } from '$lib/server/database'
|
import { prisma } from '$lib/server/database'
|
||||||
import {
|
import {
|
||||||
jsonResponse,
|
jsonResponse,
|
||||||
|
|
@ -11,6 +12,20 @@ import { removeMediaUsage, extractMediaIds } from '$lib/server/media-usage.js'
|
||||||
import { deleteFile, extractPublicId, isCloudinaryConfigured } from '$lib/server/cloudinary'
|
import { deleteFile, extractPublicId, isCloudinaryConfigured } from '$lib/server/cloudinary'
|
||||||
import { deleteFileLocally } from '$lib/server/local-storage'
|
import { deleteFileLocally } from '$lib/server/local-storage'
|
||||||
|
|
||||||
|
// Type for content node structure
|
||||||
|
interface ContentNode {
|
||||||
|
type: string
|
||||||
|
attrs?: Record<string, unknown>
|
||||||
|
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
|
// DELETE /api/media/bulk-delete - Delete multiple media files and clean up references
|
||||||
export const DELETE: RequestHandler = async (event) => {
|
export const DELETE: RequestHandler = async (event) => {
|
||||||
// Check authentication
|
// Check authentication
|
||||||
|
|
@ -165,7 +180,7 @@ async function cleanupMediaReferences(mediaIds: number[]) {
|
||||||
|
|
||||||
for (const project of projects) {
|
for (const project of projects) {
|
||||||
let needsUpdate = false
|
let needsUpdate = false
|
||||||
const updateData: any = {}
|
const updateData: Prisma.ProjectUpdateInput = {}
|
||||||
|
|
||||||
// Check featured image
|
// Check featured image
|
||||||
if (project.featuredImage && urlsToRemove.includes(project.featuredImage)) {
|
if (project.featuredImage && urlsToRemove.includes(project.featuredImage)) {
|
||||||
|
|
@ -181,9 +196,9 @@ async function cleanupMediaReferences(mediaIds: number[]) {
|
||||||
|
|
||||||
// Check gallery
|
// Check gallery
|
||||||
if (project.gallery && Array.isArray(project.gallery)) {
|
if (project.gallery && Array.isArray(project.gallery)) {
|
||||||
const filteredGallery = project.gallery.filter((item: any) => {
|
const filteredGallery = (project.gallery as GalleryItem[]).filter((item) => {
|
||||||
const itemId = typeof item === 'object' ? item.id : parseInt(item)
|
const itemId = item.id || item.mediaId
|
||||||
return !mediaIds.includes(itemId)
|
return itemId ? !mediaIds.includes(Number(itemId)) : true
|
||||||
})
|
})
|
||||||
if (filteredGallery.length !== project.gallery.length) {
|
if (filteredGallery.length !== project.gallery.length) {
|
||||||
updateData.gallery = filteredGallery.length > 0 ? filteredGallery : null
|
updateData.gallery = filteredGallery.length > 0 ? filteredGallery : null
|
||||||
|
|
@ -221,7 +236,7 @@ async function cleanupMediaReferences(mediaIds: number[]) {
|
||||||
|
|
||||||
for (const post of posts) {
|
for (const post of posts) {
|
||||||
let needsUpdate = false
|
let needsUpdate = false
|
||||||
const updateData: any = {}
|
const updateData: Prisma.PostUpdateInput = {}
|
||||||
|
|
||||||
// Check featured image
|
// Check featured image
|
||||||
if (post.featuredImage && urlsToRemove.includes(post.featuredImage)) {
|
if (post.featuredImage && urlsToRemove.includes(post.featuredImage)) {
|
||||||
|
|
@ -231,9 +246,9 @@ async function cleanupMediaReferences(mediaIds: number[]) {
|
||||||
|
|
||||||
// Check attachments
|
// Check attachments
|
||||||
if (post.attachments && Array.isArray(post.attachments)) {
|
if (post.attachments && Array.isArray(post.attachments)) {
|
||||||
const filteredAttachments = post.attachments.filter((item: any) => {
|
const filteredAttachments = (post.attachments as GalleryItem[]).filter((item) => {
|
||||||
const itemId = typeof item === 'object' ? item.id : parseInt(item)
|
const itemId = item.id || item.mediaId
|
||||||
return !mediaIds.includes(itemId)
|
return itemId ? !mediaIds.includes(Number(itemId)) : true
|
||||||
})
|
})
|
||||||
if (filteredAttachments.length !== post.attachments.length) {
|
if (filteredAttachments.length !== post.attachments.length) {
|
||||||
updateData.attachments = filteredAttachments.length > 0 ? filteredAttachments : null
|
updateData.attachments = filteredAttachments.length > 0 ? filteredAttachments : null
|
||||||
|
|
@ -263,27 +278,30 @@ async function cleanupMediaReferences(mediaIds: number[]) {
|
||||||
/**
|
/**
|
||||||
* Remove media references from rich text content
|
* 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
|
if (!content || typeof content !== 'object') return content
|
||||||
|
|
||||||
function cleanNode(node: any): any {
|
function cleanNode(node: ContentNode | null): ContentNode | null {
|
||||||
if (!node) return node
|
if (!node) return node
|
||||||
|
|
||||||
// Remove image nodes that reference deleted media
|
// Remove image nodes that reference deleted media
|
||||||
if (node.type === 'image' && node.attrs?.src) {
|
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) {
|
if (shouldRemove) {
|
||||||
return null // Mark for removal
|
return null // Mark for removal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean gallery nodes
|
// Clean gallery nodes
|
||||||
if (node.type === 'gallery' && node.attrs?.images) {
|
if (node.type === 'gallery' && node.attrs?.images && Array.isArray(node.attrs.images)) {
|
||||||
const filteredImages = node.attrs.images.filter((image: any) => !mediaIds.includes(image.id))
|
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) {
|
if (filteredImages.length === 0) {
|
||||||
return null // Remove empty gallery
|
return null // Remove empty gallery
|
||||||
} else if (filteredImages.length !== node.attrs.images.length) {
|
} else if (filteredImages.length !== (node.attrs.images as unknown[]).length) {
|
||||||
return {
|
return {
|
||||||
...node,
|
...node,
|
||||||
attrs: {
|
attrs: {
|
||||||
|
|
@ -296,7 +314,7 @@ function cleanContentFromMedia(content: any, mediaIds: number[], urlsToRemove: s
|
||||||
|
|
||||||
// Recursively clean child nodes
|
// Recursively clean child nodes
|
||||||
if (node.content) {
|
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 {
|
return {
|
||||||
...node,
|
...node,
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,32 @@
|
||||||
import type { RequestHandler } from './$types'
|
import type { RequestHandler } from './$types'
|
||||||
|
import type { Prisma } from '@prisma/client'
|
||||||
import { prisma } from '$lib/server/database'
|
import { prisma } from '$lib/server/database'
|
||||||
import { logger } from '$lib/server/logger'
|
import { logger } from '$lib/server/logger'
|
||||||
import { renderEdraContent } from '$lib/utils/content'
|
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<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DocContent {
|
||||||
|
type: 'doc'
|
||||||
|
content?: ContentNode[]
|
||||||
|
}
|
||||||
|
|
||||||
// Helper function to escape XML special characters
|
// Helper function to escape XML special characters
|
||||||
function escapeXML(str: string): string {
|
function escapeXML(str: string): string {
|
||||||
if (!str) return ''
|
if (!str) return ''
|
||||||
|
|
@ -16,7 +40,7 @@ function escapeXML(str: string): string {
|
||||||
|
|
||||||
// Helper function to convert content to HTML for full content
|
// Helper function to convert content to HTML for full content
|
||||||
// Uses the same rendering logic as the website for consistency
|
// Uses the same rendering logic as the website for consistency
|
||||||
function convertContentToHTML(content: any): string {
|
function convertContentToHTML(content: Prisma.JsonValue): string {
|
||||||
if (!content) return ''
|
if (!content) return ''
|
||||||
|
|
||||||
// Handle legacy content format (if it's just a string)
|
// Handle legacy content format (if it's just a string)
|
||||||
|
|
@ -30,7 +54,7 @@ function convertContentToHTML(content: any): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to extract text summary from content
|
// 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 ''
|
if (!content) return ''
|
||||||
|
|
||||||
let text = ''
|
let text = ''
|
||||||
|
|
@ -40,20 +64,21 @@ function extractTextSummary(content: any, maxLength: number = 300): string {
|
||||||
text = content
|
text = content
|
||||||
}
|
}
|
||||||
// Handle TipTap/Edra format
|
// Handle TipTap/Edra format
|
||||||
else if (content.type === 'doc' && content.content && Array.isArray(content.content)) {
|
else if (typeof content === 'object' && content !== null && 'type' in content && content.type === 'doc' && 'content' in content && Array.isArray(content.content)) {
|
||||||
text = content.content
|
const docContent = content as DocContent
|
||||||
.filter((node: any) => node.type === 'paragraph')
|
text = docContent.content
|
||||||
.map((node: any) => {
|
?.filter((node): node is ParagraphNode => node.type === 'paragraph')
|
||||||
|
.map((node) => {
|
||||||
if (node.content && Array.isArray(node.content)) {
|
if (node.content && Array.isArray(node.content)) {
|
||||||
return node.content
|
return node.content
|
||||||
.filter((child: any) => child.type === 'text')
|
.filter((child): child is TextNode => 'type' in child && child.type === 'text')
|
||||||
.map((child: any) => child.text || '')
|
.map((child) => child.text || '')
|
||||||
.join('')
|
.join('')
|
||||||
}
|
}
|
||||||
return ''
|
return ''
|
||||||
})
|
})
|
||||||
.filter((t: string) => t)
|
.filter((t) => t.length > 0)
|
||||||
.join(' ')
|
.join(' ') || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up and truncate
|
// Clean up and truncate
|
||||||
|
|
@ -79,8 +104,8 @@ export const GET: RequestHandler = async (event) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: Re-enable albums once database schema is updated
|
// TODO: Re-enable albums once database schema is updated
|
||||||
const universeAlbums: any[] = []
|
const universeAlbums: never[] = []
|
||||||
const photoAlbums: any[] = []
|
const photoAlbums: never[] = []
|
||||||
|
|
||||||
// Combine all content types
|
// Combine all content types
|
||||||
const items = [
|
const items = [
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue