diff --git a/source/api/actions/calendar_add.ts b/source/api/actions/calendar_add.ts index f543d4f..4d49ab3 100644 --- a/source/api/actions/calendar_add.ts +++ b/source/api/actions/calendar_add.ts @@ -12,11 +12,11 @@ namespace _zeitbild.api { name : string; access : { - default_level : ("none" | "view" | "edit" | "admin"); + default_level : string; attributed : Array< { user_id : int; - level : ("none" | "view" | "edit" | "admin"); + level : string; } >; }; @@ -43,6 +43,10 @@ namespace _zeitbild.api "/calendar", { "description": "erstellt einen Kalender", + "input_schema": () => ({ + "nullable": true, + // TODO + }), "output_schema": () => ({ "nullable": true, "type": "integer", diff --git a/source/api/actions/calendar_change.ts b/source/api/actions/calendar_change.ts new file mode 100644 index 0000000..4e2bc19 --- /dev/null +++ b/source/api/actions/calendar_change.ts @@ -0,0 +1,94 @@ + +namespace _zeitbild.api +{ + + /** + */ + export function register_calendar_change( + rest_subject : lib_plankton.rest.type_rest + ) : void + { + register< + { + name : string; + access : { + default_level : ("none" | "view" | "edit" | "admin"); + attributed : Array< + { + user_id : int; + level : ("none" | "view" | "edit" | "admin"); + } + >; + }; + }, + null + >( + rest_subject, + lib_plankton.http.enum_method.put, + "/calendar/:calendar_id", + { + "description": "ändert einen Kalender (Resource ist fest)", + "input_schema": () => ({ + "nullable": true, + // TODO + }), + "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); + + if (stuff.input === null) { + return Promise.reject(new Error("impossible")); + } + else { + const calendar_id : _zeitbild.type_calendar_id = parseInt(stuff.path_parameters["calendar_id"]); + // TODO move logic to calendar service + const calendar_object_old : _zeitbild.type_calendar_object = await _zeitbild.service.calendar.get( + calendar_id, + user_id + ); + const calendar_object_new : _zeitbild.type_calendar_object = { + "name": stuff.input.name, + "access": { + "default_level": _zeitbild.value_object.access_level.from_string(stuff.input.access.default_level), + "attributed": lib_plankton.map.hashmap.implementation_map( + lib_plankton.map.hashmap.make( + x => x.toFixed(0), + { + "pairs": ( + stuff.input.access.attributed + .map( + (entry) => ({ + "key": entry.user_id, + "value": _zeitbild.value_object.access_level.from_string(entry.level), + }) + ) + ), + } + ) + ), + }, + "resource_id": calendar_object_old.resource_id + }; + await _zeitbild.service.calendar.change( + calendar_id, + calendar_object_new, + user_id + ); + return Promise.resolve( + { + "status_code": 200, + "data": null, + } + ); + } + } + } + ); + } + +} diff --git a/source/api/actions/calendar_get.ts b/source/api/actions/calendar_get.ts new file mode 100644 index 0000000..2c26105 --- /dev/null +++ b/source/api/actions/calendar_get.ts @@ -0,0 +1,78 @@ + +namespace _zeitbild.api +{ + + /** + */ + export function register_calendar_get( + rest_subject : lib_plankton.rest.type_rest + ) : void + { + register< + null, + { + name : string; + access : { + default_level : string; + attributed : Array< + { + user_id : int; + level : string; + } + > + }; + resource_id : int; + } + >( + rest_subject, + lib_plankton.http.enum_method.get, + "/calendar/:calendar_id", + { + "description": "liest die Daten eines Kalenders aus", + "output_schema": () => ({ + "nullable": true, // TODO + }), + "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); + + const calendar_id : _zeitbild.type_calendar_id = parseInt(stuff.path_parameters["calendar_id"]); + const calendar_object : _zeitbild.type_calendar_object = await _zeitbild.service.calendar.get( + calendar_id, + user_id + ); + const result = { + "name": calendar_object.name, + "access": { + "default_level": _zeitbild.api.access_level_encode(calendar_object.access.default_level), + "attributed": lib_plankton.call.convey( + calendar_object.access.attributed, + [ + lib_plankton.map.dump, + (pairs : Array<{key : _zeitbild.type_user_id; value : _zeitbild.enum_access_level;}>) => ( + pairs + .map( + (pair : {key : _zeitbild.type_user_id; value : _zeitbild.enum_access_level;}) => ({ + "user_id": pair.key, + "level": _zeitbild.api.access_level_encode(pair.value) + }) + ) + ) + ] + ), + }, + "resource_id": calendar_object.resource_id, + }; + return Promise.resolve( + { + "status_code": 200, + "data": result, + } + ); + } + } + ); + } + +} diff --git a/source/api/actions/calendar_remove.ts b/source/api/actions/calendar_remove.ts new file mode 100644 index 0000000..693afc4 --- /dev/null +++ b/source/api/actions/calendar_remove.ts @@ -0,0 +1,44 @@ + +namespace _zeitbild.api +{ + + /** + */ + export function register_calendar_remove( + rest_subject : lib_plankton.rest.type_rest + ) : void + { + register< + null, + null + >( + rest_subject, + lib_plankton.http.enum_method.delete, + "/calendar/:calendar_id", + { + "description": "löscht einen Kalender", + "output_schema": () => ({ + "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); + + const calendar_id : _zeitbild.type_calendar_id = parseInt(stuff.path_parameters["calendar_id"]); + await _zeitbild.service.calendar.remove( + calendar_id, + user_id + ); + return Promise.resolve( + { + "status_code": 200, + "data": null, + } + ); + } + } + ); + } + +} diff --git a/source/api/actions/events.ts b/source/api/actions/events.ts index 9e32c7b..2623dbe 100644 --- a/source/api/actions/events.ts +++ b/source/api/actions/events.ts @@ -23,6 +23,7 @@ namespace _zeitbild.api { calendar_id : int; calendar_name : string; + access_level : string; event_id : (null | int); event_object : _zeitbild.type_event_object; } @@ -68,6 +69,11 @@ namespace _zeitbild.api "nullable": false, "type": "string", }, + "access_level": { + "nullable": false, + "type": "string", + "enum": ["none","view","edit","admin"], + }, "event_id": { "nullable": true, "type": "number", @@ -152,7 +158,18 @@ namespace _zeitbild.api .then( (data) => Promise.resolve({ "status_code": 200, - "data": data, + "data": ( + data + .map( + (entry) => ({ + "calendar_id": entry.calendar_id, + "calendar_name": entry.calendar_name, + "access_level": _zeitbild.api.access_level_encode(entry.access_level), + "event_id": entry.event_id, + "event_object": entry.event_object, + }) + ) + ), }) ) .catch( diff --git a/source/api/actions/users.ts b/source/api/actions/users.ts new file mode 100644 index 0000000..fc4010b --- /dev/null +++ b/source/api/actions/users.ts @@ -0,0 +1,74 @@ + +namespace _zeitbild.api +{ + + /** + */ + export function register_users( + rest_subject : lib_plankton.rest.type_rest + ) : void + { + register< + null, + Array< + { + id : int; + name : string; + } + > + >( + rest_subject, + lib_plankton.http.enum_method.get, + "/users", + { + "description": "listet alle Nutzer auf", + "query_parameters": () => ([ + { + "name": "term", + "required": false, + "description": "search term", + }, + ]), + "output_schema": () => ({ + "type": "array", + "items": { + "nullable": false, + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "nullable": false, + "type": "number", + }, + "name": { + "nullable": false, + "type": "string", + }, + }, + "required": [ + "id", + "name", + ], + } + }), + "restriction": restriction_logged_in, + "execution": async (stuff) => { + const result : Array< + { + id : _zeitbild.type_user_id; + name : string; + } + > = await _zeitbild.service.user.list( + ); + return Promise.resolve( + { + "status_code": 200, + "data": result, + } + ); + } + } + ); + } + +} diff --git a/source/api/functions.ts b/source/api/functions.ts index 93e16a7..28ebbe2 100644 --- a/source/api/functions.ts +++ b/source/api/functions.ts @@ -34,7 +34,10 @@ namespace _zeitbild.api // calendar { _zeitbild.api.register_calendar_list(rest_subject); + _zeitbild.api.register_calendar_get(rest_subject); _zeitbild.api.register_calendar_add(rest_subject); + _zeitbild.api.register_calendar_change(rest_subject); + _zeitbild.api.register_calendar_remove(rest_subject); // event { _zeitbild.api.register_calendar_event_get(rest_subject); @@ -45,6 +48,7 @@ namespace _zeitbild.api } // misc { + _zeitbild.api.register_users(rest_subject); _zeitbild.api.register_events(rest_subject); } diff --git a/source/api/transformations/datetime.ts b/source/api/transformations/datetime.ts index c44d908..bb2a51d 100644 --- a/source/api/transformations/datetime.ts +++ b/source/api/transformations/datetime.ts @@ -115,5 +115,38 @@ namespace _zeitbild.api }; } + + + /** + */ + export function access_level_encode( + access_level : _zeitbild.enum_access_level + ) : string + { + switch (access_level) { + case _zeitbild.enum_access_level.none: {return "none";} + case _zeitbild.enum_access_level.view: {return "view";} + case _zeitbild.enum_access_level.edit: {return "edit";} + case _zeitbild.enum_access_level.admin: {return "admin";} + default: {throw (new Error("invalid access level: " + String(access_level)));} + } + } + + + /** + */ + export function access_level_decode( + access_level_ : string + ) : _zeitbild.enum_access_level + { + switch (access_level_) { + case "none": {return _zeitbild.enum_access_level.none;} + case "view": {return _zeitbild.enum_access_level.view;} + case "edit": {return _zeitbild.enum_access_level.edit;} + case "admin": {return _zeitbild.enum_access_level.admin;} + default: {throw (new Error("invalid encoded access level: " + String(access_level_)));} + } + } + } diff --git a/source/main.ts b/source/main.ts index 44c90f1..83ffc25 100644 --- a/source/main.ts +++ b/source/main.ts @@ -1,4 +1,5 @@ + /** */ type type_data = { diff --git a/source/repositories/calendar.ts b/source/repositories/calendar.ts index 0d40aa5..68f0d74 100644 --- a/source/repositories/calendar.ts +++ b/source/repositories/calendar.ts @@ -285,37 +285,117 @@ namespace _zeitbild.repository.calendar /** */ - export function create( + export async function create( calendar_object : _zeitbild.type_calendar_object ) : Promise<_zeitbild.type_calendar_id> { - return ( - Promise.resolve<_zeitbild.type_calendar_object>(calendar_object) - .then( - (calendar_object) => Promise.resolve(encode(calendar_object)) - ) - .then<_zeitbild.type_calendar_id>( - (dispersal) => ( - get_core_store().create(dispersal.core_row) - .then<_zeitbild.type_calendar_id>( - (calendar_id) => ( - Promise.all( - dispersal.access_attributed_rows - .map( - (access_attributed_row) => get_access_attributed_chest().write( - [calendar_id, access_attributed_row["user_id"]], - {"level": access_attributed_row["level"]} - ) - ) - ) - .then( - () => Promise.resolve<_zeitbild.type_calendar_id>(calendar_id) - ) - ) - ) - ) - ) + const dispersal : type_dispersal = encode(calendar_object); + const core_store = get_core_store(); + const calendar_id : _zeitbild.type_calendar_id = await core_store.create( + dispersal.core_row ); + for await (const access_attributed_row of dispersal.access_attributed_rows) { + get_access_attributed_chest().write( + [calendar_id, access_attributed_row["user_id"]], + {"level": access_attributed_row["level"]} + ); + } + return Promise.resolve<_zeitbild.type_calendar_id>(calendar_id); + } + + + /** + */ + export async function update( + calendar_id : _zeitbild.type_calendar_id, + calendar_object : _zeitbild.type_calendar_object + ) : Promise + { + const dispersal : type_dispersal = encode(calendar_object); + // core + { + const core_store = get_core_store(); + await core_store.update( + calendar_id, + dispersal.core_row + ); + } + // attributed access + { + const access_attributed_chest = get_access_attributed_chest(); + const hits : Array> = await access_attributed_chest.search( + { + "expression": "(calendar_id = $calendar_id)", + "arguments": { + "calendar_id": calendar_id, + } + } + ); + const contrast = lib_plankton.list.contrast< + Record, + Record + >( + hits, + hit => hit["user_id"], + dispersal.access_attributed_rows, + row => row["user_id"] + ); + // delete + for await (const entry of contrast.only_left) { + await access_attributed_chest.delete( + [calendar_id, entry.left["user_id"]] + ); + } + // update + for await (const entry of contrast.both) { + await access_attributed_chest.write( + [calendar_id, entry.right["user_id"]], + {"level": entry.right["level"]} + ); + } + // create + for await (const entry of contrast.only_right) { + await access_attributed_chest.write( + [calendar_id, entry.right["user_id"]], + {"level": entry.right["level"]} + ); + } + } + return Promise.resolve(undefined); + } + + + /** + */ + export async function delete_( + calendar_id : _zeitbild.type_calendar_id + ) : Promise + { + const core_store = get_core_store(); + const access_attributed_chest = get_access_attributed_chest(); + // attributed access + { + const hits : Array> = await access_attributed_chest.search( + { + "expression": "(calendar_id = {{calendar_id}})", + "arguments": { + "calendar_id": calendar_id, + } + } + ); + for await (const hit of hits) { + await access_attributed_chest.delete( + [calendar_id, hit["user_id"]] + ); + } + } + // core + { + await core_store.delete( + calendar_id + ); + } + return Promise.resolve(undefined); } diff --git a/source/repositories/user.ts b/source/repositories/user.ts index e98beba..1000cd3 100644 --- a/source/repositories/user.ts +++ b/source/repositories/user.ts @@ -70,6 +70,31 @@ namespace _zeitbild.repository.user } + /** + */ + export async function list( + ) : Promise< + Array< + { + id : _zeitbild.type_user_id; + name : string; + } + > + > + { + const hits : Array<{key : int; preview : Record;}> = await get_store().search({"expression": "TRUE", "arguments": {}}); + return Promise.resolve( + hits + .map( + (hit) => ({ + "id": hit.key, + "name": hit.preview.name, + }) + ) + ); + } + + /** */ export async function read( diff --git a/source/services/calendar.ts b/source/services/calendar.ts index f1c25eb..1abf053 100644 --- a/source/services/calendar.ts +++ b/source/services/calendar.ts @@ -3,12 +3,56 @@ namespace _zeitbild.service.calendar { /** + * checks if a user has a sufficient access level */ - export function add( - calendar_object : _zeitbild.type_calendar_object - ) : Promise<_zeitbild.type_calendar_id> + function get_access_level( + calendar_object : _zeitbild.type_calendar_object, + user_id : _zeitbild.type_user_id + ) : _zeitbild.enum_access_level { - return _zeitbild.repository.calendar.create(calendar_object); + return calendar_object.access.attributed.get( + user_id, + lib_plankton.pod.make_filled<_zeitbild.enum_access_level>( + calendar_object.access.default_level + ) + ); + } + + + /** + * 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 = 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); + } } @@ -62,49 +106,62 @@ namespace _zeitbild.service.calendar /** */ export async function get( - calendar_id : _zeitbild.type_calendar_id + calendar_id : _zeitbild.type_calendar_id, + user_id : _zeitbild.type_user_id ) : Promise<_zeitbild.type_calendar_object> { - return _zeitbild.repository.calendar.read(calendar_id); + 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) + ); } /** - * 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 + export function add( + calendar_object : _zeitbild.type_calendar_object + ) : Promise<_zeitbild.type_calendar_id> { - const access_level : _zeitbild.enum_access_level = calendar_object.access.attributed.get( + 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, - lib_plankton.pod.make_filled<_zeitbild.enum_access_level>( - calendar_object.access.default_level - ) + _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) ); - 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); - } } @@ -385,6 +442,7 @@ namespace _zeitbild.service.calendar { 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; } @@ -419,6 +477,10 @@ namespace _zeitbild.service.calendar 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); @@ -436,6 +498,7 @@ namespace _zeitbild.service.calendar (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, }) diff --git a/source/services/user.ts b/source/services/user.ts index f74846c..98b438d 100644 --- a/source/services/user.ts +++ b/source/services/user.ts @@ -4,11 +4,17 @@ namespace _zeitbild.service.user /** */ - export function add( - user_object : _zeitbild.type_user_object - ) : Promise<_zeitbild.type_user_id> + export function list( + ) : Promise< + Array< + { + id : _zeitbild.type_user_id; + name : string; + } + > + > { - return _zeitbild.repository.user.create(user_object); + return _zeitbild.repository.user.list(); } @@ -21,4 +27,14 @@ namespace _zeitbild.service.user return _zeitbild.repository.user.identify(name); } + + /** + */ + export function add( + user_object : _zeitbild.type_user_object + ) : Promise<_zeitbild.type_user_id> + { + return _zeitbild.repository.user.create(user_object); + } + } diff --git a/tools/makefile b/tools/makefile index 1486827..5112546 100644 --- a/tools/makefile +++ b/tools/makefile @@ -57,8 +57,12 @@ ${dir_temp}/zeitbild-unlinked.js: \ ${dir_source}/api/actions/session_begin.ts \ ${dir_source}/api/actions/session_oidc.ts \ ${dir_source}/api/actions/session_end.ts \ + ${dir_source}/api/actions/users.ts \ ${dir_source}/api/actions/calendar_list.ts \ + ${dir_source}/api/actions/calendar_get.ts \ ${dir_source}/api/actions/calendar_add.ts \ + ${dir_source}/api/actions/calendar_change.ts \ + ${dir_source}/api/actions/calendar_remove.ts \ ${dir_source}/api/actions/calendar_event_get.ts \ ${dir_source}/api/actions/calendar_event_add.ts \ ${dir_source}/api/actions/calendar_event_change.ts \