backend/source/services/caldav.ts
2024-11-28 23:08:24 +01:00

386 lines
8.4 KiB
TypeScript

namespace _zeitbild.service.caldav
{
/**
* @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<string>);
} = {
}
) : (null | lib_plankton.xml.type_node_data)
{
const http_protocol : string = "HTTP/1.1";
let props : (null | Array<string>);
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,
lib_plankton.webdav.type_data_prop_value
> = {
// webdav 1
/**
* @see https://datatracker.ietf.org/doc/html/rfc2518#section-13.2
*/
"displayname": {
"kind": "primitive",
"data": "projects"
},
/**
* @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": {
"kind": "none",
"data": null
},
*/
/**
* @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": {
"kind": "resourcetype",
"data": {
"kind": "collection",
"type": "calendar",
}
},
// webdav 2
/**
* @see https://datatracker.ietf.org/doc/html/rfc3744#section-4.2
*/
"principal-url": {
"kind": "href",
"data": "/caldav"
},
/**
* @see https://datatracker.ietf.org/doc/html/rfc3744#section-5.1
*/
"owner": {
"kind": "primitive",
"data": ""
},
/**
* @see https://datatracker.ietf.org/doc/html/rfc3744#section-5.4
*/
"current-user-privilege-set": {
"kind": "privileges",
"data": [
"read"
]
},
// caldav
/**
* @see https://datatracker.ietf.org/doc/html/rfc4791#section-6.2.1
*/
"calendar-home-set": {
"kind": "href",
"data": "/caldav/project"
},
// WebDAV Current Principal Extension
/**
* @see https://datatracker.ietf.org/doc/html/rfc5397#section-3
*/
"current-user-principal": {
"kind": "href",
"data": "/caldav"
},
// unknown
/*
"calendar-color": {
"kind": "none",
"data": null
},
"executable": {
"kind": "none",
"data": null
},
"checked-in": {
"kind": "none",
"data": null
},
"checked-out": {
"kind": "none",
"data": null
},
"calendar-user-address-set": {
"kind": "none",
"data": null
},
*/
};
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<string> = 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]
??
{
"kind": "none",
"data": null,
}
),
};
}
}
),
"status": (http_protocol + " 200 OK"),
"description": null,
},
]
:
props.map(
(prop) => {
const prop_parts : Array<string> = 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],
},
],
"status": (http_protocol + " 200 OK"),
"description": null,
};
};
}
)
)
},
"description": null,
}
],
"description": null,
}
)
}
};
}
}
/**
*/
export function projects(
user_id : type_user_id
) : Promise<lib_plankton.xml.type_node_data>
{
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": [
{
"prop": [
{
"name": "D:displayname",
"value": {
"kind": "primitive",
"data": entry.name,
},
},
{
"name": "D:resourcetype",
"value": {
"kind": "resourcetype",
"data": {
"kind": "collection",
"type": "calendar",
}
}
},
],
"status": "HTTP/1.1 200 OK",
"description": null,
}
],
},
"description": null,
})
),
"description": null,
}
)
}
}
)
)
);
}
}