Remove unsafe type assertions and manual typeof checks across frontend and server
Frontend: - useExportProcessRecordsForCSV: add isFieldCurrencyValue guard for currency record values instead of unvalidated property access - PhonesDisplay: replace typeof === 'object' with Array.isArray in parseAdditionalPhones, use unknown instead of any Server: - Remove redundant `as string[]` casts in calendar and messaging person listeners (6 instances) - type already narrowed by ?? [] - compute-changed-additional-emails: remove as string[] casts, let Array.isArray narrow the type - transform-emails-value: remove as string[] after JSON.parse, rely on isNonEmptyArray for runtime validation - find-person-by-primary-or-additional-email: use isNonEmptyArray from twenty-shared instead of Array.isArray - create-company-and-contact.service: same isNonEmptyArray fix https://claude.ai/code/session_01K2CBC59gYw93d8YVQm3mXX
This commit is contained in:
+14
-5
@@ -1,5 +1,6 @@
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { type FieldCurrencyValue } from '@/object-record/record-field/ui/types/FieldMetadata';
|
||||
import { isFieldCurrencyValue } from '@/object-record/record-field/ui/types/guards/isFieldCurrencyValue';
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
@@ -19,16 +20,24 @@ export const useExportProcessRecordsForCSV = (objectNameSingular: string) => {
|
||||
}
|
||||
|
||||
switch (field.type) {
|
||||
case FieldMetadataType.CURRENCY:
|
||||
case FieldMetadataType.CURRENCY: {
|
||||
const currencyValue = record[field.name];
|
||||
if (!isFieldCurrencyValue(currencyValue)) {
|
||||
return processedRecord;
|
||||
}
|
||||
return {
|
||||
...processedRecord,
|
||||
[field.name]: {
|
||||
amountMicros: convertCurrencyMicrosToCurrencyAmount(
|
||||
record[field.name].amountMicros,
|
||||
),
|
||||
currencyCode: record[field.name].currencyCode,
|
||||
amountMicros:
|
||||
currencyValue.amountMicros !== null
|
||||
? convertCurrencyMicrosToCurrencyAmount(
|
||||
currencyValue.amountMicros,
|
||||
)
|
||||
: null,
|
||||
currencyCode: currencyValue.currencyCode,
|
||||
} satisfies FieldCurrencyValue,
|
||||
};
|
||||
}
|
||||
case FieldMetadataType.MULTI_SELECT:
|
||||
case FieldMetadataType.ARRAY:
|
||||
case FieldMetadataType.RAW_JSON:
|
||||
|
||||
@@ -116,18 +116,20 @@ export const PhonesDisplay = ({
|
||||
);
|
||||
};
|
||||
|
||||
const parseAdditionalPhones = (additionalPhones?: any) => {
|
||||
const parseAdditionalPhones = (additionalPhones?: unknown) => {
|
||||
if (!additionalPhones) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (typeof additionalPhones === 'object') {
|
||||
if (Array.isArray(additionalPhones)) {
|
||||
return additionalPhones;
|
||||
}
|
||||
|
||||
if (typeof additionalPhones === 'string') {
|
||||
try {
|
||||
return JSON.parse(additionalPhones);
|
||||
const parsed = JSON.parse(additionalPhones);
|
||||
|
||||
return Array.isArray(parsed) ? parsed : [];
|
||||
} catch (error) {
|
||||
logError(t`Error parsing additional phones: ${error}`);
|
||||
}
|
||||
|
||||
+4
-6
@@ -17,14 +17,12 @@ export const transformEmailsValue = (
|
||||
|
||||
if (additionalEmails) {
|
||||
try {
|
||||
const emailArray = (
|
||||
isNonEmptyString(additionalEmails)
|
||||
? JSON.parse(additionalEmails)
|
||||
: additionalEmails
|
||||
) as string[];
|
||||
const emailArray = isNonEmptyString(additionalEmails)
|
||||
? JSON.parse(additionalEmails)
|
||||
: additionalEmails;
|
||||
|
||||
additionalEmails = isNonEmptyArray(emailArray)
|
||||
? JSON.stringify(emailArray.map((email) => email.toLowerCase()))
|
||||
? JSON.stringify(emailArray.map((email: string) => email.toLowerCase()))
|
||||
: null;
|
||||
} catch {
|
||||
/* empty */
|
||||
|
||||
+3
-6
@@ -45,8 +45,7 @@ export class CalendarEventParticipantPersonListener {
|
||||
const personEmails = personWithEmails
|
||||
.flatMap((eventPayload) => [
|
||||
eventPayload.properties.after.emails.primaryEmail,
|
||||
...((eventPayload.properties.after.emails?.additionalEmails ??
|
||||
[]) as string[]),
|
||||
...(eventPayload.properties.after.emails?.additionalEmails ?? []),
|
||||
])
|
||||
.filter(isDefined);
|
||||
|
||||
@@ -82,8 +81,7 @@ export class CalendarEventParticipantPersonListener {
|
||||
const personEmails = personWithEmails
|
||||
.flatMap((eventPayload) => [
|
||||
eventPayload.properties.after.emails.primaryEmail,
|
||||
...((eventPayload.properties.after.emails?.additionalEmails ??
|
||||
[]) as string[]),
|
||||
...(eventPayload.properties.after.emails?.additionalEmails ?? []),
|
||||
])
|
||||
.filter(isDefined);
|
||||
|
||||
@@ -115,8 +113,7 @@ export class CalendarEventParticipantPersonListener {
|
||||
const personEmails = peopleHavingEmails
|
||||
.flatMap((eventPayload) => [
|
||||
eventPayload.properties.before.emails.primaryEmail,
|
||||
...((eventPayload.properties.before.emails?.additionalEmails ??
|
||||
[]) as string[]),
|
||||
...(eventPayload.properties.before.emails?.additionalEmails ?? []),
|
||||
])
|
||||
.filter(isDefined);
|
||||
|
||||
|
||||
+2
-2
@@ -8,7 +8,7 @@ import {
|
||||
ConnectedAccountProvider,
|
||||
type FieldActorSource,
|
||||
} from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { isDefined, isNonEmptyArray } from 'twenty-shared/utils';
|
||||
import { type DeepPartial, type Repository } from 'typeorm';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
@@ -251,7 +251,7 @@ export class CreateCompanyAndPersonService {
|
||||
const existingPersonOnAdditionalEmails = alreadyCreatedPeople.find(
|
||||
(person) => {
|
||||
return (
|
||||
Array.isArray(person.emails?.additionalEmails) &&
|
||||
isNonEmptyArray(person.emails?.additionalEmails) &&
|
||||
person.emails.additionalEmails.some(
|
||||
(email) => email.toLowerCase() === contact.handle.toLowerCase(),
|
||||
)
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@ import { type PersonWorkspaceEntity } from 'src/modules/person/standard-objects/
|
||||
export const computeChangedAdditionalEmails = (
|
||||
diff: Partial<ObjectRecordDiff<PersonWorkspaceEntity>>,
|
||||
) => {
|
||||
const before = diff.emails?.before?.additionalEmails as string[];
|
||||
const after = diff.emails?.after?.additionalEmails as string[];
|
||||
const before = diff.emails?.before?.additionalEmails;
|
||||
const after = diff.emails?.after?.additionalEmails;
|
||||
|
||||
if (!Array.isArray(before) || !Array.isArray(after)) {
|
||||
return {
|
||||
|
||||
+3
-1
@@ -1,3 +1,5 @@
|
||||
import { isNonEmptyArray } from 'twenty-shared/utils';
|
||||
|
||||
import { type PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
|
||||
|
||||
export const findPersonByPrimaryOrAdditionalEmail = ({
|
||||
@@ -20,7 +22,7 @@ export const findPersonByPrimaryOrAdditionalEmail = ({
|
||||
const personWithAdditionalEmail = people.find((person) => {
|
||||
const additionalEmails = person.emails?.additionalEmails;
|
||||
|
||||
if (!Array.isArray(additionalEmails)) {
|
||||
if (!isNonEmptyArray(additionalEmails)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
+4
-6
@@ -45,8 +45,7 @@ export class MessageParticipantPersonListener {
|
||||
const personEmails = personWithEmails
|
||||
.flatMap((eventPayload) => [
|
||||
eventPayload.properties.after.emails.primaryEmail,
|
||||
...((eventPayload.properties.after.emails?.additionalEmails ??
|
||||
[]) as string[]),
|
||||
...(eventPayload.properties.after.emails?.additionalEmails ?? []),
|
||||
])
|
||||
.filter(isDefined);
|
||||
|
||||
@@ -82,8 +81,7 @@ export class MessageParticipantPersonListener {
|
||||
const personEmails = personWithEmails
|
||||
.flatMap((eventPayload) => [
|
||||
eventPayload.properties.after.emails.primaryEmail,
|
||||
...((eventPayload.properties.after.emails?.additionalEmails ??
|
||||
[]) as string[]),
|
||||
...(eventPayload.properties.after.emails?.additionalEmails ?? []),
|
||||
])
|
||||
.filter(isDefined);
|
||||
|
||||
@@ -121,8 +119,8 @@ export class MessageParticipantPersonListener {
|
||||
personEmails: personWithEmails
|
||||
.flatMap((eventPayload) => [
|
||||
eventPayload.properties.before.emails.primaryEmail,
|
||||
...((eventPayload.properties.before.emails?.additionalEmails ??
|
||||
[]) as string[]),
|
||||
...(eventPayload.properties.before.emails?.additionalEmails ??
|
||||
[]),
|
||||
])
|
||||
.filter(isDefined),
|
||||
workspaceMemberIds: [],
|
||||
|
||||
Reference in New Issue
Block a user