server/packages/api/routes/api/v1/accounts/lookup/index.ts
Jesse Wierzbinski 1f03017327
Some checks failed
Mirror to Codeberg / Mirror (push) Failing after 0s
Test Publish / build (client) (push) Failing after 0s
Test Publish / build (sdk) (push) Failing after 0s
refactor: 🚚 Rename @versia/kit to @versia-server/kit
2025-06-15 23:50:34 +02:00

114 lines
3.7 KiB
TypeScript

import {
Account as AccountSchema,
RolePermission,
} from "@versia/client/schemas";
import { config } from "@versia-server/config";
import { ApiError } from "@versia-server/kit";
import { apiRoute, auth, handleZodError } from "@versia-server/kit/api";
import { Instance, User } from "@versia-server/kit/db";
import { parseUserAddress } from "@versia-server/kit/parsers";
import { Users } from "@versia-server/kit/tables";
import { and, eq, isNull } from "drizzle-orm";
import { describeRoute } from "hono-openapi";
import { resolver, validator } from "hono-openapi/zod";
import { z } from "zod";
import { rateLimit } from "../../../../../middlewares/rate-limit.ts";
export default apiRoute((app) =>
app.get(
"/api/v1/accounts/lookup",
describeRoute({
summary: "Lookup account ID from Webfinger address",
description:
"Quickly lookup a username to see if it is available, skipping WebFinger resolution.",
tags: ["Accounts"],
responses: {
200: {
description: "Account",
content: {
"application/json": {
schema: resolver(AccountSchema),
},
},
},
404: ApiError.accountNotFound().schema,
422: ApiError.validationFailed().schema,
},
}),
auth({
auth: false,
permissions: [RolePermission.Search],
}),
rateLimit(60),
validator(
"query",
z.object({
acct: AccountSchema.shape.acct.openapi({
description: "The username or Webfinger address to lookup.",
example: "lexi@beta.versia.social",
}),
}),
handleZodError,
),
async (context) => {
const { acct } = context.req.valid("query");
// Check if acct is matching format username@domain.com or @username@domain.com
const { username, domain } = parseUserAddress(acct);
// User is local
if (!domain || domain === config.http.base_url.host) {
const account = await User.fromSql(
and(eq(Users.username, username), isNull(Users.instanceId)),
);
if (account) {
return context.json(account.toApi(), 200);
}
return context.json(
{ error: `Account with username ${username} not found` },
404,
);
}
// User is remote
// Try to fetch it from database
const instance = await Instance.resolveFromHost(domain);
if (!instance) {
return context.json(
{ error: `Instance ${domain} not found` },
404,
);
}
const account = await User.fromSql(
and(
eq(Users.username, username),
eq(Users.instanceId, instance.id),
),
);
if (account) {
return context.json(account.toApi(), 200);
}
// Fetch from remote instance
const uri = await User.webFinger(username, domain);
if (!uri) {
throw ApiError.accountNotFound();
}
const foundAccount = await User.resolve(uri);
if (foundAccount) {
return context.json(foundAccount.toApi(), 200);
}
throw ApiError.accountNotFound();
},
),
);