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
16,144 changes: 16,144 additions & 0 deletions backend/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@nestjs/common": "^11.0.1",
"@nestjs/config": "^4.0.3",
"@nestjs/core": "^11.0.1",
"@nestjs/event-emitter": "^3.0.1",
"@nestjs/jwt": "^11.0.2",
"@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^11.0.1",
Expand Down
4 changes: 4 additions & 0 deletions backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { ThrottlerGuard, ThrottlerModule } from '@nestjs/throttler';
import { APP_GUARD } from '@nestjs/core';
import { EventEmitterModule } from '@nestjs/event-emitter';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import configuration from './config/configuration';
Expand All @@ -20,6 +21,7 @@ import { ClaimsModule } from './modules/claims/claims.module';
import { DisputesModule } from './modules/disputes/disputes.module';
import { AdminAnalyticsModule } from './modules/admin-analytics/admin-analytics.module';
import { SavingsModule } from './modules/savings/savings.module';
import { NotificationsModule } from './modules/notifications/notification.module';

@Module({
imports: [
Expand All @@ -32,6 +34,7 @@ import { SavingsModule } from './modules/savings/savings.module';
abortEarly: true,
},
}),
EventEmitterModule.forRoot(),
TypeOrmModule.forRootAsync({
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
Expand All @@ -53,6 +56,7 @@ import { SavingsModule } from './modules/savings/savings.module';
DisputesModule,
AdminAnalyticsModule,
SavingsModule,
NotificationsModule,
ThrottlerModule.forRoot([
{
ttl: 60000,
Expand Down
5 changes: 5 additions & 0 deletions backend/src/modules/claims/claims.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { getRepositoryToken } from '@nestjs/typeorm';
import { MedicalClaim, ClaimStatus } from './entities/medical-claim.entity';
import { Repository } from 'typeorm';
import { HospitalIntegrationService } from '../hospital-integration/hospital-integration.service';
import { EventEmitter2 } from '@nestjs/event-emitter';

describe('ClaimsService', () => {
let service: ClaimsService;
Expand Down Expand Up @@ -37,6 +38,10 @@ describe('ClaimsService', () => {
provide: HospitalIntegrationService,
useValue: mockHospitalIntegrationService,
},
{
provide: EventEmitter2,
useValue: { emit: jest.fn() },
},
],
}).compile();

Expand Down
52 changes: 51 additions & 1 deletion backend/src/modules/claims/claims.service.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { MedicalClaim, ClaimStatus } from './entities/medical-claim.entity';
import { CreateClaimDto } from './dto/create-claim.dto';
import { HospitalIntegrationService } from '../hospital-integration/hospital-integration.service';
import { ClaimUpdatedEvent } from '../notifications/events/claim-updated.event';

@Injectable()
export class ClaimsService {
Expand All @@ -13,6 +15,7 @@ export class ClaimsService {
@InjectRepository(MedicalClaim)
private readonly claimsRepository: Repository<MedicalClaim>,
private readonly hospitalIntegrationService: HospitalIntegrationService,
private readonly eventEmitter: EventEmitter2,
) { }

async createClaim(createClaimDto: CreateClaimDto): Promise<MedicalClaim> {
Expand Down Expand Up @@ -51,13 +54,60 @@ export class ClaimsService {
claim.status = verification.verified ? ClaimStatus.APPROVED : ClaimStatus.REJECTED;
claim.notes = verification.notes || claim.notes;

return await this.claimsRepository.save(claim);
const saved = await this.claimsRepository.save(claim);

this.eventEmitter.emit(
'claim.updated',
new ClaimUpdatedEvent(
saved.id,
saved.patientName,
'',
saved.hospitalName,
Number(saved.claimAmount),
saved.status,
saved.notes,
),
);

return saved;
} catch (error) {
this.logger.error(`Failed to verify claim ${claimId}:`, error);
throw error;
}
}

async updateClaimStatus(
claimId: string,
status: ClaimStatus,
patientEmail: string,
notes?: string,
): Promise<MedicalClaim> {
const claim = await this.findOne(claimId);
if (!claim) {
throw new Error('Claim not found');
}

claim.status = status;
if (notes) claim.notes = notes;

const saved = await this.claimsRepository.save(claim);

this.eventEmitter.emit(
'claim.updated',
new ClaimUpdatedEvent(
saved.id,
saved.patientName,
patientEmail,
saved.hospitalName,
Number(saved.claimAmount),
saved.status,
saved.notes,
),
);

return saved;
}

async fetchHospitalClaimData(hospitalId: string, claimId: string) {
return await this.hospitalIntegrationService.fetchClaimData(hospitalId, claimId);
}
Expand Down
49 changes: 49 additions & 0 deletions backend/src/modules/mail/mail.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Injectable, Logger } from '@nestjs/common';
import { MailerService } from '@nestjs-modules/mailer';
import { ClaimStatus } from '../claims/entities/medical-claim.entity';

@Injectable()
export class MailService {
Expand All @@ -22,4 +23,52 @@ export class MailService {
this.logger.error(`Failed to send welcome email to ${userEmail}`, error);
}
}

async sendSweepCompleted(
userEmail: string,
userName: string,
productName: string,
amount: number,
txHash?: string,
): Promise<void> {
try {
await this.mailerService.sendMail({
to: userEmail,
subject: 'Your Nestera account sweep was successful',
template: './sweep-completed',
context: { userName, productName, amount, txHash },
});
this.logger.log(`Sweep-completed email sent to ${userEmail}`);
} catch (error) {
this.logger.error(
`Failed to send sweep-completed email to ${userEmail}`,
error,
);
}
}

async sendClaimUpdated(
patientEmail: string,
patientName: string,
claimId: string,
hospitalName: string,
claimAmount: number,
status: ClaimStatus,
notes?: string,
): Promise<void> {
try {
await this.mailerService.sendMail({
to: patientEmail,
subject: `Your medical claim status has been updated to ${status}`,
template: './claim-updated',
context: { patientName, claimId, hospitalName, claimAmount, status, notes },
});
this.logger.log(`Claim-updated email sent to ${patientEmail}`);
} catch (error) {
this.logger.error(
`Failed to send claim-updated email to ${patientEmail}`,
error,
);
}
}
}
24 changes: 24 additions & 0 deletions backend/src/modules/mail/templates/claim-updated.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Claim Status Update – Nestera</title>
</head>
<body>
<h1>Medical Claim Status Update</h1>
<p>Hi {{patientName}},</p>
<p>Your medical claim has been updated. Below are the details:</p>
<table>
<tr><td><strong>Claim ID</strong></td><td>{{claimId}}</td></tr>
<tr><td><strong>Hospital</strong></td><td>{{hospitalName}}</td></tr>
<tr><td><strong>Claim Amount</strong></td><td>{{claimAmount}}</td></tr>
<tr><td><strong>Status</strong></td><td>{{status}}</td></tr>
{{#if notes}}
<tr><td><strong>Notes</strong></td><td>{{notes}}</td></tr>
{{/if}}
</table>
<p>If you have any questions, please contact our support team.</p>
<br>
<p>Best regards,<br>The Nestera Team</p>
</body>
</html>
22 changes: 22 additions & 0 deletions backend/src/modules/mail/templates/sweep-completed.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Sweep Completed – Nestera</title>
</head>
<body>
<h1>Account Sweep Successful</h1>
<p>Hi {{userName}},</p>
<p>Your savings account sweep has been completed successfully.</p>
<table>
<tr><td><strong>Product</strong></td><td>{{productName}}</td></tr>
<tr><td><strong>Amount Swept</strong></td><td>{{amount}}</td></tr>
{{#if txHash}}
<tr><td><strong>Transaction Hash</strong></td><td>{{txHash}}</td></tr>
{{/if}}
</table>
<p>The funds have been transferred to your designated account.</p>
<br>
<p>Best regards,<br>The Nestera Team</p>
</body>
</html>
38 changes: 38 additions & 0 deletions backend/src/modules/notifications/entities/notification.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import {
Entity,
Column,
PrimaryGeneratedColumn,
CreateDateColumn,
} from 'typeorm';

export enum NotificationType {
SWEEP_COMPLETED = 'SWEEP_COMPLETED',
CLAIM_UPDATED = 'CLAIM_UPDATED',
}

@Entity('notifications')
export class Notification {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column('uuid')
userId: string;

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

@Column()
title: string;

@Column('text')
message: string;

@Column({ nullable: true })
referenceId: string;

@Column({ default: false })
isRead: boolean;

@CreateDateColumn()
createdAt: Date;
}
13 changes: 13 additions & 0 deletions backend/src/modules/notifications/events/claim-updated.event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ClaimStatus } from '../../claims/entities/medical-claim.entity';

export class ClaimUpdatedEvent {
constructor(
public readonly claimId: string,
public readonly patientName: string,
public readonly patientEmail: string,
public readonly hospitalName: string,
public readonly claimAmount: number,
public readonly status: ClaimStatus,
public readonly notes?: string,
) {}
}
11 changes: 11 additions & 0 deletions backend/src/modules/notifications/events/sweep-completed.event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export class SweepCompletedEvent {
constructor(
public readonly userId: string,
public readonly userEmail: string,
public readonly userName: string,
public readonly subscriptionId: string,
public readonly productName: string,
public readonly amount: number,
public readonly txHash?: string,
) {}
}
12 changes: 12 additions & 0 deletions backend/src/modules/notifications/notification.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Notification } from './entities/notification.entity';
import { NotificationService } from './notification.service';
import { MailModule } from '../mail/mail.module';

@Module({
imports: [TypeOrmModule.forFeature([Notification]), MailModule],
providers: [NotificationService],
exports: [NotificationService],
})
export class NotificationsModule {}
Loading