fix: openapi models for feature endpoints

This commit is contained in:
John Yeo
2025-10-25 10:15:16 +01:00
parent 202dd4ca76
commit cf76096537
16 changed files with 770 additions and 270 deletions

View File

@@ -13,6 +13,10 @@
- This codebase uses Bun as its preferred package manager and Node runtime.
- **ALWAYS import from `zod/v4`**, not from `zod` directly. Example: `import { z } from "zod/v4";`
- **ALWAYS use `.meta()` for zod-openapi schema registration**, NOT `.openapi()`. Example: `ApiProductSchema.meta({ id: "Product" })`
- Always prefer foo({ bar }) over foo(bar) method signatures - no matter if we are using only one argument or not, object as param are always better, as in the future when wanting to change the order of parameters, or add new ones - its easier.
- When creating "hooks" folders, don't nest them under "components"

View File

@@ -24,6 +24,8 @@
- Prefer Guard clauses "if(!admin) return;" over explicity "if(admin) do X;" Early returns are better
- Do not run "npx tsc" - run "tsc" instead as it is installed globally.
- **ALWAYS use `.meta()` for zod-openapi schema registration**, NOT `.openapi()`. Example: `ApiProductSchema.meta({ id: "Product" })`
## Error Handling in API Routes
- NEVER use `c.json({ message: "...", code: "..." }, statusCode)` pattern for input validation or expected errors in Hono routes
- ALWAYS throw `RecaseError` from `@autumn/shared` for all validation errors, not found errors, forbidden errors, etc.

View File

@@ -62,6 +62,6 @@ export const handleDeleteFeature = async (req: any, res: any) =>
env: req.env,
});
res.status(200).json({ message: "Feature deleted" });
res.status(200).json({ success: true });
},
});

View File

@@ -0,0 +1,456 @@
import { writeFileSync } from "node:fs";
import {
getListResponseSchema,
SuccessResponseSchema,
} from "@api/common/commonResponses.js";
import yaml from "yaml";
import { z } from "zod/v4";
import { createDocument, type ZodOpenApiPathsObject } from "zod-openapi";
import { UpdateBalancesParamsSchema } from "../balances/updateBalanceModels.js";
import { SetUsageParamsSchema } from "../balances/usageModels.js";
import { CustomerDataSchema } from "../common/customerData.js";
import { EntityDataSchema } from "../common/entityData.js";
import { setUsageJsDoc } from "../common/jsDocs.js";
import { ApiEntityWithMeta } from "../entities/entitiesOpenApi.js";
import {
ApiCusFeatureSchema,
ApiCusProductSchema,
ApiCustomerSchema,
ApiFeatureSchema,
ApiProductItemSchema,
CreateCustomerParamsSchema,
CreateCustomerQuerySchema,
CreateFeatureParamsSchema,
FEATURE_EXAMPLE,
GetCustomerQuerySchema,
ListCustomersResponseSchema,
UpdateCustomerParamsSchema,
UpdateFeatureParamsSchema,
} from "../models.js";
import { ApiProductSchema, PRODUCT_EXAMPLE } from "../products/apiProduct.js";
import {
CreateProductV2ParamsSchema,
UpdateProductV2ParamsSchema,
} from "../products/productOpModels.js";
// Register schema with .meta() for OpenAPI spec generation
export const ApiProductWithMeta = ApiProductSchema.meta({
id: "Product",
examples: [PRODUCT_EXAMPLE],
});
// Register the schema with .meta() for OpenAPI spec generation
const ApiFeatureWithMeta = ApiFeatureSchema.extend({
type: z.enum(["boolean", "single_use", "continuous_use", "credit_system"]),
}).meta({
id: "Feature",
examples: [FEATURE_EXAMPLE],
});
// Register schema with .meta() for OpenAPI spec generation
const ApiCustomerWithMeta = ApiCustomerSchema.meta({
id: "Customer",
});
const productOps = {
"/products": {
get: {
summary: "List Products",
tags: ["products"],
responses: {
"200": {
description: "",
content: {
"application/json": {
schema: z.object({
list: z.array(ApiProductWithMeta),
}),
},
},
},
},
},
post: {
summary: "Create Product",
tags: ["products"],
requestBody: {
content: {
"application/json": { schema: CreateProductV2ParamsSchema },
},
},
responses: {
"200": {
description: "",
content: { "application/json": { schema: ApiProductWithMeta } },
},
},
},
},
"/products/{product_id}": {
get: {
summary: "Get Product",
tags: ["products"],
requestParams: {
path: z.object({
product_id: z.string(),
}),
},
responses: {
"200": {
description: "",
content: { "application/json": { schema: ApiProductWithMeta } },
},
},
},
post: {
summary: "Update Product",
tags: ["products"],
requestParams: {
path: z.object({
product_id: z.string(),
}),
},
requestBody: {
content: {
"application/json": { schema: UpdateProductV2ParamsSchema },
},
},
responses: {
"200": {
description: "",
content: { "application/json": { schema: ApiProductWithMeta } },
},
},
},
delete: {
summary: "Delete Product",
tags: ["products"],
requestParams: {
path: z.object({
product_id: z.string(),
}),
query: z.object({
all_versions: z.boolean().optional(),
}),
},
responses: {
"200": {
description: "",
content: {
"application/json": {
schema: SuccessResponseSchema,
},
},
},
},
},
},
};
const featureOps = {
"/features": {
get: {
summary: "List Features",
tags: ["features"],
responses: {
"200": {
description: "",
content: {
"application/json": {
schema: getListResponseSchema({ schema: ApiFeatureWithMeta }),
},
},
},
},
},
post: {
summary: "Create Feature",
tags: ["features"],
requestBody: {
content: {
"application/json": { schema: CreateFeatureParamsSchema },
},
},
responses: {
"200": {
description: "",
content: { "application/json": { schema: ApiFeatureWithMeta } },
},
},
},
},
"/features/{feature_id}": {
get: {
summary: "Get Feature",
tags: ["features"],
requestParams: {
path: z.object({
feature_id: z.string(),
}),
},
responses: {
"200": {
description: "",
content: { "application/json": { schema: ApiFeatureWithMeta } },
},
},
},
post: {
summary: "Update Feature",
tags: ["features"],
requestParams: {
path: z.object({
feature_id: z.string(),
}),
},
requestBody: {
content: {
"application/json": { schema: UpdateFeatureParamsSchema },
},
},
responses: {
"200": {
description: "",
content: { "application/json": { schema: ApiFeatureWithMeta } },
},
},
},
delete: {
summary: "Delete Feature",
tags: ["features"],
requestParams: {
path: z.object({
feature_id: z.string(),
}),
},
responses: {
"200": {
description: "",
content: {
"application/json": {
schema: SuccessResponseSchema,
},
},
},
},
},
},
};
const customerOps = {
"/customers": {
get: {
summary: "List Customers",
tags: ["customers"],
requestParams: {
query: z.object({
limit: z.number().int().min(10).max(100).optional(),
offset: z.number().int().min(0).optional(),
}),
},
responses: {
"200": {
description: "200 OK",
content: {
"application/json": { schema: ListCustomersResponseSchema },
},
},
},
},
post: {
summary: "Create Customer",
tags: ["customers"],
requestParams: {
query: CreateCustomerQuerySchema,
},
requestBody: {
content: {
"application/json": { schema: CreateCustomerParamsSchema },
},
},
responses: {
"200": {
description: "200 OK",
content: { "application/json": { schema: ApiCustomerWithMeta } },
},
},
},
},
"/customers/{customer_id}": {
get: {
summary: "Get Customer",
tags: ["customers"],
requestParams: {
path: z.object({
customer_id: z.string(),
}),
query: GetCustomerQuerySchema,
},
responses: {
"200": {
description: "200 OK",
content: { "application/json": { schema: ApiCustomerWithMeta } },
},
},
},
post: {
summary: "Update Customer",
tags: ["customers"],
requestParams: {
path: z.object({
customer_id: z.string(),
}),
query: z.object({
expand: z.string().optional(),
}),
},
requestBody: {
content: {
"application/json": { schema: UpdateCustomerParamsSchema },
},
},
responses: {
"200": {
description: "200 OK",
content: { "application/json": { schema: ApiCustomerWithMeta } },
},
},
},
delete: {
summary: "Delete Customer",
tags: ["customers"],
requestParams: {
path: z.object({
customer_id: z.string(),
}),
},
responses: {
"200": {
description: "200 OK",
content: {
"application/json": {
schema: SuccessResponseSchema,
},
},
},
},
},
},
"/customers/{customer_id}/balances": {
post: {
summary: "Set Feature Balances",
description: "Set the balance of a feature for a specific customer",
tags: ["customers"],
requestParams: {
path: z.object({
customer_id: z.string(),
}),
},
requestBody: {
content: {
"application/json": { schema: UpdateBalancesParamsSchema },
},
},
responses: {
"200": {
description: "",
content: {
"application/json": { schema: SuccessResponseSchema },
},
},
},
},
},
};
const coreOps: ZodOpenApiPathsObject = {
"/usage": {
post: {
summary: "Set Usage",
description: setUsageJsDoc,
tags: ["core"],
requestBody: {
content: {
"application/json": { schema: SetUsageParamsSchema },
},
},
responses: {
"200": {
description: "",
content: { "application/json": { schema: SuccessResponseSchema } },
},
},
},
},
};
const OPENAPI_1_2_0 = createDocument(
{
openapi: "3.1.0",
info: {
title: "Autumn API",
version: "1.2.0",
},
servers: [
{
url: "https://api.useautumn.com",
description: "Production server",
},
],
security: [
{
secretKey: [],
},
],
components: {
schemas: {
CustomerData: CustomerDataSchema,
EntityData: EntityDataSchema.meta({
id: "EntityData",
description: "Entity data for creating an entity",
}),
Customer: ApiCustomerWithMeta,
CustomerProduct: ApiCusProductSchema,
CustomerFeature: ApiCusFeatureSchema.meta({
id: "CustomerFeature",
description: "Customer feature object returned by the API",
}),
Product: ApiProductWithMeta,
ProductItem: ApiProductItemSchema,
Feature: ApiFeatureWithMeta,
Entity: ApiEntityWithMeta,
},
securitySchemes: {
secretKey: {
type: "http",
scheme: "bearer",
bearerFormat: "JWT",
},
},
},
paths: {
...productOps,
...featureOps,
...customerOps,
...coreOps,
},
},
{
// Disable the "Output" suffix that zod-openapi adds to response schemas
outputIdSuffix: "",
},
);
export const writeOpenApi_1_2_0 = () => {
const yamlContent = yaml.stringify(
JSON.parse(JSON.stringify(OPENAPI_1_2_0, null, 2)),
);
writeFileSync(
`${process.env.STAINLESS_PATH?.replace("\\ ", " ")}/openapi.yml`,
yamlContent,
"utf8",
);
};

View File

@@ -0,0 +1,25 @@
import { z } from "zod/v4";
export const UpdateBalancesParamsSchema = z
.object({
balances: z.array(
z.object({
feature_id: z.string().meta({
description: "The ID of the feature to update balance for.",
}),
balance: z.number().meta({
description: "The new balance value.",
}),
}),
),
})
.meta({
example: {
balances: [
{
feature_id: "tokens",
balance: 1000,
},
],
},
});

View File

@@ -0,0 +1,22 @@
import { z } from "zod/v4";
import { CustomerDataSchema } from "../common/customerData.js";
export const SetUsageParamsSchema = z.object({
customer_id: z.string().nonempty().meta({
description: "The ID of the customer.",
}),
feature_id: z.string().meta({
description: "The ID of the feature to set usage for.",
}),
value: z.number().meta({
description:
"The value you want to set this customer's usage of the feature to.",
}),
entity_id: z.string().optional().meta({
description: "The ID of the entity to set usage for.",
}),
customer_data: CustomerDataSchema.optional(),
});

View File

@@ -2,31 +2,29 @@ import { z } from "zod/v4";
// Base schema without top-level .meta() to avoid side effects during imports
// Individual field descriptions are kept as they don't cause registry conflicts
export const CustomerDataSchema = z.object({
export const CustomerDataSchema = z
.object({
name: z.string().nullish().meta({
description: "Customer's name",
example: "John Doe",
}),
email: z.string().nullish().meta({
description: "Customer's email address",
example: "john@example.com",
}),
fingerprint: z.string().nullish().meta({
description:
"Unique identifier (eg, serial number) to detect duplicate customers and prevent free trial abuse",
example: "fp_123abc",
}),
metadata: z
.record(z.any(), z.any())
.nullish()
.meta({
metadata: z.record(z.any(), z.any()).nullish().meta({
description: "Additional metadata for the customer",
example: { company: "Acme Inc" },
}),
stripe_id: z.string().nullish().meta({
description: "Stripe customer ID if you already have one",
example: "cus_stripe123",
}),
});
})
.meta({
id: "CustomerData",
description:
"Used to add customer details like name or email when auto-creating a customer.",
});
export type CustomerData = z.infer<typeof CustomerDataSchema>;

View File

@@ -1,5 +1,10 @@
import { ExtAttachBodySchema, ExtCheckoutParamsSchema } from "@api/models.js";
import { createJSDocDescription, docLink, example } from "@api/utils/openApiHelpers.js";
import {
createJSDocDescription,
docLink,
example,
} from "@api/utils/openApiHelpers.js";
import { SetUsageParamsSchema } from "../balances/usageModels.js";
import { CheckParamsSchema } from "../core/checkModels.js";
import {
BillingPortalParamsSchema,
@@ -104,8 +109,7 @@ export const trackJsDoc = createJSDocDescription({
});
export const cancelJsDoc = createJSDocDescription({
description:
"Cancel a customer's subscription to a product.",
description: "Cancel a customer's subscription to a product.",
whenToUse:
"Use this when a customer wants to stop their subscription. Supports immediate or end-of-period cancellation.",
body: CancelBodySchema,
@@ -199,3 +203,27 @@ export const queryJsDoc = createJSDocDescription({
}),
],
});
export const setUsageJsDoc = createJSDocDescription({
description:
"Set usage for a feature. This is similar to /track instead of incrementing usage, it sets the usage value to exactly what is provided.",
whenToUse: "Use this to set usage for a feature instead of incrementing it.",
body: SetUsageParamsSchema,
examples: [
example({
values: {
customer_id: "123",
feature_id: "api_calls",
value: 10000,
},
description: "Set usage for a feature",
}),
],
methodName: "usage",
docs: [
docLink({
url: "https://docs.useautumn.com/api-reference/core/usage",
title: "Set Usage",
}),
],
});

View File

@@ -5,6 +5,8 @@ import {
ExtCheckoutParamsSchema,
} from "@api/models.js";
import type { ZodOpenApiPathsObject } from "zod-openapi";
import { SetUsageParamsSchema } from "../balances/usageModels.js";
import { SuccessResponseSchema } from "../common/commonResponses.js";
import {
attachJsDoc,
billingPortalJsDoc,
@@ -12,6 +14,7 @@ import {
checkJsDoc,
checkoutJsDoc,
queryJsDoc,
setUsageJsDoc,
setupPaymentJsDoc,
trackJsDoc,
} from "../common/jsDocs.js";
@@ -118,24 +121,7 @@ export const coreOps: ZodOpenApiPathsObject = {
},
},
},
"/usage": {
post: {
summary: "Set Usage",
description: "Set or increment usage for a metered feature. This is similar to /track but specifically for usage-based features with the set_usage flag enabled by default.",
tags: ["core"],
requestBody: {
content: {
"application/json": { schema: TrackParamsSchema },
},
},
responses: {
"200": {
description: "200 OK",
content: { "application/json": { schema: TrackResultSchema } },
},
},
},
},
"/query": {
post: {
summary: "Query Analytics",
@@ -210,4 +196,23 @@ export const coreOps: ZodOpenApiPathsObject = {
},
},
},
"/usage": {
post: {
summary: "Set Usage",
description: setUsageJsDoc,
tags: ["core"],
requestBody: {
content: {
"application/json": { schema: SetUsageParamsSchema },
},
},
responses: {
"200": {
description: "200 OK",
content: { "application/json": { schema: SuccessResponseSchema } },
},
},
},
},
};

View File

@@ -177,45 +177,7 @@ export const ListCustomersResponseSchema = z.object({
}),
});
// Update Balances Params (based on handleUpdateBalances logic)
export const UpdateBalancesParamsSchema = z.object({
balances: z
.array(
z.object({
feature_id: z.string().meta({
description: "Feature ID to update balance for",
example: "api_calls",
}),
balance: z.number().optional().meta({
description: "New balance value (required if unlimited is not true)",
example: 1000,
}),
unlimited: z.boolean().optional().meta({
description: "Set to true to make the feature unlimited",
example: false,
}),
interval: z.string().optional().meta({
description:
"Interval to match for balance update (e.g., 'month', 'year')",
example: "month",
}),
interval_count: z.number().optional().meta({
description: "Interval count to match for balance update",
example: 1,
}),
entity_id: z.string().optional().meta({
description: "Entity ID for entity-specific balance updates",
example: "entity_123",
}),
}),
)
.meta({
description: "Array of feature balances to update",
}),
});
export type CreateCustomerParams = z.infer<typeof CreateCustomerParamsSchema>;
export type UpdateCustomerParams = z.infer<typeof UpdateCustomerParamsSchema>;
export type ListCustomersQuery = z.infer<typeof ListCustomersQuerySchema>;
export type ListCustomersResponse = z.infer<typeof ListCustomersResponseSchema>;
export type UpdateBalancesParams = z.infer<typeof UpdateBalancesParamsSchema>;

View File

@@ -1,4 +1,5 @@
import { z } from "zod/v4";
import { UpdateBalancesParamsSchema } from "../balances/updateBalanceModels.js";
import { SuccessResponseSchema } from "../common/commonResponses.js";
import { ApiCustomerSchema } from "./apiCustomer.js";
import {
@@ -6,7 +7,6 @@ import {
CreateCustomerQuerySchema,
GetCustomerQuerySchema,
ListCustomersResponseSchema,
UpdateBalancesParamsSchema,
UpdateCustomerParamsSchema,
} from "./customerOpModels.js";

View File

@@ -9,47 +9,63 @@ export enum ApiFeatureType {
CreditSystem = "credit_system",
}
export const FEATURE_EXAMPLE = {
id: "tokens",
name: "Tokens",
type: "single_use",
display: {
singular: "token",
plural: "tokens",
},
credit_schema: null,
archived: false,
};
// Base schema without .meta() to avoid side effects during imports
export const ApiFeatureSchema = z.object({
id: z.string().meta({
description: "The unique identifier of the feature",
example: "<string>",
description:
"The ID of the feature, used to refer to it in other API calls like /track or /check.",
}),
name: z.string().nullish().meta({
description: "The name of the feature",
example: "<string>",
description: "The name of the feature.",
}),
type: z.enum(ApiFeatureType).meta({
description: "The type of the feature",
example: "<string>",
}),
display: z
.object({
singular: z.string(),
plural: z.string(),
singular: z.string().meta({
description: "The singular display name for the feature.",
}),
plural: z.string().meta({
description: "The plural display name for the feature.",
}),
})
.nullish()
.meta({
description: "Display names for the feature",
example: { singular: "<string>", plural: "<string>" },
description: "Singular and plural display names for the feature.",
}),
credit_schema: z
.array(
z.object({
metered_feature_id: z.string(),
credit_cost: z.number(),
metered_feature_id: z.string().meta({
description:
"The ID of the metered feature (should be a single_use feature).",
}),
credit_cost: z.number().meta({
description: "The credit cost of the metered feature.",
}),
}),
)
.nullish()
.meta({
description: "Credit cost schema for credit system features",
example: [{ metered_feature_id: "<string>", credit_cost: 123 }],
description: "Credit cost schema for credit system features.",
}),
archived: z.boolean().nullish().meta({
description: "Whether or not the feature is archived",
example: false,
description: "Whether or not the feature is archived.",
}),
});

View File

@@ -1,30 +1,33 @@
import { z } from "zod/v4";
import { ApiFeatureType } from "./apiFeature.js";
const featureDescriptions = {
id: "The ID of the feature. This is used to refer to it in other API calls like /track or /check.",
name: "The name of the feature.",
type: "The type of the feature. 'single_use' features are consumed, like API calls, tokens, or messages. 'continuous_use' features are allocated, like seats, workspaces, or projects. 'credit_system' features are schemas that unify multiple 'single_use' features into a single credit system.",
display:
"Singular and plural display names for the feature in your user interface.",
credit_schema:
"A schema that maps 'single_use' feature IDs to credit costs. Applicable only for 'credit_system' features.",
archived:
"Whether the feature is archived. Archived features are hidden from the dashboard and list features endpoint.",
};
// Create Feature Params
export const CreateFeatureParamsSchema = z.object({
id: z.string().meta({
description: "The ID of the feature",
example: "feature_123",
}),
name: z.string().nullish().meta({
description: "The name of the feature",
example: "API Calls",
}),
type: z.enum(ApiFeatureType).meta({
description: "The type of the feature",
example: "single_use",
}),
id: z.string().meta({ description: featureDescriptions.id }),
name: z.string().nullish().meta({ description: featureDescriptions.name }),
type: z.enum(ApiFeatureType).meta({ description: featureDescriptions.type }),
display: z
.object({
singular: z.string(),
plural: z.string(),
})
.nullish()
.meta({
description: "Display names for the feature",
example: { singular: "API Call", plural: "API Calls" },
}),
.meta({ description: featureDescriptions.display }),
credit_schema: z
.array(
z.object({
@@ -33,37 +36,25 @@ export const CreateFeatureParamsSchema = z.object({
}),
)
.nullish()
.meta({
description:
"Credit schema for credit system features (only applicable when type is credit_system)",
example: [{ metered_feature_id: "api_calls", credit_cost: 10 }],
}),
.meta({ description: featureDescriptions.credit_schema }),
});
// Update Feature Params
export const UpdateFeatureParamsSchema = z.object({
id: z.string().optional().meta({
description: "The ID of the feature",
example: "feature_123",
}),
name: z.string().optional().meta({
description: "The name of the feature",
example: "API Calls",
}),
type: z.enum(ApiFeatureType).optional().meta({
description: "The type of the feature",
example: "single_use",
}),
id: z.string().optional().meta({ description: featureDescriptions.id }),
name: z.string().optional().meta({ description: featureDescriptions.name }),
type: z
.enum(ApiFeatureType)
.optional()
.meta({ description: featureDescriptions.type }),
display: z
.object({
singular: z.string(),
plural: z.string(),
})
.optional()
.meta({
description: "Display names for the feature",
example: { singular: "API Call", plural: "API Calls" },
}),
.meta({ description: featureDescriptions.display }),
credit_schema: z
.array(
z.object({
@@ -72,15 +63,11 @@ export const UpdateFeatureParamsSchema = z.object({
}),
)
.optional()
.meta({
description:
"Credit schema for credit system features (only applicable when type is credit_system)",
example: [{ metered_feature_id: "api_calls", credit_cost: 10 }],
}),
archived: z.boolean().optional().meta({
description: "Whether the feature is archived",
example: false,
}),
.meta({ description: featureDescriptions.credit_schema }),
archived: z
.boolean()
.optional()
.meta({ description: featureDescriptions.archived }),
});
export type CreateFeatureParams = z.infer<typeof CreateFeatureParamsSchema>;

View File

@@ -3,16 +3,19 @@ import {
getListResponseSchema,
SuccessResponseSchema,
} from "../common/commonResponses.js";
import { ApiFeatureSchema } from "./apiFeature.js";
import { ApiFeatureSchema, FEATURE_EXAMPLE } from "./apiFeature.js";
import {
CreateFeatureParamsSchema,
UpdateFeatureParamsSchema,
} from "./featureOpModels.js";
// Register the schema with .meta() for OpenAPI spec generation
export const ApiFeatureWithMeta = ApiFeatureSchema.meta({
export const ApiFeatureWithMeta = ApiFeatureSchema.extend({
type: z.enum(["boolean", "single_use", "continuous_use", "credit_system"]),
}).meta({
id: "Feature",
description: "Feature object returned by the API",
description: "",
example: FEATURE_EXAMPLE,
});
export const featureOps = {
@@ -20,14 +23,9 @@ export const featureOps = {
get: {
summary: "List Features",
tags: ["features"],
requestParams: {
query: z.object({
include_archived: z.boolean().optional(),
}),
},
responses: {
"200": {
description: "200 OK",
description: "",
content: {
"application/json": {
schema: getListResponseSchema({ schema: ApiFeatureWithMeta }),
@@ -46,24 +44,24 @@ export const featureOps = {
},
responses: {
"200": {
description: "200 OK",
description: "",
content: { "application/json": { schema: ApiFeatureWithMeta } },
},
},
},
},
"/features/{featureId}": {
"/features/{feature_id}": {
get: {
summary: "Get Feature",
tags: ["features"],
requestParams: {
path: z.object({
featureId: z.string(),
feature_id: z.string(),
}),
},
responses: {
"200": {
description: "200 OK",
description: "",
content: { "application/json": { schema: ApiFeatureWithMeta } },
},
},
@@ -73,7 +71,7 @@ export const featureOps = {
tags: ["features"],
requestParams: {
path: z.object({
featureId: z.string(),
feature_id: z.string(),
}),
},
requestBody: {
@@ -83,7 +81,7 @@ export const featureOps = {
},
responses: {
"200": {
description: "200 OK",
description: "",
content: { "application/json": { schema: ApiFeatureWithMeta } },
},
},
@@ -93,12 +91,12 @@ export const featureOps = {
tags: ["features"],
requestParams: {
path: z.object({
featureId: z.string(),
feature_id: z.string(),
}),
},
responses: {
"200": {
description: "200 OK",
description: "",
content: {
"application/json": {
schema: SuccessResponseSchema,

View File

@@ -1,120 +1,117 @@
import "dotenv/config";
// import "dotenv/config";
// import { execSync } from "node:child_process";
// import { existsSync, writeFileSync } from "node:fs";
// import yaml from "yaml";
// import { createDocument } from "zod-openapi";
// import { OPENAPI_1_2_0 } from "./_prevVersions/openapi1.2.0.js";
// import { CustomerDataSchema } from "./common/customerData.js";
// import { EntityDataSchema } from "./common/entityData.js";
// import { coreOps } from "./core/coreOpenApi.js";
// import { ApiCusFeatureSchema } from "./customers/cusFeatures/apiCusFeature.js";
// import { ApiCusProductSchema } from "./customers/cusProducts/apiCusProduct.js";
// import {
// ApiCustomerWithMeta,
// customerOps,
// } from "./customers/customersOpenApi.js";
// import { ApiEntityWithMeta } from "./entities/entitiesOpenApi.js";
// import { ApiFeatureWithMeta, featureOps } from "./features/featuresOpenApi.js";
// import { ApiProductItemSchema } from "./products/apiProductItem.js";
// import { ApiProductWithMeta, productOps } from "./products/productsOpenApi.js";
// const API_VERSION = "1.2.0";
// const document = createDocument(
// {
// openapi: "3.1.0",
// info: {
// title: "Autumn API",
// version: API_VERSION,
// },
// servers: [
// {
// url: "https://api.useautumn.com",
// description: "Production server",
// },
// ],
// security: [
// {
// secretKey: [],
// },
// ],
// components: {
// schemas: {
// CustomerData: CustomerDataSchema,
// EntityData: EntityDataSchema.meta({
// id: "EntityData",
// description: "Entity data for creating an entity",
// }),
// Customer: ApiCustomerWithMeta,
// CustomerProduct: ApiCusProductSchema,
// CustomerFeature: ApiCusFeatureSchema.meta({
// id: "CustomerFeature",
// description: "Customer feature object returned by the API",
// }),
// Product: ApiProductWithMeta,
// ProductItem: ApiProductItemSchema,
// Feature: ApiFeatureWithMeta,
// Entity: ApiEntityWithMeta,
// },
// securitySchemes: {
// secretKey: {
// type: "http",
// scheme: "bearer",
// bearerFormat: "JWT",
// },
// },
// },
// paths: {
// ...coreOps,
// ...customerOps,
// ...productOps,
// ...featureOps,
// // ...entityOps,
// // ...referralOps,
// },
// },
// {
// // Disable the "Output" suffix that zod-openapi adds to response schemas
// outputIdSuffix: "",
// },
// );
import { execSync } from "node:child_process";
import { existsSync, writeFileSync } from "node:fs";
import yaml from "yaml";
import { createDocument } from "zod-openapi";
import { CustomerDataSchema } from "./common/customerData.js";
import { EntityDataSchema } from "./common/entityData.js";
import { ApiCusFeatureSchema } from "./customers/cusFeatures/apiCusFeature.js";
import { ApiCusProductSchema } from "./customers/cusProducts/apiCusProduct.js";
import { ApiCustomerWithMeta } from "./customers/customersOpenApi.js";
import { ApiEntityWithMeta } from "./entities/entitiesOpenApi.js";
import { ApiFeatureWithMeta } from "./features/featuresOpenApi.js";
import { ApiProductItemSchema } from "./products/apiProductItem.js";
import { ApiProductWithMeta, productOps } from "./products/productsOpenApi.js";
import { existsSync } from "node:fs";
import { writeOpenApi_1_2_0 } from "./_prevVersions/openapi1.2.0.js";
const API_VERSION = "1.2.0";
const document = createDocument(
{
openapi: "3.1.0",
info: {
title: "Autumn API",
version: API_VERSION,
},
servers: [
{
url: "https://api.useautumn.com",
description: "Production server",
},
],
security: [
{
secretKey: [],
},
],
components: {
schemas: {
CustomerData: CustomerDataSchema.meta({
id: "CustomerData",
description: "Customer data for creating or updating a customer",
}),
EntityData: EntityDataSchema.meta({
id: "EntityData",
description: "Entity data for creating an entity",
}),
Customer: ApiCustomerWithMeta,
CustomerProduct: ApiCusProductSchema,
CustomerFeature: ApiCusFeatureSchema.meta({
id: "CustomerFeature",
description: "Customer feature object returned by the API",
}),
Product: ApiProductWithMeta,
ProductItem: ApiProductItemSchema,
Feature: ApiFeatureWithMeta,
Entity: ApiEntityWithMeta,
},
securitySchemes: {
secretKey: {
type: "http",
scheme: "bearer",
bearerFormat: "JWT",
},
},
},
paths: {
...productOps,
// ...coreOps,
// ...featureOps,
// ...customerOps,
// ...entityOps,
// ...referralOps,
},
},
{
// Disable the "Output" suffix that zod-openapi adds to response schemas
outputIdSuffix: "",
},
);
// import { OPENAPI_1_2_0 } from "./_prevVersions/openapi1.2.0.js";
// Export to YAML file during build
if (process.env.NODE_ENV !== "production") {
try {
// If --no-build flag is present, return after writing openapi.yml
if (process.argv.includes("--no-build")) {
const yamlContent = yaml.stringify(
JSON.parse(JSON.stringify(document, null, 2)),
);
writeFileSync(
`${process.env.STAINLESS_PATH?.replace("\\ ", " ")}/openapi.yml`,
yamlContent,
"utf8",
);
writeOpenApi_1_2_0();
console.log(
`OpenAPI document exported to ${process.env.STAINLESS_PATH}/openapi.yml`,
);
process.exit(0);
}
// Convert to JSON first to strip out Zod schemas and function references
const jsonStr = JSON.stringify(document, null, 2);
// Convert JSON to YAML (this avoids function serialization issues)
const jsonObj = JSON.parse(jsonStr);
const yamlContent = yaml.stringify(jsonObj);
if (process.env.STAINLESS_PATH) {
writeFileSync(
`${process.env.STAINLESS_PATH.replace("\\ ", " ")}/openapi.yml`,
yamlContent,
"utf8",
);
writeOpenApi_1_2_0();
// writeFileSync(
// `${process.env.STAINLESS_PATH.replace("\\ ", " ")}/openapi.yml`,
// yamlContent,
// "utf8",
// );
console.log(
`OpenAPI document exported to ${process.env.STAINLESS_PATH}/openapi.yml`,
);
// console.log(
// `OpenAPI document exported to ${process.env.STAINLESS_PATH}/openapi.yml`,
// );
// Run the run.sh script if it exists
const runScriptPath = `${process.env.STAINLESS_PATH.replace("\\ ", " ")}/run.sh`;

View File

@@ -8,7 +8,7 @@ import {
// Register schema with .meta() for OpenAPI spec generation
export const ApiProductWithMeta = ApiProductSchema.meta({
id: "Product",
// id: "Product",
examples: [PRODUCT_EXAMPLE],
});
@@ -17,11 +17,11 @@ export const productOps = {
get: {
summary: "List Products",
tags: ["products"],
requestParams: {
query: z.object({
customer_id: z.string().optional(),
}),
},
// requestParams: {
// query: z.object({
// customer_id: z.string().optional(),
// }),
// },
responses: {
"200": {
description: "",