added webdav time off add calendar
This commit is contained in:
parent
9445e9e624
commit
0f2bfbb6a0
@ -1,3 +1,7 @@
|
|||||||
ADMIN_USERNAME="admin"
|
ADMIN_USERNAME="admin"
|
||||||
ADMIN_PASSWORD="adminpassword"
|
ADMIN_PASSWORD="adminpassword"
|
||||||
JWT_SECRET="" ##random number string
|
JWT_SECRET="" ##random number string
|
||||||
|
NEXTCLOUD_URL=
|
||||||
|
NEXTCLOUD_USER=
|
||||||
|
NEXTCLOUD_APP_PASSWORD=
|
||||||
|
NEXTCLOUD_CALENDAR_URL=
|
||||||
@ -1,10 +1,12 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/vite": "^4.1.11",
|
"@tailwindcss/vite": "^4.1.11",
|
||||||
|
"axios": "^1.11.0",
|
||||||
"bcryptjs": "^3.0.2",
|
"bcryptjs": "^3.0.2",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^17.2.1",
|
"dotenv": "^17.2.1",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
|
"ics": "^3.8.1",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"pm2": "^6.0.8",
|
"pm2": "^6.0.8",
|
||||||
"sqlite": "^5.1.1",
|
"sqlite": "^5.1.1",
|
||||||
|
|||||||
113
server.js
113
server.js
@ -6,6 +6,8 @@ const bcrypt = require('bcryptjs');
|
|||||||
const jwt = require('jsonwebtoken');
|
const jwt = require('jsonwebtoken');
|
||||||
const cors = require('cors');
|
const cors = require('cors');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const axios = require('axios');
|
||||||
|
const ics = require('ics');
|
||||||
|
|
||||||
const PORT = process.env.PORT || 3000;
|
const PORT = process.env.PORT || 3000;
|
||||||
const JWT_SECRET = process.env.JWT_SECRET || 'default_secret_key';
|
const JWT_SECRET = process.env.JWT_SECRET || 'default_secret_key';
|
||||||
@ -86,6 +88,94 @@ async function initializeDatabase() {
|
|||||||
console.log("Database initialization complete.");
|
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() {
|
function setupRoutes() {
|
||||||
const authenticateToken = (req, res, next) => {
|
const authenticateToken = (req, res, next) => {
|
||||||
const token = req.headers['authorization']?.split(' ')[1];
|
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) => {
|
app.post('/api/admin/update-time-off-status', authenticateToken, requireRole('admin'), async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { requestId, status } = req.body;
|
const { requestId, status } = req.body;
|
||||||
await db.run('UPDATE time_off_requests SET status = ? WHERE id = ?', [status, requestId]);
|
await db.run('UPDATE time_off_requests SET status = ? WHERE id = ?', [status, requestId]);
|
||||||
res.json({ message: `Request status updated to ${status}.` });
|
|
||||||
|
// 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) {
|
} 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.' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user