From aa17938c6e4a4873cf003131b3b3c89d9a103d17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Fra=C3=9F?= Date: Mon, 25 Aug 2025 14:22:56 +0200 Subject: [PATCH] Einladungs-System (#6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Aufgaben - [\#193](https://vikunja.ramsch.sx/tasks/193) ## Zugehörige MRs - https://forgejo.linke.sx/espe/datamodel/pulls/3 - https://forgejo.linke.sx/espe/frontend-zackeneule/pulls/2 Reviewed-on: https://forgejo.linke.sx/espe/backend/pulls/6 Co-authored-by: Christian Fraß Co-committed-by: Christian Fraß --- misc/conf.example.json | 6 +- misc/sampledata.json | 73 +++ source/api/actions/group_add.ts | 88 ++++ source/api/actions/group_list.ts | 83 ++++ .../{member_summon.ts => group_modify.ts} | 62 ++- source/api/actions/group_read.ts | 72 +++ source/api/actions/invitation_accept.ts | 97 ++++ source/api/actions/invitation_create.ts | 189 ++++++++ source/api/actions/invitation_examine.ts | 143 ++++++ source/api/actions/invitation_list.ts | 94 ++++ source/api/actions/invitation_read.ts | 158 ++++++ source/api/actions/member_info.ts | 121 ----- source/api/actions/member_list.ts | 18 +- source/api/actions/member_modify.ts | 27 +- source/api/actions/member_project.ts | 153 ------ source/api/actions/member_read.ts | 97 +--- source/api/actions/member_register.ts | 159 ------ source/api/functions.ts | 21 +- source/data/localization/deu.loc.json | 3 + source/data/localization/eng.loc.json | 3 + source/database.ts | 2 +- source/helpers.ts | 20 + source/main.ts | 14 +- source/repositories/group.ts | 226 +++++++++ source/repositories/invitation.ts | 334 +++++++++++++ source/repositories/member.ts | 63 +-- source/repositories/name_index.ts | 108 ----- source/sample.ts | 146 ++++++ source/services/group.ts | 70 +++ source/services/invitation.ts | 379 +++++++++++++++ source/services/member.ts | 454 +++++++----------- source/services/name_index.ts | 31 -- source/types.ts | 54 ++- todo.md | 2 + tools/build | 24 +- tools/makefile | 22 +- 36 files changed, 2548 insertions(+), 1068 deletions(-) create mode 100644 misc/sampledata.json create mode 100644 source/api/actions/group_add.ts create mode 100644 source/api/actions/group_list.ts rename source/api/actions/{member_summon.ts => group_modify.ts} (54%) create mode 100644 source/api/actions/group_read.ts create mode 100644 source/api/actions/invitation_accept.ts create mode 100644 source/api/actions/invitation_create.ts create mode 100644 source/api/actions/invitation_examine.ts create mode 100644 source/api/actions/invitation_list.ts create mode 100644 source/api/actions/invitation_read.ts delete mode 100644 source/api/actions/member_info.ts delete mode 100644 source/api/actions/member_project.ts delete mode 100644 source/api/actions/member_register.ts create mode 100644 source/repositories/group.ts create mode 100644 source/repositories/invitation.ts delete mode 100644 source/repositories/name_index.ts create mode 100644 source/sample.ts create mode 100644 source/services/group.ts create mode 100644 source/services/invitation.ts delete mode 100644 source/services/name_index.ts create mode 100644 todo.md diff --git a/misc/conf.example.json b/misc/conf.example.json index ca1a24b..87569ce 100644 --- a/misc/conf.example.json +++ b/misc/conf.example.json @@ -2,7 +2,7 @@ "general": { "language": null, "verbosity": "info", - "verification_secret": null + "verification_secret": "foobar" }, "log": [ { @@ -26,7 +26,7 @@ "database": { "kind": "sqlite", "data": { - "path": "data.sqlite" + "path": "../espe.sqlite" } }, "email_sending": { @@ -67,7 +67,7 @@ "salt": null }, "connections": { - "frontend_url_base": null, + "frontend_url_base": "http://localhost:8888", "login_url": null } }, diff --git a/misc/sampledata.json b/misc/sampledata.json new file mode 100644 index 0000000..eef9e1f --- /dev/null +++ b/misc/sampledata.json @@ -0,0 +1,73 @@ +{ + "groups": [ + { + "id": 1, + "name": "auto", + "label": "Auto" + }, + { + "id": 2, + "name": "zug", + "label": "Zug" + }, + { + "id": 3, + "name": "flugzeug", + "label": "Flugzeug" + }, + { + "id": 4, + "name": "fahrrad", + "label": "Fahrrad" + }, + { + "id": 5, + "name": "zu_fusz", + "label": "zu Fuß" + } + ], + "admins": [ + { + "name": "admin", + "email_address": "admin@example.org", + "password": "admin" + } + ], + "members": [ + { + "id": 1, + "name": "alexandra", + "label": "Alexandra Ahorn", + "email_address": "alex-rockt@example.org", + "groups": [1, 2, 3] + }, + { + "id": 2, + "name": "berthold", + "label": "Berthold Buche", + "email_address": "bert-ohne-ernie@example.org", + "groups": [4, 5, 2] + }, + { + "id": 3, + "name": "charlotte", + "label": "Charlotte Castania", + "email_address": "charly-the-unicorn@example.org", + "groups": [4, 1] + } + ], + "invitations": [ + { + "id": 1, + "name_changeable": false, + "name_value": "daniel", + "label_changeable": true, + "label_value": "Daniel Distel", + "email_address_changeable": true, + "email_address_value": "duesentrieb@example.org", + "groups_changeable": false, + "groups_value": [3, 5] + } + ] +} + diff --git a/source/api/actions/group_add.ts b/source/api/actions/group_add.ts new file mode 100644 index 0000000..1c704e7 --- /dev/null +++ b/source/api/actions/group_add.ts @@ -0,0 +1,88 @@ +/* +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 +. + */ + +namespace _espe.api +{ + + /** + */ + export function register_group_add( + rest_subject : lib_plankton.rest_http.type_rest + ) : void + { + lib_plankton.rest_http.register< + { + name : string; + label : string; + }, + (string | _espe.type.group_id) + >( + rest_subject, + lib_plankton.http.enum_method.post, + _espe.api.full_path("/group/add"), + { + /** + * @todo translation + */ + "description": () => "erstellt eine Gruppe", + "input_schema": () => ({ + "nullable": false, + "type": "object", + "properties": { + "name": { + "nullable": false, + "type": "string", + }, + "label": { + "nullable": false, + "type": "string", + }, + }, + "additionalProperties": false, + "required": [ + "name", + "label", + ], + }), + "output_schema": () => ({ + "nullable": false, + "type": "number", + }), + "restriction": () => restriction_logged_in, + "execution": () => async ({"input": input}) => { + if (input === null) { + return Promise.resolve({ + "status_code": 400, + "data": "" + }); + } + else { + const data = await _espe.service.group.add( + { + "name": input["name"], + "label": input["label"] + } + ); + return Promise.resolve({ + "status_code": 200, + "data": data + }); + } + } + } + ); + } + +} diff --git a/source/api/actions/group_list.ts b/source/api/actions/group_list.ts new file mode 100644 index 0000000..bcccbaf --- /dev/null +++ b/source/api/actions/group_list.ts @@ -0,0 +1,83 @@ +/* +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 +. + */ + +namespace _espe.api +{ + + /** + */ + export function register_group_list( + rest_subject : lib_plankton.rest_http.type_rest + ) : void + { + lib_plankton.rest_http.register< + null, + Array< + { + id : _espe.type.invitation_id; + preview : { + name : string; + label : string; + }; + } + > + >( + rest_subject, + lib_plankton.http.enum_method.get, + _espe.api.full_path("/group/list"), + { + /** + * @todo translation + */ + "description": () => "listet alle Gruppen auf", + "output_schema": () => ({ + "type": "object", + "nullable": false, + "additionalProperties": false, + "properties": { + "id": { + "nullable": false, + "type": "intiger", + "description": "ID" + }, + "name": { + "nullable": false, + "type": "string", + "description": "Name" + }, + "label": { + "nullable": false, + "type": "string", + "description": "Beschriftung" + }, + }, + "required": [ + "id", + "name", + ] + }), + "restriction": () => restriction_logged_in, + "execution": () => async ({}) => { + const data = await _espe.service.group.list(); + return Promise.resolve({ + "status_code": 200, + "data": data, + }); + } + } + ); + } + +} diff --git a/source/api/actions/member_summon.ts b/source/api/actions/group_modify.ts similarity index 54% rename from source/api/actions/member_summon.ts rename to source/api/actions/group_modify.ts index 20eef9e..0bf9657 100644 --- a/source/api/actions/member_summon.ts +++ b/source/api/actions/group_modify.ts @@ -18,45 +18,59 @@ namespace _espe.api /** */ - export function register_member_summon( + export function register_group_modify( rest_subject : lib_plankton.rest_http.type_rest ) : void { lib_plankton.rest_http.register< { - url_template : string; + label : string; }, - ( - null - | - string - ) + null >( rest_subject, - lib_plankton.http.enum_method.post, - _espe.conf.get().server.path_base + "/member/summon/:id", + lib_plankton.http.enum_method.patch, + _espe.api.full_path("/group/modify/:id"), { - "description": () => "sendet an ein Mitglied eine E-Mail mit Aufforderung zur Registrierung", + /** + * @todo translation + */ + "description": () => "ändert eine Gruppe", + "input_schema": () => ({ + "nullable": false, + "type": "object", + "properties": { + "label": { + "nullable": false, + "type": "string", + }, + }, + "additionalProperties": false, + "required": [ + "label", + ] + }), + "output_schema": () => ({ + "nullable": true, + }), "restriction": () => restriction_logged_in, "execution": () => async ({"path_parameters": path_parameters, "input": input}) => { if (input === null) { - return Promise.reject(new Error("impossible")); + return Promise.resolve({ + "status_code": 400, + "data": null + }); } else { - const member_id : _espe.type.member_id = parseInt(path_parameters["id"]); - const url : (null | string) = await _espe.service.member.summon(member_id, input.url_template); - - return ( - (url === null) - ? Promise.resolve({ - "status_code": 409, - "data": null, - }) - : Promise.resolve({ - "status_code": 200, - "data": url, - }) + const group_id : _espe.type.group_id = parseInt(path_parameters["id"]); + const data = await _espe.service.group.modify( + group_id, + input["label"] ); + return Promise.resolve({ + "status_code": 200, + "data": null + }); } } } diff --git a/source/api/actions/group_read.ts b/source/api/actions/group_read.ts new file mode 100644 index 0000000..9434ced --- /dev/null +++ b/source/api/actions/group_read.ts @@ -0,0 +1,72 @@ +/* +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 +. + */ + +namespace _espe.api +{ + + /** + */ + export function register_group_read( + rest_subject : lib_plankton.rest_http.type_rest + ) : void + { + lib_plankton.rest_http.register< + null, + { + name : string; + label : string; + } + >( + rest_subject, + lib_plankton.http.enum_method.get, + _espe.api.full_path("/group/read/:id"), + { + /** + * @todo translation + */ + "description": () => "gibt die Daten zu einer Gruppen aus", + "output_schema": () => ({ + "nullable": false, + "type": "object", + "properties": { + "name": { + "nullable": false, + "type": "string", + }, + "label": { + "nullable": false, + "type": "string", + }, + }, + "additionalProperties": false, + "required": [ + "name", + "label", + ] + }), + "restriction": () => restriction_logged_in, + "execution": () => async ({"path_parameters": path_parameters}) => { + const group_id : _espe.type.group_id = parseInt(path_parameters["id"]); + const group_object : _espe.type.group_object = await _espe.service.group.get(group_id); + return Promise.resolve({ + "status_code": 200, + "data": group_object + }); + } + } + ); + } + +} diff --git a/source/api/actions/invitation_accept.ts b/source/api/actions/invitation_accept.ts new file mode 100644 index 0000000..19474b0 --- /dev/null +++ b/source/api/actions/invitation_accept.ts @@ -0,0 +1,97 @@ +/* +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 +. + */ + +namespace _espe.api +{ + /** + */ + export function register_invitation_accept( + rest_subject : lib_plankton.rest_http.type_rest + ) : void + { + lib_plankton.rest_http.register< + { + key : string; + data : { + name : (null | string); + label : (null | string); + groups : (null | Array); + email_address : (null | string); + password : (null | string); + }; + }, + Array< + { + incident : string; + details : Record; + } + > + >( + rest_subject, + lib_plankton.http.enum_method.post, + _espe.api.full_path("/invitation/accept"), + { + /** + * @todo translation + */ + "description": () => "nimmt eine Einladung an", + /** + * @todo + */ + "input_schema": () => ({ + "nullable": true, + }), + /** + * @todo + */ + "output_schema": () => ({ + "nullable": true, + }), + "restriction": () => restriction_none, + "execution": () => async ({"input": input}) => { + if (input === null) + { + return Promise.resolve({ + "status_code": 400, + "data": null + }); + } + else + { + try + { + const flaws = await _espe.service.invitation.accept( + input.key, + input.data + ); + return Promise.resolve({ + "status_code": 200, + "data": flaws + }); + } + catch (error) + { + return Promise.resolve({ + "status_code": 404, + "data": null + }); + } + } + } + } + ); + } + +} diff --git a/source/api/actions/invitation_create.ts b/source/api/actions/invitation_create.ts new file mode 100644 index 0000000..b681700 --- /dev/null +++ b/source/api/actions/invitation_create.ts @@ -0,0 +1,189 @@ +/* +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 +. + */ + +namespace _espe.api +{ + + /** + */ + export function register_invitation_create( + rest_subject : lib_plankton.rest_http.type_rest + ) : void + { + lib_plankton.rest_http.register< + { + data : { + name_changeable : boolean; + name_value : string; + label_changeable : boolean; + label_value : string; + email_address_changeable : boolean; + email_address_value : (null | string); + groups_changeable : boolean; + groups_value : Array; + expiry : (null | int); + }; + notification_target_url_template ?: (null | string); + send_immediatly : boolean; + }, + ( + string + | + { + id : _espe.type.member_id; + key : string; + } + ) + >( + rest_subject, + lib_plankton.http.enum_method.post, + _espe.api.full_path("/invitation/create"), + { + /** + * @todo translation + */ + "description": () => "erstellt eine Einladung und gibt die erzeugte ID und den erzeugten Schlüssel aus", + "input_schema": () => ({ + "type": "object", + "nullable": false, + "additionalProperties": false, + "properties": { + "data": { + "nullable": false, + "type": "object", + "additionalProperties": false, + "properties": { + "expiry": { + "nullable": true, + "type": "intiger", + "description": "Ablaufzeitpunkt" + }, + "name_changeable": { + "type": "boolean", + "nullable": false, + "description": "Name | änderbar" + }, + "name_value": { + "type": "string", + "nullable": true, + "description": "Name | Wert" + }, + "email_address_changeable": { + "type": "boolean", + "nullable": false, + "description": "E-Mail-Adresse | änderbar" + }, + "email_address_value": { + "type": "string", + "nullable": true, + "description": "E-Mail-Adresse | Wert" + }, + "groups_integer": { + "type": "integer", + "nullable": true, + "description": "Gruppen | Modus" + }, + "groups_value": { + "nullable": false, + "type": "array", + "items": { + "type": "integer", + "nullable": false, + }, + "description": "Gruppen | Wert" + }, + }, + "required": [ + "membership_number_changeable", + "membership_number_value", + "name_changeable", + "name_value", + "email_address_changeable", + "email_address_value", + "groups_changeable", + "groups_value", + "expiry", + ] + }, + "notification_target_url_template": { + "type": "string", + "nullable": true, + "description": "Platz-Halter: key" + }, + "send_immediatly": { + "nullable": false, + "type": "boolean", + "description": "Einladungs-Link direkt an angegebene E-Mail-Adresse versenden" + }, + }, + "required": [ + "data", + "send_immediatly", + ] + }), + "output_schema": () => ({ + "type": "object", + "nullable": false, + "properties": { + "id": { + "type": "number", + "nullable": false, + }, + "key": { + "type": "string", + "nullable": false, + }, + }, + "additionalProperties": false, + "required": [ + "id", + "key", + ] + }), + "restriction": () => restriction_logged_in, + "execution": () => async ({"input": input}) => { + if (input === null) { + return Promise.reject(new Error("impossible")); + } + else { + const invitation_info : {id : _espe.type.invitation_id; key : _espe.type.invitation_key;} = await _espe.service.invitation.create( + { + "name_changeable": input.data.name_changeable, + "name_value": input.data.name_value, + "label_changeable": input.data.label_changeable, + "label_value": input.data.label_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": invitation_info + }); + } + } + } + ); + } + +} + diff --git a/source/api/actions/invitation_examine.ts b/source/api/actions/invitation_examine.ts new file mode 100644 index 0000000..07bbdfa --- /dev/null +++ b/source/api/actions/invitation_examine.ts @@ -0,0 +1,143 @@ +/* +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 +. + */ + +namespace _espe.api +{ + /** + */ + export function register_invitation_examine( + rest_subject : lib_plankton.rest_http.type_rest + ) : void + { + lib_plankton.rest_http.register< + any, + any + >( + rest_subject, + lib_plankton.http.enum_method.get, + _espe.api.full_path("/invitation/examine"), + { + /** + * @todo translation + */ + "description": () => "gibt die Daten einer Einladung anhand ihres Schlüssels aus", + "query_parameters": () => [ + { + "name": "key", + "required": true, + "description": "key", + } + ], + "output_schema": () => ({ + "type": "object", + "nullable": false, + "additionalProperties": false, + "properties": { + "expiry": { + "nullable": true, + "type": "integer", + "description": "Ablaufzeitpunkt" + }, + "name_changeable": { + "type": "boolean", + "nullable": false, + "description": "Name | änderbar" + }, + "name_value": { + "type": "string", + "nullable": true, + "description": "Name | Wert" + }, + "label_changeable": { + "type": "boolean", + "nullable": false, + "description": "Beschriftung | änderbar" + }, + "label_value": { + "type": "string", + "nullable": true, + "description": "Beschriftung | Wert" + }, + "email_address_changeable": { + "type": "boolean", + "nullable": false, + "description": "E-Mail-Adresse | änderbar" + }, + "email_address_value": { + "type": "string", + "nullable": true, + "description": "E-Mail-Adresse | Wert" + }, + "groups_changeable": { + "type": "boolean", + "nullable": false, + "description": "Gruppen | änderbar" + }, + "groups_value": { + "nullable": false, + "type": "array", + "items": { + "type": "string", + "nullable": false, + }, + "description": "Gruppen | Wert" + }, + }, + "required": [ + "expiry", + "name_changeable", + "name_value", + "label_changeable", + "label_value", + "email_address_changeable", + "email_address_value", + "groups_changeable", + "groups_value", + ] + }), + "restriction": () => restriction_none, + "execution": () => ({"query_parameters": query_parameters, "input": input}) => { + const invitation_key : _espe.type.invitation_key = query_parameters["key"]; + return ( + _espe.service.invitation.examine(invitation_key) + .then( + (invitation_object) => Promise.resolve({ + "status_code": 200, + "data": { + "expiry": invitation_object.expiry, + "name_changeable": invitation_object.name_changeable, + "name_value": invitation_object.name_value, + "label_changeable": invitation_object.label_changeable, + "label_value": invitation_object.label_value, + "email_address_changeable": invitation_object.email_address_changeable, + "email_address_value": invitation_object.email_address_value, + "groups_changeable": invitation_object.groups_changeable, + "groups_value": invitation_object.groups_value, + } + }) + ) + .catch( + (error) => Promise.resolve({ + "status_code": 404, + "data": "not found" + }) + ) + ); + } + } + ); + } + +} diff --git a/source/api/actions/invitation_list.ts b/source/api/actions/invitation_list.ts new file mode 100644 index 0000000..0a2625b --- /dev/null +++ b/source/api/actions/invitation_list.ts @@ -0,0 +1,94 @@ +/* +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 +. + */ + +namespace _espe.api +{ + /** + */ + export function register_invitation_list( + rest_subject : lib_plankton.rest_http.type_rest + ) : void + { + lib_plankton.rest_http.register< + null, + Array< + { + id : _espe.type.invitation_id; + preview : { + key : _espe.type.invitation_key; + expiry : (null | int); + name_value : (null | string); + label_value : (null | string); + }; + } + > + >( + rest_subject, + lib_plankton.http.enum_method.get, + _espe.api.full_path("/invitation/list"), + { + /** + * @todo translation + */ + "description": () => "listet alle Einladung auf", + "output_schema": () => ({ + "type": "object", + "nullable": false, + "additionalProperties": false, + "properties": { + "id": { + "nullable": false, + "type": "intiger", + "description": "ID" + }, + "key": { + "nullable": false, + "type": "string", + "description": "Schlüssel" + }, + "expiry": { + "nullable": true, + "type": "integer", + "description": "Ablauf" + }, + "name_value": { + "nullable": false, + "type": "string", + "description": "Name" + }, + "label_value": { + "nullable": false, + "type": "string", + "description": "Beschriftung" + }, + }, + "required": [ + "id", + "key", + ] + }), + "restriction": () => restriction_logged_in, + "execution": () => async ({}) => { + const data = await _espe.service.invitation.list(); + return Promise.resolve({ + "status_code": 200, + "data": data + }); + } + } + ); + } + +} diff --git a/source/api/actions/invitation_read.ts b/source/api/actions/invitation_read.ts new file mode 100644 index 0000000..d6e056b --- /dev/null +++ b/source/api/actions/invitation_read.ts @@ -0,0 +1,158 @@ +/* +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 +. + */ + +namespace _espe.api +{ + + /** + */ + export function register_invitation_read( + rest_subject : lib_plankton.rest_http.type_rest + ) : void + { + lib_plankton.rest_http.register< + int, + ( + string + | + { + key : string; + expiry : (null | int); + name_changeable : boolean; + name_value : (null | string); + label_changeable : boolean; + label_value : (null | string); + email_address_changeable : boolean; + email_address_value : (null | string); + groups_changeable : boolean; + groups_value : (null | Array); + } + ) + >( + rest_subject, + lib_plankton.http.enum_method.get, + _espe.api.full_path("/invitation/read"), + { + /** + * @todo translation + */ + "description": () => "gibt die Daten einer Einladung anhand ihrer ID aus", + "query_parameters": () => [ + { + "name": "id", + "required": true, + "description": "id", + } + ], + "output_schema": () => ({ + "type": "object", + "nullable": false, + "additionalProperties": false, + "properties": { + "expiry": { + "nullable": true, + "type": "integer", + "description": "Ablaufzeitpunkt" + }, + "name_changeable": { + "type": "boolean", + "nullable": false, + "description": "Name | änderbar" + }, + "name_value": { + "type": "string", + "nullable": true, + "description": "Name | Wert" + }, + "label_changeable": { + "type": "boolean", + "nullable": false, + "description": "Beschriftung | änderbar" + }, + "label_value": { + "type": "string", + "nullable": true, + "description": "Beschriftung | Wert" + }, + "email_address_changeable": { + "type": "boolean", + "nullable": false, + "description": "E-Mail-Adresse | änderbar" + }, + "email_address_value": { + "type": "string", + "nullable": true, + "description": "E-Mail-Adresse | Wert" + }, + "groups_changeable": { + "type": "boolean", + "nullable": false, + "description": "Gruppen | änderbar" + }, + "groups_value": { + "nullable": false, + "type": "array", + "items": { + "type": "integer", + "nullable": false, + }, + "description": "Gruppen | Wert" + }, + }, + "required": [ + "expiry", + "name_changeable", + "name_value", + "email_address_changeable", + "email_address_value", + "groups_changeable", + "groups_value", + ] + }), + "restriction": () => restriction_logged_in, + "execution": () => ({"query_parameters": query_parameters, "input": input}) => { + const invitation_id : _espe.type.invitation_id = parseInt(query_parameters["id"]); + return ( + _espe.service.invitation.get_by_id(invitation_id) + .then( + (invitation_object) => Promise.resolve({ + "status_code": 200, + "data": { + "key": invitation_object.key, + "expiry": invitation_object.expiry, + "name_changeable": invitation_object.name_changeable, + "name_value": invitation_object.name_value, + "label_changeable": invitation_object.label_changeable, + "label_value": invitation_object.label_value, + "email_address_changeable": invitation_object.email_address_changeable, + "email_address_value": invitation_object.email_address_value, + "groups_changeable": invitation_object.groups_changeable, + "groups_value": invitation_object.groups_value, + } + }) + ) + .catch( + (error) => Promise.resolve({ + "status_code": 404, + "data": "not found" + }) + ) + ); + } + } + ); + } + +} diff --git a/source/api/actions/member_info.ts b/source/api/actions/member_info.ts deleted file mode 100644 index a5fa539..0000000 --- a/source/api/actions/member_info.ts +++ /dev/null @@ -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 -. - */ - -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 - }); - }, - } - ) - } - -} diff --git a/source/api/actions/member_list.ts b/source/api/actions/member_list.ts index 420faa5..84dd06c 100644 --- a/source/api/actions/member_list.ts +++ b/source/api/actions/member_list.ts @@ -28,9 +28,8 @@ namespace _espe.api { id : int; preview : { - membership_number : string; - name_real_value : string; - name_real_index : int; + name : string; + label : string; }; } > @@ -62,23 +61,18 @@ namespace _espe.api "type": "object", "additionalProperties": false, "properties": { - "membership_number": { + "name": { "type": "string", "nullable": false, }, - "name_real_value": { + "label": { "type": "string", "nullable": false, }, - "name_real_index": { - "type": "number", - "nullable": false, - }, }, "required": [ - "membership_number", - "name_real_value", - "name_real_index", + "name", + "label", ] } }, diff --git a/source/api/actions/member_modify.ts b/source/api/actions/member_modify.ts index ac63cff..c39c47a 100644 --- a/source/api/actions/member_modify.ts +++ b/source/api/actions/member_modify.ts @@ -24,9 +24,9 @@ namespace _espe.api { lib_plankton.rest_http.register< { - email_address_private : (null | string); - groups ?: Array; - registered : boolean; + label : string; + email_address : (null | string); + groups ?: Array; enabled : boolean; }, null @@ -40,7 +40,11 @@ namespace _espe.api "nullable": false, "type": "object", "properties": { - "email_address_private": { + "label": { + "nullable": true, + "type": "string" + }, + "email_address": { "nullable": true, "type": "string" }, @@ -52,10 +56,6 @@ namespace _espe.api "nullable": false, } }, - "registered": { - "nullable": false, - "type": "boolean" - }, "enabled": { "nullable": false, "type": "boolean" @@ -63,8 +63,7 @@ namespace _espe.api }, "additionalProperties": false, "required": [ - "email_address_private", - "registered", + "email_address", "enabled", ] }), @@ -81,13 +80,13 @@ namespace _espe.api await _espe.service.member.modify( member_id, { - "email_address_private": input.email_address_private, + "label": input.label, + "email_address": input.email_address, "groups": ( (input.groups === undefined) - ? lib_plankton.pod.make_empty>() - : lib_plankton.pod.make_filled>(input.groups) + ? lib_plankton.pod.make_empty>() + : lib_plankton.pod.make_filled>(input.groups) ), - "registered": input.registered, "enabled": input.enabled, } ); diff --git a/source/api/actions/member_project.ts b/source/api/actions/member_project.ts deleted file mode 100644 index 60431ea..0000000 --- a/source/api/actions/member_project.ts +++ /dev/null @@ -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 -. - */ - -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; - 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 - }); - } - } - } - } - ); - } - -} diff --git a/source/api/actions/member_read.ts b/source/api/actions/member_read.ts index 9df80fe..1da2c40 100644 --- a/source/api/actions/member_read.ts +++ b/source/api/actions/member_read.ts @@ -25,21 +25,12 @@ namespace _espe.api lib_plankton.rest_http.register< null, { - membership_number : (null | string); - name_real_value : string; - name_real_index : int; - email_address_private : (null | string); - groups : Array; - registered : boolean; + name : string; + label : string; + email_address : (null | string); + groups : Array; 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; - email_address_veiled : (null | string); - email_address_nominal : string; - name_login : string; } >( rest_subject, @@ -51,19 +42,15 @@ namespace _espe.api "nullable": false, "type": "object", "properties": { - "membership_number": { - "nullable": true, - "type": "string" - }, - "name_real_value": { + "name": { "nullable": false, "type": "string" }, - "name_real_index": { + "label": { "nullable": false, - "type": "number" + "type": "string" }, - "email_address_private": { + "email_address": { "nullable": true, "type": "string" }, @@ -72,67 +59,22 @@ namespace _espe.api "type": "array", "items": { "nullable": false, - "type": "string" + "type": "int" } }, - "registered": { - "nullable": false, - "type": "boolean" - }, "enabled": { "nullable": false, "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, "required": [ - "membership_number", - "name_real_value", - "name_real_index", - "email_address_private", + "name", + "label", + "email_address", "groups", - "registered", "enabled", - "email_use_veiled_address", - "email_use_nominal_address", - "email_redirect_to_private_address", - "email_allow_sending", "password_set", - "email_address_veiled", - "email_address_nominal", - "name_login", ] }), "restriction": () => restriction_logged_in, @@ -142,21 +84,12 @@ namespace _espe.api return Promise.resolve({ "status_code": 200, "data": { - "membership_number": member_object.membership_number, - "name_real_value": member_object.name_real_value, - "name_real_index": member_object.name_real_index, - "email_address_private": member_object.email_address_private, + "name": member_object.name, + "label": member_object.label, + "email_address": member_object.email_address, "groups": member_object.groups, - "registered": member_object.registered, "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), - "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), }, }); } diff --git a/source/api/actions/member_register.ts b/source/api/actions/member_register.ts deleted file mode 100644 index a46ecbe..0000000 --- a/source/api/actions/member_register.ts +++ /dev/null @@ -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 -. - */ - -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; - } - > - >( - 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 - }) - ) - ); - } - }, - } - ) - } - -} diff --git a/source/api/functions.ts b/source/api/functions.ts index e2d8fd5..d030510 100644 --- a/source/api/functions.ts +++ b/source/api/functions.ts @@ -43,12 +43,15 @@ namespace _espe.api _espe.api.register_session_begin(rest_subject); _espe.api.register_session_end(rest_subject); } + // group + { + _espe.api.register_group_list(rest_subject); + _espe.api.register_group_read(rest_subject); + _espe.api.register_group_add(rest_subject); + _espe.api.register_group_modify(rest_subject); + } // member { - _espe.api.register_member_project(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_read(rest_subject); _espe.api.register_member_modify(rest_subject); @@ -59,11 +62,13 @@ namespace _espe.api _espe.api.register_member_password_change_execute(rest_subject); } } - // invite + // invitation { - _espe.api.register_invite_create(rest_subject); - _espe.api.register_invite_examine(rest_subject); - _espe.api.register_invite_accept(rest_subject); + _espe.api.register_invitation_list(rest_subject); + _espe.api.register_invitation_read(rest_subject); + _espe.api.register_invitation_create(rest_subject); + _espe.api.register_invitation_examine(rest_subject); + _espe.api.register_invitation_accept(rest_subject); } return rest_subject; diff --git a/source/data/localization/deu.loc.json b/source/data/localization/deu.loc.json index 45b7840..8b5a524 100644 --- a/source/data/localization/deu.loc.json +++ b/source/data/localization/deu.loc.json @@ -14,9 +14,12 @@ "email.password_change.initialization.body": "Hi, {{name}}\n\nDie Funktion zum Ändern deines Passwortes wurde aufgerufen. Wenn du dein Passwort ändern willst, rufe folgenden Link auf:\n\n{{url}}\n", "email.password_change.execution.subject": "Passwort-Änderung abgeschlossen", "email.password_change.execution.body": "Hi, {{name}}\n\nDein Passwort wurde soeben geändert.\n", + "email.invitation.subject": "Einladung", + "email.invitation.body": "{{url}}", "help.args.action.description": "auszuführende Aktion; Auswahl", "help.args.action.options.serve": "Server starten", "help.args.action.options.api_doc": "API-Dokumentation gemäß OpenAPI-Spezifikation auf Standard-Ausgabe schreiben", + "help.args.action.options.sample": "Datenbank mit Beispiel-Daten befüllen", "help.args.action.options.email_test": "eine Test-E-Mail senden", "help.args.action.options.expose_conf": "Vollständige Konfiguration ausgeben", "help.args.action.options.password_image": "Passwort-Abbild errechnen und auf Standard-Ausgabe schreiben", diff --git a/source/data/localization/eng.loc.json b/source/data/localization/eng.loc.json index f9e73af..0450b8b 100644 --- a/source/data/localization/eng.loc.json +++ b/source/data/localization/eng.loc.json @@ -14,9 +14,12 @@ "email.password_change.initialization.body": "Hi, {{name}}\n\nThe function for changing your password has been triggered. If you want to change your password, open the folloling link:\n\n{{url}}", "email.password_change.execution.subject": "Password change concluded", "email.password_change.execution.body": "Hi, {{name}}\n\nYour password has just been changed.\n", + "email.invitation.subject": "invitation", + "email.invitation.body": "{{url}}", "help.args.action.description": "action to executo; options", "help.args.action.options.serve": "start server", "help.args.action.options.api_doc": "write API documentation according to OpenAPI specification to stdout", + "help.args.action.options.sample": "fill database with sample data", "help.args.action.options.email_test": "send a test e-mail", "help.args.action.options.expose_conf": "write complete configuration to stdout", "help.args.action.options.password_image": "compute password image and write to stdout", diff --git a/source/database.ts b/source/database.ts index a0945a5..abe3db1 100644 --- a/source/database.ts +++ b/source/database.ts @@ -19,7 +19,7 @@ namespace _espe.database /** */ const _compatible_revisions : Array = [ - "r6", + "r7", ]; diff --git a/source/helpers.ts b/source/helpers.ts index f5d6b43..28947d2 100644 --- a/source/helpers.ts +++ b/source/helpers.ts @@ -16,6 +16,26 @@ You should have received a copy of the GNU General Public License along with thi namespace _espe.helpers { + /** + */ + export function dbbool_encode( + value : boolean + ) : int + { + return (value ? 1 : 0); + } + + + /** + */ + export function dbbool_decode( + value : int + ) : boolean + { + return (value > 0); + } + + /** */ export type type_smtp_credentials = { diff --git a/source/main.ts b/source/main.ts index 05bafe4..7ac590b 100644 --- a/source/main.ts +++ b/source/main.ts @@ -204,6 +204,10 @@ namespace _espe "name": "api-doc", "description": lib_plankton.translate.get("help.args.action.options.api_doc") }, + { + "name": "sample", + "description": lib_plankton.translate.get("help.args.action.options.sample"), + }, { "name": "email-test", "description": lib_plankton.translate.get("help.args.action.options.email_test") @@ -271,7 +275,7 @@ namespace _espe "hidden": true, }), "conf_path": lib_plankton.args.class_argument.volatile({ - "indicators_long": ["conf_path"], + "indicators_long": ["conf-path"], "indicators_short": ["c"], "type": lib_plankton.args.enum_type.string, "mode": lib_plankton.args.enum_mode.replace, @@ -360,6 +364,14 @@ namespace _espe ); break; } + case "sample": { + const path : string = args["arg1"]; + if (path === null) { + throw (new Error("SYNTAX: sample ")); + } + _espe.sample.fill_by_path(path); + break; + } case "email-test": { await _espe.helpers.email_send( ( diff --git a/source/repositories/group.ts b/source/repositories/group.ts new file mode 100644 index 0000000..de4103d --- /dev/null +++ b/source/repositories/group.ts @@ -0,0 +1,226 @@ +/* +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 +. + */ + +namespace _espe.repository.group +{ + + /** + */ + var _store : ( + null + | + lib_plankton.storage.type_store< + _espe.type.group_id, + Record, + {}, + lib_plankton.storage.type_sql_table_autokey_search_term, + Record + > + ) = null; + + + /** + */ + function get_store( + ) : lib_plankton.storage.type_store< + _espe.type.group_id, + Record, + {}, + lib_plankton.storage.type_sql_table_autokey_search_term, + Record + > + { + 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; + + + /** + */ + function encode( + object : _espe.type.group_object + ) : type_dispersal + { + return { + "name": object.name, + "label": object.label, + }; + } + + + /** + */ + function decode( + dispersal : type_dispersal + ) : _espe.type.group_object + { + return { + "name": dispersal["name"], + "label": dispersal["label"], + }; + } + + + /** + * @todo optimize + */ + export async function list( + search_term : (null | string) + ) : Promise< + Array< + { + id : _espe.type.group_id; + preview : { + name : string; + label : 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"], + "label": preview["label"], + }, + }) + ) + ); + } + + + /** + */ + export async function read( + id : _espe.type.group_id + ) : Promise<_espe.type.group_object> + { + const row : Record = await get_store().read(id); + + const dispersal : type_dispersal = 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); + + return id; + } + + + /** + * @todo replace groups smartly + */ + export async function update( + id : _espe.type.group_id, + value : _espe.type.group_object + ) : Promise + { + const dispersal : type_dispersal = encode(value); + + // core + await get_store().update(id, dispersal); + } + + + /** + */ + export async function delete_( + id : _espe.type.group_id + ) : Promise + { + // 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 + }) + ) + ) + ) + ) + ); + } + +} + diff --git a/source/repositories/invitation.ts b/source/repositories/invitation.ts new file mode 100644 index 0000000..ed07f86 --- /dev/null +++ b/source/repositories/invitation.ts @@ -0,0 +1,334 @@ +/* +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 +. + */ + +namespace _espe.repository.invitation +{ + + /** + */ + type type_group_chest = lib_plankton.storage.type_chest< + Array, + Record, + lib_plankton.database.type_description_create_table, + lib_plankton.storage.sql_table_common.type_sql_table_common_search_term, + Record + >; + + + /** + */ + var _core_store : ( + null + | + lib_plankton.storage.type_store< + _espe.type.invitation_id, + Record, + {}, + lib_plankton.storage.type_sql_table_autokey_search_term, + Record + > + ) = null; + + + /** + */ + var _group_chest : ( + null + | + type_group_chest + ) = null; + + + /** + */ + function get_core_store( + ) : lib_plankton.storage.type_store< + _espe.type.invitation_id, + Record, + {}, + lib_plankton.storage.type_sql_table_autokey_search_term, + Record + > + { + if (_core_store === null) { + _core_store = lib_plankton.storage.sql_table_autokey_store( + { + "database_implementation": _espe.helpers.database_implementation(), + "table_name": "invitations", + "key_name": "id", + } + ); + } + else { + // do nothing + } + 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": "invitation_groups", + "key_names": ["invitation_id","group_id"], + } + ); + } + else { + // do nothing + } + return _group_chest; + } + + + /** + */ + type type_dispersal = { + core_row : Record; + group_rows : Array< + Record + >; + }; + + + /** + */ + function encode( + object : _espe.type.invitation_object + ) : type_dispersal + { + return { + "core_row": { + "key": object.key, + "expiry": object.expiry, + "name_changeable": _espe.helpers.dbbool_encode(object.name_changeable), + "name_value": object.name_value, + "label_changeable": _espe.helpers.dbbool_encode(object.label_changeable), + "label_value": object.label_value, + "email_address_changeable": _espe.helpers.dbbool_encode(object.email_address_changeable), + "email_address_value": object.email_address_value, + "groups_changeable": _espe.helpers.dbbool_encode(object.groups_changeable), + }, + "group_rows": ( + (object.groups_value ?? []) + .map( + group_id => ({ + "group_id": group_id, + }) + ) + ) + }; + } + + + /** + */ + function decode( + dispersal : type_dispersal + ) : _espe.type.invitation_object + { + return { + "key": dispersal.core_row["key"], + "expiry": dispersal.core_row["expiry"], + "name_changeable": _espe.helpers.dbbool_decode(dispersal.core_row["name_changeable"]), + "name_value": dispersal.core_row["name_value"], + "label_changeable": _espe.helpers.dbbool_decode(dispersal.core_row["label_changeable"]), + "label_value": dispersal.core_row["label_value"], + "email_address_changeable": _espe.helpers.dbbool_decode(dispersal.core_row["email_address_changeable"]), + "email_address_value": dispersal.core_row["email_address_value"], + "groups_changeable": _espe.helpers.dbbool_decode(dispersal.core_row["groups_changeable"]), + "groups_value": lib_plankton.list.sorted<_espe.type.group_id>( + dispersal.group_rows.map(row => row["group_id"]), + { + "compare_element": (group1, group2) => (group1 <= group2) + } + ), + }; + } + + + /** + * @todo optimize + */ + export async function list( + search_term : (null | string) + ) : Promise< + Array< + { + id : _espe.type.invitation_id; + preview : { + name : string; + }; + } + > + > + { + return ( + (await get_core_store().search(null)) + .filter( + ({"key": key, "preview": preview}) => ( + (search_term === null) + ? + true + : + (preview["key"] === search_term) + ) + ) + .map( + ({"key": key, "preview": preview}) => ({ + "id": key, + "preview": { + "name": preview["name_value"], + "label": preview["label_value"], + } + }) + ) + ); + } + + + /** + */ + export async function read( + id : _espe.type.invitation_id + ) : Promise<_espe.type.invitation_object> + { + const core_row : Record = await get_core_store().read(id); + const group_hits : Array<{key : Record; preview : Record;}> = await get_group_chest().search( + { + "expression": "invitation_id = $invitation_id", + "arguments": {"invitation_id": id} + } + ); + + const dispersal : type_dispersal = { + "core_row": core_row, + "group_rows": group_hits.map( + hit => ({ + "group_id": hit.preview["group_id"] + }) + ), + }; + + return decode(dispersal); + } + + + /** + */ + export async function create( + value : _espe.type.invitation_object + ) : Promise<_espe.type.invitation_id> + { + const dispersal : type_dispersal = encode(value); + + // core + const id : _espe.type.invitation_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_id"], + ], + { + "_dummy": null, + } + ); + } + + return id; + } + + + /** + */ + export async function delete_( + id : _espe.type.invitation_id + ) : Promise + { + // groups + const hits : Array<{key : Array; preview : Record;}> = await get_group_chest().search( + { + "expression": "invitation_id = $invitation_id", + "arguments": {"invitation_id": id} + } + ); + for (const hit of hits) { + await get_group_chest().delete(hit.key); + } + + // core + await get_core_store().delete(id); + } + + + /** + * @todo optimize + */ + export async function identify( + key : _espe.type.invitation_key + ) : Promise<_espe.type.invitation_id> + { + const hits : Array<{id : _espe.type.invitation_id; preview : any;}> = await list(key); + return ( + (hits.length !== 1) + ? + Promise.reject<_espe.type.invitation_id>(new Error("not found")) + : + Promise.resolve<_espe.type.invitation_id>(hits[0].id) + ); + } + + + /** + */ + export async function dump( + ) : Promise< + Array< + { + id : _espe.type.invitation_id; + object : _espe.type.invitation_object; + } + > + > + { + return ( + Promise.all( + (await get_core_store().search(null)) + .map(hit => hit.key) + .map( + id => ( + read(id) + .then( + (object) => ({ + "id": id, + "object": object + }) + ) + ) + ) + ) + ); + } + +} diff --git a/source/repositories/member.ts b/source/repositories/member.ts index e9e7398..7a8a6d3 100644 --- a/source/repositories/member.ts +++ b/source/repositories/member.ts @@ -88,7 +88,7 @@ namespace _espe.repository.member { "database_implementation": _espe.helpers.database_implementation(), "table_name": "member_groups", - "key_names": ["member_id","group_name"], + "key_names": ["member_id","group_id"], } ); } @@ -117,16 +117,10 @@ namespace _espe.repository.member { return { "core_row": { - "membership_number": object.membership_number, - "name_real_value": object.name_real_value, - "name_real_index": object.name_real_index, - "email_address_private": object.email_address_private, - "registered": (object.registered ? 1 : 0), - "enabled": (object.enabled ? 1 : 0), - "email_use_veiled_address": (object.email_use_veiled_address ? 1 : 0), - "email_use_nominal_address": (object.email_use_nominal_address ? 1 : 0), - "email_redirect_to_private_address": (object.email_redirect_to_private_address ? 1 : 0), - "email_allow_sending": (object.email_allow_sending ? 1 : 0), + "name": object.name, + "label": object.label, + "email_address": object.email_address, + "enabled": _espe.helpers.dbbool_encode(object.enabled), "password_image": object.password_image, "password_change_last_attempt": object.password_change_last_attempt, "password_change_token": object.password_change_token, @@ -135,7 +129,7 @@ namespace _espe.repository.member object.groups .map( group => ({ - "group_name": group, + "group_id": group, }) ) ) @@ -150,22 +144,16 @@ namespace _espe.repository.member ) : _espe.type.member_object { return { - "membership_number": dispersal.core_row["membership_number"], - "name_real_value": dispersal.core_row["name_real_value"], - "name_real_index": dispersal.core_row["name_real_index"], - "email_address_private": dispersal.core_row["email_address_private"], - "groups": lib_plankton.list.sorted( - dispersal.group_rows.map(row => row["group_name"]), + "name": dispersal.core_row["name"], + "label": dispersal.core_row["label"], + "email_address": dispersal.core_row["email_address"], + "groups": lib_plankton.list.sorted<_espe.type.group_id>( + dispersal.group_rows.map(row => row["group_id"]), { "compare_element": (group1, group2) => (group1 <= group2) } ), - "registered": (dispersal.core_row["registered"] > 0), - "enabled": (dispersal.core_row["enabled"] > 0), - "email_use_veiled_address": (dispersal.core_row["email_use_veiled_address"] > 0), - "email_use_nominal_address": (dispersal.core_row["email_use_nominal_address"] > 0), - "email_redirect_to_private_address": (dispersal.core_row["email_redirect_to_private_address"] > 0), - "email_allow_sending": (dispersal.core_row["email_allow_sending"] > 0), + "enabled": _espe.helpers.dbbool_decode(dispersal.core_row["enabled"]), "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"], @@ -183,9 +171,8 @@ namespace _espe.repository.member { id : _espe.type.member_id; preview : { - membership_number : string; - name_real_value : string; - name_real_index : int; + name : string; + label : string; }; } > @@ -200,21 +187,18 @@ namespace _espe.repository.member || (search_term.length <= 1) ) - ? true - : ( - preview["membership_number"].toLowerCase().includes(search_term.toLowerCase()) - || - preview["name_real_value"].toLowerCase().includes(search_term.toLowerCase()) - ) + ? + true + : + preview["name"].toLowerCase().includes(search_term.toLowerCase()) ) ) .map( ({"key": key, "preview": preview}) => ({ "id": key, "preview": { - "membership_number": preview["membership_number"], - "name_real_value": preview["name_real_value"], - "name_real_index": preview["name_real_index"], + "name": preview["name"], + "label": preview["label"], } }) ) @@ -240,7 +224,7 @@ namespace _espe.repository.member "core_row": core_row, "group_rows": group_hits.map( hit => ({ - "group_name": hit.preview["group_name"] + "group_id": hit.preview["group_id"] }) ), }; @@ -265,7 +249,7 @@ namespace _espe.repository.member await get_group_chest().write( [ id, - group_row["group_name"], + group_row["group_id"], ], { "_dummy": null, @@ -297,7 +281,6 @@ namespace _espe.repository.member "arguments": {"member_id": id} } ); - lib_plankton.log.info("update_hit", hits); for (const hit of hits) { await get_group_chest().delete(hit.key); } @@ -305,7 +288,7 @@ namespace _espe.repository.member await get_group_chest().write( [ id, - group_row["group_name"], + group_row["group_id"], ], { "_dummy": null, diff --git a/source/repositories/name_index.ts b/source/repositories/name_index.ts deleted file mode 100644 index a3afda9..0000000 --- a/source/repositories/name_index.ts +++ /dev/null @@ -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 -. - */ - -namespace _espe.repository.name_index -{ - - /** - */ - var _chest : ( - null - | - lib_plankton.storage.type_chest< - Array, - Record, - lib_plankton.database.type_description_create_table, - lib_plankton.storage.sql_table_common.type_sql_table_common_search_term, - Record - > - ) = null; - - - /** - */ - function get_chest( - ) : lib_plankton.storage.type_chest< - Array, - Record, - lib_plankton.database.type_description_create_table, - lib_plankton.storage.sql_table_common.type_sql_table_common_search_term, - Record - > - { - 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 - { - 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 - { - const name_image : string = await get_name_image(name); - let row : Record; - try { - row = await get_chest().read([name_image]); - return row["index"]; - } - catch (error) { - return 0; - } - } - - - /** - */ - export async function write( - name : string, - index : int - ) : Promise - { - const name_image : string = await get_name_image(name); - await get_chest().write([name_image], {"index": index}); - } - -} - diff --git a/source/sample.ts b/source/sample.ts new file mode 100644 index 0000000..f0db8f7 --- /dev/null +++ b/source/sample.ts @@ -0,0 +1,146 @@ +/* +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 +. + */ + +namespace _espe.sample +{ + + /** + */ + type type_data = { + groups : Array< + { + id : int; + name : string; + label : string; + } + >; + admins : Array< + { + id : int; + name : string; + email_address : (null | string); + password : string; + } + >; + members : Array< + { + id : int; + name : string; + label : string; + email_address : (null | string); + groups : Array; + } + >; + invitations : Array< + { + id : int; + name_changeable : boolean; + name_value : string; + label_changeable : boolean; + label_value : string; + email_address_changeable : boolean; + email_address_value : (null | string); + groups_changeable : boolean; + groups_value : Array; + } + >; + }; + + + /** + */ + export async function fill( + data : type_data + ) : Promise + { + const track_groups : Map = new Map(); + // groups + { + for (const group_raw of data.groups) + { + const group_id : _espe.type.group_id = await _espe.service.group.add( + { + "name": group_raw.name, + "label": group_raw.label, + } + ); + track_groups.set(group_raw.id, group_id); + } + } + // admins + { + for (const admin_raw of data.admins) + { + const admin_id : _espe.type.admin_id = await _espe.service.admin.add( + admin_raw.name, + admin_raw.email_address, + admin_raw.password, + ); + } + } + // members + { + for (const member_raw of data.members) + { + const member_id : _espe.type.member_id = await _espe.service.member.project( + { + "name": member_raw.name, + "label": member_raw.label, + "email_address": member_raw.email_address, + "groups": member_raw.groups.map(group_id => track_groups.get(group_id)), + }, + { + "silent": true, + } + ); + } + /** + * @todo passwords + */ + } + // invitations + { + for (const invitation_raw of data.invitations) + { + const result : {id : _espe.type.invitation_id; key : _espe.type.invitation_key;} = await _espe.service.invitation.create( + { + "name_changeable": invitation_raw.name_changeable, + "name_value": invitation_raw.name_value, + "label_changeable": invitation_raw.label_changeable, + "label_value": invitation_raw.label_value, + "email_address_changeable": invitation_raw.email_address_changeable, + "email_address_value": invitation_raw.email_address_value, + "groups_changeable": invitation_raw.groups_changeable, + "groups_value": invitation_raw.groups_value.map(group_id => track_groups.get(group_id)), + } + ); + } + } + } + + + /** + */ + export async function fill_by_path( + path : string + ) : Promise + { + const content : string = await lib_plankton.file.read(path); + const data : type_data = (lib_plankton.json.decode(content) as type_data); + await fill(data); + } + +} + diff --git a/source/services/group.ts b/source/services/group.ts new file mode 100644 index 0000000..3f89862 --- /dev/null +++ b/source/services/group.ts @@ -0,0 +1,70 @@ +/* +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 +. + */ + +namespace _espe.service.group +{ + + /** + */ + export function list( + ) : Promise< + Array< + { + id : _espe.type.group_id; + preview : { + name : string; + label : string; + }; + } + > + > + { + return _espe.repository.group.list(null); + } + + + /** + */ + export function get( + id : _espe.type.group_id + ) : Promise<_espe.type.group_object> + { + return _espe.repository.group.read(id); + } + + + /** + */ + export function add( + object : _espe.type.group_object + ) : Promise<_espe.type.group_id> + { + return _espe.repository.group.create(object); + } + + + /** + */ + export async function modify( + id : _espe.type.group_id, + label : string + ) : Promise + { + const object : _espe.type.group_object = await _espe.repository.group.read(id); + object.label = label; + await _espe.repository.group.update(id, object); + } + +} diff --git a/source/services/invitation.ts b/source/services/invitation.ts new file mode 100644 index 0000000..5a5d513 --- /dev/null +++ b/source/services/invitation.ts @@ -0,0 +1,379 @@ +/* +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 +. + */ + +namespace _espe.service.invitation +{ + + /** + */ + export function list( + ) : Promise< + Array< + { + id : _espe.type.invitation_id; + preview : { + key : _espe.type.invitation_key; + expiry : (null | int); + name_value : (null | string); + label_value : (null | string); + }; + } + > + > + { + return ( + _espe.repository.invitation.dump() + .then( + entries => Promise.resolve( + entries.map( + entry => ({ + "id": entry.id, + "preview": { + "key": entry.object.key, + "expiry": entry.object.expiry, + "name_value": entry.object.name_value, + "label_value": entry.object.label_value, + } + }) + ) + ) + ) + ); + } + + + /** + */ + export async function create( + { + "name_changeable": name_changeable, + "name_value": name_value, + "label_changeable": label_changeable, + "label_value": label_value, + "email_address_changeable": email_address_changeable, + "email_address_value": email_address_value, + "groups_changeable": groups_changeable, + "groups_value": groups_value, + } : { + name_changeable : boolean; + name_value : string; + label_changeable : boolean; + label_value : string; + email_address_changeable : boolean; + email_address_value : (null | string); + groups_changeable : boolean; + groups_value : Array<_espe.type.group_id>; + }, + { + "expiry": expiry = -1, + "notification_target_url_template": notification_target_url_template = null, + "send_immediatly": send_immediatly = true, + } : { + expiry ?: (null | int); + notification_target_url_template ?: (null | string); + send_immediatly ?: boolean; + } = { + } + ) : Promise<{id : _espe.type.invitation_id; key : _espe.type.invitation_key}> + { + /** + * @todo outsource to conf + */ + const default_lifetime : int = (60 * 60 * 24 * 7 * 2); + /** + * @todo proper salt + */ + const invitation_key : _espe.type.invitation_key = lib_plankton.sha256.get( + ( + name_value + + + "/" + + + lib_plankton.base.get_current_timestamp(true).toFixed(0) + ), + "secret" + ); + const invitation_object : _espe.type.invitation_object = { + "key": invitation_key, + "expiry": ( + ((expiry !== null) && (expiry < 0)) + ? + (lib_plankton.base.get_current_timestamp(true) + default_lifetime) + : + expiry + ), + "name_changeable": name_changeable, + "name_value": name_value, + "label_changeable": label_changeable, + "label_value": label_value, + "email_address_changeable": email_address_changeable, + "email_address_value": email_address_value, + "groups_changeable": groups_changeable, + "groups_value": groups_value, + }; + const invitation_id : _espe.type.invitation_id = await _espe.repository.invitation.create(invitation_object); + // send link + { + if (! send_immediatly) + { + // do nothing + } + else + { + if ( + ! ( + ( + (email_address_value !== null) + && + (email_address_value !== "") + ) + && + (notification_target_url_template !== null) + ) + ) + { + lib_plankton.log._warning( + "espe.service.invitation.create.email.condition_unmet", + { + "details": { + "provided_address": email_address_value, + "notification_target_url_template": notification_target_url_template, + }, + } + ); + } + else + { + const url : (null | string) = _espe.helpers.frontend_url_get( + notification_target_url_template, + { + "key": invitation_key, + } + ); + try { + await _espe.helpers.email_send( + [email_address_value], + lib_plankton.translate.get( + "email.invitation.subject", + { + } + ), + lib_plankton.translate.get( + "email.invitation.body", + { + "url": (url ?? "?"), + } + ), + ); + } + catch (error) + { + lib_plankton.log._error( + "espe.service.invitation.create.email.could_not_be_sent", + { + "details": { + "provided_address": email_address_value, + "error": String(error), + }, + } + ); + } + } + } + } + return { + "id": invitation_id, + "key": invitation_key, + }; + } + + + /** + */ + export function get_by_id( + id : _espe.type.invitation_id + ) : Promise<_espe.type.invitation_object> + { + return _espe.repository.invitation.read(id) + } + + + /** + */ + function get_by_key( + key : _espe.type.invitation_key + ) : Promise<_espe.type.invitation_object> + { + return ( + _espe.repository.invitation.identify(key) + .then( + (id) => _espe.repository.invitation.read(id) + ) + ); + } + + + /** + */ + export async function examine( + key : _espe.type.invitation_key + ) : Promise<_espe.type.invitation_object> + { + let invitation_object : (null | _espe.type.invitation_object); + try { + invitation_object = await get_by_key(key); + } + catch (error) { + invitation_object = null; + } + if (invitation_object === null) { + return Promise.reject(new Error("not found")) + } + else { + const now : int = lib_plankton.base.get_current_timestamp(true); + if ((invitation_object.expiry !== null) && (invitation_object.expiry < now)) { + return Promise.reject(new Error("expired")); + } + else { + return Promise.resolve(invitation_object); + } + } + } + + + /** + */ + export async function accept( + key : _espe.type.invitation_key, + data : { + name : (null | string); + label : (null | string); + groups : (null | Array<_espe.type.group_id>); + email_address : (null | string); + password : (null | string); + } + ) + : Promise< + Array< + { + incident : string; + details : Record; + } + > + > + { + 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 + */ + const invitation_object : _espe.type.invitation_object = await _espe.repository.invitation.read(invitation_id); + const now : int = lib_plankton.base.get_current_timestamp(true); + if ((invitation_object.expiry !== null) && (invitation_object.expiry < now)) + { + return Promise.reject(new Error("expired")); + } + else + { + const password : string = ( + ( + (data.password !== null) + && + (data.password !== "") + ) + ? + data.password + : + _espe.service.member.generate_password() + ); + const flaws_password : Array< + { + incident : string; + details : Record; + } + > = _espe.service.member.validate_password(password); + if (flaws_password.length > 0) + { + return ( + flaws_password + .map(flaw => ({"incident": ("password_" + flaw.incident), "details": flaw.details})) + ); + } + else + { + if ( + (invitation_object.name_value === null) + && + (data.name === null) + ) + { + throw (new Error("no name provided")); + } + else + { + const member_id : _espe.type.member_id = await _espe.service.member.add( + { + "name": ( + ( + invitation_object.name_changeable + ? + data.name + : + invitation_object.name_value + ) as string + ), + "label": ( + ( + invitation_object.label_changeable + ? + data.label + : + invitation_object.label_value + ) as string + ), + "email_address": ( + ( + invitation_object.email_address_changeable + && + (data.email_address !== null) + ) + ? + data.email_address + : + invitation_object.email_address_value + ), + "groups": ( + ( + invitation_object.groups_changeable + ? + data.groups + : + invitation_object.groups_value + ) + ?? + [] + ), + "password": password, + } + ); + + await _espe.repository.invitation.delete_(invitation_id); + + return []; + } + } + } + } + +} diff --git a/source/services/member.ts b/source/services/member.ts index 9ee771c..98ee48f 100644 --- a/source/services/member.ts +++ b/source/services/member.ts @@ -46,7 +46,7 @@ namespace _espe.service.member /** */ - function validate_password( + export function validate_password( password : string ) : Array<{incident : string; details : Record}> { @@ -59,7 +59,7 @@ namespace _espe.service.member /** */ - function generate_password( + export function generate_password( ) : string { return _espe.helper.password.generate( @@ -79,7 +79,7 @@ namespace _espe.service.member "{{object}}{{extension}}", { "object": lib_plankton.call.convey( - object.name_real_value, + object.name, [ (x : string) => x.toLowerCase(), (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"), "_"), ] ), - "extension": ( - (object.name_real_index <= 1) - ? "" - : ("." + object.name_real_index.toFixed(0)) - ), + "extension": "", } ); } @@ -106,46 +102,11 @@ namespace _espe.service.member export function name_display( object : _espe.type.member_object ) : string - { - return object.name_real_value; - } - - - /** - * 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, - } + object.label + ?? + object.name ); } @@ -157,15 +118,7 @@ namespace _espe.service.member object : _espe.type.member_object ) : (null | string) { - return ( - object.email_use_nominal_address - ? email_address_nominal(object) - : ( - object.email_use_veiled_address - ? email_address_veiled(object) - : object.email_address_private - ) - ); + return object.email_address; } @@ -207,13 +160,15 @@ namespace _espe.service.member // do nothing } else { - if (member_object.email_address_private === null) { + if (member_object.email_address === null) + { // do nothing } - else { + else + { await _espe.helpers.email_send( [ - member_object.email_address_private, + member_object.email_address, ], lib_plankton.string.coin( "{{head}} | {{core}}", @@ -277,9 +232,8 @@ namespace _espe.service.member { id : _espe.type.member_id; preview : { - membership_number : string; - name_real_value : string; - name_real_index : int; + name : string; + label : string; }; } > @@ -306,32 +260,80 @@ namespace _espe.service.member */ export async function project( data : { - membership_number : (null | string); - name_real_value : string; - email_address_private : (null | string); - groups : Array; + name : string; + label : string; + email_address : (null | string); + groups : Array<_espe.type.group_id>; + }, + { + "silent": silent = false, + } : { + silent ?: boolean; + } = { } ) : 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 = { - "membership_number": data.membership_number, - "name_real_value": data.name_real_value, - "name_real_index": name_real_index, - "email_address_private": data.email_address_private, - "registered": false, + "name": data.name, + "label": data.label, + "email_address": data.email_address, + "groups": data.groups, "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_change_last_attempt": null, "password_change_token": null, - "groups": data.groups, }; const id : _espe.type.member_id = await _espe.repository.member.create(object); - signal_change(); + if (silent) + { + // do nothing + } + else + { + signal_change(); + } + return id; + } + + + /** + * legt ein Mitglied an + */ + export async function add( + data : { + name : string; + label : string; + email_address : (null | string); + groups : Array<_espe.type.group_id>; + password : string; + }, + { + "silent": silent = false, + } : { + silent ?: boolean; + } = { + } + ) : Promise<_espe.type.member_id> + { + const object : _espe.type.member_object = { + "name": data.name, + "label": data.label, + "email_address": data.email_address, + "groups": data.groups, + "enabled": true, + "password_image": await password_image(data.password), + "password_change_last_attempt": null, + "password_change_token": null, + }; + const id : _espe.type.member_id = await _espe.repository.member.create(object); + if (silent) + { + // do nothing + } + else + { + signal_change(); + } return id; } @@ -346,10 +348,12 @@ namespace _espe.service.member { _espe.helpers.frontend_url_check(); 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; } - else { + else + { const url : (null | string) = _espe.helpers.frontend_url_get( url_template, { @@ -362,7 +366,7 @@ namespace _espe.service.member else { await _espe.helpers.email_send( [ - member_object.email_address_private, + member_object.email_address, ], lib_plankton.string.coin( "{{head}} | {{core}}", @@ -390,176 +394,33 @@ 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;}>> - { - 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;}> = []; - 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 */ export async function modify( member_id : _espe.type.member_id, data : { - email_address_private : (null | string); - registered : boolean; + label : string; + email_address : (null | string); enabled : boolean; - groups : lib_plankton.pod.type_pod>; + groups : lib_plankton.pod.type_pod>; } ) : Promise { const member_object_old : _espe.type.member_object = await get(member_id); const member_object_new : _espe.type.member_object = { - "membership_number": member_object_old.membership_number, - "name_real_value": member_object_old.name_real_value, - "name_real_index": member_object_old.name_real_index, - "email_address_private": data.email_address_private, - "registered": data.registered, + "name": member_object_old.name, + "label": data.label, + "email_address": data.email_address, + "groups": ( + lib_plankton.pod.is_filled>(data.groups) + ? lib_plankton.pod.cull>(data.groups) + : member_object_old.groups + ), "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_change_last_attempt": member_object_old.password_change_last_attempt, "password_change_token": member_object_old.password_change_token, - "groups": ( - lib_plankton.pod.is_filled>(data.groups) - ? lib_plankton.pod.cull>(data.groups) - : member_object_old.groups - ), }; await _espe.repository.member.update(member_id, member_object_new); signal_change(); @@ -595,15 +456,13 @@ namespace _espe.service.member (await _espe.repository.member.dump()) .filter( member_entry => ( - member_entry.object.registered - && 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) @@ -632,9 +491,9 @@ namespace _espe.service.member // do nothing } else { - if (member_object_old.email_address_private === null) { + if (member_object_old.email_address === null) { 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, } @@ -645,20 +504,14 @@ namespace _espe.service.member // keine echte Verifizierung, der Algorithmus ist aber der passende const token : string = await _espe.helpers.verification_get(Math.floor(Math.random() * (1 << 24))); const member_object_new : _espe.type.member_object = { - "membership_number": member_object_old.membership_number, - "name_real_value": member_object_old.name_real_value, - "name_real_index": member_object_old.name_real_index, - "email_address_private": member_object_old.email_address_private, - "registered": member_object_old.registered, + "name": member_object_old.name, + "label": member_object_old.label, + "email_address": member_object_old.email_address, "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, + "groups": member_object_old.groups, "password_image": member_object_old.password_image, "password_change_last_attempt": now, "password_change_token": token, - "groups": member_object_old.groups, }; await _espe.repository.member.update(member_id, member_object_new); // signal_change(); @@ -676,7 +529,7 @@ namespace _espe.service.member else { /*await*/ _espe.helpers.email_send( [ - member_object_old.email_address_private, + member_object_old.email_address, ], lib_plankton.string.coin( "{{head}} | {{core}}", @@ -712,10 +565,12 @@ namespace _espe.service.member ) : Promise;}>> { 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")); } - else { + else + { let flaws : Array<{incident : string; details : Record;}> = []; if ( (member_object_old.password_change_token === null) @@ -741,26 +596,20 @@ namespace _espe.service.member } else { const member_object_new : _espe.type.member_object = { - "membership_number": member_object_old.membership_number, - "name_real_value": member_object_old.name_real_value, - "name_real_index": member_object_old.name_real_index, - "email_address_private": member_object_old.email_address_private, - "registered": member_object_old.registered, + "name": member_object_old.name, + "label": member_object_old.label, + "email_address": member_object_old.email_address, + "groups": member_object_old.groups, "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_change_last_attempt": member_object_old.password_change_last_attempt, "password_change_token": null, - "groups": member_object_old.groups, }; await _espe.repository.member.update(member_id, member_object_new); signal_change(); await _espe.helpers.email_send( [ - member_object_old.email_address_private, + member_object_old.email_address, ], lib_plankton.string.coin( "{{head}} | {{core}}", @@ -787,18 +636,14 @@ namespace _espe.service.member * @todo check validity (e.g. username characters) */ export async function export_authelia_user_data( - options : { + { + "custom_data": custom_data = null, + } : { custom_data ?: (null | Array<_espe.type.member_object>); - } = {} + } = { + } ) : Promise { - options = Object.assign( - { - "custom_data": null, - }, - options - ); - type type_entry = { disabled : boolean; displayname : string; @@ -806,14 +651,42 @@ namespace _espe.service.member groups : Array; password : string; }; + + const groups_as_array : Array< + { + id : _espe.type.group_id; + preview : { + name : string; + label : string; + }; + } + > = await _espe.service.group.list(); + const groups_as_map : Map< + _espe.type.group_id, + _espe.type.group_object + > = new Map< + _espe.type.group_id, + _espe.type.group_object + >(); + for (const group_entry of groups_as_array) + { + groups_as_map.set( + group_entry.id, + { + "name": group_entry.preview.name, + "label": group_entry.preview.label, + } + ); + } + return lib_plankton.call.convey( ( ( - (options.custom_data !== undefined) + (custom_data !== undefined) && - (options.custom_data !== null) + (custom_data !== null) ) - ? (options.custom_data.map((member_object, index) => ({"id": index, "object": member_object}))) + ? (custom_data.map((member_object, index) => ({"id": index, "object": member_object}))) : await dump() ), [ @@ -825,8 +698,6 @@ namespace _espe.service.member ), (x : Array) => x.filter( entry => ( - entry.object.registered - && ( (entry.object.password_image !== null) && @@ -843,7 +714,16 @@ namespace _espe.service.member "disabled": (! entry.object.enabled), "displayname": name_display(entry.object), "email": entry.email_address, - "groups": entry.object.groups, + "groups": ( + entry.object.groups + .map( + (group_id : _espe.type.group_id) => ( + groups_as_map.get(group_id)?.name + ?? + group_id.toFixed(0) + ) + ) + ), "password": entry.object.password_image, } ]) @@ -858,20 +738,16 @@ namespace _espe.service.member /** */ export async function export_authelia_user_file( - options : { + { + "custom_data": custom_data = null, + } : { custom_data ?: (null | Array<_espe.type.member_object>); - } = {} + } = { + } ) : Promise { - options = Object.assign( - { - "custom_data": null, - }, - options - ); - const nm_yaml = require("yaml"); - return nm_yaml.stringify(await export_authelia_user_data(options)); + return nm_yaml.stringify(await export_authelia_user_data({"custom_data": custom_data})); } diff --git a/source/services/name_index.ts b/source/services/name_index.ts deleted file mode 100644 index df4d45e..0000000 --- a/source/services/name_index.ts +++ /dev/null @@ -1,31 +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 -. - */ - -namespace _espe.service.name_index -{ - - /** - */ - export async function next( - name : string - ) : Promise - { - const current : int = await _espe.repository.name_index.read(name); - const result : int = (current + 1); - await _espe.repository.name_index.write(name, result); - return result; - } - -} diff --git a/source/types.ts b/source/types.ts index aa8c6c4..a0d4c38 100644 --- a/source/types.ts +++ b/source/types.ts @@ -16,6 +16,19 @@ You should have received a copy of the GNU General Public License along with thi namespace _espe.type { + /** + */ + export type group_id = int; + + + /** + */ + export type group_object = { + name : string; + label : string; + }; + + /** */ export type admin_id = int; @@ -39,20 +52,41 @@ namespace _espe.type /** */ export type member_object = { - membership_number : (null | string); - name_real_value : string; - name_real_index : int; - email_address_private : (null | string); - groups : Array; - registered : boolean; + name : string; + label : string; + email_address : (null | string); + groups : Array; 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_change_last_attempt : (null | int); password_change_token : (null | string); }; + + /** + */ + export type invitation_id = int; + + + /** + */ + export type invitation_key = string; + + + /** + * @todo use "pod" instead of "changable"/"value" + */ + export type invitation_object = { + key : invitation_key; + expiry : (null | int); + name_changeable : boolean; + name_value : (null | string); + label_changeable : boolean; + label_value : (null | string); + email_address_changeable : boolean; + email_address_value : (null | string); + groups_changeable : boolean; + groups_value : (null | Array<_espe.type.group_id>); + }; + } diff --git a/todo.md b/todo.md new file mode 100644 index 0000000..3a53d88 --- /dev/null +++ b/todo.md @@ -0,0 +1,2 @@ +- Niederschreiben (Logging) von geheimen Angaben verhindern + diff --git a/tools/build b/tools/build index 25bfa98..4921e92 100755 --- a/tools/build +++ b/tools/build @@ -15,12 +15,21 @@ import sys as _sys import os as _os +import shutil as _shutil import argparse as _argparse def main(): ## args argument_parser = _argparse.ArgumentParser() + argument_parser.add_argument( + "-o", + "--output-directory", + type = str, + default = "/tmp/espe", + metavar = "", + help = "output directory", + ) argument_parser.add_argument( "-t", "--tests", @@ -29,12 +38,12 @@ def main(): help = "whether to also build the test routines", ) argument_parser.add_argument( - "-o", - "--output-directory", + "-c", + "--conf-path", type = str, - default = "/tmp/espe", - metavar = "", - help = "output directory", + default = "", + metavar = "", + help = "path to conf file to be put", ) args = argument_parser.parse_args() @@ -50,6 +59,11 @@ def main(): " ".join(targets), ) ) + if (args.conf_path != ""): + _shutil.copyfile( + args.conf_path, + _os.path.join(args.output_directory, "conf.json") + ) _sys.stdout.write("%s\n" % args.output_directory) diff --git a/tools/makefile b/tools/makefile index 9a44cc8..a621835 100644 --- a/tools/makefile +++ b/tools/makefile @@ -48,27 +48,34 @@ ${dir_temp}/espe-core.js ${dir_temp}/espe-core.d.ts: \ ${dir_source}/helpers/password.ts \ ${dir_source}/database.ts \ ${dir_source}/types.ts \ + ${dir_source}/repositories/group.ts \ ${dir_source}/repositories/admin.ts \ - ${dir_source}/repositories/name_index.ts \ ${dir_source}/repositories/member.ts \ - ${dir_source}/services/name_index.ts \ - ${dir_source}/services/member.ts \ + ${dir_source}/repositories/invitation.ts \ + ${dir_source}/services/group.ts \ ${dir_source}/services/admin.ts \ + ${dir_source}/services/member.ts \ + ${dir_source}/services/invitation.ts \ ${dir_source}/api/base.ts \ ${dir_source}/api/actions/meta_ping.ts \ ${dir_source}/api/actions/meta_spec.ts \ ${dir_source}/api/actions/session_begin.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_info.ts \ - ${dir_source}/api/actions/member_register.ts \ + ${dir_source}/api/actions/group_list.ts \ + ${dir_source}/api/actions/group_read.ts \ + ${dir_source}/api/actions/group_add.ts \ + ${dir_source}/api/actions/group_modify.ts \ ${dir_source}/api/actions/member_list.ts \ ${dir_source}/api/actions/member_read.ts \ ${dir_source}/api/actions/member_modify.ts \ ${dir_source}/api/actions/member_delete.ts \ ${dir_source}/api/actions/member_password_change_initialize.ts \ ${dir_source}/api/actions/member_password_change_execute.ts \ + ${dir_source}/api/actions/invitation_list.ts \ + ${dir_source}/api/actions/invitation_read.ts \ + ${dir_source}/api/actions/invitation_create.ts \ + ${dir_source}/api/actions/invitation_examine.ts \ + ${dir_source}/api/actions/invitation_accept.ts \ ${dir_source}/api/functions.ts \ ${dir_source}/conf.ts @ ${cmd_log} "compile | core …" @@ -81,6 +88,7 @@ main: core ${dir_build}/espe data ${dir_temp}/espe-main-raw.js: \ ${dir_lib}/plankton/plankton.d.ts \ ${dir_temp}/espe-core.d.ts \ + ${dir_source}/sample.ts \ ${dir_source}/main.ts @ ${cmd_log} "compile | main …" @ ${cmd_mkdir} $(dir $@)