feat: actions when viewing other user files (#918)

This commit is contained in:
diced
2025-11-03 16:37:12 -08:00
parent 04b27a2dee
commit 6e2da52f77
5 changed files with 130 additions and 69 deletions

View File

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

View File

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

View File

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

View File

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

View File

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