[task-192] [int] geht zum Teil

This commit is contained in:
Fenris Wolf 2024-11-28 23:08:24 +01:00
parent c5d52a7df5
commit 8ddba38bd5
15 changed files with 865 additions and 168 deletions

View file

@ -5,7 +5,7 @@
"kind": "stdout", "kind": "stdout",
"data": { "data": {
"threshold": "info", "threshold": "info",
"format": "human_readable" "format": "jsonl_structured"
} }
} }
], ],

View file

@ -11,14 +11,16 @@ namespace _zeitbild.api
register< register<
null, null,
( (
lib_plankton.ical.type_vcalendar null
| |
string string
|
lib_plankton.ical.type_vcalendar
) )
>( >(
rest_subject, rest_subject,
lib_plankton.caldav.enum_method.report, lib_plankton.caldav.enum_method.report,
"/caldav", "/caldav/project/:id",
{ {
"description": "trägt Veranstaltungen aus verschiedenen Kalendern zusammen im ical-Format", "description": "trägt Veranstaltungen aus verschiedenen Kalendern zusammen im ical-Format",
"query_parameters": () => ([ "query_parameters": () => ([
@ -48,89 +50,82 @@ namespace _zeitbild.api
"type": "string", "type": "string",
}), }),
"response_body_mimetype": "text/calendar", "response_body_mimetype": "text/calendar",
"response_body_encode": (output) => Buffer.from( "response_body_encode": (output) => (
(typeof(output) === "string") (output === null)
? ?
output null
: :
lib_plankton.ical.ics_encode(output) Buffer.from(
(typeof(output) === "string")
?
output
:
lib_plankton.ical.ics_encode(output)
)
), ),
"restriction": restriction_none, "restriction": restriction_none,
/**
* @todo use stuff.path_parameters["id"]
*/
"execution": async (stuff) => { "execution": async (stuff) => {
const user_id : (null | _zeitbild.type_user_id) = await ( const user_id : (null | type_user_id) = await _zeitbild.api.web_auth(
session_from_stuff(stuff) stuff.headers["Authorization"]
.then( ??
(session : {key : string; value : lib_plankton.session.type_session;}) => ( stuff.headers["authorization"]
_zeitbild.service.user.identify(session.value.name) ??
.catch(x => Promise.resolve(null))
)
)
.catch(x => Promise.resolve(null))
);
const from : lib_plankton.pit.type_pit = (
("from" in stuff.query_parameters)
?
parseInt(stuff.query_parameters["from"])
:
lib_plankton.pit.shift_week(
lib_plankton.pit.now(),
-2
)
);
const to : lib_plankton.pit.type_pit = (
("to" in stuff.query_parameters)
?
parseInt(stuff.query_parameters["to"])
:
lib_plankton.pit.shift_week(
lib_plankton.pit.now(),
+6
)
);
const calendar_ids_wanted : (null | Array<_zeitbild.type_calendar_id>) = (
(
("calendar_ids" in stuff.query_parameters)
&&
(stuff.query_parameters["calendar_ids"] !== null)
)
?
lib_plankton.call.convey(
stuff.query_parameters["calendar_ids"],
[
(x : string) => x.split(","),
(x : Array<string>) => x.map(parseInt),
(x : Array<int>) => x.filter(y => (! isNaN(y)))
]
)
:
null null
); );
if (user_id === null) {
const auth_hash_shall : string = lib_plankton.sha256.get(
(stuff.query_parameters["calendar_ids"] ?? ""),
_zeitbild.conf.get()["misc"]["auth_salt"]
);
const auth_hash_is : string = stuff.query_parameters["auth"];
/**
* @todo remove
*/
lib_plankton.log.info(
"auth_hashes",
{
"shall": auth_hash_shall,
"is": auth_hash_is,
}
);
if (! (auth_hash_is === auth_hash_shall)) {
return Promise.resolve( return Promise.resolve(
{ {
"status_code": 403, "status_code": 401,
"data": "not authorized", "data": null,
"extra_headers": {
"WWW-Authenticate": "Basic realm=Restricted",
}
} }
); );
} }
else { else {
const from : lib_plankton.pit.type_pit = (
("from" in stuff.query_parameters)
?
parseInt(stuff.query_parameters["from"])
:
lib_plankton.pit.shift_week(
lib_plankton.pit.now(),
-2
)
);
const to : lib_plankton.pit.type_pit = (
("to" in stuff.query_parameters)
?
parseInt(stuff.query_parameters["to"])
:
lib_plankton.pit.shift_week(
lib_plankton.pit.now(),
+6
)
);
const calendar_ids_wanted : (null | Array<_zeitbild.type_calendar_id>) = (
(
("calendar_ids" in stuff.query_parameters)
&&
(stuff.query_parameters["calendar_ids"] !== null)
)
?
lib_plankton.call.convey(
stuff.query_parameters["calendar_ids"],
[
(x : string) => x.split(","),
(x : Array<string>) => x.map(parseInt),
(x : Array<int>) => x.filter(y => (! isNaN(y)))
]
)
:
null
);
return ( return (
_zeitbild.service.calendar.gather_events( _zeitbild.service.calendar.gather_events(
calendar_ids_wanted, calendar_ids_wanted,

View file

@ -9,46 +9,45 @@ namespace _zeitbild.api
) : void ) : void
{ {
register< register<
null, (
string null
|
lib_plankton.xml.type_node_data
),
lib_plankton.xml.type_node_data
>( >(
rest_subject, rest_subject,
lib_plankton.caldav.enum_method.propfind, lib_plankton.caldav.enum_method.propfind,
"/caldav", "/caldav",
{ {
"query_parameters": () => ([ "request_body_mimetype": () => "text/xml",
{ "request_body_decode": () => async (body, header_content_type) => (
"name": "from", (
"required": false, (header_content_type !== null)
"description": "UNIX timestamp", &&
}, (
{ (header_content_type.startsWith("text/xml"))
"name": "to", ||
"required": false, (header_content_type.startsWith("application/xml"))
"description": "UNIX timestamp", )
}, )
{ ?
"name": "calendar_ids", lib_plankton.xml.parse(body.toString())
"required": false, :
"description": "comma separated", Promise.resolve<any>(null)
}, ),
{
"name": "auth",
"required": true,
"description": "",
},
]),
"output_schema": () => ({ "output_schema": () => ({
"nullable": false, "nullable": false,
"type": "string", "type": "string",
}), }),
"response_body_mimetype": "text/xml; charset=utf-8", "response_body_mimetype": "text/xml",
"response_body_encode": output => Buffer.from(output), "response_body_encode": output => Promise.resolve<Buffer>(
Buffer.from(
lib_plankton.xml.get_node_logic(output).compile(0)
)
),
// "restriction": restriction_basic_auth, // "restriction": restriction_basic_auth,
"restriction": restriction_none, "restriction": restriction_none,
/**
* @todo examine body
*/
"execution": async (stuff) => { "execution": async (stuff) => {
const user_id : (null | type_user_id) = await _zeitbild.api.web_auth( const user_id : (null | type_user_id) = await _zeitbild.api.web_auth(
stuff.headers["Authorization"] stuff.headers["Authorization"]
@ -61,7 +60,10 @@ namespace _zeitbild.api
return Promise.resolve( return Promise.resolve(
{ {
"status_code": 401, "status_code": 401,
"data": "", "data": {
"kind": "text",
"data": ""
},
"extra_headers": { "extra_headers": {
"WWW-Authenticate": "Basic realm=Restricted", "WWW-Authenticate": "Basic realm=Restricted",
} }
@ -69,54 +71,35 @@ namespace _zeitbild.api
); );
} }
else { else {
return ( const output : (null | lib_plankton.xml.type_node_data) = _zeitbild.service.caldav.probe(
_zeitbild.service.calendar.overview(user_id) stuff.input
.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({
"status_code": 207,
"data": (
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+
lib_plankton.webdav.data_multistatus_encode(
{
"responses": [
{
"href": "/caldav/events",
"body": {
"propstats": data.map(
(entry) => ({
"prop": [
{"name": "displayname", "value": entry.name},
// {"name": "cs:getctag", "value": "47"}, // TODO correct value
// {"name": "current-user-privilege-set", "value": ""},
],
"status": "HTTP/2.0 200 OK",
"description": entry.access_level,
})
),
},
"description": null,
}
],
"description": null,
}
)
),
})
)
); );
if (output !== null) {
return Promise.resolve(
{
"status_code": 207,
"data": output
}
);
}
else {
return Promise.resolve(
{
"status_code": 501,
"data": {
"kind": "root",
"data": {
"version": "1.0",
"encoding": "utf-8",
"content": {
"kind": "text",
"data": ""
}
}
}
}
);
}
} }
} }
} }

View file

@ -0,0 +1,115 @@
namespace _zeitbild.api
{
/**
*/
export function register_caldav_probe_via_well_known(
rest_subject : lib_plankton.rest_caldav.type_rest
) : void
{
register<
(
null
|
lib_plankton.xml.type_node_data
),
lib_plankton.xml.type_node_data
>(
rest_subject,
lib_plankton.caldav.enum_method.propfind,
"/.well-known/caldav",
{
"request_body_mimetype": () => "text/xml",
"request_body_decode": () => async (body, header_content_type) => (
(
(header_content_type !== null)
&&
(
(header_content_type.startsWith("text/xml"))
||
(header_content_type.startsWith("application/xml"))
)
)
?
lib_plankton.xml.parse(body.toString())
:
Promise.resolve<any>(null)
),
"output_schema": () => ({
"nullable": false,
"type": "string",
}),
"response_body_mimetype": "text/xml",
"response_body_encode": output => Promise.resolve<Buffer>(
Buffer.from(
lib_plankton.xml.get_node_logic(output).compile(0)
)
),
// "restriction": restriction_basic_auth,
"restriction": restriction_none,
"execution": async (stuff) => {
const user_id : (null | type_user_id) = await _zeitbild.api.web_auth(
stuff.headers["Authorization"]
??
stuff.headers["authorization"]
??
null
);
if (user_id === null) {
return Promise.resolve(
{
"status_code": 401,
"data": {
"kind": "text",
"data": ""
},
"extra_headers": {
"WWW-Authenticate": "Basic realm=Restricted",
}
}
);
}
else {
const output : (null | lib_plankton.xml.type_node_data) = _zeitbild.service.caldav.probe(
stuff.input,
{
"force_props": [
"D:displayname",
"D:resourcetype",
]
}
);
if (output !== null) {
return Promise.resolve(
{
"status_code": 207,
"data": output
}
);
}
else {
return Promise.resolve(
{
"status_code": 501,
"data": {
"kind": "root",
"data": {
"version": "1.0",
"encoding": "utf-8",
"content": {
"kind": "text",
"data": ""
}
}
}
}
);
}
}
}
}
);
}
}

View file

@ -0,0 +1,91 @@
namespace _zeitbild.api
{
/**
*/
export function register_caldav_projects(
rest_subject : lib_plankton.rest_caldav.type_rest
) : void
{
register<
(
null
|
lib_plankton.xml.type_node_data
),
lib_plankton.xml.type_node_data
>(
rest_subject,
lib_plankton.caldav.enum_method.propfind,
"/caldav/project",
{
"output_schema": () => ({
"nullable": false,
"type": "string",
}),
"request_body_mimetype": () => "text/xml",
"request_body_decode": () => async (body, header_content_type) => (
(
(header_content_type !== null)
&&
(
(header_content_type.startsWith("text/xml"))
||
(header_content_type.startsWith("application/xml"))
)
)
?
lib_plankton.xml.parse(body.toString())
:
Promise.resolve<any>(null)
),
"response_body_mimetype": "text/xml",
"response_body_encode": output => Promise.resolve<Buffer>(
Buffer.from(
lib_plankton.xml.get_node_logic(output).compile(0)
)
),
// "restriction": restriction_basic_auth,
"restriction": restriction_none,
"execution": async (stuff) => {
const user_id : (null | type_user_id) = await _zeitbild.api.web_auth(
stuff.headers["Authorization"]
??
stuff.headers["authorization"]
??
null
);
if (user_id === null) {
return Promise.resolve(
{
"status_code": 401,
"data": {
"kind": "text",
"data": ""
},
"extra_headers": {
"WWW-Authenticate": "Basic realm=Restricted",
}
}
);
}
else {
return (
_zeitbild.service.caldav.projects(user_id)
.then(
(output) => Promise.resolve(
{
"status_code": 207,
"data": output,
}
)
)
);
}
}
}
);
}
}

View file

@ -0,0 +1,32 @@
namespace _zeitbild.api
{
/**
*/
export function register_caldav_put(
rest_subject : lib_plankton.rest_caldav.type_rest
) : void
{
register<
null,
null
>(
rest_subject,
lib_plankton.caldav.enum_method.put,
"/caldav",
{
"restriction": restriction_none,
"execution": async (stuff) => {
return Promise.resolve(
{
"status_code": 200,
"data": null
}
);
}
}
);
}
}

View file

@ -17,7 +17,7 @@ namespace _zeitbild.api
lib_plankton.http.enum_method.get, lib_plankton.http.enum_method.get,
_zeitbild.conf.get().server.path_base + "/meta/ping", _zeitbild.conf.get().server.path_base + "/meta/ping",
{ {
"description": "sendet ein 'pong' zurück; gedacht um die Erreichbarkeit des Backends zu prüfen", "description": () => "sendet ein 'pong' zurück; gedacht um die Erreichbarkeit des Backends zu prüfen",
"input_schema": () => ({ "input_schema": () => ({
"nullable": true, "nullable": true,
}), }),
@ -25,8 +25,10 @@ namespace _zeitbild.api
"nullable": false, "nullable": false,
"type": "string", "type": "string",
}), }),
"restriction": restriction_none, "response_body_encode": () => (body) => Promise.resolve<string>(body),
"execution": () => { "response_body_mimetype": () => "text/plain",
"restriction": () => restriction_none,
"execution": () => () => {
return Promise.resolve({ return Promise.resolve({
"status_code": 200, "status_code": 200,
"data": "pong", "data": "pong",

View file

@ -17,14 +17,14 @@ namespace _zeitbild.api
lib_plankton.http.enum_method.get, lib_plankton.http.enum_method.get,
_zeitbild.conf.get().server.path_base + "/meta/spec", _zeitbild.conf.get().server.path_base + "/meta/spec",
{ {
"description": "gibt die API-Spezifikation im OpenAPI-Format aus", "description": () => "gibt die API-Spezifikation im OpenAPI-Format aus",
"input_schema": () => ({ "input_schema": () => ({
"nullable": true, "nullable": true,
}), }),
"output_schema": () => ({ "output_schema": () => ({
}), }),
"restriction": restriction_none, "restriction": () => restriction_none,
"execution": () => { "execution": () => () => {
return Promise.resolve({ return Promise.resolve({
"status_code": 200, "status_code": 200,
"data": lib_plankton.rest_caldav.to_oas(rest_subject), "data": lib_plankton.rest_caldav.to_oas(rest_subject),

View file

@ -23,7 +23,7 @@ namespace _zeitbild.api
lib_plankton.http.enum_method.post, lib_plankton.http.enum_method.post,
"/session/begin", "/session/begin",
{ {
"description": "führt die Anmeldung am System aus um geschützte Aktionen nutzen zu können", "description": () => "führt die Anmeldung am System aus um geschützte Aktionen nutzen zu können",
"input_schema": () => ({ "input_schema": () => ({
"type": "object", "type": "object",
"properties": { "properties": {
@ -44,8 +44,8 @@ namespace _zeitbild.api
"type": "string", "type": "string",
"description": "der Sitzungs-Schlüssel, der als Header 'X-Session-Key' gesetzt werden muss um Erlaubnis zur Ausführung geschützter Aktionen zu erhalten", "description": "der Sitzungs-Schlüssel, der als Header 'X-Session-Key' gesetzt werden muss um Erlaubnis zur Ausführung geschützter Aktionen zu erhalten",
}), }),
"restriction": restriction_none, "restriction": () => restriction_none,
"execution": async ({"input": input}) => { "execution": () => async ({"input": input}) => {
if (input === null) { if (input === null) {
return Promise.reject(new Error("impossible")); return Promise.reject(new Error("impossible"));
} }

View file

@ -19,15 +19,15 @@ namespace _zeitbild.api
lib_plankton.http.enum_method.post, lib_plankton.http.enum_method.post,
"/session/prepare", "/session/prepare",
{ {
"description": "gibt die nötigen Werkzeuge für eine Anmeldung aus", "description": () => "gibt die nötigen Werkzeuge für eine Anmeldung aus",
"input_schema": () => ({ "input_schema": () => ({
"nullable": true, "nullable": true,
}), }),
"output_schema": () => ({ "output_schema": () => ({
"nullable": false "nullable": false
}), }),
"restriction": restriction_none, "restriction": () => restriction_none,
"execution": async (stuff) => { "execution": () => async (stuff) => {
const preparation = await _zeitbild.auth.prepare(stuff.input); const preparation = await _zeitbild.auth.prepare(stuff.input);
return Promise.resolve({ return Promise.resolve({
"status_code": 200, "status_code": 200,

View file

@ -141,7 +141,7 @@ namespace _zeitbild.api
http_method : lib_plankton.caldav.enum_method, http_method : lib_plankton.caldav.enum_method,
path : string, path : string,
options : { options : {
active ?: ((version : string) => boolean); active ?: ((version : (null | string)) => boolean);
restriction ?: (null | lib_plankton.rest_caldav.type_restriction<type_input>); restriction ?: (null | lib_plankton.rest_caldav.type_restriction<type_input>);
execution ?: lib_plankton.rest_caldav.type_execution<type_input, type_output>; execution ?: lib_plankton.rest_caldav.type_execution<type_input, type_output>;
title ?: (null | string); title ?: (null | string);
@ -155,10 +155,24 @@ namespace _zeitbild.api
>); >);
input_schema ?: ((version: (null | string)) => lib_plankton.rest_caldav.type_oas_schema); input_schema ?: ((version: (null | string)) => lib_plankton.rest_caldav.type_oas_schema);
output_schema ?: ((version: (null | string)) => lib_plankton.rest_caldav.type_oas_schema); output_schema ?: ((version: (null | string)) => lib_plankton.rest_caldav.type_oas_schema);
request_body_mimetype ?: string; request_body_mimetype ?: (
request_body_decode ?: ((http_request_body : Buffer, http_request_header_content_type : (null | string)) => any); (version : (null | string))
=>
string
);
request_body_decode ?: (
(version : (null | string))
=>
(http_request_body : Buffer, http_request_header_content_type : (null | string))
=>
Promise<any>
);
response_body_mimetype ?: string; response_body_mimetype ?: string;
response_body_encode ?: ((output : any) => Buffer); response_body_encode ?: (
(output : any)
=>
Promise<Buffer>
);
} = {} } = {}
) : void ) : void
{ {
@ -171,7 +185,74 @@ namespace _zeitbild.api
rest_subject, rest_subject,
http_method, http_method,
(_zeitbild.conf.get().server.path_base + path), (_zeitbild.conf.get().server.path_base + path),
options {
"active": options.active,
/**
* @todo heed version
*/
"restriction": (
((options.restriction === undefined) || (options.restriction === null))
?
undefined
:
(version) => (options.restriction as lib_plankton.rest_caldav.type_restriction<type_input>)
),
/**
* @todo heed version
*/
"execution": (
((options.execution === undefined) || (options.execution === null))
?
undefined
:
(version) => (options.execution as lib_plankton.rest_caldav.type_execution<type_input, type_output>)
),
/**
* @todo heed version
*/
"title": (
((options.title === undefined) || (options.title === null))
?
(version) => null
:
(version) => (options.title as string)
),
/**
* @todo heed version
*/
"description": (
((options.description === undefined) || (options.description === null))
?
(version) => null
:
(version) => (options.description as string)
),
"query_parameters": options.query_parameters,
"input_schema": options.input_schema,
"output_schema": options.output_schema,
"request_body_mimetype": options.request_body_mimetype,
"request_body_decode": options.request_body_decode,
/**
* @todo heed version
*/
"response_body_mimetype": (
((options.response_body_mimetype === undefined) || (options.response_body_mimetype === null))
?
undefined
:
(version) => (options.response_body_mimetype as string)
),
/**
* @todo heed version
*/
"response_body_encode": (
((options.response_body_encode === undefined) || (options.response_body_encode === null))
?
undefined
:
(version) => (options.response_body_encode as ((output : any) => Promise<Buffer>))
),
}
); );
} }

View file

@ -15,7 +15,7 @@ namespace _zeitbild.api
"set_access_control_headers": true, "set_access_control_headers": true,
"authentication": { "authentication": {
"kind": "key_header", "kind": "key_header",
"parameters": {"name": "X-Session-Key"} "data": {"name": "X-Session-Key"}
}, },
} }
); );
@ -53,7 +53,10 @@ namespace _zeitbild.api
// caldav // caldav
{ {
_zeitbild.api.register_caldav_sniff(rest_subject); _zeitbild.api.register_caldav_sniff(rest_subject);
_zeitbild.api.register_caldav_put(rest_subject);
_zeitbild.api.register_caldav_probe(rest_subject); _zeitbild.api.register_caldav_probe(rest_subject);
_zeitbild.api.register_caldav_probe_via_well_known(rest_subject);
_zeitbild.api.register_caldav_projects(rest_subject);
_zeitbild.api.register_caldav_get(rest_subject); _zeitbild.api.register_caldav_get(rest_subject);
} }
// misc // misc

View file

@ -0,0 +1,5 @@
namespace _zeitbild.service.caldav
{
}

386
source/services/caldav.ts Normal file
View file

@ -0,0 +1,386 @@
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,
}
)
}
}
)
)
);
}
}

View file

@ -56,6 +56,7 @@ ${dir_temp}/zeitbild-unlinked.js: \
${dir_source}/services/user.ts \ ${dir_source}/services/user.ts \
${dir_source}/services/resource.ts \ ${dir_source}/services/resource.ts \
${dir_source}/services/calendar.ts \ ${dir_source}/services/calendar.ts \
${dir_source}/services/caldav.ts \
${dir_source}/api/base.ts \ ${dir_source}/api/base.ts \
${dir_source}/api/transformations/datetime.ts \ ${dir_source}/api/transformations/datetime.ts \
${dir_source}/api/actions/meta_ping.ts \ ${dir_source}/api/actions/meta_ping.ts \
@ -77,7 +78,10 @@ ${dir_temp}/zeitbild-unlinked.js: \
${dir_source}/api/actions/events.ts \ ${dir_source}/api/actions/events.ts \
${dir_source}/api/actions/export_ical.ts \ ${dir_source}/api/actions/export_ical.ts \
${dir_source}/api/actions/caldav_sniff.ts \ ${dir_source}/api/actions/caldav_sniff.ts \
${dir_source}/api/actions/caldav_put.ts \
${dir_source}/api/actions/caldav_probe.ts \ ${dir_source}/api/actions/caldav_probe.ts \
${dir_source}/api/actions/caldav_probe_via_well_known.ts \
${dir_source}/api/actions/caldav_projects.ts \
${dir_source}/api/actions/caldav_get.ts \ ${dir_source}/api/actions/caldav_get.ts \
${dir_source}/api/functions.ts \ ${dir_source}/api/functions.ts \
${dir_source}/main.ts ${dir_source}/main.ts