From ba9354fedee7b5ea56d83ebdac8f908a686dd840 Mon Sep 17 00:00:00 2001 From: Dominic Ferrando Date: Mon, 13 Oct 2025 14:55:51 -0400 Subject: [PATCH] latest --- bun.lock | 86 +++++---- package.json | 24 +-- src/lib/api.ts | 25 +-- src/lib/services/commerce/printful.ts | 13 +- src/lib/services/commerce/sync.ts | 259 +++++++++++++++----------- src/lib/services/commerce/webflow.ts | 4 +- src/lib/test/test-calculator.ts | 76 +++++++- src/routes/commerce/+page.svelte | 39 +++- 8 files changed, 331 insertions(+), 195 deletions(-) diff --git a/bun.lock b/bun.lock index f01d963..8b4b517 100644 --- a/bun.lock +++ b/bun.lock @@ -5,20 +5,20 @@ "dependencies": { "@elysiajs/cors": "^1.4.0", "@elysiajs/eden": "^1.4.1", - "@tailwindcss/vite": "^4.1.13", - "daisyui": "^5.1.13", - "elysia": "^1.4.5", - "tailwindcss": "^4.1.13", + "@tailwindcss/vite": "^4.1.14", + "daisyui": "^5.2.2", + "elysia": "^1.4.10", + "tailwindcss": "^4.1.14", }, "devDependencies": { - "@sveltejs/kit": "^2.22.0", - "@sveltejs/vite-plugin-svelte": "^6.0.0", - "@types/bun": "^1.2.22", - "svelte": "^5.0.0", + "@sveltejs/kit": "^2.46.4", + "@sveltejs/vite-plugin-svelte": "^6.2.1", + "@types/bun": "^1.3.0", + "svelte": "^5.39.11", "svelte-adapter-bun": "^0.5.2", - "svelte-check": "^4.0.0", - "typescript": "^5.0.0", - "vite": "^7.0.4", + "svelte-check": "^4.3.3", + "typescript": "^5.9.3", + "vite": "^7.1.9", }, }, }, @@ -27,7 +27,7 @@ "@elysiajs/cors": ["@elysiajs/cors@1.4.0", "", { "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-pb0SCzBfFbFSYA/U40HHO7R+YrcXBJXOWgL20eSViK33ol1e20ru2/KUaZYo5IMUn63yaTJI/bQERuQ+77ND8g=="], - "@elysiajs/eden": ["@elysiajs/eden@1.4.1", "", { "peerDependencies": { "elysia": ">= 1.4.0-exp.0" } }, "sha512-9VXMau/cvafuBa1r19ucKi+l9eesCmeuvD6uYSeq5MFO/URc233JaxZmUlWQ8gztu+pp6L7auTZdkzOQz26O+A=="], + "@elysiajs/eden": ["@elysiajs/eden@1.4.3", "", { "peerDependencies": { "elysia": ">= 1.4.0-exp.0" } }, "sha512-mX0v5cTvTJiDsDWNEEyuoqudOvW5J+tXsvp/ZOJXJF3iCIEJI0Brvm78ymPrvwiOG4nUr3lS8BxUfbNf32DSXA=="], "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.10", "", { "os": "aix", "cpu": "ppc64" }, "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw=="], @@ -143,47 +143,47 @@ "@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.5", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ=="], - "@sveltejs/kit": ["@sveltejs/kit@2.42.2", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/cookie": "^0.6.0", "acorn": "^8.14.1", "cookie": "^0.6.0", "devalue": "^5.3.2", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", "set-cookie-parser": "^2.6.0", "sirv": "^3.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0", "svelte": "^4.0.0 || ^5.0.0-next.0", "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["@opentelemetry/api"], "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-FcNICFvlSYjPiAgk8BpqTEnXkaUj6I6wDwpQBxKMpsYhUc2Q5STgsVpXOG5LqwFpUAoLAXQ4wdWul7EcAG67JQ=="], + "@sveltejs/kit": ["@sveltejs/kit@2.46.5", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/cookie": "^0.6.0", "acorn": "^8.14.1", "cookie": "^0.6.0", "devalue": "^5.3.2", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", "set-cookie-parser": "^2.6.0", "sirv": "^3.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0", "svelte": "^4.0.0 || ^5.0.0-next.0", "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["@opentelemetry/api"], "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-7TSvMrCdmig5TMyYDW876C5FljhA0wlGixtvASCiqUqtLfmyEEpaysXjC7GhR5mWcGRrCGF+L2Bl1eEaW1wTCA=="], - "@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@6.2.0", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "debug": "^4.4.1", "deepmerge": "^4.3.1", "magic-string": "^0.30.17", "vitefu": "^1.1.1" }, "peerDependencies": { "svelte": "^5.0.0", "vite": "^6.3.0 || ^7.0.0" } }, "sha512-nJsV36+o7rZUDlrnSduMNl11+RoDE1cKqOI0yUEBCcqFoAZOk47TwD3dPKS2WmRutke9StXnzsPBslY7prDM9w=="], + "@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@6.2.1", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "debug": "^4.4.1", "deepmerge": "^4.3.1", "magic-string": "^0.30.17", "vitefu": "^1.1.1" }, "peerDependencies": { "svelte": "^5.0.0", "vite": "^6.3.0 || ^7.0.0" } }, "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ=="], "@sveltejs/vite-plugin-svelte-inspector": ["@sveltejs/vite-plugin-svelte-inspector@5.0.1", "", { "dependencies": { "debug": "^4.4.1" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", "svelte": "^5.0.0", "vite": "^6.3.0 || ^7.0.0" } }, "sha512-ubWshlMk4bc8mkwWbg6vNvCeT7lGQojE3ijDh3QTR6Zr/R+GXxsGbyH4PExEPpiFmqPhYiVSVmHBjUcVc1JIrA=="], - "@tailwindcss/node": ["@tailwindcss/node@4.1.13", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.5.1", "lightningcss": "1.30.1", "magic-string": "^0.30.18", "source-map-js": "^1.2.1", "tailwindcss": "4.1.13" } }, "sha512-eq3ouolC1oEFOAvOMOBAmfCIqZBJuvWvvYWh5h5iOYfe1HFC6+GZ6EIL0JdM3/niGRJmnrOc+8gl9/HGUaaptw=="], + "@tailwindcss/node": ["@tailwindcss/node@4.1.14", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.0", "lightningcss": "1.30.1", "magic-string": "^0.30.19", "source-map-js": "^1.2.1", "tailwindcss": "4.1.14" } }, "sha512-hpz+8vFk3Ic2xssIA3e01R6jkmsAhvkQdXlEbRTk6S10xDAtiQiM3FyvZVGsucefq764euO/b8WUW9ysLdThHw=="], - "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.13", "", { "dependencies": { "detect-libc": "^2.0.4", "tar": "^7.4.3" }, "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.13", "@tailwindcss/oxide-darwin-arm64": "4.1.13", "@tailwindcss/oxide-darwin-x64": "4.1.13", "@tailwindcss/oxide-freebsd-x64": "4.1.13", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.13", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.13", "@tailwindcss/oxide-linux-arm64-musl": "4.1.13", "@tailwindcss/oxide-linux-x64-gnu": "4.1.13", "@tailwindcss/oxide-linux-x64-musl": "4.1.13", "@tailwindcss/oxide-wasm32-wasi": "4.1.13", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.13", "@tailwindcss/oxide-win32-x64-msvc": "4.1.13" } }, "sha512-CPgsM1IpGRa880sMbYmG1s4xhAy3xEt1QULgTJGQmZUeNgXFR7s1YxYygmJyBGtou4SyEosGAGEeYqY7R53bIA=="], + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.14", "", { "dependencies": { "detect-libc": "^2.0.4", "tar": "^7.5.1" }, "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.14", "@tailwindcss/oxide-darwin-arm64": "4.1.14", "@tailwindcss/oxide-darwin-x64": "4.1.14", "@tailwindcss/oxide-freebsd-x64": "4.1.14", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.14", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.14", "@tailwindcss/oxide-linux-arm64-musl": "4.1.14", "@tailwindcss/oxide-linux-x64-gnu": "4.1.14", "@tailwindcss/oxide-linux-x64-musl": "4.1.14", "@tailwindcss/oxide-wasm32-wasi": "4.1.14", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.14", "@tailwindcss/oxide-win32-x64-msvc": "4.1.14" } }, "sha512-23yx+VUbBwCg2x5XWdB8+1lkPajzLmALEfMb51zZUBYaYVPDQvBSD/WYDqiVyBIo2BZFa3yw1Rpy3G2Jp+K0dw=="], - "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.13", "", { "os": "android", "cpu": "arm64" }, "sha512-BrpTrVYyejbgGo57yc8ieE+D6VT9GOgnNdmh5Sac6+t0m+v+sKQevpFVpwX3pBrM2qKrQwJ0c5eDbtjouY/+ew=="], + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.14", "", { "os": "android", "cpu": "arm64" }, "sha512-a94ifZrGwMvbdeAxWoSuGcIl6/DOP5cdxagid7xJv6bwFp3oebp7y2ImYsnZBMTwjn5Ev5xESvS3FFYUGgPODQ=="], - "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.13", "", { "os": "darwin", "cpu": "arm64" }, "sha512-YP+Jksc4U0KHcu76UhRDHq9bx4qtBftp9ShK/7UGfq0wpaP96YVnnjFnj3ZFrUAjc5iECzODl/Ts0AN7ZPOANQ=="], + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.14", "", { "os": "darwin", "cpu": "arm64" }, "sha512-HkFP/CqfSh09xCnrPJA7jud7hij5ahKyWomrC3oiO2U9i0UjP17o9pJbxUN0IJ471GTQQmzwhp0DEcpbp4MZTA=="], - "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.13", "", { "os": "darwin", "cpu": "x64" }, "sha512-aAJ3bbwrn/PQHDxCto9sxwQfT30PzyYJFG0u/BWZGeVXi5Hx6uuUOQEI2Fa43qvmUjTRQNZnGqe9t0Zntexeuw=="], + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.14", "", { "os": "darwin", "cpu": "x64" }, "sha512-eVNaWmCgdLf5iv6Qd3s7JI5SEFBFRtfm6W0mphJYXgvnDEAZ5sZzqmI06bK6xo0IErDHdTA5/t7d4eTfWbWOFw=="], - "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.13", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Wt8KvASHwSXhKE/dJLCCWcTSVmBj3xhVhp/aF3RpAhGeZ3sVo7+NTfgiN8Vey/Fi8prRClDs6/f0KXPDTZE6nQ=="], + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.14", "", { "os": "freebsd", "cpu": "x64" }, "sha512-QWLoRXNikEuqtNb0dhQN6wsSVVjX6dmUFzuuiL09ZeXju25dsei2uIPl71y2Ic6QbNBsB4scwBoFnlBfabHkEw=="], - "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.13", "", { "os": "linux", "cpu": "arm" }, "sha512-mbVbcAsW3Gkm2MGwA93eLtWrwajz91aXZCNSkGTx/R5eb6KpKD5q8Ueckkh9YNboU8RH7jiv+ol/I7ZyQ9H7Bw=="], + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.14", "", { "os": "linux", "cpu": "arm" }, "sha512-VB4gjQni9+F0VCASU+L8zSIyjrLLsy03sjcR3bM0V2g4SNamo0FakZFKyUQ96ZVwGK4CaJsc9zd/obQy74o0Fw=="], - "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.13", "", { "os": "linux", "cpu": "arm64" }, "sha512-wdtfkmpXiwej/yoAkrCP2DNzRXCALq9NVLgLELgLim1QpSfhQM5+ZxQQF8fkOiEpuNoKLp4nKZ6RC4kmeFH0HQ=="], + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-qaEy0dIZ6d9vyLnmeg24yzA8XuEAD9WjpM5nIM1sUgQ/Zv7cVkharPDQcmm/t/TvXoKo/0knI3me3AGfdx6w1w=="], - "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.13", "", { "os": "linux", "cpu": "arm64" }, "sha512-hZQrmtLdhyqzXHB7mkXfq0IYbxegaqTmfa1p9MBj72WPoDD3oNOh1Lnxf6xZLY9C3OV6qiCYkO1i/LrzEdW2mg=="], + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-ISZjT44s59O8xKsPEIesiIydMG/sCXoMBCqsphDm/WcbnuWLxxb+GcvSIIA5NjUw6F8Tex7s5/LM2yDy8RqYBQ=="], - "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.13", "", { "os": "linux", "cpu": "x64" }, "sha512-uaZTYWxSXyMWDJZNY1Ul7XkJTCBRFZ5Fo6wtjrgBKzZLoJNrG+WderJwAjPzuNZOnmdrVg260DKwXCFtJ/hWRQ=="], + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.14", "", { "os": "linux", "cpu": "x64" }, "sha512-02c6JhLPJj10L2caH4U0zF8Hji4dOeahmuMl23stk0MU1wfd1OraE7rOloidSF8W5JTHkFdVo/O7uRUJJnUAJg=="], - "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.13", "", { "os": "linux", "cpu": "x64" }, "sha512-oXiPj5mi4Hdn50v5RdnuuIms0PVPI/EG4fxAfFiIKQh5TgQgX7oSuDWntHW7WNIi/yVLAiS+CRGW4RkoGSSgVQ=="], + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.14", "", { "os": "linux", "cpu": "x64" }, "sha512-TNGeLiN1XS66kQhxHG/7wMeQDOoL0S33x9BgmydbrWAb9Qw0KYdd8o1ifx4HOGDWhVmJ+Ul+JQ7lyknQFilO3Q=="], - "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.13", "", { "dependencies": { "@emnapi/core": "^1.4.5", "@emnapi/runtime": "^1.4.5", "@emnapi/wasi-threads": "^1.0.4", "@napi-rs/wasm-runtime": "^0.2.12", "@tybys/wasm-util": "^0.10.0", "tslib": "^2.8.0" }, "cpu": "none" }, "sha512-+LC2nNtPovtrDwBc/nqnIKYh/W2+R69FA0hgoeOn64BdCX522u19ryLh3Vf3F8W49XBcMIxSe665kwy21FkhvA=="], + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.14", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.0.5", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-uZYAsaW/jS/IYkd6EWPJKW/NlPNSkWkBlaeVBi/WsFQNP05/bzkebUL8FH1pdsqx4f2fH/bWFcUABOM9nfiJkQ=="], - "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.13", "", { "os": "win32", "cpu": "arm64" }, "sha512-dziTNeQXtoQ2KBXmrjCxsuPk3F3CQ/yb7ZNZNA+UkNTeiTGgfeh+gH5Pi7mRncVgcPD2xgHvkFCh/MhZWSgyQg=="], + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.14", "", { "os": "win32", "cpu": "arm64" }, "sha512-Az0RnnkcvRqsuoLH2Z4n3JfAef0wElgzHD5Aky/e+0tBUxUhIeIqFBTMNQvmMRSP15fWwmvjBxZ3Q8RhsDnxAA=="], - "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.13", "", { "os": "win32", "cpu": "x64" }, "sha512-3+LKesjXydTkHk5zXX01b5KMzLV1xl2mcktBJkje7rhFUpUlYJy7IMOLqjIRQncLTa1WZZiFY/foAeB5nmaiTw=="], + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.14", "", { "os": "win32", "cpu": "x64" }, "sha512-ttblVGHgf68kEE4om1n/n44I0yGPkCPbLsqzjvybhpwa6mKKtgFfAzy6btc3HRmuW7nHe0OOrSeNP9sQmmH9XA=="], - "@tailwindcss/vite": ["@tailwindcss/vite@4.1.13", "", { "dependencies": { "@tailwindcss/node": "4.1.13", "@tailwindcss/oxide": "4.1.13", "tailwindcss": "4.1.13" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-0PmqLQ010N58SbMTJ7BVJ4I2xopiQn/5i6nlb4JmxzQf8zcS5+m2Cv6tqh+sfDwtIdjoEnOvwsGQ1hkUi8QEHQ=="], + "@tailwindcss/vite": ["@tailwindcss/vite@4.1.14", "", { "dependencies": { "@tailwindcss/node": "4.1.14", "@tailwindcss/oxide": "4.1.14", "tailwindcss": "4.1.14" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-BoFUoU0XqgCUS1UXWhmDJroKKhNXeDzD7/XwabjkDIAbMnc4ULn5e2FuEuBbhZ6ENZoSYzKlzvZ44Yr6EUDUSA=="], "@tokenizer/inflate": ["@tokenizer/inflate@0.2.7", "", { "dependencies": { "debug": "^4.4.0", "fflate": "^0.8.2", "token-types": "^6.0.0" } }, "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg=="], "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], - "@types/bun": ["@types/bun@1.2.22", "", { "dependencies": { "bun-types": "1.2.22" } }, "sha512-5A/KrKos2ZcN0c6ljRSOa1fYIyCKhZfIVYeuyb4snnvomnpFqC0tTsEkdqNxbAgExV384OETQ//WAjl3XbYqQA=="], + "@types/bun": ["@types/bun@1.3.0", "", { "dependencies": { "bun-types": "1.3.0" } }, "sha512-+lAGCYjXjip2qY375xX/scJeVRmZ5cY0wyHYyCYxNcdEXrQ4AOe3gACgd4iQ8ksOslJtW4VNxBJ8llUwc3a6AA=="], "@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="], @@ -199,7 +199,7 @@ "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], - "bun-types": ["bun-types@1.2.22", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-hwaAu8tct/Zn6Zft4U9BsZcXkYomzpHJX28ofvx7k0Zz2HNz54n1n+tDgxoWFGB4PcFvJXJQloPhaV2eP3Q6EA=="], + "bun-types": ["bun-types@1.3.0", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-u8X0thhx+yJ0KmkxuEo9HAtdfgCBaM/aI9K90VQcQioAmkVp3SG3FkwWGibUFz3WdXAdcsqOcbU40lK7tbHdkQ=="], "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], @@ -211,7 +211,7 @@ "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], - "daisyui": ["daisyui@5.1.13", "", {}, "sha512-KWPF/4R+EHTJRqKZFNmSDPfAZ5xeS6YWB/2kS7Y6wGKg+atscUi2DOp6HoDD/OgGML0PJTtTpgwpTfeHVfjk7w=="], + "daisyui": ["daisyui@5.2.3", "", {}, "sha512-sldBQUIFCsSPoF4LvoHhIi9GnvBX/3aZD9NoTOvpTSX8sDjO484wQx7yEvRyREMpn4rZMvQSKKskHAHdM8+B4Q=="], "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], @@ -221,7 +221,7 @@ "devalue": ["devalue@5.3.2", "", {}, "sha512-UDsjUbpQn9kvm68slnrs+mfxwFkIflOhkanmyabZ8zOYk8SMEIbJ3TK+88g70hSIeytu4y18f0z/hYHMTrXIWw=="], - "elysia": ["elysia@1.4.6", "", { "dependencies": { "cookie": "^1.0.2", "exact-mirror": "0.2.2", "fast-decode-uri-component": "^1.0.1" }, "optionalDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "openapi-types": ">= 12.0.0" }, "peerDependencies": { "file-type": ">= 20.0.0", "typescript": ">= 5.0.0" } }, "sha512-u2CorXLPs5ZXyWP+tQR+bgka/lJA4vNpB8lDE2w/sTmdaIwoPQmHEL4J3ai6OAlluWR1kfG7T9gO3EYT9D8viQ=="], + "elysia": ["elysia@1.4.11", "", { "dependencies": { "cookie": "^1.0.2", "exact-mirror": "0.2.2", "fast-decode-uri-component": "^1.0.1" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-cphuzQj0fRw1ICRvwHy2H3xQio9bycaZUVHnDHJQnKqBfMNlZ+Hzj6TMmt9lc0Az0mvbCnPXWVF7y1MCRhUuOA=="], "enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="], @@ -253,7 +253,7 @@ "is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="], - "jiti": ["jiti@2.5.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w=="], + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], @@ -285,9 +285,7 @@ "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], - "minizlib": ["minizlib@3.0.2", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA=="], - - "mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="], + "minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="], "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="], @@ -319,17 +317,17 @@ "strtok3": ["strtok3@10.3.4", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg=="], - "svelte": ["svelte@5.39.3", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", "esrap": "^2.1.0", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-7Jwus6iXviGZAvhqbeYu3NNHA6LGyQ8EbmjdAhJUDade5rrW6g9VnBbRhUuYX4pMZLHozijsFolt88zvKPfsbQ=="], + "svelte": ["svelte@5.39.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", "esrap": "^2.1.0", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-8MxWVm2+3YwrFbPaxOlT1bbMi6OTenrAgks6soZfiaS8Fptk4EVyRIFhJc3RpO264EeSNwgjWAdki0ufg4zkGw=="], "svelte-adapter-bun": ["svelte-adapter-bun@0.5.2", "", { "dependencies": { "tiny-glob": "^0.2.9" } }, "sha512-xEtFgaal6UgrCwwkSIcapO9kopoFNUYCYqyKCikdqxX9bz2TDYnrWQZ7qBnkunMxi1HOIERUCvTcebYGiarZLA=="], - "svelte-check": ["svelte-check@4.3.1", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-lkh8gff5gpHLjxIV+IaApMxQhTGnir2pNUAqcNgeKkvK5bT/30Ey/nzBxNLDlkztCH4dP7PixkMt9SWEKFPBWg=="], + "svelte-check": ["svelte-check@4.3.3", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-RYP0bEwenDXzfv0P1sKAwjZSlaRyqBn0Fz1TVni58lqyEiqgwztTpmodJrGzP6ZT2aHl4MbTvWP6gbmQ3FOnBg=="], - "tailwindcss": ["tailwindcss@4.1.13", "", {}, "sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w=="], + "tailwindcss": ["tailwindcss@4.1.14", "", {}, "sha512-b7pCxjGO98LnxVkKjaZSDeNuljC4ueKUddjENJOADtubtdo8llTaJy7HwBMeLNSSo2N5QIAgklslK1+Ir8r6CA=="], "tapable": ["tapable@2.2.3", "", {}, "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg=="], - "tar": ["tar@7.4.3", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.0.1", "mkdirp": "^3.0.1", "yallist": "^5.0.0" } }, "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw=="], + "tar": ["tar@7.5.1", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g=="], "tiny-glob": ["tiny-glob@0.2.9", "", { "dependencies": { "globalyzer": "0.1.0", "globrex": "^0.1.2" } }, "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg=="], @@ -339,13 +337,13 @@ "totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="], - "typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="], + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="], "undici-types": ["undici-types@7.12.0", "", {}, "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ=="], - "vite": ["vite@7.1.6", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-SRYIB8t/isTwNn8vMB3MR6E+EQZM/WG1aKmmIUCfDXfVvKfc20ZpamngWHKzAmmu9ppsgxsg4b2I7c90JZudIQ=="], + "vite": ["vite@7.1.9", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg=="], "vitefu": ["vitefu@1.1.1", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ=="], @@ -359,7 +357,7 @@ "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], - "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" }, "bundled": true }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="], + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.0.7", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw=="], "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], diff --git a/package.json b/package.json index 55e1cab..6f550cf 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,14 @@ { "type": "module", "devDependencies": { - "@sveltejs/kit": "^2.42.2", - "@sveltejs/vite-plugin-svelte": "^6.2.0", - "@types/bun": "^1.2.22", - "svelte": "^5.39.3", + "@sveltejs/kit": "^2.46.5", + "@sveltejs/vite-plugin-svelte": "^6.2.1", + "@types/bun": "^1.3.0", + "svelte": "^5.39.11", "svelte-adapter-bun": "^0.5.2", - "svelte-check": "^4.3.1", - "typescript": "^5.9.2", - "vite": "^7.1.6" + "svelte-check": "^4.3.3", + "typescript": "^5.9.3", + "vite": "^7.1.9" }, "scripts": { "dev": "vite dev", @@ -20,10 +20,10 @@ }, "dependencies": { "@elysiajs/cors": "^1.4.0", - "@elysiajs/eden": "^1.4.1", - "@tailwindcss/vite": "^4.1.13", - "daisyui": "^5.1.13", - "elysia": "^1.4.6", - "tailwindcss": "^4.1.13" + "@elysiajs/eden": "^1.4.3", + "@tailwindcss/vite": "^4.1.14", + "daisyui": "^5.2.3", + "elysia": "^1.4.11", + "tailwindcss": "^4.1.14" } } diff --git a/src/lib/api.ts b/src/lib/api.ts index 0e6e655..83e781d 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -6,8 +6,8 @@ import type { ActivityPerformance, Player } from "$lib/services/calculator/util" import rawStandards from "$lib/data/standards.json" assert { type: "json" } import { Printful } from "./services/commerce/util/types"; import WebflowService from "./services/commerce/webflow"; -import PrintfulService from "./services/commerce/printful"; import SyncService from "./services/commerce/sync"; +import PrintfulService from "./services/commerce/printful"; export const app = new Elysia({ prefix: "/api" }) .use(cors({ @@ -42,17 +42,10 @@ export const app = new Elysia({ prefix: "/api" }) .group("/sync", app => app .get("/", async ({ }) => SyncService.state) .post("/:printfulProductId?", async ({ params }) => { - SyncService.state.isSyncing = true; - SyncService.state.printfulProductId = params.printfulProductId ? + const printfulProductId = params.printfulProductId ? +params.printfulProductId : undefined; - - const printfulProducts = params.printfulProductId ? - [(await PrintfulService.Products.get(+params.printfulProductId)).sync_product] : - await PrintfulService.Products.getAll(); - - await SyncService.sync(printfulProducts); - return ""; + await SyncService.sync(printfulProductId); }, { error({ error }) { console.error(error); @@ -63,6 +56,15 @@ export const app = new Elysia({ prefix: "/api" }) } }) ) + .get("/:printfulProductId", async ({ params }) => { + const printfulProductId = +params.printfulProductId; + const printfulProduct = await PrintfulService.Products.get(printfulProductId); + + const webflowProductId = printfulProduct.sync_product.external_id.split("-")[0]; + const webflowProduct = await WebflowService.Products.get(webflowProductId); + + return { printfulProduct, webflowProduct }; + }) ) // WEBHOOKS @@ -71,7 +73,7 @@ export const app = new Elysia({ prefix: "/api" }) switch (payload.type) { case Printful.Webhook.Event.ProductUpdated: { const printfulProduct = payload.data.sync_product; - await SyncService.sync([printfulProduct]); + await SyncService.sync(printfulProduct.id); break; } case Printful.Webhook.Event.ProductDeleted: { @@ -79,7 +81,6 @@ export const app = new Elysia({ prefix: "/api" }) break; } } - return ""; }, { error({ error }) { console.error(error); diff --git a/src/lib/services/commerce/printful.ts b/src/lib/services/commerce/printful.ts index 8accea6..cdbb98e 100644 --- a/src/lib/services/commerce/printful.ts +++ b/src/lib/services/commerce/printful.ts @@ -41,8 +41,8 @@ export default class PrintfulService { }); if (!res.ok) { - console.error("Printful product update failed:", res.statusText); - throw new Error("Failed to update Printful product"); + console.error("Printful product get failed:", await res.json()); + throw new Error("Failed to get Printful product"); } const payload: Printful.Products.MetaDataSingle = await res.json(); @@ -60,11 +60,18 @@ export default class PrintfulService { }); if (!res.ok) { - console.error("Printful product update failed:", res.statusText); + console.error("Printful product update failed:", await res.json()); throw new Error("Failed to update Printful product"); } } } + + static Util = class { + static getVariantMainImage(syncVariant: Printful.Products.SyncVariant): string { + const previewFile = syncVariant.files.find(f => f.type === "preview"); + return previewFile?.preview_url ?? syncVariant.product.image; + } + } } export const env = () => { diff --git a/src/lib/services/commerce/sync.ts b/src/lib/services/commerce/sync.ts index 4e0ed1c..0ec287f 100644 --- a/src/lib/services/commerce/sync.ts +++ b/src/lib/services/commerce/sync.ts @@ -8,62 +8,126 @@ const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); export const R2_WORKER_URL = "https://r2-worker.xominus.workers.dev"; export const WEBSITE_MEDIA_URL = "https://website-media.bladeandbrawn.com"; +type MainProduct = { + name: string, + externalId: string, + colors: Set, + variants: { + color: string, + product: Printful.Products.Product + }[] +} + export default class SyncService { static state = { isSyncing: false, - printfulProductId: undefined as undefined | number + syncingIds: [] as number[] } - static async sync(printfulProducts: Printful.Products.SyncProduct[]) { + static async sync(printfulProductId: number | undefined) { + console.log("Syncing..."); this.state.isSyncing = true; try { - const webflowProducts = await WebflowService.Products.getAll(); - - const mainPrintfulProducts: Printful.Products.SyncProduct[] = []; - for (const printfulProduct of printfulProducts) { - if (this.isSpecialPrintfulVariant(printfulProduct)) { + let printfulProducts = await PrintfulService.Products.getAll(); + if (printfulProductId) { + const printfulProduct = printfulProducts.find(p => p.id === printfulProductId); + if (printfulProduct) { const mainProductName = this.getMainProductName(printfulProduct.name); - const mainProductAlreadyInserted = mainPrintfulProducts - .some(p => p.name.includes(mainProductName)); - if (!mainProductAlreadyInserted) { - mainPrintfulProducts.push(printfulProduct); - } - } - else { - mainPrintfulProducts.push(printfulProduct); + printfulProducts = printfulProducts.filter(p => p.name.includes(mainProductName)); } } + const webflowProducts = await WebflowService.Products.getAll(); - for (const printfulProduct of mainPrintfulProducts) { + // Generate main printful products + console.log("Generating main products..."); + const mainPrintfulProducts: MainProduct[] = []; + for (const printfulProduct of printfulProducts) { + const mainProductName = this.getMainProductName(printfulProduct.name); + + let mainPrintfulProduct = mainPrintfulProducts + .find(mp => mp.name.includes(mainProductName)); + if (!mainPrintfulProduct) { + mainPrintfulProduct = { + name: mainProductName, + variants: [], + externalId: printfulProduct.external_id, + colors: new Set() + } + mainPrintfulProducts.push(mainPrintfulProduct); + } + + const productColor = this.findColorInProductName(printfulProduct.name); + mainPrintfulProduct.variants.push({ + color: productColor, + product: await PrintfulService.Products.get(printfulProduct.id) + }); + mainPrintfulProduct.colors.add(productColor); + } + + // TODO: Validate main printful products + + // Sync the main printful products + for (const mainPrintfulProduct of mainPrintfulProducts) { this.state.isSyncing = true; - this.state.printfulProductId = printfulProduct.id; - await sleep(2000); + this.state.syncingIds = mainPrintfulProduct.variants.map(v => v.product.sync_product.id); - const fullPrintfulProduct = await PrintfulService.Products.get(printfulProduct.id); - const printfulVariants = fullPrintfulProduct.sync_variants; - const webflowVariants = this.convertPrintfulVariantsToWebflowSkus(printfulVariants); + const foundColors = mainPrintfulProduct.colors; + const foundSizes: Set = new Set(); - const foundColors = Array.from(new Set(printfulVariants.map(v => v.color))); - const foundSizes = Array.from(new Set(printfulVariants.map(v => v.size))); + const webflowSkus: DeepPartial[] = []; + + for (const specialVariant of mainPrintfulProduct.variants) { + for (const printfulVariant of specialVariant.product.sync_variants) { + // check if this variant size is in all other special variants + let sizeIsInAllVariants = true; + for (const specialVariant of mainPrintfulProduct.variants) { + const sizes = specialVariant.product.sync_variants.map(v => v.size); + if (!sizes.includes(printfulVariant.size)) + sizeIsInAllVariants = false; + } + + if (sizeIsInAllVariants) { + foundSizes.add(printfulVariant.size); + + webflowSkus.push({ + "id": printfulVariant.external_id, + "fieldData": { + "name": printfulVariant.name, + "slug": formatSlug(printfulVariant.name), + "sku-values": { + "color": specialVariant.color, + "size": printfulVariant.size, + }, + "price": { + "value": Math.floor(+printfulVariant.retail_price * 100), + "unit": printfulVariant.currency, + "currency": printfulVariant.currency + }, + "main-image": PrintfulService.Util.getVariantMainImage(printfulVariant) + } + }) + } + } + } // SYNC PRODUCT DATA - let webflowProduct = webflowProducts - .find(webflowProduct => webflowProduct.product.id === printfulProduct.external_id); - if (webflowProduct) { + let existingWebflowProduct = webflowProducts + .find(webflowProduct => webflowProduct.product.id === mainPrintfulProduct.externalId.split("-")[0]); + if (existingWebflowProduct) { // SYNC PRODUCT UPDATE - const webflowProductId = fullPrintfulProduct.sync_product.external_id; + const webflowProductId = mainPrintfulProduct.externalId.split("-")[0]; WebflowService.Products.update(webflowProductId, { "product": { "fieldData": { - "name": fullPrintfulProduct.sync_product.name, - "slug": formatSlug(fullPrintfulProduct.sync_product.name), + "name": mainPrintfulProduct.name, + "slug": formatSlug(mainPrintfulProduct.name), "shippable": true, "tax-category": "standard-taxable", "sku-properties": [ { "id": "color", "name": "Color", - "enum": foundColors.map(color => ({ + "enum": Array.from(foundColors).map(color => ({ "id": color, "slug": formatSlug(color), "name": color @@ -72,7 +136,7 @@ export default class SyncService { { "id": "size", "name": "Size", - "enum": foundSizes.map(size => ({ + "enum": Array.from(foundSizes).map(size => ({ "id": size, "slug": formatSlug(size), "name": size @@ -80,19 +144,18 @@ export default class SyncService { }, ] } - } + }, + "sku": webflowSkus[0] }); - // Update webflow product SKUs - const webflowProduct = await WebflowService.Products.get(webflowProductId); - if (!webflowProduct) { - console.error("Missing webflow product"); - throw new Error("Failed to create Webflow product"); - } - - for (let i = 0; i < webflowProduct.skus.length; ++i) { - const webflowSkuId = printfulVariants[i].external_id; - await WebflowService.Products.Skus.update(webflowProductId, webflowSkuId, webflowProduct.skus[i]); + for (const webflowSku of webflowSkus.slice(1)) { + const existingWebflowSku = existingWebflowProduct.skus.find(sku => sku.id === webflowSku.id); + if (webflowSku.id && existingWebflowSku) { + await WebflowService.Products.Skus.update(webflowProductId, webflowSku.id, webflowSku); + } + else { + await WebflowService.Products.Skus.create(webflowProductId, [webflowSku]); + } } } else { @@ -100,15 +163,15 @@ export default class SyncService { const webflowProductId = await WebflowService.Products.create({ "product": { "fieldData": { - "name": fullPrintfulProduct.sync_product.name, - "slug": formatSlug(fullPrintfulProduct.sync_product.name), + "name": mainPrintfulProduct.name, + "slug": formatSlug(mainPrintfulProduct.name), "shippable": true, "tax-category": "standard-taxable", "sku-properties": [ { "id": "color", "name": "Color", - "enum": foundColors.map(color => ({ + "enum": Array.from(foundColors).map(color => ({ "id": color, "slug": formatSlug(color), "name": color @@ -117,7 +180,7 @@ export default class SyncService { { "id": "size", "name": "Size", - "enum": foundSizes.map(size => ({ + "enum": Array.from(foundSizes).map(size => ({ "id": size, "slug": formatSlug(size), "name": size @@ -126,101 +189,85 @@ export default class SyncService { ] } }, - "sku": webflowVariants[0], + "sku": webflowSkus[0], }); // create webflow product SKUs - await WebflowService.Products.Skus.create(webflowProductId, webflowVariants.slice(1)); + await WebflowService.Products.Skus.create(webflowProductId, webflowSkus.slice(1)); - webflowProduct = await WebflowService.Products.get(webflowProductId); - if (!webflowProduct) { + existingWebflowProduct = await WebflowService.Products.get(webflowProductId); + if (!existingWebflowProduct) { console.error("Missing webflow product"); throw new Error("Failed to create Webflow product"); } - await PrintfulService.Products.update(printfulProduct.id, { - "sync_product": { - "external_id": String(webflowProductId) - }, - "sync_variants": webflowProduct.skus.map((sku, i) => ({ - "id": Number(printfulVariants[i].id), // printful variant id - "external_id": String(sku.id), // webflow variant id - })) - }); + for (const specialVariant of mainPrintfulProduct.variants) { + const newPrintfulVariants: DeepPartial[] = []; + for (const printfulVariant of specialVariant.product.sync_variants) { + const associatedWebflowSku = existingWebflowProduct.skus + .find(sku => sku.fieldData["sku-values"]?.["color"] === printfulVariant.color && + sku.fieldData["sku-values"]?.["size"] === printfulVariant.size); + if (associatedWebflowSku) { + newPrintfulVariants.push({ + "id": printfulVariant.id, // printful variant id + "external_id": String(associatedWebflowSku.id), // webflow variant id + }); + } + } + + await sleep(10000); + await PrintfulService.Products.update(specialVariant.product.sync_product.id, { + "sync_product": { + "id": specialVariant.product.sync_product.id, + "external_id": `${webflowProductId}-${specialVariant.color}` + }, + "sync_variants": newPrintfulVariants + }); + } } // SYNC IMAGES - const productSlug = webflowProduct.product.fieldData.slug; - const productImageUrls = await this.getProductImageUrls(productSlug); - for (const sku of webflowProduct.skus) { - sku.fieldData["main-image"] = productImageUrls[0] ?? sku.fieldData["main-image"]; - sku.fieldData["more-images"] = productImageUrls.slice(1).map(url => ({ url })); - await WebflowService.Products.Skus.update(webflowProduct.product.id, sku.id, sku); + for (const sku of existingWebflowProduct.skus) { + const skuColor = sku.fieldData["sku-values"]?.["color"]?.toLowerCase() ?? "None"; + const skuSlug = `${existingWebflowProduct.product.fieldData.slug}-${skuColor}`; + const skuImageUrls = await this.getProductImageUrls(skuSlug); + + sku.fieldData["main-image"] = skuImageUrls[0] ?? sku.fieldData["main-image"]; + sku.fieldData["more-images"] = skuImageUrls.slice(1).map(url => ({ url })); + + await WebflowService.Products.Skus.update(existingWebflowProduct.product.id, sku.id, sku); } } } catch (error) { + console.error(error); throw error; } finally { this.state.isSyncing = false; - this.state.printfulProductId = undefined; + this.state.syncingIds = []; } - - } - - private static convertPrintfulVariantsToWebflowSkus(printfulVariants: Printful.Products.SyncVariant[]) { - const getVariantMainImage = (syncVariant: Printful.Products.SyncVariant): string => { - const previewFile = syncVariant.files.find(f => f.type === "preview"); - return previewFile?.preview_url ?? syncVariant.product.image; - } - - const webflowVariants: DeepPartial[] = []; - for (const printfulVariant of printfulVariants) { - webflowVariants.push({ - "fieldData": { - "name": printfulVariant.name, - "slug": formatSlug(printfulVariant.name), - "sku-values": { - "color": printfulVariant.color, - "size": printfulVariant.size, - }, - "price": { - "value": +printfulVariant.retail_price * 100, - "unit": printfulVariant.currency, - "currency": printfulVariant.currency - }, - "main-image": getVariantMainImage(printfulVariant) - } - }); - } - return webflowVariants; } private static getProductImageUrls = async (slug: string) => { const res = await (fetch(`${R2_WORKER_URL}/product-images/${slug}`)); if (!res.ok) { - console.error("Failed to get product image keys:", res.statusText); + console.error("Failed to get product image keys:", await res.json()); throw new Error("Failed to get product image keys"); } const productImageKeys: string[] = await res.json(); return productImageKeys.map(key => `${WEBSITE_MEDIA_URL}/${key}`); } - private static findColorInProductName = (productName: string): string | undefined => { - const m = productName.match(/\[([^\]]+)\]$/); - return m ? m[1] : undefined; + public static findColorInProductName = (productName: string): string => { + const m = productName.match(/\[([^\]]+)\]/); + return m ? m[1] : "N/A"; }; - private static isSpecialPrintfulVariant = (printfulProduct: Printful.Products.SyncProduct) => { - const colorInName = this.findColorInProductName(printfulProduct.name); - return !!colorInName; - } - - private static getMainProductName = (productName: string) => { + public static getMainProductName = (productName: string) => { const colorInName = this.findColorInProductName(productName); if (colorInName) { - return productName.replace(colorInName, "").trimEnd(); + return productName.replace(`[${colorInName}]`, "").trimEnd(); } else { return productName; diff --git a/src/lib/services/commerce/webflow.ts b/src/lib/services/commerce/webflow.ts index da96eb6..f900e7b 100644 --- a/src/lib/services/commerce/webflow.ts +++ b/src/lib/services/commerce/webflow.ts @@ -17,7 +17,7 @@ export default class WebflowService { }); if (!res.ok) { - console.error("Webflow product SKU create failed:", res.statusText); + console.error("Webflow product SKU create failed:", await res.json()); throw new Error("Failed to create Webflow product SKU"); } } @@ -109,7 +109,7 @@ export default class WebflowService { }); if (!res.ok) { - console.error("Webflow product update failed:", res.statusText); + console.error("Webflow product update failed:", await res.json()); throw new Error("Failed to update Webflow product"); } } diff --git a/src/lib/test/test-calculator.ts b/src/lib/test/test-calculator.ts index 33a7583..1cce6cd 100644 --- a/src/lib/test/test-calculator.ts +++ b/src/lib/test/test-calculator.ts @@ -1,6 +1,11 @@ import { Activity, ftToCm, Gender, getAvgWeight, inToCm, kgToLb, lbToKg, minToMs, secToMs, type ActivityPerformance, type Player } from "$lib/services/calculator/util"; -import { Printful } from "$lib/services/commerce/printful"; -import { Webflow } from "$lib/services/commerce/webflow"; +import PrintfulService from "$lib/services/commerce/printful"; +import SyncService from "$lib/services/commerce/sync"; +import type { DeepPartial } from "$lib/services/commerce/util/misc"; +import type { Printful } from "$lib/services/commerce/util/types"; +import WebflowService from "$lib/services/commerce/webflow"; + +const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); const player: Player = { metrics: { @@ -52,12 +57,71 @@ const computedPerformances: ActivityPerformance[] = [ // }) // }); -const productId = "68d080a129f538cd565bf56d"; +const p = await WebflowService.Products.get("68e66dc4633e577c91eda713"); +console.log(p); +// const printfulProducts = await PrintfulService.Products.getAll(); +// const webflowProducts = await WebflowService.Products.getAll(); +// +// for (const printfulProduct of printfulProducts) { +// const fullPrintfulProduct = await PrintfulService.Products.get(printfulProduct.id); +// console.log(fullPrintfulProduct.sync_product.name); +// const existingWebflowProduct = webflowProducts.find(p => fullPrintfulProduct.sync_product.name.includes(p.product.fieldData.name)) +// if (!existingWebflowProduct) { +// continue; +// } +// console.log(existingWebflowProduct.product.fieldData.name); +// +// console.log(existingWebflowProduct.skus.map(s => s.fieldData["sku-values"])); +// console.log(fullPrintfulProduct.sync_variants.map(s => ({ color: s.color, size: s.size }))); +// +// const newPrintfulVariants: DeepPartial[] = []; +// for (const printfulVariant of fullPrintfulProduct.sync_variants) { +// const associatedWebflowSku = existingWebflowProduct.skus +// .find(sku => sku.fieldData["sku-values"]?.["color"] === printfulVariant.color && +// sku.fieldData["sku-values"]?.["size"] === printfulVariant.size); +// if (associatedWebflowSku) { +// newPrintfulVariants.push({ +// "id": printfulVariant.id, // printful variant id +// "external_id": String(associatedWebflowSku.id), // webflow variant id +// }); +// } +// } +// +// if (!newPrintfulVariants.length) { +// continue; +// } +// +// await sleep(10000); +// await PrintfulService.Products.update(printfulProduct.id, { +// "sync_product": { +// "id": printfulProduct.id, +// "external_id": existingWebflowProduct.product.id + "-" + SyncService.findColorInProductName(printfulProduct.name) +// }, +// "sync_variants": newPrintfulVariants +// }); +// } +// console.log("DONE"); -const product = await Webflow.Products.get("68d080a129f538cd565bf56d"); -const products = await Webflow.Products.getAll(); - +// +// await WebflowService.Products.update(wProduct?.product.id!, wProduct!); +// +// const sku = { +// "fieldData": { +// "name": "Test", +// "slug": "test-test", +// "sku-values": { +// "color": "black", +// "size": "L", +// }, +// "price": { +// "value": +0 * 100, +// "unit": "USD", +// }, +// "main-image": "https://cdn.prod.website-files.com/675a3fbe966e58ea9aa710e4/68d09d3d8e1e8bad26a9cf07_knight-title-tank-white-10.png" +// } +// } +// const skuId = "68d080a5d90e8263e52242e9"; diff --git a/src/routes/commerce/+page.svelte b/src/routes/commerce/+page.svelte index ec3b69b..eb39087 100644 --- a/src/routes/commerce/+page.svelte +++ b/src/routes/commerce/+page.svelte @@ -7,12 +7,12 @@ const synchronizer = $state({ isSyncing: false, - printfulProductId: undefined as undefined | number, + syncingIds: [] as number[], poll: async function () { const res = await api.products.sync.get(); if (res.data) { - synchronizer.isSyncing = res.data.isSyncing; - synchronizer.printfulProductId = res.data.printfulProductId; + synchronizer.isSyncing = res.data.isSyncing ?? false; + synchronizer.syncingIds = res.data.syncingIds ?? []; } }, startPolling: () => { @@ -27,9 +27,7 @@ }, sync: async (printfulProductId: number) => { synchronizer.startPolling(); - await api.products - .sync({ printfulProductId: String(printfulProductId) }) - .post(); + await api.products.sync({ printfulProductId }).post(); }, }); @@ -45,7 +43,7 @@ ) => { const webflowProducts = await data.products.webflow; return webflowProducts.some( - (p) => p.product.id === printfulProduct.external_id, + (p) => p.product.id === printfulProduct.external_id.split("-")[0], ); }; @@ -58,7 +56,11 @@ onclick={() => synchronizer.syncAll()} class="btn btn-xl mb-5" > - Sync all + {#if synchronizer.isSyncing} + Syncing... + {:else} + Sync all + {/if}
@@ -72,10 +74,27 @@ {#each printfulProducts as printfulProduct} - +
{printfulProduct.name} {#await isProductSynced(printfulProduct) then isSynced} - {#if synchronizer.printfulProductId === printfulProduct.id} + {#if synchronizer.syncingIds.includes(printfulProduct.id)}
Syncing...