feat: add VIP search option and profile editing functionality

- Added a new button for ' VIP поиск' in command handlers.
- Implemented profile editing states and methods in message handlers.
- Enhanced profile model to include hobbies, religion, dating goals, and lifestyle preferences.
- Updated profile service to handle new fields and ensure proper database interactions.
- Introduced a VIP function in matching service to find candidates based on dating goals.
This commit is contained in:
2025-09-12 22:13:26 +09:00
parent 17efb2fb53
commit 8893b4ad22
9 changed files with 1528 additions and 50 deletions

View File

@@ -381,4 +381,34 @@ export class MatchingService {
// Используем ProfileService для правильного маппинга данных
return this.profileService.mapEntityToProfile(candidateData);
}
// VIP функция: поиск кандидатов по цели знакомства
async getCandidatesWithGoal(userProfile: Profile, targetGoal: string): Promise<Profile[]> {
const swipedUsersResult = await query(`
SELECT swiped_id
FROM swipes
WHERE swiper_id = $1
`, [userProfile.userId]);
const swipedUserIds = swipedUsersResult.rows.map((row: any) => row.swiped_id);
swipedUserIds.push(userProfile.userId); // Исключаем себя
let candidateQuery = `
SELECT DISTINCT p.*, u.telegram_id, u.username, u.first_name, u.last_name
FROM profiles p
JOIN users u ON p.user_id = u.id
WHERE p.is_visible = true
AND p.is_active = true
AND p.gender = $1
AND p.dating_goal = $2
AND p.user_id NOT IN (${swipedUserIds.map((_: any, i: number) => `$${i + 3}`).join(', ')})
ORDER BY p.created_at DESC
LIMIT 50
`;
const params = [userProfile.interestedIn, targetGoal, ...swipedUserIds];
const result = await query(candidateQuery, params);
return result.rows.map((row: any) => this.profileService.mapEntityToProfile(row));
}
}

View File

@@ -50,13 +50,15 @@ export class ProfileService {
await query(`
INSERT INTO profiles (
id, user_id, name, age, gender, looking_for, bio, photos, interests,
location, education, occupation, height, latitude, longitude,
verification_status, is_active, is_visible, created_at, updated_at
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20)
hobbies, location, education, occupation, height, religion, dating_goal,
latitude, longitude, verification_status, is_active, is_visible,
created_at, updated_at
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23)
`, [
profileId, userId, profile.name, profile.age, profile.gender, profile.interestedIn,
profile.bio, profile.photos, profile.interests,
profile.city, profile.education, profile.job, profile.height,
profile.bio, profile.photos, profile.interests, profile.hobbies,
profile.city, profile.education, profile.job, profile.height,
profile.religion, profile.datingGoal,
profile.location?.latitude, profile.location?.longitude,
'unverified', true, profile.isVisible, profile.createdAt, profile.updatedAt
]);
@@ -106,6 +108,15 @@ export class ProfileService {
return result.rows[0].id;
}
// Получение пользователя по Telegram ID
async getUserByTelegramId(telegramId: string): Promise<any | null> {
const result = await query(`
SELECT * FROM users WHERE telegram_id = $1
`, [parseInt(telegramId)]);
return result.rows.length > 0 ? result.rows[0] : null;
}
// Создание пользователя если не существует
async ensureUser(telegramId: string, userData: any): Promise<string> {
// Используем UPSERT для избежания дублирования
@@ -146,7 +157,7 @@ export class ProfileService {
// Строим динамический запрос обновления
Object.entries(updates).forEach(([key, value]) => {
if (value !== undefined) {
if (value !== undefined && key !== 'updatedAt') { // Исключаем updatedAt из цикла
switch (key) {
case 'photos':
case 'interests':
@@ -175,6 +186,7 @@ export class ProfileService {
return existingProfile;
}
// Всегда добавляем updated_at в конце
updateFields.push(`updated_at = $${paramIndex++}`);
updateValues.push(new Date());
updateValues.push(userId);
@@ -409,10 +421,18 @@ export class ProfileService {
bio: entity.bio,
photos: parsePostgresArray(entity.photos),
interests: parsePostgresArray(entity.interests),
hobbies: entity.hobbies,
city: entity.location || entity.city,
education: entity.education,
job: entity.occupation || entity.job,
height: entity.height,
religion: entity.religion,
datingGoal: entity.dating_goal,
lifestyle: {
smoking: entity.smoking,
drinking: entity.drinking,
kids: entity.has_kids
},
location: entity.latitude && entity.longitude ? {
latitude: entity.latitude,
longitude: entity.longitude
@@ -431,6 +451,18 @@ export class ProfileService {
// Преобразование camelCase в snake_case
private camelToSnake(str: string): string {
// Специальные случаи для некоторых полей
const specialCases: { [key: string]: string } = {
'interestedIn': 'looking_for',
'job': 'occupation',
'city': 'location',
'datingGoal': 'dating_goal'
};
if (specialCases[str]) {
return specialCases[str];
}
return str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
}