Allow editing org team member name, mobile, and password from settings.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -44,6 +44,26 @@ export class InviteUserDto {
|
||||
orgRoleId?: string;
|
||||
}
|
||||
|
||||
export class UpdateOrgUserDto {
|
||||
@IsString()
|
||||
@MinLength(2)
|
||||
@MaxLength(120)
|
||||
fullName!: string;
|
||||
|
||||
@IsString()
|
||||
@Matches(/^09\d{9}$|^\+98\d{10}$|^98\d{10}$|^9\d{9}$/, {
|
||||
message: 'شماره موبایل معتبر نیست',
|
||||
})
|
||||
mobile!: string;
|
||||
|
||||
@IsOptional()
|
||||
@ValidateIf((_, value) => typeof value === 'string' && value.length > 0)
|
||||
@IsString()
|
||||
@MinLength(8)
|
||||
@MaxLength(72)
|
||||
password?: string;
|
||||
}
|
||||
|
||||
export class UpdateUserRoleDto {
|
||||
@IsOptional()
|
||||
@IsIn(ALL_ROLES)
|
||||
|
||||
@@ -15,6 +15,7 @@ import { Roles } from '../auth/roles.decorator';
|
||||
import { RolesGuard } from '../auth/roles.guard';
|
||||
import {
|
||||
InviteUserDto,
|
||||
UpdateOrgUserDto,
|
||||
UpdateUserCommissionDto,
|
||||
UpdateUserRoleDto,
|
||||
UpdateUserStatusDto,
|
||||
@@ -50,6 +51,21 @@ export class UsersController {
|
||||
return this.usersService.invite(user.organizationId, user.role, dto);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@Roles('owner', 'admin')
|
||||
updateProfile(
|
||||
@CurrentUser() user: { organizationId: string; role: UserRole },
|
||||
@Param('id') id: string,
|
||||
@Body() dto: UpdateOrgUserDto,
|
||||
) {
|
||||
return this.usersService.updateProfile(
|
||||
user.organizationId,
|
||||
user.role,
|
||||
id,
|
||||
dto,
|
||||
);
|
||||
}
|
||||
|
||||
@Patch(':id/commission')
|
||||
@Roles('owner', 'admin')
|
||||
updateCommission(
|
||||
|
||||
@@ -9,6 +9,7 @@ import type {
|
||||
DataVisibility,
|
||||
InviteUserRequest,
|
||||
OrgUserDto,
|
||||
UpdateOrgUserRequest,
|
||||
UserRole,
|
||||
} from '@mento/shared';
|
||||
import { DATA_VISIBILITIES } from '@mento/shared';
|
||||
@@ -136,6 +137,80 @@ export class UsersService {
|
||||
}
|
||||
}
|
||||
|
||||
async updateProfile(
|
||||
organizationId: string,
|
||||
actorRole: UserRole,
|
||||
targetId: string,
|
||||
dto: UpdateOrgUserRequest,
|
||||
): Promise<OrgUserDto> {
|
||||
if (actorRole !== 'owner' && actorRole !== 'admin') {
|
||||
throw new ForbiddenException('فقط مالک یا مدیر میتواند مشخصات کاربر را ویرایش کند');
|
||||
}
|
||||
|
||||
const target = await this.memberships.findUserInOrgById(
|
||||
organizationId,
|
||||
targetId,
|
||||
);
|
||||
if (target.role === 'owner' && actorRole !== 'owner') {
|
||||
throw new ForbiddenException('مدیر نمیتواند مشخصات مالک را تغییر دهد');
|
||||
}
|
||||
|
||||
const fullName = dto.fullName.trim();
|
||||
if (fullName.length < 2) {
|
||||
throw new BadRequestException('نام باید حداقل دو حرف باشد');
|
||||
}
|
||||
const mobile = normalizeMobile(dto.mobile);
|
||||
if (!mobile) {
|
||||
throw new BadRequestException('شماره موبایل معتبر نیست');
|
||||
}
|
||||
|
||||
const previousMobile = target.user.mobile ?? '';
|
||||
if (mobile !== previousMobile) {
|
||||
const taken = await this.prisma.user.findFirst({
|
||||
where: { mobile, id: { not: targetId } },
|
||||
select: { id: true },
|
||||
});
|
||||
if (taken) {
|
||||
throw new ConflictException('این شماره موبایل قبلاً ثبت شده است');
|
||||
}
|
||||
}
|
||||
|
||||
const data: Prisma.UserUpdateInput = { fullName, mobile };
|
||||
const prevEmail = target.user.email;
|
||||
const wasSynthetic =
|
||||
prevEmail.endsWith('@users.mento.local') ||
|
||||
prevEmail === inviteEmailFromMobile(previousMobile);
|
||||
if (wasSynthetic && mobile !== previousMobile) {
|
||||
const nextEmail = inviteEmailFromMobile(mobile);
|
||||
const emailTaken = await this.prisma.user.findFirst({
|
||||
where: { email: nextEmail, id: { not: targetId } },
|
||||
select: { id: true },
|
||||
});
|
||||
if (!emailTaken) data.email = nextEmail;
|
||||
}
|
||||
|
||||
const password = dto.password?.trim() ?? '';
|
||||
if (password) {
|
||||
if (password.length < 8) {
|
||||
throw new BadRequestException('رمز عبور باید حداقل ۸ کاراکتر باشد');
|
||||
}
|
||||
data.passwordHash = await bcrypt.hash(password, 10);
|
||||
}
|
||||
|
||||
try {
|
||||
await this.prisma.user.update({
|
||||
where: { id: targetId },
|
||||
data,
|
||||
});
|
||||
} catch (e) {
|
||||
if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === 'P2002') {
|
||||
throw new ConflictException('این شماره موبایل یا ایمیل قبلاً ثبت شده است');
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
return this.dtoOf(organizationId, targetId);
|
||||
}
|
||||
|
||||
async updateCommission(
|
||||
organizationId: string,
|
||||
actorRole: UserRole,
|
||||
|
||||
Reference in New Issue
Block a user