Skip to content

Commit e8a489a

Browse files
authored
👷🏻 Auto-generate frontend client (fastapi#1320)
1 parent 5693985 commit e8a489a

File tree

7 files changed

+100
-31
lines changed

7 files changed

+100
-31
lines changed

.github/workflows/generate-client.yml

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: Generate Client
2+
3+
on:
4+
pull_request:
5+
types:
6+
- opened
7+
- synchronize
8+
9+
jobs:
10+
generate-client:
11+
permissions:
12+
contents: write
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
with:
17+
ref: ${{ github.head_ref }}
18+
token: ${{ secrets.FULL_STACK_FASTAPI_TEMPLATE_REPO_TOKEN }}
19+
- uses: actions/setup-node@v4
20+
with:
21+
node-version: lts/*
22+
- uses: actions/setup-python@v5
23+
with:
24+
python-version: '3.10'
25+
- name: Install dependencies
26+
run: npm ci
27+
working-directory: frontend
28+
- run: pip install ./backend
29+
- run: bash scripts/generate-client.sh
30+
- name: Commit changes
31+
run: |
32+
git config --local user.email "[email protected]"
33+
git config --local user.name "github-actions"
34+
git add frontend/src/client
35+
git diff --staged --quiet || git commit -m "✨ Autogenerate frontend client"
36+
git push
37+
38+
# https://github.com/marketplace/actions/alls-green#why
39+
generate-client-alls-green: # This job does nothing and is only used for the branch protection
40+
if: always()
41+
needs:
42+
- generate-client
43+
runs-on: ubuntu-latest
44+
steps:
45+
- name: Decide whether the needed jobs succeeded or failed
46+
uses: re-actors/alls-green@release/v1
47+
with:
48+
jobs: ${{ toJSON(needs) }}
49+

frontend/README.md

+15-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ The frontend is built with [Vite](https://vitejs.dev/), [React](https://reactjs.
44

55
## Frontend development
66

7-
Before you begin, ensure that you have either the Node Version Manager (nvm) or Fast Node Manager (fnm) installed on your system.
7+
Before you begin, ensure that you have either the Node Version Manager (nvm) or Fast Node Manager (fnm) installed on your system.
88

99
* To install fnm follow the [official fnm guide](https://github.com/Schniz/fnm#installation). If you prefer nvm, you can install it using the [official nvm guide](https://github.com/nvm-sh/nvm#installing-and-updating).
1010

@@ -27,7 +27,7 @@ nvm install
2727

2828
```bash
2929
# If using fnm
30-
fnm use
30+
fnm use
3131

3232
# If using nvm
3333
nvm use
@@ -74,6 +74,19 @@ But it would be only to clean them up, leaving them won't really have any effect
7474

7575
## Generate Client
7676

77+
### Automatically
78+
79+
* Activate the backend virtual environment.
80+
* From the top level project directory, run the script:
81+
82+
```bash
83+
./scripts/generate-frontend-client.sh
84+
```
85+
86+
* Commit the changes.
87+
88+
### Manually
89+
7790
* Start the Docker Compose stack.
7891

7992
* Download the OpenAPI JSON file from `http://localhost/api/v1/openapi.json` and copy it to a new file `openapi.json` at the root of the `frontend` directory.

frontend/biome.json

-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
"files": {
77
"ignore": [
88
"node_modules",
9-
"src/client/",
109
"src/routeTree.gen.ts",
1110
"playwright.config.ts",
1211
"playwright-report"

frontend/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"build": "tsc && vite build",
99
"lint": "biome check --apply-unsafe --no-errors-on-unmatched --files-ignore-unknown=true ./",
1010
"preview": "vite preview",
11-
"generate-client": "openapi-ts --input ./openapi.json --output ./src/client --client axios --exportSchemas true && biome format --write ./src/client"
11+
"generate-client": "openapi-ts --input ./openapi.json --output ./src/client --client axios --exportSchemas true"
1212
},
1313
"dependencies": {
1414
"@chakra-ui/icons": "2.1.1",

frontend/src/client/core/request.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import axios from "axios"
22
import type {
33
AxiosError,
4-
AxiosInstance,
54
AxiosRequestConfig,
65
AxiosResponse,
6+
AxiosInstance,
77
} from "axios"
88

99
import { ApiError } from "./ApiError"
@@ -151,12 +151,12 @@ export const getHeaders = async (
151151
)
152152

153153
if (isStringWithValue(token)) {
154-
headers.Authorization = `Bearer ${token}`
154+
headers["Authorization"] = `Bearer ${token}`
155155
}
156156

157157
if (isStringWithValue(username) && isStringWithValue(password)) {
158158
const credentials = base64(`${username}:${password}`)
159-
headers.Authorization = `Basic ${credentials}`
159+
headers["Authorization"] = `Basic ${credentials}`
160160
}
161161

162162
if (options.body !== undefined) {

frontend/src/client/services.ts

+24-24
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,20 @@ import { request as __request } from "./core/request"
44

55
import type {
66
Body_login_login_access_token,
7-
ItemCreate,
8-
ItemPublic,
9-
ItemUpdate,
10-
ItemsPublic,
117
Message,
128
NewPassword,
139
Token,
10+
UserPublic,
1411
UpdatePassword,
1512
UserCreate,
16-
UserPublic,
1713
UserRegister,
14+
UsersPublic,
1815
UserUpdate,
1916
UserUpdateMe,
20-
UsersPublic,
17+
ItemCreate,
18+
ItemPublic,
19+
ItemsPublic,
20+
ItemUpdate,
2121
} from "./models"
2222

2323
export type TDataLoginAccessToken = {
@@ -50,7 +50,7 @@ export class LoginService {
5050
formData: formData,
5151
mediaType: "application/x-www-form-urlencoded",
5252
errors: {
53-
422: "Validation Error",
53+
422: `Validation Error`,
5454
},
5555
})
5656
}
@@ -85,7 +85,7 @@ export class LoginService {
8585
email,
8686
},
8787
errors: {
88-
422: "Validation Error",
88+
422: `Validation Error`,
8989
},
9090
})
9191
}
@@ -106,7 +106,7 @@ export class LoginService {
106106
body: requestBody,
107107
mediaType: "application/json",
108108
errors: {
109-
422: "Validation Error",
109+
422: `Validation Error`,
110110
},
111111
})
112112
}
@@ -128,7 +128,7 @@ export class LoginService {
128128
email,
129129
},
130130
errors: {
131-
422: "Validation Error",
131+
422: `Validation Error`,
132132
},
133133
})
134134
}
@@ -180,7 +180,7 @@ export class UsersService {
180180
limit,
181181
},
182182
errors: {
183-
422: "Validation Error",
183+
422: `Validation Error`,
184184
},
185185
})
186186
}
@@ -201,7 +201,7 @@ export class UsersService {
201201
body: requestBody,
202202
mediaType: "application/json",
203203
errors: {
204-
422: "Validation Error",
204+
422: `Validation Error`,
205205
},
206206
})
207207
}
@@ -248,7 +248,7 @@ export class UsersService {
248248
body: requestBody,
249249
mediaType: "application/json",
250250
errors: {
251-
422: "Validation Error",
251+
422: `Validation Error`,
252252
},
253253
})
254254
}
@@ -269,7 +269,7 @@ export class UsersService {
269269
body: requestBody,
270270
mediaType: "application/json",
271271
errors: {
272-
422: "Validation Error",
272+
422: `Validation Error`,
273273
},
274274
})
275275
}
@@ -290,7 +290,7 @@ export class UsersService {
290290
body: requestBody,
291291
mediaType: "application/json",
292292
errors: {
293-
422: "Validation Error",
293+
422: `Validation Error`,
294294
},
295295
})
296296
}
@@ -312,7 +312,7 @@ export class UsersService {
312312
user_id: userId,
313313
},
314314
errors: {
315-
422: "Validation Error",
315+
422: `Validation Error`,
316316
},
317317
})
318318
}
@@ -336,7 +336,7 @@ export class UsersService {
336336
body: requestBody,
337337
mediaType: "application/json",
338338
errors: {
339-
422: "Validation Error",
339+
422: `Validation Error`,
340340
},
341341
})
342342
}
@@ -356,7 +356,7 @@ export class UsersService {
356356
user_id: userId,
357357
},
358358
errors: {
359-
422: "Validation Error",
359+
422: `Validation Error`,
360360
},
361361
})
362362
}
@@ -382,7 +382,7 @@ export class UtilsService {
382382
email_to: emailTo,
383383
},
384384
errors: {
385-
422: "Validation Error",
385+
422: `Validation Error`,
386386
},
387387
})
388388
}
@@ -425,7 +425,7 @@ export class ItemsService {
425425
limit,
426426
},
427427
errors: {
428-
422: "Validation Error",
428+
422: `Validation Error`,
429429
},
430430
})
431431
}
@@ -446,7 +446,7 @@ export class ItemsService {
446446
body: requestBody,
447447
mediaType: "application/json",
448448
errors: {
449-
422: "Validation Error",
449+
422: `Validation Error`,
450450
},
451451
})
452452
}
@@ -466,7 +466,7 @@ export class ItemsService {
466466
id,
467467
},
468468
errors: {
469-
422: "Validation Error",
469+
422: `Validation Error`,
470470
},
471471
})
472472
}
@@ -490,7 +490,7 @@ export class ItemsService {
490490
body: requestBody,
491491
mediaType: "application/json",
492492
errors: {
493-
422: "Validation Error",
493+
422: `Validation Error`,
494494
},
495495
})
496496
}
@@ -510,7 +510,7 @@ export class ItemsService {
510510
id,
511511
},
512512
errors: {
513-
422: "Validation Error",
513+
422: `Validation Error`,
514514
},
515515
})
516516
}

scripts/generate-client.sh

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#! /usr/bin/env bash
2+
3+
PYTHONPATH=backend python -c "import app.main; import json; print(json.dumps(app.main.app.openapi()))" > openapi.json
4+
node frontend/modify-openapi-operationids.js
5+
mv openapi.json frontend/
6+
cd frontend
7+
npm run generate-client
8+
npx biome format --write ./src/client

0 commit comments

Comments
 (0)