summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--client_test/index.html3
-rw-r--r--package-lock.json43
-rw-r--r--package.json5
-rw-r--r--src/Server.ts9
-rw-r--r--src/routes/auth/register.ts1
-rw-r--r--src/routes/channels/#channel_id/messages/#message_id/index.ts2
-rw-r--r--src/routes/channels/#channel_id/messages/index.ts49
-rw-r--r--src/routes/users/#id/index.ts4
-rw-r--r--src/routes/users/@me/index.ts30
-rw-r--r--src/schema/Message.ts1
-rw-r--r--src/util/Message.ts2
-rw-r--r--src/util/cdn.ts24
-rw-r--r--src/util/instanceOf.ts3
13 files changed, 108 insertions, 68 deletions
diff --git a/client_test/index.html b/client_test/index.html

index de2fe44b..0da8784a 100644 --- a/client_test/index.html +++ b/client_test/index.html
@@ -13,12 +13,11 @@ window.GLOBAL_ENV = { API_ENDPOINT: "/api", WEBAPP_ENDPOINT: "", - CDN_HOST: "cdn.discordapp.com", + CDN_HOST: "//localhost:3003", ASSET_ENDPOINT: "", MEDIA_PROXY_ENDPOINT: "https://media.discordapp.net", WIDGET_ENDPOINT: "//discord.com/widget", INVITE_HOST: "discord.gg", - GUILD_TEMPLATE_HOST: "discord.new", GIFT_CODE_HOST: "discord.gift", RELEASE_CHANNEL: "stable", diff --git a/package-lock.json b/package-lock.json
index 35201da4..052f63c6 100644 --- a/package-lock.json +++ b/package-lock.json
@@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@fosscord/server-util": "^1.3.9", + "@fosscord/server-util": "^1.3.10", "@types/jest": "^26.0.22", "@types/json-schema": "^7.0.7", "ajv": "^8.4.0", @@ -24,11 +24,12 @@ "env-paths": "^2.2.1", "express": "^4.17.1", "express-validator": "^6.9.2", + "form-data": "^3.0.0", "i18next": "^19.8.5", "i18next-http-middleware": "^3.1.3", "i18next-node-fs-backend": "^2.1.3", "jsonwebtoken": "^8.5.1", - "lambert-server": "^1.2.2", + "lambert-server": "^1.2.3", "missing-native-js-functions": "^1.2.6", "mongodb": "^3.6.5", "mongoose": "^5.12.3", @@ -520,9 +521,9 @@ } }, "node_modules/@fosscord/server-util": { - "version": "1.3.9", - "resolved": "https://registry.npmjs.org/@fosscord/server-util/-/server-util-1.3.9.tgz", - "integrity": "sha512-1oOcMMOBVJO3BodyKQaP3ukg9ok8qfCeIAHSCcloO02lAq45Y+EI7Y7i5a8dotYl7CP8Uv8ke9mGqI3Tojg0Fw==", + "version": "1.3.10", + "resolved": "https://registry.npmjs.org/@fosscord/server-util/-/server-util-1.3.10.tgz", + "integrity": "sha512-pu+XAoerl/WLFxoNxT1NV7Nj0QT+QigK5ghr1VCXkN5N/pUAJUyC72fJPYk+5Ug0CbJkPb0XNsRVJpuz8k0R2g==", "dependencies": { "@types/jsonwebtoken": "^8.5.0", "@types/mongoose-autopopulate": "^0.10.1", @@ -2123,8 +2124,7 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "node_modules/atob": { "version": "2.1.2", @@ -3290,7 +3290,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -3848,7 +3847,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -4872,7 +4870,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", - "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -7928,9 +7925,9 @@ } }, "node_modules/lambert-server": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/lambert-server/-/lambert-server-1.2.2.tgz", - "integrity": "sha512-rsQlFQZDYl3+feM25WNdV8cUf6yS5SYyelCFV8ohF6pMLqQJfXcbvsEYzGeF1pIIkjnWehxTK2J9kJJpNWeWFg==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/lambert-server/-/lambert-server-1.2.3.tgz", + "integrity": "sha512-tBcxVH5Hj6ts/hk11e5ABc1ihxH9aIrXJth/9ivkfeqWjZEEzGrxvEmtnPULwGGy+k6lvUoZw725LDgVxoYGKQ==", "dependencies": { "body-parser": "^1.19.0", "express": "^4.17.1", @@ -12860,9 +12857,9 @@ } }, "@fosscord/server-util": { - "version": "1.3.9", - "resolved": "https://registry.npmjs.org/@fosscord/server-util/-/server-util-1.3.9.tgz", - "integrity": "sha512-1oOcMMOBVJO3BodyKQaP3ukg9ok8qfCeIAHSCcloO02lAq45Y+EI7Y7i5a8dotYl7CP8Uv8ke9mGqI3Tojg0Fw==", + "version": "1.3.10", + "resolved": "https://registry.npmjs.org/@fosscord/server-util/-/server-util-1.3.10.tgz", + "integrity": "sha512-pu+XAoerl/WLFxoNxT1NV7Nj0QT+QigK5ghr1VCXkN5N/pUAJUyC72fJPYk+5Ug0CbJkPb0XNsRVJpuz8k0R2g==", "requires": { "@types/jsonwebtoken": "^8.5.0", "@types/mongoose-autopopulate": "^0.10.1", @@ -14196,8 +14193,7 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "atob": { "version": "2.1.2", @@ -15195,7 +15191,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "requires": { "delayed-stream": "~1.0.0" } @@ -15691,8 +15686,7 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, "delegates": { "version": "1.0.0", @@ -16533,7 +16527,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", - "dev": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -18981,9 +18974,9 @@ } }, "lambert-server": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/lambert-server/-/lambert-server-1.2.2.tgz", - "integrity": "sha512-rsQlFQZDYl3+feM25WNdV8cUf6yS5SYyelCFV8ohF6pMLqQJfXcbvsEYzGeF1pIIkjnWehxTK2J9kJJpNWeWFg==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/lambert-server/-/lambert-server-1.2.3.tgz", + "integrity": "sha512-tBcxVH5Hj6ts/hk11e5ABc1ihxH9aIrXJth/9ivkfeqWjZEEzGrxvEmtnPULwGGy+k6lvUoZw725LDgVxoYGKQ==", "requires": { "body-parser": "^1.19.0", "express": "^4.17.1", diff --git a/package.json b/package.json
index 0f651646..b3b2414b 100644 --- a/package.json +++ b/package.json
@@ -29,7 +29,7 @@ }, "homepage": "https://github.com/fosscord/fosscord-api#readme", "dependencies": { - "@fosscord/server-util": "^1.3.9", + "@fosscord/server-util": "^1.3.10", "@types/jest": "^26.0.22", "@types/json-schema": "^7.0.7", "ajv": "^8.4.0", @@ -44,11 +44,12 @@ "env-paths": "^2.2.1", "express": "^4.17.1", "express-validator": "^6.9.2", + "form-data": "^3.0.0", "i18next": "^19.8.5", "i18next-http-middleware": "^3.1.3", "i18next-node-fs-backend": "^2.1.3", "jsonwebtoken": "^8.5.1", - "lambert-server": "^1.2.2", + "lambert-server": "^1.2.3", "missing-native-js-functions": "^1.2.6", "mongodb": "^3.6.5", "mongoose": "^5.12.3", diff --git a/src/Server.ts b/src/Server.ts
index 79a8bba3..0ff4df8c 100644 --- a/src/Server.ts +++ b/src/Server.ts
@@ -97,7 +97,7 @@ export class FosscordServer extends Server { app.use("/api/v8", prefix); this.app = app; this.app.use(ErrorHandler); - const indexHTML = await fs.readFile(path.join(__dirname, "..", "client_test", "index.html")); + const indexHTML = await fs.readFile(path.join(__dirname, "..", "client_test", "index.html"), { encoding: "utf8" }); this.app.use("/assets", express.static(path.join(__dirname, "..", "assets"))); @@ -143,7 +143,12 @@ export class FosscordServer extends Server { this.app.get("*", (req, res) => { res.set("Cache-Control", "public, max-age=" + 60 * 60 * 24); res.set("content-type", "text/html"); - res.send(indexHTML); + res.send( + indexHTML.replace( + /CDN_HOST: ".+"/, + `CDN_HOST: "${(Config.get().cdn.endpoint || "http://localhost:3003").replace(/https?:/, "")}"` + ) + ); }); return super.start(); } diff --git a/src/routes/auth/register.ts b/src/routes/auth/register.ts
index e24485da..50bac43a 100644 --- a/src/routes/auth/register.ts +++ b/src/routes/auth/register.ts
@@ -181,6 +181,7 @@ router.post( premium: false, premium_type: 0, phone: null, + bio: "", mfa_enabled: false, verified: false, disabled: false, diff --git a/src/routes/channels/#channel_id/messages/#message_id/index.ts b/src/routes/channels/#channel_id/messages/#message_id/index.ts
index cfff07d1..aee517fc 100644 --- a/src/routes/channels/#channel_id/messages/#message_id/index.ts +++ b/src/routes/channels/#channel_id/messages/#message_id/index.ts
@@ -45,6 +45,8 @@ router.patch("/", check(MessageCreateSchema), async (req, res) => { return res.json(toObject(message)); }); +// TODO: delete attachments in message + router.delete("/", async (req, res) => { const { message_id, channel_id } = req.params; diff --git a/src/routes/channels/#channel_id/messages/index.ts b/src/routes/channels/#channel_id/messages/index.ts
index cdc46d14..4bf1516d 100644 --- a/src/routes/channels/#channel_id/messages/index.ts +++ b/src/routes/channels/#channel_id/messages/index.ts
@@ -1,24 +1,13 @@ import { Router } from "express"; -import { - ChannelModel, - ChannelType, - getPermission, - Message, - MessageCreateEvent, - MessageDocument, - MessageModel, - Snowflake, - toObject -} from "@fosscord/server-util"; +import { Attachment, ChannelModel, ChannelType, getPermission, MessageDocument, MessageModel, toObject } from "@fosscord/server-util"; import { HTTPError } from "lambert-server"; import { MessageCreateSchema } from "../../../../schema/Message"; import { check, instanceOf, Length } from "../../../../util/instanceOf"; -import { PublicUserProjection } from "../../../../util/User"; import multer from "multer"; -import { emitEvent } from "../../../../util/Event"; import { Query } from "mongoose"; -import { PublicMemberProjection } from "../../../../util/Member"; import { sendMessage } from "../../../../util/Message"; +import { uploadFile } from "../../../../util/cdn"; + const router: Router = Router(); export default router; @@ -93,7 +82,14 @@ router.get("/", async (req, res) => { }); // TODO: config max upload size -const messageUpload = multer({ limits: { fieldSize: 1024 * 1024 * 1024 * 50 } }); // max upload 50 mb +const messageUpload = multer({ + limits: { + fileSize: 1024 * 1024 * 100, + fields: 10, + files: 1 + }, + storage: multer.memoryStorage() +}); // max upload 50 mb // TODO: dynamically change limit of MessageCreateSchema with config // TODO: check: sum of all characters in an embed structure must not exceed 6000 characters @@ -101,14 +97,31 @@ const messageUpload = multer({ limits: { fieldSize: 1024 * 1024 * 1024 * 50 } }) // https://discord.com/developers/docs/resources/channel#create-message // TODO: text channel slowdown // TODO: trim and replace message content and every embed field + // Send message -router.post("/", check(MessageCreateSchema), async (req, res) => { +router.post("/", check(MessageCreateSchema), messageUpload.single("file"), async (req, res) => { const { channel_id } = req.params; - const body = req.body as MessageCreateSchema; + var body = req.body as MessageCreateSchema; + const attachments: Attachment[] = []; + + if (req.file) { + try { + const file = await uploadFile(`/attachments/${channel_id}`, req.file); + attachments.push({ ...file, proxy_url: file.url }); + } catch (error) { + return res.status(400).json(error); + } + } + + if (body.payload_json) { + body = JSON.parse(body.payload_json); + const errors = instanceOf(MessageCreateSchema, body, { req }); + if (errors !== true) throw errors; + } const embeds = []; if (body.embed) embeds.push(body.embed); - const data = await sendMessage({ ...body, type: 0, pinned: false, author_id: req.user_id, embeds, channel_id }); + const data = await sendMessage({ ...body, type: 0, pinned: false, author_id: req.user_id, embeds, channel_id, attachments }); return res.send(data); }); diff --git a/src/routes/users/#id/index.ts b/src/routes/users/#id/index.ts
index 185b2e5f..a2ad3ae6 100644 --- a/src/routes/users/#id/index.ts +++ b/src/routes/users/#id/index.ts
@@ -6,10 +6,8 @@ const router: Router = Router(); router.get("/", async (req: Request, res: Response) => { const { id } = req.params; - const user = await getPublicUser(id); - if (!user) throw new HTTPError("User not found", 404); - res.json(user); + res.json(await getPublicUser(id)); }); export default router; diff --git a/src/routes/users/@me/index.ts b/src/routes/users/@me/index.ts
index d139203d..4f17fbee 100644 --- a/src/routes/users/@me/index.ts +++ b/src/routes/users/@me/index.ts
@@ -2,31 +2,35 @@ import { Router, Request, Response } from "express"; import { UserModel, toObject } from "@fosscord/server-util"; import { HTTPError } from "lambert-server"; import { getPublicUser } from "../../../util/User"; -import { UserModifySchema } from "../../../schema/User" +import { UserModifySchema } from "../../../schema/User"; import { check } from "../../../util/instanceOf"; +import { uploadFile } from "../../../util/cdn"; const router: Router = Router(); router.get("/", async (req: Request, res: Response) => { - const user = await UserModel.findOne({ id: req.user_id }).exec(); - if (!user) throw new HTTPError("User not found", 404); - - var publicUser = await getPublicUser(user.id); - - res.json(publicUser); + res.json(await getPublicUser(req.user_id)); }); router.patch("/", check(UserModifySchema), async (req: Request, res: Response) => { const body = req.body as UserModifySchema; - const user = await UserModel.findOne({ id: req.user_id }).exec(); - if (!user) throw new HTTPError("User not found", 404); + if (body.avatar) { + try { + const mimetype = body.avatar.split(":")[1].split(";")[0]; + const buffer = Buffer.from(body.avatar.split(",")[1], "base64"); + + // @ts-ignore + const { id } = await uploadFile(`/avatars/${req.user_id}`, { buffer, mimetype, originalname: "avatar" }); + body.avatar = id; + } catch (error) { + throw new HTTPError("Invalid avatar"); + } + } - var newuser = await UserModel.findOneAndUpdate({ id: req.user_id }, { - ...body - }).exec(); + const user = await UserModel.findOneAndUpdate({ id: req.user_id }, body).exec(); - res.json(newuser); + res.json(toObject(user)); }); export default router; diff --git a/src/schema/Message.ts b/src/schema/Message.ts
index e6aa42b3..b2e4b1f7 100644 --- a/src/schema/Message.ts +++ b/src/schema/Message.ts
@@ -68,4 +68,5 @@ export interface MessageCreateSchema { fail_if_not_exists: boolean; }; payload_json?: string; + file?: any; } diff --git a/src/util/Message.ts b/src/util/Message.ts
index 27796997..9b928031 100644 --- a/src/util/Message.ts +++ b/src/util/Message.ts
@@ -50,7 +50,7 @@ export async function handleMessage(opts: Partial<Message>) { mention_channels_ids: [], mention_role_ids: [], mention_user_ids: [], - attachments: [], // TODO: message attachments + attachments: opts.attachments || [], // TODO: message attachments embeds: opts.embeds || [], reactions: opts.reactions || [], type: opts.type ?? 0 diff --git a/src/util/cdn.ts b/src/util/cdn.ts new file mode 100644
index 00000000..a66e2215 --- /dev/null +++ b/src/util/cdn.ts
@@ -0,0 +1,24 @@ +import { Config } from "@fosscord/server-util"; +import FormData from "form-data"; +import fetch from "node-fetch"; + +export async function uploadFile(path: string, file: Express.Multer.File) { + const form = new FormData(); + form.append("file", file.buffer, { + contentType: file.mimetype, + filename: file.originalname + }); + + const response = await fetch(`${Config.get().cdn.endpoint || "http://localhost:3003"}${path}`, { + headers: { + signature: Config.get().security.requestSignature, + ...form.getHeaders() + }, + method: "POST", + body: form + }); + const result = await response.json(); + + if (response.status !== 200) throw result; + return result; +} diff --git a/src/util/instanceOf.ts b/src/util/instanceOf.ts
index b67bde27..93a92805 100644 --- a/src/util/instanceOf.ts +++ b/src/util/instanceOf.ts
@@ -74,10 +74,9 @@ export function instanceOf( ): Boolean { if (!ref) ref = { obj: null, key: "" }; if (!path) path = "body"; + if (!type) return true; // no type was specified try { - if (!type) return true; // no type was specified - if (value == null) { if (optional) return true; throw new FieldError("BASE_TYPE_REQUIRED", req.t("common:field.BASE_TYPE_REQUIRED"));