fix: openapi models for feature endpoints
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 });
|
||||
},
|
||||
});
|
||||
|
||||
456
shared/api/_prevVersions/openapi1.2.0.ts
Normal file
456
shared/api/_prevVersions/openapi1.2.0.ts
Normal 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",
|
||||
);
|
||||
};
|
||||
25
shared/api/balances/updateBalanceModels.ts
Normal file
25
shared/api/balances/updateBalanceModels.ts
Normal 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,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
22
shared/api/balances/usageModels.ts
Normal file
22
shared/api/balances/usageModels.ts
Normal 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(),
|
||||
});
|
||||
@@ -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({
|
||||
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({
|
||||
description: "Additional metadata for the customer",
|
||||
example: { company: "Acme Inc" },
|
||||
export const CustomerDataSchema = z
|
||||
.object({
|
||||
name: z.string().nullish().meta({
|
||||
description: "Customer's name",
|
||||
}),
|
||||
stripe_id: z.string().nullish().meta({
|
||||
description: "Stripe customer ID if you already have one",
|
||||
example: "cus_stripe123",
|
||||
}),
|
||||
});
|
||||
email: z.string().nullish().meta({
|
||||
description: "Customer's email address",
|
||||
}),
|
||||
fingerprint: z.string().nullish().meta({
|
||||
description:
|
||||
"Unique identifier (eg, serial number) to detect duplicate customers and prevent free trial abuse",
|
||||
}),
|
||||
metadata: z.record(z.any(), z.any()).nullish().meta({
|
||||
description: "Additional metadata for the customer",
|
||||
}),
|
||||
stripe_id: z.string().nullish().meta({
|
||||
description: "Stripe customer ID if you already have one",
|
||||
}),
|
||||
})
|
||||
.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>;
|
||||
|
||||
@@ -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",
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -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 } },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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.",
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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`;
|
||||
|
||||
@@ -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: "",
|
||||
|
||||
Reference in New Issue
Block a user