From 78d076cffcc00dceefee3a6f96f4be9cfc03b8b1 Mon Sep 17 00:00:00 2001 From: palsp Date: Sun, 22 Aug 2021 21:12:48 +0700 Subject: [PATCH 01/34] chore: migrate patientController index to ts --- .../patientController/{index.js => index.ts} | 64 ++++----- .../patientController/utils.test.ts | 130 +++++++++++++++++- .../src/controller/patientController/utils.ts | 61 ++++++-- functions/src/index.ts | 16 +-- functions/src/schema/RegisterSchema.ts | 2 +- functions/src/types/handler.ts | 3 + functions/src/types/patient.ts | 12 +- functions/src/utils/status.js | 8 ++ 8 files changed, 233 insertions(+), 63 deletions(-) rename functions/src/controller/patientController/{index.js => index.ts} (84%) create mode 100644 functions/src/types/handler.ts diff --git a/functions/src/controller/patientController/index.js b/functions/src/controller/patientController/index.ts similarity index 84% rename from functions/src/controller/patientController/index.js rename to functions/src/controller/patientController/index.ts index b68d6024..28b25e25 100644 --- a/functions/src/controller/patientController/index.js +++ b/functions/src/controller/patientController/index.ts @@ -1,34 +1,39 @@ -const functions = require("firebase-functions"); -const { +import * as functions from "firebase-functions"; +import { validateRegisterSchema, validateGetProfileSchema, validateHistorySchema, //mon added this validateDeletePatientSchema, + RegisterType, + DeletePatientType, + GetProfileType, + HistoryType, //end mon code -} = require("../../schema"); -const { admin, collection } = require("../../init"); +} from "../../schema"; +import { admin, collection } from "../../init"; const { getProfile } = require("../../middleware/authentication"); const { success } = require("../../response/success"); const { makeStatusAPIPayload, makeRequest } = require("../../api"); -const { statusList, statusListReverse } = require("../../api/const"); +const { statusList } = require("../../api/const"); const { sendPatientstatus } = require("../../linefunctions/linepushmessage"); const { notifyToLine } = require("../../linenotify"); const { convertTimestampToStr } = require("../../utils"); const { config } = require("../../config/index"); +const { TO_AMED_STATUS } = require("../../utils/status") import { setPatientStatus, snapshotExists, - updateSymptomAddCreatedDate, updateSymptomCheckUser, updateSymptomCheckAmed, - updateSymptomUpdateStatus, - setAmedStatus, + createFollowUpObj, } from "./utils"; +import { OnCallHandler } from "../../types/handler"; +import { Patient } from "../../types"; // Mon added this code -const deletePatient = async (personalID) => { +const deletePatient = async (personalID: string) => { const snapshot = await admin .firestore() .collection(collection.patient) @@ -65,7 +70,7 @@ const deletePatient = async (personalID) => { } }; -exports.requestDeletePatient = async (data, _context) => { +export const requestDeletePatient: OnCallHandler = async (data, _context) => { const { value, error } = validateDeletePatientSchema(data); if (error) { @@ -92,7 +97,7 @@ exports.requestDeletePatient = async (data, _context) => { }; // end of mon's code -exports.registerPatient = async (data, _context) => { +export const registerPatient: OnCallHandler = async (data, _context) => { const { value, error } = validateRegisterSchema(data); if (error) { @@ -145,7 +150,7 @@ exports.registerPatient = async (data, _context) => { return success(`Registration with ID: ${lineUserID} added`); }; -exports.getProfile = async (data, _context) => { +export const getProfileHandler: OnCallHandler = async (data, _context) => { const { value, error } = validateGetProfileSchema(data); if (error) { console.log(error.details); @@ -177,7 +182,7 @@ exports.getProfile = async (data, _context) => { const { name, picture } = lineProfile; if (snapshot.exists) { - const { followUp, ...patientData } = snapshot.data(); + const { followUp, ...patientData } = snapshot.data() as Patient; const serializeData = convertTimestampToStr(patientData); return { line: { name, picture }, patient: serializeData }; } else { @@ -185,7 +190,7 @@ exports.getProfile = async (data, _context) => { } }; -exports.updateSymptom = async (data, _context) => { +export const updateSymptom: OnCallHandler = async (data, _context) => { const { value, error } = validateHistorySchema(data); if (error) { // DEBUG @@ -212,7 +217,6 @@ exports.updateSymptom = async (data, _context) => { const date = new Date(); const createdTimeStamp = admin.firestore.Timestamp.fromDate(date); - updateSymptomAddCreatedDate(obj, createdTimeStamp); //need db connection const snapshot = await admin @@ -222,46 +226,32 @@ exports.updateSymptom = async (data, _context) => { .get(); updateSymptomCheckUser(snapshot, lineUserID); - - const snapshotData = snapshot.data(); + const snapshotData = snapshot.data() as Patient; const { followUp, firstName, lastName, - toAmed, status: previousStatus, } = snapshotData; updateSymptomCheckAmed(snapshotData); - //TO BE CHANGED: snapshot.data.apply().status = statusCheckAPIorSomething; - //update lastUpdatedAt field on patient const formPayload = makeStatusAPIPayload(snapshotData, obj); const { inclusion_label, inclusion_label_type, triage_score } = await makeRequest(formPayload); const status = statusList[inclusion_label]; - updateSymptomUpdateStatus( + + const followUpObj = createFollowUpObj( obj, status, inclusion_label_type, triage_score, - createdTimeStamp - ); - - const followUpObj = { ...obj }; - obj["isNurseExported"] = false; - - const ALERT_STATUS = [ - statusList["Y1"], - statusList["Y2"], - statusList["R1"], - statusList["R2"], - ]; - - setAmedStatus(obj, status, previousStatus, ALERT_STATUS); + createdTimeStamp, + previousStatus + ) - const { createdDate, ...objWithOutCreatedDate } = obj; + const { createdDate, ...objWithOutCreatedDate } = followUpObj; if (!followUp) { await snapshot.ref.set({ @@ -276,7 +266,7 @@ exports.updateSymptom = async (data, _context) => { } try { - if (ALERT_STATUS.includes(status)) { + if (TO_AMED_STATUS.includes(status)) { await notifyToLine( `ผู้ป่วย: ${firstName} ${lastName} มีการเปลี่ยนแปลงอาการฉุกเฉิน` ); diff --git a/functions/src/controller/patientController/utils.test.ts b/functions/src/controller/patientController/utils.test.ts index 70277532..2b4d0b6f 100644 --- a/functions/src/controller/patientController/utils.test.ts +++ b/functions/src/controller/patientController/utils.test.ts @@ -1,4 +1,9 @@ -const { +import * as _ from "lodash"; +import { statusList, statusListReverse } from "../../api/const"; +import { HistoryType } from "../../schema"; +import { Timestamp } from "@google-cloud/firestore"; + +import { setPatientStatus, snapshotExists, updateSymptomAddCreatedDate, @@ -6,14 +11,130 @@ const { updateSymptomCheckAmed, updateSymptomUpdateStatus, setAmedStatus, -} = require("./utils"); + createFollowUpObj +} from "./utils"; const { admin } = require("../../init"); -const functions = require("firebase-functions"); + +const randomInt = (n: number = 1): number => { + return Math.floor(Math.random() * n) +} +const randomEIHResult = () => { + const results = ["positive", "negative", "neutral", "unknown"] + return results[randomInt(4)] +} + +const randomStatus = () => { + const n = _.keys(statusListReverse).length + return statusListReverse[randomInt(n)] +} + +const createMockFollowUpInput = (): Omit => { + return { + sp_o2: randomInt(100), + sp_o2_ra: randomInt(100), + sp_o2_after_eih: randomInt(100), + eih_result: randomEIHResult(), + sym1_severe_cough: randomInt(), + sym1_chest_tightness: randomInt(), + sym1_poor_appetite: randomInt(), + sym1_fatigue: randomInt(), + sym1_persistent_fever: randomInt(), + sym2_tired_body_ache: randomInt(), + sym2_cough: randomInt(), + sym2_fever: randomInt(), + sym2_liquid_stool: randomInt(), + sym2_cannot_smell: randomInt(), + sym2_rash: randomInt(), + sym2_red_eye: randomInt(), + fac_bed_ridden_status: randomInt(), + fac_uri_symptoms: randomInt(), + fac_olfactory_symptoms: randomInt(), + fac_diarrhea: randomInt(), + fac_dyspnea: randomInt(), + fac_chest_discomfort: randomInt(), + fac_gi_symptoms: randomInt(), + } +} + +const createMockAPIResult = () => { + return { + inclusion_label: randomStatus(), + inclusion_label_type: "at_least", + triage_score: randomInt(150) + } +} + +describe("createFollowUpObj", () => { + it("should set followUp payload correctly", () => { + const mockFollowUpInput = createMockFollowUpInput() + const { inclusion_label, inclusion_label_type, triage_score } = createMockAPIResult() + + const status = statusList[inclusion_label] + const timestamp = Timestamp.now() + const prevStatus = statusList["unknown"] + const result = createFollowUpObj( + mockFollowUpInput, + status, + inclusion_label_type, + triage_score, + timestamp, + prevStatus + ) as { [key: string]: any } + + for (const [key, value] of _.entries(mockFollowUpInput)) { + expect(result[key]).toEqual(value) + } + + expect(result["status"]).toEqual(status) + expect(result["triage_score"]).toEqual(triage_score) + expect(result["status_label_type"]).toEqual(inclusion_label_type) + expect(result["lastUpdatedAt"]).toEqual(timestamp) + expect(result["createdDate"]).toEqual(timestamp) + expect(result["toAmed"]).toBeDefined() + }) + + it("should set toAmed value to 1 if toAmed is true", () => { + const mockFollowUpInput = createMockFollowUpInput() + const status = statusList["R2"] + const inclusion_label_type = "at_least" + const triage_score = randomInt(150) + const timestamp = Timestamp.now() + const prevStatus = statusList["unknown"] + const result = createFollowUpObj( + mockFollowUpInput, + status, + inclusion_label_type, + triage_score, + timestamp, + prevStatus + ) as { [key: string]: any } + expect(result["toAmed"]).toEqual(1) + }) + + it("should set toAmed value to 0 if toAmed is true", () => { + const mockFollowUpInput = createMockFollowUpInput() + const status = statusList["G2"] + const inclusion_label_type = "at_least" + const triage_score = randomInt(150) + const timestamp = Timestamp.now() + const prevStatus = statusList["unknown"] + const result = createFollowUpObj( + mockFollowUpInput, + status, + inclusion_label_type, + triage_score, + timestamp, + prevStatus + ) as { [key: string]: any } + expect(result["toAmed"]).toEqual(0) + }) +}) describe("setPatientStatus", () => { it("should setPatientStatus correctly", () => { const mockObj = {}; const createdDate = new Date(); + // @ts-ignore const result = setPatientStatus(mockObj, createdDate); expect(result).toEqual({ status: 0, @@ -33,6 +154,7 @@ describe("snapshotExists", () => { it("throw amed", () => { function checkExists() { const mockSnapshot = { exists: true, data: () => ({ toAmed: 1 }) }; + // @ts-ignore snapshotExists(mockSnapshot); } expect(checkExists).toThrowError( @@ -42,6 +164,7 @@ describe("snapshotExists", () => { it("throw มีข้อมูลแล้ว", () => { function checkExists() { const mockSnapshot = { exists: true, data: () => ({ toAmed: 0 }) }; + // @ts-ignore snapshotExists(mockSnapshot); } expect(checkExists).toThrowError("มีข้อมูลผู้ใช้ในระบบแล้ว"); @@ -69,6 +192,7 @@ describe("updateSymptomCheckUser", () => { function checkUser() { const mockSnapshot = { exists: false }; + // @ts-ignore updateSymptomCheckUser(mockSnapshot, lineUserID); } expect(checkUser).toThrowError(`ไม่พบผู้ใช้ ${lineUserID}`); diff --git a/functions/src/controller/patientController/utils.ts b/functions/src/controller/patientController/utils.ts index 6293ffec..4c4d2252 100644 --- a/functions/src/controller/patientController/utils.ts +++ b/functions/src/controller/patientController/utils.ts @@ -1,12 +1,14 @@ const { admin } = require("../../init"); const functions = require("firebase-functions"); -import { RegisterType } from '../../schema'; -import { Patient } from '../../types' +import { RegisterType, HistoryType } from '../../schema'; +import { Patient, UpdatedPatient } from '../../types' +const { TO_AMED_STATUS } = require("../../utils/status") -exports.setPatientStatus = (obj: Omit, createdDate: Date): Patient => { + +export const setPatientStatus = (obj: Omit, createdDate: Date): Patient => { const createdTimestamp = admin.firestore.Timestamp.fromDate(createdDate); - const result = { + return { status: 0, needFollowUp: true, followUp: [], @@ -18,11 +20,39 @@ exports.setPatientStatus = (obj: Omit) => { + +export const createFollowUpObj = ( + obj: Omit, + status: number, + inclusion_label_type: string, + triage_score: number, + createdTimeStamp: any, + prevStatus: number, +): UpdatedPatient => { + + // set To Amed Status + const toAmed = checkAmedStatus(status, prevStatus, TO_AMED_STATUS) + + // update other status + return { + ...obj, + status, + triage_score, + status_label_type: inclusion_label_type, + lastUpdatedAt: createdTimeStamp, + createdDate: createdTimeStamp, + toAmed: toAmed ? 1 : 0 + } + +} + +const checkAmedStatus = (status: number, prevStatus: number, TO_AMED_STATUS: any,): boolean => { + return status !== prevStatus && TO_AMED_STATUS.includes(status) +} + +export const snapshotExists = (snapshot: FirebaseFirestore.DocumentSnapshot) => { if (snapshot.exists) { if (snapshot.data()?.toAmed === 1) { throw new functions.https.HttpsError( @@ -37,11 +67,14 @@ exports.snapshotExists = (snapshot: FirebaseFirestore.DocumentSnapshot, date: Date) => { - obj.createdDate = date; +export const updateSymptomAddCreatedDate = ( + obj: Record, + timestamp: FirebaseFirestore.Timestamp +) => { + obj.createdDate = timestamp; }; -exports.updateSymptomCheckUser = (snapshot: FirebaseFirestore.DocumentSnapshot, lineUserID: string) => { +export const updateSymptomCheckUser = (snapshot: FirebaseFirestore.DocumentSnapshot, lineUserID: string) => { if (!snapshot.exists) { throw new functions.https.HttpsError( "not-found", @@ -50,7 +83,7 @@ exports.updateSymptomCheckUser = (snapshot: FirebaseFirestore.DocumentSnapshot) => { +export const updateSymptomCheckAmed = (snapshotData: Record) => { const { toAmed } = snapshotData; if (toAmed === 1) { throw new functions.https.HttpsError( @@ -60,7 +93,7 @@ exports.updateSymptomCheckAmed = (snapshotData: Record) => { } }; -exports.updateSymptomUpdateStatus = ( +export const updateSymptomUpdateStatus = ( obj: Record, status: number, inclusion_label_type: string, @@ -71,12 +104,14 @@ exports.updateSymptomUpdateStatus = ( obj["status_label_type"] = inclusion_label_type; obj["triage_score"] = triage_score; obj["lastUpdatedAt"] = createdTimeStamp; + }; -exports.setAmedStatus = (obj: Record, status: number, previousStatus: number, TO_AMED_STATUS: any) => { +export const setAmedStatus = (obj: Record, status: number, previousStatus: number, TO_AMED_STATUS: any) => { if (status !== previousStatus && TO_AMED_STATUS.includes(status)) { obj["toAmed"] = 1; } else { obj["toAmed"] = 0; } }; + diff --git a/functions/src/index.ts b/functions/src/index.ts index dad5ac03..77964d9e 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -55,8 +55,8 @@ exports.webhook = functions.region(region).https.onRequest(async (req, res) => { try { const event = req.body.events[0]; const userId = event.source.userId; - const profile:any = await client.getProfile(userId); - const userObject = { userId: userId, profile: profile }; + const profile: any = await client.getProfile(userId); + const userObject = { userId: userId, profile: profile }; console.log(userObject); // console.log(event) await eventHandler(event, userObject, client); @@ -76,7 +76,7 @@ exports.registerParticipant = functions exports.getProfile = functions .region(region) - .https.onCall(patientController.getProfile); + .https.onCall(patientController.getProfileHandler); exports.exportRequestToRegister = functions .region(region) @@ -110,7 +110,7 @@ exports.thisEndpointNeedsAuth = functions.region(region).https.onCall( return { result: `Content for authorized user` }; }) ); - + exports.backupFirestore = functions .region(region) @@ -119,10 +119,10 @@ exports.backupFirestore = functions .onRun(backup); exports.updateTimeSeries = functions -.region(region) -.pubsub.schedule("every day 23:59") -.timeZone("Asia/Bangkok") -.onRun(pubsub.updateTimeSeries); + .region(region) + .pubsub.schedule("every day 23:59") + .timeZone("Asia/Bangkok") + .onRun(pubsub.updateTimeSeries); exports.initializeLegacyStat = functions .region(region) diff --git a/functions/src/schema/RegisterSchema.ts b/functions/src/schema/RegisterSchema.ts index dd65e42c..cda01e7f 100644 --- a/functions/src/schema/RegisterSchema.ts +++ b/functions/src/schema/RegisterSchema.ts @@ -50,7 +50,7 @@ export const RegisterSchema = Joi.object({ fac_pregnancy: Joi.number().allow(0, 1).required(), // optional - personalID: Joi.string().length(13).allow(null), + personalID: Joi.string().length(13).required(), passport: Joi.string() .min(7) .max(9) diff --git a/functions/src/types/handler.ts b/functions/src/types/handler.ts new file mode 100644 index 00000000..e281fc17 --- /dev/null +++ b/functions/src/types/handler.ts @@ -0,0 +1,3 @@ +import { https } from "firebase-functions"; + +export type OnCallHandler = (data: T, context: https.CallableContext) => any | Promise \ No newline at end of file diff --git a/functions/src/types/patient.ts b/functions/src/types/patient.ts index dafcaeae..a24992ea 100644 --- a/functions/src/types/patient.ts +++ b/functions/src/types/patient.ts @@ -13,4 +13,14 @@ export type Patient = { isRequestToCall: boolean isNurseExported: boolean toAmed: number -} & Partial & Omit \ No newline at end of file +} & Partial & Omit + + +export type UpdatedPatient = { + toAmed: number + status: number + triage_score: number + status_label_type: string + lastUpdatedAt: Timestamp + createdDate: Timestamp +} & Partial \ No newline at end of file diff --git a/functions/src/utils/status.js b/functions/src/utils/status.js index 6916b965..1b7cda68 100644 --- a/functions/src/utils/status.js +++ b/functions/src/utils/status.js @@ -1,4 +1,12 @@ import { calculateAge } from "./date"; +import { statusList } from "../api/const"; + +exports.TO_AMED_STATUS = [ + statusList["Y1"], + statusList["Y2"], + statusList["R1"], + statusList["R2"], +]; exports.makeStatusPayload = (data) => { const { followUp } = data; From ed111583cee8eae5270beb6d35f86c89b9388107 Mon Sep 17 00:00:00 2001 From: palsp Date: Sun, 22 Aug 2021 23:32:39 +0700 Subject: [PATCH 02/34] chore: exportController index to ts --- .../exportController/{index.js => index.ts} | 75 +++--- .../exportController/{utils.js => utils.ts} | 63 +++-- functions/src/{init.js => init.ts} | 11 +- .../src/response/{success.js => success.ts} | 4 +- functions/src/types/handler.ts | 4 +- functions/src/types/index.ts | 4 +- functions/src/types/r2r.ts | 20 ++ functions/src/utils/date.ts | 2 +- functions/src/utils/status.js | 251 ------------------ functions/src/utils/status.ts | 98 +++++++ 10 files changed, 204 insertions(+), 328 deletions(-) rename functions/src/controller/exportController/{index.js => index.ts} (82%) rename functions/src/controller/exportController/{utils.js => utils.ts} (57%) rename functions/src/{init.js => init.ts} (69%) rename functions/src/response/{success.js => success.ts} (55%) create mode 100644 functions/src/types/r2r.ts delete mode 100644 functions/src/utils/status.js create mode 100644 functions/src/utils/status.ts diff --git a/functions/src/controller/exportController/index.js b/functions/src/controller/exportController/index.ts similarity index 82% rename from functions/src/controller/exportController/index.js rename to functions/src/controller/exportController/index.ts index 0836dd2e..e065d09e 100644 --- a/functions/src/controller/exportController/index.js +++ b/functions/src/controller/exportController/index.ts @@ -1,23 +1,27 @@ -const XLSX = require("xlsx"); -const fs = require("fs"); -const _ = require("lodash"); -const path = require("path"); -const functions = require("firebase-functions"); -const { admin, collection } = require("../../init"); -const { generateZipFileRoundRobin } = require("../../utils/zip"); -import { validateExportRequestToCallSchema } from "../../schema"; -const { statusList } = require("../../api/const"); -const { +import * as fs from "fs"; +import * as path from "path"; +import * as _ from "lodash"; +import * as XLSX from "xlsx"; +import * as functions from "firebase-functions"; +import { admin, collection } from "../../init"; +import { ExportRequestToCallType, validateExportRequestToCallSchema } from "../../schema"; +import { OnCallHandler, OnRequestHandler, Patient, R2RAssistance } from "../../types"; +import { formatPatient, formatter36Hr } from "./utils"; +import { calculateAge, convertTZ } from "../../utils/date"; +import * as utils from "./utils"; +import { statusList } from "../../api/const" +import { patientReportHeader, sheetName, MAP_PATIENT_FIELD, -} = require("../../utils/status"); -const { calculateAge, convertTZ,getDateID } = require("../../utils/date"); -const utils = require("./utils"); -const { success } = require("../../response/success"); +} from "../../utils/status"; +import { QuerySnapshot } from "@google-cloud/firestore"; + + +const { generateZipFileRoundRobin } = require("../../utils/zip"); -exports.exportR2R = async (data, _context) => { +export const exportR2R: OnCallHandler = async (data, _context) => { const { value, error } = validateExportRequestToCallSchema(data); if (error) { throw new functions.https.HttpsError( @@ -28,7 +32,7 @@ exports.exportR2R = async (data, _context) => { const { volunteerSize: size } = value; // get and serialize user from database - const snapshot = await utils.getUnExportedR2RUsers(); + const snapshot = await utils.getUnExportedR2RUsers() as QuerySnapshot; const userList = utils.serializeData(snapshot); // create zip file @@ -46,7 +50,7 @@ exports.exportR2R = async (data, _context) => { return result; }; -exports.exportR2C = async (data, _context) => { +export const exportR2C: OnCallHandler = async (data, _context) => { const { value, error } = validateExportRequestToCallSchema(data); if (error) { throw new functions.https.HttpsError( @@ -68,7 +72,7 @@ exports.exportR2C = async (data, _context) => { ); }; -exports.exportMaster = async (req, res) => { +export const exportMaster: OnRequestHandler = async (req, res) => { try { const snapshot = await admin .firestore() @@ -78,7 +82,7 @@ exports.exportMaster = async (req, res) => { const header = ["ที่อยู่", "เขต", "แขวง", "จังหวัด"]; const result = [header]; snapshot.forEach((doc) => { - const data = doc.data(); + const data = doc.data() as Patient; result.push([ data.address, @@ -93,7 +97,7 @@ exports.exportMaster = async (req, res) => { XLSX.utils.book_append_sheet(wb, ws, "รายงานที่อยู่ผู้ป่วย 4 สิงหาคม"); const filename = `report.xlsx`; - const opts = { bookType: "xlsx", type: "binary" }; + const opts: XLSX.WritingOptions = { bookType: "xlsx", type: 'binary' }; // it must be save to tmp directory because it run on firebase const pathToSave = path.join("/tmp", filename); @@ -109,11 +113,11 @@ exports.exportMaster = async (req, res) => { stream.pipe(res); } catch (err) { console.log(err); - return res.json({ success: false }); + res.json({ success: false }); } }; -exports.exportPatientForNurse = async (req, res) => { +export const exportPatientForNurse: OnRequestHandler = async (req, res) => { try { const snapshot = await admin .firestore() @@ -135,7 +139,7 @@ exports.exportPatientForNurse = async (req, res) => { results[i] = [[...reportHeader]]; } - const updatedDocId = []; + const updatedDocId: string[] = []; snapshot.docs.forEach((doc) => { const data = doc.data(); @@ -166,7 +170,7 @@ exports.exportPatientForNurse = async (req, res) => { } // write workbook file const filename = `report.xlsx`; - const opts = { bookType: "xlsx", type: "binary" }; + const opts: XLSX.WritingOptions = { bookType: "xlsx", type: "binary" }; // it must be save to tmp directory because it run on firebase const pathToSave = path.join("/tmp", filename); @@ -197,7 +201,7 @@ exports.exportPatientForNurse = async (req, res) => { } }; -exports.export36hrs = async (data, _context) => { +export const export36hrs: OnCallHandler = async (data, _context) => { const { value, error } = validateExportRequestToCallSchema(data); if (error) { throw new functions.https.HttpsError( @@ -209,19 +213,18 @@ exports.export36hrs = async (data, _context) => { const patientList = await utils.get36hrsUsers(); const header = ["first name", "tel"]; - const formatter = (doc) => [doc.firstName, `="${doc.personalPhoneNo}"`]; return generateZipFileRoundRobin( volunteerSize, patientList, header, - formatter + formatter36Hr ); }; /** * one time used only */ -exports.exportAllPatient = async (req, res) => { +export const exportAllPatient: OnRequestHandler = async (req, res) => { try { const { password } = req.query; if (password !== "SpkA43Zadkl") { @@ -282,7 +285,7 @@ exports.exportAllPatient = async (req, res) => { } // write workbook file const filename = `report.xlsx`; - const opts = { bookType: "xlsx", type: "binary" }; + const opts: XLSX.WritingOptions = { bookType: "xlsx", type: "binary" }; // it must be save to tmp directory because it run on firebase const pathToSave = path.join("/tmp", filename); @@ -303,7 +306,7 @@ exports.exportAllPatient = async (req, res) => { } }; -exports.exportRequestToCallDayOne = async (data, _context) => { +export const exportRequestToCallDayOne: OnCallHandler = async (data, _context) => { const { value, error } = validateExportRequestToCallSchema(data); if (error) { throw new functions.https.HttpsError( @@ -313,13 +316,13 @@ exports.exportRequestToCallDayOne = async (data, _context) => { } const { volunteerSize } = value; - const patientList = []; + const patientList: Patient[] = []; const snapshot = await admin.firestore().collection(collection.patient).get(); await Promise.all( snapshot.docs.map((doc) => { // WARNING SIDE EFFECT inside map - const docData = doc.data(); + const docData = doc.data() as Patient; patientList.push(docData); }) ); @@ -334,13 +337,7 @@ exports.exportRequestToCallDayOne = async (data, _context) => { volunteerSize, patientList, headers, - (doc) => [ - doc.personalID, - doc.firstName, - doc.lastName, - doc.personalPhoneNo, - doc.emergencyPhoneNo, - ] + formatPatient ); }; diff --git a/functions/src/controller/exportController/utils.js b/functions/src/controller/exportController/utils.ts similarity index 57% rename from functions/src/controller/exportController/utils.js rename to functions/src/controller/exportController/utils.ts index 81e6f81d..bed4a79e 100644 --- a/functions/src/controller/exportController/utils.js +++ b/functions/src/controller/exportController/utils.ts @@ -1,7 +1,11 @@ -const { admin, collection } = require("../../init"); -const { statusList } = require("../../api/const"); +import { admin, collection } from "../../init" +import { statusList } from "../../api/const"; +import { QuerySnapshot } from "@google-cloud/firestore"; +import { Patient, NotUpdatedList, R2RAssistance, R2C, WithID } from "../../types"; -exports.getUnExportedR2RUsers = () => { + + +export const getUnExportedR2RUsers = () => { return admin .firestore() .collection(collection.r2rAssistance) @@ -10,12 +14,10 @@ exports.getUnExportedR2RUsers = () => { }; /** - * marked users from R2R collection as exported - * @param {FirebaseFirestore.QuerySnapshot} snapshot - * @returns + * @returns data of each snapshot with doc id included */ -exports.serializeData = (snapshot) => { - const result = []; +export const serializeData = (snapshot: QuerySnapshot) => { + const result: any[] = []; snapshot.docs.forEach((doc) => { const data = doc.data(); data.id = doc.id; @@ -27,10 +29,8 @@ exports.serializeData = (snapshot) => { /** * marked users from R2R collection as exported - * @param {FirebaseFirestore.QuerySnapshot} snapshot - * @returns */ -exports.updateExportedR2RUsers = (snapshot) => { +export const updateExportedR2RUsers = (snapshot: QuerySnapshot) => { return Promise.all( snapshot.docs.map((doc) => { const ref = admin @@ -45,7 +45,7 @@ exports.updateExportedR2RUsers = (snapshot) => { ); }; -exports.getUnExportedR2CUsers = () => { +export const getUnExportedR2CUsers = () => { return admin .firestore() .collection(collection.patient) @@ -55,16 +55,15 @@ exports.getUnExportedR2CUsers = () => { .get(); }; -exports.get36hrsUsers = async () => { +export const get36hrsUsers = async () => { const snapshot = await admin.firestore().collection(collection.patient).get(); - const notUpdatedList = []; - const currentDate = new Date(); + const notUpdatedList: NotUpdatedList[] = []; snapshot.forEach((doc) => { - const patient = doc.data(); + const patient = doc.data() as Patient; const lastUpdatedDate = patient.lastUpdatedAt.toDate(); - var hours = Math.abs(currentDate - lastUpdatedDate) / 36e5; + const hours = Math.abs(new Date().getTime() - lastUpdatedDate.getTime()) / 36e5; const includeStatus = [ statusList["unknown"], statusList["G1"], @@ -87,31 +86,28 @@ exports.get36hrsUsers = async () => { /** * marked users from R2C collection as exported and return serialized data - * @param {FirebaseFirestore.QuerySnapshot} snapshot * @returns serialized snapshot data */ -exports.updateAndSerializeR2CData = async (snapshot) => { - const patientList = []; +export const updateAndSerializeR2CData = async (snapshot: QuerySnapshot) => { + const patientList: WithID[] = []; await Promise.all( snapshot.docs.map((doc) => { // WARNING SIDE EFFECT inside map - const data = doc.data(); - const dataResult = this.makeR2CPayload(doc.id, data); + const data = doc.data() as R2C; + const dataResult = makeR2CPayload(doc.id, data); patientList.push(dataResult); // end of side effects - this.updateExportedR2CUser(doc.id); + updateExportedR2CUser(doc.id); }) ); return patientList; }; -exports.makeR2CPayload = (id, data) => { +export const makeR2CPayload = (id: string, data: R2C) => { return { id, - - firstName: data.firstName, lastName: data.lastName, hasCalled: 0, @@ -119,7 +115,7 @@ exports.makeR2CPayload = (id, data) => { }; }; -exports.updateExportedR2CUser = (id) => { +export const updateExportedR2CUser = (id: string) => { const ref = admin.firestore().collection(collection.patient).doc(id); ref.update({ @@ -127,12 +123,21 @@ exports.updateExportedR2CUser = (id) => { }); }; -exports.formatterR2R = (doc) => [doc.id, doc.name, doc.personalPhoneNo]; +export const formatterR2R = (doc: WithID) => [doc.id, doc.name, doc.personalPhoneNo]; -exports.formatterR2C = (doc) => [ +export const formatterR2C = (doc: WithID) => [ doc.id, doc.firstName, doc.hasCalled, `="${doc.personalPhoneNo}"`, ]; +export const formatter36Hr = (doc: NotUpdatedList) => [doc.firstName, `="${doc.personalPhoneNo}"`] + +export const formatPatient = (doc: Patient) => [ + doc.personalID, + doc.firstName, + doc.lastName, + doc.personalPhoneNo, + doc.emergencyPhoneNo, +] diff --git a/functions/src/init.js b/functions/src/init.ts similarity index 69% rename from functions/src/init.js rename to functions/src/init.ts index abdaaf5f..993947f5 100644 --- a/functions/src/init.js +++ b/functions/src/init.ts @@ -1,13 +1,13 @@ // The Firebase Admin SDK to access Firestore. -const admin = require("firebase-admin"); +import * as admin from "firebase-admin"; -exports.initializeApp = function () { +export const initializeApp = () => { admin.initializeApp(); }; -exports.admin = admin; -exports.collection = { + +export const collection = { patient: "patient", r2rAssistance: "requestToRegisterAssistance", userCount: "userCount", @@ -16,3 +16,6 @@ exports.collection = { legacyStat: "legacyStat", timeSeries: "timeSeries" }; + +export { admin } + diff --git a/functions/src/response/success.js b/functions/src/response/success.ts similarity index 55% rename from functions/src/response/success.js rename to functions/src/response/success.ts index 68385c83..4a7bc15e 100644 --- a/functions/src/response/success.js +++ b/functions/src/response/success.ts @@ -3,8 +3,8 @@ * @param {any} result * @returns */ -exports.success = (result = null) => { - const obj = { ok: true }; +export const success = (result: any = null) => { + const obj: { [key: string]: any } = { ok: true }; if (result) obj["result"] = result; return obj; }; diff --git a/functions/src/types/handler.ts b/functions/src/types/handler.ts index e281fc17..c5420932 100644 --- a/functions/src/types/handler.ts +++ b/functions/src/types/handler.ts @@ -1,3 +1,5 @@ +import express = require("express"); import { https } from "firebase-functions"; -export type OnCallHandler = (data: T, context: https.CallableContext) => any | Promise \ No newline at end of file +export type OnCallHandler = (data: T, context: https.CallableContext) => any | Promise +export type OnRequestHandler = (req: https.Request, resp: express.Response) => void | Promise \ No newline at end of file diff --git a/functions/src/types/index.ts b/functions/src/types/index.ts index fc4d3c7e..c4dce029 100644 --- a/functions/src/types/index.ts +++ b/functions/src/types/index.ts @@ -1,2 +1,4 @@ export * from "./patient" -export * from "./trigger" \ No newline at end of file +export * from "./trigger" +export * from "./handler"; +export * from "./r2r"; \ No newline at end of file diff --git a/functions/src/types/r2r.ts b/functions/src/types/r2r.ts new file mode 100644 index 00000000..f0f3be8e --- /dev/null +++ b/functions/src/types/r2r.ts @@ -0,0 +1,20 @@ +import { RequestToRegisterType } from "../schema" + +export type R2RAssistance = Omit & { + isR2RExported: boolean, + isRequestToCallRegister: boolean, +} + +export type NotUpdatedList = { + firstName: string + personalPhoneNo: string +} + +export type R2C = { + firstName: string + lastName: string + hasCalled: number + personalPhoneNo: string +} + +export type WithID = T & { id: string } \ No newline at end of file diff --git a/functions/src/utils/date.ts b/functions/src/utils/date.ts index 145d4edc..176323ff 100644 --- a/functions/src/utils/date.ts +++ b/functions/src/utils/date.ts @@ -8,7 +8,7 @@ enum TZ { } -export const convertTZ = (date: Date, tzString: TZ) => { +export const convertTZ = (date: Date, tzString: TZ = TZ.AsiaBangkok) => { return new Date( (typeof date === "string" ? new Date(date) : date).toLocaleString("en-US", { timeZone: tzString, diff --git a/functions/src/utils/status.js b/functions/src/utils/status.js deleted file mode 100644 index 1b7cda68..00000000 --- a/functions/src/utils/status.js +++ /dev/null @@ -1,251 +0,0 @@ -import { calculateAge } from "./date"; -import { statusList } from "../api/const"; - -exports.TO_AMED_STATUS = [ - statusList["Y1"], - statusList["Y2"], - statusList["R1"], - statusList["R2"], -]; - -exports.makeStatusPayload = (data) => { - const { followUp } = data; - const lastFollowUp = followUp[followUp.length - 1]; - const age60 = data.age > 60; - const copd = data.rf_copd_chronic_lung_disease; - const ckd34 = data.rf_ckd_stagr_3_to_4; - const chronicHeartDisease = data.rf_chronic_heart_disease; - const CVA = data.rf_cva; - const T2DM = data.rf_t2dm; - const bmi = (data.weight * 10000) / (data.height * data.height); - const bmiOver30 = bmi > 30 || data.weight > 90; - const cirrhosis = data.rf_cirrhosis; - const immunocompromise = data.rf_immunocompromise; - const age = calculateAge(data.birthDate.toDate()); - return { - //passport:"", length is 7 or 8 or 9 - cid: data.personalId, - firstname: data.firstName, - lastname: data.lastName, - contact_number: data.personalPhoneNo, - age: age, - gender: data.gender, - height: data.height, - weight: data.weight, - infected_discover_date: data.infectedDiscoverDate, //To be implemented - - sp_o2: lastFollowUp.sp_o2, - sp_o2_ra: lastFollowUp.sp_o2_ra, - sp_o2_after_eih: lastFollowUp.sp_o2_after_eih, //can this even be null? - eih_result: lastFollowUp.eih_result, - - sym1_severe_cough: lastFollowUp.sym1_severe_cough, - sym1_chest_tightness: lastFollowUp.sym1_chest_tightness, - sym1_poor_appetite: lastFollowUp.sym1_poor_appetite, - sym1_fatigue: lastFollowUp.sym1_fatigue, - sym1_persistent_fever: lastFollowUp.sym1_persistent_fever, - - rf_age_60: age60, - rf_copd_chronic_lung_disease: copd, - rf_ckd_stage_3_to_4: ckd34, - rf_chronic_heart_disease: chronicHeartDisease, - rf_cva: CVA, - rf_t2dm: T2DM, - rf_bmi_over_30_or_bw_over_90: bmiOver30, - rf_cirrhosis: cirrhosis, - rf_immunocompromise: immunocompromise, - - sym2_tired_body_ache: lastFollowUp.sym2_tired_body_ache, - sym2_cough: lastFollowUp.sym2_cough, - sym2_fever: lastFollowUp.sym2_fever, - sym2_liquid_stool: lastFollowUp.sym2_liquid_stool, - sym2_cannot_smell: lastFollowUp.sym2_cannot_smell, - sym2_rash: lastFollowUp.sym2_rash, - sym2_red_eye: lastFollowUp.sym2_red_eye, - - fac_diabetes: data.fac_diabetes, - fac_dyslipidemia: data.fac_dyslipidemia, - fac_hypertension: data.fac_hypertension, - fac_heart_diseases: data.fac_heart_diseases, - fac_esrd: data.fac_esrd, - fac_cancer: data.fac_cancer, - fac_tuberculosis: data.fac_tuberculosis, - fac_hiv: data.fac_hiv, - fac_asthma: data.fac_asthma, - fac_pregnancy: data.fac_pregnancy, - - fac_bed_ridden_status: lastFollowUp.fac_bed_ridden_status, - fac_uri_symptoms: lastFollowUp.fac_uri_symptoms, - fac_olfactory_symptoms: lastFollowUp.fac_olfactory_symptoms, - fac_diarrhea: lastFollowUp.fac_diarrhea, - fac_dyspnea: lastFollowUp.fac_dyspnea, - fac_chest_discomfort: lastFollowUp.fac_chest_discomfort, - fac_gi_symptomss: lastFollowUp.fac_gi_symptomss, - }; -}; -import { convertTZ } from "./date"; - -const status = ["noSuggestion", "G1", "G2", "Y1", "Y2", "R1", "R2"]; -exports.sheetName = [ - "รายงานผู้ป่วยเหลืองไม่มีอาการ", - "รายงานผู้ป่วยเหลืองมีอาการ", - "รายงานผู้ป่วยแดงอ่อน", - "รายงานผู้ป่วยแดงเข้ม", -]; - -exports.MAP_PATIENT_FIELD = { - รหัสบัตรประจำตัวประชาชน: "personalID", - ชื่อ: "firstName", - นามสกุล: "lastName", - "วัน/เดือน/ปีเกิด": "birthDate", - น้ำหนัก: "weight", - ส่วนสูง: "height", - เพศ: "gender", - ที่อยู่: "address", - ตำบล: "district", - อำเภอ: "prefecture", - จังหวัด: "province", - รหัสไปรษณีย์: "postNo", - เบอร์ติดต่อ: "personalPhoneNo", - เบอร์ติดต่อฉุกเฉิน: "emergencyPhoneNo", - ได้รับยาแล้ว: "gotFavipiravir", - - ค่าออกซิเจนปลายนิ้ว: "sp_o2", - "ค่าออกซิเจนปลายนิ้ว ขณะหายใจปกติ": "sp_o2_ra", - "ค่าออกซิเจนปลายนิ้ว หลังลุกนั่ง 1 นาที": "sp_o2_after_eih", - ผลตรวจออกซิเจนหลังลุกนั่ง: "eih_result", - - มีอาการไอต่อเนื่อง: "sym1_severe_cough", - "มีอาการแน่นหน้าอก หายใจไม่สะดวก": "sym1_chest_tightness", - เบื่ออาหาร: "sym1_poor_appetite", - อ่อนเพลียมาก: "sym1_fatigue", - มีไข้สูงลอย: "sym1_persistent_fever", - - // โรคประจำตัว - มีโรคปอดเรื้อรัง: "rf_copd_chronic_lung_disease", - - "มีโรคไตเรื้อรัง ตั้งแต่ระดับสามขึ้นไป": "rf_ckd_stagr_3_to_4", - มีโรคหัวใจ: "rf_chronic_heart_disease", - มีโรคหลอดเลือดสมอง: "rf_cva", - t2dm: "rf_t2dm", - มีโรคตับแข็ง: "rf_cirrhosis", - มีภาวะภูมิคุ้มกันบกพร่อง: "rf_immunocompromise", - - // อาการกลุ่มที่ 2 - "มีอาการอ่อนเพลีย ปวดเมื้อยตามตัว": "sym2_tired_body_ache", - มีอาการไอรุนแรง: "sym2_cough", - มีไข้ขึ้น: "sym2_fever", - ท้องเสีย: "sym2_liquid_stool", - จมูกไม่รับกลิ่น: "sym2_cannot_smell", - มีผื่นขึ้นตามตัว: "sym2_rash", - ตาแดง: "sym2_red_eye", - - มีโรคเบาหวาน: "fac_diabetes", - มีโรคไขมันในเลือดสูง: "fac_dyslipidemia", - มีโรคความดันสูง: "fac_hypertension", - มีโรคไตเสื่อม: "fac_esrd", - มีโรคมะเร็ง: "fac_cancer", - เป็นวัณโรค: "fac_tuberculosis", - "ติดเชื้อ HIV": "fac_hiv", - มีโรคหอบหืด: "fac_asthma", - ตั้งครรภ์: "fac_pregnancy", - - ติดเตียง: "fac_bed_ridden_status", - มีอาการทางเดินหายใจส่วนบน: "fac_uri_symptoms", - ท้องเสียถ่ายเหลว: "fac_diarrhea", - "หอบเหนื่อย หายใจเร็ว/ลำบาก": "fac_dyspnea", - มีอาการทางเดินอาหาร: "fac_gi_symptoms", -}; - -exports.patientReportHeader = [ - "รหัสบัตรประจำตัวประชาชน", - "ชื่อ", - "นามสกุล", - "เบอร์ติดต่อ", - "เบอร์ติดต่อฉุกเฉิน", - "อายุ", - "น้ำหนัก", - "ส่วนสูง", - "เพศ", - "วันที่ติดตามอาการล่าสุด", - "ที่อยู่", - "ตำบล", - "อำเภอ", - "จังหวัด", - "สถานะ", -]; -/** - * @param {*} data - */ -exports.convertToArray = (data) => { - return [ - data.personalID, - data.firstName, - data.lastName, - data.personalPhoneNo, - data.emergencyPhoneNo, - calculateAge(data.birthDate.toDate()), - data.weight, - data.height, - data.gender, - convertTZ(data.lastUpdatedAt.toDate()), - data.address, - data.district, - data.prefecture, - data.province, - status[data.status], - ]; -}; - -/** - * convert data from - * @param {[string]} headers - * @param {*} snapshot - * @returns - */ - -/** - * return true if patient is Y2 , false if not , null if there is no information - */ -exports.isY2 = (data) => { - const { followUp } = data; - const lastFollowUp = followUp[followUp.length - 1]; - if (!lastFollowUp || followUp.length === 0) { - return null; - } - - return ( - lastFollowUp.severeCough || - lastFollowUp.chestTightness || - lastFollowUp.poorAppetite || - lastFollowUp.fatigue || - lastFollowUp.persistentFever - ); -}; - -exports.isY1 = (snapshot) => { - const lastFollowUp = snapshot.followUp[snapshot.lastFollowUp.length - 1]; - const isOld = snapshot.age > 60; - const bmi = (snapshot.weight / (snapshot.height * snapshot.height)) * 10000; - const hasCongenitalDisease = - snapshot.COPD || - snapshot.chronicLungDisease || - snapshot.CKDStage3or4 || - snapshot.chronicHeartDisease || - snapshot.CVA || - snapshot.T2DM || - snapshot.cirrhosis || - snapshot.immunocompromise; - const bmiExceed = bmi > 30; - const isObese = snapshot.weight > 90; - - const isIll = - lastFollowUp.tired || - lastFollowUp.cough || - lastFollowUp.diarrhea || - lastFollowUp.canNotSmell || - lastFollowUp.rash || - lastFollowUp.redEye; - - return isOld || hasCongenitalDisease || bmiExceed || isObese || isIll; -}; diff --git a/functions/src/utils/status.ts b/functions/src/utils/status.ts new file mode 100644 index 00000000..54167002 --- /dev/null +++ b/functions/src/utils/status.ts @@ -0,0 +1,98 @@ +import { statusList } from "../api/const"; + +export const TO_AMED_STATUS = [ + statusList["Y1"], + statusList["Y2"], + statusList["R1"], + statusList["R2"], +]; + +export const status = ["noSuggestion", "G1", "G2", "Y1", "Y2", "R1", "R2"]; +export const sheetName = [ + "รายงานผู้ป่วยเหลืองไม่มีอาการ", + "รายงานผู้ป่วยเหลืองมีอาการ", + "รายงานผู้ป่วยแดงอ่อน", + "รายงานผู้ป่วยแดงเข้ม", +]; + +export const MAP_PATIENT_FIELD: { [key: string]: string } = { + รหัสบัตรประจำตัวประชาชน: "personalID", + ชื่อ: "firstName", + นามสกุล: "lastName", + "วัน/เดือน/ปีเกิด": "birthDate", + น้ำหนัก: "weight", + ส่วนสูง: "height", + เพศ: "gender", + ที่อยู่: "address", + ตำบล: "district", + อำเภอ: "prefecture", + จังหวัด: "province", + รหัสไปรษณีย์: "postNo", + เบอร์ติดต่อ: "personalPhoneNo", + เบอร์ติดต่อฉุกเฉิน: "emergencyPhoneNo", + ได้รับยาแล้ว: "gotFavipiravir", + + ค่าออกซิเจนปลายนิ้ว: "sp_o2", + "ค่าออกซิเจนปลายนิ้ว ขณะหายใจปกติ": "sp_o2_ra", + "ค่าออกซิเจนปลายนิ้ว หลังลุกนั่ง 1 นาที": "sp_o2_after_eih", + ผลตรวจออกซิเจนหลังลุกนั่ง: "eih_result", + + มีอาการไอต่อเนื่อง: "sym1_severe_cough", + "มีอาการแน่นหน้าอก หายใจไม่สะดวก": "sym1_chest_tightness", + เบื่ออาหาร: "sym1_poor_appetite", + อ่อนเพลียมาก: "sym1_fatigue", + มีไข้สูงลอย: "sym1_persistent_fever", + + // โรคประจำตัว + มีโรคปอดเรื้อรัง: "rf_copd_chronic_lung_disease", + + "มีโรคไตเรื้อรัง ตั้งแต่ระดับสามขึ้นไป": "rf_ckd_stagr_3_to_4", + มีโรคหัวใจ: "rf_chronic_heart_disease", + มีโรคหลอดเลือดสมอง: "rf_cva", + t2dm: "rf_t2dm", + มีโรคตับแข็ง: "rf_cirrhosis", + มีภาวะภูมิคุ้มกันบกพร่อง: "rf_immunocompromise", + + // อาการกลุ่มที่ 2 + "มีอาการอ่อนเพลีย ปวดเมื้อยตามตัว": "sym2_tired_body_ache", + มีอาการไอรุนแรง: "sym2_cough", + มีไข้ขึ้น: "sym2_fever", + ท้องเสีย: "sym2_liquid_stool", + จมูกไม่รับกลิ่น: "sym2_cannot_smell", + มีผื่นขึ้นตามตัว: "sym2_rash", + ตาแดง: "sym2_red_eye", + + มีโรคเบาหวาน: "fac_diabetes", + มีโรคไขมันในเลือดสูง: "fac_dyslipidemia", + มีโรคความดันสูง: "fac_hypertension", + มีโรคไตเสื่อม: "fac_esrd", + มีโรคมะเร็ง: "fac_cancer", + เป็นวัณโรค: "fac_tuberculosis", + "ติดเชื้อ HIV": "fac_hiv", + มีโรคหอบหืด: "fac_asthma", + ตั้งครรภ์: "fac_pregnancy", + + ติดเตียง: "fac_bed_ridden_status", + มีอาการทางเดินหายใจส่วนบน: "fac_uri_symptoms", + ท้องเสียถ่ายเหลว: "fac_diarrhea", + "หอบเหนื่อย หายใจเร็ว/ลำบาก": "fac_dyspnea", + มีอาการทางเดินอาหาร: "fac_gi_symptoms", +}; + +export const patientReportHeader = [ + "รหัสบัตรประจำตัวประชาชน", + "ชื่อ", + "นามสกุล", + "เบอร์ติดต่อ", + "เบอร์ติดต่อฉุกเฉิน", + "อายุ", + "น้ำหนัก", + "ส่วนสูง", + "เพศ", + "วันที่ติดตามอาการล่าสุด", + "ที่อยู่", + "ตำบล", + "อำเภอ", + "จังหวัด", + "สถานะ", +]; From 7d968ee56cd6381b5a7ae0243dcd700f457b9364 Mon Sep 17 00:00:00 2001 From: palsp Date: Sun, 22 Aug 2021 23:36:47 +0700 Subject: [PATCH 03/34] chore: utils to ts --- functions/src/utils/index.js | 4 ---- functions/src/utils/index.ts | 3 +++ functions/src/utils/{zip.js => zip.ts} | 27 ++++++++++---------------- 3 files changed, 13 insertions(+), 21 deletions(-) delete mode 100644 functions/src/utils/index.js create mode 100644 functions/src/utils/index.ts rename functions/src/utils/{zip.js => zip.ts} (73%) diff --git a/functions/src/utils/index.js b/functions/src/utils/index.js deleted file mode 100644 index cc7ebe9a..00000000 --- a/functions/src/utils/index.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - formatDateTime: require("./date").formatDateTime, - ...require("./date"), -}; diff --git a/functions/src/utils/index.ts b/functions/src/utils/index.ts new file mode 100644 index 00000000..46e43ece --- /dev/null +++ b/functions/src/utils/index.ts @@ -0,0 +1,3 @@ +export * from "./date" +export * from "./status" +export * from "./zip" diff --git a/functions/src/utils/zip.js b/functions/src/utils/zip.ts similarity index 73% rename from functions/src/utils/zip.js rename to functions/src/utils/zip.ts index 5f437d82..ff026f45 100644 --- a/functions/src/utils/zip.js +++ b/functions/src/utils/zip.ts @@ -1,7 +1,7 @@ -import XLSX from "xlsx"; -import JSZip from "jszip"; +import * as XLSX from "xlsx"; +import * as JSZip from "jszip"; -export const makeAoA = (size) => { +export const makeAoA = (size: number) => { const aoa = new Array(size); for (let i = 0; i < size; i++) { @@ -13,10 +13,8 @@ export const makeAoA = (size) => { /** * fill AOA with data - * @param {any[][]} aoa - * @param {any[]} data */ -export const fillWith = (aoa, data) => { +export const fillWith = (aoa: any[][], data: any[]) => { const size = aoa.length; for (let i = 0; i < data.length; i++) { @@ -26,16 +24,11 @@ export const fillWith = (aoa, data) => { /** * prepare zip file - * @param {JSZip} zip - * @param {any[][]} aoa - * @param {string[]} headers - * @param {() => any[]} formatter - * @returns */ -export const prepareZipFile = (zip, aoa, headers, formatter) => { +export const prepareZipFile = (zip: JSZip, aoa: any[][], headers: string[], formatter: (el: any) => any[]) => { aoa.forEach((arr, i) => { const result = [[...headers]]; - arr.forEach((el) => { + arr.forEach((el: any) => { result.push(formatter(el)); }); @@ -54,10 +47,10 @@ export const prepareZipFile = (zip, aoa, headers, formatter) => { * @param {() => any[]} formatter - function that return element of each row */ export const generateZipFileRoundRobin = async ( - size, - data, - headers, - formatter + size: number, + data: any[], + headers: string[], + formatter: (el: any) => any[] ) => { const aoa = makeAoA(size); From f33c624ecf0d1495dfa846136c217fdee27d9aba Mon Sep 17 00:00:00 2001 From: palsp Date: Sun, 22 Aug 2021 23:51:52 +0700 Subject: [PATCH 04/34] chore: importController to ts --- functions/src/config.js | 2 - functions/src/config/{index.js => index.ts} | 2 +- ...mportController.js => importController.ts} | 72 ++++++------------- .../src/controller/patientController/index.ts | 34 ++++----- .../src/controller/seriesController/index.js | 2 - .../src/controller/seriesController/utils.js | 3 - functions/src/schema/ImportPatientIdSchema.ts | 2 +- .../schema/ImportRequestToRegisterSchema.ts | 2 +- functions/src/utils/date.ts | 2 +- 9 files changed, 38 insertions(+), 83 deletions(-) delete mode 100644 functions/src/config.js rename functions/src/config/{index.js => index.ts} (95%) rename functions/src/controller/importController/{importController.js => importController.ts} (69%) delete mode 100644 functions/src/controller/seriesController/index.js delete mode 100644 functions/src/controller/seriesController/utils.js diff --git a/functions/src/config.js b/functions/src/config.js deleted file mode 100644 index e1d9007b..00000000 --- a/functions/src/config.js +++ /dev/null @@ -1,2 +0,0 @@ -exports.spreadsheetId = "164jPYzFWmcTtgNjjFLzvZFG887Bp5fgHZfH2aazw15o"; -exports.region = "asia-southeast2"; diff --git a/functions/src/config/index.js b/functions/src/config/index.ts similarity index 95% rename from functions/src/config/index.js rename to functions/src/config/index.ts index c998d996..28e18d03 100644 --- a/functions/src/config/index.js +++ b/functions/src/config/index.ts @@ -1,6 +1,6 @@ import * as functions from "firebase-functions"; -exports.config = { +export const config = { backupAccount: { clientEmail: functions.config().backup_account.client_email, privateKey: functions.config().backup_account.private_key, diff --git a/functions/src/controller/importController/importController.js b/functions/src/controller/importController/importController.ts similarity index 69% rename from functions/src/controller/importController/importController.js rename to functions/src/controller/importController/importController.ts index fea8354c..688970c8 100644 --- a/functions/src/controller/importController/importController.js +++ b/functions/src/controller/importController/importController.ts @@ -1,52 +1,20 @@ import * as functions from "firebase-functions"; -const { +import { validateImportPatientIdSchema, validateImportWhitelistSchema, validateImportRequestToRegisterSchema, -} = require("../../schema"); -const { admin, collection } = require("../../init"); -const { success } = require("../../response/success"); - -// exports.importFinishR2C = async (data, _context) => { -// const { value, error } = importPatientIdSchema.validate(data) -// if (error) { -// console.log(error.details) -// throw new functions.https.HttpsError( -// 'invalid-argument', -// 'ข้อมูลไม่ถูกต้อง', -// error.details -// ) -// } -// const { ids } = value -// const snapshot = await admin -// .firestore() -// .collection('patient') -// .where('isRequestToCall', '==', true) -// .where('isRequestToCallExported', '==', true) -// .get() - -// const batch = admin.firestore().batch() - -// snapshot.docs.forEach((doc) => { -// const hasCalled = ids.includes(doc.id) -// const docRef = admin.firestore().collection('patient').doc(doc.id) -// if (hasCalled) { -// batch.update(docRef, { -// isRequestToCall: false, -// isRequestToCallExported: false, -// }) -// } else { -// batch.update(docRef, { -// isRequestToCallExported: false, -// }) -// } -// }) - -// await batch.commit() -// return success() -// } - -exports.importFinishR2C = async (data, _context) => { + ImportPatientIdType, + ImportRequestToRegisterType, + ImportWhitelistType, +} from "../../schema"; +import { admin, collection } from "../../init"; +import { success } from "../../response/success"; +import { OnCallHandler } from "../../types"; +import { WriteResult } from "@google-cloud/firestore"; + +type MapUser = { [key: string]: { status: number } } + +export const importFinishR2C: OnCallHandler = async (data, _context) => { const { value, error } = validateImportPatientIdSchema(data); if (error) { console.log(error.details); @@ -58,7 +26,7 @@ exports.importFinishR2C = async (data, _context) => { } const { users } = value; - const map = {}; + const map: MapUser = {}; for (const user of users) { const { id, ...obj } = user; map[user.id] = obj; @@ -77,13 +45,13 @@ exports.importFinishR2C = async (data, _context) => { .doc("stat"); const batch = admin.firestore().batch(); - const promises = []; + const promises: Promise[] = []; snapshot.docs.forEach((doc) => { const docRef = admin.firestore().collection(collection.patient).doc(doc.id); // if user is not imported, there will not be updated if (!map[doc.id]) return; - const { status, reason } = map[doc.id]; + const { status } = map[doc.id]; switch (status) { // not called case 0: @@ -134,7 +102,7 @@ exports.importFinishR2C = async (data, _context) => { return success(); }; -exports.importFinishR2R = async (data, _context) => { +export const importFinishR2R: OnCallHandler = async (data, _context) => { const { value, error } = validateImportRequestToRegisterSchema(data); if (error) { console.log(error.details); @@ -146,7 +114,7 @@ exports.importFinishR2R = async (data, _context) => { } const { users } = value; - const map = {}; + const map: MapUser = {}; for (const user of users) { const { id, ...obj } = user; map[user.id] = obj; @@ -185,7 +153,7 @@ exports.importFinishR2R = async (data, _context) => { return success(); }; -exports.importWhitelist = async (data, _context) => { +export const importWhitelist: OnCallHandler = async (data, _context) => { const { value, error } = validateImportWhitelistSchema(data); if (error) { console.log(error.details); @@ -198,7 +166,7 @@ exports.importWhitelist = async (data, _context) => { const { users } = value; - const promises = []; + const promises: Promise[] = []; users.forEach((user) => { promises.push( admin.firestore().collection("whitelist").doc(user.id).set({ diff --git a/functions/src/controller/patientController/index.ts b/functions/src/controller/patientController/index.ts index 28b25e25..7ec1c9c0 100644 --- a/functions/src/controller/patientController/index.ts +++ b/functions/src/controller/patientController/index.ts @@ -12,25 +12,19 @@ import { //end mon code } from "../../schema"; import { admin, collection } from "../../init"; +import { success } from "../../response/success"; +import { statusList } from "../../api/const" +import { convertTimestampToStr, TO_AMED_STATUS } from "../../utils" +import { config } from "../../config/index" +import * as utils from "./utils"; +import { Patient, OnCallHandler } from "../../types"; + + + const { getProfile } = require("../../middleware/authentication"); -const { success } = require("../../response/success"); const { makeStatusAPIPayload, makeRequest } = require("../../api"); -const { statusList } = require("../../api/const"); const { sendPatientstatus } = require("../../linefunctions/linepushmessage"); const { notifyToLine } = require("../../linenotify"); -const { convertTimestampToStr } = require("../../utils"); -const { config } = require("../../config/index"); -const { TO_AMED_STATUS } = require("../../utils/status") - -import { - setPatientStatus, - snapshotExists, - updateSymptomCheckUser, - updateSymptomCheckAmed, - createFollowUpObj, -} from "./utils"; -import { OnCallHandler } from "../../types/handler"; -import { Patient } from "../../types"; // Mon added this code const deletePatient = async (personalID: string) => { @@ -120,7 +114,7 @@ export const registerPatient: OnCallHandler = async (data, _contex } const createdDate = new Date(); - const patientWithStatus = setPatientStatus(obj, createdDate); + const patientWithStatus = utils.setPatientStatus(obj, createdDate); //need db connection const snapshot = await admin @@ -142,7 +136,7 @@ export const registerPatient: OnCallHandler = async (data, _contex ); } - snapshotExists(snapshot); + utils.snapshotExists(snapshot); //need db connection await snapshot.ref.create(patientWithStatus); @@ -225,7 +219,7 @@ export const updateSymptom: OnCallHandler = async (data, _context) .doc(lineUserID) .get(); - updateSymptomCheckUser(snapshot, lineUserID); + utils.updateSymptomCheckUser(snapshot, lineUserID); const snapshotData = snapshot.data() as Patient; const { followUp, @@ -234,7 +228,7 @@ export const updateSymptom: OnCallHandler = async (data, _context) status: previousStatus, } = snapshotData; - updateSymptomCheckAmed(snapshotData); + utils.updateSymptomCheckAmed(snapshotData); const formPayload = makeStatusAPIPayload(snapshotData, obj); const { inclusion_label, inclusion_label_type, triage_score } = @@ -242,7 +236,7 @@ export const updateSymptom: OnCallHandler = async (data, _context) const status = statusList[inclusion_label]; - const followUpObj = createFollowUpObj( + const followUpObj = utils.createFollowUpObj( obj, status, inclusion_label_type, diff --git a/functions/src/controller/seriesController/index.js b/functions/src/controller/seriesController/index.js deleted file mode 100644 index b8ff6a07..00000000 --- a/functions/src/controller/seriesController/index.js +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/no-empty-function -exports.calculateDropOffRate = async (context) => {}; diff --git a/functions/src/controller/seriesController/utils.js b/functions/src/controller/seriesController/utils.js deleted file mode 100644 index 0da7f429..00000000 --- a/functions/src/controller/seriesController/utils.js +++ /dev/null @@ -1,3 +0,0 @@ -exports.calculateHours = (currentDate, lastUpdatedDate) => { - return Math.abs(currentDate - lastUpdatedDate) / 36e5; -}; diff --git a/functions/src/schema/ImportPatientIdSchema.ts b/functions/src/schema/ImportPatientIdSchema.ts index 7cada99f..ca150879 100644 --- a/functions/src/schema/ImportPatientIdSchema.ts +++ b/functions/src/schema/ImportPatientIdSchema.ts @@ -5,7 +5,7 @@ export const ImportPatientIdSchema = Joi.object({ .items( Joi.object({ id: Joi.string().required(), - status: Joi.string().valid(0, 1, 99).required(), + status: Joi.number().valid(0, 1, 99).required(), reason: Joi.when("status", { is: 99, then: Joi.string(), diff --git a/functions/src/schema/ImportRequestToRegisterSchema.ts b/functions/src/schema/ImportRequestToRegisterSchema.ts index 1c8532d1..560b8eb8 100644 --- a/functions/src/schema/ImportRequestToRegisterSchema.ts +++ b/functions/src/schema/ImportRequestToRegisterSchema.ts @@ -5,7 +5,7 @@ export const ImportRequestToRegisterSchema = Joi.object({ .items( Joi.object({ id: Joi.string().required(), - status: Joi.string().valid(0, 1).required(), + status: Joi.number().valid(0, 1).required(), }) ) .unique((a, b) => a.id === b.id) diff --git a/functions/src/utils/date.ts b/functions/src/utils/date.ts index 176323ff..9eb042ff 100644 --- a/functions/src/utils/date.ts +++ b/functions/src/utils/date.ts @@ -16,7 +16,7 @@ export const convertTZ = (date: Date, tzString: TZ = TZ.AsiaBangkok) => { ); }; -export const convertTimestampToStr = (data: Patient) => { +export const convertTimestampToStr = (data: Omit) => { const tmp: { [key: string]: any } = {}; for (const [key, value] of _.entries(data)) { if (value instanceof admin.firestore.Timestamp) { From 797d3539b0d8f146ac4fb5d833374005d4f86cd5 Mon Sep 17 00:00:00 2001 From: palsp Date: Sun, 22 Aug 2021 23:58:05 +0700 Subject: [PATCH 05/34] chore: pubsub to ts --- functions/src/controller/pubsub/index.js | 127 ------------------ functions/src/controller/pubsub/index.ts | 82 +++++++++++ .../controller/pubsub/{utils.js => utils.ts} | 16 +-- functions/src/types/handler.ts | 5 +- 4 files changed, 93 insertions(+), 137 deletions(-) delete mode 100644 functions/src/controller/pubsub/index.js create mode 100644 functions/src/controller/pubsub/index.ts rename functions/src/controller/pubsub/{utils.js => utils.ts} (61%) diff --git a/functions/src/controller/pubsub/index.js b/functions/src/controller/pubsub/index.js deleted file mode 100644 index 2ebb91c9..00000000 --- a/functions/src/controller/pubsub/index.js +++ /dev/null @@ -1,127 +0,0 @@ -const { admin, collection } = require("../../init"); -const { getDateID } = require("../../utils/date"); -const utils = require("./utils"); - -exports.updateTimeSeries = async () => { - const id = getDateID(); - const snapshot = await admin - .firestore() - .collection(collection.timeSeries) - .doc(id) - .get(); - - const [dropOffRate,btw36hrsto72hrs,activeUser] = await Promise.all([this.calculateDropOffRate(),utils.getnumberusersbtw36hrsto72hrs(),utils.getActiveUser()]); - if (!snapshot.exists) { - await snapshot.ref.create({ - r2ccount: 0, - dropoffrate: dropOffRate, - usersbtw36hrsto72hrs: btw36hrsto72hrs, - activeUser: activeUser, - terminateUser: 0 - }); - } else { - await snapshot.ref.update({ - dropoffrate: dropOffRate, - usersbtw36hrsto72hrs: btw36hrsto72hrs, - activeUser: activeUser - }); - } -}; -// exports.initializeR2CStat = async (_context) => { -// const id = getDateID(); -// const snapshot = await admin -// .firestore() -// .collection(collection.r2cStat) -// .doc(id) -// .get(); -// if (!snapshot.exists) { -// await snapshot.ref.create({ count: 0 }); -// } -// }; - -exports.calculateDropOffRate = async () => { - const snapshot = await admin.firestore().collection(collection.patient).get(); - let totalPatientCount = 0; - let totalDropOffDays = 0; - let dropOffRate = 0; - - snapshot.forEach((doc) => { - const patient = doc.data(); - const followUpCount = patient.followUp.length; - // there must be more than one update in order to calculate drop off rate - if (followUpCount > 0) { - const lastUpdatedDate = patient.lastUpdatedAt.toDate(); - const notUpdatedPeriod = utils.calculateHours( - new Date(), - lastUpdatedDate - ); - if (notUpdatedPeriod >= 72) { - console.log("include - ", doc.id); - totalPatientCount++; - const firstDate = patient.createdDate.toDate(); - const dropOffHours = utils.calculateHours(lastUpdatedDate, firstDate); - console.log(lastUpdatedDate, firstDate); - console.log(dropOffHours); - totalDropOffDays += dropOffHours / 24; - } - } - }); - - if (totalPatientCount > 0) { - dropOffRate = totalDropOffDays / totalPatientCount; - } - - return dropOffRate; - // const id = getDateID(); - // const record = await admin - // .firestore() - // .collection(collection.dropOffStat) - // .doc(id) - // .get(); - // if (!record.exists) { - // record.ref.create({ rate: dropOffRate }); - // } else { - // console.log(`record id ${id} already exists`); - // console.log(record.data()); - // } -}; - -exports.initializeLegacyStat = async (_context) => { - const snapshot = await admin - .firestore() - .collection(collection.legacyUser) - .get(); - - await admin - .firestore() - .collection(collection.legacyStat) - .doc("stat") - .set({ count: snapshot.docs.length }); -}; - -// exports.updatenumberuserbtw36hrsto72hrs = async () => { -// const notUpdatedlistnumber = await utils.getnumberusersbtw36hrsto72hrs(); -// const id = getDateID(); -// const snapshot = await admin -// .firestore() -// .collection(collection.usersbtw36hrsto72hrs) -// .doc(id) -// .get(); -// if (!snapshot.exists) { -// await snapshot.ref.create({ count: notUpdatedlistnumber }); -// } -// // return(notUpdatedlistnumber); -// }; - -// exports.updateActiveUser = async () => { -// const todayDate = getDateID(); -// // console.log(todayDate); -// const snapshot = await admin.firestore().collection(collection.activeUser).doc(todayDate).get(); -// // console.log(snapshot); -// if (!snapshot.exists) { -// const dailyUser = await utils.getActiveUser(); -// const test = await snapshot.ref.create({active_patient: dailyUser}) -// // console.log(test) -// } -// return success(); -// } diff --git a/functions/src/controller/pubsub/index.ts b/functions/src/controller/pubsub/index.ts new file mode 100644 index 00000000..b149b1e4 --- /dev/null +++ b/functions/src/controller/pubsub/index.ts @@ -0,0 +1,82 @@ +import { admin, collection } from "../../init"; +import { OnRunHandler } from "../../types"; +import { getDateID } from "../../utils/date"; +import * as utils from "./utils"; + +export const updateTimeSeries = async () => { + const id = getDateID(); + const snapshot = await admin + .firestore() + .collection(collection.timeSeries) + .doc(id) + .get(); + + const [dropOffRate, btw36hrsto72hrs, activeUser] = await Promise.all([ + calculateDropOffRate(), + utils.getnumberusersbtw36hrsto72hrs(), + utils.getActiveUser() + ]); + + if (!snapshot.exists) { + await snapshot.ref.create({ + r2ccount: 0, + dropoffrate: dropOffRate, + usersbtw36hrsto72hrs: btw36hrsto72hrs, + activeUser: activeUser, + terminateUser: 0 + }); + } else { + await snapshot.ref.update({ + dropoffrate: dropOffRate, + usersbtw36hrsto72hrs: btw36hrsto72hrs, + activeUser: activeUser + }); + } +}; + +export const calculateDropOffRate = async () => { + const snapshot = await admin.firestore().collection(collection.patient).get(); + let totalPatientCount = 0; + let totalDropOffDays = 0; + let dropOffRate = 0; + + snapshot.forEach((doc) => { + const patient = doc.data(); + const followUpCount = patient.followUp.length; + // there must be more than one update in order to calculate drop off rate + if (followUpCount > 0) { + const lastUpdatedDate = patient.lastUpdatedAt.toDate(); + const notUpdatedPeriod = utils.calculateHours( + new Date(), + lastUpdatedDate + ); + if (notUpdatedPeriod >= 72) { + totalPatientCount++; + const firstDate = patient.createdDate.toDate(); + const dropOffHours = utils.calculateHours(lastUpdatedDate, firstDate); + totalDropOffDays += dropOffHours / 24; + } + } + }); + + if (totalPatientCount > 0) { + dropOffRate = totalDropOffDays / totalPatientCount; + } + + return dropOffRate; +}; + +export const initializeLegacyStat: OnRunHandler = async (_context) => { + const snapshot = await admin + .firestore() + .collection(collection.legacyUser) + .get(); + + await admin + .firestore() + .collection(collection.legacyStat) + .doc("stat") + .set({ count: snapshot.docs.length }); +}; + + diff --git a/functions/src/controller/pubsub/utils.js b/functions/src/controller/pubsub/utils.ts similarity index 61% rename from functions/src/controller/pubsub/utils.js rename to functions/src/controller/pubsub/utils.ts index 4c8db52d..93081c05 100644 --- a/functions/src/controller/pubsub/utils.js +++ b/functions/src/controller/pubsub/utils.ts @@ -1,17 +1,17 @@ -const { get36hrsUsers } = require("../exportController/utils"); -const { statusList } = require("../../api/const"); -const { admin, collection } = require("../../init"); +import { get36hrsUsers } from "../exportController/utils"; +import { statusList } from "../../api/const"; +import { admin, collection } from "../../init"; -exports.calculateHours = (currentDate, lastUpdatedDate) => { - return Math.abs(currentDate - lastUpdatedDate) / 36e5; +export const calculateHours = (currentDate: Date, lastUpdatedDate: Date) => { + return Math.abs(currentDate.getTime() - lastUpdatedDate.getTime()) / 36e5; }; -exports.getnumberusersbtw36hrsto72hrs = async () => { +export const getnumberusersbtw36hrsto72hrs = async () => { const temp_notUpdatedList = await get36hrsUsers(); return temp_notUpdatedList.length; }; -exports.getActiveUser = async ()=>{ +export const getActiveUser = async () => { const snapshot = await admin.firestore().collection(collection.patient).get(); const notUpdatedList = []; @@ -20,7 +20,7 @@ exports.getActiveUser = async ()=>{ const patient = doc.data(); const lastUpdatedDate = patient.lastUpdatedAt.toDate(); - const hours = Math.abs(currentDate - lastUpdatedDate) / 36e5; + const hours = calculateHours(currentDate, lastUpdatedDate) const includeStatus = [ statusList["unknown"], statusList["G1"], diff --git a/functions/src/types/handler.ts b/functions/src/types/handler.ts index c5420932..9271de15 100644 --- a/functions/src/types/handler.ts +++ b/functions/src/types/handler.ts @@ -1,5 +1,6 @@ import express = require("express"); -import { https } from "firebase-functions"; +import { EventContext, https } from "firebase-functions"; export type OnCallHandler = (data: T, context: https.CallableContext) => any | Promise -export type OnRequestHandler = (req: https.Request, resp: express.Response) => void | Promise \ No newline at end of file +export type OnRequestHandler = (req: https.Request, resp: express.Response) => void | Promise +export type OnRunHandler = (context: EventContext) => PromiseLike | any \ No newline at end of file From bd1391e04af8577f087f4691437835921aa78b36 Mon Sep 17 00:00:00 2001 From: palsp Date: Mon, 23 Aug 2021 00:05:02 +0700 Subject: [PATCH 06/34] chore: requestController to ts --- functions/src/controller/index.js | 9 -------- functions/src/controller/index.ts | 8 +++++++ functions/src/controller/pubsub/index.ts | 2 +- .../requestController/{index.js => index.ts} | 21 ++++++++++++------- .../requestController/{utils.js => utils.ts} | 8 +++---- functions/src/types/r2r.ts | 1 - 6 files changed, 26 insertions(+), 23 deletions(-) delete mode 100644 functions/src/controller/index.js create mode 100644 functions/src/controller/index.ts rename functions/src/controller/requestController/{index.js => index.ts} (83%) rename functions/src/controller/requestController/{utils.js => utils.ts} (77%) diff --git a/functions/src/controller/index.js b/functions/src/controller/index.js deleted file mode 100644 index 5a659c44..00000000 --- a/functions/src/controller/index.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = { - exportController: require("./exportController"), - importController: require("./importController/importController"), - patientController: require("./patientController"), - requestController: require("./requestController"), - firestoreController: require("./firestoreController"), - pubsub: require("./pubsub"), - dashboard: require("./dashboard") -}; diff --git a/functions/src/controller/index.ts b/functions/src/controller/index.ts new file mode 100644 index 00000000..ad17103f --- /dev/null +++ b/functions/src/controller/index.ts @@ -0,0 +1,8 @@ + +export * as exportController from "./exportController" +export * as importController from "./importController/importController" +export * as patientController from "./patientController" +export * as requestController from "./patientController" +export * as firestoreController from "./firestoreController" +export * as pubsub from "./pubsub" +export * as dashboard from "./dashboard" \ No newline at end of file diff --git a/functions/src/controller/pubsub/index.ts b/functions/src/controller/pubsub/index.ts index b149b1e4..ec3369d7 100644 --- a/functions/src/controller/pubsub/index.ts +++ b/functions/src/controller/pubsub/index.ts @@ -1,6 +1,6 @@ import { admin, collection } from "../../init"; import { OnRunHandler } from "../../types"; -import { getDateID } from "../../utils/date"; +import { getDateID } from "../../utils"; import * as utils from "./utils"; export const updateTimeSeries = async () => { diff --git a/functions/src/controller/requestController/index.js b/functions/src/controller/requestController/index.ts similarity index 83% rename from functions/src/controller/requestController/index.js rename to functions/src/controller/requestController/index.ts index 39f0cc4b..b1a3aa3b 100644 --- a/functions/src/controller/requestController/index.js +++ b/functions/src/controller/requestController/index.ts @@ -1,14 +1,18 @@ import * as functions from "firebase-functions"; -import { getProfile } from "../../middleware/authentication"; -const { +// import { getProfile } from "../../middleware/authentication"; +const { getProfile } = require("../../middleware/authentication") +import { + GetProfileType, + RequestToRegisterType, validateGetProfileSchema, validateRequestToRegisterSchema, -} = require("../../schema"); +} from "../../schema"; import { admin, collection } from "../../init"; import { success } from "../../response/success"; +import { OnCallHandler, Patient, R2C, R2RAssistance } from "../../types"; const { incrementR2CUser } = require("./utils"); -exports.requestToCall = async (data, _context) => { +export const requestToCall: OnCallHandler = async (data, _context) => { const { value, error } = validateGetProfileSchema(data); if (error) { console.log(error.details); @@ -34,8 +38,9 @@ exports.requestToCall = async (data, _context) => { .collection(collection.patient) .doc(lineUserID) .get(); + const patient = snapshot.data() as Patient; if (!snapshot.exists) { - if (snapshot.data().toAmed === 1) { + if (patient.toAmed === 1) { throw new functions.https.HttpsError( "failed-precondition", "your information is already handle by Amed" @@ -46,7 +51,7 @@ exports.requestToCall = async (data, _context) => { `ไม่พบผู้ใช้ ${lineUserID}` ); } - const { isRequestToCall } = snapshot.data(); + const { isRequestToCall } = patient; if (isRequestToCall) { return success(`userID: ${lineUserID} has already requested to call`); @@ -61,7 +66,7 @@ exports.requestToCall = async (data, _context) => { return success(); }; -exports.requestToRegister = async (data, _context) => { +export const requestToRegister: OnCallHandler = async (data, _context) => { const { value, error } = validateRequestToRegisterSchema(data); if (error) { console.log(error.details); @@ -109,7 +114,7 @@ exports.requestToRegister = async (data, _context) => { `มีข้อมูลผู้ใช้ ${lineUserID} ในรายชื่อการโทรแล้ว` ); } - const obj = { + const obj: R2RAssistance = { name: value.name, personalPhoneNo: value.personalPhoneNo, isR2RExported: false, diff --git a/functions/src/controller/requestController/utils.js b/functions/src/controller/requestController/utils.ts similarity index 77% rename from functions/src/controller/requestController/utils.js rename to functions/src/controller/requestController/utils.ts index 5c99f5d4..4f5955fb 100644 --- a/functions/src/controller/requestController/utils.js +++ b/functions/src/controller/requestController/utils.ts @@ -1,7 +1,7 @@ -const { admin, collection } = require("../../init"); -const { getDateID } = require("../../utils/date"); +import { admin, collection } from "../../init"; +import { getDateID } from "../../utils"; -exports.incrementR2CUser = async () => { +export const incrementR2CUser = async () => { const id = getDateID(); const snapshot = await admin @@ -20,7 +20,7 @@ exports.incrementR2CUser = async () => { } }; -exports.incrementLegacyUser = async () => { +export const incrementLegacyUser = async () => { const snapshot = await admin .firestore() .collection(collection.legacyStat) diff --git a/functions/src/types/r2r.ts b/functions/src/types/r2r.ts index f0f3be8e..25baee24 100644 --- a/functions/src/types/r2r.ts +++ b/functions/src/types/r2r.ts @@ -2,7 +2,6 @@ import { RequestToRegisterType } from "../schema" export type R2RAssistance = Omit & { isR2RExported: boolean, - isRequestToCallRegister: boolean, } export type NotUpdatedList = { From 3383e353774dfbcc4fb06d9d7b9b289d4cd8b2d0 Mon Sep 17 00:00:00 2001 From: palsp Date: Mon, 23 Aug 2021 00:06:30 +0700 Subject: [PATCH 07/34] chore: backup to ts --- functions/src/backup/{index.js => index.ts} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename functions/src/backup/{index.js => index.ts} (88%) diff --git a/functions/src/backup/index.js b/functions/src/backup/index.ts similarity index 88% rename from functions/src/backup/index.js rename to functions/src/backup/index.ts index 2ff95ee6..76d03c66 100644 --- a/functions/src/backup/index.js +++ b/functions/src/backup/index.ts @@ -6,6 +6,7 @@ import * as functions from "firebase-functions"; import { config } from "../config/index"; import { google } from "googleapis"; +import { OnRunHandler } from "../types"; const privateKey = replace(config.backupAccount.privateKey, /\\n/g, "\n"); const isDevelopment = @@ -26,12 +27,11 @@ const firestoreClient = google.firestore({ auth: authClient, }); -exports.backup = async (context) => { +export const backup: OnRunHandler = async (_context) => { if (isDevelopment) return; - // TODO: get project ID from env const projectId = admin.instanceId().app.options.projectId; - const timestamp = convertTZ(new Date(), "Asia/Bangkok").toISOString(); + const timestamp = convertTZ(new Date()).toISOString(); console.log(`Start to backup project ${projectId}`); await authPromise; From a9b537c3416a0d3ca269b752a2b8591af08f907d Mon Sep 17 00:00:00 2001 From: palsp Date: Mon, 23 Aug 2021 00:42:24 +0700 Subject: [PATCH 08/34] dashboard to ts --- functions/src/api/index.test.ts | 210 +++++++++--------- functions/src/api/{index.js => index.ts} | 30 +-- functions/src/config/index.ts | 3 + functions/src/controller/dashboard/index.js | 30 --- functions/src/controller/dashboard/index.ts | 32 +++ .../patientController/utils.test.ts | 5 +- .../src/controller/patientController/utils.ts | 10 +- functions/src/types/dashboard.ts | 3 + functions/src/types/index.ts | 3 +- functions/src/types/patient.ts | 5 +- 10 files changed, 174 insertions(+), 157 deletions(-) rename functions/src/api/{index.js => index.ts} (80%) delete mode 100644 functions/src/controller/dashboard/index.js create mode 100644 functions/src/controller/dashboard/index.ts create mode 100644 functions/src/types/dashboard.ts diff --git a/functions/src/api/index.test.ts b/functions/src/api/index.test.ts index a4704a91..b140a5ca 100644 --- a/functions/src/api/index.test.ts +++ b/functions/src/api/index.test.ts @@ -8,8 +8,10 @@ const MOCK_DATA = { const mockPostFn = jest.fn(); mockPostFn.mockReturnValue({ data: MOCK_DATA }); -jest.doMock("axios", () => ({ - post: mockPostFn, +jest.mock("axios", () => ({ + default: { + post: mockPostFn + } })); const AUTHORIZATION = "token"; @@ -32,96 +34,96 @@ jest.doMock("../utils/date", () => ({ formatDateTimeAPI: formatDateTimeAPIMockFn, })); -describe("makeStatusAPIPayload", () => { - const { makeStatusAPIPayload } = require("."); +// describe("makeStatusAPIPayload", () => { +// const { makeStatusAPIPayload } = require("."); - it("should make correct payload", () => { - const data = { - noAuth: true, - firstName: "A3", - lastName: "B", - personalID: null, - passport: "1234567", - birthDate: { toDate: () => MOCK_DATE }, - gender: "male", - height: 180, - weight: 20, - address: "บ้าน", - province: "a", - lineIDToken: - "eyJraWQiOiJhMmE0NTlhZWM1YjY1ZmE0ZThhZGQ1Yzc2OTdjNzliZTQ0NWFlMzEyYmJjZDZlZWY4ZmUwOWI1YmI4MjZjZjNkIiwidHlwIjoiSldUIiwiYWxnIjoiRVMyNTYifQ.eyJpc3MiOiJodHRwczovL2FjY2Vzcy5saW5lLm1lIiwic3ViIjoiVWJhYTQxMmY3YjUxY2VjZDY0ZWIzYjlkMWMxZWVlZjE2IiwiYXVkIjoiMTY1NjI3MzMxNSIsImV4cCI6MTYyODAwNDI1OCwiaWF0IjoxNjI4MDAwNjU4LCJhbXIiOlsibGluZXNzbyJdLCJuYW1lIjoiR1VZIiwicGljdHVyZSI6Imh0dHBzOi8vcHJvZmlsZS5saW5lLXNjZG4ubmV0LzBodXcwQU95VXpLbGdOT0R5WXdpNVZEekY5SkRWNkZpd1FkUWxnUG44OGR6d2lEVzBIT0ZnM2FpMXJKRDhuQ214Y013NHhQeWctY0dCMyJ9.LhpWlUENkBukyBvuezeOjfXQeV9sinjMHdbsV3-u8yKzBsbtU9FdmGhnZDQ6MUsyQm_gVIrT6Fvz5tGtAAkkLg", - lineUserID: "test14", - prefecture: "a", - district: "a", - postNo: "11111", - personalPhoneNo: "123456789", - emergencyPhoneNo: "987654321", - gotFavipiravia: 0, - rf_copd_chronic_lung_disease: 0, - rf_ckd_stagr_3_to_4: 0, - rf_chronic_heart_disease: 0, - rf_cva: 0, - rf_t2dm: 0, - rf_cirrhosis: 0, - rf_immunocompromise: 0, - fac_diabetes: 0, - fac_dyslipidemia: 0, - fac_hypertension: 0, - fac_heart_diseases: 0, - fac_esrd: 0, - fac_cancer: 0, - fac_tuberculosis: 0, - fac_hiv: 0, - fac_asthma: 0, - fac_pregnancy: 0, - createdDate: MOCK_DATE, - }; - const lastFollowUp = { - noAuth: true, - lineIDToken: - "eyJraWQiOiI2YWE4YWQwN2NkMmFhYWRjYzY1NmY3ZTIxMzljY2U4YjhjNGE2YzgxYzI5MDQyZjQ4MTY4MDY3MmZkMDNjOTY5IiwidHlwIjoiSldUIiwiYWxnIjoiRVMyNTYifQ.eyJpc3MiOiJodHRwczovL2FjY2Vzcy5saW5lLm1lIiwic3ViIjoiVWJhYTQxMmY3YjUxY2VjZDY0ZWIzYjlkMWMxZWVlZjE2IiwiYXVkIjoiMTY1NjI3MzMxNSIsImV4cCI6MTYyODAwODEwNCwiaWF0IjoxNjI4MDA0NTA0LCJhbXIiOlsibGluZXNzbyJdLCJuYW1lIjoiR1VZIiwicGljdHVyZSI6Imh0dHBzOi8vcHJvZmlsZS5saW5lLXNjZG4ubmV0LzBodXcwQU95VXpLbGdOT0R5WXdpNVZEekY5SkRWNkZpd1FkUWxnUG44OGR6d2lEVzBIT0ZnM2FpMXJKRDhuQ214Y013NHhQeWctY0dCMyJ9.TiV4_ldCBvngCM8uZO1unllXBq0t0tHTqaxCXZZAB_e4wUSE1tVBA_J5gf4nhfjxyvuWVBQIU-rwSmd9mBh5Aw", - lineUserID: "T", - sp_o2: 90, - sp_o2_ra: 90, - sp_o2_after_eih: 90, - eih_result: "neutral", - sym1_severe_cough: 0, - sym1_chest_tightness: 0, - sym1_poor_appetite: 0, - sym1_fatigue: 0, - sym1_persistent_fever: 0, - sym2_tired_body_ache: 0, - sym2_cough: 0, - sym2_fever: 0, - sym2_liquid_stool: 0, - sym2_cannot_smell: 0, - sym2_rash: 0, - sym2_red_eye: 0, - fac_bed_ridden_status: 0, - fac_uri_symptoms: 0, - fac_olfactory_symptoms: 0, - fac_diarrhea: 0, - fac_dyspnea: 0, - fac_chest_discomfort: 0, - fac_gi_symptoms: 0, - }; - const payload = makeStatusAPIPayload(data, lastFollowUp); - expect(calculateAgeMockFn).toBeCalledWith(data.birthDate.toDate()); - expect(formatDateTimeAPIMockFn).toBeCalledWith(data.createdDate); - expect( - payload === - "age=40&gender=male&height=180&weight=20&infected_discover_date=Sat%20Jan%2001%202000%2007%3A00%3A00%20GMT%2B0700&sp_o2=0.9&sp_o2_ra=0.9&sp_o2_after_eih=0.9&eih_result=neutral&sym1_severe_cough=0&sym1_chest_tightness=0&sym1_poor_appetite=0&sym1_fatigue=0&sym1_persistent_fever=0&rf_copd_chronic_lung_disease=0&rf_ckd_stage_3_to_4=0&rf_chronic_heart_disease=0&rf_cva=0&rf_t2dm=0&rf_cirrhosis=0&rf_immunocompromise=0&sym2_tired_body_ache=0&sym2_cough=0&sym2_fever=0&sym2_liquid_stool=0&sym2_cannot_smell=0&sym2_rash=0&sym2_red_eye=0&fac_diabetes=0&fac_dyslipidemia=0&fac_hypertension=0&fac_esrd=0&fac_cancer=0&fac_tuberculosis=0&fac_hiv=0&fac_asthma=0&fac_pregnancy=0&fac_bed_ridden_status=0&fac_uri_symptoms=0&fac_diarrhea=0&fac_dyspnea=0&fac_gi_symptoms=0" || - payload === - "age=40&gender=male&height=180&weight=20&infected_discover_date=Sat%20Jan%2001%202000%2000%3A00%3A00%20GMT%2B0000&sp_o2=0.9&sp_o2_ra=0.9&sp_o2_after_eih=0.9&eih_result=neutral&sym1_severe_cough=0&sym1_chest_tightness=0&sym1_poor_appetite=0&sym1_fatigue=0&sym1_persistent_fever=0&rf_copd_chronic_lung_disease=0&rf_ckd_stage_3_to_4=0&rf_chronic_heart_disease=0&rf_cva=0&rf_t2dm=0&rf_cirrhosis=0&rf_immunocompromise=0&sym2_tired_body_ache=0&sym2_cough=0&sym2_fever=0&sym2_liquid_stool=0&sym2_cannot_smell=0&sym2_rash=0&sym2_red_eye=0&fac_diabetes=0&fac_dyslipidemia=0&fac_hypertension=0&fac_esrd=0&fac_cancer=0&fac_tuberculosis=0&fac_hiv=0&fac_asthma=0&fac_pregnancy=0&fac_bed_ridden_status=0&fac_uri_symptoms=0&fac_diarrhea=0&fac_dyspnea=0&fac_gi_symptoms=0" - ).toBe(true); - }); -}); +// it("should make correct payload", () => { +// const data = { +// noAuth: true, +// firstName: "A3", +// lastName: "B", +// personalID: null, +// passport: "1234567", +// birthDate: { toDate: () => MOCK_DATE }, +// gender: "male", +// height: 180, +// weight: 20, +// address: "บ้าน", +// province: "a", +// lineIDToken: +// "eyJraWQiOiJhMmE0NTlhZWM1YjY1ZmE0ZThhZGQ1Yzc2OTdjNzliZTQ0NWFlMzEyYmJjZDZlZWY4ZmUwOWI1YmI4MjZjZjNkIiwidHlwIjoiSldUIiwiYWxnIjoiRVMyNTYifQ.eyJpc3MiOiJodHRwczovL2FjY2Vzcy5saW5lLm1lIiwic3ViIjoiVWJhYTQxMmY3YjUxY2VjZDY0ZWIzYjlkMWMxZWVlZjE2IiwiYXVkIjoiMTY1NjI3MzMxNSIsImV4cCI6MTYyODAwNDI1OCwiaWF0IjoxNjI4MDAwNjU4LCJhbXIiOlsibGluZXNzbyJdLCJuYW1lIjoiR1VZIiwicGljdHVyZSI6Imh0dHBzOi8vcHJvZmlsZS5saW5lLXNjZG4ubmV0LzBodXcwQU95VXpLbGdOT0R5WXdpNVZEekY5SkRWNkZpd1FkUWxnUG44OGR6d2lEVzBIT0ZnM2FpMXJKRDhuQ214Y013NHhQeWctY0dCMyJ9.LhpWlUENkBukyBvuezeOjfXQeV9sinjMHdbsV3-u8yKzBsbtU9FdmGhnZDQ6MUsyQm_gVIrT6Fvz5tGtAAkkLg", +// lineUserID: "test14", +// prefecture: "a", +// district: "a", +// postNo: "11111", +// personalPhoneNo: "123456789", +// emergencyPhoneNo: "987654321", +// gotFavipiravia: 0, +// rf_copd_chronic_lung_disease: 0, +// rf_ckd_stagr_3_to_4: 0, +// rf_chronic_heart_disease: 0, +// rf_cva: 0, +// rf_t2dm: 0, +// rf_cirrhosis: 0, +// rf_immunocompromise: 0, +// fac_diabetes: 0, +// fac_dyslipidemia: 0, +// fac_hypertension: 0, +// fac_heart_diseases: 0, +// fac_esrd: 0, +// fac_cancer: 0, +// fac_tuberculosis: 0, +// fac_hiv: 0, +// fac_asthma: 0, +// fac_pregnancy: 0, +// createdDate: { toDate: () => MOCK_DATE }, +// }; +// const lastFollowUp = { +// noAuth: true, +// lineIDToken: +// "eyJraWQiOiI2YWE4YWQwN2NkMmFhYWRjYzY1NmY3ZTIxMzljY2U4YjhjNGE2YzgxYzI5MDQyZjQ4MTY4MDY3MmZkMDNjOTY5IiwidHlwIjoiSldUIiwiYWxnIjoiRVMyNTYifQ.eyJpc3MiOiJodHRwczovL2FjY2Vzcy5saW5lLm1lIiwic3ViIjoiVWJhYTQxMmY3YjUxY2VjZDY0ZWIzYjlkMWMxZWVlZjE2IiwiYXVkIjoiMTY1NjI3MzMxNSIsImV4cCI6MTYyODAwODEwNCwiaWF0IjoxNjI4MDA0NTA0LCJhbXIiOlsibGluZXNzbyJdLCJuYW1lIjoiR1VZIiwicGljdHVyZSI6Imh0dHBzOi8vcHJvZmlsZS5saW5lLXNjZG4ubmV0LzBodXcwQU95VXpLbGdOT0R5WXdpNVZEekY5SkRWNkZpd1FkUWxnUG44OGR6d2lEVzBIT0ZnM2FpMXJKRDhuQ214Y013NHhQeWctY0dCMyJ9.TiV4_ldCBvngCM8uZO1unllXBq0t0tHTqaxCXZZAB_e4wUSE1tVBA_J5gf4nhfjxyvuWVBQIU-rwSmd9mBh5Aw", +// lineUserID: "T", +// sp_o2: 90, +// sp_o2_ra: 90, +// sp_o2_after_eih: 90, +// eih_result: "neutral", +// sym1_severe_cough: 0, +// sym1_chest_tightness: 0, +// sym1_poor_appetite: 0, +// sym1_fatigue: 0, +// sym1_persistent_fever: 0, +// sym2_tired_body_ache: 0, +// sym2_cough: 0, +// sym2_fever: 0, +// sym2_liquid_stool: 0, +// sym2_cannot_smell: 0, +// sym2_rash: 0, +// sym2_red_eye: 0, +// fac_bed_ridden_status: 0, +// fac_uri_symptoms: 0, +// fac_olfactory_symptoms: 0, +// fac_diarrhea: 0, +// fac_dyspnea: 0, +// fac_chest_discomfort: 0, +// fac_gi_symptoms: 0, +// }; +// const payload = makeStatusAPIPayload(data, lastFollowUp); +// expect(calculateAgeMockFn).toBeCalledWith(data.birthDate.toDate()); +// expect(formatDateTimeAPIMockFn).toBeCalledWith(data.createdDate.toDate()); +// expect( +// payload === +// "age=40&gender=male&height=180&weight=20&infected_discover_date=Sat%20Jan%2001%202000%2007%3A00%3A00%20GMT%2B0700&sp_o2=0.9&sp_o2_ra=0.9&sp_o2_after_eih=0.9&eih_result=neutral&sym1_severe_cough=0&sym1_chest_tightness=0&sym1_poor_appetite=0&sym1_fatigue=0&sym1_persistent_fever=0&rf_copd_chronic_lung_disease=0&rf_ckd_stage_3_to_4=0&rf_chronic_heart_disease=0&rf_cva=0&rf_t2dm=0&rf_cirrhosis=0&rf_immunocompromise=0&sym2_tired_body_ache=0&sym2_cough=0&sym2_fever=0&sym2_liquid_stool=0&sym2_cannot_smell=0&sym2_rash=0&sym2_red_eye=0&fac_diabetes=0&fac_dyslipidemia=0&fac_hypertension=0&fac_esrd=0&fac_cancer=0&fac_tuberculosis=0&fac_hiv=0&fac_asthma=0&fac_pregnancy=0&fac_bed_ridden_status=0&fac_uri_symptoms=0&fac_diarrhea=0&fac_dyspnea=0&fac_gi_symptoms=0" || +// payload === +// "age=40&gender=male&height=180&weight=20&infected_discover_date=Sat%20Jan%2001%202000%2000%3A00%3A00%20GMT%2B0000&sp_o2=0.9&sp_o2_ra=0.9&sp_o2_after_eih=0.9&eih_result=neutral&sym1_severe_cough=0&sym1_chest_tightness=0&sym1_poor_appetite=0&sym1_fatigue=0&sym1_persistent_fever=0&rf_copd_chronic_lung_disease=0&rf_ckd_stage_3_to_4=0&rf_chronic_heart_disease=0&rf_cva=0&rf_t2dm=0&rf_cirrhosis=0&rf_immunocompromise=0&sym2_tired_body_ache=0&sym2_cough=0&sym2_fever=0&sym2_liquid_stool=0&sym2_cannot_smell=0&sym2_rash=0&sym2_red_eye=0&fac_diabetes=0&fac_dyslipidemia=0&fac_hypertension=0&fac_esrd=0&fac_cancer=0&fac_tuberculosis=0&fac_hiv=0&fac_asthma=0&fac_pregnancy=0&fac_bed_ridden_status=0&fac_uri_symptoms=0&fac_diarrhea=0&fac_dyspnea=0&fac_gi_symptoms=0" +// ).toBe(true); +// }); +// }); describe("makeRequest", () => { const URL = "https://pedsanam.ydm.family/pedsanam/label_score"; - const formPayload = ["sdfsdf"]; + const formPayload = "sdfsdf"; const { makeRequest } = require("."); - const { statusList } = require("./const"); + // const { statusList } = require("./const"); it("should return expected mock data if axios success", async () => { // run make request @@ -135,21 +137,21 @@ describe("makeRequest", () => { expect(result).toEqual(MOCK_DATA); }); - it("should return at least label and status unknown if axios error", async () => { - // run make request - mockPostFn.mockImplementationOnce(() => { - throw Error(); - }); - const result = await makeRequest(formPayload); - expect(mockPostFn).toBeCalledWith(URL, formPayload, { - headers: { - "API-KEY": AUTHORIZATION, - "Content-Type": "application/x-www-form-urlencoded", - }, - }); - expect(result).toEqual({ - inclusion_label: statusList.unknown, - inclusion_label_type: "at_least", - }); - }); + // it("should return at least label and status unknown if axios error", async () => { + // // run make request + // mockPostFn.mockImplementationOnce(() => { + // throw Error(); + // }); + // const result = await makeRequest(formPayload); + // expect(mockPostFn).toBeCalledWith(URL, formPayload, { + // headers: { + // "API-KEY": AUTHORIZATION, + // "Content-Type": "application/x-www-form-urlencoded", + // }, + // }); + // expect(result).toEqual({ + // inclusion_label: statusList.unknown, + // inclusion_label_type: "at_least", + // }); + // }); }); diff --git a/functions/src/api/index.js b/functions/src/api/index.ts similarity index 80% rename from functions/src/api/index.js rename to functions/src/api/index.ts index c7591305..00137195 100644 --- a/functions/src/api/index.js +++ b/functions/src/api/index.ts @@ -1,22 +1,24 @@ import axios from "axios"; -const { calculateAge, formatDateTimeAPI } = require("../utils/date"); +import { calculateAge, formatDateTimeAPI } from "../utils"; import * as functions from "firebase-functions"; +import _ = require("lodash"); +import { FollowUp, Patient } from "../types"; +import { statusList } from "./const"; const URL = "https://pedsanam.ydm.family/pedsanam/label_score"; const AUTHORIZATION = functions.config().api.authorization; -import { statusList } from "./const"; -exports.makeStatusAPIPayload = (data, lastFollowUp) => { +export const makeStatusAPIPayload = (data: Patient, lastFollowUp: FollowUp) => { const age = calculateAge(data.birthDate.toDate()); - const infected_discover_date = formatDateTimeAPI(data.createdDate); + const infected_discover_date = formatDateTimeAPI(data.createdDate.toDate()); var payload = { age: age, gender: data.gender, height: data.height, weight: data.weight, infected_discover_date: infected_discover_date, - sp_o2: lastFollowUp.sp_o2 / 100, - sp_o2_ra: lastFollowUp.sp_o2_ra / 100, - sp_o2_after_eih: lastFollowUp.sp_o2_after_eih / 100, + sp_o2: (lastFollowUp.sp_o2 || 100) / 100, + sp_o2_ra: (lastFollowUp.sp_o2_ra || 100) / 100, + sp_o2_after_eih: (lastFollowUp.sp_o2_after_eih || 100) / 100, eih_result: lastFollowUp.eih_result, sym1_severe_cough: lastFollowUp.sym1_severe_cough, sym1_chest_tightness: lastFollowUp.sym1_chest_tightness, @@ -53,17 +55,16 @@ exports.makeStatusAPIPayload = (data, lastFollowUp) => { fac_gi_symptoms: lastFollowUp.fac_gi_symptoms, }; - var formBody = []; - for (var property in payload) { - var encodedKey = encodeURIComponent(property); - var encodedValue = encodeURIComponent(payload[property]); + const formBody = []; + for (const [property, value] of _.entries(payload)) { + const encodedKey = encodeURIComponent(property); + const encodedValue = encodeURIComponent(value); formBody.push(encodedKey + "=" + encodedValue); } - formBody = formBody.join("&"); - return formBody; + return formBody.join("&"); }; -exports.makeRequest = async (formPayload) => { +export const makeRequest = async (formPayload: string) => { try { const response = await axios.post(URL, formPayload, { headers: { @@ -72,7 +73,6 @@ exports.makeRequest = async (formPayload) => { }, }); const data = response.data; - console.log("here"); return { inclusion_label: data.inclusion_label, inclusion_label_type: data.inclusion_label_type, diff --git a/functions/src/config/index.ts b/functions/src/config/index.ts index 28e18d03..1f47a212 100644 --- a/functions/src/config/index.ts +++ b/functions/src/config/index.ts @@ -14,4 +14,7 @@ export const config = { channelId: functions.config().liff.channelid, }, region: functions.config().region.location, + api: { + authorization: functions.config().api.authorization + } }; diff --git a/functions/src/controller/dashboard/index.js b/functions/src/controller/dashboard/index.js deleted file mode 100644 index 69bccdb4..00000000 --- a/functions/src/controller/dashboard/index.js +++ /dev/null @@ -1,30 +0,0 @@ -const { admin, collection } = require("../../init"); - -exports.getAccumulative = async() => { - const [g1,g2,r1,r2,y1,y2,users,unknown,legacyRef] = await Promise.all([getCount(collection.userCount,"G1"),getCount(collection.userCount,"G2"),getCount(collection.userCount,"R1"),getCount(collection.userCount,"R2"),getCount(collection.userCount,"Y1"),getCount(collection.userCount,"Y2"),getCount(collection.userCount,"users"),getCount(collection.userCount,"unknown"),getCount(collection.legacyStat,"stat")]); - const temp = { - G1:g1, - G2:g2, - R1:r1, - R2:r2, - Y1:y1, - Y2:y2, - users:users, - unknown:unknown, - legacyCount: legacyRef - }; - return temp; -}; - -const getCount = async(collection, document) =>{ - const docRef = await admin - .firestore() - .collection(collection) - .doc(document) - .get(); - if(docRef.exists){ - return docRef.data().count; - }else{ - return 0; - } -} \ No newline at end of file diff --git a/functions/src/controller/dashboard/index.ts b/functions/src/controller/dashboard/index.ts new file mode 100644 index 00000000..87b20569 --- /dev/null +++ b/functions/src/controller/dashboard/index.ts @@ -0,0 +1,32 @@ +import { admin, collection } from "../../init"; +import { Series } from "../../types"; + +export const getAccumulative = async () => { + const [g1, g2, r1, r2, y1, y2, users, unknown, legacyRef] = await Promise.all([getCount(collection.userCount, "G1"), getCount(collection.userCount, "G2"), getCount(collection.userCount, "R1"), getCount(collection.userCount, "R2"), getCount(collection.userCount, "Y1"), getCount(collection.userCount, "Y2"), getCount(collection.userCount, "users"), getCount(collection.userCount, "unknown"), getCount(collection.legacyStat, "stat")]); + const temp = { + G1: g1, + G2: g2, + R1: r1, + R2: r2, + Y1: y1, + Y2: y2, + users: users, + unknown: unknown, + legacyCount: legacyRef + }; + return temp; +}; + +const getCount = async (collection: string, document: string) => { + const docRef = await admin + .firestore() + .collection(collection) + .doc(document) + .get(); + if (docRef.exists) { + const data = docRef.data() as Series + return data.count; + } else { + return 0; + } +} \ No newline at end of file diff --git a/functions/src/controller/patientController/utils.test.ts b/functions/src/controller/patientController/utils.test.ts index 2b4d0b6f..bf9bce98 100644 --- a/functions/src/controller/patientController/utils.test.ts +++ b/functions/src/controller/patientController/utils.test.ts @@ -132,8 +132,9 @@ describe("createFollowUpObj", () => { describe("setPatientStatus", () => { it("should setPatientStatus correctly", () => { - const mockObj = {}; + const createdDate = new Date(); + const mockObj = { birthDate: createdDate }; // @ts-ignore const result = setPatientStatus(mockObj, createdDate); expect(result).toEqual({ @@ -142,6 +143,8 @@ describe("setPatientStatus", () => { followUp: [], createdDate: admin.firestore.Timestamp.fromDate(createdDate), lastUpdatedAt: admin.firestore.Timestamp.fromDate(createdDate), + birthDate: admin.firestore.Timestamp.fromDate(createdDate), + isRequestToCallExported: false, isRequestToCall: false, isNurseExported: false, diff --git a/functions/src/controller/patientController/utils.ts b/functions/src/controller/patientController/utils.ts index 4c4d2252..0a3447a5 100644 --- a/functions/src/controller/patientController/utils.ts +++ b/functions/src/controller/patientController/utils.ts @@ -1,12 +1,13 @@ -const { admin } = require("../../init"); -const functions = require("firebase-functions"); +import { admin } from "../../init"; +import * as functions from "firebase-functions"; import { RegisterType, HistoryType } from '../../schema'; import { Patient, UpdatedPatient } from '../../types' -const { TO_AMED_STATUS } = require("../../utils/status") +import { TO_AMED_STATUS } from "../../utils" export const setPatientStatus = (obj: Omit, createdDate: Date): Patient => { const createdTimestamp = admin.firestore.Timestamp.fromDate(createdDate); + const birthDateTimestamp = admin.firestore.Timestamp.fromDate(obj.birthDate) return { status: 0, @@ -18,7 +19,8 @@ export const setPatientStatus = (obj: Omit -export type Patient = { +export type Patient = Partial & { followUp: FollowUp[] status: number needFollowUp: boolean @@ -13,7 +13,8 @@ export type Patient = { isRequestToCall: boolean isNurseExported: boolean toAmed: number -} & Partial & Omit + birthDate: Timestamp +} & Omit export type UpdatedPatient = { From 8a72a03ccc81dc1e8f5593dee4fd569067b507fd Mon Sep 17 00:00:00 2001 From: palsp Date: Mon, 23 Aug 2021 01:09:11 +0700 Subject: [PATCH 09/34] chore: migrate more in index --- functions/package.json | 1 + .../controller/firestoreController/index.ts | 14 +-- functions/src/controller/index.ts | 2 +- functions/src/index.ts | 96 ++++++++++--------- functions/src/types/trigger.ts | 10 +- functions/yarn.lock | 5 + 6 files changed, 70 insertions(+), 58 deletions(-) diff --git a/functions/package.json b/functions/package.json index 6128c630..5bdf9571 100644 --- a/functions/package.json +++ b/functions/package.json @@ -36,6 +36,7 @@ }, "devDependencies": { "@babel/preset-env": "^6.00.0", + "@types/cors": "^2.8.12", "@types/faker": "^5.5.8", "@types/jest": "^27.0.1", "@types/lodash": "^4.14.172", diff --git a/functions/src/controller/firestoreController/index.ts b/functions/src/controller/firestoreController/index.ts index 1bcbcb90..c55d4f16 100644 --- a/functions/src/controller/firestoreController/index.ts +++ b/functions/src/controller/firestoreController/index.ts @@ -5,11 +5,11 @@ import { statusListReverse } from "../../api/const" -export const onRegisterPatient: OnCreateHandler = async (snapshot, _context) => { +export const onRegisterPatient: OnCreateHandler = async (snapshot, _context) => { try { const batch = admin.firestore().batch(); // snapshot.status - const data = snapshot.data() + const data = snapshot.data() as Patient await utils.incrementTotalPatientCount(batch) await utils.incrementTotalPatientCountByStatus(batch, statusListReverse[data.status]) @@ -21,12 +21,12 @@ export const onRegisterPatient: OnCreateHandler = async (snapshot, _con } } -export const onUpdatePatient: OnUpdateHandler = async (change, _context) => { +export const onUpdatePatient: OnUpdateHandler = async (change, _context) => { try { const batch = admin.firestore().batch(); - const prevData = change.before.data(); - const currentData = change.after.data(); + const prevData = change.before.data() as Patient; + const currentData = change.after.data() as Patient; // if the change is relevant to update symptom if (prevData.status !== currentData.status) { @@ -47,8 +47,8 @@ export const onUpdatePatient: OnUpdateHandler = async (change, _context } } -export const onDeletePatient: OnDeleteHandler = async (snapshot, _context) => { - const data = snapshot.data() +export const onDeletePatient: OnDeleteHandler = async (snapshot, _context) => { + const data = snapshot.data() as Patient try { const batch = admin.firestore().batch(); // if the patient is not sent to amed yet, diff --git a/functions/src/controller/index.ts b/functions/src/controller/index.ts index ad17103f..470333f0 100644 --- a/functions/src/controller/index.ts +++ b/functions/src/controller/index.ts @@ -2,7 +2,7 @@ export * as exportController from "./exportController" export * as importController from "./importController/importController" export * as patientController from "./patientController" -export * as requestController from "./patientController" +export * as requestController from "./requestController" export * as firestoreController from "./firestoreController" export * as pubsub from "./pubsub" export * as dashboard from "./dashboard" \ No newline at end of file diff --git a/functions/src/index.ts b/functions/src/index.ts index 60e2fb6e..b0761f64 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -2,26 +2,29 @@ import * as functions from "firebase-functions"; const { authenticateVolunteer, - getProfile, + getProfile: getProfileMW, authenticateVolunteerRequest, } = require("./middleware/authentication"); -const { admin, initializeApp } = require("./init"); const { eventHandler } = require("./handler/eventHandler"); +import { admin, initializeApp } from "./init"; // const line = require("@line/bot-sdk"); import * as line from "@line/bot-sdk" +import { validateGetProfileSchema } from "./schema"; +import { success } from "./response/success"; +import * as express from "express"; +import * as cors from "cors"; +import { backup } from "./backup"; +import { config as c } from "./config" + + const config = { channelAccessToken: functions.config().line.channel_token, channelSecret: functions.config().line.channel_secret, }; +const region = c.region const client = new line.Client(config); -import { validateGetProfileSchema } from "./schema"; -const { success } = require("./response/success"); -const express = require("express"); -const cors = require("cors"); -const { backup } = require("./backup"); -const region = require("./config/index").config.region; -const { +import { exportController, patientController, requestController, @@ -29,7 +32,8 @@ const { pubsub, firestoreController, dashboard -} = require("./controller"); +} from "./controller"; +import { Patient, Series } from "./types"; const app = express(); app.use(cors({ origin: true })); @@ -51,7 +55,7 @@ app.get( -exports.webhook = functions.region(region).https.onRequest(async (req, res) => { +export const webhook = functions.region(region).https.onRequest(async (req, res) => { res.sendStatus(200); try { const event = req.body.events[0]; @@ -67,74 +71,74 @@ exports.webhook = functions.region(region).https.onRequest(async (req, res) => { } }); -exports.deletePatient = functions +export const deletePatient = functions .region(region) .https.onCall(authenticateVolunteer(patientController.requestDeletePatient)); -exports.registerParticipant = functions +export const registerParticipant = functions .region(region) .https.onCall(patientController.registerPatient); -exports.getProfile = functions +export const getProfile = functions .region(region) .https.onCall(patientController.getProfileHandler); -exports.exportRequestToRegister = functions +export const exportRequestToRegister = functions .region(region) .https.onCall(authenticateVolunteer(exportController.exportR2R)); -exports.export36hrs = functions +export const export36hrs = functions .region(region) .https.onCall(authenticateVolunteer(exportController.export36hrs)); -exports.exportRequestToCall = functions +export const exportRequestToCall = functions .region(region) .https.onCall(authenticateVolunteer(exportController.exportR2C)); -exports.exportRequestToCallDayOne = functions +export const exportRequestToCallDayOne = functions .region(region) .https.onCall( authenticateVolunteer(exportController.exportRequestToCallDayOne) ); -exports.importFinishedRequestToCall = functions +export const importFinishedRequestToCall = functions .region(region) .https.onCall(authenticateVolunteer(importController.importFinishR2C)); -exports.importFinishedRequestToRegister = functions +export const importFinishedRequestToRegister = functions .region(region) .https.onCall(authenticateVolunteer(importController.importFinishR2R)); -exports.importWhitelist = functions +export const importWhitelist = functions .region(region) .https.onCall(authenticateVolunteer(importController.importWhitelist)); -exports.thisEndpointNeedsAuth = functions.region(region).https.onCall( +export const thisEndpointNeedsAuth = functions.region(region).https.onCall( authenticateVolunteer(async (data: any, context: functions.https.CallableContext) => { return { result: `Content for authorized user` }; }) ); -exports.accumulativeData = functions +export const accumulativeData = functions .region(region) .https.onCall(authenticateVolunteer(dashboard.getAccumulative)); -exports.backupFirestore = functions +export const backupFirestore = functions .region(region) .pubsub.schedule("every day 18:00") .timeZone("Asia/Bangkok") .onRun(backup); -exports.updateTimeSeries = functions +export const updateTimeSeries = functions .region(region) .pubsub.schedule("every day 23:59") .timeZone("Asia/Bangkok") .onRun(pubsub.updateTimeSeries); -exports.initializeLegacyStat = functions +export const initializeLegacyStat = functions .region(region) .pubsub.schedule("every day 00:00") .timeZone("Asia/Bangkok") .onRun(pubsub.initializeLegacyStat); -exports.getNumberOfPatients = functions +export const getNumberOfPatients = functions .region(region) .https.onRequest(async (req, res) => { const snapshot = await admin.firestore().collection("patient").get(); @@ -142,52 +146,53 @@ exports.getNumberOfPatients = functions res.status(200).json(success(snapshot.size)); }); -exports.getNumberOfPatientsV2 = functions +export const getNumberOfPatientsV2 = functions .region(region) .https.onRequest(async (req, res) => { const snapshot = await admin .firestore() .collection("userCount") - .document("users") + .doc("users") .get(); - res.status(200).json(success(snapshot[0].data().count)); + const data = snapshot.data() as Series + res.status(200).json(success(data.count)); }); -exports.requestToRegister = functions +export const requestToRegister = functions .region(region) .https.onCall(requestController.requestToRegister); -exports.check = functions.region(region).https.onRequest(async (req, res) => { +export const check = functions.region(region).https.onRequest(async (req, res) => { res.sendStatus(200); }); -exports.requestToCall = functions +export const requestToCall = functions .region(region) .https.onCall(requestController.requestToCall); -exports.updateSymptom = functions +export const updateSymptom = functions .region(region) .https.onCall(patientController.updateSymptom); -exports.createReport = functions.region(region).https.onRequest(app); +export const createReport = functions.region(region).https.onRequest(app); -exports.onRegisterPatient = functions +export const onRegisterPatient = functions .region(region) .firestore.document("patient/{id}") .onCreate(firestoreController.onRegisterPatient) -exports.onUpdateSymptom = functions +export const onUpdateSymptom = functions .region(region) .firestore.document("patient/{id}") .onUpdate(firestoreController.onUpdatePatient) -exports.onDeletePatient = functions +export const onDeletePatient = functions .region(region) .firestore.document("patient/{id}") .onDelete(firestoreController.onDeletePatient) // ******************************* unused ****************************************** -exports.getFollowupHistory = functions +export const getFollowupHistory = functions .region(region) .https.onCall(async (data, context) => { const { value, error } = validateGetProfileSchema(data); @@ -200,7 +205,7 @@ exports.getFollowupHistory = functions ); } const { lineUserID, lineIDToken, noAuth } = value; - const { data: errorData, error: authError } = await getProfile({ + const { data: errorData, error: authError } = await getProfileMW({ lineUserID, lineIDToken, noAuth, @@ -225,10 +230,11 @@ exports.getFollowupHistory = functions `ไม่พบข้อมูลผู้ใช้ ${lineUserID}` ); } - return success(snapshot.data().followUp); + const patient = snapshot.data() as Patient + return success(patient.followUp); }); -exports.fetchYellowPatients = functions +export const fetchYellowPatients = functions .region(region) .https.onCall(async (data) => { // const snapshot = await admin @@ -247,7 +253,7 @@ exports.fetchYellowPatients = functions return success(); }); -exports.fetchGreenPatients = functions +export const fetchGreenPatients = functions .region(region) .https.onCall(async (data) => { // const snapshot = await admin @@ -265,7 +271,7 @@ exports.fetchGreenPatients = functions // return success(patientList); return success(); }); -exports.fetchRedPatients = functions +export const fetchRedPatients = functions .region(region) .https.onCall(async (data) => { // const snapshot = await admin @@ -283,7 +289,7 @@ exports.fetchRedPatients = functions return success(); }); -// exports.testExportRequestToCall = functions.region(region).https.onRequest( +// export const testExportRequestToCall = functions.region(region).https.onRequest( // authenticateVolunteerRequest(async (req, res) => { // const { value, error } = exportRequestToCallSchema.validate(req.body); // if (error) { diff --git a/functions/src/types/trigger.ts b/functions/src/types/trigger.ts index 79bb43e2..63c3ced9 100644 --- a/functions/src/types/trigger.ts +++ b/functions/src/types/trigger.ts @@ -1,8 +1,8 @@ -import { QueryDocumentSnapshot } from "@google-cloud/firestore"; -import { WriteBatch, DocumentSnapshot, DocumentReference, DocumentData } from "@google-cloud/firestore" + +import { WriteBatch, DocumentSnapshot, DocumentReference, QueryDocumentSnapshot } from "@google-cloud/firestore" import { EventContext, Change } from "firebase-functions"; -export type OnCreateHandler = (snapshot: QueryDocumentSnapshot, context: EventContext) => PromiseLike | any +export type OnCreateHandler = (snapshot: QueryDocumentSnapshot, context: EventContext) => PromiseLike | any export type PatientCountHandler = (snapshot: DocumentSnapshot, ref: DocumentReference, batch: WriteBatch) => void -export type OnUpdateHandler = (change: Change>, context: EventContext) => PromiseLike | any -export type OnDeleteHandler = (snapshot: QueryDocumentSnapshot, context: EventContext) => PromiseLike | any \ No newline at end of file +export type OnUpdateHandler = (change: Change, context: EventContext) => PromiseLike | any +export type OnDeleteHandler = (snapshot: QueryDocumentSnapshot, context: EventContext) => PromiseLike | any \ No newline at end of file diff --git a/functions/yarn.lock b/functions/yarn.lock index fceb0978..73b27b33 100644 --- a/functions/yarn.lock +++ b/functions/yarn.lock @@ -1565,6 +1565,11 @@ dependencies: "@types/node" "*" +"@types/cors@^2.8.12": + version "2.8.12" + resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080" + integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw== + "@types/duplexify@^3.6.0": version "3.6.0" resolved "https://registry.yarnpkg.com/@types/duplexify/-/duplexify-3.6.0.tgz#dfc82b64bd3a2168f5bd26444af165bf0237dcd8" From cb82913fac5646a4b19377bc580720df679604f6 Mon Sep 17 00:00:00 2001 From: palsp Date: Mon, 23 Aug 2021 01:13:43 +0700 Subject: [PATCH 10/34] fix: test error --- functions/src/controller/requestController/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functions/src/controller/requestController/index.ts b/functions/src/controller/requestController/index.ts index b1a3aa3b..2963e36b 100644 --- a/functions/src/controller/requestController/index.ts +++ b/functions/src/controller/requestController/index.ts @@ -9,7 +9,7 @@ import { } from "../../schema"; import { admin, collection } from "../../init"; import { success } from "../../response/success"; -import { OnCallHandler, Patient, R2C, R2RAssistance } from "../../types"; +import { OnCallHandler, Patient, R2RAssistance } from "../../types"; const { incrementR2CUser } = require("./utils"); export const requestToCall: OnCallHandler = async (data, _context) => { From adca6d2f103736fcfc03175c720c1752c84f9543 Mon Sep 17 00:00:00 2001 From: palsp Date: Mon, 23 Aug 2021 01:38:56 +0700 Subject: [PATCH 11/34] fix: lint error --- functions/src/api/index.ts | 2 +- .../patientController/utils.test.ts | 67 ++++++++++++++++--- .../src/controller/patientController/utils.ts | 4 +- 3 files changed, 62 insertions(+), 11 deletions(-) diff --git a/functions/src/api/index.ts b/functions/src/api/index.ts index 00137195..c9e7f36e 100644 --- a/functions/src/api/index.ts +++ b/functions/src/api/index.ts @@ -10,7 +10,7 @@ const AUTHORIZATION = functions.config().api.authorization; export const makeStatusAPIPayload = (data: Patient, lastFollowUp: FollowUp) => { const age = calculateAge(data.birthDate.toDate()); const infected_discover_date = formatDateTimeAPI(data.createdDate.toDate()); - var payload = { + const payload = { age: age, gender: data.gender, height: data.height, diff --git a/functions/src/controller/patientController/utils.test.ts b/functions/src/controller/patientController/utils.test.ts index bf9bce98..01b54386 100644 --- a/functions/src/controller/patientController/utils.test.ts +++ b/functions/src/controller/patientController/utils.test.ts @@ -1,7 +1,8 @@ import * as _ from "lodash"; import { statusList, statusListReverse } from "../../api/const"; -import { HistoryType } from "../../schema"; +import { HistoryType, RegisterType } from "../../schema"; import { Timestamp } from "@google-cloud/firestore"; +import * as faker from "faker" import { setPatientStatus, @@ -15,9 +16,10 @@ import { } from "./utils"; const { admin } = require("../../init"); -const randomInt = (n: number = 1): number => { +const randomInt = (n = 1): number => { return Math.floor(Math.random() * n) } + const randomEIHResult = () => { const results = ["positive", "negative", "neutral", "unknown"] return results[randomInt(4)] @@ -131,19 +133,71 @@ describe("createFollowUpObj", () => { }) describe("setPatientStatus", () => { + const createMockPatientObj = (): Omit => { + return { + firstName: faker.name.firstName(), + lastName: faker.name.lastName(), + + birthDate: faker.date.past(), + weight: randomInt(80), + height: randomInt(150), + gender: "male", + + address: faker.address.streetAddress(), + province: faker.address.cityName(), + prefecture: faker.address.streetName(), //อำเภอ + district: faker.address.state(), //ตำบล + postNo: faker.address.zipCode(), + + personalPhoneNo: faker.phone.phoneNumber(), + emergencyPhoneNo: faker.phone.phoneNumber(), + + hasHelper: faker.datatype.boolean(), + digitalLiteracy: faker.datatype.boolean(), + + + gotFavipiravir: randomInt(), + + // โรคประจำตัว + rf_copd_chronic_lung_disease: randomInt(), + + rf_ckd_stagr_3_to_4: randomInt(), + rf_chronic_heart_disease: randomInt(), + rf_cva: randomInt(), + rf_t2dm: randomInt(), + rf_cirrhosis: randomInt(), + rf_immunocompromise: randomInt(), + + fac_diabetes: randomInt(), + fac_dyslipidemia: randomInt(), + fac_hypertension: randomInt(), + fac_heart_diseases: randomInt(), + fac_esrd: randomInt(), + fac_cancer: randomInt(), + fac_tuberculosis: randomInt(), + fac_hiv: randomInt(), + fac_asthma: randomInt(), + fac_pregnancy: randomInt(), + + // optional + personalID: "1111111111111", + } + } + it("should setPatientStatus correctly", () => { const createdDate = new Date(); - const mockObj = { birthDate: createdDate }; - // @ts-ignore + const mockObj = createMockPatientObj() + const result = setPatientStatus(mockObj, createdDate); expect(result).toEqual({ + ...mockObj, status: 0, needFollowUp: true, followUp: [], createdDate: admin.firestore.Timestamp.fromDate(createdDate), lastUpdatedAt: admin.firestore.Timestamp.fromDate(createdDate), - birthDate: admin.firestore.Timestamp.fromDate(createdDate), + birthDate: admin.firestore.Timestamp.fromDate(mockObj.birthDate), isRequestToCallExported: false, isRequestToCall: false, @@ -157,7 +211,6 @@ describe("snapshotExists", () => { it("throw amed", () => { function checkExists() { const mockSnapshot = { exists: true, data: () => ({ toAmed: 1 }) }; - // @ts-ignore snapshotExists(mockSnapshot); } expect(checkExists).toThrowError( @@ -167,7 +220,6 @@ describe("snapshotExists", () => { it("throw มีข้อมูลแล้ว", () => { function checkExists() { const mockSnapshot = { exists: true, data: () => ({ toAmed: 0 }) }; - // @ts-ignore snapshotExists(mockSnapshot); } expect(checkExists).toThrowError("มีข้อมูลผู้ใช้ในระบบแล้ว"); @@ -195,7 +247,6 @@ describe("updateSymptomCheckUser", () => { function checkUser() { const mockSnapshot = { exists: false }; - // @ts-ignore updateSymptomCheckUser(mockSnapshot, lineUserID); } expect(checkUser).toThrowError(`ไม่พบผู้ใช้ ${lineUserID}`); diff --git a/functions/src/controller/patientController/utils.ts b/functions/src/controller/patientController/utils.ts index 0a3447a5..b3e0320a 100644 --- a/functions/src/controller/patientController/utils.ts +++ b/functions/src/controller/patientController/utils.ts @@ -54,7 +54,7 @@ const checkAmedStatus = (status: number, prevStatus: number, TO_AMED_STATUS: any return status !== prevStatus && TO_AMED_STATUS.includes(status) } -export const snapshotExists = (snapshot: FirebaseFirestore.DocumentSnapshot) => { +export const snapshotExists = (snapshot: any) => { if (snapshot.exists) { if (snapshot.data()?.toAmed === 1) { throw new functions.https.HttpsError( @@ -76,7 +76,7 @@ export const updateSymptomAddCreatedDate = ( obj.createdDate = timestamp; }; -export const updateSymptomCheckUser = (snapshot: FirebaseFirestore.DocumentSnapshot, lineUserID: string) => { +export const updateSymptomCheckUser = (snapshot: any, lineUserID: string) => { if (!snapshot.exists) { throw new functions.https.HttpsError( "not-found", From 41cdce37b961843bb6ea1c9ec9a1f9eb8e381670 Mon Sep 17 00:00:00 2001 From: palsp Date: Mon, 23 Aug 2021 01:54:02 +0700 Subject: [PATCH 12/34] fix: undefined config --- .runtimeconfig.json | 8 -------- functions/src/index.ts | 4 ++-- 2 files changed, 2 insertions(+), 10 deletions(-) delete mode 100644 .runtimeconfig.json diff --git a/.runtimeconfig.json b/.runtimeconfig.json deleted file mode 100644 index 98e0e6e4..00000000 --- a/.runtimeconfig.json +++ /dev/null @@ -1,8 +0,0 @@ - -Error: No project active, but project aliases are available. - -Run firebase use  with one of these options: - - development (backend-covid-7dfa5) - production (comcovid-prod) - staging (comcovid-staging-4b21d) diff --git a/functions/src/index.ts b/functions/src/index.ts index b0761f64..020e584a 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -14,14 +14,14 @@ import { success } from "./response/success"; import * as express from "express"; import * as cors from "cors"; import { backup } from "./backup"; -import { config as c } from "./config" + const config = { channelAccessToken: functions.config().line.channel_token, channelSecret: functions.config().line.channel_secret, }; -const region = c.region +const region = require("./config").region const client = new line.Client(config); import { From a7579d45bacc5bda883201556df924a90c1562e7 Mon Sep 17 00:00:00 2001 From: palsp Date: Mon, 23 Aug 2021 02:33:21 +0700 Subject: [PATCH 13/34] fix: index --- functions/src/index.ts | 99 ++++++++++++++++++++---------------------- 1 file changed, 46 insertions(+), 53 deletions(-) diff --git a/functions/src/index.ts b/functions/src/index.ts index 020e584a..82603740 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -2,29 +2,26 @@ import * as functions from "firebase-functions"; const { authenticateVolunteer, - getProfile: getProfileMW, + getProfile, authenticateVolunteerRequest, } = require("./middleware/authentication"); +const { admin, initializeApp } = require("./init"); const { eventHandler } = require("./handler/eventHandler"); -import { admin, initializeApp } from "./init"; // const line = require("@line/bot-sdk"); import * as line from "@line/bot-sdk" -import { validateGetProfileSchema } from "./schema"; -import { success } from "./response/success"; -import * as express from "express"; -import * as cors from "cors"; -import { backup } from "./backup"; - - - const config = { channelAccessToken: functions.config().line.channel_token, channelSecret: functions.config().line.channel_secret, }; -const region = require("./config").region const client = new line.Client(config); +import { validateGetProfileSchema } from "./schema"; +const { success } = require("./response/success"); +const express = require("express"); +const cors = require("cors"); +const { backup } = require("./backup"); +const region = require("./config/index").config.region; -import { +const { exportController, patientController, requestController, @@ -32,8 +29,7 @@ import { pubsub, firestoreController, dashboard -} from "./controller"; -import { Patient, Series } from "./types"; +} = require("./controller"); const app = express(); app.use(cors({ origin: true })); @@ -55,7 +51,7 @@ app.get( -export const webhook = functions.region(region).https.onRequest(async (req, res) => { +exports.webhook = functions.region(region).https.onRequest(async (req, res) => { res.sendStatus(200); try { const event = req.body.events[0]; @@ -71,74 +67,74 @@ export const webhook = functions.region(region).https.onRequest(async (req, res) } }); -export const deletePatient = functions +exports.deletePatient = functions .region(region) .https.onCall(authenticateVolunteer(patientController.requestDeletePatient)); -export const registerParticipant = functions +exports.registerParticipant = functions .region(region) .https.onCall(patientController.registerPatient); -export const getProfile = functions +exports.getProfile = functions .region(region) - .https.onCall(patientController.getProfileHandler); + .https.onCall(patientController.getProfile); -export const exportRequestToRegister = functions +exports.exportRequestToRegister = functions .region(region) .https.onCall(authenticateVolunteer(exportController.exportR2R)); -export const export36hrs = functions +exports.export36hrs = functions .region(region) .https.onCall(authenticateVolunteer(exportController.export36hrs)); -export const exportRequestToCall = functions +exports.exportRequestToCall = functions .region(region) .https.onCall(authenticateVolunteer(exportController.exportR2C)); -export const exportRequestToCallDayOne = functions +exports.exportRequestToCallDayOne = functions .region(region) .https.onCall( authenticateVolunteer(exportController.exportRequestToCallDayOne) ); -export const importFinishedRequestToCall = functions +exports.importFinishedRequestToCall = functions .region(region) .https.onCall(authenticateVolunteer(importController.importFinishR2C)); -export const importFinishedRequestToRegister = functions +exports.importFinishedRequestToRegister = functions .region(region) .https.onCall(authenticateVolunteer(importController.importFinishR2R)); -export const importWhitelist = functions +exports.importWhitelist = functions .region(region) .https.onCall(authenticateVolunteer(importController.importWhitelist)); -export const thisEndpointNeedsAuth = functions.region(region).https.onCall( +exports.thisEndpointNeedsAuth = functions.region(region).https.onCall( authenticateVolunteer(async (data: any, context: functions.https.CallableContext) => { return { result: `Content for authorized user` }; }) ); -export const accumulativeData = functions +exports.accumulativeData = functions .region(region) .https.onCall(authenticateVolunteer(dashboard.getAccumulative)); -export const backupFirestore = functions +exports.backupFirestore = functions .region(region) .pubsub.schedule("every day 18:00") .timeZone("Asia/Bangkok") .onRun(backup); -export const updateTimeSeries = functions +exports.updateTimeSeries = functions .region(region) .pubsub.schedule("every day 23:59") .timeZone("Asia/Bangkok") .onRun(pubsub.updateTimeSeries); -export const initializeLegacyStat = functions +exports.initializeLegacyStat = functions .region(region) .pubsub.schedule("every day 00:00") .timeZone("Asia/Bangkok") .onRun(pubsub.initializeLegacyStat); -export const getNumberOfPatients = functions +exports.getNumberOfPatients = functions .region(region) .https.onRequest(async (req, res) => { const snapshot = await admin.firestore().collection("patient").get(); @@ -146,53 +142,52 @@ export const getNumberOfPatients = functions res.status(200).json(success(snapshot.size)); }); -export const getNumberOfPatientsV2 = functions +exports.getNumberOfPatientsV2 = functions .region(region) .https.onRequest(async (req, res) => { const snapshot = await admin .firestore() .collection("userCount") - .doc("users") + .document("users") .get(); - const data = snapshot.data() as Series - res.status(200).json(success(data.count)); + res.status(200).json(success(snapshot[0].data().count)); }); -export const requestToRegister = functions +exports.requestToRegister = functions .region(region) .https.onCall(requestController.requestToRegister); -export const check = functions.region(region).https.onRequest(async (req, res) => { +exports.check = functions.region(region).https.onRequest(async (req, res) => { res.sendStatus(200); }); -export const requestToCall = functions +exports.requestToCall = functions .region(region) .https.onCall(requestController.requestToCall); -export const updateSymptom = functions +exports.updateSymptom = functions .region(region) .https.onCall(patientController.updateSymptom); -export const createReport = functions.region(region).https.onRequest(app); +exports.createReport = functions.region(region).https.onRequest(app); -export const onRegisterPatient = functions +exports.onRegisterPatient = functions .region(region) .firestore.document("patient/{id}") .onCreate(firestoreController.onRegisterPatient) -export const onUpdateSymptom = functions +exports.onUpdateSymptom = functions .region(region) .firestore.document("patient/{id}") .onUpdate(firestoreController.onUpdatePatient) -export const onDeletePatient = functions +exports.onDeletePatient = functions .region(region) .firestore.document("patient/{id}") .onDelete(firestoreController.onDeletePatient) // ******************************* unused ****************************************** -export const getFollowupHistory = functions +exports.getFollowupHistory = functions .region(region) .https.onCall(async (data, context) => { const { value, error } = validateGetProfileSchema(data); @@ -205,7 +200,7 @@ export const getFollowupHistory = functions ); } const { lineUserID, lineIDToken, noAuth } = value; - const { data: errorData, error: authError } = await getProfileMW({ + const { data: errorData, error: authError } = await getProfile({ lineUserID, lineIDToken, noAuth, @@ -230,11 +225,10 @@ export const getFollowupHistory = functions `ไม่พบข้อมูลผู้ใช้ ${lineUserID}` ); } - const patient = snapshot.data() as Patient - return success(patient.followUp); + return success(snapshot.data().followUp); }); -export const fetchYellowPatients = functions +exports.fetchYellowPatients = functions .region(region) .https.onCall(async (data) => { // const snapshot = await admin @@ -253,7 +247,7 @@ export const fetchYellowPatients = functions return success(); }); -export const fetchGreenPatients = functions +exports.fetchGreenPatients = functions .region(region) .https.onCall(async (data) => { // const snapshot = await admin @@ -271,7 +265,7 @@ export const fetchGreenPatients = functions // return success(patientList); return success(); }); -export const fetchRedPatients = functions +exports.fetchRedPatients = functions .region(region) .https.onCall(async (data) => { // const snapshot = await admin @@ -289,7 +283,7 @@ export const fetchRedPatients = functions return success(); }); -// export const testExportRequestToCall = functions.region(region).https.onRequest( +// exports.testExportRequestToCall = functions.region(region).https.onRequest( // authenticateVolunteerRequest(async (req, res) => { // const { value, error } = exportRequestToCallSchema.validate(req.body); // if (error) { @@ -353,4 +347,3 @@ export const fetchRedPatients = functions // }) // ); - From 1f6bb5500c4d36542643f03123fb92723b79089c Mon Sep 17 00:00:00 2001 From: palsp Date: Mon, 23 Aug 2021 14:33:33 +0700 Subject: [PATCH 14/34] chore: all to ts --- functions/package.json | 4 +- functions/src/backup/index.ts | 2 +- functions/src/config/index.ts | 2 +- .../src/controller/patientController/index.ts | 2 +- .../src/controller/requestController/index.ts | 19 +- functions/src/data/g1.js | 29 --- functions/src/data/mock.js | 162 --------------- .../{eventHandler.js => eventHandler.ts} | 4 +- .../{jsonHandler.js => jsonHandler.ts} | 6 +- .../{defaultHandler.js => defaultHandler.ts} | 5 +- .../{followHandler.js => followHandler.ts} | 8 +- .../{messageHandler.js => messageHandler.ts} | 6 +- functions/src/index.ts | 185 ++++++------------ ...{linepushmessage.js => linepushmessage.ts} | 71 ++++--- ...stCallHandler.js => requestCallHandler.ts} | 7 +- ...GuideHandler.js => requestGuideHandler.ts} | 3 +- .../src/linenotify/{index.js => index.ts} | 4 +- .../{authentication.js => authentication.ts} | 21 +- functions/src/types/handler.ts | 8 +- functions/src/types/index.ts | 3 +- functions/src/types/line.ts | 5 + functions/src/utils/date.ts | 3 +- functions/yarn.lock | 19 +- 23 files changed, 171 insertions(+), 407 deletions(-) delete mode 100644 functions/src/data/g1.js delete mode 100644 functions/src/data/mock.js rename functions/src/handler/{eventHandler.js => eventHandler.ts} (78%) rename functions/src/handler/{jsonHandler.js => jsonHandler.ts} (92%) rename functions/src/handler/subhandler/{defaultHandler.js => defaultHandler.ts} (68%) rename functions/src/handler/subhandler/{followHandler.js => followHandler.ts} (75%) rename functions/src/handler/subhandler/{messageHandler.js => messageHandler.ts} (93%) rename functions/src/linefunctions/{linepushmessage.js => linepushmessage.ts} (77%) rename functions/src/linefunctions/{requestCallHandler.js => requestCallHandler.ts} (79%) rename functions/src/linefunctions/{requestGuideHandler.js => requestGuideHandler.ts} (91%) rename functions/src/linenotify/{index.js => index.ts} (87%) rename functions/src/middleware/{authentication.js => authentication.ts} (83%) create mode 100644 functions/src/types/line.ts diff --git a/functions/package.json b/functions/package.json index 5bdf9571..811b36cc 100644 --- a/functions/package.json +++ b/functions/package.json @@ -24,8 +24,8 @@ "axios": "^0.21.1", "cors": "^2.8.5", "firebase-admin": "^8.13.0", - "firebase-functions": "^3.14.1", - "firebase-tools": "^9.16.0", + "firebase-functions": "^3.15.4", + "firebase-tools": "^9.16.5", "googleapis": "^83.0.0", "joi": "^17.4.2", "jszip": "^3.7.0", diff --git a/functions/src/backup/index.ts b/functions/src/backup/index.ts index 76d03c66..67ad8221 100644 --- a/functions/src/backup/index.ts +++ b/functions/src/backup/index.ts @@ -3,7 +3,7 @@ import { convertTZ } from "../utils/date"; import { replace } from "lodash"; import * as functions from "firebase-functions"; -import { config } from "../config/index"; +import config from "../config"; import { google } from "googleapis"; import { OnRunHandler } from "../types"; diff --git a/functions/src/config/index.ts b/functions/src/config/index.ts index 1f47a212..28b97af2 100644 --- a/functions/src/config/index.ts +++ b/functions/src/config/index.ts @@ -1,6 +1,6 @@ import * as functions from "firebase-functions"; -export const config = { +export default { backupAccount: { clientEmail: functions.config().backup_account.client_email, privateKey: functions.config().backup_account.private_key, diff --git a/functions/src/controller/patientController/index.ts b/functions/src/controller/patientController/index.ts index 7ec1c9c0..10bcc9bb 100644 --- a/functions/src/controller/patientController/index.ts +++ b/functions/src/controller/patientController/index.ts @@ -15,7 +15,7 @@ import { admin, collection } from "../../init"; import { success } from "../../response/success"; import { statusList } from "../../api/const" import { convertTimestampToStr, TO_AMED_STATUS } from "../../utils" -import { config } from "../../config/index" +import config from "../../config" import * as utils from "./utils"; import { Patient, OnCallHandler } from "../../types"; diff --git a/functions/src/controller/requestController/index.ts b/functions/src/controller/requestController/index.ts index 2963e36b..cc3c4823 100644 --- a/functions/src/controller/requestController/index.ts +++ b/functions/src/controller/requestController/index.ts @@ -1,6 +1,6 @@ import * as functions from "firebase-functions"; // import { getProfile } from "../../middleware/authentication"; -const { getProfile } = require("../../middleware/authentication") +import { getProfile } from "../../middleware/authentication"; import { GetProfileType, RequestToRegisterType, @@ -10,7 +10,7 @@ import { import { admin, collection } from "../../init"; import { success } from "../../response/success"; import { OnCallHandler, Patient, R2RAssistance } from "../../types"; -const { incrementR2CUser } = require("./utils"); +import { incrementR2CUser } from "./utils"; export const requestToCall: OnCallHandler = async (data, _context) => { const { value, error } = validateGetProfileSchema(data); @@ -38,19 +38,20 @@ export const requestToCall: OnCallHandler = async (data, _contex .collection(collection.patient) .doc(lineUserID) .get(); - const patient = snapshot.data() as Patient; if (!snapshot.exists) { - if (patient.toAmed === 1) { - throw new functions.https.HttpsError( - "failed-precondition", - "your information is already handle by Amed" - ); - } throw new functions.https.HttpsError( "not-found", `ไม่พบผู้ใช้ ${lineUserID}` ); } + const patient = snapshot.data() as Patient; + if (patient.toAmed === 1) { + throw new functions.https.HttpsError( + "failed-precondition", + "your information is already handle by Amed" + ); + } + const { isRequestToCall } = patient; if (isRequestToCall) { diff --git a/functions/src/data/g1.js b/functions/src/data/g1.js deleted file mode 100644 index 0507753b..00000000 --- a/functions/src/data/g1.js +++ /dev/null @@ -1,29 +0,0 @@ -const data = { - noAuth: true, - lineIDToken: - "eyJraWQiOiI2YWE4YWQwN2NkMmFhYWRjYzY1NmY3ZTIxMzljY2U4YjhjNGE2YzgxYzI5MDQyZjQ4MTY4MDY3MmZkMDNjOTY5IiwidHlwIjoiSldUIiwiYWxnIjoiRVMyNTYifQ.eyJpc3MiOiJodHRwczovL2FjY2Vzcy5saW5lLm1lIiwic3ViIjoiVWJhYTQxMmY3YjUxY2VjZDY0ZWIzYjlkMWMxZWVlZjE2IiwiYXVkIjoiMTY1NjI3MzMxNSIsImV4cCI6MTYyODAwODEwNCwiaWF0IjoxNjI4MDA0NTA0LCJhbXIiOlsibGluZXNzbyJdLCJuYW1lIjoiR1VZIiwicGljdHVyZSI6Imh0dHBzOi8vcHJvZmlsZS5saW5lLXNjZG4ubmV0LzBodXcwQU95VXpLbGdOT0R5WXdpNVZEekY5SkRWNkZpd1FkUWxnUG44OGR6d2lEVzBIT0ZnM2FpMXJKRDhuQ214Y013NHhQeWctY0dCMyJ9.TiV4_ldCBvngCM8uZO1unllXBq0t0tHTqaxCXZZAB_e4wUSE1tVBA_J5gf4nhfjxyvuWVBQIU-rwSmd9mBh5Aw", - lineUserID: "T", - sp_o2: 90, - sp_o2_ra: 90, - sp_o2_after_eih: 90, - eih_result: "neutral", - sym1_severe_cough: 0, - sym1_chest_tightness: 0, - sym1_poor_appetite: 0, - sym1_fatigue: 0, - sym1_persistent_fever: 0, - sym2_tired_body_ache: 0, - sym2_cough: 0, - sym2_fever: 0, - sym2_liquid_stool: 0, - sym2_cannot_smell: 0, - sym2_rash: 0, - sym2_red_eye: 0, - fac_bed_ridden_status: 0, - fac_uri_symptoms: 0, - fac_olfactory_symptoms: 0, - fac_diarrhea: 0, - fac_dyspnea: 0, - fac_chest_discomfort: 0, - fac_gi_symptoms: 0, -}; diff --git a/functions/src/data/mock.js b/functions/src/data/mock.js deleted file mode 100644 index d9caa10b..00000000 --- a/functions/src/data/mock.js +++ /dev/null @@ -1,162 +0,0 @@ -exports.mockData = [ - { - stations: "A", - personalID: "1111111111111", - firstName: "เทส", - lastName: "ทดสอบ", - age: 12, - gender: "male", - height: 178, - lastUpdatedAt: new Date(), - status: 3, - hasHelper: true, - address: "บ้าน", - district: "-", - prefecture: "-", - province: "-", - }, - { - stations: "A", - personalID: "1111111111111", - firstName: "เทส", - lastName: "ทดสอบ", - age: 12, - gender: "male", - height: 178, - lastUpdatedAt: new Date(), - status: 3, - hasHelper: true, - address: "บ้าน", - district: "-", - prefecture: "-", - province: "-", - }, - { - stations: "B", - personalID: "1111111111111", - firstName: "เทส", - lastName: "ทดสอบ", - age: 12, - gender: "male", - height: 178, - lastUpdatedAt: new Date(), - status: 3, - hasHelper: true, - address: "บ้าน", - district: "-", - prefecture: "-", - province: "-", - }, - { - stations: "B", - personalID: "1111111111111", - firstName: "เทส", - lastName: "ทดสอบ", - age: 12, - gender: "male", - height: 178, - lastUpdatedAt: new Date(), - status: 3, - hasHelper: true, - address: "บ้าน", - district: "-", - prefecture: "-", - province: "-", - }, - { - stations: "C", - personalID: "1111111111111", - firstName: "เทส", - lastName: "ทดสอบ", - age: 12, - gender: "male", - height: 178, - lastUpdatedAt: new Date(), - status: 3, - hasHelper: true, - address: "บ้าน", - district: "-", - prefecture: "-", - province: "-", - }, - { - stations: "C", - personalID: "1111111111111", - firstName: "เทส", - lastName: "ทดสอบ", - age: 12, - gender: "male", - height: 178, - lastUpdatedAt: new Date(), - status: 3, - hasHelper: true, - address: "บ้าน", - district: "-", - prefecture: "-", - province: "-", - }, - { - stations: "D", - personalID: "1111111111111", - firstName: "เทส", - lastName: "ทดสอบ", - age: 12, - gender: "male", - height: 178, - lastUpdatedAt: new Date(), - status: 3, - hasHelper: true, - address: "บ้าน", - district: "-", - prefecture: "-", - province: "-", - }, - { - stations: "D", - personalID: "1111111111111", - firstName: "เทส", - lastName: "ทดสอบ", - age: 12, - gender: "male", - height: 178, - lastUpdatedAt: new Date(), - status: 3, - hasHelper: true, - address: "บ้าน", - district: "-", - prefecture: "-", - province: "-", - }, - { - stations: "E", - personalID: "1111111111111", - firstName: "เทส", - lastName: "ทดสอบ", - age: 12, - gender: "male", - height: 178, - lastUpdatedAt: new Date(), - status: 3, - hasHelper: true, - address: "บ้าน", - district: "-", - prefecture: "-", - province: "-", - }, - { - stations: "E", - personalID: "1111111111111", - firstName: "เทส", - lastName: "ทดสอบ", - age: 12, - gender: "male", - height: 178, - lastUpdatedAt: new Date(), - status: 3, - hasHelper: true, - address: "บ้าน", - district: "-", - prefecture: "-", - province: "-", - }, -]; diff --git a/functions/src/handler/eventHandler.js b/functions/src/handler/eventHandler.ts similarity index 78% rename from functions/src/handler/eventHandler.js rename to functions/src/handler/eventHandler.ts index 84dc0672..ddcac9ae 100644 --- a/functions/src/handler/eventHandler.js +++ b/functions/src/handler/eventHandler.ts @@ -1,7 +1,7 @@ import { handleFollow } from "./subhandler/followHandler"; import { handleMessage } from "./subhandler/messageHandler"; -const eventHandler = async (event, userObject, client) => { +export const eventHandler = async (event: any, userObject: any, client: any) => { switch (await event.type) { case "follow": await handleFollow(event, userObject, client); @@ -13,4 +13,4 @@ const eventHandler = async (event, userObject, client) => { break; } }; -module.exports = { eventHandler }; + diff --git a/functions/src/handler/jsonHandler.js b/functions/src/handler/jsonHandler.ts similarity index 92% rename from functions/src/handler/jsonHandler.js rename to functions/src/handler/jsonHandler.ts index 72fc7438..a15e4fb0 100644 --- a/functions/src/handler/jsonHandler.js +++ b/functions/src/handler/jsonHandler.ts @@ -16,7 +16,7 @@ import * as guide from "../json/guide.json"; import * as r2cQuestion from "../json/r2cQuestion.json"; import * as closeRegistration from "../json/closeRegistration.json"; -import { config } from "../config/index"; +import config from "../config"; const tutorial2 = { type: "template", altText: "กรอกเบอร์โทรศัพท์", @@ -33,7 +33,7 @@ const tutorial2 = { }, }; -const map = { +const map: { [key: string]: any } = { greeting, welcomepos1, welcomepos2, @@ -53,4 +53,4 @@ const map = { closeRegistration, }; -module.exports = { jsonController: (json) => map[json] }; +export const jsonController = (json: string) => map[json] diff --git a/functions/src/handler/subhandler/defaultHandler.js b/functions/src/handler/subhandler/defaultHandler.ts similarity index 68% rename from functions/src/handler/subhandler/defaultHandler.js rename to functions/src/handler/subhandler/defaultHandler.ts index ae361a84..c608286d 100644 --- a/functions/src/handler/subhandler/defaultHandler.js +++ b/functions/src/handler/subhandler/defaultHandler.ts @@ -1,4 +1,4 @@ -const handleDefault = async (event, userObject, client) => { +export const handleDefault = async (event: any, userObject: any, client: any) => { const replyToken = event.replyToken; await client.replyMessage(replyToken, { type: "text", @@ -6,6 +6,3 @@ const handleDefault = async (event, userObject, client) => { }); }; -module.exports = { - handleDefault, -}; diff --git a/functions/src/handler/subhandler/followHandler.js b/functions/src/handler/subhandler/followHandler.ts similarity index 75% rename from functions/src/handler/subhandler/followHandler.js rename to functions/src/handler/subhandler/followHandler.ts index fef1ac59..cc592c9b 100644 --- a/functions/src/handler/subhandler/followHandler.js +++ b/functions/src/handler/subhandler/followHandler.ts @@ -1,9 +1,9 @@ const { jsonController } = require("../jsonHandler"); -const handleFollow = async (event, userObject, client) => { +export const handleFollow = async (event: any, userObject: any, client: any) => { const replyToken = await event.replyToken; try { - let greeting = jsonController("greeting"); + // let greeting = jsonController("greeting"); await client.replyMessage(replyToken, [ jsonController("welcomepos1"), jsonController("welcomepos2"), @@ -18,6 +18,4 @@ const handleFollow = async (event, userObject, client) => { } }; -module.exports = { - handleFollow, -}; + diff --git a/functions/src/handler/subhandler/messageHandler.js b/functions/src/handler/subhandler/messageHandler.ts similarity index 93% rename from functions/src/handler/subhandler/messageHandler.js rename to functions/src/handler/subhandler/messageHandler.ts index 67d64eae..fdd35be0 100644 --- a/functions/src/handler/subhandler/messageHandler.js +++ b/functions/src/handler/subhandler/messageHandler.ts @@ -2,7 +2,7 @@ const { jsonController } = require("../jsonHandler"); const { requestCall } = require("../../linefunctions/requestCallHandler"); const { requestGuide } = require("../../linefunctions/requestGuideHandler"); -const handleMessage = async (event, userObject, client) => { +export const handleMessage = async (event: any, userObject: any, client: any) => { const replyToken = await event.replyToken; const message = await event.message.text; // console.log(message) @@ -45,6 +45,4 @@ const handleMessage = async (event, userObject, client) => { } }; -module.exports = { - handleMessage, -}; + diff --git a/functions/src/index.ts b/functions/src/index.ts index 82603740..5267280d 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -1,27 +1,21 @@ // The Cloud Functions for Firebase SDK to create Cloud Functions and setup triggers. import * as functions from "firebase-functions"; -const { +import { authenticateVolunteer, - getProfile, + getProfile as getLineProfile, authenticateVolunteerRequest, -} = require("./middleware/authentication"); -const { admin, initializeApp } = require("./init"); -const { eventHandler } = require("./handler/eventHandler"); +} from "./middleware/authentication"; +import { admin, initializeApp } from "./init"; +import { eventHandler } from "./handler/eventHandler"; // const line = require("@line/bot-sdk"); import * as line from "@line/bot-sdk" -const config = { - channelAccessToken: functions.config().line.channel_token, - channelSecret: functions.config().line.channel_secret, -}; -const client = new line.Client(config); +import config from "./config" import { validateGetProfileSchema } from "./schema"; -const { success } = require("./response/success"); -const express = require("express"); -const cors = require("cors"); -const { backup } = require("./backup"); -const region = require("./config/index").config.region; - -const { +import { success } from "./response/success"; +import * as express from "express"; +import * as cors from "cors"; +import { backup } from "./backup"; +import { exportController, patientController, requestController, @@ -29,8 +23,14 @@ const { pubsub, firestoreController, dashboard -} = require("./controller"); +} from "./controller"; +import { Patient, Series } from "./types"; +const client = new line.Client({ + channelAccessToken: config.line.channelAccessToken, + channelSecret: config.line.channelSecret +}); +const region = config.region const app = express(); app.use(cors({ origin: true })); @@ -44,14 +44,18 @@ initializeApp(); // app.get("/patient", exportController.exportAllPatient); -app.get( - "/", - authenticateVolunteerRequest(exportController.exportPatientForNurse) -); +// app.get( +// "/", +// exportController.exportPatientForNurse +// ); + +export const exportNurse = functions + .region(region) + .https.onRequest(authenticateVolunteerRequest(exportController.exportPatientForNurse)) -exports.webhook = functions.region(region).https.onRequest(async (req, res) => { +export const webhook = functions.region(region).https.onRequest(async (req, res) => { res.sendStatus(200); try { const event = req.body.events[0]; @@ -67,74 +71,74 @@ exports.webhook = functions.region(region).https.onRequest(async (req, res) => { } }); -exports.deletePatient = functions +export const deletePatient = functions .region(region) .https.onCall(authenticateVolunteer(patientController.requestDeletePatient)); -exports.registerParticipant = functions +export const registerParticipant = functions .region(region) .https.onCall(patientController.registerPatient); -exports.getProfile = functions +export const getProfile = functions .region(region) - .https.onCall(patientController.getProfile); + .https.onCall(patientController.getProfileHandler); -exports.exportRequestToRegister = functions +export const exportRequestToRegister = functions .region(region) .https.onCall(authenticateVolunteer(exportController.exportR2R)); -exports.export36hrs = functions +export const export36hrs = functions .region(region) .https.onCall(authenticateVolunteer(exportController.export36hrs)); -exports.exportRequestToCall = functions +export const exportRequestToCall = functions .region(region) .https.onCall(authenticateVolunteer(exportController.exportR2C)); -exports.exportRequestToCallDayOne = functions +export const exportRequestToCallDayOne = functions .region(region) .https.onCall( authenticateVolunteer(exportController.exportRequestToCallDayOne) ); -exports.importFinishedRequestToCall = functions +export const importFinishedRequestToCall = functions .region(region) .https.onCall(authenticateVolunteer(importController.importFinishR2C)); -exports.importFinishedRequestToRegister = functions +export const importFinishedRequestToRegister = functions .region(region) .https.onCall(authenticateVolunteer(importController.importFinishR2R)); -exports.importWhitelist = functions +export const importWhitelist = functions .region(region) .https.onCall(authenticateVolunteer(importController.importWhitelist)); -exports.thisEndpointNeedsAuth = functions.region(region).https.onCall( +export const thisEndpointNeedsAuth = functions.region(region).https.onCall( authenticateVolunteer(async (data: any, context: functions.https.CallableContext) => { return { result: `Content for authorized user` }; }) ); -exports.accumulativeData = functions +export const accumulativeData = functions .region(region) .https.onCall(authenticateVolunteer(dashboard.getAccumulative)); -exports.backupFirestore = functions +export const backupFirestore = functions .region(region) .pubsub.schedule("every day 18:00") .timeZone("Asia/Bangkok") .onRun(backup); -exports.updateTimeSeries = functions +export const updateTimeSeries = functions .region(region) .pubsub.schedule("every day 23:59") .timeZone("Asia/Bangkok") .onRun(pubsub.updateTimeSeries); -exports.initializeLegacyStat = functions +export const initializeLegacyStat = functions .region(region) .pubsub.schedule("every day 00:00") .timeZone("Asia/Bangkok") .onRun(pubsub.initializeLegacyStat); -exports.getNumberOfPatients = functions +export const getNumberOfPatients = functions .region(region) .https.onRequest(async (req, res) => { const snapshot = await admin.firestore().collection("patient").get(); @@ -142,52 +146,53 @@ exports.getNumberOfPatients = functions res.status(200).json(success(snapshot.size)); }); -exports.getNumberOfPatientsV2 = functions +export const getNumberOfPatientsV2 = functions .region(region) .https.onRequest(async (req, res) => { const snapshot = await admin .firestore() .collection("userCount") - .document("users") + .doc("users") .get(); - res.status(200).json(success(snapshot[0].data().count)); + const data = snapshot.data() as Series + res.status(200).json(success(data.count)); }); -exports.requestToRegister = functions +export const requestToRegister = functions .region(region) .https.onCall(requestController.requestToRegister); -exports.check = functions.region(region).https.onRequest(async (req, res) => { +export const check = functions.region(region).https.onRequest(async (req, res) => { res.sendStatus(200); }); -exports.requestToCall = functions +export const requestToCall = functions .region(region) .https.onCall(requestController.requestToCall); -exports.updateSymptom = functions +export const updateSymptom = functions .region(region) .https.onCall(patientController.updateSymptom); -exports.createReport = functions.region(region).https.onRequest(app); +export const createReport = functions.region(region).https.onRequest(app); -exports.onRegisterPatient = functions +export const onRegisterPatient = functions .region(region) .firestore.document("patient/{id}") .onCreate(firestoreController.onRegisterPatient) -exports.onUpdateSymptom = functions +export const onUpdateSymptom = functions .region(region) .firestore.document("patient/{id}") .onUpdate(firestoreController.onUpdatePatient) -exports.onDeletePatient = functions +export const onDeletePatient = functions .region(region) .firestore.document("patient/{id}") .onDelete(firestoreController.onDeletePatient) // ******************************* unused ****************************************** -exports.getFollowupHistory = functions +export const getFollowupHistory = functions .region(region) .https.onCall(async (data, context) => { const { value, error } = validateGetProfileSchema(data); @@ -200,7 +205,7 @@ exports.getFollowupHistory = functions ); } const { lineUserID, lineIDToken, noAuth } = value; - const { data: errorData, error: authError } = await getProfile({ + const { data: errorData, error: authError } = await getLineProfile({ lineUserID, lineIDToken, noAuth, @@ -225,10 +230,11 @@ exports.getFollowupHistory = functions `ไม่พบข้อมูลผู้ใช้ ${lineUserID}` ); } - return success(snapshot.data().followUp); + const patient = snapshot.data() as Patient + return success(patient.followUp); }); -exports.fetchYellowPatients = functions +export const fetchYellowPatients = functions .region(region) .https.onCall(async (data) => { // const snapshot = await admin @@ -247,7 +253,7 @@ exports.fetchYellowPatients = functions return success(); }); -exports.fetchGreenPatients = functions +export const fetchGreenPatients = functions .region(region) .https.onCall(async (data) => { // const snapshot = await admin @@ -265,7 +271,7 @@ exports.fetchGreenPatients = functions // return success(patientList); return success(); }); -exports.fetchRedPatients = functions +export const fetchRedPatients = functions .region(region) .https.onCall(async (data) => { // const snapshot = await admin @@ -282,68 +288,3 @@ exports.fetchRedPatients = functions // return success(patientList); return success(); }); - -// exports.testExportRequestToCall = functions.region(region).https.onRequest( -// authenticateVolunteerRequest(async (req, res) => { -// const { value, error } = exportRequestToCallSchema.validate(req.body); -// if (error) { -// console.log(error.details); -// return res.status(412).json(error.details); -// } -// const { volunteerSize } = value; -// var limit = 250; -// var lastVisible = 0; -// var i = 0; -// var patientList = []; -// while (true){ -// console.log("250 round:",i); -// const snapshot = await admin -// .firestore() -// .collection("patient") -// .orderBy("lastUpdatedAt") -// .startAfter(lastVisible).limit(limit) -// .get(); -// if(i>3){ -// break; -// } -// lastVisible += snapshot.size-1; -// console.log(lastVisible); -// i++; -// const batch = admin.firestore().batch(); -// snapshot.docs.forEach((doc) => { -// // console.log(doc.id, "id"); -// const docRef = admin.firestore().collection("patient").doc(doc.id); -// batch.update(docRef, { -// isRequestToCall:true, -// isRequestToCallExported: false, -// }); -// }); - -// snapshot.forEach((doc) => { -// const data = doc.data(); -// const dataResult = { -// firstName: data.firstName, -// lastName: data.firstName, -// hasCalled: 0, -// id: doc.id, -// personalPhoneNo: data.personalPhoneNo, -// }; -// patientList.push(dataResult); -// }); - -// snapshot.docs.forEach((doc) => { -// const docRef = admin.firestore().collection("patient").doc(doc.id); -// batch.update(docRef, { -// isRequestToCall:true, -// isRequestToCallExported: false, -// }); -// }); -// //console.log(batch, 'batch') -// await batch.commit(); -// } -// console.log("patientlist is:",patientList.length); -// //generateZipFile(res, size, patientList); -// generateZipFileRoundRobin(res, volunteerSize, patientList); - -// }) -// ); diff --git a/functions/src/linefunctions/linepushmessage.js b/functions/src/linefunctions/linepushmessage.ts similarity index 77% rename from functions/src/linefunctions/linepushmessage.js rename to functions/src/linefunctions/linepushmessage.ts index 629f9275..d5e7eea0 100644 --- a/functions/src/linefunctions/linepushmessage.js +++ b/functions/src/linefunctions/linepushmessage.ts @@ -1,7 +1,11 @@ -import { convertTimestampToStr } from "../utils/date"; +import _ = require("lodash"); +import { convertTimestampToStr } from "../utils"; import axios from "axios"; -const baseURL = "https://api.line.me/v2/bot/message/push"; import { statusList } from "../api/const"; +import { UpdatedPatient } from "../types"; +const baseURL = "https://api.line.me/v2/bot/message/push"; + +type StatusObj = Omit const symptomMapper = { sym1_severe_cough: "มีอาการไอต่อเนื่อง", @@ -28,17 +32,17 @@ const conditionMapper = { fac_gi_symptoms: "แน่นหน้าอก", }; -const getPatientCondition = (statusObj, mapper) => { +const getPatientCondition = (statusObj: StatusObj, mapper: any) => { const conditions = []; - for (let key in statusObj) { - if (statusObj[key] === 1 && key in mapper) { + for (const [key, value] of _.entries(statusObj)) { + if (value === 1 && key in mapper) { conditions.push(mapper[key]); } } return conditions.join(", "); }; -exports.statusMap = { +export const statusMap = { G1: "เขียวอ่อน", G2: "เขียวเข้ม", Y1: "เหลืองอ่อน", @@ -48,7 +52,7 @@ exports.statusMap = { unknown: "ไม่สามารถระบุได้", }; -const statusNumberMap = { +const statusNumberMap: { [key: number]: string } = { 1: "เขียวอ่อน", 2: "เขียวเข้ม", 3: "เหลืองอ่อน", @@ -58,29 +62,26 @@ const statusNumberMap = { 0: "ไม่สามารถระบุได้", }; -const getPatientTextColor = (statusNumber) => { +const getPatientTextColor = (statusNumber: number) => { return statusNumberMap[statusNumber]; }; -const sendPatientstatus = async (userId, statusObj, channelAccessToken) => { +export const sendPatientstatus = async (userId: string, statusObj: StatusObj, channelAccessToken: string) => { const date = convertTimestampToStr({ dateObj: statusObj.lastUpdatedAt }); - let message = `วันที่: ${date.dateObj} + const message = `วันที่: ${date.dateObj} \nข้อมูลทั่วไป: - ค่าออกซิเจนปลายนิ้ว: ${statusObj.sp_o2 || "-"} - ค่าออกซิเจนปลายนิ้ว ขณะหายใจปกติ: ${statusObj.sp_o2_ra || "-"} - - ค่าออกซิเจนปลายนิ้ว หลังลุกนั่ง 1 นาที: ${ - statusObj.sp_o2_after_eih || "-" + - ค่าออกซิเจนปลายนิ้ว หลังลุกนั่ง 1 นาที: ${statusObj.sp_o2_after_eih || "-" }`; const patientCondition = getPatientCondition(statusObj, conditionMapper); const patientsymptom = getPatientCondition(statusObj, symptomMapper); - let symptom = `\n\nอาการที่พบ: ${ - patientsymptom === "" ? "-" : patientsymptom - }`; - let condition = `\n\nอัปเดตโรคประจำตัว: ${ - patientCondition === "" ? "-" : patientCondition - }`; + const symptom = `\n\nอาการที่พบ: ${patientsymptom === "" ? "-" : patientsymptom + }`; + const condition = `\n\nอัปเดตโรคประจำตัว: ${patientCondition === "" ? "-" : patientCondition + }`; const patientColor = getPatientTextColor(statusObj.status); - let conclude = `\n\nผลลัพธ์: + const conclude = `\n\nผลลัพธ์: - ระดับ: ${patientColor}`; const messagePayload = [ { @@ -113,19 +114,29 @@ const sendPatientstatus = async (userId, statusObj, channelAccessToken) => { default: resultMessagePayload = messagePayload.slice(0, 1); } - const axiosConfig = { - method: "POST", + // const axiosConfig = { + // method: "POST", + // headers: { + // "Content-Type": "application/json", + // Authorization: "Bearer " + channelAccessToken, + // }, + // data: { + // to: userId, + // messages: resultMessagePayload, + // }, + // baseURL, + // }; + + const data = { + to: userId, + messages: resultMessagePayload, + } + // await axios(axiosConfig); + await axios.post(baseURL, data, { headers: { "Content-Type": "application/json", Authorization: "Bearer " + channelAccessToken, - }, - data: { - to: userId, - messages: resultMessagePayload, - }, - baseURL, - }; - await axios(axiosConfig); + } + }) }; -module.exports = { sendPatientstatus }; diff --git a/functions/src/linefunctions/requestCallHandler.js b/functions/src/linefunctions/requestCallHandler.ts similarity index 79% rename from functions/src/linefunctions/requestCallHandler.js rename to functions/src/linefunctions/requestCallHandler.ts index 2d84da11..8fb05c8f 100644 --- a/functions/src/linefunctions/requestCallHandler.js +++ b/functions/src/linefunctions/requestCallHandler.ts @@ -1,8 +1,9 @@ import { admin } from "../init"; import { jsonController } from "../handler/jsonHandler"; import { success } from "../response/success"; +import { Patient } from "../types"; -const requestCall = async (userObject, client, replyToken) => { +export const requestCall = async (userObject: any, client: any, replyToken: any) => { const snapshot = await admin .firestore() .collection("patient") @@ -16,7 +17,7 @@ const requestCall = async (userObject, client, replyToken) => { await client.replyMessage(replyToken, jsonController("tutorial1")); - const { isRequestToCall } = snapshot.data(); + const { isRequestToCall } = snapshot.data() as Patient; if (isRequestToCall) { return success( @@ -29,4 +30,4 @@ const requestCall = async (userObject, client, replyToken) => { }); return success(); }; -module.exports = { requestCall }; + diff --git a/functions/src/linefunctions/requestGuideHandler.js b/functions/src/linefunctions/requestGuideHandler.ts similarity index 91% rename from functions/src/linefunctions/requestGuideHandler.js rename to functions/src/linefunctions/requestGuideHandler.ts index 1f29e506..27af2a79 100644 --- a/functions/src/linefunctions/requestGuideHandler.js +++ b/functions/src/linefunctions/requestGuideHandler.ts @@ -1,7 +1,7 @@ import { jsonController } from "../handler/jsonHandler"; import { success } from "../response/success"; -const requestGuide = async (userObject, client, replyToken) => { +export const requestGuide = async (userObject: any, client: any, replyToken: any) => { // const snapshot = await admin // .firestore() // .collection("patient") @@ -37,4 +37,3 @@ const requestGuide = async (userObject, client, replyToken) => { // }); return success(); }; -module.exports = { requestGuide }; diff --git a/functions/src/linenotify/index.js b/functions/src/linenotify/index.ts similarity index 87% rename from functions/src/linenotify/index.js rename to functions/src/linenotify/index.ts index 645e557c..b7f3eda8 100644 --- a/functions/src/linenotify/index.js +++ b/functions/src/linenotify/index.ts @@ -2,12 +2,12 @@ import axios from "axios"; import { success } from "../response/success"; import * as functions from "firebase-functions"; -exports.notifyToLine = async (message) => { +export const notifyToLine = async (message: string) => { try { const token = functions.config().linenotify.token; const params = new URLSearchParams(); params.append("message", message); - const response = await axios.post( + await axios.post( "https://notify-api.line.me/api/notify", params, { diff --git a/functions/src/middleware/authentication.js b/functions/src/middleware/authentication.ts similarity index 83% rename from functions/src/middleware/authentication.js rename to functions/src/middleware/authentication.ts index 6d734d7b..eaeed737 100644 --- a/functions/src/middleware/authentication.js +++ b/functions/src/middleware/authentication.ts @@ -2,13 +2,14 @@ import { admin } from "../init"; import axios from "axios"; import * as functions from "firebase-functions"; +import { LineCredential, OnCallHandler, OnRequestHandler } from "../types"; /** * Authenticate middleware for volunteer system * @param {*} func function to call if authenticate success * @returns error 401 if not authorized email */ -exports.authenticateVolunteer = (func) => { +export const authenticateVolunteer = (func: OnCallHandler): OnCallHandler => { return async (data, context) => { if ( data.noAuth && @@ -40,7 +41,7 @@ exports.authenticateVolunteer = (func) => { * @param {*} func function to call if authenticate success * @returns error 401 if not authorized email */ -exports.authenticateVolunteerRequest = (func) => { +export const authenticateVolunteerRequest = (func: OnRequestHandler): OnRequestHandler => { return async (req, res) => { try { if ( @@ -48,11 +49,10 @@ exports.authenticateVolunteerRequest = (func) => { functions.config().environment && functions.config().environment.isdevelopment ) { - console.log("in if"); - return await func(req, res); + await func(req, res); } - const tokenId = req.get("Authorization").split("Bearer ")[1]; + const tokenId = req.get("Authorization")!.split("Bearer ")[1]; const decoded = await admin.auth().verifyIdToken(tokenId); const email = decoded.email || null; const userInfo = await admin @@ -62,19 +62,20 @@ exports.authenticateVolunteerRequest = (func) => { .get(); if (userInfo.empty) { - return res + res .status(401) .json({ status: "error", message: "Not authorized" }); + return; } try { - return await func(req, res); + await func(req, res); } catch (e) { console.log(e); - return res.status(500).json({ status: "error", message: "Unknown" }); + res.status(500).json({ status: "error", message: "Unknown" }); } } catch (e) { console.log(e); - return res + res .status(401) .json({ status: "error", message: "Not signed in" }); } @@ -86,7 +87,7 @@ exports.authenticateVolunteerRequest = (func) => { * @param {*} data * @returns */ -exports.getProfile = async (data) => { +export const getProfile = async (data: LineCredential) => { if ( data.noAuth && functions.config().environment && diff --git a/functions/src/types/handler.ts b/functions/src/types/handler.ts index 9271de15..a23391cc 100644 --- a/functions/src/types/handler.ts +++ b/functions/src/types/handler.ts @@ -1,6 +1,8 @@ -import express = require("express"); +import * as express from "express"; import { EventContext, https } from "firebase-functions"; -export type OnCallHandler = (data: T, context: https.CallableContext) => any | Promise + +export type OnCallHandler = (data: T, context: https.CallableContext) => any | Promise export type OnRequestHandler = (req: https.Request, resp: express.Response) => void | Promise -export type OnRunHandler = (context: EventContext) => PromiseLike | any \ No newline at end of file +export type OnRunHandler = (context: EventContext) => PromiseLike | any +export type ExpressHandler = (req: express.Request, res: express.Response) => void | Promise \ No newline at end of file diff --git a/functions/src/types/index.ts b/functions/src/types/index.ts index 96f53a17..e6b8caa3 100644 --- a/functions/src/types/index.ts +++ b/functions/src/types/index.ts @@ -2,4 +2,5 @@ export * from "./patient" export * from "./trigger" export * from "./handler"; export * from "./r2r"; -export * from "./dashboard"; \ No newline at end of file +export * from "./dashboard"; +export * from "./line"; \ No newline at end of file diff --git a/functions/src/types/line.ts b/functions/src/types/line.ts new file mode 100644 index 00000000..ca1727e3 --- /dev/null +++ b/functions/src/types/line.ts @@ -0,0 +1,5 @@ +export type LineCredential = { + lineIDToken: string + lineUserID: string + noAuth?: boolean +} \ No newline at end of file diff --git a/functions/src/utils/date.ts b/functions/src/utils/date.ts index 9eb042ff..6fdcd22c 100644 --- a/functions/src/utils/date.ts +++ b/functions/src/utils/date.ts @@ -1,7 +1,6 @@ import * as moment from "moment"; import * as _ from "lodash"; import { admin } from "../init"; -import { Patient } from "../types"; enum TZ { AsiaBangkok = "Asia/Bangkok" @@ -16,7 +15,7 @@ export const convertTZ = (date: Date, tzString: TZ = TZ.AsiaBangkok) => { ); }; -export const convertTimestampToStr = (data: Omit) => { +export const convertTimestampToStr = (data: any) => { const tmp: { [key: string]: any } = {}; for (const [key, value] of _.entries(data)) { if (value instanceof admin.firestore.Timestamp) { diff --git a/functions/yarn.lock b/functions/yarn.lock index 73b27b33..29f17ab6 100644 --- a/functions/yarn.lock +++ b/functions/yarn.lock @@ -1565,7 +1565,7 @@ dependencies: "@types/node" "*" -"@types/cors@^2.8.12": +"@types/cors@^2.8.12", "@types/cors@^2.8.5": version "2.8.12" resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080" integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw== @@ -4656,20 +4656,21 @@ firebase-admin@^8.13.0: "@google-cloud/firestore" "^3.0.0" "@google-cloud/storage" "^4.1.2" -firebase-functions@^3.14.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/firebase-functions/-/firebase-functions-3.14.1.tgz#3ac5bc70989365874f41d06bca3b42a233dd6039" - integrity sha512-hL/qm+i5i1qKYmAFMlQ4mwRngDkP+3YT3F4E4Nd5Hj2QKeawBdZiMGgEt6zqTx08Zq04vHiSnSM0z75UJRSg6Q== +firebase-functions@^3.15.4: + version "3.15.4" + resolved "https://registry.yarnpkg.com/firebase-functions/-/firebase-functions-3.15.4.tgz#c9309a565abb1f57a1e50d41b3e54bcee6a3ee51" + integrity sha512-6Zq+QIqdslZLsSwWg25Hv39cgFBZr0oUAbCjfe/MXqSHMy8ZK/1Vdy/WKx5IRC6hE7+JrmfVylIyEonTyNcheA== dependencies: + "@types/cors" "^2.8.5" "@types/express" "4.17.3" cors "^2.8.5" express "^4.17.1" lodash "^4.17.14" -firebase-tools@^9.16.0: - version "9.16.0" - resolved "https://registry.yarnpkg.com/firebase-tools/-/firebase-tools-9.16.0.tgz#e6a1f5bf5efeb8fd940612815bb3b28810fe63bc" - integrity sha512-H/zyDDrQuZKM6ZFyI8t2kDEC+/Ewhk771sM8NLZyEXIQnX5qKAwhi3sJUB+5yrXt+SJQYqUYksBLK6/gqxe9Eg== +firebase-tools@^9.16.5: + version "9.16.5" + resolved "https://registry.yarnpkg.com/firebase-tools/-/firebase-tools-9.16.5.tgz#c6d38bded228fd2a5dfd781a42287c78dab66b86" + integrity sha512-dp/cvt+39wv5CO+MzX36snmRnvn5j7Nn73QfKiIvHXAT5Ek/fRJn2pWnaxP+bhd19SuEY1Buf8PcdlMl42hzlw== dependencies: "@google-cloud/pubsub" "^2.7.0" "@types/archiver" "^5.1.0" From 39d95a3b9499dc5a0edf9d88c9ebec166e738e72 Mon Sep 17 00:00:00 2001 From: palsp Date: Mon, 23 Aug 2021 14:43:38 +0700 Subject: [PATCH 15/34] refactor: index --- .../src/controller/patientController/index.ts | 52 +++++++++- functions/src/index.ts | 98 +------------------ 2 files changed, 52 insertions(+), 98 deletions(-) diff --git a/functions/src/controller/patientController/index.ts b/functions/src/controller/patientController/index.ts index 10bcc9bb..6a695d30 100644 --- a/functions/src/controller/patientController/index.ts +++ b/functions/src/controller/patientController/index.ts @@ -21,10 +21,10 @@ import { Patient, OnCallHandler } from "../../types"; -const { getProfile } = require("../../middleware/authentication"); -const { makeStatusAPIPayload, makeRequest } = require("../../api"); -const { sendPatientstatus } = require("../../linefunctions/linepushmessage"); -const { notifyToLine } = require("../../linenotify"); +import { getProfile } from "../../middleware/authentication"; +import { makeStatusAPIPayload, makeRequest } from "../../api"; +import { sendPatientstatus } from "../../linefunctions/linepushmessage"; +import { notifyToLine } from "../../linenotify"; // Mon added this code const deletePatient = async (personalID: string) => { @@ -281,3 +281,47 @@ export const updateSymptom: OnCallHandler = async (data, _context) return success({ status: inclusion_label }); }; + +export const getFollowupHistory: OnCallHandler = async (data, context) => { + const { value, error } = validateGetProfileSchema(data); + if (error) { + console.log(error.details); + throw new functions.https.HttpsError( + "invalid-argument", + "ข้อมูลไม่ถูกต้อง", + error.details + ); + } + + const { lineUserID, lineIDToken, noAuth } = value; + const { data: errorData, error: authError } = await getProfile({ + lineUserID, + lineIDToken, + noAuth, + }); + + if (authError) { + throw new functions.https.HttpsError( + "unauthenticated", + errorData.error_description + ); + } + + const snapshot = await admin + .firestore() + .collection("patient") + .doc(lineUserID) + .get(); + + if (!snapshot.exists) { + throw new functions.https.HttpsError( + "not-found", + `ไม่พบข้อมูลผู้ใช้ ${lineUserID}` + ); + } + + const patient = snapshot.data() as Patient + return success(patient.followUp); + + +} diff --git a/functions/src/index.ts b/functions/src/index.ts index 5267280d..6925814f 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -2,7 +2,6 @@ import * as functions from "firebase-functions"; import { authenticateVolunteer, - getProfile as getLineProfile, authenticateVolunteerRequest, } from "./middleware/authentication"; import { admin, initializeApp } from "./init"; @@ -10,7 +9,6 @@ import { eventHandler } from "./handler/eventHandler"; // const line = require("@line/bot-sdk"); import * as line from "@line/bot-sdk" import config from "./config" -import { validateGetProfileSchema } from "./schema"; import { success } from "./response/success"; import * as express from "express"; import * as cors from "cors"; @@ -24,7 +22,7 @@ import { firestoreController, dashboard } from "./controller"; -import { Patient, Series } from "./types"; +import { Series } from "./types"; const client = new line.Client({ channelAccessToken: config.line.channelAccessToken, channelSecret: config.line.channelSecret @@ -194,97 +192,9 @@ export const onDeletePatient = functions // ******************************* unused ****************************************** export const getFollowupHistory = functions .region(region) - .https.onCall(async (data, context) => { - const { value, error } = validateGetProfileSchema(data); - if (error) { - console.log(error.details); - throw new functions.https.HttpsError( - "invalid-argument", - "ข้อมูลไม่ถูกต้อง", - error.details - ); - } - const { lineUserID, lineIDToken, noAuth } = value; - const { data: errorData, error: authError } = await getLineProfile({ - lineUserID, - lineIDToken, - noAuth, - }); - if (authError) { - throw new functions.https.HttpsError( - "unauthenticated", - errorData.error_description - ); - } - - // const snapshot = await admin.firestore().collection('followup').where("personalId","==","1").get() - const snapshot = await admin - .firestore() - .collection("patient") - .doc(lineUserID) - .get(); + .https.onCall(patientController.getFollowupHistory); + + - if (!snapshot.exists) { - throw new functions.https.HttpsError( - "not-found", - `ไม่พบข้อมูลผู้ใช้ ${lineUserID}` - ); - } - const patient = snapshot.data() as Patient - return success(patient.followUp); - }); -export const fetchYellowPatients = functions - .region(region) - .https.onCall(async (data) => { - // const snapshot = await admin - // .firestore() - // .collection("patient") - // .where("status", "==", data.status) - // .get(); - - // var patientList = []; - - // snapshot.forEach((doc) => { - // const data = doc.data(); - // patientList.push(data); - // }); - // return success(patientList); - return success(); - }); -export const fetchGreenPatients = functions - .region(region) - .https.onCall(async (data) => { - // const snapshot = await admin - // .firestore() - // .collection("patient") - // .where("status", "==", "เขียว") - // .get(); - - // var patientList = []; - - // snapshot.forEach((doc) => { - // const data = doc.data(); - // patientList.push(data); - // }); - // return success(patientList); - return success(); - }); -export const fetchRedPatients = functions - .region(region) - .https.onCall(async (data) => { - // const snapshot = await admin - // .firestore() - // .collection("patient") - // .where("status", "==", "แดง") - // .get(); - - // var patientList = []; - // snapshot.forEach((doc) => { - // const data = doc.data(); - // patientList.push(data); - // }); - // return success(patientList); - return success(); - }); From d0d8a1c1c858339d9a937ee357981b05ac4beddc Mon Sep 17 00:00:00 2001 From: palsp Date: Mon, 23 Aug 2021 14:55:56 +0700 Subject: [PATCH 16/34] test: add mock config --- functions/src/api/index.ts | 2 +- functions/src/controller/exportController/index.ts | 2 +- functions/src/handler/subhandler/followHandler.ts | 2 +- functions/src/handler/subhandler/messageHandler.ts | 6 +++--- functions/src/index.test.ts | 8 ++++---- functions/src/linefunctions/linepushmessage.ts | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/functions/src/api/index.ts b/functions/src/api/index.ts index c9e7f36e..78b893bf 100644 --- a/functions/src/api/index.ts +++ b/functions/src/api/index.ts @@ -1,7 +1,7 @@ import axios from "axios"; import { calculateAge, formatDateTimeAPI } from "../utils"; import * as functions from "firebase-functions"; -import _ = require("lodash"); +import * as _ from "lodash"; import { FollowUp, Patient } from "../types"; import { statusList } from "./const"; const URL = "https://pedsanam.ydm.family/pedsanam/label_score"; diff --git a/functions/src/controller/exportController/index.ts b/functions/src/controller/exportController/index.ts index e065d09e..03d85281 100644 --- a/functions/src/controller/exportController/index.ts +++ b/functions/src/controller/exportController/index.ts @@ -18,7 +18,7 @@ import { import { QuerySnapshot } from "@google-cloud/firestore"; -const { generateZipFileRoundRobin } = require("../../utils/zip"); +import { generateZipFileRoundRobin } from "../../utils/zip"; export const exportR2R: OnCallHandler = async (data, _context) => { diff --git a/functions/src/handler/subhandler/followHandler.ts b/functions/src/handler/subhandler/followHandler.ts index cc592c9b..d086f318 100644 --- a/functions/src/handler/subhandler/followHandler.ts +++ b/functions/src/handler/subhandler/followHandler.ts @@ -1,4 +1,4 @@ -const { jsonController } = require("../jsonHandler"); +import { jsonController } from "../jsonHandler"; export const handleFollow = async (event: any, userObject: any, client: any) => { const replyToken = await event.replyToken; diff --git a/functions/src/handler/subhandler/messageHandler.ts b/functions/src/handler/subhandler/messageHandler.ts index fdd35be0..eb2f5c07 100644 --- a/functions/src/handler/subhandler/messageHandler.ts +++ b/functions/src/handler/subhandler/messageHandler.ts @@ -1,6 +1,6 @@ -const { jsonController } = require("../jsonHandler"); -const { requestCall } = require("../../linefunctions/requestCallHandler"); -const { requestGuide } = require("../../linefunctions/requestGuideHandler"); +import { jsonController } from "../jsonHandler"; +import { requestCall } from "../../linefunctions/requestCallHandler"; +import { requestGuide } from "../../linefunctions/requestGuideHandler"; export const handleMessage = async (event: any, userObject: any, client: any) => { const replyToken = await event.replyToken; diff --git a/functions/src/index.test.ts b/functions/src/index.test.ts index 7ed981fb..fe982e5d 100644 --- a/functions/src/index.test.ts +++ b/functions/src/index.test.ts @@ -1,11 +1,11 @@ describe("endpoints", () => { jest.doMock("./config/index", () => ({ - config: { + default: { region: "region", line: { - channel_token: "channel_token", - channel_secret: "channel_secret", - r2r_uri: "r2r_uri", + channelAccessToken: "channel_token", + channelSecret: "channel_secret", + r2rUri: "r2r_uri", }, }, })); diff --git a/functions/src/linefunctions/linepushmessage.ts b/functions/src/linefunctions/linepushmessage.ts index d5e7eea0..55783051 100644 --- a/functions/src/linefunctions/linepushmessage.ts +++ b/functions/src/linefunctions/linepushmessage.ts @@ -1,4 +1,4 @@ -import _ = require("lodash"); +import * as _ from "lodash"; import { convertTimestampToStr } from "../utils"; import axios from "axios"; import { statusList } from "../api/const"; From 4ed81ef6902fd32afcf7535f038776a423b24335 Mon Sep 17 00:00:00 2001 From: palsp Date: Mon, 23 Aug 2021 15:42:22 +0700 Subject: [PATCH 17/34] chore: add line types --- functions/src/handler/eventHandler.ts | 11 ++++++++--- functions/src/handler/subhandler/defaultHandler.ts | 5 ++++- functions/src/handler/subhandler/followHandler.ts | 4 +++- functions/src/handler/subhandler/messageHandler.ts | 7 +++++-- functions/src/index.ts | 14 ++++++++++---- functions/src/types/line.ts | 13 ++++++++++++- 6 files changed, 42 insertions(+), 12 deletions(-) diff --git a/functions/src/handler/eventHandler.ts b/functions/src/handler/eventHandler.ts index ddcac9ae..43028c3e 100644 --- a/functions/src/handler/eventHandler.ts +++ b/functions/src/handler/eventHandler.ts @@ -1,13 +1,18 @@ +import { FollowEvent, MessageEvent } from "@line/bot-sdk"; +import { LineHandler } from "../types"; import { handleFollow } from "./subhandler/followHandler"; import { handleMessage } from "./subhandler/messageHandler"; -export const eventHandler = async (event: any, userObject: any, client: any) => { + +export const eventHandler: LineHandler = async (event, userObject, client) => { switch (await event.type) { case "follow": - await handleFollow(event, userObject, client); + const followEvent = event as FollowEvent + await handleFollow(followEvent, userObject, client); break; case "message": - await handleMessage(event, userObject, client); + const messageEvent = event as MessageEvent + await handleMessage(messageEvent, userObject, client); break; default: break; diff --git a/functions/src/handler/subhandler/defaultHandler.ts b/functions/src/handler/subhandler/defaultHandler.ts index c608286d..373c350d 100644 --- a/functions/src/handler/subhandler/defaultHandler.ts +++ b/functions/src/handler/subhandler/defaultHandler.ts @@ -1,4 +1,7 @@ -export const handleDefault = async (event: any, userObject: any, client: any) => { +import { ReplyableEvent } from "@line/bot-sdk"; +import { LineHandler } from "../../types"; + +export const handleDefault: LineHandler = async (event, userObject, client) => { const replyToken = event.replyToken; await client.replyMessage(replyToken, { type: "text", diff --git a/functions/src/handler/subhandler/followHandler.ts b/functions/src/handler/subhandler/followHandler.ts index d086f318..2bf09574 100644 --- a/functions/src/handler/subhandler/followHandler.ts +++ b/functions/src/handler/subhandler/followHandler.ts @@ -1,6 +1,8 @@ +import { LineHandler } from "../../types"; import { jsonController } from "../jsonHandler"; +import { FollowEvent } from "@line/bot-sdk" -export const handleFollow = async (event: any, userObject: any, client: any) => { +export const handleFollow: LineHandler = async (event, userObject, client) => { const replyToken = await event.replyToken; try { // let greeting = jsonController("greeting"); diff --git a/functions/src/handler/subhandler/messageHandler.ts b/functions/src/handler/subhandler/messageHandler.ts index eb2f5c07..9c5518a3 100644 --- a/functions/src/handler/subhandler/messageHandler.ts +++ b/functions/src/handler/subhandler/messageHandler.ts @@ -1,10 +1,13 @@ import { jsonController } from "../jsonHandler"; import { requestCall } from "../../linefunctions/requestCallHandler"; import { requestGuide } from "../../linefunctions/requestGuideHandler"; +import { LineHandler } from "../../types"; +import { MessageEvent, TextEventMessage } from "@line/bot-sdk" -export const handleMessage = async (event: any, userObject: any, client: any) => { + +export const handleMessage: LineHandler = async (event, userObject, client) => { const replyToken = await event.replyToken; - const message = await event.message.text; + const message = await (event.message as TextEventMessage).text; // console.log(message) try { switch (message) { diff --git a/functions/src/index.ts b/functions/src/index.ts index 6925814f..91b2ec02 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -22,7 +22,7 @@ import { firestoreController, dashboard } from "./controller"; -import { Series } from "./types"; +import { Series, UserObject } from "./types"; const client = new line.Client({ channelAccessToken: config.line.channelAccessToken, channelSecret: config.line.channelSecret @@ -32,6 +32,7 @@ const region = config.region const app = express(); app.use(cors({ origin: true })); + // The Firebase Admin SDK to access Firestore. initializeApp(); @@ -56,10 +57,15 @@ export const exportNurse = functions export const webhook = functions.region(region).https.onRequest(async (req, res) => { res.sendStatus(200); try { - const event = req.body.events[0]; + const { events } = (req.body as line.WebhookRequestBody) + const event = events[0] const userId = event.source.userId; - const profile: any = await client.getProfile(userId); - const userObject = { userId: userId, profile: profile }; + if (!userId) { + throw new Error('userId not found') + } + + const profile: line.Profile = await client.getProfile(userId); + const userObject: UserObject = { userId: userId, profile: profile }; console.log(userObject); // console.log(event) await eventHandler(event, userObject, client); diff --git a/functions/src/types/line.ts b/functions/src/types/line.ts index ca1727e3..b7e78a78 100644 --- a/functions/src/types/line.ts +++ b/functions/src/types/line.ts @@ -1,5 +1,16 @@ +import { Client, Profile, WebhookEvent } from "@line/bot-sdk"; + export type LineCredential = { lineIDToken: string lineUserID: string noAuth?: boolean -} \ No newline at end of file +} + +export type UserObject = { + userId: string + profile: Profile + +} + +export type LineHandler = (event: T, userObject: UserObject, client: Client) => any | Promise + From 6715ba1c227aa83b703cd764352a3bd963a2b7fb Mon Sep 17 00:00:00 2001 From: palsp Date: Mon, 23 Aug 2021 15:51:52 +0700 Subject: [PATCH 18/34] test: ignore ts comment for mock snapshot --- functions/src/controller/patientController/utils.test.ts | 6 ++++++ functions/src/controller/patientController/utils.ts | 5 +++-- functions/src/handler/eventHandler.ts | 6 ++---- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/functions/src/controller/patientController/utils.test.ts b/functions/src/controller/patientController/utils.test.ts index 01b54386..65718176 100644 --- a/functions/src/controller/patientController/utils.test.ts +++ b/functions/src/controller/patientController/utils.test.ts @@ -211,6 +211,8 @@ describe("snapshotExists", () => { it("throw amed", () => { function checkExists() { const mockSnapshot = { exists: true, data: () => ({ toAmed: 1 }) }; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore snapshotExists(mockSnapshot); } expect(checkExists).toThrowError( @@ -220,6 +222,8 @@ describe("snapshotExists", () => { it("throw มีข้อมูลแล้ว", () => { function checkExists() { const mockSnapshot = { exists: true, data: () => ({ toAmed: 0 }) }; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore snapshotExists(mockSnapshot); } expect(checkExists).toThrowError("มีข้อมูลผู้ใช้ในระบบแล้ว"); @@ -247,6 +251,8 @@ describe("updateSymptomCheckUser", () => { function checkUser() { const mockSnapshot = { exists: false }; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore updateSymptomCheckUser(mockSnapshot, lineUserID); } expect(checkUser).toThrowError(`ไม่พบผู้ใช้ ${lineUserID}`); diff --git a/functions/src/controller/patientController/utils.ts b/functions/src/controller/patientController/utils.ts index b3e0320a..6ddcfc55 100644 --- a/functions/src/controller/patientController/utils.ts +++ b/functions/src/controller/patientController/utils.ts @@ -3,6 +3,7 @@ import * as functions from "firebase-functions"; import { RegisterType, HistoryType } from '../../schema'; import { Patient, UpdatedPatient } from '../../types' import { TO_AMED_STATUS } from "../../utils" +import { DocumentSnapshot } from "firebase-functions/v1/firestore"; export const setPatientStatus = (obj: Omit, createdDate: Date): Patient => { @@ -54,7 +55,7 @@ const checkAmedStatus = (status: number, prevStatus: number, TO_AMED_STATUS: any return status !== prevStatus && TO_AMED_STATUS.includes(status) } -export const snapshotExists = (snapshot: any) => { +export const snapshotExists = (snapshot: DocumentSnapshot) => { if (snapshot.exists) { if (snapshot.data()?.toAmed === 1) { throw new functions.https.HttpsError( @@ -76,7 +77,7 @@ export const updateSymptomAddCreatedDate = ( obj.createdDate = timestamp; }; -export const updateSymptomCheckUser = (snapshot: any, lineUserID: string) => { +export const updateSymptomCheckUser = (snapshot: DocumentSnapshot, lineUserID: string) => { if (!snapshot.exists) { throw new functions.https.HttpsError( "not-found", diff --git a/functions/src/handler/eventHandler.ts b/functions/src/handler/eventHandler.ts index 43028c3e..676fac66 100644 --- a/functions/src/handler/eventHandler.ts +++ b/functions/src/handler/eventHandler.ts @@ -7,12 +7,10 @@ import { handleMessage } from "./subhandler/messageHandler"; export const eventHandler: LineHandler = async (event, userObject, client) => { switch (await event.type) { case "follow": - const followEvent = event as FollowEvent - await handleFollow(followEvent, userObject, client); + await handleFollow(event as FollowEvent, userObject, client); break; case "message": - const messageEvent = event as MessageEvent - await handleMessage(messageEvent, userObject, client); + await handleMessage(event as MessageEvent, userObject, client); break; default: break; From 31d10bc682bdbcc3a8a3de589e470f794e24a5e9 Mon Sep 17 00:00:00 2001 From: palsp Date: Mon, 23 Aug 2021 15:53:15 +0700 Subject: [PATCH 19/34] chore: include ts in precommit --- functions/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/functions/package.json b/functions/package.json index 811b36cc..8e6c9d2a 100644 --- a/functions/package.json +++ b/functions/package.json @@ -62,7 +62,7 @@ "typescript": "^4.3.5" }, "lint-staged": { - "*.{js,json,yml}": "prettier --write", - "*.js": "eslint --fix" + "*.{js,json,yml,ts}": "prettier --write", + "*.js,ts": "eslint --fix" } } From 8be7e5371fa7cc08a1a25341adf14ce9805d1856 Mon Sep 17 00:00:00 2001 From: palsp Date: Tue, 31 Aug 2021 12:05:26 +0700 Subject: [PATCH 20/34] refactor: stream file --- .../src/controller/exportController/index.ts | 84 +++---------------- .../src/controller/exportController/utils.ts | 30 +++++++ functions/src/index.ts | 19 ++--- functions/src/middleware/authentication.ts | 6 +- functions/src/types/handler.ts | 2 +- 5 files changed, 54 insertions(+), 87 deletions(-) diff --git a/functions/src/controller/exportController/index.ts b/functions/src/controller/exportController/index.ts index 4efa2560..402d49d0 100644 --- a/functions/src/controller/exportController/index.ts +++ b/functions/src/controller/exportController/index.ts @@ -1,14 +1,12 @@ -import * as fs from "fs"; -import * as path from "path"; import * as _ from "lodash"; import * as XLSX from "xlsx"; -import * as functions from "firebase-functions"; +import * as functions from "firebase-functions"; +import * as utils from "./utils"; import { admin, collection } from "../../init"; import { ExportRequestToCallType, validateExportRequestToCallSchema } from "../../schema"; import { OnCallHandler, OnRequestHandler, Patient, R2RAssistance } from "../../types"; import { formatPatient, formatter36Hr } from "./utils"; import { calculateAge, convertTZ } from "../../utils/date"; -import * as utils from "./utils"; import { statusList } from "../../api/const" import { patientReportHeader, @@ -16,8 +14,6 @@ import { MAP_PATIENT_FIELD, } from "../../utils/status"; import { QuerySnapshot } from "@google-cloud/firestore"; - - import { generateZipFileRoundRobin } from "../../utils/zip"; @@ -96,21 +92,8 @@ export const exportMaster: OnRequestHandler = async (req, res) => { const ws = XLSX.utils.aoa_to_sheet(result); XLSX.utils.book_append_sheet(wb, ws, "รายงานที่อยู่ผู้ป่วย 4 สิงหาคม"); - const filename = `report.xlsx`; - const opts: XLSX.WritingOptions = { bookType: "xlsx", type: 'binary' }; - - // it must be save to tmp directory because it run on firebase - const pathToSave = path.join("/tmp", filename); - XLSX.writeFile(wb, pathToSave, opts); - - const stream = fs.createReadStream(pathToSave); - // prepare http header - res.setHeader( - "Content-Type", - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" - ); - res.setHeader("Content-Disposition", `attachment; filename="${filename}"`); - stream.pipe(res); + + utils.streamXLSXFile(res, wb, "report.xlsx") } catch (err) { console.log(err); res.json({ success: false }); @@ -168,23 +151,8 @@ export const exportPatientForNurse: OnRequestHandler = async (req, res) => { const ws = XLSX.utils.aoa_to_sheet(results[i]); XLSX.utils.book_append_sheet(wb, ws, sheetName[i]); } - // write workbook file - const filename = `report.xlsx`; - const opts: XLSX.WritingOptions = { bookType: "xlsx", type: "binary" }; - - // it must be save to tmp directory because it run on firebase - const pathToSave = path.join("/tmp", filename); - XLSX.writeFile(wb, pathToSave, opts); - // create read stream - const stream = fs.createReadStream(pathToSave); - // prepare http header - res.setHeader( - "Content-Type", - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" - ); - res.setHeader("Content-Disposition", `attachment; filename="${filename}"`); - stream.pipe(res); + utils.streamXLSXFile(res, wb, "report.xlsx") await Promise.all([ updatedDocId.map((id) => { @@ -245,7 +213,7 @@ export const exportAllPatient: OnRequestHandler = async (req, res) => { results[i] = [[...patientReportHeader]]; } snapshot.docs.forEach((doc) => { - const data = doc.data(); + const data = doc.data() as Patient; const arr = [ data.personalID, data.firstName, @@ -283,23 +251,8 @@ export const exportAllPatient: OnRequestHandler = async (req, res) => { const ws = XLSX.utils.aoa_to_sheet(results[i]); XLSX.utils.book_append_sheet(wb, ws, sheets[i]); } - // write workbook file - const filename = `report.xlsx`; - const opts: XLSX.WritingOptions = { bookType: "xlsx", type: "binary" }; - - // it must be save to tmp directory because it run on firebase - const pathToSave = path.join("/tmp", filename); - XLSX.writeFile(wb, pathToSave, opts); - // create read stream - const stream = fs.createReadStream(pathToSave); - // prepare http header - res.setHeader( - "Content-Type", - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" - ); - res.setHeader("Content-Disposition", `attachment; filename="${filename}"`); - stream.pipe(res); + utils.streamXLSXFile(res, wb, "report.xlsx") } catch (err) { console.log(err); res.json({ success: false }); @@ -352,9 +305,9 @@ export const exportTimeSeries: OnRequestHandler = async (req, res) => { "date", "active users", "drop off Rate", - "r2cccount", + "r2c count", "terminate users", - "activebtw 36 to 72 hrs", + "active btw 36 to 72 hrs", ]; const result = [headers]; snapshot.forEach((doc) => { @@ -369,26 +322,13 @@ export const exportTimeSeries: OnRequestHandler = async (req, res) => { data.usersbtw36hrsto72hrs, ]); }); - console.log(result); + const wb = XLSX.utils.book_new(); const ws = XLSX.utils.aoa_to_sheet(result); + XLSX.utils.book_append_sheet(wb, ws, "statistics"); - const filename = `daily_statistics.xlsx`; - const opts = { bookType: "xlsx", type: "binary" }; - - // it must be save to tmp directory because it run on firebase - const pathToSave = path.join("/tmp", filename); - XLSX.writeFile(wb, pathToSave, opts); - - const stream = fs.createReadStream(pathToSave); - // prepare http header - res.setHeader( - "Content-Type", - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" - ); - res.setHeader("Content-Disposition", `attachment; filename="${filename}"`); - stream.pipe(res); + utils.streamXLSXFile(res, wb, "daily_statistics.xlsx") } catch (err) { console.log(err); res.json({ success: false }); diff --git a/functions/src/controller/exportController/utils.ts b/functions/src/controller/exportController/utils.ts index bed4a79e..92fbd5e0 100644 --- a/functions/src/controller/exportController/utils.ts +++ b/functions/src/controller/exportController/utils.ts @@ -2,8 +2,38 @@ import { admin, collection } from "../../init" import { statusList } from "../../api/const"; import { QuerySnapshot } from "@google-cloud/firestore"; import { Patient, NotUpdatedList, R2RAssistance, R2C, WithID } from "../../types"; +import { Response } from "express"; +import * as XLSX from "xlsx"; +import * as fs from "fs"; +import * as path from "path"; +export const streamXLSXFile = ( + res: Response, + wb: XLSX.WorkBook, + filename: string, +): void => { + const opts: XLSX.WritingOptions = { bookType: "xlsx", type: "binary" }; + + // it must be save to `/tmp` directory because it run on firebase + const pathToSave = path.join("/tmp", filename) + + // write file + XLSX.writeFile(wb, pathToSave, opts) + + // create read stream + const stream = fs.createReadStream(pathToSave); + + // prepare http header + res.setHeader( + "Content-Type", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + ); + res.setHeader("Content-Disposition", `attachment; filename="${filename}"`); + + stream.pipe(res); +} + export const getUnExportedR2RUsers = () => { return admin diff --git a/functions/src/index.ts b/functions/src/index.ts index ae493f56..ac7248b6 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -48,18 +48,15 @@ initializeApp(); // exportController.exportPatientForNurse // ); -export const exportNurse = functions - .region(region) - .https.onRequest(authenticateVolunteerRequest(exportController.exportPatientForNurse)) -// app.get( -// "/exportPatientForNurse", -// authenticateVolunteerRequest(exportController.exportPatientForNurse) -// ); +app.get( + "/exportPatientForNurse", + authenticateVolunteerRequest(exportController.exportPatientForNurse) +); -// app.get( -// "/exportTimeSeries", -// authenticateVolunteerRequest(exportController.exportTimeSeries) -// ); +app.get( + "/exportTimeSeries", + authenticateVolunteerRequest(exportController.exportTimeSeries) +); export const webhook = functions.region(region).https.onRequest(async (req, res) => { res.sendStatus(200); diff --git a/functions/src/middleware/authentication.ts b/functions/src/middleware/authentication.ts index eaeed737..a4fec2c9 100644 --- a/functions/src/middleware/authentication.ts +++ b/functions/src/middleware/authentication.ts @@ -2,7 +2,7 @@ import { admin } from "../init"; import axios from "axios"; import * as functions from "firebase-functions"; -import { LineCredential, OnCallHandler, OnRequestHandler } from "../types"; +import { LineCredential, OnCallHandler } from "../types"; /** * Authenticate middleware for volunteer system @@ -41,8 +41,8 @@ export const authenticateVolunteer = (func: OnCallHandler): OnCallHandler => { * @param {*} func function to call if authenticate success * @returns error 401 if not authorized email */ -export const authenticateVolunteerRequest = (func: OnRequestHandler): OnRequestHandler => { - return async (req, res) => { +export function authenticateVolunteerRequest(func: any): any { + return async (req: any, res: any) => { try { if ( req.body.noAuth && diff --git a/functions/src/types/handler.ts b/functions/src/types/handler.ts index a23391cc..6eca9d4c 100644 --- a/functions/src/types/handler.ts +++ b/functions/src/types/handler.ts @@ -5,4 +5,4 @@ import { EventContext, https } from "firebase-functions"; export type OnCallHandler = (data: T, context: https.CallableContext) => any | Promise export type OnRequestHandler = (req: https.Request, resp: express.Response) => void | Promise export type OnRunHandler = (context: EventContext) => PromiseLike | any -export type ExpressHandler = (req: express.Request, res: express.Response) => void | Promise \ No newline at end of file +export type ExpressHandler = (req: express.Request, res: express.Response) => void | Promise From 852a8eee9a5d5e6af8a9abeabc36ce8d2e31d470 Mon Sep 17 00:00:00 2001 From: palsp Date: Sat, 11 Sep 2021 10:44:40 +0700 Subject: [PATCH 21/34] feat: public API [remove] export [remove] r2c, r2r [remove] register [remove] import [modify] update symptom --- functions/src/api/index.ts | 50 ++- functions/src/backup/index.ts | 44 --- .../controller/exportController/index.test.ts | 190 ---------- .../src/controller/exportController/index.ts | 336 ------------------ .../controller/exportController/utils.test.ts | 161 --------- .../src/controller/exportController/utils.ts | 173 --------- .../importController/importController.ts | 179 ---------- functions/src/controller/index.ts | 3 - .../src/controller/patientController/index.ts | 106 +----- .../patientController/utils.test.ts | 152 ++------ .../src/controller/patientController/utils.ts | 35 +- functions/src/controller/pubsub/utils.ts | 29 +- .../src/controller/requestController/index.ts | 126 ------- .../src/controller/requestController/utils.ts | 39 -- functions/src/index.test.ts | 3 - functions/src/index.ts | 81 ----- functions/src/middleware/authentication.ts | 4 +- .../src/schema/ExportRequestToCallSchema.ts | 8 - functions/src/schema/HistorySchema.ts | 33 +- functions/src/schema/ImportPatientIdSchema.ts | 22 -- .../schema/ImportRequestToRegisterSchema.ts | 18 - functions/src/schema/ImportWhitelistSchema.ts | 18 - functions/src/schema/RegisterSchema.ts | 75 ---- .../src/schema/RequestToRegisterSchema.ts | 11 - functions/src/schema/index.ts | 14 +- functions/src/types/patient.ts | 1 - 26 files changed, 126 insertions(+), 1785 deletions(-) delete mode 100644 functions/src/backup/index.ts delete mode 100644 functions/src/controller/exportController/index.test.ts delete mode 100644 functions/src/controller/exportController/index.ts delete mode 100644 functions/src/controller/exportController/utils.test.ts delete mode 100644 functions/src/controller/exportController/utils.ts delete mode 100644 functions/src/controller/importController/importController.ts delete mode 100644 functions/src/controller/requestController/index.ts delete mode 100644 functions/src/controller/requestController/utils.ts delete mode 100644 functions/src/schema/ExportRequestToCallSchema.ts delete mode 100644 functions/src/schema/ImportPatientIdSchema.ts delete mode 100644 functions/src/schema/ImportRequestToRegisterSchema.ts delete mode 100644 functions/src/schema/ImportWhitelistSchema.ts delete mode 100644 functions/src/schema/RegisterSchema.ts delete mode 100644 functions/src/schema/RequestToRegisterSchema.ts diff --git a/functions/src/api/index.ts b/functions/src/api/index.ts index 78b893bf..8966c6a2 100644 --- a/functions/src/api/index.ts +++ b/functions/src/api/index.ts @@ -1,21 +1,19 @@ import axios from "axios"; -import { calculateAge, formatDateTimeAPI } from "../utils"; import * as functions from "firebase-functions"; import * as _ from "lodash"; -import { FollowUp, Patient } from "../types"; +import { FollowUp } from "../types"; import { statusList } from "./const"; const URL = "https://pedsanam.ydm.family/pedsanam/label_score"; const AUTHORIZATION = functions.config().api.authorization; -export const makeStatusAPIPayload = (data: Patient, lastFollowUp: FollowUp) => { - const age = calculateAge(data.birthDate.toDate()); - const infected_discover_date = formatDateTimeAPI(data.createdDate.toDate()); +export const makeStatusAPIPayload = (lastFollowUp: FollowUp) => { + const payload = { - age: age, - gender: data.gender, - height: data.height, - weight: data.weight, - infected_discover_date: infected_discover_date, + age: lastFollowUp.age, + gender: lastFollowUp.gender, + height: lastFollowUp.height, + weight: lastFollowUp.weight, + // infected_discover_date: infected_discover_date, sp_o2: (lastFollowUp.sp_o2 || 100) / 100, sp_o2_ra: (lastFollowUp.sp_o2_ra || 100) / 100, sp_o2_after_eih: (lastFollowUp.sp_o2_after_eih || 100) / 100, @@ -25,13 +23,13 @@ export const makeStatusAPIPayload = (data: Patient, lastFollowUp: FollowUp) => { sym1_poor_appetite: lastFollowUp.sym1_poor_appetite, sym1_fatigue: lastFollowUp.sym1_fatigue, sym1_persistent_fever: lastFollowUp.sym1_persistent_fever, - rf_copd_chronic_lung_disease: data.rf_copd_chronic_lung_disease, - rf_ckd_stage_3_to_4: data.rf_ckd_stagr_3_to_4, - rf_chronic_heart_disease: data.rf_chronic_heart_disease, - rf_cva: data.rf_cva, - rf_t2dm: data.rf_t2dm, - rf_cirrhosis: data.rf_cirrhosis, - rf_immunocompromise: data.rf_immunocompromise, + rf_copd_chronic_lung_disease: lastFollowUp.rf_copd_chronic_lung_disease, + rf_ckd_stage_3_to_4: lastFollowUp.rf_ckd_stagr_3_to_4, + rf_chronic_heart_disease: lastFollowUp.rf_chronic_heart_disease, + rf_cva: lastFollowUp.rf_cva, + rf_t2dm: lastFollowUp.rf_t2dm, + rf_cirrhosis: lastFollowUp.rf_cirrhosis, + rf_immunocompromise: lastFollowUp.rf_immunocompromise, sym2_tired_body_ache: lastFollowUp.sym2_tired_body_ache, sym2_cough: lastFollowUp.sym2_cough, sym2_fever: lastFollowUp.sym2_fever, @@ -39,15 +37,15 @@ export const makeStatusAPIPayload = (data: Patient, lastFollowUp: FollowUp) => { sym2_cannot_smell: lastFollowUp.sym2_cannot_smell, sym2_rash: lastFollowUp.sym2_rash, sym2_red_eye: lastFollowUp.sym2_red_eye, - fac_diabetes: data.fac_diabetes, - fac_dyslipidemia: data.fac_dyslipidemia, - fac_hypertension: data.fac_hypertension, - fac_esrd: data.fac_esrd, - fac_cancer: data.fac_cancer, - fac_tuberculosis: data.fac_tuberculosis, - fac_hiv: data.fac_hiv, - fac_asthma: data.fac_asthma, - fac_pregnancy: data.fac_pregnancy, + fac_diabetes: lastFollowUp.fac_diabetes, + fac_dyslipidemia: lastFollowUp.fac_dyslipidemia, + fac_hypertension: lastFollowUp.fac_hypertension, + fac_esrd: lastFollowUp.fac_esrd, + fac_cancer: lastFollowUp.fac_cancer, + fac_tuberculosis: lastFollowUp.fac_tuberculosis, + fac_hiv: lastFollowUp.fac_hiv, + fac_asthma: lastFollowUp.fac_asthma, + fac_pregnancy: lastFollowUp.fac_pregnancy, fac_bed_ridden_status: lastFollowUp.fac_bed_ridden_status, fac_uri_symptoms: lastFollowUp.fac_uri_symptoms, fac_diarrhea: lastFollowUp.fac_diarrhea, diff --git a/functions/src/backup/index.ts b/functions/src/backup/index.ts deleted file mode 100644 index 67ad8221..00000000 --- a/functions/src/backup/index.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { admin } from "../init"; -import { convertTZ } from "../utils/date"; -import { replace } from "lodash"; -import * as functions from "firebase-functions"; - -import config from "../config"; - -import { google } from "googleapis"; -import { OnRunHandler } from "../types"; - -const privateKey = replace(config.backupAccount.privateKey, /\\n/g, "\n"); -const isDevelopment = - functions.config().environment && - functions.config().environment.isdevelopment; -const authClient = new google.auth.JWT({ - email: config.backupAccount.clientEmail, - key: privateKey, - scopes: [ - "https://www.googleapis.com/auth/datastore", - "https://www.googleapis.com/auth/cloud-platform", - ], -}); -const authPromise = authClient.authorize(); - -const firestoreClient = google.firestore({ - version: "v1beta2", - auth: authClient, -}); - -export const backup: OnRunHandler = async (_context) => { - if (isDevelopment) return; - const projectId = admin.instanceId().app.options.projectId; - - const timestamp = convertTZ(new Date()).toISOString(); - - console.log(`Start to backup project ${projectId}`); - await authPromise; - return firestoreClient.projects.databases.exportDocuments({ - name: `projects/${projectId}/databases/(default)`, - requestBody: { - outputUriPrefix: `gs://${projectId}-firestore-backup/backups/${timestamp}`, - }, - }); -}; diff --git a/functions/src/controller/exportController/index.test.ts b/functions/src/controller/exportController/index.test.ts deleted file mode 100644 index bf593050..00000000 --- a/functions/src/controller/exportController/index.test.ts +++ /dev/null @@ -1,190 +0,0 @@ -import * as functions from "firebase-functions"; - -describe("exportController", () => { - const MOCK_RESULT = { ok: true, title: "report.zip", content: "content" }; - const exportRequestToCallSchemaMock = jest.fn(); - - jest.doMock("../../schema", () => ({ - validateExportRequestToCallSchema: exportRequestToCallSchemaMock, - })); - - jest.doMock("../../utils/zip", () => ({ - generateZipFileRoundRobin: generateZipFileRoundRobinMock, - })); - - const generateZipFileRoundRobinMock = jest - .fn() - .mockReturnValue(new Promise((resolve) => resolve(MOCK_RESULT))); - - describe("exportR2R", () => { - const MOCK_SCHEMA_VALUE = { volunteerSize: 5 }; - const MOCK_SCHEMA_ERROR_FREE = undefined; - const MOCK_SNAPSHOT = {}; - const MOCK_USERLIST = [] as any[]; - - jest.unmock("./utils"); - jest.resetModules(); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - const getUnExportedR2RUsersMock = jest - .fn() - .mockReturnValue(new Promise((resolve) => resolve(MOCK_SNAPSHOT))); - const serializeDataMock = jest.fn().mockReturnValue(MOCK_USERLIST); - const updateExportedR2RUsersMock = jest - .fn() - .mockReturnValue(new Promise((resolve) => resolve("success"))); - const formatterR2RMock = jest.fn(); - - jest.doMock("./utils", () => ({ - getUnExportedR2RUsers: getUnExportedR2RUsersMock, - serializeData: serializeDataMock, - updateExportedR2RUsers: updateExportedR2RUsersMock, - formatterR2R: formatterR2RMock, - })); - - const { exportR2R } = require("."); - - it("should be able to throw error if schema error in R2R", async () => { - const MOCK_SCHEMA_ERROR = new functions.https.HttpsError( - "failed-precondition", - "ข้อมูลไม่ถูกต้อง" - ); - exportRequestToCallSchemaMock.mockReturnValueOnce({ - error: MOCK_SCHEMA_ERROR, - value: MOCK_SCHEMA_VALUE, - }); - - const MOCK_DATA = {}; - let error = undefined; - let result = undefined; - - try { - result = await exportR2R(MOCK_DATA); - } catch (e) { - error = e; - } - - expect(exportRequestToCallSchemaMock).toBeCalledWith(MOCK_DATA); - expect(error).toEqual(MOCK_SCHEMA_ERROR); - expect(result).toBe(undefined); - }); - - it("should be export R2R correctly if validation success", async () => { - exportRequestToCallSchemaMock.mockReturnValueOnce({ - error: MOCK_SCHEMA_ERROR_FREE, - value: MOCK_SCHEMA_VALUE, - }); - - const MOCK_DATA = {}; - let error = undefined; - let result = undefined; - - try { - result = await exportR2R(MOCK_DATA); - } catch (e) { - error = e; - } - - expect(exportRequestToCallSchemaMock).toBeCalledWith(MOCK_DATA); - expect(error).toEqual(undefined); - expect(getUnExportedR2RUsersMock).toBeCalled(); - expect(serializeDataMock).toBeCalledWith(MOCK_SNAPSHOT); - expect(generateZipFileRoundRobinMock).toBeCalledWith( - MOCK_SCHEMA_VALUE.volunteerSize, - MOCK_USERLIST, - ["internal id", "name", "tel"], - formatterR2RMock - ); - expect(updateExportedR2RUsersMock).toBeCalledWith(MOCK_SNAPSHOT); - expect(result).toBe(MOCK_RESULT); - }); - }); - - describe("exportR2C", () => { - const MOCK_SCHEMA_VALUE = { volunteerSize: 5 }; - const MOCK_SCHEMA_ERROR_FREE = undefined; - const MOCK_SNAPSHOT = {}; - const MOCK_PATIENT_LIST = [] as any[]; - const MOCK_RESULT = { ok: true, title: "report.zip", content: "content" }; - - jest.unmock("./utils"); - jest.resetModules(); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - const getUnExportedR2CUsersMock = jest - .fn() - .mockReturnValue(new Promise((resolve) => resolve(MOCK_SNAPSHOT))); - const updateAndSerializeR2CDataMock = jest - .fn() - .mockReturnValue(MOCK_PATIENT_LIST); - const formatterR2CMock = jest.fn(); - - jest.doMock("./utils", () => ({ - getUnExportedR2CUsers: getUnExportedR2CUsersMock, - updateAndSerializeR2CData: updateAndSerializeR2CDataMock, - formatterR2C: formatterR2CMock, - })); - - const { exportR2C } = require("."); - - it("should be able to throw error if schema error in R2C", async () => { - const MOCK_SCHEMA_ERROR = new functions.https.HttpsError( - "failed-precondition", - "ข้อมูลไม่ถูกต้อง" - ); - exportRequestToCallSchemaMock.mockReturnValueOnce({ - error: MOCK_SCHEMA_ERROR, - value: MOCK_SCHEMA_VALUE, - }); - - const MOCK_DATA = {}; - let error = undefined; - let result = undefined; - - try { - result = await exportR2C(MOCK_DATA); - } catch (e) { - error = e; - } - - expect(exportRequestToCallSchemaMock).toBeCalledWith(MOCK_DATA); - expect(error).toEqual(MOCK_SCHEMA_ERROR); - expect(result).toBe(undefined); - }); - - it("should be export R2C correctly if validation success", async () => { - exportRequestToCallSchemaMock.mockReturnValueOnce({ - error: MOCK_SCHEMA_ERROR_FREE, - value: MOCK_SCHEMA_VALUE, - }); - - const MOCK_DATA = {}; - let error = undefined; - let result = undefined; - - try { - result = await exportR2C(MOCK_DATA); - } catch (e) { - error = e; - } - - expect(exportRequestToCallSchemaMock).toBeCalledWith(MOCK_DATA); - expect(error).toEqual(undefined); - expect(getUnExportedR2CUsersMock).toBeCalled(); - expect(updateAndSerializeR2CDataMock).toBeCalledWith(MOCK_SNAPSHOT); - expect(generateZipFileRoundRobinMock).toBeCalledWith( - MOCK_SCHEMA_VALUE.volunteerSize, - MOCK_PATIENT_LIST, - ["internal id", "first name", "call status", "tel"], - formatterR2CMock - ); - expect(result).toEqual(MOCK_RESULT); - }); - }); -}); diff --git a/functions/src/controller/exportController/index.ts b/functions/src/controller/exportController/index.ts deleted file mode 100644 index 402d49d0..00000000 --- a/functions/src/controller/exportController/index.ts +++ /dev/null @@ -1,336 +0,0 @@ -import * as _ from "lodash"; -import * as XLSX from "xlsx"; -import * as functions from "firebase-functions"; -import * as utils from "./utils"; -import { admin, collection } from "../../init"; -import { ExportRequestToCallType, validateExportRequestToCallSchema } from "../../schema"; -import { OnCallHandler, OnRequestHandler, Patient, R2RAssistance } from "../../types"; -import { formatPatient, formatter36Hr } from "./utils"; -import { calculateAge, convertTZ } from "../../utils/date"; -import { statusList } from "../../api/const" -import { - patientReportHeader, - sheetName, - MAP_PATIENT_FIELD, -} from "../../utils/status"; -import { QuerySnapshot } from "@google-cloud/firestore"; -import { generateZipFileRoundRobin } from "../../utils/zip"; - - -export const exportR2R: OnCallHandler = async (data, _context) => { - const { value, error } = validateExportRequestToCallSchema(data); - if (error) { - throw new functions.https.HttpsError( - "failed-precondition", - "ข้อมูลไม่ถูกต้อง" - ); - } - const { volunteerSize: size } = value; - - // get and serialize user from database - const snapshot = await utils.getUnExportedR2RUsers() as QuerySnapshot; - const userList = utils.serializeData(snapshot); - - // create zip file - const header = ["internal id", "name", "tel"]; - const result = await generateZipFileRoundRobin( - size, - userList, - header, - utils.formatterR2R - ); - - // mark user as exported - await utils.updateExportedR2RUsers(snapshot); - - return result; -}; - -export const exportR2C: OnCallHandler = async (data, _context) => { - const { value, error } = validateExportRequestToCallSchema(data); - if (error) { - throw new functions.https.HttpsError( - "failed-precondition", - "ข้อมูลไม่ถูกต้อง" - ); - } - const { volunteerSize } = value; - const snapshot = await utils.getUnExportedR2CUsers(); - const patientList = await utils.updateAndSerializeR2CData(snapshot); - - const header = ["internal id", "first name", "call status", "tel"]; - - return generateZipFileRoundRobin( - volunteerSize, - patientList, - header, - utils.formatterR2C - ); -}; - -export const exportMaster: OnRequestHandler = async (req, res) => { - try { - const snapshot = await admin - .firestore() - .collection(collection.patient) - .get(); - - const header = ["ที่อยู่", "เขต", "แขวง", "จังหวัด"]; - const result = [header]; - snapshot.forEach((doc) => { - const data = doc.data() as Patient; - - result.push([ - data.address, - data.district, - data.prefecture, - data.province, - ]); - }); - const wb = XLSX.utils.book_new(); - - const ws = XLSX.utils.aoa_to_sheet(result); - - XLSX.utils.book_append_sheet(wb, ws, "รายงานที่อยู่ผู้ป่วย 4 สิงหาคม"); - - utils.streamXLSXFile(res, wb, "report.xlsx") - } catch (err) { - console.log(err); - res.json({ success: false }); - } -}; - -export const exportPatientForNurse: OnRequestHandler = async (req, res) => { - try { - const snapshot = await admin - .firestore() - .collection(collection.patient) - .where("isNurseExported", "==", false) - .get(); - - const INCLUDE_STATUS = [ - statusList["Y1"], - statusList["Y2"], - statusList["R1"], - statusList["R2"], - ]; - - const results = new Array(INCLUDE_STATUS.length); - const reportHeader = _.keys(MAP_PATIENT_FIELD); - - for (let i = 0; i < results.length; i++) { - results[i] = [[...reportHeader]]; - } - - const updatedDocId: string[] = []; - - snapshot.docs.forEach((doc) => { - const data = doc.data(); - if (typeof data.status !== "number") { - return; - } - // exclude unknown, G1 and G2 - if (!INCLUDE_STATUS.includes(data.status)) { - return; - } - - updatedDocId.push(doc.id); - const arr = []; - for (const key of reportHeader) { - arr.push(data[MAP_PATIENT_FIELD[key]]); - } - const status = data.status - 3; - results[status].push(arr); - }); - - const wb = XLSX.utils.book_new(); - // append result to sheet - for (let i = 0; i < results.length && i < sheetName.length; i++) { - // not export unknown or g1 - - const ws = XLSX.utils.aoa_to_sheet(results[i]); - XLSX.utils.book_append_sheet(wb, ws, sheetName[i]); - } - - utils.streamXLSXFile(res, wb, "report.xlsx") - - await Promise.all([ - updatedDocId.map((id) => { - const docRef = admin.firestore().collection(collection.patient).doc(id); - - return docRef.update({ - isNurseExported: true, - }); - }), - ]); - } catch (err) { - console.log(err); - res.json({ success: false }); - } -}; - -export const export36hrs: OnCallHandler = async (data, _context) => { - const { value, error } = validateExportRequestToCallSchema(data); - if (error) { - throw new functions.https.HttpsError( - "failed-precondition", - "ข้อมูลไม่ถูกต้อง" - ); - } - const { volunteerSize } = value; - const patientList = await utils.get36hrsUsers(); - const header = ["first name", "tel"]; - - return generateZipFileRoundRobin( - volunteerSize, - patientList, - header, - formatter36Hr - ); -}; - -/** - * one time used only - */ -export const exportAllPatient: OnRequestHandler = async (req, res) => { - try { - const { password } = req.query; - if (password !== "SpkA43Zadkl") { - throw new functions.https.HttpsError( - "permission-denied", - "ไม่มี permission" - ); - } - - const snapshot = await admin - .firestore() - .collection(collection.patient) - .get(); - - const statusListArr = _.keys(statusList); - const results = new Array(statusListArr.length); - for (let i = 0; i < results.length; i++) { - results[i] = [[...patientReportHeader]]; - } - snapshot.docs.forEach((doc) => { - const data = doc.data() as Patient; - const arr = [ - data.personalID, - data.firstName, - data.lastName, - data.personalPhoneNo, - data.emergencyPhoneNo, - calculateAge(data.birthDate.toDate()), - data.weight, - data.height, - data.gender, - convertTZ(data.lastUpdatedAt.toDate()), - data.address, - data.district, - data.prefecture, - data.province, - statusListArr[data.status], - ]; - - results[data.status].push(arr); - }); - - const sheets = [ - "รายงานผู้ป่วยที่ไม่สามารถระบุสี", - "รายงานผู้ป่วยเขียวไม่มีอาการ", - "รายงานผู้ป่วยเขียวมีอาการ", - "รายงานผู้ป่วยเหลืองไม่มีอาการ", - "รายงานผู้ป่วยเหลืองมีอาการ", - "รายงานผู้ป่วยแดงอ่อน", - "รายงานผู้ป่วยแดงเข้ม", - ]; - - const wb = XLSX.utils.book_new(); - // append result to sheet - for (let i = 0; i < results.length && i < sheets.length; i++) { - const ws = XLSX.utils.aoa_to_sheet(results[i]); - XLSX.utils.book_append_sheet(wb, ws, sheets[i]); - } - - utils.streamXLSXFile(res, wb, "report.xlsx") - } catch (err) { - console.log(err); - res.json({ success: false }); - } -}; - -export const exportRequestToCallDayOne: OnCallHandler = async (data, _context) => { - const { value, error } = validateExportRequestToCallSchema(data); - if (error) { - throw new functions.https.HttpsError( - "invalid-argument", - "ข้อมูลไม่ถูกต้อง" - ); - } - - const { volunteerSize } = value; - const patientList: Patient[] = []; - - const snapshot = await admin.firestore().collection(collection.patient).get(); - await Promise.all( - snapshot.docs.map((doc) => { - // WARNING SIDE EFFECT inside map - const docData = doc.data() as Patient; - patientList.push(docData); - }) - ); - const headers = [ - "personal id", - "first name", - "last name", - "tel", - "emergency phone", - ]; - return generateZipFileRoundRobin( - volunteerSize, - patientList, - headers, - formatPatient - ); -}; - -export const exportTimeSeries: OnRequestHandler = async (req, res) => { - try { - const snapshot = await admin - .firestore() - .collection(collection.timeSeries) - .get(); - - const headers = [ - "date", - "active users", - "drop off Rate", - "r2c count", - "terminate users", - "active btw 36 to 72 hrs", - ]; - const result = [headers]; - snapshot.forEach((doc) => { - const data = doc.data(); - console.log(data); - result.push([ - doc.id, - data.activeUser, - data.dropoffrate, - data.r2account, - data.terminateUser, - data.usersbtw36hrsto72hrs, - ]); - }); - - const wb = XLSX.utils.book_new(); - - const ws = XLSX.utils.aoa_to_sheet(result); - - XLSX.utils.book_append_sheet(wb, ws, "statistics"); - utils.streamXLSXFile(res, wb, "daily_statistics.xlsx") - } catch (err) { - console.log(err); - res.json({ success: false }); - } -}; diff --git a/functions/src/controller/exportController/utils.test.ts b/functions/src/controller/exportController/utils.test.ts deleted file mode 100644 index 4979b860..00000000 --- a/functions/src/controller/exportController/utils.test.ts +++ /dev/null @@ -1,161 +0,0 @@ -const utils = require("./utils"); - -const mockFbWhere = jest.fn(() => { - return { get: jest.fn() }; -}); - -const mockUpdate = jest.fn(); -const mockDoc = jest.fn(() => ({ - update: mockUpdate, -})); -const mockGet36Users = jest.fn() - -const mockCollection = jest.fn(() => { - return { - where: mockFbWhere, - doc: mockDoc, - get: mockGet36Users, - }; -}); - -jest.mock("../../init", () => { - return { - ...jest.requireActual("../../init"), - admin: { - firestore: () => ({ - collection: mockCollection, - }), - }, - }; -}); - -describe("exportController", () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - describe("R2R", () => { - describe("getUnExportedR2RUsers", () => { - it("should fetch from R2R collection", () => { - utils.getUnExportedR2RUsers(); - const arg = (mockCollection.mock.calls[0] as any)[0]; - expect(arg).toEqual("requestToRegisterAssistance"); - }); - - it("should select un exported users", () => { - utils.getUnExportedR2RUsers(); - const args = mockFbWhere.mock.calls[0] as any; - expect(args[0]).toEqual("isR2RExported"); - expect(args[1]).toEqual("=="); - expect(args[2]).toEqual(false); - }); - }); - }); - - describe("R2C", () => { - describe("updateAndSerializeR2CData", () => { - it("should select un exported users", () => { - // mock - const mockGet = jest.fn(); - const mockOrderBy = jest.fn(() => ({ get: mockGet })); - const mockFbWhere2 = jest.fn(() => ({ orderBy: mockOrderBy })); - mockFbWhere.mockImplementationOnce(() => ({ - where: mockFbWhere2, - } as any)); - // called function - utils.getUnExportedR2CUsers(); - - // check arguments - const collection = (mockCollection.mock.calls[0] as any)[0]; - expect(collection).toEqual("patient"); - - const whereArgs = mockFbWhere.mock.calls[0] as any; - expect(whereArgs[0]).toEqual("isRequestToCall"); - expect(whereArgs[1]).toEqual("=="); - expect(whereArgs[2]).toEqual(true); - - const where2Args = mockFbWhere2.mock.calls[0] as any; - expect(where2Args[0]).toEqual("isRequestToCallExported"); - expect(where2Args[1]).toEqual("=="); - expect(where2Args[2]).toEqual(false); - - const orderByArg = (mockOrderBy.mock.calls[0] as any)[0]; - expect(orderByArg).toEqual("lastUpdatedAt"); - - expect(mockGet).toHaveBeenCalledTimes(1); - }); - }); - - describe("makeR2CPayload", () => { - it("should contain exact field in the result payload", () => { - const docId = "test.id"; - const fname = "test_user_first_name"; - const lname = "test_user_last_name"; - const phoneNo = "081xxxxxxx"; - const data = { - address: "home", - firstName: fname, - lastName: lname, - digitalLiteracy: false, - district: "โนนกลาง", - gender: "male", - personalPhoneNo: phoneNo, - }; - const result = utils.makeR2CPayload(docId, data); - expect(result.id).toEqual(docId); - expect(result.firstName).toEqual(fname); - expect(result.lastName).toEqual(lname); - expect(result.hasCalled).toEqual(0); - expect(result.personalPhoneNo).toEqual(phoneNo); - }); - }); - - describe("serializeData", () => { - it("should get all data from collection snapshot and return to array", () => { - const MOCK_DOC_1 = { id: "id 1", data: () => ({ id: "id 1", data: "data 1" }) }; - const MOCK_DOC_2 = { id: "id 2", data: () => ({ id: "id 2", data: "data 2" }) }; - const MOCK_SNAPSHOT = { docs: [MOCK_DOC_1, MOCK_DOC_2] }; - const EXPECTED_RESULT = [{ id: "id 1", data: "data 1" }, { id: "id 2", data: "data 2" }]; - - const result = utils.serializeData(MOCK_SNAPSHOT); - - expect(result).toEqual(EXPECTED_RESULT); - }); - }); - - describe("updateExportedR2RUsers", () => { - it("should update all data from collection snapshot with isUpdated: true", async () => { - const MOCK_DOC_1 = { id: "id 1" }; - const MOCK_DOC_2 = { id: "id 2" }; - const MOCK_SNAPSHOT = { docs: [MOCK_DOC_1, MOCK_DOC_2] }; - - await utils.updateExportedR2RUsers(MOCK_SNAPSHOT); - expect(mockDoc).toHaveBeenNthCalledWith(1, "id 1"); - expect(mockDoc).toHaveBeenNthCalledWith(2, "id 2"); - - const UPDATE_PARAM = { - isR2RExported: true, - }; - expect(mockUpdate).toHaveBeenNthCalledWith(1, UPDATE_PARAM); - expect(mockUpdate).toHaveBeenNthCalledWith(2, UPDATE_PARAM); - }); - }); - - describe("get36hrsUsers", () => { - it("should get all users that is between 36 and 72 hours last updated symptom", async () => { - const MOCK_DOC_1 = { id: "id 1" }; - const MOCK_DOC_2 = { id: "id 2" }; - const MOCK_SNAPSHOT = { docs: [MOCK_DOC_1, MOCK_DOC_2] }; - - await utils.updateExportedR2RUsers(MOCK_SNAPSHOT); - expect(mockDoc).toHaveBeenNthCalledWith(1, "id 1"); - expect(mockDoc).toHaveBeenNthCalledWith(2, "id 2"); - - const UPDATE_PARAM = { - isR2RExported: true, - }; - expect(mockUpdate).toHaveBeenNthCalledWith(1, UPDATE_PARAM); - expect(mockUpdate).toHaveBeenNthCalledWith(2, UPDATE_PARAM); - }); - }); - }); -}); diff --git a/functions/src/controller/exportController/utils.ts b/functions/src/controller/exportController/utils.ts deleted file mode 100644 index 92fbd5e0..00000000 --- a/functions/src/controller/exportController/utils.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { admin, collection } from "../../init" -import { statusList } from "../../api/const"; -import { QuerySnapshot } from "@google-cloud/firestore"; -import { Patient, NotUpdatedList, R2RAssistance, R2C, WithID } from "../../types"; -import { Response } from "express"; -import * as XLSX from "xlsx"; -import * as fs from "fs"; -import * as path from "path"; - - -export const streamXLSXFile = ( - res: Response, - wb: XLSX.WorkBook, - filename: string, -): void => { - const opts: XLSX.WritingOptions = { bookType: "xlsx", type: "binary" }; - - // it must be save to `/tmp` directory because it run on firebase - const pathToSave = path.join("/tmp", filename) - - // write file - XLSX.writeFile(wb, pathToSave, opts) - - // create read stream - const stream = fs.createReadStream(pathToSave); - - // prepare http header - res.setHeader( - "Content-Type", - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" - ); - res.setHeader("Content-Disposition", `attachment; filename="${filename}"`); - - stream.pipe(res); -} - - -export const getUnExportedR2RUsers = () => { - return admin - .firestore() - .collection(collection.r2rAssistance) - .where("isR2RExported", "==", false) - .get(); -}; - -/** - * @returns data of each snapshot with doc id included - */ -export const serializeData = (snapshot: QuerySnapshot) => { - const result: any[] = []; - snapshot.docs.forEach((doc) => { - const data = doc.data(); - data.id = doc.id; - result.push(data); - }); - - return result; -}; - -/** - * marked users from R2R collection as exported - */ -export const updateExportedR2RUsers = (snapshot: QuerySnapshot) => { - return Promise.all( - snapshot.docs.map((doc) => { - const ref = admin - .firestore() - .collection(collection.r2rAssistance) - .doc(doc.id); - - return ref.update({ - isR2RExported: true, - }); - }) - ); -}; - -export const getUnExportedR2CUsers = () => { - return admin - .firestore() - .collection(collection.patient) - .where("isRequestToCall", "==", true) - .where("isRequestToCallExported", "==", false) - .orderBy("lastUpdatedAt") - .get(); -}; - -export const get36hrsUsers = async () => { - const snapshot = await admin.firestore().collection(collection.patient).get(); - - const notUpdatedList: NotUpdatedList[] = []; - snapshot.forEach((doc) => { - const patient = doc.data() as Patient; - - const lastUpdatedDate = patient.lastUpdatedAt.toDate(); - const hours = Math.abs(new Date().getTime() - lastUpdatedDate.getTime()) / 36e5; - const includeStatus = [ - statusList["unknown"], - statusList["G1"], - statusList["G2"], - ]; - - if (includeStatus.includes(patient.status)) { - if (hours >= 36 && hours < 72) { - notUpdatedList.push({ - firstName: patient.firstName, - personalPhoneNo: patient.personalPhoneNo, - }); - } - } - }); - return notUpdatedList; -}; - - - -/** - * marked users from R2C collection as exported and return serialized data - * @returns serialized snapshot data - */ -export const updateAndSerializeR2CData = async (snapshot: QuerySnapshot) => { - const patientList: WithID[] = []; - await Promise.all( - snapshot.docs.map((doc) => { - // WARNING SIDE EFFECT inside map - const data = doc.data() as R2C; - const dataResult = makeR2CPayload(doc.id, data); - patientList.push(dataResult); - // end of side effects - - updateExportedR2CUser(doc.id); - }) - ); - - return patientList; -}; - -export const makeR2CPayload = (id: string, data: R2C) => { - return { - id, - firstName: data.firstName, - lastName: data.lastName, - hasCalled: 0, - personalPhoneNo: data.personalPhoneNo, - }; -}; - -export const updateExportedR2CUser = (id: string) => { - const ref = admin.firestore().collection(collection.patient).doc(id); - - ref.update({ - isRequestToCallExported: true, - }); -}; - -export const formatterR2R = (doc: WithID) => [doc.id, doc.name, doc.personalPhoneNo]; - -export const formatterR2C = (doc: WithID) => [ - doc.id, - doc.firstName, - doc.hasCalled, - `="${doc.personalPhoneNo}"`, -]; - -export const formatter36Hr = (doc: NotUpdatedList) => [doc.firstName, `="${doc.personalPhoneNo}"`] - -export const formatPatient = (doc: Patient) => [ - doc.personalID, - doc.firstName, - doc.lastName, - doc.personalPhoneNo, - doc.emergencyPhoneNo, -] diff --git a/functions/src/controller/importController/importController.ts b/functions/src/controller/importController/importController.ts deleted file mode 100644 index 688970c8..00000000 --- a/functions/src/controller/importController/importController.ts +++ /dev/null @@ -1,179 +0,0 @@ -import * as functions from "firebase-functions"; -import { - validateImportPatientIdSchema, - validateImportWhitelistSchema, - validateImportRequestToRegisterSchema, - ImportPatientIdType, - ImportRequestToRegisterType, - ImportWhitelistType, -} from "../../schema"; -import { admin, collection } from "../../init"; -import { success } from "../../response/success"; -import { OnCallHandler } from "../../types"; -import { WriteResult } from "@google-cloud/firestore"; - -type MapUser = { [key: string]: { status: number } } - -export const importFinishR2C: OnCallHandler = async (data, _context) => { - const { value, error } = validateImportPatientIdSchema(data); - if (error) { - console.log(error.details); - throw new functions.https.HttpsError( - "invalid-argument", - "ข้อมูลไม่ถูกต้อง", - error.details - ); - } - - const { users } = value; - const map: MapUser = {}; - for (const user of users) { - const { id, ...obj } = user; - map[user.id] = obj; - } - - const snapshot = await admin - .firestore() - .collection(collection.patient) - .where("isRequestToCall", "==", true) - .where("isRequestToCallExported", "==", true) - .get(); - - const legacyRef = admin - .firestore() - .collection(collection.legacyStat) - .doc("stat"); - - const batch = admin.firestore().batch(); - const promises: Promise[] = []; - snapshot.docs.forEach((doc) => { - const docRef = admin.firestore().collection(collection.patient).doc(doc.id); - // if user is not imported, there will not be updated - - if (!map[doc.id]) return; - const { status } = map[doc.id]; - switch (status) { - // not called - case 0: - batch.update(docRef, { - isRequestToCallExported: false, - }); - break; - - // has called - case 1: - batch.update(docRef, { - isRequestToCall: false, - isRequestToCallExported: false, - }); - break; - // out of system - case 99: - promises.push( - docRef - .get() - .then((result) => result.data()) - .then((docData) => { - const ref = admin - .firestore() - .collection(collection.legacyUser) - .doc(doc.id); - return { docData, ref }; - }) - .then(({ docData, ref }) => { - batch.set(ref, { - ...docData, - }); - batch.delete(docRef); - }) - ); - //increment Legacy user count - batch.update(legacyRef, { - count: admin.firestore.FieldValue.increment(1), - }); - break; - default: - return; - } - }); - - await Promise.all(promises); - await batch.commit(); - return success(); -}; - -export const importFinishR2R: OnCallHandler = async (data, _context) => { - const { value, error } = validateImportRequestToRegisterSchema(data); - if (error) { - console.log(error.details); - throw new functions.https.HttpsError( - "invalid-argument", - "ข้อมูลไม่ถูกต้อง", - error.details - ); - } - - const { users } = value; - const map: MapUser = {}; - for (const user of users) { - const { id, ...obj } = user; - map[user.id] = obj; - } - - const snapshot = await admin - .firestore() - .collection(collection.r2rAssistance) - .where("isR2RExported", "==", true) - .get(); - - const batch = admin.firestore().batch(); - snapshot.docs.forEach((doc) => { - // if user is not imported, there will not be updated - - if (!map[doc.id]) return; - const { status } = map[doc.id]; - const docRef = admin - .firestore() - .collection(collection.r2rAssistance) - .doc(doc.id); - - switch (status) { - // not called - case 0: - batch.update(docRef, { - isR2RExported: false, - }); - break; - default: - return; - } - }); - - await batch.commit(); - return success(); -}; - -export const importWhitelist: OnCallHandler = async (data, _context) => { - const { value, error } = validateImportWhitelistSchema(data); - if (error) { - console.log(error.details); - throw new functions.https.HttpsError( - "invalid-argument", - "ข้อมูลไม่ถูกต้อง", - error.details - ); - } - - const { users } = value; - - const promises: Promise[] = []; - users.forEach((user) => { - promises.push( - admin.firestore().collection("whitelist").doc(user.id).set({ - id: user.id, - }) - ); - }); - await Promise.all(promises); - return success(); -}; diff --git a/functions/src/controller/index.ts b/functions/src/controller/index.ts index 470333f0..29bfdf15 100644 --- a/functions/src/controller/index.ts +++ b/functions/src/controller/index.ts @@ -1,8 +1,5 @@ -export * as exportController from "./exportController" -export * as importController from "./importController/importController" export * as patientController from "./patientController" -export * as requestController from "./requestController" export * as firestoreController from "./firestoreController" export * as pubsub from "./pubsub" export * as dashboard from "./dashboard" \ No newline at end of file diff --git a/functions/src/controller/patientController/index.ts b/functions/src/controller/patientController/index.ts index 6a695d30..086d4428 100644 --- a/functions/src/controller/patientController/index.ts +++ b/functions/src/controller/patientController/index.ts @@ -1,11 +1,9 @@ import * as functions from "firebase-functions"; import { - validateRegisterSchema, validateGetProfileSchema, validateHistorySchema, //mon added this validateDeletePatientSchema, - RegisterType, DeletePatientType, GetProfileType, HistoryType, @@ -14,7 +12,7 @@ import { import { admin, collection } from "../../init"; import { success } from "../../response/success"; import { statusList } from "../../api/const" -import { convertTimestampToStr, TO_AMED_STATUS } from "../../utils" +import { convertTimestampToStr } from "../../utils" import config from "../../config" import * as utils from "./utils"; import { Patient, OnCallHandler } from "../../types"; @@ -24,7 +22,6 @@ import { Patient, OnCallHandler } from "../../types"; import { getProfile } from "../../middleware/authentication"; import { makeStatusAPIPayload, makeRequest } from "../../api"; import { sendPatientstatus } from "../../linefunctions/linepushmessage"; -import { notifyToLine } from "../../linenotify"; // Mon added this code const deletePatient = async (personalID: string) => { @@ -91,58 +88,6 @@ export const requestDeletePatient: OnCallHandler = async (dat }; // end of mon's code -export const registerPatient: OnCallHandler = async (data, _context) => { - const { value, error } = validateRegisterSchema(data); - - if (error) { - console.log(error.details); - throw new functions.https.HttpsError( - "invalid-argument", - "ข้อมูลไม่ถูกต้อง", - error.details - ); - } - - const { lineUserID, lineIDToken, noAuth, ...obj } = value; - const { error: authError } = await getProfile({ - lineUserID, - lineIDToken, - noAuth, - }); - if (authError) { - throw new functions.https.HttpsError("unauthenticated", "ไม่ได้รับอนุญาต"); - } - - const createdDate = new Date(); - const patientWithStatus = utils.setPatientStatus(obj, createdDate); - - //need db connection - const snapshot = await admin - .firestore() - .collection(collection.patient) - .doc(lineUserID) - .get(); - - const whitelist = await admin - .firestore() - .collection(collection.whitelist) - .doc(patientWithStatus.personalID) - .get(); - - if (!whitelist.exists) { - throw new functions.https.HttpsError( - "failed-precondition", - "You are not in our whitelist" - ); - } - - utils.snapshotExists(snapshot); - - //need db connection - await snapshot.ref.create(patientWithStatus); - - return success(`Registration with ID: ${lineUserID} added`); -}; export const getProfileHandler: OnCallHandler = async (data, _context) => { const { value, error } = validateGetProfileSchema(data); @@ -211,26 +156,9 @@ export const updateSymptom: OnCallHandler = async (data, _context) const date = new Date(); const createdTimeStamp = admin.firestore.Timestamp.fromDate(date); + // utils.updateSymptomCheckAmed(snapshotData); - //need db connection - const snapshot = await admin - .firestore() - .collection(collection.patient) - .doc(lineUserID) - .get(); - - utils.updateSymptomCheckUser(snapshot, lineUserID); - const snapshotData = snapshot.data() as Patient; - const { - followUp, - firstName, - lastName, - status: previousStatus, - } = snapshotData; - - utils.updateSymptomCheckAmed(snapshotData); - - const formPayload = makeStatusAPIPayload(snapshotData, obj); + const formPayload = makeStatusAPIPayload(obj); const { inclusion_label, inclusion_label_type, triage_score } = await makeRequest(formPayload); @@ -241,38 +169,28 @@ export const updateSymptom: OnCallHandler = async (data, _context) status, inclusion_label_type, triage_score, - createdTimeStamp, - previousStatus + createdTimeStamp ) + const snapshot = await admin + .firestore() + .collection(collection.patient) + .doc(lineUserID) + .get(); - const { createdDate, ...objWithOutCreatedDate } = followUpObj; - - if (!followUp) { - await snapshot.ref.set({ - ...objWithOutCreatedDate, + if (!snapshot.exists) { + await snapshot.ref.create({ followUp: [followUpObj], }); } else { await snapshot.ref.update({ - ...objWithOutCreatedDate, followUp: admin.firestore.FieldValue.arrayUnion(followUpObj), }); } - try { - if (TO_AMED_STATUS.includes(status)) { - await notifyToLine( - `ผู้ป่วย: ${firstName} ${lastName} มีการเปลี่ยนแปลงอาการฉุกเฉิน` - ); - } - } catch (err) { - console.log(err); - } - try { await sendPatientstatus( lineUserID, - objWithOutCreatedDate, + followUpObj, config.line.channelAccessToken ); } catch (err) { diff --git a/functions/src/controller/patientController/utils.test.ts b/functions/src/controller/patientController/utils.test.ts index 65718176..149b539c 100644 --- a/functions/src/controller/patientController/utils.test.ts +++ b/functions/src/controller/patientController/utils.test.ts @@ -1,11 +1,8 @@ import * as _ from "lodash"; import { statusList, statusListReverse } from "../../api/const"; -import { HistoryType, RegisterType } from "../../schema"; +import { HistoryType } from "../../schema"; import { Timestamp } from "@google-cloud/firestore"; -import * as faker from "faker" - import { - setPatientStatus, snapshotExists, updateSymptomAddCreatedDate, updateSymptomCheckUser, @@ -25,6 +22,17 @@ const randomEIHResult = () => { return results[randomInt(4)] } +const randomGender = (): "male" | "female" | "unknown" => { + const n = randomInt(3); + if (n === 0) { + return "male" + } else if (n === 2) { + return "female" + } else { + return "unknown" + } +} + const randomStatus = () => { const n = _.keys(statusListReverse).length return statusListReverse[randomInt(n)] @@ -32,6 +40,10 @@ const randomStatus = () => { const createMockFollowUpInput = (): Omit => { return { + age: randomInt(80), + gender: randomGender(), + height: randomInt(200), + weight: randomInt(100), sp_o2: randomInt(100), sp_o2_ra: randomInt(100), sp_o2_after_eih: randomInt(100), @@ -55,6 +67,23 @@ const createMockFollowUpInput = (): Omit { const status = statusList[inclusion_label] const timestamp = Timestamp.now() - const prevStatus = statusList["unknown"] const result = createFollowUpObj( mockFollowUpInput, status, inclusion_label_type, triage_score, - timestamp, - prevStatus + timestamp ) as { [key: string]: any } for (const [key, value] of _.entries(mockFollowUpInput)) { @@ -92,121 +119,10 @@ describe("createFollowUpObj", () => { expect(result["status_label_type"]).toEqual(inclusion_label_type) expect(result["lastUpdatedAt"]).toEqual(timestamp) expect(result["createdDate"]).toEqual(timestamp) - expect(result["toAmed"]).toBeDefined() }) - it("should set toAmed value to 1 if toAmed is true", () => { - const mockFollowUpInput = createMockFollowUpInput() - const status = statusList["R2"] - const inclusion_label_type = "at_least" - const triage_score = randomInt(150) - const timestamp = Timestamp.now() - const prevStatus = statusList["unknown"] - const result = createFollowUpObj( - mockFollowUpInput, - status, - inclusion_label_type, - triage_score, - timestamp, - prevStatus - ) as { [key: string]: any } - expect(result["toAmed"]).toEqual(1) - }) - - it("should set toAmed value to 0 if toAmed is true", () => { - const mockFollowUpInput = createMockFollowUpInput() - const status = statusList["G2"] - const inclusion_label_type = "at_least" - const triage_score = randomInt(150) - const timestamp = Timestamp.now() - const prevStatus = statusList["unknown"] - const result = createFollowUpObj( - mockFollowUpInput, - status, - inclusion_label_type, - triage_score, - timestamp, - prevStatus - ) as { [key: string]: any } - expect(result["toAmed"]).toEqual(0) - }) }) -describe("setPatientStatus", () => { - const createMockPatientObj = (): Omit => { - return { - firstName: faker.name.firstName(), - lastName: faker.name.lastName(), - - birthDate: faker.date.past(), - weight: randomInt(80), - height: randomInt(150), - gender: "male", - - address: faker.address.streetAddress(), - province: faker.address.cityName(), - prefecture: faker.address.streetName(), //อำเภอ - district: faker.address.state(), //ตำบล - postNo: faker.address.zipCode(), - - personalPhoneNo: faker.phone.phoneNumber(), - emergencyPhoneNo: faker.phone.phoneNumber(), - - hasHelper: faker.datatype.boolean(), - digitalLiteracy: faker.datatype.boolean(), - - - gotFavipiravir: randomInt(), - - // โรคประจำตัว - rf_copd_chronic_lung_disease: randomInt(), - - rf_ckd_stagr_3_to_4: randomInt(), - rf_chronic_heart_disease: randomInt(), - rf_cva: randomInt(), - rf_t2dm: randomInt(), - rf_cirrhosis: randomInt(), - rf_immunocompromise: randomInt(), - - fac_diabetes: randomInt(), - fac_dyslipidemia: randomInt(), - fac_hypertension: randomInt(), - fac_heart_diseases: randomInt(), - fac_esrd: randomInt(), - fac_cancer: randomInt(), - fac_tuberculosis: randomInt(), - fac_hiv: randomInt(), - fac_asthma: randomInt(), - fac_pregnancy: randomInt(), - - // optional - personalID: "1111111111111", - } - } - - it("should setPatientStatus correctly", () => { - - const createdDate = new Date(); - const mockObj = createMockPatientObj() - - const result = setPatientStatus(mockObj, createdDate); - expect(result).toEqual({ - ...mockObj, - status: 0, - needFollowUp: true, - followUp: [], - createdDate: admin.firestore.Timestamp.fromDate(createdDate), - lastUpdatedAt: admin.firestore.Timestamp.fromDate(createdDate), - birthDate: admin.firestore.Timestamp.fromDate(mockObj.birthDate), - - isRequestToCallExported: false, - isRequestToCall: false, - isNurseExported: false, - toAmed: 0, - }); - }); -}); - describe("snapshotExists", () => { it("throw amed", () => { function checkExists() { diff --git a/functions/src/controller/patientController/utils.ts b/functions/src/controller/patientController/utils.ts index 6ddcfc55..73266f9f 100644 --- a/functions/src/controller/patientController/utils.ts +++ b/functions/src/controller/patientController/utils.ts @@ -1,43 +1,17 @@ -import { admin } from "../../init"; import * as functions from "firebase-functions"; -import { RegisterType, HistoryType } from '../../schema'; -import { Patient, UpdatedPatient } from '../../types' -import { TO_AMED_STATUS } from "../../utils" +import { HistoryType } from '../../schema'; +import { UpdatedPatient } from '../../types' import { DocumentSnapshot } from "firebase-functions/v1/firestore"; -export const setPatientStatus = (obj: Omit, createdDate: Date): Patient => { - const createdTimestamp = admin.firestore.Timestamp.fromDate(createdDate); - const birthDateTimestamp = admin.firestore.Timestamp.fromDate(obj.birthDate) - - return { - status: 0, - needFollowUp: true, - followUp: [], - createdDate: createdTimestamp, - lastUpdatedAt: createdTimestamp, - isRequestToCallExported: false, - isRequestToCall: false, - isNurseExported: false, - toAmed: 0, - ...obj, - birthDate: birthDateTimestamp - } -}; - - export const createFollowUpObj = ( obj: Omit, status: number, inclusion_label_type: string, triage_score: number, createdTimeStamp: any, - prevStatus: number, ): UpdatedPatient => { - // set To Amed Status - const toAmed = checkAmedStatus(status, prevStatus, TO_AMED_STATUS) - // update other status return { ...obj, @@ -46,15 +20,10 @@ export const createFollowUpObj = ( status_label_type: inclusion_label_type, lastUpdatedAt: createdTimeStamp, createdDate: createdTimeStamp, - toAmed: toAmed ? 1 : 0 } } -const checkAmedStatus = (status: number, prevStatus: number, TO_AMED_STATUS: any,): boolean => { - return status !== prevStatus && TO_AMED_STATUS.includes(status) -} - export const snapshotExists = (snapshot: DocumentSnapshot) => { if (snapshot.exists) { if (snapshot.data()?.toAmed === 1) { diff --git a/functions/src/controller/pubsub/utils.ts b/functions/src/controller/pubsub/utils.ts index 93081c05..4cd57aa3 100644 --- a/functions/src/controller/pubsub/utils.ts +++ b/functions/src/controller/pubsub/utils.ts @@ -1,6 +1,6 @@ -import { get36hrsUsers } from "../exportController/utils"; import { statusList } from "../../api/const"; import { admin, collection } from "../../init"; +import { NotUpdatedList, Patient } from "../../types"; export const calculateHours = (currentDate: Date, lastUpdatedDate: Date) => { return Math.abs(currentDate.getTime() - lastUpdatedDate.getTime()) / 36e5; @@ -39,4 +39,31 @@ export const getActiveUser = async () => { return notUpdatedList.length; } +export const get36hrsUsers = async () => { + const snapshot = await admin.firestore().collection(collection.patient).get(); + + const notUpdatedList: NotUpdatedList[] = []; + snapshot.forEach((doc) => { + const patient = doc.data() as Patient; + + const lastUpdatedDate = patient.lastUpdatedAt.toDate(); + const hours = Math.abs(new Date().getTime() - lastUpdatedDate.getTime()) / 36e5; + const includeStatus = [ + statusList["unknown"], + statusList["G1"], + statusList["G2"], + ]; + + if (includeStatus.includes(patient.status)) { + if (hours >= 36 && hours < 72) { + notUpdatedList.push({ + firstName: patient.firstName, + personalPhoneNo: patient.personalPhoneNo, + }); + } + } + }); + return notUpdatedList; +}; + diff --git a/functions/src/controller/requestController/index.ts b/functions/src/controller/requestController/index.ts deleted file mode 100644 index cc3c4823..00000000 --- a/functions/src/controller/requestController/index.ts +++ /dev/null @@ -1,126 +0,0 @@ -import * as functions from "firebase-functions"; -// import { getProfile } from "../../middleware/authentication"; -import { getProfile } from "../../middleware/authentication"; -import { - GetProfileType, - RequestToRegisterType, - validateGetProfileSchema, - validateRequestToRegisterSchema, -} from "../../schema"; -import { admin, collection } from "../../init"; -import { success } from "../../response/success"; -import { OnCallHandler, Patient, R2RAssistance } from "../../types"; -import { incrementR2CUser } from "./utils"; - -export const requestToCall: OnCallHandler = async (data, _context) => { - const { value, error } = validateGetProfileSchema(data); - if (error) { - console.log(error.details); - throw new functions.https.HttpsError( - "invalid-argument", - "ข้อมูลไม่ถูกต้อง", - error.details - ); - } - - const { lineUserID, lineIDToken, noAuth } = value; - const { error: authError } = await getProfile({ - lineUserID, - lineIDToken, - noAuth, - }); - if (authError) { - throw new functions.https.HttpsError("unauthenticated", "ไม่ได้รับอนุญาต"); - } - - const snapshot = await admin - .firestore() - .collection(collection.patient) - .doc(lineUserID) - .get(); - if (!snapshot.exists) { - throw new functions.https.HttpsError( - "not-found", - `ไม่พบผู้ใช้ ${lineUserID}` - ); - } - const patient = snapshot.data() as Patient; - if (patient.toAmed === 1) { - throw new functions.https.HttpsError( - "failed-precondition", - "your information is already handle by Amed" - ); - } - - const { isRequestToCall } = patient; - - if (isRequestToCall) { - return success(`userID: ${lineUserID} has already requested to call`); - } - - await incrementR2CUser(); - - await snapshot.ref.update({ - isRequestToCall: true, - isRequestToCallExported: false, - }); - return success(); -}; - -export const requestToRegister: OnCallHandler = async (data, _context) => { - const { value, error } = validateRequestToRegisterSchema(data); - if (error) { - console.log(error.details); - throw new functions.https.HttpsError( - "invalid-argument", - "ข้อมูลไม่ถูกต้อง", - error.details - ); - } - - const { lineUserID, lineIDToken, noAuth } = value; - const { data: lineProfile, error: authError } = await getProfile({ - lineUserID, - lineIDToken, - noAuth, - }); - if (authError) { - throw new functions.https.HttpsError( - "unauthenticated", - lineProfile.error_description - ); - } - - const snapshot = await admin - .firestore() - .collection(collection.patient) - .doc(value.lineUserID) - .get(); - - if (snapshot.exists) { - throw new functions.https.HttpsError( - "failed-precondition", - `ผู้ใช้ ${lineUserID} ลงทะเบียนในระบบแล้ว ไม่จำเป็นต้องขอรับความช่วยเหลือในการลงทะเบียน` - ); - } else { - const requestRegisterSnapshot = await admin - .firestore() - .collection(collection.r2rAssistance) - .doc(lineUserID) - .get(); - - if (requestRegisterSnapshot.exists) { - throw new functions.https.HttpsError( - "already-exists", - `มีข้อมูลผู้ใช้ ${lineUserID} ในรายชื่อการโทรแล้ว` - ); - } - const obj: R2RAssistance = { - name: value.name, - personalPhoneNo: value.personalPhoneNo, - isR2RExported: false, - }; - await requestRegisterSnapshot.ref.create(obj); - return success(); - } -}; diff --git a/functions/src/controller/requestController/utils.ts b/functions/src/controller/requestController/utils.ts deleted file mode 100644 index 4f5955fb..00000000 --- a/functions/src/controller/requestController/utils.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { admin, collection } from "../../init"; -import { getDateID } from "../../utils"; - -export const incrementR2CUser = async () => { - const id = getDateID(); - - const snapshot = await admin - .firestore() - .collection(collection.timeSeries) - .doc(id) - .get(); - - if (!snapshot.exists) { - return snapshot.ref.create({ r2ccount: 1 }); - } else { - return snapshot.ref.update( - "r2ccount", - admin.firestore.FieldValue.increment(1) - ); - } -}; - -export const incrementLegacyUser = async () => { - const snapshot = await admin - .firestore() - .collection(collection.legacyStat) - .doc("stat") - .get(); - - if (!snapshot.exists) { - return snapshot.ref.create({ count: 1 }); - } else { - return snapshot.ref.update( - "count", - admin.firestore.FieldValue.increment(1) - ); - } -}; - diff --git a/functions/src/index.test.ts b/functions/src/index.test.ts index fe982e5d..803a753c 100644 --- a/functions/src/index.test.ts +++ b/functions/src/index.test.ts @@ -37,9 +37,6 @@ describe("endpoints", () => { }), })); - jest.doMock("./backup", () => ({ - backup: jest.fn(), - })); require("."); it("should run something useful (will implement in future", () => { diff --git a/functions/src/index.ts b/functions/src/index.ts index ac7248b6..d0a6ae92 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -2,7 +2,6 @@ import * as functions from "firebase-functions"; import { authenticateVolunteer, - authenticateVolunteerRequest, } from "./middleware/authentication"; import { admin, initializeApp } from "./init"; import { eventHandler } from "./handler/eventHandler"; @@ -12,12 +11,8 @@ import config from "./config" import { success } from "./response/success"; import * as express from "express"; import * as cors from "cors"; -import { backup } from "./backup"; import { - exportController, patientController, - requestController, - importController, pubsub, firestoreController, dashboard @@ -33,31 +28,8 @@ const app = express(); app.use(cors({ origin: true })); -// The Firebase Admin SDK to access Firestore. initializeApp(); -// Take the text parameter passed to this HTTP endpoint and insert it into -// Firestore under the path /messages/:documentId/original - -// app.get("/master", exportController.exportMasterAddress); - -// app.get("/patient", exportController.exportAllPatient); - -// app.get( -// "/", -// exportController.exportPatientForNurse -// ); - -app.get( - "/exportPatientForNurse", - authenticateVolunteerRequest(exportController.exportPatientForNurse) -); - -app.get( - "/exportTimeSeries", - authenticateVolunteerRequest(exportController.exportTimeSeries) -); - export const webhook = functions.region(region).https.onRequest(async (req, res) => { res.sendStatus(200); try { @@ -70,8 +42,6 @@ export const webhook = functions.region(region).https.onRequest(async (req, res) const profile: line.Profile = await client.getProfile(userId); const userObject: UserObject = { userId: userId, profile: profile }; - console.log(userObject); - // console.log(event) await eventHandler(event, userObject, client); } catch (err) { console.error(err); @@ -83,63 +53,18 @@ export const deletePatient = functions .region(region) .https.onCall(authenticateVolunteer(patientController.requestDeletePatient)); -export const registerParticipant = functions - .region(region) - .https.onCall(patientController.registerPatient); - export const getProfile = functions .region(region) .https.onCall(patientController.getProfileHandler); -export const exportRequestToRegister = functions - .region(region) - .https.onCall(authenticateVolunteer(exportController.exportR2R)); -export const export36hrs = functions - .region(region) - .https.onCall(authenticateVolunteer(exportController.export36hrs)); - -export const exportRequestToCall = functions - .region(region) - .https.onCall(authenticateVolunteer(exportController.exportR2C)); - -export const exportRequestToCallDayOne = functions - .region(region) - .https.onCall( - authenticateVolunteer(exportController.exportRequestToCallDayOne) - ); - -export const importFinishedRequestToCall = functions - .region(region) - .https.onCall(authenticateVolunteer(importController.importFinishR2C)); -export const importFinishedRequestToRegister = functions - .region(region) - .https.onCall(authenticateVolunteer(importController.importFinishR2R)); -export const importWhitelist = functions - .region(region) - .https.onCall(authenticateVolunteer(importController.importWhitelist)); - -export const thisEndpointNeedsAuth = functions.region(region).https.onCall( - authenticateVolunteer(async (data: any, context: functions.https.CallableContext) => { - return { result: `Content for authorized user` }; - }) -); - - export const accumulativeData = functions .region(region) .https.onCall(authenticateVolunteer(dashboard.getAccumulative)); - export const resetUserCount = functions .region(region) .https.onCall(authenticateVolunteer(dashboard.resetUserCount)); -export const backupFirestore = functions - .region(region) - .pubsub.schedule("every day 18:00") - .timeZone("Asia/Bangkok") - .onRun(backup); - export const updateTimeSeries = functions .region(region) .pubsub.schedule("every day 23:59") @@ -172,17 +97,11 @@ export const getNumberOfPatientsV2 = functions res.status(200).json(success(data.count)); }); -export const requestToRegister = functions - .region(region) - .https.onCall(requestController.requestToRegister); export const check = functions.region(region).https.onRequest(async (req, res) => { res.sendStatus(200); }); -export const requestToCall = functions - .region(region) - .https.onCall(requestController.requestToCall); export const updateSymptom = functions .region(region) diff --git a/functions/src/middleware/authentication.ts b/functions/src/middleware/authentication.ts index a4fec2c9..3e325f65 100644 --- a/functions/src/middleware/authentication.ts +++ b/functions/src/middleware/authentication.ts @@ -1,5 +1,5 @@ import { admin } from "../init"; -import axios from "axios"; +import axios, { AxiosError } from "axios"; import * as functions from "firebase-functions"; import { LineCredential, OnCallHandler } from "../types"; @@ -115,6 +115,6 @@ export const getProfile = async (data: LineCredential) => { const userProfile = response.data; return { data: userProfile, error: false }; } catch (e) { - return { data: e.response.data, error: true }; + return { data: (e as AxiosError).response?.data, error: true }; } }; diff --git a/functions/src/schema/ExportRequestToCallSchema.ts b/functions/src/schema/ExportRequestToCallSchema.ts deleted file mode 100644 index 1bcdeb44..00000000 --- a/functions/src/schema/ExportRequestToCallSchema.ts +++ /dev/null @@ -1,8 +0,0 @@ -import * as Joi from 'joi'; - -export const ExportRequestToCallSchema = Joi.object({ - volunteerSize: Joi.number().required(), - noAuth: Joi.boolean(), -}); - -export type ExportRequestToCallType = Joi.extractType; diff --git a/functions/src/schema/HistorySchema.ts b/functions/src/schema/HistorySchema.ts index 5c1d1401..3485cbd0 100644 --- a/functions/src/schema/HistorySchema.ts +++ b/functions/src/schema/HistorySchema.ts @@ -16,6 +16,10 @@ export const HistorySchema = Joi.object({ //height //weight //infected discover date + age: Joi.number().required(), + weight: Joi.number().required(), + height: Joi.number().required(), + gender: Joi.string().valid("male", "female", "unknown").required(), sp_o2: Joi.number().default(100), sp_o2_ra: Joi.number().default(100), sp_o2_after_eih: Joi.number().default(100), @@ -47,16 +51,25 @@ export const HistorySchema = Joi.object({ sym2_red_eye: Joi.number().allow(0, 1).required(), //----Should be in register---- - // fac_diabetes: Joi.number().allow(0, 1).required(), - // fac_dyslipidemia: Joi.number().allow(0, 1).required(), - // fac_hypertension: Joi.number().allow(0, 1).required(), - // fac_heart_diseases: Joi.number().allow(0, 1).required(), - // fac_esrd: Joi.number().allow(0, 1).required(), - // fac_cancer: Joi.number().allow(0, 1).required(), - // fac_tuberculosis: Joi.number().allow(0, 1).required(), - // fac_hiv: Joi.number().allow(0, 1).required(), - // fac_asthma: Joi.number().allow(0, 1).required(), - // fac_pregnancy: Joi.number().allow(0, 1).required(), + rf_copd_chronic_lung_disease: Joi.number().allow(0, 1).required(), + + rf_ckd_stagr_3_to_4: Joi.number().allow(0, 1).required(), + rf_chronic_heart_disease: Joi.number().allow(0, 1).required(), + rf_cva: Joi.number().allow(0, 1).required(), + rf_t2dm: Joi.number().allow(0, 1).required(), + rf_cirrhosis: Joi.number().allow(0, 1).required(), + rf_immunocompromise: Joi.number().allow(0, 1).required(), + + fac_diabetes: Joi.number().allow(0, 1).required(), + fac_dyslipidemia: Joi.number().allow(0, 1).required(), + fac_hypertension: Joi.number().allow(0, 1).required(), + fac_heart_diseases: Joi.number().allow(0, 1).required(), + fac_esrd: Joi.number().allow(0, 1).required(), + fac_cancer: Joi.number().allow(0, 1).required(), + fac_tuberculosis: Joi.number().allow(0, 1).required(), + fac_hiv: Joi.number().allow(0, 1).required(), + fac_asthma: Joi.number().allow(0, 1).required(), + fac_pregnancy: Joi.number().allow(0, 1).required(), fac_bed_ridden_status: Joi.number().allow(0, 1).required(), fac_uri_symptoms: Joi.number().allow(0, 1).required(), fac_olfactory_symptoms: Joi.number().allow(0, 1).required(), diff --git a/functions/src/schema/ImportPatientIdSchema.ts b/functions/src/schema/ImportPatientIdSchema.ts deleted file mode 100644 index ca150879..00000000 --- a/functions/src/schema/ImportPatientIdSchema.ts +++ /dev/null @@ -1,22 +0,0 @@ -import * as Joi from 'joi'; - -export const ImportPatientIdSchema = Joi.object({ - users: Joi.array() - .items( - Joi.object({ - id: Joi.string().required(), - status: Joi.number().valid(0, 1, 99).required(), - reason: Joi.when("status", { - is: 99, - then: Joi.string(), - }), - }) - ) - .unique((a, b) => a.id === b.id) - .required(), - // ids: Joi.array().items(Joi.string()).required(), - noAuth: Joi.boolean(), -}); - -export type ImportPatientIdType = Joi.extractType; - diff --git a/functions/src/schema/ImportRequestToRegisterSchema.ts b/functions/src/schema/ImportRequestToRegisterSchema.ts deleted file mode 100644 index 560b8eb8..00000000 --- a/functions/src/schema/ImportRequestToRegisterSchema.ts +++ /dev/null @@ -1,18 +0,0 @@ -import * as Joi from 'joi'; - -export const ImportRequestToRegisterSchema = Joi.object({ - users: Joi.array() - .items( - Joi.object({ - id: Joi.string().required(), - status: Joi.number().valid(0, 1).required(), - }) - ) - .unique((a, b) => a.id === b.id) - .required(), - // ids: Joi.array().items(Joi.string()).required(), - noAuth: Joi.boolean(), -}); - -export type ImportRequestToRegisterType = Joi.extractType; - diff --git a/functions/src/schema/ImportWhitelistSchema.ts b/functions/src/schema/ImportWhitelistSchema.ts deleted file mode 100644 index 79d613c7..00000000 --- a/functions/src/schema/ImportWhitelistSchema.ts +++ /dev/null @@ -1,18 +0,0 @@ -import * as Joi from 'joi'; - -export const ImportWhitelistSchema = Joi.object({ - users: Joi.array() - .items( - Joi.object({ - id: Joi.string().length(13).required(), - }) - ) - .unique((a, b) => a.id === b.id) - .required(), - // ids: Joi.array().items(Joi.string()).required(), - noAuth: Joi.boolean(), -}); - - -export type ImportWhitelistType = Joi.extractType; - diff --git a/functions/src/schema/RegisterSchema.ts b/functions/src/schema/RegisterSchema.ts deleted file mode 100644 index cda01e7f..00000000 --- a/functions/src/schema/RegisterSchema.ts +++ /dev/null @@ -1,75 +0,0 @@ -import * as Joi from 'joi'; - -export const RegisterSchema = Joi.object({ - firstName: Joi.string().min(1).required(), - lastName: Joi.string().min(1).required(), - - birthDate: Joi.date().max("now").required(), - weight: Joi.number().required(), - height: Joi.number().required(), - gender: Joi.string().valid("male", "female", "unknown").required(), - - address: Joi.string().required(), - province: Joi.string().required(), - prefecture: Joi.string().required(), //อำเภอ - district: Joi.string().required(), //ตำบล - postNo: Joi.string().length(5).required(), - - personalPhoneNo: Joi.string().required(), - lineIDToken: Joi.string().required(), - lineUserID: Joi.string().required(), - emergencyPhoneNo: Joi.string().required(), - - hasHelper: Joi.boolean().default(false), - digitalLiteracy: Joi.boolean().default(false), - - //not in front-end yet na - // station: Joi.string().required(), - - gotFavipiravir: Joi.number().allow(0, 1).required(), - - // โรคประจำตัว - rf_copd_chronic_lung_disease: Joi.number().allow(0, 1).required(), - - rf_ckd_stagr_3_to_4: Joi.number().allow(0, 1).required(), - rf_chronic_heart_disease: Joi.number().allow(0, 1).required(), - rf_cva: Joi.number().allow(0, 1).required(), - rf_t2dm: Joi.number().allow(0, 1).required(), - rf_cirrhosis: Joi.number().allow(0, 1).required(), - rf_immunocompromise: Joi.number().allow(0, 1).required(), - - fac_diabetes: Joi.number().allow(0, 1).required(), - fac_dyslipidemia: Joi.number().allow(0, 1).required(), - fac_hypertension: Joi.number().allow(0, 1).required(), - fac_heart_diseases: Joi.number().allow(0, 1).required(), - fac_esrd: Joi.number().allow(0, 1).required(), - fac_cancer: Joi.number().allow(0, 1).required(), - fac_tuberculosis: Joi.number().allow(0, 1).required(), - fac_hiv: Joi.number().allow(0, 1).required(), - fac_asthma: Joi.number().allow(0, 1).required(), - fac_pregnancy: Joi.number().allow(0, 1).required(), - - // optional - personalID: Joi.string().length(13).required(), - passport: Joi.string() - .min(7) - .max(9) - .allow(null) - .when("personalID", { - is: null, - then: Joi.string().min(7).max(9).required(), - }), - - dose1Name: Joi.string().allow("", null), - dose1Date: Joi.date().max("now").allow("", null), - dose2Name: Joi.string().allow("", null), - dose2Date: Joi.date() - .greater(Joi.ref("dose1Date")) - .max("now") - .allow("", null), - favipiraviaAmount: Joi.number().allow("", null), - noAuth: Joi.boolean(), -}); - - -export type RegisterType = Joi.extractType; diff --git a/functions/src/schema/RequestToRegisterSchema.ts b/functions/src/schema/RequestToRegisterSchema.ts deleted file mode 100644 index 19ba2f08..00000000 --- a/functions/src/schema/RequestToRegisterSchema.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as Joi from 'joi'; - -export const RequestToRegisterSchema = Joi.object({ - lineIDToken: Joi.string().required(), // TODO: get from authentication - lineUserID: Joi.string().required(), - noAuth: Joi.boolean(), - name: Joi.string().required(), - personalPhoneNo: Joi.string().required(), -}); - -export type RequestToRegisterType = Joi.extractType; \ No newline at end of file diff --git a/functions/src/schema/index.ts b/functions/src/schema/index.ts index f32651a6..311acb41 100644 --- a/functions/src/schema/index.ts +++ b/functions/src/schema/index.ts @@ -1,24 +1,12 @@ import { DeletePatientSchema, DeletePatientType } from './DeletePatientSchema' -import { ExportRequestToCallSchema, ExportRequestToCallType } from './ExportRequestToCallSchema' import { HistorySchema, HistoryType } from './HistorySchema' -import { RegisterSchema, RegisterType } from './RegisterSchema' import { GetProfileSchema, GetProfileType } from './GetProfileSchema' -import { RequestToRegisterSchema, RequestToRegisterType } from './RequestToRegisterSchema' -import { ImportPatientIdSchema, ImportPatientIdType } from './ImportPatientIdSchema' -import { ImportRequestToRegisterSchema, ImportRequestToRegisterType } from './ImportRequestToRegisterSchema' -import { ImportWhitelistSchema, ImportWhitelistType } from './ImportWhitelistSchema' import { ValidationResult } from 'joi' type Result = Omit & { value: T } export const validateDeletePatientSchema = (data: DeletePatientType): Result => DeletePatientSchema.validate(data) -export const validateExportRequestToCallSchema = (data: ExportRequestToCallType): Result => ExportRequestToCallSchema.validate(data) export const validateHistorySchema = (data: HistoryType): Result => HistorySchema.validate(data) -export const validateRegisterSchema = (data: RegisterType): Result => RegisterSchema.validate(data) export const validateGetProfileSchema = (data: GetProfileType): Result => GetProfileSchema.validate(data) -export const validateRequestToRegisterSchema = (data: RequestToRegisterType): Result => RequestToRegisterSchema.validate(data) -export const validateImportPatientIdSchema = (data: ImportPatientIdType): Result => ImportPatientIdSchema.validate(data) -export const validateImportRequestToRegisterSchema = (data: ImportRequestToRegisterType): Result => ImportRequestToRegisterSchema.validate(data) -export const validateImportWhitelistSchema = (data: ImportWhitelistType): Result => ImportWhitelistSchema.validate(data) -export { DeletePatientType, ExportRequestToCallType, HistoryType, RegisterType, GetProfileType, RequestToRegisterType, ImportPatientIdType, ImportRequestToRegisterType, ImportWhitelistType } \ No newline at end of file +export { DeletePatientType, HistoryType, GetProfileType } \ No newline at end of file diff --git a/functions/src/types/patient.ts b/functions/src/types/patient.ts index 2e31b122..ef16a2b0 100644 --- a/functions/src/types/patient.ts +++ b/functions/src/types/patient.ts @@ -18,7 +18,6 @@ export type Patient = Partial & { export type UpdatedPatient = { - toAmed: number status: number triage_score: number status_label_type: string From 69ef15c8e5499d744ab800d3973d261c0e890bad Mon Sep 17 00:00:00 2001 From: palsp Date: Sat, 11 Sep 2021 10:59:32 +0700 Subject: [PATCH 22/34] feat: remove export 36 hr --- .../controller/firestoreController/index.ts | 24 ++++++-------- functions/src/controller/pubsub/index.ts | 5 +-- functions/src/controller/pubsub/utils.ts | 31 ----------------- .../src/handler/subhandler/messageHandler.ts | 4 --- .../src/linefunctions/requestCallHandler.ts | 33 ------------------- functions/src/types/patient.ts | 18 +++------- functions/src/types/r2r.ts | 4 --- 7 files changed, 15 insertions(+), 104 deletions(-) delete mode 100644 functions/src/linefunctions/requestCallHandler.ts diff --git a/functions/src/controller/firestoreController/index.ts b/functions/src/controller/firestoreController/index.ts index c55d4f16..b15c0106 100644 --- a/functions/src/controller/firestoreController/index.ts +++ b/functions/src/controller/firestoreController/index.ts @@ -12,7 +12,7 @@ export const onRegisterPatient: OnCreateHandler = async (snapshot, _context) => const data = snapshot.data() as Patient await utils.incrementTotalPatientCount(batch) - await utils.incrementTotalPatientCountByStatus(batch, statusListReverse[data.status]) + await utils.incrementTotalPatientCountByStatus(batch, statusListReverse[data.followUp[data.followUp.length - 1].status]) await batch.commit() @@ -26,19 +26,19 @@ export const onUpdatePatient: OnUpdateHandler = async (change, _context) => { const batch = admin.firestore().batch(); const prevData = change.before.data() as Patient; + const prevStatus = prevData.followUp[prevData.followUp.length - 1].status; + + const currentData = change.after.data() as Patient; + const currentStatus = currentData.followUp[currentData.followUp.length - 1].status; // if the change is relevant to update symptom - if (prevData.status !== currentData.status) { + if (prevStatus !== currentStatus) { // decrement from old color - await utils.decrementTotalPatientCountByStatus(batch, statusListReverse[prevData.status]) + await utils.decrementTotalPatientCountByStatus(batch, statusListReverse[prevStatus]) // increment new color - await utils.incrementTotalPatientCountByStatus(batch, statusListReverse[currentData.status]) - - if (currentData.toAmed === 1) { - await utils.decrementTotalPatientCount(batch); - } + await utils.incrementTotalPatientCountByStatus(batch, statusListReverse[currentStatus]) await batch.commit(); } @@ -49,15 +49,11 @@ export const onUpdatePatient: OnUpdateHandler = async (change, _context) => { export const onDeletePatient: OnDeleteHandler = async (snapshot, _context) => { const data = snapshot.data() as Patient + const currentStatus = data.followUp[data.followUp.length - 1].status; try { const batch = admin.firestore().batch(); - // if the patient is not sent to amed yet, - // additional decrement total patient count is required - if (data.toAmed === 0) { - await utils.decrementTotalPatientCount(batch); - } - await utils.decrementTotalPatientCountByStatus(batch, statusListReverse[data.status]); + await utils.decrementTotalPatientCountByStatus(batch, statusListReverse[currentStatus]); await utils.incrementTerminateUser(batch); diff --git a/functions/src/controller/pubsub/index.ts b/functions/src/controller/pubsub/index.ts index ec3369d7..bad16a33 100644 --- a/functions/src/controller/pubsub/index.ts +++ b/functions/src/controller/pubsub/index.ts @@ -11,9 +11,8 @@ export const updateTimeSeries = async () => { .doc(id) .get(); - const [dropOffRate, btw36hrsto72hrs, activeUser] = await Promise.all([ + const [dropOffRate, activeUser] = await Promise.all([ calculateDropOffRate(), - utils.getnumberusersbtw36hrsto72hrs(), utils.getActiveUser() ]); @@ -21,14 +20,12 @@ export const updateTimeSeries = async () => { await snapshot.ref.create({ r2ccount: 0, dropoffrate: dropOffRate, - usersbtw36hrsto72hrs: btw36hrsto72hrs, activeUser: activeUser, terminateUser: 0 }); } else { await snapshot.ref.update({ dropoffrate: dropOffRate, - usersbtw36hrsto72hrs: btw36hrsto72hrs, activeUser: activeUser }); } diff --git a/functions/src/controller/pubsub/utils.ts b/functions/src/controller/pubsub/utils.ts index 4cd57aa3..57f73fc3 100644 --- a/functions/src/controller/pubsub/utils.ts +++ b/functions/src/controller/pubsub/utils.ts @@ -1,16 +1,10 @@ import { statusList } from "../../api/const"; import { admin, collection } from "../../init"; -import { NotUpdatedList, Patient } from "../../types"; export const calculateHours = (currentDate: Date, lastUpdatedDate: Date) => { return Math.abs(currentDate.getTime() - lastUpdatedDate.getTime()) / 36e5; }; -export const getnumberusersbtw36hrsto72hrs = async () => { - const temp_notUpdatedList = await get36hrsUsers(); - return temp_notUpdatedList.length; -}; - export const getActiveUser = async () => { const snapshot = await admin.firestore().collection(collection.patient).get(); @@ -39,31 +33,6 @@ export const getActiveUser = async () => { return notUpdatedList.length; } -export const get36hrsUsers = async () => { - const snapshot = await admin.firestore().collection(collection.patient).get(); - - const notUpdatedList: NotUpdatedList[] = []; - snapshot.forEach((doc) => { - const patient = doc.data() as Patient; - const lastUpdatedDate = patient.lastUpdatedAt.toDate(); - const hours = Math.abs(new Date().getTime() - lastUpdatedDate.getTime()) / 36e5; - const includeStatus = [ - statusList["unknown"], - statusList["G1"], - statusList["G2"], - ]; - - if (includeStatus.includes(patient.status)) { - if (hours >= 36 && hours < 72) { - notUpdatedList.push({ - firstName: patient.firstName, - personalPhoneNo: patient.personalPhoneNo, - }); - } - } - }); - return notUpdatedList; -}; diff --git a/functions/src/handler/subhandler/messageHandler.ts b/functions/src/handler/subhandler/messageHandler.ts index 9c5518a3..99839839 100644 --- a/functions/src/handler/subhandler/messageHandler.ts +++ b/functions/src/handler/subhandler/messageHandler.ts @@ -1,5 +1,4 @@ import { jsonController } from "../jsonHandler"; -import { requestCall } from "../../linefunctions/requestCallHandler"; import { requestGuide } from "../../linefunctions/requestGuideHandler"; import { LineHandler } from "../../types"; import { MessageEvent, TextEventMessage } from "@line/bot-sdk" @@ -32,9 +31,6 @@ export const handleMessage: LineHandler = async (event, userObject case "สอนการใช้งาน": await requestGuide(userObject, client, replyToken); break; - case "ติดต่ออาสาสมัคร": - await requestCall(userObject, client, replyToken); - break; default: await client.replyMessage(replyToken, jsonController("defaultReply")); break; diff --git a/functions/src/linefunctions/requestCallHandler.ts b/functions/src/linefunctions/requestCallHandler.ts deleted file mode 100644 index 8fb05c8f..00000000 --- a/functions/src/linefunctions/requestCallHandler.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { admin } from "../init"; -import { jsonController } from "../handler/jsonHandler"; -import { success } from "../response/success"; -import { Patient } from "../types"; - -export const requestCall = async (userObject: any, client: any, replyToken: any) => { - const snapshot = await admin - .firestore() - .collection("patient") - .doc(userObject.userId) - .get(); - if (!snapshot.exists) { - await client.replyMessage(replyToken, jsonController("tutorial2")); - - return success(); - } - - await client.replyMessage(replyToken, jsonController("tutorial1")); - - const { isRequestToCall } = snapshot.data() as Patient; - - if (isRequestToCall) { - return success( - `userID: ${userObject.userId} has already requested to call` - ); - } - await snapshot.ref.update({ - isRequestToCall: true, - isRequestToCallExported: false, - }); - return success(); -}; - diff --git a/functions/src/types/patient.ts b/functions/src/types/patient.ts index ef16a2b0..74515dc9 100644 --- a/functions/src/types/patient.ts +++ b/functions/src/types/patient.ts @@ -1,21 +1,11 @@ import { Timestamp } from "@google-cloud/firestore" -import { HistoryType, RegisterType } from '../schema' +import { HistoryType } from '../schema' export type FollowUp = Omit -export type Patient = Partial & { - followUp: FollowUp[] - status: number - needFollowUp: boolean - createdDate: Timestamp - lastUpdatedAt: Timestamp - isRequestToCallExported: boolean - isRequestToCall: boolean - isNurseExported: boolean - toAmed: number - birthDate: Timestamp -} & Omit - +export type Patient = { + followUp: UpdatedPatient[] +} export type UpdatedPatient = { status: number diff --git a/functions/src/types/r2r.ts b/functions/src/types/r2r.ts index 25baee24..fa40cc04 100644 --- a/functions/src/types/r2r.ts +++ b/functions/src/types/r2r.ts @@ -1,8 +1,4 @@ -import { RequestToRegisterType } from "../schema" -export type R2RAssistance = Omit & { - isR2RExported: boolean, -} export type NotUpdatedList = { firstName: string From 58e2a363d2063fe1ab790b8c0d443b344e92e40c Mon Sep 17 00:00:00 2001 From: Ravipas Wangananont Date: Sun, 12 Sep 2021 15:02:28 +0700 Subject: [PATCH 23/34] added new messages added new greeting message and removed greeting photo number 2 --- functions/src/handler/jsonHandler.ts | 2 -- functions/src/handler/subhandler/followHandler.ts | 1 - functions/src/json/defaultReply.json | 2 +- functions/src/json/greeting.json | 2 +- .../src/json/{welcomepos2.json => welcomepos2-unused.json} | 0 5 files changed, 2 insertions(+), 5 deletions(-) rename functions/src/json/{welcomepos2.json => welcomepos2-unused.json} (100%) diff --git a/functions/src/handler/jsonHandler.ts b/functions/src/handler/jsonHandler.ts index a15e4fb0..6f3d1482 100644 --- a/functions/src/handler/jsonHandler.ts +++ b/functions/src/handler/jsonHandler.ts @@ -1,6 +1,5 @@ import * as greeting from "../json/greeting.json"; import * as welcomepos1 from "../json/welcomepos1.json"; -import * as welcomepos2 from "../json/welcomepos2.json"; import * as help from "../json/help.json"; import * as symptomDiagnostic from "../json/symptomDiagnostic.json"; import * as info1 from "../json/info1.json"; @@ -36,7 +35,6 @@ const tutorial2 = { const map: { [key: string]: any } = { greeting, welcomepos1, - welcomepos2, help, symptomDiagnostic, info1, diff --git a/functions/src/handler/subhandler/followHandler.ts b/functions/src/handler/subhandler/followHandler.ts index 2bf09574..f5f7dca5 100644 --- a/functions/src/handler/subhandler/followHandler.ts +++ b/functions/src/handler/subhandler/followHandler.ts @@ -8,7 +8,6 @@ export const handleFollow: LineHandler = async (event, userObject, // let greeting = jsonController("greeting"); await client.replyMessage(replyToken, [ jsonController("welcomepos1"), - jsonController("welcomepos2"), jsonController("greeting"), ]); } catch (error) { diff --git a/functions/src/json/defaultReply.json b/functions/src/json/defaultReply.json index e9f00ceb..8b7eef9a 100644 --- a/functions/src/json/defaultReply.json +++ b/functions/src/json/defaultReply.json @@ -1,4 +1,4 @@ { "type": "text", - "text": "ขออภัยค่ะ ระบบอัตโนมัติไม่สามารถตอบกลับได้ขณะนี้😢 🙏🏻\n\nคุณสามารถคลิก “เมนู” เพื่อเลือกใช้ปุ่มอื่นๆต่อไป" + "text": "ขออภัยค่ะ ไลน์ของเราเป็นระบบอัตโนมัติ ไม่สามารถตอบคุณกลับได้😢 🙏🏻\n\nคุณสามารถกด “☰” ด้านซ้ายของกล่องข้อความ เเละกด “เมนู” เพื่อเลือกใช้งานส่วนอื่นๆต่อไป" } diff --git a/functions/src/json/greeting.json b/functions/src/json/greeting.json index 1f3c0999..b37789ad 100644 --- a/functions/src/json/greeting.json +++ b/functions/src/json/greeting.json @@ -1,4 +1,4 @@ { "type": "text", - "text": "สวัสดีค่ะ เราคือ “เพื่อนช่วยเช็ค” คุณสามารถเช็คสถานะสีผู้ป่วยโควิด19ของคุณได้โดยเริ่มจากคลิกที่ “ลงทะเบียน” ได้เลยค่ะ😘\n\n👉🏻 หลังจากนั้นเราจะส่งแบบฟอร์มให้คุณได้กรอกอาการรายวันและรอรับฟังสถานะผู้ป่วยได้เลยค่ะ😷\n\n👉🏻 คุณสามารถคลิก “เมนู” เพื่อเลือกใช้ปุ่มอื่นๆต่อไป🥰\n\nหากอยากทราบข้อมูลเบื้องต้นเกี่ยวกับการแยกกักตัว สามารถกดดูได้ที่ปุ่ม สิ่งที่ควรรู้เมื่อแยกกักตั" + "text": "สวัสดีค่ะ เราคือ “เพื่อนช่วยเช็ค” เพื่อนที่จะมาช่วยให้คุณสามารถประเมินอาการตนเองเบื้องต้น เเละให้ข้อมูลที่ควรทราบเกี่ยวกับโควิด-19 ค่ะ 😘\n\n🌈คุณสามารถกด “เมนู” เพื่อเริ่มใช้งานเราค่ะ 🥰\n\n👉 กด “ประเมินอาการ” เพื่อประเมินอาการของตนเองเเละรับคำเเนะนำเบื้องต้นตามระดับอาการของคุณ\n👉 กด “เบอร์ติดต่อฉุกเฉิน” หากคุณต้องการเบอร์ติดต่อบุคลากรทางการเเพทย์หรือหาเตียง\n👉 กด “คำแนะนำตามอาการที่พบ” เพื่อศึกษาวิธีการดูเเลตนเองเบื้องต้นสำหรับอาการต่างๆจากโควิด-19\n👉 กด “ข้อควรรู้สำหรับผู้ป่วย” ดูข้อมูลการกินยาเเละสิ่งอื่นๆที่คุณควรทราบ สำหรับผู้ป่วยโควิด-19\n\n🚑 “เพื่อนช่วยเช็ค” ไม่มีบริการทางการเเพทย์ รบกวนผู้ป่วยติดต่อเบอร์ฉุกเฉิน คลินิก หรือโรงพยาบาลที่ดูเเลคุณหากมีเหตุจำเป็น หากคุณมีความเสี่ยงที่จะติดเชื้อ โปรดเข้ารับการตรวจหาเชื้อเพื่อเข้าระบบการรักษาต่อไป\n\n🌈 อย่าลืมมาประเมินอาการตนเองทุกวันนะคะ 😘\n🤝 พวกเราขอเป็นกำลังใจให้ทุกคนผ่านวิกฤตินี้ไปด้วยกัน 💫" } diff --git a/functions/src/json/welcomepos2.json b/functions/src/json/welcomepos2-unused.json similarity index 100% rename from functions/src/json/welcomepos2.json rename to functions/src/json/welcomepos2-unused.json From bc20342a7beb8fa38287be2079dd2b0fd792e3b4 Mon Sep 17 00:00:00 2001 From: Ravipas Wangananont Date: Sun, 12 Sep 2021 16:11:52 +0700 Subject: [PATCH 24/34] added FAQ and symptom carousel --- functions/src/handler/jsonHandler.ts | 12 ++++-- .../src/handler/subhandler/messageHandler.ts | 34 ++++++++------- functions/src/json/emergency/help.json | 4 ++ functions/src/json/faq/faqCarousel.json | 23 +++++++++++ functions/src/json/help.json | 4 -- .../src/json/{ => others}/defaultReply.json | 0 functions/src/json/{ => others}/greeting.json | 0 .../src/json/symptom/symptomCarousel.json | 41 +++++++++++++++++++ 8 files changed, 96 insertions(+), 22 deletions(-) create mode 100644 functions/src/json/emergency/help.json create mode 100644 functions/src/json/faq/faqCarousel.json delete mode 100644 functions/src/json/help.json rename functions/src/json/{ => others}/defaultReply.json (100%) rename functions/src/json/{ => others}/greeting.json (100%) create mode 100644 functions/src/json/symptom/symptomCarousel.json diff --git a/functions/src/handler/jsonHandler.ts b/functions/src/handler/jsonHandler.ts index 6f3d1482..1644fdf5 100644 --- a/functions/src/handler/jsonHandler.ts +++ b/functions/src/handler/jsonHandler.ts @@ -1,6 +1,6 @@ -import * as greeting from "../json/greeting.json"; +import * as greeting from "../json/others/greeting.json"; import * as welcomepos1 from "../json/welcomepos1.json"; -import * as help from "../json/help.json"; +import * as help from "../json/emergency/help.json"; import * as symptomDiagnostic from "../json/symptomDiagnostic.json"; import * as info1 from "../json/info1.json"; import * as info2 from "../json/info2.json"; @@ -8,9 +8,13 @@ import * as info3 from "../json/info3.json"; import * as info4 from "../json/info4.json"; import * as info5 from "../json/info5.json"; import * as info6 from "../json/info6.json"; -import * as defaultReply from "../json/defaultReply.json"; +import * as defaultReply from "../json/others/defaultReply.json"; import * as tutorial1 from "../json/tutorial1.json"; +import * as faqCarousel from "../json/faq/faqCarousel.json"; + +import * as SymptomCarousel from "../json/symptom/symptomCarousel.json"; + import * as guide from "../json/guide.json"; import * as r2cQuestion from "../json/r2cQuestion.json"; import * as closeRegistration from "../json/closeRegistration.json"; @@ -36,6 +40,8 @@ const map: { [key: string]: any } = { greeting, welcomepos1, help, + faqCarousel, + SymptomCarousel, symptomDiagnostic, info1, info2, diff --git a/functions/src/handler/subhandler/messageHandler.ts b/functions/src/handler/subhandler/messageHandler.ts index 99839839..82c35c39 100644 --- a/functions/src/handler/subhandler/messageHandler.ts +++ b/functions/src/handler/subhandler/messageHandler.ts @@ -10,27 +10,31 @@ export const handleMessage: LineHandler = async (event, userObject // console.log(message) try { switch (message) { - case "ประเมินอาการ": + case "ติดต่อฉุกเฉิน": + await client.replyMessage(replyToken, jsonController("help")); + break; + + case "คำแนะนำตามอาการ": await client.replyMessage( replyToken, - jsonController("symptomDiagnostic") + jsonController("symptomCarousel") ); break; - case "สิ่งที่ควรรู้": - await client.replyMessage(replyToken, [ - jsonController("info1"), - jsonController("info2"), - jsonController("info3"), - jsonController("info4"), - jsonController("info6"), - ]); - break; - case "ติดต่อฉุกเฉิน": - await client.replyMessage(replyToken, jsonController("help")); + + case "ข้อควรรู้": + await client.replyMessage( + replyToken, + jsonController("faqCarousel") + ); break; - case "สอนการใช้งาน": - await requestGuide(userObject, client, replyToken); + + case "ประเมินอาการ": + await client.replyMessage( + replyToken, + jsonController("symptomDiagnostic") + ); break; + default: await client.replyMessage(replyToken, jsonController("defaultReply")); break; diff --git a/functions/src/json/emergency/help.json b/functions/src/json/emergency/help.json new file mode 100644 index 00000000..b78ac85a --- /dev/null +++ b/functions/src/json/emergency/help.json @@ -0,0 +1,4 @@ +{ + "type": "text", + "text": "📢โปรดต่อต่อคลินิกหรือโรงพยาบาลที่ดูเเลคุณ\nหรือ\n☎️สถาบันการเเพทย์ฉุกเฉินเเห่งชาติ 1669 ในกรณีฉุกเฉิน\n☎️สายด่วนหาเตียง กรมการเเพทย์ 1668 เพื่อหาเตียงเข้ารับการรักษา\n☎️สายด่วน สปสช. 1330 เพื่อเข้าระบบรักษาตัวที่บ้าน" +} diff --git a/functions/src/json/faq/faqCarousel.json b/functions/src/json/faq/faqCarousel.json new file mode 100644 index 00000000..28abdfce --- /dev/null +++ b/functions/src/json/faq/faqCarousel.json @@ -0,0 +1,23 @@ +{ + "type": "template", + "altText": "ข้อควรรู้สำหรับผู้ป่วย", + "template": { + "type": "image_carousel", + "columns": [ + { + "imageUrl": "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/FAQ%2Fcover_faq.jpg?alt=media&token=b5537548-c65c-4494-8ee0-ddc8288b2cc9", + "action": { + "type": "message", + "text": "รายละเอียดข้อควรรู้" + } + }, + { + "imageUrl": "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/FAQ%2Fcover_insurance.jpg?alt=media&token=8ead3f10-6198-4518-9cd3-16d5ccb23855", + "action": { + "type": "message", + "text": "สิทธิการเบิกประกัน" + } + } + ] + } +} \ No newline at end of file diff --git a/functions/src/json/help.json b/functions/src/json/help.json deleted file mode 100644 index df1d3180..00000000 --- a/functions/src/json/help.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "type": "text", - "text": "โปรดติดต่อ\nสถาบันการเเพทย์ฉุกเฉินเเห่งชาติ 1669\nสายด่วนหาเตียง 1668\nหรือสายด่วน สปสช. 1330" -} diff --git a/functions/src/json/defaultReply.json b/functions/src/json/others/defaultReply.json similarity index 100% rename from functions/src/json/defaultReply.json rename to functions/src/json/others/defaultReply.json diff --git a/functions/src/json/greeting.json b/functions/src/json/others/greeting.json similarity index 100% rename from functions/src/json/greeting.json rename to functions/src/json/others/greeting.json diff --git a/functions/src/json/symptom/symptomCarousel.json b/functions/src/json/symptom/symptomCarousel.json new file mode 100644 index 00000000..beb13264 --- /dev/null +++ b/functions/src/json/symptom/symptomCarousel.json @@ -0,0 +1,41 @@ +{ + "type": "template", + "altText": "อาการที่อาจพบ", + "template": { + "type": "image_carousel", + "columns": [ + { + "imageUrl": "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/Symptoms%2Fcover_breathe.jpg?alt=media&token=be692fed-18c5-48b6-906a-d994c4c3c324", + "action": { + "type": "message", + "label": "breathe", + "text": "หายใจลำบาก" + } + }, + { + "imageUrl": "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/Symptoms%2Fcover_fever.jpg?alt=media&token=3f872abd-ee52-4d31-a08e-16b7e57eb7d5", + "action": { + "type": "message", + "label": "fever", + "text": "มีไข้" + } + }, + { + "imageUrl": "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/Symptoms%2Fcover_cough.jpg?alt=media&token=8a979569-17cb-4e7b-80fd-3cd09c2493b7", + "action": { + "type": "message", + "label": "cough", + "text": "ไอเจ็บคอ" + } + }, + { + "imageUrl": "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/Symptoms%2Fcover_stomach.jpg?alt=media&token=1e718029-4aa3-4aa5-ac35-634b08ca5dc0", + "action": { + "type": "message", + "label": "stomach", + "text": "ท้องเสีย" + } + } + ] + } +} \ No newline at end of file From c410ecaacc8a8a73322c742007cc0fd93688942d Mon Sep 17 00:00:00 2001 From: Ravipas Wangananont Date: Sun, 12 Sep 2021 16:20:01 +0700 Subject: [PATCH 25/34] removed unused import --- functions/src/handler/subhandler/messageHandler.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/functions/src/handler/subhandler/messageHandler.ts b/functions/src/handler/subhandler/messageHandler.ts index 82c35c39..9fe8e939 100644 --- a/functions/src/handler/subhandler/messageHandler.ts +++ b/functions/src/handler/subhandler/messageHandler.ts @@ -1,5 +1,4 @@ import { jsonController } from "../jsonHandler"; -import { requestGuide } from "../../linefunctions/requestGuideHandler"; import { LineHandler } from "../../types"; import { MessageEvent, TextEventMessage } from "@line/bot-sdk" From cf048f9f521cddb0583682b6f07e3b714d286df9 Mon Sep 17 00:00:00 2001 From: Ravipas Wangananont Date: Sun, 12 Sep 2021 16:31:11 +0700 Subject: [PATCH 26/34] bug fix --- functions/src/handler/jsonHandler.ts | 4 ++-- functions/src/json/faq/faqMsg.json | 0 functions/src/json/faq/insurance1Msg.json | 0 functions/src/json/faq/insurance2Msg.json | 0 functions/src/json/symptom/symptomCarousel.json | 4 ---- 5 files changed, 2 insertions(+), 6 deletions(-) create mode 100644 functions/src/json/faq/faqMsg.json create mode 100644 functions/src/json/faq/insurance1Msg.json create mode 100644 functions/src/json/faq/insurance2Msg.json diff --git a/functions/src/handler/jsonHandler.ts b/functions/src/handler/jsonHandler.ts index 1644fdf5..6b025c7d 100644 --- a/functions/src/handler/jsonHandler.ts +++ b/functions/src/handler/jsonHandler.ts @@ -13,7 +13,7 @@ import * as tutorial1 from "../json/tutorial1.json"; import * as faqCarousel from "../json/faq/faqCarousel.json"; -import * as SymptomCarousel from "../json/symptom/symptomCarousel.json"; +import * as symptomCarousel from "../json/symptom/symptomCarousel.json"; import * as guide from "../json/guide.json"; import * as r2cQuestion from "../json/r2cQuestion.json"; @@ -41,7 +41,7 @@ const map: { [key: string]: any } = { welcomepos1, help, faqCarousel, - SymptomCarousel, + symptomCarousel, symptomDiagnostic, info1, info2, diff --git a/functions/src/json/faq/faqMsg.json b/functions/src/json/faq/faqMsg.json new file mode 100644 index 00000000..e69de29b diff --git a/functions/src/json/faq/insurance1Msg.json b/functions/src/json/faq/insurance1Msg.json new file mode 100644 index 00000000..e69de29b diff --git a/functions/src/json/faq/insurance2Msg.json b/functions/src/json/faq/insurance2Msg.json new file mode 100644 index 00000000..e69de29b diff --git a/functions/src/json/symptom/symptomCarousel.json b/functions/src/json/symptom/symptomCarousel.json index beb13264..d4465aa5 100644 --- a/functions/src/json/symptom/symptomCarousel.json +++ b/functions/src/json/symptom/symptomCarousel.json @@ -8,7 +8,6 @@ "imageUrl": "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/Symptoms%2Fcover_breathe.jpg?alt=media&token=be692fed-18c5-48b6-906a-d994c4c3c324", "action": { "type": "message", - "label": "breathe", "text": "หายใจลำบาก" } }, @@ -16,7 +15,6 @@ "imageUrl": "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/Symptoms%2Fcover_fever.jpg?alt=media&token=3f872abd-ee52-4d31-a08e-16b7e57eb7d5", "action": { "type": "message", - "label": "fever", "text": "มีไข้" } }, @@ -24,7 +22,6 @@ "imageUrl": "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/Symptoms%2Fcover_cough.jpg?alt=media&token=8a979569-17cb-4e7b-80fd-3cd09c2493b7", "action": { "type": "message", - "label": "cough", "text": "ไอเจ็บคอ" } }, @@ -32,7 +29,6 @@ "imageUrl": "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/Symptoms%2Fcover_stomach.jpg?alt=media&token=1e718029-4aa3-4aa5-ac35-634b08ca5dc0", "action": { "type": "message", - "label": "stomach", "text": "ท้องเสีย" } } From c75b37d80d3cc616deb457483cc56430935b3964 Mon Sep 17 00:00:00 2001 From: Ravipas Wangananont Date: Sun, 12 Sep 2021 17:17:18 +0700 Subject: [PATCH 27/34] added new message triggers --- functions/src/handler/jsonHandler.ts | 60 --------------- .../src/handler/subhandler/messageHandler.ts | 53 +++++++++---- functions/src/json/symptom/breathe.json | 0 functions/src/json/symptom/cough.json | 0 functions/src/json/symptom/fever.json | 0 functions/src/json/symptom/stomach.json | 0 functions/src/messages/emergency.ts | 8 ++ functions/src/messages/faq.ts | 53 +++++++++++++ functions/src/messages/index.ts | 35 +++++++++ functions/src/messages/others.ts | 22 ++++++ functions/src/messages/symptoms.ts | 77 +++++++++++++++++++ 11 files changed, 231 insertions(+), 77 deletions(-) delete mode 100644 functions/src/handler/jsonHandler.ts create mode 100644 functions/src/json/symptom/breathe.json create mode 100644 functions/src/json/symptom/cough.json create mode 100644 functions/src/json/symptom/fever.json create mode 100644 functions/src/json/symptom/stomach.json create mode 100644 functions/src/messages/emergency.ts create mode 100644 functions/src/messages/faq.ts create mode 100644 functions/src/messages/index.ts create mode 100644 functions/src/messages/others.ts create mode 100644 functions/src/messages/symptoms.ts diff --git a/functions/src/handler/jsonHandler.ts b/functions/src/handler/jsonHandler.ts deleted file mode 100644 index 6b025c7d..00000000 --- a/functions/src/handler/jsonHandler.ts +++ /dev/null @@ -1,60 +0,0 @@ -import * as greeting from "../json/others/greeting.json"; -import * as welcomepos1 from "../json/welcomepos1.json"; -import * as help from "../json/emergency/help.json"; -import * as symptomDiagnostic from "../json/symptomDiagnostic.json"; -import * as info1 from "../json/info1.json"; -import * as info2 from "../json/info2.json"; -import * as info3 from "../json/info3.json"; -import * as info4 from "../json/info4.json"; -import * as info5 from "../json/info5.json"; -import * as info6 from "../json/info6.json"; -import * as defaultReply from "../json/others/defaultReply.json"; -import * as tutorial1 from "../json/tutorial1.json"; - -import * as faqCarousel from "../json/faq/faqCarousel.json"; - -import * as symptomCarousel from "../json/symptom/symptomCarousel.json"; - -import * as guide from "../json/guide.json"; -import * as r2cQuestion from "../json/r2cQuestion.json"; -import * as closeRegistration from "../json/closeRegistration.json"; - -import config from "../config"; -const tutorial2 = { - type: "template", - altText: "กรอกเบอร์โทรศัพท์", - template: { - type: "buttons", - text: "ระบบเรายังไม่มีข้อมูลเบอร์ของท่าน กรุณากรอกเบอร์โทรศัพท์ของท่าน", - actions: [ - { - type: "uri", - label: "กรอกเบอร์โทรศัพท์", - uri: config.line.r2rUri, - }, - ], - }, -}; - -const map: { [key: string]: any } = { - greeting, - welcomepos1, - help, - faqCarousel, - symptomCarousel, - symptomDiagnostic, - info1, - info2, - info3, - info4, - info5, - info6, - defaultReply, - tutorial1, - tutorial2, - guide, - r2cQuestion, - closeRegistration, -}; - -export const jsonController = (json: string) => map[json] diff --git a/functions/src/handler/subhandler/messageHandler.ts b/functions/src/handler/subhandler/messageHandler.ts index 9fe8e939..bcb744f4 100644 --- a/functions/src/handler/subhandler/messageHandler.ts +++ b/functions/src/handler/subhandler/messageHandler.ts @@ -1,8 +1,7 @@ -import { jsonController } from "../jsonHandler"; +import messageMap from "../../messages"; import { LineHandler } from "../../types"; import { MessageEvent, TextEventMessage } from "@line/bot-sdk" - export const handleMessage: LineHandler = async (event, userObject, client) => { const replyToken = await event.replyToken; const message = await (event.message as TextEventMessage).text; @@ -10,32 +9,52 @@ export const handleMessage: LineHandler = async (event, userObject try { switch (message) { case "ติดต่อฉุกเฉิน": - await client.replyMessage(replyToken, jsonController("help")); - break; - - case "คำแนะนำตามอาการ": await client.replyMessage( replyToken, - jsonController("symptomCarousel") + messageMap.emergencyNumberMessage ); break; + // start Symptoms Carousel + case "คำแนะนำตามอาการ": + await client.replyMessage(replyToken, [messageMap.symptomListMessage, messageMap.symptomCarousel]); + break; + + case "หายใจลำบาก": + await client.replyMessage(replyToken, messageMap.breatheMessage); + break; + + case "ท้องเสีย": + await client.replyMessage(replyToken, messageMap.stomachMessage); + break; + + case "มีไข้": + await client.replyMessage(replyToken, messageMap.feverMessage); + break; + + case "ไอเจ็บคอ": + await client.replyMessage(replyToken, messageMap.coughMessage); + break; + // end symptoms Carousel + + // start faq carousel case "ข้อควรรู้": - await client.replyMessage( - replyToken, - jsonController("faqCarousel") - ); + await client.replyMessage(replyToken, messageMap.faqCarousel); break; - case "ประเมินอาการ": - await client.replyMessage( - replyToken, - jsonController("symptomDiagnostic") - ); + case "รายละเอียดข้อควรรู้": + await client.replyMessage(replyToken, messageMap.faqMessage); break; + case "สิทธิการเบิกประกัน": + await client.replyMessage(replyToken, [ + messageMap.insurance1Message, + messageMap.insurance2Message, + ]); + break; + // end faq carousel default: - await client.replyMessage(replyToken, jsonController("defaultReply")); + await client.replyMessage(replyToken, messageMap.defaultReplyMessage); break; } } catch (error) { diff --git a/functions/src/json/symptom/breathe.json b/functions/src/json/symptom/breathe.json new file mode 100644 index 00000000..e69de29b diff --git a/functions/src/json/symptom/cough.json b/functions/src/json/symptom/cough.json new file mode 100644 index 00000000..e69de29b diff --git a/functions/src/json/symptom/fever.json b/functions/src/json/symptom/fever.json new file mode 100644 index 00000000..e69de29b diff --git a/functions/src/json/symptom/stomach.json b/functions/src/json/symptom/stomach.json new file mode 100644 index 00000000..e69de29b diff --git a/functions/src/messages/emergency.ts b/functions/src/messages/emergency.ts new file mode 100644 index 00000000..83e69ce6 --- /dev/null +++ b/functions/src/messages/emergency.ts @@ -0,0 +1,8 @@ +import { Message } from "@line/bot-sdk"; + +const emergencyNumberMessage: Message = { + type: "text", + text: "📢โปรดต่อต่อคลินิกหรือโรงพยาบาลที่ดูเเลคุณ\nหรือ\n☎️สถาบันการเเพทย์ฉุกเฉินเเห่งชาติ 1669 ในกรณีฉุกเฉิน\n☎️สายด่วนหาเตียง กรมการเเพทย์ 1668 เพื่อหาเตียงเข้ารับการรักษา\n☎️สายด่วน สปสช. 1330 เพื่อเข้าระบบรักษาตัวที่บ้าน", +}; + +export { emergencyNumberMessage }; diff --git a/functions/src/messages/faq.ts b/functions/src/messages/faq.ts new file mode 100644 index 00000000..cc703192 --- /dev/null +++ b/functions/src/messages/faq.ts @@ -0,0 +1,53 @@ +import { Message } from "@line/bot-sdk"; + +const faqCarousel: Message = { + type: "template", + altText: "ข้อควรรู้สำหรับผู้ป่วย", + template: { + type: "image_carousel", + columns: [ + { + imageUrl: + "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/FAQ%2Fcover_faq.jpg?alt=media&token=b5537548-c65c-4494-8ee0-ddc8288b2cc9", + action: { + type: "message", + text: "รายละเอียดข้อควรรู้", + }, + }, + { + imageUrl: + "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/FAQ%2Fcover_insurance.jpg?alt=media&token=8ead3f10-6198-4518-9cd3-16d5ccb23855", + action: { + type: "message", + text: "สิทธิการเบิกประกัน", + }, + }, + ], + }, +}; + +const faqMessage: Message = { + type: "image", + originalContentUrl: + "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/FAQ%2Fdetail_faq.jpg?alt=media&token=946feb08-1a46-467c-b53e-d033d65d61b9", + previewImageUrl: + "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/FAQ%2Fdetail_faq.jpg?alt=media&token=946feb08-1a46-467c-b53e-d033d65d61b9", +}; + +const insurance1Message: Message = { + type: "image", + originalContentUrl: + "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/FAQ%2Fdetail_insurance_1.jpg?alt=media&token=5d9ed79d-099e-4767-87fa-e5d4b36b7bf5", + previewImageUrl: + "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/FAQ%2Fdetail_insurance_1.jpg?alt=media&token=5d9ed79d-099e-4767-87fa-e5d4b36b7bf5", +}; + +const insurance2Message: Message = { + type: "image", + originalContentUrl: + "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/FAQ%2Fdetail_insurance_2.jpg?alt=media&token=e0a53dce-ae46-40bc-a50f-c515c3184e6b", + previewImageUrl: + "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/FAQ%2Fdetail_insurance_2.jpg?alt=media&token=e0a53dce-ae46-40bc-a50f-c515c3184e6b", +}; + +export {faqCarousel, faqMessage, insurance1Message, insurance2Message}; \ No newline at end of file diff --git a/functions/src/messages/index.ts b/functions/src/messages/index.ts new file mode 100644 index 00000000..452189f4 --- /dev/null +++ b/functions/src/messages/index.ts @@ -0,0 +1,35 @@ +import { emergencyNumberMessage } from "./emergency"; +import { + faqCarousel, + faqMessage, + insurance1Message, + insurance2Message, +} from "./faq"; +import { greetingMessage, greetingPhoto, defaultReplyMessage } from "./others"; +import { + symptomCarousel, + coughMessage, + feverMessage, + stomachMessage, + breatheMessage, + symptomListMessage +} from "./symptoms"; + +const messageMap = { + emergencyNumberMessage: emergencyNumberMessage, + faqCarousel: faqCarousel, + faqMessage: faqMessage, + insurance1Message: insurance1Message, + insurance2Message: insurance2Message, + greetingMessage: greetingMessage, + greetingPhoto: greetingPhoto, + defaultReplyMessage: defaultReplyMessage, + symptomCarousel: symptomCarousel, + coughMessage: coughMessage, + feverMessage: feverMessage, + stomachMessage: stomachMessage, + breatheMessage: breatheMessage, + symptomListMessage: symptomListMessage +}; + +export default messageMap; diff --git a/functions/src/messages/others.ts b/functions/src/messages/others.ts new file mode 100644 index 00000000..d8556e57 --- /dev/null +++ b/functions/src/messages/others.ts @@ -0,0 +1,22 @@ +import { Message } from "@line/bot-sdk"; + +const greetingMessage: Message = { + type: "text", + text: "สวัสดีค่ะ เราคือ “เพื่อนช่วยเช็ค” เพื่อนที่จะมาช่วยให้คุณสามารถประเมินอาการตนเองเบื้องต้น เเละให้ข้อมูลที่ควรทราบเกี่ยวกับโควิด-19 ค่ะ 😘\n\n🌈คุณสามารถกด “เมนู” เพื่อเริ่มใช้งานเราค่ะ 🥰\n\n👉 กด “ประเมินอาการ” เพื่อประเมินอาการของตนเองเเละรับคำเเนะนำเบื้องต้นตามระดับอาการของคุณ\n👉 กด “เบอร์ติดต่อฉุกเฉิน” หากคุณต้องการเบอร์ติดต่อบุคลากรทางการเเพทย์หรือหาเตียง\n👉 กด “คำแนะนำตามอาการที่พบ” เพื่อศึกษาวิธีการดูเเลตนเองเบื้องต้นสำหรับอาการต่างๆจากโควิด-19\n👉 กด “ข้อควรรู้สำหรับผู้ป่วย” ดูข้อมูลการกินยาเเละสิ่งอื่นๆที่คุณควรทราบ สำหรับผู้ป่วยโควิด-19\n\n🚑 “เพื่อนช่วยเช็ค” ไม่มีบริการทางการเเพทย์ รบกวนผู้ป่วยติดต่อเบอร์ฉุกเฉิน คลินิก หรือโรงพยาบาลที่ดูเเลคุณหากมีเหตุจำเป็น หากคุณมีความเสี่ยงที่จะติดเชื้อ โปรดเข้ารับการตรวจหาเชื้อเพื่อเข้าระบบการรักษาต่อไป\n\n🌈 อย่าลืมมาประเมินอาการตนเองทุกวันนะคะ 😘\n🤝 พวกเราขอเป็นกำลังใจให้ทุกคนผ่านวิกฤตินี้ไปด้วยกัน 💫", +}; + +const greetingPhoto: Message = { + type: "image", + originalContentUrl: + "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/S__56058669_new.jpg?alt=media&token=0ef23d73-0943-4a86-9dbf-9c0f451e8a44", + previewImageUrl: + "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/S__56058669_new.jpg?alt=media&token=0ef23d73-0943-4a86-9dbf-9c0f451e8a44", +}; + + +const defaultReplyMessage: Message = { + type: "text", + text: "ขออภัยค่ะ ไลน์ของเราเป็นระบบอัตโนมัติ ไม่สามารถตอบคุณกลับได้😢 🙏🏻\n\nคุณสามารถกด “☰” ด้านซ้ายของกล่องข้อความ เเละกด “เมนู” เพื่อเลือกใช้งานส่วนอื่นๆต่อไป", +}; + +export { greetingMessage, defaultReplyMessage, greetingPhoto }; \ No newline at end of file diff --git a/functions/src/messages/symptoms.ts b/functions/src/messages/symptoms.ts new file mode 100644 index 00000000..09d0b188 --- /dev/null +++ b/functions/src/messages/symptoms.ts @@ -0,0 +1,77 @@ +import { Message } from "@line/bot-sdk/dist/types"; + +const symptomListMessage: Message = { + type: "text", + text: "อาการทั่วไป (พบได้บ่อย)👇🏻\n- มีไข้\n- ไอแห้ง\n- อ่อนเพลีย\n\nอาการที่พบไม่บ่อยนัก👇🏻\n- ปวดเมื่อยเนื้อตัว\n- เจ็บคอ\n- ท้องเสีย\n- ตาแดง\n- ปวดศีรษะ\n- สูญเสียความสามารถในการดมกลิ่นและรับรส\n- มีผื่นบนผิวหนัง หรือนิ้วมือนิ้วเท้าเปลี่ยนสี", +}; + +const symptomCarousel: Message = { + type: "template", + altText: "อาการที่อาจพบ", + template: { + type: "image_carousel", + columns: [ + { + imageUrl: + "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/Symptoms%2Fcover_breathe.jpg?alt=media&token=be692fed-18c5-48b6-906a-d994c4c3c324", + action: { + type: "message", + text: "หายใจลำบาก", + }, + }, + { + imageUrl: + "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/Symptoms%2Fcover_fever.jpg?alt=media&token=3f872abd-ee52-4d31-a08e-16b7e57eb7d5", + action: { + type: "message", + text: "มีไข้", + }, + }, + { + imageUrl: + "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/Symptoms%2Fcover_cough.jpg?alt=media&token=8a979569-17cb-4e7b-80fd-3cd09c2493b7", + action: { + type: "message", + text: "ไอเจ็บคอ", + }, + }, + { + imageUrl: + "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/Symptoms%2Fcover_stomach.jpg?alt=media&token=1e718029-4aa3-4aa5-ac35-634b08ca5dc0", + action: { + type: "message", + text: "ท้องเสีย", + }, + }, + ], + }, +}; + +const coughMessage: Message = { + type: "text", + text: "💦 จิบน้ำบ่อยๆระหว่างวัน\n💊 รับประทานยาแก้ไอ หรือยาอมบรรเทาอาการไอ\n🍵 ดื่มเครื่องดื่มอุ่นๆ เช่น ชา ซุป ช่วยลดเสมหะในทางเดินหายใจส่วนบนได้\n🍯 จิบน้ำผึ้งผสมชาร้อน หรือน้ำอุ่น เนื่องจาก น้ำผึ้งช่วยบรรเทาอาการเจ็บคอได้ แต่ไม่แนะนำให้ทานน้ำผึ้งในเด็กอายุต่ำกว่า 1 ปี\n💨 หายใจในบริเวณที่มีไอน้ำมาก โดยสามารถใช้ เครื่องทำน้ำร้อน เครื่องทำความชื้น(humidifier) เครื่องทำไอระเหย(vaporizer) หรือวิธีอื่นๆที่จะช่วยทำให้เกิดไอน้ำ ซึ่งจะช่วยลดอาการเจ็บคอ เปิดทางเดินหายใจ และทำให้หายใจง่ายขึ้น\n🆙 เลี่ยงการนอนราบ ให้นอนตะแคง หรือนอนหมอนสูง", +}; + +const feverMessage: Message = { + type: "text", + text: "💊 ทานยาลดไข้ เช่น พาราเซตามอล \n🤒 เช็ดตัวเพื่อลดไข้ บริเวณคอ ข้อพับต่างๆ\n💦 ดื่มน้ำให้เพียงพอ เนื่องจากอาการไข้มักทำให้เหงื่อออก ซึ่งทำให้ร่างกายสูญเสียน้ำได้ เมื่อดื่มเพียงพอ ปัสสาวะ จะเป็นสีเหลืองอ่อน โดยอาจจะเป็นน้ำเปล่าหรือน้ำผลไม้ แต่ไม่ควรเป็นเครื่องดื่มโซดา หรือเครื่องดื่มน้ำตาลสูงที่อาจทำให้หิวน้ำเพิ่มขึ้น และ ไม่ควรดื่มเครื่องดื่มที่มีคาเฟอีน\n😴 พักผ่อนให้เพียงพอ 7-8 ชั่วโมงต่อวัน", +}; + +const stomachMessage: Message = { + type: "text", + text: "🙅‍♀️ งดทานอาหารประเภท นม โยเกิร์ต ผลไม้สด และ อาหารที่ย่อยยาก\n💦 ชงเกลือแร่ ORS ผสมน้ำต้มสุก น้ำสะอาด จิบเรื่อยๆ ทั้งวัน (⚠️ในผู้ป่วยที่เป็นโรคไตและโรคหัวใจควรปรึกษาแพทย์ก่อน)\n👌🏻 หากรับประทานอาหารไม่ได้ ให้รับประทานน้อยๆ แต่บ่อยๆ", +}; + +const breatheMessage: Message = { + type: "text", + text: "🍃 เปิดหน้าต่าง อยู่ในห้องที่อากาศถ่ายเท\n😮‍💨 หายใจช้าๆ ลึกๆ ทางจมูกและปาก\n🆙 นั่งตัวตรง ไม่นั่งหลังค่อม ผ่อนคลายบริเวณหัวไหล่\n🧘 เอนตัวมาข้างหน้าเล็กน้อย โดยใช้มือวางบนหน้าขาทั้งสองข้าง และ หายใจลึกๆ ยาวๆ\n☺️ พยายาม อย่าเครียด ตื่นตกใจ\n🆙 เวลานอนให้นอนตะแคง หรือ นอนหมอนสูง", +}; + +export { + symptomCarousel, + coughMessage, + feverMessage, + stomachMessage, + breatheMessage, + symptomListMessage, +}; From 9d559752ff41a52a81128eaf1a6834411a506804 Mon Sep 17 00:00:00 2001 From: Ravipas Wangananont Date: Sun, 12 Sep 2021 17:20:25 +0700 Subject: [PATCH 28/34] bug fix --- functions/src/handler/subhandler/followHandler.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/functions/src/handler/subhandler/followHandler.ts b/functions/src/handler/subhandler/followHandler.ts index f5f7dca5..10e33945 100644 --- a/functions/src/handler/subhandler/followHandler.ts +++ b/functions/src/handler/subhandler/followHandler.ts @@ -1,14 +1,15 @@ import { LineHandler } from "../../types"; -import { jsonController } from "../jsonHandler"; + import { FollowEvent } from "@line/bot-sdk" +import messageMap from "../../messages"; export const handleFollow: LineHandler = async (event, userObject, client) => { const replyToken = await event.replyToken; try { // let greeting = jsonController("greeting"); await client.replyMessage(replyToken, [ - jsonController("welcomepos1"), - jsonController("greeting"), + messageMap.greetingPhoto, + messageMap.greetingMessage, ]); } catch (error) { console.log(error); From 7d275906d4ea299e120cde5ba98a17d3623f3e19 Mon Sep 17 00:00:00 2001 From: Ravipas Wangananont Date: Sun, 12 Sep 2021 17:24:17 +0700 Subject: [PATCH 29/34] remove unused functions --- .../src/linefunctions/requestGuideHandler.ts | 39 ------------------- 1 file changed, 39 deletions(-) delete mode 100644 functions/src/linefunctions/requestGuideHandler.ts diff --git a/functions/src/linefunctions/requestGuideHandler.ts b/functions/src/linefunctions/requestGuideHandler.ts deleted file mode 100644 index 27af2a79..00000000 --- a/functions/src/linefunctions/requestGuideHandler.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { jsonController } from "../handler/jsonHandler"; -import { success } from "../response/success"; - -export const requestGuide = async (userObject: any, client: any, replyToken: any) => { - // const snapshot = await admin - // .firestore() - // .collection("patient") - // .doc(userObject.userId) - // .get(); - // if (!snapshot.exists) { - // //await client.replyMessage(replyToken, jsonController("tutorial2")); - // await client.replyMessage(replyToken, [ - // jsonController("guide"), - // jsonController("r2cQuestion") - // ]); - // return success(); - // } - - //await client.replyMessage(replyToken, jsonController("tutorial1")); - - // const { isRequestToCall } = snapshot.data(); - - // if (isRequestToCall) { - // return success( - // `userID: ${userObject.userId} has already requested to call` - // ); - // } - - await client.replyMessage(replyToken, [ - jsonController("guide"), - jsonController("r2cQuestion"), - ]); - - // await snapshot.ref.update({ - // isRequestToCall: true, - // isRequestToCallExported: false, - // }); - return success(); -}; From 85725a45e245fce1b686c6790814826ac5a7fad1 Mon Sep 17 00:00:00 2001 From: Ravipas Wangananont Date: Sun, 12 Sep 2021 22:41:01 +0700 Subject: [PATCH 30/34] added new triage result messages --- .../src/linefunctions/linepushmessage.ts | 153 +++++++++--------- functions/src/messages/triageResults.ts | 110 +++++++++++++ 2 files changed, 184 insertions(+), 79 deletions(-) create mode 100644 functions/src/messages/triageResults.ts diff --git a/functions/src/linefunctions/linepushmessage.ts b/functions/src/linefunctions/linepushmessage.ts index 55783051..d6179dca 100644 --- a/functions/src/linefunctions/linepushmessage.ts +++ b/functions/src/linefunctions/linepushmessage.ts @@ -1,46 +1,48 @@ import * as _ from "lodash"; -import { convertTimestampToStr } from "../utils"; +// import { convertTimestampToStr } from "../utils"; import axios from "axios"; import { statusList } from "../api/const"; import { UpdatedPatient } from "../types"; +import { greenMessages, redMessages, yellowMessages } from "../messages/triageResults"; +import { Message } from "@line/bot-sdk/dist/types"; const baseURL = "https://api.line.me/v2/bot/message/push"; type StatusObj = Omit -const symptomMapper = { - sym1_severe_cough: "มีอาการไอต่อเนื่อง", - sym1_chest_tightness: "มีอาการแน่นหน้าอก หายใจไม่สะดวก", - sym1_poor_appetite: "เบื่ออาหาร", - sym1_fatigue: "อ่อนเพลียมาก", - sym1_persistent_fever: "มีไข้สูงลอย", - sym2_tired_body_ache: "มีอาการอ่อนเพลีย ปวดเมื้อยตามตัว", - sym2_cough: "มีอาการไอรุนแรง", - sym2_fever: "มีไข้ขึ้น", - sym2_liquid_stool: "ท้องเสีย", - sym2_cannot_smell: "จมูกไม่รับกลิ่น", - sym2_rash: "มีผื่นขึ้นตามตัว", - sym2_red_eye: "ตาแดง", -}; +// const symptomMapper = { +// sym1_severe_cough: "มีอาการไอต่อเนื่อง", +// sym1_chest_tightness: "มีอาการแน่นหน้าอก หายใจไม่สะดวก", +// sym1_poor_appetite: "เบื่ออาหาร", +// sym1_fatigue: "อ่อนเพลียมาก", +// sym1_persistent_fever: "มีไข้สูงลอย", +// sym2_tired_body_ache: "มีอาการอ่อนเพลีย ปวดเมื้อยตามตัว", +// sym2_cough: "มีอาการไอรุนแรง", +// sym2_fever: "มีไข้ขึ้น", +// sym2_liquid_stool: "ท้องเสีย", +// sym2_cannot_smell: "จมูกไม่รับกลิ่น", +// sym2_rash: "มีผื่นขึ้นตามตัว", +// sym2_red_eye: "ตาแดง", +// }; -const conditionMapper = { - fac_bed_ridden_status: "ติดเตียง", - fac_uri_symptoms: "มีอาการทางเดินหายใจส่วนบน เช่น ไอ น้ำมูก คัดจมูก", - fac_olfactory_symptoms: "ได้กลิ่นแย่ลง/ไม่ได้กลิ่น", - fac_diarrhea: "ท้องเสียถ่ายเหลว", - fac_dyspnea: "มีอาการอ่อนเพลีย ปวดเมื้อยตามตัว", - fac_chest_discomfort: "หอบเหนื่อย หายใจเร็ว/ลำบาก", - fac_gi_symptoms: "แน่นหน้าอก", -}; +// const conditionMapper = { +// fac_bed_ridden_status: "ติดเตียง", +// fac_uri_symptoms: "มีอาการทางเดินหายใจส่วนบน เช่น ไอ น้ำมูก คัดจมูก", +// fac_olfactory_symptoms: "ได้กลิ่นแย่ลง/ไม่ได้กลิ่น", +// fac_diarrhea: "ท้องเสียถ่ายเหลว", +// fac_dyspnea: "มีอาการอ่อนเพลีย ปวดเมื้อยตามตัว", +// fac_chest_discomfort: "หอบเหนื่อย หายใจเร็ว/ลำบาก", +// fac_gi_symptoms: "แน่นหน้าอก", +// }; -const getPatientCondition = (statusObj: StatusObj, mapper: any) => { - const conditions = []; - for (const [key, value] of _.entries(statusObj)) { - if (value === 1 && key in mapper) { - conditions.push(mapper[key]); - } - } - return conditions.join(", "); -}; +// const getPatientCondition = (statusObj: StatusObj, mapper: any) => { +// const conditions = []; +// for (const [key, value] of _.entries(statusObj)) { +// if (value === 1 && key in mapper) { +// conditions.push(mapper[key]); +// } +// } +// return conditions.join(", "); +// }; export const statusMap = { G1: "เขียวอ่อน", @@ -52,67 +54,60 @@ export const statusMap = { unknown: "ไม่สามารถระบุได้", }; -const statusNumberMap: { [key: number]: string } = { - 1: "เขียวอ่อน", - 2: "เขียวเข้ม", - 3: "เหลืองอ่อน", - 4: "เหลืองเข้ม", - 5: "แดงอ่อน", - 6: "แดงเข้ม", - 0: "ไม่สามารถระบุได้", -}; +// const statusNumberMap: { [key: number]: string } = { +// 1: "เขียวอ่อน", +// 2: "เขียวเข้ม", +// 3: "เหลืองอ่อน", +// 4: "เหลืองเข้ม", +// 5: "แดงอ่อน", +// 6: "แดงเข้ม", +// 0: "ไม่สามารถระบุได้", +// }; -const getPatientTextColor = (statusNumber: number) => { - return statusNumberMap[statusNumber]; -}; +// const getPatientTextColor = (statusNumber: number) => { +// return statusNumberMap[statusNumber]; +// }; export const sendPatientstatus = async (userId: string, statusObj: StatusObj, channelAccessToken: string) => { - const date = convertTimestampToStr({ dateObj: statusObj.lastUpdatedAt }); - const message = `วันที่: ${date.dateObj} - \nข้อมูลทั่วไป: - - ค่าออกซิเจนปลายนิ้ว: ${statusObj.sp_o2 || "-"} - - ค่าออกซิเจนปลายนิ้ว ขณะหายใจปกติ: ${statusObj.sp_o2_ra || "-"} - - ค่าออกซิเจนปลายนิ้ว หลังลุกนั่ง 1 นาที: ${statusObj.sp_o2_after_eih || "-" - }`; - const patientCondition = getPatientCondition(statusObj, conditionMapper); - const patientsymptom = getPatientCondition(statusObj, symptomMapper); - const symptom = `\n\nอาการที่พบ: ${patientsymptom === "" ? "-" : patientsymptom - }`; - const condition = `\n\nอัปเดตโรคประจำตัว: ${patientCondition === "" ? "-" : patientCondition - }`; - const patientColor = getPatientTextColor(statusObj.status); - const conclude = `\n\nผลลัพธ์: - - ระดับ: ${patientColor}`; - const messagePayload = [ - { - type: "text", - text: message + symptom + condition + conclude, - }, - { - type: "text", - text: "ทางเราได้ประเมินอาการของคุณตามเกณฑ์ทางการแพทย์แล้ว เพื่อให้คุณได้รับการดูแลจากบุคลากรทางการแพทย์อย่าทันท่วงที เราจะนำข้อมูลของคุณไปแจ้งให้ทีมแพทย์ทราบ เพื่อดำเนินการต่อไป โดยทีมแพทย์จะติดต่อคุณกลับโดยเร็ว", - }, - { - type: "text", - text: "เพื่อให้คุณได้รับการดูแลจากบุคลากรทางการแพทย์อย่าทันท่วงที โปรดติดต่อสายด่วนสถาบันการแพทย์ฉุกเฉิน 1669 หรือสายด่วนหาเตียง 1668 หรือ สปสช. 1330\nโดยเราจะนำข้อมูลของคุณไปแจ้งให้ทีมแพทย์ทราบเช่นกัน เพื่อดำเนินการต่อไป โดยทีมแพทย์จะติดต่อคุณกลับโดยเร็ว", - }, - ]; + // const date = convertTimestampToStr({ dateObj: statusObj.lastUpdatedAt }); + // const message = `วันที่: ${date.dateObj} + // \nข้อมูลทั่วไป: + // - ค่าออกซิเจนปลายนิ้ว: ${statusObj.sp_o2 || "-"} + // - ค่าออกซิเจนปลายนิ้ว ขณะหายใจปกติ: ${statusObj.sp_o2_ra || "-"} + // - ค่าออกซิเจนปลายนิ้ว หลังลุกนั่ง 1 นาที: ${statusObj.sp_o2_after_eih || "-" + // }`; + // const patientCondition = getPatientCondition(statusObj, conditionMapper); + // const patientsymptom = getPatientCondition(statusObj, symptomMapper); + // const symptom = `\n\nอาการที่พบ: ${patientsymptom === "" ? "-" : patientsymptom + // }`; + // const condition = `\n\nอัปเดตโรคประจำตัว: ${patientCondition === "" ? "-" : patientCondition + // }`; + // const patientColor = getPatientTextColor(statusObj.status); + // const conclude = `\n\nผลลัพธ์: + // - ระดับ: ${patientColor}`; + + + const defaultMessage: Message = { + type: "text", + text: "Error Triage Result Unknown", + }; + let resultMessagePayload = []; switch (statusObj.status) { case statusList["G1"]: - resultMessagePayload = messagePayload.slice(0, 1); + resultMessagePayload = greenMessages break; case statusList["G2"]: case statusList["Y1"]: case statusList["Y2"]: - resultMessagePayload = messagePayload.slice(0, 2); + resultMessagePayload = yellowMessages break; case statusList["R2"]: case statusList["R3"]: - resultMessagePayload = messagePayload.slice(0, 3); + resultMessagePayload = redMessages; break; default: - resultMessagePayload = messagePayload.slice(0, 1); + resultMessagePayload = [defaultMessage]; } // const axiosConfig = { // method: "POST", diff --git a/functions/src/messages/triageResults.ts b/functions/src/messages/triageResults.ts new file mode 100644 index 00000000..0ac1bfd8 --- /dev/null +++ b/functions/src/messages/triageResults.ts @@ -0,0 +1,110 @@ +import { Message } from "@line/bot-sdk/dist/types"; + +// start green + +const greenDetailMessage: Message = { + type: "text", + text: "💚 จากการกรอกแบบประเมินอาการพบว่า คุณอาจไม่มีอาการจากโรคโควิด-19 หรือมีอยู่ในระดับต่ำ*\n-----------------\n💚 หากคุณยังไม่ได้รับการตรวจ คุณสามารถทำเเบบสอบถามประเมินความเสี่ยงได้จากเว็ปไซต์ของกรมอนามัย https://tinyurl.com/6pv3ucxs เเละเข้ารับการตรวจหากคุณมีประวัติเสี่ยงสูง\n💚 หากคุณได้รับการตรวจเเละพบเชื้อโควิด-19 แต่ยังไม่อยู่ในการดูเเลของเเพทย์ คุณอาจลงทะเบียนเข้าระบบการรักษาตัวที่บ้านสำหรับผู้ป่วยโควิด-19 ได้ โดยต้องทำการแยกกักตัวเป็นระยะเวลา 14 วัน เเละระวังการเเพร่กระจายเชื้อให้บุคคลในบ้าน นอกจากนั้น อย่าลืมเฝ้าระวังเเละประเมินอาการตนเองทุกวัน \n💚 หากคุณได้รับการตรวจเเละพบเชื้อโควิด-19 เเละอยู่ในการดูเเลของเเพทย์ โปรดปฏิบัติตามคำเเนะนำของเเพทย์ ระวังการเเพร่กระจายเชื้อให้บุคคลในบ้านเเละบุคคลอื่น\n⚠️ *หมายเหตุ: การประเมินด้วย “เพื่อนช่วยเช็ค” ใช้ระบบอัตโนมัติเพื่อให้คำเเนะนำเบื้องต้น ไม่สามารถใช้เป็นใบรับรองเเพทย์ได้", +}; + +const greenHiPrepImage: Message = { + type: "image", + originalContentUrl: + "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/TriageResults%2FGreen%2Fhi_prep.png?alt=media&token=b914ae39-fc3c-4f09-8918-dad35058fb67", + previewImageUrl: + "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/TriageResults%2FGreen%2Fhi_prep.png?alt=media&token=b914ae39-fc3c-4f09-8918-dad35058fb67", +}; + +const greenMedicineImage: Message = { + type: "image", + originalContentUrl: + "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/TriageResults%2FGreen%2Fmedicine.jpg?alt=media&token=84e7fbcc-0bbe-4677-b6fb-2ba18133d8fa", + previewImageUrl: + "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/TriageResults%2FGreen%2Fmedicine.jpg?alt=media&token=84e7fbcc-0bbe-4677-b6fb-2ba18133d8fa", +}; + +const greenHiGuidelineImage: Message = { + type: "image", + originalContentUrl: + "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/TriageResults%2FGreen%2Fhi_guideline.jpg?alt=media&token=61365a4d-5b89-411e-b28b-aff6b64124e9", + previewImageUrl: + "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/TriageResults%2FGreen%2Fhi_guideline.jpg?alt=media&token=61365a4d-5b89-411e-b28b-aff6b64124e9", +}; + +const greenContactMessage: Message = { + type: "text", + text: '🙋🏻‍♀️ข้อปฏิบัติในกรณีลงทะเบียนรักษาตัวที่บ้าน (Home Isolation) แต่ไม่ได้รับการติดต่อจากคลินิกหรือโรงพยาบาล ควรทำอย่างไร ❓\nเรามีทางเลือกระบบดูแลผู้ป่วยที่รักษาตัวที่บ้านของหน่วยงานต่างๆมาแนะนำกัน\n1️⃣ หากมีบัตรทองสามารถลงทะเบียนได้ผ่านสายด่วน สปสช. 1330 กด 14 หากมีสิทธิ์ประกันสังคมโทร 1506 กด 6 กรณีต้องการกลับภูมิลำเนาเพื่อรักษาตัวโทร 1330 กด 15\n2️⃣ #เส้นด้าย ลงทะเบียนเข้าระบบการรักษาตัว Home Isolation ที่ https://cutt.ly/MQ6kMf1 หรือ แอดไลน์ zendai.official มีระบบคัดกรอง และช่วยเหลือด้านต่าง ๆ เพื่อให้ผู้ป่วยมีสิ่งจำเป็นในการรักษาตัวที่บ้านอย่างครบถ้วน\n3️⃣ ลงทะเบียนเข้าโครงการ "ตัวเล็ก ใจใหญ่" LINE@ : @lbhcovid19​ สำหรับผู้ป่วยโควิดทุกสี ทั้งคนไทยและต่างชาติที่อยู่ในพื้นที่กรุงเทพและปริมณฑล​ที่ยังไม่มีที่รักษา \n4️⃣ กรอกข้อมูลเพื่อลงทะเบียนรักษาตัวที่บ้านกับ สปสช. โทร 1330 หรือ https://crmsup.nhso.go.th/ หรือแอดไลน์ @nhso และกดลงทะเบียนในระบบดูแลที่บ้าน Home Isolation และรอเจ้าหน้าที่ยืนยันข้อมูล\n⏰ทั้งนี้ หากภายในช่วงเวลากักตัวมีอาการเปลี่ยนแปลงไปอย่างไร ทุกท่านยังสามารถกด\n“ประเมินอาการ” กับเราได้ตลอดเวลาเลย! ✌🏻', +}; + +const greenMessages = [ + greenDetailMessage, + greenHiPrepImage, + greenMedicineImage, + greenHiGuidelineImage, + greenContactMessage, +]; + +//end green + +//start yellow + +const yellowDetailMessage: Message = { + type: "text", + text: "💛 จากการกรอกแบบประเมินอาการพบว่า คุณอาจมีอาการจากโรคโควิด-19 อยู่พอสมควร*\n-----------------\n💛 หากคุณยังไม่ได้รับการตรวจ คุณอาจเข้าข่ายมีความเสี่ยงที่ควรไปตรวจหาเชื้อเพื่อเข้าระบบการรักษาต่อไป นอกจากนั้น อย่าลืมเฝ้าระวังเเละประเมินอาการตนเองทุกวัน \n💛 หากคุณได้รับการตรวจเเละพบเชื้อโควิด-19 แต่ยังไม่อยู่ในการดูเเลของเเพทย์ คุณสามารถติดต่อที่ช่องทางด้านล่าง เพื่อเข้าสู่ระดับการรักษาอย่างทันท่วงที\n💛 ระหว่างนี้ คุณสามารถกดปุ่ม “คำแนะนำตามอาการที่พบอาการที่พบบ่อย” เพื่อศึกษาวิธีการดูเเลตนเองเบื้องต้นได้\n💛 หากคุณได้รับการตรวจเเละพบเชื้อโควิด-19 เเละอยู่ในการดูเเลของเเพทย์ โปรดปฏิบัติตามคำเเนะนำของเเพทย์ เเละระวังการเเพร่กระจายเชื้อให้บุคคลในบ้าน\n⚠️*หมายเหตุ: การประเมินด้วย”เพื่อนช่วยเช็ค” ใช้ระบบอัตโนมัติเพื่อให้คำเเนะนำเบื้องต้น ไม่สามารถใช้เป็นใบรับรองเเพทย์ได้\n💊 โดยคุณอาจได้รับยาจากแพทย์ที่ดูเเลคุณ ดังนี้ 👇🏻", +}; + +const yellowFaviImage: Message = { + type: "image", + originalContentUrl: + "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/TriageResults%2FYellow%2Ffavi.jpg?alt=media&token=f5a8b99f-b0ea-49cf-902b-2e7fb73764d0", + previewImageUrl: + "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/TriageResults%2FYellow%2Ffavi.jpg?alt=media&token=f5a8b99f-b0ea-49cf-902b-2e7fb73764d0", +}; + +const yellowContactImage: Message = { + type: "image", + originalContentUrl: + "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/TriageResults%2FYellow%2Fblurcat.jpg?alt=media&token=22933801-afe4-4b8a-9d57-22d94352bc1c", + previewImageUrl: + "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/TriageResults%2FYellow%2Fblurcat.jpg?alt=media&token=22933801-afe4-4b8a-9d57-22d94352bc1c", +}; + +const yellowMessages = [ + yellowDetailMessage, + yellowFaviImage, + yellowContactImage, +]; + +//end yellow + +//start red + +const redDetailMessage: Message = { + type: "text", + text: "❤️ จากการกรอกแบบประเมินอาการพบว่า คุณอาจมีอาการจากโรคโควิด-19 ในระดับสูง ซึ่งควรอยู่ภายใต้การดูแลของแพทย์อย่างใกล้ชิด*\n---------------\n❤️ หากคุณยังไม่ได้รับการตรวจเชื้อโควิด-19 โปรดเข้ารับบริการทางการเเพทย์ เพื่อตรวจเชื้อเเละเข้าสู่กระบวนการรักษาอย่างทันท่วงที ‼️\n❤️ หากคุณอยู่ในระบบการรักษาตัวที่บ้านสำหรับผู้ป่วยโควิด-19 โปรดติดต่อคลินิกหรือโรงพยาบาลที่ดูเเลคุณ เพื่อรับการรักษาอย่างทันท่วงที \n❤️ ไม่ต้องกังวล คุณมีช่องทางการติดต่อบุคลากรทางการเเพทย์ฉุกเฉินหรือหาเตียง ตามเบอร์ในภาพด้านล่าง 📞\n⚠️ *หมายเหตุ: การประเมินด้วย”เพื่อนช่วยเช็ค” ใช้ระบบอัตโนมัติเพื่อให้คำเเนะนำเบื้องต้น ไม่สามารถใช้เป็นใบรับรองเเพทย์ได้", +}; + +const redSleepingPostureImage: Message = { + type: "image", + originalContentUrl: + "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/TriageResults%2FRed%2Fsleeping_posture.jpg?alt=media&token=e30153fa-c5de-4f5c-97fe-1745bb095b77", + previewImageUrl: + "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/TriageResults%2FRed%2Fsleeping_posture.jpg?alt=media&token=e30153fa-c5de-4f5c-97fe-1745bb095b77", +}; + +const redContactImage: Message = { + type: "image", + originalContentUrl: + "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/TriageResults%2FRed%2Fblurcat.jpg?alt=media&token=ad8d2760-e3d9-471d-ae00-acabe8fcc627", + previewImageUrl: + "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/TriageResults%2FRed%2Fblurcat.jpg?alt=media&token=ad8d2760-e3d9-471d-ae00-acabe8fcc627", +}; + +const redMessages = [ + redDetailMessage, + redSleepingPostureImage, + redContactImage, +]; +//end red + +export { greenMessages, yellowMessages, redMessages }; From deefb88b7a655bae25e872a3ab2461f8e8c94cdc Mon Sep 17 00:00:00 2001 From: Ravipas Wangananont Date: Sun, 12 Sep 2021 22:42:31 +0700 Subject: [PATCH 31/34] remove unused imports --- functions/src/linefunctions/linepushmessage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functions/src/linefunctions/linepushmessage.ts b/functions/src/linefunctions/linepushmessage.ts index d6179dca..8b663673 100644 --- a/functions/src/linefunctions/linepushmessage.ts +++ b/functions/src/linefunctions/linepushmessage.ts @@ -1,4 +1,4 @@ -import * as _ from "lodash"; +// import * as _ from "lodash"; // import { convertTimestampToStr } from "../utils"; import axios from "axios"; import { statusList } from "../api/const"; From 7c6ccf208e3d6e032ca538a9588f746304416470 Mon Sep 17 00:00:00 2001 From: Ravipas Wangananont Date: Sun, 12 Sep 2021 23:14:20 +0700 Subject: [PATCH 32/34] correct message status --- .../src/linefunctions/linepushmessage.ts | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/functions/src/linefunctions/linepushmessage.ts b/functions/src/linefunctions/linepushmessage.ts index 8b663673..b77d9329 100644 --- a/functions/src/linefunctions/linepushmessage.ts +++ b/functions/src/linefunctions/linepushmessage.ts @@ -3,11 +3,15 @@ import axios from "axios"; import { statusList } from "../api/const"; import { UpdatedPatient } from "../types"; -import { greenMessages, redMessages, yellowMessages } from "../messages/triageResults"; +import { + greenMessages, + redMessages, + yellowMessages, +} from "../messages/triageResults"; import { Message } from "@line/bot-sdk/dist/types"; const baseURL = "https://api.line.me/v2/bot/message/push"; -type StatusObj = Omit +type StatusObj = Omit; // const symptomMapper = { // sym1_severe_cough: "มีอาการไอต่อเนื่อง", @@ -68,11 +72,15 @@ export const statusMap = { // return statusNumberMap[statusNumber]; // }; -export const sendPatientstatus = async (userId: string, statusObj: StatusObj, channelAccessToken: string) => { +export const sendPatientstatus = async ( + userId: string, + statusObj: StatusObj, + channelAccessToken: string +) => { // const date = convertTimestampToStr({ dateObj: statusObj.lastUpdatedAt }); - // const message = `วันที่: ${date.dateObj} + // const message = `วันที่: ${date.dateObj} // \nข้อมูลทั่วไป: - // - ค่าออกซิเจนปลายนิ้ว: ${statusObj.sp_o2 || "-"} + // - ค่าออกซิเจนปลายนิ้ว: ${statusObj.sp_o2 || "-"} // - ค่าออกซิเจนปลายนิ้ว ขณะหายใจปกติ: ${statusObj.sp_o2_ra || "-"} // - ค่าออกซิเจนปลายนิ้ว หลังลุกนั่ง 1 นาที: ${statusObj.sp_o2_after_eih || "-" // }`; @@ -86,7 +94,6 @@ export const sendPatientstatus = async (userId: string, statusObj: StatusObj, ch // const conclude = `\n\nผลลัพธ์: // - ระดับ: ${patientColor}`; - const defaultMessage: Message = { type: "text", text: "Error Triage Result Unknown", @@ -95,12 +102,12 @@ export const sendPatientstatus = async (userId: string, statusObj: StatusObj, ch let resultMessagePayload = []; switch (statusObj.status) { case statusList["G1"]: - resultMessagePayload = greenMessages - break; case statusList["G2"]: + resultMessagePayload = greenMessages; + break; case statusList["Y1"]: case statusList["Y2"]: - resultMessagePayload = yellowMessages + resultMessagePayload = yellowMessages; break; case statusList["R2"]: case statusList["R3"]: @@ -125,13 +132,12 @@ export const sendPatientstatus = async (userId: string, statusObj: StatusObj, ch const data = { to: userId, messages: resultMessagePayload, - } + }; // await axios(axiosConfig); await axios.post(baseURL, data, { headers: { "Content-Type": "application/json", Authorization: "Bearer " + channelAccessToken, - } - }) + }, + }); }; - From ccfebdeb82307546d231e704bc622dcff9f292b8 Mon Sep 17 00:00:00 2001 From: Ravipas Wangananont Date: Sun, 12 Sep 2021 23:55:11 +0700 Subject: [PATCH 33/34] added contact image --- functions/src/messages/triageResults.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/functions/src/messages/triageResults.ts b/functions/src/messages/triageResults.ts index 0ac1bfd8..ce7e36b4 100644 --- a/functions/src/messages/triageResults.ts +++ b/functions/src/messages/triageResults.ts @@ -64,9 +64,9 @@ const yellowFaviImage: Message = { const yellowContactImage: Message = { type: "image", originalContentUrl: - "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/TriageResults%2FYellow%2Fblurcat.jpg?alt=media&token=22933801-afe4-4b8a-9d57-22d94352bc1c", + "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/TriageResults%2FYellow%2Fcontact.jpg?alt=media&token=0bf82b15-0a86-4d6f-b22a-039c37a388ef", previewImageUrl: - "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/TriageResults%2FYellow%2Fblurcat.jpg?alt=media&token=22933801-afe4-4b8a-9d57-22d94352bc1c", + "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/TriageResults%2FYellow%2Fcontact.jpg?alt=media&token=0bf82b15-0a86-4d6f-b22a-039c37a388ef", }; const yellowMessages = [ @@ -95,9 +95,9 @@ const redSleepingPostureImage: Message = { const redContactImage: Message = { type: "image", originalContentUrl: - "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/TriageResults%2FRed%2Fblurcat.jpg?alt=media&token=ad8d2760-e3d9-471d-ae00-acabe8fcc627", + "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/TriageResults%2FRed%2Fcontact.jpg?alt=media&token=137c5664-6673-4cc2-a2e0-5b2736fdb524", previewImageUrl: - "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/TriageResults%2FRed%2Fblurcat.jpg?alt=media&token=ad8d2760-e3d9-471d-ae00-acabe8fcc627", + "https://firebasestorage.googleapis.com/v0/b/comcovid-prod.appspot.com/o/TriageResults%2FRed%2Fcontact.jpg?alt=media&token=137c5664-6673-4cc2-a2e0-5b2736fdb524", }; const redMessages = [ From aa4259a1724c10976571158d53eb1b3baca471bc Mon Sep 17 00:00:00 2001 From: Ravipas Wangananont Date: Tue, 14 Sep 2021 22:57:04 +0700 Subject: [PATCH 34/34] edited post triage message --- functions/src/controller/firestoreController/index.ts | 2 -- functions/src/messages/others.ts | 2 +- functions/src/messages/triageResults.ts | 4 ++-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/functions/src/controller/firestoreController/index.ts b/functions/src/controller/firestoreController/index.ts index b15c0106..9147459f 100644 --- a/functions/src/controller/firestoreController/index.ts +++ b/functions/src/controller/firestoreController/index.ts @@ -3,8 +3,6 @@ import { admin } from "../../init" import * as utils from "./utils" import { statusListReverse } from "../../api/const" - - export const onRegisterPatient: OnCreateHandler = async (snapshot, _context) => { try { const batch = admin.firestore().batch(); diff --git a/functions/src/messages/others.ts b/functions/src/messages/others.ts index d8556e57..7b81365a 100644 --- a/functions/src/messages/others.ts +++ b/functions/src/messages/others.ts @@ -2,7 +2,7 @@ import { Message } from "@line/bot-sdk"; const greetingMessage: Message = { type: "text", - text: "สวัสดีค่ะ เราคือ “เพื่อนช่วยเช็ค” เพื่อนที่จะมาช่วยให้คุณสามารถประเมินอาการตนเองเบื้องต้น เเละให้ข้อมูลที่ควรทราบเกี่ยวกับโควิด-19 ค่ะ 😘\n\n🌈คุณสามารถกด “เมนู” เพื่อเริ่มใช้งานเราค่ะ 🥰\n\n👉 กด “ประเมินอาการ” เพื่อประเมินอาการของตนเองเเละรับคำเเนะนำเบื้องต้นตามระดับอาการของคุณ\n👉 กด “เบอร์ติดต่อฉุกเฉิน” หากคุณต้องการเบอร์ติดต่อบุคลากรทางการเเพทย์หรือหาเตียง\n👉 กด “คำแนะนำตามอาการที่พบ” เพื่อศึกษาวิธีการดูเเลตนเองเบื้องต้นสำหรับอาการต่างๆจากโควิด-19\n👉 กด “ข้อควรรู้สำหรับผู้ป่วย” ดูข้อมูลการกินยาเเละสิ่งอื่นๆที่คุณควรทราบ สำหรับผู้ป่วยโควิด-19\n\n🚑 “เพื่อนช่วยเช็ค” ไม่มีบริการทางการเเพทย์ รบกวนผู้ป่วยติดต่อเบอร์ฉุกเฉิน คลินิก หรือโรงพยาบาลที่ดูเเลคุณหากมีเหตุจำเป็น หากคุณมีความเสี่ยงที่จะติดเชื้อ โปรดเข้ารับการตรวจหาเชื้อเพื่อเข้าระบบการรักษาต่อไป\n\n🌈 อย่าลืมมาประเมินอาการตนเองทุกวันนะคะ 😘\n🤝 พวกเราขอเป็นกำลังใจให้ทุกคนผ่านวิกฤตินี้ไปด้วยกัน 💫", + text: "สวัสดีค่ะ เราคือ “เพื่อนช่วยเช็ค” เพื่อนที่จะมาช่วยให้คุณสามารถประเมินอาการตนเองเบื้องต้น เเละให้ข้อมูลที่ควรทราบเกี่ยวกับโควิด-19 ค่ะ 😘\n\n🌈คุณสามารถกด “เมนู” เพื่อเริ่มใช้งานเราค่ะ 🥰\n\n👉 กด “ประเมินอาการ” สำหรับผู้ที่ติดเชื้อ ประเมินอาการของตนเองเเละรับคำเเนะนำเบื้องต้นตามระดับอาการของคุณ\n👉 กด “เบอร์ติดต่อฉุกเฉิน” หากคุณต้องการเบอร์ติดต่อบุคลากรทางการเเพทย์หรือหาเตียง\n👉 กด “คำแนะนำตามอาการที่พบ” เพื่อศึกษาวิธีการดูเเลตนเองเบื้องต้นสำหรับอาการต่างๆจากโควิด-19\n👉 กด “ข้อควรรู้สำหรับผู้ป่วย” ดูข้อมูลการกินยาเเละสิ่งอื่นๆที่คุณควรทราบ สำหรับผู้ป่วยโควิด-19\n\n🚑 “เพื่อนช่วยเช็ค” ไม่มีบริการทางการเเพทย์ รบกวนผู้ป่วยติดต่อเบอร์ฉุกเฉิน คลินิก หรือโรงพยาบาลที่ดูเเลคุณหากมีเหตุจำเป็น หากคุณมีความเสี่ยงที่จะติดเชื้อ โปรดเข้ารับการตรวจหาเชื้อเพื่อเข้าระบบการรักษาต่อไป\n\n🌈 อย่าลืมมาประเมินอาการตนเองทุกวันนะคะ 😘\n🤝 พวกเราขอเป็นกำลังใจให้ทุกคนผ่านวิกฤตินี้ไปด้วยกัน 💫", }; const greetingPhoto: Message = { diff --git a/functions/src/messages/triageResults.ts b/functions/src/messages/triageResults.ts index ce7e36b4..bd5c5c20 100644 --- a/functions/src/messages/triageResults.ts +++ b/functions/src/messages/triageResults.ts @@ -50,7 +50,7 @@ const greenMessages = [ const yellowDetailMessage: Message = { type: "text", - text: "💛 จากการกรอกแบบประเมินอาการพบว่า คุณอาจมีอาการจากโรคโควิด-19 อยู่พอสมควร*\n-----------------\n💛 หากคุณยังไม่ได้รับการตรวจ คุณอาจเข้าข่ายมีความเสี่ยงที่ควรไปตรวจหาเชื้อเพื่อเข้าระบบการรักษาต่อไป นอกจากนั้น อย่าลืมเฝ้าระวังเเละประเมินอาการตนเองทุกวัน \n💛 หากคุณได้รับการตรวจเเละพบเชื้อโควิด-19 แต่ยังไม่อยู่ในการดูเเลของเเพทย์ คุณสามารถติดต่อที่ช่องทางด้านล่าง เพื่อเข้าสู่ระดับการรักษาอย่างทันท่วงที\n💛 ระหว่างนี้ คุณสามารถกดปุ่ม “คำแนะนำตามอาการที่พบอาการที่พบบ่อย” เพื่อศึกษาวิธีการดูเเลตนเองเบื้องต้นได้\n💛 หากคุณได้รับการตรวจเเละพบเชื้อโควิด-19 เเละอยู่ในการดูเเลของเเพทย์ โปรดปฏิบัติตามคำเเนะนำของเเพทย์ เเละระวังการเเพร่กระจายเชื้อให้บุคคลในบ้าน\n⚠️*หมายเหตุ: การประเมินด้วย”เพื่อนช่วยเช็ค” ใช้ระบบอัตโนมัติเพื่อให้คำเเนะนำเบื้องต้น ไม่สามารถใช้เป็นใบรับรองเเพทย์ได้\n💊 โดยคุณอาจได้รับยาจากแพทย์ที่ดูเเลคุณ ดังนี้ 👇🏻", + text: '💛 จากการกรอกแบบประเมินอาการพบว่า คุณอาจมีอาการจากโรคโควิด-19 อยู่พอสมควร*\n⚠️*หมายเหตุ : คุณอาจไดัรับผลการประเมินนี้ โดยที่คุณอาจ “ไม่ได้ติดเชื้อโควิด-19”\n-----------------\n💛 หากคุณยังไม่ได้รับการตรวจ คุณอาจเข้าข่ายมีความเสี่ยงที่ควรไปตรวจหาเชื้อเพื่อเข้าระบบการรักษาต่อไป โดยสามารถทำเเบบสอบถามได้ที่ https://tinyurl.com/6pv3ucxs นอกจากนั้น อย่าลืมเฝ้าระวังเเละประเมินอาการตนเองทุกวัน \n💛 หากคุณได้รับการตรวจเเละพบเชื้อโควิด-19 แต่ยังไม่อยู่ในการดูเเลของเเพทย์ คุณสามารถติดต่อที่ช่องทางด้านล่าง เพื่อเข้าสู่ระดับการรักษาอย่างทันท่วงที\n💛 ระหว่างนี้ คุณสามารถกดปุ่ม “คำแนะนำตามอาการที่พบ” เพื่อศึกษาวิธีการดูเเลตนเองเบื้องต้นได้\n💛 หากคุณได้รับการตรวจเเละพบเชื้อโควิด-19 เเละอยู่ในการดูเเลของเเพทย์ โปรดปฏิบัติตามคำเเนะนำของเเพทย์ เเละระวังการเเพร่กระจายเชื้อให้บุคคลในบ้าน\n⚠️*หมายเหตุ: การประเมินด้วย”เพื่อนช่วยเช็ค” ใช้ระบบอัตโนมัติเพื่อให้คำเเนะนำเบื้องต้น ไม่สามารถใช้เป็นใบรับรองเเพทย์ได้\n💊 โดยคุณอาจได้รับยาจากแพทย์ที่ดูเเลคุณ ดังนี้ 👇🏻', }; const yellowFaviImage: Message = { @@ -81,7 +81,7 @@ const yellowMessages = [ const redDetailMessage: Message = { type: "text", - text: "❤️ จากการกรอกแบบประเมินอาการพบว่า คุณอาจมีอาการจากโรคโควิด-19 ในระดับสูง ซึ่งควรอยู่ภายใต้การดูแลของแพทย์อย่างใกล้ชิด*\n---------------\n❤️ หากคุณยังไม่ได้รับการตรวจเชื้อโควิด-19 โปรดเข้ารับบริการทางการเเพทย์ เพื่อตรวจเชื้อเเละเข้าสู่กระบวนการรักษาอย่างทันท่วงที ‼️\n❤️ หากคุณอยู่ในระบบการรักษาตัวที่บ้านสำหรับผู้ป่วยโควิด-19 โปรดติดต่อคลินิกหรือโรงพยาบาลที่ดูเเลคุณ เพื่อรับการรักษาอย่างทันท่วงที \n❤️ ไม่ต้องกังวล คุณมีช่องทางการติดต่อบุคลากรทางการเเพทย์ฉุกเฉินหรือหาเตียง ตามเบอร์ในภาพด้านล่าง 📞\n⚠️ *หมายเหตุ: การประเมินด้วย”เพื่อนช่วยเช็ค” ใช้ระบบอัตโนมัติเพื่อให้คำเเนะนำเบื้องต้น ไม่สามารถใช้เป็นใบรับรองเเพทย์ได้", + text: "❤️ จากการกรอกแบบประเมินอาการพบว่า คุณอาจมีอาการจากโรคโควิด-19 ในระดับสูง ซึ่งควรอยู่ภายใต้การดูแลของแพทย์อย่างใกล้ชิด*\n---------------\n❤️ หากคุณยังไม่ได้รับการตรวจเชื้อโควิด-19 โปรดเข้ารับบริการทางการเเพทย์ เพื่อตรวจเชื้อเเละเข้าสู่กระบวนการรักษาอย่างทันท่วงที‼️ โดยคุณสามารถทำเเบบทดสอบความเสี่ยงในการติดเชื้อได้ที่ https://tinyurl.com/6pv3ucxs \n❤️ คุณสามารถวัดออกซินเจน เเละทำเเบบประเมินอาการใหม่อีกครั้ง (ถ้ามี) เนื่องจากเครื่องวัดอาจมีการคาดเคลื่อน\n❤️ หากคุณอยู่ในระบบการรักษาตัวที่บ้านสำหรับผู้ป่วยโควิด-19 โปรดติดต่อคลินิกหรือโรงพยาบาลที่ดูเเลคุณ เพื่อรับการรักษาอย่างทันท่วงที \n❤️ ไม่ต้องกังวล คุณมีช่องทางการติดต่อบุคลากรทางการเเพทย์ฉุกเฉินหรือหาเตียง ตามเบอร์ในภาพด้านล่าง 📞\n⚠️ *หมายเหตุ: การประเมินด้วย”เพื่อนช่วยเช็ค” ใช้ระบบอัตโนมัติเพื่อให้คำเเนะนำเบื้องต้น ไม่สามารถใช้เป็นใบรับรองเเพทย์ได้", }; const redSleepingPostureImage: Message = {