diff --git a/package-lock.json b/package-lock.json index 7dd85d06d..6190e86a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -810,57 +810,6 @@ "source-map-support": "^0.5.21" } }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", - "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", - "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", - "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-arm64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", @@ -878,312 +827,6 @@ "node": ">=12" } }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", - "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", - "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", - "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", - "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", - "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", - "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-loong64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", - "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-mips64el": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", - "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ppc64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", - "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-riscv64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", - "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-s390x": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", - "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", - "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/netbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", - "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/openbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", - "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/sunos-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", - "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", - "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", - "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", - "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/@esbuild-kit/core-utils/node_modules/esbuild": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", @@ -1234,70 +877,6 @@ "get-tsconfig": "^4.7.0" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", - "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz", - "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz", - "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz", - "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@esbuild/darwin-arm64": { "version": "0.25.4", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz", @@ -1314,326 +893,6 @@ "node": ">=18" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz", - "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz", - "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz", - "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz", - "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz", - "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz", - "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz", - "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz", - "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz", - "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz", - "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz", - "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz", - "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz", - "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz", - "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz", - "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz", - "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz", - "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz", - "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz", - "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz", - "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", @@ -2787,71 +2046,6 @@ "darwin" ] }, - "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", - "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", - "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", - "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", - "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", - "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@noble/hashes": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", @@ -8285,32 +7479,6 @@ "react-dom": "^18.0 || ^19.0 || ^19.0.0-rc" } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.0.tgz", - "integrity": "sha512-KxN+zCjOYHGwCl4UCtSfZ6jrq/qi88JDUtiEFk8LELEHq2Egfc/FgW+jItZiOLRuQfb/3xJSgFuNPC9jzggX+A==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.0.tgz", - "integrity": "sha512-yDvqx3lWlcugozax3DItKJI5j05B0d4Kvnjx+5mwiUpWramVvmAByYigMplaoAQ3pvdprGCTCE03eduqE/8mPQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, "node_modules/@rollup/rollup-darwin-arm64": { "version": "4.41.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.0.tgz", @@ -8324,227 +7492,6 @@ "darwin" ] }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.0.tgz", - "integrity": "sha512-gE5ACNSxHcEZyP2BA9TuTakfZvULEW4YAOtxl/A/YDbIir/wPKukde0BNPlnBiP88ecaN4BJI2TtAd+HKuZPQQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.0.tgz", - "integrity": "sha512-GSxU6r5HnWij7FoSo7cZg3l5GPg4HFLkzsFFh0N/b16q5buW1NAWuCJ+HMtIdUEi6XF0qH+hN0TEd78laRp7Dg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.0.tgz", - "integrity": "sha512-KGiGKGDg8qLRyOWmk6IeiHJzsN/OYxO6nSbT0Vj4MwjS2XQy/5emsmtoqLAabqrohbgLWJ5GV3s/ljdrIr8Qjg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.0.tgz", - "integrity": "sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.0.tgz", - "integrity": "sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.0.tgz", - "integrity": "sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.0.tgz", - "integrity": "sha512-l+QK99je2zUKGd31Gh+45c4pGDAqZSuWQiuRFCdHYC2CSiO47qUWsCcenrI6p22hvHZrDje9QjwSMAFL3iwXwQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.0.tgz", - "integrity": "sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.0.tgz", - "integrity": "sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.0.tgz", - "integrity": "sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.0.tgz", - "integrity": "sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.0.tgz", - "integrity": "sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.0.tgz", - "integrity": "sha512-XMLeKjyH8NsEDCRptf6LO8lJk23o9wvB+dJwcXMaH6ZQbbkHu2dbGIUindbMtRN6ux1xKi16iXWu6q9mu7gDhQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.0.tgz", - "integrity": "sha512-m/P7LycHZTvSQeXhFmgmdqEiTqSV80zn6xHaQ1JSqwCtD1YGtwEK515Qmy9DcB2HK4dOUVypQxvhVSy06cJPEg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.0.tgz", - "integrity": "sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.0.tgz", - "integrity": "sha512-tmazCrAsKzdkXssEc65zIE1oC6xPHwfy9d5Ta25SRCDOZS+I6RypVVShWALNuU9bxIfGA0aqrmzlzoM5wO5SPQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.0.tgz", - "integrity": "sha512-h1J+Yzjo/X+0EAvR2kIXJDuTuyT7drc+t2ALY0nIcGPbTatNOf0VWdhEA2Z4AAjv6X1NJV7SYo5oCTYRJhSlVA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@selderee/plugin-htmlparser2": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz", @@ -8815,22 +7762,6 @@ "@tailwindcss/oxide-win32-x64-msvc": "4.1.7" } }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.7.tgz", - "integrity": "sha512-IWA410JZ8fF7kACus6BrUwY2Z1t1hm0+ZWNEzykKmMNM09wQooOcN/VXr0p/WJdtHZ90PvJf2AIBS/Ceqx1emg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, "node_modules/@tailwindcss/oxide-darwin-arm64": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.7.tgz", @@ -8847,179 +7778,6 @@ "node": ">= 10" } }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.7.tgz", - "integrity": "sha512-q77rWjEyGHV4PdDBtrzO0tgBBPlQWKY7wZK0cUok/HaGgbNKecegNxCGikuPJn5wFAlIywC3v+WMBt0PEBtwGw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.7.tgz", - "integrity": "sha512-RfmdbbK6G6ptgF4qqbzoxmH+PKfP4KSVs7SRlTwcbRgBwezJkAO3Qta/7gDy10Q2DcUVkKxFLXUQO6J3CRvBGw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.7.tgz", - "integrity": "sha512-OZqsGvpwOa13lVd1z6JVwQXadEobmesxQ4AxhrwRiPuE04quvZHWn/LnihMg7/XkN+dTioXp/VMu/p6A5eZP3g==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.7.tgz", - "integrity": "sha512-voMvBTnJSfKecJxGkoeAyW/2XRToLZ227LxswLAwKY7YslG/Xkw9/tJNH+3IVh5bdYzYE7DfiaPbRkSHFxY1xA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.7.tgz", - "integrity": "sha512-PjGuNNmJeKHnP58M7XyjJyla8LPo+RmwHQpBI+W/OxqrwojyuCQ+GUtygu7jUqTEexejZHr/z3nBc/gTiXBj4A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.7.tgz", - "integrity": "sha512-HMs+Va+ZR3gC3mLZE00gXxtBo3JoSQxtu9lobbZd+DmfkIxR54NO7Z+UQNPsa0P/ITn1TevtFxXTpsRU7qEvWg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.7.tgz", - "integrity": "sha512-MHZ6jyNlutdHH8rd+YTdr3QbXrHXqwIhHw9e7yXEBcQdluGwhpQY2Eku8UZK6ReLaWtQ4gijIv5QoM5eE+qlsA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.7.tgz", - "integrity": "sha512-ANaSKt74ZRzE2TvJmUcbFQ8zS201cIPxUDm5qez5rLEwWkie2SkGtA4P+GPTj+u8N6JbPrC8MtY8RmJA35Oo+A==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@emnapi/wasi-threads": "^1.0.2", - "@napi-rs/wasm-runtime": "^0.2.9", - "@tybys/wasm-util": "^0.9.0", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.7.tgz", - "integrity": "sha512-HUiSiXQ9gLJBAPCMVRk2RT1ZrBjto7WvqsPBwUrNK2BcdSxMnk19h4pjZjI7zgPhDxlAbJSumTC4ljeA9y0tEw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.7.tgz", - "integrity": "sha512-rYHGmvoHiLJ8hWucSfSOEmdCBIGZIq7SpkPRSqLsH2Ab2YUNgKeAPT1Fi2cx3+hnYOrAb0jp9cRyode3bBW4mQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, "node_modules/@tailwindcss/vite": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.7.tgz", @@ -14552,186 +13310,6 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", - "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", - "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", - "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", - "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", - "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", - "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", - "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", - "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", - "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -20209,9 +18787,9 @@ } }, "node_modules/zod": { - "version": "3.25.20", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.20.tgz", - "integrity": "sha512-z03fqpTMDF1G02VLKUMt6vyACE7rNWkh3gpXVHgPTw28NPtDFRGvcpTtPwn2kMKtQ0idtYJUTxchytmnqYswcw==", + "version": "3.25.23", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.23.tgz", + "integrity": "sha512-Od2bdMosahjSrSgJtakrwjMDb1zM1A3VIHCPGveZt/3/wlrTWBya2lmEh2OYe4OIu8mPTmmr0gnLHIWQXdtWBg==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -20455,7 +19033,7 @@ "dependencies": { "drizzle-orm": "^0.43.1", "drizzle-zod": "^0.8.1", - "zod": "^3.x" + "zod": "^3.25.23" }, "devDependencies": { "typescript": "^5.7.2" diff --git a/server/src/db/initDrizzle.ts b/server/src/db/initDrizzle.ts index e6eb5d7cc..683876ce4 100644 --- a/server/src/db/initDrizzle.ts +++ b/server/src/db/initDrizzle.ts @@ -1,28 +1,18 @@ +import dotenv from "dotenv"; +dotenv.config(); + import postgres from "postgres"; import { drizzle } from "drizzle-orm/postgres-js"; -import { - customers, - chatResults, - organizations, - apiKeys, - rewards, -} from "./schema/index.js"; -import * as relations from "./relations.js"; - -const client = postgres(process.env.DATABASE_URL!); +import { schemas } from "@autumn/shared"; export const initDrizzle = () => { + const client = postgres(process.env.DATABASE_URL!); + const db = drizzle(client, { - schema: { - customers, - chatResults, - organizations, - apiKeys, - rewards, - ...relations, - }, + schema: schemas, }); - return db; + + return { db, client }; }; -export type DrizzleCli = ReturnType; +export type DrizzleCli = ReturnType["db"]; diff --git a/server/src/db/schema/index.ts b/server/src/db/schema/index.ts deleted file mode 100644 index ffb6f3972..000000000 --- a/server/src/db/schema/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { customers } from "./tables/cusTable.js"; -import { chatResults } from "./tables/chatResultsTable.js"; -// import { organizations } from "./tables/orgTable.js"; -import { apiKeys } from "./tables/apiKeysTable.js"; -import { entitlements } from "./tables/entitlementsTable.js"; -import { features } from "./tables/featuresTable.js"; -import { products } from "./tables/productsTable.js"; -import { prices } from "./tables/pricesTable.js"; -import { organizations } from "./tables/orgTable.js"; - -export { - customers, - chatResults, - organizations, - apiKeys, - entitlements, - features, - products, - prices, -}; -export * from "./tables/allTables.js"; diff --git a/server/src/db/schema/relations.ts b/server/src/db/schema/relations.ts deleted file mode 100644 index 53464ff6e..000000000 --- a/server/src/db/schema/relations.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { relations } from "drizzle-orm"; -import { features } from "./tables/featuresTable.js"; -import { products } from "./tables/productsTable.js"; -import { organizations } from "./tables/orgTable.js"; -import { apiKeys } from "./tables/apiKeysTable.js"; -import { entitlements } from "./tables/entitlementsTable.js"; -import { prices } from "./tables/pricesTable.js"; - -export const organizationsRelations = relations(organizations, ({ many }) => ({ - apiKeys: many(apiKeys), - features: many(features), -})); - -export const featuresRelations = relations(features, ({ one }) => ({ - org: one(organizations, { - fields: [features.org_id], - references: [organizations.id], - }), -})); - -export const productsRelations = relations(products, ({ one, many }) => ({ - org: one(organizations, { - fields: [products.org_id], - references: [organizations.id], - }), - entitlements: many(entitlements), - prices: many(prices), -})); - -export const entitlementsRelations = relations(entitlements, ({ one }) => ({ - feature: one(features, { - fields: [entitlements.internal_feature_id], - references: [features.internal_id], - }), -})); diff --git a/server/src/db/schema/tables/allTables.ts b/server/src/db/schema/tables/allTables.ts deleted file mode 100644 index ca1200646..000000000 --- a/server/src/db/schema/tables/allTables.ts +++ /dev/null @@ -1,496 +0,0 @@ -import { - pgTable, - text, - numeric, - jsonb, - boolean, - foreignKey, - unique, - primaryKey, -} from "drizzle-orm/pg-core"; -import { organizations } from "../index.js"; -import { features } from "./featuresTable.js"; -import { prices } from "../index.js"; - -export const rewards = pgTable( - "rewards", - { - internalId: text("internal_id").primaryKey().notNull(), - env: text(), - name: text(), - orgId: text("org_id"), - createdAt: numeric("created_at"), - discountConfig: jsonb("discount_config"), - freeProductId: text("free_product_id"), - id: text(), - promoCodes: jsonb("promo_codes").array(), - type: text(), - }, - (table) => [ - foreignKey({ - columns: [table.orgId], - foreignColumns: [organizations.id], - name: "coupons_org_id_fkey", - }).onDelete("cascade"), - ], -); - -export const rewardPrograms = pgTable( - "reward_programs", - { - internalId: text("internal_id").primaryKey().notNull(), - id: text(), - createdAt: numeric("created_at").notNull(), - internalRewardId: text("internal_reward_id"), - maxRedemptions: numeric("max_redemptions"), - unlimitedRedemptions: boolean("unlimited_redemptions").default(false), - orgId: text("org_id"), - env: text(), - when: text().default("immediately"), - productIds: text("product_ids").array().default([""]), - excludeTrial: boolean("exclude_trial").default(false), - receivedBy: text("received_by"), - }, - (table) => [ - foreignKey({ - columns: [table.internalRewardId], - foreignColumns: [rewards.internalId], - name: "reward_triggers_internal_reward_id_fkey", - }).onDelete("cascade"), - foreignKey({ - columns: [table.orgId], - foreignColumns: [organizations.id], - name: "reward_triggers_org_id_fkey", - }).onDelete("cascade"), - ], -); - -export const customerPrices = pgTable( - "customer_prices", - { - id: text().primaryKey().notNull(), - createdAt: numeric("created_at").notNull(), - customerProductId: text("customer_product_id"), - priceId: text("price_id"), - options: jsonb(), - internalCustomerId: text("internal_customer_id"), - }, - (table) => [ - foreignKey({ - columns: [table.customerProductId], - foreignColumns: [customerProducts.id], - name: "customer_prices_customer_product_id_fkey", - }).onDelete("cascade"), - foreignKey({ - columns: [table.internalCustomerId], - foreignColumns: [customers.internalId], - name: "customer_prices_internal_customer_id_fkey", - }).onDelete("cascade"), - foreignKey({ - columns: [table.priceId], - foreignColumns: [prices.id], - name: "customer_prices_price_id_fkey", - }), - ], -); - -export const entities = pgTable( - "entities", - { - internalId: text("internal_id").primaryKey().notNull(), - orgId: text("org_id"), - env: text(), - internalCustomerId: text("internal_customer_id"), - createdAt: numeric("created_at").notNull(), - id: text(), - name: text(), - deleted: boolean().default(false).notNull(), - internalFeatureId: text("internal_feature_id"), - featureId: text("feature_id"), - }, - (table) => [ - foreignKey({ - columns: [table.internalCustomerId], - foreignColumns: [customers.internalId], - name: "entities_internal_customer_id_fkey", - }).onDelete("cascade"), - foreignKey({ - columns: [table.internalFeatureId], - foreignColumns: [features.internal_id], - name: "entities_internal_feature_id_fkey", - }).onDelete("cascade"), - foreignKey({ - columns: [table.orgId], - foreignColumns: [organizations.id], - name: "entities_org_id_fkey", - }).onDelete("cascade"), - unique("entity_id_constraint").on( - table.orgId, - table.env, - table.internalCustomerId, - table.id, - ), - ], -); - -// export const entitlements = pgTable( -// "entitlements", -// { -// createdAt: numeric("created_at").notNull(), -// internalFeatureId: text("internal_feature_id"), -// orgId: text("org_id"), -// internalProductId: text("internal_product_id"), -// allowanceType: text("allowance_type"), -// allowance: numeric(), -// interval: text(), -// id: text().primaryKey().notNull(), -// featureId: text("feature_id"), -// isCustom: boolean("is_custom").default(false), -// carryFromPrevious: boolean("carry_from_previous").default(false), -// entityFeatureId: text("entity_feature_id"), -// }, -// (table) => [ -// foreignKey({ -// columns: [table.internalFeatureId], -// foreignColumns: [features.internal_id], -// name: "entitlements_internal_feature_id_fkey", -// }).onDelete("cascade"), -// foreignKey({ -// columns: [table.internalProductId], -// foreignColumns: [products.internalId], -// name: "entitlements_internal_product_id_fkey", -// }) -// .onUpdate("cascade") -// .onDelete("cascade"), -// unique("entitlements_id_key").on(table.id), -// ], -// ); - -export const events = pgTable( - "events", - { - id: text().primaryKey().notNull(), - orgId: text("org_id").notNull(), - timestamp: numeric().notNull(), - env: text().notNull(), - customerId: text("customer_id").notNull(), - eventName: text("event_name").notNull(), - properties: jsonb(), - idempotencyKey: text("idempotency_key"), - internalCustomerId: text("internal_customer_id"), - value: numeric(), - setUsage: boolean("set_usage").default(false), - entityId: text("entity_id"), - }, - (table) => [ - foreignKey({ - columns: [table.internalCustomerId], - foreignColumns: [customers.internalId], - name: "events_internal_customer_id_fkey", - }).onDelete("cascade"), - unique("unique_event_constraint").on( - table.orgId, - table.env, - table.customerId, - table.eventName, - table.idempotencyKey, - ), - ], -); - -export const freeTrials = pgTable( - "free_trials", - { - id: text().primaryKey().notNull(), - createdAt: numeric("created_at").notNull(), - duration: text().default("day"), - length: numeric(), - internalProductId: text("internal_product_id"), - uniqueFingerprint: boolean("unique_fingerprint"), - isCustom: boolean("is_custom").default(false), - }, - (table) => [ - foreignKey({ - columns: [table.internalProductId], - foreignColumns: [products.internalId], - name: "free_trials_internal_product_id_fkey", - }).onDelete("cascade"), - ], -); - -export const invoiceItems = pgTable( - "invoice_items", - { - createdAt: numeric("created_at").notNull(), - updatedAt: numeric("updated_at"), - customerPriceId: text("customer_price_id"), - periodStart: numeric("period_start"), - periodEnd: numeric("period_end"), - prorationStart: numeric("proration_start"), - prorationEnd: numeric("proration_end"), - quantity: numeric(), - amount: numeric(), - currency: text(), - id: text().primaryKey().notNull(), - addedToStripe: boolean("added_to_stripe").default(false), - customerId: text("customer_id"), - }, - (table) => [ - foreignKey({ - columns: [table.customerPriceId], - foreignColumns: [customerPrices.id], - name: "invoice_items_customer_price_id_fkey", - }).onDelete("cascade"), - unique("invoice_items_id_key").on(table.id), - ], -); - -export const customers = pgTable( - "customers", - { - name: text().default(""), - orgId: text("org_id").notNull(), - createdAt: numeric("created_at").notNull(), - internalId: text("internal_id").primaryKey().notNull(), - id: text(), - env: text(), - processor: jsonb(), - email: text().default(""), - fingerprint: text(), - metadata: jsonb().default({}), - }, - (table) => [unique("cus_id_constraint").on(table.orgId, table.id, table.env)], -); - -export const customerProducts = pgTable( - "customer_products", - { - id: text().primaryKey().notNull(), - internalCustomerId: text("internal_customer_id").notNull(), - customerId: text("customer_id"), - internalProductId: text("internal_product_id"), - createdAt: numeric("created_at"), - status: text(), - processor: jsonb(), - canceledAt: numeric("canceled_at"), - endedAt: numeric("ended_at"), - startsAt: numeric("starts_at"), - options: jsonb().array(), - productId: text("product_id"), - freeTrialId: text("free_trial_id"), - trialEndsAt: numeric("trial_ends_at"), - collectionMethod: text("collection_method").default("charge_automatically"), - subscriptionIds: text("subscription_ids").array(), - scheduledIds: text("scheduled_ids").array(), - isCustom: boolean("is_custom").default(false).notNull(), - quantity: numeric().default("1"), - internalEntityId: text("internal_entity_id"), - entityId: text("entity_id"), - }, - (table) => [ - foreignKey({ - columns: [table.freeTrialId], - foreignColumns: [freeTrials.id], - name: "customer_products_free_trial_id_fkey", - }), - foreignKey({ - columns: [table.internalCustomerId], - foreignColumns: [customers.internalId], - name: "customer_products_internal_customer_id_fkey", - }) - .onUpdate("cascade") - .onDelete("cascade"), - foreignKey({ - columns: [table.internalEntityId], - foreignColumns: [entities.internalId], - name: "customer_products_internal_entity_id_fkey", - }).onDelete("set null"), - foreignKey({ - columns: [table.internalProductId], - foreignColumns: [products.internalId], - name: "customer_products_internal_product_id_fkey", - }), - ], -); - -export const metadata = pgTable("metadata", { - id: text().primaryKey().notNull(), - createdAt: numeric("created_at").notNull(), - expiresAt: numeric("expires_at"), - data: jsonb(), -}); - -export const products = pgTable( - "products", - { - internalId: text("internal_id").primaryKey().notNull(), - createdAt: numeric("created_at").notNull(), - name: text(), - orgId: text("org_id"), - env: text(), - isAddOn: boolean("is_add_on"), - processor: jsonb(), - isDefault: boolean("is_default").default(false), - id: text(), - group: text().default(""), - version: numeric().default("1"), - }, - (table) => [ - foreignKey({ - columns: [table.orgId], - foreignColumns: [organizations.id], - name: "products_org_id_fkey", - }).onDelete("cascade"), - ], -); - -export const rewardRedemptions = pgTable( - "reward_redemptions", - { - id: text().primaryKey().notNull(), - createdAt: numeric("created_at").notNull(), - updatedAt: numeric("updated_at"), - internalCustomerId: text("internal_customer_id"), - triggered: boolean(), - internalRewardProgramId: text("internal_reward_program_id"), - applied: boolean().default(false), - referralCodeId: text("referral_code_id"), - }, - (table) => [ - foreignKey({ - columns: [table.internalCustomerId], - foreignColumns: [customers.internalId], - name: "reward_redemptions_internal_customer_id_fkey", - }).onDelete("cascade"), - foreignKey({ - columns: [table.internalRewardProgramId], - foreignColumns: [rewardPrograms.internalId], - name: "reward_redemptions_internal_reward_program_id_fkey", - }).onDelete("cascade"), - foreignKey({ - columns: [table.referralCodeId], - foreignColumns: [referralCodes.id], - name: "reward_redemptions_referral_code_id_fkey", - }).onDelete("cascade"), - ], -); - -export const migrationJobs = pgTable( - "migration_jobs", - { - id: text().primaryKey().notNull(), - createdAt: numeric("created_at").notNull(), - updatedAt: numeric("updated_at"), - currentStep: text("current_step"), - fromInternalProductId: text("from_internal_product_id"), - toInternalProductId: text("to_internal_product_id"), - stepDetails: jsonb("step_details"), - orgId: text("org_id"), - env: text(), - }, - (table) => [ - foreignKey({ - columns: [table.fromInternalProductId], - foreignColumns: [products.internalId], - name: "migration_jobs_from_internal_product_id_fkey", - }).onDelete("cascade"), - foreignKey({ - columns: [table.orgId], - foreignColumns: [organizations.id], - name: "migration_jobs_org_id_fkey", - }).onDelete("cascade"), - foreignKey({ - columns: [table.toInternalProductId], - foreignColumns: [products.internalId], - name: "migration_jobs_to_internal_product_id_fkey", - }).onDelete("cascade"), - ], -); - -export const subscriptions = pgTable( - "subscriptions", - { - id: text().primaryKey().notNull(), - stripeId: text("stripe_id"), - stripeScheduleId: text("stripe_schedule_id"), - createdAt: numeric("created_at"), - metadata: jsonb().default({}), - usageFeatures: text("usage_features").array().default([""]), - orgId: text("org_id"), - env: text(), - currentPeriodStart: numeric("current_period_start"), - currentPeriodEnd: numeric("current_period_end"), - }, - (table) => [ - foreignKey({ - columns: [table.orgId], - foreignColumns: [organizations.id], - name: "subscriptions_org_id_fkey", - }).onDelete("cascade"), - unique("subscriptions_stripe_id_key").on(table.stripeId), - ], -); - -export const referralCodes = pgTable( - "referral_codes", - { - code: text().notNull(), - orgId: text("org_id").notNull(), - env: text().notNull(), - internalCustomerId: text("internal_customer_id"), - internalRewardProgramId: text("internal_reward_program_id"), - id: text().notNull(), - createdAt: numeric("created_at"), - }, - (table) => [ - foreignKey({ - columns: [table.internalCustomerId], - foreignColumns: [customers.internalId], - name: "referral_codes_internal_customer_id_fkey", - }).onDelete("cascade"), - foreignKey({ - columns: [table.internalRewardProgramId], - foreignColumns: [rewardPrograms.internalId], - name: "referral_codes_internal_reward_program_id_fkey", - }).onDelete("cascade"), - foreignKey({ - columns: [table.orgId], - foreignColumns: [organizations.id], - name: "referral_codes_org_id_fkey", - }).onDelete("cascade"), - primaryKey({ - columns: [table.code, table.orgId, table.env], - name: "referral_codes_pkey", - }), - unique("referral_codes_id_key").on(table.id), - ], -); - -export const migrationErrors = pgTable( - "migration_errors", - { - internalCustomerId: text("internal_customer_id").notNull(), - migrationJobId: text("migration_job_id").notNull(), - createdAt: numeric("created_at"), - updatedAt: numeric("updated_at"), - data: jsonb(), - message: text(), - code: text(), - }, - (table) => [ - foreignKey({ - columns: [table.internalCustomerId], - foreignColumns: [customers.internalId], - name: "migration_customers_internal_customer_id_fkey", - }).onDelete("cascade"), - foreignKey({ - columns: [table.migrationJobId], - foreignColumns: [migrationJobs.id], - name: "migration_customers_migration_job_id_fkey", - }).onDelete("cascade"), - primaryKey({ - columns: [table.internalCustomerId, table.migrationJobId], - name: "migration_errors_pkey", - }), - ], -); diff --git a/server/src/db/schema/tables/invoicesTable.ts b/server/src/db/schema/tables/invoicesTable.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/server/src/db/schema/tables/orgTable.ts b/server/src/db/schema/tables/orgTable.ts deleted file mode 100644 index a8eb91731..000000000 --- a/server/src/db/schema/tables/orgTable.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { - pgTable, - text, - numeric, - jsonb, - boolean, - unique, -} from "drizzle-orm/pg-core"; - -import { Organization } from "@autumn/shared"; - -export const organizations = pgTable( - "organizations", - { - id: text().primaryKey().notNull(), - slug: text().notNull(), - default_currency: text("default_currency"), - stripe_connected: boolean("stripe_connected"), - stripe_config: - jsonb("stripe_config").$type(), - test_pkey: text("test_pkey"), - live_pkey: text("live_pkey"), - svix_config: jsonb("svix_config").$type(), - created_at: numeric("created_at"), - config: jsonb().default({}), - }, - (table) => [ - unique("organizations_test_pkey_key").on(table.test_pkey), - unique("organizations_live_pkey_key").on(table.live_pkey), - ], -); diff --git a/server/src/external/autumn/autumnUtils.ts b/server/src/external/autumn/autumnUtils.ts index fe3adf546..2aeebb639 100644 --- a/server/src/external/autumn/autumnUtils.ts +++ b/server/src/external/autumn/autumnUtils.ts @@ -1,8 +1,6 @@ -import { AppEnv, ErrCode, MinOrg, Organization } from "@autumn/shared"; +import { AppEnv, ErrCode, Organization } from "@autumn/shared"; import { Autumn } from "./autumnCli.js"; import RecaseError from "@/utils/errorUtils.js"; -import { OrgService } from "@/internal/orgs/OrgService.js"; -import { SupabaseClient } from "@supabase/supabase-js"; export enum FeatureId { Products = "products", @@ -10,43 +8,12 @@ export enum FeatureId { Revenue = "revenue", } -export const sendFeatureEvent = async ({ - minOrg, - env, - incrementBy, -}: { - minOrg: MinOrg; - env: AppEnv; - incrementBy: number; -}) => { - if (env !== AppEnv.Live) { - return; - } - - try { - const autumn = new Autumn(); - - await autumn.sendEvent({ - customerId: minOrg.id, - eventName: "feature", - properties: { - value: incrementBy, - }, - customer_data: { - name: minOrg.slug, - }, - }); - } catch (error: any) { - console.log("Failed to send feature event", error?.message || error); - } -}; - export const sendProductEvent = async ({ - minOrg, + org, env, incrementBy, }: { - minOrg: MinOrg; + org: Organization; env: AppEnv; incrementBy: number; }) => { @@ -58,13 +25,13 @@ export const sendProductEvent = async ({ const autumn = new Autumn(); await autumn.sendEvent({ - customerId: minOrg.id, + customerId: org.id, eventName: "product", properties: { value: incrementBy, }, customer_data: { - name: minOrg.slug, + name: org.slug, }, }); console.log("sent product event", incrementBy); @@ -74,11 +41,11 @@ export const sendProductEvent = async ({ }; export const isEntitled = async ({ - minOrg, + org, env, featureId, }: { - minOrg: MinOrg; + org: Organization; env: AppEnv; featureId: FeatureId; }) => { @@ -91,10 +58,10 @@ export const isEntitled = async ({ let result; try { result = await autumn.entitled({ - customerId: minOrg.id, + customerId: org.id, featureId: featureId, customer_data: { - name: minOrg.slug, + name: org.slug, }, }); } catch (error: any) { diff --git a/server/src/external/stripe/createStripePrice/createStripeArrearProrated.ts b/server/src/external/stripe/createStripePrice/createStripeArrearProrated.ts index d6326aa41..054d742c3 100644 --- a/server/src/external/stripe/createStripePrice/createStripeArrearProrated.ts +++ b/server/src/external/stripe/createStripePrice/createStripeArrearProrated.ts @@ -89,7 +89,7 @@ export const createStripeMeteredPrice = async ({ ...productData, ...priceAmountData, currency: org.default_currency, - nickname: `Autumn Price (${price.name}) [Placeholder]`, + nickname: `Autumn Price (${feature!.name}) [Placeholder]`, recurring: { ...(billingIntervalToStripe(price.config!.interval!) as any), meter: meter!.id, @@ -102,7 +102,7 @@ export const createStripeMeteredPrice = async ({ export const arrearProratedToStripeTiers = ( price: Price, - entitlement: EntitlementWithFeature + entitlement: EntitlementWithFeature, ) => { let usageConfig = structuredClone(price.config) as UsagePriceConfig; @@ -198,7 +198,7 @@ export const createStripeArrearProrated = async ({ recurring: { ...(recurringData as any), }, - nickname: `Autumn Price (${price.name})`, + nickname: `Autumn Price (${relatedEnt.feature.name})`, }); config.stripe_price_id = stripePrice.id; diff --git a/server/src/external/stripe/createStripePrice/createStripeInArrear.ts b/server/src/external/stripe/createStripePrice/createStripeInArrear.ts index 77f3bb1bf..330603786 100644 --- a/server/src/external/stripe/createStripePrice/createStripeInArrear.ts +++ b/server/src/external/stripe/createStripePrice/createStripeInArrear.ts @@ -52,7 +52,7 @@ export const searchStripeMeter = async ({ logger.info(`Stripe meter list took ${end - start}ms`); let stripeMeter = allStripeMeters.find( - (m) => m.event_name == eventName || m.id == meterId + (m) => m.event_name == eventName || m.id == meterId, ); return stripeMeter; @@ -86,7 +86,7 @@ export const getStripeMeter = async ({ createNew = true; } else { logger.info( - `✅ Found existing meter for ${product.name} - ${feature!.name}` + `✅ Found existing meter for ${product.name} - ${feature!.name}`, ); return stripeMeter; } @@ -106,7 +106,7 @@ export const getStripeMeter = async ({ // IN ARREAR export const priceToInArrearTiers = ( price: Price, - entitlement: Entitlement + entitlement: Entitlement, ) => { let usageConfig = structuredClone(price.config) as UsagePriceConfig; const tiers: any[] = []; @@ -176,7 +176,7 @@ export const createStripeInArrearPrice = async ({ if (internalEntityId && !useCheckout) { if (!curStripeProduct) { logger.info( - `Creating stripe in arrear product for ${relatedEnt.feature.name} (internal entity ID exists!)` + `Creating stripe in arrear product for ${relatedEnt.feature.name} (internal entity ID exists!)`, ); let stripeProduct = await stripeCli.products.create({ name: `${product.name} - ${feature!.name}`, @@ -198,7 +198,7 @@ export const createStripeInArrearPrice = async ({ } logger.info( - `Creating stripe in arrear price for ${relatedEnt.feature.name} (no internal entity ID)` + `Creating stripe in arrear price for ${relatedEnt.feature.name} (no internal entity ID)`, ); if (!feature) { @@ -222,7 +222,7 @@ export const createStripeInArrearPrice = async ({ const tiers = priceToInArrearTiers( price, - getPriceEntitlement(price, entitlements) + getPriceEntitlement(price, entitlements), ); let priceAmountData = {}; @@ -260,7 +260,7 @@ export const createStripeInArrearPrice = async ({ meter: meter!.id, usage_type: "metered", }, - nickname: `Autumn Price (${price.name})`, + nickname: `Autumn Price (${relatedEnt.feature.name})`, }); config.stripe_price_id = stripePrice.id; diff --git a/server/src/external/stripe/createStripePrice/createStripePrepaid.ts b/server/src/external/stripe/createStripePrice/createStripePrepaid.ts index f3f92a548..c4801c4f8 100644 --- a/server/src/external/stripe/createStripePrice/createStripePrepaid.ts +++ b/server/src/external/stripe/createStripePrice/createStripePrepaid.ts @@ -21,7 +21,7 @@ import { Decimal } from "decimal.js"; export const prepaidToStripeTiers = ( price: Price, - entitlement: EntitlementWithFeature + entitlement: EntitlementWithFeature, ) => { let usageConfig = structuredClone(price.config) as UsagePriceConfig; @@ -137,7 +137,7 @@ export const createStripePrepaid = async ({ recurring: { ...(recurringData as any), }, - nickname: `Autumn Price (${price.name})`, + nickname: `Autumn Price (${relatedEnt.feature.name})`, }); config.stripe_price_id = stripePrice.id; diff --git a/server/src/external/stripe/priceToStripeItem/priceToStripeItem.ts b/server/src/external/stripe/priceToStripeItem/priceToStripeItem.ts index 8d8e435a8..301df66a1 100644 --- a/server/src/external/stripe/priceToStripeItem/priceToStripeItem.ts +++ b/server/src/external/stripe/priceToStripeItem/priceToStripeItem.ts @@ -104,7 +104,7 @@ export const priceToStripeItem = ({ if (!priceId) { throw new RecaseError({ code: ErrCode.PriceNotFound, - message: `Couldn't find price: ${price.name}, ${price.id} in Stripe`, + message: `Couldn't find Autumn price: ${price.id} in Stripe`, statusCode: 400, }); } diff --git a/server/src/external/webhooks/clerkWebhooks.ts b/server/src/external/webhooks/clerkWebhooks.ts index 7be8f0dbb..c4def8bd2 100644 --- a/server/src/external/webhooks/clerkWebhooks.ts +++ b/server/src/external/webhooks/clerkWebhooks.ts @@ -16,10 +16,8 @@ import { import { DrizzleCli } from "@/db/initDrizzle.js"; import { constructOrg } from "@/internal/orgs/orgUtils.js"; import { createOnboardingProducts } from "@/internal/orgs/onboarding/createOnboardingProducts.js"; -import { organizations } from "@/db/schema/tables/orgTable.js"; import { eq } from "drizzle-orm"; -import { ErrCode } from "@/errors/errCodes.js"; -import { Organization } from "@autumn/shared"; +import { Organization, organizations } from "@autumn/shared"; const verifyClerkWebhook = async (req: Request, res: Response) => { const wh = new Webhook(process.env.CLERK_SIGNING_SECRET!); @@ -60,6 +58,7 @@ const verifyClerkWebhook = async (req: Request, res: Response) => { export const handleClerkWebhook = async (req: any, res: any) => { let event = await verifyClerkWebhook(req, res); + if (!event) { return; } @@ -70,7 +69,11 @@ export const handleClerkWebhook = async (req: any, res: any) => { try { switch (eventType) { case "organization.created": - await handleOrgCreated(req.db, eventData); + await saveOrgToDB({ + db: req.db, + id: eventData.id, + slug: eventData.slug, + }); break; case "organization.deleted": @@ -99,47 +102,46 @@ export const handleClerkWebhook = async (req: any, res: any) => { }); }; -export const handleOrgCreated = async ( - db: DrizzleCli, - eventData: { - id: string; - slug: string; - created_at: number; - }, -) => { - console.log( - `Handling organization.created: ${eventData.slug} (${eventData.id})`, - ); +export const saveOrgToDB = async ({ + db, + id, + slug, +}: { + db: DrizzleCli; + id: string; + slug: string; +}) => { + console.log(`Handling organization.created: ${slug} (${id})`); try { // 2. Insert org await OrgService.insert({ db, org: constructOrg({ - id: eventData.id, - slug: eventData.slug, + id, + slug, }), }); // 1. Create svix webhoooks const { sandboxApp, liveApp } = await initOrgSvixApps({ - slug: eventData.slug, - id: eventData.id, + slug, + id, }); await OrgService.update({ db, - orgId: eventData.id, + orgId: id, updates: { svix_config: { sandbox_app_id: sandboxApp.id, live_app_id: liveApp.id }, }, }); - console.log(`Created svix webhooks for org ${eventData.id}`); + console.log(`Created svix webhooks for org ${id}`); } catch (error: any) { if (error?.data && error.data.code == "23505") { console.error( - `Org ${eventData.id} already exists in Supabase -- skipping creationg`, + `Org ${id} already exists in Supabase -- skipping creationg`, ); return; } @@ -152,16 +154,16 @@ export const handleOrgCreated = async ( const batch = []; try { - batch.push( - createOnboardingProducts({ - db, - orgId: eventData.id, - }), - ); + // batch.push( + // createOnboardingProducts({ + // db, + // orgId: eventData.id, + // }), + // ); batch.push( sendOnboardingEmail({ - orgId: eventData.id, + orgId: id, clerkCli: createClerkCli(), }), ); @@ -201,7 +203,7 @@ const handleOrgDeleted = async ({ console.log("1. Deleting svix webhooks"); const batch = []; - if (org.svix_config.sandbox_app_id) { + if (org.svix_config?.sandbox_app_id) { batch.push( deleteSvixApp({ appId: org.svix_config.sandbox_app_id, @@ -209,7 +211,7 @@ const handleOrgDeleted = async ({ ); } - if (org.svix_config.live_app_id) { + if (org.svix_config?.live_app_id) { batch.push( deleteSvixApp({ appId: org.svix_config.live_app_id, diff --git a/server/src/index.ts b/server/src/index.ts index 65ad8c700..a13843ce5 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -29,21 +29,23 @@ const init = async () => { server.keepAliveTimeout = 120000; // 120 seconds server.headersTimeout = 120000; // 120 seconds should be >= keepAliveTimeout - const pgClient = new pg.Client(process.env.DATABASE_URL || ""); - await pgClient.connect(); + const pgClient = new pg.Client( + process.env.SUPABASE_CONNECTION_STRING || process.env.DATABASE_URL || "", + ); + await pgClient.connect(); await QueueManager.getInstance(); // initialize the queue manager await CacheManager.getInstance(); // await initWorkers(); const supabaseClient = createSupabaseClient(); const logtailAll = createLogtailAll(); - const drizzle = initDrizzle(); + const { client, db } = initDrizzle(); app.use((req: any, res, next) => { req.sb = supabaseClient; req.pg = pgClient; - req.db = drizzle; + req.db = db; req.logger = logger; req.logtailAll = logtailAll; diff --git a/server/src/internal/api/apiRouter.ts b/server/src/internal/api/apiRouter.ts index 6a4762439..5f71dd4e3 100644 --- a/server/src/internal/api/apiRouter.ts +++ b/server/src/internal/api/apiRouter.ts @@ -29,8 +29,8 @@ apiRouter.use(apiAuthMiddleware); apiRouter.use(pricingMiddleware); apiRouter.use((req: any, res: any, next: any) => { const logtailContext: any = { - org_id: req.minOrg?.id, - org_slug: req.minOrg?.slug, + org_id: req.org?.id, + org_slug: req.org?.slug, method: req.method, url: req.originalUrl, body: req.body, @@ -61,14 +61,14 @@ apiRouter.use((req: any, res: any, next: any) => { return; } req.logtailAll.info( - `[${res.statusCode}] ${req.method} ${req.originalUrl} (${req.minOrg?.slug})`, + `[${res.statusCode}] ${req.method} ${req.originalUrl} (${req.org?.slug})`, { req: { ...logtailContext, }, statusCode: res.statusCode, res: res.locals.responseBody, - } + }, ); req.logtailAll.flush(); } catch (error) { diff --git a/server/src/internal/api/customers/cusRouter.ts b/server/src/internal/api/customers/cusRouter.ts index 8a51f6408..783b2de0c 100644 --- a/server/src/internal/api/customers/cusRouter.ts +++ b/server/src/internal/api/customers/cusRouter.ts @@ -69,14 +69,14 @@ cusRouter.post("/:customer_id", handleUpdateCustomer); // Update customer entitlement directly cusRouter.post( "/customer_entitlements/:customer_entitlement_id", - handleUpdateEntitlement + handleUpdateEntitlement, ); cusRouter.post("/:customer_id/balances", handleUpdateBalances); cusRouter.post( "/customer_products/:customer_product_id", - handleCusProductExpired + handleCusProductExpired, ); cusRouter.get("/:customer_id/billing_portal", async (req: any, res: any) => { @@ -115,7 +115,7 @@ cusRouter.get("/:customer_id/billing_portal", async (req: any, res: any) => { const stripeCli = createStripeCli({ org, env: req.env }); const portal = await stripeCli.billingPortal.sessions.create({ customer: customer.processor.id, - return_url: returnUrl || org.stripe_config.success_url, + return_url: returnUrl || org.stripe_config?.success_url, }); if (org.api_version >= APIVersion.v1_1) { diff --git a/server/src/internal/api/features/featureApiRouter.ts b/server/src/internal/api/features/featureApiRouter.ts index 37c147358..9b694ecdf 100644 --- a/server/src/internal/api/features/featureApiRouter.ts +++ b/server/src/internal/api/features/featureApiRouter.ts @@ -74,7 +74,7 @@ featureApiRouter.post("", async (req: any, res) => { let data = req.body; try { - let { sb, orgId, env, logtail: logger } = req; + let { db, orgId, env, logtail: logger } = req; let parsedFeature = validateFeature(data); let feature: Feature = { @@ -87,8 +87,7 @@ featureApiRouter.post("", async (req: any, res) => { let org = await OrgService.getFromReq(req); let insertedData = await FeatureService.insert({ - db: req.db, - sb, + db, data: feature, logger, }); diff --git a/server/src/internal/api/products/handleDeleteProduct.ts b/server/src/internal/api/products/handleDeleteProduct.ts index 751208f11..ad987789d 100644 --- a/server/src/internal/api/products/handleDeleteProduct.ts +++ b/server/src/internal/api/products/handleDeleteProduct.ts @@ -16,18 +16,16 @@ export const handleDeleteProduct = (req: any, res: any) => const orgId = req.orgId; const env = req.env; - const [org, product] = await Promise.all([ - OrgService.getFullOrg({ - sb, - orgId, - }), - ProductService.getProductStrict({ - sb, - productId, - orgId, - env, - }), - ]); + console.log("Org ID", orgId); + console.log("Product ID", productId); + console.log("Env", env); + + const product = await ProductService.getProductStrict({ + sb, + productId, + orgId, + env, + }); if (!product) { throw new RecaseError({ @@ -39,7 +37,7 @@ export const handleDeleteProduct = (req: any, res: any) => let cusProducts = await CusProductService.getByInternalProductId( sb, - product.internal_id + product.internal_id, ); if (cusProducts.length > 0) { diff --git a/server/src/internal/api/products/handlers/handleCopyProduct.ts b/server/src/internal/api/products/handlers/handleCopyProduct.ts index bf8e30c62..be9455e7c 100644 --- a/server/src/internal/api/products/handlers/handleCopyProduct.ts +++ b/server/src/internal/api/products/handlers/handleCopyProduct.ts @@ -16,7 +16,7 @@ export const handleCopyProduct = async (req: any, res: any) => const sb = req.sb; const orgId = req.orgId; const fromEnv = req.env; - const { env: toEnv, id: toId, name: toName } = req.body; + const { db, env: toEnv, id: toId, name: toName } = req.body; let { logtail: logger } = req; if (!toEnv || !toId || !toName) { @@ -85,8 +85,7 @@ export const handleCopyProduct = async (req: any, res: any) => if (!toFeature) { let res = await FeatureService.insert({ - db: req.db, - sb, + db, data: initNewFeature({ data: CreateFeatureSchema.parse(fromFeature), orgId, diff --git a/server/src/internal/credits/CreditService.ts b/server/src/internal/credits/CreditService.ts deleted file mode 100644 index 655916f3c..000000000 --- a/server/src/internal/credits/CreditService.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { CreditSystem } from "@autumn/shared"; -import { SupabaseClient } from "@supabase/supabase-js"; - -export class CreditService { - static async getByOrg(sb: SupabaseClient, orgId: string) { - let { data, error } = await sb - .from("credit_systems") - .select("*") - .eq("org_id", orgId); - - if (error) throw error; - - return data; - } - - static async insert(sb: SupabaseClient, creditSystem: CreditSystem) { - let { data, error } = await sb.from("credit_systems").insert(creditSystem); - - if (error) throw error; - - return data; - } -} diff --git a/server/src/internal/credits/creditsRouter.ts b/server/src/internal/credits/creditsRouter.ts deleted file mode 100644 index 8d99d967b..000000000 --- a/server/src/internal/credits/creditsRouter.ts +++ /dev/null @@ -1,42 +0,0 @@ -import express from "express"; -import { generateId } from "@/utils/genUtils.js"; -import { getFeaturesByOrg } from "../features/featureCrud.js"; -import { CreditService } from "./CreditService.js"; -import { CreditSystem } from "@autumn/shared"; - -export const creditsRouter = express.Router(); - -creditsRouter.get("", async (req: any, res) => { - let orgId = req.orgId; - - try { - let features = await getFeaturesByOrg(orgId); - let creditSystems = await CreditService.getByOrg(req.sb, orgId); - - res.status(200).json({ features, credit_systems: creditSystems }); - } catch (error: any) { - console.log("Error fetching features:", error); - res.status(500).json({ error: error.message }); - } -}); - -creditsRouter.post("/systems", async (req: any, res) => { - let orgId = req.orgId; - let data = req.body; - - try { - let creditSystem: CreditSystem = { - internal_id: generateId("cs"), - org_id: orgId, - created_at: Date.now(), - ...data, - }; - - await CreditService.insert(req.sb, creditSystem); - console.log("Successfully inserted credit system into DB"); - res.status(200).json({ message: "Credit system created" }); - } catch (error: any) { - console.log("Error inserting credit system into DB:", error); - res.status(500).json({ error: error.message }); - } -}); diff --git a/server/src/internal/customers/add-product/handleAddProduct.ts b/server/src/internal/customers/add-product/handleAddProduct.ts index a9591e4f7..3893474ea 100644 --- a/server/src/internal/customers/add-product/handleAddProduct.ts +++ b/server/src/internal/customers/add-product/handleAddProduct.ts @@ -48,14 +48,6 @@ import { SuccessCode } from "@autumn/shared"; import { getStripeSubs } from "@/external/stripe/stripeSubUtils.js"; import { getInvoiceItems } from "../invoices/invoiceUtils.js"; -import { sendSvixEvent } from "@/external/svix/svixUtils.js"; -import { getCustomerDetails } from "@/internal/api/customers/getCustomerDetails.js"; -import { addTaskToQueue } from "@/queue/queueUtils.js"; -import { JobName } from "@/queue/JobName.js"; -import { - constructProductsUpdatedData, - ProductsUpdatedDataSchema, -} from "@/external/svix/handleProductsUpdatedWebhook.js"; export const handleBillNowPrices = async ({ sb, @@ -98,7 +90,7 @@ export const handleBillNowPrices = async ({ let mergeCusProduct = !disableMerge && !freeTrial && org.config.merge_billing_cycles ? cusProducts?.find((cp) => - products.some((p) => p.group == cp.product.group) + products.some((p) => p.group == cp.product.group), ) : undefined; @@ -113,7 +105,7 @@ export const handleBillNowPrices = async ({ } let mergeWithSub = mergeSubs.find( - (sub) => subToAutumnInterval(sub) == itemSet.interval + (sub) => subToAutumnInterval(sub) == itemSet.interval, ); let subscription; @@ -126,7 +118,7 @@ export const handleBillNowPrices = async ({ if (attachParams.billingAnchor) { billingCycleAnchorUnix = getAlignedIntervalUnix( attachParams.billingAnchor, - itemSet.interval + itemSet.interval, ); } @@ -192,7 +184,7 @@ export const handleBillNowPrices = async ({ : undefined, carryExistingUsages, scenario: AttachScenario.New, - }) + }), ); } await Promise.all(batchInsert); @@ -250,7 +242,7 @@ export const handleBillNowPrices = async ({ customer_id: customer.id || customer.internal_id, invoice: invoiceOnly ? invoices?.[0] : undefined, - }) + }), ); } else { res.status(200).json({ @@ -338,8 +330,8 @@ export const handleOneOffPrices = async ({ billingType == BillingType.UsageInAdvance ? UsageModel.Prepaid : price.config?.type == PriceType.Usage - ? UsageModel.PayPerUse - : null, + ? UsageModel.PayPerUse + : null, feature_name: entitlement?.feature.name, }; } @@ -390,7 +382,7 @@ export const handleOneOffPrices = async ({ if (!attachParams.invoiceOnly) { stripeInvoice = await stripeCli.invoices.finalizeInvoice( stripeInvoice.id, - getInvoiceExpansion() + getInvoiceExpansion(), ); logger.info(" 3. Paying invoice"); @@ -428,7 +420,7 @@ export const handleOneOffPrices = async ({ sb, attachParams: attachToInsertParams(attachParams, product), lastInvoiceId: stripeInvoice.id, - }) + }), ); } await Promise.all(batchInsert); @@ -460,7 +452,7 @@ export const handleOneOffPrices = async ({ product_ids: products.map((p) => p.id), customer_id: customer.id || customer.internal_id, scenario: AttachScenario.New, - }) + }), ); } }; @@ -492,14 +484,14 @@ export const handleAddProduct = async ({ if (product.is_add_on) { logger.info( `Adding add-on ${chalk.yellowBright( - product.name - )} to customer ${chalk.yellowBright(customer.id)}` + product.name, + )} to customer ${chalk.yellowBright(customer.id)}`, ); } else { logger.info( `Adding product ${chalk.yellowBright( - product.name - )} to customer ${chalk.yellowBright(customer.id)}` + product.name, + )} to customer ${chalk.yellowBright(customer.id)}`, ); } } @@ -545,7 +537,7 @@ export const handleAddProduct = async ({ billLaterOnly: true, carryExistingUsages, keepResetIntervals, - }) + }), ); } await Promise.all(batchInsert); @@ -564,7 +556,7 @@ export const handleAddProduct = async ({ .join(", ")}`, product_ids: products.map((p) => p.id), customer_id: customer.id, - }) + }), ); } else { res.status(200).json({ diff --git a/server/src/internal/customers/products/cusProductUtils.ts b/server/src/internal/customers/products/cusProductUtils.ts index 81b848cf3..abf74e82c 100644 --- a/server/src/internal/customers/products/cusProductUtils.ts +++ b/server/src/internal/customers/products/cusProductUtils.ts @@ -448,7 +448,7 @@ export const processFullCusProduct = ({ return { tiers: tiers, - name: price.name, + name: "", quantity: priceOptions?.quantity, }; } @@ -487,6 +487,13 @@ export const processFullCusProduct = ({ } if (apiVersion >= APIVersion.v1_1) { + if ((!subIds || subIds.length == 0) && trialing) { + stripeSubData = { + current_period_start: cusProduct.starts_at, + current_period_end: cusProduct.trial_ends_at, + }; + } + return CusProductResponseSchema.parse({ id: cusProduct.product.id, name: cusProduct.product.name, diff --git a/server/src/internal/dev/ApiKeyService.ts b/server/src/internal/dev/ApiKeyService.ts index 05eb08a8a..2d6356de9 100644 --- a/server/src/internal/dev/ApiKeyService.ts +++ b/server/src/internal/dev/ApiKeyService.ts @@ -2,8 +2,9 @@ import { CacheType } from "@/external/caching/cacheActions.js"; import { CacheManager } from "@/external/caching/CacheManager.js"; import { getAPIKeyCache } from "@/external/caching/cacheUtils.js"; import { sbWithRetry } from "@/external/supabaseUtils.js"; +import { getApiVersion } from "@/utils/versionUtils.js"; -import { ApiKey, AppEnv, ErrCode } from "@autumn/shared"; +import { ApiKey, AppEnv, ErrCode, OrgConfigSchema } from "@autumn/shared"; import { SupabaseClient } from "@supabase/supabase-js"; export class ApiKeyService { @@ -31,8 +32,14 @@ export class ApiKeyService { } let org = structuredClone(data.organization); - delete org.features; + + // Add org config and api version + org.config = OrgConfigSchema.parse(org.config || {}); + org.api_version = getApiVersion({ + createdAt: org.created_at, + }); + return { org, features: data.organization?.features || [], @@ -133,7 +140,7 @@ export class CachedKeyService { }); } catch (error) { console.error( - `(warning) failed to clear cache for verify action: ${error}` + `(warning) failed to clear cache for verify action: ${error}`, ); } } diff --git a/server/src/internal/features/FeatureService.ts b/server/src/internal/features/FeatureService.ts index 44001f709..3f42102c4 100644 --- a/server/src/internal/features/FeatureService.ts +++ b/server/src/internal/features/FeatureService.ts @@ -1,6 +1,12 @@ import { ErrCode } from "@/errors/errCodes.js"; import RecaseError from "@/utils/errorUtils.js"; -import { AppEnv, CreditSchemaItem, Feature, FeatureType } from "@autumn/shared"; +import { + AppEnv, + CreditSchemaItem, + Feature, + features, + FeatureType, +} from "@autumn/shared"; import { SupabaseClient } from "@supabase/supabase-js"; import { Client } from "pg"; import { creditSystemContainsFeature } from "./creditSystemUtils.js"; @@ -8,6 +14,25 @@ import { clearOrgCache } from "../orgs/orgUtils/clearOrgCache.js"; import { DrizzleCli } from "@/db/initDrizzle.js"; export class FeatureService { + static async list({ + db, + orgId, + env, + }: { + db: DrizzleCli; + orgId: string; + env: AppEnv; + }) { + const features = await db.query.features.findMany({ + where: (features, { eq, and }) => + and(eq(features.org_id, orgId), eq(features.env, env)), + + orderBy: (features, { desc }) => [desc(features.internal_id)], + }); + + return features as Feature[]; + } + static async getFromReq(req: any) { if (req.features) return req.features as Feature[]; const features = await FeatureService.getFeatures({ @@ -123,6 +148,7 @@ export class FeatureService { return data; } + static async updateStrict({ db, sb, @@ -177,23 +203,33 @@ export class FeatureService { } static async insert({ - sb, db, data, logger, }: { - sb: SupabaseClient; db: DrizzleCli; data: Feature[] | Feature; logger: any; }) { // Insert feature into DB - let { data: insertedData, error } = await sb - .from("features") - .insert(data) - .select(); + // let { data: insertedData, error } = - if (error) { + try { + let insertedData = await db + .insert(features) + .values(data as any) // DRIZZLE TYPE REFACTOR + .returning(); + + if (insertedData && insertedData.length > 0) { + let orgId = insertedData[0].org_id; + await clearOrgCache({ + db, + orgId: orgId!, + logger, + }); + } + return insertedData; + } catch (error: any) { if (error.code === "23505") { let id = Array.isArray(data) ? data.map((f) => f.id) : data.id; throw new RecaseError({ @@ -202,19 +238,7 @@ export class FeatureService { statusCode: 400, }); } - throw error; } - - if (insertedData && insertedData.length > 0) { - let orgId = insertedData[0].org_id; - await clearOrgCache({ - db, - orgId, - logger, - }); - } - - return insertedData; } static async deleteStrict({ diff --git a/server/src/internal/features/creditSystemUtils.ts b/server/src/internal/features/creditSystemUtils.ts index 631c8da61..ddb8607d3 100644 --- a/server/src/internal/features/creditSystemUtils.ts +++ b/server/src/internal/features/creditSystemUtils.ts @@ -1,5 +1,4 @@ -import RecaseError from "@/utils/errorUtils.js"; -import { CreditSchemaItem, ErrCode, FeatureType } from "@autumn/shared"; +import { CreditSchemaItem, FeatureType } from "@autumn/shared"; import { Feature } from "@autumn/shared"; import { Decimal } from "decimal.js"; @@ -36,7 +35,7 @@ export const getCreditSystemsFromFeature = ({ creditSystemContainsFeature({ creditSystem: f, meteredFeatureId: featureId, - }) + }), ); }; diff --git a/server/src/internal/features/featureUtils.ts b/server/src/internal/features/featureUtils.ts index 03ec89091..eec164656 100644 --- a/server/src/internal/features/featureUtils.ts +++ b/server/src/internal/features/featureUtils.ts @@ -142,69 +142,6 @@ export const getObjectsUsingFeature = async ({ return { entitlements, prices, creditSystems, linkedEntitlements }; }; -export const constructBooleanFeature = ({ - featureId, - orgId, - env, -}: { - featureId: string; - orgId: string; - env: AppEnv; -}) => { - let newFeature: Feature = { - internal_id: generateId("fe"), - org_id: orgId, - env, - created_at: Date.now(), - - id: featureId, - name: keyToTitle(featureId), - type: FeatureType.Boolean, - config: null, - }; - - return newFeature; -}; - -export const constructMeteredFeature = ({ - featureId, - orgId, - env, - usageType, -}: { - featureId: string; - orgId: string; - env: AppEnv; - usageType: FeatureUsageType; -}) => { - let newFeature: Feature = { - internal_id: generateId("fe"), - org_id: orgId, - env, - created_at: Date.now(), - - id: featureId, - name: keyToTitle(featureId), - type: FeatureType.Metered, - config: { - filters: [ - { - property: "event_name", - operator: "eq", - value: [], - }, - ], - aggregate: { - type: AggregateType.Sum, - property: "value", - }, - usage_type: usageType, - }, - }; - - return newFeature; -}; - export const runSaveFeatureDisplayTask = async ({ db, sb, diff --git a/server/src/internal/features/utils/constructFeatureUtils.ts b/server/src/internal/features/utils/constructFeatureUtils.ts new file mode 100644 index 000000000..0a8be2ce9 --- /dev/null +++ b/server/src/internal/features/utils/constructFeatureUtils.ts @@ -0,0 +1,100 @@ +import { AggregateType } from "@autumn/shared"; + +import { AppEnv, FeatureType, FeatureUsageType } from "@autumn/shared"; + +import { generateId, keyToTitle } from "@/utils/genUtils.js"; +import { Feature } from "@autumn/shared"; + +export const constructFeature = ({ + id, + name, + orgId, + type, + env, + config, + display, +}: { + id: string; + name: string; + orgId: string; + type: FeatureType; + env: AppEnv; + config: any; + display: any; +}) => { + let newFeature: Feature = { + internal_id: generateId("fe"), + id, + name, + org_id: orgId, + env, + created_at: Date.now(), + type, + config, + display, + }; + + return newFeature; +}; +export const constructBooleanFeature = ({ + featureId, + orgId, + env, +}: { + featureId: string; + orgId: string; + env: AppEnv; +}) => { + let newFeature: Feature = { + internal_id: generateId("fe"), + org_id: orgId, + env, + created_at: Date.now(), + + id: featureId, + name: keyToTitle(featureId), + type: FeatureType.Boolean, + config: null, + }; + + return newFeature; +}; + +export const constructMeteredFeature = ({ + featureId, + orgId, + env, + usageType, +}: { + featureId: string; + orgId: string; + env: AppEnv; + usageType: FeatureUsageType; +}) => { + let newFeature: Feature = { + internal_id: generateId("fe"), + org_id: orgId, + env, + created_at: Date.now(), + + id: featureId, + name: keyToTitle(featureId), + type: FeatureType.Metered, + config: { + filters: [ + { + property: "event_name", + operator: "eq", + value: [], + }, + ], + aggregate: { + type: AggregateType.Sum, + property: "value", + }, + usage_type: usageType, + }, + }; + + return newFeature; +}; diff --git a/server/src/internal/mainRouter.ts b/server/src/internal/mainRouter.ts index 1b5c538e6..a439c5817 100644 --- a/server/src/internal/mainRouter.ts +++ b/server/src/internal/mainRouter.ts @@ -1,81 +1,32 @@ import dotenv from "dotenv"; dotenv.config(); + import { orgRouter } from "./orgs/orgRouter.js"; import { Router } from "express"; import { userRouter } from "./users/userRouter.js"; import { withAuth, withOrgAuth } from "../middleware/authMiddleware.js"; import { featureRouter } from "./features/featureRouter.js"; -import { creditsRouter } from "./credits/creditsRouter.js"; import { productRouter } from "./products/internalProductRouter.js"; import { devRouter } from "./dev/devRouter.js"; import { cusRouter } from "./customers/internalCusRouter.js"; import { testRouter } from "./test/testRouter.js"; -import { createClerkCli } from "../external/clerkUtils.js"; -import { ErrCode } from "@autumn/shared"; -import RecaseError, { handleRequestError } from "@/utils/errorUtils.js"; - -import { StatusCodes } from "http-status-codes"; -import { handleOrgCreated } from "@/external/webhooks/clerkWebhooks.js"; import { autumnHandler } from "autumn-js/express"; import { onboardingRouter } from "./orgs/onboarding/onboardingRouter.js"; +import { handlePostOrg } from "./orgs/handlers/handlePostOrg.js"; + const mainRouter = Router(); mainRouter.get("", async (req: any, res) => { res.status(200).json({ message: "Hello World" }); }); -mainRouter.use("/onboarding", withAuth, onboardingRouter); - -mainRouter.post("/organization", withAuth, async (req: any, res) => { - try { - let { db } = req; - let { orgId } = req.body; - - let userId = req.userId; - let clerk = createClerkCli(); - - let org = await clerk.organizations.getOrganization({ - organizationId: orgId, - }); - - if (!org) { - throw new RecaseError({ - message: "Organization not found", - code: ErrCode.OrgNotFound, - statusCode: StatusCodes.NOT_FOUND, - }); - } - - const memberships = await clerk.users.getOrganizationMembershipList({ - userId: userId, - }); - - let userIsAdmin = memberships.data.some((m) => m.organization.id === orgId); - - if (!userIsAdmin) { - throw new RecaseError({ - message: "User is not an admin of this organization", - code: ErrCode.OrgNotFound, - statusCode: StatusCodes.NOT_FOUND, - }); - } - - await handleOrgCreated(db, { - id: orgId, - slug: org.slug || "", - created_at: Date.now(), - }), - res.status(200).json({ message: "Success" }); - } catch (error: any) { - handleRequestError({ error, req, res, action: "create org" }); - } -}); - +mainRouter.post("/organization", withAuth, handlePostOrg); mainRouter.use("/users", withAuth, userRouter); + +mainRouter.use("/onboarding", withOrgAuth, onboardingRouter); mainRouter.use("/organization", withOrgAuth, orgRouter); mainRouter.use("/features", withOrgAuth, featureRouter); -mainRouter.use("/credits", withOrgAuth, creditsRouter); mainRouter.use("/products", withOrgAuth, productRouter); mainRouter.use("/dev", devRouter); mainRouter.use("/customers", withOrgAuth, cusRouter); @@ -85,17 +36,15 @@ mainRouter.use( "/api/autumn", withOrgAuth, (req: any, res: any, next: any) => { - console.log("Autumn middleware:", req.originalUrl); + // console.log("Autumn middleware:", req.originalUrl); next(); }, autumnHandler({ identify: async (req: any) => { - console.log("Org:", req.minOrg); - console.log("User:", req.user); return { - customerId: req.minOrg?.id, + customerId: req.org?.id, customerData: { - name: req.minOrg?.slug, + name: req.org?.slug, email: req.user?.email, }, }; diff --git a/server/src/internal/orgs/OrgService.ts b/server/src/internal/orgs/OrgService.ts index 5e79b82bc..8a15d1d96 100644 --- a/server/src/internal/orgs/OrgService.ts +++ b/server/src/internal/orgs/OrgService.ts @@ -4,10 +4,33 @@ import { SupabaseClient } from "@supabase/supabase-js"; import { getApiVersion } from "@/utils/versionUtils.js"; import { clearOrgCache } from "./orgUtils/clearOrgCache.js"; import { DrizzleCli } from "@/db/initDrizzle.js"; -import { organizations, apiKeys } from "@/db/schema/index.js"; import { eq } from "drizzle-orm"; +import { organizations, apiKeys } from "@autumn/shared"; export class OrgService { + // Drizzle get + static async get({ db, orgId }: { db: DrizzleCli; orgId: string }) { + const result = await db.query.organizations.findFirst({ + where: eq(organizations.id, orgId), + }); + + if (!result) { + throw new RecaseError({ + message: "Organization not found", + code: ErrCode.OrgNotFound, + statusCode: 404, + }); + } + + return { + ...result, + config: OrgConfigSchema.parse(result.config || {}), + api_version: getApiVersion({ + createdAt: result.created_at!, + }), + }; + } + static async getWithKeys({ db, orgId, @@ -96,6 +119,7 @@ export class OrgService { return data; } + static async getFromReq(req: any) { if (req.org) { let org = structuredClone(req.org); @@ -194,17 +218,22 @@ export class OrgService { orgId: string; updates: any; }) { - let result = await db - .update(organizations) - .set(updates) - .where(eq(organizations.id, orgId)) - .returning(); + try { + let result = await db + .update(organizations) + .set(updates) + .where(eq(organizations.id, orgId)) + .returning(); - await clearOrgCache({ - db, - orgId, - }); + await clearOrgCache({ + db, + orgId, + }); - return result.length > 0 ? result[0] : null; + return result.length > 0 ? result[0] : null; + } catch (error) { + console.error(error); + throw error; + } } } diff --git a/server/src/internal/orgs/handlers/handlePostOrg.ts b/server/src/internal/orgs/handlers/handlePostOrg.ts new file mode 100644 index 000000000..8d7c5ebf2 --- /dev/null +++ b/server/src/internal/orgs/handlers/handlePostOrg.ts @@ -0,0 +1,52 @@ +import { createClerkCli } from "@/external/clerkUtils.js"; +import { saveOrgToDB } from "@/external/webhooks/clerkWebhooks.js"; + +import { Request, Response } from "@/utils/models/Request.js"; +import { routeHandler } from "@/utils/routerUtils.js"; + +export const handlePostOrg = async (req: any, res: any) => + routeHandler({ + req, + res, + action: "create org", + handler: async (req: Request, res: Response) => { + const { userId, db } = req; + + const clerk = createClerkCli(); + const user = await clerk.users.getUser(userId!); + + let userMemberships = await clerk.users.getOrganizationMembershipList({ + userId: userId!, + }); + + let org; + + if (userMemberships.data.length === 0) { + org = await clerk.organizations.createOrganization({ + name: `${user.firstName}'s Org`, + }); + + // 2. Create org membership for user + await clerk.organizations.createOrganizationMembership({ + organizationId: org.id, + userId: userId!, + role: "org:admin", + }); + + await saveOrgToDB({ + db, + id: org.id, + slug: org.slug, + }); + + console.log(`Created new org: ${org.id} (${org.slug})`); + } else { + org = userMemberships.data[0].organization; + } + + res.status(200).json({ + id: org.id, + slug: org.slug, + }); + }, + }); diff --git a/server/src/internal/orgs/onboarding/createOnboardingProducts.ts b/server/src/internal/orgs/onboarding/createOnboardingProducts.ts index 3c4bcc373..6807a8f0f 100644 --- a/server/src/internal/orgs/onboarding/createOnboardingProducts.ts +++ b/server/src/internal/orgs/onboarding/createOnboardingProducts.ts @@ -1,5 +1,4 @@ import { DrizzleCli } from "@/db/initDrizzle.js"; -import { entitlements, prices, features, products } from "@/db/schema/index.js"; import { generateId, keyToTitle } from "@/utils/genUtils.js"; import { FeatureType, @@ -9,6 +8,12 @@ import { AllowanceType, PriceType, BillingInterval, + + // DB Models + entitlements, + prices, + features, + products, } from "@autumn/shared"; import { AppEnv } from "autumn-js"; diff --git a/server/src/internal/orgs/onboarding/onboardingRouter.ts b/server/src/internal/orgs/onboarding/onboardingRouter.ts index e0d4718c5..883960046 100644 --- a/server/src/internal/orgs/onboarding/onboardingRouter.ts +++ b/server/src/internal/orgs/onboarding/onboardingRouter.ts @@ -1,15 +1,18 @@ import { Router } from "express"; import { Request } from "@/utils/models/Request.js"; -import { chatResults } from "@/db/schema/index.js"; + import { eq } from "drizzle-orm"; import { routeHandler } from "@/utils/routerUtils.js"; import RecaseError from "@/utils/errorUtils.js"; import { createClerkCli } from "@/external/clerkUtils.js"; -import { handleOrgCreated } from "@/external/webhooks/clerkWebhooks.js"; -import { OrgService } from "../OrgService.js"; import { AppEnv } from "@autumn/shared"; import { parseChatResultFeatures } from "./parseChatFeatures.js"; import { parseChatProducts } from "./parseChatProducts.js"; +import { chatResults } from "@autumn/shared"; +import { ProductService } from "@/internal/products/ProductService.js"; +import { FeatureService } from "@/internal/features/FeatureService.js"; +import { EntitlementService } from "@/internal/products/entitlements/EntitlementService.js"; +import { PriceService } from "@/internal/prices/PriceService.js"; export const onboardingRouter = Router(); @@ -19,7 +22,7 @@ onboardingRouter.post("", async (req: Request, res: any) => res, action: "onboarding", handler: async (req: Request, res: any) => { - const { db, userId } = req; + const { db, sb, logtail: logger, org } = req; const { token } = req.body; if (!token) { @@ -30,7 +33,6 @@ onboardingRouter.post("", async (req: Request, res: any) => }); } - // 1. Get chat result let chatResult = await db.query.chatResults.findFirst({ where: eq(chatResults.id, token), }); @@ -43,49 +45,69 @@ onboardingRouter.post("", async (req: Request, res: any) => }); } - let clerk = createClerkCli(); - let user = await clerk.users.getUser(userId!); - - // console.log(`Creating default org for user ${user.id}`); - // let org = await clerk.organizations.createOrganization({ - // name: `${user.firstName}'s Org`, - // }); - - // // 2. Create org membership for user - // await clerk.organizations.createOrganizationMembership({ - // organizationId: org.id, - // userId: userId!, - // role: "org:admin", - // }); - - // // 3. Create org in db - // await handleOrgCreated(db, { - // id: org.id, - // slug: org.slug, - // created_at: org.createdAt, - // }); - - // 4. Create new products - let features = chatResult?.data.features; - let products = chatResult?.data.products; - - let backendFeatures = parseChatResultFeatures(features); - let backendProducts = parseChatProducts({ - features: backendFeatures, - chatProducts: products, + let curProducts = await ProductService.getFullProducts({ + sb, + orgId: org.id, + env: AppEnv.Sandbox, }); - console.log(backendFeatures); + let curFeatures = await FeatureService.list({ + db, + orgId: org.id, + env: AppEnv.Sandbox, + }); - // for (const feature of features) { - // await ProductService.create({ - // sb, - // product: feature, - // }); - // } + let newProducts = chatResult.data.products.filter((product) => { + return !curProducts.some((p) => p.id === product.id); + }); + + let newFeatures = chatResult.data.features.filter((feature) => { + return !curFeatures.some((f) => f.id === feature.id); + }); + + if (newFeatures.length > 0 || newProducts.length > 0) { + let backendFeatures = parseChatResultFeatures({ + features: newFeatures, + orgId: org.id, + }); + + let { products, prices, ents } = await parseChatProducts({ + db, + sb, + logger, + orgId: org.id, + features: [...curFeatures, ...backendFeatures], + chatProducts: newProducts, + }); + + await Promise.all([ + FeatureService.insert({ + db, + data: backendFeatures, + logger, + }), + (async () => { + for (const product of products) { + await ProductService.create({ sb, product }); + } + })(), + ]); + + await EntitlementService.insert({ + sb, + data: ents, + }); + + await PriceService.insert({ + sb, + data: prices, + }); + } res.status(200).json({ - message: "Onboarding successful", + org_id: org.id, + feature_ids: chatResult.data.features.map((f) => f.id), + product_ids: chatResult.data.products.map((p) => p.id), }); }, }), diff --git a/server/src/internal/orgs/onboarding/parseChatFeatures.ts b/server/src/internal/orgs/onboarding/parseChatFeatures.ts index b5e2de9b8..f86ca5a0f 100644 --- a/server/src/internal/orgs/onboarding/parseChatFeatures.ts +++ b/server/src/internal/orgs/onboarding/parseChatFeatures.ts @@ -1,10 +1,12 @@ import { validateMeteredConfig } from "@/internal/features/featureUtils.js"; +import { constructFeature } from "@/internal/features/utils/constructFeatureUtils.js"; import RecaseError from "@/utils/errorUtils.js"; +import { keyToTitle } from "@/utils/genUtils.js"; import { AggregateType, + AppEnv, ChatFeatureCreditSchema, ChatResultFeature, - Feature, FeatureType, FeatureUsageType, MeteredConfig, @@ -46,7 +48,13 @@ const validateFeatures = (features: ChatResultFeature[]) => { }); }; -export const parseChatResultFeatures = (features: ChatResultFeature[]) => { +export const parseChatResultFeatures = ({ + features, + orgId, +}: { + features: ChatResultFeature[]; + orgId: string; +}) => { validateFeatures(features); return features.map((feature) => { @@ -75,13 +83,15 @@ export const parseChatResultFeatures = (features: ChatResultFeature[]) => { }); } - let backendFeat: Feature = { + let backendFeat = constructFeature({ id: feature.id, - name: feature.name, - type: type, + name: keyToTitle(feature.id), + type, + env: AppEnv.Sandbox, + config, + orgId: orgId, display: feature.display, - config: config, - }; + }); return backendFeat; }); diff --git a/server/src/internal/orgs/onboarding/parseChatProducts.ts b/server/src/internal/orgs/onboarding/parseChatProducts.ts index 9084cefbc..b01c0a5ab 100644 --- a/server/src/internal/orgs/onboarding/parseChatProducts.ts +++ b/server/src/internal/orgs/onboarding/parseChatProducts.ts @@ -1,27 +1,71 @@ +import { DrizzleCli } from "@/db/initDrizzle.js"; +import { handleNewProductItems } from "@/internal/products/product-items/productItemInitUtils.js"; import { constructProduct } from "@/internal/products/productUtils.js"; import { generateId } from "@/utils/genUtils.js"; -import { Feature, Product, ProductV2 } from "@autumn/shared"; +import { + AppEnv, + CreateProductSchema, + EntInsertSchema, + Entitlement, + Feature, + Price, + Product, + ProductV2, +} from "@autumn/shared"; +import { SupabaseClient } from "@supabase/supabase-js"; +import { z } from "zod"; -export const parseChatProducts = ({ +export const parseChatProducts = async ({ + db, + sb, + logger, features, + orgId, chatProducts, }: { + db: DrizzleCli; + sb: SupabaseClient; + logger: any; features: Feature[]; + orgId: string; chatProducts: ProductV2[]; }) => { - let products: ProductV2[] = []; + let products: Product[] = []; + + let allPrices: Price[] = []; + let allEnts: Entitlement[] = []; for (const product of chatProducts) { - let backendPro: Product = constructProduct({ - productData: { - id: product.id, - name: product.name, - is_add_on: product.is_add_on, - is_default: product.is_default, - }, - orgId: orgId, - env: env, + let backendProduct: Product = constructProduct({ + productData: CreateProductSchema.parse({ + ...product, + }), + orgId, + env: AppEnv.Sandbox, }); - products.push(); + + let { prices, entitlements } = await handleNewProductItems({ + db, + sb, + curPrices: [], + curEnts: [], + newItems: product.items, + product: backendProduct, + features, + saveToDb: false, + isCustom: false, + logger, + }); + + products.push(backendProduct); + allPrices.push(...prices); + + allEnts.push( + ...entitlements.map((ent) => { + return EntInsertSchema.parse(ent) as unknown as Entitlement; + }), + ); } + + return { products, prices: allPrices, ents: allEnts }; }; diff --git a/server/src/internal/orgs/orgUtils/clearOrgCache.ts b/server/src/internal/orgs/orgUtils/clearOrgCache.ts index 5bc25333c..e96636d96 100644 --- a/server/src/internal/orgs/orgUtils/clearOrgCache.ts +++ b/server/src/internal/orgs/orgUtils/clearOrgCache.ts @@ -3,7 +3,6 @@ import { OrgService } from "../OrgService.js"; import { CacheManager } from "@/external/caching/CacheManager.js"; import { CacheType } from "@/external/caching/cacheActions.js"; import { DrizzleCli } from "@/db/initDrizzle.js"; -import { ApiKey } from "@/db/schema/tables/apiKeysTable.js"; export const clearOrgCache = async ({ // sb, @@ -30,7 +29,7 @@ export const clearOrgCache = async ({ return; } - let secretKeys = org.api_keys.map((key: ApiKey) => key.hashed_key); + let secretKeys = org.api_keys.map((key: any) => key.hashed_key); let publicKeys = [org.test_pkey, org.live_pkey]; let batchDelete = []; diff --git a/server/src/internal/prices/priceInitUtils.ts b/server/src/internal/prices/priceInitUtils.ts index ed880fd01..4aea3952d 100644 --- a/server/src/internal/prices/priceInitUtils.ts +++ b/server/src/internal/prices/priceInitUtils.ts @@ -65,7 +65,7 @@ export const constructPrice = ({ // GET PRICES const validatePrice = ( price: Price, - relatedEnt?: Entitlement | undefined | null + relatedEnt?: Entitlement | undefined | null, ) => { if (!price.config?.type) { throw new RecaseError({ @@ -138,7 +138,7 @@ export const tiersAreSame = (tiers1: any[], tiers2: any[]) => { export const pricesAreSame = ( price1: Price, price2: Price, - logDifferences = false + logDifferences = false, ) => { // if (price1.name !== price2.name) return false; @@ -170,7 +170,7 @@ export const pricesAreSame = ( "Differences:", Object.values(diffs) .filter((d) => d.condition) - .map((d) => d.message) + .map((d) => d.message), ); } @@ -208,10 +208,10 @@ export const pricesAreSame = ( usage_tiers: { condition: !tiersAreSame( usageConfig1.usage_tiers, - usageConfig2.usage_tiers + usageConfig2.usage_tiers, ), message: `Usage tiers different: ${usageConfig1.usage_tiers.map( - (t) => `${t.to} (${t.amount})` + (t) => `${t.to} (${t.amount})`, )} !== ${usageConfig2.usage_tiers.map((t) => `${t.to} (${t.amount})`)}`, }, // stripe_price_id: { @@ -245,7 +245,7 @@ export const pricesAreSame = ( "Differences:", Object.values(diffs) .filter((d) => d.condition) - .map((d) => d.message) + .map((d) => d.message), ); } @@ -328,7 +328,7 @@ export const handleNewPrices = async ({ const feature = features.find((f) => f.id === config.feature_id); if (!feature) { throw new RecaseError({ - message: `Feature ${config.feature_id} not found for price ${price.name}`, + message: `Feature ${config.feature_id} not found for price (autumn id: ${price.id})`, code: ErrCode.FeatureNotFound, statusCode: 400, }); @@ -345,7 +345,7 @@ export const handleNewPrices = async ({ // 1. Deleted entitlements: filter out entitlements that are not in newEnts const removedPrices: Price[] = curPrices.filter( - (price) => !newPrices.some((p: Price) => p.id === price.id) + (price) => !newPrices.some((p: Price) => p.id === price.id), ); const createdPrices: Price[] = []; @@ -356,7 +356,7 @@ export const handleNewPrices = async ({ const relatedEnt = getPriceEntitlement( newPrice, - entitlements as EntitlementWithFeature[] + entitlements as EntitlementWithFeature[], ); validatePrice(newPrice, relatedEnt); @@ -370,7 +370,7 @@ export const handleNewPrices = async ({ orgId, internalProductId, isCustom, - }) + }), ); } @@ -391,7 +391,7 @@ export const handleNewPrices = async ({ internalProductId, isCustom, keepStripePrice: newVersion && pricesAreSame(curPrice, newPrice), - }) + }), ); removedPrices.push(curPrice); } @@ -421,7 +421,7 @@ export const handleNewPrices = async ({ if (product.is_default && !isCustom) { if ( createdPrices.some( - (p) => getBillingType(p.config!) == BillingType.UsageInAdvance + (p) => getBillingType(p.config!) == BillingType.UsageInAdvance, ) ) { throw new RecaseError({ @@ -455,6 +455,6 @@ export const handleNewPrices = async ({ } console.log( - `Successfully handled new prices. Created ${createdPrices.length}, updated ${updatedPrices.length}, removed ${removedPrices.length}` + `Successfully handled new prices. Created ${createdPrices.length}, updated ${updatedPrices.length}, removed ${removedPrices.length}`, ); }; diff --git a/server/src/internal/products/ProductService.ts b/server/src/internal/products/ProductService.ts index f135a53cf..53e6e2e02 100644 --- a/server/src/internal/products/ProductService.ts +++ b/server/src/internal/products/ProductService.ts @@ -82,7 +82,7 @@ export class ProductService { const { data, error } = await sb .from("products") .select( - "*, prices(*), entitlements(*, feature:features(*)), free_trial:free_trials(*)" + "*, prices(*), entitlements(*, feature:features(*)), free_trial:free_trials(*)", ) .eq("org_id", orgId) .eq("env", env) @@ -198,7 +198,7 @@ export class ProductService { ), prices(*), free_trial:free_trials(*) - ` + `, ) .eq("org_id", orgId) .eq("env", env) @@ -271,7 +271,7 @@ export class ProductService { feature:features (id, name, type) ), prices (*) - ` + `, ); if (productId) { @@ -448,7 +448,7 @@ export class ProductService { if (!data) { console.log( "ProductService.getByFeature returning no data, error:", - error + error, ); return []; } @@ -458,41 +458,3 @@ export class ProductService { return data; } } - -// static async deleteProduct({ -// sb, -// productId, -// orgId, -// env, -// }: { -// sb: SupabaseClient; -// productId: string; -// orgId: string; -// env: AppEnv; -// }) { -// const { error } = await sb -// .from("products") -// .delete() -// .eq("id", productId) -// .eq("org_id", orgId) -// .eq("env", env); - -// if (error) { -// throw error; -// } -// } - -// UNUSED -// static async getProducts(sb: SupabaseClient, orgId: string, env: AppEnv) { -// const { data, error } = await sb -// .from("products") -// .select("*") -// .eq("org_id", orgId) -// .eq("env", env); - -// if (error) { -// throw error; -// } - -// return data; -// } diff --git a/server/src/internal/products/internalProductRouter.ts b/server/src/internal/products/internalProductRouter.ts index be3228a02..6330d0540 100644 --- a/server/src/internal/products/internalProductRouter.ts +++ b/server/src/internal/products/internalProductRouter.ts @@ -97,7 +97,7 @@ productRouter.get("/counts", async (req: any, res) => { sb, internalProductId: product.internal_id, }); - }) + }), ); // let result: { [key: string]: any } = {}; diff --git a/server/src/internal/products/product-items/createFeaturesFromItems.ts b/server/src/internal/products/product-items/createFeaturesFromItems.ts index db755894f..a7d930e1f 100644 --- a/server/src/internal/products/product-items/createFeaturesFromItems.ts +++ b/server/src/internal/products/product-items/createFeaturesFromItems.ts @@ -9,11 +9,12 @@ import { } from "@autumn/shared"; import { SupabaseClient } from "@supabase/supabase-js"; import { isBooleanFeatureItem } from "./getItemType.js"; +import { validateFeatureId } from "@/internal/features/featureUtils.js"; import { constructBooleanFeature, constructMeteredFeature, - validateFeatureId, -} from "@/internal/features/featureUtils.js"; +} from "@/internal/features/utils/constructFeatureUtils.js"; + import RecaseError from "@/utils/errorUtils.js"; import { nullish } from "@/utils/genUtils.js"; diff --git a/server/src/internal/products/product-items/mapFromItem.ts b/server/src/internal/products/product-items/mapFromItem.ts index e474f661e..1fc4c0cac 100644 --- a/server/src/internal/products/product-items/mapFromItem.ts +++ b/server/src/internal/products/product-items/mapFromItem.ts @@ -56,7 +56,6 @@ export const toPrice = ({ org_id: orgId, internal_product_id: internalProductId, is_custom: isCustom, - name: "", config, }; @@ -106,8 +105,8 @@ export const toFeature = ({ allowance_type: isBoolean ? null : item.included_usage == Infinite - ? AllowanceType.Unlimited - : AllowanceType.Fixed, + ? AllowanceType.Unlimited + : AllowanceType.Fixed, interval: isBoolean ? null : (itemToEntInterval(item) as EntInterval), @@ -206,8 +205,6 @@ export const toFeatureAndPrice = ({ org_id: orgId, internal_product_id: internalProductId, is_custom: isCustom, - name: "", - config, entitlement_id: ent.id, }; diff --git a/server/src/internal/products/product-items/productItemInitUtils.ts b/server/src/internal/products/product-items/productItemInitUtils.ts index d85e2b33d..69161bb7a 100644 --- a/server/src/internal/products/product-items/productItemInitUtils.ts +++ b/server/src/internal/products/product-items/productItemInitUtils.ts @@ -153,6 +153,7 @@ export const handleNewProductItems = async ({ logger, isCustom, newVersion, + saveToDb = true, }: { db: DrizzleCli; sb: SupabaseClient; @@ -164,9 +165,9 @@ export const handleNewProductItems = async ({ logger: any; isCustom: boolean; newVersion?: boolean; + saveToDb?: boolean; }) => { // Create features if not exist... - if (!newItems) { return { prices: [], @@ -175,7 +176,6 @@ export const handleNewProductItems = async ({ } // Validate product items... - let { allFeatures, newFeatures } = validateProductItems({ newItems, features, @@ -258,16 +258,15 @@ export const handleNewProductItems = async ({ `Ents: new(${newEnts.length}), updated(${updatedEnts.length}), deleted(${deletedEnts.length})`, ); - if (newFeatures.length > 0) { + if (newFeatures.length > 0 && saveToDb) { await FeatureService.insert({ db, - sb, data: newFeatures, logger, }); } - if (isCustom || newVersion) { + if ((isCustom || newVersion) && saveToDb) { return handleCustomProductItems({ sb, newPrices, @@ -280,15 +279,17 @@ export const handleNewProductItems = async ({ }); } - await updateDbPricesAndEnts({ - sb, - newPrices, - newEnts, - updatedPrices, - updatedEnts, - deletedPrices, - deletedEnts, - }); + if (saveToDb) { + await updateDbPricesAndEnts({ + sb, + newPrices, + newEnts, + updatedPrices, + updatedEnts, + deletedPrices, + deletedEnts, + }); + } return { prices: [...newPrices, ...updatedPrices], diff --git a/server/src/internal/products/productV2Utils.ts b/server/src/internal/products/productV2Utils.ts index 9de2b02b6..14f265243 100644 --- a/server/src/internal/products/productV2Utils.ts +++ b/server/src/internal/products/productV2Utils.ts @@ -44,7 +44,7 @@ export const mapToProductItems = ({ let relatedEnt = getPriceEntitlement( price, entitlements, - allowFeatureMatch + allowFeatureMatch, ); if (!relatedEnt) { diff --git a/server/src/middleware/pricingMiddleware.ts b/server/src/middleware/pricingMiddleware.ts index fd84431e3..64cae77b0 100644 --- a/server/src/middleware/pricingMiddleware.ts +++ b/server/src/middleware/pricingMiddleware.ts @@ -15,7 +15,7 @@ export const pricingMiddleware = async (req: any, res: any, next: any) => { try { if (path == "/products" && method == "POST") { await isEntitled({ - minOrg: req.minOrg, + org: req.org, env: req.env, featureId: FeatureId.Products, }); @@ -27,7 +27,7 @@ export const pricingMiddleware = async (req: any, res: any, next: any) => { if (path == "/products" && method === "POST") { console.log("sending product create event"); await sendProductEvent({ - minOrg: req.minOrg, + org: req.org, env: req.env, incrementBy: 1, }); @@ -36,7 +36,7 @@ export const pricingMiddleware = async (req: any, res: any, next: any) => { if (path.match(/^\/products\/[^\/]+$/) && method === "DELETE") { console.log("sending product delete event"); await sendProductEvent({ - minOrg: req.minOrg, + org: req.org, env: req.env, incrementBy: -1, }); diff --git a/server/src/utils/models/Request.ts b/server/src/utils/models/Request.ts index 1982d1de2..f21615036 100644 --- a/server/src/utils/models/Request.ts +++ b/server/src/utils/models/Request.ts @@ -1,15 +1,12 @@ import { AppEnv, Feature, MinOrg, Organization } from "@autumn/shared"; import { Logtail } from "@logtail/node"; import { SupabaseClient } from "@supabase/supabase-js"; -import { PostgresJsDatabase } from "drizzle-orm/postgres-js"; import type { Request as ExpressRequest, Response as ExpressResponse, } from "express"; import { Client } from "pg"; -import * as schema from "@/db/schema/index.js"; -import postgres from "postgres"; import { DrizzleCli } from "@/db/initDrizzle.js"; export interface Request extends ExpressRequest { diff --git a/server/tests/utils/setup.ts b/server/tests/utils/setup.ts index 5dd5b37f5..7bf18550a 100644 --- a/server/tests/utils/setup.ts +++ b/server/tests/utils/setup.ts @@ -5,6 +5,7 @@ import { Feature, FeatureType, FullProduct, + organizations, Price, PriceType, RewardProgram, @@ -25,6 +26,7 @@ import { CacheManager } from "@/external/caching/CacheManager.js"; import { CacheType } from "@/external/caching/cacheActions.js"; import { hashApiKey } from "@/internal/dev/api-keys/apiKeyUtils.js"; import { initDrizzle } from "@/db/initDrizzle.js"; +import { eq } from "drizzle-orm"; export const getAxiosInstance = ( apiKey: string = process.env.UNIT_TEST_AUTUMN_SECRET_KEY!, @@ -242,7 +244,8 @@ export const setupOrg = async ({ }) => { const axiosInstance = getAxiosInstance(); const sb = createSupabaseClient(); - const db = initDrizzle(); + const { client, db } = initDrizzle(); + const autumn = new Autumn(process.env.UNIT_TEST_AUTUMN_SECRET_KEY!); let insertFeatures = []; @@ -251,7 +254,7 @@ export const setupOrg = async ({ } await Promise.all(insertFeatures); - const org = await OrgService.getFullOrg({ sb, orgId }); + const org = await OrgService.get({ db, orgId }); await OrgService.update({ db, orgId, @@ -263,6 +266,8 @@ export const setupOrg = async ({ }, }); + await client.end(); + const { data: newFeatures } = await sb .from("features") .select("*") @@ -456,14 +461,4 @@ export const setupOrg = async ({ } await Promise.all(insertRewardTriggers); console.log("✅ Inserted reward triggers"); - - // Initialize stripe products - // // How to check if mocha is in parallel mode? - // if (process.env.MOCHA_PARALLEL) { - // console.log("MOCHA RUNNING IN PARALLEL"); - // await AutumnCli.initStripeProducts(); - // console.log("✅ Initialized stripe products / prices"); - // } else { - // console.log("MOCHA RUNNING IN SERIAL"); - // } }; diff --git a/server/tsconfig.json b/server/tsconfig.json index 326797002..e9b5abb4d 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -25,7 +25,22 @@ "@shared/*": ["../shared/*"] } }, - "include": ["src", "tests", "tests-old/utils.ts", "tests-old/global.ts"], + "include": [ + "src", + "tests", + "tests-old/utils.ts", + "tests-old/global.ts", + "../shared/db/allTables.ts", + "../shared/db/apiKeysTable.ts", + "../shared/db/chatResultsTable.ts", + "../shared/db/cusTable.ts", + "../shared/db/entitlementsTable.ts", + "../shared/db/featuresTable.ts", + "../shared/db/invoicesTable.ts", + "../shared/db/orgTable.ts", + "../shared/db/pricesTable.ts", + "../shared/db/productsTable.ts" + ], "references": [ { "path": "../shared" diff --git a/shared/models/orgModels/orgModels.ts b/shared/archives/orgModels_old.ts similarity index 72% rename from shared/models/orgModels/orgModels.ts rename to shared/archives/orgModels_old.ts index 919d4ae7c..9663443f1 100644 --- a/shared/models/orgModels/orgModels.ts +++ b/shared/archives/orgModels_old.ts @@ -1,6 +1,8 @@ +// Old File + import { z } from "zod"; -import { OrgConfigSchema } from "./orgConfigModels.js"; -import { APIVersion } from "../apiVersionEnum.js"; +import { OrgConfigSchema } from "../models/orgModels/orgConfig.js"; +import { APIVersion } from "../models/apiVersionEnum.js"; export const MinOrgSchema = z.object({ id: z.string(), @@ -20,20 +22,13 @@ export const SvixConfigSchema = z.object({ live_app_id: z.string(), }); -export const FrontendOrganizationSchema = z.object({ - id: z.string(), - slug: z.string(), - default_currency: z.string(), - stripe_connected: z.boolean().default(false), - created_at: z.number(), -}); - export const OrganizationSchema = z.object({ id: z.string(), slug: z.string(), - default_currency: z.string(), + default_currency: z.string().default("usd"), stripe_connected: z.boolean().default(false), stripe_config: StripeConfigSchema.optional().nullable(), + test_pkey: z.string(), live_pkey: z.string(), created_at: z.number(), @@ -47,8 +42,7 @@ export const OrganizationSchema = z.object({ api_version: z.number().nullish(), }); +export type MinOrg = z.infer; export type Organization = z.infer; export type StripeConfig = z.infer; export type SvixConfig = z.infer; -export type MinOrg = z.infer; -export type FrontendOrganization = z.infer; diff --git a/shared/db/allTables.ts b/shared/db/allTables.ts new file mode 100644 index 000000000..d3c57e4e8 --- /dev/null +++ b/shared/db/allTables.ts @@ -0,0 +1,495 @@ +// import { +// pgTable, +// text, +// numeric, +// jsonb, +// boolean, +// foreignKey, +// unique, +// primaryKey, +// } from "drizzle-orm/pg-core"; + +// import { organizations } from "./orgTable.js"; + +// export const rewards = pgTable( +// "rewards", +// { +// internalId: text("internal_id").primaryKey().notNull(), +// env: text(), +// name: text(), +// orgId: text("org_id"), +// createdAt: numeric("created_at"), +// discountConfig: jsonb("discount_config"), +// freeProductId: text("free_product_id"), +// id: text(), +// promoCodes: jsonb("promo_codes").array(), +// type: text(), +// }, +// (table) => [ +// foreignKey({ +// columns: [table.orgId], +// foreignColumns: [organizations.id], +// name: "coupons_org_id_fkey", +// }).onDelete("cascade"), +// ], +// ); + +// export const rewardPrograms = pgTable( +// "reward_programs", +// { +// internalId: text("internal_id").primaryKey().notNull(), +// id: text(), +// createdAt: numeric("created_at").notNull(), +// internalRewardId: text("internal_reward_id"), +// maxRedemptions: numeric("max_redemptions"), +// unlimitedRedemptions: boolean("unlimited_redemptions").default(false), +// orgId: text("org_id"), +// env: text(), +// when: text().default("immediately"), +// productIds: text("product_ids").array().default([""]), +// excludeTrial: boolean("exclude_trial").default(false), +// receivedBy: text("received_by"), +// }, +// (table) => [ +// foreignKey({ +// columns: [table.internalRewardId], +// foreignColumns: [rewards.internalId], +// name: "reward_triggers_internal_reward_id_fkey", +// }).onDelete("cascade"), +// foreignKey({ +// columns: [table.orgId], +// foreignColumns: [organizations.id], +// name: "reward_triggers_org_id_fkey", +// }).onDelete("cascade"), +// ], +// ); + +// export const customerPrices = pgTable( +// "customer_prices", +// { +// id: text().primaryKey().notNull(), +// createdAt: numeric("created_at").notNull(), +// customerProductId: text("customer_product_id"), +// priceId: text("price_id"), +// options: jsonb(), +// internalCustomerId: text("internal_customer_id"), +// }, +// (table) => [ +// foreignKey({ +// columns: [table.customerProductId], +// foreignColumns: [customerProducts.id], +// name: "customer_prices_customer_product_id_fkey", +// }).onDelete("cascade"), +// foreignKey({ +// columns: [table.internalCustomerId], +// foreignColumns: [customers.internalId], +// name: "customer_prices_internal_customer_id_fkey", +// }).onDelete("cascade"), +// foreignKey({ +// columns: [table.priceId], +// foreignColumns: [prices.id], +// name: "customer_prices_price_id_fkey", +// }), +// ], +// ); + +// export const entities = pgTable( +// "entities", +// { +// internalId: text("internal_id").primaryKey().notNull(), +// orgId: text("org_id"), +// env: text(), +// internalCustomerId: text("internal_customer_id"), +// createdAt: numeric("created_at").notNull(), +// id: text(), +// name: text(), +// deleted: boolean().default(false).notNull(), +// internalFeatureId: text("internal_feature_id"), +// featureId: text("feature_id"), +// }, +// (table) => [ +// foreignKey({ +// columns: [table.internalCustomerId], +// foreignColumns: [customers.internalId], +// name: "entities_internal_customer_id_fkey", +// }).onDelete("cascade"), +// foreignKey({ +// columns: [table.internalFeatureId], +// foreignColumns: [features.internal_id], +// name: "entities_internal_feature_id_fkey", +// }).onDelete("cascade"), +// foreignKey({ +// columns: [table.orgId], +// foreignColumns: [organizations.id], +// name: "entities_org_id_fkey", +// }).onDelete("cascade"), +// unique("entity_id_constraint").on( +// table.orgId, +// table.env, +// table.internalCustomerId, +// table.id, +// ), +// ], +// ); + +// // export const entitlements = pgTable( +// // "entitlements", +// // { +// // createdAt: numeric("created_at").notNull(), +// // internalFeatureId: text("internal_feature_id"), +// // orgId: text("org_id"), +// // internalProductId: text("internal_product_id"), +// // allowanceType: text("allowance_type"), +// // allowance: numeric(), +// // interval: text(), +// // id: text().primaryKey().notNull(), +// // featureId: text("feature_id"), +// // isCustom: boolean("is_custom").default(false), +// // carryFromPrevious: boolean("carry_from_previous").default(false), +// // entityFeatureId: text("entity_feature_id"), +// // }, +// // (table) => [ +// // foreignKey({ +// // columns: [table.internalFeatureId], +// // foreignColumns: [features.internal_id], +// // name: "entitlements_internal_feature_id_fkey", +// // }).onDelete("cascade"), +// // foreignKey({ +// // columns: [table.internalProductId], +// // foreignColumns: [products.internalId], +// // name: "entitlements_internal_product_id_fkey", +// // }) +// // .onUpdate("cascade") +// // .onDelete("cascade"), +// // unique("entitlements_id_key").on(table.id), +// // ], +// // ); + +// export const events = pgTable( +// "events", +// { +// id: text().primaryKey().notNull(), +// orgId: text("org_id").notNull(), +// timestamp: numeric().notNull(), +// env: text().notNull(), +// customerId: text("customer_id").notNull(), +// eventName: text("event_name").notNull(), +// properties: jsonb(), +// idempotencyKey: text("idempotency_key"), +// internalCustomerId: text("internal_customer_id"), +// value: numeric(), +// setUsage: boolean("set_usage").default(false), +// entityId: text("entity_id"), +// }, +// (table) => [ +// foreignKey({ +// columns: [table.internalCustomerId], +// foreignColumns: [customers.internalId], +// name: "events_internal_customer_id_fkey", +// }).onDelete("cascade"), +// unique("unique_event_constraint").on( +// table.orgId, +// table.env, +// table.customerId, +// table.eventName, +// table.idempotencyKey, +// ), +// ], +// ); + +// export const freeTrials = pgTable( +// "free_trials", +// { +// id: text().primaryKey().notNull(), +// createdAt: numeric("created_at").notNull(), +// duration: text().default("day"), +// length: numeric(), +// internalProductId: text("internal_product_id"), +// uniqueFingerprint: boolean("unique_fingerprint"), +// isCustom: boolean("is_custom").default(false), +// }, +// (table) => [ +// foreignKey({ +// columns: [table.internalProductId], +// foreignColumns: [products.internalId], +// name: "free_trials_internal_product_id_fkey", +// }).onDelete("cascade"), +// ], +// ); + +// export const invoiceItems = pgTable( +// "invoice_items", +// { +// createdAt: numeric("created_at").notNull(), +// updatedAt: numeric("updated_at"), +// customerPriceId: text("customer_price_id"), +// periodStart: numeric("period_start"), +// periodEnd: numeric("period_end"), +// prorationStart: numeric("proration_start"), +// prorationEnd: numeric("proration_end"), +// quantity: numeric(), +// amount: numeric(), +// currency: text(), +// id: text().primaryKey().notNull(), +// addedToStripe: boolean("added_to_stripe").default(false), +// customerId: text("customer_id"), +// }, +// (table) => [ +// foreignKey({ +// columns: [table.customerPriceId], +// foreignColumns: [customerPrices.id], +// name: "invoice_items_customer_price_id_fkey", +// }).onDelete("cascade"), +// unique("invoice_items_id_key").on(table.id), +// ], +// ); + +// export const customers = pgTable( +// "customers", +// { +// name: text().default(""), +// orgId: text("org_id").notNull(), +// createdAt: numeric("created_at").notNull(), +// internalId: text("internal_id").primaryKey().notNull(), +// id: text(), +// env: text(), +// processor: jsonb(), +// email: text().default(""), +// fingerprint: text(), +// metadata: jsonb().default({}), +// }, +// (table) => [unique("cus_id_constraint").on(table.orgId, table.id, table.env)], +// ); + +// export const customerProducts = pgTable( +// "customer_products", +// { +// id: text().primaryKey().notNull(), +// internalCustomerId: text("internal_customer_id").notNull(), +// customerId: text("customer_id"), +// internalProductId: text("internal_product_id"), +// createdAt: numeric("created_at"), +// status: text(), +// processor: jsonb(), +// canceledAt: numeric("canceled_at"), +// endedAt: numeric("ended_at"), +// startsAt: numeric("starts_at"), +// options: jsonb().array(), +// productId: text("product_id"), +// freeTrialId: text("free_trial_id"), +// trialEndsAt: numeric("trial_ends_at"), +// collectionMethod: text("collection_method").default("charge_automatically"), +// subscriptionIds: text("subscription_ids").array(), +// scheduledIds: text("scheduled_ids").array(), +// isCustom: boolean("is_custom").default(false).notNull(), +// quantity: numeric().default("1"), +// internalEntityId: text("internal_entity_id"), +// entityId: text("entity_id"), +// }, +// (table) => [ +// foreignKey({ +// columns: [table.freeTrialId], +// foreignColumns: [freeTrials.id], +// name: "customer_products_free_trial_id_fkey", +// }), +// foreignKey({ +// columns: [table.internalCustomerId], +// foreignColumns: [customers.internalId], +// name: "customer_products_internal_customer_id_fkey", +// }) +// .onUpdate("cascade") +// .onDelete("cascade"), +// foreignKey({ +// columns: [table.internalEntityId], +// foreignColumns: [entities.internalId], +// name: "customer_products_internal_entity_id_fkey", +// }).onDelete("set null"), +// foreignKey({ +// columns: [table.internalProductId], +// foreignColumns: [products.internalId], +// name: "customer_products_internal_product_id_fkey", +// }), +// ], +// ); + +// export const metadata = pgTable("metadata", { +// id: text().primaryKey().notNull(), +// createdAt: numeric("created_at").notNull(), +// expiresAt: numeric("expires_at"), +// data: jsonb(), +// }); + +// export const products = pgTable( +// "products", +// { +// internalId: text("internal_id").primaryKey().notNull(), +// createdAt: numeric("created_at").notNull(), +// name: text(), +// orgId: text("org_id"), +// env: text(), +// isAddOn: boolean("is_add_on"), +// processor: jsonb(), +// isDefault: boolean("is_default").default(false), +// id: text(), +// group: text().default(""), +// version: numeric().default("1"), +// }, +// (table) => [ +// foreignKey({ +// columns: [table.orgId], +// foreignColumns: [organizations.id], +// name: "products_org_id_fkey", +// }).onDelete("cascade"), +// ], +// ); + +// export const rewardRedemptions = pgTable( +// "reward_redemptions", +// { +// id: text().primaryKey().notNull(), +// createdAt: numeric("created_at").notNull(), +// updatedAt: numeric("updated_at"), +// internalCustomerId: text("internal_customer_id"), +// triggered: boolean(), +// internalRewardProgramId: text("internal_reward_program_id"), +// applied: boolean().default(false), +// referralCodeId: text("referral_code_id"), +// }, +// (table) => [ +// foreignKey({ +// columns: [table.internalCustomerId], +// foreignColumns: [customers.internalId], +// name: "reward_redemptions_internal_customer_id_fkey", +// }).onDelete("cascade"), +// foreignKey({ +// columns: [table.internalRewardProgramId], +// foreignColumns: [rewardPrograms.internalId], +// name: "reward_redemptions_internal_reward_program_id_fkey", +// }).onDelete("cascade"), +// foreignKey({ +// columns: [table.referralCodeId], +// foreignColumns: [referralCodes.id], +// name: "reward_redemptions_referral_code_id_fkey", +// }).onDelete("cascade"), +// ], +// ); + +// export const migrationJobs = pgTable( +// "migration_jobs", +// { +// id: text().primaryKey().notNull(), +// createdAt: numeric("created_at").notNull(), +// updatedAt: numeric("updated_at"), +// currentStep: text("current_step"), +// fromInternalProductId: text("from_internal_product_id"), +// toInternalProductId: text("to_internal_product_id"), +// stepDetails: jsonb("step_details"), +// orgId: text("org_id"), +// env: text(), +// }, +// (table) => [ +// foreignKey({ +// columns: [table.fromInternalProductId], +// foreignColumns: [products.internalId], +// name: "migration_jobs_from_internal_product_id_fkey", +// }).onDelete("cascade"), +// foreignKey({ +// columns: [table.orgId], +// foreignColumns: [organizations.id], +// name: "migration_jobs_org_id_fkey", +// }).onDelete("cascade"), +// foreignKey({ +// columns: [table.toInternalProductId], +// foreignColumns: [products.internalId], +// name: "migration_jobs_to_internal_product_id_fkey", +// }).onDelete("cascade"), +// ], +// ); + +// export const subscriptions = pgTable( +// "subscriptions", +// { +// id: text().primaryKey().notNull(), +// stripeId: text("stripe_id"), +// stripeScheduleId: text("stripe_schedule_id"), +// createdAt: numeric("created_at"), +// metadata: jsonb().default({}), +// usageFeatures: text("usage_features").array().default([""]), +// orgId: text("org_id"), +// env: text(), +// currentPeriodStart: numeric("current_period_start"), +// currentPeriodEnd: numeric("current_period_end"), +// }, +// (table) => [ +// foreignKey({ +// columns: [table.orgId], +// foreignColumns: [organizations.id], +// name: "subscriptions_org_id_fkey", +// }).onDelete("cascade"), +// unique("subscriptions_stripe_id_key").on(table.stripeId), +// ], +// ); + +// export const referralCodes = pgTable( +// "referral_codes", +// { +// code: text().notNull(), +// orgId: text("org_id").notNull(), +// env: text().notNull(), +// internalCustomerId: text("internal_customer_id"), +// internalRewardProgramId: text("internal_reward_program_id"), +// id: text().notNull(), +// createdAt: numeric("created_at"), +// }, +// (table) => [ +// foreignKey({ +// columns: [table.internalCustomerId], +// foreignColumns: [customers.internalId], +// name: "referral_codes_internal_customer_id_fkey", +// }).onDelete("cascade"), +// foreignKey({ +// columns: [table.internalRewardProgramId], +// foreignColumns: [rewardPrograms.internalId], +// name: "referral_codes_internal_reward_program_id_fkey", +// }).onDelete("cascade"), +// foreignKey({ +// columns: [table.orgId], +// foreignColumns: [organizations.id], +// name: "referral_codes_org_id_fkey", +// }).onDelete("cascade"), +// primaryKey({ +// columns: [table.code, table.orgId, table.env], +// name: "referral_codes_pkey", +// }), +// unique("referral_codes_id_key").on(table.id), +// ], +// ); + +// export const migrationErrors = pgTable( +// "migration_errors", +// { +// internalCustomerId: text("internal_customer_id").notNull(), +// migrationJobId: text("migration_job_id").notNull(), +// createdAt: numeric("created_at"), +// updatedAt: numeric("updated_at"), +// data: jsonb(), +// message: text(), +// code: text(), +// }, +// (table) => [ +// foreignKey({ +// columns: [table.internalCustomerId], +// foreignColumns: [customers.internalId], +// name: "migration_customers_internal_customer_id_fkey", +// }).onDelete("cascade"), +// foreignKey({ +// columns: [table.migrationJobId], +// foreignColumns: [migrationJobs.id], +// name: "migration_customers_migration_job_id_fkey", +// }).onDelete("cascade"), +// primaryKey({ +// columns: [table.internalCustomerId, table.migrationJobId], +// name: "migration_errors_pkey", +// }), +// ], +// ); diff --git a/server/src/db/schema/tables/apiKeysTable.ts b/shared/db/apiKeysTable.ts similarity index 91% rename from server/src/db/schema/tables/apiKeysTable.ts rename to shared/db/apiKeysTable.ts index a6687e048..1a4121670 100644 --- a/server/src/db/schema/tables/apiKeysTable.ts +++ b/shared/db/apiKeysTable.ts @@ -7,7 +7,7 @@ import { unique, } from "drizzle-orm/pg-core"; -import { organizations } from "../index.js"; +import { organizations } from "../models/orgModels/orgTable.js"; export const apiKeys = pgTable( "api_keys", diff --git a/server/src/db/schema/tables/cusTable.ts b/shared/db/cusTable.ts similarity index 92% rename from server/src/db/schema/tables/cusTable.ts rename to shared/db/cusTable.ts index 20b733aa0..beed0877a 100644 --- a/server/src/db/schema/tables/cusTable.ts +++ b/shared/db/cusTable.ts @@ -1,5 +1,5 @@ import { pgTable, text, numeric, jsonb, unique } from "drizzle-orm/pg-core"; -import { collatePgColumn } from "../schemaUtils.js"; +import { collatePgColumn } from "./utils.js"; export const customers = pgTable( "customers", diff --git a/server/src/db/schema/tables/pricesTable.ts b/shared/db/pricesTable.ts similarity index 92% rename from server/src/db/schema/tables/pricesTable.ts rename to shared/db/pricesTable.ts index 0c051ca72..efb24a871 100644 --- a/server/src/db/schema/tables/pricesTable.ts +++ b/shared/db/pricesTable.ts @@ -7,7 +7,7 @@ import { jsonb, text, } from "drizzle-orm/pg-core"; -import { entitlements } from "./entitlementsTable.js"; +import { entitlements } from "../models/productModels/entModels/entTable.js"; import { products } from "./productsTable.js"; export const prices = pgTable( diff --git a/server/src/db/schema/tables/productsTable.ts b/shared/db/productsTable.ts similarity index 58% rename from server/src/db/schema/tables/productsTable.ts rename to shared/db/productsTable.ts index 70659097f..6530eeac6 100644 --- a/server/src/db/schema/tables/productsTable.ts +++ b/shared/db/productsTable.ts @@ -6,9 +6,11 @@ import { pgTable, text, } from "drizzle-orm/pg-core"; -import { organizations } from "../index.js"; -import { sql } from "drizzle-orm"; -import { sqlNow } from "../schemaUtils.js"; +import { sqlNow } from "./utils.js"; +import { relations } from "drizzle-orm"; +import { organizations } from "../models/orgModels/orgTable.js"; +import { entitlements } from "../models/productModels/entModels/entTable.js"; +import { prices } from "./pricesTable.js"; export const products = pgTable( "products", @@ -33,3 +35,12 @@ export const products = pgTable( }).onDelete("cascade"), ], ); + +export const productsRelations = relations(products, ({ one, many }) => ({ + org: one(organizations, { + fields: [products.org_id], + references: [organizations.id], + }), + entitlements: many(entitlements), + prices: many(prices), +})); diff --git a/server/src/db/relations.ts b/shared/db/relations.ts similarity index 56% rename from server/src/db/relations.ts rename to shared/db/relations.ts index 65fb75fc5..0b474f762 100644 --- a/server/src/db/relations.ts +++ b/shared/db/relations.ts @@ -1,9 +1,7 @@ -import { relations } from "drizzle-orm"; -import { apiKeys, organizations } from "./schema/index.js"; +import { apiKeys } from "./apiKeysTable.js"; -export const organizationsRelations = relations(organizations, ({ many }) => ({ - api_keys: many(apiKeys), -})); +import { organizations } from "../models/orgModels/orgTable.js"; +import { relations } from "drizzle-orm"; export const apiKeysRelations = relations(apiKeys, ({ one }) => ({ organization: one(organizations, { diff --git a/shared/db/schema.ts b/shared/db/schema.ts new file mode 100644 index 000000000..4fb6845c5 --- /dev/null +++ b/shared/db/schema.ts @@ -0,0 +1,47 @@ +import { organizations } from "../models/orgModels/orgTable.js"; +import { chatResults } from "../models/chatResultModels/chatResultTable.js"; +import { entitlements } from "../models/productModels/entModels/entTable.js"; +import { features } from "../models/featureModels/featureTable.js"; + +import { apiKeys } from "./apiKeysTable.js"; +import { customers } from "./cusTable.js"; +import { prices } from "./pricesTable.js"; +import { products } from "./productsTable.js"; + +// import * as relations from "./relations.js"; + +// Relations +import { organizationsRelations } from "../models/orgModels/orgRelations.js"; +import { apiKeysRelations } from "./relations.js"; +import { entitlementsRelations } from "../models/productModels/entModels/entRelations.js"; +import { featureRelations } from "../models/featureModels/featureRelations.js"; + +const relations = { + organizationsRelations, + apiKeysRelations, + entitlementsRelations, + featureRelations, +}; + +export const schemas = { + apiKeys, + customers, + chatResults, + prices, + organizations, + entitlements, + features, + products, + ...relations, +}; + +export { + apiKeys, + organizations, + customers, + chatResults, + prices, + entitlements, + features, + products, +}; diff --git a/server/src/db/schema/schemaUtils.ts b/shared/db/utils.ts similarity index 100% rename from server/src/db/schema/schemaUtils.ts rename to shared/db/utils.ts diff --git a/shared/errors/errCode.ts b/shared/enums/ErrCode.ts similarity index 100% rename from shared/errors/errCode.ts rename to shared/enums/ErrCode.ts diff --git a/shared/logger/LoggerAction.ts b/shared/enums/LoggerAction.ts similarity index 100% rename from shared/logger/LoggerAction.ts rename to shared/enums/LoggerAction.ts diff --git a/shared/errors/SuccessCode.ts b/shared/enums/SuccessCode.ts similarity index 100% rename from shared/errors/SuccessCode.ts rename to shared/enums/SuccessCode.ts diff --git a/shared/errors/internalErrCode.ts b/shared/errors/internalErrCode.ts deleted file mode 100644 index bcbe98da8..000000000 --- a/shared/errors/internalErrCode.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const InternalErrCode = { - UpdateBalanceFailed: "update_balance_failed", -}; diff --git a/shared/index.ts b/shared/index.ts index 8c447ba64..38afee708 100644 --- a/shared/index.ts +++ b/shared/index.ts @@ -1,9 +1,23 @@ -// Feature Models +// Schemas +export * from "./db/schema.js"; +// 1. Org Models +export * from "./models/orgModels/orgTable.js"; +export * from "./models/orgModels/orgConfig.js"; + +// 2. Feature Models +export * from "./models/featureModels/featureTable.js"; +export * from "./models/featureModels/featureEnums.js"; export * from "./models/featureModels/featureModels.js"; -export * from "./models/featureModels/meteredModels.js"; -export * from "./models/featureModels/dbConnModels.js"; -export * from "./models/featureModels/creditModels.js"; +export * from "./models/featureModels/featureConfig/meteredConfig.js"; +export * from "./models/featureModels/featureConfig/creditConfig.js"; + +// 3. Entitlement Models +export * from "./models/productModels/entModels/entTable.js"; + +// 3. Chat Result Models +export * from "./models/chatResultModels/chatResultTable.js"; +export * from "./models/chatResultModels/chatResultFeature.js"; // Gen Models export * from "./models/genModels.js"; @@ -26,11 +40,6 @@ export * from "./models/cusModels/cusPriceModels/cusPriceModels.js"; export * from "./models/cusModels/invoiceModels/invoiceModels.js"; export * from "./models/cusModels/cusResponseModels.js"; -// Error Codes -export * from "./errors/errCode.js"; -export * from "./errors/internalErrCode.js"; -export * from "./logger/LoggerAction.js"; - // Entity Models export * from "./models/cusModels/entityModels/entityModels.js"; @@ -51,7 +60,6 @@ export * from "./models/productModels/productItemModels.js"; export * from "./models/productModels/productV2Models.js"; export * from "./models/productModels/productResponseModels.js"; export * from "./models/apiVersionEnum.js"; -export * from "./errors/SuccessCode.js"; export * from "./models/productModels/productItemModels/prodItemResponseModels.js"; export * from "./models/cusModels/fullCusModel.js"; @@ -72,6 +80,11 @@ export * from "./models/checkModels/checkPreviewModels.js"; export * from "./models/rewardModels/rewardResponseModels.js"; // Org Models -export * from "./models/orgModels/orgModels.js"; -export * from "./models/orgModels/orgConfigModels.js"; -export * from "./models/orgModels/chatResultModels.js"; +// export * from "./models/orgModels/orgModels.js"; + +export * from "./models/chatResultModels/chatResultFeature.js"; + +// ENUMS +export * from "./enums/SuccessCode.js"; +export * from "./enums/ErrCode.js"; +export * from "./enums/LoggerAction.js"; diff --git a/shared/internal/run/runModels.ts b/shared/internal/run/runModels.ts deleted file mode 100644 index 0f5913c43..000000000 --- a/shared/internal/run/runModels.ts +++ /dev/null @@ -1,29 +0,0 @@ -export type LogEntry = { - level: "info" | "error" | "warn" | "debug"; - name: string; - msg: string; - timestamp: number; -}; - -export type LogGroup = { - name: string; - id: string; - logs: LogEntry[]; -}; - -export type WorkflowRun = { - id: string; - created_at: number; - workspace_id: string; - - logs: LogGroup; - inputs: Record; - - status: "running" | "completed" | "failed"; - - workflow: { - id: string; - external_id: string; - }; - // status: "pending" | "running" | "completed" | "failed"; -}; diff --git a/shared/internal/workflow/workflowModels.ts b/shared/internal/workflow/workflowModels.ts deleted file mode 100644 index e6b1d1328..000000000 --- a/shared/internal/workflow/workflowModels.ts +++ /dev/null @@ -1,22 +0,0 @@ -export type Workflow = { - id?: string; - workspace_id?: string; - created_at?: number; - last_updated?: number; - external_id?: string; - inputs?: Record; - file_contents?: string; - - env?: string | null; - package_json?: string | null; - typescript_contents?: string; -}; - -// export enum WorkflowInputType { -// String = "String", -// } - -export type WorkflowInput = { - name: string; - type: "string" | "number" | "boolean" | "object"; -}; diff --git a/shared/internal/workflow/workflowUtils.ts b/shared/internal/workflow/workflowUtils.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/shared/models/orgModels/chatResultModels.ts b/shared/models/chatResultModels/chatResultFeature.ts similarity index 100% rename from shared/models/orgModels/chatResultModels.ts rename to shared/models/chatResultModels/chatResultFeature.ts diff --git a/server/src/db/schema/tables/chatResultsTable.ts b/shared/models/chatResultModels/chatResultTable.ts similarity index 81% rename from server/src/db/schema/tables/chatResultsTable.ts rename to shared/models/chatResultModels/chatResultTable.ts index 726fe8965..7cf0ba3a4 100644 --- a/server/src/db/schema/tables/chatResultsTable.ts +++ b/shared/models/chatResultModels/chatResultTable.ts @@ -1,6 +1,7 @@ import { pgTable, text, numeric, jsonb, boolean } from "drizzle-orm/pg-core"; -import { collatePgColumn } from "../schemaUtils.js"; + import { ChatResultFeature, ProductV2 } from "@autumn/shared"; +import { collatePgColumn } from "../../db/utils.js"; export const chatResults = pgTable("chat_results", { id: text("id").primaryKey(), @@ -11,7 +12,6 @@ export const chatResults = pgTable("chat_results", { products: ProductV2[]; }>() .notNull(), - processed: boolean("processed").default(false), }).enableRLS(); collatePgColumn(chatResults.id, "C"); diff --git a/shared/models/featureModels/creditModels.ts b/shared/models/featureModels/creditModels.ts deleted file mode 100644 index e7cf5ae5b..000000000 --- a/shared/models/featureModels/creditModels.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { FeatureUsageType } from "./meteredModels.js"; - -export type CreditSchemaItem = { - metered_feature_id: string; - feature_amount: number; - credit_amount: number; -}; - -// export type CreditSchema = { -// items: CreditSchemaItem[]; -// }; - -export type CreditSystemConfig = { - schema: CreditSchemaItem[]; - usage_type: FeatureUsageType; -}; - -export type CreditSystem = { - internal_id: string; - org_id: string; - id: string; - name: string; - created_at: number; - config: CreditSystemConfig; -}; diff --git a/shared/models/featureModels/dbConnModels.ts b/shared/models/featureModels/dbConnModels.ts deleted file mode 100644 index 3c2b6d526..000000000 --- a/shared/models/featureModels/dbConnModels.ts +++ /dev/null @@ -1,12 +0,0 @@ -export enum DBConnectionProvider { - Postgres = "postgres", -} - -export interface DBConnection { - id: string; - org_id: string; - provider: DBConnectionProvider; - display_name: string; - connection_string: string; - created_at: number; -} diff --git a/shared/models/featureModels/featureConfig/creditConfig.ts b/shared/models/featureModels/featureConfig/creditConfig.ts new file mode 100644 index 000000000..3d5be4aaa --- /dev/null +++ b/shared/models/featureModels/featureConfig/creditConfig.ts @@ -0,0 +1,22 @@ +import { FeatureUsageType } from "../featureEnums.js"; +import { z } from "zod"; + +export const CreditSchemaItemSchema = z.object({ + metered_feature_id: z.string(), + feature_amount: z.number(), + credit_amount: z.number(), +}); + +export const CreditSystemConfigSchema = z.object({ + schema: z.array( + z.object({ + metered_feature_id: z.string(), + feature_amount: z.number(), + credit_amount: z.number(), + }), + ), + usage_type: z.nativeEnum(FeatureUsageType), +}); + +export type CreditSystemConfig = z.infer; +export type CreditSchemaItem = z.infer; diff --git a/shared/models/featureModels/meteredModels.ts b/shared/models/featureModels/featureConfig/meteredConfig.ts similarity index 80% rename from shared/models/featureModels/meteredModels.ts rename to shared/models/featureModels/featureConfig/meteredConfig.ts index c11f23d06..a45cca7a5 100644 --- a/shared/models/featureModels/meteredModels.ts +++ b/shared/models/featureModels/featureConfig/meteredConfig.ts @@ -1,4 +1,5 @@ import { z } from "zod"; +import { FeatureUsageType } from "../featureEnums.js"; export interface Expression { property: string; @@ -22,15 +23,6 @@ export const AggregateSchema = z.object({ property: z.string().nullable(), }); -export const GroupBySchema = z.object({ - property: z.string(), -}); - -export enum FeatureUsageType { - Single = "single_use", - Continuous = "continuous_use", -} - export const MeteredConfigSchema = z.object({ filters: z.array(ExpressionSchema), aggregate: AggregateSchema, diff --git a/shared/models/featureModels/featureEnums.ts b/shared/models/featureModels/featureEnums.ts new file mode 100644 index 000000000..d9e5f8250 --- /dev/null +++ b/shared/models/featureModels/featureEnums.ts @@ -0,0 +1,15 @@ +export enum FeatureType { + Boolean = "boolean", + Metered = "metered", + CreditSystem = "credit_system", +} + +export enum AggregateType { + Count = "count", + Sum = "sum", +} + +export enum FeatureUsageType { + Single = "single_use", + Continuous = "continuous_use", +} diff --git a/shared/models/featureModels/featureModels.ts b/shared/models/featureModels/featureModels.ts index a1bae96bf..940f40f4d 100644 --- a/shared/models/featureModels/featureModels.ts +++ b/shared/models/featureModels/featureModels.ts @@ -1,16 +1,6 @@ import { z } from "zod"; import { AppEnv } from "../genModels.js"; - -export enum FeatureType { - Boolean = "boolean", - Metered = "metered", - CreditSystem = "credit_system", -} - -export enum AggregateType { - Count = "count", - Sum = "sum", -} +import { FeatureType } from "./featureEnums.js"; export const FeatureSchema = z.object({ internal_id: z.string().optional(), diff --git a/shared/models/featureModels/featureRelations.ts b/shared/models/featureModels/featureRelations.ts new file mode 100644 index 000000000..883219ed6 --- /dev/null +++ b/shared/models/featureModels/featureRelations.ts @@ -0,0 +1,10 @@ +import { relations } from "drizzle-orm"; +import { organizations } from "../orgModels/orgTable.js"; +import { features } from "./featureTable.js"; + +export const featureRelations = relations(features, ({ one }) => ({ + org: one(organizations, { + fields: [features.org_id], + references: [organizations.id], + }), +})); diff --git a/server/src/db/schema/tables/featuresTable.ts b/shared/models/featureModels/featureTable.ts similarity index 62% rename from server/src/db/schema/tables/featuresTable.ts rename to shared/models/featureModels/featureTable.ts index 8325372b0..de80aa52f 100644 --- a/server/src/db/schema/tables/featuresTable.ts +++ b/shared/models/featureModels/featureTable.ts @@ -6,8 +6,12 @@ import { text, unique, } from "drizzle-orm/pg-core"; -import { organizations } from "../index.js"; + import { relations } from "drizzle-orm"; +import { organizations } from "../orgModels/orgTable.js"; +import { MeteredConfig } from "./featureConfig/meteredConfig.js"; +import { CreditSystemConfig } from "./featureConfig/creditConfig.js"; +import { collatePgColumn } from "../../db/utils.js"; export const features = pgTable( "features", @@ -17,8 +21,8 @@ export const features = pgTable( id: text().notNull(), name: text(), type: text(), - created_at: numeric("created_at"), - config: jsonb(), + created_at: numeric({ mode: "number" }), + config: jsonb().$type(), env: text().default("live"), display: jsonb(), }, @@ -31,3 +35,5 @@ export const features = pgTable( unique("feature_id_constraint").on(table.org_id, table.id, table.env), ], ); + +collatePgColumn(features.internal_id, "C"); diff --git a/shared/models/orgModels/orgConfigModels.ts b/shared/models/orgModels/orgConfig.ts similarity index 100% rename from shared/models/orgModels/orgConfigModels.ts rename to shared/models/orgModels/orgConfig.ts diff --git a/shared/models/orgModels/orgRelations.ts b/shared/models/orgModels/orgRelations.ts new file mode 100644 index 000000000..3b03d49fc --- /dev/null +++ b/shared/models/orgModels/orgRelations.ts @@ -0,0 +1,9 @@ +import { relations } from "drizzle-orm"; +import { apiKeys } from "../../db/apiKeysTable.js"; +import { features } from "../featureModels/featureTable.js"; +import { organizations } from "./orgTable.js"; + +export const organizationsRelations = relations(organizations, ({ many }) => ({ + api_keys: many(apiKeys), + features: many(features), +})); diff --git a/shared/models/orgModels/orgTable.ts b/shared/models/orgModels/orgTable.ts new file mode 100644 index 000000000..367255f71 --- /dev/null +++ b/shared/models/orgModels/orgTable.ts @@ -0,0 +1,52 @@ +import { + pgTable, + text, + numeric, + jsonb, + boolean, + unique, +} from "drizzle-orm/pg-core"; + +import { OrgConfig } from "./orgConfig.js"; + +export type MinOrg = { + id: string; + slug: string; +}; + +export type SvixConfig = { + sandbox_app_id: string; + live_app_id: string; +}; + +export type StripeConfig = { + test_api_key: string; + live_api_key: string; + test_webhook_secret: string; + live_webhook_secret: string; + success_url: string; +}; + +export const organizations = pgTable( + "organizations", + { + id: text().primaryKey().notNull(), + slug: text().notNull(), + default_currency: text("default_currency").notNull().default("usd"), + stripe_connected: boolean("stripe_connected").default(false), + stripe_config: jsonb("stripe_config").$type(), + test_pkey: text("test_pkey"), + live_pkey: text("live_pkey"), + svix_config: jsonb("svix_config").notNull().$type(), + created_at: numeric({ mode: "number" }), + config: jsonb().default({}).notNull().$type(), + }, + (table) => [ + unique("organizations_test_pkey_key").on(table.test_pkey), + unique("organizations_live_pkey_key").on(table.live_pkey), + ], +); + +export type Organization = typeof organizations.$inferSelect & { + api_version: number; +}; diff --git a/shared/models/productModels/entModels/entRelations.ts b/shared/models/productModels/entModels/entRelations.ts new file mode 100644 index 000000000..377286cae --- /dev/null +++ b/shared/models/productModels/entModels/entRelations.ts @@ -0,0 +1,11 @@ +import { relations } from "drizzle-orm"; + +import { features } from "../../featureModels/featureTable.js"; +import { entitlements } from "./entTable.js"; + +export const entitlementsRelations = relations(entitlements, ({ one }) => ({ + feature: one(features, { + fields: [entitlements.internal_feature_id], + references: [features.internal_id], + }), +})); diff --git a/server/src/db/schema/tables/entitlementsTable.ts b/shared/models/productModels/entModels/entTable.ts similarity index 75% rename from server/src/db/schema/tables/entitlementsTable.ts rename to shared/models/productModels/entModels/entTable.ts index 74d61582f..4b2332249 100644 --- a/server/src/db/schema/tables/entitlementsTable.ts +++ b/shared/models/productModels/entModels/entTable.ts @@ -5,24 +5,28 @@ import { foreignKey, unique, text, + integer, } from "drizzle-orm/pg-core"; -import { products, features } from "../index.js"; + +import { features } from "../../featureModels/featureTable.js"; +import { products } from "../../../db/productsTable.js"; +import { createInsertSchema } from "drizzle-zod"; export const entitlements = pgTable( "entitlements", { - created_at: numeric("created_at").notNull(), internal_feature_id: text("internal_feature_id"), org_id: text("org_id"), internal_product_id: text("internal_product_id"), allowance_type: text("allowance_type"), - allowance: numeric(), + allowance: numeric({ mode: "number" }), interval: text(), id: text().primaryKey().notNull(), feature_id: text("feature_id"), is_custom: boolean("is_custom").default(false), carry_from_previous: boolean("carry_from_previous").default(false), entity_feature_id: text("entity_feature_id"), + created_at: numeric("created_at", { mode: "number" }).notNull(), }, (table) => [ foreignKey({ @@ -40,3 +44,5 @@ export const entitlements = pgTable( unique("entitlements_id_key").on(table.id), ], ); + +export const EntInsertSchema = createInsertSchema(entitlements); diff --git a/shared/models/productModels/entitlementModels.ts b/shared/models/productModels/entitlementModels.ts index f257eeae3..7cce539a3 100644 --- a/shared/models/productModels/entitlementModels.ts +++ b/shared/models/productModels/entitlementModels.ts @@ -1,5 +1,6 @@ import { z } from "zod"; -import { FeatureSchema, FeatureType } from "../featureModels/featureModels.js"; +import { FeatureSchema } from "../featureModels/featureModels.js"; +import { FeatureType } from "../featureModels/featureEnums.js"; import { EntInterval } from "../genModels.js"; import { UsagePriceConfigSchema } from "./usagePriceModels.js"; diff --git a/shared/models/productModels/priceModels.ts b/shared/models/productModels/priceModels.ts index af0e20356..645376290 100644 --- a/shared/models/productModels/priceModels.ts +++ b/shared/models/productModels/priceModels.ts @@ -25,7 +25,7 @@ export const PriceSchema = z.object({ billing_type: z.nativeEnum(BillingType).nullish(), is_custom: z.boolean().optional(), - name: z.string().optional(), + // name: z.string().optional(), config: FixedPriceConfigSchema.or(UsagePriceConfigSchema).optional(), entitlement_id: z.string().nullish(), }); @@ -33,7 +33,7 @@ export const PriceSchema = z.object({ export type Price = z.infer; export const CreatePriceSchema = z.object({ - name: z.string().nonempty(), + // name: z.string().nonempty(), config: FixedPriceConfigSchema.or(UsagePriceConfigSchema), }); diff --git a/shared/package.json b/shared/package.json index b6086a034..74a0ff37f 100644 --- a/shared/package.json +++ b/shared/package.json @@ -16,7 +16,7 @@ "dependencies": { "drizzle-orm": "^0.43.1", "drizzle-zod": "^0.8.1", - "zod": "^3.x" + "zod": "^3.25.23" }, "devDependencies": { "typescript": "^5.7.2" diff --git a/vite/src/app/layout.tsx b/vite/src/app/layout.tsx index ec8d4f4f3..58f3914cc 100644 --- a/vite/src/app/layout.tsx +++ b/vite/src/app/layout.tsx @@ -19,6 +19,7 @@ import { Button } from "@/components/ui/button"; import { ArrowUpRightFromSquare } from "lucide-react"; import { AutumnProvider } from "autumn-js/react"; import { useAuth } from "@clerk/clerk-react"; + export function MainLayout() { const { isLoaded: isUserLoaded, user } = useUser(); const { organization: org } = useOrganization(); @@ -157,7 +158,7 @@ const MainContent = () => { )}
diff --git a/vite/src/components/general/AdminHover.tsx b/vite/src/components/general/AdminHover.tsx index 052e787bc..b28036b34 100644 --- a/vite/src/components/general/AdminHover.tsx +++ b/vite/src/components/general/AdminHover.tsx @@ -9,19 +9,21 @@ import { useState } from "react"; export const AdminHover = ({ children, texts, + hide = false, }: { children: React.ReactNode; texts: (string | { key: string; value: string } | undefined | null)[]; + hide?: boolean; }) => { const { isLoaded, user } = useUser(); const email = user?.primaryEmailAddress?.emailAddress; - let isAdmin = + const isAdmin = email === "johnyeocx@gmail.com" || email === "ayush@recaseai.com" || email === "johnyeo10@gmail.com" || email == "npmrundemo@gmail.com"; - if (!isAdmin) return children; + if (!isAdmin || hide) return children; return ( diff --git a/vite/src/components/general/OnboardingStep.tsx b/vite/src/components/general/OnboardingStep.tsx index cc2c36856..eb9bc4999 100644 --- a/vite/src/components/general/OnboardingStep.tsx +++ b/vite/src/components/general/OnboardingStep.tsx @@ -15,7 +15,7 @@ function Step({ title, children, className, description, number }: StepProps) {
@@ -28,7 +28,7 @@ function Step({ title, children, className, description, number }: StepProps) {

{title}

{description && ( -
+
{description}
)} diff --git a/vite/src/components/general/PageSectionHeader.tsx b/vite/src/components/general/PageSectionHeader.tsx index 2203bc0bf..3023e955a 100644 --- a/vite/src/components/general/PageSectionHeader.tsx +++ b/vite/src/components/general/PageSectionHeader.tsx @@ -1,9 +1,28 @@ -export const PageSectionHeader = ({ title }: { title: string }) => { +import { cn } from "@/lib/utils"; + +export const PageSectionHeader = ({ + title, + isOnboarding = false, + addButton, + className, +}: { + title: string; + isOnboarding?: boolean; + addButton?: React.ReactNode; + className?: string; +}) => { return ( -
+

{title}

+ {addButton &&
{addButton}
}
); }; diff --git a/vite/src/components/general/TableGrid.tsx b/vite/src/components/general/TableGrid.tsx index 42dcc881e..ef7ae1b45 100644 --- a/vite/src/components/general/TableGrid.tsx +++ b/vite/src/components/general/TableGrid.tsx @@ -5,12 +5,14 @@ export const Row = ({ className, type, onClick, + isOnboarding = false, ...props }: { children?: React.ReactNode; className?: string; type?: "header" | "body"; onClick?: () => void; + isOnboarding?: boolean; props?: React.ComponentProps<"div">; }) => { return ( @@ -19,7 +21,8 @@ export const Row = ({ "grid grid-cols-[repeat(auto-fit,_minmax(0,_1fr))] gap-2 w-full px-10 h-8 items-center hover:bg-primary/5 whitespace-nowrap", type === "header" && "text-xs text-t3 h-8 -mb-1 items-center hover:bg-primary/0", - className + isOnboarding && "px-2", + className, )} onClick={onClick} {...props} @@ -40,7 +43,7 @@ export const Item = ({
{children} diff --git a/vite/src/services/products/ProductService.tsx b/vite/src/services/products/ProductService.tsx index 592ff5912..2c05d17cf 100644 --- a/vite/src/services/products/ProductService.tsx +++ b/vite/src/services/products/ProductService.tsx @@ -3,13 +3,13 @@ import axios, { AxiosInstance } from "axios"; export class ProductService { static async createProduct(axiosInstance: AxiosInstance, data: any) { const response = await axiosInstance.post("/v1/products", data); - return response.data.id; + return response.data; } static async updateProduct( axiosInstance: AxiosInstance, productId: string, - data: any + data: any, ) { await axiosInstance.post(`/v1/products/${productId}`, data); } @@ -25,7 +25,7 @@ export class ProductService { static async createPrice( axiosInstance: AxiosInstance, productId: string, - data: any + data: any, ) { await axiosInstance.post(`/products/${productId}/prices`, data); } @@ -39,7 +39,7 @@ export class ProductService { static async copyProduct( axiosInstance: AxiosInstance, productId: string, - data: any + data: any, ) { await axiosInstance.post(`/v1/products/${productId}/copy`, data); } diff --git a/vite/src/services/useAxiosInstance.tsx b/vite/src/services/useAxiosInstance.tsx index 3548bf3d1..233b6fccd 100644 --- a/vite/src/services/useAxiosInstance.tsx +++ b/vite/src/services/useAxiosInstance.tsx @@ -3,14 +3,16 @@ import { endpoint } from "@/utils/constants"; import { useAuth } from "@clerk/clerk-react"; import { AppEnv } from "@autumn/shared"; import { useEnv } from "@/utils/envUtils"; +const defaultParams = { + isAuth: true, +}; + +export function useAxiosInstance(params?: { env?: AppEnv; isAuth?: boolean }) { + const finalParams: any = { + ...defaultParams, + ...(params || {}), + }; -export function useAxiosInstance({ - env, - isAuth = true, -}: { - env?: AppEnv; - isAuth?: boolean; -}) { const trueEnv = useEnv(); const axiosInstance = axios.create({ @@ -19,7 +21,7 @@ export function useAxiosInstance({ const { getToken } = useAuth(); - if (isAuth) { + if (finalParams.isAuth) { axiosInstance.interceptors.request.use( async (config: any) => { const token = await getToken({ @@ -35,7 +37,7 @@ export function useAxiosInstance({ }, (error: any) => { return Promise.reject(error); - } + }, ); } diff --git a/vite/src/views/credits/CreditSystemsView.tsx b/vite/src/views/credits/CreditSystemsView.tsx index 4c51c4285..d7a2c38cb 100644 --- a/vite/src/views/credits/CreditSystemsView.tsx +++ b/vite/src/views/credits/CreditSystemsView.tsx @@ -1,15 +1,11 @@ "use client"; -import React, { useContext } from "react"; - -import { AppEnv, DBConnection, Feature, FeatureType } from "@autumn/shared"; +import { AppEnv } from "@autumn/shared"; import CreateCreditSystem from "./CreateCreditSystem"; -import { useFeaturesContext } from "../features/FeaturesContext"; import { useAxiosSWR } from "@/services/useAxiosSwr"; import LoadingScreen from "../general/LoadingScreen"; import { FeaturesContext } from "../features/FeaturesContext"; import { CreditSystemsTable } from "./CreditSystemsTable"; -import { CustomToaster } from "@/components/general/CustomToaster"; function CreditSystemsView({ env }: { env: AppEnv }) { const { data, isLoading, error, mutate } = useAxiosSWR({ diff --git a/vite/src/views/customers/customer/CustomerView.tsx b/vite/src/views/customers/customer/CustomerView.tsx index 4eb9333cc..869796fe0 100644 --- a/vite/src/views/customers/customer/CustomerView.tsx +++ b/vite/src/views/customers/customer/CustomerView.tsx @@ -20,13 +20,8 @@ import { useEffect, useState } from "react"; import ErrorScreen from "@/views/general/ErrorScreen"; import { InvoicesTable } from "./InvoicesTable"; -import { AdminHover } from "@/components/general/AdminHover"; import { CustomerSidebar } from "./customer-sidebar/customer-sidebar"; import { CustomerBreadcrumbs } from "./customer-breadcrumbs"; -import { Building2, X } from "lucide-react"; -import { Button } from "@/components/ui/button"; -import { XMarkIcon } from "@heroicons/react/24/outline"; -import { EntityHeader } from "./components/entity-header"; import { SelectEntity } from "./customer-sidebar/select-entity"; export default function CustomerView({ env }: { env: AppEnv }) { diff --git a/vite/src/views/customers/customer/add-product/AddProductButton.tsx b/vite/src/views/customers/customer/add-product/AddProductButton.tsx index d14e33320..8d545fdff 100644 --- a/vite/src/views/customers/customer/add-product/AddProductButton.tsx +++ b/vite/src/views/customers/customer/add-product/AddProductButton.tsx @@ -17,12 +17,7 @@ import { import { ProductActionState } from "@/utils/models"; import { File, ShoppingCart, Upload } from "lucide-react"; -export const AddProductButton = ({}: // setUseInvoice, -{ - // setUseInvoice?: (useInvoice: boolean) => void; - // handleCreateProduct: (useInvoice?: boolean) => Promise; - // actionState: any; -}) => { +export const AddProductButton = () => { const [checkoutLoading, setCheckoutLoading] = useState(false); const [invoiceLoading, setInvoiceLoading] = useState(false); const [open, setOpen] = useState(false); diff --git a/vite/src/views/customers/customer/customer-sidebar/customer-entities.tsx b/vite/src/views/customers/customer/customer-sidebar/customer-entities.tsx index a8685742c..0b573bf22 100644 --- a/vite/src/views/customers/customer/customer-sidebar/customer-entities.tsx +++ b/vite/src/views/customers/customer/customer-sidebar/customer-entities.tsx @@ -14,7 +14,7 @@ export const CustomerEntities = () => { const entity = entities.find((entity: Entity) => entity.id === entityId); const feature = features.find( - (feature: Feature) => entity?.internal_feature_id === feature.internal_id + (feature: Feature) => entity?.internal_feature_id === feature.internal_id, ); const featureName = getFeatureName({ diff --git a/vite/src/views/customers/customer/product/CustomerProductView.tsx b/vite/src/views/customers/customer/product/CustomerProductView.tsx index 22ad5a3bb..89575c4b7 100644 --- a/vite/src/views/customers/customer/product/CustomerProductView.tsx +++ b/vite/src/views/customers/customer/product/CustomerProductView.tsx @@ -108,8 +108,8 @@ export default function CustomerProductView() { } }, [entityIdParam]); - let version = searchParams.get("version"); - let customer_product_id = searchParams.get("id"); + const version = searchParams.get("version"); + const customer_product_id = searchParams.get("id"); const { data, isLoading, mutate, error } = useAxiosSWR({ url: `/customers/${customer_id}/product/${product_id}${getProductUrlParams({ version, @@ -143,7 +143,7 @@ export default function CustomerProductView() { useEffect(() => { if (!data?.product || !data?.customer) return; - let product = data.product; + const product = data.product; initialProductRef.current = structuredClone(product); if (product.options) { @@ -192,7 +192,7 @@ export default function CustomerProductView() { if (isLoading) return ; const oneTimePurchase = pricesOnlyOneOff( product?.items || [], - product?.is_add_on || false + product?.is_add_on || false, ); if (!customer_id || !product_id) { @@ -220,7 +220,7 @@ export default function CustomerProductView() { // Continue with product creation if no required options await createProduct( - useInvoiceLatest !== undefined ? useInvoiceLatest : useInvoice + useInvoiceLatest !== undefined ? useInvoiceLatest : useInvoice, ); } catch (error) { toast.error(getBackendErr(error, "Error checking required options")); @@ -229,7 +229,7 @@ export default function CustomerProductView() { const createProduct = async (useInvoiceLatest?: boolean) => { try { - let isCustom = hasChanges; + const isCustom = hasChanges; const { data } = await CusService.addProduct(axiosInstance, customer_id, { product_id, @@ -255,7 +255,7 @@ export default function CustomerProductView() { entity_id: entityId, })}`, navigation, - env + env, ); toast.success(data.message || "Successfully attached product"); @@ -281,7 +281,7 @@ export default function CustomerProductView() { navigateTo( `/integrations/stripe?redirect=${redirectUrl}`, navigation, - env + env, ); } else { toast.error(getBackendErr(error, "Error creating product")); @@ -422,15 +422,15 @@ export default function CustomerProductView() { entityId ? `?entity_id=${entityId}` : "" }`, navigation, - env + env, ) } > {customer.name ? customer.name : customer.id - ? customer.id - : customer.email} + ? customer.id + : customer.email} {product.name} @@ -438,14 +438,7 @@ export default function CustomerProductView() {
- {product && ( - {}} - version={version ? parseInt(version) : product.version} - /> - )} + {product && } {options.length > 0 && ( { - console.log("Create Metered Feature"); - }; - - return ( -
-

DB Connections

- - {dbConns?.map((dbConn: DBConnection) => ( -

- {dbConn.display_name}

- ))} -

Features

- - {features - ?.filter((feature: Feature) => feature.type === FeatureType.Metered) - .map((feature: Feature) => ( -

- {feature.name}

- ))} - - -
- ); -} - -export default MeteredView; diff --git a/vite/src/views/onboarding/OnboardingView.tsx b/vite/src/views/onboarding/OnboardingView.tsx index 67dec6572..b563239e6 100644 --- a/vite/src/views/onboarding/OnboardingView.tsx +++ b/vite/src/views/onboarding/OnboardingView.tsx @@ -2,7 +2,7 @@ import { useState, useEffect, useRef } from "react"; import { toast } from "sonner"; -import { useOrganization } from "@clerk/clerk-react"; +import { useOrganization, useOrganizationList } from "@clerk/clerk-react"; import { useSearchParams } from "react-router"; import Step from "@/components/general/OnboardingStep"; import { AppEnv } from "@autumn/shared"; @@ -18,11 +18,15 @@ import AttachProduct from "./onboarding-steps/04_AttachProduct"; import CheckAccessStep from "./onboarding-steps/05_CheckAccess"; import Install from "./onboarding-steps/Install"; import LoadingScreen from "../general/LoadingScreen"; +import { ProductList } from "./onboarding-steps/03_ProductList"; +import { useCreateOrg } from "./hooks/useCreateOrg"; function OnboardingView() { const env = useEnv(); const { organization: org } = useOrganization(); + const { setActive } = useOrganizationList(); + const [searchParams] = useSearchParams(); const [orgCreated, setOrgCreated] = useState(org ? true : false); @@ -31,30 +35,9 @@ function OnboardingView() { const hasHandledOrg = useRef(false); const hasHandledToken = useRef(false); - const axiosInstance = useAxiosInstance({ env }); - - const [loading, setLoading] = useState( - searchParams.get("token") ? true : false, - ); - - const handleToken = async () => { - if (searchParams.get("token")) { - console.log("Token: ", searchParams.get("token")); - axiosInstance.post("/onboarding", { - token: searchParams.get("token"), - }); - - // await new Promise((resolve) => setTimeout(resolve, 3000)); - setLoading(false); - } - }; - - useEffect(() => { - if (searchParams.get("token") && !hasHandledToken.current) { - hasHandledToken.current = true; - handleToken(); - } - }, [searchParams]); + const axiosInstance = useAxiosInstance(); + const token = searchParams.get("token"); + const [loading, setLoading] = useState(true); const { data: productData, @@ -66,69 +49,55 @@ function OnboardingView() { withAuth: true, }); - const pollForOrg = async () => { - for (let i = 0; i < 10; i++) { - console.log("polling for org, attempt", i); - const requiredProdLength = env == AppEnv.Sandbox ? 2 : 0; + useEffect(() => { + const handleToken = async () => { try { - const response = await axiosInstance.get("/products/data"); - const pollingData = response.data; + const { data } = await axiosInstance.post("/onboarding", { + token, + }); - if (pollingData?.products.length != requiredProdLength) { - throw new Error("Products not created"); - } - setOrgCreated(true); await productMutate(); - return; } catch (error) { - console.log("error", error); + console.error(error); + } finally { + setLoading(false); } - await new Promise((resolve) => setTimeout(resolve, 1000)); - } + }; - window.location.reload(); - }; + if (org && token && !hasHandledToken.current) { + hasHandledToken.current = true; + handleToken(); + } + }, [org, searchParams, token, axiosInstance, productMutate]); useEffect(() => { - const toastMessage = searchParams.get("toast"); - if (toastMessage) { - toast.error(toastMessage); + if (org && !token) { + setLoading(false); } - }, [searchParams]); + }, [org, token]); - useEffect(() => { - if (org && !orgCreated && !hasHandledOrg.current) { - hasHandledOrg.current = true; - pollForOrg(); - } - }, [org, orgCreated]); + useCreateOrg({ productMutate }); - if (loading) { + if (loading || productLoading) { return ; } return (
- - {orgCreated && ( + {productData && ( <> - - - + + - + - + diff --git a/vite/src/views/onboarding/hooks/useCreateOrg.tsx b/vite/src/views/onboarding/hooks/useCreateOrg.tsx new file mode 100644 index 000000000..d40a9eda0 --- /dev/null +++ b/vite/src/views/onboarding/hooks/useCreateOrg.tsx @@ -0,0 +1,31 @@ +import { useOrganization } from "@clerk/clerk-react"; + +import { useAxiosInstance } from "@/services/useAxiosInstance"; +import { useOrganizationList } from "@clerk/clerk-react"; +import { useEffect, useRef } from "react"; + +export const useCreateOrg = ({ + productMutate, +}: { + productMutate: () => Promise; +}) => { + const axiosInstance = useAxiosInstance(); + const { organization: org } = useOrganization(); + const { setActive } = useOrganizationList(); + const hasCreatedOrg = useRef(false); + + useEffect(() => { + const createDefaultOrg = async () => { + if (hasCreatedOrg.current) return; + + hasCreatedOrg.current = true; + const { data } = await axiosInstance.post("/organization"); + await setActive?.({ organization: data.id }); + await productMutate(); + }; + + if (!org) { + createDefaultOrg(); + } + }, [org]); +}; diff --git a/vite/src/views/onboarding/onboarding-steps/03_CreateProduct.tsx b/vite/src/views/onboarding/onboarding-steps/03_CreateProduct.tsx index b40481af9..61231b104 100644 --- a/vite/src/views/onboarding/onboarding-steps/03_CreateProduct.tsx +++ b/vite/src/views/onboarding/onboarding-steps/03_CreateProduct.tsx @@ -11,9 +11,7 @@ import { useEnv } from "@/utils/envUtils"; import { getBackendErr } from "@/utils/genUtils"; import { toast } from "sonner"; import { ProductContext } from "@/views/products/product/ProductContext"; -import { ManageProduct } from "@/views/products/product/ManageProduct"; import { ProductItemTable } from "@/views/products/product/product-item/ProductItemTable"; -import { ArrowUp, CopyIcon, PlusIcon } from "lucide-react"; import { Input } from "@/components/ui/input"; import { slugify } from "@/utils/formatUtils/formatTextUtils"; @@ -26,12 +24,12 @@ export const CreateProductStep = ({ setProductId: (productId: string) => void; number: number; }) => { - let [newProduct, setNewProduct] = useState(defaultProduct); - let [createClicked, setCreateClicked] = useState(false); - let [createProductLoading, setCreateProductLoading] = useState(false); + const env = useEnv(); + const axiosInstance = useAxiosInstance({ env }); - let env = useEnv(); - let axiosInstance = useAxiosInstance({ env }); + const [newProduct, setNewProduct] = useState(defaultProduct); + const [createClicked, setCreateClicked] = useState(false); + const [createProductLoading, setCreateProductLoading] = useState(false); const { data, isLoading, mutate } = useAxiosSWR({ url: `/products/${newProduct.id}/data`, @@ -60,7 +58,7 @@ export const CreateProductStep = ({ const res = await ProductService.updateProduct( axiosInstance, productId, - product + product, ); toast.success("Product items successfully created"); await mutate(); @@ -91,12 +89,6 @@ export const CreateProductStep = ({

} > - {/*

- Then, create your Products, which - are the pricing plans that grant access to those features. -

-

Some examples have been created for you.

*/} - {product ? ( Promise; +}) => { + const env = useEnv(); + + const [searchParams] = useSearchParams(); + const token = searchParams.get("token"); + + const [product, setProduct] = useState(data.products[0]); + const [features, setFeatures] = useState(data.features); + const [open, setOpen] = useState(false); + + if (!data.products) return null; + + return ( + + Products define the features your customers can access and how much + they cost. Create your first product to get started ☝️. +

+ } + > + + + + {/* */} + { + await mutate(); + setProduct(newProduct); + setOpen(true); + }} + /> + + } + className="pr-0" + /> + + { + setProduct(data.products.find((p: ProductV2) => p.id === id)); + setOpen(true); + }} + /> + +
+ ); +}; + +const EditProductDialog = ({ + product, + features, + setProduct, + setFeatures, + mutate, + open, + setOpen, +}: { + product: any; + setProduct: (product: any) => void; + features: any[]; + setFeatures: (features: any[]) => void; + mutate: () => Promise; + open: boolean; + setOpen: (open: boolean) => void; +}) => { + const env = useEnv(); + const axiosInstance = useAxiosInstance(); + const [createProductLoading, setCreateProductLoading] = useState(false); + + const updateProduct = async () => { + setCreateProductLoading(true); + setCreateProductLoading(true); + try { + const res = await ProductService.updateProduct( + axiosInstance, + product.id, + product, + ); + toast.success("Product items successfully created"); + await mutate(); + setOpen(false); + } catch (error) { + toast.error(getBackendErr(error, "Failed to update product")); + } + setCreateProductLoading(false); + setCreateProductLoading(false); + }; + return ( + + + + {/* Edit Product */} + +
+ + + + + +
+ +
+ +
+
+
+
+ ); +}; diff --git a/vite/src/views/products/CreateProduct.tsx b/vite/src/views/products/CreateProduct.tsx index 4d51ecb01..98c39ec65 100644 --- a/vite/src/views/products/CreateProduct.tsx +++ b/vite/src/views/products/CreateProduct.tsx @@ -11,48 +11,63 @@ import { ProductService } from "@/services/products/ProductService"; import { useNavigate } from "react-router"; import { useAxiosInstance } from "@/services/useAxiosInstance"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { toast } from "sonner"; import { useProductsContext } from "./ProductsContext"; import { PlusIcon } from "lucide-react"; import { getBackendErr, navigateTo } from "@/utils/genUtils"; import { ProductConfig } from "./ProductConfig"; +import { ProductV2 } from "@autumn/shared"; -export let defaultProduct = { +export const defaultProduct = { name: "", id: "", group: "", is_add_on: false, is_default: false, }; -function CreateProduct() { - const { env, mutate } = useProductsContext(); - const axiosInstance = useAxiosInstance({ env }); - const navigate = useNavigate(); +function CreateProduct({ + onSuccess, +}: { + onSuccess?: (newProduct: ProductV2) => Promise; +}) { + const { env, mutate } = useProductsContext(); const [loading, setLoading] = useState(false); const [product, setProduct] = useState(defaultProduct); - - const [idChanged, setIdChanged] = useState(false); const [open, setOpen] = useState(false); + const axiosInstance = useAxiosInstance({ env }); + const navigate = useNavigate(); + const handleCreateClicked = async () => { setLoading(true); try { - const productId = await ProductService.createProduct( + const newProduct = await ProductService.createProduct( axiosInstance, - product + product, ); await mutate(); - navigateTo(`/products/${productId}`, navigate, env); + if (onSuccess) { + await onSuccess(newProduct); + } else { + navigateTo(`/products/${newProduct.id}`, navigate, env); + } + setOpen(false); } catch (error) { toast.error(getBackendErr(error, "Failed to create product")); } setLoading(false); }; + useEffect(() => { + if (open) { + setProduct(defaultProduct); + } + }, [open]); + return ( diff --git a/vite/src/views/products/ProductsTable.tsx b/vite/src/views/products/ProductsTable.tsx index 43c5d8258..3c3253b2f 100644 --- a/vite/src/views/products/ProductsTable.tsx +++ b/vite/src/views/products/ProductsTable.tsx @@ -1,22 +1,23 @@ import { formatUnixToDateTime } from "@/utils/formatUtils/formatDateUtils"; import { Product } from "@autumn/shared"; import { useNavigate } from "react-router"; -import { ProductRowToolbar } from "./ProductRowToolbar"; +import { ProductRowToolbar } from "./components/ProductRowToolbar"; import { navigateTo } from "@/utils/genUtils"; import { useProductsContext } from "./ProductsContext"; -import { Badge } from "@/components/ui/badge"; import { AdminHover } from "@/components/general/AdminHover"; -import { - Tooltip, - TooltipTrigger, - TooltipContent, - TooltipProvider, -} from "@/components/ui/tooltip"; -import { keyToTitle } from "@/utils/formatUtils/formatTextUtils"; import { Item, Row } from "@/components/general/TableGrid"; import CopyButton from "@/components/general/CopyButton"; +import { cn } from "@/lib/utils"; +import { ProductCountsTooltip } from "./components/ProductCountsTooltip"; +import { ProductTypeBadge } from "./components/ProductTypeBadge"; -export const ProductsTable = ({ products }: { products: Product[] }) => { +export const ProductsTable = ({ + products, + onRowClick, +}: { + products: Product[]; + onRowClick?: (id: string) => void; +}) => { const { env, onboarding } = useProductsContext(); const navigate = useNavigate(); const { allCounts } = useProductsContext(); @@ -24,28 +25,53 @@ export const ProductsTable = ({ products }: { products: Product[] }) => { return ( <> {products && products.length > 0 ? ( - + Name Product ID - Active - Type - {!onboarding ? "Group" : ""} - {!onboarding ? "Created At" : ""} - + {!onboarding && ( + <> + Active + Type + Group + Created At + + )} + ) : ( -
- Products define the features your customers can access and how much - they cost. Create your first product to get started ☝️. -
+ !onboarding && ( +
+ Products define the features your customers can access and how much + they cost. Create your first product to get started ☝️. +
+ ) )} {products && products.map((product) => ( navigateTo(`/products/${product.id}`, navigate, env)} + className={cn( + "grid-cols-18 gap-2 items-center text-sm cursor-pointer hover:bg-primary/5 text-t2 whitespace-nowrap", + onboarding && "grid-cols-12", + )} + isOnboarding={onboarding} + onClick={() => { + if (onRowClick) { + onRowClick(product.id); + } else { + navigateTo(`/products/${product.id}`, navigate, env); + } + }} > { {product.id} - - - - -

- {(allCounts && allCounts[product.id]?.active) || 0} -

-
- - {allCounts && - allCounts[product.id] && - Object.keys(allCounts[product.id]).map((key) => { - if (key === "active" || key == "custom" || key == "all") - return null; - return ( -
- {keyToTitle(key)}: {allCounts[product.id][key]} -
- ); - })} -
-
-
-
- - {product.is_default ? ( - Default - ) : product.is_add_on ? ( - Add-On - ) : ( - <> - )} - - {!onboarding && product.group} - - {!onboarding && ( - <> + {!onboarding && ( + <> + + + + + + + + {!onboarding && product.group} + + {formatUnixToDateTime(product.created_at).date} - {/* - {" "} - {formatUnixToDateTime(product.created_at).time} - */} - + + + )} + - + >
diff --git a/vite/src/views/products/ProductsView.tsx b/vite/src/views/products/ProductsView.tsx index 04684db93..cccde94d6 100644 --- a/vite/src/views/products/ProductsView.tsx +++ b/vite/src/views/products/ProductsView.tsx @@ -27,7 +27,6 @@ import { FeaturesContext } from "../features/FeaturesContext"; function ProductsView({ env }: { env: AppEnv }) { const [tab, setTab] = useState("products"); const [selectedProduct, setSelectedProduct] = useState(null); - const [showCredits, setShowCredits] = useState(false); const { data, isLoading, mutate } = useAxiosSWR({ url: `/products/data`, env: env, @@ -54,7 +53,7 @@ function ProductsView({ env }: { env: AppEnv }) { const creditSystems = featuresData?.features?.filter( - (f: Feature) => f.type === "credit_system" + (f: Feature) => f.type === "credit_system", ) || []; if (isLoading) return ; @@ -75,11 +74,11 @@ function ProductsView({ env }: { env: AppEnv }) { value={{ features: featuresData?.features?.filter( - (f: Feature) => f.type !== "credit_system" + (f: Feature) => f.type !== "credit_system", ) || [], creditSystems: featuresData?.features?.filter( - (f: Feature) => f.type === "credit_system" + (f: Feature) => f.type === "credit_system", ) || [], dbConns: featuresData?.dbConns || [], env, diff --git a/vite/src/views/products/CopyDialog.tsx b/vite/src/views/products/components/CopyDialog.tsx similarity index 96% rename from vite/src/views/products/CopyDialog.tsx rename to vite/src/views/products/components/CopyDialog.tsx index 5f19b0610..116209427 100644 --- a/vite/src/views/products/CopyDialog.tsx +++ b/vite/src/views/products/components/CopyDialog.tsx @@ -19,7 +19,7 @@ import { SelectValue, } from "@/components/ui/select"; import { Select } from "@/components/ui/select"; -import { useProductsContext } from "./ProductsContext"; +import { useProductsContext } from "../ProductsContext"; export const CopyDialog = ({ product, @@ -36,7 +36,7 @@ export const CopyDialog = ({ const [name, setName] = useState(product.name); const [id, setId] = useState(product.id); const [toEnv, setToEnv] = useState( - env == AppEnv.Live ? AppEnv.Sandbox : AppEnv.Live + env == AppEnv.Live ? AppEnv.Sandbox : AppEnv.Live, ); const handleCopy = async () => { diff --git a/vite/src/views/products/components/ProductCountsTooltip.tsx b/vite/src/views/products/components/ProductCountsTooltip.tsx new file mode 100644 index 000000000..ee5274ea6 --- /dev/null +++ b/vite/src/views/products/components/ProductCountsTooltip.tsx @@ -0,0 +1,45 @@ +import { keyToTitle } from "@/utils/formatUtils/formatTextUtils"; +import { + TooltipProvider, + Tooltip, + TooltipTrigger, + TooltipContent, +} from "@/components/ui/tooltip"; +import { Product } from "@autumn/shared"; + +export const ProductCountsTooltip = ({ + allCounts, + product, +}: { + allCounts: any; + product: Product; +}) => { + return ( + + + +

+ {(allCounts && allCounts[product.id]?.active) || 0} +

+
+ + {allCounts && + allCounts[product.id] && + Object.keys(allCounts[product.id]).map((key) => { + if (key === "active" || key == "custom" || key == "all") + return null; + return ( +
+ {keyToTitle(key)}: {allCounts[product.id][key]} +
+ ); + })} +
+
+
+ ); +}; diff --git a/vite/src/views/products/ProductRowToolbar.tsx b/vite/src/views/products/components/ProductRowToolbar.tsx similarity index 97% rename from vite/src/views/products/ProductRowToolbar.tsx rename to vite/src/views/products/components/ProductRowToolbar.tsx index 3c1190e91..50b3228bb 100644 --- a/vite/src/views/products/ProductRowToolbar.tsx +++ b/vite/src/views/products/components/ProductRowToolbar.tsx @@ -11,12 +11,12 @@ import { toast } from "sonner"; import { Product } from "@autumn/shared"; import { ProductService } from "@/services/products/ProductService"; -import { useProductsContext } from "./ProductsContext"; +import { useProductsContext } from "../ProductsContext"; import { useAxiosInstance } from "@/services/useAxiosInstance"; import { getBackendErr } from "@/utils/genUtils"; import { ToolbarButton } from "@/components/general/table-components/ToolbarButton"; import { Dialog, DialogTrigger } from "@/components/ui/dialog"; -import { UpdateProductDialog } from "./UpdateProduct"; +import { UpdateProductDialog } from "../UpdateProduct"; import { CopyDialog } from "./CopyDialog"; import { Copy, Delete, Pen } from "lucide-react"; diff --git a/vite/src/views/products/components/ProductTypeBadge.tsx b/vite/src/views/products/components/ProductTypeBadge.tsx new file mode 100644 index 000000000..5709fb1be --- /dev/null +++ b/vite/src/views/products/components/ProductTypeBadge.tsx @@ -0,0 +1,16 @@ +import { Badge } from "@/components/ui/badge"; +import { Product } from "@autumn/shared"; + +export const ProductTypeBadge = ({ product }: { product: Product }) => { + return ( + <> + {product.is_default ? ( + Default + ) : product.is_add_on ? ( + Add-On + ) : ( + <> + )} + + ); +}; diff --git a/vite/src/views/products/product/ManageProduct.tsx b/vite/src/views/products/product/ManageProduct.tsx index 992006378..df1a98e74 100644 --- a/vite/src/views/products/product/ManageProduct.tsx +++ b/vite/src/views/products/product/ManageProduct.tsx @@ -6,31 +6,19 @@ import { ProductItemTable } from "./product-item/ProductItemTable"; import { SelectEntity } from "@/views/customers/customer/customer-sidebar/select-entity"; export const ManageProduct = ({ - customerData, - showFreeTrial, - setShowFreeTrial, - version, + hideAdminHover = false, }: { - customerData?: any; - showFreeTrial: boolean; - setShowFreeTrial: (showFreeTrial: boolean) => void; - version?: number; + hideAdminHover?: boolean; }) => { const env = useEnv(); - let { product, entityId, customer } = useProductContext(); - - const navigate = useNavigate(); - - // const entity = customer?.entities.find( - // (entity: Entity) => entity.id === entityId - // ); + const { isOnboarding, product, entityId, customer } = useProductContext(); return (
- +

{product.name}

diff --git a/vite/src/views/products/product/ProductView.tsx b/vite/src/views/products/product/ProductView.tsx index 255fb4c5f..c120b917b 100644 --- a/vite/src/views/products/product/ProductView.tsx +++ b/vite/src/views/products/product/ProductView.tsx @@ -63,7 +63,7 @@ function ProductView({ env }: { env: AppEnv }) { const [selectedEntitlementAllowance, setSelectedEntitlementAllowance] = useState<"unlimited" | number>(0); const [originalProduct, setOriginalProduct] = useState( - null + null, ); useEffect(() => { @@ -119,8 +119,8 @@ function ProductView({ env }: { env: AppEnv }) { ? "Add entitlements and prices to create a new product" : `Make a change to the entitlements or prices to update ${product?.name}` : isNewProduct - ? `Create a new product: ${product?.name} ` - : `Save changes to product: ${product?.name}`, + ? `Create a new product: ${product?.name} ` + : `Save changes to product: ${product?.name}`, }; if (isLoading) return ; @@ -209,11 +209,7 @@ function ProductView({ env }: { env: AppEnv }) {
- +
diff --git a/vite/src/views/products/product/product-item/ProductItemTable.tsx b/vite/src/views/products/product/product-item/ProductItemTable.tsx index 8b903a755..01e788aa1 100644 --- a/vite/src/views/products/product/product-item/ProductItemTable.tsx +++ b/vite/src/views/products/product/product-item/ProductItemTable.tsx @@ -31,10 +31,11 @@ export const ProductItemTable = ({ }: { isOnboarding?: boolean; }) => { - let { product, setProduct, features, org } = useProductContext(); - let [selectedItem, setSelectedItem] = useState(null); - let [selectedIndex, setSelectedIndex] = useState(null); - let [open, setOpen] = useState(false); + const { product, setProduct, features, org } = useProductContext(); + const [selectedItem, setSelectedItem] = useState(null); + const [selectedIndex, setSelectedIndex] = useState(null); + const [open, setOpen] = useState(false); + const getFreeFeatureString = (item: ProductItem) => { const feature = features.find((f: Feature) => f.id == item.feature_id); @@ -85,7 +86,7 @@ export const ProductItemTable = ({ })}`; } - let feature = features.find((f: Feature) => f.id == item.feature_id); + const feature = features.find((f: Feature) => f.id == item.feature_id); amountStr += ` per ${item.billing_units! > 1 ? item.billing_units : ""} ${ feature?.name @@ -103,8 +104,8 @@ export const ProductItemTable = ({ }; const getFixedPriceString = (item: ProductItem) => { - let currency = org?.default_currency || "USD"; - let formattedAmount = formatAmount({ + const currency = org?.default_currency || "USD"; + const formattedAmount = formatAmount({ defaultCurrency: currency, amount: item.price!, }); @@ -116,7 +117,7 @@ export const ProductItemTable = ({ return `${formattedAmount}`; }; - let handleRowClick = (item: ProductItem, index: number) => { + const handleRowClick = (item: ProductItem, index: number) => { setSelectedItem(item); setSelectedIndex(index); setOpen(true); @@ -175,7 +176,7 @@ export const ProductItemTable = ({

@@ -189,14 +190,14 @@ export const ProductItemTable = ({

{product.items.map((item: ProductItem, index: number) => { - let itemType = getItemType(item); + const itemType = getItemType(item); return (
handleRowClick(item, index)} > @@ -212,8 +213,8 @@ export const ProductItemTable = ({ {itemType === ProductItemType.Feature ? getFreeFeatureString(item) : itemType === ProductItemType.Price - ? getFixedPriceString(item) - : getPaidFeatureString(item)} + ? getFixedPriceString(item) + : getPaidFeatureString(item)} @@ -223,7 +224,7 @@ export const ProductItemTable = ({ "text-xs flex gap-1 items-center opacity-0", (itemType === ProductItemType.Feature || itemType === ProductItemType.FeaturePrice) && - "opacity-100" + "opacity-100", )} > Feature @@ -235,7 +236,7 @@ export const ProductItemTable = ({ "text-xs flex gap-1 items-center opacity-0", (itemType === ProductItemType.Price || itemType === ProductItemType.FeaturePrice) && - "opacity-100" + "opacity-100", )} > Price