feat: multiple stuffs
This commit is contained in:
parent
9c5b3f60d5
commit
5b9b454330
29 changed files with 185 additions and 41 deletions
1
.npmrc
Normal file
1
.npmrc
Normal file
|
@ -0,0 +1 @@
|
|||
package-lock=false
|
|
@ -55,7 +55,7 @@
|
|||
"fflate": "^0.7.4",
|
||||
"find-my-way": "^7.5.0",
|
||||
"katex": "^0.16.4",
|
||||
"mantine-datatable": "^1.8.6",
|
||||
"mantine-datatable": "2.0.1",
|
||||
"minio": "^7.0.32",
|
||||
"ms": "canary",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
|
|
|
@ -50,6 +50,7 @@ export default function FileModal({
|
|||
refresh,
|
||||
reducedActions = false,
|
||||
exifEnabled,
|
||||
compress,
|
||||
}: {
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
|
@ -58,6 +59,7 @@ export default function FileModal({
|
|||
refresh: () => void;
|
||||
reducedActions?: boolean;
|
||||
exifEnabled?: boolean;
|
||||
compress: boolean;
|
||||
}) {
|
||||
const deleteFile = useFileDelete();
|
||||
const favoriteFile = useFileFavorite();
|
||||
|
@ -215,11 +217,11 @@ export default function FileModal({
|
|||
<Stack>
|
||||
<Type
|
||||
file={file}
|
||||
src={`/r/${encodeURI(file.name)}`}
|
||||
src={`/r/${encodeURI(file.name)}?compress=${compress}`}
|
||||
alt={file.name}
|
||||
popup
|
||||
sx={{ minHeight: 200 }}
|
||||
style={{ minHeight: 200 }}
|
||||
sx={{ minHeight: 200, overflowY: 'scroll', maxHeight: 500 }}
|
||||
style={{ minHeight: 200, overflowY: 'scroll', maxHeight: 500 }}
|
||||
disableMediaPreview={false}
|
||||
overrideRender={overrideRender}
|
||||
setOverrideRender={setOverrideRender}
|
||||
|
|
|
@ -34,6 +34,7 @@ export default function File({
|
|||
exifEnabled,
|
||||
refreshImages,
|
||||
reducedActions = false,
|
||||
onDash,
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const deleteFile = useFileDelete();
|
||||
|
@ -57,9 +58,10 @@ export default function File({
|
|||
refresh={refresh}
|
||||
reducedActions={reducedActions}
|
||||
exifEnabled={exifEnabled}
|
||||
compress={onDash}
|
||||
/>
|
||||
|
||||
<Card sx={{ maxWidth: '100%', height: '100%' }} shadow='md'>
|
||||
<Card sx={{ maxWidth: '100%', height: '100%' }} shadow='md' onClick={() => setOpen(true)}>
|
||||
<Card.Section>
|
||||
<LoadingOverlay visible={loading} />
|
||||
<Type
|
||||
|
@ -78,9 +80,8 @@ export default function File({
|
|||
width: '100%',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
src={`/r/${encodeURI(image.name)}`}
|
||||
src={`/r/${encodeURI(image.name)}?compress=${onDash}`}
|
||||
alt={image.name}
|
||||
onClick={() => setOpen(true)}
|
||||
disableMediaPreview={disableMediaPreview}
|
||||
/>
|
||||
</Card.Section>
|
||||
|
|
|
@ -99,6 +99,7 @@ export default function ZiplineTheming({ Component, pageProps, ...props }) {
|
|||
},
|
||||
Modal: {
|
||||
defaultProps: {
|
||||
closeButtonProps: { size: 'lg' },
|
||||
centered: true,
|
||||
transitionProps: {
|
||||
exitDuration: 100,
|
||||
|
@ -118,7 +119,7 @@ export default function ZiplineTheming({ Component, pageProps, ...props }) {
|
|||
},
|
||||
LoadingOverlay: {
|
||||
defaultProps: {
|
||||
overlayProps: {
|
||||
overlayprops: {
|
||||
blur: 3,
|
||||
color: theme.colorScheme === 'dark' ? theme.colors.dark[6] : 'white',
|
||||
},
|
||||
|
|
|
@ -45,8 +45,8 @@ function Placeholder({ text, Icon, ...props }) {
|
|||
);
|
||||
|
||||
return (
|
||||
<Box sx={{ height: 200 }} {...props}>
|
||||
<Center sx={{ height: 200 }}>
|
||||
<Box sx={{ height: 320 }} {...props}>
|
||||
<Center sx={{ height: 320 }}>
|
||||
<PlaceholderContent text={text} Icon={Icon} />
|
||||
</Center>
|
||||
</Box>
|
||||
|
@ -158,6 +158,8 @@ export default function Type({ file, popup = false, disableMediaPreview, ...prop
|
|||
image: (
|
||||
<Image
|
||||
placeholder={<PlaceholderContent Icon={IconPhotoCancel} text={'Image failed to load...'} />}
|
||||
height={320}
|
||||
fit='contain'
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
|
|
|
@ -5,7 +5,7 @@ import File from 'components/File';
|
|||
import MutedText from 'components/MutedText';
|
||||
import { useRecent } from 'lib/queries/files';
|
||||
|
||||
export default function RecentFiles({ disableMediaPreview, exifEnabled }) {
|
||||
export default function RecentFiles({ disableMediaPreview, exifEnabled, compress }) {
|
||||
const recent = useRecent('media');
|
||||
|
||||
return (
|
||||
|
@ -25,6 +25,7 @@ export default function RecentFiles({ disableMediaPreview, exifEnabled }) {
|
|||
disableMediaPreview={disableMediaPreview}
|
||||
exifEnabled={exifEnabled}
|
||||
refreshImages={recent.refetch}
|
||||
onDash={compress}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
|
|
|
@ -22,7 +22,7 @@ import { useRecoilValue } from 'recoil';
|
|||
import RecentFiles from './RecentFiles';
|
||||
import { StatCards } from './StatCards';
|
||||
|
||||
export default function Dashboard({ disableMediaPreview, exifEnabled }) {
|
||||
export default function Dashboard({ disableMediaPreview, exifEnabled, compress }) {
|
||||
const user = useRecoilValue(userSelector);
|
||||
|
||||
const recent = useRecent('media');
|
||||
|
@ -138,6 +138,7 @@ export default function Dashboard({ disableMediaPreview, exifEnabled }) {
|
|||
refresh={() => files.refetch()}
|
||||
reducedActions={false}
|
||||
exifEnabled={exifEnabled}
|
||||
compress={compress}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
@ -148,7 +149,7 @@ export default function Dashboard({ disableMediaPreview, exifEnabled }) {
|
|||
|
||||
<StatCards />
|
||||
|
||||
<RecentFiles disableMediaPreview={disableMediaPreview} exifEnabled={exifEnabled} />
|
||||
<RecentFiles disableMediaPreview={disableMediaPreview} exifEnabled={exifEnabled} compress={compress} />
|
||||
|
||||
<Box my='sm'>
|
||||
<Title>Files</Title>
|
||||
|
|
|
@ -10,7 +10,7 @@ import { useRouter } from 'next/router';
|
|||
import { useEffect, useState } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
export default function FilePagation({ disableMediaPreview, exifEnabled, queryPage }) {
|
||||
export default function FilePagation({ disableMediaPreview, exifEnabled, queryPage, compress }) {
|
||||
const [checked, setChecked] = useRecoilState(showNonMediaSelector);
|
||||
const [numPages, setNumPages] = useState(Number(queryPage)); // just set it to the queryPage, since the req may have not loaded yet
|
||||
const [page, setPage] = useState(Number(queryPage));
|
||||
|
@ -75,6 +75,7 @@ export default function FilePagation({ disableMediaPreview, exifEnabled, queryPa
|
|||
disableMediaPreview={disableMediaPreview}
|
||||
exifEnabled={exifEnabled}
|
||||
refreshImages={pages.refetch}
|
||||
onDash={compress}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
|
|
|
@ -7,7 +7,7 @@ import Link from 'next/link';
|
|||
import { useEffect, useState } from 'react';
|
||||
import FilePagation from './FilePagation';
|
||||
|
||||
export default function Files({ disableMediaPreview, exifEnabled, queryPage }) {
|
||||
export default function Files({ disableMediaPreview, exifEnabled, queryPage, compress }) {
|
||||
const [favoritePage, setFavoritePage] = useState(1);
|
||||
const [favoriteNumPages, setFavoriteNumPages] = useState(0);
|
||||
const favoritePages = usePaginatedFiles(favoritePage, 'media', true);
|
||||
|
@ -50,6 +50,7 @@ export default function Files({ disableMediaPreview, exifEnabled, queryPage }) {
|
|||
disableMediaPreview={disableMediaPreview}
|
||||
exifEnabled={exifEnabled}
|
||||
refreshImages={favoritePages.refetch}
|
||||
onDash={compress}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
|
@ -75,6 +76,7 @@ export default function Files({ disableMediaPreview, exifEnabled, queryPage }) {
|
|||
disableMediaPreview={disableMediaPreview}
|
||||
exifEnabled={exifEnabled}
|
||||
queryPage={queryPage}
|
||||
compress={compress}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -3,7 +3,14 @@ import File from 'components/File';
|
|||
import MutedText from 'components/MutedText';
|
||||
import { useFolder } from 'lib/queries/folders';
|
||||
|
||||
export default function ViewFolderFilesModal({ open, setOpen, folderId, disableMediaPreview, exifEnabled }) {
|
||||
export default function ViewFolderFilesModal({
|
||||
open,
|
||||
setOpen,
|
||||
folderId,
|
||||
disableMediaPreview,
|
||||
exifEnabled,
|
||||
compress,
|
||||
}) {
|
||||
if (!folderId) return null;
|
||||
|
||||
const folder = useFolder(folderId, true);
|
||||
|
@ -26,6 +33,7 @@ export default function ViewFolderFilesModal({ open, setOpen, folderId, disableM
|
|||
image={file}
|
||||
exifEnabled={exifEnabled}
|
||||
refreshImages={folder.refetch}
|
||||
onDash={compress}
|
||||
/>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
|
|
|
@ -22,7 +22,7 @@ import { useEffect, useState } from 'react';
|
|||
import CreateFolderModal from './CreateFolderModal';
|
||||
import ViewFolderFilesModal from './ViewFolderFilesModal';
|
||||
|
||||
export default function Folders({ disableMediaPreview, exifEnabled }) {
|
||||
export default function Folders({ disableMediaPreview, exifEnabled, compress }) {
|
||||
const folders = useFolders();
|
||||
const [createOpen, setCreateOpen] = useState(false);
|
||||
const [createWithFile, setCreateWithFile] = useState(null);
|
||||
|
@ -113,6 +113,7 @@ export default function Folders({ disableMediaPreview, exifEnabled }) {
|
|||
folderId={activeFolderId}
|
||||
disableMediaPreview={disableMediaPreview}
|
||||
exifEnabled={exifEnabled}
|
||||
compress={compress}
|
||||
/>
|
||||
|
||||
<Group mb='md'>
|
||||
|
|
|
@ -5,11 +5,18 @@ export interface ConfigCore {
|
|||
port: number;
|
||||
database_url: string;
|
||||
logger: boolean;
|
||||
compression: ConfigCompression;
|
||||
|
||||
stats_interval: number;
|
||||
invites_interval: number;
|
||||
}
|
||||
|
||||
export interface ConfigCompression {
|
||||
enabled: boolean;
|
||||
threshold: number;
|
||||
on_dashboard: boolean;
|
||||
}
|
||||
|
||||
export interface ConfigDatasource {
|
||||
type: 'local' | 's3' | 'supabase';
|
||||
local: ConfigLocalDatasource;
|
||||
|
|
|
@ -64,6 +64,9 @@ export default function readConfig() {
|
|||
map('CORE_LOGGER', 'boolean', 'core.logger'),
|
||||
map('CORE_STATS_INTERVAL', 'number', 'core.stats_interval'),
|
||||
map('CORE_INVITES_INTERVAL', 'number', 'core.invites_interval'),
|
||||
map('CORE_COMPRESSION_ENABLED', 'boolean', 'core.compression.enabled'),
|
||||
map('CORE_COMPRESSION_THRESHOLD', 'human-to-byte', 'core.compression.threshold'),
|
||||
map('CORE_COMPRESSION_ON_DASHBOARD', 'boolean', 'core.compression.on_dashboard'),
|
||||
|
||||
map('DATASOURCE_TYPE', 'string', 'datasource.type'),
|
||||
|
||||
|
|
|
@ -34,6 +34,16 @@ const validator = s.object({
|
|||
logger: s.boolean.default(false),
|
||||
stats_interval: s.number.default(1800),
|
||||
invites_interval: s.number.default(1800),
|
||||
compression: s
|
||||
.object({
|
||||
enabled: s.boolean.default(false),
|
||||
threshold: s.number.default(2048),
|
||||
on_dashboard: s.boolean.default(true),
|
||||
})
|
||||
.default({
|
||||
enabled: false,
|
||||
on_dashboard: false,
|
||||
}),
|
||||
}),
|
||||
datasource: s
|
||||
.object({
|
||||
|
|
|
@ -23,7 +23,7 @@ export function parseContent(
|
|||
image: content.embed.image,
|
||||
}
|
||||
: null,
|
||||
url: args[3],
|
||||
url: args.link,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -64,6 +64,7 @@ export const getServerSideProps: GetServerSideProps<ServerSideProps> = async (ct
|
|||
max_size: config.chunks.max_size,
|
||||
totp_enabled: config.mfa.totp_enabled,
|
||||
exif_enabled: config.exif.enabled,
|
||||
compress: config.core.compression.on_dashboard,
|
||||
} as ServerSideProps,
|
||||
};
|
||||
|
||||
|
|
|
@ -24,11 +24,21 @@ export function parseString(str: string, value: ParseValue) {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (matches.groups.prop in ['password', 'avatar']) {
|
||||
if (['password', 'avatar'].includes(matches.groups.prop)) {
|
||||
str = replaceCharsFromString(str, '{unknown_property}', matches.index, re.lastIndex);
|
||||
re.lastIndex = matches.index;
|
||||
continue;
|
||||
}
|
||||
if (['originalName', 'name'].includes(matches.groups.prop)) {
|
||||
str = replaceCharsFromString(
|
||||
str,
|
||||
decodeURIComponent(escape(getV[matches.groups.prop])),
|
||||
matches.index,
|
||||
re.lastIndex
|
||||
);
|
||||
re.lastIndex = matches.index;
|
||||
continue;
|
||||
}
|
||||
|
||||
const v = getV[matches.groups.prop];
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
username: 'administrator',
|
||||
password: await hashPassword('password'),
|
||||
token: createToken(),
|
||||
superAdmin: true,
|
||||
administrator: true,
|
||||
},
|
||||
});
|
||||
|
|
|
@ -72,9 +72,14 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
|||
return res.json(newTarget);
|
||||
});
|
||||
} else if (req.method === 'PATCH') {
|
||||
if (target.administrator && !user.superAdmin) return res.forbidden('cannot modify administrator');
|
||||
if (
|
||||
(target.administrator && !user.superAdmin) ||
|
||||
(target.administrator && user.administrator && !user.superAdmin)
|
||||
)
|
||||
return res.forbidden('cannot modify administrator');
|
||||
|
||||
logger.debug(`attempting to update user ${id} with ${JSON.stringify(req.body)}`);
|
||||
logger.debug(`user ${id} has ${!req.body.administrator} in administrator`);
|
||||
|
||||
if (req.body.password) {
|
||||
const hashed = await hashPassword(req.body.password);
|
||||
|
@ -84,7 +89,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
|||
});
|
||||
}
|
||||
|
||||
if (req.body.administrator) {
|
||||
if (typeof req.body.administrator != 'undefined') {
|
||||
await prisma.user.update({
|
||||
where: { id: target.id },
|
||||
data: { administrator: req.body.administrator },
|
||||
|
|
|
@ -99,6 +99,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
|||
id: true,
|
||||
favorite: true,
|
||||
views: true,
|
||||
folderId: true,
|
||||
maxViews: true,
|
||||
size: true,
|
||||
},
|
||||
|
|
|
@ -26,6 +26,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
|||
maxViews: true,
|
||||
folderId: true,
|
||||
size: true,
|
||||
favorite: true,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ export default function FilesPage(props) {
|
|||
disableMediaPreview={props.disable_media_preview}
|
||||
exifEnabled={props.exif_enabled}
|
||||
queryPage={props.queryPage}
|
||||
compress={props.compress}
|
||||
/>
|
||||
</Layout>
|
||||
</>
|
||||
|
|
|
@ -18,7 +18,11 @@ export default function FilesPage(props) {
|
|||
</Head>
|
||||
|
||||
<Layout props={props}>
|
||||
<Folders disableMediaPreview={props.disable_media_preview} exifEnabled={props.exif_enabled} />
|
||||
<Folders
|
||||
disableMediaPreview={props.disable_media_preview}
|
||||
exifEnabled={props.exif_enabled}
|
||||
compress={props.compress}
|
||||
/>
|
||||
</Layout>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -16,7 +16,11 @@ export default function DashboardPage(props) {
|
|||
<title>{props.title}</title>
|
||||
</Head>
|
||||
<Layout props={props}>
|
||||
<Dashboard disableMediaPreview={props.disable_media_preview} exifEnabled={props.exif_enabled} />
|
||||
<Dashboard
|
||||
disableMediaPreview={props.disable_media_preview}
|
||||
exifEnabled={props.exif_enabled}
|
||||
compress={props.compress}
|
||||
/>
|
||||
</Layout>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -25,9 +25,10 @@ type Props = {
|
|||
folder: LimitedFolder;
|
||||
uploadRoute: string;
|
||||
title: string;
|
||||
compress: boolean;
|
||||
};
|
||||
|
||||
export default function Folder({ title, folder }: Props) {
|
||||
export default function Folder({ title, folder, compress }: Props) {
|
||||
const full_title = `${title} - ${folder.name}`;
|
||||
return (
|
||||
<>
|
||||
|
@ -55,6 +56,7 @@ export default function Folder({ title, folder }: Props) {
|
|||
exifEnabled={false}
|
||||
refreshImages={null}
|
||||
reducedActions={true}
|
||||
onDash={compress}
|
||||
/>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
|
@ -113,6 +115,7 @@ export const getServerSideProps: GetServerSideProps<Props> = async (context) =>
|
|||
folder,
|
||||
uploadRoute: config.uploader.route,
|
||||
title: config.website.title,
|
||||
compress: config.core.compression.on_dashboard,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Box, Button, Modal, PasswordInput } from '@mantine/core';
|
||||
import type { File } from '@prisma/client';
|
||||
import config from 'lib/config';
|
||||
import Link from 'components/Link';
|
||||
import exts from 'lib/exts';
|
||||
import prisma from 'lib/prisma';
|
||||
|
@ -15,13 +16,18 @@ export default function EmbeddedFile({
|
|||
user,
|
||||
pass,
|
||||
prismRender,
|
||||
onDash,
|
||||
compress,
|
||||
}: {
|
||||
file: File;
|
||||
file: File & { imageProps?: HTMLImageElement };
|
||||
user: UserExtended;
|
||||
pass: boolean;
|
||||
prismRender: boolean;
|
||||
onDash: boolean;
|
||||
compress?: boolean;
|
||||
}) {
|
||||
const dataURL = (route: string) => `${route}/${encodeURI(file.name)}`;
|
||||
const dataURL = (route: string) =>
|
||||
`${route}/${encodeURI(file.name)}?compress=${compress == null ? onDash : compress}`;
|
||||
|
||||
const router = useRouter();
|
||||
const [opened, setOpened] = useState(pass);
|
||||
|
@ -60,6 +66,7 @@ export default function EmbeddedFile({
|
|||
if (url) {
|
||||
imageEl.src = url;
|
||||
}
|
||||
file.imageProps = img;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -94,7 +101,11 @@ export default function EmbeddedFile({
|
|||
|
||||
{file.mimetype.startsWith('image') && (
|
||||
<>
|
||||
<meta property='og:image' content={`/r/${file.name}`} />
|
||||
<meta property='og:type' content='image' />
|
||||
<meta property='og:image' itemProp='image' content={`/r/${file.name}`} />
|
||||
<meta property='og:url' content={`/r/${file.name}`} />
|
||||
<meta property='og:image:width' content={file.imageProps?.naturalWidth.toString()} />
|
||||
<meta property='og:image:height' content={file.imageProps?.naturalHeight.toString()} />
|
||||
<meta property='twitter:card' content='summary_large_image' />
|
||||
</>
|
||||
)}
|
||||
|
@ -116,6 +127,20 @@ export default function EmbeddedFile({
|
|||
<meta property='og:video:height' content='480' />
|
||||
</>
|
||||
)}
|
||||
{file.mimetype.startsWith('audio') && (
|
||||
<>
|
||||
<meta name='twitter:card' content='player' />
|
||||
<meta name='twitter:player:stream' content={`/r/${file.name}`} />
|
||||
<meta name='twitter:player:stream:content_type' content={file.mimetype} />
|
||||
<meta name='twitter:title' content={file.name} />
|
||||
|
||||
<meta property='og:type' content='music.song' />
|
||||
<meta property='og:url' content={`/r/${file.name}`} />
|
||||
<meta property='og:audio' content={`/r/${file.name}`} />
|
||||
<meta property='og:audio:secure_url' content={`/r/${file.name}`} />
|
||||
<meta property='og:audio:type' content={file.mimetype} />
|
||||
</>
|
||||
)}
|
||||
{!file.mimetype.startsWith('video') && !file.mimetype.startsWith('image') && (
|
||||
<meta property='og:url' content={`/r/${file.name}`} />
|
||||
)}
|
||||
|
@ -155,12 +180,18 @@ export default function EmbeddedFile({
|
|||
)}
|
||||
|
||||
{file.mimetype.startsWith('video') && (
|
||||
<video src={dataURL('/r')} controls={true} autoPlay={true} id='image_content' />
|
||||
<video src={dataURL('/r')} controls autoPlay muted id='video_content' />
|
||||
)}
|
||||
|
||||
{!file.mimetype.startsWith('video') && !file.mimetype.startsWith('image') && (
|
||||
<Link href={dataURL('/r')}>Can't preview this file. Click here to download it.</Link>
|
||||
{file.mimetype.startsWith('audio') && (
|
||||
<audio src={dataURL('/r')} controls autoPlay muted id='audio_content' />
|
||||
)}
|
||||
|
||||
{!file.mimetype.startsWith('video') &&
|
||||
!file.mimetype.startsWith('image') &&
|
||||
!file.mimetype.startsWith('audio') && (
|
||||
<Link href={dataURL('/r')}>Can't preview this file. Click here to download it.</Link>
|
||||
)}
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
|
@ -168,7 +199,7 @@ export default function EmbeddedFile({
|
|||
|
||||
export const getServerSideProps: GetServerSideProps = async (context) => {
|
||||
const { id } = context.params as { id: string };
|
||||
|
||||
const { compress = null } = context.query as unknown as { compress?: boolean };
|
||||
const file = await prisma.file.findFirst({
|
||||
where: {
|
||||
OR: [{ name: id }, { invisible: { invis: decodeURI(encodeURI(id)) } }],
|
||||
|
@ -233,6 +264,8 @@ export const getServerSideProps: GetServerSideProps = async (context) => {
|
|||
file,
|
||||
user,
|
||||
pass: file.password ? true : false,
|
||||
onDash: config.core.compression.on_dashboard,
|
||||
compress,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -2,26 +2,65 @@ import { FastifyInstance, FastifyReply } from 'fastify';
|
|||
import { guess } from 'lib/mimes';
|
||||
import { extname } from 'path';
|
||||
import fastifyPlugin from 'fastify-plugin';
|
||||
import { createBrotliCompress, createDeflate, createGzip } from 'zlib';
|
||||
import pump from 'pump';
|
||||
import { Transform } from 'stream';
|
||||
|
||||
function rawFileDecorator(fastify: FastifyInstance, _, done) {
|
||||
fastify.decorateReply('rawFile', rawFile);
|
||||
done();
|
||||
|
||||
async function rawFile(this: FastifyReply, id: string) {
|
||||
const { download } = this.request.query as { download?: string };
|
||||
const { download, compress } = this.request.query as { download?: string; compress?: boolean };
|
||||
|
||||
const data = await this.server.datasource.get(id);
|
||||
if (!data) return this.notFound();
|
||||
|
||||
const mimetype = await guess(extname(id).slice(1));
|
||||
const size = await this.server.datasource.size(id);
|
||||
|
||||
this.header('Content-Length', size);
|
||||
this.header('Content-Type', download ? 'application/octet-stream' : mimetype);
|
||||
|
||||
if (
|
||||
this.server.config.core.compression.enabled &&
|
||||
compress &&
|
||||
!this.request.headers['X-Zipline-NoCompress'] &&
|
||||
!!this.request.headers['accept-encoding']
|
||||
)
|
||||
if (size > this.server.config.core.compression.threshold)
|
||||
return this.send(useCompress.call(this, data));
|
||||
this.header('Content-Length', size);
|
||||
return this.send(data);
|
||||
}
|
||||
}
|
||||
|
||||
function useCompress(this: FastifyReply, data: NodeJS.ReadableStream) {
|
||||
let compress: Transform;
|
||||
|
||||
switch ((this.request.headers['accept-encoding'] as string).split(', ')[0]) {
|
||||
case 'gzip':
|
||||
case 'x-gzip':
|
||||
compress = createGzip();
|
||||
this.header('Content-Encoding', 'gzip');
|
||||
break;
|
||||
case 'deflate':
|
||||
compress = createDeflate();
|
||||
this.header('Content-Encoding', 'deflate');
|
||||
break;
|
||||
case 'br':
|
||||
compress = createBrotliCompress();
|
||||
this.header('Content-Encoding', 'br');
|
||||
break;
|
||||
default:
|
||||
this.server.logger
|
||||
.child('response')
|
||||
.error(`Unsupported encoding: ${this.request.headers['accept-encoding']}}`);
|
||||
break;
|
||||
}
|
||||
if (!compress) return data;
|
||||
setTimeout(() => compress.destroy(), 2000);
|
||||
return pump(data, compress, (err) => (err ? this.server.logger.error(err) : null));
|
||||
}
|
||||
|
||||
export default fastifyPlugin(rawFileDecorator, {
|
||||
name: 'rawFile',
|
||||
decorators: {
|
||||
|
|
14
yarn.lock
14
yarn.lock
|
@ -7167,14 +7167,14 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mantine-datatable@npm:^1.8.6":
|
||||
version: 1.8.6
|
||||
resolution: "mantine-datatable@npm:1.8.6"
|
||||
"mantine-datatable@npm:2.0.1":
|
||||
version: 2.0.1
|
||||
resolution: "mantine-datatable@npm:2.0.1"
|
||||
peerDependencies:
|
||||
"@mantine/core": ^5.10.4
|
||||
"@mantine/hooks": ^5.10.4
|
||||
"@mantine/core": ^6.0.0
|
||||
"@mantine/hooks": ^6.0.0
|
||||
react: ^18.2.0
|
||||
checksum: a4558418067ada3e269e3d9dc0153b613b031ecd5fccd484b45a2b13e403084971870515d51c888d933eb2f4d85f6df2ad221b0c84a2bf2cd602d27938bb9029
|
||||
checksum: aa39a662619373a82ed0408ffbe4ade71449abf281495af6820bfe9113d6de22005128d8ac1ff7d6a89f4919d58c1c2ceb0fdfa4e9be37050350d2635f595f6b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
@ -11729,7 +11729,7 @@ __metadata:
|
|||
fflate: ^0.7.4
|
||||
find-my-way: ^7.5.0
|
||||
katex: ^0.16.4
|
||||
mantine-datatable: ^1.8.6
|
||||
mantine-datatable: 2.0.1
|
||||
minio: ^7.0.32
|
||||
ms: canary
|
||||
multer: ^1.4.5-lts.1
|
||||
|
|
Loading…
Reference in a new issue