👷🏻 Auto-generate frontend client (#1320)
This commit is contained in:
49
.github/workflows/generate-client.yml
vendored
Normal file
49
.github/workflows/generate-client.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
name: Generate Client
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
- synchronize
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
generate-client:
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ github.head_ref }}
|
||||||
|
token: ${{ secrets.FULL_STACK_FASTAPI_TEMPLATE_REPO_TOKEN }}
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: lts/*
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.10'
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
working-directory: frontend
|
||||||
|
- run: pip install ./backend
|
||||||
|
- run: bash scripts/generate-client.sh
|
||||||
|
- name: Commit changes
|
||||||
|
run: |
|
||||||
|
git config --local user.email "github-actions@github.com"
|
||||||
|
git config --local user.name "github-actions"
|
||||||
|
git add frontend/src/client
|
||||||
|
git diff --staged --quiet || git commit -m "✨ Autogenerate frontend client"
|
||||||
|
git push
|
||||||
|
|
||||||
|
# https://github.com/marketplace/actions/alls-green#why
|
||||||
|
generate-client-alls-green: # This job does nothing and is only used for the branch protection
|
||||||
|
if: always()
|
||||||
|
needs:
|
||||||
|
- generate-client
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Decide whether the needed jobs succeeded or failed
|
||||||
|
uses: re-actors/alls-green@release/v1
|
||||||
|
with:
|
||||||
|
jobs: ${{ toJSON(needs) }}
|
||||||
|
|
@@ -74,6 +74,19 @@ But it would be only to clean them up, leaving them won't really have any effect
|
|||||||
|
|
||||||
## Generate Client
|
## Generate Client
|
||||||
|
|
||||||
|
### Automatically
|
||||||
|
|
||||||
|
* Activate the backend virtual environment.
|
||||||
|
* From the top level project directory, run the script:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/generate-frontend-client.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
* Commit the changes.
|
||||||
|
|
||||||
|
### Manually
|
||||||
|
|
||||||
* Start the Docker Compose stack.
|
* Start the Docker Compose stack.
|
||||||
|
|
||||||
* 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.
|
* 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.
|
||||||
|
@@ -6,7 +6,6 @@
|
|||||||
"files": {
|
"files": {
|
||||||
"ignore": [
|
"ignore": [
|
||||||
"node_modules",
|
"node_modules",
|
||||||
"src/client/",
|
|
||||||
"src/routeTree.gen.ts",
|
"src/routeTree.gen.ts",
|
||||||
"playwright.config.ts",
|
"playwright.config.ts",
|
||||||
"playwright-report"
|
"playwright-report"
|
||||||
|
@@ -8,7 +8,7 @@
|
|||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"lint": "biome check --apply-unsafe --no-errors-on-unmatched --files-ignore-unknown=true ./",
|
"lint": "biome check --apply-unsafe --no-errors-on-unmatched --files-ignore-unknown=true ./",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"generate-client": "openapi-ts --input ./openapi.json --output ./src/client --client axios --exportSchemas true && biome format --write ./src/client"
|
"generate-client": "openapi-ts --input ./openapi.json --output ./src/client --client axios --exportSchemas true"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chakra-ui/icons": "2.1.1",
|
"@chakra-ui/icons": "2.1.1",
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import axios from "axios"
|
import axios from "axios"
|
||||||
import type {
|
import type {
|
||||||
AxiosError,
|
AxiosError,
|
||||||
AxiosInstance,
|
|
||||||
AxiosRequestConfig,
|
AxiosRequestConfig,
|
||||||
AxiosResponse,
|
AxiosResponse,
|
||||||
|
AxiosInstance,
|
||||||
} from "axios"
|
} from "axios"
|
||||||
|
|
||||||
import { ApiError } from "./ApiError"
|
import { ApiError } from "./ApiError"
|
||||||
@@ -151,12 +151,12 @@ export const getHeaders = async (
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (isStringWithValue(token)) {
|
if (isStringWithValue(token)) {
|
||||||
headers.Authorization = `Bearer ${token}`
|
headers["Authorization"] = `Bearer ${token}`
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isStringWithValue(username) && isStringWithValue(password)) {
|
if (isStringWithValue(username) && isStringWithValue(password)) {
|
||||||
const credentials = base64(`${username}:${password}`)
|
const credentials = base64(`${username}:${password}`)
|
||||||
headers.Authorization = `Basic ${credentials}`
|
headers["Authorization"] = `Basic ${credentials}`
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.body !== undefined) {
|
if (options.body !== undefined) {
|
||||||
|
@@ -4,20 +4,20 @@ import { request as __request } from "./core/request"
|
|||||||
|
|
||||||
import type {
|
import type {
|
||||||
Body_login_login_access_token,
|
Body_login_login_access_token,
|
||||||
ItemCreate,
|
|
||||||
ItemPublic,
|
|
||||||
ItemUpdate,
|
|
||||||
ItemsPublic,
|
|
||||||
Message,
|
Message,
|
||||||
NewPassword,
|
NewPassword,
|
||||||
Token,
|
Token,
|
||||||
|
UserPublic,
|
||||||
UpdatePassword,
|
UpdatePassword,
|
||||||
UserCreate,
|
UserCreate,
|
||||||
UserPublic,
|
|
||||||
UserRegister,
|
UserRegister,
|
||||||
|
UsersPublic,
|
||||||
UserUpdate,
|
UserUpdate,
|
||||||
UserUpdateMe,
|
UserUpdateMe,
|
||||||
UsersPublic,
|
ItemCreate,
|
||||||
|
ItemPublic,
|
||||||
|
ItemsPublic,
|
||||||
|
ItemUpdate,
|
||||||
} from "./models"
|
} from "./models"
|
||||||
|
|
||||||
export type TDataLoginAccessToken = {
|
export type TDataLoginAccessToken = {
|
||||||
@@ -50,7 +50,7 @@ export class LoginService {
|
|||||||
formData: formData,
|
formData: formData,
|
||||||
mediaType: "application/x-www-form-urlencoded",
|
mediaType: "application/x-www-form-urlencoded",
|
||||||
errors: {
|
errors: {
|
||||||
422: "Validation Error",
|
422: `Validation Error`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -85,7 +85,7 @@ export class LoginService {
|
|||||||
email,
|
email,
|
||||||
},
|
},
|
||||||
errors: {
|
errors: {
|
||||||
422: "Validation Error",
|
422: `Validation Error`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -106,7 +106,7 @@ export class LoginService {
|
|||||||
body: requestBody,
|
body: requestBody,
|
||||||
mediaType: "application/json",
|
mediaType: "application/json",
|
||||||
errors: {
|
errors: {
|
||||||
422: "Validation Error",
|
422: `Validation Error`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -128,7 +128,7 @@ export class LoginService {
|
|||||||
email,
|
email,
|
||||||
},
|
},
|
||||||
errors: {
|
errors: {
|
||||||
422: "Validation Error",
|
422: `Validation Error`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -180,7 +180,7 @@ export class UsersService {
|
|||||||
limit,
|
limit,
|
||||||
},
|
},
|
||||||
errors: {
|
errors: {
|
||||||
422: "Validation Error",
|
422: `Validation Error`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -201,7 +201,7 @@ export class UsersService {
|
|||||||
body: requestBody,
|
body: requestBody,
|
||||||
mediaType: "application/json",
|
mediaType: "application/json",
|
||||||
errors: {
|
errors: {
|
||||||
422: "Validation Error",
|
422: `Validation Error`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -248,7 +248,7 @@ export class UsersService {
|
|||||||
body: requestBody,
|
body: requestBody,
|
||||||
mediaType: "application/json",
|
mediaType: "application/json",
|
||||||
errors: {
|
errors: {
|
||||||
422: "Validation Error",
|
422: `Validation Error`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -269,7 +269,7 @@ export class UsersService {
|
|||||||
body: requestBody,
|
body: requestBody,
|
||||||
mediaType: "application/json",
|
mediaType: "application/json",
|
||||||
errors: {
|
errors: {
|
||||||
422: "Validation Error",
|
422: `Validation Error`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -290,7 +290,7 @@ export class UsersService {
|
|||||||
body: requestBody,
|
body: requestBody,
|
||||||
mediaType: "application/json",
|
mediaType: "application/json",
|
||||||
errors: {
|
errors: {
|
||||||
422: "Validation Error",
|
422: `Validation Error`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -312,7 +312,7 @@ export class UsersService {
|
|||||||
user_id: userId,
|
user_id: userId,
|
||||||
},
|
},
|
||||||
errors: {
|
errors: {
|
||||||
422: "Validation Error",
|
422: `Validation Error`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -336,7 +336,7 @@ export class UsersService {
|
|||||||
body: requestBody,
|
body: requestBody,
|
||||||
mediaType: "application/json",
|
mediaType: "application/json",
|
||||||
errors: {
|
errors: {
|
||||||
422: "Validation Error",
|
422: `Validation Error`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -356,7 +356,7 @@ export class UsersService {
|
|||||||
user_id: userId,
|
user_id: userId,
|
||||||
},
|
},
|
||||||
errors: {
|
errors: {
|
||||||
422: "Validation Error",
|
422: `Validation Error`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -382,7 +382,7 @@ export class UtilsService {
|
|||||||
email_to: emailTo,
|
email_to: emailTo,
|
||||||
},
|
},
|
||||||
errors: {
|
errors: {
|
||||||
422: "Validation Error",
|
422: `Validation Error`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -425,7 +425,7 @@ export class ItemsService {
|
|||||||
limit,
|
limit,
|
||||||
},
|
},
|
||||||
errors: {
|
errors: {
|
||||||
422: "Validation Error",
|
422: `Validation Error`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -446,7 +446,7 @@ export class ItemsService {
|
|||||||
body: requestBody,
|
body: requestBody,
|
||||||
mediaType: "application/json",
|
mediaType: "application/json",
|
||||||
errors: {
|
errors: {
|
||||||
422: "Validation Error",
|
422: `Validation Error`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -466,7 +466,7 @@ export class ItemsService {
|
|||||||
id,
|
id,
|
||||||
},
|
},
|
||||||
errors: {
|
errors: {
|
||||||
422: "Validation Error",
|
422: `Validation Error`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -490,7 +490,7 @@ export class ItemsService {
|
|||||||
body: requestBody,
|
body: requestBody,
|
||||||
mediaType: "application/json",
|
mediaType: "application/json",
|
||||||
errors: {
|
errors: {
|
||||||
422: "Validation Error",
|
422: `Validation Error`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -510,7 +510,7 @@ export class ItemsService {
|
|||||||
id,
|
id,
|
||||||
},
|
},
|
||||||
errors: {
|
errors: {
|
||||||
422: "Validation Error",
|
422: `Validation Error`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
8
scripts/generate-client.sh
Normal file
8
scripts/generate-client.sh
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#! /usr/bin/env bash
|
||||||
|
|
||||||
|
PYTHONPATH=backend python -c "import app.main; import json; print(json.dumps(app.main.app.openapi()))" > openapi.json
|
||||||
|
node frontend/modify-openapi-operationids.js
|
||||||
|
mv openapi.json frontend/
|
||||||
|
cd frontend
|
||||||
|
npm run generate-client
|
||||||
|
npx biome format --write ./src/client
|
Reference in New Issue
Block a user