[add] Termine hinzufügen und entfernen [fix] Rechte-Prüfung

This commit is contained in:
Fenris Wolf 2024-09-26 10:33:33 +02:00
parent a17b7dd785
commit 1c8acbaed6
15 changed files with 702 additions and 461 deletions

View file

@ -40,7 +40,7 @@ namespace _zeitbild.api
>( >(
rest_subject, rest_subject,
lib_plankton.http.enum_method.post, lib_plankton.http.enum_method.post,
"/calendar/add", "/calendar",
{ {
"description": "erstellt einen Kalender", "description": "erstellt einen Kalender",
"output_schema": () => ({ "output_schema": () => ({

View file

@ -9,65 +9,50 @@ namespace _zeitbild.api
) : void ) : void
{ {
register< register<
{ _zeitbild.type_event_object, // TODO aufdröseln
calendar_id : int; (
event : _zeitbild.type_event_object; // TODO aufdröseln null
}, |
null string
)
>( >(
rest_subject, rest_subject,
lib_plankton.http.enum_method.post, lib_plankton.http.enum_method.post,
"/calendar/event_add", "/calendar/:calendar_id/event",
{ {
"description": "fügt einen Termin hinzu", "description": "fügt einen Termin hinzu",
"input_schema": () => ({ "input_schema": () => ({
"nullable": false,
"type": "object", "type": "object",
"properties": { "properties": {
"calendar_id": { "name": {
"nullable": false, "nullable": false,
"type": "integer", "type": "string"
}, },
"event": { "begin": _zeitbild.api.datetime_type(
"nullable": false, {
"type": "object", "nullable": false,
"properties": { }
"name": { ),
"nullable": false, "end": _zeitbild.api.datetime_type(
"type": "string" {
}, "nullable": true,
// TODO: fix }
"begin": { ),
"type": "int", "location": {
"nullable": false, "type": "string",
}, "nullable": true,
// TODO: fix },
"end": { "description": {
"type": "int", "type": "string",
"nullable": true, "nullable": true,
},
"location": {
"type": "string",
"nullable": true,
},
"description": {
"type": "string",
"nullable": true,
},
},
"required": [
"name",
"begin",
"end",
"location",
"description",
],
"additionalProperties": false
}, },
}, },
"required": [ "required": [
"calendar_id", "name",
"event", "begin",
"end",
"location",
"description",
], ],
"additionalProperties": false "additionalProperties": false
}), }),
@ -77,14 +62,18 @@ namespace _zeitbild.api
}), }),
"restriction": restriction_logged_in, "restriction": restriction_logged_in,
"execution": async (stuff) => { "execution": async (stuff) => {
const session : {key : string; value : lib_plankton.session.type_session;} = await session_from_stuff(stuff);
const user_id : _zeitbild.type_user_id = await _zeitbild.service.user.identify(session.value.name);
if (stuff.input === null) { if (stuff.input === null) {
return Promise.reject(new Error("impossible")); return Promise.reject(new Error("impossible"));
} }
else { else {
return ( return (
_zeitbild.service.calendar.event_add( _zeitbild.service.calendar.event_add(
stuff.input.calendar_id, parseInt(stuff.path_parameters["calendar_id"]),
stuff.input.event stuff.input,
user_id
) )
.then( .then(
() => Promise.resolve({ () => Promise.resolve({
@ -92,6 +81,12 @@ namespace _zeitbild.api
"data": null, "data": null,
}) })
) )
.catch(
(reason) => Promise.resolve({
"status_code": 403,
"data": String(reason),
})
)
); );
} }
} }

View file

@ -0,0 +1,57 @@
namespace _zeitbild.api
{
/**
*/
export function register_calendar_event_remove(
rest_subject : lib_plankton.rest.type_rest
) : void
{
register<
null,
(
null
|
string
)
>(
rest_subject,
lib_plankton.http.enum_method.delete,
"/calendar/:calendar_id/event/:event_id",
{
"description": "entfernt einen Termin",
"output_schema": () => ({
"nullable": true,
"type": "null"
}),
"restriction": restriction_logged_in,
"execution": async (stuff) => {
const session : {key : string; value : lib_plankton.session.type_session;} = await session_from_stuff(stuff);
const user_id : _zeitbild.type_user_id = await _zeitbild.service.user.identify(session.value.name);
return (
_zeitbild.service.calendar.event_remove(
parseInt(stuff.path_parameters["calendar_id"]),
parseInt(stuff.path_parameters["event_id"]),
user_id
)
.then(
() => Promise.resolve({
"status_code": 200,
"data": null,
})
)
.catch(
(reason) => Promise.resolve({
"status_code": 403,
"data": String(reason),
})
)
);
}
}
);
}
}

View file

@ -20,7 +20,7 @@ namespace _zeitbild.api
>( >(
rest_subject, rest_subject,
lib_plankton.http.enum_method.get, lib_plankton.http.enum_method.get,
"/calendars", "/calendar",
{ {
"description": "listet alle verfügbaren Kalender auf", "description": "listet alle verfügbaren Kalender auf",
"query_parameters": () => ([ "query_parameters": () => ([

View file

@ -18,13 +18,17 @@ namespace _zeitbild.api
Array<_zeitbild.type_calendar_id> Array<_zeitbild.type_calendar_id>
); );
}, },
Array< (
{ Array<
calendar_id : int; {
calendar_name : string; calendar_id : int;
event : _zeitbild.type_event_object; calendar_name : string;
} event : _zeitbild.type_event_object;
> }
>
|
string
)
>( >(
rest_subject, rest_subject,
lib_plankton.http.enum_method.get, lib_plankton.http.enum_method.get,
@ -67,16 +71,16 @@ namespace _zeitbild.api
"type": "string", "type": "string",
"nullable": false, "nullable": false,
}, },
// TODO: fix "begin": _zeitbild.api.datetime_type(
"begin": { {
"type": "int", "nullable": false,
"nullable": false, }
}, ),
// TODO: fix "end": _zeitbild.api.datetime_type(
"end": { {
"type": "int", "nullable": true,
"nullable": true, }
}, ),
"location": { "location": {
"type": "string", "type": "string",
"nullable": true, "nullable": true,
@ -109,7 +113,7 @@ namespace _zeitbild.api
const from : _zeitbild.helpers.type_pit = parseInt(stuff.query_parameters["from"]); const from : _zeitbild.helpers.type_pit = parseInt(stuff.query_parameters["from"]);
const to : _zeitbild.helpers.type_pit = parseInt(stuff.query_parameters["to"]); const to : _zeitbild.helpers.type_pit = parseInt(stuff.query_parameters["to"]);
const calendar_ids_wanted : Array<_zeitbild.type_calendar_id> = ( const calendar_ids_wanted : (null | Array<_zeitbild.type_calendar_id>) = (
( (
("calendar_ids" in stuff.query_parameters) ("calendar_ids" in stuff.query_parameters)
&& &&
@ -127,34 +131,26 @@ namespace _zeitbild.api
: :
null null
); );
const calendar_ids_allowed : Array<_zeitbild.type_calendar_id> = ( return (
(await _zeitbild.service.calendar.overview(user_id)) _zeitbild.service.calendar.gather_events(
.map((x : any) => x.id) calendar_ids_wanted,
); from,
const calendar_ids : Array<_zeitbild.type_calendar_id> = ( to,
( user_id
(calendar_ids_wanted === null) )
? .then(
calendar_ids_allowed (data) => Promise.resolve({
: "status_code": 200,
( "data": data,
calendar_ids_wanted })
.filter( )
(calendar_id) => calendar_ids_allowed.includes(calendar_id) .catch(
) (reason) => Promise.resolve({
) "status_code": 403,
"data": String(reason),
})
) )
); );
calendar_ids.sort();
const data = await _zeitbild.service.calendar.gather_events(
calendar_ids,
from,
to
);
return Promise.resolve({
"status_code": 200,
"data": data,
});
} }
} }
); );

View file

@ -35,7 +35,11 @@ namespace _zeitbild.api
{ {
_zeitbild.api.register_calendar_list(rest_subject); _zeitbild.api.register_calendar_list(rest_subject);
_zeitbild.api.register_calendar_add(rest_subject); _zeitbild.api.register_calendar_add(rest_subject);
_zeitbild.api.register_calendar_event_add(rest_subject); // event
{
_zeitbild.api.register_calendar_event_add(rest_subject);
_zeitbild.api.register_calendar_event_remove(rest_subject);
}
} }
// misc // misc
{ {

View file

@ -0,0 +1,119 @@
namespace _zeitbild.api
{
/**
*/
export function date_type(
options : {
nullable ?: boolean;
} = {}
) : lib_plankton.rest.type_oas_schema
{
options = Object.assign(
{
"nullable": false,
},
options
);
return {
"nullable": options.nullable,
"type": "object",
"properties": {
"year": {
"nullable": false,
"type": "integer"
},
"month": {
"nullable": false,
"type": "integer"
},
"day": {
"nullable": false,
"type": "integer"
},
},
"required": [
"year",
"month",
"day"
],
"additionalProperties": false
};
}
/**
*/
export function time_type(
options : {
nullable ?: boolean;
} = {}
) : lib_plankton.rest.type_oas_schema
{
options = Object.assign(
{
"nullable": false,
},
options
);
return {
"nullable": options.nullable,
"type": "object",
"properties": {
"hour": {
"nullable": false,
"type": "integer"
},
"minute": {
"nullable": false,
"type": "integer"
},
"second": {
"nullable": false,
"type": "integer"
},
},
"required": [
"hour",
"minute",
"second"
],
"additionalProperties": false
};
}
/**
*/
export function datetime_type(
options : {
nullable ?: boolean;
} = {}
) : lib_plankton.rest.type_oas_schema
{
options = Object.assign(
{
"nullable": false,
},
options
);
return {
"nullable": options.nullable,
"type": "object",
"properties": {
"timezone_shift": {
"nullable": false,
"type": "integer"
},
"date": date_type({"nullable": false}),
"time": time_type({"nullable": true}),
},
"required": [
],
"additionalProperties": false
};
}
}

View file

@ -81,21 +81,24 @@ async function data_init(
} }
for await (const calendar_raw of data.calendars) { for await (const calendar_raw of data.calendars) {
let resource_object : _zeitbild.type_resource_object; let resource_object : _zeitbild.type_resource_object;
let resource_id : _zeitbild.type_resource_id;
switch (calendar_raw.resource.kind) { switch (calendar_raw.resource.kind) {
case "local": { case "local": {
const event_ids : Array<_zeitbild.type_event_id> = await Promise.all<_zeitbild.type_event_id>(
calendar_raw.resource.data.events
.map(
// TODO do not use repository, but service
(event_raw : _zeitbild.type_event_object) => _zeitbild.repository.local_resource_event.create(event_raw)
)
);
resource_object = { resource_object = {
"kind": "local", "kind": "local",
"data": { "data": {
"event_ids": event_ids, "event_ids": [],
} }
}; };
resource_id = await _zeitbild.service.resource.add(
resource_object
);
/*const event_ids : Array<_zeitbild.type_local_resource_event_id> = */await Promise.all(
calendar_raw.resource.data.events
.map(
(event_raw : _zeitbild.type_event_object) => _zeitbild.service.resource.event_add(resource_id, event_raw)
)
);
break; break;
} }
case "caldav": { case "caldav": {
@ -106,12 +109,12 @@ async function data_init(
"read_only": calendar_raw.resource.data.read_only, "read_only": calendar_raw.resource.data.read_only,
} }
}; };
resource_id = await _zeitbild.service.resource.add(
resource_object
);
break; break;
} }
} }
const resource_id : _zeitbild.type_resource_id = await _zeitbild.service.resource.add(
resource_object
);
const calendar_object : _zeitbild.type_calendar_object = { const calendar_object : _zeitbild.type_calendar_object = {
"name": calendar_raw.name, "name": calendar_raw.name,
"access": { "access": {

View file

@ -172,14 +172,21 @@ namespace _zeitbild.repository.calendar
"name": dispersal.core_row["name"], "name": dispersal.core_row["name"],
"access": { "access": {
"default_level": decode_access_level(dispersal.core_row["access_level_default"]), "default_level": decode_access_level(dispersal.core_row["access_level_default"]),
"attributed": Object.fromEntries( "attributed": lib_plankton.map.hashmap.implementation_map(
dispersal.access_attributed_rows lib_plankton.map.hashmap.make<_zeitbild.type_user_id, _zeitbild.enum_access_level>(
.map( x => x.toFixed(0),
(access_attributed_row) => ([ {
// "calendar_id": access_attributed_row["calendar_id"], "pairs": (
access_attributed_row["user_id"], dispersal.access_attributed_rows
decode_access_level(access_attributed_row["level"]), .map(
]) (access_attributed_row) => ({
// "calendar_id": access_attributed_row["calendar_id"],
"key": access_attributed_row["preview"]["user_id"],
"value": decode_access_level(access_attributed_row["preview"]["level"]),
})
)
),
}
) )
), ),
}, },

View file

@ -1,180 +0,0 @@
namespace _zeitbild.repository.local_resource_event
{
/**
*/
var _store : (
null
|
lib_plankton.storage.type_store<
int,
Record<string, any>,
{},
lib_plankton.storage.type_sql_table_autokey_search_term,
Record<string, any>
>
) = null;
/**
*/
function get_store(
) : lib_plankton.storage.type_store<
int,
Record<string, any>,
{},
lib_plankton.storage.type_sql_table_autokey_search_term,
Record<string, any>
>
{
if (_store === null) {
_store = lib_plankton.storage.sql_table_autokey_store(
{
"database_implementation": _zeitbild.database.get_implementation(),
"table_name": "events",
"key_name": "id",
}
);
}
else {
// do nothing
}
return _store;
}
/**
*/
function encode(
event : _zeitbild.type_event_object
) : Record<string, any>
{
const encode_datetime : ((datetime : _zeitbild.helpers.type_datetime) => string) = ((datetime) => {
return lib_plankton.string.coin(
"{{timezone_shift}}|{{date}}{{macro_time}}",
{
"timezone_shift": datetime.timezone_shift.toFixed(0).padStart(2, "0"),
"date": lib_plankton.string.coin(
"{{year}}-{{month}}-{{day}}",
{
"year": datetime.date.year.toFixed(0).padStart(4, "0"),
"month": datetime.date.month.toFixed(0).padStart(2, "0"),
"day": datetime.date.day.toFixed(0).padStart(2, "0"),
}
),
"macro_time": (
(datetime.time === null)
?
""
:
lib_plankton.string.coin(
"T{{hour}}:{{minute}}:{{second}}",
{
"hour": datetime.time.hour.toFixed(0).padStart(2, "0"),
"minute": datetime.time.minute.toFixed(0).padStart(2, "0"),
"second": datetime.time.second.toFixed(0).padStart(2, "0"),
}
)
),
}
);
});
return {
"name": event.name,
"begin": encode_datetime(event.begin),
"end": (
(event.end === null)
?
null
:
encode_datetime(event.end)
),
"location": event.location,
"description": event.description,
}
}
/**
*/
function decode(
row : Record<string, any>
) : _zeitbild.type_event_object
{
const decode_datetime : ((datetime_raw : string) => _zeitbild.helpers.type_datetime) = ((datetime_raw) => {
const parts : Array<string> = datetime_raw.split("|");
const timezone_shift : int = parseInt(parts[0]);
if (parts[1].length <= 10) {
return {
"timezone_shift": timezone_shift,
"date": {
"year": parseInt(parts[1].slice(0, 4)),
"month": parseInt(parts[1].slice(5, 7)),
"day": parseInt(parts[1].slice(8, 10)),
},
"time": null
};
}
else {
return {
"timezone_shift": timezone_shift,
"date": {
"year": parseInt(parts[1].slice(0, 4)),
"month": parseInt(parts[1].slice(5, 7)),
"day": parseInt(parts[1].slice(8, 10)),
},
"time": {
"hour": parseInt(parts[1].slice(11, 13)),
"minute": parseInt(parts[1].slice(14, 16)),
"second": parseInt(parts[1].slice(17, 19)),
}
};
}
});
return {
"name": row["name"],
"begin": decode_datetime(row["begin"]),
"end": (
(row["end"] === null)
?
null
:
decode_datetime(row["end"])
),
"location": row["location"],
"description": row["description"],
};
}
/**
*/
export function read(
event_id : _zeitbild.type_event_id
) : Promise<_zeitbild.type_event_object>
{
return (
get_store().read(event_id)
.then(
row => Promise.resolve<_zeitbild.type_event_object>(decode(row))
)
);
}
/**
*/
export function create(
event_object : _zeitbild.type_event_object
) : Promise<_zeitbild.type_event_id>
{
return (
Promise.resolve(encode(event_object))
.then(
(row) => get_store().create(row)
)
);
}
}

View file

@ -4,14 +4,22 @@ namespace _zeitbild.repository.resource
/** /**
*/ */
var _local_resource_event_chest : ( type type_local_resource_event_stuff = {
event : _zeitbild.type_event_object;
local_resource_id : int;
}
/**
*/
var _local_resource_event_store : (
null null
| |
lib_plankton.storage.type_chest< lib_plankton.storage.type_store<
Array<any>, int,
Record<string, any>, Record<string, any>,
lib_plankton.database.type_description_create_table, {},
lib_plankton.storage.sql_table_common.type_sql_table_common_search_term, lib_plankton.storage.type_sql_table_autokey_search_term,
Record<string, any> Record<string, any>
> >
) = null; ) = null;
@ -64,28 +72,28 @@ namespace _zeitbild.repository.resource
/** /**
*/ */
function get_local_resource_event_chest( function get_local_resource_event_store(
) : lib_plankton.storage.type_chest< ) : lib_plankton.storage.type_store<
Array<any>, int,
Record<string, any>, Record<string, any>,
lib_plankton.database.type_description_create_table, {},
lib_plankton.storage.sql_table_common.type_sql_table_common_search_term, lib_plankton.storage.type_sql_table_autokey_search_term,
Record<string, any> Record<string, any>
> >
{ {
if (_local_resource_event_chest === null) { if (_local_resource_event_store === null) {
_local_resource_event_chest = lib_plankton.storage.sql_table_common.chest( _local_resource_event_store = lib_plankton.storage.sql_table_autokey_store(
{ {
"database_implementation": _zeitbild.database.get_implementation(), "database_implementation": _zeitbild.database.get_implementation(),
"table_name": "local_resource_events", "table_name": "local_resource_events",
"key_names": ["local_resource_id","event_id"], "key_name": "id",
} }
); );
} }
else { else {
// do nothing // do nothing
} }
return _local_resource_event_chest; return _local_resource_event_store;
} }
@ -170,6 +178,114 @@ namespace _zeitbild.repository.resource
} }
/**
*/
function encode_local_resource_event(
stuff : type_local_resource_event_stuff
) : Record<string, any>
{
const encode_datetime : ((datetime : _zeitbild.helpers.type_datetime) => string) = ((datetime) => {
return lib_plankton.string.coin(
"{{timezone_shift}}|{{date}}{{macro_time}}",
{
"timezone_shift": datetime.timezone_shift.toFixed(0).padStart(2, "0"),
"date": lib_plankton.string.coin(
"{{year}}-{{month}}-{{day}}",
{
"year": datetime.date.year.toFixed(0).padStart(4, "0"),
"month": datetime.date.month.toFixed(0).padStart(2, "0"),
"day": datetime.date.day.toFixed(0).padStart(2, "0"),
}
),
"macro_time": (
(datetime.time === null)
?
""
:
lib_plankton.string.coin(
"T{{hour}}:{{minute}}:{{second}}",
{
"hour": datetime.time.hour.toFixed(0).padStart(2, "0"),
"minute": datetime.time.minute.toFixed(0).padStart(2, "0"),
"second": datetime.time.second.toFixed(0).padStart(2, "0"),
}
)
),
}
);
});
return {
"local_resource_id": stuff.local_resource_id,
"name": stuff.event.name,
"begin": encode_datetime(stuff.event.begin),
"end": (
(stuff.event.end === null)
?
null
:
encode_datetime(stuff.event.end)
),
"location": stuff.event.location,
"description": stuff.event.description,
}
}
/**
*/
function decode_local_resource_event(
row : Record<string, any>
) : type_local_resource_event_stuff
{
const decode_datetime : ((datetime_raw : string) => _zeitbild.helpers.type_datetime) = ((datetime_raw) => {
const parts : Array<string> = datetime_raw.split("|");
const timezone_shift : int = parseInt(parts[0]);
if (parts[1].length <= 10) {
return {
"timezone_shift": timezone_shift,
"date": {
"year": parseInt(parts[1].slice(0, 4)),
"month": parseInt(parts[1].slice(5, 7)),
"day": parseInt(parts[1].slice(8, 10)),
},
"time": null
};
}
else {
return {
"timezone_shift": timezone_shift,
"date": {
"year": parseInt(parts[1].slice(0, 4)),
"month": parseInt(parts[1].slice(5, 7)),
"day": parseInt(parts[1].slice(8, 10)),
},
"time": {
"hour": parseInt(parts[1].slice(11, 13)),
"minute": parseInt(parts[1].slice(14, 16)),
"second": parseInt(parts[1].slice(17, 19)),
}
};
}
});
return {
"local_resource_id": row["local_resource_id"],
"event": {
"name": row["name"],
"begin": decode_datetime(row["begin"]),
"end": (
(row["end"] === null)
?
null
:
decode_datetime(row["end"])
),
"location": row["location"],
"description": row["description"],
}
};
}
/** /**
*/ */
/* /*
@ -220,7 +336,7 @@ namespace _zeitbild.repository.resource
switch (dataset_core.kind) { switch (dataset_core.kind) {
case "local": { case "local": {
const dataset_extra_local_core : Record<string, any> = await get_local_resource_core_store().read(dataset_core.sub_id); const dataset_extra_local_core : Record<string, any> = await get_local_resource_core_store().read(dataset_core.sub_id);
const datasets_extra_local_event_ids : Array<Record<string, any>> = await get_local_resource_event_chest().search( const datasets_extra_local_event_ids : Array<Record<string, any>> = await get_local_resource_event_store().search(
{ {
"expression": "(local_resource_id = $local_resource_id)", "expression": "(local_resource_id = $local_resource_id)",
"arguments": { "arguments": {
@ -234,7 +350,7 @@ namespace _zeitbild.repository.resource
"data": { "data": {
"event_ids": ( "event_ids": (
datasets_extra_local_event_ids datasets_extra_local_event_ids
.map((hit) => hit.preview["event_id"]) .map((hit) => hit.preview["id"])
) )
} }
} }
@ -276,13 +392,6 @@ namespace _zeitbild.repository.resource
"_dummy": null, "_dummy": null,
} }
); );
for await (const event_id of resource_object.data.event_ids) {
await get_local_resource_event_chest().write(
[local_resource_id, event_id],
{
}
)
}
const resource_id : _zeitbild.type_resource_id = await get_resource_core_store().create( const resource_id : _zeitbild.type_resource_id = await get_resource_core_store().create(
{ {
"kind": "local", "kind": "local",
@ -365,12 +474,12 @@ namespace _zeitbild.repository.resource
for await (const entry of contrast.both) { for await (const entry of contrast.both) {
await get_local_resource_event_store().update( await get_local_resource_event_store().update(
entry.left.key, entry.left.key,
encode_event(entry.right.object) encode_local_resource_event(entry.right.object)
); );
} }
for await (const entry of contrast.only_right) { for await (const entry of contrast.only_right) {
const event_id : type_local_resource_event_id = await get_local_resource_event_store().create( const event_id : type_local_resource_event_id = await get_local_resource_event_store().create(
encode_event(entry.right.object) encode_local_resource_event(entry.right.object)
); );
} }
*/ */
@ -398,10 +507,10 @@ namespace _zeitbild.repository.resource
/** /**
*/ */
export async function local_resource_event_add( export async function local_resource_event_read(
resource_id : _zeitbild.type_resource_id, resource_id : _zeitbild.type_resource_id,
event_id : _zeitbild.type_event_id local_resource_event_id : _zeitbild.type_local_resource_event_id
) : Promise<void> ) : Promise<_zeitbild.type_event_object>
{ {
const dataset_core : Record<string, any> = await get_resource_core_store().read(resource_id); const dataset_core : Record<string, any> = await get_resource_core_store().read(resource_id);
if (! (dataset_core.kind === "local")) { if (! (dataset_core.kind === "local")) {
@ -409,12 +518,40 @@ namespace _zeitbild.repository.resource
} }
else { else {
return ( return (
get_local_resource_event_chest().write( get_local_resource_event_store().read(
[dataset_core["sub_id"], event_id], local_resource_event_id
{}
) )
.then( .then(
() => Promise.resolve(undefined) (row) => Promise.resolve(decode_local_resource_event(row))
)
.then(
(local_resource_event_stuff : type_local_resource_event_stuff) => Promise.resolve(
local_resource_event_stuff.event
)
)
);
}
}
/**
*/
export async function local_resource_event_create(
resource_id : _zeitbild.type_resource_id,
event_object : _zeitbild.type_event_object
) : Promise<_zeitbild.type_local_resource_event_id>
{
const dataset_core : Record<string, any> = await get_resource_core_store().read(resource_id);
if (! (dataset_core.kind === "local")) {
throw (new Error("not a local resource"));
}
else {
return get_local_resource_event_store().create(
encode_local_resource_event(
{
"local_resource_id": dataset_core["sub_id"],
"event": event_object,
}
) )
); );
} }
@ -425,7 +562,7 @@ namespace _zeitbild.repository.resource
*/ */
export async function local_resource_event_delete( export async function local_resource_event_delete(
resource_id : _zeitbild.type_resource_id, resource_id : _zeitbild.type_resource_id,
event_id : _zeitbild.type_event_id local_resource_event_id : _zeitbild.type_local_resource_event_id
) : Promise<void> ) : Promise<void>
{ {
const dataset_core : Record<string, any> = await get_resource_core_store().read(resource_id); const dataset_core : Record<string, any> = await get_resource_core_store().read(resource_id);
@ -434,8 +571,8 @@ namespace _zeitbild.repository.resource
} }
else { else {
return ( return (
get_local_resource_event_chest().delete( get_local_resource_event_store().delete(
[dataset_core["sub_id"], event_id] local_resource_event_id
) )
.then( .then(
() => Promise.resolve(undefined) () => Promise.resolve(undefined)

View file

@ -69,22 +69,94 @@ namespace _zeitbild.service.calendar
} }
/**
* checks if a user has a sufficient access level
*/
function wrap_check_access_level<type_result>(
calendar_object : _zeitbild.type_calendar_object,
user_id : _zeitbild.type_user_id,
threshold : _zeitbild.enum_access_level,
success_handler : (
(access_level : _zeitbild.enum_access_level)
=>
Promise<type_result>
)
) : Promise<type_result>
{
const access_level : _zeitbild.enum_access_level = calendar_object.access.attributed.get(
user_id,
lib_plankton.pod.make_filled<_zeitbild.enum_access_level>(
calendar_object.access.default_level
)
);
if (! _zeitbild.value_object.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),
}
)
)
);
}
else {
return success_handler(access_level);
}
}
/** /**
*/ */
export async function event_add( export async function event_add(
calendar_id : _zeitbild.type_calendar_id, calendar_id : _zeitbild.type_calendar_id,
event_object : _zeitbild.type_event_object event_object : _zeitbild.type_event_object,
user_id : _zeitbild.type_user_id
) : Promise<void> ) : Promise<void>
{ {
const calendar_object : _zeitbild.type_calendar_object = await _zeitbild.repository.calendar.read( const calendar_object : _zeitbild.type_calendar_object = await _zeitbild.repository.calendar.read(
calendar_id calendar_id
); );
const event_id : _zeitbild.type_event_id = await _zeitbild.repository.local_resource_event.create( return wrap_check_access_level<void>(
event_object calendar_object,
user_id,
_zeitbild.enum_access_level.edit,
async () => {
/*const event_id : _zeitbild.type_local_resource_event_id = */await _zeitbild.service.resource.event_add(
calendar_object.resource_id,
event_object
);
return Promise.resolve<void>(undefined);
}
); );
return _zeitbild.repository.resource.local_resource_event_add( }
calendar_object.resource_id,
event_id
/**
* @todo check access level
*/
export async function event_remove(
calendar_id : _zeitbild.type_calendar_id,
local_resource_event_id : _zeitbild.type_local_resource_event_id,
user_id : _zeitbild.type_user_id
) : Promise<void>
{
const calendar_object : _zeitbild.type_calendar_object = await _zeitbild.repository.calendar.read(
calendar_id
);
return wrap_check_access_level<void>(
calendar_object,
user_id,
_zeitbild.enum_access_level.edit,
async () => {
await _zeitbild.service.resource.event_remove(
calendar_object.resource_id,
local_resource_event_id
);
return Promise.resolve<void>(undefined);
}
); );
} }
@ -94,7 +166,8 @@ namespace _zeitbild.service.calendar
async function get_events( async function get_events(
calendar_id : _zeitbild.type_calendar_id, calendar_id : _zeitbild.type_calendar_id,
from_pit : _zeitbild.helpers.type_pit, from_pit : _zeitbild.helpers.type_pit,
to_pit : _zeitbild.helpers.type_pit to_pit : _zeitbild.helpers.type_pit,
user_id : _zeitbild.type_user_id
) : Promise< ) : Promise<
Array< Array<
_zeitbild.type_event_object _zeitbild.type_event_object
@ -102,127 +175,139 @@ namespace _zeitbild.service.calendar
> >
{ {
const calendar_object : _zeitbild.type_calendar_object = await _zeitbild.repository.calendar.read(calendar_id); const calendar_object : _zeitbild.type_calendar_object = await _zeitbild.repository.calendar.read(calendar_id);
const resource_object : _zeitbild.type_resource_object = await _zeitbild.repository.resource.read(calendar_object.resource_id); return wrap_check_access_level<Array<_zeitbild.type_event_object>>(
switch (resource_object.kind) { calendar_object,
case "local": { user_id,
return ( _zeitbild.enum_access_level.view,
Promise.all( async () => {
resource_object.data.event_ids const resource_object : _zeitbild.type_resource_object = await _zeitbild.repository.resource.read(calendar_object.resource_id);
.map( switch (resource_object.kind) {
(event_id) => _zeitbild.repository.local_resource_event.read(event_id) case "local": {
) return (
) Promise.all(
.then( resource_object.data.event_ids
(events) => Promise.resolve( .map(
events (event_id) => _zeitbild.repository.resource.local_resource_event_read(
calendar_object.resource_id,
event_id
)
)
)
.then(
(events) => Promise.resolve(
events
.filter(
(event : _zeitbild.type_event_object) => _zeitbild.helpers.pit_is_between(
_zeitbild.helpers.pit_from_datetime(event.begin),
from_pit,
to_pit
)
)
)
)
);
break;
}
case "caldav": {
// TODO readonly
const url : lib_plankton.url.type_url = lib_plankton.url.decode(
resource_object.data.url
);
const http_request : lib_plankton.http.type_request = {
"version": "HTTP/2",
"scheme": ((url.scheme === "https") ? "https" : "http"),
"host": url.host,
"path": (url.path ?? "/"),
"query": url.query,
"method": lib_plankton.http.enum_method.get,
"headers": {},
"body": null,
};
// TODO: cache?
const http_response : lib_plankton.http.type_response = await lib_plankton.http.call(
http_request,
{
}
);
const vcalendar : lib_plankton.ical.type_vcalendar = lib_plankton.ical.ics_decode(
http_response.body.toString(),
{
}
);
return Promise.resolve(
vcalendar.vevents
.map(
(vevent : lib_plankton.ical.type_vevent) => (
(vevent.dtstart !== undefined)
?
{
"name": (
(vevent.summary !== undefined)
?
vevent.summary
:
"???"
),
"begin": _zeitbild.helpers.ical_dt_to_own_datetime(vevent.dtstart),
"end": (
(vevent.dtend !== undefined)
?
_zeitbild.helpers.ical_dt_to_own_datetime(vevent.dtend)
:
null
),
"location": (
(vevent.location !== undefined)
?
vevent.location
:
null
),
"description": (
(vevent.description !== undefined)
?
vevent.description
:
null
),
}
:
null
)
)
.filter( .filter(
(event : _zeitbild.type_event_object) => _zeitbild.helpers.pit_is_between( (event) => (event !== null)
)
.filter(
(event) => _zeitbild.helpers.pit_is_between(
_zeitbild.helpers.pit_from_datetime(event.begin), _zeitbild.helpers.pit_from_datetime(event.begin),
from_pit, from_pit,
to_pit to_pit
) )
) )
) );
) break;
);
break;
}
case "caldav": {
// TODO readonly
const url : lib_plankton.url.type_url = lib_plankton.url.decode(
resource_object.data.url
);
const http_request : lib_plankton.http.type_request = {
"version": "HTTP/2",
"scheme": ((url.scheme === "https") ? "https" : "http"),
"host": url.host,
"path": (url.path ?? "/"),
"query": url.query,
"method": lib_plankton.http.enum_method.get,
"headers": {},
"body": null,
};
// TODO: cache?
const http_response : lib_plankton.http.type_response = await lib_plankton.http.call(
http_request,
{
} }
); default: {
const vcalendar : lib_plankton.ical.type_vcalendar = lib_plankton.ical.ics_decode( return Promise.reject(
http_response.body.toString(), new Error("invalid resource kind: " + resource_object["kind"])
{ );
break;
} }
); }
return Promise.resolve(
vcalendar.vevents
.map(
(vevent : lib_plankton.ical.type_vevent) => (
(vevent.dtstart !== undefined)
?
{
"name": (
(vevent.summary !== undefined)
?
vevent.summary
:
"???"
),
"begin": _zeitbild.helpers.ical_dt_to_own_datetime(vevent.dtstart),
"end": (
(vevent.dtend !== undefined)
?
_zeitbild.helpers.ical_dt_to_own_datetime(vevent.dtend)
:
null
),
"location": (
(vevent.location !== undefined)
?
vevent.location
:
null
),
"description": (
(vevent.description !== undefined)
?
vevent.description
:
null
),
}
:
null
)
)
.filter(
(event) => (event !== null)
)
.filter(
(event) => _zeitbild.helpers.pit_is_between(
_zeitbild.helpers.pit_from_datetime(event.begin),
from_pit,
to_pit
)
)
);
break;
} }
default: { );
return Promise.reject(
new Error("invalid resource kind: " + resource_object["kind"])
);
break;
}
}
} }
/** /**
* @todo check access level
*/ */
export async function gather_events( export async function gather_events(
calendar_ids : Array<_zeitbild.type_calendar_id>, calendar_ids_wanted : (null | Array<_zeitbild.type_calendar_id>),
from_pit : _zeitbild.helpers.type_pit, from_pit : _zeitbild.helpers.type_pit,
to_pit : _zeitbild.helpers.type_pit to_pit : _zeitbild.helpers.type_pit,
user_id : _zeitbild.type_user_id
) : Promise< ) : Promise<
Array< Array<
{ {
@ -233,6 +318,26 @@ namespace _zeitbild.service.calendar
> >
> >
{ {
const calendar_ids_allowed : Array<_zeitbild.type_calendar_id> = (
(await overview(user_id))
.map((x : any) => x.id)
);
// use set intersection
const calendar_ids : Array<_zeitbild.type_calendar_id> = (
(
(calendar_ids_wanted === null)
?
calendar_ids_allowed
:
(
calendar_ids_wanted
.filter(
(calendar_id) => calendar_ids_allowed.includes(calendar_id)
)
)
)
);
calendar_ids.sort();
return ( return (
Promise.all( Promise.all(
calendar_ids calendar_ids
@ -244,7 +349,8 @@ namespace _zeitbild.service.calendar
const events : Array<_zeitbild.type_event_object> = await get_events( const events : Array<_zeitbild.type_event_object> = await get_events(
calendar_id, calendar_id,
from_pit, from_pit,
to_pit to_pit,
user_id
); );
return Promise.resolve( return Promise.resolve(
events events

View file

@ -17,21 +17,18 @@ namespace _zeitbild.service.resource
export async function event_add( export async function event_add(
resource_id : _zeitbild.type_resource_id, resource_id : _zeitbild.type_resource_id,
event_object : _zeitbild.type_event_object event_object : _zeitbild.type_event_object
) : Promise<void> ) : Promise<_zeitbild.type_local_resource_event_id>
{ {
const resource_object : _zeitbild.type_resource_object = await _zeitbild.repository.resource.read( const resource_object : _zeitbild.type_resource_object = await _zeitbild.repository.resource.read(
resource_id resource_id
); );
switch (resource_object.kind) { switch (resource_object.kind) {
case "local": { case "local": {
const event_id : _zeitbild.type_event_id = await _zeitbild.repository.local_resource_event.create( const local_resource_event_id : _zeitbild.type_local_resource_event_id = await _zeitbild.repository.resource.local_resource_event_create(
resource_id,
event_object event_object
); );
await _zeitbild.repository.resource.local_resource_event_add( return Promise.resolve<_zeitbild.type_local_resource_event_id>(local_resource_event_id);
resource_id,
event_id
);
return Promise.resolve<void>(undefined);
break; break;
} }
case "caldav": { case "caldav": {
@ -56,7 +53,7 @@ namespace _zeitbild.service.resource
*/ */
export async function event_remove( export async function event_remove(
resource_id : _zeitbild.type_resource_id, resource_id : _zeitbild.type_resource_id,
event_object : _zeitbild.type_event_object local_resource_event_id : _zeitbild.type_local_resource_event_id
) : Promise<void> ) : Promise<void>
{ {
const resource_object : _zeitbild.type_resource_object = await _zeitbild.repository.resource.read( const resource_object : _zeitbild.type_resource_object = await _zeitbild.repository.resource.read(
@ -64,12 +61,9 @@ namespace _zeitbild.service.resource
); );
switch (resource_object.kind) { switch (resource_object.kind) {
case "local": { case "local": {
const event_id : _zeitbild.type_event_id = await _zeitbild.repository.local_resource_event.create(
event_object
);
await _zeitbild.repository.resource.local_resource_event_delete( await _zeitbild.repository.resource.local_resource_event_delete(
resource_id, resource_id,
event_id local_resource_event_id
); );
return Promise.resolve<void>(undefined); return Promise.resolve<void>(undefined);
break; break;

View file

@ -31,11 +31,6 @@ namespace _zeitbild
}; };
/**
*/
export type type_event_id = int;
/** /**
*/ */
export type type_event_object = { export type type_event_object = {
@ -59,6 +54,11 @@ namespace _zeitbild
}; };
/**
*/
export type type_local_resource_event_id = int;
/** /**
*/ */
export type type_resource_id = int; export type type_resource_id = int;
@ -70,7 +70,9 @@ namespace _zeitbild
{ {
kind : "local"; kind : "local";
data : { data : {
event_ids : Array<type_event_id>; event_ids : Array<
type_local_resource_event_id
>;
}; };
} }
| |

View file

@ -43,7 +43,6 @@ ${dir_temp}/zeitbild-unlinked.js: \
${dir_source}/value_objects/access_level.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/local_resource_event.ts \
${dir_source}/repositories/resource.ts \ ${dir_source}/repositories/resource.ts \
${dir_source}/repositories/calendar.ts \ ${dir_source}/repositories/calendar.ts \
${dir_source}/services/auth_internal.ts \ ${dir_source}/services/auth_internal.ts \
@ -51,6 +50,7 @@ ${dir_temp}/zeitbild-unlinked.js: \
${dir_source}/services/resource.ts \ ${dir_source}/services/resource.ts \
${dir_source}/services/calendar.ts \ ${dir_source}/services/calendar.ts \
${dir_source}/api/base.ts \ ${dir_source}/api/base.ts \
${dir_source}/api/transformations/datetime.ts \
${dir_source}/api/actions/meta_ping.ts \ ${dir_source}/api/actions/meta_ping.ts \
${dir_source}/api/actions/meta_spec.ts \ ${dir_source}/api/actions/meta_spec.ts \
${dir_source}/api/actions/session_prepare.ts \ ${dir_source}/api/actions/session_prepare.ts \
@ -60,6 +60,7 @@ ${dir_temp}/zeitbild-unlinked.js: \
${dir_source}/api/actions/calendar_list.ts \ ${dir_source}/api/actions/calendar_list.ts \
${dir_source}/api/actions/calendar_add.ts \ ${dir_source}/api/actions/calendar_add.ts \
${dir_source}/api/actions/calendar_event_add.ts \ ${dir_source}/api/actions/calendar_event_add.ts \
${dir_source}/api/actions/calendar_event_remove.ts \
${dir_source}/api/actions/events.ts \ ${dir_source}/api/actions/events.ts \
${dir_source}/api/functions.ts \ ${dir_source}/api/functions.ts \
${dir_source}/main.ts ${dir_source}/main.ts