Skip to content

Commit ef3bdef

Browse files
committed
fix: extract mime type
1 parent 0779948 commit ef3bdef

File tree

4 files changed

+61
-51
lines changed

4 files changed

+61
-51
lines changed

src/features/book/manager/form-book-cover.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useQuery } from '@tanstack/react-query';
22
import { useFormContext, useWatch } from 'react-hook-form';
33
import { useTranslation } from 'react-i18next';
4+
import { join } from 'remeda';
45

56
import { orpc } from '@/lib/orpc/client';
67

@@ -12,7 +13,10 @@ import {
1213
import { UploadButton } from '@/components/ui/upload-button';
1314

1415
import { BookCover } from '@/features/book/book-cover';
15-
import { FormFieldsBook } from '@/features/book/schema';
16+
import {
17+
bookCoverAcceptedFileTypes,
18+
FormFieldsBook,
19+
} from '@/features/book/schema';
1620

1721
export const FormBookCover = () => {
1822
const { t } = useTranslation(['book']);
@@ -61,7 +65,7 @@ export const FormBookCover = () => {
6165
<UploadButton
6266
uploadRoute="bookCover"
6367
inputProps={{
64-
accept: 'image/png, image/jpeg, image/webp, image/gif',
68+
accept: join(bookCoverAcceptedFileTypes, ','),
6569
}}
6670
className="absolute top-1/2 left-1/2 -translate-1/2 bg-black/50 text-white"
6771
variant="ghost"

src/features/book/schema.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,10 @@ export const zFormFieldsBook = () =>
2424
zBook()
2525
.pick({ title: true, author: true, publisher: true, coverId: true })
2626
.extend({ genreId: zu.fieldText.required() });
27+
28+
export const bookCoverAcceptedFileTypes = [
29+
'image/png',
30+
'image/jpeg',
31+
'image/webp',
32+
'image/gif',
33+
];

src/routes/api/upload.ts

Lines changed: 3 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,16 @@
1-
import {
2-
handleRequest,
3-
RejectUpload,
4-
route,
5-
type Router,
6-
} from '@better-upload/server';
1+
import { handleRequest, type Router } from '@better-upload/server';
72
import { createFileRoute } from '@tanstack/react-router';
83

9-
import i18n from '@/lib/i18n';
104
import { uploadClient } from '@/lib/s3';
115

126
import { envServer } from '@/env/server';
13-
import { auth } from '@/server/auth';
7+
import { bookCover } from '@/server/upload/book-cover';
148

159
const router = {
1610
client: uploadClient,
1711
bucketName: envServer.S3_BUCKET_NAME,
1812
routes: {
19-
bookCover: route({
20-
// [TODO] Check to find a way to extract these rules so they can be reused in the accept field of file inputs
21-
fileTypes: ['image/png', 'image/jpeg', 'image/webp', 'image/gif'],
22-
maxFileSize: 1024 * 1024 * 100, // 100Mb
23-
onBeforeUpload: async ({ req, file }) => {
24-
const session = await auth.api.getSession(req);
25-
if (!session?.user) {
26-
throw new RejectUpload(
27-
i18n.t('book:manager.uploadErrors.NOT_AUTHENTICATED')
28-
);
29-
}
30-
31-
// Only admins should be able to update book covers
32-
const canUpdateBookCover = await auth.api.userHasPermission({
33-
body: {
34-
userId: session.user.id,
35-
permissions: {
36-
book: ['create', 'update'],
37-
},
38-
role: 'admin',
39-
},
40-
});
41-
42-
if (!canUpdateBookCover.success) {
43-
throw new RejectUpload(
44-
i18n.t('book:manager.uploadErrors.UNAUTHORIZED')
45-
);
46-
}
47-
48-
// normalize file extension from detected mimetype (file.type)
49-
const fileExtension = file.type.split('/').at(-1) as string;
50-
return {
51-
// I think it is a good idea to create a random file id
52-
// This allow us to invalidate cache (because the id will always be random)
53-
// and it also prevent the user to upload a file with the same name (aka. objectKey), but different file content
54-
objectInfo: {
55-
key: `books/${crypto.randomUUID()}.${fileExtension}`,
56-
},
57-
};
58-
},
59-
}),
13+
bookCover,
6014
},
6115
} as const satisfies Router;
6216

src/server/upload/book-cover.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { RejectUpload, route } from '@better-upload/server';
2+
3+
import i18n from '@/lib/i18n';
4+
5+
import { bookCoverAcceptedFileTypes } from '@/features/book/schema';
6+
import { auth } from '@/server/auth';
7+
8+
export const bookCover = route({
9+
fileTypes: bookCoverAcceptedFileTypes,
10+
maxFileSize: 1024 * 1024 * 100, // 100Mb
11+
onBeforeUpload: async ({ req, file }) => {
12+
const session = await auth.api.getSession(req);
13+
if (!session?.user) {
14+
throw new RejectUpload(
15+
i18n.t('book:manager.uploadErrors.NOT_AUTHENTICATED')
16+
);
17+
}
18+
19+
// Only admins should be able to update book covers
20+
const canUpdateBookCover = await auth.api.userHasPermission({
21+
body: {
22+
userId: session.user.id,
23+
permissions: {
24+
book: ['create', 'update'],
25+
},
26+
role: 'admin',
27+
},
28+
});
29+
30+
if (!canUpdateBookCover.success) {
31+
throw new RejectUpload(i18n.t('book:manager.uploadErrors.UNAUTHORIZED'));
32+
}
33+
34+
// normalize file extension from detected mimetype (file.type)
35+
const fileExtension = file.type.split('/').at(-1) as string;
36+
return {
37+
// I think it is a good idea to create a random file id
38+
// This allow us to invalidate cache (because the id will always be random)
39+
// and it also prevent the user to upload a file with the same name (aka. objectKey), but different file content
40+
objectInfo: {
41+
key: `books/${crypto.randomUUID()}.${fileExtension}`,
42+
},
43+
};
44+
},
45+
});

0 commit comments

Comments
 (0)