add whatsapp modules
This commit is contained in:
@@ -19,3 +19,8 @@ MAIL_HOST=
|
|||||||
MAIL_PORT=
|
MAIL_PORT=
|
||||||
MAIL_USERNAME=
|
MAIL_USERNAME=
|
||||||
MAIL_PASSWORD=
|
MAIL_PASSWORD=
|
||||||
|
|
||||||
|
WHATSAPP_BUSINESS_ACCESS_TOKEN=
|
||||||
|
WHATSAPP_BUSINESS_PHONE_NUMBER_ID=
|
||||||
|
WHATSAPP_BUSINESS_ACCOUNT_ID=
|
||||||
|
WHATSAPP_BUSINESS_WEBHOOK_VERIFY_TOKEN=
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import type { AbstractJwtService } from "@/common/services/jwt-service/abstract.
|
|||||||
import { LibraryJwtService } from "@/common/services/jwt-service/library.jwt-service";
|
import { LibraryJwtService } from "@/common/services/jwt-service/library.jwt-service";
|
||||||
import type { AbstractPaymentService } from "@/common/services/payment-service/abstract.payment-service";
|
import type { AbstractPaymentService } from "@/common/services/payment-service/abstract.payment-service";
|
||||||
import { MidtransPaymentService } from "@/common/services/payment-service/midtrans.payment-service";
|
import { MidtransPaymentService } from "@/common/services/payment-service/midtrans.payment-service";
|
||||||
|
import type { AbstractWhatsAppService } from "@/common/services/whatsapp-service/abstract.whatsapp-service";
|
||||||
|
import { MetaWhatsAppService } from "@/common/services/whatsapp-service/meta.whatsapp-service";
|
||||||
import { serverConfig } from "@/configs/server.config";
|
import { serverConfig } from "@/configs/server.config";
|
||||||
import { AdminController } from "@/modules/admin/admin.controller";
|
import { AdminController } from "@/modules/admin/admin.controller";
|
||||||
import { AdminMapper } from "@/modules/admin/admin.mapper";
|
import { AdminMapper } from "@/modules/admin/admin.mapper";
|
||||||
@@ -36,6 +38,7 @@ import { TransportationClassController } from "@/modules/transportation-class/tr
|
|||||||
import { TransportationClassMapper } from "@/modules/transportation-class/transportation-class.mapper";
|
import { TransportationClassMapper } from "@/modules/transportation-class/transportation-class.mapper";
|
||||||
import { TransportationController } from "@/modules/transportation/transportation.controller";
|
import { TransportationController } from "@/modules/transportation/transportation.controller";
|
||||||
import { TransportationMapper } from "@/modules/transportation/transportation.mapper";
|
import { TransportationMapper } from "@/modules/transportation/transportation.mapper";
|
||||||
|
import { WhatsAppController } from "@/modules/whatsapp/whatsapp.controller";
|
||||||
import compression from "compression";
|
import compression from "compression";
|
||||||
import cors from "cors";
|
import cors from "cors";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
@@ -49,6 +52,7 @@ export class Application {
|
|||||||
private _fileStorage!: AbstractFileStorage;
|
private _fileStorage!: AbstractFileStorage;
|
||||||
private _jwtService!: AbstractJwtService;
|
private _jwtService!: AbstractJwtService;
|
||||||
private _paymentService!: AbstractPaymentService;
|
private _paymentService!: AbstractPaymentService;
|
||||||
|
private _whatsAppService!: AbstractWhatsAppService;
|
||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
this._app = express();
|
this._app = express();
|
||||||
@@ -59,6 +63,7 @@ export class Application {
|
|||||||
this._fileStorage = new LocalFileStorage();
|
this._fileStorage = new LocalFileStorage();
|
||||||
this._jwtService = new LibraryJwtService();
|
this._jwtService = new LibraryJwtService();
|
||||||
this._paymentService = new MidtransPaymentService();
|
this._paymentService = new MidtransPaymentService();
|
||||||
|
this._whatsAppService = new MetaWhatsAppService();
|
||||||
}
|
}
|
||||||
|
|
||||||
public initializeMiddlewares() {
|
public initializeMiddlewares() {
|
||||||
@@ -82,13 +87,14 @@ export class Application {
|
|||||||
const transportationClassMapper = new TransportationClassMapper(
|
const transportationClassMapper = new TransportationClassMapper(
|
||||||
transportationMapper,
|
transportationMapper,
|
||||||
);
|
);
|
||||||
|
const partnerMapper = new PartnerMapper();
|
||||||
const packageMapper = new PackageMapper(
|
const packageMapper = new PackageMapper(
|
||||||
|
partnerMapper,
|
||||||
flightMapper,
|
flightMapper,
|
||||||
hotelMapper,
|
hotelMapper,
|
||||||
transportationMapper,
|
transportationMapper,
|
||||||
);
|
);
|
||||||
const adminMapper = new AdminMapper();
|
const adminMapper = new AdminMapper();
|
||||||
const partnerMapper = new PartnerMapper();
|
|
||||||
const orderMapper = new OrderMapper(packageMapper, partnerMapper);
|
const orderMapper = new OrderMapper(packageMapper, partnerMapper);
|
||||||
|
|
||||||
const countryRouter = new CountryController(
|
const countryRouter = new CountryController(
|
||||||
@@ -155,6 +161,9 @@ export class Application {
|
|||||||
this._jwtService,
|
this._jwtService,
|
||||||
).buildRouter();
|
).buildRouter();
|
||||||
const staticRouter = new StaticController(this._fileStorage).buildRouter();
|
const staticRouter = new StaticController(this._fileStorage).buildRouter();
|
||||||
|
const whatsAppRouter = new WhatsAppController(
|
||||||
|
this._whatsAppService,
|
||||||
|
).buildRouter();
|
||||||
|
|
||||||
this._app.use("/countries", countryRouter);
|
this._app.use("/countries", countryRouter);
|
||||||
this._app.use("/cities", cityRouter);
|
this._app.use("/cities", cityRouter);
|
||||||
@@ -171,6 +180,7 @@ export class Application {
|
|||||||
this._app.use("/partners", partnerRouter);
|
this._app.use("/partners", partnerRouter);
|
||||||
this._app.use("/orders", orderRouter);
|
this._app.use("/orders", orderRouter);
|
||||||
this._app.use("/statics", staticRouter);
|
this._app.use("/statics", staticRouter);
|
||||||
|
this._app.use("/whatsapp", whatsAppRouter);
|
||||||
}
|
}
|
||||||
|
|
||||||
public initializeErrorHandlers() {}
|
public initializeErrorHandlers() {}
|
||||||
|
|||||||
5
src/common/errors/whatsapp.error.ts
Normal file
5
src/common/errors/whatsapp.error.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export class WhatsAppError extends Error {
|
||||||
|
public constructor(message: string) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export abstract class AbstractWhatsAppService {
|
||||||
|
public abstract sendMessage(to: string, message: string): Promise<void>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { WhatsAppError } from "@/common/errors/whatsapp.error";
|
||||||
|
import { AbstractWhatsAppService } from "@/common/services/whatsapp-service/abstract.whatsapp-service";
|
||||||
|
import { whatsAppBusinessConfig } from "@/configs/whatsapp-business.config";
|
||||||
|
|
||||||
|
export class MetaWhatsAppService extends AbstractWhatsAppService {
|
||||||
|
public async sendMessage(to: string, message: string): Promise<void> {
|
||||||
|
const response = await fetch(
|
||||||
|
`https://graph.facebook.com/v22.0/${whatsAppBusinessConfig.phoneNumberId}/messages`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${whatsAppBusinessConfig.accessToken}`,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
messaging_product: "whatsapp",
|
||||||
|
to,
|
||||||
|
type: "text",
|
||||||
|
text: { body: message },
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error(await response.json());
|
||||||
|
throw new WhatsAppError("Failed to send WhatsApp message");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
export function generateRandomCode(length: number): string {
|
export function generateRandomCode(length: number, characters: string): string {
|
||||||
const numbers = "0123456789";
|
|
||||||
|
|
||||||
let result = "";
|
let result = "";
|
||||||
for (let i = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) {
|
||||||
const randomIndex = Math.floor(Math.random() * numbers.length);
|
const randomIndex = Math.floor(Math.random() * characters.length);
|
||||||
result += numbers[randomIndex];
|
result += characters[randomIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -52,5 +52,18 @@ export const _env = z
|
|||||||
.min(0, "Min 0."),
|
.min(0, "Min 0."),
|
||||||
MAIL_USERNAME: z.string("Must be string.").nonempty("Must not empty."),
|
MAIL_USERNAME: z.string("Must be string.").nonempty("Must not empty."),
|
||||||
MAIL_PASSWORD: z.string("Must be string.").nonempty("Must not empty."),
|
MAIL_PASSWORD: z.string("Must be string.").nonempty("Must not empty."),
|
||||||
|
|
||||||
|
WHATSAPP_BUSINESS_ACCESS_TOKEN: z
|
||||||
|
.string("Must be string.")
|
||||||
|
.nonempty("Must not empty."),
|
||||||
|
WHATSAPP_BUSINESS_PHONE_NUMBER_ID: z
|
||||||
|
.string("Must be string.")
|
||||||
|
.nonempty("Must not empty."),
|
||||||
|
WHATSAPP_BUSINESS_ACCOUNT_ID: z
|
||||||
|
.string("Must be string.")
|
||||||
|
.nonempty("Must not empty."),
|
||||||
|
WHATSAPP_BUSINESS_WEBHOOK_VERIFY_TOKEN: z
|
||||||
|
.string("Must be string.")
|
||||||
|
.nonempty("Must not empty."),
|
||||||
})
|
})
|
||||||
.parse(Bun.env);
|
.parse(Bun.env);
|
||||||
|
|||||||
8
src/configs/whatsapp-business.config.ts
Normal file
8
src/configs/whatsapp-business.config.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { _env } from "@/configs/_env";
|
||||||
|
|
||||||
|
export const whatsAppBusinessConfig = {
|
||||||
|
accessToken: _env.WHATSAPP_BUSINESS_ACCESS_TOKEN,
|
||||||
|
phoneNumberId: _env.WHATSAPP_BUSINESS_PHONE_NUMBER_ID,
|
||||||
|
accountId: _env.WHATSAPP_BUSINESS_ACCOUNT_ID,
|
||||||
|
webhookVerifyToken: _env.WHATSAPP_BUSINESS_WEBHOOK_VERIFY_TOKEN,
|
||||||
|
} as const;
|
||||||
35
src/database/entities/package-consult-session.entity.ts
Normal file
35
src/database/entities/package-consult-session.entity.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { Package } from "@/database/entities/package.entity";
|
||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
ManyToOne,
|
||||||
|
PrimaryKey,
|
||||||
|
Property,
|
||||||
|
Unique,
|
||||||
|
type Rel,
|
||||||
|
} from "@mikro-orm/core";
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class PackageConsultSession {
|
||||||
|
@PrimaryKey({ type: "varchar", length: 30 })
|
||||||
|
id!: string;
|
||||||
|
|
||||||
|
@Property({ type: "varchar", length: 6 })
|
||||||
|
@Unique()
|
||||||
|
code!: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => Package)
|
||||||
|
package!: Rel<Package>;
|
||||||
|
|
||||||
|
@Property({
|
||||||
|
type: "timestamp",
|
||||||
|
onCreate: () => new Date(),
|
||||||
|
})
|
||||||
|
createdAt!: Date;
|
||||||
|
|
||||||
|
@Property({
|
||||||
|
type: "timestamp",
|
||||||
|
onCreate: () => new Date(),
|
||||||
|
onUpdate: () => new Date(),
|
||||||
|
})
|
||||||
|
updatedAt!: Date;
|
||||||
|
}
|
||||||
@@ -69,7 +69,7 @@ export class AdminController extends Controller {
|
|||||||
|
|
||||||
const verification = orm.em.create(Verification, {
|
const verification = orm.em.create(Verification, {
|
||||||
id: ulid(),
|
id: ulid(),
|
||||||
code: generateRandomCode(6),
|
code: generateRandomCode(6, "0123456789"),
|
||||||
type: VerificationType.createAdmin,
|
type: VerificationType.createAdmin,
|
||||||
expiredAt: dateFns.addHours(new Date(), 1),
|
expiredAt: dateFns.addHours(new Date(), 1),
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
@@ -294,7 +294,7 @@ export class AdminController extends Controller {
|
|||||||
admin.permissions = body.permissions;
|
admin.permissions = body.permissions;
|
||||||
admin.verification = orm.em.create(Verification, {
|
admin.verification = orm.em.create(Verification, {
|
||||||
id: ulid(),
|
id: ulid(),
|
||||||
code: generateRandomCode(6),
|
code: generateRandomCode(6, "1234567890"),
|
||||||
type: VerificationType.updateAdmin,
|
type: VerificationType.updateAdmin,
|
||||||
expiredAt: dateFns.addHours(new Date(), 1),
|
expiredAt: dateFns.addHours(new Date(), 1),
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
@@ -357,7 +357,7 @@ export class AdminController extends Controller {
|
|||||||
admin.email = body.new_email;
|
admin.email = body.new_email;
|
||||||
admin.verification = orm.em.create(Verification, {
|
admin.verification = orm.em.create(Verification, {
|
||||||
id: ulid(),
|
id: ulid(),
|
||||||
code: generateRandomCode(6),
|
code: generateRandomCode(6, "1234567890"),
|
||||||
type: VerificationType.changeEmailAdmin,
|
type: VerificationType.changeEmailAdmin,
|
||||||
expiredAt: dateFns.addHours(new Date(), 1),
|
expiredAt: dateFns.addHours(new Date(), 1),
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
@@ -435,7 +435,7 @@ export class AdminController extends Controller {
|
|||||||
admin.password = await Bun.password.hash(body.new_password);
|
admin.password = await Bun.password.hash(body.new_password);
|
||||||
admin.verification = orm.em.create(Verification, {
|
admin.verification = orm.em.create(Verification, {
|
||||||
id: ulid(),
|
id: ulid(),
|
||||||
code: generateRandomCode(6),
|
code: generateRandomCode(6, "1234567890"),
|
||||||
type: VerificationType.changePasswordAdmin,
|
type: VerificationType.changePasswordAdmin,
|
||||||
expiredAt: dateFns.addHours(new Date(), 1),
|
expiredAt: dateFns.addHours(new Date(), 1),
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export class OrderController extends Controller {
|
|||||||
|
|
||||||
const verification = orm.em.create(Verification, {
|
const verification = orm.em.create(Verification, {
|
||||||
id: ulid(),
|
id: ulid(),
|
||||||
code: generateRandomCode(6),
|
code: generateRandomCode(6, "0123456789"),
|
||||||
type: VerificationType.createOrder,
|
type: VerificationType.createOrder,
|
||||||
expiredAt: dateFns.addHours(new Date(), 1),
|
expiredAt: dateFns.addHours(new Date(), 1),
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
|
|||||||
@@ -9,9 +9,11 @@ import type {
|
|||||||
ListResponse,
|
ListResponse,
|
||||||
SingleResponse,
|
SingleResponse,
|
||||||
} from "@/common/types";
|
} from "@/common/types";
|
||||||
|
import { generateRandomCode } from "@/common/utils";
|
||||||
import { FlightClass } from "@/database/entities/flight-class.entity";
|
import { FlightClass } from "@/database/entities/flight-class.entity";
|
||||||
import { FlightSchedule } from "@/database/entities/flight-schedule.entity";
|
import { FlightSchedule } from "@/database/entities/flight-schedule.entity";
|
||||||
import { Hotel } from "@/database/entities/hotel.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 { PackageDetail } from "@/database/entities/package-detail.entity";
|
||||||
import { PackageItineraryDay } from "@/database/entities/package-itinerary-day.entity";
|
import { PackageItineraryDay } from "@/database/entities/package-itinerary-day.entity";
|
||||||
import { PackageItineraryImage } from "@/database/entities/package-itinerary-image.entity";
|
import { PackageItineraryImage } from "@/database/entities/package-itinerary-image.entity";
|
||||||
@@ -35,6 +37,7 @@ import {
|
|||||||
packageRequestSchema,
|
packageRequestSchema,
|
||||||
} from "@/modules/package/package.schemas";
|
} from "@/modules/package/package.schemas";
|
||||||
import type {
|
import type {
|
||||||
|
PackageConsultResponse,
|
||||||
PackageDetailResponse,
|
PackageDetailResponse,
|
||||||
PackageResponse,
|
PackageResponse,
|
||||||
} from "@/modules/package/package.types";
|
} from "@/modules/package/package.types";
|
||||||
@@ -98,6 +101,47 @@ export class PackageController extends Controller {
|
|||||||
} satisfies SingleResponse<PackageResponse>);
|
} 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) {
|
async list(req: Request, res: Response) {
|
||||||
const parseQueryResult = paginationQuerySchema.safeParse(req.query);
|
const parseQueryResult = paginationQuerySchema.safeParse(req.query);
|
||||||
if (!parseQueryResult.success) {
|
if (!parseQueryResult.success) {
|
||||||
@@ -1325,6 +1369,11 @@ export class PackageController extends Controller {
|
|||||||
isAdminMiddleware(this.jwtService, [AdminPermission.createPackage]),
|
isAdminMiddleware(this.jwtService, [AdminPermission.createPackage]),
|
||||||
this.create.bind(this),
|
this.create.bind(this),
|
||||||
);
|
);
|
||||||
|
router.post(
|
||||||
|
"/:id/consult",
|
||||||
|
createOrmContextMiddleware,
|
||||||
|
this.consult.bind(this),
|
||||||
|
);
|
||||||
router.get("/", createOrmContextMiddleware, this.list.bind(this));
|
router.get("/", createOrmContextMiddleware, this.list.bind(this));
|
||||||
router.get("/:id", createOrmContextMiddleware, this.view.bind(this));
|
router.get("/:id", createOrmContextMiddleware, this.view.bind(this));
|
||||||
router.put(
|
router.put(
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ export type PackageResponse = {
|
|||||||
updated_at: Date;
|
updated_at: Date;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type PackageConsultResponse = {
|
||||||
|
session_code: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type PackageItineraryWidgetResponse =
|
export type PackageItineraryWidgetResponse =
|
||||||
| {
|
| {
|
||||||
type: "transport";
|
type: "transport";
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export class PartnerController extends Controller {
|
|||||||
|
|
||||||
const verification = orm.em.create(Verification, {
|
const verification = orm.em.create(Verification, {
|
||||||
id: ulid(),
|
id: ulid(),
|
||||||
code: generateRandomCode(6),
|
code: generateRandomCode(6, "0123456789"),
|
||||||
type: VerificationType.createPartner,
|
type: VerificationType.createPartner,
|
||||||
expiredAt: dateFns.addHours(new Date(), 1),
|
expiredAt: dateFns.addHours(new Date(), 1),
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
@@ -281,7 +281,7 @@ export class PartnerController extends Controller {
|
|||||||
partner.name = body.name;
|
partner.name = body.name;
|
||||||
partner.verification = orm.em.create(Verification, {
|
partner.verification = orm.em.create(Verification, {
|
||||||
id: ulid(),
|
id: ulid(),
|
||||||
code: generateRandomCode(6),
|
code: generateRandomCode(6, "0123456789"),
|
||||||
type: VerificationType.updatePartner,
|
type: VerificationType.updatePartner,
|
||||||
expiredAt: dateFns.addHours(new Date(), 1),
|
expiredAt: dateFns.addHours(new Date(), 1),
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
@@ -344,7 +344,7 @@ export class PartnerController extends Controller {
|
|||||||
partner.email = body.new_email;
|
partner.email = body.new_email;
|
||||||
partner.verification = orm.em.create(Verification, {
|
partner.verification = orm.em.create(Verification, {
|
||||||
id: ulid(),
|
id: ulid(),
|
||||||
code: generateRandomCode(6),
|
code: generateRandomCode(6, "0123456789"),
|
||||||
type: VerificationType.changeEmailPartner,
|
type: VerificationType.changeEmailPartner,
|
||||||
expiredAt: dateFns.addHours(new Date(), 1),
|
expiredAt: dateFns.addHours(new Date(), 1),
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
@@ -422,7 +422,7 @@ export class PartnerController extends Controller {
|
|||||||
partner.password = await Bun.password.hash(body.new_password);
|
partner.password = await Bun.password.hash(body.new_password);
|
||||||
partner.verification = orm.em.create(Verification, {
|
partner.verification = orm.em.create(Verification, {
|
||||||
id: ulid(),
|
id: ulid(),
|
||||||
code: generateRandomCode(6),
|
code: generateRandomCode(6, "0123456789"),
|
||||||
type: VerificationType.changePasswordPartner,
|
type: VerificationType.changePasswordPartner,
|
||||||
expiredAt: dateFns.addHours(new Date(), 1),
|
expiredAt: dateFns.addHours(new Date(), 1),
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
|
|||||||
92
src/modules/whatsapp/whatsapp.controller.ts
Normal file
92
src/modules/whatsapp/whatsapp.controller.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/modules/whatsapp/whatsapp.types.ts
Normal file
31
src/modules/whatsapp/whatsapp.types.ts
Normal 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";
|
||||||
|
}[];
|
||||||
|
}[];
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user