Skip to content

Commit

Permalink
feat(api): Added feedback form module (#210)
Browse files Browse the repository at this point in the history
Co-authored-by: Rajdip Bhattacharya <agentR47@gmail.com>
  • Loading branch information
rayaanoidPrime and rajdip-b committed May 12, 2024
1 parent b53dd7c commit ae1efd8
Show file tree
Hide file tree
Showing 10 changed files with 256 additions and 1 deletion.
4 changes: 3 additions & 1 deletion apps/api/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { ProviderModule } from '../provider/provider.module'
import { ScheduleModule } from '@nestjs/schedule'
import { EnvSchema } from '../common/env/env.schema'
import { IntegrationModule } from '../integration/integration.module'
import { FeedbackModule } from '../feedback/feedback.module'

@Module({
controllers: [AppController],
Expand Down Expand Up @@ -54,7 +55,8 @@ import { IntegrationModule } from '../integration/integration.module'
ApprovalModule,
SocketModule,
ProviderModule,
IntegrationModule
IntegrationModule,
FeedbackModule
],
providers: [
{
Expand Down
25 changes: 25 additions & 0 deletions apps/api/src/feedback/controller/feedback.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Test, TestingModule } from '@nestjs/testing'
import { FeedbackController } from './feedback.controller'
import { FeedbackService } from '../service/feedback.service'
import { MAIL_SERVICE } from '../../mail/services/interface.service'
import { MockMailService } from '../../mail/services/mock.service'

describe('FeedbackController', () => {
let controller: FeedbackController

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [FeedbackController],
providers: [
FeedbackService,
{ provide: MAIL_SERVICE, useValue: MockMailService }
]
}).compile()

controller = module.get<FeedbackController>(FeedbackController)
})

it('should be defined', () => {
expect(controller).toBeDefined()
})
})
38 changes: 38 additions & 0 deletions apps/api/src/feedback/controller/feedback.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Controller, Post, Body, HttpStatus } from '@nestjs/common'
import { Public } from '../../decorators/public.decorator'
import { FeedbackService } from '../service/feedback.service'
import { ApiTags, ApiBody, ApiResponse, ApiOperation } from '@nestjs/swagger'

@ApiTags('Feedback Controller')
@Controller('feedback')
export class FeedbackController {
constructor(private readonly feedbackService: FeedbackService) {}

@Public()
@Post()
@ApiOperation({
summary: 'Send Feedback message to Admin',
description: 'This endpoint sends a feedback message to the Admin email.'
})
@ApiBody({
schema: {
type: 'object',
properties: {
feedback: {
type: 'string',
example: 'Your feedback message here'
}
}
}
})
@ApiResponse({
status: HttpStatus.CREATED,
description: 'Feedback registered successfully'
})
@ApiResponse({ status: HttpStatus.BAD_REQUEST, description: 'Bad Request' })
async registerFeedback(
@Body() feedbackData: { feedback: string }
): Promise<void> {
await this.feedbackService.registerFeedback(feedbackData.feedback)
}
}
109 changes: 109 additions & 0 deletions apps/api/src/feedback/feedback.e2e.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { Test, TestingModule } from '@nestjs/testing'
import { AppModule } from '../app/app.module'
import { FeedbackService } from '../feedback/service/feedback.service'
import { MockMailService } from '../mail/services/mock.service'
import {
FastifyAdapter,
NestFastifyApplication
} from '@nestjs/platform-fastify'
import { MAIL_SERVICE } from '../mail/services/interface.service'
import { FeedbackModule } from './feedback.module'
import { MailModule } from '../mail/mail.module'
import { PrismaService } from '../prisma/prisma.service'
import { User } from '@prisma/client'
import cleanUp from '../common/cleanup'

describe('Feedback Controller (E2E)', () => {
let app: NestFastifyApplication
let feedbackService: FeedbackService
let mockMailService: MockMailService
let prisma: PrismaService
let user: User

beforeAll(async () => {
const moduleRef: TestingModule = await Test.createTestingModule({
imports: [AppModule, FeedbackModule, MailModule]
})
.overrideProvider(MAIL_SERVICE)
.useClass(MockMailService)
.compile()

app = moduleRef.createNestApplication<NestFastifyApplication>(
new FastifyAdapter()
)
feedbackService = moduleRef.get(FeedbackService)
mockMailService = moduleRef.get(MAIL_SERVICE)

prisma = moduleRef.get(PrismaService)

await app.init()
await app.getHttpAdapter().getInstance().ready()

await cleanUp(prisma)
})

beforeEach(async () => {
user = await prisma.user.create({
data: {
email: 'john@keyshade.xyz',
name: 'John',
isActive: true,
isAdmin: false,
isOnboardingFinished: false
}
})
})

afterEach(async () => {
if (user) {
await prisma.user.delete({
where: { id: user.id }
})
}
})

afterAll(async () => {
await prisma.$disconnect()
await app.close()
})

it('should be defined', async () => {
expect(app).toBeDefined()
expect(feedbackService).toBeDefined()
expect(mockMailService).toBeDefined()
expect(prisma).toBeDefined()
})

it('should register feedback successfully', async () => {
const feedbackMessage = 'Test feedback message'

const { statusCode } = await app.inject({
method: 'POST',
url: '/feedback',
payload: { feedback: feedbackMessage },
headers: {
'x-e2e-user-email': user.email
}
})

expect(statusCode).toBe(201)
})

it('should handle empty feedback', async () => {
const { statusCode, payload } = await app.inject({
method: 'POST',
url: '/feedback',
payload: { feedback: '' },
headers: {
'x-e2e-user-email': user.email
}
})

expect(statusCode).toBe(400)
expect(JSON.parse(payload)).toEqual({
error: 'Bad Request',
message: 'Feedback cannot be null or empty',
statusCode: 400
})
})
})
9 changes: 9 additions & 0 deletions apps/api/src/feedback/feedback.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Module } from '@nestjs/common'
import { FeedbackService } from './service/feedback.service'
import { FeedbackController } from './controller/feedback.controller'

@Module({
providers: [FeedbackService],
controllers: [FeedbackController]
})
export class FeedbackModule {}
23 changes: 23 additions & 0 deletions apps/api/src/feedback/service/feedback.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Test, TestingModule } from '@nestjs/testing'
import { FeedbackService } from './feedback.service'
import { MAIL_SERVICE } from '../../mail/services/interface.service'
import { MockMailService } from '../../mail/services/mock.service'

describe('FeedbackService', () => {
let service: FeedbackService

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
FeedbackService,
{ provide: MAIL_SERVICE, useClass: MockMailService }
]
}).compile()

service = module.get<FeedbackService>(FeedbackService)
})

it('should be defined', () => {
expect(service).toBeDefined()
})
})
21 changes: 21 additions & 0 deletions apps/api/src/feedback/service/feedback.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { BadRequestException, Inject, Injectable } from '@nestjs/common'
import {
IMailService,
MAIL_SERVICE
} from '../../mail/services/interface.service'

@Injectable()
export class FeedbackService {
constructor(
@Inject(MAIL_SERVICE) private readonly mailService: IMailService
) {}

async registerFeedback(feedback: string): Promise<void> {
if (!feedback || feedback.trim().length === 0) {
throw new BadRequestException('Feedback cannot be null or empty')
}
const adminEmail = 'admin@keyshade.xyz'

await this.mailService.feedbackEmail(adminEmail, feedback.trim())
}
}
2 changes: 2 additions & 0 deletions apps/api/src/mail/services/interface.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@ export interface IMailService {
accountLoginEmail(email: string): Promise<void>

adminUserCreateEmail(email: string): Promise<void>

feedbackEmail(email: string, feedback: string): Promise<void>
}
22 changes: 22 additions & 0 deletions apps/api/src/mail/services/mail.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,28 @@ export class MailService implements IMailService {
await this.sendEmail(process.env.ADMIN_EMAIL, subject, body)
}

async feedbackEmail(email: string, feedback: string): Promise<void> {
const subject = 'New Feedback Received !'
const body = `<!DOCTYPE html>
<html>
<head>
<title>New Feedback Received !</title>
</head>
<body>
<h1>New Feedback Received</h1>
<p>Hello,</p>
<p>We have received new feedback from a user:</p>
<blockquote>${feedback}</blockquote>
<p>Please review this feedback as soon as possible.</p>
<p>Thank you.</p>
<p>Best Regards,</p>
<p>Keyshade Team</p>
</body>
</html>
`
await this.sendEmail(email, subject, body)
}

private async sendEmail(
email: string,
subject: string,
Expand Down
4 changes: 4 additions & 0 deletions apps/api/src/mail/services/mock.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,8 @@ export class MockMailService implements IMailService {
async accountLoginEmail(email: string): Promise<void> {
this.log.log(`Account Login Email for ${email}`)
}

async feedbackEmail(email: string, feedback: string): Promise<void> {
this.log.log(`Feedback is : ${feedback}, for email : ${email}`)
}
}

0 comments on commit ae1efd8

Please sign in to comment.