[mod] access level stuff

This commit is contained in:
Fenris Wolf 2024-09-21 10:56:00 +02:00
parent 901fab3ac4
commit 2f6514c10b
11 changed files with 328 additions and 142 deletions

View file

@ -23,13 +23,15 @@
{
"id": 1,
"name": "house",
"public": false,
"members": [
{
"user_id": 1,
"role": "editor"
}
],
"access": {
"default_level": "none",
"attributed": [
{
"user_id": 1,
"level": "edit"
}
]
},
"resource": {
"kind": "local",
"data": {
@ -86,17 +88,19 @@
{
"id": 2,
"name": "turf",
"public": false,
"members": [
{
"user_id": 1,
"role": "viewer"
},
{
"user_id": 2,
"role": "editor"
}
],
"access": {
"default_level": "none",
"attributed": [
{
"user_id": 1,
"level": "view"
},
{
"user_id": 2,
"level": "edit"
}
]
},
"resource": {
"kind": "local",
"data": {
@ -138,21 +142,23 @@
{
"id": 3,
"name": "town",
"public": true,
"members": [
{
"user_id": 1,
"role": "viewer"
},
{
"user_id": 2,
"role": "viewer"
},
{
"user_id": 3,
"role": "editor"
}
],
"access": {
"default_level": "none",
"attributed": [
{
"user_id": 1,
"level": "view"
},
{
"user_id": 2,
"level": "view"
},
{
"user_id": 3,
"level": "edit"
}
]
},
"resource": {
"kind": "local",
"data": {

View file

@ -0,0 +1,50 @@
namespace _zeitbild.api
{
/**
*/
export function register_calendar_list(
rest_subject : lib_plankton.rest.type_rest
) : void
{
register<
{
name : string;
public : boolean;
members : Array<
{
user_id : user_id;
role : role;
}
>;
resource_id : resource_id;
},
int
>(
rest_subject,
lib_plankton.http.enum_method.post,
"/calendar/add",
{
"description": "erstellt einen Kalender",
"output_schema": () => ({
"nullable": true,
"type": "integer",
}),
"restriction": restriction_logged_in,
"execution": async (stuff) => {
return (
_zeitbild.service.calendar.overview(user_id)
.then(
data => Promise.resolve({
"status_code": 200,
"data": data,
})
)
);
}
}
);
}
}

View file

@ -12,10 +12,9 @@ namespace _zeitbild.api
null,
Array<
{
id : _zeitbild.type.calendar_id;
id : int;
name : string;
public : boolean;
role : (null | _zeitbild.type.role);
access_level : string;
}
>
>(
@ -41,13 +40,10 @@ namespace _zeitbild.api
"type": "string",
"nullable": false,
},
"public": {
"type": "boolean",
"nullable": false,
},
"role": {
"access_level": {
"type": "string",
"nullable": true,
"enum": ["none", "edit", "view", "admin"]
},
},
"required": [
@ -66,7 +62,19 @@ namespace _zeitbild.api
return (
_zeitbild.service.calendar.overview(user_id)
.then(
data => Promise.resolve({
(data_raw) => Promise.resolve(
data_raw
.map(
(entry) => ({
"id": entry.id,
"name": entry.name,
"access_level": _zeitbild.value_object.access_level.to_string(entry.access_level),
})
)
)
)
.then(
(data) => Promise.resolve({
"status_code": 200,
"data": data,
})

View file

@ -142,9 +142,8 @@ namespace _zeitbild.api
)
)
)
// @ts-ignore
.toSorted()
);
calendar_ids.sort();
const data = await _zeitbild.service.calendar.gather_events(
calendar_ids,
from,

View file

@ -14,13 +14,15 @@ type type_data = {
{
id : int;
name : string;
public : boolean;
members : Array<
{
user_id : int;
role : _zeitbild.type.role;
}
>;
access : {
default_level : ("none" | "view" | "edit" | "admin");
attributed : Array<
{
user_id : int;
level : ("none" | "view" | "edit" | "admin");
}
>;
};
resource : _zeitbild.type.resource_object;
}
>;
@ -47,11 +49,12 @@ async function data_init(
"calendar": {},
};
for await (const user_raw of data.users) {
const user_object : _zeitbild.type.user_object = {
"name": user_raw.name,
"email_address": user_raw.email_address,
};
const user_id : _zeitbild.type.user_id = await _zeitbild.service.user.add(
{
"name": user_raw.name,
"email_address": user_raw.email_address,
}
user_object
);
await _zeitbild.service.auth_internal.set(
user_raw.name,
@ -60,24 +63,35 @@ async function data_init(
track.user[user_raw.id] = user_id;
}
for await (const calendar_raw of data.calendars) {
const resource_object : _zeitbild.type.resource_object = calendar_raw.resource;
const resource_id : _zeitbild.type.resource_id = await _zeitbild.service.resource.add(
calendar_raw.resource
resource_object
);
const calendar_id : _zeitbild.type.calendar_id = await _zeitbild.service.calendar.add(
{
"name": calendar_raw.name,
"public": calendar_raw.public,
"members": (
calendar_raw.members
.map(
(member_raw) => ({
"user_id": track.user[member_raw.user_id],
"role": member_raw.role,
})
const calendar_object : _zeitbild.type.calendar_object = {
"name": calendar_raw.name,
"access": {
"default_level": _zeitbild.value_object.access_level.from_string(calendar_raw.access.default_level),
"attributed": lib_plankton.map.hashmap.implementation_map(
lib_plankton.map.hashmap.make(
x => x.toFixed(0),
{
"pairs": (
calendar_raw.access.attributed
.map(
(entry) => ({
"key": track.user[entry.user_id],
"value": _zeitbild.value_object.access_level.from_string(entry.level),
})
)
),
}
)
),
"resource_id": resource_id,
}
},
"resource_id": resource_id,
};
const calendar_id : _zeitbild.type.calendar_id = await _zeitbild.service.calendar.add(
calendar_object
);
track.calendar[calendar_raw.id] = calendar_id;
}

View file

@ -9,7 +9,7 @@ namespace _zeitbild.repository.calendar
string,
any
>;
member_rows : Array<
access_attributed_rows : Array<
Record<
string,
any
@ -35,7 +35,7 @@ namespace _zeitbild.repository.calendar
/**
*/
var _member_chest : (
var _access_attributed_chest : (
null
|
lib_plankton.storage.type_chest<
@ -77,7 +77,7 @@ namespace _zeitbild.repository.calendar
/**
*/
function get_member_chest(
function get_access_attributed_chest(
) : lib_plankton.storage.type_chest<
Array<any>,
Record<string, any>,
@ -86,11 +86,11 @@ namespace _zeitbild.repository.calendar
Record<string, any>
>
{
if (_member_chest === null) {
_member_chest = lib_plankton.storage.sql_table_common.chest(
if (_access_attributed_chest === null) {
_access_attributed_chest = lib_plankton.storage.sql_table_common.chest(
{
"database_implementation": _zeitbild.database.get_implementation(),
"table_name": "calendar_members",
"table_name": "calendar_access_attributed",
"key_names": ["calendar_id","user_id"],
}
);
@ -98,7 +98,41 @@ namespace _zeitbild.repository.calendar
else {
// do nothing
}
return _member_chest;
return _access_attributed_chest;
}
/**
*/
function encode_access_level(
access_level : _zeitbild.type.enum_access_level
) : int
{
return (
[
_zeitbild.type.enum_access_level.none,
_zeitbild.type.enum_access_level.view,
_zeitbild.type.enum_access_level.edit,
_zeitbild.type.enum_access_level.admin,
].indexOf(access_level)
);
}
/**
*/
function decode_access_level(
access_level_encoded : int
) : _zeitbild.type.enum_access_level
{
return (
[
_zeitbild.type.enum_access_level.none,
_zeitbild.type.enum_access_level.view,
_zeitbild.type.enum_access_level.edit,
_zeitbild.type.enum_access_level.admin,
][access_level_encoded]
);
}
@ -111,16 +145,16 @@ namespace _zeitbild.repository.calendar
return {
"core_row": {
"name": object.name,
"public": object.public,
"access_level_default": encode_access_level(object.access.default_level),
"resource_id": object.resource_id,
},
"member_rows": (
object.members
"access_attributed_rows": (
lib_plankton.map.dump(object.access.attributed)
.map(
(member) => ({
({"key": user_id, "value": level}) => ({
// "calendar_id": calendar_id,
"user_id": member.user_id,
"role": member.role,
"user_id": user_id,
"level": encode_access_level(level),
})
)
),
@ -136,17 +170,19 @@ namespace _zeitbild.repository.calendar
{
return {
"name": dispersal.core_row["name"],
"public": dispersal.core_row["public"],
"members": (
dispersal.member_rows
.map(
(member_row) => ({
"calendar_id": member_row["calendar_id"],
"user_id": member_row["user_id"],
"role": member_row["role"],
})
)
),
"access": {
"default_level": decode_access_level(dispersal.core_row["access_level_default"]),
"attributed": Object.fromEntries(
dispersal.access_attributed_rows
.map(
(access_attributed_row) => ([
// "calendar_id": access_attributed_row["calendar_id"],
access_attributed_row["user_id"],
decode_access_level(access_attributed_row["level"]),
])
)
),
},
"resource_id": dispersal.core_row["resource_id"],
};
}
@ -213,7 +249,7 @@ namespace _zeitbild.repository.calendar
get_core_store().read(id)
.then(
(core_row) => (
get_member_chest().search(
get_access_attributed_chest().search(
{
"expression": "(calendar_id = $calendar_id)",
"arguments": {
@ -222,10 +258,10 @@ namespace _zeitbild.repository.calendar
}
)
.then(
(member_rows) => Promise.resolve<type_dispersal>(
(access_attributed_rows) => Promise.resolve<type_dispersal>(
{
"core_row": core_row,
"member_rows": member_rows,
"access_attributed_rows": access_attributed_rows,
}
)
)
@ -257,11 +293,11 @@ namespace _zeitbild.repository.calendar
.then<_zeitbild.type.calendar_id>(
(calendar_id) => (
Promise.all(
dispersal.member_rows
dispersal.access_attributed_rows
.map(
(member_row) => get_member_chest().write(
[calendar_id, member_row["user_id"]],
{"role": member_row["role"]}
(access_attributed_row) => get_access_attributed_chest().write(
[calendar_id, access_attributed_row["user_id"]],
{"level": access_attributed_row["level"]}
)
)
)
@ -277,51 +313,43 @@ namespace _zeitbild.repository.calendar
/**
* @todo sort for role, public and id
*/
export function overview(
export async function overview(
user_id : _zeitbild.type.user_id
) : Promise<
Array<
{
id : _zeitbild.type.calendar_id;
name : string;
public : boolean;
role : (null | _zeitbild.type.role);
access_level : _zeitbild.type.enum_access_level;
}
>
>
{
return (
_zeitbild.database.get_implementation().query_free_get(
{
"template": "SELECT x.id AS id, x.name AS name, x.public AS public, MAX(y.role) AS role FROM calendars AS x LEFT OUTER JOIN calendar_members AS y ON (x.id = y.calendar_id) WHERE (x.public OR (y.user_id = $user_id)) GROUP BY x.id;",
"arguments": {
"user_id": user_id,
lib_plankton.file.read("sql/calendar_overview.sql")
.then(
(template) => _zeitbild.database.get_implementation().query_free_get(
{
"template": template,
"arguments": {
"user_id": user_id,
}
}
}
)
)
.then(
(rows) => Promise.resolve(
rows.map(
rows
.map(
(row) => ({
"id": row["id"],
"name": row["name"],
"public": (row["public"] === 1),
"role": row["role"],
"access_level": decode_access_level(row["access_level"]),
})
)
)
)
/*
.then(
(entries) => Promise.resolve(
entries.toSorted(
(x, y) => (x
)
)
)
*/
)
}
}

View file

@ -0,0 +1,20 @@
SELECT
x.id AS id,
x.name AS name,
(
CASE
WHEN MAX(y.level) IS NULL THEN x.access_level_default
ELSE MAX(y.level)
END
) AS access_level
FROM
calendars AS x
LEFT OUTER JOIN calendar_access_attributed AS y ON ((x.id = y.calendar_id) AND (y.user_id = $user_id))
GROUP BY
x.id
HAVING
(access_level > 0)
ORDER BY
access_level,
id
;

View file

@ -50,8 +50,7 @@ namespace _zeitbild.service.calendar
{
id : _zeitbild.type.calendar_id;
name : string;
public : boolean;
role : (null | _zeitbild.type.role);
access_level : _zeitbild.type.enum_access_level;
}
>
>

View file

@ -6,13 +6,12 @@ namespace _zeitbild.type
/**
*/
export type role = (
"admin"
|
"editor"
|
"viewer"
);
export enum enum_access_level {
none,
view,
edit,
admin
}
/**
@ -91,13 +90,13 @@ namespace _zeitbild.type
*/
export type calendar_object = {
name : string;
public : boolean;
members : Array<
{
user_id : user_id;
role : role;
}
>;
access : {
default_level : enum_access_level;
attributed : lib_plankton.map.type_map<
user_id,
enum_access_level
>;
};
resource_id : resource_id;
};

View file

@ -0,0 +1,55 @@
/**
*/
namespace _zeitbild.value_object.access_level
{
/**
*/
export function to_string(
access_level : _zeitbild.type.enum_access_level
) : string
{
switch (access_level) {
case _zeitbild.type.enum_access_level.none: {return "none";}
case _zeitbild.type.enum_access_level.view: {return "view";}
case _zeitbild.type.enum_access_level.edit: {return "edit";}
case _zeitbild.type.enum_access_level.admin: {return "admin";}
default: {throw (new Error("invalid access level: " + String(access_level)));}
}
}
/**
*/
export function from_string(
access_level_ : string
) : _zeitbild.type.enum_access_level
{
switch (access_level_) {
case "none": {return _zeitbild.type.enum_access_level.none;}
case "view": {return _zeitbild.type.enum_access_level.view;}
case "edit": {return _zeitbild.type.enum_access_level.edit;}
case "admin": {return _zeitbild.type.enum_access_level.admin;}
default: {throw (new Error("invalid encoded access level: " + String(access_level_)));}
}
}
/**
*/
export function order(
x : _zeitbild.type.enum_access_level,
y : _zeitbild.type.enum_access_level
) : boolean
{
const list : Array<_zeitbild.type.enum_access_level> = [
_zeitbild.type.enum_access_level.none,
_zeitbild.type.enum_access_level.view,
_zeitbild.type.enum_access_level.edit,
_zeitbild.type.enum_access_level.admin,
];
return (list.indexOf(x) <= list.indexOf(y));
}
}

View file

@ -18,11 +18,14 @@ cmd_tsc := ${dir_tools}/typescript/node_modules/.bin/tsc
## rules
.PHONY: default
default: ${dir_build}/zeitbild node_modules
default: sql ${dir_build}/zeitbild node_modules
.PHONY: node_modules
node_modules:
@ cd ${dir_build} && npm install sqlite3 bcrypt
.PHONY: sql
sql: \
$(wildcard ${dir_source}/repositories/sql/*)
@ ${cmd_log} "sql …"
@ ${cmd_mkdir} ${dir_build}/sql
@ ${cmd_cp} -r -u $^ ${dir_build}/sql/
${dir_temp}/conf.ts: \
${dir_source}/conf.ts.tpl \
@ -37,6 +40,7 @@ ${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}/repositories/auth_internal.ts \
${dir_source}/repositories/user.ts \
${dir_source}/repositories/resource.ts \
@ -72,3 +76,7 @@ ${dir_build}/zeitbild: \
@ ${cmd_mkdir} $(dir $@)
@ ${cmd_cat} $^ > $@
@ ${cmd_chmod} +x $@
.PHONY: node_modules
node_modules:
@ cd ${dir_build} && npm install sqlite3 bcrypt