From b08a6e11138c41956836015e4de4caa74331c46e Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Thu, 30 Oct 2025 17:05:11 -0700 Subject: [PATCH 01/37] Don't prefetch pixel links. --- src/app/(main)/pixels/[pixelId]/PixelHeader.tsx | 2 +- src/components/common/LinkButton.tsx | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/app/(main)/pixels/[pixelId]/PixelHeader.tsx b/src/app/(main)/pixels/[pixelId]/PixelHeader.tsx index 7e1a62b87..a49729910 100644 --- a/src/app/(main)/pixels/[pixelId]/PixelHeader.tsx +++ b/src/app/(main)/pixels/[pixelId]/PixelHeader.tsx @@ -11,7 +11,7 @@ export function PixelHeader() { return ( } marginBottom="3"> - + diff --git a/src/components/common/LinkButton.tsx b/src/components/common/LinkButton.tsx index ca1baccbc..251f77626 100644 --- a/src/components/common/LinkButton.tsx +++ b/src/components/common/LinkButton.tsx @@ -8,6 +8,7 @@ export interface LinkButtonProps extends ButtonProps { target?: string; scroll?: boolean; variant?: any; + prefetch?: boolean; children?: ReactNode; } @@ -16,6 +17,7 @@ export function LinkButton({ variant, scroll = true, target, + prefetch, children, ...props }: LinkButtonProps) { @@ -23,7 +25,7 @@ export function LinkButton({ return ( From 067567cbf69ac0838c27e38548eead5a15c4b41d Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 31 Oct 2025 00:13:02 -0700 Subject: [PATCH 02/37] Force geo build with BUILD_GEO env var. --- package.json | 16 +-- pnpm-lock.yaml | 328 +++++++++++++++++++++---------------------- scripts/build-geo.js | 2 +- src/lib/detect.ts | 65 +-------- src/lib/ip.ts | 63 +++++++++ 5 files changed, 237 insertions(+), 237 deletions(-) create mode 100644 src/lib/ip.ts diff --git a/package.json b/package.json index 055b98e3b..4c2f1df8d 100644 --- a/package.json +++ b/package.json @@ -72,8 +72,8 @@ "@dicebear/core": "^9.2.3", "@fontsource/inter": "^5.2.8", "@hello-pangea/dnd": "^17.0.0", - "@prisma/adapter-pg": "^6.17.1", - "@prisma/client": "^6.17.1", + "@prisma/adapter-pg": "^6.18.0", + "@prisma/client": "^6.18.0", "@prisma/extension-read-replicas": "^0.4.1", "@react-spring/web": "^10.0.3", "@svgr/cli": "^8.1.0", @@ -97,7 +97,7 @@ "esbuild": "^0.25.11", "eslint-plugin-promise": "^6.1.1", "fs-extra": "^11.3.2", - "immer": "^10.1.3", + "immer": "^10.2.0", "ipaddr.js": "^2.0.1", "is-ci": "^3.0.1", "is-docker": "^3.0.0", @@ -113,7 +113,7 @@ "npm-run-all": "^4.1.5", "papaparse": "^5.5.3", "pg": "^8.16.3", - "prisma": "6.17.1", + "prisma": "^6.18.0", "pure-rand": "^7.0.1", "react": "^19.2.0", "react-dom": "^19.2.0", @@ -133,16 +133,16 @@ }, "devDependencies": { "@formatjs/cli": "^4.2.29", - "@netlify/plugin-nextjs": "^5.14.2", + "@netlify/plugin-nextjs": "^5.14.4", "@rollup/plugin-alias": "^5.0.0", "@rollup/plugin-commonjs": "^25.0.4", "@rollup/plugin-json": "^6.0.0", "@rollup/plugin-node-resolve": "^15.2.0", "@rollup/plugin-replace": "^5.0.2", "@rollup/plugin-terser": "^0.4.4", - "@rollup/plugin-typescript": "^12.1.4", + "@rollup/plugin-typescript": "^12.3.0", "@types/jest": "^30.0.0", - "@types/node": "^24.9.1", + "@types/node": "^24.9.2", "@types/react": "^19.2.2", "@types/react-dom": "^19.2.2", "@types/react-window": "^1.8.8", @@ -163,7 +163,7 @@ "extract-react-intl-messages": "^4.1.1", "husky": "^9.1.7", "jest": "^29.7.0", - "lint-staged": "^16.2.5", + "lint-staged": "^16.2.6", "postcss": "^8.5.6", "postcss-flexbugs-fixes": "^5.0.2", "postcss-import": "^15.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 70ff5f20b..f9656c418 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,14 +27,14 @@ importers: specifier: ^17.0.0 version: 17.0.0(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@prisma/adapter-pg': - specifier: ^6.17.1 - version: 6.17.1 + specifier: ^6.18.0 + version: 6.18.0 '@prisma/client': - specifier: ^6.17.1 - version: 6.17.1(prisma@6.17.1(typescript@5.9.3))(typescript@5.9.3) + specifier: ^6.18.0 + version: 6.18.0(prisma@6.18.0(typescript@5.9.3))(typescript@5.9.3) '@prisma/extension-read-replicas': specifier: ^0.4.1 - version: 0.4.1(@prisma/client@6.17.1(prisma@6.17.1(typescript@5.9.3))(typescript@5.9.3)) + version: 0.4.1(@prisma/client@6.18.0(prisma@6.18.0(typescript@5.9.3))(typescript@5.9.3)) '@react-spring/web': specifier: ^10.0.3 version: 10.0.3(react-dom@19.2.0(react@19.2.0))(react@19.2.0) @@ -46,7 +46,7 @@ importers: version: 5.90.5(react@19.2.0) '@umami/react-zen': specifier: ^0.203.0 - version: 0.203.0(@babel/core@7.28.3)(@types/react@19.2.2)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@10.1.3)(use-sync-external-store@1.6.0(react@19.2.0)) + version: 0.203.0(@babel/core@7.28.3)(@types/react@19.2.2)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@10.2.0)(use-sync-external-store@1.6.0(react@19.2.0)) '@umami/redis-client': specifier: ^0.29.0 version: 0.29.0 @@ -102,8 +102,8 @@ importers: specifier: ^11.3.2 version: 11.3.2 immer: - specifier: ^10.1.3 - version: 10.1.3 + specifier: ^10.2.0 + version: 10.2.0 ipaddr.js: specifier: ^2.0.1 version: 2.2.0 @@ -150,8 +150,8 @@ importers: specifier: ^8.16.3 version: 8.16.3 prisma: - specifier: 6.17.1 - version: 6.17.1(typescript@5.9.3) + specifier: 6.18.0 + version: 6.18.0(typescript@5.9.3) pure-rand: specifier: ^7.0.1 version: 7.0.1 @@ -199,14 +199,14 @@ importers: version: 4.1.12 zustand: specifier: ^5.0.8 - version: 5.0.8(@types/react@19.2.2)(immer@10.1.3)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)) + version: 5.0.8(@types/react@19.2.2)(immer@10.2.0)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)) devDependencies: '@formatjs/cli': specifier: ^4.2.29 - version: 4.8.4(ts-jest@29.4.5(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@30.0.5)(babel-jest@29.7.0(@babel/core@7.28.3))(esbuild@0.25.11)(jest-util@30.0.5)(jest@29.7.0(@types/node@24.9.1)(ts-node@10.9.2(@types/node@24.9.1)(typescript@5.9.3)))(typescript@5.9.3)) + version: 4.8.4(ts-jest@29.4.5(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@30.0.5)(babel-jest@29.7.0(@babel/core@7.28.3))(esbuild@0.25.11)(jest-util@30.0.5)(jest@29.7.0(@types/node@24.9.2)(ts-node@10.9.2(@types/node@24.9.2)(typescript@5.9.3)))(typescript@5.9.3)) '@netlify/plugin-nextjs': - specifier: ^5.14.2 - version: 5.14.2 + specifier: ^5.14.4 + version: 5.14.4 '@rollup/plugin-alias': specifier: ^5.0.0 version: 5.1.1(rollup@4.52.5) @@ -226,14 +226,14 @@ importers: specifier: ^0.4.4 version: 0.4.4(rollup@4.52.5) '@rollup/plugin-typescript': - specifier: ^12.1.4 - version: 12.1.4(rollup@4.52.5)(tslib@2.8.1)(typescript@5.9.3) + specifier: ^12.3.0 + version: 12.3.0(rollup@4.52.5)(tslib@2.8.1)(typescript@5.9.3) '@types/jest': specifier: ^30.0.0 version: 30.0.0 '@types/node': - specifier: ^24.9.1 - version: 24.9.1 + specifier: ^24.9.2 + version: 24.9.2 '@types/react': specifier: ^19.2.2 version: 19.2.2 @@ -281,22 +281,22 @@ importers: version: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) eslint-plugin-jest: specifier: ^27.9.0 - version: 27.9.0(@typescript-eslint/eslint-plugin@8.46.2(@typescript-eslint/parser@8.46.2(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(jest@29.7.0(@types/node@24.9.1)(ts-node@10.9.2(@types/node@24.9.1)(typescript@5.9.3)))(typescript@5.9.3) + version: 27.9.0(@typescript-eslint/eslint-plugin@8.46.2(@typescript-eslint/parser@8.46.2(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(jest@29.7.0(@types/node@24.9.2)(ts-node@10.9.2(@types/node@24.9.2)(typescript@5.9.3)))(typescript@5.9.3) eslint-plugin-prettier: specifier: ^5.5.3 version: 5.5.4(eslint-config-prettier@10.1.8(eslint@8.57.1))(eslint@8.57.1)(prettier@3.6.2) extract-react-intl-messages: specifier: ^4.1.1 - version: 4.1.1(ts-jest@29.4.5(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@30.0.5)(babel-jest@29.7.0(@babel/core@7.28.3))(esbuild@0.25.11)(jest-util@30.0.5)(jest@29.7.0(@types/node@24.9.1)(ts-node@10.9.2(@types/node@24.9.1)(typescript@5.9.3)))(typescript@5.9.3)) + version: 4.1.1(ts-jest@29.4.5(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@30.0.5)(babel-jest@29.7.0(@babel/core@7.28.3))(esbuild@0.25.11)(jest-util@30.0.5)(jest@29.7.0(@types/node@24.9.2)(ts-node@10.9.2(@types/node@24.9.2)(typescript@5.9.3)))(typescript@5.9.3)) husky: specifier: ^9.1.7 version: 9.1.7 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@24.9.1)(ts-node@10.9.2(@types/node@24.9.1)(typescript@5.9.3)) + version: 29.7.0(@types/node@24.9.2)(ts-node@10.9.2(@types/node@24.9.2)(typescript@5.9.3)) lint-staged: - specifier: ^16.2.5 - version: 16.2.5 + specifier: ^16.2.6 + version: 16.2.6 postcss: specifier: ^8.5.6 version: 8.5.6 @@ -335,7 +335,7 @@ importers: version: 2.2.4(rollup@4.52.5) rollup-plugin-postcss: specifier: ^4.0.2 - version: 4.0.2(postcss@8.5.6)(ts-node@10.9.2(@types/node@24.9.1)(typescript@5.9.3)) + version: 4.0.2(postcss@8.5.6)(ts-node@10.9.2(@types/node@24.9.2)(typescript@5.9.3)) stylelint: specifier: ^15.10.1 version: 15.11.0(typescript@5.9.3) @@ -353,10 +353,10 @@ importers: version: 6.2.1 ts-jest: specifier: ^29.4.5 - version: 29.4.5(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@30.0.5)(babel-jest@29.7.0(@babel/core@7.28.3))(esbuild@0.25.11)(jest-util@30.0.5)(jest@29.7.0(@types/node@24.9.1)(ts-node@10.9.2(@types/node@24.9.1)(typescript@5.9.3)))(typescript@5.9.3) + version: 29.4.5(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@30.0.5)(babel-jest@29.7.0(@babel/core@7.28.3))(esbuild@0.25.11)(jest-util@30.0.5)(jest@29.7.0(@types/node@24.9.2)(ts-node@10.9.2(@types/node@24.9.2)(typescript@5.9.3)))(typescript@5.9.3) ts-node: specifier: ^10.9.1 - version: 10.9.2(@types/node@24.9.1)(typescript@5.9.3) + version: 10.9.2(@types/node@24.9.2)(typescript@5.9.3) tsup: specifier: ^8.5.0 version: 8.5.0(jiti@2.6.1)(postcss@8.5.6)(typescript@5.9.3)(yaml@2.8.1) @@ -1548,8 +1548,8 @@ packages: '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} - '@netlify/plugin-nextjs@5.14.2': - resolution: {integrity: sha512-Jyn8OdAlB8oTWwLJePTUkaZ4Up/4zlsMmiy/ZiLqfNdn/2Q7WuGOZUXzJEYH2+NRz2EcZlneDleD0WEp8vQCzQ==} + '@netlify/plugin-nextjs@5.14.4': + resolution: {integrity: sha512-HnMHG0tksVoS2E6ImcX9o/EcVH1dckb8ZL1FyghKRsEPYCo+20hQ6zncd5EEOW7K22UN+n1EprCROWUmsbhYMg==} engines: {node: '>=18.0.0'} '@next/env@15.5.3': @@ -1681,11 +1681,11 @@ packages: resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - '@prisma/adapter-pg@6.17.1': - resolution: {integrity: sha512-iy5aL2HHzW2sAGspgeUGXgM+JDiXm31R4iONSnAWdpFSc/SWgsR+z8muS0HAZnzclAUwqXrWFJh4RbneCM2ktA==} + '@prisma/adapter-pg@6.18.0': + resolution: {integrity: sha512-eBtBOL1z2Sh0Rc2hwa4dmwoNeFs5T69tsA62AkhBvnzNfTcr/YfLeJae/wjNmvRttYDPmdiuhqZCRPNY1+8fOA==} - '@prisma/client@6.17.1': - resolution: {integrity: sha512-zL58jbLzYamjnNnmNA51IOZdbk5ci03KviXCuB0Tydc9btH2kDWsi1pQm2VecviRTM7jGia0OPPkgpGnT3nKvw==} + '@prisma/client@6.18.0': + resolution: {integrity: sha512-jnL2I9gDnPnw4A+4h5SuNn8Gc+1mL1Z79U/3I9eE2gbxJG1oSA+62ByPW4xkeDgwE0fqMzzpAZ7IHxYnLZ4iQA==} engines: {node: '>=18.18'} peerDependencies: prisma: '*' @@ -1696,31 +1696,31 @@ packages: typescript: optional: true - '@prisma/config@6.17.1': - resolution: {integrity: sha512-fs8wY6DsvOCzuiyWVckrVs1LOcbY4LZNz8ki4uUIQ28jCCzojTGqdLhN2Jl5lDnC1yI8/gNIKpsWDM8pLhOdwA==} + '@prisma/config@6.18.0': + resolution: {integrity: sha512-rgFzspCpwsE+q3OF/xkp0fI2SJ3PfNe9LLMmuSVbAZ4nN66WfBiKqJKo/hLz3ysxiPQZf8h1SMf2ilqPMeWATQ==} - '@prisma/debug@6.17.1': - resolution: {integrity: sha512-Vf7Tt5Wh9XcndpbmeotuqOMLWPTjEKCsgojxXP2oxE1/xYe7PtnP76hsouG9vis6fctX+TxgmwxTuYi/+xc7dQ==} + '@prisma/debug@6.18.0': + resolution: {integrity: sha512-PMVPMmxPj0ps1VY75DIrT430MoOyQx9hmm174k6cmLZpcI95rAPXOQ+pp8ANQkJtNyLVDxnxVJ0QLbrm/ViBcg==} - '@prisma/driver-adapter-utils@6.17.1': - resolution: {integrity: sha512-GT4QbVUyJa5sXj5vjrBPnZ68v1P/FoKBCSIkrFrCPrOi6OSzk+567XDZyBtWIECKxruuMTa7fU4wwNmtMQylrg==} + '@prisma/driver-adapter-utils@6.18.0': + resolution: {integrity: sha512-9wgSriEKs4j1ePxlv1/RNfJV9Gu5rzG37Neshg+DfrCcUY3amroERvTjyR04w5J1THdGdOTgGL9VdJcVaKRMmQ==} - '@prisma/engines-version@6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac': - resolution: {integrity: sha512-17140E3huOuD9lMdJ9+SF/juOf3WR3sTJMVyyenzqUPbuH+89nPhSWcrY+Mf7tmSs6HvaO+7S+HkELinn6bhdg==} + '@prisma/engines-version@6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f': + resolution: {integrity: sha512-T7Af4QsJQnSgWN1zBbX+Cha5t4qjHRxoeoWpK4JugJzG/ipmmDMY5S+O0N1ET6sCBNVkf6lz+Y+ZNO9+wFU8pQ==} - '@prisma/engines@6.17.1': - resolution: {integrity: sha512-D95Ik3GYZkqZ8lSR4EyFOJ/tR33FcYRP8kK61o+WMsyD10UfJwd7+YielflHfKwiGodcqKqoraWw8ElAgMDbPw==} + '@prisma/engines@6.18.0': + resolution: {integrity: sha512-i5RzjGF/ex6AFgqEe2o1IW8iIxJGYVQJVRau13kHPYEL1Ck8Zvwuzamqed/1iIljs5C7L+Opiz5TzSsUebkriA==} '@prisma/extension-read-replicas@0.4.1': resolution: {integrity: sha512-mCMDloqUKUwx2o5uedTs1FHX3Nxdt1GdRMoeyp1JggjiwOALmIYWhxfIN08M2BZ0w8SKwvJqicJZMjkQYkkijw==} peerDependencies: '@prisma/client': ^6.5.0 - '@prisma/fetch-engine@6.17.1': - resolution: {integrity: sha512-AYZiHOs184qkDMiTeshyJCtyL4yERkjfTkJiSJdYuSfc24m94lTNL5+GFinZ6vVz+ktX4NJzHKn1zIFzGTWrWg==} + '@prisma/fetch-engine@6.18.0': + resolution: {integrity: sha512-TdaBvTtBwP3IoqVYoGIYpD4mWlk0pJpjTJjir/xLeNWlwog7Sl3bD2J0jJ8+5+q/6RBg+acb9drsv5W6lqae7A==} - '@prisma/get-platform@6.17.1': - resolution: {integrity: sha512-AKEn6fsfz0r482S5KRDFlIGEaq9wLNcgalD1adL+fPcFFblIKs1sD81kY/utrHdqKuVC6E1XSRpegDK3ZLL4Qg==} + '@prisma/get-platform@6.18.0': + resolution: {integrity: sha512-uXNJCJGhxTCXo2B25Ta91Rk1/Nmlqg9p7G9GKh8TPhxvAyXCvMNQoogj4JLEUy+3ku8g59cpyQIKFhqY2xO2bg==} '@react-aria/autocomplete@3.0.0-rc.3': resolution: {integrity: sha512-vemf7h3hvIDk3MxiiPryysfYgJDg8R72X46dRIeg0+cXKYxjPYou64/DTucSV2z5J6RC5JalINu0jIDaLhEILw==} @@ -2445,8 +2445,8 @@ packages: rollup: optional: true - '@rollup/plugin-typescript@12.1.4': - resolution: {integrity: sha512-s5Hx+EtN60LMlDBvl5f04bEiFZmAepk27Q+mr85L/00zPDn1jtzlTV6FWn81MaIwqfWzKxmOJrBWHU6vtQyedQ==} + '@rollup/plugin-typescript@12.3.0': + resolution: {integrity: sha512-7DP0/p7y3t67+NabT9f8oTBFE6gGkto4SA6Np2oudYmZE/m1dt8RB0SjL1msMxFpLo631qjRCcBlAbq1ml/Big==} engines: {node: '>=14.0.0'} peerDependencies: rollup: ^2.14.0||^3.0.0||^4.0.0 @@ -2787,8 +2787,8 @@ packages: '@types/node@14.18.63': resolution: {integrity: sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==} - '@types/node@24.9.1': - resolution: {integrity: sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==} + '@types/node@24.9.2': + resolution: {integrity: sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA==} '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -3474,8 +3474,8 @@ packages: resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} engines: {node: '>=8'} - cli-truncate@5.1.0: - resolution: {integrity: sha512-7JDGG+4Zp0CsknDCedl0DYdaeOhc46QNpXi3NLQblkZpXXgA6LncLDUUyvrjSvZeF3VRQa+KiMGomazQrC1V8g==} + cli-truncate@5.1.1: + resolution: {integrity: sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==} engines: {node: '>=20'} client-only@0.0.1: @@ -3972,8 +3972,8 @@ packages: ecdsa-sig-formatter@1.0.11: resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} - effect@3.16.12: - resolution: {integrity: sha512-N39iBk0K71F9nb442TLbTkjl24FLUzuvx2i1I2RsEAQsdAdUTuUoW0vlfUXgkMTUOnYqKnWcFfqw4hK4Pw27hg==} + effect@3.18.4: + resolution: {integrity: sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==} electron-to-chromium@1.5.202: resolution: {integrity: sha512-NxbYjRmiHcHXV1Ws3fWUW+SLb62isauajk45LUJ/HgIOkUA7jLZu/X2Iif+X9FBNK8QkF9Zb4Q2mcwXCcY30mg==} @@ -3982,8 +3982,8 @@ packages: resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} engines: {node: '>=12'} - emoji-regex@10.5.0: - resolution: {integrity: sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg==} + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -4689,8 +4689,8 @@ packages: immediate@3.0.6: resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} - immer@10.1.3: - resolution: {integrity: sha512-tmjF/k8QDKydUlm3mZU+tjM6zeq9/fFpPqH9SzWmBnVVKsPBg/V66qsMwb3/Bo90cgUN+ghdVBess+hPsxUyRw==} + immer@10.2.0: + resolution: {integrity: sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==} import-cwd@3.0.0: resolution: {integrity: sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==} @@ -5303,8 +5303,8 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - lint-staged@16.2.5: - resolution: {integrity: sha512-o36wH3OX0jRWqDw5dOa8a8x6GXTKaLM+LvhRaucZxez0IxA+KNDUCiyjBfNgsMNmchwSX6urLSL7wShcUqAang==} + lint-staged@16.2.6: + resolution: {integrity: sha512-s1gphtDbV4bmW1eylXpVMk2u7is7YsrLl8hzrtvC70h4ByhcMLZFY01Fx05ZUDNuv1H8HO4E+e2zgejV1jVwNw==} engines: {node: '>=20.17'} hasBin: true @@ -5317,8 +5317,8 @@ packages: enquirer: optional: true - listr2@9.0.4: - resolution: {integrity: sha512-1wd/kpAdKRLwv7/3OKC8zZ5U8e/fajCfWMxacUvB79S5nLrYGPtUI/8chMQhn3LQjsRVErTb9i1ECAwW0ZIHnQ==} + listr2@9.0.5: + resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==} engines: {node: '>=20.0.0'} load-json-file@4.0.0: @@ -6450,8 +6450,8 @@ packages: resolution: {integrity: sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - prisma@6.17.1: - resolution: {integrity: sha512-ac6h0sM1Tg3zu8NInY+qhP/S9KhENVaw9n1BrGKQVFu05JT5yT5Qqqmb8tMRIE3ZXvVj4xcRA5yfrsy4X7Yy5g==} + prisma@6.18.0: + resolution: {integrity: sha512-bXWy3vTk8mnRmT+SLyZBQoC2vtV9Z8u7OHvEu+aULYxwiop/CPiFZ+F56KsNRNf35jw+8wcu8pmLsjxpBxAO9g==} engines: {node: '>=18.18'} hasBin: true peerDependencies: @@ -8301,10 +8301,10 @@ snapshots: '@fontsource/jetbrains-mono@5.2.8': {} - '@formatjs/cli@4.8.4(ts-jest@29.4.5(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@30.0.5)(babel-jest@29.7.0(@babel/core@7.28.3))(esbuild@0.25.11)(jest-util@30.0.5)(jest@29.7.0(@types/node@24.9.1)(ts-node@10.9.2(@types/node@24.9.1)(typescript@5.9.3)))(typescript@5.9.3))': + '@formatjs/cli@4.8.4(ts-jest@29.4.5(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@30.0.5)(babel-jest@29.7.0(@babel/core@7.28.3))(esbuild@0.25.11)(jest-util@30.0.5)(jest@29.7.0(@types/node@24.9.2)(ts-node@10.9.2(@types/node@24.9.2)(typescript@5.9.3)))(typescript@5.9.3))': dependencies: '@formatjs/icu-messageformat-parser': 2.1.0 - '@formatjs/ts-transformer': 3.9.4(ts-jest@29.4.5(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@30.0.5)(babel-jest@29.7.0(@babel/core@7.28.3))(esbuild@0.25.11)(jest-util@30.0.5)(jest@29.7.0(@types/node@24.9.1)(ts-node@10.9.2(@types/node@24.9.1)(typescript@5.9.3)))(typescript@5.9.3)) + '@formatjs/ts-transformer': 3.9.4(ts-jest@29.4.5(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@30.0.5)(babel-jest@29.7.0(@babel/core@7.28.3))(esbuild@0.25.11)(jest-util@30.0.5)(jest@29.7.0(@types/node@24.9.2)(ts-node@10.9.2(@types/node@24.9.2)(typescript@5.9.3)))(typescript@5.9.3)) '@types/estree': 0.0.50 '@types/fs-extra': 9.0.13 '@types/json-stable-stringify': 1.2.0 @@ -8391,15 +8391,15 @@ snapshots: optionalDependencies: typescript: 5.9.3 - '@formatjs/ts-transformer@2.13.0(ts-jest@29.4.5(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@30.0.5)(babel-jest@29.7.0(@babel/core@7.28.3))(esbuild@0.25.11)(jest-util@30.0.5)(jest@29.7.0(@types/node@24.9.1)(ts-node@10.9.2(@types/node@24.9.1)(typescript@5.9.3)))(typescript@5.9.3))': + '@formatjs/ts-transformer@2.13.0(ts-jest@29.4.5(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@30.0.5)(babel-jest@29.7.0(@babel/core@7.28.3))(esbuild@0.25.11)(jest-util@30.0.5)(jest@29.7.0(@types/node@24.9.2)(ts-node@10.9.2(@types/node@24.9.2)(typescript@5.9.3)))(typescript@5.9.3))': dependencies: intl-messageformat-parser: 6.1.2 tslib: 2.8.1 typescript: 4.9.5 optionalDependencies: - ts-jest: 29.4.5(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@30.0.5)(babel-jest@29.7.0(@babel/core@7.28.3))(esbuild@0.25.11)(jest-util@30.0.5)(jest@29.7.0(@types/node@24.9.1)(ts-node@10.9.2(@types/node@24.9.1)(typescript@5.9.3)))(typescript@5.9.3) + ts-jest: 29.4.5(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@30.0.5)(babel-jest@29.7.0(@babel/core@7.28.3))(esbuild@0.25.11)(jest-util@30.0.5)(jest@29.7.0(@types/node@24.9.2)(ts-node@10.9.2(@types/node@24.9.2)(typescript@5.9.3)))(typescript@5.9.3) - '@formatjs/ts-transformer@3.9.4(ts-jest@29.4.5(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@30.0.5)(babel-jest@29.7.0(@babel/core@7.28.3))(esbuild@0.25.11)(jest-util@30.0.5)(jest@29.7.0(@types/node@24.9.1)(ts-node@10.9.2(@types/node@24.9.1)(typescript@5.9.3)))(typescript@5.9.3))': + '@formatjs/ts-transformer@3.9.4(ts-jest@29.4.5(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@30.0.5)(babel-jest@29.7.0(@babel/core@7.28.3))(esbuild@0.25.11)(jest-util@30.0.5)(jest@29.7.0(@types/node@24.9.2)(ts-node@10.9.2(@types/node@24.9.2)(typescript@5.9.3)))(typescript@5.9.3))': dependencies: '@formatjs/icu-messageformat-parser': 2.1.0 '@types/node': 14.18.63 @@ -8407,7 +8407,7 @@ snapshots: tslib: 2.8.1 typescript: 4.9.5 optionalDependencies: - ts-jest: 29.4.5(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@30.0.5)(babel-jest@29.7.0(@babel/core@7.28.3))(esbuild@0.25.11)(jest-util@30.0.5)(jest@29.7.0(@types/node@24.9.1)(ts-node@10.9.2(@types/node@24.9.1)(typescript@5.9.3)))(typescript@5.9.3) + ts-jest: 29.4.5(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@30.0.5)(babel-jest@29.7.0(@babel/core@7.28.3))(esbuild@0.25.11)(jest-util@30.0.5)(jest@29.7.0(@types/node@24.9.2)(ts-node@10.9.2(@types/node@24.9.2)(typescript@5.9.3)))(typescript@5.9.3) '@hello-pangea/dnd@17.0.0(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: @@ -8655,27 +8655,27 @@ snapshots: '@jest/console@29.7.0': dependencies: '@jest/types': 29.6.3 - '@types/node': 24.9.1 + '@types/node': 24.9.2 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 slash: 3.0.0 - '@jest/core@29.7.0(ts-node@10.9.2(@types/node@24.9.1)(typescript@5.9.3))': + '@jest/core@29.7.0(ts-node@10.9.2(@types/node@24.9.2)(typescript@5.9.3))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0 '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 24.9.1 + '@types/node': 24.9.2 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@24.9.1)(ts-node@10.9.2(@types/node@24.9.1)(typescript@5.9.3)) + jest-config: 29.7.0(@types/node@24.9.2)(ts-node@10.9.2(@types/node@24.9.2)(typescript@5.9.3)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -8702,7 +8702,7 @@ snapshots: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 24.9.1 + '@types/node': 24.9.2 jest-mock: 29.7.0 '@jest/expect-utils@29.7.0': @@ -8724,7 +8724,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 24.9.1 + '@types/node': 24.9.2 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -8742,7 +8742,7 @@ snapshots: '@jest/pattern@30.0.1': dependencies: - '@types/node': 24.9.1 + '@types/node': 24.9.2 jest-regex-util: 30.0.1 '@jest/reporters@29.7.0': @@ -8753,7 +8753,7 @@ snapshots: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.30 - '@types/node': 24.9.1 + '@types/node': 24.9.2 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 @@ -8827,7 +8827,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 24.9.1 + '@types/node': 24.9.2 '@types/yargs': 17.0.33 chalk: 4.1.2 @@ -8837,7 +8837,7 @@ snapshots: '@jest/schemas': 30.0.5 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 24.9.1 + '@types/node': 24.9.2 '@types/yargs': 17.0.33 chalk: 4.1.2 @@ -8874,7 +8874,7 @@ snapshots: '@tybys/wasm-util': 0.10.0 optional: true - '@netlify/plugin-nextjs@5.14.2': {} + '@netlify/plugin-nextjs@5.14.4': {} '@next/env@15.5.3': {} @@ -8951,56 +8951,56 @@ snapshots: '@pkgr/core@0.2.9': {} - '@prisma/adapter-pg@6.17.1': + '@prisma/adapter-pg@6.18.0': dependencies: - '@prisma/driver-adapter-utils': 6.17.1 + '@prisma/driver-adapter-utils': 6.18.0 pg: 8.16.3 postgres-array: 3.0.4 transitivePeerDependencies: - pg-native - '@prisma/client@6.17.1(prisma@6.17.1(typescript@5.9.3))(typescript@5.9.3)': + '@prisma/client@6.18.0(prisma@6.18.0(typescript@5.9.3))(typescript@5.9.3)': optionalDependencies: - prisma: 6.17.1(typescript@5.9.3) + prisma: 6.18.0(typescript@5.9.3) typescript: 5.9.3 - '@prisma/config@6.17.1': + '@prisma/config@6.18.0': dependencies: c12: 3.1.0 deepmerge-ts: 7.1.5 - effect: 3.16.12 + effect: 3.18.4 empathic: 2.0.0 transitivePeerDependencies: - magicast - '@prisma/debug@6.17.1': {} + '@prisma/debug@6.18.0': {} - '@prisma/driver-adapter-utils@6.17.1': + '@prisma/driver-adapter-utils@6.18.0': dependencies: - '@prisma/debug': 6.17.1 + '@prisma/debug': 6.18.0 - '@prisma/engines-version@6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac': {} + '@prisma/engines-version@6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f': {} - '@prisma/engines@6.17.1': + '@prisma/engines@6.18.0': dependencies: - '@prisma/debug': 6.17.1 - '@prisma/engines-version': 6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac - '@prisma/fetch-engine': 6.17.1 - '@prisma/get-platform': 6.17.1 + '@prisma/debug': 6.18.0 + '@prisma/engines-version': 6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f + '@prisma/fetch-engine': 6.18.0 + '@prisma/get-platform': 6.18.0 - '@prisma/extension-read-replicas@0.4.1(@prisma/client@6.17.1(prisma@6.17.1(typescript@5.9.3))(typescript@5.9.3))': + '@prisma/extension-read-replicas@0.4.1(@prisma/client@6.18.0(prisma@6.18.0(typescript@5.9.3))(typescript@5.9.3))': dependencies: - '@prisma/client': 6.17.1(prisma@6.17.1(typescript@5.9.3))(typescript@5.9.3) + '@prisma/client': 6.18.0(prisma@6.18.0(typescript@5.9.3))(typescript@5.9.3) - '@prisma/fetch-engine@6.17.1': + '@prisma/fetch-engine@6.18.0': dependencies: - '@prisma/debug': 6.17.1 - '@prisma/engines-version': 6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac - '@prisma/get-platform': 6.17.1 + '@prisma/debug': 6.18.0 + '@prisma/engines-version': 6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f + '@prisma/get-platform': 6.18.0 - '@prisma/get-platform@6.17.1': + '@prisma/get-platform@6.18.0': dependencies: - '@prisma/debug': 6.17.1 + '@prisma/debug': 6.18.0 '@react-aria/autocomplete@3.0.0-rc.3(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: @@ -10179,7 +10179,7 @@ snapshots: optionalDependencies: rollup: 4.52.5 - '@rollup/plugin-typescript@12.1.4(rollup@4.52.5)(tslib@2.8.1)(typescript@5.9.3)': + '@rollup/plugin-typescript@12.3.0(rollup@4.52.5)(tslib@2.8.1)(typescript@5.9.3)': dependencies: '@rollup/pluginutils': 5.2.0(rollup@4.52.5) resolve: 1.22.10 @@ -10440,20 +10440,20 @@ snapshots: '@types/fs-extra@8.1.5': dependencies: - '@types/node': 24.9.1 + '@types/node': 24.9.2 '@types/fs-extra@9.0.13': dependencies: - '@types/node': 24.9.1 + '@types/node': 24.9.2 '@types/glob@7.2.0': dependencies: '@types/minimatch': 6.0.0 - '@types/node': 24.9.1 + '@types/node': 24.9.2 '@types/graceful-fs@4.1.9': dependencies: - '@types/node': 24.9.1 + '@types/node': 24.9.2 '@types/hoist-non-react-statics@3.3.7(@types/react@19.2.2)': dependencies: @@ -10491,7 +10491,7 @@ snapshots: '@types/node@14.18.63': {} - '@types/node@24.9.1': + '@types/node@24.9.2': dependencies: undici-types: 7.16.0 @@ -10533,7 +10533,7 @@ snapshots: '@types/yauzl@2.10.3': dependencies: - '@types/node': 24.9.1 + '@types/node': 24.9.2 optional: true '@typescript-eslint/eslint-plugin@8.46.2(@typescript-eslint/parser@8.46.2(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)': @@ -10670,7 +10670,7 @@ snapshots: '@typescript-eslint/types': 8.46.2 eslint-visitor-keys: 4.2.1 - '@umami/react-zen@0.203.0(@babel/core@7.28.3)(@types/react@19.2.2)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@10.1.3)(use-sync-external-store@1.6.0(react@19.2.0))': + '@umami/react-zen@0.203.0(@babel/core@7.28.3)(@types/react@19.2.2)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@10.2.0)(use-sync-external-store@1.6.0(react@19.2.0))': dependencies: '@fontsource/jetbrains-mono': 5.2.8 '@internationalized/date': 3.10.0 @@ -10687,7 +10687,7 @@ snapshots: react-hook-form: 7.65.0(react@19.2.0) react-icons: 5.5.0(react@19.2.0) thenby: 1.3.4 - zustand: 5.0.8(@types/react@19.2.2)(immer@10.1.3)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)) + zustand: 5.0.8(@types/react@19.2.2)(immer@10.2.0)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)) transitivePeerDependencies: - '@babel/core' - '@opentelemetry/api' @@ -11046,12 +11046,12 @@ snapshots: dependencies: '@babel/types': 7.28.2 - babel-plugin-react-intl@7.9.4(ts-jest@29.4.5(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@30.0.5)(babel-jest@29.7.0(@babel/core@7.28.3))(esbuild@0.25.11)(jest-util@30.0.5)(jest@29.7.0(@types/node@24.9.1)(ts-node@10.9.2(@types/node@24.9.1)(typescript@5.9.3)))(typescript@5.9.3)): + babel-plugin-react-intl@7.9.4(ts-jest@29.4.5(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@30.0.5)(babel-jest@29.7.0(@babel/core@7.28.3))(esbuild@0.25.11)(jest-util@30.0.5)(jest@29.7.0(@types/node@24.9.2)(ts-node@10.9.2(@types/node@24.9.2)(typescript@5.9.3)))(typescript@5.9.3)): dependencies: '@babel/core': 7.28.3 '@babel/helper-plugin-utils': 7.27.1 '@babel/types': 7.28.2 - '@formatjs/ts-transformer': 2.13.0(ts-jest@29.4.5(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@30.0.5)(babel-jest@29.7.0(@babel/core@7.28.3))(esbuild@0.25.11)(jest-util@30.0.5)(jest@29.7.0(@types/node@24.9.1)(ts-node@10.9.2(@types/node@24.9.1)(typescript@5.9.3)))(typescript@5.9.3)) + '@formatjs/ts-transformer': 2.13.0(ts-jest@29.4.5(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@30.0.5)(babel-jest@29.7.0(@babel/core@7.28.3))(esbuild@0.25.11)(jest-util@30.0.5)(jest@29.7.0(@types/node@24.9.2)(ts-node@10.9.2(@types/node@24.9.2)(typescript@5.9.3)))(typescript@5.9.3)) '@types/babel__core': 7.20.5 '@types/fs-extra': 9.0.13 '@types/schema-utils': 2.4.0 @@ -11284,7 +11284,7 @@ snapshots: slice-ansi: 3.0.0 string-width: 4.2.3 - cli-truncate@5.1.0: + cli-truncate@5.1.1: dependencies: slice-ansi: 7.1.2 string-width: 8.1.0 @@ -11389,13 +11389,13 @@ snapshots: optionalDependencies: typescript: 5.9.3 - create-jest@29.7.0(@types/node@24.9.1)(ts-node@10.9.2(@types/node@24.9.1)(typescript@5.9.3)): + create-jest@29.7.0(@types/node@24.9.2)(ts-node@10.9.2(@types/node@24.9.2)(typescript@5.9.3)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@24.9.1)(ts-node@10.9.2(@types/node@24.9.1)(typescript@5.9.3)) + jest-config: 29.7.0(@types/node@24.9.2)(ts-node@10.9.2(@types/node@24.9.2)(typescript@5.9.3)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -11837,7 +11837,7 @@ snapshots: dependencies: safe-buffer: 5.2.1 - effect@3.16.12: + effect@3.18.4: dependencies: '@standard-schema/spec': 1.0.0 fast-check: 3.23.2 @@ -11846,7 +11846,7 @@ snapshots: emittery@0.13.1: {} - emoji-regex@10.5.0: {} + emoji-regex@10.6.0: {} emoji-regex@8.0.0: {} @@ -12113,13 +12113,13 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@8.46.2(@typescript-eslint/parser@8.46.2(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(jest@29.7.0(@types/node@24.9.1)(ts-node@10.9.2(@types/node@24.9.1)(typescript@5.9.3)))(typescript@5.9.3): + eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@8.46.2(@typescript-eslint/parser@8.46.2(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(jest@29.7.0(@types/node@24.9.2)(ts-node@10.9.2(@types/node@24.9.2)(typescript@5.9.3)))(typescript@5.9.3): dependencies: '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.9.3) eslint: 8.57.1 optionalDependencies: '@typescript-eslint/eslint-plugin': 8.46.2(@typescript-eslint/parser@8.46.2(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) - jest: 29.7.0(@types/node@24.9.1)(ts-node@10.9.2(@types/node@24.9.1)(typescript@5.9.3)) + jest: 29.7.0(@types/node@24.9.2)(ts-node@10.9.2(@types/node@24.9.2)(typescript@5.9.3)) transitivePeerDependencies: - supports-color - typescript @@ -12322,10 +12322,10 @@ snapshots: extend@3.0.2: {} - extract-react-intl-messages@4.1.1(ts-jest@29.4.5(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@30.0.5)(babel-jest@29.7.0(@babel/core@7.28.3))(esbuild@0.25.11)(jest-util@30.0.5)(jest@29.7.0(@types/node@24.9.1)(ts-node@10.9.2(@types/node@24.9.1)(typescript@5.9.3)))(typescript@5.9.3)): + extract-react-intl-messages@4.1.1(ts-jest@29.4.5(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@30.0.5)(babel-jest@29.7.0(@babel/core@7.28.3))(esbuild@0.25.11)(jest-util@30.0.5)(jest@29.7.0(@types/node@24.9.2)(ts-node@10.9.2(@types/node@24.9.2)(typescript@5.9.3)))(typescript@5.9.3)): dependencies: '@babel/core': 7.28.3 - babel-plugin-react-intl: 7.9.4(ts-jest@29.4.5(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@30.0.5)(babel-jest@29.7.0(@babel/core@7.28.3))(esbuild@0.25.11)(jest-util@30.0.5)(jest@29.7.0(@types/node@24.9.1)(ts-node@10.9.2(@types/node@24.9.1)(typescript@5.9.3)))(typescript@5.9.3)) + babel-plugin-react-intl: 7.9.4(ts-jest@29.4.5(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@30.0.5)(babel-jest@29.7.0(@babel/core@7.28.3))(esbuild@0.25.11)(jest-util@30.0.5)(jest@29.7.0(@types/node@24.9.2)(ts-node@10.9.2(@types/node@24.9.2)(typescript@5.9.3)))(typescript@5.9.3)) flat: 5.0.2 glob: 7.2.3 js-yaml: 3.14.1 @@ -12762,7 +12762,7 @@ snapshots: immediate@3.0.6: {} - immer@10.1.3: {} + immer@10.2.0: {} import-cwd@3.0.0: dependencies: @@ -13078,7 +13078,7 @@ snapshots: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 24.9.1 + '@types/node': 24.9.2 chalk: 4.1.2 co: 4.6.0 dedent: 1.6.0 @@ -13098,16 +13098,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@24.9.1)(ts-node@10.9.2(@types/node@24.9.1)(typescript@5.9.3)): + jest-cli@29.7.0(@types/node@24.9.2)(ts-node@10.9.2(@types/node@24.9.2)(typescript@5.9.3)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@24.9.1)(typescript@5.9.3)) + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@24.9.2)(typescript@5.9.3)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@24.9.1)(ts-node@10.9.2(@types/node@24.9.1)(typescript@5.9.3)) + create-jest: 29.7.0(@types/node@24.9.2)(ts-node@10.9.2(@types/node@24.9.2)(typescript@5.9.3)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@24.9.1)(ts-node@10.9.2(@types/node@24.9.1)(typescript@5.9.3)) + jest-config: 29.7.0(@types/node@24.9.2)(ts-node@10.9.2(@types/node@24.9.2)(typescript@5.9.3)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -13117,7 +13117,7 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@24.9.1)(ts-node@10.9.2(@types/node@24.9.1)(typescript@5.9.3)): + jest-config@29.7.0(@types/node@24.9.2)(ts-node@10.9.2(@types/node@24.9.2)(typescript@5.9.3)): dependencies: '@babel/core': 7.28.3 '@jest/test-sequencer': 29.7.0 @@ -13142,8 +13142,8 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 24.9.1 - ts-node: 10.9.2(@types/node@24.9.1)(typescript@5.9.3) + '@types/node': 24.9.2 + ts-node: 10.9.2(@types/node@24.9.2)(typescript@5.9.3) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -13179,7 +13179,7 @@ snapshots: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 24.9.1 + '@types/node': 24.9.2 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -13189,7 +13189,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 24.9.1 + '@types/node': 24.9.2 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -13247,13 +13247,13 @@ snapshots: jest-mock@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 24.9.1 + '@types/node': 24.9.2 jest-util: 29.7.0 jest-mock@30.0.5: dependencies: '@jest/types': 30.0.5 - '@types/node': 24.9.1 + '@types/node': 24.9.2 jest-util: 30.0.5 jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): @@ -13290,7 +13290,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 24.9.1 + '@types/node': 24.9.2 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -13318,7 +13318,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 24.9.1 + '@types/node': 24.9.2 chalk: 4.1.2 cjs-module-lexer: 1.4.3 collect-v8-coverage: 1.0.2 @@ -13364,7 +13364,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 24.9.1 + '@types/node': 24.9.2 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -13373,7 +13373,7 @@ snapshots: jest-util@30.0.5: dependencies: '@jest/types': 30.0.5 - '@types/node': 24.9.1 + '@types/node': 24.9.2 chalk: 4.1.2 ci-info: 4.3.0 graceful-fs: 4.2.11 @@ -13392,7 +13392,7 @@ snapshots: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 24.9.1 + '@types/node': 24.9.2 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -13401,17 +13401,17 @@ snapshots: jest-worker@29.7.0: dependencies: - '@types/node': 24.9.1 + '@types/node': 24.9.2 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@24.9.1)(ts-node@10.9.2(@types/node@24.9.1)(typescript@5.9.3)): + jest@29.7.0(@types/node@24.9.2)(ts-node@10.9.2(@types/node@24.9.2)(typescript@5.9.3)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@24.9.1)(typescript@5.9.3)) + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@24.9.2)(typescript@5.9.3)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@24.9.1)(ts-node@10.9.2(@types/node@24.9.1)(typescript@5.9.3)) + jest-cli: 29.7.0(@types/node@24.9.2)(ts-node@10.9.2(@types/node@24.9.2)(typescript@5.9.3)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -13564,10 +13564,10 @@ snapshots: lines-and-columns@1.2.4: {} - lint-staged@16.2.5: + lint-staged@16.2.6: dependencies: commander: 14.0.1 - listr2: 9.0.4 + listr2: 9.0.5 micromatch: 4.0.8 nano-spawn: 2.0.0 pidtree: 0.6.0 @@ -13587,9 +13587,9 @@ snapshots: optionalDependencies: enquirer: 2.4.1 - listr2@9.0.4: + listr2@9.0.5: dependencies: - cli-truncate: 5.1.0 + cli-truncate: 5.1.1 colorette: 2.0.20 eventemitter3: 5.0.1 log-update: 6.1.0 @@ -14359,13 +14359,13 @@ snapshots: postcss: 8.5.6 postcss-value-parser: 4.2.0 - postcss-load-config@3.1.4(postcss@8.5.6)(ts-node@10.9.2(@types/node@24.9.1)(typescript@5.9.3)): + postcss-load-config@3.1.4(postcss@8.5.6)(ts-node@10.9.2(@types/node@24.9.2)(typescript@5.9.3)): dependencies: lilconfig: 2.1.0 yaml: 1.10.2 optionalDependencies: postcss: 8.5.6 - ts-node: 10.9.2(@types/node@24.9.1)(typescript@5.9.3) + ts-node: 10.9.2(@types/node@24.9.2)(typescript@5.9.3) postcss-load-config@6.0.1(jiti@2.6.1)(postcss@8.5.6)(yaml@2.8.1): dependencies: @@ -14688,10 +14688,10 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.3.1 - prisma@6.17.1(typescript@5.9.3): + prisma@6.18.0(typescript@5.9.3): dependencies: - '@prisma/config': 6.17.1 - '@prisma/engines': 6.17.1 + '@prisma/config': 6.18.0 + '@prisma/engines': 6.18.0 optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: @@ -15104,7 +15104,7 @@ snapshots: dependencies: rollup: 4.52.5 - rollup-plugin-postcss@4.0.2(postcss@8.5.6)(ts-node@10.9.2(@types/node@24.9.1)(typescript@5.9.3)): + rollup-plugin-postcss@4.0.2(postcss@8.5.6)(ts-node@10.9.2(@types/node@24.9.2)(typescript@5.9.3)): dependencies: chalk: 4.1.2 concat-with-sourcemaps: 1.1.0 @@ -15113,7 +15113,7 @@ snapshots: p-queue: 6.6.2 pify: 5.0.0 postcss: 8.5.6 - postcss-load-config: 3.1.4(postcss@8.5.6)(ts-node@10.9.2(@types/node@24.9.1)(typescript@5.9.3)) + postcss-load-config: 3.1.4(postcss@8.5.6)(ts-node@10.9.2(@types/node@24.9.2)(typescript@5.9.3)) postcss-modules: 4.3.1(postcss@8.5.6) promise.series: 0.2.0 resolve: 1.22.10 @@ -15465,7 +15465,7 @@ snapshots: string-width@7.2.0: dependencies: - emoji-regex: 10.5.0 + emoji-regex: 10.6.0 get-east-asian-width: 1.4.0 strip-ansi: 7.1.2 @@ -15803,12 +15803,12 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-jest@29.4.5(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@30.0.5)(babel-jest@29.7.0(@babel/core@7.28.3))(esbuild@0.25.11)(jest-util@30.0.5)(jest@29.7.0(@types/node@24.9.1)(ts-node@10.9.2(@types/node@24.9.1)(typescript@5.9.3)))(typescript@5.9.3): + ts-jest@29.4.5(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@30.0.5)(babel-jest@29.7.0(@babel/core@7.28.3))(esbuild@0.25.11)(jest-util@30.0.5)(jest@29.7.0(@types/node@24.9.2)(ts-node@10.9.2(@types/node@24.9.2)(typescript@5.9.3)))(typescript@5.9.3): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 handlebars: 4.7.8 - jest: 29.7.0(@types/node@24.9.1)(ts-node@10.9.2(@types/node@24.9.1)(typescript@5.9.3)) + jest: 29.7.0(@types/node@24.9.2)(ts-node@10.9.2(@types/node@24.9.2)(typescript@5.9.3)) json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 @@ -15824,14 +15824,14 @@ snapshots: esbuild: 0.25.11 jest-util: 30.0.5 - ts-node@10.9.2(@types/node@24.9.1)(typescript@5.9.3): + ts-node@10.9.2(@types/node@24.9.2)(typescript@5.9.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 24.9.1 + '@types/node': 24.9.2 acorn: 8.15.0 acorn-walk: 8.3.4 arg: 4.1.3 @@ -16227,9 +16227,9 @@ snapshots: zod@4.1.12: {} - zustand@5.0.8(@types/react@19.2.2)(immer@10.1.3)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)): + zustand@5.0.8(@types/react@19.2.2)(immer@10.2.0)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)): optionalDependencies: '@types/react': 19.2.2 - immer: 10.1.3 + immer: 10.2.0 react: 19.2.0 use-sync-external-store: 1.6.0(react@19.2.0) diff --git a/scripts/build-geo.js b/scripts/build-geo.js index c5f634b47..b6ac42c4b 100644 --- a/scripts/build-geo.js +++ b/scripts/build-geo.js @@ -6,7 +6,7 @@ import https from 'https'; import zlib from 'zlib'; import tar from 'tar'; -if (process.env.VERCEL) { +if (process.env.VERCEL && !process.env.BUILD_GEO) { console.log('Vercel environment detected. Skipping geo setup.'); process.exit(0); } diff --git a/src/lib/detect.ts b/src/lib/detect.ts index 19ad16168..16ff97da2 100644 --- a/src/lib/detect.ts +++ b/src/lib/detect.ts @@ -5,27 +5,10 @@ import isLocalhost from 'is-localhost-ip'; import ipaddr from 'ipaddr.js'; import maxmind from 'maxmind'; import { safeDecodeURIComponent } from '@/lib/url'; +import { stripPort, getIpAddress } from '@/lib/ip'; const MAXMIND = 'maxmind'; -// The order here is important and influences how IPs are detected by lib/detect.ts -// Please do not change the order unless you know exactly what you're doing - read https://developers.cloudflare.com/fundamentals/reference/http-headers/ -export const IP_ADDRESS_HEADERS = [ - 'x-client-ip', - 'x-forwarded-for', - 'cf-connecting-ip', // This should be *after* x-forwarded-for, so that x-forwarded-for is respected if present - 'do-connecting-ip', - 'fastly-client-ip', - 'true-client-ip', - 'x-real-ip', - 'x-cluster-client-ip', - 'x-forwarded', - 'forwarded', - 'x-appengine-user-ip', - 'x-nf-client-connection-ip', - 'x-real-ip', -]; - const PROVIDER_HEADERS = [ // Cloudflare headers { @@ -47,52 +30,6 @@ const PROVIDER_HEADERS = [ }, ]; -function stripPort(ip) { - if (ip.startsWith('[')) { - const endBracket = ip.indexOf(']'); - if (endBracket !== -1) { - return ip.slice(0, endBracket + 1); - } - } - - const idx = ip.lastIndexOf(':'); - if (idx !== -1) { - if (ip.includes('.') || /^[a-zA-Z0-9.-]+$/.test(ip.slice(0, idx))) { - return ip.slice(0, idx); - } - } - - return ip; -} - -export function getIpAddress(headers: Headers) { - const customHeader = process.env.CLIENT_IP_HEADER; - - if (customHeader && headers.get(customHeader)) { - return headers.get(customHeader); - } - - const header = IP_ADDRESS_HEADERS.find(name => { - return headers.get(name); - }); - - const ip = headers.get(header); - - if (header === 'x-forwarded-for') { - return ip?.split(',')?.[0]?.trim(); - } - - if (header === 'forwarded') { - const match = ip.match(/for=(\[?[0-9a-fA-F:.]+\]?)/); - - if (match) { - return match[1]; - } - } - - return ip; -} - export function getDevice(userAgent: string) { const { device } = UAParser(userAgent); diff --git a/src/lib/ip.ts b/src/lib/ip.ts new file mode 100644 index 000000000..170701505 --- /dev/null +++ b/src/lib/ip.ts @@ -0,0 +1,63 @@ +// The order here is important and influences how IPs are detected by lib/detect.ts +// Please do not change the order unless you know exactly what you're doing - read https://developers.cloudflare.com/fundamentals/reference/http-headers/ +export const IP_ADDRESS_HEADERS = [ + 'x-client-ip', + 'x-forwarded-for', + 'cf-connecting-ip', // This should be *after* x-forwarded-for, so that x-forwarded-for is respected if present + 'do-connecting-ip', + 'fastly-client-ip', + 'true-client-ip', + 'x-real-ip', + 'x-cluster-client-ip', + 'x-forwarded', + 'forwarded', + 'x-appengine-user-ip', + 'x-nf-client-connection-ip', + 'x-real-ip', +]; + +export function getIpAddress(headers: Headers) { + const customHeader = process.env.CLIENT_IP_HEADER; + + if (customHeader && headers.get(customHeader)) { + return headers.get(customHeader); + } + + const header = IP_ADDRESS_HEADERS.find(name => { + return headers.get(name); + }); + + const ip = headers.get(header); + + if (header === 'x-forwarded-for') { + return ip?.split(',')?.[0]?.trim(); + } + + if (header === 'forwarded') { + const match = ip.match(/for=(\[?[0-9a-fA-F:.]+\]?)/); + + if (match) { + return match[1]; + } + } + + return ip; +} + +export function stripPort(ip: string) { + if (ip.startsWith('[')) { + const endBracket = ip.indexOf(']'); + if (endBracket !== -1) { + return ip.slice(0, endBracket + 1); + } + } + + const idx = ip.lastIndexOf(':'); + if (idx !== -1) { + if (ip.includes('.') || /^[a-zA-Z0-9.-]+$/.test(ip.slice(0, idx))) { + return ip.slice(0, idx); + } + } + + return ip; +} From eb3cfbfa884f3003b786a17f785899861f83ecf1 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 31 Oct 2025 00:21:02 -0700 Subject: [PATCH 03/37] Updated lock file. --- pnpm-lock.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f9656c418..5ca04cd91 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -150,7 +150,7 @@ importers: specifier: ^8.16.3 version: 8.16.3 prisma: - specifier: 6.18.0 + specifier: ^6.18.0 version: 6.18.0(typescript@5.9.3) pure-rand: specifier: ^7.0.1 From c9f522b24d7ebfef59ccbe4b3976f1b40f9f6707 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Fri, 31 Oct 2025 09:12:32 -0700 Subject: [PATCH 04/37] remove MySQL references from README --- README.md | 9 +-------- src/lib/db.ts | 3 +-- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index fcbe856f1..6d166d8c8 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ A detailed getting started guide can be found at [umami.is/docs](https://umami.i ### Requirements - A server with Node.js version 18.18 or newer -- A database. Umami supports [MariaDB](https://www.mariadb.org/) (minimum v10.5), [MySQL](https://www.mysql.com/) (minimum v8.0) and [PostgreSQL](https://www.postgresql.org/) (minimum v12.14) databases. +- A database. Umami supports [PostgreSQL](https://www.postgresql.org/) (minimum v12.14) databases. ### Get the Source Code and Install Packages @@ -58,7 +58,6 @@ The connection URL format: ```bash postgresql://username:mypassword@localhost:5432/mydb -mysql://username:mypassword@localhost:3306/mydb ``` ### Build the Application @@ -93,12 +92,6 @@ Alternatively, to pull just the Umami Docker image with PostgreSQL support: docker pull docker.umami.is/umami-software/umami:postgresql-latest ``` -Or with MySQL support: - -```bash -docker pull docker.umami.is/umami-software/umami:mysql-latest -``` - --- ## 🔄 Getting Updates diff --git a/src/lib/db.ts b/src/lib/db.ts index 0ffedd0d8..72a18c062 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -1,6 +1,5 @@ export const PRISMA = 'prisma'; export const POSTGRESQL = 'postgresql'; -export const MYSQL = 'mysql'; export const CLICKHOUSE = 'clickhouse'; export const KAFKA = 'kafka'; export const KAFKA_PRODUCER = 'kafka-producer'; @@ -31,7 +30,7 @@ export async function runQuery(queries: any) { const db = getDatabaseType(); - if (db === POSTGRESQL || db === MYSQL) { + if (db === POSTGRESQL) { return queries[PRISMA](); } } From 684863d6c7d6c9d7de247cf8119b6fcd32bb39e0 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Mon, 3 Nov 2025 16:44:01 -0800 Subject: [PATCH 05/37] Added laptop detection. Reorganized IP headers. --- src/lib/detect.ts | 14 +++++++++++--- src/lib/ip.ts | 19 ++++++++----------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/lib/detect.ts b/src/lib/detect.ts index 16ff97da2..c5528465d 100644 --- a/src/lib/detect.ts +++ b/src/lib/detect.ts @@ -30,10 +30,18 @@ const PROVIDER_HEADERS = [ }, ]; -export function getDevice(userAgent: string) { +export function getDevice(userAgent: string, screen: string = '') { const { device } = UAParser(userAgent); - return device?.type || 'desktop'; + const [width] = screen.split('x'); + + const type = device?.type || 'desktop'; + + if (type === 'desktop' && screen && +width <= 1920) { + return 'laptop'; + } + + return type; } function getRegionCode(country: string, region: string) { @@ -108,7 +116,7 @@ export async function getClientInfo(request: Request, payload: Record Date: Mon, 3 Nov 2025 16:54:58 -0800 Subject: [PATCH 06/37] Updated Dockerfile to match current Prisma version. --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index ddde77fc5..39887e465 100644 --- a/Dockerfile +++ b/Dockerfile @@ -42,7 +42,7 @@ RUN set -x \ && apk add --no-cache curl # Script dependencies -RUN pnpm add npm-run-all dotenv chalk semver prisma@6.16.3 @prisma/adapter-pg@6.16.3 +RUN pnpm add npm-run-all dotenv chalk semver prisma@6.18.0 @prisma/adapter-pg@6.18.0 # Permissions for prisma RUN chown -R nextjs:nodejs node_modules/.pnpm/ From 5398cd89fe7d4d27efc8d96a720e1d217954a43e Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Mon, 3 Nov 2025 21:01:12 -0800 Subject: [PATCH 07/37] Reordered IP headers. --- src/lib/ip.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/ip.ts b/src/lib/ip.ts index a818f03d7..9e0f99109 100644 --- a/src/lib/ip.ts +++ b/src/lib/ip.ts @@ -1,9 +1,9 @@ export const IP_ADDRESS_HEADERS = [ - 'x-real-ip', + 'true-client-ip', // CDN + 'x-real-ip', // Reverse proxy 'x-forwarded-for', 'cf-connecting-ip', // Cloudflare 'fastly-client-ip', // Fastly - 'true-client-ip', // Akamai 'x-nf-client-connection-ip', // Netlify 'do-connecting-ip', // Digital Ocean 'x-appengine-user-ip', // Google App Ending From a6d4519a980dc301c7675ccf7ab69137b5469351 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Tue, 4 Nov 2025 09:34:37 -0800 Subject: [PATCH 08/37] set maxHeight for DialogButton. remove from individual buttons --- .../websites/[websiteId]/cohorts/CohortAddButton.tsx | 1 - .../websites/[websiteId]/cohorts/CohortEditButton.tsx | 1 - .../websites/[websiteId]/segments/SegmentAddButton.tsx | 1 - .../[websiteId]/segments/SegmentEditButton.tsx | 1 - src/components/input/DialogButton.tsx | 10 +++++++++- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/app/(main)/websites/[websiteId]/cohorts/CohortAddButton.tsx b/src/app/(main)/websites/[websiteId]/cohorts/CohortAddButton.tsx index 737ab502c..3f7f87232 100644 --- a/src/app/(main)/websites/[websiteId]/cohorts/CohortAddButton.tsx +++ b/src/app/(main)/websites/[websiteId]/cohorts/CohortAddButton.tsx @@ -12,7 +12,6 @@ export function CohortAddButton({ websiteId }: { websiteId: string }) { label={formatMessage(labels.cohort)} variant="primary" width="800px" - height="calc(100dvh - 40px)" > {({ close }) => { return ; diff --git a/src/app/(main)/websites/[websiteId]/cohorts/CohortEditButton.tsx b/src/app/(main)/websites/[websiteId]/cohorts/CohortEditButton.tsx index 48944677d..aea022094 100644 --- a/src/app/(main)/websites/[websiteId]/cohorts/CohortEditButton.tsx +++ b/src/app/(main)/websites/[websiteId]/cohorts/CohortEditButton.tsx @@ -21,7 +21,6 @@ export function CohortEditButton({ variant="quiet" title={formatMessage(labels.cohort)} width="800px" - height="calc(100dvh - 40px)" > {({ close }) => { return ( diff --git a/src/app/(main)/websites/[websiteId]/segments/SegmentAddButton.tsx b/src/app/(main)/websites/[websiteId]/segments/SegmentAddButton.tsx index aed63ac68..052d3185b 100644 --- a/src/app/(main)/websites/[websiteId]/segments/SegmentAddButton.tsx +++ b/src/app/(main)/websites/[websiteId]/segments/SegmentAddButton.tsx @@ -12,7 +12,6 @@ export function SegmentAddButton({ websiteId }: { websiteId: string }) { label={formatMessage(labels.segment)} variant="primary" width="800px" - height="calc(100dvh - 40px)" > {({ close }) => { return ; diff --git a/src/app/(main)/websites/[websiteId]/segments/SegmentEditButton.tsx b/src/app/(main)/websites/[websiteId]/segments/SegmentEditButton.tsx index 8c025772b..6d422c95b 100644 --- a/src/app/(main)/websites/[websiteId]/segments/SegmentEditButton.tsx +++ b/src/app/(main)/websites/[websiteId]/segments/SegmentEditButton.tsx @@ -21,7 +21,6 @@ export function SegmentEditButton({ title={formatMessage(labels.segment)} variant="quiet" width="800px" - height="calc(100dvh - 40px)" > {({ close }) => { return ( diff --git a/src/components/input/DialogButton.tsx b/src/components/input/DialogButton.tsx index f7184da6f..a3a49252a 100644 --- a/src/components/input/DialogButton.tsx +++ b/src/components/input/DialogButton.tsx @@ -33,11 +33,19 @@ export function DialogButton({ ...props }: DialogButtonProps) { const { isMobile } = useMobile(); - const style: CSSProperties = { width, height, minWidth, minHeight, padding: '32px' }; + const style: CSSProperties = { + width, + height, + minWidth, + minHeight, + maxHeight: 'calc(100dvh - 40px)', + padding: '32px', + }; if (isMobile) { style.width = '100%'; style.height = '100%'; + style.maxHeight = '100%'; style.overflowY = 'auto'; } From b5795a8b3f961926153d88641265a50cc5746938 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Thu, 6 Nov 2025 16:16:53 -0800 Subject: [PATCH 09/37] Fixed update notice. --- src/app/(main)/UpdateNotice.tsx | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/app/(main)/UpdateNotice.tsx b/src/app/(main)/UpdateNotice.tsx index 357287912..81e2ca3af 100644 --- a/src/app/(main)/UpdateNotice.tsx +++ b/src/app/(main)/UpdateNotice.tsx @@ -1,5 +1,5 @@ import { useEffect, useCallback, useState } from 'react'; -import { Button, AlertBanner, Flexbox } from '@umami/react-zen'; +import { Button, AlertBanner, Column, Row } from '@umami/react-zen'; import { setItem } from '@/lib/storage'; import { useVersion, checkVersion } from '@/store/version'; import { REPO_URL, VERSION_CHECK } from '@/lib/constants'; @@ -47,13 +47,15 @@ export function UpdateNotice({ user, config }) { } return ( - - - - - - + + + + + + + + ); } From 6135ef9dd218186ed663f89a511fa66bfecc6aec Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Thu, 6 Nov 2025 22:24:08 -0800 Subject: [PATCH 10/37] Fixed test. --- src/lib/__tests__/detect.test.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lib/__tests__/detect.test.ts b/src/lib/__tests__/detect.test.ts index fcf706af1..f02ac8392 100644 --- a/src/lib/__tests__/detect.test.ts +++ b/src/lib/__tests__/detect.test.ts @@ -1,4 +1,4 @@ -import * as detect from '../detect'; +import { getIpAddress } from '../ip'; const IP = '127.0.0.1'; const BAD_IP = '127.127.127.127'; @@ -6,23 +6,23 @@ const BAD_IP = '127.127.127.127'; test('getIpAddress: Custom header', () => { process.env.CLIENT_IP_HEADER = 'x-custom-ip-header'; - expect(detect.getIpAddress(new Headers({ 'x-custom-ip-header': IP }))).toEqual(IP); + expect(getIpAddress(new Headers({ 'x-custom-ip-header': IP }))).toEqual(IP); }); test('getIpAddress: CloudFlare header', () => { - expect(detect.getIpAddress(new Headers({ 'cf-connecting-ip': IP }))).toEqual(IP); + expect(getIpAddress(new Headers({ 'cf-connecting-ip': IP }))).toEqual(IP); }); test('getIpAddress: Standard header', () => { - expect(detect.getIpAddress(new Headers({ 'x-forwarded-for': IP }))).toEqual(IP); + expect(getIpAddress(new Headers({ 'x-forwarded-for': IP }))).toEqual(IP); }); test('getIpAddress: CloudFlare header is lower priority than standard header', () => { - expect( - detect.getIpAddress(new Headers({ 'cf-connecting-ip': BAD_IP, 'x-forwarded-for': IP })), - ).toEqual(IP); + expect(getIpAddress(new Headers({ 'cf-connecting-ip': BAD_IP, 'x-forwarded-for': IP }))).toEqual( + IP, + ); }); test('getIpAddress: No header', () => { - expect(detect.getIpAddress(new Headers())).toEqual(null); + expect(getIpAddress(new Headers())).toEqual(null); }); From 4272bb4c4d44aec362ecf0e1c16be61c5f053525 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Thu, 6 Nov 2025 22:48:34 -0800 Subject: [PATCH 11/37] Removed db types from docker build. --- .github/workflows/cd.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index a02e9900c..1b9e8965b 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -15,10 +15,6 @@ jobs: packages: write id-token: write - strategy: - matrix: - db-type: [postgresql] - steps: - uses: actions/checkout@v5 @@ -53,7 +49,6 @@ jobs: ghcr.io/${{ github.repository }} flavor: | latest=auto - prefix=${{ matrix.db-type }}- tags: | type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} @@ -65,7 +60,6 @@ jobs: with: context: . platforms: linux/amd64,linux/arm64 - build-args: DATABASE_TYPE=${{ matrix.db-type }} push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} From 437c9603dbf9e2362c3a19fd4c5952592867708f Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Thu, 6 Nov 2025 22:58:26 -0800 Subject: [PATCH 12/37] Fixed build. --- .github/workflows/cd.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 1b9e8965b..d3ee4ee2a 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -53,6 +53,9 @@ jobs: type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} + type=ref,event=branch + type=ref,event=pr + type=sha - name: Build and push Docker image id: build-and-push From 04a05bbf26b0855441b9c2cc4cfa1a4ad49efa9b Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Thu, 6 Nov 2025 23:35:14 -0800 Subject: [PATCH 13/37] Added workflow input. --- .github/workflows/cd.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index d3ee4ee2a..1e070a7c2 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -5,6 +5,11 @@ on: tags: - 'v*.*.*' workflow_dispatch: + inputs: + manual_tag: + description: 'Optional image tag (e.g. 1.2.3, beta, nightly)' + required: false + default: '' jobs: build: @@ -54,8 +59,7 @@ jobs: type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} type=ref,event=branch - type=ref,event=pr - type=sha + type=raw,value=latest,enable=${{ github.event_name == 'workflow_dispatch' }} - name: Build and push Docker image id: build-and-push From dd6556968cfbf8d05b18e7e93e2f37963015958a Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Thu, 6 Nov 2025 23:58:12 -0800 Subject: [PATCH 14/37] Updated image tag. --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 7b51db66c..8c8a47a6e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ --- services: umami: - image: ghcr.io/umami-software/umami:postgresql-latest + image: ghcr.io/umami-software/umami:latest ports: - "3000:3000" environment: From a90b7881387ac54d745fd8c11325e312c0fcc0d8 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 7 Nov 2025 00:09:53 -0800 Subject: [PATCH 15/37] Updated cd script. --- .github/workflows/cd.yml | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 1e070a7c2..3098d314b 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -6,8 +6,8 @@ on: - 'v*.*.*' workflow_dispatch: inputs: - manual_tag: - description: 'Optional image tag (e.g. 1.2.3, beta, nightly)' + version: + description: 'Optional image version (e.g. 3.0.0, beta)' required: false default: '' @@ -23,7 +23,6 @@ jobs: steps: - uses: actions/checkout@v5 - # Install cosign (for image signing) - name: Install cosign uses: sigstore/cosign-installer@v3 @@ -45,6 +44,21 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + # Normalize manual input if provided + - name: Normalize manual version + id: normalize + run: | + INPUT="${{ github.event.inputs.version }}" + if [[ -n "$INPUT" ]]; then + # Strip leading v if present + VERSION="${INPUT#v}" + MAJOR=$(echo "$VERSION" | cut -d. -f1) + MINOR=$(echo "$VERSION" | cut -d. -f2) + echo "version_tags=${VERSION},${MAJOR}.${MINOR},${MAJOR}" >> $GITHUB_ENV + else + echo "version_tags=" >> $GITHUB_ENV + fi + - name: Extract Docker metadata id: meta uses: docker/metadata-action@v5 @@ -55,11 +69,18 @@ jobs: flavor: | latest=auto tags: | + # Semver tags from real Git tags type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} + + # Manual input derived tags + type=raw,value=${{ env.version_tags }},enable=${{ env.version_tags != '' }} + + # Fallbacks for branches/PRs type=ref,event=branch - type=raw,value=latest,enable=${{ github.event_name == 'workflow_dispatch' }} + type=ref,event=pr + type=sha - name: Build and push Docker image id: build-and-push @@ -73,7 +94,6 @@ jobs: cache-from: type=gha cache-to: type=gha,mode=max - # Sign the published image digest - name: Sign the published Docker image env: TAGS: ${{ steps.meta.outputs.tags }} From df3ca02e8b0d8e9f4ba7027e43eb1d35e6f93b80 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 7 Nov 2025 08:52:16 -0800 Subject: [PATCH 16/37] Always push latest for Docker. --- .github/workflows/cd.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 3098d314b..515aa2aad 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -54,7 +54,8 @@ jobs: VERSION="${INPUT#v}" MAJOR=$(echo "$VERSION" | cut -d. -f1) MINOR=$(echo "$VERSION" | cut -d. -f2) - echo "version_tags=${VERSION},${MAJOR}.${MINOR},${MAJOR}" >> $GITHUB_ENV + # Include latest explicitly + echo "version_tags=${VERSION},${MAJOR}.${MINOR},${MAJOR},latest" >> $GITHUB_ENV else echo "version_tags=" >> $GITHUB_ENV fi @@ -69,17 +70,16 @@ jobs: flavor: | latest=auto tags: | - # Semver tags from real Git tags + # From real Git tags (v1.2.3) type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} - # Manual input derived tags + # Manual input tags type=raw,value=${{ env.version_tags }},enable=${{ env.version_tags != '' }} - # Fallbacks for branches/PRs + # Fallbacks type=ref,event=branch - type=ref,event=pr type=sha - name: Build and push Docker image From d2f512cae7af0538fc67231404d9ed31ff2c3adb Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 7 Nov 2025 09:14:19 -0800 Subject: [PATCH 17/37] Don't publish .sig files. --- .github/workflows/cd-manual.yml | 58 --------------------------------- .github/workflows/cd.yml | 21 +++++------- 2 files changed, 9 insertions(+), 70 deletions(-) delete mode 100644 .github/workflows/cd-manual.yml diff --git a/.github/workflows/cd-manual.yml b/.github/workflows/cd-manual.yml deleted file mode 100644 index df6aa6289..000000000 --- a/.github/workflows/cd-manual.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: Create docker images (manual) - -on: - workflow_dispatch: - inputs: - version: - type: string - description: Version - required: true - -jobs: - build: - name: Build, push, and deploy - runs-on: ubuntu-latest - - strategy: - matrix: - db-type: [postgresql] - - steps: - - uses: actions/checkout@v3 - - - name: Extract version parts from input - id: extract_version - run: | - echo "version=$(echo ${{ github.event.inputs.version }})" >> $GITHUB_ENV - echo "major=$(echo ${{ github.event.inputs.version }} | cut -d. -f1)" >> $GITHUB_ENV - echo "minor=$(echo ${{ github.event.inputs.version }} | cut -d. -f2)" >> $GITHUB_ENV - - - name: Generate tags - id: generate_tags - run: | - echo "tag_major=$(echo ${{ matrix.db-type }}-${{ env.major }})" >> $GITHUB_ENV - echo "tag_minor=$(echo ${{ matrix.db-type }}-${{ env.major }}.${{ env.minor }})" >> $GITHUB_ENV - echo "tag_patch=$(echo ${{ matrix.db-type }}-${{ env.version }})" >> $GITHUB_ENV - echo "tag_latest=$(echo ${{ matrix.db-type }}-latest)" >> $GITHUB_ENV - - - uses: mr-smithers-excellent/docker-build-push@v6 - name: Build & push Docker image to ghcr.io for ${{ matrix.db-type }} - with: - image: umami - tags: ${{ env.tag_major }}, ${{ env.tag_minor }}, ${{ env.tag_patch }}, ${{ env.tag_latest }} - buildArgs: DATABASE_TYPE=${{ matrix.db-type }} - registry: ghcr.io - multiPlatform: true - platform: linux/amd64,linux/arm64 - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - uses: mr-smithers-excellent/docker-build-push@v6 - name: Build & push Docker image to docker.io for ${{ matrix.db-type }} - with: - image: umamisoftware/umami - tags: ${{ env.tag_major }}, ${{ env.tag_minor }}, ${{ env.tag_patch }}, ${{ env.tag_latest }} - buildArgs: DATABASE_TYPE=${{ matrix.db-type }} - registry: docker.io - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 515aa2aad..a4934e797 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -50,11 +50,9 @@ jobs: run: | INPUT="${{ github.event.inputs.version }}" if [[ -n "$INPUT" ]]; then - # Strip leading v if present VERSION="${INPUT#v}" MAJOR=$(echo "$VERSION" | cut -d. -f1) MINOR=$(echo "$VERSION" | cut -d. -f2) - # Include latest explicitly echo "version_tags=${VERSION},${MAJOR}.${MINOR},${MAJOR},latest" >> $GITHUB_ENV else echo "version_tags=" >> $GITHUB_ENV @@ -70,15 +68,10 @@ jobs: flavor: | latest=auto tags: | - # From real Git tags (v1.2.3) type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} - - # Manual input tags type=raw,value=${{ env.version_tags }},enable=${{ env.version_tags != '' }} - - # Fallbacks type=ref,event=branch type=sha @@ -93,9 +86,13 @@ jobs: labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max + provenance: false # disable automatic attestations + + # Generate a local provenance attestation instead of uploading signatures + - name: Generate provenance attestation + run: | + cosign attest --yes \ + --predicate <(echo '{"build":"github-actions","repo":"${{ github.repository }}","run_id":"${{ github.run_id }}"}') \ + --type slsaprovenance \ + ${{ steps.meta.outputs.tags }} - - name: Sign the published Docker image - env: - TAGS: ${{ steps.meta.outputs.tags }} - DIGEST: ${{ steps.build-and-push.outputs.digest }} - run: echo "${TAGS}" | xargs -I {} cosign sign --yes "{}@${DIGEST}" From 3e9ca8761ee4ae3dab686fd4fceea0f9aec29e20 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 7 Nov 2025 09:15:01 -0800 Subject: [PATCH 18/37] Removed workflow script. --- .github/workflows/delete-untagged-images.yml | 22 -------------------- 1 file changed, 22 deletions(-) delete mode 100644 .github/workflows/delete-untagged-images.yml diff --git a/.github/workflows/delete-untagged-images.yml b/.github/workflows/delete-untagged-images.yml deleted file mode 100644 index a23a1bd27..000000000 --- a/.github/workflows/delete-untagged-images.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Delete untagged GHCR images - -on: - workflow_dispatch: # Run manually from the Actions tab - -jobs: - cleanup: - name: Delete all untagged images - runs-on: ubuntu-latest - - permissions: - packages: write - contents: read - - steps: - - name: Delete untagged GHCR images - uses: actions/delete-package-versions@v5 - with: - package-name: "umami" # 👈 change if your GHCR package name differs - package-type: "container" - delete-only-untagged-versions: true - min-versions-to-keep: 0 From 6ee93f7ac92a49ece115024e46d2dce51c963ebf Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 7 Nov 2025 12:21:17 -0800 Subject: [PATCH 19/37] Updated README and cd.yml. --- .github/workflows/cd.yml | 59 ++++++++++++++++++++++------------------ README.md | 2 +- 2 files changed, 34 insertions(+), 27 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index a4934e797..534b23210 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -7,7 +7,7 @@ on: workflow_dispatch: inputs: version: - description: 'Optional image version (e.g. 3.0.0, beta)' + description: 'Optional image version (e.g. 3.0.0, v3.0.0, or 3.0.0-beta.1)' required: false default: '' @@ -29,6 +29,13 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 + - name: Log into GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Log into Docker Hub if: github.repository == 'umami-software/umami' uses: docker/login-action@v3 @@ -37,27 +44,29 @@ jobs: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - - name: Log into GHCR - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - # Normalize manual input if provided - - name: Normalize manual version - id: normalize + # Compute tags for the image + - name: Compute version tags + id: compute run: | INPUT="${{ github.event.inputs.version }}" + TAGS="" + if [[ -n "$INPUT" ]]; then - VERSION="${INPUT#v}" + VERSION="${INPUT#v}" # strip leading v MAJOR=$(echo "$VERSION" | cut -d. -f1) MINOR=$(echo "$VERSION" | cut -d. -f2) - echo "version_tags=${VERSION},${MAJOR}.${MINOR},${MAJOR},latest" >> $GITHUB_ENV - else - echo "version_tags=" >> $GITHUB_ENV + + # prereleases (e.g., 3.0.0-beta) do NOT get 'latest' + if [[ "$VERSION" == *-* ]]; then + TAGS="${VERSION}" + else + TAGS="${VERSION},${MAJOR}.${MINOR},${MAJOR},latest" + fi fi + echo "tags=$TAGS" >> $GITHUB_OUTPUT + echo "Computed tags: $TAGS" + - name: Extract Docker metadata id: meta uses: docker/metadata-action@v5 @@ -65,34 +74,32 @@ jobs: images: | umamisoftware/umami,enable=${{ github.repository == 'umami-software/umami' }} ghcr.io/${{ github.repository }} - flavor: | - latest=auto tags: | - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{major}} - type=raw,value=${{ env.version_tags }},enable=${{ env.version_tags != '' }} + type=semver,pattern={{version}},enable=${{ github.ref_type == 'tag' }} + type=semver,pattern={{major}}.{{minor}},enable=${{ github.ref_type == 'tag' }} + type=semver,pattern={{major}},enable=${{ github.ref_type == 'tag' }} + type=raw,value=${{ steps.compute.outputs.tags }},enable=${{ steps.compute.outputs.tags != '' }} type=ref,event=branch type=sha + # Build and push images - name: Build and push Docker image id: build-and-push uses: docker/build-push-action@v6 with: context: . - platforms: linux/amd64,linux/arm64 push: true + platforms: linux/amd64,linux/arm64 tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max - provenance: false # disable automatic attestations + provenance: false # disable automatic registry attestations - # Generate a local provenance attestation instead of uploading signatures - - name: Generate provenance attestation + # Generate a local provenance attestation (not uploaded) + - name: Generate local provenance attestation run: | cosign attest --yes \ --predicate <(echo '{"build":"github-actions","repo":"${{ github.repository }}","run_id":"${{ github.run_id }}"}') \ --type slsaprovenance \ ${{ steps.meta.outputs.tags }} - diff --git a/README.md b/README.md index 6d166d8c8..d3791e269 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ docker compose up -d Alternatively, to pull just the Umami Docker image with PostgreSQL support: ```bash -docker pull docker.umami.is/umami-software/umami:postgresql-latest +docker pull docker.umami.is/umami-software/umami:latest ``` --- From 8119dae3c3d951f9525c3d2e03587f451dd9dc14 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 7 Nov 2025 13:59:50 -0800 Subject: [PATCH 20/37] Updated GH workflow. --- .github/workflows/cd.yml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 534b23210..f44d17689 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -23,9 +23,6 @@ jobs: steps: - uses: actions/checkout@v5 - - name: Install cosign - uses: sigstore/cosign-installer@v3 - - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -95,11 +92,3 @@ jobs: cache-from: type=gha cache-to: type=gha,mode=max provenance: false # disable automatic registry attestations - - # Generate a local provenance attestation (not uploaded) - - name: Generate local provenance attestation - run: | - cosign attest --yes \ - --predicate <(echo '{"build":"github-actions","repo":"${{ github.repository }}","run_id":"${{ github.run_id }}"}') \ - --type slsaprovenance \ - ${{ steps.meta.outputs.tags }} From e3ca002d77827430e7df155da31ffcc4b9c8aa13 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 7 Nov 2025 14:35:05 -0800 Subject: [PATCH 21/37] Fixed tags in build. --- .github/workflows/cd.yml | 51 +++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index f44d17689..f21f58aa1 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -18,7 +18,6 @@ jobs: permissions: contents: read packages: write - id-token: write steps: - uses: actions/checkout@v5 @@ -41,54 +40,52 @@ jobs: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - # Compute tags for the image - name: Compute version tags id: compute run: | INPUT="${{ github.event.inputs.version }}" + REF_TYPE="${{ github.ref_type }}" + REF_NAME="${{ github.ref_name }}" + + # Determine version source + if [[ -n "$INPUT" ]]; then + VERSION="${INPUT#v}" + elif [[ "$REF_TYPE" == "tag" ]]; then + VERSION="${REF_NAME#v}" + else + VERSION="" + fi + TAGS="" - if [[ -n "$INPUT" ]]; then - VERSION="${INPUT#v}" # strip leading v + if [[ -n "$VERSION" ]]; then MAJOR=$(echo "$VERSION" | cut -d. -f1) MINOR=$(echo "$VERSION" | cut -d. -f2) - # prereleases (e.g., 3.0.0-beta) do NOT get 'latest' if [[ "$VERSION" == *-* ]]; then - TAGS="${VERSION}" + # prerelease: only version tag + TAGS="$VERSION" else - TAGS="${VERSION},${MAJOR}.${MINOR},${MAJOR},latest" + # stable release: version + hierarchy + latest + TAGS="$VERSION,${MAJOR}.${MINOR},${MAJOR},latest" fi + else + # Non-tag build (e.g. from main branch) + TAGS="${REF_NAME}" fi echo "tags=$TAGS" >> $GITHUB_OUTPUT echo "Computed tags: $TAGS" - - name: Extract Docker metadata - id: meta - uses: docker/metadata-action@v5 - with: - images: | - umamisoftware/umami,enable=${{ github.repository == 'umami-software/umami' }} - ghcr.io/${{ github.repository }} - tags: | - type=semver,pattern={{version}},enable=${{ github.ref_type == 'tag' }} - type=semver,pattern={{major}}.{{minor}},enable=${{ github.ref_type == 'tag' }} - type=semver,pattern={{major}},enable=${{ github.ref_type == 'tag' }} - type=raw,value=${{ steps.compute.outputs.tags }},enable=${{ steps.compute.outputs.tags != '' }} - type=ref,event=branch - type=sha - - # Build and push images - name: Build and push Docker image - id: build-and-push uses: docker/build-push-action@v6 with: context: . push: true platforms: linux/amd64,linux/arm64 - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} + tags: | + umamisoftware/umami:${{ steps.compute.outputs.tags }} + ghcr.io/${{ github.repository }}:${{ steps.compute.outputs.tags }} cache-from: type=gha cache-to: type=gha,mode=max - provenance: false # disable automatic registry attestations + provenance: false From de6515139e5067375dc62601b5fb699846bfe161 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 7 Nov 2025 18:17:51 -0800 Subject: [PATCH 22/37] Fixed Docker permissions. --- .github/workflows/cd.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index f21f58aa1..6569954ea 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -67,7 +67,7 @@ jobs: TAGS="$VERSION" else # stable release: version + hierarchy + latest - TAGS="$VERSION,${MAJOR}.${MINOR},${MAJOR},latest" + TAGS="$VERSION,${MAJOR}.${MINOR},${MAJOR},postgresql-latest,latest" fi else # Non-tag build (e.g. from main branch) @@ -78,14 +78,15 @@ jobs: echo "Computed tags: $TAGS" - name: Build and push Docker image + id: build uses: docker/build-push-action@v6 with: context: . push: true platforms: linux/amd64,linux/arm64 - tags: | - umamisoftware/umami:${{ steps.compute.outputs.tags }} - ghcr.io/${{ github.repository }}:${{ steps.compute.outputs.tags }} cache-from: type=gha cache-to: type=gha,mode=max provenance: false + tags: | + ghcr.io/${{ github.repository }}:${{ steps.compute.outputs.tags }} + ${{ github.repository == 'umami-software/umami' && format('umamisoftware/umami:{0}', steps.compute.outputs.tags) || '' }} From 6ba9c1c40c398020a9720a7d2ad4ecfa1c4639bc Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 7 Nov 2025 22:41:10 -0800 Subject: [PATCH 23/37] New docker workflow. --- .github/workflows/cd.yml | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 6569954ea..a9509bce0 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -78,15 +78,23 @@ jobs: echo "Computed tags: $TAGS" - name: Build and push Docker image - id: build - uses: docker/build-push-action@v6 - with: - context: . - push: true - platforms: linux/amd64,linux/arm64 - cache-from: type=gha - cache-to: type=gha,mode=max - provenance: false - tags: | - ghcr.io/${{ github.repository }}:${{ steps.compute.outputs.tags }} - ${{ github.repository == 'umami-software/umami' && format('umamisoftware/umami:{0}', steps.compute.outputs.tags) || '' }} + run: | + TAGS="${{ steps.compute.outputs.tags }}" + + # Set image targets conditionally + if [[ "${{ github.repository }}" == "umami-software/umami" ]]; then + IMAGES=("umamisoftware/umami" "ghcr.io/${{ github.repository }}") + else + IMAGES=("ghcr.io/${{ github.repository }}") + fi + + for IMAGE in "${IMAGES[@]}"; do + echo "Building and pushing $IMAGE with tags: $TAGS" + docker buildx build \ + --platform linux/amd64,linux/arm64 \ + --push \ + $(echo "$TAGS" | tr ',' '\n' | sed "s|^|--tag ${IMAGE}:|") \ + --cache-from type=gha \ + --cache-to type=gha,mode=max \ + . + done From 1879c161eeba2591f72b4a9186d48f5836ca04a4 Mon Sep 17 00:00:00 2001 From: metaloozee Date: Sun, 9 Nov 2025 00:22:06 +0530 Subject: [PATCH 24/37] fix: Redirect loop on auth failure --- src/app/(main)/App.tsx | 8 ++------ src/app/logout/LogoutPage.tsx | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/app/(main)/App.tsx b/src/app/(main)/App.tsx index 32218d115..7700639da 100644 --- a/src/app/(main)/App.tsx +++ b/src/app/(main)/App.tsx @@ -9,18 +9,14 @@ import { MobileNav } from '@/app/(main)/MobileNav'; export function App({ children }) { const { user, isLoading, error } = useLoginQuery(); const config = useConfig(); - const { pathname, router } = useNavigation(); + const { pathname } = useNavigation(); if (isLoading || !config) { return ; } if (error) { - if (process.env.cloudMode) { - window.location.href = '/login'; - } else { - router.push('/login'); - } + window.location.href = '/login'; return null; } diff --git a/src/app/logout/LogoutPage.tsx b/src/app/logout/LogoutPage.tsx index 909f35de3..bd4717961 100644 --- a/src/app/logout/LogoutPage.tsx +++ b/src/app/logout/LogoutPage.tsx @@ -13,7 +13,7 @@ export function LogoutPage() { async function logout() { await post('/auth/logout'); - router.push('/login'); + window.location.href = '/login'; } removeClientAuthToken(); From bf548c5acae58cc1420d3ae7e79127f3db654e81 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Sun, 9 Nov 2025 21:19:38 -0800 Subject: [PATCH 25/37] Fix revenue bigInt but and case insensitive currency --- src/queries/sql/reports/getRevenue.ts | 38 +++++++++++---------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/src/queries/sql/reports/getRevenue.ts b/src/queries/sql/reports/getRevenue.ts index e13106ced..5771bdef0 100644 --- a/src/queries/sql/reports/getRevenue.ts +++ b/src/queries/sql/reports/getRevenue.ts @@ -41,6 +41,15 @@ async function relationalQuery( currency, }); + const joinQuery = filterQuery + ? `join website_event + on website_event.website_id = revenue.website_id + and website_event.session_id = revenue.session_id + and website_event.event_id = revenue.event_id + and website_event.website_id = {{websiteId::uuid}} + and website_event.created_at between {{startDate}} and {{endDate}}` + : ''; + const chart = await rawQuery( ` select @@ -48,17 +57,12 @@ async function relationalQuery( ${getDateSQL('revenue.created_at', unit, timezone)} t, sum(revenue.revenue) y from revenue - join website_event - on website_event.website_id = revenue.website_id - and website_event.session_id = revenue.session_id - and website_event.event_id = revenue.event_id - and website_event.website_id = {{websiteId::uuid}} - and website_event.created_at between {{startDate}} and {{endDate}} + ${joinQuery} ${cohortQuery} ${joinSessionQuery} where revenue.website_id = {{websiteId::uuid}} and revenue.created_at between {{startDate}} and {{endDate}} - and revenue.currency like {{currency}} + and revenue.currency ilike {{currency}} ${filterQuery} group by x, t order by t @@ -72,19 +76,14 @@ async function relationalQuery( session.country as name, sum(revenue) value from revenue - join website_event - on website_event.website_id = revenue.website_id - and website_event.session_id = revenue.session_id - and website_event.event_id = revenue.event_id - and website_event.website_id = {{websiteId::uuid}} - and website_event.created_at between {{startDate}} and {{endDate}} + ${joinQuery} join session on session.website_id = revenue.website_id and session.session_id = revenue.session_id ${cohortQuery} where revenue.website_id = {{websiteId::uuid}} and revenue.created_at between {{startDate}} and {{endDate}} - and revenue.currency = {{currency}} + and revenue.currency ilike {{currency}} ${filterQuery} group by session.country `, @@ -98,23 +97,18 @@ async function relationalQuery( count(distinct revenue.event_id) as count, count(distinct revenue.session_id) as unique_count from revenue - join website_event - on website_event.website_id = revenue.website_id - and website_event.session_id = revenue.session_id - and website_event.event_id = revenue.event_id - and website_event.website_id = {{websiteId::uuid}} - and website_event.created_at between {{startDate}} and {{endDate}} + ${joinQuery} ${cohortQuery} ${joinSessionQuery} where revenue.website_id = {{websiteId::uuid}} and revenue.created_at between {{startDate}} and {{endDate}} - and revenue.currency = {{currency}} + and revenue.currency ilike {{currency}} ${filterQuery} `, queryParams, ).then(result => result?.[0]); - total.average = total.count > 0 ? total.sum / total.count : 0; + total.average = total.count > 0 ? Number(total.sum) / Number(total.count) : 0; return { chart, country, total }; } From f30724629cb4a6b4c401c78e83085b7671e1fbe4 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Sun, 9 Nov 2025 21:37:35 -0800 Subject: [PATCH 26/37] Fix null and string return types from getWebsiteStats --- src/queries/sql/getWebsiteStats.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/queries/sql/getWebsiteStats.ts b/src/queries/sql/getWebsiteStats.ts index 391d22be2..4a4bef78b 100644 --- a/src/queries/sql/getWebsiteStats.ts +++ b/src/queries/sql/getWebsiteStats.ts @@ -36,11 +36,11 @@ async function relationalQuery( return rawQuery( ` select - sum(t.c) as "pageviews", + cast(coalesce(sum(t.c), 0) as bigint) as "pageviews", count(distinct t.session_id) as "visitors", count(distinct t.visit_id) as "visits", - sum(case when t.c = 1 then 1 else 0 end) as "bounces", - sum(${getTimestampDiffSQL('t.min_time', 't.max_time')}) as "totaltime" + coalesce(sum(case when t.c = 1 then 1 else 0 end), 0) as "bounces", + cast(coalesce(sum(${getTimestampDiffSQL('t.min_time', 't.max_time')}), 0) as bigint) as "totaltime" from ( select website_event.session_id, From 9230f3cb7b18203614fe2856d216540916be785c Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Sun, 9 Nov 2025 22:03:06 -0800 Subject: [PATCH 27/37] manually include basePath --- src/app/(main)/App.tsx | 2 +- src/app/logout/LogoutPage.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/(main)/App.tsx b/src/app/(main)/App.tsx index 7700639da..ec08838d1 100644 --- a/src/app/(main)/App.tsx +++ b/src/app/(main)/App.tsx @@ -16,7 +16,7 @@ export function App({ children }) { } if (error) { - window.location.href = '/login'; + window.location.href = `${process.env.basePath || ''}/login`; return null; } diff --git a/src/app/logout/LogoutPage.tsx b/src/app/logout/LogoutPage.tsx index bd4717961..d66d62a98 100644 --- a/src/app/logout/LogoutPage.tsx +++ b/src/app/logout/LogoutPage.tsx @@ -13,7 +13,7 @@ export function LogoutPage() { async function logout() { await post('/auth/logout'); - window.location.href = '/login'; + window.location.href = `${process.env.basePath || ''}/login`; } removeClientAuthToken(); From f3e246c64bf75093664472d0cae32a6067089327 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Sun, 9 Nov 2025 23:58:20 -0800 Subject: [PATCH 28/37] fix hasdata queries, add hasData to website events, fix sessionactivity truncation, --- .../[websiteId]/events/EventsTable.tsx | 37 ++++++++++++++++++- .../[websiteId]/sessions/SessionActivity.tsx | 9 +++-- src/queries/sql/events/getEventData.ts | 28 +++++++------- src/queries/sql/events/getWebsiteEvents.ts | 6 ++- .../sql/sessions/getSessionActivity.ts | 4 +- 5 files changed, 60 insertions(+), 24 deletions(-) diff --git a/src/app/(main)/websites/[websiteId]/events/EventsTable.tsx b/src/app/(main)/websites/[websiteId]/events/EventsTable.tsx index e9e3e6a0c..ea0edde1b 100644 --- a/src/app/(main)/websites/[websiteId]/events/EventsTable.tsx +++ b/src/app/(main)/websites/[websiteId]/events/EventsTable.tsx @@ -1,11 +1,24 @@ -import { DataTable, DataColumn, Row, Text, DataTableProps, IconLabel } from '@umami/react-zen'; +import { + DataTable, + DataColumn, + Row, + Text, + DataTableProps, + IconLabel, + Button, + Dialog, + DialogTrigger, + Icon, + Popover, +} from '@umami/react-zen'; import { useFormat, useMessages, useNavigation } from '@/components/hooks'; import { Avatar } from '@/components/common/Avatar'; import Link from 'next/link'; -import { Eye } from '@/components/icons'; +import { Eye, FileText } from '@/components/icons'; import { Lightning } from '@/components/svg'; import { DateDistance } from '@/components/common/DateDistance'; import { TypeIcon } from '@/components/common/TypeIcon'; +import { EventData } from '@/components/metrics/EventData'; export function EventsTable(props: DataTableProps) { const { formatMessage, labels } = useMessages(); @@ -32,6 +45,7 @@ export function EventsTable(props: DataTableProps) { > {row.eventName || row.urlPath} + {row.hasData > 0 && } ); }} @@ -72,3 +86,22 @@ export function EventsTable(props: DataTableProps) { ); } + +const PropertiesButton = props => { + return ( + + + + + + + + + ); +}; diff --git a/src/app/(main)/websites/[websiteId]/sessions/SessionActivity.tsx b/src/app/(main)/websites/[websiteId]/sessions/SessionActivity.tsx index b9f34e485..7bcf1b760 100644 --- a/src/app/(main)/websites/[websiteId]/sessions/SessionActivity.tsx +++ b/src/app/(main)/websites/[websiteId]/sessions/SessionActivity.tsx @@ -14,7 +14,7 @@ import { import { LoadingPanel } from '@/components/common/LoadingPanel'; import { Eye, FileText } from '@/components/icons'; import { Lightning } from '@/components/svg'; -import { useMessages, useSessionActivityQuery, useTimezone } from '@/components/hooks'; +import { useMessages, useMobile, useSessionActivityQuery, useTimezone } from '@/components/hooks'; import { EventData } from '@/components/metrics/EventData'; export function SessionActivity({ @@ -36,6 +36,7 @@ export function SessionActivity({ startDate, endDate, ); + const { isMobile } = useMobile(); let lastDay = null; return ( @@ -50,16 +51,16 @@ export function SessionActivity({ {showHeader && {formatTimezoneDate(createdAt, 'PPPP')}} - {formatTimezoneDate(createdAt, 'pp')} + {formatTimezoneDate(createdAt, 'pp')} {eventName ? : } - + {eventName ? formatMessage(labels.triggeredEvent) : formatMessage(labels.viewedPage)} - + {eventName || urlPath} {hasData > 0 && } diff --git a/src/queries/sql/events/getEventData.ts b/src/queries/sql/events/getEventData.ts index 42dc2040b..269258a8d 100644 --- a/src/queries/sql/events/getEventData.ts +++ b/src/queries/sql/events/getEventData.ts @@ -19,20 +19,20 @@ async function relationalQuery(websiteId: string, eventId: string) { return rawQuery( ` - select website_id as "websiteId", - session_id as "sessionId", - event_id as "eventId", - url_path as "urlPath", - event_name as "eventName", - data_key as "dataKey", - string_value as "stringValue", - number_value as "numberValue", - date_value as "dateValue", - data_type as "dataType", - created_at as "createdAt" + select event_data.website_id as "websiteId", + event_data.website_event_id as "eventId", + website_event.event_name as "eventName", + event_data.data_key as "dataKey", + event_data.string_value as "stringValue", + event_data.number_value as "numberValue", + event_data.date_value as "dateValue", + event_data.data_type as "dataType", + event_data.created_at as "createdAt" from event_data - website_id = {{websiteId::uuid}} - event_id = {{eventId::uuid}} + join website_event on website_event.event_id = event_data.website_event_id + and website_event.website_id = {{websiteId::uuid}} + where event_data.website_id = {{websiteId::uuid}} + and event_data.website_event_id = {{eventId::uuid}} `, { websiteId, eventId }, FUNCTION_NAME, @@ -45,9 +45,7 @@ async function clickhouseQuery(websiteId: string, eventId: string): Promise Date: Mon, 10 Nov 2025 01:07:11 -0800 Subject: [PATCH 29/37] fix realtime logs for mobile --- .../[websiteId]/realtime/RealtimeLog.tsx | 38 ++++++++++++++----- .../[websiteId]/realtime/RealtimePage.tsx | 5 ++- src/components/metrics/ListTable.tsx | 6 +-- 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/app/(main)/websites/[websiteId]/realtime/RealtimeLog.tsx b/src/app/(main)/websites/[websiteId]/realtime/RealtimeLog.tsx index 9ae19bf89..3dec340f2 100644 --- a/src/app/(main)/websites/[websiteId]/realtime/RealtimeLog.tsx +++ b/src/app/(main)/websites/[websiteId]/realtime/RealtimeLog.tsx @@ -9,6 +9,7 @@ import { useCountryNames, useLocale, useMessages, + useMobile, useNavigation, useTimezone, useWebsite, @@ -40,6 +41,7 @@ export function RealtimeLog({ data }: { data: any }) { const { countryNames } = useCountryNames(locale); const [filter, setFilter] = useState(TYPE_ALL); const { updateParams } = useNavigation(); + const { isPhone } = useMobile(); const buttons = [ { @@ -123,12 +125,18 @@ export function RealtimeLog({ data }: { data: any }) { const row = logs[index]; return ( - - - - {getTime(row)} + + + + + + + {getTime(row)} + - {getDetail(row)} + + {getDetail(row)} + ); @@ -168,10 +176,22 @@ export function RealtimeLog({ data }: { data: any }) { return ( {formatMessage(labels.activity)} - - - - + {isPhone ? ( + <> + + + + + + + + ) : ( + + + + + )} + {logs?.length === 0 && } {logs?.length > 0 && ( diff --git a/src/app/(main)/websites/[websiteId]/realtime/RealtimePage.tsx b/src/app/(main)/websites/[websiteId]/realtime/RealtimePage.tsx index 7f9ab6085..0f9fa358b 100644 --- a/src/app/(main)/websites/[websiteId]/realtime/RealtimePage.tsx +++ b/src/app/(main)/websites/[websiteId]/realtime/RealtimePage.tsx @@ -6,7 +6,7 @@ import { PageBody } from '@/components/common/PageBody'; import { Panel } from '@/components/common/Panel'; import { RealtimeChart } from '@/components/metrics/RealtimeChart'; import { WorldMap } from '@/components/metrics/WorldMap'; -import { useRealtimeQuery } from '@/components/hooks'; +import { useMobile, useRealtimeQuery } from '@/components/hooks'; import { RealtimeLog } from './RealtimeLog'; import { RealtimeHeader } from './RealtimeHeader'; import { RealtimePaths } from './RealtimePaths'; @@ -16,6 +16,7 @@ import { percentFilter } from '@/lib/filters'; export function RealtimePage({ websiteId }: { websiteId: string }) { const { data, isLoading, error } = useRealtimeQuery(websiteId); + const { isMobile } = useMobile(); if (isLoading || error) { return ; @@ -48,7 +49,7 @@ export function RealtimePage({ websiteId }: { websiteId: string }) { - + diff --git a/src/components/metrics/ListTable.tsx b/src/components/metrics/ListTable.tsx index 303556b01..e76e01745 100644 --- a/src/components/metrics/ListTable.tsx +++ b/src/components/metrics/ListTable.tsx @@ -57,7 +57,7 @@ export function ListTable({ showPercentage={showPercentage} change={renderChange ? renderChange(row, index) : null} currency={currency} - isMobile={isPhone} + isPhone={isPhone} /> ); }; @@ -101,7 +101,7 @@ const AnimatedRow = ({ animate, showPercentage = true, currency, - isMobile, + isPhone, }) => { const props = useSpring({ width: percent, @@ -120,7 +120,7 @@ const AnimatedRow = ({ gap > - + {label} From 49e1582c288bc5bc71f3f4e0482873b0930c1297 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Mon, 10 Nov 2025 15:36:43 -0800 Subject: [PATCH 30/37] implement generateTimeSeries for eventsChart --- src/components/metrics/EventsChart.tsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/components/metrics/EventsChart.tsx b/src/components/metrics/EventsChart.tsx index 7301faf44..246772b3e 100644 --- a/src/components/metrics/EventsChart.tsx +++ b/src/components/metrics/EventsChart.tsx @@ -1,10 +1,11 @@ -import { useMemo, useState, useEffect } from 'react'; -import { colord } from 'colord'; import { BarChart, BarChartProps } from '@/components/charts/BarChart'; +import { LoadingPanel } from '@/components/common/LoadingPanel'; import { useDateRange, useLocale, useWebsiteEventsSeriesQuery } from '@/components/hooks'; import { renderDateLabels } from '@/lib/charts'; import { CHART_COLORS } from '@/lib/constants'; -import { LoadingPanel } from '@/components/common/LoadingPanel'; +import { generateTimeSeries } from '@/lib/date'; +import { colord } from 'colord'; +import { useCallback, useEffect, useMemo, useState } from 'react'; export interface EventsChartProps extends BarChartProps { websiteId: string; @@ -15,7 +16,7 @@ export function EventsChart({ websiteId, focusLabel }: EventsChartProps) { const { dateRange: { startDate, endDate, unit }, } = useDateRange(); - const { locale } = useLocale(); + const { locale, dateLocale } = useLocale(); const { data, isLoading, error } = useWebsiteEventsSeriesQuery(websiteId); const [label, setLabel] = useState(focusLabel); @@ -37,7 +38,7 @@ export function EventsChart({ websiteId, focusLabel }: EventsChartProps) { const color = colord(CHART_COLORS[index % CHART_COLORS.length]); return { label: key, - data: map[key], + data: generateTimeSeries(map[key], startDate, endDate, unit, dateLocale), lineTension: 0, backgroundColor: color.alpha(0.6).toRgbString(), borderColor: color.alpha(0.7).toRgbString(), @@ -54,6 +55,8 @@ export function EventsChart({ websiteId, focusLabel }: EventsChartProps) { } }, [focusLabel]); + const renderXLabel = useCallback(renderDateLabels(unit, locale), [unit, locale]); + return ( {chartData && ( @@ -63,7 +66,7 @@ export function EventsChart({ websiteId, focusLabel }: EventsChartProps) { maxDate={endDate} unit={unit} stacked={true} - renderXLabel={renderDateLabels(unit, locale)} + renderXLabel={renderXLabel} height="400px" /> )} From a1d6204373b13bb4675de3aacc49a650b24318be Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Mon, 10 Nov 2025 17:24:51 -0800 Subject: [PATCH 31/37] add canonicalizeTimezone conversions Co-authored-by: Om Mishra --- src/components/hooks/useDateParameters.ts | 4 ++-- src/components/hooks/useTimezone.ts | 15 +++++++++++++-- src/lib/constants.ts | 21 +++++++++++++++++++++ 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/components/hooks/useDateParameters.ts b/src/components/hooks/useDateParameters.ts index 359bbc1fd..16e123142 100644 --- a/src/components/hooks/useDateParameters.ts +++ b/src/components/hooks/useDateParameters.ts @@ -5,7 +5,7 @@ export function useDateParameters() { const { dateRange: { startDate, endDate, unit }, } = useDateRange(); - const { timezone, toUtc } = useTimezone(); + const { timezone, toUtc, canonicalizeTimezone } = useTimezone(); return { startAt: +toUtc(startDate), @@ -13,6 +13,6 @@ export function useDateParameters() { startDate: toUtc(startDate).toISOString(), endDate: toUtc(endDate).toISOString(), unit, - timezone, + timezone: canonicalizeTimezone(timezone), }; } diff --git a/src/components/hooks/useTimezone.ts b/src/components/hooks/useTimezone.ts index 0e1fe6cd3..3770c26bd 100644 --- a/src/components/hooks/useTimezone.ts +++ b/src/components/hooks/useTimezone.ts @@ -1,5 +1,5 @@ import { setItem } from '@/lib/storage'; -import { TIMEZONE_CONFIG } from '@/lib/constants'; +import { TIMEZONE_CONFIG, TIMEZONE_LEGACY } from '@/lib/constants'; import { formatInTimeZone, zonedTimeToUtc, utcToZonedTime } from 'date-fns-tz'; import { useApp, setTimezone } from '@/store/app'; import { useLocale } from './useLocale'; @@ -34,5 +34,16 @@ export function useTimezone() { return utcToZonedTime(date, timezone); }; - return { timezone, saveTimezone, formatTimezoneDate, toUtc, fromUtc }; + const canonicalizeTimezone = (timezone: string): string => { + return TIMEZONE_LEGACY[timezone] ?? timezone; + }; + + return { + timezone, + saveTimezone, + formatTimezoneDate, + toUtc, + fromUtc, + canonicalizeTimezone, + }; } diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 50a25b8d8..195fe1be8 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -658,3 +658,24 @@ export const CURRENCIES = [ { id: 'OMR', name: 'Omani Rial' }, { id: 'GHS', name: 'Ghanaian Cedi' }, ]; + +export const TIMEZONE_LEGACY: Record = { + 'Asia/Batavia': 'Asia/Jakarta', + 'Asia/Calcutta': 'Asia/Kolkata', + 'Asia/Chongqing': 'Asia/Shanghai', + 'Asia/Harbin': 'Asia/Shanghai', + 'Asia/Jayapura': 'Asia/Pontianak', + 'Asia/Katmandu': 'Asia/Kathmandu', + 'Asia/Macao': 'Asia/Macau', + 'Asia/Rangoon': 'Asia/Yangon', + 'Asia/Saigon': 'Asia/Ho_Chi_Minh', + 'Europe/Kiev': 'Europe/Kyiv', + 'Europe/Zaporozhye': 'Europe/Kyiv', + 'Etc/UTC': 'UTC', + 'US/Arizona': 'America/Phoenix', + 'US/Central': 'America/Chicago', + 'US/Eastern': 'America/New_York', + 'US/Mountain': 'America/Denver', + 'US/Pacific': 'America/Los_Angeles', + 'US/Samoa': 'Pacific/Pago_Pago', +}; From 13ab84d50e7f8bfa4837ba8f7e48e97703b227dd Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Mon, 10 Nov 2025 17:26:06 -0800 Subject: [PATCH 32/37] Revert "add canonicalizeTimezone conversions" This reverts commit a1d6204373b13bb4675de3aacc49a650b24318be. --- src/components/hooks/useDateParameters.ts | 4 ++-- src/components/hooks/useTimezone.ts | 15 ++------------- src/lib/constants.ts | 21 --------------------- 3 files changed, 4 insertions(+), 36 deletions(-) diff --git a/src/components/hooks/useDateParameters.ts b/src/components/hooks/useDateParameters.ts index 16e123142..359bbc1fd 100644 --- a/src/components/hooks/useDateParameters.ts +++ b/src/components/hooks/useDateParameters.ts @@ -5,7 +5,7 @@ export function useDateParameters() { const { dateRange: { startDate, endDate, unit }, } = useDateRange(); - const { timezone, toUtc, canonicalizeTimezone } = useTimezone(); + const { timezone, toUtc } = useTimezone(); return { startAt: +toUtc(startDate), @@ -13,6 +13,6 @@ export function useDateParameters() { startDate: toUtc(startDate).toISOString(), endDate: toUtc(endDate).toISOString(), unit, - timezone: canonicalizeTimezone(timezone), + timezone, }; } diff --git a/src/components/hooks/useTimezone.ts b/src/components/hooks/useTimezone.ts index 3770c26bd..0e1fe6cd3 100644 --- a/src/components/hooks/useTimezone.ts +++ b/src/components/hooks/useTimezone.ts @@ -1,5 +1,5 @@ import { setItem } from '@/lib/storage'; -import { TIMEZONE_CONFIG, TIMEZONE_LEGACY } from '@/lib/constants'; +import { TIMEZONE_CONFIG } from '@/lib/constants'; import { formatInTimeZone, zonedTimeToUtc, utcToZonedTime } from 'date-fns-tz'; import { useApp, setTimezone } from '@/store/app'; import { useLocale } from './useLocale'; @@ -34,16 +34,5 @@ export function useTimezone() { return utcToZonedTime(date, timezone); }; - const canonicalizeTimezone = (timezone: string): string => { - return TIMEZONE_LEGACY[timezone] ?? timezone; - }; - - return { - timezone, - saveTimezone, - formatTimezoneDate, - toUtc, - fromUtc, - canonicalizeTimezone, - }; + return { timezone, saveTimezone, formatTimezoneDate, toUtc, fromUtc }; } diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 195fe1be8..50a25b8d8 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -658,24 +658,3 @@ export const CURRENCIES = [ { id: 'OMR', name: 'Omani Rial' }, { id: 'GHS', name: 'Ghanaian Cedi' }, ]; - -export const TIMEZONE_LEGACY: Record = { - 'Asia/Batavia': 'Asia/Jakarta', - 'Asia/Calcutta': 'Asia/Kolkata', - 'Asia/Chongqing': 'Asia/Shanghai', - 'Asia/Harbin': 'Asia/Shanghai', - 'Asia/Jayapura': 'Asia/Pontianak', - 'Asia/Katmandu': 'Asia/Kathmandu', - 'Asia/Macao': 'Asia/Macau', - 'Asia/Rangoon': 'Asia/Yangon', - 'Asia/Saigon': 'Asia/Ho_Chi_Minh', - 'Europe/Kiev': 'Europe/Kyiv', - 'Europe/Zaporozhye': 'Europe/Kyiv', - 'Etc/UTC': 'UTC', - 'US/Arizona': 'America/Phoenix', - 'US/Central': 'America/Chicago', - 'US/Eastern': 'America/New_York', - 'US/Mountain': 'America/Denver', - 'US/Pacific': 'America/Los_Angeles', - 'US/Samoa': 'Pacific/Pago_Pago', -}; From 839bf3898fa1f7bcc607529262788f294c273c7a Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Mon, 10 Nov 2025 17:27:45 -0800 Subject: [PATCH 33/37] add canonicalizeTimezone conversions Co-authored-by: Om Mishra --- src/components/hooks/useDateParameters.ts | 4 ++-- src/components/hooks/useTimezone.ts | 15 +++++++++++++-- src/lib/constants.ts | 21 +++++++++++++++++++++ 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/components/hooks/useDateParameters.ts b/src/components/hooks/useDateParameters.ts index 359bbc1fd..16e123142 100644 --- a/src/components/hooks/useDateParameters.ts +++ b/src/components/hooks/useDateParameters.ts @@ -5,7 +5,7 @@ export function useDateParameters() { const { dateRange: { startDate, endDate, unit }, } = useDateRange(); - const { timezone, toUtc } = useTimezone(); + const { timezone, toUtc, canonicalizeTimezone } = useTimezone(); return { startAt: +toUtc(startDate), @@ -13,6 +13,6 @@ export function useDateParameters() { startDate: toUtc(startDate).toISOString(), endDate: toUtc(endDate).toISOString(), unit, - timezone, + timezone: canonicalizeTimezone(timezone), }; } diff --git a/src/components/hooks/useTimezone.ts b/src/components/hooks/useTimezone.ts index 0e1fe6cd3..3770c26bd 100644 --- a/src/components/hooks/useTimezone.ts +++ b/src/components/hooks/useTimezone.ts @@ -1,5 +1,5 @@ import { setItem } from '@/lib/storage'; -import { TIMEZONE_CONFIG } from '@/lib/constants'; +import { TIMEZONE_CONFIG, TIMEZONE_LEGACY } from '@/lib/constants'; import { formatInTimeZone, zonedTimeToUtc, utcToZonedTime } from 'date-fns-tz'; import { useApp, setTimezone } from '@/store/app'; import { useLocale } from './useLocale'; @@ -34,5 +34,16 @@ export function useTimezone() { return utcToZonedTime(date, timezone); }; - return { timezone, saveTimezone, formatTimezoneDate, toUtc, fromUtc }; + const canonicalizeTimezone = (timezone: string): string => { + return TIMEZONE_LEGACY[timezone] ?? timezone; + }; + + return { + timezone, + saveTimezone, + formatTimezoneDate, + toUtc, + fromUtc, + canonicalizeTimezone, + }; } diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 50a25b8d8..195fe1be8 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -658,3 +658,24 @@ export const CURRENCIES = [ { id: 'OMR', name: 'Omani Rial' }, { id: 'GHS', name: 'Ghanaian Cedi' }, ]; + +export const TIMEZONE_LEGACY: Record = { + 'Asia/Batavia': 'Asia/Jakarta', + 'Asia/Calcutta': 'Asia/Kolkata', + 'Asia/Chongqing': 'Asia/Shanghai', + 'Asia/Harbin': 'Asia/Shanghai', + 'Asia/Jayapura': 'Asia/Pontianak', + 'Asia/Katmandu': 'Asia/Kathmandu', + 'Asia/Macao': 'Asia/Macau', + 'Asia/Rangoon': 'Asia/Yangon', + 'Asia/Saigon': 'Asia/Ho_Chi_Minh', + 'Europe/Kiev': 'Europe/Kyiv', + 'Europe/Zaporozhye': 'Europe/Kyiv', + 'Etc/UTC': 'UTC', + 'US/Arizona': 'America/Phoenix', + 'US/Central': 'America/Chicago', + 'US/Eastern': 'America/New_York', + 'US/Mountain': 'America/Denver', + 'US/Pacific': 'America/Los_Angeles', + 'US/Samoa': 'Pacific/Pago_Pago', +}; From 592f7c0ae728d21dbd1c3acd8cc85e87ffdb14f8 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Mon, 10 Nov 2025 21:08:55 -0800 Subject: [PATCH 34/37] Added check for REDIS_URL. Closes #3677. --- docker-compose.yml | 1 - scripts/check-db.js | 4 ++++ src/tracker/index.js | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 8c8a47a6e..348c294ca 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,7 +6,6 @@ services: - "3000:3000" environment: DATABASE_URL: postgresql://umami:umami@db:5432/umami - DATABASE_TYPE: postgresql APP_SECRET: replace-me-with-a-random-string depends_on: db: diff --git a/scripts/check-db.js b/scripts/check-db.js index 7c7daaa11..09577699d 100644 --- a/scripts/check-db.js +++ b/scripts/check-db.js @@ -36,6 +36,10 @@ async function checkEnv() { } else { success('DATABASE_URL is defined.'); } + + if (process.env.REDIS_URL) { + success('REDIS_URL is defined.'); + } } async function checkConnection() { diff --git a/src/tracker/index.js b/src/tracker/index.js index c4c420b6c..18b3ff89f 100644 --- a/src/tracker/index.js +++ b/src/tracker/index.js @@ -45,7 +45,7 @@ if (excludeSearch) u.search = ''; if (excludeHash) u.hash = ''; return u.toString(); - } catch (e) { + } catch { return raw; } }; From 14f3db550bb6705b1c5e4b92409ea0d5eb2ff3a4 Mon Sep 17 00:00:00 2001 From: Maxime-J Date: Tue, 11 Nov 2025 10:32:31 +0100 Subject: [PATCH 35/37] Use raw query with on conflict in createSession --- src/app/api/send/route.ts | 1 + src/queries/sql/sessions/createSession.ts | 75 ++++++++++++----------- 2 files changed, 40 insertions(+), 36 deletions(-) diff --git a/src/app/api/send/route.ts b/src/app/api/send/route.ts index f5e00c8aa..2c2085bfb 100644 --- a/src/app/api/send/route.ts +++ b/src/app/api/send/route.ts @@ -146,6 +146,7 @@ export async function POST(request: Request) { region, city, distinctId: id, + createdAt, }); } diff --git a/src/queries/sql/sessions/createSession.ts b/src/queries/sql/sessions/createSession.ts index 958754f1a..b5106a541 100644 --- a/src/queries/sql/sessions/createSession.ts +++ b/src/queries/sql/sessions/createSession.ts @@ -1,41 +1,44 @@ import { Prisma } from '@/generated/prisma/client'; import prisma from '@/lib/prisma'; -export async function createSession(data: Prisma.SessionCreateInput) { - const { - id, - websiteId, - browser, - os, - device, - screen, - language, - country, - region, - city, - distinctId, - } = data; +const FUNCTION_NAME = 'createSession'; - try { - return await prisma.client.session.create({ - data: { - id, - websiteId, - browser, - os, - device, - screen, - language, - country, - region, - city, - distinctId, - }, - }); - } catch (e: any) { - if (e.message.toLowerCase().includes('unique constraint')) { - return null; - } - throw e; - } +export async function createSession(data: Prisma.SessionCreateInput) { + const { rawQuery } = prisma; + + await rawQuery( + ` + insert into session ( + session_id, + website_id, + browser, + os, + device, + screen, + language, + country, + region, + city, + distinct_id, + created_at + ) + values ( + {{id}}, + {{websiteId}}, + {{browser}}, + {{os}}, + {{device}}, + {{screen}}, + {{language}}, + {{country}}, + {{region}}, + {{city}}, + {{distinctId}}, + {{createdAt}} + ) + on conflict (session_id) do nothing + `, + data, + FUNCTION_NAME, + ); } From 30781430c5ef51c6d4d79b620ffa4f34293bf9d4 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Tue, 11 Nov 2025 13:13:25 -0800 Subject: [PATCH 36/37] remove timezone from realtime. Closes #3700 --- src/app/api/realtime/[websiteId]/route.ts | 8 +------- src/components/hooks/queries/useRealtimeQuery.ts | 6 ++---- src/lib/clickhouse.ts | 2 +- src/queries/sql/pageviews/getPageviewStats.ts | 2 +- src/queries/sql/sessions/getSessionStats.ts | 2 +- 5 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/app/api/realtime/[websiteId]/route.ts b/src/app/api/realtime/[websiteId]/route.ts index 054a12418..eaa0bbd8c 100644 --- a/src/app/api/realtime/[websiteId]/route.ts +++ b/src/app/api/realtime/[websiteId]/route.ts @@ -1,21 +1,15 @@ import { REALTIME_RANGE } from '@/lib/constants'; import { getQueryFilters, parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; -import { timezoneParam } from '@/lib/schema'; import { canViewWebsite } from '@/permissions'; import { getRealtimeData } from '@/queries/sql'; import { startOfMinute, subMinutes } from 'date-fns'; -import z from 'zod'; export async function GET( request: Request, { params }: { params: Promise<{ websiteId: string }> }, ) { - const schema = z.object({ - timezone: timezoneParam, - }); - - const { auth, query, error } = await parseRequest(request, schema); + const { auth, query, error } = await parseRequest(request); if (error) { return error(); diff --git a/src/components/hooks/queries/useRealtimeQuery.ts b/src/components/hooks/queries/useRealtimeQuery.ts index 582fe9fa3..ed388b032 100644 --- a/src/components/hooks/queries/useRealtimeQuery.ts +++ b/src/components/hooks/queries/useRealtimeQuery.ts @@ -1,4 +1,3 @@ -import { useTimezone } from '@/components/hooks/useTimezone'; import { REALTIME_INTERVAL } from '@/lib/constants'; import { useApi } from '../useApi'; @@ -24,11 +23,10 @@ export interface RealtimeData { export function useRealtimeQuery(websiteId: string) { const { get, useQuery } = useApi(); - const { timezone } = useTimezone(); const { data, isLoading, error } = useQuery({ - queryKey: ['realtime', { websiteId, timezone }], + queryKey: ['realtime', { websiteId }], queryFn: async () => { - return get(`/realtime/${websiteId}`, { timezone }); + return get(`/realtime/${websiteId}`); }, enabled: !!websiteId, refetchInterval: REALTIME_INTERVAL, diff --git a/src/lib/clickhouse.ts b/src/lib/clickhouse.ts index 5e6e7133c..4880e122e 100644 --- a/src/lib/clickhouse.ts +++ b/src/lib/clickhouse.ts @@ -61,7 +61,7 @@ function getDateStringSQL(data: any, unit: string = 'utc', timezone?: string) { function getDateSQL(field: string, unit: string, timezone?: string) { if (timezone) { - return `toDateTime(date_trunc('${unit}', ${field}, '${timezone}'), '${timezone}')`; + return `toDateTime(date_trunc('${unit}', ${field}, '${timezone}'))`; } return `toDateTime(date_trunc('${unit}', ${field}))`; } diff --git a/src/queries/sql/pageviews/getPageviewStats.ts b/src/queries/sql/pageviews/getPageviewStats.ts index 7dd9ac93e..a6619e87d 100644 --- a/src/queries/sql/pageviews/getPageviewStats.ts +++ b/src/queries/sql/pageviews/getPageviewStats.ts @@ -45,7 +45,7 @@ async function clickhouseQuery( websiteId: string, filters: QueryFilters, ): Promise<{ x: string; y: number }[]> { - const { timezone = 'utc', unit = 'day' } = filters; + const { timezone = 'UTC', unit = 'day' } = filters; const { parseFilters, rawQuery, getDateSQL } = clickhouse; const { filterQuery, cohortQuery, queryParams } = parseFilters({ ...filters, diff --git a/src/queries/sql/sessions/getSessionStats.ts b/src/queries/sql/sessions/getSessionStats.ts index 07582d395..ea93b2263 100644 --- a/src/queries/sql/sessions/getSessionStats.ts +++ b/src/queries/sql/sessions/getSessionStats.ts @@ -45,7 +45,7 @@ async function clickhouseQuery( websiteId: string, filters: QueryFilters, ): Promise<{ x: string; y: number }[]> { - const { timezone = 'utc', unit = 'day' } = filters; + const { timezone = 'UTC', unit = 'day' } = filters; const { parseFilters, rawQuery, getDateSQL } = clickhouse; const { filterQuery, cohortQuery, queryParams } = parseFilters({ ...filters, From bf498d92392571fbf025011c5d30627c5d30ff42 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Tue, 11 Nov 2025 13:45:41 -0800 Subject: [PATCH 37/37] add RealtimeData to types --- .../hooks/queries/useRealtimeQuery.ts | 21 +------------------ src/lib/types.ts | 20 ++++++++++++++++++ 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/components/hooks/queries/useRealtimeQuery.ts b/src/components/hooks/queries/useRealtimeQuery.ts index ed388b032..9e20da049 100644 --- a/src/components/hooks/queries/useRealtimeQuery.ts +++ b/src/components/hooks/queries/useRealtimeQuery.ts @@ -1,25 +1,6 @@ import { REALTIME_INTERVAL } from '@/lib/constants'; import { useApi } from '../useApi'; - -export interface RealtimeData { - countries: Record; - events: any[]; - pageviews: any[]; - referrers: Record; - timestamp: number; - series: { - views: any[]; - visitors: any[]; - }; - totals: { - views: number; - visitors: number; - events: number; - countries: number; - }; - urls: Record; - visitors: any[]; -} +import { RealtimeData } from '@/lib/types'; export function useRealtimeQuery(websiteId: string) { const { get, useQuery } = useApi(); diff --git a/src/lib/types.ts b/src/lib/types.ts index 1237f5199..e5d4ecc5f 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -116,3 +116,23 @@ export interface PageResult { sortDescending?: boolean; search?: string; } + +export interface RealtimeData { + countries: Record; + events: any[]; + pageviews: any[]; + referrers: Record; + timestamp: number; + series: { + views: any[]; + visitors: any[]; + }; + totals: { + views: number; + visitors: number; + events: number; + countries: number; + }; + urls: Record; + visitors: any[]; +}