From 564161f20d5aea7982f9010463df536106b6e0e6 Mon Sep 17 00:00:00 2001 From: Hoang Lam Date: Tue, 9 Apr 2019 18:43:43 +0700 Subject: [PATCH 01/12] feat(orders): add order service and create report excel file --- .gitignore | 5 + src/index.ts | 62 +++++++---- src/services/index.ts | 3 +- src/services/order.ts | 221 ++++++++++++++++++++++++++-------------- src/services/variant.ts | 48 ++++++--- src/utils/xlsx.ts | 35 +++++-- 6 files changed, 255 insertions(+), 119 deletions(-) diff --git a/.gitignore b/.gitignore index deed335..6b42bd6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ node_modules/ dist/ .env +server.js +docker-compose.yml +src/exportData/* +exports/ +orders.json diff --git a/src/index.ts b/src/index.ts index cbf9f1d..aad4c35 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,25 +1,47 @@ -import {processData} from "./services"; import fs from "fs"; import path from "path"; -import {createXlSXfile} from "./utils/xlsx"; - -if (process.env.NODE_ENV === 'development') { - require('dotenv').config() +import moment from "moment"; +import { + generateXLSXOrderData, + fetchOrderDataRecursion +} from "./services/order"; +import { createXlSXfile } from "./utils/xlsx"; + +if (process.env.NODE_ENV === "development") { + require("dotenv").config(); } -(async() => { - const dir = path.join(__dirname, `/exportData/`) - - if (!fs.existsSync(dir)) { - await fs.mkdirSync(dir, {recursive: false}) - } - - const data = await processData(); - createXlSXfile({data, dirName: 'src/exportData'}) +const DIR_NAME = process.env.DIR_NAME || "exports"; + +(async () => { + console.log("\n\n\n"); + if (!fs.existsSync(DIR_NAME)) { + await fs.mkdirSync(DIR_NAME, { recursive: false }); + } + + const since = moment() + .add(-10, "days") + .startOf("day") + .toDate(); + + const until = moment() + .add(-1, "days") + .endOf("day") + .toDate(); + + const orderData = await fetchOrderDataRecursion({ + since, + until + }); + + const completedOrders = orderData.completedOrders; + const data = await generateXLSXOrderData(completedOrders); + const orderDate = moment().add(-1, "days"); + createXlSXfile({ + data, + fileName: `order-${orderDate.format("YYYY-MM-DD")}`, + title: `Danh sách đơn hàng hoàn thành ngày ${orderDate.format( + "DD-MM-YYYY" + )}` + }); })(); - - - - - - diff --git a/src/services/index.ts b/src/services/index.ts index f014c92..30feb79 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -1,2 +1,3 @@ -export {processData} from './order' +export {} from './order' export {getVariantByIds} from './variant' +import { processingOrderData } from "./order"; diff --git a/src/services/order.ts b/src/services/order.ts index 7ceae0f..036131b 100644 --- a/src/services/order.ts +++ b/src/services/order.ts @@ -1,86 +1,157 @@ -import * as dotenv from 'dotenv' import axios from "axios"; -import moment from 'moment'; -import lodash from 'lodash'; -import path from "path"; +import moment from "moment"; +import lodash from "lodash"; +import { getVariantByIds } from "./variant"; import * as xlsx from "node-xlsx"; -import fs from "fs"; -import {config} from "dotenv"; -import {getVariantByIds} from "./variant"; - - -const xlsxHeader = ['Phieu Xuat', 'Ngay', 'Ma Khach Hang', 'Ten Khach Hang', 'Ma Hang', 'Ten Hang', 'So Luong', 'Don Gia', 'Trang Thai']; - -const since = moment(new Date('04/01/2019').toISOString()).startOf('day').toDate() -const util = moment().add(-1, 'days').endOf('day').toDate() - -console.log(util) - -const date = moment().subtract(1, 'days').format('DD-MM-YYYY').toString() - -// let dirWriteFile = `${__dirname}/exportData/Order-Report-` + date + `.xlsx` -let dirWriteFile = path.resolve('src/exportData/', 'abc.xlsx') -if (process.env.NODE_ENV === 'development') { - require('dotenv').config() +import fs, { watchFile } from "fs"; +import path from "path"; +const ORDER_REPORT_HEADERS = [ + "Mã đơn hàng", + "Ngày tạo", + "Ngày hoàn thành", + "SĐT khách hàng", + "Tên khách hàng", + "SKU", + "Tên sản phẩm", + "Số lượng", + "Đơn giá", + "Trạng thái" +]; + +if (process.env.NODE_ENV === "development") { + require("dotenv").config(); } const request = axios.create({ - baseURL: 'https://api.storelammoc.vn/orders', - timeout: 15e3, - headers: {'x-access-token': process.env.TOKEN_KEY}, + baseURL: "https://api.storelammoc.vn/orders", + timeout: 15e3, + headers: { "x-access-token": process.env.TOKEN_KEY } }); -export async function getData(options: { since: Date, util: Date, limit?: number }) { - try { - const response = await request.get(`/customerAgencies/agencies/57e7af98b52ee4d36dc6c7c8`, { - params: { - apiVersion: 2, - ...options - } - }) - return response.data - } catch (error) { - throw error - } - -} - -export async function processData() { - const a = await getData({since, limit: 10000, util}) - let rawData = a.customerAgencyOrders; - let data = [] - - let variantIds = [] - for (let z in rawData) { - for (let y in rawData[z].items) { - let item = rawData[z].items[y]; - variantIds.push(item.variant.id) +export async function fetchOrders(options: { + since: Date; + until: Date; + limit?: number; +}): Promise<{ hasMore: boolean; customerAgencyOrders: any[] }> { + try { + const response = await request.get( + `/customerAgencies/agencies/57e7af98b52ee4d36dc6c7c8`, + { + params: { + apiVersion: 2, + ...options } - } - - const variants = await getVariantByIds(variantIds); - const variantEntities = lodash.keyBy(variants, '_id') - - for (let i in rawData) { - let order = rawData[i] - for (let x in rawData[i].items) { - let itemData = [] - let item = rawData[i].items[x]; - let productLink = 'https://storelammoc.vn/products/' + item.product.id + '?variantId=' + item.variant.id - let itemName = item.product.name + ' ( ' + item.variant.name + ' )' - let orderCreatedAt = moment(new Date(order.createdAt)).format('DD-MM-YYYY HH:mm') - itemData.push('CO' + order._id, orderCreatedAt, order.customerInfo.phone, order.customerInfo.name, lodash.get(variantEntities, `${item.variant.id}.sku`), itemName, item.quantity, item.price, order.status,) - data.push(itemData) - } - } - - data.unshift(xlsxHeader) - // console.log(JSON.stringify(rawData[0], null, '\t')) + } + ); - return data + return response.data; + } catch (error) { + throw error; + } } - -//check hasmore = true ? loop -// check exist file in index ( when start up) -//get orders, process orders => array data excell, exportxlsx \ No newline at end of file +export const generateXLSXOrderData = async (orders: any[]) => { + const variantIds = lodash.flatten( + orders.map(order => { + return order.items.map((item: any) => item.variant.id); + }) + ); + + const variants = await getVariantByIds(variantIds); + const variantEntities = lodash.keyBy(variants, "_id"); + let processedOrders = lodash.flatten( + orders.map(order => { + let orderCreatedAt = moment(new Date(order.createdAt)).format( + "DD-MM-YYYY HH:mm" + ); + let orderUpdatedAt = moment(new Date(order.updatedAt)).format( + "DD-MM-YYYY HH:mm" + ); + const orderDetails = [ + `CO${order.id}`, + orderCreatedAt, + orderUpdatedAt, + order.customerInfo.phone, + order.customerInfo.name + ]; + + const itemsDetails = (order.items || []).map((item: any) => { + const sku = lodash.get( + variantEntities, + `${lodash.get(item, "variant.id")}.sku` + ); + const name = + lodash.get(item, "product.name") + + " (" + + lodash.get(item, "variant.name") + + ")"; + return { + sku, + name, + quantity: item.quantity, + price: item.price + }; + }); + + return itemsDetails.map((item: any) => [ + ...orderDetails, + item.sku, + item.name, + item.quantity, + item.price, + order.status + ]); + }) + ); + processedOrders.unshift(ORDER_REPORT_HEADERS); + + return processedOrders; +}; + +export const fetchOrderDataRecursion = async ({ + prevOrders = [], + since, + until, + limit = 20 +}: { + prevOrders?: any[]; + since: Date; + until: Date; + limit?: number; +}): Promise<{ completedOrders: any[] }> => { + const data = await fetchOrders({ since, limit, until }); + + const { hasMore = false, customerAgencyOrders = [] } = data || {}; + + if (hasMore === true) { + const lastUntil = lodash.last(customerAgencyOrders).createdAt; + + return await fetchOrderDataRecursion({ + prevOrders: prevOrders.concat(customerAgencyOrders), + until: lastUntil, + since + }); + } + + const completedOrders = prevOrders.filter(prevOrder => { + if ( + prevOrder.status === "COMPLETED" && + moment(prevOrder.updatedAt).format("DD-MM-YYYY") === + moment() + .add(-1, "days") + .format("DD-MM-YYYY") + ) + return true; + else return false; + }); + + return { + completedOrders + }; +}; + +export const filterOrderData = async ({ + data: [] +}: { + data: []; +}): Promise => {}; diff --git a/src/services/variant.ts b/src/services/variant.ts index cf3a05f..a48ebff 100644 --- a/src/services/variant.ts +++ b/src/services/variant.ts @@ -1,24 +1,42 @@ -import axios from 'axios' -if (process.env.NODE_ENV === 'development') { - require('dotenv').config() -} +import axios from "axios"; +import lodash from "lodash"; +if (process.env.NODE_ENV === "development") { + require("dotenv").config(); +} const request = axios.create({ - baseURL: 'https://api.storelammoc.vn', - timeout: 15e3, - headers: {'x-access-token': process.env.TOKEN_KEY}, + baseURL: "https://api.storelammoc.vn", + timeout: 15e3, + headers: { "x-access-token": process.env.TOKEN_KEY } }); +export async function getVariantByIds(ids: string[]) { + try { + let variants = []; + const length = ids.length; + let loopIdx = 0; + const maxPerProcess = 100; + const loopCount = Math.round(length / maxPerProcess + 0.49); + while (loopIdx < loopCount) { + const processingIds = ids.slice( + loopIdx * maxPerProcess, + (loopIdx + 1) * maxPerProcess + ); -export async function getVariantByIds(ids: string[]) { - try { - const response = await request.post(`/v1/variants`, { - variants: ids - }) - return response.data - } catch (e) { - throw e + const response = await request.post(`/v1/variants`, { + variants: processingIds + }); + variants.push(response.data); + loopIdx++; } + // console.log(variants) + return lodash.flatten(variants); + } catch (e) { + throw e; + } } + +// +// diff --git a/src/utils/xlsx.ts b/src/utils/xlsx.ts index e77a3d7..4f00b20 100644 --- a/src/utils/xlsx.ts +++ b/src/utils/xlsx.ts @@ -1,11 +1,30 @@ -import moment from 'moment'; -import path from 'path' +import path from "path"; import * as xlsx from "node-xlsx"; import fs from "fs"; -export function createXlSXfile(option: { data: any[][], dirName: string }) { - const date = moment().add(-1, 'days').format('DD-MM-YYYY').toString() - var buffer = xlsx.build([{data: option.data, name: 'Report ' + date}]) - fs.writeFileSync(path.resolve(option.dirName, 'abc.xlsx'), buffer, {flag: 'w+'}) - console.log('Export XLSX Done !') -} \ No newline at end of file +if (process.env.NODE_ENV === "development") { + require("dotenv").config(); +} + +const DIR_NAME = process.env.DIR_NAME || "exports"; + +export function createXlSXfile(option: { + data: any[][]; + fileName: string; + title: string; +}) { + let data = option.data; + data.unshift([option.title.toUpperCase()], []); + const range = { s: { c: 0, r: 0 }, e: { c: data[3].length - 1, r: 0 } }; + const XLSXOption = { "!merges": [range] }; + + const buffer = xlsx.build( + [{ data: option.data, name: "Report " }], + XLSXOption + ); + + fs.writeFileSync(path.resolve(DIR_NAME, `${option.fileName}.xlsx`), buffer, { + flag: "w+" + }); + console.log("Export XLSX Done !"); +} From b125a4ed42aaec22c631123be16938c55c954957 Mon Sep 17 00:00:00 2001 From: Hoang Lam Date: Wed, 10 Apr 2019 10:10:59 +0700 Subject: [PATCH 02/12] fixbug(orders): update variable name, function name --- .env.example | 2 + .prettierrc | 9 ++ package-lock.json | 1 + package.json | 2 +- src/index.ts | 61 ++++++------- src/services/index.ts | 3 - src/services/order.ts | 185 ++++++++++++++++++---------------------- src/services/variant.ts | 43 +++++----- src/utils/xlsx.ts | 39 ++++----- 9 files changed, 159 insertions(+), 186 deletions(-) create mode 100644 .env.example create mode 100644 .prettierrc delete mode 100644 src/services/index.ts diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..98724db --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +TOKEN_KEY= +DIR_NAME= \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..17f669f --- /dev/null +++ b/.prettierrc @@ -0,0 +1,9 @@ +{ + "printWidth": 100, + "tabWidth": 2, + "trailingComma": "all", + "semi": false, + "singleQuote": true, + "endOfLine": "lf", + "arrowParens": "always" +} diff --git a/package-lock.json b/package-lock.json index cc4f4ba..9301ab5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -488,6 +488,7 @@ "version": "6.1.1", "resolved": "https://registry.npmjs.org/@types/dotenv/-/dotenv-6.1.1.tgz", "integrity": "sha512-ftQl3DtBvqHl9L16tpqqzA4YzCSXZfi7g8cQceTz5rOlYtk/IZbFjAv3mLOQlNIgOaylCQWQoBdDQHPgEBJPHg==", + "dev": true, "requires": { "@types/node": "*" } diff --git a/package.json b/package.json index 8136495..e66cfa7 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,6 @@ "author": "", "license": "ISC", "dependencies": { - "@types/dotenv": "^6.1.1", "@types/express": "^4.16.1", "@types/node-xlsx": "^0.12.1", "axios": "^0.18.0", @@ -22,6 +21,7 @@ "object-assign": "^4.1.1" }, "devDependencies": { + "@types/dotenv": "^6.1.1", "@types/jest": "24.0.11", "@types/lodash": "4.14.123", "dotenv": "7.0.0", diff --git a/src/index.ts b/src/index.ts index aad4c35..143c92b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,47 +1,40 @@ -import fs from "fs"; -import path from "path"; -import moment from "moment"; -import { - generateXLSXOrderData, - fetchOrderDataRecursion -} from "./services/order"; -import { createXlSXfile } from "./utils/xlsx"; +import fs from 'fs' +import moment from 'moment' +import { generateXLSXOrderData, fetchOrdersSequential } from './services/order' +import { createXlSXfile } from './utils/xlsx' -if (process.env.NODE_ENV === "development") { - require("dotenv").config(); +if (process.env.NODE_ENV === 'development') { + require('dotenv').config() } -const DIR_NAME = process.env.DIR_NAME || "exports"; - -(async () => { - console.log("\n\n\n"); - if (!fs.existsSync(DIR_NAME)) { - await fs.mkdirSync(DIR_NAME, { recursive: false }); +const EXPORTS_PATH = process.env.EXPORTS_PATH || 'exports' +;(async () => { + console.log('\n\n\n') + if (!fs.existsSync(EXPORTS_PATH )) { + await fs.mkdirSync(EXPORTS_PATH , { recursive: false }) } const since = moment() - .add(-10, "days") - .startOf("day") - .toDate(); + .add(-10, 'days') + .startOf('day') + .toDate() const until = moment() - .add(-1, "days") - .endOf("day") - .toDate(); + .add(-1, 'days') + .endOf('day') + .toDate() - const orderData = await fetchOrderDataRecursion({ + const orderData = await fetchOrdersSequential({ since, - until - }); + until, + }) - const completedOrders = orderData.completedOrders; - const data = await generateXLSXOrderData(completedOrders); - const orderDate = moment().add(-1, "days"); + const completedOrders = orderData.completedOrders + const data = await generateXLSXOrderData(completedOrders) + const orderDate = moment().add(-1, 'days') createXlSXfile({ data, - fileName: `order-${orderDate.format("YYYY-MM-DD")}`, - title: `Danh sách đơn hàng hoàn thành ngày ${orderDate.format( - "DD-MM-YYYY" - )}` - }); -})(); + fileName: `order-${orderDate.format('YYYY-MM-DD')}`, + title: `Danh sách đơn hàng hoàn thành ngày ${orderDate.format('DD-MM-YYYY')}`, + }) +})() diff --git a/src/services/index.ts b/src/services/index.ts deleted file mode 100644 index 30feb79..0000000 --- a/src/services/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export {} from './order' -export {getVariantByIds} from './variant' -import { processingOrderData } from "./order"; diff --git a/src/services/order.ts b/src/services/order.ts index 036131b..db8a8d8 100644 --- a/src/services/order.ts +++ b/src/services/order.ts @@ -1,97 +1,82 @@ -import axios from "axios"; -import moment from "moment"; -import lodash from "lodash"; -import { getVariantByIds } from "./variant"; -import * as xlsx from "node-xlsx"; -import fs, { watchFile } from "fs"; -import path from "path"; -const ORDER_REPORT_HEADERS = [ - "Mã đơn hàng", - "Ngày tạo", - "Ngày hoàn thành", - "SĐT khách hàng", - "Tên khách hàng", - "SKU", - "Tên sản phẩm", - "Số lượng", - "Đơn giá", - "Trạng thái" -]; - -if (process.env.NODE_ENV === "development") { - require("dotenv").config(); +import axios from 'axios' +import moment from 'moment' +import lodash from 'lodash' +import { getVariantsByIds } from './variant' + +const ORDER_REPORT_COLUMNS = [ + 'Mã đơn hàng', + 'Ngày tạo', + 'Ngày hoàn thành', + 'SĐT khách hàng', + 'Tên khách hàng', + 'SKU', + 'Tên sản phẩm', + 'Số lượng', + 'Đơn giá', + 'Trạng thái', +] + +if (process.env.NODE_ENV === 'development') { + require('dotenv').config() } const request = axios.create({ - baseURL: "https://api.storelammoc.vn/orders", + baseURL: 'https://api.storelammoc.vn/orders', timeout: 15e3, - headers: { "x-access-token": process.env.TOKEN_KEY } -}); + headers: { 'x-access-token': process.env.TOKEN_KEY }, +}) export async function fetchOrders(options: { - since: Date; - until: Date; - limit?: number; + since: Date + until: Date + limit?: number }): Promise<{ hasMore: boolean; customerAgencyOrders: any[] }> { try { - const response = await request.get( - `/customerAgencies/agencies/57e7af98b52ee4d36dc6c7c8`, - { - params: { - apiVersion: 2, - ...options - } - } - ); + const response = await request.get(`/customerAgencies/agencies/57e7af98b52ee4d36dc6c7c8`, { + params: { + apiVersion: 2, + ...options, + }, + }) - return response.data; + return response.data } catch (error) { - throw error; + throw error } } export const generateXLSXOrderData = async (orders: any[]) => { const variantIds = lodash.flatten( - orders.map(order => { - return order.items.map((item: any) => item.variant.id); - }) - ); - - const variants = await getVariantByIds(variantIds); - const variantEntities = lodash.keyBy(variants, "_id"); - let processedOrders = lodash.flatten( - orders.map(order => { - let orderCreatedAt = moment(new Date(order.createdAt)).format( - "DD-MM-YYYY HH:mm" - ); - let orderUpdatedAt = moment(new Date(order.updatedAt)).format( - "DD-MM-YYYY HH:mm" - ); + orders.map((order) => { + return order.items.map((item: any) => item.variant.id) + }), + ) + + const variants = await getVariantsByIds(variantIds) + const variantEntities = lodash.keyBy(variants, '_id') + const processedOrders = lodash.flatten( + orders.map((order) => { + let orderCreatedAt = moment(new Date(order.createdAt)).format('DD-MM-YYYY HH:mm') + let orderUpdatedAt = moment(new Date(order.updatedAt)).format('DD-MM-YYYY HH:mm') const orderDetails = [ `CO${order.id}`, orderCreatedAt, orderUpdatedAt, order.customerInfo.phone, - order.customerInfo.name - ]; + order.customerInfo.name, + ] const itemsDetails = (order.items || []).map((item: any) => { - const sku = lodash.get( - variantEntities, - `${lodash.get(item, "variant.id")}.sku` - ); + const sku = lodash.get(variantEntities, `${lodash.get(item, 'variant.id')}.sku`) const name = - lodash.get(item, "product.name") + - " (" + - lodash.get(item, "variant.name") + - ")"; + lodash.get(item, 'product.name') + ' (' + lodash.get(item, 'variant.name') + ')' return { sku, name, quantity: item.quantity, - price: item.price - }; - }); + price: item.price, + } + }) return itemsDetails.map((item: any) => [ ...orderDetails, @@ -99,59 +84,55 @@ export const generateXLSXOrderData = async (orders: any[]) => { item.name, item.quantity, item.price, - order.status - ]); - }) - ); - processedOrders.unshift(ORDER_REPORT_HEADERS); + order.status, + ]) + }), + ) + processedOrders.unshift(ORDER_REPORT_COLUMNS) - return processedOrders; -}; + return processedOrders +} -export const fetchOrderDataRecursion = async ({ +export const fetchOrdersSequential = async ({ prevOrders = [], since, until, - limit = 20 + limit = 20, }: { - prevOrders?: any[]; - since: Date; - until: Date; - limit?: number; + prevOrders?: any[] + since: Date + until: Date + limit?: number }): Promise<{ completedOrders: any[] }> => { - const data = await fetchOrders({ since, limit, until }); + const data = await fetchOrders({ since, limit, until }) - const { hasMore = false, customerAgencyOrders = [] } = data || {}; + const { hasMore = false, customerAgencyOrders = [] } = data || {} if (hasMore === true) { - const lastUntil = lodash.last(customerAgencyOrders).createdAt; + const lastUntil = lodash.last(customerAgencyOrders).createdAt - return await fetchOrderDataRecursion({ + return await fetchOrdersSequential({ prevOrders: prevOrders.concat(customerAgencyOrders), until: lastUntil, - since - }); + since, + }) } - const completedOrders = prevOrders.filter(prevOrder => { + const completedOrders = prevOrders.filter((prevOrder) => { if ( - prevOrder.status === "COMPLETED" && - moment(prevOrder.updatedAt).format("DD-MM-YYYY") === + prevOrder.status === 'COMPLETED' && + moment(prevOrder.updatedAt).format('DD-MM-YYYY') === moment() - .add(-1, "days") - .format("DD-MM-YYYY") + .add(-1, 'days') + .format('DD-MM-YYYY') ) - return true; - else return false; - }); + return true + else return false + }) return { - completedOrders - }; -}; + completedOrders, + } +} -export const filterOrderData = async ({ - data: [] -}: { - data: []; -}): Promise => {}; +export const filterOrderData = async ({ data: [] }: { data: [] }): Promise => {} diff --git a/src/services/variant.ts b/src/services/variant.ts index a48ebff..de43b96 100644 --- a/src/services/variant.ts +++ b/src/services/variant.ts @@ -1,40 +1,37 @@ -import axios from "axios"; -import lodash from "lodash"; +import axios from 'axios' +import lodash from 'lodash' -if (process.env.NODE_ENV === "development") { - require("dotenv").config(); +if (process.env.NODE_ENV === 'development') { + require('dotenv').config() } const request = axios.create({ - baseURL: "https://api.storelammoc.vn", + baseURL: 'https://api.storelammoc.vn', timeout: 15e3, - headers: { "x-access-token": process.env.TOKEN_KEY } -}); + headers: { 'x-access-token': process.env.TOKEN_KEY }, +}) -export async function getVariantByIds(ids: string[]) { +export async function getVariantsByIds(ids: string[]) { try { - let variants = []; - const length = ids.length; - let loopIdx = 0; - const maxPerProcess = 100; - const loopCount = Math.round(length / maxPerProcess + 0.49); + let variants = [] + const length = ids.length + let loopIdx = 0 + const maxPerProcess = 100 + const loopCount = Math.round(length / maxPerProcess + 0.49) while (loopIdx < loopCount) { - const processingIds = ids.slice( - loopIdx * maxPerProcess, - (loopIdx + 1) * maxPerProcess - ); + const processingIds = ids.slice(loopIdx * maxPerProcess, (loopIdx + 1) * maxPerProcess) const response = await request.post(`/v1/variants`, { - variants: processingIds - }); - variants.push(response.data); - loopIdx++; + variants: processingIds, + }) + variants.push(response.data) + loopIdx++ } // console.log(variants) - return lodash.flatten(variants); + return lodash.flatten(variants) } catch (e) { - throw e; + throw e } } diff --git a/src/utils/xlsx.ts b/src/utils/xlsx.ts index 4f00b20..b1c2176 100644 --- a/src/utils/xlsx.ts +++ b/src/utils/xlsx.ts @@ -1,30 +1,23 @@ -import path from "path"; -import * as xlsx from "node-xlsx"; -import fs from "fs"; +import path from 'path' +import * as xlsx from 'node-xlsx' +import fs from 'fs' -if (process.env.NODE_ENV === "development") { - require("dotenv").config(); +if (process.env.NODE_ENV === 'development') { + require('dotenv').config() } -const DIR_NAME = process.env.DIR_NAME || "exports"; +const DIR_NAME = process.env.DIR_NAME || 'exports' -export function createXlSXfile(option: { - data: any[][]; - fileName: string; - title: string; -}) { - let data = option.data; - data.unshift([option.title.toUpperCase()], []); - const range = { s: { c: 0, r: 0 }, e: { c: data[3].length - 1, r: 0 } }; - const XLSXOption = { "!merges": [range] }; +export function createXlSXfile(options: { data: any[][]; fileName: string; title: string }) { + const data = options.data + data.unshift([options.title.toUpperCase()], []) + const range = { s: { c: 0, r: 0 }, e: { c: data[3].length - 1, r: 0 } } + const XLSXOption = { '!merges': [range] } - const buffer = xlsx.build( - [{ data: option.data, name: "Report " }], - XLSXOption - ); + const buffer = xlsx.build([{ data: options.data, name: 'Report ' }], XLSXOption) - fs.writeFileSync(path.resolve(DIR_NAME, `${option.fileName}.xlsx`), buffer, { - flag: "w+" - }); - console.log("Export XLSX Done !"); + fs.writeFileSync(path.resolve(DIR_NAME, `${options.fileName}.xlsx`), buffer, { + flag: 'w+', + }) + console.log('Export XLSX Done !') } From 4a147a1fa5df8c52873e7fb00e0801ae0530b497 Mon Sep 17 00:00:00 2001 From: Hoang Lam Date: Wed, 10 Apr 2019 10:19:00 +0700 Subject: [PATCH 03/12] fixbug(orders): remove server file config --- .gitignore | 1 - package-lock.json | 5 +++++ package.json | 1 + src/index.ts | 6 +++--- src/services/variant.ts | 3 --- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 6b42bd6..e42ff17 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ node_modules/ dist/ .env -server.js docker-compose.yml src/exportData/* exports/ diff --git a/package-lock.json b/package-lock.json index 9301ab5..b466cee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4617,6 +4617,11 @@ "xlsx": "^0.14.1" } }, + "nodemailer": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.1.0.tgz", + "integrity": "sha512-mzKGT5Q1PY84v6oRVjy88ymMDLUbPqvIr26n9Uy3j2nXzdhKWx1z4GLSHOyX8655zMkQng1MFR3lK+cE1egS6Q==" + }, "nodemon": { "version": "1.18.10", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.18.10.tgz", diff --git a/package.json b/package.json index e66cfa7..065b35b 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "lodash": "^4.17.11", "moment": "^2.24.0", "node-xlsx": "^0.14.1", + "nodemailer": "^6.1.0", "object-assign": "^4.1.1" }, "devDependencies": { diff --git a/src/index.ts b/src/index.ts index 143c92b..0c71a2c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,11 +7,11 @@ if (process.env.NODE_ENV === 'development') { require('dotenv').config() } -const EXPORTS_PATH = process.env.EXPORTS_PATH || 'exports' +const EXPORTS_PATH = process.env.EXPORTS_PATH || 'exports' ;(async () => { console.log('\n\n\n') - if (!fs.existsSync(EXPORTS_PATH )) { - await fs.mkdirSync(EXPORTS_PATH , { recursive: false }) + if (!fs.existsSync(EXPORTS_PATH)) { + await fs.mkdirSync(EXPORTS_PATH, { recursive: false }) } const since = moment() diff --git a/src/services/variant.ts b/src/services/variant.ts index de43b96..684d954 100644 --- a/src/services/variant.ts +++ b/src/services/variant.ts @@ -34,6 +34,3 @@ export async function getVariantsByIds(ids: string[]) { throw e } } - -// -// From 2d184d47b63ce89df4e55c1a0f859fcd72abf90d Mon Sep 17 00:00:00 2001 From: Hoang Lam Date: Thu, 11 Apr 2019 14:22:27 +0700 Subject: [PATCH 04/12] fixbug(orders): update variable name, and remove unnecessary file, content --- .env.example | 14 +++++++++++++- .gitignore | 1 - package-lock.json | 39 +++++++++++++++++++++++++++++++++++---- package.json | 11 ++++++++--- src/index.ts | 40 ---------------------------------------- src/services/order.ts | 4 ---- src/services/variant.ts | 4 ---- src/utils/xlsx.ts | 4 ++-- 8 files changed, 58 insertions(+), 59 deletions(-) delete mode 100644 src/index.ts diff --git a/.env.example b/.env.example index 98724db..f35b52c 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,14 @@ TOKEN_KEY= -DIR_NAME= \ No newline at end of file +EXPORTS_PATH= +MAIL_USER= +MAIL_CLIENT_ID= +MAIL_CLIENT_SECRET= +MAIL_REFRESH_TOKEN= +MAIL_ACCESS_TOKEN= +MAIL_EXPIRES= +DURATION_DAYS_GET_ORDERS= +MAIL_SEND_TO= +MAIL_SEND_FROM= +MAIL_SEND_CC= +MAIL_SEND_BCC= +MAIL_SUBJECT= \ No newline at end of file diff --git a/.gitignore b/.gitignore index e42ff17..4413610 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,5 @@ node_modules/ dist/ .env docker-compose.yml -src/exportData/* exports/ orders.json diff --git a/package-lock.json b/package-lock.json index b466cee..a12358e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -471,6 +471,7 @@ "version": "1.17.0", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.0.tgz", "integrity": "sha512-a2+YeUjPkztKJu5aIF2yArYFQQp8d51wZ7DavSHjFuY1mqVgidGyzEQ41JIVNy82fXj8yPgy2vJmfIywgESW6w==", + "dev": true, "requires": { "@types/connect": "*", "@types/node": "*" @@ -480,6 +481,7 @@ "version": "3.4.32", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.32.tgz", "integrity": "sha512-4r8qa0quOvh7lGD0pre62CAb1oni1OO6ecJLGCezTmhQ8Fz50Arx9RUszryR8KlgK6avuSXvviL6yWyViQABOg==", + "dev": true, "requires": { "@types/node": "*" } @@ -497,6 +499,7 @@ "version": "4.16.1", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.16.1.tgz", "integrity": "sha512-V0clmJow23WeyblmACoxbHBu2JKlE5TiIme6Lem14FnPW9gsttyHtk6wq7njcdIWH1njAaFgR8gW09lgY98gQg==", + "dev": true, "requires": { "@types/body-parser": "*", "@types/express-serve-static-core": "*", @@ -507,6 +510,7 @@ "version": "4.16.2", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.16.2.tgz", "integrity": "sha512-qgc8tjnDrc789rAQed8NoiFLV5VGcItA4yWNFphqGU0RcuuQngD00g3LHhWIK3HQ2XeDgVCmlNPDlqi3fWBHnQ==", + "dev": true, "requires": { "@types/node": "*", "@types/range-parser": "*" @@ -542,27 +546,41 @@ "@types/mime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz", - "integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==" + "integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==", + "dev": true }, "@types/node": { "version": "11.13.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.0.tgz", - "integrity": "sha512-rx29MMkRdVmzunmiA4lzBYJNnXsW/PhG4kMBy2ATsYaDjGGR75dCFEVVROKpNwlVdcUX3xxlghKQOeDPBJobng==" + "integrity": "sha512-rx29MMkRdVmzunmiA4lzBYJNnXsW/PhG4kMBy2ATsYaDjGGR75dCFEVVROKpNwlVdcUX3xxlghKQOeDPBJobng==", + "dev": true }, "@types/node-xlsx": { "version": "0.12.1", "resolved": "https://registry.npmjs.org/@types/node-xlsx/-/node-xlsx-0.12.1.tgz", - "integrity": "sha512-KIP4AaUvh69FM7OozMAppWT/av0+/HgZEmFKKcq/XIXUFdOpiWxE8oC5Jq33kXgvfdLS0q6n687iGJvoe/0gkA==" + "integrity": "sha512-KIP4AaUvh69FM7OozMAppWT/av0+/HgZEmFKKcq/XIXUFdOpiWxE8oC5Jq33kXgvfdLS0q6n687iGJvoe/0gkA==", + "dev": true + }, + "@types/nodemailer": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-4.6.7.tgz", + "integrity": "sha512-slR1wz8I8O20CHNBNhYhObRZ8zeG5FnfFUWLZKk1f0UDYaLZOsBjUfCC9VEFi7oSRCC886DfKmq1JncPDMOrng==", + "dev": true, + "requires": { + "@types/node": "*" + } }, "@types/range-parser": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", - "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==" + "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==", + "dev": true }, "@types/serve-static": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.2.tgz", "integrity": "sha512-/BZ4QRLpH/bNYgZgwhKEh+5AsboDBcUdlBYgzoLX0fpj3Y2gp6EApyOlM3bK53wQS/OE1SrdSYBAbux2D1528Q==", + "dev": true, "requires": { "@types/express-serve-static-core": "*", "@types/mime": "*" @@ -2859,6 +2877,14 @@ "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", "dev": true }, + "graphql": { + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-14.2.1.tgz", + "integrity": "sha512-2PL1UbvKeSjy/lUeJqHk+eR9CvuErXoCNwJI4jm3oNFEeY+9ELqHNKO1ZuSxAkasPkpWbmT/iMRMFxd3cEL3tQ==", + "requires": { + "iterall": "^1.2.2" + } + }, "growly": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", @@ -3462,6 +3488,11 @@ "handlebars": "^4.1.0" } }, + "iterall": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.2.2.tgz", + "integrity": "sha512-yynBb1g+RFUPY64fTrFv7nsjRrENBQJaX2UL+2Szc9REFrSNm1rpSXHGzhmAy7a9uv3vlvgBlXnf9RqmPH1/DA==" + }, "jest": { "version": "24.7.1", "resolved": "https://registry.npmjs.org/jest/-/jest-24.7.1.tgz", diff --git a/package.json b/package.json index 065b35b..09077a1 100644 --- a/package.json +++ b/package.json @@ -6,15 +6,17 @@ "scripts": { "test": "jest", "tsc": "tsc", - "dev": "tsnd --respawn src/index.ts" + "dev": "tsnd -r dotenv/config --respawn src/services/order.ts", + "product": "tsnd -r dotenv/config --respawn src/services/product.ts", + "category": "tsnd -r dotenv/config --respawn src/services/category.ts", + "dev:production-order": "set NODE_ENV=production && tsnd -r dotenv/config --respawn src/services/reports/order.ts" }, "author": "", "license": "ISC", "dependencies": { - "@types/express": "^4.16.1", - "@types/node-xlsx": "^0.12.1", "axios": "^0.18.0", "body-parser": "^1.18.3", + "graphql": "^14.2.1", "lodash": "^4.17.11", "moment": "^2.24.0", "node-xlsx": "^0.14.1", @@ -22,6 +24,9 @@ "object-assign": "^4.1.1" }, "devDependencies": { + "@types/express": "^4.16.1", + "@types/node-xlsx": "^0.12.1", + "@types/nodemailer": "^4.6.7", "@types/dotenv": "^6.1.1", "@types/jest": "24.0.11", "@types/lodash": "4.14.123", diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index 0c71a2c..0000000 --- a/src/index.ts +++ /dev/null @@ -1,40 +0,0 @@ -import fs from 'fs' -import moment from 'moment' -import { generateXLSXOrderData, fetchOrdersSequential } from './services/order' -import { createXlSXfile } from './utils/xlsx' - -if (process.env.NODE_ENV === 'development') { - require('dotenv').config() -} - -const EXPORTS_PATH = process.env.EXPORTS_PATH || 'exports' -;(async () => { - console.log('\n\n\n') - if (!fs.existsSync(EXPORTS_PATH)) { - await fs.mkdirSync(EXPORTS_PATH, { recursive: false }) - } - - const since = moment() - .add(-10, 'days') - .startOf('day') - .toDate() - - const until = moment() - .add(-1, 'days') - .endOf('day') - .toDate() - - const orderData = await fetchOrdersSequential({ - since, - until, - }) - - const completedOrders = orderData.completedOrders - const data = await generateXLSXOrderData(completedOrders) - const orderDate = moment().add(-1, 'days') - createXlSXfile({ - data, - fileName: `order-${orderDate.format('YYYY-MM-DD')}`, - title: `Danh sách đơn hàng hoàn thành ngày ${orderDate.format('DD-MM-YYYY')}`, - }) -})() diff --git a/src/services/order.ts b/src/services/order.ts index db8a8d8..52c6b88 100644 --- a/src/services/order.ts +++ b/src/services/order.ts @@ -16,10 +16,6 @@ const ORDER_REPORT_COLUMNS = [ 'Trạng thái', ] -if (process.env.NODE_ENV === 'development') { - require('dotenv').config() -} - const request = axios.create({ baseURL: 'https://api.storelammoc.vn/orders', timeout: 15e3, diff --git a/src/services/variant.ts b/src/services/variant.ts index 684d954..d6780fe 100644 --- a/src/services/variant.ts +++ b/src/services/variant.ts @@ -1,10 +1,6 @@ import axios from 'axios' import lodash from 'lodash' -if (process.env.NODE_ENV === 'development') { - require('dotenv').config() -} - const request = axios.create({ baseURL: 'https://api.storelammoc.vn', timeout: 15e3, diff --git a/src/utils/xlsx.ts b/src/utils/xlsx.ts index b1c2176..1fa53c1 100644 --- a/src/utils/xlsx.ts +++ b/src/utils/xlsx.ts @@ -6,7 +6,7 @@ if (process.env.NODE_ENV === 'development') { require('dotenv').config() } -const DIR_NAME = process.env.DIR_NAME || 'exports' +const EXPORTS_PATH = process.env.DIR_NAME || 'exports' export function createXlSXfile(options: { data: any[][]; fileName: string; title: string }) { const data = options.data @@ -16,7 +16,7 @@ export function createXlSXfile(options: { data: any[][]; fileName: string; title const buffer = xlsx.build([{ data: options.data, name: 'Report ' }], XLSXOption) - fs.writeFileSync(path.resolve(DIR_NAME, `${options.fileName}.xlsx`), buffer, { + fs.writeFileSync(path.resolve(EXPORTS_PATH, `${options.fileName}.xlsx`), buffer, { flag: 'w+', }) console.log('Export XLSX Done !') From 7fb43ff96e91c257f7f6cc336c247f5f658dfd55 Mon Sep 17 00:00:00 2001 From: Hoang Lam Date: Thu, 11 Apr 2019 14:24:57 +0700 Subject: [PATCH 05/12] feat(mail): add feature export xlsx file from data and send email --- src/services/mail.ts | 63 +++++++++++++++++++++++++++++++++++ src/services/reports/order.ts | 58 ++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 src/services/mail.ts create mode 100644 src/services/reports/order.ts diff --git a/src/services/mail.ts b/src/services/mail.ts new file mode 100644 index 0000000..300e442 --- /dev/null +++ b/src/services/mail.ts @@ -0,0 +1,63 @@ +import nodemailer from 'nodemailer' + +export async function sendMail(options: { + mailFrom: string + mailTo: string + mailCc?: any + mailBcc?: any + subject: string + fileName?: string + filePath?: string +}) { + let transporterOptions: any = { + host: 'smtp.gmail.com', + port: 465, + secure: true, + auth: { + type: 'OAuth2', + user: process.env.MAIL_USER, + clientId: process.env.MAIL_CLIENT_ID, + clientSecret: process.env.MAIL_CLIENT_SECRET, + refreshToken: process.env.MAIL_REFRESH_TOKEN, + accessToken: process.env.MAIL_ACCESS_TOKEN, + expires: process.env.MAIL_EXPIRES, + // accessUrl: + }, + } + + if (process.env.NODE_ENV === 'development') { + const testAccount = await nodemailer.createTestAccount() + transporterOptions = { + host: 'smtp.ethereal.email', + port: 587, + secure: false, + auth: { + user: testAccount.user, + pass: testAccount.pass, + }, + } + } + const transporter = nodemailer.createTransport(transporterOptions) + + transporter.verify(function(error, success) { + if (error) { + console.log(error) + } else { + console.log('Server is ready to take our messages') + } + }) + + let mailMessage = await transporter.sendMail({ + from: options.mailFrom, + to: options.mailTo, + bcc: options.mailBcc, + cc: options.mailCc, + subject: options.subject, + attachments: [ + { + filename: options.fileName, + path: options.filePath, + }, + ], + }) +} diff --git a/src/services/reports/order.ts b/src/services/reports/order.ts new file mode 100644 index 0000000..d3d6d0a --- /dev/null +++ b/src/services/reports/order.ts @@ -0,0 +1,58 @@ +import moment from 'moment' +import fs from 'fs' +import { generateXLSXOrderData, fetchOrdersSequential } from '../order' +import { createXlSXfile } from '../../utils/xlsx' +import { sendMail } from '../mail' +const EXPORTS_PATH = process.env.EXPORTS_PATH || 'exports' + +export async function createXlSXfileAndSendMail() { + const MAIL_SEND_FROM = process.env.MAIL_SEND_FROM || '' + const MAIL_SEND_TO = process.env.MAIL_SEND_TO || '' + const MAIL_SEND_CC = process.env.MAIL_SEND_CC + const MAIL_SEND_BCC = process.env.MAIL_SEND_BCC + const MAIL_SUBJECT = process.env.MAIL_SUBJECT || 'Orders Daily Report' + const DURATION_DAYS_GET_ORDERS = process.env.DURATION_DAYS_GET_ORDERS || 30 + + console.log('\n\n\n') + if (!fs.existsSync(EXPORTS_PATH)) { + await fs.mkdirSync(EXPORTS_PATH, { recursive: false }) + } + + const since = moment() + .subtract(DURATION_DAYS_GET_ORDERS, 'days') + .startOf('day') + .toDate() + + const until = moment() + .add(-1, 'days') + .endOf('day') + .toDate() + + const orderData = await fetchOrdersSequential({ + since, + until, + }) + + const completedOrders = orderData.completedOrders + const data = await generateXLSXOrderData(completedOrders) + const orderDate = moment().add(-1, 'days') + const fileName = `order-${orderDate.format('YYYY-MM-DD')}` + + createXlSXfile({ + data, + fileName: fileName, + title: `Danh sách đơn hàng hoàn thành ngày ${orderDate.format('DD-MM-YYYY')}`, + }) + + await sendMail({ + mailCc: MAIL_SEND_CC, + mailBcc: MAIL_SEND_BCC, + fileName: `${fileName}.xlsx`, + mailFrom: MAIL_SEND_FROM, + mailTo: MAIL_SEND_TO, + filePath: `./${EXPORTS_PATH}/${fileName}.xlsx`, + subject: MAIL_SUBJECT, + }) +} + +createXlSXfileAndSendMail() From 1f6c4fcea7d0d34f678d1e08926823ec7646063c Mon Sep 17 00:00:00 2001 From: Hoang Lam Date: Thu, 11 Apr 2019 17:39:57 +0700 Subject: [PATCH 06/12] feat(mail): intergrated slack add intergration slack api add comment in .env example retry send mail 3 times if fail --- .env.example | 15 +++- package-lock.json | 14 ++-- package.json | 18 ++--- src/services/mail.ts | 135 +++++++++++++++++++------------- src/services/order.ts | 12 ++- src/services/reports/order.ts | 14 +++- src/utils/slack-notification.ts | 21 +++++ src/utils/xlsx.ts | 3 +- yarn.lock | 20 ++--- 9 files changed, 165 insertions(+), 87 deletions(-) create mode 100644 src/utils/slack-notification.ts diff --git a/.env.example b/.env.example index f35b52c..6147dd6 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,14 @@ +# DURATION_DAYS_GET_ORDERS: time period from today to fetch data, ex '15' +# EXPORTS_PATH: root path export data, default 'exports' +# MAIL_USER: email of user that get token key +# MAIL_SEND_FROM: email address that send this mail +# MAIL_SEND_TO: abc@abc.com, cde@cde.org +# MAIL_SEND_CC: email recieve as cc mail +# MAIL_SEND_BCC: email recieve as bcc mail +# MAIL_SUBJECT: title of email, ex 'Report Data' +# MAX_TIME_RETRY_SEND_MAIL: number of resend times if an error occurs +# SLACK_TOKEN: token to send notification ex: {{https://hooks.slack.com/services/}}SLACK_TOKEN + TOKEN_KEY= EXPORTS_PATH= MAIL_USER= @@ -11,4 +22,6 @@ MAIL_SEND_TO= MAIL_SEND_FROM= MAIL_SEND_CC= MAIL_SEND_BCC= -MAIL_SUBJECT= \ No newline at end of file +MAIL_SUBJECT= +MAX_TIME_RETRY_SEND_MAIL= +SLACK_TOKEN= \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index a12358e..d11ac56 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4654,12 +4654,12 @@ "integrity": "sha512-mzKGT5Q1PY84v6oRVjy88ymMDLUbPqvIr26n9Uy3j2nXzdhKWx1z4GLSHOyX8655zMkQng1MFR3lK+cE1egS6Q==" }, "nodemon": { - "version": "1.18.10", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.18.10.tgz", - "integrity": "sha512-we51yBb1TfEvZamFchRgcfLbVYgg0xlGbyXmOtbBzDwxwgewYS/YbZ5tnlnsH51+AoSTTsT3A2E/FloUbtH8cQ==", + "version": "1.18.11", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.18.11.tgz", + "integrity": "sha512-KdN3tm1zkarlqNo4+W9raU3ihM4H15MVMSE/f9rYDZmFgDHAfAJsomYrHhApAkuUemYjFyEeXlpCOQ2v5gtBEw==", "dev": true, "requires": { - "chokidar": "^2.1.0", + "chokidar": "^2.1.5", "debug": "^3.1.0", "ignore-by-default": "^1.0.1", "minimatch": "^3.0.4", @@ -6364,9 +6364,9 @@ } }, "typescript": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.2.tgz", - "integrity": "sha512-Og2Vn6Mk7JAuWA1hQdDQN/Ekm/SchX80VzLhjKN9ETYrIepBFAd8PkOdOTK2nKt0FCkmMZKBJvQ1dV1gIxPu/A==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.3.tgz", + "integrity": "sha512-FFgHdPt4T/duxx6Ndf7hwgMZZjZpB+U0nMNGVCYPq0rEzWKjEDobm4J6yb3CS7naZ0yURFqdw9Gwc7UOh/P9oQ==", "dev": true }, "uglify-js": { diff --git a/package.json b/package.json index 09077a1..4f2c40f 100644 --- a/package.json +++ b/package.json @@ -6,10 +6,10 @@ "scripts": { "test": "jest", "tsc": "tsc", - "dev": "tsnd -r dotenv/config --respawn src/services/order.ts", - "product": "tsnd -r dotenv/config --respawn src/services/product.ts", - "category": "tsnd -r dotenv/config --respawn src/services/category.ts", - "dev:production-order": "set NODE_ENV=production && tsnd -r dotenv/config --respawn src/services/reports/order.ts" + "dev": "tsnd -r dotenv/config --respawn --transpileOnly src/services/reports/order.ts", + "product": "tsnd -r dotenv/config --respawn --transpileOnly src/services/product.ts", + "category": "tsnd -r dotenv/config --respawn --transpileOnly src/services/category.ts", + "dev:production-order": "set NODE_ENV=production && tsnd -r dotenv/config --respawn --transpileOnly src/services/reports/order.ts" }, "author": "", "license": "ISC", @@ -24,18 +24,18 @@ "object-assign": "^4.1.1" }, "devDependencies": { - "@types/express": "^4.16.1", - "@types/node-xlsx": "^0.12.1", - "@types/nodemailer": "^4.6.7", "@types/dotenv": "^6.1.1", + "@types/express": "^4.16.1", "@types/jest": "24.0.11", "@types/lodash": "4.14.123", + "@types/node-xlsx": "^0.12.1", + "@types/nodemailer": "^4.6.7", "dotenv": "7.0.0", "jest": "24.7.1", - "nodemon": "1.18.10", + "nodemon": "1.18.11", "prettier": "1.16.4", "ts-jest": "24.0.2", "ts-node-dev": "1.0.0-pre.32", - "typescript": "3.4.2" + "typescript": "3.4.3" } } diff --git a/src/services/mail.ts b/src/services/mail.ts index 300e442..096ba8f 100644 --- a/src/services/mail.ts +++ b/src/services/mail.ts @@ -1,63 +1,92 @@ -import nodemailer from 'nodemailer' +import nodemailer, { SentMessageInfo } from 'nodemailer' +import { sendNotification } from '../utils/slack-notification' -export async function sendMail(options: { - mailFrom: string - mailTo: string - mailCc?: any - mailBcc?: any - subject: string - fileName?: string - filePath?: string -}) { - let transporterOptions: any = { - host: 'smtp.gmail.com', - port: 465, - secure: true, - auth: { - type: 'OAuth2', - user: process.env.MAIL_USER, - clientId: process.env.MAIL_CLIENT_ID, - clientSecret: process.env.MAIL_CLIENT_SECRET, - refreshToken: process.env.MAIL_REFRESH_TOKEN, - accessToken: process.env.MAIL_ACCESS_TOKEN, - expires: process.env.MAIL_EXPIRES, - // accessUrl: - }, - } - - if (process.env.NODE_ENV === 'development') { - const testAccount = await nodemailer.createTestAccount() - transporterOptions = { - host: 'smtp.ethereal.email', - port: 587, - secure: false, +const MAX_TIME_RETRY_SEND_MAIL = process.env.MAX_TIME_RETRY_SEND_MAIL || 3 +export async function sendMail( + options: { + mailFrom: string + mailTo: string + mailCc?: any + mailBcc?: any + subject: string + fileName?: string + filePath?: string + }, + retryCount = 0, +): Promise { + try { + console.log('Start send mail to', options.mailTo) + let transporterOptions: any = { + host: 'smtp.gmail.com', + port: 465, + secure: true, auth: { - user: testAccount.user, - pass: testAccount.pass, + type: 'OAuth2', + user: process.env.MAIL_USER, + clientId: process.env.MAIL_CLIENT_ID, + clientSecret: process.env.MAIL_CLIENT_SECRET, + refreshToken: process.env.MAIL_REFRESH_TOKEN, + accessToken: '123', //process.env.MAIL_ACCESS_TOKEN, + expires: process.env.MAIL_EXPIRES, + // accessUrl: }, } - } - const transporter = nodemailer.createTransport(transporterOptions) - transporter.verify(function(error, success) { - if (error) { - console.log(error) - } else { - console.log('Server is ready to take our messages') + if (process.env.NODE_ENV === 'development') { + const testAccount = await nodemailer.createTestAccount() + transporterOptions = { + host: 'smtp.ethereal.email', + port: 587, + secure: false, + auth: { + user: testAccount.user, + pass: testAccount.pass, + }, + } } - }) + const transporter = nodemailer.createTransport(transporterOptions) + + await transporter.verify() - let mailMessage = await transporter.sendMail({ - from: options.mailFrom, - to: options.mailTo, - bcc: options.mailBcc, - cc: options.mailCc, - subject: options.subject, - attachments: [ + return await transporter.sendMail( { - filename: options.fileName, - path: options.filePath, + from: options.mailFrom, + to: options.mailTo, + bcc: options.mailBcc, + cc: options.mailCc, + subject: options.subject, + attachments: [ + { + filename: options.fileName, + path: options.filePath, + }, + ], }, - ], - }) + function(err, info) { + const notifitionStatus = 'success' + const notifitionTitle = ':heavy_check_mark: Daily Report Orders' + const notifitionSubtitle = ':100: Successed ' + sendNotification({ + status: notifitionStatus, + subtitle: notifitionSubtitle, + title: notifitionTitle, + }) + }, + ) + } catch (err) { + console.log(retryCount) + if (retryCount < MAX_TIME_RETRY_SEND_MAIL) { + return await sendMail(options, retryCount + 1) + } + + const notifitionStatus = 'danger' + const notifitionTitle = ':no_entry: Orders Daily Report' + const notifitionSubtitle = 'There was a failure.' + sendNotification({ + data: err, + status: notifitionStatus, + subtitle: notifitionSubtitle, + title: notifitionTitle, + }) + } } diff --git a/src/services/order.ts b/src/services/order.ts index 52c6b88..3f44218 100644 --- a/src/services/order.ts +++ b/src/services/order.ts @@ -2,6 +2,7 @@ import axios from 'axios' import moment from 'moment' import lodash from 'lodash' import { getVariantsByIds } from './variant' +import { sendNotification } from '../utils/slack-notification' const ORDER_REPORT_COLUMNS = [ 'Mã đơn hàng', @@ -37,6 +38,15 @@ export async function fetchOrders(options: { return response.data } catch (error) { + const notifitionStatus = 'danger' + const notifitionTitle = ':no_entry: Orders Daily Report' + const notifitionSubtitle = 'There was a failure when fetch orders.' + sendNotification({ + data: error, + status: notifitionStatus, + subtitle: notifitionSubtitle, + title: notifitionTitle, + }) throw error } } @@ -130,5 +140,3 @@ export const fetchOrdersSequential = async ({ completedOrders, } } - -export const filterOrderData = async ({ data: [] }: { data: [] }): Promise => {} diff --git a/src/services/reports/order.ts b/src/services/reports/order.ts index d3d6d0a..af2dec4 100644 --- a/src/services/reports/order.ts +++ b/src/services/reports/order.ts @@ -13,7 +13,6 @@ export async function createXlSXfileAndSendMail() { const MAIL_SUBJECT = process.env.MAIL_SUBJECT || 'Orders Daily Report' const DURATION_DAYS_GET_ORDERS = process.env.DURATION_DAYS_GET_ORDERS || 30 - console.log('\n\n\n') if (!fs.existsSync(EXPORTS_PATH)) { await fs.mkdirSync(EXPORTS_PATH, { recursive: false }) } @@ -38,12 +37,14 @@ export async function createXlSXfileAndSendMail() { const orderDate = moment().add(-1, 'days') const fileName = `order-${orderDate.format('YYYY-MM-DD')}` - createXlSXfile({ + await createXlSXfile({ data, fileName: fileName, title: `Danh sách đơn hàng hoàn thành ngày ${orderDate.format('DD-MM-YYYY')}`, }) + console.log('Export XLSX Done !') + await sendMail({ mailCc: MAIL_SEND_CC, mailBcc: MAIL_SEND_BCC, @@ -55,4 +56,11 @@ export async function createXlSXfileAndSendMail() { }) } -createXlSXfileAndSendMail() +;(async () => { + try { + await createXlSXfileAndSendMail() + } catch (err) { + console.log(err) + // send mail, noti + } +})() diff --git a/src/utils/slack-notification.ts b/src/utils/slack-notification.ts new file mode 100644 index 0000000..f6cb5dc --- /dev/null +++ b/src/utils/slack-notification.ts @@ -0,0 +1,21 @@ +import axios from 'axios' + +const slackToken = `https://hooks.slack.com/services/${process.env.SLACK_TOKEN}` + +export function sendNotification(options: { + data?: string + status: string + title: string + subtitle: string +}) { + axios.post(slackToken, { + text: options.title, + attachments: [ + { + color: options.status, + title: options.subtitle, + text: JSON.stringify(options.data), + }, + ], + }) +} diff --git a/src/utils/xlsx.ts b/src/utils/xlsx.ts index 1fa53c1..2f670de 100644 --- a/src/utils/xlsx.ts +++ b/src/utils/xlsx.ts @@ -16,8 +16,7 @@ export function createXlSXfile(options: { data: any[][]; fileName: string; title const buffer = xlsx.build([{ data: options.data, name: 'Report ' }], XLSXOption) - fs.writeFileSync(path.resolve(EXPORTS_PATH, `${options.fileName}.xlsx`), buffer, { + return fs.writeFileSync(path.resolve(EXPORTS_PATH, `${options.fileName}.xlsx`), buffer, { flag: 'w+', }) - console.log('Export XLSX Done !') } diff --git a/yarn.lock b/yarn.lock index 1f009b4..f369efa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -900,7 +900,7 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.4.1, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chokidar@^2.1.0: +chokidar@^2.1.5: version "2.1.5" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.5.tgz#0ae8434d962281a5f56c72869e79cb6d9d86ad4d" integrity sha512-i0TprVWp+Kj4WRPtInjexJ8Q+BqTE909VpH8xVhXrJkoc5QC8VO9TryGOqTr+2hljzc1sC62t22h5tZePodM/A== @@ -3242,12 +3242,12 @@ node-xlsx@^0.14.1: buffer-from "^1.1.0" xlsx "^0.14.1" -nodemon@1.18.10: - version "1.18.10" - resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.18.10.tgz#3ba63f64eb4c283cf3e4f75f30817e9d4f393afe" - integrity sha512-we51yBb1TfEvZamFchRgcfLbVYgg0xlGbyXmOtbBzDwxwgewYS/YbZ5tnlnsH51+AoSTTsT3A2E/FloUbtH8cQ== +nodemon@1.18.11: + version "1.18.11" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.18.11.tgz#d836ab663776e7995570b963da5bfc807e53f6b8" + integrity sha512-KdN3tm1zkarlqNo4+W9raU3ihM4H15MVMSE/f9rYDZmFgDHAfAJsomYrHhApAkuUemYjFyEeXlpCOQ2v5gtBEw== dependencies: - chokidar "^2.1.0" + chokidar "^2.1.5" debug "^3.1.0" ignore-by-default "^1.0.1" minimatch "^3.0.4" @@ -4515,10 +4515,10 @@ type-is@~1.6.16: media-typer "0.3.0" mime-types "~2.1.18" -typescript@3.4.2: - version "3.4.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.2.tgz#9ed4e6475d906f589200193be056f5913caed481" - integrity sha512-Og2Vn6Mk7JAuWA1hQdDQN/Ekm/SchX80VzLhjKN9ETYrIepBFAd8PkOdOTK2nKt0FCkmMZKBJvQ1dV1gIxPu/A== +typescript@3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.3.tgz#0eb320e4ace9b10eadf5bc6103286b0f8b7c224f" + integrity sha512-FFgHdPt4T/duxx6Ndf7hwgMZZjZpB+U0nMNGVCYPq0rEzWKjEDobm4J6yb3CS7naZ0yURFqdw9Gwc7UOh/P9oQ== uglify-js@^3.1.4: version "3.5.3" From a47828693077ca589ef2921f5b15d2ee25f3484d Mon Sep 17 00:00:00 2001 From: Hoang Lam Date: Thu, 11 Apr 2019 17:44:50 +0700 Subject: [PATCH 07/12] fixbug(mail) : update gitignore --- .gitignore | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitignore b/.gitignore index 4413610..deed335 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,3 @@ node_modules/ dist/ .env -docker-compose.yml -exports/ -orders.json From eab496586d961a1173f8352a5eb3c535d86163bf Mon Sep 17 00:00:00 2001 From: Hoang Lam Date: Thu, 11 Apr 2019 18:07:11 +0700 Subject: [PATCH 08/12] chore: remove debug code --- src/services/mail.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/mail.ts b/src/services/mail.ts index 096ba8f..e10ebac 100644 --- a/src/services/mail.ts +++ b/src/services/mail.ts @@ -26,7 +26,7 @@ export async function sendMail( clientId: process.env.MAIL_CLIENT_ID, clientSecret: process.env.MAIL_CLIENT_SECRET, refreshToken: process.env.MAIL_REFRESH_TOKEN, - accessToken: '123', //process.env.MAIL_ACCESS_TOKEN, + accessToken: process.env.MAIL_ACCESS_TOKEN, expires: process.env.MAIL_EXPIRES, // accessUrl: }, @@ -81,7 +81,7 @@ export async function sendMail( const notifitionStatus = 'danger' const notifitionTitle = ':no_entry: Orders Daily Report' - const notifitionSubtitle = 'There was a failure.' + const notifitionSubtitle = 'There was a failure when send email.' sendNotification({ data: err, status: notifitionStatus, From b32b148ce6612e194b98ee0eed0969ab87e1b2e0 Mon Sep 17 00:00:00 2001 From: Hoang Lam Date: Thu, 11 Apr 2019 19:07:03 +0700 Subject: [PATCH 09/12] chore: update slack noti --- src/services/mail.ts | 50 ++++---------- src/services/order.ts | 114 ++++++++++++++++---------------- src/services/reports/order.ts | 32 ++++++++- src/utils/slack-notification.ts | 5 +- src/utils/xlsx.ts | 5 +- 5 files changed, 105 insertions(+), 101 deletions(-) diff --git a/src/services/mail.ts b/src/services/mail.ts index e10ebac..fc03d47 100644 --- a/src/services/mail.ts +++ b/src/services/mail.ts @@ -1,5 +1,4 @@ import nodemailer, { SentMessageInfo } from 'nodemailer' -import { sendNotification } from '../utils/slack-notification' const MAX_TIME_RETRY_SEND_MAIL = process.env.MAX_TIME_RETRY_SEND_MAIL || 3 export async function sendMail( @@ -48,45 +47,24 @@ export async function sendMail( await transporter.verify() - return await transporter.sendMail( - { - from: options.mailFrom, - to: options.mailTo, - bcc: options.mailBcc, - cc: options.mailCc, - subject: options.subject, - attachments: [ - { - filename: options.fileName, - path: options.filePath, - }, - ], - }, - function(err, info) { - const notifitionStatus = 'success' - const notifitionTitle = ':heavy_check_mark: Daily Report Orders' - const notifitionSubtitle = ':100: Successed ' - sendNotification({ - status: notifitionStatus, - subtitle: notifitionSubtitle, - title: notifitionTitle, - }) - }, - ) + return await transporter.sendMail({ + from: options.mailFrom, + to: options.mailTo, + bcc: options.mailBcc, + cc: options.mailCc, + subject: options.subject, + attachments: [ + { + filename: options.fileName, + path: options.filePath, + }, + ], + }) } catch (err) { console.log(retryCount) if (retryCount < MAX_TIME_RETRY_SEND_MAIL) { return await sendMail(options, retryCount + 1) } - - const notifitionStatus = 'danger' - const notifitionTitle = ':no_entry: Orders Daily Report' - const notifitionSubtitle = 'There was a failure when send email.' - sendNotification({ - data: err, - status: notifitionStatus, - subtitle: notifitionSubtitle, - title: notifitionTitle, - }) + throw err } } diff --git a/src/services/order.ts b/src/services/order.ts index 5186fc1..396a296 100644 --- a/src/services/order.ts +++ b/src/services/order.ts @@ -2,8 +2,6 @@ import axios from 'axios' import moment from 'moment' import lodash from 'lodash' import { getVariantsByIds } from './variant' -import { sendNotification } from '../utils/slack-notification' - const ORDER_REPORT_COLUMNS = [ 'Mã đơn hàng', @@ -39,65 +37,67 @@ export async function fetchOrders(options: { return response.data } catch (error) { + if (error.response && error.response.data) { + const err = new Error(error.response.data.message) as Error & { code: string } + err.code = error.response.data.code + throw err + } - const notifitionStatus = 'danger' - const notifitionTitle = ':no_entry: Orders Daily Report' - const notifitionSubtitle = 'There was a failure when fetch orders.' - sendNotification({ - data: error, - status: notifitionStatus, - subtitle: notifitionSubtitle, - title: notifitionTitle, - }) + throw error } } export const generateXLSXOrderData = async (orders: any[]) => { - const variantIds = lodash.flatten( - orders.map((order) => { - return order.items.map((item: any) => item.variant.id) - }), - ) - - const variants = await getVariantsByIds(variantIds) - const variantEntities = lodash.keyBy(variants, '_id') - const processedOrders = lodash.flatten( - orders.map((order) => { - let orderCreatedAt = moment(new Date(order.createdAt)).format('DD-MM-YYYY HH:mm') - let orderUpdatedAt = moment(new Date(order.updatedAt)).format('DD-MM-YYYY HH:mm') - const orderDetails = [ - `CO${order.id}`, - orderCreatedAt, - orderUpdatedAt, - order.customerInfo.phone, - order.customerInfo.name, - ] - - const itemsDetails = (order.items || []).map((item: any) => { - const sku = lodash.get(variantEntities, `${lodash.get(item, 'variant.id')}.sku`) - const name = - lodash.get(item, 'product.name') + ' (' + lodash.get(item, 'variant.name') + ')' - return { - sku, - name, - quantity: item.quantity, - price: item.price, - } - }) - - return itemsDetails.map((item: any) => [ - ...orderDetails, - item.sku, - item.name, - item.quantity, - item.price, - order.status, - ]) - }), - ) - processedOrders.unshift(ORDER_REPORT_COLUMNS) - - return processedOrders + try { + const variantIds = lodash.flatten( + orders.map((order) => { + return order.items.map((item: any) => item.variant.id) + }), + ) + + const variants = await getVariantsByIds(variantIds) + const variantEntities = lodash.keyBy(variants, '_id') + const processedOrders = lodash.flatten( + orders.map((order) => { + let orderCreatedAt = moment(new Date(order.createdAt)).format('DD-MM-YYYY HH:mm') + let orderUpdatedAt = moment(new Date(order.updatedAt)).format('DD-MM-YYYY HH:mm') + const orderDetails = [ + `CO${order.id}`, + orderCreatedAt, + orderUpdatedAt, + order.customerInfo.phone, + order.customerInfo.name, + ] + + const itemsDetails = (order.items || []).map((item: any) => { + const sku = lodash.get(variantEntities, `${lodash.get(item, 'variant.id')}.sku`) + const name = + lodash.get(item, 'product.name') + ' (' + lodash.get(item, 'variant.name') + ')' + return { + sku, + name, + quantity: item.quantity, + price: item.price, + } + }) + + return itemsDetails.map((item: any) => [ + ...orderDetails, + item.sku, + item.name, + item.quantity, + item.price, + order.status, + ]) + }), + ) + processedOrders.unshift(ORDER_REPORT_COLUMNS) + + return processedOrders + } catch (err) { + const error = new Error('generateXLSXOrderData failure') + throw error + } } export const fetchOrdersSequential = async ({ @@ -141,5 +141,3 @@ export const fetchOrdersSequential = async ({ completedOrders, } } - - diff --git a/src/services/reports/order.ts b/src/services/reports/order.ts index af2dec4..5c394a0 100644 --- a/src/services/reports/order.ts +++ b/src/services/reports/order.ts @@ -3,6 +3,7 @@ import fs from 'fs' import { generateXLSXOrderData, fetchOrdersSequential } from '../order' import { createXlSXfile } from '../../utils/xlsx' import { sendMail } from '../mail' +import { sendNotification } from '../../utils/slack-notification' const EXPORTS_PATH = process.env.EXPORTS_PATH || 'exports' export async function createXlSXfileAndSendMail() { @@ -41,6 +42,13 @@ export async function createXlSXfileAndSendMail() { data, fileName: fileName, title: `Danh sách đơn hàng hoàn thành ngày ${orderDate.format('DD-MM-YYYY')}`, + }).catch((err) => { + sendNotification({ + status: 'danger', + title: 'Daily Report Orders', + subtitle: 'There was a failure in createXLSXfile.', + }) + throw err }) console.log('Export XLSX Done !') @@ -53,6 +61,13 @@ export async function createXlSXfileAndSendMail() { mailTo: MAIL_SEND_TO, filePath: `./${EXPORTS_PATH}/${fileName}.xlsx`, subject: MAIL_SUBJECT, + }).catch((err) => { + sendNotification({ + status: 'danger', + title: 'Daily Report Orders', + subtitle: 'There was a failure in sendMail.', + }) + throw err }) } @@ -60,7 +75,20 @@ export async function createXlSXfileAndSendMail() { try { await createXlSXfileAndSendMail() } catch (err) { - console.log(err) - // send mail, noti + // sendNotification({ + // status: 'danger', + // title: 'Daily Report Orders', + // subtitle: 'There was a failure when create XLSX file and send mail.', + // }) + } finally { + const notifitionStatus = 'success' + const notifitionTitle = ':heavy_check_mark: Daily Report Orders' + const notifitionSubtitle = ':100: Successed ' + sendNotification({ + status: notifitionStatus, + subtitle: notifitionSubtitle, + title: notifitionTitle, + code: 200, + }) } })() diff --git a/src/utils/slack-notification.ts b/src/utils/slack-notification.ts index f6cb5dc..c9ef446 100644 --- a/src/utils/slack-notification.ts +++ b/src/utils/slack-notification.ts @@ -3,7 +3,8 @@ import axios from 'axios' const slackToken = `https://hooks.slack.com/services/${process.env.SLACK_TOKEN}` export function sendNotification(options: { - data?: string + message?: string + code?: number status: string title: string subtitle: string @@ -14,7 +15,7 @@ export function sendNotification(options: { { color: options.status, title: options.subtitle, - text: JSON.stringify(options.data), + text: options.message, }, ], }) diff --git a/src/utils/xlsx.ts b/src/utils/xlsx.ts index f49bc6d..7067091 100644 --- a/src/utils/xlsx.ts +++ b/src/utils/xlsx.ts @@ -8,7 +8,7 @@ if (process.env.NODE_ENV === 'development') { const EXPORTS_PATH = process.env.DIR_NAME || 'exports' -export function createXlSXfile(options: { data: any[][]; fileName: string; title: string }) { +export async function createXlSXfile(options: { data: any[][]; fileName: string; title: string }) { const data = options.data data.unshift([options.title.toUpperCase()], []) const range = { s: { c: 0, r: 0 }, e: { c: data[3].length - 1, r: 0 } } @@ -16,8 +16,7 @@ export function createXlSXfile(options: { data: any[][]; fileName: string; title const buffer = xlsx.build([{ data: options.data, name: 'Report ' }], XLSXOption) - - return fs.writeFileSync(path.resolve(EXPORTS_PATH, `${options.fileName}.xlsx`), buffer, { + return await fs.writeFileSync(path.resolve(EXPORTS_PATH, `${options.fileName}.xlsx`), buffer, { flag: 'w+', }) } From 58a9d1a0c776ce6fb5080667f0460c731e3b0b55 Mon Sep 17 00:00:00 2001 From: Hoang Lam Date: Fri, 12 Apr 2019 15:07:49 +0700 Subject: [PATCH 10/12] chore(mail): update syntax es6 --- .env.example | 28 ++++++++++++---------------- package.json | 4 ++-- src/services/mail.ts | 23 +++++------------------ src/services/order.ts | 8 ++++---- src/services/reports/order.ts | 17 ++++++++--------- src/utils/slack-notification.ts | 4 ++-- src/utils/xlsx.ts | 8 ++++++-- 7 files changed, 39 insertions(+), 53 deletions(-) diff --git a/.env.example b/.env.example index 6bf3ea9..9cc10ef 100644 --- a/.env.example +++ b/.env.example @@ -1,32 +1,28 @@ - -# DURATION_DAYS_GET_ORDERS: time period from today to fetch data, ex '15' -# EXPORTS_PATH: root path export data, default 'exports' -# MAIL_USER: email of user that get token key -# MAIL_SEND_FROM: email address that send this mail -# MAIL_SEND_TO: abc@abc.com, cde@cde.org -# MAIL_SEND_CC: email recieve as cc mail -# MAIL_SEND_BCC: email recieve as bcc mail -# MAIL_SUBJECT: title of email, ex 'Report Data' -# MAX_TIME_RETRY_SEND_MAIL: number of resend times if an error occurs -# SLACK_TOKEN: token to send notification ex: {{https://hooks.slack.com/services/}}SLACK_TOKEN - - - TOKEN_KEY= +# root path export data, default 'exports' EXPORTS_PATH= +# email of user that get token key MAIL_USER= MAIL_CLIENT_ID= MAIL_CLIENT_SECRET= MAIL_REFRESH_TOKEN= MAIL_ACCESS_TOKEN= MAIL_EXPIRES= +# time period from today to fetch data, ex '15' DURATION_DAYS_GET_ORDERS= +# email recieve: ex: abc@abc.com, cde@cde.org MAIL_SEND_TO= +# email address that send this mail MAIL_SEND_FROM= +# email recieve as cc mail MAIL_SEND_CC= +# email recieve as bcc mail MAIL_SEND_BCC= -MAIL_SUBJECT= +# title of email, ex 'Report Data' +MAIL_TITLE= +# number of resend times if an error occurs MAX_TIME_RETRY_SEND_MAIL= +# token to send notification ex: {{https://hooks.slack.com/services/}}SLACK_TOKEN SLACK_TOKEN= -MAIL_SUBJECT= + diff --git a/package.json b/package.json index 9cb793b..efc8d84 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,9 @@ "description": "", "main": "index.js", "scripts": { - "test": "jest", + "test": "set NODE_ENV=development && jest --setupFiles dotenv/config", "tsc": "tsc", - "dev": "tsnd -r dotenv/config --respawn --transpileOnly src/services/reports/order.ts", + "dev": "set NODE_ENV=development && tsnd -r dotenv/config --respawn --transpileOnly src/services/reports/order.ts", "product": "tsnd -r dotenv/config --respawn --transpileOnly src/services/product.ts", "category": "tsnd -r dotenv/config --respawn --transpileOnly src/services/category.ts", "dev:production-order": "set NODE_ENV=production && tsnd -r dotenv/config --respawn --transpileOnly src/services/reports/order.ts" diff --git a/src/services/mail.ts b/src/services/mail.ts index fc03d47..54a7d9b 100644 --- a/src/services/mail.ts +++ b/src/services/mail.ts @@ -1,21 +1,21 @@ import nodemailer, { SentMessageInfo } from 'nodemailer' const MAX_TIME_RETRY_SEND_MAIL = process.env.MAX_TIME_RETRY_SEND_MAIL || 3 -export async function sendMail( +export const sendMail = async ( options: { mailFrom: string mailTo: string mailCc?: any mailBcc?: any - subject: string + title: string fileName?: string filePath?: string }, retryCount = 0, -): Promise { +): Promise => { try { console.log('Start send mail to', options.mailTo) - let transporterOptions: any = { + const transporterOptions: any = { host: 'smtp.gmail.com', port: 465, secure: true, @@ -30,19 +30,6 @@ export async function sendMail( // accessUrl: }, } - - if (process.env.NODE_ENV === 'development') { - const testAccount = await nodemailer.createTestAccount() - transporterOptions = { - host: 'smtp.ethereal.email', - port: 587, - secure: false, - auth: { - user: testAccount.user, - pass: testAccount.pass, - }, - } - } const transporter = nodemailer.createTransport(transporterOptions) await transporter.verify() @@ -52,7 +39,7 @@ export async function sendMail( to: options.mailTo, bcc: options.mailBcc, cc: options.mailCc, - subject: options.subject, + subject: options.title, attachments: [ { filename: options.fileName, diff --git a/src/services/order.ts b/src/services/order.ts index 396a296..c269335 100644 --- a/src/services/order.ts +++ b/src/services/order.ts @@ -22,11 +22,11 @@ const request = axios.create({ headers: { 'x-access-token': process.env.TOKEN_KEY }, }) -export async function fetchOrders(options: { +export const fetchOrders = async (options: { since: Date until: Date limit?: number -}): Promise<{ hasMore: boolean; customerAgencyOrders: any[] }> { +}): Promise<{ hasMore: boolean; customerAgencyOrders: any[] }> => { try { const response = await request.get(`/customerAgencies/agencies/57e7af98b52ee4d36dc6c7c8`, { params: { @@ -59,8 +59,8 @@ export const generateXLSXOrderData = async (orders: any[]) => { const variantEntities = lodash.keyBy(variants, '_id') const processedOrders = lodash.flatten( orders.map((order) => { - let orderCreatedAt = moment(new Date(order.createdAt)).format('DD-MM-YYYY HH:mm') - let orderUpdatedAt = moment(new Date(order.updatedAt)).format('DD-MM-YYYY HH:mm') + const orderCreatedAt = moment(new Date(order.createdAt)).format('DD-MM-YYYY HH:mm') + const orderUpdatedAt = moment(new Date(order.updatedAt)).format('DD-MM-YYYY HH:mm') const orderDetails = [ `CO${order.id}`, orderCreatedAt, diff --git a/src/services/reports/order.ts b/src/services/reports/order.ts index 5c394a0..c3e1036 100644 --- a/src/services/reports/order.ts +++ b/src/services/reports/order.ts @@ -6,12 +6,12 @@ import { sendMail } from '../mail' import { sendNotification } from '../../utils/slack-notification' const EXPORTS_PATH = process.env.EXPORTS_PATH || 'exports' -export async function createXlSXfileAndSendMail() { +export const createXlSXfileAndSendMail = async () => { const MAIL_SEND_FROM = process.env.MAIL_SEND_FROM || '' const MAIL_SEND_TO = process.env.MAIL_SEND_TO || '' const MAIL_SEND_CC = process.env.MAIL_SEND_CC const MAIL_SEND_BCC = process.env.MAIL_SEND_BCC - const MAIL_SUBJECT = process.env.MAIL_SUBJECT || 'Orders Daily Report' + const MAIL_TITLE = process.env.MAIL_TITLE || 'Orders Daily Report' const DURATION_DAYS_GET_ORDERS = process.env.DURATION_DAYS_GET_ORDERS || 30 if (!fs.existsSync(EXPORTS_PATH)) { @@ -60,7 +60,7 @@ export async function createXlSXfileAndSendMail() { mailFrom: MAIL_SEND_FROM, mailTo: MAIL_SEND_TO, filePath: `./${EXPORTS_PATH}/${fileName}.xlsx`, - subject: MAIL_SUBJECT, + title: MAIL_TITLE, }).catch((err) => { sendNotification({ status: 'danger', @@ -70,16 +70,15 @@ export async function createXlSXfileAndSendMail() { throw err }) } - ;(async () => { try { await createXlSXfileAndSendMail() } catch (err) { - // sendNotification({ - // status: 'danger', - // title: 'Daily Report Orders', - // subtitle: 'There was a failure when create XLSX file and send mail.', - // }) + sendNotification({ + status: 'danger', + title: 'Daily Report Orders', + subtitle: 'There was a failure when create XLSX file and send mail.', + }) } finally { const notifitionStatus = 'success' const notifitionTitle = ':heavy_check_mark: Daily Report Orders' diff --git a/src/utils/slack-notification.ts b/src/utils/slack-notification.ts index c9ef446..7c12a69 100644 --- a/src/utils/slack-notification.ts +++ b/src/utils/slack-notification.ts @@ -2,13 +2,13 @@ import axios from 'axios' const slackToken = `https://hooks.slack.com/services/${process.env.SLACK_TOKEN}` -export function sendNotification(options: { +export const sendNotification = (options: { message?: string code?: number status: string title: string subtitle: string -}) { +}) => { axios.post(slackToken, { text: options.title, attachments: [ diff --git a/src/utils/xlsx.ts b/src/utils/xlsx.ts index 7067091..3e8c1ea 100644 --- a/src/utils/xlsx.ts +++ b/src/utils/xlsx.ts @@ -8,7 +8,11 @@ if (process.env.NODE_ENV === 'development') { const EXPORTS_PATH = process.env.DIR_NAME || 'exports' -export async function createXlSXfile(options: { data: any[][]; fileName: string; title: string }) { +export const createXlSXfile = async (options: { + data: any[][] + fileName: string + title: string +}) => { const data = options.data data.unshift([options.title.toUpperCase()], []) const range = { s: { c: 0, r: 0 }, e: { c: data[3].length - 1, r: 0 } } @@ -16,7 +20,7 @@ export async function createXlSXfile(options: { data: any[][]; fileName: string; const buffer = xlsx.build([{ data: options.data, name: 'Report ' }], XLSXOption) - return await fs.writeFileSync(path.resolve(EXPORTS_PATH, `${options.fileName}.xlsx`), buffer, { + return fs.writeFileSync(path.resolve(EXPORTS_PATH, `${options.fileName}.xlsx`), buffer, { flag: 'w+', }) } From 52ca6b11082e09fa261f958728a9e7e6f5f0738d Mon Sep 17 00:00:00 2001 From: Hoang Lam Date: Fri, 12 Apr 2019 14:32:57 +0700 Subject: [PATCH 11/12] fea(products): fetch product and export xlsx --- .circleci/config.yml | 114 +++++++++++++++++++--------- .gitignore | 2 +- docker-compose.yml | 14 ++++ package.json | 4 +- src/services/__test__/order.test.ts | 33 ++++---- src/services/categories.ts | 20 +++++ src/services/mail.ts | 2 + src/services/product.ts | 91 ++++++++++++++++++++++ src/services/reports/category.ts | 91 ++++++++++++++++++++++ src/services/reports/product.ts | 77 +++++++++++++++++++ src/services/variant.ts | 2 + tslint.json | 15 ++++ 12 files changed, 411 insertions(+), 54 deletions(-) create mode 100644 docker-compose.yml create mode 100644 src/services/categories.ts create mode 100644 src/services/product.ts create mode 100644 src/services/reports/category.ts create mode 100644 src/services/reports/product.ts create mode 100644 tslint.json diff --git a/.circleci/config.yml b/.circleci/config.yml index 8d43158..a0518bf 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,38 +1,82 @@ -version: 2 # use CircleCI 2.0 -jobs: # a collection of steps - build: # runs not using Workflows must have a `build` job as entry point - working_directory: ~/mern-starter # directory where steps will run - docker: # run the steps with Docker - - image: circleci/node:4.8.2 # ...with this image as the primary container; this is where all `steps` will run - - image: mongo:3.4.4 # and this image as the secondary service container - steps: # a collection of executable commands - - checkout # special step to check out source code to working directory +version: 2.1 +jobs: + test: + docker: + - image: circleci/node:lts + steps: + - checkout + - restore_cache: + keys: + - yarn-lock-{{ checksum "yarn.lock" }} - run: - name: update-npm - command: 'sudo npm install -g npm@latest' - - restore_cache: # special step to restore the dependency cache - # Read about caching dependencies: https://circleci.com/docs/2.0/caching/ - key: dependency-cache-{{ checksum "package.json" }} + name: Install dependencies + command: yarn install && ls -l node_modules - run: - name: install-npm-wee - command: npm install - - save_cache: # special step to save the dependency cache - key: dependency-cache-{{ checksum "package.json" }} + name: Run tests + command: | + yarn test + yarn category + # - run: + # name: Run tests + # command: | + # yarn test + # yarn build + - save_cache: + key: yarn-lock-{{ checksum "yarn.lock" }} paths: - - ./node_modules - - run: # run tests - name: test - command: npm test - - run: # run coverage report - name: code-coverage - command: './node_modules/.bin/nyc report --reporter=text-lcov' - - store_artifacts: # special step to save test results as as artifact - # Upload test summary for display in Artifacts: https://circleci.com/docs/2.0/artifacts/ - path: test-results.xml - prefix: tests - - store_artifacts: # for display in Artifacts: https://circleci.com/docs/2.0/artifacts/ - path: coverage - prefix: coverage - - store_test_results: # for display in Test Summary: https://circleci.com/docs/2.0/collect-test-data/ - path: test-results.xml - # See https://circleci.com/docs/2.0/deployment-integrations/ for deploy examples \ No newline at end of file + - node_modules + + build_docker_images: + docker: + - image: circleci/node:lts + steps: + - checkout + - setup_remote_docker: + docker_layer_caching: true + - run: + name: Build docker image + command: docker build -t $DOCKERHUB_REPO:$CIRCLE_SHA1 -t $DOCKERHUB_REPO:master -f Dockerfile . + - run: + name: Publish Docker Image to Docker Hub + command: | + echo "$DOCKERHUB_PASSWORD" | docker login -u "$DOCKERHUB_USER" --password-stdin + docker push $DOCKERHUB_REPO:$CIRCLE_SHA1 + docker push $DOCKERHUB_REPO:master + push_release_image_tags: + docker: + - image: circleci/node:lts + steps: + - setup_remote_docker: + docker_layer_caching: true + - run: echo 'export VERSION_TAG=$(echo $CIRCLE_TAG | tr -d v)' >> $BASH_ENV + - run: + name: Tag & publish docker image + command: | + echo $VERSION_TAG + echo "$DOCKERHUB_PASSWORD" | docker login -u "$DOCKERHUB_USER" --password-stdin + docker pull $DOCKERHUB_REPO:$CIRCLE_SHA1 + docker tag $DOCKERHUB_REPO:$CIRCLE_SHA1 $DOCKERHUB_REPO:latest + docker tag $DOCKERHUB_REPO:$CIRCLE_SHA1 $DOCKERHUB_REPO:$VERSION_TAG + docker push $DOCKERHUB_REPO:latest + docker push $DOCKERHUB_REPO:$VERSION_TAG +workflows: + version: 2.1 + + test_and_build_docker_image: + jobs: + - test + - build_docker_images: + requires: + - test + filters: + branches: + only: + - report-product + - push_release_image_tags: + requires: + - build_docker_images + filters: + tags: + only: /^v.*/ + branches: + ignore: /.*/ diff --git a/.gitignore b/.gitignore index 4413610..fd27086 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ node_modules/ dist/ .env -docker-compose.yml +# docker-compose.yml exports/ orders.json diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..4f282a6 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,14 @@ +FROM node:10-alpine +WORKDIR /app + +ADD ["package.json", "yarn.lock", "./"] + +RUN yarn install + +ADD . . + +RUN yarn build + +EXPOSE 4000 + +CMD ["yarn", "category"] diff --git a/package.json b/package.json index 4f2c40f..8d8f1d4 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,8 @@ "test": "jest", "tsc": "tsc", "dev": "tsnd -r dotenv/config --respawn --transpileOnly src/services/reports/order.ts", - "product": "tsnd -r dotenv/config --respawn --transpileOnly src/services/product.ts", - "category": "tsnd -r dotenv/config --respawn --transpileOnly src/services/category.ts", + "product": "tsnd -r dotenv/config --respawn --transpileOnly src/services/reports/product.ts", + "category": "tsnd -r dotenv/config --respawn --transpileOnly src/services/reports/category.ts", "dev:production-order": "set NODE_ENV=production && tsnd -r dotenv/config --respawn --transpileOnly src/services/reports/order.ts" }, "author": "", diff --git a/src/services/__test__/order.test.ts b/src/services/__test__/order.test.ts index 722ad29..aed717c 100644 --- a/src/services/__test__/order.test.ts +++ b/src/services/__test__/order.test.ts @@ -1,5 +1,6 @@ import moment from 'moment' -import {getData} from "../order"; +import { fetchOrders } from 'services/order'; +// import {getData} from "../order"; var since = moment().startOf('day').subtract(1, 'days').format() // since.toStrin @@ -10,21 +11,21 @@ var since = moment().startOf('day').subtract(1, 'days').format() // }); // }); -describe("Test order services", () => { - it("Test since and util is not empty", async () => { - // expect.assertions(1) - try { - await getData({ - since: new Date(), - util: '', - }) +// describe("Test order services", () => { +// it("Test since and util is not empty", async () => { +// // expect.assertions(1) +// try { +// await fetchOrders({ +// since: new Date(), +// util: '', +// }) - } catch (err) { - console.log(err) - expect(err).toMatchObject({ +// } catch (err) { +// console.log(err) +// expect(err).toMatchObject({ - }) - } - }); -}); +// }) +// } +// }); +// }); diff --git a/src/services/categories.ts b/src/services/categories.ts new file mode 100644 index 0000000..8996a00 --- /dev/null +++ b/src/services/categories.ts @@ -0,0 +1,20 @@ +import axios from 'axios' +const request = axios.create({ + baseURL: 'https://api.storelammoc.vn/', + timeout: 15e3, + headers: { 'x-access-token': process.env.TOKEN_KEY }, +}) + +export const fetchCategories = async (options: { limit?: number; variantSKU?: string }) => { + try { + const response = await request.get(`/v2/categories`, { + params: { + ...options, + }, + }) + + return response.data + } catch (error) { + throw error + } +} diff --git a/src/services/mail.ts b/src/services/mail.ts index fc03d47..2d15130 100644 --- a/src/services/mail.ts +++ b/src/services/mail.ts @@ -66,5 +66,7 @@ export async function sendMail( return await sendMail(options, retryCount + 1) } throw err + // } finally { + // console.log('Send Mail Done !') } } diff --git a/src/services/product.ts b/src/services/product.ts new file mode 100644 index 0000000..5be85ff --- /dev/null +++ b/src/services/product.ts @@ -0,0 +1,91 @@ +import axios from 'axios' +import lodash from 'lodash' + +const request = axios.create({ + baseURL: 'https://api.storelammoc.vn', + timeout: 15e3, + headers: { 'x-access-token': process.env.TOKEN_KEY }, +}) + +const PRODUCTS_REPORT_COLUMNS = [ + 'Tên sản phẩm', + 'Hãng sản xuất', + 'SKU', + 'Tồn kho', + 'Giá', + 'Tên', + 'Khối lượng', +] + +export async function fetchProducts(options: { + page?: number + perPage?: number + categoryId?: string +}): Promise<{ products: any[]; pages: number }> { + try { + const response = await request.get(`/v2/products`, { + params: { + ...options, + }, + }) + + return response.data + } catch (error) { + throw error + } +} + +export const fetchProductsSequential = async ({ + productsList = [], + page = 0, + perPage, +}: { + productsList?: any[] + page?: number + perPage: number +}): Promise => { + const data = await fetchProducts({ page, perPage }) + const { products = [], pages = 0 } = data || {} + const processedProducts = lodash.flatten( + products.map((product) => { + const name = product.name + const manufacturer = product.manufacturer + const otherDetails = [name, manufacturer] + + const productItem = product.variants.map((item: any) => { + const instock = item.inStock + const price = item.price + const name = item.name + const sku = item.sku + const weight = item.weight + return { + sku, + instock, + price, + name, + weight, + } + }) + + return productItem.map((item: any) => [ + ...otherDetails, + item.sku, + item.instock, + item.price, + item.name, + item.weight, + ]) + }), + ) + + if (page < pages) { + console.log(page * perPage) + return await fetchProductsSequential({ + page: page + 1, + perPage, + productsList: productsList.concat(processedProducts), + }) + } + productsList.unshift(PRODUCTS_REPORT_COLUMNS) + return productsList +} diff --git a/src/services/reports/category.ts b/src/services/reports/category.ts new file mode 100644 index 0000000..f6e5f43 --- /dev/null +++ b/src/services/reports/category.ts @@ -0,0 +1,91 @@ +import fs from 'fs' +import moment from 'moment' +import { fetchCategories } from '../categories' +import { createXlSXfile } from '../../utils/xlsx' +import { sendNotification } from '../../utils/slack-notification' +import { sendMail } from '../mail' + +const EXPORTS_PATH = process.env.EXPORTS_PATH || 'exports' +const REPORT_DATE = moment() + .format('YYYY-MM-DD') + .toString() + +export const createCategoriesXlSXfileAndSendMail = async () => { + const MAIL_CATE_SEND_FROM = process.env.MAIL_SEND_FROM || '' + const MAIL_CATE_SEND_TO = process.env.MAIL_SEND_TO || '' + const MAIL_CATE_SEND_CC = process.env.MAIL_SEND_CC + const MAIL_CATE_SEND_BCC = process.env.MAIL_SEND_BCC + const MAIL_CATE_SUBJECT = process.env.MAIL_SUBJECT || 'Categories list periodically' + + if (!fs.existsSync(EXPORTS_PATH)) { + await fs.mkdirSync(EXPORTS_PATH, { recursive: false }) + } + + const categoriesData = await fetchCategories({}) + const processedCategories = categoriesData.map((category: any) => { + const cateName = category.name + const cateId = category._id + const cateParentId = category.parentId + if (cateParentId != null) { + const cateParent = categoriesData + .filter((category: any) => category._id == cateParentId) + .map((cate: any) => cate.name)[0] + const options = [cateId, cateName, cateParent] + return options + } else { + const options = [cateId, cateName, cateParentId] + return options + } + }) + await createXlSXfile({ + data: processedCategories, + fileName: `categories-${REPORT_DATE}`, + title: `categories-${REPORT_DATE}`, + }) + .catch((err) => { + sendNotification({ + status: 'danger', + title: 'Category List Periodically', + subtitle: 'There was a failure in createXLSXFile.', + }) + throw err + }) + .finally(() => { + console.log('Export Category XLSX Done !') + }) + + await sendMail({ + mailCc: MAIL_CATE_SEND_CC, + mailBcc: MAIL_CATE_SEND_BCC, + fileName: `categories${REPORT_DATE}.xlsx`, + mailFrom: MAIL_CATE_SEND_FROM, + mailTo: MAIL_CATE_SEND_TO, + filePath: `./${EXPORTS_PATH}/categories-${REPORT_DATE}.xlsx`, + subject: MAIL_CATE_SUBJECT, + }).catch((err) => { + sendNotification({ + status: 'danger', + title: 'Categories List Periodically', + subtitle: 'There was a failure in sendMail.', + }) + throw err + }) +} +;(async () => { + try { + await createCategoriesXlSXfileAndSendMail() + } catch (err) { + console.log(err) + throw err + } finally { + const notifitionStatus = 'good' + const notifitionTitle = ':heavy_check_mark: Categories list periodically' + const notifitionSubtitle = ':100: Successed ' + sendNotification({ + status: notifitionStatus, + subtitle: notifitionSubtitle, + title: notifitionTitle, + code: 200, + }) + } +})() diff --git a/src/services/reports/product.ts b/src/services/reports/product.ts new file mode 100644 index 0000000..a6a396e --- /dev/null +++ b/src/services/reports/product.ts @@ -0,0 +1,77 @@ +import { fetchProductsSequential } from '../product' +import { createXlSXfile } from '../../utils/xlsx' +import moment = require('moment') +import fs from 'fs' +import { sendNotification } from '../../utils/slack-notification' +import { sendMail } from '../mail' + +const EXPORTS_PATH = process.env.EXPORTS_PATH || 'exports' +const REPORT_DATE = moment() + .format('YYYY-MM-DD') + .toString() + +const createProductXlSXfileAndSendMail = async () => { + const MAIL_PRODUCT_SEND_FROM = process.env.MAIL_SEND_FROM || '' + const MAIL_PRODUCT_SEND_TO = process.env.MAIL_SEND_TO || '' + const MAIL_PRODUCT_SEND_CC = process.env.MAIL_SEND_CC + const MAIL_PRODUCT_SEND_BCC = process.env.MAIL_SEND_BCC + const MAIL_PRODUCT_SUBJECT = process.env.MAIL_SUBJECT || 'Product list periodically' + + if (!fs.existsSync(EXPORTS_PATH)) { + await fs.mkdirSync(EXPORTS_PATH, { recursive: false }) + } + + const productsData = await fetchProductsSequential({ perPage: 50 }) + + await createXlSXfile({ + data: productsData, + fileName: `products-${REPORT_DATE}`, + title: `products-${REPORT_DATE}`, + }) + .catch((err) => { + sendNotification({ + status: 'danger', + title: 'Product List Periodically', + subtitle: 'There was a failure in createXLSXFile.', + }) + throw err + }) + .finally(() => { + console.log('Export XLSX Done !') + }) + + await sendMail({ + mailCc: MAIL_PRODUCT_SEND_CC, + mailBcc: MAIL_PRODUCT_SEND_BCC, + fileName: `products${REPORT_DATE}.xlsx`, + mailFrom: MAIL_PRODUCT_SEND_FROM, + mailTo: MAIL_PRODUCT_SEND_TO, + filePath: `./${EXPORTS_PATH}/products-${REPORT_DATE}.xlsx`, + subject: MAIL_PRODUCT_SUBJECT, + }).catch((err) => { + sendNotification({ + status: 'danger', + title: 'Product List Periodically', + subtitle: 'There was a failure in sendMail.', + }) + throw err + }) +} +;(async () => { + try { + await createProductXlSXfileAndSendMail() + } catch (err) { + console.log(err) + throw err + } finally { + const notifitionStatus = 'good' + const notifitionTitle = ':heavy_check_mark: Product list periodically' + const notifitionSubtitle = ':100: Successed ' + sendNotification({ + status: notifitionStatus, + subtitle: notifitionSubtitle, + title: notifitionTitle, + code: 200, + }) + } +})() diff --git a/src/services/variant.ts b/src/services/variant.ts index d6780fe..ac1fe01 100644 --- a/src/services/variant.ts +++ b/src/services/variant.ts @@ -30,3 +30,5 @@ export async function getVariantsByIds(ids: string[]) { throw e } } + + diff --git a/tslint.json b/tslint.json new file mode 100644 index 0000000..2629148 --- /dev/null +++ b/tslint.json @@ -0,0 +1,15 @@ +{ + "extends": ["tslint:recommended", "tslint-config-prettier"], + "linterOptions": { + "exclude": ["config/**/*.js", "node_modules/**/*.ts", "coverage/lcov-report/*.js"] + }, + "rules": { + "no-console": { + "severity": "warn" + }, + "object-literal-sort-keys": false, + "ordered-imports": { + "severity": "warn" + } + } +} From 735036e6cb9514464f00ea4a8d8e1c2d57aa3949 Mon Sep 17 00:00:00 2001 From: Hoang Lam Date: Wed, 17 Apr 2019 16:20:37 +0700 Subject: [PATCH 12/12] feat(category): fetch category, export xlsx and send mail --- .circleci/config.yml | 122 ++++++++++++++++++------------- .gitignore | 3 +- docker-compose.yml | 14 ---- exportData | Bin 16639 -> 0 bytes package-lock.json | 16 ++++ package.json | 11 ++- src/services/mail.ts | 4 +- src/services/reports/category.ts | 12 ++- src/services/reports/product.ts | 7 +- 9 files changed, 109 insertions(+), 80 deletions(-) delete mode 100644 docker-compose.yml delete mode 100644 exportData diff --git a/.circleci/config.yml b/.circleci/config.yml index a0518bf..a97d4f4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,82 +1,100 @@ -version: 2.1 +version: 2 jobs: - test: + build: docker: - image: circleci/node:lts + working_directory: ~/app/reporter steps: - checkout - restore_cache: keys: - - yarn-lock-{{ checksum "yarn.lock" }} + - v1-dependencies-{{ checksum "package.json" }} + - v1-dependencies- - run: name: Install dependencies command: yarn install && ls -l node_modules - - run: - name: Run tests - command: | - yarn test - yarn category - # - run: - # name: Run tests - # command: | - # yarn test - # yarn build + - run: pwd && ls -la - save_cache: - key: yarn-lock-{{ checksum "yarn.lock" }} paths: - node_modules - - build_docker_images: + key: v1-dependencies-{{ checksum "package.json" }} + - persist_to_workspace: + root: ~/app + paths: + - reporter + report-order: docker: - image: circleci/node:lts + working_directory: ~/app/reporter steps: - - checkout - - setup_remote_docker: - docker_layer_caching: true + - attach_workspace: + at: ~/app + - run: pwd && ls -la - run: - name: Build docker image - command: docker build -t $DOCKERHUB_REPO:$CIRCLE_SHA1 -t $DOCKERHUB_REPO:master -f Dockerfile . + name: Fetch ORDER data, create xlsx and send mail + command: yarn order:ci + + report-product: + docker: + - image: circleci/node:lts + working_directory: ~/app/reporter + steps: + - attach_workspace: + at: ~/app + - run: pwd && ls -la - run: - name: Publish Docker Image to Docker Hub - command: | - echo "$DOCKERHUB_PASSWORD" | docker login -u "$DOCKERHUB_USER" --password-stdin - docker push $DOCKERHUB_REPO:$CIRCLE_SHA1 - docker push $DOCKERHUB_REPO:master - push_release_image_tags: + name: Fetch PRODUCT data, create xlsx and send mail + command: yarn product:ci + + report-category: docker: - image: circleci/node:lts + working_directory: ~/app/reporter steps: - - setup_remote_docker: - docker_layer_caching: true - - run: echo 'export VERSION_TAG=$(echo $CIRCLE_TAG | tr -d v)' >> $BASH_ENV + - attach_workspace: + at: ~/app + - run: pwd && ls -la - run: - name: Tag & publish docker image - command: | - echo $VERSION_TAG - echo "$DOCKERHUB_PASSWORD" | docker login -u "$DOCKERHUB_USER" --password-stdin - docker pull $DOCKERHUB_REPO:$CIRCLE_SHA1 - docker tag $DOCKERHUB_REPO:$CIRCLE_SHA1 $DOCKERHUB_REPO:latest - docker tag $DOCKERHUB_REPO:$CIRCLE_SHA1 $DOCKERHUB_REPO:$VERSION_TAG - docker push $DOCKERHUB_REPO:latest - docker push $DOCKERHUB_REPO:$VERSION_TAG -workflows: - version: 2.1 + name: Fetch CATEGORY data, create xlsx and send mail + command: yarn category:ci - test_and_build_docker_image: +workflows: + version: 2 + Orders Report: + triggers: + - schedule: + cron: '0 2 * * *' + filters: + branches: + only: master jobs: - - test - - build_docker_images: + - build + - report-order: requires: - - test + - build + + Product Report: + triggers: + - schedule: + cron: '0 2 * * 1' filters: branches: - only: - - report-product - - push_release_image_tags: + only: master + jobs: + - build + - report-product: requires: - - build_docker_images + - build + + Category Report: + triggers: + - schedule: + cron: '0 2 * * 1' filters: - tags: - only: /^v.*/ branches: - ignore: /.*/ + only: master + jobs: + - build + - report-category: + requires: + - build diff --git a/.gitignore b/.gitignore index fd27086..0d9a17f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ node_modules/ dist/ .env -# docker-compose.yml exports/ -orders.json + diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 4f282a6..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,14 +0,0 @@ -FROM node:10-alpine -WORKDIR /app - -ADD ["package.json", "yarn.lock", "./"] - -RUN yarn install - -ADD . . - -RUN yarn build - -EXPOSE 4000 - -CMD ["yarn", "category"] diff --git a/exportData b/exportData deleted file mode 100644 index 57a1554d7e81855d5a21311e487cf33c02412897..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16639 zcmeHOON=DRS#Cc7*=Vs>_5p#ALUlkO=&s6o^mH}dwVHm^^h{6pw7S}}Ymj9{Wkyxy zbiOt-tGdhLfOZ8ewg6#_VGl^&1KNXSJZM+K-dPKYK|b3T5C@P(v%3};X)hqejqi`h z%F4*f>c>o5VA$EMuB^x}{`lj6#UHPO-H&|q;|ug}^$&l0|L?#2o6r9GhZYv-^DN3N z-#iHYAS#=FD3(rLXYD!sVIvVnw(qT%jHOD63D5K`+v~5FK6BJwTq&_A=AOkJ-xKSl zv4~2~wVwI-ni;GT5h5qT*cK53B`;bvgZ0uNj)T>5IWh;r<7}=)~Uc z9zoDiskL@J2prqwF|4iC83+;I>a3Nsg=;&4lkpvJJB(Uuqj+^B%-9cEWPbsws+L%n zM}o+$mqt9axfg>h!PF1=>;zF9wx0LH zdi=441^WCfOzqSupQMr~OMw_qXL2by<)0TZjwlGBSJ}AhBwo*LZ{oG#MQ9XRZ73XR zOwqs&$i6Ha7EQ5?g`&4!x^ArQ*5Qn6vgNzBIDv-JXGR_A`u;J!xM{7IDg;#`%3`+= zn_6q3f5O10fmTdR>VQ4Yb_d)X zupRF8b>xPex9)%9kd1U->|vP{7oHmG~nKV z9ofRuVYhOy5K>?O+d6DlhuyZlW7gpwI~Z^`MV6tmRBzQ(Z}c>38P}T2-cCAud*1?+qXVhP5RvJd5Sy|4(wlo+y%xZ+e577%A5s7dl zO0A8D-?rJ^zkAtZ=*NKR?QClVTKoQzQL8L3Zgnp8s7en~6+Wu5YkL|}U9jw-D-uT?4){ngF+uWspF9vt4;-0pO4 z?%&WU?CLNYpd(5sFc84uUDr34MqU}2f;BXe05rHIM6cFs8kJse1w-;RQMD>7)#sAn z4&R;wsio~|#!^d-g`SkLtfWxs{u{MfW2w}orNCGkI(QRc%~)zxqiz`W9BfO6?f7K; z``l&wunV2ywgxS>E7rNOH`7M@A{F-@zBXp=!>`$_|L_fynUCIj-DNQ!GoxOqJkN}3 z!*y8)sg5JYNh`1+RXq8fx1vOndN+2G(#kT_mN%m_-rdrA(VA;VI_!Kq>h+qUUh5ev zu%Ak$TIpTm&Dx4)M}?4SB+=}sp4t&TDLcBROJJj(v!j)zYmKT=Z7$>gsd-A9+XPlO zqeiu67)H)Cw{+Nk|KXd6?70ZKt%It|m6hejGBS`wN`W#_v8@AdfA{{Q2d~-8{O*0u zj_pTp{f*1I4`1^TW8QkeI)+iLZnW8kkMxseiNZd#pLz380;=k5@2JuSbdfiXM4!_cR6 zH~W)$L$@o!4#hm{dUS*YoyH~>y{M-)c0zGQTu8oaBYflbD&j9CD|r1Z9cPLAYw;A-riB z(~NNP?+{QH#IIA4`il<-&iMKWnC6y6d z_#HVlM8*a75V8{D+sRa$3QQ-Ivt5yhO4X29>)0SfG>$f%kd3&rUP3#Te?rDvVh)dD zyg;5U|4GsD)ufUW ZK*Ovd1dPxQAxGLKW``xrrqg`)a+e+z6fD_N+wzs!8kttKs z&sP%wH%-im6)#zu_4bOfF`=0OsmEE`o0UeTuHz+0;cGKIH#asm8qI{43`jjv7jZz#3NEG{j6~TPV`&Dh3&&^MmWZOFI(I_!H>31jj(An5_C06p>PW159~aTQ z&3!+M-13h%u`Cf3VLO=EWwAG-W0)vs2Yy@x-^0>N7EZsR2P}NdQ#aPJGB_t}>mU)IdH}US-j?;<9 zj<_Ak+&jVmWVKO9^|0!LiDNnu3kG;3Peub8t{-w)hlPIpyd4iZ15P`7wDitb`%yx= zAF;s4ihM;D7t$s?a{pM(HswN*iX0J-_k2qg*Ru1?l#Jx6k7+x#s2Zvffr~bVO#y&q zn0lhd48_%4vFTkiBx|#+rClZwPo$rA4TY&Rk=ZchLy+<^wylYGWRlzwa!Et!XrdM= zlOK(Wl3uHh46tyoRHPMWRENzD;#9FW+rO#mE+@@YY7pA_Oh~hs&%_0VJRvX@GAic1 z$?>wj<#_C5z0_<}8zp9PoUFiZ4bnN&4WP7$azXC∾wun~*slOQoeGtb9>g+qB2c zVyiN+cri!nni~gU6mRippjwcx5>+`KwWRp3Hn3}ORUJqyAsciq#zk!fCh|YV1yrr8 zR`hz<+RrLGQ$n&+Ka)u44{>;RqRV1*X1A$*+mlHZ{i(YNwBMMJtZ zCW7&y-DjbV;Upe}LL4A0N$Wd@n2`)Uilmbek`|R=HwqNnT`>~Q5yik|ioqo|fT_!9 zl7s1NcGungJGNa!)1J88yQ}t!_Ne+;Y z`qM0_KJf}|@M|^`w|_zwE;0aT##$AzW_nz0OqhcfQ>7H*5rf&lsuwA$;gJHQdg>w*QiV#-L`pT3WIiO$ACbP(c5}ie&L;jg+xw}$ zFDdLaiPD?Xd~vopXdq17eM>+1T}IC*p;HQ*|%qd5#A9q&-|m~EX-<{zmflnbdHl60Gh&{T~K zEv-mL6*Bf(R58I5PK}mnWjZc%PUWT*s9qA1T8?7~R3XK~oz~Rs@DBm^Y)~e|{IC?B~NGwC8v`elK zo7gHy7OQ*MxNyd*Sk_a9sS1>Xk~OFnU(Ycpuvrv)oEuB z=kH|~6z&`}#;q~VV`VCosRr8Z`k{sM{#iw+qSl%tdStf9>IU>3`vLxT{W$hrJXkjG z`yO}jSf12K9zfO%P1J*FA0HkO^;wGc~tb&52Bx?rf6r#lCUmMmYw#J z#&G7|8Sl%}HEnq(A=Q@W?@GDt4s8dyTY+pq z(>k3>TjmWh?>a(exoInqw53MKqx2!sAg#3|D`DGou|i$1!Rg-;OGHAwSuc?(CifxA^cJ>XP_IiT z<>%HLdDfB}Es9f^(vm!-w1f&g951;X8^YTjX&MkZZ(mG*bS7U!U%$e$sE*>}c89z` zvV{6u>PXR?2D)yL>j3teas7wNi&+Y2aZBs6szK$&HTrh1um+VE*AQ-TjnhSXQ(Wg{ zZXJ44T<2a;+?RWSnAaKks;K{G)QRT^0Oj*Ildp=JIipTs<)xN2VtQLF1)SW*c?eCV z*@^{LmU!@zcFE&c5;wVnOKYKxcqYn0!ldCS16OET^Eoal(jg`c$$b~M@zVyK; zbJgYw8AXWSM&;$dH4ZZT^Uh)6{Y;da19 z8B%)N(ymPEd=;n?{Hkrw~wX!{Zlq3crJpw#V82I(myGXuaJ9?)$r73~0E=mGZb>yq&CvMZKLA7ui umnV=Pn5G22-_(Ywe8qflnO1DCNv7w7nuFb+_({Tk4F9zdn7-bkU;ho0$XO-; diff --git a/package-lock.json b/package-lock.json index d11ac56..db97826 100644 --- a/package-lock.json +++ b/package-lock.json @@ -549,6 +549,14 @@ "integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==", "dev": true }, + "@types/moment-timezone": { + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/@types/moment-timezone/-/moment-timezone-0.5.12.tgz", + "integrity": "sha512-hnHH2+Efg2vExr/dSz+IX860nSiyk9Sk4pJF2EmS11lRpMcNXeB4KBW5xcgw2QPsb9amTXdsVNEe5IoJXiT0uw==", + "requires": { + "moment": ">=2.14.0" + } + }, "@types/node": { "version": "11.13.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.0.tgz", @@ -4554,6 +4562,14 @@ "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" }, + "moment-timezone": { + "version": "0.5.23", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.23.tgz", + "integrity": "sha512-WHFH85DkCfiNMDX5D3X7hpNH3/PUhjTGcD0U1SgfBGZxJ3qUmJh5FdvaFjcClxOvB3rzdfj4oRffbI38jEnC1w==", + "requires": { + "moment": ">= 2.9.0" + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", diff --git a/package.json b/package.json index 8d8f1d4..eb33bb2 100644 --- a/package.json +++ b/package.json @@ -7,18 +7,23 @@ "test": "jest", "tsc": "tsc", "dev": "tsnd -r dotenv/config --respawn --transpileOnly src/services/reports/order.ts", - "product": "tsnd -r dotenv/config --respawn --transpileOnly src/services/reports/product.ts", - "category": "tsnd -r dotenv/config --respawn --transpileOnly src/services/reports/category.ts", - "dev:production-order": "set NODE_ENV=production && tsnd -r dotenv/config --respawn --transpileOnly src/services/reports/order.ts" + "product": "set NODE_ENV=production && tsnd -r dotenv/config --respawn --transpileOnly src/services/reports/product.ts", + "category": "set NODE_ENV=production && tsnd -r dotenv/config --respawn --transpileOnly src/services/reports/category.ts", + "order": "set NODE_ENV=production && tsnd -r dotenv/config --respawn --transpileOnly src/services/reports/order.ts", + "order:ci": "set NODE_ENV=production && ts-node -r dotenv/config --transpile-only src/services/reports/order.ts", + "product:ci": "set NODE_ENV=production && ts-node -r dotenv/config --transpile-only src/services/reports/product.ts", + "category:ci": "set NODE_ENV=production && ts-node -r dotenv/config --transpile-only src/services/reports/category.ts" }, "author": "", "license": "ISC", "dependencies": { + "@types/moment-timezone": "^0.5.12", "axios": "^0.18.0", "body-parser": "^1.18.3", "graphql": "^14.2.1", "lodash": "^4.17.11", "moment": "^2.24.0", + "moment-timezone": "^0.5.23", "node-xlsx": "^0.14.1", "nodemailer": "^6.1.0", "object-assign": "^4.1.1" diff --git a/src/services/mail.ts b/src/services/mail.ts index 2d15130..2f34e81 100644 --- a/src/services/mail.ts +++ b/src/services/mail.ts @@ -66,7 +66,7 @@ export async function sendMail( return await sendMail(options, retryCount + 1) } throw err - // } finally { - // console.log('Send Mail Done !') + } finally { + console.log('Send Mail Done !') } } diff --git a/src/services/reports/category.ts b/src/services/reports/category.ts index f6e5f43..0ba5662 100644 --- a/src/services/reports/category.ts +++ b/src/services/reports/category.ts @@ -1,21 +1,24 @@ import fs from 'fs' -import moment from 'moment' +import moment from 'moment-timezone' import { fetchCategories } from '../categories' import { createXlSXfile } from '../../utils/xlsx' import { sendNotification } from '../../utils/slack-notification' import { sendMail } from '../mail' +moment.tz.setDefault('Asia/Ho_Chi_Minh') const EXPORTS_PATH = process.env.EXPORTS_PATH || 'exports' const REPORT_DATE = moment() .format('YYYY-MM-DD') .toString() +const CATEGORIES_REPORT_COLUMNS = ['Id', 'Tên', 'Cha'] + export const createCategoriesXlSXfileAndSendMail = async () => { const MAIL_CATE_SEND_FROM = process.env.MAIL_SEND_FROM || '' const MAIL_CATE_SEND_TO = process.env.MAIL_SEND_TO || '' const MAIL_CATE_SEND_CC = process.env.MAIL_SEND_CC const MAIL_CATE_SEND_BCC = process.env.MAIL_SEND_BCC - const MAIL_CATE_SUBJECT = process.env.MAIL_SUBJECT || 'Categories list periodically' + const MAIL_CATE_SUBJECT = process.env.MAIL_SUBJECT || 'Update Category List' if (!fs.existsSync(EXPORTS_PATH)) { await fs.mkdirSync(EXPORTS_PATH, { recursive: false }) @@ -37,6 +40,7 @@ export const createCategoriesXlSXfileAndSendMail = async () => { return options } }) + processedCategories.unshift(CATEGORIES_REPORT_COLUMNS) await createXlSXfile({ data: processedCategories, fileName: `categories-${REPORT_DATE}`, @@ -45,7 +49,7 @@ export const createCategoriesXlSXfileAndSendMail = async () => { .catch((err) => { sendNotification({ status: 'danger', - title: 'Category List Periodically', + title: 'Update Category List', subtitle: 'There was a failure in createXLSXFile.', }) throw err @@ -57,7 +61,7 @@ export const createCategoriesXlSXfileAndSendMail = async () => { await sendMail({ mailCc: MAIL_CATE_SEND_CC, mailBcc: MAIL_CATE_SEND_BCC, - fileName: `categories${REPORT_DATE}.xlsx`, + fileName: `categories-${REPORT_DATE}.xlsx`, mailFrom: MAIL_CATE_SEND_FROM, mailTo: MAIL_CATE_SEND_TO, filePath: `./${EXPORTS_PATH}/categories-${REPORT_DATE}.xlsx`, diff --git a/src/services/reports/product.ts b/src/services/reports/product.ts index a6a396e..2a125bf 100644 --- a/src/services/reports/product.ts +++ b/src/services/reports/product.ts @@ -1,10 +1,11 @@ import { fetchProductsSequential } from '../product' import { createXlSXfile } from '../../utils/xlsx' -import moment = require('moment') +import moment from 'moment-timezone' import fs from 'fs' import { sendNotification } from '../../utils/slack-notification' import { sendMail } from '../mail' +moment.tz.setDefault('Asia/Ho_Chi_Minh') const EXPORTS_PATH = process.env.EXPORTS_PATH || 'exports' const REPORT_DATE = moment() .format('YYYY-MM-DD') @@ -15,7 +16,7 @@ const createProductXlSXfileAndSendMail = async () => { const MAIL_PRODUCT_SEND_TO = process.env.MAIL_SEND_TO || '' const MAIL_PRODUCT_SEND_CC = process.env.MAIL_SEND_CC const MAIL_PRODUCT_SEND_BCC = process.env.MAIL_SEND_BCC - const MAIL_PRODUCT_SUBJECT = process.env.MAIL_SUBJECT || 'Product list periodically' + const MAIL_PRODUCT_SUBJECT = process.env.MAIL_SUBJECT || 'Update Product List' if (!fs.existsSync(EXPORTS_PATH)) { await fs.mkdirSync(EXPORTS_PATH, { recursive: false }) @@ -31,7 +32,7 @@ const createProductXlSXfileAndSendMail = async () => { .catch((err) => { sendNotification({ status: 'danger', - title: 'Product List Periodically', + title: 'Update Product List', subtitle: 'There was a failure in createXLSXFile.', }) throw err