7b6fb52df7
## Summary - **Backend**: Add JSON validation for the `blocknote` subfield in rich text API inputs — rejects values that aren't valid JSON or aren't arrays (BlockNote content is always `PartialBlock[]`). This prevents corrupted data from being persisted to the database. - **Frontend**: Replace all 5 unprotected `JSON.parse` calls on blocknote content with the safe `parseJson` utility from `twenty-shared`. Invalid content now degrades gracefully (empty block / empty string / unchanged passthrough) instead of crashing the app. - **Tests**: Added integration tests for invalid blocknote JSON (both GraphQL and REST), unit tests for the new validation, and updated existing test constants to use valid BlockNote JSON. ## Context A user reported a `SyntaxError: Expected ',' or ']' after array element` crash caused by malformed blocknote JSON stored in the database. The data had `"children":[]` nested inside the `content` array instead of as a sibling property. The API accepted this invalid JSON because it only validated that `blocknote` was a string, not that it contained valid JSON. On the frontend, 5 call sites used bare `JSON.parse` with no error handling, causing a white-screen crash. ## Test plan - [x] Unit tests pass: `validate-rich-text-field-or-throw.util.spec.ts` (10/10) - [x] Integration tests pass: `rich-text-field-create-input-validation` (8/8) - [ ] Verify creating a note with valid rich text still works end-to-end - [ ] Verify API returns clear error when blocknote contains invalid JSON - [ ] Verify frontend renders empty block instead of crashing when encountering corrupted data 🤖 Generated with [Claude Code](https://claude.com/claude-code)
33 lines
867 B
TypeScript
33 lines
867 B
TypeScript
import { isNonEmptyString } from '@sniptt/guards';
|
|
|
|
import { parseInitialBlocknote } from '@/blocknote-editor/utils/parseInitialBlocknote';
|
|
|
|
export type AttachmentInfo = {
|
|
path: string;
|
|
name: string;
|
|
};
|
|
|
|
const ATTACHMENT_BLOCK_TYPES = ['image', 'file', 'video', 'audio'];
|
|
|
|
export const getActivityAttachmentPathsAndName = (
|
|
stringifiedActivityBlocknote: string,
|
|
): AttachmentInfo[] => {
|
|
const blocks = parseInitialBlocknote(stringifiedActivityBlocknote) ?? [];
|
|
|
|
return blocks.reduce((acc: AttachmentInfo[], block) => {
|
|
const props = block.props as { url?: string; name?: string } | undefined;
|
|
|
|
if (
|
|
block.type !== undefined &&
|
|
ATTACHMENT_BLOCK_TYPES.includes(block.type) &&
|
|
isNonEmptyString(props?.url)
|
|
) {
|
|
acc.push({
|
|
path: props.url,
|
|
name: props?.name ?? '',
|
|
});
|
|
}
|
|
return acc;
|
|
}, []);
|
|
};
|