more fix and update
This commit is contained in:
@@ -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' } }
|
||||
]
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user