feat: Enhance BaseModel with improved shadowing property cleanup and validation during save operations

This commit is contained in:
anonpenguin 2025-06-19 13:54:54 +03:00
parent abb9734b36
commit bac55a5e0c

View File

@ -60,6 +60,7 @@ export abstract class BaseModel {
for (const [fieldName] of modelClass.fields) { for (const [fieldName] of modelClass.fields) {
// If there's an instance property, remove it and create a working getter // If there's an instance property, remove it and create a working getter
if (this.hasOwnProperty(fieldName)) { if (this.hasOwnProperty(fieldName)) {
const oldValue = (this as any)[fieldName];
delete (this as any)[fieldName]; delete (this as any)[fieldName];
// Define a working getter directly on the instance // Define a working getter directly on the instance
@ -80,11 +81,28 @@ export abstract class BaseModel {
} }
} }
private autoGenerateRequiredFields(): void {
const modelClass = this.constructor as typeof BaseModel;
// Auto-generate slug for Post models if missing
if (modelClass.name === 'Post') {
const titleValue = this.getFieldValue('title');
const slugValue = this.getFieldValue('slug');
if (titleValue && !slugValue) {
// Generate a temporary slug before validation (will be refined in AfterCreate)
const tempSlug = titleValue.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '') + '-temp';
this.setFieldValue('slug', tempSlug);
}
}
}
// Core CRUD operations // Core CRUD operations
async save(): Promise<this> { async save(): Promise<this> {
await this.validate();
if (this._isNew) { if (this._isNew) {
// Clean up any instance properties before hooks run
this.cleanupShadowingProperties();
await this.beforeCreate(); await this.beforeCreate();
// Clean up any instance properties created by hooks // Clean up any instance properties created by hooks
@ -100,6 +118,18 @@ export abstract class BaseModel {
this.setFieldValue('createdAt', now); this.setFieldValue('createdAt', now);
this.setFieldValue('updatedAt', now); this.setFieldValue('updatedAt', now);
// Clean up any additional shadowing properties after setting timestamps
this.cleanupShadowingProperties();
// Auto-generate required fields that have hooks to generate them
this.autoGenerateRequiredFields();
// Clean up any shadowing properties after auto-generation
this.cleanupShadowingProperties();
// Validate after all field generation is complete
await this.validate();
// Save to database (will be implemented when database manager is ready) // Save to database (will be implemented when database manager is ready)
await this._saveToDatabase(); await this._saveToDatabase();
@ -116,6 +146,9 @@ export abstract class BaseModel {
// Set timestamp using Field setter // Set timestamp using Field setter
this.setFieldValue('updatedAt', Date.now()); this.setFieldValue('updatedAt', Date.now());
// Validate after hooks have run
await this.validate();
// Update in database // Update in database
await this._updateInDatabase(); await this._updateInDatabase();
@ -408,6 +441,8 @@ export abstract class BaseModel {
for (const [fieldName, fieldConfig] of modelClass.fields) { for (const [fieldName, fieldConfig] of modelClass.fields) {
const privateKey = `_${fieldName}`; const privateKey = `_${fieldName}`;
const value = (this as any)[privateKey]; const value = (this as any)[privateKey];
const fieldErrors = this.validateField(fieldName, value, fieldConfig); const fieldErrors = this.validateField(fieldName, value, fieldConfig);
errors.push(...fieldErrors); errors.push(...fieldErrors);
} }
@ -614,6 +649,22 @@ export abstract class BaseModel {
return values; return values;
} }
// Ensure user databases exist for user-scoped models
private async ensureUserDatabasesExist(framework: any, userId: string): Promise<void> {
try {
// Try to get user databases - if this fails, they don't exist
await framework.databaseManager.getUserMappings(userId);
} catch (error) {
// If user not found, create databases for them
if (error.message.includes('not found in directory')) {
console.log(`Creating databases for user ${userId}`);
await framework.databaseManager.createUserDatabases(userId);
} else {
throw error;
}
}
}
// Database operations integrated with DatabaseManager // Database operations integrated with DatabaseManager
private async _saveToDatabase(): Promise<void> { private async _saveToDatabase(): Promise<void> {
const framework = this.getFrameworkInstance(); const framework = this.getFrameworkInstance();
@ -626,12 +677,18 @@ export abstract class BaseModel {
try { try {
if (modelClass.scope === 'user') { if (modelClass.scope === 'user') {
// For user-scoped models, we need a userId // For user-scoped models, we need a userId (check common field names)
const userId = (this as any).userId; const userId = (this as any).userId || (this as any).authorId || (this as any).ownerId;
if (!userId) { if (!userId) {
throw new Error('User-scoped models must have a userId field'); throw new Error('User-scoped models must have a userId, authorId, or ownerId field');
} }
// Ensure user databases exist before accessing them
await this.ensureUserDatabasesExist(framework, userId);
// Ensure user databases exist before accessing them
await this.ensureUserDatabasesExist(framework, userId);
const database = await framework.databaseManager.getUserDatabase( const database = await framework.databaseManager.getUserDatabase(
userId, userId,
modelClass.modelName, modelClass.modelName,
@ -670,11 +727,14 @@ export abstract class BaseModel {
try { try {
if (modelClass.scope === 'user') { if (modelClass.scope === 'user') {
const userId = (this as any).userId; const userId = (this as any).userId || (this as any).authorId || (this as any).ownerId;
if (!userId) { if (!userId) {
throw new Error('User-scoped models must have a userId field'); throw new Error('User-scoped models must have a userId, authorId, or ownerId field');
} }
// Ensure user databases exist before accessing them
await this.ensureUserDatabasesExist(framework, userId);
const database = await framework.databaseManager.getUserDatabase( const database = await framework.databaseManager.getUserDatabase(
userId, userId,
modelClass.modelName, modelClass.modelName,
@ -721,11 +781,14 @@ export abstract class BaseModel {
try { try {
if (modelClass.scope === 'user') { if (modelClass.scope === 'user') {
const userId = (this as any).userId; const userId = (this as any).userId || (this as any).authorId || (this as any).ownerId;
if (!userId) { if (!userId) {
throw new Error('User-scoped models must have a userId field'); throw new Error('User-scoped models must have a userId, authorId, or ownerId field');
} }
// Ensure user databases exist before accessing them
await this.ensureUserDatabasesExist(framework, userId);
const database = await framework.databaseManager.getUserDatabase( const database = await framework.databaseManager.getUserDatabase(
userId, userId,
modelClass.modelName, modelClass.modelName,