[mod] Datenbank-Anbindung

This commit is contained in:
roydfalk 2024-04-22 23:23:42 +02:00
parent 9c56055650
commit cfeee83fdb
7 changed files with 294 additions and 79 deletions

View file

@ -27,7 +27,6 @@
### Anweisungen
- `conf.json` im build-Verzeichnis anlegen
- `tools/run` ausführen
- ins Erzeugnis-Verzeichnis wechseln
- `./espe -h` ausführen
- für die meisten Anwendungsfälle ist es erforderlich eine Konfigurations-Datei anzulegen

View file

@ -4,9 +4,45 @@ namespace _espe.conf
/**
*/
export type type_conf = {
port : int;
database_path : string;
email : {
general : {
verbosity : (
"none"
|
"debug"
|
"notice"
|
"info"
|
"warning"
|
"error"
);
verification_secret : (null | string);
};
server : {
port : int;
};
database : (
{
kind : "sqlite";
data : {
path : string;
};
}
|
{
kind : "postgresql";
data : {
host : string;
port ?: int;
username : string;
password : string;
schema : string;
};
}
);
email_sending : {
mode : (
"regular"
|
@ -16,35 +52,30 @@ namespace _espe.conf
|
"drop"
);
smtp_credentials : {
host : string;
port : int;
username : string;
password : string;
};
smtp_credentials : (
null
|
{
host : string;
port : int;
username : string;
password : string;
}
);
};
session_lifetime : int;
session_drop_all_at_start : boolean;
email_domain : string;
email_numberbased_address_prefix : string;
verification_secret : string;
verbosity : (
"none"
|
"debug"
|
"notice"
|
"info"
|
"warning"
|
"error"
);
session_management : {
lifetime : int;
drop_all_at_start : boolean;
};
settings : {
target_domain : string;
prefix_for_numberbased_email_addresses : string;
};
// TODO: evtl. in Datenbank verlagern
admins : Array<
{
name : string;
password : string;
password_image : string;
}
>;
};
@ -68,17 +99,64 @@ namespace _espe.conf
: {}
);
_data = {
"port": (conf_raw["port"] ?? 7979),
"email_domain": (conf_raw["email_domain"] ?? "example.org"),
"email_numberbased_address_prefix": (conf_raw["email_numberbased_address_prefix"] ?? "member-"),
"email": conf_raw["email"], // TODO: feiner
"verification_secret": (conf_raw["verification_secret"] ?? "itsy_bitsy"),
"session_lifetime": (conf_raw["session_lifetime"] ?? 900),
"session_drop_all_at_start": true,
"verbosity": (conf_raw["verbosity"] ?? "notice"),
"database_path": (conf_raw["database_path"] ?? "data.sqlite"),
"general": (
((node_general) => ({
"verbosity": (node_general["verbosity"] ?? "notice"),
"verification_secret": (node_general["verification_secret"] ?? null),
})) (conf_raw["general"] ?? {})
),
"server": (
((node_server) => ({
"port": (node_server["port"] ?? 7979),
})) (conf_raw["server"] ?? {})
),
"database": (
((node_database) => {
const kind : string = (node_database["kind"] ?? "sqlite");
const node_database_data_raw = (node_database["data"] ?? {});
switch (kind) {
case "sqlite": {
return {
"kind": kind,
"data": {
"path": (node_database_data_raw["path"] ?? "data.sqlite"),
}
};
}
case "postgresql": {
return {
"kind": kind,
"data": node_database_data_raw,
};
}
default: {
throw (new Error("unhandled"));
break;
}
}
}) (conf_raw["database"] ?? {})
),
"email_sending": (
((node_email_sending) => ({
"mode": (node_email_sending["mode"] ?? "regular"),
"smtp_credentials": (node_email_sending["smtp_credentials"] ?? null),
})) (conf_raw["email_sending"] ?? {})
),
"session_management": (
((node_session_management) => ({
"lifetime": (node_session_management["lifetime"] ?? 900),
"drop_all_at_start": (node_session_management["drop_all_at_start"] ?? true),
})) (conf_raw["session_management"] ?? {})
),
"settings": (
((node_settings) => ({
"target_domain": (node_settings["target_domain"] ?? "example.org"),
"prefix_for_numberbased_email_addresses": (node_settings["prefix_for_numberbased_email_addresses"] ?? "member-"),
})) (conf_raw["settings"] ?? {})
),
"admins": (conf_raw["admins"] ?? []),
};
// process.stderr.write(JSON.stringify(_data, undefined, "\t"));
return Promise.resolve<void>(undefined);
}
@ -86,7 +164,7 @@ namespace _espe.conf
/**
*/
export function get(
) : any
) : type_conf
{
if (_data === null) {
throw (new Error("conf not loaded yet"));

81
source/database.ts Normal file
View file

@ -0,0 +1,81 @@
namespace _espe.database
{
/**
*/
const _compatible_revisions : Array<string> = [
"r1",
];
/**
*/
export function get_implementation(
) : lib_plankton.database.type_database
{
switch (_espe.conf.get().database.kind) {
case "sqlite": {
return lib_plankton.database.sqlite_database(
{
"path": _espe.conf.get().database.data["path"],
"verbose": false,
}
);
break;
}
default: {
throw (new Error("database implementation not available: " + _espe.conf.get().database.kind));
}
}
}
/**
*/
function get_revision(
) : Promise<(null | string)>
{
return (
get_implementation().query_select(
{
"source": "_meta",
"fields": ["revision"],
}
)
.then<(null | string)>(
(rows) => Promise.resolve<(null | string)>(rows[0]["revision"])
)
.catch<(null | string)>(
() => Promise.resolve<(null | string)>(null)
)
);
}
/**
*/
export async function check(
) : Promise<void>
{
const revision : (null | string) = await get_revision();
lib_plankton.log.info(
"database_check",
{
"revision_found": revision,
"revisions_compatible": _compatible_revisions,
}
);
if (revision === null) {
return Promise.reject<void>(new Error("database appearently missing"));
}
else {
if (! _compatible_revisions.includes(revision)) {
return Promise.reject<void>(new Error("database revision incompatible; found: " + revision + "; required: " + String(_compatible_revisions)));
}
else {
return Promise.resolve<void>(undefined);
}
}
}
}

View file

@ -50,16 +50,12 @@ namespace _espe.helpers
/**
* @deprecated
*/
export function database_implementation(
) : lib_plankton.database.type_database
{
return lib_plankton.database.sqlite_database(
{
"path": _espe.conf.get().database_path,
"verbose": false,
}
);
return _espe.database.get_implementation();
}
@ -69,10 +65,16 @@ namespace _espe.helpers
data : any
) : Promise<string>
{
return lib_plankton.sha256.get(
lib_plankton.json.encode(data),
_espe.conf.get().verification_secret
);
const secret : (null | string) = _espe.conf.get().general.verification_secret;
if (secret === null) {
return Promise.reject<string>(new Error("no verification secret specified; add in conf as 'general.verification_secret'!"));
}
else {
return lib_plankton.sha256.get(
lib_plankton.json.encode(data),
secret
);
}
}
@ -83,11 +85,17 @@ namespace _espe.helpers
verification : string
) : Promise<boolean>
{
const verification_expected : string = lib_plankton.sha256.get(
lib_plankton.json.encode(data),
_espe.conf.get().verification_secret
);
return (verification === verification_expected);
const secret : (null | string) = _espe.conf.get().general.verification_secret;
if (secret === null) {
return Promise.reject<boolean>(new Error("no verification secret specified; add in conf as 'general.verification_secret'!"));
}
else {
const verification_expected : string = lib_plankton.sha256.get(
lib_plankton.json.encode(data),
secret
);
return (verification === verification_expected);
}
}
@ -99,7 +107,7 @@ namespace _espe.helpers
content : string
) : Promise<void>
{
const mode : string = _espe.conf.get().email.mode;
const mode : string = _espe.conf.get().email_sending.mode;
lib_plankton.log.info(
"email_send",
{
@ -110,11 +118,21 @@ namespace _espe.helpers
);
switch (mode) {
case "regular": {
// TODO
if (_espe.conf.get().email_sending.smtp_credentials === null) {
return Promise.reject<void>("no smtp credentials specified; add in conf as 'email_sending.smtp_credentials'!");
}
else {
// TODO
}
break;
}
case "redirect": {
// TODO
if (_espe.conf.get().email_sending.smtp_credentials === null) {
return Promise.reject<void>("no smtp credentials specified; add in conf as 'email_sending.smtp_credentials'!");
}
else {
// TODO
}
break;
}
case "console": {

View file

@ -11,8 +11,46 @@ async function main(
"type": lib_plankton.args.enum_type.string,
"mode": lib_plankton.args.enum_mode.replace,
"default": "serve",
"info": "Aktion (serve | doc | password-image | export-authelia | help)",
"name": "action",
"info": lib_plankton.string.coin(
"auszuführende Aktion; Auswahl:\n{{selection}}\n\t\t",
{
"selection": (
[
{
"name": "serve",
"description": "Server starten",
},
{
"name": "api-doc",
"description": "API-Dokumentation gemäß OpenAPI Specification auf Standard-Ausgabe schreiben"
},
{
"name": "password-image",
"description": "Passwort-Abbild errechnen und auf Standard-Ausgabe schreiben"
},
{
"name": "export-authelia",
"description": "Export der Nutzer-Datenbank im Authelia-user-Datei-Format auf Standard-Ausgabe schreiben"
},
{
"name": "help",
"description": "Diese Hilfe auf Standard-Ausgabe schreiben"
},
]
.map(
entry => lib_plankton.string.coin(
"\t\t- {{name}}\n\t\t\t{{description}}\n",
{
"name": entry.name,
"description": entry.description,
}
)
)
.join("")
),
}
),
}),
"arg1": lib_plankton.args.class_argument.positional({
"index": 1,
@ -37,7 +75,7 @@ async function main(
"indicators_short": ["c"],
"type": lib_plankton.args.enum_type.string,
"mode": lib_plankton.args.enum_mode.replace,
"default": "conf.json",
"default": null,
"info": "Pfad zur Konfigurations-Datei",
"name": "conf-path",
}),
@ -83,17 +121,17 @@ async function main(
"notice": lib_plankton.log.enum_level.notice,
"info": lib_plankton.log.enum_level.info,
"debug":lib_plankton.log.enum_level.debug,
}[_espe.conf.get().verbosity]
}[_espe.conf.get().general.verbosity]
),
]
);
// prepare database
await _espe.database.advance_all();
switch (args["action"]) {
default: {
process.stderr.write(
"invalid action: " + args["action"]
+
"\n"
);
process.stderr.write("invalid action: " + args["action"] + "\n");
break;
}
case "password-image": {
@ -107,7 +145,7 @@ async function main(
}
break;
}
case "doc": {
case "api-doc": {
const rest_subject : lib_plankton.rest.type_rest = _espe.api.make();
process.stdout.write(
JSON.stringify(
@ -126,7 +164,7 @@ async function main(
lib_plankton.storage.sql_table_common.chest(
{
"database_implementation": _espe.helpers.database_implementation(),
"table_name": "sessions",
"table_name": "session_management",
"key_names": ["key"],
}
),
@ -143,7 +181,7 @@ async function main(
]
),
*/
"default_lifetime": _espe.conf.get().session_lifetime,
"default_lifetime": _espe.conf.get().session_management.lifetime,
}
);
@ -158,14 +196,14 @@ async function main(
);
_espe.service.member.listen_change(
async () => {
const authelia_export : string = await _espe.service.member.export_authelia_member_file();
const authelia_export : string = await _espe.service.member.export_authelia_user_file();
process.stdout.write(authelia_export + "\n");
}
);
const rest_subject : lib_plankton.rest.type_rest = _espe.api.make();
const server : lib_plankton.server.type_subject = lib_plankton.server.make(
_espe.conf.get().port,
_espe.conf.get().server.port,
async (input, metadata) => {
const http_request : lib_plankton.http.type_request = lib_plankton.http.decode_request(input);
const http_response : lib_plankton.http.type_response = await lib_plankton.rest.call(
@ -186,7 +224,7 @@ async function main(
break;
}
case "export-authelia": {
process.stdout.write(await _espe.service.member.export_authelia_member_file() + "\n");
process.stdout.write(await _espe.service.member.export_authelia_user_file() + "\n");
break;
}
}

View file

@ -271,7 +271,7 @@ namespace _espe.service.member
? ""
: ("." + value.name_real_extension)
),
"domain": _espe.conf.get().email_domain,
"domain": _espe.conf.get().settings.target_domain,
}
);
}
@ -286,9 +286,9 @@ namespace _espe.service.member
return lib_plankton.string.coin(
"{{prefix}}{{membership_number}}@{{domain}}",
{
"prefix": _espe.conf.get().email_numberbased_address_prefix,
"prefix": _espe.conf.get().settings.prefix_for_numberbased_email_addresses,
"membership_number": value.membership_number,
"domain": _espe.conf.get().email_domain,
"domain": _espe.conf.get().settings.target_domain,
}
);
}
@ -313,9 +313,9 @@ namespace _espe.service.member
/**
* @todo check validity (e.g. membername characters)
* @todo check validity (e.g. username characters)
*/
export async function export_authelia_member_file(
export async function export_authelia_user_file(
) : Promise<string>
{
const nm_yaml = require("yaml");

View file

@ -27,6 +27,7 @@ node_modules:
${dir_temp}/espe-unlinked.js: \
${dir_lib}/plankton/plankton.d.ts \
${dir_source}/helpers.ts \
${dir_source}/database.ts \
${dir_source}/service-member.ts \
${dir_source}/service-admin.ts \
${dir_source}/api/base.ts \