namespace _zeitbild.service.caldav { /** */ function get_answers( ) : Record< string, (stuff : any) => lib_plankton.webdav.type_data_prop_value > { return { // RFC2518 /** * @see https://datatracker.ietf.org/doc/html/rfc2518#section-13.2 */ "displayname": (stuff) => ({ "kind": "primitive", "data": stuff["name"] }), /** * @see https://datatracker.ietf.org/doc/html/rfc2518#section-13.4 */ /* "getcontentlength": { "kind": "none", "data": null }, */ /** * @see https://datatracker.ietf.org/doc/html/rfc2518#section-13.5 */ "getcontenttype": (stuff) => ({ "kind": "primitive", "data": stuff["content_type"] }), /** * @see https://datatracker.ietf.org/doc/html/rfc2518#section-13.7 */ /* "getlastmodified": { "kind": "none", "data": null }, */ /** * @see https://datatracker.ietf.org/doc/html/rfc2518#section-13.9 */ "resourcetype": (stuff) => ({ "kind": "resourcetype", "data": { "kind": "collection", "type": "calendar", } }), // RFCP3744 /** * @see https://datatracker.ietf.org/doc/html/rfc3744#section-4.2 */ "principal-url": (stuff) => ({ "kind": "href", "data": "/caldav" }), /** * @see https://datatracker.ietf.org/doc/html/rfc3744#section-5.1 */ "owner": (stuff) => ({ "kind": "primitive", "data": "" }), /** * @see https://datatracker.ietf.org/doc/html/rfc3744#section-5.4 */ "current-user-privilege-set": (stuff) => ({ "kind": "privileges", "data": [ "read" ] }), // RFC4791 /** * @see https://datatracker.ietf.org/doc/html/rfc4791#section-5.2.3 */ "supported-calendar-component-set": (stuff) => ({ "kind": "component_set", "data": { "name": "C:supported-calendar-component-set", "items": [ "VCALENDAR" ] } }), /** * @see https://datatracker.ietf.org/doc/html/rfc4791#section-6.2.1 */ "calendar-home-set": (stuff) => ({ "kind": "href", "data": "/caldav/project" }), // RFC4918 /** * @see https://datatracker.ietf.org/doc/html/rfc4918#section-15.6 */ "getetag": (stuff) => ({ "kind": "primitive", "data": "" }), // RFC5397 /** * @see https://datatracker.ietf.org/doc/html/rfc5397#section-3 */ "current-user-principal": (stuff) => ({ "kind": "href", "data": "/caldav" }), // RFC6638 /** * @see https://datatracker.ietf.org/doc/html/rfc6638#section-2.4.1 */ "calendar-user-address-set": (stuff) => ({ "kind": "primitive", "data": "" }), // unknown /* "calendar-color": { "kind": "none", "data": null }, "executable": { "kind": "none", "data": null }, "checked-in": { "kind": "none", "data": null }, "checked-out": { "kind": "none", "data": null }, */ }; } /** * @todo use pod for output * @todo get api paths in props from config * @todo consider to outsorce to plankton */ export function probe( input : (null | lib_plankton.xml.type_node_data), { "force_props": force_props = null, } : { force_props ?: (null | Array); } = { } ) : (null | lib_plankton.xml.type_node_data) { const http_protocol : string = "HTTP/1.1"; let props : (null | Array); if (force_props) { props = force_props; } else { if ( (input !== null) && (input.kind === "complex") && ( (input.data.tag.toLowerCase() === "d:propfind") || (input.data.tag.toLowerCase() === "propfind") ) && (input.data.children.length === 1) && (input.data.children[0].kind === "complex") && ( (input.data.children[0].data.tag.toLowerCase() === "d:prop") || (input.data.children[0].data.tag.toLowerCase() === "prop") ) ) { props = input.data.children[0].data.children.map( node => { switch (node.kind) { case "complex": { return node.data.tag; break; } default: { throw (new Error("unexpected node type for prop")); break; } } } ); props.sort(); } else { props = null; } } if (props === null) { lib_plankton.log.notice( "service.caldav.probe.unexpected_input", { "input": input, } ); return null; } else { const answers : Record< string, (stuff : any) => lib_plankton.webdav.type_data_prop_value > = get_answers(); return { "kind": "root", "data": { "version": "1.0", "encoding": "utf-8", "content": lib_plankton.webdav.data_multistatus_encode_xml( { "responses": [ { "href": "/caldav/project", "body": { /** * @todo maybe propstats needs to be filled with props (.map …) */ "propstats": ( false ? [ { "prop": (props ?? []).map( (prop) => { const prop_parts : Array = prop.toLowerCase().split(":"); const prop_normalized : string = ((prop_parts.length <= 1) ? prop_parts[0] : prop_parts.slice(1).join(":")); if (! (prop_normalized in answers)) { lib_plankton.log.error( "api.caldav_probe.unhandled_prop", prop_normalized ); throw (new Error("unhandled prop: " + prop_normalized)); } else { return { "name": prop, "value": ( answers[prop_normalized]({"name": "projects", "content_type": "text/xml"}) ?? { "kind": "none", "data": null, } ), }; } } ), "status": (http_protocol + " 200 OK"), "description": null, }, ] : props.map( (prop) => { const prop_parts : Array = prop.toLowerCase().split(":"); const prop_normalized : string = ( (prop_parts.length <= 1) ? prop_parts[0] : prop_parts.slice(1).join(":") ); if (! (prop_normalized in answers)) { lib_plankton.log.notice( "api.caldav_probe.unhandled_prop", prop_normalized ); /* throw (new Error("unhandled prop: " + prop_normalized)); */ return { "prop": [ { "name": prop, "value": { "kind": "none", "data": null, } }, ], "status": (http_protocol + " 404 Not Found"), "description": null, }; } else { return { "prop": [ { "name": prop, "value": answers[prop_normalized]({"name": "projects", "content_type": "text/xml"}), }, ], "status": (http_protocol + " 200 OK"), "description": null, }; }; } ) ) }, "description": null, } ], "description": null, } ) } }; } } /** * @todo heed input properly */ export function projects( input : lib_plankton.xml.type_node_data, user_id : type_user_id ) : Promise { const answers : Record< string, (stuff : any) => lib_plankton.webdav.type_data_prop_value > = get_answers(); /** * @todo remove */ lib_plankton.log.info( "service.caldav.input", input ); /** * @todo check data structure */ if ( ! ( (input.kind === "complex") && (input.data.tag === "propfind") && (input.data.children.length === 1) && (input.data.children[0].kind === "complex") && (input.data.children[0].data.tag === "prop") && input.data.children[0].data.children.every( node => (node.kind === "complex") ) ) ) { throw (new Error("wrong input structure")); } else { const props : Array = ( input .data.children [0] .data.children .map( (node) => node.data.tag ) ); /** * @todo remove */ lib_plankton.log.info( "service.caldav.props", props ); return ( _zeitbild.service.calendar.overview(user_id) .then( (data_raw) => Promise.resolve( data_raw .map( (entry) => ({ "id": entry.id, "name": entry.name, "access_level": _zeitbild.value_object.access_level.to_string(entry.access_level), }) ) ) ) .then( (data) => Promise.resolve( { "kind": "root", "data": { "version": "1.0", "encoding": "utf-8", "content": lib_plankton.webdav.data_multistatus_encode_xml( { "responses": data.map( (entry) => ({ "href": lib_plankton.string.coin( "/caldav/project/{{id}}", { "id": entry.id.toFixed(0), } ), "body": { "propstats": ( lib_plankton.call.wrap>(props) .convey>( (props_raw) => props_raw.map( (prop_raw) => { const prop_parts : Array = prop_raw.toLowerCase().split(":"); const prop_normalized : string = ((prop_parts.length <= 1) ? prop_parts[0] : prop_parts.slice(1).join(":")); return ( (prop_normalized in answers) ? { "found": true, "prop": { "name": prop_raw, "value": answers[prop_normalized]({"name": entry.name, "content_type": "text/calendar"}), } } : { "found": false, "prop": { "name": prop_raw, "value": {"kind": "none", "data": null} } } ); } ) ) .convey>>( (props_transformed) => lib_plankton.list.group( props_transformed, (x, y) => (x.found === y.found) ) ) .convey>( (props_grouped) => props_grouped.map( group => ( group[0].found ? { "prop": group.map(member => member.prop), "status": "HTTP/1.1 200 OK", "description": null, } : { "prop": group.map(member => member.prop), "status": "HTTP/1.1 404 Not Found", "description": null, } ) ) ) .cull() ), }, "description": null, }) ), "description": null, } ) } } ) ) ); } } }