const bcrypt = require('bcryptjs'); const { v4: uuidv4 } = require('uuid'); const { body, validationResult } = require('express-validator'); const { getDb } = require('../config/database'); const { generateTokens } = require('../middleware/auth'); const logger = require('../config/logger'); async function register(req, res) { const errors = validationResult(req); if (!errors.isEmpty()) return res.status(422).json({ success: false, errors: errors.array() }); const { name, email, phone, password } = req.body; const db = await getDb(); const existing = await db.get('SELECT id FROM users WHERE email = ?', email); if (existing) return res.status(409).json({ success: false, message: 'Email already registered' }); const hashed = await bcrypt.hash(password, 12); const id = uuidv4(); await db.run('INSERT INTO users (id, email, password, name, phone) VALUES (?, ?, ?, ?, ?)', id, email, hashed, name, phone || null); const { access, refresh } = generateTokens(id); logger.info(`New user registered: ${email}`); return res.status(201).json({ success: true, user: { id, email, name, phone: phone || null, plan: 'free' }, tokens: { access, refresh }, }); } async function login(req, res) { const errors = validationResult(req); if (!errors.isEmpty()) return res.status(422).json({ success: false, errors: errors.array() }); const { email, password } = req.body; const db = await getDb(); const user = await db.get('SELECT * FROM users WHERE email = ?', email); if (!user) return res.status(401).json({ success: false, message: 'Invalid credentials' }); const valid = await bcrypt.compare(password, user.password); if (!valid) return res.status(401).json({ success: false, message: 'Invalid credentials' }); const { access, refresh } = generateTokens(user.id); logger.info(`User logged in: ${email}`); return res.json({ success: true, user: { id: user.id, email: user.email, name: user.name, plan: user.plan }, tokens: { access, refresh }, }); } async function refreshToken(req, res) { const { refreshToken } = req.body; if (!refreshToken) return res.status(400).json({ success: false, message: 'Refresh token required' }); try { const jwt = require('jsonwebtoken'); const decoded = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET); const { access, refresh } = generateTokens(decoded.id); return res.json({ success: true, tokens: { access, refresh } }); } catch { return res.status(401).json({ success: false, message: 'Invalid refresh token' }); } } async function getMe(req, res) { const db = await getDb(); const user = await db.get('SELECT id, email, name, plan, avatar_url, created_at FROM users WHERE id = ?', req.user.id); return res.json({ success: true, user }); } async function updateProfile(req, res) { const { name, currentPassword, newPassword } = req.body; const db = await getDb(); const user = await db.get('SELECT * FROM users WHERE id = ?', req.user.id); if (newPassword) { if (!currentPassword) return res.status(400).json({ success: false, message: 'Current password required' }); const valid = await bcrypt.compare(currentPassword, user.password); if (!valid) return res.status(400).json({ success: false, message: 'Current password incorrect' }); const hashed = await bcrypt.hash(newPassword, 12); await db.run('UPDATE users SET password = ?, updated_at = datetime("now") WHERE id = ?', hashed, req.user.id); } if (name) { await db.run('UPDATE users SET name = ?, updated_at = datetime("now") WHERE id = ?', name, req.user.id); } const updated = await db.get('SELECT id, email, name, plan, avatar_url FROM users WHERE id = ?', req.user.id); return res.json({ success: true, user: updated }); } const { appendUserToSheet, getUserByPhone } = require('../services/googleSheetsService'); async function loginWithPhone(req, res) { const { phone, name, email } = req.body; if (!phone) return res.status(400).json({ success: false, message: 'Mobile number is required' }); const db = await getDb(); let user = await db.get('SELECT * FROM users WHERE phone = ?', phone); // 1. Returning User (Exists Locally) if (user) { if (name || email) { return res.status(409).json({ success: false, message: 'Mobile number already registered. Please Log In.' }); } const { access, refresh } = generateTokens(user.id); logger.info(`User logged in instantly via phone: ${phone}`); return res.json({ success: true, user: { id: user.id, email: user.email, name: user.name, phone: user.phone, plan: user.plan }, tokens: { access, refresh }, }); } // 2. New User Sign Up (Provided Name and Email) if (name && email) { const emailCheck = await db.get('SELECT id FROM users WHERE email = ?', email); if (emailCheck) return res.status(409).json({ success: false, message: 'Email already registered' }); const id = uuidv4(); const hashed = await bcrypt.hash(uuidv4(), 10); await db.run('INSERT INTO users (id, email, password, name, phone) VALUES (?, ?, ?, ?, ?)', id, email, hashed, name, phone); // Background async sheet append (Non-blocking!) appendUserToSheet({ name, email, phone }).catch(err => logger.error('Background sheet append failed:', err)); const { access, refresh } = generateTokens(id); logger.info(`New user registered instantly via phone: ${phone}`); return res.json({ success: true, user: { id, email, name, phone, plan: 'free' }, tokens: { access, refresh }, }); } // 3. Fallback: User trying to Log In but not found locally. Check Google Sheets. try { const sheetUser = await getUserByPhone(phone); if (!sheetUser) { return res.status(404).json({ success: false, message: 'Mobile number not found. Please Sign Up to continue.' }); } // Found in Google Sheets, import locally const id = uuidv4(); const hashed = await bcrypt.hash(uuidv4(), 10); const emailCheck = await db.get('SELECT id FROM users WHERE email = ?', sheetUser.email); if (emailCheck) return res.status(409).json({ success: false, message: 'Email already registered' }); await db.run('INSERT INTO users (id, email, password, name, phone) VALUES (?, ?, ?, ?, ?)', id, sheetUser.email, hashed, sheetUser.name, phone); const { access, refresh } = generateTokens(id); logger.info(`User logged in (imported from sheets): ${phone}`); return res.json({ success: true, user: { id, email: sheetUser.email, name: sheetUser.name, phone, plan: 'free' }, tokens: { access, refresh }, }); } catch (err) { logger.error(`Error querying Google Sheets for ${phone}: ${err.message}`); return res.status(500).json({ success: false, message: 'Failed to verify mobile number. Please try signing up.' }); } } module.exports = { register, login, refreshToken, getMe, updateProfile, loginWithPhone, registerRules: [], loginRules: [] };