summary refs log tree commit diff
path: root/src/index
diff options
context:
space:
mode:
Diffstat (limited to 'src/index')
-rw-r--r--src/index/Server.ts125
-rw-r--r--src/index/routes/channels/#channel_id/index.ts116
-rw-r--r--src/index/start.ts57
3 files changed, 298 insertions, 0 deletions
diff --git a/src/index/Server.ts b/src/index/Server.ts
new file mode 100644

index 00000000..0be5f0f8 --- /dev/null +++ b/src/index/Server.ts
@@ -0,0 +1,125 @@ +/* + * Fosscord: A FOSS re-implementation and extension of the Discord.com backend. + * Copyright (C) 2023 Fosscord and Fosscord Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +import { + Config, + initDatabase, + JSONReplacer, + registerRoutes, + Sentry, +} from "@fosscord/util"; +import { Request, Response, Router } from "express"; +import { Server, ServerOptions } from "lambert-server"; +import "missing-native-js-functions"; +import morgan from "morgan"; +import path from "path"; +import { red } from "picocolors"; +import { CORS, ErrorHandler, initRateLimits } from "@fosscord/api"; +import { initTranslation } from "../api/middlewares/Translation"; + +export type FosscordServerOptions = ServerOptions; + +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Express { + interface Request { + server: FosscordServer; + } + } +} + +export class FosscordServer extends Server { + public declare options: FosscordServerOptions; + + constructor(opts?: Partial<FosscordServerOptions>) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + super({ ...opts, errorHandler: false, jsonBody: false }); + } + + async start() { + await initDatabase(); + await Config.init(); + await Sentry.init(this.app); + + const logRequests = process.env["LOG_REQUESTS"] != undefined; + if (logRequests) { + this.app.use( + morgan("combined", { + skip: (req, res) => { + let skip = !( + process.env["LOG_REQUESTS"]?.includes( + res.statusCode.toString(), + ) ?? false + ); + if (process.env["LOG_REQUESTS"]?.charAt(0) == "-") + skip = !skip; + return skip; + }, + }), + ); + } + + this.app.set("json replacer", JSONReplacer); + + this.app.use(CORS); + + const app = this.app; + const api = Router(); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + this.app = api; + + await initRateLimits(api); + await initTranslation(api); + + this.routes = await registerRoutes( + this, + path.join(__dirname, "routes", "/"), + ); + + // 404 is not an error in express, so this should not be an error middleware + // this is a fine place to put the 404 handler because its after we register the routes + // and since its not an error middleware, our error handler below still works. + api.use("*", (req: Request, res: Response) => { + res.status(404).json({ + message: "404 endpoint not found", + code: 0, + }); + }); + + this.app = app; + + //app.use("/__development", ) + //app.use("/__internals", ) + app.use("/index", api); + + this.app.use(ErrorHandler); + + Sentry.errorHandler(this.app); + + if (logRequests) + console.log( + red( + `Warning: Request logging is enabled! This will spam your console!\nTo disable this, unset the 'LOG_REQUESTS' environment variable!`, + ), + ); + + return super.start(); + } +} diff --git a/src/index/routes/channels/#channel_id/index.ts b/src/index/routes/channels/#channel_id/index.ts new file mode 100644
index 00000000..dec04006 --- /dev/null +++ b/src/index/routes/channels/#channel_id/index.ts
@@ -0,0 +1,116 @@ +/* + * Fosscord: A FOSS re-implementation and extension of the Discord.com backend. + * Copyright (C) 2023 Fosscord and Fosscord Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +import { Channel, Message, User } from "@fosscord/util"; +import { Request, Response, Router } from "express"; +import { route } from "@fosscord/api"; + +const router: Router = Router(); +// TODO: delete channel +// TODO: Get channel + +router.get("/", route({}), async (req: Request, res: Response) => { + const { channel_id } = req.params; + const chunk_size = Number(req.query.chunk_size) || 100; + if (chunk_size > 1000) + return res + .status(400) + .send({ message: "chunk_size must be <= 1000", code: 50035 }); + + // Alicia - get channel + const channel = await Channel.findOne({ + where: { id: channel_id }, + relations: ["guild"], + }); + // Alicia - if channel not found, or not indexable... + if (!channel || !channel.indexable) + return res.status(404).send({ + message: "Unknown Channel (Is it indexable?)", + code: 10003, + }); + + res.setHeader("Transfer-Encoding", "chunked"); + // Alicia - base body... + res.write("context and info here...\n"); + + // Alicia - get messages + console.time("get channel"); + console.log("Creating query builders..."); + const messageQueryBuilder = await Message.createQueryBuilder(); + const userQueryBuilder = await User.createQueryBuilder(); + console.log(`Querying messages for channel ${channel_id}...`); + const msg_author_ids = await messageQueryBuilder + .where("channel_id = :channel_id", { channel_id }) + .orderBy("id::int8", "DESC") + .select("Message.id, Message.author_id") + .getRawMany(); + + const message_ids = msg_author_ids.map((message) => message.id); + const author_ids = msg_author_ids.map((message) => message.author_id); + + console.log( + `Got ${message_ids.length} messages for channel ${channel_id}...`, + ); + // Alicia - get authors + const author_results = await userQueryBuilder + .where("id IN (:...ids)", { + ids: author_ids.unique(), + }) + .distinctOn(["id"]) + .select("User.id, User.username, User.discriminator, User.avatar") + .getRawMany(); + // Alicia - turn result into dictionary + const authors: any = {}; + for (const author of author_results) { + authors[author.User_id] = author; + } + + console.log( + `Got ${author_results.length} authors for channel ${channel_id}...`, + ); + + // Alicia - write messages + while (message_ids.length > 0) { + const current_message_ids = message_ids.splice(0, chunk_size); + res.write(`<!-- ${message_ids.length} remain... -->\n`); + console.log(`${message_ids.length} remain...`); + const messages = await messageQueryBuilder + .select("*") + .where("id IN (:...ids)", { + ids: current_message_ids, + }) + .getRawMany(); + for (const message of messages) { + res.write(JSON.stringify(message) + "\n"); + } + } + // message_ids.slice(); + // for (const message_id of message_ids) { + // const message = await Message.findOne({ + // where: { id: message_id + "" }, + // }); + // res.write(JSON.stringify(message) + "\n"); + // res.write("\n"); + // } + + res.write(JSON.stringify(channel)); + console.timeEnd("get channel"); + res.end(); +}); + +export default router; diff --git a/src/index/start.ts b/src/index/start.ts new file mode 100644
index 00000000..7975d085 --- /dev/null +++ b/src/index/start.ts
@@ -0,0 +1,57 @@ +/* + Fosscord: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Fosscord and Fosscord Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +require("module-alias/register"); +process.on("uncaughtException", console.error); +process.on("unhandledRejection", console.error); + +import "missing-native-js-functions"; +import { config } from "dotenv"; +config(); +import { FosscordServer } from "./Server"; +import cluster from "cluster"; +import os from "os"; +let cores = 1; +try { + cores = Number(process.env.THREADS) || os.cpus().length; +} catch { + console.log("[API] Failed to get thread count! Using 1..."); +} + +if (cluster.isPrimary && process.env.NODE_ENV == "production") { + console.log(`Primary ${process.pid} is running`); + + // Fork workers. + for (let i = 0; i < cores; i++) { + cluster.fork(); + } + + cluster.on("exit", (worker) => { + console.log(`worker ${worker.process.pid} died, restart worker`); + cluster.fork(); + }); +} else { + const port = Number(process.env.PORT) || 3001; + + const server = new FosscordServer({ port }); + server.start().catch(console.error); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + global.server = server; +}