added webdav time off add calendar

This commit is contained in:
chris 2025-08-15 09:16:07 -04:00
parent 9445e9e624
commit 0f2bfbb6a0
3 changed files with 115 additions and 4 deletions

View File

@ -1,3 +1,7 @@
ADMIN_USERNAME="admin"
ADMIN_PASSWORD="adminpassword"
JWT_SECRET="" ##random number string
NEXTCLOUD_URL=
NEXTCLOUD_USER=
NEXTCLOUD_APP_PASSWORD=
NEXTCLOUD_CALENDAR_URL=

View File

@ -1,10 +1,12 @@
{
"dependencies": {
"@tailwindcss/vite": "^4.1.11",
"axios": "^1.11.0",
"bcryptjs": "^3.0.2",
"cors": "^2.8.5",
"dotenv": "^17.2.1",
"express": "^5.1.0",
"ics": "^3.8.1",
"jsonwebtoken": "^9.0.2",
"pm2": "^6.0.8",
"sqlite": "^5.1.1",

113
server.js
View File

@ -6,6 +6,8 @@ const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const cors = require('cors');
const path = require('path');
const axios = require('axios');
const ics = require('ics');
const PORT = process.env.PORT || 3000;
const JWT_SECRET = process.env.JWT_SECRET || 'default_secret_key';
@ -86,6 +88,94 @@ async function initializeDatabase() {
console.log("Database initialization complete.");
}
// In server.js
async function addEventToNextcloud(request) {
// Ensure we have the required config from the .env file
if (!process.env.NEXTCLOUD_URL) {
console.log("Nextcloud integration is not configured. Skipping calendar event creation.");
return;
}
// The ics library expects date parts in an array: [YYYY, MM, DD]
const start = request.start_date.split('-').map(Number);
// For an all-day event, the end date is the day *after* the last day.
const endDate = new Date(request.end_date);
endDate.setDate(endDate.getDate() + 1);
const end = [endDate.getFullYear(), endDate.getMonth() + 1, endDate.getDate()];
// Create the event object for the ics library
const event = {
start: start,
end: end,
title: `${request.username} - Time Off`,
description: `Reason: ${request.reason || 'Not specified'}`,
status: 'CONFIRMED',
busyStatus: 'BUSY',
};
// Generate the raw .ics file content
const { error, value: icsFileContent } = ics.createEvent(event);
if (error) {
console.error("Could not create .ics file:", error);
return;
}
// --- THIS IS THE FIX ---
// Remove the METHOD:PUBLISH line that Nextcloud/SabreDAV rejects.
const finalIcsContent = icsFileContent.replace('METHOD:PUBLISH\r\n', '');
// Each calendar event is a unique .ics file on the WebDAV server.
// We use the request ID to ensure a unique filename.
const eventUrl = `${process.env.NEXTCLOUD_URL}${process.env.NEXTCLOUD_CALENDAR_URL}time-off-${request.id}.ics`;
try {
// Send the corrected .ics content to the Nextcloud server
await axios.put(eventUrl, finalIcsContent, {
auth: {
username: process.env.NEXTCLOUD_USER,
password: process.env.NEXTCLOUD_APP_PASSWORD
},
headers: {
'Content-Type': 'text/calendar; charset=utf-8'
}
});
console.log(`Successfully added time-off request ${request.id} to Nextcloud calendar.`);
} catch (err) {
console.error("Error sending event to Nextcloud:", err.response ? err.response.data : err.message);
}
}
// In server.js
async function removeEventFromNextcloud(requestId) {
if (!process.env.NEXTCLOUD_URL) {
console.log("Nextcloud integration is not configured. Skipping calendar event removal.");
return;
}
// Construct the exact URL for the event's .ics file
const eventUrl = `${process.env.NEXTCLOUD_URL}${process.env.NEXTCLOUD_CALENDAR_URL}time-off-${requestId}.ics`;
try {
await axios.delete(eventUrl, {
auth: {
username: process.env.NEXTCLOUD_USER,
password: process.env.NEXTCLOUD_APP_PASSWORD
}
});
console.log(`Successfully removed time-off request ${requestId} from Nextcloud calendar.`);
} catch (err) {
// A 404 error is okay here; it just means the event wasn't on the calendar to begin with.
if (err.response && err.response.status === 404) {
console.log(`Event for request ${requestId} not found on calendar. Nothing to remove.`);
} else {
console.error("Error removing event from Nextcloud:", err.response ? err.response.data : err.message);
}
}
}
function setupRoutes() {
const authenticateToken = (req, res, next) => {
const token = req.headers['authorization']?.split(' ')[1];
@ -377,11 +467,26 @@ function setupRoutes() {
app.post('/api/admin/update-time-off-status', authenticateToken, requireRole('admin'), async (req, res) => {
try {
const { requestId, status } = req.body;
await db.run('UPDATE time_off_requests SET status = ? WHERE id = ?', [status, requestId]);
res.json({ message: `Request status updated to ${status}.` });
const { requestId, status } = req.body;
await db.run('UPDATE time_off_requests SET status = ? WHERE id = ?', [status, requestId]);
// Get the details of the request we're updating
const requestDetails = await db.get("SELECT * FROM time_off_requests WHERE id = ?", [requestId]);
if (requestDetails) {
if (status === 'approved') {
// If approved, add it to the calendar
await addEventToNextcloud(requestDetails);
} else if (status === 'pending' || status === 'denied') {
// If pushed back to pending or denied, remove it from the calendar
await removeEventFromNextcloud(requestId);
}
}
res.json({ message: `Request status updated to ${status}.` });
} catch (err) {
res.status(500).json({ message: 'Failed to update request status.' });
console.error("Error in update-time-off-status route:", err); // Added better logging
res.status(500).json({ message: 'Failed to update request status.' });
}
});