diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..a14702c --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,34 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 0000000..c9e03f8 --- /dev/null +++ b/backend/README.md @@ -0,0 +1,15 @@ +# job-info-sync + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run index.ts +``` + +This project was created using `bun init` in bun v1.3.2. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime. diff --git a/backend/bun.lock b/backend/bun.lock new file mode 100644 index 0000000..6051e6a --- /dev/null +++ b/backend/bun.lock @@ -0,0 +1,341 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "job-info-sync", + "dependencies": { + "axios": "^1.13.2", + "dotenv": "^17.2.3", + "exceljs": "^4.4.0", + "hono": "^4.10.6", + "joi": "^18.0.1", + "pino": "^10.1.0", + "pocketbase": "^0.26.3", + "qs": "^6.14.0", + "uuid": "^13.0.0", + "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz", + }, + "devDependencies": { + "@types/bun": "latest", + "@types/node": "^24.10.1", + }, + "peerDependencies": { + "typescript": "^5", + }, + }, + }, + "packages": { + "@fast-csv/format": ["@fast-csv/format@4.3.5", "", { "dependencies": { "@types/node": "^14.0.1", "lodash.escaperegexp": "^4.1.2", "lodash.isboolean": "^3.0.3", "lodash.isequal": "^4.5.0", "lodash.isfunction": "^3.0.9", "lodash.isnil": "^4.0.0" } }, "sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A=="], + + "@fast-csv/parse": ["@fast-csv/parse@4.3.6", "", { "dependencies": { "@types/node": "^14.0.1", "lodash.escaperegexp": "^4.1.2", "lodash.groupby": "^4.6.0", "lodash.isfunction": "^3.0.9", "lodash.isnil": "^4.0.0", "lodash.isundefined": "^3.0.1", "lodash.uniq": "^4.5.0" } }, "sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA=="], + + "@hapi/address": ["@hapi/address@5.1.1", "", { "dependencies": { "@hapi/hoek": "^11.0.2" } }, "sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA=="], + + "@hapi/formula": ["@hapi/formula@3.0.2", "", {}, "sha512-hY5YPNXzw1He7s0iqkRQi+uMGh383CGdyyIGYtB+W5N3KHPXoqychklvHhKCC9M3Xtv0OCs/IHw+r4dcHtBYWw=="], + + "@hapi/hoek": ["@hapi/hoek@11.0.7", "", {}, "sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ=="], + + "@hapi/pinpoint": ["@hapi/pinpoint@2.0.1", "", {}, "sha512-EKQmr16tM8s16vTT3cA5L0kZZcTMU5DUOZTuvpnY738m+jyP3JIUj+Mm1xc1rsLkGBQ/gVnfKYPwOmPg1tUR4Q=="], + + "@hapi/tlds": ["@hapi/tlds@1.1.4", "", {}, "sha512-Fq+20dxsxLaUn5jSSWrdtSRcIUba2JquuorF9UW1wIJS5cSUwxIsO2GIhaWynPRflvxSzFN+gxKte2HEW1OuoA=="], + + "@hapi/topo": ["@hapi/topo@6.0.2", "", { "dependencies": { "@hapi/hoek": "^11.0.2" } }, "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg=="], + + "@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="], + + "@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], + + "@types/bun": ["@types/bun@1.3.2", "", { "dependencies": { "bun-types": "1.3.2" } }, "sha512-t15P7k5UIgHKkxwnMNkJbWlh/617rkDGEdSsDbu+qNHTaz9SKf7aC8fiIlUdD5RPpH6GEkP0cK7WlvmrEBRtWg=="], + + "@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="], + + "@types/react": ["@types/react@19.2.5", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-keKxkZMqnDicuvFoJbzrhbtdLSPhj/rZThDlKWCDbgXmUg0rEUFtRssDXKYmtXluZlIqiC5VqkCgRwzuyLHKHw=="], + + "archiver": ["archiver@5.3.2", "", { "dependencies": { "archiver-utils": "^2.1.0", "async": "^3.2.4", "buffer-crc32": "^0.2.1", "readable-stream": "^3.6.0", "readdir-glob": "^1.1.2", "tar-stream": "^2.2.0", "zip-stream": "^4.1.0" } }, "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw=="], + + "archiver-utils": ["archiver-utils@2.1.0", "", { "dependencies": { "glob": "^7.1.4", "graceful-fs": "^4.2.0", "lazystream": "^1.0.0", "lodash.defaults": "^4.2.0", "lodash.difference": "^4.5.0", "lodash.flatten": "^4.4.0", "lodash.isplainobject": "^4.0.6", "lodash.union": "^4.6.0", "normalize-path": "^3.0.0", "readable-stream": "^2.0.0" } }, "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw=="], + + "async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="], + + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + + "atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="], + + "axios": ["axios@1.13.2", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "big-integer": ["big-integer@1.6.52", "", {}, "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg=="], + + "binary": ["binary@0.3.0", "", { "dependencies": { "buffers": "~0.1.1", "chainsaw": "~0.1.0" } }, "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg=="], + + "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], + + "bluebird": ["bluebird@3.4.7", "", {}, "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA=="], + + "brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + + "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], + + "buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="], + + "buffer-indexof-polyfill": ["buffer-indexof-polyfill@1.0.2", "", {}, "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A=="], + + "buffers": ["buffers@0.1.1", "", {}, "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ=="], + + "bun-types": ["bun-types@1.3.2", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-i/Gln4tbzKNuxP70OWhJRZz1MRfvqExowP7U6JKoI8cntFrtxg7RJK3jvz7wQW54UuvNC8tbKHHri5fy74FVqg=="], + + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], + + "chainsaw": ["chainsaw@0.1.0", "", { "dependencies": { "traverse": ">=0.3.0 <0.4" } }, "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ=="], + + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], + + "compress-commons": ["compress-commons@4.1.2", "", { "dependencies": { "buffer-crc32": "^0.2.13", "crc32-stream": "^4.0.2", "normalize-path": "^3.0.0", "readable-stream": "^3.6.0" } }, "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg=="], + + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + + "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="], + + "crc-32": ["crc-32@1.2.2", "", { "bin": { "crc32": "bin/crc32.njs" } }, "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ=="], + + "crc32-stream": ["crc32-stream@4.0.3", "", { "dependencies": { "crc-32": "^1.2.0", "readable-stream": "^3.4.0" } }, "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw=="], + + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + + "dayjs": ["dayjs@1.11.19", "", {}, "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw=="], + + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + + "dotenv": ["dotenv@17.2.3", "", {}, "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w=="], + + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + + "duplexer2": ["duplexer2@0.1.4", "", { "dependencies": { "readable-stream": "^2.0.2" } }, "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA=="], + + "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], + + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], + + "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + + "exceljs": ["exceljs@4.4.0", "", { "dependencies": { "archiver": "^5.0.0", "dayjs": "^1.8.34", "fast-csv": "^4.3.1", "jszip": "^3.10.1", "readable-stream": "^3.6.0", "saxes": "^5.0.1", "tmp": "^0.2.0", "unzipper": "^0.10.11", "uuid": "^8.3.0" } }, "sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg=="], + + "fast-csv": ["fast-csv@4.3.6", "", { "dependencies": { "@fast-csv/format": "4.3.5", "@fast-csv/parse": "4.3.6" } }, "sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw=="], + + "follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="], + + "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], + + "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], + + "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], + + "fstream": ["fstream@1.0.12", "", { "dependencies": { "graceful-fs": "^4.1.2", "inherits": "~2.0.0", "mkdirp": ">=0.5 0", "rimraf": "2" } }, "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + + "glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], + + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + + "hono": ["hono@4.10.6", "", {}, "sha512-BIdolzGpDO9MQ4nu3AUuDwHZZ+KViNm+EZ75Ae55eMXMqLVhDFqEMXxtUe9Qh8hjL+pIna/frs2j6Y2yD5Ua/g=="], + + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + + "immediate": ["immediate@3.0.6", "", {}, "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="], + + "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="], + + "joi": ["joi@18.0.1", "", { "dependencies": { "@hapi/address": "^5.1.1", "@hapi/formula": "^3.0.2", "@hapi/hoek": "^11.0.7", "@hapi/pinpoint": "^2.0.1", "@hapi/tlds": "^1.1.1", "@hapi/topo": "^6.0.2", "@standard-schema/spec": "^1.0.0" } }, "sha512-IiQpRyypSnLisQf3PwuN2eIHAsAIGZIrLZkd4zdvIar2bDyhM91ubRjy8a3eYablXsh9BeI/c7dmPYHca5qtoA=="], + + "jszip": ["jszip@3.10.1", "", { "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", "readable-stream": "~2.3.6", "setimmediate": "^1.0.5" } }, "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g=="], + + "lazystream": ["lazystream@1.0.1", "", { "dependencies": { "readable-stream": "^2.0.5" } }, "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw=="], + + "lie": ["lie@3.3.0", "", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ=="], + + "listenercount": ["listenercount@1.0.1", "", {}, "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ=="], + + "lodash.defaults": ["lodash.defaults@4.2.0", "", {}, "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="], + + "lodash.difference": ["lodash.difference@4.5.0", "", {}, "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA=="], + + "lodash.escaperegexp": ["lodash.escaperegexp@4.1.2", "", {}, "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw=="], + + "lodash.flatten": ["lodash.flatten@4.4.0", "", {}, "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g=="], + + "lodash.groupby": ["lodash.groupby@4.6.0", "", {}, "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw=="], + + "lodash.isboolean": ["lodash.isboolean@3.0.3", "", {}, "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="], + + "lodash.isequal": ["lodash.isequal@4.5.0", "", {}, "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="], + + "lodash.isfunction": ["lodash.isfunction@3.0.9", "", {}, "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw=="], + + "lodash.isnil": ["lodash.isnil@4.0.0", "", {}, "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng=="], + + "lodash.isplainobject": ["lodash.isplainobject@4.0.6", "", {}, "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="], + + "lodash.isundefined": ["lodash.isundefined@3.0.1", "", {}, "sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA=="], + + "lodash.union": ["lodash.union@4.6.0", "", {}, "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw=="], + + "lodash.uniq": ["lodash.uniq@4.5.0", "", {}, "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ=="], + + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="], + + "mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="], + + "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], + + "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], + + "on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="], + + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + + "pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="], + + "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], + + "pino": ["pino@10.1.0", "", { "dependencies": { "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^2.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^3.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-0zZC2ygfdqvqK8zJIr1e+wT1T/L+LF6qvqvbzEQ6tiMAoTqEVK9a1K3YRu8HEUvGEvNqZyPJTtb2sNIoTkB83w=="], + + "pino-abstract-transport": ["pino-abstract-transport@2.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw=="], + + "pino-std-serializers": ["pino-std-serializers@7.0.0", "", {}, "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA=="], + + "pocketbase": ["pocketbase@0.26.3", "", {}, "sha512-5deUKRoEczpxxuHzwr6/DHVmgbggxylEVig8CKN+MjvtYxPUqX/C6puU0yaR2yhTi8zrh7J9s7Ty+qBGwVzWOQ=="], + + "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="], + + "process-warning": ["process-warning@5.0.0", "", {}, "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA=="], + + "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], + + "qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="], + + "quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="], + + "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + + "readdir-glob": ["readdir-glob@1.1.3", "", { "dependencies": { "minimatch": "^5.1.0" } }, "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA=="], + + "real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="], + + "rimraf": ["rimraf@2.7.1", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "./bin.js" } }, "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w=="], + + "safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], + + "safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="], + + "saxes": ["saxes@5.0.1", "", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw=="], + + "setimmediate": ["setimmediate@1.0.5", "", {}, "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="], + + "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], + + "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], + + "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], + + "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], + + "sonic-boom": ["sonic-boom@4.2.0", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww=="], + + "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], + + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + + "tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], + + "thread-stream": ["thread-stream@3.1.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A=="], + + "tmp": ["tmp@0.2.5", "", {}, "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow=="], + + "traverse": ["traverse@0.3.9", "", {}, "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + + "unzipper": ["unzipper@0.10.14", "", { "dependencies": { "big-integer": "^1.6.17", "binary": "~0.3.0", "bluebird": "~3.4.1", "buffer-indexof-polyfill": "~1.0.0", "duplexer2": "~0.1.4", "fstream": "^1.0.12", "graceful-fs": "^4.2.2", "listenercount": "~1.0.1", "readable-stream": "~2.3.6", "setimmediate": "~1.0.4" } }, "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "uuid": ["uuid@13.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w=="], + + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + + "xlsx": ["xlsx@https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz", { "bin": { "xlsx": "./bin/xlsx.njs" } }], + + "xmlchars": ["xmlchars@2.2.0", "", {}, "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="], + + "zip-stream": ["zip-stream@4.1.1", "", { "dependencies": { "archiver-utils": "^3.0.4", "compress-commons": "^4.1.2", "readable-stream": "^3.6.0" } }, "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ=="], + + "@fast-csv/format/@types/node": ["@types/node@14.18.63", "", {}, "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ=="], + + "@fast-csv/parse/@types/node": ["@types/node@14.18.63", "", {}, "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ=="], + + "archiver-utils/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + + "duplexer2/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + + "exceljs/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], + + "glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + + "jszip/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + + "lazystream/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + + "string_decoder/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "unzipper/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + + "zip-stream/archiver-utils": ["archiver-utils@3.0.4", "", { "dependencies": { "glob": "^7.2.3", "graceful-fs": "^4.2.0", "lazystream": "^1.0.0", "lodash.defaults": "^4.2.0", "lodash.difference": "^4.5.0", "lodash.flatten": "^4.4.0", "lodash.isplainobject": "^4.0.6", "lodash.union": "^4.6.0", "normalize-path": "^3.0.0", "readable-stream": "^3.6.0" } }, "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw=="], + + "archiver-utils/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + + "duplexer2/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + + "glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + + "jszip/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + + "lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + + "unzipper/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + } +} diff --git a/backend/config.json b/backend/config.json new file mode 100644 index 0000000..d03f9f4 --- /dev/null +++ b/backend/config.json @@ -0,0 +1,8 @@ +{ + "tenantId": "3fd97ea7-b124-41f1-855f-52d8ac3b16c7", + "clientId": "3c846e71-9609-40e1-b458-0eb805e21b9f", + "sharepointFileUrl": "https://czflex.sharepoint.com/:x:/r/sites/Team/Shared%20Documents/General/JOBS%201000-9999.xls", + "pbUrl": "http://10.10.1.109:8080", + "pbCollection": "Job_Info_TestEnv", + "pbEmail": "pbserviceupdate@cardoza.construction" +} \ No newline at end of file diff --git a/backend/excelsync.log b/backend/excelsync.log new file mode 100644 index 0000000..b0308e5 --- /dev/null +++ b/backend/excelsync.log @@ -0,0 +1,1918 @@ +π΅ Checking token acquisition and excel readiness +β Graph token acquired +β PocketBase token acquired +β Error, not ready for updating.exceltopb is not defined +π΅ Checking token acquisition and excel readiness +β Graph token acquired +β PocketBase token acquired +β Ready for updating! +π΅ Checking token acquisition and excel readiness +β Graph token acquired +β PocketBase token acquired +β Failed to fetch schema: The authorized record is not allowed to perform this action. +β Error, not ready for updating.The authorized record is not allowed to perform this action. +π΅ Checking token acquisition and excel readiness +β Graph token acquired +β Error, not ready for updating.pbEmail2 is not defined +π΅ Checking token acquisition and excel readiness +β Graph token acquired +β Error, not ready for updating.Failed to authenticate. +π΅ Checking token acquisition and excel readiness +β Graph token acquired +β Error, not ready for updating.Missing or invalid collection context. +π΅ Checking token acquisition and excel readiness +β Graph token acquired +β Error, not ready for updating.Failed to authenticate. +π΅ Checking token acquisition and excel readiness +β Graph token acquired +β PocketBase token acquired +β Schema loaded for Job_Info_TestEnv +[ + { + "autogeneratePattern": "[a-z0-9]{15}", + "hidden": false, + "id": "text3208210256", + "max": 15, + "min": 15, + "name": "id", + "pattern": "^[a-z0-9]+$", + "presentable": false, + "primaryKey": true, + "required": true, + "system": true, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text2388997171", + "max": 0, + "min": 0, + "name": "Job_Number", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text2378820285", + "max": 0, + "min": 0, + "name": "Job_Full_Name", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text2158740783", + "max": 0, + "min": 0, + "name": "Job_Name", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text3732958801", + "max": 0, + "min": 0, + "name": "Job_Address", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text1381428224", + "max": 0, + "min": 0, + "name": "Job_Type", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text1908819108", + "max": 0, + "min": 0, + "name": "Flag", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text1677020795", + "max": 0, + "min": 0, + "name": "Job_Status", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text2518113217", + "max": 0, + "min": 0, + "name": "Job_Division", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text2325867089", + "max": 0, + "min": 0, + "name": "Estimator", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text3229868610", + "max": 0, + "min": 0, + "name": "Office_Rep", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text899226661", + "max": 0, + "min": 0, + "name": "Manager", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text806559182", + "max": 0, + "min": 0, + "name": "Company_Client", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text1854315289", + "max": 0, + "min": 0, + "name": "Contact_Person", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text1956405909", + "max": 0, + "min": 0, + "name": "Phone_Number", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text642995056", + "max": 0, + "min": 0, + "name": "Email", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text3208080729", + "max": 0, + "min": 0, + "name": "Due_Date", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text4112652156", + "max": 0, + "min": 0, + "name": "Due_Date_Source", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text1362567553", + "max": 0, + "min": 0, + "name": "Due_Date_Counter", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text2050867814", + "max": 0, + "min": 0, + "name": "Due_Time", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text1613691138", + "max": 0, + "min": 0, + "name": "Docs_Uploaded", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text701636273", + "max": 0, + "min": 0, + "name": "QB_Created", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text159074814", + "max": 0, + "min": 0, + "name": "Added_To_Calendar", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text2443123173", + "max": 0, + "min": 0, + "name": "PSwift_Uploaded", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text1028487862", + "max": 0, + "min": 0, + "name": "Voxer_Created", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text2654168435", + "max": 0, + "min": 0, + "name": "Estimate_Reviewed", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text1033433223", + "max": 0, + "min": 0, + "name": "Estimate_Draft", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text4143394575", + "max": 0, + "min": 0, + "name": "Estimate_Approved", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text2051602006", + "max": 0, + "min": 0, + "name": "Start_Date", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text811897623", + "max": 0, + "min": 0, + "name": "Schedule_Confidence", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text3235547528", + "max": 0, + "min": 0, + "name": "Notes", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text1270095775", + "max": 0, + "min": 0, + "name": "Job_QB_Link", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text957490280", + "max": 0, + "min": 0, + "name": "Voxer_Link", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text2814497178", + "max": 0, + "min": 0, + "name": "Job_Codes", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text275762613", + "max": 0, + "min": 0, + "name": "Submission_Date", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text1286797620", + "max": 0, + "min": 0, + "name": "Active", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text3795106522", + "max": 0, + "min": 0, + "name": "Job_Folder_Link", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text282225560", + "max": 0, + "min": 0, + "name": "Tax_Exempt", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "hidden": false, + "id": "bool545115032", + "name": "Has_Attachments", + "presentable": false, + "required": false, + "system": false, + "type": "bool" + }, + { + "hidden": false, + "id": "json2488040839", + "maxSize": 0, + "name": "Attachment_List", + "presentable": false, + "required": false, + "system": false, + "type": "json" + }, + { + "cascadeDelete": false, + "collectionId": "pbc_194787171", + "hidden": false, + "id": "relation2779200245", + "maxSelect": 999, + "minSelect": 0, + "name": "Note_Ref", + "presentable": false, + "required": false, + "system": false, + "type": "relation" + }, + { + "hidden": false, + "id": "json4080242417", + "maxSize": 0, + "name": "latest_note_summary", + "presentable": false, + "required": false, + "system": false, + "type": "json" + }, + { + "hidden": false, + "id": "json2575510732", + "maxSize": 0, + "name": "note_thread_text", + "presentable": false, + "required": false, + "system": false, + "type": "json" + }, + { + "hidden": false, + "id": "number3771328437", + "max": null, + "min": null, + "name": "note_count", + "onlyInt": false, + "presentable": false, + "required": false, + "system": false, + "type": "number" + }, + { + "hidden": false, + "id": "date2500832881", + "max": "", + "min": "", + "name": "last_note_at", + "presentable": false, + "required": false, + "system": false, + "type": "date" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text2569355707", + "max": 0, + "min": 0, + "name": "Job_Number_Parent", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text2730408412", + "max": 0, + "min": 0, + "name": "Tax_Exempt_Docs_Uploaded", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text725757662", + "max": 0, + "min": 0, + "name": "Project_Manager", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "hidden": false, + "id": "autodate2990389176", + "name": "created", + "onCreate": true, + "onUpdate": false, + "presentable": false, + "system": false, + "type": "autodate" + }, + { + "hidden": false, + "id": "autodate3332085495", + "name": "updated", + "onCreate": true, + "onUpdate": true, + "presentable": false, + "system": false, + "type": "autodate" + } +] +β Ready for updating! +π΅ Checking token acquisition and excel readiness +β Graph token acquired +β PocketBase token acquired +β Schema loaded for Job_Info_TestEnv +[ + { + "autogeneratePattern": "[a-z0-9]{15}", + "hidden": false, + "id": "text3208210256", + "max": 15, + "min": 15, + "name": "id", + "pattern": "^[a-z0-9]+$", + "presentable": false, + "primaryKey": true, + "required": true, + "system": true, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text2388997171", + "max": 0, + "min": 0, + "name": "Job_Number", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text2378820285", + "max": 0, + "min": 0, + "name": "Job_Full_Name", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text2158740783", + "max": 0, + "min": 0, + "name": "Job_Name", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text3732958801", + "max": 0, + "min": 0, + "name": "Job_Address", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text1381428224", + "max": 0, + "min": 0, + "name": "Job_Type", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text1677020795", + "max": 0, + "min": 0, + "name": "Job_Status", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text2518113217", + "max": 0, + "min": 0, + "name": "Job_Division", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text2325867089", + "max": 0, + "min": 0, + "name": "Estimator", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text3229868610", + "max": 0, + "min": 0, + "name": "Office_Rep", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text899226661", + "max": 0, + "min": 0, + "name": "Project_Manager", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text806559182", + "max": 0, + "min": 0, + "name": "Company_Client", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text1854315289", + "max": 0, + "min": 0, + "name": "Contact_Person", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text1956405909", + "max": 0, + "min": 0, + "name": "Phone_Number", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text642995056", + "max": 0, + "min": 0, + "name": "Email", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text4112652156", + "max": 0, + "min": 0, + "name": "Due_Date_Source", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text1362567553", + "max": 0, + "min": 0, + "name": "Due_Date_Counter", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text2050867814", + "max": 0, + "min": 0, + "name": "Due_Time", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text811897623", + "max": 0, + "min": 0, + "name": "Schedule_Confidence", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text3235547528", + "max": 0, + "min": 0, + "name": "Notes", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text1270095775", + "max": 0, + "min": 0, + "name": "Job_QB_Link", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text957490280", + "max": 0, + "min": 0, + "name": "Voxer_Link", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text2814497178", + "max": 0, + "min": 0, + "name": "Job_Codes", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text3795106522", + "max": 0, + "min": 0, + "name": "Job_Folder_Link", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "hidden": false, + "id": "bool545115032", + "name": "Has_Attachments", + "presentable": false, + "required": false, + "system": false, + "type": "bool" + }, + { + "hidden": false, + "id": "json2488040839", + "maxSize": 0, + "name": "Attachment_List", + "presentable": false, + "required": false, + "system": false, + "type": "json" + }, + { + "cascadeDelete": false, + "collectionId": "pbc_194787171", + "hidden": false, + "id": "relation2779200245", + "maxSelect": 999, + "minSelect": 0, + "name": "Note_Ref", + "presentable": false, + "required": false, + "system": false, + "type": "relation" + }, + { + "hidden": false, + "id": "json4080242417", + "maxSize": 0, + "name": "latest_note_summary", + "presentable": false, + "required": false, + "system": false, + "type": "json" + }, + { + "hidden": false, + "id": "json2575510732", + "maxSize": 0, + "name": "note_thread_text", + "presentable": false, + "required": false, + "system": false, + "type": "json" + }, + { + "hidden": false, + "id": "number3771328437", + "max": null, + "min": null, + "name": "note_count", + "onlyInt": false, + "presentable": false, + "required": false, + "system": false, + "type": "number" + }, + { + "hidden": false, + "id": "date2500832881", + "max": "", + "min": "", + "name": "last_note_at", + "presentable": false, + "required": false, + "system": false, + "type": "date" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text2569355707", + "max": 0, + "min": 0, + "name": "Job_Number_Parent", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "hidden": false, + "id": "date2051602006", + "max": "", + "min": "", + "name": "Start_Date", + "presentable": false, + "required": false, + "system": false, + "type": "date" + }, + { + "hidden": false, + "id": "date275762613", + "max": "", + "min": "", + "name": "Submission_Date", + "presentable": false, + "required": false, + "system": false, + "type": "date" + }, + { + "hidden": false, + "id": "bool1908819108", + "name": "Flag", + "presentable": false, + "required": false, + "system": false, + "type": "bool" + }, + { + "hidden": false, + "id": "bool282225560", + "name": "Tax_Exempt", + "presentable": false, + "required": false, + "system": false, + "type": "bool" + }, + { + "hidden": false, + "id": "bool1286797620", + "name": "Active", + "presentable": false, + "required": false, + "system": false, + "type": "bool" + }, + { + "hidden": false, + "id": "date3208080729", + "max": "", + "min": "", + "name": "Due_Date", + "presentable": false, + "required": false, + "system": false, + "type": "date" + }, + { + "hidden": false, + "id": "bool1523500494", + "name": "Docs_Uploaded", + "presentable": false, + "required": false, + "system": false, + "type": "bool" + }, + { + "hidden": false, + "id": "bool3561832989", + "name": "Tax_Exempt_Docs_Uploaded", + "presentable": false, + "required": false, + "system": false, + "type": "bool" + }, + { + "hidden": false, + "id": "bool4143394575", + "name": "Estimate_Approved", + "presentable": false, + "required": false, + "system": false, + "type": "bool" + }, + { + "hidden": false, + "id": "bool701636273", + "name": "QB_Created", + "presentable": false, + "required": false, + "system": false, + "type": "bool" + }, + { + "hidden": false, + "id": "bool159074814", + "name": "Added_To_Calendar", + "presentable": false, + "required": false, + "system": false, + "type": "bool" + }, + { + "hidden": false, + "id": "bool2443123173", + "name": "PSwift_Uploaded", + "presentable": false, + "required": false, + "system": false, + "type": "bool" + }, + { + "hidden": false, + "id": "bool1028487862", + "name": "Voxer_Created", + "presentable": false, + "required": false, + "system": false, + "type": "bool" + }, + { + "hidden": false, + "id": "bool2654168435", + "name": "Estimate_Reviewed", + "presentable": false, + "required": false, + "system": false, + "type": "bool" + }, + { + "hidden": false, + "id": "bool1033433223", + "name": "Estimate_Draft", + "presentable": false, + "required": false, + "system": false, + "type": "bool" + }, + { + "hidden": false, + "id": "autodate2990389176", + "name": "created", + "onCreate": true, + "onUpdate": false, + "presentable": false, + "system": false, + "type": "autodate" + }, + { + "hidden": false, + "id": "autodate3332085495", + "name": "updated", + "onCreate": true, + "onUpdate": true, + "presentable": false, + "system": false, + "type": "autodate" + } +] +β Ready for updating! +π΅ Checking token acquisition and excel readiness +β Graph token acquired +β PocketBase token acquired +β Schema loaded for Job_Info_TestEnv +[ + { + "autogeneratePattern": "[a-z0-9]{15}", + "hidden": false, + "id": "text3208210256", + "max": 15, + "min": 15, + "name": "id", + "pattern": "^[a-z0-9]+$", + "presentable": false, + "primaryKey": true, + "required": true, + "system": true, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text2388997171", + "max": 0, + "min": 0, + "name": "Job_Number", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text2378820285", + "max": 0, + "min": 0, + "name": "Job_Full_Name", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text2158740783", + "max": 0, + "min": 0, + "name": "Job_Name", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text3732958801", + "max": 0, + "min": 0, + "name": "Job_Address", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text1381428224", + "max": 0, + "min": 0, + "name": "Job_Type", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text1677020795", + "max": 0, + "min": 0, + "name": "Job_Status", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text2518113217", + "max": 0, + "min": 0, + "name": "Job_Division", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text2325867089", + "max": 0, + "min": 0, + "name": "Estimator", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text3229868610", + "max": 0, + "min": 0, + "name": "Office_Rep", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text899226661", + "max": 0, + "min": 0, + "name": "Project_Manager", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text806559182", + "max": 0, + "min": 0, + "name": "Company_Client", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text1854315289", + "max": 0, + "min": 0, + "name": "Contact_Person", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text1956405909", + "max": 0, + "min": 0, + "name": "Phone_Number", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text642995056", + "max": 0, + "min": 0, + "name": "Email", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text4112652156", + "max": 0, + "min": 0, + "name": "Due_Date_Source", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text1362567553", + "max": 0, + "min": 0, + "name": "Due_Date_Counter", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text2050867814", + "max": 0, + "min": 0, + "name": "Due_Time", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text811897623", + "max": 0, + "min": 0, + "name": "Schedule_Confidence", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text3235547528", + "max": 0, + "min": 0, + "name": "Notes", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text1270095775", + "max": 0, + "min": 0, + "name": "Job_QB_Link", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text957490280", + "max": 0, + "min": 0, + "name": "Voxer_Link", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text2814497178", + "max": 0, + "min": 0, + "name": "Job_Codes", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text3795106522", + "max": 0, + "min": 0, + "name": "Job_Folder_Link", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "hidden": false, + "id": "bool545115032", + "name": "Has_Attachments", + "presentable": false, + "required": false, + "system": false, + "type": "bool" + }, + { + "hidden": false, + "id": "json2488040839", + "maxSize": 0, + "name": "Attachment_List", + "presentable": false, + "required": false, + "system": false, + "type": "json" + }, + { + "cascadeDelete": false, + "collectionId": "pbc_194787171", + "hidden": false, + "id": "relation2779200245", + "maxSelect": 999, + "minSelect": 0, + "name": "Note_Ref", + "presentable": false, + "required": false, + "system": false, + "type": "relation" + }, + { + "hidden": false, + "id": "json4080242417", + "maxSize": 0, + "name": "latest_note_summary", + "presentable": false, + "required": false, + "system": false, + "type": "json" + }, + { + "hidden": false, + "id": "json2575510732", + "maxSize": 0, + "name": "note_thread_text", + "presentable": false, + "required": false, + "system": false, + "type": "json" + }, + { + "hidden": false, + "id": "number3771328437", + "max": null, + "min": null, + "name": "note_count", + "onlyInt": false, + "presentable": false, + "required": false, + "system": false, + "type": "number" + }, + { + "hidden": false, + "id": "date2500832881", + "max": "", + "min": "", + "name": "last_note_at", + "presentable": false, + "required": false, + "system": false, + "type": "date" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text2569355707", + "max": 0, + "min": 0, + "name": "Job_Number_Parent", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "hidden": false, + "id": "date2051602006", + "max": "", + "min": "", + "name": "Start_Date", + "presentable": false, + "required": false, + "system": false, + "type": "date" + }, + { + "hidden": false, + "id": "date275762613", + "max": "", + "min": "", + "name": "Submission_Date", + "presentable": false, + "required": false, + "system": false, + "type": "date" + }, + { + "hidden": false, + "id": "bool1908819108", + "name": "Flag", + "presentable": false, + "required": false, + "system": false, + "type": "bool" + }, + { + "hidden": false, + "id": "bool282225560", + "name": "Tax_Exempt", + "presentable": false, + "required": false, + "system": false, + "type": "bool" + }, + { + "hidden": false, + "id": "bool1286797620", + "name": "Active", + "presentable": false, + "required": false, + "system": false, + "type": "bool" + }, + { + "hidden": false, + "id": "date3208080729", + "max": "", + "min": "", + "name": "Due_Date", + "presentable": false, + "required": false, + "system": false, + "type": "date" + }, + { + "hidden": false, + "id": "bool1523500494", + "name": "Docs_Uploaded", + "presentable": false, + "required": false, + "system": false, + "type": "bool" + }, + { + "hidden": false, + "id": "bool3561832989", + "name": "Tax_Exempt_Docs_Uploaded", + "presentable": false, + "required": false, + "system": false, + "type": "bool" + }, + { + "hidden": false, + "id": "bool4143394575", + "name": "Estimate_Approved", + "presentable": false, + "required": false, + "system": false, + "type": "bool" + }, + { + "hidden": false, + "id": "bool701636273", + "name": "QB_Created", + "presentable": false, + "required": false, + "system": false, + "type": "bool" + }, + { + "hidden": false, + "id": "bool159074814", + "name": "Added_To_Calendar", + "presentable": false, + "required": false, + "system": false, + "type": "bool" + }, + { + "hidden": false, + "id": "bool2443123173", + "name": "PSwift_Uploaded", + "presentable": false, + "required": false, + "system": false, + "type": "bool" + }, + { + "hidden": false, + "id": "bool1028487862", + "name": "Voxer_Created", + "presentable": false, + "required": false, + "system": false, + "type": "bool" + }, + { + "hidden": false, + "id": "bool2654168435", + "name": "Estimate_Reviewed", + "presentable": false, + "required": false, + "system": false, + "type": "bool" + }, + { + "hidden": false, + "id": "bool1033433223", + "name": "Estimate_Draft", + "presentable": false, + "required": false, + "system": false, + "type": "bool" + }, + { + "hidden": false, + "id": "autodate2990389176", + "name": "created", + "onCreate": true, + "onUpdate": false, + "presentable": false, + "system": false, + "type": "autodate" + }, + { + "hidden": false, + "id": "autodate3332085495", + "name": "updated", + "onCreate": true, + "onUpdate": true, + "presentable": false, + "system": false, + "type": "autodate" + } +] +β Ready for updating! +π΅ Checking token acquisition and excel readiness +β Graph token acquired +β PocketBase token acquired +β Error, not ready for updating.getPocketBaseSchema is not defined +π΅ Checking token acquisition and excel readiness +β Graph token acquired +β Error, not ready for updating. Failed to authenticate. +π΅ Checking token acquisition and excel readiness +β Graph token acquired +β PocketBase token acquired +β Error, not ready for updating.exceltopb is not defined +π΅ Checking token acquisition and excel readiness +β Graph token acquired +β PocketBase token acquired +β Error, not ready for updating. fetchExcelRange is not defined +π΅ Checking token acquisition and excel readiness +β Graph token acquired +β PocketBase token acquired +π΅ Checking token acquisition and excel readiness +β Graph token acquired +β PocketBase token acquired +π΅ Checking token acquisition and excel readiness +π΅ Requesting Graph token... +β Graph token acquired +π΅ Logging into PocketBase... +β PocketBase token acquired +π΅ Requesting usedRange for Job Sheet +π΅ Starting Excel Syncβ¦ +βοΈ Export server bypassed (ENABLE_EXPORT=false) +β Graph token acquired +β PocketBase token acquired diff --git a/backend/index.ts b/backend/index.ts new file mode 100644 index 0000000..f67b2c6 --- /dev/null +++ b/backend/index.ts @@ -0,0 +1 @@ +console.log("Hello via Bun!"); \ No newline at end of file diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 0000000..7e814f6 --- /dev/null +++ b/backend/package.json @@ -0,0 +1,31 @@ +{ + "name": "job-info-sync", + "module": "index.ts", + "type": "module", + "private": true, + "scripts": { + "dev": "bun --watch src/server.ts", + "sharelink": "bun run utilities/sharepointShareLinktoGraph.html", + "sync": "bun run /src/excelsync.js", + "server": "bun run src/server.ts" + }, + "devDependencies": { + "@types/bun": "latest", + "@types/node": "^24.10.1" + }, + "peerDependencies": { + "typescript": "^5" + }, + "dependencies": { + "axios": "^1.13.2", + "dotenv": "^17.2.3", + "exceljs": "^4.4.0", + "hono": "^4.10.6", + "joi": "^18.0.1", + "pino": "^10.1.0", + "pocketbase": "^0.26.3", + "qs": "^6.14.0", + "uuid": "^13.0.0", + "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz" + } +} diff --git a/backend/src/excelsync.js b/backend/src/excelsync.js new file mode 100644 index 0000000..29bb8ee --- /dev/null +++ b/backend/src/excelsync.js @@ -0,0 +1,513 @@ +// excelsync.js +import { FIELD_CONFIG } from "./fieldConfig.js"; +import fs from "fs"; +import axios from "axios"; +import qs from "qs"; +import * as XLSX from "xlsx"; +import dotenv from "dotenv"; +import { serve } from "bun"; +import PocketBase from "pocketbase" + +dotenv.config(); + +// ----------------------------- +// EXCEL PARSER +// ----------------------------- +const HEADER_ROW_INDEX = 2; // Row 3 +const DATA_START_ROW_INDEX = 3; // Row 4 + +// ----------------------------- +// CONFIG +// ----------------------------- +const CONFIG = JSON.parse(fs.readFileSync("config.json", "utf8")); +const tenantId = CONFIG.tenantId; +const clientId = CONFIG.clientId; +const sharepointFileUrl = CONFIG.sharepointFileUrl; // production Excel file URL +const pbUrl = CONFIG.pbUrl; +const pbCollection = CONFIG.pbCollection || "Job_Info_TestEnv"; +const pbEmail = CONFIG.pbEmail; + +const clientSecret = process.env.CLIENT_SECRET; +const pbPassword = process.env.PB_PASSWORD; + +if (!tenantId || !clientId || !clientSecret || !sharepointFileUrl || !pbUrl || !pbEmail || !pbPassword) { + console.error("β Missing required config/env values"); + process.exit(1); +} +console.log({ + tenantId, + clientId, + clientSecret: clientSecret ? clientSecret.slice(0,4) + "..." : null, + sharepointFileUrl, + pbUrl, + pbEmail, + pbPassword: pbPassword ? "***" : null +}); +// ----------------------------- +// LOGGING +// ----------------------------- +function log(message) { + console.log(message); + fs.appendFileSync("excelsync.log", message + "\n"); +} + +// ----------------------------- +// HEADER + ROW DETECTION HELPERS +// ----------------------------- +//Gets the last column letter in the table converting from 0 index to excel actual column in letter form. +function columnLetter(index) { + let letter = ""; + while (index >= 0) { + letter = String.fromCharCode((index % 26) + 65) + letter; + index = Math.floor(index / 26) - 1; + } + return letter; +} + +const headerRowIndex = HEADER_ROW_INDEX + 1; + +function getColumnLetterForHeader(headerName, sheet, headerRowIndex) { + const headerRow = XLSX.utils.sheet_to_json(sheet, { header: 1, range: `${headerRowIndex}:${headerRowIndex}` })[0] + .map(h => (h || "").toString().trim().toLowerCase()); + const idx = headerRow.findIndex(h => h === headerName.toLowerCase()); + if (idx === -1) { + log(`β Header "${headerName}" not found. Row contents: ${JSON.stringify(headerRow)}`); + throw new Error(`Header "${headerName}" not found on row ${headerRowIndex}`); + } + log(`β Header "${headerName}" found at column index ${idx}`); + return columnLetter(idx); +} +function getNotesColumnLetterFromParsedHeaders(headerMap) { + const headers = Object.keys(headerMap); + const notesIndex = headers.findIndex(h => h.trim().toLowerCase() === "notes"); + if (notesIndex === -1) { + log(`β 'Notes' header not found in HEADER_MAP keys: ${headers.join(", ")}`); + throw new Error("Notes column not found"); + } + return columnLetter(notesIndex); +} +// ----------------------------- +// GRAPH TOKEN +// ----------------------------- +async function getGraphToken() { + const tokenUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`; + const body = qs.stringify({ + client_id: clientId, + client_secret: clientSecret, + scope: "https://graph.microsoft.com/.default", + grant_type: "client_credentials", + }); + const res = await axios.post(tokenUrl, body, { + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + }); + log("β Graph token acquired"); + return res.data.access_token; +} + +// ----------------------------- +// GRAPH DOWNLOADER HELPER +// ----------------------------- +async function downloadExcelFromGraph(graphToken, driveId, itemId) { + const url = `https://graph.microsoft.com/v1.0/drives/${driveId}/items/${itemId}/content`; + const res = await axios.get(url, { + headers: { Authorization: `Bearer ${graphToken}` }, + responseType: "arraybuffer", // important: get raw binary + }); + return Buffer.from(res.data); // convert to Buffer for XLSX +} + +// ----------------------------- +// GRAPH UPLOADER HELPER +// ----------------------------- +async function uploadExcelToGraph(graphToken, driveId, itemId, buffer) { + const url = `https://graph.microsoft.com/v1.0/drives/${driveId}/items/${itemId}/content`; + const res = await axios.put(url, buffer, { + headers: { + Authorization: `Bearer ${graphToken}`, + "Content-Type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + } + }); + log(`β Excel file updated in SharePoint: ${res.status} ${res.statusText}`); +} + + +// ----------------------------- +// POCKETBASE LOGIN +// ----------------------------- +async function getPocketBaseClient() { + const pb = new PocketBase(pbUrl); + await pb.collection("users").authWithPassword(pbEmail, pbPassword); + log("β PocketBase token acquired"); + return pb; +} + +function parseExcelFile(input, headerMap) { + let data; + // In case of data from graph + if (Buffer.isBuffer(input)) { + data = input; + log(`Reading Excel file from Graph buffer`); + // In case of file path (Not used at this time) + } else { + data = fs.readFileSync(input); + log(`Reading Excel file: ${input}`); + } + + const workbook = XLSX.read(data, { type: "buffer", cellDates: true }); + const sheet = workbook.Sheets["Job Sheet"]; // explicitly use Job Sheet + const range = XLSX.utils.decode_range(sheet["!ref"]); + const merges = sheet["!merges"] || []; + + const getValue = (r, c) => { + const addr = XLSX.utils.encode_cell({ r, c }); + const cell = sheet[addr]; + if (cell) return cell.v ?? cell.w ?? null; + for (const m of merges) { + if (m.s.r <= r && r <= m.e.r && m.s.c <= c && c <= m.e.c) { + const master = sheet[XLSX.utils.encode_cell({ r: m.s.r, c: m.s.c })]; + return master ? (master.v ?? master.w ?? null) : null; + } + } + return null; + }; + + // Read headers + const rawHeaders = []; + for (let c = range.s.c; c <= range.e.c; c++) { + let h = getValue(HEADER_ROW_INDEX, c); + rawHeaders.push(h ? String(h).trim() : ""); + } + log(`Found ${rawHeaders.length} headers in Row 3`); + log(`Headers: ${rawHeaders.join(" | ")}`); + // Compare Excel headers against HEADER_MAP + const mappedHeaders = []; + const unmappedHeaders = []; + for (const h of rawHeaders) { + if (headerMap[h]) { + mappedHeaders.push([h, headerMap[h]]); + } else { + unmappedHeaders.push([h, ""]); + } + } + +// Output as CSV-style rows +console.log("\nπ Header mapping preview (CSV style):"); +console.log("ExcelHeader,PocketBaseField"); +for (const [excel, pb] of mappedHeaders) { + console.log(`${excel},${pb}`); +} +for (const [excel, pb] of unmappedHeaders) { + console.log(`${excel},${pb}`); +} + + // Parse data rows + const rows = []; + for (let r = DATA_START_ROW_INDEX; r <= range.e.r; r++) { + const row = {}; + let hasData = false; + for (let c = range.s.c; c <= range.e.c; c++) { + const excelHeader = rawHeaders[c - range.s.c]; + const pbField = headerMap[excelHeader]; + if (!pbField) continue; + let val = getValue(r, c); + if (val !== null && val !== undefined) { + const s = String(val).trim(); + if (s === "" || /^n\/?a$/i.test(s) || s === "#N/A") val = null; + } + row[pbField] = val; + if (val !== null && val !== "") hasData = true; + } + if (hasData) rows.push(row); + } + + log(`Loaded ${rows.length} real jobs`); + if (rows[0]) { + log(`First job preview: Job_Number=${rows[0].Job_Number}, pb_id=${rows[0].id}`); + + } + return rows; +} + +// ----------------------------- +// HEADER MAP +// ----------------------------- +const HEADER_MAP = { + "Job_Number": "Job_Number", + "Job_Full_Name": "Job_Full_Name", + "Job_Name": "Job_Name", + "Job Address": "Job_Address", + "Job Type": "Job_Type", + "Flag": "Flag", + "Tax_Exempt": "Tax_Exempt", + "Job Status": "Job_Status", + "Job Division": "Job_Division", + "Estimator": "Estimator", + "Office Rep": "Office_Rep", + "Manager": "Project_Manager", + "Company/Client": "Company_Client", + "Contact Person": "Contact_Person", + "Phone Number": "Phone_Number", + "Email": "Email", + "Due Date": "Due_Date", + "Due Date Source": "Due_Date_Source", + "Due Date Counter": "Due_Date_Counter", + "Due Time": "Due_Time", + "Tax_Exempt_Docs_Uploaded": "Tax_Exempt_Docs_Uploaded", + "Docs Uploaded": "Docs_Uploaded", + "QB Created": "QB_Created", + "Added to Calendar": "Added_To_Calendar", + "PSwift Uploaded": "PSwift_Uploaded", + "Voxer Created": "Voxer_Created", + "Estimate Reviewed": "Estimate_Reviewed", + "Estimate Draft": "Estimate_Draft", + "Estimate Approved": "Estimate_Approved", + "Start Date": "Start_Date", + "Schedule_Confidence": "Schedule_Confidence", + "Notes": "Notes", + "Job QB Link": "Job_QB_Link", + "Voxer Link": "Voxer_Link", + "Job_Codes": "Job_Codes", + "Submission Date": "Submission_Date", + "Active": "Active", + "Job Folder Link": "Job_Folder_Link", + "pb_id": "id" +}; + +// ----------------------------- +// MAIN +// ----------------------------- +let parsedRows = []; +(async () => { + try { + log("π΅ Starting Excel Syncβ¦"); + + const graphToken = await getGraphToken(); + const pb = await getPocketBaseClient(); + // These can be update if the Excel File changes + const driveId = "b!9YOqqr2xM0G2DFBwMEJYY4UzQgocFddEtpLYWuS9_AtkCKl2q8yjQJteQ7Ti4QSx"; + const itemId = "01SPNXLDSQTMG4F6BDANCLJU2FZKEDQLVU"; + const excelBuffer = await downloadExcelFromGraph(graphToken, driveId, itemId); + parsedRows = parseExcelFile(excelBuffer, HEADER_MAP); + + // Run enhanced test (safe, no writes) + runEnhancedTest(parsedRows, graphToken, pb); + // At this stage you have: + // - graphToken (for SharePoint updates later) + // - pb client (for PB updates) + // - rows[] mapped to PB fields + + const updatedBuffer = exportExcelforDL(parsedRows); + await updateNotesInSharePoint(graphToken, driveId, itemId, "Job Sheet", parsedRows, pb, excelBuffer); + // Sync to PocketBase + //await syncRowsToPocketBase(parsedRows, pb); + + log("β First part complete: tokens + Excel parsing ready"); + } catch (err) { + log("β Sync failed: " + (err.message || err)); + process.exit(1); + } +})(); +// ----------------------------- +// ENHANCED TEST HARNESS +// ----------------------------- +function runEnhancedTest(rows, graphToken, pb) { + log("π Running enhanced test (no writes)β¦"); + + // 1. Verify tokens + log(`Graph token length: ${graphToken.length}`); + log(`PocketBase token length: ${pb.authStore.token.length}`); + + // 2. Verify Excel parsing + log(`Parsed ${rows.length} rows from Excel`); + if (rows.length > 0) { + const sample = rows[0]; + log("Sample row preview:"); + log(` Job_Number: ${sample.Job_Number}`); + log(` Job_Full_Name: ${sample.Job_Full_Name}`); + log(` Active: ${sample.Active}`); + log(` pb_id: ${sample.id}`); + log(` Notes: ${sample.Notes}`); + } + + // 3. Verify header mapping + const mappedFields = Object.values(HEADER_MAP); + const sampleKeys = Object.keys(rows[0] || {}); + const missingFields = mappedFields.filter(f => !sampleKeys.includes(f)); + if (missingFields.length > 0) { + log(`β οΈ Missing mapped fields in sample row: ${missingFields.join(", ")}`); + } else { + log("β All mapped fields present in sample row"); + } + + log("β Enhanced test complete β no writes performed"); +} + + + +// ------------------------- +// Export to xlsx - Excel +// --------------------------- + +// Toggle this flag to enable/disable the export server +const ENABLE_EXPORT = false; // set to false to bypass download + +function exportExcelforDL(parsedRows) { + if (!parsedRows || parsedRows.length === 0) { + throw new Error("β οΈ No rows to export"); + } + + const worksheet = XLSX.utils.json_to_sheet(parsedRows); + + // Ensure Job_Number is the first column + const cols = Object.keys(parsedRows[0] || {}); + if (cols.includes("Job_Number")) { + const ordered = ["Job_Number", ...cols.filter(c => c !== "Job_Number")]; + const remapped = parsedRows.map(r => + ordered.reduce((acc, key) => { acc[key] = r[key]; return acc; }, {}) + ); + const ws2 = XLSX.utils.json_to_sheet(remapped); + Object.assign(worksheet, ws2); + } + + const workbook = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(workbook, worksheet, "ParsedData"); + + return XLSX.write(workbook, { type: "buffer", bookType: "xlsx" }); +} + +if (ENABLE_EXPORT) { + const PORT = 3000; + serve({ + port: PORT, + fetch(req) { + const url = new URL(req.url); + + if (url.pathname === "/") { + const html = ` + + +
Click below to download the parsed Excel file:
+ + +`; + return new Response(html, { headers: { "Content-Type": "text/html" } }); + } + + if (url.pathname === "/download") { + try { + const buffer = exportExcelforDL(parsedRows); + return new Response(buffer, { + headers: { + "Content-Type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "Content-Disposition": "attachment; filename=ParsedOutput.xlsx" + } + }); + } catch (err) { + return new Response("β Failed to generate Excel file: " + err.message, { status: 500 }); + } + } + + return new Response("Visit / to open the export page."); + } + }); + + log(`π ExcelSync export server running at http://localhost:${PORT}/`); + log(`π Open this URL in your browser to verify and download the parsed Excel file.`); +} else { + log("βοΈ Export server bypassed (ENABLE_EXPORT=false)"); +} + + +// ----------------------------- +// UPDATE NOTES CELLS IN SHAREPOINT (REAL-TIME) +// ----------------------------- +function getNotesColumnLetterFromParsedHeaders(headerMap) { + const headers = Object.keys(headerMap); + const notesIndex = headers.findIndex(h => h.trim().toLowerCase() === "notes"); + if (notesIndex === -1) { + log(`β 'Notes' header not found in HEADER_MAP keys: ${headers.join(", ")}`); + throw new Error("Notes column not found"); + } + return columnLetter(notesIndex); +} + +// Update only changed Notes cells using workbook session + PATCH. +// This version relies solely on DATA_START_ROW_INDEX for row mapping. +async function updateNotesInSharePoint(graphToken, driveId, itemId, worksheetName, parsedRows, pb, workbookBuffer) { + const wb = XLSX.read(workbookBuffer, { type: "buffer", cellDates: true }); + const sheet = wb.Sheets[worksheetName]; + if (!sheet) throw new Error(`Worksheet "${worksheetName}" not found`); + + // Column detection aligned to HEADER_MAP (same mapping used by parseExcelFile) + const notesColumnLetter = getNotesColumnLetterFromParsedHeaders(HEADER_MAP); + log(`β Notes column resolved to ${notesColumnLetter} via HEADER_MAP`); + + // Create a workbook session that persists changes + const sessionRes = await axios.post( + `https://graph.microsoft.com/v1.0/drives/${driveId}/items/${itemId}/workbook/createSession`, + { persistChanges: true }, + { headers: { Authorization: `Bearer ${graphToken}` } } + ); + const sessionId = sessionRes.data.id; + + let changes = 0; + + // Loop parsed rows; map i to Excel row using DATA_START_ROW_INDEX (zero-based) + for (let i = 0; i < parsedRows.length; i++) { + const row = parsedRows[i]; + const jobNumber = row.Job_Number; + if (jobNumber == null || jobNumber === "") continue; + + try { + const records = await pb.collection(pbCollection).getFullList({ + filter: Number.isFinite(jobNumber) ? `Job_Number=${jobNumber}` : `Job_Number="${jobNumber}"`, + batchSize: 1 + }); + if (records.length === 0) continue; + + // Normalize notes for fair comparison + const normalize = v => (v ?? "").toString().replace(/\s+/g, " ").trim(); + const pbNotes = normalize(records[0].Notes); + const excelNotes = normalize(row.Notes); + + if (pbNotes !== excelNotes) { + // Excel uses 1-based row numbers; DATA_START_ROW_INDEX is zero-based + const excelRowNumber = DATA_START_ROW_INDEX + i + 1; + const cellAddress = `${notesColumnLetter}${excelRowNumber}`; + + log(`βοΈ Notes change for Job_Number=${jobNumber}`); + log(` Before: "${excelNotes}"`); + log(` After : "${pbNotes}"`); + log(`β‘οΈ Writing to ${worksheetName}!${cellAddress}`); + + await axios.patch( + `https://graph.microsoft.com/v1.0/drives/${driveId}/items/${itemId}/workbook/worksheets('${worksheetName}')/range(address='${cellAddress}')`, + { values: [[pbNotes]] }, + { + headers: { + Authorization: `Bearer ${graphToken}`, + "Content-Type": "application/json", + "workbook-session-id": sessionId + } + } + ); + + row.Notes = pbNotes; + changes++; + } + } catch (err) { + log(`β Failed for Job_Number=${jobNumber}: ${err.message}`); + } + } + + await axios.post( + `https://graph.microsoft.com/v1.0/drives/${driveId}/items/${itemId}/workbook/closeSession`, + {}, + { headers: { Authorization: `Bearer ${graphToken}`, "workbook-session-id": sessionId } } + ); + + log(`β Notes-only sync complete. Changes applied: ${changes}`); +} \ No newline at end of file diff --git a/backend/src/excelsync.log b/backend/src/excelsync.log new file mode 100644 index 0000000..e69de29 diff --git a/backend/src/excelsyncbk.js b/backend/src/excelsyncbk.js new file mode 100644 index 0000000..e1021f9 --- /dev/null +++ b/backend/src/excelsyncbk.js @@ -0,0 +1,370 @@ +// excelsync.js +import fs from "fs"; +import axios from "axios"; +import qs from "qs"; +import * as XLSX from "xlsx"; +import dotenv from "dotenv"; +import { serve } from "bun"; +import PocketBase from "pocketbase" + +dotenv.config(); + +// ----------------------------- +// CONFIG +// ----------------------------- +const CONFIG = JSON.parse(fs.readFileSync("config.json", "utf8")); +const tenantId = CONFIG.tenantId; +const clientId = CONFIG.clientId; +const sharepointFileUrl = CONFIG.sharepointFileUrl; // production Excel file URL +const pbUrl = CONFIG.pbUrl; +const pbCollection = CONFIG.pbCollection || "Job_Info_TestEnv"; +const pbEmail = CONFIG.pbEmail; + +const clientSecret = process.env.CLIENT_SECRET; +const pbPassword = process.env.PB_PASSWORD; + +if (!tenantId || !clientId || !clientSecret || !sharepointFileUrl || !pbUrl || !pbEmail || !pbPassword) { + console.error("β Missing required config/env values"); + process.exit(1); +} +console.log({ + tenantId, + clientId, + clientSecret: clientSecret ? clientSecret.slice(0,4) + "..." : null, + sharepointFileUrl, + pbUrl, + pbEmail, + pbPassword: pbPassword ? "***" : null +}); +// ----------------------------- +// LOGGING +// ----------------------------- +function log(message) { + console.log(message); + fs.appendFileSync("src/excelsync.log", message + "\n"); +} + +// ----------------------------- +// GRAPH TOKEN +// ----------------------------- +async function getGraphToken() { + const tokenUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`; + const body = qs.stringify({ + client_id: clientId, + client_secret: clientSecret, + scope: "https://graph.microsoft.com/.default", + grant_type: "client_credentials", + }); + const res = await axios.post(tokenUrl, body, { + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + }); + log("β Graph token acquired"); + return res.data.access_token; +} + +// ----------------------------- +// GRAPH DOWNLOADER HELPER +// ----------------------------- +async function downloadExcelFromGraph(graphToken, driveId, itemId) { + const url = `https://graph.microsoft.com/v1.0/drives/${driveId}/items/${itemId}/content`; + const res = await axios.get(url, { + headers: { Authorization: `Bearer ${graphToken}` }, + responseType: "arraybuffer", // important: get raw binary + }); + return Buffer.from(res.data); // convert to Buffer for XLSX +} + +// ----------------------------- +// POCKETBASE LOGIN +// ----------------------------- +async function getPocketBaseClient() { + const pb = new PocketBase(pbUrl); + await pb.collection("users").authWithPassword(pbEmail, pbPassword); + log("β PocketBase token acquired"); + return pb; +} + +// ----------------------------- +// EXCEL PARSER +// ----------------------------- +const HEADER_ROW_INDEX = 2; // Row 3 +const DATA_START_ROW_INDEX = 3; // Row 4 + +function parseExcelFile(input, headerMap) { + let data; + // In case of data from graph + if (Buffer.isBuffer(input)) { + data = input; + log(`Reading Excel file from Graph buffer`); + // In case of file path (Not used at this time) + } else { + data = fs.readFileSync(input); + log(`Reading Excel file: ${input}`); + } + + const workbook = XLSX.read(data, { type: "buffer", cellDates: true }); + const sheet = workbook.Sheets["Job Sheet"]; // explicitly use Job Sheet + const range = XLSX.utils.decode_range(sheet["!ref"]); + const merges = sheet["!merges"] || []; + + const getValue = (r, c) => { + const addr = XLSX.utils.encode_cell({ r, c }); + const cell = sheet[addr]; + if (cell) return cell.v ?? cell.w ?? null; + for (const m of merges) { + if (m.s.r <= r && r <= m.e.r && m.s.c <= c && c <= m.e.c) { + const master = sheet[XLSX.utils.encode_cell({ r: m.s.r, c: m.s.c })]; + return master ? (master.v ?? master.w ?? null) : null; + } + } + return null; + }; + + // Read headers + const rawHeaders = []; + for (let c = range.s.c; c <= range.e.c; c++) { + let h = getValue(HEADER_ROW_INDEX, c); + rawHeaders.push(h ? String(h).trim() : ""); + } + log(`Found ${rawHeaders.length} headers in Row 3`); + log(`Headers: ${rawHeaders.join(" | ")}`); + // Compare Excel headers against HEADER_MAP + const mappedHeaders = []; + const unmappedHeaders = []; + for (const h of rawHeaders) { + if (headerMap[h]) { + mappedHeaders.push([h, headerMap[h]]); + } else { + unmappedHeaders.push([h, ""]); + } + } + +// Output as CSV-style rows +console.log("\nπ Header mapping preview (CSV style):"); +console.log("ExcelHeader,PocketBaseField"); +for (const [excel, pb] of mappedHeaders) { + console.log(`${excel},${pb}`); +} +for (const [excel, pb] of unmappedHeaders) { + console.log(`${excel},${pb}`); +} + + // Parse data rows + const rows = []; + for (let r = DATA_START_ROW_INDEX; r <= range.e.r; r++) { + const row = {}; + let hasData = false; + for (let c = range.s.c; c <= range.e.c; c++) { + const excelHeader = rawHeaders[c - range.s.c]; + const pbField = headerMap[excelHeader]; + if (!pbField) continue; + let val = getValue(r, c); + if (val !== null && val !== undefined) { + const s = String(val).trim(); + if (s === "" || /^n\/?a$/i.test(s) || s === "#N/A") val = null; + } + row[pbField] = val; + if (val !== null && val !== "") hasData = true; + } + if (hasData) rows.push(row); + } + + log(`Loaded ${rows.length} real jobs`); + if (rows[0]) { + log(`First job preview: Job_Number=${rows[0].Job_Number}, pb_id=${rows[0].id}`); + + } + return rows; +} + +// ----------------------------- +// HEADER MAP +// ----------------------------- +const HEADER_MAP = { + "Job_Number": "Job_Number", + "Job_Full_Name": "Job_Full_Name", + "Job_Name": "Job_Name", + "Job Address": "Job_Address", + "Job Type": "Job_Type", + "Flag": "Flag", + "Tax_Exempt": "Tax_Exempt", + "Job Status": "Job_Status", + "Job Division": "Division", + "Estimator": "Estimator", + "Office Rep": "Office_Rep", + "Manager": "Project_Manager", + "Company/Client": "Company_Client", + "Contact Person": "Contact_Person", + "Phone Number": "Phone_Number", + "Email": "Email", + "Due Date": "Due_Date", + "Due Date Source": "Due_Date_Source", + "Due Date Counter": "Due_Date_Counter", + "Due Time": "Due_Time", + "Tax_Exempt_Docs_Uploaded": "Tax_Exempt_Docs_Uploaded", + "Docs Uploaded": "Docs_Uploaded", + "QB Created": "QB_Created", + "Added to Calendar": "Calendar_Added", + "PSwift Uploaded": "PSwift_Uploaded", + "Voxer Created": "Voxer_Created", + "Estimate Reviewed": "Estimate_Reviewed", + "Estimate Draft": "Estimate_Draft", + "Estimate Approved": "Estimate_Approved", + "Start Date": "Start_Date", + "Schedule_Confidence": "Schedule_Confidence", + "Notes": "Notes", + "Job QB Link": "Job_QB_Link", + "Voxer Link": "Voxer_Link", + "Job_Codes": "Job_Codes", + "Submission Date": "Submission_Date", + "Active": "Active", + "Job Folder Link": "Job_Folder_Link", + "pb_id": "id" +}; + +// ----------------------------- +// MAIN +// ----------------------------- +let parsedRows = []; +(async () => { + try { + log("π΅ Starting Excel Syncβ¦"); + + const graphToken = await getGraphToken(); + const pb = await getPocketBaseClient(); + // These can be update if the Excel File changes + const driveId = "b!9YOqqr2xM0G2DFBwMEJYY4UzQgocFddEtpLYWuS9_AtkCKl2q8yjQJteQ7Ti4QSx"; + const itemId = "01SPNXLDSQTMG4F6BDANCLJU2FZKEDQLVU"; + const excelBuffer = await downloadExcelFromGraph(graphToken, driveId, itemId); + parsedRows = parseExcelFile(excelBuffer, HEADER_MAP); + + // Run enhanced test (safe, no writes) + runEnhancedTest(parsedRows, graphToken, pb); + // At this stage you have: + // - graphToken (for SharePoint updates later) + // - pb client (for PB updates) + // - rows[] mapped to PB fields + + // Sync to PocketBase + // await syncRowsToPocketBase(parsedRows, pb); + + log("β First part complete: tokens + Excel parsing ready"); + } catch (err) { + log("β Sync failed: " + (err.message || err)); + process.exit(1); + } +})(); +// ----------------------------- +// ENHANCED TEST HARNESS +// ----------------------------- +function runEnhancedTest(rows, graphToken, pb) { + log("π Running enhanced test (no writes)β¦"); + + // 1. Verify tokens + log(`Graph token length: ${graphToken.length}`); + log(`PocketBase token length: ${pb.authStore.token.length}`); + + // 2. Verify Excel parsing + log(`Parsed ${rows.length} rows from Excel`); + if (rows.length > 0) { + const sample = rows[0]; + log("Sample row preview:"); + log(` Job_Number: ${sample.Job_Number}`); + log(` Job_Full_Name: ${sample.Job_Full_Name}`); + log(` Active: ${sample.Active}`); + log(` pb_id: ${sample.id}`); + log(` Notes: ${sample.Notes}`); + } + + // 3. Verify header mapping + const mappedFields = Object.values(HEADER_MAP); + const sampleKeys = Object.keys(rows[0] || {}); + const missingFields = mappedFields.filter(f => !sampleKeys.includes(f)); + if (missingFields.length > 0) { + log(`β οΈ Missing mapped fields in sample row: ${missingFields.join(", ")}`); + } else { + log("β All mapped fields present in sample row"); + } + + log("β Enhanced test complete β no writes performed"); +} + + + +// ------------------------- +// Export to xlsx - Excel +// --------------------------- + +function exportExcelforDL(parsedRows) { + if (!parsedRows || parsedRows.length === 0) { + throw new Error("β οΈ No rows to export"); + } + + const worksheet = XLSX.utils.json_to_sheet(parsedRows); + + // Ensure Job_Number is the first column + const cols = Object.keys(parsedRows[0] || {}); + if (cols.includes("Job_Number")) { + const ordered = ["Job_Number", ...cols.filter(c => c !== "Job_Number")]; + const remapped = parsedRows.map(r => + ordered.reduce((acc, key) => { acc[key] = r[key]; return acc; }, {}) + ); + const ws2 = XLSX.utils.json_to_sheet(remapped); + Object.assign(worksheet, ws2); + } + + const workbook = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(workbook, worksheet, "ParsedData"); + + return XLSX.write(workbook, { type: "buffer", bookType: "xlsx" }); +} + +function isParsedReady() { + return Array.isArray(parsedRows) && parsedRows.length > 0; +} + +const PORT = 3000; + +serve({ + port: PORT, + fetch(req) { + const url = new URL(req.url); + + if (url.pathname === "/") { + const html = ` + + +Click below to download the parsed Excel file:
+ + +`; + return new Response(html, { headers: { "Content-Type": "text/html" } }); + } + + if (url.pathname === "/download") { + try { + if (!isParsedReady()) { + return new Response("β οΈ Parsed rows are not ready yet. Try again in a moment.", { status: 409 }); + } + const buffer = exportExcelforDL(parsedRows); + return new Response(buffer, { + headers: { + "Content-Type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "Content-Disposition": "attachment; filename=ParsedOutput.xlsx" + } + }); + } catch (err) { + return new Response("β Failed to generate Excel file: " + err.message, { status: 500 }); + } + } + + return new Response("Visit / to open the export page."); + } +}); + +// Prompt on startup +log(`π ExcelSync export server running at http://localhost:${PORT}/`); +log(`π Open this URL in your browser to verify and download the parsed Excel file.`); diff --git a/backend/src/excelsyncnew.js b/backend/src/excelsyncnew.js new file mode 100644 index 0000000..348e080 --- /dev/null +++ b/backend/src/excelsyncnew.js @@ -0,0 +1,375 @@ +//notes - works to get excel downloaded bypass downloader with "false" +// excelsync.js +import fs from "fs"; +import axios from "axios"; +import qs from "qs"; +import * as XLSX from "xlsx"; +import dotenv from "dotenv"; +import { serve } from "bun"; +import PocketBase from "pocketbase" + +dotenv.config(); + +// ----------------------------- +// CONFIG +// ----------------------------- +const CONFIG = JSON.parse(fs.readFileSync("config.json", "utf8")); +const tenantId = CONFIG.tenantId; +const clientId = CONFIG.clientId; +const sharepointFileUrl = CONFIG.sharepointFileUrl; // production Excel file URL +const pbUrl = CONFIG.pbUrl; +const pbCollection = CONFIG.pbCollection || "Job_Info_TestEnv"; +const pbEmail = CONFIG.pbEmail; + +const clientSecret = process.env.CLIENT_SECRET; +const pbPassword = process.env.PB_PASSWORD; + +if (!tenantId || !clientId || !clientSecret || !sharepointFileUrl || !pbUrl || !pbEmail || !pbPassword) { + console.error("β Missing required config/env values"); + process.exit(1); +} +console.log({ + tenantId, + clientId, + clientSecret: clientSecret ? clientSecret.slice(0,4) + "..." : null, + sharepointFileUrl, + pbUrl, + pbEmail, + pbPassword: pbPassword ? "***" : null +}); +// ----------------------------- +// LOGGING +// ----------------------------- +function log(message) { + console.log(message); + fs.appendFileSync("src/excelsync.log", message + "\n"); +} + +// ----------------------------- +// GRAPH TOKEN +// ----------------------------- +async function getGraphToken() { + const tokenUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`; + const body = qs.stringify({ + client_id: clientId, + client_secret: clientSecret, + scope: "https://graph.microsoft.com/.default", + grant_type: "client_credentials", + }); + const res = await axios.post(tokenUrl, body, { + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + }); + log("β Graph token acquired"); + return res.data.access_token; +} + +// ----------------------------- +// GRAPH DOWNLOADER HELPER +// ----------------------------- +async function downloadExcelFromGraph(graphToken, driveId, itemId) { + const url = `https://graph.microsoft.com/v1.0/drives/${driveId}/items/${itemId}/content`; + const res = await axios.get(url, { + headers: { Authorization: `Bearer ${graphToken}` }, + responseType: "arraybuffer", // important: get raw binary + }); + return Buffer.from(res.data); // convert to Buffer for XLSX +} + +// ----------------------------- +// POCKETBASE LOGIN +// ----------------------------- +async function getPocketBaseClient() { + const pb = new PocketBase(pbUrl); + await pb.collection("users").authWithPassword(pbEmail, pbPassword); + log("β PocketBase token acquired"); + return pb; +} + +// ----------------------------- +// EXCEL PARSER +// ----------------------------- +const HEADER_ROW_INDEX = 2; // Row 3 +const DATA_START_ROW_INDEX = 3; // Row 4 + +function parseExcelFile(input, headerMap) { + let data; + // In case of data from graph + if (Buffer.isBuffer(input)) { + data = input; + log(`Reading Excel file from Graph buffer`); + // In case of file path (Not used at this time) + } else { + data = fs.readFileSync(input); + log(`Reading Excel file: ${input}`); + } + + const workbook = XLSX.read(data, { type: "buffer", cellDates: true }); + const sheet = workbook.Sheets["Job Sheet"]; // explicitly use Job Sheet + const range = XLSX.utils.decode_range(sheet["!ref"]); + const merges = sheet["!merges"] || []; + + const getValue = (r, c) => { + const addr = XLSX.utils.encode_cell({ r, c }); + const cell = sheet[addr]; + if (cell) return cell.v ?? cell.w ?? null; + for (const m of merges) { + if (m.s.r <= r && r <= m.e.r && m.s.c <= c && c <= m.e.c) { + const master = sheet[XLSX.utils.encode_cell({ r: m.s.r, c: m.s.c })]; + return master ? (master.v ?? master.w ?? null) : null; + } + } + return null; + }; + + // Read headers + const rawHeaders = []; + for (let c = range.s.c; c <= range.e.c; c++) { + let h = getValue(HEADER_ROW_INDEX, c); + rawHeaders.push(h ? String(h).trim() : ""); + } + log(`Found ${rawHeaders.length} headers in Row 3`); + log(`Headers: ${rawHeaders.join(" | ")}`); + // Compare Excel headers against HEADER_MAP + const mappedHeaders = []; + const unmappedHeaders = []; + for (const h of rawHeaders) { + if (headerMap[h]) { + mappedHeaders.push([h, headerMap[h]]); + } else { + unmappedHeaders.push([h, ""]); + } + } + +// Output as CSV-style rows +console.log("\nπ Header mapping preview (CSV style):"); +console.log("ExcelHeader,PocketBaseField"); +for (const [excel, pb] of mappedHeaders) { + console.log(`${excel},${pb}`); +} +for (const [excel, pb] of unmappedHeaders) { + console.log(`${excel},${pb}`); +} + + // Parse data rows + const rows = []; + for (let r = DATA_START_ROW_INDEX; r <= range.e.r; r++) { + const row = {}; + let hasData = false; + for (let c = range.s.c; c <= range.e.c; c++) { + const excelHeader = rawHeaders[c - range.s.c]; + const pbField = headerMap[excelHeader]; + if (!pbField) continue; + let val = getValue(r, c); + if (val !== null && val !== undefined) { + const s = String(val).trim(); + if (s === "" || /^n\/?a$/i.test(s) || s === "#N/A") val = null; + } + row[pbField] = val; + if (val !== null && val !== "") hasData = true; + } + if (hasData) rows.push(row); + } + + log(`Loaded ${rows.length} real jobs`); + if (rows[0]) { + log(`First job preview: Job_Number=${rows[0].Job_Number}, pb_id=${rows[0].id}`); + + } + return rows; +} + +// ----------------------------- +// HEADER MAP +// ----------------------------- +const HEADER_MAP = { + "Job_Number": "Job_Number", + "Job_Full_Name": "Job_Full_Name", + "Job_Name": "Job_Name", + "Job Address": "Job_Address", + "Job Type": "Job_Type", + "Flag": "Flag", + "Tax_Exempt": "Tax_Exempt", + "Job Status": "Job_Status", + "Job Division": "Job_Division", + "Estimator": "Estimator", + "Office Rep": "Office_Rep", + "Manager": "Project_Manager", + "Company/Client": "Company_Client", + "Contact Person": "Contact_Person", + "Phone Number": "Phone_Number", + "Email": "Email", + "Due Date": "Due_Date", + "Due Date Source": "Due_Date_Source", + "Due Date Counter": "Due_Date_Counter", + "Due Time": "Due_Time", + "Tax_Exempt_Docs_Uploaded": "Tax_Exempt_Docs_Uploaded", + "Docs Uploaded": "Docs_Uploaded", + "QB Created": "QB_Created", + "Added to Calendar": "Added_To_Calendar", + "PSwift Uploaded": "PSwift_Uploaded", + "Voxer Created": "Voxer_Created", + "Estimate Reviewed": "Estimate_Reviewed", + "Estimate Draft": "Estimate_Draft", + "Estimate Approved": "Estimate_Approved", + "Start Date": "Start_Date", + "Schedule_Confidence": "Schedule_Confidence", + "Notes": "Notes", + "Job QB Link": "Job_QB_Link", + "Voxer Link": "Voxer_Link", + "Job_Codes": "Job_Codes", + "Submission Date": "Submission_Date", + "Active": "Active", + "Job Folder Link": "Job_Folder_Link", + "pb_id": "id" +}; + +// ----------------------------- +// MAIN +// ----------------------------- +let parsedRows = []; +(async () => { + try { + log("π΅ Starting Excel Syncβ¦"); + + const graphToken = await getGraphToken(); + const pb = await getPocketBaseClient(); + // These can be update if the Excel File changes + const driveId = "b!9YOqqr2xM0G2DFBwMEJYY4UzQgocFddEtpLYWuS9_AtkCKl2q8yjQJteQ7Ti4QSx"; + const itemId = "01SPNXLDSQTMG4F6BDANCLJU2FZKEDQLVU"; + const excelBuffer = await downloadExcelFromGraph(graphToken, driveId, itemId); + parsedRows = parseExcelFile(excelBuffer, HEADER_MAP); + + // Run enhanced test (safe, no writes) + runEnhancedTest(parsedRows, graphToken, pb); + // At this stage you have: + // - graphToken (for SharePoint updates later) + // - pb client (for PB updates) + // - rows[] mapped to PB fields + + // Sync to PocketBase + //await syncRowsToPocketBase(parsedRows, pb); + + log("β First part complete: tokens + Excel parsing ready"); + } catch (err) { + log("β Sync failed: " + (err.message || err)); + process.exit(1); + } +})(); +// ----------------------------- +// ENHANCED TEST HARNESS +// ----------------------------- +function runEnhancedTest(rows, graphToken, pb) { + log("π Running enhanced test (no writes)β¦"); + + // 1. Verify tokens + log(`Graph token length: ${graphToken.length}`); + log(`PocketBase token length: ${pb.authStore.token.length}`); + + // 2. Verify Excel parsing + log(`Parsed ${rows.length} rows from Excel`); + if (rows.length > 0) { + const sample = rows[0]; + log("Sample row preview:"); + log(` Job_Number: ${sample.Job_Number}`); + log(` Job_Full_Name: ${sample.Job_Full_Name}`); + log(` Active: ${sample.Active}`); + log(` pb_id: ${sample.id}`); + log(` Notes: ${sample.Notes}`); + } + + // 3. Verify header mapping + const mappedFields = Object.values(HEADER_MAP); + const sampleKeys = Object.keys(rows[0] || {}); + const missingFields = mappedFields.filter(f => !sampleKeys.includes(f)); + if (missingFields.length > 0) { + log(`β οΈ Missing mapped fields in sample row: ${missingFields.join(", ")}`); + } else { + log("β All mapped fields present in sample row"); + } + + log("β Enhanced test complete β no writes performed"); +} + + + +// ------------------------- +// Export to xlsx - Excel +// --------------------------- + +// Toggle this flag to enable/disable the export server +const ENABLE_EXPORT = true; // set to false to bypass download + +function exportExcelforDL(parsedRows) { + if (!parsedRows || parsedRows.length === 0) { + throw new Error("β οΈ No rows to export"); + } + + const worksheet = XLSX.utils.json_to_sheet(parsedRows); + + // Ensure Job_Number is the first column + const cols = Object.keys(parsedRows[0] || {}); + if (cols.includes("Job_Number")) { + const ordered = ["Job_Number", ...cols.filter(c => c !== "Job_Number")]; + const remapped = parsedRows.map(r => + ordered.reduce((acc, key) => { acc[key] = r[key]; return acc; }, {}) + ); + const ws2 = XLSX.utils.json_to_sheet(remapped); + Object.assign(worksheet, ws2); + } + + const workbook = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(workbook, worksheet, "ParsedData"); + + return XLSX.write(workbook, { type: "buffer", bookType: "xlsx" }); +} + +if (ENABLE_EXPORT) { + const PORT = 3000; + serve({ + port: PORT, + fetch(req) { + const url = new URL(req.url); + + if (url.pathname === "/") { + const html = ` + + +Click below to download the parsed Excel file:
+ + +`; + return new Response(html, { headers: { "Content-Type": "text/html" } }); + } + + if (url.pathname === "/download") { + try { + const buffer = exportExcelforDL(parsedRows); + return new Response(buffer, { + headers: { + "Content-Type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "Content-Disposition": "attachment; filename=ParsedOutput.xlsx" + } + }); + } catch (err) { + return new Response("β Failed to generate Excel file: " + err.message, { status: 500 }); + } + } + + return new Response("Visit / to open the export page."); + } + }); + + log(`π ExcelSync export server running at http://localhost:${PORT}/`); + log(`π Open this URL in your browser to verify and download the parsed Excel file.`); +} else { + log("βοΈ Export server bypassed (ENABLE_EXPORT=false)"); +} + + +// ----------------------------- +// FINAL SYNC FUNCTION +// ----------------------------- + diff --git a/backend/src/fieldConfig.js b/backend/src/fieldConfig.js new file mode 100644 index 0000000..b7eb227 --- /dev/null +++ b/backend/src/fieldConfig.js @@ -0,0 +1,194 @@ +// fieldConfig.js +export const FIELD_CONFIG = { + id: { + excelHeader: "pb_id", + pbField: "id", + type: "string", + required: true + }, + Job_Number: { + excelHeader: "Job_Number", + pbField: "Job_Number", + type: "string" + }, + Job_Full_Name: { + excelHeader: "Job_Full_Name", + pbField: "Job_Full_Name", + type: "string" + }, + Job_Name: { + excelHeader: "Job_Name", + pbField: "Job_Name", + type: "string" + }, + Job_Address: { + excelHeader: "Job Address", + pbField: "Job_Address", + type: "string" + }, + Job_Type: { + excelHeader: "Job Type", + pbField: "Job_Type", + type: "string" + }, + Job_Status: { + excelHeader: "Job Status", + pbField: "Job_Status", + type: "string" + }, + Job_Division: { + excelHeader: "Job Division", + pbField: "Job_Division", + type: "string" + }, + Estimator: { + excelHeader: "Estimator", + pbField: "Estimator", + type: "string" + }, + Office_Rep: { + excelHeader: "Office Rep", + pbField: "Office_Rep", + type: "string" + }, + Project_Manager: { + excelHeader: "Manager", + pbField: "Project_Manager", + type: "string" + }, + Company_Client: { + excelHeader: "Company/Client", + pbField: "Company_Client", + type: "string" + }, + Contact_Person: { + excelHeader: "Contact Person", + pbField: "Contact_Person", + type: "string" + }, + Phone_Number: { + excelHeader: "Phone Number", + pbField: "Phone_Number", + type: "string" + }, + Email: { + excelHeader: "Email", + pbField: "Email", + type: "string" + }, + Due_Date: { + excelHeader: "Due Date", + pbField: "Due_Date", + type: "date" + }, + Due_Date_Source: { + excelHeader: "Due Date Source", + pbField: "Due_Date_Source", + type: "string" + }, + Due_Date_Counter: { + excelHeader: "Due Date Counter", + pbField: "Due_Date_Counter", + type: "string" + }, + Due_Time: { + excelHeader: "Due Time", + pbField: "Due_Time", + type: "string" + }, + Docs_Uploaded: { + excelHeader: "Docs Uploaded", + pbField: "Docs_Uploaded", + type: "bool" + }, + QB_Created: { + excelHeader: "QB Created", + pbField: "QB_Created", + type: "bool" + }, + Added_To_Calendar: { + excelHeader: "Added to Calendar", + pbField: "Added_To_Calendar", + type: "bool" + }, + PSwift_Uploaded: { + excelHeader: "PSwift Uploaded", + pbField: "PSwift_Uploaded", + type: "bool" + }, + Voxer_Created: { + excelHeader: "Voxer Created", + pbField: "Voxer_Created", + type: "bool" + }, + Estimate_Reviewed: { + excelHeader: "Estimate Reviewed", + pbField: "Estimate_Reviewed", + type: "bool" + }, + Estimate_Draft: { + excelHeader: "Estimate Draft", + pbField: "Estimate_Draft", + type: "bool" + }, + Estimate_Approved: { + excelHeader: "Estimate Approved", + pbField: "Estimate_Approved", + type: "bool" + }, + Start_Date: { + excelHeader: "Start Date", + pbField: "Start_Date", + type: "date" + }, + Submission_Date: { + excelHeader: "Submission Date", + pbField: "Submission_Date", + type: "date" + }, + Active: { + excelHeader: "Active", + pbField: "Active", + type: "bool" + }, + Tax_Exempt: { + excelHeader: "Tax_Exempt", + pbField: "Tax_Exempt", + type: "bool" + }, + Tax_Exempt_Docs_Uploaded: { + excelHeader: "Tax_Exempt_Docs_Uploaded", + pbField: "Tax_Exempt_Docs_Uploaded", + type: "bool" + }, + Flag: { + excelHeader: "Flag", + pbField: "Flag", + type: "bool" + }, + Notes: { + excelHeader: "Notes", + pbField: "Notes", + type: "string" + }, + Job_QB_Link: { + excelHeader: "Job QB Link", + pbField: "Job_QB_Link", + type: "string" + }, + Voxer_Link: { + excelHeader: "Voxer Link", + pbField: "Voxer_Link", + type: "string" + }, + Job_Codes: { + excelHeader: "Job_Codes", + pbField: "Job_Codes", + type: "string" + }, + Job_Folder_Link: { + excelHeader: "Job Folder Link", + pbField: "Job_Folder_Link", + type: "string" + } +}; diff --git a/backend/src/server.ts b/backend/src/server.ts new file mode 100644 index 0000000..cfa9467 --- /dev/null +++ b/backend/src/server.ts @@ -0,0 +1,26 @@ +import dotenv from "dotenv"; +import PocketBase from "pocketbase"; + +dotenv.config(); + +const pb = new PocketBase(process.env.PB_URL); + +const email = process.env.PB_EMAIL; +const password = process.env.PB_PASSWORD; + +if (!email || !password) { + throw new Error("Missing PB_EMAIL or PB_PASSWORD in .env"); +} +try { + await pb.collection("users").authWithPassword(email, password); +} catch (err: any) { + console.error("Error response:", err.response); +} + +const authData = await pb.collection("users").authWithPassword(email, password); + +console.log("Valid:", pb.authStore.isValid); +console.log("Token:", pb.authStore.token); +console.log("User ID:", pb.authStore.record?.id); + +pb.authStore.clear(); \ No newline at end of file diff --git a/backend/src/sharepointdownload.js b/backend/src/sharepointdownload.js new file mode 100644 index 0000000..f8c4751 --- /dev/null +++ b/backend/src/sharepointdownload.js @@ -0,0 +1,77 @@ +// sharepointDownload.js +import axios from "axios"; +import qs from "qs"; +import dotenv from "dotenv"; +import fs from "fs"; + +dotenv.config(); + +// ----------------------------- +// CONFIG +// ----------------------------- +const CONFIG = JSON.parse(fs.readFileSync("config.json", "utf8")); +const tenantId = CONFIG.tenantId; +const clientId = CONFIG.clientId; + +// Secret stays in .env +const clientSecret = process.env.CLIENT_SECRET; + +// ----------------------------- +// GRAPH TOKEN +// ----------------------------- +async function getGraphToken() { + const tokenUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`; + const body = qs.stringify({ + client_id: clientId, + client_secret: clientSecret, + scope: "https://graph.microsoft.com/.default", + grant_type: "client_credentials", + }); + const res = await axios.post(tokenUrl, body, { + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + }); + console.log("β Graph token acquired"); + return res.data.access_token; +} + +// ----------------------------- +// DOWNLOAD BY ITEM ID +// ----------------------------- +// Example link: https://czflex.sharepoint.com/sites/Team/Shared%20Documents/General/JOBS%201000-9999.xlsx?d=wc20d9b5023f84403b4d345ca88382eb4 +// The ?d=... part is the itemId +async function downloadFileById(graphToken, sitePath, itemId) { + const url = `https://graph.microsoft.com/v1.0/sites/${sitePath}/drive/items/${itemId}/content`; + const res = await axios.get(url, { + headers: { Authorization: `Bearer ${graphToken}` }, + responseType: "arraybuffer", + }); + console.log("β File downloaded, bytes:", res.data.byteLength); + return res.data; +} + +// ----------------------------- +// MAIN +// ----------------------------- +(async () => { + try { + const graphToken = await getGraphToken(); + + // sitePath matches your site + const sitePath = "czflex.sharepoint.com:/sites/Team"; + + // itemId from your link (?d=...) + const itemId = "wc20d9b5023f84403b4d345ca88382eb4"; + + const buffer = await downloadFileById(graphToken, sitePath, itemId); + + // At this point you can feed buffer into XLSX.read(buffer, { type: "buffer", cellDates: true }) + // Example: + // const workbook = XLSX.read(buffer, { type: "buffer", cellDates: true }); + // const sheetNames = workbook.SheetNames; + // console.log("Sheets:", sheetNames); + + } catch (err) { + console.error("β Error:", err.response?.data || err.message || err); + } +})(); +"id": "czflex.sharepoint.com,aaaa83f5-b1bd-4133-b60c-507030425863,0a423385-151c-44d7-b692-d85ae4bdfc0b" \ No newline at end of file diff --git a/backend/src/streamsync.js b/backend/src/streamsync.js new file mode 100644 index 0000000..78cda04 --- /dev/null +++ b/backend/src/streamsync.js @@ -0,0 +1,260 @@ +// excelsync.js +import { FIELD_CONFIG } from "./fieldConfig.js"; +import fs from "fs"; +import axios from "axios"; +import qs from "qs"; +import dotenv from "dotenv"; +import PocketBase from "pocketbase"; + +dotenv.config(); + +// ----------------------------- +// EXCEL PARSER +// ----------------------------- +const HEADER_ROW_INDEX = 2; // 0-index β Row 3 actual +const DATA_START_ROW_INDEX = 3; // 0-index β Row 4 actual + +// ----------------------------- +// CONFIG +// ----------------------------- +const CONFIG = JSON.parse(fs.readFileSync("config.json", "utf8")); +const tenantId = CONFIG.tenantId; +const clientId = CONFIG.clientId; +const pbUrl = CONFIG.pbUrl; +const pbCollection = CONFIG.pbCollection || "Job_Info_TestEnv"; +const pbEmail = CONFIG.pbEmail; +const clientSecret = process.env.CLIENT_SECRET; +const pbPassword = process.env.PB_PASSWORD; + +// ----------------------------- +// LOGGING +// ----------------------------- +function log(message) { + console.log(message); + fs.appendFileSync("excelsync.log", message + "\n"); +} + +// ----------------------------- +// GRAPH TOKEN +// ----------------------------- +async function getGraphToken() { + log("π΅ Requesting Graph token..."); + const tokenUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`; + const body = qs.stringify({ + client_id: clientId, + client_secret: clientSecret, + scope: "https://graph.microsoft.com/.default", + grant_type: "client_credentials", + }); + const res = await axios.post(tokenUrl, body, { + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + }); + log("β Graph token acquired"); + return res.data.access_token; +} + +// ----------------------------- +// POCKETBASE LOGIN +// ----------------------------- +async function getPocketBaseClient() { + log("π΅ Logging into PocketBase..."); + const pb = new PocketBase(pbUrl); + // β οΈ CHANGE "users" if your auth collection is different + await pb.collection("users").authWithPassword(pbEmail, pbPassword); + log("β PocketBase token acquired"); + return pb; +} +// ----------------------------- +// CLEAN + COERCE +// ----------------------------- +function cleanString(value) { + if (value == null) return ""; + return String(value).replace(/\u00A0/g, " ").trim(); +} + +function coerceValue(fieldConfig, rawValue) { + switch (fieldConfig.type) { + case "string": return cleanString(rawValue); + case "bool": return ["true","1","yes","y"].includes(cleanString(rawValue).toLowerCase()); + case "date": + if (typeof rawValue === "number") { + const origin = new Date(Date.UTC(1899, 11, 30)); + return new Date(origin.getTime() + rawValue * 86400000).toISOString(); + } + const d = new Date(cleanString(rawValue)); + return isNaN(d.getTime()) ? null : d.toISOString(); + case "number": return Number(cleanString(rawValue)); + case "json": try { return JSON.parse(cleanString(rawValue)); } catch { return null; } + case "relation": return cleanString(rawValue); + default: return cleanString(rawValue); + } +} + +// ----------------------------- +// PAYLOAD BUILDER +// ----------------------------- +export function buildPayload(headers, row) { + const payload = {}; + headers.forEach((excelHeader, i) => { + const fieldConfig = Object.values(FIELD_CONFIG).find(f => f.excelHeader === excelHeader); + if (!fieldConfig) return; + payload[fieldConfig.pbField] = coerceValue(fieldConfig, row[i]); + }); + return payload; +} + +// ----------------------------- +// DIFFERENCE LOGGER + UPDATE CHECK +// ----------------------------- +function logDifferences(pbRecord, headers, row) { + const diffs = []; + headers.forEach((excelHeader, i) => { + const fieldConfig = Object.values(FIELD_CONFIG).find(f => f.excelHeader === excelHeader); + if (!fieldConfig) return; + const newValue = coerceValue(fieldConfig, row[i]); + const currentValue = coerceValue(fieldConfig, pbRecord[fieldConfig.pbField]); + if (newValue !== currentValue) { + diffs.push({ field: fieldConfig.pbField, excelHeader, current: currentValue, incoming: newValue }); + } + }); + if (diffs.length > 0) { + log(`Differences detected for record: ${pbRecord.id}`); + diffs.forEach(d => { + log(`Field: ${d.field} (Excel: ${d.excelHeader})\n Current: ${JSON.stringify(d.current)}\n Incoming: ${JSON.stringify(d.incoming)}\n`); + }); + } +} + +export function needsUpdate(pbRecord, headers, row) { + let hasDiff = false; + headers.forEach((excelHeader, i) => { + const fieldConfig = Object.values(FIELD_CONFIG).find(f => f.excelHeader === excelHeader); + if (!fieldConfig) return; + const newValue = coerceValue(fieldConfig, row[i]); + const currentValue = coerceValue(fieldConfig, pbRecord[fieldConfig.pbField]); + if (newValue !== currentValue) hasDiff = true; + }); + if (hasDiff) logDifferences(pbRecord, headers, row); + return hasDiff; +} +// ----------------------------- +// GRAPH HELPERS +// ----------------------------- +async function getUsedRangeInfo(graphToken, driveId, itemId, worksheetName) { + log(`π΅ Requesting usedRange for ${worksheetName}`); + const url = `https://graph.microsoft.com/v1.0/drives/${driveId}/items/${itemId}/workbook/worksheets('${worksheetName}')/usedRange`; + const res = await axios.get(url, { headers: { Authorization: `Bearer ${graphToken}` } }); + log(`β Used range address: ${res.data.address}`); + return res.data.address; +} + +function extractLastColumn(address) { + const match = address.match(/:([A-Z]+)\d+$/); + if (!match) { + log("β οΈ Falling back to column Z, check sheet width"); + return "Z"; + } + return match[1]; +} + +async function fetchExcelRange(graphToken, driveId, itemId, worksheetName, range) { + log(`π΅ Fetching range ${range} from worksheet ${worksheetName}`); + const url = `https://graph.microsoft.com/v1.0/drives/${driveId}/items/${itemId}/workbook/worksheets('${worksheetName}')/range(address='${range}')`; + const res = await axios.get(url, { headers: { Authorization: `Bearer ${graphToken}` } }); + log(`β Range ${range} returned ${res.data.values?.length || 0} rows`); + return res.data.values || []; +} + +async function fetchAllRows(graphToken, driveId, itemId, worksheetName, blockSize = 500) { + const address = await getUsedRangeInfo(graphToken, driveId, itemId, worksheetName); + const lastCol = extractLastColumn(address); + + let allRows = []; + let startRow = 1; + + while (true) { + const endRow = startRow + blockSize - 1; + const range = `A${startRow}:${lastCol}${endRow}`; + const rows = await fetchExcelRange(graphToken, driveId, itemId, worksheetName, range); + + if (!rows.length) break; + allRows = allRows.concat(rows); + + if (rows.length < blockSize) break; + startRow = endRow + 1; + } + + return allRows; +} + +// ----------------------------- +// MAIN +// ----------------------------- +(async () => { + try { + log("π΅ Checking token acquisition and excel readiness"); + + const graphToken = await getGraphToken(); + const pb = await getPocketBaseClient(); + + const driveId = "b!9YOqqr2xM0G2DFBwMEJYY4UzQgocFddEtpLYWuS9_AtkCKl2q8yjQJteQ7Ti4QSx"; + const itemId = "01SPNXLDSQTMG4F6BDANCLJU2FZKEDQLVU"; + const worksheetName = "Job Sheet"; + + const rows = await fetchAllRows(graphToken, driveId, itemId, worksheetName); + log(`β Total rows fetched: ${rows.length}`); + + if (!rows.length) { + log("β οΈ No rows returned from Graph. Check driveId, itemId, and worksheetName."); + return; + } + + const headers = rows[HEADER_ROW_INDEX]; + const dataRows = rows.slice(DATA_START_ROW_INDEX); + + if (!headers || !Array.isArray(headers)) { + log("β Header row not found at expected index. Verify HEADER_ROW_INDEX alignment."); + return; + } + + log(`β Headers detected: ${JSON.stringify(headers)}`); + log(`β Data rows to process: ${dataRows.length}`); + + for (const row of dataRows) { + if (!row || row.length === 0) continue; // skip empty rows + + const payload = buildPayload(headers, row); + + // Ensure primary key exists before querying PocketBase + const jobNumber = payload.Job_Number; + if (!jobNumber || String(jobNumber).trim() === "") { + log("β οΈ Skipping row with missing Job_Number."); + continue; + } + + let existing = null; + try { + existing = await pb.collection(pbCollection).getFirstListItem(`Job_Number="${jobNumber}"`); + } catch { + existing = null; + } + + if (!existing) { + log(`β Creating new record: ${jobNumber}`); + await pb.collection(pbCollection).create(payload); + } else { + if (needsUpdate(existing, headers, row)) { + log(`βοΈ Updating record: ${existing.id} (${jobNumber})`); + await pb.collection(pbCollection).update(existing.id, payload); + } else { + log(`β No changes for record: ${existing.id} (${jobNumber})`); + } + } + } + + log("β Sync complete, PocketBase updated!"); + } catch (err) { + log("β Error, not ready for updating. " + (err?.message || err)); + process.exit(1); + } +})(); diff --git a/backend/src/streamsyncbk.js b/backend/src/streamsyncbk.js new file mode 100644 index 0000000..7081e92 --- /dev/null +++ b/backend/src/streamsyncbk.js @@ -0,0 +1,136 @@ +// excelsync.js +import fs from "fs"; +import axios from "axios"; +import qs from "qs"; +// may need for reading excel +//import * as XLSX from "xlsx"; +import dotenv from "dotenv"; +// may need to serve html to port +//import { serve } from "bun"; +import PocketBase from "pocketbase" + +dotenv.config(); // connects to .env for sensitive info + +// ----------------------------- +// EXCEL PARSER +// ----------------------------- +const HEADER_ROW_INDEX = 2; // O index --> Row 3 Actual +const DATA_START_ROW_INDEX = 3; // 0 index --> Row 4 Actual +const RANGE_HEADER_ROW = 3 // Excel index headers +const RANGE_DATA_ROW = 4 // Excel index data + +// ----------------------------- +// CONFIG +// ----------------------------- +const CONFIG = JSON.parse(fs.readFileSync("config.json", "utf8")); +const tenantId = CONFIG.tenantId; +const clientId = CONFIG.clientId; +//const sharepointFileUrl = CONFIG.sharepointFileUrl; // production Excel file URL +const pbUrl = CONFIG.pbUrl; +const pbCollection = CONFIG.pbCollection || "Job_Info_TestEnv"; +const pbEmail = CONFIG.pbEmail; +const clientSecret = process.env.CLIENT_SECRET; +const pbPassword = process.env.PB_PASSWORD; + +// ----------------------------- +// LOGGING +// ----------------------------- +function log(message) { + console.log(message); + fs.appendFileSync("excelsync.log", message + "\n"); +} + +// ----------------------------- +// GRAPH TOKEN +// ----------------------------- +async function getGraphToken() { + const tokenUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`; + const body = qs.stringify({ + client_id: clientId, + client_secret: clientSecret, + scope: "https://graph.microsoft.com/.default", + grant_type: "client_credentials", + }); + const res = await axios.post(tokenUrl, body, { + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + }); + log("β Graph token acquired"); + return res.data.access_token; +} +// ----------------------------- +// POCKETBASE LOGIN AND TOKEN +// ----------------------------- +async function getPocketBaseClient() { + const pb = new PocketBase(pbUrl); + await pb.collection("users").authWithPassword(pbEmail, pbPassword); + log("β PocketBase token acquired"); + return pb; +} + +// ----------------------------- +// HEADER MAP +// ----------------------------- +const HEADER_MAP = { + "Job_Number": "Job_Number", + "Job_Full_Name": "Job_Full_Name", + "Job_Name": "Job_Name", + "Job Address": "Job_Address", + "Job Type": "Job_Type", + "Flag": "Flag", + "Tax_Exempt": "Tax_Exempt", + "Job Status": "Job_Status", + "Job Division": "Job_Division", + "Estimator": "Estimator", + "Office Rep": "Office_Rep", + "Manager": "Project_Manager", + "Company/Client": "Company_Client", + "Contact Person": "Contact_Person", + "Phone Number": "Phone_Number", + "Email": "Email", + "Due Date": "Due_Date", + "Due Date Source": "Due_Date_Source", + "Due Date Counter": "Due_Date_Counter", + "Due Time": "Due_Time", + "Tax_Exempt_Docs_Uploaded": "Tax_Exempt_Docs_Uploaded", + "Docs Uploaded": "Docs_Uploaded", + "QB Created": "QB_Created", + "Added to Calendar": "Added_To_Calendar", + "PSwift Uploaded": "PSwift_Uploaded", + "Voxer Created": "Voxer_Created", + "Estimate Reviewed": "Estimate_Reviewed", + "Estimate Draft": "Estimate_Draft", + "Estimate Approved": "Estimate_Approved", + "Start Date": "Start_Date", + "Schedule_Confidence": "Schedule_Confidence", + "Notes": "Notes", + "Job QB Link": "Job_QB_Link", + "Voxer Link": "Voxer_Link", + "Job_Codes": "Job_Codes", + "Submission Date": "Submission_Date", + "Active": "Active", + "Job Folder Link": "Job_Folder_Link", + "pb_id": "id" +}; + +// ----------------------------- +// MAIN +// ----------------------------- +(async () => { + try { + log("π΅ Checking token acquisition and excel readiness"); + + const graphToken = await getGraphToken(); + const pb = await getPocketBaseClient(); + // These can be update if the Excel File changes + //const driveId = "b!9YOqqr2xM0G2DFBwMEJYY4UzQgocFddEtpLYWuS9_AtkCKl2q8yjQJteQ7Ti4QSx"; + //const itemId = "01SPNXLDSQTMG4F6BDANCLJU2FZKEDQLVU" + + await exceltopb(graphToken, driveId, itemId, "Job Sheet", pb); + await pbtoupdate(pbCollection); + + log("β Ready for updating!"); + } catch (err) { + log("β Error, not ready for updating." + (err.message || err)); + process.exit(1); + } +})(); \ No newline at end of file diff --git a/backend/src/testsharepointaccess.js b/backend/src/testsharepointaccess.js new file mode 100644 index 0000000..5754c01 --- /dev/null +++ b/backend/src/testsharepointaccess.js @@ -0,0 +1,105 @@ +import axios from "axios"; +import qs from "qs"; +import fs from "fs"; +import dotenv from "dotenv"; + +// ----------------------------- +// CONFIG +// ----------------------------- +dotenv.config(); +// Load nonβsensitive values from config.json +const CONFIG = JSON.parse(fs.readFileSync("config.json", "utf8")); +const tenantId = CONFIG.tenantId; +const clientId = CONFIG.clientId; +const testSharePointFileUrl = CONFIG.testSharePointFileUrl; + +// Load secret only from .env +const clientSecret = process.env.CLIENT_SECRET; + +// ----------------------------- +// Validation +// ----------------------------- +const missing = []; +if (!tenantId) missing.push("tenantId"); +if (!clientId) missing.push("clientId"); +if (!clientSecret) missing.push("clientSecret"); +if (!testSharePointFileUrl) missing.push("testSharePointFileUrl"); + +if (missing.length > 0) { + console.error("β Missing values:", missing.join(", ")); + process.exit(1); +} else { + console.log("β All required values present"); +} + +// ----------------------------- +// 1. Get Graph Token (Client Credentials) +// ----------------------------- +async function getGraphToken() { + const tokenUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`; + + const body = qs.stringify({ + client_id: clientId, + client_secret: clientSecret, + scope: "https://graph.microsoft.com/.default", + grant_type: "client_credentials", + }); + + try { + const res = await axios.post(tokenUrl, body, { + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + }); + + console.log("β Token acquired"); + // can use to test to see the full token as needed, just remove this comment// console.log("π Access Token:", res.data.access_token); + return res.data.access_token; + + } catch (err) { + console.error("β Failed to acquire token"); + console.error(err.response?.data || err.message); + process.exit(1); + } +} + +// ----------------------------- +// 2. Convert SharePoint File URL β Drive Item ID +// ----------------------------- +function normalizeSharePointUrl(fileUrl) { + return fileUrl + .replace("guestaccess.aspx", "") + .replace(/\?.*$/, ""); // remove ?query +} + +// ----------------------------- +// 3. Test GET file metadata +// ----------------------------- +async function testFileAccess(token) { + const cleanUrl = normalizeSharePointUrl(testSharePointFileUrl); + + console.log("π Normalized URL:", cleanUrl); + + const graphUrl = + `https://graph.microsoft.com/v1.0/shares/u!${Buffer.from(cleanUrl).toString("base64")}/driveItem`; + + try { + const res = await axios.get(graphUrl, { + headers: { Authorization: `Bearer ${token}` }, + }); + + console.log("β SharePoint file access test succeeded!"); + console.log("File name:", res.data.name); + console.log("Drive Item ID:", res.data.id); + } catch (err) { + console.error("β SharePoint file access failed"); + console.error(err.response?.data || err.message); + } +} + +// ----------------------------- +// MAIN +// ----------------------------- +(async () => { + console.log("π΅ Testing SharePoint Accessβ¦"); + const token = await getGraphToken(); + await testFileAccess(token); +})(); diff --git a/backend/test.html b/backend/test.html new file mode 100644 index 0000000..53a181c --- /dev/null +++ b/backend/test.html @@ -0,0 +1,204 @@ +///Paste your SharePoint/OneDrive link below and click Encode.
+ + + + + +