Skip to content

Commit e1765fc

Browse files
OpenAPI docs: Added server config. Added authentication information and applicable doc links. Added specs for /user-query, /project/start, /project/stop, /purchase/get-purchases, and standardized some JSON returned by the API.
1 parent a51c031 commit e1765fc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+559
-71
lines changed

src/packages/next/lib/api/schema/accounts/search.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { AccountUserSchema } from "./common";
88
export const AccountSearchInputSchema = z
99
.object({
1010
query: z.string()
11-
.describe(`Comma- or space-delimited) list of account e-mail addresses, account ids,
11+
.describe(`Comma- or space-delimited list of account e-mail addresses, account ids,
1212
and/or first/last names to query for an account by.`),
1313
})
1414
.describe(

src/packages/next/lib/api/schema/common.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { z } from "../framework";
22

3-
const BasicallyAnHTTP204 = (status: string) =>
3+
const BasicallyAnHTTP204 = <T extends string>(status: T) =>
44
z.object({
55
status: z.enum([status]).describe(
66
`Indicates the status of this operation; if the operation was successful, the
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { z } from "../../framework";
2+
3+
import { FailedAPIOperationSchema, OkAPIOperationSchema } from "../common";
4+
5+
import { ProjectIdSchema } from "./common";
6+
7+
// OpenAPI spec
8+
//
9+
export const StartProjectInputSchema = z
10+
.object({
11+
project_id: ProjectIdSchema,
12+
})
13+
.describe("Starts a running project.");
14+
15+
export const StartProjectOutputSchema = z.union([
16+
FailedAPIOperationSchema,
17+
OkAPIOperationSchema,
18+
]);
19+
20+
export type StartProjectInput = z.infer<typeof StartProjectInputSchema>;
21+
export type StartProjectOutput = z.infer<typeof StartProjectOutputSchema>;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { z } from "../../framework";
2+
3+
import { FailedAPIOperationSchema, OkAPIOperationSchema } from "../common";
4+
5+
import { ProjectIdSchema } from "./common";
6+
7+
// OpenAPI spec
8+
//
9+
export const StopProjectInputSchema = z
10+
.object({
11+
project_id: ProjectIdSchema,
12+
})
13+
.describe("Stops a running project.");
14+
15+
export const StopProjectOutputSchema = z.union([
16+
FailedAPIOperationSchema,
17+
OkAPIOperationSchema,
18+
]);
19+
20+
export type StopProjectInput = z.infer<typeof StopProjectInputSchema>;
21+
export type StopProjectOutput = z.infer<typeof StopProjectOutputSchema>;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { z } from "../../framework";
2+
3+
export const PurchaseIdSchema = z.number().min(0).describe("Purchase id.");
4+
5+
export type PurchaseId = z.infer<typeof PurchaseIdSchema>;
6+
7+
export const InvoiceIdSchema = z.number().min(0).describe("Invoice id.");
8+
9+
export type InvoiceId = z.infer<typeof InvoiceIdSchema>;
10+
11+
export const DayStatementIdSchema = z
12+
.number()
13+
.min(0)
14+
.describe("Daily statement id.");
15+
16+
export type DayStatementId = z.infer<typeof DayStatementIdSchema>;
17+
18+
export const MonthStatementIdSchema = z
19+
.number()
20+
.min(0)
21+
.describe("Monthly statement id.");
22+
23+
export type MonthStatementId = z.infer<typeof MonthStatementIdSchema>;
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import { z } from "../../framework";
2+
3+
import { FailedAPIOperationSchema } from "../common";
4+
import { ProjectIdSchema } from "../projects/common";
5+
6+
import {
7+
DayStatementIdSchema,
8+
InvoiceIdSchema,
9+
MonthStatementIdSchema,
10+
PurchaseIdSchema,
11+
} from "./common";
12+
13+
const PurchaseServiceSchema = z
14+
.string()
15+
.describe("The service being charged for, e.g., `openai-gpt-4`, etc.");
16+
17+
// OpenAPI spec
18+
//
19+
export const GetPurchasesInputSchema = z
20+
.object({
21+
limit: z
22+
.number()
23+
.default(100)
24+
.describe("Upper bound on the number of purchases to return.")
25+
.nullish(),
26+
offset: z
27+
.number()
28+
.describe("Number of purchases by which to offset results.")
29+
.nullish(),
30+
service: PurchaseServiceSchema.nullish(),
31+
project_id: ProjectIdSchema.describe(
32+
"The project id associated with this purchase, if one exists.",
33+
).nullish(),
34+
group: z
35+
.boolean()
36+
.describe(
37+
`If \`true\`, results are groups by service and project id, and then decreasingly
38+
ordered by cost. Otherwise, results are ordered by time, with the newest
39+
purchases returned first.`,
40+
)
41+
.nullish(),
42+
cutoff: z
43+
.union([z.string(), z.number()])
44+
.describe(
45+
`When provided, only purchases which occur _after_ the timestamp specified in this
46+
field will be returned.`,
47+
)
48+
.nullish(),
49+
thisMonth: z
50+
.boolean()
51+
.describe(
52+
"If `true`, only purchases since the most recent closing date will be returned.",
53+
)
54+
.nullish(),
55+
day_statement_id: DayStatementIdSchema.describe(
56+
"Daily statement id of the statement that includes this purchase",
57+
).nullish(),
58+
month_statement_id: MonthStatementIdSchema.describe(
59+
"Monthly statement id of the statement that includes this purchase",
60+
).nullish(),
61+
no_statement: z
62+
.boolean()
63+
.describe(
64+
`If \`true\`, only purchases which are
65+
_not_ associated with a monthly or daily statement are returned.`,
66+
)
67+
.nullish(),
68+
})
69+
.describe("Gets user purchases.");
70+
71+
export const GetPurchasesOutputSchema = z.union([
72+
FailedAPIOperationSchema,
73+
z
74+
.array(
75+
z.object({
76+
id: PurchaseIdSchema,
77+
time: z.string().describe("Time at which this purchase was logged."),
78+
cost: z.number().describe(
79+
`The cost in US dollars. Not set if the purchase isn't finished, e.g., when
80+
upgrading a project this is only set when project stops or purchase is finalized.
81+
This takes precedence over the \`cost_per_hour\` times the length of the period
82+
when active.`,
83+
),
84+
period_start: z.string().describe(
85+
`When the purchase starts being active (e.g., a 1 week license starts and ends on
86+
specific days; for metered purchases it is when the purchased started charging) `,
87+
),
88+
period_end: z.string().describe(
89+
`When the purchase stops being active. For metered purchases, it's when the
90+
purchase finished being charged, in which case the cost field should be equal to
91+
the length of the period times the \`cost_per_hour\`.`,
92+
),
93+
cost_per_hour: z.number().describe(
94+
`The cost in US dollars per hour. This is used to compute the cost so far for
95+
metered purchases when the cost field isn't set yet. The cost so far is the
96+
number of hours since \`period_start\` times the \`cost_per_hour\`. The
97+
description field may also contain redundant cost per hour information, but this
98+
\`cost_per_hour\` field is the definitive source of truth. Once the \`cost\`
99+
field is set, this \`cost_per_hour\` is just useful for display purposes.`,
100+
),
101+
cost_so_far: z.number().describe(
102+
`The cost so far in US dollars for a metered purchase that accumulates. This is
103+
used, e.g., for data transfer charges.`,
104+
),
105+
service: PurchaseServiceSchema,
106+
description: z.map(z.string(), z.any()).describe(
107+
`An object that provides additional details about what was purchased and can have
108+
an arbitrary format. This is mainly used to provide extra insight when rendering
109+
this purchase for users, and its content should not be relied on for queries.`,
110+
),
111+
invoice_id: InvoiceIdSchema.nullable().describe(
112+
`The id of the Stripe invoice that was sent that included this item. May be
113+
null. **Legacy Behavior:** if paid via a payment intent, this will be the id of
114+
a payment intent instead, and it will start with \`pi_\`.`,
115+
),
116+
project_id: ProjectIdSchema.nullable().describe(
117+
`The id of the project where this purchase happened. Not all purchases
118+
necessarily involve a project, and so this field may be null.`,
119+
),
120+
pending: z
121+
.boolean()
122+
.nullable()
123+
.describe(
124+
`If \`true\`, then this transaction is considered pending, which means that
125+
for a few days it doesn't count against the user's quotas for the purposes of
126+
deciding whether or not a purchase is allowed. This is needed so we can charge
127+
a user for their subscriptions, then collect the money from them, without all
128+
of the running pay-as-you-go project upgrades suddenly breaking (etc.).`,
129+
),
130+
note: z
131+
.string()
132+
.nullable()
133+
.describe(
134+
`Non-private notes about this purchase. The user has read-only access to this
135+
field.`,
136+
),
137+
}),
138+
)
139+
.describe(
140+
`An array of purchases filtered and/or grouped according to the provided request
141+
body.`,
142+
),
143+
]);
144+
145+
export type GetPurchasesInput = z.infer<typeof GetPurchasesInputSchema>;
146+
export type GetPurchasesOutput = z.infer<typeof GetPurchasesOutputSchema>;

src/packages/next/lib/api/schema/shopping/cart/add.ts

Whitespace-only changes.
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { z } from "../framework";
2+
3+
import { FailedAPIOperationSchema } from "./common";
4+
import { AccountIdSchema } from "./accounts/common";
5+
import { ProjectIdSchema } from "./projects/common";
6+
7+
const ExampleUserQuerySchema = z.object({
8+
accounts: z
9+
.object({
10+
account_id: AccountIdSchema.nullable(),
11+
email_address: z.string().nullable(),
12+
})
13+
.describe(
14+
`Used to query for the account id and e-mail address of the account corresponding to
15+
the API key provided in this request.`,
16+
),
17+
});
18+
19+
const ExampleDirectoryListingSchema = z.object({
20+
listings: z
21+
.object({
22+
project_id: ProjectIdSchema,
23+
path: z
24+
.string()
25+
.nullable()
26+
.describe("Path relative to user's `$HOME` directory."),
27+
listing: z
28+
.union([
29+
z.null(),
30+
z.array(
31+
z.object({
32+
name: z.string().describe("File name."),
33+
size: z.number().min(0).describe("File size."),
34+
mtime: z
35+
.number()
36+
.describe("Time at which the file was last modified."),
37+
}),
38+
),
39+
])
40+
.describe(
41+
"This field should be `null` when querying for a list of files.",
42+
),
43+
})
44+
.describe(
45+
"Object containing project id and file path for which to list files.",
46+
),
47+
});
48+
49+
const GenericUserQuerySchema = z.any();
50+
51+
// OpenAPI spec
52+
//
53+
export const UserQueryInputSchema = z
54+
.object({
55+
query: z.union([
56+
ExampleUserQuerySchema,
57+
ExampleDirectoryListingSchema,
58+
GenericUserQuerySchema.describe(
59+
`Many other generic queries are supported; you can learn more about this endpoint
60+
by viewing the corresponding CoCalc source code at
61+
https://github.com/sagemathinc/cocalc/blob/master/src/packages/next/pages/api/v2/user-query.ts.`,
62+
),
63+
]),
64+
})
65+
.describe(
66+
`Used to fetch or set data corresponding to a particular account. Generally speaking,
67+
when \`null\` values are provided for a specific field, this endpoint acts as a
68+
getter; otherwise, it acts as a setter for the provided fields.`,
69+
);
70+
71+
export const UserQueryOutputSchema = z.union([
72+
FailedAPIOperationSchema,
73+
z.object({
74+
query: z.union([
75+
ExampleUserQuerySchema.describe(
76+
`An example response for an e-mail address and account id query.`,
77+
),
78+
ExampleDirectoryListingSchema.describe(
79+
"An example response for a directory listing query.",
80+
),
81+
GenericUserQuerySchema.describe(
82+
`Generally, the object returned from this request mimics the structure of the
83+
input query with fields populated as applicable. For more information on this
84+
request, check out the corresponding CoCalc source code at
85+
https://github.com/sagemathinc/cocalc/blob/master/src/packages/next/pages/api/v2/user-query.ts.`,
86+
),
87+
]),
88+
}),
89+
]);
90+
91+
export type UserQueryInput = z.infer<typeof UserQueryInputSchema>;
92+
export type UserQueryOutput = z.infer<typeof UserQueryOutputSchema>;

src/packages/next/lib/api/status.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { OkAPIOperation, SuccessfulAPIOperation } from "./schema/common";
2+
3+
export const OkStatus: OkAPIOperation = { status: "ok" };
4+
export const SuccessStatus: SuccessfulAPIOperation = { status: "success" };

src/packages/next/pages/api/v2/accounts/ban.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import userIsInGroup from "@cocalc/server/accounts/is-in-group";
88
import { banUser } from "@cocalc/server/accounts/ban";
99

1010
import { apiRoute, apiRouteOperation } from "lib/api";
11+
import { SuccessStatus } from "lib/api/status";
1112
import {
1213
BanAccountInputSchema,
1314
BanAccountOutputSchema,
@@ -34,14 +35,14 @@ async function get(req) {
3435

3536
const { account_id } = getParams(req);
3637
await banUser(account_id);
37-
return { status: "success" };
38+
return SuccessStatus;
3839
}
3940

4041
export default apiRoute({
4142
ban: apiRouteOperation({
4243
method: "POST",
4344
openApiOperation: {
44-
tags: ["Accounts"],
45+
tags: ["Accounts", "Admin"],
4546
},
4647
})
4748
.input({

0 commit comments

Comments
 (0)