More work on reservation waiting list support: adding waiting list module and connecting to email queue

This commit is contained in:
Collin Duncan 2023-07-29 10:49:44 +02:00
parent 56dda6fd28
commit e7503e3074
No known key found for this signature in database
12 changed files with 248 additions and 300 deletions

309
package-lock.json generated
View file

@ -21,7 +21,8 @@
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.14.0", "class-validator": "^0.14.0",
"dayjs": "^1.11.7", "dayjs": "^1.11.7",
"imapflow": "^1.0.134", "imap": "^0.8.19",
"mailparser-mit": "^1.0.0",
"puppeteer": "^20.4.0", "puppeteer": "^20.4.0",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"rxjs": "^7.2.0", "rxjs": "^7.2.0",
@ -34,8 +35,8 @@
"@types/cron": "^2.0.1", "@types/cron": "^2.0.1",
"@types/express": "^4.17.13", "@types/express": "^4.17.13",
"@types/imap": "^0.8.37", "@types/imap": "^0.8.37",
"@types/imapflow": "^1.0.13",
"@types/jest": "29.5.1", "@types/jest": "29.5.1",
"@types/mailparser-mit": "^1.0.1",
"@types/node": "18.16.12", "@types/node": "18.16.12",
"@types/puppeteer": "^7.0.4", "@types/puppeteer": "^7.0.4",
"@types/supertest": "^2.0.11", "@types/supertest": "^2.0.11",
@ -45,7 +46,6 @@
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-simple-import-sort": "^10.0.0", "eslint-plugin-simple-import-sort": "^10.0.0",
"imap": "^0.8.19",
"jest": "29.5.0", "jest": "29.5.0",
"prettier": "^2.3.2", "prettier": "^2.3.2",
"source-map-support": "^0.5.20", "source-map-support": "^0.5.20",
@ -2596,15 +2596,6 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/imapflow": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/@types/imapflow/-/imapflow-1.0.13.tgz",
"integrity": "sha512-TD8h02jSTDsXTE79HL0cSkjrU/ufXmSIsZv96O2sNBDA1VluqzKkjV2LrXSPIuXE1tn7JhND7o0iyP4r7haNFw==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/istanbul-lib-coverage": { "node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz",
@ -2650,6 +2641,15 @@
"integrity": "sha512-uKRI5QORDnrGFYgcdAVnHvEIvEZ8noTpP/Bg+HeUzZghwinDlIS87DEenV5r1YoOF9G4x600YsUXLWZ19rmTmg==", "integrity": "sha512-uKRI5QORDnrGFYgcdAVnHvEIvEZ8noTpP/Bg+HeUzZghwinDlIS87DEenV5r1YoOF9G4x600YsUXLWZ19rmTmg==",
"dev": true "dev": true
}, },
"node_modules/@types/mailparser-mit": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@types/mailparser-mit/-/mailparser-mit-1.0.1.tgz",
"integrity": "sha512-OKfkaDuyIOTixMDv5a3ypKXPFb2oY3NVKUlLrhQGkqFh6VwiE1sfzGnKCiMPKcvBEUwi7sqyZpAzcTMP6zxqJw==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/mime": { "node_modules/@types/mime": {
"version": "1.3.2", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
@ -3152,17 +3152,6 @@
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
}, },
"node_modules/abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
"dependencies": {
"event-target-shim": "^5.0.0"
},
"engines": {
"node": ">=6.5"
}
},
"node_modules/accepts": { "node_modules/accepts": {
"version": "1.3.8", "version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@ -3211,6 +3200,11 @@
"node": ">=0.4.0" "node": ">=0.4.0"
} }
}, },
"node_modules/addressparser": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/addressparser/-/addressparser-1.0.1.tgz",
"integrity": "sha512-aQX7AISOMM7HFE0iZ3+YnD07oIeJqWGVnJ+ZIKaBZAk03ftmVYVqsGas/rbXKR21n4D/hKCSHypvcyOkds/xzg=="
},
"node_modules/agent-base": { "node_modules/agent-base": {
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
@ -3460,14 +3454,6 @@
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"dev": true "dev": true
}, },
"node_modules/atomic-sleep": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
"integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/babel-jest": { "node_modules/babel-jest": {
"version": "29.5.0", "version": "29.5.0",
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz",
@ -4797,14 +4783,6 @@
"iconv-lite": "^0.6.2" "iconv-lite": "^0.6.2"
} }
}, },
"node_modules/encoding-japanese": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encoding-japanese/-/encoding-japanese-2.0.0.tgz",
"integrity": "sha512-++P0RhebUC8MJAwJOsT93dT+5oc5oPImp1HubZpAuCZ5kTLnhuuBhKHj2jJeO/Gj93idPBWmIuQ9QWMe5rX3pQ==",
"engines": {
"node": ">=8.10.0"
}
},
"node_modules/end-of-stream": { "node_modules/end-of-stream": {
"version": "1.4.4", "version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
@ -5361,14 +5339,6 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/event-target-shim": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
"engines": {
"node": ">=6"
}
},
"node_modules/events": { "node_modules/events": {
"version": "3.3.0", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
@ -5544,6 +5514,11 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
},
"node_modules/external-editor": { "node_modules/external-editor": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
@ -5636,14 +5611,6 @@
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="
}, },
"node_modules/fast-redact": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.2.0.tgz",
"integrity": "sha512-zaTadChr+NekyzallAMXATXLOR8MNx3zqpZ0MUF2aGf4EathnG0f32VLODNlY8IuGY3HoRO2L6/6fSzNsLaHIw==",
"engines": {
"node": ">=6"
}
},
"node_modules/fast-safe-stringify": { "node_modules/fast-safe-stringify": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
@ -6337,6 +6304,7 @@
"version": "0.6.3", "version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"optional": true,
"dependencies": { "dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0" "safer-buffer": ">= 2.1.2 < 3.0.0"
}, },
@ -6376,7 +6344,6 @@
"version": "0.8.19", "version": "0.8.19",
"resolved": "https://registry.npmjs.org/imap/-/imap-0.8.19.tgz", "resolved": "https://registry.npmjs.org/imap/-/imap-0.8.19.tgz",
"integrity": "sha512-z5DxEA1uRnZG73UcPA4ES5NSCGnPuuouUx43OPX7KZx1yzq3N8/vx2mtXEShT5inxB3pRgnfG1hijfu7XN2YMw==", "integrity": "sha512-z5DxEA1uRnZG73UcPA4ES5NSCGnPuuouUx43OPX7KZx1yzq3N8/vx2mtXEShT5inxB3pRgnfG1hijfu7XN2YMw==",
"dev": true,
"dependencies": { "dependencies": {
"readable-stream": "1.1.x", "readable-stream": "1.1.x",
"utf7": ">=1.0.2" "utf7": ">=1.0.2"
@ -6388,14 +6355,12 @@
"node_modules/imap/node_modules/isarray": { "node_modules/imap/node_modules/isarray": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="
"dev": true
}, },
"node_modules/imap/node_modules/readable-stream": { "node_modules/imap/node_modules/readable-stream": {
"version": "1.1.14", "version": "1.1.14",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
"integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==",
"dev": true,
"dependencies": { "dependencies": {
"core-util-is": "~1.0.0", "core-util-is": "~1.0.0",
"inherits": "~2.0.1", "inherits": "~2.0.1",
@ -6406,24 +6371,7 @@
"node_modules/imap/node_modules/string_decoder": { "node_modules/imap/node_modules/string_decoder": {
"version": "0.10.31", "version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ=="
"dev": true
},
"node_modules/imapflow": {
"version": "1.0.134",
"resolved": "https://registry.npmjs.org/imapflow/-/imapflow-1.0.134.tgz",
"integrity": "sha512-giJ9Byi89fzt5T7WHdroZonOoSQ9k7Bi9cSmfRT6zGC/v6/gKFnNG0xyoYyxpiKsJvYRImUsG6MBEKVdlbp2Eg==",
"dependencies": {
"encoding-japanese": "2.0.0",
"iconv-lite": "0.6.3",
"libbase64": "1.2.1",
"libmime": "5.2.1",
"libqp": "2.0.1",
"mailsplit": "5.4.0",
"nodemailer": "6.9.4",
"pino": "8.14.1",
"socks": "2.7.1"
}
}, },
"node_modules/import-fresh": { "node_modules/import-fresh": {
"version": "3.3.0", "version": "3.3.0",
@ -8576,32 +8524,11 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/libbase64": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/libbase64/-/libbase64-1.2.1.tgz",
"integrity": "sha512-l+nePcPbIG1fNlqMzrh68MLkX/gTxk/+vdvAb388Ssi7UuUN31MI44w4Yf33mM3Cm4xDfw48mdf3rkdHszLNew=="
},
"node_modules/libmime": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/libmime/-/libmime-5.2.1.tgz",
"integrity": "sha512-A0z9O4+5q+ZTj7QwNe/Juy1KARNb4WaviO4mYeFC4b8dBT2EEqK2pkM+GC8MVnkOjqhl5nYQxRgnPYRRTNmuSQ==",
"dependencies": {
"encoding-japanese": "2.0.0",
"iconv-lite": "0.6.3",
"libbase64": "1.2.1",
"libqp": "2.0.1"
}
},
"node_modules/libphonenumber-js": { "node_modules/libphonenumber-js": {
"version": "1.10.30", "version": "1.10.30",
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.30.tgz", "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.30.tgz",
"integrity": "sha512-PLGc+xfrQrkya/YK2/5X+bPpxRmyJBHM+xxz9krUdSgk4Vs2ZwxX5/Ow0lv3r9PDlDtNWb4u+it8MY5rZ0IyGw==" "integrity": "sha512-PLGc+xfrQrkya/YK2/5X+bPpxRmyJBHM+xxz9krUdSgk4Vs2ZwxX5/Ow0lv3r9PDlDtNWb4u+it8MY5rZ0IyGw=="
}, },
"node_modules/libqp": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/libqp/-/libqp-2.0.1.tgz",
"integrity": "sha512-Ka0eC5LkF3IPNQHJmYBWljJsw0UvM6j+QdKRbWyCdTmYwvIDE6a7bCm0UkTAL/K+3KXK5qXT/ClcInU01OpdLg=="
},
"node_modules/lines-and-columns": { "node_modules/lines-and-columns": {
"version": "1.2.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
@ -8774,25 +8701,26 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/mailsplit": { "node_modules/mailparser-mit": {
"version": "5.4.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/mailsplit/-/mailsplit-5.4.0.tgz", "resolved": "https://registry.npmjs.org/mailparser-mit/-/mailparser-mit-1.0.0.tgz",
"integrity": "sha512-wnYxX5D5qymGIPYLwnp6h8n1+6P6vz/MJn5AzGjZ8pwICWssL+CCQjWBIToOVHASmATot4ktvlLo6CyLfOXWYA==", "integrity": "sha512-sckRITNb3VCT1sQ275g47MAN786pQ5lU20bLY5f794dF/ARGzuVATQ64gO13FOw8jayjFT10e5ttsripKGGXcw==",
"dependencies": { "dependencies": {
"libbase64": "1.2.1", "addressparser": "^1.0.1",
"libmime": "5.2.0", "iconv-lite": "~0.4.24",
"libqp": "2.0.1" "mime": "^1.6.0",
"uue": "^3.1.0"
} }
}, },
"node_modules/mailsplit/node_modules/libmime": { "node_modules/mailparser-mit/node_modules/iconv-lite": {
"version": "5.2.0", "version": "0.4.24",
"resolved": "https://registry.npmjs.org/libmime/-/libmime-5.2.0.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-X2U5Wx0YmK0rXFbk67ASMeqYIkZ6E5vY7pNWRKtnNzqjvdYYG8xtPDpCnuUEnPU9vlgNev+JoSrcaKSUaNvfsw==", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"dependencies": { "dependencies": {
"encoding-japanese": "2.0.0", "safer-buffer": ">= 2.1.2 < 3"
"iconv-lite": "0.6.3", },
"libbase64": "1.2.1", "engines": {
"libqp": "2.0.1" "node": ">=0.10.0"
} }
}, },
"node_modules/make-dir": { "node_modules/make-dir": {
@ -9331,14 +9259,6 @@
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.8.tgz", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.8.tgz",
"integrity": "sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==" "integrity": "sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A=="
}, },
"node_modules/nodemailer": {
"version": "6.9.4",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.4.tgz",
"integrity": "sha512-CXjQvrQZV4+6X5wP6ZIgdehJamI63MFoYFGGPtHudWym9qaEHDNdPzaj5bfMCvxG1vhAileSWW90q7nL0N36mA==",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/nopt": { "node_modules/nopt": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
@ -9399,11 +9319,6 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/on-exit-leak-free": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz",
"integrity": "sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w=="
},
"node_modules/on-finished": { "node_modules/on-finished": {
"version": "2.4.1", "version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
@ -9846,79 +9761,6 @@
"url": "https://github.com/sponsors/jonschlinkert" "url": "https://github.com/sponsors/jonschlinkert"
} }
}, },
"node_modules/pino": {
"version": "8.14.1",
"resolved": "https://registry.npmjs.org/pino/-/pino-8.14.1.tgz",
"integrity": "sha512-8LYNv7BKWXSfS+k6oEc6occy5La+q2sPwU3q2ljTX5AZk7v+5kND2o5W794FyRaqha6DJajmkNRsWtPpFyMUdw==",
"dependencies": {
"atomic-sleep": "^1.0.0",
"fast-redact": "^3.1.1",
"on-exit-leak-free": "^2.1.0",
"pino-abstract-transport": "v1.0.0",
"pino-std-serializers": "^6.0.0",
"process-warning": "^2.0.0",
"quick-format-unescaped": "^4.0.3",
"real-require": "^0.2.0",
"safe-stable-stringify": "^2.3.1",
"sonic-boom": "^3.1.0",
"thread-stream": "^2.0.0"
},
"bin": {
"pino": "bin.js"
}
},
"node_modules/pino-abstract-transport": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.0.0.tgz",
"integrity": "sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA==",
"dependencies": {
"readable-stream": "^4.0.0",
"split2": "^4.0.0"
}
},
"node_modules/pino-abstract-transport/node_modules/buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.2.1"
}
},
"node_modules/pino-abstract-transport/node_modules/readable-stream": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.2.tgz",
"integrity": "sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA==",
"dependencies": {
"abort-controller": "^3.0.0",
"buffer": "^6.0.3",
"events": "^3.3.0",
"process": "^0.11.10",
"string_decoder": "^1.3.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/pino-std-serializers": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz",
"integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA=="
},
"node_modules/pirates": { "node_modules/pirates": {
"version": "4.0.5", "version": "4.0.5",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz",
@ -10010,24 +9852,11 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1" "url": "https://github.com/chalk/ansi-styles?sponsor=1"
} }
}, },
"node_modules/process": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
"integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/process-nextick-args": { "node_modules/process-nextick-args": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
}, },
"node_modules/process-warning": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/process-warning/-/process-warning-2.2.0.tgz",
"integrity": "sha512-/1WZ8+VQjR6avWOgHeEPd7SDQmFQ1B5mC1eRXsCm5TarlNmx/wCsa5GEaxGm05BORRtyG/Ex/3xq3TuRvq57qg=="
},
"node_modules/progress": { "node_modules/progress": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
@ -10261,11 +10090,6 @@
} }
] ]
}, },
"node_modules/quick-format-unescaped": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
"integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="
},
"node_modules/randombytes": { "node_modules/randombytes": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@ -10337,14 +10161,6 @@
"node": ">=8.10.0" "node": ">=8.10.0"
} }
}, },
"node_modules/real-require": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz",
"integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==",
"engines": {
"node": ">= 12.13.0"
}
},
"node_modules/rechoir": { "node_modules/rechoir": {
"version": "0.6.2", "version": "0.6.2",
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz",
@ -10571,14 +10387,6 @@
} }
] ]
}, },
"node_modules/safe-stable-stringify": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz",
"integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==",
"engines": {
"node": ">=10"
}
},
"node_modules/safer-buffer": { "node_modules/safer-buffer": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@ -10798,14 +10606,6 @@
"node": ">= 10" "node": ">= 10"
} }
}, },
"node_modules/sonic-boom": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.3.0.tgz",
"integrity": "sha512-LYxp34KlZ1a2Jb8ZQgFCK3niIHzibdwtwNUWKg0qQRzsDoJ3Gfgkf8KdBTFU3SkejDEIlWwnSnpVdOZIhFMl/g==",
"dependencies": {
"atomic-sleep": "^1.0.0"
}
},
"node_modules/source-map": { "node_modules/source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@ -10823,14 +10623,6 @@
"source-map": "^0.6.0" "source-map": "^0.6.0"
} }
}, },
"node_modules/split2": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
"engines": {
"node": ">= 10.x"
}
},
"node_modules/sprintf-js": { "node_modules/sprintf-js": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
@ -11259,14 +11051,6 @@
"node": ">=0.8" "node": ">=0.8"
} }
}, },
"node_modules/thread-stream": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.3.0.tgz",
"integrity": "sha512-kaDqm1DET9pp3NXwR8382WHbnpXnRkN9xGN9dQt3B2+dmXiW8X1SOwmFOxAErEQ47ObhZ96J6yhZNXuyCOL7KA==",
"dependencies": {
"real-require": "^0.2.0"
}
},
"node_modules/through": { "node_modules/through": {
"version": "2.3.8", "version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
@ -12063,7 +11847,6 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/utf7/-/utf7-1.0.2.tgz", "resolved": "https://registry.npmjs.org/utf7/-/utf7-1.0.2.tgz",
"integrity": "sha512-qQrPtYLLLl12NF4DrM9CvfkxkYI97xOb5dsnGZHE3teFr0tWiEZ9UdgMPczv24vl708cYMpe6mGXGHrotIp3Bw==", "integrity": "sha512-qQrPtYLLLl12NF4DrM9CvfkxkYI97xOb5dsnGZHE3teFr0tWiEZ9UdgMPczv24vl708cYMpe6mGXGHrotIp3Bw==",
"dev": true,
"dependencies": { "dependencies": {
"semver": "~5.3.0" "semver": "~5.3.0"
} }
@ -12072,7 +11855,6 @@
"version": "5.3.0", "version": "5.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
"integrity": "sha512-mfmm3/H9+67MCVix1h+IXTpDwL6710LyHuk7+cWC9T1mE0qz4iHhh6r4hU2wrIT9iTsAAC2XQRvfblL028cpLw==", "integrity": "sha512-mfmm3/H9+67MCVix1h+IXTpDwL6710LyHuk7+cWC9T1mE0qz4iHhh6r4hU2wrIT9iTsAAC2XQRvfblL028cpLw==",
"dev": true,
"bin": { "bin": {
"semver": "bin/semver" "semver": "bin/semver"
} }
@ -12090,6 +11872,15 @@
"node": ">= 0.4.0" "node": ">= 0.4.0"
} }
}, },
"node_modules/uue": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/uue/-/uue-3.1.2.tgz",
"integrity": "sha512-axKLXVqwtdI/czrjG0X8hyV1KLgeWx8F4KvSbvVCnS+RUvsQMGRjx0kfuZDXXqj0LYvVJmx3B9kWlKtEdRrJLg==",
"dependencies": {
"escape-string-regexp": "~1.0.5",
"extend": "~3.0.0"
}
},
"node_modules/uuid": { "node_modules/uuid": {
"version": "8.3.2", "version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",

View file

@ -38,6 +38,7 @@
"class-validator": "^0.14.0", "class-validator": "^0.14.0",
"dayjs": "^1.11.7", "dayjs": "^1.11.7",
"imap": "^0.8.19", "imap": "^0.8.19",
"mailparser-mit": "^1.0.0",
"puppeteer": "^20.4.0", "puppeteer": "^20.4.0",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"rxjs": "^7.2.0", "rxjs": "^7.2.0",
@ -51,6 +52,7 @@
"@types/express": "^4.17.13", "@types/express": "^4.17.13",
"@types/imap": "^0.8.37", "@types/imap": "^0.8.37",
"@types/jest": "29.5.1", "@types/jest": "29.5.1",
"@types/mailparser-mit": "^1.0.1",
"@types/node": "18.16.12", "@types/node": "18.16.12",
"@types/puppeteer": "^7.0.4", "@types/puppeteer": "^7.0.4",
"@types/supertest": "^2.0.11", "@types/supertest": "^2.0.11",

View file

@ -11,6 +11,7 @@ import { LoggerModule } from './logger/module'
import { RecurringReservationsModule } from './recurringReservations/module' import { RecurringReservationsModule } from './recurringReservations/module'
import { ReservationsModule } from './reservations/module' import { ReservationsModule } from './reservations/module'
import { RunnerModule } from './runner/module' import { RunnerModule } from './runner/module'
import { WaitingListModule } from './waitingList/module'
@Module({ @Module({
imports: [ imports: [
@ -48,6 +49,7 @@ import { RunnerModule } from './runner/module'
RunnerModule, RunnerModule,
LoggerModule, LoggerModule,
EmailModule, EmailModule,
WaitingListModule,
], ],
}) })
export class AppModule implements NestModule { export class AppModule implements NestModule {

View file

@ -1,9 +1,8 @@
import { Inject, Injectable } from '@nestjs/common' import { Inject, Injectable } from '@nestjs/common'
import { ConfigService } from '@nestjs/config' import { ConfigService } from '@nestjs/config'
import { Dayjs } from 'dayjs'
import * as Imap from 'imap' import * as Imap from 'imap'
import { MailParser, ParsedEmail } from 'mailparser-mit'
import dayjs from '../common/dayjs'
import { LoggerService } from '../logger/service' import { LoggerService } from '../logger/service'
import { Email } from './types' import { Email } from './types'
@ -59,12 +58,12 @@ export class EmailClient {
if (current > max) { if (current > max) {
throw Error(`Max attempts reached for ${label}`) throw Error(`Max attempts reached for ${label}`)
} }
this.loggerService.log(`Attempting ${label} [${current} / ${max}]`) this.loggerService.debug(`Attempting ${label} [${current} / ${max}]`)
try { try {
fn() fn()
} catch (error: unknown) { } catch (error: unknown) {
this.loggerService.warn( this.loggerService.debug(
`Attempting ${label} hit error at attempt ${current}`, `Attempting ${label} hit error at attempt ${current}`,
) )
setTimeout( setTimeout(
@ -85,6 +84,10 @@ export class EmailClient {
messages: { total: totalMessages, new: newMessages }, messages: { total: totalMessages, new: newMessages },
} = mailbox } = mailbox
this.loggerService.debug(
`Total messages: ${totalMessages}; New messages: ${newMessages}; Received messages: ${numMessages}`,
)
if (newMessages > 0) { if (newMessages > 0) {
const startingMessage = totalMessages - newMessages + 1 // starting message is inclusive const startingMessage = totalMessages - newMessages + 1 // starting message is inclusive
const emails = await this.fetchMails(`${startingMessage}`, '*') const emails = await this.fetchMails(`${startingMessage}`, '*')
@ -132,47 +135,36 @@ export class EmailClient {
}) })
} }
private async generateEmailBody(
stream: NodeJS.ReadableStream,
): Promise<Buffer> {
return new Promise<Buffer>((res, rej) => {
const bufferData: any[] = []
stream.on('data', (chunk) => {
bufferData.push(chunk)
})
stream.on('end', () => {
res(Buffer.concat(bufferData))
})
stream.on('error', (error: Error) => rej(error))
})
}
private async generateEmail(msg: Imap.ImapMessage): Promise<Email> { private async generateEmail(msg: Imap.ImapMessage): Promise<Email> {
return new Promise((res, rej) => { return new Promise((res, rej) => {
let id: number const mailParser = new MailParser({ defaultCharset: 'utf-8' })
let date: Dayjs let id: string
let bodyPromise: Promise<Buffer>
msg.on('body', async (stream) => { msg.on('body', async (stream) => {
try { try {
bodyPromise = this.generateEmailBody(stream) stream.pipe(mailParser)
} catch (error: unknown) { } catch (error: unknown) {
rej(error) rej(error)
} }
}) })
msg.on('attributes', (attr) => { msg.on('attributes', (attr) => {
id = attr.uid id = `${attr.uid}`
date = dayjs(attr.date)
}) })
msg.on('error', (error: Error) => rej(error)) msg.on('error', (error: Error) => rej(error))
msg.on('end', async () => { mailParser.on('error', (error: Error) => rej(error))
const body = (await bodyPromise).toString()
mailParser.on('end', (mail: ParsedEmail) => {
const { from = [], subject = '', text = '' } = mail
const fromString = from
.map(({ address, name }) => `${name} <${address}>`)
.join(';')
res({ res({
id, id,
date, from: fromString,
body, subject,
text,
}) })
}) })
}) })
@ -186,7 +178,7 @@ export class EmailClient {
`${startingMessageId}:${endingMessageId}`, `${startingMessageId}:${endingMessageId}`,
{ {
bodies: '', bodies: '',
struct: true, markSeen: true,
}, },
) )
@ -220,6 +212,10 @@ export class EmailClient {
this.loggerService.debug('email client end') this.loggerService.debug('email client end')
this.setStatus(EmailClientStatus.NotReady) this.setStatus(EmailClientStatus.NotReady)
}) })
this.imapClient.on('error', (error: Error) => {
this.loggerService.error(`Error with imap client ${error.message}`)
})
} }
public addCustomListener( public addCustomListener(

1
src/email/config.ts Normal file
View file

@ -0,0 +1 @@
export const EMAILS_QUEUE_NAME = 'emails'

View file

@ -1,12 +1,17 @@
import { BullModule } from '@nestjs/bull'
import { Module } from '@nestjs/common' import { Module } from '@nestjs/common'
import { LoggerModule } from '../logger/module' import { LoggerModule } from '../logger/module'
import { EmailClient } from './client' import { EmailClient } from './client'
import { EMAILS_QUEUE_NAME } from './config'
import { EmailProvider } from './provider' import { EmailProvider } from './provider'
@Module({ @Module({
imports: [
LoggerModule,
BullModule.registerQueue({ name: EMAILS_QUEUE_NAME }),
],
providers: [EmailClient, EmailProvider], providers: [EmailClient, EmailProvider],
imports: [LoggerModule],
exports: [EmailClient, EmailProvider], exports: [EmailClient, EmailProvider],
}) })
export class EmailModule {} export class EmailModule {}

View file

@ -1,8 +1,9 @@
import { InjectQueue } from '@nestjs/bull'
import { Inject, Injectable } from '@nestjs/common' import { Inject, Injectable } from '@nestjs/common'
import { Box } from 'imap' import { Queue } from 'bull'
import { LoggerService } from '../logger/service'
import { EmailClient } from './client' import { EmailClient } from './client'
import { EMAILS_QUEUE_NAME } from './config'
import { Email } from './types' import { Email } from './types'
@Injectable() @Injectable()
@ -11,14 +12,16 @@ export class EmailProvider {
@Inject(EmailClient) @Inject(EmailClient)
private readonly emailClient: EmailClient, private readonly emailClient: EmailClient,
@Inject(LoggerService) @InjectQueue(EMAILS_QUEUE_NAME)
private readonly loggerService: LoggerService, private readonly emailsQueue: Queue,
) { ) {
this.registerEmailListener() this.registerEmailListener()
} }
private async handleReceivedEmails(emails: Email[]) { private async handleReceivedEmails(emails: Email[]) {
this.loggerService.debug(JSON.stringify(emails)) this.emailsQueue.addBulk(
emails.map((email) => ({ name: email.id, data: email })),
)
} }
public registerEmailListener() { public registerEmailListener() {

View file

@ -1,7 +1,6 @@
import { Dayjs } from 'dayjs'
export interface Email { export interface Email {
id: number id: string
date: Dayjs from: string
body: string subject: string
text: string
} }

View file

@ -27,6 +27,14 @@ export class ReservationsService {
.getMany() .getMany()
} }
getByDateOnWaitingList(date = dayjs()) {
return this.reservationsRepository
.createQueryBuilder()
.where(`DATE(dateRangeStart, '-7 day') = DATE(:date)`, { date })
.andWhere('waitListed = true')
.getMany()
}
create(reservation: Reservation) { create(reservation: Reservation) {
return this.reservationsRepository.save(reservation) return this.reservationsRepository.save(reservation)
} }

20
src/waitingList/module.ts Normal file
View file

@ -0,0 +1,20 @@
import { BullModule } from '@nestjs/bull'
import { Module } from '@nestjs/common'
import { ReservationsModule } from 'src/reservations/module'
import { EMAILS_QUEUE_NAME } from '../email/config'
import { LoggerModule } from '../logger/module'
import { RESERVATIONS_QUEUE_NAME } from '../reservations/config'
import { WaitingListService } from './service'
@Module({
imports: [
LoggerModule,
ReservationsModule,
BullModule.registerQueue({ name: EMAILS_QUEUE_NAME }),
BullModule.registerQueue({ name: RESERVATIONS_QUEUE_NAME }),
],
providers: [WaitingListService],
exports: [WaitingListService],
})
export class WaitingListModule {}

116
src/waitingList/service.ts Normal file
View file

@ -0,0 +1,116 @@
import { InjectQueue, Process, Processor } from '@nestjs/bull'
import { Inject } from '@nestjs/common'
import { Job, Queue } from 'bull'
import { ReservationsService } from 'src/reservations/service'
import dayjs from '../common/dayjs'
import { EMAILS_QUEUE_NAME } from '../email/config'
import { Email } from '../email/types'
import { LoggerService } from '../logger/service'
import { RESERVATIONS_QUEUE_NAME } from '../reservations/config'
import { WaitingListDetails } from './types'
const EMAIL_SUBJECT_REGEX = new RegExp(
/^personal waitinglist reservation free at/i,
)
const EMAIL_ADDRESS = 'Squash City <no-reply@i-reservations.nl>'
const EMAIL_DATE_REGEX = new RegExp(
/^Datum: ([0-9]{1,2}-[0-9]{1,2}-[0,9]{4})$/im,
)
const EMAIL_START_TIME_REGEX = new RegExp(
/^Begintijd: ([0-9]{1,2}:[0-9]{1,2})$/im,
)
const EMAIL_END_TIME_REGEX = new RegExp(/^Eindtijd: ([0-9]{1,2}:[0-9]{1,2})$/im)
@Processor(EMAILS_QUEUE_NAME)
export class WaitingListService {
constructor(
@InjectQueue(RESERVATIONS_QUEUE_NAME)
private readonly reservationsQueue: Queue,
@Inject(ReservationsService)
private readonly reservationsService: ReservationsService,
@Inject(LoggerService)
private readonly loggerService: LoggerService,
) {}
@Process()
async processEmail(job: Job<Email>) {
const { data: email } = job
this.loggerService.log('Handling email', {
id: email.id,
from: email.from,
subject: email.subject,
})
if (!this.isRelevantEmail) return
await this.handleWaitingListEmail(email)
}
private async handleWaitingListEmail(email: Email) {
const { date, startTime } = this.getWaitingListDetails(email)
const dateRangeStart = dayjs(`${date} ${startTime}`)
const reservations = await this.reservationsService.getByDateOnWaitingList(
dateRangeStart,
)
if (reservations.length) {
this.loggerService.error('Found no reservations on waiting list')
return
}
this.loggerService.log(
`Found ${reservations.length} reservations on waiting list`,
)
await this.reservationsQueue.addBulk(reservations.map((r) => ({ data: r })))
}
private getWaitingListDetails(email: Email): WaitingListDetails {
const dateResult = EMAIL_DATE_REGEX.exec(email.text)
const startTimeResult = EMAIL_START_TIME_REGEX.exec(email.text)
const endTimeResult = EMAIL_END_TIME_REGEX.exec(email.text)
if (dateResult == null || dateResult[1] == null) {
throw new WaitingListDetailsError('Date not found')
}
if (startTimeResult == null || startTimeResult[1] == null) {
throw new WaitingListDetailsError('Start time not found')
}
if (endTimeResult == null || endTimeResult[1] == null) {
throw new WaitingListDetailsError('End time not found')
}
return {
date: dateResult[1],
startTime: startTimeResult[1],
endTime: endTimeResult[1],
}
}
private isRelevantEmail(email: Email): boolean {
if (!EMAIL_SUBJECT_REGEX.test(email.subject)) {
this.loggerService.log('Ignoring email, irrelevant subject', {
id: email.id,
subject: email.subject,
})
return false
}
if (EMAIL_ADDRESS !== email.from) {
this.loggerService.log('Ignoring email, irrelevant sender', {
id: email.id,
sender: email.from,
})
return false
}
return true
}
}
export class WaitingListDetailsError extends Error {}

5
src/waitingList/types.ts Normal file
View file

@ -0,0 +1,5 @@
export interface WaitingListDetails {
date: string
startTime: string
endTime: string
}