Repository: hngprojects/hng_boilerplate_nestjs

Issue:
[FIX] Incorrect response format in getSupportedTimezones method
## Bug Description
The getSupportedTimezones method in the TimezonesService incorrectly formats the response, omitting key timezone details.
<!-- Provide a clear and concise description of the bug -->

## Steps to Reproduce

<!-- List the steps to reproduce the behavior -->

1. Start the NestJS server.
2. Send a GET request to /timezones.
3. Observe the response format.


## Expected Behavior
The API should return a list of timezones with their respective timezone, gmtOffset, and description properties.
<!-- Describe what you expected to happen -->

## Current Behavior
The API returns a list where the timezone field is formatted as (UTC {gmtOffset}) {description}, instead of providing separate fields for timezone, gmtOffset, and description.
<!-- Describe what actually happens -->

## Screenshots/Logs

![Image](https://github.com/user-attachments/assets/6b37b129-ea0c-4b33-92a2-6d53a8c30a0c)

<!-- If applicable, add screenshots or logs to help explain the problem -->

## Environment
1. OS: Windows 10
2. Browser/Device: Postman
3. Version: Node.js 18, NestJS 10
<!-- Please complete the following information -->



## Possible Solution
Modify getSupportedTimezones in TimezonesService to return the raw timezone, gmtOffset, and description fields instead of concatenating them into a single string.
<!-- If you have suggestions on how to fix the bug -->

## Additional Context
This issue affects frontend clients relying on structured timezone data for dropdown selections and filtering.
<!-- Add any other context about the problem here -->


Hints from issue comments:

Solution patch:
diff --git a/.env.example b/.env.example
index 018066e3..e92f4153 100644
--- a/.env.example
+++ b/.env.example
@@ -1,41 +1,33 @@
-NODE_ENV=development
 PROFILE=local
-PORT=5000
-HOST=
-REDIS_PORT= 6379
-DB_SSL=true
+NODE_ENV=development
+
+PORT=3008
 
 JWT_SECRET=someSecrets
 JWT_EXPIRY_TIMEFRAME=3600
+
+
 REDIS_HOST=localhost
 REDIS_PORT=6379
-DB_TYPE=
+
+DB_TYPE=postgres
 DB_USERNAME=
 DB_PASSWORD=
-DB_HOST=
-DB_NAME=hng
+DB_HOST=localhost
+DB_DATABASE=
 DB_ENTITIES=dist/src/modules/**/entities/**/*.entity{.ts,.js}
 DB_MIGRATIONS=dist/db/migrations/*{.ts,.js}
-POSGRES_USER=$DB_USERNAME
-POST
-JWT_SECRET=gsgs
-JWT_EXPIRY_TIMEFRAME=1500000
-DB_SSL=false
-JWT_REFRESH_SECRET=bbp
-JWT_REFRESH_EXPIRY_TIMEFRAME=15
-GOOGLE_REDIRECT_URI=
+
+JWT_SECRET=someSecrets
+JWT_EXPIRY_TIMEFRAME=3600
+
+ADMIN_SECRET_KEY=sometext
+
 GOOGLE_CLIENT_SECRET=
 GOOGLE_CLIENT_ID=
-OAUTH_LOGIN_REDIRECT=
-SMTP_HOST=
+
+SMTP_HOST=sandbox.smtp.mailtrap.io
 SMTP_PORT=587
-SERVER_NAME=Boilerplate
+SERVER_NAME=api
 SMTP_USER=
-SMTP_PASSWORD=
-FRONTEND_URL=
-ADMIN_SECRET=
-SUPPORT_EMAIL=
-AUTH_PASSWORD=
-BASE_URL=
-FLUTTERWAVE_SECRET_KEY=
-FLUTTERWAVE_BASE_URL=
+SMTP_PASSWORD=
\ No newline at end of file
diff --git a/.env.local b/.env.local
index 6771b01b..7648ab28 100644
--- a/.env.local
+++ b/.env.local
@@ -31,7 +31,7 @@ NODE_ENV=development
 PORT=3000
 
 DB_USERNAME=username
-DB_PASSWORD=password
+DB_PASSWORD=password123
 DB_TYPE=postgres
 DB_NAME=database
 DB_HOST=localhost
diff --git a/.gitignore b/.gitignore
index b5f15abc..0a7f13b9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -55,6 +55,11 @@ fabric.properties
 ##
 ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
 
+
+# DB
+data/db/
+
+docker-compose.yml
 # User-specific files
 *.suo
 *.user
@@ -402,12 +407,12 @@ dist
 
 # User specific ignores 
 todo.txt
+/.vscode/
 .vscode/
 
 # Docker compose
 docker-compose.yml
 data/
-<<<<<<< HEAD
 data/
 docker-compose.yml
 package-lock.json
@@ -419,16 +424,12 @@ docker-compose.yml
 data/
 .dev.env
 
-
-=======
-
-# Docker compose
-docker-compose.yml
-data/
->>>>>>> d080450 (chore: created a docker-compose.yml file and updated the gitignore)
+/compose/compose.yaml
+compose/compose.yaml
 
 data/
 docker-compose.yml
+package-lock.json/
 package-lock.json
 .dev.env
 
diff --git a/compose/compose.yaml b/compose/compose.yaml
deleted file mode 100644
index 335a2e94..00000000
--- a/compose/compose.yaml
+++ /dev/null
@@ -1,65 +0,0 @@
-name: nestjs
-
-services:
-  app:
-    image: ${COMPOSE_PROJECT_NAME}
-    build: .
-    env_file:
-      - .env
-    depends_on:
-      db:
-        condition: service_healthy
-      redis:
-        condition: service_healthy
-    healthcheck:
-      test: 'wget -qO- http://app:${PORT}'
-      interval: 10s
-      timeout: 10s
-      retries: 3
-
-  db:
-    image: postgres:16-alpine
-    env_file:
-      - .env
-    environment:
-      - POSTGRES_USER=${DB_USERNAME}
-      - POSTGRES_PASSWORD=${DB_PASSWORD}
-      - POSTGRES_DB=${DB_NAME}
-    volumes:
-      - postgres_data:/var/lib/postgresql/data
-    healthcheck:
-      test: 'pg_isready -U postgres'
-      interval: 5s
-      timeout: 5s
-      retries: 3
-    restart: always
-
-  redis:
-    image: redis:7-alpine
-    env_file:
-      - .env
-    volumes:
-      - redis_data:/data
-    healthcheck:
-      test: 'redis-cli ping | grep PONG'
-      interval: 5s
-      timeout: 5s
-      retries: 3
-    restart: always
-
-  nginx:
-    image: nginx:alpine
-    volumes:
-      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
-    depends_on:
-      app:
-        condition: service_healthy
-    healthcheck:
-      test: 'wget -qO- http://nginx:80'
-      interval: 5s
-      timeout: 5s
-      retries: 3
-
-volumes:
-  postgres_data:
-  redis_data:
diff --git a/config/s3.config.ts b/config/s3.config.ts
new file mode 100644
index 00000000..8dc0544b
--- /dev/null
+++ b/config/s3.config.ts
@@ -0,0 +1,8 @@
+import { registerAs } from '@nestjs/config';
+
+export default registerAs('s3', () => ({
+  accessKey: process.env.AWS_ACCESS_KEY || '',
+  secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || '',
+  region: process.env.AWS_REGION || 'us-east-2',
+  bucketName: process.env.AWS_S3_BUCKET_NAME || '',
+}));
diff --git a/local repo b/local repo
new file mode 100644
index 00000000..a622f163
--- /dev/null
+++ b/local repo	
@@ -0,0 +1,6 @@
+gerge branch 'dev' into feat/s3-resume-upload
+# Please enter a commit message to explain why this merge is necessary,
+# especially if it merges an updated upstream into a topic branch.
+#
+# Lines starting with '#' will be ignored, and an empty message aborts
+# the commit.
diff --git a/package.json b/package.json
index 847f7b50..7baeed44 100644
--- a/package.json
+++ b/package.json
@@ -41,12 +41,14 @@
     "@nestjs/jwt": "^11.0.0",
     "@nestjs/passport": "^11.0.5",
     "@nestjs/platform-express": "^11.0.10",
+    "@nestjs/schedule": "^5.0.1",
     "@nestjs/serve-static": "^5.0.3",
     "@nestjs/swagger": "^11.0.5",
     "@nestjs/typeorm": "^11.0.0",
     "@types/nodemailer": "^6.4.17",
     "@types/speakeasy": "^2.0.10",
     "@vitalets/google-translate-api": "^9.2.1",
+    "aws-sdk": "^2.1692.0",
     "bcrypt": "^5.1.1",
     "bcryptjs": "^3.0.2",
     "bull": "^4.16.5",
@@ -57,7 +59,7 @@
     "file-type-mime": "^0.4.6",
     "google-auth-library": "^9.15.1",
     "handlebars": "^4.7.8",
-    "html-validator": "^6.0.1",
+    "html-validator": "^5.1.18",
     "ioredis": "^5.5.0",
     "joi": "^17.13.3",
     "module-alias": "^2.2.3",
@@ -82,7 +84,7 @@
   "devDependencies": {
     "@commitlint/cli": "^19.7.1",
     "@commitlint/config-conventional": "^19.7.1",
-    "@nestjs-modules/mailer": "^2.0.2",
+    "@nestjs-modules/mailer": "^1.6.1",
     "@nestjs/cli": "^11.0.5",
     "@nestjs/common": "^11.0.10",
     "@nestjs/schematics": "^11.0.1",
diff --git a/src/app.module.ts b/src/app.module.ts
index 276af8f5..d9cc362d 100644
--- a/src/app.module.ts
+++ b/src/app.module.ts
@@ -48,7 +48,7 @@ import { ServeStaticModule } from '@nestjs/serve-static';
 import { join } from 'path';
 import { LanguageGuard } from '@guards/language.guard';
 import { ApiStatusModule } from '@modules/api-status/api-status.module';
-
+import s3Config from '@config/s3.config';
 @Module({
   providers: [
     {
@@ -82,7 +82,7 @@ import { ApiStatusModule } from '@modules/api-status/api-status.module';
        */
       envFilePath: ['.env.development.local', `.env.${process.env.PROFILE}`],
       isGlobal: true,
-      load: [serverConfig, authConfig],
+      load: [serverConfig, authConfig, s3Config],
       validationSchema: Joi.object({
         NODE_ENV: Joi.string().valid('development', 'production', 'test', 'provision').required(),
         PROFILE: Joi.string().valid('local', 'development', 'production', 'ci', 'testing', 'staging').required(),
diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts
index 05f15312..9da65d5f 100644
--- a/src/modules/auth/auth.service.ts
+++ b/src/modules/auth/auth.service.ts
@@ -22,6 +22,7 @@ import { RequestSigninTokenDto } from './dto/request-signin-token.dto';
 import { OtpDto } from '@modules/otp/dto/otp.dto';
 import { DataSource, EntityManager } from 'typeorm';
 import { CreateOrganisationRecordOptions } from '@modules/organisations/dto/create-organisation-options';
+
 @Injectable()
 export default class AuthenticationService {
   constructor(
@@ -41,12 +42,10 @@ export default class AuthenticationService {
         identifierType: 'email',
       });
 
-      console.log('userExists', userExists);
       if (userExists) {
         throw new CustomHttpException(SYS_MSG.USER_ACCOUNT_EXIST, HttpStatus.BAD_REQUEST);
       }
 
-      console.log('createUserDto', createUserDto);
       const user = await this.userService.createUser(createUserDto, manager);
 
       if (!user) {
diff --git a/src/modules/billing-plans/billing-plan.controller.ts b/src/modules/billing-plans/billing-plan.controller.ts
index 55e68de4..180313f3 100644
--- a/src/modules/billing-plans/billing-plan.controller.ts
+++ b/src/modules/billing-plans/billing-plan.controller.ts
@@ -11,8 +11,11 @@ import {
   HttpCode,
   HttpStatus,
 } from '@nestjs/common';
-import { ApiTags } from '@nestjs/swagger';
+import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
+import { SuperAdminGuard } from '../../guards/super-admin.guard';
 import { BillingPlanService } from './billing-plan.service';
+import { skipAuth } from '@shared/helpers/skipAuth';
+import { BillingPlanDto } from './dto/billing-plan.dto';
 import {
   createBillingPlanDocs,
   deleteBillingPlanDocs,
@@ -20,9 +23,6 @@ import {
   getSingleBillingPlanDocs,
   updateBillingPlanDocs,
 } from './docs/billing-plan-docs';
-import { SuperAdminGuard } from '@guards/super-admin.guard';
-import { BillingPlanDto } from './dto/billing-plan.dto';
-import { skipAuth } from '@shared/helpers/skipAuth';
 import { UpdateBillingPlanDto } from './dto/update-billing-plan.dto';
 
 @ApiTags('Billing Plans')
@@ -68,4 +68,17 @@ export class BillingPlanController {
   async deleteBillingPlan(@Param('id', ParseUUIDPipe) id: string) {
     return this.billingPlanService.deleteBillingPlan(id);
   }
+
+  //  For sending renewal reminders
+
+  @Post('send-renewal-reminder/:id')
+  @ApiOperation({ summary: 'Send renewal reminder for a billing plan' })
+  @ApiResponse({
+    status: 200,
+    description: 'Renewal reminder sent successfully.',
+  })
+  @ApiResponse({ status: 404, description: 'Billing plan not found' })
+  async sendRenewalReminder(@Param('id') id: string) {
+    return this.billingPlanService.sendRenewalReminder(id);
+  }
 }
diff --git a/src/modules/billing-plans/billing-plan.module.ts b/src/modules/billing-plans/billing-plan.module.ts
index f5e7f756..8d17a48a 100644
--- a/src/modules/billing-plans/billing-plan.module.ts
+++ b/src/modules/billing-plans/billing-plan.module.ts
@@ -1,16 +1,43 @@
 import { Module } from '@nestjs/common';
+import { BillingPlanService } from './billing-plan.service';
+import { BillingPlanController } from './billing-plan.controller';
 import { TypeOrmModule } from '@nestjs/typeorm';
 import { BillingPlan } from './entities/billing-plan.entity';
-import { User } from '@modules/user/entities/user.entity';
-import { Organisation } from '@modules/organisations/entities/organisations.entity';
-import { OrganisationUserRole } from '@modules/role/entities/organisation-user-role.entity';
-import { Role } from '@modules/role/entities/role.entity';
-import { BillingPlanController } from './billing-plan.controller';
-import { BillingPlanService } from './billing-plan.service';
+import { User } from '../user/entities/user.entity';
+import { Organisation } from '../organisations/entities/organisations.entity';
+import { OrganisationUserRole } from '../role/entities/organisation-user-role.entity';
+import { Role } from '../role/entities/role.entity';
+import { MailerModule } from '@nestjs-modules/mailer';
+import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter';
+import { ScheduleModule } from '@nestjs/schedule';
+import { SubscriptionScheduler } from './subscription-scheduler';
 
 @Module({
-  imports: [TypeOrmModule.forFeature([BillingPlan, User, Organisation, OrganisationUserRole, Role])],
+  imports: [
+    TypeOrmModule.forFeature([BillingPlan, User, Organisation, OrganisationUserRole, Role]),
+    MailerModule.forRoot({
+      transport: {
+        host: 'smtp.example.com',
+        port: 587,
+        auth: {
+          user: 'user@example.com',
+          pass: 'password',
+        },
+      },
+      defaults: {
+        from: '"No Reply" <noreply@example.com>',
+      },
+      template: {
+        dir: __dirname + '/../../email/hng-templates',
+        adapter: new HandlebarsAdapter(),
+        options: {
+          strict: true,
+        },
+      },
+    }),
+    ScheduleModule.forRoot(),
+  ],
   controllers: [BillingPlanController],
-  providers: [BillingPlanService],
+  providers: [BillingPlanService, SubscriptionScheduler],
 })
 export class BillingPlanModule {}
diff --git a/src/modules/billing-plans/billing-plan.service.ts b/src/modules/billing-plans/billing-plan.service.ts
index 29eae7e7..f9bd94c6 100644
--- a/src/modules/billing-plans/billing-plan.service.ts
+++ b/src/modules/billing-plans/billing-plan.service.ts
@@ -1,18 +1,27 @@
-import { Injectable, HttpStatus, BadRequestException, NotFoundException } from '@nestjs/common';
+import {
+  Injectable,
+  HttpStatus,
+  HttpException,
+  BadRequestException,
+  NotFoundException,
+  InternalServerErrorException,
+} from '@nestjs/common';
 import { Repository } from 'typeorm';
-import { InjectRepository } from '@nestjs/typeorm';
 import { BillingPlan } from './entities/billing-plan.entity';
+import { InjectRepository } from '@nestjs/typeorm';
 import { BillingPlanDto } from './dto/billing-plan.dto';
-import { CustomHttpException } from '@shared/helpers/custom-http-filter';
 import * as SYS_MSG from '@shared/constants/SystemMessages';
+import { CustomHttpException } from '@shared/helpers/custom-http-filter';
 import { BillingPlanMapper } from './mapper/billing-plan.mapper';
 import { UpdateBillingPlanDto } from './dto/update-billing-plan.dto';
+import { MailerService } from '@nestjs-modules/mailer';
 
 @Injectable()
 export class BillingPlanService {
   constructor(
     @InjectRepository(BillingPlan)
-    private readonly billingPlanRepository: Repository<BillingPlan>
+    private readonly billingPlanRepository: Repository<BillingPlan>,
+    private readonly mailerService: MailerService
   ) {}
 
   async createBillingPlan(createBillingPlanDto: BillingPlanDto) {
@@ -81,4 +90,37 @@ export class BillingPlanService {
     }
     await this.billingPlanRepository.delete(id);
   }
+
+  // New methods for subscription renewal reminders
+  async getAllSubscriptions(): Promise<BillingPlan[]> {
+    return this.billingPlanRepository.find();
+  }
+
+  async sendRenewalReminder(subscriptionId: string): Promise<void> {
+    const subscription = await this.billingPlanRepository.findOne({ where: { id: subscriptionId } });
+    if (!subscription) {
+      throw new NotFoundException('Subscription not found');
+    }
+
+    const expirationDate = new Date(subscription.expirationDate);
+    const currentDate = new Date();
+    const daysUntilExpiration = (expirationDate.getTime() - currentDate.getTime()) / (1000 * 3600 * 24);
+
+    if (daysUntilExpiration <= 7) {
+      try {
+        await this.mailerService.sendMail({
+          to: subscription.email,
+          subject: 'Subscription Renewal Reminder',
+          template: './renewal-reminder',
+          context: {
+            name: subscription.name,
+            expirationDate: subscription.expirationDate,
+          },
+        });
+        console.log(`Renewal reminder sent to ${subscription.email}`);
+      } catch (error) {
+        console.error(`Failed to send renewal reminder to ${subscription.email}: ${error.message}`);
+      }
+    }
+  }
 }
diff --git a/src/modules/billing-plans/entities/billing-plan.entity.ts b/src/modules/billing-plans/entities/billing-plan.entity.ts
index c53c69fa..157927d0 100644
--- a/src/modules/billing-plans/entities/billing-plan.entity.ts
+++ b/src/modules/billing-plans/entities/billing-plan.entity.ts
@@ -17,4 +17,10 @@ export class BillingPlan extends AbstractBaseEntity {
 
   @Column({ type: 'int', nullable: true })
   amount: number;
+
+  @Column({ type: 'date', nullable: true })
+  expirationDate: Date;
+
+  @Column({ type: 'text', nullable: true })
+  email: string;
 }
diff --git a/src/modules/billing-plans/subscription-scheduler.ts b/src/modules/billing-plans/subscription-scheduler.ts
new file mode 100644
index 00000000..7d84aeb4
--- /dev/null
+++ b/src/modules/billing-plans/subscription-scheduler.ts
@@ -0,0 +1,17 @@
+import { Injectable } from '@nestjs/common';
+import { Cron } from '@nestjs/schedule';
+import { BillingPlanService } from './billing-plan.service';
+
+@Injectable()
+export class SubscriptionScheduler {
+  constructor(private readonly billingPlanService: BillingPlanService) {}
+
+  @Cron('0 0 * * *')
+  async handleCron() {
+    console.log('Running daily subscription renewal reminder job');
+    const subscriptions = await this.billingPlanService.getAllSubscriptions();
+    for (const subscription of subscriptions) {
+      await this.billingPlanService.sendRenewalReminder(subscription.id);
+    }
+  }
+}
diff --git a/src/modules/comments/dto/add-comment.dto.ts b/src/modules/comments/dtos/add-comment.dto.ts
similarity index 100%
rename from src/modules/comments/dto/add-comment.dto.ts
rename to src/modules/comments/dtos/add-comment.dto.ts
diff --git a/src/modules/comments/entities/comment.entity.ts b/src/modules/comments/entities/comment.entity.ts
deleted file mode 100644
index 830fd264..00000000
--- a/src/modules/comments/entities/comment.entity.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { AbstractBaseEntity } from '../../../entities/base.entity';
-import { Entity, Column, ManyToOne } from 'typeorm';
-import { User } from '../../user/entities/user.entity';
-
-@Entity()
-export class Comment extends AbstractBaseEntity {
-  @Column({ nullable: false })
-  model_id: string;
-
-  @Column()
-  model_type: string;
-
-  @Column()
-  content: string;
-
-  @ManyToOne(() => User, user => user.comments)
-  user: User;
-}
diff --git a/src/modules/comments/entities/comments.entity.ts b/src/modules/comments/entities/comments.entity.ts
index 1ecbf0ef..8d4f2261 100644
--- a/src/modules/comments/entities/comments.entity.ts
+++ b/src/modules/comments/entities/comments.entity.ts
@@ -14,9 +14,9 @@ export class Comment extends AbstractBaseEntity {
   @ManyToOne(() => User, user => user.comments, { cascade: true })
   user: User;
 
-  @Column({ nullable: false })
+  @Column({ nullable: true })
   model_id: string;
 
-  @Column()
+  @Column({ nullable: true })
   model_type: string;
 }
diff --git a/src/modules/email/email.consumer.ts b/src/modules/email/email.consumer.ts
index c6d401b0..f025a9ac 100644
--- a/src/modules/email/email.consumer.ts
+++ b/src/modules/email/email.consumer.ts
@@ -96,7 +96,7 @@ export default class EmailQueueConsumer {
     }
   }
 
-  @Process('in-app-notification')
+  @Process('login-otp')
   async sendLoginOtpEmailJob(job: Job<MailInterface>) {
     try {
       const {
@@ -113,7 +113,7 @@ export default class EmailQueueConsumer {
     }
   }
 
-  @Process('login-otp')
+  @Process('in-app-notification')
   async sendNotificationMail(job: Job<MailInterface>) {
     try {
       const {
diff --git a/src/modules/email/hng-templates/login-otp.hbs b/src/modules/email/hng-templates/login-otp.hbs
index 3dd04aea..f69e6ea2 100644
--- a/src/modules/email/hng-templates/login-otp.hbs
+++ b/src/modules/email/hng-templates/login-otp.hbs
@@ -1,20 +1,71 @@
 <html lang='en'><head><meta charset='UTF-8' /><meta
       name='viewport'
       content='width=device-width, initial-scale=1.0'
-    /><title>Email Confirmation</title><style>body { font-family: Arial, sans-serif; background-color: #f4f4f4; margin:
-      0; padding: 0; } .container { width: 100%; max-width: 600px; margin: 0 auto; background-color: #ffffff; padding:
-      20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); } .header { text-align: center; padding-bottom:
-      20px; } .header img { width: 120px; } .content { text-align: center; padding: 20px; } .content h1 { color: #333;
-      font-size: 24px; margin-bottom: 15px; } .content p { color: #666; font-size: 16px; line-height: 1.5;
-      margin-bottom: 20px; } .btn { display: inline-block; padding: 12px 25px; color: #ffffff; background-color:
-      #28a745; text-decoration: none; border-radius: 5px; font-weight: bold; font-size: 16px; margin-top: 20px; }
-      .footer { text-align: center; padding-top: 20px; font-size: 12px; color: #888888; } .footer a { color: #28a745;
-      text-decoration: none; }</style></head><body><div class='container'><div class='header'><img
+    /><title>Email Confirmation</title><style>
+      body {
+        font-family: Arial, sans-serif;
+        background-color: #f4f4f4;
+        margin: 0;
+        padding: 0;
+      }
+      .container {
+        width: 100%;
+        max-width: 600px;
+        margin: 0 auto;
+        background-color: #ffffff;
+        padding: 20px;
+        border-radius: 8px;
+        box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+      }
+      .header {
+        text-align: center;
+        padding-bottom: 20px;
+      }
+      .header img {
+        width: 120px;
+      }
+      .content {
+        text-align: center;
+        padding: 20px;
+      }
+      .content h1 {
+        color: #333;
+        font-size: 24px;
+        margin-bottom: 15px;
+      }
+      .content p {
+        color: #666;
+        font-size: 16px;
+        line-height: 1.5;
+        margin-bottom: 20px;
+      }
+      .btn {
+        display: inline-block;
+        padding: 12px 25px;
+        color: #ffffff;
+        background-color: #28a745;
+        text-decoration: none;
+        border-radius: 5px;
+        font-weight: bold;
+        font-size: 16px;
+        margin-top: 20px;
+      }
+      .footer {
+        text-align: center;
+        padding-top: 20px;
+        font-size: 12px;
+        color: #888888;
+      }
+      .footer a {
+        color: #28a745;
+        text-decoration: none;
+      }
+    </style></head><body><div class='container'><div class='header'><img
           src='https://www.shutterstock.com/image-vector/circle-line-simple-design-logo-600nw-2174926871.jpg'
           alt='Company Logo'
         /></div><div class='content'><h1>Confirm Your Email Address</h1><p>Hello {{email}}</p><p>Thank you for
           registering. Please confirm your email address by entering the otp:
-          <strong>{{otp}}</strong></p><p>If you didn't create an account with us, you can safely ignore this email.</p></div><div
+          <strong>{{token}}</strong></p><p>If you didn't create an account with us, you can safely ignore this email.</p></div><div
         class='footer'
       ><p>&copy; 2024 Company Name. All rights reserved.</p><p><a
             href='https://example.com/unsubscribe'
diff --git a/src/modules/email/hng-templates/renewal-reminder.hbs b/src/modules/email/hng-templates/renewal-reminder.hbs
new file mode 100644
index 00000000..af9ecf94
--- /dev/null
+++ b/src/modules/email/hng-templates/renewal-reminder.hbs
@@ -0,0 +1,11 @@
+<html>
+  <head>
+    <title>Subscription Renewal Reminder</title>
+  </head>
+  <body>
+    <p>Dear {{name}},</p>
+    <p>This is a friendly reminder that your subscription is set to expire on {{expirationDate}}.</p>
+    <p>Please renew your subscription to continue enjoying our services.</p>
+    <p>Thank you!</p>
+  </body>
+</html>
\ No newline at end of file
diff --git a/src/modules/jobs/dto/job-application.dto.ts b/src/modules/jobs/dto/job-application.dto.ts
index 4d1413be..6e1192c6 100644
--- a/src/modules/jobs/dto/job-application.dto.ts
+++ b/src/modules/jobs/dto/job-application.dto.ts
@@ -1,6 +1,6 @@
 import { ApiProperty } from '@nestjs/swagger';
-import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
-
+import { IsEmail, IsNotEmpty, IsString, IsOptional } from 'class-validator';
+import { Express } from 'express';
 export class JobApplicationDto {
   @IsNotEmpty()
   @IsString()
@@ -16,8 +16,9 @@ export class JobApplicationDto {
   })
   email: string;
 
-  @IsNotEmpty()
-  resume: string;
+  @IsOptional()
+  @ApiProperty({ type: 'string', format: 'binary', required: false })
+  resume: Express.Multer.File;
 
   @IsNotEmpty()
   @IsString()
diff --git a/src/modules/jobs/jobs.controller.ts b/src/modules/jobs/jobs.controller.ts
index 0185af65..1391fbe1 100644
--- a/src/modules/jobs/jobs.controller.ts
+++ b/src/modules/jobs/jobs.controller.ts
@@ -11,8 +11,12 @@ import {
   ValidationPipe,
   ParseUUIDPipe,
   Patch,
+  UseInterceptors,
+  UploadedFile,
+  BadRequestException,
 } from '@nestjs/common';
 import {
+  ApiConsumes,
   ApiBadRequestResponse,
   ApiBearerAuth,
   ApiBody,
@@ -25,6 +29,7 @@ import {
   ApiUnprocessableEntityResponse,
   ApiParam,
 } from '@nestjs/swagger';
+import { FileInterceptor } from '@nestjs/platform-express';
 import { skipAuth } from '@shared/helpers/skipAuth';
 import { JobApplicationErrorDto } from './dto/job-application-error.dto';
 import { JobApplicationResponseDto } from './dto/job-application-response.dto';
@@ -55,11 +60,20 @@ export class JobsController {
   @ApiUnprocessableEntityResponse({
     description: 'Job application deadline passed',
   })
+  @UseInterceptors(FileInterceptor('resume')) // Handles file uploads
+  @ApiConsumes('multipart/form-data')
   @ApiBadRequestResponse({ description: 'Invalid request body', type: JobApplicationErrorDto })
   @ApiInternalServerErrorResponse({ description: 'Internal server error', type: JobApplicationErrorDto })
   @Post('/:id/applications')
-  async applyForJob(@Param('id') id: string, @Body() jobApplicationDto: JobApplicationDto) {
-    return this.jobService.applyForJob(id, jobApplicationDto);
+  async applyForJob(
+    @Param('id') id: string,
+    @Body() jobApplicationDto: JobApplicationDto,
+    @UploadedFile() resume: Express.Multer.File
+  ) {
+    if (!resume) {
+      throw new BadRequestException('Resume file is required');
+    }
+    return this.jobService.applyForJob(id, jobApplicationDto, resume);
   }
 
   @UseGuards(AuthGuard)
diff --git a/src/modules/jobs/jobs.module.ts b/src/modules/jobs/jobs.module.ts
index 02560008..9f09f307 100644
--- a/src/modules/jobs/jobs.module.ts
+++ b/src/modules/jobs/jobs.module.ts
@@ -13,13 +13,13 @@ import { UserModule } from '@modules/user/user.module';
 import { JobOwnerGuard } from '@guards/job-owner.guard';
 import { AuthGuard } from '@guards/auth.guard';
 import { SuperAdminGuard } from '@guards/super-admin.guard';
-
+import { S3Service } from '@modules/s3/s3.service';
 @Module({
   imports: [
     TypeOrmModule.forFeature([Job, User, JobApplication, Organisation, OrganisationUserRole, Profile, Role]),
     UserModule,
   ],
-  providers: [JobsService, JobOwnerGuard, AuthGuard, SuperAdminGuard],
+  providers: [JobsService, JobOwnerGuard, AuthGuard, SuperAdminGuard, S3Service],
   controllers: [JobsController],
 })
 export class JobsModule {}
diff --git a/src/modules/jobs/jobs.service.ts b/src/modules/jobs/jobs.service.ts
index 50c93025..96dbc1db 100644
--- a/src/modules/jobs/jobs.service.ts
+++ b/src/modules/jobs/jobs.service.ts
@@ -14,7 +14,7 @@ import { User } from '@modules/user/entities/user.entity';
 import { CustomHttpException } from '@shared/helpers/custom-http-filter';
 import { pick } from '@shared/helpers/pick';
 import { UpdateJobDto } from './dto/update-job.dto';
-
+import { S3Service } from '@modules/s3/s3.service';
 @Injectable()
 export class JobsService {
   constructor(
@@ -23,10 +23,15 @@ export class JobsService {
     @InjectRepository(Job)
     private readonly jobRepository: Repository<Job>,
     @InjectRepository(JobApplication)
-    private readonly jobApplicationRepository: Repository<JobApplication>
+    private readonly jobApplicationRepository: Repository<JobApplication>,
+    private readonly s3Service: S3Service
   ) {}
 
-  async applyForJob(jobId: string, jobApplicationDto: JobApplicationDto): Promise<JobApplicationResponseDto> {
+  async applyForJob(
+    jobId: string,
+    jobApplicationDto: JobApplicationDto,
+    resume: Express.Multer.File
+  ): Promise<JobApplicationResponseDto> {
     const job: FindJobResponseDto = await this.getJob(jobId);
 
     const { is_deleted, deadline } = job.data;
@@ -39,11 +44,9 @@ export class JobsService {
       throw new CustomHttpException(SYS_MSG.DEADLINE_PASSED, HttpStatus.UNPROCESSABLE_ENTITY);
     }
 
-    const { resume, applicant_name, ...others } = jobApplicationDto;
-
-    // TODO: Upload resume to the cloud and grab URL
+    const { applicant_name, ...others } = jobApplicationDto;
 
-    const resumeUrl = `https://example.com/${applicant_name.split(' ').join('_')}.pdf`;
+    const resumeUrl = await this.s3Service.uploadFile(resume, 'resumes');
 
     const createJobApplication = this.jobApplicationRepository.create({
       ...others,
diff --git a/src/modules/languages/languages.controller.ts b/src/modules/languages/languages.controller.ts
index 7a45f265..f3471d2a 100644
--- a/src/modules/languages/languages.controller.ts
+++ b/src/modules/languages/languages.controller.ts
@@ -1,55 +1,78 @@
-import { Controller, Post, Body, Get, Patch, Param, Res, Request, Response } from '@nestjs/common';
+import { Controller, Post, Body, Get, Patch, Param, Res, Request, Response, UseGuards } from '@nestjs/common';
 import { ApiTags, ApiOperation, ApiResponse, ApiBody, ApiBearerAuth } from '@nestjs/swagger';
 import { CreateLanguageDto, UpdateLanguageDto } from './dto/create-language.dto';
 import { LanguagesService } from './languages.service';
+import { AuthGuard } from '@guards/auth.guard';
+import { NotFoundException, UnauthorizedException } from '@nestjs/common';
 
 @ApiTags('Languages')
 @Controller('languages')
 export class LanguagesController {
-  constructor(private readonly languagesService: LanguagesService) {}
-
-  @Post()
-  @ApiBearerAuth()
-  @ApiOperation({ summary: 'Create a new language' })
-  @ApiBody({ type: CreateLanguageDto })
-  @ApiResponse({ status: 201, description: 'Language successfully created.' })
-  @ApiResponse({ status: 409, description: 'Language already exists.' })
-  @ApiResponse({ status: 401, description: 'Unauthorized' })
-  async createLanguage(@Body() createLanguageDto: CreateLanguageDto) {
-    return this.languagesService.createLanguage(createLanguageDto);
-  }
-
-  @Get()
-  @ApiBearerAuth()
-  @ApiOperation({ summary: 'Get all supported languages' })
-  @ApiResponse({ status: 200, description: 'List of supported languages.' })
-  @ApiResponse({ status: 401, description: 'Unauthorized' })
-  async getLanguages(@Response() response): Promise<any> {
-    const result = await this.languagesService.getSupportedLanguages();
-    return response.status(result.status_code).json(result);
-  }
-
-  @Get(':id')
-  @ApiBearerAuth()
-  @ApiOperation({ summary: 'Get all languages from a userId' })
-  @ApiResponse({ status: 200, description: 'List of  languages by the user.' })
-  @ApiResponse({ status: 401, description: 'Unauthorized' })
-  @ApiResponse({ status: 400, description: 'Invalid UserId' })
-  @ApiResponse({ status: 404, description: 'No languages found for the specified user' })
-  @ApiResponse({ status: 500, description: 'Internal server error' })
-  async getLanguagesByUserId(@Param('id') id: string, @Request() req, @Response() response): Promise<any> {
-    const result = await this.languagesService.getLanguagesById(id, req.user);
-    return response.status(result.status_code).json(result);
-  }
-
-  @Patch(':id')
-  @ApiBearerAuth()
-  @ApiOperation({ summary: 'Update a language' })
-  @ApiBody({ type: UpdateLanguageDto })
-  @ApiResponse({ status: 200, description: 'Language successfully updated.' })
-  @ApiResponse({ status: 404, description: 'Language not found.' })
-  @ApiResponse({ status: 401, description: 'Unauthorized' })
-  async updateLanguage(@Param('id') id: string, @Body() updateLanguageDto: UpdateLanguageDto) {
-    return this.languagesService.updateLanguage(id, updateLanguageDto);
-  }
+    constructor(private readonly languagesService: LanguagesService) { }
+
+    @Post()
+    @ApiBearerAuth()
+    @ApiOperation({ summary: 'Create a new language' })
+    @ApiBody({ type: CreateLanguageDto })
+    @ApiResponse({ status: 201, description: 'Language successfully created.' })
+    @ApiResponse({ status: 409, description: 'Language already exists.' })
+    @ApiResponse({ status: 401, description: 'Unauthorized' })
+    async createLanguage(@Body() createLanguageDto: CreateLanguageDto) {
+        return this.languagesService.createLanguage(createLanguageDto);
+    }
+
+    @Get()
+    @ApiBearerAuth()
+    @ApiOperation({ summary: 'Get all supported languages' })
+    @ApiResponse({ status: 200, description: 'List of supported languages.' })
+    @ApiResponse({ status: 401, description: 'Unauthorized' })
+    async getLanguages(@Response() response): Promise<any> {
+        const result = await this.languagesService.getSupportedLanguages();
+        return response.status(result.status_code).json(result);
+    }
+
+    @Get(':id')
+    @ApiBearerAuth()
+    @ApiOperation({ summary: 'Get all languages from a userId' })
+    @ApiResponse({ status: 200, description: 'List of  languages by the user.' })
+    @ApiResponse({ status: 401, description: 'Unauthorized' })
+    @ApiResponse({ status: 400, description: 'Invalid UserId' })
+    @ApiResponse({ status: 404, description: 'No languages found for the specified user' })
+    @ApiResponse({ status: 500, description: 'Internal server error' })
+    async getLanguagesByUserId(@Param('id') id: string, @Request() req, @Response() response): Promise<any> {
+        const result = await this.languagesService.getLanguagesById(id, req.user);
+        return response.status(result.status_code).json(result);
+    }
+
+    @Patch(':id')
+    @ApiBearerAuth()
+    @ApiOperation({ summary: 'Update a language' })
+    @ApiBody({ type: UpdateLanguageDto })
+    @ApiResponse({ status: 200, description: 'Language successfully updated.' })
+    @ApiResponse({ status: 404, description: 'Language not found.' })
+    @ApiResponse({ status: 401, description: 'Unauthorized' })
+    async updateLanguage(@Param('id') id: string, @Body() updateLanguageDto: UpdateLanguageDto) {
+        return this.languagesService.updateLanguage(id, updateLanguageDto);
+    }
+
+    @ApiBearerAuth()
+    @UseGuards(AuthGuard)
+    @ApiOperation({ summary: 'Get all languages by User ID' })
+    @ApiResponse({ status: 200, description: 'Returns languages associated with user.' })
+    @ApiResponse({ status: 404, description: 'User not found or no languages available.' })
+    @ApiResponse({ status: 401, description: 'Unauthorized access.' })
+    @Get(':id/languages')
+    async getUserLanguages(@Param('id') userId: string) {
+        if (!userId) {
+            throw new UnauthorizedException('Invalid user ID.');
+        }
+
+        const languages = await this.languagesService.getUserLanguages(userId);
+
+        if (!languages.length) {
+            throw new NotFoundException('No languages found for this user.');
+        }
+
+        return { status: 200, data: languages };
+    }
 }
diff --git a/src/modules/languages/languages.service.ts b/src/modules/languages/languages.service.ts
index bd3a682f..0155f2aa 100644
--- a/src/modules/languages/languages.service.ts
+++ b/src/modules/languages/languages.service.ts
@@ -1,12 +1,12 @@
 import {
-  BadRequestException,
-  ConflictException,
-  ForbiddenException,
-  HttpStatus,
-  Injectable,
-  InternalServerErrorException,
-  Logger,
-  NotFoundException,
+    BadRequestException,
+    ConflictException,
+    ForbiddenException,
+    HttpStatus,
+    Injectable,
+    InternalServerErrorException,
+    Logger,
+    NotFoundException,
 } from '@nestjs/common';
 import { InjectRepository } from '@nestjs/typeorm';
 import { Repository } from 'typeorm';
@@ -17,145 +17,153 @@ import { isUUID } from 'class-validator';
 
 @Injectable()
 export class LanguagesService {
-  constructor(
-    @InjectRepository(Language)
-    private readonly languageRepository: Repository<Language>
-  ) {}
+    constructor(
+        @InjectRepository(Language)
+        private readonly languageRepository: Repository<Language>,
+        @InjectRepository(User)
+        private readonly userRepository: Repository<User>,
+    ) { }
 
-  async createLanguage(createLanguageDto: CreateLanguageDto): Promise<any> {
-    try {
-      const languageExists = await this.languageRepository.findOne({
-        where: { language: createLanguageDto.language },
-      });
+    async createLanguage(createLanguageDto: CreateLanguageDto): Promise<any> {
+        try {
+            const languageExists = await this.languageRepository.findOne({
+                where: { language: createLanguageDto.language },
+            });
 
-      if (languageExists) {
-        throw new ConflictException({
-          status_code: HttpStatus.CONFLICT,
-          message: 'Language already exists',
-        });
-      }
+            if (languageExists) {
+                throw new ConflictException({
+                    status_code: HttpStatus.CONFLICT,
+                    message: 'Language already exists',
+                });
+            }
 
-      const newLanguage = this.languageRepository.create(createLanguageDto);
-      await this.languageRepository.save(newLanguage);
+            const newLanguage = this.languageRepository.create(createLanguageDto);
+            await this.languageRepository.save(newLanguage);
 
-      return {
-        status_code: HttpStatus.CREATED,
-        message: 'Language Created Successfully',
-        language: newLanguage,
-      };
-    } catch (error) {
-      if (error instanceof ConflictException) {
-        throw error;
-      }
-      Logger.error('LanguagesServiceError ~ createLanguage ~', error);
-      throw new InternalServerErrorException({
-        message: 'An error occurred',
-        status_code: HttpStatus.INTERNAL_SERVER_ERROR,
-      });
+            return {
+                status_code: HttpStatus.CREATED,
+                message: 'Language Created Successfully',
+                language: newLanguage,
+            };
+        } catch (error) {
+            if (error instanceof ConflictException) {
+                throw error;
+            }
+            Logger.error('LanguagesServiceError ~ createLanguage ~', error);
+            throw new InternalServerErrorException({
+                message: 'An error occurred',
+                status_code: HttpStatus.INTERNAL_SERVER_ERROR,
+            });
+        }
     }
-  }
 
-  async getSupportedLanguages(): Promise<any> {
-    try {
-      const languages = await this.languageRepository.find();
-      const formattedLanguages = languages.map(language => ({
-        language: `${language.language} (${language.description})`,
-      }));
-      return {
-        status_code: HttpStatus.OK,
-        message: 'Languages fetched successfully',
-        languages: formattedLanguages,
-      };
-    } catch (error) {
-      Logger.error('LanguagesServiceError ~ fetchLanguages ~', error);
-      throw new InternalServerErrorException({
-        message: 'An error occurred',
-        status_code: HttpStatus.INTERNAL_SERVER_ERROR,
-      });
+    async getSupportedLanguages(): Promise<any> {
+        try {
+            const languages = await this.languageRepository.find();
+            const formattedLanguages = languages.map(language => ({
+                language: `${language.language} (${language.description})`,
+            }));
+            return {
+                status_code: HttpStatus.OK,
+                message: 'Languages fetched successfully',
+                languages: formattedLanguages,
+            };
+        } catch (error) {
+            Logger.error('LanguagesServiceError ~ fetchLanguages ~', error);
+            throw new InternalServerErrorException({
+                message: 'An error occurred',
+                status_code: HttpStatus.INTERNAL_SERVER_ERROR,
+            });
+        }
     }
-  }
 
-  async updateLanguage(id: string, updateLanguageDto: UpdateLanguageDto): Promise<any> {
-    try {
-      const language = await this.languageRepository.findOne({ where: { id } });
-      if (!language) {
-        throw new NotFoundException({
-          status_code: HttpStatus.NOT_FOUND,
-          message: 'Language not found',
-        });
-      }
+    async updateLanguage(id: string, updateLanguageDto: UpdateLanguageDto): Promise<any> {
+        try {
+            const language = await this.languageRepository.findOne({ where: { id } });
+            if (!language) {
+                throw new NotFoundException({
+                    status_code: HttpStatus.NOT_FOUND,
+                    message: 'Language not found',
+                });
+            }
 
-      Object.assign(language, updateLanguageDto);
-      await this.languageRepository.save(language);
+            Object.assign(language, updateLanguageDto);
+            await this.languageRepository.save(language);
 
-      return {
-        status_code: HttpStatus.OK,
-        message: 'Language successfully updated',
-        language,
-      };
-    } catch (error) {
-      if (error instanceof NotFoundException) {
-        throw error;
-      }
-      Logger.error('LanguagesServiceError ~ updateLanguage ~', error);
-      throw new InternalServerErrorException({
-        message: 'An error occurred',
-        status_code: HttpStatus.INTERNAL_SERVER_ERROR,
-      });
+            return {
+                status_code: HttpStatus.OK,
+                message: 'Language successfully updated',
+                language,
+            };
+        } catch (error) {
+            if (error instanceof NotFoundException) {
+                throw error;
+            }
+            Logger.error('LanguagesServiceError ~ updateLanguage ~', error);
+            throw new InternalServerErrorException({
+                message: 'An error occurred',
+                status_code: HttpStatus.INTERNAL_SERVER_ERROR,
+            });
+        }
     }
-  }
-  async getLanguagesById(userId: string, user: User): Promise<any> {
-    try {
-      if (!isUUID(userId)) {
-        throw new BadRequestException('Invalid user Id');
-      }
+    async getLanguagesById(userId: string, user: User): Promise<any> {
+        try {
+            if (!isUUID(userId)) {
+                throw new BadRequestException('Invalid user Id');
+            }
 
-      if (user.id !== userId) {
-        throw new ForbiddenException({
-          status_code: HttpStatus.FORBIDDEN,
-          message: 'You are not authorized to access this resource',
-        });
-      }
+            if (user.id !== userId) {
+                throw new ForbiddenException({
+                    status_code: HttpStatus.FORBIDDEN,
+                    message: 'You are not authorized to access this resource',
+                });
+            }
 
-      const languages = await this.languageRepository
-        .createQueryBuilder('language')
-        .innerJoin('language.users', 'user')
-        .where('user.id = :userId', { userId })
-        .getMany();
+            const languages = await this.languageRepository
+                .createQueryBuilder('language')
+                .innerJoin('language.users', 'user')
+                .where('user.id = :userId', { userId })
+                .getMany();
 
-      if (!languages || languages.length === 0) {
-        throw new NotFoundException({
-          status_code: HttpStatus.NOT_FOUND,
-          message: 'Languages associated with this user not found',
-        });
-      }
+            if (!languages || languages.length === 0) {
+                throw new NotFoundException({
+                    status_code: HttpStatus.NOT_FOUND,
+                    message: 'Languages associated with this user not found',
+                });
+            }
 
-      const formattedLanguages = languages.map(language => ({
-        id: language.id,
-        language: language.language,
-        description: language.description,
-        code: language.code,
-      }));
+            const formattedLanguages = languages.map(language => ({
+                id: language.id,
+                language: language.language,
+                description: language.description,
+                code: language.code,
+            }));
 
-      return {
-        status: 'OK',
-        status_code: HttpStatus.OK,
-        message: 'Languages fetched successfully',
-        data: formattedLanguages,
-      };
-    } catch (error) {
-      if (
-        error instanceof NotFoundException ||
-        error instanceof ForbiddenException ||
-        error instanceof BadRequestException
-      ) {
-        throw error;
-      }
-      Logger.error('LanguagesServiceError ~ getLanguagesById ~', error);
-      throw new InternalServerErrorException({
-        message: 'An error occurred',
-        status_code: HttpStatus.INTERNAL_SERVER_ERROR,
-      });
+            return {
+                status: 'OK',
+                status_code: HttpStatus.OK,
+                message: 'Languages fetched successfully',
+                data: formattedLanguages,
+            };
+        } catch (error) {
+            if (
+                error instanceof NotFoundException ||
+                error instanceof ForbiddenException ||
+                error instanceof BadRequestException
+            ) {
+                throw error;
+            }
+            Logger.error('LanguagesServiceError ~ getLanguagesById ~', error);
+            throw new InternalServerErrorException({
+                message: 'An error occurred',
+                status_code: HttpStatus.INTERNAL_SERVER_ERROR,
+            });
+        }
+    }
+
+    async getUserLanguages(userId: string): Promise<Language[]> {
+        const user = await this.userRepository.findOne({ where: { id: userId }, relations: ['languages'] });
+        if (!user) throw new NotFoundException('User not found.');
+        return user.languages || [];
     }
-  }
 }
diff --git a/src/modules/newsletter-subscription/dto/resubscribe-newsletter.dto.ts b/src/modules/newsletter-subscription/dto/resubscribe-newsletter.dto.ts
new file mode 100644
index 00000000..5ea5620a
--- /dev/null
+++ b/src/modules/newsletter-subscription/dto/resubscribe-newsletter.dto.ts
@@ -0,0 +1,18 @@
+import { IsEmail, IsNotEmpty, IsOptional, IsUUID } from 'class-validator';
+
+export class ResubscribeNewsletterDto {
+  @IsOptional()
+  @IsUUID()
+  id?: string;
+
+  @IsOptional()
+  @IsEmail()
+  email?: string;
+
+  @IsNotEmpty()
+  validateUserIdentifier?() {
+    if (!this.id && !this.email) {
+      throw new Error('Either userId or email must be provided.');
+    }
+  }
+}
diff --git a/src/modules/newsletter-subscription/entities/newsletter-subscription.entity.ts b/src/modules/newsletter-subscription/entities/newsletter-subscription.entity.ts
index 3ec042d9..4c07fa55 100644
--- a/src/modules/newsletter-subscription/entities/newsletter-subscription.entity.ts
+++ b/src/modules/newsletter-subscription/entities/newsletter-subscription.entity.ts
@@ -1,14 +1,26 @@
 import { AbstractBaseEntity } from '../../../entities/base.entity';
-import { Column, DeleteDateColumn, Entity } from 'typeorm';
+import { Column, DeleteDateColumn, Entity, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm';
 
 @Entity('newsletters')
 export class NewsletterSubscription extends AbstractBaseEntity {
-  @Column({ unique: true })
+  @PrimaryGeneratedColumn('uuid')
+  id: string;
+
+  @Column({ unique: true, nullable: true })
   email: string;
 
+  @CreateDateColumn()
+  createdAt: Date;
+
+  @UpdateDateColumn()
+  updatedAt: Date;
+
   @DeleteDateColumn()
   deletedAt: Date;
 
   @Column({ default: false })
   isUnsubscribed: boolean;
+
+  @Column({ default: 'inactive' }) // inactive, active, unsubscribed
+  status: string;
 }
diff --git a/src/modules/newsletter-subscription/newsletter-subscription.controller.ts b/src/modules/newsletter-subscription/newsletter-subscription.controller.ts
index 2cfcb2f6..5a56ceba 100644
--- a/src/modules/newsletter-subscription/newsletter-subscription.controller.ts
+++ b/src/modules/newsletter-subscription/newsletter-subscription.controller.ts
@@ -6,6 +6,7 @@ import { ApiBearerAuth, ApiOperation, ApiParam, ApiResponse, ApiTags } from '@ne
 import { NewsletterSubscriptionResponseDto } from './dto/newsletter-subscription.response.dto';
 import { SuperAdminGuard } from '@guards/super-admin.guard';
 import { UnsubscribeNewsletterDto } from './dto/unsubscribe-newsletter.dto';
+import { ResubscribeNewsletterDto } from './dto/resubscribe-newsletter.dto';
 
 @ApiTags('Newsletter Subscription')
 @Controller('newsletter-subscription')
@@ -21,6 +22,17 @@ export class NewsletterSubscriptionController {
     return this.newsletterSubscriptionService.newsletterSubscription(createNewsletterDto);
   }
 
+  @skipAuth()
+  @Post('resubscribe')
+  @HttpCode(HttpStatus.OK)
+  @ApiOperation({ summary: 'Resubscribe to the newsletter' })
+  @ApiResponse({ status: 200, description: 'User successfully resubscribed.' })
+  @ApiResponse({ status: 400, description: 'User is already subscribed.' })
+  @ApiResponse({ status: 404, description: 'User not found or not unsubscribed.' })
+  resubscribe(@Body() resubscribeDto: ResubscribeNewsletterDto) {
+    return this.newsletterSubscriptionService.resubscribe(resubscribeDto);
+  }
+
   @ApiBearerAuth()
   @UseGuards(SuperAdminGuard)
   @Get()
diff --git a/src/modules/newsletter-subscription/newsletter-subscription.service.ts b/src/modules/newsletter-subscription/newsletter-subscription.service.ts
index cf44397d..982486da 100644
--- a/src/modules/newsletter-subscription/newsletter-subscription.service.ts
+++ b/src/modules/newsletter-subscription/newsletter-subscription.service.ts
@@ -1,9 +1,10 @@
-import { Injectable, NotFoundException } from '@nestjs/common';
+import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
 import { InjectRepository } from '@nestjs/typeorm';
 import { IsNull, Not, Repository } from 'typeorm';
 import { CreateNewsletterSubscriptionDto } from './dto/create-newsletter-subscription.dto';
 import { NewsletterSubscriptionResponseDto } from './dto/newsletter-subscription.response.dto';
 import { NewsletterSubscription } from './entities/newsletter-subscription.entity';
+import { ResubscribeNewsletterDto } from './dto/resubscribe-newsletter.dto';
 
 @Injectable()
 export class NewsletterSubscriptionService {
@@ -85,4 +86,27 @@ export class NewsletterSubscriptionService {
 
     return { message: `Email ${email} has been unsubscribed successfully` };
   }
+  async resubscribe(dto: ResubscribeNewsletterDto): Promise<{ message: string }> {
+    const { id, email } = dto;
+
+    // Find the subscription record
+    const userSubscription = await this.newsletterSubscriptionRepository.findOne({
+      where: [{ id }, { email }],
+    });
+
+    if (!userSubscription) {
+      throw new NotFoundException('User not found or not unsubscribed.');
+    }
+
+    // Check if the user is already subscribed
+    if (userSubscription.status === 'active') {
+      throw new BadRequestException('User is already subscribed.');
+    }
+
+    // Update the subscription status
+    userSubscription.status = 'active';
+    await this.newsletterSubscriptionRepository.save(userSubscription);
+
+    return { message: 'Successfully resubscribed to the newsletter.' };
+  }
 }
diff --git a/src/modules/products/current-user.decorator.ts b/src/modules/products/current-user.decorator.ts
new file mode 100644
index 00000000..2cb9757b
--- /dev/null
+++ b/src/modules/products/current-user.decorator.ts
@@ -0,0 +1,6 @@
+import { createParamDecorator, ExecutionContext } from '@nestjs/common';
+
+export const CurrentUser = createParamDecorator((data: unknown, ctx: ExecutionContext) => {
+  const request = ctx.switchToHttp().getRequest();
+  return request.user; // This works if AuthGuard attaches user object to request
+});
diff --git a/src/modules/products/dto/create-review.dto.ts b/src/modules/products/dto/create-review.dto.ts
new file mode 100644
index 00000000..2e31569b
--- /dev/null
+++ b/src/modules/products/dto/create-review.dto.ts
@@ -0,0 +1,11 @@
+import { IsInt, IsString, Max, Min } from 'class-validator';
+
+export class CreateReviewDto {
+  @IsInt()
+  @Min(1)
+  @Max(5)
+  rating: number;
+
+  @IsString()
+  review: string;
+}
diff --git a/src/modules/products/dto/product-response.dto.ts b/src/modules/products/dto/product-response.dto.ts
new file mode 100644
index 00000000..287ef3c1
--- /dev/null
+++ b/src/modules/products/dto/product-response.dto.ts
@@ -0,0 +1,20 @@
+import { ApiProperty } from '@nestjs/swagger';
+
+export class ProductResponseDto {
+  id: string;
+  name: string;
+  description: string;
+  averageRating: number;
+  totalReviews: number;
+
+  @ApiProperty({ type: () => [ReviewSummaryDto] })
+  recentReviews: ReviewSummaryDto[];
+}
+
+// Define ReviewSummaryDto **inside the same file**, avoiding unnecessary imports
+class ReviewSummaryDto {
+  rating: number;
+  review: string;
+  createdBy: string;
+  createdAt: Date;
+}
diff --git a/src/modules/products/entities/product.entity.ts b/src/modules/products/entities/product.entity.ts
index 945aca9a..addef46c 100644
--- a/src/modules/products/entities/product.entity.ts
+++ b/src/modules/products/entities/product.entity.ts
@@ -1,9 +1,10 @@
-import { AbstractBaseEntity } from '../../../entities/base.entity';
+import { AbstractBaseEntity } from '@entities/base.entity';
 import { Column, DeleteDateColumn, Entity, ManyToOne, OneToMany } from 'typeorm';
-import { Comment } from '../../../modules/comments/entities/comments.entity';
-import { Organisation } from '../../../modules/organisations/entities/organisations.entity';
+import { Comment } from '@modules/comments/entities/comments.entity';
+import { Organisation } from '@modules/organisations/entities/organisations.entity';
 import { Cart } from '../../dashboard/entities/cart.entity';
 import { OrderItem } from '../../dashboard/entities/order-items.entity';
+import { Review } from './review.entity';
 
 export enum StockStatusType {
   IN_STOCK = 'in stock',
@@ -40,6 +41,9 @@ export class Product extends AbstractBaseEntity {
   @Column({ type: 'int', nullable: false, default: 0 })
   quantity: number;
 
+  @OneToMany(() => Review, review => review.product)
+  reviews: Review[];
+
   @Column({
     type: 'enum',
     enum: ProductSizeType,
@@ -60,7 +64,7 @@ export class Product extends AbstractBaseEntity {
   @DeleteDateColumn()
   deletedAt?: Date;
 
-  @OneToMany(() => Comment, comment => comment.product)
+  @OneToMany(() => Comment, comment => comment.product, { onUpdate: 'CASCADE' })
   comments?: Comment[];
 
   @OneToMany(() => OrderItem, orderItem => orderItem.product)
diff --git a/src/modules/products/entities/review.entity.ts b/src/modules/products/entities/review.entity.ts
new file mode 100644
index 00000000..3d228954
--- /dev/null
+++ b/src/modules/products/entities/review.entity.ts
@@ -0,0 +1,26 @@
+import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, CreateDateColumn, UpdateDateColumn } from 'typeorm';
+import { Product } from './product.entity';
+
+@Entity()
+export class Review {
+  @PrimaryGeneratedColumn('uuid')
+  id: string;
+
+  @Column({ type: 'int', width: 1 })
+  rating: number;
+
+  @Column({ type: 'text' })
+  review: string;
+
+  @Column()
+  createdBy: string; // This will be user ID
+
+  @ManyToOne(() => Product, product => product.reviews, { onDelete: 'CASCADE' })
+  product: Product;
+
+  @CreateDateColumn()
+  createdAt: Date;
+
+  @UpdateDateColumn()
+  updatedAt: Date;
+}
diff --git a/src/modules/products/products.controller.ts b/src/modules/products/products.controller.ts
index cd2741d2..f03ce781 100644
--- a/src/modules/products/products.controller.ts
+++ b/src/modules/products/products.controller.ts
@@ -18,9 +18,13 @@ import { ProductsService } from './products.service';
 import { UpdateProductDTO } from './dto/update-product.dto';
 import { isUUID } from 'class-validator';
 import { GetTotalProductsResponseDto } from './dto/get-total-products.dto';
+import { CreateReviewDto } from './dto/create-review.dto';
+import { User } from '../user/entities/user.entity';
+import { AuthGuard } from '../../guards/auth.guard';
+import { CurrentUser } from './current-user.decorator';
 import { skipAuth } from '@shared/helpers/skipAuth';
 import { OwnershipGuard } from '@guards/authorization.guard';
-import { AddCommentDto } from '@modules/comments/dto/add-comment.dto';
+import { AddCommentDto } from '@modules/comments/dtos/add-comment.dto';
 import { INVALID_ORG_ID, INVALID_PRODUCT_ID } from '@shared/constants/SystemMessages';
 import { CustomHttpException } from '@shared/helpers/custom-http-filter';
 import { SuperAdminGuard } from '@guards/super-admin.guard';
@@ -153,10 +157,9 @@ export class ProductsController {
 
   @ApiBearerAuth()
   @UseGuards(OwnershipGuard)
-  @Post('organisations/:productId/comments')
-  @ApiBearerAuth()
+  @Post('organisations/:orgId/products/:productId/comments')
   @ApiOperation({ summary: 'Creates a comment for a product' })
-  @ApiParam({ name: 'id', description: 'organisation ID', example: '870ccb14-d6b0-4a50-b459-9895af803i89' })
+  @ApiParam({ name: 'orgId', description: 'organisation ID', example: '870ccb14-d6b0-4a50-b459-9895af803i89' })
   @ApiParam({ name: 'productId', description: 'product ID', example: '126ccb14-d6b0-4a50-b459-9895af803h6y' })
   @ApiBody({ type: AddCommentDto, description: 'Comment to be added' })
   @ApiResponse({ status: 201, description: 'Comment added successfully' })
@@ -170,9 +173,9 @@ export class ProductsController {
 
   @ApiBearerAuth()
   @UseGuards(OwnershipGuard)
-  @Get('organisations/:productId/stock')
+  @Get('organisations/:orgId/products/:productId/stock')
   @ApiOperation({ summary: 'Gets a product stock details by id' })
-  @ApiParam({ name: 'id', description: 'Organization ID', example: '12345' })
+  @ApiParam({ name: 'orgId', description: 'Organization ID', example: '12345' })
   @ApiParam({ name: 'productId', description: 'Product ID' })
   @ApiResponse({ status: 200, description: 'Product stock retrieved successfully' })
   @ApiResponse({ status: 404, description: 'Product not found' })
@@ -180,4 +183,30 @@ export class ProductsController {
   async getProductStock(@Param('productId') productId: string) {
     return this.productsService.getProductStock(productId);
   }
+
+  @ApiBearerAuth()
+  @UseGuards(AuthGuard)
+  @Post(':productId/review')
+  @ApiOperation({ summary: 'Submit or Update Product Review' })
+  async submitReview(
+    @Param('productId') productId: string,
+    @CurrentUser() user: User, // Use the correct custom decorator
+    @Body() dto: CreateReviewDto // Consistent DTO name
+  ) {
+    return this.productsService.submitReview(user.id, productId, dto);
+  }
+
+  @Delete(':productId/reviews')
+  @UseGuards(AuthGuard)
+  @ApiBearerAuth()
+  @ApiOperation({ summary: 'Delete Product Review' })
+  async deleteReview(@Param('productId') productId: string, @CurrentUser() user: User) {
+    return this.productsService.deleteReview(user.id, productId);
+  }
+
+  @Get('view/reviews/:productId')
+  @ApiOperation({ summary: 'Get Product Details (with Reviews)' })
+  async getProductDetails(@Param('productId') productId: string) {
+    return this.productsService.getProductDetails(productId);
+  }
 }
diff --git a/src/modules/products/products.module.ts b/src/modules/products/products.module.ts
index 26251ef3..e16f7d21 100644
--- a/src/modules/products/products.module.ts
+++ b/src/modules/products/products.module.ts
@@ -3,6 +3,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
 import { Product } from './entities/product.entity';
 import { ProductsController } from './products.controller';
 import { ProductsService } from './products.service';
+import { Review } from './entities/review.entity';
 import { UserModule } from '@modules/user/user.module';
 import { Cart } from '@modules/dashboard/entities/cart.entity';
 import { OrderItem } from '@modules/dashboard/entities/order-items.entity';
@@ -13,7 +14,6 @@ import { ProductVariant } from './entities/product-variant.entity';
 import { Organisation } from '@modules/organisations/entities/organisations.entity';
 import { Order } from '@modules/dashboard/entities/order.entity';
 import { Comment } from '@modules/comments/entities/comments.entity';
-
 @Module({
   imports: [
     TypeOrmModule.forFeature([
@@ -27,6 +27,7 @@ import { Comment } from '@modules/comments/entities/comments.entity';
       Order,
       OrderItem,
       Cart,
+      Review,
     ]),
     UserModule,
   ],
diff --git a/src/modules/products/products.service.ts b/src/modules/products/products.service.ts
index e41dd2e5..0482ea83 100644
--- a/src/modules/products/products.service.ts
+++ b/src/modules/products/products.service.ts
@@ -1,23 +1,19 @@
-import {
-  ForbiddenException,
-  HttpStatus,
-  Injectable,
-  InternalServerErrorException,
-  Logger,
-  NotFoundException,
-} from '@nestjs/common';
+import { HttpStatus, Injectable, InternalServerErrorException, Logger, NotFoundException } from '@nestjs/common';
 import { InjectRepository } from '@nestjs/typeorm';
 import { endOfMonth, startOfMonth, subMonths } from 'date-fns';
 import { Repository } from 'typeorm';
 import * as SYS_MSG from '@shared/constants/SystemMessages';
-import { AddCommentDto } from '../comments/dto/add-comment.dto';
 import { Comment } from '../comments/entities/comments.entity';
 import { Organisation } from '../organisations/entities/organisations.entity';
 import { User } from '../user/entities/user.entity';
 import { CreateProductRequestDto } from './dto/create-product.dto';
 import { UpdateProductDTO } from './dto/update-product.dto';
 import { Product, ProductSizeType, StockStatusType } from './entities/product.entity';
+import { CreateReviewDto } from './dto/create-review.dto';
+import { Review } from './entities/review.entity';
+import { ProductResponseDto } from './dto/product-response.dto';
 import { CustomHttpException } from '@shared/helpers/custom-http-filter';
+import { AddCommentDto } from '../comments/dtos/add-comment.dto';
 
 interface SearchCriteria {
   name?: string;
@@ -33,7 +29,8 @@ export class ProductsService {
     @InjectRepository(Product) private productRepository: Repository<Product>,
     @InjectRepository(Organisation) private organisationRepository: Repository<Organisation>,
     @InjectRepository(Comment) private commentRepository: Repository<Comment>,
-    @InjectRepository(User) private userRepository: Repository<User>
+    @InjectRepository(User) private userRepository: Repository<User>,
+    @InjectRepository(Review) private reviewRepository: Repository<Review> // Add this line
   ) {}
 
   async createProduct(id: string, dto: CreateProductRequestDto) {
@@ -58,32 +55,18 @@ export class ProductsService {
     newProduct.org = org;
     const statusCal = await this.calculateProductStatus(dto.quantity);
     newProduct.stock_status = statusCal;
-    newProduct.cost_price = 0.2 * dto.price - dto.price;
+    newProduct.cost_price = dto.price - 0.2 * dto.price;
     const product = await this.productRepository.save(newProduct);
-    if (!product || !newProduct)
-      throw new InternalServerErrorException({
-        status_code: 500,
-        status: 'Internal server error',
-        message: 'An unexpected error occurred. Please try again later.',
-      });
 
+    delete product.org;
     return {
-      status: 'success',
+      status_code: HttpStatus.CREATED,
       message: 'Product created successfully',
-      data: {
-        id: product.id,
-        name: product.name,
-        description: product.description,
-        price: product.price,
-        status: product.stock_status,
-        quantity: product.quantity,
-        created_at: product.created_at,
-        updated_at: product.updated_at,
-      },
+      data: product,
     };
   }
 
-  async getAllProducts({ page = 1, pageSize = 2 }: { page: number; pageSize: number }) {
+  async getAllProducts({ page = 1, pageSize = 10 }: { page: number; pageSize: number }) {
     const skip = (page - 1) * pageSize;
     const allProucts = await this.productRepository.find({ skip, take: pageSize });
     const totalProducts = await this.productRepository.count();
@@ -135,13 +118,12 @@ export class ProductsService {
         status_code: 422,
       });
 
-    const { name, category, minPrice, maxPrice } = criteria;
+    const { name, minPrice, maxPrice } = criteria;
     const query = this.productRepository.createQueryBuilder('product').where('product.orgId = :orgId', { orgId });
 
     if (name) {
       query.andWhere('product.name ILIKE :name', { name: `%${name}%` });
     }
-
     if (minPrice) {
       query.andWhere('product.price >= :minPrice', { minPrice });
     }
@@ -162,7 +144,7 @@ export class ProductsService {
     return {
       success: true,
       statusCode: 200,
-      products,
+      data: products,
     };
   }
 
@@ -172,26 +154,9 @@ export class ProductsService {
   }
 
   async updateProduct(id: string, productId: string, updateProductDto: UpdateProductDTO) {
-    const org = await this.organisationRepository.findOne({ where: { id } });
-    if (!org)
-      throw new InternalServerErrorException({
-        status: 'Unprocessable entity exception',
-        message: 'Invalid organisation credentials',
-        status_code: 422,
-      });
-    const product = await this.productRepository.findOne({ where: { id: productId }, relations: ['org'] });
+    const product = await this.productRepository.findOne({ where: { id: productId, org: { id } } });
     if (!product) {
-      throw new NotFoundException({
-        error: 'Product not found',
-        status_code: HttpStatus.NOT_FOUND,
-      });
-    }
-
-    if (product.org.id !== org.id) {
-      throw new ForbiddenException({
-        status: 'fail',
-        message: 'Not allowed to perform this action',
-      });
+      throw new NotFoundException('Invalid products credentials');
     }
 
     try {
@@ -249,7 +214,6 @@ export class ProductsService {
   }
 
   async addCommentToProduct(productId: string, commentDto: AddCommentDto, userId: string) {
-    const { comment } = commentDto;
     const product = await this.productRepository.findOne({ where: { id: productId } });
     const user = await this.userRepository.findOne({ where: { id: userId } });
 
@@ -257,7 +221,7 @@ export class ProductsService {
       throw new CustomHttpException(SYS_MSG.PRODUCT_NOT_FOUND, HttpStatus.NOT_FOUND);
     }
 
-    const productComment = this.commentRepository.create({ comment, product, user });
+    const productComment = this.commentRepository.create({ ...commentDto, product, user });
 
     const saveComment = await this.commentRepository.save(productComment);
 
@@ -317,10 +281,9 @@ export class ProductsService {
     const lastMonthEnds = this.getDateRange(lastMonth).monthEnds;
 
     const totalProductsThisMonth = await this.getTotalProductsForDateRange(monthStarts, monthEnds);
-
     const totalProductsLastMonth = await this.getTotalProductsForDateRange(lastMonthStarts, lastMonthEnds);
 
-    let percentageChange;
+    let percentageChange: string;
 
     if (totalProductsLastMonth === totalProductsThisMonth) {
       percentageChange =
@@ -340,4 +303,72 @@ export class ProductsService {
       },
     };
   }
+
+  async submitReview(userId: string, productId: string, dto: CreateReviewDto) {
+    const product = await this.productRepository.findOne({
+      where: { id: productId },
+      relations: ['reviews'],
+    });
+
+    if (!product) {
+      throw new NotFoundException('Product not found');
+    }
+
+    let review = product.reviews.find(r => r.createdBy === userId);
+    if (review) {
+      // Update existing review
+      review.rating = dto.rating;
+      review.review = dto.review;
+      review.updatedAt = new Date();
+    } else {
+      // Create new review
+      review = this.reviewRepository.create({ ...dto, createdBy: userId, product });
+    }
+
+    await this.reviewRepository.save(review);
+    return { message: 'Review saved successfully' };
+  }
+
+  async deleteReview(userId: string, productId: string) {
+    const review = await this.reviewRepository.findOne({ where: { product: { id: productId }, createdBy: userId } });
+    if (!review) {
+      throw new NotFoundException('Review not found');
+    }
+
+    await this.reviewRepository.remove(review);
+    return { message: 'Review deleted successfully' };
+  }
+
+  async getProductDetails(productId: string): Promise<ProductResponseDto> {
+    const product = await this.productRepository.findOne({
+      where: { id: productId },
+      relations: ['reviews'],
+    });
+
+    if (!product) {
+      throw new NotFoundException('Product not found');
+    }
+
+    const averageRating =
+      product.reviews.length > 0 ? product.reviews.reduce((acc, r) => acc + r.rating, 0) / product.reviews.length : 0;
+
+    const recentReviews = product.reviews
+      .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())
+      .slice(0, 5)
+      .map(r => ({
+        rating: r.rating,
+        review: r.review,
+        createdBy: r.createdBy,
+        createdAt: r.createdAt,
+      }));
+
+    return {
+      id: product.id,
+      name: product.name,
+      description: product.description,
+      averageRating,
+      totalReviews: product.reviews.length,
+      recentReviews,
+    };
+  }
 }
diff --git a/src/modules/s3/s3.service.ts b/src/modules/s3/s3.service.ts
new file mode 100644
index 00000000..327e4b2b
--- /dev/null
+++ b/src/modules/s3/s3.service.ts
@@ -0,0 +1,49 @@
+import { S3 } from 'aws-sdk';
+import { Injectable } from '@nestjs/common';
+import { ConfigService } from '@nestjs/config';
+import { CustomHttpException } from '@shared/helpers/custom-http-filter';
+import { HttpStatus } from '@nestjs/common';
+
+@Injectable()
+export class S3Service {
+  private s3: S3;
+  private bucketName: string;
+
+  constructor(private readonly configService: ConfigService) {
+    this.s3 = new S3({
+      accessKeyId: this.configService.get<string>('AWS_ACCESS_KEY'),
+      secretAccessKey: this.configService.get<string>('AWS_SECRET_ACCESS_KEY'),
+      region: this.configService.get<string>('AWS_REGION'),
+    });
+    this.bucketName = this.configService.get<string>('AWS_S3_BUCKET_NAME');
+  }
+
+  /**
+   * Uploads a file to S3
+   * @param file - The file to upload
+   * @param folder - The folder in S3 where the file should be stored (e.g., 'resumes', 'images')
+   * @returns The file URL
+   */
+  async uploadFile(file: Express.Multer.File, folder: string): Promise<string> {
+    if (!file) {
+      throw new CustomHttpException('File is required', HttpStatus.BAD_REQUEST);
+    }
+
+    const fileExtension = file.originalname.split('.').pop();
+    const fileKey = `${folder}/${Date.now()}_${file.originalname}`;
+
+    const uploadParams = {
+      Bucket: this.bucketName,
+      Key: fileKey,
+      Body: file.buffer,
+      ContentType: file.mimetype,
+    };
+
+    try {
+      const { Location } = await this.s3.upload(uploadParams).promise();
+      return Location;
+    } catch (error) {
+      throw new CustomHttpException('Failed to upload file', HttpStatus.INTERNAL_SERVER_ERROR);
+    }
+  }
+}
diff --git a/src/modules/timezones/timezones.controller.ts b/src/modules/timezones/timezones.controller.ts
index a988738f..f739fb34 100644
--- a/src/modules/timezones/timezones.controller.ts
+++ b/src/modules/timezones/timezones.controller.ts
@@ -25,9 +25,8 @@ export class TimezonesController {
   @ApiOperation({ summary: 'Get all supported timezones' })
   @ApiResponse({ status: 200, description: 'List of supported timezones.' })
   @ApiResponse({ status: 401, description: 'Unauthorized' })
-  async getTimezones(@Res() response: Response): Promise<any> {
-    const result = await this.timezonesService.getSupportedTimezones();
-    return response.status(result.status_code).json(result);
+  async getTimezones() {
+    return this.timezonesService.getSupportedTimezones();
   }
 
   @Patch(':id')
diff --git a/src/modules/timezones/timezones.service.ts b/src/modules/timezones/timezones.service.ts
index 97b5429e..4c6b4548 100644
--- a/src/modules/timezones/timezones.service.ts
+++ b/src/modules/timezones/timezones.service.ts
@@ -21,6 +21,7 @@ import {
 
 @Injectable()
 export class TimezonesService {
+  private logger = new Logger(TimezonesService.name);
   constructor(
     @InjectRepository(Timezone)
     private readonly timezoneRepository: Repository<Timezone>
@@ -48,7 +49,7 @@ export class TimezonesService {
         timezone: newTimezone,
       };
     } catch (error) {
-      console.log('TimezonesServiceError ~ createTimezone ~', error);
+      this.logger.error('TimezonesServiceError ~ createTimezone ~', error);
       throw new InternalServerErrorException({
         message: ERROR_OCCURED,
         status_code: HttpStatus.INTERNAL_SERVER_ERROR,
@@ -60,15 +61,18 @@ export class TimezonesService {
     try {
       const timezones = await this.timezoneRepository.find();
       const formattedTimezones = timezones.map(tz => ({
-        timezone: `(UTC ${tz.gmtOffset}) ${tz.description}`,
+        timezone: tz.timezone,
+        gmtOffset: tz.gmtOffset,
+        description: tz.description,
       }));
+
       return {
         status_code: HttpStatus.OK,
         message: SUCCESS,
         timezones: formattedTimezones,
       };
     } catch (error) {
-      Logger.error('TimezonesServiceError ~ fetchTimezones ~', error);
+      this.logger.error('TimezonesServiceError ~ fetchTimezones ~', error);
       throw new InternalServerErrorException({
         message: FETCH_TIMEZONE_FAILURE,
         status_code: HttpStatus.INTERNAL_SERVER_ERROR,
@@ -95,7 +99,7 @@ export class TimezonesService {
         timezone,
       };
     } catch (error) {
-      Logger.error('TimezonesServiceError ~ updateTimezone ~', error);
+      this.logger.error('TimezonesServiceError ~ updateTimezone ~', error);
       throw new InternalServerErrorException({
         message: ERROR_OCCURED,
         status_code: HttpStatus.INTERNAL_SERVER_ERROR,



Test patch:
diff --git a/src/modules/billing-plans/tests/billing-plan.service.spec.ts b/src/modules/billing-plans/tests/billing-plan.service.spec.ts
index c37daa76..7c72994a 100644
--- a/src/modules/billing-plans/tests/billing-plan.service.spec.ts
+++ b/src/modules/billing-plans/tests/billing-plan.service.spec.ts
@@ -1,5 +1,3 @@
-import 'module-alias/register';
-import 'reflect-metadata';
 import { Test, TestingModule } from '@nestjs/testing';
 import { BillingPlanService } from '../billing-plan.service';
 import { Repository } from 'typeorm';
@@ -9,6 +7,8 @@ import { NotFoundException, BadRequestException, HttpStatus } from '@nestjs/comm
 import { CustomHttpException } from '@shared/helpers/custom-http-filter';
 import * as SYS_MSG from '@shared/constants/SystemMessages';
 import { BillingPlanMapper } from '../mapper/billing-plan.mapper';
+import { SubscriptionScheduler } from '../subscription-scheduler';
+import { MailerService } from '@nestjs-modules/mailer';
 
 describe('BillingPlanService', () => {
   let service: BillingPlanService;
@@ -22,6 +22,12 @@ describe('BillingPlanService', () => {
           provide: getRepositoryToken(BillingPlan),
           useClass: Repository,
         },
+        {
+          provide: MailerService,
+          useValue: {
+            sendMail: jest.fn().mockResolvedValue(undefined),
+          },
+        },
       ],
     }).compile();
 
@@ -48,6 +54,8 @@ describe('BillingPlanService', () => {
         is_active: true,
         created_at: new Date(),
         updated_at: new Date(),
+        expirationDate: new Date(),
+        email: 'test@example.com',
       };
 
       jest.spyOn(repository, 'findOne').mockResolvedValue(billingPlan as BillingPlan);
@@ -70,6 +78,8 @@ describe('BillingPlanService', () => {
           is_active: true,
           created_at: new Date(),
           updated_at: new Date(),
+          expirationDate: new Date(),
+          email: 'test1@example.com',
         },
         {
           id: '2',
@@ -80,9 +90,11 @@ describe('BillingPlanService', () => {
           is_active: true,
           created_at: new Date(),
           updated_at: new Date(),
+          expirationDate: new Date(),
+          email: 'test2@example.com',
         },
         {
-          id: '1',
+          id: '3',
           name: 'Premium',
           description: 'premium plan',
           amount: 120,
@@ -90,6 +102,8 @@ describe('BillingPlanService', () => {
           is_active: true,
           created_at: new Date(),
           updated_at: new Date(),
+          expirationDate: new Date(),
+          email: 'test3@example.com',
         },
       ];
 
@@ -121,6 +135,8 @@ describe('BillingPlanService', () => {
         is_active: true,
         created_at: new Date(),
         updated_at: new Date(),
+        expirationDate: new Date(),
+        email: 'test@example.com',
       };
 
       jest.spyOn(repository, 'findOneBy').mockResolvedValue(billingPlan as BillingPlan);
@@ -144,3 +160,38 @@ describe('BillingPlanService', () => {
     });
   });
 });
+
+describe('SubscriptionScheduler', () => {
+  let scheduler: SubscriptionScheduler;
+  let billingPlanService: BillingPlanService;
+
+  beforeEach(async () => {
+    const module: TestingModule = await Test.createTestingModule({
+      providers: [
+        SubscriptionScheduler,
+        {
+          provide: BillingPlanService,
+          useValue: {
+            getAllSubscriptions: jest.fn().mockResolvedValue([
+              { id: 1, expirationDate: new Date(), email: 'test1@example.com' },
+              { id: 2, expirationDate: new Date(), email: 'test2@example.com' },
+            ]),
+            sendRenewalReminder: jest.fn().mockResolvedValue(undefined),
+          },
+        },
+      ],
+    }).compile();
+
+    scheduler = module.get<SubscriptionScheduler>(SubscriptionScheduler);
+    billingPlanService = module.get<BillingPlanService>(BillingPlanService);
+  });
+
+  it('should send renewal reminders to all subscriptions', async () => {
+    await scheduler.handleCron();
+
+    expect(billingPlanService.getAllSubscriptions).toHaveBeenCalled();
+    expect(billingPlanService.sendRenewalReminder).toHaveBeenCalledTimes(2);
+    expect(billingPlanService.sendRenewalReminder).toHaveBeenCalledWith(1);
+    expect(billingPlanService.sendRenewalReminder).toHaveBeenCalledWith(2);
+  });
+});
diff --git a/src/modules/jobs/tests/jobs.service.spec.ts b/src/modules/jobs/tests/jobs.service.spec.ts
index 542da41a..534d4524 100644
--- a/src/modules/jobs/tests/jobs.service.spec.ts
+++ b/src/modules/jobs/tests/jobs.service.spec.ts
@@ -13,13 +13,19 @@ import { JobsService } from '../jobs.service';
 import { jobsMock } from './mocks/jobs.mock';
 import { JobSearchDto } from '../dto/jobSearch.dto';
 import { UpdateJobDto } from '../dto/update-job.dto';
+import { S3Service } from '@modules/s3/s3.service';
+import { isPassed } from '../utils/helpers';
 
+jest.mock('../utils/helpers', () => ({
+  isPassed: jest.fn(() => false),
+}));
 describe('JobsService', () => {
   let service: JobsService;
   let jobRepository: Repository<Job>;
   let userRepository: Repository<User>;
   let userDto: UserResponseDTO;
   let createJobDto: JobDto;
+  let s3Service: S3Service;
 
   const mockJob = {
     data: {
@@ -27,11 +33,22 @@ describe('JobsService', () => {
       deadline: new Date(new Date().getTime() + 1000 * 60 * 60 * 24).toISOString(),
     },
   };
-
+  const mockFile: Express.Multer.File = {
+    fieldname: 'resume',
+    originalname: 'resume.pdf',
+    encoding: '7bit',
+    mimetype: 'application/pdf',
+    buffer: Buffer.from('mock file content'),
+    size: 1024,
+    destination: '',
+    filename: '',
+    path: '',
+    stream: null,
+  };
   const mockJobApplicationDto: JobApplicationDto = {
     applicant_name: 'John Doe',
     email: 'johndoe@example.com',
-    resume: 'resume content',
+    resume: mockFile,
     cover_letter: 'Cover letter text',
   };
 
@@ -72,6 +89,7 @@ describe('JobsService', () => {
             save: jest.fn(),
             findOneBy: jest.fn(),
             update: jest.fn(),
+
             createQueryBuilder: jest.fn().mockReturnValue({
               where: jest.fn().mockReturnThis(),
               andWhere: jest.fn().mockReturnThis(),
@@ -104,12 +122,19 @@ describe('JobsService', () => {
             update: jest.fn(),
           },
         },
+        {
+          provide: S3Service,
+          useValue: {
+            uploadFile: jest.fn().mockResolvedValue('https://s3-bucket-url/resume.pdf'),
+          },
+        },
       ],
     }).compile();
 
     service = module.get<JobsService>(JobsService);
     userRepository = module.get(getRepositoryToken(User));
     jobRepository = module.get(getRepositoryToken(Job));
+    s3Service = module.get<S3Service>(S3Service);
 
     jest.spyOn(userRepository, 'findOne').mockResolvedValue(userDto as User);
     jest.spyOn(jobRepository, 'create').mockReturnValue({ ...createJobDto, user: userDto } as Job);
@@ -161,7 +186,7 @@ describe('JobsService', () => {
         data: { is_deleted: true, deadline: new Date().toISOString() },
       } as any);
 
-      await expect(service.applyForJob('jobId', mockJobApplicationDto)).rejects.toThrow(
+      await expect(service.applyForJob('jobId', mockJobApplicationDto, {} as Express.Multer.File)).rejects.toThrow(
         new CustomHttpException('Job deleted', HttpStatus.NOT_FOUND)
       );
     });
@@ -170,30 +195,34 @@ describe('JobsService', () => {
       jest.spyOn(service, 'getJob').mockResolvedValue({
         data: { is_deleted: false, deadline: new Date(new Date().getTime() - 1000 * 60 * 60 * 24).toISOString() },
       } as any);
+      (isPassed as jest.Mock).mockReturnValue(true);
 
-      await expect(service.applyForJob('jobId', mockJobApplicationDto)).rejects.toThrow(
+      await expect(
+        service.applyForJob('jobId', mockJobApplicationDto, {} as Express.Multer.File)
+      ).rejects.toMatchObject(
         new CustomHttpException('Job application deadline passed', HttpStatus.UNPROCESSABLE_ENTITY)
       );
     });
+    it('should successfully create a job application with resume uploaded to S3', async () => {
+      const resume = { buffer: Buffer.from('test file'), originalname: 'resume.pdf' } as Express.Multer.File;
 
-    it('should successfully create a job application', async () => {
       jest.spyOn(service, 'getJob').mockResolvedValue(mockJob as any);
-      const createMock = jest.fn().mockReturnValue(mockJobApplicationDto);
-      const saveMock = jest.fn().mockResolvedValue(mockJobApplicationResponse);
+      jest.spyOn(s3Service, 'uploadFile').mockResolvedValue('https://s3-bucket-url/resume.pdf');
+      (isPassed as jest.Mock).mockReturnValue(false);
 
-      jest.spyOn(service['jobApplicationRepository'], 'create').mockImplementation(createMock);
-      jest.spyOn(service['jobApplicationRepository'], 'save').mockImplementation(saveMock);
+      jest
+        .spyOn(service['jobApplicationRepository'], 'create')
+        .mockReturnValue({ id: '1', ...mockJobApplicationDto } as any);
+      jest
+        .spyOn(service['jobApplicationRepository'], 'save')
+        .mockResolvedValue({ id: '1', ...mockJobApplicationDto } as any);
 
-      const result = await service.applyForJob('jobId', mockJobApplicationDto);
+      const result = await service.applyForJob('jobId', mockJobApplicationDto, resume);
 
       expect(result).toEqual(mockJobApplicationResponse);
-      expect(createMock).toHaveBeenCalledWith({
-        ...mockJobApplicationDto,
-        applicant_name: 'John Doe',
-        resume: `https://example.com/John_Doe.pdf`,
-        ...mockJob,
-      });
-      expect(saveMock).toHaveBeenCalled();
+      expect(s3Service.uploadFile).toHaveBeenCalledWith(resume, 'resumes'); // ✅ Updated expectation
+      expect(service['jobApplicationRepository'].create).toHaveBeenCalled();
+      expect(service['jobApplicationRepository'].save).toHaveBeenCalled();
     });
   });
 
diff --git a/src/modules/languages/tests/languages.controller.spec.ts b/src/modules/languages/tests/languages.controller.spec.ts
index 06b610cf..907d6e35 100644
--- a/src/modules/languages/tests/languages.controller.spec.ts
+++ b/src/modules/languages/tests/languages.controller.spec.ts
@@ -7,6 +7,7 @@ import { Language } from '../entities/language.entity';
 import { CreateLanguageDto } from '../dto/create-language.dto';
 import { BadRequestException, ForbiddenException, HttpException, HttpStatus, NotFoundException } from '@nestjs/common';
 import { User } from '@modules/user/entities/user.entity';
+import { JwtService } from '@nestjs/jwt';
 
 const mockLanguageRepository = {
   findOne: jest.fn(),
@@ -21,6 +22,11 @@ const mockLanguagesService = {
   getSupportedLanguages: jest.fn(),
 };
 
+const mockJwtService = {
+  sign: jest.fn(),
+  verify: jest.fn(),
+};
+
 describe('LanguagesController', () => {
   let controller: LanguagesController;
   let service: LanguagesService;
@@ -37,6 +43,10 @@ describe('LanguagesController', () => {
           provide: getRepositoryToken(Language),
           useValue: mockLanguageRepository,
         },
+        {
+          provide: JwtService,
+          useValue: mockJwtService,
+        },
       ],
     }).compile();
 
diff --git a/src/modules/languages/tests/languages.service.spec.ts b/src/modules/languages/tests/languages.service.spec.ts
index 30af51be..67175a41 100644
--- a/src/modules/languages/tests/languages.service.spec.ts
+++ b/src/modules/languages/tests/languages.service.spec.ts
@@ -27,9 +27,14 @@ const mockLanguageRepository = {
   })),
 };
 
+const mockUserRepository = {
+  findOne: jest.fn(),
+};
+
 describe('LanguagesService', () => {
   let service: LanguagesService;
   let repository: Repository<Language>;
+  let userRepository: Repository<User>;
 
   beforeEach(async () => {
     const module: TestingModule = await Test.createTestingModule({
@@ -39,6 +44,10 @@ describe('LanguagesService', () => {
           provide: getRepositoryToken(Language),
           useValue: mockLanguageRepository,
         },
+        {
+          provide: getRepositoryToken(User),
+          useValue: mockUserRepository, // Add this line
+        },
       ],
     }).compile();
 
@@ -100,274 +109,58 @@ describe('LanguagesService', () => {
         })
       );
     });
+  });
 
-    it('should handle errors during creation', async () => {
-      const createLanguageDto: CreateLanguageDto = {
-        language: 'English',
-        code: 'en',
-        description: 'English',
-      };
+  describe('getUserLanguages', () => {
+    let languageService: LanguagesService;
+    let languageRepository: Repository<Language>;
+    let userRepository: Repository<User>;
 
-      jest.spyOn(repository, 'findOne').mockRejectedValue(
-        new HttpException(
+    beforeEach(async () => {
+      const module: TestingModule = await Test.createTestingModule({
+        providers: [
+          LanguagesService,
           {
-            message: 'An error occurred',
-            status_code: HttpStatus.INTERNAL_SERVER_ERROR,
+            provide: getRepositoryToken(Language),
+            useClass: Repository,
           },
-          HttpStatus.INTERNAL_SERVER_ERROR
-        )
-      );
-
-      await expect(service.createLanguage(createLanguageDto)).rejects.toThrow(
-        new HttpException(
           {
-            message: 'An error occurred',
-            status_code: HttpStatus.INTERNAL_SERVER_ERROR,
+            provide: getRepositoryToken(User),
+            useClass: Repository,
           },
-          HttpStatus.INTERNAL_SERVER_ERROR
-        )
-      );
-    });
-  });
-
-  describe('getSupportedLanguages', () => {
-    it('should return a list of languages', async () => {
-      const languages: Language[] = [
-        {
-          id: '1',
-          language: 'English',
-          code: 'en',
-          description: 'English',
-          created_at: new Date(),
-          updated_at: new Date(),
-        },
-        {
-          id: '2',
-          language: 'Spanish',
-          code: 'es',
-          description: 'Español',
-          created_at: new Date(),
-          updated_at: new Date(),
-        },
-      ];
+        ],
+      }).compile();
 
-      jest.spyOn(repository, 'find').mockResolvedValue(languages);
-
-      const result = await service.getSupportedLanguages();
-      expect(result).toEqual({
-        status_code: HttpStatus.OK,
-        message: 'Languages fetched successfully',
-        languages: languages.map(language => ({
-          language: `${language.language} (${language.description})`,
-        })),
-      });
+      languageService = module.get<LanguagesService>(LanguagesService);
+      languageRepository = module.get<Repository<Language>>(getRepositoryToken(Language));
+      userRepository = module.get<Repository<User>>(getRepositoryToken(User));
     });
 
-    it('should handle errors during fetch', async () => {
-      jest.spyOn(repository, 'find').mockRejectedValue(
-        new HttpException(
-          {
-            message: 'An error occurred',
-            status_code: HttpStatus.INTERNAL_SERVER_ERROR,
-          },
-          HttpStatus.INTERNAL_SERVER_ERROR
-        )
-      );
-
-      await expect(service.getSupportedLanguages()).rejects.toThrow(
-        new HttpException(
-          {
-            message: 'An error occurred',
-            status_code: HttpStatus.INTERNAL_SERVER_ERROR,
-          },
-          HttpStatus.INTERNAL_SERVER_ERROR
-        )
-      );
+    it('should be defined', () => {
+      expect(languageService).toBeDefined();
     });
-  });
-
-  describe('updateLanguage', () => {
-    it('should update a language', async () => {
-      const id = 'some-id';
-      const updateLanguageDto: UpdateLanguageDto = {
-        language: 'English',
-        code: 'en',
-        description: 'English Language',
-      };
 
-      const updatedLanguage = {
-        id,
-        ...updateLanguageDto,
+    it('should return languages if user exists', async () => {
+      const mockUser: User = {
+        id: '123',
+        first_name: 'John',
+        last_name: 'Doe',
+        email: 'john.doe@example.com',
+        status: 'active',
+        languages: [{ id: '1', language: 'English', code: 'en', description: 'English' }],
         created_at: new Date(),
         updated_at: new Date(),
-      } as Language;
-
-      jest.spyOn(repository, 'findOne').mockResolvedValue(updatedLanguage);
-      jest.spyOn(repository, 'save').mockResolvedValue(updatedLanguage);
-
-      const result = await service.updateLanguage(id, updateLanguageDto);
-      expect(result).toEqual({
-        status_code: HttpStatus.OK,
-        message: 'Language successfully updated',
-        language: updatedLanguage,
-      });
-    });
-
-    it('should handle language not found', async () => {
-      const id = 'non-existent-id';
-      const updateLanguageDto: UpdateLanguageDto = {
-        language: 'English',
-        code: 'en',
-        description: 'English Language',
-      };
-
-      jest.spyOn(repository, 'findOne').mockResolvedValue(null);
-
-      await expect(service.updateLanguage(id, updateLanguageDto)).rejects.toThrow(
-        new NotFoundException({
-          status_code: HttpStatus.NOT_FOUND,
-          message: 'Language not found',
-        })
-      );
-    });
-
-    it('should handle errors during update', async () => {
-      const id = 'some-id';
-      const updateLanguageDto: UpdateLanguageDto = {
-        language: 'English',
-        code: 'en',
-        description: 'English Language',
-      };
-
-      jest.spyOn(repository, 'findOne').mockRejectedValue(
-        new HttpException(
-          {
-            message: 'An error occurred',
-            status_code: HttpStatus.INTERNAL_SERVER_ERROR,
-          },
-          HttpStatus.INTERNAL_SERVER_ERROR
-        )
-      );
-
-      await expect(service.updateLanguage(id, updateLanguageDto)).rejects.toThrow(
-        new HttpException(
-          {
-            message: 'An error occurred',
-            status_code: HttpStatus.INTERNAL_SERVER_ERROR,
-          },
-          HttpStatus.INTERNAL_SERVER_ERROR
-        )
-      );
-    });
-  });
-
-  describe('getLanguagesByUserId', () => {
-    it('should return languages associated with a user', async () => {
-      const userId = '550e8400-e29b-41d4-a716-446655440000';
-      const user = {
-        id: userId,
-      } as User;
-
-      const languages: Language[] = [
-        {
-          id: '1',
-          language: 'English',
-          code: 'en',
-          description: 'English',
-          created_at: new Date(),
-          updated_at: new Date(),
-        },
-        {
-          id: '2',
-          language: 'Spanish',
-          code: 'es',
-          description: 'Español',
-          created_at: new Date(),
-          updated_at: new Date(),
-        },
-      ];
-
-      jest.spyOn(repository, 'createQueryBuilder').mockReturnValue({
-        innerJoin: jest.fn().mockReturnThis(),
-        where: jest.fn().mockReturnThis(),
-        getMany: jest.fn().mockResolvedValue(languages),
-      } as any);
-
-      const result = await service.getLanguagesById(userId, user);
-      expect(result).toEqual({
-        status: 'OK',
-        status_code: HttpStatus.OK,
-        message: 'Languages fetched successfully',
-        data: languages.map(language => ({
-          id: language.id,
-          language: language.language,
-          description: language.description,
-          code: language.code,
-        })),
-      });
-    });
-
-    it('should handle invalid user ID', async () => {
-      const userId = 'invalid-id';
-      const user = {
-        id: 'user-id',
-      } as User;
-
-      await expect(service.getLanguagesById(userId, user)).rejects.toThrow(new BadRequestException('Invalid user Id'));
-    });
-
-    it('should handle unauthorized access', async () => {
-      const userId = '550e8400-e29b-41d4-a716-446655440000';
-      const user = {
-        id: '6a5db1d0-87c2-4602-a5a5-0ffacc2377d8',
-      } as User;
-
-      await expect(service.getLanguagesById(userId, user)).rejects.toThrow(
-        new ForbiddenException({
-          status_code: HttpStatus.FORBIDDEN,
-          message: 'You are not authorized to access this resource',
-        })
-      );
-    });
-
-    it('should handle no languages found for the user', async () => {
-      const userId = '6a5db1d0-87c2-4602-a5a5-0ffacc2377d8';
-      const user = {
-        id: userId,
       } as User;
 
-      jest.spyOn(repository, 'createQueryBuilder').mockReturnValue({
-        innerJoin: jest.fn().mockReturnThis(),
-        where: jest.fn().mockReturnThis(),
-        getMany: jest.fn().mockResolvedValue([]),
-      } as any);
+      jest.spyOn(userRepository, 'findOne').mockResolvedValue(mockUser);
 
-      await expect(service.getLanguagesById(userId, user)).rejects.toThrow(
-        new NotFoundException({
-          status_code: HttpStatus.NOT_FOUND,
-          message: 'Languages associated with this user not found',
-        })
-      );
+      const result = await languageService.getUserLanguages('123');
+      expect(result).toEqual(mockUser.languages);
     });
 
-    it('should handle errors during fetch', async () => {
-      const userId = '6a5db1d0-87c2-4602-a5a5-0ffacc2377d8';
-      const user = {
-        id: userId,
-      } as User;
-
-      jest.spyOn(repository, 'createQueryBuilder').mockReturnValue({
-        innerJoin: jest.fn().mockReturnThis(),
-        where: jest.fn().mockReturnThis(),
-        getMany: jest.fn().mockRejectedValue(new Error('An error occurred')),
-      } as any);
-
-      await expect(service.getLanguagesById(userId, user)).rejects.toThrow(
-        new InternalServerErrorException({
-          message: 'An error occurred',
-          status_code: HttpStatus.INTERNAL_SERVER_ERROR,
-        })
-      );
+    it('should throw NotFoundException if user does not exist', async () => {
+      jest.spyOn(userRepository, 'findOne').mockResolvedValue(null);
+      await expect(languageService.getUserLanguages('123')).rejects.toThrow(NotFoundException);
     });
   });
 });
diff --git a/src/modules/newsletter-subscription/tests/newsletter-subscription.service.spec.ts b/src/modules/newsletter-subscription/tests/newsletter-subscription.service.spec.ts
index f7e05a74..87a11818 100644
--- a/src/modules/newsletter-subscription/tests/newsletter-subscription.service.spec.ts
+++ b/src/modules/newsletter-subscription/tests/newsletter-subscription.service.spec.ts
@@ -189,4 +189,38 @@ describe('NewsletterService', () => {
       expect(repository.findOne).toHaveBeenCalledWith({ where: { email } });
     });
   });
+
+  describe('resubscribe', () => {
+    it('should successfully resubscribe a user who was unsubscribed', async () => {
+      const dto = { email: 'test@example.com' };
+      const mockSubscription = {
+        id: '1',
+        email: 'test@example.com',
+        status: 'unsubscribed',
+      } as NewsletterSubscription;
+
+      jest.spyOn(repository, 'findOne').mockResolvedValue(mockSubscription);
+      jest.spyOn(repository, 'save').mockResolvedValue({ ...mockSubscription, status: 'active' });
+
+      const result = await service.resubscribe(dto);
+
+      expect(result).toEqual({ message: 'Successfully resubscribed to the newsletter.' });
+      expect(repository.findOne).toHaveBeenCalledWith(
+        expect.objectContaining({
+          where: expect.arrayContaining([expect.objectContaining({ email: dto.email })]),
+        })
+      );
+      expect(repository.save).toHaveBeenCalledWith(expect.objectContaining({ status: 'active' }));
+    });
+
+    it('should throw NotFoundException if user is not found or was never subscribed', async () => {
+      const dto = { email: 'notfound@example.com' };
+
+      jest.spyOn(repository, 'findOne').mockResolvedValue(null);
+
+      await expect(service.resubscribe(dto)).rejects.toThrow(
+        new NotFoundException('User not found or not unsubscribed.')
+      );
+    });
+  });
 });
diff --git a/src/modules/products/tests/mocks/deleted-product.mock.ts b/src/modules/products/tests/mocks/deleted-product.mock.ts
index 5781f036..0852993d 100644
--- a/src/modules/products/tests/mocks/deleted-product.mock.ts
+++ b/src/modules/products/tests/mocks/deleted-product.mock.ts
@@ -24,4 +24,5 @@ export const deletedProductMock: Product = {
   org: orgMock,
   created_at: new Date(),
   updated_at: new Date(),
+  reviews: [],
 };
diff --git a/src/modules/products/tests/mocks/product.mock.ts b/src/modules/products/tests/mocks/product.mock.ts
index c6b26ac6..5a8d000c 100644
--- a/src/modules/products/tests/mocks/product.mock.ts
+++ b/src/modules/products/tests/mocks/product.mock.ts
@@ -24,4 +24,5 @@ export const productMock: Product = {
   cost_price: 10,
   cart: [],
   orderItems: [],
+  reviews: [],
 };
diff --git a/src/modules/products/tests/products.service.spec.ts b/src/modules/products/tests/products.service.spec.ts
index 964c04c8..6c842caf 100644
--- a/src/modules/products/tests/products.service.spec.ts
+++ b/src/modules/products/tests/products.service.spec.ts
@@ -1,22 +1,22 @@
 import { InternalServerErrorException, NotFoundException } from '@nestjs/common';
 import { Test, TestingModule } from '@nestjs/testing';
 import { getRepositoryToken } from '@nestjs/typeorm';
-import { DeleteResult, Repository, UpdateResult } from 'typeorm';
+import { Repository, UpdateResult } from 'typeorm';
 import { CustomHttpException } from '@shared/helpers/custom-http-filter';
-import { Comment } from '../../../modules/comments/entities/comments.entity';
-import { Organisation } from '../../../modules/organisations/entities/organisations.entity';
-import { orgMock } from '../../../modules/organisations/tests/mocks/organisation.mock';
-import { User } from '../../../modules/user/entities/user.entity';
-import { mockUser } from '../../../modules/user/tests/mocks/user.mock';
-import { AddCommentDto } from '../../comments/dto/add-comment.dto';
+import { Comment } from '@modules/comments/entities/comments.entity';
+import { Organisation } from '@modules/organisations/entities/organisations.entity';
+import { orgMock } from '@modules/organisations/tests/mocks/organisation.mock';
+import { User } from '@modules/user/entities/user.entity';
+import { mockUser } from '@modules/user/tests/mocks/user.mock';
 import { UpdateProductDTO } from '../dto/update-product.dto';
 import { ProductVariant } from '../entities/product-variant.entity';
 import { Product } from '../entities/product.entity';
 import { ProductsService } from '../products.service';
 import { mockComment } from './mocks/comment.mock';
-import { deletedProductMock } from './mocks/deleted-product.mock';
 import { createProductRequestDtoMock } from './mocks/product-request-dto.mock';
 import { productMock } from './mocks/product.mock';
+import { Review } from '../entities/review.entity';
+import { AddCommentDto } from '../../comments/dtos/add-comment.dto';
 
 describe('ProductsService', () => {
   let service: ProductsService;
@@ -24,6 +24,7 @@ describe('ProductsService', () => {
   let organisationRepository: Repository<Organisation>;
   let userRepository: Repository<User>;
   let commentRepository: Repository<Comment>;
+  let reviewRepository: Repository<Review>;
 
   beforeEach(async () => {
     const module: TestingModule = await Test.createTestingModule({
@@ -58,6 +59,15 @@ describe('ProductsService', () => {
             save: jest.fn(),
           },
         },
+        {
+          provide: getRepositoryToken(Review), // ✅ Added the missing ReviewRepository correctly
+          useValue: {
+            createQueryBuilder: jest.fn(), // Optional — mock these if Review is used in queries
+            findOne: jest.fn(),
+            save: jest.fn(),
+            create: jest.fn(),
+          },
+        },
         {
           provide: getRepositoryToken(User),
           useClass: Repository,
@@ -80,7 +90,6 @@ describe('ProductsService', () => {
     const createdProduct = await service.createProduct(orgMock.id, createProductRequestDtoMock);
 
     expect(createdProduct.message).toEqual('Product created successfully');
-    expect(createdProduct.status).toEqual('success');
   });
 
   describe('searchProducts', () => {
@@ -97,7 +106,7 @@ describe('ProductsService', () => {
 
       const products = await service.searchProducts(orgMock.id, searchCriteria);
 
-      expect(products).toEqual({ success: true, statusCode: 200, products: [productMock] });
+      expect(products).toEqual({ success: true, statusCode: 200, data: [productMock] });
     });
 
     it('should return products based on price range search', async () => {
@@ -113,7 +122,7 @@ describe('ProductsService', () => {
 
       const products = await service.searchProducts(orgMock.id, searchCriteria);
 
-      expect(products).toEqual({ success: true, statusCode: 200, products: [productMock] });
+      expect(products).toEqual({ success: true, statusCode: 200, data: [productMock] });
     });
 
     it('should return products based on combined search criteria', async () => {
@@ -129,7 +138,7 @@ describe('ProductsService', () => {
 
       const products = await service.searchProducts(orgMock.id, searchCriteria);
 
-      expect(products).toEqual({ success: true, statusCode: 200, products: [productMock] });
+      expect(products).toEqual({ success: true, statusCode: 200, data: [productMock] });
     });
 
     it('should throw NotFoundException if no products match search criteria', async () => {
@@ -217,7 +226,7 @@ describe('ProductsService', () => {
     });
 
     it('should throw an error', async () => {
-      const addCommentDto: AddCommentDto = {
+      const AddCommentDto: AddCommentDto = {
         comment: 'New Comment',
       };
 
@@ -226,7 +235,7 @@ describe('ProductsService', () => {
       jest.spyOn(commentRepository, 'create').mockReturnValue(mockComment);
       jest.spyOn(commentRepository, 'save').mockResolvedValue(mockComment);
 
-      await expect(service.addCommentToProduct(productMock.id, addCommentDto, mockUser.id)).rejects.toThrow(
+      await expect(service.addCommentToProduct(productMock.id, AddCommentDto, mockUser.id)).rejects.toThrow(
         CustomHttpException
       );
     });