Compare commits
2 Commits
4d7daff496
...
83f165381d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83f165381d | ||
|
|
aa581a4d14 |
@@ -18,13 +18,15 @@ services:
|
|||||||
- TINY_API_TOKEN=${TINY_API_TOKEN}
|
- TINY_API_TOKEN=${TINY_API_TOKEN}
|
||||||
- GRAPHS_API_URL=${GRAPHS_API_URL}
|
- GRAPHS_API_URL=${GRAPHS_API_URL}
|
||||||
- GRAPHS_API_KEY=${GRAPHS_API_KEY}
|
- GRAPHS_API_KEY=${GRAPHS_API_KEY}
|
||||||
- GRAPHS_BACKFILL_CONTACT_FALLBACK=${GRAPHS_BACKFILL_CONTACT_FALLBACK}
|
- GRAPHS_BACKFILL_CONTACT_FALLBACK=${GRAPHS_BACKFILL_CONTACT_FALLBACK:-true}
|
||||||
- GRAPHS_BACKFILL_TINY_REQUEST_DELAY_MS=${GRAPHS_BACKFILL_TINY_REQUEST_DELAY_MS}
|
- GRAPHS_BACKFILL_TINY_REQUEST_DELAY_MS=${GRAPHS_BACKFILL_TINY_REQUEST_DELAY_MS:-6000}
|
||||||
- GRAPHS_BACKFILL_ORDER_DELAY_MS=${GRAPHS_BACKFILL_ORDER_DELAY_MS}
|
- GRAPHS_BACKFILL_ORDER_DELAY_MS=${GRAPHS_BACKFILL_ORDER_DELAY_MS:-0}
|
||||||
- GRAPHS_BACKFILL_TINY_BLOCK_DELAY_MS=${GRAPHS_BACKFILL_TINY_BLOCK_DELAY_MS}
|
- GRAPHS_BACKFILL_TINY_BLOCK_DELAY_MS=${GRAPHS_BACKFILL_TINY_BLOCK_DELAY_MS:-600000}
|
||||||
- GRAPHS_BACKFILL_MAX_TINY_BLOCK_RETRIES=${GRAPHS_BACKFILL_MAX_TINY_BLOCK_RETRIES}
|
- GRAPHS_BACKFILL_MAX_TINY_BLOCK_RETRIES=${GRAPHS_BACKFILL_MAX_TINY_BLOCK_RETRIES:-0}
|
||||||
- TINY_FETCH_SELLER_DETAILS=${TINY_FETCH_SELLER_DETAILS}
|
- TINY_FETCH_SELLER_DETAILS=${TINY_FETCH_SELLER_DETAILS:-false}
|
||||||
- TINY_ORDER_DETAILS_CACHE_TTL_MS=${TINY_ORDER_DETAILS_CACHE_TTL_MS}
|
- TINY_ORDER_DETAILS_CACHE_TTL_MS=${TINY_ORDER_DETAILS_CACHE_TTL_MS:-120000}
|
||||||
|
- TINY_LIVE_REQUEST_DELAY_MS=${TINY_LIVE_REQUEST_DELAY_MS:-6000}
|
||||||
|
- TINY_LIVE_MAX_QUEUE_WAIT_MS=${TINY_LIVE_MAX_QUEUE_WAIT_MS:-30000}
|
||||||
# Optional: If you use a shared Docker network for Nginx Proxy Manager, you can attach it here:
|
# Optional: If you use a shared Docker network for Nginx Proxy Manager, you can attach it here:
|
||||||
# networks:
|
# networks:
|
||||||
# - nginx-proxy-manager-network
|
# - nginx-proxy-manager-network
|
||||||
|
|||||||
@@ -18,6 +18,26 @@ const orderDetailsCache = new Map<string, CachedOrderDetails>();
|
|||||||
const inFlightOrderDetails = new Map<string, Promise<OrderDetailsResult | null>>();
|
const inFlightOrderDetails = new Map<string, Promise<OrderDetailsResult | null>>();
|
||||||
const configuredCacheTtlMs = Number(process.env.TINY_ORDER_DETAILS_CACHE_TTL_MS || 120000);
|
const configuredCacheTtlMs = Number(process.env.TINY_ORDER_DETAILS_CACHE_TTL_MS || 120000);
|
||||||
const ORDER_DETAILS_CACHE_TTL_MS = Number.isFinite(configuredCacheTtlMs) ? configuredCacheTtlMs : 120000;
|
const ORDER_DETAILS_CACHE_TTL_MS = Number.isFinite(configuredCacheTtlMs) ? configuredCacheTtlMs : 120000;
|
||||||
|
const numberEnv = (name: string, fallback: number) => {
|
||||||
|
const raw = process.env[name];
|
||||||
|
if (raw === undefined || raw.trim() === '') return fallback;
|
||||||
|
|
||||||
|
const value = Number(raw);
|
||||||
|
return Number.isFinite(value) && value >= 0 ? value : fallback;
|
||||||
|
};
|
||||||
|
const LIVE_TINY_REQUEST_DELAY_MS = numberEnv('TINY_LIVE_REQUEST_DELAY_MS', 6000);
|
||||||
|
const LIVE_TINY_MAX_QUEUE_WAIT_MS = numberEnv('TINY_LIVE_MAX_QUEUE_WAIT_MS', 30000);
|
||||||
|
let liveTinyQueueTail: Promise<void> = Promise.resolve();
|
||||||
|
let liveTinyNextRequestAt = 0;
|
||||||
|
let liveTinyPendingRequests = 0;
|
||||||
|
|
||||||
|
class LiveTinyQueueBacklogError extends Error {
|
||||||
|
constructor(orderId: string, estimatedWaitMs: number) {
|
||||||
|
super(`Tiny live queue is too busy for Order ID ${orderId}. Estimated wait ${estimatedWaitMs}ms.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
|
||||||
function pruneExpiredOrderDetailsCache() {
|
function pruneExpiredOrderDetailsCache() {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
@@ -50,6 +70,42 @@ function setCachedOrderDetails(orderId: string, cached: Omit<CachedOrderDetails,
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function runWithLiveTinySlot<T>(orderId: string, servicePhp: string, operation: () => Promise<T>): Promise<T> {
|
||||||
|
if (LIVE_TINY_REQUEST_DELAY_MS <= 0) {
|
||||||
|
return operation();
|
||||||
|
}
|
||||||
|
|
||||||
|
const estimatedWaitMs = Math.max(0, liveTinyNextRequestAt - Date.now()) + (liveTinyPendingRequests * LIVE_TINY_REQUEST_DELAY_MS);
|
||||||
|
if (estimatedWaitMs > LIVE_TINY_MAX_QUEUE_WAIT_MS) {
|
||||||
|
throw new LiveTinyQueueBacklogError(orderId, estimatedWaitMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
liveTinyPendingRequests += 1;
|
||||||
|
const previousTail = liveTinyQueueTail.catch(() => undefined);
|
||||||
|
const runPromise = previousTail.then(async () => {
|
||||||
|
const waitMs = Math.max(0, liveTinyNextRequestAt - Date.now());
|
||||||
|
if (waitMs > 0) {
|
||||||
|
console.log(`[Tiny Live Queue] Waiting ${waitMs}ms before ${servicePhp} for Order ID: ${orderId}.`);
|
||||||
|
await sleep(waitMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
liveTinyNextRequestAt = Date.now() + LIVE_TINY_REQUEST_DELAY_MS;
|
||||||
|
console.log(`[Tiny Live Queue] Fetching ${servicePhp} for Order ID: ${orderId}. Pending: ${liveTinyPendingRequests}.`);
|
||||||
|
return operation();
|
||||||
|
}).finally(() => {
|
||||||
|
liveTinyPendingRequests = Math.max(0, liveTinyPendingRequests - 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
liveTinyQueueTail = runPromise.then(() => undefined, () => undefined);
|
||||||
|
return runPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function tinyLivePost(orderId: string, servicePhp: string, params: URLSearchParams) {
|
||||||
|
return runWithLiveTinySlot(orderId, servicePhp, () => axios.post(`https://api.tiny.com.br/api2/${servicePhp}`, params, {
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchOrderDetailsFromTiny(orderId: string, tinyApiToken: string): Promise<OrderDetailsResult | null> {
|
async function fetchOrderDetailsFromTiny(orderId: string, tinyApiToken: string): Promise<OrderDetailsResult | null> {
|
||||||
console.log(`Fetching full details for Order ID: ${orderId} from Tiny API...`);
|
console.log(`Fetching full details for Order ID: ${orderId} from Tiny API...`);
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
@@ -57,9 +113,7 @@ async function fetchOrderDetailsFromTiny(orderId: string, tinyApiToken: string):
|
|||||||
params.append('id', orderId);
|
params.append('id', orderId);
|
||||||
params.append('formato', 'JSON');
|
params.append('formato', 'JSON');
|
||||||
|
|
||||||
const apiResponse = await axios.post('https://api.tiny.com.br/api2/pedido.obter.php', params, {
|
const apiResponse = await tinyLivePost(orderId, 'pedido.obter.php', params);
|
||||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
|
|
||||||
});
|
|
||||||
|
|
||||||
if (apiResponse.data?.retorno?.status !== 'OK') {
|
if (apiResponse.data?.retorno?.status !== 'OK') {
|
||||||
console.error('Tiny API returned an error:', apiResponse.data?.retorno?.erros || 'Unknown error');
|
console.error('Tiny API returned an error:', apiResponse.data?.retorno?.erros || 'Unknown error');
|
||||||
@@ -80,9 +134,7 @@ async function fetchOrderDetailsFromTiny(orderId: string, tinyApiToken: string):
|
|||||||
vendorParams.append('id', idVendedor);
|
vendorParams.append('id', idVendedor);
|
||||||
vendorParams.append('formato', 'JSON');
|
vendorParams.append('formato', 'JSON');
|
||||||
|
|
||||||
const vendorResponse = await axios.post('https://api.tiny.com.br/api2/contato.obter.php', vendorParams, {
|
const vendorResponse = await tinyLivePost(orderId, 'contato.obter.php', vendorParams);
|
||||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
|
|
||||||
});
|
|
||||||
|
|
||||||
if (vendorResponse.data?.retorno?.status === 'OK') {
|
if (vendorResponse.data?.retorno?.status === 'OK') {
|
||||||
const contato = vendorResponse.data.retorno.contato;
|
const contato = vendorResponse.data.retorno.contato;
|
||||||
@@ -178,7 +230,11 @@ export const handleTinyOrderUpdate = async (req: Request, res: Response): Promis
|
|||||||
whatsappVendedor = details.whatsappVendedor;
|
whatsappVendedor = details.whatsappVendedor;
|
||||||
}
|
}
|
||||||
} catch (apiError: any) {
|
} catch (apiError: any) {
|
||||||
console.error('Failed to fetch from Tiny API:', apiError.message);
|
if (apiError instanceof LiveTinyQueueBacklogError) {
|
||||||
|
console.warn(`${apiError.message} Forwarding without Tiny enrichment.`);
|
||||||
|
} else {
|
||||||
|
console.error('Failed to fetch from Tiny API:', apiError.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn('TINY_API_TOKEN is not set in environment variables. Skipping full details fetch.');
|
console.warn('TINY_API_TOKEN is not set in environment variables. Skipping full details fetch.');
|
||||||
|
|||||||
@@ -62,7 +62,10 @@ const NORMALIZE_PHONE = process.env.GRAPHS_BACKFILL_NORMALIZE_PHONE !== 'false';
|
|||||||
const START_DATE = process.env.GRAPHS_BACKFILL_START_DATE || '13/02/2025';
|
const START_DATE = process.env.GRAPHS_BACKFILL_START_DATE || '13/02/2025';
|
||||||
const END_DATE = process.env.GRAPHS_BACKFILL_END_DATE || getTodayDate();
|
const END_DATE = process.env.GRAPHS_BACKFILL_END_DATE || getTodayDate();
|
||||||
const numberEnv = (name: string, fallback: number) => {
|
const numberEnv = (name: string, fallback: number) => {
|
||||||
const value = Number(process.env[name] ?? fallback);
|
const raw = process.env[name];
|
||||||
|
if (raw === undefined || raw.trim() === '') return fallback;
|
||||||
|
|
||||||
|
const value = Number(raw);
|
||||||
return Number.isFinite(value) && value >= 0 ? value : fallback;
|
return Number.isFinite(value) && value >= 0 ? value : fallback;
|
||||||
};
|
};
|
||||||
const MAX_DAYS = numberEnv('GRAPHS_BACKFILL_MAX_DAYS', 0);
|
const MAX_DAYS = numberEnv('GRAPHS_BACKFILL_MAX_DAYS', 0);
|
||||||
|
|||||||
Reference in New Issue
Block a user