[ini]
This commit is contained in:
parent
279bedf74e
commit
175e57b1f3
33 changed files with 13494 additions and 7271 deletions
27
.editorconfig
Normal file
27
.editorconfig
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
# see https://EditorConfig.org
|
||||||
|
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_size = tab
|
||||||
|
indent_style = tab
|
||||||
|
tab_width = 4
|
||||||
|
insert_final_newline = true
|
||||||
|
max_line_length = 80
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
curly_bracket_next_line = false
|
||||||
|
indent_brace_style = K&R
|
||||||
|
spaces_around_operators = true
|
||||||
|
spaces_around_brackets = false
|
||||||
|
quote_type = double
|
||||||
|
|
||||||
|
[*.y{,a}ml{,lint}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
{
|
{
|
||||||
"view_mode": "table",
|
"version": 1,
|
||||||
"timezone_shift": 0
|
"log": [
|
||||||
|
{"kind": "stdout", "data": {"threshold": "debug"}}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
4169
lib/plankton/plankton.d.ts
vendored
4169
lib/plankton/plankton.d.ts
vendored
File diff suppressed because it is too large
Load diff
14124
lib/plankton/plankton.js
14124
lib/plankton/plankton.js
File diff suppressed because it is too large
Load diff
74
source/api/actions/calendar_list.ts
Normal file
74
source/api/actions/calendar_list.ts
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
|
||||||
|
namespace _zeitbild.api
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export function register_calendar_list(
|
||||||
|
rest_subject : lib_plankton.rest.type_rest
|
||||||
|
) : void
|
||||||
|
{
|
||||||
|
register<
|
||||||
|
null,
|
||||||
|
Array<
|
||||||
|
{
|
||||||
|
id : _zeitbild.type.calendar_id;
|
||||||
|
preview : {
|
||||||
|
name : string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
>
|
||||||
|
>(
|
||||||
|
rest_subject,
|
||||||
|
lib_plankton.http.enum_method.get,
|
||||||
|
"/calendar/list",
|
||||||
|
{
|
||||||
|
"description": "listet alle Kalender auf",
|
||||||
|
"query_parameters": [
|
||||||
|
],
|
||||||
|
"output_schema": () => ({
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"nullable": false,
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "number",
|
||||||
|
"nullable": false,
|
||||||
|
},
|
||||||
|
"preview": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"name",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"preview",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
"restriction": restriction_none, // TODO
|
||||||
|
"execution": () => (
|
||||||
|
_zeitbild.service.calendar.list(null)
|
||||||
|
.then(
|
||||||
|
data => Promise.resolve({
|
||||||
|
"status_code": 200,
|
||||||
|
"data": data,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
40
source/api/actions/meta_ping.ts
Normal file
40
source/api/actions/meta_ping.ts
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
|
||||||
|
namespace _zeitbild.api
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export function register_meta_ping(
|
||||||
|
rest_subject : lib_plankton.rest.type_rest
|
||||||
|
) : void
|
||||||
|
{
|
||||||
|
lib_plankton.rest.register<
|
||||||
|
null,
|
||||||
|
string
|
||||||
|
>
|
||||||
|
(
|
||||||
|
rest_subject,
|
||||||
|
lib_plankton.http.enum_method.get,
|
||||||
|
_zeitbild.conf.get().server.path_base + "/meta/ping",
|
||||||
|
{
|
||||||
|
"description": "sendet ein 'pong' zurück; gedacht um die Erreichbarkeit des Backends zu prüfen",
|
||||||
|
"input_schema": () => ({
|
||||||
|
"nullable": true,
|
||||||
|
}),
|
||||||
|
"output_schema": () => ({
|
||||||
|
"nullable": false,
|
||||||
|
"type": "string",
|
||||||
|
}),
|
||||||
|
"restriction": restriction_none,
|
||||||
|
"execution": () => {
|
||||||
|
return Promise.resolve({
|
||||||
|
"status_code": 200,
|
||||||
|
"data": "pong",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
37
source/api/actions/meta_spec.ts
Normal file
37
source/api/actions/meta_spec.ts
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
|
||||||
|
namespace _zeitbild.api
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export function register_meta_spec(
|
||||||
|
rest_subject : lib_plankton.rest.type_rest
|
||||||
|
) : void
|
||||||
|
{
|
||||||
|
lib_plankton.rest.register<
|
||||||
|
null,
|
||||||
|
any
|
||||||
|
>
|
||||||
|
(
|
||||||
|
rest_subject,
|
||||||
|
lib_plankton.http.enum_method.get,
|
||||||
|
_zeitbild.conf.get().server.path_base + "/meta/spec",
|
||||||
|
{
|
||||||
|
"description": "gibt die API-Spezifikation im OpenAPI-Format aus",
|
||||||
|
"input_schema": () => ({
|
||||||
|
"nullable": true,
|
||||||
|
}),
|
||||||
|
"output_schema": () => ({
|
||||||
|
}),
|
||||||
|
"restriction": restriction_none,
|
||||||
|
"execution": () => {
|
||||||
|
return Promise.resolve({
|
||||||
|
"status_code": 200,
|
||||||
|
"data": lib_plankton.rest.to_oas(rest_subject),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
76
source/api/base.ts
Normal file
76
source/api/base.ts
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
|
||||||
|
namespace _zeitbild.api
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo zu plankton auslagern?
|
||||||
|
*/
|
||||||
|
type type_stuff = {
|
||||||
|
version: (null | string);
|
||||||
|
headers: Record<string, string>;
|
||||||
|
path_parameters: Record<string, string>;
|
||||||
|
query_parameters: Record<string, string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export async function session_from_stuff(
|
||||||
|
stuff : {headers : Record<string, string>;}
|
||||||
|
) : Promise<{key : string; value : lib_plankton.session.type_session}>
|
||||||
|
{
|
||||||
|
const key : string = (stuff.headers["X-Session-Key"] || stuff.headers["X-Session-Key".toLowerCase()]);
|
||||||
|
const value : lib_plankton.session.type_session = await lib_plankton.session.get(key);
|
||||||
|
return {"key": key, "value": value};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export const restriction_none : lib_plankton.rest.type_restriction<any> = (
|
||||||
|
(stuff) => Promise.resolve<boolean>(true)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export function register<type_input, type_output>(
|
||||||
|
rest_subject : lib_plankton.rest.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>;
|
||||||
|
title ?: (null | string);
|
||||||
|
description ?: (null | string);
|
||||||
|
query_parameters ?: Array<
|
||||||
|
{
|
||||||
|
name : string;
|
||||||
|
description : (null | string);
|
||||||
|
required : boolean;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
input_schema ?: ((version: (null | string)) => lib_plankton.rest.type_oas_schema);
|
||||||
|
output_schema ?: ((version: (null | string)) => lib_plankton.rest.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;
|
||||||
|
response_body_encode ?: ((output : any) => Buffer);
|
||||||
|
} = {}
|
||||||
|
) : void
|
||||||
|
{
|
||||||
|
options = Object.assign(
|
||||||
|
{
|
||||||
|
},
|
||||||
|
options
|
||||||
|
);
|
||||||
|
lib_plankton.rest.register<type_input, type_output>(
|
||||||
|
rest_subject,
|
||||||
|
http_method,
|
||||||
|
(_zeitbild.conf.get().server.path_base + path),
|
||||||
|
options
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
37
source/api/functions.ts
Normal file
37
source/api/functions.ts
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
|
||||||
|
namespace _zeitbild.api
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export function make(
|
||||||
|
) : lib_plankton.rest.type_rest
|
||||||
|
{
|
||||||
|
const rest_subject : lib_plankton.rest.type_rest = lib_plankton.rest.make(
|
||||||
|
{
|
||||||
|
"title": "zeitbild",
|
||||||
|
"versioning_method": "header",
|
||||||
|
"versioning_header_name": "X-Api-Version",
|
||||||
|
"set_access_control_headers": true,
|
||||||
|
"authentication": {
|
||||||
|
"kind": "key_header",
|
||||||
|
"parameters": {"name": "X-Session-Key"}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// meta
|
||||||
|
{
|
||||||
|
_zeitbild.api.register_meta_ping(rest_subject);
|
||||||
|
_zeitbild.api.register_meta_spec(rest_subject);
|
||||||
|
}
|
||||||
|
// calendar
|
||||||
|
{
|
||||||
|
_zeitbild.api.register_calendar_list(rest_subject);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return rest_subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
6
source/backend.ts
Normal file
6
source/backend.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
namespace _zeitbild.frontend.resources.backend
|
||||||
|
{
|
||||||
|
}
|
237
source/conf.ts
Normal file
237
source/conf.ts
Normal file
|
@ -0,0 +1,237 @@
|
||||||
|
|
||||||
|
namespace _zeitbild.conf
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
type type_log_threshold = (
|
||||||
|
"debug"
|
||||||
|
|
|
||||||
|
"info"
|
||||||
|
|
|
||||||
|
"notice"
|
||||||
|
|
|
||||||
|
"warning"
|
||||||
|
|
|
||||||
|
"error"
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
type type_log_format = (
|
||||||
|
"jsonl"
|
||||||
|
|
|
||||||
|
"human_readable"
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export type type_conf = {
|
||||||
|
general : {
|
||||||
|
language : (null | string);
|
||||||
|
};
|
||||||
|
log : Array<
|
||||||
|
{
|
||||||
|
kind : "stdout";
|
||||||
|
data : {
|
||||||
|
threshold : type_log_threshold;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
||||||
|
{
|
||||||
|
kind : "file";
|
||||||
|
data : {
|
||||||
|
threshold : type_log_threshold;
|
||||||
|
path : string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
||||||
|
{
|
||||||
|
kind : "email";
|
||||||
|
data : {
|
||||||
|
threshold : type_log_threshold;
|
||||||
|
smtp_credentials : {
|
||||||
|
host : string;
|
||||||
|
port : int;
|
||||||
|
username : string;
|
||||||
|
password : string;
|
||||||
|
};
|
||||||
|
sender : string;
|
||||||
|
receivers : Array<string>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
server : {
|
||||||
|
host : string;
|
||||||
|
port : int;
|
||||||
|
path_base : string;
|
||||||
|
};
|
||||||
|
database : (
|
||||||
|
{
|
||||||
|
kind : "sqlite";
|
||||||
|
data : {
|
||||||
|
path : string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
||||||
|
{
|
||||||
|
kind : "postgresql";
|
||||||
|
data : {
|
||||||
|
host : string;
|
||||||
|
port ?: int;
|
||||||
|
username : string;
|
||||||
|
password : string;
|
||||||
|
schema : string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
session_management : {
|
||||||
|
in_memory : boolean;
|
||||||
|
drop_all_at_start : boolean;
|
||||||
|
lifetime : int;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
var _data : (null | type_conf) = null;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export function inject(
|
||||||
|
conf_raw : any
|
||||||
|
) : void
|
||||||
|
{
|
||||||
|
const version : int = (conf_raw["version"] ?? 1);
|
||||||
|
_data = {
|
||||||
|
"general": (
|
||||||
|
((node_general) => ({
|
||||||
|
"language": (node_general["language"] ?? null),
|
||||||
|
})) (conf_raw["general"] ?? {})
|
||||||
|
),
|
||||||
|
"log": (
|
||||||
|
(() => {
|
||||||
|
const node_log = (
|
||||||
|
conf_raw["log"]
|
||||||
|
??
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
node_log.map(
|
||||||
|
(node_log_entry : any) => ({
|
||||||
|
"kind": node_log_entry["kind"],
|
||||||
|
"data": Object.assign(
|
||||||
|
{
|
||||||
|
"format": "human_readable",
|
||||||
|
"threshold": "notice",
|
||||||
|
},
|
||||||
|
(node_log_entry["data"] ?? {})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}) ()
|
||||||
|
),
|
||||||
|
"server": (
|
||||||
|
((node_server) => ({
|
||||||
|
"host": (() => {
|
||||||
|
return (node_server["host"] ?? "::");
|
||||||
|
}) (),
|
||||||
|
"port": (node_server["port"] ?? 7845),
|
||||||
|
"path_base": (node_server["path_base"] ?? ""),
|
||||||
|
})) (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"),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "postgresql": {
|
||||||
|
return {
|
||||||
|
"kind": kind,
|
||||||
|
"data": node_database_data_raw,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw (new Error("unhandled"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) (conf_raw["database"] ?? {})
|
||||||
|
),
|
||||||
|
"session_management": (
|
||||||
|
((node_session_management) => ({
|
||||||
|
"in_memory": (node_session_management["in_memory"] ?? true),
|
||||||
|
"drop_all_at_start": (node_session_management["drop_all_at_start"] ?? true),
|
||||||
|
"lifetime": (node_session_management["lifetime"] ?? 900),
|
||||||
|
})) (conf_raw["session_management"] ?? {})
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo mandatory fields
|
||||||
|
*/
|
||||||
|
export async function load(
|
||||||
|
path : string
|
||||||
|
) : Promise<void>
|
||||||
|
{
|
||||||
|
let conf_raw : any;
|
||||||
|
if (! (await lib_plankton.file.exists(path))) {
|
||||||
|
// return Promise.reject<void>(new Error("configuration file not found: " + path + "; using fallback"));
|
||||||
|
conf_raw = {};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
conf_raw = lib_plankton.json.decode(await lib_plankton.file.read(path));
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
conf_raw = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (conf_raw === null) {
|
||||||
|
return Promise.reject<void>("configuration file could not be read");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
inject(conf_raw);
|
||||||
|
// process.stderr.write(JSON.stringify(_data, undefined, "\t"));
|
||||||
|
return Promise.resolve<void>(undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export function get(
|
||||||
|
) : type_conf
|
||||||
|
{
|
||||||
|
if (_data === null) {
|
||||||
|
throw (new Error("conf not loaded yet"));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return _data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
112
source/database.ts
Normal file
112
source/database.ts
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
|
||||||
|
namespace _zeitbild.database
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
const _compatible_revisions : Array<string> = [
|
||||||
|
"r1",
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export function get_implementation(
|
||||||
|
) : lib_plankton.database.type_database
|
||||||
|
{
|
||||||
|
switch (_zeitbild.conf.get().database.kind) {
|
||||||
|
case "sqlite": {
|
||||||
|
type type_parameters = {
|
||||||
|
path : string;
|
||||||
|
};
|
||||||
|
const parameters : type_parameters = (_zeitbild.conf.get().database.data as type_parameters);
|
||||||
|
return lib_plankton.database.sqlite_database(
|
||||||
|
{
|
||||||
|
"path": parameters.path,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "postgresql": {
|
||||||
|
type type_parameters = {
|
||||||
|
host : string;
|
||||||
|
port ?: int;
|
||||||
|
username : string;
|
||||||
|
password : string;
|
||||||
|
schema : string;
|
||||||
|
};
|
||||||
|
const parameters : type_parameters = (_zeitbild.conf.get().database.data as type_parameters);
|
||||||
|
return lib_plankton.database.postgresql_database(
|
||||||
|
{
|
||||||
|
"host": parameters.host,
|
||||||
|
"port": parameters.port,
|
||||||
|
"username": parameters.username,
|
||||||
|
"password": parameters.password,
|
||||||
|
"schema": parameters.schema,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw (new Error("database implementation not available: " + _zeitbild.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)>(
|
||||||
|
(reason) => {
|
||||||
|
lib_plankton.log.warning(
|
||||||
|
"database_get_revision_error",
|
||||||
|
{
|
||||||
|
"reason": String(reason),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
namespace _zeitbild.frontend.helpers
|
namespace _zeitbild.helpers
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
|
@ -1,26 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8"/>
|
|
||||||
<link rel="stylesheet" type="text/css" href="style.css"/>
|
|
||||||
<script type="text/javascript" src="logic.js">
|
|
||||||
</script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
document.addEventListener(
|
|
||||||
"DOMContentLoaded",
|
|
||||||
() => {
|
|
||||||
_zeitbild.frontend.main()
|
|
||||||
.then(
|
|
||||||
() => {}
|
|
||||||
)
|
|
||||||
.catch(
|
|
||||||
(error) => {console.error(error);}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,333 +0,0 @@
|
||||||
/**
|
|
||||||
*/
|
|
||||||
namespace _zeitbild.frontend.resources.backend
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
var _data : _zeitbild.frontend.type_datamodel;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
export async function init(
|
|
||||||
) : Promise<void>
|
|
||||||
{
|
|
||||||
const path : string = "data.json";
|
|
||||||
if (_data === undefined) {
|
|
||||||
_data = lib_plankton.call.convey(
|
|
||||||
await lib_plankton.file.read(path),
|
|
||||||
[
|
|
||||||
lib_plankton.json.decode,
|
|
||||||
(data_raw : any) => (
|
|
||||||
({
|
|
||||||
"users": data_raw["users"],
|
|
||||||
"calendars": (
|
|
||||||
data_raw["calendars"]
|
|
||||||
.map(
|
|
||||||
(calendar_entry_raw : any) => ({
|
|
||||||
"id": calendar_entry_raw["id"],
|
|
||||||
"object": (
|
|
||||||
((calendar_object_raw) => {
|
|
||||||
switch (calendar_object_raw["kind"]) {
|
|
||||||
default: {
|
|
||||||
return calendar_object_raw;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "concrete": {
|
|
||||||
return {
|
|
||||||
"kind": "concrete",
|
|
||||||
"data": {
|
|
||||||
"name": calendar_object_raw["data"]["name"],
|
|
||||||
"private": (
|
|
||||||
calendar_object_raw["data"]["private"]
|
|
||||||
??
|
|
||||||
false
|
|
||||||
),
|
|
||||||
"users": calendar_object_raw["data"]["users"],
|
|
||||||
"events": (
|
|
||||||
calendar_object_raw["data"]["events"]
|
|
||||||
.map(
|
|
||||||
(event_raw : any) => ({
|
|
||||||
"name": event_raw["name"],
|
|
||||||
"begin": event_raw["begin"],
|
|
||||||
"end": (
|
|
||||||
(
|
|
||||||
(
|
|
||||||
event_raw["end"]
|
|
||||||
??
|
|
||||||
null
|
|
||||||
)
|
|
||||||
===
|
|
||||||
null
|
|
||||||
)
|
|
||||||
?
|
|
||||||
null
|
|
||||||
:
|
|
||||||
event_raw["end"]
|
|
||||||
),
|
|
||||||
"location": (
|
|
||||||
event_raw["location"]
|
|
||||||
??
|
|
||||||
null
|
|
||||||
),
|
|
||||||
"description": (
|
|
||||||
event_raw["description"]
|
|
||||||
??
|
|
||||||
null
|
|
||||||
),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}) (calendar_entry_raw["object"])
|
|
||||||
),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
),
|
|
||||||
}) as type_datamodel
|
|
||||||
),
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
return Promise.resolve<void>(undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
export async function calendar_list(
|
|
||||||
) : Promise<
|
|
||||||
Array<
|
|
||||||
{
|
|
||||||
key : type_calendar_id;
|
|
||||||
preview : {
|
|
||||||
name : string;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
>
|
|
||||||
{
|
|
||||||
await init();
|
|
||||||
return Promise.resolve(
|
|
||||||
_data.calendars
|
|
||||||
.map(
|
|
||||||
(calendar_entry) => {
|
|
||||||
switch (calendar_entry.object.kind) {
|
|
||||||
case "concrete": {
|
|
||||||
return {
|
|
||||||
"key": calendar_entry.id,
|
|
||||||
"preview": {
|
|
||||||
"name": calendar_entry.object.data.name,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "caldav": {
|
|
||||||
return {
|
|
||||||
"key": calendar_entry.id,
|
|
||||||
"preview": {
|
|
||||||
"name": "(imported)",
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
export async function calendar_read(
|
|
||||||
calendar_id : type_calendar_id
|
|
||||||
) : Promise<type_calendar_object>
|
|
||||||
{
|
|
||||||
await init();
|
|
||||||
const hits = (
|
|
||||||
_data.calendars
|
|
||||||
.filter(
|
|
||||||
(calendar_entry) => (calendar_entry.id === calendar_id)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
if (hits.length <= 0) {
|
|
||||||
return Promise.reject<type_calendar_object>(new Error("not found"));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return Promise.resolve<type_calendar_object>(hits[0].object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @todo prevent loops
|
|
||||||
*/
|
|
||||||
export async function calendar_gather_events(
|
|
||||||
calendar_ids : Array<type_calendar_id>,
|
|
||||||
from_pit : _zeitbild.frontend.helpers.type_pit,
|
|
||||||
to_pit : _zeitbild.frontend.helpers.type_pit
|
|
||||||
) : Promise<
|
|
||||||
Array<
|
|
||||||
{
|
|
||||||
calendar_id : type_calendar_id;
|
|
||||||
calendar_name : string;
|
|
||||||
event : type_event;
|
|
||||||
}
|
|
||||||
>
|
|
||||||
>
|
|
||||||
{
|
|
||||||
lib_plankton.log.info(
|
|
||||||
"calendar_gather_events",
|
|
||||||
{
|
|
||||||
"calendar_ids": calendar_ids,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
await init();
|
|
||||||
let result : Array<
|
|
||||||
{
|
|
||||||
calendar_id : type_calendar_id;
|
|
||||||
calendar_name : string;
|
|
||||||
event : type_event;
|
|
||||||
}
|
|
||||||
> = [];
|
|
||||||
for await (const calendar_id of calendar_ids) {
|
|
||||||
const calendar_object : type_calendar_object = await calendar_read(
|
|
||||||
calendar_id
|
|
||||||
);
|
|
||||||
if (calendar_object.data.private) {
|
|
||||||
lib_plankton.log.info(
|
|
||||||
"calendar_gather_events_private_calendar_blocked",
|
|
||||||
{
|
|
||||||
"calendar_id": calendar_id,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
switch (calendar_object.kind) {
|
|
||||||
case "concrete": {
|
|
||||||
result = (
|
|
||||||
result
|
|
||||||
.concat(
|
|
||||||
calendar_object.data.events
|
|
||||||
.filter(
|
|
||||||
(event) => _zeitbild.frontend.helpers.pit_is_between(
|
|
||||||
_zeitbild.frontend.helpers.pit_from_datetime(event.begin),
|
|
||||||
from_pit,
|
|
||||||
to_pit
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.map(
|
|
||||||
(event) => ({
|
|
||||||
"calendar_id": calendar_id,
|
|
||||||
"calendar_name": calendar_object.data.name,
|
|
||||||
"event": event
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "caldav": {
|
|
||||||
const url : lib_plankton.url.type_url = lib_plankton.url.decode(
|
|
||||||
calendar_object.data.source_url
|
|
||||||
);
|
|
||||||
const http_request : lib_plankton.http.type_request = {
|
|
||||||
"version": "HTTP/2",
|
|
||||||
"scheme": ((url.scheme === "https") ? "https" : "http"),
|
|
||||||
"host": url.host,
|
|
||||||
"path": (url.path ?? "/"),
|
|
||||||
"query": url.query,
|
|
||||||
"method": lib_plankton.http.enum_method.get,
|
|
||||||
"headers": {},
|
|
||||||
"body": null,
|
|
||||||
};
|
|
||||||
// TODO: cache?
|
|
||||||
const http_response : lib_plankton.http.type_response = await lib_plankton.http.call(
|
|
||||||
http_request,
|
|
||||||
{
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const vcalendar : lib_plankton.ical.type_vcalendar = lib_plankton.ical.ics_decode(
|
|
||||||
http_response.body.toString(),
|
|
||||||
{
|
|
||||||
}
|
|
||||||
);
|
|
||||||
result = (
|
|
||||||
result
|
|
||||||
.concat(
|
|
||||||
vcalendar.vevents
|
|
||||||
.map(
|
|
||||||
(vevent : lib_plankton.ical.type_vevent) => (
|
|
||||||
(vevent.dtstart !== undefined)
|
|
||||||
?
|
|
||||||
{
|
|
||||||
"name": (
|
|
||||||
(vevent.summary !== undefined)
|
|
||||||
?
|
|
||||||
vevent.summary
|
|
||||||
:
|
|
||||||
"???"
|
|
||||||
),
|
|
||||||
"begin": _zeitbild.frontend.helpers.ical_dt_to_own_datetime(vevent.dtstart),
|
|
||||||
"end": (
|
|
||||||
(vevent.dtend !== undefined)
|
|
||||||
?
|
|
||||||
_zeitbild.frontend.helpers.ical_dt_to_own_datetime(vevent.dtend)
|
|
||||||
:
|
|
||||||
null
|
|
||||||
),
|
|
||||||
"location": (
|
|
||||||
(vevent.location !== undefined)
|
|
||||||
?
|
|
||||||
vevent.location
|
|
||||||
:
|
|
||||||
null
|
|
||||||
),
|
|
||||||
"description": (
|
|
||||||
(vevent.description !== undefined)
|
|
||||||
?
|
|
||||||
vevent.description
|
|
||||||
:
|
|
||||||
null
|
|
||||||
),
|
|
||||||
}
|
|
||||||
:
|
|
||||||
null
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.filter(
|
|
||||||
(event) => (event !== null)
|
|
||||||
)
|
|
||||||
.filter(
|
|
||||||
(event) => _zeitbild.frontend.helpers.pit_is_between(
|
|
||||||
_zeitbild.frontend.helpers.pit_from_datetime(event.begin),
|
|
||||||
from_pit,
|
|
||||||
to_pit
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.map(
|
|
||||||
(event) => ({
|
|
||||||
"calendar_id": calendar_id,
|
|
||||||
"calendar_name": calendar_object.data.name,
|
|
||||||
"event": event,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Promise.resolve(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,218 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
async function main(
|
|
||||||
args_raw : Array<string>
|
|
||||||
) : Promise<void>
|
|
||||||
{
|
|
||||||
const arg_handler : lib_plankton.args.class_handler = new lib_plankton.args.class_handler({
|
|
||||||
"data_path": lib_plankton.args.class_argument.volatile({
|
|
||||||
"indicators_long": ["data-path"],
|
|
||||||
"indicators_short": ["d"],
|
|
||||||
"type": lib_plankton.args.enum_type.string,
|
|
||||||
"mode": lib_plankton.args.enum_mode.replace,
|
|
||||||
"default": "data.json",
|
|
||||||
// "info": null,
|
|
||||||
"name": "data-path",
|
|
||||||
}),
|
|
||||||
"timezone_shift": lib_plankton.args.class_argument.volatile({
|
|
||||||
"indicators_long": ["timezone-shift"],
|
|
||||||
"indicators_short": ["t"],
|
|
||||||
"type": lib_plankton.args.enum_type.integer,
|
|
||||||
"mode": lib_plankton.args.enum_mode.replace,
|
|
||||||
"default": 0,
|
|
||||||
// "info": null,
|
|
||||||
"name": "timezone-shift",
|
|
||||||
}),
|
|
||||||
"calendar_id": lib_plankton.args.class_argument.volatile({
|
|
||||||
"indicators_long": ["calendar"],
|
|
||||||
"indicators_short": ["c"],
|
|
||||||
"type": lib_plankton.args.enum_type.integer,
|
|
||||||
"mode": lib_plankton.args.enum_mode.replace,
|
|
||||||
"default": 1,
|
|
||||||
// "info": null,
|
|
||||||
"name": "calendar_id",
|
|
||||||
}),
|
|
||||||
"view_mode": lib_plankton.args.class_argument.volatile({
|
|
||||||
"indicators_long": ["view-moode"],
|
|
||||||
"indicators_short": ["m"],
|
|
||||||
"type": lib_plankton.args.enum_type.string,
|
|
||||||
"mode": lib_plankton.args.enum_mode.replace,
|
|
||||||
"default": "table",
|
|
||||||
// "info": null,
|
|
||||||
"name": "view_mode",
|
|
||||||
}),
|
|
||||||
"help": lib_plankton.args.class_argument.volatile({
|
|
||||||
"indicators_long": ["help"],
|
|
||||||
"indicators_short": ["h"],
|
|
||||||
"type": lib_plankton.args.enum_type.boolean,
|
|
||||||
"mode": lib_plankton.args.enum_mode.replace,
|
|
||||||
"default": false,
|
|
||||||
// "info": null,
|
|
||||||
"name": "help",
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
const args : Record<string, any> = arg_handler.read(lib_plankton.args.enum_environment.cli, args_raw.join(" "));
|
|
||||||
|
|
||||||
// init
|
|
||||||
lib_plankton.log.conf_push(
|
|
||||||
[
|
|
||||||
// lib_plankton.log.channel_make({"kind": "file", "data": {"threshold": "debug", "path": "/tmp/kalender.log"}}),
|
|
||||||
lib_plankton.log.channel_make({"kind": "file", "data": {"threshold": "info", "path": "/dev/stderr"}}),
|
|
||||||
// lib_plankton.log.channel_make({"kind": "stdout", "data": {"threshold": "info"}}),
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
// exec
|
|
||||||
if (args["help"]) {
|
|
||||||
process.stdout.write(
|
|
||||||
arg_handler.generate_help(
|
|
||||||
{
|
|
||||||
"programname": "kalender",
|
|
||||||
"description": "Kalender",
|
|
||||||
"executable": "kalender",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
+
|
|
||||||
"\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const data : type_datamodel = lib_plankton.call.convey(
|
|
||||||
await lib_plankton.file.read(args["data_path"]),
|
|
||||||
[
|
|
||||||
lib_plankton.json.decode,
|
|
||||||
(data_raw : any) => (
|
|
||||||
({
|
|
||||||
"users": data_raw["users"],
|
|
||||||
"calendars": (
|
|
||||||
data_raw["calendars"]
|
|
||||||
.map(
|
|
||||||
(calendar_entry_raw : any) => ({
|
|
||||||
"id": calendar_entry_raw["id"],
|
|
||||||
"object": (
|
|
||||||
((calendar_object_raw) => {
|
|
||||||
switch (calendar_object_raw["kind"]) {
|
|
||||||
default: {
|
|
||||||
return calendar_object_raw;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "concrete": {
|
|
||||||
return {
|
|
||||||
"kind": "concrete",
|
|
||||||
"data": {
|
|
||||||
"name": calendar_object_raw["data"]["name"],
|
|
||||||
"users": calendar_object_raw["data"]["users"],
|
|
||||||
"events": (
|
|
||||||
calendar_object_raw["data"]["events"]
|
|
||||||
.map(
|
|
||||||
(event_raw : any) => ({
|
|
||||||
"name": event_raw["name"],
|
|
||||||
"begin": event_raw["begin"],
|
|
||||||
"end": (
|
|
||||||
(event_raw["end"] === null)
|
|
||||||
?
|
|
||||||
null
|
|
||||||
:
|
|
||||||
event_raw["end"]
|
|
||||||
),
|
|
||||||
"description": event_raw["description"],
|
|
||||||
})
|
|
||||||
)
|
|
||||||
),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}) (calendar_entry_raw["object"])
|
|
||||||
),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
),
|
|
||||||
}) as type_datamodel
|
|
||||||
),
|
|
||||||
]
|
|
||||||
);
|
|
||||||
let content : string;
|
|
||||||
switch (args["view_mode"]) {
|
|
||||||
default: {
|
|
||||||
content = "";
|
|
||||||
throw (new Error("invalid view mode"));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "table": {
|
|
||||||
content = await calendar_view_table_html(
|
|
||||||
data,
|
|
||||||
args.calendar_id,
|
|
||||||
{
|
|
||||||
"timezone_shift": args["timezone_shift"],
|
|
||||||
}
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "list": {
|
|
||||||
content = await calendar_view_list_html(
|
|
||||||
data,
|
|
||||||
args.calendar_id,
|
|
||||||
{
|
|
||||||
"timezone_shift": args["timezone_shift"],
|
|
||||||
}
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const output : string = template_coin(
|
|
||||||
"main",
|
|
||||||
{
|
|
||||||
"content": content,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
process.stdout.write(output);
|
|
||||||
}
|
|
||||||
return Promise.resolve<void>(undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
process.stderr.write(
|
|
||||||
JSON.stringify(
|
|
||||||
{
|
|
||||||
"x1": datetime_from_date_object(
|
|
||||||
new Date(Date.now()),
|
|
||||||
{
|
|
||||||
"timezone_shift": 2,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
"x2": datetime_from_date_object(
|
|
||||||
new Date(Date.now()),
|
|
||||||
{
|
|
||||||
"timezone_shift": 0,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
"x3": pit_from_year_and_week(
|
|
||||||
2024,
|
|
||||||
37,
|
|
||||||
{
|
|
||||||
"timezone_shift": 0,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
},
|
|
||||||
undefined,
|
|
||||||
"\t"
|
|
||||||
)
|
|
||||||
+
|
|
||||||
"\n"
|
|
||||||
);
|
|
||||||
*/
|
|
||||||
(
|
|
||||||
main(process.argv.slice(2))
|
|
||||||
.then(
|
|
||||||
() => {}
|
|
||||||
)
|
|
||||||
/*
|
|
||||||
.catch(
|
|
||||||
(error) => {process.stderr.write(String(error) + "\n");}
|
|
||||||
)
|
|
||||||
*/
|
|
||||||
);
|
|
|
@ -1,133 +0,0 @@
|
||||||
/**
|
|
||||||
*/
|
|
||||||
namespace _zeitbild.frontend
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
type type_conf = {
|
|
||||||
view_mode : string;
|
|
||||||
calendar_ids : Array<int>;
|
|
||||||
timezone_shift : int;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
async function render(
|
|
||||||
conf : type_conf,
|
|
||||||
calendar_ids : Array<type_calendar_id>
|
|
||||||
) : Promise<void>
|
|
||||||
{
|
|
||||||
calendar_ids.sort();
|
|
||||||
const target : HTMLElement = (document.querySelector("body") as HTMLBodyElement);
|
|
||||||
switch (conf.view_mode) {
|
|
||||||
default: {
|
|
||||||
throw (new Error("invalid view mode"));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "table": {
|
|
||||||
const content : string = await _zeitbild.frontend.view.calendar_view_table_html(
|
|
||||||
calendar_ids,
|
|
||||||
{
|
|
||||||
"from": {
|
|
||||||
"year": 2024,
|
|
||||||
"week": 35
|
|
||||||
},
|
|
||||||
"to": {
|
|
||||||
"year": 2024,
|
|
||||||
"week": 43
|
|
||||||
},
|
|
||||||
"timezone_shift": conf.timezone_shift,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
target.innerHTML = content;
|
|
||||||
/*
|
|
||||||
document.querySelectorAll(".tableview-sources-entry").forEach(
|
|
||||||
(element) => {
|
|
||||||
element.addEventListener(
|
|
||||||
"click",
|
|
||||||
(event) => {
|
|
||||||
const element_ : HTMLElement = (event.target as HTMLElement);
|
|
||||||
const calendar_id : type_calendar_id = parseInt(element_.getAttribute("rel") as string);
|
|
||||||
const active : boolean = element_.classList.toggle("tableview-sources-entry-active");
|
|
||||||
render(
|
|
||||||
conf,
|
|
||||||
lib_plankton.call.convey(
|
|
||||||
calendar_ids,
|
|
||||||
[
|
|
||||||
(x : Array<int>) => (
|
|
||||||
active
|
|
||||||
?
|
|
||||||
calendar_ids.concat([calendar_id])
|
|
||||||
:
|
|
||||||
calendar_ids.filter(y => (y !== calendar_id))
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
*/
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "list": {
|
|
||||||
const content : string = await _zeitbild.frontend.view.calendar_view_list_html(
|
|
||||||
calendar_ids,
|
|
||||||
{
|
|
||||||
"timezone_shift": conf.timezone_shift,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
target.innerHTML = content;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Promise.resolve<void>(undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
export async function main(
|
|
||||||
) : Promise<void>
|
|
||||||
{
|
|
||||||
// init
|
|
||||||
lib_plankton.log.conf_push(
|
|
||||||
[
|
|
||||||
lib_plankton.log.channel_make({"kind": "console", "data": {"threshold": "info"}}),
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
// conf
|
|
||||||
const conf : type_conf = lib_plankton.json.decode(await lib_plankton.file.read("conf.json"));
|
|
||||||
|
|
||||||
// args
|
|
||||||
|
|
||||||
const url : URL = new URL(window.location.toString());
|
|
||||||
const calendar_ids : Array<type_calendar_id> = (
|
|
||||||
(url.searchParams.get("ids") !== null)
|
|
||||||
?
|
|
||||||
lib_plankton.call.convey(
|
|
||||||
url.searchParams.get("ids"),
|
|
||||||
[
|
|
||||||
(x : string) => lib_plankton.string.split(x, ","),
|
|
||||||
(x : Array<string>) => x.map(y => parseInt(y)),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
:
|
|
||||||
(await _zeitbild.frontend.resources.backend.calendar_list()).map(x => x.key)
|
|
||||||
);
|
|
||||||
|
|
||||||
// exec
|
|
||||||
await render(
|
|
||||||
conf,
|
|
||||||
calendar_ids
|
|
||||||
);
|
|
||||||
|
|
||||||
return Promise.resolve<void>(undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,103 +0,0 @@
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
namespace _zeitbild.frontend
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
type type_role = (
|
|
||||||
"editor"
|
|
||||||
|
|
|
||||||
"viewer"
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
type type_user_id = int;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
type type_user_object = {
|
|
||||||
name : string;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
export type type_event = {
|
|
||||||
name : string;
|
|
||||||
begin : _zeitbild.frontend.helpers.type_datetime;
|
|
||||||
end : (
|
|
||||||
null
|
|
||||||
|
|
|
||||||
_zeitbild.frontend.helpers.type_datetime
|
|
||||||
);
|
|
||||||
location : (
|
|
||||||
null
|
|
||||||
|
|
|
||||||
string
|
|
||||||
);
|
|
||||||
description : (
|
|
||||||
null
|
|
||||||
|
|
|
||||||
string
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
export type type_calendar_id = int;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
export type type_calendar_object = (
|
|
||||||
{
|
|
||||||
kind : "concrete";
|
|
||||||
data : {
|
|
||||||
name : string;
|
|
||||||
private : boolean;
|
|
||||||
users : Array<
|
|
||||||
{
|
|
||||||
id : type_user_id;
|
|
||||||
role : type_role;
|
|
||||||
}
|
|
||||||
>;
|
|
||||||
events : Array<type_event>;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
||||||
{
|
|
||||||
kind : "caldav";
|
|
||||||
data : {
|
|
||||||
name : string;
|
|
||||||
private : boolean;
|
|
||||||
read_only : boolean;
|
|
||||||
source_url : string;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
export type type_datamodel = {
|
|
||||||
users : Array<
|
|
||||||
{
|
|
||||||
id : type_user_id;
|
|
||||||
object : type_user_object;
|
|
||||||
}
|
|
||||||
>;
|
|
||||||
calendars : Array<
|
|
||||||
{
|
|
||||||
id : type_calendar_id;
|
|
||||||
object : type_calendar_object;
|
|
||||||
}
|
|
||||||
>;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
227
source/main.ts
Normal file
227
source/main.ts
Normal file
|
@ -0,0 +1,227 @@
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
async function main(
|
||||||
|
args_raw : Array<string>
|
||||||
|
) : Promise<void>
|
||||||
|
{
|
||||||
|
// init1
|
||||||
|
lib_plankton.log.conf_push(
|
||||||
|
[
|
||||||
|
lib_plankton.log.channel_make({"kind": "stdout", "data": {"threshold": "debug"}}),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// args
|
||||||
|
const arg_handler : lib_plankton.args.class_handler = new lib_plankton.args.class_handler({
|
||||||
|
"action": lib_plankton.args.class_argument.positional({
|
||||||
|
"index": 0,
|
||||||
|
"type": lib_plankton.args.enum_type.string,
|
||||||
|
"mode": lib_plankton.args.enum_mode.replace,
|
||||||
|
"default": "serve",
|
||||||
|
"name": "action",
|
||||||
|
"info": lib_plankton.string.coin(
|
||||||
|
"{{description}}:\n{{options}}\n\t\t",
|
||||||
|
{
|
||||||
|
"description": "action",
|
||||||
|
"options": (
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "serve",
|
||||||
|
"description": "serve"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "api-doc",
|
||||||
|
"description": "api-doc"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "expose-conf",
|
||||||
|
"description": "expose-conf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "help",
|
||||||
|
"description": "help"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
.map(
|
||||||
|
entry => lib_plankton.string.coin(
|
||||||
|
"\t\t- {{name}}\n\t\t\t{{description}}\n",
|
||||||
|
{
|
||||||
|
"name": entry.name,
|
||||||
|
"description": entry.description,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.join("")
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
"conf_path": lib_plankton.args.class_argument.volatile({
|
||||||
|
"indicators_long": ["conf_path"],
|
||||||
|
"indicators_short": ["c"],
|
||||||
|
"type": lib_plankton.args.enum_type.string,
|
||||||
|
"mode": lib_plankton.args.enum_mode.replace,
|
||||||
|
"default": "conf.json",
|
||||||
|
// "info": null,
|
||||||
|
"name": "conf-path",
|
||||||
|
}),
|
||||||
|
"data_path": lib_plankton.args.class_argument.volatile({
|
||||||
|
"indicators_long": ["data-path"],
|
||||||
|
"indicators_short": ["d"],
|
||||||
|
"type": lib_plankton.args.enum_type.string,
|
||||||
|
"mode": lib_plankton.args.enum_mode.replace,
|
||||||
|
"default": "data.json",
|
||||||
|
// "info": null,
|
||||||
|
"name": "data-path",
|
||||||
|
}),
|
||||||
|
"help": lib_plankton.args.class_argument.volatile({
|
||||||
|
"indicators_long": ["help"],
|
||||||
|
"indicators_short": ["h"],
|
||||||
|
"type": lib_plankton.args.enum_type.boolean,
|
||||||
|
"mode": lib_plankton.args.enum_mode.replace,
|
||||||
|
"default": false,
|
||||||
|
// "info": null,
|
||||||
|
"name": "help",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const args : Record<string, any> = arg_handler.read(lib_plankton.args.enum_environment.cli, args_raw.join(" "));
|
||||||
|
|
||||||
|
// init2
|
||||||
|
await _zeitbild.conf.load(args["conf_path"]);
|
||||||
|
lib_plankton.log.conf_push(
|
||||||
|
_zeitbild.conf.get().log.map(
|
||||||
|
log_output => lib_plankton.log.channel_make(
|
||||||
|
{
|
||||||
|
"kind": log_output.kind,
|
||||||
|
"data": log_output.data
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// exec
|
||||||
|
if (args["help"]) {
|
||||||
|
process.stdout.write(
|
||||||
|
arg_handler.generate_help(
|
||||||
|
{
|
||||||
|
"programname": "zeitbild",
|
||||||
|
"description": "zeitbild-backend",
|
||||||
|
"executable": "zeitbild",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
+
|
||||||
|
"\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
switch (args["action"]) {
|
||||||
|
default: {
|
||||||
|
lib_plankton.log.error(
|
||||||
|
"main_invalid_action",
|
||||||
|
{
|
||||||
|
"action": args["action"],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "expose-conf": {
|
||||||
|
process.stdout.write(
|
||||||
|
JSON.stringify(
|
||||||
|
_zeitbild.conf.get(),
|
||||||
|
undefined,
|
||||||
|
"\t"
|
||||||
|
)
|
||||||
|
+
|
||||||
|
"\n"
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "api-doc": {
|
||||||
|
lib_plankton.log.conf_push([]);
|
||||||
|
const rest_subject : lib_plankton.rest.type_rest = _zeitbild.api.make();
|
||||||
|
lib_plankton.log.conf_pop();
|
||||||
|
process.stdout.write(
|
||||||
|
JSON.stringify(
|
||||||
|
lib_plankton.rest.to_oas(rest_subject),
|
||||||
|
undefined,
|
||||||
|
"\t"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "serve": {
|
||||||
|
// prepare database
|
||||||
|
await _zeitbild.database.check();
|
||||||
|
|
||||||
|
await lib_plankton.session.setup(
|
||||||
|
{
|
||||||
|
"data_chest": (
|
||||||
|
_zeitbild.conf.get().session_management.in_memory
|
||||||
|
? lib_plankton.storage.memory.implementation_chest<lib_plankton.session.type_session>({})
|
||||||
|
: lib_plankton.call.convey(
|
||||||
|
lib_plankton.storage.sql_table_common.chest(
|
||||||
|
{
|
||||||
|
"database_implementation": _zeitbild.database.get_implementation(),
|
||||||
|
"table_name": "sessions",
|
||||||
|
"key_names": ["key"],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
[
|
||||||
|
(core : any) => ({
|
||||||
|
"setup": (input : any) => core.setup(undefined),
|
||||||
|
"clear": () => core.clear(),
|
||||||
|
"write": (key : any, value : any) => core.write([key], {"data": JSON.stringify(value)}),
|
||||||
|
"delete": (key : any) => core.delete([key]),
|
||||||
|
"read": (key : any) => core.read([key]).then((row : any) => JSON.parse(row["data"])),
|
||||||
|
// "search": (term : any) => core.search(term).then(() => []),
|
||||||
|
"search": (term : any) => Promise.reject(new Error("not implemented")),
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"default_lifetime": _zeitbild.conf.get().session_management.lifetime,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const rest_subject : lib_plankton.rest.type_rest = _zeitbild.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(
|
||||||
|
rest_subject,
|
||||||
|
http_request,
|
||||||
|
{
|
||||||
|
"checklevel_restriction": lib_plankton.api.enum_checklevel.hard,
|
||||||
|
// "checklevel_input": lib_plankton.api.enum_checklevel.soft,
|
||||||
|
// "checklevel_output": lib_plankton.api.enum_checklevel.soft,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const output : string = lib_plankton.http.encode_response(http_response);
|
||||||
|
return output;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"host": _zeitbild.conf.get().server.host,
|
||||||
|
"port": _zeitbild.conf.get().server.port,
|
||||||
|
// DANGER! DANGER!
|
||||||
|
"threshold": 0.125,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
lib_plankton.server.start(server);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Promise.resolve<void>(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
(
|
||||||
|
main(process.argv.slice(2))
|
||||||
|
.then(
|
||||||
|
() => {}
|
||||||
|
)
|
||||||
|
.catch(
|
||||||
|
(error) => {process.stderr.write(String(error) + "\n");}
|
||||||
|
)
|
||||||
|
);
|
254
source/repositories/calendar.ts
Normal file
254
source/repositories/calendar.ts
Normal file
|
@ -0,0 +1,254 @@
|
||||||
|
|
||||||
|
namespace _zeitbild.repository.calendar
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
var _core_store : (
|
||||||
|
null
|
||||||
|
|
|
||||||
|
lib_plankton.storage.type_core_store<
|
||||||
|
_zeitbild.type.calendar_id,
|
||||||
|
Record<string, any>,
|
||||||
|
{},
|
||||||
|
lib_plankton.storage.type_sql_table_autokey_search_term,
|
||||||
|
Record<string, any>
|
||||||
|
>
|
||||||
|
) = null;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
var _event_store : (
|
||||||
|
null
|
||||||
|
|
|
||||||
|
lib_plankton.storage.type_core_store<
|
||||||
|
_zeitbild.type.event_id,
|
||||||
|
Record<string, any>,
|
||||||
|
{},
|
||||||
|
lib_plankton.storage.type_sql_table_autokey_search_term,
|
||||||
|
Record<string, any>
|
||||||
|
>
|
||||||
|
) = null;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
function get_core_store(
|
||||||
|
) : lib_plankton.storage.type_core_store<
|
||||||
|
_zeitbild.type.calendar_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_core_store(
|
||||||
|
{
|
||||||
|
"database_implementation": _zeitbild.database.get_implementation(),
|
||||||
|
"table_name": "calendars",
|
||||||
|
"key_name": "id",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
return _core_store;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
function get_event_store(
|
||||||
|
) : lib_plankton.storage.type_core_store<
|
||||||
|
_zeitbild.type.event_id,
|
||||||
|
Record<string, any>,
|
||||||
|
{},
|
||||||
|
lib_plankton.storage.type_sql_table_autokey_search_term,
|
||||||
|
Record<string, any>
|
||||||
|
>
|
||||||
|
{
|
||||||
|
if (_event_store === null) {
|
||||||
|
_event_store = lib_plankton.storage.sql_table_autokey_core_store(
|
||||||
|
{
|
||||||
|
"database_implementation": _zeitbild.database.get_implementation(),
|
||||||
|
"table_name": "events",
|
||||||
|
"key_name": "id",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
return _event_store;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo use events table
|
||||||
|
*/
|
||||||
|
function encode(
|
||||||
|
object : _zeitbild.type.calendar_object
|
||||||
|
) : Record<string, any>
|
||||||
|
{
|
||||||
|
switch (object.kind) {
|
||||||
|
/*
|
||||||
|
case "concrete": {
|
||||||
|
const data_raw : any = lib_plankton.json.encode(object.data);
|
||||||
|
data_raw["users"]
|
||||||
|
return {
|
||||||
|
"name": object.name,
|
||||||
|
"private": object.private,
|
||||||
|
"kind": object.kind,
|
||||||
|
"data": {
|
||||||
|
"users": data_raw["users"],
|
||||||
|
"events": [] // TODO
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
default: {
|
||||||
|
return {
|
||||||
|
"name": object.name,
|
||||||
|
"private": object.private,
|
||||||
|
"kind": object.kind,
|
||||||
|
"data": lib_plankton.json.encode(object.data),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
function decode(
|
||||||
|
row : Record<string, any>
|
||||||
|
) : _zeitbild.type.calendar_object
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
"name": row["name"],
|
||||||
|
"private": row["private"],
|
||||||
|
"kind": row["kind"],
|
||||||
|
"data": lib_plankton.json.decode(row["data"]),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export async function dump(
|
||||||
|
) : Promise<
|
||||||
|
Array<
|
||||||
|
{
|
||||||
|
id : _zeitbild.type.calendar_id;
|
||||||
|
object : _zeitbild.type.calendar_object;
|
||||||
|
}
|
||||||
|
>
|
||||||
|
>
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
(await get_core_store().search(null))
|
||||||
|
.map(
|
||||||
|
({"key": key, "preview": preview}) => ({
|
||||||
|
"id": key,
|
||||||
|
"object": (preview as _zeitbild.type.calendar_object),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo optimize
|
||||||
|
*/
|
||||||
|
export async function list(
|
||||||
|
search_term : (null | string)
|
||||||
|
) : Promise<
|
||||||
|
Array<
|
||||||
|
{
|
||||||
|
id : _zeitbild.type.calendar_id;
|
||||||
|
preview : {
|
||||||
|
name : string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
>
|
||||||
|
>
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
(await get_core_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"],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export async function read(
|
||||||
|
id : _zeitbild.type.calendar_id
|
||||||
|
) : Promise<_zeitbild.type.calendar_object>
|
||||||
|
{
|
||||||
|
const row : Record<string, any> = await get_core_store().read(id);
|
||||||
|
|
||||||
|
return decode(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export async function create(
|
||||||
|
value : _zeitbild.type.calendar_object
|
||||||
|
) : Promise<_zeitbild.type.calendar_id>
|
||||||
|
{
|
||||||
|
const row : Record<string, any> = encode(value);
|
||||||
|
const id : _zeitbild.type.calendar_id = await get_core_store().create(row);
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export async function update(
|
||||||
|
id : _zeitbild.type.calendar_id,
|
||||||
|
value : _zeitbild.type.calendar_object
|
||||||
|
) : Promise<void>
|
||||||
|
{
|
||||||
|
const row : Record<string, any> = encode(value);
|
||||||
|
|
||||||
|
await get_core_store().update(id, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export async function delete_(
|
||||||
|
id : _zeitbild.type.calendar_id
|
||||||
|
) : Promise<void>
|
||||||
|
{
|
||||||
|
await get_core_store().delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
206
source/services/calendar.ts
Normal file
206
source/services/calendar.ts
Normal file
|
@ -0,0 +1,206 @@
|
||||||
|
|
||||||
|
namespace _zeitbild.service.calendar
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export async function list(
|
||||||
|
search_term : (null | string)
|
||||||
|
) : Promise<
|
||||||
|
Array<
|
||||||
|
{
|
||||||
|
id : _zeitbild.type.calendar_id;
|
||||||
|
preview : {
|
||||||
|
name : string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
>
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
_zeitbild.repository.calendar.list(search_term)
|
||||||
|
.then(
|
||||||
|
x => x.map(
|
||||||
|
(y : any) => ({
|
||||||
|
"id": y.key,
|
||||||
|
"preview": y.preview,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export async function get(
|
||||||
|
calendar_id : _zeitbild.type.calendar_id
|
||||||
|
) : Promise<_zeitbild.type.calendar_object>
|
||||||
|
{
|
||||||
|
return _zeitbild.repository.calendar.read(calendar_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo prevent loops
|
||||||
|
*/
|
||||||
|
export async function gather_events(
|
||||||
|
calendar_ids : Array<_zeitbild.type.calendar_id>,
|
||||||
|
from_pit : _zeitbild.helpers.type_pit,
|
||||||
|
to_pit : _zeitbild.helpers.type_pit
|
||||||
|
) : Promise<
|
||||||
|
Array<
|
||||||
|
{
|
||||||
|
calendar_id : _zeitbild.type.calendar_id;
|
||||||
|
calendar_name : string;
|
||||||
|
event : _zeitbild.type.event_object;
|
||||||
|
}
|
||||||
|
>
|
||||||
|
>
|
||||||
|
{
|
||||||
|
lib_plankton.log.info(
|
||||||
|
"calendar_gather_events",
|
||||||
|
{
|
||||||
|
"calendar_ids": calendar_ids,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
let result : Array<
|
||||||
|
{
|
||||||
|
calendar_id : _zeitbild.type.calendar_id;
|
||||||
|
calendar_name : string;
|
||||||
|
event : _zeitbild.type.event_object;
|
||||||
|
}
|
||||||
|
> = [];
|
||||||
|
for await (const calendar_id of calendar_ids) {
|
||||||
|
const calendar_object : _zeitbild.type.calendar_object = await _zeitbild.repository.calendar.read(
|
||||||
|
calendar_id
|
||||||
|
);
|
||||||
|
if (calendar_object.private) {
|
||||||
|
lib_plankton.log.info(
|
||||||
|
"calendar_gather_events_private_calendar_blocked",
|
||||||
|
{
|
||||||
|
"calendar_id": calendar_id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
switch (calendar_object.kind) {
|
||||||
|
case "concrete": {
|
||||||
|
result = (
|
||||||
|
result
|
||||||
|
.concat(
|
||||||
|
calendar_object.data.events
|
||||||
|
.filter(
|
||||||
|
(event : _zeitbild.type.event_object) => _zeitbild.helpers.pit_is_between(
|
||||||
|
_zeitbild.helpers.pit_from_datetime(event.begin),
|
||||||
|
from_pit,
|
||||||
|
to_pit
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.map(
|
||||||
|
(event : _zeitbild.type.event_object) => ({
|
||||||
|
"calendar_id": calendar_id,
|
||||||
|
"calendar_name": calendar_object.name,
|
||||||
|
"event": event
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "caldav": {
|
||||||
|
const url : lib_plankton.url.type_url = lib_plankton.url.decode(
|
||||||
|
calendar_object.data.source_url
|
||||||
|
);
|
||||||
|
const http_request : lib_plankton.http.type_request = {
|
||||||
|
"version": "HTTP/2",
|
||||||
|
"scheme": ((url.scheme === "https") ? "https" : "http"),
|
||||||
|
"host": url.host,
|
||||||
|
"path": (url.path ?? "/"),
|
||||||
|
"query": url.query,
|
||||||
|
"method": lib_plankton.http.enum_method.get,
|
||||||
|
"headers": {},
|
||||||
|
"body": null,
|
||||||
|
};
|
||||||
|
// TODO: cache?
|
||||||
|
const http_response : lib_plankton.http.type_response = await lib_plankton.http.call(
|
||||||
|
http_request,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const vcalendar : lib_plankton.ical.type_vcalendar = lib_plankton.ical.ics_decode(
|
||||||
|
http_response.body.toString(),
|
||||||
|
{
|
||||||
|
}
|
||||||
|
);
|
||||||
|
result = (
|
||||||
|
result
|
||||||
|
.concat(
|
||||||
|
vcalendar.vevents
|
||||||
|
.map(
|
||||||
|
(vevent : lib_plankton.ical.type_vevent) => (
|
||||||
|
(vevent.dtstart !== undefined)
|
||||||
|
?
|
||||||
|
{
|
||||||
|
"name": (
|
||||||
|
(vevent.summary !== undefined)
|
||||||
|
?
|
||||||
|
vevent.summary
|
||||||
|
:
|
||||||
|
"???"
|
||||||
|
),
|
||||||
|
"begin": _zeitbild.helpers.ical_dt_to_own_datetime(vevent.dtstart),
|
||||||
|
"end": (
|
||||||
|
(vevent.dtend !== undefined)
|
||||||
|
?
|
||||||
|
_zeitbild.helpers.ical_dt_to_own_datetime(vevent.dtend)
|
||||||
|
:
|
||||||
|
null
|
||||||
|
),
|
||||||
|
"location": (
|
||||||
|
(vevent.location !== undefined)
|
||||||
|
?
|
||||||
|
vevent.location
|
||||||
|
:
|
||||||
|
null
|
||||||
|
),
|
||||||
|
"description": (
|
||||||
|
(vevent.description !== undefined)
|
||||||
|
?
|
||||||
|
vevent.description
|
||||||
|
:
|
||||||
|
null
|
||||||
|
),
|
||||||
|
}
|
||||||
|
:
|
||||||
|
null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.filter(
|
||||||
|
(event) => (event !== null)
|
||||||
|
)
|
||||||
|
.filter(
|
||||||
|
(event) => _zeitbild.helpers.pit_is_between(
|
||||||
|
_zeitbild.helpers.pit_from_datetime(event.begin),
|
||||||
|
from_pit,
|
||||||
|
to_pit
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.map(
|
||||||
|
(event) => ({
|
||||||
|
"calendar_id": calendar_id,
|
||||||
|
"calendar_name": calendar_object.name,
|
||||||
|
"event": event,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Promise.resolve(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,84 +0,0 @@
|
||||||
html {
|
|
||||||
background-color: #111;
|
|
||||||
color: #FFF;
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar-pane-left {
|
|
||||||
flex-basis: 12.5%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar-pane-right {
|
|
||||||
flex-basis: 87.5%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tableview-sources {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
list-style-type: none;
|
|
||||||
font-size: 0.75em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tableview-sources-entry {
|
|
||||||
margin: 8px;
|
|
||||||
padding: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tableview-sources-entry:not(.tableview-sources-entry-active) {
|
|
||||||
filter: saturate(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar-cell {
|
|
||||||
border: 1px solid #888;
|
|
||||||
padding: 8px;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar-cell-day {
|
|
||||||
width: 13.5%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar-cell-week {
|
|
||||||
width: 5.5%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar-cell-regular {
|
|
||||||
width: 13.5%;
|
|
||||||
height: 120px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar-cell-today {
|
|
||||||
outline: 4px solid #FFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar-day {
|
|
||||||
font-size: 0.75em;
|
|
||||||
cursor: help;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar-events {
|
|
||||||
margin: 0; padding: 0;
|
|
||||||
list-style-type: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar-event_entry {
|
|
||||||
margin: 4px;
|
|
||||||
padding: 4px;
|
|
||||||
border-radius: 2px;
|
|
||||||
font-size: 0.75em;
|
|
||||||
color: #FFF;
|
|
||||||
font-weight: bold;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8"/>
|
|
||||||
<link rel="stylesheet" type="text/css" href="style.css"/>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,3 +0,0 @@
|
||||||
<li class="calendar-event_entry" style="background-color: {{color}};" title="{{title}}">
|
|
||||||
{{name}}
|
|
||||||
</li>
|
|
|
@ -1,8 +0,0 @@
|
||||||
<td class="calendar-cell calendar-cell-regular{{extra_classes}}">
|
|
||||||
<span class="calendar-day" title="{{title}}">
|
|
||||||
{{day}}
|
|
||||||
</span>
|
|
||||||
<ul class="calendar-events">
|
|
||||||
{{entries}}
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
|
@ -1,6 +0,0 @@
|
||||||
<tr>
|
|
||||||
<th class="calendar-cell calendar-cell-week">
|
|
||||||
{{week}}
|
|
||||||
</th>
|
|
||||||
{{cells}}
|
|
||||||
</tr>
|
|
|
@ -1 +0,0 @@
|
||||||
<li class="tableview-sources-entry tableview-sources-entry-active" style="background-color: {{color}}" rel="{{rel}}">{{name}}</li>
|
|
|
@ -1,27 +0,0 @@
|
||||||
<div class="calendar">
|
|
||||||
<div class="calendar-pane calendar-pane-left">
|
|
||||||
<ul class="tableview-sources">
|
|
||||||
{{sources}}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="calendar-pane calendar-pane-right">
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th class="calendar-cell"></th>
|
|
||||||
<th class="calendar-cell calendar-cell-day">Mo</th>
|
|
||||||
<th class="calendar-cell calendar-cell-day">Di</th>
|
|
||||||
<th class="calendar-cell calendar-cell-day">Mi</th>
|
|
||||||
<th class="calendar-cell calendar-cell-day">Do</th>
|
|
||||||
<th class="calendar-cell calendar-cell-day">Fr</th>
|
|
||||||
<th class="calendar-cell calendar-cell-day">Sa</th>
|
|
||||||
<th class="calendar-cell calendar-cell-day">So</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{{rows}}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
93
source/types.ts
Normal file
93
source/types.ts
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
namespace _zeitbild.type
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
type role = (
|
||||||
|
"editor"
|
||||||
|
|
|
||||||
|
"viewer"
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
type user_id = int;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
type user_object = {
|
||||||
|
name : string;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export type event_id = int;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export type event_object = {
|
||||||
|
name : string;
|
||||||
|
begin : _zeitbild.helpers.type_datetime;
|
||||||
|
end : (
|
||||||
|
null
|
||||||
|
|
|
||||||
|
_zeitbild.helpers.type_datetime
|
||||||
|
);
|
||||||
|
location : (
|
||||||
|
null
|
||||||
|
|
|
||||||
|
string
|
||||||
|
);
|
||||||
|
description : (
|
||||||
|
null
|
||||||
|
|
|
||||||
|
string
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export type calendar_id = int;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export type calendar_object = (
|
||||||
|
{
|
||||||
|
name : string;
|
||||||
|
private : boolean;
|
||||||
|
}
|
||||||
|
&
|
||||||
|
(
|
||||||
|
{
|
||||||
|
kind : "concrete";
|
||||||
|
data : {
|
||||||
|
users : Array<
|
||||||
|
{
|
||||||
|
id : user_id;
|
||||||
|
role : role;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
events : Array<event_object>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
||||||
|
{
|
||||||
|
kind : "caldav";
|
||||||
|
data : {
|
||||||
|
read_only : boolean;
|
||||||
|
source_url : string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
|
@ -12,7 +12,7 @@ def main():
|
||||||
"-o",
|
"-o",
|
||||||
"--output-directory",
|
"--output-directory",
|
||||||
type = str,
|
type = str,
|
||||||
default = "/tmp/kalender",
|
default = "/tmp/zeitbild",
|
||||||
metavar = "<output-directory>",
|
metavar = "<output-directory>",
|
||||||
help = "output directory",
|
help = "output directory",
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
dir_lib := lib
|
dir_lib := lib
|
||||||
dir_source := source
|
dir_source := source
|
||||||
dir_temp := /tmp/kalender-temp
|
dir_temp := /tmp/zeitbild-temp
|
||||||
dir_build := build
|
dir_build := build
|
||||||
dir_tools := tools
|
dir_tools := tools
|
||||||
|
|
||||||
|
@ -11,53 +11,42 @@ cmd_chmod := chmod
|
||||||
cmd_cp := cp
|
cmd_cp := cp
|
||||||
cmd_log := echo "--"
|
cmd_log := echo "--"
|
||||||
cmd_mkdir := mkdir -p
|
cmd_mkdir := mkdir -p
|
||||||
|
cmd_echo := echo
|
||||||
cmd_tsc := ${dir_tools}/typescript/node_modules/.bin/tsc
|
cmd_tsc := ${dir_tools}/typescript/node_modules/.bin/tsc
|
||||||
|
|
||||||
|
|
||||||
## rules
|
## rules
|
||||||
|
|
||||||
.PHONY: default
|
.PHONY: default
|
||||||
default: index templates style logic
|
default: ${dir_build}/zeitbild
|
||||||
|
|
||||||
.PHONY: index
|
${dir_temp}/zeitbild-unlinked.js: \
|
||||||
index: ${dir_build}/index.html
|
|
||||||
|
|
||||||
${dir_build}/index.html: \
|
|
||||||
${dir_source}/index.html
|
|
||||||
@ ${cmd_log} "index …"
|
|
||||||
@ ${cmd_cp} -u -v $^ $@
|
|
||||||
|
|
||||||
.PHONY: templates
|
|
||||||
templates: \
|
|
||||||
$(wildcard ${dir_source}/templates/*)
|
|
||||||
@ ${cmd_log} "templates …"
|
|
||||||
@ ${cmd_mkdir} ${dir_build}/templates
|
|
||||||
@ ${cmd_cp} -r -u -v ${dir_source}/templates/* ${dir_build}/templates/
|
|
||||||
|
|
||||||
.PHONY: style
|
|
||||||
style: \
|
|
||||||
$(wildcard ${dir_source}/style/*)
|
|
||||||
@ ${cmd_log} "style …"
|
|
||||||
@ ${cmd_mkdir} ${dir_build}
|
|
||||||
@ ${cmd_cat} ${dir_source}/style/* > ${dir_build}/style.css
|
|
||||||
|
|
||||||
.PHONY: logic
|
|
||||||
logic: ${dir_build}/logic.js
|
|
||||||
|
|
||||||
${dir_temp}/logic-unlinked.js: \
|
|
||||||
${dir_lib}/plankton/plankton.d.ts \
|
${dir_lib}/plankton/plankton.d.ts \
|
||||||
${dir_source}/logic/helpers.ts \
|
${dir_source}/helpers.ts \
|
||||||
${dir_source}/logic/types.ts \
|
${dir_source}/conf.ts \
|
||||||
${dir_source}/logic/backend.ts \
|
${dir_source}/database.ts \
|
||||||
${dir_source}/logic/view.ts \
|
${dir_source}/types.ts \
|
||||||
${dir_source}/logic/main.ts
|
${dir_source}/repositories/calendar.ts \
|
||||||
@ ${cmd_log} "logic | compile …"
|
${dir_source}/services/calendar.ts \
|
||||||
|
${dir_source}/api/base.ts \
|
||||||
|
${dir_source}/api/actions/meta_ping.ts \
|
||||||
|
${dir_source}/api/actions/meta_spec.ts \
|
||||||
|
${dir_source}/api/actions/calendar_list.ts \
|
||||||
|
${dir_source}/api/functions.ts \
|
||||||
|
${dir_source}/main.ts
|
||||||
|
@ ${cmd_log} "compile …"
|
||||||
@ ${cmd_mkdir} $(dir $@)
|
@ ${cmd_mkdir} $(dir $@)
|
||||||
@ ${cmd_tsc} --lib dom,es2020 --strict $^ --outFile $@
|
@ ${cmd_tsc} --lib es2020 --strict $^ --outFile $@
|
||||||
|
|
||||||
${dir_build}/logic.js: \
|
${dir_temp}/head.js:
|
||||||
|
@ ${cmd_mkdir} $(dir $@)
|
||||||
|
@ ${cmd_echo} "#!/usr/bin/env node" > $@
|
||||||
|
|
||||||
|
${dir_build}/zeitbild: \
|
||||||
|
${dir_temp}/head.js \
|
||||||
${dir_lib}/plankton/plankton.js \
|
${dir_lib}/plankton/plankton.js \
|
||||||
${dir_temp}/logic-unlinked.js
|
${dir_temp}/zeitbild-unlinked.js
|
||||||
@ ${cmd_log} "logic | link …"
|
@ ${cmd_log} "link …"
|
||||||
@ ${cmd_mkdir} $(dir $@)
|
@ ${cmd_mkdir} $(dir $@)
|
||||||
@ ${cmd_cat} $^ > $@
|
@ ${cmd_cat} $^ > $@
|
||||||
|
@ ${cmd_chmod} +x $@
|
||||||
|
|
|
@ -7,22 +7,26 @@ dir=lib/plankton
|
||||||
modules=""
|
modules=""
|
||||||
modules="${modules} base"
|
modules="${modules} base"
|
||||||
modules="${modules} call"
|
modules="${modules} call"
|
||||||
|
modules="${modules} log"
|
||||||
|
modules="${modules} storage"
|
||||||
|
modules="${modules} database"
|
||||||
|
modules="${modules} session"
|
||||||
modules="${modules} file"
|
modules="${modules} file"
|
||||||
|
modules="${modules} string"
|
||||||
modules="${modules} structures"
|
modules="${modules} structures"
|
||||||
modules="${modules} json"
|
modules="${modules} json"
|
||||||
modules="${modules} args"
|
|
||||||
modules="${modules} string"
|
|
||||||
modules="${modules} color"
|
|
||||||
modules="${modules} xml"
|
|
||||||
modules="${modules} ical"
|
modules="${modules} ical"
|
||||||
modules="${modules} http"
|
|
||||||
modules="${modules} log"
|
|
||||||
modules="${modules} url"
|
modules="${modules} url"
|
||||||
|
modules="${modules} http"
|
||||||
|
modules="${modules} api"
|
||||||
|
modules="${modules} rest"
|
||||||
|
modules="${modules} server"
|
||||||
|
modules="${modules} args"
|
||||||
|
|
||||||
|
|
||||||
## exec
|
## exec
|
||||||
|
|
||||||
mkdir -p ${dir}
|
mkdir -p ${dir}
|
||||||
cd ${dir}
|
cd ${dir}
|
||||||
ptk bundle web ${modules}
|
ptk bundle node ${modules}
|
||||||
cd - > /dev/null
|
cd - > /dev/null
|
||||||
|
|
Loading…
Add table
Reference in a new issue