This commit is contained in:
Flam3rboy 2021-04-24 12:49:12 +02:00
commit fe8a1c1000
47 changed files with 530 additions and 773 deletions

1
.npmignore Normal file
View File

@ -0,0 +1 @@
!dist/

View File

@ -1,7 +1,9 @@
# Fosscord API Server # Fosscord API Server
This repository contains the HTTP API Server
This repository contains the Fosscord HTTP API Server
## Bug Tracker ## Bug Tracker
[Project Board](https://github.com/fosscord/fosscord-api/projects/2) [Project Board](https://github.com/fosscord/fosscord-api/projects/2)
## API ## API
@ -10,7 +12,9 @@ We use [express](https://expressjs.com/) for the HTTP Server and
[lambert-server](https://www.npmjs.com/package/lambert-server) for route handling and body validation (customized). [lambert-server](https://www.npmjs.com/package/lambert-server) for route handling and body validation (customized).
## Contribution ## Contribution
You should be familiar with: You should be familiar with:
- [Git](https://git-scm.com/) - [Git](https://git-scm.com/)
- [NodeJS](https://nodejs.org/) - [NodeJS](https://nodejs.org/)
- [TypeScript](https://www.typescriptlang.org/) - [TypeScript](https://www.typescriptlang.org/)
@ -19,21 +23,29 @@ You should be familiar with:
and the other technologies we use and the other technologies we use
### Getting Started ### Getting Started
Clone the Repository: Clone the Repository:
```bash ```bash
git clone https://github.com/fosscord/fosscord-api git clone https://github.com/fosscord/fosscord-api
cd discord-server cd discord-server
``` ```
#### Install (dev)dependencies: #### Install (dev)dependencies:
```bash ```bash
npm install npm install
npm install --only=dev npm install --only=dev
``` ```
#### Starting: #### Starting:
``` ```
npm start npm start
``` ```
#### Debugging: #### Debugging:
**Vscode:** **Vscode:**
The Launch file configuration is in ``./vscode/launch.json``, The Launch file configuration is in `./vscode/launch.json`,
so you can just debug the server by pressing ``F5`` or the ``> Launch Server`` button so you can just debug the server by pressing `F5` or the `> Launch Server` button

830
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,35 +1,42 @@
{ {
"name": "fosscord-api", "name": "@fosscord/api",
"version": "1.0.0", "version": "1.0.0",
"description": "This repository contains the HTTP API Server", "description": "This repository contains the HTTP API Server",
"main": "index.js", "main": "dist/Server.js",
"types": "dist/Server.d.ts",
"scripts": { "scripts": {
"test": "jest", "test": "jest",
"test:watch": "jest --watch", "test:watch": "jest --watch",
"start": "npm run build:util && npm run build && node dist/", "start": "npm run build:util && npm run build && node dist/start",
"build": "tsc -b .", "build": "tsc -b .",
"build:util": "tsc -b ./node_modules/fosscord-server-util/", "build:util": "tsc -b ./node_modules/@fosscord/server-util/",
"postinstall": "npm i github:fosscord/fosscord-server-util && patch-package" "postinstall": "patch-package"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/fosscord/fosscord-api.git" "url": "git+https://github.com/fosscord/fosscord-api.git"
}, },
"keywords": [], "keywords": [
"author": "", "discord",
"fosscord",
"fosscord-api",
"discord open source",
"discord-open-source"
],
"author": "Fosscord",
"license": "ISC", "license": "ISC",
"bugs": { "bugs": {
"url": "https://github.com/fosscord/fosscord-api/issues" "url": "https://github.com/fosscord/fosscord-api/issues"
}, },
"homepage": "https://github.com/fosscord/fosscord-api#readme", "homepage": "https://github.com/fosscord/fosscord-api#readme",
"dependencies": { "dependencies": {
"@fosscord/server-util": "^1.0.4",
"@types/jest": "^26.0.22", "@types/jest": "^26.0.22",
"bcrypt": "^5.0.0", "bcrypt": "^5.0.0",
"body-parser": "^1.19.0", "body-parser": "^1.19.0",
"dotenv": "^8.2.0", "dotenv": "^8.2.0",
"express": "^4.17.1", "express": "^4.17.1",
"express-validator": "^6.9.2", "express-validator": "^6.9.2",
"fosscord-server-util": "github:fosscord/fosscord-server-util",
"i18next": "^19.8.5", "i18next": "^19.8.5",
"i18next-http-middleware": "^3.1.0", "i18next-http-middleware": "^3.1.0",
"i18next-node-fs-backend": "^2.1.3", "i18next-node-fs-backend": "^2.1.3",

View File

@ -4,7 +4,7 @@ import { Connection } from "mongoose";
import { Server, ServerOptions } from "lambert-server"; import { Server, ServerOptions } from "lambert-server";
import { Authentication, GlobalRateLimit } from "./middlewares/"; import { Authentication, GlobalRateLimit } from "./middlewares/";
import Config from "./util/Config"; import Config from "./util/Config";
import { db } from "fosscord-server-util"; import { db } from "@fosscord/server-util";
import i18next from "i18next"; import i18next from "i18next";
import i18nextMiddleware, { I18next } from "i18next-http-middleware"; import i18nextMiddleware, { I18next } from "i18next-http-middleware";
import i18nextBackend from "i18next-node-fs-backend"; import i18nextBackend from "i18next-node-fs-backend";
@ -13,21 +13,21 @@ import { BodyParser } from "./middlewares/BodyParser";
import { Router } from "express"; import { Router } from "express";
import fetch from "node-fetch"; import fetch from "node-fetch";
export interface DiscordServerOptions extends ServerOptions {} export interface FosscordServerOptions extends ServerOptions {}
declare global { declare global {
namespace Express { namespace Express {
interface Request { interface Request {
// @ts-ignore // @ts-ignore
server: DiscordServer; server: FosscordServer;
} }
} }
} }
export class DiscordServer extends Server { export class FosscordServer extends Server {
public options: DiscordServerOptions; public options: FosscordServerOptions;
constructor(opts?: Partial<DiscordServerOptions>) { constructor(opts?: Partial<FosscordServerOptions>) {
// @ts-ignore // @ts-ignore
super({ ...opts, errorHandler: false, jsonBody: false }); super({ ...opts, errorHandler: false, jsonBody: false });
} }

View File

@ -1,17 +1,19 @@
process.on("uncaughtException", console.error); export * from "./Server";
process.on("unhandledRejection", console.error); export * from "./middlewares/";
export * from "./schema/Ban";
import "missing-native-js-functions"; export * from "./schema/Channel";
import { config } from "dotenv"; export * from "./schema/Guild";
config(); export * from "./schema/Invite";
import { DiscordServer } from "./Server"; export * from "./schema/Message";
export * from "./util/Captcha";
var port = Number(process.env.PORT); export * from "./util/Config";
if (isNaN(port)) port = 1000; export * from "./util/Constants";
export * from "./util/Event";
const server = new DiscordServer({ port }); export * from "./util/instanceOf";
server.start().catch(console.error); export * from "./util/Event";
export * from "./util/instanceOf";
// @ts-ignore export * from "./util/Member";
global.server = server; export * from "./util/RandomInviteID";
export default server; export * from "./util/String";
export * from "./util/User";
export { check as checkPassword } from "./util/passwordStrength";

View File

@ -1,6 +1,6 @@
import { NextFunction, Request, Response } from "express"; import { NextFunction, Request, Response } from "express";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";
import { checkToken } from "fosscord-server-util"; import { checkToken } from "@fosscord/server-util";
export const NO_AUTHORIZATION_ROUTES = [ export const NO_AUTHORIZATION_ROUTES = [
"/api/v8/auth/login", "/api/v8/auth/login",

View File

@ -1,4 +1,6 @@
import { Authentication } from "./Authentication"; export * from "./GlobalRateLimit";
import { GlobalRateLimit } from "./GlobalRateLimit"; export * from "./Authentication";
export * from "./BodyParser";
export { Authentication, GlobalRateLimit }; export * from "./CORS";
export * from "./ErrorHandler";
export * from "./RateLimit";

View File

@ -2,7 +2,7 @@ import { Request, Response, Router } from "express";
import { check, FieldErrors, Length } from "../../util/instanceOf"; import { check, FieldErrors, Length } from "../../util/instanceOf";
import bcrypt from "bcrypt"; import bcrypt from "bcrypt";
import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
import { User, UserModel } from "fosscord-server-util"; import { UserModel } from "@fosscord/server-util";
import Config from "../../util/Config"; import Config from "../../util/Config";
import { adjustEmail } from "./register"; import { adjustEmail } from "./register";

View File

@ -1,6 +1,6 @@
import { Request, Response, Router } from "express"; import { Request, Response, Router } from "express";
import Config from "../../util/Config"; import Config from "../../util/Config";
import { trimSpecial, User, Snowflake, UserModel } from "fosscord-server-util"; import { trimSpecial, User, Snowflake, UserModel } from "@fosscord/server-util";
import bcrypt from "bcrypt"; import bcrypt from "bcrypt";
import { check, Email, EMAIL_REGEX, FieldErrors, Length } from "../../util/instanceOf"; import { check, Email, EMAIL_REGEX, FieldErrors, Length } from "../../util/instanceOf";
import "missing-native-js-functions"; import "missing-native-js-functions";

View File

@ -1,4 +1,14 @@
import { Router } from "express"; import { Router } from "express";
const router: Router = Router(); const router: Router = Router();
// TODO:
export default router; export default router;
/**
*
* @param {"webhook_channel_id":"754001514330062952"}
*
* Creates a WebHook in the channel and returns the id of it
*
* @returns {"channel_id": "816382962056560690", "webhook_id": "834910735095037962"}
*/

View File

@ -1,4 +1,30 @@
import { ChannelModel, getPermission, toObject } from "@fosscord/server-util";
import { Router } from "express"; import { Router } from "express";
import { HTTPError } from "lambert-server";
const router: Router = Router(); const router: Router = Router();
// TODO: delete channel
// TODO: Get channel
router.delete("/", async(req,res)=>{
const {channel_id} = req.params
const channel = await ChannelModel.findOne({ id: channel_id }, { guild_id: true, type: true, permission_overwrites: true }).exec();
if (!channel) throw new HTTPError("Channel not found", 404);
if (channel.guild_id) {
const permission = await getPermission(req.user_id, channel.guild_id)
permission.hasThrow("MANAGE_CHANNELS")
// TODO Channel Update Gateway event will fire for each of them
await ChannelModel.updateMany({parent_id: channel_id}, {$set: {channel_id: null}}).exec()
await ChannelModel.deleteOne({id: channel_id})
}
// TODO: Dm channel "close" not delete
const data = toObject(channel);
//TODO: Reload channel list if request successful
res.send(data)
})
export default router; export default router;

View File

@ -7,7 +7,7 @@ import { emitEvent } from "../../../util/Event";
import { InviteCreateSchema } from "../../../schema/Invite"; import { InviteCreateSchema } from "../../../schema/Invite";
import { getPermission, ChannelModel, InviteModel, InviteCreateEvent, toObject } from "fosscord-server-util"; import { getPermission, ChannelModel, InviteModel, InviteCreateEvent, toObject } from "@fosscord/server-util";
const router: Router = Router(); const router: Router = Router();
@ -22,10 +22,7 @@ router.post("/", check(InviteCreateSchema), async (req: Request, res: Response)
const { guild_id } = channel; const { guild_id } = channel;
const permission = await getPermission(user_id, guild_id); const permission = await getPermission(user_id, guild_id);
permission.hasThrow("CREATE_INSTANT_INVITE");
if (!permission.has("CREATE_INSTANT_INVITE")) {
throw new HTTPError("You aren't authorised to access this endpoint", 401);
}
const invite = { const invite = {
code: random(), code: random(),
@ -55,10 +52,7 @@ router.get("/", async (req: Request, res: Response) => {
} }
const { guild_id } = channel; const { guild_id } = channel;
const permission = await getPermission(user_id, guild_id); const permission = await getPermission(user_id, guild_id);
permission.hasThrow("MANAGE_CHANNELS");
if (!permission.has("MANAGE_CHANNELS")) {
throw new HTTPError("You aren't authorised to access this endpoint", 401);
}
const invites = await InviteModel.find({ guild_id }).exec(); const invites = await InviteModel.find({ guild_id }).exec();

View File

@ -0,0 +1,8 @@
import { Router } from "express";
const router = Router();
// TODO:
// router.post("/", (req, res) => {});
export default router;

View File

@ -0,0 +1,6 @@
import { Router } from "express";
const router = Router();
// TODO:
export default router;

View File

@ -0,0 +1,6 @@
import { Router } from "express";
const router = Router();
// TODO:
export default router;

View File

@ -1,5 +1,5 @@
import { Router } from "express"; import { Router } from "express";
import { ChannelModel, getPermission, MessageDeleteBulkEvent, MessageModel } from "fosscord-server-util"; import { ChannelModel, getPermission, MessageDeleteBulkEvent, MessageModel } from "@fosscord/server-util";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";
import Config from "../../../../util/Config"; import Config from "../../../../util/Config";
import { emitEvent } from "../../../../util/Event"; import { emitEvent } from "../../../../util/Event";
@ -13,12 +13,12 @@ export default router;
// TODO: should this request fail, if you provide messages older than 14 days/invalid ids? // TODO: should this request fail, if you provide messages older than 14 days/invalid ids?
// https://discord.com/developers/docs/resources/channel#bulk-delete-messages // https://discord.com/developers/docs/resources/channel#bulk-delete-messages
router.post("/", check({ messages: [String] }), async (req, res) => { router.post("/", check({ messages: [String] }), async (req, res) => {
const channel_id = req.params.channel_id const channel_id = req.params.channel_id;
const channel = await ChannelModel.findOne({ id: channel_id }, { permission_overwrites: true, guild_id: true }).exec(); const channel = await ChannelModel.findOne({ id: channel_id }, { permission_overwrites: true, guild_id: true }).exec();
if (!channel?.guild_id) throw new HTTPError("Can't bulk delete dm channel messages", 400); if (!channel?.guild_id) throw new HTTPError("Can't bulk delete dm channel messages", 400);
const permission = await getPermission(req.user_id, channel?.guild_id, channel_id, { channel }); const permission = await getPermission(req.user_id, channel?.guild_id, channel_id, { channel });
if (!permission.has("MANAGE_MESSAGES")) throw new HTTPError("You are missing the MANAGE_MESSAGES permissions"); permission.hasThrow("MANAGE_MESSAGES");
const { maxBulkDelete } = Config.get().limits.message; const { maxBulkDelete } = Config.get().limits.message;

View File

@ -9,7 +9,7 @@ import {
MessageModel, MessageModel,
Snowflake, Snowflake,
toObject, toObject,
} from "fosscord-server-util"; } from "@fosscord/server-util";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";
import { MessageCreateSchema } from "../../../../schema/Message"; import { MessageCreateSchema } from "../../../../schema/Message";
import { check, instanceOf, Length } from "../../../../util/instanceOf"; import { check, instanceOf, Length } from "../../../../util/instanceOf";
@ -22,7 +22,7 @@ const router: Router = Router();
export default router; export default router;
function isTextChannel(type: ChannelType): boolean { export function isTextChannel(type: ChannelType): boolean {
switch (type) { switch (type) {
case ChannelType.GUILD_VOICE: case ChannelType.GUILD_VOICE:
case ChannelType.GUILD_CATEGORY: case ChannelType.GUILD_CATEGORY:
@ -62,7 +62,8 @@ router.get("/", async (req, res) => {
if (channel.guild_id) { if (channel.guild_id) {
const permissions = await getPermission(req.user_id, channel.guild_id, channel_id, { channel }); const permissions = await getPermission(req.user_id, channel.guild_id, channel_id, { channel });
if (!permissions.has("VIEW_CHANNEL")) throw new HTTPError("You don't have permission to view this channel", 401); permissions.hasThrow("VIEW_CHANNEL");
if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json([]); if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json([]);
} else if (channel.recipients) { } else if (channel.recipients) {
// group/dm channel // group/dm channel
@ -106,11 +107,10 @@ router.post("/", check(MessageCreateSchema), async (req, res) => {
if (channel.guild_id) { if (channel.guild_id) {
const permissions = await getPermission(req.user_id, channel.guild_id, channel_id, { channel }); const permissions = await getPermission(req.user_id, channel.guild_id, channel_id, { channel });
if (!permissions.has("SEND_MESSAGES")) throw new HTTPError("You don't have the SEND_MESSAGES permission"); permissions.hasThrow("SEND_MESSAGES");
if (body.tts && !permissions.has("SEND_TTS_MESSAGES")) throw new HTTPError("You are missing the SEND_TTS_MESSAGES permission"); if (body.tts) permissions.hasThrow("SEND_TTS_MESSAGES");
if (body.message_reference) { if (body.message_reference) {
if (!permissions.has("READ_MESSAGE_HISTORY")) permissions.hasThrow("READ_MESSAGE_HISTORY");
throw new HTTPError("You are missing the READ_MESSAGE_HISTORY permission to reply");
if (body.message_reference.guild_id !== channel.guild_id) if (body.message_reference.guild_id !== channel.guild_id)
throw new HTTPError("You can only reference messages from this guild"); throw new HTTPError("You can only reference messages from this guild");
} }

View File

@ -1,4 +1,5 @@
import { Router } from "express"; import { Router } from "express";
const router: Router = Router(); const router: Router = Router();
// TODO:
export default router; export default router;

View File

@ -1,4 +1,5 @@
import { Router } from "express"; import { Router } from "express";
const router: Router = Router(); const router: Router = Router();
// TODO:
export default router; export default router;

View File

@ -1,4 +1,5 @@
import { Router } from "express"; import { Router } from "express";
const router: Router = Router(); const router: Router = Router();
// TODO:
export default router; export default router;

View File

@ -1,4 +1,5 @@
import { Router } from "express"; import { Router } from "express";
const router: Router = Router(); const router: Router = Router();
// TODO:
export default router; export default router;

View File

@ -1,4 +1,27 @@
import { Router } from "express"; import { Router } from "express";
import { check, Length } from "../../../util/instanceOf";
import { ChannelModel, getPermission, trimSpecial } from "@fosscord/server-util";
import { HTTPError } from "lambert-server";
import { isTextChannel } from "./messages/index";
const router: Router = Router(); const router: Router = Router();
// TODO:
// TODO: use Image Data Type for avatar instead of String
router.post("/", check({ name: new Length(String, 1, 80), $avatar: String }), async (req, res) => {
const channel_id = req.params.channel_id;
const channel = await ChannelModel.findOne({ id: channel_id }, { guild_id: true, type: true }).exec();
if (!channel) throw new HTTPError("Channel not found", 404);
isTextChannel(channel.type);
if (!channel.guild_id) throw new HTTPError("Not a guild channel", 400);
const permission = await getPermission(req.user_id, channel.guild_id);
permission.hasThrow("MANAGE_WEBHOOKS");
var { avatar, name } = req.body as { name: string; avatar?: string };
name = trimSpecial(name);
if (name === "clyde") throw new HTTPError("Invalid name", 400);
});
export default router; export default router;

View File

@ -1,5 +1,5 @@
import { Request, Response, Router } from "express"; import { Request, Response, Router } from "express";
import { BanModel, getPermission, GuildBanAddEvent, GuildBanRemoveEvent, GuildModel, toObject } from "fosscord-server-util"; import { BanModel, getPermission, GuildBanAddEvent, GuildBanRemoveEvent, GuildModel, toObject } from "@fosscord/server-util";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";
import { getIpAdress } from "../../../middlewares/GlobalRateLimit"; import { getIpAdress } from "../../../middlewares/GlobalRateLimit";
import { BanCreateSchema } from "../../../schema/Ban"; import { BanCreateSchema } from "../../../schema/Ban";
@ -35,7 +35,7 @@ router.post("/:user_id", check(BanCreateSchema), async (req: Request, res: Respo
const banned_user = await getPublicUser(banned_user_id); const banned_user = await getPublicUser(banned_user_id);
const perms = await getPermission(req.user_id, guild_id); const perms = await getPermission(req.user_id, guild_id);
if (!perms.has("BAN_MEMBERS")) throw new HTTPError("You don't have the permission to ban members", 403); perms.hasThrow("BAN_MEMBERS");
if (req.user_id === banned_user_id) throw new HTTPError("You can't ban yourself", 400); if (req.user_id === banned_user_id) throw new HTTPError("You can't ban yourself", 400);
await removeMember(banned_user_id, guild_id); await removeMember(banned_user_id, guild_id);
@ -69,9 +69,7 @@ router.delete("/:user_id", async (req: Request, res: Response) => {
if (!guild) throw new HTTPError("Guild not found", 404); if (!guild) throw new HTTPError("Guild not found", 404);
const perms = await getPermission(req.user_id, guild_id); const perms = await getPermission(req.user_id, guild_id);
if (!perms.has("BAN_MEMBERS")) { perms.hasThrow("BAN_MEMBERS");
throw new HTTPError("No permissions", 403);
}
await BanModel.deleteOne({ await BanModel.deleteOne({
user_id: banned_user_id, user_id: banned_user_id,

View File

@ -1,5 +1,5 @@
import { Router } from "express"; import { Router } from "express";
import { ChannelCreateEvent, ChannelModel, ChannelType, GuildModel, Snowflake, toObject } from "fosscord-server-util"; import { ChannelCreateEvent, ChannelModel, ChannelType, GuildModel, Snowflake, toObject } from "@fosscord/server-util";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";
import { ChannelModifySchema } from "../../../schema/Channel"; import { ChannelModifySchema } from "../../../schema/Channel";
import { emitEvent } from "../../../util/Event"; import { emitEvent } from "../../../util/Event";

View File

@ -12,7 +12,7 @@ import {
RoleModel, RoleModel,
toObject, toObject,
UserModel, UserModel,
} from "fosscord-server-util"; } from "@fosscord/server-util";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";
import { GuildUpdateSchema } from "../../../schema/Guild"; import { GuildUpdateSchema } from "../../../schema/Guild";
import { emitEvent } from "../../../util/Event"; import { emitEvent } from "../../../util/Event";
@ -41,7 +41,7 @@ router.patch("/", check(GuildUpdateSchema), async (req: Request, res: Response)
// TODO: guild update check image // TODO: guild update check image
const perms = await getPermission(req.user_id, guild_id); const perms = await getPermission(req.user_id, guild_id);
if (!perms.has("MANAGE_GUILD")) throw new HTTPError("You do not have the MANAGE_GUILD permission", 401); perms.hasThrow("MANAGE_GUILD");
const guild = await GuildModel.findOneAndUpdate({ id: guild_id }, body) const guild = await GuildModel.findOneAndUpdate({ id: guild_id }, body)
.populate({ path: "joined_at", match: { id: req.user_id } }) .populate({ path: "joined_at", match: { id: req.user_id } })

View File

@ -1,5 +1,5 @@
import { Request, Response, Router } from "express"; import { Request, Response, Router } from "express";
import { GuildModel, MemberModel, toObject } from "fosscord-server-util"; import { GuildModel, MemberModel, toObject } from "@fosscord/server-util";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";
import { instanceOf, Length } from "../../../util/instanceOf"; import { instanceOf, Length } from "../../../util/instanceOf";
import { PublicMemberProjection } from "../../../util/Member"; import { PublicMemberProjection } from "../../../util/Member";

View File

@ -1,5 +1,5 @@
import { Router, Request, Response } from "express"; import { Router, Request, Response } from "express";
import { RoleModel, GuildModel, Snowflake, Guild } from "fosscord-server-util"; import { RoleModel, GuildModel, Snowflake, Guild } from "@fosscord/server-util";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";
import { check } from "./../../util/instanceOf"; import { check } from "./../../util/instanceOf";
import { GuildCreateSchema } from "../../schema/Guild"; import { GuildCreateSchema } from "../../schema/Guild";
@ -9,6 +9,8 @@ import { addMember } from "../../util/Member";
const router: Router = Router(); const router: Router = Router();
//TODO: create default channel
router.post("/", check(GuildCreateSchema), async (req: Request, res: Response) => { router.post("/", check(GuildCreateSchema), async (req: Request, res: Response) => {
const body = req.body as GuildCreateSchema; const body = req.body as GuildCreateSchema;

View File

@ -1,5 +1,5 @@
import { Router, Request, Response } from "express"; import { Router, Request, Response } from "express";
import { getPermission, InviteModel, toObject } from "fosscord-server-util"; import { getPermission, InviteModel, toObject } from "@fosscord/server-util";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";
const router: Router = Router(); const router: Router = Router();
@ -21,7 +21,8 @@ router.delete("/:invite_code", async (req: Request, res: Response) => {
const { guild_id, channel_id } = invite; const { guild_id, channel_id } = invite;
const perms = await getPermission(req.user_id, guild_id, channel_id); const perms = await getPermission(req.user_id, guild_id, channel_id);
if (!perms.has("MANAGE_GUILD") || !perms.has("MANAGE_CHANNELS")) throw new HTTPError("You aren't allow", 401); if (!perms.has("MANAGE_GUILD") && !perms.has("MANAGE_CHANNELS"))
throw new HTTPError("You missing the MANAGE_GUILD or MANAGE_CHANNELS permission", 401);
await InviteModel.deleteOne({ code }).exec(); await InviteModel.deleteOne({ code }).exec();

View File

@ -0,0 +1,19 @@
import { Router, Request, Response } from "express";
import { UserModel, toObject } from "@fosscord/server-util";
import { getPublicUser } from "../../../util/User";
import { HTTPError } from "lambert-server";
import { UserUpdateSchema } from "../../../schema/User";
import { check } from "../../../util/instanceOf";
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);
});
export default router;

View File

@ -0,0 +1,69 @@
import {
Router,
Request,
Response
} from "express";
import {
ChannelModel,
ChannelCreateEvent,
DMChannel,
UserModel,
toObject,
ChannelType,
Snowflake
} from "@fosscord/server-util";
import {
HTTPError
} from "lambert-server";
import {
emitEvent
} from "../../../util/Event";
import {
getPublicUser
} from "../../../util/User";
import {
DmChannelCreateSchema
} from "../../../schema/Channel";
import {
check
} from "../../../util/instanceOf";
const router: Router = Router();
router.get("/", async (req: Request, res: Response) => {
const user = await UserModel.findOne({
id: req.user_id
}, {
guilds: true
}).exec();
if (!user) throw new HTTPError("User not found", 404);
var testID = "829044530203328513"; //FOR TEST
var channels = await ChannelModel.find({
recipients: req.user_id,
type: 1
}).exec();
res.json(toObject(channels));
});
router.post("/", check(DmChannelCreateSchema), async (req, res) => {
const body = req.body as DmChannelCreateSchema;
const channel = {
...body,
owner_id: req.user_id,
id: Snowflake.generate(),
type: ChannelType.DM,
created_at: new Date(),
};
await new ChannelModel(channel).save();
/*Event({ event: "CHANNEL_CREATE", data: channel } as ChannelCreateEvent);*/
res.json(channel);
});
export default router;

View File

@ -1,5 +1,5 @@
import { Router, Request, Response } from "express"; import { Router, Request, Response } from "express";
import { GuildModel, MemberModel, UserModel, GuildDeleteEvent, GuildMemberRemoveEvent, toObject } from "fosscord-server-util"; import { GuildModel, MemberModel, UserModel, GuildDeleteEvent, GuildMemberRemoveEvent, toObject } from "@fosscord/server-util";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";
import { emitEvent } from "../../../util/Event"; import { emitEvent } from "../../../util/Event";
import { getPublicUser } from "../../../util/User"; import { getPublicUser } from "../../../util/User";

View File

@ -1,5 +1,5 @@
import { Router, Request, Response } from "express"; import { Router, Request, Response } from "express";
import { UserModel } from "fosscord-server-util"; import { UserModel } from "@fosscord/server-util";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";
const router: Router = Router(); const router: Router = Router();

View File

@ -1,3 +1,4 @@
import { ChannelType } from "@fosscord/server-util";
import { Length } from "../util/instanceOf"; import { Length } from "../util/instanceOf";
export const ChannelModifySchema = { export const ChannelModifySchema = {
@ -20,6 +21,24 @@ export const ChannelModifySchema = {
$nsfw: Boolean, $nsfw: Boolean,
}; };
export const DmChannelCreateSchema = {
owner_id: String,
$id: String,
$created_at: Date,
name: String,
type: Number,
recipients: [String]
}
export interface DmChannelCreateSchema {
owner_id: String;
id?: String;
created_at?: Date;
name: String;
type: Number;
recipients: String[];
}
export interface ChannelModifySchema { export interface ChannelModifySchema {
name: string; name: string;
type: number; type: number;

View File

@ -1,4 +1,4 @@
import { ChannelSchema, GuildChannel } from "fosscord-server-util"; import { ChannelSchema, GuildChannel } from "@fosscord/server-util";
import { Length } from "../util/instanceOf"; import { Length } from "../util/instanceOf";
export const GuildCreateSchema = { export const GuildCreateSchema = {

View File

@ -1,4 +1,4 @@
import { Embed, EmbedImage } from "fosscord-server-util"; import { Embed, EmbedImage } from "@fosscord/server-util";
import { Length } from "../util/instanceOf"; import { Length } from "../util/instanceOf";
export const MessageCreateSchema = { export const MessageCreateSchema = {

43
src/schema/User.ts Normal file
View File

@ -0,0 +1,43 @@
export const UserUpdateSchema = {
id: String,
username: String,
discriminator: String,
avatar: String || null,
$phone: String,
desktop: Boolean,
mobile: Boolean,
premium: Boolean,
premium_type: Number,
bot: Boolean,
system: Boolean,
nsfw_allowed: Boolean,
mfa_enabled: Boolean,
created_at: Date,
verified: Boolean,
$email: String,
flags: BigInt,
public_flags: BigInt,
$guilds: [String],
};
export interface UserUpdateSchema {
id: string;
username: string;
discriminator: string;
avatar: string | null;
phone?: string;
desktop: boolean;
mobile: boolean;
premium: boolean;
premium_type: number;
bot: boolean;
system: boolean;
nsfw_allowed: boolean;
mfa_enabled: boolean;
created_at: Date;
verified: boolean;
email?: string;
flags: bigint;
public_flags: bigint;
guilds: string[];
}

33
src/start.ts Normal file
View File

@ -0,0 +1,33 @@
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";
const cores = os.cpus().length;
if (cluster.isMaster && process.env.production == "true") {
console.log(`Primary ${process.pid} is running`);
// Fork workers.
for (let i = 0; i < cores; i++) {
cluster.fork();
}
cluster.on("exit", (worker, code, signal) => {
console.log(`worker ${worker.process.pid} died, restart worker`);
cluster.fork();
});
} else {
var port = Number(process.env.PORT);
if (isNaN(port)) port = 1000;
const server = new FosscordServer({ port });
server.start().catch(console.error);
// @ts-ignore
global.server = server;
}

View File

@ -1,4 +1,4 @@
import { getPermission } from "fosscord-server-util"; import { getPermission } from "@fosscord/server-util";
async function main() { async function main() {
const t = await getPermission("811642917432066048", "812327318532915201"); const t = await getPermission("811642917432066048", "812327318532915201");

View File

@ -0,0 +1 @@
export {};

View File

@ -1,4 +1,4 @@
import { Config, Snowflake } from "fosscord-server-util"; import { Config, Snowflake } from "@fosscord/server-util";
import crypto from "crypto"; import crypto from "crypto";
import fs from "fs"; import fs from "fs";
@ -16,7 +16,7 @@ export default {
setAll: Config.setAll, setAll: Config.setAll,
}; };
export interface RateLimit { export interface RateLimitOptions {
count: number; count: number;
timespan: number; timespan: number;
} }
@ -62,8 +62,8 @@ export interface DefaultOptions {
}; };
routes: { routes: {
auth?: { auth?: {
login?: RateLimit; login?: RateLimitOptions;
register?: RateLimit; register?: RateLimitOptions;
}; };
channel?: {}; channel?: {};
// TODO: rate limit configuration for all routes // TODO: rate limit configuration for all routes

View File

@ -1,4 +1,4 @@
import { Event, EventModel } from "fosscord-server-util"; import { Event, EventModel } from "@fosscord/server-util";
export async function emitEvent(payload: Omit<Event, "created_at">) { export async function emitEvent(payload: Omit<Event, "created_at">) {
const obj = { const obj = {

View File

@ -7,7 +7,7 @@ import {
GuildModel, GuildModel,
MemberModel, MemberModel,
UserModel, UserModel,
} from "fosscord-server-util"; } from "@fosscord/server-util";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";
import Config from "./Config"; import Config from "./Config";
import { emitEvent } from "./Event"; import { emitEvent } from "./Event";

View File

@ -1,4 +1,4 @@
import { toObject, UserModel } from "fosscord-server-util"; import { toObject, UserModel } from "@fosscord/server-util";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";
export const PublicUserProjection = { export const PublicUserProjection = {

View File

@ -34,6 +34,9 @@ export function FieldErrors(fields: Record<string, { code?: string; message: str
); );
} }
// TODO: implement Image data type: Data URI scheme that supports JPG, GIF, and PNG formats. An example Data URI format is: data:image/jpeg;base64,BASE64_ENCODED_JPEG_IMAGE_DATA
// Ensure you use the proper content type (image/jpeg, image/png, image/gif) that matches the image data being provided.
export class FieldError extends Error { export class FieldError extends Error {
constructor(public code: string | number, public message: string, public errors?: any) { constructor(public code: string | number, public message: string, public errors?: any) {
super(message); super(message);

View File

@ -1,5 +1,5 @@
{ {
"include": ["src/**/*.ts"], "include": ["src/**/*.ts", "src/test/rethink_test.ts.disabled"],
"compilerOptions": { "compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */ /* Visit https://aka.ms/tsconfig.json to read more about this file */