caldav-adapter 4.3.1 → 5.0.1

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.
Files changed (94) hide show
  1. package/.commitlintrc.js +3 -0
  2. package/.editorconfig +9 -0
  3. package/.gitattributes +1 -0
  4. package/.github/workflows/ci.yml +24 -0
  5. package/.husky/commit-msg +4 -0
  6. package/.husky/pre-commit +4 -0
  7. package/.lintstagedrc.js +5 -0
  8. package/.prettierrc.js +5 -0
  9. package/.remarkignore +1 -0
  10. package/.remarkrc.js +3 -0
  11. package/.xo-config.js +8 -0
  12. package/LICENSE +21 -0
  13. package/README.md +26 -240
  14. package/common/parse-body.js +16 -0
  15. package/common/response.js +51 -0
  16. package/common/tags.js +412 -0
  17. package/common/winston.js +32 -0
  18. package/common/x-build.js +109 -0
  19. package/common/xml.js +36 -0
  20. package/index.js +171 -0
  21. package/package.json +58 -40
  22. package/routes/calendar/calendar/calendar-multiget.js +48 -0
  23. package/routes/calendar/calendar/calendar-query.js +80 -0
  24. package/routes/calendar/calendar/delete.js +40 -0
  25. package/routes/calendar/calendar/event-response.js +37 -0
  26. package/routes/calendar/calendar/expand-property.js +11 -0
  27. package/routes/calendar/calendar/get.js +27 -0
  28. package/routes/calendar/calendar/propfind.js +73 -0
  29. package/routes/calendar/calendar/proppatch.js +32 -0
  30. package/routes/calendar/calendar/put.js +76 -0
  31. package/routes/calendar/calendar/report.js +36 -0
  32. package/routes/calendar/calendar/sync-collection.js +36 -0
  33. package/routes/calendar/calendar.js +80 -0
  34. package/routes/calendar/user/propfind.js +60 -0
  35. package/routes/calendar/user/proppatch.js +31 -0
  36. package/routes/principal/mkcalendar.js +69 -0
  37. package/routes/principal/principal.js +33 -0
  38. package/routes/principal/propfind.js +32 -0
  39. package/routes/principal/report.js +27 -0
  40. package/test/test.js +8 -0
  41. package/lib/common/date.d.ts +0 -2
  42. package/lib/common/date.js +0 -11
  43. package/lib/common/eventBuild.d.ts +0 -6
  44. package/lib/common/eventBuild.js +0 -170
  45. package/lib/common/parseBody.d.ts +0 -2
  46. package/lib/common/parseBody.js +0 -21
  47. package/lib/common/response.d.ts +0 -6
  48. package/lib/common/response.js +0 -53
  49. package/lib/common/tags.d.ts +0 -263
  50. package/lib/common/tags.js +0 -337
  51. package/lib/common/winston.d.ts +0 -11
  52. package/lib/common/winston.js +0 -26
  53. package/lib/common/xBuild.d.ts +0 -17
  54. package/lib/common/xBuild.js +0 -80
  55. package/lib/common/xml.d.ts +0 -17
  56. package/lib/common/xml.js +0 -30
  57. package/lib/index.d.ts +0 -143
  58. package/lib/index.js +0 -8
  59. package/lib/koa.d.ts +0 -21
  60. package/lib/koa.js +0 -130
  61. package/lib/routes/calendar/calendar/calendar-multiget.d.ts +0 -7
  62. package/lib/routes/calendar/calendar/calendar-multiget.js +0 -68
  63. package/lib/routes/calendar/calendar/calendar-query.d.ts +0 -7
  64. package/lib/routes/calendar/calendar/calendar-query.js +0 -68
  65. package/lib/routes/calendar/calendar/delete.d.ts +0 -5
  66. package/lib/routes/calendar/calendar/delete.js +0 -40
  67. package/lib/routes/calendar/calendar/eventResponse.d.ts +0 -7
  68. package/lib/routes/calendar/calendar/eventResponse.js +0 -36
  69. package/lib/routes/calendar/calendar/expand-property.d.ts +0 -7
  70. package/lib/routes/calendar/calendar/expand-property.js +0 -15
  71. package/lib/routes/calendar/calendar/get.d.ts +0 -5
  72. package/lib/routes/calendar/calendar/get.js +0 -30
  73. package/lib/routes/calendar/calendar/propfind.d.ts +0 -8
  74. package/lib/routes/calendar/calendar/propfind.js +0 -76
  75. package/lib/routes/calendar/calendar/proppatch.d.ts +0 -5
  76. package/lib/routes/calendar/calendar/proppatch.js +0 -53
  77. package/lib/routes/calendar/calendar/put.d.ts +0 -5
  78. package/lib/routes/calendar/calendar/put.js +0 -68
  79. package/lib/routes/calendar/calendar/report.d.ts +0 -5
  80. package/lib/routes/calendar/calendar/report.js +0 -39
  81. package/lib/routes/calendar/calendar/sync-collection.d.ts +0 -10
  82. package/lib/routes/calendar/calendar/sync-collection.js +0 -57
  83. package/lib/routes/calendar/calendar.d.ts +0 -3
  84. package/lib/routes/calendar/calendar.js +0 -78
  85. package/lib/routes/calendar/user/propfind.d.ts +0 -5
  86. package/lib/routes/calendar/user/propfind.js +0 -64
  87. package/lib/routes/calendar/user/proppatch.d.ts +0 -5
  88. package/lib/routes/calendar/user/proppatch.js +0 -52
  89. package/lib/routes/principal/principal.d.ts +0 -3
  90. package/lib/routes/principal/principal.js +0 -32
  91. package/lib/routes/principal/propfind.d.ts +0 -3
  92. package/lib/routes/principal/propfind.js +0 -50
  93. package/lib/routes/principal/report.d.ts +0 -3
  94. 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
- "version": "4.3.1",
4
- "main": "lib/index.js",
5
- "types": "lib/index.d.ts",
6
- "description": "Middleware to handle CalDAV requests to node web server.",
7
- "repository": {
8
- "type": "git",
9
- "url": "https://github.com/sedenardi/node-caldav-adapter.git"
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.1",
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
- "moment-timezone": "^0.5.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
+ };