first commit
This commit is contained in:
51
src/Amazon.js
Executable file
51
src/Amazon.js
Executable file
@@ -0,0 +1,51 @@
|
||||
const Product = require('./Product')
|
||||
, parser = require('./Parser')
|
||||
, client = require('./Client');
|
||||
|
||||
|
||||
|
||||
const amazon = {
|
||||
us: {
|
||||
data: {
|
||||
url: 'https://www.amazon.com',
|
||||
categories: {},
|
||||
subcategories: {}
|
||||
},
|
||||
utils: {
|
||||
fakeTitle: (length, calls, leadingTxt) => {
|
||||
let result = '';
|
||||
let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let charactersLength = characters.length;
|
||||
for (var i = 0; i < length; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||
}
|
||||
let lengthRand = length //Math.ceil(Math.random(3, length))
|
||||
return --calls ? amazon.us.utils.fakeTitle(lengthRand, calls, leadingTxt + result + '-') : leadingTxt + result;
|
||||
}
|
||||
},
|
||||
task: {
|
||||
categories: () => {
|
||||
return client.request({ amazon: amazon, call: 'categories' }).then(parser.categories);
|
||||
},
|
||||
subCategories: (category) => {
|
||||
return client.request({ amazon: amazon, category: category, call: 'subCategories' }).then(parser.subCategories);
|
||||
},
|
||||
|
||||
productLink: (link) => {
|
||||
return client.request({ amazon: amazon, link: link, call: 'productLinks' }).then(parser.productLinks);
|
||||
},
|
||||
|
||||
productDetail: (asin) => {
|
||||
let product = new Product()
|
||||
return product.fetch(amazon, asin)
|
||||
},
|
||||
|
||||
offerListing: (asin) => {
|
||||
return client.request({ amazon: amazon, asin:asin, call: 'offerListing' }).then(parser.offerListing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = amazon;
|
||||
53
src/Client.js
Executable file
53
src/Client.js
Executable file
@@ -0,0 +1,53 @@
|
||||
const fetch = require("node-fetch");
|
||||
|
||||
|
||||
const Client = {
|
||||
headers: {
|
||||
'authority' : 'www.amazon.com',
|
||||
'method' : 'GET',
|
||||
'path' : '',
|
||||
'scheme' : 'https',
|
||||
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3',
|
||||
'accept-encoding' : 'gzip, deflate, br',
|
||||
'accept-language' : 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7',
|
||||
'cache-control' : 'max-age=0',
|
||||
'sec-fetch-mode' : 'navigate',
|
||||
'sec-fetch-site' : 'none',
|
||||
'sec-fetch-user' : '?1',
|
||||
'upgrade-insecure-requests' : '1',
|
||||
'user-agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36',
|
||||
cookie: 'lc-main=ko_KR;i18n-prefs=KRW'
|
||||
},
|
||||
request: async (args) => {
|
||||
let url = Client[args.call](args)
|
||||
Client.headers['path'] = url.replace(args.amazon.us.data.url, '')
|
||||
return fetch(url, { headers: Client.headers }).then(res => res.textConverted());
|
||||
},
|
||||
offerListing: (args) => {
|
||||
let url = args.amazon.us.data.url + '/dp/offer-listing/' + args.asin + '/ref=dp_olp_new_mbc?ie=UTF8&condition=new';
|
||||
console.log('- request product offer listing page url : ' + url);
|
||||
return url;
|
||||
},
|
||||
productDetail: (args) => {
|
||||
let url = args.amazon.us.data.url + '/' + args.amazon.us.utils.fakeTitle(7, 4, '') + '/dp/' + args.asin;
|
||||
console.log('- request product detail page url : ' + url);
|
||||
return url;
|
||||
},
|
||||
categories: (args) => {
|
||||
let url = args.amazon.us.data.url + '/gp/site-directory';
|
||||
console.log('- request category page url : ' + url)
|
||||
return url;
|
||||
},
|
||||
subCategories: (args) => {
|
||||
let url = args.amazon.us.data.url + args.category.link;
|
||||
console.log('- request sub category page url : ' + url)
|
||||
return url;
|
||||
},
|
||||
productLinks: (args) => {
|
||||
let url = args.amazon.us.data.url + args.link;
|
||||
console.log('- request product links page url : ' + url)
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Client;
|
||||
54
src/Parser.js
Executable file
54
src/Parser.js
Executable file
@@ -0,0 +1,54 @@
|
||||
const cheerio = require("cheerio");
|
||||
|
||||
const Parser = {
|
||||
categories: body => {
|
||||
let data = []
|
||||
const $ = cheerio.load(body)
|
||||
$(".a-container a").map((k, v) => {
|
||||
data.push({
|
||||
link: $(v).attr('href'),
|
||||
title: $(v).text()
|
||||
});
|
||||
})
|
||||
console.log('- fetching ' + data.length + ' category links completed')
|
||||
return data
|
||||
},
|
||||
subCategories: body => {
|
||||
let data = []
|
||||
const $ = cheerio.load(body)
|
||||
$(".left_nav.browseBox li a").map((k, v) => {
|
||||
data.push({
|
||||
link: $(v).attr('href'),
|
||||
title: $(v).text()
|
||||
});
|
||||
})
|
||||
return data;
|
||||
},
|
||||
productLinks: body => {
|
||||
let data = []
|
||||
const $ = cheerio.load(body)
|
||||
$("#mainResults a.a-link-normal").map((k, v) => {
|
||||
let title = $(v).find('h2').text().trim();
|
||||
if (title) {
|
||||
data.push({
|
||||
link: $(v).attr('href'),
|
||||
title: title
|
||||
});
|
||||
}
|
||||
})
|
||||
// console.log(data)
|
||||
amazon.us.data.subcategories = data;
|
||||
data.forEach((v, k) => {
|
||||
console.log(v.link)
|
||||
})
|
||||
},
|
||||
|
||||
offerListing: body => {
|
||||
const $ = cheerio.load(body)
|
||||
let stock = $('#olpOfferList .a-row.olpOffer').length;
|
||||
let price = $('#olpOfferList div.a-column.a-span2.olpPriceColumn').last().find('span').text().trim();
|
||||
return {stock: stock, price: price}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Parser
|
||||
118
src/Product.js
Executable file
118
src/Product.js
Executable file
@@ -0,0 +1,118 @@
|
||||
const cheerio = require("cheerio")
|
||||
, client = require('./Client');
|
||||
|
||||
|
||||
class Product {
|
||||
|
||||
constructor() {
|
||||
this.data = {}
|
||||
this.amazon = null
|
||||
}
|
||||
|
||||
async fetch(amazon, asin) {
|
||||
this.amazon = amazon;
|
||||
|
||||
let args = [{ amazon: this.amazon, asin: asin, call: 'productDetail', resolver: 'parseHtmlDetail' },
|
||||
{ amazon: this.amazon, asin: asin, call: 'offerListing', resolver: 'parseHtmlOfferListing' }]
|
||||
let fetchPromises = args.map(async args => {
|
||||
return await client.request(args)
|
||||
})
|
||||
|
||||
for (let k in fetchPromises) {
|
||||
await fetchPromises[k].then(body => this[args[k].resolver](body))
|
||||
}
|
||||
this.data.asin = asin
|
||||
return this.data
|
||||
}
|
||||
|
||||
parseHtmlDetail(body) {
|
||||
let $ = cheerio.load(body)
|
||||
this.data.title = $('#productTitle').text().trim()
|
||||
let merchant = $('#merchant-info a');
|
||||
if (!merchant.length) {
|
||||
merchant = $('#merchant-info');
|
||||
}
|
||||
this.data.seller = {
|
||||
title: merchant.text().replace('에서 판매하고 배송합니다.', '').replace('주문 처리', '').trim(),
|
||||
link: merchant.attr('href')
|
||||
}
|
||||
|
||||
this.data.img = $('#landingImage').attr('src')
|
||||
this.data.description = this.description(this.amazon, $)
|
||||
this.data.category = this.category(this.amazon, $)
|
||||
this.data.price = this.price(this.amazon, $)
|
||||
this.data.colors = this.colors(this.amazon, $)
|
||||
this.data.colorsASIN = this.colorsASIN(this.amazon, $)
|
||||
}
|
||||
|
||||
description(amazon, $) {
|
||||
return {
|
||||
content: $('#aplus').html(),
|
||||
spec: $('#descriptionAndDetails').html(),
|
||||
videos: function () {
|
||||
if ($('#vse-rel-videos-carousel ol li a').length) {
|
||||
return {
|
||||
link: amazon.us.data.url + $('#vse-rel-videos-carousel ol li a').eq(0).attr('href'),
|
||||
title: $('#vse-rel-videos-carousel ol li a div.vse-video-content .vse-video-title-text').eq(0).text().trim()
|
||||
}
|
||||
} else return {}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
parseHtmlOfferListing(body) {
|
||||
let $ = cheerio.load(body)
|
||||
let stock = $('#olpOfferList .a-row.olpOffer').length;
|
||||
let price = $('#olpOfferList div.a-column.a-span2.olpPriceColumn').last().find('span').text().trim();
|
||||
// let seller = $('#olpOfferList .olpSellerName').last().find('span a').text().trim();
|
||||
// let sellerLink = $('#olpOfferList .olpSellerName').last().find('span a').attr('href');
|
||||
this.data.stock = stock;
|
||||
this.data.price = price;
|
||||
// this.data.seller = { title: seller, link: sellerLink }
|
||||
}
|
||||
|
||||
category(amazon, $) {
|
||||
let categoryPath = []
|
||||
$('#wayfinding-breadcrumbs_feature_div li a').map((k, n) => {
|
||||
categoryPath.push({ title: $(n).text().trim(), link: amazon.us.data.url + $(n).attr('href') })
|
||||
})
|
||||
return categoryPath
|
||||
}
|
||||
|
||||
price(amazon, $) {
|
||||
let price;
|
||||
price = $('#olp-upd-new > span > a').eq(0).text().trim()
|
||||
if (price) {
|
||||
if (price.split('$').length == 2) {
|
||||
return '$' + price.split('$')[1]
|
||||
}
|
||||
}
|
||||
price = $('#priceblock_ourprice').text().trim()
|
||||
if (price) return price
|
||||
}
|
||||
|
||||
colors(amazon, $) {
|
||||
let colors = []
|
||||
$('li[id^=color_name_]').map((k, n) => {
|
||||
colors.push({
|
||||
img: $(n).find('img').attr('src'),
|
||||
title: $(n).find('img').attr('alt'),
|
||||
})
|
||||
})
|
||||
return colors
|
||||
}
|
||||
colorsASIN(amazon, $) {
|
||||
let colorsASIN = []
|
||||
$('li[id^=color_name_]').map((k, n) => {
|
||||
let asin = $(n).attr('data-defaultasin');
|
||||
if (!asin) {
|
||||
asin = $(n).attr('data-dp-url').split('/')[2];
|
||||
}
|
||||
colorsASIN.push(asin)
|
||||
})
|
||||
return colorsASIN
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = Product;
|
||||
117
src/component/PurchaseComponent.js
Normal file
117
src/component/PurchaseComponent.js
Normal file
@@ -0,0 +1,117 @@
|
||||
const logger = require('logops')
|
||||
, db = require("../model");
|
||||
|
||||
|
||||
|
||||
class PurchaseComponent {
|
||||
|
||||
getPurchaseData(data, now) {
|
||||
return {
|
||||
OrgCode: data.orgCode,
|
||||
PurchaseNo: data.purchaseNo,
|
||||
Status: data.status,
|
||||
CreatedAt: now
|
||||
}
|
||||
}
|
||||
|
||||
getPurchaseItemData(itemData, purchase, now) {
|
||||
return itemData.map(item => {
|
||||
return {
|
||||
PurchaseId: purchase.Id,
|
||||
ItemLink: item.itemLink,
|
||||
ItemName: item.itemName,
|
||||
ItemBrand: item.itemBrand,
|
||||
ItemUnitPrice: item.itemUnitPrice,
|
||||
ItemTax: item.itemTax,
|
||||
ItemCount: item.itemCount,
|
||||
Status: item.status,
|
||||
CreatedAt: now
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getSearchPurchaseItemClause(params) {
|
||||
let clause = { where: {}, raw: true }
|
||||
if (params.purchaseId) {
|
||||
clause.where.PurchaseId = Array.isArray(params.purchaseId) ? { [db.Sequelize.Op.in]: params.purchaseId } : params.purchaseId
|
||||
}
|
||||
if (params.id) {
|
||||
clause.where.Id = Array.isArray(params.id) ? { [db.Sequelize.Op.in]: params.id } : params.id
|
||||
}
|
||||
if (params.sdate && params.edate) {
|
||||
clause.where[params.daterangeby ? params.daterangeby : 'CreatedAt'] = { [db.Sequelize.Op.between]: [params.sdate, params.edate] }
|
||||
}
|
||||
if (params.limit) {
|
||||
clause.limit = parseInt(params.limit)
|
||||
}
|
||||
if (params.offset) {
|
||||
clause.offset = parseInt(params.offset)
|
||||
}
|
||||
if (params.column) {
|
||||
clause.attributes = Array.isArray(params.column) ? params.column : [params.column]
|
||||
}
|
||||
if (params.sort) {
|
||||
clause.order = [
|
||||
[params.sort, params.order ? params.order.toUpperCase() : 'ASC']
|
||||
]
|
||||
}
|
||||
return clause
|
||||
}
|
||||
|
||||
findPurchaseById(purchaseId) {
|
||||
let purchases = {}
|
||||
return db.purchase.findAll({ where: { Id: { [db.Sequelize.Op.in]: purchaseId } } }).then(rows => {
|
||||
rows.forEach(p => {
|
||||
purchases[p.Id] = p
|
||||
})
|
||||
return purchases
|
||||
})
|
||||
}
|
||||
|
||||
findPurchaseItemOptionByItemId(purchaseItemId) {
|
||||
let itemOptions = {}
|
||||
let clause = { where: { ItemId: { [db.Sequelize.Op.in]: purchaseItemId } } }
|
||||
return db.purchaseItemOption.findAll(clause).then(options => {
|
||||
options.forEach(opt => {
|
||||
if (itemOptions.hasOwnProperty(opt.ItemId) == false) {
|
||||
itemOptions[opt.ItemId] = []
|
||||
}
|
||||
itemOptions[opt.ItemId].push([opt.OptName, opt.OptValue])
|
||||
})
|
||||
return itemOptions
|
||||
});
|
||||
}
|
||||
|
||||
getSearchTotalPurchaseItem(params) {
|
||||
let clause = this.getSearchPurchaseItemClause(params)
|
||||
delete clause.limit;
|
||||
return db.purchaseItem.count(clause).then(cnt => cnt)
|
||||
}
|
||||
|
||||
findPurchaseByOrgCodeAndPurchaseNo(params) {
|
||||
let clause = { where: {} }
|
||||
if (params.sword) {
|
||||
clause.where.PurchaseNo = params.sword
|
||||
}
|
||||
if (params.orgCode) {
|
||||
clause.where.OrgCode = params.orgCode
|
||||
}
|
||||
return db.purchase.findAll(clause).then(purchases => {
|
||||
return purchases.map(p => p.Id)
|
||||
})
|
||||
}
|
||||
|
||||
replaceItemOption(itemId, itemOptions) {
|
||||
return db.purchaseItemOption.destroy({ where: { ItemId: itemId } }).then(a => {
|
||||
let options = itemOptions.map(opt => {
|
||||
if(opt.optValue) {
|
||||
return { ItemId: itemId, OptName: opt.optName, OptValue: opt.optValue }
|
||||
}
|
||||
})
|
||||
return db.purchaseItemOption.bulkCreate(options).then(inserted => inserted.length > 0)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = new PurchaseComponent
|
||||
20
src/controller/CrawlController.js
Normal file
20
src/controller/CrawlController.js
Normal file
@@ -0,0 +1,20 @@
|
||||
const service = require("../service/CrawlService")
|
||||
, logger = require('logops');
|
||||
|
||||
|
||||
class CrawlController {
|
||||
getMethodName(req) {
|
||||
return req.method.toLowerCase()
|
||||
+ req.params.what.split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join('')
|
||||
}
|
||||
|
||||
crawl(req, callback) {
|
||||
let fn = this.getMethodName(req);
|
||||
let params = Object.assign(req.body, req.query)
|
||||
params = Object.assign(params, req.params)
|
||||
logger.info('Invoking method TestService.%s', fn)
|
||||
service[fn](params, callback)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new CrawlController
|
||||
20
src/controller/PurchaseController.js
Normal file
20
src/controller/PurchaseController.js
Normal file
@@ -0,0 +1,20 @@
|
||||
const purchase = require("../service/PurchaseService")
|
||||
, logger = require('logops');
|
||||
|
||||
|
||||
class PurchaseController {
|
||||
getMethodName(req) {
|
||||
return req.method.toLowerCase()
|
||||
+ req.params.what.split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join('')
|
||||
}
|
||||
|
||||
purchase(req, callback) {
|
||||
let fn = this.getMethodName(req);
|
||||
let params = Object.assign(req.body, req.query)
|
||||
params = Object.assign(params, req.params)
|
||||
logger.info('Invoking method PurchaseService.%s', fn)
|
||||
purchase[fn](params, callback)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new PurchaseController
|
||||
85
src/model/IoBoundModel.js
Normal file
85
src/model/IoBoundModel.js
Normal file
@@ -0,0 +1,85 @@
|
||||
/* jshint indent: 2 */
|
||||
|
||||
module.exports = function (sequelize, DataTypes) {
|
||||
return sequelize.define('ioBound', {
|
||||
id: {
|
||||
autoIncrement: true,
|
||||
type: DataTypes.INTEGER(11).UNSIGNED,
|
||||
allowNull: false,
|
||||
primaryKey: true,
|
||||
field: 'id'
|
||||
},
|
||||
IoType: {
|
||||
type: DataTypes.STRING(2),
|
||||
allowNull: false,
|
||||
field: 'type'
|
||||
},
|
||||
ItemId: {
|
||||
type: DataTypes.STRING(20),
|
||||
allowNull: false,
|
||||
field: 'pcode'
|
||||
},
|
||||
Barcode: {
|
||||
type: DataTypes.STRING(30),
|
||||
allowNull: false,
|
||||
field: 'ean'
|
||||
},
|
||||
SerialNo: {
|
||||
type: DataTypes.STRING(30),
|
||||
allowNull: false,
|
||||
field: 'serialno'
|
||||
},
|
||||
ArtNo: {
|
||||
type: DataTypes.STRING(30),
|
||||
allowNull: false,
|
||||
comment: 'Article Number/PZN',
|
||||
field: 'artno'
|
||||
},
|
||||
Qty: {
|
||||
type: DataTypes.INTEGER(9).UNSIGNED,
|
||||
allowNull: false,
|
||||
comment: '실재고',
|
||||
field: 'qty'
|
||||
},
|
||||
OrgCode: {
|
||||
type: DataTypes.STRING(10),
|
||||
allowNull: false,
|
||||
field: 'c_code'
|
||||
},
|
||||
ShippingCode: {
|
||||
type: DataTypes.STRING(30),
|
||||
allowNull: false,
|
||||
field: 'wcode'
|
||||
},
|
||||
BoxCode: {
|
||||
type: DataTypes.STRING(30),
|
||||
allowNull: false,
|
||||
field: 'bcode'
|
||||
},
|
||||
CreatedBy: {
|
||||
type: DataTypes.STRING(30),
|
||||
allowNull: false,
|
||||
field: 'reg_user'
|
||||
},
|
||||
CreatedAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
field: 'regtime'
|
||||
},
|
||||
UpdatedBy: {
|
||||
type: DataTypes.STRING(10),
|
||||
allowNull: false,
|
||||
field: 'update_user'
|
||||
},
|
||||
UpdatedAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
field: 'update_time'
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
tableName: 'wms_in_out_bound',
|
||||
freezeTableName: true,
|
||||
timestamps: false
|
||||
});
|
||||
};
|
||||
27
src/model/IoTypeModel.js
Normal file
27
src/model/IoTypeModel.js
Normal file
@@ -0,0 +1,27 @@
|
||||
/* jshint indent: 2 */
|
||||
|
||||
module.exports = function (sequelize, DataTypes) {
|
||||
return sequelize.define('ioType', {
|
||||
code: {
|
||||
type: DataTypes.STRING(2),
|
||||
allowNull: false,
|
||||
primaryKey: true,
|
||||
field: 'code'
|
||||
},
|
||||
Name: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
field: 'name'
|
||||
},
|
||||
NameKr: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
field: 'name_kr'
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
tableName: 'wms_in_out_type',
|
||||
freezeTableName: true,
|
||||
timestamps: false
|
||||
});
|
||||
};
|
||||
38
src/model/Purchase.js
Normal file
38
src/model/Purchase.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/* jshint indent: 2 */
|
||||
|
||||
module.exports = function(sequelize, DataTypes) {
|
||||
return sequelize.define('purchase', {
|
||||
Id: {
|
||||
type: DataTypes.INTEGER(11).UNSIGNED,
|
||||
allowNull: false,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
field: 'seq'
|
||||
},
|
||||
OrgCode: {
|
||||
type: DataTypes.STRING(10),
|
||||
allowNull: true,
|
||||
field: 'company_code'
|
||||
},
|
||||
PurchaseNo: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
field: 'purchase_no'
|
||||
},
|
||||
Status: {
|
||||
type: DataTypes.ENUM('발주요청'),
|
||||
allowNull: false,
|
||||
field: 'status'
|
||||
},
|
||||
CreatedAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
field: 'created_at'
|
||||
}
|
||||
}, {
|
||||
tableName: 'ibl_purchase',
|
||||
freezeTableName: true,
|
||||
timestamps: false
|
||||
});
|
||||
};
|
||||
62
src/model/PurchaseItem.js
Normal file
62
src/model/PurchaseItem.js
Normal file
@@ -0,0 +1,62 @@
|
||||
/* jshint indent: 2 */
|
||||
|
||||
module.exports = function(sequelize, DataTypes) {
|
||||
return sequelize.define('purchaseItem', {
|
||||
Id: {
|
||||
type: DataTypes.INTEGER(11).UNSIGNED,
|
||||
allowNull: false,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
field: 'seq'
|
||||
},
|
||||
PurchaseId: {
|
||||
type: DataTypes.INTEGER(11).UNSIGNED,
|
||||
allowNull: false,
|
||||
field: 'purchase_seq'
|
||||
},
|
||||
ItemLink: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: false,
|
||||
field: 'item_link'
|
||||
},
|
||||
ItemName: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
field: 'item_name'
|
||||
},
|
||||
ItemBrand: {
|
||||
type: DataTypes.STRING(30),
|
||||
allowNull: false,
|
||||
field: 'item_brand'
|
||||
},
|
||||
ItemUnitPrice: {
|
||||
type: DataTypes.DECIMAL,
|
||||
allowNull: false,
|
||||
field: 'item_unit_price'
|
||||
},
|
||||
ItemTax: {
|
||||
type: DataTypes.ENUM('0','7','19','5','16'),
|
||||
allowNull: false,
|
||||
field: 'item_tax'
|
||||
},
|
||||
ItemCount: {
|
||||
type: DataTypes.INTEGER(4),
|
||||
allowNull: false,
|
||||
field: 'item_count'
|
||||
},
|
||||
Status: {
|
||||
type: DataTypes.ENUM('발주요청'),
|
||||
allowNull: false,
|
||||
field: 'status'
|
||||
},
|
||||
CreatedAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
field: 'created_at'
|
||||
}
|
||||
}, {
|
||||
tableName: 'ibl_purchase_item',
|
||||
freezeTableName: true,
|
||||
timestamps: false
|
||||
});
|
||||
};
|
||||
23
src/model/PurchaseItemOption.js
Normal file
23
src/model/PurchaseItemOption.js
Normal file
@@ -0,0 +1,23 @@
|
||||
/* jshint indent: 2 */
|
||||
|
||||
module.exports = function(sequelize, DataTypes) {
|
||||
return sequelize.define('purchaseItemOption', {
|
||||
ItemId: {
|
||||
type: DataTypes.INTEGER(11).UNSIGNED,
|
||||
allowNull: false,
|
||||
primaryKey: true
|
||||
},
|
||||
OptName: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false
|
||||
},
|
||||
OptValue: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false
|
||||
}
|
||||
}, {
|
||||
tableName: 'ibp_purchase_itemopt',
|
||||
freezeTableName: true,
|
||||
timestamps: false
|
||||
});
|
||||
};
|
||||
42
src/model/index.js
Normal file
42
src/model/index.js
Normal file
@@ -0,0 +1,42 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const Sequelize = require('sequelize');
|
||||
const basename = path.basename(__filename);
|
||||
const args = require("../../config/Config")
|
||||
const env = args.env || 'development';
|
||||
const config = require('../../config/config.json')[env];
|
||||
const db = {};
|
||||
|
||||
let sequelize;
|
||||
if (config.use_env_variable) {
|
||||
sequelize = new Sequelize(args.database, config.username, config.password, config);
|
||||
} else {
|
||||
sequelize = new Sequelize(config.database, config.username, config.password, config);
|
||||
}
|
||||
|
||||
sequelize.authenticate().then(() => {
|
||||
console.log('Connection has been established successfully.');
|
||||
}).catch(err => {
|
||||
console.error('Unable to connect to the database:', err);
|
||||
process.exit(1)
|
||||
});
|
||||
|
||||
fs.readdirSync(__dirname).filter(file => {
|
||||
return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
|
||||
}).forEach(file => {
|
||||
const model = sequelize.import(path.join(__dirname, file));
|
||||
db[model.name] = model;
|
||||
});
|
||||
|
||||
Object.keys(db).forEach(modelName => {
|
||||
if (db[modelName].associate) {
|
||||
db[modelName].associate(db);
|
||||
}
|
||||
});
|
||||
|
||||
db.sequelize = sequelize;
|
||||
db.Sequelize = Sequelize;
|
||||
|
||||
module.exports = db;
|
||||
38
src/service/CrawlService.js
Normal file
38
src/service/CrawlService.js
Normal file
@@ -0,0 +1,38 @@
|
||||
const logger = require("logops")
|
||||
, HttpStatus = require('http-status-codes')
|
||||
, cheerio = require('cheerio')
|
||||
, puppeteer = require('puppeteer');
|
||||
|
||||
|
||||
|
||||
|
||||
class CrawlService {
|
||||
|
||||
constructor() {
|
||||
this.browser = null;
|
||||
this.page = null;
|
||||
}
|
||||
|
||||
async request(url) {
|
||||
if (this.page == null) {
|
||||
this.browser = await puppeteer.launch({ headless: true });
|
||||
this.page = await this.browser.newPage();
|
||||
}
|
||||
await this.page.goto(url)
|
||||
return this.page.evaluate(() => document.documentElement.outerHTML)
|
||||
}
|
||||
|
||||
get(params, callback) {
|
||||
this.request('https://www.amazon.de/').then(html => {
|
||||
const $ = cheerio.load(html)
|
||||
let selector = 'div.a-section.a-spacing-none.feed-carousel.first-carousel ul li a'
|
||||
let links = $(selector).map((i, el) => {
|
||||
return $(el).attr('href')
|
||||
}).get()
|
||||
callback(links)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = new CrawlService
|
||||
103
src/service/PurchaseService.js
Normal file
103
src/service/PurchaseService.js
Normal file
@@ -0,0 +1,103 @@
|
||||
const db = require("../model")
|
||||
, logger = require("logops")
|
||||
, restClient = require('../component/RestClientComponent')
|
||||
, HttpStatus = require('http-status-codes')
|
||||
, purchaseComponent = require('../component/PurchaseComponent');
|
||||
|
||||
|
||||
class PurchaseService {
|
||||
response(res, callback) {
|
||||
if (!res.code) res.code = HttpStatus.OK
|
||||
if (!res.message) res.message = HttpStatus.getStatusText(res.code)
|
||||
if (res.code) {
|
||||
callback(res)
|
||||
return;
|
||||
}
|
||||
callback({ code: HttpStatus.NOT_FOUND, message: HttpStatus.getStatusText(HttpStatus.NOT_FOUND) })
|
||||
}
|
||||
|
||||
post(params, callback) {
|
||||
db.purchase.findAll({
|
||||
where: { PurchaseNo: params.itemData[0].purchaseNo }
|
||||
}).then(result => {
|
||||
if (result.length) {
|
||||
logger.error('Purchase No. Duplicated %s', JSON.stringify(params))
|
||||
callback({ code: HttpStatus.CONFLICT, message: HttpStatus.getStatusText(HttpStatus.CONFLICT) })
|
||||
} else {
|
||||
let now = new Date();
|
||||
db.purchase.create(purchaseComponent.getPurchaseData(params.itemData[0], now)).then(purchase => {
|
||||
db.purchaseItem.bulkCreate(purchaseComponent.getPurchaseItemData(params.itemData, purchase, now)).then(createdItems => {
|
||||
let itemOptions = []
|
||||
createdItems.map((created, k) => {
|
||||
params.itemData[k].itemOptions.map(opt => {
|
||||
if (opt.optValue) {
|
||||
itemOptions.push({
|
||||
ItemId: created.Id,
|
||||
OptName: opt.optName,
|
||||
OptValue: opt.optValue
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
db.purchaseItemOption.bulkCreate(itemOptions).then(result => {
|
||||
callback(result.length ? { inserted: createdItems.map(item => item.Id) } : { inserted: [] })
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
put(params, callback) {
|
||||
let itemOptions = params.ItemOptions;
|
||||
delete params.ItemOptions
|
||||
Promise.all([
|
||||
db.purchaseItem.update(params, { where: { Id: params.Id } }).then(updated => updated > 0),
|
||||
purchaseComponent.replaceItemOption(params.Id, itemOptions)
|
||||
]).then(results => {
|
||||
logger.info('Update purchase promise results - %s', JSON.stringify(results))
|
||||
let code = results.filter(result => result == true).length ? HttpStatus.OK : HttpStatus.NOT_MODIFIED
|
||||
callback({ code: code, message: HttpStatus.getStatusText(code) })
|
||||
})
|
||||
}
|
||||
|
||||
async getSearch(params, callback) {
|
||||
if (params.sword || params.orgCode) {
|
||||
params.purchaseId = await purchaseComponent.findPurchaseByOrgCodeAndPurchaseNo(params);
|
||||
}
|
||||
let clause = purchaseComponent.getSearchPurchaseItemClause(params)
|
||||
if (Object.keys(clause.where).length) {
|
||||
db.purchaseItem.findAll(clause).then(rows => {
|
||||
let responseBody = { code: HttpStatus.NOT_FOUND, message: HttpStatus.getStatusText(HttpStatus.NOT_FOUND) }
|
||||
if (rows.length) {
|
||||
Promise.all([
|
||||
purchaseComponent.getSearchTotalPurchaseItem(params),
|
||||
purchaseComponent.findPurchaseById(rows.map(row => row.PurchaseId)),
|
||||
purchaseComponent.findPurchaseItemOptionByItemId(rows.map(row => row.Id))
|
||||
]).then(results => {
|
||||
let total = results[0]
|
||||
let purchases = results[1];
|
||||
let itemOptions = results[2];
|
||||
rows.forEach((row, k) => {
|
||||
rows[k].PurchaseNo = purchases[row.PurchaseId].PurchaseNo
|
||||
rows[k].OrgCode = purchases[row.PurchaseId].OrgCode
|
||||
rows[k].ItemOptions = itemOptions[row.Id]
|
||||
})
|
||||
responseBody = { code: HttpStatus.OK, message: HttpStatus.getStatusText(HttpStatus.OK), data: { total: total, rows: rows } }
|
||||
this.response(responseBody, callback)
|
||||
})
|
||||
} else {
|
||||
this.response(responseBody, callback)
|
||||
}
|
||||
});
|
||||
} else {
|
||||
logger.debug('Clause has no where statement - %s', JSON.stringify(clause))
|
||||
this.response({
|
||||
code: HttpStatus.BAD_REQUEST,
|
||||
message: HttpStatus.getStatusText(HttpStatus.BAD_REQUEST)
|
||||
}, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new PurchaseService
|
||||
92
src/service/RestClientService.js
Normal file
92
src/service/RestClientService.js
Normal file
@@ -0,0 +1,92 @@
|
||||
const fetch = require("node-fetch")
|
||||
, logger = require("logops");
|
||||
|
||||
|
||||
class RestClientService {
|
||||
|
||||
constructor() {
|
||||
this.token = null
|
||||
this.emptyResponse = { code: 200, data: null }
|
||||
this.localhost = 'http://localhost:31000/api/v1/'
|
||||
this.clientId = 'F8Nbz9gGUvLgdn8T'
|
||||
this.clientSecret = 'Fesmx7hHCCMx7Cby7FhV2fAtf2NYEFzP5fCKuwEEdpf8hpnsnnQn9qYhNHt6SsDD'
|
||||
this.setToken()
|
||||
}
|
||||
|
||||
setToken() {
|
||||
this.token = null
|
||||
return fetch(this.localhost + 'oauth/token?client_id=' + this.clientId + '&grant_type=client_credentials&client_secret=' + this.clientSecret, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'content-type': 'application/json'
|
||||
}
|
||||
}).then(res => res.json()).then(res => {
|
||||
logger.debug(res, 'Tracking service got app token successfully')
|
||||
this.token = res.access_token
|
||||
return this.token
|
||||
})
|
||||
}
|
||||
|
||||
getHeader(additionalheaders) {
|
||||
let headers = {
|
||||
"Authorization": "Bearer " + this.token,
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
return additionalheaders ? Object.assign(headers, additionalheaders) : headers
|
||||
}
|
||||
|
||||
getResponse(data) {
|
||||
if (data.code == 406) {
|
||||
logger.info(data, 'Token expired and requesting new token')
|
||||
this.emptyResponse.tokenPromise = this.setToken();
|
||||
return this.emptyResponse
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
getProductBrandByItemId(params) {
|
||||
return fetch(this.localhost + 'catalog/brand', {
|
||||
method: 'OPTIONS',
|
||||
headers: this.getHeader(),
|
||||
body: JSON.stringify(params)
|
||||
}).then(res => res.json()).then(data => this.getResponse(data))
|
||||
}
|
||||
|
||||
getProductByBrand(params) {
|
||||
return fetch(this.localhost + 'catalog/brand/' + params.brand, {
|
||||
method: 'GET',
|
||||
headers: this.getHeader()
|
||||
}).then(res => res.json()).then(data => this.getResponse(data))
|
||||
}
|
||||
|
||||
getParcel(params) {
|
||||
if (this.token == null) return this.emptyResponse
|
||||
let defaultParams = {
|
||||
"request": "get", "column": ["ShippingCode"],
|
||||
"status": [2], "limit": 1, "offset": 0, "groupby": ["ShippingCode"], "order": [['CreatedAt', 'DESC']],
|
||||
'daterangeby': 'ExportedAt',
|
||||
'daterange': {
|
||||
sdate: (function () {
|
||||
let d = new Date();
|
||||
d.setMonth(d.getMonth() - 1);
|
||||
return d.toLocaleDateString();
|
||||
})(),
|
||||
edate: (function () {
|
||||
let d = new Date();
|
||||
d.setDate(d.getDate() + 2);
|
||||
return d.toLocaleDateString();
|
||||
})()
|
||||
}
|
||||
}
|
||||
Object.assign(defaultParams, params)
|
||||
logger.debug(defaultParams, 'Request parcel service params')
|
||||
return fetch(this.localhost + 'parcel', {
|
||||
method: 'POST',
|
||||
headers: this.getHeader(),
|
||||
body: JSON.stringify(defaultParams)
|
||||
}).then(res => res.json()).then(data => this.getResponse(data))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = new RestClientService;
|
||||
Reference in New Issue
Block a user