visit.queries.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. const { isAfter, subDays, subHours, set } = require("date-fns");
  2. const utils = require("../utils");
  3. const redis = require("../redis");
  4. const knex = require("../knex");
  5. async function add(params) {
  6. const data = {
  7. ...params,
  8. country: params.country.toLowerCase(),
  9. referrer: params.referrer.toLowerCase()
  10. };
  11. const visit = await knex("visits")
  12. .where({ link_id: params.id })
  13. .andWhere(
  14. knex.raw("date_trunc('hour', created_at) = date_trunc('hour', ?)", [
  15. knex.fn.now()
  16. ])
  17. )
  18. .first();
  19. if (visit) {
  20. await knex("visits")
  21. .where({ id: visit.id })
  22. .increment(`br_${data.browser}`, 1)
  23. .increment(`os_${data.os}`, 1)
  24. .increment("total", 1)
  25. .update({
  26. updated_at: new Date().toISOString(),
  27. countries: knex.raw(
  28. "jsonb_set(countries, '{??}', (COALESCE(countries->>?,'0')::int + 1)::text::jsonb)",
  29. [data.country, data.country]
  30. ),
  31. referrers: knex.raw(
  32. "jsonb_set(referrers, '{??}', (COALESCE(referrers->>?,'0')::int + 1)::text::jsonb)",
  33. [data.referrer, data.referrer]
  34. )
  35. });
  36. } else {
  37. await knex("visits").insert({
  38. [`br_${data.browser}`]: 1,
  39. countries: { [data.country]: 1 },
  40. referrers: { [data.referrer]: 1 },
  41. [`os_${data.os}`]: 1,
  42. total: 1,
  43. link_id: data.id
  44. });
  45. }
  46. return visit;
  47. };
  48. async function find(match, total) {
  49. // if (match.link_id) {
  50. // const key = redis.key.stats(match.link_id);
  51. // const cached = await redis.client.get(key);
  52. // if (cached) return JSON.parse(cached);
  53. // }
  54. const stats = {
  55. lastDay: {
  56. stats: utils.getInitStats(),
  57. views: new Array(24).fill(0),
  58. total: 0
  59. },
  60. lastWeek: {
  61. stats: utils.getInitStats(),
  62. views: new Array(7).fill(0),
  63. total: 0
  64. },
  65. lastMonth: {
  66. stats: utils.getInitStats(),
  67. views: new Array(30).fill(0),
  68. total: 0
  69. },
  70. lastYear: {
  71. stats: utils.getInitStats(),
  72. views: new Array(12).fill(0),
  73. total: 0
  74. }
  75. };
  76. const visitsStream = knex("visits").where(match).stream();
  77. const nowUTC = utils.getUTCDate();
  78. const now = new Date();
  79. const periods = utils.getStatsPeriods(now);
  80. for await (const visit of visitsStream) {
  81. periods.forEach(([type, fromDate]) => {
  82. const isIncluded = isAfter(new Date(visit.created_at), fromDate);
  83. if (!isIncluded) return;
  84. const diffFunction = utils.getDifferenceFunction(type);
  85. const diff = diffFunction(now, new Date(visit.created_at));
  86. const index = stats[type].views.length - diff - 1;
  87. const view = stats[type].views[index];
  88. const period = stats[type].stats;
  89. stats[type].stats = {
  90. browser: {
  91. chrome: period.browser.chrome + visit.br_chrome,
  92. edge: period.browser.edge + visit.br_edge,
  93. firefox: period.browser.firefox + visit.br_firefox,
  94. ie: period.browser.ie + visit.br_ie,
  95. opera: period.browser.opera + visit.br_opera,
  96. other: period.browser.other + visit.br_other,
  97. safari: period.browser.safari + visit.br_safari
  98. },
  99. os: {
  100. android: period.os.android + visit.os_android,
  101. ios: period.os.ios + visit.os_ios,
  102. linux: period.os.linux + visit.os_linux,
  103. macos: period.os.macos + visit.os_macos,
  104. other: period.os.other + visit.os_other,
  105. windows: period.os.windows + visit.os_windows
  106. },
  107. country: {
  108. ...period.country,
  109. ...Object.entries(visit.countries).reduce(
  110. (obj, [country, count]) => ({
  111. ...obj,
  112. [country]: (period.country[country] || 0) + count
  113. }),
  114. {}
  115. )
  116. },
  117. referrer: {
  118. ...period.referrer,
  119. ...Object.entries(visit.referrers).reduce(
  120. (obj, [referrer, count]) => ({
  121. ...obj,
  122. [referrer]: (period.referrer[referrer] || 0) + count
  123. }),
  124. {}
  125. )
  126. }
  127. };
  128. stats[type].views[index] += visit.total;
  129. stats[type].total += visit.total;
  130. });
  131. }
  132. const response = {
  133. lastYear: {
  134. stats: utils.statsObjectToArray(stats.lastYear.stats),
  135. views: stats.lastYear.views,
  136. total: stats.lastYear.total
  137. },
  138. lastDay: {
  139. stats: utils.statsObjectToArray(stats.lastDay.stats),
  140. views: stats.lastDay.views,
  141. total: stats.lastDay.total
  142. },
  143. lastMonth: {
  144. stats: utils.statsObjectToArray(stats.lastMonth.stats),
  145. views: stats.lastMonth.views,
  146. total: stats.lastMonth.total
  147. },
  148. lastWeek: {
  149. stats: utils.statsObjectToArray(stats.lastWeek.stats),
  150. views: stats.lastWeek.views,
  151. total: stats.lastWeek.total
  152. },
  153. updatedAt: new Date().toISOString()
  154. };
  155. if (match.link_id) {
  156. const cacheTime = utils.getStatsCacheTime(total);
  157. const key = redis.key.stats(match.link_id);
  158. redis.client.set(key, JSON.stringify(response), "EX", cacheTime);
  159. }
  160. return response;
  161. };
  162. module.exports = {
  163. add,
  164. find,
  165. }