296 lines
8.9 KiB
JavaScript
296 lines
8.9 KiB
JavaScript
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}`);
|
|
}); |