180 lines
5.0 KiB
JavaScript
180 lines
5.0 KiB
JavaScript
const { isAfter, subDays, subHours, set } = require("date-fns");
|
|
|
|
const utils = require("../utils");
|
|
const redis = require("../redis");
|
|
const knex = require("../knex");
|
|
|
|
async function add(params) {
|
|
const data = {
|
|
...params,
|
|
country: params.country.toLowerCase(),
|
|
referrer: params.referrer.toLowerCase()
|
|
};
|
|
|
|
const visit = await knex("visits")
|
|
.where({ link_id: params.id })
|
|
.andWhere(
|
|
knex.raw("date_trunc('hour', created_at) = date_trunc('hour', ?)", [
|
|
knex.fn.now()
|
|
])
|
|
)
|
|
.first();
|
|
|
|
if (visit) {
|
|
await knex("visits")
|
|
.where({ id: visit.id })
|
|
.increment(`br_${data.browser}`, 1)
|
|
.increment(`os_${data.os}`, 1)
|
|
.increment("total", 1)
|
|
.update({
|
|
updated_at: new Date().toISOString(),
|
|
countries: knex.raw(
|
|
"jsonb_set(countries, '{??}', (COALESCE(countries->>?,'0')::int + 1)::text::jsonb)",
|
|
[data.country, data.country]
|
|
),
|
|
referrers: knex.raw(
|
|
"jsonb_set(referrers, '{??}', (COALESCE(referrers->>?,'0')::int + 1)::text::jsonb)",
|
|
[data.referrer, data.referrer]
|
|
)
|
|
});
|
|
} else {
|
|
await knex("visits").insert({
|
|
[`br_${data.browser}`]: 1,
|
|
countries: { [data.country]: 1 },
|
|
referrers: { [data.referrer]: 1 },
|
|
[`os_${data.os}`]: 1,
|
|
total: 1,
|
|
link_id: data.id
|
|
});
|
|
}
|
|
|
|
return visit;
|
|
};
|
|
|
|
async function find(match, total) {
|
|
if (match.link_id) {
|
|
const key = redis.key.stats(match.link_id);
|
|
const cached = await redis.client.get(key);
|
|
if (cached) return JSON.parse(cached);
|
|
}
|
|
|
|
const stats = {
|
|
lastDay: {
|
|
stats: utils.getInitStats(),
|
|
views: new Array(24).fill(0),
|
|
total: 0
|
|
},
|
|
lastWeek: {
|
|
stats: utils.getInitStats(),
|
|
views: new Array(7).fill(0),
|
|
total: 0
|
|
},
|
|
lastMonth: {
|
|
stats: utils.getInitStats(),
|
|
views: new Array(30).fill(0),
|
|
total: 0
|
|
},
|
|
lastYear: {
|
|
stats: utils.getInitStats(),
|
|
views: new Array(12).fill(0),
|
|
total: 0
|
|
}
|
|
};
|
|
|
|
const visitsStream = knex("visits").where(match).stream();
|
|
const nowUTC = utils.getUTCDate();
|
|
const now = new Date();
|
|
|
|
const periods = utils.getStatsPeriods(now);
|
|
|
|
for await (const visit of visitsStream) {
|
|
periods.forEach(([type, fromDate]) => {
|
|
const isIncluded = isAfter(new Date(visit.created_at), fromDate);
|
|
if (!isIncluded) return;
|
|
const diffFunction = utils.getDifferenceFunction(type);
|
|
const diff = diffFunction(now, new Date(visit.created_at));
|
|
const index = stats[type].views.length - diff - 1;
|
|
const view = stats[type].views[index];
|
|
const period = stats[type].stats;
|
|
stats[type].stats = {
|
|
browser: {
|
|
chrome: period.browser.chrome + visit.br_chrome,
|
|
edge: period.browser.edge + visit.br_edge,
|
|
firefox: period.browser.firefox + visit.br_firefox,
|
|
ie: period.browser.ie + visit.br_ie,
|
|
opera: period.browser.opera + visit.br_opera,
|
|
other: period.browser.other + visit.br_other,
|
|
safari: period.browser.safari + visit.br_safari
|
|
},
|
|
os: {
|
|
android: period.os.android + visit.os_android,
|
|
ios: period.os.ios + visit.os_ios,
|
|
linux: period.os.linux + visit.os_linux,
|
|
macos: period.os.macos + visit.os_macos,
|
|
other: period.os.other + visit.os_other,
|
|
windows: period.os.windows + visit.os_windows
|
|
},
|
|
country: {
|
|
...period.country,
|
|
...Object.entries(visit.countries).reduce(
|
|
(obj, [country, count]) => ({
|
|
...obj,
|
|
[country]: (period.country[country] || 0) + count
|
|
}),
|
|
{}
|
|
)
|
|
},
|
|
referrer: {
|
|
...period.referrer,
|
|
...Object.entries(visit.referrers).reduce(
|
|
(obj, [referrer, count]) => ({
|
|
...obj,
|
|
[referrer]: (period.referrer[referrer] || 0) + count
|
|
}),
|
|
{}
|
|
)
|
|
}
|
|
};
|
|
stats[type].views[index] += visit.total;
|
|
stats[type].total += visit.total;
|
|
});
|
|
}
|
|
|
|
const response = {
|
|
lastYear: {
|
|
stats: utils.statsObjectToArray(stats.lastYear.stats),
|
|
views: stats.lastYear.views,
|
|
total: stats.lastYear.total
|
|
},
|
|
lastDay: {
|
|
stats: utils.statsObjectToArray(stats.lastDay.stats),
|
|
views: stats.lastDay.views,
|
|
total: stats.lastDay.total
|
|
},
|
|
lastMonth: {
|
|
stats: utils.statsObjectToArray(stats.lastMonth.stats),
|
|
views: stats.lastMonth.views,
|
|
total: stats.lastMonth.total
|
|
},
|
|
lastWeek: {
|
|
stats: utils.statsObjectToArray(stats.lastWeek.stats),
|
|
views: stats.lastWeek.views,
|
|
total: stats.lastWeek.total
|
|
},
|
|
updatedAt: new Date().toISOString()
|
|
};
|
|
|
|
if (match.link_id) {
|
|
const cacheTime = utils.getStatsCacheTime(total);
|
|
const key = redis.key.stats(match.link_id);
|
|
redis.client.set(key, JSON.stringify(response), "EX", cacheTime);
|
|
}
|
|
|
|
return response;
|
|
};
|
|
|
|
|
|
module.exports = {
|
|
add,
|
|
find,
|
|
} |