Skip to content
This repository was archived by the owner on Jan 28, 2026. It is now read-only.

Commit c6e4c02

Browse files
committed
feat: Add Statistics module with controller and service for system statistics retrieval
1 parent d7ac9d4 commit c6e4c02

4 files changed

Lines changed: 167 additions & 0 deletions

File tree

src/app.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { QuestUpdateModule } from './quest-update/quest-update.module';
1717
import { OrganizationUpdateModule } from './organization-update/organization-update.module';
1818
import { AppController } from './app.controller';
1919
import { PrometheusModule } from './prometheus/prometheus.module';
20+
import { StatisticsModule } from './statistics/statistics.module';
2021

2122
@Module({
2223
imports: [
@@ -41,6 +42,7 @@ import { PrometheusModule } from './prometheus/prometheus.module';
4142
QuestUpdateModule,
4243
OrganizationUpdateModule,
4344
PrometheusModule,
45+
StatisticsModule,
4446
],
4547
controllers: [AppController],
4648
})
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { Controller, Get, Version } from '@nestjs/common';
2+
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
3+
import { StatisticsService } from './statistics.service';
4+
import { UseGuards } from '@nestjs/common';
5+
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
6+
7+
@ApiTags('Статистика')
8+
@Controller('statistics')
9+
export class StatisticsController {
10+
constructor(private readonly statisticsService: StatisticsService) {}
11+
12+
@Get()
13+
@UseGuards(JwtAuthGuard)
14+
@ApiOperation({ summary: 'Получить статистику системы' })
15+
@ApiResponse({
16+
status: 200,
17+
description: 'Статистика успешно получена',
18+
schema: {
19+
type: 'object',
20+
properties: {
21+
citiesCount: { type: 'number', description: 'Количество городов' },
22+
regionsCount: { type: 'number', description: 'Количество регионов' },
23+
usersCount: { type: 'number', description: 'Количество пользователей' },
24+
organizationsCount: { type: 'number', description: 'Количество организаций' },
25+
totalQuests: { type: 'number', description: 'Всего квестов' },
26+
activeQuests: { type: 'number', description: 'Активные квесты' },
27+
completedQuests: { type: 'number', description: 'Завершённые квесты' },
28+
},
29+
},
30+
})
31+
@ApiResponse({ status: 401, description: 'Не авторизован' })
32+
async getStatistics() {
33+
return this.statisticsService.getStatistics();
34+
}
35+
}
36+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Module } from '@nestjs/common';
2+
import { StatisticsController } from './statistics.controller';
3+
import { StatisticsService } from './statistics.service';
4+
import { DatabaseModule } from '../database/database.module';
5+
6+
@Module({
7+
imports: [DatabaseModule],
8+
controllers: [StatisticsController],
9+
providers: [StatisticsService],
10+
exports: [StatisticsService],
11+
})
12+
export class StatisticsModule {}
13+
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { Injectable, Inject } from '@nestjs/common';
2+
import { DATABASE_CONNECTION } from '../database/database.module';
3+
import { NodePgDatabase } from 'drizzle-orm/node-postgres';
4+
import { cities, regions, users, organizations, quests } from '../database/schema';
5+
import { eq, and, ne, count } from 'drizzle-orm';
6+
7+
export interface StatisticsDto {
8+
citiesCount: number;
9+
regionsCount: number;
10+
usersCount: number;
11+
organizationsCount: number;
12+
totalQuests: number;
13+
activeQuests: number;
14+
completedQuests: number;
15+
}
16+
17+
@Injectable()
18+
export class StatisticsService {
19+
private cache: StatisticsDto | null = null;
20+
private cacheTime: number = 0;
21+
private readonly CACHE_TTL = 60 * 1000; // 1 минута в миллисекундах
22+
23+
constructor(
24+
@Inject(DATABASE_CONNECTION)
25+
private db: NodePgDatabase,
26+
) {}
27+
28+
async getStatistics(): Promise<StatisticsDto> {
29+
const now = Date.now();
30+
31+
// Проверяем кеш
32+
if (this.cache && (now - this.cacheTime) < this.CACHE_TTL) {
33+
return this.cache;
34+
}
35+
36+
// Выполняем все запросы параллельно для оптимизации
37+
const [
38+
citiesResult,
39+
regionsResult,
40+
usersResult,
41+
organizationsResult,
42+
totalQuestsResult,
43+
activeQuestsResult,
44+
completedQuestsResult,
45+
] = await Promise.all([
46+
// Количество городов (исключая удаленные)
47+
this.db
48+
.select({ count: count(cities.id) })
49+
.from(cities)
50+
.where(ne(cities.recordStatus, 'DELETED')),
51+
52+
// Количество регионов (исключая удаленные)
53+
this.db
54+
.select({ count: count(regions.id) })
55+
.from(regions)
56+
.where(ne(regions.recordStatus, 'DELETED')),
57+
58+
// Количество пользователей (исключая удаленные)
59+
this.db
60+
.select({ count: count(users.id) })
61+
.from(users)
62+
.where(ne(users.recordStatus, 'DELETED')),
63+
64+
// Количество организаций (исключая удаленные)
65+
this.db
66+
.select({ count: count(organizations.id) })
67+
.from(organizations)
68+
.where(ne(organizations.recordStatus, 'DELETED')),
69+
70+
// Всего квестов (исключая удаленные)
71+
this.db
72+
.select({ count: count(quests.id) })
73+
.from(quests)
74+
.where(ne(quests.recordStatus, 'DELETED')),
75+
76+
// Активные квесты (status = 'active', исключая удаленные)
77+
this.db
78+
.select({ count: count(quests.id) })
79+
.from(quests)
80+
.where(
81+
and(
82+
eq(quests.status, 'active'),
83+
ne(quests.recordStatus, 'DELETED'),
84+
),
85+
),
86+
87+
// Завершённые квесты (status = 'completed', исключая удаленные)
88+
this.db
89+
.select({ count: count(quests.id) })
90+
.from(quests)
91+
.where(
92+
and(
93+
eq(quests.status, 'completed'),
94+
ne(quests.recordStatus, 'DELETED'),
95+
),
96+
),
97+
]);
98+
99+
const statistics: StatisticsDto = {
100+
citiesCount: citiesResult[0]?.count ?? 0,
101+
regionsCount: regionsResult[0]?.count ?? 0,
102+
usersCount: usersResult[0]?.count ?? 0,
103+
organizationsCount: organizationsResult[0]?.count ?? 0,
104+
totalQuests: totalQuestsResult[0]?.count ?? 0,
105+
activeQuests: activeQuestsResult[0]?.count ?? 0,
106+
completedQuests: completedQuestsResult[0]?.count ?? 0,
107+
};
108+
109+
// Обновляем кеш
110+
this.cache = statistics;
111+
this.cacheTime = now;
112+
113+
return statistics;
114+
}
115+
}
116+

0 commit comments

Comments
 (0)