@diagramers/api
Version:
Diagramers API - A comprehensive Node.js API template with TypeScript, Firebase Functions, and Socket.io
280 lines (238 loc) • 8.53 kB
text/typescript
import { DatabaseConfig } from '../config/interfaces';
import { logger } from '../logging';
import { DatabaseSeeder } from './seeder';
import * as mongoose from 'mongoose';
import * as fs from 'fs';
import * as path from 'path';
export interface DatabaseConnection {
connect(): Promise<void>;
disconnect(): Promise<void>;
isConnected(): boolean;
getConnection(): any;
createCollections(): Promise<void>;
}
export class MongoDBConnection implements DatabaseConnection {
private connected = false;
private connectionString: string;
constructor(connectionString: string) {
this.connectionString = connectionString;
}
async connect(): Promise<void> {
try {
logger.info('[MongoDB] Connecting to database...');
// Set mongoose options
mongoose.set('strictQuery', false);
// Connect to MongoDB
await mongoose.connect(this.connectionString, {
maxPoolSize: 10,
serverSelectionTimeoutMS: 5000,
socketTimeoutMS: 45000,
});
// Handle connection events
mongoose.connection.on('connected', () => {
logger.info('[MongoDB] Connected to database successfully');
this.connected = true;
});
mongoose.connection.on('error', (err) => {
logger.error('[MongoDB] Database connection error:', err);
this.connected = false;
});
mongoose.connection.on('disconnected', () => {
logger.info('[MongoDB] Database disconnected');
this.connected = false;
});
// Handle process termination
process.on('SIGINT', async () => {
await this.disconnect();
process.exit(0);
});
} catch (error) {
logger.error('[MongoDB] Failed to connect to database:', error);
throw error;
}
}
async disconnect(): Promise<void> {
try {
logger.info('[MongoDB] Disconnecting from database...');
await mongoose.disconnect();
this.connected = false;
logger.info('[MongoDB] Disconnected from database successfully');
} catch (error) {
logger.error('[MongoDB] Error disconnecting from database:', error);
throw error;
}
}
isConnected(): boolean {
return mongoose.connection.readyState === 1;
}
getConnection(): any {
return mongoose.connection;
}
async createCollections(): Promise<void> {
try {
logger.info('[MongoDB] Creating collections and indexes...');
// Get all registered models
const models = mongoose.modelNames();
for (const modelName of models) {
try {
const model = mongoose.model(modelName);
const collectionName = model.collection.name;
// Create collection if it doesn't exist
const collections = await mongoose.connection.db.listCollections({ name: collectionName }).toArray();
if (collections.length === 0) {
await mongoose.connection.createCollection(collectionName);
logger.info(`[MongoDB] Created collection: ${collectionName}`);
} else {
logger.debug(`[MongoDB] Collection already exists: ${collectionName}`);
}
// Create indexes
if (model.schema.indexes) {
for (const index of model.schema.indexes()) {
try {
await model.collection.createIndex(index[0] as any, index[1] as any || {});
logger.debug(`[MongoDB] Created index for ${collectionName}: ${JSON.stringify(index[0])}`);
} catch (indexError: any) {
// Index might already exist, which is fine
if (indexError.code !== 85) { // 85 is "Index already exists" error
logger.warn(`[MongoDB] Failed to create index for ${collectionName}:`, indexError.message);
}
}
}
}
} catch (modelError) {
logger.warn(`[MongoDB] Error processing model ${modelName}:`, modelError);
}
}
logger.info('[MongoDB] Collections and indexes created successfully');
} catch (error) {
logger.error('[MongoDB] Error creating collections:', error);
throw error;
}
}
}
export class MockDatabaseConnection implements DatabaseConnection {
private connected = false;
async connect(): Promise<void> {
logger.info('[MockDatabase] Pretending to connect to database...');
this.connected = true;
}
async disconnect(): Promise<void> {
logger.info('[MockDatabase] Pretending to disconnect from database...');
this.connected = false;
}
isConnected(): boolean {
return this.connected;
}
getConnection(): any {
return null;
}
async createCollections(): Promise<void> {
logger.info('[MockDatabase] Pretending to create collections...');
}
}
export class DatabaseManager {
private connections: Map<string, DatabaseConnection> = new Map();
private config: DatabaseConfig;
private seeder: DatabaseSeeder;
constructor(config: DatabaseConfig) {
this.config = config;
this.seeder = new DatabaseSeeder(this, config);
}
async initialize(): Promise<void> {
try {
logger.info('[DatabaseManager] Initializing database connections...');
await this.createConnection('main');
// Create collections and indexes
const mainConnection = this.getConnection('main');
if (mainConnection) {
await mainConnection.createCollections();
}
// Run seeding if enabled
if (process.env.DB_SEED === 'true') {
await this.seeder.seed({
force: process.env.DB_SEED_FORCE === 'true',
truncate: process.env.DB_SEED_TRUNCATE === 'true',
environment: process.env.NODE_ENV
});
}
logger.info('[DatabaseManager] Database connections initialized successfully');
} catch (error) {
logger.error('[DatabaseManager] Failed to initialize database connections:', error);
throw error;
}
}
async createConnection(name: string): Promise<DatabaseConnection> {
// Use MongoDB connection for main database
if (name === 'main') {
let connectionString = process.env.MONGODB_URI;
if (!connectionString) {
// Build connection string from config
if (this.config.dialect === 'mongodb') {
const { host, port, username, password, database } = this.config;
if (username && password) {
connectionString = `mongodb://${username}:${password}@${host}:${port}/${database}`;
} else {
connectionString = `mongodb://${host}:${port}/${database}`;
}
} else {
connectionString = 'mongodb://localhost:27017/diagramers';
}
}
const connection = new MongoDBConnection(connectionString);
await connection.connect();
this.connections.set(name, connection);
return connection;
}
// Use mock connection for other databases (if needed)
const connection = new MockDatabaseConnection();
await connection.connect();
this.connections.set(name, connection);
return connection;
}
getConnection(name: string = 'main'): DatabaseConnection | undefined {
return this.connections.get(name);
}
/**
* Get the database seeder
*/
getSeeder(): DatabaseSeeder {
return this.seeder;
}
/**
* Run database seeding
*/
async seed(options?: any): Promise<void> {
return this.seeder.seed(options);
}
/**
* Reset database (truncate and reseed)
*/
async reset(): Promise<void> {
return this.seeder.reset();
}
/**
* Create collections for all registered models
*/
async createAllCollections(): Promise<void> {
for (const [name, connection] of this.connections) {
try {
await connection.createCollections();
logger.info(`[DatabaseManager] Created collections for connection: ${name}`);
} catch (error) {
logger.error(`[DatabaseManager] Error creating collections for ${name}:`, error);
}
}
}
async closeAll(): Promise<void> {
logger.info('[DatabaseManager] Closing all database connections...');
for (const [name, connection] of this.connections) {
try {
await connection.disconnect();
logger.info(`[DatabaseManager] Closed connection: ${name}`);
} catch (error) {
logger.error(`[DatabaseManager] Error closing connection ${name}:`, error);
}
}
this.connections.clear();
}
}