From db9848cce7544389019cc96f658ff8a6c5f571e9 Mon Sep 17 00:00:00 2001 From: Francisco Gaona Date: Mon, 22 Dec 2025 10:24:02 +0100 Subject: [PATCH] Use routes closer to the route for objects --- backend/src/object/object.service.ts | 141 ++++++---- frontend/components/AppSidebar.vue | 116 ++++++++- .../[objectName]/[[recordId]]/[[view]].vue | 242 ++++++++++++++++++ 3 files changed, 435 insertions(+), 64 deletions(-) create mode 100644 frontend/pages/[objectName]/[[recordId]]/[[view]].vue 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 -