forked from hngprojects/hng_boilerplate_expressjs
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'dev' into feat/Create-Squeeze-Page
- Loading branch information
Showing
8 changed files
with
280 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import { Request, Response, NextFunction } from "express"; | ||
import { NewsLetterSubscriptionService } from "../services/newsLetterSubscription.service"; | ||
import { BadRequest } from "../middleware"; | ||
|
||
const newsLetterSubscriptionService = new NewsLetterSubscriptionService(); | ||
|
||
/** | ||
* @swagger | ||
* /api/v1/newsletter-subscription: | ||
* post: | ||
* summary: Subscribe to the newsletter | ||
* description: Allows a user to subscribe to the newsletter by providing an email address. | ||
* tags: | ||
* - Newsletter Subscription | ||
* requestBody: | ||
* required: true | ||
* content: | ||
* application/json: | ||
* schema: | ||
* type: object | ||
* properties: | ||
* email: | ||
* type: string | ||
* format: email | ||
* example: [email protected] | ||
* description: The user's email address for subscribing to the newsletter. | ||
* responses: | ||
* 201: | ||
* description: Subscription successful. | ||
* content: | ||
* application/json: | ||
* schema: | ||
* type: object | ||
* properties: | ||
* status: | ||
* type: string | ||
* example: success | ||
* message: | ||
* type: string | ||
* example: Subscriber subscription successful | ||
* 200: | ||
* description: User is already subscribed to the newsletter. | ||
* content: | ||
* application/json: | ||
* schema: | ||
* type: object | ||
* properties: | ||
* status: | ||
* type: string | ||
* example: success | ||
* message: | ||
* type: string | ||
* example: You are already subscribed to our newsletter. | ||
* | ||
* 500: | ||
* description: Internal server error. An error occurred while processing the subscription. | ||
* content: | ||
* application/json: | ||
* schema: | ||
* type: object | ||
* properties: | ||
* status: | ||
* type: boolean | ||
* example: false | ||
* status_code: | ||
* type: number | ||
* example: 500 | ||
* message: | ||
* type: string | ||
* example: An error occurred while processing your request. | ||
*/ | ||
const subscribeToNewsletter = async ( | ||
req: Request, | ||
res: Response, | ||
next: NextFunction, | ||
): Promise<any> => { | ||
try { | ||
const { email } = req.body; | ||
if (!email) { | ||
throw new BadRequest("Email is missing in request body."); | ||
} | ||
const subscriber = await newsLetterSubscriptionService.subscribeUser(email); | ||
res.status(!subscriber.isSubscribe ? 201 : 200).json({ | ||
status: "success", | ||
message: !subscriber.isSubscribe | ||
? "Subscriber subscription successful" | ||
: "You are already subscribed to our newsletter", | ||
}); | ||
} catch (error) { | ||
next(error); | ||
} | ||
}; | ||
|
||
export { subscribeToNewsletter }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { | ||
Entity, | ||
PrimaryGeneratedColumn, | ||
Column, | ||
CreateDateColumn, | ||
} from "typeorm"; | ||
|
||
@Entity() | ||
export class NewsLetterSubscriber { | ||
@PrimaryGeneratedColumn("uuid") | ||
id: string; | ||
|
||
@Column() | ||
email: string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { Router } from "express"; | ||
import { subscribeToNewsletter } from "../controllers/NewsLetterSubscriptionController"; | ||
import { authMiddleware } from "../middleware"; | ||
|
||
const newsLetterSubscriptionRoute = Router(); | ||
|
||
newsLetterSubscriptionRoute.post( | ||
"/newsletter-subscription", | ||
authMiddleware, | ||
subscribeToNewsletter, | ||
); | ||
|
||
export { newsLetterSubscriptionRoute }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { Repository } from "typeorm"; | ||
import { NewsLetterSubscriber } from "../models/newsLetterSubscription"; | ||
import { INewsLetterSubscriptionService } from "../types"; | ||
import AppDataSource from "../data-source"; | ||
import { BadRequest, HttpError } from "../middleware"; | ||
|
||
export class NewsLetterSubscriptionService | ||
implements INewsLetterSubscriptionService | ||
{ | ||
private newsLetterSubscriber: Repository<NewsLetterSubscriber>; | ||
|
||
constructor() { | ||
this.newsLetterSubscriber = | ||
AppDataSource.getRepository(NewsLetterSubscriber); | ||
} | ||
|
||
public async subscribeUser(email: string): Promise<{ | ||
isSubscribe: boolean; | ||
subscriber: NewsLetterSubscriber; | ||
}> { | ||
let isSubscribe = false; | ||
const isExistingSubscriber = await this.newsLetterSubscriber.findOne({ | ||
where: { email }, | ||
}); | ||
if (isExistingSubscriber) { | ||
isSubscribe = true; | ||
return { isSubscribe, subscriber: isExistingSubscriber }; | ||
} | ||
const newSubscriber = new NewsLetterSubscriber(); | ||
newSubscriber.email = email; | ||
const subscriber = await this.newsLetterSubscriber.save(newSubscriber); | ||
if (!subscriber) { | ||
throw new HttpError( | ||
500, | ||
"An error occurred while processing your request", | ||
); | ||
} | ||
return { isSubscribe, subscriber }; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import { Repository } from "typeorm"; | ||
import AppDataSource from "../data-source"; | ||
import { NewsLetterSubscriber } from "../models/newsLetterSubscription"; | ||
import { NewsLetterSubscriptionService } from "../services/newsLetterSubscription.service"; | ||
|
||
jest.mock("../data-source", () => ({ | ||
__esModule: true, | ||
default: { | ||
getRepository: jest.fn(), | ||
initialize: jest.fn(), | ||
isInitialized: false, | ||
}, | ||
})); | ||
|
||
jest.mock("../utils", () => ({ | ||
...jest.requireActual("../utils"), | ||
verifyToken: jest.fn(), | ||
})); | ||
|
||
jest.mock("../models"); | ||
jest.mock("../utils"); | ||
|
||
describe("NewsLetterSubscriptionService", () => { | ||
let newsLetterSubscriptionService: NewsLetterSubscriptionService; | ||
let newsLetterRepositoryMock: jest.Mocked< | ||
Repository<NewsLetterSubscriptionService> | ||
>; | ||
|
||
beforeEach(() => { | ||
newsLetterRepositoryMock = { | ||
findOne: jest.fn(), | ||
save: jest.fn(), | ||
} as any; | ||
|
||
(AppDataSource.getRepository as jest.Mock).mockImplementation((entity) => { | ||
if (entity === NewsLetterSubscriber) return newsLetterRepositoryMock; | ||
}); | ||
|
||
newsLetterSubscriptionService = new NewsLetterSubscriptionService(); | ||
}); | ||
|
||
afterEach(() => { | ||
jest.resetAllMocks(); | ||
}); | ||
|
||
describe("SubscribeToNewsLetter", () => { | ||
it("should subscribe a new user", async () => { | ||
const user = new NewsLetterSubscriber(); | ||
user.email = "[email protected]"; | ||
|
||
const payload = { | ||
email: "[email protected]", | ||
}; | ||
|
||
(newsLetterRepositoryMock.findOne as jest.Mock).mockResolvedValue(null); | ||
(newsLetterRepositoryMock.save as jest.Mock).mockImplementation( | ||
(user) => { | ||
user.id = "456"; | ||
return Promise.resolve(user); | ||
}, | ||
); | ||
const result = | ||
await newsLetterSubscriptionService.subscribeUser("[email protected]"); | ||
|
||
expect(result.isSubscribe).toBe(false); | ||
expect(result.subscriber).toEqual({ | ||
id: "456", | ||
email: "[email protected]", | ||
}); | ||
expect(newsLetterRepositoryMock.save).toHaveBeenCalled(); | ||
}); | ||
|
||
it("should handle already subscribed user", async () => { | ||
const user = new NewsLetterSubscriber(); | ||
user.id = "123"; | ||
user.email = "[email protected]"; | ||
|
||
(newsLetterRepositoryMock.findOne as jest.Mock).mockResolvedValue(user); | ||
(newsLetterRepositoryMock.save as jest.Mock).mockImplementation( | ||
(user) => { | ||
user.id = "456"; | ||
return Promise.resolve(user); | ||
}, | ||
); | ||
const result = | ||
await newsLetterSubscriptionService.subscribeUser("[email protected]"); | ||
|
||
expect(result.isSubscribe).toBe(true); | ||
expect(result.subscriber).toEqual({ | ||
id: "123", | ||
email: "[email protected]", | ||
}); | ||
expect(newsLetterRepositoryMock.save).not.toHaveBeenCalled(); | ||
}); | ||
|
||
it("should throw an error if something goes wrong", async () => { | ||
(newsLetterRepositoryMock.findOne as jest.Mock).mockRejectedValue( | ||
new Error("An error occurred while processing your request"), | ||
); | ||
|
||
await expect( | ||
newsLetterSubscriptionService.subscribeUser("[email protected]"), | ||
).rejects.toThrow("An error occurred while processing your request"); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters