Skip to content

Commit

Permalink
Merge pull request #731 from it-at-m/716-generierung-der-service-schn…
Browse files Browse the repository at this point in the history
…ittstellen-für-das-frontend

716 generierung der service schnittstellen für das frontend
  • Loading branch information
vjohnslhm authored Jan 28, 2025
2 parents 8379108 + 68740c1 commit e557d6b
Show file tree
Hide file tree
Showing 30 changed files with 1,802 additions and 311 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ node_modules
/docs/.vitepress/cache/
/docs/.vitepress/dist/
/docs/node_modules/

/wls-gui-wahllokalsystem/src/api/wls-clients/generated-*-api/.openapi-generator
/wls-gui-wahllokalsystem/src/api/wls-clients/generated-*-api/.openapi-generator-ignore
19 changes: 13 additions & 6 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import {withMermaid} from "vitepress-plugin-mermaid"

const PATH_SERVICES = '/services/';
const PATH_TECHNIK = '/technik/';
const PATH_ECOSYSTEM = PATH_TECHNIK + 'ecosystem/';
const PATH_NAMING_CONVENTIONS = PATH_TECHNIK + 'naming_conventions/';
const PATH_ADR = PATH_TECHNIK + 'adr/';
const PATH_NAMING_CONVENTIONS = PATH_TECHNIK + 'naming_conventions/';
const PATH_GUIDES = PATH_TECHNIK + 'guides/';
const PATH_SERVICES = '/services/';
const PATH_API_CLIENT_GENERATION = PATH_GUIDES + 'api-client-generation/'
const PATH_SYSSPEC = PATH_TECHNIK + "systemspecification/";

// https://vitepress.dev/reference/site-config
Expand Down Expand Up @@ -77,10 +78,16 @@ export default withMermaid({
},
{
text: 'Guides', link: `${PATH_GUIDES}`, collapsed: true, items: [
{
text: 'API-Client generieren',
link: `${PATH_GUIDES}how-to-create-client-from-open-api-json.md`
},
{text: 'Api-Client generieren', link: `${PATH_API_CLIENT_GENERATION}`, collapsed: true, items: [
{
text: 'API-Client im Backend',
link: `${PATH_API_CLIENT_GENERATION}how-to-create-client-from-open-api-json.md`
},
{
text: 'API-Client im Frontend',
link: `${PATH_API_CLIENT_GENERATION}generate-client-from-openapi-json-frontend.md`
},
]},
{text: 'Datenbankzugriff', link: `${PATH_GUIDES}db-access.md`},
{text: 'Neuer Microservice', link: `${PATH_GUIDES}new-service.md`}
]
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
# API-Client aus einem openapi.json File im Frontend

## Installation der openapitools

Anders als im Backend gibt es für das Frontend kein Plugin, um den Openapi Generator zu integrieren, sondern muss
als CLI Befehl ausgeführt werden. Mit diesem Befehl kann der openapi-generator global auf dem Rechner
installiert werden:

```shell
npm install @openapitools/openapi-generator-cli -g typescript-fetch
```

::: details Errorhandling
Sollte dabei diese Fehlermeldung auftauchen:
![img_1.png](../../../public/error_install_openapi-generator.png)
können die Schritte aus
[diesem Stack-Beitrag](https://stackoverflow.com/questions/18088372/how-to-npm-install-global-not-as-root/59227497#59227497)
befolgt werden.
:::

Bei Bedarf muss zusätzlich noch der Proxy konfiguriert werden. Anschließend kann im Terminal mit dem Befehl
`openapi-generator-cli version` geprüft werden, ob die Installation erfolgreich war.

## Individualisieren des Generators

Damit der generierte Code zum Projekt passt, wurden die Templates des openapi-generators angepasst.

> [!IMPORTANT]
> Die Anpassung des Templates ist einmalig erfolgt und muss nicht für jeden Service wiederholt werden. Das geänderte
> Template File wurde mit auf GitHub gepushed und liegt unter
> [wls-gui-wahllokalsystem/src/api/wls-clients/custom-openapi-template-files](https://github.com/it-at-m/Wahllokalsystem/tree/dev/wls-gui-wahllokalsystem/src/api/wls-clients/custom-openapi-template-files).
> Dieser Abschnitt dient nur zur Information und muss nicht für die Generierung des Codes ausgeführt werden.
Mit dem Befehl

```shell
openapi-generator-cli author template -g typescript-fetch -o ./my-custom-template
```

können die Template Files heruntergeladen werden. Hierbei steht `-o` für Output und es kann der Ort angegeben werden, an
dem die heruntergeladenen Files gespeichert werden sollen. Für das WLS wurde das File `runtime.mustache` angepasst. Die
veränderten oder hinzugefügten Codezeilen sind mit dem Kommentar `//Abweichung vom Standard-Template` gekennzeichnet.
Folgende Code-Zeilen wurden hinzugefügt:

::: code-group
```:line-numbers=123 [runtime.mustache (Teil 1)]
protected async request(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction): Promise<Response> {
const { url, init } = await this.createFetchParams(context, initOverrides);
const response = await this.fetchApi(url, init);
// Abweichung vom Standard-Template: Zusätzliche Prüfung auf Statuscode 204 und >300 // [!code ++]
if (response.status === 204) { // [!code ++]
throw new WLSError(response, "Es konnten keine Daten gefunden werden", "T", response.status.toString()) // [!code ++]
} else if (response && (response.status >= 200 && response.status < 300)) { // [!code ++]
return response; // [!code ++]
} else if (response && response.status == 400) { // [!code ++]
const raw = await response.text(); // [!code ++]
let content; // [!code ++]
try { // [!code ++]
content = JSON.parse(raw); // [!code ++]
} catch (e) { // [!code ++]
content = {}; // [!code ++]
} // [!code ++]
const content = JSON.parse(raw); // [!code ++]
const wlsError = isWLSException(content) // [!code ++]
? generateWlsExceptionFromJson(content) // [!code ++]
: createDefaultWlsError({ // [!code ++]
message: "Ungültige Anfrage", // [!code ++]
code: response.status.toString(), // [!code ++]
}); // [!code ++]
throw new WLSError( // [!code ++]
response, // [!code ++]
wlsError.message, // [!code ++]
wlsError.category, // [!code ++]
wlsError.code, // [!code ++]
wlsError.service // [!code ++]
); // [!code ++]
}
throw new ResponseError(response, 'Response returned an error code');
}
```

```:line-numbers=295 [runtime.mustache (Teil 2)]
// Abweichung vom Standard-Template: Implementierung von WLSError und WLSException // [!code ++]
export class WLSError extends Error { // [!code ++]
override name: "WLSError" = "WLSError"; // [!code ++]
category?: string; // [!code ++]
code?: string; // [!code ++]
service?: string; // [!code ++]
constructor( // [!code ++]
public response: Response, // [!code ++]
msg?: string, // [!code ++]
category?: string, // [!code ++]
code?: string, // [!code ++]
service?: string // [!code ++]
) { // [!code ++]
super(msg); // [!code ++]
this.category = category; // [!code ++]
this.code = code; // [!code ++]
this.service = service; // [!code ++]
} // [!code ++]
} // [!code ++]
// [!code ++]
export function createDefaultWlsError({ // [!code ++]
message = "Ein unbekannter Fehler ist aufgetreten", // [!code ++]
category = "T", // [!code ++]
code = "undefined", // [!code ++]
service = "undefined", // [!code ++]
}: { // [!code ++]
message?: string; // [!code ++]
category?: string; // [!code ++]
code?: string; // [!code ++]
service?: string; // [!code ++]
}): WLSError { // [!code ++]
return new WLSError( // [!code ++]
new Response(), // [!code ++]
message, // [!code ++]
category, // [!code ++]
code, // [!code ++]
service // [!code ++]
); // [!code ++]
} // [!code ++]
// [!code ++]
export interface WLSException { // [!code ++]
category: string; // [!code ++]
code: string; // [!code ++]
message: string; // [!code ++]
service: string; // [!code ++]
} // [!code ++]
// [!code ++]
/** // [!code ++]
* Type guard to check if an object is a WLSException // [!code ++]
* @param obj - The object to check // [!code ++]
* @returns True if the object has all required WLSException properties with correct types // [!code ++]
*/ // [!code ++]
// eslint-disable-next-line @typescript-eslint/no-explicit-any // [!code ++]
export function isWLSException(obj: any): obj is WLSException { // [!code ++]
return ( // [!code ++]
obj && // [!code ++]
typeof obj.category === "string" && // [!code ++]
typeof obj.code === "string" && // [!code ++]
typeof obj.message === "string" && // [!code ++]
typeof obj.service === "string" // [!code ++]
); // [!code ++]
} // [!code ++]
// [!code ++]
/** // [!code ++]
* Generates a WLSError instance from a JSON object. // [!code ++]
* @param content - The JSON-object with all relevant information // [!code ++]
* @returns the generated WLSError object from JSON data // [!code ++]
*/ // [!code ++]
// eslint-disable-next-line @typescript-eslint/no-explicit-any // [!code ++]
export function generateWlsExceptionFromJson(content: any): WLSException { // [!code ++]
return { // [!code ++]
category: content.category, // [!code ++]
code: content.code, // [!code ++]
message: content.message, // [!code ++]
service: content.service, // [!code ++]
}; // [!code ++]
} // [!code ++]
```
:::

## Generierung des Codes

Es gibt zwei Möglichkeiten, den Befehl auszuführen, um den gewünschten Code generieren zu lassen.

#### 1) Ausführen des Befehls im Terminal
Im Terminal kann, wenn man sich innerhalb der `wls-gui-wahllokalsystem`-Directory befindet, für jedes
`openapi.json`-File mit folgendem Befehl der entsprechende Code generiert werden:

```shell
openapi-generator-cli generate -i src/resources/openapis/<openapi-file> -g typescript-fetch -o src/api/wls-clients/generated-<domain>-api --template-dir src/api/wls-clients/custom-openapi-template-files
```

Dabei gilt:
- Das `-i` steht für Input und gibt den Ort an, an welchem das `openapi.json`-File gespeichert ist:
_"src/resources/openapis/\<openapi-file\>"_. `<openapi-file>` wird dabei durch das entsprechende
Release-File ersetzt. Beispiel: `openapi.broadcast.0.2.0.json`
- Das `-o` steht für Output und gibt den Ort an, an welchem der generierte Code gespeichert werden soll:
_"src/api/wls-clients/generated-\<domain\>-api"_. `<domain>` wird dabei durch den entsprechenden
WLS-Service ersetzt. Beispiel: `generated-broadcast-api`
- Das `--template-dir` sorgt dafür, dass die angepassten Templates bei der Generierung berücksichtigt werden und gibt
den Ort an, an dem diese gespeichert sind: _"src/api/wls-clients/custom-openapi-template-files"_

::: details Beispiel Broadcast-API
Der komplette Befehl für die Generierung der Broadcast API über das Terminal würde so aussehen:
```shell
openapi-generator-cli generate -i src/resources/openapis/openapi.broadcast.0.2.0.json -g typescript-fetch -o src/api/wls-clients/generated-broadcast-api --template-dir src/api/wls-clients/custom-openapi-template-files
```
:::

#### 2) Ausführen des Skripts `gen:gen-<domain>`

In der `package.json` kann der oben genannte Befehl als Skript hinzugefügt werden. Das sieht dann so aus:

```json
"scripts": {
"dev": "vite",
/* ... */
"gen:gen-<domain>": "openapi-generator-cli generate -i src/resources/openapis/<openapi-file> -g typescript-fetch -o src/api/wls-clients/generated-<domain>-api --template-dir src/api/wls-clients/custom-openapi-template-files" // [!code ++]
},
```

::: details Beispiel Broadcast-API
Der komplette Befehl für die Generierung der Broadcast API über das `package.json`-File würde so aussehen:
```json
"scripts": {
"gen:gen-broadcast": "openapi-generator-cli generate -i src/resources/openapis/openapi.broadcast.0.2.0.json -g typescript-fetch -o src/api/wls-clients/generated-broadcast-api --template-dir src/api/wls-clients/custom-openapi-template-files"
},
```
:::

## Nutzung des generierten Codes

Es werden bei der Generierung unter anderem `*ControllerApi.ts`-Files und `*DTO.ts`-Files erstellt.
Am Beispiel vom Broadcast-Service wird gezeigt, wie der Code anschließend aufgerufen werden kann:

Damit die korrekte URL hinterlegt wird, muss beim Erstellen jeder `*ControllerApi`-Instanz der `basePath` überschrieben
werden:

::: code-group
```typescript [Vue File]
import {BroadcastControllerApi, Configuration} from "@/api/wls-clients/generated-broadcast-api";
import {BROADCAST_SERVICE_API_URL} from "@/constants";

const broadcastCA = new BroadcastControllerApi(
new Configuration({
basePath: BROADCAST_SERVICE_API_URL,
})
);
```

```typescript [constants.ts]
const WLS_SERVICE_API_URL = "/api/";

export const BROADCAST_SERVICE_API_URL = WLS_SERVICE_API_URL + "broadcast-service";
```
:::

Die Fetch Aufrufe erfolgen dann zum Beispiel so:

```typescript
import type {DeleteMessageRequest, GetMessageRequest} from "@/api/wls-clients/generated-broadcast-api";
import {WLSError} from "@/api/wls-clients/generated-broadcast-api";

const getParams: GetMessageRequest = {wahlbezirkID};
broadcastCA
.getMessage(getParams)
.then((content) => {
const nachrichtID = content.oid;
const deleteParams: DeleteMessageRequest = {nachrichtID};
broadcastCA.deleteMessage(deleteParams, postConfig()).catch(() => {
errorToShow.value = "Es ist ein Fehler beim Lesen der Nachricht aufgetreten";
});
messageToShow.value = content.nachricht;
})
.catch((error: WLSError) => {
errorToShow.value = error.message;
});
```

Im Fall eines `400`er Codes in der Response, was in den meisten Fällen einer WlsException entspricht, können diese Werte
dann wie folgt aufgerufen und weiterverarbeitet werden:

```typescript
broadcastCA
.then(/*xyz*/)
.catch((error: WLSError) => {
errorToShow.value = error.service + " - " + error.message + " (Code: " + error.code + ")";
});
```

Die Ausgabe wäre in diesem Beispiel: `WLS-BROADCAST - Das Object BroadcastMessage ist nicht vollständig. (Code: 150)`
Original file line number Diff line number Diff line change
@@ -1,17 +1,6 @@
# API-Client aus OpenAPI-Spec erstellen

## Einleitung

Alle Services stellen ihre API in Form einer OpenAPI Spezifikation zur Verfügung. Diese Dateien liegen den Assets
der jeweiligen Releases bei.

![Übersicht über Release von wls-eai-service Version 0.0.1-RC1](/screenshot-wls-eai-service-release-0.0.1-RC1.png)
*Übersicht über [Release](https://github.com/it-at-m/Wahllokalsystem/releases/tag/wls-eai-service%2F0.0.1-RC1) von
wls-eai-service Version 0.0.1-RC1*

Durch ein Maven-Plugin lassen sich aus der Datei `openapi.json` der Client generieren.

## Einfügen des Plugins
## Einfügen des Maven-Plugins

Um aus der Spezifikation die Java-Klassen zu erstellen, muss das Generator-Plugin eingebunden werden.

Expand Down
15 changes: 15 additions & 0 deletions docs/src/technik/guides/api-client-generation/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# API-Client Generierung

Alle Services stellen ihre API in Form einer OpenAPI Spezifikation zur Verfügung. Diese Dateien liegen den Assets
der jeweiligen Releases bei.

![Übersicht über Release von wls-eai-service Version 0.0.1-RC1](/screenshot-wls-eai-service-release-0.0.1-RC1.png)
*Übersicht über [Release](https://github.com/it-at-m/Wahllokalsystem/releases/tag/wls-eai-service%2F0.0.1-RC1) von
wls-eai-service Version 0.0.1-RC1*

___

Mithilfe dieses `openapi.json`-Releasefiles erfolgt die Generation des Datenmodells und der Client-API im
[Backend](/technik/guides/api-client-generation/how-to-create-client-from-open-api-json.md) über ein Maven-Plugin,
während im [Frontend](/technik/guides/api-client-generation/generate-client-from-openapi-json-frontend.md) CLI-Befehle
zum Einsatz kommen.
5 changes: 4 additions & 1 deletion wls-gui-wahllokalsystem/.prettierignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
dist
node_modules
node_modules

src/api/wls-clients/generated-*-api
src/resources/openapis
9 changes: 8 additions & 1 deletion wls-gui-wahllokalsystem/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,14 @@ export default [
}),
vuePrettierEslintConfigSkipFormatting,
{
ignores: ["dist", "target", "node_modules", "env.d.ts"],
ignores: [
"dist",
"target",
"node_modules",
"env.d.ts",
"src/api/wls-clients/generated-*-api",
"src/resources/openapis",
],
},
{
rules: {
Expand Down
7 changes: 7 additions & 0 deletions wls-gui-wahllokalsystem/openapitools.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json",
"spaces": 2,
"generator-cli": {
"version": "7.10.0"
}
}
3 changes: 2 additions & 1 deletion wls-gui-wahllokalsystem/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"build": "vue-tsc --build --force && vite build",
"preview": "vite preview",
"lint": "prettier . --check && eslint . && vue-tsc --noEmit",
"fix": "prettier . --write && eslint . --fix"
"fix": "prettier . --write && eslint . --fix",
"gen:gen-broadcast": "openapi-generator-cli generate -i src/resources/openapis/openapi.broadcast.0.2.0.json -g typescript-fetch -o src/api/wls-clients/generated-broadcast-api --template-dir src/api/wls-clients/custom-openapi-template-files"
},
"dependencies": {
"@vueuse/core": "12.4.0",
Expand Down
Loading

0 comments on commit e557d6b

Please sign in to comment.