very-express
Version:
based on json schema and openapi, generate Express RestApi server with mongoDb
176 lines (156 loc) • 7.27 kB
text/typescript
// {{headerComment}}
import { Request, Response, NextFunction } from "express";
import { DataSource, EntityTarget, ObjectLiteral } from "typeorm";
import mongoose from "mongoose";
// import { Document } from "mongoose";
// import { sqlMigrations } from "../_models/sqlMigration.gen";
import { VexRepository } from "../_types/vex";
import { TypeOrmRepositoryAdapter } from "./TypeOrmRepositoryAdapter.gen";
// import { MongooseRepositoryAdapter } from "./MongooseRepositoryAdapter.gen";
import utils from "./../_utils";
export class VexDbConnector {
private sqlUrl: string;
private sqlCa: string;
private mongoUrl: string;
private recordAccessLog: boolean = false;
public dataSource?: DataSource;
constructor(options: {
sqlUrl?: string;
sqlCa?: string;
mongoUrl?: string;
recordAccessLog?: boolean;
}) {
this.sqlUrl = options.sqlUrl || "";
this.sqlCa = options.sqlCa || "";
this.mongoUrl = options.mongoUrl || "";
this.recordAccessLog = options.recordAccessLog || false;
this.middleware = this.middleware.bind(this);
}
connect(): void {
if (this.sqlUrl) this.connectSql();
if (this.mongoUrl) this.connectMongo();
}
close(): void {
if (this.sqlUrl) this.closeSql();
if (this.mongoUrl) this.closeMongo();
}
connectSql(): void {
utils.log.infoSql("Connecting to SQL DB (TypeORM)...");
const sqlUrl = new URL(this.sqlUrl);
const ca = !this.sqlCa ? undefined : this.sqlCa.startsWith("-----BEGIN") ? this.sqlCa : Buffer.from(this.sqlCa, "base64").toString("utf8");
const insecure = (process.env.SQL_INSECURE_TLS || "").toLowerCase() === "true";
const ssl = {
rejectUnauthorized: insecure ? false : true,
ca,
};
const ds = new DataSource({
type: "postgres",
host: sqlUrl.hostname,
port: Number(sqlUrl.port) || 5432,
username: sqlUrl.username,
password: sqlUrl.password,
database: sqlUrl.pathname.slice(1),
ssl,
synchronize: (process.env.SQL_SYNCHRONIZE || "").toLowerCase() === "true",
logging: false,
entities: [__dirname + "/../_models/*.gen.{ts,js}"],
migrations: [],
});
const connectWithRetry = (retryTime = 10): void => {
ds.initialize()
.then(() => {
this.dataSource = ds;
utils.log.infoSql("TypeORM DataSource initialized");
// this.runSqlMigrations(ds);
})
.catch((err) => {
utils.log.errorSql(`Failed to initialize TypeORM, retrying in ${retryTime}s`, err);
setTimeout(() => connectWithRetry(retryTime), retryTime * 1000);
});
};
connectWithRetry(10);
}
// private async runSqlMigrations(ds: DataSource): Promise<void> {
// if (!sqlMigrations.length) return;
// const runner = ds.createQueryRunner();
// await runner.connect();
// try {
// for (const sql of sqlMigrations) {
// utils.log.infoSql(`Applying migration: ${sql}`);
// await runner.query(sql);
// }
// utils.log.infoSql(`${sqlMigrations.length} migrations applied`);
// } catch (err) {
// utils.log.errorSql("SQL migration failed", err);
// } finally {
// await runner.release();
// }
// }
closeSql(): void {
if (this.dataSource?.isInitialized) {
this.dataSource.destroy()
.then(() => utils.log.infoSql("TypeORM DataSource closed"))
.catch((err) => utils.log.errorSql("Error closing TypeORM DataSource", err));
}
}
// TODO: complete mongoose support, handle join and select
connectMongo(): void {
console.warn("VeryExpress unified db adapter (MongooseRepositoryAdapter) for Mongodb are under development, use with caution. Contribute to \"https://github.com/Justin26l/VeryExpress\"");
utils.log.infoMongo("Connecting to MongoDB (Driver: Mongoose)...");
const connectWithRetry = (retryTime = 10): void => {
mongoose.connect(this.mongoUrl)
.then(() => utils.log.infoMongo("Mongoose connected"))
.catch((err) => {
utils.log.errorMongo(`Failed to connect Mongoose, retrying in ${retryTime}s`, err);
setTimeout(() => connectWithRetry(retryTime), retryTime * 1000);
});
};
connectWithRetry(10);
}
// TODO: complete mongoose support, handle join and select
closeMongo(): void {
console.warn("VeryExpress unified db adapter (MongooseRepositoryAdapter) for Mongodb are under development, use with caution. Contribute to \"https://github.com/Justin26l/VeryExpress\"");
mongoose.disconnect()
.then(() => utils.log.infoMongo("Mongoose disconnected"))
.catch((err) => utils.log.errorMongo("Error disconnecting Mongoose", err));
}
// getRepository<T extends Document>(target: { schema: unknown; modelName: string }): VexRepository<T>;
getRepository<T extends ObjectLiteral>(target: EntityTarget<T>): VexRepository<T>;
getRepository<T>(target: EntityTarget<ObjectLiteral> | any): VexRepository<T> {
// TODO: complete mongoose support, handle join and select
if (target?.schema && target?.modelName) {
console.warn("VeryExpress unified db adapter (MongooseRepositoryAdapter) for Mongodb are under development, use with caution. Contribute to \"https://github.com/Justin26l/VeryExpress\"");
// return new MongooseRepositoryAdapter<T & Document>(target);
}
if (!this.dataSource) throw new Error("SQL DataSource not initialized");
return new TypeOrmRepositoryAdapter<T & ObjectLiteral>(this.dataSource.getRepository(target));
}
async middleware(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
if (this.sqlUrl && (!this.dataSource || !this.dataSource.isInitialized)) {
utils.response.send(res, 503, { code: utils.response.code.DB_CONN_ERR });
return;
}
if (this.recordAccessLog && this.dataSource?.isInitialized) {
const logEntry = {
timestamp: new Date().getTime(),
ipa: req.socket.remoteAddress || req.headers["x-forwarded-for"],
method: req.method,
url: req.url.split("?")[0],
headers: JSON.stringify(req.headers),
query: JSON.stringify(req.query),
};
try {
await this.dataSource.getRepository("AccessLog").save(logEntry);
}
catch { /* table may not exist */ }
}
next();
}
catch (err: unknown) {
utils.response.send(res, 500, { code: utils.response.code.DB_CONN_ERR });
return;
}
}
}
export default VexDbConnector;