From 141546fee1463945a49dc66c6463bbc1849f169f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Fra=C3=9F?= Date: Fri, 17 Jan 2025 09:49:13 +0100 Subject: [PATCH] [task-70] --- source/api/actions/member_modify.ts | 14 ++ source/api/actions/member_project.ts | 10 + source/api/actions/member_read.ts | 11 ++ source/database.ts | 2 +- source/repositories/member.ts | 267 ++++++++++++++++++++------- source/services/member.ts | 14 +- source/types.ts | 1 + 7 files changed, 251 insertions(+), 68 deletions(-) diff --git a/source/api/actions/member_modify.ts b/source/api/actions/member_modify.ts index 3314766..03c5939 100644 --- a/source/api/actions/member_modify.ts +++ b/source/api/actions/member_modify.ts @@ -25,6 +25,7 @@ namespace _espe.api register< { email_address_private : (null | string); + groups ?: Array; registered : boolean; enabled : boolean; }, @@ -43,6 +44,14 @@ namespace _espe.api "nullable": true, "type": "string" }, + "groups": { + "nullable": false, + "type": "array", + "items": { + "type": "string", + "nullable": false, + } + }, "registered": { "nullable": false, "type": "boolean" @@ -73,6 +82,11 @@ namespace _espe.api member_id, { "email_address_private": input.email_address_private, + "groups": ( + (input.groups === undefined) + ? lib_plankton.pod.make_empty>() + : lib_plankton.pod.make_filled>(input.groups) + ), "registered": input.registered, "enabled": input.enabled, } diff --git a/source/api/actions/member_project.ts b/source/api/actions/member_project.ts index 293cebb..bafdacf 100644 --- a/source/api/actions/member_project.ts +++ b/source/api/actions/member_project.ts @@ -27,6 +27,7 @@ namespace _espe.api membership_number : (null | string); name_real_value : string; email_address_private : (null | string); + groups ?: Array; notification_target_url_template ?: (null | string); }, ( @@ -60,6 +61,14 @@ namespace _espe.api "nullable": true, "description": "private E-Mail-Adresse" }, + "groups": { + "nullable": false, + "type": "array", + "items": { + "type": "string", + "nullable": false, + } + }, "notification_target_url_template": { "type": "string", "nullable": true, @@ -109,6 +118,7 @@ namespace _espe.api ) : null ), + "groups": (input.groups ?? []), } ); if (! _espe.conf.get().settings.misc.auto_register) { diff --git a/source/api/actions/member_read.ts b/source/api/actions/member_read.ts index 90de074..66e010c 100644 --- a/source/api/actions/member_read.ts +++ b/source/api/actions/member_read.ts @@ -29,6 +29,7 @@ namespace _espe.api name_real_value : string; name_real_index : int; email_address_private : (null | string); + groups : Array; registered : boolean; enabled : boolean; email_use_veiled_address : boolean; @@ -66,6 +67,14 @@ namespace _espe.api "nullable": true, "type": "string" }, + "groups": { + "nullable": false, + "type": "array", + "items": { + "nullable": false, + "type": "string" + } + }, "registered": { "nullable": false, "type": "boolean" @@ -113,6 +122,7 @@ namespace _espe.api "name_real_value", "name_real_index", "email_address_private", + "groups", "registered", "enabled", "email_use_veiled_address", @@ -136,6 +146,7 @@ namespace _espe.api "name_real_value": member_object.name_real_value, "name_real_index": member_object.name_real_index, "email_address_private": member_object.email_address_private, + "groups": member_object.groups, "registered": member_object.registered, "enabled": member_object.enabled, "email_use_veiled_address": member_object.email_use_veiled_address, diff --git a/source/database.ts b/source/database.ts index d11e967..079f54a 100644 --- a/source/database.ts +++ b/source/database.ts @@ -19,7 +19,7 @@ namespace _espe.database /** */ const _compatible_revisions : Array = [ - "r4", + "r5", ]; diff --git a/source/repositories/member.ts b/source/repositories/member.ts index d929029..60f0e90 100644 --- a/source/repositories/member.ts +++ b/source/repositories/member.ts @@ -15,10 +15,21 @@ You should have received a copy of the GNU General Public License along with thi namespace _espe.repository.member { + + /** + */ + type type_group_chest = lib_plankton.storage.type_chest< + Array, + Record, + lib_plankton.database.type_description_create_table, + lib_plankton.storage.sql_table_common.type_sql_table_common_search_term, + Record + >; + /** */ - var _store : ( + var _core_store : ( null | lib_plankton.storage.type_store< @@ -33,7 +44,16 @@ namespace _espe.repository.member /** */ - function get_store( + var _group_chest : ( + null + | + type_group_chest + ) = null; + + + /** + */ + function get_core_store( ) : lib_plankton.storage.type_store< _espe.type.member_id, Record, @@ -42,8 +62,8 @@ namespace _espe.repository.member Record > { - if (_store === null) { - _store = lib_plankton.storage.sql_table_autokey_store( + if (_core_store === null) { + _core_store = lib_plankton.storage.sql_table_autokey_store( { "database_implementation": _espe.helpers.database_implementation(), "table_name": "members", @@ -54,30 +74,71 @@ namespace _espe.repository.member else { // do nothing } - return _store; + return _core_store; } + /** + */ + function get_group_chest( + ) : type_group_chest + { + if (_group_chest === null) { + _group_chest = lib_plankton.storage.sql_table_common.chest( + { + "database_implementation": _espe.helpers.database_implementation(), + "table_name": "member_groups", + "key_names": ["member_id","group"], + } + ); + } + else { + // do nothing + } + return _group_chest; + } + + + /** + */ + type type_dispersal = { + core_row : Record; + group_rows : Array< + Record + >; + }; + + /** */ function encode( object : _espe.type.member_object - ) : Record + ) : type_dispersal { return { - "membership_number": object.membership_number, - "name_real_value": object.name_real_value, - "name_real_index": object.name_real_index, - "email_address_private": object.email_address_private, - "registered": (object.registered ? 1 : 0), - "enabled": (object.enabled ? 1 : 0), - "email_use_veiled_address": (object.email_use_veiled_address ? 1 : 0), - "email_use_nominal_address": (object.email_use_nominal_address ? 1 : 0), - "email_redirect_to_private_address": (object.email_redirect_to_private_address ? 1 : 0), - "email_allow_sending": (object.email_allow_sending ? 1 : 0), - "password_image": object.password_image, - "password_change_last_attempt": object.password_change_last_attempt, - "password_change_token": object.password_change_token, + "core_row": { + "membership_number": object.membership_number, + "name_real_value": object.name_real_value, + "name_real_index": object.name_real_index, + "email_address_private": object.email_address_private, + "registered": (object.registered ? 1 : 0), + "enabled": (object.enabled ? 1 : 0), + "email_use_veiled_address": (object.email_use_veiled_address ? 1 : 0), + "email_use_nominal_address": (object.email_use_nominal_address ? 1 : 0), + "email_redirect_to_private_address": (object.email_redirect_to_private_address ? 1 : 0), + "email_allow_sending": (object.email_allow_sending ? 1 : 0), + "password_image": object.password_image, + "password_change_last_attempt": object.password_change_last_attempt, + "password_change_token": object.password_change_token, + }, + "group_rows": ( + object.groups + .map( + group => ({ + "group": group, + }) + ) + ) }; } @@ -85,51 +146,31 @@ namespace _espe.repository.member /** */ function decode( - row : Record + dispersal : type_dispersal ) : _espe.type.member_object { return { - "membership_number": row["membership_number"], - "name_real_value": row["name_real_value"], - "name_real_index": row["name_real_index"], - "email_address_private": row["email_address_private"], - "registered": (row["registered"] > 0), - "enabled": (row["enabled"] > 0), - "email_use_veiled_address": (row["email_use_veiled_address"] > 0), - "email_use_nominal_address": (row["email_use_nominal_address"] > 0), - "email_redirect_to_private_address": (row["email_redirect_to_private_address"] > 0), - "email_allow_sending": (row["email_allow_sending"] > 0), - "password_image": row["password_image"], - "password_change_last_attempt": row["password_change_last_attempt"], - "password_change_token": row["password_change_token"], + "membership_number": dispersal.core_row["membership_number"], + "name_real_value": dispersal.core_row["name_real_value"], + "name_real_index": dispersal.core_row["name_real_index"], + "email_address_private": dispersal.core_row["email_address_private"], + "groups": lib_plankton.list.sorted( + dispersal.group_rows.map(row => row["group"]), + (group1, group2) => ((group1 <= group2) ? 1 : 0) + ), + "registered": (dispersal.core_row["registered"] > 0), + "enabled": (dispersal.core_row["enabled"] > 0), + "email_use_veiled_address": (dispersal.core_row["email_use_veiled_address"] > 0), + "email_use_nominal_address": (dispersal.core_row["email_use_nominal_address"] > 0), + "email_redirect_to_private_address": (dispersal.core_row["email_redirect_to_private_address"] > 0), + "email_allow_sending": (dispersal.core_row["email_allow_sending"] > 0), + "password_image": dispersal.core_row["password_image"], + "password_change_last_attempt": dispersal.core_row["password_change_last_attempt"], + "password_change_token": dispersal.core_row["password_change_token"], }; } - /** - */ - export async function dump( - ) : Promise< - Array< - { - id : _espe.type.member_id; - object : _espe.type.member_object; - } - > - > - { - return ( - (await get_store().search(null)) - .map( - ({"key": key, "preview": preview}) => ({ - "id": key, - "object": (preview as _espe.type.member_object), - }) - ) - ); - } - - /** * @todo optimize */ @@ -149,7 +190,7 @@ namespace _espe.repository.member > { return ( - (await get_store().search(null)) + (await get_core_store().search(null)) .filter( ({"key": key, "preview": preview}) => ( ( @@ -185,9 +226,24 @@ namespace _espe.repository.member id : _espe.type.member_id ) : Promise<_espe.type.member_object> { - const row : Record = await get_store().read(id); + const core_row : Record = await get_core_store().read(id); + const group_hits : Array<{key : Record; preview : Record;}> = await get_group_chest().search( + { + "expression": "member_id = $member_id", + "arguments": {"member_id": id} + } + ); - return decode(row); + const dispersal : type_dispersal = { + "core_row": core_row, + "group_rows": group_hits.map( + hit => ({ + "group": hit.preview["group"] + }) + ), + }; + + return decode(dispersal); } @@ -197,23 +253,60 @@ namespace _espe.repository.member value : _espe.type.member_object ) : Promise<_espe.type.member_id> { - const row : Record = encode(value); - const id : _espe.type.member_id = await get_store().create(row); + const dispersal : type_dispersal = encode(value); + + const id : _espe.type.member_id = await get_core_store().create(dispersal.core_row); + + for await (const group_row of dispersal.group_rows) { + await get_group_chest().write( + [ + id, + group_row["group"], + ], + { + "_dummy": null, + } + ); + } return id; } /** + * @todo replace groups smartly */ export async function update( id : _espe.type.member_id, value : _espe.type.member_object ) : Promise { - const row : Record = encode(value); + const dispersal : type_dispersal = encode(value); - await get_store().update(id, row); + // core + await get_core_store().update(id, dispersal.core_row); + + // groups + const hits : Array<{key : Array; preview : Record;}> = await get_group_chest().search( + { + "expression": "member_id = $member_id", + "arguments": {"member_id": id} + } + ); + for (const hit of hits) { + await get_group_chest().delete([hit.key]); + } + for await (const group_row of dispersal.group_rows) { + await get_group_chest().write( + [ + id, + group_row["group"], + ], + { + "_dummy": null, + } + ); + } } @@ -223,7 +316,51 @@ namespace _espe.repository.member id : _espe.type.member_id ) : Promise { - await get_store().delete(id); + // groups + const hits : Array<{key : Array; preview : Record;}> = await get_group_chest().search( + { + "expression": "member_id = $member_id", + "arguments": {"member_id": id} + } + ); + for (const hit of hits) { + await get_group_chest().delete([hit.key]); + } + + // core + await get_core_store().delete(id); + } + + + /** + */ + export async function dump( + ) : Promise< + Array< + { + id : _espe.type.member_id; + object : _espe.type.member_object; + } + > + > + { + return ( + Promise.all( + (await get_core_store().search(null)) + .map(hit => hit.key) + .map( + id => ( + read(id) + .then( + (object) => ({ + "id": id, + "object": object + }) + ) + ) + ) + ) + ); } } diff --git a/source/services/member.ts b/source/services/member.ts index fb83b6d..6268f73 100644 --- a/source/services/member.ts +++ b/source/services/member.ts @@ -309,6 +309,7 @@ namespace _espe.service.member membership_number : (null | string); name_real_value : string; email_address_private : (null | string); + groups : Array; } ) : Promise<_espe.type.member_id> { @@ -327,6 +328,7 @@ namespace _espe.service.member "password_image": null, "password_change_last_attempt": null, "password_change_token": null, + "groups": data.groups, }; const id : _espe.type.member_id = await _espe.repository.member.create(object); signal_change(); @@ -534,6 +536,7 @@ namespace _espe.service.member email_address_private : (null | string); registered : boolean; enabled : boolean; + groups : lib_plankton.pod.type_pod>; } ) : Promise { @@ -552,6 +555,11 @@ namespace _espe.service.member "password_image": member_object_old.password_image, "password_change_last_attempt": member_object_old.password_change_last_attempt, "password_change_token": member_object_old.password_change_token, + "groups": ( + lib_plankton.pod.is_filled>(data.groups) + ? lib_plankton.pod.cull>(data.groups) + : member_object_old.groups + ), }; await _espe.repository.member.update(member_id, member_object_new); signal_change(); @@ -650,6 +658,7 @@ namespace _espe.service.member "password_image": member_object_old.password_image, "password_change_last_attempt": now, "password_change_token": token, + "groups": member_object_old.groups, }; await _espe.repository.member.update(member_id, member_object_new); // signal_change(); @@ -745,6 +754,7 @@ namespace _espe.service.member "password_image": await password_image(password_new), "password_change_last_attempt": member_object_old.password_change_last_attempt, "password_change_token": null, + "groups": member_object_old.groups, }; await _espe.repository.member.update(member_id, member_object_new); signal_change(); @@ -833,7 +843,7 @@ namespace _espe.service.member "disabled": (! entry.object.enabled), "displayname": name_display(entry.object), "email": entry.email_address, - "groups": [], + "groups": entry.object.groups, "password": entry.object.password_image, } ]) @@ -987,7 +997,7 @@ namespace _espe.service.member ); if (http_response.status_code !== 200) { lib_plankton.log.warning( - "output_arcback_failed", + "output_arc_failed", { "http_response_status_code": http_response.status_code, } diff --git a/source/types.ts b/source/types.ts index 3929d8c..bae22e5 100644 --- a/source/types.ts +++ b/source/types.ts @@ -28,6 +28,7 @@ namespace _espe.type name_real_value : string; name_real_index : int; email_address_private : (null | string); + groups : Array; registered : boolean; enabled : boolean; email_use_veiled_address : boolean;