const { parsePropfind, buildMultistatus, buildResponse, buildPropstat, buildHref, } = require('./utils'); const { generateETag } = require('../utils/etag-generator'); const { generateCTag } = require('../utils/ctag-generator'); const calendarRepository = require('../repositories/calendar-repository'); const taskRepository = require('../../tasks/repository'); const vtodoSerializer = require('../icalendar/vtodo-serializer'); async function handlePropfind(req, res) { try { const { username } = req.params; if (!req.currentUser || req.currentUser.email !== username) { return res.status(403).json({ error: 'Forbidden' }); } const userId = req.currentUser.id; const depth = parseInt(req.headers.depth || '0', 10); let propfindRequest; try { propfindRequest = await parsePropfind( req.rawBody || '' ); } catch (error) { console.error('PROPFIND parse error:', error); return res.status(400).json({ error: 'Invalid PROPFIND request' }); } const isTaskRequest = req.params.uid; const responses = []; if (isTaskRequest) { const taskUid = req.params.uid.replace('.ics', ''); const task = await taskRepository.findByUid(taskUid); if (!task || task.user_id !== userId) { return res.status(404).json({ error: 'Task not found' }); } const response = await buildTaskResponse( task, username, propfindRequest ); responses.push(response); } else { const calendarResponse = await buildCalendarResponse( username, userId, propfindRequest ); responses.push(calendarResponse); if (depth > 0) { const tasks = await taskRepository.findByUser(userId); for (const task of tasks) { const taskResponse = await buildTaskResponse( task, username, propfindRequest ); responses.push(taskResponse); } } } const xml = buildMultistatus(responses); res.status(207) .set('Content-Type', 'application/xml; charset=utf-8') .send(xml); } catch (error) { console.error('PROPFIND handler error:', error); return res.status(500).json({ error: 'Internal server error' }); } } async function buildCalendarResponse(username, userId, propfindRequest) { const href = buildHref(username); const tasks = await taskRepository.findByUser(userId); const ctag = generateCTag(tasks); const props = { 'D:resourcetype': { 'D:collection': '', 'C:calendar': '', }, 'D:displayname': 'Tududi Tasks', 'C:calendar-description': 'Tasks from Tududi', 'C:supported-calendar-component-set': { 'C:comp': { $: { name: 'VTODO' }, }, }, 'D:getcontenttype': 'text/calendar; charset=utf-8', 'C:getctag': ctag, 'D:current-user-privilege-set': { 'D:privilege': [ { 'D:read': '' }, { 'D:write': '' }, { 'D:write-content': '' }, { 'D:bind': '' }, { 'D:unbind': '' }, ], }, }; const propstat = buildPropstat(props); return buildResponse(href, propstat); } async function buildTaskResponse(task, username, propfindRequest) { const href = buildHref(username, task.uid); const etag = generateETag(task); try { const vtodo = await vtodoSerializer.serializeTaskToVTODO(task); const props = { 'D:resourcetype': '', 'D:displayname': task.name, 'D:getcontenttype': 'text/calendar; charset=utf-8; component=VTODO', 'D:getetag': etag, 'D:getcontentlength': Buffer.byteLength(vtodo, 'utf8').toString(), 'D:getlastmodified': new Date(task.updated_at).toUTCString(), }; const propstat = buildPropstat(props); return buildResponse(href, propstat); } catch (error) { console.error(`Error building task response for ${task.uid}:`, error); const errorProps = { 'D:resourcetype': '', 'D:displayname': task.name, }; const propstat = buildPropstat( errorProps, 'HTTP/1.1 500 Internal Server Error' ); return buildResponse(href, propstat); } } module.exports = { handlePropfind, };