caldav-adapter 4.3.1 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.commitlintrc.js +3 -0
- package/.editorconfig +9 -0
- package/.gitattributes +1 -0
- package/.github/workflows/ci.yml +24 -0
- package/.husky/commit-msg +4 -0
- package/.husky/pre-commit +4 -0
- package/.lintstagedrc.js +5 -0
- package/.prettierrc.js +5 -0
- package/.remarkignore +1 -0
- package/.remarkrc.js +3 -0
- package/.xo-config.js +8 -0
- package/LICENSE +21 -0
- package/README.md +26 -240
- package/common/parse-body.js +16 -0
- package/common/response.js +51 -0
- package/common/tags.js +412 -0
- package/common/winston.js +32 -0
- package/common/x-build.js +109 -0
- package/common/xml.js +36 -0
- package/index.js +171 -0
- package/package.json +58 -40
- package/routes/calendar/calendar/calendar-multiget.js +48 -0
- package/routes/calendar/calendar/calendar-query.js +80 -0
- package/routes/calendar/calendar/delete.js +40 -0
- package/routes/calendar/calendar/event-response.js +37 -0
- package/routes/calendar/calendar/expand-property.js +11 -0
- package/routes/calendar/calendar/get.js +27 -0
- package/routes/calendar/calendar/propfind.js +73 -0
- package/routes/calendar/calendar/proppatch.js +32 -0
- package/routes/calendar/calendar/put.js +76 -0
- package/routes/calendar/calendar/report.js +36 -0
- package/routes/calendar/calendar/sync-collection.js +36 -0
- package/routes/calendar/calendar.js +80 -0
- package/routes/calendar/user/propfind.js +60 -0
- package/routes/calendar/user/proppatch.js +31 -0
- package/routes/principal/principal.js +31 -0
- package/routes/principal/propfind.js +32 -0
- package/routes/principal/report.js +27 -0
- package/test/test.js +8 -0
- package/lib/common/date.d.ts +0 -2
- package/lib/common/date.js +0 -11
- package/lib/common/eventBuild.d.ts +0 -6
- package/lib/common/eventBuild.js +0 -170
- package/lib/common/parseBody.d.ts +0 -2
- package/lib/common/parseBody.js +0 -21
- package/lib/common/response.d.ts +0 -6
- package/lib/common/response.js +0 -53
- package/lib/common/tags.d.ts +0 -263
- package/lib/common/tags.js +0 -337
- package/lib/common/winston.d.ts +0 -11
- package/lib/common/winston.js +0 -26
- package/lib/common/xBuild.d.ts +0 -17
- package/lib/common/xBuild.js +0 -80
- package/lib/common/xml.d.ts +0 -17
- package/lib/common/xml.js +0 -30
- package/lib/index.d.ts +0 -143
- package/lib/index.js +0 -8
- package/lib/koa.d.ts +0 -21
- package/lib/koa.js +0 -130
- package/lib/routes/calendar/calendar/calendar-multiget.d.ts +0 -7
- package/lib/routes/calendar/calendar/calendar-multiget.js +0 -68
- package/lib/routes/calendar/calendar/calendar-query.d.ts +0 -7
- package/lib/routes/calendar/calendar/calendar-query.js +0 -68
- package/lib/routes/calendar/calendar/delete.d.ts +0 -5
- package/lib/routes/calendar/calendar/delete.js +0 -40
- package/lib/routes/calendar/calendar/eventResponse.d.ts +0 -7
- package/lib/routes/calendar/calendar/eventResponse.js +0 -36
- package/lib/routes/calendar/calendar/expand-property.d.ts +0 -7
- package/lib/routes/calendar/calendar/expand-property.js +0 -15
- package/lib/routes/calendar/calendar/get.d.ts +0 -5
- package/lib/routes/calendar/calendar/get.js +0 -30
- package/lib/routes/calendar/calendar/propfind.d.ts +0 -8
- package/lib/routes/calendar/calendar/propfind.js +0 -76
- package/lib/routes/calendar/calendar/proppatch.d.ts +0 -5
- package/lib/routes/calendar/calendar/proppatch.js +0 -53
- package/lib/routes/calendar/calendar/put.d.ts +0 -5
- package/lib/routes/calendar/calendar/put.js +0 -68
- package/lib/routes/calendar/calendar/report.d.ts +0 -5
- package/lib/routes/calendar/calendar/report.js +0 -39
- package/lib/routes/calendar/calendar/sync-collection.d.ts +0 -10
- package/lib/routes/calendar/calendar/sync-collection.js +0 -57
- package/lib/routes/calendar/calendar.d.ts +0 -3
- package/lib/routes/calendar/calendar.js +0 -78
- package/lib/routes/calendar/user/propfind.d.ts +0 -5
- package/lib/routes/calendar/user/propfind.js +0 -64
- package/lib/routes/calendar/user/proppatch.d.ts +0 -5
- package/lib/routes/calendar/user/proppatch.js +0 -52
- package/lib/routes/principal/principal.d.ts +0 -3
- package/lib/routes/principal/principal.js +0 -32
- package/lib/routes/principal/propfind.d.ts +0 -3
- package/lib/routes/principal/propfind.js +0 -50
- package/lib/routes/principal/report.d.ts +0 -3
- package/lib/routes/principal/report.js +0 -32
package/index.js
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
const path = require('node:path');
|
|
2
|
+
const { pathToRegexp } = require('path-to-regexp');
|
|
3
|
+
const basicAuth = require('basic-auth');
|
|
4
|
+
const parseBody = require('./common/parse-body');
|
|
5
|
+
const winston = require('./common/winston');
|
|
6
|
+
const cal = require('./routes/calendar/calendar');
|
|
7
|
+
const pri = require('./routes/principal/principal');
|
|
8
|
+
|
|
9
|
+
const defaults = {
|
|
10
|
+
caldavRoot: '/',
|
|
11
|
+
calendarRoot: 'cal',
|
|
12
|
+
principalRoot: 'p',
|
|
13
|
+
logEnabled: false
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
module.exports = function (options) {
|
|
17
|
+
options = Object.assign(defaults, options);
|
|
18
|
+
|
|
19
|
+
const log = winston({ ...options, label: 'index' });
|
|
20
|
+
|
|
21
|
+
const rootRoute = path.join('/', options.caldavRoot);
|
|
22
|
+
const calendarRoute = path.join(rootRoute, options.calendarRoot);
|
|
23
|
+
const principalRoute = path.join(rootRoute, options.principalRoot, '/');
|
|
24
|
+
|
|
25
|
+
const rootRegexp = pathToRegexp(path.join(rootRoute, '/:params*'));
|
|
26
|
+
const calendarRegex = { keys: [] };
|
|
27
|
+
calendarRegex.regexp = pathToRegexp(
|
|
28
|
+
path.join(calendarRoute, '/:principalId/:calendarId?/:eventId*'),
|
|
29
|
+
calendarRegex.keys
|
|
30
|
+
);
|
|
31
|
+
const principalRegex = { keys: [] };
|
|
32
|
+
principalRegex.regexp = pathToRegexp(
|
|
33
|
+
path.join(principalRoute, '/:principalId?'),
|
|
34
|
+
principalRegex.keys
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const calendarRoutes = cal({
|
|
38
|
+
logEnabled: options.logEnabled,
|
|
39
|
+
logLevel: options.logLevel,
|
|
40
|
+
data: options.data
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const principalRoutes = pri({
|
|
44
|
+
logEnabled: options.logEnabled,
|
|
45
|
+
logLevel: options.logLevel,
|
|
46
|
+
data: options.data
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const fillParameters = function (ctx) {
|
|
50
|
+
ctx.state.params = {};
|
|
51
|
+
|
|
52
|
+
let regex;
|
|
53
|
+
if (calendarRegex.regexp.test(ctx.url)) {
|
|
54
|
+
regex = calendarRegex;
|
|
55
|
+
} else if (principalRegex.regexp.test(ctx.url)) {
|
|
56
|
+
regex = principalRegex;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!regex) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const captures = ctx.url.match(regex.regexp);
|
|
64
|
+
for (let i = 0; i < regex.keys.length; i++) {
|
|
65
|
+
let captured = captures[i + 1];
|
|
66
|
+
if (typeof captured === 'string') {
|
|
67
|
+
captured = decodeURIComponent(captured);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
ctx.state.params[regex.keys[i].name] = captured;
|
|
71
|
+
if (typeof captured === 'string' && captured.endsWith('.ics')) {
|
|
72
|
+
ctx.state.params[regex.keys[i].name] = captured.slice(0, -4);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const auth = async function (ctx) {
|
|
78
|
+
const creds = basicAuth(ctx);
|
|
79
|
+
if (!creds) {
|
|
80
|
+
ctx.status = 401;
|
|
81
|
+
ctx.response.set(
|
|
82
|
+
'WWW-Authenticate',
|
|
83
|
+
`Basic realm="${options.authRealm}"`
|
|
84
|
+
);
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
ctx.state.user = await options.authenticate({
|
|
89
|
+
username: creds.name,
|
|
90
|
+
password: creds.pass,
|
|
91
|
+
principalId: ctx.state.params.principalId
|
|
92
|
+
});
|
|
93
|
+
if (!ctx.state.user) {
|
|
94
|
+
ctx.status = 401;
|
|
95
|
+
ctx.response.set(
|
|
96
|
+
'WWW-Authenticate',
|
|
97
|
+
`Basic realm="${options.authRealm}"`
|
|
98
|
+
);
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!ctx.state.params.principalId) {
|
|
103
|
+
ctx.state.params.principalId = ctx.state.user.principalId;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return true;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const fillRoutes = function (ctx) {
|
|
110
|
+
ctx.state.principalRootUrl = principalRoute;
|
|
111
|
+
if (ctx.state.params.principalId) {
|
|
112
|
+
ctx.state.calendarHomeUrl = path.join(
|
|
113
|
+
calendarRoute,
|
|
114
|
+
ctx.state.params.principalId,
|
|
115
|
+
'/'
|
|
116
|
+
);
|
|
117
|
+
ctx.state.principalUrl = path.join(
|
|
118
|
+
principalRoute,
|
|
119
|
+
ctx.state.params.principalId,
|
|
120
|
+
'/'
|
|
121
|
+
);
|
|
122
|
+
if (ctx.state.params.calendarId) {
|
|
123
|
+
ctx.state.calendarUrl = path.join(
|
|
124
|
+
calendarRoute,
|
|
125
|
+
ctx.state.params.principalId,
|
|
126
|
+
ctx.state.params.calendarId,
|
|
127
|
+
'/'
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
return async function (ctx, next) {
|
|
134
|
+
if (
|
|
135
|
+
ctx.url.toLowerCase() === '/.well-known/caldav' &&
|
|
136
|
+
!options.disableWellKnown
|
|
137
|
+
) {
|
|
138
|
+
// Return ctx.redirect(rootRoute);
|
|
139
|
+
ctx.status = 404;
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!rootRegexp.test(ctx.url)) {
|
|
144
|
+
await next();
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
ctx.state.caldav = true;
|
|
149
|
+
fillParameters(ctx);
|
|
150
|
+
const authed = await auth(ctx);
|
|
151
|
+
if (!authed) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
fillRoutes(ctx);
|
|
156
|
+
|
|
157
|
+
await parseBody(ctx);
|
|
158
|
+
log.verbose('REQUEST BODY', ctx?.request?.body || '<empty>');
|
|
159
|
+
|
|
160
|
+
if (calendarRegex.regexp.test(ctx.url)) {
|
|
161
|
+
await calendarRoutes(ctx);
|
|
162
|
+
} else if (principalRegex.regexp.test(ctx.url)) {
|
|
163
|
+
await principalRoutes(ctx);
|
|
164
|
+
} else {
|
|
165
|
+
ctx.redirect(principalRoute);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
log.verbose('RESPONSE BODY', ctx.body || '<empty>');
|
|
170
|
+
};
|
|
171
|
+
};
|
package/package.json
CHANGED
|
@@ -1,53 +1,71 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "caldav-adapter",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
},
|
|
11
|
-
"author": "Sanders DeNardi <sedenardi@gmail.com> (http://www.sandersdenardi.com/)",
|
|
12
|
-
"homepage": "https://github.com/sedenardi/node-caldav-adapter",
|
|
13
|
-
"license": "MIT",
|
|
14
|
-
"engines": {
|
|
15
|
-
"node": ">= 8.0.0"
|
|
16
|
-
},
|
|
17
|
-
"scripts": {
|
|
18
|
-
"build": "tsc",
|
|
19
|
-
"eslint": "eslint --ext .ts src/**",
|
|
20
|
-
"test": "npm run eslint && npm run build",
|
|
21
|
-
"watch": "nodemon -e js --ignore node_modules/ --inspect example/server.js"
|
|
22
|
-
},
|
|
23
|
-
"devDependencies": {
|
|
24
|
-
"@types/ical": "^0.6.1",
|
|
25
|
-
"@types/koa": "^2.13.12",
|
|
26
|
-
"@types/lodash": "^4.14.202",
|
|
27
|
-
"@types/node": "^20.10.6",
|
|
28
|
-
"@types/xmldom": "^0.1.34",
|
|
29
|
-
"@typescript-eslint/eslint-plugin": "^6.16.0",
|
|
30
|
-
"@typescript-eslint/parser": "^6.16.0",
|
|
31
|
-
"eslint": "^8.56.0",
|
|
32
|
-
"koa": "^2.15.0",
|
|
33
|
-
"koa-compress": "^5.1.1",
|
|
34
|
-
"koa-morgan": "^1.0.1",
|
|
35
|
-
"nodemon": "^3.0.2",
|
|
36
|
-
"typescript": "^5.3.3"
|
|
37
|
-
},
|
|
3
|
+
"description": "CalDAV server for Node.js and Koa. Modernized and maintained for Forward Email.",
|
|
4
|
+
"version": "5.0.0",
|
|
5
|
+
"author": "Sanders DeNardi and Forward Email LLC",
|
|
6
|
+
"contributors": [
|
|
7
|
+
"Sanders DeNardi <sedenardi@gmail.com> (http://www.sandersdenardi.com/)",
|
|
8
|
+
"Forward Email (https://forwardemail.net)"
|
|
9
|
+
],
|
|
38
10
|
"dependencies": {
|
|
11
|
+
"@xmldom/xmldom": "^0.8.10",
|
|
39
12
|
"basic-auth": "^2.0.1",
|
|
40
|
-
"ical": "^0.8.0",
|
|
41
|
-
"ical-generator": "^1.10.0",
|
|
42
13
|
"lodash": "^4.17.21",
|
|
43
14
|
"moment": "^2.30.1",
|
|
44
|
-
"
|
|
15
|
+
"node-ical": "^0.17.1",
|
|
45
16
|
"path-to-regexp": "^6.2.1",
|
|
46
17
|
"raw-body": "^2.5.2",
|
|
47
|
-
"rrule": "^2.8.1",
|
|
48
18
|
"winston": "^3.11.0",
|
|
49
19
|
"xmlbuilder2": "^3.1.1",
|
|
50
|
-
"xmldom": "^0.6.0",
|
|
51
20
|
"xpath": "0.0.34"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@commitlint/cli": "^18.4.3",
|
|
24
|
+
"@commitlint/config-conventional": "^18.4.3",
|
|
25
|
+
"ava": "^5.3.1",
|
|
26
|
+
"cross-env": "^7.0.3",
|
|
27
|
+
"eslint": "^8.55.0",
|
|
28
|
+
"eslint-config-xo-lass": "^2.0.1",
|
|
29
|
+
"fixpack": "^4.0.0",
|
|
30
|
+
"husky": "^8.0.3",
|
|
31
|
+
"koa": "^2.15.0",
|
|
32
|
+
"koa-compress": "^5.1.1",
|
|
33
|
+
"koa-morgan": "^1.0.1",
|
|
34
|
+
"lint-staged": "^15.2.0",
|
|
35
|
+
"nodemon": "^3.0.3",
|
|
36
|
+
"nyc": "^15.1.0",
|
|
37
|
+
"remark-cli": "11",
|
|
38
|
+
"remark-preset-github": "^4.0.4",
|
|
39
|
+
"xo": "^0.56.0"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18"
|
|
43
|
+
},
|
|
44
|
+
"homepage": "https://github.com/forwardemail/caldav-adapter",
|
|
45
|
+
"keywords": [
|
|
46
|
+
"caldav",
|
|
47
|
+
"calendar",
|
|
48
|
+
"card",
|
|
49
|
+
"carddav",
|
|
50
|
+
"dav",
|
|
51
|
+
"express",
|
|
52
|
+
"fastify",
|
|
53
|
+
"ical",
|
|
54
|
+
"koa",
|
|
55
|
+
"web",
|
|
56
|
+
"webdav"
|
|
57
|
+
],
|
|
58
|
+
"license": "MIT",
|
|
59
|
+
"main": "index.js",
|
|
60
|
+
"repository": {
|
|
61
|
+
"type": "git",
|
|
62
|
+
"url": "https://github.com/forwardemail/caldav-adapter.git"
|
|
63
|
+
},
|
|
64
|
+
"scripts": {
|
|
65
|
+
"lint": "xo --fix && remark . -qfo && fixpack",
|
|
66
|
+
"prepare": "husky install",
|
|
67
|
+
"pretest": "npm run lint",
|
|
68
|
+
"test": "npm run test-coverage",
|
|
69
|
+
"test-coverage": "cross-env NODE_ENV=test nyc ava"
|
|
52
70
|
}
|
|
53
71
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const _ = require('lodash');
|
|
2
|
+
const xml = require('../../../common/xml');
|
|
3
|
+
const { response, status } = require('../../../common/x-build');
|
|
4
|
+
const winston = require('../../../common/winston');
|
|
5
|
+
|
|
6
|
+
module.exports = function (options) {
|
|
7
|
+
const log = winston({
|
|
8
|
+
...options,
|
|
9
|
+
label: 'calendar/report/calendar-multiget'
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
return async function (ctx, calendar) {
|
|
13
|
+
const hrefs = xml.get('/CAL:calendar-multiget/D:href', ctx.request.xml);
|
|
14
|
+
const eventActions = _.map(hrefs, async (node) => {
|
|
15
|
+
const href = node.textContent;
|
|
16
|
+
if (!href) {
|
|
17
|
+
return response(href, status[404]);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const hrefParts = href.split('/');
|
|
21
|
+
const eventId = hrefParts.at(-1).slice(0, -4);
|
|
22
|
+
const event = await options.data.getEvent({
|
|
23
|
+
eventId,
|
|
24
|
+
principalId: ctx.state.params.principalId,
|
|
25
|
+
calendarId: ctx.state.params.calendarId,
|
|
26
|
+
user: ctx.state.user,
|
|
27
|
+
fullData: true
|
|
28
|
+
});
|
|
29
|
+
log.debug(`event ${event ? 'found' : 'missing'}: ${eventId}`);
|
|
30
|
+
if (!event) {
|
|
31
|
+
return response(href, status[404]);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const ics = await options.data.buildICS(event, calendar);
|
|
35
|
+
return response(href, status[200], [
|
|
36
|
+
{
|
|
37
|
+
'D:getetag': options.data.getETag(event)
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
'CAL:calendar-data': ics
|
|
41
|
+
}
|
|
42
|
+
]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const responses = await Promise.all(eventActions);
|
|
46
|
+
return { responses };
|
|
47
|
+
};
|
|
48
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const _ = require('lodash');
|
|
2
|
+
const moment = require('moment');
|
|
3
|
+
const xml = require('../../../common/xml');
|
|
4
|
+
const calEventResponse = require('./event-response');
|
|
5
|
+
|
|
6
|
+
module.exports = function (options) {
|
|
7
|
+
// const log = winston({ ...opts, label: 'calendar/report/calendar-query' });
|
|
8
|
+
const eventResponse = calEventResponse(options);
|
|
9
|
+
return async function (ctx, calendar) {
|
|
10
|
+
/* https://tools.ietf.org/html/rfc4791#section-9.9 */
|
|
11
|
+
const filters = xml.get(
|
|
12
|
+
"/CAL:calendar-query/CAL:filter/CAL:comp-filter[@name='VCALENDAR']/CAL:comp-filter[@name='VEVENT']/CAL:time-range",
|
|
13
|
+
ctx.request.xml
|
|
14
|
+
);
|
|
15
|
+
const { children } = xml.getWithChildren(
|
|
16
|
+
'/CAL:calendar-query/D:prop',
|
|
17
|
+
ctx.request.xml
|
|
18
|
+
);
|
|
19
|
+
const fullData = _.some(children, (child) => {
|
|
20
|
+
return child.localName === 'calendar-data';
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
if (!filters?.[0]) {
|
|
24
|
+
const events = await options.data.getEventsForCalendar({
|
|
25
|
+
principalId: ctx.state.params.principalId,
|
|
26
|
+
calendarId: options.data.getCalendarId(calendar),
|
|
27
|
+
user: ctx.state.user,
|
|
28
|
+
fullData
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return eventResponse(ctx, events, calendar, children);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
//
|
|
35
|
+
// TODO: support rest of calendar-query
|
|
36
|
+
// <https://datatracker.ietf.org/doc/html/rfc4791#section-7.8>
|
|
37
|
+
//
|
|
38
|
+
// TODO: support multiple filters and missing filters:
|
|
39
|
+
//
|
|
40
|
+
// <https://datatracker.ietf.org/doc/html/rfc4791#section-9.7>
|
|
41
|
+
// - [ ] 9.7.1. CALDAV:comp-filter XML Element . . . . . . . . . . . . 85
|
|
42
|
+
// - [ ] 9.7.2. CALDAV:prop-filter XML Element . . . . . . . . . . . . 86
|
|
43
|
+
// - [ ] 9.7.3. CALDAV:param-filter XML Element . . . . . . . . . . . 87
|
|
44
|
+
// - [ ] 9.7.4. CALDAV:is-not-defined XML Element . . . . . . . . . . 88
|
|
45
|
+
// - [ ] 9.7.5. CALDAV:text-match XML Element . . . . . . . . . . . . 88
|
|
46
|
+
//
|
|
47
|
+
// TODO: what else (?)
|
|
48
|
+
//
|
|
49
|
+
|
|
50
|
+
const filter = filters[0];
|
|
51
|
+
const startAttr = _.find(filter.attributes, { localName: 'start' });
|
|
52
|
+
const endAttr = _.find(filter.attributes, { localName: 'end' });
|
|
53
|
+
|
|
54
|
+
//
|
|
55
|
+
// rudimentary validation
|
|
56
|
+
//
|
|
57
|
+
let start = null;
|
|
58
|
+
let end = null;
|
|
59
|
+
|
|
60
|
+
if (
|
|
61
|
+
startAttr &&
|
|
62
|
+
startAttr.nodeValue &&
|
|
63
|
+
moment(startAttr.nodeValue).isValid()
|
|
64
|
+
)
|
|
65
|
+
start = moment(startAttr.nodeValue).toDate();
|
|
66
|
+
|
|
67
|
+
if (endAttr && endAttr.nodeValue && moment(endAttr.nodeValue).isValid())
|
|
68
|
+
end = moment(endAttr.nodeValue).toDate();
|
|
69
|
+
|
|
70
|
+
const events = await options.data.getEventsByDate({
|
|
71
|
+
principalId: ctx.state.params.principalId,
|
|
72
|
+
calendarId: options.data.getCalendarId(calendar),
|
|
73
|
+
start,
|
|
74
|
+
end,
|
|
75
|
+
user: ctx.state.user,
|
|
76
|
+
fullData
|
|
77
|
+
});
|
|
78
|
+
return eventResponse(ctx, events, calendar, children);
|
|
79
|
+
};
|
|
80
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const { notFound } = require('../../../common/x-build');
|
|
2
|
+
const { setMissingMethod } = require('../../../common/response');
|
|
3
|
+
const winston = require('../../../common/winston');
|
|
4
|
+
|
|
5
|
+
/* https://tools.ietf.org/html/rfc2518#section-8.6 */
|
|
6
|
+
module.exports = function (options) {
|
|
7
|
+
const log = winston({ ...options, label: 'calendar/delete' });
|
|
8
|
+
const exec = async function (ctx, calendar) {
|
|
9
|
+
if (calendar.readonly) {
|
|
10
|
+
setMissingMethod(ctx);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (!ctx.state.params.eventId) {
|
|
15
|
+
log.warn('eventId param not present');
|
|
16
|
+
ctx.body = notFound(ctx.url); // Make more meaningful
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const existing = await options.data.getEvent({
|
|
21
|
+
eventId: ctx.state.params.eventId,
|
|
22
|
+
principalId: ctx.state.params.principalId,
|
|
23
|
+
calendarId: ctx.state.params.calendarId,
|
|
24
|
+
user: ctx.state.user,
|
|
25
|
+
fullData: false
|
|
26
|
+
});
|
|
27
|
+
log.debug(`existing event${existing ? '' : ' not'} found`);
|
|
28
|
+
|
|
29
|
+
await options.data.deleteEvent({
|
|
30
|
+
eventId: ctx.state.params.eventId,
|
|
31
|
+
principalId: ctx.state.params.principalId,
|
|
32
|
+
calendarId: ctx.state.params.calendarId,
|
|
33
|
+
user: ctx.state.user
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
exec
|
|
39
|
+
};
|
|
40
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const path = require('node:path');
|
|
2
|
+
const _ = require('lodash');
|
|
3
|
+
const {
|
|
4
|
+
response,
|
|
5
|
+
status,
|
|
6
|
+
missingPropstats
|
|
7
|
+
} = require('../../../common/x-build');
|
|
8
|
+
const commonTags = require('../../../common/tags');
|
|
9
|
+
|
|
10
|
+
module.exports = function (options) {
|
|
11
|
+
const tags = commonTags(options);
|
|
12
|
+
|
|
13
|
+
return async function (ctx, events, calendar, children) {
|
|
14
|
+
const eventActions = _.map(events, async (event) => {
|
|
15
|
+
const misses = [];
|
|
16
|
+
const propActions = _.map(children, async (child) => {
|
|
17
|
+
return tags.getResponse({
|
|
18
|
+
resource: 'event',
|
|
19
|
+
child,
|
|
20
|
+
ctx,
|
|
21
|
+
calendar,
|
|
22
|
+
event
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
const pRes = await Promise.all(propActions);
|
|
26
|
+
const url = path.join(ctx.url, `${event.eventId}.ics`);
|
|
27
|
+
const resp = response(url, status[200], _.compact(pRes));
|
|
28
|
+
if (misses.length > 0) {
|
|
29
|
+
resp['D:propstat'].push(missingPropstats(misses));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return resp;
|
|
33
|
+
});
|
|
34
|
+
const responses = await Promise.all(eventActions);
|
|
35
|
+
return { responses };
|
|
36
|
+
};
|
|
37
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const { response, status } = require('../../../common/x-build');
|
|
2
|
+
const winston = require('../../../common/winston');
|
|
3
|
+
|
|
4
|
+
module.exports = function (options) {
|
|
5
|
+
const log = winston({ ...options, label: 'calendar/report/expand-property' });
|
|
6
|
+
return async function (ctx) {
|
|
7
|
+
// ,calendar
|
|
8
|
+
log.debug('returning blank 200 response');
|
|
9
|
+
return { responses: [response(ctx.url, status[200])] };
|
|
10
|
+
};
|
|
11
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const { setMissingMethod } = require('../../../common/response');
|
|
2
|
+
const winston = require('../../../common/winston');
|
|
3
|
+
|
|
4
|
+
module.exports = function (options) {
|
|
5
|
+
const log = winston({ ...options, label: 'calendar/get' });
|
|
6
|
+
|
|
7
|
+
const exec = async function (ctx, calendar) {
|
|
8
|
+
const event = await options.data.getEvent({
|
|
9
|
+
eventId: ctx.state.params.eventId,
|
|
10
|
+
principalId: ctx.state.params.principalId,
|
|
11
|
+
calendarId: ctx.state.params.calendarId,
|
|
12
|
+
user: ctx.state.user,
|
|
13
|
+
fullData: true
|
|
14
|
+
});
|
|
15
|
+
if (!event) {
|
|
16
|
+
log.debug(`event ${ctx.state.params.eventId} not found`);
|
|
17
|
+
setMissingMethod(ctx);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return options.data.buildICS(event, calendar);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
exec
|
|
26
|
+
};
|
|
27
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const path = require('node:path');
|
|
2
|
+
const _ = require('lodash');
|
|
3
|
+
const xml = require('../../../common/xml');
|
|
4
|
+
const {
|
|
5
|
+
build,
|
|
6
|
+
multistatus,
|
|
7
|
+
response,
|
|
8
|
+
status
|
|
9
|
+
} = require('../../../common/x-build');
|
|
10
|
+
const commonTags = require('../../../common/tags');
|
|
11
|
+
const calEventResponse = require('./event-response');
|
|
12
|
+
|
|
13
|
+
module.exports = function (options) {
|
|
14
|
+
const tags = commonTags(options);
|
|
15
|
+
const eventResponse = calEventResponse(options);
|
|
16
|
+
|
|
17
|
+
const calendarResponse = async function (ctx, calendar) {
|
|
18
|
+
const { children } = xml.getWithChildren(
|
|
19
|
+
'/D:propfind/D:prop',
|
|
20
|
+
ctx.request.xml
|
|
21
|
+
);
|
|
22
|
+
const actions = _.map(children, async (child) => {
|
|
23
|
+
return tags.getResponse({
|
|
24
|
+
resource: 'calendar',
|
|
25
|
+
child,
|
|
26
|
+
ctx,
|
|
27
|
+
calendar
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
const res = await Promise.all(actions);
|
|
31
|
+
|
|
32
|
+
const calendarUrl = path.join(
|
|
33
|
+
ctx.state.calendarHomeUrl,
|
|
34
|
+
options.data.getCalendarId(calendar),
|
|
35
|
+
'/'
|
|
36
|
+
);
|
|
37
|
+
const props = _.compact(res);
|
|
38
|
+
return response(
|
|
39
|
+
calendarUrl,
|
|
40
|
+
props.length > 0 ? status[200] : status[404],
|
|
41
|
+
props
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const exec = async function (ctx, calendar) {
|
|
46
|
+
const resp = await calendarResponse(ctx, calendar);
|
|
47
|
+
const resps = [resp];
|
|
48
|
+
|
|
49
|
+
const { children } = xml.getWithChildren(
|
|
50
|
+
'/D:propfind/D:prop',
|
|
51
|
+
ctx.request.xml
|
|
52
|
+
);
|
|
53
|
+
const fullData = _.some(children, (child) => {
|
|
54
|
+
return child.localName === 'calendar-data';
|
|
55
|
+
});
|
|
56
|
+
const events = await options.data.getEventsForCalendar({
|
|
57
|
+
principalId: ctx.state.params.principalId,
|
|
58
|
+
calendarId: options.data.getCalendarId(calendar),
|
|
59
|
+
user: ctx.state.user,
|
|
60
|
+
fullData
|
|
61
|
+
});
|
|
62
|
+
const { responses } = await eventResponse(ctx, events, calendar, children);
|
|
63
|
+
resps.push(...responses);
|
|
64
|
+
|
|
65
|
+
const ms = multistatus(resps);
|
|
66
|
+
return build(ms);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
exec,
|
|
71
|
+
calendarResponse
|
|
72
|
+
};
|
|
73
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const _ = require('lodash');
|
|
2
|
+
const xml = require('../../../common/xml');
|
|
3
|
+
const { build, multistatus } = require('../../../common/x-build');
|
|
4
|
+
const commonTags = require('../../../common/tags');
|
|
5
|
+
|
|
6
|
+
module.exports = function (options) {
|
|
7
|
+
const tags = commonTags(options);
|
|
8
|
+
|
|
9
|
+
const exec = async function (ctx, calendar) {
|
|
10
|
+
const { children } = xml.getWithChildren(
|
|
11
|
+
'/D:propertyupdate/D:set/D:prop',
|
|
12
|
+
ctx.request.xml
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
const actions = _.map(children, async (child) => {
|
|
16
|
+
return tags.getResponse({
|
|
17
|
+
resource: 'calendarProppatch',
|
|
18
|
+
child,
|
|
19
|
+
ctx,
|
|
20
|
+
calendar
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
const res = await Promise.all(actions);
|
|
24
|
+
|
|
25
|
+
const ms = multistatus(_.compact(res));
|
|
26
|
+
return build(ms);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
exec
|
|
31
|
+
};
|
|
32
|
+
};
|