feat(stats): add user statistics to Prometheus metrics and Grafana

- Add user metrics to mana-core-auth MetricsService:
  - auth_users_total: Total registered users
  - auth_users_verified: Email-verified users
  - auth_users_created_today/this_week/this_month
- Create Grafana user-statistics dashboard with:
  - User overview stats (total, verified, verification rate, new today)
  - Registration period breakdown (today/week/month)
  - User growth trends over time
- Enhance telegram-stats-bot /users command:
  - Add yesterday comparison with trends
  - Add week-over-week comparison
  - Add mini bar chart for last 7 days registration
- Include user stats in daily Telegram report
This commit is contained in:
Till-JS 2026-01-26 10:53:57 +01:00
parent 9fedb7cfdd
commit 0cd2bc858a
6 changed files with 798 additions and 13 deletions

View file

@ -8,6 +8,14 @@ export interface UserStats {
todayNewUsers: number;
weekNewUsers: number;
monthNewUsers: number;
yesterdayNewUsers: number;
lastWeekNewUsers: number;
dailyRegistrations: DailyRegistration[];
}
export interface DailyRegistration {
date: string;
count: number;
}
@Injectable()
@ -43,31 +51,60 @@ export class UsersService implements OnModuleInit {
const startOfToday = new Date(now);
startOfToday.setHours(0, 0, 0, 0);
const startOfYesterday = new Date(startOfToday);
startOfYesterday.setDate(startOfYesterday.getDate() - 1);
const startOfWeek = new Date(now);
const day = startOfWeek.getDay();
const diff = startOfWeek.getDate() - day + (day === 0 ? -6 : 1);
startOfWeek.setDate(diff);
startOfWeek.setHours(0, 0, 0, 0);
const startOfLastWeek = new Date(startOfWeek);
startOfLastWeek.setDate(startOfLastWeek.getDate() - 7);
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
// Main stats query
const [result] = await this.sql`
SELECT
COUNT(*) as total_users,
COUNT(*) FILTER (WHERE email_verified = true) as verified_users,
COUNT(*) FILTER (WHERE created_at >= ${startOfToday.toISOString()}) as today_new_users,
COUNT(*) FILTER (WHERE created_at >= ${startOfYesterday.toISOString()} AND created_at < ${startOfToday.toISOString()}) as yesterday_new_users,
COUNT(*) FILTER (WHERE created_at >= ${startOfWeek.toISOString()}) as week_new_users,
COUNT(*) FILTER (WHERE created_at >= ${startOfLastWeek.toISOString()} AND created_at < ${startOfWeek.toISOString()}) as last_week_new_users,
COUNT(*) FILTER (WHERE created_at >= ${startOfMonth.toISOString()}) as month_new_users
FROM auth.users
WHERE deleted_at IS NULL
`;
// Get daily registrations for last 7 days
const dailyStats = await this.sql`
SELECT
DATE(created_at) as date,
COUNT(*) as count
FROM auth.users
WHERE deleted_at IS NULL
AND created_at >= ${new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString()}
GROUP BY DATE(created_at)
ORDER BY date DESC
`;
const dailyRegistrations: DailyRegistration[] = dailyStats.map((row) => ({
date: new Date(row.date).toISOString().split('T')[0],
count: Number(row.count),
}));
return {
totalUsers: Number(result.total_users),
verifiedUsers: Number(result.verified_users),
todayNewUsers: Number(result.today_new_users),
yesterdayNewUsers: Number(result.yesterday_new_users),
weekNewUsers: Number(result.week_new_users),
lastWeekNewUsers: Number(result.last_week_new_users),
monthNewUsers: Number(result.month_new_users),
dailyRegistrations,
};
} catch (error) {
this.logger.error('Failed to fetch user stats:', error);