|
@@ -1,22 +1,14 @@
|
|
|
const bcrypt = require('bcryptjs');
|
|
const bcrypt = require('bcryptjs');
|
|
|
const _ = require('lodash/');
|
|
const _ = require('lodash/');
|
|
|
-const {
|
|
|
|
|
- isAfter,
|
|
|
|
|
- isSameHour,
|
|
|
|
|
- isSameDay,
|
|
|
|
|
- isSameMonth,
|
|
|
|
|
- subDays,
|
|
|
|
|
- subHours,
|
|
|
|
|
- subMonths,
|
|
|
|
|
-} = require('date-fns');
|
|
|
|
|
|
|
+const { isAfter, subDays } = require('date-fns');
|
|
|
const driver = require('./neo4j');
|
|
const driver = require('./neo4j');
|
|
|
const config = require('../config');
|
|
const config = require('../config');
|
|
|
-const { generateShortUrl } = require('../utils');
|
|
|
|
|
-
|
|
|
|
|
-const getUTCDate = (dateString = Date.now()) => {
|
|
|
|
|
- const date = new Date(dateString);
|
|
|
|
|
- return new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours());
|
|
|
|
|
-};
|
|
|
|
|
|
|
+const {
|
|
|
|
|
+ generateShortUrl,
|
|
|
|
|
+ statsObjectToArray,
|
|
|
|
|
+ getDifferenceFunction,
|
|
|
|
|
+ getUTCDate,
|
|
|
|
|
+} = require('../utils');
|
|
|
|
|
|
|
|
const queryNewUrl = 'CREATE (l:URL { id: $id, target: $target, createdAt: $createdAt }) RETURN l';
|
|
const queryNewUrl = 'CREATE (l:URL { id: $id, target: $target, createdAt: $createdAt }) RETURN l';
|
|
|
|
|
|
|
@@ -289,7 +281,10 @@ exports.deleteUrl = ({ id, domain, user }) =>
|
|
|
.catch(err => session.close() || reject(err));
|
|
.catch(err => session.close() || reject(err));
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
-/* Collecting stats */
|
|
|
|
|
|
|
+/*
|
|
|
|
|
+** Collecting stats
|
|
|
|
|
+*/
|
|
|
|
|
+
|
|
|
const initialStats = {
|
|
const initialStats = {
|
|
|
browser: {
|
|
browser: {
|
|
|
IE: 0,
|
|
IE: 0,
|
|
@@ -314,118 +309,112 @@ const initialStats = {
|
|
|
dates: [],
|
|
dates: [],
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-const filterByDate = days => record => isAfter(record.date, subDays(getUTCDate(), days));
|
|
|
|
|
-
|
|
|
|
|
-/* eslint-disable no-param-reassign */
|
|
|
|
|
-const calcStats = (obj, record) => {
|
|
|
|
|
- obj.browser[record.browser] += 1;
|
|
|
|
|
- obj.os[record.os] += 1;
|
|
|
|
|
- obj.country[record.country] = obj.country[record.country] + 1 || 1;
|
|
|
|
|
- obj.referrer[record.referrer] = obj.referrer[record.referrer] + 1 || 1;
|
|
|
|
|
- obj.dates = [...obj.dates, record.date];
|
|
|
|
|
- return obj;
|
|
|
|
|
-};
|
|
|
|
|
-/* eslint-enable no-param-reassign */
|
|
|
|
|
-
|
|
|
|
|
-const objectToArray = item => {
|
|
|
|
|
- const objToArr = key =>
|
|
|
|
|
- Array.from(Object.keys(item[key]))
|
|
|
|
|
- .map(name => ({
|
|
|
|
|
- name,
|
|
|
|
|
- value: item[key][name],
|
|
|
|
|
- }))
|
|
|
|
|
- .sort((a, b) => b.value - a.value);
|
|
|
|
|
-
|
|
|
|
|
- return {
|
|
|
|
|
- browser: objToArr('browser'),
|
|
|
|
|
- os: objToArr('os'),
|
|
|
|
|
- country: objToArr('country'),
|
|
|
|
|
- referrer: objToArr('referrer'),
|
|
|
|
|
- };
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-const calcViewPerDate = (views, period, sub, compare, lastDate = getUTCDate(), arr = []) => {
|
|
|
|
|
- if (arr.length === period) return arr;
|
|
|
|
|
-
|
|
|
|
|
- const matchedStats = views.filter(date => compare(date, lastDate));
|
|
|
|
|
- const viewsPerDate = [matchedStats.length, ...arr];
|
|
|
|
|
-
|
|
|
|
|
- return calcViewPerDate(views, period, sub, compare, sub(lastDate, 1), viewsPerDate);
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-const calcViews = {
|
|
|
|
|
- 0: views => calcViewPerDate(views, 24, subHours, isSameHour),
|
|
|
|
|
- 1: views => calcViewPerDate(views, 7, subDays, isSameDay),
|
|
|
|
|
- 2: views => calcViewPerDate(views, 30, subDays, isSameDay),
|
|
|
|
|
- 3: views => calcViewPerDate(views, 18, subMonths, isSameMonth),
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
exports.getStats = ({ id, domain, user }) =>
|
|
exports.getStats = ({ id, domain, user }) =>
|
|
|
new Promise((resolve, reject) => {
|
|
new Promise((resolve, reject) => {
|
|
|
const session = driver.session();
|
|
const session = driver.session();
|
|
|
|
|
|
|
|
- session
|
|
|
|
|
- .readTransaction(tx =>
|
|
|
|
|
- tx.run(
|
|
|
|
|
- 'MATCH (l:URL { id: $id })<-[:CREATED]-(u:USER { email: $email }) ' +
|
|
|
|
|
- `${domain ? 'MATCH (l)-[:USES]->(domain { name: $domain })' : ''}` +
|
|
|
|
|
- 'MATCH (v)-[:VISITED]->(l) ' +
|
|
|
|
|
- 'MATCH (v)-[:BROWSED_BY]->(b) ' +
|
|
|
|
|
- 'MATCH (v)-[:LOCATED_IN]->(c) ' +
|
|
|
|
|
- 'MATCH (v)-[:OS]->(o) ' +
|
|
|
|
|
- 'MATCH (v)-[:REFERRED_BY]->(r) ' +
|
|
|
|
|
- 'MATCH (v)-[:VISITED_IN]->(d) ' +
|
|
|
|
|
- 'RETURN l, b.browser AS browser, c.country AS country,' +
|
|
|
|
|
- `${domain ? 'domain.name AS domain, ' : ''}` +
|
|
|
|
|
- 'o.os AS os, r.referrer AS referrer, d.date AS date ' +
|
|
|
|
|
- 'ORDER BY d.date DESC',
|
|
|
|
|
- {
|
|
|
|
|
- email: user.email,
|
|
|
|
|
- domain,
|
|
|
|
|
- id,
|
|
|
|
|
- }
|
|
|
|
|
- )
|
|
|
|
|
- )
|
|
|
|
|
- .then(({ records }) => {
|
|
|
|
|
- session.close();
|
|
|
|
|
-
|
|
|
|
|
- if (!records.length) resolve([]);
|
|
|
|
|
-
|
|
|
|
|
- const allStats = records.map(record => ({
|
|
|
|
|
- browser: record.get('browser'),
|
|
|
|
|
- os: record.get('os'),
|
|
|
|
|
- country: record.get('country'),
|
|
|
|
|
- referrer: record.get('referrer'),
|
|
|
|
|
- date: record.get('date'),
|
|
|
|
|
- }));
|
|
|
|
|
-
|
|
|
|
|
- const statsPeriods = [1, 7, 30, 550];
|
|
|
|
|
|
|
+ const stats = {
|
|
|
|
|
+ lastDay: {
|
|
|
|
|
+ stats: _.cloneDeep(initialStats),
|
|
|
|
|
+ views: new Array(24).fill(0),
|
|
|
|
|
+ },
|
|
|
|
|
+ lastWeek: {
|
|
|
|
|
+ stats: _.cloneDeep(initialStats),
|
|
|
|
|
+ views: new Array(7).fill(0),
|
|
|
|
|
+ },
|
|
|
|
|
+ lastMonth: {
|
|
|
|
|
+ stats: _.cloneDeep(initialStats),
|
|
|
|
|
+ views: new Array(30).fill(0),
|
|
|
|
|
+ },
|
|
|
|
|
+ allTime: {
|
|
|
|
|
+ stats: _.cloneDeep(initialStats),
|
|
|
|
|
+ views: new Array(18).fill(0),
|
|
|
|
|
+ },
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ let total = 0;
|
|
|
|
|
+
|
|
|
|
|
+ const statsPeriods = [[1, 'lastDay'], [7, 'lastWeek'], [30, 'lastMonth']];
|
|
|
|
|
|
|
|
- const stats = statsPeriods
|
|
|
|
|
- .map(statsPeriod => allStats.filter(filterByDate(statsPeriod)))
|
|
|
|
|
- .map(statsPeriod => statsPeriod.reduce(calcStats, _.cloneDeep(initialStats)))
|
|
|
|
|
- .map((statsPeriod, index) => ({
|
|
|
|
|
- stats: objectToArray(statsPeriod),
|
|
|
|
|
- views: calcViews[index](statsPeriod.dates),
|
|
|
|
|
- }));
|
|
|
|
|
-
|
|
|
|
|
- const response = {
|
|
|
|
|
- total: records.length,
|
|
|
|
|
|
|
+ session
|
|
|
|
|
+ .run(
|
|
|
|
|
+ 'MATCH (l:URL { id: $id })<-[:CREATED]-(u:USER { email: $email }) ' +
|
|
|
|
|
+ `${domain ? 'MATCH (l)-[:USES]->(domain { name: $domain })' : ''}` +
|
|
|
|
|
+ 'MATCH (v)-[:VISITED]->(l) ' +
|
|
|
|
|
+ 'MATCH (v)-[:BROWSED_BY]->(b) ' +
|
|
|
|
|
+ 'MATCH (v)-[:LOCATED_IN]->(c) ' +
|
|
|
|
|
+ 'MATCH (v)-[:OS]->(o) ' +
|
|
|
|
|
+ 'MATCH (v)-[:REFERRED_BY]->(r) ' +
|
|
|
|
|
+ 'MATCH (v)-[:VISITED_IN]->(d) ' +
|
|
|
|
|
+ 'RETURN l, b.browser AS browser, c.country AS country,' +
|
|
|
|
|
+ 'o.os AS os, r.referrer AS referrer, d.date AS date ' +
|
|
|
|
|
+ 'ORDER BY d.date DESC',
|
|
|
|
|
+ {
|
|
|
|
|
+ email: user.email,
|
|
|
|
|
+ domain,
|
|
|
id,
|
|
id,
|
|
|
- shortUrl: `http${!domain ? 's' : ''}://${
|
|
|
|
|
- domain ? records[0].get('domain') : config.DEFAULT_DOMAIN
|
|
|
|
|
- }/${id}`,
|
|
|
|
|
- target: records[0].get('l').properties.target,
|
|
|
|
|
- updatedAt: new Date().toISOString(),
|
|
|
|
|
- lastDay: stats[0],
|
|
|
|
|
- lastWeek: stats[1],
|
|
|
|
|
- lastMonth: stats[2],
|
|
|
|
|
- allTime: stats[3],
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- return resolve(response);
|
|
|
|
|
- })
|
|
|
|
|
- .catch(err => session.close() || reject(err));
|
|
|
|
|
|
|
+ }
|
|
|
|
|
+ )
|
|
|
|
|
+ .subscribe({
|
|
|
|
|
+ onNext(record) {
|
|
|
|
|
+ total += 1;
|
|
|
|
|
+ const browser = record.get('browser');
|
|
|
|
|
+ const os = record.get('os');
|
|
|
|
|
+ const country = record.get('country');
|
|
|
|
|
+ const referrer = record.get('referrer');
|
|
|
|
|
+ const date = record.get('date');
|
|
|
|
|
+
|
|
|
|
|
+ statsPeriods.forEach(([days, type]) => {
|
|
|
|
|
+ const isIncluded = isAfter(date, subDays(getUTCDate(), days));
|
|
|
|
|
+ if (isIncluded) {
|
|
|
|
|
+ const period = stats[type].stats;
|
|
|
|
|
+ const diffFunction = getDifferenceFunction(type);
|
|
|
|
|
+ const now = new Date();
|
|
|
|
|
+ const diff = diffFunction(now, date);
|
|
|
|
|
+ const index = stats[type].views.length - diff - 1;
|
|
|
|
|
+ const view = stats[type].views[index];
|
|
|
|
|
+ period.browser[browser] += 1;
|
|
|
|
|
+ period.os[os] += 1;
|
|
|
|
|
+ period.country[country] = period.country[country] + 1 || 1;
|
|
|
|
|
+ period.referrer[referrer] = period.referrer[referrer] + 1 || 1;
|
|
|
|
|
+ stats[type].views[index] = view + 1 || 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const allTime = stats.allTime.stats;
|
|
|
|
|
+ const diffFunction = getDifferenceFunction('allTime');
|
|
|
|
|
+ const now = new Date();
|
|
|
|
|
+ const diff = diffFunction(now, date);
|
|
|
|
|
+ const index = stats.allTime.views.length - diff - 1;
|
|
|
|
|
+ const view = stats.allTime.views[index];
|
|
|
|
|
+ allTime.browser[browser] += 1;
|
|
|
|
|
+ allTime.os[os] += 1;
|
|
|
|
|
+ allTime.country[country] = allTime.country[country] + 1 || 1;
|
|
|
|
|
+ allTime.referrer[referrer] = allTime.referrer[referrer] + 1 || 1;
|
|
|
|
|
+ allTime.dates = [...allTime.dates, date];
|
|
|
|
|
+ stats.allTime.views[index] = view + 1 || 1;
|
|
|
|
|
+ },
|
|
|
|
|
+ onCompleted() {
|
|
|
|
|
+ stats.lastDay.stats = statsObjectToArray(stats.lastDay.stats);
|
|
|
|
|
+ stats.lastWeek.stats = statsObjectToArray(stats.lastWeek.stats);
|
|
|
|
|
+ stats.lastMonth.stats = statsObjectToArray(stats.lastMonth.stats);
|
|
|
|
|
+ stats.allTime.stats = statsObjectToArray(stats.allTime.stats);
|
|
|
|
|
+ const response = {
|
|
|
|
|
+ total,
|
|
|
|
|
+ id: 'sv-v104',
|
|
|
|
|
+ updatedAt: new Date().toISOString(),
|
|
|
|
|
+ lastDay: stats.lastDay,
|
|
|
|
|
+ lastWeek: stats.lastWeek,
|
|
|
|
|
+ lastMonth: stats.lastMonth,
|
|
|
|
|
+ allTime: stats.allTime,
|
|
|
|
|
+ };
|
|
|
|
|
+ return resolve(response);
|
|
|
|
|
+ },
|
|
|
|
|
+ onError(error) {
|
|
|
|
|
+ session.close();
|
|
|
|
|
+ return reject(error);
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
exports.urlCountFromDate = ({ date, email }) =>
|
|
exports.urlCountFromDate = ({ date, email }) =>
|