762 lines
18 KiB
TypeScript
762 lines
18 KiB
TypeScript
/*
|
|
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.calendar
|
|
{
|
|
|
|
/**
|
|
*/
|
|
type type_core_row = {
|
|
name : string;
|
|
hue : int;
|
|
access_public : boolean;
|
|
access_level_default : int;
|
|
resource_id : int;
|
|
};
|
|
|
|
|
|
/**
|
|
*/
|
|
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;
|
|
};
|
|
|
|
|
|
/**
|
|
*/
|
|
type type_dispersal = {
|
|
core_row : type_core_row;
|
|
access_attributed_group_rows : Array<type_access_attributed_group_row>;
|
|
access_attributed_user_rows : Array<type_access_attributed_user_row>;
|
|
};
|
|
|
|
|
|
/**
|
|
*/
|
|
const hue_scaling : int = 0xFFFF;
|
|
|
|
|
|
/**
|
|
*/
|
|
var _core_store : (
|
|
null
|
|
|
|
|
lib_plankton.storage.type_store<
|
|
_zeitbild.type_calendar_id,
|
|
Record<string, any>,
|
|
{},
|
|
lib_plankton.storage.type_sql_table_autokey_search_term,
|
|
Record<string, any>
|
|
>
|
|
) = null;
|
|
|
|
|
|
/**
|
|
*/
|
|
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<
|
|
Array<any>,
|
|
Record<string, any>,
|
|
lib_plankton.database.type_description_create_table,
|
|
lib_plankton.storage.sql_table_common.type_sql_table_common_search_term,
|
|
Record<string, any>
|
|
>
|
|
) = null;
|
|
|
|
|
|
/**
|
|
*/
|
|
function get_core_store(
|
|
)
|
|
: lib_plankton.storage.type_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_store(
|
|
{
|
|
"database_implementation": _zeitbild.database.get_implementation(),
|
|
"table_name": "calendars",
|
|
"key_name": "id",
|
|
}
|
|
);
|
|
}
|
|
else
|
|
{
|
|
// do nothing
|
|
}
|
|
return _core_store;
|
|
}
|
|
|
|
|
|
/**
|
|
*/
|
|
function get_access_attributed_group_chest(
|
|
) : lib_plankton.storage.type_chest<
|
|
Array<any>,
|
|
Record<string, any>,
|
|
lib_plankton.database.type_description_create_table,
|
|
lib_plankton.storage.sql_table_common.type_sql_table_common_search_term,
|
|
Record<string, any>
|
|
>
|
|
{
|
|
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_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
|
|
{
|
|
// do nothing
|
|
}
|
|
return _access_attributed_user_chest;
|
|
}
|
|
|
|
|
|
/**
|
|
*/
|
|
function encode_access_level(
|
|
access_level : _zeitbild.enum_access_level
|
|
) : int
|
|
{
|
|
return (
|
|
[
|
|
_zeitbild.enum_access_level.none,
|
|
_zeitbild.enum_access_level.view,
|
|
_zeitbild.enum_access_level.edit,
|
|
_zeitbild.enum_access_level.admin,
|
|
].indexOf(access_level)
|
|
);
|
|
}
|
|
|
|
|
|
/**
|
|
*/
|
|
function decode_access_level(
|
|
access_level_encoded : int
|
|
) : _zeitbild.enum_access_level
|
|
{
|
|
return (
|
|
[
|
|
_zeitbild.enum_access_level.none,
|
|
_zeitbild.enum_access_level.view,
|
|
_zeitbild.enum_access_level.edit,
|
|
_zeitbild.enum_access_level.admin,
|
|
][access_level_encoded]
|
|
);
|
|
}
|
|
|
|
|
|
/**
|
|
*/
|
|
function encode(
|
|
object : _zeitbild.type_calendar_object
|
|
)
|
|
: type_dispersal
|
|
{
|
|
return {
|
|
"core_row": {
|
|
"name": object.name,
|
|
"hue": Math.floor(object.hue * hue_scaling),
|
|
"access_public": object.access.public,
|
|
"access_level_default": encode_access_level(object.access.default_level),
|
|
"resource_id": object.resource_id,
|
|
},
|
|
"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,
|
|
"user_id": user_id,
|
|
"level": encode_access_level(level),
|
|
})
|
|
)
|
|
),
|
|
};
|
|
}
|
|
|
|
|
|
/**
|
|
*/
|
|
function decode(
|
|
dispersal : type_dispersal
|
|
)
|
|
: _zeitbild.type_calendar_object
|
|
{
|
|
return {
|
|
"name": dispersal.core_row.name,
|
|
"hue": (dispersal.core_row.hue / hue_scaling),
|
|
"access": {
|
|
"public": dispersal.core_row.access_public,
|
|
"default_level": decode_access_level(dispersal.core_row.access_level_default),
|
|
"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_user_rows
|
|
.map(
|
|
(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),
|
|
})
|
|
)
|
|
),
|
|
}
|
|
)
|
|
),
|
|
},
|
|
"resource_id": dispersal.core_row.resource_id,
|
|
};
|
|
}
|
|
|
|
|
|
/**
|
|
*/
|
|
export async function read(
|
|
id : _zeitbild.type_calendar_id
|
|
)
|
|
: Promise<_zeitbild.type_calendar_object>
|
|
{
|
|
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": {
|
|
"calendar_id": id,
|
|
}
|
|
}
|
|
)
|
|
.then(
|
|
(hits) => Promise.resolve<Array<type_access_attributed_group_row>>(
|
|
hits
|
|
.map(
|
|
hit => (
|
|
{
|
|
// "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 => (
|
|
{
|
|
// "calendar_id": null,
|
|
"user_id": hit.preview.user_id,
|
|
"level": hit.preview.level,
|
|
}
|
|
)
|
|
)
|
|
)
|
|
)
|
|
);
|
|
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;
|
|
}
|
|
|
|
|
|
/**
|
|
*/
|
|
export async function create(
|
|
calendar_object : _zeitbild.type_calendar_object
|
|
) : Promise<_zeitbild.type_calendar_id>
|
|
{
|
|
const dispersal : type_dispersal = encode(calendar_object);
|
|
const core_store = get_core_store();
|
|
const calendar_id : _zeitbild.type_calendar_id = await core_store.create(
|
|
dispersal.core_row
|
|
);
|
|
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);
|
|
return Promise.resolve<_zeitbild.type_calendar_id>(calendar_id);
|
|
}
|
|
|
|
|
|
/**
|
|
*/
|
|
export async function update(
|
|
calendar_id : _zeitbild.type_calendar_id,
|
|
calendar_object : _zeitbild.type_calendar_object
|
|
) : Promise<void>
|
|
{
|
|
const dispersal : type_dispersal = encode(calendar_object);
|
|
// core
|
|
{
|
|
const core_store = get_core_store();
|
|
await core_store.update(
|
|
calendar_id,
|
|
dispersal.core_row
|
|
);
|
|
}
|
|
// attributed:group
|
|
{
|
|
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": {
|
|
"calendar_id": calendar_id,
|
|
}
|
|
}
|
|
);
|
|
const contrast = lib_plankton.list.contrast<
|
|
Record<string, any>,
|
|
Record<string, any>
|
|
>(
|
|
hits,
|
|
hit => hit["user_id"],
|
|
dispersal.access_attributed_user_rows,
|
|
row => row["user_id"]
|
|
);
|
|
// delete
|
|
for await (const entry of contrast.only_left) {
|
|
await access_attributed_user_chest.delete(
|
|
[calendar_id, entry.left["user_id"]]
|
|
);
|
|
}
|
|
// update
|
|
for await (const entry of contrast.both) {
|
|
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_user_chest.write(
|
|
[calendar_id, entry.right["user_id"]],
|
|
{"level": entry.right["level"]}
|
|
);
|
|
}
|
|
}
|
|
await lib_plankton.cache.clear(_zeitbild.cache_regular);
|
|
return Promise.resolve<void>(undefined);
|
|
}
|
|
|
|
|
|
/**
|
|
* @todo remove events from resource?
|
|
* @todo remove resource
|
|
*/
|
|
export async function delete_(
|
|
calendar_id : _zeitbild.type_calendar_id
|
|
)
|
|
: Promise<void>
|
|
{
|
|
await lib_plankton.cache.clear(_zeitbild.cache_regular);
|
|
const core_store = get_core_store();
|
|
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 chest.search(
|
|
{
|
|
"expression": "(calendar_id = $calendar_id)",
|
|
"arguments": {
|
|
"calendar_id": calendar_id,
|
|
}
|
|
}
|
|
);
|
|
for (const hit of hits)
|
|
{
|
|
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
|
|
{
|
|
await core_store.delete(
|
|
calendar_id
|
|
);
|
|
}
|
|
return Promise.resolve<void>(undefined);
|
|
}
|
|
|
|
|
|
/**
|
|
*/
|
|
type type_overview_entry = {
|
|
id : _zeitbild.type_calendar_id;
|
|
name : string;
|
|
hue : float;
|
|
access_level : _zeitbild.enum_access_level;
|
|
}
|
|
|
|
|
|
/**
|
|
*/
|
|
export async function overview(
|
|
user_id : (null | _zeitbild.type_user_id)
|
|
)
|
|
: Promise<
|
|
Array<
|
|
type_overview_entry
|
|
>
|
|
>
|
|
{
|
|
type type_data = {
|
|
hits_core : Array<
|
|
{
|
|
key : int;
|
|
preview : Record<string, any>;
|
|
}
|
|
>;
|
|
hits_access_attributed_group : Array<
|
|
{
|
|
key : int;
|
|
preview : Record<string, any>;
|
|
}
|
|
>;
|
|
hits_access_attributed_user : Array<
|
|
{
|
|
key : int;
|
|
preview : Record<string, any>;
|
|
}
|
|
>;
|
|
};
|
|
return lib_plankton.cache.get_complex<any, Array<type_overview_entry>>(
|
|
_zeitbild.cache_regular,
|
|
"calendar_overview",
|
|
{
|
|
"user_id": user_id,
|
|
},
|
|
60,
|
|
async () => lib_plankton.call.convey(
|
|
{
|
|
"hits_core": await get_core_store().search(
|
|
{
|
|
"expression": "TRUE",
|
|
"arguments": {}
|
|
}
|
|
),
|
|
"hits_access_attributed_group": await get_access_attributed_group_chest().search(
|
|
(user_id === null)
|
|
?
|
|
{
|
|
"expression": "TRUE",
|
|
"arguments": {}
|
|
}
|
|
:
|
|
{
|
|
"expression": "(group_id IN (SELECT group_id FROM user_groups WHERE (user_id = $user_id)))",
|
|
"arguments": {"user_id": user_id}
|
|
}
|
|
),
|
|
"hits_access_attributed_user": await get_access_attributed_user_chest().search(
|
|
(user_id === null)
|
|
?
|
|
{
|
|
"expression": "TRUE",
|
|
"arguments": {}
|
|
}
|
|
:
|
|
{
|
|
"expression": "(user_id = $user_id)",
|
|
"arguments": {"user_id": user_id}
|
|
}
|
|
),
|
|
},
|
|
[
|
|
// transform
|
|
(data : type_data) => data.hits_core.map(
|
|
(hit_core) => {
|
|
const calendar_id : _zeitbild.type_calendar_id = hit_core.key;
|
|
return {
|
|
"id": calendar_id,
|
|
"name": hit_core.preview["name"],
|
|
"hue": (hit_core.preview["hue"] / hue_scaling),
|
|
"access_level": _zeitbild.access_level_determine_raw(
|
|
hit_core.preview["access_public"],
|
|
(
|
|
(user_id === null)
|
|
?
|
|
null
|
|
:
|
|
{
|
|
"default": decode_access_level(hit_core.preview["access_level_default"]),
|
|
"group": lib_plankton.call.convey(
|
|
data.hits_access_attributed_group,
|
|
[
|
|
(x : Array<{key : int; preview : Record<string, any>}>) => x.filter(
|
|
hit_access_attributed_group => (
|
|
(hit_access_attributed_group.preview.calendar_id === calendar_id)
|
|
)
|
|
),
|
|
(x : Array<{key : int; preview : Record<string, any>}>) => x.map(
|
|
hit_access_attributed_group => hit_access_attributed_group.preview.level
|
|
),
|
|
(x : Array<int>) => x.map(
|
|
decode_access_level
|
|
),
|
|
]
|
|
),
|
|
"user": lib_plankton.call.convey(
|
|
data.hits_access_attributed_user,
|
|
[
|
|
(x : Array<{key : int; preview : Record<string, any>}>) => x.filter(
|
|
hits_access_attributed_user => (
|
|
(hits_access_attributed_user.preview.calendar_id === calendar_id)
|
|
)
|
|
),
|
|
(x : Array<{key : int; preview : Record<string, any>}>) => x.map(
|
|
hits_access_attributed_user => hits_access_attributed_user.preview.level
|
|
),
|
|
(x : Array<int>) => x.map(
|
|
decode_access_level
|
|
),
|
|
(x : Array<_zeitbild.enum_access_level>) => (x[0] ?? null),
|
|
]
|
|
),
|
|
}
|
|
)
|
|
),
|
|
};
|
|
}
|
|
),
|
|
// only keep visible calendars
|
|
(x : Array<type_overview_entry>) => x.filter(
|
|
(row) => (
|
|
! _zeitbild.access_level_order(
|
|
row.access_level,
|
|
_zeitbild.enum_access_level.none
|
|
)
|
|
)
|
|
),
|
|
// sort by access level and name
|
|
(x : Array<type_overview_entry>) => lib_plankton.list.sorted<type_overview_entry>(
|
|
x,
|
|
{
|
|
"compare_element": lib_plankton.order.order_lexicographic_pair_wrapped<type_overview_entry, _zeitbild.enum_access_level, int>(
|
|
row => row.access_level,
|
|
row => row.id,
|
|
{
|
|
"order_first": (a, b) => _zeitbild.access_level_order(b, a),
|
|
"order_second": (a, b) => (a <= b)
|
|
}
|
|
),
|
|
}
|
|
),
|
|
]
|
|
)
|
|
);
|
|
}
|
|
|
|
}
|
|
|