diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml
index c9180e6..c7f9609 100644
--- a/.github/ISSUE_TEMPLATE/bug.yml
+++ b/.github/ISSUE_TEMPLATE/bug.yml
@@ -15,10 +15,10 @@ body:
id: version
attributes:
label: Version
- description: What version of Zipline are you using?
+ description: What version (or docker image) of Zipline are you using?
options:
+ - latest (ghcr.io/diced/zipline or ghcr.io/diced/zipline:latest)
- upstream (ghcr.io/diced/zipline:trunk)
- - latest (ghcr.io/diced/zipline:latest)
- other (provide version in additional info)
validations:
required: true
diff --git a/src/components/File/FileModal.tsx b/src/components/File/FileModal.tsx
index ed57719..b583657 100644
--- a/src/components/File/FileModal.tsx
+++ b/src/components/File/FileModal.tsx
@@ -49,6 +49,7 @@ export default function FileModal({
reducedActions = false,
exifEnabled,
compress,
+ otherUser = false,
}: {
open: boolean;
setOpen: (open: boolean) => void;
@@ -58,6 +59,7 @@ export default function FileModal({
reducedActions?: boolean;
exifEnabled?: boolean;
compress: boolean;
+ otherUser: boolean;
}) {
const deleteFile = useFileDelete();
const favoriteFile = useFileFavorite();
@@ -276,7 +278,7 @@ export default function FileModal({
)}
- {reducedActions ? null : inFolder && !folders.isLoading ? (
+ {reducedActions || otherUser ? null : inFolder && !folders.isLoading ? (
f.id === file.folderId)?.name ?? ''}"`}
>
diff --git a/src/components/File/index.tsx b/src/components/File/index.tsx
index 8ab6a22..7670c44 100644
--- a/src/components/File/index.tsx
+++ b/src/components/File/index.tsx
@@ -32,9 +32,10 @@ export default function File({
image,
disableMediaPreview,
exifEnabled,
- refreshImages,
+ refreshImages = undefined,
reducedActions = false,
onDash,
+ otherUser = false,
}) {
const [open, setOpen] = useState(false);
const deleteFile = useFileDelete();
@@ -44,7 +45,7 @@ export default function File({
const folders = useFolders();
const refresh = () => {
- refreshImages();
+ if (!otherUser) refreshImages();
folders.refetch();
};
@@ -59,6 +60,7 @@ export default function File({
reducedActions={reducedActions}
exifEnabled={exifEnabled}
compress={onDash}
+ otherUser={otherUser}
/>
setOpen(true)}>
diff --git a/src/components/pages/Dashboard/index.tsx b/src/components/pages/Dashboard/index.tsx
index 8de2918..bcbfeff 100644
--- a/src/components/pages/Dashboard/index.tsx
+++ b/src/components/pages/Dashboard/index.tsx
@@ -126,6 +126,7 @@ export default function Dashboard({ disableMediaPreview, exifEnabled, compress }
reducedActions={false}
exifEnabled={exifEnabled}
compress={compress}
+ otherUser={false}
/>
)}
diff --git a/src/components/pages/Users/UserFiles.tsx b/src/components/pages/Users/UserFiles.tsx
new file mode 100644
index 0000000..0b4cc77
--- /dev/null
+++ b/src/components/pages/Users/UserFiles.tsx
@@ -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({ 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 (
+
+
+
+
+
+
+
Nothing here
+
+ {currentUser.username} seems to have not uploaded any files... yet
+
+
+
+
+
+ );
+ }
+
+ return (
+ <>
+
+ push('/dashboard/users')} color='primary'>
+
+
+ {currentUser.username}'s Files
+
+
+
+ {currentUser.files.map((file) => (
+
+
+
+ ))}
+
+ >
+ );
+}
diff --git a/src/components/pages/Users/index.tsx b/src/components/pages/Users/index.tsx
index 993c6d9..443d0b7 100644
--- a/src/components/pages/Users/index.tsx
+++ b/src/components/pages/Users/index.tsx
@@ -6,6 +6,7 @@ import type { User } from '@prisma/client';
import {
IconClipboardCopy,
IconEdit,
+ IconExternalLink,
IconGridDots,
IconList,
IconUserExclamation,
@@ -116,6 +117,10 @@ export default function Users() {
}
};
+ const openUser = async (user) => {
+ await router.push(`/dashboard/users/${user.id}`);
+ };
+
useEffect(() => {
updateUsers();
}, []);
@@ -181,6 +186,13 @@ export default function Users() {
+ {(!self.superAdmin && user.superAdmin) || (self.superAdmin && user.superAdmin) ? null : (
+
+ openUser(user)}>
+
+
+
+ )}
),
},
diff --git a/src/pages/api/user/[id].ts b/src/pages/api/user/[id].ts
index 651bc08..5767f3a 100644
--- a/src/pages/api/user/[id].ts
+++ b/src/pages/api/user/[id].ts
@@ -14,6 +14,10 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
where: {
id: Number(id),
},
+ include: {
+ files: true,
+ Folder: true,
+ },
});
if (!target) return res.notFound('user not found');
@@ -175,6 +179,10 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
} else {
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);
}
}
diff --git a/src/pages/api/user/files.ts b/src/pages/api/user/files.ts
index d8ce4b8..ab84430 100644
--- a/src/pages/api/user/files.ts
+++ b/src/pages/api/user/files.ts
@@ -31,15 +31,46 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
} else {
if (!req.body.id) return res.badRequest('no file id');
- const file = await prisma.file.delete({
+ let 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.delete({
+ where: {
+ id: req.body.id,
+ },
+ include: {
+ user: {
+ select: {
+ administrator: true,
+ superAdmin: true,
+ username: true,
+ id: true,
+ },
+ },
},
});
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
if (file.password) file.password = true;
@@ -51,14 +82,33 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
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({
where: { id: req.body.id },
data: {
favorite: req.body.favorite,
},
});
-
+ }
// @ts-ignore
if (file.password) file.password = true;
return res.json(file);
diff --git a/src/pages/dashboard/users/[id].tsx b/src/pages/dashboard/users/[id].tsx
new file mode 100644
index 0000000..03fa1bf
--- /dev/null
+++ b/src/pages/dashboard/users/[id].tsx
@@ -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 ;
+
+ const title = `${props.title} - User - ${props.userId}`;
+ return (
+ <>
+
+ {title}
+
+
+
+
+ >
+ );
+}
+
+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,
+ },
+ };
+};
diff --git a/src/pages/dashboard/users.tsx b/src/pages/dashboard/users/index.tsx
similarity index 93%
rename from src/pages/dashboard/users.tsx
rename to src/pages/dashboard/users/index.tsx
index 676b457..971df81 100644
--- a/src/pages/dashboard/users.tsx
+++ b/src/pages/dashboard/users/index.tsx
@@ -10,7 +10,7 @@ export default function UsersPage(props) {
if (loading) return ;
- const title = `${props.title} - User`;
+ const title = `${props.title} - Users`;
return (
<>
diff --git a/src/server/util.ts b/src/server/util.ts
index 9234d7b..dec7af0 100644
--- a/src/server/util.ts
+++ b/src/server/util.ts
@@ -61,6 +61,7 @@ export async function migrations() {
logger.error(
`Unable to connect to database \`${process.env.DATABASE_URL}\`, check your database connection`
);
+ logger.debug(error);
} else {
logger.error('Failed to migrate database... exiting...');
logger.error(error);