Compare commits

...

15 Commits

Author SHA1 Message Date
dicedtomato
5e6d28deac Merge branch 'trunk' into drizzle 2025-09-19 20:29:44 -07:00
dicedtomato
590e46f18e Merge branch 'trunk' into drizzle 2025-09-18 12:42:29 -07:00
dicedtomato
2eaee1a92e Merge branch 'trunk' into drizzle 2025-09-18 12:35:59 -07:00
diced
4758bd145e fix: accidental force push lmaoo (#875)
PR: #875
2025-09-18 12:35:00 -07:00
dicedtomato
8487e07006 Merge branch 'trunk' into drizzle 2025-09-09 17:06:05 -07:00
TheShadowEevee
83246d6a4b fix: increase TLD length regex (#886)
30 matches the Second Level Domain length limit. This should allow for TLDs longer than 2 or 3 characters, such as .solutions

Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>
2025-09-09 16:50:46 -07:00
dicedtomato
9df12e141f Merge branch 'trunk' into drizzle 2025-09-08 23:08:50 -07:00
dicedtomato
02e25aa608 Merge branch 'trunk' into drizzle 2025-09-08 23:07:03 -07:00
dicedtomato
cf570af0a8 Merge branch 'trunk' into drizzle 2025-09-08 15:59:39 -07:00
dicedtomato
6644eac0ed Merge branch 'trunk' into drizzle 2025-09-08 12:13:09 -07:00
diced
a14337bdd4 fix: lock resolution 2025-09-06 12:56:58 -07:00
dicedtomato
ab4b9c4ac1 Merge branch 'trunk' into drizzle 2025-09-06 12:55:57 -07:00
dicedtomato
40f7d39426 Merge branch 'trunk' into drizzle 2025-09-05 21:07:54 -07:00
diced
34f27d4da4 feat: add drizzle packages 2025-09-05 19:41:47 -07:00
diced
7b2af8b8c5 feat: initial drizzle setup 2025-09-04 22:05:21 -07:00
9 changed files with 3480 additions and 0 deletions

13
drizzle.config.ts Normal file
View File

@@ -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,
});

View File

@@ -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",

410
pnpm-lock.yaml generated
View File

@@ -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

View File

@@ -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);

File diff suppressed because it is too large Load Diff

View File

@@ -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
}
]
}

125
src/drizzle/relations.ts Normal file
View File

@@ -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],
}),
}));

497
src/drizzle/schema.ts Normal file
View File

@@ -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' }),
],
);

View File

@@ -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');
}