Add more tests, fix roiutes

This commit is contained in:
Jesse Wierzbinski 2024-04-11 13:12:23 -10:00
parent 0469187876
commit 3ccff003f5
No known key found for this signature in database
11 changed files with 923 additions and 80 deletions

View file

@ -847,7 +847,7 @@ export const createNewStatus = async (
content["text/markdown"]?.content ||
"",
contentType: "text/html",
visibility: visibility,
visibility,
sensitive: is_sensitive,
spoilerText: spoiler_text,
instanceId: author.instanceId || null,
@ -1148,7 +1148,7 @@ export const statusToAPI = async (
`/@${statusToConvert.author.username}/${statusToConvert.id}`,
config.http.base_url,
).toString(),
visibility: "public",
visibility: statusToConvert.visibility as APIStatus["visibility"],
url:
statusToConvert.uri ||
new URL(

View file

@ -0,0 +1,110 @@
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
import {
deleteOldTestUsers,
getTestStatuses,
getTestUsers,
sendTestRequest,
} from "~tests/utils";
import { config } from "~index";
import { meta } from "./index";
import type { APIStatus } from "~types/entities/status";
import type { APIAccount } from "~types/entities/account";
import { getUserUri } from "~database/entities/User";
await deleteOldTestUsers();
const { users, tokens, deleteUsers } = await getTestUsers(5);
const timeline = (await getTestStatuses(40, users[0])).toReversed();
afterAll(async () => {
await deleteUsers();
});
beforeAll(async () => {
for (const status of timeline) {
await fetch(
new URL(
`/api/v1/statuses/${status.id}/favourite`,
config.http.base_url,
),
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
},
},
);
}
});
// /api/v1/accounts/:id
describe(meta.route, () => {
test("should return 401 if not authenticated and trying to use following", async () => {
const response = await sendTestRequest(
new Request(
new URL(
`${meta.route.replace(":id", users[0].id)}?following=true`,
config.http.base_url,
),
),
);
expect(response.status).toBe(401);
});
test("should return 404 if ID is invalid", async () => {
const response = await sendTestRequest(
new Request(
new URL(
meta.route.replace(":id", "invalid"),
config.http.base_url,
),
),
);
expect(response.status).toBe(404);
});
test("should return user", async () => {
const response = await sendTestRequest(
new Request(
new URL(
meta.route.replace(":id", users[0].id),
config.http.base_url,
),
),
);
expect(response.status).toBe(200);
const data = (await response.json()) as APIAccount;
expect(data).toMatchObject({
id: users[0].id,
username: users[0].username,
display_name: users[0].displayName,
avatar: expect.any(String),
header: expect.any(String),
locked: users[0].isLocked,
created_at: new Date(users[0].createdAt).toISOString(),
followers_count: 0,
following_count: 0,
statuses_count: 40,
note: users[0].note,
acct: users[0].username,
url: expect.any(String),
avatar_static: expect.any(String),
header_static: expect.any(String),
emojis: [],
moved: null,
fields: [],
bot: false,
group: false,
limited: false,
noindex: false,
suspended: false,
pleroma: {
is_admin: false,
is_moderator: false,
},
} satisfies APIAccount);
});
});

View file

@ -1,9 +1,6 @@
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { client } from "~database/datasource";
import type { UserWithRelations } from "~database/entities/User";
import { userToAPI } from "~database/entities/User";
import { userRelations } from "~database/entities/relations";
import { findFirstUser, userToAPI } from "~database/entities/User";
export const meta = applyConfig({
allowedMethods: ["GET"],
@ -34,15 +31,9 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
const { user } = extraData.auth;
let foundUser: UserWithRelations | null;
try {
foundUser = await client.user.findUnique({
where: { id },
include: userRelations,
});
} catch (e) {
return errorResponse("Invalid ID", 404);
}
const foundUser = await findFirstUser({
where: (user, { eq }) => eq(user.id, id),
}).catch(() => null);
if (!foundUser) return errorResponse("User not found", 404);

View file

@ -0,0 +1,91 @@
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
import {
deleteOldTestUsers,
getTestStatuses,
getTestUsers,
sendTestRequest,
} from "~tests/utils";
import { config } from "~index";
import { meta } from "./index";
import type { APIStatus } from "~types/entities/status";
import type { APIAccount } from "~types/entities/account";
import { getUserUri } from "~database/entities/User";
await deleteOldTestUsers();
const { users, tokens, deleteUsers } = await getTestUsers(5);
afterAll(async () => {
await deleteUsers();
});
// /api/v1/accounts/lookup
describe(meta.route, () => {
test("should return 400 if acct is missing", async () => {
const response = await sendTestRequest(
new Request(new URL(meta.route, config.http.base_url), {
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
},
}),
);
expect(response.status).toBe(400);
});
test("should return 400 if acct is empty", async () => {
const response = await sendTestRequest(
new Request(new URL(`${meta.route}?acct=`, config.http.base_url), {
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
},
}),
);
expect(response.status).toBe(400);
});
test("should return 404 if acct is invalid", async () => {
const response = await sendTestRequest(
new Request(
new URL(`${meta.route}?acct=invalid`, config.http.base_url),
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
},
},
),
);
expect(response.status).toBe(404);
});
test("should return 200 with users", async () => {
const response = await sendTestRequest(
new Request(
new URL(
`${meta.route}?acct=${users[0].username}`,
config.http.base_url,
),
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
},
},
),
);
expect(response.status).toBe(200);
const data = (await response.json()) as APIAccount[];
expect(data).toEqual(
expect.objectContaining({
id: users[0].id,
username: users[0].username,
display_name: users[0].displayName,
avatar: expect.any(String),
header: expect.any(String),
}),
);
});
});

View file

@ -1,9 +1,10 @@
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { client } from "~database/datasource";
import type { UserWithRelations } from "~database/entities/User";
import { resolveWebFinger, userToAPI } from "~database/entities/User";
import { userRelations } from "~database/entities/relations";
import {
findFirstUser,
resolveWebFinger,
userToAPI,
} from "~database/entities/User";
export const meta = applyConfig({
allowedMethods: ["GET"],
@ -57,11 +58,8 @@ export default apiRoute<{
username = username.slice(1);
}
const account = await client.user.findFirst({
where: {
username,
},
include: userRelations,
const account = await findFirstUser({
where: (user, { eq }) => eq(user.username, username),
});
if (account) {

View file

@ -0,0 +1,114 @@
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
import {
deleteOldTestUsers,
getTestStatuses,
getTestUsers,
sendTestRequest,
} from "~tests/utils";
import { config } from "~index";
import { meta } from "./index";
import type { APIStatus } from "~types/entities/status";
import type { APIAccount } from "~types/entities/account";
import { getUserUri } from "~database/entities/User";
await deleteOldTestUsers();
const { users, tokens, deleteUsers } = await getTestUsers(5);
afterAll(async () => {
await deleteUsers();
});
// /api/v1/accounts/search
describe(meta.route, () => {
test("should return 400 if q is missing", async () => {
const response = await sendTestRequest(
new Request(new URL(meta.route, config.http.base_url), {
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
},
}),
);
expect(response.status).toBe(400);
});
test("should return 400 if q is empty", async () => {
const response = await sendTestRequest(
new Request(new URL(`${meta.route}?q=`, config.http.base_url), {
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
},
}),
);
expect(response.status).toBe(400);
});
test("should return 400 if limit is less than 1", async () => {
const response = await sendTestRequest(
new Request(
new URL(
`${meta.route}?q=${users[0].username}&limit=0`,
config.http.base_url,
),
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
},
},
),
);
expect(response.status).toBe(400);
});
test("should return 400 if limit is greater than 80", async () => {
const response = await sendTestRequest(
new Request(
new URL(
`${meta.route}?q=${users[0].username}&limit=100`,
config.http.base_url,
),
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
},
},
),
);
expect(response.status).toBe(400);
});
test("should return 200 with users", async () => {
const response = await sendTestRequest(
new Request(
new URL(
`${meta.route}?q=${users[0].username}`,
config.http.base_url,
),
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
},
},
),
);
expect(response.status).toBe(200);
const data = (await response.json()) as APIAccount[];
expect(data).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: users[0].id,
username: users[0].username,
display_name: users[0].displayName,
avatar: expect.any(String),
header: expect.any(String),
}),
]),
);
});
});

View file

@ -1,8 +1,13 @@
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { client } from "~database/datasource";
import { userToAPI } from "~database/entities/User";
import { userRelations } from "~database/entities/relations";
import { sql } from "drizzle-orm";
import {
findManyUsers,
resolveWebFinger,
userToAPI,
type UserWithRelations,
} from "~database/entities/User";
import { user } from "~drizzle/schema";
export const meta = applyConfig({
allowedMethods: ["GET"],
@ -29,46 +34,47 @@ export default apiRoute<{
following = false,
limit = 40,
offset,
resolve,
q,
} = extraData.parsedRequest;
const { user } = extraData.auth;
const { user: self } = extraData.auth;
if (!user && following) return errorResponse("Unauthorized", 401);
if (!self && following) return errorResponse("Unauthorized", 401);
if (limit < 1 || limit > 80) {
return errorResponse("Limit must be between 1 and 80", 400);
}
// TODO: Add WebFinger resolve
if (!q) {
return errorResponse("Query is required", 400);
}
const accounts = await client.user.findMany({
where: {
OR: [
{
displayName: {
contains: q,
},
},
{
username: {
contains: q,
},
},
],
relationshipSubjects: following
? {
some: {
ownerId: user?.id,
following,
},
}
: undefined,
},
take: Number(limit),
skip: Number(offset || 0),
include: userRelations,
});
const [username, host] = q?.split("@") || [];
const accounts: UserWithRelations[] = [];
if (resolve && username && host) {
const resolvedUser = await resolveWebFinger(username, host);
if (resolvedUser) {
accounts.push(resolvedUser);
}
} else {
accounts.push(
...(await findManyUsers({
where: (account, { or, like }) =>
or(
like(account.displayName, `%${q}%`),
like(account.username, `%${q}%`),
following
? sql`EXISTS (SELECT 1 FROM "Relationship" WHERE "Relationship"."subjectId" = ${user.id} AND "Relationship"."ownerId" = ${account.id} AND "Relationship"."following" = true)`
: undefined,
),
offset: Number(offset),
})),
);
}
return jsonResponse(accounts.map((acct) => userToAPI(acct)));
});

View file

@ -0,0 +1,116 @@
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
import {
deleteOldTestUsers,
getTestStatuses,
getTestUsers,
sendTestRequest,
} from "~tests/utils";
import { config } from "~index";
import { meta } from "./favourited_by";
import type { APIStatus } from "~types/entities/status";
import type { APIAccount } from "~types/entities/account";
await deleteOldTestUsers();
const { users, tokens, deleteUsers } = await getTestUsers(5);
const timeline = (await getTestStatuses(40, users[0])).toReversed();
afterAll(async () => {
await deleteUsers();
});
beforeAll(async () => {
for (const status of timeline) {
await fetch(
new URL(
`/api/v1/statuses/${status.id}/favourite`,
config.http.base_url,
),
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
},
},
);
}
});
// /api/v1/statuses/:id/favourited_by
describe(meta.route, () => {
test("should return 401 if not authenticated", async () => {
const response = await sendTestRequest(
new Request(
new URL(
meta.route.replace(":id", timeline[0].id),
config.http.base_url,
),
),
);
expect(response.status).toBe(401);
});
test("should return 400 if limit is less than 1", async () => {
const response = await sendTestRequest(
new Request(
new URL(
`${meta.route.replace(":id", timeline[0].id)}?limit=0`,
config.http.base_url,
),
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
},
},
),
);
expect(response.status).toBe(400);
});
test("should return 400 if limit is greater than 80", async () => {
const response = await sendTestRequest(
new Request(
new URL(
`${meta.route.replace(":id", timeline[0].id)}?limit=100`,
config.http.base_url,
),
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
},
},
),
);
expect(response.status).toBe(400);
});
test("should return 200 with users", async () => {
const response = await sendTestRequest(
new Request(
new URL(
meta.route.replace(":id", timeline[0].id),
config.http.base_url,
),
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
},
},
),
);
expect(response.status).toBe(200);
expect(response.headers.get("content-type")).toBe("application/json");
const objects = (await response.json()) as APIAccount[];
expect(objects.length).toBe(1);
for (const [index, status] of objects.entries()) {
expect(status.id).toBe(users[1].id);
expect(status.username).toBe(users[1].username);
}
});
});

View file

@ -0,0 +1,116 @@
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
import {
deleteOldTestUsers,
getTestStatuses,
getTestUsers,
sendTestRequest,
} from "~tests/utils";
import { config } from "~index";
import { meta } from "./reblogged_by";
import type { APIStatus } from "~types/entities/status";
import type { APIAccount } from "~types/entities/account";
await deleteOldTestUsers();
const { users, tokens, deleteUsers } = await getTestUsers(5);
const timeline = (await getTestStatuses(40, users[0])).toReversed();
afterAll(async () => {
await deleteUsers();
});
beforeAll(async () => {
for (const status of timeline) {
await fetch(
new URL(
`/api/v1/statuses/${status.id}/reblog`,
config.http.base_url,
),
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[1].accessToken}`,
},
},
);
}
});
// /api/v1/statuses/:id/reblogged_by
describe(meta.route, () => {
test("should return 401 if not authenticated", async () => {
const response = await sendTestRequest(
new Request(
new URL(
meta.route.replace(":id", timeline[0].id),
config.http.base_url,
),
),
);
expect(response.status).toBe(401);
});
test("should return 400 if limit is less than 1", async () => {
const response = await sendTestRequest(
new Request(
new URL(
`${meta.route.replace(":id", timeline[0].id)}?limit=0`,
config.http.base_url,
),
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
},
},
),
);
expect(response.status).toBe(400);
});
test("should return 400 if limit is greater than 80", async () => {
const response = await sendTestRequest(
new Request(
new URL(
`${meta.route.replace(":id", timeline[0].id)}?limit=100`,
config.http.base_url,
),
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
},
},
),
);
expect(response.status).toBe(400);
});
test("should return 200 with users", async () => {
const response = await sendTestRequest(
new Request(
new URL(
meta.route.replace(":id", timeline[0].id),
config.http.base_url,
),
{
headers: {
Authorization: `Bearer ${tokens[0].accessToken}`,
},
},
),
);
expect(response.status).toBe(200);
expect(response.headers.get("content-type")).toBe("application/json");
const objects = (await response.json()) as APIAccount[];
expect(objects.length).toBe(1);
for (const [index, status] of objects.entries()) {
expect(status.id).toBe(users[1].id);
expect(status.username).toBe(users[1].username);
}
});
});

View file

@ -0,0 +1,297 @@
import { afterAll, describe, expect, test } from "bun:test";
import {
deleteOldTestUsers,
getTestUsers,
sendTestRequest,
} from "~tests/utils";
import { config } from "~index";
import { meta } from "./index";
import type { APIStatus } from "~types/entities/status";
await deleteOldTestUsers();
const { users, tokens, deleteUsers } = await getTestUsers(5);
afterAll(async () => {
await deleteUsers();
});
describe(meta.route, () => {
test("should return 405 if method is not allowed", async () => {
const response = await sendTestRequest(
new Request(new URL(meta.route, config.http.base_url), {
method: "GET",
}),
);
expect(response.status).toBe(405);
});
test("should return 401 if not authenticated", async () => {
const response = await sendTestRequest(
new Request(new URL(meta.route, config.http.base_url), {
method: "POST",
}),
);
expect(response.status).toBe(401);
});
test("should return 422 is status is empty", async () => {
const response = await sendTestRequest(
new Request(new URL(meta.route, config.http.base_url), {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${tokens[0].accessToken}`,
},
body: JSON.stringify({}),
}),
);
expect(response.status).toBe(422);
});
test("should return 400 is status is too long", async () => {
const response = await sendTestRequest(
new Request(new URL(meta.route, config.http.base_url), {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${tokens[0].accessToken}`,
},
body: JSON.stringify({
status: "a".repeat(config.validation.max_note_size + 1),
federate: false,
}),
}),
);
expect(response.status).toBe(400);
});
test("should return 422 is visibility is invalid", async () => {
const response = await sendTestRequest(
new Request(new URL(meta.route, config.http.base_url), {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${tokens[0].accessToken}`,
},
body: JSON.stringify({
status: "Hello, world!",
visibility: "invalid",
federate: false,
}),
}),
);
expect(response.status).toBe(422);
});
test("should return 422 if scheduled_at is invalid", async () => {
const response = await sendTestRequest(
new Request(new URL(meta.route, config.http.base_url), {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${tokens[0].accessToken}`,
},
body: JSON.stringify({
status: "Hello, world!",
scheduled_at: "invalid",
federate: false,
}),
}),
);
expect(response.status).toBe(422);
});
test("should return 404 is in_reply_to_id is invalid", async () => {
const response = await sendTestRequest(
new Request(new URL(meta.route, config.http.base_url), {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${tokens[0].accessToken}`,
},
body: JSON.stringify({
status: "Hello, world!",
in_reply_to_id: "invalid",
federate: false,
}),
}),
);
expect(response.status).toBe(404);
});
test("should return 404 is quote_id is invalid", async () => {
const response = await sendTestRequest(
new Request(new URL(meta.route, config.http.base_url), {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${tokens[0].accessToken}`,
},
body: JSON.stringify({
status: "Hello, world!",
quote_id: "invalid",
federate: false,
}),
}),
);
expect(response.status).toBe(404);
});
test("should return 422 is media_ids is invalid", async () => {
const response = await sendTestRequest(
new Request(new URL(meta.route, config.http.base_url), {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${tokens[0].accessToken}`,
},
body: JSON.stringify({
status: "Hello, world!",
media_ids: ["invalid"],
federate: false,
}),
}),
);
expect(response.status).toBe(422);
});
test("should create a post", async () => {
const response = await sendTestRequest(
new Request(new URL(meta.route, config.http.base_url), {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${tokens[0].accessToken}`,
},
body: JSON.stringify({
status: "Hello, world!",
federate: false,
}),
}),
);
expect(response.status).toBe(200);
expect(response.headers.get("content-type")).toBe("application/json");
const object = (await response.json()) as APIStatus;
expect(object.content).toBe("<p>Hello, world!</p>");
});
test("should create a post with visibility", async () => {
const response = await sendTestRequest(
new Request(new URL(meta.route, config.http.base_url), {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${tokens[0].accessToken}`,
},
body: JSON.stringify({
status: "Hello, world!",
visibility: "unlisted",
federate: false,
}),
}),
);
expect(response.status).toBe(200);
expect(response.headers.get("content-type")).toBe("application/json");
const object = (await response.json()) as APIStatus;
expect(object.content).toBe("<p>Hello, world!</p>");
expect(object.visibility).toBe("unlisted");
});
test("should create a post with a reply", async () => {
const response = await sendTestRequest(
new Request(new URL(meta.route, config.http.base_url), {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${tokens[0].accessToken}`,
},
body: JSON.stringify({
status: "Hello, world!",
federate: false,
}),
}),
);
const object = (await response.json()) as APIStatus;
const response2 = await sendTestRequest(
new Request(new URL(meta.route, config.http.base_url), {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${tokens[0].accessToken}`,
},
body: JSON.stringify({
status: "Hello, world again!",
in_reply_to_id: object.id,
federate: false,
}),
}),
);
expect(response2.status).toBe(200);
expect(response2.headers.get("content-type")).toBe("application/json");
const object2 = (await response2.json()) as APIStatus;
expect(object2.content).toBe("<p>Hello, world again!</p>");
expect(object2.in_reply_to_id).toBe(object.id);
});
test("should create a post with a quote", async () => {
const response = await sendTestRequest(
new Request(new URL(meta.route, config.http.base_url), {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${tokens[0].accessToken}`,
},
body: JSON.stringify({
status: "Hello, world!",
federate: false,
}),
}),
);
const object = (await response.json()) as APIStatus;
const response2 = await sendTestRequest(
new Request(new URL(meta.route, config.http.base_url), {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${tokens[0].accessToken}`,
},
body: JSON.stringify({
status: "Hello, world again!",
quote_id: object.id,
federate: false,
}),
}),
);
expect(response2.status).toBe(200);
expect(response2.headers.get("content-type")).toBe("application/json");
const object2 = (await response2.json()) as APIStatus;
expect(object2.content).toBe("<p>Hello, world again!</p>");
expect(object2.quote_id).toBe(object.id);
});
});

View file

@ -14,6 +14,7 @@ import {
} from "~database/entities/Status";
import type { UserWithRelations } from "~database/entities/User";
import { statusAndUserRelations } from "~database/entities/relations";
import { db } from "~drizzle/db";
import type { APIStatus } from "~types/entities/status";
export const meta = applyConfig({
@ -49,7 +50,7 @@ export default apiRoute<{
content_type?: string;
federate?: boolean;
}>(async (req, matchedRoute, extraData) => {
const { user, token } = extraData.auth;
const { user } = extraData.auth;
if (!user) return errorResponse("Unauthorized", 401);
@ -138,11 +139,22 @@ export default apiRoute<{
}
if (scheduled_at) {
if (new Date(scheduled_at).getTime() < Date.now()) {
if (
Number.isNaN(new Date(scheduled_at).getTime()) ||
new Date(scheduled_at).getTime() < Date.now()
) {
return errorResponse("Scheduled time must be in the future", 422);
}
}
// Validate visibility
if (
visibility &&
!["public", "unlisted", "private", "direct"].includes(visibility)
) {
return errorResponse("Invalid visibility", 422);
}
let sanitizedStatus: string;
if (content_type === "text/markdown") {
@ -162,14 +174,6 @@ export default apiRoute<{
);
}
// Validate visibility
if (
visibility &&
!["public", "unlisted", "private", "direct"].includes(visibility)
) {
return errorResponse("Invalid visibility", 422);
}
// Get reply account and status if exists
let replyStatus: StatusWithRelations | null = null;
let quote: StatusWithRelations | null = null;
@ -177,7 +181,7 @@ export default apiRoute<{
if (in_reply_to_id) {
replyStatus = await findFirstStatuses({
where: (status, { eq }) => eq(status.id, in_reply_to_id),
});
}).catch(() => null);
if (!replyStatus) {
return errorResponse("Reply status not found", 404);
@ -187,7 +191,7 @@ export default apiRoute<{
if (quote_id) {
quote = await findFirstStatuses({
where: (status, { eq }) => eq(status.id, quote_id),
});
}).catch(() => null);
if (!quote) {
return errorResponse("Quote status not found", 404);
@ -200,17 +204,17 @@ export default apiRoute<{
}
// Check if media attachments are all valid
if (media_ids && media_ids.length > 0) {
const foundAttachments = await db.query.attachment
.findMany({
where: (attachment, { inArray }) =>
inArray(attachment.id, media_ids),
})
.catch(() => []);
const foundAttachments = await client.attachment.findMany({
where: {
id: {
in: media_ids ?? [],
},
},
});
if (foundAttachments.length !== (media_ids ?? []).length) {
return errorResponse("Invalid media IDs", 422);
if (foundAttachments.length !== (media_ids ?? []).length) {
return errorResponse("Invalid media IDs", 422);
}
}
const mentions = await parseTextMentions(sanitizedStatus);
@ -222,7 +226,7 @@ export default apiRoute<{
content: sanitizedStatus ?? "",
},
},
visibility as APIStatus["visibility"],
visibility ?? "public",
sensitive ?? false,
spoiler_text ?? "",
[],