kutt/server/queries/visit.queries.js
2024-09-12 14:26:39 +03:30

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,
}