feat: view other user files (#408)

* feat: Add the capability of viewing another user's images as admin.

* fix: add columns, oops...

* fix: Gotta check if the user's legit before letting them see

* fix: made administrators non-viewable

* Please don't reference yourself

* fix: superAdmin > admin

---------

Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>
This commit is contained in:
Jayvin Hernandez 2023-05-23 22:34:21 -07:00 committed by GitHub
parent a2c085719a
commit f40d65a9f7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 153 additions and 4 deletions

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

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