WIP - Fix objection and model registry
This commit is contained in:
@@ -5,6 +5,7 @@ import { BaseModel } from './base.model';
|
||||
import { ModelRegistry } from './model.registry';
|
||||
import { ObjectMetadata } from './dynamic-model.factory';
|
||||
import { TenantDatabaseService } from '../../tenant/tenant-database.service';
|
||||
import { UserModel, RoleModel, PermissionModel } from './system-models';
|
||||
|
||||
/**
|
||||
* Service to manage dynamic models for a specific tenant
|
||||
@@ -21,11 +22,30 @@ export class ModelService {
|
||||
*/
|
||||
getTenantRegistry(tenantId: string): ModelRegistry {
|
||||
if (!this.tenantRegistries.has(tenantId)) {
|
||||
this.tenantRegistries.set(tenantId, new ModelRegistry());
|
||||
const registry = new ModelRegistry();
|
||||
|
||||
// Register system models that are defined as static Objection models
|
||||
this.registerSystemModels(registry);
|
||||
|
||||
this.tenantRegistries.set(tenantId, registry);
|
||||
}
|
||||
return this.tenantRegistries.get(tenantId)!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register static system models in the registry
|
||||
* Uses simplified models without complex relationMappings to avoid modelPath issues
|
||||
*/
|
||||
private registerSystemModels(registry: ModelRegistry): void {
|
||||
// Register system models by their API name (used in referenceObject fields)
|
||||
// These are simplified versions without relationMappings to avoid dependency issues
|
||||
registry.registerModel('User', UserModel as any);
|
||||
registry.registerModel('Role', RoleModel as any);
|
||||
registry.registerModel('Permission', PermissionModel as any);
|
||||
|
||||
this.logger.debug('Registered system models: User, Role, Permission');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and register a model for a tenant
|
||||
*/
|
||||
@@ -60,6 +80,24 @@ export class ModelService {
|
||||
): Promise<ModelClass<BaseModel>> {
|
||||
const knex = await this.tenantDbService.getTenantKnexById(tenantId);
|
||||
const model = this.getModel(tenantId, objectApiName);
|
||||
|
||||
// Bind knex to the model and also to all models in the registry
|
||||
// This ensures system models also have knex bound when they're used in relations
|
||||
const registry = this.getTenantRegistry(tenantId);
|
||||
const allModels = registry.getAllModelNames();
|
||||
|
||||
// Bind knex to all models to ensure relations work
|
||||
for (const modelName of allModels) {
|
||||
try {
|
||||
const m = registry.getModel(modelName);
|
||||
if (m && !m.knex()) {
|
||||
m.knex(knex);
|
||||
}
|
||||
} catch (error) {
|
||||
// Ignore errors for models that don't need binding
|
||||
}
|
||||
}
|
||||
|
||||
return model.bindKnex(knex);
|
||||
}
|
||||
|
||||
@@ -78,4 +116,69 @@ export class ModelService {
|
||||
const registry = this.getTenantRegistry(tenantId);
|
||||
return registry.getAllModelNames();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure a model is registered with all its dependencies.
|
||||
* This method handles recursive model creation for related objects.
|
||||
*
|
||||
* @param tenantId - The tenant ID
|
||||
* @param objectApiName - The object API name to ensure registration for
|
||||
* @param fetchMetadata - Callback function to fetch object metadata (provided by ObjectService)
|
||||
* @param visited - Set to track visited models and prevent infinite loops
|
||||
*/
|
||||
async ensureModelWithDependencies(
|
||||
tenantId: string,
|
||||
objectApiName: string,
|
||||
fetchMetadata: (apiName: string) => Promise<ObjectMetadata>,
|
||||
visited: Set<string> = new Set(),
|
||||
): Promise<void> {
|
||||
// Prevent infinite recursion
|
||||
if (visited.has(objectApiName)) {
|
||||
return;
|
||||
}
|
||||
visited.add(objectApiName);
|
||||
|
||||
// Check if model already exists
|
||||
if (this.hasModel(tenantId, objectApiName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Fetch the object metadata
|
||||
const objectMetadata = await fetchMetadata(objectApiName);
|
||||
|
||||
// Extract lookup fields to find dependencies
|
||||
const lookupFields = objectMetadata.fields.filter(
|
||||
f => f.type === 'LOOKUP' && f.referenceObject
|
||||
);
|
||||
|
||||
// Recursively ensure all dependent models are registered first
|
||||
for (const field of lookupFields) {
|
||||
if (field.referenceObject) {
|
||||
try {
|
||||
await this.ensureModelWithDependencies(
|
||||
tenantId,
|
||||
field.referenceObject,
|
||||
fetchMetadata,
|
||||
visited,
|
||||
);
|
||||
} catch (error) {
|
||||
// If related object doesn't exist (e.g., system tables), skip it
|
||||
this.logger.debug(
|
||||
`Skipping registration of related model ${field.referenceObject}: ${error.message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now create and register this model (all dependencies are ready)
|
||||
await this.createModelForObject(tenantId, objectMetadata);
|
||||
this.logger.log(`Registered model for ${objectApiName} in tenant ${tenantId}`);
|
||||
} catch (error) {
|
||||
this.logger.warn(
|
||||
`Failed to ensure model for ${objectApiName}: ${error.message}`
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user