From 55576cb63822f740caa17b6ca4e26315fbef93ef Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Mon, 10 Jul 2023 23:33:15 -0700 Subject: [PATCH] Add possibility to invite members to workspace (#579) * Add possibility to invite members to workspace * Update endpoints * Wrap up front end * Fix according to review * Fix lint --- front/src/App.tsx | 1 + front/src/generated/graphql.tsx | 117 ++++++++++++++++++ front/src/index.tsx | 16 +-- front/src/modules/auth/hooks/useAuth.ts | 56 ++++++++- front/src/modules/auth/services/update.ts | 30 +++++ .../components/right-drawer/CommentThread.tsx | 4 +- front/src/pages/auth/PasswordLogin.tsx | 78 ++++++++---- front/src/sync-hooks/types/AuthPath.ts | 1 + front/src/testing/mock-data/users.ts | 4 +- .../workspace-count-aggregate.input.ts | 3 + .../workspace-count-aggregate.output.ts | 3 + ...orkspace-count-order-by-aggregate.input.ts | 3 + .../workspace/workspace-create-many.input.ts | 5 + ...ce-create-without-comment-threads.input.ts | 5 + ...workspace-create-without-comments.input.ts | 5 + ...orkspace-create-without-companies.input.ts | 5 + .../workspace-create-without-people.input.ts | 5 + ...reate-without-pipeline-progresses.input.ts | 5 + ...ce-create-without-pipeline-stages.input.ts | 5 + ...orkspace-create-without-pipelines.input.ts | 5 + ...e-create-without-workspace-member.input.ts | 5 + .../workspace/workspace-create.input.ts | 5 + .../workspace/workspace-group-by.output.ts | 5 + .../workspace-max-aggregate.input.ts | 3 + .../workspace-max-aggregate.output.ts | 5 + .../workspace-max-order-by-aggregate.input.ts | 3 + .../workspace-min-aggregate.input.ts | 3 + .../workspace-min-aggregate.output.ts | 5 + .../workspace-min-order-by-aggregate.input.ts | 3 + ...rkspace-order-by-with-aggregation.input.ts | 3 + .../workspace-order-by-with-relation.input.ts | 3 + .../workspace/workspace-scalar-field.enum.ts | 1 + ...pace-scalar-where-with-aggregates.input.ts | 3 + ...ed-create-without-comment-threads.input.ts | 5 + ...unchecked-create-without-comments.input.ts | 5 + ...nchecked-create-without-companies.input.ts | 5 + ...e-unchecked-create-without-people.input.ts | 5 + ...reate-without-pipeline-progresses.input.ts | 5 + ...ed-create-without-pipeline-stages.input.ts | 5 + ...nchecked-create-without-pipelines.input.ts | 5 + ...d-create-without-workspace-member.input.ts | 5 + .../workspace-unchecked-create.input.ts | 5 + .../workspace-unchecked-update-many.input.ts | 3 + ...ed-update-without-comment-threads.input.ts | 3 + ...unchecked-update-without-comments.input.ts | 3 + ...nchecked-update-without-companies.input.ts | 3 + ...e-unchecked-update-without-people.input.ts | 3 + ...pdate-without-pipeline-progresses.input.ts | 3 + ...ed-update-without-pipeline-stages.input.ts | 3 + ...nchecked-update-without-pipelines.input.ts | 3 + ...d-update-without-workspace-member.input.ts | 3 + .../workspace-unchecked-update.input.ts | 3 + .../workspace-update-many-mutation.input.ts | 3 + ...ce-update-without-comment-threads.input.ts | 3 + ...workspace-update-without-comments.input.ts | 3 + ...orkspace-update-without-companies.input.ts | 3 + .../workspace-update-without-people.input.ts | 3 + ...pdate-without-pipeline-progresses.input.ts | 3 + ...ce-update-without-pipeline-stages.input.ts | 3 + ...orkspace-update-without-pipelines.input.ts | 3 + ...e-update-without-workspace-member.input.ts | 3 + .../workspace/workspace-update.input.ts | 3 + .../workspace/workspace-where.input.ts | 3 + .../@generated/workspace/workspace.model.ts | 3 + server/src/core/auth/auth.module.ts | 3 +- server/src/core/auth/auth.resolver.ts | 20 +++ server/src/core/auth/dto/sign-up.input.ts | 20 +++ .../dto/workspace-invite-hash-valid.entity.ts | 7 ++ .../auth/dto/workspace-invite-hash.input.ts | 11 ++ .../core/auth/services/auth.service.spec.ts | 5 + server/src/core/auth/services/auth.service.ts | 81 +++++++++--- server/src/core/user/user.service.ts | 24 ++-- .../migration.sql | 2 + server/src/database/schema.prisma | 3 + server/src/database/seeds/workspaces.ts | 2 + 75 files changed, 629 insertions(+), 63 deletions(-) create mode 100644 server/src/core/auth/dto/sign-up.input.ts create mode 100644 server/src/core/auth/dto/workspace-invite-hash-valid.entity.ts create mode 100644 server/src/core/auth/dto/workspace-invite-hash.input.ts create mode 100644 server/src/database/migrations/20230711002359_workspace_invite_hash/migration.sql diff --git a/front/src/App.tsx b/front/src/App.tsx index 2cce1173b..596b3e0c6 100644 --- a/front/src/App.tsx +++ b/front/src/App.tsx @@ -39,6 +39,7 @@ function AuthRoutes() { } /> } /> } /> + } /> } diff --git a/front/src/generated/graphql.tsx b/front/src/generated/graphql.tsx index 26f71c67f..fc6a5fbcb 100644 --- a/front/src/generated/graphql.tsx +++ b/front/src/generated/graphql.tsx @@ -1163,6 +1163,7 @@ export type Mutation = { deleteManyPipelineProgress: AffectedRows; deleteWorkspaceMember: WorkspaceMember; renewToken: AuthTokens; + signUp: LoginToken; updateOneCommentThread: CommentThread; updateOneCompany?: Maybe; updateOnePerson?: Maybe; @@ -1244,6 +1245,13 @@ export type MutationRenewTokenArgs = { }; +export type MutationSignUpArgs = { + email: Scalars['String']; + password: Scalars['String']; + workspaceInviteHash?: InputMaybe; +}; + + export type MutationUpdateOneCommentThreadArgs = { data: CommentThreadUpdateInput; where: CommentThreadWhereUniqueInput; @@ -2439,6 +2447,7 @@ export type PipelineWhereUniqueInput = { export type Query = { __typename?: 'Query'; checkUserExists: UserExists; + checkWorkspaceInviteHashIsValid: WorkspaceInviteHashValid; clientConfig: ClientConfig; currentUser: User; currentWorkspace: Workspace; @@ -2460,6 +2469,11 @@ export type QueryCheckUserExistsArgs = { }; +export type QueryCheckWorkspaceInviteHashIsValidArgs = { + inviteHash: Scalars['String']; +}; + + export type QueryFindManyCommentThreadsArgs = { cursor?: InputMaybe; distinct?: InputMaybe>; @@ -2917,6 +2931,7 @@ export type Workspace = { displayName?: Maybe; domainName?: Maybe; id: Scalars['ID']; + inviteHash?: Maybe; logo?: Maybe; people?: Maybe>; pipelineProgresses?: Maybe>; @@ -2926,6 +2941,11 @@ export type Workspace = { workspaceMember?: Maybe>; }; +export type WorkspaceInviteHashValid = { + __typename?: 'WorkspaceInviteHashValid'; + isValid: Scalars['Boolean']; +}; + export type WorkspaceMember = { __typename?: 'WorkspaceMember'; createdAt: Scalars['DateTime']; @@ -3054,6 +3074,7 @@ export type WorkspaceUpdateInput = { displayName?: InputMaybe; domainName?: InputMaybe; id?: InputMaybe; + inviteHash?: InputMaybe; logo?: InputMaybe; people?: InputMaybe; pipelineProgresses?: InputMaybe; @@ -3086,6 +3107,23 @@ export type ChallengeMutationVariables = Exact<{ export type ChallengeMutation = { __typename?: 'Mutation', challenge: { __typename?: 'LoginToken', loginToken: { __typename?: 'AuthToken', expiresAt: string, token: string } } }; +export type SignUpMutationVariables = Exact<{ + email: Scalars['String']; + password: Scalars['String']; +}>; + + +export type SignUpMutation = { __typename?: 'Mutation', signUp: { __typename?: 'LoginToken', loginToken: { __typename?: 'AuthToken', expiresAt: string, token: string } } }; + +export type SignUpToWorkspaceMutationVariables = Exact<{ + email: Scalars['String']; + password: Scalars['String']; + workspaceInviteHash: Scalars['String']; +}>; + + +export type SignUpToWorkspaceMutation = { __typename?: 'Mutation', signUp: { __typename?: 'LoginToken', loginToken: { __typename?: 'AuthToken', expiresAt: string, token: string } } }; + export type VerifyMutationVariables = Exact<{ loginToken: Scalars['String']; }>; @@ -3526,6 +3564,85 @@ export function useChallengeMutation(baseOptions?: Apollo.MutationHookOptions; export type ChallengeMutationResult = Apollo.MutationResult; export type ChallengeMutationOptions = Apollo.BaseMutationOptions; +export const SignUpDocument = gql` + mutation SignUp($email: String!, $password: String!) { + signUp(email: $email, password: $password) { + loginToken { + expiresAt + token + } + } +} + `; +export type SignUpMutationFn = Apollo.MutationFunction; + +/** + * __useSignUpMutation__ + * + * To run a mutation, you first call `useSignUpMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useSignUpMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [signUpMutation, { data, loading, error }] = useSignUpMutation({ + * variables: { + * email: // value for 'email' + * password: // value for 'password' + * }, + * }); + */ +export function useSignUpMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(SignUpDocument, options); + } +export type SignUpMutationHookResult = ReturnType; +export type SignUpMutationResult = Apollo.MutationResult; +export type SignUpMutationOptions = Apollo.BaseMutationOptions; +export const SignUpToWorkspaceDocument = gql` + mutation SignUpToWorkspace($email: String!, $password: String!, $workspaceInviteHash: String!) { + signUp( + email: $email + password: $password + workspaceInviteHash: $workspaceInviteHash + ) { + loginToken { + expiresAt + token + } + } +} + `; +export type SignUpToWorkspaceMutationFn = Apollo.MutationFunction; + +/** + * __useSignUpToWorkspaceMutation__ + * + * To run a mutation, you first call `useSignUpToWorkspaceMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useSignUpToWorkspaceMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [signUpToWorkspaceMutation, { data, loading, error }] = useSignUpToWorkspaceMutation({ + * variables: { + * email: // value for 'email' + * password: // value for 'password' + * workspaceInviteHash: // value for 'workspaceInviteHash' + * }, + * }); + */ +export function useSignUpToWorkspaceMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(SignUpToWorkspaceDocument, options); + } +export type SignUpToWorkspaceMutationHookResult = ReturnType; +export type SignUpToWorkspaceMutationResult = Apollo.MutationResult; +export type SignUpToWorkspaceMutationOptions = Apollo.BaseMutationOptions; export const VerifyDocument = gql` mutation Verify($loginToken: String!) { verify(loginToken: $loginToken) { diff --git a/front/src/index.tsx b/front/src/index.tsx index 639529492..95070ddec 100644 --- a/front/src/index.tsx +++ b/front/src/index.tsx @@ -27,15 +27,15 @@ root.render( - - - - + + + + - - - - + + + + diff --git a/front/src/modules/auth/hooks/useAuth.ts b/front/src/modules/auth/hooks/useAuth.ts index 3584a0f07..90bfdde7a 100644 --- a/front/src/modules/auth/hooks/useAuth.ts +++ b/front/src/modules/auth/hooks/useAuth.ts @@ -1,7 +1,12 @@ import { useCallback } from 'react'; import { useRecoilState } from 'recoil'; -import { useChallengeMutation, useVerifyMutation } from '~/generated/graphql'; +import { + useChallengeMutation, + useSignUpMutation, + useSignUpToWorkspaceMutation, + useVerifyMutation, +} from '~/generated/graphql'; import { currentUserState } from '../states/currentUserState'; import { isAuthenticatingState } from '../states/isAuthenticatingState'; @@ -13,6 +18,8 @@ export function useAuth() { const [, setIsAuthenticating] = useRecoilState(isAuthenticatingState); const [challenge] = useChallengeMutation(); + const [signUp] = useSignUpMutation(); + const [SignUpToWorkspace] = useSignUpToWorkspaceMutation(); const [verify] = useVerifyMutation(); const handleChallenge = useCallback( @@ -74,10 +81,57 @@ export function useAuth() { setTokenPair(null); }, [setTokenPair]); + const handleSignUp = useCallback( + async (email: string, password: string) => { + const signUpResult = await signUp({ + variables: { + email, + password, + }, + }); + + if (signUpResult.errors) { + throw signUpResult.errors; + } + + if (!signUpResult.data?.signUp) { + throw new Error('No login token'); + } + + await handleVerify(signUpResult.data?.signUp.loginToken.token); + }, + [signUp, handleVerify], + ); + + const handleSignUpToWorkspace = useCallback( + async (email: string, password: string, workspaceInviteHash: string) => { + const signUpResult = await SignUpToWorkspace({ + variables: { + email, + password, + workspaceInviteHash, + }, + }); + + if (signUpResult.errors) { + throw signUpResult.errors; + } + + if (!signUpResult.data?.signUp) { + throw new Error('No login token'); + } + + await handleVerify(signUpResult.data?.signUp.loginToken.token); + }, + [SignUpToWorkspace, handleVerify], + ); + return { challenge: handleChallenge, verify: handleVerify, login: handleLogin, + signUp: handleSignUp, + signUpToWorkspace: handleSignUpToWorkspace, logout: handleLogout, }; } diff --git a/front/src/modules/auth/services/update.ts b/front/src/modules/auth/services/update.ts index ac6e61241..8b2e30c45 100644 --- a/front/src/modules/auth/services/update.ts +++ b/front/src/modules/auth/services/update.ts @@ -11,6 +11,36 @@ export const CHALLENGE = gql` } `; +export const SIGN_UP = gql` + mutation SignUp($email: String!, $password: String!) { + signUp(email: $email, password: $password) { + loginToken { + expiresAt + token + } + } + } +`; + +export const SIGN_UP_TO_WORKSPACE = gql` + mutation SignUpToWorkspace( + $email: String! + $password: String! + $workspaceInviteHash: String! + ) { + signUp( + email: $email + password: $password + workspaceInviteHash: $workspaceInviteHash + ) { + loginToken { + expiresAt + token + } + } + } +`; + export const VERIFY = gql` mutation Verify($loginToken: String!) { verify(loginToken: $loginToken) { diff --git a/front/src/modules/comments/components/right-drawer/CommentThread.tsx b/front/src/modules/comments/components/right-drawer/CommentThread.tsx index 48af42021..277cc76d8 100644 --- a/front/src/modules/comments/components/right-drawer/CommentThread.tsx +++ b/front/src/modules/comments/components/right-drawer/CommentThread.tsx @@ -130,10 +130,10 @@ export function CommentThread({ } useEffect(() => { - if (commentThread) { + if (commentThread && !title) { setTitle(commentThread?.title ?? ''); } - }, [commentThread]); + }, [commentThread, title]); if (!commentThread) { return <>; diff --git a/front/src/pages/auth/PasswordLogin.tsx b/front/src/pages/auth/PasswordLogin.tsx index 341f49e93..1a23939e8 100644 --- a/front/src/pages/auth/PasswordLogin.tsx +++ b/front/src/pages/auth/PasswordLogin.tsx @@ -1,5 +1,5 @@ -import { useCallback, useState } from 'react'; -import { useNavigate } from 'react-router-dom'; +import { useCallback, useEffect, useState } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; import styled from '@emotion/styled'; import { motion } from 'framer-motion'; import { useRecoilState } from 'recoil'; @@ -62,35 +62,61 @@ export function PasswordLogin() { ); const [formError, setFormError] = useState(''); - const { login } = useAuth(); - - const handleLogin = useCallback(async () => { - try { - setMockMode(false); - - await login(authFlowUserEmail, internalPassword); - - navigate('/auth/create/workspace'); - } catch (err: any) { - setFormError(err.message); - } - }, [login, authFlowUserEmail, internalPassword, setMockMode, navigate]); - - useScopedHotkeys( - 'enter', - () => { - handleLogin(); - }, - InternalHotkeysScope.PasswordLogin, - [handleLogin], - ); - + const { login, signUp, signUpToWorkspace } = useAuth(); const { loading, data } = useCheckUserExistsQuery({ variables: { email: authFlowUserEmail, }, }); + useEffect(() => { + setMockMode(true); + }, [setMockMode]); + + const workspaceInviteHash = useParams().workspaceInviteHash; + + const handleSubmit = useCallback(async () => { + try { + setMockMode(false); + if (data?.checkUserExists.exists) { + await login(authFlowUserEmail, internalPassword); + } else { + if (workspaceInviteHash) { + await signUpToWorkspace( + authFlowUserEmail, + internalPassword, + workspaceInviteHash, + ); + } else { + await signUp(authFlowUserEmail, internalPassword); + } + } + navigate('/auth/create/workspace'); + } catch (err: any) { + setFormError(err.message); + } + }, [ + login, + signUp, + signUpToWorkspace, + authFlowUserEmail, + internalPassword, + setMockMode, + navigate, + data?.checkUserExists.exists, + + workspaceInviteHash, + ]); + + useScopedHotkeys( + 'enter', + () => { + handleSubmit(); + }, + InternalHotkeysScope.PasswordLogin, + [handleSubmit], + ); + return ( <> @@ -124,7 +150,7 @@ export function PasswordLogin() { diff --git a/front/src/sync-hooks/types/AuthPath.ts b/front/src/sync-hooks/types/AuthPath.ts index e528e46ad..3d3c3aca4 100644 --- a/front/src/sync-hooks/types/AuthPath.ts +++ b/front/src/sync-hooks/types/AuthPath.ts @@ -4,4 +4,5 @@ export enum AuthPath { PasswordLogin = 'password-login', CreateWorkspace = 'create/workspace', CreateProfile = 'create/profile', + InviteLink = 'invite/:workspaceInviteHash', } diff --git a/front/src/testing/mock-data/users.ts b/front/src/testing/mock-data/users.ts index 8893faf04..d6f45cb15 100644 --- a/front/src/testing/mock-data/users.ts +++ b/front/src/testing/mock-data/users.ts @@ -13,7 +13,7 @@ type MockedUser = Pick< workspaceMember: Pick & { workspace: Pick< Workspace, - 'id' | 'displayName' | 'domainName' | 'logo' | '__typename' + 'id' | 'displayName' | 'domainName' | 'logo' | 'inviteHash' | '__typename' >; }; }; @@ -35,6 +35,7 @@ export const mockedUsersData: Array = [ id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6b', displayName: 'Twenty', domainName: 'twenty.com', + inviteHash: 'twenty.com-invite-hash', logo: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAA7EAAAOxAGVKw4bAAACb0lEQVR4nO2VO4taQRTHr3AblbjxEVlwCwVhg7BoqqCIjy/gAyyFWNlYBOxsfH0KuxgQGwXRUkGuL2S7i1barGAgiwbdW93SnGOc4BonPiKahf3DwXFmuP/fPM4ZlvmlTxAhCBdzHnEQWYiv7Mr4C3NeuVYhQYDPzOUUQgDLBQGcLHNhvQK8DACPx8PTxiqVyvISG43GbyaT6Qfpn06n0m63e/tPAPF4vJ1MJu8kEsnWTCkWi1yr1RKGw+GDRqPBOTfr44vFQvD7/Q/lcpmaaVQAr9fLp1IpO22c47hGOBz+MB6PH+Vy+VYDAL8qlUoGtVotzOfzq4MAgsHgE/6KojiQyWR/bKVSqbSszHFM8Pl8z1YK48JsNltCOBwOnrYLO+8AAIjb+nHbycoTiUQfDJ7tFq4YAHiVSmXBxcD41u8flQU8z7fhzO0r83atVns3Go3u9Xr9x0O/RQXo9/tsIBBg6vX606a52Wz+bZ7P5/WwG29gxSJzhKgA6XTaDoFNF+krFAocmC//4yWEcSf2wTm7mCO19xFgSsKOLI16vV7b7XY7mRNoLwA0JymJ5uQIzgIAuX5PzDElT2m+E8BqtQ4ymcx7Yq7T6a6ZE4sKgOadTucaCwkxp1UzlEKh0GDxIXOwDWHAdi6Xe3swQDQa/Q7mywoolUpvsaptymazDWKxmBHTlWXZm405BFZoNpuGgwEmk4mE2SGtVivii4f1AO7J3ZopkQCQj7Ar1FeRChCJRJzVapX6DKNIfSc1Ax+wtQWQ55h6bH8FWDfYV4fO3wlwDr0C/BcADYiTPCxHqIEA2QsCZAkAKnRGkMbKN/sTX5YHPQ1e7SkAAAAASUVORK5CYII=', }, }, @@ -54,6 +55,7 @@ export const mockedUsersData: Array = [ id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6b', displayName: 'Twenty', domainName: 'twenty.com', + inviteHash: 'twenty.com-invite-hash', logo: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAA7EAAAOxAGVKw4bAAACb0lEQVR4nO2VO4taQRTHr3AblbjxEVlwCwVhg7BoqqCIjy/gAyyFWNlYBOxsfH0KuxgQGwXRUkGuL2S7i1barGAgiwbdW93SnGOc4BonPiKahf3DwXFmuP/fPM4ZlvmlTxAhCBdzHnEQWYiv7Mr4C3NeuVYhQYDPzOUUQgDLBQGcLHNhvQK8DACPx8PTxiqVyvISG43GbyaT6Qfpn06n0m63e/tPAPF4vJ1MJu8kEsnWTCkWi1yr1RKGw+GDRqPBOTfr44vFQvD7/Q/lcpmaaVQAr9fLp1IpO22c47hGOBz+MB6PH+Vy+VYDAL8qlUoGtVotzOfzq4MAgsHgE/6KojiQyWR/bKVSqbSszHFM8Pl8z1YK48JsNltCOBwOnrYLO+8AAIjb+nHbycoTiUQfDJ7tFq4YAHiVSmXBxcD41u8flQU8z7fhzO0r83atVns3Go3u9Xr9x0O/RQXo9/tsIBBg6vX606a52Wz+bZ7P5/WwG29gxSJzhKgA6XTaDoFNF+krFAocmC//4yWEcSf2wTm7mCO19xFgSsKOLI16vV7b7XY7mRNoLwA0JymJ5uQIzgIAuX5PzDElT2m+E8BqtQ4ymcx7Yq7T6a6ZE4sKgOadTucaCwkxp1UzlEKh0GDxIXOwDWHAdi6Xe3swQDQa/Q7mywoolUpvsaptymazDWKxmBHTlWXZm405BFZoNpuGgwEmk4mE2SGtVivii4f1AO7J3ZopkQCQj7Ar1FeRChCJRJzVapX6DKNIfSc1Ax+wtQWQ55h6bH8FWDfYV4fO3wlwDr0C/BcADYiTPCxHqIEA2QsCZAkAKnRGkMbKN/sTX5YHPQ1e7SkAAAAASUVORK5CYII=', }, }, diff --git a/server/src/core/@generated/workspace/workspace-count-aggregate.input.ts b/server/src/core/@generated/workspace/workspace-count-aggregate.input.ts index f1d672e1b..3037aa5b2 100644 --- a/server/src/core/@generated/workspace/workspace-count-aggregate.input.ts +++ b/server/src/core/@generated/workspace/workspace-count-aggregate.input.ts @@ -17,6 +17,9 @@ export class WorkspaceCountAggregateInput { @Field(() => Boolean, {nullable:true}) logo?: true; + @Field(() => Boolean, {nullable:true}) + inviteHash?: true; + @HideField() deletedAt?: true; diff --git a/server/src/core/@generated/workspace/workspace-count-aggregate.output.ts b/server/src/core/@generated/workspace/workspace-count-aggregate.output.ts index df84d04c0..8cdb999f4 100644 --- a/server/src/core/@generated/workspace/workspace-count-aggregate.output.ts +++ b/server/src/core/@generated/workspace/workspace-count-aggregate.output.ts @@ -18,6 +18,9 @@ export class WorkspaceCountAggregate { @Field(() => Int, {nullable:false}) logo!: number; + @Field(() => Int, {nullable:false}) + inviteHash!: number; + @HideField() deletedAt!: number; diff --git a/server/src/core/@generated/workspace/workspace-count-order-by-aggregate.input.ts b/server/src/core/@generated/workspace/workspace-count-order-by-aggregate.input.ts index 8ae33e3e0..5e64ba8f3 100644 --- a/server/src/core/@generated/workspace/workspace-count-order-by-aggregate.input.ts +++ b/server/src/core/@generated/workspace/workspace-count-order-by-aggregate.input.ts @@ -18,6 +18,9 @@ export class WorkspaceCountOrderByAggregateInput { @Field(() => SortOrder, {nullable:true}) logo?: keyof typeof SortOrder; + @Field(() => SortOrder, {nullable:true}) + inviteHash?: keyof typeof SortOrder; + @HideField() deletedAt?: keyof typeof SortOrder; diff --git a/server/src/core/@generated/workspace/workspace-create-many.input.ts b/server/src/core/@generated/workspace/workspace-create-many.input.ts index 1059d52aa..ea966b3c9 100644 --- a/server/src/core/@generated/workspace/workspace-create-many.input.ts +++ b/server/src/core/@generated/workspace/workspace-create-many.input.ts @@ -26,6 +26,11 @@ export class WorkspaceCreateManyInput { @Validator.IsOptional() logo?: string; + @Field(() => String, {nullable:true}) + @Validator.IsString() + @Validator.IsOptional() + inviteHash?: string; + @HideField() deletedAt?: Date | string; diff --git a/server/src/core/@generated/workspace/workspace-create-without-comment-threads.input.ts b/server/src/core/@generated/workspace/workspace-create-without-comment-threads.input.ts index 87b098ca1..a4f5b8811 100644 --- a/server/src/core/@generated/workspace/workspace-create-without-comment-threads.input.ts +++ b/server/src/core/@generated/workspace/workspace-create-without-comment-threads.input.ts @@ -33,6 +33,11 @@ export class WorkspaceCreateWithoutCommentThreadsInput { @Validator.IsOptional() logo?: string; + @Field(() => String, {nullable:true}) + @Validator.IsString() + @Validator.IsOptional() + inviteHash?: string; + @HideField() deletedAt?: Date | string; diff --git a/server/src/core/@generated/workspace/workspace-create-without-comments.input.ts b/server/src/core/@generated/workspace/workspace-create-without-comments.input.ts index 9edda0694..1c5bf3177 100644 --- a/server/src/core/@generated/workspace/workspace-create-without-comments.input.ts +++ b/server/src/core/@generated/workspace/workspace-create-without-comments.input.ts @@ -33,6 +33,11 @@ export class WorkspaceCreateWithoutCommentsInput { @Validator.IsOptional() logo?: string; + @Field(() => String, {nullable:true}) + @Validator.IsString() + @Validator.IsOptional() + inviteHash?: string; + @HideField() deletedAt?: Date | string; diff --git a/server/src/core/@generated/workspace/workspace-create-without-companies.input.ts b/server/src/core/@generated/workspace/workspace-create-without-companies.input.ts index 04fda1215..82e08c92b 100644 --- a/server/src/core/@generated/workspace/workspace-create-without-companies.input.ts +++ b/server/src/core/@generated/workspace/workspace-create-without-companies.input.ts @@ -33,6 +33,11 @@ export class WorkspaceCreateWithoutCompaniesInput { @Validator.IsOptional() logo?: string; + @Field(() => String, {nullable:true}) + @Validator.IsString() + @Validator.IsOptional() + inviteHash?: string; + @HideField() deletedAt?: Date | string; diff --git a/server/src/core/@generated/workspace/workspace-create-without-people.input.ts b/server/src/core/@generated/workspace/workspace-create-without-people.input.ts index 9930dd133..a8e6a9e7e 100644 --- a/server/src/core/@generated/workspace/workspace-create-without-people.input.ts +++ b/server/src/core/@generated/workspace/workspace-create-without-people.input.ts @@ -33,6 +33,11 @@ export class WorkspaceCreateWithoutPeopleInput { @Validator.IsOptional() logo?: string; + @Field(() => String, {nullable:true}) + @Validator.IsString() + @Validator.IsOptional() + inviteHash?: string; + @HideField() deletedAt?: Date | string; diff --git a/server/src/core/@generated/workspace/workspace-create-without-pipeline-progresses.input.ts b/server/src/core/@generated/workspace/workspace-create-without-pipeline-progresses.input.ts index e96a710c7..acb95644e 100644 --- a/server/src/core/@generated/workspace/workspace-create-without-pipeline-progresses.input.ts +++ b/server/src/core/@generated/workspace/workspace-create-without-pipeline-progresses.input.ts @@ -33,6 +33,11 @@ export class WorkspaceCreateWithoutPipelineProgressesInput { @Validator.IsOptional() logo?: string; + @Field(() => String, {nullable:true}) + @Validator.IsString() + @Validator.IsOptional() + inviteHash?: string; + @HideField() deletedAt?: Date | string; diff --git a/server/src/core/@generated/workspace/workspace-create-without-pipeline-stages.input.ts b/server/src/core/@generated/workspace/workspace-create-without-pipeline-stages.input.ts index 9bdb786e3..ba4be9ed7 100644 --- a/server/src/core/@generated/workspace/workspace-create-without-pipeline-stages.input.ts +++ b/server/src/core/@generated/workspace/workspace-create-without-pipeline-stages.input.ts @@ -33,6 +33,11 @@ export class WorkspaceCreateWithoutPipelineStagesInput { @Validator.IsOptional() logo?: string; + @Field(() => String, {nullable:true}) + @Validator.IsString() + @Validator.IsOptional() + inviteHash?: string; + @HideField() deletedAt?: Date | string; diff --git a/server/src/core/@generated/workspace/workspace-create-without-pipelines.input.ts b/server/src/core/@generated/workspace/workspace-create-without-pipelines.input.ts index 61a433f2c..e3f462522 100644 --- a/server/src/core/@generated/workspace/workspace-create-without-pipelines.input.ts +++ b/server/src/core/@generated/workspace/workspace-create-without-pipelines.input.ts @@ -33,6 +33,11 @@ export class WorkspaceCreateWithoutPipelinesInput { @Validator.IsOptional() logo?: string; + @Field(() => String, {nullable:true}) + @Validator.IsString() + @Validator.IsOptional() + inviteHash?: string; + @HideField() deletedAt?: Date | string; diff --git a/server/src/core/@generated/workspace/workspace-create-without-workspace-member.input.ts b/server/src/core/@generated/workspace/workspace-create-without-workspace-member.input.ts index 3687f938e..0a872a25b 100644 --- a/server/src/core/@generated/workspace/workspace-create-without-workspace-member.input.ts +++ b/server/src/core/@generated/workspace/workspace-create-without-workspace-member.input.ts @@ -33,6 +33,11 @@ export class WorkspaceCreateWithoutWorkspaceMemberInput { @Validator.IsOptional() logo?: string; + @Field(() => String, {nullable:true}) + @Validator.IsString() + @Validator.IsOptional() + inviteHash?: string; + @HideField() deletedAt?: Date | string; diff --git a/server/src/core/@generated/workspace/workspace-create.input.ts b/server/src/core/@generated/workspace/workspace-create.input.ts index c700fcb0f..db54d1f4d 100644 --- a/server/src/core/@generated/workspace/workspace-create.input.ts +++ b/server/src/core/@generated/workspace/workspace-create.input.ts @@ -34,6 +34,11 @@ export class WorkspaceCreateInput { @Validator.IsOptional() logo?: string; + @Field(() => String, {nullable:true}) + @Validator.IsString() + @Validator.IsOptional() + inviteHash?: string; + @HideField() deletedAt?: Date | string; diff --git a/server/src/core/@generated/workspace/workspace-group-by.output.ts b/server/src/core/@generated/workspace/workspace-group-by.output.ts index 0acfc669c..5b635fe67 100644 --- a/server/src/core/@generated/workspace/workspace-group-by.output.ts +++ b/server/src/core/@generated/workspace/workspace-group-by.output.ts @@ -29,6 +29,11 @@ export class WorkspaceGroupBy { @Validator.IsOptional() logo?: string; + @Field(() => String, {nullable:true}) + @Validator.IsString() + @Validator.IsOptional() + inviteHash?: string; + @HideField() deletedAt?: Date | string; diff --git a/server/src/core/@generated/workspace/workspace-max-aggregate.input.ts b/server/src/core/@generated/workspace/workspace-max-aggregate.input.ts index 022e0dc7e..6e417e976 100644 --- a/server/src/core/@generated/workspace/workspace-max-aggregate.input.ts +++ b/server/src/core/@generated/workspace/workspace-max-aggregate.input.ts @@ -17,6 +17,9 @@ export class WorkspaceMaxAggregateInput { @Field(() => Boolean, {nullable:true}) logo?: true; + @Field(() => Boolean, {nullable:true}) + inviteHash?: true; + @HideField() deletedAt?: true; diff --git a/server/src/core/@generated/workspace/workspace-max-aggregate.output.ts b/server/src/core/@generated/workspace/workspace-max-aggregate.output.ts index df11a248b..b553530e3 100644 --- a/server/src/core/@generated/workspace/workspace-max-aggregate.output.ts +++ b/server/src/core/@generated/workspace/workspace-max-aggregate.output.ts @@ -26,6 +26,11 @@ export class WorkspaceMaxAggregate { @Validator.IsOptional() logo?: string; + @Field(() => String, {nullable:true}) + @Validator.IsString() + @Validator.IsOptional() + inviteHash?: string; + @HideField() deletedAt?: Date | string; diff --git a/server/src/core/@generated/workspace/workspace-max-order-by-aggregate.input.ts b/server/src/core/@generated/workspace/workspace-max-order-by-aggregate.input.ts index c61f48b08..267d586a4 100644 --- a/server/src/core/@generated/workspace/workspace-max-order-by-aggregate.input.ts +++ b/server/src/core/@generated/workspace/workspace-max-order-by-aggregate.input.ts @@ -18,6 +18,9 @@ export class WorkspaceMaxOrderByAggregateInput { @Field(() => SortOrder, {nullable:true}) logo?: keyof typeof SortOrder; + @Field(() => SortOrder, {nullable:true}) + inviteHash?: keyof typeof SortOrder; + @HideField() deletedAt?: keyof typeof SortOrder; diff --git a/server/src/core/@generated/workspace/workspace-min-aggregate.input.ts b/server/src/core/@generated/workspace/workspace-min-aggregate.input.ts index ce6803ca1..62f0b0409 100644 --- a/server/src/core/@generated/workspace/workspace-min-aggregate.input.ts +++ b/server/src/core/@generated/workspace/workspace-min-aggregate.input.ts @@ -17,6 +17,9 @@ export class WorkspaceMinAggregateInput { @Field(() => Boolean, {nullable:true}) logo?: true; + @Field(() => Boolean, {nullable:true}) + inviteHash?: true; + @HideField() deletedAt?: true; diff --git a/server/src/core/@generated/workspace/workspace-min-aggregate.output.ts b/server/src/core/@generated/workspace/workspace-min-aggregate.output.ts index c761b7d10..78ec25d71 100644 --- a/server/src/core/@generated/workspace/workspace-min-aggregate.output.ts +++ b/server/src/core/@generated/workspace/workspace-min-aggregate.output.ts @@ -26,6 +26,11 @@ export class WorkspaceMinAggregate { @Validator.IsOptional() logo?: string; + @Field(() => String, {nullable:true}) + @Validator.IsString() + @Validator.IsOptional() + inviteHash?: string; + @HideField() deletedAt?: Date | string; diff --git a/server/src/core/@generated/workspace/workspace-min-order-by-aggregate.input.ts b/server/src/core/@generated/workspace/workspace-min-order-by-aggregate.input.ts index 6834c1f8d..e98cfa129 100644 --- a/server/src/core/@generated/workspace/workspace-min-order-by-aggregate.input.ts +++ b/server/src/core/@generated/workspace/workspace-min-order-by-aggregate.input.ts @@ -18,6 +18,9 @@ export class WorkspaceMinOrderByAggregateInput { @Field(() => SortOrder, {nullable:true}) logo?: keyof typeof SortOrder; + @Field(() => SortOrder, {nullable:true}) + inviteHash?: keyof typeof SortOrder; + @HideField() deletedAt?: keyof typeof SortOrder; diff --git a/server/src/core/@generated/workspace/workspace-order-by-with-aggregation.input.ts b/server/src/core/@generated/workspace/workspace-order-by-with-aggregation.input.ts index ae5e311d8..3e27b600a 100644 --- a/server/src/core/@generated/workspace/workspace-order-by-with-aggregation.input.ts +++ b/server/src/core/@generated/workspace/workspace-order-by-with-aggregation.input.ts @@ -21,6 +21,9 @@ export class WorkspaceOrderByWithAggregationInput { @Field(() => SortOrder, {nullable:true}) logo?: keyof typeof SortOrder; + @Field(() => SortOrder, {nullable:true}) + inviteHash?: keyof typeof SortOrder; + @HideField() deletedAt?: keyof typeof SortOrder; diff --git a/server/src/core/@generated/workspace/workspace-order-by-with-relation.input.ts b/server/src/core/@generated/workspace/workspace-order-by-with-relation.input.ts index 95964e2ac..3c6176ca6 100644 --- a/server/src/core/@generated/workspace/workspace-order-by-with-relation.input.ts +++ b/server/src/core/@generated/workspace/workspace-order-by-with-relation.input.ts @@ -26,6 +26,9 @@ export class WorkspaceOrderByWithRelationInput { @Field(() => SortOrder, {nullable:true}) logo?: keyof typeof SortOrder; + @Field(() => SortOrder, {nullable:true}) + inviteHash?: keyof typeof SortOrder; + @HideField() deletedAt?: keyof typeof SortOrder; diff --git a/server/src/core/@generated/workspace/workspace-scalar-field.enum.ts b/server/src/core/@generated/workspace/workspace-scalar-field.enum.ts index 02cf89b8d..c4cde6d11 100644 --- a/server/src/core/@generated/workspace/workspace-scalar-field.enum.ts +++ b/server/src/core/@generated/workspace/workspace-scalar-field.enum.ts @@ -5,6 +5,7 @@ export enum WorkspaceScalarFieldEnum { domainName = "domainName", displayName = "displayName", logo = "logo", + inviteHash = "inviteHash", deletedAt = "deletedAt", createdAt = "createdAt", updatedAt = "updatedAt" diff --git a/server/src/core/@generated/workspace/workspace-scalar-where-with-aggregates.input.ts b/server/src/core/@generated/workspace/workspace-scalar-where-with-aggregates.input.ts index 7a8b4f7d1..b5b44e90f 100644 --- a/server/src/core/@generated/workspace/workspace-scalar-where-with-aggregates.input.ts +++ b/server/src/core/@generated/workspace/workspace-scalar-where-with-aggregates.input.ts @@ -30,6 +30,9 @@ export class WorkspaceScalarWhereWithAggregatesInput { @Field(() => StringNullableWithAggregatesFilter, {nullable:true}) logo?: StringNullableWithAggregatesFilter; + @Field(() => StringNullableWithAggregatesFilter, {nullable:true}) + inviteHash?: StringNullableWithAggregatesFilter; + @HideField() deletedAt?: DateTimeNullableWithAggregatesFilter; diff --git a/server/src/core/@generated/workspace/workspace-unchecked-create-without-comment-threads.input.ts b/server/src/core/@generated/workspace/workspace-unchecked-create-without-comment-threads.input.ts index c88813d0b..19d067d7b 100644 --- a/server/src/core/@generated/workspace/workspace-unchecked-create-without-comment-threads.input.ts +++ b/server/src/core/@generated/workspace/workspace-unchecked-create-without-comment-threads.input.ts @@ -33,6 +33,11 @@ export class WorkspaceUncheckedCreateWithoutCommentThreadsInput { @Validator.IsOptional() logo?: string; + @Field(() => String, {nullable:true}) + @Validator.IsString() + @Validator.IsOptional() + inviteHash?: string; + @HideField() deletedAt?: Date | string; diff --git a/server/src/core/@generated/workspace/workspace-unchecked-create-without-comments.input.ts b/server/src/core/@generated/workspace/workspace-unchecked-create-without-comments.input.ts index 12b5fad61..f7d33b930 100644 --- a/server/src/core/@generated/workspace/workspace-unchecked-create-without-comments.input.ts +++ b/server/src/core/@generated/workspace/workspace-unchecked-create-without-comments.input.ts @@ -33,6 +33,11 @@ export class WorkspaceUncheckedCreateWithoutCommentsInput { @Validator.IsOptional() logo?: string; + @Field(() => String, {nullable:true}) + @Validator.IsString() + @Validator.IsOptional() + inviteHash?: string; + @HideField() deletedAt?: Date | string; diff --git a/server/src/core/@generated/workspace/workspace-unchecked-create-without-companies.input.ts b/server/src/core/@generated/workspace/workspace-unchecked-create-without-companies.input.ts index bab9da2b7..1fd58c868 100644 --- a/server/src/core/@generated/workspace/workspace-unchecked-create-without-companies.input.ts +++ b/server/src/core/@generated/workspace/workspace-unchecked-create-without-companies.input.ts @@ -33,6 +33,11 @@ export class WorkspaceUncheckedCreateWithoutCompaniesInput { @Validator.IsOptional() logo?: string; + @Field(() => String, {nullable:true}) + @Validator.IsString() + @Validator.IsOptional() + inviteHash?: string; + @HideField() deletedAt?: Date | string; diff --git a/server/src/core/@generated/workspace/workspace-unchecked-create-without-people.input.ts b/server/src/core/@generated/workspace/workspace-unchecked-create-without-people.input.ts index 34bf078af..8e8705569 100644 --- a/server/src/core/@generated/workspace/workspace-unchecked-create-without-people.input.ts +++ b/server/src/core/@generated/workspace/workspace-unchecked-create-without-people.input.ts @@ -33,6 +33,11 @@ export class WorkspaceUncheckedCreateWithoutPeopleInput { @Validator.IsOptional() logo?: string; + @Field(() => String, {nullable:true}) + @Validator.IsString() + @Validator.IsOptional() + inviteHash?: string; + @HideField() deletedAt?: Date | string; diff --git a/server/src/core/@generated/workspace/workspace-unchecked-create-without-pipeline-progresses.input.ts b/server/src/core/@generated/workspace/workspace-unchecked-create-without-pipeline-progresses.input.ts index f1f4a0df7..398670e2e 100644 --- a/server/src/core/@generated/workspace/workspace-unchecked-create-without-pipeline-progresses.input.ts +++ b/server/src/core/@generated/workspace/workspace-unchecked-create-without-pipeline-progresses.input.ts @@ -33,6 +33,11 @@ export class WorkspaceUncheckedCreateWithoutPipelineProgressesInput { @Validator.IsOptional() logo?: string; + @Field(() => String, {nullable:true}) + @Validator.IsString() + @Validator.IsOptional() + inviteHash?: string; + @HideField() deletedAt?: Date | string; diff --git a/server/src/core/@generated/workspace/workspace-unchecked-create-without-pipeline-stages.input.ts b/server/src/core/@generated/workspace/workspace-unchecked-create-without-pipeline-stages.input.ts index 7d004620b..ae0ec021d 100644 --- a/server/src/core/@generated/workspace/workspace-unchecked-create-without-pipeline-stages.input.ts +++ b/server/src/core/@generated/workspace/workspace-unchecked-create-without-pipeline-stages.input.ts @@ -33,6 +33,11 @@ export class WorkspaceUncheckedCreateWithoutPipelineStagesInput { @Validator.IsOptional() logo?: string; + @Field(() => String, {nullable:true}) + @Validator.IsString() + @Validator.IsOptional() + inviteHash?: string; + @HideField() deletedAt?: Date | string; diff --git a/server/src/core/@generated/workspace/workspace-unchecked-create-without-pipelines.input.ts b/server/src/core/@generated/workspace/workspace-unchecked-create-without-pipelines.input.ts index afa376a4b..a58e27443 100644 --- a/server/src/core/@generated/workspace/workspace-unchecked-create-without-pipelines.input.ts +++ b/server/src/core/@generated/workspace/workspace-unchecked-create-without-pipelines.input.ts @@ -33,6 +33,11 @@ export class WorkspaceUncheckedCreateWithoutPipelinesInput { @Validator.IsOptional() logo?: string; + @Field(() => String, {nullable:true}) + @Validator.IsString() + @Validator.IsOptional() + inviteHash?: string; + @HideField() deletedAt?: Date | string; diff --git a/server/src/core/@generated/workspace/workspace-unchecked-create-without-workspace-member.input.ts b/server/src/core/@generated/workspace/workspace-unchecked-create-without-workspace-member.input.ts index 89ab96a85..e5b0244e0 100644 --- a/server/src/core/@generated/workspace/workspace-unchecked-create-without-workspace-member.input.ts +++ b/server/src/core/@generated/workspace/workspace-unchecked-create-without-workspace-member.input.ts @@ -33,6 +33,11 @@ export class WorkspaceUncheckedCreateWithoutWorkspaceMemberInput { @Validator.IsOptional() logo?: string; + @Field(() => String, {nullable:true}) + @Validator.IsString() + @Validator.IsOptional() + inviteHash?: string; + @HideField() deletedAt?: Date | string; diff --git a/server/src/core/@generated/workspace/workspace-unchecked-create.input.ts b/server/src/core/@generated/workspace/workspace-unchecked-create.input.ts index 737c3b918..dc6e31b2a 100644 --- a/server/src/core/@generated/workspace/workspace-unchecked-create.input.ts +++ b/server/src/core/@generated/workspace/workspace-unchecked-create.input.ts @@ -34,6 +34,11 @@ export class WorkspaceUncheckedCreateInput { @Validator.IsOptional() logo?: string; + @Field(() => String, {nullable:true}) + @Validator.IsString() + @Validator.IsOptional() + inviteHash?: string; + @HideField() deletedAt?: Date | string; diff --git a/server/src/core/@generated/workspace/workspace-unchecked-update-many.input.ts b/server/src/core/@generated/workspace/workspace-unchecked-update-many.input.ts index 396e39d88..126c92f66 100644 --- a/server/src/core/@generated/workspace/workspace-unchecked-update-many.input.ts +++ b/server/src/core/@generated/workspace/workspace-unchecked-update-many.input.ts @@ -21,6 +21,9 @@ export class WorkspaceUncheckedUpdateManyInput { @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) logo?: NullableStringFieldUpdateOperationsInput; + @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) + inviteHash?: NullableStringFieldUpdateOperationsInput; + @HideField() deletedAt?: NullableDateTimeFieldUpdateOperationsInput; diff --git a/server/src/core/@generated/workspace/workspace-unchecked-update-without-comment-threads.input.ts b/server/src/core/@generated/workspace/workspace-unchecked-update-without-comment-threads.input.ts index eff6cb2a9..bef0926e0 100644 --- a/server/src/core/@generated/workspace/workspace-unchecked-update-without-comment-threads.input.ts +++ b/server/src/core/@generated/workspace/workspace-unchecked-update-without-comment-threads.input.ts @@ -28,6 +28,9 @@ export class WorkspaceUncheckedUpdateWithoutCommentThreadsInput { @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) logo?: NullableStringFieldUpdateOperationsInput; + @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) + inviteHash?: NullableStringFieldUpdateOperationsInput; + @HideField() deletedAt?: NullableDateTimeFieldUpdateOperationsInput; diff --git a/server/src/core/@generated/workspace/workspace-unchecked-update-without-comments.input.ts b/server/src/core/@generated/workspace/workspace-unchecked-update-without-comments.input.ts index 8859f5025..94e7575ef 100644 --- a/server/src/core/@generated/workspace/workspace-unchecked-update-without-comments.input.ts +++ b/server/src/core/@generated/workspace/workspace-unchecked-update-without-comments.input.ts @@ -28,6 +28,9 @@ export class WorkspaceUncheckedUpdateWithoutCommentsInput { @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) logo?: NullableStringFieldUpdateOperationsInput; + @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) + inviteHash?: NullableStringFieldUpdateOperationsInput; + @HideField() deletedAt?: NullableDateTimeFieldUpdateOperationsInput; diff --git a/server/src/core/@generated/workspace/workspace-unchecked-update-without-companies.input.ts b/server/src/core/@generated/workspace/workspace-unchecked-update-without-companies.input.ts index 87c8ff33b..73ba530d3 100644 --- a/server/src/core/@generated/workspace/workspace-unchecked-update-without-companies.input.ts +++ b/server/src/core/@generated/workspace/workspace-unchecked-update-without-companies.input.ts @@ -28,6 +28,9 @@ export class WorkspaceUncheckedUpdateWithoutCompaniesInput { @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) logo?: NullableStringFieldUpdateOperationsInput; + @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) + inviteHash?: NullableStringFieldUpdateOperationsInput; + @HideField() deletedAt?: NullableDateTimeFieldUpdateOperationsInput; diff --git a/server/src/core/@generated/workspace/workspace-unchecked-update-without-people.input.ts b/server/src/core/@generated/workspace/workspace-unchecked-update-without-people.input.ts index 92fff10aa..9b2bcdf87 100644 --- a/server/src/core/@generated/workspace/workspace-unchecked-update-without-people.input.ts +++ b/server/src/core/@generated/workspace/workspace-unchecked-update-without-people.input.ts @@ -28,6 +28,9 @@ export class WorkspaceUncheckedUpdateWithoutPeopleInput { @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) logo?: NullableStringFieldUpdateOperationsInput; + @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) + inviteHash?: NullableStringFieldUpdateOperationsInput; + @HideField() deletedAt?: NullableDateTimeFieldUpdateOperationsInput; diff --git a/server/src/core/@generated/workspace/workspace-unchecked-update-without-pipeline-progresses.input.ts b/server/src/core/@generated/workspace/workspace-unchecked-update-without-pipeline-progresses.input.ts index e8d6bcdf1..4c230a043 100644 --- a/server/src/core/@generated/workspace/workspace-unchecked-update-without-pipeline-progresses.input.ts +++ b/server/src/core/@generated/workspace/workspace-unchecked-update-without-pipeline-progresses.input.ts @@ -28,6 +28,9 @@ export class WorkspaceUncheckedUpdateWithoutPipelineProgressesInput { @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) logo?: NullableStringFieldUpdateOperationsInput; + @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) + inviteHash?: NullableStringFieldUpdateOperationsInput; + @HideField() deletedAt?: NullableDateTimeFieldUpdateOperationsInput; diff --git a/server/src/core/@generated/workspace/workspace-unchecked-update-without-pipeline-stages.input.ts b/server/src/core/@generated/workspace/workspace-unchecked-update-without-pipeline-stages.input.ts index fee46c1c3..80c1a4aa6 100644 --- a/server/src/core/@generated/workspace/workspace-unchecked-update-without-pipeline-stages.input.ts +++ b/server/src/core/@generated/workspace/workspace-unchecked-update-without-pipeline-stages.input.ts @@ -28,6 +28,9 @@ export class WorkspaceUncheckedUpdateWithoutPipelineStagesInput { @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) logo?: NullableStringFieldUpdateOperationsInput; + @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) + inviteHash?: NullableStringFieldUpdateOperationsInput; + @HideField() deletedAt?: NullableDateTimeFieldUpdateOperationsInput; diff --git a/server/src/core/@generated/workspace/workspace-unchecked-update-without-pipelines.input.ts b/server/src/core/@generated/workspace/workspace-unchecked-update-without-pipelines.input.ts index 06f45c274..23ff8f266 100644 --- a/server/src/core/@generated/workspace/workspace-unchecked-update-without-pipelines.input.ts +++ b/server/src/core/@generated/workspace/workspace-unchecked-update-without-pipelines.input.ts @@ -28,6 +28,9 @@ export class WorkspaceUncheckedUpdateWithoutPipelinesInput { @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) logo?: NullableStringFieldUpdateOperationsInput; + @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) + inviteHash?: NullableStringFieldUpdateOperationsInput; + @HideField() deletedAt?: NullableDateTimeFieldUpdateOperationsInput; diff --git a/server/src/core/@generated/workspace/workspace-unchecked-update-without-workspace-member.input.ts b/server/src/core/@generated/workspace/workspace-unchecked-update-without-workspace-member.input.ts index 55096ed40..3007224c8 100644 --- a/server/src/core/@generated/workspace/workspace-unchecked-update-without-workspace-member.input.ts +++ b/server/src/core/@generated/workspace/workspace-unchecked-update-without-workspace-member.input.ts @@ -28,6 +28,9 @@ export class WorkspaceUncheckedUpdateWithoutWorkspaceMemberInput { @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) logo?: NullableStringFieldUpdateOperationsInput; + @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) + inviteHash?: NullableStringFieldUpdateOperationsInput; + @HideField() deletedAt?: NullableDateTimeFieldUpdateOperationsInput; diff --git a/server/src/core/@generated/workspace/workspace-unchecked-update.input.ts b/server/src/core/@generated/workspace/workspace-unchecked-update.input.ts index c7d8a0dd8..e063135bb 100644 --- a/server/src/core/@generated/workspace/workspace-unchecked-update.input.ts +++ b/server/src/core/@generated/workspace/workspace-unchecked-update.input.ts @@ -29,6 +29,9 @@ export class WorkspaceUncheckedUpdateInput { @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) logo?: NullableStringFieldUpdateOperationsInput; + @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) + inviteHash?: NullableStringFieldUpdateOperationsInput; + @HideField() deletedAt?: NullableDateTimeFieldUpdateOperationsInput; diff --git a/server/src/core/@generated/workspace/workspace-update-many-mutation.input.ts b/server/src/core/@generated/workspace/workspace-update-many-mutation.input.ts index bd659d9f2..da2cc5e99 100644 --- a/server/src/core/@generated/workspace/workspace-update-many-mutation.input.ts +++ b/server/src/core/@generated/workspace/workspace-update-many-mutation.input.ts @@ -21,6 +21,9 @@ export class WorkspaceUpdateManyMutationInput { @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) logo?: NullableStringFieldUpdateOperationsInput; + @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) + inviteHash?: NullableStringFieldUpdateOperationsInput; + @HideField() deletedAt?: NullableDateTimeFieldUpdateOperationsInput; diff --git a/server/src/core/@generated/workspace/workspace-update-without-comment-threads.input.ts b/server/src/core/@generated/workspace/workspace-update-without-comment-threads.input.ts index 6e903e8fc..46d3b7a91 100644 --- a/server/src/core/@generated/workspace/workspace-update-without-comment-threads.input.ts +++ b/server/src/core/@generated/workspace/workspace-update-without-comment-threads.input.ts @@ -28,6 +28,9 @@ export class WorkspaceUpdateWithoutCommentThreadsInput { @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) logo?: NullableStringFieldUpdateOperationsInput; + @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) + inviteHash?: NullableStringFieldUpdateOperationsInput; + @HideField() deletedAt?: NullableDateTimeFieldUpdateOperationsInput; diff --git a/server/src/core/@generated/workspace/workspace-update-without-comments.input.ts b/server/src/core/@generated/workspace/workspace-update-without-comments.input.ts index e36bafc48..f7e052cb3 100644 --- a/server/src/core/@generated/workspace/workspace-update-without-comments.input.ts +++ b/server/src/core/@generated/workspace/workspace-update-without-comments.input.ts @@ -28,6 +28,9 @@ export class WorkspaceUpdateWithoutCommentsInput { @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) logo?: NullableStringFieldUpdateOperationsInput; + @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) + inviteHash?: NullableStringFieldUpdateOperationsInput; + @HideField() deletedAt?: NullableDateTimeFieldUpdateOperationsInput; diff --git a/server/src/core/@generated/workspace/workspace-update-without-companies.input.ts b/server/src/core/@generated/workspace/workspace-update-without-companies.input.ts index ee0e8e299..594005d65 100644 --- a/server/src/core/@generated/workspace/workspace-update-without-companies.input.ts +++ b/server/src/core/@generated/workspace/workspace-update-without-companies.input.ts @@ -28,6 +28,9 @@ export class WorkspaceUpdateWithoutCompaniesInput { @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) logo?: NullableStringFieldUpdateOperationsInput; + @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) + inviteHash?: NullableStringFieldUpdateOperationsInput; + @HideField() deletedAt?: NullableDateTimeFieldUpdateOperationsInput; diff --git a/server/src/core/@generated/workspace/workspace-update-without-people.input.ts b/server/src/core/@generated/workspace/workspace-update-without-people.input.ts index 457b83ae3..728003e06 100644 --- a/server/src/core/@generated/workspace/workspace-update-without-people.input.ts +++ b/server/src/core/@generated/workspace/workspace-update-without-people.input.ts @@ -28,6 +28,9 @@ export class WorkspaceUpdateWithoutPeopleInput { @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) logo?: NullableStringFieldUpdateOperationsInput; + @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) + inviteHash?: NullableStringFieldUpdateOperationsInput; + @HideField() deletedAt?: NullableDateTimeFieldUpdateOperationsInput; diff --git a/server/src/core/@generated/workspace/workspace-update-without-pipeline-progresses.input.ts b/server/src/core/@generated/workspace/workspace-update-without-pipeline-progresses.input.ts index 8ccddaeb1..ad4fc98d0 100644 --- a/server/src/core/@generated/workspace/workspace-update-without-pipeline-progresses.input.ts +++ b/server/src/core/@generated/workspace/workspace-update-without-pipeline-progresses.input.ts @@ -28,6 +28,9 @@ export class WorkspaceUpdateWithoutPipelineProgressesInput { @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) logo?: NullableStringFieldUpdateOperationsInput; + @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) + inviteHash?: NullableStringFieldUpdateOperationsInput; + @HideField() deletedAt?: NullableDateTimeFieldUpdateOperationsInput; diff --git a/server/src/core/@generated/workspace/workspace-update-without-pipeline-stages.input.ts b/server/src/core/@generated/workspace/workspace-update-without-pipeline-stages.input.ts index 12dba5851..4a5d7c718 100644 --- a/server/src/core/@generated/workspace/workspace-update-without-pipeline-stages.input.ts +++ b/server/src/core/@generated/workspace/workspace-update-without-pipeline-stages.input.ts @@ -28,6 +28,9 @@ export class WorkspaceUpdateWithoutPipelineStagesInput { @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) logo?: NullableStringFieldUpdateOperationsInput; + @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) + inviteHash?: NullableStringFieldUpdateOperationsInput; + @HideField() deletedAt?: NullableDateTimeFieldUpdateOperationsInput; diff --git a/server/src/core/@generated/workspace/workspace-update-without-pipelines.input.ts b/server/src/core/@generated/workspace/workspace-update-without-pipelines.input.ts index d61862282..f0153992f 100644 --- a/server/src/core/@generated/workspace/workspace-update-without-pipelines.input.ts +++ b/server/src/core/@generated/workspace/workspace-update-without-pipelines.input.ts @@ -28,6 +28,9 @@ export class WorkspaceUpdateWithoutPipelinesInput { @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) logo?: NullableStringFieldUpdateOperationsInput; + @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) + inviteHash?: NullableStringFieldUpdateOperationsInput; + @HideField() deletedAt?: NullableDateTimeFieldUpdateOperationsInput; diff --git a/server/src/core/@generated/workspace/workspace-update-without-workspace-member.input.ts b/server/src/core/@generated/workspace/workspace-update-without-workspace-member.input.ts index 446fdcd6c..29088634f 100644 --- a/server/src/core/@generated/workspace/workspace-update-without-workspace-member.input.ts +++ b/server/src/core/@generated/workspace/workspace-update-without-workspace-member.input.ts @@ -28,6 +28,9 @@ export class WorkspaceUpdateWithoutWorkspaceMemberInput { @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) logo?: NullableStringFieldUpdateOperationsInput; + @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) + inviteHash?: NullableStringFieldUpdateOperationsInput; + @HideField() deletedAt?: NullableDateTimeFieldUpdateOperationsInput; diff --git a/server/src/core/@generated/workspace/workspace-update.input.ts b/server/src/core/@generated/workspace/workspace-update.input.ts index 7276fb9ff..ea5fbdb0c 100644 --- a/server/src/core/@generated/workspace/workspace-update.input.ts +++ b/server/src/core/@generated/workspace/workspace-update.input.ts @@ -29,6 +29,9 @@ export class WorkspaceUpdateInput { @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) logo?: NullableStringFieldUpdateOperationsInput; + @Field(() => NullableStringFieldUpdateOperationsInput, {nullable:true}) + inviteHash?: NullableStringFieldUpdateOperationsInput; + @HideField() deletedAt?: NullableDateTimeFieldUpdateOperationsInput; diff --git a/server/src/core/@generated/workspace/workspace-where.input.ts b/server/src/core/@generated/workspace/workspace-where.input.ts index af2357372..fb8022421 100644 --- a/server/src/core/@generated/workspace/workspace-where.input.ts +++ b/server/src/core/@generated/workspace/workspace-where.input.ts @@ -38,6 +38,9 @@ export class WorkspaceWhereInput { @Field(() => StringNullableFilter, {nullable:true}) logo?: StringNullableFilter; + @Field(() => StringNullableFilter, {nullable:true}) + inviteHash?: StringNullableFilter; + @HideField() deletedAt?: DateTimeNullableFilter; diff --git a/server/src/core/@generated/workspace/workspace.model.ts b/server/src/core/@generated/workspace/workspace.model.ts index 9cf4eb6be..09f99b92e 100644 --- a/server/src/core/@generated/workspace/workspace.model.ts +++ b/server/src/core/@generated/workspace/workspace.model.ts @@ -27,6 +27,9 @@ export class Workspace { @Field(() => String, {nullable:true}) logo!: string | null; + @Field(() => String, {nullable:true}) + inviteHash!: string | null; + @HideField() deletedAt!: Date | null; diff --git a/server/src/core/auth/auth.module.ts b/server/src/core/auth/auth.module.ts index 0ab8cbd82..3d6f754df 100644 --- a/server/src/core/auth/auth.module.ts +++ b/server/src/core/auth/auth.module.ts @@ -9,6 +9,7 @@ import { VerifyAuthController } from './controllers/verify-auth.controller'; import { TokenService } from './services/token.service'; import { AuthResolver } from './auth.resolver'; import { EnvironmentService } from 'src/integrations/environment/environment.service'; +import { WorkspaceModule } from '../workspace/workspace.module'; const jwtModule = JwtModule.registerAsync({ useFactory: async (environmentService: EnvironmentService) => { @@ -23,7 +24,7 @@ const jwtModule = JwtModule.registerAsync({ }); @Module({ - imports: [jwtModule, UserModule], + imports: [jwtModule, UserModule, WorkspaceModule], controllers: [GoogleAuthController, VerifyAuthController], providers: [ AuthService, diff --git a/server/src/core/auth/auth.resolver.ts b/server/src/core/auth/auth.resolver.ts index a2b02961b..a234f8cb0 100644 --- a/server/src/core/auth/auth.resolver.ts +++ b/server/src/core/auth/auth.resolver.ts @@ -15,6 +15,9 @@ import { import { Prisma } from '@prisma/client'; import { UserExists } from './dto/user-exists.entity'; import { CheckUserExistsInput } from './dto/user-exists.input'; +import { WorkspaceInviteHashValid } from './dto/workspace-invite-hash-valid.entity'; +import { WorkspaceInviteHashValidInput } from './dto/workspace-invite-hash.input'; +import { SignUpInput } from './dto/sign-up.input'; @Resolver() export class AuthResolver { @@ -33,6 +36,15 @@ export class AuthResolver { return { exists }; } + @Query(() => WorkspaceInviteHashValid) + async checkWorkspaceInviteHashIsValid( + @Args() workspaceInviteHashValidInput: WorkspaceInviteHashValidInput, + ): Promise { + return await this.authService.checkWorkspaceInviteHashIsValid( + workspaceInviteHashValidInput.inviteHash, + ); + } + @Mutation(() => LoginToken) async challenge(@Args() challengeInput: ChallengeInput): Promise { const user = await this.authService.challenge(challengeInput); @@ -41,6 +53,14 @@ export class AuthResolver { return { loginToken }; } + @Mutation(() => LoginToken) + async signUp(@Args() signUpInput: SignUpInput): Promise { + const user = await this.authService.signUp(signUpInput); + const loginToken = await this.tokenService.generateLoginToken(user.email); + + return { loginToken }; + } + @Mutation(() => Verify) async verify( @Args() verifyInput: VerifyInput, diff --git a/server/src/core/auth/dto/sign-up.input.ts b/server/src/core/auth/dto/sign-up.input.ts new file mode 100644 index 000000000..49931338d --- /dev/null +++ b/server/src/core/auth/dto/sign-up.input.ts @@ -0,0 +1,20 @@ +import { ArgsType, Field } from '@nestjs/graphql'; +import { IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator'; + +@ArgsType() +export class SignUpInput { + @Field(() => String) + @IsNotEmpty() + @IsEmail() + email: string; + + @Field(() => String) + @IsNotEmpty() + @IsString() + password: string; + + @Field(() => String, { nullable: true }) + @IsString() + @IsOptional() + workspaceInviteHash?: string; +} diff --git a/server/src/core/auth/dto/workspace-invite-hash-valid.entity.ts b/server/src/core/auth/dto/workspace-invite-hash-valid.entity.ts new file mode 100644 index 000000000..4b83292f9 --- /dev/null +++ b/server/src/core/auth/dto/workspace-invite-hash-valid.entity.ts @@ -0,0 +1,7 @@ +import { Field, ObjectType } from '@nestjs/graphql'; + +@ObjectType() +export class WorkspaceInviteHashValid { + @Field(() => Boolean) + isValid: boolean; +} diff --git a/server/src/core/auth/dto/workspace-invite-hash.input.ts b/server/src/core/auth/dto/workspace-invite-hash.input.ts new file mode 100644 index 000000000..848fdb531 --- /dev/null +++ b/server/src/core/auth/dto/workspace-invite-hash.input.ts @@ -0,0 +1,11 @@ +import { ArgsType, Field } from '@nestjs/graphql'; +import { IsNotEmpty, IsString, MinLength } from 'class-validator'; + +@ArgsType() +export class WorkspaceInviteHashValidInput { + @Field(() => String) + @IsString() + @IsNotEmpty() + @MinLength(10) + inviteHash: string; +} diff --git a/server/src/core/auth/services/auth.service.spec.ts b/server/src/core/auth/services/auth.service.spec.ts index 33d44aae5..8b4739906 100644 --- a/server/src/core/auth/services/auth.service.spec.ts +++ b/server/src/core/auth/services/auth.service.spec.ts @@ -2,6 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { AuthService } from './auth.service'; import { TokenService } from './token.service'; import { UserService } from 'src/core/user/user.service'; +import { WorkspaceService } from 'src/core/workspace/services/workspace.service'; describe('AuthService', () => { let service: AuthService; @@ -18,6 +19,10 @@ describe('AuthService', () => { provide: UserService, useValue: {}, }, + { + provide: WorkspaceService, + useValue: {}, + }, ], }).compile(); diff --git a/server/src/core/auth/services/auth.service.ts b/server/src/core/auth/services/auth.service.ts index f4a6b614c..a4c9b8420 100644 --- a/server/src/core/auth/services/auth.service.ts +++ b/server/src/core/auth/services/auth.service.ts @@ -12,6 +12,9 @@ import { Verify } from '../dto/verify.entity'; import { TokenService } from './token.service'; import { Prisma } from '@prisma/client'; import { UserExists } from '../dto/user-exists.entity'; +import { WorkspaceService } from 'src/core/workspace/services/workspace.service'; +import { WorkspaceInviteHashValid } from '../dto/workspace-invite-hash-valid.entity'; +import { SignUpInput } from '../dto/sign-up.input'; export type UserPayload = { firstName: string; @@ -24,31 +27,16 @@ export class AuthService { constructor( private readonly tokenService: TokenService, private readonly userService: UserService, + private readonly workspaceService: WorkspaceService, ) {} async challenge(challengeInput: ChallengeInput) { - let user = await this.userService.findUnique({ + const user = await this.userService.findUnique({ where: { email: challengeInput.email, }, }); - const isPasswordValid = PASSWORD_REGEX.test(challengeInput.password); - - assert(!!user || isPasswordValid, 'Password too weak', BadRequestException); - - if (!user) { - const passwordHash = await hashPassword(challengeInput.password); - - user = await this.userService.createUser({ - data: { - email: challengeInput.email, - passwordHash, - locale: 'en', - }, - } as Prisma.UserCreateArgs); - } - assert(user, "This user doesn't exist", NotFoundException); assert(user.passwordHash, 'Incorrect login method', ForbiddenException); @@ -62,6 +50,53 @@ export class AuthService { return user; } + async signUp(signUpInput: SignUpInput) { + const existingUser = await this.userService.findUnique({ + where: { + email: signUpInput.email, + }, + }); + assert(!existingUser, 'This user already exists', ForbiddenException); + + const isPasswordValid = PASSWORD_REGEX.test(signUpInput.password); + assert(isPasswordValid, 'Password too weak', BadRequestException); + + const passwordHash = await hashPassword(signUpInput.password); + + if (signUpInput.workspaceInviteHash) { + const workspace = await this.workspaceService.findFirst({ + where: { + inviteHash: signUpInput.workspaceInviteHash, + }, + }); + + assert( + workspace, + 'This workspace inviteHash is invalid', + ForbiddenException, + ); + + return await this.userService.createUser( + { + data: { + email: signUpInput.email, + passwordHash, + locale: 'en', + }, + } as Prisma.UserCreateArgs, + workspace.id, + ); + } + + return await this.userService.createUser({ + data: { + email: signUpInput.email, + passwordHash, + locale: 'en', + }, + } as Prisma.UserCreateArgs); + } + async verify( email: string, select: Prisma.UserSelect & { @@ -101,4 +136,16 @@ export class AuthService { return { exists: !!user }; } + + async checkWorkspaceInviteHashIsValid( + inviteHash: string, + ): Promise { + const workspace = await this.workspaceService.findFirst({ + where: { + inviteHash, + }, + }); + + return { isValid: !!workspace }; + } } diff --git a/server/src/core/user/user.service.ts b/server/src/core/user/user.service.ts index ddc609515..7bb12d766 100644 --- a/server/src/core/user/user.service.ts +++ b/server/src/core/user/user.service.ts @@ -46,6 +46,7 @@ export class UserService { // Customs async createUser( args: Prisma.SelectSubset, + workspaceId?: string, ): Promise> { assert(args.data.email, 'email is missing', BadRequestException); @@ -55,14 +56,23 @@ export class UserService { }, create: { ...(args.data as Prisma.UserCreateInput), - // Assign the user to a new workspace by default - workspaceMember: { - create: { - workspace: { - create: {}, + + workspaceMember: workspaceId + ? { + create: { + workspace: { + connect: { id: workspaceId }, + }, + }, + } + : // Assign the user to a new workspace by default + { + create: { + workspace: { + create: {}, + }, + }, }, - }, - }, locale: 'en', }, update: {}, diff --git a/server/src/database/migrations/20230711002359_workspace_invite_hash/migration.sql b/server/src/database/migrations/20230711002359_workspace_invite_hash/migration.sql new file mode 100644 index 000000000..d603f3293 --- /dev/null +++ b/server/src/database/migrations/20230711002359_workspace_invite_hash/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "workspaces" ADD COLUMN "inviteHash" TEXT; diff --git a/server/src/database/schema.prisma b/server/src/database/schema.prisma index 85d606edd..1bc37d553 100644 --- a/server/src/database/schema.prisma +++ b/server/src/database/schema.prisma @@ -196,6 +196,9 @@ model Workspace { /// @Validator.IsString() /// @Validator.IsOptional() logo String? + /// @Validator.IsString() + /// @Validator.IsOptional() + inviteHash String? workspaceMember WorkspaceMember[] companies Company[] diff --git a/server/src/database/seeds/workspaces.ts b/server/src/database/seeds/workspaces.ts index 7f3caf637..58d26b89a 100644 --- a/server/src/database/seeds/workspaces.ts +++ b/server/src/database/seeds/workspaces.ts @@ -7,6 +7,7 @@ export const seedWorkspaces = async (prisma: PrismaClient) => { id: 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419', displayName: 'Apple', domainName: 'apple.dev', + inviteHash: 'apple.dev-invite-hash', logo: 'data:image/png;base64,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', }, }); @@ -18,6 +19,7 @@ export const seedWorkspaces = async (prisma: PrismaClient) => { id: 'twenty-dev-7ed9d212-1c25-4d02-bf25-6aeccf7ea420', displayName: 'Twenty', domainName: 'twenty.com', + inviteHash: 'twenty.com-invite-hash', logo: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAA7EAAAOxAGVKw4bAAACb0lEQVR4nO2VO4taQRTHr3AblbjxEVlwCwVhg7BoqqCIjy/gAyyFWNlYBOxsfH0KuxgQGwXRUkGuL2S7i1barGAgiwbdW93SnGOc4BonPiKahf3DwXFmuP/fPM4ZlvmlTxAhCBdzHnEQWYiv7Mr4C3NeuVYhQYDPzOUUQgDLBQGcLHNhvQK8DACPx8PTxiqVyvISG43GbyaT6Qfpn06n0m63e/tPAPF4vJ1MJu8kEsnWTCkWi1yr1RKGw+GDRqPBOTfr44vFQvD7/Q/lcpmaaVQAr9fLp1IpO22c47hGOBz+MB6PH+Vy+VYDAL8qlUoGtVotzOfzq4MAgsHgE/6KojiQyWR/bKVSqbSszHFM8Pl8z1YK48JsNltCOBwOnrYLO+8AAIjb+nHbycoTiUQfDJ7tFq4YAHiVSmXBxcD41u8flQU8z7fhzO0r83atVns3Go3u9Xr9x0O/RQXo9/tsIBBg6vX606a52Wz+bZ7P5/WwG29gxSJzhKgA6XTaDoFNF+krFAocmC//4yWEcSf2wTm7mCO19xFgSsKOLI16vV7b7XY7mRNoLwA0JymJ5uQIzgIAuX5PzDElT2m+E8BqtQ4ymcx7Yq7T6a6ZE4sKgOadTucaCwkxp1UzlEKh0GDxIXOwDWHAdi6Xe3swQDQa/Q7mywoolUpvsaptymazDWKxmBHTlWXZm405BFZoNpuGgwEmk4mE2SGtVivii4f1AO7J3ZopkQCQj7Ar1FeRChCJRJzVapX6DKNIfSc1Ax+wtQWQ55h6bH8FWDfYV4fO3wlwDr0C/BcADYiTPCxHqIEA2QsCZAkAKnRGkMbKN/sTX5YHPQ1e7SkAAAAASUVORK5CYII=', }, });