@ -28,8 +28,8 @@ const documents = {
|
|||||||
"\n mutation CreateOneFieldMetadataItem($input: CreateOneFieldMetadataInput!) {\n createOneField(input: $input) {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n settings\n defaultValue\n options\n }\n }\n": types.CreateOneFieldMetadataItemDocument,
|
"\n mutation CreateOneFieldMetadataItem($input: CreateOneFieldMetadataInput!) {\n createOneField(input: $input) {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n settings\n defaultValue\n options\n }\n }\n": types.CreateOneFieldMetadataItemDocument,
|
||||||
"\n mutation CreateOneRelationMetadataItem(\n $input: CreateOneRelationMetadataInput!\n ) {\n createOneRelationMetadata(input: $input) {\n id\n relationType\n fromObjectMetadataId\n toObjectMetadataId\n fromFieldMetadataId\n toFieldMetadataId\n createdAt\n updatedAt\n }\n }\n": types.CreateOneRelationMetadataItemDocument,
|
"\n mutation CreateOneRelationMetadataItem(\n $input: CreateOneRelationMetadataInput!\n ) {\n createOneRelationMetadata(input: $input) {\n id\n relationType\n fromObjectMetadataId\n toObjectMetadataId\n fromFieldMetadataId\n toFieldMetadataId\n createdAt\n updatedAt\n }\n }\n": types.CreateOneRelationMetadataItemDocument,
|
||||||
"\n mutation UpdateOneFieldMetadataItem(\n $idToUpdate: UUID!\n $updatePayload: UpdateFieldInput!\n ) {\n updateOneField(input: { id: $idToUpdate, update: $updatePayload }) {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n settings\n isLabelSyncedWithName\n }\n }\n": types.UpdateOneFieldMetadataItemDocument,
|
"\n mutation UpdateOneFieldMetadataItem(\n $idToUpdate: UUID!\n $updatePayload: UpdateFieldInput!\n ) {\n updateOneField(input: { id: $idToUpdate, update: $updatePayload }) {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n settings\n isLabelSyncedWithName\n }\n }\n": types.UpdateOneFieldMetadataItemDocument,
|
||||||
"\n mutation UpdateOneObjectMetadataItem(\n $idToUpdate: UUID!\n $updatePayload: UpdateObjectPayload!\n ) {\n updateOneObject(input: { id: $idToUpdate, update: $updatePayload }) {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n }\n }\n": types.UpdateOneObjectMetadataItemDocument,
|
"\n mutation UpdateOneObjectMetadataItem(\n $idToUpdate: UUID!\n $updatePayload: UpdateObjectPayload!\n ) {\n updateOneObject(input: { id: $idToUpdate, update: $updatePayload }) {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n isSearchable\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n }\n }\n": types.UpdateOneObjectMetadataItemDocument,
|
||||||
"\n mutation DeleteOneObjectMetadataItem($idToDelete: UUID!) {\n deleteOneObject(input: { id: $idToDelete }) {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n }\n }\n": types.DeleteOneObjectMetadataItemDocument,
|
"\n mutation DeleteOneObjectMetadataItem($idToDelete: UUID!) {\n deleteOneObject(input: { id: $idToDelete }) {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n isSearchable\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n }\n }\n": types.DeleteOneObjectMetadataItemDocument,
|
||||||
"\n mutation DeleteOneFieldMetadataItem($idToDelete: UUID!) {\n deleteOneField(input: { id: $idToDelete }) {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n settings\n }\n }\n": types.DeleteOneFieldMetadataItemDocument,
|
"\n mutation DeleteOneFieldMetadataItem($idToDelete: UUID!) {\n deleteOneField(input: { id: $idToDelete }) {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n settings\n }\n }\n": types.DeleteOneFieldMetadataItemDocument,
|
||||||
"\n mutation DeleteOneRelationMetadataItem($idToDelete: UUID!) {\n deleteOneRelation(input: { id: $idToDelete }) {\n id\n }\n }\n": types.DeleteOneRelationMetadataItemDocument,
|
"\n mutation DeleteOneRelationMetadataItem($idToDelete: UUID!) {\n deleteOneRelation(input: { id: $idToDelete }) {\n id\n }\n }\n": types.DeleteOneRelationMetadataItemDocument,
|
||||||
"\n query ObjectMetadataItems {\n objects(paging: { first: 1000 }) {\n edges {\n node {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isRemote\n isActive\n isSystem\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n shortcut\n isLabelSyncedWithName\n isSearchable\n duplicateCriteria\n indexMetadatas(paging: { first: 100 }) {\n edges {\n node {\n id\n createdAt\n updatedAt\n name\n indexWhereClause\n indexType\n isUnique\n indexFieldMetadatas(paging: { first: 100 }) {\n edges {\n node {\n id\n createdAt\n updatedAt\n order\n fieldMetadataId\n }\n }\n }\n }\n }\n }\n fieldsList {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isSystem\n isNullable\n isUnique\n createdAt\n updatedAt\n defaultValue\n options\n settings\n isLabelSyncedWithName\n relationDefinition {\n relationId\n direction\n sourceObjectMetadata {\n id\n nameSingular\n namePlural\n }\n sourceFieldMetadata {\n id\n name\n }\n targetObjectMetadata {\n id\n nameSingular\n namePlural\n }\n targetFieldMetadata {\n id\n name\n }\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n": types.ObjectMetadataItemsDocument,
|
"\n query ObjectMetadataItems {\n objects(paging: { first: 1000 }) {\n edges {\n node {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isRemote\n isActive\n isSystem\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n shortcut\n isLabelSyncedWithName\n isSearchable\n duplicateCriteria\n indexMetadatas(paging: { first: 100 }) {\n edges {\n node {\n id\n createdAt\n updatedAt\n name\n indexWhereClause\n indexType\n isUnique\n indexFieldMetadatas(paging: { first: 100 }) {\n edges {\n node {\n id\n createdAt\n updatedAt\n order\n fieldMetadataId\n }\n }\n }\n }\n }\n }\n fieldsList {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isSystem\n isNullable\n isUnique\n createdAt\n updatedAt\n defaultValue\n options\n settings\n isLabelSyncedWithName\n relationDefinition {\n relationId\n direction\n sourceObjectMetadata {\n id\n nameSingular\n namePlural\n }\n sourceFieldMetadata {\n id\n name\n }\n targetObjectMetadata {\n id\n nameSingular\n namePlural\n }\n targetFieldMetadata {\n id\n name\n }\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n": types.ObjectMetadataItemsDocument,
|
||||||
@ -122,11 +122,11 @@ export function graphql(source: "\n mutation UpdateOneFieldMetadataItem(\n $
|
|||||||
/**
|
/**
|
||||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||||
*/
|
*/
|
||||||
export function graphql(source: "\n mutation UpdateOneObjectMetadataItem(\n $idToUpdate: UUID!\n $updatePayload: UpdateObjectPayload!\n ) {\n updateOneObject(input: { id: $idToUpdate, update: $updatePayload }) {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n }\n }\n"): (typeof documents)["\n mutation UpdateOneObjectMetadataItem(\n $idToUpdate: UUID!\n $updatePayload: UpdateObjectPayload!\n ) {\n updateOneObject(input: { id: $idToUpdate, update: $updatePayload }) {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n }\n }\n"];
|
export function graphql(source: "\n mutation UpdateOneObjectMetadataItem(\n $idToUpdate: UUID!\n $updatePayload: UpdateObjectPayload!\n ) {\n updateOneObject(input: { id: $idToUpdate, update: $updatePayload }) {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n isSearchable\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n }\n }\n"): (typeof documents)["\n mutation UpdateOneObjectMetadataItem(\n $idToUpdate: UUID!\n $updatePayload: UpdateObjectPayload!\n ) {\n updateOneObject(input: { id: $idToUpdate, update: $updatePayload }) {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n isSearchable\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n }\n }\n"];
|
||||||
/**
|
/**
|
||||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||||
*/
|
*/
|
||||||
export function graphql(source: "\n mutation DeleteOneObjectMetadataItem($idToDelete: UUID!) {\n deleteOneObject(input: { id: $idToDelete }) {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n }\n }\n"): (typeof documents)["\n mutation DeleteOneObjectMetadataItem($idToDelete: UUID!) {\n deleteOneObject(input: { id: $idToDelete }) {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n }\n }\n"];
|
export function graphql(source: "\n mutation DeleteOneObjectMetadataItem($idToDelete: UUID!) {\n deleteOneObject(input: { id: $idToDelete }) {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n isSearchable\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n }\n }\n"): (typeof documents)["\n mutation DeleteOneObjectMetadataItem($idToDelete: UUID!) {\n deleteOneObject(input: { id: $idToDelete }) {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n isSearchable\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n }\n }\n"];
|
||||||
/**
|
/**
|
||||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||||
*/
|
*/
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -501,7 +501,6 @@ export enum FeatureFlagKey {
|
|||||||
IsNewRelationEnabled = 'IsNewRelationEnabled',
|
IsNewRelationEnabled = 'IsNewRelationEnabled',
|
||||||
IsPermissionsEnabled = 'IsPermissionsEnabled',
|
IsPermissionsEnabled = 'IsPermissionsEnabled',
|
||||||
IsPostgreSQLIntegrationEnabled = 'IsPostgreSQLIntegrationEnabled',
|
IsPostgreSQLIntegrationEnabled = 'IsPostgreSQLIntegrationEnabled',
|
||||||
IsRichTextV2Enabled = 'IsRichTextV2Enabled',
|
|
||||||
IsStripeIntegrationEnabled = 'IsStripeIntegrationEnabled',
|
IsStripeIntegrationEnabled = 'IsStripeIntegrationEnabled',
|
||||||
IsUniqueIndexesEnabled = 'IsUniqueIndexesEnabled',
|
IsUniqueIndexesEnabled = 'IsUniqueIndexesEnabled',
|
||||||
IsWorkflowEnabled = 'IsWorkflowEnabled'
|
IsWorkflowEnabled = 'IsWorkflowEnabled'
|
||||||
@ -829,6 +828,7 @@ export type Mutation = {
|
|||||||
sendInvitations: SendInvitationsOutput;
|
sendInvitations: SendInvitationsOutput;
|
||||||
signUp: SignUpOutput;
|
signUp: SignUpOutput;
|
||||||
skipSyncEmailOnboardingStep: OnboardingStepSuccess;
|
skipSyncEmailOnboardingStep: OnboardingStepSuccess;
|
||||||
|
submitFormStep: Scalars['Boolean'];
|
||||||
track: Analytics;
|
track: Analytics;
|
||||||
updateBillingSubscription: BillingUpdateOutput;
|
updateBillingSubscription: BillingUpdateOutput;
|
||||||
updateLabPublicFeatureFlag: FeatureFlag;
|
updateLabPublicFeatureFlag: FeatureFlag;
|
||||||
@ -961,6 +961,7 @@ export type MutationEditSsoIdentityProviderArgs = {
|
|||||||
|
|
||||||
export type MutationEmailPasswordResetLinkArgs = {
|
export type MutationEmailPasswordResetLinkArgs = {
|
||||||
email: Scalars['String'];
|
email: Scalars['String'];
|
||||||
|
workspaceId: Scalars['String'];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -1045,6 +1046,11 @@ export type MutationSignUpArgs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type MutationSubmitFormStepArgs = {
|
||||||
|
input: SubmitFormStepInput;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export type MutationTrackArgs = {
|
export type MutationTrackArgs = {
|
||||||
action: Scalars['String'];
|
action: Scalars['String'];
|
||||||
payload: Scalars['JSON'];
|
payload: Scalars['JSON'];
|
||||||
@ -1293,7 +1299,6 @@ export type Query = {
|
|||||||
currentWorkspace: Workspace;
|
currentWorkspace: Workspace;
|
||||||
field: Field;
|
field: Field;
|
||||||
fields: FieldConnection;
|
fields: FieldConnection;
|
||||||
findAvailableWorkspacesByEmail: Array<AvailableWorkspaceOutput>;
|
|
||||||
findManyServerlessFunctions: Array<ServerlessFunction>;
|
findManyServerlessFunctions: Array<ServerlessFunction>;
|
||||||
findOneServerlessFunction: ServerlessFunction;
|
findOneServerlessFunction: ServerlessFunction;
|
||||||
findWorkspaceFromInviteHash: Workspace;
|
findWorkspaceFromInviteHash: Workspace;
|
||||||
@ -1339,11 +1344,6 @@ export type QueryCheckWorkspaceInviteHashIsValidArgs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export type QueryFindAvailableWorkspacesByEmailArgs = {
|
|
||||||
email: Scalars['String'];
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export type QueryFindOneServerlessFunctionArgs = {
|
export type QueryFindOneServerlessFunctionArgs = {
|
||||||
input: ServerlessFunctionIdInput;
|
input: ServerlessFunctionIdInput;
|
||||||
};
|
};
|
||||||
@ -1665,6 +1665,15 @@ export type SignUpOutput = {
|
|||||||
workspace: WorkspaceUrlsAndId;
|
workspace: WorkspaceUrlsAndId;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type SubmitFormStepInput = {
|
||||||
|
/** Form response in JSON format */
|
||||||
|
response: Scalars['JSON'];
|
||||||
|
/** Workflow version ID */
|
||||||
|
stepId: Scalars['String'];
|
||||||
|
/** Workflow run ID */
|
||||||
|
workflowRunId: Scalars['String'];
|
||||||
|
};
|
||||||
|
|
||||||
export enum SubscriptionInterval {
|
export enum SubscriptionInterval {
|
||||||
Day = 'Day',
|
Day = 'Day',
|
||||||
Month = 'Month',
|
Month = 'Month',
|
||||||
@ -2152,6 +2161,11 @@ export type GetTimelineThreadsFromPersonIdQueryVariables = Exact<{
|
|||||||
|
|
||||||
export type GetTimelineThreadsFromPersonIdQuery = { __typename?: 'Query', getTimelineThreadsFromPersonId: { __typename?: 'TimelineThreadsWithTotal', totalNumberOfThreads: number, timelineThreads: Array<{ __typename?: 'TimelineThread', id: any, read: boolean, visibility: MessageChannelVisibility, lastMessageReceivedAt: string, lastMessageBody: string, subject: string, numberOfMessagesInThread: number, participantCount: number, firstParticipant: { __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }, lastTwoParticipants: Array<{ __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }> } };
|
export type GetTimelineThreadsFromPersonIdQuery = { __typename?: 'Query', getTimelineThreadsFromPersonId: { __typename?: 'TimelineThreadsWithTotal', totalNumberOfThreads: number, timelineThreads: Array<{ __typename?: 'TimelineThread', id: any, read: boolean, visibility: MessageChannelVisibility, lastMessageReceivedAt: string, lastMessageBody: string, subject: string, numberOfMessagesInThread: number, participantCount: number, firstParticipant: { __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }, lastTwoParticipants: Array<{ __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }> } };
|
||||||
|
|
||||||
|
export type EmptyQueryVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
|
export type EmptyQuery = { __typename: 'Query' };
|
||||||
|
|
||||||
export type TrackMutationVariables = Exact<{
|
export type TrackMutationVariables = Exact<{
|
||||||
action: Scalars['String'];
|
action: Scalars['String'];
|
||||||
payload: Scalars['JSON'];
|
payload: Scalars['JSON'];
|
||||||
@ -2193,6 +2207,7 @@ export type AuthorizeAppMutation = { __typename?: 'Mutation', authorizeApp: { __
|
|||||||
|
|
||||||
export type EmailPasswordResetLinkMutationVariables = Exact<{
|
export type EmailPasswordResetLinkMutationVariables = Exact<{
|
||||||
email: Scalars['String'];
|
email: Scalars['String'];
|
||||||
|
workspaceId: Scalars['String'];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
@ -2984,6 +2999,38 @@ export function useGetTimelineThreadsFromPersonIdLazyQuery(baseOptions?: Apollo.
|
|||||||
export type GetTimelineThreadsFromPersonIdQueryHookResult = ReturnType<typeof useGetTimelineThreadsFromPersonIdQuery>;
|
export type GetTimelineThreadsFromPersonIdQueryHookResult = ReturnType<typeof useGetTimelineThreadsFromPersonIdQuery>;
|
||||||
export type GetTimelineThreadsFromPersonIdLazyQueryHookResult = ReturnType<typeof useGetTimelineThreadsFromPersonIdLazyQuery>;
|
export type GetTimelineThreadsFromPersonIdLazyQueryHookResult = ReturnType<typeof useGetTimelineThreadsFromPersonIdLazyQuery>;
|
||||||
export type GetTimelineThreadsFromPersonIdQueryResult = Apollo.QueryResult<GetTimelineThreadsFromPersonIdQuery, GetTimelineThreadsFromPersonIdQueryVariables>;
|
export type GetTimelineThreadsFromPersonIdQueryResult = Apollo.QueryResult<GetTimelineThreadsFromPersonIdQuery, GetTimelineThreadsFromPersonIdQueryVariables>;
|
||||||
|
export const EmptyDocument = gql`
|
||||||
|
query Empty {
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useEmptyQuery__
|
||||||
|
*
|
||||||
|
* To run a query within a React component, call `useEmptyQuery` and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useEmptyQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||||
|
* you can use to render your UI.
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const { data, loading, error } = useEmptyQuery({
|
||||||
|
* variables: {
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useEmptyQuery(baseOptions?: Apollo.QueryHookOptions<EmptyQuery, EmptyQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useQuery<EmptyQuery, EmptyQueryVariables>(EmptyDocument, options);
|
||||||
|
}
|
||||||
|
export function useEmptyLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<EmptyQuery, EmptyQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useLazyQuery<EmptyQuery, EmptyQueryVariables>(EmptyDocument, options);
|
||||||
|
}
|
||||||
|
export type EmptyQueryHookResult = ReturnType<typeof useEmptyQuery>;
|
||||||
|
export type EmptyLazyQueryHookResult = ReturnType<typeof useEmptyLazyQuery>;
|
||||||
|
export type EmptyQueryResult = Apollo.QueryResult<EmptyQuery, EmptyQueryVariables>;
|
||||||
export const TrackDocument = gql`
|
export const TrackDocument = gql`
|
||||||
mutation Track($action: String!, $payload: JSON!) {
|
mutation Track($action: String!, $payload: JSON!) {
|
||||||
track(action: $action, payload: $payload) {
|
track(action: $action, payload: $payload) {
|
||||||
@ -3122,8 +3169,8 @@ export type AuthorizeAppMutationHookResult = ReturnType<typeof useAuthorizeAppMu
|
|||||||
export type AuthorizeAppMutationResult = Apollo.MutationResult<AuthorizeAppMutation>;
|
export type AuthorizeAppMutationResult = Apollo.MutationResult<AuthorizeAppMutation>;
|
||||||
export type AuthorizeAppMutationOptions = Apollo.BaseMutationOptions<AuthorizeAppMutation, AuthorizeAppMutationVariables>;
|
export type AuthorizeAppMutationOptions = Apollo.BaseMutationOptions<AuthorizeAppMutation, AuthorizeAppMutationVariables>;
|
||||||
export const EmailPasswordResetLinkDocument = gql`
|
export const EmailPasswordResetLinkDocument = gql`
|
||||||
mutation EmailPasswordResetLink($email: String!) {
|
mutation EmailPasswordResetLink($email: String!, $workspaceId: String!) {
|
||||||
emailPasswordResetLink(email: $email) {
|
emailPasswordResetLink(email: $email, workspaceId: $workspaceId) {
|
||||||
success
|
success
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3144,6 +3191,7 @@ export type EmailPasswordResetLinkMutationFn = Apollo.MutationFunction<EmailPass
|
|||||||
* const [emailPasswordResetLinkMutation, { data, loading, error }] = useEmailPasswordResetLinkMutation({
|
* const [emailPasswordResetLinkMutation, { data, loading, error }] = useEmailPasswordResetLinkMutation({
|
||||||
* variables: {
|
* variables: {
|
||||||
* email: // value for 'email'
|
* email: // value for 'email'
|
||||||
|
* workspaceId: // value for 'workspaceId'
|
||||||
* },
|
* },
|
||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { gql } from '@apollo/client';
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
export const EMAIL_PASSWORD_RESET_Link = gql`
|
export const EMAIL_PASSWORD_RESET_LINK = gql`
|
||||||
mutation EmailPasswordResetLink($email: String!) {
|
mutation EmailPasswordResetLink($email: String!, $workspaceId: String!) {
|
||||||
emailPasswordResetLink(email: $email) {
|
emailPasswordResetLink(email: $email, workspaceId: $workspaceId) {
|
||||||
success
|
success
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -97,6 +97,7 @@ export const SignInUpWorkspaceScopeFormEffect = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
signInUpStep !== SignInUpStep.Email &&
|
||||||
isDefined(email) &&
|
isDefined(email) &&
|
||||||
workspaceAuthProviders.password &&
|
workspaceAuthProviders.password &&
|
||||||
loadingStatus === LoadingStatus.Done
|
loadingStatus === LoadingStatus.Done
|
||||||
|
|||||||
@ -7,8 +7,12 @@ import { SOURCE_LOCALE } from 'twenty-shared';
|
|||||||
import { useHandleResetPassword } from '@/auth/sign-in-up/hooks/useHandleResetPassword';
|
import { useHandleResetPassword } from '@/auth/sign-in-up/hooks/useHandleResetPassword';
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { useEmailPasswordResetLinkMutation } from '~/generated/graphql';
|
import {
|
||||||
|
PublicWorkspaceDataOutput,
|
||||||
|
useEmailPasswordResetLinkMutation,
|
||||||
|
} from '~/generated/graphql';
|
||||||
import { dynamicActivate } from '~/utils/i18n/dynamicActivate';
|
import { dynamicActivate } from '~/utils/i18n/dynamicActivate';
|
||||||
|
import { workspacePublicDataState } from '@/auth/states/workspacePublicDataState';
|
||||||
|
|
||||||
// Mocks
|
// Mocks
|
||||||
jest.mock('@/ui/feedback/snack-bar-manager/hooks/useSnackBar');
|
jest.mock('@/ui/feedback/snack-bar-manager/hooks/useSnackBar');
|
||||||
@ -19,7 +23,14 @@ dynamicActivate(SOURCE_LOCALE);
|
|||||||
const renderHooks = () => {
|
const renderHooks = () => {
|
||||||
const { result } = renderHook(() => useHandleResetPassword(), {
|
const { result } = renderHook(() => useHandleResetPassword(), {
|
||||||
wrapper: ({ children }) =>
|
wrapper: ({ children }) =>
|
||||||
RecoilRoot({ children: I18nProvider({ i18n, children }) }),
|
RecoilRoot({
|
||||||
|
initializeState: ({ set }) => {
|
||||||
|
set(workspacePublicDataState, {
|
||||||
|
id: 'workspace-id',
|
||||||
|
} as PublicWorkspaceDataOutput);
|
||||||
|
},
|
||||||
|
children: I18nProvider({ i18n, children }),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
return { result };
|
return { result };
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,14 +4,20 @@ import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/Snac
|
|||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { useEmailPasswordResetLinkMutation } from '~/generated/graphql';
|
import { useEmailPasswordResetLinkMutation } from '~/generated/graphql';
|
||||||
|
import { workspacePublicDataState } from '@/auth/states/workspacePublicDataState';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
import { currentUserState } from '@/auth/states/currentUserState';
|
||||||
|
|
||||||
export const useHandleResetPassword = () => {
|
export const useHandleResetPassword = () => {
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSnackBar } = useSnackBar();
|
||||||
const [emailPasswordResetLink] = useEmailPasswordResetLinkMutation();
|
const [emailPasswordResetLink] = useEmailPasswordResetLinkMutation();
|
||||||
|
const workspacePublicData = useRecoilValue(workspacePublicDataState);
|
||||||
|
const currentUser = useRecoilValue(currentUserState);
|
||||||
|
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
|
|
||||||
const handleResetPassword = useCallback(
|
const handleResetPassword = useCallback(
|
||||||
(email: string) => {
|
(email = currentUser?.email) => {
|
||||||
return async () => {
|
return async () => {
|
||||||
if (!email) {
|
if (!email) {
|
||||||
enqueueSnackBar(t`Invalid email`, {
|
enqueueSnackBar(t`Invalid email`, {
|
||||||
@ -20,9 +26,16 @@ export const useHandleResetPassword = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!workspacePublicData?.id) {
|
||||||
|
enqueueSnackBar(t`Invalid workspace`, {
|
||||||
|
variant: SnackBarVariant.Error,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data } = await emailPasswordResetLink({
|
const { data } = await emailPasswordResetLink({
|
||||||
variables: { email },
|
variables: { email, workspaceId: workspacePublicData.id },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (data?.emailPasswordResetLink?.success === true) {
|
if (data?.emailPasswordResetLink?.success === true) {
|
||||||
@ -41,7 +54,13 @@ export const useHandleResetPassword = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[enqueueSnackBar, emailPasswordResetLink, t],
|
[
|
||||||
|
currentUser?.email,
|
||||||
|
workspacePublicData?.id,
|
||||||
|
enqueueSnackBar,
|
||||||
|
t,
|
||||||
|
emailPasswordResetLink,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
return { handleResetPassword };
|
return { handleResetPassword };
|
||||||
|
|||||||
@ -1,50 +1,12 @@
|
|||||||
import { useRecoilValue } from 'recoil';
|
|
||||||
import { Button, H2Title } from 'twenty-ui';
|
import { Button, H2Title } from 'twenty-ui';
|
||||||
|
|
||||||
import { currentUserState } from '@/auth/states/currentUserState';
|
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { useEmailPasswordResetLinkMutation } from '~/generated/graphql';
|
import { useHandleResetPassword } from '@/auth/sign-in-up/hooks/useHandleResetPassword';
|
||||||
|
|
||||||
export const ChangePassword = () => {
|
export const ChangePassword = () => {
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
|
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { handleResetPassword } = useHandleResetPassword();
|
||||||
|
|
||||||
const currentUser = useRecoilValue(currentUserState);
|
|
||||||
|
|
||||||
const [emailPasswordResetLink] = useEmailPasswordResetLinkMutation();
|
|
||||||
|
|
||||||
const handlePasswordResetClick = async () => {
|
|
||||||
if (!currentUser?.email) {
|
|
||||||
enqueueSnackBar(t`Invalid email`, {
|
|
||||||
variant: SnackBarVariant.Error,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { data } = await emailPasswordResetLink({
|
|
||||||
variables: {
|
|
||||||
email: currentUser.email,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (data?.emailPasswordResetLink?.success === true) {
|
|
||||||
enqueueSnackBar(t`Password reset link has been sent to the email`, {
|
|
||||||
variant: SnackBarVariant.Success,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
enqueueSnackBar(t`There was an issue`, {
|
|
||||||
variant: SnackBarVariant.Error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
enqueueSnackBar((error as Error).message, {
|
|
||||||
variant: SnackBarVariant.Error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -53,7 +15,7 @@ export const ChangePassword = () => {
|
|||||||
description={t`Receive an email containing password update link`}
|
description={t`Receive an email containing password update link`}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
onClick={handlePasswordResetClick}
|
onClick={handleResetPassword()}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
title={t`Change Password`}
|
title={t`Change Password`}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -24,7 +24,6 @@ import {
|
|||||||
AuthException,
|
AuthException,
|
||||||
AuthExceptionCode,
|
AuthExceptionCode,
|
||||||
} from 'src/engine/core-modules/auth/auth.exception';
|
} from 'src/engine/core-modules/auth/auth.exception';
|
||||||
import { AvailableWorkspaceOutput } from 'src/engine/core-modules/auth/dto/available-workspaces.output';
|
|
||||||
import { GetAuthorizationUrlForSSOInput } from 'src/engine/core-modules/auth/dto/get-authorization-url-for-sso.input';
|
import { GetAuthorizationUrlForSSOInput } from 'src/engine/core-modules/auth/dto/get-authorization-url-for-sso.input';
|
||||||
import { GetAuthorizationUrlForSSOOutput } from 'src/engine/core-modules/auth/dto/get-authorization-url-for-sso.output';
|
import { GetAuthorizationUrlForSSOOutput } from 'src/engine/core-modules/auth/dto/get-authorization-url-for-sso.output';
|
||||||
import { GetLoginTokenFromEmailVerificationTokenInput } from 'src/engine/core-modules/auth/dto/get-login-token-from-email-verification-token.input';
|
import { GetLoginTokenFromEmailVerificationTokenInput } from 'src/engine/core-modules/auth/dto/get-login-token-from-email-verification-token.input';
|
||||||
@ -366,6 +365,7 @@ export class AuthResolver {
|
|||||||
const resetToken =
|
const resetToken =
|
||||||
await this.resetPasswordService.generatePasswordResetToken(
|
await this.resetPasswordService.generatePasswordResetToken(
|
||||||
emailPasswordResetInput.email,
|
emailPasswordResetInput.email,
|
||||||
|
emailPasswordResetInput.workspaceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
return await this.resetPasswordService.sendEmailPasswordResetLink(
|
return await this.resetPasswordService.sendEmailPasswordResetLink(
|
||||||
@ -403,11 +403,4 @@ export class AuthResolver {
|
|||||||
args.passwordResetToken,
|
args.passwordResetToken,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Query(() => [AvailableWorkspaceOutput])
|
|
||||||
async findAvailableWorkspacesByEmail(
|
|
||||||
@Args('email') email: string,
|
|
||||||
): Promise<AvailableWorkspaceOutput[]> {
|
|
||||||
return this.userWorkspaceService.findAvailableWorkspacesByEmail(email);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { ArgsType, Field } from '@nestjs/graphql';
|
import { ArgsType, Field } from '@nestjs/graphql';
|
||||||
|
|
||||||
import { IsEmail, IsNotEmpty } from 'class-validator';
|
import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
|
||||||
|
|
||||||
@ArgsType()
|
@ArgsType()
|
||||||
export class EmailPasswordResetLinkInput {
|
export class EmailPasswordResetLinkInput {
|
||||||
@ -8,4 +8,9 @@ export class EmailPasswordResetLinkInput {
|
|||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
@IsEmail()
|
@IsEmail()
|
||||||
email: string;
|
email: string;
|
||||||
|
|
||||||
|
@Field(() => String)
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
workspaceId: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,4 +37,7 @@ export class PasswordResetToken {
|
|||||||
|
|
||||||
@Field(() => Date)
|
@Field(() => Date)
|
||||||
passwordResetTokenExpiresAt: Date;
|
passwordResetTokenExpiresAt: Date;
|
||||||
|
|
||||||
|
@Field(() => String)
|
||||||
|
workspaceId: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,9 +20,11 @@ import { ResetPasswordService } from './reset-password.service';
|
|||||||
describe('ResetPasswordService', () => {
|
describe('ResetPasswordService', () => {
|
||||||
let service: ResetPasswordService;
|
let service: ResetPasswordService;
|
||||||
let userRepository: Repository<User>;
|
let userRepository: Repository<User>;
|
||||||
|
let workspaceRepository: Repository<Workspace>;
|
||||||
let appTokenRepository: Repository<AppToken>;
|
let appTokenRepository: Repository<AppToken>;
|
||||||
let emailService: EmailService;
|
let emailService: EmailService;
|
||||||
let environmentService: EnvironmentService;
|
let environmentService: EnvironmentService;
|
||||||
|
let domainManagerService: DomainManagerService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
@ -32,6 +34,10 @@ describe('ResetPasswordService', () => {
|
|||||||
provide: getRepositoryToken(User, 'core'),
|
provide: getRepositoryToken(User, 'core'),
|
||||||
useClass: Repository,
|
useClass: Repository,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: getRepositoryToken(Workspace, 'core'),
|
||||||
|
useClass: Repository,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
provide: getRepositoryToken(AppToken, 'core'),
|
provide: getRepositoryToken(AppToken, 'core'),
|
||||||
useClass: Repository,
|
useClass: Repository,
|
||||||
@ -52,6 +58,7 @@ describe('ResetPasswordService', () => {
|
|||||||
getBaseUrl: jest
|
getBaseUrl: jest
|
||||||
.fn()
|
.fn()
|
||||||
.mockResolvedValue(new URL('http://localhost:3001')),
|
.mockResolvedValue(new URL('http://localhost:3001')),
|
||||||
|
buildWorkspaceURL: jest.fn(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -67,11 +74,16 @@ describe('ResetPasswordService', () => {
|
|||||||
userRepository = module.get<Repository<User>>(
|
userRepository = module.get<Repository<User>>(
|
||||||
getRepositoryToken(User, 'core'),
|
getRepositoryToken(User, 'core'),
|
||||||
);
|
);
|
||||||
|
workspaceRepository = module.get<Repository<Workspace>>(
|
||||||
|
getRepositoryToken(Workspace, 'core'),
|
||||||
|
);
|
||||||
appTokenRepository = module.get<Repository<AppToken>>(
|
appTokenRepository = module.get<Repository<AppToken>>(
|
||||||
getRepositoryToken(AppToken, 'core'),
|
getRepositoryToken(AppToken, 'core'),
|
||||||
);
|
);
|
||||||
emailService = module.get<EmailService>(EmailService);
|
emailService = module.get<EmailService>(EmailService);
|
||||||
environmentService = module.get<EnvironmentService>(EnvironmentService);
|
environmentService = module.get<EnvironmentService>(EnvironmentService);
|
||||||
|
domainManagerService =
|
||||||
|
module.get<DomainManagerService>(DomainManagerService);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be defined', () => {
|
it('should be defined', () => {
|
||||||
@ -89,8 +101,10 @@ describe('ResetPasswordService', () => {
|
|||||||
jest.spyOn(appTokenRepository, 'save').mockResolvedValue({} as AppToken);
|
jest.spyOn(appTokenRepository, 'save').mockResolvedValue({} as AppToken);
|
||||||
jest.spyOn(environmentService, 'get').mockReturnValue('1h');
|
jest.spyOn(environmentService, 'get').mockReturnValue('1h');
|
||||||
|
|
||||||
const result =
|
const result = await service.generatePasswordResetToken(
|
||||||
await service.generatePasswordResetToken('test@example.com');
|
'test@example.com',
|
||||||
|
'workspace-id',
|
||||||
|
);
|
||||||
|
|
||||||
expect(result.passwordResetToken).toBeDefined();
|
expect(result.passwordResetToken).toBeDefined();
|
||||||
expect(result.passwordResetTokenExpiresAt).toBeDefined();
|
expect(result.passwordResetTokenExpiresAt).toBeDefined();
|
||||||
@ -106,7 +120,10 @@ describe('ResetPasswordService', () => {
|
|||||||
jest.spyOn(userRepository, 'findOneBy').mockResolvedValue(null);
|
jest.spyOn(userRepository, 'findOneBy').mockResolvedValue(null);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
service.generatePasswordResetToken('nonexistent@example.com'),
|
service.generatePasswordResetToken(
|
||||||
|
'nonexistent@example.com',
|
||||||
|
'workspace-id',
|
||||||
|
),
|
||||||
).rejects.toThrow(AuthException);
|
).rejects.toThrow(AuthException);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -115,6 +132,7 @@ describe('ResetPasswordService', () => {
|
|||||||
const mockExistingToken = {
|
const mockExistingToken = {
|
||||||
userId: '1',
|
userId: '1',
|
||||||
type: AppTokenType.PasswordResetToken,
|
type: AppTokenType.PasswordResetToken,
|
||||||
|
workspaceId: 'workspace-id',
|
||||||
expiresAt: addMilliseconds(new Date(), 3600000),
|
expiresAt: addMilliseconds(new Date(), 3600000),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -126,7 +144,7 @@ describe('ResetPasswordService', () => {
|
|||||||
.mockResolvedValue(mockExistingToken as AppToken);
|
.mockResolvedValue(mockExistingToken as AppToken);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
service.generatePasswordResetToken('test@example.com'),
|
service.generatePasswordResetToken('test@example.com', 'workspace-id'),
|
||||||
).rejects.toThrow(AuthException);
|
).rejects.toThrow(AuthException);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -135,6 +153,7 @@ describe('ResetPasswordService', () => {
|
|||||||
it('should send a password reset email', async () => {
|
it('should send a password reset email', async () => {
|
||||||
const mockUser = { id: '1', email: 'test@example.com' };
|
const mockUser = { id: '1', email: 'test@example.com' };
|
||||||
const mockToken = {
|
const mockToken = {
|
||||||
|
workspaceId: 'workspace-id',
|
||||||
passwordResetToken: 'token123',
|
passwordResetToken: 'token123',
|
||||||
passwordResetTokenExpiresAt: new Date(),
|
passwordResetTokenExpiresAt: new Date(),
|
||||||
};
|
};
|
||||||
@ -142,9 +161,19 @@ describe('ResetPasswordService', () => {
|
|||||||
jest
|
jest
|
||||||
.spyOn(userRepository, 'findOneBy')
|
.spyOn(userRepository, 'findOneBy')
|
||||||
.mockResolvedValue(mockUser as User);
|
.mockResolvedValue(mockUser as User);
|
||||||
|
jest
|
||||||
|
.spyOn(workspaceRepository, 'findOneBy')
|
||||||
|
.mockResolvedValue({ id: 'workspace-id' } as Workspace);
|
||||||
jest
|
jest
|
||||||
.spyOn(environmentService, 'get')
|
.spyOn(environmentService, 'get')
|
||||||
.mockReturnValue('http://localhost:3000');
|
.mockReturnValue('http://localhost:3000');
|
||||||
|
jest
|
||||||
|
.spyOn(domainManagerService, 'buildWorkspaceURL')
|
||||||
|
.mockReturnValue(
|
||||||
|
new URL(
|
||||||
|
'https://subdomain.localhost.com:3000/reset-password/passwordResetToken',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
const result = await service.sendEmailPasswordResetLink(
|
const result = await service.sendEmailPasswordResetLink(
|
||||||
mockToken,
|
mockToken,
|
||||||
|
|||||||
@ -28,6 +28,8 @@ import { DomainManagerService } from 'src/engine/core-modules/domain-manager/ser
|
|||||||
import { EmailService } from 'src/engine/core-modules/email/email.service';
|
import { EmailService } from 'src/engine/core-modules/email/email.service';
|
||||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||||
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
|
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ResetPasswordService {
|
export class ResetPasswordService {
|
||||||
@ -36,12 +38,17 @@ export class ResetPasswordService {
|
|||||||
private readonly domainManagerService: DomainManagerService,
|
private readonly domainManagerService: DomainManagerService,
|
||||||
@InjectRepository(User, 'core')
|
@InjectRepository(User, 'core')
|
||||||
private readonly userRepository: Repository<User>,
|
private readonly userRepository: Repository<User>,
|
||||||
|
@InjectRepository(Workspace, 'core')
|
||||||
|
private readonly workspaceRepository: Repository<Workspace>,
|
||||||
@InjectRepository(AppToken, 'core')
|
@InjectRepository(AppToken, 'core')
|
||||||
private readonly appTokenRepository: Repository<AppToken>,
|
private readonly appTokenRepository: Repository<AppToken>,
|
||||||
private readonly emailService: EmailService,
|
private readonly emailService: EmailService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async generatePasswordResetToken(email: string): Promise<PasswordResetToken> {
|
async generatePasswordResetToken(
|
||||||
|
email: string,
|
||||||
|
workspaceId: string,
|
||||||
|
): Promise<PasswordResetToken> {
|
||||||
const user = await this.userRepository.findOneBy({
|
const user = await this.userRepository.findOneBy({
|
||||||
email,
|
email,
|
||||||
});
|
});
|
||||||
@ -95,12 +102,14 @@ export class ResetPasswordService {
|
|||||||
|
|
||||||
await this.appTokenRepository.save({
|
await this.appTokenRepository.save({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
workspaceId: workspaceId,
|
||||||
value: hashedResetToken,
|
value: hashedResetToken,
|
||||||
expiresAt,
|
expiresAt,
|
||||||
type: AppTokenType.PasswordResetToken,
|
type: AppTokenType.PasswordResetToken,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
workspaceId,
|
||||||
passwordResetToken: plainResetToken,
|
passwordResetToken: plainResetToken,
|
||||||
passwordResetTokenExpiresAt: expiresAt,
|
passwordResetTokenExpiresAt: expiresAt,
|
||||||
};
|
};
|
||||||
@ -122,12 +131,19 @@ export class ResetPasswordService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const frontBaseURL = this.domainManagerService.getBaseUrl();
|
const workspace = await this.workspaceRepository.findOneBy({
|
||||||
|
id: resetToken.workspaceId,
|
||||||
|
});
|
||||||
|
|
||||||
frontBaseURL.pathname = `/reset-password/${resetToken.passwordResetToken}`;
|
workspaceValidator.assertIsDefinedOrThrow(workspace);
|
||||||
|
|
||||||
|
const link = this.domainManagerService.buildWorkspaceURL({
|
||||||
|
workspace,
|
||||||
|
pathname: `/reset-password/${resetToken.passwordResetToken}`,
|
||||||
|
});
|
||||||
|
|
||||||
const emailData = {
|
const emailData = {
|
||||||
link: frontBaseURL.toString(),
|
link: link.toString(),
|
||||||
duration: ms(
|
duration: ms(
|
||||||
differenceInMilliseconds(
|
differenceInMilliseconds(
|
||||||
resetToken.passwordResetTokenExpiresAt,
|
resetToken.passwordResetTokenExpiresAt,
|
||||||
|
|||||||
Reference in New Issue
Block a user