groups finished, error check needed
This commit is contained in:
76
agenda.js
76
agenda.js
@@ -168,6 +168,82 @@ const initializeAgenda = async (mongoUri, pool, io) => { // Now accepts pgPool
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
agenda.define('groupPosChangeScheduled', async (job) => {
|
||||||
|
const { groupID, newPos, userID } = job.attrs.data;
|
||||||
|
const dateTime = new Date();
|
||||||
|
try {
|
||||||
|
// Query current group members at execution time
|
||||||
|
const {rows: peripheralRows} = await sharedPgPool.query(
|
||||||
|
`SELECT p.id, p.peripheral_number, p.device_id
|
||||||
|
FROM peripherals p
|
||||||
|
JOIN group_peripherals gp ON p.id = gp.peripheral_id
|
||||||
|
JOIN groups g ON gp.group_id = g.id
|
||||||
|
WHERE g.id = $1 AND g.user_id = $2`,
|
||||||
|
[groupID, userID]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (peripheralRows.length === 0) {
|
||||||
|
console.log(`No peripherals found in group ${groupID}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group peripherals by device_id
|
||||||
|
const deviceMap = new Map();
|
||||||
|
for (const periph of peripheralRows) {
|
||||||
|
if (!deviceMap.has(periph.device_id)) {
|
||||||
|
deviceMap.set(periph.device_id, []);
|
||||||
|
}
|
||||||
|
deviceMap.get(periph.device_id).push({
|
||||||
|
periphNum: periph.peripheral_number,
|
||||||
|
periphID: periph.id,
|
||||||
|
pos: newPos
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update database for all peripherals
|
||||||
|
for (const periph of peripheralRows) {
|
||||||
|
await sharedPgPool.query(
|
||||||
|
"UPDATE peripherals SET last_pos=$1, last_set=$2 WHERE id=$3",
|
||||||
|
[newPos, dateTime, periph.id]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send socket events to each device
|
||||||
|
for (const [deviceId, changedPosList] of deviceMap.entries()) {
|
||||||
|
const {rows: deviceRows} = await sharedPgPool.query(
|
||||||
|
"SELECT socket FROM device_tokens WHERE device_id=$1 AND connected=TRUE",
|
||||||
|
[deviceId]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (deviceRows.length === 1 && deviceRows[0].socket) {
|
||||||
|
const posWithoutID = changedPosList.map(pos => {
|
||||||
|
const { periphID, ...rest } = pos;
|
||||||
|
return rest;
|
||||||
|
});
|
||||||
|
socketIoInstance.to(deviceRows[0].socket).emit("posUpdates", posWithoutID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify user app
|
||||||
|
const {rows: userRows} = await sharedPgPool.query(
|
||||||
|
"SELECT socket FROM user_tokens WHERE user_id=$1",
|
||||||
|
[userID]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (userRows.length === 1 && userRows[0] && userRows[0].socket) {
|
||||||
|
const posWithoutNumber = peripheralRows.map(p => ({
|
||||||
|
periphID: p.id,
|
||||||
|
pos: newPos
|
||||||
|
}));
|
||||||
|
socketIoInstance.to(userRows[0].socket).emit("posUpdates", posWithoutNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error processing group schedule job:`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
agenda.on('ready', () => console.log('Agenda connected to MongoDB and ready!'));
|
agenda.on('ready', () => console.log('Agenda connected to MongoDB and ready!'));
|
||||||
agenda.on('start', (job) => console.log(`Job "${job.attrs.name}" starting`));
|
agenda.on('start', (job) => console.log(`Job "${job.attrs.name}" starting`));
|
||||||
agenda.on('complete', (job) => console.log(`Job "${job.attrs.name}" complete`));
|
agenda.on('complete', (job) => console.log(`Job "${job.attrs.name}" complete`));
|
||||||
|
|||||||
256
index.js
256
index.js
@@ -760,6 +760,85 @@ app.post('/rename_peripheral', authenticateToken, async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.post('/rename_group', authenticateToken, async (req, res) => {
|
||||||
|
console.log("Rename group");
|
||||||
|
try {
|
||||||
|
const {groupId, newName} = req.body;
|
||||||
|
const result = await pool.query("update groups set name=$1 where id=$2 and user_id=$3", [newName, groupId, req.user]);
|
||||||
|
if (result.rowCount === 0) return res.status(404).json({ error: 'Group not found' });
|
||||||
|
res.sendStatus(204);
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code == '23505') return res.status(409).json({ error: 'Group name must be unique' });
|
||||||
|
console.error(err);
|
||||||
|
res.sendStatus(500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/update_group', authenticateToken, async (req, res) => {
|
||||||
|
console.log("Update group members");
|
||||||
|
try {
|
||||||
|
const { groupId, peripheral_ids } = req.body;
|
||||||
|
|
||||||
|
if (!groupId || !peripheral_ids || !Array.isArray(peripheral_ids)) {
|
||||||
|
return res.status(400).json({ error: 'Invalid request parameters' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify group belongs to user
|
||||||
|
const {rows: groupRows} = await pool.query(
|
||||||
|
'SELECT id FROM groups WHERE id=$1 AND user_id=$2',
|
||||||
|
[groupId, req.user]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (groupRows.length === 0) {
|
||||||
|
return res.status(404).json({ error: 'Group not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify all peripherals belong to user
|
||||||
|
const {rows: periphRows} = await pool.query(
|
||||||
|
'SELECT id FROM peripherals WHERE id = ANY($1::int[]) AND user_id=$2',
|
||||||
|
[peripheral_ids, req.user]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (periphRows.length !== peripheral_ids.length) {
|
||||||
|
return res.status(403).json({ error: 'One or more peripherals do not belong to you' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this exact peripheral set already exists in another group
|
||||||
|
const {rows: duplicateRows} = await pool.query(
|
||||||
|
`SELECT g.id, g.name
|
||||||
|
FROM groups g
|
||||||
|
JOIN (
|
||||||
|
SELECT group_id, array_agg(peripheral_id ORDER BY peripheral_id) as periph_set
|
||||||
|
FROM group_peripherals
|
||||||
|
GROUP BY group_id
|
||||||
|
) gp ON g.id = gp.group_id
|
||||||
|
WHERE gp.periph_set = $1::int[]
|
||||||
|
AND g.user_id = $2
|
||||||
|
AND g.id != $3`,
|
||||||
|
[peripheral_ids.sort((a, b) => a - b), req.user, groupId]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (duplicateRows.length > 0) {
|
||||||
|
return res.status(409).json({
|
||||||
|
error: 'This combination of blinds already exists in another group',
|
||||||
|
existing_group: duplicateRows[0].name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete existing group_peripherals entries
|
||||||
|
await pool.query('DELETE FROM group_peripherals WHERE group_id=$1', [groupId]);
|
||||||
|
|
||||||
|
// Insert new group_peripherals entries
|
||||||
|
const insertValues = peripheral_ids.map(pid => `(${groupId}, ${pid})`).join(',');
|
||||||
|
await pool.query(`INSERT INTO group_peripherals (group_id, peripheral_id) VALUES ${insertValues}`);
|
||||||
|
|
||||||
|
res.status(200).json({ success: true, message: 'Group updated successfully' });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating group:', error);
|
||||||
|
res.status(500).json({ error: 'Internal Server Error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
app.post('/delete_device', authenticateToken, async (req, res) => {
|
app.post('/delete_device', authenticateToken, async (req, res) => {
|
||||||
console.log("delete device");
|
console.log("delete device");
|
||||||
try {
|
try {
|
||||||
@@ -1265,3 +1344,180 @@ app.post('/group_position_update', authenticateToken, async (req, res) => {
|
|||||||
res.status(500).json({ error: 'Internal Server Error' });
|
res.status(500).json({ error: 'Internal Server Error' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.post('/add_group_schedule', authenticateToken, async (req, res) => {
|
||||||
|
console.log("add_group_schedule");
|
||||||
|
try {
|
||||||
|
const { groupId, newPos, time, daysOfWeek } = req.body;
|
||||||
|
|
||||||
|
if (!groupId || newPos === undefined || !time || !daysOfWeek || !Array.isArray(daysOfWeek)) {
|
||||||
|
return res.status(400).json({ error: 'Missing required fields' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify group belongs to user
|
||||||
|
const {rows: groupRows} = await pool.query(
|
||||||
|
'SELECT id FROM groups WHERE id=$1 AND user_id=$2',
|
||||||
|
[groupId, req.user]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (groupRows.length === 0) {
|
||||||
|
return res.status(404).json({ error: 'Group not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for duplicate schedule
|
||||||
|
const cronExpression = createCronExpression(time, daysOfWeek);
|
||||||
|
const existingJobs = await agenda.jobs({
|
||||||
|
name: 'groupPosChangeScheduled',
|
||||||
|
'data.groupID': groupId,
|
||||||
|
'data.userID': req.user
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const existingJob of existingJobs) {
|
||||||
|
if (existingJob.attrs.repeatInterval === cronExpression && existingJob.attrs.data.newPos === newPos) {
|
||||||
|
return res.status(409).json({ error: 'Duplicate schedule already exists' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the job
|
||||||
|
const job = await agenda.create('groupPosChangeScheduled', {
|
||||||
|
groupID: groupId,
|
||||||
|
newPos,
|
||||||
|
userID: req.user
|
||||||
|
});
|
||||||
|
|
||||||
|
job.repeatEvery(cronExpression, {
|
||||||
|
timezone: 'America/New_York',
|
||||||
|
skipImmediate: true
|
||||||
|
});
|
||||||
|
|
||||||
|
await job.save();
|
||||||
|
|
||||||
|
res.status(201).json({
|
||||||
|
success: true,
|
||||||
|
message: 'Group schedule created successfully',
|
||||||
|
jobId: job.attrs._id
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating group schedule:', error);
|
||||||
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/delete_group_schedule', authenticateToken, async (req, res) => {
|
||||||
|
console.log("delete_group_schedule");
|
||||||
|
try {
|
||||||
|
const { jobId } = req.body;
|
||||||
|
|
||||||
|
if (!jobId) {
|
||||||
|
return res.status(400).json({ error: 'Missing jobId' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const job = await findUserScheduleJob(jobId, req.user);
|
||||||
|
if (!job) {
|
||||||
|
return res.status(404).json({ error: 'Schedule not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
await job.remove();
|
||||||
|
res.status(200).json({ success: true, message: 'Group schedule deleted successfully' });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting group schedule:', error);
|
||||||
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/update_group_schedule', authenticateToken, async (req, res) => {
|
||||||
|
console.log("update_group_schedule");
|
||||||
|
try {
|
||||||
|
const { jobId, newPos, time, daysOfWeek } = req.body;
|
||||||
|
|
||||||
|
if (!jobId || newPos === undefined || !time || !daysOfWeek) {
|
||||||
|
return res.status(400).json({ error: 'Missing required fields' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const job = await findUserScheduleJob(jobId, req.user);
|
||||||
|
if (!job) {
|
||||||
|
return res.status(404).json({ error: 'Schedule not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const groupId = job.attrs.data.groupID;
|
||||||
|
const cronExpression = createCronExpression(time, daysOfWeek);
|
||||||
|
|
||||||
|
// Check for duplicates excluding current job
|
||||||
|
const existingJobs = await agenda.jobs({
|
||||||
|
name: 'groupPosChangeScheduled',
|
||||||
|
'data.groupID': groupId,
|
||||||
|
'data.userID': req.user,
|
||||||
|
_id: { $ne: new ObjectId(jobId) }
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const existingJob of existingJobs) {
|
||||||
|
if (existingJob.attrs.repeatInterval === cronExpression && existingJob.attrs.data.newPos === newPos) {
|
||||||
|
return res.status(409).json({ error: 'Duplicate schedule already exists' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update job
|
||||||
|
job.attrs.data.newPos = newPos;
|
||||||
|
job.repeatEvery(cronExpression, {
|
||||||
|
timezone: 'America/New_York',
|
||||||
|
skipImmediate: true
|
||||||
|
});
|
||||||
|
|
||||||
|
await job.save();
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
message: 'Group schedule updated successfully'
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating group schedule:', error);
|
||||||
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/group_schedule_list', authenticateToken, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { groupId } = req.body;
|
||||||
|
|
||||||
|
if (!groupId) {
|
||||||
|
return res.status(400).json({ error: 'Missing groupId' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify group belongs to user
|
||||||
|
const {rows: groupRows} = await pool.query(
|
||||||
|
'SELECT id FROM groups WHERE id=$1 AND user_id=$2',
|
||||||
|
[groupId, req.user]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (groupRows.length === 0) {
|
||||||
|
return res.status(404).json({ error: 'Group not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const jobs = await agenda.jobs({
|
||||||
|
name: 'groupPosChangeScheduled',
|
||||||
|
'data.groupID': groupId,
|
||||||
|
'data.userID': req.user
|
||||||
|
});
|
||||||
|
|
||||||
|
const scheduledUpdates = jobs.map(job => {
|
||||||
|
const interval = cronParser.parseExpression(job.attrs.repeatInterval);
|
||||||
|
const schedule = {
|
||||||
|
minutes: interval.fields.minute,
|
||||||
|
hours: interval.fields.hour,
|
||||||
|
daysOfWeek: interval.fields.dayOfWeek
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: job.attrs._id,
|
||||||
|
pos: job.attrs.data.newPos,
|
||||||
|
schedule
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(200).json({ scheduledUpdates });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching group schedules:', error);
|
||||||
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user