From 1c8acbaed6b33672e9339042e58cd750fbfed010 Mon Sep 17 00:00:00 2001 From: Fenris Wolf Date: Thu, 26 Sep 2024 10:33:33 +0200 Subject: [PATCH] =?UTF-8?q?[add]=20Termine=20hinzuf=C3=BCgen=20und=20entfe?= =?UTF-8?q?rnen=20[fix]=20Rechte-Pr=C3=BCfung?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/api/actions/calendar_add.ts | 2 +- source/api/actions/calendar_event_add.ts | 91 +++--- source/api/actions/calendar_event_remove.ts | 57 ++++ source/api/actions/calendar_list.ts | 2 +- source/api/actions/events.ts | 84 +++-- source/api/functions.ts | 6 +- source/api/transformations/datetime.ts | 119 +++++++ source/main.ts | 25 +- source/repositories/calendar.ts | 23 +- source/repositories/local_resource_event.ts | 180 ----------- source/repositories/resource.ts | 207 +++++++++--- source/services/calendar.ts | 332 +++++++++++++------- source/services/resource.ts | 18 +- source/types.ts | 14 +- tools/makefile | 3 +- 15 files changed, 702 insertions(+), 461 deletions(-) create mode 100644 source/api/actions/calendar_event_remove.ts create mode 100644 source/api/transformations/datetime.ts delete mode 100644 source/repositories/local_resource_event.ts diff --git a/source/api/actions/calendar_add.ts b/source/api/actions/calendar_add.ts index 984b2b7..f543d4f 100644 --- a/source/api/actions/calendar_add.ts +++ b/source/api/actions/calendar_add.ts @@ -40,7 +40,7 @@ namespace _zeitbild.api >( rest_subject, lib_plankton.http.enum_method.post, - "/calendar/add", + "/calendar", { "description": "erstellt einen Kalender", "output_schema": () => ({ diff --git a/source/api/actions/calendar_event_add.ts b/source/api/actions/calendar_event_add.ts index e7dae14..98d4fe5 100644 --- a/source/api/actions/calendar_event_add.ts +++ b/source/api/actions/calendar_event_add.ts @@ -9,65 +9,50 @@ namespace _zeitbild.api ) : void { register< - { - calendar_id : int; - event : _zeitbild.type_event_object; // TODO aufdröseln - }, - null + _zeitbild.type_event_object, // TODO aufdröseln + ( + null + | + string + ) >( rest_subject, lib_plankton.http.enum_method.post, - "/calendar/event_add", + "/calendar/:calendar_id/event", { "description": "fügt einen Termin hinzu", "input_schema": () => ({ - "nullable": false, "type": "object", "properties": { - "calendar_id": { + "name": { "nullable": false, - "type": "integer", + "type": "string" }, - "event": { - "nullable": false, - "type": "object", - "properties": { - "name": { - "nullable": false, - "type": "string" - }, - // TODO: fix - "begin": { - "type": "int", - "nullable": false, - }, - // TODO: fix - "end": { - "type": "int", - "nullable": true, - }, - "location": { - "type": "string", - "nullable": true, - }, - "description": { - "type": "string", - "nullable": true, - }, - }, - "required": [ - "name", - "begin", - "end", - "location", - "description", - ], - "additionalProperties": false + "begin": _zeitbild.api.datetime_type( + { + "nullable": false, + } + ), + "end": _zeitbild.api.datetime_type( + { + "nullable": true, + } + ), + "location": { + "type": "string", + "nullable": true, + }, + "description": { + "type": "string", + "nullable": true, }, }, "required": [ - "calendar_id", - "event", + "name", + "begin", + "end", + "location", + "description", ], "additionalProperties": false }), @@ -77,14 +62,18 @@ namespace _zeitbild.api }), "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); + if (stuff.input === null) { return Promise.reject(new Error("impossible")); } else { return ( _zeitbild.service.calendar.event_add( - stuff.input.calendar_id, - stuff.input.event + parseInt(stuff.path_parameters["calendar_id"]), + stuff.input, + user_id ) .then( () => Promise.resolve({ @@ -92,6 +81,12 @@ namespace _zeitbild.api "data": null, }) ) + .catch( + (reason) => Promise.resolve({ + "status_code": 403, + "data": String(reason), + }) + ) ); } } diff --git a/source/api/actions/calendar_event_remove.ts b/source/api/actions/calendar_event_remove.ts new file mode 100644 index 0000000..cd83b3a --- /dev/null +++ b/source/api/actions/calendar_event_remove.ts @@ -0,0 +1,57 @@ + +namespace _zeitbild.api +{ + + /** + */ + export function register_calendar_event_remove( + rest_subject : lib_plankton.rest.type_rest + ) : void + { + register< + null, + ( + null + | + string + ) + >( + rest_subject, + lib_plankton.http.enum_method.delete, + "/calendar/:calendar_id/event/:event_id", + { + "description": "entfernt einen Termin", + "output_schema": () => ({ + "nullable": true, + "type": "null" + }), + "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); + + return ( + _zeitbild.service.calendar.event_remove( + parseInt(stuff.path_parameters["calendar_id"]), + parseInt(stuff.path_parameters["event_id"]), + user_id + ) + .then( + () => Promise.resolve({ + "status_code": 200, + "data": null, + }) + ) + .catch( + (reason) => Promise.resolve({ + "status_code": 403, + "data": String(reason), + }) + ) + ); + } + } + ); + } + +} diff --git a/source/api/actions/calendar_list.ts b/source/api/actions/calendar_list.ts index 49b7583..b3c9608 100644 --- a/source/api/actions/calendar_list.ts +++ b/source/api/actions/calendar_list.ts @@ -20,7 +20,7 @@ namespace _zeitbild.api >( rest_subject, lib_plankton.http.enum_method.get, - "/calendars", + "/calendar", { "description": "listet alle verfügbaren Kalender auf", "query_parameters": () => ([ diff --git a/source/api/actions/events.ts b/source/api/actions/events.ts index 39e3504..ee69555 100644 --- a/source/api/actions/events.ts +++ b/source/api/actions/events.ts @@ -18,13 +18,17 @@ namespace _zeitbild.api Array<_zeitbild.type_calendar_id> ); }, - Array< - { - calendar_id : int; - calendar_name : string; - event : _zeitbild.type_event_object; - } - > + ( + Array< + { + calendar_id : int; + calendar_name : string; + event : _zeitbild.type_event_object; + } + > + | + string + ) >( rest_subject, lib_plankton.http.enum_method.get, @@ -67,16 +71,16 @@ namespace _zeitbild.api "type": "string", "nullable": false, }, - // TODO: fix - "begin": { - "type": "int", - "nullable": false, - }, - // TODO: fix - "end": { - "type": "int", - "nullable": true, - }, + "begin": _zeitbild.api.datetime_type( + { + "nullable": false, + } + ), + "end": _zeitbild.api.datetime_type( + { + "nullable": true, + } + ), "location": { "type": "string", "nullable": true, @@ -109,7 +113,7 @@ namespace _zeitbild.api const from : _zeitbild.helpers.type_pit = parseInt(stuff.query_parameters["from"]); const to : _zeitbild.helpers.type_pit = parseInt(stuff.query_parameters["to"]); - const calendar_ids_wanted : Array<_zeitbild.type_calendar_id> = ( + const calendar_ids_wanted : (null | Array<_zeitbild.type_calendar_id>) = ( ( ("calendar_ids" in stuff.query_parameters) && @@ -127,34 +131,26 @@ namespace _zeitbild.api : null ); - const calendar_ids_allowed : Array<_zeitbild.type_calendar_id> = ( - (await _zeitbild.service.calendar.overview(user_id)) - .map((x : any) => x.id) - ); - 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) - ) - ) + return ( + _zeitbild.service.calendar.gather_events( + calendar_ids_wanted, + from, + to, + user_id + ) + .then( + (data) => Promise.resolve({ + "status_code": 200, + "data": data, + }) + ) + .catch( + (reason) => Promise.resolve({ + "status_code": 403, + "data": String(reason), + }) ) ); - calendar_ids.sort(); - const data = await _zeitbild.service.calendar.gather_events( - calendar_ids, - from, - to - ); - return Promise.resolve({ - "status_code": 200, - "data": data, - }); } } ); diff --git a/source/api/functions.ts b/source/api/functions.ts index 77e47da..ffb4220 100644 --- a/source/api/functions.ts +++ b/source/api/functions.ts @@ -35,7 +35,11 @@ namespace _zeitbild.api { _zeitbild.api.register_calendar_list(rest_subject); _zeitbild.api.register_calendar_add(rest_subject); - _zeitbild.api.register_calendar_event_add(rest_subject); + // event + { + _zeitbild.api.register_calendar_event_add(rest_subject); + _zeitbild.api.register_calendar_event_remove(rest_subject); + } } // misc { diff --git a/source/api/transformations/datetime.ts b/source/api/transformations/datetime.ts new file mode 100644 index 0000000..c44d908 --- /dev/null +++ b/source/api/transformations/datetime.ts @@ -0,0 +1,119 @@ + +namespace _zeitbild.api +{ + + /** + */ + export function date_type( + options : { + nullable ?: boolean; + } = {} + ) : lib_plankton.rest.type_oas_schema + { + options = Object.assign( + { + "nullable": false, + }, + options + ); + return { + "nullable": options.nullable, + "type": "object", + "properties": { + "year": { + "nullable": false, + "type": "integer" + }, + "month": { + "nullable": false, + "type": "integer" + }, + "day": { + "nullable": false, + "type": "integer" + }, + }, + "required": [ + "year", + "month", + "day" + ], + "additionalProperties": false + }; + } + + + /** + */ + export function time_type( + options : { + nullable ?: boolean; + } = {} + ) : lib_plankton.rest.type_oas_schema + { + options = Object.assign( + { + "nullable": false, + }, + options + ); + return { + "nullable": options.nullable, + "type": "object", + "properties": { + "hour": { + "nullable": false, + "type": "integer" + }, + "minute": { + "nullable": false, + "type": "integer" + }, + "second": { + "nullable": false, + "type": "integer" + }, + }, + "required": [ + "hour", + "minute", + "second" + ], + "additionalProperties": false + }; + } + + + /** + */ + export function datetime_type( + options : { + nullable ?: boolean; + } = {} + ) : lib_plankton.rest.type_oas_schema + { + options = Object.assign( + { + "nullable": false, + }, + options + ); + return { + "nullable": options.nullable, + "type": "object", + "properties": { + "timezone_shift": { + "nullable": false, + "type": "integer" + }, + "date": date_type({"nullable": false}), + "time": time_type({"nullable": true}), + }, + "required": [ + ], + "additionalProperties": false + }; + } + +} + diff --git a/source/main.ts b/source/main.ts index 6842f3f..44c90f1 100644 --- a/source/main.ts +++ b/source/main.ts @@ -81,21 +81,24 @@ async function data_init( } for await (const calendar_raw of data.calendars) { let resource_object : _zeitbild.type_resource_object; + let resource_id : _zeitbild.type_resource_id; switch (calendar_raw.resource.kind) { case "local": { - const event_ids : Array<_zeitbild.type_event_id> = await Promise.all<_zeitbild.type_event_id>( - calendar_raw.resource.data.events - .map( - // TODO do not use repository, but service - (event_raw : _zeitbild.type_event_object) => _zeitbild.repository.local_resource_event.create(event_raw) - ) - ); resource_object = { "kind": "local", "data": { - "event_ids": event_ids, + "event_ids": [], } }; + resource_id = await _zeitbild.service.resource.add( + resource_object + ); + /*const event_ids : Array<_zeitbild.type_local_resource_event_id> = */await Promise.all( + calendar_raw.resource.data.events + .map( + (event_raw : _zeitbild.type_event_object) => _zeitbild.service.resource.event_add(resource_id, event_raw) + ) + ); break; } case "caldav": { @@ -106,12 +109,12 @@ async function data_init( "read_only": calendar_raw.resource.data.read_only, } }; + resource_id = await _zeitbild.service.resource.add( + resource_object + ); break; } } - const resource_id : _zeitbild.type_resource_id = await _zeitbild.service.resource.add( - resource_object - ); const calendar_object : _zeitbild.type_calendar_object = { "name": calendar_raw.name, "access": { diff --git a/source/repositories/calendar.ts b/source/repositories/calendar.ts index a6029a6..0d40aa5 100644 --- a/source/repositories/calendar.ts +++ b/source/repositories/calendar.ts @@ -172,14 +172,21 @@ namespace _zeitbild.repository.calendar "name": dispersal.core_row["name"], "access": { "default_level": decode_access_level(dispersal.core_row["access_level_default"]), - "attributed": Object.fromEntries( - dispersal.access_attributed_rows - .map( - (access_attributed_row) => ([ - // "calendar_id": access_attributed_row["calendar_id"], - access_attributed_row["user_id"], - decode_access_level(access_attributed_row["level"]), - ]) + "attributed": lib_plankton.map.hashmap.implementation_map( + lib_plankton.map.hashmap.make<_zeitbild.type_user_id, _zeitbild.enum_access_level>( + x => x.toFixed(0), + { + "pairs": ( + dispersal.access_attributed_rows + .map( + (access_attributed_row) => ({ + // "calendar_id": access_attributed_row["calendar_id"], + "key": access_attributed_row["preview"]["user_id"], + "value": decode_access_level(access_attributed_row["preview"]["level"]), + }) + ) + ), + } ) ), }, diff --git a/source/repositories/local_resource_event.ts b/source/repositories/local_resource_event.ts deleted file mode 100644 index ffe66d0..0000000 --- a/source/repositories/local_resource_event.ts +++ /dev/null @@ -1,180 +0,0 @@ - -namespace _zeitbild.repository.local_resource_event -{ - - /** - */ - var _store : ( - null - | - lib_plankton.storage.type_store< - int, - Record, - {}, - lib_plankton.storage.type_sql_table_autokey_search_term, - Record - > - ) = null; - - - /** - */ - function get_store( - ) : lib_plankton.storage.type_store< - int, - Record, - {}, - lib_plankton.storage.type_sql_table_autokey_search_term, - Record - > - { - if (_store === null) { - _store = lib_plankton.storage.sql_table_autokey_store( - { - "database_implementation": _zeitbild.database.get_implementation(), - "table_name": "events", - "key_name": "id", - } - ); - } - else { - // do nothing - } - return _store; - } - - - /** - */ - function encode( - event : _zeitbild.type_event_object - ) : Record - { - const encode_datetime : ((datetime : _zeitbild.helpers.type_datetime) => string) = ((datetime) => { - return lib_plankton.string.coin( - "{{timezone_shift}}|{{date}}{{macro_time}}", - { - "timezone_shift": datetime.timezone_shift.toFixed(0).padStart(2, "0"), - "date": lib_plankton.string.coin( - "{{year}}-{{month}}-{{day}}", - { - "year": datetime.date.year.toFixed(0).padStart(4, "0"), - "month": datetime.date.month.toFixed(0).padStart(2, "0"), - "day": datetime.date.day.toFixed(0).padStart(2, "0"), - } - ), - "macro_time": ( - (datetime.time === null) - ? - "" - : - lib_plankton.string.coin( - "T{{hour}}:{{minute}}:{{second}}", - { - "hour": datetime.time.hour.toFixed(0).padStart(2, "0"), - "minute": datetime.time.minute.toFixed(0).padStart(2, "0"), - "second": datetime.time.second.toFixed(0).padStart(2, "0"), - } - ) - ), - } - ); - }); - return { - "name": event.name, - "begin": encode_datetime(event.begin), - "end": ( - (event.end === null) - ? - null - : - encode_datetime(event.end) - ), - "location": event.location, - "description": event.description, - } - } - - - /** - */ - function decode( - row : Record - ) : _zeitbild.type_event_object - { - const decode_datetime : ((datetime_raw : string) => _zeitbild.helpers.type_datetime) = ((datetime_raw) => { - const parts : Array = datetime_raw.split("|"); - const timezone_shift : int = parseInt(parts[0]); - if (parts[1].length <= 10) { - return { - "timezone_shift": timezone_shift, - "date": { - "year": parseInt(parts[1].slice(0, 4)), - "month": parseInt(parts[1].slice(5, 7)), - "day": parseInt(parts[1].slice(8, 10)), - }, - "time": null - }; - } - else { - return { - "timezone_shift": timezone_shift, - "date": { - "year": parseInt(parts[1].slice(0, 4)), - "month": parseInt(parts[1].slice(5, 7)), - "day": parseInt(parts[1].slice(8, 10)), - }, - "time": { - "hour": parseInt(parts[1].slice(11, 13)), - "minute": parseInt(parts[1].slice(14, 16)), - "second": parseInt(parts[1].slice(17, 19)), - } - }; - } - }); - return { - "name": row["name"], - "begin": decode_datetime(row["begin"]), - "end": ( - (row["end"] === null) - ? - null - : - decode_datetime(row["end"]) - ), - "location": row["location"], - "description": row["description"], - }; - } - - - /** - */ - export function read( - event_id : _zeitbild.type_event_id - ) : Promise<_zeitbild.type_event_object> - { - return ( - get_store().read(event_id) - .then( - row => Promise.resolve<_zeitbild.type_event_object>(decode(row)) - ) - ); - } - - - /** - */ - export function create( - event_object : _zeitbild.type_event_object - ) : Promise<_zeitbild.type_event_id> - { - return ( - Promise.resolve(encode(event_object)) - .then( - (row) => get_store().create(row) - ) - ); - } - -} diff --git a/source/repositories/resource.ts b/source/repositories/resource.ts index 4c2c1f3..5b0e788 100644 --- a/source/repositories/resource.ts +++ b/source/repositories/resource.ts @@ -4,14 +4,22 @@ namespace _zeitbild.repository.resource /** */ - var _local_resource_event_chest : ( + type type_local_resource_event_stuff = { + event : _zeitbild.type_event_object; + local_resource_id : int; + } + + + /** + */ + var _local_resource_event_store : ( null | - lib_plankton.storage.type_chest< - Array, + lib_plankton.storage.type_store< + int, Record, - lib_plankton.database.type_description_create_table, - lib_plankton.storage.sql_table_common.type_sql_table_common_search_term, + {}, + lib_plankton.storage.type_sql_table_autokey_search_term, Record > ) = null; @@ -64,28 +72,28 @@ namespace _zeitbild.repository.resource /** */ - function get_local_resource_event_chest( - ) : lib_plankton.storage.type_chest< - Array, + function get_local_resource_event_store( + ) : lib_plankton.storage.type_store< + int, Record, - lib_plankton.database.type_description_create_table, - lib_plankton.storage.sql_table_common.type_sql_table_common_search_term, + {}, + lib_plankton.storage.type_sql_table_autokey_search_term, Record > { - if (_local_resource_event_chest === null) { - _local_resource_event_chest = lib_plankton.storage.sql_table_common.chest( + if (_local_resource_event_store === null) { + _local_resource_event_store = lib_plankton.storage.sql_table_autokey_store( { "database_implementation": _zeitbild.database.get_implementation(), "table_name": "local_resource_events", - "key_names": ["local_resource_id","event_id"], + "key_name": "id", } ); } else { // do nothing } - return _local_resource_event_chest; + return _local_resource_event_store; } @@ -170,6 +178,114 @@ namespace _zeitbild.repository.resource } + /** + */ + function encode_local_resource_event( + stuff : type_local_resource_event_stuff + ) : Record + { + const encode_datetime : ((datetime : _zeitbild.helpers.type_datetime) => string) = ((datetime) => { + return lib_plankton.string.coin( + "{{timezone_shift}}|{{date}}{{macro_time}}", + { + "timezone_shift": datetime.timezone_shift.toFixed(0).padStart(2, "0"), + "date": lib_plankton.string.coin( + "{{year}}-{{month}}-{{day}}", + { + "year": datetime.date.year.toFixed(0).padStart(4, "0"), + "month": datetime.date.month.toFixed(0).padStart(2, "0"), + "day": datetime.date.day.toFixed(0).padStart(2, "0"), + } + ), + "macro_time": ( + (datetime.time === null) + ? + "" + : + lib_plankton.string.coin( + "T{{hour}}:{{minute}}:{{second}}", + { + "hour": datetime.time.hour.toFixed(0).padStart(2, "0"), + "minute": datetime.time.minute.toFixed(0).padStart(2, "0"), + "second": datetime.time.second.toFixed(0).padStart(2, "0"), + } + ) + ), + } + ); + }); + return { + "local_resource_id": stuff.local_resource_id, + "name": stuff.event.name, + "begin": encode_datetime(stuff.event.begin), + "end": ( + (stuff.event.end === null) + ? + null + : + encode_datetime(stuff.event.end) + ), + "location": stuff.event.location, + "description": stuff.event.description, + } + } + + + /** + */ + function decode_local_resource_event( + row : Record + ) : type_local_resource_event_stuff + { + const decode_datetime : ((datetime_raw : string) => _zeitbild.helpers.type_datetime) = ((datetime_raw) => { + const parts : Array = datetime_raw.split("|"); + const timezone_shift : int = parseInt(parts[0]); + if (parts[1].length <= 10) { + return { + "timezone_shift": timezone_shift, + "date": { + "year": parseInt(parts[1].slice(0, 4)), + "month": parseInt(parts[1].slice(5, 7)), + "day": parseInt(parts[1].slice(8, 10)), + }, + "time": null + }; + } + else { + return { + "timezone_shift": timezone_shift, + "date": { + "year": parseInt(parts[1].slice(0, 4)), + "month": parseInt(parts[1].slice(5, 7)), + "day": parseInt(parts[1].slice(8, 10)), + }, + "time": { + "hour": parseInt(parts[1].slice(11, 13)), + "minute": parseInt(parts[1].slice(14, 16)), + "second": parseInt(parts[1].slice(17, 19)), + } + }; + } + }); + return { + "local_resource_id": row["local_resource_id"], + "event": { + "name": row["name"], + "begin": decode_datetime(row["begin"]), + "end": ( + (row["end"] === null) + ? + null + : + decode_datetime(row["end"]) + ), + "location": row["location"], + "description": row["description"], + } + }; + } + + /** */ /* @@ -220,7 +336,7 @@ namespace _zeitbild.repository.resource switch (dataset_core.kind) { case "local": { const dataset_extra_local_core : Record = await get_local_resource_core_store().read(dataset_core.sub_id); - const datasets_extra_local_event_ids : Array> = await get_local_resource_event_chest().search( + const datasets_extra_local_event_ids : Array> = await get_local_resource_event_store().search( { "expression": "(local_resource_id = $local_resource_id)", "arguments": { @@ -234,7 +350,7 @@ namespace _zeitbild.repository.resource "data": { "event_ids": ( datasets_extra_local_event_ids - .map((hit) => hit.preview["event_id"]) + .map((hit) => hit.preview["id"]) ) } } @@ -276,13 +392,6 @@ namespace _zeitbild.repository.resource "_dummy": null, } ); - for await (const event_id of resource_object.data.event_ids) { - await get_local_resource_event_chest().write( - [local_resource_id, event_id], - { - } - ) - } const resource_id : _zeitbild.type_resource_id = await get_resource_core_store().create( { "kind": "local", @@ -365,12 +474,12 @@ namespace _zeitbild.repository.resource for await (const entry of contrast.both) { await get_local_resource_event_store().update( entry.left.key, - encode_event(entry.right.object) + encode_local_resource_event(entry.right.object) ); } for await (const entry of contrast.only_right) { const event_id : type_local_resource_event_id = await get_local_resource_event_store().create( - encode_event(entry.right.object) + encode_local_resource_event(entry.right.object) ); } */ @@ -398,10 +507,10 @@ namespace _zeitbild.repository.resource /** */ - export async function local_resource_event_add( + export async function local_resource_event_read( resource_id : _zeitbild.type_resource_id, - event_id : _zeitbild.type_event_id - ) : Promise + local_resource_event_id : _zeitbild.type_local_resource_event_id + ) : Promise<_zeitbild.type_event_object> { const dataset_core : Record = await get_resource_core_store().read(resource_id); if (! (dataset_core.kind === "local")) { @@ -409,12 +518,40 @@ namespace _zeitbild.repository.resource } else { return ( - get_local_resource_event_chest().write( - [dataset_core["sub_id"], event_id], - {} + get_local_resource_event_store().read( + local_resource_event_id ) .then( - () => Promise.resolve(undefined) + (row) => Promise.resolve(decode_local_resource_event(row)) + ) + .then( + (local_resource_event_stuff : type_local_resource_event_stuff) => Promise.resolve( + local_resource_event_stuff.event + ) + ) + ); + } + } + + + /** + */ + export async function local_resource_event_create( + resource_id : _zeitbild.type_resource_id, + event_object : _zeitbild.type_event_object + ) : Promise<_zeitbild.type_local_resource_event_id> + { + const dataset_core : Record = await get_resource_core_store().read(resource_id); + if (! (dataset_core.kind === "local")) { + throw (new Error("not a local resource")); + } + else { + return get_local_resource_event_store().create( + encode_local_resource_event( + { + "local_resource_id": dataset_core["sub_id"], + "event": event_object, + } ) ); } @@ -425,7 +562,7 @@ namespace _zeitbild.repository.resource */ export async function local_resource_event_delete( resource_id : _zeitbild.type_resource_id, - event_id : _zeitbild.type_event_id + local_resource_event_id : _zeitbild.type_local_resource_event_id ) : Promise { const dataset_core : Record = await get_resource_core_store().read(resource_id); @@ -434,8 +571,8 @@ namespace _zeitbild.repository.resource } else { return ( - get_local_resource_event_chest().delete( - [dataset_core["sub_id"], event_id] + get_local_resource_event_store().delete( + local_resource_event_id ) .then( () => Promise.resolve(undefined) diff --git a/source/services/calendar.ts b/source/services/calendar.ts index 480d61c..4baf27e 100644 --- a/source/services/calendar.ts +++ b/source/services/calendar.ts @@ -69,22 +69,94 @@ namespace _zeitbild.service.calendar } + /** + * checks if a user has a sufficient access level + */ + function wrap_check_access_level( + calendar_object : _zeitbild.type_calendar_object, + user_id : _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 = calendar_object.access.attributed.get( + user_id, + lib_plankton.pod.make_filled<_zeitbild.enum_access_level>( + calendar_object.access.default_level + ) + ); + 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 async function event_add( calendar_id : _zeitbild.type_calendar_id, - event_object : _zeitbild.type_event_object + 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 ); - const event_id : _zeitbild.type_event_id = await _zeitbild.repository.local_resource_event.create( - event_object + 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); + } ); - return _zeitbild.repository.resource.local_resource_event_add( - calendar_object.resource_id, - event_id + } + + + /** + * @todo check access level + */ + 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); + } ); } @@ -94,7 +166,8 @@ namespace _zeitbild.service.calendar async function get_events( calendar_id : _zeitbild.type_calendar_id, from_pit : _zeitbild.helpers.type_pit, - to_pit : _zeitbild.helpers.type_pit + to_pit : _zeitbild.helpers.type_pit, + user_id : _zeitbild.type_user_id ) : Promise< Array< _zeitbild.type_event_object @@ -102,127 +175,139 @@ namespace _zeitbild.service.calendar > { const calendar_object : _zeitbild.type_calendar_object = await _zeitbild.repository.calendar.read(calendar_id); - 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.local_resource_event.read(event_id) - ) - ) - .then( - (events) => Promise.resolve( - events + 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( + (events) => Promise.resolve( + events + .filter( + (event : _zeitbild.type_event_object) => _zeitbild.helpers.pit_is_between( + _zeitbild.helpers.pit_from_datetime(event.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 vcalendar : lib_plankton.ical.type_vcalendar = lib_plankton.ical.ics_decode( + http_response.body.toString(), + { + } + ); + 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 + ), + "description": ( + (vevent.description !== undefined) + ? + vevent.description + : + null + ), + } + : + null + ) + ) .filter( - (event : _zeitbild.type_event_object) => _zeitbild.helpers.pit_is_between( + (event) => (event !== null) + ) + .filter( + (event) => _zeitbild.helpers.pit_is_between( _zeitbild.helpers.pit_from_datetime(event.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, - { + ); + break; } - ); - const vcalendar : lib_plankton.ical.type_vcalendar = lib_plankton.ical.ics_decode( - http_response.body.toString(), - { + default: { + return Promise.reject( + new Error("invalid resource kind: " + resource_object["kind"]) + ); + break; } - ); - 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 - ), - "description": ( - (vevent.description !== undefined) - ? - vevent.description - : - null - ), - } - : - null - ) - ) - .filter( - (event) => (event !== null) - ) - .filter( - (event) => _zeitbild.helpers.pit_is_between( - _zeitbild.helpers.pit_from_datetime(event.begin), - from_pit, - to_pit - ) - ) - ); - break; + } } - default: { - return Promise.reject( - new Error("invalid resource kind: " + resource_object["kind"]) - ); - break; - } - } + ); } /** + * @todo check access level */ export async function gather_events( - calendar_ids : Array<_zeitbild.type_calendar_id>, + calendar_ids_wanted : (null | Array<_zeitbild.type_calendar_id>), from_pit : _zeitbild.helpers.type_pit, - to_pit : _zeitbild.helpers.type_pit + to_pit : _zeitbild.helpers.type_pit, + user_id : _zeitbild.type_user_id ) : Promise< Array< { @@ -233,6 +318,26 @@ namespace _zeitbild.service.calendar > > { + 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 ( Promise.all( calendar_ids @@ -244,7 +349,8 @@ namespace _zeitbild.service.calendar const events : Array<_zeitbild.type_event_object> = await get_events( calendar_id, from_pit, - to_pit + to_pit, + user_id ); return Promise.resolve( events diff --git a/source/services/resource.ts b/source/services/resource.ts index 2268ab5..a401191 100644 --- a/source/services/resource.ts +++ b/source/services/resource.ts @@ -17,21 +17,18 @@ namespace _zeitbild.service.resource export async function event_add( resource_id : _zeitbild.type_resource_id, event_object : _zeitbild.type_event_object - ) : Promise + ) : Promise<_zeitbild.type_local_resource_event_id> { const resource_object : _zeitbild.type_resource_object = await _zeitbild.repository.resource.read( resource_id ); switch (resource_object.kind) { case "local": { - const event_id : _zeitbild.type_event_id = await _zeitbild.repository.local_resource_event.create( + const local_resource_event_id : _zeitbild.type_local_resource_event_id = await _zeitbild.repository.resource.local_resource_event_create( + resource_id, event_object ); - await _zeitbild.repository.resource.local_resource_event_add( - resource_id, - event_id - ); - return Promise.resolve(undefined); + return Promise.resolve<_zeitbild.type_local_resource_event_id>(local_resource_event_id); break; } case "caldav": { @@ -56,7 +53,7 @@ namespace _zeitbild.service.resource */ export async function event_remove( resource_id : _zeitbild.type_resource_id, - event_object : _zeitbild.type_event_object + local_resource_event_id : _zeitbild.type_local_resource_event_id ) : Promise { const resource_object : _zeitbild.type_resource_object = await _zeitbild.repository.resource.read( @@ -64,12 +61,9 @@ namespace _zeitbild.service.resource ); switch (resource_object.kind) { case "local": { - const event_id : _zeitbild.type_event_id = await _zeitbild.repository.local_resource_event.create( - event_object - ); await _zeitbild.repository.resource.local_resource_event_delete( resource_id, - event_id + local_resource_event_id ); return Promise.resolve(undefined); break; diff --git a/source/types.ts b/source/types.ts index 6ec6e69..d540af5 100644 --- a/source/types.ts +++ b/source/types.ts @@ -31,11 +31,6 @@ namespace _zeitbild }; - /** - */ - export type type_event_id = int; - - /** */ export type type_event_object = { @@ -59,6 +54,11 @@ namespace _zeitbild }; + /** + */ + export type type_local_resource_event_id = int; + + /** */ export type type_resource_id = int; @@ -70,7 +70,9 @@ namespace _zeitbild { kind : "local"; data : { - event_ids : Array; + event_ids : Array< + type_local_resource_event_id + >; }; } | diff --git a/tools/makefile b/tools/makefile index f1f7f27..97b480f 100644 --- a/tools/makefile +++ b/tools/makefile @@ -43,7 +43,6 @@ ${dir_temp}/zeitbild-unlinked.js: \ ${dir_source}/value_objects/access_level.ts \ ${dir_source}/repositories/auth_internal.ts \ ${dir_source}/repositories/user.ts \ - ${dir_source}/repositories/local_resource_event.ts \ ${dir_source}/repositories/resource.ts \ ${dir_source}/repositories/calendar.ts \ ${dir_source}/services/auth_internal.ts \ @@ -51,6 +50,7 @@ ${dir_temp}/zeitbild-unlinked.js: \ ${dir_source}/services/resource.ts \ ${dir_source}/services/calendar.ts \ ${dir_source}/api/base.ts \ + ${dir_source}/api/transformations/datetime.ts \ ${dir_source}/api/actions/meta_ping.ts \ ${dir_source}/api/actions/meta_spec.ts \ ${dir_source}/api/actions/session_prepare.ts \ @@ -60,6 +60,7 @@ ${dir_temp}/zeitbild-unlinked.js: \ ${dir_source}/api/actions/calendar_list.ts \ ${dir_source}/api/actions/calendar_add.ts \ ${dir_source}/api/actions/calendar_event_add.ts \ + ${dir_source}/api/actions/calendar_event_remove.ts \ ${dir_source}/api/actions/events.ts \ ${dir_source}/api/functions.ts \ ${dir_source}/main.ts