Compare commits

...

18 Commits

Author SHA1 Message Date
diced
633dfd4712 feat(v3.7.5): version 2023-11-05 22:35:12 -08:00
Digital
e6ed7a36d5 feat: whitelisted discord & redirect uri oauth (#469)
* Allow Redirect URI Configuration

* Prettier

* Add Whitelisted Users

* Update discord.ts

* Whitespace

* Whitespace

---------

Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>
2023-11-05 22:01:43 -08:00
diced
93cb9eec4c fix: overwriting existing files #467 2023-11-05 21:53:16 -08:00
diced
4849cd8221 fix: add warning when wordlist missing #478 2023-11-04 15:24:00 -07:00
diced
89c58044a3 fix: imported files incl size #468 2023-11-04 15:20:37 -07:00
diced
40fb11256f fix: non-english characters encoding (#471) 2023-10-08 11:06:16 -07:00
diced
d112c3a509 feat/fix: UPLOADER_RANDOM_WORDS_sEPERATOR 2023-09-29 20:06:18 -07:00
Jayvin Hernandez
23af36563f fix: no size on folders page (#465) 2023-09-29 19:53:06 -07:00
Kashall
28db15eb77 fix: util method to check if variable is not null (#458)
* chore: fix oauth truthyness

* chore: remove unused util function

* chore: lint

---------

Co-authored-by: Jayvin Hernandez <gogojayvin923@gmail.com>
2023-09-09 09:57:00 -07:00
Lucas Reis
e9054bd3e5 fix: og video type (#462) 2023-09-09 09:54:13 -07:00
diced
713f857e28 feat(v3.7.4): version 2023-08-29 15:36:43 -07:00
diced
5d6768029f fix: WEBSITE_SHOW_VERSION=false works now (#450) 2023-08-29 15:33:20 -07:00
Jayvin Hernandez
72e24a8b86 fix: trailing spaces giphy (#449) 2023-08-29 15:25:26 -07:00
diced
86c3e780d1 fix: docker size optimizations 2023-08-12 23:58:19 -07:00
diced
5102620953 fix: letters cut off #448 2023-08-08 23:48:26 -07:00
diced
4d728f9f8b fix: domain 2023-08-08 23:46:46 -07:00
dicedtomato
faf5098357 feat(v3..7.3): version 2023-07-31 19:06:28 -07:00
diced
c4066fc851 fix: change domains 2023-07-31 18:28:21 -07:00
29 changed files with 137 additions and 60 deletions

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "zipline",
"version": "3.7.2",
"version": "3.7.5",
"license": "MIT",
"scripts": {
"dev": "npm-run-all build:server dev:run",

View File

@@ -1498,4 +1498,4 @@ wheat
white
whitesmoke
yellow
yellowgreen
yellowgreen

View File

@@ -1747,4 +1747,4 @@ zigzagsalamander
zonetailedpigeon
zooplankton
zopilote
zorilla
zorilla

View File

@@ -360,6 +360,11 @@ export default function Layout({ children, props }) {
compact
size='xl'
p='sm'
styles={{
label: {
overflow: 'unset',
},
}}
>
{user.username}
</Button>

View File

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

View File

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

View File

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

View File

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

View File

@@ -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',
@@ -144,7 +145,7 @@ const validator = s.object({
)
.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

View File

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

View File

@@ -19,7 +19,7 @@ export default async function formatFileName(nameFormat: NameFormat, originalNam
return name;
case 'gfycat':
return gfycat();
return gfycat() ?? random();
default:
return random();
}

View File

@@ -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[] = [];

View File

@@ -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,9 +33,9 @@ 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}` : ''
}`,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 ', '')
@@ -199,17 +209,20 @@ async function handler(req: NextApiReq, res: NextApiRes) {
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);
let 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']) {
fileName = (req.headers['x-zipline-filename'] as string) || fileName;
const existing = await prisma.file.findFirst({
where: {
name: fileName,
@@ -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,
},
});

View File

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

View File

@@ -12,6 +12,7 @@ type LimitedFolder = {
createdAt: Date | string;
mimetype: string;
views: number;
size: number;
}[];
user: {
username: string;
@@ -83,6 +84,7 @@ export const getServerSideProps: GetServerSideProps<Props> = async (context) =>
views: true,
createdAt: true,
password: true,
size: true,
},
},
user: {

View File

@@ -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}`} />

View File

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

View File

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

View File

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