diff --git a/backend/src/object/object.service.ts b/backend/src/object/object.service.ts index f3c85d0..530748b 100644 --- a/backend/src/object/object.service.ts +++ b/backend/src/object/object.service.ts @@ -84,6 +84,25 @@ export class ObjectService { return knex('field_definitions').where({ id }).first(); } + // Helper to get table name from object definition + private getTableName(objectApiName: string): string { + // Convert CamelCase to snake_case and pluralize + // Account -> accounts, ContactPerson -> contact_persons + const snakeCase = objectApiName + .replace(/([A-Z])/g, '_$1') + .toLowerCase() + .replace(/^_/, ''); + + // Simple pluralization (can be enhanced) + if (snakeCase.endsWith('y')) { + return snakeCase.slice(0, -1) + 'ies'; + } else if (snakeCase.endsWith('s')) { + return snakeCase; + } else { + return snakeCase + 's'; + } + } + // Runtime endpoints - CRUD operations async getRecords( tenantId: string, @@ -93,15 +112,25 @@ export class ObjectService { ) { const knex = await this.tenantDbService.getTenantKnex(tenantId); - // For demonstration, using Account as example static object - if (objectApiName === 'Account') { - return knex('accounts') - .where({ ownerId: userId }) - .where(filters || {}); + // Verify object exists + await this.getObjectDefinition(tenantId, objectApiName); + + const tableName = this.getTableName(objectApiName); + + let query = knex(tableName); + + // Add ownership filter if ownerId field exists + const hasOwner = await knex.schema.hasColumn(tableName, 'ownerId'); + if (hasOwner) { + query = query.where({ ownerId: userId }); } - - // For custom objects, you'd need dynamic query building - throw new Error(`Runtime queries for ${objectApiName} not yet implemented`); + + // Apply additional filters + if (filters) { + query = query.where(filters); + } + + return query.select('*'); } async getRecord( @@ -112,19 +141,26 @@ export class ObjectService { ) { const knex = await this.tenantDbService.getTenantKnex(tenantId); - if (objectApiName === 'Account') { - const record = await knex('accounts') - .where({ id: recordId, ownerId: userId }) - .first(); + // Verify object exists + await this.getObjectDefinition(tenantId, objectApiName); + + const tableName = this.getTableName(objectApiName); + + let query = knex(tableName).where({ id: recordId }); + + // Add ownership filter if ownerId field exists + const hasOwner = await knex.schema.hasColumn(tableName, 'ownerId'); + if (hasOwner) { + query = query.where({ ownerId: userId }); + } + + const record = await query.first(); - if (!record) { - throw new NotFoundException('Record not found'); - } - - return record; + if (!record) { + throw new NotFoundException('Record not found'); } - throw new Error(`Runtime queries for ${objectApiName} not yet implemented`); + return record; } async createRecord( @@ -135,19 +171,28 @@ export class ObjectService { ) { const knex = await this.tenantDbService.getTenantKnex(tenantId); - if (objectApiName === 'Account') { - const [id] = await knex('accounts').insert({ - id: knex.raw('(UUID())'), - ownerId: userId, - ...data, - created_at: knex.fn.now(), - updated_at: knex.fn.now(), - }); - - return knex('accounts').where({ id }).first(); + // Verify object exists + await this.getObjectDefinition(tenantId, objectApiName); + + const tableName = this.getTableName(objectApiName); + + // Check if table has ownerId column + const hasOwner = await knex.schema.hasColumn(tableName, 'ownerId'); + + const recordData: any = { + id: knex.raw('(UUID())'), + ...data, + created_at: knex.fn.now(), + updated_at: knex.fn.now(), + }; + + if (hasOwner) { + recordData.ownerId = userId; } - - throw new Error(`Runtime queries for ${objectApiName} not yet implemented`); + + const [id] = await knex(tableName).insert(recordData); + + return knex(tableName).where({ id }).first(); } async updateRecord( @@ -159,18 +204,16 @@ export class ObjectService { ) { const knex = await this.tenantDbService.getTenantKnex(tenantId); - if (objectApiName === 'Account') { - // Verify ownership - await this.getRecord(tenantId, objectApiName, recordId, userId); + // Verify object exists and user has access + await this.getRecord(tenantId, objectApiName, recordId, userId); + + const tableName = this.getTableName(objectApiName); - await knex('accounts') - .where({ id: recordId }) - .update({ ...data, updated_at: knex.fn.now() }); - - return knex('accounts').where({ id: recordId }).first(); - } - - throw new Error(`Runtime queries for ${objectApiName} not yet implemented`); + await knex(tableName) + .where({ id: recordId }) + .update({ ...data, updated_at: knex.fn.now() }); + + return knex(tableName).where({ id: recordId }).first(); } async deleteRecord( @@ -181,15 +224,13 @@ export class ObjectService { ) { const knex = await this.tenantDbService.getTenantKnex(tenantId); - if (objectApiName === 'Account') { - // Verify ownership - await this.getRecord(tenantId, objectApiName, recordId, userId); + // Verify object exists and user has access + await this.getRecord(tenantId, objectApiName, recordId, userId); + + const tableName = this.getTableName(objectApiName); - await knex('accounts').where({ id: recordId }).delete(); - - return { success: true }; - } - - throw new Error(`Runtime queries for ${objectApiName} not yet implemented`); + await knex(tableName).where({ id: recordId }).delete(); + + return { success: true }; } } diff --git a/frontend/components/AppSidebar.vue b/frontend/components/AppSidebar.vue index 4ee7830..350bc14 100644 --- a/frontend/components/AppSidebar.vue +++ b/frontend/components/AppSidebar.vue @@ -1,4 +1,5 @@ @@ -82,11 +112,12 @@ const menuItems = [ + - Application + Navigation - + @@ -127,6 +158,63 @@ const menuItems = [ + + + + Objects + + + + + + + {{ obj.label || obj.apiName }} + + + + + + + + + + Apps + + + + + + + + {{ app.label }} + + + + + + + + + + {{ obj.label || obj.apiName }} + + + + + + + + + + diff --git a/frontend/pages/[objectName]/[[recordId]]/[[view]].vue b/frontend/pages/[objectName]/[[recordId]]/[[view]].vue new file mode 100644 index 0000000..eb64eca --- /dev/null +++ b/frontend/pages/[objectName]/[[recordId]]/[[view]].vue @@ -0,0 +1,242 @@ + + + + + + + + + {{ objectDefinition?.label || objectApiName }} + + {{ objectDefinition.description }} + + + + + + + + Loading {{ objectApiName }}... + + + + + + + ⚠️ + Error + {{ error }} + Go Back + + + + + + + + handleDelete([currentRecord])" + @back="handleBack" + /> + + + + + + + +
+ {{ objectDefinition.description }} +
Loading {{ objectApiName }}...
{{ error }}