diff --git a/drizzle.config.ts b/drizzle.config.ts new file mode 100644 index 000000000..7d524fd0f --- /dev/null +++ b/drizzle.config.ts @@ -0,0 +1,13 @@ +import 'dotenv/config'; +import { defineConfig } from 'drizzle-kit'; + +export default defineConfig({ + dialect: 'postgresql', + out: './src/drizzle', + schema: './src/drizzle/schema.ts', + dbCredentials: { + url: process.env.DATABASE_URL as string, + }, + verbose: true, + strict: true, +}); diff --git a/package.json b/package.json index 70b3f1849..7825e893f 100755 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "cross-env": "^10.0.0", "dayjs": "^1.11.18", "dotenv": "^17.2.2", + "drizzle-orm": "^0.44.5", "fast-glob": "^3.3.3", "fastify": "^5.5.0", "fastify-plugin": "^5.0.1", @@ -69,6 +70,7 @@ "ms": "^2.1.3", "multer": "2.0.2", "otplib": "^12.0.1", + "pg": "^8.16.3", "prisma": "6.13.0", "qrcode": "^1.5.4", "react": "^19.1.1", @@ -90,10 +92,12 @@ "@types/ms": "^2.1.0", "@types/multer": "^2.0.0", "@types/node": "^24.3.0", + "@types/pg": "^8.15.5", "@types/qrcode": "^1.5.5", "@types/react": "^19.1.12", "@types/react-dom": "^19.1.9", "@vitejs/plugin-react": "^5.0.2", + "drizzle-kit": "^0.31.4", "eslint": "^9.34.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-jsx-a11y": "^6.10.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 682f1b380..8dce37d2a 100755 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -113,6 +113,9 @@ importers: dotenv: specifier: ^17.2.2 version: 17.2.2 + drizzle-orm: + specifier: ^0.44.5 + version: 0.44.5(@prisma/client@6.13.0(prisma@6.13.0(typescript@5.9.2))(typescript@5.9.2))(@types/pg@8.15.5)(pg@8.16.3)(prisma@6.13.0(typescript@5.9.2)) fast-glob: specifier: ^3.3.3 version: 3.3.3 @@ -152,6 +155,9 @@ importers: otplib: specifier: ^12.0.1 version: 12.0.1 + pg: + specifier: ^8.16.3 + version: 8.16.3 prisma: specifier: 6.13.0 version: 6.13.0(typescript@5.9.2) @@ -210,6 +216,9 @@ importers: '@types/node': specifier: ^24.3.0 version: 24.3.0 + '@types/pg': + specifier: ^8.15.5 + version: 8.15.5 '@types/qrcode': specifier: ^1.5.5 version: 1.5.5 @@ -222,6 +231,9 @@ importers: '@vitejs/plugin-react': specifier: ^5.0.2 version: 5.0.2(vite@7.1.4(@types/node@24.3.0)(jiti@2.5.1)(sass@1.92.0)(sugarss@5.0.1(postcss@8.5.6))(tsx@4.20.5)) + drizzle-kit: + specifier: ^0.31.4 + version: 0.31.4 eslint: specifier: ^9.34.0 version: 9.34.0(jiti@2.5.1) @@ -579,12 +591,23 @@ packages: resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} engines: {node: '>=18'} + '@drizzle-team/brocli@0.10.2': + resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} + '@emnapi/runtime@1.4.5': resolution: {integrity: sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==} '@epic-web/invariant@1.0.0': resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==} + '@esbuild-kit/core-utils@3.3.2': + resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} + deprecated: 'Merged into tsx: https://tsx.is' + + '@esbuild-kit/esm-loader@2.6.5': + resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==} + deprecated: 'Merged into tsx: https://tsx.is' + '@esbuild/aix-ppc64@0.25.5': resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==} engines: {node: '>=18'} @@ -597,6 +620,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/android-arm64@0.18.20': + resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.25.5': resolution: {integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==} engines: {node: '>=18'} @@ -609,6 +638,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm@0.18.20': + resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.25.5': resolution: {integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==} engines: {node: '>=18'} @@ -621,6 +656,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-x64@0.18.20': + resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.25.5': resolution: {integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==} engines: {node: '>=18'} @@ -633,6 +674,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/darwin-arm64@0.18.20': + resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.25.5': resolution: {integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==} engines: {node: '>=18'} @@ -645,6 +692,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-x64@0.18.20': + resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.25.5': resolution: {integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==} engines: {node: '>=18'} @@ -657,6 +710,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/freebsd-arm64@0.18.20': + resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.25.5': resolution: {integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==} engines: {node: '>=18'} @@ -669,6 +728,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-x64@0.18.20': + resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.25.5': resolution: {integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==} engines: {node: '>=18'} @@ -681,6 +746,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/linux-arm64@0.18.20': + resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.25.5': resolution: {integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==} engines: {node: '>=18'} @@ -693,6 +764,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm@0.18.20': + resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.25.5': resolution: {integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==} engines: {node: '>=18'} @@ -705,6 +782,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-ia32@0.18.20': + resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.25.5': resolution: {integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==} engines: {node: '>=18'} @@ -717,6 +800,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-loong64@0.18.20': + resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.25.5': resolution: {integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==} engines: {node: '>=18'} @@ -729,6 +818,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-mips64el@0.18.20': + resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.25.5': resolution: {integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==} engines: {node: '>=18'} @@ -741,6 +836,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-ppc64@0.18.20': + resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.25.5': resolution: {integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==} engines: {node: '>=18'} @@ -753,6 +854,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-riscv64@0.18.20': + resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.25.5': resolution: {integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==} engines: {node: '>=18'} @@ -765,6 +872,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-s390x@0.18.20': + resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.25.5': resolution: {integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==} engines: {node: '>=18'} @@ -777,6 +890,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-x64@0.18.20': + resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.25.5': resolution: {integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==} engines: {node: '>=18'} @@ -801,6 +920,12 @@ packages: cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-x64@0.18.20': + resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.25.5': resolution: {integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==} engines: {node: '>=18'} @@ -825,6 +950,12 @@ packages: cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-x64@0.18.20': + resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.25.5': resolution: {integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==} engines: {node: '>=18'} @@ -843,6 +974,12 @@ packages: cpu: [arm64] os: [openharmony] + '@esbuild/sunos-x64@0.18.20': + resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.25.5': resolution: {integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==} engines: {node: '>=18'} @@ -855,6 +992,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/win32-arm64@0.18.20': + resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.25.5': resolution: {integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==} engines: {node: '>=18'} @@ -867,6 +1010,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-ia32@0.18.20': + resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.25.5': resolution: {integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==} engines: {node: '>=18'} @@ -879,6 +1028,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-x64@0.18.20': + resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.25.5': resolution: {integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==} engines: {node: '>=18'} @@ -2013,6 +2168,9 @@ packages: '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + '@types/pg@8.15.5': + resolution: {integrity: sha512-LF7lF6zWEKxuT3/OR8wAZGzkg4ENGXFNyiV/JeOt9z5B+0ZVwbql9McqX5c/WStFq1GaGso7H1AzP/qSzmlCKQ==} + '@types/qrcode@1.5.5': resolution: {integrity: sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==} @@ -2632,6 +2790,102 @@ packages: resolution: {integrity: sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==} engines: {node: '>=12'} + drizzle-kit@0.31.4: + resolution: {integrity: sha512-tCPWVZWZqWVx2XUsVpJRnH9Mx0ClVOf5YUHerZ5so1OKSlqww4zy1R5ksEdGRcO3tM3zj0PYN6V48TbQCL1RfA==} + hasBin: true + + drizzle-orm@0.44.5: + resolution: {integrity: sha512-jBe37K7d8ZSKptdKfakQFdeljtu3P2Cbo7tJoJSVZADzIKOBo9IAJPOmMsH2bZl90bZgh8FQlD8BjxXA/zuBkQ==} + peerDependencies: + '@aws-sdk/client-rds-data': '>=3' + '@cloudflare/workers-types': '>=4' + '@electric-sql/pglite': '>=0.2.0' + '@libsql/client': '>=0.10.0' + '@libsql/client-wasm': '>=0.10.0' + '@neondatabase/serverless': '>=0.10.0' + '@op-engineering/op-sqlite': '>=2' + '@opentelemetry/api': ^1.4.1 + '@planetscale/database': '>=1.13' + '@prisma/client': '*' + '@tidbcloud/serverless': '*' + '@types/better-sqlite3': '*' + '@types/pg': '*' + '@types/sql.js': '*' + '@upstash/redis': '>=1.34.7' + '@vercel/postgres': '>=0.8.0' + '@xata.io/client': '*' + better-sqlite3: '>=7' + bun-types: '*' + expo-sqlite: '>=14.0.0' + gel: '>=2' + knex: '*' + kysely: '*' + mysql2: '>=2' + pg: '>=8' + postgres: '>=3' + prisma: '*' + sql.js: '>=1' + sqlite3: '>=5' + peerDependenciesMeta: + '@aws-sdk/client-rds-data': + optional: true + '@cloudflare/workers-types': + optional: true + '@electric-sql/pglite': + optional: true + '@libsql/client': + optional: true + '@libsql/client-wasm': + optional: true + '@neondatabase/serverless': + optional: true + '@op-engineering/op-sqlite': + optional: true + '@opentelemetry/api': + optional: true + '@planetscale/database': + optional: true + '@prisma/client': + optional: true + '@tidbcloud/serverless': + optional: true + '@types/better-sqlite3': + optional: true + '@types/pg': + optional: true + '@types/sql.js': + optional: true + '@upstash/redis': + optional: true + '@vercel/postgres': + optional: true + '@xata.io/client': + optional: true + better-sqlite3: + optional: true + bun-types: + optional: true + expo-sqlite: + optional: true + gel: + optional: true + knex: + optional: true + kysely: + optional: true + mysql2: + optional: true + pg: + optional: true + postgres: + optional: true + prisma: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -2691,6 +2945,16 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} + esbuild-register@3.6.0: + resolution: {integrity: sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==} + peerDependencies: + esbuild: '>=0.12 <1' + + esbuild@0.18.20: + resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} + engines: {node: '>=12'} + hasBin: true + esbuild@0.25.5: resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==} engines: {node: '>=18'} @@ -4398,6 +4662,13 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + source-map@0.8.0-beta.0: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} engines: {node: '>= 8'} @@ -5656,6 +5927,8 @@ snapshots: '@csstools/css-tokenizer@3.0.4': {} + '@drizzle-team/brocli@0.10.2': {} + '@emnapi/runtime@1.4.5': dependencies: tslib: 2.8.1 @@ -5663,102 +5936,160 @@ snapshots: '@epic-web/invariant@1.0.0': {} + '@esbuild-kit/core-utils@3.3.2': + dependencies: + esbuild: 0.18.20 + source-map-support: 0.5.21 + + '@esbuild-kit/esm-loader@2.6.5': + dependencies: + '@esbuild-kit/core-utils': 3.3.2 + get-tsconfig: 4.10.1 + '@esbuild/aix-ppc64@0.25.5': optional: true '@esbuild/aix-ppc64@0.25.9': optional: true + '@esbuild/android-arm64@0.18.20': + optional: true + '@esbuild/android-arm64@0.25.5': optional: true '@esbuild/android-arm64@0.25.9': optional: true + '@esbuild/android-arm@0.18.20': + optional: true + '@esbuild/android-arm@0.25.5': optional: true '@esbuild/android-arm@0.25.9': optional: true + '@esbuild/android-x64@0.18.20': + optional: true + '@esbuild/android-x64@0.25.5': optional: true '@esbuild/android-x64@0.25.9': optional: true + '@esbuild/darwin-arm64@0.18.20': + optional: true + '@esbuild/darwin-arm64@0.25.5': optional: true '@esbuild/darwin-arm64@0.25.9': optional: true + '@esbuild/darwin-x64@0.18.20': + optional: true + '@esbuild/darwin-x64@0.25.5': optional: true '@esbuild/darwin-x64@0.25.9': optional: true + '@esbuild/freebsd-arm64@0.18.20': + optional: true + '@esbuild/freebsd-arm64@0.25.5': optional: true '@esbuild/freebsd-arm64@0.25.9': optional: true + '@esbuild/freebsd-x64@0.18.20': + optional: true + '@esbuild/freebsd-x64@0.25.5': optional: true '@esbuild/freebsd-x64@0.25.9': optional: true + '@esbuild/linux-arm64@0.18.20': + optional: true + '@esbuild/linux-arm64@0.25.5': optional: true '@esbuild/linux-arm64@0.25.9': optional: true + '@esbuild/linux-arm@0.18.20': + optional: true + '@esbuild/linux-arm@0.25.5': optional: true '@esbuild/linux-arm@0.25.9': optional: true + '@esbuild/linux-ia32@0.18.20': + optional: true + '@esbuild/linux-ia32@0.25.5': optional: true '@esbuild/linux-ia32@0.25.9': optional: true + '@esbuild/linux-loong64@0.18.20': + optional: true + '@esbuild/linux-loong64@0.25.5': optional: true '@esbuild/linux-loong64@0.25.9': optional: true + '@esbuild/linux-mips64el@0.18.20': + optional: true + '@esbuild/linux-mips64el@0.25.5': optional: true '@esbuild/linux-mips64el@0.25.9': optional: true + '@esbuild/linux-ppc64@0.18.20': + optional: true + '@esbuild/linux-ppc64@0.25.5': optional: true '@esbuild/linux-ppc64@0.25.9': optional: true + '@esbuild/linux-riscv64@0.18.20': + optional: true + '@esbuild/linux-riscv64@0.25.5': optional: true '@esbuild/linux-riscv64@0.25.9': optional: true + '@esbuild/linux-s390x@0.18.20': + optional: true + '@esbuild/linux-s390x@0.25.5': optional: true '@esbuild/linux-s390x@0.25.9': optional: true + '@esbuild/linux-x64@0.18.20': + optional: true + '@esbuild/linux-x64@0.25.5': optional: true @@ -5771,6 +6102,9 @@ snapshots: '@esbuild/netbsd-arm64@0.25.9': optional: true + '@esbuild/netbsd-x64@0.18.20': + optional: true + '@esbuild/netbsd-x64@0.25.5': optional: true @@ -5783,6 +6117,9 @@ snapshots: '@esbuild/openbsd-arm64@0.25.9': optional: true + '@esbuild/openbsd-x64@0.18.20': + optional: true + '@esbuild/openbsd-x64@0.25.5': optional: true @@ -5792,24 +6129,36 @@ snapshots: '@esbuild/openharmony-arm64@0.25.9': optional: true + '@esbuild/sunos-x64@0.18.20': + optional: true + '@esbuild/sunos-x64@0.25.5': optional: true '@esbuild/sunos-x64@0.25.9': optional: true + '@esbuild/win32-arm64@0.18.20': + optional: true + '@esbuild/win32-arm64@0.25.5': optional: true '@esbuild/win32-arm64@0.25.9': optional: true + '@esbuild/win32-ia32@0.18.20': + optional: true + '@esbuild/win32-ia32@0.25.5': optional: true '@esbuild/win32-ia32@0.25.9': optional: true + '@esbuild/win32-x64@0.18.20': + optional: true + '@esbuild/win32-x64@0.25.5': optional: true @@ -7082,6 +7431,12 @@ snapshots: '@types/normalize-package-data@2.4.4': {} + '@types/pg@8.15.5': + dependencies: + '@types/node': 24.3.0 + pg-protocol: 1.10.3 + pg-types: 2.2.0 + '@types/qrcode@1.5.5': dependencies: '@types/node': 24.3.0 @@ -7717,6 +8072,22 @@ snapshots: dotenv@17.2.2: {} + drizzle-kit@0.31.4: + dependencies: + '@drizzle-team/brocli': 0.10.2 + '@esbuild-kit/esm-loader': 2.6.5 + esbuild: 0.25.9 + esbuild-register: 3.6.0(esbuild@0.25.9) + transitivePeerDependencies: + - supports-color + + drizzle-orm@0.44.5(@prisma/client@6.13.0(prisma@6.13.0(typescript@5.9.2))(typescript@5.9.2))(@types/pg@8.15.5)(pg@8.16.3)(prisma@6.13.0(typescript@5.9.2)): + optionalDependencies: + '@prisma/client': 6.13.0(prisma@6.13.0(typescript@5.9.2))(typescript@5.9.2) + '@types/pg': 8.15.5 + pg: 8.16.3 + prisma: 6.13.0(typescript@5.9.2) + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -7841,6 +8212,38 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 + esbuild-register@3.6.0(esbuild@0.25.9): + dependencies: + debug: 4.4.1 + esbuild: 0.25.9 + transitivePeerDependencies: + - supports-color + + esbuild@0.18.20: + optionalDependencies: + '@esbuild/android-arm': 0.18.20 + '@esbuild/android-arm64': 0.18.20 + '@esbuild/android-x64': 0.18.20 + '@esbuild/darwin-arm64': 0.18.20 + '@esbuild/darwin-x64': 0.18.20 + '@esbuild/freebsd-arm64': 0.18.20 + '@esbuild/freebsd-x64': 0.18.20 + '@esbuild/linux-arm': 0.18.20 + '@esbuild/linux-arm64': 0.18.20 + '@esbuild/linux-ia32': 0.18.20 + '@esbuild/linux-loong64': 0.18.20 + '@esbuild/linux-mips64el': 0.18.20 + '@esbuild/linux-ppc64': 0.18.20 + '@esbuild/linux-riscv64': 0.18.20 + '@esbuild/linux-s390x': 0.18.20 + '@esbuild/linux-x64': 0.18.20 + '@esbuild/netbsd-x64': 0.18.20 + '@esbuild/openbsd-x64': 0.18.20 + '@esbuild/sunos-x64': 0.18.20 + '@esbuild/win32-arm64': 0.18.20 + '@esbuild/win32-ia32': 0.18.20 + '@esbuild/win32-x64': 0.18.20 + esbuild@0.25.5: optionalDependencies: '@esbuild/aix-ppc64': 0.25.5 @@ -9991,6 +10394,13 @@ snapshots: source-map-js@1.2.1: {} + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + source-map@0.8.0-beta.0: dependencies: whatwg-url: 7.1.0 diff --git a/src/drizzle/0000_bouncy_mantis.sql b/src/drizzle/0000_bouncy_mantis.sql new file mode 100644 index 000000000..b2caad0e2 --- /dev/null +++ b/src/drizzle/0000_bouncy_mantis.sql @@ -0,0 +1,287 @@ +CREATE TYPE "public"."IncompleteFileStatus" AS ENUM('PENDING', 'PROCESSING', 'COMPLETE', 'FAILED');--> statement-breakpoint +CREATE TYPE "public"."OAuthProviderType" AS ENUM('DISCORD', 'GOOGLE', 'GITHUB', 'OIDC');--> statement-breakpoint +CREATE TYPE "public"."Role" AS ENUM('USER', 'ADMIN', 'SUPERADMIN');--> statement-breakpoint +CREATE TYPE "public"."UserFilesQuota" AS ENUM('BY_BYTES', 'BY_FILES');--> statement-breakpoint + +--> statement-breakpoint +CREATE TABLE "Zipline" ( + "id" text PRIMARY KEY NOT NULL, + "createdAt" timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL, + "updatedAt" timestamp(3) NOT NULL, + "firstSetup" boolean DEFAULT true NOT NULL, + "coreReturnHttpsUrls" boolean DEFAULT false NOT NULL, + "coreDefaultDomain" text, + "coreTempDirectory" text NOT NULL, + "chunksEnabled" boolean DEFAULT true NOT NULL, + "chunksMax" text DEFAULT '95mb' NOT NULL, + "chunksSize" text DEFAULT '25mb' NOT NULL, + "tasksDeleteInterval" text DEFAULT '30m' NOT NULL, + "tasksClearInvitesInterval" text DEFAULT '30m' NOT NULL, + "tasksMaxViewsInterval" text DEFAULT '30m' NOT NULL, + "tasksThumbnailsInterval" text DEFAULT '30m' NOT NULL, + "tasksMetricsInterval" text DEFAULT '30m' NOT NULL, + "filesRoute" text DEFAULT '/u' NOT NULL, + "filesLength" integer DEFAULT 6 NOT NULL, + "filesDefaultFormat" text DEFAULT 'random' NOT NULL, + "filesDisabledExtensions" text[], + "filesMaxFileSize" text DEFAULT '100mb' NOT NULL, + "filesDefaultExpiration" text, + "filesAssumeMimetypes" boolean DEFAULT false NOT NULL, + "filesDefaultDateFormat" text DEFAULT 'YYYY-MM-DD_HH:mm:ss' NOT NULL, + "filesRemoveGpsMetadata" boolean DEFAULT false NOT NULL, + "urlsRoute" text DEFAULT '/go' NOT NULL, + "urlsLength" integer DEFAULT 6 NOT NULL, + "featuresImageCompression" boolean DEFAULT true NOT NULL, + "featuresRobotsTxt" boolean DEFAULT true NOT NULL, + "featuresHealthcheck" boolean DEFAULT true NOT NULL, + "featuresUserRegistration" boolean DEFAULT false NOT NULL, + "featuresOauthRegistration" boolean DEFAULT false NOT NULL, + "featuresDeleteOnMaxViews" boolean DEFAULT true NOT NULL, + "featuresThumbnailsEnabled" boolean DEFAULT true NOT NULL, + "featuresThumbnailsNumberThreads" integer DEFAULT 4 NOT NULL, + "featuresMetricsEnabled" boolean DEFAULT true NOT NULL, + "featuresMetricsAdminOnly" boolean DEFAULT false NOT NULL, + "featuresMetricsShowUserSpecific" boolean DEFAULT true NOT NULL, + "invitesEnabled" boolean DEFAULT true NOT NULL, + "invitesLength" integer DEFAULT 6 NOT NULL, + "websiteTitle" text DEFAULT 'Zipline' NOT NULL, + "websiteTitleLogo" text, + "websiteExternalLinks" jsonb DEFAULT '[{"url":"https://github.com/diced/zipline","name":"GitHub"},{"url":"https://zipline.diced.sh/","name":"Documentation"}]'::jsonb NOT NULL, + "websiteLoginBackground" text, + "websiteDefaultAvatar" text, + "websiteTos" text, + "websiteThemeDefault" text DEFAULT 'system' NOT NULL, + "websiteThemeDark" text DEFAULT 'builtin:dark_gray' NOT NULL, + "websiteThemeLight" text DEFAULT 'builtin:light_gray' NOT NULL, + "oauthBypassLocalLogin" boolean DEFAULT false NOT NULL, + "oauthLoginOnly" boolean DEFAULT false NOT NULL, + "oauthDiscordClientId" text, + "oauthDiscordClientSecret" text, + "oauthDiscordRedirectUri" text, + "oauthGoogleClientId" text, + "oauthGoogleClientSecret" text, + "oauthGoogleRedirectUri" text, + "oauthGithubClientId" text, + "oauthGithubClientSecret" text, + "oauthGithubRedirectUri" text, + "oauthOidcClientId" text, + "oauthOidcClientSecret" text, + "oauthOidcAuthorizeUrl" text, + "oauthOidcTokenUrl" text, + "oauthOidcUserinfoUrl" text, + "oauthOidcRedirectUri" text, + "mfaTotpEnabled" boolean DEFAULT false NOT NULL, + "mfaTotpIssuer" text DEFAULT 'Zipline' NOT NULL, + "mfaPasskeys" boolean DEFAULT false NOT NULL, + "ratelimitEnabled" boolean DEFAULT true NOT NULL, + "ratelimitMax" integer DEFAULT 10 NOT NULL, + "ratelimitWindow" integer, + "ratelimitAdminBypass" boolean DEFAULT true NOT NULL, + "ratelimitAllowList" text[], + "httpWebhookOnUpload" text, + "httpWebhookOnShorten" text, + "discordWebhookUrl" text, + "discordUsername" text, + "discordAvatarUrl" text, + "discordOnUploadWebhookUrl" text, + "discordOnUploadUsername" text, + "discordOnUploadAvatarUrl" text, + "discordOnUploadContent" text, + "discordOnUploadEmbed" jsonb, + "discordOnShortenWebhookUrl" text, + "discordOnShortenUsername" text, + "discordOnShortenAvatarUrl" text, + "discordOnShortenContent" text, + "discordOnShortenEmbed" jsonb, + "pwaEnabled" boolean DEFAULT false NOT NULL, + "pwaTitle" text DEFAULT 'Zipline' NOT NULL, + "pwaShortName" text DEFAULT 'Zipline' NOT NULL, + "pwaDescription" text DEFAULT 'Zipline' NOT NULL, + "pwaThemeColor" text DEFAULT '#000000' NOT NULL, + "pwaBackgroundColor" text DEFAULT '#000000' NOT NULL, + "websiteLoginBackgroundBlur" boolean DEFAULT true NOT NULL, + "filesRandomWordsNumAdjectives" integer DEFAULT 2 NOT NULL, + "filesRandomWordsSeparator" text DEFAULT '-' NOT NULL, + "featuresVersionAPI" text DEFAULT 'https://zipline-version.diced.sh' NOT NULL, + "featuresVersionChecking" boolean DEFAULT true NOT NULL, + "oauthDiscordAllowedIds" text[] DEFAULT '{"RAY"}', + "oauthDiscordDeniedIds" text[] DEFAULT '{"RAY"}', + "domains" text[] DEFAULT '{"RAY"}', + "filesDefaultCompressionFormat" text DEFAULT 'jpg', + "featuresThumbnailsFormat" text DEFAULT 'jpg' NOT NULL +); +--> statement-breakpoint +CREATE TABLE "Metric" ( + "id" text PRIMARY KEY NOT NULL, + "createdAt" timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL, + "updatedAt" timestamp(3) NOT NULL, + "data" jsonb NOT NULL +); +--> statement-breakpoint +CREATE TABLE "Url" ( + "id" text PRIMARY KEY NOT NULL, + "createdAt" timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL, + "updatedAt" timestamp(3) NOT NULL, + "code" text NOT NULL, + "vanity" text, + "destination" text NOT NULL, + "views" integer DEFAULT 0 NOT NULL, + "maxViews" integer, + "password" text, + "userId" text, + "enabled" boolean DEFAULT true NOT NULL +); +--> statement-breakpoint +CREATE TABLE "Folder" ( + "id" text PRIMARY KEY NOT NULL, + "createdAt" timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL, + "updatedAt" timestamp(3) NOT NULL, + "name" text NOT NULL, + "public" boolean DEFAULT false NOT NULL, + "userId" text NOT NULL, + "allowUploads" boolean DEFAULT false NOT NULL +); +--> statement-breakpoint +CREATE TABLE "User" ( + "id" text PRIMARY KEY NOT NULL, + "createdAt" timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL, + "updatedAt" timestamp(3) NOT NULL, + "username" text NOT NULL, + "password" text, + "avatar" text, + "token" text NOT NULL, + "role" "Role" DEFAULT 'USER' NOT NULL, + "view" jsonb DEFAULT '{}'::jsonb NOT NULL, + "totpSecret" text, + "sessions" text[] +); +--> statement-breakpoint +CREATE TABLE "Export" ( + "id" text PRIMARY KEY NOT NULL, + "createdAt" timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL, + "updatedAt" timestamp(3) NOT NULL, + "completed" boolean DEFAULT false NOT NULL, + "path" text NOT NULL, + "files" integer NOT NULL, + "size" text NOT NULL, + "userId" text NOT NULL +); +--> statement-breakpoint +CREATE TABLE "UserQuota" ( + "id" text PRIMARY KEY NOT NULL, + "createdAt" timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL, + "updatedAt" timestamp(3) NOT NULL, + "filesQuota" "UserFilesQuota" NOT NULL, + "maxBytes" text, + "maxFiles" integer, + "maxUrls" integer, + "userId" text +); +--> statement-breakpoint +CREATE TABLE "UserPasskey" ( + "id" text PRIMARY KEY NOT NULL, + "createdAt" timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL, + "updatedAt" timestamp(3) NOT NULL, + "lastUsed" timestamp(3), + "name" text NOT NULL, + "reg" jsonb NOT NULL, + "userId" text NOT NULL +); +--> statement-breakpoint +CREATE TABLE "OAuthProvider" ( + "id" text PRIMARY KEY NOT NULL, + "createdAt" timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL, + "updatedAt" timestamp(3) NOT NULL, + "userId" text NOT NULL, + "provider" "OAuthProviderType" NOT NULL, + "username" text NOT NULL, + "accessToken" text NOT NULL, + "refreshToken" text, + "oauthId" text +); +--> statement-breakpoint +CREATE TABLE "File" ( + "id" text PRIMARY KEY NOT NULL, + "createdAt" timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL, + "updatedAt" timestamp(3) NOT NULL, + "deletesAt" timestamp(3), + "name" text NOT NULL, + "originalName" text, + "size" bigint NOT NULL, + "type" text NOT NULL, + "views" integer DEFAULT 0 NOT NULL, + "maxViews" integer, + "favorite" boolean DEFAULT false NOT NULL, + "password" text, + "userId" text, + "folderId" text +); +--> statement-breakpoint +CREATE TABLE "Thumbnail" ( + "id" text PRIMARY KEY NOT NULL, + "createdAt" timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL, + "updatedAt" timestamp(3) NOT NULL, + "path" text NOT NULL, + "fileId" text NOT NULL +); +--> statement-breakpoint +CREATE TABLE "IncompleteFile" ( + "id" text PRIMARY KEY NOT NULL, + "createdAt" timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL, + "updatedAt" timestamp(3) NOT NULL, + "status" "IncompleteFileStatus" NOT NULL, + "chunksTotal" integer NOT NULL, + "chunksComplete" integer NOT NULL, + "metadata" jsonb NOT NULL, + "userId" text NOT NULL +); +--> statement-breakpoint +CREATE TABLE "Tag" ( + "id" text PRIMARY KEY NOT NULL, + "createdAt" timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL, + "updatedAt" timestamp(3) NOT NULL, + "name" text NOT NULL, + "color" text NOT NULL, + "userId" text +); +--> statement-breakpoint +CREATE TABLE "Invite" ( + "id" text PRIMARY KEY NOT NULL, + "createdAt" timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL, + "updatedAt" timestamp(3) NOT NULL, + "expiresAt" timestamp(3), + "code" text NOT NULL, + "uses" integer DEFAULT 0 NOT NULL, + "maxUses" integer, + "inviterId" text NOT NULL +); +--> statement-breakpoint +CREATE TABLE "_FileToTag" ( + "A" text NOT NULL, + "B" text NOT NULL, + CONSTRAINT "_FileToTag_AB_pkey" PRIMARY KEY("A","B") +); +--> statement-breakpoint +ALTER TABLE "Url" ADD CONSTRAINT "Url_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE set null ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "Folder" ADD CONSTRAINT "Folder_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "Export" ADD CONSTRAINT "Export_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "UserQuota" ADD CONSTRAINT "UserQuota_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "UserPasskey" ADD CONSTRAINT "UserPasskey_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "OAuthProvider" ADD CONSTRAINT "OAuthProvider_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE restrict ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "File" ADD CONSTRAINT "File_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE set null ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "File" ADD CONSTRAINT "File_folderId_fkey" FOREIGN KEY ("folderId") REFERENCES "public"."Folder"("id") ON DELETE set null ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "Thumbnail" ADD CONSTRAINT "Thumbnail_fileId_fkey" FOREIGN KEY ("fileId") REFERENCES "public"."File"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "IncompleteFile" ADD CONSTRAINT "IncompleteFile_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "Tag" ADD CONSTRAINT "Tag_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE set null ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "Invite" ADD CONSTRAINT "Invite_inviterId_fkey" FOREIGN KEY ("inviterId") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "_FileToTag" ADD CONSTRAINT "_FileToTag_A_fkey" FOREIGN KEY ("A") REFERENCES "public"."File"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "_FileToTag" ADD CONSTRAINT "_FileToTag_B_fkey" FOREIGN KEY ("B") REFERENCES "public"."Tag"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +CREATE UNIQUE INDEX "Url_code_vanity_key" ON "Url" USING btree ("code" text_ops,"vanity" text_ops);--> statement-breakpoint +CREATE UNIQUE INDEX "User_token_key" ON "User" USING btree ("token" text_ops);--> statement-breakpoint +CREATE UNIQUE INDEX "User_username_key" ON "User" USING btree ("username" text_ops);--> statement-breakpoint +CREATE UNIQUE INDEX "UserQuota_userId_key" ON "UserQuota" USING btree ("userId" text_ops);--> statement-breakpoint +CREATE UNIQUE INDEX "OAuthProvider_provider_oauthId_key" ON "OAuthProvider" USING btree ("provider" text_ops,"oauthId" text_ops);--> statement-breakpoint +CREATE UNIQUE INDEX "Thumbnail_fileId_key" ON "Thumbnail" USING btree ("fileId" text_ops);--> statement-breakpoint +CREATE UNIQUE INDEX "Tag_name_key" ON "Tag" USING btree ("name" text_ops);--> statement-breakpoint +CREATE UNIQUE INDEX "Invite_code_key" ON "Invite" USING btree ("code" text_ops);--> statement-breakpoint +CREATE INDEX "_FileToTag_B_index" ON "_FileToTag" USING btree ("B" text_ops); diff --git a/src/drizzle/meta/0000_snapshot.json b/src/drizzle/meta/0000_snapshot.json new file mode 100644 index 000000000..ae10c339d --- /dev/null +++ b/src/drizzle/meta/0000_snapshot.json @@ -0,0 +1,2037 @@ +{ + "id": "00000000-0000-0000-0000-000000000000", + "prevId": "", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.Zipline": { + "name": "Zipline", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true + }, + "firstSetup": { + "name": "firstSetup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "coreReturnHttpsUrls": { + "name": "coreReturnHttpsUrls", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "coreDefaultDomain": { + "name": "coreDefaultDomain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "coreTempDirectory": { + "name": "coreTempDirectory", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chunksEnabled": { + "name": "chunksEnabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "chunksMax": { + "name": "chunksMax", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'95mb'" + }, + "chunksSize": { + "name": "chunksSize", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'25mb'" + }, + "tasksDeleteInterval": { + "name": "tasksDeleteInterval", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'30m'" + }, + "tasksClearInvitesInterval": { + "name": "tasksClearInvitesInterval", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'30m'" + }, + "tasksMaxViewsInterval": { + "name": "tasksMaxViewsInterval", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'30m'" + }, + "tasksThumbnailsInterval": { + "name": "tasksThumbnailsInterval", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'30m'" + }, + "tasksMetricsInterval": { + "name": "tasksMetricsInterval", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'30m'" + }, + "filesRoute": { + "name": "filesRoute", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'/u'" + }, + "filesLength": { + "name": "filesLength", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 6 + }, + "filesDefaultFormat": { + "name": "filesDefaultFormat", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'random'" + }, + "filesDisabledExtensions": { + "name": "filesDisabledExtensions", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "filesMaxFileSize": { + "name": "filesMaxFileSize", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'100mb'" + }, + "filesDefaultExpiration": { + "name": "filesDefaultExpiration", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "filesAssumeMimetypes": { + "name": "filesAssumeMimetypes", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "filesDefaultDateFormat": { + "name": "filesDefaultDateFormat", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'YYYY-MM-DD_HH:mm:ss'" + }, + "filesRemoveGpsMetadata": { + "name": "filesRemoveGpsMetadata", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "urlsRoute": { + "name": "urlsRoute", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'/go'" + }, + "urlsLength": { + "name": "urlsLength", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 6 + }, + "featuresImageCompression": { + "name": "featuresImageCompression", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "featuresRobotsTxt": { + "name": "featuresRobotsTxt", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "featuresHealthcheck": { + "name": "featuresHealthcheck", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "featuresUserRegistration": { + "name": "featuresUserRegistration", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "featuresOauthRegistration": { + "name": "featuresOauthRegistration", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "featuresDeleteOnMaxViews": { + "name": "featuresDeleteOnMaxViews", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "featuresThumbnailsEnabled": { + "name": "featuresThumbnailsEnabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "featuresThumbnailsNumberThreads": { + "name": "featuresThumbnailsNumberThreads", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 4 + }, + "featuresMetricsEnabled": { + "name": "featuresMetricsEnabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "featuresMetricsAdminOnly": { + "name": "featuresMetricsAdminOnly", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "featuresMetricsShowUserSpecific": { + "name": "featuresMetricsShowUserSpecific", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "invitesEnabled": { + "name": "invitesEnabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "invitesLength": { + "name": "invitesLength", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 6 + }, + "websiteTitle": { + "name": "websiteTitle", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'Zipline'" + }, + "websiteTitleLogo": { + "name": "websiteTitleLogo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "websiteExternalLinks": { + "name": "websiteExternalLinks", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[{\"url\":\"https://github.com/diced/zipline\",\"name\":\"GitHub\"},{\"url\":\"https://zipline.diced.sh/\",\"name\":\"Documentation\"}]'::jsonb" + }, + "websiteLoginBackground": { + "name": "websiteLoginBackground", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "websiteDefaultAvatar": { + "name": "websiteDefaultAvatar", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "websiteTos": { + "name": "websiteTos", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "websiteThemeDefault": { + "name": "websiteThemeDefault", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "websiteThemeDark": { + "name": "websiteThemeDark", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'builtin:dark_gray'" + }, + "websiteThemeLight": { + "name": "websiteThemeLight", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'builtin:light_gray'" + }, + "oauthBypassLocalLogin": { + "name": "oauthBypassLocalLogin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "oauthLoginOnly": { + "name": "oauthLoginOnly", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "oauthDiscordClientId": { + "name": "oauthDiscordClientId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauthDiscordClientSecret": { + "name": "oauthDiscordClientSecret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauthDiscordRedirectUri": { + "name": "oauthDiscordRedirectUri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauthGoogleClientId": { + "name": "oauthGoogleClientId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauthGoogleClientSecret": { + "name": "oauthGoogleClientSecret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauthGoogleRedirectUri": { + "name": "oauthGoogleRedirectUri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauthGithubClientId": { + "name": "oauthGithubClientId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauthGithubClientSecret": { + "name": "oauthGithubClientSecret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauthGithubRedirectUri": { + "name": "oauthGithubRedirectUri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauthOidcClientId": { + "name": "oauthOidcClientId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauthOidcClientSecret": { + "name": "oauthOidcClientSecret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauthOidcAuthorizeUrl": { + "name": "oauthOidcAuthorizeUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauthOidcTokenUrl": { + "name": "oauthOidcTokenUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauthOidcUserinfoUrl": { + "name": "oauthOidcUserinfoUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauthOidcRedirectUri": { + "name": "oauthOidcRedirectUri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mfaTotpEnabled": { + "name": "mfaTotpEnabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "mfaTotpIssuer": { + "name": "mfaTotpIssuer", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'Zipline'" + }, + "mfaPasskeys": { + "name": "mfaPasskeys", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "ratelimitEnabled": { + "name": "ratelimitEnabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "ratelimitMax": { + "name": "ratelimitMax", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10 + }, + "ratelimitWindow": { + "name": "ratelimitWindow", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "ratelimitAdminBypass": { + "name": "ratelimitAdminBypass", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "ratelimitAllowList": { + "name": "ratelimitAllowList", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "httpWebhookOnUpload": { + "name": "httpWebhookOnUpload", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "httpWebhookOnShorten": { + "name": "httpWebhookOnShorten", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discordWebhookUrl": { + "name": "discordWebhookUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discordUsername": { + "name": "discordUsername", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discordAvatarUrl": { + "name": "discordAvatarUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discordOnUploadWebhookUrl": { + "name": "discordOnUploadWebhookUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discordOnUploadUsername": { + "name": "discordOnUploadUsername", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discordOnUploadAvatarUrl": { + "name": "discordOnUploadAvatarUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discordOnUploadContent": { + "name": "discordOnUploadContent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discordOnUploadEmbed": { + "name": "discordOnUploadEmbed", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "discordOnShortenWebhookUrl": { + "name": "discordOnShortenWebhookUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discordOnShortenUsername": { + "name": "discordOnShortenUsername", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discordOnShortenAvatarUrl": { + "name": "discordOnShortenAvatarUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discordOnShortenContent": { + "name": "discordOnShortenContent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discordOnShortenEmbed": { + "name": "discordOnShortenEmbed", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "pwaEnabled": { + "name": "pwaEnabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "pwaTitle": { + "name": "pwaTitle", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'Zipline'" + }, + "pwaShortName": { + "name": "pwaShortName", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'Zipline'" + }, + "pwaDescription": { + "name": "pwaDescription", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'Zipline'" + }, + "pwaThemeColor": { + "name": "pwaThemeColor", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'#000000'" + }, + "pwaBackgroundColor": { + "name": "pwaBackgroundColor", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'#000000'" + }, + "websiteLoginBackgroundBlur": { + "name": "websiteLoginBackgroundBlur", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "filesRandomWordsNumAdjectives": { + "name": "filesRandomWordsNumAdjectives", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "filesRandomWordsSeparator": { + "name": "filesRandomWordsSeparator", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'-'" + }, + "featuresVersionAPI": { + "name": "featuresVersionAPI", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'https://zipline-version.diced.sh'" + }, + "featuresVersionChecking": { + "name": "featuresVersionChecking", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "oauthDiscordAllowedIds": { + "name": "oauthDiscordAllowedIds", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{\"RAY\"}'" + }, + "oauthDiscordDeniedIds": { + "name": "oauthDiscordDeniedIds", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{\"RAY\"}'" + }, + "domains": { + "name": "domains", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{\"RAY\"}'" + }, + "filesDefaultCompressionFormat": { + "name": "filesDefaultCompressionFormat", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'jpg'" + }, + "featuresThumbnailsFormat": { + "name": "featuresThumbnailsFormat", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'jpg'" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {}, + "policies": {}, + "isRLSEnabled": false + }, + "public.Metric": { + "name": "Metric", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {}, + "policies": {}, + "isRLSEnabled": false + }, + "public.Url": { + "name": "Url", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vanity": { + "name": "vanity", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "destination": { + "name": "destination", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "maxViews": { + "name": "maxViews", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + } + }, + "indexes": { + "Url_code_vanity_key": { + "name": "Url_code_vanity_key", + "columns": [ + { + "expression": "code", + "asc": true, + "nulls": "last", + "opclass": "text_ops", + "isExpression": false + }, + { + "expression": "vanity", + "asc": true, + "nulls": "last", + "opclass": "text_ops", + "isExpression": false + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "Url_userId_fkey": { + "name": "Url_userId_fkey", + "tableFrom": "Url", + "tableTo": "User", + "schemaTo": "public", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {}, + "policies": {}, + "isRLSEnabled": false + }, + "public.Folder": { + "name": "Folder", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allowUploads": { + "name": "allowUploads", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "Folder_userId_fkey": { + "name": "Folder_userId_fkey", + "tableFrom": "Folder", + "tableTo": "User", + "schemaTo": "public", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {}, + "policies": {}, + "isRLSEnabled": false + }, + "public.User": { + "name": "User", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar": { + "name": "avatar", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "Role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'USER'" + }, + "view": { + "name": "view", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "totpSecret": { + "name": "totpSecret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sessions": { + "name": "sessions", + "type": "text[]", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "User_token_key": { + "name": "User_token_key", + "columns": [ + { + "expression": "token", + "asc": true, + "nulls": "last", + "opclass": "text_ops", + "isExpression": false + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "User_username_key": { + "name": "User_username_key", + "columns": [ + { + "expression": "username", + "asc": true, + "nulls": "last", + "opclass": "text_ops", + "isExpression": false + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {}, + "policies": {}, + "isRLSEnabled": false + }, + "public.Export": { + "name": "Export", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true + }, + "completed": { + "name": "completed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "files": { + "name": "files", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "Export_userId_fkey": { + "name": "Export_userId_fkey", + "tableFrom": "Export", + "tableTo": "User", + "schemaTo": "public", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {}, + "policies": {}, + "isRLSEnabled": false + }, + "public.UserQuota": { + "name": "UserQuota", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true + }, + "filesQuota": { + "name": "filesQuota", + "type": "UserFilesQuota", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "maxBytes": { + "name": "maxBytes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "maxFiles": { + "name": "maxFiles", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "maxUrls": { + "name": "maxUrls", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "UserQuota_userId_key": { + "name": "UserQuota_userId_key", + "columns": [ + { + "expression": "userId", + "asc": true, + "nulls": "last", + "opclass": "text_ops", + "isExpression": false + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "UserQuota_userId_fkey": { + "name": "UserQuota_userId_fkey", + "tableFrom": "UserQuota", + "tableTo": "User", + "schemaTo": "public", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {}, + "policies": {}, + "isRLSEnabled": false + }, + "public.UserPasskey": { + "name": "UserPasskey", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true + }, + "lastUsed": { + "name": "lastUsed", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reg": { + "name": "reg", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "UserPasskey_userId_fkey": { + "name": "UserPasskey_userId_fkey", + "tableFrom": "UserPasskey", + "tableTo": "User", + "schemaTo": "public", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {}, + "policies": {}, + "isRLSEnabled": false + }, + "public.OAuthProvider": { + "name": "OAuthProvider", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "OAuthProviderType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "accessToken": { + "name": "accessToken", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refreshToken": { + "name": "refreshToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauthId": { + "name": "oauthId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "OAuthProvider_provider_oauthId_key": { + "name": "OAuthProvider_provider_oauthId_key", + "columns": [ + { + "expression": "provider", + "asc": true, + "nulls": "last", + "opclass": "text_ops", + "isExpression": false + }, + { + "expression": "oauthId", + "asc": true, + "nulls": "last", + "opclass": "text_ops", + "isExpression": false + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "OAuthProvider_userId_fkey": { + "name": "OAuthProvider_userId_fkey", + "tableFrom": "OAuthProvider", + "tableTo": "User", + "schemaTo": "public", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {}, + "policies": {}, + "isRLSEnabled": false + }, + "public.File": { + "name": "File", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true + }, + "deletesAt": { + "name": "deletesAt", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "originalName": { + "name": "originalName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "size": { + "name": "size", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "maxViews": { + "name": "maxViews", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "favorite": { + "name": "favorite", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "folderId": { + "name": "folderId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "File_userId_fkey": { + "name": "File_userId_fkey", + "tableFrom": "File", + "tableTo": "User", + "schemaTo": "public", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "File_folderId_fkey": { + "name": "File_folderId_fkey", + "tableFrom": "File", + "tableTo": "Folder", + "schemaTo": "public", + "columnsFrom": [ + "folderId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {}, + "policies": {}, + "isRLSEnabled": false + }, + "public.Thumbnail": { + "name": "Thumbnail", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fileId": { + "name": "fileId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "Thumbnail_fileId_key": { + "name": "Thumbnail_fileId_key", + "columns": [ + { + "expression": "fileId", + "asc": true, + "nulls": "last", + "opclass": "text_ops", + "isExpression": false + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "Thumbnail_fileId_fkey": { + "name": "Thumbnail_fileId_fkey", + "tableFrom": "Thumbnail", + "tableTo": "File", + "schemaTo": "public", + "columnsFrom": [ + "fileId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {}, + "policies": {}, + "isRLSEnabled": false + }, + "public.IncompleteFile": { + "name": "IncompleteFile", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "IncompleteFileStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "chunksTotal": { + "name": "chunksTotal", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "chunksComplete": { + "name": "chunksComplete", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "IncompleteFile_userId_fkey": { + "name": "IncompleteFile_userId_fkey", + "tableFrom": "IncompleteFile", + "tableTo": "User", + "schemaTo": "public", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {}, + "policies": {}, + "isRLSEnabled": false + }, + "public.Tag": { + "name": "Tag", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "Tag_name_key": { + "name": "Tag_name_key", + "columns": [ + { + "expression": "name", + "asc": true, + "nulls": "last", + "opclass": "text_ops", + "isExpression": false + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "Tag_userId_fkey": { + "name": "Tag_userId_fkey", + "tableFrom": "Tag", + "tableTo": "User", + "schemaTo": "public", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {}, + "policies": {}, + "isRLSEnabled": false + }, + "public.Invite": { + "name": "Invite", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uses": { + "name": "uses", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "maxUses": { + "name": "maxUses", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "inviterId": { + "name": "inviterId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "Invite_code_key": { + "name": "Invite_code_key", + "columns": [ + { + "expression": "code", + "asc": true, + "nulls": "last", + "opclass": "text_ops", + "isExpression": false + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "Invite_inviterId_fkey": { + "name": "Invite_inviterId_fkey", + "tableFrom": "Invite", + "tableTo": "User", + "schemaTo": "public", + "columnsFrom": [ + "inviterId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {}, + "policies": {}, + "isRLSEnabled": false + }, + "public._FileToTag": { + "name": "_FileToTag", + "schema": "", + "columns": { + "A": { + "name": "A", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "B": { + "name": "B", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "_FileToTag_B_index": { + "name": "_FileToTag_B_index", + "columns": [ + { + "expression": "B", + "asc": true, + "nulls": "last", + "opclass": "text_ops", + "isExpression": false + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "_FileToTag_A_fkey": { + "name": "_FileToTag_A_fkey", + "tableFrom": "_FileToTag", + "tableTo": "File", + "schemaTo": "public", + "columnsFrom": [ + "A" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "_FileToTag_B_fkey": { + "name": "_FileToTag_B_fkey", + "tableFrom": "_FileToTag", + "tableTo": "Tag", + "schemaTo": "public", + "columnsFrom": [ + "B" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "_FileToTag_AB_pkey": { + "name": "_FileToTag_AB_pkey", + "columns": [ + "A", + "B" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraints": {}, + "policies": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.IncompleteFileStatus": { + "name": "IncompleteFileStatus", + "values": [ + "PENDING", + "PROCESSING", + "COMPLETE", + "FAILED" + ], + "schema": "public" + }, + "public.OAuthProviderType": { + "name": "OAuthProviderType", + "values": [ + "DISCORD", + "GOOGLE", + "GITHUB", + "OIDC" + ], + "schema": "public" + }, + "public.Role": { + "name": "Role", + "values": [ + "USER", + "ADMIN", + "SUPERADMIN" + ], + "schema": "public" + }, + "public.UserFilesQuota": { + "name": "UserFilesQuota", + "values": [ + "BY_BYTES", + "BY_FILES" + ], + "schema": "public" + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "tables": { + "Zipline": { + "columns": { + "filesDisabledExtensions": { + "isArray": true, + "dimensions": 1, + "rawType": "text" + }, + "ratelimitAllowList": { + "isArray": true, + "dimensions": 1, + "rawType": "text" + }, + "oauthDiscordAllowedIds": { + "isArray": true, + "dimensions": 1, + "rawType": "text" + }, + "oauthDiscordDeniedIds": { + "isArray": true, + "dimensions": 1, + "rawType": "text" + }, + "domains": { + "isArray": true, + "dimensions": 1, + "rawType": "text" + } + } + }, + "User": { + "columns": { + "sessions": { + "isArray": true, + "dimensions": 1, + "rawType": "text" + } + } + } + } + } +} diff --git a/src/drizzle/meta/_journal.json b/src/drizzle/meta/_journal.json new file mode 100644 index 000000000..a44f8bdf1 --- /dev/null +++ b/src/drizzle/meta/_journal.json @@ -0,0 +1,20 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1756926875085, + "tag": "0000_bouncy_mantis", + "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1756931651183, + "tag": "0001_next_red_ghost", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/src/drizzle/relations.ts b/src/drizzle/relations.ts new file mode 100644 index 000000000..caf032451 --- /dev/null +++ b/src/drizzle/relations.ts @@ -0,0 +1,125 @@ +import { relations } from 'drizzle-orm/relations'; +import { + user, + url, + folder, + exportTable, + userQuota, + userPasskey, + oauthProvider, + file, + thumbnail, + incompleteFile, + tag, + invite, + fileToTag, +} from './schema'; + +export const urlRelations = relations(url, ({ one }) => ({ + user: one(user, { + fields: [url.userId], + references: [user.id], + }), +})); + +export const userRelations = relations(user, ({ many }) => ({ + urls: many(url), + folders: many(folder), + exports: many(exportTable), + userQuotas: many(userQuota), + userPasskeys: many(userPasskey), + oauthProviders: many(oauthProvider), + files: many(file), + incompleteFiles: many(incompleteFile), + tags: many(tag), + invites: many(invite), +})); + +export const folderRelations = relations(folder, ({ one, many }) => ({ + user: one(user, { + fields: [folder.userId], + references: [user.id], + }), + files: many(file), +})); + +export const exportRelations = relations(exportTable, ({ one }) => ({ + user: one(user, { + fields: [exportTable.userId], + references: [user.id], + }), +})); + +export const userQuotaRelations = relations(userQuota, ({ one }) => ({ + user: one(user, { + fields: [userQuota.userId], + references: [user.id], + }), +})); + +export const userPasskeyRelations = relations(userPasskey, ({ one }) => ({ + user: one(user, { + fields: [userPasskey.userId], + references: [user.id], + }), +})); + +export const oauthProviderRelations = relations(oauthProvider, ({ one }) => ({ + user: one(user, { + fields: [oauthProvider.userId], + references: [user.id], + }), +})); + +export const fileRelations = relations(file, ({ one, many }) => ({ + user: one(user, { + fields: [file.userId], + references: [user.id], + }), + folder: one(folder, { + fields: [file.folderId], + references: [folder.id], + }), + thumbnails: many(thumbnail), + fileToTags: many(fileToTag), +})); + +export const thumbnailRelations = relations(thumbnail, ({ one }) => ({ + file: one(file, { + fields: [thumbnail.fileId], + references: [file.id], + }), +})); + +export const incompleteFileRelations = relations(incompleteFile, ({ one }) => ({ + user: one(user, { + fields: [incompleteFile.userId], + references: [user.id], + }), +})); + +export const tagRelations = relations(tag, ({ one, many }) => ({ + user: one(user, { + fields: [tag.userId], + references: [user.id], + }), + fileToTags: many(fileToTag), +})); + +export const inviteRelations = relations(invite, ({ one }) => ({ + user: one(user, { + fields: [invite.inviterId], + references: [user.id], + }), +})); + +export const fileToTagRelations = relations(fileToTag, ({ one }) => ({ + file: one(file, { + fields: [fileToTag.a], + references: [file.id], + }), + tag: one(tag, { + fields: [fileToTag.b], + references: [tag.id], + }), +})); diff --git a/src/drizzle/schema.ts b/src/drizzle/schema.ts new file mode 100644 index 000000000..dc8a32392 --- /dev/null +++ b/src/drizzle/schema.ts @@ -0,0 +1,497 @@ +import { + pgTable, + timestamp, + text, + integer, + boolean, + jsonb, + uniqueIndex, + foreignKey, + bigint, + index, + primaryKey, + pgEnum, +} from 'drizzle-orm/pg-core'; +import { sql } from 'drizzle-orm'; + +export const incompleteFileStatus = pgEnum('IncompleteFileStatus', [ + 'PENDING', + 'PROCESSING', + 'COMPLETE', + 'FAILED', +]); +export const oauthProviderType = pgEnum('OAuthProviderType', ['DISCORD', 'GOOGLE', 'GITHUB', 'OIDC']); +export const role = pgEnum('Role', ['USER', 'ADMIN', 'SUPERADMIN']); +export const userFilesQuota = pgEnum('UserFilesQuota', ['BY_BYTES', 'BY_FILES']); + +export const zipline = pgTable('Zipline', { + id: text().primaryKey().notNull(), + createdAt: timestamp({ precision: 3, mode: 'string' }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: timestamp({ precision: 3, mode: 'string' }).notNull(), + firstSetup: boolean().default(true).notNull(), + coreReturnHttpsUrls: boolean().default(false).notNull(), + coreDefaultDomain: text(), + coreTempDirectory: text().notNull(), + chunksEnabled: boolean().default(true).notNull(), + chunksMax: text().default('95mb').notNull(), + chunksSize: text().default('25mb').notNull(), + tasksDeleteInterval: text().default('30m').notNull(), + tasksClearInvitesInterval: text().default('30m').notNull(), + tasksMaxViewsInterval: text().default('30m').notNull(), + tasksThumbnailsInterval: text().default('30m').notNull(), + tasksMetricsInterval: text().default('30m').notNull(), + filesRoute: text().default('/u').notNull(), + filesLength: integer().default(6).notNull(), + filesDefaultFormat: text().default('random').notNull(), + filesDisabledExtensions: text().array(), + filesMaxFileSize: text().default('100mb').notNull(), + filesDefaultExpiration: text(), + filesAssumeMimetypes: boolean().default(false).notNull(), + filesDefaultDateFormat: text().default('YYYY-MM-DD_HH:mm:ss').notNull(), + filesRemoveGpsMetadata: boolean().default(false).notNull(), + urlsRoute: text().default('/go').notNull(), + urlsLength: integer().default(6).notNull(), + featuresImageCompression: boolean().default(true).notNull(), + featuresRobotsTxt: boolean().default(true).notNull(), + featuresHealthcheck: boolean().default(true).notNull(), + featuresUserRegistration: boolean().default(false).notNull(), + featuresOauthRegistration: boolean().default(false).notNull(), + featuresDeleteOnMaxViews: boolean().default(true).notNull(), + featuresThumbnailsEnabled: boolean().default(true).notNull(), + featuresThumbnailsNumberThreads: integer().default(4).notNull(), + featuresMetricsEnabled: boolean().default(true).notNull(), + featuresMetricsAdminOnly: boolean().default(false).notNull(), + featuresMetricsShowUserSpecific: boolean().default(true).notNull(), + invitesEnabled: boolean().default(true).notNull(), + invitesLength: integer().default(6).notNull(), + websiteTitle: text().default('Zipline').notNull(), + websiteTitleLogo: text(), + websiteExternalLinks: jsonb() + .default([ + { url: 'https://github.com/diced/zipline', name: 'GitHub' }, + { url: 'https://zipline.diced.sh/', name: 'Documentation' }, + ]) + .notNull(), + websiteLoginBackground: text(), + websiteDefaultAvatar: text(), + websiteTos: text(), + websiteThemeDefault: text().default('system').notNull(), + websiteThemeDark: text().default('builtin:dark_gray').notNull(), + websiteThemeLight: text().default('builtin:light_gray').notNull(), + oauthBypassLocalLogin: boolean().default(false).notNull(), + oauthLoginOnly: boolean().default(false).notNull(), + oauthDiscordClientId: text(), + oauthDiscordClientSecret: text(), + oauthDiscordRedirectUri: text(), + oauthGoogleClientId: text(), + oauthGoogleClientSecret: text(), + oauthGoogleRedirectUri: text(), + oauthGithubClientId: text(), + oauthGithubClientSecret: text(), + oauthGithubRedirectUri: text(), + oauthOidcClientId: text(), + oauthOidcClientSecret: text(), + oauthOidcAuthorizeUrl: text(), + oauthOidcTokenUrl: text(), + oauthOidcUserinfoUrl: text(), + oauthOidcRedirectUri: text(), + mfaTotpEnabled: boolean().default(false).notNull(), + mfaTotpIssuer: text().default('Zipline').notNull(), + mfaPasskeys: boolean().default(false).notNull(), + ratelimitEnabled: boolean().default(true).notNull(), + ratelimitMax: integer().default(10).notNull(), + ratelimitWindow: integer(), + ratelimitAdminBypass: boolean().default(true).notNull(), + ratelimitAllowList: text().array(), + httpWebhookOnUpload: text(), + httpWebhookOnShorten: text(), + discordWebhookUrl: text(), + discordUsername: text(), + discordAvatarUrl: text(), + discordOnUploadWebhookUrl: text(), + discordOnUploadUsername: text(), + discordOnUploadAvatarUrl: text(), + discordOnUploadContent: text(), + discordOnUploadEmbed: jsonb(), + discordOnShortenWebhookUrl: text(), + discordOnShortenUsername: text(), + discordOnShortenAvatarUrl: text(), + discordOnShortenContent: text(), + discordOnShortenEmbed: jsonb(), + pwaEnabled: boolean().default(false).notNull(), + pwaTitle: text().default('Zipline').notNull(), + pwaShortName: text().default('Zipline').notNull(), + pwaDescription: text().default('Zipline').notNull(), + pwaThemeColor: text().default('#000000').notNull(), + pwaBackgroundColor: text().default('#000000').notNull(), + websiteLoginBackgroundBlur: boolean().default(true).notNull(), + filesRandomWordsNumAdjectives: integer().default(2).notNull(), + filesRandomWordsSeparator: text().default('-').notNull(), + featuresVersionAPI: text().default('https://zipline-version.diced.sh').notNull(), + featuresVersionChecking: boolean().default(true).notNull(), + oauthDiscordAllowedIds: text().array().default(['RAY']), + oauthDiscordDeniedIds: text().array().default(['RAY']), + domains: text().array().default(['RAY']), + filesDefaultCompressionFormat: text().default('jpg'), + featuresThumbnailsFormat: text().default('jpg').notNull(), +}); + +export const metric = pgTable('Metric', { + id: text().primaryKey().notNull(), + createdAt: timestamp({ precision: 3, mode: 'string' }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: timestamp({ precision: 3, mode: 'string' }).notNull(), + data: jsonb().notNull(), +}); + +export const url = pgTable( + 'Url', + { + id: text().primaryKey().notNull(), + createdAt: timestamp({ precision: 3, mode: 'string' }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: timestamp({ precision: 3, mode: 'string' }).notNull(), + code: text().notNull(), + vanity: text(), + destination: text().notNull(), + views: integer().default(0).notNull(), + maxViews: integer(), + password: text(), + userId: text(), + enabled: boolean().default(true).notNull(), + }, + (table) => [ + uniqueIndex('Url_code_vanity_key').using( + 'btree', + table.code.asc().nullsLast().op('text_ops'), + table.vanity.asc().nullsLast().op('text_ops'), + ), + foreignKey({ + columns: [table.userId], + foreignColumns: [user.id], + name: 'Url_userId_fkey', + }) + .onUpdate('cascade') + .onDelete('set null'), + ], +); + +export const folder = pgTable( + 'Folder', + { + id: text().primaryKey().notNull(), + createdAt: timestamp({ precision: 3, mode: 'string' }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: timestamp({ precision: 3, mode: 'string' }).notNull(), + name: text().notNull(), + public: boolean().default(false).notNull(), + userId: text().notNull(), + allowUploads: boolean().default(false).notNull(), + }, + (table) => [ + foreignKey({ + columns: [table.userId], + foreignColumns: [user.id], + name: 'Folder_userId_fkey', + }) + .onUpdate('cascade') + .onDelete('cascade'), + ], +); + +export const user = pgTable( + 'User', + { + id: text().primaryKey().notNull(), + createdAt: timestamp({ precision: 3, mode: 'string' }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: timestamp({ precision: 3, mode: 'string' }).notNull(), + username: text().notNull(), + password: text(), + avatar: text(), + token: text().notNull(), + role: role().default('USER').notNull(), + view: jsonb().default({}).notNull(), + totpSecret: text(), + sessions: text().array(), + }, + (table) => [ + uniqueIndex('User_token_key').using('btree', table.token.asc().nullsLast().op('text_ops')), + uniqueIndex('User_username_key').using('btree', table.username.asc().nullsLast().op('text_ops')), + ], +); + +export const exportTable = pgTable( + 'Export', + { + id: text().primaryKey().notNull(), + createdAt: timestamp({ precision: 3, mode: 'string' }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: timestamp({ precision: 3, mode: 'string' }).notNull(), + completed: boolean().default(false).notNull(), + path: text().notNull(), + files: integer().notNull(), + size: text().notNull(), + userId: text().notNull(), + }, + (table) => [ + foreignKey({ + columns: [table.userId], + foreignColumns: [user.id], + name: 'Export_userId_fkey', + }) + .onUpdate('cascade') + .onDelete('cascade'), + ], +); + +export const userQuota = pgTable( + 'UserQuota', + { + id: text().primaryKey().notNull(), + createdAt: timestamp({ precision: 3, mode: 'string' }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: timestamp({ precision: 3, mode: 'string' }).notNull(), + filesQuota: userFilesQuota().notNull(), + maxBytes: text(), + maxFiles: integer(), + maxUrls: integer(), + userId: text(), + }, + (table) => [ + uniqueIndex('UserQuota_userId_key').using('btree', table.userId.asc().nullsLast().op('text_ops')), + foreignKey({ + columns: [table.userId], + foreignColumns: [user.id], + name: 'UserQuota_userId_fkey', + }) + .onUpdate('cascade') + .onDelete('cascade'), + ], +); + +export const userPasskey = pgTable( + 'UserPasskey', + { + id: text().primaryKey().notNull(), + createdAt: timestamp({ precision: 3, mode: 'string' }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: timestamp({ precision: 3, mode: 'string' }).notNull(), + lastUsed: timestamp({ precision: 3, mode: 'string' }), + name: text().notNull(), + reg: jsonb().notNull(), + userId: text().notNull(), + }, + (table) => [ + foreignKey({ + columns: [table.userId], + foreignColumns: [user.id], + name: 'UserPasskey_userId_fkey', + }) + .onUpdate('cascade') + .onDelete('cascade'), + ], +); + +export const oauthProvider = pgTable( + 'OAuthProvider', + { + id: text().primaryKey().notNull(), + createdAt: timestamp({ precision: 3, mode: 'string' }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: timestamp({ precision: 3, mode: 'string' }).notNull(), + userId: text().notNull(), + provider: oauthProviderType().notNull(), + username: text().notNull(), + accessToken: text().notNull(), + refreshToken: text(), + oauthId: text(), + }, + (table) => [ + uniqueIndex('OAuthProvider_provider_oauthId_key').using( + 'btree', + table.provider.asc().nullsLast().op('text_ops'), + table.oauthId.asc().nullsLast().op('text_ops'), + ), + foreignKey({ + columns: [table.userId], + foreignColumns: [user.id], + name: 'OAuthProvider_userId_fkey', + }) + .onUpdate('cascade') + .onDelete('restrict'), + ], +); + +export const file = pgTable( + 'File', + { + id: text().primaryKey().notNull(), + createdAt: timestamp({ precision: 3, mode: 'string' }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: timestamp({ precision: 3, mode: 'string' }).notNull(), + deletesAt: timestamp({ precision: 3, mode: 'string' }), + name: text().notNull(), + originalName: text(), + // You can use { mode: "bigint" } if numbers are exceeding js number limitations + size: bigint({ mode: 'number' }).notNull(), + type: text().notNull(), + views: integer().default(0).notNull(), + maxViews: integer(), + favorite: boolean().default(false).notNull(), + password: text(), + userId: text(), + folderId: text(), + }, + (table) => [ + foreignKey({ + columns: [table.userId], + foreignColumns: [user.id], + name: 'File_userId_fkey', + }) + .onUpdate('cascade') + .onDelete('set null'), + foreignKey({ + columns: [table.folderId], + foreignColumns: [folder.id], + name: 'File_folderId_fkey', + }) + .onUpdate('cascade') + .onDelete('set null'), + ], +); + +export const thumbnail = pgTable( + 'Thumbnail', + { + id: text().primaryKey().notNull(), + createdAt: timestamp({ precision: 3, mode: 'string' }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: timestamp({ precision: 3, mode: 'string' }).notNull(), + path: text().notNull(), + fileId: text().notNull(), + }, + (table) => [ + uniqueIndex('Thumbnail_fileId_key').using('btree', table.fileId.asc().nullsLast().op('text_ops')), + foreignKey({ + columns: [table.fileId], + foreignColumns: [file.id], + name: 'Thumbnail_fileId_fkey', + }) + .onUpdate('cascade') + .onDelete('cascade'), + ], +); + +export const incompleteFile = pgTable( + 'IncompleteFile', + { + id: text().primaryKey().notNull(), + createdAt: timestamp({ precision: 3, mode: 'string' }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: timestamp({ precision: 3, mode: 'string' }).notNull(), + status: incompleteFileStatus().notNull(), + chunksTotal: integer().notNull(), + chunksComplete: integer().notNull(), + metadata: jsonb().notNull(), + userId: text().notNull(), + }, + (table) => [ + foreignKey({ + columns: [table.userId], + foreignColumns: [user.id], + name: 'IncompleteFile_userId_fkey', + }) + .onUpdate('cascade') + .onDelete('cascade'), + ], +); + +export const tag = pgTable( + 'Tag', + { + id: text().primaryKey().notNull(), + createdAt: timestamp({ precision: 3, mode: 'string' }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: timestamp({ precision: 3, mode: 'string' }).notNull(), + name: text().notNull(), + color: text().notNull(), + userId: text(), + }, + (table) => [ + uniqueIndex('Tag_name_key').using('btree', table.name.asc().nullsLast().op('text_ops')), + foreignKey({ + columns: [table.userId], + foreignColumns: [user.id], + name: 'Tag_userId_fkey', + }) + .onUpdate('cascade') + .onDelete('set null'), + ], +); + +export const invite = pgTable( + 'Invite', + { + id: text().primaryKey().notNull(), + createdAt: timestamp({ precision: 3, mode: 'string' }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: timestamp({ precision: 3, mode: 'string' }).notNull(), + expiresAt: timestamp({ precision: 3, mode: 'string' }), + code: text().notNull(), + uses: integer().default(0).notNull(), + maxUses: integer(), + inviterId: text().notNull(), + }, + (table) => [ + uniqueIndex('Invite_code_key').using('btree', table.code.asc().nullsLast().op('text_ops')), + foreignKey({ + columns: [table.inviterId], + foreignColumns: [user.id], + name: 'Invite_inviterId_fkey', + }) + .onUpdate('cascade') + .onDelete('cascade'), + ], +); + +export const fileToTag = pgTable( + '_FileToTag', + { + a: text('A').notNull(), + b: text('B').notNull(), + }, + (table) => [ + index().using('btree', table.b.asc().nullsLast().op('text_ops')), + foreignKey({ + columns: [table.a], + foreignColumns: [file.id], + name: '_FileToTag_A_fkey', + }) + .onUpdate('cascade') + .onDelete('cascade'), + foreignKey({ + columns: [table.b], + foreignColumns: [tag.id], + name: '_FileToTag_B_fkey', + }) + .onUpdate('cascade') + .onDelete('cascade'), + primaryKey({ columns: [table.a, table.b], name: '_FileToTag_AB_pkey' }), + ], +); diff --git a/src/lib/db/migration/drizzle.ts b/src/lib/db/migration/drizzle.ts new file mode 100644 index 000000000..637b15d80 --- /dev/null +++ b/src/lib/db/migration/drizzle.ts @@ -0,0 +1,87 @@ +import { migrate } from 'drizzle-orm/node-postgres/migrator'; +import { drizzle } from 'drizzle-orm/node-postgres'; +import pg from 'pg'; +import { join } from 'path'; +import { log } from '@/lib/logger'; + +const logger = log('db').c('drizzle'); + +async function drizzleBootstrap(client: pg.Client) { + await client.query('CREATE SCHEMA IF NOT EXISTS "drizzle"'); + + await client.query(` + CREATE TABLE IF NOT EXISTS "drizzle"."__drizzle_migrations" ( + id SERIAL PRIMARY KEY, + hash text NOT NULL, + created_at numeric + ) + `); +} + +async function migrateExistingPrisma(client: pg.Client) { + // check if there is a _prisma_migrations table + // if there is then we continue with prisma -> drizzle. + + const resPrisma = await client.query(` + SELECT EXISTS ( + SELECT FROM information_schema.tables + WHERE table_schema = 'public' + AND table_name = '_prisma_migrations' + ) + `); + + const existsPrisma = resPrisma.rows[0]?.exists; + if (!existsPrisma) { + logger.debug('no existing prisma migrations found, skipping prisma -> drizzle migration step'); + return; + } + + logger.debug('existing prisma migrations found, migrating to drizzle'); + + // at this point, there should already be a __drizzle_migrations table + // now looking for the first migration so we can manually insert it if needed. + + const firstMigration = 1756926875085; + + const res = await client.query( + ` + SELECT COUNT(*) FROM drizzle.__drizzle_migrations WHERE created_at = $1 + `, + [firstMigration], + ); + + const count = parseInt(res.rows[0]?.count || '0', 10); + + logger.debug('finding existing first migrations', { count }); + + if (count === 0) { + logger.debug('inserting first migration manually'); + + await client.query( + ` + INSERT INTO drizzle.__drizzle_migrations (created_at, hash) + VALUES ($1, $2) + `, + [firstMigration, 'manual'], + ); + } +} + +export async function runDrizzleMigrations() { + const client = new pg.Client({ connectionString: process.env.DATABASE_URL }); + await client.connect(); + const db = drizzle(client); + + // ensure drizzle migrations table exists + await drizzleBootstrap(client); + + // migrate from prisma to drizzle + await migrateExistingPrisma(client); + + // now we can run migrations with drizzle + await migrate(db, { + migrationsFolder: join(process.cwd(), 'src', 'drizzle'), + }); + + logger.info('migrations complete'); +}