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
503 changes: 503 additions & 0 deletions backend/REPORTING_README.md

Large diffs are not rendered by default.

4,596 changes: 3,431 additions & 1,165 deletions backend/package-lock.json

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@nestjs/bull": "^11.0.4",
"@nestjs/common": "^10.0.0",
"@nestjs/config": "^4.0.2",
"@nestjs/core": "^10.0.0",
Expand All @@ -31,14 +32,20 @@
"@nestjs/throttler": "^6.5.0",
"@nestjs/typeorm": "^11.0.0",
"bcrypt": "^6.0.0",
"bullmq": "^5.67.0",
"cache-manager": "^7.2.8",
"cache-manager-redis-store": "^3.0.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.2",
"exceljs": "^4.4.0",
"nodemailer": "^7.0.12",
"passport": "^0.7.0",
"passport-github2": "^0.1.12",
"passport-google-oauth20": "^2.0.0",
"passport-jwt": "^4.0.1",
"pdfkit": "^0.17.2",
"pg": "^8.17.2",
"redis": "^5.10.0",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1",
"stellar-sdk": "^13.3.0",
Expand All @@ -58,6 +65,7 @@
"@types/passport-github2": "^1.2.9",
"@types/passport-google-oauth20": "^2.0.17",
"@types/passport-jwt": "^4.0.1",
"@types/pdfkit": "^0.17.4",
"@types/supertest": "^6.0.0",
"@typescript-eslint/eslint-plugin": "^8.0.0",
"@typescript-eslint/parser": "^8.0.0",
Expand Down
2 changes: 2 additions & 0 deletions backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AuthModule } from './auth/auth.module';
import { UsersModule } from './users/users.module';
import { ReportingModule } from './reporting/reporting.module';
import { JwtAuthGuard } from './common/guards/jwt-auth.guard';
import { RolesGuard } from './common/guards/roles.guard';
import { getDatabaseConfig } from './config/database.config';
Expand Down Expand Up @@ -49,6 +50,7 @@ import { getDatabaseConfig } from './config/database.config';
]),
AuthModule,
UsersModule,
ReportingModule,
],
controllers: [AppController],
providers: [
Expand Down
42 changes: 42 additions & 0 deletions backend/src/reporting/dto/generate-report.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { IsEnum, IsOptional, IsString, IsObject, IsDateString, IsUUID } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { ReportType, ReportFormat } from '../entities/report.entity';

export class GenerateReportDto {
@ApiProperty({ description: 'Report title' })
@IsString()
title: string;

@ApiPropertyOptional({ description: 'Report description' })
@IsString()
@IsOptional()
description?: string;

@ApiProperty({ enum: ReportType, description: 'Type of report to generate' })
@IsEnum(ReportType)
type: ReportType;

@ApiProperty({ enum: ReportFormat, description: 'Export format' })
@IsEnum(ReportFormat)
format: ReportFormat;

@ApiPropertyOptional({ description: 'Start date for report data' })
@IsDateString()
@IsOptional()
startDate?: string;

@ApiPropertyOptional({ description: 'End date for report data' })
@IsDateString()
@IsOptional()
endDate?: string;

@ApiPropertyOptional({ description: 'Additional filters as JSON object' })
@IsObject()
@IsOptional()
filters?: Record<string, any>;

@ApiPropertyOptional({ description: 'Template ID to use' })
@IsUUID()
@IsOptional()
templateId?: string;
}
48 changes: 48 additions & 0 deletions backend/src/reporting/dto/report-filter.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { IsEnum, IsOptional, IsString, IsDateString } from 'class-validator';
import { ApiPropertyOptional } from '@nestjs/swagger';
import { ReportType, ReportStatus, ReportFormat } from '../entities/report.entity';

export class ReportFilterDto {
@ApiPropertyOptional({ enum: ReportType, description: 'Filter by report type' })
@IsEnum(ReportType)
@IsOptional()
type?: ReportType;

@ApiPropertyOptional({ enum: ReportStatus, description: 'Filter by report status' })
@IsEnum(ReportStatus)
@IsOptional()
status?: ReportStatus;

@ApiPropertyOptional({ enum: ReportFormat, description: 'Filter by report format' })
@IsEnum(ReportFormat)
@IsOptional()
format?: ReportFormat;

@ApiPropertyOptional({ description: 'Filter by start date (created after)' })
@IsDateString()
@IsOptional()
startDate?: string;

@ApiPropertyOptional({ description: 'Filter by end date (created before)' })
@IsDateString()
@IsOptional()
endDate?: string;

@ApiPropertyOptional({ description: 'Page number', default: 1 })
@IsOptional()
page?: number;

@ApiPropertyOptional({ description: 'Items per page', default: 10 })
@IsOptional()
limit?: number;

@ApiPropertyOptional({ description: 'Sort field', default: 'createdAt' })
@IsString()
@IsOptional()
sortBy?: string;

@ApiPropertyOptional({ description: 'Sort order', default: 'DESC', enum: ['ASC', 'DESC'] })
@IsString()
@IsOptional()
sortOrder?: 'ASC' | 'DESC';
}
73 changes: 73 additions & 0 deletions backend/src/reporting/dto/schedule.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { IsEnum, IsOptional, IsString, IsObject, IsArray, IsUUID } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { ScheduleFrequency } from '../entities/report-schedule.entity';

export class CreateScheduleDto {
@ApiProperty({ description: 'Schedule name' })
@IsString()
name: string;

@ApiPropertyOptional({ description: 'Schedule description' })
@IsString()
@IsOptional()
description?: string;

@ApiProperty({ description: 'Template ID to use for scheduled reports' })
@IsUUID()
templateId: string;

@ApiProperty({ enum: ScheduleFrequency, description: 'Frequency of report generation' })
@IsEnum(ScheduleFrequency)
frequency: ScheduleFrequency;

@ApiPropertyOptional({ description: 'Custom cron expression (if frequency is CUSTOM)' })
@IsString()
@IsOptional()
cronExpression?: string;

@ApiPropertyOptional({ description: 'Filters to apply to scheduled reports' })
@IsObject()
@IsOptional()
filters?: Record<string, any>;

@ApiPropertyOptional({ description: 'Email addresses to send reports to' })
@IsArray()
@IsOptional()
recipients?: string[];
}

export class UpdateScheduleDto {
@ApiPropertyOptional({ description: 'Schedule name' })
@IsString()
@IsOptional()
name?: string;

@ApiPropertyOptional({ description: 'Schedule description' })
@IsString()
@IsOptional()
description?: string;

@ApiPropertyOptional({ enum: ScheduleFrequency, description: 'Frequency of report generation' })
@IsEnum(ScheduleFrequency)
@IsOptional()
frequency?: ScheduleFrequency;

@ApiPropertyOptional({ description: 'Custom cron expression' })
@IsString()
@IsOptional()
cronExpression?: string;

@ApiPropertyOptional({ description: 'Filters to apply' })
@IsObject()
@IsOptional()
filters?: Record<string, any>;

@ApiPropertyOptional({ description: 'Email addresses to send reports to' })
@IsArray()
@IsOptional()
recipients?: string[];

@ApiPropertyOptional({ description: 'Whether schedule is active' })
@IsOptional()
isActive?: boolean;
}
88 changes: 88 additions & 0 deletions backend/src/reporting/dto/template.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { IsEnum, IsOptional, IsString, IsObject, IsArray, IsBoolean } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { ReportType, ReportFormat } from '../entities/report.entity';

export class CreateTemplateDto {
@ApiProperty({ description: 'Template name' })
@IsString()
name: string;

@ApiPropertyOptional({ description: 'Template description' })
@IsString()
@IsOptional()
description?: string;

@ApiProperty({ enum: ReportType, description: 'Type of report this template generates' })
@IsEnum(ReportType)
type: ReportType;

@ApiProperty({
enum: ReportFormat,
isArray: true,
description: 'Supported export formats for this template'
})
@IsArray()
supportedFormats: ReportFormat[];

@ApiProperty({ description: 'Template configuration (columns, charts, filters, styling)' })
@IsObject()
config: {
columns?: string[];
charts?: any[];
filters?: any[];
styling?: any;
layout?: string;
};

@ApiPropertyOptional({ description: 'SQL query template' })
@IsString()
@IsOptional()
queryTemplate?: string;

@ApiPropertyOptional({ description: 'Whether template is active', default: true })
@IsBoolean()
@IsOptional()
isActive?: boolean;
}

export class UpdateTemplateDto {
@ApiPropertyOptional({ description: 'Template name' })
@IsString()
@IsOptional()
name?: string;

@ApiPropertyOptional({ description: 'Template description' })
@IsString()
@IsOptional()
description?: string;

@ApiPropertyOptional({
enum: ReportFormat,
isArray: true,
description: 'Supported export formats'
})
@IsArray()
@IsOptional()
supportedFormats?: ReportFormat[];

@ApiPropertyOptional({ description: 'Template configuration' })
@IsObject()
@IsOptional()
config?: {
columns?: string[];
charts?: any[];
filters?: any[];
styling?: any;
layout?: string;
};

@ApiPropertyOptional({ description: 'SQL query template' })
@IsString()
@IsOptional()
queryTemplate?: string;

@ApiPropertyOptional({ description: 'Whether template is active' })
@IsBoolean()
@IsOptional()
isActive?: boolean;
}
87 changes: 87 additions & 0 deletions backend/src/reporting/entities/report-schedule.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
ManyToOne,
JoinColumn,
Index,
} from 'typeorm';
import { User } from '../../entities/user.entity';
import { ReportTemplate } from './report-template.entity';

export enum ScheduleFrequency {
DAILY = 'daily',
WEEKLY = 'weekly',
MONTHLY = 'monthly',
CUSTOM = 'custom',
}

@Entity('report_schedules')
@Index(['userId'])
@Index(['nextRunAt'])
@Index(['isActive'])
export class ReportSchedule {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column()
name: string;

@Column({ type: 'text', nullable: true })
description: string;

@Column({ type: 'uuid' })
userId: string;

@ManyToOne(() => User, { eager: true })
@JoinColumn({ name: 'userId' })
user: User;

@Column({ type: 'uuid' })
templateId: string;

@ManyToOne(() => ReportTemplate, { eager: true })
@JoinColumn({ name: 'templateId' })
template: ReportTemplate;

@Column({
type: 'enum',
enum: ScheduleFrequency,
})
frequency: ScheduleFrequency;

@Column({ type: 'jsonb', nullable: true })
cronExpression: string;

@Column({ type: 'jsonb', nullable: true })
filters: Record<string, any>;

@Column({ type: 'simple-array', nullable: true })
recipients: string[];

@Column({ default: true })
isActive: boolean;

@Column({ type: 'timestamp', nullable: true })
lastRunAt: Date;

@Column({ type: 'timestamp', nullable: true })
nextRunAt: Date;

@Column({ type: 'int', default: 0 })
runCount: number;

@Column({ type: 'int', default: 0 })
failureCount: number;

@Column({ type: 'text', nullable: true })
lastError: string;

@CreateDateColumn()
createdAt: Date;

@UpdateDateColumn()
updatedAt: Date;
}
Loading