stats-reporting.service.ts 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. import { Injectable } from '@nestjs/common';
  2. import { Prisma } from '@prisma/mongo-stats/client';
  3. import type {
  4. AdsDailyStats,
  5. AdsHourlyStats,
  6. ChannelDailyUserStats,
  7. ChannelHourlyUserStats,
  8. } from '@prisma/mongo-stats/client';
  9. import { PrismaMongoService } from '../../prisma/prisma-mongo.service';
  10. import {
  11. ChannelStatsQueryDto,
  12. AdsStatsQueryDto,
  13. } from './dto/stats-reporting.dto';
  14. import {
  15. assertRange,
  16. alignDayStart,
  17. alignHourStart,
  18. } from './stats-reporting.time';
  19. interface PaginatedResult<T> {
  20. page: number;
  21. size: number;
  22. total: number;
  23. items: T[];
  24. }
  25. interface PaginationParams {
  26. page?: number;
  27. size?: number;
  28. }
  29. interface NormalizedPagination {
  30. page: number;
  31. size: number;
  32. skip: number;
  33. take: number;
  34. }
  35. const DEFAULT_PAGE = 1;
  36. const DEFAULT_SIZE = 10;
  37. const MAX_SIZE = 200;
  38. @Injectable()
  39. export class StatsReportingService {
  40. constructor(private readonly prisma: PrismaMongoService) {}
  41. async getAdsHourly(query: AdsStatsQueryDto): Promise<any> {
  42. assertRange(query.fromSec, query.toSec);
  43. const { page, size, skip, take } = this.normalizePagination({
  44. page: query.page,
  45. size: query.size,
  46. });
  47. const fromAligned = alignHourStart(query.fromSec);
  48. const toAligned = alignHourStart(query.toSec);
  49. const where: Prisma.AdsHourlyStatsWhereInput = {
  50. hourStartAt: {
  51. gte: BigInt(fromAligned),
  52. lt: BigInt(toAligned),
  53. },
  54. };
  55. if (query.adsId) {
  56. where.adsId = query.adsId;
  57. }
  58. const [items, total] = await Promise.all([
  59. this.prisma.adsHourlyStats.findMany({
  60. where,
  61. orderBy: [{ hourStartAt: 'desc' }, { id: 'desc' }],
  62. skip,
  63. take,
  64. }),
  65. this.prisma.adsHourlyStats.count({ where }),
  66. ]);
  67. return { page, size, total, items };
  68. }
  69. async getAdsDaily(query: AdsStatsQueryDto): Promise<any> {
  70. assertRange(query.fromSec, query.toSec);
  71. const { page, size, skip, take } = this.normalizePagination({
  72. page: query.page,
  73. size: query.size,
  74. });
  75. const fromAligned = alignDayStart(query.fromSec);
  76. const toAligned = alignDayStart(query.toSec);
  77. const where: Prisma.AdsDailyStatsWhereInput = {
  78. dayStartAt: {
  79. gte: BigInt(fromAligned),
  80. lt: BigInt(toAligned),
  81. },
  82. };
  83. if (query.adsId) {
  84. where.adsId = query.adsId;
  85. }
  86. const [items, total] = await Promise.all([
  87. this.prisma.adsDailyStats.findMany({
  88. where,
  89. orderBy: [{ dayStartAt: 'desc' }, { id: 'desc' }],
  90. skip,
  91. take,
  92. }),
  93. this.prisma.adsDailyStats.count({ where }),
  94. ]);
  95. return { page, size, total, items };
  96. }
  97. async getChannelHourlyUsers(query: ChannelStatsQueryDto): Promise<any> {
  98. assertRange(query.fromSec, query.toSec);
  99. const { page, size, skip, take } = this.normalizePagination({
  100. page: query.page,
  101. size: query.size,
  102. });
  103. const fromAligned = alignHourStart(query.fromSec);
  104. const toAligned = alignHourStart(query.toSec);
  105. const where: Prisma.ChannelHourlyUserStatsWhereInput = {
  106. hourStartAt: {
  107. gte: BigInt(fromAligned),
  108. lt: BigInt(toAligned),
  109. },
  110. };
  111. if (query.channelId) {
  112. where.channelId = query.channelId;
  113. }
  114. const [items, total] = await Promise.all([
  115. this.prisma.channelHourlyUserStats.findMany({
  116. where,
  117. orderBy: [{ hourStartAt: 'desc' }, { id: 'desc' }],
  118. skip,
  119. take,
  120. }),
  121. this.prisma.channelHourlyUserStats.count({ where }),
  122. ]);
  123. return { page, size, total, items };
  124. }
  125. async getChannelDailyUsers(query: ChannelStatsQueryDto): Promise<any> {
  126. assertRange(query.fromSec, query.toSec);
  127. const { page, size, skip, take } = this.normalizePagination({
  128. page: query.page,
  129. size: query.size,
  130. });
  131. const fromAligned = alignDayStart(query.fromSec);
  132. const toAligned = alignDayStart(query.toSec);
  133. const where: Prisma.ChannelDailyUserStatsWhereInput = {
  134. dayStartAt: {
  135. gte: BigInt(fromAligned),
  136. lt: BigInt(toAligned),
  137. },
  138. };
  139. if (query.channelId) {
  140. where.channelId = query.channelId;
  141. }
  142. const [items, total] = await Promise.all([
  143. this.prisma.channelDailyUserStats.findMany({
  144. where,
  145. orderBy: [{ dayStartAt: 'desc' }, { id: 'desc' }],
  146. skip,
  147. take,
  148. }),
  149. this.prisma.channelDailyUserStats.count({ where }),
  150. ]);
  151. return { page, size, total, items };
  152. }
  153. private normalizePagination({
  154. page,
  155. size,
  156. }: PaginationParams = {}): NormalizedPagination {
  157. const normalizedPage = this.normalizePage(page);
  158. const normalizedSize = this.normalizeSize(size);
  159. const skip = Math.max(0, (normalizedPage - 1) * normalizedSize);
  160. return {
  161. page: normalizedPage,
  162. size: normalizedSize,
  163. skip,
  164. take: normalizedSize,
  165. };
  166. }
  167. private normalizePage(value?: number) {
  168. if (!Number.isFinite(value ?? NaN)) {
  169. return DEFAULT_PAGE;
  170. }
  171. return Math.max(DEFAULT_PAGE, Math.floor(value));
  172. }
  173. private normalizeSize(value?: number) {
  174. if (!Number.isFinite(value ?? NaN)) {
  175. return DEFAULT_SIZE;
  176. }
  177. const normalized = Math.floor(value);
  178. if (normalized < 1) {
  179. return DEFAULT_SIZE;
  180. }
  181. return Math.min(MAX_SIZE, normalized);
  182. }
  183. }