fix and update
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { PrismaService } from '../infra/prisma.service';
|
||||
import {
|
||||
AdminInsightsQueryOptions,
|
||||
AdminInsightsRetentionService
|
||||
} from './admin-insights-retention.service';
|
||||
|
||||
const MODERATABLE_ROOM_FILTER = {
|
||||
isE2E: false,
|
||||
@@ -23,7 +27,10 @@ const ROOM_TYPE_LABELS: Record<string, string> = {
|
||||
|
||||
@Injectable()
|
||||
export class AdminUserInsightsService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly retention: AdminInsightsRetentionService
|
||||
) {}
|
||||
|
||||
async ensureHumanUser(userId: string) {
|
||||
const user = await this.prisma.user.findFirst({
|
||||
@@ -35,14 +42,17 @@ export class AdminUserInsightsService {
|
||||
return user;
|
||||
}
|
||||
|
||||
async getSignInHistory(userId: string, search?: string, limit = 50, offset = 0) {
|
||||
async getSignInHistory(userId: string, options: AdminInsightsQueryOptions = {}) {
|
||||
await this.ensureHumanUser(userId);
|
||||
const take = Math.min(Math.max(limit, 1), 100);
|
||||
const skip = Math.max(offset, 0);
|
||||
const searchTerm = search?.trim();
|
||||
const range = await this.retention.resolveDateRange(options.dateFrom, options.dateTo);
|
||||
const take = Math.min(Math.max(options.limit ?? 50, 1), 100);
|
||||
const skip = Math.max(options.offset ?? 0, 0);
|
||||
const searchTerm = options.search?.trim();
|
||||
const createdAt = this.retention.createdAtFilter(range);
|
||||
|
||||
const where = {
|
||||
userId,
|
||||
createdAt,
|
||||
...(searchTerm
|
||||
? {
|
||||
OR: [
|
||||
@@ -67,6 +77,7 @@ export class AdminUserInsightsService {
|
||||
]);
|
||||
|
||||
return {
|
||||
...this.retention.insightsMeta(range),
|
||||
total,
|
||||
events: events.map((event) => ({
|
||||
id: event.id,
|
||||
@@ -80,19 +91,21 @@ export class AdminUserInsightsService {
|
||||
};
|
||||
}
|
||||
|
||||
async getActivityTimeline(userId: string, search?: string, limit = 50, offset = 0) {
|
||||
async getActivityTimeline(userId: string, options: AdminInsightsQueryOptions = {}) {
|
||||
await this.ensureHumanUser(userId);
|
||||
const searchTerm = search?.trim().toLowerCase();
|
||||
const take = Math.min(Math.max(limit, 1), 100);
|
||||
const range = await this.retention.resolveDateRange(options.dateFrom, options.dateTo);
|
||||
const searchTerm = options.search?.trim().toLowerCase();
|
||||
const take = Math.min(Math.max(options.limit ?? 50, 1), 100);
|
||||
const createdAt = this.retention.createdAtFilter(range);
|
||||
|
||||
const [logs, documents, messages, rooms, families, consents] = await Promise.all([
|
||||
this.prisma.userActivityLog.findMany({
|
||||
where: { userId },
|
||||
where: { userId, createdAt },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 200
|
||||
}),
|
||||
this.prisma.userDocument.findMany({
|
||||
where: { userId },
|
||||
where: { userId, createdAt },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 100
|
||||
}),
|
||||
@@ -100,6 +113,7 @@ export class AdminUserInsightsService {
|
||||
where: {
|
||||
senderId: userId,
|
||||
deletedAt: null,
|
||||
createdAt,
|
||||
room: MODERATABLE_ROOM_FILTER
|
||||
},
|
||||
include: { room: { include: { group: true } } },
|
||||
@@ -107,18 +121,18 @@ export class AdminUserInsightsService {
|
||||
take: 100
|
||||
}),
|
||||
this.prisma.chatRoom.findMany({
|
||||
where: { createdById: userId, ...MODERATABLE_ROOM_FILTER },
|
||||
where: { createdById: userId, createdAt, ...MODERATABLE_ROOM_FILTER },
|
||||
include: { group: true },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 50
|
||||
}),
|
||||
this.prisma.familyGroup.findMany({
|
||||
where: { ownerId: userId },
|
||||
where: { ownerId: userId, createdAt },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 50
|
||||
}),
|
||||
this.prisma.oAuthConsent.findMany({
|
||||
where: { userId },
|
||||
where: { userId, createdAt },
|
||||
include: { client: true },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 50
|
||||
@@ -200,24 +214,33 @@ export class AdminUserInsightsService {
|
||||
return haystack.includes(searchTerm);
|
||||
});
|
||||
|
||||
const skip = Math.max(offset, 0);
|
||||
const skip = Math.max(options.offset ?? 0, 0);
|
||||
return {
|
||||
...this.retention.insightsMeta(range),
|
||||
total: deduped.length,
|
||||
items: deduped.slice(skip, skip + take)
|
||||
};
|
||||
}
|
||||
|
||||
async listUserChatRooms(userId: string, search?: string, limit = 50, offset = 0) {
|
||||
async listUserChatRooms(userId: string, options: AdminInsightsQueryOptions = {}) {
|
||||
await this.ensureHumanUser(userId);
|
||||
const take = Math.min(Math.max(limit, 1), 100);
|
||||
const skip = Math.max(offset, 0);
|
||||
const searchTerm = search?.trim();
|
||||
const range = await this.retention.resolveDateRange(options.dateFrom, options.dateTo);
|
||||
const take = Math.min(Math.max(options.limit ?? 50, 1), 100);
|
||||
const skip = Math.max(options.offset ?? 0, 0);
|
||||
const searchTerm = options.search?.trim();
|
||||
const createdAt = this.retention.createdAtFilter(range);
|
||||
|
||||
const memberships = await this.prisma.chatRoomMember.findMany({
|
||||
where: {
|
||||
userId,
|
||||
room: {
|
||||
...MODERATABLE_ROOM_FILTER,
|
||||
messages: {
|
||||
some: {
|
||||
senderId: userId,
|
||||
createdAt
|
||||
}
|
||||
},
|
||||
...(searchTerm
|
||||
? {
|
||||
OR: [
|
||||
@@ -238,12 +261,18 @@ export class AdminUserInsightsService {
|
||||
}
|
||||
},
|
||||
messages: {
|
||||
where: { deletedAt: null },
|
||||
where: { deletedAt: null, createdAt },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 1,
|
||||
include: { sender: { select: { displayName: true } } }
|
||||
},
|
||||
_count: { select: { messages: true } }
|
||||
_count: {
|
||||
select: {
|
||||
messages: {
|
||||
where: { senderId: userId, createdAt }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -278,13 +307,15 @@ export class AdminUserInsightsService {
|
||||
});
|
||||
|
||||
return {
|
||||
...this.retention.insightsMeta(range),
|
||||
total: rooms.length,
|
||||
rooms: rooms.slice(skip, skip + take)
|
||||
};
|
||||
}
|
||||
|
||||
async listRoomMessages(userId: string, roomId: string, search?: string, limit = 50, beforeMessageId?: string) {
|
||||
async listRoomMessages(userId: string, roomId: string, options: AdminInsightsQueryOptions = {}) {
|
||||
await this.ensureHumanUser(userId);
|
||||
const range = await this.retention.resolveDateRange(options.dateFrom, options.dateTo);
|
||||
const room = await this.getModeratableRoom(roomId);
|
||||
const member = await this.prisma.chatRoomMember.findUnique({
|
||||
where: { roomId_userId: { roomId, userId } }
|
||||
@@ -293,19 +324,21 @@ export class AdminUserInsightsService {
|
||||
throw new ForbiddenException('Пользователь не состоит в этом чате');
|
||||
}
|
||||
|
||||
const take = Math.min(Math.max(limit, 1), 100);
|
||||
const searchTerm = search?.trim();
|
||||
const take = Math.min(Math.max(options.limit ?? 50, 1), 100);
|
||||
const searchTerm = options.search?.trim();
|
||||
const createdAt = this.retention.createdAtFilter(range);
|
||||
|
||||
let cursorDate: Date | undefined;
|
||||
if (beforeMessageId) {
|
||||
const cursor = await this.prisma.chatMessage.findFirst({ where: { id: beforeMessageId, roomId } });
|
||||
if (options.beforeMessageId) {
|
||||
const cursor = await this.prisma.chatMessage.findFirst({ where: { id: options.beforeMessageId, roomId } });
|
||||
if (cursor) cursorDate = cursor.createdAt;
|
||||
}
|
||||
|
||||
const messages = await this.prisma.chatMessage.findMany({
|
||||
where: {
|
||||
roomId,
|
||||
...(cursorDate ? { createdAt: { lt: cursorDate } } : {}),
|
||||
createdAt,
|
||||
...(cursorDate ? { createdAt: { lt: cursorDate, gte: range.from, lte: range.to } } : {}),
|
||||
...(searchTerm
|
||||
? {
|
||||
OR: [
|
||||
@@ -321,25 +354,30 @@ export class AdminUserInsightsService {
|
||||
});
|
||||
|
||||
return {
|
||||
...this.retention.insightsMeta(range),
|
||||
room: {
|
||||
id: room.id,
|
||||
name: room.name,
|
||||
type: room.type,
|
||||
groupName: room.group.name
|
||||
},
|
||||
messages: messages.reverse().map((message) => this.toAdminMessage(message))
|
||||
messages: messages.reverse().map((message) =>
|
||||
this.toAdminMessage(message, { roomName: room.name, groupName: room.group.name })
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
async searchUserChatMessages(userId: string, search?: string, limit = 50, offset = 0) {
|
||||
async searchUserChatMessages(userId: string, options: AdminInsightsQueryOptions) {
|
||||
await this.ensureHumanUser(userId);
|
||||
const searchTerm = search?.trim();
|
||||
const searchTerm = options.search?.trim();
|
||||
if (!searchTerm) {
|
||||
throw new BadRequestException('Укажите текст для поиска по сообщениям');
|
||||
}
|
||||
|
||||
const take = Math.min(Math.max(limit, 1), 100);
|
||||
const skip = Math.max(offset, 0);
|
||||
const range = await this.retention.resolveDateRange(options.dateFrom, options.dateTo);
|
||||
const take = Math.min(Math.max(options.limit ?? 50, 1), 100);
|
||||
const skip = Math.max(options.offset ?? 0, 0);
|
||||
const createdAt = this.retention.createdAtFilter(range);
|
||||
|
||||
const where = {
|
||||
room: {
|
||||
@@ -347,6 +385,7 @@ export class AdminUserInsightsService {
|
||||
...MODERATABLE_ROOM_FILTER
|
||||
},
|
||||
deletedAt: null,
|
||||
createdAt,
|
||||
OR: [
|
||||
{ content: { contains: searchTerm, mode: 'insensitive' as const } },
|
||||
{ sender: { displayName: { contains: searchTerm, mode: 'insensitive' as const } } }
|
||||
@@ -368,12 +407,13 @@ export class AdminUserInsightsService {
|
||||
]);
|
||||
|
||||
return {
|
||||
...this.retention.insightsMeta(range),
|
||||
total,
|
||||
messages: messages.map((message) => ({
|
||||
...this.toAdminMessage(message),
|
||||
roomId: message.roomId,
|
||||
roomName: message.room.name,
|
||||
groupName: message.room.group.name
|
||||
...this.toAdminMessage(message, {
|
||||
roomName: message.room.name,
|
||||
groupName: message.room.group.name
|
||||
})
|
||||
}))
|
||||
};
|
||||
}
|
||||
@@ -447,17 +487,24 @@ export class AdminUserInsightsService {
|
||||
return text.length > 160 ? `${text.slice(0, 157)}…` : text;
|
||||
}
|
||||
|
||||
private toAdminMessage(message: {
|
||||
id: string;
|
||||
senderId: string;
|
||||
type: string;
|
||||
content: string | null;
|
||||
isEncrypted: boolean;
|
||||
deletedAt: Date | null;
|
||||
createdAt: Date;
|
||||
editedAt: Date | null;
|
||||
sender: { id: string; displayName: string };
|
||||
}) {
|
||||
private toAdminMessage(
|
||||
message: {
|
||||
id: string;
|
||||
roomId: string;
|
||||
senderId: string;
|
||||
type: string;
|
||||
content: string | null;
|
||||
storageKey: string | null;
|
||||
mimeType: string | null;
|
||||
metadata: unknown;
|
||||
isEncrypted: boolean;
|
||||
deletedAt: Date | null;
|
||||
createdAt: Date;
|
||||
editedAt: Date | null;
|
||||
sender: { id: string; displayName: string };
|
||||
},
|
||||
context?: { roomName?: string; groupName?: string }
|
||||
) {
|
||||
return {
|
||||
id: message.id,
|
||||
senderId: message.senderId,
|
||||
@@ -468,7 +515,18 @@ export class AdminUserInsightsService {
|
||||
isEncrypted: message.isEncrypted,
|
||||
isDeleted: Boolean(message.deletedAt),
|
||||
createdAt: message.createdAt.toISOString(),
|
||||
editedAt: message.editedAt?.toISOString()
|
||||
editedAt: message.editedAt?.toISOString(),
|
||||
roomId: message.roomId,
|
||||
roomName: context?.roomName,
|
||||
groupName: context?.groupName,
|
||||
storageKey: message.deletedAt || message.isEncrypted ? undefined : message.storageKey ?? undefined,
|
||||
mimeType: message.deletedAt || message.isEncrypted ? undefined : message.mimeType ?? undefined,
|
||||
metadataJson:
|
||||
message.deletedAt || message.isEncrypted
|
||||
? undefined
|
||||
: message.metadata
|
||||
? JSON.stringify(message.metadata)
|
||||
: undefined
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user