Begin work on refactoring every single route to use new subsystems

This commit is contained in:
Jesse Wierzbinski 2024-03-10 12:48:14 -10:00
parent 3b75f5f0a5
commit 05140f0d6f
No known key found for this signature in database
19 changed files with 128 additions and 183 deletions

18
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,18 @@
{
"configurations": [
{
"type": "node",
"name": "vscode-jest-tests.v2.lysand",
"request": "launch",
"args": [
"test",
"${jest.testFile}"
],
"cwd": "/home/jessew/Dev/lysand",
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"disableOptimisticBPs": true,
"program": "/home/jessew/.bun/bin/bun"
}
]
}

View file

@ -1,3 +1,5 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
"typescript.tsdk": "node_modules/typescript/lib",
"jest.jestCommandLine": "/home/jessew/.bun/bin/bun test",
"jest.rootPath": "."
}

View file

@ -6,7 +6,7 @@
* Fuses both and provides a way to retrieve individual values
*/
import { parse, stringify } from "@iarna/toml";
/* import { parse, stringify } from "@iarna/toml";
import chalk from "chalk";
import merge from "merge-deep-ts";
@ -23,10 +23,10 @@ const scanConfig = async () => {
}
return parse(await config.text()) as ConfigType;
};
}; */
// Creates the internal config with nothing in it if it doesnt exist
const scanInternalConfig = async () => {
/* const scanInternalConfig = async () => {
const config = Bun.file(process.cwd() + "/config/config.internal.toml");
if (!(await config.exists())) {
@ -397,18 +397,18 @@ export const configDefaults: ConfigType = {
max_coeff: 1,
},
custom_ratelimits: {},
};
}; */
export const getConfig = () => {
/* export const getConfig = () => {
// Deeply merge configDefaults, config and internalConfig
return merge([configDefaults, config, internalConfig]) as any as ConfigType;
};
*/
/**
* Sets the internal config
* @param newConfig Any part of ConfigType
*/
export const setConfig = async (newConfig: Partial<ConfigType>) => {
/* export const setConfig = async (newConfig: Partial<ConfigType>) => {
const newInternalConfig = merge([
internalConfig,
newConfig,
@ -421,16 +421,16 @@ export const setConfig = async (newConfig: Partial<ConfigType>) => {
newInternalConfig as any
)}`
);
};
}; */
export const getHost = () => {
/* export const getHost = () => {
const url = new URL(getConfig().http.base_url);
return url.host;
};
*/
// Refresh config every 5 seconds
setInterval(() => {
/* setInterval(() => {
scanConfig()
.then(newConfig => {
if (newConfig !== config) {
@ -440,6 +440,7 @@ setInterval(() => {
.catch(e => {
console.error(e);
});
}, 5000);
}, 5000); */
export { config };
/* export { config };
*/

View file

@ -1,7 +1,5 @@
import { MatchedRoute } from "bun";
import { getConfig, getHost } from "~classes/configmanager";
import { xmlResponse } from "@response";
import { applyConfig } from "@api";
import { apiRoute, applyConfig } from "@api";
export const meta = applyConfig({
allowedMethods: ["GET"],
@ -16,18 +14,13 @@ export const meta = applyConfig({
});
/**
* Host meta endpoint
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
const config = getConfig();
export default apiRoute(async (req, matchedRoute, extraData) => {
const config = await extraData.configManager.getConfig();
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}"/>
</XRD>
`);
};
});

View file

@ -1,7 +1,5 @@
import { jsonResponse } from "@response";
import { MatchedRoute } from "bun";
import { getConfig } from "~classes/configmanager";
import { applyConfig } from "@api";
import { apiRoute, applyConfig } from "@api";
export const meta = applyConfig({
allowedMethods: ["GET"],
@ -15,14 +13,9 @@ export const meta = applyConfig({
route: "/.well-known/lysand",
});
/**
* Lysand instance metadata endpoint
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
const config = getConfig();
export default apiRoute(async (req, matchedRoute, extraData) => {
const config = await extraData.configManager.getConfig();
// In the format acct:name@example.com
return jsonResponse({
type: "ServerMetadata",
@ -47,4 +40,4 @@ export default async (
website: "https://lysand.org",
// TODO: Add admins, moderators field
})
};
})

View file

@ -1,6 +1,4 @@
import { MatchedRoute } from "bun";
import { getConfig, getHost } from "~classes/configmanager";
import { applyConfig } from "@api";
import { apiRoute, applyConfig } from "@api";
export const meta = applyConfig({
allowedMethods: ["GET"],
@ -15,14 +13,8 @@ export const meta = applyConfig({
});
/**
* Redirect to /nodeinfo/2.0
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
const config = getConfig();
export default apiRoute(async (req, matchedRoute, extraData) => {
const config = await extraData.configManager.getConfig();
return new Response("", {
status: 301,
@ -30,4 +22,4 @@ export default async (
Location: `${config.http.base_url}/.well-known/nodeinfo/2.0`,
},
});
};
});

View file

@ -1,7 +1,5 @@
import { errorResponse, jsonResponse } from "@response";
import { MatchedRoute } from "bun";
import { getConfig, getHost } from "~classes/configmanager";
import { applyConfig } from "@api";
import { apiRoute, applyConfig } from "@api";
import { client } from "~database/datasource";
export const meta = applyConfig({
@ -16,21 +14,16 @@ export const meta = applyConfig({
route: "/.well-known/webfinger",
});
/**
* ActivityPub WebFinger endpoint
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
export default apiRoute(async (req, matchedRoute, extraData) => {
// In the format acct:name@example.com
const resource = matchedRoute.query.resource;
const requestedUser = resource.split("acct:")[1];
const config = getConfig();
const config = await extraData.configManager.getConfig();
const host = new URL(config.http.base_url).hostname;
// Check if user is a local user
if (requestedUser.split("@")[1] !== getHost()) {
if (requestedUser.split("@")[1] !== host) {
return errorResponse("User is a remote user", 404);
}
@ -43,7 +36,7 @@ export default async (
}
return jsonResponse({
subject: `acct:${user.username}@${getHost()}`,
subject: `acct:${user.username}@${host}`,
links: [
{
@ -63,4 +56,4 @@ export default async (
}
]
})
};
});

View file

@ -1,12 +1,10 @@
import { applyConfig } from "@api";
import type { MatchedRoute } from "bun";
import { apiRoute, applyConfig } from "@api";
import { randomBytes } from "crypto";
import { client } from "~database/datasource";
import { TokenType } from "~database/entities/Token";
import { userRelations } from "~database/entities/User";
import type { APIRouteMeta } from "~types/api";
export const meta: APIRouteMeta = applyConfig({
export const meta = applyConfig({
allowedMethods: ["POST"],
ratelimits: {
max: 4,
@ -21,10 +19,10 @@ export const meta: APIRouteMeta = applyConfig({
/**
* OAuth Code flow
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
export default apiRoute<{
email: string;
password: string;
}>(async (req, matchedRoute, extraData) => {
const scopes = (matchedRoute.query.scope || "")
.replaceAll("+", " ")
.split(" ");
@ -32,10 +30,7 @@ export default async (
const response_type = matchedRoute.query.response_type;
const client_id = matchedRoute.query.client_id;
const formData = await req.formData();
const email = formData.get("email")?.toString() || null;
const password = formData.get("password")?.toString() || null;
const { email, password } = extraData.parsedRequest;
const redirectToLogin = (error: string) =>
Response.redirect(
@ -96,4 +91,4 @@ export default async (
// Redirect back to application
return Response.redirect(`${redirect_uri}?code=${code}`, 302);
};
});

View file

@ -1,6 +1,5 @@
import { errorResponse } from "@response";
import { applyConfig } from "@api";
import type { MatchedRoute } from "bun";
import { apiRoute, applyConfig } from "@api";
export const meta = applyConfig({
allowedMethods: ["GET"],
@ -14,10 +13,7 @@ export const meta = applyConfig({
},
});
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
export default apiRoute(async (req, matchedRoute) => {
// TODO: Add checks for disabled or not email verified accounts
const id = matchedRoute.params.id;
@ -46,4 +42,4 @@ export default async (
"Content-Range": `bytes ${start}-${end}/${file.size}`,
},
});
};
});

View file

@ -1,4 +1,4 @@
import { applyConfig } from "@api";
import { apiRoute, applyConfig } from "@api";
import { jsonResponse } from "@response";
export const meta = applyConfig({
@ -16,8 +16,7 @@ export const meta = applyConfig({
/**
* ActivityPub nodeinfo 2.0 endpoint
*/
// eslint-disable-next-line @typescript-eslint/require-await
export default async (): Promise<Response> => {
export default apiRoute(() => {
// TODO: Implement this
return jsonResponse({
version: "2.0",
@ -31,4 +30,4 @@ export default async (): Promise<Response> => {
openRegistrations: false,
metadata: {},
});
};
});

View file

@ -1,7 +1,5 @@
import { applyConfig } from "@api";
import { getConfig } from "~classes/configmanager";
import { apiRoute, applyConfig } from "@api";
import { oauthRedirectUri } from "@constants";
import type { MatchedRoute } from "bun";
import {
calculatePKCECodeChallenge,
discoveryRequest,
@ -25,11 +23,7 @@ export const meta = applyConfig({
/**
* Redirects the user to the external OAuth provider
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
// eslint-disable-next-line @typescript-eslint/require-await
): Promise<Response> => {
export default apiRoute(async (req, matchedRoute, extraData) => {
const redirectToLogin = (error: string) =>
Response.redirect(
`/oauth/authorize?` +
@ -49,7 +43,7 @@ export default async (
return redirectToLogin("Missing client_id");
}
const config = getConfig();
const config = await extraData.configManager.getConfig();
const issuer = config.oidc.providers.find(
provider => provider.id === issuerId
@ -98,4 +92,4 @@ export default async (
}).toString(),
302
);
};
});

View file

@ -1,7 +1,5 @@
import { applyConfig } from "@api";
import { getConfig } from "~classes/configmanager";
import { apiRoute, applyConfig } from "@api";
import { oauthRedirectUri } from "@constants";
import type { MatchedRoute } from "bun";
import { randomBytes } from "crypto";
import {
authorizationCodeGrantRequest,
@ -33,10 +31,7 @@ export const meta = applyConfig({
/**
* Redirects the user to the external OAuth provider
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
export default apiRoute(async (req, matchedRoute, extraData) => {
const redirectToLogin = (error: string) =>
Response.redirect(
`/oauth/authorize?` +
@ -65,7 +60,7 @@ export default async (
return redirectToLogin("Invalid flow");
}
const config = getConfig();
const config = await extraData.configManager.getConfig();
const issuer = config.oidc.providers.find(
provider => provider.id === issuerParam
@ -192,4 +187,4 @@ export default async (
`${flow.application.redirect_uris}?code=${code}`,
302
);
};
});

View file

@ -1,5 +1,4 @@
import { applyConfig } from "@api";
import { getConfig } from "~classes/configmanager";
import { apiRoute, applyConfig } from "@api";
import { jsonResponse } from "@response";
export const meta = applyConfig({
@ -17,9 +16,8 @@ export const meta = applyConfig({
/**
* Lists available OAuth providers
*/
// eslint-disable-next-line @typescript-eslint/require-await
export default async (): Promise<Response> => {
const config = getConfig();
export default apiRoute(async (req, matchedRoute, extraData) => {
const config = await extraData.configManager.getConfig();
return jsonResponse(
config.oidc.providers.map(p => ({
@ -28,4 +26,4 @@ export default async (): Promise<Response> => {
id: p.id,
}))
);
};
});

View file

@ -1,5 +1,4 @@
import { applyConfig } from "@api";
import { parseRequest } from "@request";
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import { client } from "~database/datasource";
@ -18,16 +17,16 @@ export const meta = applyConfig({
/**
* Allows getting token from OAuth code
*/
export default async (req: Request): Promise<Response> => {
const { grant_type, code, redirect_uri, client_id, client_secret, scope } =
await parseRequest<{
export default apiRoute<{
grant_type: string;
code: string;
redirect_uri: string;
client_id: string;
client_secret: string;
scope: string;
}>(req);
}>(async (req, matchedRoute, extraData) => {
const { grant_type, code, redirect_uri, client_id, client_secret, scope } =
extraData.parsedRequest;
if (grant_type !== "authorization_code")
return errorResponse(
@ -61,4 +60,4 @@ export default async (req: Request): Promise<Response> => {
scope: token.scope,
created_at: token.created_at,
});
};
});

View file

@ -1,8 +1,5 @@
/* eslint-disable @typescript-eslint/require-await */
/* eslint-disable @typescript-eslint/no-unused-vars */
import { applyConfig } from "@api";
import { apiRoute, applyConfig } from "@api";
import { jsonResponse } from "@response";
import type { MatchedRoute } from "bun";
export const meta = applyConfig({
allowedMethods: ["GET"],
@ -16,12 +13,6 @@ export const meta = applyConfig({
route: "/object/:id",
});
/**
* Fetch a user
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
export default apiRoute(() => {
return jsonResponse({});
};
});

View file

@ -1,10 +1,7 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unused-vars */
import { applyConfig } from "@api";
import { getConfig } from "~classes/configmanager";
// TODO: Refactor into smaller packages
import { apiRoute, applyConfig } from "@api";
import { getBestContentType } from "@content_types";
import { errorResponse, jsonResponse } from "@response";
import type { MatchedRoute } from "bun";
import { client } from "~database/datasource";
import { parseEmojis } from "~database/entities/Emoji";
import { createLike, deleteLike } from "~database/entities/Like";
@ -39,13 +36,10 @@ export const meta = applyConfig({
/**
* ActivityPub user inbox endpoint
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
export default apiRoute(async (req, matchedRoute, extraData) => {
const username = matchedRoute.params.username;
const config = getConfig();
const config = await extraData.configManager.getConfig();
try {
if (
@ -313,7 +307,7 @@ export default async (
}
// Create new reblog
const newReblog = await client.status.create({
await client.status.create({
data: {
authorId: author.id,
reblogId: rebloggedStatus.id,
@ -405,4 +399,4 @@ export default async (
}
return jsonResponse({});
};
});

View file

@ -1,9 +1,5 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unused-vars */
import { applyConfig } from "@api";
import { getConfig } from "~classes/configmanager";
import { apiRoute, applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response";
import type { MatchedRoute } from "bun";
import { client } from "~database/datasource";
import { userRelations, userToLysand } from "~database/entities/User";
@ -22,14 +18,9 @@ export const meta = applyConfig({
/**
* ActivityPub user inbox endpoint
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
export default apiRoute(async (req, matchedRoute) => {
const uuid = matchedRoute.params.uuid;
const config = getConfig();
const user = await client.user.findUnique({
where: {
id: uuid,
@ -42,4 +33,4 @@ export default async (
}
return jsonResponse(userToLysand(user));
};
});

View file

@ -1,7 +1,5 @@
import { jsonResponse } from "@response";
import type { MatchedRoute } from "bun";
import { getConfig, getHost } from "~classes/configmanager";
import { applyConfig } from "@api";
import { apiRoute, applyConfig } from "@api";
import {
statusAndUserRelations,
statusToLysand,
@ -23,13 +21,11 @@ export const meta = applyConfig({
/**
* ActivityPub user outbox endpoint
*/
export default async (
req: Request,
matchedRoute: MatchedRoute
): Promise<Response> => {
export default apiRoute(async (req, matchedRoute, extraData) => {
const uuid = matchedRoute.params.uuid;
const pageNumber = Number(matchedRoute.query.page) || 1;
const config = getConfig();
const config = await extraData.configManager.getConfig();
const host = new URL(config.http.base_url).hostname;
const statuses = await client.status.findMany({
where: {
@ -53,19 +49,19 @@ export default async (
});
return jsonResponse({
first: `${getHost()}/users/${uuid}/outbox?page=1`,
last: `${getHost()}/users/${uuid}/outbox?page=1`,
first: `${host}/users/${uuid}/outbox?page=1`,
last: `${host}/users/${uuid}/outbox?page=1`,
total_items: totalStatuses,
// Server actor
author: `${config.http.base_url}/users/actor`,
next:
statuses.length === 20
? `${getHost()}/users/${uuid}/outbox?page=${pageNumber + 1}`
? `${host}/users/${uuid}/outbox?page=${pageNumber + 1}`
: undefined,
prev:
pageNumber > 1
? `${getHost()}/users/${uuid}/outbox?page=${pageNumber - 1}`
? `${host}/users/${uuid}/outbox?page=${pageNumber - 1}`
: undefined,
items: statuses.map(s => statusToLysand(s)),
});
};
});

View file

@ -1,4 +1,5 @@
import { getConfig } from "~classes/configmanager";
import type { RouteHandler } from "~server/api/routes.type";
import type { APIRouteMeta } from "~types/api";
export const applyConfig = (routeMeta: APIRouteMeta) => {
@ -16,3 +17,7 @@ export const applyConfig = (routeMeta: APIRouteMeta) => {
return newMeta;
};
export const apiRoute = <T>(routeFunction: RouteHandler<T>) => {
return routeFunction;
};