mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 08:28:19 +01:00
feat(api): ✨ Add support for batch account data API
This commit is contained in:
parent
287f428a83
commit
7bd07801f2
|
|
@ -5,6 +5,7 @@
|
||||||
### API
|
### API
|
||||||
|
|
||||||
- [x] 🥺 Emoji Reactions are now available! You can react to any note with custom emojis.
|
- [x] 🥺 Emoji Reactions are now available! You can react to any note with custom emojis.
|
||||||
|
- [x] 🔎 Added support for [batch account data API](https://docs.joinmastodon.org/methods/accounts/#index).
|
||||||
|
|
||||||
# `0.8.0` • Federation 2: Electric Boogaloo
|
# `0.8.0` • Federation 2: Electric Boogaloo
|
||||||
|
|
||||||
|
|
|
||||||
52
api/api/v1/accounts/index.get.test.ts
Normal file
52
api/api/v1/accounts/index.get.test.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
import { afterAll, describe, expect, test } from "bun:test";
|
||||||
|
import { generateClient, getTestUsers } from "~/tests/utils";
|
||||||
|
|
||||||
|
const { users, deleteUsers } = await getTestUsers(5);
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await deleteUsers();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("/api/v1/accounts", () => {
|
||||||
|
test("should return accounts", async () => {
|
||||||
|
await using client = await generateClient();
|
||||||
|
|
||||||
|
const { data, ok } = await client.getAccounts(users.map((u) => u.id));
|
||||||
|
|
||||||
|
expect(ok).toBe(true);
|
||||||
|
expect(data).toEqual(
|
||||||
|
expect.arrayContaining(
|
||||||
|
users.map((u) =>
|
||||||
|
expect.objectContaining({
|
||||||
|
id: u.id,
|
||||||
|
username: u.data.username,
|
||||||
|
acct: u.data.username,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should skip nonexistent accounts", async () => {
|
||||||
|
await using client = await generateClient();
|
||||||
|
|
||||||
|
const { data, ok } = await client.getAccounts([
|
||||||
|
...users.map((u) => u.id),
|
||||||
|
"00000000-0000-0000-0000-000000000000",
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(ok).toBe(true);
|
||||||
|
expect(data).toEqual(
|
||||||
|
expect.arrayContaining(
|
||||||
|
users.map((u) =>
|
||||||
|
expect.objectContaining({
|
||||||
|
id: u.id,
|
||||||
|
username: u.data.username,
|
||||||
|
acct: u.data.username,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(data).toHaveLength(users.length);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { zBoolean } from "@versia/client/schemas";
|
import { Account as AccountSchema, zBoolean } from "@versia/client/schemas";
|
||||||
import { User } from "@versia/kit/db";
|
import { User } from "@versia/kit/db";
|
||||||
import { Users } from "@versia/kit/tables";
|
import { Users } from "@versia/kit/tables";
|
||||||
import { and, eq, isNull } from "drizzle-orm";
|
import { and, eq, isNull } from "drizzle-orm";
|
||||||
|
|
@ -6,7 +6,7 @@ import { describeRoute } from "hono-openapi";
|
||||||
import { resolver, validator } from "hono-openapi/zod";
|
import { resolver, validator } from "hono-openapi/zod";
|
||||||
import ISO6391 from "iso-639-1";
|
import ISO6391 from "iso-639-1";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { apiRoute, auth, handleZodError, jsonOrForm } from "@/api";
|
import { apiRoute, auth, handleZodError, jsonOrForm, qsQuery } from "@/api";
|
||||||
import { tempmailDomains } from "@/tempmail";
|
import { tempmailDomains } from "@/tempmail";
|
||||||
import { ApiError } from "~/classes/errors/api-error";
|
import { ApiError } from "~/classes/errors/api-error";
|
||||||
import { config } from "~/config.ts";
|
import { config } from "~/config.ts";
|
||||||
|
|
@ -42,7 +42,67 @@ const schema = z.object({
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default apiRoute((app) =>
|
export default apiRoute((app) => {
|
||||||
|
app.get(
|
||||||
|
"/api/v1/accounts",
|
||||||
|
describeRoute({
|
||||||
|
summary: "Get multiple accounts",
|
||||||
|
description: "View information about multiple profiles.",
|
||||||
|
externalDocs: {
|
||||||
|
url: "https://docs.joinmastodon.org/methods/accounts/#index",
|
||||||
|
},
|
||||||
|
tags: ["Accounts"],
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description:
|
||||||
|
"Account records for the requested confirmed and approved accounts. There can be fewer records than requested if the accounts do not exist or are not confirmed.",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: resolver(z.array(AccountSchema)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
422: ApiError.validationFailed().schema,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
qsQuery(),
|
||||||
|
auth({
|
||||||
|
auth: false,
|
||||||
|
scopes: [],
|
||||||
|
challenge: false,
|
||||||
|
}),
|
||||||
|
rateLimit(40),
|
||||||
|
validator(
|
||||||
|
"query",
|
||||||
|
z.object({
|
||||||
|
id: z
|
||||||
|
.array(AccountSchema.shape.id)
|
||||||
|
.min(1)
|
||||||
|
.max(40)
|
||||||
|
.or(AccountSchema.shape.id.transform((v) => [v]))
|
||||||
|
.openapi({
|
||||||
|
description: "The IDs of the Accounts in the database.",
|
||||||
|
example: [
|
||||||
|
"f137ce6f-ff5e-4998-b20f-0361ba9be007",
|
||||||
|
"8424c654-5d03-4a1b-bec8-4e87db811b5d",
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
handleZodError,
|
||||||
|
),
|
||||||
|
async (context) => {
|
||||||
|
const { id: ids } = context.req.valid("query");
|
||||||
|
|
||||||
|
// Find accounts by IDs
|
||||||
|
const accounts = await User.fromIds(ids);
|
||||||
|
|
||||||
|
return context.json(
|
||||||
|
accounts.map((account) => account.toApi()),
|
||||||
|
200,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
app.post(
|
app.post(
|
||||||
"/api/v1/accounts",
|
"/api/v1/accounts",
|
||||||
describeRoute({
|
describeRoute({
|
||||||
|
|
@ -360,5 +420,5 @@ export default apiRoute((app) =>
|
||||||
|
|
||||||
return context.text("", 200);
|
return context.text("", 200);
|
||||||
},
|
},
|
||||||
),
|
);
|
||||||
);
|
});
|
||||||
|
|
|
||||||
|
|
@ -71,9 +71,7 @@ export default apiRoute((app) =>
|
||||||
const { user } = context.get("auth");
|
const { user } = context.get("auth");
|
||||||
|
|
||||||
// TODO: Implement with_suspended
|
// TODO: Implement with_suspended
|
||||||
const { id } = context.req.valid("query");
|
const { id: ids } = context.req.valid("query");
|
||||||
|
|
||||||
const ids = Array.isArray(id) ? id : [id];
|
|
||||||
|
|
||||||
const relationships = await Relationship.fromOwnerAndSubjects(
|
const relationships = await Relationship.fromOwnerAndSubjects(
|
||||||
user,
|
user,
|
||||||
|
|
|
||||||
|
|
@ -703,6 +703,28 @@ export class Client extends BaseClient {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/v1/accounts
|
||||||
|
*
|
||||||
|
* @param ids The account IDs.
|
||||||
|
* @return An array of accounts.
|
||||||
|
*/
|
||||||
|
public getAccounts(
|
||||||
|
ids: string[],
|
||||||
|
extra?: RequestInit,
|
||||||
|
): Promise<Output<z.infer<typeof Account>[]>> {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
for (const id of ids) {
|
||||||
|
params.append("id[]", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.get<z.infer<typeof Account>[]>(
|
||||||
|
`/api/v1/accounts?${params.toString()}`,
|
||||||
|
extra,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /api/v1/accounts/id
|
* GET /api/v1/accounts/id
|
||||||
*
|
*
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue