[task-193] [int]

This commit is contained in:
roydfalk 2025-07-07 12:10:52 +02:00
parent 39df56c8af
commit ff75196d26
23 changed files with 614 additions and 1295 deletions

View file

@ -1,4 +1,26 @@
{ {
"groups": [
{
"id": 1,
"name": "auto"
},
{
"id": 2,
"name": "zug"
},
{
"id": 3,
"name": "flugzeug"
},
{
"id": 4,
"name": "fahrrad"
},
{
"id": 5,
"name": "zu_fuß"
}
],
"admins": [ "admins": [
{ {
"name": "admin", "name": "admin",
@ -8,34 +30,33 @@
], ],
"members": [ "members": [
{ {
"membership_number": "123", "id": 1,
"name_real": "Alexandra Ahorn", "name": "Alexandra Ahorn",
"email_address_private": "alex-rockt@example.org", "email_address_private": "alex-rockt@example.org",
"groups": ["auto","zug","flugzeug"] "groups": [1, 2, 3]
}, },
{ {
"membership_number": "234", "id": 2,
"name_real": "Berthold Buche", "name": "Berthold Buche",
"email_address_private": "bert-ohne-ernie@example.org", "email_address_private": "bert-ohne-ernie@example.org",
"groups": ["fahrrad","zu_fuß","zug"] "groups": [4, 5, 2]
}, },
{ {
"membership_number": "345", "id": 3,
"name_real": "Charlotte Castania", "name": "Charlotte Castania",
"email_adress_private": "charly-the-unicorn@example.org", "email_adress_private": "charly-the-unicorn@example.org",
"groups": ["fahrrad","auto"] "groups": [4, 1]
} }
], ],
"invites": [ "invites": [
{ {
"membership_number_changeable": false, "id": 1,
"membership_number_value": "456",
"name_changeable": true, "name_changeable": true,
"name_value": "Daniel Distel", "name_value": "Daniel Distel",
"email_address_changeable": true, "email_address_changeable": true,
"email_address_value": "duesentrieb@example.org", "email_address_value": "duesentrieb@example.org",
"groups_changeable": false, "groups_changeable": false,
"groups_value": ["flugzeug","zu_fuß"] "groups_value": [3, 5]
} }
] ]
} }

View file

@ -17,7 +17,7 @@ namespace _espe.api
{ {
/** /**
*/ */
export function register_invite_accept( export function register_invitation_accept(
rest_subject : lib_plankton.rest_http.type_rest rest_subject : lib_plankton.rest_http.type_rest
) : void ) : void
{ {
@ -25,9 +25,8 @@ namespace _espe.api
{ {
key : string; key : string;
data : { data : {
membership_number : (null | string); name : (null | string);
groups : Array<string>; groups : (null | Array<int>);
name : string;
email_address : (null | string); email_address : (null | string);
password : (null | string); password : (null | string);
}; };
@ -41,7 +40,7 @@ namespace _espe.api
>( >(
rest_subject, rest_subject,
lib_plankton.http.enum_method.post, lib_plankton.http.enum_method.post,
_espe.api.full_path("/invite/accept"), _espe.api.full_path("/invitation/accept"),
{ {
/** /**
* @todo translation * @todo translation
@ -72,7 +71,7 @@ namespace _espe.api
{ {
try try
{ {
const flaws = await _espe.service.invite.accept( const flaws = await _espe.service.invitation.accept(
input.key, input.key,
input.data input.data
); );

View file

@ -18,21 +18,19 @@ namespace _espe.api
/** /**
*/ */
export function register_invite_create( export function register_invitation_create(
rest_subject : lib_plankton.rest_http.type_rest rest_subject : lib_plankton.rest_http.type_rest
) : void ) : void
{ {
lib_plankton.rest_http.register< lib_plankton.rest_http.register<
{ {
data : { data : {
membership_number_changeable : boolean;
membership_number_value : (null | string);
name_changeable : boolean; name_changeable : boolean;
name_value : string; name_value : string;
email_address_changeable : boolean; email_address_changeable : boolean;
email_address_value : (null | string); email_address_value : (null | string);
groups_changeable : boolean; groups_changeable : boolean;
groups_value : Array<string>; groups_value : Array<int>;
expiry : (null | int); expiry : (null | int);
}; };
notification_target_url_template ?: (null | string); notification_target_url_template ?: (null | string);
@ -49,7 +47,7 @@ namespace _espe.api
>( >(
rest_subject, rest_subject,
lib_plankton.http.enum_method.post, lib_plankton.http.enum_method.post,
_espe.api.full_path("/invite/create"), _espe.api.full_path("/invitation/create"),
{ {
/** /**
* @todo translation * @todo translation
@ -70,16 +68,6 @@ namespace _espe.api
"type": "intiger", "type": "intiger",
"description": "Ablaufzeitpunkt" "description": "Ablaufzeitpunkt"
}, },
"membership_number_changeable": {
"type": "boolean",
"nullable": false,
"description": "Mitgliedsnummer | änderbar"
},
"membership_number_value": {
"type": "string",
"nullable": true,
"description": "Mitgliedsnummer | Wert"
},
"name_changeable": { "name_changeable": {
"type": "boolean", "type": "boolean",
"nullable": false, "nullable": false,
@ -109,7 +97,7 @@ namespace _espe.api
"nullable": false, "nullable": false,
"type": "array", "type": "array",
"items": { "items": {
"type": "string", "type": "integer",
"nullable": false, "nullable": false,
}, },
"description": "Gruppen | Wert" "description": "Gruppen | Wert"
@ -168,43 +156,25 @@ namespace _espe.api
return Promise.reject(new Error("impossible")); return Promise.reject(new Error("impossible"));
} }
else { else {
if ( const invitation_info : {id : _espe.type.invitation_id; key : _espe.type.invitation_key;} = await _espe.service.invitation.create(
(! _espe.conf.get().settings.misc.facultative_membership_number) {
&& "name_changeable": input.data.name_changeable,
( "name_value": input.data.name_value,
(input.data.membership_number_value === null) "email_address_changeable": input.data.email_address_changeable,
|| "email_address_value": input.data.email_address_value,
(input.data.membership_number_value === "") "groups_changeable": input.data.groups_changeable,
) "groups_value": input.data.groups_value,
) { },
return Promise.resolve({ {
"status_code": 400, "expiry": input.data.expiry,
"data": "membership number required" "notification_target_url_template": input.notification_target_url_template,
}); "send_immediatly": input.send_immediatly,
} }
else { );
const invite_info : {id : _espe.type.invite_id; key : _espe.type.invite_key;} = await _espe.service.invite.create( return Promise.resolve({
{ "status_code": 201,
"membership_number_changeable": input.data.membership_number_changeable, "data": invitation_info
"membership_number_value": input.data.membership_number_value, });
"name_changeable": input.data.name_changeable,
"name_value": input.data.name_value,
"email_address_changeable": input.data.email_address_changeable,
"email_address_value": input.data.email_address_value,
"groups_changeable": input.data.groups_changeable,
"groups_value": input.data.groups_value,
},
{
"expiry": input.data.expiry,
"notification_target_url_template": input.notification_target_url_template,
"send_immediatly": input.send_immediatly,
}
);
return Promise.resolve({
"status_code": 201,
"data": invite_info
});
}
} }
} }
} }

View file

@ -17,7 +17,7 @@ namespace _espe.api
{ {
/** /**
*/ */
export function register_invite_examine( export function register_invitation_examine(
rest_subject : lib_plankton.rest_http.type_rest rest_subject : lib_plankton.rest_http.type_rest
) : void ) : void
{ {
@ -27,7 +27,7 @@ namespace _espe.api
>( >(
rest_subject, rest_subject,
lib_plankton.http.enum_method.get, lib_plankton.http.enum_method.get,
_espe.api.full_path("/invite/examine"), _espe.api.full_path("/invitation/examine"),
{ {
/** /**
* @todo translation * @todo translation
@ -50,16 +50,6 @@ namespace _espe.api
"type": "integer", "type": "integer",
"description": "Ablaufzeitpunkt" "description": "Ablaufzeitpunkt"
}, },
"membership_number_changeable": {
"type": "boolean",
"nullable": false,
"description": "Mitgliedsnummer | änderbar"
},
"membership_number_value": {
"type": "string",
"nullable": true,
"description": "Mitgliedsnummer | Wert"
},
"name_changeable": { "name_changeable": {
"type": "boolean", "type": "boolean",
"nullable": false, "nullable": false,
@ -97,34 +87,30 @@ namespace _espe.api
}, },
"required": [ "required": [
"expiry", "expiry",
"membership_number_mode", "name_changeable",
"membership_number_value",
"name_mode",
"name_value", "name_value",
"email_address_mode", "email_address_changeable",
"email_address_value", "email_address_value",
"groups_mode", "groups_changeable",
"groups_value", "groups_value",
] ]
}), }),
"restriction": () => restriction_none, "restriction": () => restriction_none,
"execution": () => ({"query_parameters": query_parameters, "input": input}) => { "execution": () => ({"query_parameters": query_parameters, "input": input}) => {
const invite_key : _espe.type.invite_key = query_parameters["key"]; const invitation_key : _espe.type.invitation_key = query_parameters["key"];
return ( return (
_espe.service.invite.examine(invite_key) _espe.service.invitation.examine(invitation_key)
.then( .then(
(invite_object) => Promise.resolve({ (invitation_object) => Promise.resolve({
"status_code": 200, "status_code": 200,
"data": { "data": {
"expiry": invite_object.expiry, "expiry": invitation_object.expiry,
"membership_number_changeable": invite_object.membership_number_changeable, "name_changeable": invitation_object.name_changeable,
"membership_number_value": invite_object.membership_number_value, "name_value": invitation_object.name_value,
"name_changeable": invite_object.name_changeable, "email_address_changeable": invitation_object.email_address_changeable,
"name_value": invite_object.name_value, "email_address_value": invitation_object.email_address_value,
"email_address_changeable": invite_object.email_address_changeable, "groups_changeable": invitation_object.groups_changeable,
"email_address_value": invite_object.email_address_value, "groups_value": invitation_object.groups_value,
"groups_changeable": invite_object.groups_changeable,
"groups_value": invite_object.groups_value,
} }
}) })
) )

View file

@ -17,7 +17,7 @@ namespace _espe.api
{ {
/** /**
*/ */
export function register_invite_list( export function register_invitation_list(
rest_subject : lib_plankton.rest_http.type_rest rest_subject : lib_plankton.rest_http.type_rest
) : void ) : void
{ {
@ -25,16 +25,16 @@ namespace _espe.api
null, null,
Array< Array<
{ {
id : _espe.type.invite_id; id : _espe.type.invitation_id;
key : _espe.type.invite_key; key : _espe.type.invitation_key;
expiry : (null | int); expiry : (null | int);
name_value : string; name_value : (null | string);
} }
> >
>( >(
rest_subject, rest_subject,
lib_plankton.http.enum_method.get, lib_plankton.http.enum_method.get,
_espe.api.full_path("/invite/list"), _espe.api.full_path("/invitation/list"),
{ {
/** /**
* @todo translation * @todo translation
@ -73,7 +73,7 @@ namespace _espe.api
}), }),
"restriction": () => restriction_logged_in, "restriction": () => restriction_logged_in,
"execution": () => async ({}) => { "execution": () => async ({}) => {
const data = await _espe.service.invite.list(); const data = await _espe.service.invitation.list();
return Promise.resolve({ return Promise.resolve({
"status_code": 200, "status_code": 200,
"data": data "data": data

View file

@ -18,7 +18,7 @@ namespace _espe.api
/** /**
*/ */
export function register_invite_read( export function register_invitation_read(
rest_subject : lib_plankton.rest_http.type_rest rest_subject : lib_plankton.rest_http.type_rest
) : void ) : void
{ {
@ -30,20 +30,18 @@ namespace _espe.api
{ {
key : string; key : string;
expiry : (null | int); expiry : (null | int);
membership_number_changeable : boolean;
membership_number_value : (null | string);
name_changeable : boolean; name_changeable : boolean;
name_value : string; name_value : (null | string);
email_address_changeable : boolean; email_address_changeable : boolean;
email_address_value : (null | string); email_address_value : (null | string);
groups_changeable : boolean; groups_changeable : boolean;
groups_value : Array<string>; groups_value : (null | Array<int>);
} }
) )
>( >(
rest_subject, rest_subject,
lib_plankton.http.enum_method.get, lib_plankton.http.enum_method.get,
_espe.api.full_path("/invite/read"), _espe.api.full_path("/invitation/read"),
{ {
/** /**
* @todo translation * @todo translation
@ -66,16 +64,6 @@ namespace _espe.api
"type": "integer", "type": "integer",
"description": "Ablaufzeitpunkt" "description": "Ablaufzeitpunkt"
}, },
"membership_number_changeable": {
"type": "boolean",
"nullable": false,
"description": "Mitgliedsnummer | änderbar"
},
"membership_number_value": {
"type": "string",
"nullable": true,
"description": "Mitgliedsnummer | Wert"
},
"name_changeable": { "name_changeable": {
"type": "boolean", "type": "boolean",
"nullable": false, "nullable": false,
@ -105,7 +93,7 @@ namespace _espe.api
"nullable": false, "nullable": false,
"type": "array", "type": "array",
"items": { "items": {
"type": "string", "type": "integer",
"nullable": false, "nullable": false,
}, },
"description": "Gruppen | Wert" "description": "Gruppen | Wert"
@ -113,8 +101,6 @@ namespace _espe.api
}, },
"required": [ "required": [
"expiry", "expiry",
"membership_number_mode",
"membership_number_value",
"name_mode", "name_mode",
"name_value", "name_value",
"email_address_mode", "email_address_mode",
@ -125,23 +111,21 @@ namespace _espe.api
}), }),
"restriction": () => restriction_logged_in, "restriction": () => restriction_logged_in,
"execution": () => ({"query_parameters": query_parameters, "input": input}) => { "execution": () => ({"query_parameters": query_parameters, "input": input}) => {
const invite_id : _espe.type.invite_id = parseInt(query_parameters["id"]); const invitation_id : _espe.type.invitation_id = parseInt(query_parameters["id"]);
return ( return (
_espe.service.invite.get_by_id(invite_id) _espe.service.invitation.get_by_id(invitation_id)
.then( .then(
(invite_object) => Promise.resolve({ (invitation_object) => Promise.resolve({
"status_code": 200, "status_code": 200,
"data": { "data": {
"key": invite_object.key, "key": invitation_object.key,
"expiry": invite_object.expiry, "expiry": invitation_object.expiry,
"membership_number_changeable": invite_object.membership_number_changeable, "name_changeable": invitation_object.name_changeable,
"membership_number_value": invite_object.membership_number_value, "name_value": invitation_object.name_value,
"name_changeable": invite_object.name_changeable, "email_address_changeable": invitation_object.email_address_changeable,
"name_value": invite_object.name_value, "email_address_value": invitation_object.email_address_value,
"email_address_changeable": invite_object.email_address_changeable, "groups_changeable": invitation_object.groups_changeable,
"email_address_value": invite_object.email_address_value, "groups_value": invitation_object.groups_value,
"groups_changeable": invite_object.groups_changeable,
"groups_value": invite_object.groups_value,
} }
}) })
) )

View file

@ -1,121 +0,0 @@
/*
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Backend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
*/
namespace _espe.api
{
/**
* @todo zeitliche Begrenzung?
*/
export function register_member_info(
rest_subject : lib_plankton.rest_http.type_rest
) : void
{
lib_plankton.rest_http.register<
int,
(
null
|
{
name_real_value : string;
name_real_index : int;
name_login : string;
email_address_veiled : (null | string);
email_address_nominal : string;
}
)
>(
rest_subject,
lib_plankton.http.enum_method.get,
_espe.api.full_path("/member/info/:id"),
{
"description": () => "gibt Angaben über ein Mitglied aus, die für die Registrierung verwendet werden dürfen",
"input_schema": () => ({
"type": "number",
"nullable": false,
}),
"output_schema": () => ({
"type": "object",
"nullable": false,
"additionalProperties": false,
"properties": {
"name_real_value": {
"type": "string",
"nullable": false,
},
"name_real_index": {
"type": "number",
"nullable": false,
},
"name_login": {
"type": "string",
"nullable": false,
},
"email_address_veiled": {
"type": "string",
"nullable": true,
},
"email_address_nominal": {
"type": "string",
"nullable": false,
},
},
"required": [
"name_real_value",
"name_real_index",
"name_login",
"email_address_veiled",
"email_address_nominal",
]
}),
"query_parameters": () => [
{
"name": "key",
"required": true,
"description": "Zugriffs-Schlüssel",
},
],
"restriction": () => restriction_verification(
stuff => parseInt(stuff.path_parameters["id"]),
stuff => stuff.query_parameters["key"]
),
"execution": () => async ({"path_parameters": path_parameters, "input": input}) => {
const member_id : _espe.type.member_id = parseInt(path_parameters["id"]);
const data : (
null
|
{
name_real_value : string;
name_real_index : int;
name_login : string;
email_address_veiled : (null | string);
email_address_nominal : string;
}
) = await _espe.service.member.info(member_id);
return Promise.resolve({
"status_code": (
(data === null)
? 409
: 200
),
"data": data
});
},
}
)
}
}

View file

@ -28,9 +28,7 @@ namespace _espe.api
{ {
id : int; id : int;
preview : { preview : {
membership_number : string; name : string;
name_real_value : string;
name_real_index : int;
}; };
} }
> >
@ -62,18 +60,10 @@ namespace _espe.api
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
"membership_number": { "name": {
"type": "string", "type": "string",
"nullable": false, "nullable": false,
}, },
"name_real_value": {
"type": "string",
"nullable": false,
},
"name_real_index": {
"type": "number",
"nullable": false,
},
}, },
"required": [ "required": [
"membership_number", "membership_number",

View file

@ -24,9 +24,8 @@ namespace _espe.api
{ {
lib_plankton.rest_http.register< lib_plankton.rest_http.register<
{ {
email_address_private : (null | string); email_address : (null | string);
groups ?: Array<string>; groups ?: Array<int>;
registered : boolean;
enabled : boolean; enabled : boolean;
}, },
null null
@ -40,7 +39,7 @@ namespace _espe.api
"nullable": false, "nullable": false,
"type": "object", "type": "object",
"properties": { "properties": {
"email_address_private": { "email_address": {
"nullable": true, "nullable": true,
"type": "string" "type": "string"
}, },
@ -52,10 +51,6 @@ namespace _espe.api
"nullable": false, "nullable": false,
} }
}, },
"registered": {
"nullable": false,
"type": "boolean"
},
"enabled": { "enabled": {
"nullable": false, "nullable": false,
"type": "boolean" "type": "boolean"
@ -63,8 +58,7 @@ namespace _espe.api
}, },
"additionalProperties": false, "additionalProperties": false,
"required": [ "required": [
"email_address_private", "email_address",
"registered",
"enabled", "enabled",
] ]
}), }),
@ -81,13 +75,12 @@ namespace _espe.api
await _espe.service.member.modify( await _espe.service.member.modify(
member_id, member_id,
{ {
"email_address_private": input.email_address_private, "email_address": input.email_address,
"groups": ( "groups": (
(input.groups === undefined) (input.groups === undefined)
? lib_plankton.pod.make_empty<Array<string>>() ? lib_plankton.pod.make_empty<Array<_espe.type.group_id>>()
: lib_plankton.pod.make_filled<Array<string>>(input.groups) : lib_plankton.pod.make_filled<Array<_espe.type.group_id>>(input.groups)
), ),
"registered": input.registered,
"enabled": input.enabled, "enabled": input.enabled,
} }
); );

View file

@ -1,153 +0,0 @@
/*
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Backend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
*/
namespace _espe.api
{
/**
*/
export function register_member_project(
rest_subject : lib_plankton.rest_http.type_rest
) : void
{
lib_plankton.rest_http.register<
{
membership_number : (null | string);
name_real_value : string;
email_address_private : (null | string);
groups ?: Array<string>;
notification_target_url_template ?: (null | string);
},
(
string
|
_espe.type.member_id
)
>(
rest_subject,
lib_plankton.http.enum_method.post,
_espe.api.full_path("/member/project"),
{
"description": () => "erstellt ein neues Mitglied und gibt die erzeugte ID aus",
"input_schema": () => ({
"type": "object",
"nullable": false,
"additionalProperties": false,
"properties": {
"membership_number": {
"type": "string",
"nullable": false,
"description": "Mitgliedsnummer"
},
"name_real_value": {
"type": "string",
"nullable": false,
"description": "Klarname"
},
"email_address_private": {
"type": "string",
"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,
"description": "Platz-Halter: id"
},
},
"required": [
"membership_number",
"name_real_value",
]
}),
"output_schema": () => ({
"type": "number",
"nullable": false,
}),
"restriction": () => restriction_logged_in,
"execution": () => async ({"input": input}) => {
if (input === null) {
return Promise.reject(new Error("impossible"));
}
else {
if (
(! _espe.conf.get().settings.misc.facultative_membership_number)
&&
(
(input.membership_number === null)
||
(input.membership_number === "")
)
) {
return Promise.resolve({
"status_code": 400,
"data": "membership number required"
});
}
else {
const member_id : _espe.type.member_id = await _espe.service.member.project(
{
"membership_number": input.membership_number,
"name_real_value": input.name_real_value,
"email_address_private": (
("email_address_private" in input)
? (
(input.email_address_private !== "")
? input.email_address_private
: null
)
: null
),
"groups": (input.groups ?? []),
}
);
if (! _espe.conf.get().settings.misc.auto_register) {
// do nothing
}
else {
// TODO: Werte in Konfiguration auslagern
await _espe.service.member.register(
member_id,
{
"email_use_veiled_address": false,
"email_use_nominal_address": false,
"email_redirect_to_private_address": false,
"password": null,
},
{
"notification_target_url_template": input.notification_target_url_template,
}
);
}
return Promise.resolve({
"status_code": 201,
"data": member_id
});
}
}
}
}
);
}
}

View file

@ -25,21 +25,11 @@ namespace _espe.api
lib_plankton.rest_http.register< lib_plankton.rest_http.register<
null, null,
{ {
membership_number : (null | string); name : string;
name_real_value : string; email_address : (null | string);
name_real_index : int; groups : Array<int>;
email_address_private : (null | string);
groups : Array<string>;
registered : boolean;
enabled : boolean; enabled : boolean;
email_use_veiled_address : boolean;
email_use_nominal_address : boolean;
email_redirect_to_private_address : boolean;
email_allow_sending : boolean;
password_set : boolean; password_set : boolean;
email_address_veiled : (null | string);
email_address_nominal : string;
name_login : string;
} }
>( >(
rest_subject, rest_subject,
@ -51,19 +41,11 @@ namespace _espe.api
"nullable": false, "nullable": false,
"type": "object", "type": "object",
"properties": { "properties": {
"membership_number": { "name": {
"nullable": true,
"type": "string"
},
"name_real_value": {
"nullable": false, "nullable": false,
"type": "string" "type": "string"
}, },
"name_real_index": { "email_address": {
"nullable": false,
"type": "number"
},
"email_address_private": {
"nullable": true, "nullable": true,
"type": "string" "type": "string"
}, },
@ -72,67 +54,21 @@ namespace _espe.api
"type": "array", "type": "array",
"items": { "items": {
"nullable": false, "nullable": false,
"type": "string" "type": "int"
} }
}, },
"registered": {
"nullable": false,
"type": "boolean"
},
"enabled": { "enabled": {
"nullable": false, "nullable": false,
"type": "boolean" "type": "boolean"
}, },
"email_use_veiled_address": {
"nullable": false,
"type": "boolean"
},
"email_use_nominal_address": {
"nullable": false,
"type": "boolean"
},
"email_redirect_to_private_address": {
"nullable": false,
"type": "boolean"
},
"email_allow_sending": {
"nullable": false,
"type": "boolean"
},
"password_set": {
"nullable": false,
"type": "boolean"
},
"email_address_veiled": {
"nullable": true,
"type": "string"
},
"email_address_nominal": {
"nullable": false,
"type": "string"
},
"name_login": {
"nullable": false,
"type": "string"
},
}, },
"additionalProperties": false, "additionalProperties": false,
"required": [ "required": [
"membership_number", "name",
"name_real_value", "email_address",
"name_real_index",
"email_address_private",
"groups", "groups",
"registered",
"enabled", "enabled",
"email_use_veiled_address",
"email_use_nominal_address",
"email_redirect_to_private_address",
"email_allow_sending",
"password_set", "password_set",
"email_address_veiled",
"email_address_nominal",
"name_login",
] ]
}), }),
"restriction": () => restriction_logged_in, "restriction": () => restriction_logged_in,
@ -142,21 +78,11 @@ namespace _espe.api
return Promise.resolve({ return Promise.resolve({
"status_code": 200, "status_code": 200,
"data": { "data": {
"membership_number": member_object.membership_number, "name": member_object.name,
"name_real_value": member_object.name_real_value, "email_address": member_object.email_address,
"name_real_index": member_object.name_real_index,
"email_address_private": member_object.email_address_private,
"groups": member_object.groups, "groups": member_object.groups,
"registered": member_object.registered,
"enabled": member_object.enabled, "enabled": member_object.enabled,
"email_use_veiled_address": member_object.email_use_veiled_address,
"email_use_nominal_address": member_object.email_use_nominal_address,
"email_redirect_to_private_address": member_object.email_redirect_to_private_address,
"email_allow_sending": member_object.email_allow_sending,
"password_set": (member_object.password_image !== null), "password_set": (member_object.password_image !== null),
"name_login": _espe.service.member.name_login(member_object),
"email_address_veiled": _espe.service.member.email_address_veiled(member_object),
"email_address_nominal": _espe.service.member.email_address_nominal(member_object),
}, },
}); });
} }

View file

@ -1,159 +0,0 @@
/*
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Backend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
*/
namespace _espe.api
{
/**
* @todo zeitliche Begrenzung?
*/
export function register_member_register(
rest_subject : lib_plankton.rest_http.type_rest
) : void
{
lib_plankton.rest_http.register<
{
email_use_veiled_address : boolean;
email_use_nominal_address : boolean;
email_redirect_to_private_address : boolean;
password : (null | string);
notification_target_url_template ?: (null | string);
},
Array<
{
incident : string;
details : Record<string, any>;
}
>
>(
rest_subject,
lib_plankton.http.enum_method.post,
_espe.api.full_path("/member/register/:id"),
{
"description": () => "nimmt zusätzliche Angaben eines Mitglieds entgegen",
"input_schema": () => ({
"type": "object",
"nullable": false,
"additionalProperties": false,
"properties": {
"email_use_veiled_address": {
"type": "boolean",
"nullable": false,
"description": "ob die nummern-basierte E-Mail-Adresse eingerichtet werden soll",
},
"email_use_nominal_address": {
"type": "boolean",
"nullable": false,
"description": "ob die namens-basierte E-Mail-Adresse eingerichtet werden soll",
},
"email_redirect_to_private_address": {
"type": "boolean",
"nullable": false,
"description": "ob auf die Partei-Adressen eingehende E-Mails zur privaten Adresse weitergeleitet werden sollen",
},
"password": {
"type": "string",
"nullable": true,
"description": "Passwort für alle Netz-Dienste",
},
"notification_target_url_template": {
"type": "string",
"nullable": true,
"description": "Platz-Halter: id"
},
},
"required": [
"email_use_veiled_address",
"email_use_nominal_address",
"email_redirect_to_private_address",
"password",
]
}),
"output_schema": () => ({
"nullable": false,
"type": "array",
"items": {
"nullable": false,
"type": "object",
"properties": {
"incident": {
"nullable": false,
"type": "string"
},
"details": {
"nullable": false,
"type": "object",
"properties": {},
"additionalProperties": {
"nullable": true
},
"required": []
},
},
"additionalProperties": false,
"required": [
"incident",
"details",
]
}
}),
"query_parameters": () => [
{
"name": "key",
"required": true,
"description": "Zugriffs-Schlüssel",
},
],
"restriction": () => restriction_verification(
stuff => parseInt(stuff.path_parameters["id"]),
stuff => stuff.query_parameters["key"]
),
"execution": () => ({"path_parameters": path_parameters, "input": input}) => {
if (input === null) {
return Promise.reject(new Error("impossible"));
}
else {
const member_id : _espe.type.member_id = parseInt(path_parameters["id"]);
return (
_espe.service.member.register(
member_id,
{
"email_use_veiled_address": input.email_use_veiled_address,
"email_use_nominal_address": input.email_use_nominal_address,
"email_redirect_to_private_address": input.email_redirect_to_private_address,
"password": input.password,
},
{
"notification_target_url_template": (input.notification_target_url_template ?? null),
}
)
.then(
flaws => Promise.resolve({
"status_code": (
(flaws.length <= 0)
? 200
: 409
),
"data": flaws
})
)
);
}
},
}
)
}
}

View file

@ -45,10 +45,7 @@ namespace _espe.api
} }
// member // member
{ {
_espe.api.register_member_project(rest_subject);
_espe.api.register_member_summon(rest_subject); _espe.api.register_member_summon(rest_subject);
_espe.api.register_member_info(rest_subject);
_espe.api.register_member_register(rest_subject);
_espe.api.register_member_list(rest_subject); _espe.api.register_member_list(rest_subject);
_espe.api.register_member_read(rest_subject); _espe.api.register_member_read(rest_subject);
_espe.api.register_member_modify(rest_subject); _espe.api.register_member_modify(rest_subject);
@ -59,13 +56,13 @@ namespace _espe.api
_espe.api.register_member_password_change_execute(rest_subject); _espe.api.register_member_password_change_execute(rest_subject);
} }
} }
// invite // invitation
{ {
_espe.api.register_invite_list(rest_subject); _espe.api.register_invitation_list(rest_subject);
_espe.api.register_invite_read(rest_subject); _espe.api.register_invitation_read(rest_subject);
_espe.api.register_invite_create(rest_subject); _espe.api.register_invitation_create(rest_subject);
_espe.api.register_invite_examine(rest_subject); _espe.api.register_invitation_examine(rest_subject);
_espe.api.register_invite_accept(rest_subject); _espe.api.register_invitation_accept(rest_subject);
} }
return rest_subject; return rest_subject;

View file

@ -0,0 +1,224 @@
/*
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Backend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
*/
namespace _espe.repository.group
{
/**
*/
var _store : (
null
|
lib_plankton.storage.type_store<
_espe.type.group_id,
Record<string, any>,
{},
lib_plankton.storage.type_sql_table_autokey_search_term,
Record<string, any>
>
) = null;
/**
*/
function get_store(
) : lib_plankton.storage.type_store<
_espe.type.group_id,
Record<string, any>,
{},
lib_plankton.storage.type_sql_table_autokey_search_term,
Record<string, any>
>
{
if (_store === null)
{
_store = lib_plankton.storage.sql_table_autokey_store(
{
"database_implementation": _espe.helpers.database_implementation(),
"table_name": "groups",
"key_name": "id",
}
);
}
else
{
// do nothing
}
return _store;
}
/**
*/
type type_dispersal = Record<string, any>;
/**
*/
function encode(
object : _espe.type.group_object
) : type_dispersal
{
return {
"name": object.name,
};
}
/**
*/
function decode(
dispersal : type_dispersal
) : _espe.type.group_object
{
return {
"name": dispersal.core_row["name"],
};
}
/**
* @todo optimize
*/
export async function list(
search_term : (null | string)
) : Promise<
Array<
{
id : _espe.type.group_id;
preview : {
name : string;
};
}
>
>
{
return (
(await get_store().search(null))
.filter(
({"key": key, "preview": preview}) => (
(
(search_term === null)
||
(search_term.length <= 1)
)
?
true
:
preview["name"].toLowerCase().includes(search_term.toLowerCase())
)
)
.map(
({"key": key, "preview": preview}) => ({
"id": key,
"preview": {
"name": preview["name"],
}
})
)
);
}
/**
*/
export async function read(
id : _espe.type.group_id
) : Promise<_espe.type.group_object>
{
const core_row : Record<string, any> = await get_store().read(id);
const dispersal : type_dispersal = {
"core_row": core_row,
};
return decode(dispersal);
}
/**
*/
export async function create(
value : _espe.type.group_object
) : Promise<_espe.type.group_id>
{
const dispersal : type_dispersal = encode(value);
// core
const id : _espe.type.group_id = await get_store().create(dispersal.core_row);
return id;
}
/**
* @todo replace groups smartly
*/
export async function update(
id : _espe.type.group_id,
value : _espe.type.group_object
) : Promise<void>
{
const dispersal : type_dispersal = encode(value);
// core
await get_store().update(id, dispersal.core_row);
}
/**
*/
export async function delete_(
id : _espe.type.group_id
) : Promise<void>
{
// core
await get_store().delete(id);
}
/**
*/
export async function dump(
) : Promise<
Array<
{
id : _espe.type.group_id;
object : _espe.type.group_object;
}
>
>
{
return (
Promise.all(
(await get_store().search(null))
.map(hit => hit.key)
.map(
id => (
read(id)
.then(
(object) => ({
"id": id,
"object": object
})
)
)
)
)
);
}
}

View file

@ -13,7 +13,7 @@ You should have received a copy of the GNU General Public License along with thi
<https://www.gnu.org/licenses/>. <https://www.gnu.org/licenses/>.
*/ */
namespace _espe.repository.invite namespace _espe.repository.invitation
{ {
/** /**
@ -33,7 +33,7 @@ namespace _espe.repository.invite
null null
| |
lib_plankton.storage.type_store< lib_plankton.storage.type_store<
_espe.type.invite_id, _espe.type.invitation_id,
Record<string, any>, Record<string, any>,
{}, {},
lib_plankton.storage.type_sql_table_autokey_search_term, lib_plankton.storage.type_sql_table_autokey_search_term,
@ -55,7 +55,7 @@ namespace _espe.repository.invite
*/ */
function get_core_store( function get_core_store(
) : lib_plankton.storage.type_store< ) : lib_plankton.storage.type_store<
_espe.type.invite_id, _espe.type.invitation_id,
Record<string, any>, Record<string, any>,
{}, {},
lib_plankton.storage.type_sql_table_autokey_search_term, lib_plankton.storage.type_sql_table_autokey_search_term,
@ -66,7 +66,7 @@ namespace _espe.repository.invite
_core_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": "invites", "table_name": "invitations",
"key_name": "id", "key_name": "id",
} }
); );
@ -87,8 +87,8 @@ namespace _espe.repository.invite
_group_chest = lib_plankton.storage.sql_table_common.chest( _group_chest = lib_plankton.storage.sql_table_common.chest(
{ {
"database_implementation": _espe.helpers.database_implementation(), "database_implementation": _espe.helpers.database_implementation(),
"table_name": "invite_groups", "table_name": "invitation_groups",
"key_names": ["invite_id","group_name"], "key_names": ["invitation_id","group_name"],
} }
); );
} }
@ -112,15 +112,13 @@ namespace _espe.repository.invite
/** /**
*/ */
function encode( function encode(
object : _espe.type.invite_object object : _espe.type.invitation_object
) : type_dispersal ) : type_dispersal
{ {
return { return {
"core_row": { "core_row": {
"key": object.key, "key": object.key,
"expiry": object.expiry, "expiry": object.expiry,
"membership_number_changeable": _espe.helpers.dbbool_encode(object.membership_number_changeable),
"membership_number_value": object.membership_number_value,
"name_changeable": _espe.helpers.dbbool_encode(object.name_changeable), "name_changeable": _espe.helpers.dbbool_encode(object.name_changeable),
"name_value": object.name_value, "name_value": object.name_value,
"email_address_changeable": _espe.helpers.dbbool_encode(object.email_address_changeable), "email_address_changeable": _espe.helpers.dbbool_encode(object.email_address_changeable),
@ -128,7 +126,7 @@ namespace _espe.repository.invite
"groups_changeable": _espe.helpers.dbbool_encode(object.groups_changeable), "groups_changeable": _espe.helpers.dbbool_encode(object.groups_changeable),
}, },
"group_rows": ( "group_rows": (
object.groups_value (object.groups_value ?? [])
.map( .map(
group => ({ group => ({
"group_name": group, "group_name": group,
@ -143,19 +141,17 @@ namespace _espe.repository.invite
*/ */
function decode( function decode(
dispersal : type_dispersal dispersal : type_dispersal
) : _espe.type.invite_object ) : _espe.type.invitation_object
{ {
return { return {
"key": dispersal.core_row["key"], "key": dispersal.core_row["key"],
"expiry": dispersal.core_row["expiry"], "expiry": dispersal.core_row["expiry"],
"membership_number_changeable": _espe.helpers.dbbool_decode(dispersal.core_row["membership_number_changeable"]),
"membership_number_value": dispersal.core_row["membership_number_value"],
"name_changeable": _espe.helpers.dbbool_decode(dispersal.core_row["name_changeable"]), "name_changeable": _espe.helpers.dbbool_decode(dispersal.core_row["name_changeable"]),
"name_value": dispersal.core_row["name_value"], "name_value": dispersal.core_row["name_value"],
"email_address_changeable": _espe.helpers.dbbool_decode(dispersal.core_row["email_address_changeable"]), "email_address_changeable": _espe.helpers.dbbool_decode(dispersal.core_row["email_address_changeable"]),
"email_address_value": dispersal.core_row["email_address_value"], "email_address_value": dispersal.core_row["email_address_value"],
"groups_changeable": _espe.helpers.dbbool_decode(dispersal.core_row["groups_changeable"]), "groups_changeable": _espe.helpers.dbbool_decode(dispersal.core_row["groups_changeable"]),
"groups_value": lib_plankton.list.sorted<string>( "groups_value": lib_plankton.list.sorted<_espe.type.group_id>(
dispersal.group_rows.map(row => row["group_name"]), dispersal.group_rows.map(row => row["group_name"]),
{ {
"compare_element": (group1, group2) => (group1 <= group2) "compare_element": (group1, group2) => (group1 <= group2)
@ -173,7 +169,7 @@ namespace _espe.repository.invite
) : Promise< ) : Promise<
Array< Array<
{ {
id : _espe.type.invite_id; id : _espe.type.invitation_id;
preview : { preview : {
name : string; name : string;
}; };
@ -207,14 +203,14 @@ namespace _espe.repository.invite
/** /**
*/ */
export async function read( export async function read(
id : _espe.type.invite_id id : _espe.type.invitation_id
) : Promise<_espe.type.invite_object> ) : Promise<_espe.type.invitation_object>
{ {
const core_row : Record<string, any> = await get_core_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( const group_hits : Array<{key : Record<string, any>; preview : Record<string, any>;}> = await get_group_chest().search(
{ {
"expression": "invite_id = $invite_id", "expression": "invitation_id = $invitation_id",
"arguments": {"invite_id": id} "arguments": {"invitation_id": id}
} }
); );
@ -234,13 +230,13 @@ namespace _espe.repository.invite
/** /**
*/ */
export async function create( export async function create(
value : _espe.type.invite_object value : _espe.type.invitation_object
) : Promise<_espe.type.invite_id> ) : Promise<_espe.type.invitation_id>
{ {
const dispersal : type_dispersal = encode(value); const dispersal : type_dispersal = encode(value);
// core // core
const id : _espe.type.invite_id = await get_core_store().create(dispersal.core_row); const id : _espe.type.invitation_id = await get_core_store().create(dispersal.core_row);
// groups // groups
for await (const group_row of dispersal.group_rows) { for await (const group_row of dispersal.group_rows) {
@ -262,14 +258,14 @@ namespace _espe.repository.invite
/** /**
*/ */
export async function delete_( export async function delete_(
id : _espe.type.invite_id id : _espe.type.invitation_id
) : Promise<void> ) : Promise<void>
{ {
// groups // groups
const hits : Array<{key : Array<any>; preview : Record<string, any>;}> = await get_group_chest().search( const hits : Array<{key : Array<any>; preview : Record<string, any>;}> = await get_group_chest().search(
{ {
"expression": "invite_id = $invite_id", "expression": "invitation_id = $invitation_id",
"arguments": {"invite_id": id} "arguments": {"invitation_id": id}
} }
); );
for (const hit of hits) { for (const hit of hits) {
@ -285,16 +281,16 @@ namespace _espe.repository.invite
* @todo optimize * @todo optimize
*/ */
export async function identify( export async function identify(
key : _espe.type.invite_key key : _espe.type.invitation_key
) : Promise<_espe.type.invite_id> ) : Promise<_espe.type.invitation_id>
{ {
const hits : Array<{id : _espe.type.invite_id; preview : any;}> = await list(key); const hits : Array<{id : _espe.type.invitation_id; preview : any;}> = await list(key);
return ( return (
(hits.length !== 1) (hits.length !== 1)
? ?
Promise.reject<_espe.type.invite_id>(new Error("not found")) Promise.reject<_espe.type.invitation_id>(new Error("not found"))
: :
Promise.resolve<_espe.type.invite_id>(hits[0].id) Promise.resolve<_espe.type.invitation_id>(hits[0].id)
); );
} }
@ -305,8 +301,8 @@ namespace _espe.repository.invite
) : Promise< ) : Promise<
Array< Array<
{ {
id : _espe.type.invite_id; id : _espe.type.invitation_id;
object : _espe.type.invite_object; object : _espe.type.invitation_object;
} }
> >
> >

View file

@ -88,7 +88,7 @@ namespace _espe.repository.member
{ {
"database_implementation": _espe.helpers.database_implementation(), "database_implementation": _espe.helpers.database_implementation(),
"table_name": "member_groups", "table_name": "member_groups",
"key_names": ["member_id","group_name"], "key_names": ["member_id","group_id"],
} }
); );
} }
@ -117,16 +117,9 @@ namespace _espe.repository.member
{ {
return { return {
"core_row": { "core_row": {
"membership_number": object.membership_number, "name": object.name,
"name_real_value": object.name_real_value, "email_address": object.email_address,
"name_real_index": object.name_real_index,
"email_address_private": object.email_address_private,
"registered": _espe.helpers.dbbool_encode(object.registered),
"enabled": _espe.helpers.dbbool_encode(object.enabled), "enabled": _espe.helpers.dbbool_encode(object.enabled),
"email_use_veiled_address": _espe.helpers.dbbool_encode(object.email_use_veiled_address),
"email_use_nominal_address": _espe.helpers.dbbool_encode(object.email_use_nominal_address),
"email_redirect_to_private_address": _espe.helpers.dbbool_encode(object.email_redirect_to_private_address),
"email_allow_sending": _espe.helpers.dbbool_encode(object.email_allow_sending),
"password_image": object.password_image, "password_image": object.password_image,
"password_change_last_attempt": object.password_change_last_attempt, "password_change_last_attempt": object.password_change_last_attempt,
"password_change_token": object.password_change_token, "password_change_token": object.password_change_token,
@ -135,7 +128,7 @@ namespace _espe.repository.member
object.groups object.groups
.map( .map(
group => ({ group => ({
"group_name": group, "group_id": group,
}) })
) )
) )
@ -150,22 +143,15 @@ namespace _espe.repository.member
) : _espe.type.member_object ) : _espe.type.member_object
{ {
return { return {
"membership_number": dispersal.core_row["membership_number"], "name": dispersal.core_row["name"],
"name_real_value": dispersal.core_row["name_real_value"], "email_address": dispersal.core_row["email_address"],
"name_real_index": dispersal.core_row["name_real_index"], "groups": lib_plankton.list.sorted<_espe.type.group_id>(
"email_address_private": dispersal.core_row["email_address_private"], dispersal.group_rows.map(row => row["group_id"]),
"groups": lib_plankton.list.sorted<string>(
dispersal.group_rows.map(row => row["group_name"]),
{ {
"compare_element": (group1, group2) => (group1 <= group2) "compare_element": (group1, group2) => (group1 <= group2)
} }
), ),
"registered": _espe.helpers.dbbool_decode(dispersal.core_row["registered"]),
"enabled": _espe.helpers.dbbool_decode(dispersal.core_row["enabled"]), "enabled": _espe.helpers.dbbool_decode(dispersal.core_row["enabled"]),
"email_use_veiled_address": _espe.helpers.dbbool_decode(dispersal.core_row["email_use_veiled_address"]),
"email_use_nominal_address": _espe.helpers.dbbool_decode(dispersal.core_row["email_use_nominal_address"]),
"email_redirect_to_private_address": _espe.helpers.dbbool_decode(dispersal.core_row["email_redirect_to_private_address"]),
"email_allow_sending": _espe.helpers.dbbool_decode(dispersal.core_row["email_allow_sending"]),
"password_image": dispersal.core_row["password_image"], "password_image": dispersal.core_row["password_image"],
"password_change_last_attempt": dispersal.core_row["password_change_last_attempt"], "password_change_last_attempt": dispersal.core_row["password_change_last_attempt"],
"password_change_token": dispersal.core_row["password_change_token"], "password_change_token": dispersal.core_row["password_change_token"],
@ -183,9 +169,7 @@ namespace _espe.repository.member
{ {
id : _espe.type.member_id; id : _espe.type.member_id;
preview : { preview : {
membership_number : string; name : string;
name_real_value : string;
name_real_index : int;
}; };
} }
> >
@ -200,21 +184,17 @@ namespace _espe.repository.member
|| ||
(search_term.length <= 1) (search_term.length <= 1)
) )
? true ?
: ( true
preview["membership_number"].toLowerCase().includes(search_term.toLowerCase()) :
|| preview["name"].toLowerCase().includes(search_term.toLowerCase())
preview["name_real_value"].toLowerCase().includes(search_term.toLowerCase())
)
) )
) )
.map( .map(
({"key": key, "preview": preview}) => ({ ({"key": key, "preview": preview}) => ({
"id": key, "id": key,
"preview": { "preview": {
"membership_number": preview["membership_number"], "name": preview["name"],
"name_real_value": preview["name_real_value"],
"name_real_index": preview["name_real_index"],
} }
}) })
) )
@ -240,7 +220,7 @@ namespace _espe.repository.member
"core_row": core_row, "core_row": core_row,
"group_rows": group_hits.map( "group_rows": group_hits.map(
hit => ({ hit => ({
"group_name": hit.preview["group_name"] "group_id": hit.preview["group_id"]
}) })
), ),
}; };
@ -265,7 +245,7 @@ namespace _espe.repository.member
await get_group_chest().write( await get_group_chest().write(
[ [
id, id,
group_row["group_name"], group_row["group_id"],
], ],
{ {
"_dummy": null, "_dummy": null,
@ -293,11 +273,10 @@ namespace _espe.repository.member
// groups // groups
const hits : Array<{key : Array<any>; preview : Record<string, any>;}> = await get_group_chest().search( const hits : Array<{key : Array<any>; preview : Record<string, any>;}> = await get_group_chest().search(
{ {
"expression": "member_id = $member_id", "expression": "group_id = $group_id",
"arguments": {"member_id": id} "arguments": {"member_id": id}
} }
); );
lib_plankton.log.info("update_hit", hits);
for (const hit of hits) { for (const hit of hits) {
await get_group_chest().delete(hit.key); await get_group_chest().delete(hit.key);
} }
@ -305,7 +284,7 @@ namespace _espe.repository.member
await get_group_chest().write( await get_group_chest().write(
[ [
id, id,
group_row["group_name"], group_row["group_id"],
], ],
{ {
"_dummy": null, "_dummy": null,

View file

@ -1,108 +0,0 @@
/*
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Backend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
*/
namespace _espe.repository.name_index
{
/**
*/
var _chest : (
null
|
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>
>
) = null;
/**
*/
function get_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>
>
{
if (_chest === null) {
_chest = lib_plankton.storage.sql_table_common.chest(
{
"database_implementation": _espe.helpers.database_implementation(),
"table_name": "name_indices",
"key_names": ["name_image"],
}
);
}
else {
// do nothing
}
return _chest;
}
/**
*/
async function get_name_image(
name : string
) : Promise<string>
{
return (
(! _espe.conf.get().settings.name_index.veil)
? name
: await lib_plankton.sha256.get(
lib_plankton.json.encode(name),
(_espe.conf.get().settings.name_index.salt ?? undefined)
)
);
}
/**
*/
export async function read(
name : string
) : Promise<int>
{
const name_image : string = await get_name_image(name);
let row : Record<string, any>;
try {
row = await get_chest().read([name_image]);
return row["index"];
}
catch (error) {
return 0;
}
}
/**
*/
export async function write(
name : string,
index : int
) : Promise<void>
{
const name_image : string = await get_name_image(name);
await get_chest().write([name_image], {"index": index});
}
}

View file

@ -19,8 +19,15 @@ namespace _espe.sample
/** /**
*/ */
type type_data = { type type_data = {
groups : Array<
{
id : int;
name : string;
}
>;
admins : Array< admins : Array<
{ {
id : int;
name : string; name : string;
email_address : (null | string); email_address : (null | string);
password : string; password : string;
@ -28,22 +35,21 @@ namespace _espe.sample
>; >;
members : Array< members : Array<
{ {
membership_number : string; id : int;
name_real : string; name : string;
email_address_private : (null | string); email_address : (null | string);
groups : Array<string>; groups : Array<int>;
} }
>; >;
invites : Array< invitations : Array<
{ {
membership_number_changeable : boolean; id : int;
membership_number_value : string;
name_changeable : boolean; name_changeable : boolean;
name_value : string; name_value : string;
email_address_changeable : boolean; email_address_changeable : boolean;
email_address_value : (null | string); email_address_value : (null | string);
groups_changeable : boolean; groups_changeable : boolean;
groups_value : Array<string>; groups_value : Array<int>;
} }
>; >;
}; };
@ -55,9 +61,23 @@ namespace _espe.sample
data : type_data data : type_data
) : Promise<void> ) : Promise<void>
{ {
const track_groups : Map<int, _espe.type.group_id> = new Map<int, _espe.type.group_id>();
// groups
{
for (const group_raw of data.groups)
{
const group_id : _espe.type.group_id = await _espe.service.group.add(
{
"name": group_raw.name,
}
);
track_groups.set(group_raw.id, group_id);
}
}
// admins // admins
{ {
for (const admin_raw of data.admins) { for (const admin_raw of data.admins)
{
const admin_id : _espe.type.admin_id = await _espe.service.admin.add( const admin_id : _espe.type.admin_id = await _espe.service.admin.add(
admin_raw.name, admin_raw.name,
admin_raw.email_address, admin_raw.email_address,
@ -67,13 +87,13 @@ namespace _espe.sample
} }
// members // members
{ {
for (const member_raw of data.members) { for (const member_raw of data.members)
{
const member_id : _espe.type.member_id = await _espe.service.member.project( const member_id : _espe.type.member_id = await _espe.service.member.project(
{ {
"membership_number": member_raw.membership_number, "name": member_raw.name,
"name_real_value": member_raw.name_real, "email_address": member_raw.email_address,
"email_address_private": member_raw.email_address_private, "groups": member_raw.groups.map(track_groups.get),
"groups": member_raw.groups
}, },
{ {
"silent": true, "silent": true,
@ -84,19 +104,18 @@ namespace _espe.sample
* @todo passwords * @todo passwords
*/ */
} }
// invites // invitations
{ {
for (const invite_raw of data.invites) { for (const invitation_raw of data.invitations)
const result : {id : _espe.type.invite_id; key : _espe.type.invite_key;} = await _espe.service.invite.create( {
const result : {id : _espe.type.invitation_id; key : _espe.type.invitation_key;} = await _espe.service.invitation.create(
{ {
"membership_number_changeable": invite_raw.membership_number_changeable, "name_changeable": invitation_raw.name_changeable,
"membership_number_value": invite_raw.membership_number_value, "name_value": invitation_raw.name_value,
"name_changeable": invite_raw.name_changeable, "email_address_changeable": invitation_raw.email_address_changeable,
"name_value": invite_raw.name_value, "email_address_value": invitation_raw.email_address_value,
"email_address_changeable": invite_raw.email_address_changeable, "groups_changeable": invitation_raw.groups_changeable,
"email_address_value": invite_raw.email_address_value, "groups_value": invitation_raw.groups_value.map(track_groups.get),
"groups_changeable": invite_raw.groups_changeable,
"groups_value": invite_raw.groups_value,
} }
); );
} }

View file

@ -13,19 +13,16 @@ You should have received a copy of the GNU General Public License along with thi
<https://www.gnu.org/licenses/>. <https://www.gnu.org/licenses/>.
*/ */
namespace _espe.service.name_index namespace _espe.service.group
{ {
/** /**
*/ */
export async function next( export function add(
name : string group_object : _espe.type.group_object
) : Promise<int> ) : Promise<_espe.type.group_id>
{ {
const current : int = await _espe.repository.name_index.read(name); return _espe.repository.group.create(group_object);
const result : int = (current + 1);
await _espe.repository.name_index.write(name, result);
return result;
} }
} }

View file

@ -13,7 +13,7 @@ You should have received a copy of the GNU General Public License along with thi
<https://www.gnu.org/licenses/>. <https://www.gnu.org/licenses/>.
*/ */
namespace _espe.service.invite namespace _espe.service.invitation
{ {
/** /**
@ -22,16 +22,16 @@ namespace _espe.service.invite
) : Promise< ) : Promise<
Array< Array<
{ {
id : _espe.type.invite_id; id : _espe.type.invitation_id;
key : _espe.type.invite_key; key : _espe.type.invitation_key;
expiry : (null | int); expiry : (null | int);
name_value : string; name_value : (null | string);
} }
> >
> >
{ {
return ( return (
_espe.repository.invite.dump() _espe.repository.invitation.dump()
.then( .then(
entries => Promise.resolve( entries => Promise.resolve(
entries.map( entries.map(
@ -52,8 +52,6 @@ namespace _espe.service.invite
*/ */
export async function create( export async function create(
{ {
"membership_number_changeable": membership_number_changeable,
"membership_number_value": membership_number_value,
"name_changeable": name_changeable, "name_changeable": name_changeable,
"name_value": name_value, "name_value": name_value,
"email_address_changeable": email_address_changeable, "email_address_changeable": email_address_changeable,
@ -61,14 +59,12 @@ namespace _espe.service.invite
"groups_changeable": groups_changeable, "groups_changeable": groups_changeable,
"groups_value": groups_value, "groups_value": groups_value,
} : { } : {
membership_number_changeable : boolean;
membership_number_value : (null | string);
name_changeable : boolean; name_changeable : boolean;
name_value : string; name_value : string;
email_address_changeable : boolean; email_address_changeable : boolean;
email_address_value : (null | string); email_address_value : (null | string);
groups_changeable : boolean; groups_changeable : boolean;
groups_value : Array<string>; groups_value : Array<_espe.type.group_id>;
}, },
{ {
"expiry": expiry = -1, "expiry": expiry = -1,
@ -80,7 +76,7 @@ namespace _espe.service.invite
send_immediatly ?: boolean; send_immediatly ?: boolean;
} = { } = {
} }
) : Promise<{id : _espe.type.invite_id; key : _espe.type.invite_key}> ) : Promise<{id : _espe.type.invitation_id; key : _espe.type.invitation_key}>
{ {
/** /**
* @todo outsource to conf * @todo outsource to conf
@ -89,7 +85,7 @@ namespace _espe.service.invite
/** /**
* @todo proper salt * @todo proper salt
*/ */
const invite_key : _espe.type.invite_key = lib_plankton.sha256.get( const invitation_key : _espe.type.invitation_key = lib_plankton.sha256.get(
( (
name_value name_value
+ +
@ -99,8 +95,8 @@ namespace _espe.service.invite
), ),
"secret" "secret"
); );
const invite_object : _espe.type.invite_object = { const invitation_object : _espe.type.invitation_object = {
"key": invite_key, "key": invitation_key,
"expiry": ( "expiry": (
((expiry !== null) && (expiry < 0)) ((expiry !== null) && (expiry < 0))
? ?
@ -108,8 +104,6 @@ namespace _espe.service.invite
: :
expiry expiry
), ),
"membership_number_changeable": membership_number_changeable,
"membership_number_value": membership_number_value,
"name_changeable": name_changeable, "name_changeable": name_changeable,
"name_value": name_value, "name_value": name_value,
"email_address_changeable": email_address_changeable, "email_address_changeable": email_address_changeable,
@ -117,7 +111,7 @@ namespace _espe.service.invite
"groups_changeable": groups_changeable, "groups_changeable": groups_changeable,
"groups_value": groups_value, "groups_value": groups_value,
}; };
const invite_id : _espe.type.invite_id = await _espe.repository.invite.create(invite_object); const invitation_id : _espe.type.invitation_id = await _espe.repository.invitation.create(invitation_object);
// send link // send link
{ {
if (! send_immediatly) if (! send_immediatly)
@ -139,7 +133,7 @@ namespace _espe.service.invite
) )
{ {
lib_plankton.log._warning( lib_plankton.log._warning(
"espe.service.invite.create.email.condition_unmet", "espe.service.invitation.create.email.condition_unmet",
{ {
"details": { "details": {
"provided_address": email_address_value, "provided_address": email_address_value,
@ -153,7 +147,7 @@ namespace _espe.service.invite
const url : (null | string) = _espe.helpers.frontend_url_get( const url : (null | string) = _espe.helpers.frontend_url_get(
notification_target_url_template, notification_target_url_template,
{ {
"key": invite_key, "key": invitation_key,
} }
); );
try { try {
@ -175,7 +169,7 @@ namespace _espe.service.invite
catch (error) catch (error)
{ {
lib_plankton.log._error( lib_plankton.log._error(
"espe.service.invite.create.email.could_not_be_sent", "espe.service.invitation.create.email.could_not_be_sent",
{ {
"details": { "details": {
"provided_address": email_address_value, "provided_address": email_address_value,
@ -188,8 +182,8 @@ namespace _espe.service.invite
} }
} }
return { return {
"id": invite_id, "id": invitation_id,
"key": invite_key, "key": invitation_key,
}; };
} }
@ -197,23 +191,23 @@ namespace _espe.service.invite
/** /**
*/ */
export function get_by_id( export function get_by_id(
id : _espe.type.invite_id id : _espe.type.invitation_id
) : Promise<_espe.type.invite_object> ) : Promise<_espe.type.invitation_object>
{ {
return _espe.repository.invite.read(id) return _espe.repository.invitation.read(id)
} }
/** /**
*/ */
function get_by_key( function get_by_key(
key : _espe.type.invite_key key : _espe.type.invitation_key
) : Promise<_espe.type.invite_object> ) : Promise<_espe.type.invitation_object>
{ {
return ( return (
_espe.repository.invite.identify(key) _espe.repository.invitation.identify(key)
.then( .then(
(id) => _espe.repository.invite.read(id) (id) => _espe.repository.invitation.read(id)
) )
); );
} }
@ -222,26 +216,26 @@ namespace _espe.service.invite
/** /**
*/ */
export async function examine( export async function examine(
key : _espe.type.invite_key key : _espe.type.invitation_key
) : Promise<_espe.type.invite_object> ) : Promise<_espe.type.invitation_object>
{ {
let invite_object : (null | _espe.type.invite_object); let invitation_object : (null | _espe.type.invitation_object);
try { try {
invite_object = await get_by_key(key); invitation_object = await get_by_key(key);
} }
catch (error) { catch (error) {
invite_object = null; invitation_object = null;
} }
if (invite_object === null) { if (invitation_object === null) {
return Promise.reject(new Error("not found")) return Promise.reject(new Error("not found"))
} }
else { else {
const now : int = lib_plankton.base.get_current_timestamp(true); const now : int = lib_plankton.base.get_current_timestamp(true);
if ((invite_object.expiry !== null) && (invite_object.expiry < now)) { if ((invitation_object.expiry !== null) && (invitation_object.expiry < now)) {
return Promise.reject(new Error("expired")); return Promise.reject(new Error("expired"));
} }
else { else {
return Promise.resolve(invite_object); return Promise.resolve(invitation_object);
} }
} }
} }
@ -250,11 +244,10 @@ namespace _espe.service.invite
/** /**
*/ */
export async function accept( export async function accept(
key : _espe.type.invite_key, key : _espe.type.invitation_key,
data : { data : {
membership_number : (null | string);
groups : Array<string>;
name : (null | string); name : (null | string);
groups : (null | Array<_espe.type.group_id>);
email_address : (null | string); email_address : (null | string);
password : (null | string); password : (null | string);
} }
@ -268,13 +261,13 @@ namespace _espe.service.invite
> >
> >
{ {
const invite_id : _espe.type.invite_id = await _espe.repository.invite.identify(key); const invitation_id : _espe.type.invitation_id = await _espe.repository.invitation.identify(key);
/** /**
* might throw, but that's fine, since caught and handled in the API action * might throw, but that's fine, since caught and handled in the API action
*/ */
const invite_object : _espe.type.invite_object = await _espe.repository.invite.read(invite_id); const invitation_object : _espe.type.invitation_object = await _espe.repository.invitation.read(invitation_id);
const now : int = lib_plankton.base.get_current_timestamp(true); const now : int = lib_plankton.base.get_current_timestamp(true);
if ((invite_object.expiry !== null) && (invite_object.expiry < now)) if ((invitation_object.expiry !== null) && (invitation_object.expiry < now))
{ {
return Promise.reject(new Error("expired")); return Promise.reject(new Error("expired"));
} }
@ -306,51 +299,57 @@ namespace _espe.service.invite
} }
else else
{ {
const member_id : _espe.type.member_id = await _espe.service.member.add( if (
{ (invitation_object.name_value === null)
"membership_number": ( &&
invite_object.membership_number_changeable (data.name === null)
? )
data.membership_number {
: throw (new Error("no name provided"));
invite_object.membership_number_value }
), else
"name_real_value": ( {
( const member_id : _espe.type.member_id = await _espe.service.member.add(
invite_object.name_changeable {
&& "name": (
(data.name !== null) (
) invitation_object.name_changeable
? ?
data.name data.name
: :
invite_object.name_value invitation_object.name_value
), ) as string
"email_address_private": ( ),
( "email_address": (
invite_object.email_address_changeable (
&& invitation_object.email_address_changeable
(data.email_address !== null) &&
) (data.email_address !== null)
? )
data.email_address ?
: data.email_address
invite_object.email_address_value :
), invitation_object.email_address_value
"groups": ( ),
invite_object.groups_changeable "groups": (
? (
data.groups invitation_object.groups_changeable
: ?
invite_object.groups_value data.groups
), :
"password": password, invitation_object.groups_value
} )
); ??
[]
),
"password": password,
}
);
await _espe.repository.invite.delete_(invite_id); await _espe.repository.invitation.delete_(invitation_id);
return []; return [];
}
} }
} }
} }

View file

@ -79,7 +79,7 @@ namespace _espe.service.member
"{{object}}{{extension}}", "{{object}}{{extension}}",
{ {
"object": lib_plankton.call.convey( "object": lib_plankton.call.convey(
object.name_real_value, object.name,
[ [
(x : string) => x.toLowerCase(), (x : string) => x.toLowerCase(),
(x : string) => x.replace(new RegExp(" ", "g"), "."), (x : string) => x.replace(new RegExp(" ", "g"), "."),
@ -90,11 +90,7 @@ namespace _espe.service.member
(x : string) => x.replace(new RegExp("[^0-9a-z-\.]", "g"), "_"), (x : string) => x.replace(new RegExp("[^0-9a-z-\.]", "g"), "_"),
] ]
), ),
"extension": ( "extension": "",
(object.name_real_index <= 1)
? ""
: ("." + object.name_real_index.toFixed(0))
),
} }
); );
} }
@ -107,46 +103,7 @@ namespace _espe.service.member
object : _espe.type.member_object object : _espe.type.member_object
) : string ) : string
{ {
return object.name_real_value; return object.name;
}
/**
* ermittelt die verschleierte E-Mail-Adresse des Mitglieds
*/
export function email_address_veiled(
object : _espe.type.member_object
) : (null | string)
{
return (
(object.membership_number === null)
? null
: lib_plankton.string.coin(
"{{prefix}}{{membership_number}}@{{domain}}",
{
"prefix": _espe.conf.get().settings.misc.prefix_for_veiled_email_addresses,
"membership_number": object.membership_number,
"domain": _espe.conf.get().settings.organisation.domain,
}
)
);
}
/**
* ermittelt die namentliche E-Mail-Adresse des Mitglieds
*/
export function email_address_nominal(
object : _espe.type.member_object
) : string
{
return lib_plankton.string.coin(
"{{user}}@{{domain}}",
{
"user": name_login(object),
"domain": _espe.conf.get().settings.organisation.domain,
}
);
} }
@ -157,15 +114,7 @@ namespace _espe.service.member
object : _espe.type.member_object object : _espe.type.member_object
) : (null | string) ) : (null | string)
{ {
return ( return object.email_address;
object.email_use_nominal_address
? email_address_nominal(object)
: (
object.email_use_veiled_address
? email_address_veiled(object)
: object.email_address_private
)
);
} }
@ -207,13 +156,15 @@ namespace _espe.service.member
// do nothing // do nothing
} }
else { else {
if (member_object.email_address_private === null) { if (member_object.email_address === null)
{
// do nothing // do nothing
} }
else { else
{
await _espe.helpers.email_send( await _espe.helpers.email_send(
[ [
member_object.email_address_private, member_object.email_address,
], ],
lib_plankton.string.coin( lib_plankton.string.coin(
"{{head}} | {{core}}", "{{head}} | {{core}}",
@ -277,9 +228,7 @@ namespace _espe.service.member
{ {
id : _espe.type.member_id; id : _espe.type.member_id;
preview : { preview : {
membership_number : string; name : string;
name_real_value : string;
name_real_index : int;
}; };
} }
> >
@ -306,10 +255,9 @@ namespace _espe.service.member
*/ */
export async function project( export async function project(
data : { data : {
membership_number : (null | string); name : string;
name_real_value : string; email_address : (null | string);
email_address_private : (null | string); groups : Array<_espe.type.group_id>;
groups : Array<string>;
}, },
{ {
"silent": silent = false, "silent": silent = false,
@ -319,28 +267,22 @@ namespace _espe.service.member
} }
) : Promise<_espe.type.member_id> ) : Promise<_espe.type.member_id>
{ {
const name_real_index : int = await _espe.service.name_index.next(data.name_real_value);
const object : _espe.type.member_object = { const object : _espe.type.member_object = {
"membership_number": data.membership_number, "name": data.name,
"name_real_value": data.name_real_value, "email_address": data.email_address,
"name_real_index": name_real_index, "groups": data.groups,
"email_address_private": data.email_address_private,
"registered": false,
"enabled": true, "enabled": true,
"email_use_veiled_address": false,
"email_use_nominal_address": false,
"email_redirect_to_private_address": false,
"email_allow_sending": false,
"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);
if (silent) { if (silent)
{
// do nothing // do nothing
} }
else { else
{
signal_change(); signal_change();
} }
return id; return id;
@ -352,10 +294,9 @@ namespace _espe.service.member
*/ */
export async function add( export async function add(
data : { data : {
membership_number : (null | string); name : string;
name_real_value : string; email_address : (null | string);
email_address_private : (null | string); groups : Array<_espe.type.group_id>;
groups : Array<string>;
password : string; password : string;
}, },
{ {
@ -366,22 +307,14 @@ namespace _espe.service.member
} }
) : Promise<_espe.type.member_id> ) : Promise<_espe.type.member_id>
{ {
const name_real_index : int = await _espe.service.name_index.next(data.name_real_value);
const object : _espe.type.member_object = { const object : _espe.type.member_object = {
"membership_number": data.membership_number, "name": data.name,
"name_real_value": data.name_real_value, "email_address": data.email_address,
"name_real_index": name_real_index, "groups": data.groups,
"email_address_private": data.email_address_private,
"registered": false,
"enabled": true, "enabled": true,
"email_use_veiled_address": false,
"email_use_nominal_address": false,
"email_redirect_to_private_address": false,
"email_allow_sending": false,
"password_image": await password_image(data.password), "password_image": await password_image(data.password),
"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);
if (silent) if (silent)
@ -406,10 +339,12 @@ namespace _espe.service.member
{ {
_espe.helpers.frontend_url_check(); _espe.helpers.frontend_url_check();
const member_object : _espe.type.member_object = await get(member_id); const member_object : _espe.type.member_object = await get(member_id);
if (member_object.email_address_private === null) { if (member_object.email_address === null)
{
return null; return null;
} }
else { else
{
const url : (null | string) = _espe.helpers.frontend_url_get( const url : (null | string) = _espe.helpers.frontend_url_get(
url_template, url_template,
{ {
@ -422,7 +357,7 @@ namespace _espe.service.member
else { else {
await _espe.helpers.email_send( await _espe.helpers.email_send(
[ [
member_object.email_address_private, member_object.email_address,
], ],
lib_plankton.string.coin( lib_plankton.string.coin(
"{{head}} | {{core}}", "{{head}} | {{core}}",
@ -450,176 +385,31 @@ namespace _espe.service.member
} }
/**
* gibt Daten über ein Mitglied aus, die relevant für die Registrierung sind
*/
export async function info(
member_id : _espe.type.member_id
) : Promise<
(
null
|
{
name_real_value : string;
name_real_index : int;
name_login : string;
email_address_veiled : (null | string);
email_address_nominal : string;
}
)
>
{
const member_object : _espe.type.member_object = await _espe.repository.member.read(member_id);
if (! member_object.registered) {
return {
"name_real_value": member_object.name_real_value,
"name_real_index": member_object.name_real_index,
"name_login": name_login(member_object),
"email_address_veiled": email_address_veiled(member_object),
"email_address_nominal": email_address_nominal(member_object),
};
}
else {
return null;
}
}
/**
* führt die Registrierung für ein Mitglied durch
*/
export async function register(
member_id : _espe.type.member_id,
data : {
email_use_veiled_address : boolean;
email_use_nominal_address : boolean;
email_redirect_to_private_address : boolean;
password : (null | string);
},
options : {
notification_target_url_template ?: (null | string);
} = {}
) : Promise<Array<{incident : string; details : Record<string, any>;}>>
{
options = Object.assign(
{
"notification_target_url_template": null,
},
options
);
const member_object : _espe.type.member_object = await get(member_id);
let flaws : Array<{incident : string; details : Record<string, any>;}> = [];
let password_value : string;
let password_generated : boolean;
if (member_object.registered) {
flaws.push({"incident": "already_registered", "details": {}});
password_value = "";
password_generated = false;
}
else {
if (
(data.password !== null)
&&
(data.password !== "")
) {
flaws = flaws.concat(
validate_password(data.password)
.map(flaw => ({"incident": ("password_" + flaw.incident), "details": flaw.details}))
);
password_value = data.password;
password_generated = false;
}
else {
password_value = generate_password();
password_generated = true;
}
}
if (flaws.length > 0) {
// do nothing
}
else {
member_object.email_use_veiled_address = data.email_use_veiled_address;
member_object.email_use_nominal_address = data.email_use_nominal_address;
member_object.email_redirect_to_private_address = data.email_redirect_to_private_address;
member_object.password_image = await password_image(password_value);
member_object.registered = true;
await _espe.repository.member.update(member_id, member_object);
signal_change();
{
const url : (null | string) = (
(
(options.notification_target_url_template === undefined)
||
(options.notification_target_url_template === null)
)
? null
: _espe.helpers.frontend_url_get(
options.notification_target_url_template,
{
"id": member_id.toFixed(0),
}
)
);
/*await*/ _espe.service.admin.notify_all(
lib_plankton.string.coin(
"{{head}} | {{core}}",
{
"head": _espe.conf.get().settings.organisation.name,
"core": lib_plankton.translate.get("email.registration.subject"),
}
),
lib_plankton.string.coin(
lib_plankton.translate.get("email.registration.body"),
{
"name_display": name_display(member_object),
"url": (url ?? "?"),
}
)
);
}
/*await*/ send_activation_email(member_object, {"password": password_generated ? password_value : null});
}
return Promise.resolve(flaws);
}
/** /**
* ändert bestimmte Daten des Mitglied * ändert bestimmte Daten des Mitglied
*/ */
export async function modify( export async function modify(
member_id : _espe.type.member_id, member_id : _espe.type.member_id,
data : { data : {
email_address_private : (null | string); email_address : (null | string);
registered : boolean;
enabled : boolean; enabled : boolean;
groups : lib_plankton.pod.type_pod<Array<string>>; groups : lib_plankton.pod.type_pod<Array<_espe.type.group_id>>;
} }
) : Promise<void> ) : Promise<void>
{ {
const member_object_old : _espe.type.member_object = await get(member_id); const member_object_old : _espe.type.member_object = await get(member_id);
const member_object_new : _espe.type.member_object = { const member_object_new : _espe.type.member_object = {
"membership_number": member_object_old.membership_number, "name": member_object_old.name,
"name_real_value": member_object_old.name_real_value, "email_address": data.email_address,
"name_real_index": member_object_old.name_real_index, "groups": (
"email_address_private": data.email_address_private, lib_plankton.pod.is_filled<Array<_espe.type.group_id>>(data.groups)
"registered": data.registered, ? lib_plankton.pod.cull<Array<_espe.type.group_id>>(data.groups)
: member_object_old.groups
),
"enabled": data.enabled, "enabled": data.enabled,
"email_use_veiled_address": member_object_old.email_use_veiled_address,
"email_use_nominal_address": member_object_old.email_use_nominal_address,
"email_redirect_to_private_address": member_object_old.email_redirect_to_private_address,
"email_allow_sending": member_object_old.email_allow_sending,
"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();
@ -655,15 +445,13 @@ namespace _espe.service.member
(await _espe.repository.member.dump()) (await _espe.repository.member.dump())
.filter( .filter(
member_entry => ( member_entry => (
member_entry.object.registered
&&
member_entry.object.enabled member_entry.object.enabled
&& &&
( (
( (
(! (member_entry.object.email_address_private === null)) (! (member_entry.object.email_address === null))
&& &&
(member_entry.object.email_address_private === identifier) (member_entry.object.email_address === identifier)
) )
|| ||
(name_login(member_entry.object) === identifier) (name_login(member_entry.object) === identifier)
@ -692,9 +480,9 @@ namespace _espe.service.member
// do nothing // do nothing
} }
else { else {
if (member_object_old.email_address_private === null) { if (member_object_old.email_address === null) {
lib_plankton.log.notice( lib_plankton.log.notice(
"member_password_change_impossible_due_to_missing_private_email_address", "member_password_change_impossible_due_to_missing_email_address",
{ {
"member_id": member_id, "member_id": member_id,
} }
@ -705,20 +493,13 @@ namespace _espe.service.member
// keine echte Verifizierung, der Algorithmus ist aber der passende // keine echte Verifizierung, der Algorithmus ist aber der passende
const token : string = await _espe.helpers.verification_get(Math.floor(Math.random() * (1 << 24))); const token : string = await _espe.helpers.verification_get(Math.floor(Math.random() * (1 << 24)));
const member_object_new : _espe.type.member_object = { const member_object_new : _espe.type.member_object = {
"membership_number": member_object_old.membership_number, "name": member_object_old.name,
"name_real_value": member_object_old.name_real_value, "email_address": member_object_old.email_address,
"name_real_index": member_object_old.name_real_index,
"email_address_private": member_object_old.email_address_private,
"registered": member_object_old.registered,
"enabled": member_object_old.enabled, "enabled": member_object_old.enabled,
"email_use_veiled_address": member_object_old.email_use_veiled_address, "groups": member_object_old.groups,
"email_use_nominal_address": member_object_old.email_use_nominal_address,
"email_redirect_to_private_address": member_object_old.email_redirect_to_private_address,
"email_allow_sending": member_object_old.email_allow_sending,
"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();
@ -736,7 +517,7 @@ namespace _espe.service.member
else { else {
/*await*/ _espe.helpers.email_send( /*await*/ _espe.helpers.email_send(
[ [
member_object_old.email_address_private, member_object_old.email_address,
], ],
lib_plankton.string.coin( lib_plankton.string.coin(
"{{head}} | {{core}}", "{{head}} | {{core}}",
@ -772,10 +553,12 @@ namespace _espe.service.member
) : Promise<Array<{incident : string; details : Record<string, any>;}>> ) : Promise<Array<{incident : string; details : Record<string, any>;}>>
{ {
const member_object_old : _espe.type.member_object = await _espe.repository.member.read(member_id); const member_object_old : _espe.type.member_object = await _espe.repository.member.read(member_id);
if (member_object_old.email_address_private === null) { if (member_object_old.email_address === null)
{
return Promise.reject(new Error("private e-mail address missing")); return Promise.reject(new Error("private e-mail address missing"));
} }
else { else
{
let flaws : Array<{incident : string; details : Record<string, any>;}> = []; let flaws : Array<{incident : string; details : Record<string, any>;}> = [];
if ( if (
(member_object_old.password_change_token === null) (member_object_old.password_change_token === null)
@ -801,26 +584,19 @@ namespace _espe.service.member
} }
else { else {
const member_object_new : _espe.type.member_object = { const member_object_new : _espe.type.member_object = {
"membership_number": member_object_old.membership_number, "name": member_object_old.name,
"name_real_value": member_object_old.name_real_value, "email_address": member_object_old.email_address,
"name_real_index": member_object_old.name_real_index, "groups": member_object_old.groups,
"email_address_private": member_object_old.email_address_private,
"registered": member_object_old.registered,
"enabled": member_object_old.enabled, "enabled": member_object_old.enabled,
"email_use_veiled_address": member_object_old.email_use_veiled_address,
"email_use_nominal_address": member_object_old.email_use_nominal_address,
"email_redirect_to_private_address": member_object_old.email_redirect_to_private_address,
"email_allow_sending": member_object_old.email_allow_sending,
"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();
await _espe.helpers.email_send( await _espe.helpers.email_send(
[ [
member_object_old.email_address_private, member_object_old.email_address,
], ],
lib_plankton.string.coin( lib_plankton.string.coin(
"{{head}} | {{core}}", "{{head}} | {{core}}",
@ -903,6 +679,9 @@ 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,
/**
* @todo covert to string
*/
"groups": entry.object.groups, "groups": entry.object.groups,
"password": entry.object.password_image, "password": entry.object.password_image,
} }

View file

@ -16,6 +16,18 @@ You should have received a copy of the GNU General Public License along with thi
namespace _espe.type namespace _espe.type
{ {
/**
*/
export type group_id = int;
/**
*/
export type group_object = {
name : string;
};
/** /**
*/ */
export type admin_id = int; export type admin_id = int;
@ -39,17 +51,10 @@ namespace _espe.type
/** /**
*/ */
export type member_object = { export type member_object = {
membership_number : (null | string); name : string;
name_real_value : string; email_address : (null | string);
name_real_index : int; groups : Array<group_id>;
email_address_private : (null | string);
groups : Array<string>;
registered : boolean;
enabled : boolean; enabled : boolean;
email_use_veiled_address : boolean;
email_use_nominal_address : boolean;
email_redirect_to_private_address : boolean;
email_allow_sending : boolean;
password_image : (null | string); password_image : (null | string);
password_change_last_attempt : (null | int); password_change_last_attempt : (null | int);
password_change_token : (null | string); password_change_token : (null | string);
@ -58,27 +63,26 @@ namespace _espe.type
/** /**
*/ */
export type invite_id = int; export type invitation_id = int;
/** /**
*/ */
export type invite_key = string; export type invitation_key = string;
/** /**
* @todo use "pod" instead of "changable"/"value"
*/ */
export type invite_object = { export type invitation_object = {
key : invite_key; key : invitation_key;
expiry : (null | int); expiry : (null | int);
membership_number_changeable : boolean;
membership_number_value : (null | string);
name_changeable : boolean; name_changeable : boolean;
name_value : string; name_value : (null | string);
email_address_changeable : boolean; email_address_changeable : boolean;
email_address_value : (null | string); email_address_value : (null | string);
groups_changeable : boolean; groups_changeable : boolean;
groups_value : Array<string>; groups_value : (null | Array<_espe.type.group_id>);
}; };
} }

View file

@ -48,23 +48,20 @@ ${dir_temp}/espe-core.js ${dir_temp}/espe-core.d.ts: \
${dir_source}/helpers/password.ts \ ${dir_source}/helpers/password.ts \
${dir_source}/database.ts \ ${dir_source}/database.ts \
${dir_source}/types.ts \ ${dir_source}/types.ts \
${dir_source}/repositories/group.ts \
${dir_source}/repositories/admin.ts \ ${dir_source}/repositories/admin.ts \
${dir_source}/repositories/name_index.ts \
${dir_source}/repositories/member.ts \ ${dir_source}/repositories/member.ts \
${dir_source}/repositories/invite.ts \ ${dir_source}/repositories/invitation.ts \
${dir_source}/services/group.ts \
${dir_source}/services/admin.ts \ ${dir_source}/services/admin.ts \
${dir_source}/services/name_index.ts \
${dir_source}/services/member.ts \ ${dir_source}/services/member.ts \
${dir_source}/services/invite.ts \ ${dir_source}/services/invitation.ts \
${dir_source}/api/base.ts \ ${dir_source}/api/base.ts \
${dir_source}/api/actions/meta_ping.ts \ ${dir_source}/api/actions/meta_ping.ts \
${dir_source}/api/actions/meta_spec.ts \ ${dir_source}/api/actions/meta_spec.ts \
${dir_source}/api/actions/session_begin.ts \ ${dir_source}/api/actions/session_begin.ts \
${dir_source}/api/actions/session_end.ts \ ${dir_source}/api/actions/session_end.ts \
${dir_source}/api/actions/member_project.ts \
${dir_source}/api/actions/member_summon.ts \ ${dir_source}/api/actions/member_summon.ts \
${dir_source}/api/actions/member_info.ts \
${dir_source}/api/actions/member_register.ts \
${dir_source}/api/actions/member_list.ts \ ${dir_source}/api/actions/member_list.ts \
${dir_source}/api/actions/member_read.ts \ ${dir_source}/api/actions/member_read.ts \
${dir_source}/api/actions/member_modify.ts \ ${dir_source}/api/actions/member_modify.ts \