first commit

This commit is contained in:
Aditya Pulipaka
2025-06-28 19:11:25 -05:00
commit 53fde4ba6a
6 changed files with 4055 additions and 0 deletions

69
.gitignore vendored Normal file
View File

@@ -0,0 +1,69 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
firebase-debug.log*
firebase-debug.*.log*
# Firebase cache
.firebase/
# Firebase config
# Uncomment this if you'd like others to create their own Firebase project.
# For a team working on the same Firebase project(s), it is recommended to leave
# it commented so all members can deploy to the same project(s) in .firebaserc.
# .firebaserc
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# dataconnect generated files
.dataconnect

48
agenda.js Normal file
View File

@@ -0,0 +1,48 @@
// agenda.js (modified to receive the already created pool)
const Agenda = require('agenda');
const socketIo = require('socket.io');
let agenda;
let wssInstance;
let sharedPgPool; // This will hold the pool instance passed from server.js
const initializeAgenda = async (mongoUri, pool, wss) => { // Now accepts pgPool
wssInstance = wss;
sharedPgPool = pool; // Store the passed pool
agenda = new Agenda({
db: {
address: mongoUri || 'mongodb://localhost:27017/myScheduledApp',
collection: 'agendaJobs',
}
});
agenda.define('manual update position', async (job) => {
const { recordId, dataToUpdate, conditionCheck } = job.attrs.data;
console.log(`Processing job for recordId: ${recordId} at ${new Date()}`);
try {
const updatedRecord = result.rows[0];
} catch (error) {
console.error(`Error processing job for recordId ${recordId}:`, error);
throw error;
}
});
agenda.on('ready', () => console.log('Agenda connected to MongoDB and ready!'));
agenda.on('start', (job) => console.log(`Job "${job.attrs.name}" starting`));
agenda.on('complete', (job) => console.log(`Job "${job.attrs.name}" complete`));
agenda.on('success', (job) => console.log(`Job "${job.attrs.name}" succeeded`));
agenda.on('fail', (err, job) => console.error(`Job "${job.attrs.name}" failed: ${err.message}`));
await agenda.start();
console.log('Agenda job processing started.');
};
module.exports = {
agenda,
initializeAgenda
};

19
db.js Normal file
View File

@@ -0,0 +1,19 @@
// db.js
const mongoose = require('mongoose');
const connectDB = async () => {
try {
const mongoUri = process.env.MONGO_URI || 'mongodb://localhost:27017/myScheduledApp';
await mongoose.connect(mongoUri, {
// These options are often recommended for newer Mongoose versions, though some might be deprecated in future
// useNewUrlParser: true,
// useUnifiedTopology: true,
});
console.log('MongoDB connected successfully for Mongoose!');
} catch (err) {
console.error('MongoDB connection error (Mongoose):', err);
process.exit(1); // Exit process with failure
}
};
module.exports = connectDB;

425
index.js Normal file
View File

@@ -0,0 +1,425 @@
const { verify, hash } = require('argon2');
const express = require('express');
const { json } = require('express');
const { Pool } = require('pg');
require('dotenv').config();
const jwt = require('jsonwebtoken');
const http = require('http');
const socketIo = require('socket.io');
const app = express();
const port = 3000;
app.use(json());
const pool = new Pool();
const server = http.createServer(app);
const io = socketIo(server)
const JWT_SECRET = process.env.JWT_SECRET;
const TOKEN_EXPIRY = '5d';
io.on('connection', (socket) => {
console.log("periph connected");
socket.on("authenticate", async (data) => {
try {
const authData = JSON.parse(data);
const token = authData.token;
const periphId = authData.id;
const {rows} = await pool.query("select * from device_tokens where device_id=$1 and token=$2 and connected=FALSE",
[periphId, token]
);
if (rows.length != 1) {
const errorResponse = {
type: 'error',
code: 404,
message: 'Device not found'
};
socket.emit("error", errorResponse);
socket.disconnect(true);
}
else {
await pool.query("update device_tokens set connected=TRUE where device_id=$1 and token=$2 and connected=FALSE",
[periphId, token]
);
const successResponse = {
type: 'success',
code: 200,
message: 'Device found'
};
socket.emit("success", successResponse);
}
} catch (error) {
console.error('Error during periph authentication:', error);
// Send an error message to the client
socket.emit('error', { type: 'error', code: 500 });
// Disconnect the client
socket.disconnect(true);
}
})
})
async function createToken(userId) {
const token = jwt.sign({ type: 'user', userId }, JWT_SECRET, { expiresIn: TOKEN_EXPIRY });
await pool.query("delete from user_tokens where user_id=$1", [userId]);
await pool.query("insert into user_tokens (user_id, token) values ($1, $2)", [userId, token]);
return token;
}
async function createPeripheralToken(peripheralId) {
const token = jwt.sign({ type: 'peripheral', peripheralId }, JWT_SECRET);
await pool.query("insert into device_tokens (device_id, token) values ($1, $2)", [peripheralId, token]);
return token;
}
async function createTempPeriphToken(peripheralId) {
const token = jwt.sign({type: 'peripheral', peripheralId}, JWT_SECRET, {expiresIn: '2m'} );
await pool.query("insert into device_tokens (device_id, token) values ($1, $2)", [peripheralId, token]);
return token;
}
async function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader?.split(' ')[1];
if (!token) return res.sendStatus(401);
try {
const payload = jwt.verify(token, JWT_SECRET);
if (payload.type === 'user') {
const {rows} = await pool.query("select user_id from user_tokens where token=$1", [token]);
if (rows.length != 1) throw new Error("Invalid/Expired Token");
req.user = payload.userId; // make Id accessible in route handlers
}
else if (payload.type === 'peripheral'){
const {rows} = await pool.query("select device_id from device_tokens where token=$1", [token]);
if (rows.length != 1) throw new Error("Invalid/Expired Token");
req.peripheral = payload.peripheralId;
}
next();
} catch {
return res.sendStatus(403); // invalid/expired token
}
}
app.get('/', (req, res) => {
// console.log(req);
res.send('Hello World!');
});
app.post('/login', async (req, res) => {
const { email, password } = req.body;
console.log('login');
if (!email || !password) return res.status(400).json({error: 'email and password required'});
try {
const {rows} = await pool.query('select id, password_hash_string from users where email = $1', [email]);
if (rows.length === 0) return res.status(401).json({error: 'Invalid Credentials'});
const user = rows[0]
console.log('user found');
const verified = await verify(user.password_hash_string, password);
if (!verified) return res.status(401).json({ error: 'Invalid credentials' });
console.log("password correct");
const token = await createToken(user.id); // token is now tied to ID
res.status(200).json({token});
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Internal server error' });
}
});
app.post('/create_user', async (req, res) => {
console.log("got post req");
const {name, email, password} = req.body
try {
const hashedPass = await hash(password);
await pool.query("insert into users (name, email, password_hash_string) values (nullif($1, ''), $2, $3)",
[name, email, hashedPass]
);
return res.sendStatus(201);
} catch (err) {
console.error(err);
if (err.code === '23505') {
return res.status(409).json({ error: 'Email already in use' });
}
return res.sendStatus(500);
}
});
app.get('/verify', authenticateToken, async (req, res) => {
try {
// Issue a new token to extend session
const newToken = await createToken(req.user);
res.status(200).json({token: newToken});
} catch {
res.status(500).json({ error: 'server error' });
}
});
app.get('/device_list', authenticateToken, async (req, res) => {
try {
console.log("device List request");
console.log(req.user);
const {rows} = await pool.query('select id, device_name from devices where user_id = $1', [req.user]);
const deviceNames = rows.map(row => row.device_name);
const deviceIds = rows.map(row => row.id);
res.status(200).json({ device_ids: deviceIds, devices: deviceNames });
} catch {
res.status(500).json({error: 'Internal Server Error'});
}
});
app.get('/device_name', authenticateToken, async (req, res) => {
console.log("deviceName");
try {
const {deviceId} = req.query;
const {rows} = await pool.query('select device_name from devices where id=$1 and user_id=$2',
[deviceId, req.user]);
if (rows.length != 1) return res.sendStatus(404);
const deviceName = rows[0].device_name;
res.status(200).json({device_name: deviceName});
} catch {
res.sendStatus(500);
}
});
app.get('/peripheral_list', authenticateToken, async (req, res) => {
console.log("periph list")
try {
const {deviceId} = req.query;
const {rows} = await pool.query('select id, peripheral_number, peripheral_name from peripherals where device_id=$1 and user_id=$2',
[deviceId, req.user]);
const peripheralIds = rows.map(row => row.id);
const portNums = rows.map(row => row.peripheral_number);
const peripheralNames = rows.map(row => row.peripheral_name);
res.status(200).json({peripheral_ids: peripheralIds, port_nums: portNums, peripheral_names: peripheralNames});
} catch {
res.sendStatus(500);
}
})
app.post('/add_device', authenticateToken, async (req, res) => {
try {
console.log("add device request");
console.log(req.user);
console.log(req.peripheral);
const {deviceName} = req.body;
console.log(deviceName);
const {rows} = await pool.query("insert into devices (user_id, device_name) values ($1, $2) returning id",
[req.user, deviceName]
); // finish token return based on device ID.
const deviceInitToken = await createTempPeriphToken(rows[0].id);
res.status(201).json({token: deviceInitToken});
} catch (err) {
console.log(err);
if (err.code == '23505') {
return res.status(409).json({ error: 'Device Name in use' });
}
res.status(500).json({error: 'Internal Server Error'});
}
});
app.post('/add_peripheral', authenticateToken, async (req, res) => {
try {
const {device_id, port_num, peripheral_name} = req.body;
await pool.query("insert into peripherals (device_id, peripheral_number, peripheral_name, user_id) values ($1, $2, $3, $4)",
[device_id, port_num, peripheral_name, req.user]
);
res.sendStatus(201);
} catch (err){
if (err.code == '23505') return res.sendStatus(409);
res.sendStatus(500);
}
});
app.get('/verify_device', authenticateToken, async (req, res) => {
console.log("device verify");
try{
console.log(req.peripheral);
await pool.query("delete from device_tokens where device_id=$1", [req.peripheral]);
const newToken = await createPeripheralToken(req.peripheral);
console.log("New Token", newToken);
res.json({token: newToken, id: req.peripheral});
} catch {
res.status(500).json({error: "server error"});
}
});
app.get('/position', authenticateToken, async (req, res) => {
console.log("devicepos");
try {
const {rows} = await pool.query("select * from peripherals where device_id=$1", [req.peripheral]);
if (rows.length == 0) {
return res.sendStatus(404);
}
const lastPosList = rows.map(row => row.last_pos);
const portNums = rows.map(row => row.peripheral_number);
const awaitCalibList = rows.map(row => row.await_calib);
const ready = rows.map(row => row.calibrated);
res.status(200).json({positions: lastPosList, port_nums: portNums, calib_needed: awaitCalibList, ready: ready});
} catch {
res.status(500).json({error: "server error"});
}
});
app.post('/manual_position_update', authenticateToken, async (req, res) => {
console.log("setpos");
try {
const {periphId, newPos, time} = req.body;
const dateTime = new Date(time);
const result = await pool.query("update peripherals set last_pos=$1, last_set=$2 where id=$3 and user_id=$4",
[newPos, dateTime, periphId, req.user]
);
if (result.rowCount === 0) return res.sendStatus(404);
res.sendStatus(204);
} catch {
res.status(500).json({error: "server error"});
}
});
app.post('/calib', authenticateToken, async (req, res) => {
console.log("calibrate");
try {
const {periphId} = req.body;
const result = await pool.query("update peripherals set await_calib=true where id=$1 and user_id=$2",
[periphId, req.user]);
if (result.rowCount === 0) return res.sendStatus(404);
res.sendStatus(204);
} catch {
res.sendStatus(500);
}
})
app.post('/cancel_calib', authenticateToken, async (req, res) => {
console.log("cancelCalib");
try {
const {periphId} = req.body;
const result = await pool.query("update peripherals set await_calib=false where id=$1 and user_id=$2",
[periphId, req.user]);
if (result.rowCount === 0) return res.sendStatus(404);
res.sendStatus(204);
} catch {
res.sendStatus(500);
}
});
app.get('/peripheral_status', authenticateToken, async (req, res) => {
console.log("status");
try {
const {periphId} = req.query;
const {rows} = await pool.query("select last_pos, last_set, calibrated, await_calib from peripherals where id=$1 and user_id=$2",
[periphId, req.user]
);
if (rows.length != 1) return res.sendStatus(404);
res.status(200).json({last_pos: rows[0].last_pos, last_set: rows[0].last_set,
calibrated: rows[0].calibrated, await_calib: rows[0].await_calib});
} catch {
res.sendStatus(500);
}
});
app.get('/peripheral_name', authenticateToken, async (req, res) => {
console.log("urmom");
try {
const {periphId} = req.query;
const {rows} = await pool.query("select peripheral_name from peripherals where id=$1 and user_id=$2",
[periphId, req.user]
);
if (rows.length != 1) return res.sendStatus(404);
res.status(200).json({name: rows[0].peripheral_name});
} catch {
res.sendStatus(500);
}
})
app.post('/completed_calib', authenticateToken, async (req, res) => {
console.log("calibration complete");
try {
const {portNum} = req.body;
const result = await pool.query("update peripherals set calibrated=true, await_calib=false where device_id=$1 and peripheral_number=$2",
[req.peripheral, portNum]
);
if (result.rowCount === 0) return res.sendStatus(404);
res.sendStatus(204);
} catch (error) {
console.error(error);
res.sendStatus(500);
}
});
app.post('/rename_device', authenticateToken, async (req, res) => {
console.log("Hub rename");
try {
const {deviceId, newName} = req.body;
const result = await pool.query("update devices set device_name=$1 where id=$2 and user_id=$3", [newName, deviceId, req.user]);
if (result.rowCount === 0) return res.sendStatus(404);
res.sendStatus(204);
} catch (err) {
if (err.code == '23505') return res.sendStatus(409);
console.error(err);
res.sendStatus(500);
}
});
app.post('/rename_peripheral', authenticateToken, async (req, res) => {
console.log("Hub rename");
try {
const {periphId, newName} = req.body;
const result = await pool.query("update peripherals set peripheral_name=$1 where id=$2 and user_id=$3", [newName, periphId, req.user]);
if (result.rowCount === 0) return res.sendStatus(404);
res.sendStatus(204);
} catch (err) {
if (err.code == '23505') return res.sendStatus(409);
console.error(err);
res.sendStatus(500);
}
});
app.post('/delete_device', authenticateToken, async (req, res) => {
console.log("delete device");
try {
const {deviceId} = req.body;
const {rows} = await pool.query("delete from devices where user_id=$1 and id=$2 returning id",
[req.user, deviceId]
);
if (rows.length != 1) {
return res.status(404).json({ error: 'Device not found' });
}
await pool.query("delete from device_tokens where device_id=$1", [rows[0].id]);
res.sendStatus(204);
} catch {
res.status(500).json({error: "server error"});
}
});
app.post('/delete_peripheral', authenticateToken, async (req, res) => {
console.log("delete peripheral");
try {
const {periphId} = req.body;
const {rows} = await pool.query("delete from peripherals where user_id = $1 and id=$2 returning id",
[req.user, periphId]
);
if (rows.length != 1) {
return res.status(404).json({ error: 'Device not found' });
}
res.sendStatus(204);
} catch {
res.sendStatus(500);
}
})
server.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`);
});

3471
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

23
package.json Normal file
View File

@@ -0,0 +1,23 @@
{
"name": "blinds_express",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"agenda": "^5.0.0",
"argon2": "^0.43.0",
"dotenv": "^16.5.0",
"express": "^5.1.0",
"jsonwebtoken": "^9.0.2",
"mongoose": "^8.16.1",
"node-schedule": "^2.1.1",
"pg": "^8.16.0",
"socket.io": "^4.8.1"
}
}