feat: adiciona redis e healthcheck

- Implementa cliente Redis com retry e cache
- Adiciona healthcheck da API
- Configura tipagem para Next.js API routes
- Implementa cache de histórias
- Adiciona tratamento de erros robusto
- Configura monitoramento de conexões
- Otimiza performance com cache distribuído
This commit is contained in:
Lucas Santana 2024-12-25 13:55:03 -03:00
parent 521a99a5c2
commit cc23c83c05
6 changed files with 1136 additions and 21 deletions

View File

@ -28,6 +28,11 @@ e este projeto adere ao [Semantic Versioning](https://semver.org/lang/pt-BR/).
- Dockerfile com multi-stage build para otimização
- Configuração de registry no Gitea
- Cache de histórias com Redis
- Implementação de cliente Redis com:
- Retry strategy otimizada
- Reconexão automática
- Tipagem forte
- Funções utilitárias para cache
- Scripts de deploy e monitoramento
### Modificado

1068
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -19,8 +19,12 @@
"@radix-ui/react-tabs": "^1.1.2",
"@supabase/supabase-js": "^2.39.7",
"@tanstack/react-query": "^5.62.8",
"@types/ioredis": "^4.28.10",
"@types/next": "^8.0.7",
"clsx": "^2.1.1",
"ioredis": "^5.4.2",
"lucide-react": "^0.344.0",
"next": "^15.1.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.54.2",

View File

@ -1,5 +1,62 @@
import { Redis } from 'ioredis';
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379');
if (!process.env.REDIS_URL) {
throw new Error('REDIS_URL não configurada');
}
const redis = new Redis(process.env.REDIS_URL, {
maxRetriesPerRequest: 3,
retryStrategy(times) {
const delay = Math.min(times * 50, 2000);
return delay;
},
reconnectOnError(err) {
const targetError = 'READONLY';
if (err.message.includes(targetError)) {
return true;
}
return false;
}
});
redis.on('error', (error) => {
console.error('[Redis] Erro de conexão:', error);
});
redis.on('connect', () => {
console.log('[Redis] Conectado com sucesso');
});
export async function getFromCache<T>(key: string): Promise<T | null> {
try {
const cached = await redis.get(key);
if (cached) {
return JSON.parse(cached) as T;
}
return null;
} catch (error) {
console.error('[Redis] Erro ao buscar do cache:', error);
return null;
}
}
export async function setInCache(key: string, value: any, expireInSeconds = 3600): Promise<void> {
try {
await redis.set(key, JSON.stringify(value), 'EX', expireInSeconds);
} catch (error) {
console.error('[Redis] Erro ao salvar no cache:', error);
}
}
export async function invalidateCache(pattern: string): Promise<void> {
try {
const keys = await redis.keys(pattern);
if (keys.length > 0) {
await redis.del(...keys);
}
} catch (error) {
console.error('[Redis] Erro ao invalidar cache:', error);
}
}
export default redis;

View File

@ -1,6 +1,7 @@
import redis from '@/lib/redis';
import { supabase } from '@/lib/supabase';
export default async function handler(req, res) {
export default async function handler(_: any, res: { status: (arg0: number) => { (): any; new(): any; json: { (arg0: { status: string; error?: any; }): void; new(): any; }; }; }) {
try {
// Verifica conexão com Redis
await redis.ping();
@ -11,7 +12,11 @@ export default async function handler(req, res) {
if (error) throw error;
res.status(200).json({ status: 'healthy' });
} catch (error) {
res.status(500).json({ status: 'unhealthy', error: error.message });
} catch (error: unknown) {
if (error instanceof Error) {
res.status(500).json({ status: 'unhealthy', error: error.message });
} else {
res.status(500).json({ status: 'unhealthy', error: 'Erro desconhecido' });
}
}
}
}

View File

@ -1,7 +1,11 @@
import { NextApiRequest, NextApiResponse } from 'next';
import redis from '@/lib/redis';
import { supabase } from '@/lib/supabase';
export default async function handler(req, res) {
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const { id } = req.query;
// Tenta pegar do cache primeiro