# Developer Documentation

Learn how to set up your development environment.

# Repositories & CI/CD pipeline

This application consists of one main server and one or many rendering servers. For shared types, there is an exchange library.

## Repositories

[Verfassungsbooks/Verfassungsbooks](https://github.com/Verfassungsblog/Verfassungsbooks)

[Verfassungsbooks/Verfassungsbooks-Rendering-Server](https://github.com/Verfassungsblog/Verfassungsbooks-Rendering-Server)

[Verfassungsbooks/Verfassungsbooks-Exchange-Lib](https://github.com/Verfassungsblog/Verfassungsbooks-Exchange-Lib)

## CI/CD Pipeline

Currently, we are using GitHub actions to automatically build and test on new commits or pull requests. Pushs to the `Verfassungsbooks` master branch are also automatically deployed to the [staging test environment](https://editor-staging.verfassungsblog.de), pushs to the `Verfassungsbooks-Rendering-Server` master branch are automatically deployed to the staging test rendering server (editor-staging-rendering1.verfassungsblog.de)

### GitHub Action Workflows

The repositories have three GitHub workflows configured:

- build-master.yml -&gt; Builds and tests commits to the master branch, automatically deploys them to the staging system
- build-test-all.yml -&gt; Builds ant tests commits / pull requests to any branch (except master)
- push-to-prod.yml -&gt; manually triggered; builds and tests a specific commit, deploys to productive system (editor.verfassungsblog.de / editor-rendering1.verfassungsblog.de)

# Writing new API Endpoints

### Security

<p class="callout danger">Important: All non-public routes **must** use type Session in the routes parameters! Otherwise, no login is required to access it!</p>

Minimal boilerplate:

```rust
#[get("/api/non/public/endpoint")]
pub async fn my_endpoint(
    _session: Session,
) -> Json<ApiResult<()>> {
  ApiResult::new_data(());
}
```

### Endpoints

Typically, you should implement GET, POST, DELETE and PATCH routes. POST routes are only used to create new objects, PATCH routes are used to update an existing object.

#### GET Route Example

Route to get a specific section in a project

```rust
/// GET /api/projects/<project_id>/sections/<content_path>?<expand>
///
/// Parameters:
/// * project_id (string) - the projects uuid
/// * content_path (string) - path to a specific section, split by ':'
/// * expand (string, optional) - optionally expand one of these fields: authors, editors, subsections
/// 
/// By default strips out subsections & only returns id's for authors and editors.
/// Use the optional expand query parameter to expand these fields
/// E.g. ?expand=authors,editors,subsections will show the full data
/// 
#[get("/api/projects/<project_id>/sections/<content_path>?<expand>")]
pub async fn get_section(
    project_id: &str,
    content_path: &str,
    expand: Option<&str>,
    _session: Session,
    settings: &State<Settings>,
    project_storage: &State<Arc<ProjectStorage>>,
    data_storage: &State<Arc<DataStorage>>
) -> Json<ApiResult<APISectionResult>> {
    ...
}
```

### Test your routes

You may test your new endpoints with curl. First, obtain a session cookie via your browser. Then use curl:

`curl -v --cookie 'session=INSERTYOURCOOKIEHERE' <host>/api/<route>`

# In Depth: Editor CRDT

<p class="callout danger">Work in Progress! CRDT isn't implemented yet.</p>

### Ablauf:

1. Client möchte Kapitel bearbeiten, Client öffnet web socket mit Server
2. Client bekommt yDoc von Server
3. Client wandelt yDoc Änderungen in EditorJS Blockänderungen um &lt;-- Herausforderung 1
4. Bei Änderungen an Blöcken wird änderung in yDoc Änderung umgewandelt &lt;-- Herausforderung 2
5. yDoc Änderung wird an Server geschickt
6. Server schickt yDoc Änderung an alle anderen Clients

Beispiel EditorJS Block JSON:

```json
  {
    "id": "jaJh1uNPLq",
    "type": "paragraph",
    "data": {
      "text": "In the spring of 2024, video cameras from numerous global news outlets turned their attention to a court in Strasbourg. People traveled from across Europe, gathering with signs in front of the courthouse. Minors from Portugal stood alongside senior citizens from Switzerland to witness one of the most significant moments in the recent history of the European Convention on Human Rights. For the first time, the European Court of Human Rights (ECtHR) ruled on the impact of the climate crisis on human rights and what this means for the Convention’s signatory states. The court’s Grand Chamber issued rulings on three cases: the case of <i>Carême v. France</i><citation data-key=\"ECtHR_careme_2024\">C</citation> (“<i>Carême</i>”), brought by the former mayor of Grande-Synthe, France; the case of <i>Duarte Agostinho and Others v. Portugal and 32 Others</i><citation data-key=\"ECtHR_duarte_2024\">C</citation>&nbsp;(“<i>Duarte Agostinho</i>”), brought by six youth applicants from Portugal; and the case of <i>Verein KlimaSeniorinnen Schweiz and Others v. Switzerland</i><citation data-key=\"noauthor_hudoc_nodate\">C</citation><i>&nbsp;</i>(“<i>KlimaSeniorinnen</i>”). While the first two cases were deemed inadmissible, the court handed down a ruling in <i>Klimaseniorinnen</i>, which is already regarded as one of the most important judgments in climate change litigation. The court stated that “the state has a positive duty to adopt, and effectively implement in practice, regulations and measures capable of mitigating the existing and potentially irreversible future effects of climate change”. Regarding the Swiss government, one of the Convention’s signatory states, the court concluded that by failing to put in place a sufficient domestic regulatory framework for climate change mitigation, the government violated Article 8 of the European Convention on Human Rights (ECHR), the right to respect for private and family life. Article 8 requires “that each Contracting State undertake measures for the substantial and progressive reduction of their respective GHG emission levels, with a view to reaching net neutrality within, in principle, the next three decades” (<i>KlimaSeniorinnen</i>, para. 548). Moreover, the Court found a violation of the right of access to court (Article 6 of the ECHR). "
    },
    "tunes": {}
  },
  {
    "id": "M29CgX78Fc",
    "type": "header",
    "data": {
      "text": "The three climate rulings",
      "level": 1
    },
    "tunes": {}
  },
```

EditorJS hat eine insert Funktion, über die können wir Blöcke dann einfügen: [https://editorjs.io/blocks/#insert](https://editorjs.io/blocks/#insert)

Wir müssen ein äquivalent in yrs Typen schaffen, tunes brauchen wir nicht, id's vermutlich auch nicht (?). Data muss eine map sein und hat neben text optionale andere einträge

### Protokoll

Clients und Server kommunizieren über Websockets miteinander. Das erste Byte jeder Nachricht bestimmt den Nachrichtentyp:

<table border="1" id="bkmrk-1st-byte-decimal-val" style="border-collapse: collapse; width: 100%; height: 466px;"><colgroup><col style="width: 15.2563%;"></col><col style="width: 10.2503%;"></col><col style="width: 12.0381%;"></col><col style="width: 10.2503%;"></col><col style="width: 26.1025%;"></col><col style="width: 26.1025%;"></col></colgroup><tbody><tr style="height: 46.6px;"><td style="height: 46.6px;">1st Byte Decimal Value</td><td style="height: 46.6px;">Name</td><td style="height: 46.6px;">Binary / JSON?</td><td style="height: 46.6px;">Sent from</td><td style="height: 46.6px;">Data</td><td style="height: 46.6px;">Description</td></tr><tr style="height: 63.4px;"><td style="height: 63.4px;">10</td><td style="height: 63.4px;">CONNECT</td><td style="height: 63.4px;">JSON</td><td style="height: 63.4px;">Client</td><td style="height: 63.4px;">document\_id</td><td style="height: 63.4px;">Connect to server with existing session and edit document with document\_id</td></tr><tr style="height: 29.8px;"><td style="height: 29.8px;">11</td><td style="height: 29.8px;">WELCOME</td><td style="height: 29.8px;">JSON</td><td style="height: 29.8px;">Server</td><td style="height: 29.8px;">client\_id</td><td style="height: 29.8px;">todo</td></tr><tr style="height: 29.8px;"><td style="height: 29.8px;">20</td><td style="height: 29.8px;">GETDOC</td><td style="height: 29.8px;">Binary</td><td style="height: 29.8px;">Client</td><td style="height: 29.8px;">StateVector</td><td style="height: 29.8px;">todo</td></tr><tr style="height: 46.6px;"><td style="height: 46.6px;"><s>21</s></td><td style="height: 46.6px;"><s>DOCUPDATE</s></td><td style="height: 46.6px;"><s>Binary</s></td><td style="height: 46.6px;"><s>Server</s></td><td style="height: 46.6px;"><s>Document update</s></td><td style="height: 46.6px;"><s>todo</s></td></tr><tr style="height: 46.6px;"><td style="height: 46.6px;">30</td><td style="height: 46.6px;">DOCUPDATE</td><td style="height: 46.6px;">Binary</td><td style="height: 46.6px;">Client / Server</td><td style="height: 46.6px;">Document update</td><td style="height: 46.6px;">todo</td></tr><tr style="height: 46.6px;"><td style="height: 46.6px;">40</td><td style="height: 46.6px;">SETCURSOR</td><td style="height: 46.6px;">JSON</td><td style="height: 46.6px;">Client/Server</td><td style="height: 46.6px;">client\_id, block\_id, start, end (optional, only for selections)</td><td style="height: 46.6px;">todo</td></tr><tr style="height: 46.6px;"><td style="height: 46.6px;">41</td><td style="height: 46.6px;">REMOVECURSOR</td><td style="height: 46.6px;">JSON</td><td style="height: 46.6px;">Server</td><td style="height: 46.6px;">client\_id</td><td style="height: 46.6px;">todo</td></tr><tr style="height: 46.6px;"><td style="height: 46.6px;">50</td><td style="height: 46.6px;">DISCONNECT</td><td style="height: 46.6px;">JSON</td><td style="height: 46.6px;">Client</td><td style="height: 46.6px;">client\_id</td><td style="height: 46.6px;">todo</td></tr><tr style="height: 63.4px;"><td style="height: 63.4px;">60</td><td style="height: 63.4px;">ERROR</td><td style="height: 63.4px;">JSON</td><td style="height: 63.4px;">Server</td><td style="height: 63.4px;">status\_code, error\_msg</td><td style="height: 63.4px;">error occured, e.g. authorization failed due to invalid session\_id, document not found etc.</td></tr></tbody></table>

**Auth:** The client has to send a valid session cookie when establishing the websocket.