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 { 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<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 as string)
}));
}
}

View File

@ -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<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 as string)
}));
}
}

View File

@ -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<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 as string)
}));
}
}

View File

@ -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<Settings> {
try {

View File

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

View File

@ -80,6 +80,7 @@ export interface Post {
tipo_publicacion_id: string | PublicationType;
imagen_destacada_id?: string | MediaItem;
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;
}