Edit login suoerAdmin
This commit is contained in:
@@ -11,14 +11,44 @@ import {
|
||||
import { USER_ROLES, type UserRole } from '@mento/shared';
|
||||
|
||||
export class PlatformLoginDto {
|
||||
@IsEmail()
|
||||
email!: string;
|
||||
@IsString()
|
||||
@MinLength(8)
|
||||
@MaxLength(40)
|
||||
mobile!: string;
|
||||
|
||||
@IsString()
|
||||
@MinLength(6)
|
||||
password!: string;
|
||||
}
|
||||
|
||||
export class CreatePlatformAdminDto {
|
||||
@IsString()
|
||||
@MinLength(2)
|
||||
@MaxLength(120)
|
||||
fullName!: string;
|
||||
|
||||
@IsString()
|
||||
@MinLength(8)
|
||||
@MaxLength(40)
|
||||
mobile!: string;
|
||||
|
||||
@IsString()
|
||||
@MinLength(8)
|
||||
@MaxLength(120)
|
||||
password!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsEmail()
|
||||
email?: string;
|
||||
}
|
||||
|
||||
export class ResetPlatformAdminPasswordDto {
|
||||
@IsString()
|
||||
@MinLength(8)
|
||||
@MaxLength(120)
|
||||
password!: string;
|
||||
}
|
||||
|
||||
export class UpdatePlatformStatusDto {
|
||||
@IsBoolean()
|
||||
isActive!: boolean;
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import {
|
||||
BadRequestException,
|
||||
ConflictException,
|
||||
Injectable,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import * as bcrypt from 'bcryptjs';
|
||||
import type { PlatformAdminDto, PlatformLoginResponse } from '@mento/shared';
|
||||
import { normalizeMobile } from '../auth/auth.service';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { PlatformLoginDto } from './dto';
|
||||
|
||||
@@ -13,9 +19,12 @@ export class PlatformAuthService {
|
||||
) {}
|
||||
|
||||
async login(dto: PlatformLoginDto): Promise<PlatformLoginResponse> {
|
||||
const email = dto.email.trim().toLowerCase();
|
||||
const mobile = normalizeMobile(dto.mobile);
|
||||
if (!mobile) {
|
||||
throw new UnauthorizedException('اطلاعات ورود نادرست است');
|
||||
}
|
||||
const admin = await this.prisma.platformAdmin.findUnique({
|
||||
where: { email },
|
||||
where: { mobile },
|
||||
});
|
||||
if (!admin || admin.isActive === false) {
|
||||
throw new UnauthorizedException('اطلاعات ورود نادرست است');
|
||||
@@ -47,15 +56,25 @@ export class PlatformAuthService {
|
||||
return this.toDto(admin);
|
||||
}
|
||||
|
||||
private toDto(admin: {
|
||||
toDto(admin: {
|
||||
id: string;
|
||||
email: string;
|
||||
mobile: string | null;
|
||||
email: string | null;
|
||||
fullName: string;
|
||||
isActive?: boolean;
|
||||
lastLoginAt?: Date | null;
|
||||
createdAt?: Date;
|
||||
}): PlatformAdminDto {
|
||||
return {
|
||||
id: admin.id,
|
||||
mobile: admin.mobile,
|
||||
email: admin.email,
|
||||
fullName: admin.fullName,
|
||||
...(admin.isActive !== undefined ? { isActive: admin.isActive } : {}),
|
||||
...(admin.lastLoginAt !== undefined
|
||||
? { lastLoginAt: admin.lastLoginAt?.toISOString() ?? null }
|
||||
: {}),
|
||||
...(admin.createdAt ? { createdAt: admin.createdAt.toISOString() } : {}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@ export type PlatformJwtPayload = {
|
||||
|
||||
export type PlatformActor = {
|
||||
platformAdminId: string;
|
||||
email: string;
|
||||
mobile: string | null;
|
||||
email: string | null;
|
||||
fullName: string;
|
||||
};
|
||||
|
||||
@@ -39,6 +40,7 @@ export class PlatformJwtStrategy extends PassportStrategy(
|
||||
where: { id: payload.sub },
|
||||
select: {
|
||||
id: true,
|
||||
mobile: true,
|
||||
email: true,
|
||||
fullName: true,
|
||||
isActive: true,
|
||||
@@ -49,6 +51,7 @@ export class PlatformJwtStrategy extends PassportStrategy(
|
||||
}
|
||||
return {
|
||||
platformAdminId: admin.id,
|
||||
mobile: admin.mobile,
|
||||
email: admin.email,
|
||||
fullName: admin.fullName,
|
||||
};
|
||||
|
||||
@@ -8,14 +8,18 @@ import {
|
||||
Query,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { CurrentUser } from '../auth/current-user.decorator';
|
||||
import {
|
||||
CreatePlatformAdminDto,
|
||||
CreatePlatformOrganizationDto,
|
||||
ResetPlatformAdminPasswordDto,
|
||||
ResetPlatformUserPasswordDto,
|
||||
UpdatePlatformMembershipRoleDto,
|
||||
UpdatePlatformOrganizationDto,
|
||||
UpdatePlatformStatusDto,
|
||||
} from './dto';
|
||||
import { PlatformAuthGuard } from './platform-auth.guard';
|
||||
import type { PlatformActor } from './platform-jwt.strategy';
|
||||
import { PlatformService } from './platform.service';
|
||||
|
||||
@Controller('platform')
|
||||
@@ -28,6 +32,33 @@ export class PlatformController {
|
||||
return this.platform.stats();
|
||||
}
|
||||
|
||||
@Get('admins')
|
||||
listAdmins() {
|
||||
return this.platform.listAdmins();
|
||||
}
|
||||
|
||||
@Post('admins')
|
||||
createAdmin(@Body() dto: CreatePlatformAdminDto) {
|
||||
return this.platform.createAdmin(dto);
|
||||
}
|
||||
|
||||
@Patch('admins/:id')
|
||||
setAdminActive(
|
||||
@Param('id') id: string,
|
||||
@Body() dto: UpdatePlatformStatusDto,
|
||||
@CurrentUser() actor: PlatformActor,
|
||||
) {
|
||||
return this.platform.setAdminActive(id, dto.isActive, actor.platformAdminId);
|
||||
}
|
||||
|
||||
@Patch('admins/:id/password')
|
||||
resetAdminPassword(
|
||||
@Param('id') id: string,
|
||||
@Body() dto: ResetPlatformAdminPasswordDto,
|
||||
) {
|
||||
return this.platform.resetAdminPassword(id, dto);
|
||||
}
|
||||
|
||||
@Get('organizations')
|
||||
listOrganizations(@Query('q') q?: string) {
|
||||
return this.platform.listOrganizations(q);
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import type {
|
||||
PlatformAdminDto,
|
||||
PlatformOrganizationDto,
|
||||
PlatformStatsDto,
|
||||
PlatformUserDto,
|
||||
@@ -19,7 +20,9 @@ import { OrgRolesService } from '../org-roles/org-roles.service';
|
||||
import { PipelinesService } from '../pipelines/pipelines.service';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import type {
|
||||
CreatePlatformAdminDto,
|
||||
CreatePlatformOrganizationDto,
|
||||
ResetPlatformAdminPasswordDto,
|
||||
ResetPlatformUserPasswordDto,
|
||||
UpdatePlatformOrganizationDto,
|
||||
} from './dto';
|
||||
@@ -472,6 +475,133 @@ export class PlatformService {
|
||||
return this.getUser(row.userId);
|
||||
}
|
||||
|
||||
async listAdmins(): Promise<PlatformAdminDto[]> {
|
||||
const rows = await this.prisma.platformAdmin.findMany({
|
||||
orderBy: { createdAt: 'asc' },
|
||||
});
|
||||
return rows.map((row) => this.adminToDto(row));
|
||||
}
|
||||
|
||||
async createAdmin(dto: CreatePlatformAdminDto): Promise<PlatformAdminDto> {
|
||||
const fullName = dto.fullName.trim();
|
||||
if (fullName.length < 2) {
|
||||
throw new BadRequestException('نام معتبر نیست');
|
||||
}
|
||||
const mobile = normalizeMobile(dto.mobile);
|
||||
if (!mobile) {
|
||||
throw new BadRequestException('شماره موبایل معتبر نیست');
|
||||
}
|
||||
const password = dto.password.trim();
|
||||
if (password.length < 8) {
|
||||
throw new BadRequestException('رمز عبور حداقل ۸ کاراکتر لازم است');
|
||||
}
|
||||
const email = dto.email?.trim().toLowerCase() || null;
|
||||
|
||||
const existingMobile = await this.prisma.platformAdmin.findUnique({
|
||||
where: { mobile },
|
||||
select: { id: true },
|
||||
});
|
||||
if (existingMobile) {
|
||||
throw new ConflictException('این شماره موبایل قبلاً ثبت شده است');
|
||||
}
|
||||
if (email) {
|
||||
const existingEmail = await this.prisma.platformAdmin.findUnique({
|
||||
where: { email },
|
||||
select: { id: true },
|
||||
});
|
||||
if (existingEmail) {
|
||||
throw new ConflictException('این ایمیل قبلاً ثبت شده است');
|
||||
}
|
||||
}
|
||||
|
||||
const passwordHash = await bcrypt.hash(password, 10);
|
||||
try {
|
||||
const created = await this.prisma.platformAdmin.create({
|
||||
data: {
|
||||
fullName,
|
||||
mobile,
|
||||
email,
|
||||
passwordHash,
|
||||
},
|
||||
});
|
||||
return this.adminToDto(created);
|
||||
} catch (e) {
|
||||
if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === 'P2002') {
|
||||
throw new ConflictException('موبایل یا ایمیل تکراری است');
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async setAdminActive(
|
||||
id: string,
|
||||
isActive: boolean,
|
||||
actorId: string,
|
||||
): Promise<PlatformAdminDto> {
|
||||
const existing = await this.prisma.platformAdmin.findUnique({
|
||||
where: { id },
|
||||
select: { id: true },
|
||||
});
|
||||
if (!existing) throw new NotFoundException('ادمین پیدا نشد');
|
||||
if (!isActive && id === actorId) {
|
||||
throw new BadRequestException('نمیتوانید حساب خودتان را غیرفعال کنید');
|
||||
}
|
||||
if (!isActive) {
|
||||
const activeCount = await this.prisma.platformAdmin.count({
|
||||
where: { isActive: true, id: { not: id } },
|
||||
});
|
||||
if (activeCount < 1) {
|
||||
throw new BadRequestException('حداقل یک سوپرادمین فعال لازم است');
|
||||
}
|
||||
}
|
||||
const updated = await this.prisma.platformAdmin.update({
|
||||
where: { id },
|
||||
data: { isActive },
|
||||
});
|
||||
return this.adminToDto(updated);
|
||||
}
|
||||
|
||||
async resetAdminPassword(
|
||||
id: string,
|
||||
dto: ResetPlatformAdminPasswordDto,
|
||||
): Promise<{ ok: true }> {
|
||||
const existing = await this.prisma.platformAdmin.findUnique({
|
||||
where: { id },
|
||||
select: { id: true },
|
||||
});
|
||||
if (!existing) throw new NotFoundException('ادمین پیدا نشد');
|
||||
const password = dto.password.trim();
|
||||
if (password.length < 8) {
|
||||
throw new BadRequestException('رمز عبور حداقل ۸ کاراکتر لازم است');
|
||||
}
|
||||
const passwordHash = await bcrypt.hash(password, 10);
|
||||
await this.prisma.platformAdmin.update({
|
||||
where: { id },
|
||||
data: { passwordHash },
|
||||
});
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
private adminToDto(row: {
|
||||
id: string;
|
||||
mobile: string | null;
|
||||
email: string | null;
|
||||
fullName: string;
|
||||
isActive: boolean;
|
||||
lastLoginAt: Date | null;
|
||||
createdAt: Date;
|
||||
}): PlatformAdminDto {
|
||||
return {
|
||||
id: row.id,
|
||||
mobile: row.mobile,
|
||||
email: row.email,
|
||||
fullName: row.fullName,
|
||||
isActive: row.isActive,
|
||||
lastLoginAt: row.lastLoginAt?.toISOString() ?? null,
|
||||
createdAt: row.createdAt.toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
private orgToDto(row: {
|
||||
id: string;
|
||||
name: string;
|
||||
|
||||
Reference in New Issue
Block a user