mirror of
https://github.com/diced/zipline.git
synced 2025-12-05 20:40:12 -08:00
feat: actions when viewing other user files (#918)
This commit is contained in:
@@ -88,11 +88,13 @@ export default function FileModal({
|
||||
setOpen,
|
||||
file,
|
||||
reduce,
|
||||
user,
|
||||
}: {
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
file?: File | null;
|
||||
reduce?: boolean;
|
||||
user?: string;
|
||||
}) {
|
||||
const clipboard = useClipboard();
|
||||
const warnDeletion = useSettingsStore((state) => state.settings.warnDeletion);
|
||||
@@ -226,7 +228,7 @@ export default function FileModal({
|
||||
)}
|
||||
</SimpleGrid>
|
||||
|
||||
{!reduce && (
|
||||
{!reduce && !user && (
|
||||
<SimpleGrid cols={{ base: 1, md: 2 }} spacing='md' my='xs'>
|
||||
<Box>
|
||||
<Title order={4} mt='lg' mb='xs'>
|
||||
|
||||
@@ -69,20 +69,23 @@ export async function bulkDelete(ids: string[], setSelectedFiles: (files: File[]
|
||||
});
|
||||
}
|
||||
|
||||
export async function bulkFavorite(ids: string[]) {
|
||||
export async function bulkFavorite(ids: string[], favorite: boolean) {
|
||||
const text = favorite ? 'favorite' : 'unfavorite';
|
||||
const textcaps = favorite ? 'Favorite' : 'Unfavorite';
|
||||
|
||||
modals.openConfirmModal({
|
||||
centered: true,
|
||||
title: `Favorite ${ids.length} file${ids.length === 1 ? '' : 's'}?`,
|
||||
children: `You are about to favorite ${ids.length} file${ids.length === 1 ? '' : 's'}.`,
|
||||
title: `${textcaps} ${ids.length} file${ids.length === 1 ? '' : 's'}?`,
|
||||
children: `You are about to ${text} ${ids.length} file${ids.length === 1 ? '' : 's'}.`,
|
||||
labels: {
|
||||
cancel: 'Cancel',
|
||||
confirm: 'Favorite',
|
||||
confirm: `${textcaps}`,
|
||||
},
|
||||
confirmProps: { color: 'yellow' },
|
||||
onConfirm: async () => {
|
||||
notifications.show({
|
||||
title: 'Favoriting files',
|
||||
message: `Favoriting ${ids.length} file${ids.length === 1 ? '' : 's'}`,
|
||||
title: `${textcaps}ing files`,
|
||||
message: `${textcaps}ing ${ids.length} file${ids.length === 1 ? '' : 's'}`,
|
||||
color: 'yellow',
|
||||
loading: true,
|
||||
id: 'bulk-favorite',
|
||||
@@ -96,13 +99,13 @@ export async function bulkFavorite(ids: string[]) {
|
||||
{
|
||||
files: ids,
|
||||
|
||||
favorite: true,
|
||||
favorite,
|
||||
},
|
||||
);
|
||||
|
||||
if (error) {
|
||||
notifications.update({
|
||||
title: 'Error while favoriting files',
|
||||
title: 'Error while modifying files',
|
||||
message: error.error,
|
||||
color: 'red',
|
||||
icon: <IconStarsOff size='1rem' />,
|
||||
@@ -112,8 +115,8 @@ export async function bulkFavorite(ids: string[]) {
|
||||
});
|
||||
} else if (data) {
|
||||
notifications.update({
|
||||
title: 'Favorited files',
|
||||
message: `Favorited ${data.count} file${ids.length === 1 ? '' : 's'}`,
|
||||
title: `${textcaps}d files`,
|
||||
message: `${textcaps}d ${data.count} file${ids.length === 1 ? '' : 's'}`,
|
||||
color: 'yellow',
|
||||
icon: <IconStarsFilled size='1rem' />,
|
||||
id: 'bulk-favorite',
|
||||
|
||||
@@ -388,6 +388,8 @@ export default function FileTable({
|
||||
}
|
||||
}, [searchField]);
|
||||
|
||||
const unfavoriteAll = selectedFiles.every((file) => file.favorite);
|
||||
|
||||
return (
|
||||
<>
|
||||
<FileModal
|
||||
@@ -396,6 +398,7 @@ export default function FileTable({
|
||||
if (!open) setSelectedFile(null);
|
||||
}}
|
||||
file={selectedFile}
|
||||
user={id}
|
||||
/>
|
||||
|
||||
<TableEditModal opened={tableEdit.open} onCLose={() => tableEdit.setOpen(false)} />
|
||||
@@ -427,11 +430,18 @@ export default function FileTable({
|
||||
variant='outline'
|
||||
color='yellow'
|
||||
leftSection={<IconStar size='1rem' />}
|
||||
onClick={() => bulkFavorite(selectedFiles.map((x) => x.id))}
|
||||
onClick={() =>
|
||||
bulkFavorite(
|
||||
selectedFiles.map((x) => x.id),
|
||||
!unfavoriteAll,
|
||||
)
|
||||
}
|
||||
>
|
||||
Favorite {selectedFiles.length} file{selectedFiles.length > 1 ? 's' : ''}
|
||||
{unfavoriteAll ? 'Unfavorite' : 'Favorite'} {selectedFiles.length} file
|
||||
{selectedFiles.length > 1 ? 's' : ''}
|
||||
</Button>
|
||||
|
||||
{!id && (
|
||||
<Combobox
|
||||
store={combobox}
|
||||
withinPortal={false}
|
||||
@@ -469,6 +479,7 @@ export default function FileTable({
|
||||
</Combobox.Options>
|
||||
</Combobox.Dropdown>
|
||||
</Combobox>
|
||||
)}
|
||||
</Group>
|
||||
|
||||
<Button
|
||||
|
||||
@@ -7,6 +7,7 @@ import { File, fileSelect } from '@/lib/db/models/file';
|
||||
import { log } from '@/lib/logger';
|
||||
import { userMiddleware } from '@/server/middleware/user';
|
||||
import fastifyPlugin from 'fastify-plugin';
|
||||
import { canInteract } from '@/lib/role';
|
||||
|
||||
export type ApiUserFilesIdResponse = File;
|
||||
|
||||
@@ -33,12 +34,13 @@ export default fastifyPlugin(
|
||||
const file = await prisma.file.findFirst({
|
||||
where: {
|
||||
OR: [{ id: req.params.id }, { name: req.params.id }],
|
||||
userId: req.user.id,
|
||||
},
|
||||
select: fileSelect,
|
||||
select: { User: true, ...fileSelect },
|
||||
});
|
||||
if (!file) return res.notFound();
|
||||
|
||||
if (!canInteract(req.user.role, file.User?.role ?? 'USER')) return res.notFound();
|
||||
|
||||
return res.send(file);
|
||||
});
|
||||
|
||||
@@ -49,12 +51,13 @@ export default fastifyPlugin(
|
||||
const file = await prisma.file.findFirst({
|
||||
where: {
|
||||
OR: [{ id: req.params.id }, { name: req.params.id }],
|
||||
userId: req.user.id,
|
||||
},
|
||||
select: fileSelect,
|
||||
select: { User: true, ...fileSelect },
|
||||
});
|
||||
if (!file) return res.notFound();
|
||||
|
||||
if (!canInteract(req.user.role, file.User?.role ?? 'USER')) return res.notFound();
|
||||
|
||||
const data: Prisma.FileUpdateInput = {};
|
||||
|
||||
if (req.body.favorite !== undefined) data.favorite = req.body.favorite;
|
||||
@@ -126,6 +129,7 @@ export default fastifyPlugin(
|
||||
logger.info(`${req.user.username} updated file ${newFile.name}`, {
|
||||
updated: Object.keys(req.body),
|
||||
id: newFile.id,
|
||||
owner: file.User?.id,
|
||||
});
|
||||
|
||||
return res.send(newFile);
|
||||
@@ -135,11 +139,15 @@ export default fastifyPlugin(
|
||||
const file = await prisma.file.findFirst({
|
||||
where: {
|
||||
OR: [{ id: req.params.id }, { name: req.params.id }],
|
||||
userId: req.user.id,
|
||||
},
|
||||
include: {
|
||||
User: true,
|
||||
},
|
||||
});
|
||||
if (!file) return res.notFound();
|
||||
|
||||
if (!canInteract(req.user.role, file.User?.role ?? 'USER')) return res.notFound();
|
||||
|
||||
const deletedFile = await prisma.file.delete({
|
||||
where: {
|
||||
id: file.id,
|
||||
@@ -151,6 +159,7 @@ export default fastifyPlugin(
|
||||
|
||||
logger.info(`${req.user.username} deleted file ${deletedFile.name}`, {
|
||||
size: bytes(deletedFile.size),
|
||||
owner: file.User?.id,
|
||||
});
|
||||
|
||||
return res.send(deletedFile);
|
||||
|
||||
@@ -2,6 +2,8 @@ import { datasource } from '@/lib/datasource';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { log } from '@/lib/logger';
|
||||
import { secondlyRatelimit } from '@/lib/ratelimits';
|
||||
import { canInteract } from '@/lib/role';
|
||||
import { Role } from '@/prisma/client';
|
||||
import { userMiddleware } from '@/server/middleware/user';
|
||||
import fastifyPlugin from 'fastify-plugin';
|
||||
|
||||
@@ -22,6 +24,18 @@ type Body = {
|
||||
|
||||
const logger = log('api').c('user').c('files').c('transaction');
|
||||
|
||||
function checkInteraction(current: Role, roles: Role[]) {
|
||||
const indices: number[] = [];
|
||||
|
||||
for (let i = 0; i !== roles.length; ++i) {
|
||||
if (!canInteract(current, roles[i])) {
|
||||
indices.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
return indices;
|
||||
}
|
||||
|
||||
export const PATH = '/api/user/files/transaction';
|
||||
export default fastifyPlugin(
|
||||
(server, _, done) => {
|
||||
@@ -34,14 +48,28 @@ export default fastifyPlugin(
|
||||
if (!files || !files.length) return res.badRequest('Cannot process transaction without files');
|
||||
|
||||
if (typeof favorite === 'boolean') {
|
||||
const toFavoriteFiles = await prisma.file.findMany({
|
||||
where: {
|
||||
id: { in: files },
|
||||
},
|
||||
include: {
|
||||
User: true,
|
||||
},
|
||||
});
|
||||
|
||||
const invalids = checkInteraction(
|
||||
req.user.role,
|
||||
toFavoriteFiles.map((f) => f.User?.role ?? 'USER'),
|
||||
);
|
||||
if (invalids.length > 0)
|
||||
return res.forbidden(`You don't have the permission to modify files[${invalids.join(', ')}]`);
|
||||
|
||||
const resp = await prisma.file.updateMany({
|
||||
where: {
|
||||
id: {
|
||||
in: files,
|
||||
},
|
||||
userId: req.user.id,
|
||||
},
|
||||
|
||||
data: {
|
||||
favorite: favorite,
|
||||
},
|
||||
@@ -51,6 +79,7 @@ export default fastifyPlugin(
|
||||
|
||||
logger.info(`${req.user.username} ${favorite ? 'favorited' : 'unfavorited'} ${resp.count} files`, {
|
||||
user: req.user.id,
|
||||
owners: toFavoriteFiles.map((f) => f.userId),
|
||||
});
|
||||
|
||||
return res.send(resp);
|
||||
@@ -108,21 +137,28 @@ export default fastifyPlugin(
|
||||
files: files.length,
|
||||
});
|
||||
|
||||
if (delete_datasourceFiles) {
|
||||
const dFiles = await prisma.file.findMany({
|
||||
const toDeleteFiles = await prisma.file.findMany({
|
||||
where: {
|
||||
id: {
|
||||
in: files,
|
||||
id: { in: files },
|
||||
},
|
||||
userId: req.user.id,
|
||||
include: {
|
||||
User: true,
|
||||
},
|
||||
});
|
||||
|
||||
for (let i = 0; i !== dFiles.length; ++i) {
|
||||
await datasource.delete(dFiles[i].name);
|
||||
const invalids = checkInteraction(
|
||||
req.user.role,
|
||||
toDeleteFiles.map((f) => f.User?.role ?? 'USER'),
|
||||
);
|
||||
if (invalids.length > 0)
|
||||
return res.forbidden(`You don't have the permission to delete files[${invalids.join(', ')}]`);
|
||||
|
||||
if (delete_datasourceFiles) {
|
||||
for (let i = 0; i !== toDeleteFiles.length; ++i) {
|
||||
await datasource.delete(toDeleteFiles[i].name);
|
||||
}
|
||||
|
||||
logger.info(`${req.user.username} deleted ${dFiles.length} files from datasource`, {
|
||||
logger.info(`${req.user.username} deleted ${toDeleteFiles.length} files from datasource`, {
|
||||
user: req.user.id,
|
||||
});
|
||||
}
|
||||
@@ -132,7 +168,6 @@ export default fastifyPlugin(
|
||||
id: {
|
||||
in: files,
|
||||
},
|
||||
userId: req.user.id,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -140,6 +175,7 @@ export default fastifyPlugin(
|
||||
|
||||
logger.info(`${req.user.username} deleted ${resp.count} files`, {
|
||||
user: req.user.id,
|
||||
owners: toDeleteFiles.map((f) => f.userId),
|
||||
});
|
||||
|
||||
return res.send(resp);
|
||||
|
||||
Reference in New Issue
Block a user