Switch to migrations fully

This commit is contained in:
TheArcaneBrony
2022-08-07 02:52:09 +02:00
parent cba3844c6f
commit 365abc36dd
23 changed files with 3662 additions and 323 deletions
@@ -45,7 +45,8 @@ router.post("/", route({ body: "InviteCreateSchema", permission: "CREATE_INSTANT
channel_id: channel_id,
inviter_id: user_id
}).save();
const data = invite.toJSON();
//TODO: check this, removed toJSON call
const data = JSON.parse(JSON.stringify(invite));
data.inviter = await User.getPublicUser(req.user_id);
data.guild = await Guild.findOne({ where: { id: guild_id } });
data.channel = channel;
+2 -1
View File
@@ -61,7 +61,8 @@ router.patch("/", route({ body: "GuildUpdateSchema"}), async (req: Request, res:
// TODO: check if body ids are valid
guild.assign(body);
const data = guild.toJSON();
//TODO: check this, removed toJSON call
const data = JSON.parse(JSON.stringify(guild));
// TODO: guild hashes
// TODO: fix vanity_url_code, template_id
delete data.vanity_url_code;
+2 -1
View File
@@ -1,4 +1,5 @@
import { Config, Guild, Session } from "@fosscord/util";
import { createQueryBuilder } from "typeorm";
export async function initInstance() {
// TODO: clean up database and delete tombstone data
@@ -9,7 +10,7 @@ export async function initInstance() {
const { autoJoin } = Config.get().guild;
if (autoJoin.enabled && !autoJoin.guilds?.length) {
let guild = await Guild.findOne({});
let guild = await Guild.findOne({where: {}, order: {id: "ASC"}});
if (guild) {
// @ts-ignore
await Config.set({ guild: { autoJoin: { guilds: [guild.id] } } });
+2 -1
View File
@@ -201,9 +201,10 @@ export async function postHandleMessage(message: Message) {
export async function sendMessage(opts: MessageOptions) {
const message = await handleMessage({ ...opts, timestamp: new Date() });
//TODO: check this, removed toJSON call
await Promise.all([
Message.insert(message),
emitEvent({ event: "MESSAGE_CREATE", channel_id: opts.channel_id, data: message.toJSON() } as MessageCreateEvent)
emitEvent({ event: "MESSAGE_CREATE", channel_id: opts.channel_id, data: message } as MessageCreateEvent)
]);
postHandleMessage(message).catch((e) => {}); // no await as it should catch error non-blockingly
+5 -5
View File
@@ -1,9 +1,9 @@
export * from "./entities/AssetCacheItem";
export * from "./handlers/Message";
export * from "./handlers/route";
export * from "./handlers/Voice";
export * from "./utility/Base64";
export * from "./utility/ipAddress";
export * from "./handlers/Message";
export * from "./utility/passwordStrength";
export * from "./utility/RandomInviteID";
export * from "./handlers/route";
export * from "./utility/String";
export * from "./handlers/Voice";
export * from "./entities/AssetCacheItem";
export * from "./utility/String";
BIN
View File
Binary file not shown.
+2 -1
View File
@@ -86,6 +86,7 @@
"node-fetch": "^2.6.7",
"node-os-utils": "^1.3.7",
"patch-package": "^6.4.7",
"pg": "^8.7.3",
"picocolors": "^1.0.0",
"proxy-agent": "^5.0.0",
"reflect-metadata": "^0.1.13",
@@ -94,4 +95,4 @@
"typescript": "^4.1.2",
"ws": "^8.8.1"
}
}
}
-9
View File
@@ -1,9 +0,0 @@
{
"type": "sqlite",
"database": "../bundle/database.db",
"migrations": ["src/migrations/*.ts"],
"entities": ["src/entities/*.ts"],
"cli": {
"migrationsDir": "src/migrations"
}
}
-16
View File
@@ -1,7 +1,6 @@
import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
import { BaseClass } from "./BaseClass";
import { User } from "./User";
import crypto from "crypto";
@Entity("backup_codes")
export class BackupCode extends BaseClass {
@@ -17,19 +16,4 @@ export class BackupCode extends BaseClass {
@Column()
expired: boolean;
}
export function generateMfaBackupCodes(user_id: string) {
let backup_codes: BackupCode[] = [];
for (let i = 0; i < 10; i++) {
const code = BackupCode.create({
user: { id: user_id },
code: crypto.randomBytes(4).toString("hex"), // 8 characters
consumed: false,
expired: false,
});
backup_codes.push(code);
}
return backup_codes;
}
+16 -10
View File
@@ -8,27 +8,30 @@ export class BaseClassWithoutId extends BaseEntity {
this.assign(props);
}
private get construct(): any {
/*private get construct(): any {
return this.constructor;
}
}*/
private get metadata() {
return this.construct.getRepository().metadata as EntityMetadata;
}
/*private get metadata() {
console.log("getMetadata")
return dataSource.getRepository(this.constructor).metadata as EntityMetadata;
//return this.construct.getRepository().metadata as EntityMetadata;
}*/
assign(props: any = {}) {
//console.log(`assign (${typeof this})...`)
delete props.opts;
delete props.props;
const properties = new Set(
/*const properties = new Set(
this.metadata.columns
.map((x: any) => x.propertyName)
.concat(this.metadata.relations.map((x) => x.propertyName))
);
);*/
// will not include relational properties
for (const key in props) {
if (!properties.has(key)) continue;
//if (!properties.has(key)) continue;
// @ts-ignore
const setter = this[`set${key.capitalize()}`]; // use setter function if it exists
@@ -41,7 +44,8 @@ export class BaseClassWithoutId extends BaseEntity {
}
}
toJSON(): any {
/*toJSON(): any {
console.log("toJSON...")
return Object.fromEntries(
this.metadata.columns // @ts-ignore
.map((x) => [x.propertyName, this[x.propertyName]]) // @ts-ignore
@@ -54,6 +58,7 @@ export class BaseClassWithoutId extends BaseEntity {
propertyPath: string,
value: number | string
) {
console.log("increment...")
const repository = this.getRepository();
return repository.increment(conditions, propertyPath, value);
}
@@ -63,9 +68,10 @@ export class BaseClassWithoutId extends BaseEntity {
propertyPath: string,
value: number | string
) {
console.log("increment...")
const repository = this.getRepository();
return repository.decrement(conditions, propertyPath, value);
}
}*/
}
export const PrimaryIdColumn = process.env.DATABASE?.startsWith("mongodb") ? ObjectIdColumn : PrimaryColumn;
+7 -4
View File
@@ -122,17 +122,19 @@ export class Member extends BaseClassWithoutId {
}
static async removeFromGuild(user_id: string, guild_id: string) {
const guild = await Guild.findOneOrFail({ select: ["owner_id"], where: { id: guild_id } });
const guild = await Guild.findOneOrFail({ select: ["owner_id", "member_count"], where: { id: guild_id } });
if (guild.owner_id === user_id) throw new Error("The owner cannot be removed of the guild");
const member = await Member.findOneOrFail({ where: { id: user_id, guild_id }, relations: ["user"] });
// use promise all to execute all promises at the same time -> save time
//TODO: check for bugs
if(guild.member_count) guild.member_count--;
return Promise.all([
Member.delete({
id: user_id,
guild_id,
}),
Guild.decrement({ id: guild_id }, "member_count", -1),
//Guild.decrement({ id: guild_id }, "member_count", -1),
emitEvent({
event: "GUILD_DELETE",
@@ -259,7 +261,8 @@ export class Member extends BaseClassWithoutId {
mute: false,
pending: false,
};
//TODO: check for bugs
if(guild.member_count) guild.member_count++;
await Promise.all([
new Member({
...member,
@@ -276,7 +279,7 @@ export class Member extends BaseClassWithoutId {
},
// Member.save is needed because else the roles relations wouldn't be updated
}).save(),
Guild.increment({ id: guild_id }, "member_count", 1),
//Guild.increment({ id: guild_id }, "member_count", 1),
emitEvent({
event: "GUILD_MEMBER_ADD",
data: {
+1
View File
@@ -4,3 +4,4 @@ export * from "./util/index";
export * from "./interfaces/index";
export * from "./entities/index";
export * from "./dtos/index";
export * from "./util/MFA";
@@ -1,13 +0,0 @@
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"`);
}
}
@@ -1,23 +0,0 @@
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`);
}
}
@@ -1,19 +0,0 @@
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`
);
}
}
@@ -1,66 +0,0 @@
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> {}
}
@@ -1,11 +0,0 @@
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> {}
}
@@ -1,15 +0,0 @@
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> {}
}
@@ -1,16 +0,0 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class ReleaseTypo1648643945733 implements MigrationInterface {
name = "ReleaseTypo1648643945733";
public async up(queryRunner: QueryRunner): Promise<void> {
//drop table first because typeorm creates it before migrations run
await queryRunner.dropTable("client_release", true);
await queryRunner.renameTable("client_relase", "client_release");
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable("client_relase", true);
await queryRunner.renameTable("client_release", "client_relase");
}
}
File diff suppressed because it is too large Load Diff
+67 -39
View File
@@ -1,72 +1,100 @@
import path from "path";
import "reflect-metadata";
import { DataSource, createConnection } from "typeorm";
import { DataSource, createConnection, DataSourceOptions, PrimaryColumn, PrimaryGeneratedColumn } from "typeorm";
import * as Models from "../entities";
import { Migration } from "../entities/Migration";
import { yellow, green, red } from "picocolors";
import fs from "fs";
import { exit } from "process";
import { BaseClass, BaseClassWithoutId } from "../entities";
// UUID extension option is only supported with postgres
// We want to generate all id's with Snowflakes that's why we have our own BaseEntity class
let promise: Promise<any>;
let dataSource: DataSource | undefined;
let dbConnectionString = process.env.DATABASE || path.join(process.cwd(), "database.db");
let verbose_db = false;
let dataSource: DataSource;
export async function initDatabase(): Promise<DataSource> {
if (dataSource) return dataSource; // prevent initalizing multiple times
let dso = getDataSourceOptions();
console.log(`[Database] ${yellow(`Connecting to ${dso.type} database...`)}`);
//promise = dataSource.initialize();
//await promise;
console.log(`[Database] ${green("Connected!")}`);
return promise;
}
export function closeDatabase() {
dataSource?.destroy();
}
function getDataSourceOptions(): DataSourceOptions {
//get connection string and check for migrations
const dbConnectionString = process.env.DATABASE || path.join(process.cwd(), "database.db");
const type = dbConnectionString.includes("://") ? dbConnectionString.split(":")[0]?.replace("+srv", "") : "sqlite" as any;
const isSqlite = type.includes("sqlite");
if(process.env.DB_VERBOSE) verbose_db = true;
console.log(`[Database] ${yellow(`connecting to ${type} db`)}`);
const migrationsExist = fs.existsSync(path.join(__dirname, "..", "migrations", type));
//read env vars
const synchronizeInsteadOfMigrations = "DB_UNSAFE" in process.env;
const verboseDb = "DB_VERBOSE" in process.env;
if(isSqlite)
console.log(`[Database] ${red(`You are running sqlite! Please keep in mind that we recommend setting up a dedicated database!`)}`);
if(verbose_db)
console.log(`[Database] ${red(`Verbose database logging is enabled, this might impact performance! Unset VERBOSE_DB to disable.`)}`);
// @ts-ignore
dataSource = new DataSource({
if(verboseDb)
console.log(`[Database] ${red(`Verbose database logging is enabled, this might impact performance! Unset DB_VERBOSE to disable.`)}`);
if(synchronizeInsteadOfMigrations){
console.log(`[Database] ${red(`Unsafe database upgrades are enabled! We are not responsible for broken databases! Unset DB_UNSAFE to disable.`)}`);
}
else if(!migrationsExist) {
console.log(`[Database] ${red(`Database engine not supported! Set UNSAFE_DB to bypass.`)}`);
console.log(`[Database] ${red(`Please mention this to Fosscord developers, and provide this info:`)}`);
console.log(`[Database]\n${red(JSON.stringify({
db_type: type,
migrations_exist: migrationsExist
}, null, 4))}`);
//exit(1);
}
return {
type,
charset: 'utf8mb4',
url: isSqlite ? undefined : dbConnectionString,
database: isSqlite ? dbConnectionString : undefined,
// @ts-ignore
entities: Object.values(Models).filter((x) => x.constructor.name !== "Object" && x.name),
synchronize: type !== "mongodb",
logging: verbose_db,
//entities: Object.values(Models).filter((x) => x.constructor.name !== "Object" && x.constructor.name !== "Array" && x.constructor.name !== "BigInt" && x).map(x=>x.name),
entities: Object.values(Models).filter((x) => x.constructor.name == "Function" && shouldIncludeEntity(x.name)),
synchronize: synchronizeInsteadOfMigrations,
logging: verboseDb,
cache: {
duration: 1000 * 3, // cache all find queries for 3 seconds
},
bigNumberStrings: false,
supportBigNumbers: true,
name: "default",
migrations: [path.join(__dirname, "..", "migrations", "*.js")],
});
promise = dataSource.initialize();
await promise;
// run migrations, and if it is a new fresh database, set it to the last migration
if (dataSource.migrations.length) {
if (!(await Migration.findOne({}))) {
let i = 0;
await Migration.insert(
dataSource.migrations.map((x) => ({
id: i++,
name: x.name,
timestamp: Date.now(),
}))
);
migrations: synchronizeInsteadOfMigrations ? [] : [path.join(__dirname, "..", "migrations", type, "*.js")],
migrationsRun: !synchronizeInsteadOfMigrations,
cli: {
migrationsDir: `src/migrations/${type}`
}
}
await dataSource.runMigrations();
console.log(`[Database] ${green("connected")}`);
return promise;
} as DataSourceOptions;
}
export { dataSource };
export function closeDatabase() {
dataSource?.destroy();
function shouldIncludeEntity(name: string): boolean {
return ![
BaseClassWithoutId,
PrimaryColumn,
BaseClass,
PrimaryGeneratedColumn
].map(x=>x.name).includes(name);
}
export default dataSource = new DataSource(getDataSourceOptions());
-72
View File
@@ -1,72 +0,0 @@
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, red } from "picocolors";
// UUID extension option is only supported with postgres
// We want to generate all id's with Snowflakes that's why we have our own BaseEntity class
let promise: Promise<any>;
let dbConnection: Connection | undefined;
let dbConnectionString = process.env.DATABASE || path.join(process.cwd(), "database.db");
export function initDatabase(): Promise<Connection> {
if (promise) return promise; // prevent initalizing multiple times
const type = dbConnectionString.includes("://") ? dbConnectionString.split(":")[0]?.replace("+srv", "") : "sqlite";
const isSqlite = type.includes("sqlite");
console.log(`[Database] ${yellow(`connecting to ${type} db`)}`);
if(isSqlite) {
console.log(`[Database] ${red(`You are running sqlite! Please keep in mind that we recommend setting up a dedicated database!`)}`);
}
// @ts-ignore
promise = createConnection({
type,
charset: 'utf8mb4',
url: isSqlite ? undefined : dbConnectionString,
database: isSqlite ? dbConnectionString : undefined,
// @ts-ignore
entities: Object.values(Models).filter((x) => x.constructor.name !== "Object" && x.name),
synchronize: type !== "mongodb",
logging: false,
cache: {
duration: 1000 * 3, // cache all find queries for 3 seconds
},
bigNumberStrings: false,
supportBigNumbers: true,
name: "default",
migrations: [path.join(__dirname, "..", "migrations", "*.js")],
});
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")}`);
});
return promise;
}
export { dbConnection };
export function closeDatabase() {
dbConnection?.close();
}
+17
View File
@@ -0,0 +1,17 @@
import crypto from "crypto";
import { BackupCode } from "../entities/BackupCodes";
export function generateMfaBackupCodes(user_id: string) {
let backup_codes: BackupCode[] = [];
for (let i = 0; i < 10; i++) {
const code = BackupCode.create({
user: { id: user_id },
code: crypto.randomBytes(4).toString("hex"), // 8 characters
consumed: false,
expired: false,
});
backup_codes.push(code);
}
return backup_codes;
}