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.
This commit is contained in:
Carlos (Almeira) 2026-03-28 09:57:42 -05:00
parent ea551469b7
commit 339ac0125e
7 changed files with 55 additions and 40 deletions

View File

@ -4,6 +4,8 @@ import { Query } from 'node-appwrite';
import { NewsCard } from '@/components/NewsCard'; import { NewsCard } from '@/components/NewsCard';
import { GlobalSidebar, hasSidebarContent } from '@/components/AdWrappers'; import { GlobalSidebar, hasSidebarContent } from '@/components/AdWrappers';
import { Sparkles } from 'lucide-react'; import { Sparkles } from 'lucide-react';
import { Settings } from '@/types/settings';
import { Post, MediaItem } from '@/types/publications';
async function fetchClasificadosData() { async function fetchClasificadosData() {
try { try {
@ -11,7 +13,7 @@ async function fetchClasificadosData() {
// 1. Settings // 1. Settings
const settingsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, '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 postsCount = settings.feed_news_count || 12;
const cardSettings = { const cardSettings = {
@ -26,7 +28,7 @@ async function fetchClasificadosData() {
Query.equal('codigo', 'CLASIFICADO') Query.equal('codigo', 'CLASIFICADO')
]); ]);
let posts: any[] = []; let posts: Post[] = [];
if (typeRes.documents.length > 0) { if (typeRes.documents.length > 0) {
const typeId = typeRes.documents[0].$id; const typeId = typeRes.documents[0].$id;
const postsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'posts', [ const postsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'posts', [
@ -35,16 +37,19 @@ async function fetchClasificadosData() {
Query.orderDesc('fecha_publicacion'), Query.orderDesc('fecha_publicacion'),
Query.limit(postsCount) Query.limit(postsCount)
]); ]);
posts = postsRes.documents; posts = postsRes.documents as unknown as Post[];
// 3. Resolve Media // 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) { if (mediaIds.length > 0) {
const mediaRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'media', [ const mediaRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'media', [
Query.equal('$id', mediaIds) Query.equal('$id', mediaIds)
]); ]);
const mediaMap = new Map(mediaRes.documents.map(m => [m.$id, m])); const mediaMap = new Map<string, MediaItem>(mediaRes.documents.map(m => [m.$id, m as unknown as MediaItem]));
posts = posts.map(p => ({ ...p, resolved_media: mediaMap.get(p.imagen_destacada_id) })); posts = posts.map(p => ({
...p,
resolved_media: mediaMap.get(p.imagen_destacada_id as string)
}));
} }
} }

View File

@ -4,6 +4,8 @@ import { Query } from 'node-appwrite';
import { NewsCard } from '@/components/NewsCard'; import { NewsCard } from '@/components/NewsCard';
import { GlobalSidebar, hasSidebarContent } from '@/components/AdWrappers'; import { GlobalSidebar, hasSidebarContent } from '@/components/AdWrappers';
import { Calendar } from 'lucide-react'; import { Calendar } from 'lucide-react';
import { Settings } from '@/types/settings';
import { Post, MediaItem } from '@/types/publications';
async function fetchEventosData() { async function fetchEventosData() {
try { try {
@ -11,7 +13,7 @@ async function fetchEventosData() {
// 1. Settings // 1. Settings
const settingsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, '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 postsCount = settings.feed_news_count || 12;
const cardSettings = { const cardSettings = {
@ -26,7 +28,7 @@ async function fetchEventosData() {
Query.equal('codigo', 'EVENTO') Query.equal('codigo', 'EVENTO')
]); ]);
let posts: any[] = []; let posts: Post[] = [];
if (typeRes.documents.length > 0) { if (typeRes.documents.length > 0) {
const typeId = typeRes.documents[0].$id; const typeId = typeRes.documents[0].$id;
const postsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'posts', [ const postsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'posts', [
@ -35,16 +37,19 @@ async function fetchEventosData() {
Query.orderDesc('fecha_publicacion'), Query.orderDesc('fecha_publicacion'),
Query.limit(postsCount) Query.limit(postsCount)
]); ]);
posts = postsRes.documents; posts = postsRes.documents as unknown as Post[];
// 3. Resolve Media // 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) { if (mediaIds.length > 0) {
const mediaRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'media', [ const mediaRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'media', [
Query.equal('$id', mediaIds) Query.equal('$id', mediaIds)
]); ]);
const mediaMap = new Map(mediaRes.documents.map(m => [m.$id, m])); const mediaMap = new Map<string, MediaItem>(mediaRes.documents.map(m => [m.$id, m as unknown as MediaItem]));
posts = posts.map(p => ({ ...p, resolved_media: mediaMap.get(p.imagen_destacada_id) })); posts = posts.map(p => ({
...p,
resolved_media: mediaMap.get(p.imagen_destacada_id as string)
}));
} }
} }

View File

@ -6,33 +6,31 @@ import { GlobalSidebar, hasSidebarContent } from '@/components/AdWrappers';
import { RadioTower, Sparkles, ChevronDown } from 'lucide-react'; import { RadioTower, Sparkles, ChevronDown } from 'lucide-react';
import { decodeHtmlEntities } from '@/lib/utils'; import { decodeHtmlEntities } from '@/lib/utils';
import { Settings } from '@/types/settings';
import { Post, MediaItem } from '@/types/publications';
async function fetchHomeData() { async function fetchHomeData() {
try { try {
const client = await createAdminClient(); const client = await createAdminClient();
// 1. Settings with fallback // 1. Settings
let settings: any = {}; const settingsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'settings', []);
try { const settings = (settingsRes.documents.length > 0 ? settingsRes.documents[0] : {}) as Settings;
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);
}
const postsCount = settings?.feed_news_count || 12; const postsCount = settings.feed_news_count || 12;
const cardSettings = { const cardSettings = {
showCategory: settings?.news_card_show_category ?? false, showCategory: settings.news_card_show_category ?? false,
showFooterDate: settings?.show_news_date ?? true, showFooterDate: settings.show_news_date ?? false,
showFooterButton: settings?.news_card_show_footer_button ?? true, showFooterButton: settings.news_card_show_footer_button ?? true,
footerText: settings?.news_card_footer_text || "" 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', [ const typeRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'publication_types', [
Query.equal('codigo', 'NOTICIA') Query.equal('codigo', 'NOTICIA')
]); ]);
let posts: any[] = []; let posts: Post[] = [];
if (typeRes.documents.length > 0) { if (typeRes.documents.length > 0) {
const typeId = typeRes.documents[0].$id; const typeId = typeRes.documents[0].$id;
const postsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'posts', [ const postsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'posts', [
@ -41,19 +39,18 @@ async function fetchHomeData() {
Query.orderDesc('fecha_publicacion'), Query.orderDesc('fecha_publicacion'),
Query.limit(postsCount) 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'); const mediaIds = posts.map(p => p.imagen_destacada_id).filter(id => id && typeof id === 'string');
if (mediaIds.length > 0) { if (mediaIds.length > 0) {
const mediaRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'media', [ const mediaRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'media', [
Query.equal('$id', mediaIds) Query.equal('$id', mediaIds)
]); ]);
const mediaMap = new Map(mediaRes.documents.map(m => [m.$id, m])); const mediaMap = new Map<string, MediaItem>(mediaRes.documents.map(m => [m.$id, m as unknown as MediaItem]));
posts = posts.map(p => ({
posts = posts.map(p => ({ ...p,
...p, resolved_media: mediaMap.get(p.imagen_destacada_id as string)
resolved_media: mediaMap.get(p.imagen_destacada_id)
})); }));
} }
} }

View File

@ -3,11 +3,7 @@ import { createAdminClient } from '@/lib/appwrite.server';
import TopBanner from './TopBanner'; import TopBanner from './TopBanner';
import ClientSidebars from './ClientSidebars'; import ClientSidebars from './ClientSidebars';
interface Settings { import { Settings } from '@/types/settings';
rotation_seconds: number;
ticker_count: number;
[key: string]: any;
}
async function fetchSettings(): Promise<Settings> { async function fetchSettings(): Promise<Settings> {
try { try {

View File

@ -3,6 +3,7 @@ import { createSessionClient } from '@/lib/appwrite.server';
import { Query } from 'node-appwrite'; import { Query } from 'node-appwrite';
import Link from 'next/link'; import Link from 'next/link';
import { MessageCircle } from 'lucide-react'; import { MessageCircle } from 'lucide-react';
import { Settings } from '@/types/settings';
export default async function NewsTicker() { export default async function NewsTicker() {
let tickerItems: any[] = []; let tickerItems: any[] = [];
@ -18,7 +19,7 @@ export default async function NewsTicker() {
// 1. Fetch Settings // 1. Fetch Settings
const settingsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, '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; const tickerCount = settings.ticker_count || 10;
// 2. Fetch News (NOTICIA) // 2. Fetch News (NOTICIA)

View File

@ -80,6 +80,7 @@ export interface Post {
tipo_publicacion_id: string | PublicationType; tipo_publicacion_id: string | PublicationType;
imagen_destacada_id?: string | MediaItem; imagen_destacada_id?: string | MediaItem;
cliente_id?: string | Client; cliente_id?: string | Client;
resolved_media?: MediaItem;
} }
// ───────────────────────────────────────────── // ─────────────────────────────────────────────

10
src/types/settings.ts Normal file
View File

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