Refactors, bugfixing

This commit is contained in:
Jesse Wierzbinski 2024-04-07 17:28:18 -10:00
parent 5812618170
commit e26d604a54
No known key found for this signature in database
42 changed files with 370 additions and 376 deletions

View file

@ -19,7 +19,10 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
return xmlResponse(`
<?xml version="1.0" encoding="UTF-8"?>
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
<Link rel="lrdd" template="${config.http.base_url}/.well-known/webfinger?resource={uri}"/>
<Link rel="lrdd" template="${new URL(
"/.well-known/webfinger",
config.http.base_url,
).toString()}?resource={uri}"/>
</XRD>
`);
});

View file

@ -1,4 +1,5 @@
import { apiRoute, applyConfig } from "@api";
import { redirect } from "@response";
export const meta = applyConfig({
allowedMethods: ["GET"],
@ -15,10 +16,8 @@ export const meta = applyConfig({
export default apiRoute(async (req, matchedRoute, extraData) => {
const config = await extraData.configManager.getConfig();
return new Response("", {
status: 301,
headers: {
Location: `${config.http.base_url}/.well-known/nodeinfo/2.0`,
},
});
return redirect(
new URL("/.well-known/nodeinfo/2.0", config.http.base_url),
301,
);
});

View file

@ -41,18 +41,11 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
links: [
{
rel: "self",
type: "application/activity+json",
href: `${config.http.base_url}/users/${user.username}/actor`,
},
{
rel: "https://webfinger.net/rel/profile-page",
type: "text/html",
href: `${config.http.base_url}/users/${user.username}`,
},
{
rel: "self",
type: 'application/activity+json; profile="https://www.w3.org/ns/activitystreams"',
href: `${config.http.base_url}/users/${user.username}/actor`,
type: "application/json",
href: new URL(
`/users/${user.id}`,
config.http.base_url,
).toString(),
},
],
});

View file

@ -1,5 +1,4 @@
import { apiRoute, applyConfig } from "@api";
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { errorResponse, jsonResponse } from "@response";
import { client } from "~database/datasource";
import { userToAPI } from "~database/entities/User";

View file

@ -1,5 +1,4 @@
import { apiRoute, applyConfig } from "@api";
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { errorResponse, jsonResponse } from "@response";
import { client } from "~database/datasource";
import { userToAPI } from "~database/entities/User";

View file

@ -33,7 +33,6 @@ export default apiRoute<{
if (!self) return errorResponse("Unauthorized", 401);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { notifications, duration } = extraData.parsedRequest;
const user = await client.user.findUnique({

View file

@ -1,6 +1,4 @@
import { apiRoute, applyConfig } from "@api";
import type { Prisma, Status, User } from "@prisma/client";
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { errorResponse, jsonResponse } from "@response";
import { fetchTimeline } from "@timelines";
import { client } from "~database/datasource";

View file

@ -1,5 +1,5 @@
import { apiRoute, applyConfig } from "@api";
import { jsonResponse } from "@response";
import { jsonResponse, response } from "@response";
import { tempmailDomains } from "@tempmail";
import ISO6391 from "iso-639-1";
import { client } from "~database/datasource";
@ -200,7 +200,5 @@ export default apiRoute<{
email: body.email ?? "",
});
return new Response("", {
status: 200,
});
return response(null, 200);
});

View file

@ -1,7 +1,8 @@
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { fetchTimeline } from "@timelines";
import { client } from "~database/datasource";
import { userToAPI } from "~database/entities/User";
import { userToAPI, type UserWithRelations } from "~database/entities/User";
import { userRelations } from "~database/entities/relations";
export const meta = applyConfig({
@ -13,25 +14,48 @@ export const meta = applyConfig({
},
auth: {
required: true,
oauthPermissions: ["read:blocks"],
},
});
export default apiRoute(async (req, matchedRoute, extraData) => {
export default apiRoute<{
max_id?: string;
since_id?: string;
min_id?: string;
limit?: number;
}>(async (req, matchedRoute, extraData) => {
const { user } = extraData.auth;
if (!user) return errorResponse("Unauthorized", 401);
const blocks = await client.user.findMany({
where: {
relationshipSubjects: {
some: {
ownerId: user.id,
blocking: true,
const { max_id, since_id, limit = 40 } = extraData.parsedRequest;
const { objects: blocks, link } = await fetchTimeline<UserWithRelations>(
client.user,
{
where: {
relationshipSubjects: {
some: {
ownerId: user.id,
blocking: true,
},
},
id: {
lt: max_id,
gte: since_id,
},
},
include: userRelations,
take: Number(limit),
},
include: userRelations,
});
req,
);
return jsonResponse(blocks.map((u) => userToAPI(u)));
return jsonResponse(
blocks.map((u) => userToAPI(u)),
200,
{
Link: link,
},
);
});

View file

@ -1,7 +1,11 @@
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { fetchTimeline } from "@timelines";
import { client } from "~database/datasource";
import { statusToAPI } from "~database/entities/Status";
import {
statusToAPI,
type StatusWithRelations,
} from "~database/entities/Status";
import { statusAndUserRelations } from "~database/entities/relations";
export const meta = applyConfig({
@ -32,35 +36,29 @@ export default apiRoute<{
if (!user) return errorResponse("Unauthorized", 401);
const objects = await client.status.findMany({
where: {
id: {
lt: max_id ?? undefined,
gte: since_id ?? undefined,
gt: min_id ?? undefined,
},
likes: {
some: {
likerId: user.id,
const { objects, link } = await fetchTimeline<StatusWithRelations>(
client.status,
{
where: {
id: {
lt: max_id ?? undefined,
gte: since_id ?? undefined,
gt: min_id ?? undefined,
},
likes: {
some: {
likerId: user.id,
},
},
},
include: statusAndUserRelations,
take: Number(limit),
orderBy: {
id: "desc",
},
},
include: statusAndUserRelations,
take: Number(limit),
orderBy: {
id: "desc",
},
});
// Constuct HTTP Link header (next and prev)
const linkHeader = [];
if (objects.length > 0) {
const urlWithoutQuery = req.url.split("?")[0];
linkHeader.push(
`<${urlWithoutQuery}?max_id=${objects.at(-1)?.id}>; rel="next"`,
`<${urlWithoutQuery}?min_id=${objects[0].id}>; rel="prev"`,
);
}
req,
);
return jsonResponse(
await Promise.all(
@ -68,7 +66,7 @@ export default apiRoute<{
),
200,
{
Link: linkHeader.join(", "),
Link: link,
},
);
});

View file

@ -1,7 +1,8 @@
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { fetchTimeline } from "@timelines";
import { client } from "~database/datasource";
import { userToAPI } from "~database/entities/User";
import { userToAPI, type UserWithRelations } from "~database/entities/User";
import { userRelations } from "~database/entities/relations";
export const meta = applyConfig({
@ -32,42 +33,36 @@ export default apiRoute<{
if (!user) return errorResponse("Unauthorized", 401);
const objects = await client.user.findMany({
where: {
id: {
lt: max_id ?? undefined,
gte: since_id ?? undefined,
gt: min_id ?? undefined,
},
relationships: {
some: {
subjectId: user.id,
requested: true,
const { objects, link } = await fetchTimeline<UserWithRelations>(
client.user,
{
where: {
id: {
lt: max_id ?? undefined,
gte: since_id ?? undefined,
gt: min_id ?? undefined,
},
relationships: {
some: {
subjectId: user.id,
requested: true,
},
},
},
include: userRelations,
take: Number(limit),
orderBy: {
id: "desc",
},
},
include: userRelations,
take: Number(limit),
orderBy: {
id: "desc",
},
});
// Constuct HTTP Link header (next and prev)
const linkHeader = [];
if (objects.length > 0) {
const urlWithoutQuery = req.url.split("?")[0];
linkHeader.push(
`<${urlWithoutQuery}?max_id=${objects.at(-1)?.id}>; rel="next"`,
`<${urlWithoutQuery}?min_id=${objects[0].id}>; rel="prev"`,
);
}
req,
);
return jsonResponse(
objects.map((user) => userToAPI(user)),
200,
{
Link: linkHeader.join(", "),
Link: link,
},
);
});

View file

@ -1,5 +1,5 @@
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { errorResponse, jsonResponse, response } from "@response";
import type { MediaBackend } from "media-manager";
import { MediaBackendType } from "media-manager";
import { client } from "~database/datasource";
@ -52,9 +52,7 @@ export default apiRoute<{
if (attachment.url) {
return jsonResponse(attachmentToAPI(attachment));
}
return new Response(null, {
status: 206,
});
return response(null, 206);
}
case "PUT": {
const { description, thumbnail } = extraData.parsedRequest;

View file

@ -1,7 +1,8 @@
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { fetchTimeline } from "@timelines";
import { client } from "~database/datasource";
import { userToAPI } from "~database/entities/User";
import { userToAPI, type UserWithRelations } from "~database/entities/User";
import { userRelations } from "~database/entities/relations";
export const meta = applyConfig({
@ -13,25 +14,40 @@ export const meta = applyConfig({
},
auth: {
required: true,
oauthPermissions: ["read:mutes"],
},
});
export default apiRoute(async (req, matchedRoute, extraData) => {
export default apiRoute<{
max_id?: string;
since_id?: string;
limit?: number;
}>(async (req, matchedRoute, extraData) => {
const { user } = extraData.auth;
const { max_id, since_id, limit = 40 } = extraData.parsedRequest;
if (!user) return errorResponse("Unauthorized", 401);
const blocks = await client.user.findMany({
where: {
relationshipSubjects: {
some: {
ownerId: user.id,
muting: true,
const { objects: blocks, link } = await fetchTimeline<UserWithRelations>(
client.user,
{
where: {
relationshipSubjects: {
some: {
ownerId: user.id,
muting: true,
},
},
id: {
lt: max_id,
gte: since_id,
},
},
include: userRelations,
take: Number(limit),
},
include: userRelations,
});
req,
);
return jsonResponse(blocks.map((u) => userToAPI(u)));
});

View file

@ -1,5 +1,7 @@
import { apiRoute, applyConfig } from "@api";
import type { Prisma } from "@prisma/client";
import { errorResponse, jsonResponse } from "@response";
import { fetchTimeline } from "@timelines";
import { client } from "~database/datasource";
import { notificationToAPI } from "~database/entities/Notification";
import {
@ -50,53 +52,49 @@ export default apiRoute<{
return errorResponse("Can't use both types and exclude_types", 400);
}
const objects = await client.notification.findMany({
where: {
notifiedId: user.id,
id: {
lt: max_id,
gt: min_id,
gte: since_id,
const { objects, link } = await fetchTimeline<
Prisma.NotificationGetPayload<{
include: {
account: {
include: typeof userRelations;
};
status: {
include: typeof statusAndUserRelations;
};
};
}>
>(
client.notification,
{
where: {
id: {
lt: max_id ?? undefined,
gte: since_id ?? undefined,
gt: min_id ?? undefined,
},
accountId: account_id,
},
type: {
in: types,
notIn: exclude_types,
include: {
account: {
include: userRelations,
},
status: {
include: statusAndUserRelations,
},
},
accountId: account_id,
orderBy: {
id: "desc",
},
take: Number(limit),
},
include: {
account: {
include: userRelations,
},
status: {
include: statusAndUserRelations,
},
},
orderBy: {
id: "desc",
},
take: Number(limit),
});
// Constuct HTTP Link header (next and prev)
const linkHeader = [];
if (objects.length > 0) {
const urlWithoutQuery = req.url.split("?")[0];
linkHeader.push(
`<${urlWithoutQuery}?max_id=${objects[0].id}&limit=${limit}>; rel="next"`,
);
linkHeader.push(
`<${urlWithoutQuery}?since_id=${
objects.at(-1)?.id
}&limit=${limit}>; rel="prev"`,
);
}
req,
);
return jsonResponse(
await Promise.all(objects.map((n) => notificationToAPI(n))),
200,
{
Link: linkHeader.join(", "),
Link: link,
},
);
});

View file

@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { client } from "~database/datasource";

View file

@ -1,8 +1,9 @@
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { fetchTimeline } from "@timelines";
import { client } from "~database/datasource";
import { isViewableByUser } from "~database/entities/Status";
import { userToAPI } from "~database/entities/User";
import { userToAPI, type UserWithRelations } from "~database/entities/User";
import {
statusAndUserRelations,
userRelations,
@ -42,63 +43,48 @@ export default apiRoute<{
if (!status || !isViewableByUser(status, user))
return errorResponse("Record not found", 404);
const {
max_id = null,
min_id = null,
since_id = null,
limit = 40,
} = extraData.parsedRequest;
const { max_id, min_id, since_id, limit = 40 } = extraData.parsedRequest;
// Check for limit limits
if (limit > 80) return errorResponse("Invalid limit (maximum is 80)", 400);
if (limit < 1) return errorResponse("Invalid limit", 400);
const objects = await client.user.findMany({
where: {
likes: {
some: {
likedId: status.id,
const { objects, link } = await fetchTimeline<UserWithRelations>(
client.user,
{
where: {
likes: {
some: {
likedId: status.id,
},
},
id: {
lt: max_id,
gte: since_id,
gt: min_id,
},
},
id: {
lt: max_id ?? undefined,
gte: since_id ?? undefined,
gt: min_id ?? undefined,
},
},
include: {
...userRelations,
likes: {
where: {
likedId: status.id,
include: {
...userRelations,
likes: {
where: {
likedId: status.id,
},
},
},
take: Number(limit),
orderBy: {
id: "desc",
},
},
take: Number(limit),
orderBy: {
id: "desc",
},
});
// Constuct HTTP Link header (next and prev)
const linkHeader = [];
if (objects.length > 0) {
const urlWithoutQuery = req.url.split("?")[0];
linkHeader.push(
`<${urlWithoutQuery}?max_id=${objects[0].id}&limit=${limit}>; rel="next"`,
);
linkHeader.push(
`<${urlWithoutQuery}?since_id=${
objects[objects.length - 1].id
}&limit=${limit}>; rel="prev"`,
);
}
req,
);
return jsonResponse(
objects.map((user) => userToAPI(user)),
200,
{
Link: linkHeader.join(", "),
Link: link,
},
);
});

View file

@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { client } from "~database/datasource";

View file

@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { client } from "~database/datasource";
@ -58,7 +57,10 @@ export default apiRoute<{
authorId: user.id,
reblogId: status.id,
isReblog: true,
uri: `${config.http.base_url}/statuses/FAKE-${crypto.randomUUID()}`,
uri: new URL(
`/statuses/FAKE-${crypto.randomUUID()}`,
config.http.base_url,
).toString(),
visibility,
sensitive: false,
},
@ -68,7 +70,10 @@ export default apiRoute<{
await client.status.update({
where: { id: newReblog.id },
data: {
uri: `${config.http.base_url}/statuses/${newReblog.id}`,
uri: new URL(
`/statuses/${newReblog.id}`,
config.http.base_url,
).toString(),
},
include: statusAndUserRelations,
});
@ -89,7 +94,10 @@ export default apiRoute<{
await statusToAPI(
{
...newReblog,
uri: `${config.http.base_url}/statuses/${newReblog.id}`,
uri: new URL(
`/statuses/${newReblog.id}`,
config.http.base_url,
).toString(),
},
user,
),

View file

@ -1,8 +1,9 @@
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { fetchTimeline } from "@timelines";
import { client } from "~database/datasource";
import { isViewableByUser } from "~database/entities/Status";
import { userToAPI } from "~database/entities/User";
import { type UserWithRelations, userToAPI } from "~database/entities/User";
import {
statusAndUserRelations,
userRelations,
@ -53,53 +54,43 @@ export default apiRoute<{
if (limit > 80) return errorResponse("Invalid limit (maximum is 80)", 400);
if (limit < 1) return errorResponse("Invalid limit", 400);
const objects = await client.user.findMany({
where: {
statuses: {
some: {
reblogId: status.id,
const { objects, link } = await fetchTimeline<UserWithRelations>(
client.user,
{
where: {
statuses: {
some: {
reblogId: status.id,
},
},
id: {
lt: max_id ?? undefined,
gte: since_id ?? undefined,
gt: min_id ?? undefined,
},
},
id: {
lt: max_id ?? undefined,
gte: since_id ?? undefined,
gt: min_id ?? undefined,
},
},
include: {
...userRelations,
statuses: {
where: {
reblogId: status.id,
include: {
...userRelations,
statuses: {
where: {
reblogId: status.id,
},
include: statusAndUserRelations,
},
include: statusAndUserRelations,
},
take: Number(limit),
orderBy: {
id: "desc",
},
},
take: Number(limit),
orderBy: {
id: "desc",
},
});
// Constuct HTTP Link header (next and prev)
const linkHeader = [];
if (objects.length > 0) {
const urlWithoutQuery = req.url.split("?")[0];
linkHeader.push(
`<${urlWithoutQuery}?max_id=${objects[0].id}&limit=${limit}>; rel="next"`,
);
linkHeader.push(
`<${urlWithoutQuery}?since_id=${
objects[objects.length - 1].id
}&limit=${limit}>; rel="prev"`,
);
}
req,
);
return jsonResponse(
objects.map((user) => userToAPI(user)),
200,
{
Link: linkHeader.join(", "),
Link: link,
},
);
});

View file

@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { client } from "~database/datasource";

View file

@ -1,9 +1,9 @@
import { apiRoute, applyConfig } from "@api";
import { errorResponse } from "@response";
import { errorResponse, response } from "@response";
export const meta = applyConfig({
allowedMethods: ["GET"],
route: "/media/:id",
route: "/api/v1/media/:id",
ratelimits: {
max: 100,
duration: 60,
@ -35,11 +35,9 @@ export default apiRoute(async (req, matchedRoute) => {
if (!(await file.exists())) return errorResponse("File not found", 404);
// Can't directly copy file into Response because this crashes Bun for now
return new Response(buffer, {
headers: {
"Content-Type": file.type || "application/octet-stream",
"Content-Length": `${file.size - start}`,
"Content-Range": `bytes ${start}-${end}/${file.size}`,
},
return response(buffer, 200, {
"Content-Type": file.type || "application/octet-stream",
"Content-Length": `${file.size - start}`,
"Content-Range": `bytes ${start}-${end}/${file.size}`,
});
});

View file

@ -157,7 +157,6 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
return redirectToLogin("No user found with that account");
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!flow.application) return redirectToLogin("Invalid client_id");
const code = randomBytes(32).toString("hex");

View file

@ -51,14 +51,11 @@ export default apiRoute(async (req, matchedRoute, extraData) => {
last: `${host}/users/${uuid}/outbox?page=1`,
total_items: totalStatuses,
// Server actor
author: `${config.http.base_url}/users/actor`,
next:
statuses.length === 20
? `${host}/users/${uuid}/outbox?page=${pageNumber + 1}`
: undefined,
prev:
pageNumber > 1
? `${host}/users/${uuid}/outbox?page=${pageNumber - 1}`
author: new URL("/users/actor", config.http.base_url).toString(),
next: statuses.length === 20
? new URL(`/users/${uuid}/outbox?page=${pageNumber + 1}`, config.http.base_url).toString(),
prev: pageNumber > 1
? new URL(`/users/${uuid}/outbox?page=${pageNumber - 1}`, config.http.base_url).toString()
: undefined,
items: statuses.map((s) => statusToLysand(s)),
});