Merge branch 'trunk' into feature/oauth-authentik

This commit is contained in:
Jayvin Hernandez 2023-05-25 10:29:04 -07:00 committed by GitHub
commit a12b18c546
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 210 additions and 10 deletions

View file

@ -15,10 +15,10 @@ body:
id: version id: version
attributes: attributes:
label: Version label: Version
description: What version of Zipline are you using? description: What version (or docker image) of Zipline are you using?
options: options:
- latest (ghcr.io/diced/zipline or ghcr.io/diced/zipline:latest)
- upstream (ghcr.io/diced/zipline:trunk) - upstream (ghcr.io/diced/zipline:trunk)
- latest (ghcr.io/diced/zipline:latest)
- other (provide version in additional info) - other (provide version in additional info)
validations: validations:
required: true required: true

View file

@ -49,6 +49,7 @@ export default function FileModal({
reducedActions = false, reducedActions = false,
exifEnabled, exifEnabled,
compress, compress,
otherUser = false,
}: { }: {
open: boolean; open: boolean;
setOpen: (open: boolean) => void; setOpen: (open: boolean) => void;
@ -58,6 +59,7 @@ export default function FileModal({
reducedActions?: boolean; reducedActions?: boolean;
exifEnabled?: boolean; exifEnabled?: boolean;
compress: boolean; compress: boolean;
otherUser: boolean;
}) { }) {
const deleteFile = useFileDelete(); const deleteFile = useFileDelete();
const favoriteFile = useFileFavorite(); const favoriteFile = useFileFavorite();
@ -276,7 +278,7 @@ export default function FileModal({
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
)} )}
{reducedActions ? null : inFolder && !folders.isLoading ? ( {reducedActions || otherUser ? null : inFolder && !folders.isLoading ? (
<Tooltip <Tooltip
label={`Remove from folder "${folders.data.find((f) => f.id === file.folderId)?.name ?? ''}"`} label={`Remove from folder "${folders.data.find((f) => f.id === file.folderId)?.name ?? ''}"`}
> >

View file

@ -32,9 +32,10 @@ export default function File({
image, image,
disableMediaPreview, disableMediaPreview,
exifEnabled, exifEnabled,
refreshImages, refreshImages = undefined,
reducedActions = false, reducedActions = false,
onDash, onDash,
otherUser = false,
}) { }) {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const deleteFile = useFileDelete(); const deleteFile = useFileDelete();
@ -44,7 +45,7 @@ export default function File({
const folders = useFolders(); const folders = useFolders();
const refresh = () => { const refresh = () => {
refreshImages(); if (!otherUser) refreshImages();
folders.refetch(); folders.refetch();
}; };
@ -59,6 +60,7 @@ export default function File({
reducedActions={reducedActions} reducedActions={reducedActions}
exifEnabled={exifEnabled} exifEnabled={exifEnabled}
compress={onDash} compress={onDash}
otherUser={otherUser}
/> />
<Card sx={{ maxWidth: '100%', height: '100%' }} shadow='md' onClick={() => setOpen(true)}> <Card sx={{ maxWidth: '100%', height: '100%' }} shadow='md' onClick={() => setOpen(true)}>

View file

@ -126,6 +126,7 @@ export default function Dashboard({ disableMediaPreview, exifEnabled, compress }
reducedActions={false} reducedActions={false}
exifEnabled={exifEnabled} exifEnabled={exifEnabled}
compress={compress} compress={compress}
otherUser={false}
/> />
)} )}

View file

@ -0,0 +1,82 @@
import { ActionIcon, Button, Center, Group, SimpleGrid, Title } from '@mantine/core';
import { File } from '@prisma/client';
import { IconArrowLeft, IconFile } from '@tabler/icons-react';
import FileComponent from 'components/File';
import MutedText from 'components/MutedText';
import useFetch from 'hooks/useFetch';
import { userSelector } from 'lib/recoil/user';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
import { useRecoilState } from 'recoil';
type UserFiles = {
id: number;
username: string;
files?: File[];
error?: unknown;
};
export default function UserFiles({ userId, disableMediaPreview, exifEnabled, compress }) {
const [currentUser, viewUser] = useState<UserFiles>({ id: 0, username: 'user' });
const [self] = useRecoilState(userSelector);
const { push } = useRouter();
useEffect(() => {
if (self.id == userId) push('/dashboard/files');
(async () => {
const user: UserFiles = await useFetch(`/api/user/${userId}`);
if (!user.error) {
viewUser(user);
} else {
push('/dashboard');
}
})();
}, [userId]);
if (!currentUser.files || currentUser.files.length === 0) {
return (
<Center sx={{ flexDirection: 'column' }}>
<Group>
<div>
<IconFile size={48} />
</div>
<div>
<Title>Nothing here</Title>
<MutedText size='md'>
{currentUser.username} seems to have not uploaded any files... yet
</MutedText>
</div>
<Button size='md' onClick={() => push('/dashboard/users')}>
Head back?
</Button>
</Group>
</Center>
);
}
return (
<>
<Group mb='md'>
<ActionIcon size='lg' onClick={() => push('/dashboard/users')} color='primary'>
<IconArrowLeft />
</ActionIcon>
<Title>{currentUser.username}&apos;s Files</Title>
</Group>
<SimpleGrid cols={3} spacing='lg' breakpoints={[{ maxWidth: 'sm', cols: 1, spacing: 'sm' }]}>
{currentUser.files.map((file) => (
<div key={file.id}>
<FileComponent
image={file}
disableMediaPreview={disableMediaPreview}
exifEnabled={exifEnabled}
onDash={compress}
otherUser={true}
/>
</div>
))}
</SimpleGrid>
</>
);
}

View file

@ -6,6 +6,7 @@ import type { User } from '@prisma/client';
import { import {
IconClipboardCopy, IconClipboardCopy,
IconEdit, IconEdit,
IconExternalLink,
IconGridDots, IconGridDots,
IconList, IconList,
IconUserExclamation, IconUserExclamation,
@ -116,6 +117,10 @@ export default function Users() {
} }
}; };
const openUser = async (user) => {
await router.push(`/dashboard/users/${user.id}`);
};
useEffect(() => { useEffect(() => {
updateUsers(); updateUsers();
}, []); }, []);
@ -181,6 +186,13 @@ export default function Users() {
<IconEdit size='1rem' /> <IconEdit size='1rem' />
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
{(!self.superAdmin && user.superAdmin) || (self.superAdmin && user.superAdmin) ? null : (
<Tooltip label='Open user'>
<ActionIcon color='cyan' onClick={() => openUser(user)}>
<IconExternalLink size='1rem' />
</ActionIcon>
</Tooltip>
)}
</Group> </Group>
), ),
}, },

View file

@ -14,6 +14,10 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
where: { where: {
id: Number(id), id: Number(id),
}, },
include: {
files: true,
Folder: true,
},
}); });
if (!target) return res.notFound('user not found'); if (!target) return res.notFound('user not found');
@ -175,6 +179,10 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
} else { } else {
delete target.password; delete target.password;
if (user.superAdmin && target.superAdmin) delete target.files;
if (user.administrator && !user.superAdmin && (target.administrator || target.superAdmin))
delete target.files;
return res.json(target); return res.json(target);
} }
} }

View file

@ -31,15 +31,46 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
} else { } else {
if (!req.body.id) return res.badRequest('no file id'); if (!req.body.id) return res.badRequest('no file id');
const file = await prisma.file.delete({ let file = await prisma.file.findFirst({
where: { where: {
id: req.body.id, id: req.body.id,
userId: user.id,
},
include: {
user: {
select: {
administrator: true,
superAdmin: true,
username: true,
id: true,
},
},
},
});
if (!file && (!user.administrator || !user.superAdmin)) return res.notFound('file not found');
file = await prisma.file.delete({
where: {
id: req.body.id,
},
include: {
user: {
select: {
administrator: true,
superAdmin: true,
username: true,
id: true,
},
},
}, },
}); });
await datasource.delete(file.name); await datasource.delete(file.name);
logger.info(`User ${user.username} (${user.id}) deleted an image ${file.name} (${file.id})`); logger.info(
`User ${user.username} (${user.id}) deleted an image ${file.name} (${file.id}) owned by ${file.user.username} (${file.user.id})`
);
// @ts-ignore // @ts-ignore
if (file.password) file.password = true; if (file.password) file.password = true;
@ -51,14 +82,33 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
let file; let file;
if (req.body.favorite !== null) if (req.body.favorite !== null) {
file = await prisma.file.findFirst({
where: {
id: req.body.id,
userId: user.id,
},
include: {
user: {
select: {
administrator: true,
superAdmin: true,
username: true,
id: true,
},
},
},
});
if (!file && (!user.administrator || !user.superAdmin)) return res.notFound('file not found');
file = await prisma.file.update({ file = await prisma.file.update({
where: { id: req.body.id }, where: { id: req.body.id },
data: { data: {
favorite: req.body.favorite, favorite: req.body.favorite,
}, },
}); });
}
// @ts-ignore // @ts-ignore
if (file.password) file.password = true; if (file.password) file.password = true;
return res.json(file); return res.json(file);

View file

@ -0,0 +1,42 @@
import { LoadingOverlay } from '@mantine/core';
import Layout from 'components/Layout';
import UserFiles from 'components/pages/Users/UserFiles';
import useLogin from 'hooks/useLogin';
import Head from 'next/head';
import { getServerSideProps as middlewareProps } from 'middleware/getServerSideProps';
import { GetServerSideProps } from 'next';
export default function UsersId(props) {
const { loading } = useLogin();
if (loading) return <LoadingOverlay visible={loading} />;
const title = `${props.title} - User - ${props.userId}`;
return (
<>
<Head>
<title>{title}</title>
</Head>
<Layout props={props}>
<UserFiles
userId={props.userId}
disableMediaPreview={props.disable_media_preview}
exifEnabled={props.exif_enabled}
compress={props.compress}
/>
</Layout>
</>
);
}
export const getServerSideProps: GetServerSideProps = async (context) => {
const { id } = context.params as { id: string };
// @ts-ignore
const { props } = await middlewareProps(context);
return {
props: {
userId: id,
...props,
},
};
};

View file

@ -10,7 +10,7 @@ export default function UsersPage(props) {
if (loading) return <LoadingOverlay visible={loading} />; if (loading) return <LoadingOverlay visible={loading} />;
const title = `${props.title} - User`; const title = `${props.title} - Users`;
return ( return (
<> <>
<Head> <Head>

View file

@ -61,6 +61,7 @@ export async function migrations() {
logger.error( 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 { } else {
logger.error('Failed to migrate database... exiting...'); logger.error('Failed to migrate database... exiting...');
logger.error(error); logger.error(error);