Add Contact standard object, related lists, meilisearch, pagination, search, AI assistant
This commit is contained in:
@@ -1,7 +1,38 @@
|
||||
import { Model, ModelOptions, QueryContext, snakeCaseMappers } from 'objection';
|
||||
import { Model, ModelOptions, QueryContext } from 'objection';
|
||||
|
||||
export class BaseModel extends Model {
|
||||
static columnNameMappers = snakeCaseMappers();
|
||||
/**
|
||||
* Use a minimal column mapper: keep property names as-is, but handle
|
||||
* timestamp fields that are stored as created_at/updated_at in the DB.
|
||||
*/
|
||||
static columnNameMappers = {
|
||||
parse(dbRow: Record<string, any>) {
|
||||
const mapped: Record<string, any> = {};
|
||||
for (const [key, value] of Object.entries(dbRow || {})) {
|
||||
if (key === 'created_at') {
|
||||
mapped.createdAt = value;
|
||||
} else if (key === 'updated_at') {
|
||||
mapped.updatedAt = value;
|
||||
} else {
|
||||
mapped[key] = value;
|
||||
}
|
||||
}
|
||||
return mapped;
|
||||
},
|
||||
format(model: Record<string, any>) {
|
||||
const mapped: Record<string, any> = {};
|
||||
for (const [key, value] of Object.entries(model || {})) {
|
||||
if (key === 'createdAt') {
|
||||
mapped.created_at = value;
|
||||
} else if (key === 'updatedAt') {
|
||||
mapped.updated_at = value;
|
||||
} else {
|
||||
mapped[key] = value;
|
||||
}
|
||||
}
|
||||
return mapped;
|
||||
},
|
||||
};
|
||||
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
|
||||
33
backend/src/models/contact-detail.model.ts
Normal file
33
backend/src/models/contact-detail.model.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { BaseModel } from './base.model';
|
||||
|
||||
export class ContactDetail extends BaseModel {
|
||||
static tableName = 'contact_details';
|
||||
|
||||
id!: string;
|
||||
relatedObjectType!: 'Account' | 'Contact';
|
||||
relatedObjectId!: string;
|
||||
detailType!: string;
|
||||
label?: string;
|
||||
value!: string;
|
||||
isPrimary!: boolean;
|
||||
|
||||
// Provide optional relations for each supported parent type.
|
||||
static relationMappings = {
|
||||
account: {
|
||||
relation: BaseModel.BelongsToOneRelation,
|
||||
modelClass: 'account.model',
|
||||
join: {
|
||||
from: 'contact_details.relatedObjectId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
contact: {
|
||||
relation: BaseModel.BelongsToOneRelation,
|
||||
modelClass: 'contact.model',
|
||||
join: {
|
||||
from: 'contact_details.relatedObjectId',
|
||||
to: 'contacts.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
30
backend/src/models/contact.model.ts
Normal file
30
backend/src/models/contact.model.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { BaseModel } from './base.model';
|
||||
|
||||
export class Contact extends BaseModel {
|
||||
static tableName = 'contacts';
|
||||
|
||||
id!: string;
|
||||
firstName!: string;
|
||||
lastName!: string;
|
||||
accountId!: string;
|
||||
ownerId?: string;
|
||||
|
||||
static relationMappings = {
|
||||
account: {
|
||||
relation: BaseModel.BelongsToOneRelation,
|
||||
modelClass: 'account.model',
|
||||
join: {
|
||||
from: 'contacts.accountId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
owner: {
|
||||
relation: BaseModel.BelongsToOneRelation,
|
||||
modelClass: 'user.model',
|
||||
join: {
|
||||
from: 'contacts.ownerId',
|
||||
to: 'users.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -30,6 +30,8 @@ export interface UIMetadata {
|
||||
step?: number; // For number
|
||||
accept?: string; // For file/image
|
||||
relationDisplayField?: string; // Which field to display for relations
|
||||
relationObjects?: string[]; // For polymorphic relations
|
||||
relationTypeField?: string; // Field API name storing the selected relation type
|
||||
|
||||
// Formatting
|
||||
format?: string; // Date format, number format, etc.
|
||||
|
||||
Reference in New Issue
Block a user