2024-03-04 02:27:08 +01:00
|
|
|
import { getConfig } from "~classes/configmanager";
|
2023-10-02 02:07:29 +02:00
|
|
|
import { jsonResponse } from "@response";
|
2023-10-23 02:32:17 +02:00
|
|
|
import chalk from "chalk";
|
2023-09-15 05:21:38 +02:00
|
|
|
import { appendFile } from "fs/promises";
|
2023-10-16 05:51:29 +02:00
|
|
|
import { matches } from "ip-matching";
|
2023-11-23 05:10:37 +01:00
|
|
|
import { getFromRequest } from "~database/entities/User";
|
2023-11-19 21:36:54 +01:00
|
|
|
import { mkdir } from "fs/promises";
|
2023-11-23 05:10:37 +01:00
|
|
|
import type { PrismaClientInitializationError } from "@prisma/client/runtime/library";
|
2023-12-02 00:00:00 +01:00
|
|
|
import { initializeRedisCache } from "@redis";
|
2023-12-03 05:11:30 +01:00
|
|
|
import { connectMeili } from "@meilisearch";
|
2023-12-08 09:25:31 +01:00
|
|
|
import { matchRoute } from "~routes";
|
2023-11-23 00:04:31 +01:00
|
|
|
|
2023-11-30 05:16:58 +01:00
|
|
|
const timeAtStart = performance.now();
|
2023-09-11 05:54:14 +02:00
|
|
|
|
2023-10-23 02:32:17 +02:00
|
|
|
console.log(`${chalk.green(`>`)} ${chalk.bold("Starting Lysand...")}`);
|
2023-09-11 05:46:20 +02:00
|
|
|
|
2023-09-11 05:54:14 +02:00
|
|
|
const config = getConfig();
|
2023-09-15 05:21:38 +02:00
|
|
|
const requests_log = Bun.file(process.cwd() + "/logs/requests.log");
|
|
|
|
|
|
2023-12-09 04:32:45 +01:00
|
|
|
// Needs to be imported after config is loaded
|
|
|
|
|
import { client } from "~database/datasource";
|
|
|
|
|
|
2023-12-09 02:51:48 +01:00
|
|
|
// NODE_ENV seems to be broken and output `development` even when set to production, so use the flag instead
|
|
|
|
|
const isProd =
|
|
|
|
|
process.env.NODE_ENV === "production" || process.argv.includes("--prod");
|
|
|
|
|
|
2023-09-15 05:21:38 +02:00
|
|
|
if (!(await requests_log.exists())) {
|
2023-10-23 02:32:17 +02:00
|
|
|
console.log(`${chalk.green(`✓`)} ${chalk.bold("Creating logs folder...")}`);
|
2023-11-19 21:36:54 +01:00
|
|
|
await mkdir(process.cwd() + "/logs");
|
2023-09-15 05:21:38 +02:00
|
|
|
await Bun.write(process.cwd() + "/logs/requests.log", "");
|
|
|
|
|
}
|
2023-09-11 05:54:14 +02:00
|
|
|
|
2023-12-02 00:00:00 +01:00
|
|
|
const redisCache = await initializeRedisCache();
|
|
|
|
|
|
2023-12-03 05:11:30 +01:00
|
|
|
if (config.meilisearch.enabled) {
|
|
|
|
|
await connectMeili();
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-02 00:00:00 +01:00
|
|
|
if (redisCache) {
|
|
|
|
|
client.$use(redisCache);
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-21 00:58:39 +01:00
|
|
|
// Check if database is reachable
|
2023-11-29 04:57:35 +01:00
|
|
|
let postCount = 0;
|
2023-11-21 00:58:39 +01:00
|
|
|
try {
|
2023-11-29 04:57:35 +01:00
|
|
|
postCount = await client.status.count();
|
2023-11-21 00:58:39 +01:00
|
|
|
} catch (e) {
|
|
|
|
|
const error = e as PrismaClientInitializationError;
|
|
|
|
|
console.error(
|
|
|
|
|
`${chalk.red(`✗`)} ${chalk.bold(
|
|
|
|
|
"Error while connecting to database: "
|
|
|
|
|
)} ${error.message}`
|
|
|
|
|
);
|
|
|
|
|
process.exit(1);
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-11 05:31:08 +02:00
|
|
|
Bun.serve({
|
2023-09-22 04:15:12 +02:00
|
|
|
port: config.http.bind_port,
|
|
|
|
|
hostname: config.http.bind || "0.0.0.0", // defaults to "0.0.0.0"
|
2023-09-11 05:31:08 +02:00
|
|
|
async fetch(req) {
|
2023-10-16 05:51:29 +02:00
|
|
|
/* Check for banned IPs */
|
|
|
|
|
const request_ip = this.requestIP(req)?.address ?? "";
|
2023-09-15 05:21:38 +02:00
|
|
|
|
2023-10-16 05:51:29 +02:00
|
|
|
for (const ip of config.http.banned_ips) {
|
|
|
|
|
try {
|
|
|
|
|
if (matches(ip, request_ip)) {
|
|
|
|
|
return new Response(undefined, {
|
|
|
|
|
status: 403,
|
|
|
|
|
statusText: "Forbidden",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error(`[-] Error while parsing banned IP "${ip}" `);
|
|
|
|
|
throw e;
|
2023-09-15 05:21:38 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-16 05:51:29 +02:00
|
|
|
await logRequest(req);
|
|
|
|
|
|
2023-10-02 02:07:29 +02:00
|
|
|
if (req.method === "OPTIONS") {
|
|
|
|
|
return jsonResponse({});
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-08 09:25:31 +01:00
|
|
|
const { file, matchedRoute } = matchRoute(req.url);
|
2023-09-11 05:31:08 +02:00
|
|
|
|
|
|
|
|
if (matchedRoute) {
|
2023-12-08 09:25:31 +01:00
|
|
|
const meta = (await file).meta;
|
2023-10-16 05:51:29 +02:00
|
|
|
|
|
|
|
|
// Check for allowed requests
|
|
|
|
|
if (!meta.allowedMethods.includes(req.method as any)) {
|
|
|
|
|
return new Response(undefined, {
|
|
|
|
|
status: 405,
|
|
|
|
|
statusText: `Method not allowed: allowed methods are: ${meta.allowedMethods.join(
|
|
|
|
|
", "
|
|
|
|
|
)}`,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: Check for ratelimits
|
2023-11-12 07:39:59 +01:00
|
|
|
const auth = await getFromRequest(req);
|
2023-11-11 03:36:06 +01:00
|
|
|
|
2023-10-16 05:51:29 +02:00
|
|
|
// Check for authentication if required
|
|
|
|
|
if (meta.auth.required) {
|
2023-11-11 03:36:06 +01:00
|
|
|
if (!auth.user) {
|
2023-10-16 05:51:29 +02:00
|
|
|
return new Response(undefined, {
|
|
|
|
|
status: 401,
|
|
|
|
|
statusText: "Unauthorized",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} else if (
|
|
|
|
|
(meta.auth.requiredOnMethods ?? []).includes(req.method as any)
|
|
|
|
|
) {
|
2023-11-11 03:36:06 +01:00
|
|
|
if (!auth.user) {
|
2023-10-16 05:51:29 +02:00
|
|
|
return new Response(undefined, {
|
|
|
|
|
status: 401,
|
|
|
|
|
statusText: "Unauthorized",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-08 09:25:31 +01:00
|
|
|
return await (await file).default(req.clone(), matchedRoute, auth);
|
2023-09-11 05:31:08 +02:00
|
|
|
} else {
|
2023-12-09 03:50:25 +01:00
|
|
|
// Proxy response from Vite at localhost:5173 if in development mode
|
|
|
|
|
if (isProd) {
|
|
|
|
|
if (new URL(req.url).pathname.startsWith("/assets")) {
|
|
|
|
|
// Serve from pages/dist/assets
|
2023-12-09 02:51:48 +01:00
|
|
|
return new Response(
|
2023-12-09 03:50:25 +01:00
|
|
|
Bun.file(`./pages/dist${new URL(req.url).pathname}`)
|
2023-12-09 02:51:48 +01:00
|
|
|
);
|
2023-12-07 01:29:26 +01:00
|
|
|
}
|
2023-12-04 02:25:55 +01:00
|
|
|
|
2023-12-09 03:50:25 +01:00
|
|
|
// Serve from pages/dist
|
2024-03-04 04:29:44 +01:00
|
|
|
return new Response(Bun.file(`./pages/dist/index.html`));
|
2023-12-09 03:50:25 +01:00
|
|
|
} else {
|
|
|
|
|
const proxy = await fetch(
|
|
|
|
|
req.url.replace(
|
|
|
|
|
config.http.base_url,
|
|
|
|
|
"http://localhost:5173"
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (proxy.status !== 404) {
|
|
|
|
|
return proxy;
|
|
|
|
|
}
|
2023-12-04 02:25:55 +01:00
|
|
|
}
|
|
|
|
|
|
2023-09-11 05:46:20 +02:00
|
|
|
return new Response(undefined, {
|
2023-09-11 05:31:08 +02:00
|
|
|
status: 404,
|
|
|
|
|
statusText: "Route not found",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
});
|
2023-09-11 05:46:20 +02:00
|
|
|
|
2023-10-16 05:51:29 +02:00
|
|
|
const logRequest = async (req: Request) => {
|
|
|
|
|
if (config.logging.log_requests_verbose) {
|
|
|
|
|
await appendFile(
|
|
|
|
|
`${process.cwd()}/logs/requests.log`,
|
|
|
|
|
`[${new Date().toISOString()}] ${req.method} ${
|
|
|
|
|
req.url
|
|
|
|
|
}\n\tHeaders:\n`
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Add headers
|
2024-03-04 04:29:44 +01:00
|
|
|
// @ts-expect-error TypeScript is missing entries for some reason
|
2024-03-04 05:33:09 +01:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
2023-10-16 05:51:29 +02:00
|
|
|
const headers = req.headers.entries();
|
|
|
|
|
|
|
|
|
|
for (const [key, value] of headers) {
|
|
|
|
|
await appendFile(
|
|
|
|
|
`${process.cwd()}/logs/requests.log`,
|
|
|
|
|
`\t\t${key}: ${value}\n`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const body = await req.clone().text();
|
|
|
|
|
|
|
|
|
|
await appendFile(
|
|
|
|
|
`${process.cwd()}/logs/requests.log`,
|
|
|
|
|
`\tBody:\n\t${body}\n`
|
|
|
|
|
);
|
|
|
|
|
} else if (config.logging.log_requests) {
|
|
|
|
|
await appendFile(
|
|
|
|
|
process.cwd() + "/logs/requests.log",
|
|
|
|
|
`[${new Date().toISOString()}] ${req.method} ${req.url}\n`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2023-10-23 02:32:17 +02:00
|
|
|
// Remove previous console.log
|
2023-11-22 01:56:58 +01:00
|
|
|
// console.clear();
|
2023-10-23 02:32:17 +02:00
|
|
|
|
|
|
|
|
console.log(
|
|
|
|
|
`${chalk.green(`✓`)} ${chalk.bold(
|
|
|
|
|
`Lysand started at ${chalk.blue(
|
|
|
|
|
`${config.http.bind}:${config.http.bind_port}`
|
2023-11-30 05:16:58 +01:00
|
|
|
)} in ${chalk.gray((performance.now() - timeAtStart).toFixed(0))}ms`
|
2023-10-23 02:32:17 +02:00
|
|
|
)}`
|
|
|
|
|
);
|
2023-11-21 00:58:39 +01:00
|
|
|
|
|
|
|
|
console.log(
|
|
|
|
|
`${chalk.green(`✓`)} ${chalk.bold(`Database is ${chalk.blue("online")}`)}`
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Print "serving x posts"
|
|
|
|
|
console.log(
|
|
|
|
|
`${chalk.green(`✓`)} ${chalk.bold(
|
|
|
|
|
`Serving ${chalk.blue(postCount)} posts`
|
|
|
|
|
)}`
|
|
|
|
|
);
|