react-cms
Version:
For personal use. Not production.
1,474 lines (917 loc) • 28.4 kB
JavaScript
;
import React from 'react';
import ReactDom from 'react-dom/server';
import { match, RouterContext } from 'react-router';
import { Provider } from "react-redux";
const fs = require('fs');
// import configureStore from '../../../app/store';
// import routes from "../../../app/routes";
// import {MainApp} from "modules/Site/components/App/";
var Model = require('objection').Model;
// import Response from './components/response';
const debug = require('debug')("react-cms:router");
import md5 from 'md5';
import fetch from 'node-fetch';
const geoip = require('geoip-lite');
const FormData = require('form-data');
// import config, {
// db as db_config,
// host,
// site_url,
// } from '../../config/config';
// let {
// connection: {
// prefix,
// },
// } = db_config;
let knex;
let styles = {};
let apiData;
let mapData;
let citiesData;
/*
OLD Router
*/
export default class Router {
clients = []; // WS-соединения
users = []; // Активные пользователи
constructor(options){
// const SendMODXRequest = (action, params) => {
// return this.SendMODXRequest(action, params, req);
// }
this.inited = false;
const {
config,
db_config,
// host,
site_url,
configureStore,
routes,
MainApp,
Response,
defaultQuery,
rootResolver,
RootType,
Mutation,
rootDirectives,
} = options;
let {
connection: {
prefix,
},
} = db_config;
this.configureStore = configureStore;
this.routes = routes;
this.MainApp = MainApp;
this.Response = Response;
this.site_url = site_url;
this.prefix = prefix;
this.defaultQuery = defaultQuery;
this.rootResolver = rootResolver;
this.RootType = RootType;
this.Mutation = Mutation;
this.rootDirectives = rootDirectives;
knex = require('knex')(db_config);
this.response = new this.Response(this, null, null, knex, config, this.clients, this.SendMessage, ::this.SendMODXRequest);
// this.response.localQuery({
// operationName: "Resources",
// variables: {
// resourceExcludeTemplates: 0,
// },
// req: {},
// })
// .then(r => {
// const {
// resources,
// } = r.data;
// this.resources = resources;
// })
// .catch(e => {
// console.error(e);
// });
this.router = this.createRouter(options);
this.init();
}
async init(){
await this.loadData();
this.setReloadDataInterval();
}
setReloadDataInterval(){
setInterval(::this.reloadData, 1000 * 600);
}
async loadData() {
// debug("loadData main");
// knex.raw("SET SESSION group_concat_max_len = 10000000;").then().catch(e => {
// console.error("SET SESSION Error", e);
// });
await this.loadApiData();
// this.loadMapData();
await this.loadCitiesData();
this.loadRedirects();
this.inited = true;
return true;
}
async reloadData(){
// debug("reloadData");
const result = await this.loadData();
// debug("apiData reloaded");
return result;
}
loadApiData(){
return this.response.remoteQuery({
operationName: "apiData",
variables: {
resourceExcludeTemplates: 0,
getCompanyGallery: true,
getImageFormats: false,
},
req: {},
})
.then(r => {
apiData = r.data;
this.response.initData(apiData);
})
.catch(e => {
console.error(e);
});
}
loadCitiesData(){
// this.response.localQuery({
// operationName: "Cities",
// variables: {
// limit: 0,
// getCompanyGallery: false,
// // getImageFormats: true,
// getTVs: false,
// },
// req: {},
// })
// .then(r => {
// citiesData = r.data;
// })
// .catch(e => {
// console.error(e);
// });
}
loadRedirects(){
this.response.remoteQuery({
operationName: "Redirects",
variables: {
redirectsLimit: 0,
},
})
.then(r => {
const {
redirects,
} = r.data;
this.redirects = redirects;
})
.catch(e => {
console.error(e);
});
}
clearCache = () => {
return this.loadData();
}
createRouter = (options) => {
const {
} = options || {};
var express = require('express');
let router = express.Router();
// debug("Server started");
var httpServ = require('http');
require('express-ws')(options.app);
/*
WS
*/
const SendMessageToAll = function(ws, message, original_message, exclude_current){
let clients = this.clients;
delete message.cookie;
delete message.password;
if(original_message){
delete original_message.cookie;
delete original_message.password;
}
for(var i in clients){
var client = clients[i];
if(exclude_current && client == ws){
continue;
}
this.SendMessage(client, message, original_message);
}
}
// setInterval(() => {
// let res = {};
// let req = {};
// let request = {};
// let response = new Response(req, res, request, knex, clients, SendMessage, config);
// // return response.process();
// response.notifyUsersUnreadMessages({
// headers: {},
// }, {}, response, {});
// }, 1000 * 60 * 5);
/*
Cлужба рассылки уведомлений о новых письмах
*/
// setInterval(() => {
// let res = {};
// let req = {};
// let request = {};
// let response = new Response(req, res, request, knex, clients, SendMessage, config);
// // return response.process();
// response.notifyUsersUnreadMessages({
// headers: {},
// }, {}, response, {});
// }, 1000 * 60 * 5);
const success = function(req, res, response, knex){
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify(response));
}
const failure = function(req, res, response){
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify(response));
}
const processResponse = function(req, res, response){
if(response.success){
return success(req, res, response);
}
else{
return failure(req, res, response);
}
}
/*
* Static
* */
/*
* Надстройка WebSocket для роутера
* */
// var expressWs = require('express-ws')(options.app);
/*
* API
* */
//
const SendUsersActivity = function(){
let clients =this.clients;
var users_list = [];
var ids = {};
var total_active_clients = 0;
for(var i in clients){
var client = clients[i];
if(
client.readyState == client.OPEN
){
total_active_clients++;
if(
client.user
&& client.user.id
&& !ids[client.user.id]
){
ids[client.user.id] = true;
users_list.push(client.user);
}
}
}
users_list.reverse();
// for(var i in clients){
// SendMessage(clients[i], {
// type: "active_users_list",
// users: users_list,
// });
// }
/*
* Если список пользователей изменился, отправляем статистику
* */
if(md5(users_list) != md5(users)){
users = users_list;
SendMessageToAll({
type: "active_users_list",
users: users,
});
}
// debug('SendUsersActivity');
// debug('total_active_clients', total_active_clients);
// debug('users', users);
return;
}
function SendMessageToUsers(message, users_ids){
let clients = this.clients;
if(!users_ids || users_ids == ""){
return;
}
users_ids = users_ids.split(",");
for(var i in users_ids){
users_ids[i] = Number(users_ids[i]);
}
clients.map(function(client){
// var client = clients[i];
// if(exclude_current && client == ws){
// continue;
// }
// break;
if(users_ids.indexOf(client.user_id) != -1){
this.SendMessage(client, message);
}
// SendMessage(client, message);
});
}
function SendMessageToUsers(message, users_ids){
let clients = this.clients;
if(!users_ids || users_ids == ""){
return;
}
users_ids = users_ids.split(",");
for(var i in users_ids){
users_ids[i] = Number(users_ids[i]);
}
clients.map(function(client){
if(users_ids.indexOf(client.user_id) != -1){
this.SendMessage(client, message);
}
});
}
router.ws('/api/', (ws, req) => {
try{
this.processWsRequest(ws, req);
}
catch(e){
console.error(e);
}
});
/*
* API
* */
// /*
// * Собираем кукисы из оригинального запроса и если передаются куки в параметрах запроса,
// * то объединяем их
// * */
router.post('/api/', ::this.processPostRequest);
router.use('/', ::this.processMainRequest);
return router;
};
async processPostRequest(req, res, next){
const startTime = new Date().getTime();
// debug("processPostRequest");
const request = Object.assign(req.query, req.body);
const result = await this.response.process(req, res, request);
const endTime = new Date().getTime();
const diff = (endTime - startTime) / 1000;
// debug("processPostRequest", `${diff.toFixed(3)} sec`);
};
processWsRequest = (ws, req) => {
let clients = this.clients;
// debug("Server. WS Requested");
ws.id = md5(new Date().getTime());
clients.push(ws);
ws.on('message', async (message) => {
// debug('Я получил от вас сообщение: ' + message);
try{
message = JSON.parse(message);
// let response = message;
// debug("Server. Received message", message);
const {
type,
} = message;
var result = {};
const raw_text = message.text;
const text = raw_text;
switch(type){
case 'message':
var client_id = message.client_id;
if(!client_id){
result = {
type: "error"
,text: "Не был указан ID клиента"
};
}
else if(!text){
result = {
type: "error"
,text: "Текст не может быть пустым"
};
}
else{
result = {
type: "message"
,sender: {
id: client_id
,guest: ws.client_data.guest
,name: ws.client_data.name
,photo: ws.client_data.photo
}
,text: text
};
}
if(result.type == "error"){
ws.send(JSON.stringify(result));
}
else{
for(var i in clients){
if(clients[i].readyState == WebSocket.OPEN){
clients[i].send(JSON.stringify(result));
}
}
var d = new Date();
var n = d.getTime();
Message.query().insert({
text: text
,raw_text: raw_text
,ts: String(n).substr(0,10) + '.' + String(n).substr(10)
,user_id: ws.user_id
,channel_id: ws.channel_id
}).then(function (record) {
});
}
break;
case 'joined':
ws.user_id = response.id;
break;
case 'coords':
const {
coords,
} = message;
ws.coords = coords;
break;
default:
this.SendMessage(ws, {
type: "error"
,text: "Неизвестный тип сообщения"
}, response);
}
}
catch(e){
console.error(e);
}
});
// ws.send(JSON.stringify({
// type: "hello"
// }));
ws.on('close', function(){
// debug("Соединение закрыто");
for(var i in clients){
if(clients[i] === ws){
clients.splice(i, 1);
debug("Удален клиент из общего списка");
break;
}
}
// SendUsersActivity();
});
this.SendMessage(ws, {
type: "hello"
}, {});
};
async processMainRequest(req, res){
const startTime = new Date().getTime();
// debug("processMainRequest");
if(!this.inited){
console.error("Сервер все еще запускается");
return res.status(503).send("Сервис запускается");
}
const url = req.url;
const decodedURI = decodeURI(req.url).replace(/\@[0-9\.\,]+/, '');
// Яндекс наиндексировал херни
let redirectMatch;
if(decodedURI){
if(/.+\/bani-otzivy\/$/.test(decodedURI)){
return res.redirect(301, '/bani-otzivy/');
}
// else if(/.+\/topics\/$/.test(decodedURI)){
// return res.redirect(301, '/topics/');
// }
redirectMatch = decodedURI.match(/.+(\/(topics|city|ratings)\/.*)/);
if(redirectMatch && redirectMatch[1]){
return res.redirect(301, redirectMatch[1]);
}
if(/.+\/contacts.html$/.test(decodedURI)){
return res.redirect(301, '/contacts.html');
}
}
await match({
routes: this.routes,
location: url,
}, async (error, redirectLocation, renderProps) => {
const {
redirects,
} = this;
const redurectUri = decodedURI.replace(/^\/+/, '');
// const resource = this.resources && this.resources.find(n => n.uri === redurectUri);
// if(redurectUri && !resource){
// const redirect = redirects && redirects.find(n => n.uri === redurectUri || `${n.uri}/` === redurectUri);
// if (redirect) { // Если необходимо сделать redirect
// const link = '/' + redirect.redirect_uri;
// if(decodedURI !== link){
// return res.redirect(301, link);
// }
// }
// }
if (redirectLocation) { // Если необходимо сделать redirect
return res.redirect(301, redirectLocation.pathname + redirectLocation.search);
}
if (error) { // Произошла ошибка любого рода
return res.status(500).send(error.message);
}
if (!renderProps) { // мы не определили путь, который бы подошел для URL
const prefix = this.prefix;
// if(!prevent){
// debug("knex resreq.headers", req);
var q = knex(`${prefix}monitor_requests`)
.insert({
site_url: req.hostname,
url: req.url,
http_status: 404,
context_key: "web",
parent: 0,
path: '/www/gorodskie-bani.ru/',
uuid: '',
ip: '',
time: 0,
php_error_info: '',
referer: '',
user_agent: req.headers['user-agent'],
})
;
q.then((result) => {
// debug("knex SQL", q.toString());
// debug("knex result", result);
}).catch(e => {
console.error(e);
});
return res.status(404).send('Not found');
// }
}
let html;
try{
const {
params,
location,
routes,
} = renderProps;
let ip = req.headers['x-real-ip'];
if(!ip || /^127./.test(ip)){
ip = "178.219.186.12";
// ip = "109.184.14.163";
}
let geo = geoip.lookup(ip);
if(!geo){
geo = { range: [ 3000743936, 3000744959 ],
country: 'RU',
region: '48',
city: 'Moscow',
ll: [ 55.7485, 37.6184 ],
metro: 0,
zip: 101194,
};
}
// const {
// 1: baseRouter,
// } = routes || [];
// const {
// component: Component,
// } = baseRouter || {};
// let component = "MainPage";
// if(Component){
// switch(Component){
// // Страница компаний
// case CompaniesPage:
// component = "CompaniesPage";
// break;
// }
// }
// else{
// throw("Не был получен базовый компонент");
// }
let resourceData = await this.response.localQuery({
operationName: "SiteContent",
variables: {
request: renderProps,
// component,
geo,
},
req,
})
.then(r => {
return r.data;
})
.catch(e => {
console.error("Server SiteContent error", e);
// console.error("Server SiteContent error", JSON.stringify(e));
// e = JSON.parse(e);
throw(e);
});
// let {
// // state: __state,
// siteContent,
// ... debugState
// } = resourceData || {};
let {
siteContent,
} = resourceData || {};
let {
status,
state: resourceState,
user,
} = siteContent || {};
if(!siteContent){
status = 404;
}
/*
Если статус 404, то смотрим редиректы
*/
if(status === 404 && redurectUri){
const redirect = redirects && redirects.find(n => n.uri === redurectUri || `${n.uri}/` === redurectUri);
if (redirect) { // Если необходимо сделать redirect
const link = '/' + redirect.redirect_uri;
if(decodedURI !== link){
return res.redirect(301, link);
}
}
}
let {
coords,
} = resourceState || {};
// Перетираем координаты, если есть/
if(coords){
geo.ll = [coords.lat, coords.lng];
}
// Запрашиваем данные для пользователя
let store = this.configureStore();
let state = store.getState();
Object.assign(state.document, {
// apiData,
// mapData,
// citiesData,
geo,
resourceState: siteContent || null,
});
Object.assign(state.user, {
user,
});
store = this.configureStore(state);
let appExports = {};
const MainApp = this.MainApp;
// debug("ReactDom.renderToString");
// const componentHTML = ReactDom.renderToString(
// <MainApp
// appExports={appExports}
// defaultQuery={this.defaultQuery}
// rootResolver={this.rootResolver}
// RootType={this.RootType}
// Mutation={this.Mutation}
// rootDirectives={this.rootDirectives}
// >
// <Provider store={store}>
// <RouterContext
// {...renderProps}
// />
// </Provider>
// </MainApp>
// );
const componentHTML = ReactDom.renderToString(
<MainApp
appExports={appExports}
>
<Provider store={store}>
<RouterContext
{...renderProps}
/>
</Provider>
</MainApp>
);
// Object.assign(renderProps, {
// appExports,
// });
// const componentHTML = ReactDom.renderToString(
// <Provider store={store}>
// <RouterContext
// // appExports={appExports}
// {...renderProps}
// />
// </Provider>
// );
const stylesGenerated = appExports.theme && appExports.theme.sheetsToString();
// debug("ReactDom.renderToString result");
// let style = '<style>';
// style += stylesGenerated;
// style += '</style>';
let style = '';
style += stylesGenerated;
// const stylesGenerated = exports.theme.sheetsToString();
let searchable;
if(/\/profile\/.*?\/add-topic/.test(decodedURI)){
searchable = false;
}
// html = this.renderHTML(req, componentHTML, state, resource, style, searchable, appExports);
html = this.renderHTML(req, componentHTML, state, style, searchable, appExports);
if(status && status !== 200){
res.status(status);
}
}
catch(e){
console.error("Server Response error", e);
return res.status(500).send(e.message || e);
};
return res.end(html);
});
// const endTime = new Date().getTime();
// const diff = (endTime - startTime) / 1000;
// debug("processMainRequest", `${diff.toFixed(3)} sec`);
return;
};
// renderHTML(req, componentHTML, initialState, resource, style, searchable, appExports) {
renderHTML(req, componentHTML, initialState, style, searchable, appExports) {
let assetsUrl;
let js_src;
let css_src;
let inline_styles;
let basePath = process.cwd() + "/";
let buildPath = basePath + "build/";
if(process.env.NODE_ENV === 'production'){
assetsUrl = "/assets/components/modxsite/templates/v2.0/build/";
var htmlFileName = "index.html";
// var html = fs.readFileSync(Path.join(assetsUrl, htmlFileName), "utf8");
var html = fs.readFileSync( `${buildPath}${htmlFileName}`, "utf8");
let match = html.match(/<script .*?src="(.*?)"/);
if(match){
js_src = match[1];
}
let css_match = html.match(/<link [^>]*?href="([^\"]*?\.css)" rel="stylesheet"/);
if(css_match){
css_src = css_match[1];
// style = `
// @import url('${css_src}');
// ${style}
// `;
// const css_array = css_src.split("/");
// css_src = "";
// var filename = `${buildPath}css/${css_array[css_array.length - 1]}`;
// inline_styles = fs.readFileSync(filename, "utf-8");
// inline_styles = `<style>${inline_styles}</style>`;
}
}
else{
assetsUrl = "/build/";
js_src = `${assetsUrl}main.js`;
css_src = `${assetsUrl}css/main.css`;
}
let jState = "";
const outputState = initialState.document.outputState;
const {
resourceState,
} = initialState.document || {};
Object.assign(initialState.document, {
apiData: null,
outputState: null,
inputState: appExports.outputState,
// mapData: appExports.mapData || null,
mapData: null,
// citiesData,
// geo,
});
jState = JSON.stringify(initialState);
jState = jState.replace(/<script.*?>.*?<\/script>/g, '');
// let {
// name,
// longtitle,
// description,
// searchable: resourceSearchable,
// } = resource || {};
let {
title,
description,
robots,
status,
} = resourceState || {};
description = description && description.replace('"', '\"') || '';
const headerScripts = this.getHeaderScripts();
return `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${title || ''}</title>
<meta name="description" content="${description || ''}">
<meta name="robots" content="${robots ? robots : "index, follow"}" />
<link rel="shortcut icon" href="/assets/components/modxsite/templates/pivkarta/v2/favicon.ico"/>
<base href="/" />
${headerScripts}
${style && `<style>${style}</style>` || ""}
${css_src ? `<link rel="stylesheet" href="${css_src}">` : ""}
${inline_styles || ""}
</head>
<body>
<div id="root">${componentHTML}</div>
</body>
<script type="application/javascript">
window.REDUX_INITIAL_STATE = ${jState};
</script>
<script type="application/javascript" src="${js_src}" async></script>
</html>
`;
};
SendMessage = (client, message, original_message) => {
if(client && client.readyState === client.OPEN){
if(typeof message !== "object"){
message = {
text: "message"
};
}
if(!message.ts){
message.ts = new Date().getTime();
}
delete message.cookie;
delete message.password;
if(original_message){
delete original_message.cookie;
delete original_message.password;
message.original_message = original_message;
}
// client.send(JSON.stringify(message));
client && typeof client.send === "function" && client.send(JSON.stringify(message));
}
}
getHeaderScripts(){
return '';
}
async SendMODXRequest(action, params, req){
// return {};
// const req = this.req;
const method = 'POST';
let {
url = `/assets/components/modxsite/connectors/connector.php?pub_action=${action}`,
} = params;
let options = {
// host: host,
// port: 80,
// path: url,
method,
headers: {
// 'Content-Type': 'application/x-www-form-urlencoded',
// 'Content-Length': Buffer.byteLength(postData)
},
// json: {
// users: users
// }
};
let form;
let {
sort,
...other
} = params;
params = {...other};
if(sort){
if(Array.isArray(sort)){
sort = sort[0];
if(sort){
params.sort = sort.by;
params.dir = sort.dir || undefined;
}
}
}
if(method == 'POST' && params){
options.body = JSON.stringify(params);
}
/*
* Собираем кукисы из оригинального запроса и если передаются куки в параметрах запроса,
* то объединяем их
* */
var cookies = [];
let cookies_obj;
if(req.headers && req.headers.cookie){
let cooks = req.headers.cookie.split(";");
cookies_obj = {};
cooks.map(function(item){
var match = item.match(/ *(.+?)=(.+)/);
if(match){
cookies_obj[match[1]] = match[2];
}
});
}
if(cookies_obj){
for(var i in cookies_obj){
cookies.push(i + '=' + cookies_obj[i]);
}
}
if(cookies){
options.headers.Cookie = cookies;
// debug("options.headers", options.headers);
}
let result = await fetch(this.site_url + url, options)
.then(function(res) {
return res.json();
})
.then(function(r) {
return r;
})
.catch(e => {
console.error(e);
});
if(result){
if(result.success){
result.data = result.object;
}
result.count = result.object && Array.isArray(result.object) ? result.object.length : result.object ? 1 : 0;
}
return result;
};
}