summary refs log tree commit diff
path: root/util/src
diff options
context:
space:
mode:
authoruurgothat <cckhmck@gmail.com>2021-10-17 21:49:46 +0300
committeruurgothat <cckhmck@gmail.com>2021-10-17 21:49:46 +0300
commitdb3d6e116a3b49669103abdf5f2d156b2c78c3c4 (patch)
tree1a1a5f4d03ca5d0b94ca5f47456e7cd536ba21e7 /util/src
parentMerge branch 'master' of https://github.com/fosscord/fosscord-server (diff)
parentUpdate README.md (diff)
downloadserver-db3d6e116a3b49669103abdf5f2d156b2c78c3c4.tar.xz
Merge branch 'master' of https://github.com/fosscord/fosscord-server
Diffstat (limited to 'util/src')
-rw-r--r--util/src/entities/AuditLog.ts5
-rw-r--r--util/src/entities/BaseClass.ts57
-rw-r--r--util/src/entities/Channel.ts2
-rw-r--r--util/src/entities/Config.ts23
-rw-r--r--util/src/entities/Emoji.ts14
-rw-r--r--util/src/entities/Guild.ts8
-rw-r--r--util/src/entities/Invite.ts11
-rw-r--r--util/src/entities/Member.ts21
-rw-r--r--util/src/entities/Message.ts7
-rw-r--r--util/src/entities/Migration.ts18
-rw-r--r--util/src/entities/RateLimit.ts3
-rw-r--r--util/src/entities/ReadState.ts5
-rw-r--r--util/src/entities/Relationship.ts2
-rw-r--r--util/src/entities/Session.ts20
-rw-r--r--util/src/entities/Sticker.ts36
-rw-r--r--util/src/entities/StickerPack.ts31
-rw-r--r--util/src/entities/TeamMember.ts2
-rw-r--r--util/src/entities/User.ts25
-rw-r--r--util/src/entities/Webhook.ts5
-rw-r--r--util/src/entities/index.ts2
-rw-r--r--util/src/index.ts6
-rw-r--r--util/src/interfaces/Activity.ts37
-rw-r--r--util/src/interfaces/Event.ts55
-rw-r--r--util/src/interfaces/Presence.ts4
-rw-r--r--util/src/migrations/1633864260873-EmojiRoles.ts13
-rw-r--r--util/src/migrations/1633864669243-EmojiUser.ts23
-rw-r--r--util/src/migrations/1633881705509-VanityInvite.ts19
-rw-r--r--util/src/migrations/1634308884591-Stickers.ts66
-rw-r--r--util/src/migrations/1634424361103-Presence.ts11
-rw-r--r--util/src/migrations/1634426540271-MigrationTimestamp.ts15
-rw-r--r--util/src/migrations/migrate_db_engine.ts129
-rw-r--r--util/src/util/Database.ts20
-rw-r--r--util/src/util/Event.ts2
-rw-r--r--util/src/util/Rights.ts10
-rw-r--r--util/src/util/Snowflake.ts2
-rw-r--r--util/src/util/TraverseDirectory.ts10
-rw-r--r--util/src/util/cdn.ts25
-rw-r--r--util/src/util/index.ts1
38 files changed, 439 insertions, 306 deletions
diff --git a/util/src/entities/AuditLog.ts b/util/src/entities/AuditLog.ts

index ae9feb76..4b81ed6a 100644 --- a/util/src/entities/AuditLog.ts +++ b/util/src/entities/AuditLog.ts
@@ -55,10 +55,7 @@ export class AuditLog extends BaseClass { @ManyToOne(() => User, (user: User) => user.id) user: User; - @Column({ - type: "simple-enum", - enum: AuditLogEvents, - }) + @Column({ type: "int" }) action_type: AuditLogEvents; @Column({ type: "simple-json", nullable: true }) diff --git a/util/src/entities/BaseClass.ts b/util/src/entities/BaseClass.ts
index beccf04b..d20078e5 100644 --- a/util/src/entities/BaseClass.ts +++ b/util/src/entities/BaseClass.ts
@@ -1,19 +1,8 @@ import "reflect-metadata"; -import { - BaseEntity, - BeforeInsert, - BeforeUpdate, - EntityMetadata, - FindConditions, - ObjectIdColumn, - PrimaryColumn, -} from "typeorm"; +import { BaseEntity, EntityMetadata, FindConditions, ObjectIdColumn, PrimaryColumn } from "typeorm"; import { Snowflake } from "../util/Snowflake"; import "missing-native-js-functions"; -// TODO use class-validator https://typeorm.io/#/validation with class annotators (isPhone/isEmail) combined with types from typescript-json-schema -// btw. we don't use class-validator for everything, because we need to explicitly set the type instead of deriving it from typescript also it doesn't easily support nested objects - export class BaseClassWithoutId extends BaseEntity { constructor(props?: any) { super(); @@ -42,7 +31,7 @@ export class BaseClassWithoutId extends BaseEntity { for (const key in props) { if (!properties.has(key)) continue; // @ts-ignore - const setter = this[`set${key.capitalize()}`]; + const setter = this[`set${key.capitalize()}`]; // use setter function if it exists if (setter) { setter.call(this, props[key]); @@ -53,12 +42,6 @@ export class BaseClassWithoutId extends BaseEntity { } } - @BeforeUpdate() - @BeforeInsert() - validate() { - return this; - } - toJSON(): any { return Object.fromEntries( this.metadata.columns // @ts-ignore @@ -76,42 +59,6 @@ export class BaseClassWithoutId extends BaseEntity { const repository = this.getRepository(); return repository.decrement(conditions, propertyPath, value); } - - // static async delete<T>(criteria: FindConditions<T>, options?: RemoveOptions) { - // if (!criteria) throw new Error("You need to specify delete criteria"); - - // const repository = this.getRepository(); - // const promises = repository.metadata.relations.map(async (x) => { - // if (x.orphanedRowAction !== "delete") return; - - // const foreignKey = - // x.foreignKeys.find((key) => key.entityMetadata === repository.metadata) || - // x.inverseRelation?.foreignKeys[0]; // find foreign key for this entity - // if (!foreignKey) { - // throw new Error( - // `Foreign key not found for entity ${repository.metadata.name} in relation ${x.propertyName}` - // ); - // } - // const id = (criteria as any)[foreignKey.referencedColumnNames[0]]; - // if (!id) throw new Error("id missing in criteria options " + foreignKey.referencedColumnNames); - - // if (x.relationType === "many-to-many") { - // return getConnection() - // .createQueryBuilder() - // .relation(this, x.propertyName) - // .of(id) - // .remove({ [foreignKey.columnNames[0]]: id }); - // } else if ( - // x.relationType === "one-to-one" || - // x.relationType === "many-to-one" || - // x.relationType === "one-to-many" - // ) { - // return (x.inverseEntityMetadata.target as any).delete({ [foreignKey.columnNames[0]]: id }); - // } - // }); - // await Promise.all(promises); - // return super.delete(criteria, options); - // } } export const PrimaryIdColumn = process.env.DATABASE?.startsWith("mongodb") ? ObjectIdColumn : PrimaryColumn; diff --git a/util/src/entities/Channel.ts b/util/src/entities/Channel.ts
index 51d8b026..bd2e5a58 100644 --- a/util/src/entities/Channel.ts +++ b/util/src/entities/Channel.ts
@@ -39,7 +39,7 @@ export class Channel extends BaseClass { @Column({ type: "text", nullable: true }) icon?: string | null; - @Column({ type: "simple-enum", enum: ChannelType }) + @Column({ type: "int" }) type: ChannelType; @OneToMany(() => Recipient, (recipient: Recipient) => recipient.channel, { diff --git a/util/src/entities/Config.ts b/util/src/entities/Config.ts
index 813649ac..b3167ac7 100644 --- a/util/src/entities/Config.ts +++ b/util/src/entities/Config.ts
@@ -51,11 +51,6 @@ export interface ConfigValue { general: { instanceId: string; }; - permissions: { - user: { - createGuilds: boolean; - }; - }; limits: { user: { maxGuilds: number; @@ -64,6 +59,7 @@ export interface ConfigValue { }; guild: { maxRoles: number; + maxEmojis: number; maxMembers: number; maxChannels: number; maxChannelsInCategory: number; @@ -153,6 +149,11 @@ export interface ConfigValue { canLeave: boolean; }; }; + gif: { + enabled: boolean; + provider: "tenor"; // more coming soon + apiKey?: string; + }; rabbitmq: { host: string | null; }; @@ -175,11 +176,6 @@ export const DefaultConfigOptions: ConfigValue = { general: { instanceId: Snowflake.generate(), }, - permissions: { - user: { - createGuilds: true, - }, - }, limits: { user: { maxGuilds: 100, @@ -188,6 +184,7 @@ export const DefaultConfigOptions: ConfigValue = { }, guild: { maxRoles: 250, + maxEmojis: 50, // TODO: max emojis per guild per nitro level maxMembers: 250000, maxChannels: 500, maxChannelsInCategory: 50, @@ -305,7 +302,6 @@ export const DefaultConfigOptions: ConfigValue = { }, ], }, - guild: { showAllGuildsInDiscovery: false, autoJoin: { @@ -314,6 +310,11 @@ export const DefaultConfigOptions: ConfigValue = { guilds: [], }, }, + gif: { + enabled: true, + provider: "tenor", + apiKey: "LIVDSRZULELA", + }, rabbitmq: { host: null, }, diff --git a/util/src/entities/Emoji.ts b/util/src/entities/Emoji.ts
index a252d9f4..03218375 100644 --- a/util/src/entities/Emoji.ts +++ b/util/src/entities/Emoji.ts
@@ -1,4 +1,5 @@ -import { Column, Entity, JoinColumn, ManyToOne } from "typeorm"; +import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; +import { User } from "."; import { BaseClass } from "./BaseClass"; import { Guild } from "./Guild"; import { Role } from "./Role"; @@ -20,6 +21,14 @@ export class Emoji extends BaseClass { }) guild: Guild; + @Column({ nullable: true }) + @RelationId((emoji: Emoji) => emoji.user) + user_id: string; + + @JoinColumn({ name: "user_id" }) + @ManyToOne(() => User) + user: User; + @Column() managed: boolean; @@ -28,4 +37,7 @@ export class Emoji extends BaseClass { @Column() require_colons: boolean; + + @Column({ type: "simple-array" }) + roles: string[]; // roles this emoji is whitelisted to (new discord feature?) } diff --git a/util/src/entities/Guild.ts b/util/src/entities/Guild.ts
index 35595191..157f0921 100644 --- a/util/src/entities/Guild.ts +++ b/util/src/entities/Guild.ts
@@ -258,14 +258,6 @@ export class Guild extends BaseClass { unavailable?: boolean; @Column({ nullable: true }) - @RelationId((guild: Guild) => guild.vanity_url) - vanity_url_code?: string; - - @JoinColumn({ name: "vanity_url_code" }) - @ManyToOne(() => Invite) - vanity_url?: Invite; - - @Column({ nullable: true }) verification_level?: number; @Column({ type: "simple-json" }) diff --git a/util/src/entities/Invite.ts b/util/src/entities/Invite.ts
index 82556fab..b3e00957 100644 --- a/util/src/entities/Invite.ts +++ b/util/src/entities/Invite.ts
@@ -1,6 +1,6 @@ -import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; +import { Column, Entity, JoinColumn, ManyToOne, RelationId, PrimaryColumn } from "typeorm"; import { Member } from "./Member"; -import { BaseClass, PrimaryIdColumn } from "./BaseClass"; +import { BaseClassWithoutId } from "./BaseClass"; import { Channel } from "./Channel"; import { Guild } from "./Guild"; import { User } from "./User"; @@ -8,8 +8,8 @@ import { User } from "./User"; export const PublicInviteRelation = ["inviter", "guild", "channel"]; @Entity("invites") -export class Invite extends BaseClass { - @PrimaryIdColumn() +export class Invite extends BaseClassWithoutId { + @PrimaryColumn() code: string; @Column() @@ -71,6 +71,9 @@ export class Invite extends BaseClass { @Column({ nullable: true }) target_user_type?: number; + @Column({ nullable: true}) + vanity_url?: boolean; + static async joinGuild(user_id: string, code: string) { const invite = await Invite.findOneOrFail({ code }); if (invite.uses++ >= invite.max_uses && invite.max_uses !== 0) await Invite.delete({ code }); diff --git a/util/src/entities/Member.ts b/util/src/entities/Member.ts
index 7d7ac40a..0f7be2a7 100644 --- a/util/src/entities/Member.ts +++ b/util/src/entities/Member.ts
@@ -26,6 +26,22 @@ import { BaseClassWithoutId } from "./BaseClass"; import { Ban, PublicGuildRelations } from "."; import { DiscordApiErrors } from "../util/Constants"; +export const MemberPrivateProjection: (keyof Member)[] = [ + "id", + "guild", + "guild_id", + "deaf", + "joined_at", + "last_message_id", + "mute", + "nick", + "pending", + "premium_since", + "roles", + "settings", + "user", +]; + @Entity("members") @Index(["id", "guild_id"], { unique: true }) export class Member extends BaseClassWithoutId { @@ -81,9 +97,12 @@ export class Member extends BaseClassWithoutId { @Column() pending: boolean; - @Column({ type: "simple-json" }) + @Column({ type: "simple-json", select: false }) settings: UserGuildSettings; + @Column({ nullable: true }) + last_message_id?: string; + // TODO: update // @Column({ type: "simple-json" }) // read_state: ReadState; diff --git a/util/src/entities/Message.ts b/util/src/entities/Message.ts
index 04c3c7aa..a4d38315 100644 --- a/util/src/entities/Message.ts +++ b/util/src/entities/Message.ts
@@ -46,9 +46,6 @@ export enum MessageType { @Entity("messages") export class Message extends BaseClass { - @Column() - id: string; - @Column({ nullable: true }) @RelationId((message: Message) => message.channel) channel_id: string; @@ -130,7 +127,7 @@ export class Message extends BaseClass { mention_channels: Channel[]; @JoinTable({ name: "message_stickers" }) - @ManyToMany(() => Sticker) + @ManyToMany(() => Sticker, { cascade: true, onDelete: "CASCADE" }) sticker_items?: Sticker[]; @OneToMany(() => Attachment, (attachment: Attachment) => attachment.message, { @@ -151,7 +148,7 @@ export class Message extends BaseClass { @Column({ nullable: true }) pinned?: boolean; - @Column({ type: "simple-enum", enum: MessageType }) + @Column({ type: "int" }) type: MessageType; @Column({ type: "simple-json", nullable: true }) diff --git a/util/src/entities/Migration.ts b/util/src/entities/Migration.ts new file mode 100644
index 00000000..7393496f --- /dev/null +++ b/util/src/entities/Migration.ts
@@ -0,0 +1,18 @@ +import { Column, Entity, ObjectIdColumn, PrimaryGeneratedColumn } from "typeorm"; +import { BaseClassWithoutId } from "."; + +export const PrimaryIdAutoGenerated = process.env.DATABASE?.startsWith("mongodb") + ? ObjectIdColumn + : PrimaryGeneratedColumn; + +@Entity("migrations") +export class Migration extends BaseClassWithoutId { + @PrimaryIdAutoGenerated() + id: number; + + @Column({ type: 'bigint' }) + timestamp: number; + + @Column() + name: string; +} diff --git a/util/src/entities/RateLimit.ts b/util/src/entities/RateLimit.ts
index fa9c32c1..f5916f6b 100644 --- a/util/src/entities/RateLimit.ts +++ b/util/src/entities/RateLimit.ts
@@ -3,9 +3,6 @@ import { BaseClass } from "./BaseClass"; @Entity("rate_limits") export class RateLimit extends BaseClass { - @Column() - id: "global" | "error" | string; // channel_239842397 | guild_238927349823 | webhook_238923423498 - @Column() // no relation as it also executor_id: string; diff --git a/util/src/entities/ReadState.ts b/util/src/entities/ReadState.ts
index 89480e83..ebef89be 100644 --- a/util/src/entities/ReadState.ts +++ b/util/src/entities/ReadState.ts
@@ -32,13 +32,8 @@ export class ReadState extends BaseClass { user: User; @Column({ nullable: true }) - @RelationId((read_state: ReadState) => read_state.last_message) last_message_id: string; - @JoinColumn({ name: "last_message_id" }) - @ManyToOne(() => Message, { nullable: true }) - last_message?: Message; - @Column({ nullable: true }) last_pin_timestamp?: Date; diff --git a/util/src/entities/Relationship.ts b/util/src/entities/Relationship.ts
index e016b36b..c3592c76 100644 --- a/util/src/entities/Relationship.ts +++ b/util/src/entities/Relationship.ts
@@ -35,7 +35,7 @@ export class Relationship extends BaseClass { @Column({ nullable: true }) nickname?: string; - @Column({ type: "simple-enum", enum: RelationshipType }) + @Column({ type: "int" }) type: RelationshipType; toPublicRelationship() { diff --git a/util/src/entities/Session.ts b/util/src/entities/Session.ts
index 7cc325f5..969efa89 100644 --- a/util/src/entities/Session.ts +++ b/util/src/entities/Session.ts
@@ -1,6 +1,8 @@ import { User } from "./User"; import { BaseClass } from "./BaseClass"; import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; +import { Status } from "../interfaces/Status"; +import { Activity } from "../interfaces/Activity"; //TODO we need to remove all sessions on server start because if the server crashes without closing websockets it won't delete them @@ -17,11 +19,13 @@ export class Session extends BaseClass { user: User; //TODO check, should be 32 char long hex string - @Column({ nullable: false }) + @Column({ nullable: false, select: false }) session_id: string; - activities: []; //TODO + @Column({ type: "simple-json", nullable: true }) + activities: Activity[]; + // TODO client_status @Column({ type: "simple-json", select: false }) client_info: { client: string; @@ -29,6 +33,14 @@ export class Session extends BaseClass { version: number; }; - @Column({ nullable: false }) - status: string; //TODO enum + @Column({ nullable: false, type: "varchar" }) + status: Status; //TODO enum } + +export const PrivateSessionProjection: (keyof Session)[] = [ + "user_id", + "session_id", + "activities", + "client_info", + "status", +]; diff --git a/util/src/entities/Sticker.ts b/util/src/entities/Sticker.ts
index ab224d1d..37bc6fbe 100644 --- a/util/src/entities/Sticker.ts +++ b/util/src/entities/Sticker.ts
@@ -1,4 +1,5 @@ -import { Column, Entity, JoinColumn, ManyToOne } from "typeorm"; +import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; +import { User } from "./User"; import { BaseClass } from "./BaseClass"; import { Guild } from "./Guild"; @@ -8,6 +9,7 @@ export enum StickerType { } export enum StickerFormatType { + GIF = 0, // gif is a custom format type and not in discord spec PNG = 1, APNG = 2, LOTTIE = 3, @@ -21,11 +23,22 @@ export class Sticker extends BaseClass { @Column({ nullable: true }) description?: string; - @Column() - tags: string; + @Column({ nullable: true }) + available?: boolean; - @Column() - pack_id: string; + @Column({ nullable: true }) + tags?: string; + + @Column({ nullable: true }) + @RelationId((sticker: Sticker) => sticker.pack) + pack_id?: string; + + @JoinColumn({ name: "pack_id" }) + @ManyToOne(() => require("./StickerPack").StickerPack, { + onDelete: "CASCADE", + nullable: true, + }) + pack: import("./StickerPack").StickerPack; @Column({ nullable: true }) guild_id?: string; @@ -36,9 +49,18 @@ export class Sticker extends BaseClass { }) guild?: Guild; - @Column({ type: "simple-enum", enum: StickerType }) + @Column({ nullable: true }) + user_id?: string; + + @JoinColumn({ name: "user_id" }) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) + user?: User; + + @Column({ type: "int" }) type: StickerType; - @Column({ type: "simple-enum", enum: StickerFormatType }) + @Column({ type: "int" }) format_type: StickerFormatType; } diff --git a/util/src/entities/StickerPack.ts b/util/src/entities/StickerPack.ts new file mode 100644
index 00000000..ec8c69a2 --- /dev/null +++ b/util/src/entities/StickerPack.ts
@@ -0,0 +1,31 @@ +import { Column, Entity, JoinColumn, ManyToOne, OneToMany, OneToOne, RelationId } from "typeorm"; +import { Sticker } from "."; +import { BaseClass } from "./BaseClass"; + +@Entity("sticker_packs") +export class StickerPack extends BaseClass { + @Column() + name: string; + + @Column({ nullable: true }) + description?: string; + + @Column({ nullable: true }) + banner_asset_id?: string; + + @OneToMany(() => Sticker, (sticker: Sticker) => sticker.pack, { + cascade: true, + orphanedRowAction: "delete", + }) + stickers: Sticker[]; + + // sku_id: string + + @Column({ nullable: true }) + @RelationId((pack: StickerPack) => pack.cover_sticker) + cover_sticker_id?: string; + + @ManyToOne(() => Sticker, { nullable: true }) + @JoinColumn() + cover_sticker?: Sticker; +} diff --git a/util/src/entities/TeamMember.ts b/util/src/entities/TeamMember.ts
index bdfdccf0..b726e1e8 100644 --- a/util/src/entities/TeamMember.ts +++ b/util/src/entities/TeamMember.ts
@@ -9,7 +9,7 @@ export enum TeamMemberState { @Entity("team_members") export class TeamMember extends BaseClass { - @Column({ type: "simple-enum", enum: TeamMemberState }) + @Column({ type: "int" }) membership_state: TeamMemberState; @Column({ type: "simple-array" }) diff --git a/util/src/entities/User.ts b/util/src/entities/User.ts
index 97564af3..bc852616 100644 --- a/util/src/entities/User.ts +++ b/util/src/entities/User.ts
@@ -4,7 +4,7 @@ import { BitField } from "../util/BitField"; import { Relationship } from "./Relationship"; import { ConnectedAccount } from "./ConnectedAccount"; import { Config, FieldErrors, Snowflake, trimSpecial } from ".."; -import { Member } from "."; +import { Member, Session } from "."; export enum PublicUserEnum { username, @@ -131,6 +131,9 @@ export class User extends BaseClass { @Column() rights: string; // Rights + @OneToMany(() => Session, (session: Session) => session.user) + sessions: Session[]; + @JoinColumn({ name: "relationship_ids" }) @OneToMany(() => Relationship, (relationship: Relationship) => relationship.from, { cascade: true, @@ -198,7 +201,7 @@ export class User extends BaseClass { // randomly generates a discriminator between 1 and 9999 and checks max five times if it already exists // if it all five times already exists, abort with USERNAME_TOO_MANY_USERS error // else just continue - // TODO: is there any better way to generate a random discriminator only once, without checking if it already exists in the mongodb database? + // TODO: is there any better way to generate a random discriminator only once, without checking if it already exists in the database? for (let tries = 0; tries < 5; tries++) { discriminator = Math.randomIntBetween(1, 9999).toString().padStart(4, "0"); exists = await User.findOne({ where: { discriminator, username: username }, select: ["id"] }); @@ -219,7 +222,7 @@ export class User extends BaseClass { // if nsfw_allowed is null/undefined it'll require date_of_birth to set it to true/false const language = req.language === "en" ? "en-US" : req.language || "en-US"; - const user = await new User({ + const user = new User({ created_at: new Date(), username: username, discriminator, @@ -246,13 +249,17 @@ export class User extends BaseClass { }, settings: { ...defaultSettings, locale: language }, fingerprints: [], - }).save(); + }); + + await user.save(); - if (Config.get().guild.autoJoin.enabled) { - for (const guild of Config.get().guild.autoJoin.guilds || []) { - await Member.addToGuild(user.id, guild); + setImmediate(async () => { + if (Config.get().guild.autoJoin.enabled) { + for (const guild of Config.get().guild.autoJoin.guilds || []) { + await Member.addToGuild(user.id, guild).catch((e) => {}); + } } - } + }); return user; } @@ -291,7 +298,7 @@ export const defaultSettings: UserSettings = { render_reactions: true, restricted_guilds: [], show_current_game: true, - status: "offline", + status: "online", stream_notifications_enabled: true, theme: "dark", timezone_offset: 0, diff --git a/util/src/entities/Webhook.ts b/util/src/entities/Webhook.ts
index 8382435f..89538417 100644 --- a/util/src/entities/Webhook.ts +++ b/util/src/entities/Webhook.ts
@@ -12,10 +12,7 @@ export enum WebhookType { @Entity("webhooks") export class Webhook extends BaseClass { - @Column() - id: string; - - @Column({ type: "simple-enum", enum: WebhookType }) + @Column({ type: "int" }) type: WebhookType; @Column({ nullable: true }) diff --git a/util/src/entities/index.ts b/util/src/entities/index.ts
index 7b1c9750..b52841c9 100644 --- a/util/src/entities/index.ts +++ b/util/src/entities/index.ts
@@ -11,6 +11,7 @@ export * from "./Guild"; export * from "./Invite"; export * from "./Member"; export * from "./Message"; +export * from "./Migration"; export * from "./RateLimit"; export * from "./ReadState"; export * from "./Recipient"; @@ -18,6 +19,7 @@ export * from "./Relationship"; export * from "./Role"; export * from "./Session"; export * from "./Sticker"; +export * from "./StickerPack"; export * from "./Team"; export * from "./TeamMember"; export * from "./Template"; diff --git a/util/src/index.ts b/util/src/index.ts
index fc00d46b..ae0f7e54 100644 --- a/util/src/index.ts +++ b/util/src/index.ts
@@ -1,12 +1,6 @@ import "reflect-metadata"; -// export * as Constants from "../util/Constants"; export * from "./util/index"; export * from "./interfaces/index"; export * from "./entities/index"; export * from "./dtos/index"; - -// import Config from "../util/Config"; -// import db, { MongooseCache, toObject } from "./util/Database"; - -// export { Config }; diff --git a/util/src/interfaces/Activity.ts b/util/src/interfaces/Activity.ts
index f5a3c270..43984afd 100644 --- a/util/src/interfaces/Activity.ts +++ b/util/src/interfaces/Activity.ts
@@ -1,37 +1,38 @@ export interface Activity { - name: string; - type: ActivityType; - url?: string; - created_at?: Date; + name: string; // the activity's name + type: ActivityType; // activity type // TODO: check if its between range 0-5 + url?: string; // stream url, is validated when type is 1 + created_at?: number; // unix timestamp of when the activity was added to the user's session timestamps?: { - start?: number; - end?: number; - }[]; - application_id?: string; + // unix timestamps for start and/or end of the game + start: number; + end: number; + }; + application_id?: string; // application id for the game details?: string; state?: string; emoji?: { name: string; id?: string; - amimated?: boolean; + animated: boolean; }; party?: { id?: string; - size?: [number, number]; + size?: [number]; // used to show the party's current and maximum size // TODO: array length 2 }; assets?: { - large_image?: string; - large_text?: string; - small_image?: string; - small_text?: string; + large_image?: string; // the id for a large asset of the activity, usually a snowflake + large_text?: string; // text displayed when hovering over the large image of the activity + small_image?: string; // the id for a small asset of the activity, usually a snowflake + small_text?: string; // text displayed when hovering over the small image of the activity }; secrets?: { - join?: string; - spectate?: string; - match?: string; + join?: string; // the secret for joining a party + spectate?: string; // the secret for spectating a game + match?: string; // the secret for a specific instanced match }; instance?: boolean; - flags?: bigint; + flags: string; // activity flags OR d together, describes what the payload includes } export enum ActivityType { diff --git a/util/src/interfaces/Event.ts b/util/src/interfaces/Event.ts
index 03099bbb..a5253c09 100644 --- a/util/src/interfaces/Event.ts +++ b/util/src/interfaces/Event.ts
@@ -12,6 +12,8 @@ import { Interaction } from "./Interaction"; import { ConnectedAccount } from "../entities/ConnectedAccount"; import { Relationship, RelationshipType } from "../entities/Relationship"; import { Presence } from "./Presence"; +import { Sticker } from ".."; +import { Activity, Status } from "."; export interface Event { guild_id?: string; @@ -185,14 +187,22 @@ export interface GuildBanRemoveEvent extends Event { }; } -export interface GuildEmojiUpdateEvent extends Event { - event: "GUILD_EMOJI_UPDATE"; +export interface GuildEmojisUpdateEvent extends Event { + event: "GUILD_EMOJIS_UPDATE"; data: { guild_id: string; emojis: Emoji[]; }; } +export interface GuildStickersUpdateEvent extends Event { + event: "GUILD_STICKERS_UPDATE"; + data: { + guild_id: string; + stickers: Sticker[]; + }; +} + export interface GuildIntegrationUpdateEvent extends Event { event: "GUILD_INTEGRATIONS_UPDATE"; data: { @@ -445,6 +455,37 @@ export interface RelationshipRemoveEvent extends Event { data: Omit<PublicRelationship, "nickname">; } +export interface SessionsReplace extends Event { + event: "SESSIONS_REPLACE"; + data: { + activities: Activity[]; + client_info: { + version: number; + os: string; + client: string; + }; + status: Status; + }[]; +} + +export interface GuildMemberListUpdate extends Event { + event: "GUILD_MEMBER_LIST_UPDATE"; + data: { + groups: { id: string; count: number }[]; + guild_id: string; + id: string; + member_count: number; + online_count: number; + ops: { + index: number; + item: { + member?: PublicMember & { presence: Presence }; + group?: { id: string; count: number }[]; + }; + }[]; + }; +} + export type EventData = | InvalidatedEvent | ReadyEvent @@ -459,12 +500,13 @@ export type EventData = | GuildDeleteEvent | GuildBanAddEvent | GuildBanRemoveEvent - | GuildEmojiUpdateEvent + | GuildEmojisUpdateEvent | GuildIntegrationUpdateEvent | GuildMemberAddEvent | GuildMemberRemoveEvent | GuildMemberUpdateEvent | GuildMembersChunkEvent + | GuildMemberListUpdate | GuildRoleCreateEvent | GuildRoleUpdateEvent | GuildRoleDeleteEvent @@ -514,6 +556,7 @@ export enum EVENTEnum { GuildMemberUpdate = "GUILD_MEMBER_UPDATE", GuildMemberSpeaking = "GUILD_MEMBER_SPEAKING", GuildMembersChunk = "GUILD_MEMBERS_CHUNK", + GuildMemberListUpdate = "GUILD_MEMBER_LIST_UPDATE", GuildRoleCreate = "GUILD_ROLE_CREATE", GuildRoleDelete = "GUILD_ROLE_DELETE", GuildRoleUpdate = "GUILD_ROLE_UPDATE", @@ -537,6 +580,7 @@ export enum EVENTEnum { ApplicationCommandCreate = "APPLICATION_COMMAND_CREATE", ApplicationCommandUpdate = "APPLICATION_COMMAND_UPDATE", ApplicationCommandDelete = "APPLICATION_COMMAND_DELETE", + SessionsReplace = "SESSIONS_REPLACE", } export type EVENT = @@ -552,13 +596,15 @@ export type EVENT = | "GUILD_DELETE" | "GUILD_BAN_ADD" | "GUILD_BAN_REMOVE" - | "GUILD_EMOJI_UPDATE" + | "GUILD_EMOJIS_UPDATE" + | "GUILD_STICKERS_UPDATE" | "GUILD_INTEGRATIONS_UPDATE" | "GUILD_MEMBER_ADD" | "GUILD_MEMBER_REMOVE" | "GUILD_MEMBER_UPDATE" | "GUILD_MEMBER_SPEAKING" | "GUILD_MEMBERS_CHUNK" + | "GUILD_MEMBER_LIST_UPDATE" | "GUILD_ROLE_CREATE" | "GUILD_ROLE_DELETE" | "GUILD_ROLE_UPDATE" @@ -587,6 +633,7 @@ export type EVENT = | "MESSAGE_ACK" | "RELATIONSHIP_ADD" | "RELATIONSHIP_REMOVE" + | "SESSIONS_REPLACE" | CUSTOMEVENTS; export type CUSTOMEVENTS = "INVALIDATED" | "RATELIMIT"; diff --git a/util/src/interfaces/Presence.ts b/util/src/interfaces/Presence.ts
index 4a1ff038..7663891a 100644 --- a/util/src/interfaces/Presence.ts +++ b/util/src/interfaces/Presence.ts
@@ -1,10 +1,12 @@ import { ClientStatus, Status } from "./Status"; import { Activity } from "./Activity"; +import { PublicUser } from "../entities/User"; export interface Presence { - user_id: string; + user: PublicUser; guild_id?: string; status: Status; activities: Activity[]; client_status: ClientStatus; + // TODO: game } diff --git a/util/src/migrations/1633864260873-EmojiRoles.ts b/util/src/migrations/1633864260873-EmojiRoles.ts new file mode 100644
index 00000000..f0d709f2 --- /dev/null +++ b/util/src/migrations/1633864260873-EmojiRoles.ts
@@ -0,0 +1,13 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class EmojiRoles1633864260873 implements MigrationInterface { + name = "EmojiRoles1633864260873"; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(`ALTER TABLE "emojis" ADD "roles" text NOT NULL DEFAULT ''`); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(`ALTER TABLE "emojis" DROP COLUMN column_name "roles"`); + } +} diff --git a/util/src/migrations/1633864669243-EmojiUser.ts b/util/src/migrations/1633864669243-EmojiUser.ts new file mode 100644
index 00000000..982405d7 --- /dev/null +++ b/util/src/migrations/1633864669243-EmojiUser.ts
@@ -0,0 +1,23 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class EmojiUser1633864669243 implements MigrationInterface { + name = "EmojiUser1633864669243"; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(`ALTER TABLE "emojis" ADD "user_id" varchar`); + try { + await queryRunner.query( + `ALTER TABLE "emojis" ADD CONSTRAINT FK_fa7ddd5f9a214e28ce596548421 FOREIGN KEY (user_id) REFERENCES users(id)` + ); + } catch (error) { + console.error( + "sqlite doesn't support altering foreign keys: https://stackoverflow.com/questions/1884818/how-do-i-add-a-foreign-key-to-an-existing-sqlite-table" + ); + } + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(`ALTER TABLE "emojis" DROP COLUMN column_name "user_id"`); + await queryRunner.query(`ALTER TABLE "emojis" DROP CONSTRAINT FK_fa7ddd5f9a214e28ce596548421`); + } +} diff --git a/util/src/migrations/1633881705509-VanityInvite.ts b/util/src/migrations/1633881705509-VanityInvite.ts new file mode 100644
index 00000000..45485310 --- /dev/null +++ b/util/src/migrations/1633881705509-VanityInvite.ts
@@ -0,0 +1,19 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class VanityInvite1633881705509 implements MigrationInterface { + name = "VanityInvite1633881705509"; + + public async up(queryRunner: QueryRunner): Promise<void> { + try { + await queryRunner.query(`ALTER TABLE "emojis" DROP COLUMN vanity_url_code`); + await queryRunner.query(`ALTER TABLE "emojis" DROP CONSTRAINT FK_c2c1809d79eb120ea0cb8d342ad`); + } catch (error) {} + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(`ALTER TABLE "emojis" ADD vanity_url_code varchar`); + await queryRunner.query( + `ALTER TABLE "emojis" ADD CONSTRAINT FK_c2c1809d79eb120ea0cb8d342ad FOREIGN KEY ("vanity_url_code") REFERENCES "invites"("code") ON DELETE NO ACTION ON UPDATE NO ACTION` + ); + } +} diff --git a/util/src/migrations/1634308884591-Stickers.ts b/util/src/migrations/1634308884591-Stickers.ts new file mode 100644
index 00000000..fbc4649f --- /dev/null +++ b/util/src/migrations/1634308884591-Stickers.ts
@@ -0,0 +1,66 @@ +import { MigrationInterface, QueryRunner, Table, TableColumn, TableForeignKey } from "typeorm"; + +export class Stickers1634308884591 implements MigrationInterface { + name = "Stickers1634308884591"; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.dropForeignKey("read_states", "FK_6f255d873cfbfd7a93849b7ff74"); + await queryRunner.changeColumn( + "stickers", + "tags", + new TableColumn({ name: "tags", type: "varchar", isNullable: true }) + ); + await queryRunner.changeColumn( + "stickers", + "pack_id", + new TableColumn({ name: "pack_id", type: "varchar", isNullable: true }) + ); + await queryRunner.changeColumn("stickers", "type", new TableColumn({ name: "type", type: "integer" })); + await queryRunner.changeColumn( + "stickers", + "format_type", + new TableColumn({ name: "format_type", type: "integer" }) + ); + await queryRunner.changeColumn( + "stickers", + "available", + new TableColumn({ name: "available", type: "boolean", isNullable: true }) + ); + await queryRunner.changeColumn( + "stickers", + "user_id", + new TableColumn({ name: "user_id", type: "boolean", isNullable: true }) + ); + await queryRunner.createForeignKey( + "stickers", + new TableForeignKey({ + name: "FK_8f4ee73f2bb2325ff980502e158", + columnNames: ["user_id"], + referencedColumnNames: ["id"], + referencedTableName: "users", + onDelete: "CASCADE", + }) + ); + await queryRunner.createTable( + new Table({ + name: "sticker_packs", + columns: [ + new TableColumn({ name: "id", type: "varchar", isPrimary: true }), + new TableColumn({ name: "name", type: "varchar" }), + new TableColumn({ name: "description", type: "varchar", isNullable: true }), + new TableColumn({ name: "banner_asset_id", type: "varchar", isNullable: true }), + new TableColumn({ name: "cover_sticker_id", type: "varchar", isNullable: true }), + ], + foreignKeys: [ + new TableForeignKey({ + columnNames: ["cover_sticker_id"], + referencedColumnNames: ["id"], + referencedTableName: "stickers", + }), + ], + }) + ); + } + + public async down(queryRunner: QueryRunner): Promise<void> {} +} diff --git a/util/src/migrations/1634424361103-Presence.ts b/util/src/migrations/1634424361103-Presence.ts new file mode 100644
index 00000000..729955b8 --- /dev/null +++ b/util/src/migrations/1634424361103-Presence.ts
@@ -0,0 +1,11 @@ +import { MigrationInterface, QueryRunner, TableColumn } from "typeorm"; + +export class Presence1634424361103 implements MigrationInterface { + name = "Presence1634424361103"; + + public async up(queryRunner: QueryRunner): Promise<void> { + queryRunner.addColumn("sessions", new TableColumn({ name: "activites", type: "text" })); + } + + public async down(queryRunner: QueryRunner): Promise<void> {} +} diff --git a/util/src/migrations/1634426540271-MigrationTimestamp.ts b/util/src/migrations/1634426540271-MigrationTimestamp.ts new file mode 100644
index 00000000..3208b25b --- /dev/null +++ b/util/src/migrations/1634426540271-MigrationTimestamp.ts
@@ -0,0 +1,15 @@ +import { MigrationInterface, QueryRunner, TableColumn } from "typeorm"; + +export class MigrationTimestamp1634426540271 implements MigrationInterface { + name = "MigrationTimestamp1634426540271"; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.changeColumn( + "migrations", + "timestamp", + new TableColumn({ name: "timestampe", type: "bigint", isNullable: false }) + ); + } + + public async down(queryRunner: QueryRunner): Promise<void> {} +} diff --git a/util/src/migrations/migrate_db_engine.ts b/util/src/migrations/migrate_db_engine.ts deleted file mode 100644
index 33024a8d..00000000 --- a/util/src/migrations/migrate_db_engine.ts +++ /dev/null
@@ -1,129 +0,0 @@ -import { config } from "dotenv"; -config(); -import { BaseEntity, createConnection, EntityTarget } from "typeorm"; -import { initDatabase } from "../util/Database"; -import "missing-native-js-functions"; -import { - Application, - Attachment, - Ban, - Channel, - ConnectedAccount, - defaultSettings, - Emoji, - Guild, - Invite, - Member, - Message, - RateLimit, - ReadState, - Recipient, - Relationship, - Role, - Sticker, - Team, - TeamMember, - Template, - User, - VoiceState, - Webhook, -} from ".."; - -async function main() { - if (!process.env.FROM) throw new Error("FROM database env connection string not set"); - - // manually arrange them because of foreign key - const entities = [ - User, - Guild, - Channel, - Invite, - Role, - Ban, - Application, - Emoji, - ConnectedAccount, - Member, - ReadState, - Recipient, - Relationship, - Sticker, - Team, - TeamMember, - Template, - VoiceState, - Webhook, - Message, - Attachment, - ]; - - const newDB = await initDatabase(); - - // @ts-ignore - const oldDB = await createConnection({ - type: process.env.FROM.split(":")[0]?.replace("+srv", ""), - url: process.env.FROM, - entities, - name: "old", - }); - let i = 0; - - try { - for (const e of entities) { - const entity = e as EntityTarget<any>; - const entries = await oldDB.manager.find(entity); - //@ts-ignore - console.log("migrated " + entries.length + " " + entity.name); - - for (const entry of entries) { - console.log(i++); - - if (entry instanceof User) { - console.log("instance of User"); - if (entry.bio == null) entry.bio = ""; - if (entry.rights == null) entry.rights = "0"; - if (entry.disabled == null) entry.disabled = false; - if (entry.fingerprints == null) entry.fingerprints = []; - if (entry.deleted == null) entry.deleted = false; - if (entry.data == null) { - entry.data = { - valid_tokens_since: new Date(0), - hash: undefined, - }; - // @ts-ignore - if (entry.user_data) { - // TODO: relationships - entry.data = { - // @ts-ignore - valid_tokens_since: entry.user_data.valid_tokens_since, // @ts-ignore - hash: entry.user_data.hash, - }; - } - } - // @ts-ignore - if (entry.settings == null) { - entry.settings = defaultSettings; - // @ts-ignore - if (entry.user_data) entry.settings = entry.user_settings; - } - } - - // try { - await newDB.manager.insert(entity, entry); - // } catch (error) { - // if (!entry.id) throw new Error("object doesn't have a unique id: " + entry); - // await newDB.manager.update(entity, { id: entry.id }, entry); - // } - } - // @ts-ignore - console.log("migrated all " + entity.name); - } - } catch (error) { - console.error((error as any).message); - } - - console.log("SUCCESS migrated all data"); - await newDB.close(); -} - -main().caught(); diff --git a/util/src/util/Database.ts b/util/src/util/Database.ts
index 8bce3a6f..6124ffab 100644 --- a/util/src/util/Database.ts +++ b/util/src/util/Database.ts
@@ -2,6 +2,7 @@ import path from "path"; import "reflect-metadata"; import { Connection, createConnection } from "typeorm"; import * as Models from "../entities"; +import { Migration } from "../entities/Migration"; import { yellow, green } from "nanocolors"; // UUID extension option is only supported with postgres @@ -33,10 +34,27 @@ export function initDatabase(): Promise<Connection> { bigNumberStrings: false, supportBigNumbers: true, name: "default", + migrations: [path.join(__dirname, "..", "migrations", "*.js")], }); - promise.then((connection) => { + promise.then(async (connection: Connection) => { dbConnection = connection; + + // run migrations, and if it is a new fresh database, set it to the last migration + if (connection.migrations.length) { + if (!(await Migration.findOne({}))) { + let i = 0; + + await Migration.insert( + connection.migrations.map((x) => ({ + id: i++, + name: x.name, + timestamp: Date.now(), + })) + ); + } + } + await connection.runMigrations(); console.log(`[Database] ${green("connected")}`); }); diff --git a/util/src/util/Event.ts b/util/src/util/Event.ts
index bf9547b1..8ed009d5 100644 --- a/util/src/util/Event.ts +++ b/util/src/util/Event.ts
@@ -46,7 +46,9 @@ export async function listenEvent(event: string, callback: (event: EventOpts) => } else { const cancel = () => { events.removeListener(event, callback); + events.setMaxListeners(events.getMaxListeners() - 1); }; + events.setMaxListeners(events.getMaxListeners() + 1); events.addListener(event, (opts) => callback({ ...opts, cancel })); return cancel; diff --git a/util/src/util/Rights.ts b/util/src/util/Rights.ts
index a266e4f7..5edd9142 100644 --- a/util/src/util/Rights.ts +++ b/util/src/util/Rights.ts
@@ -30,7 +30,7 @@ export class Rights extends BitField { MANAGE_MESSAGES: BitFlag(3), // Can't see other messages but delete/edit them in channels that they can see MANAGE_RATE_LIMITS: BitFlag(4), MANAGE_ROUTING: BitFlag(5), // can create custom message routes to any channel/guild - MANAGE_TICKETS: BitFlag(6), + MANAGE_TICKETS: BitFlag(6), // can respond to and resolve support tickets MANAGE_USERS: BitFlag(7), ADD_MEMBERS: BitFlag(8), // can manually add any members in their guilds BYPASS_RATE_LIMITS: BitFlag(9), @@ -39,7 +39,7 @@ export class Rights extends BitField { CREATE_DMS: BitFlag(12), CREATE_DM_GROUPS: BitFlag(13), CREATE_GUILDS: BitFlag(14), - CREATE_INVITES: BitFlag(15), + CREATE_INVITES: BitFlag(15), // can create mass invites in the guilds that they have CREATE_INSTANT_INVITE CREATE_ROLES: BitFlag(16), CREATE_TEMPLATES: BitFlag(17), CREATE_WEBHOOKS: BitFlag(18), @@ -50,9 +50,13 @@ export class Rights extends BitField { SELF_EDIT_MESSAGES: BitFlag(23), SELF_EDIT_NAME: BitFlag(24), SEND_MESSAGES: BitFlag(25), - USE_SCREEN: BitFlag(26), + USE_ACTIVITIES: BitFlag(26), // use (game) activities in voice channels (e.g. Watch together) USE_VIDEO: BitFlag(27), USE_VOICE: BitFlag(28), + INVITE_USERS: BitFlag(29), // can create user-specific invites in the guilds that they have INVITE_USERS + SELF_DELETE_DISABLE: BitFlag(30), // can disable/delete own account + DEBTABLE: BitFlag(31), // can use pay-to-use features + CREDITABLE: BitFlag(32) // can receive money from monetisation related features }; any(permission: RightResolvable, checkOperator = true) { diff --git a/util/src/util/Snowflake.ts b/util/src/util/Snowflake.ts
index f7a13388..3f6e3c63 100644 --- a/util/src/util/Snowflake.ts +++ b/util/src/util/Snowflake.ts
@@ -84,7 +84,7 @@ export class Snowflake { } static generate() { - var time = BigInt(Date.now() - Snowflake.EPOCH) << 22n; + var time = BigInt(Date.now() - Snowflake.EPOCH) << BigInt(22); var worker = Snowflake.workerId << 17n; var process = Snowflake.processId << 12n; var increment = Snowflake.INCREMENT++; diff --git a/util/src/util/TraverseDirectory.ts b/util/src/util/TraverseDirectory.ts new file mode 100644
index 00000000..275b7dcc --- /dev/null +++ b/util/src/util/TraverseDirectory.ts
@@ -0,0 +1,10 @@ +import { Server, traverseDirectory } from "lambert-server"; + +const DEFAULT_FILTER = /^([^\.].*)(?<!\.d)\.(js)$/; + +export function registerRoutes(server: Server, root: string) { + return traverseDirectory( + { dirname: root, recursive: true, filter: DEFAULT_FILTER }, + server.registerRoute.bind(server, root) + ); +} diff --git a/util/src/util/cdn.ts b/util/src/util/cdn.ts
index 8d45f85f..ea950cd1 100644 --- a/util/src/util/cdn.ts +++ b/util/src/util/cdn.ts
@@ -4,7 +4,9 @@ import fetch from "node-fetch"; import { Config } from "./Config"; import multer from "multer"; -export async function uploadFile(path: string, file: Express.Multer.File) { +export async function uploadFile(path: string, file?: Express.Multer.File) { + if (!file?.buffer) throw new HTTPError("Missing file in body"); + const form = new FormData(); form.append("file", file.buffer, { contentType: file.mimetype, @@ -25,30 +27,15 @@ export async function uploadFile(path: string, file: Express.Multer.File) { return result; } -export async function handleFile( - path: string, - body?: string -): Promise< - | (string & { - id: string; - content_type: string; - size: number; - url: string; - }) - | undefined -> { +export async function handleFile(path: string, body?: string): Promise<string | undefined> { if (!body || !body.startsWith("data:")) return undefined; try { const mimetype = body.split(":")[1].split(";")[0]; const buffer = Buffer.from(body.split(",")[1], "base64"); // @ts-ignore - const file = await uploadFile(path, { buffer, mimetype, originalname: "banner" }); - const obj = file.id; - for (const key in file) { - obj[key] = file[key]; - } - return obj; + const { id } = await uploadFile(path, { buffer, mimetype, originalname: "banner" }); + return id; } catch (error) { console.error(error); throw new HTTPError("Invalid " + path); diff --git a/util/src/util/index.ts b/util/src/util/index.ts
index 67583635..c5703468 100644 --- a/util/src/util/index.ts +++ b/util/src/util/index.ts
@@ -17,3 +17,4 @@ export * from "./Rights"; export * from "./Snowflake"; export * from "./String"; export * from "./Array"; +export * from "./TraverseDirectory";