[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, "id": 1,
"name": "house", "name": "house",
"public": false, "access": {
"members": [ "default_level": "none",
{ "attributed": [
"user_id": 1, {
"role": "editor" "user_id": 1,
} "level": "edit"
], }
]
},
"resource": { "resource": {
"kind": "local", "kind": "local",
"data": { "data": {
@ -86,17 +88,19 @@
{ {
"id": 2, "id": 2,
"name": "turf", "name": "turf",
"public": false, "access": {
"members": [ "default_level": "none",
{ "attributed": [
"user_id": 1, {
"role": "viewer" "user_id": 1,
}, "level": "view"
{ },
"user_id": 2, {
"role": "editor" "user_id": 2,
} "level": "edit"
], }
]
},
"resource": { "resource": {
"kind": "local", "kind": "local",
"data": { "data": {
@ -138,21 +142,23 @@
{ {
"id": 3, "id": 3,
"name": "town", "name": "town",
"public": true, "access": {
"members": [ "default_level": "none",
{ "attributed": [
"user_id": 1, {
"role": "viewer" "user_id": 1,
}, "level": "view"
{ },
"user_id": 2, {
"role": "viewer" "user_id": 2,
}, "level": "view"
{ },
"user_id": 3, {
"role": "editor" "user_id": 3,
} "level": "edit"
], }
]
},
"resource": { "resource": {
"kind": "local", "kind": "local",
"data": { "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, null,
Array< Array<
{ {
id : _zeitbild.type.calendar_id; id : int;
name : string; name : string;
public : boolean; access_level : string;
role : (null | _zeitbild.type.role);
} }
> >
>( >(
@ -41,13 +40,10 @@ namespace _zeitbild.api
"type": "string", "type": "string",
"nullable": false, "nullable": false,
}, },
"public": { "access_level": {
"type": "boolean",
"nullable": false,
},
"role": {
"type": "string", "type": "string",
"nullable": true, "nullable": true,
"enum": ["none", "edit", "view", "admin"]
}, },
}, },
"required": [ "required": [
@ -66,7 +62,19 @@ namespace _zeitbild.api
return ( return (
_zeitbild.service.calendar.overview(user_id) _zeitbild.service.calendar.overview(user_id)
.then( .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, "status_code": 200,
"data": data, "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( const data = await _zeitbild.service.calendar.gather_events(
calendar_ids, calendar_ids,
from, from,

View file

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

View file

@ -9,7 +9,7 @@ namespace _zeitbild.repository.calendar
string, string,
any any
>; >;
member_rows : Array< access_attributed_rows : Array<
Record< Record<
string, string,
any any
@ -35,7 +35,7 @@ namespace _zeitbild.repository.calendar
/** /**
*/ */
var _member_chest : ( var _access_attributed_chest : (
null null
| |
lib_plankton.storage.type_chest< 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< ) : lib_plankton.storage.type_chest<
Array<any>, Array<any>,
Record<string, any>, Record<string, any>,
@ -86,11 +86,11 @@ namespace _zeitbild.repository.calendar
Record<string, any> Record<string, any>
> >
{ {
if (_member_chest === null) { if (_access_attributed_chest === null) {
_member_chest = lib_plankton.storage.sql_table_common.chest( _access_attributed_chest = lib_plankton.storage.sql_table_common.chest(
{ {
"database_implementation": _zeitbild.database.get_implementation(), "database_implementation": _zeitbild.database.get_implementation(),
"table_name": "calendar_members", "table_name": "calendar_access_attributed",
"key_names": ["calendar_id","user_id"], "key_names": ["calendar_id","user_id"],
} }
); );
@ -98,7 +98,41 @@ namespace _zeitbild.repository.calendar
else { else {
// do nothing // 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 { return {
"core_row": { "core_row": {
"name": object.name, "name": object.name,
"public": object.public, "access_level_default": encode_access_level(object.access.default_level),
"resource_id": object.resource_id, "resource_id": object.resource_id,
}, },
"member_rows": ( "access_attributed_rows": (
object.members lib_plankton.map.dump(object.access.attributed)
.map( .map(
(member) => ({ ({"key": user_id, "value": level}) => ({
// "calendar_id": calendar_id, // "calendar_id": calendar_id,
"user_id": member.user_id, "user_id": user_id,
"role": member.role, "level": encode_access_level(level),
}) })
) )
), ),
@ -136,17 +170,19 @@ namespace _zeitbild.repository.calendar
{ {
return { return {
"name": dispersal.core_row["name"], "name": dispersal.core_row["name"],
"public": dispersal.core_row["public"], "access": {
"members": ( "default_level": decode_access_level(dispersal.core_row["access_level_default"]),
dispersal.member_rows "attributed": Object.fromEntries(
.map( dispersal.access_attributed_rows
(member_row) => ({ .map(
"calendar_id": member_row["calendar_id"], (access_attributed_row) => ([
"user_id": member_row["user_id"], // "calendar_id": access_attributed_row["calendar_id"],
"role": member_row["role"], access_attributed_row["user_id"],
}) decode_access_level(access_attributed_row["level"]),
) ])
), )
),
},
"resource_id": dispersal.core_row["resource_id"], "resource_id": dispersal.core_row["resource_id"],
}; };
} }
@ -213,7 +249,7 @@ namespace _zeitbild.repository.calendar
get_core_store().read(id) get_core_store().read(id)
.then( .then(
(core_row) => ( (core_row) => (
get_member_chest().search( get_access_attributed_chest().search(
{ {
"expression": "(calendar_id = $calendar_id)", "expression": "(calendar_id = $calendar_id)",
"arguments": { "arguments": {
@ -222,10 +258,10 @@ namespace _zeitbild.repository.calendar
} }
) )
.then( .then(
(member_rows) => Promise.resolve<type_dispersal>( (access_attributed_rows) => Promise.resolve<type_dispersal>(
{ {
"core_row": core_row, "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>( .then<_zeitbild.type.calendar_id>(
(calendar_id) => ( (calendar_id) => (
Promise.all( Promise.all(
dispersal.member_rows dispersal.access_attributed_rows
.map( .map(
(member_row) => get_member_chest().write( (access_attributed_row) => get_access_attributed_chest().write(
[calendar_id, member_row["user_id"]], [calendar_id, access_attributed_row["user_id"]],
{"role": member_row["role"]} {"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 user_id : _zeitbild.type.user_id
) : Promise< ) : Promise<
Array< Array<
{ {
id : _zeitbild.type.calendar_id; id : _zeitbild.type.calendar_id;
name : string; name : string;
public : boolean; access_level : _zeitbild.type.enum_access_level;
role : (null | _zeitbild.type.role);
} }
> >
> >
{ {
return ( return (
_zeitbild.database.get_implementation().query_free_get( lib_plankton.file.read("sql/calendar_overview.sql")
{ .then(
"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;", (template) => _zeitbild.database.get_implementation().query_free_get(
"arguments": { {
"user_id": user_id, "template": template,
"arguments": {
"user_id": user_id,
}
} }
} )
) )
.then( .then(
(rows) => Promise.resolve( (rows) => Promise.resolve(
rows.map( rows
.map(
(row) => ({ (row) => ({
"id": row["id"], "id": row["id"],
"name": row["name"], "name": row["name"],
"public": (row["public"] === 1), "access_level": decode_access_level(row["access_level"]),
"role": row["role"],
}) })
) )
) )
) )
/*
.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; id : _zeitbild.type.calendar_id;
name : string; name : string;
public : boolean; access_level : _zeitbild.type.enum_access_level;
role : (null | _zeitbild.type.role);
} }
> >
> >

View file

@ -6,13 +6,12 @@ namespace _zeitbild.type
/** /**
*/ */
export type role = ( export enum enum_access_level {
"admin" none,
| view,
"editor" edit,
| admin
"viewer" }
);
/** /**
@ -91,13 +90,13 @@ namespace _zeitbild.type
*/ */
export type calendar_object = { export type calendar_object = {
name : string; name : string;
public : boolean; access : {
members : Array< default_level : enum_access_level;
{ attributed : lib_plankton.map.type_map<
user_id : user_id; user_id,
role : role; enum_access_level
} >;
>; };
resource_id : resource_id; 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 ## rules
.PHONY: default .PHONY: default
default: ${dir_build}/zeitbild node_modules default: sql ${dir_build}/zeitbild node_modules
.PHONY: node_modules .PHONY: sql
node_modules: sql: \
@ cd ${dir_build} && npm install sqlite3 bcrypt $(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_temp}/conf.ts: \
${dir_source}/conf.ts.tpl \ ${dir_source}/conf.ts.tpl \
@ -37,6 +40,7 @@ ${dir_temp}/zeitbild-unlinked.js: \
${dir_source}/database.ts \ ${dir_source}/database.ts \
${dir_source}/auth.ts \ ${dir_source}/auth.ts \
${dir_source}/types.ts \ ${dir_source}/types.ts \
${dir_source}/value_objects/access_level.ts \
${dir_source}/repositories/auth_internal.ts \ ${dir_source}/repositories/auth_internal.ts \
${dir_source}/repositories/user.ts \ ${dir_source}/repositories/user.ts \
${dir_source}/repositories/resource.ts \ ${dir_source}/repositories/resource.ts \
@ -72,3 +76,7 @@ ${dir_build}/zeitbild: \
@ ${cmd_mkdir} $(dir $@) @ ${cmd_mkdir} $(dir $@)
@ ${cmd_cat} $^ > $@ @ ${cmd_cat} $^ > $@
@ ${cmd_chmod} +x $@ @ ${cmd_chmod} +x $@
.PHONY: node_modules
node_modules:
@ cd ${dir_build} && npm install sqlite3 bcrypt