diff --git a/source/api/actions/invite_accept.ts b/source/api/actions/invite_accept.ts
new file mode 100644
index 0000000..c31ae74
--- /dev/null
+++ b/source/api/actions/invite_accept.ts
@@ -0,0 +1,62 @@
+/*
+Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Backend
+Copyright (C) 2024 Christian Fraß
+
+This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
+License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
+version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with this program. If not, see
+.
+ */
+
+namespace _espe.api
+{
+ /**
+ */
+ export function register_invite_examine(
+ rest_subject : lib_plankton.rest.type_rest
+ ) : void
+ {
+ register<
+ any,
+ any
+ >(
+ rest_subject,
+ lib_plankton.http.enum_method.post,
+ "/invite/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}) => {
+ /**
+ * @todo
+ */
+ return Promise.resolve({
+ "status_code": 501,
+ "data": null
+ });
+ }
+ }
+ );
+ }
+
+}
diff --git a/source/api/actions/invite_create.ts b/source/api/actions/invite_create.ts
new file mode 100644
index 0000000..b432632
--- /dev/null
+++ b/source/api/actions/invite_create.ts
@@ -0,0 +1,167 @@
+/*
+Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Backend
+Copyright (C) 2024 Christian Fraß
+
+This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
+License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
+version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with this program. If not, see
+.
+ */
+
+namespace _espe.api
+{
+
+ /**
+ */
+ export function register_invite_create(
+ rest_subject : lib_plankton.rest.type_rest
+ ) : void
+ {
+ register<
+ {
+ membership_number_mode : int;
+ membership_number_value : (null | string);
+ name_mode : int;
+ name_value : string;
+ email_address_mode : int;
+ email_address_value : (null | string);
+ groups_value : Array;
+ // notification_target_url_template ?: (null | string);
+ },
+ (
+ string
+ |
+ {
+ id : _espe.type.member_id;
+ key : string;
+ }
+ )
+ >(
+ rest_subject,
+ lib_plankton.http.enum_method.post,
+ "/invite/create",
+ {
+ /**
+ * @todo translation
+ */
+ "description": "erstellt eine Einladung neues Mitglied und gibt die erzeugte ID und den erzeugten Schlüssel 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_value",
+ "name_value",
+ "email_address_value",
+ ]
+ }),
+ "output_schema": () => ({
+ "type": "object",
+ "nullable": false,
+ "properties": {
+ "id": {
+ "type": "number",
+ "nullable": false,
+ },
+ "key": {
+ "type": "string",
+ "nullable": false,
+ },
+ },
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "key",
+ ]
+ }),
+ "restriction": restriction_logged_in,
+ "execution": async ({"input": input}) => {
+ if (input === null) {
+ return Promise.reject(new Error("impossible"));
+ }
+ else {
+ if (
+ (! _espe.conf.get().settings.misc.facultative_membership_number)
+ &&
+ (
+ (input.membership_number_value === null)
+ ||
+ (input.membership_number_value === "")
+ )
+ ) {
+ return Promise.resolve({
+ "status_code": 400,
+ "data": "membership number required"
+ });
+ }
+ else {
+ const invite_info : {id : _espe.type.invite_id; key : _espe.type.invite_key;} = await _espe.service.invite.create(
+ {
+ "membership_number_mode": _espe.helpers.invite_prefill_mode_decode(input.membership_number_mode),
+ "membership_number_value": input.membership_number_value,
+ "name_mode": _espe.helpers.invite_prefill_mode_decode(input.name_mode),
+ "name_value": input.name_value,
+ "email_address_mode": _espe.helpers.invite_prefill_mode_decode(input.email_address_mode),
+ "email_address_value": (
+ ("email_address_value" in input)
+ ? (
+ (input.email_address_value !== "")
+ ? input.email_address_value
+ : null
+ )
+ : null
+ ),
+ "groups_mode": _espe.helpers.invite_prefill_mode_decode(input.groups_mode),
+ "groups_value": input.groups_value,
+ }
+ );
+ return Promise.resolve({
+ "status_code": 201,
+ "data": invite_info
+ });
+ }
+ }
+ }
+ }
+ );
+ }
+
+}
+
diff --git a/source/api/actions/invite_examine.ts b/source/api/actions/invite_examine.ts
new file mode 100644
index 0000000..9122018
--- /dev/null
+++ b/source/api/actions/invite_examine.ts
@@ -0,0 +1,62 @@
+/*
+Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Backend
+Copyright (C) 2024 Christian Fraß
+
+This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
+License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
+version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with this program. If not, see
+.
+ */
+
+namespace _espe.api
+{
+ /**
+ */
+ export function register_invite_examine(
+ rest_subject : lib_plankton.rest.type_rest
+ ) : void
+ {
+ register<
+ any,
+ any
+ >(
+ rest_subject,
+ lib_plankton.http.enum_method.get,
+ "/invite/examine",
+ {
+ /**
+ * @todo translation
+ */
+ "description": "gibt die Daten einer Einladung anhand ihres Schlüssels aus",
+ "input_schema": () => ({
+ "type": "string"
+ "nullable": false,
+ }),
+ /**
+ * @todo
+ */
+ "output_schema": () => ({
+ "nullable": true,
+ }),
+ "restriction": restriction_none,
+ "execution": async ({"input": input}) => {
+ const invite_key : _espe.type.invite_key = input;
+ const invite_object : _espe.type.invite_object = await _espe.service.invite.examine(invite_key);
+ /**
+ * @todo
+ */
+ return Promise.resolve({
+ "status_code": 501,
+ "data": null
+ });
+ }
+ }
+ );
+ }
+
+}
diff --git a/source/api/functions.ts b/source/api/functions.ts
index 3a34e22..d609dbd 100644
--- a/source/api/functions.ts
+++ b/source/api/functions.ts
@@ -59,7 +59,12 @@ namespace _espe.api
_espe.api.register_member_password_change_execute(rest_subject);
}
}
-
+ // invite
+ {
+ _espe.api.register_invite_create(rest_subject);
+ _espe.api.register_invite_examine(rest_subject);
+ _espe.api.register_invite_accept(rest_subject);
+ }
return rest_subject;
}
diff --git a/source/database.ts b/source/database.ts
index a0945a5..abe3db1 100644
--- a/source/database.ts
+++ b/source/database.ts
@@ -19,7 +19,7 @@ namespace _espe.database
/**
*/
const _compatible_revisions : Array = [
- "r6",
+ "r7",
];
diff --git a/source/helpers.ts b/source/helpers.ts
index f5d6b43..2dffccf 100644
--- a/source/helpers.ts
+++ b/source/helpers.ts
@@ -248,4 +248,34 @@ namespace _espe.helpers
}
}
+
+ /**
+ */
+ export function invite_prefill_mode_encode(
+ invite_prefill_mode : _espe.type.invite_prefill_mode
+ ) : int
+ {
+ switch (invite_prefill_mode) {
+ case _espe.type.invite_prefill_mode.hidden: return 0;
+ case _espe.type.invite_prefill_mode.locked: return 1;
+ case _espe.type.invite_prefill_mode.free: return 2;
+ default: throw (new Error("unhandled invite prefill mode: " + String(invite_prefill_mode)));
+ }
+ }
+
+
+ /**
+ */
+ export function invite_prefill_mode_decode(
+ invite_prefill_mode_encoded : int
+ ) : _espe.type.invite_prefill_mode
+ {
+ switch (invite_prefill_mode) {
+ case 0: return _espe.type.invite_prefill_mode.hidden;
+ case 1: return _espe.type.invite_prefill_mode.locked;
+ case 2: return _espe.type.invite_prefill_mode.free;
+ default: throw (new Error("unhandled encoded invite prefill mode: " + String(invite_prefill_mode_encoded)));
+ }
+ }
+
}
diff --git a/source/repositories/invite.ts b/source/repositories/invite.ts
new file mode 100644
index 0000000..86e7e4c
--- /dev/null
+++ b/source/repositories/invite.ts
@@ -0,0 +1,302 @@
+/*
+Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Backend
+Copyright (C) 2024 Christian Fraß
+
+This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
+License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
+version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with this program. If not, see
+.
+ */
+
+namespace _espe.repository.invite
+{
+
+ /**
+ */
+ type type_group_chest = lib_plankton.storage.type_chest<
+ Array,
+ Record,
+ lib_plankton.database.type_description_create_table,
+ lib_plankton.storage.sql_table_common.type_sql_table_common_search_term,
+ Record
+ >;
+
+
+ /**
+ */
+ var _core_store : (
+ null
+ |
+ lib_plankton.storage.type_store<
+ _espe.type.invite_id,
+ Record,
+ {},
+ lib_plankton.storage.type_sql_table_autokey_search_term,
+ Record
+ >
+ ) = null;
+
+
+ /**
+ */
+ var _group_chest : (
+ null
+ |
+ type_group_chest
+ ) = null;
+
+
+ /**
+ */
+ function get_core_store(
+ ) : lib_plankton.storage.type_store<
+ _espe.type.invite_id,
+ Record,
+ {},
+ lib_plankton.storage.type_sql_table_autokey_search_term,
+ Record
+ >
+ {
+ if (_core_store === null) {
+ _core_store = lib_plankton.storage.sql_table_autokey_store(
+ {
+ "database_implementation": _espe.helpers.database_implementation(),
+ "table_name": "invites",
+ "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": "invite_groups",
+ "key_names": ["invite_id","group_name"],
+ }
+ );
+ }
+ else {
+ // do nothing
+ }
+ return _group_chest;
+ }
+
+
+ /**
+ */
+ type type_dispersal = {
+ core_row : Record;
+ group_rows : Array<
+ Record
+ >;
+ };
+
+
+ /**
+ */
+ function encode(
+ object : _espe.type.member_object
+ ) : type_dispersal
+ {
+ return {
+ "core_row": {
+ "key": object.key,
+ "membership_number_mode": invite_prefill_mode_encode(object.membership_number_mode),
+ "membership_number_value": object.membership_number_value,
+ "name_mode": invite_prefill_mode_encode(object.name_mode),
+ "name_value": object.name_value,
+ "email_address_mode": invite_prefill_mode_encode(object.email_address_mode),
+ "email_address_value": object.email_address_value,
+ "groups_mode": invite_prefill_mode_encode(object.groups_mode),
+ },
+ "group_rows": (
+ object.groups
+ .map(
+ group => ({
+ "group_name": group,
+ })
+ )
+ )
+ };
+ }
+
+
+ /**
+ */
+ function decode(
+ dispersal : type_dispersal
+ ) : _espe.type.member_object
+ {
+ return {
+ "key": dispersal.core_row["key"],
+ "membership_number_mode": invite_prefill_mode_decode(dispersal.core_row["membership_number_mode"]),
+ "membership_number_value": dispersal.core_row["membership_number_value"],
+ "name_mode": invite_prefill_mode_decode(dispersal.core_row["name_mode"]),
+ "name_value": dispersal.core_row["name_value"],
+ "email_address_mode": invite_prefill_mode_decode(dispersal.core_row["email_address_mode"]),
+ "email_address_value": dispersal.core_row["email_address_value"],
+ "groups_mode": invite_prefill_mode_decode(dispersal.core_row["groups_mode"]),
+ "groups_value": lib_plankton.list.sorted(
+ dispersal.group_rows.map(row => row["group_name"]),
+ (group1, group2) => ((group1 <= group2) ? 0 : 1)
+ ),
+ };
+ }
+
+
+ /**
+ * @todo optimize
+ */
+ export async function list(
+ search_term : (null | string)
+ ) : Promise<
+ Array<
+ {
+ id : _espe.type.inivite_id;
+ preview : {
+ name : string;
+ };
+ }
+ >
+ >
+ {
+ return (
+ (await get_core_store().search(null))
+ .map(
+ ({"key": key, "preview": preview}) => ({
+ "id": key,
+ "preview": {
+ "name": preview["name_value"],
+ }
+ })
+ )
+ );
+ }
+
+
+ /**
+ */
+ export async function read(
+ id : _espe.type.invite_id
+ ) : Promise<_espe.type.invite_object>
+ {
+ const core_row : Record = await get_core_store().read(id);
+ const group_hits : Array<{key : Record; preview : Record;}> = await get_group_chest().search(
+ {
+ "expression": "invite_id = $invite_id",
+ "arguments": {"invite_id": id}
+ }
+ );
+
+ const dispersal : type_dispersal = {
+ "core_row": core_row,
+ "group_rows": group_hits.map(
+ hit => ({
+ "group_name": hit.preview["group_name"]
+ })
+ ),
+ };
+
+ return decode(dispersal);
+ }
+
+
+ /**
+ */
+ export async function create(
+ value : _espe.type.invite_object
+ ) : Promise<_espe.type.invite_id>
+ {
+ const dispersal : type_dispersal = encode(value);
+
+ // core
+ const id : _espe.type.invite_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_name"],
+ ],
+ {
+ "_dummy": null,
+ }
+ );
+ }
+
+ return id;
+ }
+
+
+ /**
+ */
+ export async function delete_(
+ id : _espe.type.invite_id
+ ) : Promise
+ {
+ // groups
+ const hits : Array<{key : Array; preview : Record;}> = await get_group_chest().search(
+ {
+ "expression": "invite_id = $invite_id",
+ "arguments": {"invite_id": id}
+ }
+ );
+ for (const hit of hits) {
+ await get_group_chest().delete(hit.key);
+ }
+
+ // core
+ await get_core_store().delete(id);
+ }
+
+
+ /**
+ */
+ export async function dump(
+ ) : Promise<
+ Array<
+ {
+ id : _espe.type.invite_id;
+ object : _espe.type.invite_object;
+ }
+ >
+ >
+ {
+ return (
+ Promise.all(
+ (await get_core_store().search(null))
+ .map(hit => hit.key)
+ .map(
+ id => (
+ read(id)
+ .then(
+ (object) => ({
+ "id": id,
+ "object": object
+ })
+ )
+ )
+ )
+ )
+ );
+ }
+
+}
diff --git a/source/services/invite.ts b/source/services/invite.ts
new file mode 100644
index 0000000..af0254d
--- /dev/null
+++ b/source/services/invite.ts
@@ -0,0 +1,55 @@
+/*
+Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Backend
+Copyright (C) 2024 Christian Fraß
+
+This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
+License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
+version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with this program. If not, see
+.
+ */
+
+namespace _espe.service.invite
+{
+
+ /**
+ */
+ export function create(
+ ) : Promise<{id : _espe.type.invite_id; key : _espe.type.invite_key}>
+ {
+ throw (new Error("not implemented"));
+ }
+
+
+ /**
+ */
+ export function examine(
+ key : _espe.type.invite_key
+ ) : Promise<{id : _espe.type.invite_id; key : _espe.type.invite_key}>
+ {
+ throw (new Error("not implemented"));
+ }
+
+
+ /**
+ */
+ export function accept(
+ key : _espe.type.invite_key,
+ membership_number_value : (null | string),
+ name_value : (null | string),
+ email_address_value : (null | string)
+ ) : Promise
+ {
+ /**
+ * @todo Nutzer anlegen
+ * @todo Einladung löschen
+ */
+ throw (new Error("not implemented"));
+ }
+
+}
+
diff --git a/source/types.ts b/source/types.ts
index aa8c6c4..691c55b 100644
--- a/source/types.ts
+++ b/source/types.ts
@@ -55,4 +55,39 @@ namespace _espe.type
password_change_token : (null | string);
};
+
+ /**
+ */
+ export type invite_id = int;
+
+
+ /**
+ */
+ export type invite_key = string;
+
+
+ /**
+ */
+ enum type invite_prefill_mode = {
+ hidden,
+ locked,
+ free,
+ };
+
+
+ /**
+ * @todo expiry timestamp
+ */
+ export type invite_object = {
+ key : invite_key;
+ membership_number_mode : invite_prefill_mode;
+ membership_number_value : (null | string);
+ name_mode : invite_prefill_mode;
+ name_value : string;
+ email_address_mode : invite_prefill_mode;
+ email_address_value : (null | string);
+ groups_mode : invite_prefill_mode;
+ groups_value : Array;
+ };
+
}
diff --git a/tools/makefile b/tools/makefile
index 9a44cc8..c1235a8 100644
--- a/tools/makefile
+++ b/tools/makefile
@@ -51,9 +51,11 @@ ${dir_temp}/espe-core.js ${dir_temp}/espe-core.d.ts: \
${dir_source}/repositories/admin.ts \
${dir_source}/repositories/name_index.ts \
${dir_source}/repositories/member.ts \
+ ${dir_source}/repositories/invite.ts \
+ ${dir_source}/services/admin.ts \
${dir_source}/services/name_index.ts \
${dir_source}/services/member.ts \
- ${dir_source}/services/admin.ts \
+ ${dir_source}/services/invite.ts \
${dir_source}/api/base.ts \
${dir_source}/api/actions/meta_ping.ts \
${dir_source}/api/actions/meta_spec.ts \
@@ -69,6 +71,9 @@ ${dir_temp}/espe-core.js ${dir_temp}/espe-core.d.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/invite_create.ts \
+ ${dir_source}/api/actions/invite_examine.ts \
+ ${dir_source}/api/actions/invite_accept.ts \
${dir_source}/api/functions.ts \
${dir_source}/conf.ts
@ ${cmd_log} "compile | core …"