server
parent
96fe313e96
commit
2f6fc68b76
@ -0,0 +1,28 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
/package-lock.json
|
||||
|
||||
temp
|
@ -0,0 +1,57 @@
|
||||
const Koa = require('koa')
|
||||
const app = new Koa()
|
||||
const views = require('koa-views')
|
||||
const cors = require('koa2-cors')
|
||||
const json = require('koa-json')
|
||||
const onerror = require('koa-onerror')
|
||||
const bodyparser = require('koa-bodyparser')
|
||||
const logger = require('koa-logger')
|
||||
|
||||
const index = require('./routes/index')
|
||||
|
||||
const { Aids: syncAids, hotelLgcDetails: syncHotelDetails, chinaHotelDetails: syncChinas } = require('./jobs/syncHeytripJobs');
|
||||
const rlog = require('./middleware/request_log');
|
||||
|
||||
// error handler
|
||||
onerror(app)
|
||||
|
||||
// middlewares
|
||||
app.use(bodyparser({
|
||||
enableTypes:['json', 'form', 'text']
|
||||
}))
|
||||
app.use(cors({
|
||||
origin: function(ctx){ return '*' }
|
||||
}))
|
||||
app.use(json())
|
||||
app.use(logger())
|
||||
app.use(require('koa-static')(__dirname + '/public'))
|
||||
|
||||
app.use(views(__dirname + '/views', {
|
||||
extension: 'ejs'
|
||||
}))
|
||||
|
||||
app.use(rlog);
|
||||
|
||||
// logger
|
||||
app.use(async (ctx, next) => {
|
||||
const start = new Date()
|
||||
await next()
|
||||
const ms = new Date() - start
|
||||
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)
|
||||
})
|
||||
|
||||
// schedule jobs
|
||||
// syncAids();
|
||||
// syncHotelDetails();
|
||||
// syncChinas();
|
||||
|
||||
// routes
|
||||
app.use(index.routes(), index.allowedMethods())
|
||||
// app.use(routes(), allowedMethods())
|
||||
|
||||
// error-handling
|
||||
app.on('error', (err, ctx) => {
|
||||
console.error('server error', err, ctx)
|
||||
});
|
||||
|
||||
module.exports = app
|
@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var app = require('../app');
|
||||
var debug = require('debug')('demo:server');
|
||||
var http = require('http');
|
||||
/**
|
||||
* Get port from environment and store in Express.
|
||||
*/
|
||||
|
||||
var port = normalizePort(process.env.PORT || '3020');
|
||||
// app.set('port', port);
|
||||
|
||||
/**
|
||||
* Create HTTP server.
|
||||
*/
|
||||
|
||||
var server = http.createServer(app.callback());
|
||||
|
||||
/**
|
||||
* Listen on provided port, on all network interfaces.
|
||||
*/
|
||||
|
||||
server.listen(port);
|
||||
server.on('error', onError);
|
||||
server.on('listening', onListening);
|
||||
|
||||
/**
|
||||
* Normalize a port into a number, string, or false.
|
||||
*/
|
||||
|
||||
function normalizePort(val) {
|
||||
var port = parseInt(val, 10);
|
||||
|
||||
if (isNaN(port)) {
|
||||
// named pipe
|
||||
return val;
|
||||
}
|
||||
|
||||
if (port >= 0) {
|
||||
// port number
|
||||
return port;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "error" event.
|
||||
*/
|
||||
|
||||
function onError(error) {
|
||||
if (error.syscall !== 'listen') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
var bind = typeof port === 'string'
|
||||
? 'Pipe ' + port
|
||||
: 'Port ' + port;
|
||||
|
||||
// handle specific listen errors with friendly messages
|
||||
switch (error.code) {
|
||||
case 'EACCES':
|
||||
console.error(bind + ' requires elevated privileges');
|
||||
process.exit(1);
|
||||
break;
|
||||
case 'EADDRINUSE':
|
||||
console.error(bind + ' is already in use');
|
||||
process.exit(1);
|
||||
break;
|
||||
default:
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "listening" event.
|
||||
*/
|
||||
|
||||
function onListening() {
|
||||
var addr = server.address();
|
||||
var bind = typeof addr === 'string'
|
||||
? 'pipe ' + addr
|
||||
: 'port ' + addr.port;
|
||||
debug('Listening on ' + bind);
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
const HEYTRIP_API = 'http://distapi-sandbox.heytripgo.com/Accommodation';
|
||||
const HEYTRIP_API_PROD = 'http://distapi.heytripgo.com/Accommodation';
|
||||
const LGC_MAPPED = { '1': { 'lgc': '1', locale: 'en-US' }, '2': { 'lgc': '2', locale: 'zh-CN' } };
|
||||
const DEFAULT_LGC = '1';
|
||||
|
||||
module.exports = {
|
||||
HEYTRIP_API,
|
||||
HEYTRIP_API_PROD,
|
||||
LGC_MAPPED,
|
||||
DEFAULT_LGC,
|
||||
};
|
@ -0,0 +1,53 @@
|
||||
const { Sequelize, DataTypes, Op } = require('sequelize');
|
||||
|
||||
/**
|
||||
*
|
||||
* 配置数据库
|
||||
*
|
||||
*/
|
||||
const testDB = new Sequelize('hotel_hub', 'admin', 'admin', {
|
||||
host: 'localhost',
|
||||
dialect: 'mysql',
|
||||
operatorsAliases: false,
|
||||
dialectOptions: {
|
||||
charset: 'utf8mb4',
|
||||
collate: 'utf8mb4_unicode_ci',
|
||||
supportBigNumbers: true,
|
||||
bigNumberStrings: true,
|
||||
},
|
||||
|
||||
pool: {
|
||||
max: 5,
|
||||
min: 0,
|
||||
acquire: 30000,
|
||||
idle: 10000,
|
||||
},
|
||||
timezone: '+08:00', //东八时区
|
||||
});
|
||||
|
||||
const DB = new Sequelize('hotel_hub_p', 'admin', 'admin', {
|
||||
host: 'localhost',
|
||||
dialect: 'mysql',
|
||||
operatorsAliases: false,
|
||||
dialectOptions: {
|
||||
charset: 'utf8mb4',
|
||||
collate: 'utf8mb4_unicode_ci',
|
||||
supportBigNumbers: true,
|
||||
bigNumberStrings: true,
|
||||
},
|
||||
// logging: false,
|
||||
|
||||
pool: {
|
||||
max: 5,
|
||||
min: 0,
|
||||
acquire: 30000,
|
||||
idle: 10000,
|
||||
},
|
||||
timezone: '+08:00', //东八时区
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
DataTypes, Op,
|
||||
// sequelize: testDB,
|
||||
sequelize: DB,
|
||||
};
|
@ -0,0 +1,29 @@
|
||||
class BaseController {
|
||||
response = (code = 200, msg = '', data = {}, page = 1, limit = 10) => {
|
||||
const res = this.getPagingData(data, page, limit);
|
||||
return {
|
||||
'errcode': code,
|
||||
'msg': msg,
|
||||
'title': msg,
|
||||
// 'data': data,
|
||||
...res,
|
||||
};
|
||||
};
|
||||
getPagination = (page, size = 10) => {
|
||||
const limit = size ? +size : 3;
|
||||
const offset = page ? (page-1) * limit : 0;
|
||||
|
||||
return { limit, offset };
|
||||
};
|
||||
getPagingData = (_data, page, limit) => {
|
||||
const { count: totalCount, rows: data } = _data;
|
||||
if ( !data || !totalCount) {
|
||||
return { data: _data };
|
||||
}
|
||||
const currentPage = page ? +page : 0;
|
||||
const totalPages = Math.ceil(totalCount / limit);
|
||||
|
||||
return { totalCount, data, totalPages, currentPage };
|
||||
};
|
||||
}
|
||||
module.exports = BaseController;
|
@ -0,0 +1,31 @@
|
||||
const BaseController = require('./BaseController');
|
||||
const heytripService = require('../services/heytripService');
|
||||
|
||||
class Heytrip extends BaseController {
|
||||
|
||||
getAids = async (ctx) => {
|
||||
try {
|
||||
const { nextPage } = await heytripService.syncAids();
|
||||
ctx.body = this.response(0, 'get_heytrip_ids', nextPage);
|
||||
} catch (error) {
|
||||
// console.error(error);
|
||||
ctx.body = this.response(1, error.message || 'An error occurred.');
|
||||
}
|
||||
};
|
||||
|
||||
getHotelInfo = async (ctx) => {
|
||||
const { data } = await heytripService.syncHotelDetails();
|
||||
ctx.body = this.response(0, 'get_hotel_info', data);
|
||||
};
|
||||
|
||||
getAvailability = async (ctx) => {
|
||||
try {
|
||||
const data = await heytripService.getHotelAvailability(ctx.query);
|
||||
ctx.body = this.response(0, 'get_hotel_availability', data);
|
||||
} catch (error) {
|
||||
ctx.body = this.response(1, error.message || 'An error occurred.');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = new Heytrip();
|
@ -0,0 +1,46 @@
|
||||
const BaseController = require('./BaseController');
|
||||
const heytripService = require('../services/heytripService');
|
||||
const { QuotedHotelsPrice } = require('../vendor/heytrip');
|
||||
|
||||
class Api extends BaseController {
|
||||
|
||||
hotelSearch = async(ctx) => {
|
||||
const { keyword, checkin, checkout } = ctx.query;
|
||||
const page = ctx.query.page || 1;
|
||||
const size = ctx.query.pagesize || 10;
|
||||
try {
|
||||
// if (!keyword || !checkin || !checkout) {
|
||||
if (!keyword) {
|
||||
ctx.throw(400, 'Missing required parameters`keyword`.');
|
||||
}
|
||||
|
||||
const { limit, offset } = this.getPagination(page, size);
|
||||
|
||||
const { rows, count } = await heytripService.hotelSearch(keyword, { limit, offset });
|
||||
|
||||
let quoteRes = [];
|
||||
|
||||
if (checkin && checkout) {
|
||||
const allIds = rows.map((item) => item.hotel_id);
|
||||
quoteRes = await QuotedHotelsPrice({
|
||||
hotelIds: allIds,
|
||||
nationality: 'CN', // 默认取中国报价
|
||||
CheckInDate: checkin,
|
||||
CheckOutDate: checkout,
|
||||
adultNum: 1,
|
||||
roomCount: 1,
|
||||
});
|
||||
}
|
||||
|
||||
const quoteMapped = quoteRes.reduce((r, c) => ({ ...r, [c.Id]: c }), {});
|
||||
const res = rows.map((item) => ({ ...item, base_price: quoteMapped[item.hotel_id] || {} }));
|
||||
|
||||
ctx.body = this.response(0, 'hotel_search', { rows: res, count }, page, size);
|
||||
} catch (error) {
|
||||
// console.error(error);
|
||||
ctx.statusCode = error.statusCode;
|
||||
ctx.body = this.response(1, error.message || 'An error occurred.');
|
||||
}
|
||||
}
|
||||
}
|
||||
module.exports = new Api();
|
@ -0,0 +1,41 @@
|
||||
const fs = require('fs');
|
||||
|
||||
var date = new Date();
|
||||
// var dat= date.getDate()+"-"+(date.getMonth()+1)+"-"+date.getFullYear();
|
||||
var dat= `${date.getFullYear()}-${(date.getMonth()+1)}-${date.getDate()}`;
|
||||
|
||||
var err_log = "./pm2/logs/err.log";
|
||||
var out_log = "./pm2/logs/out.log";
|
||||
|
||||
fs.mkdirSync(`./pm2/logs/${dat}`, { recursive: true });
|
||||
|
||||
err_log = './pm2/logs/' + dat + '/error.log';
|
||||
out_log = './pm2/logs/' + dat + '/output.log';
|
||||
combined_log = './pm2/logs/' + dat + '/combined.log';
|
||||
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: 'hotelhub',
|
||||
script: 'bin/www',
|
||||
max_memory_restart: "500M",
|
||||
merge_logs: true,
|
||||
max_restarts: 20,
|
||||
error_file: err_log,
|
||||
out_file: out_log,
|
||||
// instances: 1,
|
||||
watch: false
|
||||
}
|
||||
]
|
||||
}
|
||||
// export const apps = [{
|
||||
// name: 'hotelhub',
|
||||
// script: 'bin/www',
|
||||
// max_memory_restart: "500M",
|
||||
// merge_logs: true,
|
||||
// max_restarts: 20,
|
||||
// error_file: err_log,
|
||||
// out_file: out_log,
|
||||
// // instances: 1,
|
||||
// watch: '.'
|
||||
// }];
|
@ -0,0 +1,58 @@
|
||||
const { scheduleJob } = require('node-schedule');
|
||||
const heytripService = require('../services/heytripService');
|
||||
const { LGC_MAPPED } = require('../config/constants');
|
||||
|
||||
const Aids = () => {
|
||||
const job = scheduleJob('*/2 * * * * *', async function () {
|
||||
console.log('syncing heytrip, get available accommodation ids.');
|
||||
|
||||
const res = await heytripService.syncAids();
|
||||
if (res.nextPage !== true) {
|
||||
job.cancel();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const hotelLgcDetails = () => {
|
||||
const job2 = scheduleJob('*/4 * * * * *', async function () {
|
||||
console.log('-------------------------syncing heytrip, get accommodation details.-------------------------');
|
||||
|
||||
const isRunning = job2.pendingInvocations[0]?.job?.running == 1;
|
||||
if (!isRunning) {
|
||||
// const res = await heytripService.syncHotelDetails();
|
||||
// const res = await heytripService.syncInitHotelLgcDetailsAction(LGC_MAPPED['1']);
|
||||
const res = await heytripService.newHotelsLgc('1');
|
||||
|
||||
job2.cancel(); // debug: 0
|
||||
if (res.next !== true) {
|
||||
job2.cancel();
|
||||
console.log('job completed! canceled job!');
|
||||
}
|
||||
} else {
|
||||
console.log('pre job running! cancelNext');
|
||||
job2.cancelNext();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const chinaHotelDetails = () => {
|
||||
const job3 = scheduleJob('*/4 * * * * *', async function () {
|
||||
console.log('syncing heytrip, get china accommodation details.');
|
||||
const isRunning = job3.pendingInvocations[0]?.job?.running == 1;
|
||||
if (!isRunning) {
|
||||
const res = await heytripService.chinaHotelsLgc2('2');
|
||||
if (res.next !== true) {
|
||||
job3.cancel();
|
||||
console.log('job completed! canceled job!');
|
||||
// job3.reschedule('0 0 0 * * *');
|
||||
}
|
||||
} else {
|
||||
console.log('pre job running! cancelNext');
|
||||
job2.cancelNext();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
Aids, hotelLgcDetails, chinaHotelDetails
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
const requestLogsService = require('./../services/requestLogsService');
|
||||
const rlog = async (ctx, next) => {
|
||||
try {
|
||||
await next();
|
||||
} catch (err) {
|
||||
} finally {
|
||||
await requestLogsService.create({
|
||||
method: ctx.method,
|
||||
path: ctx.method === 'GET' ? ctx.path : ctx.url,
|
||||
request_data: ctx.method === 'GET' ? JSON.stringify(ctx.query) : JSON.stringify(ctx.request.body),
|
||||
ip: ctx.ip,
|
||||
});
|
||||
}
|
||||
};
|
||||
module.exports = rlog;
|
@ -0,0 +1,36 @@
|
||||
import Sequelize from 'sequelize';
|
||||
export default function(sequelize, DataTypes) {
|
||||
return sequelize.define('cache_availability', {
|
||||
sn: {
|
||||
autoIncrement: true,
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: false,
|
||||
primaryKey: true
|
||||
},
|
||||
hotel_id: {
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: false
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
tableName: 'cache_availability',
|
||||
timestamps: false,
|
||||
indexes: [
|
||||
{
|
||||
name: "PRIMARY",
|
||||
unique: true,
|
||||
using: "BTREE",
|
||||
fields: [
|
||||
{ name: "sn" },
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "cache_availability_hotel_id_IDX",
|
||||
using: "BTREE",
|
||||
fields: [
|
||||
{ name: "hotel_id" },
|
||||
]
|
||||
},
|
||||
]
|
||||
});
|
||||
};
|
@ -0,0 +1,56 @@
|
||||
const Sequelize = require('sequelize');
|
||||
module.exports = function(sequelize, DataTypes) {
|
||||
return sequelize.define('city', {
|
||||
sn: {
|
||||
autoIncrement: true,
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
primaryKey: true
|
||||
},
|
||||
id: {
|
||||
type: DataTypes.STRING(10),
|
||||
allowNull: true
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
},
|
||||
latitude: {
|
||||
type: DataTypes.DOUBLE,
|
||||
allowNull: true
|
||||
},
|
||||
longitude: {
|
||||
type: DataTypes.DOUBLE,
|
||||
allowNull: true
|
||||
},
|
||||
lgc: {
|
||||
type: DataTypes.TINYINT,
|
||||
allowNull: false
|
||||
},
|
||||
locale: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
tableName: 'city',
|
||||
timestamps: false,
|
||||
indexes: [
|
||||
{
|
||||
name: "PRIMARY",
|
||||
unique: true,
|
||||
using: "BTREE",
|
||||
fields: [
|
||||
{ name: "sn" },
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "city_id_IDX",
|
||||
using: "BTREE",
|
||||
fields: [
|
||||
{ name: "id" },
|
||||
]
|
||||
},
|
||||
]
|
||||
});
|
||||
};
|
@ -0,0 +1,56 @@
|
||||
const Sequelize = require('sequelize');
|
||||
module.exports = function(sequelize, DataTypes) {
|
||||
return sequelize.define('country', {
|
||||
sn: {
|
||||
autoIncrement: true,
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
primaryKey: true
|
||||
},
|
||||
id: {
|
||||
type: DataTypes.STRING(10),
|
||||
allowNull: true
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
},
|
||||
latitude: {
|
||||
type: DataTypes.DOUBLE,
|
||||
allowNull: true
|
||||
},
|
||||
longitude: {
|
||||
type: DataTypes.DOUBLE,
|
||||
allowNull: true
|
||||
},
|
||||
lgc: {
|
||||
type: DataTypes.TINYINT,
|
||||
allowNull: false
|
||||
},
|
||||
locale: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
tableName: 'country',
|
||||
timestamps: false,
|
||||
indexes: [
|
||||
{
|
||||
name: "PRIMARY",
|
||||
unique: true,
|
||||
using: "BTREE",
|
||||
fields: [
|
||||
{ name: "sn" },
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "countries_id_IDX",
|
||||
using: "BTREE",
|
||||
fields: [
|
||||
{ name: "id" },
|
||||
]
|
||||
},
|
||||
]
|
||||
});
|
||||
};
|
@ -0,0 +1,72 @@
|
||||
const Sequelize = require('sequelize');
|
||||
module.exports = function(sequelize, DataTypes) {
|
||||
return sequelize.define('facility', {
|
||||
sn: {
|
||||
autoIncrement: true,
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: false,
|
||||
primaryKey: true
|
||||
},
|
||||
hotel_id: {
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: false
|
||||
},
|
||||
lgc: {
|
||||
type: DataTypes.TINYINT,
|
||||
allowNull: false
|
||||
},
|
||||
locale: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
},
|
||||
type: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
},
|
||||
category: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
},
|
||||
category_name: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
},
|
||||
id: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
},
|
||||
symbol: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
},
|
||||
tooltip: {
|
||||
type: DataTypes.STRING(1000),
|
||||
allowNull: true
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
tableName: 'facility',
|
||||
timestamps: false,
|
||||
indexes: [
|
||||
{
|
||||
name: "PRIMARY",
|
||||
unique: true,
|
||||
using: "BTREE",
|
||||
fields: [
|
||||
{ name: "sn" },
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "facility_hotel_id_IDX",
|
||||
using: "BTREE",
|
||||
fields: [
|
||||
{ name: "hotel_id" },
|
||||
]
|
||||
},
|
||||
]
|
||||
});
|
||||
};
|
@ -0,0 +1,100 @@
|
||||
const Sequelize = require('sequelize');
|
||||
module.exports = function(sequelize, DataTypes) {
|
||||
return sequelize.define('hotelinfo2', {
|
||||
hi2_sn: {
|
||||
autoIncrement: true,
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: false,
|
||||
primaryKey: true
|
||||
},
|
||||
hotel_id: {
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: false
|
||||
},
|
||||
lgc: {
|
||||
type: DataTypes.TINYINT,
|
||||
allowNull: false
|
||||
},
|
||||
locale: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
},
|
||||
hi2_hotel_name: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
},
|
||||
hi2_address: {
|
||||
type: DataTypes.STRING(1000),
|
||||
allowNull: true
|
||||
},
|
||||
hi2_description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true
|
||||
},
|
||||
h2_instruction_desc: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true
|
||||
},
|
||||
h2_instruction_special: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true
|
||||
},
|
||||
h2_instruction_fees_desc: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true
|
||||
},
|
||||
h2_location_walkable_places_title: {
|
||||
type: DataTypes.STRING(1000),
|
||||
allowNull: true
|
||||
},
|
||||
h2_location_walkable_places_desc: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true
|
||||
},
|
||||
h2_location_highlight_desc: {
|
||||
type: DataTypes.STRING(1000),
|
||||
allowNull: true
|
||||
},
|
||||
h2_review_desc: {
|
||||
type: DataTypes.STRING(1000),
|
||||
allowNull: true
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
tableName: 'hotelinfo2',
|
||||
timestamps: false,
|
||||
indexes: [
|
||||
{
|
||||
name: "PRIMARY",
|
||||
unique: true,
|
||||
using: "BTREE",
|
||||
fields: [
|
||||
{ name: "hi2_sn" },
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "hotelinfo2_unique",
|
||||
unique: true,
|
||||
using: "BTREE",
|
||||
fields: [
|
||||
{ name: "hotel_id" },
|
||||
{ name: "lgc" },
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "hotelinfo2_hotel_id_IDX",
|
||||
using: "BTREE",
|
||||
fields: [
|
||||
{ name: "hotel_id" },
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "hotelinfo2_lgc_IDX",
|
||||
using: "BTREE",
|
||||
fields: [
|
||||
{ name: "lgc" },
|
||||
]
|
||||
},
|
||||
]
|
||||
});
|
||||
};
|
@ -0,0 +1,83 @@
|
||||
const Sequelize = require('sequelize');
|
||||
module.exports = function(sequelize, DataTypes) {
|
||||
return sequelize.define('images', {
|
||||
sn: {
|
||||
autoIncrement: true,
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: false,
|
||||
primaryKey: true
|
||||
},
|
||||
hotel_id: {
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: false
|
||||
},
|
||||
info_source: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
},
|
||||
info_source_id: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
},
|
||||
type: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
comment: "Mid小图; Max大图;"
|
||||
},
|
||||
url: {
|
||||
type: DataTypes.STRING(1000),
|
||||
allowNull: true
|
||||
},
|
||||
caption: {
|
||||
type: DataTypes.STRING(1000),
|
||||
allowNull: true
|
||||
},
|
||||
category: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
},
|
||||
lgc: {
|
||||
type: DataTypes.TINYINT,
|
||||
allowNull: false
|
||||
},
|
||||
locale: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
tableName: 'images',
|
||||
timestamps: false,
|
||||
indexes: [
|
||||
{
|
||||
name: "PRIMARY",
|
||||
unique: true,
|
||||
using: "BTREE",
|
||||
fields: [
|
||||
{ name: "sn" },
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "image_urls_hotel_id_IDX",
|
||||
using: "BTREE",
|
||||
fields: [
|
||||
{ name: "hotel_id" },
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "image_urls_source_id_IDX",
|
||||
using: "BTREE",
|
||||
fields: [
|
||||
{ name: "info_source_id" },
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "images_lgc_IDX",
|
||||
using: "BTREE",
|
||||
fields: [
|
||||
{ name: "lgc" },
|
||||
]
|
||||
},
|
||||
]
|
||||
});
|
||||
};
|
@ -0,0 +1,64 @@
|
||||
const Sequelize = require('sequelize');
|
||||
module.exports = function(sequelize, DataTypes) {
|
||||
return sequelize.define('informations', {
|
||||
sn: {
|
||||
autoIncrement: true,
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: false,
|
||||
primaryKey: true
|
||||
},
|
||||
hotel_id: {
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: false
|
||||
},
|
||||
lgc: {
|
||||
type: DataTypes.TINYINT,
|
||||
allowNull: false
|
||||
},
|
||||
locale: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
},
|
||||
category: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
},
|
||||
category_name: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
},
|
||||
id: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
},
|
||||
value: {
|
||||
type: DataTypes.STRING(1000),
|
||||
allowNull: true
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
tableName: 'informations',
|
||||
timestamps: false,
|
||||
indexes: [
|
||||
{
|
||||
name: "PRIMARY",
|
||||
unique: true,
|
||||
using: "BTREE",
|
||||
fields: [
|
||||
{ name: "sn" },
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "facility_hotel_id_IDX",
|
||||
using: "BTREE",
|
||||
fields: [
|
||||
{ name: "hotel_id" },
|
||||
]
|
||||
},
|
||||
]
|
||||
});
|
||||
};
|
@ -0,0 +1,49 @@
|
||||
var DataTypes = require("sequelize").DataTypes;
|
||||
var _city = require("./city");
|
||||
var _country = require("./country");
|
||||
var _facility = require("./facility");
|
||||
var _heytrip_ids = require("./heytrip_ids");
|
||||
var _hotelinfo = require("./hotelinfo");
|
||||
var _hotelinfo2 = require("./hotelinfo2");
|
||||
var _images = require("./images");
|
||||
var _informations = require("./informations");
|
||||
var _locations = require("./locations");
|
||||
var _request_logs = require("./request_logs");
|
||||
var _reviews = require("./reviews");
|
||||
var _reviews_summaries = require("./reviews_summaries");
|
||||
var _rooms = require("./rooms");
|
||||
|
||||
function initModels(sequelize) {
|
||||
var cityModel = _city(sequelize, DataTypes);
|
||||
var countryModel = _country(sequelize, DataTypes);
|
||||
var facilityModel = _facility(sequelize, DataTypes);
|
||||
var heytripIdsModel = _heytrip_ids(sequelize, DataTypes);
|
||||
var hotelinfoModel = _hotelinfo(sequelize, DataTypes);
|
||||
var hotelinfo2Model = _hotelinfo2(sequelize, DataTypes);
|
||||
var imagesModel = _images(sequelize, DataTypes);
|
||||
var informationsModel = _informations(sequelize, DataTypes);
|
||||
var locationsModel = _locations(sequelize, DataTypes);
|
||||
var requestLogsModel = _request_logs(sequelize, DataTypes);
|
||||
var reviewsModel = _reviews(sequelize, DataTypes);
|
||||
var reviewsSummariesModel = _reviews_summaries(sequelize, DataTypes);
|
||||
var roomsModel = _rooms(sequelize, DataTypes);
|
||||
|
||||
return {
|
||||
cityModel,
|
||||
countryModel,
|
||||
facilityModel,
|
||||
heytripIdsModel,
|
||||
hotelinfoModel,
|
||||
hotelinfo2Model,
|
||||
imagesModel,
|
||||
informationsModel,
|
||||
locationsModel,
|
||||
requestLogsModel,
|
||||
reviewsModel,
|
||||
reviewsSummariesModel,
|
||||
roomsModel,
|
||||
};
|
||||
}
|
||||
module.exports = initModels;
|
||||
module.exports.initModels = initModels;
|
||||
module.exports.default = initModels;
|
@ -0,0 +1,54 @@
|
||||
const Sequelize = require('sequelize');
|
||||
module.exports = function(sequelize, DataTypes) {
|
||||
return sequelize.define('request_logs', {
|
||||
sn: {
|
||||
autoIncrement: true,
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: false,
|
||||
primaryKey: true
|
||||
},
|
||||
action: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
},
|
||||
method: {
|
||||
type: DataTypes.STRING(15),
|
||||
allowNull: true
|
||||
},
|
||||
path: {
|
||||
type: DataTypes.STRING(1000),
|
||||
allowNull: true
|
||||
},
|
||||
request_data: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true
|
||||
},
|
||||
snapshots: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true
|
||||
},
|
||||
ip: {
|
||||
type: DataTypes.STRING(200),
|
||||
allowNull: true
|
||||
},
|
||||
createtime: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.Sequelize.literal('CURRENT_TIMESTAMP')
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
tableName: 'request_logs',
|
||||
timestamps: false,
|
||||
indexes: [
|
||||
{
|
||||
name: "PRIMARY",
|
||||
unique: true,
|
||||
using: "BTREE",
|
||||
fields: [
|
||||
{ name: "sn" },
|
||||
]
|
||||
},
|
||||
]
|
||||
});
|
||||
};
|
@ -0,0 +1,74 @@
|
||||
const Sequelize = require('sequelize');
|
||||
module.exports = function(sequelize, DataTypes) {
|
||||
return sequelize.define('reviews', {
|
||||
sn: {
|
||||
autoIncrement: true,
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: false,
|
||||
primaryKey: true
|
||||
},
|
||||
hotel_id: {
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: false
|
||||
},
|
||||
lgc: {
|
||||
type: DataTypes.TINYINT,
|
||||
allowNull: false
|
||||
},
|
||||
locale: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
},
|
||||
type: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
comment: "ScoreDetails; Summaries; PositiveMentions"
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
},
|
||||
category: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
comment: "种类 cleanliness(环境和清洁度) facilities(设施) location(位置) roomComfort(客房舒适度) staffPerformance(服务)valueForMoney(性价比)"
|
||||
},
|
||||
score: {
|
||||
type: DataTypes.FLOAT,
|
||||
allowNull: true
|
||||
},
|
||||
city_average: {
|
||||
type: DataTypes.FLOAT,
|
||||
allowNull: true
|
||||
},
|
||||
mention_name: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
},
|
||||
mention_count: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
tableName: 'reviews',
|
||||
timestamps: false,
|
||||
indexes: [
|
||||
{
|
||||
name: "PRIMARY",
|
||||
unique: true,
|
||||
using: "BTREE",
|
||||
fields: [
|
||||
{ name: "sn" },
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "reviews_hotel_id_IDX",
|
||||
using: "BTREE",
|
||||
fields: [
|
||||
{ name: "hotel_id" },
|
||||
]
|
||||
},
|
||||
]
|
||||
});
|
||||
};
|
@ -0,0 +1,64 @@
|
||||
const Sequelize = require('sequelize');
|
||||
module.exports = function(sequelize, DataTypes) {
|
||||
return sequelize.define('reviews_summaries', {
|
||||
sn: {
|
||||
autoIncrement: true,
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: false,
|
||||
primaryKey: true
|
||||
},
|
||||
hotel_id: {
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: false
|
||||
},
|
||||
lgc: {
|
||||
type: DataTypes.TINYINT,
|
||||
allowNull: false
|
||||
},
|
||||
locale: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
},
|
||||
country: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
},
|
||||
reviewer: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
},
|
||||
review_rating: {
|
||||
type: DataTypes.FLOAT,
|
||||
allowNull: true
|
||||
},
|
||||
desc: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true
|
||||
},
|
||||
review_date: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
tableName: 'reviews_summaries',
|
||||
timestamps: false,
|
||||
indexes: [
|
||||
{
|
||||
name: "PRIMARY",
|
||||
unique: true,
|
||||
using: "BTREE",
|
||||
fields: [
|
||||
{ name: "sn" },
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "reviews_hotel_id_IDX",
|
||||
using: "BTREE",
|
||||
fields: [
|
||||
{ name: "hotel_id" },
|
||||
]
|
||||
},
|
||||
]
|
||||
});
|
||||
};
|
@ -0,0 +1,112 @@
|
||||
const Sequelize = require('sequelize');
|
||||
module.exports = function(sequelize, DataTypes) {
|
||||
return sequelize.define('rooms', {
|
||||
sn: {
|
||||
autoIncrement: true,
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: false,
|
||||
primaryKey: true
|
||||
},
|
||||
hotel_id: {
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: false
|
||||
},
|
||||
lgc: {
|
||||
type: DataTypes.TINYINT,
|
||||
allowNull: false
|
||||
},
|
||||
locale: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
},
|
||||
room_id: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false
|
||||
},
|
||||
room_name: {
|
||||
type: DataTypes.STRING(500),
|
||||
allowNull: true
|
||||
},
|
||||
locale_name: {
|
||||
type: DataTypes.STRING(500),
|
||||
allowNull: true
|
||||
},
|
||||
bed_type_desc: {
|
||||
type: DataTypes.STRING(1000),
|
||||
allowNull: true
|
||||
},
|
||||
area: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
},
|
||||
views: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
},
|
||||
window: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: "窗户信息 0未知 1有窗 2无窗 3部分有窗 4内窗 5封闭窗 6部分内窗"
|
||||
},
|
||||
floor: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
},
|
||||
wireless_wideband: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: "无线网络信息 0未知 1无 2免费 3收费 4部分收费 5部分有且收费 6部分有且免费"
|
||||
},
|
||||
wired_broadband: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: "有线网络信息 0未知 1无 2免费 3收费 4部分收费 5部分有且收费 6部分有且免费"
|
||||
},
|
||||
smoking: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: "吸烟信息 0未知 1禁烟 2部分禁烟 3可吸烟"
|
||||
},
|
||||
bathroom_type: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: "卫浴信息 0未知 1独立卫浴 2公共卫浴"
|
||||
},
|
||||
max_occupancy: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true
|
||||
},
|
||||
bedrooms: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
tableName: 'rooms',
|
||||
timestamps: false,
|
||||
indexes: [
|
||||
{
|
||||
name: "PRIMARY",
|
||||
unique: true,
|
||||
using: "BTREE",
|
||||
fields: [
|
||||
{ name: "sn" },
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "rooms_room_id_IDX",
|
||||
using: "BTREE",
|
||||
fields: [
|
||||
{ name: "room_id" },
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "rooms_hotel_id_IDX",
|
||||
using: "BTREE",
|
||||
fields: [
|
||||
{ name: "hotel_id" },
|
||||
]
|
||||
},
|
||||
]
|
||||
});
|
||||
};
|
@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "HotelHub",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "node bin/www",
|
||||
"dev": "nodemon bin/www",
|
||||
"prd": "pm2 start bin/www",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.7.3",
|
||||
"debug": "^4.1.1",
|
||||
"ejs": "~2.3.3",
|
||||
"koa": "^2.7.0",
|
||||
"koa-bodyparser": "^4.2.1",
|
||||
"koa-convert": "^1.2.0",
|
||||
"koa-json": "^2.0.2",
|
||||
"koa-logger": "^3.2.0",
|
||||
"koa-onerror": "^4.1.0",
|
||||
"koa-router": "^7.4.0",
|
||||
"koa-static": "^5.0.0",
|
||||
"koa-views": "^6.2.0",
|
||||
"koa2-cors": "^2.0.6",
|
||||
"mysql2": "^3.11.0",
|
||||
"node-schedule": "^2.1.1",
|
||||
"sequelize": "^6.37.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^1.19.1"
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
body {
|
||||
padding: 50px;
|
||||
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #00B7FF;
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
const router = require('koa-router')();
|
||||
const Heytrip = require('./../controllers/Heytrip');
|
||||
const Api = require('./../controllers/Api');
|
||||
|
||||
// router.get('/get_heytrip_ids', Heytrip.getAids);
|
||||
|
||||
// router.get('/get_hotel_info', Heytrip.getHotelInfo);
|
||||
|
||||
router.get('/search_hotel', Api.hotelSearch);
|
||||
|
||||
router.get('/availability', Heytrip.getAvailability);
|
||||
|
||||
module.exports = router;
|
@ -0,0 +1,14 @@
|
||||
import Router from 'koa-router';
|
||||
const router = new Router();
|
||||
|
||||
router.prefix('/users')
|
||||
|
||||
router.get('/', function (ctx, next) {
|
||||
ctx.body = 'this is a users response!'
|
||||
})
|
||||
|
||||
router.get('/bar', function (ctx, next) {
|
||||
ctx.body = 'this is a users/bar response'
|
||||
})
|
||||
|
||||
export default router
|
@ -0,0 +1,307 @@
|
||||
const db = require('../config/db');
|
||||
const initModels = require('./../models/init-models');
|
||||
const { AvailableAccommodationIds, AccommodationsDetails, Availability } = require('../vendor/heytrip');
|
||||
const { isEmpty } = require('../utils/commons');
|
||||
const { resolveDetails, resolveRatePlans } = require('../helper/heytripDataHelper');
|
||||
const { DEFAULT_LGC, LGC_MAPPED } = require('../config/constants');
|
||||
|
||||
const { sequelize: Sequelize, Op } = db;
|
||||
|
||||
const models = initModels(Sequelize);
|
||||
|
||||
const Country = models.countryModel;
|
||||
const City = models.cityModel;
|
||||
|
||||
const HeytripIds = models.heytripIdsModel;
|
||||
const Hotelinfo = models.hotelinfoModel;
|
||||
const Hotelinfo2 = models.hotelinfo2Model;
|
||||
const Rooms = models.roomsModel;
|
||||
const Facility = models.facilityModel;
|
||||
const Images = models.imagesModel;
|
||||
const Informations = models.informationsModel;
|
||||
const Reviews = models.reviewsModel;
|
||||
const ReviewsSummaries = models.reviewsSummariesModel;
|
||||
const Locations = models.locationsModel;
|
||||
// const CacheAvailability = models.cacheAvailabilityModel;
|
||||
|
||||
// HeytripIds.hasMany(Hotelinfo, {
|
||||
// foreignKey: 'hotel_id',
|
||||
// onDelete: 'NO ACTION',
|
||||
// onUpdate: 'NO ACTION',
|
||||
// });
|
||||
// Hotelinfo.belongsTo(HeytripIds, { as: 'aid', foreignKey: 'hotel_id', onDelete: 'NO ACTION', onUpdate: 'NO ACTION' });
|
||||
// Hotelinfo.hasMany(Rooms, { sourceKey: 'hotel_id', foreignKey: 'hotel_id', onDelete: 'NO ACTION', onUpdate: 'NO ACTION' });
|
||||
// Rooms.belongsTo(Hotelinfo, { targetKey: 'hotel_id', foreignKey: 'hotel_id', onDelete: 'NO ACTION', onUpdate: 'NO ACTION', });
|
||||
Hotelinfo.hasMany(Hotelinfo2, { as: 'lgc_info', sourceKey: 'hotel_id', foreignKey: 'hotel_id', onDelete: 'NO ACTION', onUpdate: 'NO ACTION' });
|
||||
Hotelinfo.hasMany(City, { as: 'city', sourceKey: 'city_id', foreignKey: 'id', onDelete: 'NO ACTION', onUpdate: 'NO ACTION', }); // 多语种, 所以实际是 hasMany , 用 hasOne 要指定 lgc= 1 或者2
|
||||
Hotelinfo.hasMany(Country, { as: 'country', sourceKey: 'country_code', foreignKey: 'id', onDelete: 'NO ACTION', onUpdate: 'NO ACTION', }); // 多语种, 所以实际是 hasMany , 用 hasOne 要指定 lgc= 1 或者2
|
||||
// Hotelinfo2.belongsTo(Hotelinfo, { targetKey: 'hotel_id', foreignKey: 'hotel_id', onDelete: 'NO ACTION', onUpdate: 'NO ACTION', });
|
||||
|
||||
class Heytrip {
|
||||
/**
|
||||
* 搜索酒店
|
||||
*/
|
||||
hotelSearch = async (keyword, options) => {
|
||||
const keywordSplit = keyword.split(' ');
|
||||
const keywordWhere = (field) => keywordSplit.map((word) => Sequelize.where(Sequelize.fn('instr', Sequelize.col(field), word), { [Op.gt]: 0 }));
|
||||
// 'hotelinfo.address'
|
||||
const keywordSearch = ['hotelinfo.hotel_name', ].map((field) => ({ [Op.and]: keywordWhere(field) }));
|
||||
const keywordOrder = ['hotelinfo.hotel_name',].reduce((ro, field) => ro.concat(keywordSplit.map((word) => Sequelize.fn('instr', Sequelize.col(field), word))), []);
|
||||
|
||||
// const keywordSearchCount = ['hi2_hotel_name', 'hi2_address'].map((field) => ({ [Op.and]: keywordWhere(field) }));
|
||||
// const countRows = await Hotelinfo2.count({
|
||||
// where: {
|
||||
// [Op.or]: keywordSearchCount,
|
||||
// },
|
||||
// // distinct: true,
|
||||
// group: ['hotel_id'],
|
||||
// });
|
||||
// const count = countRows.length;
|
||||
// const findIds = countRows.map((item) => item.hotel_id);
|
||||
|
||||
const { count, rows } = await Hotelinfo.findAndCountAll({
|
||||
// const [ rows ] = await Hotelinfo.findAll({
|
||||
include: [
|
||||
{
|
||||
model: Hotelinfo2,
|
||||
as: 'lgc_info',
|
||||
attributes: [
|
||||
['hi2_sn', 'sn'],
|
||||
'hotel_id',
|
||||
'lgc',
|
||||
'locale',
|
||||
['hi2_hotel_name', 'hotel_name'],
|
||||
['hi2_address', 'address'],
|
||||
['hi2_description', 'description'],
|
||||
['h2_review_desc', 'review_desc'],
|
||||
['h2_location_highlight_desc', 'location_highlight_desc'],
|
||||
],
|
||||
required: false,
|
||||
separate: true,
|
||||
},
|
||||
{ model: City, as: 'city', attributes: ['id', 'name'], where: { lgc: 2 }, required: false, separate: true, },
|
||||
{ model: Country, as: 'country', attributes: ['id', 'name'], where: { lgc: 2 }, required: false, separate: true, },
|
||||
],
|
||||
// include: [{ model: Hotelinfo2, as: 'h2', required: true }],
|
||||
where: {
|
||||
[Op.or]: keywordSearch,
|
||||
// hotel_id: findIds,
|
||||
},
|
||||
order: keywordOrder,
|
||||
...options,
|
||||
// raw: true,
|
||||
nest: true,
|
||||
});
|
||||
|
||||
return { count, rows: rows.map((item) => item.dataValues) };
|
||||
// return { count, rows };
|
||||
};
|
||||
|
||||
getLastPageIndex = async () => {
|
||||
const ret = await HeytripIds.max('page_index');
|
||||
return ret;
|
||||
};
|
||||
|
||||
syncAids = async () => {
|
||||
const lastPageIndex = await this.getLastPageIndex();
|
||||
const pageIndex = lastPageIndex + 1;
|
||||
|
||||
const ids = await AvailableAccommodationIds(pageIndex);
|
||||
if (isEmpty(ids)) {
|
||||
return {
|
||||
nextPage: false,
|
||||
pageIndex,
|
||||
};
|
||||
}
|
||||
|
||||
const insertRows = ids.map((id) => ({ hotel_id: id, page_index: pageIndex }));
|
||||
await HeytripIds.bulkCreate(insertRows);
|
||||
|
||||
return {
|
||||
nextPage: true,
|
||||
pageIndex,
|
||||
};
|
||||
};
|
||||
|
||||
newHotels = async () => {
|
||||
const [rows] = await Sequelize.query(
|
||||
'SELECT i.hotel_id, IFNULL(h.hi_sn ,0) info_exists FROM heytrip_ids as i LEFT JOIN hotelinfo AS h ON h.hotel_id = i.hotel_id WHERE h.hi_sn IS NULL AND update_flag != 0 ORDER BY info_exists LIMIT 10'
|
||||
);
|
||||
return rows;
|
||||
};
|
||||
|
||||
newHotelsLgc = async (lgc) => {
|
||||
// const [rows] = await Sequelize.query(
|
||||
// `SELECT h.hotel_id
|
||||
// FROM hotelinfo AS h
|
||||
// LEFT JOIN hotelinfo2 AS h2 ON h.hotel_id =h2.hotel_id
|
||||
// AND h2.lgc = ${lgc}
|
||||
// WHERE h2.hi2_sn IS NULL LIMIT 10`
|
||||
// );
|
||||
const [rows] = await Sequelize.query(
|
||||
`SELECT i.hotel_id ,IFNULL(h.hi2_sn, 0) info_exists
|
||||
FROM heytrip_ids AS i
|
||||
LEFT JOIN hotelinfo2 AS h ON h.hotel_id = i.hotel_id
|
||||
AND h.lgc = ${lgc}
|
||||
WHERE h.hi2_sn IS NULL
|
||||
AND update_flag != 99
|
||||
ORDER BY info_exists LIMIT 10`
|
||||
);
|
||||
const res = await this.syncInitHotelLgcDetailsAction(rows, LGC_MAPPED[lgc]);
|
||||
return res;
|
||||
};
|
||||
|
||||
chinaHotels = async () => {
|
||||
const [rows] = await Sequelize.query(
|
||||
'SELECT i.hotel_id, IFNULL(h.hi_sn ,0) info_exists FROM heytrip_ids as i LEFT JOIN hotelinfo AS h ON h.hotel_id = i.hotel_id WHERE h.hi_sn IS NULL AND update_flag != 0 AND i.hotel_id > 20000000 ORDER BY info_exists LIMIT 10'
|
||||
);
|
||||
const res = await this.syncInitHotelDetailsAction(rows, LGC_MAPPED['2']);
|
||||
return res;
|
||||
};
|
||||
chinaHotelsLgc2 = async (lgc) => {
|
||||
// const [rows] = await Sequelize.query(
|
||||
// `SELECT h.hotel_id
|
||||
// FROM hotelinfo AS h
|
||||
// LEFT JOIN hotelinfo2 AS h2 ON h.hotel_id =h2.hotel_id
|
||||
// AND h2.lgc = ${lgc}
|
||||
// WHERE h2.hi2_sn IS NULL LIMIT 10`
|
||||
// );
|
||||
const [rows] = await Sequelize.query(
|
||||
`SELECT i.hotel_id ,IFNULL(h.hi2_sn, 0) info_exists
|
||||
FROM heytrip_ids AS i
|
||||
INNER JOIN hotelinfo AS h1 ON h1.hotel_id =i.hotel_id
|
||||
LEFT JOIN hotelinfo2 AS h ON h.hotel_id = i.hotel_id
|
||||
AND h.lgc = ${lgc}
|
||||
WHERE h.hi2_sn IS NULL
|
||||
AND update_flag != 99
|
||||
AND h1.country_code ='CN'
|
||||
-- AND i.hotel_id > 20000000
|
||||
ORDER BY info_exists LIMIT 10`
|
||||
);
|
||||
const res = await this.syncInitHotelLgcDetailsAction(rows, LGC_MAPPED[lgc]);
|
||||
return res;
|
||||
};
|
||||
|
||||
syncInitHotelDetailsAction = async (rows, lgcObj = LGC_MAPPED[DEFAULT_LGC]) => {
|
||||
let allIds = [];
|
||||
try {
|
||||
|
||||
allIds = rows.map((item) => item.hotel_id);
|
||||
const updateIds = rows.filter((item) => item.info_exists !== 0).map((item) => item.hotel_id);
|
||||
const newIds = rows.filter((item) => item.info_exists === 0).map((item) => item.hotel_id);
|
||||
|
||||
if (isEmpty(rows)) {
|
||||
return { next: !isEmpty(allIds), data: allIds };
|
||||
}
|
||||
const res = await AccommodationsDetails({
|
||||
Language: lgcObj.locale,
|
||||
AccommodationIds: allIds,
|
||||
});
|
||||
|
||||
const resIds = res.map((item) => item.HotelId);
|
||||
// hotel info
|
||||
const insertData = extractDetails(res, lgcObj);
|
||||
// return insertData; // debug: 0
|
||||
|
||||
/** 开始Database */
|
||||
const sequelizeOptions = { logging: false };
|
||||
const result = await Sequelize.transaction(async t => {
|
||||
let Info;
|
||||
if ( ! isEmpty(insertData.info)) Info = await Hotelinfo.bulkCreate(insertData.info, sequelizeOptions);
|
||||
if ( ! isEmpty(insertData.info2)) await Hotelinfo2.bulkCreate(insertData.info2, sequelizeOptions);
|
||||
if ( ! isEmpty(insertData.rooms)) await Rooms.bulkCreate(insertData.rooms, sequelizeOptions);
|
||||
if ( ! isEmpty(insertData.images)) await Images.bulkCreate(insertData.images, sequelizeOptions);
|
||||
if ( ! isEmpty(insertData.facility)) await Facility.bulkCreate(insertData.facility, sequelizeOptions);
|
||||
if ( ! isEmpty(insertData.infos)) await Informations.bulkCreate(insertData.infos, sequelizeOptions);
|
||||
if ( ! isEmpty(insertData.reviews)) await Reviews.bulkCreate(insertData.reviews, sequelizeOptions);
|
||||
if ( ! isEmpty(insertData.reviews_summaries)) await ReviewsSummaries.bulkCreate(insertData.reviews_summaries, sequelizeOptions);
|
||||
if ( ! isEmpty(insertData.locations)) await Locations.bulkCreate(insertData.locations, sequelizeOptions);
|
||||
if ( ! isEmpty(allIds)) await HeytripIds.update({ update_flag: 0 }, { where: { hotel_id: allIds } });
|
||||
return Info;
|
||||
});
|
||||
|
||||
return { next: !isEmpty(allIds), data: allIds };
|
||||
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
return { next: false, data: allIds };
|
||||
}
|
||||
}
|
||||
|
||||
syncInitHotelLgcDetailsAction = async (rows, lgcObj = LGC_MAPPED[DEFAULT_LGC]) => {
|
||||
let allIds = [];
|
||||
try {
|
||||
|
||||
allIds = rows.map((item) => item.hotel_id);
|
||||
if (isEmpty(rows)) {
|
||||
return { next: !isEmpty(allIds), data: allIds };
|
||||
}
|
||||
const _BaseInfoExists = await Hotelinfo.findAll({ where: { hotel_id: allIds } });
|
||||
const updateIds = _BaseInfoExists.map((item) => `${item.hotel_id}`);
|
||||
console.log('updateIds', updateIds);
|
||||
|
||||
|
||||
const res = await AccommodationsDetails({
|
||||
Language: lgcObj.locale,
|
||||
AccommodationIds: allIds,
|
||||
});
|
||||
|
||||
const resIds = res.map((item) => item.HotelId);
|
||||
// hotel info
|
||||
const insertData = resolveDetails(res, lgcObj);
|
||||
// return insertData; // debug: 0
|
||||
|
||||
/** 开始Database */
|
||||
const result = await Sequelize.transaction(async transaction => {
|
||||
const sequelizeOptions = { logging: false, transaction };
|
||||
let Info;
|
||||
|
||||
const newInfo = insertData.info.filter(iitem => !updateIds.includes(`${iitem.hotel_id}`));
|
||||
console.log('newInfo', newInfo.map(xx => xx.hotel_id));
|
||||
if ( ! isEmpty(newInfo)) Info = await Hotelinfo.bulkCreate(newInfo, sequelizeOptions);
|
||||
|
||||
if ( ! isEmpty(insertData.info2)) await Hotelinfo2.bulkCreate(insertData.info2, sequelizeOptions);
|
||||
if ( ! isEmpty(insertData.rooms)) await Rooms.bulkCreate(insertData.rooms, sequelizeOptions);
|
||||
if ( ! isEmpty(insertData.images)) await Images.bulkCreate(insertData.images, sequelizeOptions);
|
||||
if ( ! isEmpty(insertData.facility)) await Facility.bulkCreate(insertData.facility, sequelizeOptions);
|
||||
if ( ! isEmpty(insertData.infos)) await Informations.bulkCreate(insertData.infos, sequelizeOptions);
|
||||
if ( ! isEmpty(insertData.reviews)) await Reviews.bulkCreate(insertData.reviews, sequelizeOptions);
|
||||
if ( ! isEmpty(insertData.reviews_summaries)) await ReviewsSummaries.bulkCreate(insertData.reviews_summaries, sequelizeOptions);
|
||||
if ( ! isEmpty(insertData.locations)) await Locations.bulkCreate(insertData.locations, sequelizeOptions);
|
||||
|
||||
if ( ! isEmpty(newInfo)) await HeytripIds.update({ update_flag: 0 }, { where: { hotel_id: newInfo.map(x => x.hotel_id) } });
|
||||
|
||||
return Info;
|
||||
});
|
||||
|
||||
return { next: !isEmpty(allIds), data: allIds };
|
||||
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
return { next: false, data: allIds };
|
||||
}
|
||||
};
|
||||
|
||||
getHotelAvailability = async (param) => {
|
||||
const { hotel_id, checkin, checkout, adults, children_ages, rooms, nationality } = param;
|
||||
const paramBody = {
|
||||
Language: 'en-US',
|
||||
// Language: 'zh-CN',
|
||||
AccommodationIds: [Number(hotel_id)],
|
||||
CheckInDate: checkin,
|
||||
CheckOutDate: checkout,
|
||||
Nationality: nationality || 'CN', // 默认取中国报价
|
||||
NumberOfAdults: adults || 1,
|
||||
ChildrenAges: children_ages || null, // 入住每个儿童年龄 [6,8]
|
||||
// ChildrenAges: null,
|
||||
NumberOfRooms: rooms || 1,
|
||||
Currency: 'CNY',
|
||||
};
|
||||
const _quoteRes = await Availability(paramBody);
|
||||
const quoteRes = resolveRatePlans(_quoteRes);
|
||||
return quoteRes;
|
||||
};
|
||||
}
|
||||
module.exports = new Heytrip();
|
@ -0,0 +1,17 @@
|
||||
const db = require('../config/db');
|
||||
const initModels = require('./../models/init-models');
|
||||
|
||||
const Sequelize = db.sequelize;
|
||||
const models = initModels(Sequelize);
|
||||
|
||||
const Logs = models.requestLogsModel;
|
||||
|
||||
// Logs.sync({ force: false });
|
||||
|
||||
class requestLogs {
|
||||
static async create(data) {
|
||||
return await Logs.create(data, { logging: false });
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = requestLogs;
|
@ -0,0 +1,87 @@
|
||||
const crypto = require('crypto');
|
||||
const axios = require('axios');
|
||||
const { get, post } = axios;
|
||||
|
||||
const { HEYTRIP_API, HEYTRIP_API_PROD } = require('./../config/constants');
|
||||
const { chunk, isEmpty } = require('../utils/commons');
|
||||
|
||||
const make_token = () => {
|
||||
var apiKey = '18daad53d0ec4003a207c41ddaf63b78';
|
||||
var secret = 'f76e547e55964812bf94cc0d31f74333';
|
||||
var timestamp = Math.round(new Date().getTime() / 1000);
|
||||
var hash = crypto
|
||||
.createHash('sha512')
|
||||
.update(apiKey + secret + timestamp)
|
||||
.digest('hex');
|
||||
var authHeaderValue = 'Bearer apikey=' + apiKey + ',signature=' + hash + ',timestamp=' + timestamp;
|
||||
return authHeaderValue;
|
||||
};
|
||||
const AvailableAccommodationIds = async (pageIndex) => {
|
||||
const response = await get(`${HEYTRIP_API_PROD}/AvailableAccommodationIds?pageIndex=${pageIndex}`, {
|
||||
headers: {
|
||||
Authorization: make_token(),
|
||||
},
|
||||
});
|
||||
console.log('Call pageIndex', pageIndex, response.data.TotalPage);
|
||||
return response.data.Data;
|
||||
};
|
||||
|
||||
const AccommodationsDetails = async (body) => {
|
||||
if (isEmpty(body.AccommodationIds)) {
|
||||
return [];
|
||||
}
|
||||
const response = await post(
|
||||
`${HEYTRIP_API_PROD}/AccommodationsDetails`,
|
||||
{ ...body },
|
||||
{
|
||||
headers: {
|
||||
Authorization: make_token(),
|
||||
},
|
||||
}
|
||||
);
|
||||
return Object.values(response.data.Data || {});
|
||||
};
|
||||
|
||||
const Availability = async (body) => {
|
||||
// console.log('Call Heytrip');
|
||||
const response = await post(
|
||||
`${HEYTRIP_API_PROD}/Availability`,
|
||||
{ ...body },
|
||||
{
|
||||
headers: {
|
||||
Authorization: make_token(),
|
||||
},
|
||||
}
|
||||
);
|
||||
// console.log(response.config);
|
||||
return response.data.Data || [];
|
||||
};
|
||||
|
||||
const QuotedHotelsPrice = async (body) => {
|
||||
const idsChunk = body.hotelIds.length > 10 ? chunk(body.hotelIds, 10) : [body.hotelIds];
|
||||
let quoteRes = [];
|
||||
for await (const piece of idsChunk) {
|
||||
const response = await post(
|
||||
`${HEYTRIP_API_PROD}/QuotedHotelsPrice`,
|
||||
{ ...body, hotelIds: piece },
|
||||
{
|
||||
headers: {
|
||||
Authorization: make_token(),
|
||||
},
|
||||
}
|
||||
);
|
||||
quoteRes = quoteRes.concat(response.data.Data || []);
|
||||
}
|
||||
return quoteRes;
|
||||
};
|
||||
|
||||
const Country = async (param) => {};
|
||||
|
||||
const City = async (param) => {};
|
||||
|
||||
module.exports = {
|
||||
AvailableAccommodationIds,
|
||||
AccommodationsDetails,
|
||||
Availability,
|
||||
QuotedHotelsPrice,
|
||||
};
|
@ -0,0 +1,3 @@
|
||||
<h1><%= message %></h1>
|
||||
<h2><%= error.status %></h2>
|
||||
<pre><%= error.stack %></pre>
|
@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title><%= title %></title>
|
||||
<link rel='stylesheet' href='/stylesheets/style.css' />
|
||||
</head>
|
||||
<body>
|
||||
<h1><%= title %></h1>
|
||||
<p>EJS Welcome to <%= title %></p>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue