add whatsapp modules

This commit is contained in:
ItsMalma
2025-12-01 18:22:37 +07:00
parent 1068ad9246
commit 393b65830c
16 changed files with 297 additions and 15 deletions

View File

@@ -69,7 +69,7 @@ export class AdminController extends Controller {
const verification = orm.em.create(Verification, {
id: ulid(),
code: generateRandomCode(6),
code: generateRandomCode(6, "0123456789"),
type: VerificationType.createAdmin,
expiredAt: dateFns.addHours(new Date(), 1),
createdAt: new Date(),
@@ -294,7 +294,7 @@ export class AdminController extends Controller {
admin.permissions = body.permissions;
admin.verification = orm.em.create(Verification, {
id: ulid(),
code: generateRandomCode(6),
code: generateRandomCode(6, "1234567890"),
type: VerificationType.updateAdmin,
expiredAt: dateFns.addHours(new Date(), 1),
createdAt: new Date(),
@@ -357,7 +357,7 @@ export class AdminController extends Controller {
admin.email = body.new_email;
admin.verification = orm.em.create(Verification, {
id: ulid(),
code: generateRandomCode(6),
code: generateRandomCode(6, "1234567890"),
type: VerificationType.changeEmailAdmin,
expiredAt: dateFns.addHours(new Date(), 1),
createdAt: new Date(),
@@ -435,7 +435,7 @@ export class AdminController extends Controller {
admin.password = await Bun.password.hash(body.new_password);
admin.verification = orm.em.create(Verification, {
id: ulid(),
code: generateRandomCode(6),
code: generateRandomCode(6, "1234567890"),
type: VerificationType.changePasswordAdmin,
expiredAt: dateFns.addHours(new Date(), 1),
createdAt: new Date(),

View File

@@ -65,7 +65,7 @@ export class OrderController extends Controller {
const verification = orm.em.create(Verification, {
id: ulid(),
code: generateRandomCode(6),
code: generateRandomCode(6, "0123456789"),
type: VerificationType.createOrder,
expiredAt: dateFns.addHours(new Date(), 1),
createdAt: new Date(),

View File

@@ -9,9 +9,11 @@ import type {
ListResponse,
SingleResponse,
} from "@/common/types";
import { generateRandomCode } from "@/common/utils";
import { FlightClass } from "@/database/entities/flight-class.entity";
import { FlightSchedule } from "@/database/entities/flight-schedule.entity";
import { Hotel } from "@/database/entities/hotel.entity";
import { PackageConsultSession } from "@/database/entities/package-consult-session.entity";
import { PackageDetail } from "@/database/entities/package-detail.entity";
import { PackageItineraryDay } from "@/database/entities/package-itinerary-day.entity";
import { PackageItineraryImage } from "@/database/entities/package-itinerary-image.entity";
@@ -35,6 +37,7 @@ import {
packageRequestSchema,
} from "@/modules/package/package.schemas";
import type {
PackageConsultResponse,
PackageDetailResponse,
PackageResponse,
} from "@/modules/package/package.types";
@@ -98,6 +101,47 @@ export class PackageController extends Controller {
} satisfies SingleResponse<PackageResponse>);
}
async consult(req: Request, res: Response) {
const parseParamsResult = packageParamsSchema.safeParse(req.params);
if (!parseParamsResult.success) {
return this.handleZodError(parseParamsResult.error, res, "params");
}
const params = parseParamsResult.data;
const package_ = await orm.em.findOne(
Package,
{ id: params.id },
{ populate: ["*"] },
);
if (!package_) {
return res.status(404).json({
data: null,
errors: [
{
path: "id",
location: "params",
message: "Package not found.",
},
],
} satisfies ErrorResponse);
}
const consultSession = orm.em.create(PackageConsultSession, {
id: ulid(),
code: generateRandomCode(6, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"),
package: package_,
createdAt: new Date(),
updatedAt: new Date(),
});
return res.status(200).json({
data: {
session_code: consultSession.code,
},
errors: null,
} satisfies SingleResponse<PackageConsultResponse>);
}
async list(req: Request, res: Response) {
const parseQueryResult = paginationQuerySchema.safeParse(req.query);
if (!parseQueryResult.success) {
@@ -1325,6 +1369,11 @@ export class PackageController extends Controller {
isAdminMiddleware(this.jwtService, [AdminPermission.createPackage]),
this.create.bind(this),
);
router.post(
"/:id/consult",
createOrmContextMiddleware,
this.consult.bind(this),
);
router.get("/", createOrmContextMiddleware, this.list.bind(this));
router.get("/:id", createOrmContextMiddleware, this.view.bind(this));
router.put(

View File

@@ -33,6 +33,10 @@ export type PackageResponse = {
updated_at: Date;
};
export type PackageConsultResponse = {
session_code: string;
};
export type PackageItineraryWidgetResponse =
| {
type: "transport";

View File

@@ -67,7 +67,7 @@ export class PartnerController extends Controller {
const verification = orm.em.create(Verification, {
id: ulid(),
code: generateRandomCode(6),
code: generateRandomCode(6, "0123456789"),
type: VerificationType.createPartner,
expiredAt: dateFns.addHours(new Date(), 1),
createdAt: new Date(),
@@ -281,7 +281,7 @@ export class PartnerController extends Controller {
partner.name = body.name;
partner.verification = orm.em.create(Verification, {
id: ulid(),
code: generateRandomCode(6),
code: generateRandomCode(6, "0123456789"),
type: VerificationType.updatePartner,
expiredAt: dateFns.addHours(new Date(), 1),
createdAt: new Date(),
@@ -344,7 +344,7 @@ export class PartnerController extends Controller {
partner.email = body.new_email;
partner.verification = orm.em.create(Verification, {
id: ulid(),
code: generateRandomCode(6),
code: generateRandomCode(6, "0123456789"),
type: VerificationType.changeEmailPartner,
expiredAt: dateFns.addHours(new Date(), 1),
createdAt: new Date(),
@@ -422,7 +422,7 @@ export class PartnerController extends Controller {
partner.password = await Bun.password.hash(body.new_password);
partner.verification = orm.em.create(Verification, {
id: ulid(),
code: generateRandomCode(6),
code: generateRandomCode(6, "0123456789"),
type: VerificationType.changePasswordPartner,
expiredAt: dateFns.addHours(new Date(), 1),
createdAt: new Date(),

View File

@@ -0,0 +1,92 @@
import { Controller } from "@/common/controller";
import { createOrmContextMiddleware } from "@/common/middlewares/create-orm-context.middleware";
import type { AbstractWhatsAppService } from "@/common/services/whatsapp-service/abstract.whatsapp-service";
import { whatsAppBusinessConfig } from "@/configs/whatsapp-business.config";
import { PackageConsultSession } from "@/database/entities/package-consult-session.entity";
import { orm } from "@/database/orm";
import type { WhatsAppWebhookPayload } from "@/modules/whatsapp/whatsapp.types";
import { Router, type Request, type Response } from "express";
export class WhatsAppController extends Controller {
public constructor(
private readonly whatsAppService: AbstractWhatsAppService,
) {
super();
}
async webhookVerification(req: Request, res: Response) {
const hubMode = req.query["hub.mode"];
const hubVerifyToken = req.query["hub.verify_token"];
const hubChallenge = req.query["hub.challenge"];
if (
hubMode === "subscribe" &&
hubVerifyToken === whatsAppBusinessConfig.webhookVerifyToken
) {
res.status(200).send(hubChallenge);
} else {
res.status(403).send();
}
}
async webhook(req: Request, res: Response) {
const body = req.body as WhatsAppWebhookPayload;
console.log(JSON.stringify(body, null, 2));
const messageText = body.entry[0].changes[0].value.messages[0].text.body;
const sessionCode = messageText
.trim()
.match(/session\s*#?\s*([a-zA-Z0-9]{4,})/i)?.[0];
if (!sessionCode) {
return res.status(200).send();
}
const packageConsultSession = await orm.em.findOne(PackageConsultSession, {
code: sessionCode,
});
if (!packageConsultSession) {
return res.status(200).send();
}
const partner = packageConsultSession.package.partner;
const timeOfDay = new Date().getHours();
let salutation: string;
if (timeOfDay > 4 && timeOfDay < 12) {
salutation = "pagi";
} else if (timeOfDay >= 12 && timeOfDay < 15) {
salutation = "siang";
} else if (timeOfDay >= 15 && timeOfDay < 18) {
salutation = "sore";
} else {
salutation = "malam";
}
console.log(this.whatsAppService);
await this.whatsAppService.sendMessage(
body.entry[0].changes[0].value.messages[0].from,
`Selamat ${salutation},
Anda akan terhubung dengan mitra kami untuk konsultasi.
Saudara ${"Adam Akmal Madani"}
Nomor: ${"085218606125"}
Terima kasih.`,
);
return res.status(200).send();
}
public buildRouter(): Router {
const router = Router();
router.get("/webhook", this.webhookVerification.bind(this));
router.post(
"/webhook",
createOrmContextMiddleware,
this.webhook.bind(this),
);
return router;
}
}

View File

@@ -0,0 +1,31 @@
export type WhatsAppWebhookPayload = {
object: "whatsapp_business_account";
entry: {
id: string;
changes: {
value: {
messaging_product: "whatsapp";
metadata: {
display_phone_number: string;
phone_number_id: string;
};
contacts: {
profile: {
name: string;
};
wa_id: string;
}[];
messages: {
from: string;
id: string;
timestamp: string;
text: {
body: string;
};
type: "text";
}[];
};
field: "messages";
}[];
}[];
};