From ae471aadde1736d71116e28e149387297f914e81 Mon Sep 17 00:00:00 2001 From: Nainah23 <133117208+Nainah23@users.noreply.github.com> Date: Thu, 8 Aug 2024 12:38:03 +0300 Subject: [PATCH 1/4] feat: Create Squeeze Page --- src/controllers/SqueezeController.ts | 83 ++++++++++++++++++++++++++++ src/controllers/index.ts | 1 + src/middleware/index.ts | 2 +- src/models/index.ts | 1 + src/models/squeeze.ts | 48 ++++++++++++++++ src/routes/index.ts | 1 + src/routes/squeeze.ts | 15 +++++ src/schema/squeezeSchema.ts | 68 +++++++++++++++++++++++ src/services/index.ts | 1 + src/services/squeezeService.ts | 33 +++++++++++ 10 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 src/controllers/SqueezeController.ts create mode 100644 src/models/squeeze.ts create mode 100644 src/routes/squeeze.ts create mode 100644 src/schema/squeezeSchema.ts create mode 100644 src/services/squeezeService.ts diff --git a/src/controllers/SqueezeController.ts b/src/controllers/SqueezeController.ts new file mode 100644 index 00000000..402bf391 --- /dev/null +++ b/src/controllers/SqueezeController.ts @@ -0,0 +1,83 @@ +import { Request, Response } from "express"; +import { SqueezeService } from "../services"; + + +class SqueezeController { + /** + * @openapi + * /api/v1/squeeze-pages: + * post: + * tags: + * - Squeeze + * summary: Create a new squeeze + * description: Create a new squeeze entry. + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * email: + * type: string + * first_name: + * type: string + * last_name: + * type: string + * phone: + * type: string + * location: + * type: string + * job_title: + * type: string + * company: + * type: string + * interests: + * type: array + * referral_source: + * type: string + * required: + * - email + * - first_name + * - last_name + * responses: + * 201: + * description: Squeeze created successfully + * content: + * application/json: + * schema: + * type: object + * properties: + * status: + * type: string + * example: "success" + * message: + * type: string + * example: "Squeeze created successfully" + * data: + * type: object + * 400: + * description: Bad request + * 409: + * description: Conflict + */ + public createSqueeze = async (req: Request, res: Response) => { + try { + const squeezeData = req.body; + const squeeze = await SqueezeService.createSqueeze(squeezeData); + res.status(201).json({ + status: "success", + message: "Squeeze record created successfully.", + data: squeeze, + }); + } catch (error) { + res.status(500).json({ + status: "error", + message: "An error occurred while creating the squeeze record.", + error: error.message, + }); + } + }; +} + +export { SqueezeController }; \ No newline at end of file diff --git a/src/controllers/index.ts b/src/controllers/index.ts index 7ec9c282..bb90c59a 100644 --- a/src/controllers/index.ts +++ b/src/controllers/index.ts @@ -17,3 +17,4 @@ export * from "./contactController"; export * from "./FaqController"; export * from "./OrgController"; export * from "./runTestController"; +export * from "./SqueezeController"; diff --git a/src/middleware/index.ts b/src/middleware/index.ts index 44396361..967050d0 100644 --- a/src/middleware/index.ts +++ b/src/middleware/index.ts @@ -1,4 +1,4 @@ export * from "./error"; export * from "./auth"; export * from "./checkUserRole"; -export * from "./organizationValidation"; +export * from "./organizationValidation"; \ No newline at end of file diff --git a/src/models/index.ts b/src/models/index.ts index e8a14eb5..c812ba84 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -20,3 +20,4 @@ export * from "./invitation"; export * from "./contact-us"; export * from "./faq"; export * from "./orgInviteToken"; +export * from "./squeeze"; diff --git a/src/models/squeeze.ts b/src/models/squeeze.ts new file mode 100644 index 00000000..95798152 --- /dev/null +++ b/src/models/squeeze.ts @@ -0,0 +1,48 @@ +import { + Entity, + Column, + PrimaryGeneratedColumn, + CreateDateColumn, + UpdateDateColumn, + } from "typeorm"; + + @Entity() + class Squeeze { + @PrimaryGeneratedColumn("uuid") + id: string; + + @Column({ unique: true }) + email: string; + + @Column({ nullable: true }) + first_name?: string; + + @Column({ nullable: true }) + last_name?: string; + + @Column({ nullable: true }) + phone?: string; + + @Column({ nullable: true }) + location?: string; + + @Column({ nullable: true }) + job_title?: string; + + @Column({ nullable: true }) + company?: string; + + @Column("simple-array", { nullable: true }) + interests?: string[]; + + @Column({ nullable: true }) + referral_source?: string; + + @CreateDateColumn() + createdAt: Date; + + @UpdateDateColumn() + updatedAt: Date; + } + + export { Squeeze }; \ No newline at end of file diff --git a/src/routes/index.ts b/src/routes/index.ts index 2d1594e1..afed24f7 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -17,3 +17,4 @@ export * from "./testimonial"; export * from "./user"; export * from "./faq"; export * from "./run-test"; +export * from "./squeeze"; diff --git a/src/routes/squeeze.ts b/src/routes/squeeze.ts new file mode 100644 index 00000000..069a259d --- /dev/null +++ b/src/routes/squeeze.ts @@ -0,0 +1,15 @@ +import { Router } from "express"; +import { SqueezeController } from "../controllers"; +import { squeezeSchema } from "../schema/squeezeSchema"; +import { authMiddleware } from "../middleware"; + +const squeezeRoute = Router(); +const squeezecontroller = new SqueezeController(); + +squeezeRoute.post( + "/squeeze-pages", + authMiddleware, + squeezecontroller.createSqueeze.bind(squeezecontroller), +); + +export { squeezeRoute }; \ No newline at end of file diff --git a/src/schema/squeezeSchema.ts b/src/schema/squeezeSchema.ts new file mode 100644 index 00000000..203ecb75 --- /dev/null +++ b/src/schema/squeezeSchema.ts @@ -0,0 +1,68 @@ +import { z } from "zod"; + +/** + * @openapi + * components: + * schemas: + * Squeeze: + * type: object + * properties: + * id: + * type: string + * format: uuid + * example: "e02c7c4e-bb92-4f9a-8d91-bc9e63c5b8d5" + * email: + * type: string + * format: email + * example: "naina@example.com" + * first_name: + * type: string + * example: "Nainah" + * last_name: + * type: string + * example: "Kamah" + * phone: + * type: string + * example: "+254123456789" + * location: + * type: string + * example: "Nairobi" + * job_title: + * type: string + * example: "Backend Engineer" + * company: + * type: string + * example: "HNG" + * interests: + * type: array + * example: ["Tech", "Politics", "Youth"] + * referral_source: + * type: string + * example: "Internet" + * createdAt: + * type: string + * format: date-time + * example: "2024-08-03T14:00:00Z" + * updatedAt: + * type: string + * format: date-time + * example: "2024-08-03T14:00:00Z" + * required: + * - id + * - email + * - createdAt + * - updatedAt + */ +const squeezeSchema = z.object({ + email: z.string().email("Invalid email address"), + first_name: z.string().min(1, "First name is required"), + last_name: z.string().min(1, "Last name is required"), + phone: z.string().optional(), + location: z.string().optional(), + job_title: z.string().optional(), + company: z.string().optional(), + interests: z.array(z.string()).optional(), + referral_source: z.string().optional(), +}); + +export { squeezeSchema }; \ No newline at end of file diff --git a/src/services/index.ts b/src/services/index.ts index 9d35fb67..f7db0185 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -10,3 +10,4 @@ export * from "./payment/flutter.service"; export * from "./contactService"; export * from "./faq.services"; export * from "./org.services"; +export * from "./squeezeService"; diff --git a/src/services/squeezeService.ts b/src/services/squeezeService.ts new file mode 100644 index 00000000..9816ebc1 --- /dev/null +++ b/src/services/squeezeService.ts @@ -0,0 +1,33 @@ +import { Squeeze } from "../models"; +import AppDataSource from "../data-source"; +import { Conflict, ResourceNotFound } from "../middleware"; +import { squeezeSchema } from "../schema/squeezeSchema"; + +const squeezeRepository = AppDataSource.getRepository(Squeeze); + +class SqueezeService { + static async createSqueeze(data: Partial): Promise { + const validation = squeezeSchema.safeParse(data); + if (!validation.success) { + throw new Conflict( + "Validation failed: " + + validation.error.errors.map((e) => e.message).join(", "), + ); + } + + const existingSqueeze = await squeezeRepository.findOne({ + where: { email: data.email }, + }); + + if (existingSqueeze) { + throw new Conflict( + "A squeeze has already been generated using this email", + ); + } + const squeeze = squeezeRepository.create(data); + const savedSqueeze = await squeezeRepository.save(squeeze); + return savedSqueeze; + } +} + +export { SqueezeService }; \ No newline at end of file From 8d42712032740a234b9fa23621dfb50d9ad315ac Mon Sep 17 00:00:00 2001 From: Nainah23 <133117208+Nainah23@users.noreply.github.com> Date: Thu, 8 Aug 2024 13:09:37 +0300 Subject: [PATCH 2/4] fix: CI/CD errors --- src/services/squeezeService.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/services/squeezeService.ts b/src/services/squeezeService.ts index 9816ebc1..0b9de156 100644 --- a/src/services/squeezeService.ts +++ b/src/services/squeezeService.ts @@ -6,13 +6,13 @@ import { squeezeSchema } from "../schema/squeezeSchema"; const squeezeRepository = AppDataSource.getRepository(Squeeze); class SqueezeService { - static async createSqueeze(data: Partial): Promise { - const validation = squeezeSchema.safeParse(data); - if (!validation.success) { - throw new Conflict( - "Validation failed: " + - validation.error.errors.map((e) => e.message).join(", "), - ); + public async createSqueeze(data: Partial): Promise { + const validation = squeezeSchema.safeParse(data); + if (!validation.success) { + throw new Conflict( + "Validation failed: " + + validation.error.errors.map((e) => e.message).join(", "), + ); } const existingSqueeze = await squeezeRepository.findOne({ From 88ea1c8d423e3cb4baa309808203020f43954115 Mon Sep 17 00:00:00 2001 From: Nainah23 <133117208+Nainah23@users.noreply.github.com> Date: Thu, 8 Aug 2024 13:49:34 +0300 Subject: [PATCH 3/4] Fix error handling and repository initialization in SqueezeService --- src/routes/squeeze.ts | 1 - src/services/squeezeService.ts | 48 ++++++++++++++++++++-------------- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/src/routes/squeeze.ts b/src/routes/squeeze.ts index 069a259d..3a66db6a 100644 --- a/src/routes/squeeze.ts +++ b/src/routes/squeeze.ts @@ -1,6 +1,5 @@ import { Router } from "express"; import { SqueezeController } from "../controllers"; -import { squeezeSchema } from "../schema/squeezeSchema"; import { authMiddleware } from "../middleware"; const squeezeRoute = Router(); diff --git a/src/services/squeezeService.ts b/src/services/squeezeService.ts index 0b9de156..cba1e034 100644 --- a/src/services/squeezeService.ts +++ b/src/services/squeezeService.ts @@ -1,33 +1,41 @@ import { Squeeze } from "../models"; import AppDataSource from "../data-source"; -import { Conflict, ResourceNotFound } from "../middleware"; +import { Conflict, BadRequest } from "../middleware"; import { squeezeSchema } from "../schema/squeezeSchema"; - -const squeezeRepository = AppDataSource.getRepository(Squeeze); +import { Repository } from "typeorm"; class SqueezeService { - public async createSqueeze(data: Partial): Promise { - const validation = squeezeSchema.safeParse(data); - if (!validation.success) { - throw new Conflict( - "Validation failed: " + - validation.error.errors.map((e) => e.message).join(", "), - ); - } + private squeezeRepository: Repository; - const existingSqueeze = await squeezeRepository.findOne({ - where: { email: data.email }, - }); + constructor() { + this.squeezeRepository = AppDataSource.getRepository(Squeeze); + } - if (existingSqueeze) { + public async createSqueeze(data: Partial): Promise { + const validation = squeezeSchema.safeParse(data); + if (!validation.success) { throw new Conflict( - "A squeeze has already been generated using this email", + "Validation failed: " + + validation.error.errors.map((e) => e.message).join(", ") ); } - const squeeze = squeezeRepository.create(data); - const savedSqueeze = await squeezeRepository.save(squeeze); - return savedSqueeze; + + try { + const existingSqueeze = await this.squeezeRepository.findOne({ + where: { email: data.email }, + }); + + if (existingSqueeze) { + throw new Conflict("A squeeze has already been generated using this email"); + } + + const squeeze = this.squeezeRepository.create(data); + const savedSqueeze = await this.squeezeRepository.save(squeeze); + return savedSqueeze; + } catch (error) { + throw new BadRequest("Failed to create squeeze: " + error.message); + } } } -export { SqueezeService }; \ No newline at end of file +export { SqueezeService }; From 630b3443ba027f4ddb95b0cf3b61142f4c7cf3b8 Mon Sep 17 00:00:00 2001 From: Nainah23 <133117208+Nainah23@users.noreply.github.com> Date: Thu, 8 Aug 2024 14:08:03 +0300 Subject: [PATCH 4/4] fix: Instantiate the SqueezeService --- src/controllers/SqueezeController.ts | 41 ++++++++++++++++------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/src/controllers/SqueezeController.ts b/src/controllers/SqueezeController.ts index 402bf391..744d9a3f 100644 --- a/src/controllers/SqueezeController.ts +++ b/src/controllers/SqueezeController.ts @@ -1,8 +1,13 @@ import { Request, Response } from "express"; import { SqueezeService } from "../services"; - class SqueezeController { + private squeezeService: SqueezeService; + + constructor() { + this.squeezeService = new SqueezeService(); + } + /** * @openapi * /api/v1/squeeze-pages: @@ -62,22 +67,22 @@ class SqueezeController { * description: Conflict */ public createSqueeze = async (req: Request, res: Response) => { - try { - const squeezeData = req.body; - const squeeze = await SqueezeService.createSqueeze(squeezeData); - res.status(201).json({ - status: "success", - message: "Squeeze record created successfully.", - data: squeeze, - }); - } catch (error) { - res.status(500).json({ - status: "error", - message: "An error occurred while creating the squeeze record.", - error: error.message, - }); - } - }; + try { + const squeezeData = req.body; + const squeeze = await this.squeezeService.createSqueeze(squeezeData); // Use the instance method + res.status(201).json({ + status: "success", + message: "Squeeze record created successfully.", + data: squeeze, + }); + } catch (error) { + res.status(500).json({ + status: "error", + message: "An error occurred while creating the squeeze record.", + error: error.message, + }); + } + }; } -export { SqueezeController }; \ No newline at end of file +export { SqueezeController };