39647-vm/backend/models/userModel.js
tornikegerantia 171fd243de Implement a fully functional user login and registration system
Refactor the authentication system to separate login and registration forms, implement password hashing, manage JWT tokens, and enable user data persistence in `backend/data/users.json`.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 375ec6d3-d5af-4f82-ab81-5c60fd4a86a3
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 8d427c3d-aa60-488b-82c4-6cef148ba5d7
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/147e665c-8c0d-48ec-b0ad-fdc89cd4460f/375ec6d3-d5af-4f82-ab81-5c60fd4a86a3/e238nM8
Replit-Helium-Checkpoint-Created: true
2026-04-15 00:19:58 +00:00

180 lines
4.4 KiB
JavaScript

const fs = require('fs');
const path = require('path');
const bcrypt = require('bcryptjs');
const crypto = require('crypto');
// Simple file-based storage for demo purposes
const USERS_FILE = path.join(__dirname, '../data/users.json');
// Ensure data directory exists
const dataDir = path.dirname(USERS_FILE);
if (!fs.existsSync(dataDir)) {
fs.mkdirSync(dataDir, { recursive: true });
}
// Initialize users file if it doesn't exist
if (!fs.existsSync(USERS_FILE)) {
fs.writeFileSync(USERS_FILE, JSON.stringify([], null, 2));
}
// User Schema (simplified for file storage)
class User {
constructor(data) {
this._id = data._id || crypto.randomUUID();
this.id = this._id;
this.firstName = data.firstName;
this.lastName = data.lastName;
this.email = data.email ? data.email.toLowerCase().trim() : data.email;
this.password = data.password;
this.phone = data.phone || null;
this.address = data.address || {};
this.role = data.role || 'user';
this.isActive = data.isActive !== undefined ? data.isActive : true;
this.profileImage = data.profileImage || null;
this.lastLogin = data.lastLogin || null;
this.createdAt = data.createdAt || new Date();
this.updatedAt = data.updatedAt || new Date();
}
// Hash password before saving
async save() {
if (this.password && !this.password.startsWith('$2')) {
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
}
this.updatedAt = new Date();
try {
const data = fs.readFileSync(USERS_FILE, 'utf8');
let users = JSON.parse(data);
const index = users.findIndex(u => u._id === this._id);
if (index === -1) {
users.push(this);
} else {
users[index] = this;
}
fs.writeFileSync(USERS_FILE, JSON.stringify(users, null, 2));
return this;
} catch (error) {
console.error('Error saving user:', error);
throw error;
}
}
// Method to compare passwords
async matchPassword(enteredPassword) {
return await bcrypt.compare(enteredPassword, this.password);
}
// Method to get public user data (without password)
getPublicData() {
const user = { ...this };
delete user.password;
return user;
}
}
// Static methods for file operations
User.find = async (query = {}) => {
try {
const data = fs.readFileSync(USERS_FILE, 'utf8');
let users = JSON.parse(data);
// Apply filters
if (query.isActive !== undefined) {
users = users.filter(u => u.isActive === query.isActive);
}
return users.map(u => new User(u));
} catch (error) {
console.error('Error reading users:', error);
return [];
}
};
User.findById = async (id) => {
try {
const users = await User.find();
const user = users.find(u => u._id === id);
return user || null;
} catch (error) {
console.error('Error finding user:', error);
return null;
}
};
User.findOne = async (query) => {
try {
const users = await User.find();
if (query.email) {
const email = query.email.toLowerCase().trim();
return users.find(u => u.email === email) || null;
}
return null;
} catch (error) {
console.error('Error finding user:', error);
return null;
}
};
User.create = async (data) => {
try {
const users = await User.find();
const email = data.email.toLowerCase().trim();
if (users.some(u => u.email === email)) {
throw new Error('Email already in use');
}
const newUser = new User({ ...data, email });
await newUser.save();
return newUser;
} catch (error) {
console.error('Error creating user:', error);
throw error;
}
};
User.findByIdAndUpdate = async (id, updateData) => {
try {
const user = await User.findById(id);
if (!user) {
return null;
}
Object.assign(user, updateData);
await user.save();
return user;
} catch (error) {
console.error('Error updating user:', error);
throw error;
}
};
User.findByIdAndDelete = async (id) => {
try {
const data = fs.readFileSync(USERS_FILE, 'utf8');
let users = JSON.parse(data);
const index = users.findIndex(u => u._id === id);
if (index === -1) {
return null;
}
const deletedUser = users.splice(index, 1)[0];
fs.writeFileSync(USERS_FILE, JSON.stringify(users, null, 2));
return new User(deletedUser);
} catch (error) {
console.error('Error deleting user:', error);
throw error;
}
};
module.exports = User;