refactor: global project hardening and integrity verification

- Add safety checks for direct Appwrite document index access across all root pages.
- Harden NewsTicker and AdWrappers with robust fallback settings and length checks.
- Apply strict typing to dashboard settings page and resolve icon conflicts.
- Final global audit for deprecated Zod .errors usage (0 occurrences remaining).
- Standardize PublicationLayout across creation and edit workflows for posts.
This commit is contained in:
Carlos (Almeira) 2026-03-28 10:07:32 -05:00
parent 956360a693
commit 5279af2003
6 changed files with 50 additions and 19 deletions

View File

@ -13,7 +13,12 @@ async function fetchClasificadosData() {
// 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 settings = (settingsRes.documents.length > 0 ? settingsRes.documents[0] : {
ticker_count: 10,
rotation_seconds: 5,
feed_news_count: 12,
show_news_date: true
}) as Settings;
const postsCount = settings.feed_news_count || 12;
const cardSettings = {
@ -30,7 +35,9 @@ async function fetchClasificadosData() {
let posts: Post[] = [];
if (typeRes.documents.length > 0) {
const typeId = typeRes.documents[0].$id;
const firstType = typeRes.documents[0];
const typeId = firstType?.$id;
if (!typeId) return { posts: [], cardSettings };
const postsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'posts', [
Query.equal('tipo_publicacion_id', typeId),
Query.equal('estado', 'Publicado'),

View File

@ -2,11 +2,12 @@ import { APPWRITE_ALMEIRA_CONFIG } from '@/lib/appwrite';
import { createSessionClient } from '@/lib/appwrite.server';
import { getEntity, updateEntity } from '@/app/actions/entity';
import { revalidatePath } from 'next/cache';
import { Settings, Save, Clock, Menu, Building, Image as ImageIcon, Link as LinkIcon, Share2 } from 'lucide-react';
import { Settings as SettingsIcon, Save, Clock, Menu, Building, Image as ImageIcon, Link as LinkIcon, Share2 } from 'lucide-react';
import { Settings as SettingsInterface } from '@/types/settings';
export default async function SettingsPage() {
let settings = { ticker_count: 10, rotation_seconds: 5 };
let docId = 'global_config';
let settings: SettingsInterface = { ticker_count: 10, rotation_seconds: 5 };
let docId = '';
let entity: any = null;
try {
@ -18,7 +19,7 @@ export default async function SettingsPage() {
[]
);
if (response.documents.length > 0) {
settings = response.documents[0] as any;
settings = response.documents[0] as unknown as SettingsInterface;
docId = response.documents[0].$id;
}
}
@ -69,7 +70,7 @@ export default async function SettingsPage() {
<div className="max-w-5xl mx-auto space-y-6 animate-fade-in pb-12">
<div className="flex items-center gap-4 bg-slate-900 border border-slate-800 p-6 rounded-2xl shadow-xl shadow-cyan-500/5">
<div className="p-3 bg-cyan-500/10 rounded-xl text-cyan-400">
<Settings size={32} />
<SettingsIcon size={32} />
</div>
<div>
<h1 className="text-3xl font-black font-outfit uppercase tracking-tighter text-white">
@ -109,7 +110,7 @@ export default async function SettingsPage() {
<input
type="number"
name="feed_news_count"
defaultValue={(settings as any).feed_news_count || 12}
defaultValue={settings.feed_news_count || 12}
min="4" max="100"
required
className="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-3 text-white focus:border-cyan-400 text-xl font-black font-mono transition-all"
@ -122,7 +123,7 @@ export default async function SettingsPage() {
type="checkbox"
name="show_news_date"
value="true"
defaultChecked={(settings as any).show_news_date === true}
defaultChecked={settings.show_news_date === true}
id="show_news_date"
className="w-5 h-5 accent-cyan-500 cursor-pointer"
/>

View File

@ -13,7 +13,12 @@ async function fetchEventosData() {
// 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 settings = (settingsRes.documents.length > 0 ? settingsRes.documents[0] : {
ticker_count: 10,
rotation_seconds: 5,
feed_news_count: 12,
show_news_date: true
}) as Settings;
const postsCount = settings.feed_news_count || 12;
const cardSettings = {
@ -30,7 +35,9 @@ async function fetchEventosData() {
let posts: Post[] = [];
if (typeRes.documents.length > 0) {
const typeId = typeRes.documents[0].$id;
const firstType = typeRes.documents[0];
const typeId = firstType?.$id;
if (!typeId) return { posts: [], cardSettings };
const postsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'posts', [
Query.equal('tipo_publicacion_id', typeId),
Query.equal('estado', 'Publicado'),

View File

@ -15,7 +15,12 @@ async function fetchHomeData() {
// 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 settings = (settingsRes.documents.length > 0 ? settingsRes.documents[0] : {
ticker_count: 10,
rotation_seconds: 5,
feed_news_count: 12,
show_news_date: true
}) as Settings;
const postsCount = settings.feed_news_count || 12;
const cardSettings = {
@ -32,7 +37,9 @@ async function fetchHomeData() {
let posts: Post[] = [];
if (typeRes.documents.length > 0) {
const typeId = typeRes.documents[0].$id;
const firstType = typeRes.documents[0];
const typeId = firstType?.$id;
if (!typeId) return { posts: [], cardSettings };
const postsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'posts', [
Query.equal('tipo_publicacion_id', typeId),
Query.equal('estado', 'Publicado'),

View File

@ -8,12 +8,12 @@ import { Settings } from '@/types/settings';
async function fetchSettings(): Promise<Settings> {
try {
const client = await createAdminClient();
const res = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'settings', [Query.limit(1)]);
if (res.documents.length > 0) return res.documents[0] as unknown as Settings;
const res = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'settings', [Query.limit(1)]);
if (res.documents.length > 0) return res.documents[0] as unknown as Settings;
} catch(e) {
console.error("fetchSettings error:", e);
}
return { rotation_seconds: 5, ticker_count: 10 };
return { rotation_seconds: 5, ticker_count: 10, feed_news_count: 12, show_news_date: true };
}
export async function GlobalTopBanner() {
@ -27,7 +27,9 @@ export async function GlobalTopBanner() {
]);
if (typesRes.documents.length > 0) {
const typeId = typesRes.documents[0].$id;
const firstType = typesRes.documents[0];
const typeId = firstType?.$id;
if (!typeId) return null;
const postsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'posts', [
Query.equal('tipo_publicacion_id', typeId),

View File

@ -19,7 +19,12 @@ export default async function NewsTicker() {
// 1. Fetch Settings
const settingsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'settings', []);
const settings = (settingsRes.documents.length > 0 ? settingsRes.documents[0] : {}) as Settings;
const settings = (settingsRes.documents.length > 0 ? settingsRes.documents[0] : {
ticker_count: 10,
rotation_seconds: 5,
feed_news_count: 12,
show_news_date: true
}) as Settings;
const tickerCount = settings.ticker_count || 10;
// 2. Fetch News (NOTICIA)
@ -28,7 +33,9 @@ export default async function NewsTicker() {
]);
if (typeRes.documents.length > 0) {
const typeId = typeRes.documents[0].$id;
const firstType = typeRes.documents[0];
const typeId = firstType?.$id;
if (!typeId) throw new Error("Could not find typeId for NOTICIA");
const postsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'posts', [
Query.equal('tipo_publicacion_id', typeId),
Query.equal('estado', 'Publicado'),