Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
d5841ef
create categories and ordering
Metauriel Feb 17, 2026
b9ae01f
first outline
Metauriel Feb 17, 2026
5dcc23d
room documentation
Metauriel Feb 23, 2026
201917a
begin board documentation
Metauriel Mar 9, 2026
5aa3c69
futher work on board, split into multiple files
Metauriel Mar 16, 2026
2bc3dfe
authorization
Metauriel Mar 27, 2026
7973a8a
adjust parent documentation to new implemenation changes
Metauriel Apr 1, 2026
b929df9
add details about persistance and websockets
Metauriel Apr 2, 2026
b128fcd
BC-11268 - Add Documentation for Across Module Operations - Sagas and…
uidp Feb 20, 2026
c90acad
BC-11270 - bettermarks (#94)
virgilchiriac Feb 23, 2026
7179e27
BC-11178 - check for cve's and update deps (#93)
luejoh Feb 23, 2026
85fb346
Bettermark using dev environments (#95)
CeEv Mar 24, 2026
1720e0b
Merge branch 'main' into tf/room-and-board-documentation
Metauriel Apr 2, 2026
90d7715
fix numbering
Metauriel Apr 2, 2026
91ce3f7
fix typos
Metauriel Apr 2, 2026
95e3fd0
attempt fix links
Metauriel Apr 2, 2026
90f3d41
fix more typos
Metauriel Apr 2, 2026
1cadb8e
remove broken links
Metauriel Apr 2, 2026
6cb841c
fix some links
Metauriel Apr 2, 2026
dda2514
fix typos
Metauriel Apr 2, 2026
25b55e6
review comments
Metauriel Apr 2, 2026
c3dc3d1
more review comments
Metauriel Apr 2, 2026
c28a2fd
fix typos
Metauriel Apr 2, 2026
da3b83f
reference materialized paths
Metauriel Apr 2, 2026
68a7fb4
minor adjustments
Metauriel Apr 2, 2026
8607de1
more review comments
Metauriel Apr 2, 2026
cc876dd
minor adjustment
Metauriel Apr 2, 2026
a95ade4
some additional empty lines
Metauriel Apr 2, 2026
0f67ebb
more details for viewer and applicant
Metauriel Apr 2, 2026
eef978d
explain teacher visibility better
Metauriel Apr 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/topics/board/_category_.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"label": "Boards",
"position": 6,
"link": {
"type": "generated-index",
"description": "Learn about the Boards."
}
}
23 changes: 23 additions & 0 deletions docs/topics/board/column-board.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# The Column Board

The column board is the primary use case for the board, and the central way to create content in the SVS.

## Structure

The `ColumnBoard` is structured in three layers. It contains a number of `Columns`, each containing a number of `Cards`. Each of the cards can contain various `Elements`, which are the different forms of content, like `RichTextElement`, `LinkElement`, `FileElement` and so forth.

The `ColumnBoard` can be either rendered as such with the columns next to each other, or as a `ListBoard` with the Columns acting as sections rendered below each other.

## Elements

The `ColumnBoard` supports a wide and expanding list of element types, including:

- `RichTextElement` for both plain and rich text, based on the CKEditor 5.
- `LinkElement` for links to any website, with a cached preview image for privacy reasons.
- `FileElement` for storing files using our [FileStorage](../files-storage/How%20it%20works.md).
- `FileFolderElement` for storing a large number of files at once.
- `VideoConferenceElement` for starting video conferences using Big Blue Button
- `DrawingElement` for collaborative Whiteboards using tldraw
- `CollaborativeTextEditorElement` for using Etherpad
- `ExternalToolElement` for including LTI Tools
- `H5pElement` for including h5p content
23 changes: 23 additions & 0 deletions docs/topics/board/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Overview

## Introduction

The "Board" (dubbed "Bereiche" in German) is a module for collaboratively editing structured content, intended to be used for preparing, teaching, and following up on lessons in a school context.

It provides a flexible data structure which can be reused for different usecases. At the time of writing it is used by the ColumnBoard and the MediaBoard.

The Board supports a variety of content elements, including rich text, files, links, and interactive elements, as well as structural elements like columns and cards, that can be freely rearranged via drag&drop.

It is fully collaborative with live editing via websockets and adapts to different authorization contexts.

## Status in Q1/2026

The board in its original intention is incomplete. Note that this section is subject to change and may be out of date.

- Most notably missing is a "tasks" element, that would enable each viewer of the board to independently submit a solution to a challenge, to implement both homeworks and tasks during lessons
- We had planned refactorings towards a "trait" based system based on composable features, that would further separate the generic "board" implementation from the specific Nodetypes and Elements and their particular features.
- We had then planned a refactoring that would enable us to make (new) Element implementations external to the Board Module, providing full extendability without having to make changes to the core Boards implementation
- We had planned a number of technical features to further improve stability and conflict resolution when used by many people at once, most notably fractional indexing
- We had planned a number of performance optimizations, in particular in regards to authorisation and operations that only require small subsets of the board.
- We had planned a feature that would allow efficient retrieval and rendering of specific content from across multiple boards. Most notably, this would drive the "Task Overview", by showing a user all of his open tasks across all of his boards.
- We had planned a feature to configure access to parts of a board, like columns or cards, for example making them invisible to viewers, or enabling single users to edit them.
96 changes: 96 additions & 0 deletions docs/topics/board/technical-details.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Technical Details

## Data Structure

Each board is made of a tree of "BoardNodes", with a single Root.
Each Node is of a particular Type, which defines its structural and functional features. Most notably, each Nodetype defines what other Nodes can be its children, creating a fixed but easily changeable structure.

```typescript
export class Column extends BoardNode<ColumnProps> {

[...]

canHaveChild(childNode: AnyBoardNode): boolean {
return childNode instanceof Card;
}
}
```

We separate between structural Nodes (eg. `ColumnBoard`, `Column`, `Card`) and the actual content that can be put on a card, colloquially called *elements* (eg. `RichTextElement`, `FileElement`, `LinkElement`, `H5pElement`), which are leaves of the tree.

## Contexts

Each board has a single context it belongs into. The context provides part of the Configuration of the board (enabling and disabling features) and provides the Users Roles for Authorization.

The most important type of context is the [Room](../rooms/overview.md), but other potential parents include Courses and single Users for personal Boards.

The exact type of the context is abstracted for the board and replaced with a common interface.

The context is resolved through the `BoardContextResolverService`, which provides
a `PreparedBoardContext` containing the boardRoles of the users, as well as the configuration of a board.

## Authorization

Central to the authorization in the board is the `BoardNodeAuthorizable`, which can be built through the `boardNodeAuthorizableService`.
It is constructed with a specific user in mind, and contains all information required for the authorization including the users permissions on that specific board, and the settings of the board.

The `BoardNodeRule` can operate in two different ways. First, it implements our [rules interface](../authorization/concept.md), which allows any external services to determine basic read and write permissions for any boardNodes. This is for example used by the [fileStorage](../files-storage/How%20it%20works.md) and other microservices to authorize access to external resources that belong to the board or one of its nodes.

Secondly, we use an extended interface within the boardModule that allows checking permissions for specific operations on nodes.

```typescript
const boardNodeAuthorizable = await this.boardNodeAuthorizableService.getBoardAuthorizable(board);

throwForbiddenIfFalse(this.boardNodeRule.can('findBoard', user, boardNodeAuthorizable));
```

The rule then contains the logic to authorize each operation against the boardNodeAuthorizable, and specifically what permissions are required for which operation.

The rule can also provide the list of operations a specific user is authorized for on the board. This list is available to the frontend and used to show the user which operations he can perform.

## Websockets

The Websocket interface is implemented using [NestJS Gateways](https://docs.nestjs.com/websockets/gateways), with [Socket.IO](https://socket.io/) underneath.

```typescript
@SubscribeMessage('update-board-title-request')
@EnsureRequestContext()
public async updateBoardTitle(socket: Socket, data: UpdateBoardTitleMessageParams): Promise<void> {
const emitter = this.buildBoardSocketEmitter({ socket, action: 'update-board-title' });
const { userId } = this.getCurrentUser(socket);
try {
const board = await this.boardUc.updateBoardTitle(userId, data.boardId, data.newTitle);
emitter.emitToClientAndRoom(data, board);
} catch {
emitter.emitFailure(data);
}
}
```

Each operation on the board can be triggered with a message of the form `${action}-request`, like in the example `update-board-title-request`.

The response is given with `${action}-success` or `${action}-failure`.

Success messages are sent to all clients that are connected to the same board. To ensure this even when the clients are connected to different server instances, we use a MongoDB IO Adapter to synchronize messages.

The failure messages are only sent to the client that triggered them.

## Persistence Layer

All BoardNodes, no matter their type, are stored in a single collection through a single entity.

Each `BoardNodeEntity` represents a single node of the tree, and stores the entire path of its ancestors as a [Materialized Path](https://www.mongodb.com/docs/manual/tutorial/model-tree-structures-with-materialized-paths/), as well as its own level within in tree and its position among its siblings. This structure allows for efficient retrieval both of the chain of ancestors for a specific node, as well as all descendants of a node (by searching for an id within the paths.)

The `BoardNodeEntity` can store all properties of all types of BoardNode. When the node is loaded and the DO is constructed by the repo the data is also validated to ensure it matches its corresponding type.

## Loading

Since Boards can contain a large amount of Content, it is designed to be loaded in multiple stages.

At first, a `BoardSkeleton` is loaded, which contains only the structural nodes, ie. columns and cards. Each card stores its approximate height, allowing the frontend to render a loading stage where each card can be represented, without any content jumping around too much as the loading progresses. The cards themselves are loaded in batches as a second step

Some Content Elements, like images, require data from a different source to be rendered, which is loaded separately.

This spreading of data allows the board to render quickly, without having to wait for all downloads to complete. This is especially relevant for users in locations with slow connections and low bandwidth, as is the case in some schools.

Utilizing a Peer to Peer approach to relieve bandwidth is intended, but not yet implemented.
2 changes: 1 addition & 1 deletion docs/topics/collabora/_category_.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"label": "Collabora",
"position": 6,
"position": 7,
"link": {
"type": "generated-index",
"description": "Learn about the Collabora service."
Expand Down
2 changes: 1 addition & 1 deletion docs/topics/common-cartridge/_category_.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"label": "Common Cartridge",
"position": 7,
"position": 8,
"link": {
"type": "generated-index",
"description": "Learn about the Common Cartridge service."
Expand Down
2 changes: 1 addition & 1 deletion docs/topics/etherpad/_category_.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"label": "Etherpad",
"position": 8,
"position": 9,
"link": {
"type": "generated-index",
"description": "Learn about the etherpad repo."
Expand Down
2 changes: 1 addition & 1 deletion docs/topics/files-storage/_category_.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"label": "Files Storage",
"position": 9,
"position": 10,
"link": {
"type": "generated-index",
"description": "Learn about the files-storage repo."
Expand Down
2 changes: 1 addition & 1 deletion docs/topics/h5p/_category_.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"label": "H5P",
"position": 10,
"position": 11,
"link": {
"type": "generated-index",
"description": "Learn about the H5P services & modules."
Expand Down
2 changes: 1 addition & 1 deletion docs/topics/ldap/_category_.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"label": "LDAP",
"position": 11,
"position": 12,
"link": {
"type": "generated-index",
"description": "Learn about the LDAP strategy."
Expand Down
2 changes: 1 addition & 1 deletion docs/topics/moin-punkt-schule/_category_.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"label": "Moin.Schule",
"position": 12,
"position": 13,
"link": {
"type": "generated-index",
"description": "Learn about the moin.schule service."
Expand Down
2 changes: 1 addition & 1 deletion docs/topics/oauth/_category_.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"label": "OAuth",
"position": 13,
"position": 14,
"link": {
"type": "generated-index",
"description": "Learn about the OAuth strategy."
Expand Down
2 changes: 1 addition & 1 deletion docs/topics/provisioning/_category_.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"label": "Provisioning",
"position": 14,
"position": 15,
"link": {
"type": "generated-index",
"description": "Learn about the provisioning service."
Expand Down
8 changes: 8 additions & 0 deletions docs/topics/rooms/_category_.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"label": "Rooms",
"position": 16,
"link": {
"type": "generated-index",
"description": "Learn about the Rooms."
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
79 changes: 79 additions & 0 deletions docs/topics/rooms/overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Overview

## Introduction

Rooms are digital spaces where people can interact and collaborate, designed both for teachers and students during and outside of lessons, and for collaboration between Teachers.

Each room has a number of members in various roles and content in the form of collaborative columnboards, as well as some configuration that governs what features are available, how these features work, and what roles can interact with them.

## Modules

![Module Structure](./img/rooms-structure.excalidraw.png)

There are five Modules working together in rooms.

- The Rooms-API is the entry point for all requests regarding the Room (note that the boards have their own API). Read more about [API Modules](../../backend-design-patterns/architecture.md#api-modules) for more information.

- The Room module is responsible for the Room object itself. It contains the configuration of the room, and references to all content that belongs to the room.

- The Room-Membership module is essentially a bridge between `Groups` and `Rooms`. It utilizes the `Groups` module to store the users of a room and their roles, while containing any user related logic specific to rooms. It's responsible to construct the authorizables, which play a major part in the authorization checks for the room.

- The [Boards Module](../board/introduction.md) is used to implement the content of the room.

- The Groups module provides an abstraction to store groups of users with their context-specific roles. It is used to store which roles a user has in what room.

## Roles

The Room uses its own roles, that can be assigned independently of school roles. Note however that there might be business restrictions on who can have which role based on their school role, such as students never being owner of a room.

The roles follow a linear hierarchy, meaning a higher role can do anything a lower role is able to do. The roles as defined by the requirements are as follows:

### Room Owner

There can always be only one owner. The owner has to be a teacher, ensuring that there is at least one teacher in the room for supervision.

The owner has all permissions within the room, but can not leave without passing ownership to another teacher first.

Should a room ever be without owner, no other user is allowed to access the room, until a school administrator has assigned a new owner.

### Room Admin

The admin can add and manage the users in the room and the settings of the room.

To add students from another school to a room, it is necessary to grant a teacher from that school the Room Admin role, so that he can invite students from his school.

### Room Editor

The editor is someone who is responsible for and can edit all content in the room.

In a school context, a teaching person should have at least the editor role.

### Room Viewer

The viewer is a consumer of the room. That generally means he can't edit content within the room, though some content may allow viewers to do so.

For example, a board can be configured to allow room viewers to edit.

In a school context, this is the role a student is intended to have.

### Room Applicant

When a user uses a room invitation link that requires confirmation, he is added as a room applicant.

he is not yet a member of the room, but needs to be confirmed by an admin.

## Cross-School Operation

Each room is part of exactly one school. It is however possible to add users from other schools to a room, should that be necessary.

When a user from another school is added to a room, he is also added as a *guest* to the school. Only Users with any role on the room's school, including guests, may access a given room.

A user is only allowed to add another user when he is either in the same school as that user or if that user is a publicly visible teacher (based on the visibility settings of the instance and the user).

Let's consider an example where `Teacher A` is owner of `Room A` at `School A`, and needs to add Users from `School B`.

`Teacher A` is not allowed to see or add `Student B` from the other school. He can however add `Teacher B` from the other school (if that teacher is publicly visible), and give him the Room Admin role.

`Teacher B` can then add more users, including `Student B` from his own school to the Room.

Once in a Room, users can interact with the content of the room no matter their original school.
2 changes: 1 addition & 1 deletion docs/topics/schulcloud-client/_category_.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"label": "Schulcloud Client",
"position": 15,
"position": 17,
"link": {
"type": "generated-index",
"description": "Learn about the schulcloud-client repo."
Expand Down
2 changes: 1 addition & 1 deletion docs/topics/schulcloud-server/_category_.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"label": "Schulcloud Server",
"position": 16,
"position": 18,
"link": {
"type": "generated-index",
"description": "Learn about the schulcloud-server repo."
Expand Down
2 changes: 1 addition & 1 deletion docs/topics/tldraw-server/_category_.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"label": "Tldraw Server",
"position": 17,
"position": 19,
"link": {
"type": "generated-index",
"description": "Learn about the tldraw-server repo."
Expand Down
2 changes: 1 addition & 1 deletion docs/topics/tools/_category_.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"label": "Tools",
"position": 18,
"position": 20,
"link": {
"type": "generated-index",
"description": "Learn about tools."
Expand Down
2 changes: 1 addition & 1 deletion docs/topics/tsp-sync/_category_.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"label": "TSP Synchronisation",
"position": 19,
"position": 21,
"link": {
"type": "generated-index",
"description": "Learn about the Synchronisation of Userdata from the 'Thüringer Schulportal'"
Expand Down
2 changes: 1 addition & 1 deletion docs/topics/vue-client/_category_.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"label": "Vue Client",
"position": 20,
"position": 22,
"link": {
"type": "generated-index",
"description": "Learn about the vue-client repo."
Expand Down
Loading