fix: cache JSON concorrente, validação do sync e timeout nas chamadas ao Open Food Facts
- JsonCacheManager: a leitura inicial concorrente carregava o arquivo duas vezes e a segunda descartava as alterações da primeira; gravações simultâneas escreviam no mesmo arquivo. Agora a leitura é compartilhada e a gravação é enfileirada, via arquivo temporário + rename. - POST /api/cache/sync: aceitava qualquer objeto e usava code como chave do cache (inclusive ausente ou não numérico); agora só grava produtos com code numérico e conta os demais em failed. JSON malformado responde 400 em vez de 500. - GET /api/products/[barcode]: resposta da API sem product virava 200 com corpo vazio; agora responde 404. - Chamadas ao Open Food Facts (busca e produto) passam a ter timeout de 15 s e o User-Agent vem de um único lugar (lib/offSearch).
This commit is contained in:
1 parent
5ee5dcd893
commit
30d7ef701f
7 files changed
+273
-30
No files matched your search
+33
-17
@@ -18,6 +18,10 @@ interface JsonCacheData {
|
||||
export class JsonCacheManager {
|
||||
private cacheFilePath: string;
|
||||
private cacheData: JsonCacheData | null = null;
|
||||
/** Fila de gravações: evita que duas gravações simultâneas corrompam o arquivo. */
|
||||
private writeQueue: Promise<void> = Promise.resolve();
|
||||
/** Leitura inicial em andamento, compartilhada por chamadas concorrentes. */
|
||||
private loadPromise: Promise<void> | null = null;
|
||||
private static instance: JsonCacheManager;
|
||||
|
||||
constructor() {
|
||||
@@ -53,7 +57,14 @@ export class JsonCacheManager {
|
||||
/**
|
||||
* Carrega dados do cache do arquivo JSON
|
||||
*/
|
||||
private async loadCache(): Promise<void> {
|
||||
private loadCache(): Promise<void> {
|
||||
// Sem isto, duas chamadas simultâneas na primeira leitura carregam o arquivo
|
||||
// duas vezes e a segunda descarta as alterações feitas pela primeira.
|
||||
this.loadPromise ??= this.readCacheFile();
|
||||
return this.loadPromise;
|
||||
}
|
||||
|
||||
private async readCacheFile(): Promise<void> {
|
||||
if (this.cacheData) return;
|
||||
|
||||
try {
|
||||
@@ -86,23 +97,28 @@ export class JsonCacheManager {
|
||||
/**
|
||||
* Salva dados do cache no arquivo JSON
|
||||
*/
|
||||
private async saveCache(): Promise<void> {
|
||||
if (!this.cacheData) return;
|
||||
private saveCache(): Promise<void> {
|
||||
const write = async () => {
|
||||
if (!this.cacheData) return;
|
||||
|
||||
try {
|
||||
await fs.writeFile(
|
||||
this.cacheFilePath,
|
||||
JSON.stringify(this.cacheData, null, 2),
|
||||
"utf-8"
|
||||
);
|
||||
console.log(
|
||||
`[JSON Cache] Cache salvo: ${
|
||||
Object.keys(this.cacheData.products).length
|
||||
} produtos`
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("[JSON Cache] Erro ao salvar cache:", error);
|
||||
}
|
||||
try {
|
||||
// Grava em arquivo temporário e renomeia: o arquivo final nunca fica pela metade
|
||||
const tempPath = `${this.cacheFilePath}.tmp`;
|
||||
await fs.writeFile(tempPath, JSON.stringify(this.cacheData, null, 2), "utf-8");
|
||||
await fs.rename(tempPath, this.cacheFilePath);
|
||||
console.log(
|
||||
`[JSON Cache] Cache salvo: ${
|
||||
Object.keys(this.cacheData.products).length
|
||||
} produtos`
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("[JSON Cache] Erro ao salvar cache:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// write() nunca rejeita, então a fila continua mesmo após uma falha
|
||||
this.writeQueue = this.writeQueue.then(write);
|
||||
return this.writeQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user