Skip to content

Commit

Permalink
feat(api): integrate OpenAPI client generator for frontend
Browse files Browse the repository at this point in the history
- Add OpenAPI CLI generator dependency for TypeScript-Axios library
- Implement resource entity fetching with generated client
- Update DTO types in NestJS server
- Add client library generation instructions to README
  • Loading branch information
jimmypalelil committed Jan 29, 2025
1 parent d8de089 commit 96491f2
Show file tree
Hide file tree
Showing 24 changed files with 2,958 additions and 242 deletions.
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ coverage.xml
.hypothesis/
.pytest_cache/
cover/
*/coverage



Expand All @@ -221,6 +222,7 @@ env.bak/
venv.bak/
# End of https://www.toptal.com/developers/gitignore/api/node,java,python,go
.idea
.run
*.key
*.pem
*.pub
Expand All @@ -239,3 +241,10 @@ venv.bak/
# Playwright
playwright-report/
test-results/

# Ignoring all the extra files generated by openapi-generator-cli
frontend/src/service/recreation-resource/.gitignore
frontend/src/service/recreation-resource/.npmignore
frontend/src/service/recreation-resource/.openapi-generator-ignore
frontend/src/service/recreation-resource/git_push.sh
frontend/src/service/recreation-resource/.openapi-generator
77 changes: 77 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,57 @@ npm install
npm run dev
```

### **OpenAPI/Swagger Documentation**

The backend uses NestJS's built-in Swagger module (@nestjs/swagger) to
automatically generate OpenAPI documentation from TypeScript decorators.

#### **Swagger Decorators**

Decorators are used throughout the codebase to provide metadata for API
documentation:

- @ApiTags() - Groups related endpoints together
- @ApiOperation() - Describes what an endpoint does
- @ApiResponse() - Documents possible response types
- @ApiProperty() - Documents DTO properties and their types

Example usage in a controller:

```tsx
@ApiTags("parks")
@Controller("parks")
export class ParksController {
@Get()
@ApiOperation({ summary: "Get all parks" })
@ApiResponse({
status: 200,
description: "List of parks returned",
type: [ParkDto],
})
findAll(): Promise<ParkDto[]> {
return this.parksService.findAll();
}
}
```

#### **Accessing Generated Documentation**

When running the backend server, Swagger UI is available at:

`http://localhost:3000/api/docs`

The raw OpenAPI specification can be accessed at:

`http://localhost:3000/api/docs-json`

This documentation is automatically generated from the TypeScript code and is
used to:

- Provide interactive API documentation through Swagger UI
- Generate TypeScript client types using openapi-generator
- Ensure API consistency and type safety across the frontend and backend

### Frontend

Create an `.env` file in the `frontend` directory using the example in
Expand All @@ -75,6 +126,32 @@ npm run dev

Navigate to `http://localhost:3000` in your web browser to view the application.

### Generate Client Library

#### Prerequisites

Install Java Development Kit (JDK) 17:

```bash
brew install openjdk@17
```

#### Generate TypeScript Axios Client

Run the following command to generate the TypeScript client library from your
OpenAPI specification:

```bash
npx openapi-generator-cli generate -i http://localhost:3000/api/docs-json -g typescript-axios -o src/service/recreation-resource --skip-validate-spec
```

This command will:

- Generate TypeScript client code using Axios
- Use the OpenAPI spec from your local NestJS server which should be running on
port **3000**
- Output the generated code to `src/service/recreation-resource` directory

## Pre-commit hooks

Pre-commit is set up to run checks for linting, formatting, and secrets.
Expand Down
1 change: 0 additions & 1 deletion backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { PaginatedRecreationResourceDto } from "./paginated-recreation-resouce.dto";
import { RecreationResourceDto } from "./recreation-resource.dto";

const recResourceArrayResolved: RecreationResourceDto[] = [
new RecreationResourceDto(),
new RecreationResourceDto(),
new RecreationResourceDto(),
new RecreationResourceDto(),
];

describe("PaginatedRecreationResourceDto", () => {
it("should validate the structure of paginated response", () => {
const paginatedResponse = new PaginatedRecreationResourceDto();
paginatedResponse.data = recResourceArrayResolved;
paginatedResponse.total = 4;
paginatedResponse.page = 1;
paginatedResponse.limit = 10;

expect(paginatedResponse).toHaveProperty("data");
expect(paginatedResponse).toHaveProperty("total");
expect(paginatedResponse).toHaveProperty("page");
expect(Array.isArray(paginatedResponse.data)).toBe(true);
expect(typeof paginatedResponse.total).toBe("number");
expect(typeof paginatedResponse.page).toBe("number");
});

it("should handle empty data array", () => {
const emptyPaginatedResponse = new PaginatedRecreationResourceDto();
emptyPaginatedResponse.data = [];
emptyPaginatedResponse.total = 0;
emptyPaginatedResponse.page = 1;
emptyPaginatedResponse.limit = 10;

expect(emptyPaginatedResponse.data).toHaveLength(0);
expect(emptyPaginatedResponse.total).toBe(0);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ApiProperty } from "@nestjs/swagger";
import { RecreationResourceDto } from "./recreation-resource.dto";

export class PaginatedRecreationResourceDto {
@ApiProperty({ type: [RecreationResourceDto] })
data: RecreationResourceDto[];

@ApiProperty()
total: number;

@ApiProperty()
page: number;

@ApiProperty()
limit: number;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import {
RecreationActivityDto,
RecreationStatusDto,
RecreationResourceDto,
} from "./recreation-resource.dto";

describe("Recreation DTOs", () => {
describe("RecreationActivityDto", () => {
it("should create a valid RecreationActivityDto instance", () => {
const activity = new RecreationActivityDto();
activity.recreation_activity_code = "HIKING";
activity.description = "Hiking trails available for all skill levels";

expect(activity instanceof RecreationActivityDto).toBeTruthy();
expect(activity.recreation_activity_code).toBeDefined();
expect(activity.description).toBeDefined();
});
});

describe("RecreationStatusDto", () => {
it("should create a valid RecreationStatusDto", () => {
const status = new RecreationStatusDto();
status.status_code = "CLOSED";
status.comment = "Temporary closure due to weather conditions";
status.description = "The facility is currently closed to visitors";

expect(status.status_code).toBeDefined();
expect(status.description).toBeDefined();
});

it("should allow null comment", () => {
const status: RecreationStatusDto = {
status_code: "OPEN",
comment: null,
description: "The facility is open",
};

expect(status.comment).toBeNull();
});
});

describe("RecreationResourceDto", () => {
it("should create a valid RecreationResourceDto", () => {
const resource: RecreationResourceDto = {
rec_resource_id: "rec-123-abc",
name: "Evergreen Valley Campground",
description:
"A scenic campground nestled in the heart of Evergreen Valley",
site_location: "123 Forest Road, Mountain View, CA 94043",
recreation_activity: [
{
recreation_activity_code: "HIKING",
description: "Hiking trails available for all skill levels",
},
],
recreation_status: {
status_code: "OPEN",
comment: null,
description: "The facility is open",
},
};

expect(resource.rec_resource_id).toBeDefined();
expect(resource.name.length).toBeGreaterThanOrEqual(1);
expect(resource.name.length).toBeLessThanOrEqual(100);
expect(Array.isArray(resource.recreation_activity)).toBeTruthy();
expect(resource.recreation_status).toBeDefined();
});

it("should allow null description", () => {
const resource: RecreationResourceDto = {
rec_resource_id: "rec-123-abc",
name: "Test Resource",
description: null,
site_location: "Test Location",
recreation_activity: [],
recreation_status: {
status_code: "OPEN",
comment: null,
description: "Open",
},
};

expect(resource.description).toBeNull();
});
});
});
67 changes: 52 additions & 15 deletions backend/src/recreation-resource/dto/recreation-resource.dto.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,77 @@
import { ApiProperty } from "@nestjs/swagger";

export class RecreationActivityDto {
@ApiProperty({
description: "Unique code identifying the recreation activity",
example: "HIKING",
})
recreation_activity_code: string;

@ApiProperty({
description: "Detailed description of the activity",
example: "Hiking trails available for all skill levels",
})
description: string;
}

export class RecreationStatusDto {
@ApiProperty({
description: "Status code of the resource",
})
status_code: string;

@ApiProperty({
description: "Additional status information",
example: "Temporary closure due to weather conditions",
nullable: true,
})
comment: string;

@ApiProperty({
description: "Detailed status description",
example: "The facility is currently closed to visitors",
})
description: string;
}

export class RecreationResourceDto {
@ApiProperty({
description: "The ID of the Recreation Resource",
description: "Unique identifier of the Recreation Resource",
example: "rec-123-abc",
format: "uuid",
})
rec_resource_id: string;

@ApiProperty({
description: "The name of the Recreation Resource",
description: "Official name of the Recreation Resource",
example: "Evergreen Valley Campground",
minLength: 1,
maxLength: 100,
})
name: string;

@ApiProperty({
description: "The description of the Recreation Resource",
description: "Detailed description of the Recreation Resource",
example: "A scenic campground nestled in the heart of Evergreen Valley",
nullable: true,
})
description: string;

@ApiProperty({
description: "The location of the Recreation Resource",
description: "Physical location of the Recreation Resource",
example: "123 Forest Road, Mountain View, CA 94043",
})
site_location: string;

@ApiProperty({
description: "The list of available activities at the Recreation Resource",
description: "List of recreational activities available at this resource",
type: [RecreationActivityDto],
})
recreation_activity: {
recreation_activity_code: string;
description: string;
}[];
recreation_activity: RecreationActivityDto[];

@ApiProperty({
description: "The status of the Recreation Resource",
description: "Current operational status of the Recreation Resource",
type: RecreationStatusDto,
})
recreation_status: {
status_code: string;
comment: string;
description: string;
};
recreation_status: RecreationStatusDto;
}
Loading

0 comments on commit 96491f2

Please sign in to comment.