import { eq, gte, lte, and, sql } from 'drizzle-orm';
import type { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
import { TimeSeriesMetricsService } from './TimeSeriesMetricsService';
import { serverInstallMetrics } from '../../db/schema.ts';
import type {
QueryParams,
BucketData,
StandardResponse
} from './TimeSeriesMetricsService';
interface ServerInstallBucket extends BucketData {
timestamp: number;
installation_count: number;
uninstallation_count: number;
}
export class ServerInstallMetricsService extends TimeSeriesMetricsService {
private db: PostgresJsDatabase;
constructor(db: PostgresJsDatabase) {
super();
this.db = db;
}
// Return metric type identifier
getMetricType(): string {
return 'server_installations';
}
// Query database for buckets
async queryBuckets(params: QueryParams): Promise<BucketData[]> {
const { startTime, endTime, interval, filters } = params;
const startTimestamp = Math.floor(startTime.getTime() / 1000);
const endTimestamp = Math.floor(endTime.getTime() / 1000);
// Build query with filters
const whereConditions = [
eq(serverInstallMetrics.user_id, filters.user_id),
eq(serverInstallMetrics.team_id, filters.team_id),
eq(serverInstallMetrics.bucket_interval, interval),
gte(serverInstallMetrics.bucket_timestamp, startTimestamp),
lte(serverInstallMetrics.bucket_timestamp, endTimestamp)
];
// Optional filters
if (filters.server_id) {
whereConditions.push(eq(serverInstallMetrics.server_id, filters.server_id));
}
if (filters.satellite_id) {
whereConditions.push(eq(serverInstallMetrics.satellite_id, filters.satellite_id));
}
// Query with aggregation (handle multiple satellites/servers per bucket)
const results = await this.db
.select({
bucket_timestamp: serverInstallMetrics.bucket_timestamp,
installation_count: sql<number>`SUM(${serverInstallMetrics.installation_count})`,
uninstallation_count: sql<number>`SUM(${serverInstallMetrics.uninstallation_count})`
})
.from(serverInstallMetrics)
.where(and(...whereConditions))
.groupBy(serverInstallMetrics.bucket_timestamp)
.orderBy(serverInstallMetrics.bucket_timestamp);
return results.map(row => ({
timestamp: row.bucket_timestamp,
installation_count: row.installation_count,
uninstallation_count: row.uninstallation_count
}));
}
// Public method for API endpoint
async getServerInstallMetrics(
userId: string,
teamId: string,
timeRange: string,
interval: string,
serverId?: string,
satelliteId?: string
): Promise<StandardResponse> {
// Parse time range
const { start, end } = this.parseTimeRange(timeRange);
// Validate interval
this.validateInterval(interval);
// Generate bucket timestamps
const timestamps = this.generateBucketTimestamps(start, end, interval);
// Build filters
const filters: Record<string, string> = {
user_id: userId,
team_id: teamId
};
if (serverId) filters.server_id = serverId;
if (satelliteId) filters.satellite_id = satelliteId;
// Query actual data
const buckets = await this.queryBuckets({
startTime: start,
endTime: end,
interval,
filters
});
// Fill missing buckets with zeros
const filledBuckets = this.fillMissingBuckets(buckets, timestamps);
// Calculate summary statistics
const summary = this.calculateSummary(filledBuckets);
// Format standardized response
return this.formatResponse({
metricType: this.getMetricType(),
timeRange: { start, end, interval },
filters,
buckets: filledBuckets,
summary
});
}
}