managarten/services/matrix-stats-bot/src/users/users.service.ts
Claude 7c5e9e3c49
feat(matrix): add Stats Bot and Project Doc Bot services
Complete GDPR-compliant bot suite for Matrix:

matrix-stats-bot (port 3312):
- Analytics reports from Umami
- Commands: !stats, !today, !week, !realtime, !users
- Scheduled daily/weekly reports to Matrix room

matrix-project-doc-bot (port 3313):
- Project documentation with photos, voice, text
- Voice transcription via OpenAI Whisper
- Blog generation with 5 styles (casual, technical, tutorial, social, story)
- Commands: !new, !projects, !switch, !status, !generate, !export
- Uses PostgreSQL + S3 (MinIO) for storage

Changes:
- docker-compose.macmini.yml: Added both Matrix bots
- health-check.sh: Added health checks for both bots

Environment variables required:
- MATRIX_STATS_BOT_TOKEN, MATRIX_PROJECT_DOC_BOT_TOKEN
- OPENAI_API_KEY (for Project Doc Bot)

https://claude.ai/code/session_01E3r5aFW3YLAhEJfsL2ryhv
2026-01-28 00:44:28 +00:00

55 lines
1.7 KiB
TypeScript

import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import postgres from 'postgres';
interface UserStats {
total: number;
verified: number;
lastWeek: number;
lastMonth: number;
}
@Injectable()
export class UsersService implements OnModuleInit {
private readonly logger = new Logger(UsersService.name);
private sql: postgres.Sql | null = null;
constructor(private configService: ConfigService) {}
async onModuleInit() {
const databaseUrl = this.configService.get<string>('database.url');
if (databaseUrl) {
try {
this.sql = postgres(databaseUrl);
this.logger.log('Database connected for user stats');
} catch (error) {
this.logger.warn('Failed to connect to database:', error);
}
} else {
this.logger.warn('DATABASE_URL not configured - user stats disabled');
}
}
async getUserStats(): Promise<UserStats | null> {
if (!this.sql) {
return null;
}
try {
const [totalResult] = await this.sql`SELECT COUNT(*) as count FROM "user"`;
const [verifiedResult] = await this.sql`SELECT COUNT(*) as count FROM "user" WHERE "emailVerified" = true`;
const [weekResult] = await this.sql`SELECT COUNT(*) as count FROM "user" WHERE "createdAt" > NOW() - INTERVAL '7 days'`;
const [monthResult] = await this.sql`SELECT COUNT(*) as count FROM "user" WHERE "createdAt" > NOW() - INTERVAL '30 days'`;
return {
total: parseInt(totalResult.count, 10),
verified: parseInt(verifiedResult.count, 10),
lastWeek: parseInt(weekResult.count, 10),
lastMonth: parseInt(monthResult.count, 10),
};
} catch (error) {
this.logger.error('Failed to get user stats:', error);
return null;
}
}
}