Skip to content

Commit aaa8a03

Browse files
committed
Set up openapi-ts
1 parent a229adf commit aaa8a03

File tree

10 files changed

+81
-46
lines changed

10 files changed

+81
-46
lines changed

.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
frontend/bundles/
22
frontend/webpack_bundles/
3+
frontend/js/api/

.eslintrc.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,15 @@ module.exports = {
4646
version: "detect",
4747
},
4848
},
49+
overrides: [
50+
{
51+
files: ["openapi-ts.config.ts"],
52+
rules: {
53+
"import/no-extraneous-dependencies": [
54+
"error",
55+
{ devDependencies: true },
56+
],
57+
},
58+
},
59+
],
4960
};

.pre-commit-config.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,9 @@ repos:
6464
language: system
6565
files: ^backend/
6666
pass_filenames: false
67+
- id: frontend-api
68+
name: frontend-api-local
69+
entry: npm run openapi-ts
70+
language: system
71+
files: backend/schema\.yml$
72+
pass_filenames: false

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ docker_setup:
2020
docker compose build --no-cache backend
2121
docker compose run --rm backend python manage.py spectacular --color --file schema.yml
2222
docker compose run frontend npm install
23+
docker compose run --rm frontend npm run openapi-ts
2324

2425
docker_test:
2526
docker compose run backend python manage.py test $(ARG) --parallel --keepdb
@@ -51,3 +52,6 @@ docker_backend_shell:
5152

5253
docker_backend_update_schema:
5354
docker compose run --rm backend python manage.py spectacular --color --file schema.yml
55+
56+
docker_frontend_update_api:
57+
docker compose run --rm frontend npm run openapi-ts

backend/common/serializers.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from rest_framework import serializers
2+
3+
4+
class MessageSerializer(serializers.Serializer):
5+
message = serializers.CharField()

backend/common/views.py

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,34 @@
11
from django.views import generic
22

3-
from drf_spectacular.utils import OpenApiExample, OpenApiResponse, extend_schema
3+
from drf_spectacular.utils import OpenApiExample, extend_schema
44
from rest_framework import status, viewsets
55
from rest_framework.decorators import action
66
from rest_framework.permissions import AllowAny
77
from rest_framework.response import Response
88

9+
from .serializers import MessageSerializer
10+
911

1012
class IndexView(generic.TemplateView):
1113
template_name = "common/index.html"
1214

1315

1416
class RestViewSet(viewsets.ViewSet):
17+
serializer_class = MessageSerializer
18+
1519
@extend_schema(
1620
summary="Check REST API",
1721
description="This endpoint checks if the REST API is working.",
18-
responses={
19-
200: OpenApiResponse(
20-
description="Successful Response",
21-
examples=[
22-
OpenApiExample(
23-
"Example Response",
24-
value={
25-
"result": "This message comes from the backend. "
26-
"If you're seeing this, the REST API is working!"
27-
},
28-
response_only=True,
29-
)
30-
],
22+
examples=[
23+
OpenApiExample(
24+
"Successful Response",
25+
value={
26+
"message": "This message comes from the backend. "
27+
"If you're seeing this, the REST API is working!"
28+
},
29+
response_only=True,
3130
)
32-
},
31+
],
3332
methods=["GET"],
3433
)
3534
@action(
@@ -39,10 +38,11 @@ class RestViewSet(viewsets.ViewSet):
3938
url_path="rest-check",
4039
)
4140
def rest_check(self, request):
42-
return Response(
43-
{
44-
"result": "This message comes from the backend. "
41+
serializer = self.serializer_class(
42+
data={
43+
"message": "This message comes from the backend. "
4544
"If you're seeing this, the REST API is working!"
46-
},
47-
status=status.HTTP_200_OK,
45+
}
4846
)
47+
serializer.is_valid(raise_exception=True)
48+
return Response(serializer.data, status=status.HTTP_200_OK)

frontend/js/pages/Home.tsx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
import { useState, useEffect } from "react";
22
import Button from "react-bootstrap/Button";
3-
import { useDispatch, useSelector } from "react-redux";
43

54
import DjangoImgSrc from "../../assets/images/django-logo-negative.png";
6-
import { AppDispatch, RootState } from "../store";
7-
import { fetchRestCheck } from "../store/rest_check";
5+
import { RestService } from "../api";
86

97
const Home = () => {
10-
const dispatch: AppDispatch = useDispatch();
11-
const restCheck = useSelector((state: RootState) => state.restCheck);
12-
useEffect(() => {
13-
const action = fetchRestCheck();
14-
dispatch(action);
15-
}, [dispatch]);
16-
178
const [showBugComponent, setShowBugComponent] = useState(false);
9+
const [restCheck, setRestCheck] =
10+
useState<Awaited<ReturnType<typeof RestService.restRestCheckRetrieve>>>();
11+
12+
useEffect(() => {
13+
async function onFetchRestCheck() {
14+
setRestCheck(await RestService.restRestCheckRetrieve());
15+
}
16+
onFetchRestCheck();
17+
}, []);
1818

1919
return (
2020
<>
@@ -31,7 +31,7 @@ const Home = () => {
3131
<img alt="Django Negative Logo" src={DjangoImgSrc} />
3232
</div>
3333
<h2>Rest API</h2>
34-
<p>{restCheck?.data?.payload?.result}</p>
34+
<p>{restCheck?.message}</p>
3535
<Button variant="outline-dark" onClick={() => setShowBugComponent(true)}>
3636
Click to test if Sentry is capturing frontend errors! (Should only work
3737
in Production)
Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,18 @@
11
import { render, screen, waitFor } from "@testing-library/react";
2-
import { useDispatch, useSelector } from "react-redux";
32

4-
import { fetchRestCheck } from "../../store/rest_check";
3+
import { RestService } from "../../api";
54
import Home from "../Home";
65

7-
jest.mock("react-redux", () => ({
8-
useDispatch: jest.fn(),
9-
useSelector: jest.fn(),
10-
}));
11-
12-
jest.mock("../../store/rest_check", () => ({
13-
fetchRestCheck: jest.fn(),
6+
jest.mock("../../api", () => ({
7+
RestService: {
8+
restRestCheckRetrieve: jest.fn(),
9+
},
1410
}));
1511

1612
describe("Home", () => {
1713
beforeEach(() => {
18-
(useDispatch as unknown as jest.Mock).mockReturnValue(jest.fn());
19-
(useSelector as unknown as jest.Mock).mockReturnValue({
20-
data: { payload: { result: "Test Result" } },
14+
(RestService.restRestCheckRetrieve as jest.Mock).mockResolvedValue({
15+
message: "Test Result",
2116
});
2217
});
2318

@@ -33,11 +28,11 @@ describe("Home", () => {
3328
expect(await screen.findByText("Test Result")).toBeInTheDocument();
3429
});
3530

36-
test("dispatches fetchRestCheck action on mount", async () => {
31+
test("calls restRestCheckRetrieve on mount", async () => {
3732
render(<Home />);
3833

3934
await waitFor(() => {
40-
expect(fetchRestCheck).toHaveBeenCalledWith();
35+
expect(RestService.restRestCheckRetrieve).toHaveBeenCalled();
4136
});
4237
});
4338
});

openapi-ts.config.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { defineConfig } from "@hey-api/openapi-ts";
2+
3+
export default defineConfig({
4+
input: "backend/schema.yml",
5+
output: {
6+
path: "frontend/js/api",
7+
format: "prettier",
8+
},
9+
client: "axios",
10+
useOptions: true,
11+
});

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616
"build": "NODE_ENV=production tsc && webpack --progress --bail --mode=production",
1717
"lint": "eslint frontend --fix",
1818
"tsc": "tsc -p ./tsconfig.json --noEmit",
19-
"coverage": "jest --coverage"
19+
"coverage": "jest --coverage",
20+
"openapi-ts": "openapi-ts"
2021
},
2122
"dependencies": {
23+
"@hey-api/openapi-ts": "^0.44.0",
2224
"@reduxjs/toolkit": "~2.2.4",
2325
"@sentry/browser": "~8.0.0",
2426
"@sentry/react": "~8.0.0",

0 commit comments

Comments
 (0)