jws-backend/src/controllers/09-web-hook-controller.ts
Methapon Metanipat 12bf2182dc
feat: line (#13)
* add wedhook line

* text message

* add router get employee, request, quotation

* move code

* do not crash application when not set line token

This feature is opt-in

* dot not crash when not set line client id

Main auth method is keycloak

* change dotenv

* fix: wrong env

* refactor: change to get instead of post

* refactor: remove body for employee get endpoint

* feat: add work relation include

* feat: include customer relation in employee

* feat: add line file controller

* add detail flex message and get date requestWork

* chore: update deps lock

* fix: error line token

* fix: redirect head instead if response with body

* feat: add response relation

* fix: route casing

* add userId in customerBranch verifyOTP

* delete consile log

* add is registered endpoint placeholder

* feat: quotation list

* fix: wrong endpoint name

* feat: include relation in get by id request data

* add where userId line

* refactor: adjust parameter for liff app

* delete code

* refactor: remove post quotation endpoint

* refactor: add where userId line for quotation

* feat: add pending only parameter

* refactor: more condition for inProgressOnly

* refactor: update condition

* feat: add line quotation attachment endpoint

* feat: include product in request work line endpoint

* refactor: pending only now cover more condition

* feat: include invoice with payment relation

* chore: update api docs tag

* chore: clean

* feat: check for registered user

* fix: wrong file location

* feat: add email client for sending an otp

* chore: move some deps to dev deps

* add otpCode otpExpires

* add send-otp and verify-otp

---------

Co-authored-by: Kanjana <kanjana@chamomind.com>
Co-authored-by: chamomind <chamomind@localhost>
Co-authored-by: Methapon2001 <61303214+Methapon2001@users.noreply.github.com>
2025-02-20 16:07:16 +07:00

183 lines
6.5 KiB
TypeScript

import { Body, Controller, Get, Post, Query, Response, Route, Tags } from "tsoa";
import prisma from "../db";
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import timezone from "dayjs/plugin/timezone";
import HttpError from "../interfaces/http-error";
import HttpStatus from "../interfaces/http-status";
import { notFoundError } from "../utils/error";
dayjs.extend(utc);
dayjs.extend(timezone);
interface WebhookPayload {
destination?: string;
events: Array<{
mode?: string;
deliveryContext?: Record<string, any>;
webhookEventId?: string;
type: string;
replyToken: string;
source: {
userId: string;
type: string;
};
timestamp: number;
message: {
id?: string;
type: string;
text?: string;
quoteToken?: string;
stickerId?: string;
packageId?: string;
stickerResourceType?: string;
keywords?: string[];
emojis?: string[] | { productId: string; emojiId: string; index: number; length: number }[];
};
}>;
}
interface accessToken {
code: string;
state: string;
}
@Route("api/v1/webhook")
@Tags("Webhook")
export class WebHookController extends Controller {
async #getLineToken() {
if (!process.env.LINE_MESSAGING_API_TOKEN) {
console.warn("Line Webhook Activated but LINE_MESSAGING_API_TOKEN not set.");
throw new HttpError(HttpStatus.NOT_IMPLEMENTED, "NOT IMPLEMENTED", "notImplemented");
}
return process.env.LINE_MESSAGING_API_TOKEN;
}
@Post()
@Response(200, "Webhook received successfully")
public async receiveWebhook(@Body() payload: WebhookPayload) {
const token = await this.#getLineToken();
if (!payload || !payload.events || !Array.isArray(payload.events)) {
this.setStatus(400);
return { message: "Invalid payload structure" };
}
if (payload.events.length > 0) {
const userIdLine = payload.events[0]?.source?.userId;
const dataNow = dayjs().tz("Asia/Bangkok").startOf("day");
// const dataUser = await prisma.customerBranch.findFirst({
// where:{
// userId:userIdLine
// }
// })
const dataEmployee = await prisma.employeePassport.findMany({
select: {
firstName: true,
firstNameEN: true,
lastName: true,
lastNameEN: true,
employeeId: true,
expireDate: true,
employee: {
select: {
firstName: true,
lastName: true,
customerBranch: {
select: {
firstName: true,
firstNameEN: true,
lastName: true,
lastNameEN: true,
customerName: true,
customer: {
select: {
customerType: true,
registeredBranch: {
select: {
telephoneNo: true,
},
},
},
},
},
},
},
},
},
where: {
expireDate: {
lt: dataNow.add(30, "day").toDate(),
},
},
orderBy: {
expireDate: "asc",
},
});
if (payload?.events[0]?.message) {
const message = payload.events[0].message.text;
if (message === "เมนูหลัก > ข้อความ") {
const dataUser = userIdLine;
const textHead = "JWS ALERT:";
let textData = "";
if (dataEmployee.length > 0) {
const customerName =
dataEmployee[0]?.employee?.customerBranch?.customerName ?? "ไม่ระบุ";
const telephoneNo =
dataEmployee[0]?.employee?.customerBranch?.customer.registeredBranch.telephoneNo ??
"ไม่ระบุ";
const textEmployer = `เรียน คุณ${customerName}`;
const textAlert = "ขอแจ้งให้ทราบว่าหนังสือเดินทางของลูกจ้าง";
const textAlert2 = "และจำเป็นต้องดำเนินการต่ออายุในเร็ว ๆ นี้";
const textExpDate =
"🔹 กรุณาตรวจสอบและดำเนินการต่ออายุภายในวันที่กำหนด เพื่อป้องกันปัญหาด้านเอกสารหรือการเดินทางที่อาจเกิดขึ้น";
const textAlert3 = "หากดำเนินการเรียบร้อยแล้ว กรุณาแจ้งให้ฝ่ายที่เกี่ยวข้องทราบ 🙏";
let textFooter = `📞 สอบถามข้อมูลเพิ่มเติม: ${telephoneNo}`;
const textEmployees = dataEmployee
.map((item, index) => {
const dateFormat =
dayjs(item.expireDate).format("DD/MM/") + (dayjs(item.expireDate).year() + 543);
const diffDate = dayjs(item.expireDate).diff(dayjs(), "day");
return `${index + 1}. คุณ${item.firstName} ${item.lastName} วันหมดอายุเอกสาร : ${dateFormat} ใกล้หมดอายุอีก ${diffDate} วัน\n https://taii-cmm.case-collection.com/api/v1/line/employee/${item.employeeId}`;
})
.join("\n");
textData = `${textHead}\n\n${textEmployer}\n\n${textAlert}\n${textEmployees}\n${textAlert2}\n\n${textExpDate}\n\n${textAlert3}\n\n${textFooter}`;
} else {
textData = `${textHead}\n\nขออภัย ไม่พบข้อมูลหนังสือเดินทางที่มีกำหนดหมดอายุภายใน 30 วันข้างหน้า 🙏`;
}
const data = {
to: dataUser,
messages: [
{
type: "text",
text: textData,
},
],
};
await fetch("https://api.line.me/v2/bot/message/push", {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
}
}
}
return { message: "Webhook received successfully" };
}
}