const express = require('express'); const cors = require('cors'); const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); const sqlite3 = require('sqlite3').verbose(); const helmet = require('helmet'); const rateLimit = require('express-rate-limit'); const path = require('path'); const { v4: uuidv4 } = require('uuid'); const app = express(); const PORT = process.env.PORT || 12004; const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key-change-in-production'; // Middleware app.use(helmet()); app.use(cors()); app.use(express.json()); app.use(express.static(path.join(__dirname, '..'))); // Rate limiting const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100 // limit each IP to 100 requests per windowMs }); app.use('/api/', limiter); // Initialize SQLite database const db = new sqlite3.Database('./mindshift.db'); // Create tables db.serialize(() => { // Users table db.run(`CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT UNIQUE NOT NULL, password TEXT NOT NULL, name TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, last_login DATETIME )`); // Mood tracking table db.run(`CREATE TABLE IF NOT EXISTS mood_entries ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, mood_type TEXT NOT NULL, intensity INTEGER NOT NULL, notes TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users (id) )`); // Thoughts table db.run(`CREATE TABLE IF NOT EXISTS thoughts ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, situation TEXT NOT NULL, automatic_thought TEXT NOT NULL, emotion TEXT NOT NULL, emotion_intensity INTEGER NOT NULL, cognitive_distortion TEXT, alternative_thought TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users (id) )`); // Gratitude entries table db.run(`CREATE TABLE IF NOT EXISTS gratitude_entries ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, entry TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users (id) )`); // Progress tracking table db.run(`CREATE TABLE IF NOT EXISTS progress ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, date DATE NOT NULL, mood_score INTEGER, thoughts_count INTEGER DEFAULT 0, gratitude_count INTEGER DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE(user_id, date), FOREIGN KEY (user_id) REFERENCES users (id) )`); // Notifications table db.run(`CREATE TABLE IF NOT EXISTS notifications ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, title TEXT NOT NULL, message TEXT NOT NULL, type TEXT DEFAULT 'info', read BOOLEAN DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users (id) )`); // Exercise Logs table db.run(`CREATE TABLE IF NOT EXISTS exercise_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, exercise_type TEXT NOT NULL, duration INTEGER NOT NULL, completed BOOLEAN DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users (id) )`); }); // JWT authentication middleware function authenticateToken(req, res, next) { const authHeader = req.headers['authorization']; const token = authHeader && authHeader.split(' ')[1]; if (!token) { return res.status(401).json({ error: 'Access token required' }); } jwt.verify(token, JWT_SECRET, (err, user) => { if (err) { return res.status(403).json({ error: 'Invalid token' }); } req.user = user; next(); }); } // Auth routes app.post('/api/auth/register', async (req, res) => { try { const { email, password, name } = req.body; if (!email || !password || !name) { return res.status(400).json({ error: 'Email, password, and name are required' }); } // Check if user already exists db.get('SELECT id FROM users WHERE email = ?', [email], async (err, user) => { if (err) { return res.status(500).json({ error: 'Database error' }); } if (user) { return res.status(400).json({ error: 'User already exists' }); } // Hash password const saltRounds = 10; const hashedPassword = await bcrypt.hash(password, saltRounds); // Insert new user db.run('INSERT INTO users (email, password, name) VALUES (?, ?, ?)', [email, hashedPassword, name], function(err) { if (err) { return res.status(500).json({ error: 'Database error' }); } // Generate JWT const token = jwt.sign( { id: this.lastID, email, name }, JWT_SECRET, { expiresIn: '24h' } ); res.status(201).json({ message: 'User created successfully', token, user: { id: this.lastID, email, name } }); }); }); } catch (error) { res.status(500).json({ error: 'Server error' }); } }); app.post('/api/auth/login', (req, res) => { try { const { email, password } = req.body; if (!email || !password) { return res.status(400).json({ error: 'Email and password are required' }); } // Find user db.get('SELECT * FROM users WHERE email = ?', [email], async (err, user) => { if (err) { return res.status(500).json({ error: 'Database error' }); } if (!user) { return res.status(401).json({ error: 'Invalid credentials' }); } // Check password const validPassword = await bcrypt.compare(password, user.password); if (!validPassword) { return res.status(401).json({ error: 'Invalid credentials' }); } // Update last login db.run('UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE id = ?', [user.id]); // Generate JWT const token = jwt.sign( { id: user.id, email: user.email, name: user.name }, JWT_SECRET, { expiresIn: '24h' } ); res.json({ message: 'Login successful', token, user: { id: user.id, email: user.email, name: user.name } }); }); } catch (error) { res.status(500).json({ error: 'Server error' }); } }); // Get user profile app.get('/api/user/profile', authenticateToken, (req, res) => { db.get('SELECT id, email, name, created_at, last_login FROM users WHERE id = ?', [req.user.id], (err, user) => { if (err) { return res.status(500).json({ error: 'Database error' }); } if (!user) { return res.status(404).json({ error: 'User not found' }); } res.json(user); }); }); // Mood tracking app.post('/api/mood/track', authenticateToken, (req, res) => { try { const { moodType, intensity, notes } = req.body; if (!moodType || !intensity || intensity < 1 || intensity > 10) { return res.status(400).json({ error: 'Invalid mood data' }); } // Insert mood entry db.run('INSERT INTO mood_entries (user_id, mood_type, intensity, notes) VALUES (?, ?, ?, ?)', [req.user.id, moodType, intensity, notes || null], function(err) { if (err) { return res.status(500).json({ error: 'Database error' }); } // Update or create progress entry for today const today = new Date().toISOString().split('T')[0]; db.run(`INSERT OR REPLACE INTO progress (user_id, date, mood_score, thoughts_count, gratitude_count) VALUES (?, ?, COALESCE((SELECT mood_score FROM progress WHERE user_id = ? AND date = ?), ?), COALESCE((SELECT thoughts_count FROM progress WHERE user_id = ? AND date = ?), 0), COALESCE((SELECT gratitude_count FROM progress WHERE user_id = ? AND date = ?), 0))`, [req.user.id, today, req.user.id, today, intensity, req.user.id, today, req.user.id, today], function(err) { if (err) { return res.status(500).json({ error: 'Database error' }); } res.status(201).json({ message: 'Mood tracked successfully', entryId: this.lastID }); }); }); } catch (error) { res.status(500).json({ error: 'Server error' }); } }); // Get mood history app.get('/api/mood/history', authenticateToken, (req, res) => { const limit = parseInt(req.query.limit) || 30; db.all('SELECT * FROM mood_entries WHERE user_id = ? ORDER BY created_at DESC LIMIT ?', [req.user.id, limit], (err, entries) => { if (err) { return res.status(500).json({ error: 'Database error' }); } res.json(entries); }); }); // Thought recording app.post('/api/thoughts', authenticateToken, (req, res) => { try { const { situation, automaticThought, emotion, emotionIntensity, cognitiveDistortion, alternativeThought } = req.body; if (!situation || !automaticThought || !emotion || !emotionIntensity || emotionIntensity < 1 || emotionIntensity > 10) { return res.status(400).json({ error: 'Invalid thought data' }); } // Insert thought entry db.run('INSERT INTO thoughts (user_id, situation, automatic_thought, emotion, emotion_intensity, cognitive_distortion, alternative_thought) VALUES (?, ?, ?, ?, ?, ?, ?)', [req.user.id, situation, automaticThought, emotion, emotionIntensity, cognitiveDistortion || null, alternativeThought || null], function(err) { if (err) { return res.status(500).json({ error: 'Database error' }); } // Update progress entry for today const today = new Date().toISOString().split('T')[0]; db.run(`INSERT OR REPLACE INTO progress (user_id, date, mood_score, thoughts_count, gratitude_count) VALUES (?, ?, COALESCE((SELECT mood_score FROM progress WHERE user_id = ? AND date = ?), 0), COALESCE((SELECT thoughts_count FROM progress WHERE user_id = ? AND date = ?), 0) + 1, COALESCE((SELECT gratitude_count FROM progress WHERE user_id = ? AND date = ?), 0))`, [req.user.id, today, req.user.id, today, req.user.id, today, req.user.id, today], function(err) { if (err) { return res.status(500).json({ error: 'Database error' }); } res.status(201).json({ message: 'Thought recorded successfully', entryId: this.lastID }); }); }); } catch (error) { res.status(500).json({ error: 'Server error' }); } }); // Get thoughts history app.get('/api/thoughts', authenticateToken, (req, res) => { const limit = parseInt(req.query.limit) || 30; db.all('SELECT * FROM thoughts WHERE user_id = ? ORDER BY created_at DESC LIMIT ?', [req.user.id, limit], (err, entries) => { if (err) { return res.status(500).json({ error: 'Database error' }); } res.json(entries); }); }); // Gratitude entries app.post('/api/gratitude', authenticateToken, (req, res) => { try { const { entry } = req.body; if (!entry || entry.trim() === '') { return res.status(400).json({ error: 'Gratitude entry is required' }); } // Insert gratitude entry db.run('INSERT INTO gratitude_entries (user_id, entry) VALUES (?, ?)', [req.user.id, entry], function(err) { if (err) { return res.status(500).json({ error: 'Database error' }); } // Update progress entry for today const today = new Date().toISOString().split('T')[0]; db.run(`INSERT OR REPLACE INTO progress (user_id, date, mood_score, thoughts_count, gratitude_count) VALUES (?, ?, COALESCE((SELECT mood_score FROM progress WHERE user_id = ? AND date = ?), 0), COALESCE((SELECT thoughts_count FROM progress WHERE user_id = ? AND date = ?), 0), COALESCE((SELECT gratitude_count FROM progress WHERE user_id = ? AND date = ?), 0) + 1)`, [req.user.id, today, req.user.id, today, req.user.id, today, req.user.id, today], function(err) { if (err) { return res.status(500).json({ error: 'Database error' }); } res.status(201).json({ message: 'Gratitude entry added successfully', entryId: this.lastID }); }); }); } catch (error) { res.status(500).json({ error: 'Server error' }); } }); // Log Exercise Session app.post('/api/exercises', authenticateToken, (req, res) => { try { const { exerciseType, duration } = req.body; if (!exerciseType || !duration) { return res.status(400).json({ error: 'Type and duration required' }); } db.run('INSERT INTO exercise_logs (user_id, exercise_type, duration) VALUES (?, ?, ?)', [req.user.id, exerciseType, duration], function(err) { if (err) { return res.status(500).json({ error: 'Database error' }); } res.status(201).json({ success: true, id: this.lastID }); }); } catch (error) { res.status(500).json({ error: 'Server error' }); } }); // Get gratitude entries app.get('/api/gratitude', authenticateToken, (req, res) => { const limit = parseInt(req.query.limit) || 30; db.all('SELECT * FROM gratitude_entries WHERE user_id = ? ORDER BY created_at DESC LIMIT ?', [req.user.id, limit], (err, entries) => { if (err) { return res.status(500).json({ error: 'Database error' }); } res.json(entries); }); }); // Get progress data app.get('/api/progress', authenticateToken, (req, res) => { const days = parseInt(req.query.days) || 30; db.all('SELECT * FROM progress WHERE user_id = ? ORDER BY date DESC LIMIT ?', [req.user.id, days], (err, entries) => { if (err) { return res.status(500).json({ error: 'Database error' }); } res.json(entries); }); }); // Get dashboard stats app.get('/api/dashboard/stats', authenticateToken, (req, res) => { const today = new Date().toISOString().split('T')[0]; // Get today's progress db.get('SELECT * FROM progress WHERE user_id = ? AND date = ?', [req.user.id, today], (err, todayProgress) => { if (err) { return res.status(500).json({ error: 'Database error' }); } // Get this week's mood entries const weekAgo = new Date(); weekAgo.setDate(weekAgo.getDate() - 7); const weekAgoStr = weekAgo.toISOString().split('T')[0]; db.all('SELECT AVG(intensity) as avgMood, COUNT(*) as count FROM mood_entries WHERE user_id = ? AND created_at >= ?', [req.user.id, weekAgoStr], (err, weekMood) => { if (err) { return res.status(500).json({ error: 'Database error' }); } // Get total counts db.get(`SELECT (SELECT COUNT(*) FROM mood_entries WHERE user_id = ?) as totalMoods, (SELECT COUNT(*) FROM thoughts WHERE user_id = ?) as totalThoughts, (SELECT COUNT(*) FROM gratitude_entries WHERE user_id = ?) as totalGratitude, (SELECT COUNT(*) FROM exercise_logs WHERE user_id = ?) as totalSessions`, [req.user.id, req.user.id, req.user.id, req.user.id], (err, totals) => { if (err) { return res.status(500).json({ error: 'Database error' }); } res.json({ today: todayProgress || { mood_score: 0, thoughts_count: 0, gratitude_count: 0 }, week: { avgMood: weekMood[0].avgMood || 0, moodCount: weekMood[0].count || 0 }, totals: totals }); }); }); }); }); // Notifications app.get('/api/notifications', authenticateToken, (req, res) => { db.all('SELECT * FROM notifications WHERE user_id = ? ORDER BY created_at DESC', [req.user.id], (err, notifications) => { if (err) { return res.status(500).json({ error: 'Database error' }); } res.json(notifications); }); }); app.post('/api/notifications', authenticateToken, (req, res) => { const { title, message, type } = req.body; if (!title || !message) { return res.status(400).json({ error: 'Title and message are required' }); } db.run('INSERT INTO notifications (user_id, title, message, type) VALUES (?, ?, ?, ?)', [req.user.id, title, message, type || 'info'], function(err) { if (err) { return res.status(500).json({ error: 'Database error' }); } // Return the created notification db.get('SELECT * FROM notifications WHERE id = ?', [this.lastID], (err, notification) => { if (err) { return res.status(500).json({ error: 'Database error' }); } res.status(201).json(notification); }); }); }); app.put('/api/notifications/:id/read', authenticateToken, (req, res) => { const notificationId = req.params.id; db.run('UPDATE notifications SET read = 1 WHERE id = ? AND user_id = ?', [notificationId, req.user.id], function(err) { if (err) { return res.status(500).json({ error: 'Database error' }); } if (this.changes === 0) { return res.status(404).json({ error: 'Notification not found' }); } res.json({ success: true }); }); }); app.delete('/api/notifications/:id', authenticateToken, (req, res) => { const notificationId = req.params.id; db.run('DELETE FROM notifications WHERE id = ? AND user_id = ?', [notificationId, req.user.id], function(err) { if (err) { return res.status(500).json({ error: 'Database error' }); } if (this.changes === 0) { return res.status(404).json({ error: 'Notification not found' }); } res.json({ success: true }); }); }); // Start server app.listen(PORT, () => { console.log(`MindShift server running on port ${PORT}`); });