feat: user can have multiple workspaces (backend) (#4036)
* create user-workspace mapping * user-workspace service and integration * invite condition on sign-up/sign-in * save/update defaultWorkspace on signup * add unique decorator on user-workspace entity * remove resolver permissions * Fixes * Fixes * Fix tests * Fixes --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -18,6 +18,7 @@ import { GoogleGmailAuthController } from 'src/core/auth/controllers/google-gmai
|
||||
import { VerifyAuthController } from 'src/core/auth/controllers/verify-auth.controller';
|
||||
import { TokenService } from 'src/core/auth/services/token.service';
|
||||
import { GoogleGmailService } from 'src/core/auth/services/google-gmail.service';
|
||||
import { UserWorkspaceModule } from 'src/core/user-workspace/user-workspace.module';
|
||||
|
||||
import { AuthResolver } from './auth.resolver';
|
||||
|
||||
@ -45,6 +46,7 @@ const jwtModule = JwtModule.registerAsync({
|
||||
TypeORMModule,
|
||||
TypeOrmModule.forFeature([Workspace, User, RefreshToken], 'core'),
|
||||
HttpModule,
|
||||
UserWorkspaceModule,
|
||||
],
|
||||
controllers: [
|
||||
GoogleAuthController,
|
||||
|
||||
@ -91,6 +91,7 @@ export class AuthResolver {
|
||||
@Mutation(() => LoginToken)
|
||||
async signUp(@Args() signUpInput: SignUpInput): Promise<LoginToken> {
|
||||
const user = await this.authService.signUp(signUpInput);
|
||||
|
||||
const loginToken = await this.tokenService.generateLoginToken(user.email);
|
||||
|
||||
return { loginToken };
|
||||
@ -120,6 +121,8 @@ export class AuthResolver {
|
||||
verifyInput.loginToken,
|
||||
);
|
||||
|
||||
assert(email, 'Invalid token', ForbiddenException);
|
||||
|
||||
const result = await this.authService.verify(email);
|
||||
|
||||
return result;
|
||||
|
||||
@ -9,6 +9,7 @@ import { Workspace } from 'src/core/workspace/workspace.entity';
|
||||
import { User } from 'src/core/user/user.entity';
|
||||
import { EnvironmentService } from 'src/integrations/environment/environment.service';
|
||||
import { EmailService } from 'src/integrations/email/email.service';
|
||||
import { UserWorkspaceService } from 'src/core/user-workspace/user-workspace.service';
|
||||
|
||||
import { AuthService } from './auth.service';
|
||||
import { TokenService } from './token.service';
|
||||
@ -28,6 +29,10 @@ describe('AuthService', () => {
|
||||
provide: UserService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: UserWorkspaceService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: WorkspaceManagerService,
|
||||
useValue: {},
|
||||
|
||||
@ -34,6 +34,7 @@ import { EnvironmentService } from 'src/integrations/environment/environment.ser
|
||||
import { EmailService } from 'src/integrations/email/email.service';
|
||||
import { UpdatePassword } from 'src/core/auth/dto/update-password.entity';
|
||||
import { getImageBufferFromUrl } from 'src/utils/image';
|
||||
import { UserWorkspaceService } from 'src/core/user-workspace/user-workspace.service';
|
||||
|
||||
import { TokenService } from './token.service';
|
||||
|
||||
@ -54,6 +55,7 @@ export class AuthService {
|
||||
private readonly workspaceRepository: Repository<Workspace>,
|
||||
@InjectRepository(User, 'core')
|
||||
private readonly userRepository: Repository<User>,
|
||||
private readonly userWorkspaceService: UserWorkspaceService,
|
||||
private readonly httpService: HttpService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
private readonly emailService: EmailService,
|
||||
@ -95,11 +97,16 @@ export class AuthService {
|
||||
if (!firstName) firstName = '';
|
||||
if (!lastName) lastName = '';
|
||||
|
||||
const existingUser = await this.userRepository.findOneBy({
|
||||
email: email,
|
||||
const existingUser = await this.userRepository.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
},
|
||||
relations: ['defaultWorkspace'],
|
||||
});
|
||||
|
||||
assert(!existingUser, 'This user already exists', ForbiddenException);
|
||||
if (existingUser && !workspaceInviteHash) {
|
||||
assert(!existingUser, 'This user already exists', ForbiddenException);
|
||||
}
|
||||
|
||||
if (password) {
|
||||
const isPasswordValid = PASSWORD_REGEX.test(password);
|
||||
@ -157,6 +164,31 @@ export class AuthService {
|
||||
imagePath = paths[0];
|
||||
}
|
||||
|
||||
if (existingUser && workspaceInviteHash) {
|
||||
const userWorkspaceExists =
|
||||
await this.userWorkspaceService.checkUserWorkspaceExists(
|
||||
existingUser.id,
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
if (!userWorkspaceExists) {
|
||||
await this.userWorkspaceService.create(existingUser.id, workspace.id);
|
||||
|
||||
await this.userWorkspaceService.createWorkspaceMember(
|
||||
workspace.id,
|
||||
existingUser,
|
||||
);
|
||||
}
|
||||
|
||||
const updatedUser = await this.userRepository.save({
|
||||
id: existingUser.id,
|
||||
defaultWorkspace: workspace,
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
return Object.assign(existingUser, updatedUser);
|
||||
}
|
||||
|
||||
const userToCreate = this.userRepository.create({
|
||||
email: email,
|
||||
firstName: firstName,
|
||||
@ -169,9 +201,8 @@ export class AuthService {
|
||||
|
||||
const user = await this.userRepository.save(userToCreate);
|
||||
|
||||
if (workspaceInviteHash) {
|
||||
await this.userService.createWorkspaceMember(user);
|
||||
}
|
||||
await this.userWorkspaceService.create(user.id, workspace.id);
|
||||
await this.userWorkspaceService.createWorkspaceMember(workspace.id, user);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user