mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
refactor: Extract Invitation routes to InvitationController (no-changelog) (#7726)
This PR: - Creates `InvitationController` - Moves `POST /users` to `POST /invitations` and move related test to `invitations.api.tests` - Moves `POST /users/:id` to `POST /invitations/:id/accept` and move related test to `invitations.api.tests` - Adjusts FE to use new endpoints - Moves all the invitation logic to the `UserService` --------- Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
This commit is contained in:
@@ -1,16 +1,22 @@
|
||||
import { Service } from 'typedi';
|
||||
import Container, { Service } from 'typedi';
|
||||
import type { EntityManager, FindManyOptions, FindOneOptions, FindOptionsWhere } from 'typeorm';
|
||||
import { In } from 'typeorm';
|
||||
import { User } from '@db/entities/User';
|
||||
import type { IUserSettings } from 'n8n-workflow';
|
||||
import { UserRepository } from '@db/repositories/user.repository';
|
||||
import { getInstanceBaseUrl } from '@/UserManagement/UserManagementHelper';
|
||||
import { generateUserInviteUrl, getInstanceBaseUrl } from '@/UserManagement/UserManagementHelper';
|
||||
import type { PublicUser } from '@/Interfaces';
|
||||
import type { PostHogClient } from '@/posthog';
|
||||
import { type JwtPayload, JwtService } from './jwt.service';
|
||||
import { TokenExpiredError } from 'jsonwebtoken';
|
||||
import { Logger } from '@/Logger';
|
||||
import { createPasswordSha } from '@/auth/jwt';
|
||||
import { UserManagementMailer } from '@/UserManagement/email';
|
||||
import { InternalHooks } from '@/InternalHooks';
|
||||
import { RoleService } from '@/services/role.service';
|
||||
import { ErrorReporterProxy as ErrorReporter } from 'n8n-workflow';
|
||||
import { InternalServerError } from '@/ResponseHelper';
|
||||
import type { UserRequest } from '@/requests';
|
||||
|
||||
@Service()
|
||||
export class UserService {
|
||||
@@ -18,6 +24,8 @@ export class UserService {
|
||||
private readonly logger: Logger,
|
||||
private readonly userRepository: UserRepository,
|
||||
private readonly jwtService: JwtService,
|
||||
private readonly mailer: UserManagementMailer,
|
||||
private readonly roleService: RoleService,
|
||||
) {}
|
||||
|
||||
async findOne(options: FindOneOptions<User>) {
|
||||
@@ -169,4 +177,114 @@ export class UserService {
|
||||
|
||||
return Promise.race([fetchPromise, timeoutPromise]);
|
||||
}
|
||||
|
||||
private async sendEmails(owner: User, toInviteUsers: { [key: string]: string }) {
|
||||
const domain = getInstanceBaseUrl();
|
||||
|
||||
return Promise.all(
|
||||
Object.entries(toInviteUsers).map(async ([email, id]) => {
|
||||
const inviteAcceptUrl = generateUserInviteUrl(owner.id, id);
|
||||
const invitedUser: UserRequest.InviteResponse = {
|
||||
user: {
|
||||
id,
|
||||
email,
|
||||
inviteAcceptUrl,
|
||||
emailSent: false,
|
||||
},
|
||||
error: '',
|
||||
};
|
||||
|
||||
try {
|
||||
const result = await this.mailer.invite({
|
||||
email,
|
||||
inviteAcceptUrl,
|
||||
domain,
|
||||
});
|
||||
if (result.emailSent) {
|
||||
invitedUser.user.emailSent = true;
|
||||
delete invitedUser.user?.inviteAcceptUrl;
|
||||
void Container.get(InternalHooks).onUserTransactionalEmail({
|
||||
user_id: id,
|
||||
message_type: 'New user invite',
|
||||
public_api: false,
|
||||
});
|
||||
}
|
||||
|
||||
void Container.get(InternalHooks).onUserInvite({
|
||||
user: owner,
|
||||
target_user_id: Object.values(toInviteUsers),
|
||||
public_api: false,
|
||||
email_sent: result.emailSent,
|
||||
});
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
void Container.get(InternalHooks).onEmailFailed({
|
||||
user: owner,
|
||||
message_type: 'New user invite',
|
||||
public_api: false,
|
||||
});
|
||||
this.logger.error('Failed to send email', {
|
||||
userId: owner.id,
|
||||
inviteAcceptUrl,
|
||||
domain,
|
||||
email,
|
||||
});
|
||||
invitedUser.error = e.message;
|
||||
}
|
||||
}
|
||||
|
||||
return invitedUser;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
public async inviteMembers(owner: User, emails: string[]) {
|
||||
const memberRole = await this.roleService.findGlobalMemberRole();
|
||||
|
||||
const existingUsers = await this.findMany({
|
||||
where: { email: In(emails) },
|
||||
relations: ['globalRole'],
|
||||
select: ['email', 'password', 'id'],
|
||||
});
|
||||
|
||||
const existUsersEmails = existingUsers.map((user) => user.email);
|
||||
|
||||
const toCreateUsers = emails.filter((email) => !existUsersEmails.includes(email));
|
||||
|
||||
const pendingUsersToInvite = existingUsers.filter((email) => email.isPending);
|
||||
|
||||
const createdUsers = new Map<string, string>();
|
||||
|
||||
this.logger.debug(
|
||||
toCreateUsers.length > 1
|
||||
? `Creating ${toCreateUsers.length} user shells...`
|
||||
: 'Creating 1 user shell...',
|
||||
);
|
||||
|
||||
try {
|
||||
await this.getManager().transaction(async (transactionManager) =>
|
||||
Promise.all(
|
||||
toCreateUsers.map(async (email) => {
|
||||
const newUser = Object.assign(new User(), {
|
||||
email,
|
||||
globalRole: memberRole,
|
||||
});
|
||||
const savedUser = await transactionManager.save<User>(newUser);
|
||||
createdUsers.set(email, savedUser.id);
|
||||
return savedUser;
|
||||
}),
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
ErrorReporter.error(error);
|
||||
this.logger.error('Failed to create user shells', { userShells: createdUsers });
|
||||
throw new InternalServerError('An error occurred during user creation');
|
||||
}
|
||||
|
||||
pendingUsersToInvite.forEach(({ email, id }) => createdUsers.set(email, id));
|
||||
|
||||
const usersInvited = await this.sendEmails(owner, Object.fromEntries(createdUsers));
|
||||
|
||||
return { usersInvited, usersCreated: toCreateUsers };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user