Skip to content

Commit

Permalink
fix(tiny-erp): Early respond webhooks with OK and property queue to r…
Browse files Browse the repository at this point in the history
…etry on failure
  • Loading branch information
leomp12 committed May 16, 2024
1 parent 3acf338 commit f2f09e3
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 93 deletions.
4 changes: 2 additions & 2 deletions packages/apps/tiny-erp/src/event-to-tiny.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,9 @@ const handleApiEvent: ApiEventHandler = async ({
appData,
canCreateNew,
isHiddenQueue,
).then((payload) => {
).then((payload: any) => {
return afterQueue(queueEntry, appData, app, payload);
}).catch((err) => {
}).catch((err: any) => {
return afterQueue(queueEntry, appData, app, err);
});
}
Expand Down
13 changes: 10 additions & 3 deletions packages/apps/tiny-erp/src/integration/after-tiny-queue.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import type { AppOrId } from '@cloudcommerce/firebase/lib/helpers/update-app-data';
import logger from 'firebase-functions/logger';
import updateAppData from '@cloudcommerce/firebase/lib/helpers/update-app-data';

export default async (queueEntry, appData, application, payload) => {
export default async (
queueEntry: Record<string, any>,
appData: Record<string, any>,
application: AppOrId,
payload: any,
) => {
const isError = payload instanceof Error;
const isImportation = queueEntry.action.endsWith('importation');
const isQueued = !queueEntry.isNotQueued;
const logs = appData.logs || [];
const logEntry = {
resource: /order/i.test(queueEntry.queue) ? 'orders' : 'products',
Expand All @@ -29,7 +36,7 @@ export default async (queueEntry, appData, application, payload) => {
if (response) {
const { data, status } = response;
notes = `Error: Status ${status} \n${JSON.stringify(data)}`;
if (!status || status === 429 || status >= 500) {
if (isQueued && (!status || status === 429 || status >= 500)) {
return setTimeout(() => {
throw payload;
}, 2000);
Expand All @@ -50,7 +57,7 @@ export default async (queueEntry, appData, application, payload) => {
logEntry.notes = notes.substring(0, 5000);
}

if (isError || !isImportation) {
if (isQueued && (isError || !isImportation)) {
logs.unshift(logEntry);
await updateAppData(application, {
logs: logs.slice(0, 200),
Expand Down
184 changes: 96 additions & 88 deletions packages/apps/tiny-erp/src/tiny-webhook.ts
Original file line number Diff line number Diff line change
@@ -1,103 +1,111 @@
import type { Request, Response } from 'firebase-functions';
import type { Applications } from '@cloudcommerce/types';
import logger from 'firebase-functions/logger';
import { info } from 'firebase-functions/logger';
import api from '@cloudcommerce/api';
import config from '@cloudcommerce/firebase/lib/config';
import importProduct from './integration/import-product-from-tiny';
import importOrder from './integration/import-order-from-tiny';

let appData: Record<string, any> = {};
let application: Applications;
import afterQueue from './integration/after-tiny-queue';

export default async (req: Request, res: Response) => {
const tinyToken = req.query.token;
if (typeof tinyToken === 'string' && tinyToken && req.body) {
const { dados, tipo } = req.body;
if (dados) {
/*
TODO: Check Tiny server IPs
const clientIp = req.get('x-forwarded-for') || req.connection.remoteAddress
*/
const { TINYERP_TOKEN } = process.env;
if (!TINYERP_TOKEN || TINYERP_TOKEN !== tinyToken) {
const { apps: { tinyErp: { appId } } } = config.get();
const applicationId = req.query._id;
const appEndpoint = applicationId && typeof applicationId === 'string'
? `applications/${applicationId}`
: `applications/app_id:${appId}`;
application = (await api.get(appEndpoint as `applications/${Applications['_id']}`)).data;
appData = {
...application.data,
...application.hidden_data,
};
if (appData.tiny_api_token !== tinyToken) {
res.sendStatus(401);
return;
}
process.env.TINYERP_TOKEN = tinyToken;
}
if (typeof tinyToken !== 'string' || !tinyToken) {
res.sendStatus(403);
return;
}
const { body } = req;
if (typeof body !== 'object' || !body) {
res.sendStatus(422);
return;
}
const { dados, tipo } = body;
if (typeof dados !== 'object' || !dados) {
res.sendStatus(400);
return;
}
/*
TODO: Check Tiny server IPs
const clientIp = req.get('x-forwarded-for') || req.connection.remoteAddress
*/

if (dados.idVendaTiny) {
const orderNumber = `id:${dados.idVendaTiny}`;
const queueEntry = {
nextId: orderNumber,
isNotQueued: true,
};
await importOrder({}, queueEntry);
} else if (
(tipo === 'produto' || tipo === 'estoque')
&& (dados.id || dados.idProduto)
&& (dados.codigo || dados.sku)
) {
const nextId = String(dados.skuMapeamento || dados.sku || dados.codigo);
const tinyStockUpdate = {
ref: `${nextId}`,
tipo,
produto: {
id: dados.idProduto,
codigo: dados.sku,
...dados,
},
};
logger.info(`> Tiny webhook: ${nextId} => ${tinyStockUpdate.produto.saldo}`);
const queueEntry = {
nextId,
tinyStockUpdate,
isNotQueued: true,
app: application,
};
await importProduct({}, queueEntry, appData, false, true);
}
const { apps: { tinyErp: { appId } } } = config.get();
const applicationId = req.query._id;
const appEndpoint = applicationId && typeof applicationId === 'string'
? `applications/${applicationId}` as `applications/${Applications['_id']}`
: `applications/app_id:${appId}` as const;
const application = (await api.get(appEndpoint)).data;
const appData = {
...application.data,
...application.hidden_data,
};
if (!process.env.TINYERP_TOKEN) {
process.env.TINYERP_TOKEN = appData.tiny_api_token;
}
if (process.env.TINYERP_TOKEN !== tinyToken) {
res.sendStatus(401);
return;
}

if (dados.idVendaTiny) {
const orderNumber = `id:${dados.idVendaTiny}`;
const queueEntry = {
nextId: orderNumber,
isNotQueued: true,
};
importOrder({}, queueEntry).catch((err: any) => {
return afterQueue(queueEntry, appData, application, err);
});
} else if (
(tipo === 'produto' || tipo === 'estoque')
&& (dados.id || dados.idProduto)
&& (dados.codigo || dados.sku)
) {
const nextId = String(dados.skuMapeamento || dados.sku || dados.codigo);
const tinyStockUpdate = {
ref: `${nextId}`,
tipo,
produto: {
id: dados.idProduto,
codigo: dados.sku,
...dados,
},
};
info(`> Tiny webhook: ${nextId} => ${tinyStockUpdate.produto.saldo}`);
const queueEntry = {
nextId,
tinyStockUpdate,
isNotQueued: true,
app: application,
};
importProduct({}, queueEntry, appData, false, true).catch((err: any) => {
return afterQueue(queueEntry, appData, application, err);
});
}

if (tipo === 'produto') {
const mapeamentos: any[] = [];
const parseTinyItem = (tinyItem) => {
if (tinyItem) {
const {
idMapeamento,
id,
codigo,
sku,
} = tinyItem;
mapeamentos.push({
idMapeamento: idMapeamento || id,
skuMapeamento: codigo || sku,
});
}
};
parseTinyItem(dados);
if (Array.isArray(dados.variacoes)) {
dados.variacoes.forEach((variacao) => {
parseTinyItem(variacao.id ? variacao : variacao.variacao);
});
}
res.status(200).send(mapeamentos);
return;
if (tipo === 'produto') {
const mapeamentos: any[] = [];
const parseTinyItem = (tinyItem) => {
if (tinyItem) {
const {
idMapeamento,
id,
codigo,
sku,
} = tinyItem;
mapeamentos.push({
idMapeamento: idMapeamento || id,
skuMapeamento: codigo || sku,
});
}
res.sendStatus(200);
return;
};
parseTinyItem(dados);
if (Array.isArray(dados.variacoes)) {
dados.variacoes.forEach((variacao) => {
parseTinyItem(variacao.id ? variacao : variacao.variacao);
});
}
logger.warn('< Invalid Tiny Webhook body', req.body);
res.status(200).send(mapeamentos);
return;
}
res.sendStatus(403);
res.sendStatus(200);
};

0 comments on commit f2f09e3

Please sign in to comment.