Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
b6f1a1a
chore: remove unwanted src/app.*.ts files
ObiFaith Nov 11, 2025
b396a43
feat: add health endpoints and email consumer functionality
ObiFaith Nov 11, 2025
144c51e
feat: add redis and email consumer validations
ObiFaith Nov 11, 2025
172b968
chore: remove console.log
ObiFaith Nov 11, 2025
ebdb49e
feat: initialize RabbitMQ service
ObiFaith Nov 12, 2025
f3fce0b
feat(db): add notification email entity
ObiFaith Nov 12, 2025
8012a48
feat: add api/v1/email/status, entity & update email consumer
ObiFaith Nov 12, 2025
bbcc575
fix: modify EmailStatus from enum to type
ObiFaith Nov 12, 2025
0386684
feat: add NotificationEmail entity
ObiFaith Nov 12, 2025
0ac40e8
feat: implement mailersend api
ObiFaith Nov 13, 2025
47ae176
feat(setup): initialize NestJS project with PostgreSQL, RabbitMQ, and…
ObiFaith Nov 10, 2025
ee34f3a
chore: remove unwanted src/app.*.ts files
ObiFaith Nov 11, 2025
85e89d7
feat: add health endpoints and email consumer functionality
ObiFaith Nov 11, 2025
c9086bc
feat: add redis and email consumer validations
ObiFaith Nov 11, 2025
41f6930
chore: remove console.log
ObiFaith Nov 11, 2025
d01740c
feat: initialize RabbitMQ service
ObiFaith Nov 12, 2025
3f0baab
feat(db): add notification email entity
ObiFaith Nov 12, 2025
ce002a6
feat: add api/v1/email/status, entity & update email consumer
ObiFaith Nov 12, 2025
e28e085
fix: modify EmailStatus from enum to type
ObiFaith Nov 12, 2025
7dc5a10
feat: add NotificationEmail entity
ObiFaith Nov 12, 2025
7aaf981
feat: implement mailersend api
ObiFaith Nov 13, 2025
7d94e8f
Merge branch 'feat/add-health-and-email-consumer' of https://github.c…
ObiFaith Nov 13, 2025
e7b3762
fix: modify mail service provider & db migration
ObiFaith Nov 13, 2025
8d7c181
chore: delete pnpm-lock.yaml
ObiFaith Nov 13, 2025
efee002
Merge branch 'dev' into feat/add-health-and-email-consumer
ObiFaith Nov 13, 2025
a114f39
chore: add pnpm-lock.yaml
ObiFaith Nov 13, 2025
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
1 change: 0 additions & 1 deletion services/email_service/.env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,3 @@ MAIL_HOST=<smtp-host>
MAIL_PORT=<smtp-port>
SMTP_USER=<your-email>
SMTP_PASS=<your-password>
MAIL_FROM="Framez <noreply@framez.app>"
1 change: 0 additions & 1 deletion services/email_service/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ MAIL_HOST=<smtp-host>
MAIL_PORT=<smtp-port>
SMTP_USER=<your-email>
SMTP_PASS=<your-password>
MAIL_FROM="Framez <noreply@framez.app>"
```

#### 4. Run PostgreSQL and RabbitMQ (via Docker Compose or local):
Expand Down
17 changes: 16 additions & 1 deletion services/email_service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,33 @@
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
"test:e2e": "jest --config ./test/jest-e2e.json",
"typeorm": "typeorm-ts-node-esm",
"migration:generate": "ts-node ./node_modules/typeorm/cli.js migration:generate -d src/config/data-source.ts",
"migration:run": "ts-node ./node_modules/typeorm/cli.js migration:run -d src/config/data-source.ts",
"migration:revert": "ts-node ./node_modules/typeorm/cli.js migration:revert -d src/config/data-source.ts"
},
"dependencies": {
"@nestjs-modules/mailer": "^2.0.2",
"@nestjs/axios": "^4.0.1",
"@nestjs/cache-manager": "^3.0.1",
"@nestjs/common": "^11.0.1",
"@nestjs/config": "^4.0.2",
"@nestjs/core": "^11.0.1",
"@nestjs/microservices": "^11.1.8",
"@nestjs/platform-express": "^11.0.1",
"@nestjs/swagger": "^11.2.1",
"@nestjs/terminus": "^11.0.0",
"@nestjs/typeorm": "^11.0.0",
"@sendgrid/mail": "^8.1.6",
"amqp-connection-manager": "^5.0.0",
"amqplib": "^0.10.9",
"cache-manager": "^7.2.4",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.2",
"dotenv": "^17.2.3",
"ioredis": "^5.8.2",
"mailersend": "^2.6.0",
"nodemailer": "^7.0.10",
"pg": "^8.16.3",
"reflect-metadata": "^0.2.2",
Expand Down
619 changes: 574 additions & 45 deletions services/email_service/pnpm-lock.yaml

Large diffs are not rendered by default.

24 changes: 18 additions & 6 deletions services/email_service/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
import {
DatabaseModule,
CacheConfigModule,
MailerConfigModule,
} from './config';
import { Module } from '@nestjs/common';
import { AppService } from './app.service';
import { HttpModule } from '@nestjs/axios';
import { ConfigModule } from '@nestjs/config';
import { AppController } from './app.controller';
import { DatabaseModule } from './config/database.module';
import { MailerConfigModule } from './config/mailer.module';
import { TerminusModule } from '@nestjs/terminus';
import { EmailModule } from './email/email.module';
import { HealthController } from './health/health.controller';
import { DatabaseLogger } from './utils/database-logger.service';
import { EmailStatusModule } from './email-status/email-status.module';
import { SendGridService } from './sendgrid/sendgrid.service';

@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true, envFilePath: '.env' }),
DatabaseModule,
MailerConfigModule,
TerminusModule,
HttpModule,
CacheConfigModule,
EmailModule,
EmailStatusModule,
],
controllers: [AppController],
providers: [AppService, DatabaseLogger],
controllers: [HealthController],
providers: [DatabaseLogger, SendGridService],
})
export class AppModule {}
39 changes: 39 additions & 0 deletions services/email_service/src/config/cache.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import Redis from 'ioredis';
import { Module } from '@nestjs/common';
import { CacheModule } from '@nestjs/cache-manager';

@Module({
imports: [
CacheModule.registerAsync({
isGlobal: true,
useFactory: () => {
if (!process.env.REDIS_URL) {
throw new Error('CacheModule: REDIS_URL is not defined!');
}

const redisClient = new Redis(process.env.REDIS_URL);

// optional logging
redisClient.on('connect', () => console.log('Redis connected'));
redisClient.on('error', (err) => console.error('Redis error:', err));

return {
store: {
get: async (key: string): Promise<any> => {
const val = await redisClient.get(key);
return val ? JSON.parse(val) : null;
},
set: async (key: string, value: any, ttl: number) => {
return redisClient.set(key, JSON.stringify(value), 'EX', ttl);
},
del: async (key: string) => {
return redisClient.del(key);
},
},
};
},
}),
],
exports: [CacheModule],
})
export class CacheConfigModule {}
18 changes: 18 additions & 0 deletions services/email_service/src/config/data-source.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { config } from 'dotenv';
import { DataSource } from 'typeorm';
import { NotificationEmail } from '../email/entity/email.entity';

config();

export const AppDataSource = new DataSource({
type: 'postgres',
host: process.env.DB_HOST,
port: Number(process.env.DB_PORT),
username: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
entities: [NotificationEmail],
migrations: ['src/migrations/*.ts'],
synchronize: true, // TODO: false
logging: true,
});
9 changes: 3 additions & 6 deletions services/email_service/src/config/database.module.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { NotificationEmail } from 'src/email/entity/email.entity';

@Module({
imports: [
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
type: 'postgres',
host: configService.get('DB_HOST'),
port: +configService.get('DB_PORT'),
username: configService.get('DB_USER'),
password: configService.get('DB_PASS'),
database: configService.get('DB_NAME'),
entities: [__dirname + '/../**/*.entity{.ts,.js}'],
url: configService.get('DATABASE_URL'),
entities: [NotificationEmail],
synchronize: false,
logging: ['error'],
extra: { max: 20, idleTimeoutMillis: 30000 },
Expand Down
3 changes: 3 additions & 0 deletions services/email_service/src/config/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './cache.module';
export * from './mailer.module';
export * from './database.module';
2 changes: 1 addition & 1 deletion services/email_service/src/config/mailer.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { ConfigModule, ConfigService } from '@nestjs/config';
pass: configService.get('MAIL_PASS'),
},
},
defaults: { from: configService.get('MAIL_FROM') },
defaults: { from: configService.get('MAIL_USER') },
}),
inject: [ConfigService],
}),
Expand Down
11 changes: 11 additions & 0 deletions services/email_service/src/email-status/email-status.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { NotificationEmail } from 'src/email/entity/email.entity';
import { EmailStatusService } from './email-status.service';

@Module({
imports: [TypeOrmModule.forFeature([NotificationEmail])],
providers: [EmailStatusService],
exports: [EmailStatusService],
})
export class EmailStatusModule {}
39 changes: 39 additions & 0 deletions services/email_service/src/email-status/email-status.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Repository } from 'typeorm';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { NotificationEmail } from '../email/entity/email.entity';

@Injectable()
export class EmailStatusService {
constructor(
@InjectRepository(NotificationEmail)
private readonly notificationEmailRepo: Repository<NotificationEmail>,
) {}

async handleStatusUpdate(body: any) {
const { notification_id, status, error } = body;

const email = await this.notificationEmailRepo.findOne({
where: { notification_id },
});
if (!email) return { success: false, message: 'Notification not found' };

email.status = status || 'failed';
email.error_message = error || null;
await this.notificationEmailRepo.save(email);

return {
success: true,
data: { notification_id, status },
message: 'Notification status updated',
meta: {
total: 1,
limit: 1,
page: 1,
total_pages: 1,
has_next: false,
has_previous: false,
},
};
}
}
Loading
Loading