diff --git a/source/api/actions/export_caldav.ts b/source/api/actions/export_caldav.ts new file mode 100644 index 0000000..d970b6e --- /dev/null +++ b/source/api/actions/export_caldav.ts @@ -0,0 +1,166 @@ + +namespace _zeitbild.api +{ + + /** + */ + export function register_export_caldav( + rest_subject : lib_plankton.rest.type_rest + ) : void + { + register< + null, + ( + lib_plankton.ical.type_vcalendar + | + string + ) + >( + rest_subject, + lib_plankton.http.enum_method.get, + "/export/caldav", + { + "description": "trägt Veranstaltungen aus verschiedenen Kalendern zusammen im CalDAV-Format", + "query_parameters": () => ([ + { + "name": "from", + "required": false, + "description": "UNIX timestamp", + }, + { + "name": "to", + "required": false, + "description": "UNIX timestamp", + }, + { + "name": "calendar_ids", + "required": false, + "description": "comma separated", + }, + { + "name": "auth", + "required": true, + "description": "", + }, + ]), + "output_schema": () => ({ + "nullable": false, + "type": "string", + }), + "response_body_mimetype": "text/calendar", + "response_body_encode": (output) => Buffer.from( + (typeof(output) === "string") + ? + output + : + lib_plankton.ical.ics_encode(output) + ), + "restriction": restriction_none, + "execution": async (stuff) => { + const user_id : (null | _zeitbild.type_user_id) = await ( + session_from_stuff(stuff) + .then( + (session : {key : string; value : lib_plankton.session.type_session;}) => ( + _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) => x.map(parseInt), + (x : Array) => x.filter(y => (! isNaN(y))) + ] + ) + : + 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( + { + "status_code": 403, + "data": "not authorized", + } + ); + } + else { + return ( + _zeitbild.service.calendar.gather_events( + calendar_ids_wanted, + from, + to, + user_id + ) + .then( + (data) => Promise.resolve( + { + "status_code": 200, + "data": _zeitbild.helpers.ical_vcalendar_from_own_event_list( + data.map(entry => entry.event_object) + ) + } + ) + ) + .catch( + (reason) => Promise.resolve( + { + "status_code": 403, + "data": String(reason), + } + ) + ) + ); + } + } + } + ); + } + +} diff --git a/source/api/functions.ts b/source/api/functions.ts index 28ebbe2..1d24611 100644 --- a/source/api/functions.ts +++ b/source/api/functions.ts @@ -46,6 +46,10 @@ namespace _zeitbild.api _zeitbild.api.register_calendar_event_remove(rest_subject); } } + // export + { + _zeitbild.api.register_export_caldav(rest_subject); + } // misc { _zeitbild.api.register_users(rest_subject); diff --git a/source/conf.ts b/source/conf.ts index 537d357..ebb56d7 100644 --- a/source/conf.ts +++ b/source/conf.ts @@ -272,6 +272,21 @@ namespace _zeitbild.conf "data": { } } + }, + "misc": { + "nullable": false, + "type": "object", + "properties": { + "auth_salt": { + "nullable": false, + "type": "string", + "default": "unsafe_auth_salt" + } + }, + "required": [ + ], + "additionalProperties": false, + "default": {} } }, "required": [ diff --git a/source/helpers.ts b/source/helpers.ts index d5b929d..94e7cb5 100644 --- a/source/helpers.ts +++ b/source/helpers.ts @@ -31,8 +31,8 @@ namespace _zeitbild.helpers ) }; } - - + + /** * @todo timezone */ @@ -58,6 +58,107 @@ namespace _zeitbild.helpers } + /** + * @todo timezone + */ + export function ical_dt_from_own_datetime( + datetime : lib_plankton.pit.type_datetime + ) : lib_plankton.ical.type_dt + { + return { + "tzid": "Europe/Berlin", + "value": { + "date": { + "year": datetime.date.year, + "month": datetime.date.month, + "day": datetime.date.day, + }, + "time": ( + (datetime.time === null) + ? + null + : + { + "utc": true, + "hour": datetime.time.hour, + "minute": datetime.time.minute, + "second": datetime.time.second, + } + ) + }, + }; + } + + + /** + */ + export function ical_vevent_from_own_event( + event_object : _zeitbild.type_event_object, + uid : string + ) : lib_plankton.ical.type_vevent + { + return { + "uid": uid, + "dtstamp": ical_dt_from_own_datetime(event_object.begin).value, + "dtstart": ical_dt_from_own_datetime(event_object.begin), + "dtend": ( + (event_object.end === null) + ? + undefined + : + ical_dt_from_own_datetime(event_object.end) + ), + "location": (event_object.location ?? undefined), + "summary": event_object.name, + "url": (event_object.link ?? undefined), + "description": (event_object.description ?? undefined), + }; + } + + + /** + * @todo assign better uids + */ + export function ical_vcalendar_from_own_event_list( + events : Array<_zeitbild.type_event_object> + ) : lib_plankton.ical.type_vcalendar + { + const pit_now : lib_plankton.pit.type_pit = lib_plankton.pit.now(); + const datetime_now : lib_plankton.pit.type_datetime = lib_plankton.pit.to_datetime(pit_now); + const stamp : string = lib_plankton.string.coin( + "{{year}}{{month}}{{day}}", + { + "year": datetime_now.date.year.toFixed(0).padStart(4, "0"), + "month": datetime_now.date.month.toFixed(0).padStart(2, "0"), + "day": datetime_now.date.day.toFixed(0).padStart(2, "0"), + } + ); + return { + "version": "2.0", + "prodid": "", + "vevents": ( + events + .map( + (entry, index) => ical_vevent_from_own_event( + entry, + lib_plankton.string.coin( + "zeitbild_{{stamp}}_{{index}}", + { + "stamp": stamp, + "index": index.toFixed(0) + } + ) + ), + ) + ), + "method": "PUBLISH", + "vtimezone": { + "tzid": "Europe/Berlin", + }, + }; + } + + /** */ var _template_cache : Record = {}; diff --git a/tools/makefile b/tools/makefile index 1c2da4c..b5ea7a9 100644 --- a/tools/makefile +++ b/tools/makefile @@ -75,6 +75,7 @@ ${dir_temp}/zeitbild-unlinked.js: \ ${dir_source}/api/actions/calendar_event_change.ts \ ${dir_source}/api/actions/calendar_event_remove.ts \ ${dir_source}/api/actions/events.ts \ + ${dir_source}/api/actions/export_caldav.ts \ ${dir_source}/api/functions.ts \ ${dir_source}/main.ts @ ${cmd_log} "compile …"