Added auth functionality, initial work with views and field types
This commit is contained in:
132
backend/src/tenant/tenant-database.service.ts
Normal file
132
backend/src/tenant/tenant-database.service.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Knex, knex } from 'knex';
|
||||
import { getCentralPrisma } from '../prisma/central-prisma.service';
|
||||
import * as crypto from 'crypto';
|
||||
|
||||
@Injectable()
|
||||
export class TenantDatabaseService {
|
||||
private readonly logger = new Logger(TenantDatabaseService.name);
|
||||
private tenantConnections: Map<string, Knex> = new Map();
|
||||
|
||||
async getTenantKnex(tenantIdOrSlug: string): Promise<Knex> {
|
||||
if (this.tenantConnections.has(tenantIdOrSlug)) {
|
||||
return this.tenantConnections.get(tenantIdOrSlug);
|
||||
}
|
||||
|
||||
const centralPrisma = getCentralPrisma();
|
||||
|
||||
// Try to find tenant by ID first, then by slug
|
||||
let tenant = await centralPrisma.tenant.findUnique({
|
||||
where: { id: tenantIdOrSlug },
|
||||
});
|
||||
|
||||
if (!tenant) {
|
||||
tenant = await centralPrisma.tenant.findUnique({
|
||||
where: { slug: tenantIdOrSlug },
|
||||
});
|
||||
}
|
||||
|
||||
if (!tenant) {
|
||||
throw new Error(`Tenant ${tenantIdOrSlug} not found`);
|
||||
}
|
||||
|
||||
if (tenant.status !== 'active') {
|
||||
throw new Error(`Tenant ${tenantIdOrSlug} is not active`);
|
||||
}
|
||||
|
||||
// Decrypt password
|
||||
const decryptedPassword = this.decryptPassword(tenant.dbPassword);
|
||||
|
||||
const tenantKnex = knex({
|
||||
client: 'mysql2',
|
||||
connection: {
|
||||
host: tenant.dbHost,
|
||||
port: tenant.dbPort,
|
||||
user: tenant.dbUsername,
|
||||
password: decryptedPassword,
|
||||
database: tenant.dbName,
|
||||
},
|
||||
pool: {
|
||||
min: 2,
|
||||
max: 10,
|
||||
},
|
||||
});
|
||||
|
||||
// Test connection
|
||||
try {
|
||||
await tenantKnex.raw('SELECT 1');
|
||||
this.logger.log(`Connected to tenant database: ${tenant.dbName}`);
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Failed to connect to tenant database: ${tenant.dbName}`,
|
||||
error,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
|
||||
this.tenantConnections.set(tenantIdOrSlug, tenantKnex);
|
||||
return tenantKnex;
|
||||
}
|
||||
|
||||
async getTenantByDomain(domain: string): Promise<any> {
|
||||
const centralPrisma = getCentralPrisma();
|
||||
const domainRecord = await centralPrisma.domain.findUnique({
|
||||
where: { domain },
|
||||
include: { tenant: true },
|
||||
});
|
||||
|
||||
if (!domainRecord) {
|
||||
throw new Error(`Domain ${domain} not found`);
|
||||
}
|
||||
|
||||
if (domainRecord.tenant.status !== 'active') {
|
||||
throw new Error(`Tenant for domain ${domain} is not active`);
|
||||
}
|
||||
|
||||
return domainRecord.tenant;
|
||||
}
|
||||
|
||||
async disconnectTenant(tenantId: string) {
|
||||
const connection = this.tenantConnections.get(tenantId);
|
||||
if (connection) {
|
||||
await connection.destroy();
|
||||
this.tenantConnections.delete(tenantId);
|
||||
this.logger.log(`Disconnected tenant: ${tenantId}`);
|
||||
}
|
||||
}
|
||||
|
||||
removeTenantConnection(tenantId: string) {
|
||||
this.tenantConnections.delete(tenantId);
|
||||
this.logger.log(`Removed tenant connection from cache: ${tenantId}`);
|
||||
}
|
||||
|
||||
async disconnectAll() {
|
||||
for (const [tenantId, connection] of this.tenantConnections.entries()) {
|
||||
await connection.destroy();
|
||||
}
|
||||
this.tenantConnections.clear();
|
||||
this.logger.log('Disconnected all tenant connections');
|
||||
}
|
||||
|
||||
encryptPassword(password: string): string {
|
||||
const algorithm = 'aes-256-cbc';
|
||||
const key = Buffer.from(process.env.ENCRYPTION_KEY, 'hex');
|
||||
const iv = crypto.randomBytes(16);
|
||||
const cipher = crypto.createCipheriv(algorithm, key, iv);
|
||||
let encrypted = cipher.update(password, 'utf8', 'hex');
|
||||
encrypted += cipher.final('hex');
|
||||
return iv.toString('hex') + ':' + encrypted;
|
||||
}
|
||||
|
||||
private decryptPassword(encryptedPassword: string): string {
|
||||
const algorithm = 'aes-256-cbc';
|
||||
const key = Buffer.from(process.env.ENCRYPTION_KEY, 'hex');
|
||||
const parts = encryptedPassword.split(':');
|
||||
const iv = Buffer.from(parts[0], 'hex');
|
||||
const encrypted = parts[1];
|
||||
const decipher = crypto.createDecipheriv(algorithm, key, iv);
|
||||
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
||||
decrypted += decipher.final('utf8');
|
||||
return decrypted;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user