Skip to content

Commit bfda1a2

Browse files
committed
refactor(file-upload): Remove hook abstraction layer and regroup feature's files
1 parent 2ec53e9 commit bfda1a2

File tree

10 files changed

+113
-101
lines changed

10 files changed

+113
-101
lines changed

src/features/account/AccountProfileForm.tsx

+2-5
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ import { FieldSelect } from '@/components/FieldSelect';
1010
import { FieldUpload, FieldUploadValue } from '@/components/FieldUpload';
1111
import { LoaderFull } from '@/components/LoaderFull';
1212
import { useToastError, useToastSuccess } from '@/components/Toast';
13-
import { useFetchAvatar } from '@/features/account/service';
14-
import { useAvatarUpload } from '@/features/account/useAvatarUpload';
13+
import { useAvatarFetch, useAvatarUpload } from '@/features/account/service';
1514
import {
1615
AVAILABLE_LANGUAGES,
1716
DEFAULT_LANGUAGE_KEY,
@@ -26,9 +25,7 @@ export const AccountProfileForm = () => {
2625
refetchOnWindowFocus: false,
2726
});
2827

29-
const accountAvatar = useFetchAvatar(account.data?.image || '', {
30-
enabled: !!account?.data?.image,
31-
});
28+
const accountAvatar = useAvatarFetch(account.data?.image || '');
3229

3330
const toastSuccess = useToastSuccess();
3431
const toastError = useToastError();

src/features/account/service.ts

+16-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
1-
import { UseQueryOptions } from '@tanstack/react-query';
1+
import { useMutation } from '@tanstack/react-query';
2+
import { useQuery } from '@tanstack/react-query';
23

3-
import { useFileFetch } from '@/hooks/useFileFetch';
4+
import { trpc } from '@/lib/trpc/client';
45

5-
export const useFetchAvatar = (url: string, config?: UseQueryOptions) => {
6-
return useFileFetch(url, ['name'], {
6+
import { fetchFile, uploadFile } from '../files/utils';
7+
8+
export const useAvatarFetch = (url: string) => {
9+
return useQuery({
710
queryKey: ['account', url],
8-
...config,
11+
queryFn: fetchFile(url, ['name']),
12+
enabled: !!url,
13+
});
14+
};
15+
16+
export const useAvatarUpload = () => {
17+
const getPresignedUrl = trpc.account.uploadAvatarPresignedUrl.useMutation();
18+
return useMutation({
19+
mutationFn: uploadFile(getPresignedUrl.mutateAsync),
920
});
1021
};

src/features/account/useAvatarUpload.ts

-7
This file was deleted.

src/lib/s3.ts src/features/files/schemas.ts

+9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
import { z } from 'zod';
22

3+
export type FetchedFile = z.infer<ReturnType<typeof zFetchedFile>>;
4+
export const zFetchedFile = () =>
5+
z.object({
6+
fileUrl: z.string(),
7+
size: z.string().optional(),
8+
type: z.string().optional(),
9+
lastModifiedDate: z.date(),
10+
});
11+
312
export type UploadSignedUrlInput = z.infer<
413
ReturnType<typeof zUploadSignedUrlInput>
514
>;

src/features/files/utils.ts

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { UseMutateAsyncFunction } from '@tanstack/react-query';
2+
3+
import { UploadSignedUrlInput } from './schemas';
4+
import { FetchedFile } from './schemas';
5+
6+
export const fetchFile =
7+
(url: string, metadata?: string[]) => async (): Promise<FetchedFile> => {
8+
const fileResponse = await fetch(url);
9+
if (!fileResponse.ok) {
10+
throw new Error('Could not fetch the file');
11+
}
12+
13+
const lastModifiedDateHeader = fileResponse.headers.get('Last-Modified');
14+
15+
return (metadata || []).reduce(
16+
(file, currentMetadata) => {
17+
return {
18+
...file,
19+
[currentMetadata]: fileResponse.headers.get(
20+
`x-amz-meta-${currentMetadata}`
21+
),
22+
};
23+
},
24+
{
25+
fileUrl: url,
26+
size: fileResponse.headers.get('Content-Length') ?? undefined,
27+
type: fileResponse.headers.get('Content-Type') ?? undefined,
28+
lastModifiedDate: lastModifiedDateHeader
29+
? new Date(lastModifiedDateHeader)
30+
: new Date(),
31+
}
32+
);
33+
};
34+
35+
export const uploadFile =
36+
(
37+
getPresignedUrl: UseMutateAsyncFunction<
38+
{ signedUrl: string; futureFileUrl: string },
39+
unknown,
40+
UploadSignedUrlInput | void
41+
>
42+
) =>
43+
async (
44+
file?: File,
45+
{
46+
metadata,
47+
}: {
48+
metadata?: Record<string, string>;
49+
} = {}
50+
) => {
51+
if (!file) {
52+
return {
53+
fileUrl: undefined,
54+
};
55+
}
56+
57+
console.log(file.name + ' uploaded');
58+
const { signedUrl, futureFileUrl } = await getPresignedUrl({
59+
metadata: {
60+
name: file.name,
61+
...metadata,
62+
},
63+
});
64+
65+
await fetch(signedUrl, {
66+
method: 'PUT',
67+
headers: { 'Content-Type': file.type },
68+
body: file,
69+
});
70+
71+
return {
72+
fileUrl: futureFileUrl,
73+
} as const;
74+
};

src/hooks/useFileFetch.ts

-36
This file was deleted.

src/hooks/useFileUpload.ts

-45
This file was deleted.

src/server/config/s3.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ import {
66
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
77

88
import { env } from '@/env.mjs';
9-
import { ReadSignedUrlOutput, UploadSignedUrlOutput } from '@/lib/s3';
9+
import {
10+
ReadSignedUrlOutput,
11+
UploadSignedUrlOutput,
12+
} from '@/features/files/schemas';
1013

1114
const S3 = new S3Client({
1215
region: 'auto',

src/server/routers/account.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ import EmailAddressChange from '@/emails/templates/email-address-change';
77
import { env } from '@/env.mjs';
88
import { zUserAccount } from '@/features/account/schemas';
99
import { VALIDATION_TOKEN_EXPIRATION_IN_MINUTES } from '@/features/auth/utils';
10+
import {
11+
zUploadSignedUrlInput,
12+
zUploadSignedUrlOutput,
13+
} from '@/features/files/schemas';
1014
import i18n from '@/lib/i18n/server';
11-
import { zUploadSignedUrlInput, zUploadSignedUrlOutput } from '@/lib/s3';
1215
import {
1316
deleteUsedCode,
1417
generateCode,

src/server/routers/files.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import { randomUUID } from 'crypto';
22

33
import { env } from '@/env.mjs';
4-
import { zUploadSignedUrlInput, zUploadSignedUrlOutput } from '@/lib/s3';
4+
import {
5+
zUploadSignedUrlInput,
6+
zUploadSignedUrlOutput,
7+
} from '@/features/files/schemas';
58
import { getS3UploadSignedUrl } from '@/server/config/s3';
69
import { createTRPCRouter, protectedProcedure } from '@/server/config/trpc';
710

0 commit comments

Comments
 (0)