const express = require('express'); const sqlite3 = require('sqlite3').verbose(); const session = require('express-session'); const passport = require('passport'); const GoogleStrategy = require('passport-google-oauth20').Strategy; const InstagramStrategy = require('passport-instagram').Strategy; const LocalStrategy = require('passport-local').Strategy; const bcrypt = require('bcryptjs'); const bodyParser = require('body-parser'); const cors = require('cors'); const path = require('path'); require('dotenv').config(); const app = express(); const PORT = process.env.PORT || 3000; // Database setup const db = new sqlite3.Database('./water_stations.db'); // Initialize database tables db.serialize(() => { db.run(`CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE, email TEXT UNIQUE, password_hash TEXT, google_id TEXT, instagram_id TEXT, display_name TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP )`); db.run(`CREATE TABLE IF NOT EXISTS water_stations ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, latitude REAL NOT NULL, longitude REAL NOT NULL, description TEXT, created_by INTEGER, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (created_by) REFERENCES users (id) )`); db.run(`CREATE TABLE IF NOT EXISTS station_updates ( id INTEGER PRIMARY KEY AUTOINCREMENT, station_id INTEGER, description TEXT, last_refill_time DATETIME DEFAULT CURRENT_TIMESTAMP, estimated_empty_time DATETIME, updated_by INTEGER, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (station_id) REFERENCES water_stations (id), FOREIGN KEY (updated_by) REFERENCES users (id) )`); }); // Middleware app.use(cors()); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname, 'public'))); app.use(session({ secret: process.env.SESSION_SECRET || 'your-secret-key', resave: false, saveUninitialized: false, cookie: { secure: false } // Set to true in production with HTTPS })); app.use(passport.initialize()); app.use(passport.session()); // Passport configuration passport.use(new LocalStrategy( { usernameField: 'username' }, async (username, password, done) => { db.get('SELECT * FROM users WHERE username = ?', [username], async (err, user) => { if (err) return done(err); if (!user) return done(null, false); const isValid = await bcrypt.compare(password, user.password_hash); if (!isValid) return done(null, false); return done(null, user); }); } )); passport.use(new GoogleStrategy({ clientID: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET, callbackURL: "/auth/google/callback" }, (accessToken, refreshToken, profile, done) => { db.get('SELECT * FROM users WHERE google_id = ?', [profile.id], (err, user) => { if (err) return done(err); if (user) { return done(null, user); } else { db.run('INSERT INTO users (google_id, display_name, email) VALUES (?, ?, ?)', [profile.id, profile.displayName, profile.emails[0].value], function(err) { if (err) return done(err); db.get('SELECT * FROM users WHERE id = ?', [this.lastID], (err, user) => { return done(err, user); }); } ); } }); })); passport.use(new InstagramStrategy({ clientID: process.env.INSTAGRAM_CLIENT_ID, clientSecret: process.env.INSTAGRAM_CLIENT_SECRET, callbackURL: "/auth/instagram/callback" }, (accessToken, refreshToken, profile, done) => { db.get('SELECT * FROM users WHERE instagram_id = ?', [profile.id], (err, user) => { if (err) return done(err); if (user) { return done(null, user); } else { db.run('INSERT INTO users (instagram_id, display_name) VALUES (?, ?)', [profile.id, profile.displayName], function(err) { if (err) return done(err); db.get('SELECT * FROM users WHERE id = ?', [this.lastID], (err, user) => { return done(err, user); }); } ); } }); })); passport.serializeUser((user, done) => { done(null, user.id); }); passport.deserializeUser((id, done) => { db.get('SELECT * FROM users WHERE id = ?', [id], (err, user) => { done(err, user); }); }); // Routes app.get('/', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'index.html')); }); // Authentication routes app.get('/auth/google', passport.authenticate('google', { scope: ['profile', 'email'] }) ); app.get('/auth/google/callback', passport.authenticate('google', { failureRedirect: '/login' }), (req, res) => { res.redirect('/dashboard'); } ); app.get('/auth/instagram', passport.authenticate('instagram') ); app.get('/auth/instagram/callback', passport.authenticate('instagram', { failureRedirect: '/login' }), (req, res) => { res.redirect('/dashboard'); } ); app.post('/auth/login', passport.authenticate('local'), (req, res) => { res.json({ success: true, user: req.user }); }); app.post('/auth/register', async (req, res) => { const { username, email, password } = req.body; try { const hashedPassword = await bcrypt.hash(password, 10); db.run('INSERT INTO users (username, email, password_hash) VALUES (?, ?, ?)', [username, email, hashedPassword], function(err) { if (err) { return res.status(400).json({ error: 'Username or email already exists' }); } db.get('SELECT * FROM users WHERE id = ?', [this.lastID], (err, user) => { if (err) return res.status(500).json({ error: 'Database error' }); req.login(user, (err) => { if (err) return res.status(500).json({ error: 'Login error' }); res.json({ success: true, user: user }); }); }); } ); } catch (error) { res.status(500).json({ error: 'Server error' }); } }); app.post('/auth/logout', (req, res) => { req.logout(() => { res.json({ success: true }); }); }); // API routes app.get('/api/user', (req, res) => { res.json({ user: req.user || null }); }); app.get('/api/stations', (req, res) => { const query = ` SELECT ws.*, u.username as created_by_name, su.description as latest_description, su.last_refill_time, su.estimated_empty_time, su.updated_at as last_updated, u2.username as updated_by_name FROM water_stations ws LEFT JOIN users u ON ws.created_by = u.id LEFT JOIN ( SELECT station_id, description, last_refill_time, estimated_empty_time, updated_by, updated_at, ROW_NUMBER() OVER (PARTITION BY station_id ORDER BY updated_at DESC) as rn FROM station_updates ) su ON ws.id = su.station_id AND su.rn = 1 LEFT JOIN users u2 ON su.updated_by = u2.id `; db.all(query, [], (err, rows) => { if (err) return res.status(500).json({ error: 'Database error' }); res.json(rows); }); }); app.post('/api/stations', (req, res) => { if (!req.user) { return res.status(401).json({ error: 'Authentication required' }); } const { name, latitude, longitude, description } = req.body; db.run('INSERT INTO water_stations (name, latitude, longitude, description, created_by) VALUES (?, ?, ?, ?, ?)', [name, latitude, longitude, description, req.user.id], function(err) { if (err) return res.status(500).json({ error: 'Database error' }); res.json({ id: this.lastID, success: true }); } ); }); app.post('/api/stations/:id/update', (req, res) => { if (!req.user) { return res.status(401).json({ error: 'Authentication required' }); } const { description, estimatedHours } = req.body; const stationId = req.params.id; const estimatedEmptyTime = new Date(); estimatedEmptyTime.setHours(estimatedEmptyTime.getHours() + parseInt(estimatedHours)); console.log("Updated by",req.user); db.run('INSERT INTO station_updates (station_id, description, estimated_empty_time, updated_by) VALUES (?, ?, ?, ?)', [stationId, description, estimatedEmptyTime.toISOString(), req.user.id], function(err) { if (err) return res.status(500).json({ error: 'Database error' }); res.json({ success: true }); } ); }); app.get('/dashboard', (req, res) => { if (!req.user) { return res.redirect('/login'); } res.sendFile(path.join(__dirname, 'public', 'dashboard.html')); }); app.get('/login', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'login.html')); }); app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); });