11744 emails broken image in emails (#12265)

- refactor file tokens
- update file token management
  - generate one token per file per workspaceId
  - move token from query params to url path
This commit is contained in:
martmull
2025-05-26 22:05:21 +02:00
committed by GitHub
parent 69badf2a66
commit aa58259019
53 changed files with 775 additions and 386 deletions

View File

@ -1,14 +0,0 @@
import { computePathWithoutToken } from '../useUploadAttachmentFile';
describe('computePathWithoutToken', () => {
it('should remove token from path', () => {
const input = 'https://example.com/image.jpg?token=abc123';
const expected = 'https://example.com/image.jpg';
expect(computePathWithoutToken(input)).toBe(expected);
});
it('should handle path without token', () => {
const input = 'https://example.com/image.jpg?size=large';
expect(computePathWithoutToken(input)).toBe(input);
});
});

View File

@ -7,13 +7,8 @@ import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getActivi
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { isNonEmptyString } from '@sniptt/guards';
import { FileFolder, useUploadFileMutation } from '~/generated/graphql';
// Note: This is probably not the right way to do this.
export const computePathWithoutToken = (attachmentPath: string): string => {
return attachmentPath.replace(/\?token=[^&]*$/, '');
};
import { isDefined } from 'twenty-shared/utils';
export const useUploadAttachmentFile = () => {
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
@ -36,12 +31,14 @@ export const useUploadAttachmentFile = () => {
},
});
const attachmentPath = result?.data?.uploadFile;
const signedFile = result?.data?.uploadFile;
if (!isNonEmptyString(attachmentPath)) {
if (!isDefined(signedFile)) {
throw new Error("Couldn't upload the attachment.");
}
const { path: attachmentPath } = signedFile;
const targetableObjectFieldIdName = getActivityTargetObjectFieldIdName({
nameSingular: targetableObject.targetObjectNameSingular,
});
@ -49,7 +46,7 @@ export const useUploadAttachmentFile = () => {
const attachmentToCreate = {
authorId: currentWorkspaceMember?.id,
name: file.name,
fullPath: computePathWithoutToken(attachmentPath),
fullPath: attachmentPath,
type: getFileType(file.name),
[targetableObjectFieldIdName]: targetableObject.id,
createdAt: new Date().toISOString(),

View File

@ -2,6 +2,9 @@ import { gql } from '@apollo/client';
export const UPLOAD_FILE = gql`
mutation uploadFile($file: Upload!, $fileFolder: FileFolder) {
uploadFile(file: $file, fileFolder: $fileFolder)
uploadFile(file: $file, fileFolder: $fileFolder) {
path
token
}
}
`;

View File

@ -2,6 +2,9 @@ import { gql } from '@apollo/client';
export const UPLOAD_IMAGE = gql`
mutation uploadImage($file: Upload!, $fileFolder: FileFolder) {
uploadImage(file: $file, fileFolder: $fileFolder)
uploadImage(file: $file, fileFolder: $fileFolder) {
path
token
}
}
`;

View File

@ -42,16 +42,16 @@ export const useRecordShowContainerActions = ({
},
});
const avatarUrl = result?.data?.uploadImage;
const avatarSignedFile = result?.data?.uploadImage;
if (!avatarUrl || isUndefinedOrNull(updateOneRecord)) {
if (!avatarSignedFile || isUndefinedOrNull(updateOneRecord)) {
return;
}
await updateOneRecord({
idToUpdate: objectRecordId,
updateOneRecordInput: {
avatarUrl,
avatarUrl: avatarSignedFile.path,
},
});
};

View File

@ -5,7 +5,7 @@ import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMembe
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { ImageInput } from '@/ui/input/components/ImageInput';
import { isDefined } from 'twenty-shared/utils';
import { buildSignedPath, isDefined } from 'twenty-shared/utils';
import { useUploadProfilePictureMutation } from '~/generated/graphql';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
@ -51,22 +51,22 @@ export const ProfilePictureUploader = () => {
setUploadController(null);
setErrorMessage(null);
const avatarUrl = result?.data?.uploadProfilePicture.split('?')[0];
const signedFile = result?.data?.uploadProfilePicture;
if (!avatarUrl) {
if (!isDefined(signedFile)) {
throw new Error('Avatar URL not found');
}
await updateOneRecord({
idToUpdate: currentWorkspaceMember?.id,
updateOneRecordInput: {
avatarUrl,
avatarUrl: signedFile.path,
},
});
setCurrentWorkspaceMember({
...currentWorkspaceMember,
avatarUrl: result?.data?.uploadProfilePicture,
avatarUrl: buildSignedPath(signedFile),
});
return result;

View File

@ -7,6 +7,7 @@ import {
useUploadWorkspaceLogoMutation,
} from '~/generated/graphql';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
import { buildSignedPath } from 'twenty-shared/utils';
export const WorkspaceLogoUploader = () => {
const [uploadLogo] = useUploadWorkspaceLogoMutation();
@ -29,7 +30,7 @@ export const WorkspaceLogoUploader = () => {
onCompleted: (data) => {
setCurrentWorkspace({
...currentWorkspace,
logo: data.uploadWorkspaceLogo,
logo: buildSignedPath(data.uploadWorkspaceLogo),
});
},
});

View File

@ -2,6 +2,9 @@ import { gql } from '@apollo/client';
export const UPLOAD_PROFILE_PICTURE = gql`
mutation UploadProfilePicture($file: Upload!) {
uploadProfilePicture(file: $file)
uploadProfilePicture(file: $file) {
path
token
}
}
`;

View File

@ -2,6 +2,9 @@ import { gql } from '@apollo/client';
export const UPLOAD_WORKSPACE_LOGO = gql`
mutation UploadWorkspaceLogo($file: Upload!) {
uploadWorkspaceLogo(file: $file)
uploadWorkspaceLogo(file: $file) {
path
token
}
}
`;