fix document 500 error and fix users tabel for bot

This commit is contained in:
lendry
2026-06-26 10:32:04 +03:00
parent ead3155ad8
commit d5e6b58955
17 changed files with 396 additions and 67 deletions

View File

@@ -4,6 +4,32 @@ import { UserStatus } from '../generated/prisma/client';
import { PrismaService } from '../infra/prisma.service';
import { AccessService } from './access.service';
import { DEFAULT_VERIFICATION_ICON, isAllowedVerificationIcon, VERIFICATION_ICONS } from './verification.constants';
import { isProtectedSystemAccount } from './system-account.util';
const HUMAN_USERS_WHERE = {
isSystemAccount: false,
linkedBotId: null
};
function buildBotAccountsWhere(search?: string) {
const botFilter = {
OR: [{ linkedBotId: { not: null } }, { isSystemAccount: true }]
};
if (!search) return botFilter;
return {
AND: [
botFilter,
{
OR: [
{ displayName: { contains: search, mode: 'insensitive' as const } },
{ username: { contains: search, mode: 'insensitive' as const } },
{ linkedBot: { username: { contains: search, mode: 'insensitive' as const } } },
{ linkedBot: { name: { contains: search, mode: 'insensitive' as const } } }
]
}
]
};
}
@Injectable()
export class AdminService {
@@ -14,16 +40,19 @@ export class AdminService {
async listUsers(search?: string) {
const users = await this.prisma.user.findMany({
where: search
? {
OR: [
{ email: { contains: search, mode: 'insensitive' } },
{ phone: { contains: search, mode: 'insensitive' } },
{ displayName: { contains: search, mode: 'insensitive' } },
{ username: { contains: search, mode: 'insensitive' } }
]
}
: undefined,
where: {
...HUMAN_USERS_WHERE,
...(search
? {
OR: [
{ email: { contains: search, mode: 'insensitive' } },
{ phone: { contains: search, mode: 'insensitive' } },
{ displayName: { contains: search, mode: 'insensitive' } },
{ username: { contains: search, mode: 'insensitive' } }
]
}
: {})
},
orderBy: { createdAt: 'desc' },
include: {
userRoles: { include: { role: true } },
@@ -34,14 +63,28 @@ export class AdminService {
return users.map((user) => this.toAdminUser(user));
}
async listBotAccounts(search?: string) {
const users = await this.prisma.user.findMany({
where: buildBotAccountsWhere(search),
orderBy: { createdAt: 'desc' },
include: {
userRoles: { include: { role: true } },
userPermissions: { include: { permission: true } },
linkedBot: { select: { username: true, isSystemBot: true } }
}
});
return users.map((user) => this.toAdminUser(user, true));
}
async updateUserProfile(userId: string, data: { displayName?: string; email?: string; phone?: string; backupEmail?: string; backupPhone?: string; status?: UserStatus }) {
await this.ensureUserExists(userId);
const user = await this.prisma.user.update({
where: { id: userId },
data,
include: { userRoles: { include: { role: true } }, userPermissions: { include: { permission: true } } }
include: { userRoles: { include: { role: true } }, userPermissions: { include: { permission: true } }, linkedBot: { select: { username: true, isSystemBot: true } } }
});
return this.toAdminUser(user);
return this.toAdminUser(user, isProtectedSystemAccount(user));
}
async resetPassword(userId: string, newPassword: string) {
@@ -53,9 +96,9 @@ export class AdminService {
const user = await this.prisma.user.update({
where: { id: userId },
data: { passwordHash: await bcrypt.hash(newPassword, 12) },
include: { userRoles: { include: { role: true } }, userPermissions: { include: { permission: true } } }
include: { userRoles: { include: { role: true } }, userPermissions: { include: { permission: true } }, linkedBot: { select: { username: true, isSystemBot: true } } }
});
return this.toAdminUser(user);
return this.toAdminUser(user, isProtectedSystemAccount(user));
}
async suspendUser(userId: string) {
@@ -64,9 +107,9 @@ export class AdminService {
const user = await this.prisma.user.update({
where: { id: userId },
data: { status: UserStatus.SUSPENDED },
include: { userRoles: { include: { role: true } }, userPermissions: { include: { permission: true } } }
include: { userRoles: { include: { role: true } }, userPermissions: { include: { permission: true } }, linkedBot: { select: { username: true, isSystemBot: true } } }
});
return this.toAdminUser(user);
return this.toAdminUser(user, isProtectedSystemAccount(user));
}
async setSuperAdmin(actorUserId: string, userId: string, isSuperAdmin: boolean) {
@@ -77,6 +120,9 @@ export class AdminService {
if (!target) {
throw new NotFoundException('Пользователь не найден');
}
if (isProtectedSystemAccount(target)) {
throw new ForbiddenException('Нельзя назначать системные учётные записи ботов супер-администраторами');
}
if (target.isSuperAdmin && !isSuperAdmin) {
const superAdmins = await this.prisma.user.count({ where: { isSuperAdmin: true, status: UserStatus.ACTIVE } });
@@ -88,9 +134,9 @@ export class AdminService {
const user = await this.prisma.user.update({
where: { id: userId },
data: { isSuperAdmin },
include: { userRoles: { include: { role: true } }, userPermissions: { include: { permission: true } } }
include: { userRoles: { include: { role: true } }, userPermissions: { include: { permission: true } }, linkedBot: { select: { username: true, isSystemBot: true } } }
});
return this.toAdminUser(user);
return this.toAdminUser(user, isProtectedSystemAccount(user));
}
async setUserVerification(actorUserId: string, userId: string, data: { isVerified: boolean; verificationIcon?: string }) {
@@ -109,9 +155,9 @@ export class AdminService {
verificationIcon: icon,
verifiedAt: new Date()
},
include: { userRoles: { include: { role: true } }, userPermissions: { include: { permission: true } } }
include: { userRoles: { include: { role: true } }, userPermissions: { include: { permission: true } }, linkedBot: { select: { username: true, isSystemBot: true } } }
});
return this.toAdminUser(user);
return this.toAdminUser(user, isProtectedSystemAccount(user));
}
const user = await this.prisma.user.update({
@@ -121,9 +167,9 @@ export class AdminService {
verificationIcon: null,
verifiedAt: null
},
include: { userRoles: { include: { role: true } }, userPermissions: { include: { permission: true } } }
include: { userRoles: { include: { role: true } }, userPermissions: { include: { permission: true } }, linkedBot: { select: { username: true, isSystemBot: true } } }
});
return this.toAdminUser(user);
return this.toAdminUser(user, isProtectedSystemAccount(user));
}
listVerificationIcons() {
@@ -135,27 +181,33 @@ export class AdminService {
const user = await this.prisma.user.update({
where: { id: userId },
data: { status: UserStatus.DELETED, deletedAt: new Date() },
include: { userRoles: { include: { role: true } }, userPermissions: { include: { permission: true } } }
include: { userRoles: { include: { role: true } }, userPermissions: { include: { permission: true } }, linkedBot: { select: { username: true, isSystemBot: true } } }
});
return this.toAdminUser(user);
return this.toAdminUser(user, isProtectedSystemAccount(user));
}
private toAdminUser(user: {
id: string;
email: string | null;
phone: string | null;
backupEmail: string | null;
backupPhone: string | null;
displayName: string;
username: string | null;
isSuperAdmin: boolean;
isVerified: boolean;
verificationIcon: string | null;
status: UserStatus;
createdAt: Date;
userRoles?: { role: { slug: string } }[];
userPermissions?: { permission: { slug: string } }[];
}) {
private toAdminUser(
user: {
id: string;
email: string | null;
phone: string | null;
backupEmail: string | null;
backupPhone: string | null;
displayName: string;
username: string | null;
isSuperAdmin: boolean;
isVerified: boolean;
verificationIcon: string | null;
status: UserStatus;
createdAt: Date;
isSystemAccount?: boolean;
linkedBotId?: string | null;
userRoles?: { role: { slug: string } }[];
userPermissions?: { permission: { slug: string } }[];
linkedBot?: { username: string; isSystemBot: boolean } | null;
},
isBot = isProtectedSystemAccount(user)
) {
return {
id: user.id,
email: user.email ?? undefined,
@@ -170,7 +222,10 @@ export class AdminService {
status: user.status,
createdAt: user.createdAt.toISOString(),
roles: user.userRoles?.map((link) => link.role.slug) ?? [],
directPermissions: user.userPermissions?.map((link) => link.permission.slug) ?? []
directPermissions: user.userPermissions?.map((link) => link.permission.slug) ?? [],
isBot,
linkedBotUsername: user.linkedBot?.username ?? undefined,
isSystemBot: user.linkedBot?.isSystemBot ?? false
};
}