316 lines
8.8 KiB
TypeScript
316 lines
8.8 KiB
TypeScript
import { Inject, Service } from 'typedi';
|
|
import events from '@/subscribers/events';
|
|
import { ServiceError } from '@/exceptions';
|
|
import {
|
|
IEditUserDTO,
|
|
ISystemUser,
|
|
ITenantUser,
|
|
ITenantUserActivatedPayload,
|
|
ITenantUserDeletedPayload,
|
|
ITenantUserEditedPayload,
|
|
ITenantUserInactivatedPayload,
|
|
} from '@/interfaces';
|
|
import RolesService from '@/services/Roles/RolesService';
|
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
|
import { ERRORS } from './constants';
|
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
|
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
|
import { UserTransformer } from './UserTransformer';
|
|
|
|
@Service()
|
|
export default class UsersService {
|
|
@Inject()
|
|
private rolesService: RolesService;
|
|
|
|
@Inject()
|
|
private tenancy: HasTenancyService;
|
|
|
|
@Inject()
|
|
private eventPublisher: EventPublisher;
|
|
|
|
@Inject()
|
|
private transformer: TransformerInjectable;
|
|
|
|
/**
|
|
* Creates a new user.
|
|
* @param {number} tenantId - Tenant id.
|
|
* @param {number} userId - User id.
|
|
* @param {IUserDTO} editUserDTO - Edit user DTO.
|
|
* @return {Promise<ISystemUser>}
|
|
*/
|
|
public async editUser(
|
|
tenantId: number,
|
|
userId: number,
|
|
editUserDTO: IEditUserDTO,
|
|
authorizedUser: ISystemUser
|
|
): Promise<any> {
|
|
const { User } = this.tenancy.models(tenantId);
|
|
const { email } = editUserDTO;
|
|
|
|
// Retrieve the tenant user or throw not found service error.
|
|
const oldTenantUser = await this.getTenantUserOrThrowError(
|
|
tenantId,
|
|
userId
|
|
);
|
|
// Validate cannot mutate the authorized user.
|
|
this.validateMutateRoleNotAuthorizedUser(
|
|
oldTenantUser,
|
|
editUserDTO,
|
|
authorizedUser
|
|
);
|
|
// Validate user email should be unique.
|
|
await this.validateUserEmailUniquiness(tenantId, email, userId);
|
|
|
|
// Retrieve the given role or throw not found service error.
|
|
const role = await this.rolesService.getRoleOrThrowError(
|
|
tenantId,
|
|
editUserDTO.roleId
|
|
);
|
|
// Updates the tenant user.
|
|
const tenantUser = await User.query().updateAndFetchById(userId, {
|
|
...editUserDTO,
|
|
});
|
|
// Triggers `onTenantUserEdited` event.
|
|
await this.eventPublisher.emitAsync(events.tenantUser.onEdited, {
|
|
tenantId,
|
|
userId,
|
|
editUserDTO,
|
|
tenantUser,
|
|
oldTenantUser,
|
|
} as ITenantUserEditedPayload);
|
|
|
|
return tenantUser;
|
|
}
|
|
|
|
/**
|
|
* Deletes the given user id.
|
|
* @param {number} tenantId - Tenant id.
|
|
* @param {number} userId - User id.
|
|
*/
|
|
public async deleteUser(tenantId: number, userId: number): Promise<void> {
|
|
const { User } = this.tenancy.models(tenantId);
|
|
|
|
// Retrieve user details or throw not found service error.
|
|
const tenantUser = await this.getTenantUserOrThrowError(tenantId, userId);
|
|
|
|
// Validate the delete user should not be the last active user.
|
|
if (tenantUser.isInviteAccepted) {
|
|
await this.validateNotLastUserDelete(tenantId);
|
|
}
|
|
// Delete user from the storage.
|
|
await User.query().findById(userId).delete();
|
|
|
|
// Triggers `onTenantUserDeleted` event.
|
|
await this.eventPublisher.emitAsync(events.tenantUser.onDeleted, {
|
|
tenantId,
|
|
userId,
|
|
tenantUser,
|
|
} as ITenantUserDeletedPayload);
|
|
}
|
|
|
|
/**
|
|
* Activate the given user id.
|
|
* @param {number} tenantId - Tenant id.
|
|
* @param {number} userId - User id.
|
|
* @return {Promise<void>}
|
|
*/
|
|
public async activateUser(
|
|
tenantId: number,
|
|
userId: number,
|
|
authorizedUser: ISystemUser
|
|
): Promise<void> {
|
|
const { User } = this.tenancy.models(tenantId);
|
|
|
|
// Throw service error if the given user is equals the authorized user.
|
|
this.throwErrorIfUserSameAuthorizedUser(userId, authorizedUser);
|
|
|
|
// Retrieve the user or throw not found service error.
|
|
const tenantUser = await this.getTenantUserOrThrowError(tenantId, userId);
|
|
|
|
// Throw serivce error if the user is already activated.
|
|
this.throwErrorIfUserActive(tenantUser);
|
|
|
|
// Marks the tenant user as active.
|
|
await User.query().findById(userId).update({ active: true });
|
|
|
|
// Triggers `onTenantUserActivated` event.
|
|
await this.eventPublisher.emitAsync(events.tenantUser.onActivated, {
|
|
tenantId,
|
|
userId,
|
|
authorizedUser,
|
|
tenantUser,
|
|
} as ITenantUserActivatedPayload);
|
|
}
|
|
|
|
/**
|
|
* Inactivate the given user id.
|
|
* @param {number} tenantId
|
|
* @param {number} userId
|
|
* @return {Promise<void>}
|
|
*/
|
|
public async inactivateUser(
|
|
tenantId: number,
|
|
userId: number,
|
|
authorizedUser: ISystemUser
|
|
): Promise<void> {
|
|
const { User } = this.tenancy.models(tenantId);
|
|
|
|
// Throw service error if the given user is equals the authorized user.
|
|
this.throwErrorIfUserSameAuthorizedUser(userId, authorizedUser);
|
|
|
|
// Retrieve the user or throw not found service error.
|
|
const tenantUser = await this.getTenantUserOrThrowError(tenantId, userId);
|
|
|
|
// Throw serivce error if the user is already inactivated.
|
|
this.throwErrorIfUserInactive(tenantUser);
|
|
|
|
// Marks the tenant user as active.
|
|
await User.query().findById(userId).update({ active: true });
|
|
|
|
// Triggers `onTenantUserActivated` event.
|
|
await this.eventPublisher.emitAsync(events.tenantUser.onInactivated, {
|
|
tenantId,
|
|
userId,
|
|
authorizedUser,
|
|
tenantUser,
|
|
} as ITenantUserInactivatedPayload);
|
|
}
|
|
|
|
/**
|
|
* Retrieve users list based on the given filter.
|
|
* @param {number} tenantId
|
|
* @param {object} filter
|
|
*/
|
|
public async getList(tenantId: number) {
|
|
const { User } = this.tenancy.models(tenantId);
|
|
|
|
const users = await User.query().withGraphFetched('role');
|
|
|
|
return this.transformer.transform(tenantId, users, new UserTransformer());
|
|
}
|
|
|
|
/**
|
|
* Retrieve the given user details.
|
|
* @param {number} tenantId - Tenant id.
|
|
* @param {number} userId - User id.
|
|
*/
|
|
public async getUser(tenantId: number, userId: number) {
|
|
// Retrieve the system user.
|
|
const user = await this.getTenantUserOrThrowError(tenantId, userId);
|
|
|
|
return user;
|
|
}
|
|
|
|
/**
|
|
* Validate user existance throw error in case user was not found.,
|
|
* @param {number} tenantId -
|
|
* @param {number} userId -
|
|
* @returns {ISystemUser}
|
|
*/
|
|
async getTenantUserOrThrowError(
|
|
tenantId: number,
|
|
userId: number
|
|
): Promise<ITenantUser> {
|
|
const { User } = this.tenancy.models(tenantId);
|
|
|
|
const user = await User.query().findById(userId);
|
|
|
|
if (!user) {
|
|
throw new ServiceError(ERRORS.USER_NOT_FOUND);
|
|
}
|
|
return user;
|
|
}
|
|
|
|
/**
|
|
* Validate the delete user should not be the last user.
|
|
* @param {number} tenantId
|
|
*/
|
|
private async validateNotLastUserDelete(tenantId: number) {
|
|
const { User } = this.tenancy.models(tenantId);
|
|
|
|
const inviteAcceptedUsers = await User.query()
|
|
.select(['id'])
|
|
.whereNotNull('invite_accepted_at');
|
|
|
|
if (inviteAcceptedUsers.length === 1) {
|
|
throw new ServiceError(ERRORS.CANNOT_DELETE_LAST_USER);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Throws service error in case the user was already active.
|
|
* @param {ISystemUser} user
|
|
* @throws {ServiceError}
|
|
*/
|
|
private throwErrorIfUserActive(user: ISystemUser) {
|
|
if (user.active) {
|
|
throw new ServiceError(ERRORS.USER_ALREADY_ACTIVE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Throws service error in case the user was already inactive.
|
|
* @param {ISystemUser} user
|
|
* @throws {ServiceError}
|
|
*/
|
|
private throwErrorIfUserInactive(user: ITenantUser) {
|
|
if (!user.active) {
|
|
throw new ServiceError(ERRORS.USER_ALREADY_INACTIVE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Throw service error in case the given user same the authorized user.
|
|
* @param {number} userId
|
|
* @param {ISystemUser} authorizedUser
|
|
*/
|
|
private throwErrorIfUserSameAuthorizedUser(
|
|
userId: number,
|
|
authorizedUser: ISystemUser
|
|
) {
|
|
if (userId === authorizedUser.id) {
|
|
throw new ServiceError(ERRORS.USER_SAME_THE_AUTHORIZED_USER);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate the given user email should be unique in the storage.
|
|
* @param {string} email
|
|
* @param {number} userId
|
|
*/
|
|
private validateUserEmailUniquiness = async (
|
|
tenantId: number,
|
|
email: string,
|
|
userId: number
|
|
) => {
|
|
const { User } = this.tenancy.models(tenantId);
|
|
|
|
const userByEmail = await User.query()
|
|
.findOne('email', email)
|
|
.whereNot('id', userId);
|
|
|
|
if (userByEmail) {
|
|
throw new ServiceError(ERRORS.EMAIL_ALREADY_EXISTS);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Validate the authorized user cannot mutate its role.
|
|
* @param {ITenantUser} oldTenantUser
|
|
* @param {IEditUserDTO} editUserDTO
|
|
* @param {ISystemUser} authorizedUser
|
|
*/
|
|
validateMutateRoleNotAuthorizedUser(
|
|
oldTenantUser: ITenantUser,
|
|
editUserDTO: IEditUserDTO,
|
|
authorizedUser: ISystemUser
|
|
) {
|
|
if (
|
|
authorizedUser.id === oldTenantUser.systemUserId &&
|
|
editUserDTO.roleId !== oldTenantUser.roleId
|
|
) {
|
|
throw new ServiceError(ERRORS.CANNOT_AUTHORIZED_USER_MUTATE_ROLE);
|
|
}
|
|
}
|
|
}
|