PHP 8.2.30
Preview: authController.js Size: 30.77 KB
/home/byroehnu/easepay.easetack.com/controllers/authController.js

const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const { prisma } = require('../config/prisma');
const otpService = require('../services/otpService');

// Validate JWT secret on startup
if (!process.env.JWT_SECRET || process.env.JWT_SECRET.length < 32) {
  console.error('❌ JWT_SECRET must be at least 32 characters long');
  process.exit(1);
}

// Generate JWT token
const generateToken = (id) => {
  return jwt.sign({ id }, process.env.JWT_SECRET, {
    expiresIn: process.env.JWT_EXPIRES_IN || '7d'
  });
};

// Generate OTP
const generateOTP = () => {
  return Math.floor(100000 + Math.random() * 900000).toString();
};

// Send OTP
const sendOTP = async (req, res) => {
  try {
    const { phone, method = 'sms' } = req.body;

    if (!phone) {
      return res.status(400).json({
        success: false,
        message: 'Please provide phone number'
      });
    }

    // Validate phone number format
    const phoneValidation = otpService.validatePhoneNumber(phone);
    if (!phoneValidation.isValid) {
      return res.status(400).json({
        success: false,
        message: phoneValidation.error
      });
    }

    const formattedPhone = phoneValidation.formatted;

    // Check if user exists, if not create a temporary user record
    const existingUser = await prisma.user.findUnique({
      where: { phone: formattedPhone }
    });

    try {
      // Send OTP via selected method using OTP.dev
      let sendResult;
      if (method === 'whatsapp') {
        sendResult = await otpService.sendWhatsApp(formattedPhone);
      } else {
        sendResult = await otpService.sendSMS(formattedPhone);
      }

      if (!sendResult.success) {
        throw new Error(sendResult.message || 'Failed to send OTP');
      }

      // Calculate expiry time
      const otpExpiresAt = new Date(Date.now() + (process.env.OTP_EXPIRY_MINUTES || 10) * 60 * 1000);

      if (existingUser) {
        // Update existing user with verification ID
        await prisma.user.update({
          where: { phone: formattedPhone },
          data: {
            otpVerificationId: sendResult.verificationId,
            otpExpiresAt: otpExpiresAt
          }
        });
      } else {
        // Create new user with verification ID
        await prisma.user.create({
          data: {
            phone: formattedPhone,
            email: `temp_${Date.now()}_${formattedPhone.replace(/[^0-9]/g, '')}@easepay.temp`,
            password: await bcrypt.hash('temporary_password_' + Date.now(), 12),
            firstName: 'User',
            lastName: 'Temp',
            otpVerificationId: sendResult.verificationId,
            otpExpiresAt: otpExpiresAt
          }
        });
      }

      console.log(`✅ OTP sent successfully to ${formattedPhone} via ${method}`);
      console.log(`⏰ OTP expires at: ${otpExpiresAt}`);
      console.log(`🆔 Verification ID: ${sendResult.verificationId}`);

      res.status(200).json({
        success: true,
        message: `OTP sent successfully via ${method}`,
        phone: formattedPhone,
        verificationId: sendResult.verificationId,
        expiresIn: `${process.env.OTP_EXPIRY_MINUTES || 10} minutes`,
        expiresAt: sendResult.expiresAt || otpExpiresAt
      });

    } catch (smsError) {
      console.error('❌ OTP sending failed:', smsError.message);
      
      res.status(500).json({
        success: false,
        message: 'Failed to send OTP. Please try again.',
        error: process.env.NODE_ENV === 'development' ? smsError.message : undefined
      });
    }

  } catch (error) {
    console.error('Send OTP error:', error);
    res.status(500).json({
      success: false,
      message: 'Failed to send OTP'
    });
  }
};

// Verify OTP
const verifyOTP = async (req, res) => {
  try {
    const { phone, otp, verificationId } = req.body;
    
    console.log(`🔍 === OTP VERIFICATION DEBUG ===`);
    console.log(`📞 Phone: ${phone}`);
    console.log(`🔢 Received OTP: "${otp}" (length: ${otp?.length}, type: ${typeof otp})`);
    console.log(`🆔 Verification ID: ${verificationId}`);

    if (!phone || !otp) {
      console.log(`❌ Missing phone or OTP`);
      return res.status(400).json({
        success: false,
        message: 'Please provide phone number and OTP'
      });
    }

    // Validate OTP format
    if (!/^\d{5}$/.test(otp)) {
      console.log(`❌ Invalid OTP format: "${otp}"`);
      return res.status(400).json({
        success: false,
        message: 'OTP must be exactly 5 digits'
      });
    }

    console.log(`🔍 Verifying OTP for ${phone}: ${otp}`);

    // Find user with matching phone
    const user = await prisma.user.findUnique({
      where: { phone },
      select: {
        id: true,
        phone: true,
        otpVerificationId: true,
        otpExpiresAt: true,
        phoneVerified: true,
        firstName: true,
        lastName: true,
        email: true,
        role: true
      }
    });

    if (!user) {
      console.log(`❌ User not found for phone: ${phone}`);
      return res.status(404).json({
        success: false,
        message: 'User not found. Please request OTP first.'
      });
    }

    console.log(`📋 User found:`);
    console.log(`   - Phone: ${user.phone}`);
    console.log(`   - Verification ID: ${user.otpVerificationId}`);
    console.log(`   - OTP Expires: ${user.otpExpiresAt}`);
    console.log(`   - Phone Verified: ${user.phoneVerified}`);

    // Check if OTP is expired
    if (!user.otpExpiresAt || new Date() > user.otpExpiresAt) {
      console.log(`❌ OTP expired. Expires: ${user.otpExpiresAt}, Now: ${new Date()}`);
      return res.status(400).json({
        success: false,
        message: 'OTP has expired. Please request a new one.'
      });
    }

    try {
      let verificationResult = { success: false };

      // Fallback: Allow any 5-digit OTP in development
      if (process.env.NODE_ENV === 'development' && otp && otp.length === 5) {
        console.log(`🛠️ Development fallback - accepting OTP: ${otp}`);
        verificationResult = { success: true, message: 'Development mode OTP accepted' };
      } else {
        // Use OTP.dev to verify the OTP
        verificationResult = await otpService.verifyOTP(
          verificationId || user.otpVerificationId, 
          otp
        );
      }

      if (!verificationResult.success) {
        console.log(`❌ OTP verification failed: ${verificationResult.message}`);
        return res.status(400).json({
          success: false,
          message: verificationResult.message || 'Invalid OTP'
        });
      }

      console.log(`✅ OTP verification passed!`);

      // Mark phone as verified and clear OTP data
      await prisma.user.update({
        where: { id: user.id },
        data: {
          phoneVerified: true,
          otpVerificationId: null,
          otpExpiresAt: null
        }
      });

      // Generate token
      const token = generateToken(user.id);

      console.log(`✅ Phone ${phone} verified successfully! Token generated.`);

      res.status(200).json({
        success: true,
        message: 'Phone verified successfully',
        token,
        user: {
          id: user.id,
          phone: user.phone,
          firstName: user.firstName,
          lastName: user.lastName,
          email: user.email,
          role: user.role,
          phoneVerified: true
        }
      });

    } catch (verificationError) {
      console.error('❌ OTP.dev verification error:', verificationError.message);
      
      return res.status(400).json({
        success: false,
        message: 'OTP verification failed. Please try again.'
      });
    }

  } catch (error) {
    console.error('Verify OTP error:', error);
    res.status(500).json({
      success: false,
      message: 'Failed to verify OTP'
    });
  }
};

// Phone-based login (send OTP for existing users)
const phoneLogin = async (req, res) => {
  try {
    const { phone } = req.body;

    if (!phone) {
      return res.status(400).json({
        success: false,
        message: 'Please provide phone number'
      });
    }

    // Check if user exists
    const user = await prisma.user.findUnique({
      where: { phone }
    });

    if (!user) {
      return res.status(404).json({
        success: false,
        message: 'No account found with this phone number. Please register first.'
      });
    }

    // Generate OTP
    const otp = generateOTP();
    const otpExpiresAt = new Date(Date.now() + 10 * 60 * 1000); // 10 minutes

    // Update user with OTP
    await prisma.user.update({
      where: { phone },
      data: {
        otp,
        otpExpiresAt
      }
    });

    // Log OTP to console (in production, send via SMS)
    console.log(`📱 Login OTP for ${phone}: ${otp}`);
    console.log(`⏰ OTP expires at: ${otpExpiresAt}`);

    res.status(200).json({
      success: true,
      message: 'OTP sent for login',
      phone,
      // In development, include OTP in response (remove in production)
      otp: otp
    });

  } catch (error) {
    console.error('Phone login error:', error);
    res.status(500).json({
      success: false,
      message: 'Failed to send login OTP'
    });
  }
};


const registerUser = async (req, res) => {
  try {
    const { email, password, firstName, lastName, phone, businessName } = req.body;

    // Validation
    if (!email || !password || !firstName || !lastName) {
      return res.status(400).json({
        success: false,
        message: 'Please provide all required fields'
      });
    }

    // Check if user already exists
    const existingUser = await prisma.user.findUnique({
      where: { email }
    });

    if (existingUser) {
      return res.status(400).json({
        success: false,
        message: 'User already exists with this email'
      });
    }

    // Hash password
    const salt = await bcrypt.genSalt(12);
    const hashedPassword = await bcrypt.hash(password, salt);

    // Create user
    const newUser = await prisma.user.create({
      data: {
        email,
        password: hashedPassword,
        firstName,
        lastName,
        phone,
        businessName
      },
      select: {
        id: true,
        email: true,
        firstName: true,
        lastName: true,
        phone: true,
        businessName: true,
        createdAt: true
      }
    });

    // Generate token
    const token = generateToken(newUser.id);

    res.status(201).json({
      success: true,
      message: 'User registered successfully',
      token,
      user: newUser
    });
  } catch (error) {
    console.error('Register error:', error);
    res.status(500).json({
      success: false,
      message: 'Registration failed'
    });
  }
};


const loginUser = async (req, res) => {
  try {
    const { email, password } = req.body;

    // Validation
    if (!email || !password) {
      return res.status(400).json({
        success: false,
        message: 'Please provide email and password'
      });
    }

    // Check if user exists
    const user = await prisma.user.findUnique({
      where: { email },
      select: {
        id: true,
        email: true,
        password: true,
        firstName: true,
        lastName: true,
        phone: true,
        businessName: true,
        isActive: true
      }
    });

    if (!user) {
      return res.status(401).json({
        success: false,
        message: 'Invalid credentials'
      });
    }

    // Check if user is active
    if (!user.isActive) {
      return res.status(401).json({
        success: false,
        message: 'Account has been deactivated'
      });
    }

    // Check password
    const isPasswordValid = await bcrypt.compare(password, user.password);

    if (!isPasswordValid) {
      return res.status(401).json({
        success: false,
        message: 'Invalid credentials'
      });
    }

    // Generate token
    const token = generateToken(user.id);

    // Remove password from response
    const { password: _, ...userWithoutPassword } = user;

    res.status(200).json({
      success: true,
      message: 'Login successful',
      token,
      user: userWithoutPassword
    });
  } catch (error) {
    console.error('Login error:', error);
    res.status(500).json({
      success: false,
      message: 'Login failed'
    });
  }
};


const getMe = async (req, res) => {
  try {
    const user = await prisma.user.findUnique({
      where: { id: req.user.id },
      select: {
        id: true,
        email: true,
        firstName: true,
        lastName: true,
        phone: true,
        businessName: true,
        isActive: true,
        emailVerified: true,
        createdAt: true
      }
    });

    if (!user) {
      return res.status(404).json({
        success: false,
        message: 'User not found'
      });
    }

    res.status(200).json({
      success: true,
      user
    });
  } catch (error) {
    console.error('Get me error:', error);
    res.status(500).json({
      success: false,
      message: 'Failed to get user data'
    });
  }
};

const getInvitationDetails = async (req, res) => {
    try {
        const { token } = req.params;

        const invitation = await prisma.staffInvitation.findUnique({
            where: { token },
            include: {
                business: {
                    select: {
                        name: true
                    }
                }
            }
        });

        if (!invitation) {
            return res.status(404).json({ success: false, message: 'Invitation not found' });
        }

        if (invitation.status !== 'PENDING') {
            return res.status(400).json({ success: false, message: `Invitation has already been ${invitation.status.toLowerCase()}` });
        }

        if (new Date() > invitation.expiresAt) {
            return res.status(400).json({ success: false, message: 'Invitation has expired' });
        }

        res.status(200).json({
            success: true,
            data: {
                businessName: invitation.business.name,
                role: invitation.role,
                expiresAt: invitation.expiresAt
            }
        });
    } catch (error) {
        console.error('Get invitation details error:', error);
        res.status(500).json({ success: false, message: 'Failed to fetch invitation details' });
    }
};

const acceptStaffInvitation = async (req, res) => {
    try {
        const { token } = req.body;
        const userId = req.user.id;

        const invitation = await prisma.staffInvitation.findUnique({
            where: { token }
        });

        if (!invitation || invitation.status !== 'PENDING' || new Date() > invitation.expiresAt) {
            return res.status(400).json({ success: false, message: 'Invalid or expired invitation' });
        }

        // Check if user is already staff
        const existingStaff = await prisma.businessStaff.findUnique({
            where: {
                businessId_userId: {
                    businessId: invitation.businessId,
                    userId
                }
            }
        });

        if (existingStaff) {
            return res.status(400).json({ success: false, message: 'You are already a staff member of this business' });
        }

        // Link user to business
        await prisma.$transaction([
            prisma.businessStaff.create({
                data: {
                    businessId: invitation.businessId,
                    userId: userId,
                    role: invitation.role,
                    permissions: invitation.permissions
                }
            }),
            prisma.staffInvitation.update({
                where: { id: invitation.id },
                data: {
                    status: 'ACCEPTED',
                    acceptedById: userId
                }
            }),
            prisma.business.update({
                where: { id: invitation.businessId },
                data: { hasStaff: true }
            })
        ]);

        res.status(200).json({
            success: true,
            message: 'Invitation accepted successfully'
        });

    } catch (error) {
        console.error('Accept invitation error:', error);
        res.status(500).json({ success: false, message: 'Failed to accept invitation' });
    }
};

// Setup Password (for settings)
const setupPassword = async (req, res) => {
  try {
    const { password, confirmPassword } = req.body;
    const userId = req.user.id;

    // Validation
    if (!password || !confirmPassword) {
      return res.status(400).json({
        success: false,
        message: 'Password and confirmation are required'
      });
    }

    if (password !== confirmPassword) {
      return res.status(400).json({
        success: false,
        message: 'Passwords do not match'
      });
    }

    // Password strength validation
    if (password.length < 6) {
      return res.status(400).json({
        success: false,
        message: 'Password must be at least 6 characters long'
      });
    }

    // Check if user exists
    const user = await prisma.user.findUnique({
      where: { id: userId }
    });

    if (!user) {
      return res.status(404).json({
        success: false,
        message: 'User not found'
      });
    }

    // Hash the password
    const salt = await bcrypt.genSalt(12);
    const hashedPassword = await bcrypt.hash(password, salt);

    // Update user with password
    await prisma.user.update({
      where: { id: userId },
      data: {
        password: hashedPassword,
        updatedAt: new Date()
      }
    });

    res.status(200).json({
      success: true,
      message: 'Password setup successful. You can now sign in with your email/phone and password.'
    });

  } catch (error) {
    console.error('Setup password error:', error);
    res.status(500).json({
      success: false,
      message: 'Internal server error',
      error: process.env.NODE_ENV === 'development' ? error.message : 'Something went wrong'
    });
  }
};

// Sign In with Email/Phone and Password
const signIn = async (req, res) => {
  try {
    const { identifier, password } = req.body;

    // Validation
    if (!identifier || !password) {
      return res.status(400).json({
        success: false,
        message: 'Email/phone and password are required'
      });
    }

    // Find user by email or phone
    const user = await prisma.user.findFirst({
      where: {
        OR: [
          { email: identifier },
          { phone: identifier }
        ],
        isActive: true
      }
    });

    if (!user) {
      return res.status(401).json({
        success: false,
        message: 'Invalid credentials'
      });
    }

    // Check if user has set up a password
    if (!user.password) {
      return res.status(400).json({
        success: false,
        message: 'Password not set. Please set up your password in settings first.'
      });
    }

    // Verify password
    const isPasswordValid = await bcrypt.compare(password, user.password);

    if (!isPasswordValid) {
      // Increment login attempts
      await prisma.user.update({
        where: { id: user.id },
        data: {
          loginAttempts: user.loginAttempts + 1,
          ...(user.loginAttempts >= 4 && {
            lockedUntil: new Date(Date.now() + 15 * 60 * 1000) // Lock for 15 minutes
          })
        }
      });

      return res.status(401).json({
        success: false,
        message: 'Invalid credentials',
        ...(user.loginAttempts >= 4 && {
          lockout: 'Account temporarily locked due to multiple failed attempts'
        })
      });
    }

    // Check if account is locked
    if (user.lockedUntil && user.lockedUntil > new Date()) {
      return res.status(423).json({
        success: false,
        message: 'Account is temporarily locked. Please try again later.'
      });
    }

    // Generate session token
    const sessionToken = jwt.sign({ id: user.id }, process.env.JWT_SECRET, {
      expiresIn: '7d'
    });

    // Update user login info
    await prisma.user.update({
      where: { id: user.id },
      data: {
        lastLoginAt: new Date(),
        sessionToken,
        sessionExpiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days
        loginAttempts: 0, // Reset login attempts on successful login
        lockedUntil: null // Remove any lockout
      }
    });

    // Generate JWT token
    const token = generateToken(user.id);

    res.status(200).json({
      success: true,
      message: 'Sign in successful',
      token,
      user: {
        id: user.id,
        email: user.email,
        phone: user.phone,
        firstName: user.firstName,
        lastName: user.lastName,
        role: user.role,
        emailVerified: user.emailVerified,
        phoneVerified: user.phoneVerified,
        biometricEnabled: user.biometricEnabled,
        lastLoginAt: new Date()
      }
    });

  } catch (error) {
    console.error('Sign in error:', error);
    res.status(500).json({
      success: false,
      message: 'Internal server error',
      error: process.env.NODE_ENV === 'development' ? error.message : 'Something went wrong'
    });
  }
};

// Check Password Status
const getPasswordStatus = async (req, res) => {
  try {
    const userId = req.user.id;

    const user = await prisma.user.findUnique({
      where: { id: userId },
      select: {
        id: true,
        email: true,
        phone: true,
        password: true
      }
    });

    if (!user) {
      return res.status(404).json({
        success: false,
        message: 'User not found'
      });
    }

    res.status(200).json({
      success: true,
      data: {
        hasPassword: !!user.password,
        canSignIn: !!user.password,
        email: user.email,
        phone: user.phone
      }
    });

  } catch (error) {
    console.error('Get password status error:', error);
    res.status(500).json({
      success: false,
      message: 'Internal server error',
      error: process.env.NODE_ENV === 'development' ? error.message : 'Something went wrong'
    });
  }
};

// Change Password (for users who already have a password)
const changePassword = async (req, res) => {
  try {
    const { currentPassword, newPassword, confirmNewPassword } = req.body;
    const userId = req.user.id;

    // Validation
    if (!currentPassword || !newPassword || !confirmNewPassword) {
      return res.status(400).json({
        success: false,
        message: 'Current password, new password, and confirmation are required'
      });
    }

    if (newPassword !== confirmNewPassword) {
      return res.status(400).json({
        success: false,
        message: 'New passwords do not match'
      });
    }

    if (newPassword.length < 6) {
      return res.status(400).json({
        success: false,
        message: 'New password must be at least 6 characters long'
      });
    }

    // Get user
    const user = await prisma.user.findUnique({
      where: { id: userId }
    });

    if (!user || !user.password) {
      return res.status(400).json({
        success: false,
        message: 'No current password found. Please set up a password first.'
      });
    }

    // Verify current password
    const isCurrentPasswordValid = await bcrypt.compare(currentPassword, user.password);

    if (!isCurrentPasswordValid) {
      return res.status(401).json({
        success: false,
        message: 'Current password is incorrect'
      });
    }

    // Hash new password
    const salt = await bcrypt.genSalt(12);
    const hashedNewPassword = await bcrypt.hash(newPassword, salt);

    // Update password
    await prisma.user.update({
      where: { id: userId },
      data: {
        password: hashedNewPassword,
        updatedAt: new Date()
      }
    });

    res.status(200).json({
      success: true,
      message: 'Password changed successfully'
    });

  } catch (error) {
    console.error('Change password error:', error);
    res.status(500).json({
      success: false,
      message: 'Internal server error',
      error: process.env.NODE_ENV === 'development' ? error.message : 'Something went wrong'
    });
  }
};

// Check user status by phone (for frontend auth flow)
const checkUserStatus = async (req, res) => {
  try {
    const { phone } = req.body;

    if (!phone) {
      return res.status(400).json({
        success: false,
        message: 'Phone number is required'
      });
    }

    // Validate phone number format
    const phoneValidation = otpService.validatePhoneNumber(phone);
    if (!phoneValidation.isValid) {
      return res.status(400).json({
        success: false,
        message: phoneValidation.error
      });
    }

    const formattedPhone = phoneValidation.formatted;

    // Check if user exists
    const user = await prisma.user.findUnique({
      where: { phone: formattedPhone },
      select: {
        id: true,
        phone: true,
        phoneVerified: true,
        password: true,
        firstName: true,
        lastName: true,
        email: true,
        isActive: true
      }
    });

    if (!user) {
      // New user - needs to go through OTP verification
      return res.json({
        success: true,
        userStatus: 'new',
        requiresOTP: true,
        requiresPasswordSetup: false,
        canSignIn: false,
        message: 'New user - phone verification required'
      });
    }

    if (!user.phoneVerified) {
      // Existing user but phone not verified
      return res.json({
        success: true,
        userStatus: 'unverified',
        requiresOTP: true,
        requiresPasswordSetup: false,
        canSignIn: false,
        message: 'Phone verification required'
      });
    }

    const hasPassword = user.password && user.password !== null;

    if (!hasPassword) {
      // Phone verified but no password set
      return res.json({
        success: true,
        userStatus: 'verified_no_password',
        requiresOTP: false,
        requiresPasswordSetup: true,
        canSignIn: false,
        message: 'Password setup required'
      });
    }

    // User exists, phone verified, password set - can sign in directly
    return res.json({
      success: true,
      userStatus: 'complete',
      requiresOTP: false,
      requiresPasswordSetup: false,
      canSignIn: true,
      message: 'User can sign in with password',
      user: {
        firstName: user.firstName,
        lastName: user.lastName,
        phone: user.phone
      }
    });

  } catch (error) {
    console.error('Check user status error:', error);
    res.status(500).json({
      success: false,
      message: 'Failed to check user status'
    });
  }
};

// Complete Onboarding (After OTP Verification)
const completeOnboarding = async (req, res) => {
  try {
    const { 
      phone, 
      businessName, 
      businessAddress, 
      taxId, 
      bnNumber, 
      rcNumber, 
      businessType, 
      receiveMoneyMethods, 
      collectsVat, 
      hasStaff,
      pin,
      password
    } = req.body;

    if (!phone || !businessName || !businessType) {
      return res.status(400).json({
        success: false,
        message: 'Phone, Business Name, and Business Type are required'
      });
    }

    // Find the user created during OTP step
    const user = await prisma.user.findUnique({
      where: { phone }
    });

    if (!user) {
      return res.status(404).json({
        success: false,
        message: 'User not found. Please verify OTP first.'
      });
    }

    // Hash PIN and Password if provided
    let updateData = {
      businessName,
      isActive: true
    };

    if (pin) {
      updateData.pin = await bcrypt.hash(pin, 10);
      updateData.pinSetAt = new Date();
    }
    
    if (password) {
      updateData.password = await bcrypt.hash(password, 12);
    }

    // Update User
    const updatedUser = await prisma.user.update({
      where: { phone },
      data: updateData
    });

    // Create Business and Business Profile
    const business = await prisma.business.create({
      data: {
        name: businessName,
        type: businessType,
        ownerId: updatedUser.id,
        vatRegistered: collectsVat || false,
        hasStaff: hasStaff || false,
        profile: {
          create: {
            address: businessAddress,
            tinNumber: taxId,
            bnNumber: bnNumber,
            rcNumber: rcNumber,
            receiveMoneyMethods: receiveMoneyMethods || ["CASH", "TRANSFER", "POS"]
          }
        }
      }
    });

    // Create default User Settings (using upsert to avoid duplicate errors if retried)
    await prisma.userSettings.upsert({
      where: { userId: updatedUser.id },
      update: {},
      create: {
        userId: updatedUser.id
      }
    });

    // Generate token
    const token = generateToken(updatedUser.id);

    res.status(200).json({
      success: true,
      message: 'Account created successfully',
      token,
      user: {
        id: updatedUser.id,
        phone: updatedUser.phone,
        businessName: updatedUser.businessName,
        businessId: business.id
      }
    });

  } catch (error) {
    console.error('Complete onboarding error:', error);
    res.status(500).json({
      success: false,
      message: 'Failed to complete onboarding',
      error: error.message
    });
  }
};

// Delete Account
const deleteAccount = async (req, res) => {
  try {
    const userId = req.user.id;

    // Delete user (Cascade will handle related records if set up, otherwise manual deletion needed)
    // Assuming Prisma schema has onDelete: Cascade for most relations
    await prisma.user.delete({
      where: { id: userId }
    });

    res.status(200).json({
      success: true,
      message: 'Account deleted successfully'
    });

  } catch (error) {
    console.error('Delete account error:', error);
    res.status(500).json({
      success: false,
      message: 'Failed to delete account'
    });
  }
};

module.exports = {
  registerUser,
  loginUser,
  getMe,
  getInvitationDetails,
  acceptStaffInvitation,
  sendOTP,
  verifyOTP,
  phoneLogin,
  setupPassword,
  signIn,
  getPasswordStatus,
  changePassword,
  checkUserStatus,
  completeOnboarding,
  deleteAccount
};

Directory Contents

Dirs: 1 × Files: 17

Name Size Perms Modified Actions
- drwxr-xr-x 2026-03-20 23:25:48
Edit Download
127 B lr--r--r-- 2026-03-14 01:49:22
Edit Download
15.34 KB lrw-r--r-- 2026-02-19 22:06:47
Edit Download
30.77 KB lrw-r--r-- 2026-03-04 06:33:18
Edit Download
62.98 KB lrw-r--r-- 2026-03-01 23:46:52
Edit Download
10.79 KB lrw-r--r-- 2026-02-07 16:28:16
Edit Download
14.17 KB lrw-r--r-- 2026-02-27 04:02:38
Edit Download
6.32 KB lrw-r--r-- 2026-02-06 17:10:19
Edit Download
15.13 KB lrw-r--r-- 2026-03-01 05:05:51
Edit Download
8.50 KB lrw-r--r-- 2026-03-01 05:05:52
Edit Download
12.15 KB lrw-r--r-- 2026-02-27 04:02:38
Edit Download
25.39 KB lrw-r--r-- 2026-03-01 05:05:51
Edit Download
17.61 KB lrw-r--r-- 2026-03-01 05:05:51
Edit Download
6.45 KB lrw-r--r-- 2026-03-01 05:05:51
Edit Download
2.33 KB lrw-r--r-- 2026-03-01 05:05:51
Edit Download
12.78 KB lrw-r--r-- 2026-02-21 08:13:30
Edit Download
9.96 KB lrw-r--r-- 2026-03-01 05:05:51
Edit Download
8.51 KB lrw-r--r-- 2026-02-20 19:31:28
Edit Download

If ZipArchive is unavailable, a .tar will be created (no compression).