diff --git a/util/src/entities/Member.ts b/util/src/entities/Member.ts
index 12b0b49a..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,7 +97,7 @@ export class Member extends BaseClassWithoutId {
@Column()
pending: boolean;
- @Column({ type: "simple-json" })
+ @Column({ type: "simple-json", select: false })
settings: UserGuildSettings;
@Column({ nullable: true })
diff --git a/util/src/entities/Message.ts b/util/src/entities/Message.ts
index 63cd6ad3..a4d38315 100644
--- a/util/src/entities/Message.ts
+++ b/util/src/entities/Message.ts
@@ -127,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, {
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/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/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 036ff2d0..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,6 +49,15 @@ export class Sticker extends BaseClass {
})
guild?: Guild;
+ @Column({ nullable: true })
+ user_id?: string;
+
+ @JoinColumn({ name: "user_id" })
+ @ManyToOne(() => User, {
+ onDelete: "CASCADE",
+ })
+ user?: User;
+
@Column({ type: "int" })
type: StickerType;
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/User.ts b/util/src/entities/User.ts
index 04f1e9cb..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,
@@ -250,11 +253,13 @@ export class User extends BaseClass {
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;
}
@@ -293,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/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/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 3c8ab8ab..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;
@@ -193,6 +195,14 @@ export interface GuildEmojisUpdateEvent extends Event {
};
}
+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
@@ -465,6 +506,7 @@ export type EventData =
| 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 =
@@ -553,12 +597,14 @@ export type EVENT =
| "GUILD_BAN_ADD"
| "GUILD_BAN_REMOVE"
| "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/1633881705509-VanityInvite.ts b/util/src/migrations/1633881705509-VanityInvite.ts
index af9b98ae..45485310 100644
--- a/util/src/migrations/1633881705509-VanityInvite.ts
+++ b/util/src/migrations/1633881705509-VanityInvite.ts
@@ -1,6 +1,8 @@
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`);
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.js b/util/src/migrations/migrate_db_engine.js
deleted file mode 100644
index 79e9d86f..00000000
--- a/util/src/migrations/migrate_db_engine.js
+++ /dev/null
@@ -1,109 +0,0 @@
-const { config } = require("dotenv");
-config();
-const { createConnection } = require("typeorm");
-const { initDatabase } = require("../../dist/util/Database");
-require("missing-native-js-functions");
-const {
- Application,
- Attachment,
- Ban,
- Channel,
- ConfigEntity,
- ConnectedAccount,
- Emoji,
- Guild,
- Invite,
- Member,
- Message,
- ReadState,
- Recipient,
- Relationship,
- Role,
- Sticker,
- Team,
- TeamMember,
- Template,
- User,
- VoiceState,
- Webhook,
-} = require("../../dist/entities/index");
-
-async function main() {
- if (!process.env.TO) throw new Error("TO database env connection string not set");
-
- // manually arrange them because of foreign keys
- const entities = [
- ConfigEntity,
- User,
- Guild,
- Channel,
- Invite,
- Role,
- Ban,
- Application,
- Emoji,
- ConnectedAccount,
- Member,
- ReadState,
- Recipient,
- Relationship,
- Sticker,
- Team,
- TeamMember,
- Template,
- VoiceState,
- Webhook,
- Message,
- Attachment,
- ];
-
- const oldDB = await initDatabase();
-
- const type = process.env.TO.includes("://") ? process.env.TO.split(":")[0]?.replace("+srv", "") : "sqlite";
- const isSqlite = type.includes("sqlite");
-
- // @ts-ignore
- const newDB = await createConnection({
- type,
- url: isSqlite ? undefined : process.env.TO,
- database: isSqlite ? process.env.TO : undefined,
- entities,
- name: "new",
- synchronize: true,
- });
- let i = 0;
-
- try {
- for (const entity of entities) {
- const entries = await oldDB.manager.find(entity);
-
- // @ts-ignore
- console.log("migrating " + entries.length + " " + entity.name + " ...");
-
- for (const entry of entries) {
- console.log(i++);
-
- try {
- await newDB.manager.insert(entity, entry);
- } catch (error) {
- try {
- if (!entry.id) throw new Error("object doesn't have a unique id: " + entry);
- await newDB.manager.update(entity, { id: entry.id }, entry);
- } catch (error) {
- console.error("couldn't migrate " + i + " " + entity.name, error);
- }
- }
- }
-
- // @ts-ignore
- console.log("migrated " + entries.length + " " + entity.name);
- }
- } catch (error) {
- console.error(error.message);
- }
-
- console.log("SUCCESS migrated all data");
- await newDB.close();
-}
-
-main().caught();
diff --git a/util/src/util/Config.ts b/util/src/util/Config.ts
index 704f3f2f..92907d0c 100644
--- a/util/src/util/Config.ts
+++ b/util/src/util/Config.ts
@@ -1,5 +1,10 @@
import "missing-native-js-functions";
import { ConfigValue, ConfigEntity, DefaultConfigOptions } from "../entities/Config";
+import path from "path";
+import fs from "fs";
+
+// TODO: yaml instead of json
+const overridePath = path.join(process.cwd(), "config.json");
var config: ConfigValue;
var pairs: ConfigEntity[];
@@ -12,8 +17,16 @@ export const Config = {
if (config) return config;
pairs = await ConfigEntity.find();
config = pairsToConfig(pairs);
+ config = (config || {}).merge(DefaultConfigOptions);
+
+ try {
+ const overrideConfig = JSON.parse(fs.readFileSync(overridePath, { encoding: "utf8" }));
+ config = overrideConfig.merge(config);
+ } catch (error) {
+ fs.writeFileSync(overridePath, JSON.stringify(config, null, 4));
+ }
- return this.set((config || {}).merge(DefaultConfigOptions));
+ return this.set(config);
},
get: function get() {
return config;
@@ -38,6 +51,7 @@ function applyConfig(val: ConfigValue) {
pair.value = obj;
return pair.save();
}
+ fs.writeFileSync(overridePath, JSON.stringify(val, null, 4));
return apply(val);
}
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/cdn.ts b/util/src/util/cdn.ts
index 4dd0078a..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,
|