namespace _zeitbild.service.calendar { /** */ function get_access_level( calendar_object : _zeitbild.type_calendar_object, user_id : (null | _zeitbild.type_user_id) ) : _zeitbild.enum_access_level { return ( lib_plankton.list.max<_zeitbild.enum_access_level, _zeitbild.enum_access_level>( [ ( calendar_object.access.public ? _zeitbild.enum_access_level.view : _zeitbild.enum_access_level.none ), ( (user_id === null) ? _zeitbild.enum_access_level.none : calendar_object.access.attributed.get( user_id, lib_plankton.pod.make_filled<_zeitbild.enum_access_level>( calendar_object.access.default_level ) ) ), ], x => x, { "compare_value": _zeitbild.value_object.access_level.order, } )?.value ?? _zeitbild.enum_access_level.none ); } /** * checks if a user has a sufficient access level */ function wrap_check_access_level( calendar_object : _zeitbild.type_calendar_object, user_id : (null | _zeitbild.type_user_id), threshold : _zeitbild.enum_access_level, success_handler : ( (access_level : _zeitbild.enum_access_level) => Promise ) ) : Promise { const access_level : _zeitbild.enum_access_level = get_access_level( calendar_object, user_id ); if (! _zeitbild.value_object.access_level.order(threshold, access_level)) { return Promise.reject( new Error( lib_plankton.string.coin( "insufficient access level; at least required: {{threshold}}, actual: {{actual}}", { "threshold": _zeitbild.value_object.access_level.to_string(threshold), "actual": _zeitbild.value_object.access_level.to_string(access_level), } ) ) ); } else { return success_handler(access_level); } } /** */ export function overview( user_id : (null | _zeitbild.type_user_id) ) : Promise< Array< { id : _zeitbild.type_calendar_id; name : string; access_level : _zeitbild.enum_access_level; } > > { return _zeitbild.repository.calendar.overview(user_id); } /** */ export async function get( calendar_id : _zeitbild.type_calendar_id, user_id : _zeitbild.type_user_id ) : Promise<_zeitbild.type_calendar_object> { const calendar_object : _zeitbild.type_calendar_object = await _zeitbild.repository.calendar.read(calendar_id); return wrap_check_access_level<_zeitbild.type_calendar_object>( calendar_object, user_id, _zeitbild.enum_access_level.view, () => Promise.resolve(calendar_object) ); } /** */ export function add( calendar_object : _zeitbild.type_calendar_object ) : Promise<_zeitbild.type_calendar_id> { return _zeitbild.repository.calendar.create(calendar_object); } /** */ export async function change( calendar_id : _zeitbild.type_calendar_id, calendar_object : _zeitbild.type_calendar_object, user_id : _zeitbild.type_user_id ) : Promise { const calendar_object_current : _zeitbild.type_calendar_object = await _zeitbild.repository.calendar.read(calendar_id); return wrap_check_access_level( calendar_object_current, user_id, _zeitbild.enum_access_level.admin, () => _zeitbild.repository.calendar.update(calendar_id, calendar_object) ); } /** */ export async function remove( calendar_id : _zeitbild.type_calendar_id, user_id : _zeitbild.type_user_id ) : Promise { const calendar_object_current : _zeitbild.type_calendar_object = await _zeitbild.repository.calendar.read(calendar_id); return wrap_check_access_level( calendar_object_current, user_id, _zeitbild.enum_access_level.admin, () => _zeitbild.repository.calendar.delete_(calendar_id) ); } /** */ export async function event_get( calendar_id : _zeitbild.type_calendar_id, local_resource_event_id : _zeitbild.type_local_resource_event_id, user_id : _zeitbild.type_user_id ) : Promise<_zeitbild.type_event_object> { const calendar_object : _zeitbild.type_calendar_object = await _zeitbild.repository.calendar.read( calendar_id ); return wrap_check_access_level<_zeitbild.type_event_object>( calendar_object, user_id, _zeitbild.enum_access_level.view, async () => { const event_object : _zeitbild.type_event_object = await _zeitbild.service.resource.event_get( calendar_object.resource_id, local_resource_event_id ); return Promise.resolve<_zeitbild.type_event_object>(event_object); } ); } /** */ export async function event_add( calendar_id : _zeitbild.type_calendar_id, event_object : _zeitbild.type_event_object, user_id : _zeitbild.type_user_id ) : Promise { const calendar_object : _zeitbild.type_calendar_object = await _zeitbild.repository.calendar.read( calendar_id ); return wrap_check_access_level( calendar_object, user_id, _zeitbild.enum_access_level.edit, async () => { /*const event_id : _zeitbild.type_local_resource_event_id = */await _zeitbild.service.resource.event_add( calendar_object.resource_id, event_object ); return Promise.resolve(undefined); } ); } /** */ export async function event_change( calendar_id : _zeitbild.type_calendar_id, local_resource_event_id : _zeitbild.type_local_resource_event_id, event_object : _zeitbild.type_event_object, user_id : _zeitbild.type_user_id ) : Promise { const calendar_object : _zeitbild.type_calendar_object = await _zeitbild.repository.calendar.read( calendar_id ); return wrap_check_access_level( calendar_object, user_id, _zeitbild.enum_access_level.edit, async () => { await _zeitbild.service.resource.event_change( calendar_object.resource_id, local_resource_event_id, event_object ); return Promise.resolve(undefined); } ); } /** */ export async function event_remove( calendar_id : _zeitbild.type_calendar_id, local_resource_event_id : _zeitbild.type_local_resource_event_id, user_id : _zeitbild.type_user_id ) : Promise { const calendar_object : _zeitbild.type_calendar_object = await _zeitbild.repository.calendar.read( calendar_id ); return wrap_check_access_level( calendar_object, user_id, _zeitbild.enum_access_level.edit, async () => { await _zeitbild.service.resource.event_remove( calendar_object.resource_id, local_resource_event_id ); return Promise.resolve(undefined); } ); } /** * @todo optimize by reducing the number of database queries */ async function get_events( calendar_id : _zeitbild.type_calendar_id, from_pit : lib_plankton.pit.type_pit, to_pit : lib_plankton.pit.type_pit, user_id : (null | _zeitbild.type_user_id) ) : Promise< Array< { id : (null | _zeitbild.type_local_resource_event_id); object : _zeitbild.type_event_object; } > > { const calendar_object : _zeitbild.type_calendar_object = await _zeitbild.repository.calendar.read(calendar_id); return wrap_check_access_level>( calendar_object, user_id, _zeitbild.enum_access_level.view, async () => { const resource_object : _zeitbild.type_resource_object = await _zeitbild.repository.resource.read(calendar_object.resource_id); switch (resource_object.kind) { case "local": { return ( Promise.all( resource_object.data.event_ids .map( (event_id) => ( _zeitbild.repository.resource.local_resource_event_read( calendar_object.resource_id, event_id ) .then( (event_object) => Promise.resolve( { "id": event_id, "object": event_object, } ) ) ) ) ) .then( (event_entries) => Promise.resolve( event_entries .filter( (event_entry : {id : (null | _zeitbild.type_local_resource_event_id); object : _zeitbild.type_event_object;}) => lib_plankton.pit.is_between( lib_plankton.pit.from_datetime(event_entry.object.begin), from_pit, to_pit ) ) ) ) ); break; } case "caldav": { // 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 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( (vevent : lib_plankton.ical.type_vevent) => ( (vevent.dtstart !== undefined) ? { "name": ( (vevent.summary !== undefined) ? vevent.summary : "???" ), "begin": _zeitbild.helpers.ical_dt_to_own_datetime(vevent.dtstart), "end": ( (vevent.dtend !== undefined) ? _zeitbild.helpers.ical_dt_to_own_datetime(vevent.dtend) : null ), "location": ( (vevent.location !== undefined) ? vevent.location : null ), "link": ( (vevent.url !== undefined) ? vevent.url : null ), "description": ( (vevent.description !== undefined) ? vevent.description : null ), } : null ) ) .filter( (event) => (event !== null) ) .map( (event) => ({ "id": null, "object": event, }) ) .filter( (event_entry) => lib_plankton.pit.is_between( lib_plankton.pit.from_datetime(event_entry.object.begin), from_pit, to_pit ) ) ); break; } default: { return Promise.reject( new Error("invalid resource kind: " + resource_object["kind"]) ); break; } } } ); } /** */ 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( calendar_ids_wanted : (null | Array<_zeitbild.type_calendar_id>), from_pit : lib_plankton.pit.type_pit, to_pit : lib_plankton.pit.type_pit, user_id : (null | _zeitbild.type_user_id) ) : Promise { const calendar_ids_allowed : Array<_zeitbild.type_calendar_id> = ( (await overview(user_id)) .map((x : any) => x.id) ); // use set intersection const calendar_ids : Array<_zeitbild.type_calendar_id> = ( ( (calendar_ids_wanted === null) ? calendar_ids_allowed : ( calendar_ids_wanted .filter( (calendar_id) => calendar_ids_allowed.includes(calendar_id) ) ) ) ); calendar_ids.sort(); return lib_plankton.cache.get_complex( _zeitbild.cache, "gather_events", { "user_id": user_id, "from_pit": from_pit, "to_pit": to_pit, "calendar_ids": calendar_ids, }, () => ( Promise.all( calendar_ids .map( async (calendar_id) => { const calendar_object : _zeitbild.type_calendar_object = await _zeitbild.repository.calendar.read( calendar_id ); const access_level : _zeitbild.enum_access_level = get_access_level( calendar_object, user_id ); const events : Array< { id : (null | _zeitbild.type_local_resource_event_id); object : _zeitbild.type_event_object; } > = await get_events( calendar_id, from_pit, to_pit, user_id ); return Promise.resolve( events .map( (event_entry) => ({ "calendar_id": calendar_id, "calendar_name": calendar_object.name, "access_level": access_level, "event_id": event_entry.id, "event_object": event_entry.object, }) ) ); } ) ) .then( (sub_results) => sub_results.reduce( (x, y) => x.concat(y), [] ) ) ) ); } }