mirror of
https://github.com/twentyhq/twenty.git
synced 2024-11-22 21:50:43 +03:00
Optimize metadata queries (#7013)
In this PR: 1. Refactor guards to avoid duplicated queries: WorkspaceAuthGuard and UserAuthGuard only check for existence of workspace and user in the request without querying the database
This commit is contained in:
parent
cf8b1161cc
commit
523df5398a
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,5 +1,5 @@
|
||||
import * as Apollo from '@apollo/client';
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
export type Maybe<T> = T | null;
|
||||
export type InputMaybe<T> = Maybe<T>;
|
||||
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
|
||||
@ -219,7 +219,7 @@ export type ExecuteServerlessFunctionInput = {
|
||||
/** Id of the serverless function to execute */
|
||||
id: Scalars['UUID'];
|
||||
/** Payload in JSON format */
|
||||
payload?: InputMaybe<Scalars['JSON']>;
|
||||
payload: Scalars['JSON'];
|
||||
/** Version of the serverless function to execute */
|
||||
version?: Scalars['String'];
|
||||
};
|
||||
@ -338,6 +338,7 @@ export enum MessageChannelVisibility {
|
||||
|
||||
export type Mutation = {
|
||||
__typename?: 'Mutation';
|
||||
activateWorkflowVersion: Scalars['Boolean'];
|
||||
activateWorkspace: Workspace;
|
||||
addUserToWorkspace: User;
|
||||
authorizeApp: AuthorizeApp;
|
||||
@ -347,15 +348,14 @@ export type Mutation = {
|
||||
createOneObject: Object;
|
||||
createOneServerlessFunction: ServerlessFunction;
|
||||
createOneServerlessFunctionFromFile: ServerlessFunction;
|
||||
deactivateWorkflowVersion: Scalars['Boolean'];
|
||||
deleteCurrentWorkspace: Workspace;
|
||||
deleteOneObject: Object;
|
||||
deleteOneServerlessFunction: ServerlessFunction;
|
||||
deleteUser: User;
|
||||
disablePostgresProxy: PostgresCredentials;
|
||||
disableWorkflowTrigger: Scalars['Boolean'];
|
||||
emailPasswordResetLink: EmailPasswordResetLink;
|
||||
enablePostgresProxy: PostgresCredentials;
|
||||
enableWorkflowTrigger: Scalars['Boolean'];
|
||||
exchangeAuthorizationCode: ExchangeAuthCode;
|
||||
executeOneServerlessFunction: ServerlessFunctionExecutionResult;
|
||||
generateApiKeyToken: ApiKeyToken;
|
||||
@ -382,6 +382,11 @@ export type Mutation = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationActivateWorkflowVersionArgs = {
|
||||
workflowVersionId: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type MutationActivateWorkspaceArgs = {
|
||||
data: ActivateWorkspaceInput;
|
||||
};
|
||||
@ -423,6 +428,11 @@ export type MutationCreateOneServerlessFunctionFromFileArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationDeactivateWorkflowVersionArgs = {
|
||||
workflowVersionId: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type MutationDeleteOneObjectArgs = {
|
||||
input: DeleteOneObjectInput;
|
||||
};
|
||||
@ -433,21 +443,11 @@ export type MutationDeleteOneServerlessFunctionArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationDisableWorkflowTriggerArgs = {
|
||||
workflowVersionId: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type MutationEmailPasswordResetLinkArgs = {
|
||||
email: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type MutationEnableWorkflowTriggerArgs = {
|
||||
workflowVersionId: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type MutationExchangeAuthorizationCodeArgs = {
|
||||
authorizationCode: Scalars['String'];
|
||||
clientSecret?: InputMaybe<Scalars['String']>;
|
||||
@ -638,9 +638,10 @@ export type Query = {
|
||||
currentWorkspace: Workspace;
|
||||
findWorkspaceFromInviteHash: Workspace;
|
||||
getAISQLQuery: AisqlQueryResult;
|
||||
getAvailablePackages: Scalars['JSON'];
|
||||
getPostgresCredentials?: Maybe<PostgresCredentials>;
|
||||
getProductPrices: ProductPricesEntity;
|
||||
getServerlessFunctionSourceCode: Scalars['String'];
|
||||
getServerlessFunctionSourceCode?: Maybe<Scalars['String']>;
|
||||
getTimelineCalendarEventsFromCompanyId: TimelineCalendarEventsWithTotal;
|
||||
getTimelineCalendarEventsFromPersonId: TimelineCalendarEventsWithTotal;
|
||||
getTimelineThreadsFromCompanyId: TimelineThreadsWithTotal;
|
||||
|
@ -1,62 +0,0 @@
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { RelationType } from '@/settings/data-model/types/RelationType';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
RelationMetadataType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
export const getRelationDefinition = ({
|
||||
objectMetadataItems,
|
||||
fieldMetadataItemOnSourceRecord,
|
||||
}: {
|
||||
objectMetadataItems: ObjectMetadataItem[];
|
||||
fieldMetadataItemOnSourceRecord: FieldMetadataItem;
|
||||
}) => {
|
||||
if (fieldMetadataItemOnSourceRecord.type !== FieldMetadataType.Relation) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const relationMetadataItem =
|
||||
fieldMetadataItemOnSourceRecord.fromRelationMetadata ||
|
||||
fieldMetadataItemOnSourceRecord.toRelationMetadata;
|
||||
|
||||
if (!relationMetadataItem) return null;
|
||||
|
||||
const relationSourceFieldMetadataItemId =
|
||||
'toFieldMetadataId' in relationMetadataItem
|
||||
? relationMetadataItem.toFieldMetadataId
|
||||
: relationMetadataItem.fromFieldMetadataId;
|
||||
|
||||
if (!relationSourceFieldMetadataItemId) return null;
|
||||
|
||||
// TODO: precise naming, is it relationTypeFromTargetPointOfView or relationTypeFromSourcePointOfView ?
|
||||
const relationType =
|
||||
relationMetadataItem.relationType === RelationMetadataType.OneToMany &&
|
||||
fieldMetadataItemOnSourceRecord.toRelationMetadata
|
||||
? ('MANY_TO_ONE' satisfies RelationType)
|
||||
: (relationMetadataItem.relationType as RelationType);
|
||||
|
||||
const targetObjectMetadataNameSingular =
|
||||
'toObjectMetadata' in relationMetadataItem
|
||||
? relationMetadataItem.toObjectMetadata.nameSingular
|
||||
: relationMetadataItem.fromObjectMetadata.nameSingular;
|
||||
|
||||
const targetObjectMetadataItem = objectMetadataItems.find(
|
||||
(item) => item.nameSingular === targetObjectMetadataNameSingular,
|
||||
);
|
||||
|
||||
if (!targetObjectMetadataItem) return null;
|
||||
|
||||
const fieldMetadataItemOnTargetRecord = targetObjectMetadataItem.fields.find(
|
||||
(field) => field.id === relationSourceFieldMetadataItemId,
|
||||
);
|
||||
|
||||
if (!fieldMetadataItemOnTargetRecord) return null;
|
||||
|
||||
return {
|
||||
fieldMetadataItemOnTargetRecord,
|
||||
targetObjectMetadataItem,
|
||||
relationType,
|
||||
};
|
||||
};
|
@ -1,6 +1,5 @@
|
||||
import { ApolloCache } from '@apollo/client';
|
||||
|
||||
import { getRelationDefinition } from '@/apollo/optimistic-effect/utils/getRelationDefinition';
|
||||
import { triggerAttachRelationOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerAttachRelationOptimisticEffect';
|
||||
import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect';
|
||||
import { triggerDetachRelationOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDetachRelationOptimisticEffect';
|
||||
@ -45,16 +44,23 @@ export const triggerUpdateRelationsOptimisticEffect = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const relationDefinition = getRelationDefinition({
|
||||
fieldMetadataItemOnSourceRecord,
|
||||
objectMetadataItems,
|
||||
});
|
||||
const relationDefinition =
|
||||
fieldMetadataItemOnSourceRecord.relationDefinition;
|
||||
|
||||
if (!relationDefinition) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { targetObjectMetadataItem, fieldMetadataItemOnTargetRecord } =
|
||||
relationDefinition;
|
||||
const { targetObjectMetadata, targetFieldMetadata } = relationDefinition;
|
||||
|
||||
const fullTargetObjectMetadataItem = objectMetadataItems.find(
|
||||
({ nameSingular }) =>
|
||||
nameSingular === targetObjectMetadata.nameSingular,
|
||||
);
|
||||
|
||||
if (!fullTargetObjectMetadataItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentFieldValueOnSourceRecord:
|
||||
| RecordGqlConnection
|
||||
@ -80,7 +86,7 @@ export const triggerUpdateRelationsOptimisticEffect = ({
|
||||
// it's an object record connection (we can still check it though as a safeguard)
|
||||
const currentFieldValueOnSourceRecordIsARecordConnection =
|
||||
isObjectRecordConnection(
|
||||
targetObjectMetadataItem.nameSingular,
|
||||
targetObjectMetadata.nameSingular,
|
||||
currentFieldValueOnSourceRecord,
|
||||
);
|
||||
|
||||
@ -93,7 +99,7 @@ export const triggerUpdateRelationsOptimisticEffect = ({
|
||||
|
||||
const updatedFieldValueOnSourceRecordIsARecordConnection =
|
||||
isObjectRecordConnection(
|
||||
targetObjectMetadataItem.nameSingular,
|
||||
targetObjectMetadata.nameSingular,
|
||||
updatedFieldValueOnSourceRecord,
|
||||
);
|
||||
|
||||
@ -112,13 +118,13 @@ export const triggerUpdateRelationsOptimisticEffect = ({
|
||||
// Instead of hardcoding it here
|
||||
const shouldCascadeDeleteTargetRecords =
|
||||
CORE_OBJECT_NAMES_TO_DELETE_ON_TRIGGER_RELATION_DETACH.includes(
|
||||
targetObjectMetadataItem.nameSingular as CoreObjectNameSingular,
|
||||
targetObjectMetadata.nameSingular as CoreObjectNameSingular,
|
||||
);
|
||||
|
||||
if (shouldCascadeDeleteTargetRecords) {
|
||||
triggerDeleteRecordsOptimisticEffect({
|
||||
cache,
|
||||
objectMetadataItem: targetObjectMetadataItem,
|
||||
objectMetadataItem: fullTargetObjectMetadataItem,
|
||||
recordsToDelete: targetRecordsToDetachFrom,
|
||||
objectMetadataItems,
|
||||
});
|
||||
@ -128,8 +134,8 @@ export const triggerUpdateRelationsOptimisticEffect = ({
|
||||
cache,
|
||||
sourceObjectNameSingular: sourceObjectMetadataItem.nameSingular,
|
||||
sourceRecordId: currentSourceRecord.id,
|
||||
fieldNameOnTargetRecord: fieldMetadataItemOnTargetRecord.name,
|
||||
targetObjectNameSingular: targetObjectMetadataItem.nameSingular,
|
||||
fieldNameOnTargetRecord: targetFieldMetadata.name,
|
||||
targetObjectNameSingular: targetObjectMetadata.nameSingular,
|
||||
targetRecordId: targetRecordToDetachFrom.id,
|
||||
});
|
||||
});
|
||||
@ -145,8 +151,8 @@ export const triggerUpdateRelationsOptimisticEffect = ({
|
||||
cache,
|
||||
sourceObjectNameSingular: sourceObjectMetadataItem.nameSingular,
|
||||
sourceRecordId: updatedSourceRecord.id,
|
||||
fieldNameOnTargetRecord: fieldMetadataItemOnTargetRecord.name,
|
||||
targetObjectNameSingular: targetObjectMetadataItem.nameSingular,
|
||||
fieldNameOnTargetRecord: targetFieldMetadata.name,
|
||||
targetObjectNameSingular: targetObjectMetadata.nameSingular,
|
||||
targetRecordId: targetRecordToAttachTo.id,
|
||||
}),
|
||||
);
|
||||
|
@ -11,7 +11,7 @@ import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/compo
|
||||
import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle';
|
||||
import { useNavigationSection } from '@/ui/navigation/navigation-drawer/hooks/useNavigationSection';
|
||||
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { useFavorites } from '../hooks/useFavorites';
|
||||
|
||||
const StyledContainer = styled(NavigationDrawerSection)`
|
||||
@ -35,7 +35,7 @@ const StyledNavigationDrawerItem = styled(NavigationDrawerItem)`
|
||||
`;
|
||||
|
||||
export const CurrentWorkspaceMemberFavorites = () => {
|
||||
const currentUser = useRecoilValue(currentUserState);
|
||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||
|
||||
const { favorites, handleReorderFavorite } = useFavorites();
|
||||
const loading = useIsPrefetchLoading();
|
||||
@ -44,12 +44,12 @@ export const CurrentWorkspaceMemberFavorites = () => {
|
||||
useNavigationSection('Favorites');
|
||||
const isNavigationSectionOpen = useRecoilValue(isNavigationSectionOpenState);
|
||||
|
||||
if (loading && isDefined(currentUser)) {
|
||||
if (loading && isDefined(currentWorkspaceMember)) {
|
||||
return <FavoritesSkeletonLoader />;
|
||||
}
|
||||
|
||||
const currentWorkspaceMemberFavorites = favorites.filter(
|
||||
(favorite) => favorite.workspaceMemberId === currentUser?.id,
|
||||
(favorite) => favorite.workspaceMemberId === currentWorkspaceMember?.id,
|
||||
);
|
||||
|
||||
if (
|
||||
|
@ -9,5 +9,6 @@ export type Favorite = {
|
||||
avatarType: AvatarType;
|
||||
link: string;
|
||||
recordId: string;
|
||||
workspaceMemberId: string;
|
||||
__typename: 'Favorite';
|
||||
};
|
||||
|
@ -19,8 +19,8 @@ export const sortFavorites = (
|
||||
const relationObject = favorite[relationField.name];
|
||||
|
||||
const relationObjectNameSingular =
|
||||
relationField.toRelationMetadata?.fromObjectMetadata.nameSingular ??
|
||||
'';
|
||||
relationField.relationDefinition?.targetObjectMetadata
|
||||
.nameSingular ?? '';
|
||||
|
||||
const objectRecordIdentifier =
|
||||
getObjectRecordIdentifierByNameSingular(
|
||||
@ -38,6 +38,7 @@ export const sortFavorites = (
|
||||
link: hasLinkToShowPage
|
||||
? objectRecordIdentifier.linkToShowPage
|
||||
: '',
|
||||
workspaceMemberId: favorite.workspaceMemberId,
|
||||
} as Favorite;
|
||||
}
|
||||
}
|
||||
|
@ -39,32 +39,6 @@ export const FIND_MANY_OBJECT_METADATA_ITEMS = gql`
|
||||
isNullable
|
||||
createdAt
|
||||
updatedAt
|
||||
fromRelationMetadata {
|
||||
id
|
||||
relationType
|
||||
toObjectMetadata {
|
||||
id
|
||||
dataSourceId
|
||||
nameSingular
|
||||
namePlural
|
||||
isSystem
|
||||
isRemote
|
||||
}
|
||||
toFieldMetadataId
|
||||
}
|
||||
toRelationMetadata {
|
||||
id
|
||||
relationType
|
||||
fromObjectMetadata {
|
||||
id
|
||||
dataSourceId
|
||||
nameSingular
|
||||
namePlural
|
||||
isSystem
|
||||
isRemote
|
||||
}
|
||||
fromFieldMetadataId
|
||||
}
|
||||
defaultValue
|
||||
options
|
||||
relationDefinition {
|
||||
|
@ -39,29 +39,27 @@ export const query = gql`
|
||||
isNullable
|
||||
createdAt
|
||||
updatedAt
|
||||
fromRelationMetadata {
|
||||
id
|
||||
relationType
|
||||
toObjectMetadata {
|
||||
relationDefinition {
|
||||
relationId
|
||||
direction
|
||||
sourceObjectMetadata {
|
||||
id
|
||||
dataSourceId
|
||||
nameSingular
|
||||
namePlural
|
||||
isSystem
|
||||
}
|
||||
toFieldMetadataId
|
||||
}
|
||||
toRelationMetadata {
|
||||
id
|
||||
relationType
|
||||
fromObjectMetadata {
|
||||
sourceFieldMetadata {
|
||||
id
|
||||
name
|
||||
}
|
||||
targetObjectMetadata {
|
||||
id
|
||||
dataSourceId
|
||||
nameSingular
|
||||
namePlural
|
||||
isSystem
|
||||
}
|
||||
fromFieldMetadataId
|
||||
targetFieldMetadata {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
defaultValue
|
||||
options
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { ReactNode } from 'react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
|
||||
import { useCreateOneRelationMetadataItem } from '@/object-metadata/hooks/useCreateOneRelationMetadataItem';
|
||||
import { RelationMetadataType } from '~/generated/graphql';
|
||||
import { RelationDefinitionType } from '~/generated/graphql';
|
||||
|
||||
import {
|
||||
query,
|
||||
@ -42,7 +42,7 @@ describe('useCreateOneRelationMetadataItem', () => {
|
||||
|
||||
await act(async () => {
|
||||
const res = await result.current.createOneRelationMetadataItem({
|
||||
relationType: RelationMetadataType.OneToOne,
|
||||
relationType: RelationDefinitionType.OneToOne,
|
||||
field: {
|
||||
label: 'label',
|
||||
},
|
||||
|
@ -1,11 +1,7 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector';
|
||||
import { RelationType } from '@/settings/data-model/types/RelationType';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
RelationMetadataType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
import { FieldMetadataItem } from '../types/FieldMetadataItem';
|
||||
|
||||
@ -17,39 +13,19 @@ export const useGetRelationMetadata = () =>
|
||||
}: {
|
||||
fieldMetadataItem: Pick<
|
||||
FieldMetadataItem,
|
||||
'fromRelationMetadata' | 'toRelationMetadata' | 'type'
|
||||
'type' | 'relationDefinition'
|
||||
>;
|
||||
}) => {
|
||||
if (fieldMetadataItem.type !== FieldMetadataType.Relation) return null;
|
||||
|
||||
const relationMetadata =
|
||||
fieldMetadataItem.fromRelationMetadata ||
|
||||
fieldMetadataItem.toRelationMetadata;
|
||||
const relationDefinition = fieldMetadataItem.relationDefinition;
|
||||
|
||||
if (!relationMetadata) return null;
|
||||
|
||||
const relationFieldMetadataId =
|
||||
'toFieldMetadataId' in relationMetadata
|
||||
? relationMetadata.toFieldMetadataId
|
||||
: relationMetadata.fromFieldMetadataId;
|
||||
|
||||
if (!relationFieldMetadataId) return null;
|
||||
|
||||
const relationType =
|
||||
relationMetadata.relationType === RelationMetadataType.OneToMany &&
|
||||
fieldMetadataItem.toRelationMetadata
|
||||
? 'MANY_TO_ONE'
|
||||
: (relationMetadata.relationType as RelationType);
|
||||
|
||||
const relationObjectMetadataNameSingular =
|
||||
'toObjectMetadata' in relationMetadata
|
||||
? relationMetadata.toObjectMetadata.nameSingular
|
||||
: relationMetadata.fromObjectMetadata.nameSingular;
|
||||
if (!relationDefinition) return null;
|
||||
|
||||
const relationObjectMetadataItem = snapshot
|
||||
.getLoadable(
|
||||
objectMetadataItemFamilySelector({
|
||||
objectName: relationObjectMetadataNameSingular,
|
||||
objectName: relationDefinition.targetObjectMetadata.nameSingular,
|
||||
objectNameType: 'singular',
|
||||
}),
|
||||
)
|
||||
@ -59,7 +35,7 @@ export const useGetRelationMetadata = () =>
|
||||
|
||||
const relationFieldMetadataItem =
|
||||
relationObjectMetadataItem.fields.find(
|
||||
(field) => field.id === relationFieldMetadataId,
|
||||
(field) => field.id === relationDefinition.targetFieldMetadata.id,
|
||||
);
|
||||
|
||||
if (!relationFieldMetadataItem) return null;
|
||||
@ -67,7 +43,7 @@ export const useGetRelationMetadata = () =>
|
||||
return {
|
||||
relationFieldMetadataItem,
|
||||
relationObjectMetadataItem,
|
||||
relationType,
|
||||
relationType: relationDefinition.direction,
|
||||
};
|
||||
},
|
||||
[],
|
||||
|
@ -3,7 +3,6 @@ import { ThemeColor } from 'twenty-ui';
|
||||
import {
|
||||
Field,
|
||||
Object as MetadataObject,
|
||||
Relation,
|
||||
RelationDefinition,
|
||||
RelationDefinitionType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
@ -18,31 +17,9 @@ export type FieldMetadataItemOption = {
|
||||
|
||||
export type FieldMetadataItem = Omit<
|
||||
Field,
|
||||
| '__typename'
|
||||
| 'fromRelationMetadata'
|
||||
| 'toRelationMetadata'
|
||||
| 'defaultValue'
|
||||
| 'options'
|
||||
| 'settings'
|
||||
| 'relationDefinition'
|
||||
'__typename' | 'defaultValue' | 'options' | 'settings' | 'relationDefinition'
|
||||
> & {
|
||||
__typename?: string;
|
||||
fromRelationMetadata?:
|
||||
| (Pick<Relation, 'id' | 'toFieldMetadataId' | 'relationType'> & {
|
||||
toObjectMetadata: Pick<
|
||||
Relation['toObjectMetadata'],
|
||||
'id' | 'nameSingular' | 'namePlural' | 'isSystem' | 'isRemote'
|
||||
>;
|
||||
})
|
||||
| null;
|
||||
toRelationMetadata?:
|
||||
| (Pick<Relation, 'id' | 'fromFieldMetadataId' | 'relationType'> & {
|
||||
fromObjectMetadata: Pick<
|
||||
Relation['fromObjectMetadata'],
|
||||
'id' | 'nameSingular' | 'namePlural' | 'isSystem' | 'isRemote'
|
||||
>;
|
||||
})
|
||||
| null;
|
||||
defaultValue?: any;
|
||||
options?: FieldMetadataItemOption[] | null;
|
||||
relationDefinition?: {
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { parseFieldRelationType } from '@/object-metadata/utils/parseFieldRelationType';
|
||||
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { getFieldButtonIcon } from '@/object-record/record-field/utils/getFieldButtonIcon';
|
||||
@ -20,17 +19,15 @@ export const formatFieldMetadataItemAsFieldDefinition = ({
|
||||
labelWidth,
|
||||
}: FieldMetadataItemAsFieldDefinitionProps): FieldDefinition<FieldMetadata> => {
|
||||
const relationObjectMetadataItem =
|
||||
field.toRelationMetadata?.fromObjectMetadata ||
|
||||
field.fromRelationMetadata?.toObjectMetadata;
|
||||
field.relationDefinition?.targetObjectMetadata;
|
||||
|
||||
const relationFieldMetadataId =
|
||||
field.toRelationMetadata?.fromFieldMetadataId ||
|
||||
field.fromRelationMetadata?.toFieldMetadataId;
|
||||
field.relationDefinition?.targetFieldMetadata.id;
|
||||
|
||||
const fieldDefintionMetadata = {
|
||||
fieldName: field.name,
|
||||
placeHolder: field.label,
|
||||
relationType: parseFieldRelationType(field),
|
||||
relationType: field.relationDefinition?.direction,
|
||||
relationFieldMetadataId,
|
||||
relationObjectMetadataNameSingular:
|
||||
relationObjectMetadataItem?.nameSingular ?? '',
|
||||
|
@ -2,6 +2,7 @@ import { RelationType } from '@/settings/data-model/types/RelationType';
|
||||
import {
|
||||
CreateRelationInput,
|
||||
Field,
|
||||
RelationDefinitionType,
|
||||
RelationMetadataType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
@ -24,8 +25,8 @@ export const formatRelationMetadataInput = (
|
||||
// => Transform into ONE_TO_MANY and invert "from" and "to" data.
|
||||
const isManyToOne = input.relationType === 'MANY_TO_ONE';
|
||||
const relationType = isManyToOne
|
||||
? RelationMetadataType.OneToMany
|
||||
: (input.relationType as RelationMetadataType);
|
||||
? RelationDefinitionType.OneToMany
|
||||
: (input.relationType as RelationDefinitionType);
|
||||
const { field: fromField, objectMetadataId: fromObjectMetadataId } =
|
||||
isManyToOne ? input.connect : input;
|
||||
const { field: toField, objectMetadataId: toObjectMetadataId } = isManyToOne
|
||||
@ -51,7 +52,7 @@ export const formatRelationMetadataInput = (
|
||||
fromLabel,
|
||||
fromName,
|
||||
fromObjectMetadataId,
|
||||
relationType,
|
||||
relationType: relationType as unknown as RelationMetadataType,
|
||||
toDescription,
|
||||
toIcon,
|
||||
toLabel,
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -4,7 +4,7 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
RelationMetadataType,
|
||||
RelationDefinitionType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
import { FieldMetadataItem } from '../types/FieldMetadataItem';
|
||||
@ -17,10 +17,7 @@ export const mapFieldMetadataToGraphQLQuery = ({
|
||||
computeReferences = false,
|
||||
}: {
|
||||
objectMetadataItems: ObjectMetadataItem[];
|
||||
field: Pick<
|
||||
FieldMetadataItem,
|
||||
'name' | 'type' | 'toRelationMetadata' | 'fromRelationMetadata'
|
||||
>;
|
||||
field: Pick<FieldMetadataItem, 'name' | 'type' | 'relationDefinition'>;
|
||||
relationrecordFields?: Record<string, any>;
|
||||
computeReferences?: boolean;
|
||||
}): any => {
|
||||
@ -49,12 +46,12 @@ export const mapFieldMetadataToGraphQLQuery = ({
|
||||
|
||||
if (
|
||||
fieldType === FieldMetadataType.Relation &&
|
||||
field.toRelationMetadata?.relationType === RelationMetadataType.OneToMany
|
||||
field.relationDefinition?.direction === RelationDefinitionType.ManyToOne
|
||||
) {
|
||||
const relationMetadataItem = objectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
objectMetadataItem.id ===
|
||||
(field.toRelationMetadata as any)?.fromObjectMetadata?.id,
|
||||
field.relationDefinition?.targetObjectMetadata.id,
|
||||
);
|
||||
|
||||
if (isUndefined(relationMetadataItem)) {
|
||||
@ -73,12 +70,12 @@ ${mapObjectMetadataToGraphQLQuery({
|
||||
|
||||
if (
|
||||
fieldType === FieldMetadataType.Relation &&
|
||||
field.fromRelationMetadata?.relationType === RelationMetadataType.OneToMany
|
||||
field.relationDefinition?.direction === RelationDefinitionType.OneToMany
|
||||
) {
|
||||
const relationMetadataItem = objectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
objectMetadataItem.id ===
|
||||
(field.fromRelationMetadata as any)?.toObjectMetadata?.id,
|
||||
field.relationDefinition?.targetObjectMetadata.id,
|
||||
);
|
||||
|
||||
if (isUndefined(relationMetadataItem)) {
|
||||
|
@ -1,55 +0,0 @@
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { FieldDefinitionRelationType } from '@/object-record/record-field/types/FieldDefinition';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
RelationMetadataType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const parseFieldRelationType = (
|
||||
field: FieldMetadataItem | undefined,
|
||||
): FieldDefinitionRelationType | undefined => {
|
||||
if (!field || field.type !== FieldMetadataType.Relation) return;
|
||||
|
||||
const config: Record<
|
||||
RelationMetadataType,
|
||||
{ from: FieldDefinitionRelationType; to: FieldDefinitionRelationType }
|
||||
> = {
|
||||
[RelationMetadataType.ManyToMany]: {
|
||||
from: 'FROM_MANY_OBJECTS',
|
||||
to: 'TO_MANY_OBJECTS',
|
||||
},
|
||||
[RelationMetadataType.OneToMany]: {
|
||||
from: 'FROM_MANY_OBJECTS',
|
||||
to: 'TO_ONE_OBJECT',
|
||||
},
|
||||
[RelationMetadataType.ManyToOne]: {
|
||||
from: 'TO_ONE_OBJECT',
|
||||
to: 'FROM_MANY_OBJECTS',
|
||||
},
|
||||
[RelationMetadataType.OneToOne]: {
|
||||
from: 'FROM_ONE_OBJECT',
|
||||
to: 'TO_ONE_OBJECT',
|
||||
},
|
||||
};
|
||||
|
||||
if (
|
||||
isDefined(field.fromRelationMetadata) &&
|
||||
field.fromRelationMetadata.relationType in config
|
||||
) {
|
||||
return config[field.fromRelationMetadata.relationType].from;
|
||||
}
|
||||
|
||||
if (
|
||||
isDefined(field.toRelationMetadata) &&
|
||||
field.toRelationMetadata.relationType in config
|
||||
) {
|
||||
return config[field.toRelationMetadata.relationType].to;
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Cannot determine field relation type for field : ${JSON.stringify(
|
||||
field,
|
||||
)}.`,
|
||||
);
|
||||
};
|
@ -6,7 +6,6 @@ import { metadataLabelSchema } from '@/object-metadata/validation-schemas/metada
|
||||
import {
|
||||
FieldMetadataType,
|
||||
RelationDefinitionType,
|
||||
RelationMetadataType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { camelCaseStringSchema } from '~/utils/validation-schemas/camelCaseStringSchema';
|
||||
|
||||
@ -16,24 +15,6 @@ export const fieldMetadataItemSchema = (existingLabels?: string[]) => {
|
||||
createdAt: z.string().datetime(),
|
||||
defaultValue: z.any().optional(),
|
||||
description: z.string().trim().nullable().optional(),
|
||||
fromRelationMetadata: z
|
||||
.object({
|
||||
__typename: z.literal('relation').optional(),
|
||||
id: z.string().uuid(),
|
||||
relationType: z.nativeEnum(RelationMetadataType),
|
||||
toFieldMetadataId: z.string().uuid(),
|
||||
toObjectMetadata: z.object({
|
||||
__typename: z.literal('object').optional(),
|
||||
dataSourceId: z.string().uuid(),
|
||||
id: z.string().uuid(),
|
||||
isRemote: z.boolean(),
|
||||
isSystem: z.boolean(),
|
||||
namePlural: z.string().trim().min(1),
|
||||
nameSingular: z.string().trim().min(1),
|
||||
}),
|
||||
})
|
||||
.nullable()
|
||||
.optional(),
|
||||
icon: z.string().startsWith('Icon').trim().nullable(),
|
||||
id: z.string().uuid(),
|
||||
isActive: z.boolean(),
|
||||
@ -84,24 +65,6 @@ export const fieldMetadataItemSchema = (existingLabels?: string[]) => {
|
||||
})
|
||||
.nullable()
|
||||
.optional(),
|
||||
toRelationMetadata: z
|
||||
.object({
|
||||
__typename: z.literal('relation').optional(),
|
||||
id: z.string().uuid(),
|
||||
relationType: z.nativeEnum(RelationMetadataType),
|
||||
fromFieldMetadataId: z.string().uuid(),
|
||||
fromObjectMetadata: z.object({
|
||||
__typename: z.literal('object').optional(),
|
||||
id: z.string().uuid(),
|
||||
dataSourceId: z.string().uuid(),
|
||||
isRemote: z.boolean(),
|
||||
isSystem: z.boolean(),
|
||||
namePlural: z.string().trim().min(1),
|
||||
nameSingular: z.string().trim().min(1),
|
||||
}),
|
||||
})
|
||||
.nullable()
|
||||
.optional(),
|
||||
type: z.nativeEnum(FieldMetadataType),
|
||||
updatedAt: z.string().datetime(),
|
||||
}) satisfies z.ZodType<FieldMetadataItem>;
|
||||
|
@ -4,17 +4,6 @@ import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
import { FieldMetadata } from './FieldMetadata';
|
||||
|
||||
export type FieldDefinitionRelationType =
|
||||
| 'FROM_MANY_OBJECTS'
|
||||
| 'FROM_ONE_OBJECT'
|
||||
| 'TO_MANY_OBJECTS'
|
||||
| 'TO_ONE_OBJECT';
|
||||
|
||||
export type RelationDirections = {
|
||||
from: FieldDefinitionRelationType;
|
||||
to: FieldDefinitionRelationType;
|
||||
};
|
||||
|
||||
export type FieldDefinition<T extends FieldMetadata> = {
|
||||
fieldMetadataId: string;
|
||||
label: string;
|
||||
|
@ -3,8 +3,8 @@ import { ThemeColor } from 'twenty-ui';
|
||||
import { RATING_VALUES } from '@/object-record/record-field/meta-types/constants/RatingValues';
|
||||
import { ZodHelperLiteral } from '@/object-record/record-field/types/ZodHelperLiteral';
|
||||
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect';
|
||||
import { WithNarrowedStringLiteralProperty } from '~/types/WithNarrowedStringLiteralProperty';
|
||||
|
||||
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||
import { CurrencyCode } from './CurrencyCode';
|
||||
|
||||
export type FieldUuidMetadata = {
|
||||
@ -110,35 +110,17 @@ export type FieldPositionMetadata = {
|
||||
fieldName: string;
|
||||
};
|
||||
|
||||
export type FieldDefinitionRelationType =
|
||||
| 'FROM_MANY_OBJECTS'
|
||||
| 'FROM_ONE_OBJECT'
|
||||
| 'TO_MANY_OBJECTS'
|
||||
| 'TO_ONE_OBJECT';
|
||||
|
||||
export type FieldRelationMetadata = {
|
||||
fieldName: string;
|
||||
objectMetadataNameSingular?: string;
|
||||
relationFieldMetadataId: string;
|
||||
relationObjectMetadataNamePlural: string;
|
||||
relationObjectMetadataNameSingular: string;
|
||||
relationType?: FieldDefinitionRelationType;
|
||||
relationType?: RelationDefinitionType;
|
||||
targetFieldMetadataName?: string;
|
||||
useEditButton?: boolean;
|
||||
};
|
||||
|
||||
export type FieldRelationOneMetadata = WithNarrowedStringLiteralProperty<
|
||||
FieldRelationMetadata,
|
||||
'relationType',
|
||||
'TO_ONE_OBJECT'
|
||||
>;
|
||||
|
||||
export type FieldRelationManyMetadata = WithNarrowedStringLiteralProperty<
|
||||
FieldRelationMetadata,
|
||||
'relationType',
|
||||
'FROM_MANY_OBJECTS'
|
||||
>;
|
||||
|
||||
export type FieldSelectMetadata = {
|
||||
objectMetadataNameSingular?: string;
|
||||
fieldName: string;
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation';
|
||||
|
||||
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||
import { FieldDefinition } from '../FieldDefinition';
|
||||
import { FieldMetadata, FieldRelationManyMetadata } from '../FieldMetadata';
|
||||
import { FieldMetadata } from '../FieldMetadata';
|
||||
|
||||
export const isFieldRelationFromManyObjects = (
|
||||
field: Pick<FieldDefinition<FieldMetadata>, 'type' | 'metadata'>,
|
||||
): field is FieldDefinition<FieldRelationManyMetadata> =>
|
||||
isFieldRelation(field) && field.metadata.relationType === 'FROM_MANY_OBJECTS';
|
||||
): field is FieldDefinition<FieldMetadata> =>
|
||||
isFieldRelation(field) &&
|
||||
field.metadata.relationType === RelationDefinitionType.OneToMany;
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation';
|
||||
|
||||
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||
import { FieldDefinition } from '../FieldDefinition';
|
||||
import { FieldMetadata, FieldRelationOneMetadata } from '../FieldMetadata';
|
||||
import { FieldMetadata } from '../FieldMetadata';
|
||||
|
||||
export const isFieldRelationToOneObject = (
|
||||
field: Pick<FieldDefinition<FieldMetadata>, 'type' | 'metadata'>,
|
||||
): field is FieldDefinition<FieldRelationOneMetadata> =>
|
||||
isFieldRelation(field) && field.metadata.relationType === 'TO_ONE_OBJECT';
|
||||
): field is FieldDefinition<FieldMetadata> =>
|
||||
isFieldRelation(field) &&
|
||||
field.metadata.relationType === RelationDefinitionType.ManyToOne;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
||||
|
||||
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||
import {
|
||||
csvDownloader,
|
||||
displayedExportProgress,
|
||||
@ -35,7 +36,10 @@ describe('generateCsv', () => {
|
||||
{ label: 'Nested', metadata: { fieldName: 'nested' } },
|
||||
{
|
||||
label: 'Relation',
|
||||
metadata: { fieldName: 'relation', relationType: 'TO_ONE_OBJECT' },
|
||||
metadata: {
|
||||
fieldName: 'relation',
|
||||
relationType: RelationDefinitionType.ManyToOne,
|
||||
},
|
||||
},
|
||||
] as ColumnDefinition<FieldMetadata>[];
|
||||
const rows = [
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
} from '@/object-record/record-index/options/hooks/useTableData';
|
||||
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||
import { FieldMetadataType } from '~/generated/graphql';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||
@ -43,7 +44,7 @@ export const generateCsv: GenerateExport = ({
|
||||
const columnsToExport = columns.filter(
|
||||
(col) =>
|
||||
!('relationType' in col.metadata && col.metadata.relationType) ||
|
||||
col.metadata.relationType === 'TO_ONE_OBJECT',
|
||||
col.metadata.relationType === RelationDefinitionType.ManyToOne,
|
||||
);
|
||||
|
||||
const objectIdColumn: ColumnDefinition<FieldMetadata> = {
|
||||
|
@ -7,6 +7,7 @@ import { Task } from '@/activities/types/Task';
|
||||
import { InformationBannerDeletedRecord } from '@/information-banner/components/deleted-record/InformationBannerDeletedRecord';
|
||||
import { useLabelIdentifierFieldMetadataItem } from '@/object-metadata/hooks/useLabelIdentifierFieldMetadataItem';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
@ -56,6 +57,8 @@ export const RecordShowContainer = ({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
|
||||
const { labelIdentifierFieldMetadataItem } =
|
||||
useLabelIdentifierFieldMetadataItem({
|
||||
objectNameSingular,
|
||||
@ -119,7 +122,7 @@ export const RecordShowContainer = ({
|
||||
const availableFieldMetadataItems = objectMetadataItem.fields
|
||||
.filter(
|
||||
(fieldMetadataItem) =>
|
||||
isFieldCellSupported(fieldMetadataItem) &&
|
||||
isFieldCellSupported(fieldMetadataItem, objectMetadataItems) &&
|
||||
fieldMetadataItem.id !== labelIdentifierFieldMetadataItem?.id,
|
||||
)
|
||||
.sort((fieldMetadataItemA, fieldMetadataItemB) =>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useCallback, useContext } from 'react';
|
||||
import { css } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useCallback, useContext } from 'react';
|
||||
import {
|
||||
IconChevronDown,
|
||||
IconComponent,
|
||||
@ -11,6 +11,7 @@ import {
|
||||
} from 'twenty-ui';
|
||||
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
||||
import { RecordChip } from '@/object-record/components/RecordChip';
|
||||
@ -37,6 +38,7 @@ import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
|
||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||
import { AnimatedEaseInOut } from '@/ui/utilities/animation/components/AnimatedEaseInOut';
|
||||
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledListItem = styled(RecordDetailRecordsListItem)<{
|
||||
isDropdownOpen?: boolean;
|
||||
@ -89,12 +91,14 @@ export const RecordDetailRelationRecordsListItem = ({
|
||||
relationType,
|
||||
} = fieldDefinition.metadata as FieldRelationMetadata;
|
||||
|
||||
const isToOneObject = relationType === 'TO_ONE_OBJECT';
|
||||
const isToOneObject = relationType === RelationDefinitionType.ManyToOne;
|
||||
const { objectMetadataItem: relationObjectMetadataItem } =
|
||||
useObjectMetadataItem({
|
||||
objectNameSingular: relationObjectMetadataNameSingular,
|
||||
});
|
||||
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
|
||||
const persistField = usePersistField();
|
||||
|
||||
const { updateOneRecord: updateOneRelationRecord } = useUpdateOneRecord({
|
||||
@ -111,7 +115,7 @@ export const RecordDetailRelationRecordsListItem = ({
|
||||
const availableRelationFieldMetadataItems = relationObjectMetadataItem.fields
|
||||
.filter(
|
||||
(fieldMetadataItem) =>
|
||||
isFieldCellSupported(fieldMetadataItem) &&
|
||||
isFieldCellSupported(fieldMetadataItem, objectMetadataItems) &&
|
||||
fieldMetadataItem.id !==
|
||||
relationObjectMetadataItem.labelIdentifierFieldMetadataId &&
|
||||
fieldMetadataItem.id !== relationFieldMetadataId,
|
||||
|
@ -32,6 +32,7 @@ import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
|
||||
import { FilterQueryParams } from '@/views/hooks/internal/useViewFromQueryParams';
|
||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||
|
||||
type RecordDetailRelationSectionProps = {
|
||||
loading: boolean;
|
||||
@ -67,8 +68,8 @@ export const RecordDetailRelationSection = ({
|
||||
>(recordStoreFamilySelector({ recordId, fieldName }));
|
||||
|
||||
// TODO: use new relation type
|
||||
const isToOneObject = relationType === 'TO_ONE_OBJECT';
|
||||
const isFromManyObjects = relationType === 'FROM_MANY_OBJECTS';
|
||||
const isToOneObject = relationType === RelationDefinitionType.ManyToOne;
|
||||
const isToManyObjects = RelationDefinitionType.OneToMany;
|
||||
|
||||
const relationRecords: ObjectRecord[] =
|
||||
fieldValue && isToOneObject
|
||||
@ -160,7 +161,7 @@ export const RecordDetailRelationSection = ({
|
||||
<RecordDetailSectionHeader
|
||||
title={fieldDefinition.label}
|
||||
link={
|
||||
isFromManyObjects
|
||||
isToManyObjects
|
||||
? {
|
||||
to: filterLinkHref,
|
||||
label: `All (${relationRecords.length})`,
|
||||
|
@ -34,22 +34,6 @@ export const mockPerformance = {
|
||||
isNullable: true,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: {
|
||||
__typename: 'relation',
|
||||
id: '0cf72416-3d94-4d94-abf3-7dc9d734435b',
|
||||
relationType: 'ONE_TO_MANY',
|
||||
fromObjectMetadata: {
|
||||
__typename: 'object',
|
||||
id: '79c2d29c-76f6-432f-91c9-df1259b73d95',
|
||||
dataSourceId: '0fd9fd54-0e8d-4f78-911c-76b33436a768',
|
||||
nameSingular: 'company',
|
||||
namePlural: 'companies',
|
||||
isSystem: false,
|
||||
isRemote: false,
|
||||
},
|
||||
fromFieldMetadataId: '7b281010-5f47-4771-b3f5-f4bcd24ed1b5',
|
||||
},
|
||||
defaultValue: null,
|
||||
options: null,
|
||||
relationDefinition: {
|
||||
@ -94,8 +78,6 @@ export const mockPerformance = {
|
||||
isNullable: false,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: "''",
|
||||
options: null,
|
||||
relationDefinition: null,
|
||||
@ -114,8 +96,6 @@ export const mockPerformance = {
|
||||
isNullable: false,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: "''",
|
||||
options: null,
|
||||
relationDefinition: null,
|
||||
@ -134,22 +114,6 @@ export const mockPerformance = {
|
||||
isNullable: true,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: {
|
||||
__typename: 'relation',
|
||||
id: 'd76f949d-023d-4b45-a71e-f39e3b1562ba',
|
||||
relationType: 'ONE_TO_MANY',
|
||||
toObjectMetadata: {
|
||||
__typename: 'object',
|
||||
id: '82222ca2-dd40-44ec-b8c5-eb0eca9ec625',
|
||||
dataSourceId: '0fd9fd54-0e8d-4f78-911c-76b33436a768',
|
||||
nameSingular: 'activityTarget',
|
||||
namePlural: 'activityTargets',
|
||||
isSystem: true,
|
||||
isRemote: false,
|
||||
},
|
||||
toFieldMetadataId: 'f5f515cc-6d8a-44c3-b2d4-f04b9868a9c5',
|
||||
},
|
||||
toRelationMetadata: null,
|
||||
defaultValue: null,
|
||||
options: null,
|
||||
relationDefinition: {
|
||||
@ -194,22 +158,6 @@ export const mockPerformance = {
|
||||
isNullable: true,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: {
|
||||
__typename: 'relation',
|
||||
id: 'a5a61d23-8ac9-4014-9441-ec3a1781a661',
|
||||
relationType: 'ONE_TO_MANY',
|
||||
toObjectMetadata: {
|
||||
__typename: 'object',
|
||||
id: '494b9b7c-a44e-4d52-b274-cdfb0e322165',
|
||||
dataSourceId: '0fd9fd54-0e8d-4f78-911c-76b33436a768',
|
||||
nameSingular: 'opportunity',
|
||||
namePlural: 'opportunities',
|
||||
isSystem: false,
|
||||
isRemote: false,
|
||||
},
|
||||
toFieldMetadataId: '86559a6f-6afc-4d5c-9bed-fc74d063791b',
|
||||
},
|
||||
toRelationMetadata: null,
|
||||
defaultValue: null,
|
||||
options: null,
|
||||
relationDefinition: {
|
||||
@ -254,22 +202,6 @@ export const mockPerformance = {
|
||||
isNullable: true,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: {
|
||||
__typename: 'relation',
|
||||
id: '456f7875-b48c-4795-a0c7-a69d7339afee',
|
||||
relationType: 'ONE_TO_MANY',
|
||||
toObjectMetadata: {
|
||||
__typename: 'object',
|
||||
id: 'eba13fca-57b7-470c-8c23-a0e640e04ffb',
|
||||
dataSourceId: '0fd9fd54-0e8d-4f78-911c-76b33436a768',
|
||||
nameSingular: 'calendarEventParticipant',
|
||||
namePlural: 'calendarEventParticipants',
|
||||
isSystem: true,
|
||||
isRemote: false,
|
||||
},
|
||||
toFieldMetadataId: 'c1cdebda-b514-4487-9b9c-aa59d8fca8eb',
|
||||
},
|
||||
toRelationMetadata: null,
|
||||
defaultValue: null,
|
||||
options: null,
|
||||
relationDefinition: {
|
||||
@ -314,8 +246,6 @@ export const mockPerformance = {
|
||||
isNullable: false,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: 'now',
|
||||
options: null,
|
||||
relationDefinition: null,
|
||||
@ -334,22 +264,6 @@ export const mockPerformance = {
|
||||
isNullable: true,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: {
|
||||
__typename: 'relation',
|
||||
id: '31542774-fb15-4d01-b00b-8fc94887f458',
|
||||
relationType: 'ONE_TO_MANY',
|
||||
toObjectMetadata: {
|
||||
__typename: 'object',
|
||||
id: 'f08422e2-14cd-4966-9cd3-bce0302cc56f',
|
||||
dataSourceId: '0fd9fd54-0e8d-4f78-911c-76b33436a768',
|
||||
nameSingular: 'favorite',
|
||||
namePlural: 'favorites',
|
||||
isSystem: true,
|
||||
isRemote: false,
|
||||
},
|
||||
toFieldMetadataId: '67d28b17-ff3c-49b4-a6da-1354be9634b0',
|
||||
},
|
||||
toRelationMetadata: null,
|
||||
defaultValue: null,
|
||||
options: null,
|
||||
relationDefinition: {
|
||||
@ -394,8 +308,6 @@ export const mockPerformance = {
|
||||
isNullable: true,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: {
|
||||
primaryLinkUrl: "''",
|
||||
primaryLinkLabel: "''",
|
||||
@ -417,22 +329,6 @@ export const mockPerformance = {
|
||||
isNullable: true,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: {
|
||||
__typename: 'relation',
|
||||
id: 'c0cc3456-afa4-46e0-820d-2db0b63a8273',
|
||||
relationType: 'ONE_TO_MANY',
|
||||
toObjectMetadata: {
|
||||
__typename: 'object',
|
||||
id: '0e3c9a9d-8a60-4671-a466-7b840a422da2',
|
||||
dataSourceId: '0fd9fd54-0e8d-4f78-911c-76b33436a768',
|
||||
nameSingular: 'attachment',
|
||||
namePlural: 'attachments',
|
||||
isSystem: true,
|
||||
isRemote: false,
|
||||
},
|
||||
toFieldMetadataId: 'a920a0d6-8e71-4ab8-90b9-ab540e04732a',
|
||||
},
|
||||
toRelationMetadata: null,
|
||||
defaultValue: null,
|
||||
options: null,
|
||||
relationDefinition: {
|
||||
@ -477,8 +373,6 @@ export const mockPerformance = {
|
||||
isNullable: false,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: "''",
|
||||
options: null,
|
||||
relationDefinition: null,
|
||||
@ -497,8 +391,6 @@ export const mockPerformance = {
|
||||
isNullable: true,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: null,
|
||||
options: null,
|
||||
relationDefinition: null,
|
||||
@ -517,8 +409,6 @@ export const mockPerformance = {
|
||||
isNullable: false,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: "''",
|
||||
options: null,
|
||||
relationDefinition: null,
|
||||
@ -537,8 +427,6 @@ export const mockPerformance = {
|
||||
isNullable: false,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: "''",
|
||||
options: null,
|
||||
relationDefinition: null,
|
||||
@ -557,8 +445,6 @@ export const mockPerformance = {
|
||||
isNullable: false,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: 'now',
|
||||
options: null,
|
||||
relationDefinition: null,
|
||||
@ -577,8 +463,6 @@ export const mockPerformance = {
|
||||
isNullable: true,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: null,
|
||||
options: null,
|
||||
relationDefinition: null,
|
||||
@ -597,22 +481,6 @@ export const mockPerformance = {
|
||||
isNullable: true,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: {
|
||||
__typename: 'relation',
|
||||
id: '25150feb-fcd7-407e-b5fa-ffe58a0450ac',
|
||||
relationType: 'ONE_TO_MANY',
|
||||
toObjectMetadata: {
|
||||
__typename: 'object',
|
||||
id: '83b5ff3e-975e-4dc9-ba4d-c645a0d8afb2',
|
||||
dataSourceId: '0fd9fd54-0e8d-4f78-911c-76b33436a768',
|
||||
nameSingular: 'timelineActivity',
|
||||
namePlural: 'timelineActivities',
|
||||
isSystem: true,
|
||||
isRemote: false,
|
||||
},
|
||||
toFieldMetadataId: '556a12d4-ef0a-4232-963f-0f317f4c5ef5',
|
||||
},
|
||||
toRelationMetadata: null,
|
||||
defaultValue: null,
|
||||
options: null,
|
||||
relationDefinition: {
|
||||
@ -657,8 +525,6 @@ export const mockPerformance = {
|
||||
isNullable: true,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: {
|
||||
lastName: "''",
|
||||
firstName: "''",
|
||||
@ -680,8 +546,6 @@ export const mockPerformance = {
|
||||
isNullable: true,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: {
|
||||
primaryLinkUrl: "''",
|
||||
primaryLinkLabel: "''",
|
||||
@ -703,22 +567,6 @@ export const mockPerformance = {
|
||||
isNullable: true,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: {
|
||||
__typename: 'relation',
|
||||
id: 'e2eb7156-6e65-4bf8-922b-670179744f27',
|
||||
relationType: 'ONE_TO_MANY',
|
||||
toObjectMetadata: {
|
||||
__typename: 'object',
|
||||
id: 'ffd8e640-84b7-4ed6-99e9-14def0f9d82b',
|
||||
dataSourceId: '0fd9fd54-0e8d-4f78-911c-76b33436a768',
|
||||
nameSingular: 'messageParticipant',
|
||||
namePlural: 'messageParticipants',
|
||||
isSystem: true,
|
||||
isRemote: false,
|
||||
},
|
||||
toFieldMetadataId: '8c4593a1-ad40-4681-92fe-43ad4fe60205',
|
||||
},
|
||||
toRelationMetadata: null,
|
||||
defaultValue: null,
|
||||
options: null,
|
||||
relationDefinition: {
|
||||
@ -763,8 +611,6 @@ export const mockPerformance = {
|
||||
isNullable: false,
|
||||
createdAt: '2024-05-16T10:54:27.788Z',
|
||||
updatedAt: '2024-05-16T10:54:27.788Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: 'uuid',
|
||||
options: null,
|
||||
relationDefinition: null,
|
||||
|
@ -6,7 +6,10 @@ import { useOpenSpreadsheetImportDialog } from '@/spreadsheet-import/hooks/useOp
|
||||
import { SpreadsheetImportDialogOptions } from '@/spreadsheet-import/types';
|
||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
RelationDefinitionType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
export const useOpenObjectRecordsSpreasheetImportDialog = (
|
||||
objectNameSingular: string,
|
||||
@ -37,7 +40,8 @@ export const useOpenObjectRecordsSpreasheetImportDialog = (
|
||||
(!fieldMetadataItem.isSystem || fieldMetadataItem.name === 'id') &&
|
||||
fieldMetadataItem.name !== 'createdAt' &&
|
||||
(fieldMetadataItem.type !== FieldMetadataType.Relation ||
|
||||
fieldMetadataItem.toRelationMetadata),
|
||||
fieldMetadataItem.relationDefinition?.direction ===
|
||||
RelationDefinitionType.ManyToOne),
|
||||
)
|
||||
.sort((fieldMetadataItemA, fieldMetadataItemB) =>
|
||||
fieldMetadataItemA.name.localeCompare(fieldMetadataItemB.name),
|
||||
|
@ -2,14 +2,14 @@ import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'
|
||||
import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation';
|
||||
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
||||
import { TABLE_COLUMNS_DENY_LIST } from '@/object-record/relation-picker/constants/TableColumnsDenyList';
|
||||
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const filterAvailableTableColumns = (
|
||||
columnDefinition: ColumnDefinition<FieldMetadata>,
|
||||
): boolean => {
|
||||
if (
|
||||
isFieldRelation(columnDefinition) &&
|
||||
columnDefinition.metadata?.relationType !== 'TO_ONE_OBJECT' &&
|
||||
columnDefinition.metadata?.relationType !== 'FROM_MANY_OBJECTS'
|
||||
columnDefinition.metadata?.relationType !== RelationDefinitionType.ManyToOne
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
@ -4,10 +4,7 @@ import { generateEmptyFieldValue } from '@/object-record/utils/generateEmptyFiel
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
export const generateDefaultFieldValue = (
|
||||
fieldMetadataItem: Pick<
|
||||
FieldMetadataItem,
|
||||
'defaultValue' | 'type' | 'fromRelationMetadata'
|
||||
>,
|
||||
fieldMetadataItem: Pick<FieldMetadataItem, 'defaultValue' | 'type'>,
|
||||
) => {
|
||||
const defaultValue = isFieldValueEmpty({
|
||||
fieldValue: fieldMetadataItem.defaultValue,
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
RelationDefinitionType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
export const generateEmptyFieldValue = (
|
||||
fieldMetadataItem: Pick<FieldMetadataItem, 'type' | 'fromRelationMetadata'>,
|
||||
fieldMetadataItem: Pick<FieldMetadataItem, 'type' | 'relationDefinition'>,
|
||||
) => {
|
||||
switch (fieldMetadataItem.type) {
|
||||
case FieldMetadataType.Email:
|
||||
@ -62,10 +63,8 @@ export const generateEmptyFieldValue = (
|
||||
}
|
||||
case FieldMetadataType.Relation: {
|
||||
if (
|
||||
!isNonEmptyString(
|
||||
fieldMetadataItem.fromRelationMetadata?.toObjectMetadata
|
||||
?.nameSingular,
|
||||
)
|
||||
fieldMetadataItem.relationDefinition?.direction ===
|
||||
RelationDefinitionType.ManyToOne
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
@ -1,12 +1,16 @@
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { isObjectMetadataAvailableForRelation } from '@/object-metadata/utils/isObjectMetadataAvailableForRelation';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
RelationMetadataType,
|
||||
RelationDefinitionType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
export const isFieldCellSupported = (fieldMetadataItem: FieldMetadataItem) => {
|
||||
export const isFieldCellSupported = (
|
||||
fieldMetadataItem: FieldMetadataItem,
|
||||
objectMetadataItems: ObjectMetadataItem[],
|
||||
) => {
|
||||
if (
|
||||
[
|
||||
FieldMetadataType.Uuid,
|
||||
@ -18,17 +22,17 @@ export const isFieldCellSupported = (fieldMetadataItem: FieldMetadataItem) => {
|
||||
}
|
||||
|
||||
if (fieldMetadataItem.type === FieldMetadataType.Relation) {
|
||||
const relationMetadata =
|
||||
fieldMetadataItem.fromRelationMetadata ??
|
||||
fieldMetadataItem.toRelationMetadata;
|
||||
const relationObjectMetadataItem =
|
||||
fieldMetadataItem.fromRelationMetadata?.toObjectMetadata ??
|
||||
fieldMetadataItem.toRelationMetadata?.fromObjectMetadata;
|
||||
const relationObjectMetadataItemId =
|
||||
fieldMetadataItem.relationDefinition?.targetObjectMetadata.id;
|
||||
|
||||
const relationObjectMetadataItem = objectMetadataItems.find(
|
||||
(item) => item.id === relationObjectMetadataItemId,
|
||||
);
|
||||
|
||||
// Hack to display targets on Notes and Tasks
|
||||
if (
|
||||
fieldMetadataItem.fromRelationMetadata?.toObjectMetadata?.nameSingular ===
|
||||
CoreObjectNameSingular.NoteTarget &&
|
||||
fieldMetadataItem.relationDefinition?.targetObjectMetadata
|
||||
?.nameSingular === CoreObjectNameSingular.NoteTarget &&
|
||||
fieldMetadataItem.relationDefinition?.sourceObjectMetadata
|
||||
.nameSingular === CoreObjectNameSingular.Note
|
||||
) {
|
||||
@ -36,8 +40,8 @@ export const isFieldCellSupported = (fieldMetadataItem: FieldMetadataItem) => {
|
||||
}
|
||||
|
||||
if (
|
||||
fieldMetadataItem.fromRelationMetadata?.toObjectMetadata?.nameSingular ===
|
||||
CoreObjectNameSingular.TaskTarget &&
|
||||
fieldMetadataItem.relationDefinition?.targetObjectMetadata
|
||||
?.nameSingular === CoreObjectNameSingular.TaskTarget &&
|
||||
fieldMetadataItem.relationDefinition?.sourceObjectMetadata
|
||||
.nameSingular === CoreObjectNameSingular.Task
|
||||
) {
|
||||
@ -45,9 +49,10 @@ export const isFieldCellSupported = (fieldMetadataItem: FieldMetadataItem) => {
|
||||
}
|
||||
|
||||
if (
|
||||
!relationMetadata ||
|
||||
!fieldMetadataItem.relationDefinition ||
|
||||
// TODO: Many to many relations are not supported yet.
|
||||
relationMetadata.relationType === RelationMetadataType.ManyToMany ||
|
||||
fieldMetadataItem.relationDefinition.direction ===
|
||||
RelationDefinitionType.ManyToMany ||
|
||||
!relationObjectMetadataItem ||
|
||||
!isObjectMetadataAvailableForRelation(relationObjectMetadataItem)
|
||||
) {
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { isString } from '@sniptt/guards';
|
||||
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { isFieldRelationToOneValue } from '@/object-record/record-field/types/guards/isFieldRelationToOneValue';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||
import { FieldMetadataType } from '~/generated/graphql';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { getUrlHostName } from '~/utils/url/getUrlHostName';
|
||||
@ -29,7 +29,8 @@ export const sanitizeRecordInput = ({
|
||||
|
||||
if (
|
||||
fieldMetadataItem.type === FieldMetadataType.Relation &&
|
||||
isFieldRelationToOneValue(fieldValue)
|
||||
fieldMetadataItem.relationDefinition?.direction ===
|
||||
RelationDefinitionType.ManyToOne
|
||||
) {
|
||||
const relationIdFieldName = `${fieldMetadataItem.name}Id`;
|
||||
const relationIdFieldMetadataItem = objectMetadataItem.fields.find(
|
||||
@ -41,6 +42,14 @@ export const sanitizeRecordInput = ({
|
||||
: undefined;
|
||||
}
|
||||
|
||||
if (
|
||||
fieldMetadataItem.type === FieldMetadataType.Relation &&
|
||||
fieldMetadataItem.relationDefinition?.direction ===
|
||||
RelationDefinitionType.OneToMany
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return [fieldName, fieldValue];
|
||||
})
|
||||
.filter(isDefined),
|
||||
|
@ -1,12 +1,12 @@
|
||||
import {
|
||||
IconComponent,
|
||||
IconRelationManyToMany,
|
||||
IconRelationManyToOne,
|
||||
IconRelationOneToMany,
|
||||
IconRelationOneToOne,
|
||||
} from 'twenty-ui';
|
||||
|
||||
import { RelationMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||
import OneToManySvg from '../assets/OneToMany.svg';
|
||||
import OneToOneSvg from '../assets/OneToOne.svg';
|
||||
import { RelationType } from '../types/RelationType';
|
||||
@ -20,20 +20,27 @@ export const RELATION_TYPES: Record<
|
||||
isImageFlipped?: boolean;
|
||||
}
|
||||
> = {
|
||||
[RelationMetadataType.OneToMany]: {
|
||||
[RelationDefinitionType.OneToMany]: {
|
||||
label: 'Has many',
|
||||
Icon: IconRelationOneToMany,
|
||||
imageSrc: OneToManySvg,
|
||||
},
|
||||
[RelationMetadataType.OneToOne]: {
|
||||
[RelationDefinitionType.OneToOne]: {
|
||||
label: 'Has one',
|
||||
Icon: IconRelationOneToOne,
|
||||
imageSrc: OneToOneSvg,
|
||||
},
|
||||
MANY_TO_ONE: {
|
||||
[RelationDefinitionType.ManyToOne]: {
|
||||
label: 'Belongs to one',
|
||||
Icon: IconRelationManyToOne,
|
||||
imageSrc: OneToManySvg,
|
||||
isImageFlipped: true,
|
||||
},
|
||||
// Not supported yet
|
||||
[RelationDefinitionType.ManyToMany]: {
|
||||
label: 'Belongs to many',
|
||||
Icon: IconRelationManyToMany,
|
||||
imageSrc: OneToManySvg,
|
||||
isImageFlipped: true,
|
||||
},
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import styled from '@emotion/styled';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import { useIcons } from 'twenty-ui';
|
||||
import { z } from 'zod';
|
||||
|
||||
@ -14,6 +14,7 @@ import { RelationType } from '@/settings/data-model/types/RelationType';
|
||||
import { IconPicker } from '@/ui/input/components/IconPicker';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const settingsDataModelFieldRelationFormSchema = z.object({
|
||||
relation: z.object({
|
||||
@ -23,7 +24,10 @@ export const settingsDataModelFieldRelationFormSchema = z.object({
|
||||
}),
|
||||
objectMetadataId: z.string().uuid(),
|
||||
type: z.enum(
|
||||
Object.keys(RELATION_TYPES) as [RelationType, ...RelationType[]],
|
||||
Object.keys(RELATION_TYPES) as [
|
||||
RelationDefinitionType,
|
||||
...RelationDefinitionType[],
|
||||
],
|
||||
),
|
||||
}),
|
||||
});
|
||||
@ -33,10 +37,7 @@ export type SettingsDataModelFieldRelationFormValues = z.infer<
|
||||
>;
|
||||
|
||||
type SettingsDataModelFieldRelationFormProps = {
|
||||
fieldMetadataItem: Pick<
|
||||
FieldMetadataItem,
|
||||
'fromRelationMetadata' | 'toRelationMetadata' | 'type'
|
||||
>;
|
||||
fieldMetadataItem: Pick<FieldMetadataItem, 'type'>;
|
||||
};
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import styled from '@emotion/styled';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
|
||||
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
|
@ -5,15 +5,12 @@ import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMe
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { isObjectMetadataAvailableForRelation } from '@/object-metadata/utils/isObjectMetadataAvailableForRelation';
|
||||
import { RelationMetadataType } from '~/generated-metadata/graphql';
|
||||
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const useRelationSettingsFormInitialValues = ({
|
||||
fieldMetadataItem,
|
||||
}: {
|
||||
fieldMetadataItem?: Pick<
|
||||
FieldMetadataItem,
|
||||
'fromRelationMetadata' | 'toRelationMetadata' | 'type'
|
||||
>;
|
||||
fieldMetadataItem?: Pick<FieldMetadataItem, 'type' | 'relationDefinition'>;
|
||||
}) => {
|
||||
const { objectMetadataItems } = useFilteredObjectMetadataItems();
|
||||
|
||||
@ -39,7 +36,7 @@ export const useRelationSettingsFormInitialValues = ({
|
||||
);
|
||||
|
||||
const initialRelationType =
|
||||
relationTypeFromFieldMetadata ?? RelationMetadataType.OneToMany;
|
||||
relationTypeFromFieldMetadata ?? RelationDefinitionType.OneToMany;
|
||||
|
||||
return {
|
||||
disableFieldEdition:
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Edge, Node } from 'reactflow';
|
||||
import dagre from '@dagrejs/dagre';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { useEffect } from 'react';
|
||||
import { Edge, Node } from 'reactflow';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
@ -43,10 +43,10 @@ export const SettingsDataModelOverviewEffect = ({
|
||||
|
||||
for (const field of object.fields) {
|
||||
if (
|
||||
isDefined(field.toRelationMetadata) &&
|
||||
isDefined(field.relationDefinition) &&
|
||||
isDefined(
|
||||
items.find(
|
||||
(x) => x.id === field.toRelationMetadata?.fromObjectMetadata.id,
|
||||
(x) => x.id === field.relationDefinition?.targetObjectMetadata.id,
|
||||
),
|
||||
)
|
||||
) {
|
||||
@ -59,8 +59,8 @@ export const SettingsDataModelOverviewEffect = ({
|
||||
id: `${sourceObj}-${targetObj}`,
|
||||
source: object.namePlural,
|
||||
sourceHandle: `${field.id}-right`,
|
||||
target: field.toRelationMetadata.fromObjectMetadata.namePlural,
|
||||
targetHandle: `${field.toRelationMetadata.fromFieldMetadataId}-left`,
|
||||
target: field.relationDefinition.targetObjectMetadata.namePlural,
|
||||
targetHandle: `${field.relationDefinition.targetObjectMetadata}-left`,
|
||||
type: 'smoothstep',
|
||||
style: {
|
||||
strokeWidth: 1,
|
||||
@ -70,8 +70,8 @@ export const SettingsDataModelOverviewEffect = ({
|
||||
markerStart: 'marker',
|
||||
data: {
|
||||
sourceField: field.id,
|
||||
targetField: field.toRelationMetadata.fromFieldMetadataId,
|
||||
relation: field.toRelationMetadata.relationType,
|
||||
targetField: field.relationDefinition.targetFieldMetadata.id,
|
||||
relation: field.relationDefinition.direction,
|
||||
sourceObject: sourceObj,
|
||||
targetObject: targetObj,
|
||||
},
|
||||
|
@ -6,6 +6,7 @@ import { useIcons } from 'twenty-ui';
|
||||
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||
|
||||
type ObjectFieldRowProps = {
|
||||
field: FieldMetadataItem;
|
||||
@ -42,21 +43,33 @@ export const ObjectFieldRow = ({ field }: ObjectFieldRowProps) => {
|
||||
{Icon && <Icon size={theme.icon.size.md} />}
|
||||
<StyledFieldName>{relatedObject?.labelPlural ?? ''}</StyledFieldName>
|
||||
<Handle
|
||||
type={field.toRelationMetadata ? 'source' : 'target'}
|
||||
type={
|
||||
field.relationDefinition?.direction ===
|
||||
RelationDefinitionType.OneToMany
|
||||
? 'source'
|
||||
: 'target'
|
||||
}
|
||||
position={Position.Right}
|
||||
id={`${field.id}-right`}
|
||||
className={
|
||||
field.fromRelationMetadata
|
||||
field.relationDefinition?.direction ===
|
||||
RelationDefinitionType.OneToMany
|
||||
? 'right-handle source-handle'
|
||||
: 'right-handle target-handle'
|
||||
}
|
||||
/>
|
||||
<Handle
|
||||
type={field.toRelationMetadata ? 'source' : 'target'}
|
||||
type={
|
||||
field.relationDefinition?.direction ===
|
||||
RelationDefinitionType.OneToMany
|
||||
? 'source'
|
||||
: 'target'
|
||||
}
|
||||
position={Position.Left}
|
||||
id={`${field.id}-left`}
|
||||
className={
|
||||
field.fromRelationMetadata
|
||||
field.relationDefinition?.direction ===
|
||||
RelationDefinitionType.OneToMany
|
||||
? 'left-handle source-handle'
|
||||
: 'left-handle target-handle'
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMe
|
||||
import { View } from '@/views/types/View';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { RelationMetadataType } from '~/generated-metadata/graphql';
|
||||
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||
import { SettingsObjectDetailTableItem } from '~/pages/settings/data-model/types/SettingsObjectDetailTableItem';
|
||||
import { SettingsObjectFieldDataType } from './SettingsObjectFieldDataType';
|
||||
|
||||
@ -224,8 +224,8 @@ export const SettingsObjectFieldItemTableRow = ({
|
||||
<SettingsObjectFieldDataType
|
||||
Icon={RelationIcon}
|
||||
label={
|
||||
relationType === RelationMetadataType.ManyToOne ||
|
||||
relationType === RelationMetadataType.OneToOne
|
||||
relationType === RelationDefinitionType.ManyToOne ||
|
||||
relationType === RelationDefinitionType.OneToOne
|
||||
? relationObjectMetadataItem?.labelSingular
|
||||
: relationObjectMetadataItem?.labelPlural
|
||||
}
|
||||
|
@ -1,5 +1,3 @@
|
||||
import { RelationMetadataType } from '~/generated-metadata/graphql';
|
||||
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||
|
||||
export type RelationType =
|
||||
| Exclude<RelationMetadataType, 'MANY_TO_MANY'>
|
||||
| 'MANY_TO_ONE';
|
||||
export type RelationType = RelationDefinitionType;
|
||||
|
@ -2,7 +2,10 @@ import { COMPANY_LABEL_IDENTIFIER_FIELD_METADATA_ID } from '@/object-metadata/ut
|
||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
||||
import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
RelationDefinitionType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
export const SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS = (
|
||||
[
|
||||
@ -65,7 +68,7 @@ export const SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS = (
|
||||
type: FieldMetadataType.Relation,
|
||||
metadata: {
|
||||
fieldName: 'favorites',
|
||||
relationType: 'FROM_MANY_OBJECTS',
|
||||
relationType: RelationDefinitionType.OneToMany,
|
||||
relationObjectMetadataNameSingular: '',
|
||||
relationObjectMetadataNamePlural: '',
|
||||
objectMetadataNameSingular: 'company',
|
||||
@ -99,7 +102,7 @@ export const SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS = (
|
||||
type: FieldMetadataType.Relation,
|
||||
metadata: {
|
||||
fieldName: 'accountOwner',
|
||||
relationType: 'TO_ONE_OBJECT',
|
||||
relationType: RelationDefinitionType.ManyToOne,
|
||||
relationObjectMetadataNameSingular: 'workspaceMember',
|
||||
relationObjectMetadataNamePlural: 'workspaceMembers',
|
||||
objectMetadataNameSingular: 'company',
|
||||
@ -116,7 +119,7 @@ export const SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS = (
|
||||
type: FieldMetadataType.Relation,
|
||||
metadata: {
|
||||
fieldName: 'people',
|
||||
relationType: 'FROM_MANY_OBJECTS',
|
||||
relationType: RelationDefinitionType.OneToMany,
|
||||
relationObjectMetadataNameSingular: '',
|
||||
relationObjectMetadataNamePlural: '',
|
||||
objectMetadataNameSingular: 'company',
|
||||
@ -133,7 +136,7 @@ export const SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS = (
|
||||
type: FieldMetadataType.Relation,
|
||||
metadata: {
|
||||
fieldName: 'attachments',
|
||||
relationType: 'FROM_MANY_OBJECTS',
|
||||
relationType: RelationDefinitionType.OneToMany,
|
||||
relationObjectMetadataNameSingular: '',
|
||||
relationObjectMetadataNamePlural: '',
|
||||
objectMetadataNameSingular: 'company',
|
||||
@ -201,7 +204,7 @@ export const SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS = (
|
||||
type: FieldMetadataType.Relation,
|
||||
metadata: {
|
||||
fieldName: 'opportunities',
|
||||
relationType: 'FROM_MANY_OBJECTS',
|
||||
relationType: RelationDefinitionType.OneToMany,
|
||||
relationObjectMetadataNameSingular: '',
|
||||
relationObjectMetadataNamePlural: '',
|
||||
objectMetadataNameSingular: 'company',
|
||||
@ -235,7 +238,7 @@ export const SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS = (
|
||||
type: FieldMetadataType.Relation,
|
||||
metadata: {
|
||||
fieldName: 'activityTargets',
|
||||
relationType: 'FROM_MANY_OBJECTS',
|
||||
relationType: RelationDefinitionType.OneToMany,
|
||||
relationObjectMetadataNameSingular: '',
|
||||
relationObjectMetadataNamePlural: '',
|
||||
objectMetadataNameSingular: 'company',
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useParams, useSearchParams } from 'react-router-dom';
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import qs from 'qs';
|
||||
import { useMemo } from 'react';
|
||||
import { useParams, useSearchParams } from 'react-router-dom';
|
||||
import { useRecoilCallback, useRecoilValue } from 'recoil';
|
||||
import z from 'zod';
|
||||
|
||||
@ -92,12 +92,12 @@ export const useViewFromQueryParams = () => {
|
||||
if (isUndefinedOrNull(filterDefinition)) return null;
|
||||
|
||||
const relationObjectMetadataNameSingular =
|
||||
fieldMetadataItem.toRelationMetadata?.fromObjectMetadata
|
||||
.nameSingular;
|
||||
fieldMetadataItem.relationDefinition?.targetObjectMetadata
|
||||
?.nameSingular;
|
||||
|
||||
const relationObjectMetadataNamePlural =
|
||||
fieldMetadataItem.toRelationMetadata?.fromObjectMetadata
|
||||
.namePlural;
|
||||
fieldMetadataItem.relationDefinition?.targetObjectMetadata
|
||||
?.namePlural;
|
||||
|
||||
const relationObjectMetadataItem =
|
||||
relationObjectMetadataNameSingular
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -83,22 +83,6 @@ const customObjectMetadataItemEdge: ObjectEdge = {
|
||||
name: 'myCustom',
|
||||
},
|
||||
},
|
||||
toRelationMetadata: null,
|
||||
fromRelationMetadata: {
|
||||
__typename: 'relation',
|
||||
id: 'c5cdbacd-2489-4409-be9e-bb4cb38f6ddd',
|
||||
relationType: 'ONE_TO_MANY',
|
||||
toFieldMetadataId: 'c9607ed7-168d-4743-a56a-689ffcfffe98',
|
||||
toObjectMetadata: {
|
||||
__typename: 'object',
|
||||
id: 'dba899da-7d88-41ac-b70e-5ea612ab4b2e',
|
||||
dataSourceId: 'd36e6a2d-28bc-459d-afd5-fe18e4405729',
|
||||
nameSingular: 'viewField',
|
||||
namePlural: 'viewFields',
|
||||
isSystem: true,
|
||||
isRemote: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -120,8 +104,6 @@ const customObjectMetadataItemEdge: ObjectEdge = {
|
||||
updatedAt: '2024-04-08T12:48:49.538Z',
|
||||
defaultValue: null,
|
||||
relationDefinition: null,
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -143,8 +125,6 @@ const customObjectMetadataItemEdge: ObjectEdge = {
|
||||
updatedAt: '2024-04-08T12:48:49.538Z',
|
||||
defaultValue: null,
|
||||
relationDefinition: null,
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -166,8 +146,6 @@ const customObjectMetadataItemEdge: ObjectEdge = {
|
||||
updatedAt: '2024-04-08T12:48:49.538Z',
|
||||
defaultValue: "''",
|
||||
relationDefinition: null,
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -189,8 +167,6 @@ const customObjectMetadataItemEdge: ObjectEdge = {
|
||||
updatedAt: '2024-04-08T12:48:49.538Z',
|
||||
defaultValue: 'now',
|
||||
relationDefinition: null,
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -212,8 +188,6 @@ const customObjectMetadataItemEdge: ObjectEdge = {
|
||||
updatedAt: '2024-04-08T12:48:49.538Z',
|
||||
defaultValue: 'now',
|
||||
relationDefinition: null,
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -235,8 +209,6 @@ const customObjectMetadataItemEdge: ObjectEdge = {
|
||||
updatedAt: '2024-04-08T12:48:49.538Z',
|
||||
defaultValue: 'uuid',
|
||||
relationDefinition: null,
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -277,8 +249,6 @@ const customObjectMetadataItemEdge: ObjectEdge = {
|
||||
updatedAt: '2024-04-08T12:48:49.538Z',
|
||||
defaultValue: null,
|
||||
relationDefinition: null,
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -17,12 +17,12 @@ import { WorkspaceSchemaFactory } from 'src/engine/api/graphql/workspace-schema.
|
||||
import { TokenService } from 'src/engine/core-modules/auth/services/token.service';
|
||||
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { CoreEngineModule } from 'src/engine/core-modules/core-engine.module';
|
||||
import { useGraphQLErrorHandlerHook } from 'src/engine/core-modules/graphql/hooks/use-graphql-error-handler.hook';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
||||
import { useSentryTracing } from 'src/engine/core-modules/exception-handler/hooks/use-sentry-tracing';
|
||||
import { useGraphQLErrorHandlerHook } from 'src/engine/core-modules/graphql/hooks/use-graphql-error-handler.hook';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { handleExceptionAndConvertToGraphQLError } from 'src/engine/utils/global-exception-handler.util';
|
||||
import { renderApolloPlayground } from 'src/engine/utils/render-apollo-playground.util';
|
||||
|
||||
@ -69,13 +69,18 @@ export class GraphQLConfigService
|
||||
let workspace: Workspace | undefined;
|
||||
|
||||
try {
|
||||
if (!this.tokenService.isTokenPresent(context.req)) {
|
||||
const { user, workspace, apiKey, workspaceMemberId } = context.req;
|
||||
|
||||
if (!workspace) {
|
||||
return new GraphQLSchema({});
|
||||
}
|
||||
|
||||
const data = await this.tokenService.validateToken(context.req);
|
||||
|
||||
return await this.createSchema(context, data);
|
||||
return await this.createSchema(context, {
|
||||
user,
|
||||
workspace,
|
||||
apiKey,
|
||||
workspaceMemberId,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof UnauthorizedException) {
|
||||
throw new GraphQLError('Unauthenticated', {
|
||||
|
@ -2,7 +2,7 @@ import { FindOptionsWhere, ObjectLiteral } from 'typeorm';
|
||||
|
||||
import { RecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||
|
||||
import { FieldMetadataMap } from 'src/engine/api/graphql/graphql-query-runner/utils/convert-object-metadata-to-map.util';
|
||||
import { FieldMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
||||
|
||||
import { GraphqlQueryFilterFieldParser } from './graphql-query-filter-field.parser';
|
||||
|
||||
|
@ -3,9 +3,9 @@ import { FindOptionsWhere, Not, ObjectLiteral } from 'typeorm';
|
||||
import { RecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
|
||||
import { FieldMetadataMap } from 'src/engine/api/graphql/graphql-query-runner/utils/convert-object-metadata-to-map.util';
|
||||
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||
import { FieldMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
||||
import { CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory';
|
||||
import { capitalize } from 'src/utils/capitalize';
|
||||
import { isPlainObject } from 'src/utils/is-plain-object';
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { OrderByDirection } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||
|
||||
import { GraphqlQueryOrderFieldParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-order/graphql-query-order.parser';
|
||||
import { FieldMetadataMap } from 'src/engine/api/graphql/graphql-query-runner/utils/convert-object-metadata-to-map.util';
|
||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { FieldMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
||||
|
||||
describe('GraphqlQueryOrderFieldParser', () => {
|
||||
let parser: GraphqlQueryOrderFieldParser;
|
||||
|
@ -10,12 +10,11 @@ import {
|
||||
GraphqlQueryRunnerException,
|
||||
GraphqlQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
||||
import { FieldMetadataMap } from 'src/engine/api/graphql/graphql-query-runner/utils/convert-object-metadata-to-map.util';
|
||||
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||
import { FieldMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
||||
import { CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory';
|
||||
import { capitalize } from 'src/utils/capitalize';
|
||||
|
||||
export class GraphqlQueryOrderFieldParser {
|
||||
private fieldMetadataMap: FieldMetadataMap;
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
|
||||
import { GraphqlQuerySelectedFieldsParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser';
|
||||
import { ObjectMetadataMap } from 'src/engine/api/graphql/graphql-query-runner/utils/convert-object-metadata-to-map.util';
|
||||
import { getRelationObjectMetadata } from 'src/engine/api/graphql/graphql-query-runner/utils/get-relation-object-metadata.util';
|
||||
import { ObjectMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
||||
|
||||
export class GraphqlQuerySelectedFieldsRelationParser {
|
||||
private objectMetadataMap: ObjectMetadataMap;
|
||||
|
@ -5,9 +5,9 @@ import {
|
||||
GraphqlQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
||||
import { GraphqlQuerySelectedFieldsRelationParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-relation.parser';
|
||||
import { ObjectMetadataMap } from 'src/engine/api/graphql/graphql-query-runner/utils/convert-object-metadata-to-map.util';
|
||||
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||
import { ObjectMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
||||
import { CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory';
|
||||
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
|
||||
import { capitalize } from 'src/utils/capitalize';
|
||||
|
@ -17,7 +17,7 @@ import { GraphqlQuerySelectedFieldsParser } from 'src/engine/api/graphql/graphql
|
||||
import {
|
||||
FieldMetadataMap,
|
||||
ObjectMetadataMap,
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/utils/convert-object-metadata-to-map.util';
|
||||
} from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
||||
|
||||
export class GraphqlQueryParser {
|
||||
private fieldMetadataMap: FieldMetadataMap;
|
||||
|
@ -9,12 +9,12 @@ import {
|
||||
GraphqlQueryRunnerException,
|
||||
GraphqlQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
||||
import { ObjectMetadataMap } from 'src/engine/api/graphql/graphql-query-runner/utils/convert-object-metadata-to-map.util';
|
||||
import { encodeCursor } from 'src/engine/api/graphql/graphql-query-runner/utils/cursors.util';
|
||||
import { getRelationObjectMetadata } from 'src/engine/api/graphql/graphql-query-runner/utils/get-relation-object-metadata.util';
|
||||
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||
import { ObjectMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
||||
import { CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory';
|
||||
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
|
||||
import { isPlainObject } from 'src/utils/is-plain-object';
|
||||
|
@ -19,11 +19,9 @@ import {
|
||||
import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
|
||||
import { ObjectRecordsToGraphqlConnectionMapper } from 'src/engine/api/graphql/graphql-query-runner/orm-mappers/object-records-to-graphql-connection.mapper';
|
||||
import { applyRangeFilter } from 'src/engine/api/graphql/graphql-query-runner/utils/apply-range-filter.util';
|
||||
import {
|
||||
convertObjectMetadataToMap,
|
||||
getObjectMetadata,
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/utils/convert-object-metadata-to-map.util';
|
||||
import { decodeCursor } from 'src/engine/api/graphql/graphql-query-runner/utils/cursors.util';
|
||||
import { getObjectMetadataOrThrow } from 'src/engine/api/graphql/graphql-query-runner/utils/get-object-metadata-or-throw.util';
|
||||
import { generateObjectMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
|
||||
export class GraphqlQueryFindManyResolverService {
|
||||
@ -51,10 +49,10 @@ export class GraphqlQueryFindManyResolverService {
|
||||
authContext.workspace.id,
|
||||
objectMetadataItem.nameSingular,
|
||||
);
|
||||
const objectMetadataMap = convertObjectMetadataToMap(
|
||||
const objectMetadataMap = generateObjectMetadataMap(
|
||||
objectMetadataCollection,
|
||||
);
|
||||
const objectMetadata = getObjectMetadata(
|
||||
const objectMetadata = getObjectMetadataOrThrow(
|
||||
objectMetadataMap,
|
||||
objectMetadataItem.nameSingular,
|
||||
);
|
||||
|
@ -13,10 +13,8 @@ import {
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
||||
import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
|
||||
import { ObjectRecordsToGraphqlConnectionMapper } from 'src/engine/api/graphql/graphql-query-runner/orm-mappers/object-records-to-graphql-connection.mapper';
|
||||
import {
|
||||
convertObjectMetadataToMap,
|
||||
getObjectMetadata,
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/utils/convert-object-metadata-to-map.util';
|
||||
import { getObjectMetadataOrThrow } from 'src/engine/api/graphql/graphql-query-runner/utils/get-object-metadata-or-throw.util';
|
||||
import { generateObjectMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
|
||||
export class GraphqlQueryFindOneResolverService {
|
||||
@ -40,10 +38,11 @@ export class GraphqlQueryFindOneResolverService {
|
||||
authContext.workspace.id,
|
||||
objectMetadataItem.nameSingular,
|
||||
);
|
||||
const objectMetadataMap = convertObjectMetadataToMap(
|
||||
const objectMetadataMap = generateObjectMetadataMap(
|
||||
objectMetadataCollection,
|
||||
);
|
||||
const objectMetadata = getObjectMetadata(
|
||||
|
||||
const objectMetadata = getObjectMetadataOrThrow(
|
||||
objectMetadataMap,
|
||||
objectMetadataItem.nameSingular,
|
||||
);
|
||||
|
@ -0,0 +1,21 @@
|
||||
import {
|
||||
GraphqlQueryRunnerException,
|
||||
GraphqlQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
||||
import { ObjectMetadataMapItem } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
||||
|
||||
export const getObjectMetadataOrThrow = (
|
||||
objectMetadataMap: Record<string, any>,
|
||||
objectName: string,
|
||||
): ObjectMetadataMapItem => {
|
||||
const objectMetadata = objectMetadataMap[objectName];
|
||||
|
||||
if (!objectMetadata) {
|
||||
throw new GraphqlQueryRunnerException(
|
||||
`Object metadata not found for ${objectName}`,
|
||||
GraphqlQueryRunnerExceptionCode.OBJECT_METADATA_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return objectMetadata;
|
||||
};
|
@ -1,6 +1,6 @@
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
|
||||
import { ObjectMetadataMap } from 'src/engine/api/graphql/graphql-query-runner/utils/convert-object-metadata-to-map.util';
|
||||
import { ObjectMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
||||
import {
|
||||
deduceRelationDirection,
|
||||
RelationDirection,
|
||||
|
@ -4,11 +4,11 @@ import GraphQLJSON from 'graphql-type-json';
|
||||
import { useCachedMetadata } from 'src/engine/api/graphql/graphql-config/hooks/use-cached-metadata';
|
||||
import { useThrottler } from 'src/engine/api/graphql/graphql-config/hooks/use-throttler';
|
||||
import { MetadataGraphQLApiModule } from 'src/engine/api/graphql/metadata-graphql-api.module';
|
||||
import { useGraphQLErrorHandlerHook } from 'src/engine/core-modules/graphql/hooks/use-graphql-error-handler.hook';
|
||||
import { DataloaderService } from 'src/engine/dataloaders/dataloader.service';
|
||||
import { CacheStorageService } from 'src/engine/core-modules/cache-storage/cache-storage.service';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
||||
import { useGraphQLErrorHandlerHook } from 'src/engine/core-modules/graphql/hooks/use-graphql-error-handler.hook';
|
||||
import { DataloaderService } from 'src/engine/dataloaders/dataloader.service';
|
||||
import { renderApolloPlayground } from 'src/engine/utils/render-apollo-playground.util';
|
||||
|
||||
export const metadataModuleFactory = async (
|
||||
|
@ -56,13 +56,13 @@ export class WorkspaceSchemaFactory {
|
||||
);
|
||||
}
|
||||
|
||||
const objectMetadataCollection =
|
||||
await this.workspaceCacheStorageService.getObjectMetadataCollection(
|
||||
const objectMetadataMap =
|
||||
await this.workspaceCacheStorageService.getObjectMetadataMap(
|
||||
authContext.workspace.id,
|
||||
currentCacheVersion,
|
||||
);
|
||||
|
||||
if (!objectMetadataCollection) {
|
||||
if (!objectMetadataMap) {
|
||||
await this.workspaceMetadataCacheService.recomputeMetadataCache(
|
||||
authContext.workspace.id,
|
||||
);
|
||||
@ -72,6 +72,13 @@ export class WorkspaceSchemaFactory {
|
||||
);
|
||||
}
|
||||
|
||||
const objectMetadataCollection = Object.values(objectMetadataMap).map(
|
||||
(objectMetadataItem) => ({
|
||||
...objectMetadataItem,
|
||||
fields: Object.values(objectMetadataItem.fields),
|
||||
}),
|
||||
);
|
||||
|
||||
// Get typeDefs from cache
|
||||
let typeDefs = await this.workspaceCacheStorageService.getGraphQLTypeDefs(
|
||||
authContext.workspace.id,
|
||||
|
@ -4,10 +4,10 @@ import { Request, Response } from 'express';
|
||||
|
||||
import { RestApiCoreService } from 'src/engine/api/rest/core/rest-api-core.service';
|
||||
import { cleanGraphQLResponse } from 'src/engine/api/rest/utils/clean-graphql-response.utils';
|
||||
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
|
||||
@Controller('rest/batch/*')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
export class RestApiCoreBatchController {
|
||||
constructor(private readonly restApiCoreService: RestApiCoreService) {}
|
||||
|
||||
|
@ -14,10 +14,11 @@ import { Request, Response } from 'express';
|
||||
|
||||
import { RestApiCoreService } from 'src/engine/api/rest/core/rest-api-core.service';
|
||||
import { cleanGraphQLResponse } from 'src/engine/api/rest/utils/clean-graphql-response.utils';
|
||||
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
|
||||
import { JwtAuthGuard } from 'src/engine/guards/jwt-auth.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
|
||||
@Controller('rest/*')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseGuards(JwtAuthGuard, WorkspaceAuthGuard)
|
||||
export class RestApiCoreController {
|
||||
constructor(private readonly restApiCoreService: RestApiCoreService) {}
|
||||
|
||||
|
@ -1,23 +1,25 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { HttpModule } from '@nestjs/axios';
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { RestApiCoreController } from 'src/engine/api/rest/core/controllers/rest-api-core.controller';
|
||||
import { RestApiCoreService } from 'src/engine/api/rest/core/rest-api-core.service';
|
||||
import { CoreQueryBuilderModule } from 'src/engine/api/rest/core/query-builder/core-query-builder.module';
|
||||
import { AuthModule } from 'src/engine/core-modules/auth/auth.module';
|
||||
import { RestApiMetadataController } from 'src/engine/api/rest/metadata/rest-api-metadata.controller';
|
||||
import { RestApiMetadataService } from 'src/engine/api/rest/metadata/rest-api-metadata.service';
|
||||
import { RestApiCoreBatchController } from 'src/engine/api/rest/core/controllers/rest-api-core-batch.controller';
|
||||
import { RestApiService } from 'src/engine/api/rest/rest-api.service';
|
||||
import { RestApiCoreController } from 'src/engine/api/rest/core/controllers/rest-api-core.controller';
|
||||
import { CoreQueryBuilderModule } from 'src/engine/api/rest/core/query-builder/core-query-builder.module';
|
||||
import { RestApiCoreService } from 'src/engine/api/rest/core/rest-api-core.service';
|
||||
import { EndingBeforeInputFactory } from 'src/engine/api/rest/input-factories/ending-before-input.factory';
|
||||
import { LimitInputFactory } from 'src/engine/api/rest/input-factories/limit-input.factory';
|
||||
import { StartingAfterInputFactory } from 'src/engine/api/rest/input-factories/starting-after-input.factory';
|
||||
import { MetadataQueryBuilderModule } from 'src/engine/api/rest/metadata/query-builder/metadata-query-builder.module';
|
||||
import { RestApiMetadataController } from 'src/engine/api/rest/metadata/rest-api-metadata.controller';
|
||||
import { RestApiMetadataService } from 'src/engine/api/rest/metadata/rest-api-metadata.service';
|
||||
import { RestApiService } from 'src/engine/api/rest/rest-api.service';
|
||||
import { AuthModule } from 'src/engine/core-modules/auth/auth.module';
|
||||
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
CoreQueryBuilderModule,
|
||||
MetadataQueryBuilderModule,
|
||||
WorkspaceCacheStorageModule,
|
||||
AuthModule,
|
||||
HttpModule,
|
||||
],
|
||||
|
@ -12,7 +12,8 @@ import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
|
||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
|
||||
@ArgsType()
|
||||
class GetAISQLQueryArgs {
|
||||
@ -20,7 +21,7 @@ class GetAISQLQueryArgs {
|
||||
text: string;
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseGuards(WorkspaceAuthGuard, UserAuthGuard)
|
||||
@Resolver(() => AISQLQueryResult)
|
||||
export class AISQLQueryResolver {
|
||||
constructor(
|
||||
|
@ -1,21 +1,18 @@
|
||||
import { Resolver, Mutation, Args, Context } from '@nestjs/graphql';
|
||||
import { UseGuards } from '@nestjs/common';
|
||||
import { Args, Context, Mutation, Resolver } from '@nestjs/graphql';
|
||||
|
||||
import { Request } from 'express';
|
||||
|
||||
import { OptionalJwtAuthGuard } from 'src/engine/guards/optional-jwt.auth.guard';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
|
||||
import { AnalyticsService } from './analytics.service';
|
||||
import { Analytics } from './analytics.entity';
|
||||
import { AnalyticsService } from './analytics.service';
|
||||
|
||||
import { CreateAnalyticsInput } from './dtos/create-analytics.input';
|
||||
|
||||
@UseGuards(OptionalJwtAuthGuard)
|
||||
@Resolver(() => Analytics)
|
||||
export class AnalyticsResolver {
|
||||
constructor(
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
|
||||
import { AppToken } from 'src/engine/core-modules/app-token/app-token.entity';
|
||||
import { CreateAppTokenInput } from 'src/engine/core-modules/app-token/dtos/create-app-token.input';
|
||||
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
|
||||
export const appTokenAutoResolverOpts: AutoResolverOpts<
|
||||
any,
|
||||
@ -34,6 +34,6 @@ export const appTokenAutoResolverOpts: AutoResolverOpts<
|
||||
one: { disabled: true },
|
||||
},
|
||||
delete: { many: { disabled: true }, one: { disabled: true } },
|
||||
guards: [JwtAuthGuard],
|
||||
guards: [WorkspaceAuthGuard],
|
||||
},
|
||||
];
|
||||
|
@ -13,7 +13,7 @@ export class BeforeCreateOneAppToken<T extends AppToken>
|
||||
instance: CreateOneInputType<T>,
|
||||
context: any,
|
||||
): Promise<CreateOneInputType<T>> {
|
||||
const userId = context?.req?.user?.user?.id;
|
||||
const userId = context?.req?.user?.id;
|
||||
|
||||
instance.input.userId = userId;
|
||||
// FIXME: These fields should be autogenerated, we need to run a migration for this
|
||||
|
@ -16,13 +16,14 @@ import { UpdatePasswordViaResetTokenInput } from 'src/engine/core-modules/auth/d
|
||||
import { ValidatePasswordResetToken } from 'src/engine/core-modules/auth/dto/validate-password-reset-token.entity';
|
||||
import { ValidatePasswordResetTokenInput } from 'src/engine/core-modules/auth/dto/validate-password-reset-token.input';
|
||||
import { AuthGraphqlApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-graphql-api-exception.filter';
|
||||
import { CaptchaGuard } from 'src/engine/core-modules/captcha/captcha.guard';
|
||||
import { UserService } from 'src/engine/core-modules/user/services/user.service';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
|
||||
import { CaptchaGuard } from 'src/engine/core-modules/captcha/captcha.guard';
|
||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
|
||||
import { ChallengeInput } from './dto/challenge.input';
|
||||
import { ImpersonateInput } from './dto/impersonate.input';
|
||||
@ -111,7 +112,7 @@ export class AuthResolver {
|
||||
}
|
||||
|
||||
@Mutation(() => TransientToken)
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseGuards(WorkspaceAuthGuard, UserAuthGuard)
|
||||
async generateTransientToken(
|
||||
@AuthUser() user: User,
|
||||
): Promise<TransientToken | void> {
|
||||
@ -141,7 +142,7 @@ export class AuthResolver {
|
||||
}
|
||||
|
||||
@Mutation(() => AuthorizeApp)
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseGuards(WorkspaceAuthGuard, UserAuthGuard)
|
||||
async authorizeApp(
|
||||
@Args() authorizeAppInput: AuthorizeAppInput,
|
||||
@AuthUser() user: User,
|
||||
@ -155,7 +156,7 @@ export class AuthResolver {
|
||||
}
|
||||
|
||||
@Mutation(() => AuthTokens)
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseGuards(WorkspaceAuthGuard, UserAuthGuard)
|
||||
async generateJWT(
|
||||
@AuthUser() user: User,
|
||||
@Args() args: GenerateJwtInput,
|
||||
@ -177,7 +178,7 @@ export class AuthResolver {
|
||||
return { tokens: tokens };
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseGuards(WorkspaceAuthGuard, UserAuthGuard)
|
||||
@Mutation(() => Verify)
|
||||
async impersonate(
|
||||
@Args() impersonateInput: ImpersonateInput,
|
||||
@ -186,7 +187,7 @@ export class AuthResolver {
|
||||
return await this.authService.impersonate(impersonateInput.userId, user);
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@Mutation(() => ApiKeyToken)
|
||||
async generateApiKeyToken(
|
||||
@Args() args: ApiKeyTokenInput,
|
||||
|
@ -36,14 +36,14 @@ import {
|
||||
JwtPayload,
|
||||
} from 'src/engine/core-modules/auth/strategies/jwt.auth.strategy';
|
||||
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { EmailService } from 'src/engine/core-modules/email/email.service';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrapper.service';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import {
|
||||
Workspace,
|
||||
WorkspaceActivationStatus,
|
||||
} from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { EmailService } from 'src/engine/core-modules/email/email.service';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
|
||||
|
@ -11,9 +11,9 @@ import {
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { ApiKeyWorkspaceEntity } from 'src/modules/api-key/standard-objects/api-key.workspace-entity';
|
||||
|
||||
@ -90,7 +90,6 @@ export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||
if (payload.workspaceId) {
|
||||
user = await this.userRepository.findOne({
|
||||
where: { id: payload.sub },
|
||||
relations: ['defaultWorkspace'],
|
||||
});
|
||||
if (!user) {
|
||||
throw new AuthException(
|
||||
|
@ -16,7 +16,8 @@ import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
|
||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
|
||||
@Resolver()
|
||||
export class BillingResolver {
|
||||
@ -37,7 +38,7 @@ export class BillingResolver {
|
||||
}
|
||||
|
||||
@Query(() => SessionEntity)
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseGuards(WorkspaceAuthGuard, UserAuthGuard)
|
||||
async billingPortalSession(
|
||||
@AuthUser() user: User,
|
||||
@Args() { returnUrlPath }: BillingSessionInput,
|
||||
@ -51,7 +52,7 @@ export class BillingResolver {
|
||||
}
|
||||
|
||||
@Mutation(() => SessionEntity)
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseGuards(WorkspaceAuthGuard, UserAuthGuard)
|
||||
async checkoutSession(
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@AuthUser() user: User,
|
||||
@ -79,7 +80,7 @@ export class BillingResolver {
|
||||
}
|
||||
|
||||
@Mutation(() => UpdateBillingEntity)
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
async updateBillingSubscription(@AuthUser() user: User) {
|
||||
await this.billingSubscriptionService.applyBillingSubscription(user);
|
||||
|
||||
|
@ -7,7 +7,7 @@ import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/
|
||||
import { TIMELINE_CALENDAR_EVENTS_MAX_PAGE_SIZE } from 'src/engine/core-modules/calendar/constants/calendar.constants';
|
||||
import { TimelineCalendarEventsWithTotal } from 'src/engine/core-modules/calendar/dtos/timeline-calendar-events-with-total.dto';
|
||||
import { TimelineCalendarEventService } from 'src/engine/core-modules/calendar/timeline-calendar-event.service';
|
||||
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
|
||||
@ArgsType()
|
||||
class GetTimelineCalendarEventsFromPersonIdArgs {
|
||||
@ -35,7 +35,7 @@ class GetTimelineCalendarEventsFromCompanyIdArgs {
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@Resolver(() => TimelineCalendarEventsWithTotal)
|
||||
export class TimelineCalendarEventResolver {
|
||||
constructor(
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { FileUploadService } from 'src/engine/core-modules/file/file-upload/services/file-upload.service';
|
||||
|
||||
import { FileUploadResolver } from './file-upload.resolver';
|
||||
@ -15,6 +16,10 @@ describe('FileUploadResolver', () => {
|
||||
provide: FileUploadService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: EnvironmentService,
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
|
@ -9,10 +9,10 @@ import { FileUploadService } from 'src/engine/core-modules/file/file-upload/serv
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { DemoEnvGuard } from 'src/engine/guards/demo.env.guard';
|
||||
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
||||
|
||||
@UseGuards(JwtAuthGuard, DemoEnvGuard)
|
||||
@UseGuards(WorkspaceAuthGuard, DemoEnvGuard)
|
||||
@Resolver()
|
||||
export class FileUploadResolver {
|
||||
constructor(private readonly fileUploadService: FileUploadService) {}
|
||||
|
@ -10,7 +10,8 @@ import { GetMessagesService } from 'src/engine/core-modules/messaging/services/g
|
||||
import { UserService } from 'src/engine/core-modules/user/services/user.service';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
|
||||
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
|
||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
|
||||
@ArgsType()
|
||||
class GetTimelineThreadsFromPersonIdArgs {
|
||||
@ -38,7 +39,7 @@ class GetTimelineThreadsFromCompanyIdArgs {
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseGuards(WorkspaceAuthGuard, UserAuthGuard)
|
||||
@Resolver(() => TimelineThreadsWithTotal)
|
||||
export class TimelineMessagingResolver {
|
||||
constructor(
|
||||
|
@ -7,9 +7,10 @@ import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
|
||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseGuards(WorkspaceAuthGuard, UserAuthGuard)
|
||||
@Resolver()
|
||||
export class OnboardingResolver {
|
||||
constructor(private readonly onboardingService: OnboardingService) {}
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { UseGuards } from '@nestjs/common';
|
||||
import { Resolver, Mutation, Query } from '@nestjs/graphql';
|
||||
import { Mutation, Query, Resolver } from '@nestjs/graphql';
|
||||
|
||||
import { PostgresCredentialsDTO } from 'src/engine/core-modules/postgres-credentials/dtos/postgres-credentials.dto';
|
||||
import { PostgresCredentialsService } from 'src/engine/core-modules/postgres-credentials/postgres-credentials.service';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
|
||||
@Resolver(() => PostgresCredentialsDTO)
|
||||
export class PostgresCredentialsResolver {
|
||||
@ -13,19 +13,19 @@ export class PostgresCredentialsResolver {
|
||||
private readonly postgresCredentialsService: PostgresCredentialsService,
|
||||
) {}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@Mutation(() => PostgresCredentialsDTO)
|
||||
async enablePostgresProxy(@AuthWorkspace() { id: workspaceId }: Workspace) {
|
||||
return this.postgresCredentialsService.enablePostgresProxy(workspaceId);
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@Mutation(() => PostgresCredentialsDTO)
|
||||
async disablePostgresProxy(@AuthWorkspace() { id: workspaceId }: Workspace) {
|
||||
return this.postgresCredentialsService.disablePostgresProxy(workspaceId);
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@Query(() => PostgresCredentialsDTO, { nullable: true })
|
||||
async getPostgresCredentials(
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
|
@ -4,15 +4,15 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
|
||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { WorkspaceInviteHashValidInput } from 'src/engine/core-modules/auth/dto/workspace-invite-hash.input';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@Resolver(() => UserWorkspace)
|
||||
export class UserWorkspaceResolver {
|
||||
constructor(
|
||||
|
@ -1,11 +1,11 @@
|
||||
import {
|
||||
AutoResolverOpts,
|
||||
ReadResolverOpts,
|
||||
PagingStrategies,
|
||||
ReadResolverOpts,
|
||||
} from '@ptc-org/nestjs-query-graphql';
|
||||
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
|
||||
export const userAutoResolverOpts: AutoResolverOpts<
|
||||
any,
|
||||
@ -33,6 +33,6 @@ export const userAutoResolverOpts: AutoResolverOpts<
|
||||
one: { disabled: true },
|
||||
},
|
||||
delete: { many: { disabled: true }, one: { disabled: true } },
|
||||
guards: [JwtAuthGuard],
|
||||
guards: [WorkspaceAuthGuard],
|
||||
},
|
||||
];
|
||||
|
@ -16,9 +16,10 @@ import { GraphQLJSONObject } from 'graphql-type-json';
|
||||
import { FileUpload, GraphQLUpload } from 'graphql-upload';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
|
||||
import { SupportDriver } from 'src/engine/core-modules/environment/interfaces/support.interface';
|
||||
import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
|
||||
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { FileUploadService } from 'src/engine/core-modules/file/file-upload/services/file-upload.service';
|
||||
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
||||
import { OnboardingStatus } from 'src/engine/core-modules/onboarding/enums/onboarding-status.enum';
|
||||
@ -31,8 +32,7 @@ import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { DemoEnvGuard } from 'src/engine/guards/demo.env.guard';
|
||||
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
||||
|
||||
const getHMACKey = (email?: string, key?: string | null) => {
|
||||
@ -43,7 +43,7 @@ const getHMACKey = (email?: string, key?: string | null) => {
|
||||
return hmac.update(email).digest('hex');
|
||||
};
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@Resolver(() => User)
|
||||
export class UserResolver {
|
||||
constructor(
|
||||
|
@ -7,11 +7,12 @@ import { WorkflowRunDTO } from 'src/engine/core-modules/workflow/dtos/workflow-r
|
||||
import { WorkflowTriggerGraphqlApiExceptionFilter } from 'src/engine/core-modules/workflow/filters/workflow-trigger-graphql-api-exception.filter';
|
||||
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
|
||||
import { AuthWorkspaceMemberId } from 'src/engine/decorators/auth/auth-workspace-member-id.decorator';
|
||||
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
|
||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { WorkflowTriggerWorkspaceService } from 'src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service';
|
||||
|
||||
@Resolver()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseGuards(WorkspaceAuthGuard, UserAuthGuard)
|
||||
@UseFilters(WorkflowTriggerGraphqlApiExceptionFilter)
|
||||
export class WorkflowTriggerResolver {
|
||||
constructor(
|
||||
|
@ -9,6 +9,8 @@ import { SendInviteLinkEmail } from 'twenty-emails';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service';
|
||||
import { EmailService } from 'src/engine/core-modules/email/email.service';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
|
||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
|
||||
@ -19,8 +21,6 @@ import {
|
||||
Workspace,
|
||||
WorkspaceActivationStatus,
|
||||
} from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { EmailService } from 'src/engine/core-modules/email/email.service';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { WorkspaceManagerService } from 'src/engine/workspace-manager/workspace-manager.service';
|
||||
|
||||
// eslint-disable-next-line @nx/workspace-inject-workspace-repository
|
||||
@ -48,7 +48,7 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
||||
}
|
||||
|
||||
const existingWorkspace = await this.workspaceRepository.findOneBy({
|
||||
id: user.defaultWorkspace.id,
|
||||
id: user.defaultWorkspaceId,
|
||||
});
|
||||
|
||||
if (!existingWorkspace) {
|
||||
@ -69,21 +69,21 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
||||
throw new Error('Worspace is not pending creation');
|
||||
}
|
||||
|
||||
await this.workspaceRepository.update(user.defaultWorkspace.id, {
|
||||
await this.workspaceRepository.update(user.defaultWorkspaceId, {
|
||||
activationStatus: WorkspaceActivationStatus.ONGOING_CREATION,
|
||||
});
|
||||
|
||||
await this.workspaceManagerService.init(user.defaultWorkspace.id);
|
||||
await this.workspaceManagerService.init(user.defaultWorkspaceId);
|
||||
await this.userWorkspaceService.createWorkspaceMember(
|
||||
user.defaultWorkspace.id,
|
||||
user.defaultWorkspaceId,
|
||||
user,
|
||||
);
|
||||
await this.workspaceRepository.update(user.defaultWorkspace.id, {
|
||||
await this.workspaceRepository.update(user.defaultWorkspaceId, {
|
||||
displayName: data.displayName,
|
||||
activationStatus: WorkspaceActivationStatus.ACTIVE,
|
||||
});
|
||||
|
||||
return user.defaultWorkspace;
|
||||
return existingWorkspace;
|
||||
}
|
||||
|
||||
async softDeleteWorkspace(id: string) {
|
||||
|
@ -4,8 +4,8 @@ import {
|
||||
ReadResolverOpts,
|
||||
} from '@ptc-org/nestjs-query-graphql';
|
||||
|
||||
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
|
||||
import { UpdateWorkspaceInput } from 'src/engine/core-modules/workspace/dtos/update-workspace-input';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
|
||||
import { Workspace } from './workspace.entity';
|
||||
|
||||
@ -36,6 +36,6 @@ export const workspaceAutoResolverOpts: AutoResolverOpts<
|
||||
many: { disabled: true },
|
||||
},
|
||||
delete: { many: { disabled: true }, one: { disabled: true } },
|
||||
guards: [JwtAuthGuard],
|
||||
guards: [WorkspaceAuthGuard],
|
||||
},
|
||||
];
|
||||
|
@ -25,7 +25,8 @@ import { UpdateWorkspaceInput } from 'src/engine/core-modules/workspace/dtos/upd
|
||||
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { DemoEnvGuard } from 'src/engine/guards/demo.env.guard';
|
||||
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
|
||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { assert } from 'src/utils/assert';
|
||||
import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
||||
|
||||
@ -33,7 +34,7 @@ import { Workspace } from './workspace.entity';
|
||||
|
||||
import { WorkspaceService } from './services/workspace.service';
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@Resolver(() => Workspace)
|
||||
export class WorkspaceResolver {
|
||||
constructor(
|
||||
@ -54,7 +55,7 @@ export class WorkspaceResolver {
|
||||
}
|
||||
|
||||
@Mutation(() => Workspace)
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseGuards(UserAuthGuard)
|
||||
async activateWorkspace(
|
||||
@Args('data') data: ActivateWorkspaceInput,
|
||||
@AuthUser() user: User,
|
||||
@ -139,6 +140,7 @@ export class WorkspaceResolver {
|
||||
}
|
||||
|
||||
@Mutation(() => SendInviteLink)
|
||||
@UseGuards(UserAuthGuard)
|
||||
async sendInviteLink(
|
||||
@Args() sendInviteLinkInput: SendInviteLinkInput,
|
||||
@AuthUser() user: User,
|
||||
|
@ -1,7 +1,11 @@
|
||||
import DataLoader from 'dataloader';
|
||||
|
||||
import { RelationMetadataLoaderPayload } from 'src/engine/dataloaders/dataloader.service';
|
||||
import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||
|
||||
export interface IDataloaders {
|
||||
relationMetadataLoader: DataLoader<string, RelationMetadataEntity>;
|
||||
relationMetadataLoader: DataLoader<
|
||||
RelationMetadataLoaderPayload,
|
||||
RelationMetadataEntity
|
||||
>;
|
||||
}
|
||||
|
@ -2,10 +2,20 @@ import { Injectable } from '@nestjs/common';
|
||||
|
||||
import DataLoader from 'dataloader';
|
||||
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
|
||||
import { IDataloaders } from 'src/engine/dataloaders/dataloader.interface';
|
||||
import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||
import { RelationMetadataService } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.service';
|
||||
|
||||
export type RelationMetadataLoaderPayload = {
|
||||
workspaceId: string;
|
||||
fieldMetadata: Pick<
|
||||
FieldMetadataInterface,
|
||||
'type' | 'id' | 'objectMetadataId'
|
||||
>;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class DataloaderService {
|
||||
constructor(
|
||||
@ -14,12 +24,18 @@ export class DataloaderService {
|
||||
|
||||
createLoaders(): IDataloaders {
|
||||
const relationMetadataLoader = new DataLoader<
|
||||
string,
|
||||
RelationMetadataLoaderPayload,
|
||||
RelationMetadataEntity
|
||||
>(async (fieldMetadataIds: string[]) => {
|
||||
>(async (dataLoaderParams: RelationMetadataLoaderPayload[]) => {
|
||||
const workspaceId = dataLoaderParams[0].workspaceId;
|
||||
const fieldMetadataItems = dataLoaderParams.map(
|
||||
(dataLoaderParam) => dataLoaderParam.fieldMetadata,
|
||||
);
|
||||
|
||||
const relationsMetadataCollection =
|
||||
await this.relationMetadataService.findManyRelationMetadataByFieldMetadataIds(
|
||||
fieldMetadataIds,
|
||||
fieldMetadataItems,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
return relationsMetadataCollection;
|
||||
|
@ -14,10 +14,10 @@ export const AuthUser = createParamDecorator(
|
||||
(options: DecoratorOptions | undefined, ctx: ExecutionContext) => {
|
||||
const request = getRequest(ctx);
|
||||
|
||||
if (!options?.allowUndefined && (!request.user || !request.user.user)) {
|
||||
if (!options?.allowUndefined && !request.user) {
|
||||
throw new ForbiddenException("You're not authorized to do this");
|
||||
}
|
||||
|
||||
return request.user ? request.user.user : undefined;
|
||||
return request.user;
|
||||
},
|
||||
);
|
||||
|
@ -6,6 +6,6 @@ export const AuthWorkspace = createParamDecorator(
|
||||
(data: unknown, ctx: ExecutionContext) => {
|
||||
const request = getRequest(ctx);
|
||||
|
||||
return request.user ? request.user.workspace : undefined;
|
||||
return request.workspace;
|
||||
},
|
||||
);
|
||||
|
@ -1,27 +1,27 @@
|
||||
import {
|
||||
Injectable,
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { GqlExecutionContext } from '@nestjs/graphql';
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { getRequest } from 'src/utils/extract-request';
|
||||
|
||||
@Injectable()
|
||||
export class DemoEnvGuard extends AuthGuard(['jwt']) {
|
||||
constructor(private readonly environmentService: EnvironmentService) {
|
||||
super();
|
||||
}
|
||||
export class DemoEnvGuard implements CanActivate {
|
||||
constructor(private readonly environmentService: EnvironmentService) {}
|
||||
|
||||
getRequest(context: ExecutionContext) {
|
||||
return getRequest(context);
|
||||
}
|
||||
canActivate(
|
||||
context: ExecutionContext,
|
||||
): boolean | Promise<boolean> | Observable<boolean> {
|
||||
const ctx = GqlExecutionContext.create(context);
|
||||
const request = ctx.getContext().req;
|
||||
|
||||
// TODO: input should be typed
|
||||
handleRequest(err: any, user: any) {
|
||||
const demoWorkspaceIds = this.environmentService.get('DEMO_WORKSPACE_IDS');
|
||||
const currentUserWorkspaceId = user?.workspace?.id;
|
||||
const currentUserWorkspaceId = request.workspace?.id;
|
||||
|
||||
if (!currentUserWorkspaceId) {
|
||||
throw new UnauthorizedException('Unauthorized for not logged in user');
|
||||
@ -31,6 +31,6 @@ export class DemoEnvGuard extends AuthGuard(['jwt']) {
|
||||
throw new UnauthorizedException('Unauthorized for demo workspace');
|
||||
}
|
||||
|
||||
return user;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
35
packages/twenty-server/src/engine/guards/jwt-auth.guard.ts
Normal file
35
packages/twenty-server/src/engine/guards/jwt-auth.guard.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
||||
|
||||
import { TokenService } from 'src/engine/core-modules/auth/services/token.service';
|
||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||
|
||||
@Injectable()
|
||||
export class JwtAuthGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly tokenService: TokenService,
|
||||
private readonly workspaceStorageCacheService: WorkspaceCacheStorageService,
|
||||
) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
|
||||
try {
|
||||
const data = await this.tokenService.validateToken(request);
|
||||
const metadataVersion =
|
||||
await this.workspaceStorageCacheService.getMetadataVersion(
|
||||
data.workspace.id,
|
||||
);
|
||||
|
||||
request.user = data.user;
|
||||
request.apiKey = data.apiKey;
|
||||
request.workspace = data.workspace;
|
||||
request.workspaceId = data.workspace.id;
|
||||
request.workspaceMetadataVersion = metadataVersion;
|
||||
request.workspaceMemberId = data.workspaceMemberId;
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
import {
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
|
||||
import { JsonWebTokenError } from 'jsonwebtoken';
|
||||
|
||||
import { assert } from 'src/utils/assert';
|
||||
import { getRequest } from 'src/utils/extract-request';
|
||||
|
||||
@Injectable()
|
||||
export class JwtAuthGuard extends AuthGuard(['jwt']) {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
getRequest(context: ExecutionContext) {
|
||||
return getRequest(context);
|
||||
}
|
||||
|
||||
handleRequest(err: any, user: any, info: any) {
|
||||
assert(user, '', UnauthorizedException);
|
||||
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (info && info instanceof Error) {
|
||||
if (info instanceof JsonWebTokenError) {
|
||||
info = String(info);
|
||||
}
|
||||
|
||||
throw new UnauthorizedException(info);
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
import { ExecutionContext, Injectable } from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
|
||||
import { getRequest } from 'src/utils/extract-request';
|
||||
|
||||
@Injectable()
|
||||
export class OptionalJwtAuthGuard extends AuthGuard(['jwt']) {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
getRequest(context: ExecutionContext) {
|
||||
const request = getRequest(context);
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
handleRequest(err, user, info) {
|
||||
if (err || info) return null;
|
||||
|
||||
return user;
|
||||
}
|
||||
}
|
15
packages/twenty-server/src/engine/guards/user-auth.guard.ts
Normal file
15
packages/twenty-server/src/engine/guards/user-auth.guard.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { CanActivate, ExecutionContext } from '@nestjs/common';
|
||||
import { GqlExecutionContext } from '@nestjs/graphql';
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
export class UserAuthGuard implements CanActivate {
|
||||
canActivate(
|
||||
context: ExecutionContext,
|
||||
): boolean | Promise<boolean> | Observable<boolean> {
|
||||
const ctx = GqlExecutionContext.create(context);
|
||||
const request = ctx.getContext().req;
|
||||
|
||||
return request.user !== undefined;
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
import { CanActivate, ExecutionContext } from '@nestjs/common';
|
||||
import { GqlExecutionContext } from '@nestjs/graphql';
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
export class WorkspaceAuthGuard implements CanActivate {
|
||||
canActivate(
|
||||
context: ExecutionContext,
|
||||
): boolean | Promise<boolean> | Observable<boolean> {
|
||||
const ctx = GqlExecutionContext.create(context);
|
||||
const request = ctx.getContext().req;
|
||||
|
||||
return request.workspace !== undefined;
|
||||
}
|
||||
}
|
@ -44,7 +44,7 @@ registerEnumType(FieldMetadataType, {
|
||||
@ObjectType('field')
|
||||
@Authorize({
|
||||
authorize: (context: any) => ({
|
||||
workspaceId: { eq: context?.req?.user?.workspace?.id },
|
||||
workspaceId: { eq: context?.req?.workspace?.id },
|
||||
}),
|
||||
})
|
||||
@QueryOptions({
|
||||
@ -132,6 +132,8 @@ export class FieldMetadataDTO<
|
||||
@HideField()
|
||||
workspaceId: string;
|
||||
|
||||
objectMetadataId: string;
|
||||
|
||||
@IsDateString()
|
||||
@Field()
|
||||
createdAt: Date;
|
||||
|
@ -9,13 +9,14 @@ import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
|
||||
|
||||
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
||||
import { ActorModule } from 'src/engine/core-modules/actor/actor.module';
|
||||
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||
import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto';
|
||||
import { FieldMetadataResolver } from 'src/engine/metadata-modules/field-metadata/field-metadata.resolver';
|
||||
import { FieldMetadataGraphqlApiExceptionInterceptor } from 'src/engine/metadata-modules/field-metadata/interceptors/field-metadata-graphql-api-exception.interceptor';
|
||||
import { IsFieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/validators/is-field-metadata-default-value.validator';
|
||||
import { IsFieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/validators/is-field-metadata-options.validator';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
||||
import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module';
|
||||
import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module';
|
||||
@ -32,7 +33,10 @@ import { UpdateFieldInput } from './dtos/update-field.input';
|
||||
imports: [
|
||||
NestjsQueryGraphQLModule.forFeature({
|
||||
imports: [
|
||||
NestjsQueryTypeOrmModule.forFeature([FieldMetadataEntity], 'metadata'),
|
||||
NestjsQueryTypeOrmModule.forFeature(
|
||||
[FieldMetadataEntity, ObjectMetadataEntity],
|
||||
'metadata',
|
||||
),
|
||||
WorkspaceMigrationModule,
|
||||
WorkspaceStatusModule,
|
||||
WorkspaceMigrationRunnerModule,
|
||||
@ -65,7 +69,7 @@ import { UpdateFieldInput } from './dtos/update-field.input';
|
||||
many: { disabled: true },
|
||||
},
|
||||
delete: { disabled: true },
|
||||
guards: [JwtAuthGuard],
|
||||
guards: [WorkspaceAuthGuard],
|
||||
interceptors: [FieldMetadataGraphqlApiExceptionInterceptor],
|
||||
},
|
||||
],
|
||||
|
@ -15,7 +15,7 @@ import {
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { IDataloaders } from 'src/engine/dataloaders/dataloader.interface';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { CreateOneFieldMetadataInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
|
||||
import { DeleteOneFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/delete-field.input';
|
||||
import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto';
|
||||
@ -25,7 +25,7 @@ import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/fi
|
||||
import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/field-metadata.service';
|
||||
import { fieldMetadataGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/field-metadata/utils/field-metadata-graphql-api-exception-handler.util';
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@Resolver(() => FieldMetadataDTO)
|
||||
export class FieldMetadataResolver {
|
||||
constructor(private readonly fieldMetadataService: FieldMetadataService) {}
|
||||
@ -103,6 +103,7 @@ export class FieldMetadataResolver {
|
||||
|
||||
@ResolveField(() => RelationDefinitionDTO, { nullable: true })
|
||||
async relationDefinition(
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@Parent() fieldMetadata: FieldMetadataDTO,
|
||||
@Context() context: { loaders: IDataloaders },
|
||||
): Promise<RelationDefinitionDTO | null | undefined> {
|
||||
@ -112,7 +113,10 @@ export class FieldMetadataResolver {
|
||||
|
||||
try {
|
||||
const relationMetadataItem =
|
||||
await context.loaders.relationMetadataLoader.load(fieldMetadata.id);
|
||||
await context.loaders.relationMetadataLoader.load({
|
||||
fieldMetadata,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
|
||||
return await this.fieldMetadataService.getRelationDefinitionFromRelationMetadata(
|
||||
fieldMetadata,
|
||||
|
@ -4,7 +4,7 @@ import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
||||
import isEmpty from 'lodash.isempty';
|
||||
import { DataSource, FindOneOptions, Repository } from 'typeorm';
|
||||
import { v4 as uuidV4 } from 'uuid';
|
||||
import { v4 as uuidV4, v4 } from 'uuid';
|
||||
|
||||
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
@ -72,6 +72,8 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
private readonly metadataDataSource: DataSource,
|
||||
@InjectRepository(FieldMetadataEntity, 'metadata')
|
||||
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
||||
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
private readonly objectMetadataService: ObjectMetadataService,
|
||||
private readonly workspaceMigrationFactory: WorkspaceMigrationFactory,
|
||||
private readonly workspaceMigrationService: WorkspaceMigrationService,
|
||||
@ -87,6 +89,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
override async createOne(
|
||||
fieldMetadataInput: CreateFieldInput,
|
||||
): Promise<FieldMetadataEntity> {
|
||||
console.time('createOne');
|
||||
const queryRunner = this.metadataDataSource.createQueryRunner();
|
||||
|
||||
await queryRunner.connect();
|
||||
@ -97,20 +100,23 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
queryRunner.manager.getRepository<FieldMetadataEntity>(
|
||||
FieldMetadataEntity,
|
||||
);
|
||||
const objectMetadata =
|
||||
await this.objectMetadataService.findOneWithinWorkspace(
|
||||
fieldMetadataInput.workspaceId,
|
||||
{
|
||||
where: {
|
||||
id: fieldMetadataInput.objectMetadataId,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
console.time('createOne query');
|
||||
const [objectMetadata] = await this.objectMetadataRepository.find({
|
||||
where: {
|
||||
id: fieldMetadataInput.objectMetadataId,
|
||||
workspaceId: fieldMetadataInput.workspaceId,
|
||||
},
|
||||
relations: ['fields'],
|
||||
order: {},
|
||||
});
|
||||
|
||||
console.timeEnd('createOne query');
|
||||
|
||||
if (!objectMetadata) {
|
||||
throw new FieldMetadataException(
|
||||
'Object metadata does not exist',
|
||||
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
|
||||
FieldMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
@ -155,22 +161,11 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
objectMetadata,
|
||||
);
|
||||
|
||||
const fieldAlreadyExists = await fieldMetadataRepository.findOne({
|
||||
where: {
|
||||
name: fieldMetadataInput.name,
|
||||
objectMetadataId: fieldMetadataInput.objectMetadataId,
|
||||
workspaceId: fieldMetadataInput.workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
if (fieldAlreadyExists) {
|
||||
throw new FieldMetadataException(
|
||||
'Field already exists',
|
||||
FieldMetadataExceptionCode.FIELD_ALREADY_EXISTS,
|
||||
);
|
||||
}
|
||||
|
||||
console.time('createOne save');
|
||||
const createdFieldMetadata = await fieldMetadataRepository.save({
|
||||
id: v4(),
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
...fieldMetadataInput,
|
||||
isNullable: generateNullable(
|
||||
fieldMetadataInput.type,
|
||||
@ -190,7 +185,10 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
isCustom: true,
|
||||
});
|
||||
|
||||
console.timeEnd('createOne save');
|
||||
|
||||
if (!fieldMetadataInput.isRemoteCreation) {
|
||||
console.time('createOne migration create');
|
||||
await this.workspaceMigrationService.createCustomMigration(
|
||||
generateMigrationName(`create-${createdFieldMetadata.name}`),
|
||||
fieldMetadataInput.workspaceId,
|
||||
@ -206,11 +204,16 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
],
|
||||
);
|
||||
|
||||
console.timeEnd('createOne migration create');
|
||||
|
||||
console.time('createOne migration run');
|
||||
await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations(
|
||||
fieldMetadataInput.workspaceId,
|
||||
);
|
||||
console.timeEnd('createOne migration run');
|
||||
}
|
||||
|
||||
console.time('createOne workspace viewField');
|
||||
// TODO: Move viewField creation to a cdc scheduler
|
||||
const dataSourceMetadata =
|
||||
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
||||
@ -270,8 +273,11 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
);
|
||||
}
|
||||
}
|
||||
console.timeEnd('createOne workspace viewField');
|
||||
|
||||
console.time('createOne internal commit');
|
||||
await workspaceQueryRunner.commitTransaction();
|
||||
console.timeEnd('createOne internal commit');
|
||||
} catch (error) {
|
||||
await workspaceQueryRunner.rollbackTransaction();
|
||||
throw error;
|
||||
@ -279,7 +285,9 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
await workspaceQueryRunner.release();
|
||||
}
|
||||
|
||||
console.time('createOne commit');
|
||||
await queryRunner.commitTransaction();
|
||||
console.timeEnd('createOne commit');
|
||||
|
||||
return createdFieldMetadata;
|
||||
} catch (error) {
|
||||
@ -287,9 +295,12 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
throw error;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
console.time('createOne increment');
|
||||
await this.workspaceMetadataVersionService.incrementMetadataVersion(
|
||||
fieldMetadataInput.workspaceId,
|
||||
);
|
||||
console.timeEnd('createOne increment');
|
||||
console.timeEnd('createOne');
|
||||
}
|
||||
}
|
||||
|
||||
@ -308,7 +319,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
FieldMetadataEntity,
|
||||
);
|
||||
|
||||
const existingFieldMetadata = await fieldMetadataRepository.findOne({
|
||||
const [existingFieldMetadata] = await fieldMetadataRepository.find({
|
||||
where: {
|
||||
id,
|
||||
workspaceId: fieldMetadataInput.workspaceId,
|
||||
@ -322,15 +333,14 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
);
|
||||
}
|
||||
|
||||
const objectMetadata =
|
||||
await this.objectMetadataService.findOneWithinWorkspace(
|
||||
fieldMetadataInput.workspaceId,
|
||||
{
|
||||
where: {
|
||||
id: existingFieldMetadata?.objectMetadataId,
|
||||
},
|
||||
},
|
||||
);
|
||||
const [objectMetadata] = await this.objectMetadataRepository.find({
|
||||
where: {
|
||||
id: existingFieldMetadata.objectMetadataId,
|
||||
workspaceId: fieldMetadataInput.workspaceId,
|
||||
},
|
||||
relations: ['fields'],
|
||||
order: {},
|
||||
});
|
||||
|
||||
if (!objectMetadata) {
|
||||
throw new FieldMetadataException(
|
||||
@ -475,7 +485,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
FieldMetadataEntity,
|
||||
);
|
||||
|
||||
const fieldMetadata = await fieldMetadataRepository.findOne({
|
||||
const [fieldMetadata] = await fieldMetadataRepository.find({
|
||||
where: {
|
||||
id: input.id,
|
||||
workspaceId: workspaceId,
|
||||
@ -489,12 +499,13 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
);
|
||||
}
|
||||
|
||||
const objectMetadata =
|
||||
await this.objectMetadataService.findOneWithinWorkspace(workspaceId, {
|
||||
where: {
|
||||
id: fieldMetadata?.objectMetadataId,
|
||||
},
|
||||
});
|
||||
const [objectMetadata] = await this.objectMetadataRepository.find({
|
||||
where: {
|
||||
id: fieldMetadata.objectMetadataId,
|
||||
},
|
||||
relations: ['fields'],
|
||||
order: {},
|
||||
});
|
||||
|
||||
if (!objectMetadata) {
|
||||
throw new FieldMetadataException(
|
||||
@ -583,7 +594,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
id: string,
|
||||
options?: FindOneOptions<FieldMetadataEntity>,
|
||||
) {
|
||||
const fieldMetadata = await this.fieldMetadataRepository.findOne({
|
||||
const [fieldMetadata] = await this.fieldMetadataRepository.find({
|
||||
...options,
|
||||
where: {
|
||||
...options?.where,
|
||||
@ -605,13 +616,15 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
workspaceId: string,
|
||||
options: FindOneOptions<FieldMetadataEntity>,
|
||||
) {
|
||||
return this.fieldMetadataRepository.findOne({
|
||||
const [fieldMetadata] = await this.fieldMetadataRepository.find({
|
||||
...options,
|
||||
where: {
|
||||
...options.where,
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
return fieldMetadata;
|
||||
}
|
||||
|
||||
private buildUpdatableStandardFieldInput(
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user