fix: #948 and tags/folders

fixes inconsistencies when editing other user's files
- tags menu shows their tags
- folders menu shows their folders
by design, you can't and will not be able to add another user's file to
your own folder.
this also introduces a few wip stuff, might be buggy, please bear with
me!
This commit is contained in:
diced
2025-12-31 00:03:55 -08:00
parent 87cf4916a5
commit bfd6a8769d
8 changed files with 98 additions and 12 deletions

View File

@@ -103,7 +103,7 @@ export default function FileModal({
const [editFileOpen, setEditFileOpen] = useState(false);
const { data: folders } = useSWR<Extract<Response['/api/user/folders'], Folder[]>>(
'/api/user/folders?noincl=true',
'/api/user/folders?noincl=true' + (user ? `&user=${user}` : ''),
);
const folderCombobox = useCombobox();
@@ -117,7 +117,9 @@ export default function FileModal({
}
};
const { data: tags } = useSWR<Extract<Response['/api/user/tags'], Tag[]>>('/api/user/tags');
const { data: tags } = useSWR<Extract<Response['/api/user/tags'], Tag[]>>(
user ? `/api/users/${user}/tags` : '/api/user/tags',
);
const tagsCombobox = useCombobox();
const [value, setValue] = useState(file?.tags?.map((x) => x.id) ?? []);
@@ -229,7 +231,7 @@ export default function FileModal({
)}
</SimpleGrid>
{!reduce && !user && (
{!reduce && (
<SimpleGrid cols={{ base: 1, md: 2 }} spacing='md' my='xs'>
<Box>
<Title order={4} mt='lg' mb='xs'>

View File

@@ -6,12 +6,12 @@ import FileModal from './FileModal';
import styles from './index.module.css';
export default function DashboardFile({ file, reduce }: { file: File; reduce?: boolean }) {
export default function DashboardFile({ file, reduce, id }: { file: File; reduce?: boolean; id?: string }) {
const [open, setOpen] = useState(false);
return (
<>
<FileModal open={open} setOpen={setOpen} file={file} reduce={reduce} />
<FileModal open={open} setOpen={setOpen} file={file} reduce={reduce} user={id} />
<Card shadow='md' radius='md' p={0} onClick={() => setOpen(true)} className={styles.file}>
<DashboardFileType key={file.id} file={file} />
</Card>

View File

@@ -59,7 +59,7 @@ export default function Files({ id }: { id?: string }) {
) : (data?.page?.length ?? 0 > 0) ? (
data?.page.map((file) => (
<Suspense fallback={<Skeleton height={350} animate />} key={file.id}>
<DashboardFile file={file} />
<DashboardFile file={file} id={id} />
</Suspense>
))
) : (

View File

@@ -85,7 +85,7 @@ export default fastifyPlugin(
if (req.body.tags !== undefined) {
const tags = await prisma.tag.findMany({
where: {
userId: req.user.id,
userId: req.user.id !== file.User?.id ? file.User?.id : req.user.id,
id: {
in: req.body.tags,
},

View File

@@ -1,7 +1,9 @@
import { prisma } from '@/lib/db';
import { fileSelect } from '@/lib/db/models/file';
import { Folder, cleanFolder } from '@/lib/db/models/folder';
import { User } from '@/lib/db/models/user';
import { log } from '@/lib/logger';
import { canInteract } from '@/lib/role';
import { userMiddleware } from '@/server/middleware/user';
import fastifyPlugin from 'fastify-plugin';
@@ -20,6 +22,16 @@ type Body = {
delete?: 'file' | 'folder';
};
// TODO: need to refactor interaction checks to use this function in the future
function checkInteraction(current?: Partial<User> | null, owner?: Partial<User> | null) {
if (!current || !owner) return false;
if (current.id === owner.id) return true;
const can = canInteract(current.role, owner.role);
return can;
}
const logger = log('api').c('user').c('folders').c('[id]');
export const PATH = '/api/user/folders/:id';
@@ -46,10 +58,11 @@ export default fastifyPlugin(
password: true,
},
},
User: true,
},
});
if (!folder) return res.notFound('Folder not found');
if (req.user.id !== folder.userId) return res.forbidden('You do not own this folder');
if (!checkInteraction(req.user, folder.User)) return res.notFound('Folder not found');
if (req.method === 'PUT') {
const { id } = req.body;
@@ -59,9 +72,12 @@ export default fastifyPlugin(
where: {
id,
},
include: {
User: true,
},
});
if (!file) return res.notFound('File not found');
if (file.userId !== req.user.id) return res.forbidden('You do not own this file');
if (!checkInteraction(req.user, file.User)) return res.notFound('File not found');
const fileInFolder = await prisma.file.findFirst({
where: {
@@ -91,6 +107,7 @@ export default fastifyPlugin(
password: true,
},
},
User: true,
},
});
@@ -145,6 +162,7 @@ export default fastifyPlugin(
password: true,
},
},
User: true,
},
});
@@ -161,9 +179,12 @@ export default fastifyPlugin(
where: {
id,
},
include: {
User: true,
},
});
if (!file) return res.notFound('File not found');
if (file.userId !== req.user.id) return res.forbidden('You do not own this file');
if (!checkInteraction(req.user, file.User)) return res.notFound('File not found');
const fileInFolder = await prisma.file.findFirst({
where: {

View File

@@ -3,6 +3,7 @@ import { fileSelect } from '@/lib/db/models/file';
import { Folder, cleanFolder, cleanFolders } from '@/lib/db/models/folder';
import { log } from '@/lib/logger';
import { secondlyRatelimit } from '@/lib/ratelimits';
import { canInteract } from '@/lib/role';
import { userMiddleware } from '@/server/middleware/user';
import fastifyPlugin from 'fastify-plugin';
@@ -17,6 +18,7 @@ type Body = {
type Query = {
noincl?: boolean;
user?: string;
};
const logger = log('api').c('user').c('folders');
@@ -25,11 +27,24 @@ export const PATH = '/api/user/folders';
export default fastifyPlugin(
(server, _, done) => {
server.get<{ Querystring: Query }>(PATH, { preHandler: [userMiddleware] }, async (req, res) => {
const { noincl } = req.query;
const { noincl, user } = req.query;
if (user) {
const user = await prisma.user.findUnique({
where: {
id: req.user.id,
},
});
if (!user) return res.notFound();
if (req.user.id !== user.id) {
if (!canInteract(req.user.role, user.role)) return res.notFound();
}
}
const folders = await prisma.folder.findMany({
where: {
userId: req.user.id,
userId: user || req.user.id,
},
orderBy: {
createdAt: 'desc',

View File

@@ -0,0 +1,48 @@
import { prisma } from '@/lib/db';
import { Tag, tagSelect } from '@/lib/db/models/tag';
import { canInteract } from '@/lib/role';
import { administratorMiddleware } from '@/server/middleware/administrator';
import { userMiddleware } from '@/server/middleware/user';
import fastifyPlugin from 'fastify-plugin';
export type ApiUsersIdTagsResponse = Tag[];
type Params = {
id: string;
};
// const logger = log('api').c('user').c('id').c('tags');
export const PATH = '/api/users/:id/tags';
export default fastifyPlugin(
(server, _, done) => {
server.get<{ Params: Params }>(
PATH,
{ preHandler: [userMiddleware, administratorMiddleware] },
async (req, res) => {
const { id } = req.params;
const user = await prisma.user.findUnique({
where: {
id,
},
});
if (!user) return res.notFound();
if (!canInteract(req.user.role, user.role)) return res.notFound();
const tags = await prisma.tag.findMany({
where: {
userId: user.id,
},
select: tagSelect,
});
return res.send(tags);
},
);
done();
},
{ name: PATH },
);