mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 08:28:19 +01:00
Add more tests, fix roiutes
This commit is contained in:
parent
0469187876
commit
3ccff003f5
|
|
@ -847,7 +847,7 @@ export const createNewStatus = async (
|
||||||
content["text/markdown"]?.content ||
|
content["text/markdown"]?.content ||
|
||||||
"",
|
"",
|
||||||
contentType: "text/html",
|
contentType: "text/html",
|
||||||
visibility: visibility,
|
visibility,
|
||||||
sensitive: is_sensitive,
|
sensitive: is_sensitive,
|
||||||
spoilerText: spoiler_text,
|
spoilerText: spoiler_text,
|
||||||
instanceId: author.instanceId || null,
|
instanceId: author.instanceId || null,
|
||||||
|
|
@ -1148,7 +1148,7 @@ export const statusToAPI = async (
|
||||||
`/@${statusToConvert.author.username}/${statusToConvert.id}`,
|
`/@${statusToConvert.author.username}/${statusToConvert.id}`,
|
||||||
config.http.base_url,
|
config.http.base_url,
|
||||||
).toString(),
|
).toString(),
|
||||||
visibility: "public",
|
visibility: statusToConvert.visibility as APIStatus["visibility"],
|
||||||
url:
|
url:
|
||||||
statusToConvert.uri ||
|
statusToConvert.uri ||
|
||||||
new URL(
|
new URL(
|
||||||
|
|
|
||||||
110
server/api/api/v1/accounts/[id]/index.test.ts
Normal file
110
server/api/api/v1/accounts/[id]/index.test.ts
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
import { apiRoute, applyConfig } from "@api";
|
import { apiRoute, applyConfig } from "@api";
|
||||||
import { errorResponse, jsonResponse } from "@response";
|
import { errorResponse, jsonResponse } from "@response";
|
||||||
import { client } from "~database/datasource";
|
import { findFirstUser, userToAPI } from "~database/entities/User";
|
||||||
import type { UserWithRelations } from "~database/entities/User";
|
|
||||||
import { userToAPI } from "~database/entities/User";
|
|
||||||
import { userRelations } from "~database/entities/relations";
|
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
allowedMethods: ["GET"],
|
allowedMethods: ["GET"],
|
||||||
|
|
@ -34,15 +31,9 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
|
||||||
|
|
||||||
const { user } = extraData.auth;
|
const { user } = extraData.auth;
|
||||||
|
|
||||||
let foundUser: UserWithRelations | null;
|
const foundUser = await findFirstUser({
|
||||||
try {
|
where: (user, { eq }) => eq(user.id, id),
|
||||||
foundUser = await client.user.findUnique({
|
}).catch(() => null);
|
||||||
where: { id },
|
|
||||||
include: userRelations,
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
return errorResponse("Invalid ID", 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!foundUser) return errorResponse("User not found", 404);
|
if (!foundUser) return errorResponse("User not found", 404);
|
||||||
|
|
||||||
|
|
|
||||||
91
server/api/api/v1/accounts/lookup/index.test.ts
Normal file
91
server/api/api/v1/accounts/lookup/index.test.ts
Normal 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),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { apiRoute, applyConfig } from "@api";
|
import { apiRoute, applyConfig } from "@api";
|
||||||
import { errorResponse, jsonResponse } from "@response";
|
import { errorResponse, jsonResponse } from "@response";
|
||||||
import { client } from "~database/datasource";
|
import {
|
||||||
import type { UserWithRelations } from "~database/entities/User";
|
findFirstUser,
|
||||||
import { resolveWebFinger, userToAPI } from "~database/entities/User";
|
resolveWebFinger,
|
||||||
import { userRelations } from "~database/entities/relations";
|
userToAPI,
|
||||||
|
} from "~database/entities/User";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
allowedMethods: ["GET"],
|
allowedMethods: ["GET"],
|
||||||
|
|
@ -57,11 +58,8 @@ export default apiRoute<{
|
||||||
username = username.slice(1);
|
username = username.slice(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const account = await client.user.findFirst({
|
const account = await findFirstUser({
|
||||||
where: {
|
where: (user, { eq }) => eq(user.username, username),
|
||||||
username,
|
|
||||||
},
|
|
||||||
include: userRelations,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (account) {
|
if (account) {
|
||||||
|
|
|
||||||
114
server/api/api/v1/accounts/search/index.test.ts
Normal file
114
server/api/api/v1/accounts/search/index.test.ts
Normal 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),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,8 +1,13 @@
|
||||||
import { apiRoute, applyConfig } from "@api";
|
import { apiRoute, applyConfig } from "@api";
|
||||||
import { errorResponse, jsonResponse } from "@response";
|
import { errorResponse, jsonResponse } from "@response";
|
||||||
import { client } from "~database/datasource";
|
import { sql } from "drizzle-orm";
|
||||||
import { userToAPI } from "~database/entities/User";
|
import {
|
||||||
import { userRelations } from "~database/entities/relations";
|
findManyUsers,
|
||||||
|
resolveWebFinger,
|
||||||
|
userToAPI,
|
||||||
|
type UserWithRelations,
|
||||||
|
} from "~database/entities/User";
|
||||||
|
import { user } from "~drizzle/schema";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
allowedMethods: ["GET"],
|
allowedMethods: ["GET"],
|
||||||
|
|
@ -29,46 +34,47 @@ export default apiRoute<{
|
||||||
following = false,
|
following = false,
|
||||||
limit = 40,
|
limit = 40,
|
||||||
offset,
|
offset,
|
||||||
|
resolve,
|
||||||
q,
|
q,
|
||||||
} = extraData.parsedRequest;
|
} = 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) {
|
if (limit < 1 || limit > 80) {
|
||||||
return errorResponse("Limit must be between 1 and 80", 400);
|
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({
|
const [username, host] = q?.split("@") || [];
|
||||||
where: {
|
|
||||||
OR: [
|
const accounts: UserWithRelations[] = [];
|
||||||
{
|
|
||||||
displayName: {
|
if (resolve && username && host) {
|
||||||
contains: q,
|
const resolvedUser = await resolveWebFinger(username, host);
|
||||||
},
|
|
||||||
},
|
if (resolvedUser) {
|
||||||
{
|
accounts.push(resolvedUser);
|
||||||
username: {
|
}
|
||||||
contains: q,
|
} else {
|
||||||
},
|
accounts.push(
|
||||||
},
|
...(await findManyUsers({
|
||||||
],
|
where: (account, { or, like }) =>
|
||||||
relationshipSubjects: following
|
or(
|
||||||
? {
|
like(account.displayName, `%${q}%`),
|
||||||
some: {
|
like(account.username, `%${q}%`),
|
||||||
ownerId: user?.id,
|
following
|
||||||
following,
|
? sql`EXISTS (SELECT 1 FROM "Relationship" WHERE "Relationship"."subjectId" = ${user.id} AND "Relationship"."ownerId" = ${account.id} AND "Relationship"."following" = true)`
|
||||||
},
|
: undefined,
|
||||||
}
|
),
|
||||||
: undefined,
|
offset: Number(offset),
|
||||||
},
|
})),
|
||||||
take: Number(limit),
|
);
|
||||||
skip: Number(offset || 0),
|
}
|
||||||
include: userRelations,
|
|
||||||
});
|
|
||||||
|
|
||||||
return jsonResponse(accounts.map((acct) => userToAPI(acct)));
|
return jsonResponse(accounts.map((acct) => userToAPI(acct)));
|
||||||
});
|
});
|
||||||
|
|
|
||||||
116
server/api/api/v1/statuses/[id]/favourited_by.test.ts
Normal file
116
server/api/api/v1/statuses/[id]/favourited_by.test.ts
Normal 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
116
server/api/api/v1/statuses/[id]/reblogged_by.test.ts
Normal file
116
server/api/api/v1/statuses/[id]/reblogged_by.test.ts
Normal 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
297
server/api/api/v1/statuses/index.test.ts
Normal file
297
server/api/api/v1/statuses/index.test.ts
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -14,6 +14,7 @@ import {
|
||||||
} from "~database/entities/Status";
|
} from "~database/entities/Status";
|
||||||
import type { UserWithRelations } from "~database/entities/User";
|
import type { UserWithRelations } from "~database/entities/User";
|
||||||
import { statusAndUserRelations } from "~database/entities/relations";
|
import { statusAndUserRelations } from "~database/entities/relations";
|
||||||
|
import { db } from "~drizzle/db";
|
||||||
import type { APIStatus } from "~types/entities/status";
|
import type { APIStatus } from "~types/entities/status";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
|
|
@ -49,7 +50,7 @@ export default apiRoute<{
|
||||||
content_type?: string;
|
content_type?: string;
|
||||||
federate?: boolean;
|
federate?: boolean;
|
||||||
}>(async (req, matchedRoute, extraData) => {
|
}>(async (req, matchedRoute, extraData) => {
|
||||||
const { user, token } = extraData.auth;
|
const { user } = extraData.auth;
|
||||||
|
|
||||||
if (!user) return errorResponse("Unauthorized", 401);
|
if (!user) return errorResponse("Unauthorized", 401);
|
||||||
|
|
||||||
|
|
@ -138,11 +139,22 @@ export default apiRoute<{
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scheduled_at) {
|
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);
|
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;
|
let sanitizedStatus: string;
|
||||||
|
|
||||||
if (content_type === "text/markdown") {
|
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
|
// Get reply account and status if exists
|
||||||
let replyStatus: StatusWithRelations | null = null;
|
let replyStatus: StatusWithRelations | null = null;
|
||||||
let quote: StatusWithRelations | null = null;
|
let quote: StatusWithRelations | null = null;
|
||||||
|
|
@ -177,7 +181,7 @@ export default apiRoute<{
|
||||||
if (in_reply_to_id) {
|
if (in_reply_to_id) {
|
||||||
replyStatus = await findFirstStatuses({
|
replyStatus = await findFirstStatuses({
|
||||||
where: (status, { eq }) => eq(status.id, in_reply_to_id),
|
where: (status, { eq }) => eq(status.id, in_reply_to_id),
|
||||||
});
|
}).catch(() => null);
|
||||||
|
|
||||||
if (!replyStatus) {
|
if (!replyStatus) {
|
||||||
return errorResponse("Reply status not found", 404);
|
return errorResponse("Reply status not found", 404);
|
||||||
|
|
@ -187,7 +191,7 @@ export default apiRoute<{
|
||||||
if (quote_id) {
|
if (quote_id) {
|
||||||
quote = await findFirstStatuses({
|
quote = await findFirstStatuses({
|
||||||
where: (status, { eq }) => eq(status.id, quote_id),
|
where: (status, { eq }) => eq(status.id, quote_id),
|
||||||
});
|
}).catch(() => null);
|
||||||
|
|
||||||
if (!quote) {
|
if (!quote) {
|
||||||
return errorResponse("Quote status not found", 404);
|
return errorResponse("Quote status not found", 404);
|
||||||
|
|
@ -200,17 +204,17 @@ export default apiRoute<{
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if media attachments are all valid
|
// 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({
|
if (foundAttachments.length !== (media_ids ?? []).length) {
|
||||||
where: {
|
return errorResponse("Invalid media IDs", 422);
|
||||||
id: {
|
}
|
||||||
in: media_ids ?? [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (foundAttachments.length !== (media_ids ?? []).length) {
|
|
||||||
return errorResponse("Invalid media IDs", 422);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const mentions = await parseTextMentions(sanitizedStatus);
|
const mentions = await parseTextMentions(sanitizedStatus);
|
||||||
|
|
@ -222,7 +226,7 @@ export default apiRoute<{
|
||||||
content: sanitizedStatus ?? "",
|
content: sanitizedStatus ?? "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
visibility as APIStatus["visibility"],
|
visibility ?? "public",
|
||||||
sensitive ?? false,
|
sensitive ?? false,
|
||||||
spoiler_text ?? "",
|
spoiler_text ?? "",
|
||||||
[],
|
[],
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue