Compare commits

..

2 commits

Author SHA1 Message Date
fenris 78729c6111 [task-416] 2025-10-23 11:34:00 +02:00
fenris 45acbbbcd9 [task-416] [upd] plankton 2025-10-23 11:33:41 +02:00
23 changed files with 1516 additions and 380 deletions

View file

@ -658,6 +658,9 @@ declare namespace lib_plankton.call {
/**
*/
export function sleep(seconds: float): Promise<void>;
/**
*/
export function null_prop<type_value_from, type_value_to>(value_from: (null | type_value_from), function_: ((value: type_value_from) => type_value_to)): (null | type_value_to);
export {};
}
declare namespace lib_plankton.email {
@ -4598,6 +4601,7 @@ declare namespace lib_plankton.auth.oidc {
name: (null | string);
label: (null | string);
email: (null | string);
groups: (null | Array<string>);
};
/**
*/

View file

@ -1491,6 +1491,16 @@ var lib_plankton;
}));
}
call.sleep = sleep;
/**
*/
function null_prop(value_from, function_) {
return ((value_from === null)
?
null
:
function_(value_from));
}
call.null_prop = null_prop;
})(call = lib_plankton.call || (lib_plankton.call = {}));
})(lib_plankton || (lib_plankton = {}));
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
@ -4056,10 +4066,10 @@ var lib_plankton;
options = Object.assign({
"compare_value": instance_compare
}, options);
if (is_empty(list)) {
/*if (is_empty<type_element>(list)) {
throw (new Error("the max-arg of an empty list is not defined"));
}
else {
else */ {
return (list
.reduce(function (result, element, index) {
var value = target_function(element);
@ -14466,8 +14476,7 @@ var lib_plankton;
}
};
lib_plankton.log.info("plankton.server.client_connected");
socket.on("data", (input_chunk_raw, x2, x3, x4, x5) => {
process.stderr.write(JSON.stringify({ x2, x3, x4, x5 }) + "\n");
socket.on("data", (input_chunk_raw) => {
lib_plankton.log.debug("plankton.server.reading_chunk", {
"chunk_raw": ((input_chunk_raw instanceof Buffer)
?
@ -16231,6 +16240,11 @@ var lib_plankton;
"name": (data["preferred_username"] ?? null),
"label": (data["name"] ?? null),
"email": (data["email"] ?? null),
"groups": (((data["groups"] === undefined) || (data["groups"] === null))
?
null
:
data["groups"].split(",")),
});
}
/**

View file

@ -1,18 +1,40 @@
{
"groups": [
{
"id": 1,
"name": "gaertner",
"label": "Gärtner"
},
{
"id": 2,
"name": "bewohner",
"label": "Bewohner"
}
],
"users": [
{
"id": 1,
"name": "alice",
"groups": [1],
"email_address": "alice@example.org",
"dav_token": "alice_dav",
"dav_token": null,
"password": "alice"
},
{
"id": 2,
"name": "bob",
"groups": [2],
"email_address": "bob@example.org",
"dav_token": null,
"password": "bob"
},
{
"id": 3,
"name": "charlie",
"groups": [1, 2],
"email_address": "charlie@example.org",
"dav_token": "charlie_dav",
"password": "charlie"
}
],
"calendars": [
@ -22,8 +44,14 @@
"hue": 0.0000,
"access": {
"public": true,
"default_level": "edit",
"attributed": [
"default_level": "view",
"attributed_group": [
],
"attributed_user": [
{
"user_id": 3,
"level": "admin"
}
]
},
"resource": {
@ -86,14 +114,16 @@
"access": {
"public": false,
"default_level": "none",
"attributed": [
"attributed_group": [
{
"group_id": 1,
"level": "view"
}
],
"attributed_user": [
{
"user_id": 1,
"level": "admin"
},
{
"user_id": 2,
"level": "view"
}
]
},
@ -204,11 +234,13 @@
"access": {
"public": false,
"default_level": "none",
"attributed": [
"attributed_group": [
{
"user_id": 1,
"level": "none"
},
"group_id": 2,
"level": "view"
}
],
"attributed_user": [
{
"user_id": 2,
"level": "admin"

View file

@ -33,7 +33,13 @@ namespace _zeitbild.api
access : {
public : boolean;
default_level : string;
attributed : Array<
attributed_group : Array<
{
group_id : int;
level : string;
}
>;
attributed_user : Array<
{
user_id : int;
level : string;
@ -112,17 +118,33 @@ namespace _zeitbild.api
"name": stuff.input.name,
"access": {
"public": stuff.input.access.public,
"default_level": _zeitbild.value_object.access_level.from_string(stuff.input.access.default_level),
"attributed": lib_plankton.map.hashmap.implementation_map(
"default_level": _zeitbild.access_level_from_string(stuff.input.access.default_level),
"attributed_group": lib_plankton.map.hashmap.implementation_map(
lib_plankton.map.hashmap.make(
x => x.toFixed(0),
{
"pairs": (
stuff.input.access.attributed
stuff.input.access.attributed_group
.map(
(entry) => ({
"key": entry.group_id,
"value": _zeitbild.access_level_from_string(entry.level),
})
)
),
}
)
),
"attributed_user": lib_plankton.map.hashmap.implementation_map(
lib_plankton.map.hashmap.make(
x => x.toFixed(0),
{
"pairs": (
stuff.input.access.attributed_user
.map(
(entry) => ({
"key": entry.user_id,
"value": _zeitbild.value_object.access_level.from_string(entry.level),
"value": _zeitbild.access_level_from_string(entry.level),
})
)
.concat(

View file

@ -34,7 +34,13 @@ namespace _zeitbild.api
access : {
public : boolean;
default_level : string;
attributed : Array<
attributed_group : Array<
{
group_id : int;
level : string;
}
>;
attributed_user : Array<
{
user_id : int;
level : string;
@ -77,17 +83,33 @@ namespace _zeitbild.api
"hue": stuff.input.hue,
"access": {
"public": stuff.input.access.public,
"default_level": _zeitbild.value_object.access_level.from_string(stuff.input.access.default_level),
"attributed": lib_plankton.map.hashmap.implementation_map(
"default_level": _zeitbild.access_level_from_string(stuff.input.access.default_level),
"attributed_group": lib_plankton.map.hashmap.implementation_map(
lib_plankton.map.hashmap.make(
x => x.toFixed(0),
{
"pairs": (
stuff.input.access.attributed
stuff.input.access.attributed_group
.map(
(entry) => ({
"key": entry.group_id,
"value": _zeitbild.access_level_from_string(entry.level),
})
)
),
}
)
),
"attributed_user": lib_plankton.map.hashmap.implementation_map(
lib_plankton.map.hashmap.make(
x => x.toFixed(0),
{
"pairs": (
stuff.input.access.attributed_user
.map(
(entry) => ({
"key": entry.user_id,
"value": _zeitbild.value_object.access_level.from_string(entry.level),
"value": _zeitbild.access_level_from_string(entry.level),
})
)
),

View file

@ -35,12 +35,18 @@ namespace _zeitbild.api
access : {
public : boolean;
default_level : string;
attributed : Array<
attributed_group : Array<
{
group_id : int;
level : string;
}
>;
attributed_user : Array<
{
user_id : int;
level : string;
}
>
>;
};
resource_id : int;
}
@ -69,8 +75,23 @@ namespace _zeitbild.api
"access": {
"public": calendar_object.access.public,
"default_level": _zeitbild.api.access_level_encode(calendar_object.access.default_level),
"attributed": lib_plankton.call.convey(
calendar_object.access.attributed,
"attributed_group": lib_plankton.call.convey(
calendar_object.access.attributed_group,
[
lib_plankton.map.dump,
(pairs : Array<{key : _zeitbild.type_group_id; value : _zeitbild.enum_access_level;}>) => (
pairs
.map(
(pair : {key : _zeitbild.type_group_id; value : _zeitbild.enum_access_level;}) => ({
"group_id": pair.key,
"level": _zeitbild.api.access_level_encode(pair.value)
})
)
)
]
),
"attributed_user": lib_plankton.call.convey(
calendar_object.access.attributed_user,
[
lib_plankton.map.dump,
(pairs : Array<{key : _zeitbild.type_user_id; value : _zeitbild.enum_access_level;}>) => (

View file

@ -101,7 +101,7 @@ namespace _zeitbild.api
"id": entry.id,
"name": entry.name,
"hue": entry.hue,
"access_level": _zeitbild.value_object.access_level.to_string(entry.access_level),
"access_level": _zeitbild.access_level_to_string(entry.access_level),
})
)
)

View file

@ -0,0 +1,110 @@
/*
This file is part of »zeitbild«.
Copyright 2025 'kcf' <fenris@folksprak.org>
»zeitbild« is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
»zeitbild« 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with »zeitbild«. If not, see <http://www.gnu.org/licenses/>.
*/
namespace _zeitbild.api
{
/**
*/
export function register_group_list(
rest_subject : lib_plankton.rest_http.type_rest
) : void
{
register<
null,
Array<
{
id : int;
name : string;
label : string;
}
>
>(
rest_subject,
lib_plankton.http.enum_method.get,
"/groups",
{
"description": "listet alle Gruppen auf",
"query_parameters": () => ([
{
"name": "term",
"required": false,
"description": "search term",
},
]),
"output_schema": () => ({
"type": "array",
"items": {
"nullable": false,
"type": "object",
"additionalProperties": false,
"properties": {
"id": {
"nullable": false,
"type": "number",
},
"name": {
"nullable": false,
"type": "string",
},
"label": {
"nullable": false,
"type": "string",
},
},
"required": [
"id",
"name",
"label",
],
}
}),
"restriction": restriction_logged_in,
"execution": async (stuff) => {
const result : Array<
{
id : _zeitbild.type_group_id;
name : string;
label : string;
}
> = (
(await _zeitbild.service.group.list())
.map(
entry => (
{
"id": entry.id,
"name": entry.object.name,
"label": entry.object.label,
}
)
)
);
return Promise.resolve(
{
"status_code": 200,
"data": result,
}
);
}
}
);
}
}

View file

@ -21,6 +21,38 @@ along with »zeitbild«. If not, see <http://www.gnu.org/licenses/>.
namespace _zeitbild.api
{
/**
*/
function get_group_name(
group_name_raw : string
)
: string
{
return lib_plankton.string.coin(
"auto-{{name_raw}}",
{
"name_raw": group_name_raw,
}
);
}
/**
*/
function get_group_label(
group_name_raw : string
)
: string
{
return lib_plankton.string.coin(
"{{name_raw}}",
{
"name_raw": group_name_raw,
}
);
}
/**
*/
export function register_session_oidc(
@ -74,6 +106,7 @@ namespace _zeitbild.api
userinfo : {
name : (null | string);
email : (null | string);
groups : (null | Array<string>);
};
redirect_uri_template : string;
} = await _zeitbild.auth.oidc_handle_authorization_callback(
@ -92,9 +125,50 @@ namespace _zeitbild.api
{
try
{
// groups
const group_ids : Array<_zeitbild.type_group_id> = await Promise.all<_zeitbild.type_group_id>(
(data.userinfo.groups ?? [])
.map(
async (group_name_raw) => {
const group_name : string = get_group_name(group_name_raw);
let group_id : (null | _zeitbild.type_group_id) = await (() => {
try
{
return _zeitbild.repository.group.identify(group_name);
}
catch (error)
{
return Promise.resolve(null);
}
}) ();
if (group_id === null)
{
group_id = await _zeitbild.service.group.add(
{
"name": group_name,
"label": get_group_label(group_name_raw),
}
);
return group_id;
}
else
{
await _zeitbild.service.group.change(
group_id,
{
"name": group_name,
"label": get_group_label(group_name_raw),
}
);
return group_id;
}
}
)
);
await _zeitbild.service.user.add(
{
"name": data.userinfo.name,
"groups": group_ids,
"email_address": data.userinfo.email,
"dav_token": null,
}

View file

@ -51,6 +51,10 @@ namespace _zeitbild.api
_zeitbild.api.register_session_oidc(rest_subject);
_zeitbild.api.register_session_status(rest_subject);
}
// groups
{
_zeitbild.api.register_group_list(rest_subject);
}
// user
{
_zeitbild.api.register_users(rest_subject);

View file

@ -190,6 +190,7 @@ namespace _zeitbild.auth
userinfo : {
name : (null | string);
email : (null | string);
groups : (null | Array<string>);
};
redirect_uri_template : string;
}
@ -207,6 +208,7 @@ namespace _zeitbild.auth
userinfo : {
name : (null | string);
email : (null | string);
groups : (null | Array<string>);
};
} = await lib_plankton.auth.oidc.handle_authorization_callback(
_subject_oidc,
@ -219,6 +221,7 @@ namespace _zeitbild.auth
userinfo : {
name : (null | string);
email : (null | string);
groups : (null | Array<string>);
};
redirect_uri_template : string;
}

View file

@ -24,7 +24,7 @@ namespace _zeitbild.database
/**
*/
const _compatible_revisions : Array<string> = [
"r5",
"r6",
];

199
source/logic.ts Normal file
View file

@ -0,0 +1,199 @@
/*
This file is part of »zeitbild«.
Copyright 2025 'kcf' <fenris@folksprak.org>
»zeitbild« is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
»zeitbild« 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with »zeitbild«. If not, see <http://www.gnu.org/licenses/>.
*/
/**
*/
namespace _zeitbild
{
/**
*/
export function access_level_to_string(
access_level : _zeitbild.enum_access_level
)
: string
{
switch (access_level)
{
case _zeitbild.enum_access_level.none: {return "none";}
case _zeitbild.enum_access_level.view: {return "view";}
case _zeitbild.enum_access_level.edit: {return "edit";}
case _zeitbild.enum_access_level.admin: {return "admin";}
default: {throw (new Error("invalid access level: " + String(access_level)));}
}
}
/**
*/
export function access_level_from_string(
representation : string
)
: _zeitbild.enum_access_level
{
switch (representation)
{
case "none": {return _zeitbild.enum_access_level.none;}
case "view": {return _zeitbild.enum_access_level.view;}
case "edit": {return _zeitbild.enum_access_level.edit;}
case "admin": {return _zeitbild.enum_access_level.admin;}
default: {throw (new Error("invalid encoded access level: " + String(representation)));}
}
}
/**
*/
export function access_level_order(
x : _zeitbild.enum_access_level,
y : _zeitbild.enum_access_level
)
: boolean
{
const list : Array<_zeitbild.enum_access_level> = [
_zeitbild.enum_access_level.none,
_zeitbild.enum_access_level.view,
_zeitbild.enum_access_level.edit,
_zeitbild.enum_access_level.admin,
];
return (list.indexOf(x) <= list.indexOf(y));
}
/**
*/
export function access_level_determine_raw(
public_ : boolean,
access_level_attributed : (
null
|
{
default : _zeitbild.enum_access_level,
group : Array<_zeitbild.enum_access_level>;
user : (null | _zeitbild.enum_access_level);
}
)
)
: _zeitbild.enum_access_level
{
return lib_plankton.call.convey(
_zeitbild.enum_access_level.none,
[
// if public
(x : _zeitbild.enum_access_level) => (
public_
?
_zeitbild.enum_access_level.view
:
x
),
// if logged in
(x : _zeitbild.enum_access_level) => (
(access_level_attributed === null)
?
x
:
lib_plankton.call.convey(
x,
[
// default
(y : _zeitbild.enum_access_level) => access_level_attributed.default,
// group
(y : _zeitbild.enum_access_level) => (
lib_plankton.call.null_prop(
lib_plankton.list.max<_zeitbild.enum_access_level, _zeitbild.enum_access_level>(
access_level_attributed.group,
z => z,
{
"compare_value": _zeitbild.access_level_order,
}
),
z => z.value
)
??
y
),
// user
(y : _zeitbild.enum_access_level) => (
(access_level_attributed.user === null)
?
y
:
access_level_attributed.user
),
]
)
),
]
);
}
/**
*/
export function access_level_determine(
calendar_object : _zeitbild.type_calendar_object,
user : (
null
|
{
id : _zeitbild.type_user_id;
object : _zeitbild.type_user_object;
}
)
)
: _zeitbild.enum_access_level
{
return access_level_determine_raw(
calendar_object.access.public,
(
(user === null)
?
null
:
{
"default": calendar_object.access.default_level,
"group": (
user.object.groups
.map<(null | _zeitbild.enum_access_level)>(
group_id => (
calendar_object.access.attributed_group.has(group_id)
?
calendar_object.access.attributed_group.get(group_id)
:
null
)
)
.filter(
x => (x !== null)
)
),
"user": (
lib_plankton.call.try_catch_wrap<_zeitbild.enum_access_level>(
() => calendar_object.access.attributed_user.get(user.id)
).value
)
}
)
);
}
}

View file

@ -34,7 +34,16 @@ namespace _zeitbild.repository.calendar
/**
*/
type type_access_attributed_row = {
type type_access_attributed_group_row = {
// calendar_id : int;
group_id : int;
level : int;
};
/**
*/
type type_access_attributed_user_row = {
// calendar_id : int;
user_id : int;
level : int;
@ -45,7 +54,8 @@ namespace _zeitbild.repository.calendar
*/
type type_dispersal = {
core_row : type_core_row;
access_attributed_rows : Array<type_access_attributed_row>;
access_attributed_group_rows : Array<type_access_attributed_group_row>;
access_attributed_user_rows : Array<type_access_attributed_user_row>;
};
@ -71,7 +81,22 @@ namespace _zeitbild.repository.calendar
/**
*/
var _access_attributed_chest : (
var _access_attributed_group_chest : (
null
|
lib_plankton.storage.type_chest<
Array<any>,
Record<string, any>,
lib_plankton.database.type_description_create_table,
lib_plankton.storage.sql_table_common.type_sql_table_common_search_term,
Record<string, any>
>
) = null;
/**
*/
var _access_attributed_user_chest : (
null
|
lib_plankton.storage.type_chest<
@ -87,7 +112,8 @@ namespace _zeitbild.repository.calendar
/**
*/
function get_core_store(
) : lib_plankton.storage.type_store<
)
: lib_plankton.storage.type_store<
_zeitbild.type_calendar_id,
Record<string, any>,
{},
@ -95,7 +121,8 @@ namespace _zeitbild.repository.calendar
Record<string, any>
>
{
if (_core_store === null) {
if (_core_store === null)
{
_core_store = lib_plankton.storage.sql_table_autokey_store(
{
"database_implementation": _zeitbild.database.get_implementation(),
@ -104,7 +131,8 @@ namespace _zeitbild.repository.calendar
}
);
}
else {
else
{
// do nothing
}
return _core_store;
@ -113,7 +141,7 @@ namespace _zeitbild.repository.calendar
/**
*/
function get_access_attributed_chest(
function get_access_attributed_group_chest(
) : lib_plankton.storage.type_chest<
Array<any>,
Record<string, any>,
@ -122,19 +150,51 @@ namespace _zeitbild.repository.calendar
Record<string, any>
>
{
if (_access_attributed_chest === null) {
_access_attributed_chest = lib_plankton.storage.sql_table_common.chest(
if (_access_attributed_group_chest === null)
{
_access_attributed_group_chest = lib_plankton.storage.sql_table_common.chest(
{
"database_implementation": _zeitbild.database.get_implementation(),
"table_name": "calendar_access_attributed",
"table_name": "calendar_access_attributed_group",
"key_names": ["calendar_id","group_id"],
}
);
}
else
{
// do nothing
}
return _access_attributed_group_chest;
}
/**
*/
function get_access_attributed_user_chest(
)
: lib_plankton.storage.type_chest<
Array<any>,
Record<string, any>,
lib_plankton.database.type_description_create_table,
lib_plankton.storage.sql_table_common.type_sql_table_common_search_term,
Record<string, any>
>
{
if (_access_attributed_user_chest === null)
{
_access_attributed_user_chest = lib_plankton.storage.sql_table_common.chest(
{
"database_implementation": _zeitbild.database.get_implementation(),
"table_name": "calendar_access_attributed_user",
"key_names": ["calendar_id","user_id"],
}
);
}
else {
else
{
// do nothing
}
return _access_attributed_chest;
return _access_attributed_user_chest;
}
@ -176,7 +236,8 @@ namespace _zeitbild.repository.calendar
*/
function encode(
object : _zeitbild.type_calendar_object
) : type_dispersal
)
: type_dispersal
{
return {
"core_row": {
@ -186,8 +247,18 @@ namespace _zeitbild.repository.calendar
"access_level_default": encode_access_level(object.access.default_level),
"resource_id": object.resource_id,
},
"access_attributed_rows": (
lib_plankton.map.dump(object.access.attributed)
"access_attributed_group_rows": (
lib_plankton.map.dump(object.access.attributed_group)
.map(
({"key": group_id, "value": level}) => ({
// "calendar_id": calendar_id,
"group_id": group_id,
"level": encode_access_level(level),
})
)
),
"access_attributed_user_rows": (
lib_plankton.map.dump(object.access.attributed_user)
.map(
({"key": user_id, "value": level}) => ({
// "calendar_id": calendar_id,
@ -204,7 +275,8 @@ namespace _zeitbild.repository.calendar
*/
function decode(
dispersal : type_dispersal
) : _zeitbild.type_calendar_object
)
: _zeitbild.type_calendar_object
{
return {
"name": dispersal.core_row.name,
@ -212,19 +284,38 @@ namespace _zeitbild.repository.calendar
"access": {
"public": dispersal.core_row.access_public,
"default_level": decode_access_level(dispersal.core_row.access_level_default),
"attributed": lib_plankton.map.hashmap.implementation_map(
"attributed_group": lib_plankton.map.hashmap.implementation_map(
lib_plankton.map.hashmap.make<_zeitbild.type_group_id, _zeitbild.enum_access_level>(
x => x.toFixed(0),
{
"pairs": (
dispersal.access_attributed_group_rows
.map(
(access_attributed_group_row) => ({
// "calendar_id": access_attributed_group_row["calendar_id"],
// "key": access_attributed_group_row["preview"]["user_id"],
"key": access_attributed_group_row.group_id,
// "value": decode_access_level(access_attributed_group_row["preview"]["level"]),
"value": decode_access_level(access_attributed_group_row.level),
})
)
),
}
)
),
"attributed_user": lib_plankton.map.hashmap.implementation_map(
lib_plankton.map.hashmap.make<_zeitbild.type_user_id, _zeitbild.enum_access_level>(
x => x.toFixed(0),
{
"pairs": (
dispersal.access_attributed_rows
dispersal.access_attributed_user_rows
.map(
(access_attributed_row) => ({
// "calendar_id": access_attributed_row["calendar_id"],
// "key": access_attributed_row["preview"]["user_id"],
"key": access_attributed_row.user_id,
// "value": decode_access_level(access_attributed_row["preview"]["level"]),
"value": decode_access_level(access_attributed_row.level),
(access_attributed_user_row) => ({
// "calendar_id": access_attributed_user_row["calendar_id"],
// "key": access_attributed_user_row["preview"]["user_id"],
"key": access_attributed_user_row.user_id,
// "value": decode_access_level(access_attributed_user_row["preview"]["level"]),
"value": decode_access_level(access_attributed_user_row.level),
})
)
),
@ -240,17 +331,14 @@ namespace _zeitbild.repository.calendar
/**
*/
export function read(
export async function read(
id : _zeitbild.type_calendar_id
) : Promise<_zeitbild.type_calendar_object>
)
: Promise<_zeitbild.type_calendar_object>
{
return (
get_core_store().read(id)
.then(
(core_row_raw) => {
const core_row : type_core_row = (core_row_raw as type_core_row);
return (
get_access_attributed_chest().search(
const core_row : type_core_row = ((await get_core_store().read(id)) as type_core_row);
const access_attributed_group_rows : Array<type_access_attributed_group_row> = await (
get_access_attributed_group_chest().search(
{
"expression": "(calendar_id = $calendar_id)",
"arguments": {
@ -259,10 +347,31 @@ namespace _zeitbild.repository.calendar
}
)
.then(
(hits) => Promise.resolve<type_dispersal>(
(hits) => Promise.resolve<Array<type_access_attributed_group_row>>(
hits
.map(
hit => (
{
"core_row": core_row,
"access_attributed_rows": (
// "calendar_id": null,
"group_id": hit.preview.group_id,
"level": hit.preview.level,
}
)
)
)
)
);
const access_attributed_user_rows : Array<type_access_attributed_user_row> = await (
get_access_attributed_user_chest().search(
{
"expression": "(calendar_id = $calendar_id)",
"arguments": {
"calendar_id": id,
}
}
)
.then(
(hits) => Promise.resolve<Array<type_access_attributed_user_row>>(
hits
.map(
hit => (
@ -273,19 +382,16 @@ namespace _zeitbild.repository.calendar
}
)
)
),
}
)
)
.then(
(dispersal) => Promise.resolve<_zeitbild.type_calendar_object>(
decode(dispersal)
)
)
);
}
)
);
const dispersal : type_dispersal = {
"core_row": core_row,
"access_attributed_group_rows": access_attributed_group_rows,
"access_attributed_user_rows": access_attributed_user_rows,
};
const calendar_object : _zeitbild.type_calendar_object = decode(dispersal);
return calendar_object;
}
@ -300,10 +406,16 @@ namespace _zeitbild.repository.calendar
const calendar_id : _zeitbild.type_calendar_id = await core_store.create(
dispersal.core_row
);
for await (const access_attributed_row of dispersal.access_attributed_rows) {
get_access_attributed_chest().write(
[calendar_id, access_attributed_row["user_id"]],
{"level": access_attributed_row["level"]}
for await (const access_attributed_group_row of dispersal.access_attributed_group_rows) {
get_access_attributed_group_chest().write(
[calendar_id, access_attributed_group_row["group_id"]],
{"level": access_attributed_group_row["level"]}
);
}
for await (const access_attributed_user_row of dispersal.access_attributed_user_rows) {
get_access_attributed_user_chest().write(
[calendar_id, access_attributed_user_row["user_id"]],
{"level": access_attributed_user_row["level"]}
);
}
await lib_plankton.cache.clear(_zeitbild.cache_regular);
@ -327,10 +439,51 @@ namespace _zeitbild.repository.calendar
dispersal.core_row
);
}
// attributed access
// attributed:group
{
const access_attributed_chest = get_access_attributed_chest();
const hits : Array<Record<string, any>> = await access_attributed_chest.search(
const access_attributed_group_chest = get_access_attributed_group_chest();
const hits : Array<Record<string, any>> = await access_attributed_group_chest.search(
{
"expression": "(calendar_id = $calendar_id)",
"arguments": {
"calendar_id": calendar_id,
}
}
);
const contrast = lib_plankton.list.contrast<
Record<string, any>,
Record<string, any>
>(
hits,
hit => hit["group_id"],
dispersal.access_attributed_group_rows,
row => row["group_id"]
);
// delete
for await (const entry of contrast.only_left) {
await access_attributed_group_chest.delete(
[calendar_id, entry.left["group_id"]]
);
}
// update
for await (const entry of contrast.both) {
await access_attributed_group_chest.write(
[calendar_id, entry.right["group_id"]],
{"level": entry.right["level"]}
);
}
// create
for await (const entry of contrast.only_right) {
await access_attributed_group_chest.write(
[calendar_id, entry.right["group_id"]],
{"level": entry.right["level"]}
);
}
}
// attributed:user
{
const access_attributed_user_chest = get_access_attributed_user_chest();
const hits : Array<Record<string, any>> = await access_attributed_user_chest.search(
{
"expression": "(calendar_id = $calendar_id)",
"arguments": {
@ -344,25 +497,25 @@ namespace _zeitbild.repository.calendar
>(
hits,
hit => hit["user_id"],
dispersal.access_attributed_rows,
dispersal.access_attributed_user_rows,
row => row["user_id"]
);
// delete
for await (const entry of contrast.only_left) {
await access_attributed_chest.delete(
await access_attributed_user_chest.delete(
[calendar_id, entry.left["user_id"]]
);
}
// update
for await (const entry of contrast.both) {
await access_attributed_chest.write(
await access_attributed_user_chest.write(
[calendar_id, entry.right["user_id"]],
{"level": entry.right["level"]}
);
}
// create
for await (const entry of contrast.only_right) {
await access_attributed_chest.write(
await access_attributed_user_chest.write(
[calendar_id, entry.right["user_id"]],
{"level": entry.right["level"]}
);
@ -384,15 +537,16 @@ namespace _zeitbild.repository.calendar
{
await lib_plankton.cache.clear(_zeitbild.cache_regular);
const core_store = get_core_store();
const access_attributed_chest = get_access_attributed_chest();
// attributed access
const access_attributed_user_chest = get_access_attributed_user_chest();
// attributed:user
{
const chest = get_access_attributed_user_chest();
const hits : Array<
{
key : Array<any>;
preview : Record<string, any>;
}
> = await access_attributed_chest.search(
> = await chest.search(
{
"expression": "(calendar_id = $calendar_id)",
"arguments": {
@ -402,9 +556,28 @@ namespace _zeitbild.repository.calendar
);
for (const hit of hits)
{
await access_attributed_chest.delete(
hit.key
await chest.delete(hit.key);
}
}
// attributed:group
{
const chest = get_access_attributed_group_chest();
const hits : Array<
{
key : Array<any>;
preview : Record<string, any>;
}
> = await chest.search(
{
"expression": "(calendar_id = $calendar_id)",
"arguments": {
"calendar_id": calendar_id,
}
}
);
for (const hit of hits)
{
await chest.delete(hit.key);
}
}
// core
@ -432,7 +605,8 @@ namespace _zeitbild.repository.calendar
*/
export async function overview(
user_id : (null | _zeitbild.type_user_id)
) : Promise<
)
: Promise<
Array<
type_overview_entry
>
@ -468,25 +642,37 @@ namespace _zeitbild.repository.calendar
"name": row["name"],
"hue": (row["hue"] / hue_scaling),
/**
* @todo unite with _zeitbild.service.calendar.get_access_level
* @todo use _zeitbild.access_level_determine
*/
"access_level": decode_access_level(
Math.max(
(row["access_public"] ? 1 : 0),
"access_level": _zeitbild.access_level_determine_raw(
row["access_public"],
(
(user_id === null)
?
0
null
:
(row["access_level_attributed"] ?? row["access_level_default"])
{
"default": decode_access_level(row["access_level_default"]),
"group": (
lib_plankton.call.null_prop<string, Array<_zeitbild.enum_access_level>>(
row["access_level_attributed_group"],
x => x.split(",").map(parseInt).map(decode_access_level)
)
??
[]
),
"user": lib_plankton.call.null_prop<int, _zeitbild.enum_access_level>(
row["access_level_attributed_user"],
decode_access_level
),
}
)
),
})
),
(x : Array<type_overview_entry>) => x.filter(
(row) => (
! _zeitbild.value_object.access_level.order(
! _zeitbild.access_level_order(
row.access_level,
_zeitbild.enum_access_level.none
)
@ -499,7 +685,7 @@ namespace _zeitbild.repository.calendar
row => row.access_level,
row => row.id,
{
"order_first": (a, b) => _zeitbild.value_object.access_level.order(b, a),
"order_first": (a, b) => _zeitbild.access_level_order(b, a),
"order_second": (a, b) => (a <= b)
}
),

View file

@ -0,0 +1,207 @@
/*
This file is part of »zeitbild«.
Copyright 2025 'kcf' <fenris@folksprak.org>
»zeitbild« is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
»zeitbild« 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with »zeitbild«. If not, see <http://www.gnu.org/licenses/>.
*/
namespace _zeitbild.repository.group
{
/**
*/
type type_row = {
name : string;
label : string;
};
/**
*/
type type_preview = {
name : string;
label : string;
};
/**
*/
var _store : (
null
|
lib_plankton.storage.type_store<
_zeitbild.type_user_id,
/*type_row*/Record<string, any>,
{},
lib_plankton.storage.type_sql_table_autokey_search_term,
/*type_preview*/Record<string, any>
>
) = null;
/**
*/
function get_store(
)
: lib_plankton.storage.type_store<
_zeitbild.type_user_id,
/*type_row*/Record<string, any>,
{},
lib_plankton.storage.type_sql_table_autokey_search_term,
/*type_preview*/Record<string, any>
>
{
if (_store === null)
{
_store = lib_plankton.storage.sql_table_autokey_store(
{
"database_implementation": _zeitbild.database.get_implementation(),
"table_name": "groups",
"key_name": "id",
}
);
}
else
{
// do nothing
}
return _store;
}
/**
*/
function encode(
group_object : _zeitbild.type_group_object
)
: type_row
{
return {
"name": group_object.name,
"label": group_object.label,
};
}
/**
*/
function decode(
row : type_row
)
: _zeitbild.type_group_object
{
return {
"name": row.name,
"label": row.label,
};
}
/**
*/
export async function list(
)
: Promise<
Array<
{
id : _zeitbild.type_group_id;
object : _zeitbild.type_group_object;
}
>
>
{
const hits : Array<{key : int; preview : /*type_preview*/Record<string, any>;}> = await get_store().search({"expression": "TRUE", "arguments": {}});
return Promise.resolve(
hits
.map(
(hit) => ({
"id": hit.key,
"object": {
"name": hit.preview.name,
"label": hit.preview.label,
}
})
)
);
}
/**
*/
export async function read(
group_id : _zeitbild.type_group_id
)
: Promise<_zeitbild.type_group_object>
{
const row : type_row = ((await get_store().read(group_id)) as type_row);
const group_object : _zeitbild.type_group_object = decode(row);
return Promise.resolve<_zeitbild.type_group_object>(group_object);
}
/**
*/
export async function create(
group_object : _zeitbild.type_group_object
)
: Promise<_zeitbild.type_group_id>
{
const row : type_row = encode(group_object);
const group_id : _zeitbild.type_group_id = await get_store().create(row);
return Promise.resolve<_zeitbild.type_group_id>(group_id);
}
/**
*/
export async function update(
group_id : _zeitbild.type_group_id,
group_object : _zeitbild.type_group_object
)
: Promise<void>
{
const row : type_row = encode(group_object);
await get_store().update(group_id, row);
return Promise.resolve<void>(undefined);
}
/**
*/
export async function identify(
name : string
)
: Promise<_zeitbild.type_group_id>
{
const hits : Array<{key : _zeitbild.type_group_id; preview : /*type_preview*/Record<string, any>;}> = await get_store().search(
{
"expression": "(name = $name)",
"arguments": {
"name": name,
}
}
);
if (hits.length <= 0)
{
return Promise.reject<_zeitbild.type_group_id>(new Error("not found"));
}
else
{
return Promise.resolve<_zeitbild.type_group_id>(hits[0].key);
}
}
}

View file

@ -1,15 +1,15 @@
-- Für gewöhnlich würde man hier gruppieren. Aufgrund des UNIQUE-constraints in "calendar_access_attributed" ist das
-- jedoch nicht nötig, da dadurch für jeden Eintrag in "calendar" mit gegebener "user_id" höchstens ein Eintrag in
-- "calendar_access_attributed" passt und da es ein LEFT OUTER JOIN ist, wird es _genau_ ein Eintrag sein
SELECT
x.id AS id,
x.name AS name,
x.hue AS hue,
x.access_public AS access_public,
x.access_level_default AS access_level_default,
y.level AS access_level_attributed
MAX(x.name) AS name,
MAX(x.hue) AS hue,
MAX(x.access_public) AS access_public,
MAX(x.access_level_default) AS access_level_default,
GROUP_CONCAT(y1.level, ',') AS access_level_attributed_group,
GROUP_CONCAT(y2.level, ',') AS access_level_attributed_user
FROM
calendars AS x
LEFT OUTER JOIN calendar_access_attributed AS y ON ((x.id = y.calendar_id) AND (y.user_id = $user_id))
LEFT OUTER JOIN calendar_access_attributed_group AS y1 ON ((x.id = y1.calendar_id) AND (y1.group_id IN (SELECT group_id FROM user_groups WHERE (user_id = $user_id))))
LEFT OUTER JOIN calendar_access_attributed_user AS y2 ON ((x.id = y2.calendar_id) AND (y2.user_id = $user_id))
GROUP BY
x.id
;

View file

@ -23,13 +23,28 @@ namespace _zeitbild.repository.user
/**
*/
type type_row = {
type type_core_row = {
name : string;
email_address : (null | string);
dav_token : (null | string);
};
/**
*/
type type_group_row_slim = {
group_id : int;
};
/**
*/
type type_group_row_fat = {
user_id : int;
group_id : int;
};
/**
*/
type type_preview = {
@ -39,12 +54,20 @@ namespace _zeitbild.repository.user
/**
*/
var _store : (
type type_dispersal = {
core : type_core_row;
groups : Array<type_group_row_slim>;
};
/**
*/
var _store_core : (
null
|
lib_plankton.storage.type_store<
_zeitbild.type_user_id,
/*type_row*/Record<string, any>,
/*type_core_row*/Record<string, any>,
{},
lib_plankton.storage.type_sql_table_autokey_search_term,
/*type_preview*/Record<string, any>
@ -54,19 +77,34 @@ namespace _zeitbild.repository.user
/**
*/
function get_store(
var _store_groups : (
null
|
lib_plankton.storage.type_store<
int,
/*type_group_row_fat*/Record<string, any>,
{},
lib_plankton.storage.type_sql_table_autokey_search_term,
Record<string, any>
>
) = null;
/**
*/
function get_store_core(
)
: lib_plankton.storage.type_store<
_zeitbild.type_user_id,
/*type_row*/Record<string, any>,
/*type_core_row*/Record<string, any>,
{},
lib_plankton.storage.type_sql_table_autokey_search_term,
/*type_preview*/Record<string, any>
>
{
if (_store === null)
if (_store_core === null)
{
_store = lib_plankton.storage.sql_table_autokey_store(
_store_core = lib_plankton.storage.sql_table_autokey_store(
{
"database_implementation": _zeitbild.database.get_implementation(),
"table_name": "users",
@ -78,7 +116,37 @@ namespace _zeitbild.repository.user
{
// do nothing
}
return _store;
return _store_core;
}
/**
*/
function get_store_groups(
)
: lib_plankton.storage.type_store<
int,
/*type_group_row_fat*/Record<string, any>,
{},
lib_plankton.storage.type_sql_table_autokey_search_term,
Record<string, any>
>
{
if (_store_groups === null)
{
_store_groups = lib_plankton.storage.sql_table_autokey_store(
{
"database_implementation": _zeitbild.database.get_implementation(),
"table_name": "user_groups",
"key_name": "id",
}
);
}
else
{
// do nothing
}
return _store_groups;
}
@ -87,12 +155,23 @@ namespace _zeitbild.repository.user
function encode(
user_object : _zeitbild.type_user_object
)
: type_row
: type_dispersal
{
return {
"core": {
"name": user_object.name,
"email_address": user_object.email_address,
"dav_token": user_object.dav_token,
},
"groups": (
user_object.groups.map(
group_id => (
{
"group_id": group_id,
}
)
)
),
};
}
@ -100,14 +179,19 @@ namespace _zeitbild.repository.user
/**
*/
function decode(
row : type_row
dispersal : type_dispersal
)
: _zeitbild.type_user_object
{
return {
"name": row.name,
"email_address": row.email_address,
"dav_token": row.dav_token,
"name": dispersal.core.name,
"groups": (
dispersal.groups.map(
group_row => group_row.group_id,
)
),
"email_address": dispersal.core.email_address,
"dav_token": dispersal.core.dav_token,
};
}
@ -125,7 +209,12 @@ namespace _zeitbild.repository.user
>
>
{
const hits : Array<{key : int; preview : /*type_preview*/Record<string, any>;}> = await get_store().search({"expression": "TRUE", "arguments": {}});
const hits : Array<{key : int; preview : /*type_preview*/Record<string, any>;}> = await get_store_core().search(
{
"expression": "TRUE",
"arguments": {}
}
);
return Promise.resolve(
hits
.map(
@ -145,8 +234,40 @@ namespace _zeitbild.repository.user
)
: Promise<_zeitbild.type_user_object>
{
const row : type_row = ((await get_store().read(user_id)) as type_row);
const user_object : _zeitbild.type_user_object = decode(row);
const core_row : type_core_row = ((await get_store_core().read(user_id)) as type_core_row);
const group_rows : Array<type_group_row_fat> = (
(
await get_store_groups().search(
{
"expression": "(user_id = $user_id)",
"arguments": {
"user_id": user_id,
}
}
)
)
.map(
hit => (
{
"user_id": hit.preview.user_id,
"group_id": hit.preview.group_id,
}
)
)
);
const dispersal : type_dispersal = {
"core": core_row,
"groups": (
group_rows.map(
group_row_fat => (
{
"group_id": group_row_fat.group_id,
}
)
)
)
};
const user_object : _zeitbild.type_user_object = decode(dispersal);
return Promise.resolve<_zeitbild.type_user_object>(user_object);
}
@ -158,8 +279,22 @@ namespace _zeitbild.repository.user
)
: Promise<_zeitbild.type_user_id>
{
const row : type_row = encode(user_object);
const user_id : _zeitbild.type_user_id = await get_store().create(row);
const dispersal : type_dispersal = encode(user_object);
// core
const user_id : _zeitbild.type_user_id = await (() => {
return get_store_core().create(dispersal.core);
}) ();
// groups
{
for (const group_row_slim of dispersal.groups)
{
const group_row_fat : type_group_row_fat = {
"user_id": user_id,
"group_id": group_row_slim.group_id,
};
await get_store_groups().create(group_row_fat);
}
}
return Promise.resolve<_zeitbild.type_user_id>(user_id);
}
@ -172,8 +307,57 @@ namespace _zeitbild.repository.user
)
: Promise<void>
{
const row : type_row = encode(user_object);
await get_store().update(user_id, row);
const dispersal : type_dispersal = encode(user_object);
// core
{
await get_store_core().update(user_id, dispersal.core);
}
// groups
{
const hits : Array<{key : int; preview : Record<string, any>;}> = await get_store_groups().search(
{
"expression": "(user_id = $user_id)",
"arguments": {
"user_id": user_id,
}
}
);
const contrast = lib_plankton.list.contrast(
hits,
hit => hit.preview.group_id.toFixed(0),
dispersal.groups,
group_row_slim => group_row_slim.group_id.toFixed(0)
);
// delete
{
for (const entry of contrast.only_left)
{
await get_store_groups().delete(entry.left.key);
}
}
// update
{
for (const entry of contrast.both)
{
const row_group_fat : type_group_row_fat = {
"user_id": user_id,
"group_id": entry.right.group_id,
};
await get_store_groups().update(entry.left.key, row_group_fat);
}
}
// create
{
for (const entry of contrast.only_right)
{
const row_group_fat : type_group_row_fat = {
"user_id": user_id,
"group_id": entry.right.group_id,
};
await get_store_groups().create(row_group_fat);
}
}
}
return Promise.resolve<void>(undefined);
}
@ -185,7 +369,7 @@ namespace _zeitbild.repository.user
)
: Promise<_zeitbild.type_user_id>
{
const hits : Array<{key : _zeitbild.type_user_id; preview : /*type_preview*/Record<string, any>;}> = await get_store().search(
const hits : Array<{key : _zeitbild.type_user_id; preview : /*type_preview*/Record<string, any>;}> = await get_store_core().search(
{
"expression": "(name = $name)",
"arguments": {

View file

@ -91,12 +91,20 @@ namespace _zeitbild.sample
/**
*/
type type_data = {
groups : Array<
{
id : int;
name : string;
label ?: string;
}
>;
users : Array<
{
id : int;
name : string;
groups ?: Array<int>;
email_address : string;
dav_token : (null | string);
dav_token ?: (null | string);
password : string;
}
>;
@ -107,7 +115,13 @@ namespace _zeitbild.sample
access : {
public ?: boolean;
default_level : ("none" | "view" | "edit" | "admin");
attributed : Array<
attributed_group ?: Array<
{
group_id : int;
level : ("none" | "view" | "edit" | "admin");
}
>;
attributed_user ?: Array<
{
user_id : int;
level : ("none" | "view" | "edit" | "admin");
@ -222,6 +236,10 @@ namespace _zeitbild.sample
) : Promise<void>
{
let track : {
group : Record<
int,
_zeitbild.type_group_id
>;
user : Record<
int,
_zeitbild.type_user_id
@ -231,15 +249,33 @@ namespace _zeitbild.sample
_zeitbild.type_user_id
>;
} = {
"group": {},
"user": {},
"calendar": {},
};
// groups
{
for await (const group_raw of data.groups)
{
const group_object : _zeitbild.type_group_object = {
"name": group_raw.name,
"label": (group_raw.label ?? group_raw.name),
};
const group_id : _zeitbild.type_group_id = await _zeitbild.service.group.add(
group_object
);
track.group[group_raw.id] = group_id;
}
}
// users
{
for await (const user_raw of data.users)
{
const user_object : _zeitbild.type_user_object = {
"name": user_raw.name,
"groups": (user_raw.groups ?? []),
"email_address": user_raw.email_address,
"dav_token": user_raw.dav_token,
"dav_token": (user_raw.dav_token ?? null),
};
const user_id : _zeitbild.type_user_id = await _zeitbild.service.user.add(
user_object
@ -250,6 +286,9 @@ namespace _zeitbild.sample
);
track.user[user_raw.id] = user_id;
}
}
// calendars
{
for await (const calendar_raw of data.calendars)
{
let resource_object : _zeitbild.type_resource_object;
@ -316,17 +355,33 @@ namespace _zeitbild.sample
),
"access": {
"public": (calendar_raw.access.public ?? false),
"default_level": _zeitbild.value_object.access_level.from_string(calendar_raw.access.default_level),
"attributed": lib_plankton.map.hashmap.implementation_map(
"default_level": _zeitbild.access_level_from_string(calendar_raw.access.default_level),
"attributed_group": lib_plankton.map.hashmap.implementation_map(
lib_plankton.map.hashmap.make(
x => x.toFixed(0),
{
"pairs": (
calendar_raw.access.attributed
(calendar_raw.access.attributed_group ?? [])
.map(
(entry) => ({
"key": track.user[entry.group_id],
"value": _zeitbild.access_level_from_string(entry.level),
})
)
),
}
)
),
"attributed_user": lib_plankton.map.hashmap.implementation_map(
lib_plankton.map.hashmap.make(
x => x.toFixed(0),
{
"pairs": (
(calendar_raw.access.attributed_user ?? [])
.map(
(entry) => ({
"key": track.user[entry.user_id],
"value": _zeitbild.value_object.access_level.from_string(entry.level),
"value": _zeitbild.access_level_from_string(entry.level),
})
)
),
@ -341,6 +396,7 @@ namespace _zeitbild.sample
);
track.calendar[calendar_raw.id] = calendar_id;
}
}
return Promise.resolve<void>(undefined);
}

View file

@ -23,41 +23,24 @@ namespace _zeitbild.service.calendar
/**
*/
function get_access_level(
async function get_access_level(
calendar_object : _zeitbild.type_calendar_object,
user_id : (null | _zeitbild.type_user_id)
) : _zeitbild.enum_access_level
)
: Promise<_zeitbild.enum_access_level>
{
return (
lib_plankton.list.max<_zeitbild.enum_access_level, _zeitbild.enum_access_level>(
[
(
calendar_object.access.public
?
_zeitbild.enum_access_level.view
:
_zeitbild.enum_access_level.none
),
return _zeitbild.access_level_determine(
calendar_object,
(
(user_id === null)
?
_zeitbild.enum_access_level.none
null
:
calendar_object.access.attributed.get(
user_id,
lib_plankton.pod.make_filled<_zeitbild.enum_access_level>(
calendar_object.access.default_level
)
)
),
],
x => x,
{
"compare_value": _zeitbild.value_object.access_level.order,
"id": user_id,
"object": (await _zeitbild.service.user.get(user_id)),
}
)?.value
??
_zeitbild.enum_access_level.none
)
);
}
@ -65,7 +48,7 @@ namespace _zeitbild.service.calendar
/**
* checks if a user has a sufficient access level
*/
function wrap_check_access_level<type_result>(
async function wrap_check_access_level<type_result>(
calendar_object : _zeitbild.type_calendar_object,
user_id : (null | _zeitbild.type_user_id),
threshold : _zeitbild.enum_access_level,
@ -74,26 +57,29 @@ namespace _zeitbild.service.calendar
=>
Promise<type_result>
)
) : Promise<type_result>
)
: Promise<type_result>
{
const access_level : _zeitbild.enum_access_level = get_access_level(
const access_level : _zeitbild.enum_access_level = await get_access_level(
calendar_object,
user_id
);
if (! _zeitbild.value_object.access_level.order(threshold, access_level)) {
if (! _zeitbild.access_level_order(threshold, access_level))
{
return Promise.reject<type_result>(
new Error(
lib_plankton.string.coin(
"insufficient access level; at least required: {{threshold}}, actual: {{actual}}",
{
"threshold": _zeitbild.value_object.access_level.to_string(threshold),
"actual": _zeitbild.value_object.access_level.to_string(access_level),
"threshold": _zeitbild.access_level_to_string(threshold),
"actual": _zeitbild.access_level_to_string(access_level),
}
)
)
);
}
else {
else
{
return success_handler(access_level);
}
}
@ -579,7 +565,7 @@ namespace _zeitbild.service.calendar
const calendar_object : _zeitbild.type_calendar_object = await _zeitbild.repository.calendar.read(
calendar_id
);
const access_level : _zeitbild.enum_access_level = get_access_level(
const access_level : _zeitbild.enum_access_level = await get_access_level(
calendar_object,
user_id
);

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

@ -0,0 +1,63 @@
/*
This file is part of »zeitbild«.
Copyright 2025 'kcf' <fenris@folksprak.org>
»zeitbild« is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
»zeitbild« 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with »zeitbild«. If not, see <http://www.gnu.org/licenses/>.
*/
namespace _zeitbild.service.group
{
/**
*/
export function list(
)
: Promise<
Array<
{
id : _zeitbild.type_group_id;
object : _zeitbild.type_group_object;
}
>
>
{
return _zeitbild.repository.group.list();
}
/**
*/
export function add(
group_object : _zeitbild.type_group_object
)
: Promise<_zeitbild.type_group_id>
{
return _zeitbild.repository.group.create(group_object);
}
/**
*/
export function change(
group_id : _zeitbild.type_group_id,
group_object : _zeitbild.type_group_object
)
: Promise<void>
{
return _zeitbild.repository.group.update(group_id, group_object);
}
}

View file

@ -33,6 +33,19 @@ namespace _zeitbild
}
/**
*/
export type type_group_id = int;
/**
*/
export type type_group_object = {
name : string;
label : string;
};
/**
*/
export type type_user_id = int;
@ -42,6 +55,9 @@ namespace _zeitbild
*/
export type type_user_object = {
name : string;
groups : Array<
type_group_id
>;
email_address : (
null
|
@ -131,7 +147,11 @@ namespace _zeitbild
access : {
public : boolean;
default_level : enum_access_level;
attributed : lib_plankton.map.type_map<
attributed_group : lib_plankton.map.type_map<
type_group_id,
enum_access_level
>;
attributed_user : lib_plankton.map.type_map<
type_user_id,
enum_access_level
>;

View file

@ -1,74 +0,0 @@
/*
This file is part of »zeitbild«.
Copyright 2025 'kcf' <fenris@folksprak.org>
»zeitbild« is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
»zeitbild« 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with »zeitbild«. If not, see <http://www.gnu.org/licenses/>.
*/
/**
*/
namespace _zeitbild.value_object.access_level
{
/**
*/
export function to_string(
access_level : _zeitbild.enum_access_level
) : string
{
switch (access_level) {
case _zeitbild.enum_access_level.none: {return "none";}
case _zeitbild.enum_access_level.view: {return "view";}
case _zeitbild.enum_access_level.edit: {return "edit";}
case _zeitbild.enum_access_level.admin: {return "admin";}
default: {throw (new Error("invalid access level: " + String(access_level)));}
}
}
/**
*/
export function from_string(
access_level_ : string
) : _zeitbild.enum_access_level
{
switch (access_level_) {
case "none": {return _zeitbild.enum_access_level.none;}
case "view": {return _zeitbild.enum_access_level.view;}
case "edit": {return _zeitbild.enum_access_level.edit;}
case "admin": {return _zeitbild.enum_access_level.admin;}
default: {throw (new Error("invalid encoded access level: " + String(access_level_)));}
}
}
/**
*/
export function order(
x : _zeitbild.enum_access_level,
y : _zeitbild.enum_access_level
) : boolean
{
const list : Array<_zeitbild.enum_access_level> = [
_zeitbild.enum_access_level.none,
_zeitbild.enum_access_level.view,
_zeitbild.enum_access_level.edit,
_zeitbild.enum_access_level.admin,
];
return (list.indexOf(x) <= list.indexOf(y));
}
}

View file

@ -47,12 +47,14 @@ ${dir_temp}/zeitbild-unlinked.js: \
${dir_source}/database.ts \
${dir_source}/auth.ts \
${dir_source}/types.ts \
${dir_source}/value_objects/access_level.ts \
${dir_source}/logic.ts \
${dir_source}/repositories/auth_internal.ts \
${dir_source}/repositories/group.ts \
${dir_source}/repositories/user.ts \
${dir_source}/repositories/resource.ts \
${dir_source}/repositories/calendar.ts \
${dir_source}/services/auth_internal.ts \
${dir_source}/services/group.ts \
${dir_source}/services/user.ts \
${dir_source}/services/resource.ts \
${dir_source}/services/calendar.ts \
@ -65,6 +67,7 @@ ${dir_temp}/zeitbild-unlinked.js: \
${dir_source}/api/actions/session_oidc.ts \
${dir_source}/api/actions/session_end.ts \
${dir_source}/api/actions/session_status.ts \
${dir_source}/api/actions/group_list.ts \
${dir_source}/api/actions/users.ts \
${dir_source}/api/actions/user_dav_conf.ts \
${dir_source}/api/actions/user_dav_token.ts \