Merge pull request #5178 from FinnStutzenstein/openslides4-dev

interface specs
This commit is contained in:
Finn Stutzenstein 2020-01-20 12:15:11 +01:00 committed by GitHub
commit 66e716bde1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 941 additions and 0 deletions

12
.gitignore vendored
View File

@ -5,3 +5,15 @@
*~
.DS_Store
.idea
# Old OS3 files and folders
.coverage
.mypy_cache
Compodoc
__pycache__
bower_components
client
make
openslides*
personal_data
tests

View File

@ -0,0 +1,27 @@
# Action Service Interface
// TODO: More Exceptions?
Exception ActionNotFound(name: string);
/**
* Executes multiple actions in the conext of the user given by the user_id.
*
* @throws ActionNotFound
*/
execute(actions: Action[], user_id: number): ExecuteResult;
interface Action {
name: string;
data: object;
}
interface ActionError {
error: string;
argumens: string[];
}
// Gives a detailed error, for all failed actions. For some actions, success
// data is returned (the object). If there is no success data, null is given
// for the action. The results index matches to the action index in the request.
Type ExecuteResult = (ActionError | object | null)[]

View File

@ -0,0 +1,200 @@
# Datastore Interface
Enum EventType {
Create,
Update,
DeleteField,
Delete,
Restore,
}
Exception ModelDoesNotExist(model: Fqid);
Exception ModelExist(model: Fqid);
Exception ModelNotDeleted(model: Fqid);
Exception ModelLocked(model: Fqid | CollectionField);
Exception InvalidFormat();
// TODO:
Exception MeetingIdNotSpecified();
## Writer
/**
* Writes Events into the datastore
*
* @throws ModelDoesNotExist
* @throws ModelExists
* @throws ModelLocked
* @throws InvalidFormat
* @throws ModelNotDeleted
*/
write(request: WriteRequest): void publishes BulkMetadataUpdate
Interface WriteRequest {
events: (CreateEvent | RestoreEvent | UpdateEvent | DeleteEvent)[];
information: {
<fqid>: Object
};
user_id: number;
locked_fields: {
<fqid>: Position;
<CollectionField>: Position;
}
}
Interface CreateEvent {
fqid: Fqid;
data: {
<field>: Value;
}
}
Interface UpdateEvent {
fqfields: {
<fqfield>: Value;
}
}
Interface RestoreEvent {
fqid: Fqid;
}
Interface DeleteEvent {
fqid: Fqid;
}
Event FieldUpdatedEvent on topic FieldUpdatedTopic {
created: Fqid[];
deleted: Fqid[];
updated: Fqfield[];
}
## Reader
/** Common Parameters:
* - position: Optionsl, if given reads the data to this position.
* - mapped_fields: List of fields, that should onl be present in the response.
* TODO:
* - meeting_id: Für Modelle außer Nutzer, Gremium, Veranstaltung und Config
* Ist eine Angabe verpflichtend. Dies beschränkt die Nutzung
* auf eine spezifische Veranstaltung.
*
* Alle Operationen fügen `meta:position` und `meta:deleted` an.
*/
/**
* Returns a model. Deleted models are not returned (and handled as a ModelDiesNotExit)
*
* @throws ModelDoesNotExist
*/
get(model: Fqid, position?: Position, mapped_fields?: fields[]): Partial<Model>;
/**
* Analogous to `get`, but also finds deleted models (see `meta_deleted` in the model)
*
* @throws ModelDoesNotExist
*/
getWithDeleted(model: Fqid, position?: Position, mapped_fields?: Field[]): Partial<Model>;
/**
* Gibt mehrere Modellinstanzen aus dem Eventstore zur aktuellen Position
* zurück. Gelöschte Instanzen werden nicht zurückgegeben.
*
* @throws ModelDoesNotExist
*/
getMany(collection: Collection, ids: Id[], position?: Position, mapped_fields?: Field[]): Partial<Model>[];
/**
* Analog zu `getMany`, gibt jedoch auch gelöschte Instanzen zurück.
*
* @throws ModelDoesNotExist
*/
getManyWithDeleted(collection: Collection, ids: Id[], position?: Position, mapped_fields?: Field[]): Partial<Model>[];
// Shortcuts for `filter*` without a filter
/**
* Gibt alle Modelle einer Collection zurück. Die Veranstaltungsid ist zwingend (s.o.)
* für einige Collections. Gibt keine gelöscheten Elemente aus.
*
* @throws MeetingIdNotSpecified()
*/
getAll(collection: Collection, meeting_id?: Id, position?: Position, mapped_fields?: Field[]): Partial<Model>[];
/**
* Wie `getAll`, gibt gelöschte und nicht-gelöschte Modelle wieder.
*
* @throws MeetingIdNotSpecified()
*/
getAllWithDeleted(collection: Collection, meeting_id?: Id, position?: Position, mapped_fields?: Field[]): Partial<Model>[];
/**
* Wie `getAll`, gibt jedoch nur gelöschte Modelle wieder.
*
* @throws MeetingIdNotSpecified()
*/
getAllOnlyDeleted(collection: Collection, meeting_id?: Id, position?: Position, mapped_fields?: Field[]): Partial<Model>[];
/**
* Gibt alle Modelle einer Collection (möglicherweise Veranstaltungsspezifisch)
* wieder, die dem filter-Ausdruck genügen. Für Filtermöglichkeiten: siehe unten.
*
* @throws MeetingIdNotSpecified()
*/
filter(collection: Collection, meeting_id?: Id, filter: Filter, position?: Position, mapped_fields?: Field[]): Partial<Model>[]
/**
* Siehe `filter`. Gibt zurück, ob mindestens ein Modell gefunden wurde.
*
* @throws MeetingIdNotSpecified()
*/
exists(collection: Collection, meeting_id?: Id, filter: Filter, position?: Position): {exists: boolean; position: Position;}
/**
* Siehe `filter`. Gibt die Anzahl an Modellen zurück.
*
* @throws MeetingIdNotSpecified()
*/
count(collection: Collection, meeting_id?: Id, filter: Filter, position?: Position): {count: number; position: Position;}
/**
* Führt einfache Min-Max-Aggregation über numerische Felder aus.
*
* @throws MeetingIdNotSpecified()
* @throws AggregationOperationInvalid(operand, value)
*/
aggregate(collection: Collection, meeting_id?: Id, filter: Filter, aggregate: Aggregate, position?: Position): Value
Type Filter = And | Or | Not | FilterOperator
/**
* Das eigentliche Filter-predikat. M[field] ist der Wert des Feldes des
* betrachteten Modells. Für alle Operatoren ist das
* Prädikat wahr, wenn `M[field] <op> value` wahr ist.
*/
Interface FilterOperator {
field: Field;
value: Value | null;
operator: '==' | '!=' | '<' | '>' | '>=' | '<=';
}
Interface Not {
not: Filter;
}
Interface And {
and: Filter[];
}
Interface Or {
or: Filter[];
}
Interface Aggregate {
field: Field;
type: 'min' | 'max';
}
/**
* Gibt n sequentielle Ids für die gegebene Collection zurück.
*/
getId(collection: Collection, n: number): Id

View File

@ -0,0 +1,68 @@
## How to specify an interface for a service?
There are many ways to describe, what a service should do. The main
characteristics (from an outside-view) are the provided functions. Each service
does have functions to execute with arguments and return types. Next to the
returnvalue, events can be published or errors be thrown. The way we want to
derscribe these interfaces is through a TypeScript-like interface definition.
The interface is the collection of all functions defined.
Example:
/**
* The Exception, if a model wasn't found. The missed id is given.
*/
Exception ObjectNotFound(id: Id);
/**
* Does something..
*
* @throws ObjectNotFound
do_it(data: MyRequest): void publishes MyEvent;
interface MyRequest {
id: Id;
text: string;
some_other_value: number;
}
Event MyEvent on topic MyEventTopic {
changed_text: string;
}
There are some common types defined here:
Type Position=number;
Type Id=number;
Type Fqid=string; // `collection/id`
Type Fqfield=string; // `collection/id/field`
Type Collection=string; // `collection`
Type CollectionField=string; // `collection/field`
Type Model=object;
Type Field=string;
Type Value=any;
The language is a bit modified:
1) Exceptions
Exceptions should be declared by giving the name and parameters:
# Exception <name>(<param1>: <type1>, ...);
A comment should state, why this exception can be thrown
2) Events in the function declaration
Each function takes some arguments and have a return type. Next to the
return type, some events can be published. With the `publishes` token, one
can give a comma seperated list of events, that can be generated.
# my_function(): void publishes Event1, Event2
Events should be specified as interfaces, but named `Events`. Also there is
the `on topic` token, on which topic the message is broadcasted.
# Event MyEvent on topic MyEventTopic { ... }
3) Functiondocumentation
Each function must have a description. Also it should tell, what exceptions
could be thrown.
4) Placeholders
If you need generic names, use placeholders: `<my_placeholder>`
E.g. if the event topic is specific for one id, the topic used may be
definied as this example:
# Event MyEvent on topic MyEventForUser<user_id> { ... }

1
docs/modellierung.drawio Normal file
View File

@ -0,0 +1 @@
<mxfile host="www.draw.io" modified="2020-01-20T11:13:06.980Z" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36" version="12.5.5" etag="lo63t2GCt8XfH-bbtAHo"><diagram id="SzigwTKClLag4ZUmXMWD" name="Page-1">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</diagram></mxfile>

633
docs/models.txt Normal file
View File

@ -0,0 +1,633 @@
Types:
Nativ: null, string, number, JSON
Fqid: Strukturierter string
HTML: string mit HTML Inhalt
<T>[]: Array von Typ T
<modellname>: number (ID) zu dem Modell
float: string mit iso-gemäßer Formatierung eines floats
decimal(X): Decimal values represented as a string with X decimal places
datetime: Datetime as a unix timestamp
// Why a number? This enables queries in the DB. And we do not
// need more precision than 1 second.
Note to structured fields: Fields with `some_field_<some_variable>` do have
values for `<some_variable>` that fits into the format of a field. E.g.
`amendment_paragraph_3`. Note that it might be possible that no kind of
this field exists in a model.
Interface projector {
id: number;
scale: number;
scroll: number;
name: string;
width: number;
aspect_ratio_numerator: number;
aspect_ratio_denominator: number;
color: string;
background_color: string;
header_background_color: string;
header_font_color: string;
header_h1_color: string;
chyron_background_color: string;
chyron_font_color: string;
show_header_footer: boolean;
show_title: boolean;
show_logo: boolean
element: projection;
element_fqid: Fqid;
elements_preview: projection[];
elements_history: projection[];
reference_projector_id: projector;
projectiondefault_ids: projectiondefault[];
meeting_id: meeting;
}
Interface projection {
id: number;
projector_id: projector;
projector_history_ids: projector;
projector_preview_id: projector;
element: Fqid;
options: JSON;
}
Interface projectiondefault {
id: number;
name: string;
display_name: string;
projector_id: projector;
meeting_id: meeting;
}
Interface tag {
id: number;
name: string;
tagged_ids: Fqid[];
meeting_id: meeting;
}
Interface projector_message {
id: number;
message: HTML;
projection_id: projection[];
meeting_id: meeting;
}
Interface projector_countdown {
id: number;
title: string;
description: string;
default_time: number;
countdown_time: number; // float?
running: boolean;
projection_id: projection[];
meeting_id: meeting;
}
Interface agenda_item {
id: number;
item_number: string;
comment: string,
closed: boolean,
type: number,
is_internal: boolean;
is_hidden: boolean;
duration: number; // in seconds
weight: number;
level: number; //für client nicht unbedingt nötig.
content_object_id: Fqid;
parent_id: agenda_item;
children_ids: agenda_item[];
projection_id: projection[];
meeting_id: meeting;
}
Interface list_of_speakers {
closed: boolean;
content_object_id: Fqid;
speaker_ids: speaker[];
projection_id: projection[];
meeting_id: meeting;
}
Interface speaker {
id: number;
begin_time: datetime;
end_time: datetime;
weight: number;
marked: boolean;
list_of_speakers_id: list_of_speakers;
user_id: user;
}
Interface topic {
id: number;
title: string;
text: HTML;
attachments_id: mediafile[];
agenda_item_id: agenda_item;
meeting_id: meeting;
}
Interface user {
id: number;
username: string;
last_email_send: string;
is_active: boolean; // TODO @emanuel: Ist ein Nutzer systemweit (organisationsweit) aktiv/inaktiv?
default_password: string;
is_committee: boolean;
about_me: HTML;
gender: string,
// TODO: auth
//auth_type: string;
// auth-spezifische felder?
title: string;
first_name: string;
last_name: string;
comment: HTML;
number: string;
email: string;
structure_level: string;
role_id: role; // Attention: prevent impelenting a "default-role" or let a
// user create such a role! This would cause the user_ids-array for this
// role to explode in size. If a user has no role, it should be handles as
// the user has no permission in the organisation and is a "normal" delegate
// there. Just a few users (expected <100) should even get a role and all
// other don't.
is_present_in_meeting_ids: meeting[];
meeting_id: meeting; // Temporary users
// All foreign keys are meeting-specific:
// - Keys are smaller (Space is in O(n^2) for n keys
// in the relation), so this saves storagespace
// - This makes quering things like this possible:
// "Give me all groups for User X in Meeting Y" without
// the need to get all groups and filter them for the meeting
group_<meeting_id>_ids: group[];
personal_note_<meeting_id>_ids: personal_note[];
projection_<meeting_id>_ids: projection[];
supported_motion_<meeting_id>_ids: motion[];
submitted_motion_<meeting_id>_ids: motion_submitter[];
assignment_related_user_<meeting_id>_ids: assignment_related_user[];
motion_vote_<meeting_id>_ids: motion_vote[];
assignment_vote_<meeting_id>_ids: assignment_vote[];
motion_voted_poll_<meeting_id>_ids: motion_poll[];
assignment_voted_poll_<meeting_id>_ids: assignment_poll[];
}
Interface group {
id: number;
name: string;
is_superadmin_group: boolean;
is_default_group: boolean;
permissions: string[];
user_ids: user[];
mediafile_access_group_ids: mediafile[];
read_comment_section_ids: motion_comment_section[];
write_comment_section_ids: motion_comment_section[];
motion_poll_ids: motion_poll[];
meeting_id: meeting;
}
Interface personal_note {
id: number;
note: HTML;
star: boolean;
user_id: user;
element: Fqid;
meeting_id: meeting;
}
// Mediafiles are delivered by the mediafile server with the URL
// `<media-prefix>/media/<meeting_id>/path`
Interface mediafile {
id: number;
title: string;
is_directory: boolean;
filesize: number; // Note: in bytes, not the human readable format anymore
filename: string; // Note: The uploaded filename. Filename and parent_id
// must be unique as well as title and parent_id must be unique.
mimetype: string;
create_timestamp: datetime;
path: string; // Note: calcuated
inherited_access_group_ids: boolean | number[]; // Note: calculated and no
// reverse-relation for the gorup ids.
access_group_ids: group[];
parent_id: mediafile;
children_ids: mediafile[];
list_of_speakers_id: list_of_speakers;
projection_ids: projection[];
attachement_ids: Fqid[];
meeting_id: meeting;
}
// New: Resource
// Resources are meeting-specific or organsation wide.
// For organisation-resources, no permission chacks are done (event the user
// must not be logged in). If a meeting-resource is requested, it is checked, if
// the user can see the meeting (Anonymous, guest, member).
// A resource has a token, e.g. `web_header` or `pdf_font_italic`, so the client
// knowns, where to put the resource. They are delivered by the mediafile server
// with the URL `<media-prefix>/resource/<id>`
Interface Resource {
id: number;
token: string;
filesize: number;
mimetype: string;
// Either the meeting is set, or the organsation.
meeting_id: meeting;
organisation_id: organisation;
}
Interface motion {
id: number;
identifier: string;
title: string;
text: HTML;
amendment_paragraph_<paragraph_number>: HTML;
modified_final_version: HTML;
reason: HTML;
category_weight: number;
state_extension: string;
recommendation_extension: string;
sort_parent_id: motion;
sort_weight: number;
created: string;
last_modified: string;
parent_id: motion;
children_ids: motion[];
origin_id: motion;
derived_motion_ids: motion[];
state_id: motion_state;
workflow_id: motion_workflow;
recommendation_id: motion_state;
category_id: category;
motion_block_id: motion_block;
submitter_ids: motion_submitter[];
supporter_ids: user[];
poll_ids: motion_poll[];
change_recommendation_ids: motion_change_recommendation[];
statute_paragraph_id: motion_statute_paragraph;
comment_ids: motion_comment[];
agenda_item_id: agenda_item;
list_of_speakers_id: list_of_speakers;
tag_ids: tag[];
attachment_ids: mediafile[];
meeting_id: meeting;
}
Interface motion_poll {
id: number;
pollmethod: string;
state: number;
type: string;
title: string;
onehundred_percent_base: string;
majority_method: string;
votesvalid: decimal(6);
votesinvalid: decimal(6);
votescast: decimal(6);
option_ids: motion_option[];
motion_id: motion;
voted_ids: user[];
group_ids: group[];
meeting_id: meeting;
}
Interface motion_option {
id: number;
yes: decimal(6);
no: decimal(6);
abstain: decimal(6);
vote_ids: motion_vote[];
}
Interface motion_vote {
id: number;
weight: decimal(6);
value: string;
option_id: motion_option;
user_id: user;
}
Interface motion_submitter {
id: number;
weight: number;
user_id: user;
motion_id: motion;
}
Interface motion_comment {
id: number;
comment: HTML;
motion_id: motion;
section_id: motion_comment_section;
}
Inteface motion_comment_section {
id: number;
name: string;
weight: number;
read_group_ids: group[];
write_group_ids: group[];
meeting_id: meeting;
}
Interface motion_category {
id: number;
name: string;
prefix: string;
weight: number;
level: number;
parent_id: motion_category;
children_ids: motion_category[];
motion_ids: motion[];
meeting_id: meeting;
}
Interface motion_block {
id: number;
title: string;
internal: boolean;
motion_ids: motion[];
agenda_item_id: agenda_item;
list_of_speakers_id: list_of_speakers;
meeting_id: meeting;
}
Interface motion_change_recommendation {
id: number;
rejected: boolean;
internal: boolean;
type: number;
other_description: string;
line_from: number;
line_to: number;
text: HTML;
creation_time: datetime;
motion_id: motion;
}
Interface motion_state {
id: number;
name: string;
recommendation_label: string;
css_class: string;
restrictions: string[],
allow_support: boolean;
allow_create_poll: boolean;
allow_submitter_edit: boolean;
set_identifier: boolean;
show_state_extension_field: boolean;
merge_amendment_into_final: number;
show_recommendation_extension_field: boolean;
next_state_ids: motion_state[];
previous_state_ids: motion_state[];
motion_ids: motion[];
motion_recommendation_ids: motion[];
workflow_id: motion_workflow;
first_state_of_workflow_id: motion_workflow;
}
Interface motion_workflow {
id: number;
name: string;
state_ids: motion_state[];
first_state_id: motion_state;
motion_ids: motion[];
meeting_id: meeting;
}
Interface motion_statute_paragraph {
id: number;
title: string;
text: HTML;
weight: number;
motion_ids: motion[];
meeting_id: meeting;
}
Interface assignment {
id: number;
title: string;
description: HTML;
open_posts: number;
phase: number;
poll_description_default: string;
assignment_related_user_ids: assignment_related_user[];
poll_ids: assignment_poll[];
agenda_item_id: agenda_item;
list_of_speakers_id: list_of_speakers;
tag_ids: tag[];
attachment_ids: mediafile[];
meeting_id: meeting;
}
Inteface assignment_related_user {
id: number;
elected: boolean;
weight: number;
assignment_id: assignment;
user_id: user;
}
Interface assignment_poll {
id: number;
allow_multiple_votes_per_candidate: boolean;
global_abstain: boolean;
global_no: boolean;
amount_global_abstain: decimal(6);
amount_global_no: decimal(6);
pollmethod: string;
state: number;
title: string;
description: string;
type: string;
onehundred_percent_base: string;
majority_method: string;
votes_amount: number;
votescast: decimal(6);
votesinvalid: decimal(6);
votesvalid: decimal(6);
assignment_id: assignment;
voted_ids: user[];
group_ids: group[];
option_ids: assignment_option[];
meeting_id: meeting;
}
Interface assignment_option {
id: number;
yes: decimal(6);
no: decimal(6);
abstain: decimal(6);
weight: number;
poll_id: assignment_poll;
user_id: user;
}
Interface assignment_vote {
id: number;
value: string;
weight: decimal(6);
option_id: assignment_option;
user_id: user;
}
// New models
Interface meeting {
id: number;
identifier: string; // For unique urls.
is_template: boolean; // Unique within a committee
enable_anonymous: boolean;
// Old "general_*" configs
name: string;
description: string;
location: string;
start_time: datetime;
end_time: datetime;
welcome_title: string;
welcome_text: HTML;
// Export section still needed?
// Or should this be moved into committee-scope or organisation-scope?
export_csv_encoding: string;
export_csv_separator: string;
export_pdf_pagenumber_alignment: string;
export pdf_fontsize: number;
export_pdf_pagesize: string;
// TODO: custom translations
// TODO: @emanuel: Are they meeting-specific, organsation-wide or should
// both have custom translation?
// Is this right here in a meeting?
users_sort_by: string;
users_enable_presence_view: boolean;
// Motions
motions_default_workflow: workflow; // TODO: relation
motions_default_statute_amendments_workflow: workflow; // TODO: relation
motions_preamble: string;
motions_default_line_numbering: string;
motions_line_length: number;
motions_reason_required: boolean;
motions_enable_text_on_projector: boolean;
motions_enable_reason_on_projector: boolean;
motions_enable_sidebox_on_projector: boolean;
motions_enable_resommendation_on_projector: boolean;
motions_show_referring_motions: boolean;
motions_show_sequential_number: boolean;
motions_recommendations_by: string;
motions_statute_recommendations_by: string;
motions_recommendation_text_mode: string;
motions_default_sorting: string;
motions_identifier_type: string;
motions_identifier_min_digits: number;
motions_identifier_with_blank: boolean;
motions_statutes_enabled: boolean;
motions_amendments_enabled: boolean;
motions_amendments_in_main_list: boolean;
motions_amendments_prefix: string;
motions_amendments_text_mode: string;
motions_amendments_multiple_paragraphs: boolean;
motions_supporters_min_amount: number;
motions_supporters_enable_autoremove: boolean;
motions_export_title: string;
motions_export_preamble: string;
motions_export_submitter_recommendation: boolean;
motions_export follow_recommendation: boolean;
// Assignments
assignments_poll_add_candidates_to_list_of_spekaers: boolean;
assignemnts_export_pdf_title: string;
assignments_export_pdf_preamble: string;
projector_ids: projector[];
projectiondefault_ids: projectiondefault[];
projector_countdown_ids: projector_countdown[];
projector_message_ids: projector_message[];
tag_ids: tag[];
agenda_item_ids: agenda_item[];
list_of_speakers_ids: list_of_speakers[];
topic_ids: topic[];
group_ids: group[];
personal_note_ids: personal_note[];
mediafile_ids: mediafile[];
motion_ids: motion[];
motion_poll_ids: motion_poll[];
motion_comment_section_ids: motion_comment_section[];
motion_category_ids: motion_category[];
motion_block_ids: motion_block[];
motion_workflow_ids: motion_workflow[];
motion_statute_paragraph_ids: motion_statute_paragraph[];
assignment_ids: assignment[];
assignment_poll_ids: assignment_poll[];
// No relations to a meeting:
// user; OK, because not meeting-specific
// projection
// speaker
// motion_option
// motion_vote
// motion_comment
// motion_submitter
// motion_change_recommendation
// motion_state
// assignment_related_user
// assignment_option
// assignment_vote
// Why: These are mostly M2M models or ones, that will never have a list view.
// There would be no need to link them to the meeting, if they can be
// reached through a parent model. E.g. a motion_option can be reached through
// a motion_poll. There will never be the need to make something like "show all
// motion_options at once".
// TODO: Is this right for all above stated models?
// TODO: We might link user to a meeting. A member is defined by checking,
// if a user is assigned to at least one group. This "query" must be checked,
// if it can be performed.
present_user_ids: user[]; // Link to user/is_present_in_meeting_ids
resource_ids: resource[];
}
Interface committee {
id: number;
name: string;
description: string; // TODO: @emanuel Or HTML? Or not needed (-> UI decision)
meeting_ids: meeting[];
member_ids: user[];
manager_ids: user[];
forward_to_committee_ids: committee[];
received_forwardings_from_committee_ids: committee[];
organisation_id: organisation;
}
Interface organisation {
id: number;
name: string;
description: HTML; // TODO: @emanuel Needed??
// Configs:
legal_notice: string;
privacy_policy: string;
login_text: string;
theme: string;
// TODO: OS3 user configs (export, email, ...)
committee_ids: committee[];
role_ids: role[];
resource_ids: resource[];
}
Interface role {
id: number;
name: string;
permissions: string[]; // 'can_manage_committees', 'can_manage_users', TODO: more permissions
is_superadmin_role: boolean;
orginasation_id: number;
user_ids: user[];
}