Skip to content
Open
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
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,13 @@
"@opentelemetry/sdk-trace-node": "^2.8.0",
"@opentelemetry/semantic-conventions": "^1.41.1",
"@prisma/client": "^7.8.0",
"@types/node-cron": "^3.0.11",
"@types/nodemailer": "^8.0.1",
"@types/react": "^19.2.17",
"axios": "^1.17.0",
"ethers": "^6.17.0",
"node-cron": "^4.5.0",
"nodemailer": "^9.0.1",
"pg": "^8.21.0",
"react": "^19.2.7",
"typeorm": "^1.0.0",
Expand Down
5 changes: 5 additions & 0 deletions src/modules/reports/dto/report.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface ReportDto {
title: string;
generatedAt: Date;
sections: { heading: string; body: string }[];
}
24 changes: 24 additions & 0 deletions src/modules/reports/email.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Injectable } from '@nestjs/common';
import * as nodemailer from 'nodemailer';

@Injectable()
export class EmailService {
private transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: Number(process.env.SMTP_PORT),
secure: false,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
});

async sendReport(content: string, subject: string) {
await this.transporter.sendMail({
from: `Security Reports <${process.env.SMTP_USER}>`,
to: process.env.REPORT_RECIPIENT,
subject,
text: content,
});
}
}
48 changes: 48 additions & 0 deletions src/modules/reports/report.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ReportService } from './report.service';
import { EmailService } from './email.service';

describe('ReportService', () => {
let service: ReportService;
let emailService: EmailService;

beforeEach(async () => {
const mockEmailService = {
sendReport: jest.fn().mockResolvedValue(undefined),
};

const module: TestingModule = await Test.createTestingModule({
providers: [
ReportService,
{
provide: EmailService,
useValue: mockEmailService,
},
],
}).compile();

service = module.get<ReportService>(ReportService);
emailService = module.get<EmailService>(EmailService);
});

it('should be defined', () => {
expect(service).toBeDefined();
});

it('should generate daily report content', async () => {
const report = await service.generateDailyReport();
expect(report).toContain('Daily security summary placeholder');
});

it('should generate weekly report content', async () => {
const report = await service.generateWeeklyReport();
expect(report).toContain('Weekly security summary placeholder');
});

it('should deliver report via EmailService', async () => {
const content = 'Test Content';
const subject = 'Test Subject';
await service.deliverReport(content, subject);
expect(emailService.sendReport).toHaveBeenCalledWith(content, subject);
});
});
21 changes: 21 additions & 0 deletions src/modules/reports/report.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Injectable } from '@nestjs/common';
import { EmailService } from './email.service';

@Injectable()
export class ReportService {
constructor(private readonly emailService: EmailService) {}

async generateDailyReport(): Promise<string> {
// TODO: aggregate daily monitoring data
return 'Daily security summary placeholder';
}

async generateWeeklyReport(): Promise<string> {
// TODO: aggregate weekly monitoring data
return 'Weekly security summary placeholder';
}

async deliverReport(content: string, subject: string) {
await this.emailService.sendReport(content, subject);
}
}
10 changes: 10 additions & 0 deletions src/modules/reports/reports.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { ReportService } from './report.service';
import { SchedulerService } from './scheduler.service';
import { EmailService } from './email.service';

@Module({
providers: [ReportService, SchedulerService, EmailService],
exports: [ReportService, SchedulerService],
})
export class ReportsModule {}
22 changes: 22 additions & 0 deletions src/modules/reports/scheduler.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Injectable, OnModuleInit } from '@nestjs/common';
import { ReportService } from './report.service';
import * as cron from 'node-cron';

@Injectable()
export class SchedulerService implements OnModuleInit {
constructor(private readonly reportService: ReportService) {}

onModuleInit() {
// Daily at 08:00 UTC
cron.schedule('0 8 * * *', async () => {
const content = await this.reportService.generateDailyReport();
await this.reportService.deliverReport(content, 'Daily Security Report');
});

// Weekly on Monday at 09:00 UTC
cron.schedule('0 9 * * MON', async () => {
const content = await this.reportService.generateWeeklyReport();
await this.reportService.deliverReport(content, 'Weekly Security Report');
});
}
}
Loading