feat(platform): setup and configure orval (#10209)

This pull request sets up and configures Orval for API client
generation. It automates the process of creating TypeScript clients from
the backend's OpenAPI specification, improving development efficiency
and reducing manual code maintenance.

### Changes 🏗️

- Configures Orval with a new configuration file (`orval.config.ts`).
- Adds scripts to `package.json` for fetching the OpenAPI spec and
generating the API client.
- Implements a custom mutator for handling authentication.
- Adds API client generation as a step in the CI workflow.
- Adds `.gitignore` entry for generated API client files.
- Adds a security middleware to prevent caching of sensitive data.

### Checklist 📋

#### For code changes:
- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [x] I have tested my changes according to the test plan:
  - [x] Verified that the API client is generated correctly.
- [x] Confirmed that the custom mutator is functioning as expected for
authentication.
- [x] Ensured that the new CI workflow step for API client generation is
successful.
  - [x] Tested generated API calls

#### For configuration changes:
- [x] `.env.example` is updated or already compatible with my changes
- [ ] `docker-compose.yml` is updated or already compatible with my
changes
- [x] I have included a list of my configuration changes in the PR
description (under **Changes**)
This commit is contained in:
Abhimanyu Yadav
2025-06-24 19:30:19 +05:30
committed by GitHub
parent e701f41e66
commit 94aed94113
21 changed files with 7704 additions and 35 deletions

View File

@@ -0,0 +1,81 @@
import { getSupabaseClient } from "@/lib/supabase/getSupabaseClient";
const BASE_URL =
process.env.NEXT_PUBLIC_AGPT_SERVER_BASE_URL || "http://localhost:8006";
const getBody = <T>(c: Response | Request): Promise<T> => {
const contentType = c.headers.get("content-type");
if (contentType && contentType.includes("application/json")) {
return c.json();
}
if (contentType && contentType.includes("application/pdf")) {
return c.blob() as Promise<T>;
}
return c.text() as Promise<T>;
};
const getSupabaseToken = async () => {
const supabase = await getSupabaseClient();
const {
data: { session },
} = (await supabase?.auth.getSession()) || {
data: { session: null },
};
return session?.access_token;
};
export const customMutator = async <T = any>(
url: string,
options: RequestInit & {
params?: any;
} = {},
): Promise<T> => {
const { params, ...requestOptions } = options;
const method = (requestOptions.method || "GET") as
| "GET"
| "POST"
| "PUT"
| "DELETE"
| "PATCH";
const data = requestOptions.body;
const headers: Record<string, string> = {
...((requestOptions.headers as Record<string, string>) || {}),
};
const token = await getSupabaseToken();
if (token) {
headers["Authorization"] = `Bearer ${token}`;
}
const isFormData = data instanceof FormData;
// Currently, only two content types are handled here: application/json and multipart/form-data
if (!isFormData && data && !headers["Content-Type"]) {
headers["Content-Type"] = "application/json";
}
const queryString = params
? "?" + new URLSearchParams(params).toString()
: "";
const response = await fetch(`${BASE_URL}${url}${queryString}`, {
...requestOptions,
method,
headers,
body: data,
});
const response_data = await getBody<T>(response);
return {
status: response.status,
response_data,
headers: response.headers,
} as T;
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,57 @@
/**
* Transformer function for orval that fixes tags in OpenAPI spec.
* 1. Create a set of tags so we have unique values
* 2. Then remove public, private, v1, and v2 tags from tags array
* 3. Then arrange remaining tags alphabetically and only keep the first one
*
* @param {OpenAPIObject} inputSchema
* @return {OpenAPIObject}
*/
export const tagTransformer = (inputSchema) => {
const processedPaths = Object.entries(inputSchema.paths || {}).reduce(
(acc, [path, pathItem]) => ({
...acc,
[path]: Object.entries(pathItem || {}).reduce(
(pathItemAcc, [verb, operation]) => {
if (typeof operation === "object" && operation !== null) {
// 1. Create a set of tags so we have unique values
const uniqueTags = Array.from(new Set(operation.tags || []));
// 2. Remove public, private, v1, and v2 tags from tags array
const filteredTags = uniqueTags.filter(
(tag) =>
!["public", "private"].includes(tag.toLowerCase()) &&
!/^v[12]$/i.test(tag),
);
// 3. Arrange tags alphabetically and only keep the first one
const sortedTags = filteredTags.sort((a, b) => a.localeCompare(b));
const firstTag = sortedTags.length > 0 ? [sortedTags[0]] : [];
return {
...pathItemAcc,
[verb]: {
...operation,
tags: firstTag,
},
};
}
return {
...pathItemAcc,
[verb]: operation,
};
},
{},
),
}),
{},
);
return {
...inputSchema,
paths: processedPaths,
};
};
export default tagTransformer;