diff --git a/source/api/actions/invite_create.ts b/source/api/actions/invite_create.ts index f43f953..ac59873 100644 --- a/source/api/actions/invite_create.ts +++ b/source/api/actions/invite_create.ts @@ -24,16 +24,19 @@ namespace _espe.api { lib_plankton.rest_http.register< { - membership_number_changeable : boolean; - membership_number_value : (null | string); - name_changeable : boolean; - name_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); + data : { + membership_number_changeable : boolean; + membership_number_value : (null | string); + name_changeable : boolean; + name_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 @@ -57,73 +60,87 @@ namespace _espe.api "nullable": false, "additionalProperties": false, "properties": { - "membership_number_changeable": { - "type": "boolean", + "data": { "nullable": false, - "description": "Mitgliedsnummer | änderbar" - }, - "membership_number_value": { - "type": "string", - "nullable": true, - "description": "Mitgliedsnummer | Wert" - }, - "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": "string", - "nullable": false, + "type": "object", + "additionalProperties": false, + "properties": { + "expiry": { + "nullable": true, + "type": "intiger", + "description": "Ablaufzeitpunkt" + }, + "membership_number_changeable": { + "type": "boolean", + "nullable": false, + "description": "Mitgliedsnummer | änderbar" + }, + "membership_number_value": { + "type": "string", + "nullable": true, + "description": "Mitgliedsnummer | Wert" + }, + "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": "string", + "nullable": false, + }, + "description": "Gruppen | Wert" + }, }, - "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", + ] }, - "expiry": { - "nullable": true, - "type": "intiger", - "description": "Ablaufzeitpunkt" - }, - /* "notification_target_url_template": { "type": "string", "nullable": true, - "description": "Platz-Halter: id" + "description": "Platz-Halter: key" + }, + "send_immediatly": { + "nullable": false, + "type": "boolean", + "description": "Einladungs-Link direkt an angegebene E-Mail-Adresse versenden" }, - */ }, "required": [ - "membership_number_changeable", - "membership_number_value", - "name_changeable", - "name_value", - "email_address_changeable", - "email_address_value", - "groups_changeable", - "groups_value", - "expiry", + "data", + "send_immediatly", ] }), "output_schema": () => ({ @@ -155,9 +172,9 @@ namespace _espe.api (! _espe.conf.get().settings.misc.facultative_membership_number) && ( - (input.membership_number_value === null) + (input.data.membership_number_value === null) || - (input.membership_number_value === "") + (input.data.membership_number_value === "") ) ) { return Promise.resolve({ @@ -168,17 +185,19 @@ namespace _espe.api else { const invite_info : {id : _espe.type.invite_id; key : _espe.type.invite_key;} = await _espe.service.invite.create( { - "membership_number_changeable": input.membership_number_changeable, - "membership_number_value": input.membership_number_value, - "name_changeable": input.name_changeable, - "name_value": input.name_value, - "email_address_changeable": input.email_address_changeable, - "email_address_value": input.email_address_value, - "groups_changeable": input.groups_changeable, - "groups_value": input.groups_value, + "membership_number_changeable": input.data.membership_number_changeable, + "membership_number_value": input.data.membership_number_value, + "name_changeable": input.data.name_changeable, + "name_value": input.data.name_value, + "email_address_changeable": input.data.email_address_changeable, + "email_address_value": input.data.email_address_value, + "groups_changeable": input.data.groups_changeable, + "groups_value": input.data.groups_value, }, { - "expiry": input.expiry, + "expiry": input.data.expiry, + "notification_target_url_template": input.notification_target_url_template, + "send_immediatly": input.send_immediatly, } ); return Promise.resolve({ diff --git a/source/api/actions/invite_read.ts b/source/api/actions/invite_read.ts new file mode 100644 index 0000000..fa3ebe6 --- /dev/null +++ b/source/api/actions/invite_read.ts @@ -0,0 +1,160 @@ +/* +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_invite_read( + rest_subject : lib_plankton.rest_http.type_rest + ) : void + { + lib_plankton.rest_http.register< + int, + ( + string + | + { + key : string; + expiry : (null | int); + membership_number_changeable : boolean; + membership_number_value : (null | string); + name_changeable : boolean; + name_value : string; + email_address_changeable : boolean; + email_address_value : (null | string); + groups_changeable : boolean; + groups_value : Array; + } + ) + >( + rest_subject, + lib_plankton.http.enum_method.get, + _espe.api.full_path("/invite/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" + }, + "membership_number_changeable": { + "type": "boolean", + "nullable": false, + "description": "Mitgliedsnummer | änderbar" + }, + "membership_number_value": { + "type": "string", + "nullable": true, + "description": "Mitgliedsnummer | Wert" + }, + "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_changeable": { + "type": "boolean", + "nullable": false, + "description": "Gruppen | änderbar" + }, + "groups_value": { + "nullable": false, + "type": "array", + "items": { + "type": "string", + "nullable": false, + }, + "description": "Gruppen | Wert" + }, + }, + "required": [ + "expiry", + "membership_number_mode", + "membership_number_value", + "name_mode", + "name_value", + "email_address_mode", + "email_address_value", + "groups_mode", + "groups_value", + ] + }), + "restriction": () => restriction_logged_in, + "execution": () => ({"query_parameters": query_parameters, "input": input}) => { + const invite_id : _espe.type.invite_id = parseInt(query_parameters["id"]); + return ( + _espe.service.invite.get_by_id(invite_id) + .then( + (invite_object) => Promise.resolve({ + "status_code": 200, + "data": { + "key": invite_object.key, + "expiry": invite_object.expiry, + "membership_number_changeable": invite_object.membership_number_changeable, + "membership_number_value": invite_object.membership_number_value, + "name_changeable": invite_object.name_changeable, + "name_value": invite_object.name_value, + "email_address_changeable": invite_object.email_address_changeable, + "email_address_value": invite_object.email_address_value, + "groups_changeable": invite_object.groups_changeable, + "groups_value": invite_object.groups_value, + } + }) + ) + .catch( + (error) => Promise.resolve({ + "status_code": 404, + "data": "not found" + }) + ) + ); + } + } + ); + } + +} diff --git a/source/api/functions.ts b/source/api/functions.ts index 04dd3a1..38b74c0 100644 --- a/source/api/functions.ts +++ b/source/api/functions.ts @@ -62,6 +62,7 @@ namespace _espe.api // invite { _espe.api.register_invite_list(rest_subject); + _espe.api.register_invite_read(rest_subject); _espe.api.register_invite_create(rest_subject); _espe.api.register_invite_examine(rest_subject); _espe.api.register_invite_accept(rest_subject); diff --git a/source/data/localization/deu.loc.json b/source/data/localization/deu.loc.json index 3a5bcc2..8b5a524 100644 --- a/source/data/localization/deu.loc.json +++ b/source/data/localization/deu.loc.json @@ -14,6 +14,8 @@ "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", diff --git a/source/data/localization/eng.loc.json b/source/data/localization/eng.loc.json index b822ef3..0450b8b 100644 --- a/source/data/localization/eng.loc.json +++ b/source/data/localization/eng.loc.json @@ -14,6 +14,8 @@ "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", diff --git a/source/services/invite.ts b/source/services/invite.ts index 7629633..522b4f1 100644 --- a/source/services/invite.ts +++ b/source/services/invite.ts @@ -72,8 +72,12 @@ namespace _espe.service.invite }, { "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.invite_id; key : _espe.type.invite_key}> @@ -114,6 +118,75 @@ namespace _espe.service.invite "groups_value": groups_value, }; const invite_id : _espe.type.invite_id = await _espe.repository.invite.create(invite_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.invite.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": invite_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.invite.create.email.could_not_be_sent", + { + "details": { + "provided_address": email_address_value, + "error": String(error), + }, + } + ); + } + } + } + } return { "id": invite_id, "key": invite_key, @@ -123,7 +196,17 @@ namespace _espe.service.invite /** */ - function get( + export function get_by_id( + id : _espe.type.invite_id + ) : Promise<_espe.type.invite_object> + { + return _espe.repository.invite.read(id) + } + + + /** + */ + function get_by_key( key : _espe.type.invite_key ) : Promise<_espe.type.invite_object> { @@ -144,7 +227,7 @@ namespace _espe.service.invite { let invite_object : (null | _espe.type.invite_object); try { - invite_object = await get(key); + invite_object = await get_by_key(key); } catch (error) { invite_object = null; diff --git a/tools/makefile b/tools/makefile index f1554e2..fdd7da5 100644 --- a/tools/makefile +++ b/tools/makefile @@ -72,6 +72,7 @@ ${dir_temp}/espe-core.js ${dir_temp}/espe-core.d.ts: \ ${dir_source}/api/actions/member_password_change_initialize.ts \ ${dir_source}/api/actions/member_password_change_execute.ts \ ${dir_source}/api/actions/invite_list.ts \ + ${dir_source}/api/actions/invite_read.ts \ ${dir_source}/api/actions/invite_create.ts \ ${dir_source}/api/actions/invite_examine.ts \ ${dir_source}/api/actions/invite_accept.ts \