Source: mongodb/mongodb.js

/**
 * @fileoverview MongoDB database operations and user authentication
 * @description This module handles all database operations including user management,
 * post management, and email verification using external APIs.
 * @author Your Name
 * @version 1.0.0
 * @requires mongodb
 * @requires mongoose
 * @requires bcryptjs
 * @requires jsonwebtoken
 * @requires axios
 */

const { MongoClient, ServerApiVersion } = require('mongodb');
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const axios = require('axios');

// Load environment variables
require('dotenv').config();

/**
 * JWT secret key from environment variables
 * @type {string}
 */
const JWT_SECRET = process.env.JWT_SECRET;

/**
 * JWT token expiration time from environment variables
 * @type {string}
 */
const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN;

/**
 * MongoDB connection URI from environment variables
 * @type {string}
 */
const uri = process.env.MONGODB_URI;

/**
 * Mailboxlayer API key for email verification
 * @type {string}
 */
const MAILBOXLAYER_API_KEY = process.env.MAILBOXLAYER_API_KEY;

/**
 * Verifies if an email address is valid and exists using Mailboxlayer API
 * @async
 * @function verifyEmail
 * @param {string} email - The email address to verify
 * @returns {Promise<Object>} Object containing validation status and reason
 * @returns {boolean} returns.valid - Whether the email is valid
 * @returns {string} returns.reason - Explanation of the validation result
 * @example
 * const result = await verifyEmail('user@example.com');
 * if (result.valid) {
 *   console.log('Email is valid:', result.reason);
 * } else {
 *   console.log('Email is invalid:', result.reason);
 * }
 * @throws {Error} If API request fails, falls back to allowing the email
 */
async function verifyEmail(email) {
  try {
    console.log(`Verifying email: ${email}`);
    
    const response = await axios.get('http://apilayer.net/api/check', {
      params: {
        access_key: MAILBOXLAYER_API_KEY,
        email: email,
        smtp: 1,      // Enables SMTP check (only works with paid plans)
        format: 1     // Returns formatted JSON
      },
      timeout: 10000 // 10 second timeout
    });

    const data = response.data;
    console.log('Mailboxlayer response:', data);

    // Check if API call was successful
    if (data.error) {
      console.error('Mailboxlayer API error:', data.error);
      return { valid: false, reason: `API error: ${data.error.info}` };
    }

    if (!data.format_valid) {
      return { valid: false, reason: 'Invalid email format' };
    }

    if (!data.mx_found) {
      return { valid: false, reason: 'Domain has no MX records' };
    }

    // Note: SMTP check might not work with free plan
    if (data.smtp_check === false) {
      return { valid: false, reason: 'SMTP check failed — mailbox may not exist' };
    }

    return { valid: true, reason: 'Email is valid and exists' };

  } catch (error) {
    console.error('Email verification error:', error.message);
    
    // If API is down or network issue, allow registration but log warning
    console.warn('Email verification service unavailable, allowing registration');
    return { valid: true, reason: 'Email verification service unavailable, but proceeding' };
  }
}

/**
 * @typedef {Object} User
 * @property {string} username - User's unique username (min 3 characters)
 * @property {string} email - User's email address (must be valid format)
 * @property {string} password - User's hashed password (min 6 characters)
 * @property {Date} createdAt - Account creation timestamp
 */

/**
 * Mongoose schema for User model
 * @type {mongoose.Schema}
 * @description Defines the structure for user documents in MongoDB
 */
const UserSchema = new mongoose.Schema({
    username: {
        type: String,
        required: [true, 'Please add a username'],
        unique: true,
        trim: true,
        minlength: [3, 'Username must be at least 3 characters long']
    },
    email: {
        type: String,
        required: [true, 'Please add an email'],
        unique: true,
        match: [
            /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
            'Please add a valid email'
        ]
    },
    password: {
        type: String,
        required: [true, 'Please add a password'],
        minlength: [6, 'Password must be at least 6 characters long'],
        select: false
    },
    createdAt: {
        type: Date,
        default: Date.now
    }
});

UserSchema.pre('save', async function (next) {
    if (!this.isModified('password')) {
        next();
    }
    const salt = await bcrypt.genSalt(10);
    this.password = await bcrypt.hash(this.password, salt);
    next();
});

UserSchema.methods.matchPassword = async function (enteredPassword) {
    return await bcrypt.compare(enteredPassword, this.password);
};

const User = mongoose.model('User', UserSchema);

// Generate JWT Token
const generateToken = (userId) => {
    return jwt.sign({ userId }, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN });
};

// Verify JWT Token
const verifyToken = (token) => {
    try {
        return jwt.verify(token, JWT_SECRET);
    } catch (error) {
        throw new Error('Invalid or expired token');
    }
};

// Get user by ID (for protected routes)
const getUserById = async (userId) => {
    try {
        const user = await User.findById(userId).select('-password');
        if (!user) {
            throw new Error('User not found');
        }
        return user;
    } catch (error) {
        throw new Error('Error fetching user');
    }
};

// MongoDB connection functions
const connectToMongoDB = async () => {
    try {
        const options = {
            serverSelectionTimeoutMS: parseInt(process.env.ServerSelectTimer) || 30000,
            socketTimeoutMS: parseInt(process.env.SocketTimeout) || 45000,
        };
        
        await mongoose.connect(uri, options);
        console.log('Connected to MongoDB Atlas successfully');
        
        // Connection event listeners
        mongoose.connection.on('error', (err) => {
            console.error('MongoDB connection error:', err);
        });
        
        mongoose.connection.on('disconnected', () => {
            console.log('MongoDB disconnected');
        });
        
        return true;
    } catch (error) {
        console.error('MongoDB connection error:', error);
        throw error;
    }
};

const closeConnection = async () => {
    try {
        await mongoose.connection.close();
        console.log('MongoDB connection closed');
    } catch (error) {
        console.error('Error closing MongoDB connection:', error);
    }
};




// Delete a user based on email and password verification
const deleteaUser = async (email, password) => {
    try {
        // Find the user with password included
        const user = await User.findOne({ email }).select('+password');
        
        if (!user) {
            throw new Error('User not found with this email');
        }

        // Verify the password
        const isPasswordMatch = await user.matchPassword(password);
        if (!isPasswordMatch) {
            throw new Error('Incorrect password');
        }
        
        // If password verification passes, delete the user
        await User.findOneAndDelete({ email });
        console.log('User deleted successfully:', email);
        return {
            success: true,
            message: 'User account deleted successfully'
        };
    } catch (error) {
        console.error('Error deleting user:', error.message);
        throw error;
    }
}


// Check if the user exists in the database and if it doesn't will create new user
const registerUser = async ({ name, email, password }) => {
    try {
        // Skip email verification in test environment
        if (process.env.NODE_ENV !== 'test' && !process.env.SKIP_EMAIL_VERIFICATION) {
            console.log('Verifying email with mailboxlayer...');
            const emailVerification = await verifyEmail(email);
            
            if (!emailVerification.valid) {
                throw new Error(`Email verification failed: ${emailVerification.reason}`);
            }
            
            console.log('Email verification passed:', emailVerification.reason);
        } else {
            console.log('Skipping email verification for test environment');
        }

        const userExists = await User.findOne({ email });
        if (userExists) {
            return {
                success: false,
                message: 'User already exists'
            };
        }

        const newUser = await User.create({
            username: name,
            email,
            password
        });

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

        console.log('User registered successfully:', newUser.username);
        return {
            success: true,
            message: 'User registered successfully',
            token: token,
            user: {
                _id: newUser._id,
                name: newUser.username,
                email: newUser.email
            }
        };
    } catch (error) {
        console.error('Error registering user:', error.message);
        return {
            success: false,
            message: error.message
        };
    }
};

// Check if the user exists if it does do login
const LoginUser = async ({ email, password }) => {
    try {
        const FindUser = await User.findOne({ email: email }).select('+password');
        
        if (!FindUser) {
            return {
                success: false,
                message: 'Invalid credentials'
            };
        }
        
        const isPasswordMatch = await FindUser.matchPassword(password);

        if (!isPasswordMatch) {
            return {
                success: false,
                message: 'Invalid credentials'
            };
        }

        // Generate token
        const token = generateToken(FindUser._id);

        console.log('User found with credentials correctly');
        return {
            success: true,
            message: 'Login successful',
            token: token,
            user: {
                _id: FindUser._id,
                name: FindUser.username,
                email: FindUser.email
            }
        };
    } catch (error) {
        console.error('Error with the Login:', error.message);
        return {
            success: false,
            message: 'Login error'
        };
    }
}









module.exports = { 
    User,
    connectToMongoDB, 
    closeConnection,
    registerUser,
    LoginUser,
    generateToken,
    verifyToken,
    getUserById,
    verifyEmail,
    deleteaUser
};