fix: unset NODE_ENV in e2e job so devDependencies install #19
Generated
+198
-25
@@ -12,6 +12,8 @@
|
|||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"multer": "^1.4.5-lts.1",
|
"multer": "^1.4.5-lts.1",
|
||||||
|
"node-cron": "^3.0.3",
|
||||||
|
"node-pg-migrate": "^7.6.1",
|
||||||
"nodemailer": "^6.9.14",
|
"nodemailer": "^6.9.14",
|
||||||
"pg": "^8.12.0"
|
"pg": "^8.12.0"
|
||||||
},
|
},
|
||||||
@@ -22,6 +24,7 @@
|
|||||||
"@types/jest": "^29.5.12",
|
"@types/jest": "^29.5.12",
|
||||||
"@types/multer": "^1.4.11",
|
"@types/multer": "^1.4.11",
|
||||||
"@types/node": "^20.14.15",
|
"@types/node": "^20.14.15",
|
||||||
|
"@types/node-cron": "^3.0.11",
|
||||||
"@types/nodemailer": "^6.4.15",
|
"@types/nodemailer": "^6.4.15",
|
||||||
"@types/pg": "^8.11.6",
|
"@types/pg": "^8.11.6",
|
||||||
"@types/supertest": "^6.0.2",
|
"@types/supertest": "^6.0.2",
|
||||||
@@ -1020,6 +1023,14 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@isaacs/cliui": {
|
||||||
|
"version": "9.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz",
|
||||||
|
"integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@istanbuljs/load-nyc-config": {
|
"node_modules/@istanbuljs/load-nyc-config": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
|
||||||
@@ -1638,12 +1649,18 @@
|
|||||||
"version": "20.19.43",
|
"version": "20.19.43",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz",
|
||||||
"integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==",
|
"integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.21.0"
|
"undici-types": "~6.21.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/node-cron": {
|
||||||
|
"version": "3.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node-cron/-/node-cron-3.0.11.tgz",
|
||||||
|
"integrity": "sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/nodemailer": {
|
"node_modules/@types/nodemailer": {
|
||||||
"version": "6.4.24",
|
"version": "6.4.24",
|
||||||
"resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.24.tgz",
|
"resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.24.tgz",
|
||||||
@@ -1658,7 +1675,7 @@
|
|||||||
"version": "8.21.0",
|
"version": "8.21.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.21.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.21.0.tgz",
|
||||||
"integrity": "sha512-AYdtudzabjLZgVgRZmAnU8bAnVUXzuJX2IYHeSIiIHm68olD+LgQYCGWdtcNYnP0uq9c4S4NibVG3Ni7VbKW7Q==",
|
"integrity": "sha512-AYdtudzabjLZgVgRZmAnU8bAnVUXzuJX2IYHeSIiIHm68olD+LgQYCGWdtcNYnP0uq9c4S4NibVG3Ni7VbKW7Q==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
@@ -1794,7 +1811,6 @@
|
|||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
@@ -1804,7 +1820,6 @@
|
|||||||
"version": "4.3.0",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"color-convert": "^2.0.1"
|
"color-convert": "^2.0.1"
|
||||||
@@ -2263,7 +2278,6 @@
|
|||||||
"version": "8.0.1",
|
"version": "8.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
||||||
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
|
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"string-width": "^4.2.0",
|
"string-width": "^4.2.0",
|
||||||
@@ -2296,7 +2310,6 @@
|
|||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"color-name": "~1.1.4"
|
"color-name": "~1.1.4"
|
||||||
@@ -2309,7 +2322,6 @@
|
|||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/combined-stream": {
|
"node_modules/combined-stream": {
|
||||||
@@ -2452,7 +2464,6 @@
|
|||||||
"version": "7.0.6",
|
"version": "7.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"path-key": "^3.1.0",
|
"path-key": "^3.1.0",
|
||||||
@@ -2601,7 +2612,6 @@
|
|||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/encodeurl": {
|
"node_modules/encodeurl": {
|
||||||
@@ -2715,7 +2725,6 @@
|
|||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
||||||
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
@@ -2925,6 +2934,32 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/foreground-child": {
|
||||||
|
"version": "3.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
|
||||||
|
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
|
||||||
|
"dependencies": {
|
||||||
|
"cross-spawn": "^7.0.6",
|
||||||
|
"signal-exit": "^4.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/foreground-child/node_modules/signal-exit": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/form-data": {
|
"node_modules/form-data": {
|
||||||
"version": "4.0.6",
|
"version": "4.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
|
||||||
@@ -3023,7 +3058,6 @@
|
|||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "6.* || 8.* || >= 10.*"
|
"node": "6.* || 8.* || >= 10.*"
|
||||||
@@ -3335,7 +3369,6 @@
|
|||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
@@ -3384,7 +3417,6 @@
|
|||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/istanbul-lib-coverage": {
|
"node_modules/istanbul-lib-coverage": {
|
||||||
@@ -3496,6 +3528,20 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jackspeak": {
|
||||||
|
"version": "4.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz",
|
||||||
|
"integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@isaacs/cliui": "^9.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "20 || >=22"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/jest": {
|
"node_modules/jest": {
|
||||||
"version": "29.7.0",
|
"version": "29.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz",
|
||||||
@@ -4371,6 +4417,14 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/minipass": {
|
||||||
|
"version": "7.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
|
||||||
|
"integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16 || 14 >=14.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mkdirp": {
|
"node_modules/mkdirp": {
|
||||||
"version": "0.5.6",
|
"version": "0.5.6",
|
||||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
||||||
@@ -4431,6 +4485,17 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/node-cron": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==",
|
||||||
|
"dependencies": {
|
||||||
|
"uuid": "8.3.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/node-int64": {
|
"node_modules/node-int64": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
|
||||||
@@ -4438,6 +4503,88 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/node-pg-migrate": {
|
||||||
|
"version": "7.9.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-pg-migrate/-/node-pg-migrate-7.9.1.tgz",
|
||||||
|
"integrity": "sha512-6z4OSN27ye8aYdX9ZU7NN2PTI5pOp34hTr+22Ej12djIYECq++gT7LPLZVOQXEeVCBOZQLqf87kC3Y36G434OQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"glob": "~11.0.0",
|
||||||
|
"yargs": "~17.7.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"node-pg-migrate": "bin/node-pg-migrate.js",
|
||||||
|
"node-pg-migrate-cjs": "bin/node-pg-migrate.js",
|
||||||
|
"node-pg-migrate-esm": "bin/node-pg-migrate.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.19.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/pg": ">=6.0.0 <9.0.0",
|
||||||
|
"pg": ">=4.3.0 <9.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/pg": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/node-pg-migrate/node_modules/balanced-match": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
|
||||||
|
"engines": {
|
||||||
|
"node": "18 || 20 || >=22"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/node-pg-migrate/node_modules/brace-expansion": {
|
||||||
|
"version": "5.0.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
|
||||||
|
"integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
|
||||||
|
"dependencies": {
|
||||||
|
"balanced-match": "^4.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "20 || >=22"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/node-pg-migrate/node_modules/glob": {
|
||||||
|
"version": "11.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz",
|
||||||
|
"integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==",
|
||||||
|
"deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
|
||||||
|
"dependencies": {
|
||||||
|
"foreground-child": "^3.3.1",
|
||||||
|
"jackspeak": "^4.1.1",
|
||||||
|
"minimatch": "^10.0.3",
|
||||||
|
"minipass": "^7.1.2",
|
||||||
|
"package-json-from-dist": "^1.0.0",
|
||||||
|
"path-scurry": "^2.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"glob": "dist/esm/bin.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "20 || >=22"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/node-pg-migrate/node_modules/minimatch": {
|
||||||
|
"version": "10.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz",
|
||||||
|
"integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==",
|
||||||
|
"dependencies": {
|
||||||
|
"brace-expansion": "^5.0.8"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "18 || 20 || >=22"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/node-releases": {
|
"node_modules/node-releases": {
|
||||||
"version": "2.0.53",
|
"version": "2.0.53",
|
||||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz",
|
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz",
|
||||||
@@ -4594,6 +4741,11 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/package-json-from-dist": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="
|
||||||
|
},
|
||||||
"node_modules/parse-json": {
|
"node_modules/parse-json": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
|
||||||
@@ -4646,7 +4798,6 @@
|
|||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
@@ -4659,6 +4810,29 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/path-scurry": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
|
||||||
|
"dependencies": {
|
||||||
|
"lru-cache": "^11.0.0",
|
||||||
|
"minipass": "^7.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "18 || 20 || >=22"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/path-scurry/node_modules/lru-cache": {
|
||||||
|
"version": "11.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz",
|
||||||
|
"integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==",
|
||||||
|
"engines": {
|
||||||
|
"node": "20 || >=22"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/path-to-regexp": {
|
"node_modules/path-to-regexp": {
|
||||||
"version": "0.1.13",
|
"version": "0.1.13",
|
||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
|
||||||
@@ -4986,7 +5160,6 @@
|
|||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||||
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
|
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@@ -5138,7 +5311,6 @@
|
|||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"shebang-regex": "^3.0.0"
|
"shebang-regex": "^3.0.0"
|
||||||
@@ -5151,7 +5323,6 @@
|
|||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
@@ -5353,7 +5524,6 @@
|
|||||||
"version": "4.2.3",
|
"version": "4.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"emoji-regex": "^8.0.0",
|
"emoji-regex": "^8.0.0",
|
||||||
@@ -5368,7 +5538,6 @@
|
|||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-regex": "^5.0.1"
|
"ansi-regex": "^5.0.1"
|
||||||
@@ -5736,7 +5905,7 @@
|
|||||||
"version": "6.21.0",
|
"version": "6.21.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/unpipe": {
|
"node_modules/unpipe": {
|
||||||
@@ -5794,6 +5963,15 @@
|
|||||||
"node": ">= 0.4.0"
|
"node": ">= 0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/uuid": {
|
||||||
|
"version": "8.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||||
|
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||||
|
"deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).",
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist/bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/v8-to-istanbul": {
|
"node_modules/v8-to-istanbul": {
|
||||||
"version": "9.3.0",
|
"version": "9.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
|
||||||
@@ -5832,7 +6010,6 @@
|
|||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"isexe": "^2.0.0"
|
"isexe": "^2.0.0"
|
||||||
@@ -5855,7 +6032,6 @@
|
|||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||||
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-styles": "^4.0.0",
|
"ansi-styles": "^4.0.0",
|
||||||
@@ -5903,7 +6079,6 @@
|
|||||||
"version": "5.0.8",
|
"version": "5.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||||
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
|
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
@@ -5920,7 +6095,6 @@
|
|||||||
"version": "17.7.3",
|
"version": "17.7.3",
|
||||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz",
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz",
|
||||||
"integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==",
|
"integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cliui": "^8.0.1",
|
"cliui": "^8.0.1",
|
||||||
@@ -5939,7 +6113,6 @@
|
|||||||
"version": "21.1.1",
|
"version": "21.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
||||||
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
|
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
|
|||||||
@@ -22,9 +22,21 @@ async function getAccessToken(): Promise<string> {
|
|||||||
return data.access_token;
|
return data.access_token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CartItem {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
price_cents: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LockedCart {
|
||||||
|
cartId: number;
|
||||||
|
items: CartItem[];
|
||||||
|
totalCents: number;
|
||||||
|
}
|
||||||
|
|
||||||
// Locks the customer's cart, verifies every item is still reserved to them,
|
// Locks the customer's cart, verifies every item is still reserved to them,
|
||||||
// and returns { cartId, items: [{id, name, price_cents}], totalCents }.
|
// and returns { cartId, items: [{id, name, price_cents}], totalCents }.
|
||||||
async function loadLockedCart(client: any, customerId: number) {
|
async function loadLockedCart(client: any, customerId: number): Promise<LockedCart | null> {
|
||||||
const { rows: cartRows } = await client.query(`SELECT id FROM carts WHERE customer_id = $1`, [customerId]);
|
const { rows: cartRows } = await client.query(`SELECT id FROM carts WHERE customer_id = $1`, [customerId]);
|
||||||
if (!cartRows.length) return null;
|
if (!cartRows.length) return null;
|
||||||
const cartId = cartRows[0].id;
|
const cartId = cartRows[0].id;
|
||||||
@@ -37,10 +49,49 @@ async function loadLockedCart(client: any, customerId: number) {
|
|||||||
[cartId]
|
[cartId]
|
||||||
);
|
);
|
||||||
if (!items.length) return { cartId, items: [], totalCents: 0 };
|
if (!items.length) return { cartId, items: [], totalCents: 0 };
|
||||||
const totalCents = items.reduce((sum: number, it: any) => sum + it.price_cents, 0);
|
const totalCents = items.reduce((sum: number, it: CartItem) => sum + it.price_cents, 0);
|
||||||
return { cartId, items, totalCents };
|
return { cartId, items, totalCents };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OpenedCheckout =
|
||||||
|
| { ok: true; checkoutId: number; cart: LockedCart }
|
||||||
|
| { ok: false; error: string };
|
||||||
|
|
||||||
|
// Both checkout flows open the same way: confirm the shipping address belongs
|
||||||
|
// to the caller, lock the cart, and record a pending checkout with its line
|
||||||
|
// items. The caller owns the transaction — on `ok: false` it should roll back
|
||||||
|
// and return the error as a 400.
|
||||||
|
async function openCheckout(
|
||||||
|
client: any,
|
||||||
|
customerId: number,
|
||||||
|
shippingAddressId: number,
|
||||||
|
processor: string,
|
||||||
|
processorOrderId: string | null
|
||||||
|
): Promise<OpenedCheckout> {
|
||||||
|
const { rows: addrRows } = await client.query(
|
||||||
|
`SELECT id FROM shipping_addresses WHERE id = $1 AND customer_id = $2`,
|
||||||
|
[shippingAddressId, customerId]
|
||||||
|
);
|
||||||
|
if (!addrRows.length) return { ok: false, error: 'invalid shipping address' };
|
||||||
|
|
||||||
|
const cart = await loadLockedCart(client, customerId);
|
||||||
|
if (!cart || !cart.items.length) return { ok: false, error: 'cart is empty' };
|
||||||
|
|
||||||
|
const { rows: checkoutRows } = await client.query(
|
||||||
|
`INSERT INTO checkouts (customer_id, shipping_address_id, processor, processor_order_id, amount_cents, status)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, 'pending') RETURNING id`,
|
||||||
|
[customerId, shippingAddressId, processor, processorOrderId, cart.totalCents]
|
||||||
|
);
|
||||||
|
const checkoutId = checkoutRows[0].id;
|
||||||
|
for (const it of cart.items) {
|
||||||
|
await client.query(
|
||||||
|
`INSERT INTO checkout_items (checkout_id, item_id, price_cents) VALUES ($1, $2, $3)`,
|
||||||
|
[checkoutId, it.id, it.price_cents]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return { ok: true, checkoutId, cart };
|
||||||
|
}
|
||||||
|
|
||||||
router.post('/paypal/create', requireCustomer, async (req: Request, res: Response) => {
|
router.post('/paypal/create', requireCustomer, async (req: Request, res: Response) => {
|
||||||
const { shippingAddressId } = req.body;
|
const { shippingAddressId } = req.body;
|
||||||
if (!shippingAddressId) return res.status(400).json({ error: 'shippingAddressId is required' });
|
if (!shippingAddressId) return res.status(400).json({ error: 'shippingAddressId is required' });
|
||||||
@@ -48,27 +99,9 @@ router.post('/paypal/create', requireCustomer, async (req: Request, res: Respons
|
|||||||
const client = await pool.connect();
|
const client = await pool.connect();
|
||||||
try {
|
try {
|
||||||
await client.query('BEGIN');
|
await client.query('BEGIN');
|
||||||
const { rows: addrRows } = await client.query(
|
const opened = await openCheckout(client, req.customerId as number, shippingAddressId, 'paypal', null);
|
||||||
`SELECT id FROM shipping_addresses WHERE id = $1 AND customer_id = $2`,
|
if (!opened.ok) { await client.query('ROLLBACK'); return res.status(400).json({ error: opened.error }); }
|
||||||
[shippingAddressId, req.customerId]
|
const { checkoutId, cart } = opened;
|
||||||
);
|
|
||||||
if (!addrRows.length) { await client.query('ROLLBACK'); return res.status(400).json({ error: 'invalid shipping address' }); }
|
|
||||||
|
|
||||||
const cart = await loadLockedCart(client, req.customerId as number);
|
|
||||||
if (!cart || !cart.items.length) { await client.query('ROLLBACK'); return res.status(400).json({ error: 'cart is empty' }); }
|
|
||||||
|
|
||||||
const { rows: checkoutRows } = await client.query(
|
|
||||||
`INSERT INTO checkouts (customer_id, shipping_address_id, processor, amount_cents, status)
|
|
||||||
VALUES ($1, $2, 'paypal', $3, 'pending') RETURNING id`,
|
|
||||||
[req.customerId, shippingAddressId, cart.totalCents]
|
|
||||||
);
|
|
||||||
const checkoutId = checkoutRows[0].id;
|
|
||||||
for (const it of cart.items) {
|
|
||||||
await client.query(
|
|
||||||
`INSERT INTO checkout_items (checkout_id, item_id, price_cents) VALUES ($1, $2, $3)`,
|
|
||||||
[checkoutId, it.id, it.price_cents]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = await getAccessToken();
|
const token = await getAccessToken();
|
||||||
const currency = process.env.SITE_CURRENCY || 'USD';
|
const currency = process.env.SITE_CURRENCY || 'USD';
|
||||||
@@ -84,7 +117,7 @@ router.post('/paypal/create', requireCustomer, async (req: Request, res: Respons
|
|||||||
value: (cart.totalCents / 100).toFixed(2),
|
value: (cart.totalCents / 100).toFixed(2),
|
||||||
breakdown: { item_total: { currency_code: currency, value: (cart.totalCents / 100).toFixed(2) } }
|
breakdown: { item_total: { currency_code: currency, value: (cart.totalCents / 100).toFixed(2) } }
|
||||||
},
|
},
|
||||||
items: cart.items.map((it: any) => ({
|
items: cart.items.map((it: CartItem) => ({
|
||||||
name: it.name.slice(0, 127),
|
name: it.name.slice(0, 127),
|
||||||
quantity: '1',
|
quantity: '1',
|
||||||
unit_amount: { currency_code: currency, value: (it.price_cents / 100).toFixed(2) }
|
unit_amount: { currency_code: currency, value: (it.price_cents / 100).toFixed(2) }
|
||||||
@@ -168,26 +201,10 @@ router.post('/demo/purchase', requireCustomer, async (req: Request, res: Respons
|
|||||||
const client = await pool.connect();
|
const client = await pool.connect();
|
||||||
try {
|
try {
|
||||||
await client.query('BEGIN');
|
await client.query('BEGIN');
|
||||||
const { rows: addrRows } = await client.query(
|
const opened = await openCheckout(client, req.customerId as number, shippingAddressId, 'demo', `demo-${Date.now()}`);
|
||||||
`SELECT id FROM shipping_addresses WHERE id = $1 AND customer_id = $2`,
|
if (!opened.ok) { await client.query('ROLLBACK'); return res.status(400).json({ error: opened.error }); }
|
||||||
[shippingAddressId, req.customerId]
|
|
||||||
);
|
|
||||||
if (!addrRows.length) { await client.query('ROLLBACK'); return res.status(400).json({ error: 'invalid shipping address' }); }
|
|
||||||
|
|
||||||
const cart = await loadLockedCart(client, req.customerId as number);
|
await completeCheckout(client, opened.checkoutId, 'demo', null, { demo: true });
|
||||||
if (!cart || !cart.items.length) { await client.query('ROLLBACK'); return res.status(400).json({ error: 'cart is empty' }); }
|
|
||||||
|
|
||||||
const { rows: checkoutRows } = await client.query(
|
|
||||||
`INSERT INTO checkouts (customer_id, shipping_address_id, processor, processor_order_id, amount_cents, status)
|
|
||||||
VALUES ($1, $2, 'demo', $3, $4, 'pending') RETURNING id`,
|
|
||||||
[req.customerId, shippingAddressId, `demo-${Date.now()}`, cart.totalCents]
|
|
||||||
);
|
|
||||||
const checkoutId = checkoutRows[0].id;
|
|
||||||
for (const it of cart.items) {
|
|
||||||
await client.query(`INSERT INTO checkout_items (checkout_id, item_id, price_cents) VALUES ($1, $2, $3)`, [checkoutId, it.id, it.price_cents]);
|
|
||||||
}
|
|
||||||
|
|
||||||
await completeCheckout(client, checkoutId, 'demo', null, { demo: true });
|
|
||||||
await client.query('COMMIT');
|
await client.query('COMMIT');
|
||||||
res.json({ status: 'completed' });
|
res.json({ status: 'completed' });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
import { Router, Request, Response } from 'express';
|
|
||||||
import { pool } from '../db';
|
|
||||||
|
|
||||||
const router = Router();
|
|
||||||
|
|
||||||
router.post('/:id/purchase', async (req: Request, res: Response) => {
|
|
||||||
if (process.env.DEMO_MODE === 'false') {
|
|
||||||
return res.status(403).json({ error: 'demo mode disabled' });
|
|
||||||
}
|
|
||||||
const itemId = req.params.id;
|
|
||||||
const client = await pool.connect();
|
|
||||||
try {
|
|
||||||
await client.query('BEGIN');
|
|
||||||
const { rows } = await client.query(`SELECT * FROM items WHERE id = $1 FOR UPDATE`, [itemId]);
|
|
||||||
const item = rows[0];
|
|
||||||
if (!item) { await client.query('ROLLBACK'); return res.status(404).json({ error: 'not found' }); }
|
|
||||||
if (item.status === 'sold') { await client.query('ROLLBACK'); return res.status(409).json({ error: 'already sold' }); }
|
|
||||||
|
|
||||||
const { rows: updated } = await client.query(
|
|
||||||
`UPDATE items SET status = 'sold', sold_at = now() WHERE id = $1 RETURNING *`,
|
|
||||||
[itemId]
|
|
||||||
);
|
|
||||||
await client.query(
|
|
||||||
`INSERT INTO orders (item_id, customer_id, processor, processor_order_id, amount_cents, status, raw_event)
|
|
||||||
VALUES ($1, $2, 'demo', $3, $4, 'completed', $5)`,
|
|
||||||
[itemId, req.customerId || null, `demo-${Date.now()}`, item.price_cents, JSON.stringify({ demo: true })]
|
|
||||||
);
|
|
||||||
await client.query('COMMIT');
|
|
||||||
res.json({ status: 'sold', item: updated[0] });
|
|
||||||
} catch (err) {
|
|
||||||
await client.query('ROLLBACK');
|
|
||||||
console.error(err);
|
|
||||||
res.status(500).json({ error: 'internal error' });
|
|
||||||
} finally {
|
|
||||||
client.release();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default router;
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
import { Router, Request, Response } from 'express';
|
|
||||||
import { pool } from '../db';
|
|
||||||
|
|
||||||
const router = Router();
|
|
||||||
const webhookRouter = Router();
|
|
||||||
|
|
||||||
const PAYPAL_BASE =
|
|
||||||
process.env.PAYPAL_ENV === 'live'
|
|
||||||
? 'https://api-m.paypal.com'
|
|
||||||
: 'https://api-m.sandbox.paypal.com';
|
|
||||||
|
|
||||||
const RESERVATION_MINUTES = parseInt(process.env.RESERVATION_MINUTES || '15', 10);
|
|
||||||
|
|
||||||
async function getAccessToken(): Promise<string> {
|
|
||||||
const auth = Buffer.from(
|
|
||||||
`${process.env.PAYPAL_CLIENT_ID}:${process.env.PAYPAL_CLIENT_SECRET}`
|
|
||||||
).toString('base64');
|
|
||||||
const resp = await fetch(`${PAYPAL_BASE}/v1/oauth2/token`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
Authorization: `Basic ${auth}`,
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded'
|
|
||||||
},
|
|
||||||
body: 'grant_type=client_credentials'
|
|
||||||
});
|
|
||||||
const data = await resp.json();
|
|
||||||
if (!resp.ok) throw new Error('paypal auth failed: ' + JSON.stringify(data));
|
|
||||||
return data.access_token;
|
|
||||||
}
|
|
||||||
|
|
||||||
router.post('/:id/create', async (req: Request, res: Response) => {
|
|
||||||
const itemId = req.params.id;
|
|
||||||
const client = await pool.connect();
|
|
||||||
try {
|
|
||||||
await client.query('BEGIN');
|
|
||||||
const { rows } = await client.query(`SELECT * FROM items WHERE id = $1 FOR UPDATE`, [itemId]);
|
|
||||||
const item = rows[0];
|
|
||||||
if (!item) { await client.query('ROLLBACK'); return res.status(404).json({ error: 'not found' }); }
|
|
||||||
if (item.status === 'sold') { await client.query('ROLLBACK'); return res.status(409).json({ error: 'already sold' }); }
|
|
||||||
if (item.status === 'reserved' && new Date(item.reserved_until) > new Date()) {
|
|
||||||
await client.query('ROLLBACK');
|
|
||||||
return res.status(409).json({ error: 'currently reserved by another checkout' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = await getAccessToken();
|
|
||||||
const amount = (item.price_cents / 100).toFixed(2);
|
|
||||||
const orderResp = await fetch(`${PAYPAL_BASE}/v2/checkout/orders`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
intent: 'CAPTURE',
|
|
||||||
purchase_units: [{
|
|
||||||
custom_id: String(item.id),
|
|
||||||
description: item.name,
|
|
||||||
amount: { currency_code: process.env.SITE_CURRENCY || 'USD', value: amount }
|
|
||||||
}]
|
|
||||||
})
|
|
||||||
});
|
|
||||||
const order = await orderResp.json();
|
|
||||||
if (!orderResp.ok) { await client.query('ROLLBACK'); return res.status(502).json({ error: 'paypal order create failed', detail: order }); }
|
|
||||||
|
|
||||||
const reservedUntil = new Date(Date.now() + RESERVATION_MINUTES * 60 * 1000);
|
|
||||||
await client.query(
|
|
||||||
`UPDATE items SET status = 'reserved', reserved_until = $1, paypal_order_id = $2 WHERE id = $3`,
|
|
||||||
[reservedUntil, order.id, item.id]
|
|
||||||
);
|
|
||||||
await client.query('COMMIT');
|
|
||||||
res.json({ orderID: order.id });
|
|
||||||
} catch (err) {
|
|
||||||
await client.query('ROLLBACK');
|
|
||||||
console.error(err);
|
|
||||||
res.status(500).json({ error: 'internal error' });
|
|
||||||
} finally {
|
|
||||||
client.release();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post('/:id/capture', async (req: Request, res: Response) => {
|
|
||||||
const itemId = req.params.id;
|
|
||||||
const { orderID } = req.body;
|
|
||||||
const client = await pool.connect();
|
|
||||||
try {
|
|
||||||
const token = await getAccessToken();
|
|
||||||
const captureResp = await fetch(`${PAYPAL_BASE}/v2/checkout/orders/${orderID}/capture`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }
|
|
||||||
});
|
|
||||||
const capture = await captureResp.json();
|
|
||||||
if (!captureResp.ok || capture.status !== 'COMPLETED') {
|
|
||||||
return res.status(502).json({ error: 'capture failed', detail: capture });
|
|
||||||
}
|
|
||||||
|
|
||||||
await client.query('BEGIN');
|
|
||||||
const { rows } = await client.query(
|
|
||||||
`UPDATE items SET status = 'sold', sold_at = now()
|
|
||||||
WHERE id = $1 AND paypal_order_id = $2 AND status != 'sold'
|
|
||||||
RETURNING *`,
|
|
||||||
[itemId, orderID]
|
|
||||||
);
|
|
||||||
const capturedAmount = capture.purchase_units?.[0]?.payments?.captures?.[0]?.amount?.value;
|
|
||||||
await client.query(
|
|
||||||
`INSERT INTO orders (item_id, customer_id, processor, processor_order_id, amount_cents, status, raw_event)
|
|
||||||
VALUES ($1, $2, 'paypal', $3, $4, 'completed', $5)`,
|
|
||||||
[itemId, req.customerId || null, orderID, Math.round(parseFloat(capturedAmount || '0') * 100), capture]
|
|
||||||
);
|
|
||||||
await client.query('COMMIT');
|
|
||||||
res.json({ status: 'sold', item: rows[0] || null });
|
|
||||||
} catch (err) {
|
|
||||||
await client.query('ROLLBACK');
|
|
||||||
console.error(err);
|
|
||||||
res.status(500).json({ error: 'internal error' });
|
|
||||||
} finally {
|
|
||||||
client.release();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
webhookRouter.post('/', async (req: Request, res: Response) => {
|
|
||||||
try {
|
|
||||||
const token = await getAccessToken();
|
|
||||||
const verifyResp = await fetch(`${PAYPAL_BASE}/v1/notifications/verify-webhook-signature`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
auth_algo: req.headers['paypal-auth-algo'],
|
|
||||||
cert_url: req.headers['paypal-cert-url'],
|
|
||||||
transmission_id: req.headers['paypal-transmission-id'],
|
|
||||||
transmission_sig: req.headers['paypal-transmission-sig'],
|
|
||||||
transmission_time: req.headers['paypal-transmission-time'],
|
|
||||||
webhook_id: process.env.PAYPAL_WEBHOOK_ID,
|
|
||||||
webhook_event: req.body
|
|
||||||
})
|
|
||||||
});
|
|
||||||
const verification = await verifyResp.json();
|
|
||||||
if (verification.verification_status !== 'SUCCESS') {
|
|
||||||
console.warn('paypal webhook signature invalid');
|
|
||||||
return res.status(400).end();
|
|
||||||
}
|
|
||||||
|
|
||||||
const event = req.body;
|
|
||||||
if (event.event_type === 'PAYMENT.CAPTURE.COMPLETED') {
|
|
||||||
const itemId = event.resource?.custom_id;
|
|
||||||
if (itemId) {
|
|
||||||
await pool.query(
|
|
||||||
`UPDATE items SET status = 'sold', sold_at = now() WHERE id = $1 AND status != 'sold'`,
|
|
||||||
[itemId]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res.status(200).end();
|
|
||||||
} catch (err) {
|
|
||||||
console.error('webhook error', err);
|
|
||||||
res.status(500).end();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export { router, webhookRouter };
|
|
||||||
Reference in New Issue
Block a user