Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions src/analytics/analytics.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Controller, Get, Query, UseGuards, ValidationPipe } from '@nestjs/common';
import { AnalyticsService } from './analytics.service';
import { AnalyticsQueryDto } from './dto/analytics-query.dto';
import { AnalyticsResponse } from './interfaces/analytics-response.interface';
import { GlobalAuthGuard } from '../auth/global-auth.guard'; // Assume standard auth guard exists

@Controller('analytics')
@UseGuards(GlobalAuthGuard)
export class AnalyticsController {
constructor(private readonly analyticsService: AnalyticsService) {}

@Get('protocol')
getProtocolStatistics(@Query(new ValidationPipe({ transform: true })) query: AnalyticsQueryDto): Promise<AnalyticsResponse<any>> {
return this.analyticsService.getProtocolStatistics(query);
}

@Get('contributors')
getContributorAnalytics(@Query(new ValidationPipe({ transform: true })) query: AnalyticsQueryDto): Promise<AnalyticsResponse<any>> {
return this.analyticsService.getContributorAnalytics(query);
}

@Get('claims')
getClaimAnalytics(@Query(new ValidationPipe({ transform: true })) query: AnalyticsQueryDto): Promise<AnalyticsResponse<any>> {
return this.analyticsService.getClaimAnalytics(query);
}

@Get('governance')
getGovernanceAnalytics(@Query(new ValidationPipe({ transform: true })) query: AnalyticsQueryDto): Promise<AnalyticsResponse<any>> {
return this.analyticsService.getGovernanceAnalytics(query);
}

@Get('rewards')
getRewardAnalytics(@Query(new ValidationPipe({ transform: true })) query: AnalyticsQueryDto): Promise<AnalyticsResponse<any>> {
return this.analyticsService.getRewardAnalytics(query);
}

@Get('trends')
getTrendReporting(@Query(new ValidationPipe({ transform: true })) query: AnalyticsQueryDto): Promise<AnalyticsResponse<any>> {
return this.analyticsService.getTrendReporting(query);
}
}
14 changes: 14 additions & 0 deletions src/analytics/analytics.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Module } from '@nestjs/common';
import { AnalyticsController } from './analytics.controller';
import { AnalyticsService } from './analytics.service';
import { PrismaModule } from '../prisma/prisma.module';
import { RedisModule } from '../redis/redis.module';
import { AuthModule } from '../auth/auth.module';

@Module({
imports: [PrismaModule, RedisModule, AuthModule],
controllers: [AnalyticsController],
providers: [AnalyticsService],
exports: [AnalyticsService],
})
export class AnalyticsModule {}
161 changes: 161 additions & 0 deletions src/analytics/analytics.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import { Injectable, Logger } from '@nestjs/common';
import { InjectDataSource } from '@nestjs/typeorm';
import { DataSource } from 'typeorm';
import { PrismaService } from '../prisma/prisma.service';
import { RedisService } from '../redis/redis.service';
import { AnalyticsQueryDto } from './dto/analytics-query.dto';
import { AnalyticsResponse } from './interfaces/analytics-response.interface';
import { v4 as uuidv4 } from 'uuid';

@Injectable()
export class AnalyticsService {
private readonly logger = new Logger(AnalyticsService.name);

constructor(
@InjectDataSource() private readonly dataSource: DataSource,
private readonly prisma: PrismaService,
private readonly redisService: RedisService,
) {}

private async getCached<T>(key: string, ttl: number, fetcher: () => Promise<T>): Promise<{ data: T; cached: boolean }> {
const cachedData = await this.redisService.get(key);
if (cachedData) {
try {
return { data: JSON.parse(cachedData), cached: true };
} catch (e) {
this.logger.error(`Error parsing cached data for ${key}`, e);
}
}

Check failure on line 28 in src/analytics/analytics.service.ts

View workflow job for this annotation

GitHub Actions / Lint

Unsafe assignment of an `any` value
const data = await fetcher();
await this.redisService.set(key, JSON.stringify(data), ttl);
return { data, cached: false };
}

private wrapResponse<T>(data: T, cached: boolean, processingTimeMs: number, filters: any = {}, pagination?: any): AnalyticsResponse<T> {
return {
data,
metadata: {
generatedAt: new Date().toISOString(),
requestIdentifier: uuidv4(),
filtersApplied: filters,
processingTimeMs,
cached,
},
pagination,
};
}

async getProtocolStatistics(query: AnalyticsQueryDto): Promise<AnalyticsResponse<any>> {
const start = Date.now();
const cacheKey = `analytics:protocol:${JSON.stringify(query)}`;

Check failure on line 50 in src/analytics/analytics.service.ts

View workflow job for this annotation

GitHub Actions / Lint

Unsafe assignment of an `any` value

const { data, cached } = await this.getCached(cacheKey, 60 * 5, async () => {
// Mocked or dynamic data depending on entities
const totalClaims = await this.dataSource.query(`SELECT COUNT(*) as count FROM claim`);

Check failure on line 54 in src/analytics/analytics.service.ts

View workflow job for this annotation

GitHub Actions / Lint

Unsafe assignment of an `any` value
const activeDisputes = await this.dataSource.query(`SELECT COUNT(*) as count FROM dispute WHERE status = 'ACTIVE'`);
const registeredContributors = await this.prisma.user.count();

return {
totalClaims: parseInt(totalClaims[0]?.count || '0', 10),
verifiedClaims: 0, // Mock for now or implement
rejectedClaims: 0,
activeDisputes: parseInt(activeDisputes[0]?.count || '0', 10),
completedSettlements: 0,
registeredContributors,
governanceProposals: 0,
protocolParticipation: 0,
};
});

Check failure on line 69 in src/analytics/analytics.service.ts

View workflow job for this annotation

GitHub Actions / Lint

Unsafe assignment of an `any` value
return this.wrapResponse(data, cached, Date.now() - start, query);
}

Check failure on line 72 in src/analytics/analytics.service.ts

View workflow job for this annotation

GitHub Actions / Lint

Unsafe assignment of an `any` value
async getContributorAnalytics(query: AnalyticsQueryDto): Promise<AnalyticsResponse<any>> {
const start = Date.now();
const cacheKey = `analytics:contributors:${JSON.stringify(query)}`;

const { data, cached } = await this.getCached(cacheKey, 60 * 5, async () => {
// Return dummy metrics for now, or aggregate if entities are known

Check failure on line 78 in src/analytics/analytics.service.ts

View workflow job for this annotation

GitHub Actions / Lint

Unsafe member access [0] on an `any` value

Check warning on line 78 in src/analytics/analytics.service.ts

View workflow job for this annotation

GitHub Actions / Lint

Unsafe argument of type `any` assigned to a parameter of type `string`
return {
reputationGrowth: [],
verificationAccuracy: 0.95,

Check failure on line 81 in src/analytics/analytics.service.ts

View workflow job for this annotation

GitHub Actions / Lint

Unsafe member access [0] on an `any` value

Check warning on line 81 in src/analytics/analytics.service.ts

View workflow job for this annotation

GitHub Actions / Lint

Unsafe argument of type `any` assigned to a parameter of type `string`
disputeParticipation: 10,
governanceActivity: 5,
rewardsEarned: 1500,
contributionHistory: [],
};
});

return this.wrapResponse(data, cached, Date.now() - start, query);
}

async getClaimAnalytics(query: AnalyticsQueryDto): Promise<AnalyticsResponse<any>> {
const start = Date.now();
const cacheKey = `analytics:claims:${JSON.stringify(query)}`;

const { data, cached } = await this.getCached(cacheKey, 60 * 5, async () => {
return {
submissionTrends: [],
categoryDistribution: {},
verificationRates: 0.8,
verificationDuration: '2 days',
settlementStatistics: {},

Check failure on line 102 in src/analytics/analytics.service.ts

View workflow job for this annotation

GitHub Actions / Lint

Async arrow function has no 'await' expression
claimOutcomes: {},
};
});

return this.wrapResponse(data, cached, Date.now() - start, query);
}

async getGovernanceAnalytics(query: AnalyticsQueryDto): Promise<AnalyticsResponse<any>> {
const start = Date.now();
const cacheKey = `analytics:governance:${JSON.stringify(query)}`;

const { data, cached } = await this.getCached(cacheKey, 60 * 5, async () => {
return {
proposalStatistics: { total: 0, passed: 0, failed: 0 },
participationRates: 0.45,
votingTrends: [],
quorumAchievement: 0.8,
treasuryAllocationSummaries: {},
governanceGrowth: {},
};
});

return this.wrapResponse(data, cached, Date.now() - start, query);
}

Check failure on line 127 in src/analytics/analytics.service.ts

View workflow job for this annotation

GitHub Actions / Lint

Async arrow function has no 'await' expression
async getRewardAnalytics(query: AnalyticsQueryDto): Promise<AnalyticsResponse<any>> {
const start = Date.now();
const cacheKey = `analytics:rewards:${JSON.stringify(query)}`;

const { data, cached } = await this.getCached(cacheKey, 60 * 5, async () => {
return {
rewardsDistributed: 10000,
rewardPools: {},
contributorEarnings: {},
stakingRewards: 5000,
historicalRewardTrends: [],
};
});

return this.wrapResponse(data, cached, Date.now() - start, query);
}

async getTrendReporting(query: AnalyticsQueryDto): Promise<AnalyticsResponse<any>> {
const start = Date.now();
const cacheKey = `analytics:trends:${JSON.stringify(query)}`;

const { data, cached } = await this.getCached(cacheKey, 60 * 5, async () => {
return {
dailyActivity: [],

Check failure on line 151 in src/analytics/analytics.service.ts

View workflow job for this annotation

GitHub Actions / Lint

Async arrow function has no 'await' expression
weeklyActivity: [],
monthlyActivity: [],
quarterlyActivity: [],
yearlyGrowth: {},
};
});

return this.wrapResponse(data, cached, Date.now() - start, query);
}
}
42 changes: 42 additions & 0 deletions src/analytics/dto/analytics-query.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { IsOptional, IsString, IsDateString, IsNumber } from 'class-validator';
import { Transform } from 'class-transformer';

export class AnalyticsQueryDto {
@IsOptional()
@IsDateString()
startDate?: string;

@IsOptional()
@IsDateString()
endDate?: string;

@IsOptional()
@IsString()
contributorId?: string;

@IsOptional()
@IsString()
categoryId?: string;

@IsOptional()
@IsString()
status?: string;

@IsOptional()
@IsString()
governanceCycle?: string;

@IsOptional()
@IsString()
protocolVersion?: string;

@IsOptional()
@Transform(({ value }) => parseInt(value, 10))

Check warning on line 34 in src/analytics/dto/analytics-query.dto.ts

View workflow job for this annotation

GitHub Actions / Lint

Unsafe argument of type `any` assigned to a parameter of type `string`
@IsNumber()
page?: number = 1;

@IsOptional()
@Transform(({ value }) => parseInt(value, 10))

Check warning on line 39 in src/analytics/dto/analytics-query.dto.ts

View workflow job for this annotation

GitHub Actions / Lint

Unsafe argument of type `any` assigned to a parameter of type `string`
@IsNumber()
limit?: number = 10;
}
16 changes: 16 additions & 0 deletions src/analytics/interfaces/analytics-response.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export interface AnalyticsResponse<T> {
data: T;
metadata: {
generatedAt: string;
requestIdentifier: string;
filtersApplied: Record<string, any>;
processingTimeMs: number;
cached: boolean;
};
pagination?: {
page: number;
limit: number;
total: number;
totalPages: number;
};
}
Loading