diff --git a/api/src/middlewares/RateLimit.ts b/api/src/middlewares/RateLimit.ts
index e0cf103a..dffbc0d9 100644
--- a/api/src/middlewares/RateLimit.ts
+++ b/api/src/middlewares/RateLimit.ts
@@ -1,11 +1,12 @@
-import { Config, listenEvent, emitEvent, RateLimit } from "@fosscord/util";
+import { Config, listenEvent } from "@fosscord/util";
import { NextFunction, Request, Response, Router } from "express";
-import { LessThan } from "typeorm";
import { getIpAdress } from "../util/ipAddress";
import { API_PREFIX_TRAILING_SLASH } from "./Authentication";
// Docs: https://discord.com/developers/docs/topics/rate-limits
+// TODO: use better caching (e.g. redis) as else it creates to much pressure on the database
+
/*
? bucket limit? Max actions/sec per bucket?
@@ -18,6 +19,14 @@ TODO: different for methods (GET/POST)
*/
+type RateLimit = {
+ id: "global" | "error" | string;
+ executor_id: string;
+ hits: number;
+ blocked: boolean;
+ expires_at: Date;
+};
+
var Cache = new Map<string, RateLimit>();
const EventRateLimit = "RATELIMIT";
@@ -46,13 +55,22 @@ export default function rateLimit(opts: {
const offender = Cache.get(executor_id + bucket_id);
- if (offender && offender.blocked) {
+ if (offender) {
const reset = offender.expires_at.getTime();
const resetAfterMs = reset - Date.now();
const resetAfterSec = resetAfterMs / 1000;
- const global = bucket_id === "global";
- if (resetAfterMs > 0) {
+ if (resetAfterMs <= 0) {
+ offender.hits = 0;
+ offender.expires_at = new Date(Date.now() + opts.window * 1000);
+ offender.blocked = false;
+
+ Cache.delete(executor_id + bucket_id);
+ }
+
+ if (offender.blocked) {
+ const global = bucket_id === "global";
+
console.log("blocked bucket: " + bucket_id, { resetAfterMs });
return (
res
@@ -67,15 +85,9 @@ export default function rateLimit(opts: {
// TODO: error rate limit message translation
.send({ message: "You are being rate limited.", retry_after: resetAfterSec, global })
);
- } else {
- offender.hits = 0;
- offender.expires_at = new Date(Date.now() + opts.window * 1000);
- offender.blocked = false;
- // mongodb ttl didn't update yet -> manually update/delete
- RateLimit.delete({ id: bucket_id, executor_id });
- Cache.delete(executor_id + bucket_id);
}
}
+
next();
const hitRouteOpts = { bucket_id, executor_id, max_hits, window: opts.window };
@@ -100,20 +112,20 @@ export async function initRateLimits(app: Router) {
Cache.set(event.channel_id as string, event.data);
event.acknowledge?.();
});
- await RateLimit.delete({ expires_at: LessThan(new Date()) }); // clean up if not already deleted
- const limits = await RateLimit.find({ blocked: true });
- limits.forEach((limit) => {
- Cache.set(limit.executor_id, limit);
- });
+ // await RateLimit.delete({ expires_at: LessThan(new Date().toISOString()) }); // cleans up if not already deleted, morethan -> older date
+ // const limits = await RateLimit.find({ blocked: true });
+ // limits.forEach((limit) => {
+ // Cache.set(limit.executor_id, limit);
+ // });
setInterval(() => {
Cache.forEach((x, key) => {
if (new Date() > x.expires_at) {
Cache.delete(key);
- RateLimit.delete({ executor_id: key });
+ // RateLimit.delete({ executor_id: key });
}
});
- }, 1000 * 60 * 10);
+ }, 1000 * 60);
app.use(
rateLimit({
@@ -139,6 +151,25 @@ export async function initRateLimits(app: Router) {
}
async function hitRoute(opts: { executor_id: string; bucket_id: string; max_hits: number; window: number }) {
+ const id = opts.executor_id + opts.bucket_id;
+ var limit = Cache.get(id);
+ if (!limit) {
+ limit = {
+ id: opts.bucket_id,
+ executor_id: opts.executor_id,
+ expires_at: new Date(Date.now() + opts.window * 1000),
+ hits: 0,
+ blocked: false
+ };
+ Cache.set(id, limit);
+ }
+
+ limit.hits++;
+ if (limit.hits >= opts.max_hits) {
+ limit.blocked = true;
+ }
+
+ /*
var ratelimit = await RateLimit.findOne({ id: opts.bucket_id, executor_id: opts.executor_id });
if (!ratelimit) {
ratelimit = new RateLimit({
@@ -167,4 +198,5 @@ async function hitRoute(opts: { executor_id: string; bucket_id: string; max_hits
}
await ratelimit.save();
+ */
}
diff --git a/api/src/routes/channels/#channel_id/messages/index.ts b/api/src/routes/channels/#channel_id/messages/index.ts
index 6307c022..17944548 100644
--- a/api/src/routes/channels/#channel_id/messages/index.ts
+++ b/api/src/routes/channels/#channel_id/messages/index.ts
@@ -77,7 +77,7 @@ router.get("/", async (req: Request, res: Response) => {
delete x.user_ids;
});
// @ts-ignore
- if (!x.author) x.author = { discriminator: "0000", username: "Deleted User", public_flags: 0n, avatar: null };
+ if (!x.author) x.author = { discriminator: "0000", username: "Deleted User", public_flags: "0", avatar: null };
return x;
});
diff --git a/api/src/routes/guilds/#guild_id/roles.ts b/api/src/routes/guilds/#guild_id/roles.ts
index e9e777b9..796a8eb8 100644
--- a/api/src/routes/guilds/#guild_id/roles.ts
+++ b/api/src/routes/guilds/#guild_id/roles.ts
@@ -40,7 +40,7 @@ router.post("/", check(RoleModifySchema), async (req: Request, res: Response) =>
managed: false,
position: 0,
tags: null,
- permissions: perms.bitfield & (body.permissions || 0n)
+ permissions: String(perms.bitfield & (body.permissions || 0n))
}).save();
await emitEvent({
diff --git a/api/src/routes/guilds/#guild_id/templates.ts b/api/src/routes/guilds/#guild_id/templates.ts
index e1d2f5fd..a7613abf 100644
--- a/api/src/routes/guilds/#guild_id/templates.ts
+++ b/api/src/routes/guilds/#guild_id/templates.ts
@@ -17,7 +17,7 @@ const TemplateGuildProjection: (keyof Guild)[] = [
"preferred_locale",
"afk_timeout",
"roles",
- "channels",
+ // "channels",
"afk_channel_id",
"system_channel_id",
"system_channel_flags",
diff --git a/api/src/routes/guilds/index.ts b/api/src/routes/guilds/index.ts
index c158c7d4..020aba6a 100644
--- a/api/src/routes/guilds/index.ts
+++ b/api/src/routes/guilds/index.ts
@@ -19,63 +19,55 @@ router.post("/", check(GuildCreateSchema), async (req: Request, res: Response) =
}
const guild_id = Snowflake.generate();
- const guild = new Guild({
- name: body.name,
- region: Config.get().regions.default,
- owner_id: req.user_id,
- icon: undefined,
- afk_channel_id: undefined,
- afk_timeout: 300,
- application_id: undefined,
- banner: undefined,
- default_message_notifications: 0,
- description: undefined,
- splash: undefined,
- discovery_splash: undefined,
- explicit_content_filter: 0,
- features: [],
- id: guild_id,
- large: undefined,
- max_members: 250000,
- max_presences: 250000,
- max_video_channel_users: 25,
- presence_count: 0,
- member_count: 0, // will automatically be increased by addMember()
- mfa_level: 0,
- preferred_locale: "en-US",
- premium_subscription_count: 0,
- premium_tier: 0,
- public_updates_channel_id: undefined,
- rules_channel_id: undefined,
- system_channel_flags: 0,
- system_channel_id: undefined,
- unavailable: false,
- vanity_url_code: undefined,
- verification_level: 0,
- welcome_screen: {
- enabled: false,
- description: "No description",
- welcome_channels: []
- },
- widget_channel_id: undefined,
- widget_enabled: false
- });
-
- const [guild_doc, role] = await Promise.all([
- new Guild(guild).save(),
- new Role({
+ const guild = new Guild(
+ {
+ name: body.name,
+ region: Config.get().regions.default,
+ owner_id: req.user_id,
+ afk_timeout: 300,
+ default_message_notifications: 0,
+ explicit_content_filter: 0,
+ features: [],
id: guild_id,
+ max_members: 250000,
+ max_presences: 250000,
+ max_video_channel_users: 25,
+ presence_count: 0,
+ member_count: 0, // will automatically be increased by addMember()
+ mfa_level: 0,
+ preferred_locale: "en-US",
+ premium_subscription_count: 0,
+ premium_tier: 0,
+ system_channel_flags: "0",
+ unavailable: false,
+ verification_level: 0,
+ welcome_screen: {
+ enabled: false,
+ description: "No description",
+ welcome_channels: []
+ },
+ widget_enabled: false
+ },
+ { id: guild_id }
+ );
+ const role = new Role(
+ {
guild_id: guild_id,
color: 0,
hoist: false,
managed: false,
mentionable: false,
name: "@everyone",
- permissions: 2251804225n,
+ permissions: String("2251804225"),
position: 0,
tags: null
- }).save()
- ]);
+ },
+ {
+ id: guild_id
+ }
+ );
+
+ await Promise.all([guild.save(), role.save()]);
if (!body.channels || !body.channels.length) body.channels = [{ id: "01", type: 0, name: "general" }];
diff --git a/api/src/schema/Guild.ts b/api/src/schema/Guild.ts
index 01690ae9..3e98fe76 100644
--- a/api/src/schema/Guild.ts
+++ b/api/src/schema/Guild.ts
@@ -33,7 +33,7 @@ export const GuildUpdateSchema = {
$icon: String,
$verification_level: Number,
$default_message_notifications: Number,
- $system_channel_flags: Number,
+ $system_channel_flags: String,
$system_channel_id: String,
$explicit_content_filter: Number,
$public_updates_channel_id: String,
diff --git a/bundle/database.db b/bundle/database.db
index 9572c45e..2d4abd49 100644
--- a/bundle/database.db
+++ b/bundle/database.db
Binary files differdiff --git a/gateway/src/opcodes/Identify.ts b/gateway/src/opcodes/Identify.ts
index 5be2acce..87008998 100644
--- a/gateway/src/opcodes/Identify.ts
+++ b/gateway/src/opcodes/Identify.ts
@@ -42,7 +42,7 @@ export async function onIdentify(this: WebSocket, data: Payload) {
}
}
- const members = await Member.find({ where: { id: this.user_id }, relations: ["guilds"] });
+ const members = await Member.find({ where: { id: this.user_id }, relations: ["guild"] });
const merged_members = members.map((x: any) => {
const y = { ...x, user_id: x.id };
delete y.settings;
diff --git a/gateway/src/schema/Activity.ts b/gateway/src/schema/Activity.ts
index 0fd0592f..f1665efd 100644
--- a/gateway/src/schema/Activity.ts
+++ b/gateway/src/schema/Activity.ts
@@ -39,7 +39,7 @@ export const ActivitySchema = {
$match: String,
},
$instance: Boolean,
- $flags: BigInt,
+ $flags: String,
},
],
$since: Number, // unix time (in milliseconds) of when the client went idle, or null if the client is not idle
@@ -79,7 +79,7 @@ export interface ActivitySchema {
match?: string; // the secret for a specific instanced match
};
instance?: boolean;
- flags: bigint; // activity flags OR d together, describes what the payload includes
+ flags: string; // activity flags OR d together, describes what the payload includes
}
];
since?: number; // unix time (in milliseconds) of when the client went idle, or null if the client is not idle
diff --git a/util/src/entities/Application.ts b/util/src/entities/Application.ts
index a87b5cea..b179d171 100644
--- a/util/src/entities/Application.ts
+++ b/util/src/entities/Application.ts
@@ -62,7 +62,7 @@ export class Application extends BaseClass {
cover_image?: string; // the application's default rich presence invite cover image hash
@Column()
- flags: number; // the application's public flags
+ flags: string; // the application's public flags
}
export interface ApplicationCommand {
diff --git a/util/src/entities/BaseClass.ts b/util/src/entities/BaseClass.ts
index 31338ff6..63ce5836 100644
--- a/util/src/entities/BaseClass.ts
+++ b/util/src/entities/BaseClass.ts
@@ -51,7 +51,8 @@ export class BaseClass extends BaseEntity {
if (setter) {
setter.call(this, props[key]);
} else {
- Object.defineProperty(this, key, { value: props[key] });
+ // @ts-ignore
+ this[key] = props[key];
}
}
}
diff --git a/util/src/entities/Emoji.ts b/util/src/entities/Emoji.ts
index 0686d476..4c0fccd3 100644
--- a/util/src/entities/Emoji.ts
+++ b/util/src/entities/Emoji.ts
@@ -15,7 +15,7 @@ export class Emoji extends BaseClass {
guild_id: string;
@JoinColumn({ name: "guild_id" })
- @ManyToOne(() => Guild, (guild: Guild) => guild.id)
+ @ManyToOne(() => Guild, (guild: Guild) => guild.emojis)
guild: Guild;
@Column()
diff --git a/util/src/entities/Guild.ts b/util/src/entities/Guild.ts
index e6a93824..3e7e8917 100644
--- a/util/src/entities/Guild.ts
+++ b/util/src/entities/Guild.ts
@@ -1,4 +1,4 @@
-import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, OneToOne, RelationId } from "typeorm";
+import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, OneToMany, OneToOne, RelationId } from "typeorm";
import { BaseClass } from "./BaseClass";
import { Channel } from "./Channel";
import { Emoji } from "./Emoji";
@@ -68,35 +68,35 @@ export class Guild extends BaseClass {
member_ids: string[];
@JoinColumn({ name: "member_ids" })
- @ManyToMany(() => Member, (member: Member) => member.id)
+ @OneToMany(() => Member, (member: Member) => member.guild)
members: Member[];
@RelationId((guild: Guild) => guild.roles)
role_ids: string[];
@JoinColumn({ name: "role_ids" })
- @ManyToMany(() => Role, (role: Role) => role.id)
+ @OneToMany(() => Role, (role: Role) => role.guild)
roles: Role[];
@RelationId((guild: Guild) => guild.channels)
channel_ids: string[];
@JoinColumn({ name: "channel_ids" })
- @ManyToMany(() => Channel, (channel: Channel) => channel.id)
+ @OneToMany(() => Channel, (channel: Channel) => channel.guild)
channels: Channel[];
@RelationId((guild: Guild) => guild.emojis)
emoji_ids: string[];
@JoinColumn({ name: "emoji_ids" })
- @ManyToMany(() => Emoji, (emoji: Emoji) => emoji.id)
+ @OneToMany(() => Emoji, (emoji: Emoji) => emoji.guild)
emojis: Emoji[];
@RelationId((guild: Guild) => guild.voice_states)
voice_state_ids: string[];
@JoinColumn({ name: "voice_state_ids" })
- @ManyToMany(() => VoiceState, (voicestate: VoiceState) => voicestate.id)
+ @OneToMany(() => VoiceState, (voicestate: VoiceState) => voicestate.guild)
voice_states: VoiceState[];
@Column({ nullable: true })
@@ -109,7 +109,7 @@ export class Guild extends BaseClass {
owner_id: string;
@JoinColumn({ name: "owner_id" })
- @ManyToOne(() => User, (user: User) => user.id)
+ @OneToOne(() => User)
owner: User;
@Column({ nullable: true })
@@ -125,14 +125,14 @@ export class Guild extends BaseClass {
public_updates_channel_id: string;
@JoinColumn({ name: "public_updates_channel_id" })
- @ManyToOne(() => Channel, (channel: Channel) => channel.id)
+ @OneToOne(() => Channel, (channel: Channel) => channel.id)
public_updates_channel?: Channel;
@RelationId((guild: Guild) => guild.rules_channel)
rules_channel_id?: string;
@JoinColumn({ name: "rules_channel_id" })
- @ManyToOne(() => Channel, (channel: Channel) => channel.id)
+ @OneToOne(() => Channel, (channel: Channel) => channel.id)
rules_channel?: string;
@Column({ nullable: true })
@@ -145,7 +145,7 @@ export class Guild extends BaseClass {
system_channel_id?: string;
@JoinColumn({ name: "system_channel_id" })
- @ManyToMany(() => Channel, (channel: Channel) => channel.id)
+ @OneToOne(() => Channel, (channel: Channel) => channel.id)
system_channel?: Channel;
@Column({ nullable: true })
@@ -158,7 +158,7 @@ export class Guild extends BaseClass {
vanity_url_code?: string;
@JoinColumn({ name: "vanity_url_code" })
- @ManyToOne(() => Invite)
+ @OneToOne(() => Invite)
vanity_url?: Invite;
@Column({ nullable: true })
@@ -180,7 +180,7 @@ export class Guild extends BaseClass {
widget_channel_id?: string;
@JoinColumn({ name: "widget_channel_id" })
- @ManyToOne(() => Channel, (channel: Channel) => channel.id)
+ @OneToOne(() => Channel, (channel: Channel) => channel.id)
widget_channel?: Channel;
@Column({ nullable: true })
diff --git a/util/src/entities/Member.ts b/util/src/entities/Member.ts
index 5b588d70..c5d289ef 100644
--- a/util/src/entities/Member.ts
+++ b/util/src/entities/Member.ts
@@ -1,6 +1,6 @@
import { PublicUser, User } from "./User";
import { BaseClass } from "./BaseClass";
-import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, RelationId } from "typeorm";
+import { Column, Entity, JoinColumn, JoinTable, ManyToMany, ManyToOne, OneToMany, RelationId } from "typeorm";
import { Guild } from "./Guild";
import { Config, emitEvent } from "../util";
import {
@@ -26,7 +26,7 @@ export class Member extends BaseClass {
guild_id: string;
@JoinColumn({ name: "guild_id" })
- @ManyToOne(() => Guild, (guild: Guild) => guild.id)
+ @ManyToOne(() => Guild, (guild: Guild) => guild.members)
guild: Guild;
@Column({ nullable: true })
@@ -35,7 +35,7 @@ export class Member extends BaseClass {
@RelationId((member: Member) => member.roles)
role_ids: string[];
- @JoinColumn({ name: "role_ids" })
+ @JoinTable()
@ManyToMany(() => Role)
roles: Role[];
diff --git a/util/src/entities/RateLimit.ts b/util/src/entities/RateLimit.ts
index 49af0416..fa9c32c1 100644
--- a/util/src/entities/RateLimit.ts
+++ b/util/src/entities/RateLimit.ts
@@ -1,6 +1,5 @@
-import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
+import { Column, Entity } from "typeorm";
import { BaseClass } from "./BaseClass";
-import { User } from "./User";
@Entity("rate_limits")
export class RateLimit extends BaseClass {
diff --git a/util/src/entities/Role.ts b/util/src/entities/Role.ts
index ddae7e40..be8c731d 100644
--- a/util/src/entities/Role.ts
+++ b/util/src/entities/Role.ts
@@ -27,8 +27,8 @@ export class Role extends BaseClass {
@Column()
name: string;
- @Column({ type: "bigint" })
- permissions: bigint;
+ @Column()
+ permissions: string;
@Column()
position: number;
diff --git a/util/src/interfaces/Event.ts b/util/src/interfaces/Event.ts
index 0de55f71..814a8beb 100644
--- a/util/src/interfaces/Event.ts
+++ b/util/src/interfaces/Event.ts
@@ -36,7 +36,7 @@ export interface ReadyEventData {
mobile: boolean;
desktop: boolean;
email: string | undefined;
- flags: bigint;
+ flags: string;
mfa_enabled: boolean;
nsfw_allowed: boolean;
phone: string | undefined;
@@ -85,7 +85,7 @@ export interface ReadyEventData {
};
application?: {
id: string;
- flags: bigint;
+ flags: string;
};
merged_members?: Omit<Member, "settings" | "user">[][];
// probably all users who the user is in contact with
@@ -95,7 +95,7 @@ export interface ReadyEventData {
id: string;
username: string;
bot: boolean;
- public_flags: bigint;
+ public_flags: string;
}[];
}
diff --git a/util/src/util/Config.ts b/util/src/util/Config.ts
index f16921bd..508a8901 100644
--- a/util/src/util/Config.ts
+++ b/util/src/util/Config.ts
@@ -7,7 +7,8 @@ var config: ConfigEntity;
export const Config = {
init: async function init() {
if (config) return config;
- config = new ConfigEntity({}, { id: "0" });
+ config = (await ConfigEntity.findOne({ id: "0" })) || new ConfigEntity({}, { id: "0" });
+
return this.set((config.value || {}).merge(DefaultConfigOptions));
},
get: function get() {
diff --git a/util/src/util/Permissions.ts b/util/src/util/Permissions.ts
index 5082d8ea..ab6aa795 100644
--- a/util/src/util/Permissions.ts
+++ b/util/src/util/Permissions.ts
@@ -212,18 +212,15 @@ export async function getPermission(user_id?: string, guild_id?: string, channel
var guild: Guild | undefined;
if (channel_id) {
- channel = await Channel.findOneOrFail(
- { id: channel_id },
- { select: ["permission_overwrites", "recipients", "owner", "guild"] }
- );
+ channel = await Channel.findOneOrFail({ id: channel_id });
if (channel.guild_id) guild_id = channel.guild_id; // derive guild_id from the channel
}
if (guild_id) {
- guild = await Guild.findOneOrFail({ id: guild_id }, { select: ["owner"] });
+ guild = await Guild.findOneOrFail({ id: guild_id });
if (guild.owner_id === user_id) return new Permissions(Permissions.FLAGS.ADMINISTRATOR);
- member = await Member.findOneOrFail({ guild_id, id: user_id }, { select: ["roles"] });
+ member = await Member.findOneOrFail({ where: { guild_id, id: user_id }, relations: ["roles"] });
}
var permission = Permissions.finalPermission({
|