Compare commits

...

16 commits

Author SHA1 Message Date
fenris 085d255ae7 [add] api:invitation_delete [mod] Frontend-URL-Templates nun in Konfiguration statt als Parameter von außen (conf 5 -> 6) 2025-10-05 23:24:47 +02:00
fenris 4538782d80 [mod] api:session_begin:eigens vorgesehene Funktion zum Zustammenstellen des Pfads nutzer 2025-10-05 22:42:46 +02:00
fenris cc244ad576 [fix] api:group_list:output_schema 2025-10-05 22:41:26 +02:00
fenris 8d803cbec1 [sty] api:member_password_change_initialize 2025-10-05 22:40:54 +02:00
fenris 7c9f856aa1 [mod] readme 2025-09-26 11:57:19 +02:00
fenris 79253873be [fix] tools:deploy 2025-09-22 21:26:50 +02:00
fenris 1ed2f003e4 [mod] readme 2025-09-22 21:26:42 +02:00
Christian Fraß ff5449766a [mod] Datei-Name der Beispiel-Konfiguration angepasst 2025-08-26 07:54:23 +00:00
Christian Fraß 547c957f42 [mod] Bei Annahme der Einladung E-Mails an Mitglied und Admins senden 2025-08-25 22:33:11 +00:00
Christian Fraß abd703981f [del] conf:settings.misc 2025-08-25 20:40:56 +00:00
Christian Fraß d77f0299b9 [del] conf:settings.name_index 2025-08-25 20:39:15 +00:00
Christian Fraß 9006170bdf [del] conf:settings.email 2025-08-25 20:38:20 +00:00
Christian Fraß 2485e9c680 [mod] invitation_handle 2025-08-25 14:08:20 +00:00
Christian Fraß d712412ac5 [mod] Gruppen und Mitglieder sortiert ausgeben 2025-08-25 13:00:43 +00:00
Christian Fraß aa17938c6e Einladungs-System (#6)
## Aufgaben

- [\#193](https://vikunja.ramsch.sx/tasks/193)

## Zugehörige MRs

- https://forgejo.linke.sx/espe/datamodel/pulls/3
- https://forgejo.linke.sx/espe/frontend-zackeneule/pulls/2

Reviewed-on: https://forgejo.linke.sx/espe/backend/pulls/6
Co-authored-by: Christian Fraß <christian.frass@dielinke-glauchau.de>
Co-committed-by: Christian Fraß <christian.frass@dielinke-glauchau.de>
2025-08-25 14:22:56 +02:00
Christian Fraß f3d510f2ec [upd] plankton [mod] logging 2025-04-03 06:19:19 +00:00
52 changed files with 6695 additions and 4193 deletions

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -2,8 +2,23 @@
"general": {
"language": null,
"verbosity": "info",
"verification_secret": null
"verification_secret": "foobar"
},
"log": [
{
"kind": "stdout",
"data": {
"threshold": "info"
}
},
{
"kind": "file",
"data": {
"threshold": "notice",
"path": "/tmp/espe/log.jsonl"
}
}
],
"server": {
"port": 4916,
"path_base": ""
@ -11,7 +26,7 @@
"database": {
"kind": "sqlite",
"data": {
"path": "data.sqlite"
"path": "../espe.sqlite"
}
},
"email_sending": {
@ -29,14 +44,6 @@
"name": "Example Orginsation",
"domain": "example.org"
},
"misc": {
"prefix_for_veiled_email_addresses": "member-",
"facultative_membership_number": true,
"auto_register": true
},
"summon_email": {
"remark": null
},
"password_policy": {
"minimum_length": 4,
"maximum_length": 12,
@ -47,16 +54,19 @@
"password_change": {
"cooldown_time": 300
},
"name_index": {
"veil": false,
"salt": null
},
"connections": {
"frontend_url_base": null,
"login_url": null
"frontend_url_base": "http://localhost:8888",
"frontend_path_template_invitation_handle": "/#invitation_handle,key={{key}}",
"frontend_path_template_password_change": "/#password_change_exec,id={{id}},key={{key}}",
"login_url": "https://login.example.org"
}
},
"output": {
"authelia": "/tmp/authelia-users.yml"
"outputs": [
{
"kind": "authelia_file",
"data": {
"path": "/tmp/authelia-users.yml"
}
}
]
}

76
misc/sampledata.json Normal file
View file

@ -0,0 +1,76 @@
{
"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],
"password": "aaa111"
},
{
"id": 2,
"name": "berthold",
"label": "Berthold Buche",
"email_address": "bert-ohne-ernie@example.org",
"groups": [4, 5, 2],
"password": "bbb222"
},
{
"id": 3,
"name": "charlotte",
"label": "Charlotte Castania",
"email_address": "charly-the-unicorn@example.org",
"groups": [4, 1],
"password": "ccc333"
}
],
"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]
}
]
}

View file

@ -2,7 +2,7 @@
## Beschreibung
- Hintergrund-Dienst für [Espe](https://gitlab.die-linke.cloud/espe/documentation)
- Hintergrund-Dienst für [Espe](/espe/meta)
## Erstellung

View file

@ -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
<https://www.gnu.org/licenses/>.
*/
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
});
}
}
}
);
}
}

View file

@ -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
<https://www.gnu.org/licenses/>.
*/
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"
},
"preview": {
"nullable": false,
"type": "object",
"properties": {
"name": {
"nullable": false,
"type": "string",
"description": "Name"
},
"label": {
"nullable": false,
"type": "string",
"description": "Beschriftung"
},
},
"additionalProperties": false,
"required": [
"name",
"label",
]
},
},
"required": [
"id",
"preview",
]
}),
"restriction": () => restriction_none,
"execution": () => async ({}) => {
const data = await _espe.service.group.list();
return Promise.resolve({
"status_code": 200,
"data": data,
});
}
}
);
}
}

View file

@ -0,0 +1,80 @@
/*
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Backend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
*/
namespace _espe.api
{
/**
*/
export function register_group_modify(
rest_subject : lib_plankton.rest_http.type_rest
) : void
{
lib_plankton.rest_http.register<
{
label : string;
},
null
>(
rest_subject,
lib_plankton.http.enum_method.patch,
_espe.api.full_path("/group/modify/:id"),
{
/**
* @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.resolve({
"status_code": 400,
"data": null
});
}
else {
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
});
}
}
}
);
}
}

View file

@ -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
<https://www.gnu.org/licenses/>.
*/
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
});
}
}
);
}
}

View file

@ -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
<https://www.gnu.org/licenses/>.
*/
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<int>);
email_address : (null | string);
password : (null | string);
};
},
Array<
{
incident : string;
details : Record<string, any>;
}
>
>(
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
});
}
}
}
}
);
}
}

View file

@ -0,0 +1,196 @@
/*
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Backend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
*/
namespace _espe.api
{
/**
*/
export function register_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<int>;
expiry : (null | int);
};
send_immediatly : boolean;
},
(
string
|
{
id : _espe.type.member_id;
key : string;
url : (null | 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",
]
},
"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,
},
"url": {
"type": "string",
"nullable": true,
},
},
"additionalProperties": false,
"required": [
"id",
"key",
"url",
]
}),
"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;
url : (null | string);
} = 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,
"send_immediatly": input.send_immediatly,
}
);
return Promise.resolve(
{
"status_code": 201,
"data": invitation_info
}
);
}
}
}
);
}
}

View file

@ -18,44 +18,46 @@ namespace _espe.api
/**
*/
export function register_member_summon(
rest_subject : lib_plankton.rest.type_rest
export function register_invitation_delete(
rest_subject : lib_plankton.rest_http.type_rest
) : void
{
lib_plankton.rest.register<
{
url_template : string;
},
(
lib_plankton.rest_http.register<
int,
null
|
string
)
>(
rest_subject,
lib_plankton.http.enum_method.post,
_espe.conf.get().server.path_base + "/member/summon/:id",
lib_plankton.http.enum_method.delete,
_espe.api.full_path("/invitation/delete"),
{
/**
* @todo translation
*/
"description": () => "löscht eine Einladung",
"input_schema": () => ({
"nullable": false,
"type": "integer",
}),
"output_schema": () => ({
"nullable": true,
}),
"restriction": () => restriction_logged_in,
"execution": () => async (stuff) => {
if (stuff.input === null)
{
"description": "sendet an ein Mitglied eine E-Mail mit Aufforderung zur Registrierung",
"restriction": restriction_logged_in,
"execution": async ({"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"]);
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({
else
{
const invitation_id : _espe.type.invitation_id = stuff.input;
await _espe.service.invitation.remove(
invitation_id
);
return Promise.resolve(
{
"status_code": 200,
"data": url,
})
"data": null
}
);
}
}
@ -64,3 +66,4 @@ namespace _espe.api
}
}

View file

@ -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
<https://www.gnu.org/licenses/>.
*/
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"
})
)
);
}
}
);
}
}

View file

@ -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
<https://www.gnu.org/licenses/>.
*/
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
});
}
}
);
}
}

View file

@ -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
<https://www.gnu.org/licenses/>.
*/
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<int>);
}
)
>(
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"
})
)
);
}
}
);
}
}

View file

@ -19,17 +19,20 @@ namespace _espe.api
/**
*/
export function register_member_delete(
rest_subject : lib_plankton.rest.type_rest
rest_subject : lib_plankton.rest_http.type_rest
) : void
{
register<null, null>(
lib_plankton.rest_http.register<
null,
null
>(
rest_subject,
lib_plankton.http.enum_method.delete,
"/member/delete/:id",
_espe.api.full_path("/member/delete/:id"),
{
"description": "löscht ein vorhandenes Mitglied",
"restriction": restriction_logged_in,
"execution": async ({"path_parameters": path_parameters}) => {
"description": () => "löscht ein vorhandenes Mitglied",
"restriction": () => restriction_logged_in,
"execution": () => async ({"path_parameters": path_parameters}) => {
const member_id : _espe.type.member_id = parseInt(path_parameters["id"]);
await _espe.service.member.remove(member_id);
return Promise.resolve({

View file

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

View file

@ -19,28 +19,27 @@ namespace _espe.api
/**
*/
export function register_member_list(
rest_subject : lib_plankton.rest.type_rest
rest_subject : lib_plankton.rest_http.type_rest
) : void
{
register<
lib_plankton.rest_http.register<
null,
Array<
{
id : int;
preview : {
membership_number : string;
name_real_value : string;
name_real_index : int;
name : string;
label : string;
};
}
>
>(
rest_subject,
lib_plankton.http.enum_method.get,
"/member/list",
_espe.api.full_path("/member/list"),
{
"description": "listet alle Mitglieder auf",
"query_parameters": [
"description": () => "listet alle Mitglieder auf",
"query_parameters": () => [
{
"name": "search_term",
"required": false,
@ -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",
]
}
},
@ -88,8 +82,8 @@ namespace _espe.api
],
}
}),
"restriction": restriction_logged_in,
"execution": ({"query_parameters": query_parameters}) => (
"restriction": () => restriction_logged_in,
"execution": () => ({"query_parameters": query_parameters}) => (
_espe.service.member.list(query_parameters["search_term"] ?? null)
.then(
data => Promise.resolve({

View file

@ -19,28 +19,32 @@ namespace _espe.api
/**
*/
export function register_member_modify(
rest_subject : lib_plankton.rest.type_rest
rest_subject : lib_plankton.rest_http.type_rest
) : void
{
register<
lib_plankton.rest_http.register<
{
email_address_private : (null | string);
groups ?: Array<string>;
registered : boolean;
label : string;
email_address : (null | string);
groups ?: Array<int>;
enabled : boolean;
},
null
>(
rest_subject,
lib_plankton.http.enum_method.patch,
"/member/modify/:id",
_espe.api.full_path("/member/modify/:id"),
{
"description": "ändert die Angaben eines vorhandenen Mitglieds",
"description": () => "ändert die Angaben eines vorhandenen Mitglieds",
"input_schema": () => ({
"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,16 +63,15 @@ namespace _espe.api
},
"additionalProperties": false,
"required": [
"email_address_private",
"registered",
"email_address",
"enabled",
]
}),
"output_schema": () => ({
"nullable": true,
}),
"restriction": restriction_logged_in,
"execution": async ({"path_parameters": path_parameters, "input": input}) => {
"restriction": () => restriction_logged_in,
"execution": () => async ({"path_parameters": path_parameters, "input": input}) => {
if (input === null) {
return Promise.reject(new Error("impossible"));
}
@ -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<Array<string>>()
: lib_plankton.pod.make_filled<Array<string>>(input.groups)
? lib_plankton.pod.make_empty<Array<_espe.type.group_id>>()
: lib_plankton.pod.make_filled<Array<_espe.type.group_id>>(input.groups)
),
"registered": input.registered,
"enabled": input.enabled,
}
);

View file

@ -20,10 +20,10 @@ namespace _espe.api
* @todo ausgeklügelte Durchsatzratenbegrenzung
*/
export function register_member_password_change_execute(
rest_subject : lib_plankton.rest.type_rest
rest_subject : lib_plankton.rest_http.type_rest
) : void
{
register<
lib_plankton.rest_http.register<
{
token : string;
password_new : string;
@ -37,9 +37,9 @@ namespace _espe.api
>(
rest_subject,
lib_plankton.http.enum_method.patch,
"/member/password_change/execute/:id",
_espe.api.full_path("/member/password_change/execute/:id"),
{
"description": "Führt eine Passwort-Änderung für ein Mitglied durch",
"description": () => "Führt eine Passwort-Änderung für ein Mitglied durch",
"input_schema": () => ({
"nullable": false,
"type": "object",
@ -88,8 +88,8 @@ namespace _espe.api
]
}
}),
"restriction": restriction_none,
"execution": ({"path_parameters": path_parameters, "input": input}) => {
"restriction": () => restriction_none,
"execution": () => ({"path_parameters": path_parameters, "input": input}) => {
if (input === null) {
return Promise.reject(new Error("impossible"));
}

View file

@ -21,10 +21,10 @@ namespace _espe.api
* @todo captcha
*/
export function register_member_password_change_initialize(
rest_subject : lib_plankton.rest.type_rest
rest_subject : lib_plankton.rest_http.type_rest
) : void
{
register<
lib_plankton.rest_http.register<
{
identifier : string;
url_template : string;
@ -33,9 +33,9 @@ namespace _espe.api
>(
rest_subject,
lib_plankton.http.enum_method.post,
"/member/password_change/initialize",
_espe.api.full_path("/member/password_change/initialize"),
{
"description": "Versucht dem gegebenen Identifikator ein Mitglied zuzuordnen und sendet dem ermittelten Mitglied einen Passwort-Änderungs-Verweis an die hinterlegte private E-Mail-Adresse",
"description": () => "Versucht dem gegebenen Identifikator ein Mitglied zuzuordnen und sendet dem ermittelten Mitglied einen Passwort-Änderungs-Verweis an die hinterlegte private E-Mail-Adresse",
"input_schema": () => ({
"nullable": false,
"type": "object",
@ -60,17 +60,24 @@ namespace _espe.api
"output_schema": () => ({
"nullable": true
}),
"restriction": restriction_none,
"execution": async ({"input": input}) => {
if (input === null) {
"restriction": () => restriction_none,
"execution": () => async ({"input": input}) => {
if (input === null)
{
return Promise.reject(new Error("impossible"));
}
else {
await _espe.service.member.password_change_initialize(input.identifier, input.url_template);
return Promise.resolve({
else
{
await _espe.service.member.password_change_initialize(
input.identifier,
input.url_template
);
return Promise.resolve(
{
"status_code": 200,
"data": null
});
}
);
}
},
}

View file

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

View file

@ -19,51 +19,38 @@ namespace _espe.api
/**
*/
export function register_member_read(
rest_subject : lib_plankton.rest.type_rest
rest_subject : lib_plankton.rest_http.type_rest
) : void
{
register<
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<string>;
registered : boolean;
name : string;
label : string;
email_address : (null | string);
groups : Array<int>;
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,
lib_plankton.http.enum_method.get,
"/member/read/:id",
_espe.api.full_path("/member/read/:id"),
{
"description": "gibt ein Mitglied anhand seiner ID aus",
"description": () => "gibt ein Mitglied anhand seiner ID aus",
"output_schema": () => ({
"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,91 +59,37 @@ 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,
"execution": async ({"path_parameters": path_parameters, "input": input}) => {
"restriction": () => restriction_logged_in,
"execution": () => async ({"path_parameters": path_parameters, "input": input}) => {
const member_id : _espe.type.member_id = parseInt(path_parameters["id"]);
const member_object : _espe.type.member_object = await _espe.service.member.get(member_id);
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),
},
});
}

View file

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

View file

@ -19,10 +19,10 @@ namespace _espe.api
/**
*/
export function register_meta_ping(
rest_subject : lib_plankton.rest.type_rest
rest_subject : lib_plankton.rest_http.type_rest
) : void
{
lib_plankton.rest.register<
lib_plankton.rest_http.register<
null,
string
>
@ -31,7 +31,7 @@ namespace _espe.api
lib_plankton.http.enum_method.get,
_espe.conf.get().server.path_base + "/meta/ping",
{
"description": "sendet ein 'pong' zurück; gedacht um die Erreichbarkeit des Backends zu prüfen",
"description": () => "sendet ein 'pong' zurück; gedacht um die Erreichbarkeit des Backends zu prüfen",
"input_schema": () => ({
"nullable": true,
}),
@ -39,8 +39,8 @@ namespace _espe.api
"nullable": false,
"type": "string",
}),
"restriction": restriction_none,
"execution": () => {
"restriction": () => restriction_none,
"execution": () => () => {
return Promise.resolve({
"status_code": 200,
"data": "pong",

View file

@ -19,10 +19,10 @@ namespace _espe.api
/**
*/
export function register_meta_spec(
rest_subject : lib_plankton.rest.type_rest
rest_subject : lib_plankton.rest_http.type_rest
) : void
{
lib_plankton.rest.register<
lib_plankton.rest_http.register<
null,
any
>
@ -31,17 +31,17 @@ namespace _espe.api
lib_plankton.http.enum_method.get,
_espe.conf.get().server.path_base + "/meta/spec",
{
"description": "gibt die API-Spezifikation im OpenAPI-Format aus",
"description": () => "gibt die API-Spezifikation im OpenAPI-Format aus",
"input_schema": () => ({
"nullable": true,
}),
"output_schema": () => ({
}),
"restriction": restriction_none,
"execution": () => {
"restriction": () => restriction_none,
"execution": () => () => {
return Promise.resolve({
"status_code": 200,
"data": lib_plankton.rest.to_oas(rest_subject),
"data": lib_plankton.rest_http.to_oas(rest_subject),
});
},
}

View file

@ -19,10 +19,10 @@ namespace _espe.api
/**
*/
export function register_session_begin(
rest_subject : lib_plankton.rest.type_rest
rest_subject : lib_plankton.rest_http.type_rest
) : void
{
lib_plankton.rest.register<
lib_plankton.rest_http.register<
{
name : string;
password : string;
@ -35,9 +35,9 @@ namespace _espe.api
>(
rest_subject,
lib_plankton.http.enum_method.post,
_espe.conf.get().server.path_base + "/session/begin",
_espe.api.full_path("/session/begin"),
{
"description": "führt die Anmeldung am System aus um geschützte Aktionen nutzen zu können",
"description": () => "führt die Anmeldung am System aus um geschützte Aktionen nutzen zu können",
"input_schema": () => ({
"type": "object",
"properties": {
@ -58,8 +58,8 @@ namespace _espe.api
"type": "string",
"description": "der Sitzungs-Schlüssel, der als Header 'X-Session-Key' gesetzt werden muss um Erlaubnis zur Ausführung geschützter Aktionen zu erhalten",
}),
"restriction": restriction_none,
"execution": async ({"input": input}) => {
"restriction": () => restriction_none,
"execution": () => async ({"input": input}) => {
if (input === null) {
return Promise.reject(new Error("impossible"));
}

View file

@ -19,23 +19,26 @@ namespace _espe.api
/**
*/
export function register_session_end(
rest_subject : lib_plankton.rest.type_rest
rest_subject : lib_plankton.rest_http.type_rest
) : void
{
register<null, null>(
lib_plankton.rest_http.register<
null,
null
>(
rest_subject,
lib_plankton.http.enum_method.delete,
"/session/end",
{
"description": "beendet eine Sitzung",
"description": () => "beendet eine Sitzung",
"input_schema": () => ({
"type": "null",
}),
"output_schema": () => ({
"type": "null",
}),
"restriction": restriction_logged_in,
"execution": async (stuff) => {
"restriction": () => restriction_logged_in,
"execution": () => async (stuff) => {
const session : {key : string; value : lib_plankton.session.type_session} = await session_from_stuff(stuff);
await lib_plankton.session.end(session.key);
return Promise.resolve({

View file

@ -20,7 +20,7 @@ namespace _espe.api
* @todo zu plankton auslagern?
*/
type type_stuff = {
version: (null | string);
// version: (null | string);
headers: Record<string, string>;
path_parameters: Record<string, string>;
query_parameters: Record<string, string>;
@ -43,9 +43,9 @@ namespace _espe.api
*/
/*
export function restriction_disjunction(
left : lib_plankton.rest.type_restriction<any>,
right : lib_plankton.rest.type_restriction<any>
) : lib_plankton.rest.type_restriction<any>
left : lib_plankton.rest_http.type_restriction<any>,
right : lib_plankton.rest_http.type_restriction<any>
) : lib_plankton.rest_http.type_restriction<any>
{
return (
(stuff) => Promise.any<boolean>(
@ -61,14 +61,14 @@ namespace _espe.api
/**
*/
export const restriction_none : lib_plankton.rest.type_restriction<any> = (
export const restriction_none : lib_plankton.rest_http.type_restriction<any> = (
(stuff) => Promise.resolve<boolean>(true)
);
/**
*/
export const restriction_logged_in : lib_plankton.rest.type_restriction<any> = (
export const restriction_logged_in : lib_plankton.rest_http.type_restriction<any> = (
(stuff) => (
session_from_stuff(stuff)
.then(() => Promise.resolve<boolean>(true))
@ -82,7 +82,7 @@ namespace _espe.api
export function restriction_verification(
extract_data : ((stuff : type_stuff) => any),
extract_verification : ((stuff : type_stuff) => string)
) : lib_plankton.rest.type_restriction<any>
) : lib_plankton.rest_http.type_restriction<any>
{
return (
(stuff) => _espe.helpers.verification_check(
@ -92,17 +92,27 @@ namespace _espe.api
);
}
/**
*/
export function full_path(
path : string
) : string
{
return (_espe.conf.get().server.path_base + path);
}
/**
*/
/*
export function register<type_input, type_output>(
rest_subject : lib_plankton.rest.type_rest,
rest_subject : lib_plankton.rest_http.type_rest,
http_method : lib_plankton.http.enum_method,
path : string,
options : {
active ?: ((version : string) => boolean);
restriction ?: (null | lib_plankton.rest.type_restriction<type_input>);
execution ?: lib_plankton.rest.type_execution<type_input, type_output>;
active ?: ((version : (null | string)) => boolean);
restriction ?: (null | lib_plankton.rest_http.type_restriction<type_input>);
execution ?: lib_plankton.rest_http.type_execution<type_input, type_output>;
title ?: (null | string);
description ?: (null | string);
query_parameters ?: Array<
@ -112,8 +122,8 @@ namespace _espe.api
required : boolean;
}
>;
input_schema ?: ((version: (null | string)) => lib_plankton.rest.type_oas_schema);
output_schema ?: ((version: (null | string)) => lib_plankton.rest.type_oas_schema);
input_schema ?: ((version: (null | string)) => lib_plankton.rest_http.type_oas_schema);
output_schema ?: ((version: (null | string)) => lib_plankton.rest_http.type_oas_schema);
request_body_mimetype ?: string;
request_body_decode ?: ((http_request_body : Buffer, http_request_header_content_type : (null | string)) => any);
response_body_mimetype ?: string;
@ -121,17 +131,12 @@ namespace _espe.api
} = {}
) : void
{
options = Object.assign(
{
},
options
);
lib_plankton.rest.register<type_input, type_output>(
lib_plankton.rest_http.register<type_input, type_output>(
rest_subject,
http_method,
(_espe.conf.get().server.path_base + path),
options
);
}
*/
}

View file

@ -19,9 +19,9 @@ namespace _espe.api
/**
*/
export function make(
) : lib_plankton.rest.type_rest
) : lib_plankton.rest_http.type_rest
{
const rest_subject : lib_plankton.rest.type_rest = lib_plankton.rest.make(
const rest_subject : lib_plankton.rest_http.type_rest = lib_plankton.rest_http.make(
{
"title": "espe",
"versioning_method": "header",
@ -29,7 +29,7 @@ namespace _espe.api
"set_access_control_headers": true,
"authentication": {
"kind": "key_header",
"parameters": {"name": "X-Session-Key"}
"data": {"name": "X-Session-Key"}
},
}
);
@ -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,7 +62,15 @@ namespace _espe.api
_espe.api.register_member_password_change_execute(rest_subject);
}
}
// invitation
{
_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_delete(rest_subject);
_espe.api.register_invitation_examine(rest_subject);
_espe.api.register_invitation_accept(rest_subject);
}
return rest_subject;
}

View file

@ -42,12 +42,7 @@ namespace _espe.conf
/**
*/
export type type_conf = {
general : {
language : (null | string);
verification_secret : (null | string);
};
log : Array<
export type type_log_channel = (
{
kind : "stdout";
data : {
@ -77,6 +72,18 @@ namespace _espe.conf
receivers : Array<string>;
};
}
);
/**
*/
export type type_conf = {
general : {
language : (null | string);
verification_secret : (null | string);
};
log : Array<
type_log_channel
>;
server : {
host : string;
@ -142,14 +149,6 @@ namespace _espe.conf
name : string;
domain : string;
};
misc : {
prefix_for_veiled_email_addresses : string;
facultative_membership_number : boolean;
auto_register : boolean;
};
summon_email : {
remark : string;
};
password_policy : {
minimum_length : (null | int);
maximum_length : (null | int);
@ -160,12 +159,10 @@ namespace _espe.conf
password_change : {
cooldown_time : int;
};
name_index : {
veil : boolean;
salt : (null | string);
};
connections : {
frontend_url_base : (null | string);
frontend_path_template_invitation_handle : (null | string);
frontend_path_template_password_change : (null | string);
login_url : (null | string);
};
};
@ -213,7 +210,7 @@ namespace _espe.conf
conf_raw : any
) : void
{
const version : int = (conf_raw["version"] ?? 5);
const version : int = (conf_raw["version"] ?? 6);
_data = {
"general": (
((node_general) => ({
@ -224,7 +221,8 @@ namespace _espe.conf
"log": (
(() => {
switch (version) {
case 1: {
case 1:
{
return [
{
"kind": "stdout",
@ -238,7 +236,9 @@ namespace _espe.conf
case 2:
case 3:
case 4:
case 5: {
case 5:
case 6:
{
const node_log = (
conf_raw["log"]
??
@ -275,12 +275,15 @@ namespace _espe.conf
switch (version) {
case 1:
case 2:
case 3: {
case 3:
{
return "::";
break;
}
case 4:
case 5: {
case 5:
case 6:
{
return (node_server["host"] ?? "::");
break
}
@ -295,7 +298,8 @@ namespace _espe.conf
const kind : string = (node_database["kind"] ?? "sqlite");
const node_database_data_raw = (node_database["data"] ?? {});
switch (kind) {
case "sqlite": {
case "sqlite":
{
return {
"kind": kind,
"data": {
@ -304,14 +308,16 @@ namespace _espe.conf
};
break;
}
case "postgresql": {
case "postgresql":
{
return {
"kind": kind,
"data": node_database_data_raw,
};
break;
}
default: {
default:
{
throw (new Error("unhandled"));
break;
}
@ -323,7 +329,8 @@ namespace _espe.conf
const kind : string = (node_email_sending["kind"] ?? "console");
const data_raw = (node_email_sending["data"] ?? {});
switch (kind) {
case "regular": {
case "regular":
{
return {
"kind": kind,
"data": {
@ -333,7 +340,8 @@ namespace _espe.conf
};
break;
}
case "redirect": {
case "redirect":
{
return {
"kind": kind,
"data": {
@ -344,7 +352,8 @@ namespace _espe.conf
};
break;
}
case "console": {
case "console":
{
return {
"kind": kind,
"data": {
@ -352,7 +361,8 @@ namespace _espe.conf
};
break;
}
case "drop": {
case "drop":
{
return {
"kind": kind,
"data": {
@ -360,7 +370,8 @@ namespace _espe.conf
};
break;
}
default: {
default:
{
throw (new Error("unhandled"));
break;
}
@ -380,18 +391,6 @@ namespace _espe.conf
"name": ((node_settings["organisation"] ?? {})["name"] ?? "Example Orginsation"), // TODO: mandatory?
"domain": ((node_settings["organisation"] ?? {})["domain"] ?? "example.org"), // TODO: mandatory?
},
"misc": (
((node_settings_misc) => ({
"prefix_for_veiled_email_addresses": (node_settings_misc["prefix_for_veiled_email_addresses"] ?? "member-"),
"facultative_membership_number": (node_settings_misc["facultative_membership_number"] ?? false),
"auto_register": (node_settings_misc["auto_register"] ?? false),
})) (node_settings["misc"] ?? {})
),
"summon_email": (
((node_settings_summon_email) => ({
"remark": (node_settings_summon_email["remark"] ?? null),
})) (node_settings["summon_email"] ?? {})
),
"password_policy": (
((node_settings_password_policy) => ({
"minimum_length": (
@ -405,8 +404,8 @@ namespace _espe.conf
: 240
),
"must_contain_letter": (node_settings_password_policy["must_contain_letter"] ?? true),
"must_contain_number": (node_settings_password_policy["must_contain_number"] ?? true),
"must_contain_special_character": (node_settings_password_policy["must_contain_special_character"] ?? true),
"must_contain_number": (node_settings_password_policy["must_contain_number"] ?? false),
"must_contain_special_character": (node_settings_password_policy["must_contain_special_character"] ?? false),
})) (node_settings["password_policy"] ?? {})
),
"password_change": (
@ -414,15 +413,47 @@ namespace _espe.conf
"cooldown_time": (node_settings_password_change["cooldown_time"] ?? 86400),
})) (node_settings["password_change"] ?? {})
),
"name_index": (
((node_settings_password_policy) => ({
"veil": (node_settings_password_policy["veil"] ?? true),
"salt": (node_settings_password_policy["salt"] ?? ""),
})) (node_settings["name_index"] ?? {})
),
"connections": (
((node_settings_connections) => ({
"frontend_url_base": (node_settings_connections["frontend_url_base"] ?? null),
"frontend_path_template_password_change": (() => {
switch (version)
{
case 1:
case 2:
case 3:
case 4:
case 5:
{
return null;
break;
}
case 6:
{
return (node_settings_connections["frontend_path_template_password_change"] ?? null);
break;
}
}
}) (),
"frontend_path_template_invitation_handle": (() => {
switch (version)
{
case 1:
case 2:
case 3:
case 4:
case 5:
{
return null;
break;
}
case 6:
{
return (node_settings_connections["frontend_path_template_invitation_handle"] ?? null);
break;
}
}
}) (),
"login_url": (node_settings_connections["login_url"] ?? null),
})) (node_settings["connections"] ?? {})
),
@ -431,7 +462,8 @@ namespace _espe.conf
"outputs": (() => {
switch (version) {
case 1:
case 2: {
case 2:
{
const node_output = (conf_raw["output"] ?? {});
return (
("authelia" in node_output)
@ -450,11 +482,15 @@ namespace _espe.conf
);
break;
}
case 3: {
case 3:
{
return (conf_raw["outputs"] ?? []);
break;
}
case 4: {
case 4:
case 5:
case 6:
{
const node_outputs = (conf_raw["outputs"] ?? []);
return node_outputs.map(
(output_description : {kind : string; data : any;}) => {

View file

@ -3,10 +3,8 @@
"identifier": "deu"
},
"tree": {
"email.summon.subject": "Registrierung",
"email.summon.body": "Hi, {{name}}\n\n{{remark}}\n\nWenn du die Dienste nutzen möchtest, rufe bitte folgende Adresse auf:\n\n{{url}}",
"email.registration.subject": "Registrierung erfolgt",
"email.registration.body": "Das Mitglied '{{name_display}}' hat sich soeben registriert:\n\n{{url}}",
"email.registration.body": "'{{name}}' ('{{label}}') wurde soeben registriert",
"email.activation.subject": "Freischaltung erfolgt",
"email.activation.body": "Hi, {{name_display}}\n\nDein Mitglieder-Konto wurde gerade freigeschalten. Du kannst dich nun anmelden:\n\nURL: {{url}}\nAnmelde-Name: {{name_login}}\n{{password_info}}",
"email.activation.password_info": "Passwort: {{password}}\n\nBitte ändere dein Passwort zeitnah!",
@ -14,9 +12,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": "Du wurdest zu {{organisation}} eingeladen. Um beizutreten, rufe bitte folgende Adresse auf:\n\n{{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",

View file

@ -3,10 +3,8 @@
"identifier": "eng"
},
"tree": {
"email.summon.subject": "Registration",
"email.summon.body": "Hi, {{name}}\n\n{{remark}}\n\nIn case you want to use online service, open the following link:\n\n{{url}}",
"email.registration.subject": "Registration received",
"email.registration.body": "The member '{{name_display}}' just registered:\n\n{{url}}",
"email.registration.body": "'{{name}}' ('{{label}}') just has been registered",
"email.activation.subject": "Activated",
"email.activation.body": "Hi, {{name_display}}\n\nYour account has just been activated. You may login now:\n\nURL: {{url}}\nLogin name: {{name_login}}\n{{password_info}}",
"email.activation.password_info": "Password: {{password}}\n\nPlease change your password soon!",
@ -14,9 +12,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": "You have been invited to {{organisation}}. In order to join, please open the following address:\n\n{{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",

View file

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

View file

@ -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 = {
@ -239,7 +259,7 @@ namespace _espe.helpers
}
else {
return lib_plankton.string.coin(
"{{base}}/{{rest}}",
"{{base}}{{rest}}",
{
"base": frontend_url_base,
"rest": lib_plankton.string.coin(template, arguments_),

View file

@ -13,27 +13,131 @@ You should have received a copy of the GNU General Public License along with thi
<https://www.gnu.org/licenses/>.
*/
namespace _espe
{
/**
*/
async function main(
function setup_log_fallback(
) : void
{
lib_plankton.log.set_main_logger(
[
{
"kind": "filtered",
"data": {
"core": {
"kind": "std",
"data": {
"target": "stdout",
"format": {
"kind": "human_readable",
"data": {}
}
}
},
"predicate": [
[
{
"item": {
"kind": "level",
"data": {"threshold": "info"}
},
}
]
],
}
},
]
);
}
/**
*/
function setup_log_from_conf(
) : void
{
lib_plankton.log.set_main_logger(
_espe.conf.get().log.map(
log_channel => {
switch (log_channel.kind) {
case "stdout": {
return {
"kind": "filtered",
"data": {
"core": {
"kind": "std",
"data": {
"target": "stdout",
"format": {"kind": "human_readable", "data": {}}
}
},
"predicate": [
[
{"item": {"kind": "level", "data": {"threshold": log_channel.data.threshold}}}
]
],
}
};
break;
}
case "file": {
return {
"kind": "filtered",
"data": {
"core": {
"kind": "file",
"data": {
"path": log_channel.data.path,
"format": {"kind": "jsonl", "data": {"structured": false}}
}
},
"predicate": [
[
{"item": {"kind": "level", "data": {"threshold": log_channel.data.threshold}}}
]
],
}
};
break;
}
case "email": {
return {
"kind": "filtered",
"data": {
"core": {
"kind": "email",
"data": log_channel.data
},
"predicate": [
[
{"item": {"kind": "level", "data": {"threshold": log_channel.data.threshold}}}
]
],
}
};
break;
}
default: {
throw (new Error("unhandled log channel"));
break;
}
}
}
)
);
}
/**
*/
export async function main(
args_raw : Array<string>
) : Promise<void>
{
// init
lib_plankton.log.conf_push(
[
lib_plankton.log.channel_make(
{
"kind": "stdout",
"data": {
"threshold": "notice",
// "format": "human_readable",
}
}
),
]
);
setup_log_fallback();
const language_codes : Array<string> = [
"deu",
"eng",
@ -100,6 +204,10 @@ async function main(
"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")
@ -167,7 +275,7 @@ async function main(
"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,
@ -198,16 +306,7 @@ async function main(
lib_plankton.translate.promote(language);
}
}
lib_plankton.log.conf_push(
_espe.conf.get().log.map(
log_output => lib_plankton.log.channel_make(
{
"kind": log_output.kind,
"data": log_output.data
}
)
)
);
setup_log_from_conf();
// exec
if (args["help"] || (args["action"] === "help")) {
@ -253,18 +352,26 @@ async function main(
break;
}
case "api-doc": {
lib_plankton.log.conf_push([]);
const rest_subject : lib_plankton.rest.type_rest = _espe.api.make();
lib_plankton.log.conf_pop();
// lib_plankton.log.conf_push([]);
const rest_subject : lib_plankton.rest_http.type_rest = _espe.api.make();
// lib_plankton.log.conf_pop();
process.stdout.write(
JSON.stringify(
lib_plankton.rest.to_oas(rest_subject),
lib_plankton.rest_http.to_oas(rest_subject),
undefined,
"\t"
)
);
break;
}
case "sample": {
const path : string = args["arg1"];
if (path === null) {
throw (new Error("SYNTAX: sample <source-file-path>"));
}
_espe.sample.fill_by_path(path);
break;
}
case "email-test": {
await _espe.helpers.email_send(
(
@ -393,11 +500,11 @@ async function main(
}
);
const rest_subject : lib_plankton.rest.type_rest = _espe.api.make();
const rest_subject : lib_plankton.rest_http.type_rest = _espe.api.make();
const server : lib_plankton.server.type_subject = lib_plankton.server.make(
async (input, metadata) => {
const http_request : lib_plankton.http.type_request = lib_plankton.http.decode_request(input.toString());
const http_response : lib_plankton.http.type_response = await lib_plankton.rest.call(
const http_response : lib_plankton.http.type_response = await lib_plankton.rest_http.call(
rest_subject,
http_request,
{
@ -428,9 +535,10 @@ async function main(
}
}
}
(
main(process.argv.slice(2))
_espe.main(process.argv.slice(2))
.then(
() => {
}

View file

@ -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
<https://www.gnu.org/licenses/>.
*/
namespace _espe.repository.group
{
/**
*/
var _store : (
null
|
lib_plankton.storage.type_store<
_espe.type.group_id,
Record<string, any>,
{},
lib_plankton.storage.type_sql_table_autokey_search_term,
Record<string, any>
>
) = null;
/**
*/
function get_store(
) : lib_plankton.storage.type_store<
_espe.type.group_id,
Record<string, any>,
{},
lib_plankton.storage.type_sql_table_autokey_search_term,
Record<string, any>
>
{
if (_store === null)
{
_store = lib_plankton.storage.sql_table_autokey_store(
{
"database_implementation": _espe.helpers.database_implementation(),
"table_name": "groups",
"key_name": "id",
}
);
}
else
{
// do nothing
}
return _store;
}
/**
*/
type type_dispersal = Record<string, any>;
/**
*/
function encode(
object : _espe.type.group_object
) : type_dispersal
{
return {
"name": object.name,
"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<string, any> = 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<void>
{
const dispersal : type_dispersal = encode(value);
// core
await get_store().update(id, dispersal);
}
/**
*/
export async function delete_(
id : _espe.type.group_id
) : Promise<void>
{
// core
await get_store().delete(id);
}
/**
*/
export async function dump(
) : Promise<
Array<
{
id : _espe.type.group_id;
object : _espe.type.group_object;
}
>
>
{
return (
Promise.all(
(await get_store().search(null))
.map(hit => hit.key)
.map(
id => (
read(id)
.then(
(object) => ({
"id": id,
"object": object
})
)
)
)
)
);
}
}

View file

@ -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
<https://www.gnu.org/licenses/>.
*/
namespace _espe.repository.invitation
{
/**
*/
type type_group_chest = lib_plankton.storage.type_chest<
Array<any>,
Record<string, any>,
lib_plankton.database.type_description_create_table,
lib_plankton.storage.sql_table_common.type_sql_table_common_search_term,
Record<string, any>
>;
/**
*/
var _core_store : (
null
|
lib_plankton.storage.type_store<
_espe.type.invitation_id,
Record<string, any>,
{},
lib_plankton.storage.type_sql_table_autokey_search_term,
Record<string, any>
>
) = null;
/**
*/
var _group_chest : (
null
|
type_group_chest
) = null;
/**
*/
function get_core_store(
) : lib_plankton.storage.type_store<
_espe.type.invitation_id,
Record<string, any>,
{},
lib_plankton.storage.type_sql_table_autokey_search_term,
Record<string, any>
>
{
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<string, any>;
group_rows : Array<
Record<string, any>
>;
};
/**
*/
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<string, any> = await get_core_store().read(id);
const group_hits : Array<{key : Record<string, any>; preview : Record<string, any>;}> = await get_group_chest().search(
{
"expression": "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<void>
{
// groups
const hits : Array<{key : Array<any>; preview : Record<string, any>;}> = 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
})
)
)
)
)
);
}
}

View file

@ -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,20 +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<string>(
dispersal.group_rows.map(row => row["group_name"]),
(group1, group2) => ((group1 <= group2) ? 0 : 1)
"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"],
@ -181,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;
};
}
>
@ -198,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"],
}
})
)
@ -238,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"]
})
),
};
@ -263,7 +249,7 @@ namespace _espe.repository.member
await get_group_chest().write(
[
id,
group_row["group_name"],
group_row["group_id"],
],
{
"_dummy": null,
@ -295,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);
}
@ -303,7 +288,7 @@ namespace _espe.repository.member
await get_group_chest().write(
[
id,
group_row["group_name"],
group_row["group_id"],
],
{
"_dummy": null,

View file

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

151
source/sample.ts Normal file
View file

@ -0,0 +1,151 @@
/*
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Backend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
*/
namespace _espe.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<int>;
password : string;
}
>;
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<int>;
}
>;
};
/**
*/
export async function fill(
data : type_data
) : Promise<void>
{
const track_groups : Map<int, _espe.type.group_id> = new Map<int, _espe.type.group_id>();
// groups
{
for (const group_raw of data.groups)
{
const group_id : _espe.type.group_id = await _espe.service.group.add(
{
"name": group_raw.name,
"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.add(
{
"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)),
"password": member_raw.password,
},
{
"password_generated": false,
"signal_change": false,
"greet_member": false,
"notify_admins": false,
}
);
}
/**
* @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<void>
{
const content : string = await lib_plankton.file.read(path);
const data : type_data = (lib_plankton.json.decode(content) as type_data);
await fill(data);
}
}

75
source/services/group.ts Normal file
View file

@ -0,0 +1,75 @@
/*
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Backend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
*/
namespace _espe.service.group
{
/**
*/
export async function list(
) : Promise<
Array<
{
id : _espe.type.group_id;
preview : {
name : string;
label : string;
};
}
>
>
{
return lib_plankton.list.sorted(
await _espe.repository.group.list(null),
{
"compare_element": (entry1, entry2) => (entry1.preview.label <= entry2.preview.label)
}
);
}
/**
*/
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<void>
{
const object : _espe.type.group_object = await _espe.repository.group.read(id);
object.label = label;
await _espe.repository.group.update(id, object);
}
}

View file

@ -0,0 +1,412 @@
/*
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Backend
Copyright (C) 2024 Christian Fraß
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see
<https://www.gnu.org/licenses/>.
*/
namespace _espe.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 : (null | string);
label_changeable : boolean;
label_value : (null | string);
email_address_changeable : boolean;
email_address_value : (null | string);
groups_changeable : boolean;
groups_value : Array<_espe.type.group_id>;
},
{
"expiry": expiry = -1,
"send_immediatly": send_immediatly = true,
} : {
expiry ?: (null | int);
send_immediatly ?: boolean;
} = {
}
) : Promise<
{
id : _espe.type.invitation_id;
key : _espe.type.invitation_key;
url : (null | string);
}
>
{
/**
* @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);
const frontend_path_template_invitation_handle : (null | string) = _espe.conf.get().settings.connections.frontend_path_template_invitation_handle;
const url : (null | string) = (
(frontend_path_template_invitation_handle === null)
?
null
:
_espe.helpers.frontend_url_get(
frontend_path_template_invitation_handle,
{
"key": invitation_key,
}
)
);
// send link
{
if (! send_immediatly)
{
// do nothing
}
else
{
if (
! (
(
(email_address_value !== null)
&&
(email_address_value !== "")
)
&&
(url !== null)
)
)
{
lib_plankton.log._warning(
"espe.service.invitation.create.email.condition_unmet",
{
"details": {
"provided_address": email_address_value,
"url": url,
},
}
);
}
else
{
try
{
await _espe.helpers.email_send(
[email_address_value],
lib_plankton.string.coin(
"{{head}} | {{core}}",
{
"head": _espe.conf.get().settings.organisation.name,
"core": lib_plankton.translate.get("email.invitation.subject"),
}
),
lib_plankton.translate.get(
"email.invitation.body",
{
"url": (url ?? "?"),
"organisation": _espe.conf.get().settings.organisation.name,
}
),
);
}
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,
"url": url,
};
}
/**
*/
export function remove(
id : _espe.type.invitation_id
) : Promise<void>
{
return _espe.repository.invitation.delete_(id);
}
/**
*/
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<string, any>;
}
>
>
{
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
{
let password_generated : boolean = (
(data.password === null)
||
(data.password === "")
);
const password_value : string = (
password_generated
?
(data.password as string)
:
_espe.service.member.generate_password()
);
const flaws_password : Array<
{
incident : string;
details : Record<string, any>;
}
> = _espe.service.member.validate_password(password_value);
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_value,
},
{
"password_generated": password_generated,
"signal_change": true,
"greet_member": true,
"notify_admins": true,
}
);
await _espe.repository.invitation.delete_(invitation_id);
return [];
}
}
}
}
}

View file

@ -46,7 +46,7 @@ namespace _espe.service.member
/**
*/
function validate_password(
export function validate_password(
password : string
) : Array<{incident : string; details : Record<string, any>}>
{
@ -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;
}
@ -192,28 +145,27 @@ namespace _espe.service.member
*/
async function send_activation_email(
member_object : _espe.type.member_object,
options : {
{
"password": password = null,
} : {
password ?: (null | string);
} = {}
) : Promise<void>
{
options = Object.assign(
if (! member_object.enabled)
{
"password": null,
},
options
);
if (! member_object.enabled) {
// 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}}",
@ -230,15 +182,17 @@ namespace _espe.service.member
"url": (_espe.conf.get().settings.connections.login_url ?? "--"),
"password_info": (
(
(options.password === undefined)
(password === undefined)
||
(options.password === null)
(password === null)
)
? ""
: lib_plankton.string.coin(
?
""
:
lib_plankton.string.coin(
lib_plankton.translate.get("email.activation.password_info"),
{
"password": options.password,
"password": password,
}
)
),
@ -277,15 +231,19 @@ 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;
};
}
>
>
{
return _espe.repository.member.list(search_term);
return lib_plankton.list.sorted(
await _espe.repository.member.list(search_term),
{
"compare_element": (entry1, entry2) => (entry1.id <= entry2.id)
}
);
}
@ -304,206 +262,75 @@ namespace _espe.service.member
/**
* legt ein Mitglied an
*/
export async function project(
export async function add(
data : {
membership_number : (null | string);
name_real_value : string;
email_address_private : (null | string);
groups : Array<string>;
name : string;
label : string;
email_address : (null | string);
groups : Array<_espe.type.group_id>;
password : string;
},
{
"password_generated": password_generated = false,
"signal_change": flag_signal_change = false,
"greet_member": flag_greet_member = false,
"notify_admins": flag_notify_admins = false,
} : {
password_generated ?: boolean;
signal_change ?: boolean;
greet_member ?: boolean;
notify_admins ?: 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_image": await password_image(data.password),
"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();
return id;
}
/**
* sendet an ein Mitglied eine E-Mail mit Aufforderung zur Registrierung
*/
export async function summon(
member_id : _espe.type.member_id,
url_template : string
) : Promise<(null | string)>
// change
{
_espe.helpers.frontend_url_check();
const member_object : _espe.type.member_object = await get(member_id);
if (member_object.email_address_private === null) {
return null;
}
else {
const url : (null | string) = _espe.helpers.frontend_url_get(
url_template,
if (! flag_signal_change)
{
"verification": await _espe.helpers.verification_get(member_id),
}
);
if (url === null) {
// do nothing
}
else {
await _espe.helpers.email_send(
[
member_object.email_address_private,
],
lib_plankton.string.coin(
"{{head}} | {{core}}",
else
{
"head": _espe.conf.get().settings.organisation.name,
"core": lib_plankton.translate.get("email.summon.subject"),
signal_change();
}
),
lib_plankton.string.coin(
lib_plankton.translate.get("email.summon.body"),
}
// greet member
{
"name": name_display(member_object),
"url": url,
"remark": (
(_espe.conf.get().settings.summon_email.remark === null)
? ""
: (_espe.conf.get().settings.summon_email.remark + "\n\n")
),
}
)
);
}
return url;
}
}
/**
* 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
|
if (! flag_greet_member)
{
name_real_value : string;
name_real_index : int;
name_login : string;
email_address_veiled : (null | string);
email_address_nominal : string;
}
)
>
{
const member_object : _espe.type.member_object = await _espe.repository.member.read(member_id);
if (! member_object.registered) {
return {
"name_real_value": member_object.name_real_value,
"name_real_index": member_object.name_real_index,
"name_login": name_login(member_object),
"email_address_veiled": email_address_veiled(member_object),
"email_address_nominal": email_address_nominal(member_object),
};
}
else {
return null;
}
}
/**
* führt die Registrierung für ein Mitglied durch
*/
export async function register(
member_id : _espe.type.member_id,
data : {
email_use_veiled_address : boolean;
email_use_nominal_address : boolean;
email_redirect_to_private_address : boolean;
password : (null | string);
},
options : {
notification_target_url_template ?: (null | string);
} = {}
) : Promise<Array<{incident : string; details : Record<string, any>;}>>
{
options = Object.assign(
{
"notification_target_url_template": null,
},
options
);
const member_object : _espe.type.member_object = await get(member_id);
let flaws : Array<{incident : string; details : Record<string, any>;}> = [];
let password_value : string;
let password_generated : boolean;
if (member_object.registered) {
flaws.push({"incident": "already_registered", "details": {}});
password_value = "";
password_generated = false;
}
else {
if (
(data.password !== null)
&&
(data.password !== "")
) {
flaws = flaws.concat(
validate_password(data.password)
.map(flaw => ({"incident": ("password_" + flaw.incident), "details": flaw.details}))
);
password_value = data.password;
password_generated = false;
}
else {
password_value = generate_password();
password_generated = true;
}
}
if (flaws.length > 0) {
// do nothing
}
else {
member_object.email_use_veiled_address = data.email_use_veiled_address;
member_object.email_use_nominal_address = data.email_use_nominal_address;
member_object.email_redirect_to_private_address = data.email_redirect_to_private_address;
member_object.password_image = await password_image(password_value);
member_object.registered = true;
await _espe.repository.member.update(member_id, member_object);
signal_change();
else
{
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,
await send_activation_email(
object,
{
"id": member_id.toFixed(0),
"password": (password_generated ? data.password : null),
}
)
);
/*await*/ _espe.service.admin.notify_all(
}
}
// notify admins
{
if (! flag_notify_admins)
{
// do nothing
}
else
{
_espe.service.admin.notify_all(
lib_plankton.string.coin(
"{{head}} | {{core}}",
{
@ -514,16 +341,14 @@ namespace _espe.service.member
lib_plankton.string.coin(
lib_plankton.translate.get("email.registration.body"),
{
"name_display": name_display(member_object),
"url": (url ?? "?"),
"name": object.name,
"label": object.label,
}
)
);
}
/*await*/ send_activation_email(member_object, {"password": password_generated ? password_value : null});
}
return Promise.resolve(flaws);
return id;
}
@ -533,33 +358,27 @@ namespace _espe.service.member
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<Array<string>>;
groups : lib_plankton.pod.type_pod<Array<_espe.type.group_id>>;
}
) : Promise<void>
{
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<Array<_espe.type.group_id>>(data.groups)
? lib_plankton.pod.cull<Array<_espe.type.group_id>>(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<Array<string>>(data.groups)
? lib_plankton.pod.cull<Array<string>>(data.groups)
: member_object_old.groups
),
};
await _espe.repository.member.update(member_id, member_object_new);
signal_change();
@ -595,15 +414,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 +449,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 +462,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 +487,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 +523,12 @@ namespace _espe.service.member
) : Promise<Array<{incident : string; details : Record<string, any>;}>>
{
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<string, any>;}> = [];
if (
(member_object_old.password_change_token === null)
@ -741,26 +554,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 +594,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<any>
{
options = Object.assign(
{
"custom_data": null,
},
options
);
type type_entry = {
disabled : boolean;
displayname : string;
@ -806,14 +609,42 @@ namespace _espe.service.member
groups : Array<string>;
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 +656,6 @@ namespace _espe.service.member
),
(x : Array<any>) => x.filter(
entry => (
entry.object.registered
&&
(
(entry.object.password_image !== null)
&&
@ -843,7 +672,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 +696,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<string>
{
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}));
}

View file

@ -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
<https://www.gnu.org/licenses/>.
*/
namespace _espe.service.name_index
{
/**
*/
export async function next(
name : string
) : Promise<int>
{
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;
}
}

View file

@ -235,9 +235,6 @@
"settings": {
"organisation": {
"domain": "testdomain.org"
},
"misc": {
"prefix_for_veiled_email_addresses": "wicht-"
}
}
},

View file

@ -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<string>;
registered : boolean;
name : string;
label : string;
email_address : (null | string);
groups : Array<group_id>;
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>);
};
}

2
todo.md Normal file
View file

@ -0,0 +1,2 @@
- Niederschreiben (Logging) von geheimen Angaben verhindern

View file

@ -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 = "<output-directory>",
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 = "<output-directory>",
help = "output directory",
default = "",
metavar = "<conf-path>",
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)

View file

@ -47,6 +47,7 @@ def main():
"--verbose",
"--exclude='conf.json'",
"--exclude='data.sqlite'",
"--exclude='log.jsonl'",
("%s/" % args.build_directory),
(
("%s" % args.target_directory)

View file

@ -48,27 +48,35 @@ ${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_delete.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 +89,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 $@)

View file

@ -16,8 +16,8 @@ modules="${modules} storage"
modules="${modules} session"
modules="${modules} json"
modules="${modules} api"
modules="${modules} rest"
modules="${modules} http"
modules="${modules} rest_http"
modules="${modules} server"
modules="${modules} email"
modules="${modules} args"