mirror of
https://github.com/diced/zipline.git
synced 2025-12-24 12:04:05 -08:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe50bebeba | ||
|
|
1f61c56f83 | ||
|
|
cabf932ca0 | ||
|
|
f6b995c28d | ||
|
|
13a19ccd2b | ||
|
|
d1dea0cd92 | ||
|
|
b39507b9a8 | ||
|
|
633dfd4712 | ||
|
|
e6ed7a36d5 | ||
|
|
93cb9eec4c | ||
|
|
4849cd8221 | ||
|
|
89c58044a3 | ||
|
|
40fb11256f | ||
|
|
d112c3a509 | ||
|
|
23af36563f | ||
|
|
28db15eb77 | ||
|
|
e9054bd3e5 | ||
|
|
713f857e28 | ||
|
|
5d6768029f | ||
|
|
72e24a8b86 | ||
|
|
86c3e780d1 | ||
|
|
5102620953 | ||
|
|
4d728f9f8b | ||
|
|
faf5098357 | ||
|
|
c4066fc851 |
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -7,5 +7,5 @@ contact_links:
|
||||
url: https://discord.gg/EAhCRfGxCF
|
||||
about: Ask for help with anything related to Zipline!
|
||||
- name: Zipline Docs
|
||||
url: https://zipline.diced.tech
|
||||
url: https://zipline.diced.sh
|
||||
about: Maybe take a look a the docs?
|
||||
|
||||
0
.yarn/releases/yarn-3.3.1.cjs
vendored
Executable file → Normal file
0
.yarn/releases/yarn-3.3.1.cjs
vendored
Executable file → Normal file
12
Dockerfile
12
Dockerfile
@@ -26,10 +26,6 @@ ENV PRISMA_QUERY_ENGINE_BINARY=/prisma-engines/query-engine \
|
||||
ZIPLINE_DOCKER_BUILD=true \
|
||||
NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
# Install production dependencies then temporarily save
|
||||
RUN yarn workspaces focus --production --all
|
||||
RUN cp -RL node_modules /tmp/node_modules
|
||||
|
||||
# Install the dependencies
|
||||
RUN yarn install --immutable
|
||||
|
||||
@@ -63,20 +59,20 @@ ENV PRISMA_QUERY_ENGINE_BINARY=/prisma-engines/query-engine \
|
||||
# Copy only the necessary files from the previous stage
|
||||
COPY --from=builder /zipline/dist ./dist
|
||||
COPY --from=builder /zipline/.next ./.next
|
||||
COPY --from=builder /zipline/package.json ./package.json
|
||||
|
||||
COPY --from=builder /zipline/mimes.json ./mimes.json
|
||||
COPY --from=builder /zipline/next.config.js ./next.config.js
|
||||
COPY --from=builder /zipline/public ./public
|
||||
|
||||
COPY --from=builder /zipline/node_modules ./node_modules
|
||||
|
||||
|
||||
# Copy Startup Script
|
||||
COPY docker-entrypoint.sh /zipline
|
||||
|
||||
# Make Startup Script Executable
|
||||
RUN chmod a+x /zipline/docker-entrypoint.sh && rm -rf /zipline/src
|
||||
|
||||
# Clean up
|
||||
RUN rm -rf /tmp/* /root/*
|
||||
RUN yarn cache clean --all
|
||||
|
||||
# Set the entrypoint to the startup script
|
||||
ENTRYPOINT ["tini", "--", "/zipline/docker-entrypoint.sh"]
|
||||
108
package.json
108
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "zipline",
|
||||
"version": "3.7.2",
|
||||
"version": "3.7.6",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "npm-run-all build:server dev:run",
|
||||
@@ -28,73 +28,73 @@
|
||||
"scripts:clear-temp": "node --enable-source-maps dist/scripts/clear-temp"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.10.6",
|
||||
"@emotion/server": "^11.10.0",
|
||||
"@mantine/core": "^6.0.4",
|
||||
"@mantine/dropzone": "^6.0.4",
|
||||
"@mantine/form": "^6.0.4",
|
||||
"@mantine/hooks": "^6.0.4",
|
||||
"@mantine/modals": "^6.0.4",
|
||||
"@mantine/next": "^6.0.4",
|
||||
"@mantine/notifications": "^6.0.4",
|
||||
"@mantine/prism": "^6.0.4",
|
||||
"@mantine/spotlight": "^6.0.4",
|
||||
"@prisma/client": "^4.10.1",
|
||||
"@prisma/internals": "^4.10.1",
|
||||
"@prisma/migrate": "^4.10.1",
|
||||
"@sapphire/shapeshift": "^3.8.1",
|
||||
"@tabler/icons-react": "^2.11.0",
|
||||
"@emotion/react": "^11.11.1",
|
||||
"@emotion/server": "^11.11.0",
|
||||
"@mantine/core": "^6.0.21",
|
||||
"@mantine/dropzone": "^6.0.21",
|
||||
"@mantine/form": "^6.0.21",
|
||||
"@mantine/hooks": "^6.0.21",
|
||||
"@mantine/modals": "^6.0.21",
|
||||
"@mantine/next": "^6.0.21",
|
||||
"@mantine/notifications": "^6.0.21",
|
||||
"@mantine/prism": "^6.0.21",
|
||||
"@mantine/spotlight": "^6.0.21",
|
||||
"@prisma/client": "^4.16.2",
|
||||
"@prisma/internals": "^4.16.2",
|
||||
"@prisma/migrate": "^4.16.2",
|
||||
"@sapphire/shapeshift": "^3.9.3",
|
||||
"@tabler/icons-react": "^2.41.0",
|
||||
"@tanstack/react-query": "^4.28.0",
|
||||
"argon2": "^0.30.3",
|
||||
"cookie": "^0.5.0",
|
||||
"dayjs": "^1.11.7",
|
||||
"dotenv": "^16.0.3",
|
||||
"argon2": "^0.31.2",
|
||||
"cookie": "^0.6.0",
|
||||
"dayjs": "^1.11.10",
|
||||
"dotenv": "^16.3.1",
|
||||
"dotenv-expand": "^10.0.0",
|
||||
"exiftool-vendored": "^21.2.0",
|
||||
"fastify": "^4.15.0",
|
||||
"fastify-plugin": "^4.5.0",
|
||||
"fflate": "^0.7.4",
|
||||
"ffmpeg-static": "^5.1.0",
|
||||
"find-my-way": "^7.6.0",
|
||||
"katex": "^0.16.4",
|
||||
"mantine-datatable": "^2.2.6",
|
||||
"minio": "^7.0.33",
|
||||
"exiftool-vendored": "^23.4.0",
|
||||
"fastify": "^4.24.3",
|
||||
"fastify-plugin": "^4.5.1",
|
||||
"fflate": "^0.8.1",
|
||||
"ffmpeg-static": "^5.2.0",
|
||||
"find-my-way": "^7.7.0",
|
||||
"katex": "^0.16.9",
|
||||
"mantine-datatable": "^2.9.14",
|
||||
"minio": "^7.1.3",
|
||||
"ms": "canary",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"next": "^13.2.4",
|
||||
"next": "^14.0.3",
|
||||
"otplib": "^12.0.1",
|
||||
"prisma": "^4.10.1",
|
||||
"prisma": "^4.16.2",
|
||||
"prismjs": "^1.29.0",
|
||||
"qrcode": "^1.5.1",
|
||||
"qrcode": "^1.5.3",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-markdown": "^8.0.6",
|
||||
"recharts": "^2.5.0",
|
||||
"recharts": "^2.10.1",
|
||||
"recoil": "^0.7.7",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"sharp": "^0.32.0"
|
||||
"remark-gfm": "^4.0.0",
|
||||
"sharp": "^0.32.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cookie": "^0.5.1",
|
||||
"@types/katex": "^0.16.0",
|
||||
"@types/minio": "^7.0.17",
|
||||
"@types/multer": "^1.4.7",
|
||||
"@types/node": "^18.15.10",
|
||||
"@types/qrcode": "^1.5.0",
|
||||
"@types/react": "^18.0.29",
|
||||
"@types/sharp": "^0.31.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.56.0",
|
||||
"@typescript-eslint/parser": "^5.56.0",
|
||||
"@types/cookie": "^0.5.4",
|
||||
"@types/katex": "^0.16.6",
|
||||
"@types/minio": "^7.1.1",
|
||||
"@types/multer": "^1.4.10",
|
||||
"@types/node": "^18.18.10",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@types/react": "^18.2.37",
|
||||
"@types/sharp": "^0.32.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.36.0",
|
||||
"eslint-config-next": "^13.2.4",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-unused-imports": "^2.0.0",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-config-next": "^14.0.3",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-prettier": "^5.0.1",
|
||||
"eslint-plugin-unused-imports": "^3.0.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^2.8.7",
|
||||
"tsup": "^6.7.0",
|
||||
"typescript": "^5.0.2"
|
||||
"prettier": "^3.1.0",
|
||||
"tsup": "^8.0.0",
|
||||
"typescript": "^5.2.2"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "File" ALTER COLUMN "size" SET DATA TYPE BIGINT;
|
||||
@@ -48,7 +48,7 @@ model File {
|
||||
originalName String?
|
||||
mimetype String @default("image/png")
|
||||
createdAt DateTime @default(now())
|
||||
size Int @default(0)
|
||||
size BigInt @default(0)
|
||||
expiresAt DateTime?
|
||||
maxViews Int?
|
||||
views Int @default(0)
|
||||
@@ -63,7 +63,7 @@ model File {
|
||||
folder Folder? @relation(fields: [folderId], references: [id], onDelete: SetNull)
|
||||
folderId Int?
|
||||
|
||||
thumbnail Thumbnail?
|
||||
thumbnail Thumbnail?
|
||||
}
|
||||
|
||||
model Thumbnail {
|
||||
|
||||
@@ -1498,4 +1498,4 @@ wheat
|
||||
white
|
||||
whitesmoke
|
||||
yellow
|
||||
yellowgreen
|
||||
yellowgreen
|
||||
@@ -1747,4 +1747,4 @@ zigzagsalamander
|
||||
zonetailedpigeon
|
||||
zooplankton
|
||||
zopilote
|
||||
zorilla
|
||||
zorilla
|
||||
@@ -125,7 +125,7 @@ export default function FileModal({
|
||||
icon: <IconPhotoCancel size='1rem' />,
|
||||
});
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -280,7 +280,7 @@ export default function Layout({ children, props }) {
|
||||
component={Link}
|
||||
href={link}
|
||||
/>
|
||||
)
|
||||
),
|
||||
)}
|
||||
</Navbar.Section>
|
||||
<Navbar.Section>
|
||||
@@ -360,6 +360,11 @@ export default function Layout({ children, props }) {
|
||||
compact
|
||||
size='xl'
|
||||
p='sm'
|
||||
styles={{
|
||||
label: {
|
||||
overflow: 'unset',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{user.username}
|
||||
</Button>
|
||||
@@ -411,16 +416,20 @@ export default function Layout({ children, props }) {
|
||||
</Menu.Item>
|
||||
<Menu.Divider />
|
||||
<>
|
||||
{oauth_providers.filter((x) =>
|
||||
user.oauth?.map(({ provider }) => provider.toLowerCase()).includes(x.name.toLowerCase())
|
||||
{oauth_providers.filter(
|
||||
(x) =>
|
||||
user.oauth
|
||||
?.map(({ provider }) => provider.toLowerCase())
|
||||
.includes(x.name.toLowerCase()),
|
||||
).length ? (
|
||||
<Menu.Label>Connected Accounts</Menu.Label>
|
||||
) : null}
|
||||
{oauth_providers
|
||||
.filter((x) =>
|
||||
user.oauth
|
||||
?.map(({ provider }) => provider.toLowerCase())
|
||||
.includes(x.name.toLowerCase())
|
||||
.filter(
|
||||
(x) =>
|
||||
user.oauth
|
||||
?.map(({ provider }) => provider.toLowerCase())
|
||||
.includes(x.name.toLowerCase()),
|
||||
)
|
||||
.map(({ name, Icon }, i) => (
|
||||
<>
|
||||
@@ -433,8 +442,11 @@ export default function Layout({ children, props }) {
|
||||
</Menu.Item>
|
||||
</>
|
||||
))}
|
||||
{oauth_providers.filter((x) =>
|
||||
user.oauth?.map(({ provider }) => provider.toLowerCase()).includes(x.name.toLowerCase())
|
||||
{oauth_providers.filter(
|
||||
(x) =>
|
||||
user.oauth
|
||||
?.map(({ provider }) => provider.toLowerCase())
|
||||
.includes(x.name.toLowerCase()),
|
||||
).length ? (
|
||||
<Menu.Divider />
|
||||
) : null}
|
||||
|
||||
@@ -29,7 +29,7 @@ export default function FilePagation({ disableMediaPreview, exifEnabled, queryPa
|
||||
},
|
||||
},
|
||||
undefined,
|
||||
{ shallow: true }
|
||||
{ shallow: true },
|
||||
);
|
||||
|
||||
const { count } = await useFetch(`/api/user/paged?count=true${!checked ? '&filter=media' : ''}`);
|
||||
|
||||
@@ -75,7 +75,7 @@ export default function Flameshot({ user, open, setOpen }) {
|
||||
let shell;
|
||||
if (values.type === 'upload-file') {
|
||||
shell = `#!/bin/bash${values.wlCompositorNotSupported ? '\nexport XDG_CURRENT_DESKTOP=sway\n' : ''}
|
||||
flameshot gui -r > /tmp/ss.png;
|
||||
flameshot gui -r > /tmp/ss.png;if [ ! -s /tmp/ss.png ]; then\n exit 1\nfi
|
||||
${curl.join(' ')}${values.noJSON ? '' : " | jq -r '.files[0]'"} | tr -d '\\n' | ${
|
||||
values.wlCompatibility ? 'wl-copy' : 'xsel -ib'
|
||||
};
|
||||
|
||||
@@ -87,7 +87,7 @@ export default function ShareX({ user, open, setOpen }) {
|
||||
const pseudoElement = document.createElement('a');
|
||||
pseudoElement.setAttribute(
|
||||
'href',
|
||||
'data:application/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(config, null, '\t'))
|
||||
'data:application/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(config, null, '\t')),
|
||||
);
|
||||
pseudoElement.setAttribute('download', `zipline${values.type === 'upload-file' ? '' : '-url'}.sxcu`);
|
||||
pseudoElement.style.display = 'none';
|
||||
|
||||
@@ -268,7 +268,7 @@ export default function Manage({ oauth_registration, oauth_providers: raw_oauth_
|
||||
size: s.size,
|
||||
full: s.name,
|
||||
}))
|
||||
.sort((a, b) => a.date.getTime() - b.date.getTime())
|
||||
.sort((a, b) => a.date.getTime() - b.date.getTime()),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -367,8 +367,7 @@ export default function Manage({ oauth_registration, oauth_providers: raw_oauth_
|
||||
<Title>Manage User</Title>
|
||||
<MutedText size='md'>
|
||||
Want to use variables in embed text? Visit{' '}
|
||||
<AnchorNext href='https://zipline.diced.tech/docs/guides/variables'>the docs</AnchorNext> for
|
||||
variables
|
||||
<AnchorNext href='https://zipline.diced.sh/docs/guides/variables'>the docs</AnchorNext> for variables
|
||||
</MutedText>
|
||||
|
||||
<TextInput
|
||||
@@ -488,7 +487,7 @@ export default function Manage({ oauth_registration, oauth_providers: raw_oauth_
|
||||
{oauth_providers
|
||||
.filter(
|
||||
(x) =>
|
||||
!user.oauth?.map(({ provider }) => provider.toLowerCase()).includes(x.name.toLowerCase())
|
||||
!user.oauth?.map(({ provider }) => provider.toLowerCase()).includes(x.name.toLowerCase()),
|
||||
)
|
||||
.map(({ link_url, name, Icon }, i) => (
|
||||
<Button key={i} size='lg' leftIcon={<Icon />} component={Link} href={link_url} my='sm'>
|
||||
|
||||
@@ -43,7 +43,7 @@ export default function File({ chunks: chunks_config }) {
|
||||
return e.returnValue;
|
||||
}
|
||||
},
|
||||
[loading]
|
||||
[loading],
|
||||
);
|
||||
|
||||
const beforeRouteChange = useCallback(
|
||||
@@ -56,7 +56,7 @@ export default function File({ chunks: chunks_config }) {
|
||||
}
|
||||
}
|
||||
},
|
||||
[loading]
|
||||
[loading],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -191,7 +191,7 @@ export default function File({ chunks: chunks_config }) {
|
||||
ready = false;
|
||||
}
|
||||
},
|
||||
false
|
||||
false,
|
||||
);
|
||||
|
||||
req.open('POST', '/api/upload');
|
||||
@@ -233,7 +233,7 @@ export default function File({ chunks: chunks_config }) {
|
||||
if (chunks_config.enabled && file.size >= chunks_config.max_size) {
|
||||
toChunkFiles.push(file);
|
||||
} else {
|
||||
body.append('file', files[i]);
|
||||
body.append('file', files[i], encodeURIComponent(files[i].name));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,7 +307,7 @@ export default function File({ chunks: chunks_config }) {
|
||||
}
|
||||
setProgress(0);
|
||||
},
|
||||
false
|
||||
false,
|
||||
);
|
||||
|
||||
if (bodyLength !== 0) {
|
||||
|
||||
@@ -213,7 +213,7 @@ export function OptionsModal({
|
||||
export default function useUploadOptions(): [
|
||||
UploadOptionsState,
|
||||
Dispatch<SetStateAction<boolean>>,
|
||||
ReactNode
|
||||
ReactNode,
|
||||
] {
|
||||
const [state, setState] = useReducer((state, newState) => ({ ...state, ...newState }), {
|
||||
expires: 'never',
|
||||
|
||||
@@ -57,6 +57,7 @@ export interface ConfigUploader {
|
||||
format_date: string;
|
||||
default_expiration: string;
|
||||
assume_mimetypes: boolean;
|
||||
random_words_separator: string;
|
||||
}
|
||||
|
||||
export interface ConfigUrls {
|
||||
@@ -135,9 +136,12 @@ export interface ConfigOAuth {
|
||||
|
||||
discord_client_id?: string;
|
||||
discord_client_secret?: string;
|
||||
discord_redirect_uri?: string;
|
||||
discord_whitelisted_users?: string[];
|
||||
|
||||
google_client_id?: string;
|
||||
google_client_secret?: string;
|
||||
google_redirect_uri?: string;
|
||||
}
|
||||
|
||||
export interface ConfigChunks {
|
||||
|
||||
@@ -98,6 +98,7 @@ export default function readConfig() {
|
||||
map('UPLOADER_FORMAT_DATE', 'string', 'uploader.format_date'),
|
||||
map('UPLOADER_DEFAULT_EXPIRATION', 'string', 'uploader.default_expiration'),
|
||||
map('UPLOADER_ASSUME_MIMETYPES', 'boolean', 'uploader.assume_mimetypes'),
|
||||
map('UPLOADER_RANDOM_WORDS_SEPARATOR', 'string', 'uploader.random_words_separator'),
|
||||
|
||||
map('URLS_ROUTE', 'string', 'urls.route'),
|
||||
map('URLS_LENGTH', 'number', 'urls.length'),
|
||||
@@ -146,9 +147,12 @@ export default function readConfig() {
|
||||
|
||||
map('OAUTH_DISCORD_CLIENT_ID', 'string', 'oauth.discord_client_id'),
|
||||
map('OAUTH_DISCORD_CLIENT_SECRET', 'string', 'oauth.discord_client_secret'),
|
||||
map('OAUTH_DISCORD_REDIRECT_URI', 'string', 'oauth.discord_redirect_uri'),
|
||||
map('OAUTH_DISCORD_WHITELISTED_USERS', 'array', 'oauth.discord_whitelisted_users'),
|
||||
|
||||
map('OAUTH_GOOGLE_CLIENT_ID', 'string', 'oauth.google_client_id'),
|
||||
map('OAUTH_GOOGLE_CLIENT_SECRET', 'string', 'oauth.google_client_secret'),
|
||||
map('OAUTH_GOOGLE_REDIRECT_URI', 'string', 'oauth.google_redirect_uri'),
|
||||
|
||||
map('FEATURES_INVITES', 'boolean', 'features.invites'),
|
||||
map('FEATURES_INVITES_LENGTH', 'number', 'features.invites_length'),
|
||||
|
||||
@@ -97,6 +97,7 @@ const validator = s.object({
|
||||
format_date: s.string.default('YYYY-MM-DD_HH:mm:ss'),
|
||||
default_expiration: s.string.optional.default(null),
|
||||
assume_mimetypes: s.boolean.default(false),
|
||||
random_words_separator: s.string.default('-'),
|
||||
})
|
||||
.default({
|
||||
default_format: 'RANDOM',
|
||||
@@ -140,11 +141,11 @@ const validator = s.object({
|
||||
s.object({
|
||||
label: s.string,
|
||||
link: s.string,
|
||||
})
|
||||
}),
|
||||
)
|
||||
.default([
|
||||
{ label: 'Zipline', link: 'https://github.com/diced/zipline' },
|
||||
{ label: 'Documentation', link: 'https://zipline.diced.tech/' },
|
||||
{ label: 'Documentation', link: 'https://zipline.diced.sh/' },
|
||||
]),
|
||||
})
|
||||
.default({
|
||||
@@ -155,7 +156,7 @@ const validator = s.object({
|
||||
|
||||
external_links: [
|
||||
{ label: 'Zipline', link: 'https://github.com/diced/zipline' },
|
||||
{ label: 'Documentation', link: 'https://zipline.diced.tech/' },
|
||||
{ label: 'Documentation', link: 'https://zipline.diced.sh/' },
|
||||
],
|
||||
}),
|
||||
discord: s
|
||||
@@ -176,9 +177,12 @@ const validator = s.object({
|
||||
|
||||
discord_client_id: s.string.nullable.default(null),
|
||||
discord_client_secret: s.string.nullable.default(null),
|
||||
discord_redirect_uri: s.string.nullable.default(null),
|
||||
discord_whitelisted_users: s.string.array.default([]),
|
||||
|
||||
google_client_id: s.string.nullable.default(null),
|
||||
google_client_secret: s.string.nullable.default(null),
|
||||
google_redirect_uri: s.string.nullable.default(null),
|
||||
})
|
||||
.nullish.default(null),
|
||||
features: s
|
||||
|
||||
@@ -49,13 +49,10 @@ export class S3 extends Datasource {
|
||||
});
|
||||
}
|
||||
|
||||
public size(file: string): Promise<number> {
|
||||
return new Promise((res) => {
|
||||
this.s3.statObject(this.config.bucket, file, (err, stat) => {
|
||||
if (err) res(0);
|
||||
else res(stat.size);
|
||||
});
|
||||
});
|
||||
public async size(file: string): Promise<number> {
|
||||
const stat = await this.s3.statObject(this.config.bucket, file);
|
||||
|
||||
return stat.size;
|
||||
}
|
||||
|
||||
public async fullSize(): Promise<number> {
|
||||
|
||||
@@ -8,7 +8,7 @@ const logger = Logger.get('discord');
|
||||
|
||||
export function parseContent(
|
||||
content: ConfigDiscordContent,
|
||||
args: ParseValue
|
||||
args: ParseValue,
|
||||
): ConfigDiscordContent & { url: string } {
|
||||
return {
|
||||
content: content.content ? parseString(content.content, args) : null,
|
||||
|
||||
@@ -1,26 +1,41 @@
|
||||
import { readFile } from 'fs/promises';
|
||||
import config from 'lib/config';
|
||||
import Logger from 'lib/logger';
|
||||
|
||||
const logger = Logger.get('random_words');
|
||||
|
||||
export type GfyCatWords = {
|
||||
adjectives: string[];
|
||||
animals: string[];
|
||||
};
|
||||
|
||||
export async function importWords(): Promise<GfyCatWords> {
|
||||
const adjectives = (await readFile('public/adjectives.txt', 'utf-8')).split('\n');
|
||||
const animals = (await readFile('public/animals.txt', 'utf-8')).split('\n');
|
||||
export async function importWords(): Promise<GfyCatWords | null> {
|
||||
try {
|
||||
const adjectives = (await readFile('public/adjectives.txt', 'utf-8')).split('\n').map((x) => x.trim());
|
||||
const animals = (await readFile('public/animals.txt', 'utf-8')).split('\n').map((x) => x.trim());
|
||||
|
||||
return {
|
||||
adjectives,
|
||||
animals,
|
||||
};
|
||||
return {
|
||||
adjectives,
|
||||
animals,
|
||||
};
|
||||
} catch {
|
||||
logger.error('public/adjectives.txt or public/animals.txt do not exist, to fix this please retrieve.');
|
||||
logger.error('to prevent this from happening again, remember to not delete your public/ directory.');
|
||||
logger.error('file names will use the RANDOM format instead until fixed');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function randomWord(words: string[]) {
|
||||
return words[Math.floor(Math.random() * words.length)];
|
||||
}
|
||||
|
||||
export default async function gfycat() {
|
||||
export default async function gfycat(): Promise<string | null> {
|
||||
const words = await importWords();
|
||||
|
||||
return `${randomWord(words.adjectives)}${randomWord(words.adjectives)}${randomWord(words.animals)}`;
|
||||
if (!words) return null;
|
||||
|
||||
return `${randomWord(words.adjectives)}${config.uploader.random_words_separator}${randomWord(
|
||||
words.adjectives,
|
||||
)}${config.uploader.random_words_separator}${randomWord(words.animals)}`;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ export default async function formatFileName(nameFormat: NameFormat, originalNam
|
||||
|
||||
return name;
|
||||
case 'gfycat':
|
||||
return gfycat();
|
||||
return gfycat() ?? random();
|
||||
default:
|
||||
return random();
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ export type ApiError = {
|
||||
export default async function useFetch(
|
||||
url: string,
|
||||
method: 'GET' | 'POST' | 'PATCH' | 'DELETE' = 'GET',
|
||||
body: ApiError | Record<string, unknown> = null
|
||||
body: ApiError | Record<string, unknown> = null,
|
||||
) {
|
||||
const headers = {};
|
||||
if (body) headers['content-type'] = 'application/json';
|
||||
|
||||
@@ -60,8 +60,8 @@ export default class Logger {
|
||||
this.formatMessage(
|
||||
LoggerLevel.ERROR,
|
||||
this.name,
|
||||
args.map((error) => (typeof error === 'string' ? error : (error as Error).stack)).join(' ')
|
||||
)
|
||||
args.map((error) => (typeof error === 'string' ? error : (error as Error).stack)).join(' '),
|
||||
),
|
||||
);
|
||||
|
||||
return this;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import config from 'lib/config';
|
||||
import { notNull } from 'lib/util';
|
||||
import { isNotNullOrUndefined } from 'lib/util';
|
||||
import { GetServerSideProps } from 'next';
|
||||
|
||||
export type OauthProvider = {
|
||||
@@ -27,9 +27,15 @@ export type ServerSideProps = {
|
||||
};
|
||||
|
||||
export const getServerSideProps: GetServerSideProps<ServerSideProps> = async (ctx) => {
|
||||
const ghEnabled = notNull(config.oauth?.github_client_id, config.oauth?.github_client_secret);
|
||||
const discEnabled = notNull(config.oauth?.discord_client_id, config.oauth?.discord_client_secret);
|
||||
const googleEnabled = notNull(config.oauth?.google_client_id, config.oauth?.google_client_secret);
|
||||
const ghEnabled =
|
||||
isNotNullOrUndefined(config.oauth?.github_client_id) &&
|
||||
isNotNullOrUndefined(config.oauth?.github_client_secret);
|
||||
const discEnabled =
|
||||
isNotNullOrUndefined(config.oauth?.discord_client_id) &&
|
||||
isNotNullOrUndefined(config.oauth?.discord_client_secret);
|
||||
const googleEnabled =
|
||||
isNotNullOrUndefined(config.oauth?.google_client_id) &&
|
||||
isNotNullOrUndefined(config.oauth?.google_client_secret);
|
||||
|
||||
const oauth_providers: OauthProvider[] = [];
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ export interface OAuthResponse {
|
||||
export const withOAuth =
|
||||
(
|
||||
provider: 'discord' | 'github' | 'google',
|
||||
oauth: (query: OAuthQuery, logger: Logger) => Promise<OAuthResponse>
|
||||
oauth: (query: OAuthQuery, logger: Logger) => Promise<OAuthResponse>,
|
||||
) =>
|
||||
async (req: NextApiReq, res: NextApiRes) => {
|
||||
const logger = Logger.get(`oauth::${provider}`);
|
||||
@@ -172,7 +172,7 @@ export const withOAuth =
|
||||
|
||||
res.setUserCookie(existingOauth.userId);
|
||||
Logger.get('user').info(
|
||||
`User ${existingOauth.username} (${existingOauth.id}) logged in via oauth(${provider})`
|
||||
`User ${existingOauth.username} (${existingOauth.id}) logged in via oauth(${provider})`,
|
||||
);
|
||||
|
||||
return res.redirect('/dashboard');
|
||||
|
||||
@@ -66,7 +66,7 @@ export type ZiplineApiConfig = {
|
||||
export const withZipline =
|
||||
(
|
||||
handler: (req: NextApiRequest, res: NextApiResponse, user?: UserExtended) => Promise<unknown>,
|
||||
api_config: ZiplineApiConfig = { methods: ['GET', 'OPTIONS'] }
|
||||
api_config: ZiplineApiConfig = { methods: ['GET', 'OPTIONS'] },
|
||||
) =>
|
||||
(req: NextApiReq, res: NextApiRes) => {
|
||||
if (!api_config.methods.includes('OPTIONS')) api_config.methods.push('OPTIONS');
|
||||
@@ -87,7 +87,7 @@ export const withZipline =
|
||||
code: 400,
|
||||
...extra,
|
||||
},
|
||||
400
|
||||
400,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -99,7 +99,7 @@ export const withZipline =
|
||||
code: 401,
|
||||
...extra,
|
||||
},
|
||||
401
|
||||
401,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -111,7 +111,7 @@ export const withZipline =
|
||||
code: 403,
|
||||
...extra,
|
||||
},
|
||||
403
|
||||
403,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -122,7 +122,7 @@ export const withZipline =
|
||||
code: 404,
|
||||
...extra,
|
||||
},
|
||||
404
|
||||
404,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -136,7 +136,7 @@ export const withZipline =
|
||||
code: 429,
|
||||
...extra,
|
||||
},
|
||||
429
|
||||
429,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -161,7 +161,7 @@ export const withZipline =
|
||||
path: '/',
|
||||
expires: new Date(1),
|
||||
maxAge: undefined,
|
||||
})
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -230,7 +230,7 @@ export const withZipline =
|
||||
error: 'method not allowed',
|
||||
code: 405,
|
||||
},
|
||||
405
|
||||
405,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,9 +16,9 @@ export const github_auth = {
|
||||
};
|
||||
|
||||
export const discord_auth = {
|
||||
oauth_url: (clientId: string, origin: string, state?: string) =>
|
||||
oauth_url: (clientId: string, origin: string, state?: string, redirect_uri?: string) =>
|
||||
`https://discord.com/api/oauth2/authorize?client_id=${clientId}&redirect_uri=${encodeURIComponent(
|
||||
`${origin}/api/auth/oauth/discord`
|
||||
redirect_uri || `${origin}/api/auth/oauth/discord`,
|
||||
)}&response_type=code&scope=identify${state ? `&state=${state}` : ''}`,
|
||||
oauth_user: async (access_token: string) => {
|
||||
const res = await fetch('https://discord.com/api/users/@me', {
|
||||
@@ -33,15 +33,15 @@ export const discord_auth = {
|
||||
};
|
||||
|
||||
export const google_auth = {
|
||||
oauth_url: (clientId: string, origin: string, state?: string) =>
|
||||
oauth_url: (clientId: string, origin: string, state?: string, redirect_uri?: string) =>
|
||||
`https://accounts.google.com/o/oauth2/auth?client_id=${clientId}&redirect_uri=${encodeURIComponent(
|
||||
`${origin}/api/auth/oauth/google`
|
||||
redirect_uri || `${origin}/api/auth/oauth/google`,
|
||||
)}&response_type=code&access_type=offline&scope=https://www.googleapis.com/auth/userinfo.profile${
|
||||
state ? `&state=${state}` : ''
|
||||
}`,
|
||||
oauth_user: async (access_token: string) => {
|
||||
const res = await fetch(
|
||||
`https://people.googleapis.com/v1/people/me?access_token=${access_token}&personFields=names,photos`
|
||||
`https://people.googleapis.com/v1/people/me?access_token=${access_token}&personFields=names,photos`,
|
||||
);
|
||||
if (!res.ok) return null;
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ export const useFiles = (query: { [key: string]: string } = {}) => {
|
||||
...x,
|
||||
createdAt: new Date(x.createdAt),
|
||||
expiresAt: x.expiresAt ? new Date(x.expiresAt) : null,
|
||||
}))
|
||||
})),
|
||||
);
|
||||
});
|
||||
};
|
||||
@@ -59,7 +59,7 @@ export const usePaginatedFiles = (page?: number, options?: Partial<PaginatedFile
|
||||
...x,
|
||||
createdAt: new Date(x.createdAt),
|
||||
expiresAt: x.expiresAt ? new Date(x.expiresAt) : null,
|
||||
}))
|
||||
})),
|
||||
);
|
||||
});
|
||||
};
|
||||
@@ -73,7 +73,7 @@ export const useRecent = (filter?: string) => {
|
||||
...x,
|
||||
createdAt: new Date(x.createdAt),
|
||||
expiresAt: x.expiresAt ? new Date(x.expiresAt) : null,
|
||||
}))
|
||||
})),
|
||||
);
|
||||
});
|
||||
};
|
||||
@@ -94,7 +94,7 @@ export function useFileDelete() {
|
||||
onSuccess: () => {
|
||||
queryClient.refetchQueries(['files']);
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ export function useFileFavorite() {
|
||||
onSuccess: () => {
|
||||
queryClient.refetchQueries(['files']);
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ export const useFolders = (query: { [key: string]: string } = {}) => {
|
||||
|
||||
return useQuery<UserFoldersResponse[]>(['folders', queryString], async () => {
|
||||
return fetch('/api/user/folders?' + queryString).then(
|
||||
(res) => res.json() as Promise<UserFoldersResponse[]>
|
||||
(res) => res.json() as Promise<UserFoldersResponse[]>,
|
||||
);
|
||||
});
|
||||
};
|
||||
@@ -26,7 +26,7 @@ export const useFolders = (query: { [key: string]: string } = {}) => {
|
||||
export const useFolder = (id: string, withFiles = false) => {
|
||||
return useQuery<UserFoldersResponse>(['folder', id], async () => {
|
||||
return fetch('/api/user/folders/' + id + (withFiles ? '?files=true' : '')).then(
|
||||
(res) => res.json() as Promise<UserFoldersResponse>
|
||||
(res) => res.json() as Promise<UserFoldersResponse>,
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -27,6 +27,6 @@ export const useStats = (amount = 2) => {
|
||||
},
|
||||
{
|
||||
staleTime: 1000 * 60 * 5, // 5 minutes
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
@@ -36,6 +36,6 @@ export function useURLDelete() {
|
||||
?.filter((u) => u.id !== variables);
|
||||
queryClient.setQueryData(['urls'], dataWithoutDeleted);
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,10 +15,12 @@ export const useVersion = () => {
|
||||
return useQuery<VersionResponse>(
|
||||
['version'],
|
||||
async () => {
|
||||
return fetch('/api/version').then((res) => res.json());
|
||||
return fetch('/api/version').then((res) => (res.ok ? res.json() : Promise.reject('')));
|
||||
},
|
||||
{
|
||||
staleTime: Infinity,
|
||||
}
|
||||
refetchInterval: false,
|
||||
refetchOnMount: false,
|
||||
retry: false,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
@@ -36,7 +36,7 @@ export const createSpotlightActions = (router: NextRouter): SpotlightAction[] =>
|
||||
title: string,
|
||||
description: string,
|
||||
link: string,
|
||||
icon: ReactNode
|
||||
icon: ReactNode,
|
||||
): SpotlightAction => {
|
||||
return actionDo(group, title, description, icon, () => linkTo(link));
|
||||
};
|
||||
@@ -46,7 +46,7 @@ export const createSpotlightActions = (router: NextRouter): SpotlightAction[] =>
|
||||
title: string,
|
||||
description: string,
|
||||
icon: ReactNode,
|
||||
action: () => void
|
||||
action: () => void,
|
||||
): SpotlightAction => {
|
||||
return {
|
||||
group,
|
||||
@@ -70,7 +70,7 @@ export const createSpotlightActions = (router: NextRouter): SpotlightAction[] =>
|
||||
'Manage Account',
|
||||
'Manage your account settings',
|
||||
'/dashboard/manage',
|
||||
<IconUser />
|
||||
<IconUser />,
|
||||
),
|
||||
|
||||
// Actions
|
||||
@@ -80,14 +80,14 @@ export const createSpotlightActions = (router: NextRouter): SpotlightAction[] =>
|
||||
'Upload Files',
|
||||
'Upload files of any kind',
|
||||
'/dashboard/upload/file',
|
||||
<IconFileUpload />
|
||||
<IconFileUpload />,
|
||||
),
|
||||
actionLink(
|
||||
'Actions',
|
||||
'Upload Text',
|
||||
'Upload code, or any other kind of text file',
|
||||
'/dashboard/upload/text',
|
||||
<IconFileText />
|
||||
<IconFileText />,
|
||||
),
|
||||
actionDo('Actions', 'Copy Token', 'Copy your API token to your clipboard', <IconClipboardCopy />, () => {
|
||||
clipboard.copy(user.token);
|
||||
@@ -99,7 +99,7 @@ export const createSpotlightActions = (router: NextRouter): SpotlightAction[] =>
|
||||
});
|
||||
}),
|
||||
|
||||
actionLink('Help', 'Documentation', 'View the documentation', 'https://zipline.diced.tech', <IconHelp />),
|
||||
actionLink('Help', 'Documentation', 'View the documentation', 'https://zipline.diced.sh', <IconHelp />),
|
||||
|
||||
// the list of actions here is very incomplete, and will be expanded in the future
|
||||
];
|
||||
|
||||
@@ -120,6 +120,6 @@ export async function getBase64URLFromURL(url: string) {
|
||||
return `data:${res.headers.get('content-type')};base64,${base64}`;
|
||||
}
|
||||
|
||||
export function notNull(a: unknown, b: unknown) {
|
||||
return a !== null && b !== null;
|
||||
export function isNotNullOrUndefined(value: unknown) {
|
||||
return value !== null && value !== undefined;
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ export function expireReadToDate(expires: string): Date {
|
||||
'6m': Date.now() + 6 * 30 * 24 * 60 * 60 * 1000,
|
||||
'8m': Date.now() + 8 * 30 * 24 * 60 * 60 * 1000,
|
||||
'1y': Date.now() + 365 * 24 * 60 * 60 * 1000,
|
||||
}[expires]
|
||||
}[expires],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ export function parseString(str: string, value: ParseValue) {
|
||||
str,
|
||||
decodeURIComponent(escape(getV[matches.groups.prop])),
|
||||
matches.index,
|
||||
re.lastIndex
|
||||
re.lastIndex,
|
||||
);
|
||||
re.lastIndex = matches.index;
|
||||
continue;
|
||||
|
||||
@@ -69,7 +69,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
logger.info(
|
||||
`Created user ${newUser.username} (${newUser.id}) ${
|
||||
code ? `from invite code ${code}` : 'via registration'
|
||||
}`
|
||||
}`,
|
||||
);
|
||||
|
||||
return res.json({ success: true });
|
||||
|
||||
@@ -37,7 +37,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||
logger.info(
|
||||
`${user.username} (${user.id}) created ${data.length} invites with codes ${data
|
||||
.map((invite) => invite.code)
|
||||
.join(', ')}`
|
||||
.join(', ')}`,
|
||||
);
|
||||
|
||||
return res.json(data);
|
||||
|
||||
@@ -51,7 +51,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
|
||||
const success = verify_totp_code(user.totpSecret, code);
|
||||
logger.debug(
|
||||
`body(${JSON.stringify(req.body)}): verify_totp_code(${user.totpSecret}, ${code}) => ${success}`
|
||||
`body(${JSON.stringify(req.body)}): verify_totp_code(${user.totpSecret}, ${code}) => ${success}`,
|
||||
);
|
||||
if (!success) return res.badRequest('Invalid code', { totp: true });
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import Logger from 'lib/logger';
|
||||
import { OAuthQuery, OAuthResponse, withOAuth } from 'lib/middleware/withOAuth';
|
||||
import { withZipline } from 'lib/middleware/withZipline';
|
||||
import { discord_auth } from 'lib/oauth';
|
||||
import { getBase64URLFromURL, notNull } from 'lib/util';
|
||||
import { getBase64URLFromURL, isNotNullOrUndefined } from 'lib/util';
|
||||
|
||||
async function handler({ code, state, host }: OAuthQuery, logger: Logger): Promise<OAuthResponse> {
|
||||
if (!config.features.oauth_registration)
|
||||
@@ -12,7 +12,10 @@ async function handler({ code, state, host }: OAuthQuery, logger: Logger): Promi
|
||||
error: 'oauth registration is disabled',
|
||||
};
|
||||
|
||||
if (!notNull(config.oauth.discord_client_id, config.oauth.discord_client_secret)) {
|
||||
if (
|
||||
!isNotNullOrUndefined(config.oauth.discord_client_id) &&
|
||||
!isNotNullOrUndefined(config.oauth.discord_client_secret)
|
||||
) {
|
||||
logger.error('Discord OAuth is not configured');
|
||||
|
||||
return {
|
||||
@@ -26,7 +29,8 @@ async function handler({ code, state, host }: OAuthQuery, logger: Logger): Promi
|
||||
redirect: discord_auth.oauth_url(
|
||||
config.oauth.discord_client_id,
|
||||
`${config.core.return_https ? 'https' : 'http'}://${host}`,
|
||||
state
|
||||
state,
|
||||
config.oauth.discord_redirect_uri,
|
||||
),
|
||||
};
|
||||
|
||||
@@ -35,7 +39,9 @@ async function handler({ code, state, host }: OAuthQuery, logger: Logger): Promi
|
||||
client_secret: config.oauth.discord_client_secret,
|
||||
code,
|
||||
grant_type: 'authorization_code',
|
||||
redirect_uri: `${config.core.return_https ? 'https' : 'http'}://${host}/api/auth/oauth/discord`,
|
||||
redirect_uri:
|
||||
config.oauth.discord_redirect_uri ||
|
||||
`${config.core.return_https ? 'https' : 'http'}://${host}/api/auth/oauth/discord`,
|
||||
scope: 'identify',
|
||||
});
|
||||
|
||||
@@ -67,6 +73,12 @@ async function handler({ code, state, host }: OAuthQuery, logger: Logger): Promi
|
||||
: `https://cdn.discordapp.com/embed/avatars/${userJson.discriminator % 5}.png`;
|
||||
const avatarBase64 = await getBase64URLFromURL(avatar);
|
||||
|
||||
if (
|
||||
config.oauth.discord_whitelisted_users?.length &&
|
||||
!config.oauth.discord_whitelisted_users.includes(userJson.id)
|
||||
)
|
||||
return { error: 'user is not whitelisted' };
|
||||
|
||||
return {
|
||||
username: userJson.username,
|
||||
user_id: userJson.id,
|
||||
|
||||
@@ -3,7 +3,7 @@ import Logger from 'lib/logger';
|
||||
import { OAuthQuery, OAuthResponse, withOAuth } from 'lib/middleware/withOAuth';
|
||||
import { withZipline } from 'lib/middleware/withZipline';
|
||||
import { github_auth } from 'lib/oauth';
|
||||
import { getBase64URLFromURL, notNull } from 'lib/util';
|
||||
import { getBase64URLFromURL, isNotNullOrUndefined } from 'lib/util';
|
||||
|
||||
async function handler({ code, state }: OAuthQuery, logger: Logger): Promise<OAuthResponse> {
|
||||
if (!config.features.oauth_registration)
|
||||
@@ -12,7 +12,10 @@ async function handler({ code, state }: OAuthQuery, logger: Logger): Promise<OAu
|
||||
error: 'oauth registration is disabled',
|
||||
};
|
||||
|
||||
if (!notNull(config.oauth.github_client_id, config.oauth.github_client_secret)) {
|
||||
if (
|
||||
!isNotNullOrUndefined(config.oauth.github_client_id) &&
|
||||
!isNotNullOrUndefined(config.oauth.github_client_secret)
|
||||
) {
|
||||
logger.error('GitHub OAuth is not configured');
|
||||
return {
|
||||
error_code: 401,
|
||||
|
||||
@@ -3,7 +3,7 @@ import Logger from 'lib/logger';
|
||||
import { OAuthQuery, OAuthResponse, withOAuth } from 'lib/middleware/withOAuth';
|
||||
import { withZipline } from 'lib/middleware/withZipline';
|
||||
import { google_auth } from 'lib/oauth';
|
||||
import { getBase64URLFromURL, notNull } from 'lib/util';
|
||||
import { getBase64URLFromURL, isNotNullOrUndefined } from 'lib/util';
|
||||
|
||||
async function handler({ code, state, host }: OAuthQuery, logger: Logger): Promise<OAuthResponse> {
|
||||
if (!config.features.oauth_registration)
|
||||
@@ -12,7 +12,10 @@ async function handler({ code, state, host }: OAuthQuery, logger: Logger): Promi
|
||||
error: 'oauth registration is disabled',
|
||||
};
|
||||
|
||||
if (!notNull(config.oauth.google_client_id, config.oauth.google_client_secret)) {
|
||||
if (
|
||||
!isNotNullOrUndefined(config.oauth.google_client_id) &&
|
||||
!isNotNullOrUndefined(config.oauth.google_client_secret)
|
||||
) {
|
||||
logger.error('Google OAuth is not configured');
|
||||
return {
|
||||
error_code: 401,
|
||||
@@ -25,7 +28,8 @@ async function handler({ code, state, host }: OAuthQuery, logger: Logger): Promi
|
||||
redirect: google_auth.oauth_url(
|
||||
config.oauth.google_client_id,
|
||||
`${config.core.return_https ? 'https' : 'http'}://${host}`,
|
||||
state
|
||||
state,
|
||||
config.oauth.google_redirect_uri,
|
||||
),
|
||||
};
|
||||
|
||||
@@ -33,7 +37,9 @@ async function handler({ code, state, host }: OAuthQuery, logger: Logger): Promi
|
||||
code,
|
||||
client_id: config.oauth.google_client_id,
|
||||
client_secret: config.oauth.google_client_secret,
|
||||
redirect_uri: `${config.core.return_https ? 'https' : 'http'}://${host}/api/auth/oauth/google`,
|
||||
redirect_uri:
|
||||
config.oauth.google_redirect_uri ||
|
||||
`${config.core.return_https ? 'https' : 'http'}://${host}/api/auth/oauth/google`,
|
||||
grant_type: 'authorization_code',
|
||||
});
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||
if (!image) return res.notFound('image not found');
|
||||
|
||||
logger.info(
|
||||
`${user.username} (${user.id}) requested to read exif metadata for image ${image.name} (${image.id})`
|
||||
`${user.username} (${user.id}) requested to read exif metadata for image ${image.name} (${image.id})`,
|
||||
);
|
||||
|
||||
if (config.datasource.type === 'local') {
|
||||
|
||||
@@ -80,6 +80,16 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
|
||||
// handle partial uploads before ratelimits
|
||||
if (req.headers['content-range'] && zconfig.chunks.enabled) {
|
||||
if (format === 'name') {
|
||||
const existing = await prisma.file.findFirst({
|
||||
where: {
|
||||
name: req.headers['x-zipline-partial-filename'] as string,
|
||||
},
|
||||
});
|
||||
|
||||
if (existing) return res.badRequest('filename already exists (conflict: NAME format)');
|
||||
}
|
||||
|
||||
// parses content-range header (bytes start-end/total)
|
||||
const [start, end, total] = req.headers['content-range']
|
||||
.replace('bytes ', '')
|
||||
@@ -101,7 +111,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
start,
|
||||
end,
|
||||
total,
|
||||
})}`
|
||||
})}`,
|
||||
);
|
||||
|
||||
const tempFile = join(zconfig.core.temp_directory, `zipline_partial_${identifier}_${start}_${end}`);
|
||||
@@ -193,29 +203,32 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
mimetype: x.mimetype,
|
||||
size: x.size,
|
||||
encoding: x.encoding,
|
||||
}))
|
||||
)}`
|
||||
})),
|
||||
)}`,
|
||||
);
|
||||
|
||||
for (let i = 0; i !== req.files.length; ++i) {
|
||||
const file = req.files[i];
|
||||
|
||||
if (file.size > zconfig.uploader[user.administrator ? 'admin_limit' : 'user_limit'])
|
||||
return res.badRequest(`file[${i}]: size too big`);
|
||||
if (!file.originalname) return res.badRequest(`file[${i}]: no filename`);
|
||||
|
||||
const ext = file.originalname.split('.').length === 1 ? '' : file.originalname.split('.').pop();
|
||||
const decodedName = decodeURI(file.originalname);
|
||||
|
||||
const ext = decodedName.split('.').length === 1 ? '' : decodedName.split('.').pop();
|
||||
if (zconfig.uploader.disabled_extensions.includes(ext))
|
||||
return res.badRequest(`file[${i}]: disabled extension recieved: ${ext}`);
|
||||
let fileName = await formatFileName(format, file.originalname);
|
||||
const fileName = await formatFileName(format, decodedName);
|
||||
|
||||
if (req.headers['x-zipline-filename']) {
|
||||
fileName = req.headers['x-zipline-filename'] as string;
|
||||
if (format === 'name' || req.headers['x-zipline-filename']) {
|
||||
const exist = (req.headers['x-zipline-filename'] as string) || decodedName;
|
||||
const existing = await prisma.file.findFirst({
|
||||
where: {
|
||||
name: fileName,
|
||||
name: exist,
|
||||
},
|
||||
});
|
||||
if (existing) return res.badRequest(`file[${i}]: filename already exists: '${fileName}'`);
|
||||
if (existing) return res.badRequest(`file[${i}]: filename already exists: '${decodedName}'`);
|
||||
}
|
||||
|
||||
let password = null;
|
||||
@@ -226,7 +239,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
let mimetype = file.mimetype;
|
||||
|
||||
if (file.mimetype === 'application/octet-stream' && zconfig.uploader.assume_mimetypes) {
|
||||
const ext = parse(file.originalname).ext.replace('.', '');
|
||||
const ext = parse(decodedName).ext.replace('.', '');
|
||||
const mime = await guess(ext);
|
||||
|
||||
if (!mime) response.assumed_mimetype = false;
|
||||
@@ -247,7 +260,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
password,
|
||||
expiresAt: expiry,
|
||||
maxViews: fileMaxViews,
|
||||
originalName: req.headers['original-name'] ? file.originalname ?? null : null,
|
||||
originalName: req.headers['original-name'] ? decodedName ?? null : null,
|
||||
size: file.size,
|
||||
},
|
||||
});
|
||||
@@ -259,7 +272,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
const buffer = await sharp(file.buffer).jpeg({ quality: imageCompressionPercent }).toBuffer();
|
||||
await datasource.save(fileUpload.name, buffer);
|
||||
logger.info(
|
||||
`User ${user.username} (${user.id}) compressed image from ${file.buffer.length} -> ${buffer.length} bytes`
|
||||
`User ${user.username} (${user.id}) compressed image from ${file.buffer.length} -> ${buffer.length} bytes`,
|
||||
);
|
||||
} else {
|
||||
await datasource.save(fileUpload.name, file.buffer);
|
||||
@@ -286,7 +299,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
user,
|
||||
fileUpload,
|
||||
`${domain}/r/${invis ? invis.invis : encodeURI(fileUpload.name)}`,
|
||||
responseUrl
|
||||
responseUrl,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||
promises.push(
|
||||
prisma.user.delete({
|
||||
where: { id: target.id },
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
if (req.body.delete_files) {
|
||||
@@ -61,7 +61,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||
where: {
|
||||
userId: target.id,
|
||||
},
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
Promise.all(promises).then((promised) => {
|
||||
@@ -71,10 +71,10 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||
|
||||
req.body.delete_files
|
||||
? logger.info(
|
||||
`User ${user.username} (${user.id}) deleted ${count} files of user ${newTarget.username} (${newTarget.id})`
|
||||
`User ${user.username} (${user.id}) deleted ${count} files of user ${newTarget.username} (${newTarget.id})`,
|
||||
)
|
||||
: logger.info(
|
||||
`User ${user.username} (${user.id}) deleted user ${newTarget.username} (${newTarget.id})`
|
||||
`User ${user.username} (${user.id}) deleted user ${newTarget.username} (${newTarget.id})`,
|
||||
);
|
||||
|
||||
delete newTarget.password;
|
||||
@@ -177,7 +177,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||
logger.debug(`updated user ${id} with ${JSON.stringify(newUser, jsonUserReplacer)}`);
|
||||
|
||||
logger.info(
|
||||
`User ${user.username} (${user.id}) updated ${target.username} (${newUser.username}) (${newUser.id})`
|
||||
`User ${user.username} (${user.id}) updated ${target.username} (${newUser.username}) (${newUser.id})`,
|
||||
);
|
||||
|
||||
delete newUser.password;
|
||||
|
||||
@@ -77,7 +77,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||
write_stream.close();
|
||||
logger.debug(`finished writing zip to ${path} at ${data.length} bytes written`);
|
||||
logger.info(
|
||||
`Export for ${user.username} (${user.id}) has completed and is available at ${export_name}`
|
||||
`Export for ${user.username} (${user.id}) has completed and is available at ${export_name}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -76,7 +76,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||
if (file.thumbnail?.name) await datasource.delete(file.thumbnail.name);
|
||||
|
||||
logger.info(
|
||||
`User ${user.username} (${user.id}) deleted an image ${file.name} (${file.id}) owned by ${file.user.username} (${file.user.id})`
|
||||
`User ${user.username} (${user.id}) deleted an image ${file.name} (${file.id}) owned by ${file.user.username} (${file.user.id})`,
|
||||
);
|
||||
|
||||
// @ts-ignore
|
||||
@@ -139,7 +139,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||
expiresAt: Date;
|
||||
maxViews: number;
|
||||
views: number;
|
||||
size: number;
|
||||
size: bigint;
|
||||
originalName: string;
|
||||
thumbnail?: { name: string };
|
||||
}[] = await prisma.file.findMany({
|
||||
|
||||
@@ -83,7 +83,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||
logger.debug(`added file ${fileIdParsed} to folder ${idParsed}`);
|
||||
|
||||
logger.info(
|
||||
`Added file "${file.name}" to folder "${folder.name}" for user ${user.username} (${user.id})`
|
||||
`Added file "${file.name}" to folder "${folder.name}" for user ${user.username} (${user.id})`,
|
||||
);
|
||||
|
||||
if (req.query.files) {
|
||||
@@ -94,7 +94,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||
|
||||
(folder.files[i] as unknown as { url: string }).url = formatRootUrl(
|
||||
config.uploader.route,
|
||||
folder.files[i].name
|
||||
folder.files[i].name,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -129,7 +129,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||
|
||||
(folder.files[i] as unknown as { url: string }).url = formatRootUrl(
|
||||
config.uploader.route,
|
||||
folder.files[i].name
|
||||
folder.files[i].name,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -213,7 +213,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||
logger.debug(`removed file ${fileIdParsed} from folder ${idParsed}`);
|
||||
|
||||
logger.info(
|
||||
`Removed file "${file.name}" from folder "${folder.name}" for user ${user.username} (${user.id})`
|
||||
`Removed file "${file.name}" from folder "${folder.name}" for user ${user.username} (${user.id})`,
|
||||
);
|
||||
|
||||
if (req.query.files) {
|
||||
@@ -224,7 +224,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||
|
||||
(folder.files[i] as unknown as { url: string }).url = formatRootUrl(
|
||||
config.uploader.route,
|
||||
folder.files[i].name
|
||||
folder.files[i].name,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -240,7 +240,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||
|
||||
(folder.files[i] as unknown as { url: string }).url = formatRootUrl(
|
||||
config.uploader.route,
|
||||
folder.files[i].name
|
||||
folder.files[i].name,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||
|
||||
if (files.length !== add.length)
|
||||
return res.badRequest(
|
||||
`files ${add.filter((id) => !files.find((file) => file.id === Number(id))).join(', ')} not found`
|
||||
`files ${add.filter((id) => !files.find((file) => file.id === Number(id))).join(', ')} not found`,
|
||||
);
|
||||
|
||||
const folder = await prisma.folder.create({
|
||||
@@ -87,7 +87,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||
|
||||
(folder.files[j] as unknown as { url: string }).url = formatRootUrl(
|
||||
config.uploader.route,
|
||||
folder.files[j].name
|
||||
folder.files[j].name,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||
error: 'oauth token expired',
|
||||
redirect_uri: discord_auth.oauth_url(
|
||||
zconfig.oauth.discord_client_id,
|
||||
`${zconfig.core.return_https ? 'https' : 'http'}://${req.headers.host}`
|
||||
`${zconfig.core.return_https ? 'https' : 'http'}://${req.headers.host}`,
|
||||
),
|
||||
});
|
||||
}
|
||||
@@ -60,7 +60,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||
error: 'oauth token expired',
|
||||
redirect_uri: discord_auth.oauth_url(
|
||||
zconfig.oauth.discord_client_id,
|
||||
`${zconfig.core.return_https ? 'https' : 'http'}://${req.headers.host}`
|
||||
`${zconfig.core.return_https ? 'https' : 'http'}://${req.headers.host}`,
|
||||
),
|
||||
});
|
||||
}
|
||||
@@ -80,7 +80,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||
const resp = await fetch(
|
||||
`https://people.googleapis.com/v1/people/me?access_token=${
|
||||
user.oauth.find((o) => o.provider === 'GOOGLE').token
|
||||
}&personFields=names,photos`
|
||||
}&personFields=names,photos`,
|
||||
);
|
||||
if (!resp.ok) {
|
||||
const provider = user.oauth.find((o) => o.provider === 'GOOGLE');
|
||||
@@ -91,7 +91,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||
error: 'oauth token expired',
|
||||
redirect_uri: google_auth.oauth_url(
|
||||
zconfig.oauth.google_client_id,
|
||||
`${zconfig.core.return_https ? 'https' : 'http'}://${req.headers.host}`
|
||||
`${zconfig.core.return_https ? 'https' : 'http'}://${req.headers.host}`,
|
||||
),
|
||||
});
|
||||
}
|
||||
@@ -114,7 +114,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||
error: 'oauth token expired',
|
||||
redirect_uri: google_auth.oauth_url(
|
||||
zconfig.oauth.google_client_id,
|
||||
`${zconfig.core.return_https ? 'https' : 'http'}://${req.headers.host}`
|
||||
`${zconfig.core.return_https ? 'https' : 'http'}://${req.headers.host}`,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||
logger.debug(
|
||||
`body(${JSON.stringify(req.body)}): verify_totp_code(${user.totpSecret}, ${
|
||||
req.body.code
|
||||
}) => ${success}`
|
||||
}) => ${success}`,
|
||||
);
|
||||
|
||||
if (!success) return res.badRequest('Invalid code');
|
||||
|
||||
@@ -15,7 +15,7 @@ const sortByValidator = s.enum(
|
||||
'size',
|
||||
'name',
|
||||
'mimetype',
|
||||
] satisfies (keyof Prisma.FileOrderByWithRelationInput)[])
|
||||
] satisfies (keyof Prisma.FileOrderByWithRelationInput)[]),
|
||||
);
|
||||
|
||||
const orderValidator = s.enum('asc', 'desc');
|
||||
@@ -83,7 +83,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||
maxViews: number;
|
||||
views: number;
|
||||
folderId: number;
|
||||
size: number;
|
||||
size: bigint;
|
||||
password: string | boolean;
|
||||
thumbnail?: { name: string };
|
||||
}[] = await prisma.file.findMany({
|
||||
|
||||
@@ -38,7 +38,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||
for (let i = 0; i !== urls.length; ++i) {
|
||||
(urls[i] as unknown as { url: string }).url = formatRootUrl(
|
||||
config.urls.route,
|
||||
urls[i].vanity ?? urls[i].id
|
||||
urls[i].vanity ?? urls[i].id,
|
||||
);
|
||||
}
|
||||
return res.json(urls);
|
||||
|
||||
@@ -7,7 +7,7 @@ async function handler(_: NextApiReq, res: NextApiRes) {
|
||||
|
||||
const pkg = JSON.parse(await readFile('package.json', 'utf8'));
|
||||
|
||||
const re = await fetch('https://zipline.diced.tech/api/version?c=' + pkg.version);
|
||||
const re = await fetch('https://zipline.diced.sh/api/version?c=' + pkg.version);
|
||||
const json = await re.json();
|
||||
|
||||
let updateToType = 'stable';
|
||||
|
||||
@@ -12,6 +12,7 @@ type LimitedFolder = {
|
||||
createdAt: Date | string;
|
||||
mimetype: string;
|
||||
views: number;
|
||||
size: bigint;
|
||||
}[];
|
||||
user: {
|
||||
username: string;
|
||||
@@ -83,6 +84,7 @@ export const getServerSideProps: GetServerSideProps<Props> = async (context) =>
|
||||
views: true,
|
||||
createdAt: true,
|
||||
password: true,
|
||||
size: true,
|
||||
},
|
||||
},
|
||||
user: {
|
||||
@@ -101,7 +103,7 @@ export const getServerSideProps: GetServerSideProps<Props> = async (context) =>
|
||||
for (let j = 0; j !== folder.files.length; ++j) {
|
||||
(folder.files[j] as unknown as { url: string }).url = formatRootUrl(
|
||||
config.uploader.route,
|
||||
folder.files[j].name
|
||||
folder.files[j].name,
|
||||
);
|
||||
|
||||
// @ts-ignore
|
||||
|
||||
@@ -57,7 +57,7 @@ export default function EmbeddedFile({
|
||||
img.addEventListener('load', function () {
|
||||
if (this.naturalWidth > innerWidth)
|
||||
imageEl.width = Math.floor(
|
||||
this.naturalWidth * Math.min(innerHeight / this.naturalHeight, innerWidth / this.naturalWidth)
|
||||
this.naturalWidth * Math.min(innerHeight / this.naturalHeight, innerWidth / this.naturalWidth),
|
||||
);
|
||||
else imageEl.width = this.naturalWidth;
|
||||
});
|
||||
@@ -128,6 +128,7 @@ export default function EmbeddedFile({
|
||||
</>
|
||||
)}
|
||||
|
||||
<meta property='og:type' content={'video.other'} />
|
||||
<meta property='og:url' content={`${host}/r/${file.name}`} />
|
||||
<meta property='og:video' content={`${host}/r/${file.name}`} />
|
||||
<meta property='og:video:url' content={`${host}/r/${file.name}`} />
|
||||
|
||||
@@ -12,7 +12,7 @@ async function main() {
|
||||
}
|
||||
|
||||
const files = (await readdir(temp)).filter(
|
||||
(x) => x.startsWith('zipline_partial_') || x.startsWith('zipline_thumb_')
|
||||
(x) => x.startsWith('zipline_partial_') || x.startsWith('zipline_thumb_'),
|
||||
);
|
||||
if (files.length === 0) {
|
||||
console.log('No partial files found, exiting..');
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { readdir, readFile } from 'fs/promises';
|
||||
import { statSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import config from 'lib/config';
|
||||
import datasource from 'lib/datasource';
|
||||
import { guess } from 'lib/mimes';
|
||||
import { migrations } from 'server/util';
|
||||
import { bytesToHuman } from 'lib/utils/bytes';
|
||||
|
||||
async function main() {
|
||||
const directory = process.argv[2];
|
||||
@@ -25,13 +27,16 @@ async function main() {
|
||||
|
||||
for (let i = 0; i !== files.length; ++i) {
|
||||
const mime = await guess(files[i].split('.').pop());
|
||||
const { size } = statSync(join(directory, files[i]));
|
||||
|
||||
data.push({
|
||||
name: files[i],
|
||||
mimetype: mime,
|
||||
userId,
|
||||
size,
|
||||
});
|
||||
console.log(`Imported ${files[i]} (${mime} mimetype) to user ${userId}`);
|
||||
|
||||
console.log(`Imported ${files[i]} (${bytesToHuman(size)}) (${mime} mimetype) to user ${userId}`);
|
||||
}
|
||||
|
||||
process.env.DATABASE_URL = config.core.database_url;
|
||||
|
||||
@@ -64,7 +64,7 @@ async function main() {
|
||||
|
||||
notFound
|
||||
? console.log(
|
||||
'At least one file has been found to not exist in the datasource but was on the database. To remove these files, run the script with the --force-delete flag.'
|
||||
'At least one file has been found to not exist in the datasource but was on the database. To remove these files, run the script with the --force-delete flag.',
|
||||
)
|
||||
: console.log('Done.');
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ function dbFileDecorator(fastify: FastifyInstance, _, done) {
|
||||
|
||||
this.header('Content-Length', size);
|
||||
this.header('Content-Type', download ? 'application/octet-stream' : file.mimetype);
|
||||
this.header('Content-Disposition', `inline; filename="${file.originalName || file.name}"`);
|
||||
this.header('Content-Disposition', `inline; filename="${encodeURI(file.originalName || file.name)}"`);
|
||||
|
||||
return this.send(data);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { FastifyInstance, FastifyReply } from 'fastify';
|
||||
import fastifyPlugin from 'fastify-plugin';
|
||||
|
||||
function postUrlDecorator(fastify: FastifyInstance, _, done) {
|
||||
fastify.decorateReply('postUrl', postUrl);
|
||||
fastify.decorateReply('postUrl', postUrl.bind(fastify));
|
||||
done();
|
||||
|
||||
async function postUrl(this: FastifyReply, url: Url) {
|
||||
|
||||
@@ -28,6 +28,12 @@ const logger = Logger.get('server');
|
||||
|
||||
const server = fastify(genFastifyOpts());
|
||||
|
||||
// Normally I would never condone this, but I lack the patience to deal with this correctly.
|
||||
// This is just to get JSON.stringify to globally serialize BigInt's
|
||||
BigInt.prototype['toJSON'] = function () {
|
||||
return Number(this);
|
||||
};
|
||||
|
||||
if (dev) {
|
||||
server.addHook('onRoute', (opts) => {
|
||||
logger.child('route').debug(JSON.stringify(opts));
|
||||
@@ -87,7 +93,7 @@ async function start() {
|
||||
url: req.url,
|
||||
headers: req.headers,
|
||||
body: req.headers['content-type']?.startsWith('application/json') ? req.body : undefined,
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -179,7 +185,7 @@ Disallow: ${config.urls.route}
|
||||
.info(
|
||||
`started ${dev ? 'development' : 'production'} zipline@${version} server${
|
||||
config.features.headless ? ' (headless)' : ''
|
||||
}`
|
||||
}`,
|
||||
);
|
||||
|
||||
await clearInvites.bind(server)();
|
||||
|
||||
@@ -11,14 +11,14 @@ async function configPlugin(fastify: FastifyInstance, config: Config) {
|
||||
fastify.logger
|
||||
.error('Secret is not set!')
|
||||
.error(
|
||||
'Running Zipline as is, without a randomized secret is not recommended and leaves your instance at risk!'
|
||||
'Running Zipline as is, without a randomized secret is not recommended and leaves your instance at risk!',
|
||||
)
|
||||
.error('Please change your secret in the config file or environment variables.')
|
||||
.error(
|
||||
'The config file is located at `.env.local`, or if using docker-compose you can change the variables in the `docker-compose.yml` file.'
|
||||
'The config file is located at `.env.local`, or if using docker-compose you can change the variables in the `docker-compose.yml` file.',
|
||||
)
|
||||
.error(
|
||||
'It is recomended to use a secret that is alphanumeric and randomized. If you include special characters, surround the secret with quotes.'
|
||||
'It is recomended to use a secret that is alphanumeric and randomized. If you include special characters, surround the secret with quotes.',
|
||||
)
|
||||
.error('A way you can generate this is through a password manager you may have.');
|
||||
|
||||
@@ -41,7 +41,7 @@ async function configPlugin(fastify: FastifyInstance, config: Config) {
|
||||
.error("Found temporary files in Zipline's temp directory.")
|
||||
.error('This can happen if Zipline crashes or is stopped while chunking a file.')
|
||||
.error(
|
||||
'If you are sure that no files are currently being processed, you can delete the files in the temp directory.'
|
||||
'If you are sure that no files are currently being processed, you can delete the files in the temp directory.',
|
||||
)
|
||||
.error('The temp directory is located at: ' + config.core.temp_directory)
|
||||
.error('If you are unsure, you can safely ignore this message.');
|
||||
|
||||
@@ -3,15 +3,13 @@ import { FastifyInstance } from 'fastify';
|
||||
import fastifyPlugin from 'fastify-plugin';
|
||||
import { migrations } from 'server/util';
|
||||
|
||||
async function prismaPlugin(fastify: FastifyInstance, _, done) {
|
||||
async function prismaPlugin(fastify: FastifyInstance) {
|
||||
process.env.DATABASE_URL = fastify.config.core?.database_url;
|
||||
await migrations();
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
fastify.decorate('prisma', prisma);
|
||||
|
||||
done();
|
||||
}
|
||||
|
||||
export default fastifyPlugin(prismaPlugin, {
|
||||
|
||||
@@ -9,7 +9,7 @@ export default async function uploadsRoute(this: FastifyInstance, req: FastifyRe
|
||||
|
||||
const image = await this.prisma.file.findFirst({
|
||||
where: {
|
||||
OR: [{ name: id }, { invisible: { invis: decodeURI(encodeURI(id)) } }],
|
||||
OR: [{ name: id }, { name: decodeURI(id) }, { invisible: { invis: decodeURI(encodeURI(id)) } }],
|
||||
},
|
||||
});
|
||||
if (!image) return reply.rawFile(id);
|
||||
@@ -28,7 +28,7 @@ export async function uploadsRouteOnResponse(
|
||||
this: FastifyInstance,
|
||||
req: FastifyRequest,
|
||||
reply: FastifyReply,
|
||||
done: () => void
|
||||
done: () => void,
|
||||
) {
|
||||
if (reply.statusCode === 200) {
|
||||
const { id } = req.params as { id: string };
|
||||
|
||||
@@ -22,7 +22,7 @@ export async function urlsRouteOnResponse(
|
||||
this: FastifyInstance,
|
||||
req: FastifyRequest,
|
||||
reply: FastifyReply,
|
||||
done: () => void
|
||||
done: () => void,
|
||||
) {
|
||||
if (reply.statusCode === 200) {
|
||||
const { id } = req.params as { id: string };
|
||||
|
||||
@@ -59,7 +59,7 @@ export async function migrations() {
|
||||
} catch (error) {
|
||||
if (error.message.startsWith('P1001')) {
|
||||
logger.error(
|
||||
`Unable to connect to database \`${process.env.DATABASE_URL}\`, check your database connection`
|
||||
`Unable to connect to database \`${process.env.DATABASE_URL}\`, check your database connection`,
|
||||
);
|
||||
logger.debug(error);
|
||||
} else {
|
||||
|
||||
@@ -59,7 +59,7 @@ async function start() {
|
||||
logger.debug('starting worker');
|
||||
|
||||
const partials = await readdir(config.core.temp_directory).then((files) =>
|
||||
files.filter((x) => x.startsWith(`zipline_partial_${file.identifier}`))
|
||||
files.filter((x) => x.startsWith(`zipline_partial_${file.identifier}`)),
|
||||
);
|
||||
|
||||
const readChunks = partials.map((x) => {
|
||||
|
||||
Reference in New Issue
Block a user