Merge pull request 'Gruppen' (#1) from task-70 into main

Reviewed-on: #1
This commit is contained in:
roydfalk 2025-01-18 09:54:03 +01:00
commit 4096014bad
7 changed files with 254 additions and 68 deletions

View file

@ -25,6 +25,7 @@ namespace _espe.api
register< register<
{ {
email_address_private : (null | string); email_address_private : (null | string);
groups ?: Array<string>;
registered : boolean; registered : boolean;
enabled : boolean; enabled : boolean;
}, },
@ -43,6 +44,14 @@ namespace _espe.api
"nullable": true, "nullable": true,
"type": "string" "type": "string"
}, },
"groups": {
"nullable": false,
"type": "array",
"items": {
"type": "string",
"nullable": false,
}
},
"registered": { "registered": {
"nullable": false, "nullable": false,
"type": "boolean" "type": "boolean"
@ -73,6 +82,11 @@ namespace _espe.api
member_id, member_id,
{ {
"email_address_private": input.email_address_private, "email_address_private": input.email_address_private,
"groups": (
(input.groups === undefined)
? lib_plankton.pod.make_empty<Array<string>>()
: lib_plankton.pod.make_filled<Array<string>>(input.groups)
),
"registered": input.registered, "registered": input.registered,
"enabled": input.enabled, "enabled": input.enabled,
} }

View file

@ -27,6 +27,7 @@ namespace _espe.api
membership_number : (null | string); membership_number : (null | string);
name_real_value : string; name_real_value : string;
email_address_private : (null | string); email_address_private : (null | string);
groups ?: Array<string>;
notification_target_url_template ?: (null | string); notification_target_url_template ?: (null | string);
}, },
( (
@ -60,6 +61,14 @@ namespace _espe.api
"nullable": true, "nullable": true,
"description": "private E-Mail-Adresse" "description": "private E-Mail-Adresse"
}, },
"groups": {
"nullable": false,
"type": "array",
"items": {
"type": "string",
"nullable": false,
}
},
"notification_target_url_template": { "notification_target_url_template": {
"type": "string", "type": "string",
"nullable": true, "nullable": true,
@ -109,6 +118,7 @@ namespace _espe.api
) )
: null : null
), ),
"groups": (input.groups ?? []),
} }
); );
if (! _espe.conf.get().settings.misc.auto_register) { if (! _espe.conf.get().settings.misc.auto_register) {

View file

@ -29,6 +29,7 @@ namespace _espe.api
name_real_value : string; name_real_value : string;
name_real_index : int; name_real_index : int;
email_address_private : (null | string); email_address_private : (null | string);
groups : Array<string>;
registered : boolean; registered : boolean;
enabled : boolean; enabled : boolean;
email_use_veiled_address : boolean; email_use_veiled_address : boolean;
@ -66,6 +67,14 @@ namespace _espe.api
"nullable": true, "nullable": true,
"type": "string" "type": "string"
}, },
"groups": {
"nullable": false,
"type": "array",
"items": {
"nullable": false,
"type": "string"
}
},
"registered": { "registered": {
"nullable": false, "nullable": false,
"type": "boolean" "type": "boolean"
@ -113,6 +122,7 @@ namespace _espe.api
"name_real_value", "name_real_value",
"name_real_index", "name_real_index",
"email_address_private", "email_address_private",
"groups",
"registered", "registered",
"enabled", "enabled",
"email_use_veiled_address", "email_use_veiled_address",
@ -136,6 +146,7 @@ namespace _espe.api
"name_real_value": member_object.name_real_value, "name_real_value": member_object.name_real_value,
"name_real_index": member_object.name_real_index, "name_real_index": member_object.name_real_index,
"email_address_private": member_object.email_address_private, "email_address_private": member_object.email_address_private,
"groups": member_object.groups,
"registered": member_object.registered, "registered": member_object.registered,
"enabled": member_object.enabled, "enabled": member_object.enabled,
"email_use_veiled_address": member_object.email_use_veiled_address, "email_use_veiled_address": member_object.email_use_veiled_address,

View file

@ -19,7 +19,7 @@ namespace _espe.database
/** /**
*/ */
const _compatible_revisions : Array<string> = [ const _compatible_revisions : Array<string> = [
"r4", "r5",
]; ];

View file

@ -15,10 +15,21 @@ You should have received a copy of the GNU General Public License along with thi
namespace _espe.repository.member namespace _espe.repository.member
{ {
/**
*/
type type_group_chest = lib_plankton.storage.type_chest<
Array<any>,
Record<string, any>,
lib_plankton.database.type_description_create_table,
lib_plankton.storage.sql_table_common.type_sql_table_common_search_term,
Record<string, any>
>;
/** /**
*/ */
var _store : ( var _core_store : (
null null
| |
lib_plankton.storage.type_store< 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< ) : lib_plankton.storage.type_store<
_espe.type.member_id, _espe.type.member_id,
Record<string, any>, Record<string, any>,
@ -42,8 +62,8 @@ namespace _espe.repository.member
Record<string, any> Record<string, any>
> >
{ {
if (_store === null) { if (_core_store === null) {
_store = lib_plankton.storage.sql_table_autokey_store( _core_store = lib_plankton.storage.sql_table_autokey_store(
{ {
"database_implementation": _espe.helpers.database_implementation(), "database_implementation": _espe.helpers.database_implementation(),
"table_name": "members", "table_name": "members",
@ -54,30 +74,71 @@ namespace _espe.repository.member
else { else {
// do nothing // 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_name"],
}
);
}
else {
// do nothing
}
return _group_chest;
}
/**
*/
type type_dispersal = {
core_row : Record<string, any>;
group_rows : Array<
Record<string, any>
>;
};
/** /**
*/ */
function encode( function encode(
object : _espe.type.member_object object : _espe.type.member_object
) : Record<string, any> ) : type_dispersal
{ {
return { return {
"membership_number": object.membership_number, "core_row": {
"name_real_value": object.name_real_value, "membership_number": object.membership_number,
"name_real_index": object.name_real_index, "name_real_value": object.name_real_value,
"email_address_private": object.email_address_private, "name_real_index": object.name_real_index,
"registered": (object.registered ? 1 : 0), "email_address_private": object.email_address_private,
"enabled": (object.enabled ? 1 : 0), "registered": (object.registered ? 1 : 0),
"email_use_veiled_address": (object.email_use_veiled_address ? 1 : 0), "enabled": (object.enabled ? 1 : 0),
"email_use_nominal_address": (object.email_use_nominal_address ? 1 : 0), "email_use_veiled_address": (object.email_use_veiled_address ? 1 : 0),
"email_redirect_to_private_address": (object.email_redirect_to_private_address ? 1 : 0), "email_use_nominal_address": (object.email_use_nominal_address ? 1 : 0),
"email_allow_sending": (object.email_allow_sending ? 1 : 0), "email_redirect_to_private_address": (object.email_redirect_to_private_address ? 1 : 0),
"password_image": object.password_image, "email_allow_sending": (object.email_allow_sending ? 1 : 0),
"password_change_last_attempt": object.password_change_last_attempt, "password_image": object.password_image,
"password_change_token": object.password_change_token, "password_change_last_attempt": object.password_change_last_attempt,
"password_change_token": object.password_change_token,
},
"group_rows": (
object.groups
.map(
group => ({
"group_name": group,
})
)
)
}; };
} }
@ -85,51 +146,31 @@ namespace _espe.repository.member
/** /**
*/ */
function decode( function decode(
row : Record<string, any> dispersal : type_dispersal
) : _espe.type.member_object ) : _espe.type.member_object
{ {
return { return {
"membership_number": row["membership_number"], "membership_number": dispersal.core_row["membership_number"],
"name_real_value": row["name_real_value"], "name_real_value": dispersal.core_row["name_real_value"],
"name_real_index": row["name_real_index"], "name_real_index": dispersal.core_row["name_real_index"],
"email_address_private": row["email_address_private"], "email_address_private": dispersal.core_row["email_address_private"],
"registered": (row["registered"] > 0), "groups": lib_plankton.list.sorted<string>(
"enabled": (row["enabled"] > 0), dispersal.group_rows.map(row => row["group_name"]),
"email_use_veiled_address": (row["email_use_veiled_address"] > 0), (group1, group2) => ((group1 <= group2) ? 0 : 1)
"email_use_nominal_address": (row["email_use_nominal_address"] > 0), ),
"email_redirect_to_private_address": (row["email_redirect_to_private_address"] > 0), "registered": (dispersal.core_row["registered"] > 0),
"email_allow_sending": (row["email_allow_sending"] > 0), "enabled": (dispersal.core_row["enabled"] > 0),
"password_image": row["password_image"], "email_use_veiled_address": (dispersal.core_row["email_use_veiled_address"] > 0),
"password_change_last_attempt": row["password_change_last_attempt"], "email_use_nominal_address": (dispersal.core_row["email_use_nominal_address"] > 0),
"password_change_token": row["password_change_token"], "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 * @todo optimize
*/ */
@ -149,7 +190,7 @@ namespace _espe.repository.member
> >
{ {
return ( return (
(await get_store().search(null)) (await get_core_store().search(null))
.filter( .filter(
({"key": key, "preview": preview}) => ( ({"key": key, "preview": preview}) => (
( (
@ -185,9 +226,24 @@ namespace _espe.repository.member
id : _espe.type.member_id id : _espe.type.member_id
) : Promise<_espe.type.member_object> ) : Promise<_espe.type.member_object>
{ {
const row : Record<string, any> = await get_store().read(id); const core_row : Record<string, any> = await get_core_store().read(id);
const group_hits : Array<{key : Record<string, any>; preview : Record<string, any>;}> = 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_name": hit.preview["group_name"]
})
),
};
return decode(dispersal);
} }
@ -197,23 +253,63 @@ namespace _espe.repository.member
value : _espe.type.member_object value : _espe.type.member_object
) : Promise<_espe.type.member_id> ) : Promise<_espe.type.member_id>
{ {
const row : Record<string, any> = encode(value); const dispersal : type_dispersal = encode(value);
const id : _espe.type.member_id = await get_store().create(row);
// core
const id : _espe.type.member_id = await get_core_store().create(dispersal.core_row);
// groups
for await (const group_row of dispersal.group_rows) {
await get_group_chest().write(
[
id,
group_row["group_name"],
],
{
"_dummy": null,
}
);
}
return id; return id;
} }
/** /**
* @todo replace groups smartly
*/ */
export async function update( export async function update(
id : _espe.type.member_id, id : _espe.type.member_id,
value : _espe.type.member_object value : _espe.type.member_object
) : Promise<void> ) : Promise<void>
{ {
const row : Record<string, any> = 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<any>; preview : Record<string, any>;}> = await get_group_chest().search(
{
"expression": "member_id = $member_id",
"arguments": {"member_id": id}
}
);
lib_plankton.log.info("update_hit", hits);
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_name"],
],
{
"_dummy": null,
}
);
}
} }
@ -223,7 +319,51 @@ namespace _espe.repository.member
id : _espe.type.member_id id : _espe.type.member_id
) : Promise<void> ) : Promise<void>
{ {
await get_store().delete(id); // groups
const hits : Array<{key : Array<any>; preview : Record<string, any>;}> = 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
})
)
)
)
)
);
} }
} }

View file

@ -309,6 +309,7 @@ namespace _espe.service.member
membership_number : (null | string); membership_number : (null | string);
name_real_value : string; name_real_value : string;
email_address_private : (null | string); email_address_private : (null | string);
groups : Array<string>;
} }
) : Promise<_espe.type.member_id> ) : Promise<_espe.type.member_id>
{ {
@ -327,6 +328,7 @@ namespace _espe.service.member
"password_image": null, "password_image": null,
"password_change_last_attempt": null, "password_change_last_attempt": null,
"password_change_token": null, "password_change_token": null,
"groups": data.groups,
}; };
const id : _espe.type.member_id = await _espe.repository.member.create(object); const id : _espe.type.member_id = await _espe.repository.member.create(object);
signal_change(); signal_change();
@ -534,6 +536,7 @@ namespace _espe.service.member
email_address_private : (null | string); email_address_private : (null | string);
registered : boolean; registered : boolean;
enabled : boolean; enabled : boolean;
groups : lib_plankton.pod.type_pod<Array<string>>;
} }
) : Promise<void> ) : Promise<void>
{ {
@ -552,6 +555,11 @@ namespace _espe.service.member
"password_image": member_object_old.password_image, "password_image": member_object_old.password_image,
"password_change_last_attempt": member_object_old.password_change_last_attempt, "password_change_last_attempt": member_object_old.password_change_last_attempt,
"password_change_token": member_object_old.password_change_token, "password_change_token": member_object_old.password_change_token,
"groups": (
lib_plankton.pod.is_filled<Array<string>>(data.groups)
? lib_plankton.pod.cull<Array<string>>(data.groups)
: member_object_old.groups
),
}; };
await _espe.repository.member.update(member_id, member_object_new); await _espe.repository.member.update(member_id, member_object_new);
signal_change(); signal_change();
@ -650,6 +658,7 @@ namespace _espe.service.member
"password_image": member_object_old.password_image, "password_image": member_object_old.password_image,
"password_change_last_attempt": now, "password_change_last_attempt": now,
"password_change_token": token, "password_change_token": token,
"groups": member_object_old.groups,
}; };
await _espe.repository.member.update(member_id, member_object_new); await _espe.repository.member.update(member_id, member_object_new);
// signal_change(); // signal_change();
@ -745,6 +754,7 @@ namespace _espe.service.member
"password_image": await password_image(password_new), "password_image": await password_image(password_new),
"password_change_last_attempt": member_object_old.password_change_last_attempt, "password_change_last_attempt": member_object_old.password_change_last_attempt,
"password_change_token": null, "password_change_token": null,
"groups": member_object_old.groups,
}; };
await _espe.repository.member.update(member_id, member_object_new); await _espe.repository.member.update(member_id, member_object_new);
signal_change(); signal_change();
@ -833,7 +843,7 @@ namespace _espe.service.member
"disabled": (! entry.object.enabled), "disabled": (! entry.object.enabled),
"displayname": name_display(entry.object), "displayname": name_display(entry.object),
"email": entry.email_address, "email": entry.email_address,
"groups": [], "groups": entry.object.groups,
"password": entry.object.password_image, "password": entry.object.password_image,
} }
]) ])
@ -987,7 +997,7 @@ namespace _espe.service.member
); );
if (http_response.status_code !== 200) { if (http_response.status_code !== 200) {
lib_plankton.log.warning( lib_plankton.log.warning(
"output_arcback_failed", "output_arc_failed",
{ {
"http_response_status_code": http_response.status_code, "http_response_status_code": http_response.status_code,
} }

View file

@ -28,6 +28,7 @@ namespace _espe.type
name_real_value : string; name_real_value : string;
name_real_index : int; name_real_index : int;
email_address_private : (null | string); email_address_private : (null | string);
groups : Array<string>;
registered : boolean; registered : boolean;
enabled : boolean; enabled : boolean;
email_use_veiled_address : boolean; email_use_veiled_address : boolean;