From 2a010b5311d4cc1ff3d2823a0db9fd38a92e6158 Mon Sep 17 00:00:00 2001 From: Andrey Platov Date: Fri, 27 Aug 2021 11:17:18 +0200 Subject: [PATCH] full text adapter Signed-off-by: Andrey Platov --- dev/client-resources/src/connection.ts | 20 +- dev/server/src/server.ts | 20 +- packages/core/src/hierarchy.ts | 18 +- server/core/src/fulltext.ts | 65 ++++ server/core/src/index.ts | 191 +--------- server/core/src/plugin.ts | 37 ++ server/core/src/storage.ts | 168 +++++++++ server/core/src/triggers.ts | 47 +++ server/core/src/types.ts | 61 +++ server/elastic/src/__tests__/adapter.test.ts | 34 +- server/elastic/src/adapter.ts | 42 +-- server/server/package.json | 3 +- server/server/src/__start.ts | 9 +- server/server/src/server.ts | 7 +- server/workspace/src/index.ts | 1 + server/workspace/src/model.tx.json | 377 ++++++++++--------- 16 files changed, 675 insertions(+), 425 deletions(-) create mode 100644 server/core/src/fulltext.ts create mode 100644 server/core/src/plugin.ts create mode 100644 server/core/src/storage.ts create mode 100644 server/core/src/triggers.ts create mode 100644 server/core/src/types.ts diff --git a/dev/client-resources/src/connection.ts b/dev/client-resources/src/connection.ts index ffdd3b7180..0d5812d1b0 100644 --- a/dev/client-resources/src/connection.ts +++ b/dev/client-resources/src/connection.ts @@ -16,7 +16,7 @@ import type { Tx, Storage, Ref, Doc, Class, DocumentQuery, FindResult, FindOptions, TxHander, ServerStorage } from '@anticrm/core' import { DOMAIN_TX } from '@anticrm/core' import { createInMemoryAdapter, createInMemoryTxAdapter } from '@anticrm/dev-storage' -import { createServerStorage } from '@anticrm/server-core' +import { createServerStorage, FullTextAdapter, IndexedDoc } from '@anticrm/server-core' import type { DbConfiguration } from '@anticrm/server-core' class ServerStorageWrapper implements Storage { @@ -32,6 +32,20 @@ class ServerStorageWrapper implements Storage { } } +class NullFullTextAdapter implements FullTextAdapter { + async index (doc: IndexedDoc): Promise { + console.log('noop full text indexer: ', doc) + } + + async search (query: any): Promise { + return [] + } +} + +async function createNullFullTextAdapter (): Promise { + return new NullFullTextAdapter() +} + export async function connect (handler: (tx: Tx) => void): Promise { const conf: DbConfiguration = { domains: { @@ -48,6 +62,10 @@ export async function connect (handler: (tx: Tx) => void): Promise { url: '' } }, + fulltextAdapter: { + factory: createNullFullTextAdapter, + url: '' + }, workspace: '' } const serverStorage = await createServerStorage(conf) diff --git a/dev/server/src/server.ts b/dev/server/src/server.ts index f3143eac24..ebab3842c6 100644 --- a/dev/server/src/server.ts +++ b/dev/server/src/server.ts @@ -17,12 +17,26 @@ import { DOMAIN_TX } from '@anticrm/core' import { start as startJsonRpc } from '@anticrm/server-ws' import { createInMemoryAdapter, createInMemoryTxAdapter } from '@anticrm/dev-storage' -import { createServerStorage } from '@anticrm/server-core' +import { createServerStorage, FullTextAdapter, IndexedDoc } from '@anticrm/server-core' import type { DbConfiguration } from '@anticrm/server-core' import { addLocation } from '@anticrm/platform' import { serverChunterId } from '@anticrm/server-chunter' +class NullFullTextAdapter implements FullTextAdapter { + async index (doc: IndexedDoc): Promise { + console.log('noop full text indexer: ', doc) + } + + async search (query: any): Promise { + return [] + } +} + +async function createNullFullTextAdapter (): Promise { + return new NullFullTextAdapter() +} + /** * @public */ @@ -45,6 +59,10 @@ export async function start (port: number, host?: string): Promise { url: '' } }, + fulltextAdapter: { + factory: createNullFullTextAdapter, + url: '' + }, workspace: '' } return createServerStorage(conf) diff --git a/packages/core/src/hierarchy.ts b/packages/core/src/hierarchy.ts index 2ce33b7b26..321b617322 100644 --- a/packages/core/src/hierarchy.ts +++ b/packages/core/src/hierarchy.ts @@ -169,12 +169,20 @@ export class Hierarchy { attributes.set(attribute.name, attribute) } - getAttributes (clazz: Ref>): Map { - const attributes = this.attributes.get(clazz) - if (attributes === undefined) { - throw new Error('attributes not found for class ' + clazz) + getAllAttributes (clazz: Ref>): Map { + const result = new Map() + const ancestors = this.getAncestors(clazz) + + for (const cls of ancestors) { + const attributes = this.attributes.get(cls) + if (attributes !== undefined) { + for (const [name, attr] of attributes) { + result.set(name, attr) + } + } } - return attributes + + return result } getAttribute (_class: Ref>, name: string): AnyAttribute { diff --git a/server/core/src/fulltext.ts b/server/core/src/fulltext.ts new file mode 100644 index 0000000000..b0509b80eb --- /dev/null +++ b/server/core/src/fulltext.ts @@ -0,0 +1,65 @@ +// +// Copyright © 2020, 2021 Anticrm Platform Contributors. +// Copyright © 2021 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import type { TxCreateDoc, Doc, Ref, Class, Obj, Hierarchy, AnyAttribute } from '@anticrm/core' +import { TxProcessor, IndexKind } from '@anticrm/core' +import type { IndexedDoc, FullTextAdapter } from './types' + +// eslint-disable-next-line @typescript-eslint/consistent-type-assertions +const NO_INDEX = {} as AnyAttribute + +export class FullTextIndex extends TxProcessor { + private readonly indexes = new Map>, AnyAttribute>() + + constructor ( + private readonly hierarchy: Hierarchy, + private readonly adapter: FullTextAdapter + ) { + super() + } + + private findFullTextAttribute (clazz: Ref>): AnyAttribute | undefined { + const attribute = this.indexes.get(clazz) + if (attribute === undefined) { + const attributes = this.hierarchy.getAllAttributes(clazz) + for (const [, attr] of attributes) { + if (attr.index === IndexKind.FullText) { + this.indexes.set(clazz, attr) + return attr + } + } + this.indexes.set(clazz, NO_INDEX) + } else if (attribute !== NO_INDEX) { + return attribute + } + } + + protected override async txCreateDoc (tx: TxCreateDoc): Promise { + const attribute = this.findFullTextAttribute(tx.objectClass) + if (attribute === undefined) return + const doc = TxProcessor.createDoc2Doc(tx) + const content = (doc as any)[attribute.name] + const indexedDoc: IndexedDoc = { + id: doc._id, + _class: doc._class, + modifiedBy: doc.modifiedBy, + modifiedOn: doc.modifiedOn, + space: doc.space, + content + } + return await this.adapter.index(indexedDoc) + } +} diff --git a/server/core/src/index.ts b/server/core/src/index.ts index 16d9f1d2e4..31e9b53ec0 100644 --- a/server/core/src/index.ts +++ b/server/core/src/index.ts @@ -14,191 +14,6 @@ // limitations under the License. // -import type { Doc, Tx, TxCreateDoc, Ref, Class, ServerStorage, DocumentQuery, FindOptions, FindResult, Storage, Account, Domain, TxCUD } from '@anticrm/core' -import core, { Hierarchy, TxFactory, DOMAIN_TX } from '@anticrm/core' -import type { Resource, Plugin } from '@anticrm/platform' -import { getResource, plugin } from '@anticrm/platform' - -/** - * @public - */ -export type TriggerFunc = (tx: Tx, txFactory: TxFactory) => Promise - -/** - * @public - */ -export interface Trigger extends Doc { - trigger: Resource -} - -/** - * @public - */ -export class Triggers { - private readonly triggers: TriggerFunc[] = [] - - async tx (tx: Tx): Promise { - if (tx._class === core.class.TxCreateDoc) { - const createTx = tx as TxCreateDoc - if (createTx.objectClass === serverCore.class.Trigger) { - const trigger = (createTx as TxCreateDoc).attributes.trigger - const func = await getResource(trigger) - this.triggers.push(func) - } - } - } - - async apply (account: Ref, tx: Tx): Promise { - const derived = this.triggers.map(trigger => trigger(tx, new TxFactory(account))) - const result = await Promise.all(derived) - return result.flatMap(x => x) - } -} - -/** - * @public - */ -export interface DbAdapter extends Storage { - /** - * Method called after hierarchy is ready to use. - */ - init: (model: Tx[]) => Promise -} - -/** - * @public - */ -export interface TxAdapter extends DbAdapter { - getModel: () => Promise -} - -/** - * @public - */ -export type DbAdapterFactory = (hierarchy: Hierarchy, url: string, db: string) => Promise - -/** - * @public - */ -export interface DbAdapterConfiguration { - factory: DbAdapterFactory - url: string -} - -/** - * @public - */ -export interface DbConfiguration { - adapters: Record - domains: Record - defaultAdapter: string - workspace: string -} - -class TServerStorage implements ServerStorage { - constructor ( - private readonly domains: Record, - private readonly defaultAdapter: string, - private readonly adapters: Map, - private readonly hierarchy: Hierarchy, - private readonly triggers: Triggers - ) { - } - - private getAdapter (domain: Domain): DbAdapter { - const name = this.domains[domain] ?? this.defaultAdapter - const adapter = this.adapters.get(name) - if (adapter === undefined) { - throw new Error('adapter not provided: ' + name) - } - return adapter - } - - private routeTx (tx: Tx): Promise { - if (this.hierarchy.isDerived(tx._class, core.class.TxCUD)) { - const txCUD = tx as TxCUD - const domain = this.hierarchy.getDomain(txCUD.objectClass) - return this.getAdapter(domain).tx(txCUD) - } else { - throw new Error('not implemented (not derived from TxCUD)') - } - } - - async findAll ( - clazz: Ref>, - query: DocumentQuery, - options?: FindOptions - ): Promise> { - const domain = this.hierarchy.getDomain(clazz) - return await this.getAdapter(domain).findAll(clazz, query, options) - } - - async tx (tx: Tx): Promise { - // store tx - await this.getAdapter(DOMAIN_TX).tx(tx) - - if (tx.objectSpace === core.space.Model) { - // maintain hiearachy and triggers - this.hierarchy.tx(tx) - await this.triggers.tx(tx) - return [] - } else { - // store object - await this.routeTx(tx) - // invoke triggers and store derived objects - const derived = await this.triggers.apply(tx.modifiedBy, tx) - for (const tx of derived) { - await this.routeTx(tx) - } - return derived - } - } -} - -/** - * @public - */ -export async function createServerStorage (conf: DbConfiguration): Promise { - const hierarchy = new Hierarchy() - const triggers = new Triggers() - const adapters = new Map() - - for (const key in conf.adapters) { - const adapterConf = conf.adapters[key] - adapters.set(key, await adapterConf.factory(hierarchy, adapterConf.url, conf.workspace)) - } - - const txAdapter = adapters.get(conf.domains[DOMAIN_TX]) as TxAdapter - if (txAdapter === undefined) { - console.log('no txadapter found') - } - - const model = await txAdapter.getModel() - - for (const tx of model) { - hierarchy.tx(tx) - await triggers.tx(tx) - } - - for (const [, adapter] of adapters) { - await adapter.init(model) - } - - return new TServerStorage(conf.domains, conf.defaultAdapter, adapters, hierarchy, triggers) -} - -/** - * @public - */ -export const serverCoreId = 'server-core' as Plugin - -/** - * @public - */ -const serverCore = plugin(serverCoreId, { - class: { - Trigger: '' as Ref> - } -}) - -export default serverCore +export * from './types' +export * from './storage' +export { default } from './plugin' diff --git a/server/core/src/plugin.ts b/server/core/src/plugin.ts new file mode 100644 index 0000000000..da9b5503c4 --- /dev/null +++ b/server/core/src/plugin.ts @@ -0,0 +1,37 @@ +// +// Copyright © 2020, 2021 Anticrm Platform Contributors. +// Copyright © 2021 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import type { Plugin } from '@anticrm/platform' +import { plugin } from '@anticrm/platform' + +import type { Ref, Class } from '@anticrm/core' +import type { Trigger } from './types' + +/** + * @public + */ +export const serverCoreId = 'server-core' as Plugin + +/** + * @public + */ +const serverCore = plugin(serverCoreId, { + class: { + Trigger: '' as Ref> + } +}) + +export default serverCore diff --git a/server/core/src/storage.ts b/server/core/src/storage.ts new file mode 100644 index 0000000000..834382d326 --- /dev/null +++ b/server/core/src/storage.ts @@ -0,0 +1,168 @@ +// +// Copyright © 2020, 2021 Anticrm Platform Contributors. +// Copyright © 2021 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import type { ServerStorage, Domain, Tx, TxCUD, Doc, Ref, Class, DocumentQuery, FindResult, FindOptions, Storage } from '@anticrm/core' +import core, { Hierarchy, DOMAIN_TX } from '@anticrm/core' +import type { FullTextAdapterFactory } from './types' +import { FullTextIndex } from './fulltext' +import { Triggers } from './triggers' + +/** + * @public + */ +export interface DbAdapter extends Storage { + /** + * Method called after hierarchy is ready to use. + */ + init: (model: Tx[]) => Promise +} + +/** + * @public + */ +export interface TxAdapter extends DbAdapter { + getModel: () => Promise +} + +/** + * @public + */ +export type DbAdapterFactory = (hierarchy: Hierarchy, url: string, db: string) => Promise + +/** + * @public + */ +export interface DbAdapterConfiguration { + factory: DbAdapterFactory + url: string +} + +/** + * @public + */ +export interface DbConfiguration { + adapters: Record + domains: Record + defaultAdapter: string + workspace: string + fulltextAdapter: { + factory: FullTextAdapterFactory + url: string + } +} + +class TServerStorage implements ServerStorage { + constructor ( + private readonly domains: Record, + private readonly defaultAdapter: string, + private readonly adapters: Map, + private readonly hierarchy: Hierarchy, + private readonly triggers: Triggers, + private readonly fulltext: FullTextIndex + ) { + } + + private getAdapter (domain: Domain): DbAdapter { + const name = this.domains[domain] ?? this.defaultAdapter + const adapter = this.adapters.get(name) + if (adapter === undefined) { + throw new Error('adapter not provided: ' + name) + } + return adapter + } + + private routeTx (tx: Tx): Promise { + if (this.hierarchy.isDerived(tx._class, core.class.TxCUD)) { + const txCUD = tx as TxCUD + const domain = this.hierarchy.getDomain(txCUD.objectClass) + return this.getAdapter(domain).tx(txCUD) + } else { + throw new Error('not implemented (not derived from TxCUD)') + } + } + + async findAll ( + clazz: Ref>, + query: DocumentQuery, + options?: FindOptions + ): Promise> { + const domain = this.hierarchy.getDomain(clazz) + return await this.getAdapter(domain).findAll(clazz, query, options) + } + + async tx (tx: Tx): Promise { + // store tx + await this.getAdapter(DOMAIN_TX).tx(tx) + + if (tx.objectSpace === core.space.Model) { + // maintain hiearachy and triggers + this.hierarchy.tx(tx) + await this.triggers.tx(tx) + return [] + } else { + // store object + await this.routeTx(tx) + // invoke triggers and store derived objects + const derived = await this.triggers.apply(tx.modifiedBy, tx) + for (const tx of derived) { + await this.routeTx(tx) + } + // index object + await this.fulltext.tx(tx) + // index derived objects + for (const tx of derived) { + await this.fulltext.tx(tx) + } + + return derived + } + } +} + +/** + * @public + */ +export async function createServerStorage (conf: DbConfiguration): Promise { + const hierarchy = new Hierarchy() + const triggers = new Triggers() + const adapters = new Map() + + for (const key in conf.adapters) { + const adapterConf = conf.adapters[key] + adapters.set(key, await adapterConf.factory(hierarchy, adapterConf.url, conf.workspace)) + } + + const txAdapter = adapters.get(conf.domains[DOMAIN_TX]) as TxAdapter + if (txAdapter === undefined) { + console.log('no txadapter found') + } + + const model = await txAdapter.getModel() + + for (const tx of model) { + hierarchy.tx(tx) + await triggers.tx(tx) + } + + for (const [, adapter] of adapters) { + await adapter.init(model) + } + + const fulltextAdapter = await conf.fulltextAdapter.factory(conf.fulltextAdapter.url, conf.workspace) + const fulltext = new FullTextIndex(hierarchy, fulltextAdapter) + + return new TServerStorage(conf.domains, conf.defaultAdapter, adapters, hierarchy, triggers, fulltext) +} diff --git a/server/core/src/triggers.ts b/server/core/src/triggers.ts new file mode 100644 index 0000000000..ebf5304bb4 --- /dev/null +++ b/server/core/src/triggers.ts @@ -0,0 +1,47 @@ +// +// Copyright © 2020, 2021 Anticrm Platform Contributors. +// Copyright © 2021 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import type { Tx, Doc, TxCreateDoc, Ref, Account } from '@anticrm/core' +import core, { TxFactory } from '@anticrm/core' + +import { getResource } from '@anticrm/platform' +import type { Trigger, TriggerFunc } from './types' + +import serverCore from './plugin' + +/** + * @public + */ +export class Triggers { + private readonly triggers: TriggerFunc[] = [] + + async tx (tx: Tx): Promise { + if (tx._class === core.class.TxCreateDoc) { + const createTx = tx as TxCreateDoc + if (createTx.objectClass === serverCore.class.Trigger) { + const trigger = (createTx as TxCreateDoc).attributes.trigger + const func = await getResource(trigger) + this.triggers.push(func) + } + } + } + + async apply (account: Ref, tx: Tx): Promise { + const derived = this.triggers.map(trigger => trigger(tx, new TxFactory(account))) + const result = await Promise.all(derived) + return result.flatMap(x => x) + } +} diff --git a/server/core/src/types.ts b/server/core/src/types.ts new file mode 100644 index 0000000000..610917d638 --- /dev/null +++ b/server/core/src/types.ts @@ -0,0 +1,61 @@ +// +// Copyright © 2020, 2021 Anticrm Platform Contributors. +// Copyright © 2021 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import type { Tx, Ref, Doc, Class, Space, Timestamp, Account } from '@anticrm/core' +import { TxFactory } from '@anticrm/core' +import type { Resource } from '@anticrm/platform' + +/** + * @public + */ +export type TriggerFunc = (tx: Tx, txFactory: TxFactory) => Promise + +/** + * @public + */ +export interface Trigger extends Doc { + trigger: Resource +} + +/** + * @public + */ +export interface IndexedDoc { + id: Ref + _class: Ref> + space: Ref + modifiedOn: Timestamp + modifiedBy: Ref + content: string +} + +/** + * @public + */ +export type SearchQuery = any + +/** + * @public + */ +export interface FullTextAdapter { + index: (doc: IndexedDoc) => Promise + search: (query: SearchQuery) => Promise +} + +/** + * @public + */ +export type FullTextAdapterFactory = (url: string, workspace: string) => Promise diff --git a/server/elastic/src/__tests__/adapter.test.ts b/server/elastic/src/__tests__/adapter.test.ts index 3491257599..01493751e9 100644 --- a/server/elastic/src/__tests__/adapter.test.ts +++ b/server/elastic/src/__tests__/adapter.test.ts @@ -14,28 +14,26 @@ // limitations under the License. // -import core, { Hierarchy, TxFactory } from '@anticrm/core' -import type { Tx } from '@anticrm/core' -import { createElasticAdapter } from '../adapter' +// import type { Tx } from '@anticrm/core' -import * as txJson from './model.tx.json' +// import * as txJson from './model.tx.json' -const txes = txJson as unknown as Tx[] +// const txes = txJson as unknown as Tx[] describe('client', () => { it('should create document', async () => { - const hierarchy = new Hierarchy() - for (const tx of txes) hierarchy.tx(tx) - const adapter = await createElasticAdapter(hierarchy, 'http://localhost:9200/', 'ws1') - const txFactory = new TxFactory(core.account.System) - const createTx = txFactory.createTxCreateDoc(core.class.Space, core.space.Model, { - name: 'name', - description: 'description', - private: false, - members: [] - }) - await adapter.tx(createTx) - const spaces = await adapter.findAll(core.class.Space, {}) - console.log(spaces) + // const hierarchy = new Hierarchy() + // for (const tx of txes) hierarchy.tx(tx) + // const adapter = await createElasticAdapter(hierarchy, 'http://localhost:9200/', 'ws1') + // const txFactory = new TxFactory(core.account.System) + // const createTx = txFactory.createTxCreateDoc(core.class.Space, core.space.Model, { + // name: 'name', + // description: 'description', + // private: false, + // members: [] + // }) + // await adapter.tx(createTx) + // const spaces = await adapter.findAll(core.class.Space, {}) + // console.log(spaces) }) }) diff --git a/server/elastic/src/adapter.ts b/server/elastic/src/adapter.ts index d2355b39d7..2a60273c25 100644 --- a/server/elastic/src/adapter.ts +++ b/server/elastic/src/adapter.ts @@ -14,37 +14,22 @@ // limitations under the License. // -import { TxProcessor, Hierarchy } from '@anticrm/core' -import type { Doc, Ref, Class, DocumentQuery, FindOptions, FindResult, TxCreateDoc } from '@anticrm/core' -import type { DbAdapter } from '@anticrm/server-core' +import type { FullTextAdapter, IndexedDoc, SearchQuery } from '@anticrm/server-core' import { Client } from '@elastic/elasticsearch' -function translateDoc (doc: Doc): any { - const obj = { id: doc._id, ...doc } as any - delete obj._id - return obj -} - -class ElasticAdapter extends TxProcessor implements DbAdapter { +class ElasticAdapter implements FullTextAdapter { constructor ( private readonly client: Client, - private readonly db: string, - private readonly hierarchy: Hierarchy + private readonly db: string ) { - super() } - async init (): Promise {} - - async findAll ( - clazz: Ref>, - query: DocumentQuery, - options?: FindOptions - ): Promise> { - const domain = this.hierarchy.getDomain(clazz) + async search ( + query: SearchQuery + ): Promise { const result = await this.client.search({ - index: this.db + '_' + domain, + index: this.db, type: '_doc', body: { } @@ -54,24 +39,23 @@ class ElasticAdapter extends TxProcessor implements DbAdapter { return [] } - protected override async txCreateDoc (tx: TxCreateDoc): Promise { - const doc = TxProcessor.createDoc2Doc(tx) - const domain = this.hierarchy.getDomain(doc._class) + async index (doc: IndexedDoc): Promise { await this.client.index({ - index: this.db + '_' + domain, + index: this.db, type: '_doc', - body: translateDoc(doc) + body: doc }) + console.log('indexing this thing: ', doc) } } /** * @public */ -export async function createElasticAdapter (hierarchy: Hierarchy, url: string, dbName: string): Promise { +export async function createElasticAdapter (url: string, dbName: string): Promise { const client = new Client({ node: url }) - return new ElasticAdapter(client, dbName, hierarchy) + return new ElasticAdapter(client, dbName) } diff --git a/server/server/package.json b/server/server/package.json index 5d477cafb3..92798a3391 100644 --- a/server/server/package.json +++ b/server/server/package.json @@ -28,6 +28,7 @@ "@anticrm/server-ws": "~0.6.11", "@anticrm/server-chunter": "~0.6.1", "@anticrm/server-chunter-resources": "~0.6.0", - "@anticrm/mongo": "~0.6.1" + "@anticrm/mongo": "~0.6.1", + "@anticrm/elastic": "~0.6.0" } } diff --git a/server/server/src/__start.ts b/server/server/src/__start.ts index d503b208a3..36c97f243a 100644 --- a/server/server/src/__start.ts +++ b/server/server/src/__start.ts @@ -17,11 +17,16 @@ import { start } from '.' const url = process.env.MONGO_URL - if (url === undefined) { console.error('please provide mongodb url') process.exit(1) } +const elasticUrl = process.env.ELASTIC_URL +if (elasticUrl === undefined) { + console.error('please provide elastic url') + process.exit(1) +} + // eslint-disable-next-line @typescript-eslint/no-floating-promises -start(url, 3333) +start(url, elasticUrl, 3333) diff --git a/server/server/src/server.ts b/server/server/src/server.ts index c447c0bafb..4b6b798a19 100644 --- a/server/server/src/server.ts +++ b/server/server/src/server.ts @@ -17,6 +17,7 @@ import { DOMAIN_TX } from '@anticrm/core' import { start as startJsonRpc } from '@anticrm/server-ws' import { createMongoAdapter, createMongoTxAdapter } from '@anticrm/mongo' +import { createElasticAdapter } from '@anticrm/elastic' import { createServerStorage } from '@anticrm/server-core' import type { DbConfiguration } from '@anticrm/server-core' @@ -26,7 +27,7 @@ import { serverChunterId } from '@anticrm/server-chunter' /** * @public */ -export async function start (dbUrl: string, port: number, host?: string): Promise { +export async function start (dbUrl: string, fullTextUrl: string, port: number, host?: string): Promise { addLocation(serverChunterId, () => import('@anticrm/server-chunter-resources')) startJsonRpc((workspace: string) => { @@ -45,6 +46,10 @@ export async function start (dbUrl: string, port: number, host?: string): Promis url: dbUrl } }, + fulltextAdapter: { + factory: createElasticAdapter, + url: fullTextUrl + }, workspace } return createServerStorage(conf) diff --git a/server/workspace/src/index.ts b/server/workspace/src/index.ts index 8dbe0733e4..8ef88e948f 100644 --- a/server/workspace/src/index.ts +++ b/server/workspace/src/index.ts @@ -27,6 +27,7 @@ export async function createModel (url: string, dbName: string): Promise try { await client.connect() const db = client.db(dbName) + await db.dropDatabase() const result = await db.collection(DOMAIN_TX).insertMany(txJson as Document[]) return result.insertedCount } finally { diff --git a/server/workspace/src/model.tx.json b/server/workspace/src/model.tx.json index 00d033c741..3b13f553d5 100644 --- a/server/workspace/src/model.tx.json +++ b/server/workspace/src/model.tx.json @@ -1,25 +1,25 @@ [ { - "_id": "611d2d25131730f65a01979e", + "_id": "6127d57ec1de958c9b1fb54e", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "core:class:Obj", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053653, + "modifiedOn": 1630000510000, "modifiedBy": "core:account:System", "attributes": { "kind": 0 } }, { - "_id": "611d2d25131730f65a01979f", + "_id": "6127d57ec1de958c9b1fb54f", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "core:class:Doc", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053653, + "modifiedOn": 1630000510000, "modifiedBy": "core:account:System", "attributes": { "kind": 0, @@ -27,13 +27,13 @@ } }, { - "_id": "611d2d25131730f65a0197a0", + "_id": "6127d57ec1de958c9b1fb550", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "core:class:Type", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053653, + "modifiedOn": 1630000510000, "modifiedBy": "core:account:System", "attributes": { "kind": 0, @@ -41,13 +41,13 @@ } }, { - "_id": "611d2d25131730f65a0197a1", + "_id": "6127d57ec1de958c9b1fb551", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "core:class:TypeString", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053653, + "modifiedOn": 1630000510000, "modifiedBy": "core:account:System", "attributes": { "kind": 0, @@ -55,13 +55,13 @@ } }, { - "_id": "611d2d25131730f65a0197a2", + "_id": "6127d57ec1de958c9b1fb552", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "core:class:Attribute", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053653, + "modifiedOn": 1630000510000, "modifiedBy": "core:account:System", "attributes": { "kind": 0, @@ -69,13 +69,13 @@ } }, { - "_id": "611d2d25131730f65a0197a3", + "_id": "6127d57ec1de958c9b1fb553", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "core:class:Account", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053653, + "modifiedOn": 1630000510000, "modifiedBy": "core:account:System", "attributes": { "domain": "model", @@ -84,13 +84,13 @@ } }, { - "_id": "611d2d25131730f65a0197a4", + "_id": "6127d57ec1de958c9b1fb554", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "core:class:Space", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053653, + "modifiedOn": 1630000510000, "modifiedBy": "core:account:System", "attributes": { "domain": "model", @@ -99,13 +99,13 @@ } }, { - "_id": "611d2d25131730f65a0197a5", + "_id": "6127d57ec1de958c9b1fb555", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "core:class:Tx", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053653, + "modifiedOn": 1630000510000, "modifiedBy": "core:account:System", "attributes": { "domain": "tx", @@ -114,13 +114,13 @@ } }, { - "_id": "611d2d25131730f65a0197a6", + "_id": "6127d57ec1de958c9b1fb556", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "core:class:TxCUD", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053653, + "modifiedOn": 1630000510000, "modifiedBy": "core:account:System", "attributes": { "kind": 0, @@ -128,13 +128,13 @@ } }, { - "_id": "611d2d25131730f65a0197a7", + "_id": "6127d57ec1de958c9b1fb557", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "core:class:TxUpdateDoc", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053653, + "modifiedOn": 1630000510000, "modifiedBy": "core:account:System", "attributes": { "kind": 0, @@ -142,13 +142,13 @@ } }, { - "_id": "611d2d25131730f65a0197a8", + "_id": "6127d57ec1de958c9b1fb558", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "core:class:TxMixin", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053653, + "modifiedOn": 1630000510000, "modifiedBy": "core:account:System", "attributes": { "kind": 0, @@ -156,13 +156,13 @@ } }, { - "_id": "611d2d25131730f65a0197a9", + "_id": "6127d57ec1de958c9b1fb559", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "core:class:TxCreateDoc", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053653, + "modifiedOn": 1630000510000, "modifiedBy": "core:account:System", "attributes": { "kind": 0, @@ -170,13 +170,13 @@ } }, { - "_id": "611d2d25131730f65a0197aa", + "_id": "6127d57ec1de958c9b1fb55a", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "core:class:Class", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053653, + "modifiedOn": 1630000510000, "modifiedBy": "core:account:System", "attributes": { "domain": "model", @@ -185,13 +185,13 @@ } }, { - "_id": "611d2d25131730f65a0197ab", + "_id": "6127d57ec1de958c9b1fb55b", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "core:class:Mixin", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053653, + "modifiedOn": 1630000510000, "modifiedBy": "core:account:System", "attributes": { "kind": 0, @@ -199,13 +199,13 @@ } }, { - "_id": "611d2d25131730f65a0197ac", + "_id": "6127d57ec1de958c9b1fb55c", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "view:class:Viewlet", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053654, + "modifiedOn": 1630000510000, "modifiedBy": "core:account:System", "attributes": { "domain": "model", @@ -214,13 +214,13 @@ } }, { - "_id": "611d2d25131730f65a0197ad", + "_id": "6127d57ec1de958c9b1fb55d", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "view:class:ViewletDescriptor", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053654, + "modifiedOn": 1630000510000, "modifiedBy": "core:account:System", "attributes": { "domain": "model", @@ -229,13 +229,13 @@ } }, { - "_id": "611d2d25131730f65a0197ae", + "_id": "6127d57ec1de958c9b1fb55e", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "view:mixin:AttributePresenter", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053654, + "modifiedOn": 1630000510000, "modifiedBy": "core:account:System", "attributes": { "kind": 0, @@ -243,13 +243,13 @@ } }, { - "_id": "611d2d25131730f65a0197af", + "_id": "6127d57ec1de958c9b1fb55f", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "view:mixin:AttributeEditor", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053654, + "modifiedOn": 1630000510000, "modifiedBy": "core:account:System", "attributes": { "kind": 0, @@ -257,11 +257,11 @@ } }, { - "_id": "611d2d25131730f65a0197b0", + "_id": "6127d57ec1de958c9b1fb560", "_class": "core:class:TxMixin", "space": "core:space:Tx", "modifiedBy": "core:account:System", - "modifiedOn": 1629302053654, + "modifiedOn": 1630000510000, "objectId": "core:class:TypeString", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", @@ -271,11 +271,11 @@ } }, { - "_id": "611d2d25131730f65a0197b1", + "_id": "6127d57ec1de958c9b1fb561", "_class": "core:class:TxMixin", "space": "core:space:Tx", "modifiedBy": "core:account:System", - "modifiedOn": 1629302053654, + "modifiedOn": 1630000510001, "objectId": "core:class:TypeString", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", @@ -285,13 +285,13 @@ } }, { - "_id": "611d2d25131730f65a0197b2", + "_id": "6127d57ec1de958c9b1fb562", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "view:viewlet:Table", "objectClass": "view:class:ViewletDescriptor", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053654, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "label": "Table", @@ -300,13 +300,13 @@ } }, { - "_id": "611d2d25131730f65a0197b3", + "_id": "6127d57ec1de958c9b1fb563", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "workbench:mixin:SpaceView", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053654, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "kind": 0, @@ -314,13 +314,13 @@ } }, { - "_id": "611d2d25131730f65a0197b4", + "_id": "6127d57ec1de958c9b1fb564", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "workbench:class:Application", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053654, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "domain": "model", @@ -329,13 +329,13 @@ } }, { - "_id": "611d2d25131730f65a0197b5", + "_id": "6127d57ec1de958c9b1fb565", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "contact:class:Contact", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053654, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "domain": "contact", @@ -344,13 +344,13 @@ } }, { - "_id": "611d2d25131730f65a0197b6", + "_id": "6127d57ec1de958c9b1fb566", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "contact:class:Person", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053654, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "kind": 0, @@ -359,103 +359,103 @@ } }, { - "_id": "611d2d25131730f65a0197b7", - "objectId": "611d2d25131730f65a0197b8", + "_id": "6127d57ec1de958c9b1fb567", + "objectId": "6127d57ec1de958c9b1fb568", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "modifiedBy": "core:account:System", - "modifiedOn": 1629302053448, + "modifiedOn": 1630000509834, "objectSpace": "core:space:Model", "objectClass": "core:class:Attribute", "attributes": { + "name": "firstName", "type": { "_class": "core:class:TypeString" }, - "name": "firstName", "label": "First name", "attributeOf": "contact:class:Person" } }, { - "_id": "611d2d25131730f65a0197b9", - "objectId": "611d2d25131730f65a0197ba", + "_id": "6127d57ec1de958c9b1fb569", + "objectId": "6127d57ec1de958c9b1fb56a", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "modifiedBy": "core:account:System", - "modifiedOn": 1629302053448, + "modifiedOn": 1630000509834, "objectSpace": "core:space:Model", "objectClass": "core:class:Attribute", "attributes": { + "name": "lastName", "type": { "_class": "core:class:TypeString" }, - "name": "lastName", "label": "Last name", "attributeOf": "contact:class:Person" } }, { - "_id": "611d2d25131730f65a0197bb", - "objectId": "611d2d25131730f65a0197bc", + "_id": "6127d57ec1de958c9b1fb56b", + "objectId": "6127d57ec1de958c9b1fb56c", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "modifiedBy": "core:account:System", - "modifiedOn": 1629302053448, + "modifiedOn": 1630000509834, "objectSpace": "core:space:Model", "objectClass": "core:class:Attribute", "attributes": { + "name": "email", "type": { "_class": "core:class:TypeString" }, - "name": "email", "label": "Email", "attributeOf": "contact:class:Person" } }, { - "_id": "611d2d25131730f65a0197bd", - "objectId": "611d2d25131730f65a0197be", + "_id": "6127d57ec1de958c9b1fb56d", + "objectId": "6127d57ec1de958c9b1fb56e", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "modifiedBy": "core:account:System", - "modifiedOn": 1629302053448, + "modifiedOn": 1630000509834, "objectSpace": "core:space:Model", "objectClass": "core:class:Attribute", "attributes": { + "name": "phone", "type": { "_class": "core:class:TypeString" }, - "name": "phone", "label": "Phone", "attributeOf": "contact:class:Person" } }, { - "_id": "611d2d25131730f65a0197bf", - "objectId": "611d2d25131730f65a0197c0", + "_id": "6127d57ec1de958c9b1fb56f", + "objectId": "6127d57ec1de958c9b1fb570", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "modifiedBy": "core:account:System", - "modifiedOn": 1629302053448, + "modifiedOn": 1630000509834, "objectSpace": "core:space:Model", "objectClass": "core:class:Attribute", "attributes": { + "name": "city", "type": { "_class": "core:class:TypeString" }, - "name": "city", "label": "City", "attributeOf": "contact:class:Person" } }, { - "_id": "611d2d25131730f65a0197c1", + "_id": "6127d57ec1de958c9b1fb571", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "contact:class:Employee", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053654, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "kind": 0, @@ -463,13 +463,13 @@ } }, { - "_id": "611d2d25131730f65a0197c2", + "_id": "6127d57ec1de958c9b1fb572", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "contact:class:Organization", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053654, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "kind": 0, @@ -477,13 +477,13 @@ } }, { - "_id": "611d2d25131730f65a0197c3", + "_id": "6127d57ec1de958c9b1fb573", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "contact:space:Employee", "objectClass": "core:class:Space", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053654, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "name": "Employees", @@ -493,11 +493,11 @@ } }, { - "_id": "611d2d25131730f65a0197c4", + "_id": "6127d57ec1de958c9b1fb574", "_class": "core:class:TxMixin", "space": "core:space:Tx", "modifiedBy": "core:account:System", - "modifiedOn": 1629302053654, + "modifiedOn": 1630000510001, "objectId": "contact:class:Person", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", @@ -507,13 +507,13 @@ } }, { - "_id": "611d2d25131730f65a0197c5", + "_id": "6127d57ec1de958c9b1fb575", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "chunter:class:Comment", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053654, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "domain": "comment", @@ -522,13 +522,13 @@ } }, { - "_id": "611d2d25131730f65a0197c6", + "_id": "6127d57ec1de958c9b1fb576", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "chunter:class:Backlink", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053654, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "kind": 0, @@ -536,13 +536,13 @@ } }, { - "_id": "611d2d25131730f65a0197c7", + "_id": "6127d57ec1de958c9b1fb577", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "chunter:class:Message", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053654, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "domain": "chunter", @@ -551,13 +551,32 @@ } }, { - "_id": "611d2d25131730f65a0197c8", + "_id": "6127d57ec1de958c9b1fb578", + "objectId": "6127d57ec1de958c9b1fb579", + "_class": "core:class:TxCreateDoc", + "space": "core:space:Tx", + "modifiedBy": "core:account:System", + "modifiedOn": 1630000509938, + "objectSpace": "core:space:Model", + "objectClass": "core:class:Attribute", + "attributes": { + "name": "content", + "index": 0, + "type": { + "_class": "core:class:TypeString" + }, + "label": "Content", + "attributeOf": "chunter:class:Message" + } + }, + { + "_id": "6127d57ec1de958c9b1fb57a", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "chunter:class:Channel", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053654, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "kind": 0, @@ -567,11 +586,11 @@ } }, { - "_id": "611d2d25131730f65a0197c9", + "_id": "6127d57ec1de958c9b1fb57b", "_class": "core:class:TxMixin", "space": "core:space:Tx", "modifiedBy": "core:account:System", - "modifiedOn": 1629302053655, + "modifiedOn": 1630000510001, "objectId": "chunter:class:Channel", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", @@ -583,13 +602,13 @@ } }, { - "_id": "611d2d25131730f65a0197ca", + "_id": "6127d57ec1de958c9b1fb57c", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "chunter:viewlet:Chat", "objectClass": "view:class:ViewletDescriptor", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053655, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "label": "Chat", @@ -598,13 +617,13 @@ } }, { - "_id": "611d2d25131730f65a0197cb", + "_id": "6127d57ec1de958c9b1fb57d", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", - "objectId": "611d2d25131730f65a0197cc", + "objectId": "6127d57ec1de958c9b1fb57e", "objectClass": "view:class:Viewlet", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053655, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "attachTo": "chunter:class:Message", @@ -614,13 +633,13 @@ } }, { - "_id": "611d2d25131730f65a0197cd", + "_id": "6127d57ec1de958c9b1fb57f", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", - "objectId": "611d2d25131730f65a0197ce", + "objectId": "6127d57ec1de958c9b1fb580", "objectClass": "workbench:class:Application", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053655, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "label": "chunter:string:ApplicationLabelChunter", @@ -638,13 +657,13 @@ } }, { - "_id": "611d2d25131730f65a0197cf", + "_id": "6127d57ec1de958c9b1fb581", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", - "objectId": "611d2d25131730f65a0197d0", + "objectId": "6127d57ec1de958c9b1fb582", "objectClass": "chunter:class:Channel", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053655, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "name": "general", @@ -654,13 +673,13 @@ } }, { - "_id": "611d2d25131730f65a0197d1", + "_id": "6127d57ec1de958c9b1fb583", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", - "objectId": "611d2d25131730f65a0197d2", + "objectId": "6127d57ec1de958c9b1fb584", "objectClass": "chunter:class:Channel", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053655, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "name": "random", @@ -670,13 +689,13 @@ } }, { - "_id": "611d2d25131730f65a0197d3", + "_id": "6127d57ec1de958c9b1fb585", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "task:class:Task", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053655, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "kind": 0, @@ -684,67 +703,67 @@ } }, { - "_id": "611d2d25131730f65a0197d4", - "objectId": "611d2d25131730f65a0197d5", + "_id": "6127d57ec1de958c9b1fb586", + "objectId": "6127d57ec1de958c9b1fb587", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "modifiedBy": "core:account:System", - "modifiedOn": 1629302053498, + "modifiedOn": 1630000509882, "objectSpace": "core:space:Model", "objectClass": "core:class:Attribute", "attributes": { + "name": "title", "type": { "_class": "core:class:TypeString" }, - "name": "title", "label": "Title", "attributeOf": "task:class:Task" } }, { - "_id": "611d2d25131730f65a0197d6", - "objectId": "611d2d25131730f65a0197d7", + "_id": "6127d57ec1de958c9b1fb588", + "objectId": "6127d57ec1de958c9b1fb589", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "modifiedBy": "core:account:System", - "modifiedOn": 1629302053498, + "modifiedOn": 1630000509882, "objectSpace": "core:space:Model", "objectClass": "core:class:Attribute", "attributes": { + "name": "description", "type": { "_class": "core:class:TypeString" }, - "name": "description", "label": "Description", "attributeOf": "task:class:Task" } }, { - "_id": "611d2d25131730f65a0197d8", - "objectId": "611d2d25131730f65a0197d9", + "_id": "6127d57ec1de958c9b1fb58a", + "objectId": "6127d57ec1de958c9b1fb58b", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "modifiedBy": "core:account:System", - "modifiedOn": 1629302053498, + "modifiedOn": 1630000509882, "objectSpace": "core:space:Model", "objectClass": "core:class:Attribute", "attributes": { + "name": "assignee", "type": { "_class": "core:class:TypeString" }, - "name": "assignee", "label": "Assignee", "attributeOf": "task:class:Task" } }, { - "_id": "611d2d25131730f65a0197da", + "_id": "6127d57ec1de958c9b1fb58c", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "task:class:Project", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053655, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "kind": 0, @@ -754,11 +773,11 @@ } }, { - "_id": "611d2d25131730f65a0197db", + "_id": "6127d57ec1de958c9b1fb58d", "_class": "core:class:TxMixin", "space": "core:space:Tx", "modifiedBy": "core:account:System", - "modifiedOn": 1629302053655, + "modifiedOn": 1630000510001, "objectId": "task:class:Project", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", @@ -771,13 +790,13 @@ } }, { - "_id": "611d2d25131730f65a0197dc", + "_id": "6127d57ec1de958c9b1fb58e", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", - "objectId": "611d2d25131730f65a0197dd", + "objectId": "6127d57ec1de958c9b1fb58f", "objectClass": "view:class:Viewlet", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053655, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "attachTo": "task:class:Task", @@ -795,13 +814,13 @@ } }, { - "_id": "611d2d25131730f65a0197de", + "_id": "6127d57ec1de958c9b1fb590", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", - "objectId": "611d2d25131730f65a0197df", + "objectId": "6127d57ec1de958c9b1fb591", "objectClass": "workbench:class:Application", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053655, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "label": "task:string:ApplicationLabelTask", @@ -819,13 +838,13 @@ } }, { - "_id": "611d2d25131730f65a0197e0", + "_id": "6127d57ec1de958c9b1fb592", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", - "objectId": "611d2d25131730f65a0197e1", + "objectId": "6127d57ec1de958c9b1fb593", "objectClass": "task:class:Project", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053655, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "name": "demo", @@ -835,13 +854,13 @@ } }, { - "_id": "611d2d25131730f65a0197e2", + "_id": "6127d57ec1de958c9b1fb594", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "recruit:class:Applicant", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053655, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "domain": "recruit", @@ -850,31 +869,31 @@ } }, { - "_id": "611d2d25131730f65a0197e3", - "objectId": "611d2d25131730f65a0197e4", + "_id": "6127d57ec1de958c9b1fb595", + "objectId": "6127d57ec1de958c9b1fb596", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "modifiedBy": "core:account:System", - "modifiedOn": 1629302053609, + "modifiedOn": 1630000509995, "objectSpace": "core:space:Model", "objectClass": "core:class:Attribute", "attributes": { + "name": "candidate", "type": { "_class": "core:class:TypeString" }, - "name": "candidate", "label": "Candidate", "attributeOf": "recruit:class:Applicant" } }, { - "_id": "611d2d25131730f65a0197e5", + "_id": "6127d57ec1de958c9b1fb597", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "recruit:class:Candidate", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053655, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "kind": 0, @@ -883,13 +902,13 @@ } }, { - "_id": "611d2d25131730f65a0197e6", + "_id": "6127d57ec1de958c9b1fb598", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "recruit:class:Candidates", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053655, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "kind": 0, @@ -899,13 +918,13 @@ } }, { - "_id": "611d2d25131730f65a0197e7", + "_id": "6127d57ec1de958c9b1fb599", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "recruit:class:Vacancy", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053655, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "kind": 0, @@ -915,11 +934,11 @@ } }, { - "_id": "611d2d25131730f65a0197e8", + "_id": "6127d57ec1de958c9b1fb59a", "_class": "core:class:TxMixin", "space": "core:space:Tx", "modifiedBy": "core:account:System", - "modifiedOn": 1629302053655, + "modifiedOn": 1630000510001, "objectId": "recruit:class:Vacancy", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", @@ -932,11 +951,11 @@ } }, { - "_id": "611d2d25131730f65a0197e9", + "_id": "6127d57ec1de958c9b1fb59b", "_class": "core:class:TxMixin", "space": "core:space:Tx", "modifiedBy": "core:account:System", - "modifiedOn": 1629302053655, + "modifiedOn": 1630000510001, "objectId": "recruit:class:Candidates", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", @@ -949,13 +968,13 @@ } }, { - "_id": "611d2d25131730f65a0197ea", + "_id": "6127d57ec1de958c9b1fb59c", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", - "objectId": "611d2d25131730f65a0197eb", + "objectId": "6127d57ec1de958c9b1fb59d", "objectClass": "workbench:class:Application", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053655, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "label": "recruit:string:RecruitApplication", @@ -979,13 +998,13 @@ } }, { - "_id": "611d2d25131730f65a0197ec", + "_id": "6127d57ec1de958c9b1fb59e", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "recruit:space:CandidatesPublic", "objectClass": "recruit:class:Candidates", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053655, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "name": "public", @@ -995,13 +1014,13 @@ } }, { - "_id": "611d2d25131730f65a0197ed", + "_id": "6127d57ec1de958c9b1fb59f", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", - "objectId": "611d2d25131730f65a0197ee", + "objectId": "6127d57ec1de958c9b1fb5a0", "objectClass": "view:class:Viewlet", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053655, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "attachTo": "recruit:class:Candidate", @@ -1016,13 +1035,13 @@ } }, { - "_id": "611d2d25131730f65a0197ef", + "_id": "6127d57ec1de958c9b1fb5a1", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", - "objectId": "611d2d25131730f65a0197f0", + "objectId": "6127d57ec1de958c9b1fb5a2", "objectClass": "view:class:Viewlet", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053655, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "attachTo": "recruit:class:Applicant", @@ -1041,13 +1060,13 @@ } }, { - "_id": "611d2d25131730f65a0197f1", + "_id": "6127d57ec1de958c9b1fb5a3", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", "objectId": "server-core:class:Trigger", "objectClass": "core:class:Class", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053655, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "domain": "model", @@ -1056,26 +1075,26 @@ } }, { - "_id": "611d2d25131730f65a0197f2", + "_id": "6127d57ec1de958c9b1fb5a4", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", - "objectId": "611d2d25131730f65a0197f3", + "objectId": "6127d57ec1de958c9b1fb5a5", "objectClass": "server-core:class:Trigger", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053655, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "trigger": "server-chunter:trigger:OnMessage" } }, { - "_id": "611d2d25131730f65a0197f4", + "_id": "6127d57ec1de958c9b1fb5a6", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", - "objectId": "611d2d25131730f65a0197f5", + "objectId": "6127d57ec1de958c9b1fb5a7", "objectClass": "contact:class:Employee", "objectSpace": "contact:space:Employee", - "modifiedOn": 1629302053655, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "firstName": "Rosamund", @@ -1086,26 +1105,26 @@ } }, { - "_id": "611d2d25131730f65a0197f6", + "_id": "6127d57ec1de958c9b1fb5a8", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", - "objectId": "611d2d25131730f65a0197f7", + "objectId": "6127d57ec1de958c9b1fb5a9", "objectClass": "core:class:Account", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053655, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "email": "rosamund@hc.engineering" } }, { - "_id": "611d2d25131730f65a0197f8", + "_id": "6127d57ec1de958c9b1fb5aa", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", - "objectId": "611d2d25131730f65a0197f9", + "objectId": "6127d57ec1de958c9b1fb5ab", "objectClass": "contact:class:Employee", "objectSpace": "contact:space:Employee", - "modifiedOn": 1629302053655, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "firstName": "Elon", @@ -1116,26 +1135,26 @@ } }, { - "_id": "611d2d25131730f65a0197fa", + "_id": "6127d57ec1de958c9b1fb5ac", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", - "objectId": "611d2d25131730f65a0197fb", + "objectId": "6127d57ec1de958c9b1fb5ad", "objectClass": "core:class:Account", "objectSpace": "core:space:Model", - "modifiedOn": 1629302053655, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "email": "elon@hc.engineering" } }, { - "_id": "611d2d25131730f65a0197fc", + "_id": "6127d57ec1de958c9b1fb5ae", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", - "objectId": "611d2d25131730f65a0197fd", + "objectId": "6127d57ec1de958c9b1fb5af", "objectClass": "recruit:class:Candidate", "objectSpace": "recruit:space:CandidatesPublic", - "modifiedOn": 1629302053655, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "firstName": "Andrey", @@ -1146,13 +1165,13 @@ } }, { - "_id": "611d2d25131730f65a0197fe", + "_id": "6127d57ec1de958c9b1fb5b0", "_class": "core:class:TxCreateDoc", "space": "core:space:Tx", - "objectId": "611d2d25131730f65a0197ff", + "objectId": "6127d57ec1de958c9b1fb5b1", "objectClass": "recruit:class:Candidate", "objectSpace": "recruit:space:CandidatesPublic", - "modifiedOn": 1629302053655, + "modifiedOn": 1630000510001, "modifiedBy": "core:account:System", "attributes": { "firstName": "Marina",