This commit is contained in:
Mike Cao
2026-06-09 23:07:24 -07:00
35 changed files with 308 additions and 145 deletions
-61
View File
@@ -331,8 +331,6 @@ importers:
specifier: ^4.1.6
version: 4.1.6(@types/node@25.9.0)(jsdom@29.1.1)(msw@2.14.6(@types/node@25.9.0)(typescript@6.0.3))(vite@8.0.11(@types/node@25.9.0)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.46.1)(tsx@4.22.2))
dist: {}
packages:
'@adobe/css-tools@4.4.4':
@@ -624,28 +622,24 @@ packages:
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@biomejs/cli-linux-arm64@2.4.15':
resolution: {integrity: sha512-owaAMZD/T4LrD0ELNCk0Km3qrRHuM0X6EAyVE1FSqGY0rbLoiDLrO4Us2tllm6cAeB2Ioa9C2C08NZPdr8+0Ug==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@biomejs/cli-linux-x64-musl@2.4.15':
resolution: {integrity: sha512-CNq/9W38SYSH023lfcQ4KKU8K0YX8T//FZUhcgtMMRABDojx5XsMV7jlweAvGSl389wJQB29Qo6Zb/a+jdvt+w==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [linux]
libc: [musl]
'@biomejs/cli-linux-x64@2.4.15':
resolution: {integrity: sha512-0jj7THz12GbUOLmMibktK6DZjqz2zV64KFxyBtcFTKPiiOIY0a7vns1elpO1dERvxpsZ5ik0oFfz0oGwFde1+g==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@biomejs/cli-win32-arm64@2.4.15':
resolution: {integrity: sha512-ouhkYdlhp/1GghEJPdWwD/Vi3gQ1nFxuSpMolWsbq3Lsq3QUR4jl6UdhhscdCugKU5vOEuMiJhvKj66O0OCq+w==}
@@ -1626,105 +1620,89 @@ packages:
resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-arm@1.2.4':
resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==}
cpu: [arm]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-ppc64@1.2.4':
resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-riscv64@1.2.4':
resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-s390x@1.2.4':
resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-x64@1.2.4':
resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linuxmusl-arm64@1.2.4':
resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@img/sharp-libvips-linuxmusl-x64@1.2.4':
resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==}
cpu: [x64]
os: [linux]
libc: [musl]
'@img/sharp-linux-arm64@0.34.5':
resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-arm@0.34.5':
resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm]
os: [linux]
libc: [glibc]
'@img/sharp-linux-ppc64@0.34.5':
resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-riscv64@0.34.5':
resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-s390x@0.34.5':
resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@img/sharp-linux-x64@0.34.5':
resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
libc: [glibc]
'@img/sharp-linuxmusl-arm64@0.34.5':
resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
libc: [musl]
'@img/sharp-linuxmusl-x64@0.34.5':
resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
libc: [musl]
'@img/sharp-wasm32@0.34.5':
resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==}
@@ -1866,28 +1844,24 @@ packages:
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@next/swc-linux-arm64-musl@16.2.6':
resolution: {integrity: sha512-URUTu1+dMkxJsPFgm+OeEvq9wf5sujw0EvgYy80TDGHTSLTnIHeqb0Eu8A3sC95IRgjejQL+kC4mw+4yPxiAXA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@next/swc-linux-x64-gnu@16.2.6':
resolution: {integrity: sha512-DOj182mPV8G3UkrayLoREM5YEYI+Dk5wv7Ox9xl1fFibAELEsFD0lDPfHIeILlutMMfdyhlzYPELG3peuKaurw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@next/swc-linux-x64-musl@16.2.6':
resolution: {integrity: sha512-HKQ5SP/V/ub73UvF7n/zeJlxk2kLmtL7Wzrg4WfmkjmNos5onJ2tKu7yZOPdL18A6Svfn3max29ym+ry7NkK4g==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [musl]
'@next/swc-win32-arm64-msvc@16.2.6':
resolution: {integrity: sha512-LZXpTlPyS5v7HhSmnvsLGP3iIYgYOBnc8r8ArlT55sGHV89bR2HlDdBjWQ+PY6SJMmk8TuVGFuxalnP3k/0Dwg==}
@@ -1960,42 +1934,36 @@ packages:
engines: {node: '>= 10.0.0'}
cpu: [arm]
os: [linux]
libc: [glibc]
'@parcel/watcher-linux-arm-musl@2.5.6':
resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==}
engines: {node: '>= 10.0.0'}
cpu: [arm]
os: [linux]
libc: [musl]
'@parcel/watcher-linux-arm64-glibc@2.5.6':
resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@parcel/watcher-linux-arm64-musl@2.5.6':
resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@parcel/watcher-linux-x64-glibc@2.5.6':
resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@parcel/watcher-linux-x64-musl@2.5.6':
resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [linux]
libc: [musl]
'@parcel/watcher-win32-arm64@2.5.6':
resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==}
@@ -2310,42 +2278,36 @@ packages:
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-arm64-musl@1.0.0-rc.18':
resolution: {integrity: sha512-QWjdxN1HJCpBTAcZ5N5F7wju3gVPzRzSpmGzx7na0c/1qpN9CFil+xt+l9lV/1M6/gqHSNXCiqPfwhVJPeLnug==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [musl]
'@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.18':
resolution: {integrity: sha512-ugCOyj7a4d9h3q9B+wXmf6g3a68UsjGh6dob5DHevHGMwDUbhsYNbSPxJsENcIttJZ9jv7qGM2UesLw5jqIhdg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-s390x-gnu@1.0.0-rc.18':
resolution: {integrity: sha512-kKWRhbsotpXkGbcd5dllUWg5gEXcDAa8u5YnP9AV5DYNbvJHGzzuwv7dpmhc8NqKMJldl0a+x76IHbspEpEmdA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-x64-gnu@1.0.0-rc.18':
resolution: {integrity: sha512-uCo8ElcCIAMyYAZyuIZ81oFkhTSIllNvUCHCAlbhlN4ji3uC28h7IIdlXyIvGO7HsuqnV9p3rD/bpH7XhIyhRw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-x64-musl@1.0.0-rc.18':
resolution: {integrity: sha512-XNOQZtuE6yUIvx4rwGemwh8kpL1xvU41FXy/s9K7T/3JVcqGzo3NfKM2HrbrGgfPYGFW42f07Wk++aOC6B9NWA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [musl]
'@rolldown/binding-openharmony-arm64@1.0.0-rc.18':
resolution: {integrity: sha512-tSn/kzrfa7tNOXr7sEacDBN4YsIqTyLqh45IO0nHDwtpKIDNDJr+VFojt+4klSpChxB29JLyduSsE0MKEwa65A==}
@@ -2492,79 +2454,66 @@ packages:
resolution: {integrity: sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==}
cpu: [arm]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.60.4':
resolution: {integrity: sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==}
cpu: [arm]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.60.4':
resolution: {integrity: sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.60.4':
resolution: {integrity: sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-loong64-gnu@4.60.4':
resolution: {integrity: sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==}
cpu: [loong64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-loong64-musl@4.60.4':
resolution: {integrity: sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==}
cpu: [loong64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-ppc64-gnu@4.60.4':
resolution: {integrity: sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-ppc64-musl@4.60.4':
resolution: {integrity: sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==}
cpu: [ppc64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-riscv64-gnu@4.60.4':
resolution: {integrity: sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-musl@4.60.4':
resolution: {integrity: sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==}
cpu: [riscv64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-s390x-gnu@4.60.4':
resolution: {integrity: sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.60.4':
resolution: {integrity: sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.60.4':
resolution: {integrity: sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==}
cpu: [x64]
os: [linux]
libc: [musl]
'@rollup/rollup-openbsd-x64@4.60.4':
resolution: {integrity: sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==}
@@ -2753,42 +2702,36 @@ packages:
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@swc/core-linux-arm64-musl@1.15.33':
resolution: {integrity: sha512-il7tYM+CpUNzieQbwAjFT1P8zqAhmGWNAGhQZBnxurXZ0aNn+5nqYFTEUKNZl7QibtT0uQXzTZrNGHCIj6Y1Og==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@swc/core-linux-ppc64-gnu@1.15.33':
resolution: {integrity: sha512-ZtNBwN0Z7CFj9Il0FcPaKdjgP7URyKu/3RfH46vq+0paOBqLj4NYldD6Qo//Duif/7IOtAraUfDOmp0PLAufog==}
engines: {node: '>=10'}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@swc/core-linux-s390x-gnu@1.15.33':
resolution: {integrity: sha512-De1IyajoOmhOYYjw/lx66bKlyDpHZTueqwpDrWgf5O7T6d1ODeJJO9/OqMBmrBQc5C+dNnlmIufHsp4QVCWufA==}
engines: {node: '>=10'}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@swc/core-linux-x64-gnu@1.15.33':
resolution: {integrity: sha512-mGTH0YxmUN+x6vRN/I6NOk5X0ogNktkwPnJ94IMvR7QjhRDwL0O8RXEDhyUM0YtwWrryBOqaJQBX4zruxEPRGw==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@swc/core-linux-x64-musl@1.15.33':
resolution: {integrity: sha512-hj628ZkSEJf6zMf5VMbYrG2O6QqyTIp2qwY6VlCjvIa9lAEZ5c2lfPblCLVGYubTeLJDxadLB/CxqQYOQABeEQ==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
libc: [musl]
'@swc/core-win32-arm64-msvc@1.15.33':
resolution: {integrity: sha512-GV2oohtN2/5+KSccl86VULu3aT+LrISC8uzgSq0FRnikpD+Zwc+sBlXmoKQ+Db6jI57ITUOIB8jRkdGMABC29g==}
@@ -4327,28 +4270,24 @@ packages:
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
libc: [glibc]
lightningcss-linux-arm64-musl@1.32.0:
resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
libc: [musl]
lightningcss-linux-x64-gnu@1.32.0:
resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
libc: [glibc]
lightningcss-linux-x64-musl@1.32.0:
resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
libc: [musl]
lightningcss-win32-arm64-msvc@1.32.0:
resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
+32 -2
View File
@@ -3,9 +3,34 @@ import { uuid } from '@/lib/crypto';
import { parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response';
import { pagingParams, reportSchema, reportTypeParam } from '@/lib/schema';
import { canUpdateWebsite, canViewAuthenticatedWebsite } from '@/permissions';
import {
canUpdateWebsite,
canViewAuthenticatedWebsite,
canViewWebsiteSection,
} from '@/permissions';
import type { ShareSection } from '@/permissions';
import { createReport, getReports } from '@/queries/prisma';
function getReportSection(type?: z.infer<typeof reportTypeParam>): ShareSection | null {
switch (type) {
case 'attribution':
case 'breakdown':
case 'performance':
case 'retention':
case 'revenue':
case 'utm':
return type;
case 'funnel':
return 'funnels';
case 'goal':
return 'goals';
case 'journey':
return 'journeys';
default:
return null;
}
}
export async function GET(request: Request) {
const schema = z.object({
websiteId: z.uuid(),
@@ -26,7 +51,12 @@ export async function GET(request: Request) {
search,
};
if (!(await canViewAuthenticatedWebsite(auth, websiteId))) {
const section = getReportSection(type);
const canView = section
? await canViewWebsiteSection(auth, websiteId, section)
: await canViewAuthenticatedWebsite(auth, websiteId);
if (!canView) {
return unauthorized();
}
@@ -3,7 +3,7 @@ import { parseEventPropertyFilters } from '@/lib/params';
import { getQueryFilters, parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response';
import { filterParams, timezoneParam, unitParam } from '@/lib/schema';
import { canViewAuthenticatedWebsite } from '@/permissions';
import { canViewWebsiteSection } from '@/permissions';
import { getEventDataArraySeries } from '@/queries/sql/events/getEventDataArraySeries';
export async function GET(
@@ -28,7 +28,7 @@ export async function GET(
const { websiteId } = await params;
if (!(await canViewAuthenticatedWebsite(auth, websiteId))) {
if (!(await canViewWebsiteSection(auth, websiteId, 'events'))) {
return unauthorized();
}
@@ -3,7 +3,7 @@ import { parseEventPropertyFilters } from '@/lib/params';
import { getQueryFilters, parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response';
import { filterParams, timezoneParam } from '@/lib/schema';
import { canViewAuthenticatedWebsite } from '@/permissions';
import { canViewWebsiteSection } from '@/permissions';
import { getEventDataDateSeries } from '@/queries/sql/events/getEventDataDateSeries';
export async function GET(
@@ -27,7 +27,7 @@ export async function GET(
const { websiteId } = await params;
if (!(await canViewAuthenticatedWebsite(auth, websiteId))) {
if (!(await canViewWebsiteSection(auth, websiteId, 'events'))) {
return unauthorized();
}
@@ -3,7 +3,7 @@ import { parseEventPropertyFilters } from '@/lib/params';
import { getQueryFilters, parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response';
import { filterParams, timezoneParam, unitParam } from '@/lib/schema';
import { canViewAuthenticatedWebsite } from '@/permissions';
import { canViewWebsiteSection } from '@/permissions';
import { getEventDataNumericSeries } from '@/queries/sql/events/getEventDataNumericSeries';
export async function GET(
@@ -29,7 +29,7 @@ export async function GET(
const { websiteId } = await params;
if (!(await canViewAuthenticatedWebsite(auth, websiteId))) {
if (!(await canViewWebsiteSection(auth, websiteId, 'events'))) {
return unauthorized();
}
@@ -3,7 +3,7 @@ import { parseEventPropertyFilters } from '@/lib/params';
import { getQueryFilters, parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response';
import { filterParams } from '@/lib/schema';
import { canViewAuthenticatedWebsite } from '@/permissions';
import { canViewWebsiteSection } from '@/permissions';
import { getEventDataNumericStats } from '@/queries/sql/events/getEventDataNumericStats';
export async function GET(
@@ -26,7 +26,7 @@ export async function GET(
const { websiteId } = await params;
if (!(await canViewAuthenticatedWebsite(auth, websiteId))) {
if (!(await canViewWebsiteSection(auth, websiteId, 'events'))) {
return unauthorized();
}
@@ -3,7 +3,7 @@ import { parseEventPropertyFilters } from '@/lib/params';
import { getQueryFilters, parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response';
import { filterParams, timezoneParam, unitParam } from '@/lib/schema';
import { canViewAuthenticatedWebsite } from '@/permissions';
import { canViewWebsiteSection } from '@/permissions';
import { getEventDataPropertySeries } from '@/queries/sql/events/getEventDataPropertySeries';
export async function GET(
@@ -28,7 +28,7 @@ export async function GET(
const { websiteId } = await params;
if (!(await canViewAuthenticatedWebsite(auth, websiteId))) {
if (!(await canViewWebsiteSection(auth, websiteId, 'events'))) {
return unauthorized();
}
@@ -3,7 +3,7 @@ import { parseEventPropertyFilters } from '@/lib/params';
import { getQueryFilters, parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response';
import { filterParams, pagingParams, timezoneParam, unitParam } from '@/lib/schema';
import { canViewAuthenticatedWebsite } from '@/permissions';
import { canViewWebsiteSection } from '@/permissions';
import { getEventDataPivot } from '@/queries/sql/events/getEventDataPivot';
export async function GET(
@@ -28,7 +28,7 @@ export async function GET(
const { websiteId } = await params;
if (!(await canViewAuthenticatedWebsite(auth, websiteId))) {
if (!(await canViewWebsiteSection(auth, websiteId, 'events'))) {
return unauthorized();
}
@@ -1,6 +1,6 @@
import { parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response';
import { canViewAuthenticatedWebsite } from '@/permissions';
import { canViewWebsiteSection } from '@/permissions';
import { getEventDataById } from '@/queries/sql/events/getEventDataById';
export async function GET(
@@ -15,7 +15,7 @@ export async function GET(
const { websiteId, eventId } = await params;
if (!(await canViewAuthenticatedWebsite(auth, websiteId))) {
if (!(await canViewWebsiteSection(auth, websiteId, 'events'))) {
return unauthorized();
}
@@ -2,7 +2,7 @@ import { z } from 'zod';
import { getQueryFilters, parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response';
import { filterParams } from '@/lib/schema';
import { canViewAuthenticatedWebsite } from '@/permissions';
import { canViewWebsiteSection } from '@/permissions';
import { getEventDataEvents } from '@/queries/sql/events/getEventDataEvents';
export async function GET(
@@ -23,7 +23,7 @@ export async function GET(
const { websiteId } = await params;
if (!(await canViewAuthenticatedWebsite(auth, websiteId))) {
if (!(await canViewWebsiteSection(auth, websiteId, 'events'))) {
return unauthorized();
}
@@ -2,7 +2,7 @@ import { z } from 'zod';
import { getQueryFilters, parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response';
import { filterParams } from '@/lib/schema';
import { canViewAuthenticatedWebsite } from '@/permissions';
import { canViewWebsiteSection } from '@/permissions';
import { getEventDataFields } from '@/queries/sql';
export async function GET(
@@ -23,7 +23,7 @@ export async function GET(
const { websiteId } = await params;
if (!(await canViewAuthenticatedWebsite(auth, websiteId))) {
if (!(await canViewWebsiteSection(auth, websiteId, 'events'))) {
return unauthorized();
}
@@ -2,7 +2,7 @@ import { z } from 'zod';
import { getQueryFilters, parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response';
import { filterParams } from '@/lib/schema';
import { canViewAuthenticatedWebsite } from '@/permissions';
import { canViewWebsiteSection } from '@/permissions';
import { getEventDataProperties } from '@/queries/sql';
export async function GET(
@@ -23,7 +23,7 @@ export async function GET(
const { websiteId } = await params;
if (!(await canViewAuthenticatedWebsite(auth, websiteId))) {
if (!(await canViewWebsiteSection(auth, websiteId, 'events'))) {
return unauthorized();
}
@@ -2,7 +2,7 @@ import { z } from 'zod';
import { getQueryFilters, parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response';
import { filterParams, pagingParams } from '@/lib/schema';
import { canViewAuthenticatedWebsite } from '@/permissions';
import { canViewWebsiteSection } from '@/permissions';
import { getEventData } from '@/queries/sql/events/getEventData';
export async function GET(
@@ -24,7 +24,7 @@ export async function GET(
const { websiteId } = await params;
if (!(await canViewAuthenticatedWebsite(auth, websiteId))) {
if (!(await canViewWebsiteSection(auth, websiteId, 'events'))) {
return unauthorized();
}
@@ -2,7 +2,7 @@ import { z } from 'zod';
import { getQueryFilters, parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response';
import { filterParams } from '@/lib/schema';
import { canViewAuthenticatedWebsite } from '@/permissions';
import { canViewWebsiteSection } from '@/permissions';
import { getEventDataStats } from '@/queries/sql';
export async function GET(
@@ -23,7 +23,7 @@ export async function GET(
const { websiteId } = await params;
if (!(await canViewAuthenticatedWebsite(auth, websiteId))) {
if (!(await canViewWebsiteSection(auth, websiteId, 'events'))) {
return unauthorized();
}
@@ -2,7 +2,7 @@ import { z } from 'zod';
import { getQueryFilters, parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response';
import { filterParams } from '@/lib/schema';
import { canViewAuthenticatedWebsite } from '@/permissions';
import { canViewWebsiteSection } from '@/permissions';
import { getEventDataValues } from '@/queries/sql';
export async function GET(
@@ -26,7 +26,7 @@ export async function GET(
const { websiteId } = await params;
if (!(await canViewAuthenticatedWebsite(auth, websiteId))) {
if (!(await canViewWebsiteSection(auth, websiteId, 'events'))) {
return unauthorized();
}
@@ -2,7 +2,7 @@ import { z } from 'zod';
import { parseRequest } from '@/lib/request';
import { json, notFound, ok, unauthorized } from '@/lib/response';
import { anyObjectParam, segmentTypeParam } from '@/lib/schema';
import { canDeleteWebsite, canUpdateWebsite, canViewAuthenticatedWebsite } from '@/permissions';
import { canDeleteWebsite, canUpdateWebsite, canViewSharedWebsiteFilters } from '@/permissions';
import { deleteSegment, getWebsiteSegment, updateSegment } from '@/queries/prisma';
export async function GET(
@@ -17,7 +17,7 @@ export async function GET(
const { websiteId, segmentId } = await params;
if (!(await canViewAuthenticatedWebsite(auth, websiteId))) {
if (!(await canViewSharedWebsiteFilters(auth, websiteId))) {
return unauthorized();
}
@@ -3,7 +3,7 @@ import { uuid } from '@/lib/crypto';
import { getQueryFilters, parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response';
import { searchParams, segmentParamSchema, segmentTypeParam } from '@/lib/schema';
import { canUpdateWebsite, canViewAuthenticatedWebsite } from '@/permissions';
import { canUpdateWebsite, canViewSharedWebsiteFilters } from '@/permissions';
import { createSegment, getWebsiteSegments } from '@/queries/prisma';
export async function GET(
@@ -24,7 +24,7 @@ export async function GET(
const { websiteId } = await params;
const { type } = query;
if (websiteId && !(await canViewAuthenticatedWebsite(auth, websiteId))) {
if (websiteId && !(await canViewSharedWebsiteFilters(auth, websiteId))) {
return unauthorized();
}
@@ -3,7 +3,7 @@ import { parsePropertyFilters } from '@/lib/params';
import { getQueryFilters, parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response';
import { filterParams, pagingParams, timezoneParam, unitParam } from '@/lib/schema';
import { canViewAuthenticatedWebsite } from '@/permissions';
import { canViewWebsiteSection } from '@/permissions';
import { getSessionDataPivot } from '@/queries/sql/sessions/getSessionDataPivot';
export async function GET(
@@ -28,7 +28,7 @@ export async function GET(
const { websiteId } = await params;
if (!(await canViewAuthenticatedWebsite(auth, websiteId))) {
if (!(await canViewWebsiteSection(auth, websiteId, 'sessions'))) {
return unauthorized();
}
@@ -3,7 +3,7 @@ import { parsePropertyFilters } from '@/lib/params';
import { getQueryFilters, parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response';
import { filterParams, timezoneParam, unitParam } from '@/lib/schema';
import { canViewAuthenticatedWebsite } from '@/permissions';
import { canViewWebsiteSection } from '@/permissions';
import { getSessionDataArraySeries } from '@/queries/sql';
export async function GET(
@@ -27,7 +27,7 @@ export async function GET(
const { websiteId } = await params;
if (!(await canViewAuthenticatedWebsite(auth, websiteId))) {
if (!(await canViewWebsiteSection(auth, websiteId, 'sessions'))) {
return unauthorized();
}
@@ -3,7 +3,7 @@ import { parsePropertyFilters } from '@/lib/params';
import { getQueryFilters, parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response';
import { filterParams, timezoneParam } from '@/lib/schema';
import { canViewAuthenticatedWebsite } from '@/permissions';
import { canViewWebsiteSection } from '@/permissions';
import { getSessionDataDateSeries } from '@/queries/sql';
export async function GET(
@@ -26,7 +26,7 @@ export async function GET(
const { websiteId } = await params;
if (!(await canViewAuthenticatedWebsite(auth, websiteId))) {
if (!(await canViewWebsiteSection(auth, websiteId, 'sessions'))) {
return unauthorized();
}
@@ -3,7 +3,7 @@ import { parsePropertyFilters } from '@/lib/params';
import { getQueryFilters, parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response';
import { filterParams, timezoneParam, unitParam } from '@/lib/schema';
import { canViewAuthenticatedWebsite } from '@/permissions';
import { canViewWebsiteSection } from '@/permissions';
import { getSessionDataNumericSeries } from '@/queries/sql';
export async function GET(
@@ -28,7 +28,7 @@ export async function GET(
const { websiteId } = await params;
if (!(await canViewAuthenticatedWebsite(auth, websiteId))) {
if (!(await canViewWebsiteSection(auth, websiteId, 'sessions'))) {
return unauthorized();
}
@@ -3,7 +3,7 @@ import { parsePropertyFilters } from '@/lib/params';
import { getQueryFilters, parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response';
import { filterParams, timezoneParam } from '@/lib/schema';
import { canViewAuthenticatedWebsite } from '@/permissions';
import { canViewWebsiteSection } from '@/permissions';
import { getSessionDataNumericStats } from '@/queries/sql';
export async function GET(
@@ -26,7 +26,7 @@ export async function GET(
const { websiteId } = await params;
if (!(await canViewAuthenticatedWebsite(auth, websiteId))) {
if (!(await canViewWebsiteSection(auth, websiteId, 'sessions'))) {
return unauthorized();
}
@@ -3,7 +3,7 @@ import { parsePropertyFilters } from '@/lib/params';
import { getQueryFilters, parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response';
import { filterParams } from '@/lib/schema';
import { canViewAuthenticatedWebsite } from '@/permissions';
import { canViewWebsiteSection } from '@/permissions';
import { getSessionDataProperties } from '@/queries/sql';
export async function GET(
@@ -25,7 +25,7 @@ export async function GET(
const { websiteId } = await params;
if (!(await canViewAuthenticatedWebsite(auth, websiteId))) {
if (!(await canViewWebsiteSection(auth, websiteId, 'sessions'))) {
return unauthorized();
}
@@ -3,7 +3,7 @@ import { parsePropertyFilters } from '@/lib/params';
import { getQueryFilters, parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response';
import { filterParams, timezoneParam, unitParam } from '@/lib/schema';
import { canViewAuthenticatedWebsite } from '@/permissions';
import { canViewWebsiteSection } from '@/permissions';
import { getSessionDataPropertySeries } from '@/queries/sql';
export async function GET(
@@ -27,7 +27,7 @@ export async function GET(
const { websiteId } = await params;
if (!(await canViewAuthenticatedWebsite(auth, websiteId))) {
if (!(await canViewWebsiteSection(auth, websiteId, 'sessions'))) {
return unauthorized();
}
@@ -3,7 +3,7 @@ import { parsePropertyFilters } from '@/lib/params';
import { getQueryFilters, parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response';
import { filterParams, timezoneParam, unitParam } from '@/lib/schema';
import { canViewAuthenticatedWebsite } from '@/permissions';
import { canViewWebsiteSection } from '@/permissions';
import { getSessionDataActivityStats } from '@/queries/sql/sessions/getSessionDataActivityStats';
export async function GET(
@@ -27,7 +27,7 @@ export async function GET(
const { websiteId } = await params;
if (!(await canViewAuthenticatedWebsite(auth, websiteId))) {
if (!(await canViewWebsiteSection(auth, websiteId, 'sessions'))) {
return unauthorized();
}
@@ -2,7 +2,7 @@ import { z } from 'zod';
import { getQueryFilters, parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response';
import { filterParams } from '@/lib/schema';
import { canViewAuthenticatedWebsite } from '@/permissions';
import { canViewWebsiteSection } from '@/permissions';
import { getSessionDataValues } from '@/queries/sql';
export async function GET(
@@ -25,7 +25,7 @@ export async function GET(
const { websiteId } = await params;
if (!(await canViewAuthenticatedWebsite(auth, websiteId))) {
if (!(await canViewWebsiteSection(auth, websiteId, 'sessions'))) {
return unauthorized();
}
@@ -1,7 +1,7 @@
import { z } from 'zod';
import { getQueryFilters, parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response';
import { canViewAuthenticatedWebsite } from '@/permissions';
import { canViewWebsiteSection } from '@/permissions';
import { getSessionActivity } from '@/queries/sql';
export async function GET(
@@ -21,7 +21,14 @@ export async function GET(
const { websiteId, sessionId } = await params;
if (!(await canViewAuthenticatedWebsite(auth, websiteId))) {
if (
!(await canViewWebsiteSection(auth, websiteId, [
'sessions',
'events',
'realtime',
'revenue',
]))
) {
return unauthorized();
}
@@ -1,6 +1,6 @@
import { parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response';
import { canViewAuthenticatedWebsite } from '@/permissions';
import { canViewWebsiteSection } from '@/permissions';
import { getSessionData } from '@/queries/sql';
export async function GET(
@@ -15,7 +15,14 @@ export async function GET(
const { websiteId, sessionId } = await params;
if (!(await canViewAuthenticatedWebsite(auth, websiteId))) {
if (
!(await canViewWebsiteSection(auth, websiteId, [
'sessions',
'events',
'realtime',
'revenue',
]))
) {
return unauthorized();
}
@@ -1,6 +1,6 @@
import { parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response';
import { canViewAuthenticatedWebsite } from '@/permissions';
import { canViewWebsiteSection } from '@/permissions';
import { getWebsiteSession } from '@/queries/sql';
export async function GET(
@@ -15,7 +15,14 @@ export async function GET(
const { websiteId, sessionId } = await params;
if (!(await canViewAuthenticatedWebsite(auth, websiteId))) {
if (
!(await canViewWebsiteSection(auth, websiteId, [
'sessions',
'events',
'realtime',
'revenue',
]))
) {
return unauthorized();
}
@@ -24,7 +24,7 @@ export async function GET(
const { websiteId } = await params;
if (!(await canViewWebsiteSection(auth, websiteId, 'sessions'))) {
if (!(await canViewWebsiteSection(auth, websiteId, ['overview', 'sessions']))) {
return unauthorized();
}
+13 -2
View File
@@ -5,6 +5,7 @@ import { createContext, type ReactNode, useEffect } from 'react';
import { useShareTokenQuery } from '@/components/hooks';
import { ENTITY_TYPE } from '@/lib/constants';
import type { ShareParameters, WhiteLabel } from '@/lib/types';
import { setShareData, useApp } from '@/store/app';
export interface ShareData {
shareId: string;
@@ -22,6 +23,8 @@ export interface ShareData {
export const ShareContext = createContext<ShareData>(null);
const selector = (state: { shareToken: { token?: string } | null }) => state.shareToken;
const ALL_SECTION_IDS = [
'overview',
'events',
@@ -55,8 +58,10 @@ export function ShareProvider({ slug, children }: { slug: string; children: Reac
const { share, isLoading, isFetching } = useShareTokenQuery(slug);
const router = useRouter();
const pathname = usePathname();
const shareToken = useApp(selector);
const path = getSharePath(pathname);
const isWebsiteShare = share?.shareType === ENTITY_TYPE.website;
const isShareReady = !!share?.token && shareToken?.token === share.token;
const allowedSections =
isWebsiteShare && share?.parameters
@@ -75,11 +80,17 @@ export function ShareProvider({ slug, children }: { slug: string; children: Reac
}
}, [shouldRedirect, slug, allowedSections, router]);
if (isFetching && isLoading) {
useEffect(() => {
return () => {
setShareData(null, null);
};
}, [slug]);
if ((isFetching && isLoading) || (share && !isShareReady)) {
return <Loading placement="absolute" />;
}
if (!share || shouldRedirect) {
if (!share || !isShareReady || shouldRedirect) {
return null;
}
@@ -1,18 +1,24 @@
import { setShareData } from '@/store/app';
import { useEffect } from 'react';
import { setShareData, useApp } from '@/store/app';
import { useApi } from '../useApi';
export function useShareTokenQuery(slug: string) {
const { get, useQuery } = useApi();
const shareId = useApp(state => state.share?.shareId);
const shareToken = useApp(state => state.shareToken?.token);
const query = useQuery({
queryKey: ['share', slug],
queryFn: async () => {
const data = await get(`/share/${slug}`);
setShareData(data, { token: data?.token });
return data;
},
queryFn: async () => get(`/share/${slug}`),
});
useEffect(() => {
if (
query.data?.token &&
(shareId !== query.data.shareId || shareToken !== query.data.token)
) {
setShareData(query.data, { token: query.data.token });
}
}, [query.data, shareId, shareToken]);
return { share: query.data, ...query };
}
+4 -8
View File
@@ -1,5 +1,4 @@
import { useMutation, useQuery } from '@tanstack/react-query';
import { usePathname } from 'next/navigation';
import { useCallback } from 'react';
import { getApiUrl } from '@/lib/api-url';
import { getClientAuthToken } from '@/lib/client';
@@ -7,8 +6,6 @@ import { SHARE_CONTEXT_HEADER, SHARE_TOKEN_HEADER } from '@/lib/constants';
import { type FetchResponse, httpDelete, httpGet, httpPost, httpPut } from '@/lib/fetch';
import { useApp } from '@/store/app';
const selector = (state: { shareToken: { token?: string } }) => state.shareToken;
async function handleResponse(res: FetchResponse): Promise<any> {
if (!res.ok) {
const { message, code, status } = res?.data?.error || {};
@@ -19,13 +16,12 @@ async function handleResponse(res: FetchResponse): Promise<any> {
}
export function useApi() {
const shareToken = useApp(selector);
const pathname = usePathname();
const isSharePath = pathname?.startsWith('/share');
const shareId = useApp(state => state.share?.shareId);
const shareToken = useApp(state => state.shareToken?.token);
const shareHeaders =
isSharePath && shareToken?.token
? { [SHARE_TOKEN_HEADER]: shareToken.token, [SHARE_CONTEXT_HEADER]: '1' }
shareId && shareToken
? { [SHARE_TOKEN_HEADER]: shareToken, [SHARE_CONTEXT_HEADER]: '1' }
: {};
const defaultHeaders = {
+148
View File
@@ -0,0 +1,148 @@
import { expect, test, vi } from 'vitest';
import { ENTITY_TYPE } from '@/lib/constants';
import {
canViewSharedWebsite,
canViewSharedWebsiteFilters,
canViewWebsiteSection,
} from './share';
vi.mock('./website', () => ({
canViewWebsite: vi.fn(),
}));
test('canViewWebsiteSection allows board shares for included websites', async () => {
await expect(
canViewWebsiteSection(
{
shareToken: {
shareType: ENTITY_TYPE.board,
websiteIds: ['website-1'],
parameters: {},
},
},
'website-1',
'goals',
),
).resolves.toBe(true);
});
test('canViewWebsiteSection respects section flags on website shares', async () => {
await expect(
canViewWebsiteSection(
{
shareToken: {
shareType: ENTITY_TYPE.website,
websiteId: 'website-1',
parameters: {
overview: true,
goals: false,
},
},
},
'website-1',
'goals',
),
).resolves.toBe(false);
});
test('canViewWebsiteSection allows any requested enabled section', async () => {
await expect(
canViewWebsiteSection(
{
shareToken: {
shareType: ENTITY_TYPE.website,
websiteId: 'website-1',
parameters: {
overview: true,
compare: false,
},
},
},
'website-1',
['overview', 'compare'],
),
).resolves.toBe(true);
});
test('canViewSharedWebsite allows board shares for included websites', async () => {
await expect(
canViewSharedWebsite(
{
shareToken: {
shareType: ENTITY_TYPE.board,
websiteIds: ['website-1'],
parameters: {},
},
},
'website-1',
),
).resolves.toBe(true);
});
test('canViewSharedWebsiteFilters requires allowFilter for share tokens', async () => {
await expect(
canViewSharedWebsiteFilters(
{
shareToken: {
shareType: ENTITY_TYPE.website,
websiteId: 'website-1',
parameters: {
allowFilter: false,
},
},
},
'website-1',
),
).resolves.toBe(false);
await expect(
canViewSharedWebsiteFilters(
{
shareToken: {
shareType: ENTITY_TYPE.website,
websiteId: 'website-1',
parameters: {
allowFilter: true,
},
},
},
'website-1',
),
).resolves.toBe(true);
});
test('canViewWebsiteSection allows pixel shares for the shared entity id', async () => {
await expect(
canViewWebsiteSection(
{
shareToken: {
shareType: ENTITY_TYPE.pixel,
pixelId: 'pixel-1',
parameters: {
overview: true,
},
},
},
'pixel-1',
'overview',
),
).resolves.toBe(true);
});
test('canViewWebsiteSection allows link shares for the shared entity id', async () => {
await expect(
canViewWebsiteSection(
{
shareToken: {
shareType: ENTITY_TYPE.link,
linkId: 'link-1',
parameters: {
overview: true,
},
},
},
'link-1',
'overview',
),
).resolves.toBe(true);
});
+21 -9
View File
@@ -40,7 +40,14 @@ type ShareSectionInput = ShareSection | ShareSection[];
function shareTokenIncludesWebsite(auth: Auth | null | undefined, websiteId: string) {
const { shareToken } = auth || {};
return shareToken?.websiteId === websiteId || shareToken?.websiteIds?.includes(websiteId);
return (
shareToken?.websiteId === websiteId ||
shareToken?.pixelId === websiteId ||
shareToken?.linkId === websiteId ||
shareToken?.websiteIds?.includes(websiteId) ||
shareToken?.pixelIds?.includes(websiteId) ||
shareToken?.linkIds?.includes(websiteId)
);
}
export async function canViewWebsiteSection(
@@ -54,11 +61,7 @@ export async function canViewWebsiteSection(
const { shareToken } = auth || {};
if (
!shareToken ||
shareToken.shareType !== ENTITY_TYPE.website ||
!shareTokenIncludesWebsite(auth, websiteId)
) {
if (!shareToken || !shareTokenIncludesWebsite(auth, websiteId)) {
return false;
}
@@ -79,11 +82,20 @@ export async function canViewSharedWebsite(auth: Auth | null | undefined, websit
return canViewWebsite(auth, websiteId);
}
return shareTokenIncludesWebsite(auth, websiteId);
}
export async function canViewSharedWebsiteFilters(
auth: Auth | null | undefined,
websiteId: string,
) {
if (auth?.user) {
return canViewWebsite(auth, websiteId);
}
const { shareToken } = auth || {};
return (
shareToken?.shareType === ENTITY_TYPE.website && shareTokenIncludesWebsite(auth, websiteId)
);
return shareTokenIncludesWebsite(auth, websiteId) && shareToken?.parameters?.allowFilter !== false;
}
export async function canViewAuthenticatedWebsite(