initial commit
commit
a4ae903b7d
|
@ -0,0 +1,30 @@
|
|||
|
||||
## for NA - US
|
||||
SQS_QUEUE_US_URL = https://sqs.us-east-1.amazonaws.com/416818527652/sp-traffic-stream-us-destination-queue
|
||||
## for NA -CA
|
||||
SQS_QUEUE_CA_URL = https://sqs.us-east-1.amazonaws.com/416818527652/sp-traffic-stream-ca-destination-queue
|
||||
## for EU - UK
|
||||
SQS_QUEUE_UK_URL = https://sqs.eu-west-1.amazonaws.com/416818527652/sp-traffic-stream-uk-destination-queue
|
||||
## for EU - DE
|
||||
SQS_QUEUE_DE_URL = https://sqs.eu-west-1.amazonaws.com/416818527652/sp-traffic-stream-de-destination-queue
|
||||
## FOR EU - FR
|
||||
SQS_QUEUE_FR_URL = https://sqs.eu-west-1.amazonaws.com/416818527652/sp-traffic-stream-fr-destination-queue
|
||||
## FOR EU - IT
|
||||
SQS_QUEUE_IT_URL = https://sqs.eu-west-1.amazonaws.com/416818527652/sp-traffic-stream-it-destination-queue
|
||||
## FOR EU - ES
|
||||
SQS_QUEUE_ES_URL = https://sqs.eu-west-1.amazonaws.com/416818527652/sp-traffic-stream-es-destination-queue
|
||||
|
||||
## REGIONS
|
||||
AWS_REGION_US_EAST = us-east-1
|
||||
AWS_REGION_EU_WEST = eu-west-1
|
||||
|
||||
|
||||
## DB CREDENTIALS
|
||||
DB_HOST = utopia-2.c5qech8o9lgg.us-east-1.rds.amazonaws.com
|
||||
DB_USER = stream-user
|
||||
DB_PASSWORD = Utopia01
|
||||
DB_NAME = ads_stream_api
|
||||
|
||||
## AWS keys
|
||||
AWS_ACCESS_KEY = AKIAWCDCR7WSP2F3Y4WJ
|
||||
AWS_SECRET_KEY = a2FKayDTYWUNjOC316n8yM+PxGZP5tAFYL8NvgMz
|
|
@ -0,0 +1,46 @@
|
|||
# Node.js
|
||||
node_modules/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Logs
|
||||
logs/
|
||||
*.log
|
||||
|
||||
# OS-specific
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Build artifacts
|
||||
dist/
|
||||
build/
|
||||
out/
|
||||
coverage/
|
||||
.next/
|
||||
.vercel/
|
||||
.cache/
|
||||
|
||||
# Testing
|
||||
coverage/
|
||||
jest-coverage/
|
||||
nyc_output/
|
||||
|
||||
# IDE/editor
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
|
||||
# TypeScript
|
||||
*.tsbuildinfo
|
||||
|
||||
# PM2 logs
|
||||
pids/
|
||||
*.pid
|
||||
*.seed
|
||||
|
||||
# Optional: Ignore local database or secrets
|
||||
*.sqlite
|
||||
*.db
|
||||
*.pem
|
|
@ -0,0 +1,122 @@
|
|||
{
|
||||
"APJ6JRA9NG5V4": {
|
||||
"profileId": 4253617697009484,
|
||||
"countryCode": "IT",
|
||||
"currencyCode": "EUR",
|
||||
"timezone": "Europe/Paris",
|
||||
"timezoneUTC": "UTC+02:00",
|
||||
"marketplace": "AMAZON_IT"
|
||||
},
|
||||
"A28R8C7NBKEWEA": {
|
||||
"profileId": 1293255248900780,
|
||||
"countryCode": "IE",
|
||||
"currencyCode": "EUR",
|
||||
"timezone": "Europe/Dublin",
|
||||
"timezoneUTC": "UTC+01:00",
|
||||
"marketplace": "AMAZON_IE"
|
||||
},
|
||||
"A2NODRKZP88ZB9": {
|
||||
"profileId": 3880305188764010,
|
||||
"countryCode": "SE",
|
||||
"currencyCode": "SEK",
|
||||
"timezone": "Europe/Stockholm",
|
||||
"timezoneUTC": "UTC+02:00",
|
||||
"marketplace": "AMAZON_SE"
|
||||
},
|
||||
"A1C3SOZRARQ6R3": {
|
||||
"profileId": 3901843410471762,
|
||||
"countryCode": "PL",
|
||||
"currencyCode": "PLN",
|
||||
"timezone": "Europe/Warsaw",
|
||||
"timezoneUTC": "UTC+02:00",
|
||||
"marketplace": "AMAZON_PL"
|
||||
},
|
||||
"A13V1IB3VIYZZH": {
|
||||
"profileId": 2247462980733839,
|
||||
"countryCode": "FR",
|
||||
"currencyCode": "EUR",
|
||||
"timezone": "Europe/Paris",
|
||||
"timezoneUTC": "UTC+02:00",
|
||||
"marketplace": "AMAZON_FR"
|
||||
},
|
||||
"A33AVAJ2PDY3EV": {
|
||||
"profileId": 3109287371398194,
|
||||
"countryCode": "TR",
|
||||
"currencyCode": "TRY",
|
||||
"timezone": "Europe/Istanbul",
|
||||
"timezoneUTC": "UTC+03:00",
|
||||
"marketplace": "AMAZON_TR"
|
||||
},
|
||||
"AMEN7PMS3EDWL": {
|
||||
"profileId": 1633590029473539,
|
||||
"countryCode": "BE",
|
||||
"currencyCode": "EUR",
|
||||
"timezone": "Europe/Brussels",
|
||||
"timezoneUTC": "UTC+02:00",
|
||||
"marketplace": "AMAZON_BE"
|
||||
},
|
||||
"A1805IZSGTT6HS": {
|
||||
"profileId": 2173727205472935,
|
||||
"countryCode": "NL",
|
||||
"currencyCode": "EUR",
|
||||
"timezone": "Europe/Amsterdam",
|
||||
"timezoneUTC": "UTC+02:00",
|
||||
"marketplace": "AMAZON_NL"
|
||||
},
|
||||
"A1RKKUPIHCS9HS": {
|
||||
"profileId": 1741306081337515,
|
||||
"countryCode": "ES",
|
||||
"currencyCode": "EUR",
|
||||
"timezone": "Europe/Paris",
|
||||
"timezoneUTC": "UTC+02:00",
|
||||
"marketplace": "AMAZON_ES"
|
||||
},
|
||||
"A1PA6795UKMFR9": {
|
||||
"profileId": 1717389288563376,
|
||||
"countryCode": "DE",
|
||||
"currencyCode": "EUR",
|
||||
"timezone": "Europe/Paris",
|
||||
"timezoneUTC": "UTC+02:00",
|
||||
"marketplace": "AMAZON_DE"
|
||||
},
|
||||
"A1F83G8C2ARO7P": {
|
||||
"profileId": 3391272133480174,
|
||||
"countryCode": "UK",
|
||||
"currencyCode": "GBP",
|
||||
"timezone": "Europe/London",
|
||||
"timezoneUTC": "UTC+01:00",
|
||||
"marketplace": "AMAZON_UK"
|
||||
},
|
||||
"A2Q3Y263D00KWC": {
|
||||
"profileId": 4134162108117933,
|
||||
"countryCode": "BR",
|
||||
"currencyCode": "BRL",
|
||||
"timezone": "America/Sao_Paulo",
|
||||
"timezoneUTC": "UTC-03:00",
|
||||
"marketplace": "AMAZON_BR"
|
||||
},
|
||||
"A2EUQ1WTGCTBG2": {
|
||||
"profileId": 3047619299309873,
|
||||
"countryCode": "CA",
|
||||
"currencyCode": "CAD",
|
||||
"timezone": "America/Los_Angeles",
|
||||
"timezoneUTC": "UTC-07:00",
|
||||
"marketplace": "AMAZON_CA"
|
||||
},
|
||||
"A1AM78C64UM0Y8": {
|
||||
"profileId": "2042812141248173",
|
||||
"countryCode": "MX",
|
||||
"currencyCode": "MXN",
|
||||
"timezone": "America/Los_Angeles",
|
||||
"timezoneUTC": "UTC-07:00",
|
||||
"marketplace": "AMAZON_MX"
|
||||
},
|
||||
"ATVPDKIKX0DER": {
|
||||
"profileId": "109435392217298",
|
||||
"countryCode": "US",
|
||||
"currencyCode": "USD",
|
||||
"timezone": "America/Los_Angeles",
|
||||
"timezoneUTC": "UTC-07:00",
|
||||
"marketplace": "AMAZON_USA"
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "amz-marketing-script",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sqs": "^3.840.0",
|
||||
"dotenv": "^17.0.0",
|
||||
"luxon": "^3.6.1",
|
||||
"mysql2": "^3.14.1"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,237 @@
|
|||
require("dotenv").config();
|
||||
const fs = require("fs");
|
||||
const { DateTime } = require("luxon");
|
||||
|
||||
const {
|
||||
SQSClient,
|
||||
ReceiveMessageCommand,
|
||||
DeleteMessageBatchCommand,
|
||||
} = require("@aws-sdk/client-sqs");
|
||||
|
||||
const mysql = require("mysql2/promise");
|
||||
const { off } = require("process");
|
||||
|
||||
const raw = fs.readFileSync("./marketplaces.json", "utf8");
|
||||
const marketplaces = JSON.parse(raw);
|
||||
|
||||
const pool = mysql.createPool({
|
||||
host: process.env.DB_HOST,
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_NAME,
|
||||
waitForConnections: true,
|
||||
connectionLimit: 10,
|
||||
});
|
||||
|
||||
// All 7 queues with their regions
|
||||
const QUEUES = [
|
||||
{ url: process.env.SQS_QUEUE_US_URL, region: process.env.AWS_REGION_US_EAST },
|
||||
{ url: process.env.SQS_QUEUE_CA_URL, region: process.env.AWS_REGION_US_EAST },
|
||||
{ url: process.env.SQS_QUEUE_UK_URL, region: process.env.AWS_REGION_EU_WEST },
|
||||
{ url: process.env.SQS_QUEUE_DE_URL, region: process.env.AWS_REGION_EU_WEST },
|
||||
{ url: process.env.SQS_QUEUE_FR_URL, region: process.env.AWS_REGION_EU_WEST },
|
||||
{ url: process.env.SQS_QUEUE_IT_URL, region: process.env.AWS_REGION_EU_WEST },
|
||||
{ url: process.env.SQS_QUEUE_ES_URL, region: process.env.AWS_REGION_EU_WEST },
|
||||
];
|
||||
|
||||
const clients = {};
|
||||
|
||||
function getClient(region) {
|
||||
if (!clients[region]) {
|
||||
clients[region] = new SQSClient({
|
||||
region,
|
||||
accessKeyId: process.env.AWS_ACCESS_KEY,
|
||||
secretAccessKey: process.env.AWS_SECRET_KEY,
|
||||
});
|
||||
}
|
||||
return clients[region];
|
||||
}
|
||||
|
||||
function parseMessage(jsonStr) {
|
||||
const data = JSON.parse(jsonStr);
|
||||
return {
|
||||
idempotency_id: data.idempotency_id,
|
||||
dataset_id: data.dataset_id,
|
||||
marketplace_id: data.marketplace_id,
|
||||
marketplace: getMarketplaceName(data.marketplace_id),
|
||||
currency: data.currency,
|
||||
advertiser_id: data.advertiser_id,
|
||||
campaign_id: data.campaign_id,
|
||||
ad_group_id: data.ad_group_id,
|
||||
ad_id: data.ad_id,
|
||||
keyword_id: data.keyword_id,
|
||||
keyword_text: data.keyword_text,
|
||||
match_type: data.match_type,
|
||||
placement: data.placement,
|
||||
time_window_start_original: data.time_window_start,
|
||||
time_window_start: formatToMysqlDatetime(data.time_window_start),
|
||||
utc_date_time: getUtcDateTime(data.time_window_start, data.marketplace_id),
|
||||
clicks: Number(data.clicks || 0),
|
||||
impressions: Number(data.impressions || 0),
|
||||
cost: Number(data.cost || 0),
|
||||
};
|
||||
}
|
||||
|
||||
function getMarketplaceName(marketplaceId) {
|
||||
const map = marketplaces[marketplaceId];
|
||||
return map?.marketplace;
|
||||
}
|
||||
|
||||
function offsetToMinutes(offsetStr) {
|
||||
const match = offsetStr.match(/^UTC([+-])(\d{2}):(\d{2})$/);
|
||||
if (!match) throw new Error(`Invalid offset format: ${offsetStr}`);
|
||||
|
||||
const [, sign, hours, minutes] = match;
|
||||
const total = parseInt(hours) * 60 + parseInt(minutes);
|
||||
return sign === "+" ? total : -total;
|
||||
}
|
||||
|
||||
function convertToUtcFromOffset(localIsoString, offsetStr) {
|
||||
const local = new Date(localIsoString);
|
||||
const offsetMinutes = offsetToMinutes(offsetStr);
|
||||
const utcDate = new Date(local.getTime() - offsetMinutes * 60 * 1000);
|
||||
|
||||
const pad = (n) => String(n).padStart(2, "0");
|
||||
return (
|
||||
`${utcDate.getUTCFullYear()}-${pad(utcDate.getUTCMonth() + 1)}-${pad(
|
||||
utcDate.getUTCDate()
|
||||
)} ` +
|
||||
`${pad(utcDate.getUTCHours())}:${pad(utcDate.getUTCMinutes())}:${pad(
|
||||
utcDate.getUTCSeconds()
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
function formatToMysqlDatetime(dateTime, marketplace_id) {
|
||||
const timezone = marketplaces[marketplace_id]?.timezone;
|
||||
const local = DateTime.fromISO(dateTime, { zone: timezone }).toUTC();
|
||||
return local.toFormat("yyyy-MM-dd HH:mm:ss");
|
||||
}
|
||||
|
||||
function getUtcDateTime(startDateTime, marketplaceId) {
|
||||
const offset = marketplaces[marketplaceId]?.timezoneUTC;
|
||||
const utcTime = convertToUtcFromOffset(startDateTime, offset);
|
||||
return utcTime;
|
||||
}
|
||||
|
||||
async function saveToDbBulk(traffics) {
|
||||
if (traffics.length === 0) return true;
|
||||
|
||||
const conn = await pool.getConnection();
|
||||
try {
|
||||
const sql = `
|
||||
REPLACE INTO sponsored_product_traffic (
|
||||
idempotency_id, dataset_id, marketplace_id, marketplace, currency, advertiser_id,
|
||||
campaign_id, ad_group_id, ad_id, keyword_id, keyword_text, match_type, placement,
|
||||
time_window_start, utc_date_time, clicks, impressions, cost
|
||||
) VALUES ?
|
||||
`;
|
||||
|
||||
const values = traffics.map((t) => [
|
||||
t.idempotency_id,
|
||||
t.dataset_id,
|
||||
t.marketplace_id,
|
||||
t.marketplace,
|
||||
t.currency,
|
||||
t.advertiser_id,
|
||||
t.campaign_id,
|
||||
t.ad_group_id,
|
||||
t.ad_id,
|
||||
t.keyword_id,
|
||||
t.keyword_text,
|
||||
t.match_type,
|
||||
t.placement,
|
||||
t.time_window_start,
|
||||
t.utc_date_time,
|
||||
t.clicks,
|
||||
t.impressions,
|
||||
t.cost,
|
||||
]);
|
||||
|
||||
await conn.query(sql, [values]);
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error("DB Bulk Error:", err);
|
||||
return false;
|
||||
} finally {
|
||||
conn.release();
|
||||
}
|
||||
}
|
||||
|
||||
async function processQueue(queue) {
|
||||
const client = getClient(queue.region);
|
||||
const BATCHES = 50;
|
||||
const traffics = [];
|
||||
const deleteEntries = [];
|
||||
|
||||
for (let i = 0; i < BATCHES; i++) {
|
||||
const command = new ReceiveMessageCommand({
|
||||
QueueUrl: queue.url,
|
||||
MaxNumberOfMessages: 10,
|
||||
WaitTimeSeconds: 10,
|
||||
});
|
||||
|
||||
const response = await client.send(command);
|
||||
const messages = response.Messages || [];
|
||||
|
||||
if (messages.length === 0) {
|
||||
`No Messages in Queue ${queue.url}`;
|
||||
break;
|
||||
}
|
||||
for (const msg of messages) {
|
||||
try {
|
||||
const traffic = parseMessage(msg.Body);
|
||||
traffics.push(traffic);
|
||||
deleteEntries.push({
|
||||
Id: msg.MessageId,
|
||||
ReceiptHandle: msg.ReceiptHandle,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(`Invalid message in ${queue.url}:`, err.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (traffics.length > 0) {
|
||||
const saved = await saveToDbBulk(traffics);
|
||||
if (saved && deleteEntries.length > 0) {
|
||||
for (let i = 0; i < deleteEntries.length; i += 10) {
|
||||
const batch = deleteEntries.slice(i, i + 10);
|
||||
await client.send(
|
||||
new DeleteMessageBatchCommand({
|
||||
QueueUrl: queue.url,
|
||||
Entries: batch,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[${queue.region}] ${queue.url} -> processed ${traffics.length} messages`
|
||||
);
|
||||
}
|
||||
|
||||
// async function main() {
|
||||
// // Parallel processing of all queues
|
||||
// await Promise.all(QUEUES.map(processQueue));
|
||||
// }
|
||||
|
||||
// main().catch(console.error);
|
||||
|
||||
async function main() {
|
||||
await Promise.all(QUEUES.map(processQueue));
|
||||
}
|
||||
|
||||
async function runLoop() {
|
||||
try {
|
||||
await main(); // wait for current execution to finish
|
||||
} catch (err) {
|
||||
console.error('Error in main():', err);
|
||||
}
|
||||
|
||||
// Wait 5 seconds before running again
|
||||
setTimeout(runLoop, 5000);
|
||||
}
|
||||
|
||||
runLoop();
|
Loading…
Reference in New Issue