more fix and update

This commit is contained in:
lendry
2026-06-24 20:15:19 +03:00
parent dcab6557d3
commit 9727cf3f35
53 changed files with 3479 additions and 494 deletions

View File

@@ -276,112 +276,109 @@ export class FamilyService {
async sendInvite(requesterId: string, groupId: string, target: string) {
async sendInvite(requesterId: string, groupId: string, options: { target?: string; inviteeUserId?: string }) {
const group = await this.getGroupWithMembers(groupId);
this.assertMember(group, requesterId);
const trimmedTarget = target.trim();
if (!trimmedTarget) {
throw new BadRequestException('Укажите email, телефон или логин приглашаемого');
}
const invitee = await this.findUserByContact(trimmedTarget);
if (invitee?.id === requesterId) {
throw new BadRequestException('Нельзя пригласить самого себя');
}
if (invitee && group.members.some((member) => member.userId === invitee.id)) {
throw new BadRequestException('Пользователь уже состоит в семье');
}
const existingPending = invitee
? await this.prisma.familyInvite.findFirst({
where: { groupId, inviteeUserId: invitee.id, status: 'PENDING' }
const invitee = options.inviteeUserId
? await this.prisma.user.findFirst({
where: { id: options.inviteeUserId, status: 'ACTIVE', deletedAt: null }
})
: options.target
? await this.findUserByContact(options.target)
: null;
: null;
if (existingPending) {
throw new BadRequestException('Приглашение уже отправлено');
if (!invitee) {
throw new BadRequestException('Пользователь не найден. Выберите его из результатов поиска.');
}
if (invitee.id === requesterId) {
throw new BadRequestException('Нельзя пригласить самого себя');
}
if (group.members.some((member) => member.userId === invitee.id)) {
throw new BadRequestException('Пользователь уже состоит в семье');
}
const existingPending = await this.prisma.familyInvite.findFirst({
where: { groupId, inviteeUserId: invitee.id, status: 'PENDING' }
});
if (existingPending) {
throw new BadRequestException('Приглашение уже отправлено');
}
const inviter = await this.prisma.user.findUnique({ where: { id: requesterId } });
const invite = await this.prisma.familyInvite.create({
data: {
groupId,
inviterId: requesterId,
inviteeUserId: invitee?.id,
targetEmail: trimmedTarget.includes('@') ? trimmedTarget : undefined,
targetPhone: !trimmedTarget.includes('@') && /^\+?\d/.test(trimmedTarget) ? trimmedTarget : undefined,
inviteeUserId: invitee.id,
targetEmail: invitee.email ?? undefined,
targetPhone: invitee.phone ?? undefined,
expiresAt: new Date(Date.now() + INVITE_TTL_DAYS * 24 * 60 * 60 * 1000)
},
include: {
group: true,
inviter: true
}
});
if (invitee) {
await this.notifications.create(
invitee.id,
'family_invite',
'Приглашение в семью',
`${inviter?.displayName ?? 'Участник'} приглашает вас в «${group.name}»`,
{ inviteId: invite.id, groupId, groupName: group.name }
);
}
await this.notifications.create(
invitee.id,
'family_invite',
'Приглашение в семью',
`${inviter?.displayName ?? 'Участник'} приглашает вас в «${group.name}»`,
{ inviteId: invite.id, groupId, groupName: group.name }
);
return this.toInvite(invite);
}
async searchInviteUsers(requesterId: string, groupId: string, query: string) {
const group = await this.getGroupWithMembers(groupId);
this.assertMember(group, requesterId);
const trimmed = query.trim();
if (trimmed.length < 2) {
return { users: [] };
}
const excludedIds = [...group.members.map((member) => member.userId), requesterId];
const digits = trimmed.replace(/\D/g, '');
const users = await this.prisma.user.findMany({
where: {
status: 'ACTIVE',
deletedAt: null,
id: { notIn: excludedIds },
OR: [
{ displayName: { contains: trimmed, mode: 'insensitive' } },
{ username: { contains: trimmed, mode: 'insensitive' } },
...(trimmed.includes('@') ? [{ email: { contains: trimmed, mode: 'insensitive' as const } }] : []),
...(digits.length >= 4 ? [{ phone: { contains: digits } }] : [])
]
},
orderBy: { displayName: 'asc' },
take: 8,
select: {
id: true,
displayName: true,
email: true,
phone: true,
username: true,
avatarStorageKey: true
}
});
return {
users: users.map((user) => ({
id: user.id,
displayName: user.displayName,
email: user.email,
phone: user.phone,
username: user.username,
hasAvatar: Boolean(user.avatarStorageKey)
}))
};
}
@@ -610,27 +607,38 @@ export class FamilyService {
private async findUserByContact(target: string) {
if (target.includes('@')) {
return this.prisma.user.findFirst({ where: { email: target, status: 'ACTIVE' } });
const trimmed = target.trim();
if (!trimmed) {
return null;
}
const normalizedPhone = target.replace(/\D/g, '');
if (trimmed.includes('@')) {
return this.prisma.user.findFirst({
where: { email: { equals: trimmed, mode: 'insensitive' }, status: 'ACTIVE', deletedAt: null }
});
}
const digits = trimmed.replace(/\D/g, '');
if (/^\+?\d/.test(trimmed) && digits.length >= 10) {
return this.prisma.user.findFirst({
where: {
status: 'ACTIVE',
deletedAt: null,
OR: [{ phone: trimmed }, { phone: { endsWith: digits.slice(-10) } }]
}
});
}
return this.prisma.user.findFirst({
where: {
status: 'ACTIVE',
OR: [{ phone: target }, { phone: { contains: normalizedPhone.slice(-10) } }, { username: target }]
deletedAt: null,
OR: [
{ username: { equals: trimmed, mode: 'insensitive' } },
{ displayName: { equals: trimmed, mode: 'insensitive' } }
]
}
});
}