From f90567d043532c2c3517ecae32d3d04e882555df Mon Sep 17 00:00:00 2001 From: Fenris Wolf Date: Thu, 25 Sep 2025 17:05:15 +0200 Subject: [PATCH] [task-192] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Tasks - [192](https://vikunja.ramsch.sx/tasks/192) ## Zugehörige MRs - [datamodel](https://forgejo.ramsch.sx/misc/zeitbild-datamodel/pulls/1) - [frontend-dali](https://forgejo.ramsch.sx/misc/zeitbild-frontend-dali/pulls/1) Reviewed-on: https://forgejo.ramsch.sx/misc/zeitbild-backend/pulls/1 Co-authored-by: Fenris Wolf Co-committed-by: Fenris Wolf --- conf/example.json | 20 ---- data/example.json | 9 +- misc/conf-example.json | 58 ++++++++++ source/api/actions/calendar_add.ts | 8 +- source/api/actions/export_ics.ts | 127 ++++++++++++++++++++++ source/api/actions/session_oidc.ts | 1 + source/api/actions/user_dav_conf.ts | 152 +++++++++++++++++++++++++++ source/api/actions/user_dav_token.ts | 53 ++++++++++ source/api/functions.ts | 15 ++- source/conf.ts | 90 +++++++++++++++- source/helpers.ts | 127 +++++++++++++++++++++- source/main.ts | 12 +-- source/repositories/resource.ts | 38 ++++--- source/repositories/user.ts | 15 +++ source/services/calendar.ts | 104 +++++++++--------- source/services/resource.ts | 32 ++---- source/services/user.ts | 21 ++++ source/types.ts | 19 +++- tools/makefile | 3 + 19 files changed, 766 insertions(+), 138 deletions(-) delete mode 100644 conf/example.json create mode 100644 misc/conf-example.json create mode 100644 source/api/actions/export_ics.ts create mode 100644 source/api/actions/user_dav_conf.ts create mode 100644 source/api/actions/user_dav_token.ts diff --git a/conf/example.json b/conf/example.json deleted file mode 100644 index 5821d0b..0000000 --- a/conf/example.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "version": 1, - "log": [ - { - "kind": "stdout", - "data": { - "threshold": "info" - } - } - ], - "session_management": { - "in_memory": false, - "lifetime": 3600 - }, - "authentication": { - "kind": "internal", - "data": { - } - } -} diff --git a/data/example.json b/data/example.json index 5022519..e564273 100644 --- a/data/example.json +++ b/data/example.json @@ -3,19 +3,22 @@ { "id": 1, "name": "alice", - "email_address": "alice@example.org", + "email_address": "alice@example.org", + "dav_token": null, "password": "alice" }, { "id": 2, "name": "bob", - "email_address": "bob@example.org", + "email_address": "bob@example.org", + "dav_token": "bob_dav", "password": "bob" }, { "id": 3, "name": "charlie", - "email_address": "charlie@example.org", + "email_address": "charlie@example.org", + "dav_token": null, "password": "charlie" } ], diff --git a/misc/conf-example.json b/misc/conf-example.json new file mode 100644 index 0000000..39e8e09 --- /dev/null +++ b/misc/conf-example.json @@ -0,0 +1,58 @@ +{ + "version": 1, + "log": [ + { + "kind": "stdout", + "data": { + "threshold": "info", + "format": "jsonl_structured" + } + } + ], + "session_management": { + "in_memory": false, + "lifetime": 3600 + }, + "authentication": { + "kind": "internal", + "data": { + } + }, + "database": { + "kind": "sqlite", + "data": { + "path": "../zeitbild.sqlite" + } + }, + "caldav": { + "address": "http://localhost:8000/calendars/-/lampukistan", + "username": "lampukistan-{{username}}", + "password": "{{password}}", + "setup_hints": [ + { + "label": "Android", + "link": "https://www.android-user.de/caldavcarddav-kalender-und-adressbuecher-ohne-google-synchronisieren/", + "remark": null + }, + { + "label": "iOS", + "link": "https://all-inkl.com/wichtig/anleitungen/programme/e-mail/caldav-kalenderfunktion/ios-mail_460.html", + "remark": "eigentlich für Server 'all-inkl.com' — Zugangsdaten müssen entsprechend geändert werden" + }, + { + "label": "Thunderbird", + "link": "https://www.uni-bielefeld.de/einrichtungen/bits/services/kuz/e-mail-und-kalender/anleitung/kalender-konfiguration-unter-thunderbird/", + "remark": "eigentlich für Server 'uni-bielefeld.de' — Zugangsdaten müssen entsprechend geändert werden" + }, + { + "label": "Evolution", + "link": "https://help.gnome.org/users/evolution/stable/calendar-caldav.html.de" + }, + { + "label": "MS Outlook", + "link": "https://www.united-domains.de/help/faq-article/wie-synchronisiere-ich-meinen-kalender-caldav-mit-ms-outlook/", + "remark": null + } + ] + } +} diff --git a/source/api/actions/calendar_add.ts b/source/api/actions/calendar_add.ts index f54fc76..21e741a 100644 --- a/source/api/actions/calendar_add.ts +++ b/source/api/actions/calendar_add.ts @@ -29,10 +29,9 @@ namespace _zeitbild.api } | { - kind : "caldav"; + kind : "ics_feed"; data : { url : string; - read_only : boolean; from_fucked_up_wordpress : boolean; }; } @@ -75,12 +74,11 @@ namespace _zeitbild.api }; break; } - case "caldav": { + case "ics_feed": { resource_object = { - "kind": "caldav", + "kind": "ics_feed", "data": { "url": stuff.input.resource.data.url, - "read_only": stuff.input.resource.data.read_only, "from_fucked_up_wordpress": stuff.input.resource.data.from_fucked_up_wordpress, } }; diff --git a/source/api/actions/export_ics.ts b/source/api/actions/export_ics.ts new file mode 100644 index 0000000..0f0d554 --- /dev/null +++ b/source/api/actions/export_ics.ts @@ -0,0 +1,127 @@ + +namespace _zeitbild.api +{ + + /** + */ + export function register_export_ics( + rest_subject : lib_plankton.rest_http.type_rest + ) : void + { + register< + null, + ( + lib_plankton.ical.type_vcalendar + | + string + ) + >( + rest_subject, + lib_plankton.http.enum_method.get, + "/export/ics", + { + "description": "trägt Veranstaltungen aus verschiedenen Kalendern zusammen im ics-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", + }, + ]), + "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_web_auth, + "execution": async (stuff) => { + const user : {id : _zeitbild.type_user_id; object : _zeitbild.type_user_object;} = await _zeitbild.api.user_from_web_auth(stuff); + + 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 + ); + + return ( + _zeitbild.service.calendar.gather_events( + calendar_ids_wanted, + from, + to, + user.id + ) + .then( + (events_extended) => Promise.resolve( + { + "status_code": 200, + "data": _zeitbild.helpers.icalendar_vcalendar_from_own_event_list( + events_extended + ) + } + ) + ) + .catch( + (reason) => Promise.resolve( + { + "status_code": 403, + "data": String(reason), + } + ) + ) + ); + } + } + ); + } + +} diff --git a/source/api/actions/session_oidc.ts b/source/api/actions/session_oidc.ts index a80aa72..7855a06 100644 --- a/source/api/actions/session_oidc.ts +++ b/source/api/actions/session_oidc.ts @@ -74,6 +74,7 @@ namespace _zeitbild.api { "name": data.userinfo.name, "email_address": data.userinfo.email, + "dav_token": null, } ); lib_plankton.log.info( diff --git a/source/api/actions/user_dav_conf.ts b/source/api/actions/user_dav_conf.ts new file mode 100644 index 0000000..b568f8b --- /dev/null +++ b/source/api/actions/user_dav_conf.ts @@ -0,0 +1,152 @@ +namespace _zeitbild.api +{ + + /** + */ + export function register_user_dav_conf( + rest_subject : lib_plankton.rest_http.type_rest + ) : void + { + register< + null, + ( + null + | + { + address : string; + username : string; + password : (null | string); + setup_hints : Array< + { + label : string; + link : string; + remark : (null | string); + } + >; + } + ) + >( + rest_subject, + lib_plankton.http.enum_method.get, + "/user_dav_conf", + { + "description": "gibt die CalDAV-Zugangsdaten eines Nutzers aus", + "output_schema": () => ({ + "nullable": true, + "type": "object", + "properties": { + "address": { + "nullable": false, + "type": "string" + }, + "username": { + "nullable": false, + "type": "string" + }, + "password": { + "nullable": true, + "type": "string" + }, + "setup_hints": { + "nullable": false, + "type": "array", + "items": { + "nullable": false, + "type": "object", + "properties": { + "label": { + "nullable": false, + "type": "string" + }, + "link": { + "nullable": false, + "type": "string" + }, + "remark": { + "nullable": true, + "type": "string", + "default": null, + }, + }, + "required": [ + "label", + "link", + ], + "additionalProperties": false + }, + "default": [] + }, + }, + "required": [ + "address", + "username", + "password", + "setup_hints", + ], + "additionalProperties": false + }), + "restriction": restriction_logged_in, + "execution": async (stuff) => { + let result : ( + null + | + { + address : string; + username : string; + password : (null | string); + setup_hints : Array< + { + label : string; + link : string; + remark : (null | string); + } + >; + } + ) = null; + const raw : (null | any) = _zeitbild.conf.get()["caldav"]; + if (raw === null) + { + result = null; + } + else + { + const session : {key : string; value : lib_plankton.session.type_session;} = await session_from_stuff(stuff); + const user_id : _zeitbild.type_user_id = await _zeitbild.service.user.identify(session.value.name); + const user_object : _zeitbild.type_user_object = await _zeitbild.service.user.get(user_id); + const arguments_ : Record = Object.fromEntries( + [ + {"key": "username", "value": user_object.name}, + {"key": "password", "value": user_object.dav_token}, + ] + .filter( + entry => (entry.value !== null) + ) + .map( + entry => ([entry.key, entry.value as string]) + ) + ); + result = { + "address": lib_plankton.string.coin(raw["address"], arguments_), + "username": lib_plankton.string.coin(raw["username"], arguments_), + "password": ( + (user_object.dav_token === null) + ? + null + : + lib_plankton.string.coin(raw["password"], arguments_) + ), + "setup_hints": raw["setup_hints"], + }; + } + return Promise.resolve( + { + "status_code": 200, + "data": result, + } + ); + } + } + ); + } + +} diff --git a/source/api/actions/user_dav_token.ts b/source/api/actions/user_dav_token.ts new file mode 100644 index 0000000..3293f5e --- /dev/null +++ b/source/api/actions/user_dav_token.ts @@ -0,0 +1,53 @@ +namespace _zeitbild.api +{ + + /** + */ + export function register_user_dav_token( + rest_subject : lib_plankton.rest_http.type_rest + ) : void + { + register< + // string, + null, + null + >( + rest_subject, + lib_plankton.http.enum_method.patch, + "/user_dav_token", + { + "description": "setzt/überschreibt den DAV-Token eines Nutzers", + /* + "input_schema": () => ({ + "nullable": false, + "type": "string" + }), + */ + "input_schema": () => ({ + "nullable": true, + }), + "output_schema": () => ({ + "nullable": true + }), + "restriction": restriction_logged_in, + "execution": async (stuff) => { + const session : {key : string; value : lib_plankton.session.type_session;} = await session_from_stuff(stuff); + const user_id : _zeitbild.type_user_id = await _zeitbild.service.user.identify(session.value.name); + // TODO: outsource to user service? + const user_object : _zeitbild.type_user_object = await _zeitbild.service.user.get(user_id); + // user_object.dav_token = stuff.input; + user_object.dav_token = lib_plankton.random.generate_string({"length": 12}); + await _zeitbild.service.user.change(user_id, user_object); + return Promise.resolve( + { + "status_code": 200, + "data": null, + } + ); + } + } + ); + } + +} + diff --git a/source/api/functions.ts b/source/api/functions.ts index 34d29d4..8ae4442 100644 --- a/source/api/functions.ts +++ b/source/api/functions.ts @@ -31,6 +31,15 @@ namespace _zeitbild.api _zeitbild.api.register_session_end(rest_subject); _zeitbild.api.register_session_oidc(rest_subject); } + // user + { + _zeitbild.api.register_users(rest_subject); + // caldav + { + _zeitbild.api.register_user_dav_conf(rest_subject); + _zeitbild.api.register_user_dav_token(rest_subject); + } + } // calendar { _zeitbild.api.register_calendar_list(rest_subject); @@ -46,13 +55,15 @@ namespace _zeitbild.api _zeitbild.api.register_calendar_event_remove(rest_subject); } } + // export + { + _zeitbild.api.register_export_ics(rest_subject); + } // misc { - _zeitbild.api.register_users(rest_subject); _zeitbild.api.register_events(rest_subject); } - return rest_subject; } diff --git a/source/conf.ts b/source/conf.ts index f984923..d611d1e 100644 --- a/source/conf.ts +++ b/source/conf.ts @@ -282,7 +282,95 @@ namespace _zeitbild.conf "data": { } } - } + }, + "external_resources": { + "nullable": false, + "type": "object", + "properties": { + "lifetime": { + "nullable": false, + "type": "integer", + "default": 14400 + } + }, + "additionalProperties": false, + "required": [ + ], + "default": { + } + }, + "caldav": { + "nullable": true, + "type": "object", + "properties": { + "address": { + "nullable": false, + "type": "string" + }, + "username": { + "nullable": false, + "type": "string" + }, + "password": { + "nullable": false, + "type": "string" + }, + "setup_hints": { + "nullable": false, + "type": "array", + "items": { + "nullable": false, + "type": "object", + "properties": { + "label": { + "nullable": false, + "type": "string" + }, + "link": { + "nullable": false, + "type": "string" + }, + "remark": { + "nullable": true, + "type": "string", + "default": null + }, + }, + "required": [ + "label", + "link", + ], + "additionalProperties": false + }, + "default": [] + }, + }, + "required": [ + "address", + "username", + "password" + ], + "additionalProperties": false, + "default": null + }, + "misc": { + "nullable": false, + "type": "object", + "properties": { + /** + * @todo make mandatory + */ + "auth_salt": { + "nullable": false, + "type": "string", + "default": "unsafe_auth_salt" + } + }, + "required": [ + ], + "additionalProperties": false, + "default": {} + }, }, "required": [ "version" diff --git a/source/helpers.ts b/source/helpers.ts index 97dc5fa..b6cb9e0 100644 --- a/source/helpers.ts +++ b/source/helpers.ts @@ -7,7 +7,7 @@ namespace _zeitbild.helpers /** * @todo timezone */ - function ical_datetime_to_own_datetime( + function icalendar_datetime_to_own_datetime( ical_datetime : lib_plankton.ical.type_datetime ) : lib_plankton.pit.type_datetime { @@ -31,12 +31,12 @@ namespace _zeitbild.helpers ) }; } - - + + /** * @todo timezone */ - export function ical_dt_to_own_datetime( + export function icalendar_dt_to_own_datetime( ical_dt: lib_plankton.ical.type_dt ) : lib_plankton.pit.type_datetime { @@ -58,6 +58,125 @@ namespace _zeitbild.helpers } + /** + * @todo timezone + */ + export function icalendar_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 icalendar_vevent_from_own_event( + event_extended : _zeitbild.type_event_extended, + index : int, + { + "stamp": stamp = "adhoc", + } + : + { + stamp ?: string; + } + = + { + } + ) : lib_plankton.ical.type_vevent + { + return { + "uid": lib_plankton.string.coin( + "zeitbild_{{stamp}}_{{id}}", + { + "stamp": stamp, + // "id": event_extended.event_id.toFixed(0), + "id": index.toFixed(0), + } + ), + "dtstamp": icalendar_dt_from_own_datetime(event_extended.event_object.begin).value, + "dtstart": icalendar_dt_from_own_datetime(event_extended.event_object.begin), + "dtend": ( + (event_extended.event_object.end === null) + ? + undefined + : + icalendar_dt_from_own_datetime(event_extended.event_object.end) + ), + "location": (event_extended.event_object.location ?? undefined), + "summary": event_extended.event_object.name, + "url": (event_extended.event_object.link ?? undefined), + "description": (event_extended.event_object.description ?? undefined), + /** + * @todo transform name + */ + "categories": [event_extended.calendar_name], + }; + } + + + /** + * @todo assign better uids + */ + export function icalendar_vcalendar_from_own_event_list( + events_extended : Array<_zeitbild.type_event_extended> + ) : 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_extended + .map( + (event_extended, index) => icalendar_vevent_from_own_event( + event_extended, + index, + { + "stamp": stamp, + } + ) + ) + ), + "method": "PUBLISH", + "vtimezone": { + "tzid": "Europe/Berlin", + }, + }; + } + + /** */ export async function template_coin( diff --git a/source/main.ts b/source/main.ts index 999e3e6..eda9f53 100644 --- a/source/main.ts +++ b/source/main.ts @@ -8,6 +8,7 @@ type type_data = { id : int; name : string; email_address : string; + dav_token : (null | string); password : string; } >; @@ -36,10 +37,9 @@ type type_data = { } | { - kind : "caldav"; + kind : "ics_feed"; data : { url : string; - read_only : boolean; from_fucked_up_wordpress ?: boolean; }; } @@ -73,6 +73,7 @@ async function data_init( const user_object : _zeitbild.type_user_object = { "name": user_raw.name, "email_address": user_raw.email_address, + "dav_token": user_raw.dav_token, }; const user_id : _zeitbild.type_user_id = await _zeitbild.service.user.add( user_object @@ -108,13 +109,12 @@ async function data_init( ); break; } - case "caldav": + case "ics_feed": { resource_object = { - "kind": "caldav", + "kind": "ics_feed", "data": { "url": calendar_raw.resource.data.url, - "read_only": calendar_raw.resource.data.read_only, "from_fucked_up_wordpress": (calendar_raw.resource.data.from_fucked_up_wordpress ?? false), } }; @@ -384,7 +384,6 @@ async function main( case "api-doc": { lib_plankton.log.set_main_logger([]); const rest_subject : lib_plankton.rest_http.type_rest = _zeitbild.api.make(); - lib_plankton.log.conf_pop(); process.stdout.write( JSON.stringify( lib_plankton.rest_http.to_oas(rest_subject), @@ -480,6 +479,7 @@ async function main( ) .catch( (error) => { + // console.error(error); process.stderr.write(String(error) + "\n"); } ) diff --git a/source/repositories/resource.ts b/source/repositories/resource.ts index a191bfd..a3f90c8 100644 --- a/source/repositories/resource.ts +++ b/source/repositories/resource.ts @@ -42,7 +42,7 @@ namespace _zeitbild.repository.resource /** */ - var _caldav_resource_store : ( + var _ics_feed_resource_store : ( null | lib_plankton.storage.type_store< @@ -126,7 +126,7 @@ namespace _zeitbild.repository.resource /** */ - function get_caldav_resource_store( + function get_ics_feed_resource_store( ) : lib_plankton.storage.type_store< int, Record, @@ -135,11 +135,11 @@ namespace _zeitbild.repository.resource Record > { - if (_caldav_resource_store === null) { - _caldav_resource_store = lib_plankton.storage.sql_table_autokey_store( + if (_ics_feed_resource_store === null) { + _ics_feed_resource_store = lib_plankton.storage.sql_table_autokey_store( { "database_implementation": _zeitbild.database.get_implementation(), - "table_name": "caldav_resources", + "table_name": "ics_feed_resources", "key_name": "id", } ); @@ -147,7 +147,7 @@ namespace _zeitbild.repository.resource else { // do nothing } - return _caldav_resource_store; + return _ics_feed_resource_store; } @@ -358,15 +358,14 @@ namespace _zeitbild.repository.resource } ); } - case "caldav": { - const dataset_extra_caldav : Record = await get_caldav_resource_store().read(dataset_core.sub_id); + case "ics_feed": { + const dataset_extra_ics_feed : Record = await get_ics_feed_resource_store().read(dataset_core.sub_id); return Promise.resolve<_zeitbild.type_resource_object>( { - "kind": "caldav", + "kind": "ics_feed", "data": { - "url": dataset_extra_caldav["url"], - "read_only": dataset_extra_caldav["read_only"], - "from_fucked_up_wordpress": dataset_extra_caldav["from_fucked_up_wordpress"], + "url": dataset_extra_ics_feed["url"], + "from_fucked_up_wordpress": dataset_extra_ics_feed["from_fucked_up_wordpress"], } } ); @@ -405,18 +404,17 @@ namespace _zeitbild.repository.resource return Promise.resolve<_zeitbild.type_resource_id>(resource_id); break; } - case "caldav": { - const caldav_resource_id : int = await get_caldav_resource_store().create( + case "ics_feed": { + const ics_feed_resource_id : int = await get_ics_feed_resource_store().create( { "url": resource_object.data.url, - "read_only": resource_object.data.read_only, "from_fucked_up_wordpress": resource_object.data.from_fucked_up_wordpress, } ); const resource_id : _zeitbild.type_resource_id = await get_resource_core_store().create( { - "kind": "caldav", - "sub_id": caldav_resource_id, + "kind": "ics_feed", + "sub_id": ics_feed_resource_id, } ); await lib_plankton.cache.clear(_zeitbild.cache_regular); @@ -491,12 +489,12 @@ namespace _zeitbild.repository.resource */ break; } - case "caldav": { - await get_caldav_resource_store().update( + case "ics_feed": { + await get_ics_feed_resource_store().update( dataset_core["sub_id"], { "url": resource_object.data.url, - "read_only": resource_object.data.read_only, + "from_fucked_up_wordpress": resource_object.data.from_fucked_up_wordpress, } ); await lib_plankton.cache.clear(_zeitbild.cache_regular); diff --git a/source/repositories/user.ts b/source/repositories/user.ts index 1000cd3..528ad11 100644 --- a/source/repositories/user.ts +++ b/source/repositories/user.ts @@ -53,6 +53,7 @@ namespace _zeitbild.repository.user return { "name": user_object.name, "email_address": user_object.email_address, + "dav_token": user_object.dav_token, }; } @@ -66,6 +67,7 @@ namespace _zeitbild.repository.user return { "name": row["name"], "email_address": row["email_address"], + "dav_token": row["dav_token"], }; } @@ -119,6 +121,19 @@ namespace _zeitbild.repository.user } + /** + */ + export async function update( + user_id : _zeitbild.type_user_id, + user_object : _zeitbild.type_user_object + ) : Promise + { + const dispersal : Record = encode(user_object); + await get_store().update(user_id, dispersal); + return Promise.resolve(undefined); + } + + /** */ export async function identify( diff --git a/source/services/calendar.ts b/source/services/calendar.ts index 8262b68..c0106fd 100644 --- a/source/services/calendar.ts +++ b/source/services/calendar.ts @@ -327,41 +327,54 @@ namespace _zeitbild.service.calendar ); break; } - case "caldav": { + case "ics_feed": { // TODO readonly - const url : lib_plankton.url.type_url = lib_plankton.url.decode( - resource_object.data.url - ); - const http_request : lib_plankton.http.type_request = { - "version": "HTTP/2", - "scheme": ((url.scheme === "https") ? "https" : "http"), - "host": url.host, - "path": (url.path ?? "/"), - "query": url.query, - "method": lib_plankton.http.enum_method.get, - "headers": {}, - "body": null, - }; - // TODO: cache? - const http_response : lib_plankton.http.type_response = await lib_plankton.http.call( - http_request, - { + const vcalendar : lib_plankton.ical.type_vcalendar = await lib_plankton.cache.get( + _zeitbild.cache_external_resources, + resource_object.data.url, + _zeitbild.conf.get().external_resources.lifetime, + async () => { + const url : lib_plankton.url.type_url = lib_plankton.url.decode( + resource_object.data.url + ); + const http_request : lib_plankton.http.type_request = { + "version": "HTTP/2", + "scheme": ((url.scheme === "https") ? "https" : "http"), + "host": url.host, + "path": (url.path ?? "/"), + "query": url.query, + "method": lib_plankton.http.enum_method.get, + "headers": {}, + "body": null, + }; + const http_response : lib_plankton.http.type_response = await lib_plankton.http.call( + http_request, + { + } + ); + const ics_raw : string = ( + (http_response.body === null) + ? + "" + : + http_response.body.toString() + ); + const vcalendar_list : Array = lib_plankton.ical.ics_decode_multi( + ics_raw, + { + "ignore_unhandled_instruction_keys": resource_object.data.from_fucked_up_wordpress, + "from_fucked_up_wordpress": resource_object.data.from_fucked_up_wordpress, + } + ); + const vcalendar : lib_plankton.ical.type_vcalendar = { + // required + "version": vcalendar_list[0].version, + "prodid": vcalendar_list[0].prodid, + "vevents": vcalendar_list.map(x => x.vevents).reduce((x, y) => x.concat(y), []), + }; + return Promise.resolve(vcalendar); } ); - const ics_raw : string = http_response.body.toString(); - const vcalendar_list : Array = lib_plankton.ical.ics_decode_multi( - ics_raw, - { - "ignore_unhandled_instruction_keys": resource_object.data.from_fucked_up_wordpress, - "from_fucked_up_wordpress": resource_object.data.from_fucked_up_wordpress, - } - ); - const vcalendar : lib_plankton.ical.type_vcalendar = { - // required - "version": vcalendar_list[0].version, - "prodid": vcalendar_list[0].prodid, - "vevents": vcalendar_list.map(x => x.vevents).reduce((x, y) => x.concat(y), []), - }; return Promise.resolve( vcalendar.vevents .map( @@ -376,11 +389,11 @@ namespace _zeitbild.service.calendar : "???" ), - "begin": _zeitbild.helpers.ical_dt_to_own_datetime(vevent.dtstart), + "begin": _zeitbild.helpers.icalendar_dt_to_own_datetime(vevent.dtstart), "end": ( (vevent.dtend !== undefined) ? - _zeitbild.helpers.ical_dt_to_own_datetime(vevent.dtend) + _zeitbild.helpers.icalendar_dt_to_own_datetime(vevent.dtend) : null ), @@ -441,19 +454,6 @@ namespace _zeitbild.service.calendar } - /** - */ - type type_gather_events_result = Array< - { - calendar_id : _zeitbild.type_calendar_id; - calendar_name : string; - access_level : _zeitbild.enum_access_level; - event_id : (null | _zeitbild.type_local_resource_event_id); - event_object : _zeitbild.type_event_object; - } - >; - - /** */ export async function gather_events( @@ -461,7 +461,7 @@ namespace _zeitbild.service.calendar from_pit : lib_plankton.pit.type_pit, to_pit : lib_plankton.pit.type_pit, user_id : (null | _zeitbild.type_user_id) - ) : Promise + ) : Promise> { const calendar_ids_allowed : Array<_zeitbild.type_calendar_id> = ( (await overview(user_id)) @@ -483,8 +483,8 @@ namespace _zeitbild.service.calendar ) ); calendar_ids.sort(); - return lib_plankton.cache.get_complex( - _zeitbild.cache, + return lib_plankton.cache.get_complex>( + _zeitbild.cache_regular, "gather_events", { "user_id": user_id, @@ -492,6 +492,10 @@ namespace _zeitbild.service.calendar "to_pit": to_pit, "calendar_ids": calendar_ids, }, + /** + * @todo expire? + */ + null, () => ( Promise.all( calendar_ids diff --git a/source/services/resource.ts b/source/services/resource.ts index ba5b53e..7b66ee0 100644 --- a/source/services/resource.ts +++ b/source/services/resource.ts @@ -31,7 +31,7 @@ namespace _zeitbild.service.resource return Promise.resolve<_zeitbild.type_event_object>(event_object); break; } - case "caldav": { + case "ics_feed": { // TODO return Promise.reject(new Error("not implemented")); break; @@ -63,14 +63,8 @@ namespace _zeitbild.service.resource return Promise.resolve<_zeitbild.type_local_resource_event_id>(local_resource_event_id); break; } - case "caldav": { - if (resource_object.data.read_only) { - return Promise.reject(new Error("can not add event to read only caldav resource")); - } - else { - // TODO - return Promise.reject(new Error("not implemented")); - } + case "ics_feed": { + return Promise.reject(new Error("unavailable")); break; } default: { @@ -102,14 +96,8 @@ namespace _zeitbild.service.resource return Promise.resolve(undefined); break; } - case "caldav": { - if (resource_object.data.read_only) { - return Promise.reject(new Error("can not change event of read only caldav resource")); - } - else { - // TODO - return Promise.reject(new Error("not implemented")); - } + case "ics_feed": { + return Promise.reject(new Error("unavailable")); break; } default: { @@ -139,14 +127,8 @@ namespace _zeitbild.service.resource return Promise.resolve(undefined); break; } - case "caldav": { - if (resource_object.data.read_only) { - return Promise.reject(new Error("can not delete event from read only caldav resource")); - } - else { - // TODO - return Promise.reject(new Error("not implemented")); - } + case "ics_feed": { + return Promise.reject(new Error("unavailable")); break; } default: { diff --git a/source/services/user.ts b/source/services/user.ts index 98b438d..ec08125 100644 --- a/source/services/user.ts +++ b/source/services/user.ts @@ -28,6 +28,16 @@ namespace _zeitbild.service.user } + /** + */ + export function get( + user_id : _zeitbild.type_user_id + ) : Promise<_zeitbild.type_user_object> + { + return _zeitbild.repository.user.read(user_id); + } + + /** */ export function add( @@ -37,4 +47,15 @@ namespace _zeitbild.service.user return _zeitbild.repository.user.create(user_object); } + + /** + */ + export function change( + user_id : _zeitbild.type_user_id, + user_object : _zeitbild.type_user_object + ) : Promise + { + return _zeitbild.repository.user.update(user_id, user_object); + } + } diff --git a/source/types.ts b/source/types.ts index 5a269c9..200a6f9 100644 --- a/source/types.ts +++ b/source/types.ts @@ -28,6 +28,11 @@ namespace _zeitbild | string ); + dav_token : ( + null + | + string + ); }; @@ -82,10 +87,9 @@ namespace _zeitbild } | { - kind : "caldav"; + kind : "ics_feed"; data : { url : string; - read_only : boolean; from_fucked_up_wordpress : boolean; }; } @@ -111,5 +115,16 @@ namespace _zeitbild }; resource_id : type_resource_id; }; + + + /** + */ + export type type_event_extended = { + calendar_id : type_calendar_id; + calendar_name : string; + access_level : enum_access_level; + event_id : (null | type_local_resource_event_id); + event_object : type_event_object; + }; } diff --git a/tools/makefile b/tools/makefile index 1c2da4c..b175eba 100644 --- a/tools/makefile +++ b/tools/makefile @@ -65,6 +65,8 @@ ${dir_temp}/zeitbild-unlinked.js: \ ${dir_source}/api/actions/session_oidc.ts \ ${dir_source}/api/actions/session_end.ts \ ${dir_source}/api/actions/users.ts \ + ${dir_source}/api/actions/user_dav_conf.ts \ + ${dir_source}/api/actions/user_dav_token.ts \ ${dir_source}/api/actions/calendar_list.ts \ ${dir_source}/api/actions/calendar_get.ts \ ${dir_source}/api/actions/calendar_add.ts \ @@ -75,6 +77,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_ics.ts \ ${dir_source}/api/functions.ts \ ${dir_source}/main.ts @ ${cmd_log} "compile …"