diff --git a/.gitignore b/.gitignore index 637d68eb2..f3b824406 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/docs/interfaces/action-service.txt b/docs/interfaces/action-service.txt new file mode 100644 index 000000000..3bb70176a --- /dev/null +++ b/docs/interfaces/action-service.txt @@ -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)[] diff --git a/docs/interfaces/datastore-service.txt b/docs/interfaces/datastore-service.txt new file mode 100644 index 000000000..c0ee9be3b --- /dev/null +++ b/docs/interfaces/datastore-service.txt @@ -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: { + : Object + }; + user_id: number; + locked_fields: { + : Position; + : Position; + } +} + +Interface CreateEvent { + fqid: Fqid; + data: { + : Value; + } +} + +Interface UpdateEvent { + fqfields: { + : 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; + +/** + * 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; + +/** + * 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[]; + +/** + * Analog zu `getMany`, gibt jedoch auch gelöschte Instanzen zurück. + * + * @throws ModelDoesNotExist + */ +getManyWithDeleted(collection: Collection, ids: Id[], position?: Position, mapped_fields?: Field[]): Partial[]; + +// 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[]; + +/** + * 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[]; + +/** + * Wie `getAll`, gibt jedoch nur gelöschte Modelle wieder. + * + * @throws MeetingIdNotSpecified() + */ +getAllOnlyDeleted(collection: Collection, meeting_id?: Id, position?: Position, mapped_fields?: Field[]): Partial[]; + +/** + * 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[] + +/** + * 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] 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 diff --git a/docs/interfaces/how-to.txt b/docs/interfaces/how-to.txt new file mode 100644 index 000000000..8c3b74c2c --- /dev/null +++ b/docs/interfaces/how-to.txt @@ -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 (: , ...); + 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: `` + 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 { ... } diff --git a/docs/modellierung.drawio b/docs/modellierung.drawio new file mode 100644 index 000000000..283559931 --- /dev/null +++ b/docs/modellierung.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/docs/models.txt b/docs/models.txt new file mode 100644 index 000000000..f85f38fb3 --- /dev/null +++ b/docs/models.txt @@ -0,0 +1,633 @@ +Types: + Nativ: null, string, number, JSON + Fqid: Strukturierter string + HTML: string mit HTML Inhalt + []: Array von Typ T + : 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_` do have +values for `` 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__ids: group[]; + personal_note__ids: personal_note[]; + projection__ids: projection[]; + supported_motion__ids: motion[]; + submitted_motion__ids: motion_submitter[]; + assignment_related_user__ids: assignment_related_user[]; + motion_vote__ids: motion_vote[]; + assignment_vote__ids: assignment_vote[]; + motion_voted_poll__ids: motion_poll[]; + assignment_voted_poll__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//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 `/resource/` +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_: 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[]; +}