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:
Aditya Pimpalkar
2024-02-25 09:58:14 +00:00
committed by GitHub
parent 52b33b5450
commit b67957bf94
10 changed files with 226 additions and 10 deletions

View File

@ -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,

View File

@ -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;

View File

@ -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: {},

View File

@ -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;
}