380 lines
13 KiB
JavaScript
380 lines
13 KiB
JavaScript
const db = require('../../../database/database');
|
|
const PersonalUser = require('../../../models/PersonalUser');
|
|
const User = require('../../../models/User');
|
|
const { logger } = require('../../../middleware/logger');
|
|
|
|
class PersonalUserRepository {
|
|
constructor(unitOfWork) {
|
|
this.unitOfWork = unitOfWork;
|
|
}
|
|
|
|
async create({ email, password, firstName, lastName, phone, referralEmail }) {
|
|
logger.info('PersonalUserRepository.create:start', { email, firstName, lastName });
|
|
try {
|
|
console.log('📊 PersonalUserRepository: Creating personal user in database...');
|
|
const hashedPassword = await User.hashPassword(password);
|
|
|
|
const conn = this.unitOfWork.connection;
|
|
// 1. Insert into users table
|
|
const userQuery = `
|
|
INSERT INTO users (email, password, user_type, role)
|
|
VALUES (?, ?, 'personal', 'user')
|
|
`;
|
|
const [userResult] = await conn.query(userQuery, [email, hashedPassword]);
|
|
const userId = userResult.insertId;
|
|
|
|
logger.info('PersonalUserRepository.create:user_created', { userId });
|
|
console.log('✅ User record created with ID:', userId);
|
|
|
|
// 2. Insert into personal_profiles table
|
|
const profileQuery = `
|
|
INSERT INTO personal_profiles (user_id, first_name, last_name, phone)
|
|
VALUES (?, ?, ?, ?)
|
|
`;
|
|
await conn.query(profileQuery, [userId, firstName, lastName, phone]);
|
|
|
|
logger.info('PersonalUserRepository.create:profile_created', { userId });
|
|
console.log('✅ Personal profile created');
|
|
|
|
logger.info('PersonalUserRepository.create:success', { userId });
|
|
return new PersonalUser(
|
|
userId,
|
|
email,
|
|
hashedPassword,
|
|
firstName,
|
|
lastName,
|
|
phone,
|
|
null,
|
|
referralEmail,
|
|
new Date()
|
|
);
|
|
} catch (error) {
|
|
logger.error('PersonalUserRepository.create:error', { email, error: error.message });
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async findByEmail(email) {
|
|
logger.info('PersonalUserRepository.findByEmail:start', { email });
|
|
try {
|
|
console.log('🔍 PersonalUserRepository: Querying database for personal user...');
|
|
const conn = this.unitOfWork.connection;
|
|
const query = `
|
|
SELECT u.*, pp.first_name, pp.last_name, pp.phone, pp.date_of_birth
|
|
FROM users u
|
|
LEFT JOIN personal_profiles pp ON u.id = pp.user_id
|
|
WHERE u.email = ? AND u.user_type = 'personal'
|
|
`;
|
|
const [rows] = await conn.query(query, [email]);
|
|
if (rows.length > 0) {
|
|
logger.info('PersonalUserRepository.findByEmail:found', { email, userId: rows[0].id });
|
|
const row = rows[0];
|
|
return new PersonalUser(
|
|
row.id,
|
|
row.email,
|
|
row.password,
|
|
row.first_name,
|
|
row.last_name,
|
|
row.phone,
|
|
row.date_of_birth,
|
|
null,
|
|
row.created_at,
|
|
row.updated_at
|
|
);
|
|
}
|
|
logger.info('PersonalUserRepository.findByEmail:not_found', { email });
|
|
return null;
|
|
} catch (error) {
|
|
logger.error('PersonalUserRepository.findByEmail:error', { email, error: error.message });
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async updateProfileAndMarkCompleted(userId, profileData) {
|
|
logger.info('PersonalUserRepository.updateProfileAndMarkCompleted:start', { userId });
|
|
try {
|
|
const conn = this.unitOfWork.connection;
|
|
const {
|
|
dateOfBirth,
|
|
nationality,
|
|
address,
|
|
zip_code,
|
|
city, // Added city
|
|
country,
|
|
phoneSecondary,
|
|
emergencyContactName,
|
|
emergencyContactPhone,
|
|
accountHolderName,
|
|
iban // Added field
|
|
} = profileData;
|
|
|
|
await conn.query(
|
|
`UPDATE personal_profiles SET
|
|
date_of_birth = ?, nationality = ?, address = ?, zip_code = ?, city = ?, country = ?,
|
|
phone_secondary = ?, emergency_contact_name = ?, emergency_contact_phone = ?,
|
|
account_holder_name = ?
|
|
WHERE user_id = ?`,
|
|
[
|
|
dateOfBirth,
|
|
nationality,
|
|
address,
|
|
zip_code,
|
|
city, // Added city
|
|
country,
|
|
phoneSecondary,
|
|
emergencyContactName,
|
|
emergencyContactPhone,
|
|
accountHolderName,
|
|
userId
|
|
]
|
|
);
|
|
|
|
logger.info('PersonalUserRepository.updateProfileAndMarkCompleted:profile_updated', { userId });
|
|
|
|
// Update IBAN in users table if provided
|
|
if (iban) {
|
|
await conn.query(
|
|
`UPDATE users SET iban = ? WHERE id = ?`,
|
|
[iban, userId]
|
|
);
|
|
logger.info('PersonalUserRepository.updateProfileAndMarkCompleted:iban_updated', { userId });
|
|
}
|
|
|
|
await conn.query(
|
|
`UPDATE user_status SET profile_completed = 1, profile_completed_at = NOW() WHERE user_id = ?`,
|
|
[userId]
|
|
);
|
|
logger.info('PersonalUserRepository.updateProfileAndMarkCompleted:profile_completed', { userId });
|
|
} catch (error) {
|
|
logger.error('PersonalUserRepository.updateProfileAndMarkCompleted:error', { userId, error: error.message });
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async findById(userId) {
|
|
logger.info('PersonalUserRepository.findById:start', { userId });
|
|
try {
|
|
const conn = this.unitOfWork.connection;
|
|
const query = `
|
|
SELECT u.*, pp.first_name, pp.last_name, pp.phone, pp.date_of_birth
|
|
FROM users u
|
|
LEFT JOIN personal_profiles pp ON u.id = pp.user_id
|
|
WHERE u.id = ? AND u.user_type = 'personal'
|
|
`;
|
|
const [rows] = await conn.query(query, [userId]);
|
|
if (rows.length > 0) {
|
|
logger.info('PersonalUserRepository.findById:found', { userId });
|
|
const row = rows[0];
|
|
return new PersonalUser(
|
|
row.id,
|
|
row.email,
|
|
row.password,
|
|
row.first_name,
|
|
row.last_name,
|
|
row.phone,
|
|
row.date_of_birth,
|
|
null,
|
|
row.created_at,
|
|
row.updated_at
|
|
);
|
|
}
|
|
logger.info('PersonalUserRepository.findById:not_found', { userId });
|
|
return null;
|
|
} catch (error) {
|
|
logger.error('PersonalUserRepository.findById:error', { userId, error: error.message });
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Returns merged profile shape used by "GET /api/me"-like responses
|
|
async getMergedProfileById(userId) {
|
|
const conn = this.unitOfWork.connection;
|
|
const [rows] = await conn.query(
|
|
`
|
|
SELECT
|
|
u.id, u.email, u.user_type, u.role, u.iban, u.created_at, u.updated_at,
|
|
pp.first_name, pp.last_name, pp.phone, pp.address, pp.date_of_birth, pp.account_holder_name
|
|
FROM users u
|
|
LEFT JOIN personal_profiles pp ON u.id = pp.user_id
|
|
WHERE u.id = ?
|
|
`,
|
|
[userId]
|
|
);
|
|
if (!rows.length) return null;
|
|
const r = rows[0];
|
|
return {
|
|
id: r.id,
|
|
email: r.email,
|
|
user_type: r.user_type,
|
|
role: r.role,
|
|
firstName: r.first_name,
|
|
lastName: r.last_name,
|
|
phone: r.phone,
|
|
address: r.address,
|
|
dateOfBirth: r.date_of_birth,
|
|
accountHolderName: r.account_holder_name,
|
|
iban: r.iban,
|
|
createdAt: r.created_at,
|
|
updatedAt: r.updated_at
|
|
};
|
|
}
|
|
|
|
async updateBasicInfo(userId, payload) {
|
|
logger.info('PersonalUserRepository.updateBasicInfo:start', { userId });
|
|
const conn = this.unitOfWork.connection;
|
|
|
|
// Lock current rows to compute diffs safely
|
|
const [[currentUser]] = await conn.query(
|
|
`SELECT id, email FROM users WHERE id = ? FOR UPDATE`,
|
|
[userId]
|
|
);
|
|
const [[currentProfile]] = await conn.query(
|
|
`SELECT first_name, last_name, phone, address FROM personal_profiles WHERE user_id = ? FOR UPDATE`,
|
|
[userId]
|
|
);
|
|
if (!currentUser) {
|
|
logger.warn('PersonalUserRepository.updateBasicInfo:not_found', { userId });
|
|
return null;
|
|
}
|
|
|
|
const { firstName, lastName, email, phone, address } = payload;
|
|
|
|
// Compute diffs
|
|
const changes = {};
|
|
const setProfile = {};
|
|
const setUser = {};
|
|
|
|
if (email !== undefined && email !== currentUser.email) {
|
|
// Uniqueness check
|
|
const [conflict] = await conn.query(
|
|
`SELECT id FROM users WHERE email = ? AND id <> ? LIMIT 1`,
|
|
[email, userId]
|
|
);
|
|
if (conflict.length) {
|
|
const err = new Error('Email already in use');
|
|
err.code = 'EMAIL_CONFLICT';
|
|
throw err;
|
|
}
|
|
setUser.email = email;
|
|
changes.email = { from: currentUser.email, to: email };
|
|
}
|
|
|
|
if (firstName !== undefined && firstName !== currentProfile?.first_name) {
|
|
setProfile.first_name = firstName;
|
|
changes.firstName = { from: currentProfile?.first_name ?? null, to: firstName };
|
|
}
|
|
if (lastName !== undefined && lastName !== currentProfile?.last_name) {
|
|
setProfile.last_name = lastName;
|
|
changes.lastName = { from: currentProfile?.last_name ?? null, to: lastName };
|
|
}
|
|
if (phone !== undefined && phone !== currentProfile?.phone) {
|
|
setProfile.phone = phone;
|
|
changes.phone = { from: currentProfile?.phone ?? null, to: phone };
|
|
}
|
|
if (address !== undefined && address !== currentProfile?.address) {
|
|
setProfile.address = address;
|
|
changes.address = { from: currentProfile?.address ?? null, to: address };
|
|
}
|
|
|
|
// Persist updates
|
|
if (Object.keys(setUser).length) {
|
|
await conn.query(
|
|
`UPDATE users SET ${Object.keys(setUser).map(k => `${k} = ?`).join(', ')} WHERE id = ?`,
|
|
[...Object.values(setUser), userId]
|
|
);
|
|
// Email changed: reset verification flags
|
|
if (setUser.email) {
|
|
await conn.query(
|
|
`UPDATE user_status SET email_verified = 0, email_verified_at = NULL WHERE user_id = ?`,
|
|
[userId]
|
|
);
|
|
}
|
|
}
|
|
if (Object.keys(setProfile).length) {
|
|
await conn.query(
|
|
`UPDATE personal_profiles SET ${Object.keys(setProfile).map(k => `${k} = ?`).join(', ')} WHERE user_id = ?`,
|
|
[...Object.values(setProfile), userId]
|
|
);
|
|
}
|
|
|
|
// Audit log
|
|
if (Object.keys(changes).length) {
|
|
await conn.query(
|
|
`INSERT INTO user_action_logs (user_id, action, details) VALUES (?, 'profile_update_basic', ?)`,
|
|
[userId, JSON.stringify({ changes })]
|
|
);
|
|
}
|
|
|
|
logger.info('PersonalUserRepository.updateBasicInfo:success', { userId, changedKeys: Object.keys(changes) });
|
|
return this.getMergedProfileById(userId);
|
|
}
|
|
|
|
async updateBankInfo(userId, payload) {
|
|
logger.info('PersonalUserRepository.updateBankInfo:start', { userId, payloadPreview: { ...payload, ...(payload?.iban ? { iban: `****${String(payload.iban).slice(-4)}` } : {}) } });
|
|
const conn = this.unitOfWork.connection;
|
|
|
|
// Lock current rows to compute diffs safely
|
|
const [[currentUser]] = await conn.query(
|
|
`SELECT id, iban FROM users WHERE id = ? FOR UPDATE`,
|
|
[userId]
|
|
);
|
|
const [[currentProfile]] = await conn.query(
|
|
`SELECT account_holder_name FROM personal_profiles WHERE user_id = ? FOR UPDATE`,
|
|
[userId]
|
|
);
|
|
if (!currentUser) {
|
|
logger.warn('PersonalUserRepository.updateBankInfo:not_found', { userId });
|
|
return null;
|
|
}
|
|
|
|
const { accountHolderName, iban } = payload;
|
|
|
|
const changes = {};
|
|
const setUser = {};
|
|
const setProfile = {};
|
|
|
|
if (iban !== undefined && iban !== currentUser.iban) {
|
|
setUser.iban = iban;
|
|
changes.iban = { from: currentUser.iban ? `****${String(currentUser.iban).slice(-4)}` : null, to: `****${String(iban).slice(-4)}` };
|
|
}
|
|
if (accountHolderName !== undefined && accountHolderName !== currentProfile?.account_holder_name) {
|
|
setProfile.account_holder_name = accountHolderName;
|
|
changes.accountHolderName = {
|
|
from: currentProfile?.account_holder_name ?? null,
|
|
to: accountHolderName
|
|
};
|
|
}
|
|
|
|
logger.info('PersonalUserRepository.updateBankInfo:diff', {
|
|
userId,
|
|
willUpdateUsers: Object.keys(setUser),
|
|
willUpdateProfile: Object.keys(setProfile),
|
|
changes
|
|
});
|
|
|
|
if (Object.keys(setUser).length) {
|
|
await conn.query(
|
|
`UPDATE users SET ${Object.keys(setUser).map(k => `${k} = ?`).join(', ')} WHERE id = ?`,
|
|
[...Object.values(setUser), userId]
|
|
);
|
|
}
|
|
if (Object.keys(setProfile).length) {
|
|
await conn.query(
|
|
`UPDATE personal_profiles SET ${Object.keys(setProfile).map(k => `${k} = ?`).join(', ')} WHERE user_id = ?`,
|
|
[...Object.values(setProfile), userId]
|
|
);
|
|
}
|
|
|
|
if (Object.keys(changes).length) {
|
|
await conn.query(
|
|
`INSERT INTO user_action_logs (user_id, action, details) VALUES (?, 'profile_update_bank', ?)`,
|
|
[userId, JSON.stringify({ changes })]
|
|
);
|
|
}
|
|
|
|
logger.info('PersonalUserRepository.updateBankInfo:success', { userId, changedKeys: Object.keys(changes) });
|
|
return this.getMergedProfileById(userId);
|
|
}
|
|
}
|
|
|
|
module.exports = PersonalUserRepository; |