refactor(database): ♻️ Move Token to its own ORM abstraction, optimize familiar_followers route

This commit is contained in:
Jesse Wierzbinski 2024-11-03 17:45:21 +01:00
parent 962c159ddd
commit 845041e4db
No known key found for this signature in database
55 changed files with 694 additions and 504 deletions

View file

@ -27,7 +27,7 @@ describe(meta.route, () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );
@ -40,7 +40,7 @@ describe(meta.route, () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );
@ -56,7 +56,7 @@ describe(meta.route, () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );

View file

@ -31,7 +31,7 @@ describe(meta.route, () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({}), body: JSON.stringify({}),
@ -46,7 +46,7 @@ describe(meta.route, () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({}), body: JSON.stringify({}),
@ -64,7 +64,7 @@ describe(meta.route, () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({}), body: JSON.stringify({}),

View file

@ -16,7 +16,7 @@ beforeAll(async () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({}), body: JSON.stringify({}),
@ -33,7 +33,7 @@ describe(meta.route, () => {
meta.route.replace(":id", users[1].id), meta.route.replace(":id", users[1].id),
{ {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );
@ -54,7 +54,7 @@ describe(meta.route, () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );
@ -65,7 +65,7 @@ describe(meta.route, () => {
meta.route.replace(":id", users[1].id), meta.route.replace(":id", users[1].id),
{ {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );

View file

@ -16,7 +16,7 @@ beforeAll(async () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({}), body: JSON.stringify({}),
@ -33,7 +33,7 @@ describe(meta.route, () => {
meta.route.replace(":id", users[0].id), meta.route.replace(":id", users[0].id),
{ {
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
}, },
}, },
); );
@ -54,7 +54,7 @@ describe(meta.route, () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );
@ -65,7 +65,7 @@ describe(meta.route, () => {
meta.route.replace(":id", users[0].id), meta.route.replace(":id", users[0].id),
{ {
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
}, },
}, },
); );

View file

@ -15,7 +15,7 @@ beforeAll(async () => {
await fakeRequest(`/api/v1/statuses/${status.id}/favourite`, { await fakeRequest(`/api/v1/statuses/${status.id}/favourite`, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({}), body: JSON.stringify({}),

View file

@ -31,7 +31,7 @@ describe(meta.route, () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({}), body: JSON.stringify({}),
@ -46,7 +46,7 @@ describe(meta.route, () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({}), body: JSON.stringify({}),
@ -64,7 +64,7 @@ describe(meta.route, () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({}), body: JSON.stringify({}),

View file

@ -17,7 +17,7 @@ beforeAll(async () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
}, },
}, },
); );
@ -33,7 +33,7 @@ describe(meta.route, () => {
{ {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );
@ -53,7 +53,7 @@ describe(meta.route, () => {
{ {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );
@ -72,7 +72,7 @@ describe(meta.route, () => {
const replyResponse = await fakeRequest("/api/v1/statuses", { const replyResponse = await fakeRequest("/api/v1/statuses", {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
}, },
body: new URLSearchParams({ body: new URLSearchParams({
status: "Reply", status: "Reply",
@ -88,7 +88,7 @@ describe(meta.route, () => {
{ {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );
@ -108,7 +108,7 @@ describe(meta.route, () => {
{ {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );
@ -126,7 +126,7 @@ describe(meta.route, () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
}, },
}, },
); );
@ -138,7 +138,7 @@ describe(meta.route, () => {
{ {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );

View file

@ -13,7 +13,7 @@ beforeAll(async () => {
await fakeRequest(`/api/v1/accounts/${users[0].id}/mute`, { await fakeRequest(`/api/v1/accounts/${users[0].id}/mute`, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
}, },
}); });
}); });
@ -36,7 +36,7 @@ describe(meta.route, () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );
@ -49,7 +49,7 @@ describe(meta.route, () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );
@ -65,7 +65,7 @@ describe(meta.route, () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );

View file

@ -0,0 +1,156 @@
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
import { fakeRequest, getTestUsers } from "~/tests/utils.ts";
import { meta } from "./index.ts";
const { users, tokens, deleteUsers } = await getTestUsers(5);
beforeAll(async () => {
// Create followers relationships
const result1 = await fakeRequest(
`/api/v1/accounts/${users[1].id}/follow`,
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
expect(result1.status).toBe(200);
const result2 = await fakeRequest(
`/api/v1/accounts/${users[2].id}/follow`,
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
expect(result2.status).toBe(200);
const result3 = await fakeRequest(
`/api/v1/accounts/${users[3].id}/follow`,
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
expect(result3.status).toBe(200);
const result4 = await fakeRequest(
`/api/v1/accounts/${users[2].id}/follow`,
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[1].data.accessToken}`,
},
},
);
expect(result4.status).toBe(200);
const result5 = await fakeRequest(
`/api/v1/accounts/${users[3].id}/follow`,
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[1].data.accessToken}`,
},
},
);
expect(result5.status).toBe(200);
const result6 = await fakeRequest(
`/api/v1/accounts/${users[3].id}/follow`,
{
method: "POST",
headers: {
Authorization: `Bearer ${tokens[2].data.accessToken}`,
},
},
);
expect(result6.status).toBe(200);
});
afterAll(async () => {
await deleteUsers();
});
describe(meta.route, () => {
test("should return 0 familiar followers", async () => {
const response = await fakeRequest(`${meta.route}?id=${users[4].id}`, {
headers: {
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
});
expect(response.status).toBe(200);
const data = await response.json();
expect(data.length).toBe(1);
expect(data[0].id).toBe(users[4].id);
expect(data[0].accounts).toBeArrayOfSize(0);
});
test("should return 1 familiar follower", async () => {
const response = await fakeRequest(`${meta.route}?id=${users[2].id}`, {
headers: {
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
});
expect(response.status).toBe(200);
const data = await response.json();
expect(data.length).toBe(1);
expect(data[0].id).toBe(users[2].id);
expect(data[0].accounts[0].id).toBe(users[1].id);
});
test("should return 2 familiar followers", async () => {
const response = await fakeRequest(`${meta.route}?id=${users[3].id}`, {
headers: {
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
});
expect(response.status).toBe(200);
const data = await response.json();
expect(data.length).toBe(1);
expect(data[0].id).toBe(users[3].id);
expect(data[0].accounts).toBeArrayOfSize(2);
expect(data[0].accounts[0].id).toBe(users[2].id);
expect(data[0].accounts[1].id).toBe(users[1].id);
});
test("should work with multiple ids", async () => {
const response = await fakeRequest(
`${meta.route}?id[]=${users[2].id}&id[]=${users[3].id}&id[]=${users[4].id}`,
{
headers: {
Authorization: `Bearer ${tokens[0].data.accessToken}`,
},
},
);
expect(response.status).toBe(200);
const data = await response.json();
expect(data.length).toBe(3);
expect(data[0].id).toBe(users[2].id);
expect(data[0].accounts[0].id).toBe(users[1].id);
expect(data[1].id).toBe(users[3].id);
expect(data[1].accounts[0].id).toBe(users[2].id);
expect(data[1].accounts[1].id).toBe(users[1].id);
expect(data[2].id).toBe(users[4].id);
expect(data[2].accounts).toBeArrayOfSize(0);
});
});

View file

@ -1,8 +1,8 @@
import { apiRoute, applyConfig, auth, qsQuery } from "@/api"; import { apiRoute, applyConfig, auth, qsQuery } from "@/api";
import { createRoute } from "@hono/zod-openapi"; import { createRoute } from "@hono/zod-openapi";
import { User, db } from "@versia/kit/db"; import { User, db } from "@versia/kit/db";
import { RolePermissions, Users } from "@versia/kit/tables"; import { RolePermissions, type Users } from "@versia/kit/tables";
import { type SQL, inArray } from "drizzle-orm"; import { type InferSelectModel, sql } from "drizzle-orm";
import { z } from "zod"; import { z } from "zod";
import { ErrorSchema } from "~/types/api"; import { ErrorSchema } from "~/types/api";
@ -23,7 +23,12 @@ export const meta = applyConfig({
export const schemas = { export const schemas = {
query: z.object({ query: z.object({
id: z.array(z.string().uuid()).min(1).max(10).or(z.string().uuid()), id: z
.array(z.string().uuid())
.min(1)
.max(10)
.or(z.string().uuid())
.transform((v) => (Array.isArray(v) ? v : [v])),
}), }),
}; };
@ -42,7 +47,12 @@ const route = createRoute({
description: "Familiar followers", description: "Familiar followers",
content: { content: {
"application/json": { "application/json": {
schema: z.array(User.schema), schema: z.array(
z.object({
id: z.string().uuid(),
accounts: z.array(User.schema),
}),
),
}, },
}, },
}, },
@ -66,53 +76,35 @@ export default apiRoute((app) =>
return context.json({ error: "Unauthorized" }, 401); return context.json({ error: "Unauthorized" }, 401);
} }
const idFollowerRelationships = await db.query.Relationships.findMany({ // Find followers of the accounts in "ids", that you also follow
columns: { const finalUsers = await Promise.all(
ownerId: true, ids.map(async (id) => ({
}, id,
where: (relationship, { inArray, and, eq }): SQL | undefined => accounts: await User.fromIds(
and( (
inArray( await db.execute(sql<InferSelectModel<typeof Users>>`
relationship.subjectId, SELECT "Users"."id" FROM "Users"
Array.isArray(ids) ? ids : [ids], INNER JOIN "Relationships" AS "SelfFollowing"
), ON "SelfFollowing"."subjectId" = "Users"."id"
eq(relationship.following, true), WHERE "SelfFollowing"."ownerId" = ${self.id}
), AND "SelfFollowing"."following" = true
}); AND EXISTS (
SELECT 1 FROM "Relationships" AS "IdsFollowers"
if (idFollowerRelationships.length === 0) { WHERE "IdsFollowers"."subjectId" = ${id}
return context.json([], 200); AND "IdsFollowers"."ownerId" = "Users"."id"
} AND "IdsFollowers"."following" = true
)
// Find users that you follow in idFollowerRelationships `)
const relevantRelationships = await db.query.Relationships.findMany({ ).rows.map((u) => u.id as string),
columns: {
subjectId: true,
},
where: (relationship, { inArray, and, eq }): SQL | undefined =>
and(
eq(relationship.ownerId, self.id),
inArray(
relationship.subjectId,
idFollowerRelationships.map((f) => f.ownerId),
),
eq(relationship.following, true),
),
});
if (relevantRelationships.length === 0) {
return context.json([], 200);
}
const finalUsers = await User.manyFromSql(
inArray(
Users.id,
relevantRelationships.map((r) => r.subjectId),
), ),
})),
); );
return context.json( return context.json(
finalUsers.map((o) => o.toApi()), finalUsers.map((u) => ({
...u,
accounts: u.accounts.map((a) => a.toApi()),
})),
200, 200,
); );
}), }),

View file

@ -16,7 +16,7 @@ describe(meta.route, () => {
`${meta.route}?acct=${users[0].data.username}`, `${meta.route}?acct=${users[0].data.username}`,
{ {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );

View file

@ -19,7 +19,7 @@ beforeAll(async () => {
const res1 = await fakeRequest(`/api/v1/accounts/${users[0].id}/follow`, { const res1 = await fakeRequest(`/api/v1/accounts/${users[0].id}/follow`, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({}), body: JSON.stringify({}),
@ -30,7 +30,7 @@ beforeAll(async () => {
const res2 = await fakeRequest(`/api/v1/accounts/${users[2].id}/follow`, { const res2 = await fakeRequest(`/api/v1/accounts/${users[2].id}/follow`, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({}), body: JSON.stringify({}),
@ -56,7 +56,7 @@ describe(meta.route, () => {
`${meta.route}?id[]=${users[2].id}`, `${meta.route}?id[]=${users[2].id}`,
{ {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );
@ -86,7 +86,7 @@ describe(meta.route, () => {
`${meta.route}?id[]=${users[1].id}`, `${meta.route}?id[]=${users[1].id}`,
{ {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );

View file

@ -16,7 +16,7 @@ describe(meta.route, () => {
`${meta.route}?q=${users[0].data.username}`, `${meta.route}?q=${users[0].data.username}`,
{ {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );

View file

@ -55,7 +55,9 @@ export default apiRoute((app) =>
return context.json({ error: "Unauthorized" }, 401); return context.json({ error: "Unauthorized" }, 401);
} }
const application = await Application.getFromToken(token); const application = await Application.getFromToken(
token.data.accessToken,
);
if (!application) { if (!application) {
return context.json({ error: "Unauthorized" }, 401); return context.json({ error: "Unauthorized" }, 401);

View file

@ -14,7 +14,7 @@ beforeAll(async () => {
// Upload one emoji as admin, then one as each user // Upload one emoji as admin, then one as each user
const response = await fakeRequest("/api/v1/emojis", { const response = await fakeRequest("/api/v1/emojis", {
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
method: "POST", method: "POST",
@ -29,7 +29,7 @@ beforeAll(async () => {
await fakeRequest("/api/v1/emojis", { await fakeRequest("/api/v1/emojis", {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
method: "POST", method: "POST",
@ -41,7 +41,7 @@ beforeAll(async () => {
await fakeRequest("/api/v1/emojis", { await fakeRequest("/api/v1/emojis", {
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
method: "POST", method: "POST",
@ -64,7 +64,7 @@ describe(meta.route, () => {
test("should return all global emojis", async () => { test("should return all global emojis", async () => {
const response = await fakeRequest(meta.route, { const response = await fakeRequest(meta.route, {
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
}, },
}); });
@ -96,7 +96,7 @@ describe(meta.route, () => {
test("should return all user emojis", async () => { test("should return all user emojis", async () => {
const response = await fakeRequest(meta.route, { const response = await fakeRequest(meta.route, {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}); });

View file

@ -15,7 +15,7 @@ beforeAll(async () => {
// Create an emoji // Create an emoji
const response = await fakeRequest("/api/v1/emojis", { const response = await fakeRequest("/api/v1/emojis", {
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
method: "POST", method: "POST",
@ -55,7 +55,7 @@ describe(meta.route, () => {
{ {
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
}, },
method: "GET", method: "GET",
}, },
@ -67,7 +67,7 @@ describe(meta.route, () => {
test("should not work if the user is trying to update an emoji they don't own", async () => { test("should not work if the user is trying to update an emoji they don't own", async () => {
const response = await fakeRequest(meta.route.replace(":id", id), { const response = await fakeRequest(meta.route.replace(":id", id), {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
method: "PATCH", method: "PATCH",
@ -82,7 +82,7 @@ describe(meta.route, () => {
test("should return the emoji", async () => { test("should return the emoji", async () => {
const response = await fakeRequest(meta.route.replace(":id", id), { const response = await fakeRequest(meta.route.replace(":id", id), {
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
}, },
method: "GET", method: "GET",
}); });
@ -95,7 +95,7 @@ describe(meta.route, () => {
test("should update the emoji", async () => { test("should update the emoji", async () => {
const response = await fakeRequest(meta.route.replace(":id", id), { const response = await fakeRequest(meta.route.replace(":id", id), {
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
method: "PATCH", method: "PATCH",
@ -112,7 +112,7 @@ describe(meta.route, () => {
test("should update the emoji with another url, but keep the shortcode", async () => { test("should update the emoji with another url, but keep the shortcode", async () => {
const response = await fakeRequest(meta.route.replace(":id", id), { const response = await fakeRequest(meta.route.replace(":id", id), {
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
method: "PATCH", method: "PATCH",
@ -129,7 +129,7 @@ describe(meta.route, () => {
test("should update the emoji to be non-global", async () => { test("should update the emoji to be non-global", async () => {
const response = await fakeRequest(meta.route.replace(":id", id), { const response = await fakeRequest(meta.route.replace(":id", id), {
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
method: "PATCH", method: "PATCH",
@ -143,7 +143,7 @@ describe(meta.route, () => {
// Check if the other user can see it // Check if the other user can see it
const response2 = await fakeRequest("/api/v1/custom_emojis", { const response2 = await fakeRequest("/api/v1/custom_emojis", {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
method: "GET", method: "GET",
}); });
@ -156,7 +156,7 @@ describe(meta.route, () => {
test("should delete the emoji", async () => { test("should delete the emoji", async () => {
const response = await fakeRequest(meta.route.replace(":id", id), { const response = await fakeRequest(meta.route.replace(":id", id), {
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
}, },
method: "DELETE", method: "DELETE",
}); });

View file

@ -64,7 +64,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, { const response = await fakeRequest(meta.route, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
}, },
body: formData, body: formData,
}); });
@ -83,7 +83,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, { const response = await fakeRequest(meta.route, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
}, },
body: formData, body: formData,
}); });
@ -95,7 +95,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, { const response = await fakeRequest(meta.route, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
@ -118,7 +118,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, { const response = await fakeRequest(meta.route, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
}, },
body: formData, body: formData,
}); });
@ -136,7 +136,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, { const response = await fakeRequest(meta.route, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
body: formData, body: formData,
}); });
@ -155,7 +155,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, { const response = await fakeRequest(meta.route, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
body: formData, body: formData,
}); });
@ -171,7 +171,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, { const response = await fakeRequest(meta.route, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[2].accessToken}`, Authorization: `Bearer ${tokens[2].data.accessToken}`,
}, },
body: formData, body: formData,
}); });

View file

@ -27,7 +27,7 @@ describe(meta.route, () => {
{ {
method: "GET", method: "GET",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );
@ -45,7 +45,7 @@ describe(meta.route, () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );
@ -69,7 +69,7 @@ describe(meta.route, () => {
{ {
method: "GET", method: "GET",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );

View file

@ -15,7 +15,7 @@ beforeAll(async () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({}), body: JSON.stringify({}),
@ -41,7 +41,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, { const response = await fakeRequest(meta.route, {
method: "GET", method: "GET",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}); });
expect(response.status).toBe(200); expect(response.status).toBe(200);
@ -62,7 +62,7 @@ describe(meta.route, () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({}), body: JSON.stringify({}),
@ -73,7 +73,7 @@ describe(meta.route, () => {
const response2 = await fakeRequest(meta.route, { const response2 = await fakeRequest(meta.route, {
method: "GET", method: "GET",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}); });
expect(response2.status).toBe(200); expect(response2.status).toBe(200);

View file

@ -11,7 +11,7 @@ beforeAll(async () => {
await fakeRequest(`/api/v1/accounts/${users[0].id}/follow`, { await fakeRequest(`/api/v1/accounts/${users[0].id}/follow`, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({}), body: JSON.stringify({}),
@ -19,7 +19,7 @@ beforeAll(async () => {
notifications = await fakeRequest("/api/v1/notifications", { notifications = await fakeRequest("/api/v1/notifications", {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}).then((r) => r.json()); }).then((r) => r.json());
@ -49,7 +49,7 @@ describe(meta.route, () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );
@ -60,7 +60,7 @@ describe(meta.route, () => {
test("should not display dismissed notification", async () => { test("should not display dismissed notification", async () => {
const response = await fakeRequest("/api/v1/notifications", { const response = await fakeRequest("/api/v1/notifications", {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}); });

View file

@ -11,7 +11,7 @@ beforeAll(async () => {
await fakeRequest(`/api/v1/accounts/${users[0].id}/follow`, { await fakeRequest(`/api/v1/accounts/${users[0].id}/follow`, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({}), body: JSON.stringify({}),
@ -19,7 +19,7 @@ beforeAll(async () => {
notifications = await fakeRequest("/api/v1/notifications", { notifications = await fakeRequest("/api/v1/notifications", {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}).then((r) => r.json()); }).then((r) => r.json());
@ -45,7 +45,7 @@ describe(meta.route, () => {
meta.route.replace(":id", "invalid"), meta.route.replace(":id", "invalid"),
{ {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );
@ -57,7 +57,7 @@ describe(meta.route, () => {
meta.route.replace(":id", "00000000-0000-0000-0000-000000000000"), meta.route.replace(":id", "00000000-0000-0000-0000-000000000000"),
{ {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );
@ -70,7 +70,7 @@ describe(meta.route, () => {
meta.route.replace(":id", notifications[0].id), meta.route.replace(":id", notifications[0].id),
{ {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );

View file

@ -11,7 +11,7 @@ beforeAll(async () => {
await fakeRequest(`/api/v1/accounts/${users[0].id}/follow`, { await fakeRequest(`/api/v1/accounts/${users[0].id}/follow`, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({}), body: JSON.stringify({}),
@ -19,7 +19,7 @@ beforeAll(async () => {
notifications = await fakeRequest("/api/v1/notifications", { notifications = await fakeRequest("/api/v1/notifications", {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}).then((r) => r.json()); }).then((r) => r.json());
@ -44,7 +44,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, { const response = await fakeRequest(meta.route, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}); });
@ -52,7 +52,7 @@ describe(meta.route, () => {
const newNotifications = await fakeRequest("/api/v1/notifications", { const newNotifications = await fakeRequest("/api/v1/notifications", {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}).then((r) => r.json()); }).then((r) => r.json());

View file

@ -12,7 +12,7 @@ beforeAll(async () => {
await fakeRequest(`/api/v1/accounts/${users[0].id}/follow`, { await fakeRequest(`/api/v1/accounts/${users[0].id}/follow`, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({}), body: JSON.stringify({}),
@ -22,7 +22,7 @@ beforeAll(async () => {
await fakeRequest(`/api/v1/statuses/${statuses[i].id}/favourite`, { await fakeRequest(`/api/v1/statuses/${statuses[i].id}/favourite`, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({}), body: JSON.stringify({}),
@ -31,7 +31,7 @@ beforeAll(async () => {
notifications = await fakeRequest("/api/v1/notifications", { notifications = await fakeRequest("/api/v1/notifications", {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}).then((r) => r.json()); }).then((r) => r.json());
@ -65,7 +65,7 @@ describe(meta.route, () => {
{ {
method: "DELETE", method: "DELETE",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );
@ -76,7 +76,7 @@ describe(meta.route, () => {
test("should not display dismissed notification", async () => { test("should not display dismissed notification", async () => {
const response = await fakeRequest("/api/v1/notifications", { const response = await fakeRequest("/api/v1/notifications", {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}); });

View file

@ -18,7 +18,7 @@ beforeAll(async () => {
const res1 = await fakeRequest(`/api/v1/accounts/${users[0].id}/follow`, { const res1 = await fakeRequest(`/api/v1/accounts/${users[0].id}/follow`, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({}), body: JSON.stringify({}),
@ -31,7 +31,7 @@ beforeAll(async () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({}), body: JSON.stringify({}),
@ -45,7 +45,7 @@ beforeAll(async () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
}, },
body: getFormData({}), body: getFormData({}),
}, },
@ -56,7 +56,7 @@ beforeAll(async () => {
const res4 = await fakeRequest("/api/v1/statuses", { const res4 = await fakeRequest("/api/v1/statuses", {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
}, },
body: new URLSearchParams({ body: new URLSearchParams({
status: `@${users[0].data.username} test mention`, status: `@${users[0].data.username} test mention`,
@ -83,7 +83,7 @@ describe(meta.route, () => {
test("should return 200 with notifications", async () => { test("should return 200 with notifications", async () => {
const response = await fakeRequest(meta.route, { const response = await fakeRequest(meta.route, {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}); });
@ -113,7 +113,7 @@ describe(meta.route, () => {
const filterResponse = await fakeRequest("/api/v2/filters", { const filterResponse = await fakeRequest("/api/v2/filters", {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/x-www-form-urlencoded", "Content-Type": "application/x-www-form-urlencoded",
}, },
body: new URLSearchParams({ body: new URLSearchParams({
@ -132,7 +132,7 @@ describe(meta.route, () => {
const response = await fakeRequest(`${meta.route}?limit=20`, { const response = await fakeRequest(`${meta.route}?limit=20`, {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}); });
@ -157,7 +157,7 @@ describe(meta.route, () => {
{ {
method: "DELETE", method: "DELETE",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );

View file

@ -77,7 +77,7 @@ describe(meta.route, () => {
{ {
method: "GET", method: "GET",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );
@ -89,7 +89,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route.replace(":id", role.id), { const response = await fakeRequest(meta.route.replace(":id", role.id), {
method: "GET", method: "GET",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}); });
@ -111,7 +111,7 @@ describe(meta.route, () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );
@ -133,7 +133,7 @@ describe(meta.route, () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );
@ -144,7 +144,7 @@ describe(meta.route, () => {
const response2 = await fakeRequest("/api/v1/roles", { const response2 = await fakeRequest("/api/v1/roles", {
method: "GET", method: "GET",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}); });
@ -171,7 +171,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route.replace(":id", role.id), { const response = await fakeRequest(meta.route.replace(":id", role.id), {
method: "DELETE", method: "DELETE",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}); });
@ -181,7 +181,7 @@ describe(meta.route, () => {
const response2 = await fakeRequest("/api/v1/roles", { const response2 = await fakeRequest("/api/v1/roles", {
method: "GET", method: "GET",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}); });
@ -210,7 +210,7 @@ describe(meta.route, () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );

View file

@ -43,7 +43,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, { const response = await fakeRequest(meta.route, {
method: "GET", method: "GET",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}); });

View file

@ -29,7 +29,7 @@ describe(meta.route, () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
}, },
}, },
); );
@ -47,7 +47,7 @@ describe(meta.route, () => {
`/api/v1/statuses/${timeline[0].id}`, `/api/v1/statuses/${timeline[0].id}`,
{ {
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
}, },
}, },
); );

View file

@ -15,7 +15,7 @@ beforeAll(async () => {
await fakeRequest(`/api/v1/statuses/${status.id}/favourite`, { await fakeRequest(`/api/v1/statuses/${status.id}/favourite`, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
}, },
}); });
} }
@ -36,7 +36,7 @@ describe(meta.route, () => {
meta.route.replace(":id", timeline[0].id), meta.route.replace(":id", timeline[0].id),
{ {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );

View file

@ -15,7 +15,7 @@ beforeAll(async () => {
await fakeRequest(`/api/v1/statuses/${status.id}/reblog`, { await fakeRequest(`/api/v1/statuses/${status.id}/reblog`, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
}, },
}); });
} }
@ -36,7 +36,7 @@ describe(meta.route, () => {
meta.route.replace(":id", timeline[0].id), meta.route.replace(":id", timeline[0].id),
{ {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );

View file

@ -29,7 +29,7 @@ describe(meta.route, () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
}, },
}, },
); );
@ -42,7 +42,7 @@ describe(meta.route, () => {
await fakeRequest(`/api/v1/statuses/${timeline[1].id}/favourite`, { await fakeRequest(`/api/v1/statuses/${timeline[1].id}/favourite`, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
}, },
}); });
}); });
@ -52,7 +52,7 @@ describe(meta.route, () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
}, },
}, },
); );
@ -70,7 +70,7 @@ describe(meta.route, () => {
`/api/v1/statuses/${timeline[1].id}`, `/api/v1/statuses/${timeline[1].id}`,
{ {
headers: { headers: {
Authorization: `Bearer ${tokens[1].accessToken}`, Authorization: `Bearer ${tokens[1].data.accessToken}`,
}, },
}, },
); );

View file

@ -39,7 +39,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, { const response = await fakeRequest(meta.route, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
body: new URLSearchParams(), body: new URLSearchParams(),
}); });
@ -51,7 +51,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, { const response = await fakeRequest(meta.route, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
body: new URLSearchParams({ body: new URLSearchParams({
status: "a".repeat(config.validation.max_note_size + 1), status: "a".repeat(config.validation.max_note_size + 1),
@ -66,7 +66,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, { const response = await fakeRequest(meta.route, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
body: new URLSearchParams({ body: new URLSearchParams({
status: "Hello, world!", status: "Hello, world!",
@ -82,7 +82,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, { const response = await fakeRequest(meta.route, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
body: new URLSearchParams({ body: new URLSearchParams({
status: "Hello, world!", status: "Hello, world!",
@ -98,7 +98,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, { const response = await fakeRequest(meta.route, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
body: new URLSearchParams({ body: new URLSearchParams({
status: "Hello, world!", status: "Hello, world!",
@ -114,7 +114,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, { const response = await fakeRequest(meta.route, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
body: new URLSearchParams({ body: new URLSearchParams({
status: "Hello, world!", status: "Hello, world!",
@ -130,7 +130,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, { const response = await fakeRequest(meta.route, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
body: new URLSearchParams({ body: new URLSearchParams({
status: "Hello, world!", status: "Hello, world!",
@ -146,7 +146,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, { const response = await fakeRequest(meta.route, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
body: new URLSearchParams({ body: new URLSearchParams({
status: "Hello, world!", status: "Hello, world!",
@ -169,7 +169,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, { const response = await fakeRequest(meta.route, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
@ -194,7 +194,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, { const response = await fakeRequest(meta.route, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
body: new URLSearchParams({ body: new URLSearchParams({
status: "Hello, world!", status: "Hello, world!",
@ -207,7 +207,7 @@ describe(meta.route, () => {
const response2 = await fakeRequest(meta.route, { const response2 = await fakeRequest(meta.route, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
body: new URLSearchParams({ body: new URLSearchParams({
status: "Hello, world again!", status: "Hello, world again!",
@ -231,7 +231,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, { const response = await fakeRequest(meta.route, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
body: new URLSearchParams({ body: new URLSearchParams({
status: "Hello, world!", status: "Hello, world!",
@ -244,7 +244,7 @@ describe(meta.route, () => {
const response2 = await fakeRequest(meta.route, { const response2 = await fakeRequest(meta.route, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
body: new URLSearchParams({ body: new URLSearchParams({
status: "Hello, world again!", status: "Hello, world again!",
@ -268,7 +268,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, { const response = await fakeRequest(meta.route, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
body: new URLSearchParams({ body: new URLSearchParams({
status: "Hello, :test:!", status: "Hello, :test:!",
@ -295,7 +295,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, { const response = await fakeRequest(meta.route, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
body: new URLSearchParams({ body: new URLSearchParams({
status: `Hello, @${users[1].data.username}!`, status: `Hello, @${users[1].data.username}!`,
@ -322,7 +322,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, { const response = await fakeRequest(meta.route, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
body: new URLSearchParams({ body: new URLSearchParams({
status: `Hello, @${users[1].data.username}@${ status: `Hello, @${users[1].data.username}@${
@ -353,7 +353,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, { const response = await fakeRequest(meta.route, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
body: new URLSearchParams({ body: new URLSearchParams({
status: "Hi! <script>alert('Hello, world!');</script>", status: "Hi! <script>alert('Hello, world!');</script>",
@ -377,7 +377,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, { const response = await fakeRequest(meta.route, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
body: new URLSearchParams({ body: new URLSearchParams({
status: "Hello, world!", status: "Hello, world!",
@ -403,7 +403,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, { const response = await fakeRequest(meta.route, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
body: new URLSearchParams({ body: new URLSearchParams({
status: "<img src='https://example.com/image.jpg'> <video src='https://example.com/video.mp4'> Test!", status: "<img src='https://example.com/image.jpg'> <video src='https://example.com/video.mp4'> Test!",

View file

@ -21,7 +21,7 @@ describe(meta.route, () => {
test("should correctly parse limit", async () => { test("should correctly parse limit", async () => {
const response = await fakeRequest(`${meta.route}?limit=5`, { const response = await fakeRequest(`${meta.route}?limit=5`, {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}); });
@ -38,7 +38,7 @@ describe(meta.route, () => {
test("should return 200 with statuses", async () => { test("should return 200 with statuses", async () => {
const response = await fakeRequest(meta.route, { const response = await fakeRequest(meta.route, {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}); });
@ -63,7 +63,7 @@ describe(meta.route, () => {
test("should send correct Link header", async () => { test("should send correct Link header", async () => {
const response = await fakeRequest(`${meta.route}?limit=20`, { const response = await fakeRequest(`${meta.route}?limit=20`, {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}); });
@ -77,7 +77,7 @@ describe(meta.route, () => {
`${meta.route}?limit=20&max_id=${timeline[19].id}`, `${meta.route}?limit=20&max_id=${timeline[19].id}`,
{ {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );
@ -104,7 +104,7 @@ describe(meta.route, () => {
`${meta.route}?limit=20&max_id=${timeline[19].id}`, `${meta.route}?limit=20&max_id=${timeline[19].id}`,
{ {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );
@ -119,7 +119,7 @@ describe(meta.route, () => {
`${meta.route}?limit=20&min_id=${timeline[20].id}`, `${meta.route}?limit=20&min_id=${timeline[20].id}`,
{ {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );
@ -145,7 +145,7 @@ describe(meta.route, () => {
const filterResponse = await fakeRequest("/api/v2/filters", { const filterResponse = await fakeRequest("/api/v2/filters", {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/x-www-form-urlencoded", "Content-Type": "application/x-www-form-urlencoded",
}, },
body: new URLSearchParams({ body: new URLSearchParams({
@ -162,7 +162,7 @@ describe(meta.route, () => {
const response = await fakeRequest(`${meta.route}?limit=20`, { const response = await fakeRequest(`${meta.route}?limit=20`, {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}); });
@ -185,7 +185,7 @@ describe(meta.route, () => {
{ {
method: "DELETE", method: "DELETE",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );

View file

@ -15,7 +15,7 @@ describe(meta.route, () => {
test("should correctly parse limit", async () => { test("should correctly parse limit", async () => {
const response = await fakeRequest(`${meta.route}?limit=5`, { const response = await fakeRequest(`${meta.route}?limit=5`, {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}); });
@ -32,7 +32,7 @@ describe(meta.route, () => {
test("should return 200 with statuses", async () => { test("should return 200 with statuses", async () => {
const response = await fakeRequest(meta.route, { const response = await fakeRequest(meta.route, {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}); });
@ -58,7 +58,7 @@ describe(meta.route, () => {
`${meta.route}?limit=20&local=true`, `${meta.route}?limit=20&local=true`,
{ {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );
@ -85,7 +85,7 @@ describe(meta.route, () => {
`${meta.route}?remote=true&allow_local_only=false&only_media=false`, `${meta.route}?remote=true&allow_local_only=false&only_media=false`,
{ {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );
@ -104,7 +104,7 @@ describe(meta.route, () => {
test("should send correct Link header", async () => { test("should send correct Link header", async () => {
const response = await fakeRequest(`${meta.route}?limit=20`, { const response = await fakeRequest(`${meta.route}?limit=20`, {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}); });
@ -118,7 +118,7 @@ describe(meta.route, () => {
`${meta.route}?limit=20&max_id=${timeline[19].id}`, `${meta.route}?limit=20&max_id=${timeline[19].id}`,
{ {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );
@ -145,7 +145,7 @@ describe(meta.route, () => {
`${meta.route}?limit=20&max_id=${timeline[19].id}`, `${meta.route}?limit=20&max_id=${timeline[19].id}`,
{ {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );
@ -160,7 +160,7 @@ describe(meta.route, () => {
`${meta.route}?limit=20&min_id=${timeline[20].id}`, `${meta.route}?limit=20&min_id=${timeline[20].id}`,
{ {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );
@ -187,7 +187,7 @@ describe(meta.route, () => {
const filterResponse = await fakeRequest("/api/v2/filters", { const filterResponse = await fakeRequest("/api/v2/filters", {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/x-www-form-urlencoded", "Content-Type": "application/x-www-form-urlencoded",
}, },
body: new URLSearchParams({ body: new URLSearchParams({
@ -206,7 +206,7 @@ describe(meta.route, () => {
const response = await fakeRequest(`${meta.route}?limit=20`, { const response = await fakeRequest(`${meta.route}?limit=20`, {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}); });
@ -229,7 +229,7 @@ describe(meta.route, () => {
{ {
method: "DELETE", method: "DELETE",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );

View file

@ -7,7 +7,7 @@ const { tokens, deleteUsers } = await getTestUsers(2);
const response = await fakeRequest("/api/v2/filters", { const response = await fakeRequest("/api/v2/filters", {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/x-www-form-urlencoded", "Content-Type": "application/x-www-form-urlencoded",
}, },
body: new URLSearchParams({ body: new URLSearchParams({
@ -49,7 +49,7 @@ describe(meta.route, () => {
meta.route.replace(":id", filter.id), meta.route.replace(":id", filter.id),
{ {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );
@ -75,7 +75,7 @@ describe(meta.route, () => {
{ {
method: "PUT", method: "PUT",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/x-www-form-urlencoded", "Content-Type": "application/x-www-form-urlencoded",
}, },
body: new URLSearchParams({ body: new URLSearchParams({
@ -111,7 +111,7 @@ describe(meta.route, () => {
{ {
method: "PUT", method: "PUT",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
body: new URLSearchParams({ body: new URLSearchParams({
"keywords_attributes[0][id]": filter.keywords[0].id, "keywords_attributes[0][id]": filter.keywords[0].id,
@ -131,7 +131,7 @@ describe(meta.route, () => {
meta.route.replace(":id", filter.id), meta.route.replace(":id", filter.id),
{ {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );
@ -148,7 +148,7 @@ describe(meta.route, () => {
{ {
method: "DELETE", method: "DELETE",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
body: formData, body: formData,
}, },
@ -161,7 +161,7 @@ describe(meta.route, () => {
meta.route.replace(":id", filter.id), meta.route.replace(":id", filter.id),
{ {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
); );

View file

@ -19,7 +19,7 @@ describe(meta.route, () => {
test("should return user filters (none)", async () => { test("should return user filters (none)", async () => {
const response = await fakeRequest(meta.route, { const response = await fakeRequest(meta.route, {
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}); });
@ -43,7 +43,7 @@ describe(meta.route, () => {
const response = await fakeRequest(meta.route, { const response = await fakeRequest(meta.route, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/x-www-form-urlencoded", "Content-Type": "application/x-www-form-urlencoded",
}, },
body: new URLSearchParams({ body: new URLSearchParams({

View file

@ -1,5 +1,5 @@
import type { Application as APIApplication } from "@versia/client/types"; import type { Application as APIApplication } from "@versia/client/types";
import { db } from "@versia/kit/db"; import { Token, db } from "@versia/kit/db";
import { Applications } from "@versia/kit/tables"; import { Applications } from "@versia/kit/tables";
import { import {
type InferInsertModel, type InferInsertModel,
@ -81,15 +81,11 @@ export class Application extends BaseInterface<typeof Applications> {
public static async getFromToken( public static async getFromToken(
token: string, token: string,
): Promise<Application | null> { ): Promise<Application | null> {
const result = await db.query.Tokens.findFirst({ const result = await Token.fromAccessToken(token);
where: (tokens, { eq }): SQL | undefined =>
eq(tokens.accessToken, token),
with: {
application: true,
},
});
return result?.application ? new Application(result.application) : null; return result?.data.application
? new Application(result.data.application)
: null;
} }
public static fromClientId(clientId: string): Promise<Application | null> { public static fromClientId(clientId: string): Promise<Application | null> {

171
classes/database/token.ts Normal file
View file

@ -0,0 +1,171 @@
import type { Token as ApiToken } from "@versia/client/types";
import { User, db } from "@versia/kit/db";
import { type Applications, Tokens } from "@versia/kit/tables";
import {
type InferInsertModel,
type InferSelectModel,
type SQL,
desc,
eq,
inArray,
} from "drizzle-orm";
import { z } from "zod";
import { BaseInterface } from "./base.ts";
export type TokenType = InferSelectModel<typeof Tokens> & {
application: InferSelectModel<typeof Applications> | null;
};
export class Token extends BaseInterface<typeof Tokens, TokenType> {
public static schema: z.ZodType<ApiToken> = z.object({
access_token: z.string(),
token_type: z.enum(["bearer"]),
scope: z.string(),
created_at: z.number(),
});
public async reload(): Promise<void> {
const reloaded = await Token.fromId(this.data.id);
if (!reloaded) {
throw new Error("Failed to reload token");
}
this.data = reloaded.data;
}
public static async fromId(id: string | null): Promise<Token | null> {
if (!id) {
return null;
}
return await Token.fromSql(eq(Tokens.id, id));
}
public static async fromIds(ids: string[]): Promise<Token[]> {
return await Token.manyFromSql(inArray(Tokens.id, ids));
}
public static async fromSql(
sql: SQL<unknown> | undefined,
orderBy: SQL<unknown> | undefined = desc(Tokens.id),
): Promise<Token | null> {
const found = await db.query.Tokens.findFirst({
where: sql,
orderBy,
with: {
application: true,
},
});
if (!found) {
return null;
}
return new Token(found);
}
public static async manyFromSql(
sql: SQL<unknown> | undefined,
orderBy: SQL<unknown> | undefined = desc(Tokens.id),
limit?: number,
offset?: number,
extra?: Parameters<typeof db.query.Tokens.findMany>[0],
): Promise<Token[]> {
const found = await db.query.Tokens.findMany({
where: sql,
orderBy,
limit,
offset,
with: {
application: true,
...extra?.with,
},
});
return found.map((s) => new Token(s));
}
public async update(newAttachment: Partial<TokenType>): Promise<TokenType> {
await db
.update(Tokens)
.set(newAttachment)
.where(eq(Tokens.id, this.id));
const updated = await Token.fromId(this.data.id);
if (!updated) {
throw new Error("Failed to update token");
}
this.data = updated.data;
return updated.data;
}
public save(): Promise<TokenType> {
return this.update(this.data);
}
public async delete(ids?: string[]): Promise<void> {
if (Array.isArray(ids)) {
await db.delete(Tokens).where(inArray(Tokens.id, ids));
} else {
await db.delete(Tokens).where(eq(Tokens.id, this.id));
}
}
public static async insert(
data: InferInsertModel<typeof Tokens>,
): Promise<Token> {
const inserted = (await db.insert(Tokens).values(data).returning())[0];
const token = await Token.fromId(inserted.id);
if (!token) {
throw new Error("Failed to insert token");
}
return token;
}
public static async insertMany(
data: InferInsertModel<typeof Tokens>[],
): Promise<Token[]> {
const inserted = await db.insert(Tokens).values(data).returning();
return await Token.fromIds(inserted.map((i) => i.id));
}
public get id(): string {
return this.data.id;
}
public static async fromAccessToken(
accessToken: string,
): Promise<Token | null> {
return await Token.fromSql(eq(Tokens.accessToken, accessToken));
}
/**
* Retrieves the associated user from this token
*
* @returns The user associated with this token
*/
public async getUser(): Promise<User | null> {
if (!this.data.userId) {
return null;
}
return await User.fromId(this.data.userId);
}
public toApi(): ApiToken {
return {
access_token: this.data.accessToken,
token_type: "Bearer",
scope: this.data.scope,
created_at: Math.floor(
new Date(this.data.createdAt).getTime() / 1000,
),
};
}
}

View file

@ -1,11 +0,0 @@
import type { Tokens } from "@versia/kit/tables";
import type { InferSelectModel } from "drizzle-orm";
/**
* The type of token.
*/
export enum TokenType {
Bearer = "Bearer",
}
export type Token = InferSelectModel<typeof Tokens>;

View file

@ -3,18 +3,10 @@ import type {
FollowAccept, FollowAccept,
FollowReject, FollowReject,
} from "@versia/federation/types"; } from "@versia/federation/types";
import { User, db } from "@versia/kit/db"; import { type Application, type Token, type User, db } from "@versia/kit/db";
import { import type { Instances, Roles, Users } from "@versia/kit/tables";
Applications, import { type InferSelectModel, type SQL, sql } from "drizzle-orm";
type Instances,
type Roles,
Tokens,
type Users,
} from "@versia/kit/tables";
import { type InferSelectModel, type SQL, eq, sql } from "drizzle-orm";
import type { ApplicationType } from "~/classes/database/application.ts";
import type { EmojiWithInstance } from "~/classes/database/emoji.ts"; import type { EmojiWithInstance } from "~/classes/database/emoji.ts";
import type { Token } from "./token.ts";
export type UserType = InferSelectModel<typeof Users>; export type UserType = InferSelectModel<typeof Users>;
@ -31,23 +23,7 @@ export type UserWithRelations = UserType & {
roles: InferSelectModel<typeof Roles>[]; roles: InferSelectModel<typeof Roles>[];
}; };
export const userRelations: { export const userRelations = {
instance: true;
emojis: {
with: {
emoji: {
with: {
instance: true;
};
};
};
};
roles: {
with: {
role: true;
};
};
} = {
instance: true, instance: true,
emojis: { emojis: {
with: { with: {
@ -63,7 +39,7 @@ export const userRelations: {
role: true, role: true,
}, },
}, },
}; } as const;
export const userExtras = { export const userExtras = {
followerCount: followerCount:
@ -103,19 +79,10 @@ export const userExtrasTemplate = (
export interface AuthData { export interface AuthData {
user: User | null; user: User | null;
token: string; token: Token | null;
application: ApplicationType | null; application: Application | null;
} }
export const getFromHeader = async (value: string): Promise<AuthData> => {
const token = value.split(" ")[1];
const { user, application } =
await retrieveUserAndApplicationFromToken(token);
return { user, token, application };
};
export const transformOutputToUserWithRelations = ( export const transformOutputToUserWithRelations = (
user: Omit<UserType, "endpoints"> & { user: Omit<UserType, "endpoints"> & {
followerCount: unknown; followerCount: unknown;
@ -180,52 +147,6 @@ export const findManyUsers = async (
return output.map((user) => transformOutputToUserWithRelations(user)); return output.map((user) => transformOutputToUserWithRelations(user));
}; };
export const retrieveUserAndApplicationFromToken = async (
accessToken: string,
): Promise<{
user: User | null;
application: ApplicationType | null;
}> => {
if (!accessToken) {
return { user: null, application: null };
}
const output = (
await db
.select({
token: Tokens,
application: Applications,
})
.from(Tokens)
.leftJoin(Applications, eq(Tokens.applicationId, Applications.id))
.where(eq(Tokens.accessToken, accessToken))
.limit(1)
)[0];
if (!output?.token.userId) {
return { user: null, application: null };
}
const user = await User.fromId(output.token.userId);
return { user, application: output.application ?? null };
};
export const retrieveToken = async (
accessToken: string,
): Promise<Token | null> => {
if (!accessToken) {
return null;
}
return (
(await db.query.Tokens.findFirst({
where: (tokens, { eq }): SQL | undefined =>
eq(tokens.accessToken, accessToken),
})) ?? null
);
};
export const followRequestToVersia = ( export const followRequestToVersia = (
follower: User, follower: User,
followee: User, followee: User,

View file

@ -1,12 +1,13 @@
// biome-ignore lint/performance/noBarrelFile: <explanation> // biome-ignore lint/performance/noBarrelFile: <explanation>
export { User } from "~/classes/database/user"; export { User } from "~/classes/database/user.ts";
export { Role } from "~/classes/database/role"; export { Role } from "~/classes/database/role.ts";
export { Attachment } from "~/classes/database/attachment"; export { Attachment } from "~/classes/database/attachment.ts";
export { Emoji } from "~/classes/database/emoji"; export { Emoji } from "~/classes/database/emoji.ts";
export { Instance } from "~/classes/database/instance"; export { Instance } from "~/classes/database/instance.ts";
export { Note } from "~/classes/database/note"; export { Note } from "~/classes/database/note.ts";
export { Timeline } from "~/classes/database/timeline"; export { Timeline } from "~/classes/database/timeline.ts";
export { Application } from "~/classes/database/application"; export { Application } from "~/classes/database/application.ts";
export { db } from "~/drizzle/db"; export { db } from "~/drizzle/db.ts";
export { Relationship } from "~/classes/database/relationship"; export { Relationship } from "~/classes/database/relationship.ts";
export { Like } from "~/classes/database/like"; export { Like } from "~/classes/database/like.ts";
export { Token } from "~/classes/database/token.ts";

View file

@ -47,7 +47,7 @@ describe("/oauth/authorize", () => {
const response = await fakeRequest("/oauth/authorize", { const response = await fakeRequest("/oauth/authorize", {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0]?.accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
Cookie: `jwt=${jwt}`, Cookie: `jwt=${jwt}`,
}, },
@ -79,7 +79,7 @@ describe("/oauth/authorize", () => {
const response = await fakeRequest("/oauth/authorize", { const response = await fakeRequest("/oauth/authorize", {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0]?.accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
Cookie: "jwt=invalid-jwt", Cookie: "jwt=invalid-jwt",
}, },
@ -118,7 +118,7 @@ describe("/oauth/authorize", () => {
const response = await fakeRequest("/oauth/authorize", { const response = await fakeRequest("/oauth/authorize", {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0]?.accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
Cookie: `jwt=${jwt}`, Cookie: `jwt=${jwt}`,
}, },
@ -160,7 +160,7 @@ describe("/oauth/authorize", () => {
const response = await fakeRequest("/oauth/authorize", { const response = await fakeRequest("/oauth/authorize", {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0]?.accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
Cookie: `jwt=${jwt}`, Cookie: `jwt=${jwt}`,
}, },
@ -200,7 +200,7 @@ describe("/oauth/authorize", () => {
const response2 = await fakeRequest("/oauth/authorize", { const response2 = await fakeRequest("/oauth/authorize", {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0]?.accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
Cookie: `jwt=${jwt2}`, Cookie: `jwt=${jwt2}`,
}, },
@ -245,7 +245,7 @@ describe("/oauth/authorize", () => {
const response = await fakeRequest("/oauth/authorize", { const response = await fakeRequest("/oauth/authorize", {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0]?.accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
Cookie: `jwt=${jwt}`, Cookie: `jwt=${jwt}`,
}, },
@ -289,7 +289,7 @@ describe("/oauth/authorize", () => {
const response = await fakeRequest("/oauth/authorize", { const response = await fakeRequest("/oauth/authorize", {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0]?.accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
Cookie: `jwt=${jwt}`, Cookie: `jwt=${jwt}`,
}, },
@ -331,7 +331,7 @@ describe("/oauth/authorize", () => {
const response = await fakeRequest("/oauth/authorize", { const response = await fakeRequest("/oauth/authorize", {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0]?.accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
Cookie: `jwt=${jwt}`, Cookie: `jwt=${jwt}`,
}, },
@ -373,7 +373,7 @@ describe("/oauth/authorize", () => {
const response = await fakeRequest("/oauth/authorize", { const response = await fakeRequest("/oauth/authorize", {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0]?.accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
Cookie: `jwt=${jwt}`, Cookie: `jwt=${jwt}`,
}, },

View file

@ -1,11 +1,10 @@
import { auth, jsonOrForm } from "@/api"; import { auth, jsonOrForm } from "@/api";
import { randomString } from "@/math"; import { randomString } from "@/math";
import { Application, User, db } from "@versia/kit/db"; import { Application, Token, User } from "@versia/kit/db";
import { RolePermissions, Tokens } from "@versia/kit/tables"; import { RolePermissions } from "@versia/kit/tables";
import { type JWTPayload, SignJWT, jwtVerify } from "jose"; import { type JWTPayload, SignJWT, jwtVerify } from "jose";
import { JOSEError } from "jose/errors"; import { JOSEError } from "jose/errors";
import { z } from "zod"; import { z } from "zod";
import { TokenType } from "~/classes/functions/token";
import type { PluginType } from "../index.ts"; import type { PluginType } from "../index.ts";
const schemas = { const schemas = {
@ -282,11 +281,11 @@ export default (plugin: PluginType): void =>
.setProtectedHeader({ alg: "EdDSA" }) .setProtectedHeader({ alg: "EdDSA" })
.sign(keys.private); .sign(keys.private);
await db.insert(Tokens).values({ await Token.insert({
accessToken: randomString(64, "base64url"), accessToken: randomString(64, "base64url"),
code, code,
scope: scope ?? application.data.scopes, scope: scope ?? application.data.scopes,
tokenType: TokenType.Bearer, tokenType: "Bearer",
applicationId: application.id, applicationId: application.id,
redirectUri: redirect_uri ?? application.data.redirectUri, redirectUri: redirect_uri ?? application.data.redirectUri,
expiresAt: new Date( expiresAt: new Date(

View file

@ -1,16 +1,10 @@
import { randomString } from "@/math.ts"; import { randomString } from "@/math.ts";
import { setCookie } from "@hono/hono/cookie"; import { setCookie } from "@hono/hono/cookie";
import { createRoute, z } from "@hono/zod-openapi"; import { createRoute, z } from "@hono/zod-openapi";
import { User, db } from "@versia/kit/db"; import { Token, User, db } from "@versia/kit/db";
import { type SQL, and, eq, isNull } from "@versia/kit/drizzle"; import { type SQL, and, eq, isNull } from "@versia/kit/drizzle";
import { import { OpenIdAccounts, RolePermissions, Users } from "@versia/kit/tables";
OpenIdAccounts,
RolePermissions,
Tokens,
Users,
} from "@versia/kit/tables";
import { SignJWT } from "jose"; import { SignJWT } from "jose";
import { TokenType } from "~/classes/functions/token.ts";
import type { PluginType } from "../../index.ts"; import type { PluginType } from "../../index.ts";
import { automaticOidcFlow } from "../../utils.ts"; import { automaticOidcFlow } from "../../utils.ts";
@ -314,11 +308,11 @@ export default (plugin: PluginType): void => {
const code = randomString(32, "hex"); const code = randomString(32, "hex");
await db.insert(Tokens).values({ await Token.insert({
accessToken: randomString(64, "base64url"), accessToken: randomString(64, "base64url"),
code, code,
scope: flow.application.scopes, scope: flow.application.scopes,
tokenType: TokenType.Bearer, tokenType: "Bearer",
userId: user.id, userId: user.id,
applicationId: flow.application.id, applicationId: flow.application.id,
}); });

View file

@ -1,7 +1,5 @@
import { afterAll, beforeAll, describe, expect, test } from "bun:test"; import { afterAll, describe, expect, test } from "bun:test";
import { Application, db } from "@versia/kit/db"; import { Application, Token } from "@versia/kit/db";
import { eq } from "@versia/kit/drizzle";
import { Tokens } from "@versia/kit/tables";
import { fakeRequest, getTestUsers } from "~/tests/utils"; import { fakeRequest, getTestUsers } from "~/tests/utils";
const { deleteUsers, users } = await getTestUsers(1); const { deleteUsers, users } = await getTestUsers(1);
@ -13,9 +11,7 @@ const application = await Application.insert({
secret: "test-secret", secret: "test-secret",
name: "Test Application", name: "Test Application",
}); });
const token = await Token.insert({
beforeAll(async () => {
await db.insert(Tokens).values({
code: "test-code", code: "test-code",
redirectUri: application.data.redirectUri, redirectUri: application.data.redirectUri,
clientId: application.data.clientId, clientId: application.data.clientId,
@ -26,15 +22,12 @@ beforeAll(async () => {
scope: application.data.scopes, scope: application.data.scopes,
userId: users[0].id, userId: users[0].id,
applicationId: application.id, applicationId: application.id,
});
}); });
afterAll(async () => { afterAll(async () => {
await deleteUsers(); await deleteUsers();
await application.delete(); await application.delete();
await db await token.delete();
.delete(Tokens)
.where(eq(Tokens.clientId, application.data.clientId));
}); });
describe("/oauth/revoke", () => { describe("/oauth/revoke", () => {

View file

@ -1,7 +1,7 @@
import { jsonOrForm } from "@/api"; import { jsonOrForm } from "@/api";
import { createRoute, z } from "@hono/zod-openapi"; import { createRoute, z } from "@hono/zod-openapi";
import { db } from "@versia/kit/db"; import { Token, db } from "@versia/kit/db";
import { type SQL, eq } from "@versia/kit/drizzle"; import { and, eq } from "@versia/kit/drizzle";
import { Tokens } from "@versia/kit/tables"; import { Tokens } from "@versia/kit/tables";
import type { PluginType } from "../../index.ts"; import type { PluginType } from "../../index.ts";
@ -62,16 +62,12 @@ export default (plugin: PluginType): void => {
const { client_id, client_secret, token } = const { client_id, client_secret, token } =
context.req.valid("json"); context.req.valid("json");
const foundToken = await db.query.Tokens.findFirst({ const foundToken = await Token.fromSql(
where: (tokenTable, { eq, and }): SQL | undefined =>
and( and(
eq(tokenTable.accessToken, token ?? ""), eq(Tokens.accessToken, token ?? ""),
eq(tokenTable.clientId, client_id), eq(Tokens.clientId, client_id),
), ),
with: { );
application: true,
},
});
if (!(foundToken && token)) { if (!(foundToken && token)) {
return context.json( return context.json(
@ -85,7 +81,7 @@ export default (plugin: PluginType): void => {
} }
// Check if the client secret is correct // Check if the client secret is correct
if (foundToken.application?.secret !== client_secret) { if (foundToken.data.application?.secret !== client_secret) {
return context.json( return context.json(
{ {
error: "unauthorized_client", error: "unauthorized_client",

View file

@ -1,7 +1,5 @@
import { afterAll, beforeAll, describe, expect, test } from "bun:test"; import { afterAll, describe, expect, test } from "bun:test";
import { Application, db } from "@versia/kit/db"; import { Application, Token } from "@versia/kit/db";
import { eq } from "@versia/kit/drizzle";
import { Tokens } from "@versia/kit/tables";
import { fakeRequest, getTestUsers } from "~/tests/utils"; import { fakeRequest, getTestUsers } from "~/tests/utils";
const { deleteUsers, users } = await getTestUsers(1); const { deleteUsers, users } = await getTestUsers(1);
@ -13,9 +11,7 @@ const application = await Application.insert({
secret: "test-secret", secret: "test-secret",
name: "Test Application", name: "Test Application",
}); });
const token = await Token.insert({
beforeAll(async () => {
await db.insert(Tokens).values({
code: "test-code", code: "test-code",
redirectUri: application.data.redirectUri, redirectUri: application.data.redirectUri,
clientId: application.data.clientId, clientId: application.data.clientId,
@ -25,15 +21,12 @@ beforeAll(async () => {
tokenType: "Bearer", tokenType: "Bearer",
scope: application.data.scopes, scope: application.data.scopes,
userId: users[0].id, userId: users[0].id,
});
}); });
afterAll(async () => { afterAll(async () => {
await deleteUsers(); await deleteUsers();
await application.delete(); await application.delete();
await db await token.delete();
.delete(Tokens)
.where(eq(Tokens.clientId, application.data.clientId));
}); });
describe("/oauth/token", () => { describe("/oauth/token", () => {

View file

@ -1,7 +1,7 @@
import { jsonOrForm } from "@/api"; import { jsonOrForm } from "@/api";
import { createRoute, z } from "@hono/zod-openapi"; import { createRoute, z } from "@hono/zod-openapi";
import { Application, db } from "@versia/kit/db"; import { Application, Token } from "@versia/kit/db";
import { type SQL, eq } from "@versia/kit/drizzle"; import { and, eq } from "@versia/kit/drizzle";
import { Tokens } from "@versia/kit/tables"; import { Tokens } from "@versia/kit/tables";
import type { PluginType } from "../../index.ts"; import type { PluginType } from "../../index.ts";
@ -154,17 +154,13 @@ export default (plugin: PluginType): void => {
); );
} }
const token = await db.query.Tokens.findFirst({ const token = await Token.fromSql(
where: (token, { eq, and }): SQL | undefined =>
and( and(
eq(token.code, code), eq(Tokens.code, code),
eq( eq(Tokens.redirectUri, decodeURI(redirect_uri)),
token.redirectUri, eq(Tokens.clientId, client_id),
decodeURI(redirect_uri),
), ),
eq(token.clientId, client_id), );
),
});
if (!token) { if (!token) {
return context.json( return context.json(
@ -177,28 +173,22 @@ export default (plugin: PluginType): void => {
} }
// Invalidate the code // Invalidate the code
await db await token.update({ code: null });
.update(Tokens)
.set({ code: null })
.where(eq(Tokens.id, token.id));
return context.json( return context.json(
{ {
access_token: token.accessToken, ...token.toApi(),
token_type: "Bearer", expires_in: token.data.expiresAt
expires_in: token.expiresAt
? Math.floor( ? Math.floor(
(new Date(token.expiresAt).getTime() - (new Date(
token.data.expiresAt,
).getTime() -
Date.now()) / Date.now()) /
1000, 1000,
) )
: null, : null,
id_token: token.idToken, id_token: token.data.idToken,
refresh_token: null, refresh_token: null,
scope: token.scope,
created_at: Math.floor(
new Date(token.createdAt).getTime() / 1000,
),
}, },
200, 200,
); );

View file

@ -13,7 +13,7 @@ describe("/api/v1/sso/:id", () => {
const response = await fakeRequest("/api/v1/sso/unknown", { const response = await fakeRequest("/api/v1/sso/unknown", {
method: "GET", method: "GET",
headers: { headers: {
Authorization: `Bearer ${tokens[0]?.accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}); });
@ -25,7 +25,7 @@ describe("/api/v1/sso/:id", () => {
const response2 = await fakeRequest("/api/v1/sso/unknown", { const response2 = await fakeRequest("/api/v1/sso/unknown", {
method: "DELETE", method: "DELETE",
headers: { headers: {
Authorization: `Bearer ${tokens[0]?.accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
}); });

View file

@ -12,7 +12,7 @@ describe("/api/v1/sso", () => {
const response = await fakeRequest("/api/v1/sso", { const response = await fakeRequest("/api/v1/sso", {
method: "GET", method: "GET",
headers: { headers: {
Authorization: `Bearer ${tokens[0]?.accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}); });
@ -24,7 +24,7 @@ describe("/api/v1/sso", () => {
const response = await fakeRequest("/api/v1/sso", { const response = await fakeRequest("/api/v1/sso", {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0]?.accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({

View file

@ -15,7 +15,7 @@ describe("API Tests", () => {
const response = await fakeRequest("/api/v1/statuses", { const response = await fakeRequest("/api/v1/statuses", {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
"Content-Type": "multipart/form-data", "Content-Type": "multipart/form-data",
}, },
body: formData, body: formData,
@ -42,7 +42,7 @@ describe("API Tests", () => {
{ {
method: "GET", method: "GET",
headers: { headers: {
Authorization: `Bearer ${tokens[0].accessToken}`, Authorization: `Bearer ${tokens[0].data.accessToken}`,
}, },
}, },
), ),

View file

@ -34,7 +34,7 @@ describe("API Tests", () => {
{ {
method: "PATCH", method: "PATCH",
headers: { headers: {
Authorization: `Bearer ${token.accessToken}`, Authorization: `Bearer ${token.data.accessToken}`,
}, },
body: getFormData({ body: getFormData({
display_name: "New Display Name", display_name: "New Display Name",
@ -59,7 +59,7 @@ describe("API Tests", () => {
"/api/v1/accounts/verify_credentials", "/api/v1/accounts/verify_credentials",
{ {
headers: { headers: {
Authorization: `Bearer ${token.accessToken}`, Authorization: `Bearer ${token.data.accessToken}`,
}, },
}, },
); );
@ -106,7 +106,7 @@ describe("API Tests", () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${token.accessToken}`, Authorization: `Bearer ${token.data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({}), body: JSON.stringify({}),
@ -132,7 +132,7 @@ describe("API Tests", () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${token.accessToken}`, Authorization: `Bearer ${token.data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({}), body: JSON.stringify({}),
@ -156,7 +156,7 @@ describe("API Tests", () => {
const response = await fakeRequest("/api/v1/blocks", { const response = await fakeRequest("/api/v1/blocks", {
method: "GET", method: "GET",
headers: { headers: {
Authorization: `Bearer ${token.accessToken}`, Authorization: `Bearer ${token.data.accessToken}`,
}, },
}); });
@ -179,7 +179,7 @@ describe("API Tests", () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${token.accessToken}`, Authorization: `Bearer ${token.data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({}), body: JSON.stringify({}),
@ -205,7 +205,7 @@ describe("API Tests", () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${token.accessToken}`, Authorization: `Bearer ${token.data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({}), body: JSON.stringify({}),
@ -231,7 +231,7 @@ describe("API Tests", () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${token.accessToken}`, Authorization: `Bearer ${token.data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({}), body: JSON.stringify({}),
@ -257,7 +257,7 @@ describe("API Tests", () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${token.accessToken}`, Authorization: `Bearer ${token.data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({ comment: "This is a new note" }), body: JSON.stringify({ comment: "This is a new note" }),
@ -283,7 +283,7 @@ describe("API Tests", () => {
{ {
method: "GET", method: "GET",
headers: { headers: {
Authorization: `Bearer ${token.accessToken}`, Authorization: `Bearer ${token.data.accessToken}`,
}, },
}, },
); );
@ -314,7 +314,7 @@ describe("API Tests", () => {
const response = await fakeRequest("/api/v1/profile/avatar", { const response = await fakeRequest("/api/v1/profile/avatar", {
method: "DELETE", method: "DELETE",
headers: { headers: {
Authorization: `Bearer ${token.accessToken}`, Authorization: `Bearer ${token.data.accessToken}`,
}, },
}); });
@ -335,7 +335,7 @@ describe("API Tests", () => {
const response = await fakeRequest("/api/v1/profile/header", { const response = await fakeRequest("/api/v1/profile/header", {
method: "DELETE", method: "DELETE",
headers: { headers: {
Authorization: `Bearer ${token.accessToken}`, Authorization: `Bearer ${token.data.accessToken}`,
}, },
}); });
@ -358,7 +358,7 @@ describe("API Tests", () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${token.accessToken}`, Authorization: `Bearer ${token.data.accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({}), body: JSON.stringify({}),
@ -371,13 +371,13 @@ describe("API Tests", () => {
); );
}); });
test("should return an array of objects with id and accounts properties, where id is a string and accounts is an array of APIAccount objects", async () => { test("should return no familiar followers", async () => {
const response = await fakeRequest( const response = await fakeRequest(
`/api/v1/accounts/familiar_followers?id[]=${user2.id}`, `/api/v1/accounts/familiar_followers?id[]=${user2.id}`,
{ {
method: "GET", method: "GET",
headers: { headers: {
Authorization: `Bearer ${token.accessToken}`, Authorization: `Bearer ${token.data.accessToken}`,
}, },
}, },
); );
@ -393,7 +393,9 @@ describe("API Tests", () => {
}[]; }[];
expect(Array.isArray(familiarFollowers)).toBe(true); expect(Array.isArray(familiarFollowers)).toBe(true);
expect(familiarFollowers.length).toBe(0); expect(familiarFollowers.length).toBe(1);
expect(familiarFollowers[0].id).toBe(user2.id);
expect(familiarFollowers[0].accounts).toBeArrayOfSize(0);
}); });
}); });
}); });

View file

@ -29,7 +29,7 @@ describe("API Tests", () => {
const response = await fakeRequest("/api/v2/media", { const response = await fakeRequest("/api/v2/media", {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${token.accessToken}`, Authorization: `Bearer ${token.data.accessToken}`,
}, },
body: formData, body: formData,
}); });
@ -52,7 +52,7 @@ describe("API Tests", () => {
const response = await fakeRequest("/api/v1/statuses", { const response = await fakeRequest("/api/v1/statuses", {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${token.accessToken}`, Authorization: `Bearer ${token.data.accessToken}`,
}, },
body: new URLSearchParams({ body: new URLSearchParams({
status: "Hello, world!", status: "Hello, world!",
@ -94,7 +94,7 @@ describe("API Tests", () => {
const response = await fakeRequest("/api/v1/statuses", { const response = await fakeRequest("/api/v1/statuses", {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${token.accessToken}`, Authorization: `Bearer ${token.data.accessToken}`,
}, },
body: new URLSearchParams({ body: new URLSearchParams({
status: "This is a reply!", status: "This is a reply!",
@ -139,7 +139,7 @@ describe("API Tests", () => {
`/api/v1/statuses/${status?.id}`, `/api/v1/statuses/${status?.id}`,
{ {
headers: { headers: {
Authorization: `Bearer ${token.accessToken}`, Authorization: `Bearer ${token.data.accessToken}`,
}, },
}, },
); );
@ -184,7 +184,7 @@ describe("API Tests", () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${token.accessToken}`, Authorization: `Bearer ${token.data.accessToken}`,
}, },
}, },
); );
@ -209,7 +209,7 @@ describe("API Tests", () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${token.accessToken}`, Authorization: `Bearer ${token.data.accessToken}`,
}, },
}, },
); );
@ -232,7 +232,7 @@ describe("API Tests", () => {
`/api/v1/statuses/${status?.id}/context`, `/api/v1/statuses/${status?.id}/context`,
{ {
headers: { headers: {
Authorization: `Bearer ${token.accessToken}`, Authorization: `Bearer ${token.data.accessToken}`,
}, },
}, },
); );
@ -259,7 +259,7 @@ describe("API Tests", () => {
{ {
method: "GET", method: "GET",
headers: { headers: {
Authorization: `Bearer ${token.accessToken}`, Authorization: `Bearer ${token.data.accessToken}`,
}, },
}, },
); );
@ -289,7 +289,7 @@ describe("API Tests", () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${token.accessToken}`, Authorization: `Bearer ${token.data.accessToken}`,
}, },
}, },
); );
@ -306,7 +306,7 @@ describe("API Tests", () => {
{ {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${token.accessToken}`, Authorization: `Bearer ${token.data.accessToken}`,
}, },
}, },
); );
@ -330,7 +330,7 @@ describe("API Tests", () => {
{ {
method: "DELETE", method: "DELETE",
headers: { headers: {
Authorization: `Bearer ${token.accessToken}`, Authorization: `Bearer ${token.data.accessToken}`,
}, },
}, },
); );

View file

@ -1,12 +1,11 @@
import { generateChallenge } from "@/challenges"; import { generateChallenge } from "@/challenges";
import { randomString } from "@/math"; import { randomString } from "@/math";
import { Note, User, db } from "@versia/kit/db"; import { Note, Token, User, db } from "@versia/kit/db";
import { Notes, Tokens, Users } from "@versia/kit/tables"; import { Notes, Users } from "@versia/kit/tables";
import { solveChallenge } from "altcha-lib"; import { solveChallenge } from "altcha-lib";
import { asc, inArray, like } from "drizzle-orm"; import { asc, inArray, like } from "drizzle-orm";
import { appFactory } from "~/app"; import { appFactory } from "~/app";
import type { Status } from "~/classes/functions/status"; import type { Status } from "~/classes/functions/status";
import type { Token } from "~/classes/functions/token";
import { searchManager } from "~/classes/search/search-manager"; import { searchManager } from "~/classes/search/search-manager";
import { setupDatabase } from "~/drizzle/db"; import { setupDatabase } from "~/drizzle/db";
import { config } from "~/packages/config-manager"; import { config } from "~/packages/config-manager";
@ -60,9 +59,7 @@ export const getTestUsers = async (
users.push(user); users.push(user);
} }
const tokens = await db const tokens = await Token.insertMany(
.insert(Tokens)
.values(
users.map((u) => ({ users.map((u) => ({
accessToken: randomString(32, "hex"), accessToken: randomString(32, "hex"),
tokenType: "bearer", tokenType: "bearer",
@ -71,12 +68,15 @@ export const getTestUsers = async (
code: randomString(32, "hex"), code: randomString(32, "hex"),
scope: "read write follow push", scope: "read write follow push",
})), })),
) );
.returning();
return { return {
users, users,
tokens, // Order tokens in the same order as users
// The first token belongs to the first user, the second token belongs to the second user, etc.
tokens: users.map(
(u) => tokens.find((t) => t.data.userId === u.id) as Token,
),
passwords, passwords,
deleteUsers: async (): Promise<void> => { deleteUsers: async (): Promise<void> => {
await db.delete(Users).where( await db.delete(Users).where(

View file

@ -11,10 +11,10 @@ import type {
Unfollow, Unfollow,
User, User,
} from "@versia/federation/types"; } from "@versia/federation/types";
import type { Application, User as DatabaseUser } from "@versia/kit/db";
import type { RolePermissions } from "@versia/kit/tables"; import type { RolePermissions } from "@versia/kit/tables";
import type { SocketAddress } from "bun"; import type { SocketAddress } from "bun";
import { z } from "zod"; import { z } from "zod";
import type { AuthData } from "~/classes/functions/user";
import type { Config } from "~/packages/config-manager"; import type { Config } from "~/packages/config-manager";
export type HttpVerb = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "OPTIONS"; export type HttpVerb = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "OPTIONS";
@ -52,11 +52,7 @@ export const ErrorSchema = z.object({
export type HonoEnv = { export type HonoEnv = {
Variables: { Variables: {
config: Config; config: Config;
auth: { auth: AuthData;
user: DatabaseUser | null;
token: string | null;
application: Application | null;
};
}; };
Bindings: { Bindings: {
ip?: SocketAddress | null; ip?: SocketAddress | null;

View file

@ -2,7 +2,7 @@ import type { Context, MiddlewareHandler } from "@hono/hono";
import { createMiddleware } from "@hono/hono/factory"; import { createMiddleware } from "@hono/hono/factory";
import type { OpenAPIHono } from "@hono/zod-openapi"; import type { OpenAPIHono } from "@hono/zod-openapi";
import { getLogger } from "@logtape/logtape"; import { getLogger } from "@logtape/logtape";
import { Application, type User, db } from "@versia/kit/db"; import { Application, Token, db } from "@versia/kit/db";
import { Challenges } from "@versia/kit/tables"; import { Challenges } from "@versia/kit/tables";
import { extractParams, verifySolution } from "altcha-lib"; import { extractParams, verifySolution } from "altcha-lib";
import chalk from "chalk"; import chalk from "chalk";
@ -24,7 +24,7 @@ import {
import { type ParsedQs, parse } from "qs"; import { type ParsedQs, parse } from "qs";
import type { z } from "zod"; import type { z } from "zod";
import { fromZodError } from "zod-validation-error"; import { fromZodError } from "zod-validation-error";
import { type AuthData, getFromHeader } from "~/classes/functions/user"; import type { AuthData } from "~/classes/functions/user";
import { config } from "~/packages/config-manager/index.ts"; import { config } from "~/packages/config-manager/index.ts";
import type { ApiRouteMetadata, HonoEnv, HttpVerb } from "~/types/api"; import type { ApiRouteMetadata, HonoEnv, HttpVerb } from "~/types/api";
@ -178,20 +178,12 @@ const checkRouteNeedsAuth = (
auth: AuthData | null, auth: AuthData | null,
authData: ApiRouteMetadata["auth"], authData: ApiRouteMetadata["auth"],
context: Context, context: Context,
): ): Response | AuthData => {
| Response if (auth?.user && auth?.token) {
| {
user: User | null;
token: string | null;
application: Application | null;
} => {
if (auth?.user) {
return { return {
user: auth.user as User, user: auth.user,
token: auth.token as string, token: auth.token,
application: auth.application application: auth.application,
? new Application(auth.application)
: null,
}; };
} }
if ( if (
@ -295,8 +287,19 @@ export const auth = (
): MiddlewareHandler<HonoEnv, string> => ): MiddlewareHandler<HonoEnv, string> =>
createMiddleware<HonoEnv>(async (context, next) => { createMiddleware<HonoEnv>(async (context, next) => {
const header = context.req.header("Authorization"); const header = context.req.header("Authorization");
const tokenString = header?.split(" ")[1];
const auth = header ? await getFromHeader(header) : null; const token = tokenString
? await Token.fromAccessToken(tokenString)
: null;
const auth: AuthData = {
token,
application: token?.data.application
? new Application(token?.data.application)
: null,
user: (await token?.getUser()) ?? null,
};
// Only exists for type casting, as otherwise weird errors happen with Hono // Only exists for type casting, as otherwise weird errors happen with Hono
const fakeResponse = context.json({}); const fakeResponse = context.json({});
@ -325,11 +328,7 @@ export const auth = (
const authCheck = checkRouteNeedsAuth(auth, authData, context) as const authCheck = checkRouteNeedsAuth(auth, authData, context) as
| typeof fakeResponse | typeof fakeResponse
| { | AuthData;
user: User | null;
token: string | null;
application: Application | null;
};
if (authCheck instanceof Response) { if (authCheck instanceof Response) {
return authCheck; return authCheck;
@ -385,7 +384,7 @@ async function parseUrlEncoded(context: Context): Promise<ParsedQs> {
export const qsQuery = (): MiddlewareHandler => { export const qsQuery = (): MiddlewareHandler => {
return createMiddleware(async (context, next) => { return createMiddleware(async (context, next) => {
const parsed = parse(context.req.query(), { const parsed = parse(new URL(context.req.url).searchParams.toString(), {
parseArrays: true, parseArrays: true,
interpretNumericEntities: true, interpretNumericEntities: true,
}); });