mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-04 14:28:15 +00:00
223 lines
7.2 KiB
TypeScript
223 lines
7.2 KiB
TypeScript
//
|
|
// Copyright © 2022 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 core, {
|
|
AccountRole,
|
|
type BulkUpdateEvent,
|
|
TxFactory,
|
|
TxProcessor,
|
|
type TxWorkspaceEvent,
|
|
WorkspaceEvent,
|
|
generateId,
|
|
type Account,
|
|
type Class,
|
|
type Doc,
|
|
type DocumentQuery,
|
|
type FindOptions,
|
|
type FindResult,
|
|
type LoadModelResponse,
|
|
type MeasureContext,
|
|
type Ref,
|
|
type SearchOptions,
|
|
type SearchQuery,
|
|
type SearchResult,
|
|
type Timestamp,
|
|
type Tx,
|
|
type TxApplyIf,
|
|
type TxApplyResult,
|
|
type TxCUD,
|
|
type TxResult
|
|
} from '@hcengineering/core'
|
|
import { type Pipeline, type SessionContext } from '@hcengineering/server-core'
|
|
import { type Token } from '@hcengineering/server-token'
|
|
import { type BroadcastCall, type Session, type SessionRequest, type StatisticsElement } from './types'
|
|
|
|
/**
|
|
* @public
|
|
*/
|
|
export class ClientSession implements Session {
|
|
createTime = Date.now()
|
|
requests = new Map<string, SessionRequest>()
|
|
binaryResponseMode: boolean = false
|
|
useCompression: boolean = true
|
|
useBroadcast: boolean = false
|
|
sessionId = ''
|
|
lastRequest = Date.now()
|
|
|
|
total: StatisticsElement = { find: 0, tx: 0 }
|
|
current: StatisticsElement = { find: 0, tx: 0 }
|
|
mins5: StatisticsElement = { find: 0, tx: 0 }
|
|
measures: { id: string, message: string, time: 0 }[] = []
|
|
|
|
constructor (
|
|
protected readonly broadcast: BroadcastCall,
|
|
protected readonly token: Token,
|
|
protected readonly _pipeline: Pipeline
|
|
) {}
|
|
|
|
getUser (): string {
|
|
return this.token.email
|
|
}
|
|
|
|
pipeline (): Pipeline {
|
|
return this._pipeline
|
|
}
|
|
|
|
async ping (): Promise<string> {
|
|
// console.log('ping')
|
|
this.lastRequest = Date.now()
|
|
return 'pong!'
|
|
}
|
|
|
|
async loadModel (ctx: MeasureContext, lastModelTx: Timestamp, hash?: string): Promise<Tx[] | LoadModelResponse> {
|
|
return await ctx.with('load-model', {}, async () => await this._pipeline.storage.loadModel(lastModelTx, hash))
|
|
}
|
|
|
|
async getAccount (ctx: MeasureContext): Promise<Account> {
|
|
const account = await this._pipeline.modelDb.findAll(core.class.Account, { email: this.token.email })
|
|
if (account.length === 0 && this.token.extra?.admin === 'true') {
|
|
const systemAccount = await this._pipeline.modelDb.findAll(core.class.Account, {
|
|
_id: this.token.email as Ref<Account>
|
|
})
|
|
if (systemAccount.length === 0) {
|
|
// Generate account for admin user
|
|
const factory = new TxFactory(core.account.System)
|
|
const email = `system:${this.token.email}`
|
|
const createTx = factory.createTxCreateDoc(
|
|
core.class.Account,
|
|
core.space.Model,
|
|
{
|
|
role: AccountRole.Owner,
|
|
email
|
|
},
|
|
this.token.email as Ref<Account>
|
|
)
|
|
const context = ctx as SessionContext
|
|
context.userEmail = this.token.email
|
|
context.admin = this.token.extra?.admin === 'true'
|
|
await this._pipeline.tx(context, createTx)
|
|
const acc = TxProcessor.createDoc2Doc(createTx)
|
|
return acc
|
|
} else {
|
|
return systemAccount[0]
|
|
}
|
|
}
|
|
return account[0]
|
|
}
|
|
|
|
async findAll<T extends Doc>(
|
|
ctx: MeasureContext,
|
|
_class: Ref<Class<T>>,
|
|
query: DocumentQuery<T>,
|
|
options?: FindOptions<T>
|
|
): Promise<FindResult<T>> {
|
|
this.lastRequest = Date.now()
|
|
this.total.find++
|
|
this.current.find++
|
|
const context = ctx as SessionContext
|
|
context.userEmail = this.token.email
|
|
context.admin = this.token.extra?.admin === 'true'
|
|
return await this._pipeline.findAll(context, _class, query, options)
|
|
}
|
|
|
|
async searchFulltext (ctx: MeasureContext, query: SearchQuery, options: SearchOptions): Promise<SearchResult> {
|
|
this.lastRequest = Date.now()
|
|
const context = ctx as SessionContext
|
|
context.userEmail = this.token.email
|
|
context.admin = this.token.extra?.admin === 'true'
|
|
return await this._pipeline.searchFulltext(context, query, options)
|
|
}
|
|
|
|
async tx (ctx: MeasureContext, tx: Tx): Promise<TxResult> {
|
|
this.lastRequest = Date.now()
|
|
this.total.tx++
|
|
this.current.tx++
|
|
const context = ctx as SessionContext
|
|
context.userEmail = this.token.email
|
|
context.admin = this.token.extra?.admin === 'true'
|
|
const [result, derived, target] = await this._pipeline.tx(context, tx)
|
|
|
|
let shouldBroadcast = true
|
|
|
|
if (tx._class === core.class.TxApplyIf) {
|
|
const apply = tx as TxApplyIf
|
|
shouldBroadcast = apply.notify ?? true
|
|
}
|
|
|
|
if (tx._class !== core.class.TxApplyIf) {
|
|
this.broadcast(null, this.token.workspace, { result: tx }, target)
|
|
}
|
|
if (shouldBroadcast) {
|
|
if (this.useBroadcast) {
|
|
if (derived.length > 250) {
|
|
const classes = new Set<Ref<Class<Doc>>>()
|
|
for (const dtx of derived) {
|
|
if (this._pipeline.storage.hierarchy.isDerived(dtx._class, core.class.TxCUD)) {
|
|
classes.add((dtx as TxCUD<Doc>).objectClass)
|
|
}
|
|
const etx = TxProcessor.extractTx(dtx)
|
|
if (this._pipeline.storage.hierarchy.isDerived(etx._class, core.class.TxCUD)) {
|
|
classes.add((etx as TxCUD<Doc>).objectClass)
|
|
}
|
|
}
|
|
console.log('Broadcasting bulk', derived.length)
|
|
const bevent = this.createBroadcastEvent(Array.from(classes))
|
|
if (tx._class === core.class.TxApplyIf) {
|
|
;(result as TxApplyResult).derived.push(bevent)
|
|
}
|
|
this.broadcast(null, this.token.workspace, { result: bevent }, target)
|
|
} else {
|
|
if (tx._class === core.class.TxApplyIf) {
|
|
;(result as TxApplyResult).derived.push(...derived)
|
|
}
|
|
while (derived.length > 0) {
|
|
const part = derived.splice(0, 250)
|
|
console.log('Broadcasting part', part.length, derived.length)
|
|
this.broadcast(null, this.token.workspace, { result: part }, target)
|
|
}
|
|
}
|
|
} else {
|
|
while (derived.length > 0) {
|
|
const part = derived.splice(0, 250)
|
|
this.broadcast(null, this.token.workspace, { result: part }, target)
|
|
}
|
|
}
|
|
}
|
|
if (tx._class === core.class.TxApplyIf) {
|
|
const apply = tx as TxApplyIf
|
|
|
|
if (apply.extraNotify !== undefined && apply.extraNotify.length > 0) {
|
|
;(result as TxApplyResult).derived.push(this.createBroadcastEvent(apply.extraNotify))
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
private createBroadcastEvent (classes: Ref<Class<Doc>>[]): TxWorkspaceEvent<BulkUpdateEvent> {
|
|
return {
|
|
_class: core.class.TxWorkspaceEvent,
|
|
_id: generateId(),
|
|
event: WorkspaceEvent.BulkUpdate,
|
|
params: {
|
|
_class: classes
|
|
},
|
|
modifiedBy: core.account.System,
|
|
modifiedOn: Date.now(),
|
|
objectSpace: core.space.DerivedTx,
|
|
space: core.space.DerivedTx
|
|
}
|
|
}
|
|
}
|