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 $@)