Skip to content

Commit

Permalink
Merge branch 'dev' into feat/Create-Squeeze-Page
Browse files Browse the repository at this point in the history
  • Loading branch information
Nainah23 authored Aug 8, 2024
2 parents 88ea1c8 + 3bd6295 commit 1ce23b8
Show file tree
Hide file tree
Showing 8 changed files with 280 additions and 0 deletions.
94 changes: 94 additions & 0 deletions src/controllers/NewsLetterSubscriptionController.ts
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 };
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
faqRouter,
helpRouter,
jobRouter,
newsLetterSubscriptionRoute,
notificationRouter,
paymentFlutterwaveRouter,
paymentRouter,
Expand Down Expand Up @@ -87,6 +88,7 @@ server.use("/api/v1", blogRouter);
server.use("/api/v1", contactRouter);
server.use("/api/v1", jobRouter);
server.use("/api/v1", roleRouter);
server.use("/api/v1", newsLetterSubscriptionRoute);

server.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerSpec));

Expand Down
15 changes: 15 additions & 0 deletions src/models/newsLetterSubscription.ts
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;
}
2 changes: 2 additions & 0 deletions src/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@ export * from "./user";
export * from "./faq";
export * from "./run-test";
export * from "./squeeze";
export * from "./newsLetterSubscription";

13 changes: 13 additions & 0 deletions src/routes/newsLetterSubscription.ts
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 };
40 changes: 40 additions & 0 deletions src/services/newsLetterSubscription.service.ts
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 };
}
}
106 changes: 106 additions & 0 deletions src/test/newsLetterSubscription.spec.ts
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");
});
});
});
8 changes: 8 additions & 0 deletions src/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,11 @@ export interface GoogleUser {
picture: string;
sub: string;
}

export interface INewsLetterSubscriptionService {
subscribeUser(email: string): Promise<any>;
}

export interface INewsLetterSubscription {
email: string;
}

0 comments on commit 1ce23b8

Please sign in to comment.