From 339ac0125e725447e1b3c12e7f849336e437b008 Mon Sep 17 00:00:00 2001 From: "Carlos (Almeira)" Date: Sat, 28 Mar 2026 09:57:42 -0500 Subject: [PATCH] refactor: improve project integrity with shared types and strict Appwrite document casting - Create shared Settings interface in @/types/settings. - Refactor NewsTicker, AdWrappers, and page components to use shared types. - Remove risky 'as any' casting from Appwrite document resolution. - Update Post interface to include resolved_media for consistent data handling. - Verify Zod error handling consistency across the src directory. --- src/app/clasificados/page.tsx | 17 ++++++++++----- src/app/eventos/page.tsx | 17 ++++++++++----- src/app/page.tsx | 41 ++++++++++++++++------------------- src/components/AdWrappers.tsx | 6 +---- src/components/NewsTicker.tsx | 3 ++- src/types/publications.ts | 1 + src/types/settings.ts | 10 +++++++++ 7 files changed, 55 insertions(+), 40 deletions(-) create mode 100644 src/types/settings.ts diff --git a/src/app/clasificados/page.tsx b/src/app/clasificados/page.tsx index ef1643a..2abf152 100644 --- a/src/app/clasificados/page.tsx +++ b/src/app/clasificados/page.tsx @@ -4,6 +4,8 @@ import { Query } from 'node-appwrite'; import { NewsCard } from '@/components/NewsCard'; import { GlobalSidebar, hasSidebarContent } from '@/components/AdWrappers'; import { Sparkles } from 'lucide-react'; +import { Settings } from '@/types/settings'; +import { Post, MediaItem } from '@/types/publications'; async function fetchClasificadosData() { try { @@ -11,7 +13,7 @@ async function fetchClasificadosData() { // 1. Settings const settingsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'settings', []); - const settings: any = settingsRes.documents.length > 0 ? settingsRes.documents[0] : {}; + const settings = (settingsRes.documents.length > 0 ? settingsRes.documents[0] : {}) as Settings; const postsCount = settings.feed_news_count || 12; const cardSettings = { @@ -26,7 +28,7 @@ async function fetchClasificadosData() { Query.equal('codigo', 'CLASIFICADO') ]); - let posts: any[] = []; + let posts: Post[] = []; if (typeRes.documents.length > 0) { const typeId = typeRes.documents[0].$id; const postsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'posts', [ @@ -35,16 +37,19 @@ async function fetchClasificadosData() { Query.orderDesc('fecha_publicacion'), Query.limit(postsCount) ]); - posts = postsRes.documents; + posts = postsRes.documents as unknown as Post[]; // 3. Resolve Media - const mediaIds = posts.map(p => p.imagen_destacada_id).filter(id => id && typeof id === 'string'); + const mediaIds = posts.map(p => p.imagen_destacada_id).filter(id => id && typeof id === 'string') as string[]; if (mediaIds.length > 0) { const mediaRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'media', [ Query.equal('$id', mediaIds) ]); - const mediaMap = new Map(mediaRes.documents.map(m => [m.$id, m])); - posts = posts.map(p => ({ ...p, resolved_media: mediaMap.get(p.imagen_destacada_id) })); + const mediaMap = new Map(mediaRes.documents.map(m => [m.$id, m as unknown as MediaItem])); + posts = posts.map(p => ({ + ...p, + resolved_media: mediaMap.get(p.imagen_destacada_id as string) + })); } } diff --git a/src/app/eventos/page.tsx b/src/app/eventos/page.tsx index a90b74d..a672242 100644 --- a/src/app/eventos/page.tsx +++ b/src/app/eventos/page.tsx @@ -4,6 +4,8 @@ import { Query } from 'node-appwrite'; import { NewsCard } from '@/components/NewsCard'; import { GlobalSidebar, hasSidebarContent } from '@/components/AdWrappers'; import { Calendar } from 'lucide-react'; +import { Settings } from '@/types/settings'; +import { Post, MediaItem } from '@/types/publications'; async function fetchEventosData() { try { @@ -11,7 +13,7 @@ async function fetchEventosData() { // 1. Settings const settingsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'settings', []); - const settings: any = settingsRes.documents.length > 0 ? settingsRes.documents[0] : {}; + const settings = (settingsRes.documents.length > 0 ? settingsRes.documents[0] : {}) as Settings; const postsCount = settings.feed_news_count || 12; const cardSettings = { @@ -26,7 +28,7 @@ async function fetchEventosData() { Query.equal('codigo', 'EVENTO') ]); - let posts: any[] = []; + let posts: Post[] = []; if (typeRes.documents.length > 0) { const typeId = typeRes.documents[0].$id; const postsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'posts', [ @@ -35,16 +37,19 @@ async function fetchEventosData() { Query.orderDesc('fecha_publicacion'), Query.limit(postsCount) ]); - posts = postsRes.documents; + posts = postsRes.documents as unknown as Post[]; // 3. Resolve Media - const mediaIds = posts.map(p => p.imagen_destacada_id).filter(id => id && typeof id === 'string'); + const mediaIds = posts.map(p => p.imagen_destacada_id).filter(id => id && typeof id === 'string') as string[]; if (mediaIds.length > 0) { const mediaRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'media', [ Query.equal('$id', mediaIds) ]); - const mediaMap = new Map(mediaRes.documents.map(m => [m.$id, m])); - posts = posts.map(p => ({ ...p, resolved_media: mediaMap.get(p.imagen_destacada_id) })); + const mediaMap = new Map(mediaRes.documents.map(m => [m.$id, m as unknown as MediaItem])); + posts = posts.map(p => ({ + ...p, + resolved_media: mediaMap.get(p.imagen_destacada_id as string) + })); } } diff --git a/src/app/page.tsx b/src/app/page.tsx index fc458f2..44cba2b 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -6,33 +6,31 @@ import { GlobalSidebar, hasSidebarContent } from '@/components/AdWrappers'; import { RadioTower, Sparkles, ChevronDown } from 'lucide-react'; import { decodeHtmlEntities } from '@/lib/utils'; +import { Settings } from '@/types/settings'; +import { Post, MediaItem } from '@/types/publications'; + async function fetchHomeData() { try { const client = await createAdminClient(); - // 1. Settings with fallback - let settings: any = {}; - try { - const settingsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'settings', [Query.limit(1)]); - settings = settingsRes.documents.length > 0 ? settingsRes.documents[0] : {}; - } catch (e) { - console.error("Settings fetch error", e); - } + // 1. Settings + const settingsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'settings', []); + const settings = (settingsRes.documents.length > 0 ? settingsRes.documents[0] : {}) as Settings; - const postsCount = settings?.feed_news_count || 12; + const postsCount = settings.feed_news_count || 12; const cardSettings = { - showCategory: settings?.news_card_show_category ?? false, - showFooterDate: settings?.show_news_date ?? true, - showFooterButton: settings?.news_card_show_footer_button ?? true, - footerText: settings?.news_card_footer_text || "" + showCategory: settings.news_card_show_category ?? false, + showFooterDate: settings.show_news_date ?? false, + showFooterButton: settings.news_card_show_footer_button ?? true, + footerText: settings.news_card_footer_text || "" }; - // 2. Get NOTICIA type ID + // 2. Main News (NOTICIA) const typeRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'publication_types', [ Query.equal('codigo', 'NOTICIA') ]); - let posts: any[] = []; + let posts: Post[] = []; if (typeRes.documents.length > 0) { const typeId = typeRes.documents[0].$id; const postsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'posts', [ @@ -41,19 +39,18 @@ async function fetchHomeData() { Query.orderDesc('fecha_publicacion'), Query.limit(postsCount) ]); - posts = postsRes.documents; + posts = postsRes.documents as unknown as Post[]; - // 3. Resolve Media documents for images + // 3. Resolve Media const mediaIds = posts.map(p => p.imagen_destacada_id).filter(id => id && typeof id === 'string'); if (mediaIds.length > 0) { const mediaRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'media', [ Query.equal('$id', mediaIds) ]); - const mediaMap = new Map(mediaRes.documents.map(m => [m.$id, m])); - - posts = posts.map(p => ({ - ...p, - resolved_media: mediaMap.get(p.imagen_destacada_id) + const mediaMap = new Map(mediaRes.documents.map(m => [m.$id, m as unknown as MediaItem])); + posts = posts.map(p => ({ + ...p, + resolved_media: mediaMap.get(p.imagen_destacada_id as string) })); } } diff --git a/src/components/AdWrappers.tsx b/src/components/AdWrappers.tsx index 49c3dbe..a79e101 100644 --- a/src/components/AdWrappers.tsx +++ b/src/components/AdWrappers.tsx @@ -3,11 +3,7 @@ import { createAdminClient } from '@/lib/appwrite.server'; import TopBanner from './TopBanner'; import ClientSidebars from './ClientSidebars'; -interface Settings { - rotation_seconds: number; - ticker_count: number; - [key: string]: any; -} +import { Settings } from '@/types/settings'; async function fetchSettings(): Promise { try { diff --git a/src/components/NewsTicker.tsx b/src/components/NewsTicker.tsx index 962fd64..368d736 100644 --- a/src/components/NewsTicker.tsx +++ b/src/components/NewsTicker.tsx @@ -3,6 +3,7 @@ import { createSessionClient } from '@/lib/appwrite.server'; import { Query } from 'node-appwrite'; import Link from 'next/link'; import { MessageCircle } from 'lucide-react'; +import { Settings } from '@/types/settings'; export default async function NewsTicker() { let tickerItems: any[] = []; @@ -18,7 +19,7 @@ export default async function NewsTicker() { // 1. Fetch Settings const settingsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'settings', []); - const settings: any = settingsRes.documents.length > 0 ? settingsRes.documents[0] : {}; + const settings = (settingsRes.documents.length > 0 ? settingsRes.documents[0] : {}) as Settings; const tickerCount = settings.ticker_count || 10; // 2. Fetch News (NOTICIA) diff --git a/src/types/publications.ts b/src/types/publications.ts index aa14bf7..f695a62 100644 --- a/src/types/publications.ts +++ b/src/types/publications.ts @@ -80,6 +80,7 @@ export interface Post { tipo_publicacion_id: string | PublicationType; imagen_destacada_id?: string | MediaItem; cliente_id?: string | Client; + resolved_media?: MediaItem; } // ───────────────────────────────────────────── diff --git a/src/types/settings.ts b/src/types/settings.ts new file mode 100644 index 0000000..804a43a --- /dev/null +++ b/src/types/settings.ts @@ -0,0 +1,10 @@ +/** + * @file settings.ts + * @description Shared interfaces for Appwrite settings documents. + */ + +export interface Settings { + rotation_seconds: number; + ticker_count: number; + [key: string]: any; +}