mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 00:52:50 +00:00
272 lines
7.3 KiB
TypeScript
272 lines
7.3 KiB
TypeScript
import {
|
|
BadRequestException,
|
|
Injectable,
|
|
NotFoundException,
|
|
} from "@nestjs/common";
|
|
import { InjectRepository } from "@nestjs/typeorm";
|
|
import { hashPassword } from "@tria-plc/api-common/utils/argon";
|
|
import { EUserStatus } from "@tria-plc/api-common/utils/enums/user.enum";
|
|
import { DataSource, In, IsNull, Repository } from "typeorm";
|
|
|
|
import { Employee, Organization, UserCredential } from "@tria-plc/iamapi-common";
|
|
import { Role } from "@tria-plc/iamapi-common/entities/iam/user/role.entity";
|
|
import { UserRole } from "@tria-plc/iamapi-common/entities/iam/user/user-role.entity";
|
|
import { User } from "@tria-plc/iamapi-common/entities/iam/user/user.entity";
|
|
|
|
import { CreateOrganizationUserDto } from "./dto/create-organization-user.dto";
|
|
|
|
const RESERVED_ROLE_KEYS = new Set([
|
|
"super_admin",
|
|
"organization_admin",
|
|
"unit_admin",
|
|
]);
|
|
const DEFAULT_USER_PASSWORD = "12345678";
|
|
|
|
@Injectable()
|
|
export class BackofficeService {
|
|
constructor(
|
|
@InjectRepository(Organization)
|
|
private readonly organizationRepository: Repository<Organization>,
|
|
@InjectRepository(Role)
|
|
private readonly roleRepository: Repository<Role>,
|
|
@InjectRepository(UserRole)
|
|
private readonly userRoleRepository: Repository<UserRole>,
|
|
@InjectRepository(User)
|
|
private readonly userRepository: Repository<User>,
|
|
private readonly dataSource: DataSource,
|
|
) {}
|
|
|
|
async createOrganizationUser(
|
|
organizationId: string,
|
|
dto: CreateOrganizationUserDto,
|
|
) {
|
|
const organizationExists = await this.organizationRepository.exists({
|
|
where: { id: organizationId },
|
|
});
|
|
|
|
if (!organizationExists) {
|
|
throw new NotFoundException("organization_not_found");
|
|
}
|
|
|
|
const email = dto.email.trim().toLowerCase();
|
|
const username = dto.username.trim().toLowerCase();
|
|
const phoneNumber = dto.phoneNumber?.trim() || undefined;
|
|
const name = {
|
|
en: dto.name.en.trim(),
|
|
...(dto.name.am?.trim() ? { am: dto.name.am.trim() } : {}),
|
|
};
|
|
|
|
const existingUsers = await this.userRepository.find({
|
|
where: [{ email }, { username }],
|
|
select: { id: true, email: true, username: true },
|
|
});
|
|
|
|
const emailUser = existingUsers.find((user) => user.email === email);
|
|
const usernameUser = existingUsers.find((user) => user.username === username);
|
|
|
|
if (emailUser && usernameUser && emailUser.id !== usernameUser.id) {
|
|
throw new BadRequestException("email_or_username_already_in_use");
|
|
}
|
|
|
|
const existingUser = emailUser ?? usernameUser;
|
|
const hashedPassword = await hashPassword(DEFAULT_USER_PASSWORD);
|
|
|
|
return this.dataSource.transaction(async (manager) => {
|
|
let user = existingUser;
|
|
|
|
if (!user) {
|
|
user = await manager.getRepository(User).save(
|
|
manager.getRepository(User).create({
|
|
email,
|
|
username,
|
|
phoneNumber,
|
|
name,
|
|
isActive: true,
|
|
hasSetPassword: true,
|
|
status: EUserStatus.ACCEPTED,
|
|
}),
|
|
);
|
|
} else {
|
|
await manager.getRepository(User).update(
|
|
{ id: user.id },
|
|
{
|
|
email,
|
|
username,
|
|
phoneNumber,
|
|
name,
|
|
isActive: true,
|
|
hasSetPassword: true,
|
|
status: EUserStatus.ACCEPTED,
|
|
},
|
|
);
|
|
}
|
|
|
|
const activeCredentialExists = await manager.getRepository(UserCredential).exists({
|
|
where: {
|
|
userId: user.id,
|
|
isActive: true,
|
|
},
|
|
});
|
|
|
|
if (!activeCredentialExists) {
|
|
await manager.getRepository(UserCredential).insert({
|
|
userId: user.id,
|
|
password: hashedPassword,
|
|
isActive: true,
|
|
});
|
|
}
|
|
|
|
let employee = await manager.getRepository(Employee).findOne({
|
|
where: {
|
|
userId: user.id,
|
|
organizationId,
|
|
isCurrent: true,
|
|
},
|
|
relations: {
|
|
user: true,
|
|
employeePositions: {
|
|
position: true,
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!employee) {
|
|
const insertResult = await manager.getRepository(Employee).insert({
|
|
userId: user.id,
|
|
organizationId,
|
|
isCurrent: true,
|
|
name,
|
|
});
|
|
|
|
employee = await manager.getRepository(Employee).findOne({
|
|
where: { id: insertResult.identifiers[0]?.id as string },
|
|
relations: {
|
|
user: true,
|
|
employeePositions: {
|
|
position: true,
|
|
},
|
|
},
|
|
});
|
|
} else {
|
|
await manager.getRepository(Employee).update(
|
|
{ id: employee.id },
|
|
{ name },
|
|
);
|
|
|
|
employee = await manager.getRepository(Employee).findOne({
|
|
where: { id: employee.id },
|
|
relations: {
|
|
user: true,
|
|
employeePositions: {
|
|
position: true,
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
if (!employee) {
|
|
throw new NotFoundException("employee_create_failed");
|
|
}
|
|
|
|
return employee;
|
|
});
|
|
}
|
|
|
|
async getEmployeeUserRoles(organizationId: string, userId: string) {
|
|
await this.assertUserBelongsToOrganization(organizationId, userId);
|
|
|
|
const userRoles = await this.userRoleRepository.find({
|
|
where: {
|
|
userId,
|
|
organizationId,
|
|
unitId: IsNull(),
|
|
},
|
|
relations: {
|
|
role: true,
|
|
},
|
|
order: {
|
|
role: {
|
|
key: "ASC",
|
|
},
|
|
},
|
|
});
|
|
|
|
return userRoles
|
|
.map((userRole) => userRole.role)
|
|
.filter((role): role is Role => Boolean(role))
|
|
.map((role) => ({
|
|
id: role.id,
|
|
key: role.key,
|
|
name: role.name,
|
|
}));
|
|
}
|
|
|
|
async replaceEmployeeUserRoles(
|
|
organizationId: string,
|
|
userId: string,
|
|
roleIds: string[],
|
|
) {
|
|
await this.assertUserBelongsToOrganization(organizationId, userId);
|
|
|
|
const uniqueRoleIds = [...new Set(roleIds)];
|
|
const roles = uniqueRoleIds.length
|
|
? await this.roleRepository.find({
|
|
where: {
|
|
id: In(uniqueRoleIds),
|
|
},
|
|
})
|
|
: [];
|
|
|
|
if (roles.length !== uniqueRoleIds.length) {
|
|
throw new NotFoundException("one_or_more_roles_not_found");
|
|
}
|
|
|
|
const reservedRoles = roles.filter((role) => RESERVED_ROLE_KEYS.has(role.key));
|
|
if (reservedRoles.length) {
|
|
throw new BadRequestException("reserved_roles_must_use_admin_actions");
|
|
}
|
|
|
|
await this.dataSource.transaction(async (manager) => {
|
|
await manager.getRepository(UserRole).delete({
|
|
userId,
|
|
organizationId,
|
|
unitId: IsNull(),
|
|
});
|
|
|
|
if (!roles.length) {
|
|
return;
|
|
}
|
|
|
|
await manager.getRepository(UserRole).insert(
|
|
roles.map((role) => ({
|
|
userId,
|
|
roleId: role.id,
|
|
organizationId,
|
|
})),
|
|
);
|
|
});
|
|
|
|
return this.getEmployeeUserRoles(organizationId, userId);
|
|
}
|
|
|
|
private async assertUserBelongsToOrganization(
|
|
organizationId: string,
|
|
userId: string,
|
|
) {
|
|
const exists = await this.userRepository
|
|
.createQueryBuilder("user")
|
|
.innerJoin(
|
|
"user.employee",
|
|
"employee",
|
|
"employee.organizationId = :organizationId AND employee.isCurrent = true",
|
|
{ organizationId },
|
|
)
|
|
.where("user.id = :userId", { userId })
|
|
.getExists();
|
|
|
|
if (!exists) {
|
|
throw new NotFoundException("user_not_found_in_organization");
|
|
}
|
|
}
|
|
}
|