mirror of
https://github.com/diced/zipline.git
synced 2025-12-24 12:04:05 -08:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e6d28deac | ||
|
|
590e46f18e | ||
|
|
2eaee1a92e | ||
|
|
4758bd145e | ||
|
|
8487e07006 | ||
|
|
83246d6a4b | ||
|
|
9df12e141f | ||
|
|
02e25aa608 | ||
|
|
cf570af0a8 | ||
|
|
6644eac0ed | ||
|
|
a14337bdd4 | ||
|
|
ab4b9c4ac1 | ||
|
|
40f7d39426 | ||
|
|
34f27d4da4 | ||
|
|
7b2af8b8c5 |
13
drizzle.config.ts
Normal file
13
drizzle.config.ts
Normal 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,
|
||||
});
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "zipline",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"version": "4.3.2",
|
||||
"version": "4.3.1",
|
||||
"scripts": {
|
||||
"build": "tsx scripts/build.ts",
|
||||
"dev": "cross-env NODE_ENV=development DEBUG=zipline tsx --require dotenv/config --enable-source-maps ./src/server",
|
||||
@@ -23,9 +23,6 @@
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "3.726.1",
|
||||
"@aws-sdk/lib-storage": "3.726.1",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@fastify/cookie": "^11.0.2",
|
||||
"@fastify/cors": "^11.1.0",
|
||||
"@fastify/multipart": "^9.0.3",
|
||||
@@ -59,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",
|
||||
@@ -72,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",
|
||||
@@ -93,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",
|
||||
|
||||
462
pnpm-lock.yaml
generated
462
pnpm-lock.yaml
generated
@@ -14,15 +14,6 @@ importers:
|
||||
'@aws-sdk/lib-storage':
|
||||
specifier: 3.726.1
|
||||
version: 3.726.1(@aws-sdk/client-s3@3.726.1)
|
||||
'@dnd-kit/core':
|
||||
specifier: ^6.3.1
|
||||
version: 6.3.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||
'@dnd-kit/sortable':
|
||||
specifier: ^10.0.0
|
||||
version: 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)
|
||||
'@dnd-kit/utilities':
|
||||
specifier: ^3.2.2
|
||||
version: 3.2.2(react@19.1.1)
|
||||
'@fastify/cookie':
|
||||
specifier: ^11.0.2
|
||||
version: 11.0.2
|
||||
@@ -122,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
|
||||
@@ -161,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)
|
||||
@@ -219,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
|
||||
@@ -231,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)
|
||||
@@ -588,27 +591,8 @@ packages:
|
||||
resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@dnd-kit/accessibility@3.1.1':
|
||||
resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
|
||||
'@dnd-kit/core@6.3.1':
|
||||
resolution: {integrity: sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
react-dom: '>=16.8.0'
|
||||
|
||||
'@dnd-kit/sortable@10.0.0':
|
||||
resolution: {integrity: sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==}
|
||||
peerDependencies:
|
||||
'@dnd-kit/core': ^6.3.0
|
||||
react: '>=16.8.0'
|
||||
|
||||
'@dnd-kit/utilities@3.2.2':
|
||||
resolution: {integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
'@drizzle-team/brocli@0.10.2':
|
||||
resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==}
|
||||
|
||||
'@emnapi/runtime@1.4.5':
|
||||
resolution: {integrity: sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==}
|
||||
@@ -616,6 +600,14 @@ packages:
|
||||
'@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'}
|
||||
@@ -628,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'}
|
||||
@@ -640,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'}
|
||||
@@ -652,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'}
|
||||
@@ -664,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'}
|
||||
@@ -676,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'}
|
||||
@@ -688,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'}
|
||||
@@ -700,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'}
|
||||
@@ -712,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'}
|
||||
@@ -724,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'}
|
||||
@@ -736,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'}
|
||||
@@ -748,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'}
|
||||
@@ -760,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'}
|
||||
@@ -772,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'}
|
||||
@@ -784,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'}
|
||||
@@ -796,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'}
|
||||
@@ -808,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'}
|
||||
@@ -832,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'}
|
||||
@@ -856,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'}
|
||||
@@ -874,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'}
|
||||
@@ -886,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'}
|
||||
@@ -898,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'}
|
||||
@@ -910,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'}
|
||||
@@ -2044,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==}
|
||||
|
||||
@@ -2663,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'}
|
||||
@@ -2722,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'}
|
||||
@@ -4429,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'}
|
||||
@@ -5687,30 +5927,7 @@ snapshots:
|
||||
|
||||
'@csstools/css-tokenizer@3.0.4': {}
|
||||
|
||||
'@dnd-kit/accessibility@3.1.1(react@19.1.1)':
|
||||
dependencies:
|
||||
react: 19.1.1
|
||||
tslib: 2.8.1
|
||||
|
||||
'@dnd-kit/core@6.3.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
|
||||
dependencies:
|
||||
'@dnd-kit/accessibility': 3.1.1(react@19.1.1)
|
||||
'@dnd-kit/utilities': 3.2.2(react@19.1.1)
|
||||
react: 19.1.1
|
||||
react-dom: 19.1.1(react@19.1.1)
|
||||
tslib: 2.8.1
|
||||
|
||||
'@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)':
|
||||
dependencies:
|
||||
'@dnd-kit/core': 6.3.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||
'@dnd-kit/utilities': 3.2.2(react@19.1.1)
|
||||
react: 19.1.1
|
||||
tslib: 2.8.1
|
||||
|
||||
'@dnd-kit/utilities@3.2.2(react@19.1.1)':
|
||||
dependencies:
|
||||
react: 19.1.1
|
||||
tslib: 2.8.1
|
||||
'@drizzle-team/brocli@0.10.2': {}
|
||||
|
||||
'@emnapi/runtime@1.4.5':
|
||||
dependencies:
|
||||
@@ -5719,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
|
||||
|
||||
@@ -5827,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
|
||||
|
||||
@@ -5839,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
|
||||
|
||||
@@ -5848,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
|
||||
|
||||
@@ -7138,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
|
||||
@@ -7773,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
|
||||
@@ -7897,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
|
||||
@@ -10047,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
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "public"."Zipline" ADD COLUMN "coreTrustProxy" BOOLEAN NOT NULL DEFAULT false;
|
||||
@@ -20,7 +20,6 @@ model Zipline {
|
||||
coreReturnHttpsUrls Boolean @default(false)
|
||||
coreDefaultDomain String?
|
||||
coreTempDirectory String // default join(tmpdir(), 'zipline')
|
||||
coreTrustProxy Boolean @default(false)
|
||||
|
||||
chunksEnabled Boolean @default(true)
|
||||
chunksMax String @default("95mb")
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
import { FieldSettings, useFileTableSettingsStore } from '@/lib/store/fileTableSettings';
|
||||
import {
|
||||
closestCenter,
|
||||
DndContext,
|
||||
DragEndEvent,
|
||||
KeyboardSensor,
|
||||
PointerSensor,
|
||||
useSensor,
|
||||
useSensors,
|
||||
} from '@dnd-kit/core';
|
||||
import { SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import { Button, Checkbox, Group, Modal, Paper, Text } from '@mantine/core';
|
||||
import { IconGripVertical } from '@tabler/icons-react';
|
||||
import { useShallow } from 'zustand/shallow';
|
||||
|
||||
export const NAMES = {
|
||||
name: 'Name',
|
||||
originalName: 'Original Name',
|
||||
tags: 'Tags',
|
||||
type: 'Type',
|
||||
size: 'Size',
|
||||
createdAt: 'Created At',
|
||||
favorite: 'Favorite',
|
||||
views: 'Views',
|
||||
};
|
||||
|
||||
function SortableTableField({ item }: { item: FieldSettings }) {
|
||||
const setVisible = useFileTableSettingsStore((state) => state.setVisible);
|
||||
|
||||
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
||||
id: item.field,
|
||||
});
|
||||
|
||||
const style: React.CSSProperties = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
opacity: isDragging ? 0.5 : 1,
|
||||
cursor: 'grab',
|
||||
width: '100%',
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper withBorder p='xs' ref={setNodeRef} style={style} {...attributes} {...listeners}>
|
||||
<Group gap='xs'>
|
||||
<IconGripVertical size='1rem' />
|
||||
|
||||
<Checkbox checked={item.visible} onChange={() => setVisible(item.field, !item.visible)} />
|
||||
|
||||
<Text>{NAMES[item.field]}</Text>
|
||||
</Group>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
export default function TableEditModal({ opened, onCLose }: { opened: boolean; onCLose: () => void }) {
|
||||
const [fields, setIndex, reset] = useFileTableSettingsStore(
|
||||
useShallow((state) => [state.fields, state.setIndex, state.reset]),
|
||||
);
|
||||
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
|
||||
useSensor(KeyboardSensor),
|
||||
);
|
||||
|
||||
const handleDragEnd = (event: DragEndEvent) => {
|
||||
const { active, over } = event;
|
||||
if (active.id !== over?.id) {
|
||||
const newIndex = fields.findIndex((item) => item.field === over?.id);
|
||||
|
||||
setIndex(active.id as FieldSettings['field'], newIndex);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal opened={opened} onClose={onCLose} title='Table Options' centered>
|
||||
<Text mb='md' size='sm' c='dimmed'>
|
||||
Select and drag fields below to make them appear/disappear/reorder in the file table view.
|
||||
</Text>
|
||||
|
||||
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
|
||||
<SortableContext items={fields.map((item) => item.field)} strategy={verticalListSortingStrategy}>
|
||||
{fields.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px' }}
|
||||
>
|
||||
<SortableTableField item={item} />
|
||||
</div>
|
||||
))}
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
|
||||
<Button fullWidth color='red' onClick={() => reset()} variant='light' mt='md'>
|
||||
Reset to Default
|
||||
</Button>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -5,8 +5,6 @@ import { bytes } from '@/lib/bytes';
|
||||
import { type File } from '@/lib/db/models/file';
|
||||
import { Folder } from '@/lib/db/models/folder';
|
||||
import { Tag } from '@/lib/db/models/tag';
|
||||
import { useQueryState } from '@/lib/hooks/useQueryState';
|
||||
import { useFileTableSettingsStore } from '@/lib/store/fileTableSettings';
|
||||
import { useSettingsStore } from '@/lib/store/settings';
|
||||
import {
|
||||
ActionIcon,
|
||||
@@ -36,17 +34,16 @@ import {
|
||||
IconFile,
|
||||
IconGridPatternFilled,
|
||||
IconStar,
|
||||
IconTableOptions,
|
||||
IconTrashFilled,
|
||||
} from '@tabler/icons-react';
|
||||
import { DataTable } from 'mantine-datatable';
|
||||
import { lazy, useEffect, useReducer, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import useSWR from 'swr';
|
||||
import TableEditModal, { NAMES } from '../TableEditModal';
|
||||
import { bulkDelete, bulkFavorite } from '../bulk';
|
||||
import TagPill from '../tags/TagPill';
|
||||
import { useApiPagination } from '../useApiPagination';
|
||||
import { useQueryState } from '@/lib/hooks/useQueryState';
|
||||
|
||||
const FileModal = lazy(() => import('@/components/file/DashboardFile/FileModal'));
|
||||
|
||||
@@ -57,6 +54,13 @@ type ReducerQuery = {
|
||||
|
||||
const PER_PAGE_OPTIONS = [10, 20, 50];
|
||||
|
||||
const NAMES = {
|
||||
name: 'Name',
|
||||
originalName: 'Original name',
|
||||
type: 'Type',
|
||||
id: 'ID',
|
||||
};
|
||||
|
||||
function SearchFilter({
|
||||
setSearchField,
|
||||
searchQuery,
|
||||
@@ -84,8 +88,8 @@ function SearchFilter({
|
||||
|
||||
return (
|
||||
<TextInput
|
||||
label={NAMES[field as keyof typeof NAMES]}
|
||||
placeholder={`Search by ${NAMES[field as keyof typeof NAMES].toLowerCase()}`}
|
||||
label={NAMES[field]}
|
||||
placeholder={`Search by ${NAMES[field].toLowerCase()}`}
|
||||
value={searchQuery[field]}
|
||||
onChange={onChange}
|
||||
size='sm'
|
||||
@@ -179,10 +183,6 @@ export default function FileTable({ id }: { id?: string }) {
|
||||
const clipboard = useClipboard();
|
||||
const warnDeletion = useSettingsStore((state) => state.settings.warnDeletion);
|
||||
|
||||
const [tableEditOpen, setTableEditOpen] = useState(false);
|
||||
|
||||
const fields = useFileTableSettingsStore((state) => state.fields);
|
||||
|
||||
const { data: folders } = useSWR<Extract<Response['/api/user/folders'], Folder[]>>(
|
||||
'/api/user/folders?noincl=true',
|
||||
);
|
||||
@@ -264,100 +264,6 @@ export default function FileTable({ id }: { id?: string }) {
|
||||
}),
|
||||
});
|
||||
|
||||
const FIELDS = [
|
||||
{
|
||||
accessor: 'name',
|
||||
sortable: true,
|
||||
filter: (
|
||||
<SearchFilter
|
||||
setSearchField={setSearchField}
|
||||
searchQuery={searchQuery}
|
||||
setSearchQuery={setSearchQuery}
|
||||
field='name'
|
||||
/>
|
||||
),
|
||||
filtering: searchField === 'name' && searchQuery.name.trim() !== '',
|
||||
},
|
||||
{
|
||||
accessor: 'originalName',
|
||||
sortable: true,
|
||||
filter: (
|
||||
<SearchFilter
|
||||
setSearchField={setSearchField}
|
||||
searchQuery={searchQuery}
|
||||
setSearchQuery={setSearchQuery}
|
||||
field='originalName'
|
||||
/>
|
||||
),
|
||||
filtering: searchField === 'originalName' && searchQuery.originalName.trim() !== '',
|
||||
},
|
||||
{
|
||||
accessor: 'tags',
|
||||
sortable: false,
|
||||
width: 200,
|
||||
render: (file: File) => (
|
||||
<ScrollArea w={180} onClick={(e) => e.stopPropagation()}>
|
||||
<Flex gap='sm'>
|
||||
{file.tags!.map((tag) => (
|
||||
<TagPill tag={tag} key={tag.id} />
|
||||
))}
|
||||
</Flex>
|
||||
</ScrollArea>
|
||||
),
|
||||
filter: (
|
||||
<TagsFilter
|
||||
setSearchField={setSearchField}
|
||||
searchQuery={searchQuery}
|
||||
setSearchQuery={setSearchQuery}
|
||||
/>
|
||||
),
|
||||
filtering: searchField === 'tags' && searchQuery.tags.trim() !== '',
|
||||
},
|
||||
{
|
||||
accessor: 'type',
|
||||
sortable: true,
|
||||
filter: (
|
||||
<SearchFilter
|
||||
setSearchField={setSearchField}
|
||||
searchQuery={searchQuery}
|
||||
setSearchQuery={setSearchQuery}
|
||||
field='type'
|
||||
/>
|
||||
),
|
||||
filtering: searchField === 'type' && searchQuery.type.trim() !== '',
|
||||
},
|
||||
{ accessor: 'size', sortable: true, render: (file: File) => bytes(file.size) },
|
||||
{
|
||||
accessor: 'createdAt',
|
||||
sortable: true,
|
||||
render: (file: File) => <RelativeDate date={file.createdAt} />,
|
||||
},
|
||||
{
|
||||
accessor: 'favorite',
|
||||
sortable: true,
|
||||
render: (file: File) => (file.favorite ? <Text c='yellow'>Yes</Text> : 'No'),
|
||||
},
|
||||
{
|
||||
accessor: 'views',
|
||||
sortable: true,
|
||||
render: (file: File) => file.views,
|
||||
},
|
||||
{
|
||||
accessor: 'id',
|
||||
hidden: searchField !== 'id' || searchQuery.id.trim() === '',
|
||||
filtering: searchField === 'id' && searchQuery.id.trim() !== '',
|
||||
},
|
||||
];
|
||||
|
||||
const visibleFields = fields.filter((f) => f.visible).map((f) => f.field);
|
||||
const columns = FIELDS.filter((f) => visibleFields.includes(f.accessor as any));
|
||||
columns.sort((a, b) => {
|
||||
const aIndex = fields.findIndex((f) => f.field === a.accessor);
|
||||
const bIndex = fields.findIndex((f) => f.field === b.accessor);
|
||||
|
||||
return aIndex - bIndex;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (data && selectedFile) {
|
||||
const file = data.page.find((x) => x.id === selectedFile.id);
|
||||
@@ -389,32 +295,19 @@ export default function FileTable({ id }: { id?: string }) {
|
||||
file={selectedFile}
|
||||
/>
|
||||
|
||||
<TableEditModal opened={tableEditOpen} onCLose={() => setTableEditOpen(false)} />
|
||||
|
||||
<Box>
|
||||
<Group>
|
||||
<Tooltip label='Table Options'>
|
||||
<ActionIcon
|
||||
variant='outline'
|
||||
onClick={() => setTableEditOpen((open) => !open)}
|
||||
style={{ position: 'relative', top: '-36.4px', left: '221px', margin: 0 }}
|
||||
>
|
||||
<IconTableOptions size='1rem' />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label='Search by ID'>
|
||||
<ActionIcon
|
||||
variant='outline'
|
||||
onClick={() => {
|
||||
setIdSearchOpen((open) => !open);
|
||||
}}
|
||||
// lol if it works it works :shrug:
|
||||
style={{ position: 'relative', top: '-36.4px', left: '221px', margin: 0 }}
|
||||
>
|
||||
<IconGridPatternFilled size='1rem' />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
<Tooltip label='Search by ID'>
|
||||
<ActionIcon
|
||||
variant='outline'
|
||||
onClick={() => {
|
||||
setIdSearchOpen((open) => !open);
|
||||
}}
|
||||
// lol if it works it works :shrug:
|
||||
style={{ position: 'relative', top: '-36.4px', left: '221px', margin: 0 }}
|
||||
>
|
||||
<IconGridPatternFilled size='1rem' />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
|
||||
<Collapse in={selectedFiles.length > 0}>
|
||||
<Paper withBorder p='sm' my='sm'>
|
||||
@@ -524,7 +417,75 @@ export default function FileTable({ id }: { id?: string }) {
|
||||
minHeight={200}
|
||||
records={data?.page ?? []}
|
||||
columns={[
|
||||
...columns,
|
||||
{
|
||||
accessor: 'name',
|
||||
sortable: true,
|
||||
filter: (
|
||||
<SearchFilter
|
||||
setSearchField={setSearchField}
|
||||
searchQuery={searchQuery}
|
||||
setSearchQuery={setSearchQuery}
|
||||
field='name'
|
||||
/>
|
||||
),
|
||||
filtering: searchField === 'name' && searchQuery.name.trim() !== '',
|
||||
},
|
||||
{
|
||||
accessor: 'tags',
|
||||
sortable: false,
|
||||
width: 200,
|
||||
render: (file) => (
|
||||
<ScrollArea w={180} onClick={(e) => e.stopPropagation()}>
|
||||
<Flex gap='sm'>
|
||||
{file.tags!.map((tag) => (
|
||||
<TagPill tag={tag} key={tag.id} />
|
||||
))}
|
||||
</Flex>
|
||||
</ScrollArea>
|
||||
),
|
||||
filter: (
|
||||
<TagsFilter
|
||||
setSearchField={setSearchField}
|
||||
searchQuery={searchQuery}
|
||||
setSearchQuery={setSearchQuery}
|
||||
/>
|
||||
),
|
||||
filtering: searchField === 'tags' && searchQuery.tags.trim() !== '',
|
||||
},
|
||||
{
|
||||
accessor: 'type',
|
||||
sortable: true,
|
||||
filter: (
|
||||
<SearchFilter
|
||||
setSearchField={setSearchField}
|
||||
searchQuery={searchQuery}
|
||||
setSearchQuery={setSearchQuery}
|
||||
field='type'
|
||||
/>
|
||||
),
|
||||
filtering: searchField === 'type' && searchQuery.type.trim() !== '',
|
||||
},
|
||||
{ accessor: 'size', sortable: true, render: (file) => bytes(file.size) },
|
||||
{
|
||||
accessor: 'createdAt',
|
||||
sortable: true,
|
||||
render: (file) => <RelativeDate date={file.createdAt} />,
|
||||
},
|
||||
{
|
||||
accessor: 'favorite',
|
||||
sortable: true,
|
||||
render: (file) => (file.favorite ? <Text c='yellow'>Yes</Text> : 'No'),
|
||||
},
|
||||
{
|
||||
accessor: 'views',
|
||||
sortable: true,
|
||||
render: (file) => file.views,
|
||||
},
|
||||
{
|
||||
accessor: 'id',
|
||||
hidden: searchField !== 'id' || searchQuery.id.trim() === '',
|
||||
filtering: searchField === 'id' && searchQuery.id.trim() !== '',
|
||||
},
|
||||
{
|
||||
accessor: 'actions',
|
||||
textAlign: 'right',
|
||||
|
||||
@@ -17,13 +17,11 @@ export default function Core({
|
||||
coreReturnHttpsUrls: boolean;
|
||||
coreDefaultDomain: string | null | undefined;
|
||||
coreTempDirectory: string;
|
||||
coreTrustProxy: boolean;
|
||||
}>({
|
||||
initialValues: {
|
||||
coreReturnHttpsUrls: false,
|
||||
coreDefaultDomain: '',
|
||||
coreTempDirectory: '/tmp/zipline',
|
||||
coreTrustProxy: false,
|
||||
},
|
||||
enhanceGetInputProps: (payload) => ({
|
||||
disabled: data?.tampered?.includes(payload.field) || false,
|
||||
@@ -47,7 +45,6 @@ export default function Core({
|
||||
coreReturnHttpsUrls: data.settings.coreReturnHttpsUrls ?? false,
|
||||
coreDefaultDomain: data.settings.coreDefaultDomain ?? '',
|
||||
coreTempDirectory: data.settings.coreTempDirectory ?? '/tmp/zipline',
|
||||
coreTrustProxy: data.settings.coreTrustProxy ?? false,
|
||||
});
|
||||
}, [data]);
|
||||
|
||||
@@ -58,20 +55,14 @@ export default function Core({
|
||||
<Title order={2}>Core</Title>
|
||||
|
||||
<form onSubmit={form.onSubmit(onSubmit)}>
|
||||
<Switch
|
||||
mt='md'
|
||||
label='Return HTTPS URLs'
|
||||
description='Return URLs with HTTPS protocol.'
|
||||
{...form.getInputProps('coreReturnHttpsUrls', { type: 'checkbox' })}
|
||||
/>
|
||||
|
||||
<SimpleGrid mt='md' cols={{ base: 1, md: 2 }} spacing='lg'>
|
||||
<Switch
|
||||
mt='md'
|
||||
label='Return HTTPS URLs'
|
||||
description='Return URLs with HTTPS protocol.'
|
||||
{...form.getInputProps('coreReturnHttpsUrls', { type: 'checkbox' })}
|
||||
/>
|
||||
|
||||
<Switch
|
||||
label='Trust Proxies'
|
||||
description='Trust the X-Forwarded-* headers set by proxies. Only enable this if you are behind a trusted proxy (nginx, caddy, etc.). Requires a server restart.'
|
||||
{...form.getInputProps('coreTrustProxy', { type: 'checkbox' })}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label='Default Domain'
|
||||
description='The domain to use when generating URLs. This value should not include the protocol.'
|
||||
|
||||
287
src/drizzle/0000_bouncy_mantis.sql
Normal file
287
src/drizzle/0000_bouncy_mantis.sql
Normal 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);
|
||||
2037
src/drizzle/meta/0000_snapshot.json
Normal file
2037
src/drizzle/meta/0000_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
20
src/drizzle/meta/_journal.json
Normal file
20
src/drizzle/meta/_journal.json
Normal 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
125
src/drizzle/relations.ts
Normal 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
497
src/drizzle/schema.ts
Normal 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' }),
|
||||
],
|
||||
);
|
||||
@@ -6,7 +6,6 @@ export const DATABASE_TO_PROP = {
|
||||
coreReturnHttpsUrls: 'core.returnHttpsUrls',
|
||||
coreDefaultDomain: 'core.defaultDomain',
|
||||
coreTempDirectory: 'core.tempDirectory',
|
||||
coreTrustProxy: 'core.trustProxy',
|
||||
|
||||
chunksMax: 'chunks.max',
|
||||
chunksSize: 'chunks.size',
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { log } from '@/lib/logger';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { parse } from './transform';
|
||||
|
||||
export type EnvType = 'string' | 'string[]' | 'number' | 'boolean' | 'byte' | 'ms' | 'json';
|
||||
export function env(property: string, env: string, type: EnvType, isDb: boolean = false) {
|
||||
export function env(property: string, env: string | string[], type: EnvType, isDb: boolean = false) {
|
||||
return {
|
||||
variable: env,
|
||||
property,
|
||||
@@ -16,14 +15,7 @@ export const ENVS = [
|
||||
env('core.port', 'CORE_PORT', 'number'),
|
||||
env('core.hostname', 'CORE_HOSTNAME', 'string'),
|
||||
env('core.secret', 'CORE_SECRET', 'string'),
|
||||
|
||||
env('core.databaseUrl', 'DATABASE_URL', 'string'),
|
||||
// or
|
||||
env('core.database.username', 'DATABASE_USERNAME', 'string', true),
|
||||
env('core.database.password', 'DATABASE_PASSWORD', 'string', true),
|
||||
env('core.database.host', 'DATABASE_HOST', 'string', true),
|
||||
env('core.database.port', 'DATABASE_PORT', 'number', true),
|
||||
env('core.database.name', 'DATABASE_NAME', 'string', true),
|
||||
env('core.databaseUrl', ['DATABASE_URL', 'CORE_DATABASE_URL'], 'string'),
|
||||
|
||||
env('datasource.type', 'DATASOURCE_TYPE', 'string'),
|
||||
env('datasource.s3.accessKeyId', 'DATASOURCE_S3_ACCESS_KEY_ID', 'string'),
|
||||
@@ -40,7 +32,6 @@ export const ENVS = [
|
||||
env('ssl.cert', 'SSL_CERT', 'string'),
|
||||
|
||||
// database stuff
|
||||
env('core.trustProxy', 'CORE_TRUST_PROXY', 'boolean', true),
|
||||
env('core.returnHttpsUrls', 'CORE_RETURN_HTTPS_URLS', 'boolean', true),
|
||||
env('core.defaultDomain', 'CORE_DEFAULT_DOMAIN', 'string', true),
|
||||
env('core.tempDirectory', 'CORE_TEMP_DIRECTORY', 'string', true),
|
||||
@@ -168,62 +159,11 @@ export const PROP_TO_ENV: Record<string, string | string[]> = Object.fromEntries
|
||||
ENVS.map((env) => [env.property, env.variable]),
|
||||
);
|
||||
|
||||
export const REQUIRED_DB_VARS = [
|
||||
'DATABASE_USERNAME',
|
||||
'DATABASE_PASSWORD',
|
||||
'DATABASE_HOST',
|
||||
'DATABASE_PORT',
|
||||
'DATABASE_NAME',
|
||||
];
|
||||
|
||||
type EnvResult = {
|
||||
env: Record<string, any>;
|
||||
dbEnv: Record<string, any>;
|
||||
};
|
||||
|
||||
export function checkDbVars(): boolean {
|
||||
if (process.env.DATABASE_URL) return true;
|
||||
|
||||
for (let i = 0; i !== REQUIRED_DB_VARS.length; ++i) {
|
||||
if (process.env[REQUIRED_DB_VARS[i]] === undefined) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function readDbVars(): Record<string, string> {
|
||||
const logger = log('config').c('readDbVars');
|
||||
|
||||
if (process.env.DATABASE_URL) return { DATABASE_URL: process.env.DATABASE_URL };
|
||||
|
||||
const dbVars: Record<string, string> = {};
|
||||
for (let i = 0; i !== REQUIRED_DB_VARS.length; ++i) {
|
||||
const value = process.env[REQUIRED_DB_VARS[i]];
|
||||
const valueFileName = process.env[`${REQUIRED_DB_VARS[i]}_FILE`];
|
||||
if (valueFileName) {
|
||||
try {
|
||||
dbVars[REQUIRED_DB_VARS[i]] = readFileSync(valueFileName, 'utf-8').trim();
|
||||
} catch {
|
||||
logger.error(`Failed to read database env value from file for ${REQUIRED_DB_VARS[i]}. Exiting...`);
|
||||
process.exit(1);
|
||||
}
|
||||
} else if (value) {
|
||||
dbVars[REQUIRED_DB_VARS[i]] = value;
|
||||
}
|
||||
}
|
||||
|
||||
if (!Object.keys(dbVars).length || Object.keys(dbVars).length !== REQUIRED_DB_VARS.length) {
|
||||
logger.error(
|
||||
`No database environment variables found (DATABASE_URL or all of [${REQUIRED_DB_VARS.join(', ')}]), exiting...`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
return dbVars;
|
||||
}
|
||||
|
||||
export function readEnv(): EnvResult {
|
||||
const logger = log('config').c('readEnv');
|
||||
const envResult: EnvResult = {
|
||||
@@ -233,19 +173,12 @@ export function readEnv(): EnvResult {
|
||||
|
||||
for (let i = 0; i !== ENVS.length; ++i) {
|
||||
const env = ENVS[i];
|
||||
|
||||
let value = process.env[env.variable];
|
||||
const valueFileName = process.env[`${env.variable}_FILE`];
|
||||
if (valueFileName) {
|
||||
try {
|
||||
value = readFileSync(valueFileName, 'utf-8').trim();
|
||||
logger.debug('Using env value from file', { variable: env.variable, file: valueFileName });
|
||||
} catch (e) {
|
||||
logger.error(`Failed to read env value from file for ${env.variable}. Skipping...`).error(e as Error);
|
||||
continue;
|
||||
}
|
||||
if (Array.isArray(env.variable)) {
|
||||
env.variable = env.variable.find((v) => process.env[v] !== undefined) || 'DATABASE_URL';
|
||||
}
|
||||
|
||||
const value = process.env[env.variable];
|
||||
|
||||
if (value === undefined) continue;
|
||||
|
||||
if (env.variable === 'DATASOURCE_TYPE') {
|
||||
|
||||
@@ -13,14 +13,6 @@ export const rawConfig: any = {
|
||||
databaseUrl: undefined,
|
||||
returnHttpsUrls: undefined,
|
||||
tempDirectory: undefined,
|
||||
trustProxy: undefined,
|
||||
database: {
|
||||
username: undefined,
|
||||
password: undefined,
|
||||
host: undefined,
|
||||
port: undefined,
|
||||
name: undefined,
|
||||
},
|
||||
},
|
||||
chunks: {
|
||||
max: undefined,
|
||||
|
||||
@@ -67,36 +67,13 @@ export const schema = z.object({
|
||||
});
|
||||
}
|
||||
}),
|
||||
databaseUrl: z.url(),
|
||||
returnHttpsUrls: z.boolean().default(false),
|
||||
defaultDomain: z.string().nullable().default(null),
|
||||
tempDirectory: z
|
||||
.string()
|
||||
.transform((s) => resolve(s))
|
||||
.default(join(tmpdir(), 'zipline')),
|
||||
trustProxy: z.boolean().default(false),
|
||||
|
||||
databaseUrl: z.url(),
|
||||
|
||||
database: z
|
||||
.object({
|
||||
username: z.string().nullable().default(null),
|
||||
password: z.string().nullable().default(null),
|
||||
host: z.string().nullable().default(null),
|
||||
port: z.number().nullable().default(null),
|
||||
name: z.string().nullable().default(null),
|
||||
})
|
||||
.superRefine((val, c) => {
|
||||
const values = Object.values(val);
|
||||
const someSet = values.some((v) => v !== null);
|
||||
const allSet = values.every((v) => v !== null);
|
||||
|
||||
if (someSet && !allSet) {
|
||||
c.addIssue({
|
||||
code: 'custom',
|
||||
message: 'If one database field is set, all fields must be set',
|
||||
});
|
||||
}
|
||||
}),
|
||||
}),
|
||||
chunks: z.object({
|
||||
max: z.string().default('95mb'),
|
||||
|
||||
@@ -4,7 +4,6 @@ import { type Prisma, PrismaClient } from '@/prisma/client';
|
||||
import { metadataSchema } from './models/incompleteFile';
|
||||
import { metricDataSchema } from './models/metric';
|
||||
import { userViewSchema } from './models/user';
|
||||
import { readDbVars, REQUIRED_DB_VARS } from '../config/read/env';
|
||||
|
||||
const building = !!process.env.ZIPLINE_BUILD;
|
||||
|
||||
@@ -32,27 +31,12 @@ function parseDbLog(env: string): Prisma.LogLevel[] {
|
||||
.filter((v) => v) as unknown as Prisma.LogLevel[];
|
||||
}
|
||||
|
||||
function pgConnectionString() {
|
||||
const vars = readDbVars();
|
||||
if (vars.DATABASE_URL) return vars.DATABASE_URL;
|
||||
|
||||
return `postgresql://${vars.DATABASE_USERNAME}:${vars.DATABASE_PASSWORD}@${vars.DATABASE_HOST}:${vars.DATABASE_PORT}/${vars.DATABASE_NAME}`;
|
||||
}
|
||||
|
||||
function getClient() {
|
||||
const logger = log('db');
|
||||
|
||||
const connectionString = pgConnectionString();
|
||||
if (!connectionString) {
|
||||
logger.error(`either DATABASE_URL or all of [${REQUIRED_DB_VARS.join(', ')}] not set, exiting...`);
|
||||
process.exit(1);
|
||||
}
|
||||
logger.info('connecting to database ' + process.env.DATABASE_URL);
|
||||
|
||||
process.env.DATABASE_URL = connectionString;
|
||||
|
||||
logger.info('connecting to database', { url: connectionString });
|
||||
|
||||
const adapter = new PrismaPg({ connectionString });
|
||||
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL });
|
||||
const client = new PrismaClient({
|
||||
adapter,
|
||||
log: process.env.ZIPLINE_DB_LOG ? parseDbLog(process.env.ZIPLINE_DB_LOG) : undefined,
|
||||
|
||||
87
src/lib/db/migration/drizzle.ts
Normal file
87
src/lib/db/migration/drizzle.ts
Normal 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');
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
const FIELDS = ['name', 'originalName', 'tags', 'type', 'size', 'createdAt', 'favorite', 'views'] as const;
|
||||
|
||||
export const defaultFields: FieldSettings[] = [
|
||||
{ field: 'name', visible: true },
|
||||
{ field: 'originalName', visible: false },
|
||||
{ field: 'tags', visible: true },
|
||||
{ field: 'type', visible: true },
|
||||
{ field: 'size', visible: true },
|
||||
{ field: 'createdAt', visible: true },
|
||||
{ field: 'favorite', visible: true },
|
||||
{ field: 'views', visible: true },
|
||||
];
|
||||
|
||||
export type FieldSettings = {
|
||||
field: (typeof FIELDS)[number];
|
||||
visible: boolean;
|
||||
};
|
||||
|
||||
export type FileTableSettings = {
|
||||
fields: FieldSettings[];
|
||||
|
||||
setVisible: (field: FieldSettings['field'], visible: boolean) => void;
|
||||
setIndex: (field: FieldSettings['field'], index: number) => void;
|
||||
reset: () => void;
|
||||
};
|
||||
|
||||
export const useFileTableSettingsStore = create<FileTableSettings>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
fields: defaultFields,
|
||||
|
||||
setVisible: (field, visible) =>
|
||||
set((state) => ({
|
||||
fields: state.fields.map((f) => (f.field === field ? { ...f, visible } : f)),
|
||||
})),
|
||||
|
||||
setIndex: (field, index) =>
|
||||
set((state) => {
|
||||
const currentIndex = state.fields.findIndex((f) => f.field === field);
|
||||
if (currentIndex === -1 || index < 0 || index >= state.fields.length) return state;
|
||||
|
||||
const newFields = [...state.fields];
|
||||
const [movedField] = newFields.splice(currentIndex, 1);
|
||||
newFields.splice(index, 0, movedField);
|
||||
|
||||
return { fields: newFields };
|
||||
}),
|
||||
|
||||
reset: () => set({ fields: defaultFields }),
|
||||
}),
|
||||
{
|
||||
name: 'zipline-file-table-settings',
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -1,6 +1,5 @@
|
||||
import { bytes } from '@/lib/bytes';
|
||||
import { reloadSettings } from '@/lib/config';
|
||||
import { checkDbVars, REQUIRED_DB_VARS } from '@/lib/config/read/env';
|
||||
import { getDatasource } from '@/lib/datasource';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { runMigrations } from '@/lib/db/migration';
|
||||
@@ -47,8 +46,8 @@ async function main() {
|
||||
const argv = process.argv.slice(2);
|
||||
logger.info('starting zipline', { mode: MODE, version: version, argv });
|
||||
|
||||
if (!checkDbVars()) {
|
||||
logger.error(`either DATABASE_URL or all of [${REQUIRED_DB_VARS.join(', ')}] not set, exiting...`);
|
||||
if (!process.env.DATABASE_URL) {
|
||||
logger.error('DATABASE_URL not set, exiting...');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -66,13 +65,6 @@ async function main() {
|
||||
|
||||
await mkdir(config.core.tempDirectory, { recursive: true });
|
||||
|
||||
logger.debug('creating server', {
|
||||
port: config.core.port,
|
||||
hostname: config.core.hostname,
|
||||
ssl: notNull(config.ssl.key, config.ssl.cert),
|
||||
trustProxy: config.core.trustProxy,
|
||||
});
|
||||
|
||||
const server = fastify({
|
||||
https: notNull(config.ssl.key, config.ssl.cert)
|
||||
? {
|
||||
@@ -80,7 +72,6 @@ async function main() {
|
||||
cert: await readFile(config.ssl.cert!, 'utf8'),
|
||||
}
|
||||
: null,
|
||||
trustProxy: config.core.trustProxy,
|
||||
});
|
||||
|
||||
await server.register(fastifyCookie, {
|
||||
|
||||
@@ -118,7 +118,6 @@ export default fastifyPlugin(
|
||||
.nullable()
|
||||
.refine((value) => !value || /^[a-z0-9-.]+$/.test(value), 'Invalid domain format'),
|
||||
coreReturnHttpsUrls: z.boolean(),
|
||||
coreTrustProxy: z.boolean(),
|
||||
|
||||
chunksEnabled: z.boolean(),
|
||||
chunksMax: zBytes,
|
||||
|
||||
@@ -41,7 +41,6 @@ export const getExtension = (filename: string, override?: string): string => {
|
||||
export type ApiUploadResponse = {
|
||||
files: {
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
url: string;
|
||||
pending?: boolean;
|
||||
@@ -213,7 +212,6 @@ export default fastifyPlugin(
|
||||
|
||||
response.files.push({
|
||||
id: fileUpload.id,
|
||||
name: fileUpload.name,
|
||||
type: fileUpload.type,
|
||||
url: encodeURI(responseUrl),
|
||||
removedGps: removedGps || undefined,
|
||||
|
||||
@@ -7,13 +7,13 @@ import { guess } from '@/lib/mimes';
|
||||
import { randomCharacters } from '@/lib/random';
|
||||
import { formatFileName } from '@/lib/uploader/formatFileName';
|
||||
import { UploadHeaders, UploadOptions, parseHeaders } from '@/lib/uploader/parseHeaders';
|
||||
import { Prisma } from '@/prisma/client';
|
||||
import { userMiddleware } from '@/server/middleware/user';
|
||||
import fastifyPlugin from 'fastify-plugin';
|
||||
import { readdir, rename, rm } from 'fs/promises';
|
||||
import { join } from 'path';
|
||||
import { Worker } from 'worker_threads';
|
||||
import { ApiUploadResponse, getExtension } from '.';
|
||||
import { Prisma } from '@/prisma/client';
|
||||
|
||||
const logger = log('api').c('upload').c('partial');
|
||||
|
||||
@@ -256,7 +256,6 @@ export default fastifyPlugin(
|
||||
|
||||
response.files.push({
|
||||
id: fileUpload.id,
|
||||
name: fileUpload.name,
|
||||
type: fileUpload.type,
|
||||
url: responseUrl,
|
||||
pending: true,
|
||||
|
||||
Reference in New Issue
Block a user