Merge remote-tracking branch 'origin/develop' into staging

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2024-12-25 22:40:43 +07:00
commit 17df8e193b
No known key found for this signature in database
GPG Key ID: BD80F68D68D8F7F2
27 changed files with 130 additions and 86 deletions

3
.vscode/launch.json vendored
View File

@ -441,7 +441,6 @@
"CLIENT_SECRET": "${env:POD_GITHUB_CLIENT_SECRET}", "CLIENT_SECRET": "${env:POD_GITHUB_CLIENT_SECRET}",
"PRIVATE_KEY": "${env:POD_GITHUB_PRIVATE_KEY}", "PRIVATE_KEY": "${env:POD_GITHUB_PRIVATE_KEY}",
"COLLABORATOR_URL": "ws://localhost:3078", "COLLABORATOR_URL": "ws://localhost:3078",
"SYSTEM_EMAIL": "anticrm@hc.engineering",
"MINIO_ENDPOINT": "localhost", "MINIO_ENDPOINT": "localhost",
"MINIO_ACCESS_KEY": "minioadmin", "MINIO_ACCESS_KEY": "minioadmin",
"MINIO_SECRET_KEY": "minioadmin", "MINIO_SECRET_KEY": "minioadmin",
@ -463,7 +462,6 @@
"args": ["src/index.ts"], "args": ["src/index.ts"],
"env": { "env": {
"ACCOUNTS_URL": "http://localhost:3000", "ACCOUNTS_URL": "http://localhost:3000",
"SYSTEM_EMAIL": "anticrm@hc.engineering",
"SECRET": "secret", "SECRET": "secret",
"DOCS_RELEASE_INTERVAL": "10000", "DOCS_RELEASE_INTERVAL": "10000",
"DOCS_IN_REVIEW_CHECK_INTERVAL": "10000", "DOCS_IN_REVIEW_CHECK_INTERVAL": "10000",
@ -523,7 +521,6 @@
"MINIO_ACCESS_KEY": "minioadmin", "MINIO_ACCESS_KEY": "minioadmin",
"MINIO_SECRET_KEY": "minioadmin", "MINIO_SECRET_KEY": "minioadmin",
"SERVICE_ID": "sign-service", "SERVICE_ID": "sign-service",
"SYSTEM_EMAIL": "",
"ACCOUNTS_URL": "http://localhost:3000", "ACCOUNTS_URL": "http://localhost:3000",
"BRANDING_PATH": "${workspaceRoot}/services/sign/pod-sign/debug/branding.json" "BRANDING_PATH": "${workspaceRoot}/services/sign/pod-sign/debug/branding.json"
}, },

View File

@ -81,7 +81,7 @@ async function moveWorkspace (
tables = tables.filter((t) => include.has(t)) tables = tables.filter((t) => include.has(t))
} }
await createTables(new MeasureMetricsContext('', {}), pgClient, tables) await createTables(new MeasureMetricsContext('', {}), pgClient, '', tables)
const token = generateToken(systemAccountEmail, wsId) const token = generateToken(systemAccountEmail, wsId)
const endpoint = await getTransactorEndpoint(token, 'external') const endpoint = await getTransactorEndpoint(token, 'external')
const connection = (await connect(endpoint, wsId, undefined, { const connection = (await connect(endpoint, wsId, undefined, {

View File

@ -361,7 +361,8 @@ export function createModel (builder: Builder): void {
viewOptions: { viewOptions: {
groupBy: [], groupBy: [],
orderBy: [], orderBy: [],
other: [vacancyHideArchivedOption] other: [vacancyHideArchivedOption],
storageKey: 'vacancyViewOptions'
} }
}, },
recruit.viewlet.TableVacancy recruit.viewlet.TableVacancy
@ -502,7 +503,8 @@ export function createModel (builder: Builder): void {
viewOptions: { viewOptions: {
groupBy: [], groupBy: [],
orderBy: [], orderBy: [],
other: [applicationDoneOption, hideApplicantsFromArchivedVacanciesOption] other: [applicationDoneOption, hideApplicantsFromArchivedVacanciesOption],
storageKey: 'applicantViewOptions'
} }
}, },
recruit.viewlet.ApplicantTable recruit.viewlet.ApplicantTable
@ -559,7 +561,8 @@ export function createModel (builder: Builder): void {
action: view.function.ShowEmptyGroups, action: view.function.ShowEmptyGroups,
label: view.string.ShowEmptyGroups label: view.string.ShowEmptyGroups
} }
] ],
storageKey: 'applicantViewOptions'
} }
if (colors) { if (colors) {
model.other.push(showColorsViewOption) model.other.push(showColorsViewOption)
@ -784,7 +787,8 @@ export function createModel (builder: Builder): void {
['modifiedOn', SortingOrder.Descending], ['modifiedOn', SortingOrder.Descending],
['createdOn', SortingOrder.Descending] ['createdOn', SortingOrder.Descending]
], ],
other: [vacancyHideArchivedOption] other: [vacancyHideArchivedOption],
storageKey: 'vacancyViewOptions'
} }
}, },
recruit.viewlet.ListVacancy recruit.viewlet.ListVacancy

View File

@ -14,7 +14,7 @@
--> -->
<script lang="ts"> <script lang="ts">
import { Attachment } from '@hcengineering/attachment' import { Attachment } from '@hcengineering/attachment'
import { Account, Class, Doc, IdMap, Markup, Ref, Space, generateId, toIdMap } from '@hcengineering/core' import { RateLimiter, Account, Class, Doc, IdMap, Markup, Ref, Space, generateId, toIdMap } from '@hcengineering/core'
import { Asset, IntlString, setPlatformStatus, unknownError } from '@hcengineering/platform' import { Asset, IntlString, setPlatformStatus, unknownError } from '@hcengineering/platform'
import { import {
DraftController, DraftController,
@ -183,12 +183,14 @@
await tick() await tick()
const list = inputFile.files const list = inputFile.files
if (list === null || list.length === 0) return if (list === null || list.length === 0) return
const limiter = new RateLimiter(10)
for (let index = 0; index < list.length; index++) { for (let index = 0; index < list.length; index++) {
const file = list.item(index) const file = list.item(index)
if (file !== null) { if (file !== null) {
await createAttachment(file) await limiter.add(() => createAttachment(file))
} }
} }
await limiter.waitProcessing()
inputFile.value = '' inputFile.value = ''
progress = false progress = false
} }
@ -196,13 +198,16 @@
async function fileDrop (e: DragEvent): Promise<void> { async function fileDrop (e: DragEvent): Promise<void> {
progress = true progress = true
const list = e.dataTransfer?.files const list = e.dataTransfer?.files
const limiter = new RateLimiter(10)
if (list === undefined || list.length === 0) return if (list === undefined || list.length === 0) return
for (let index = 0; index < list.length; index++) { for (let index = 0; index < list.length; index++) {
const file = list.item(index) const file = list.item(index)
if (file !== null) { if (file !== null) {
await createAttachment(file) await limiter.add(() => createAttachment(file))
} }
} }
await limiter.waitProcessing()
progress = false progress = false
} }
@ -257,17 +262,17 @@
return return
} }
saved = true saved = true
const promises: Promise<any>[] = [] const limiter = new RateLimiter(10)
newAttachments.forEach((p) => { newAttachments.forEach((p) => {
const attachment = attachments.get(p) const attachment = attachments.get(p)
if (attachment !== undefined) { if (attachment !== undefined) {
promises.push(saveAttachment(attachment)) void limiter.add(() => saveAttachment(attachment))
} }
}) })
removedAttachments.forEach((p) => { removedAttachments.forEach((p) => {
promises.push(deleteAttachment(p)) void limiter.add(() => deleteAttachment(p))
}) })
await Promise.all(promises) await limiter.waitProcessing()
newAttachments.clear() newAttachments.clear()
removedAttachments.clear() removedAttachments.clear()
saveDraft() saveDraft()

View File

@ -125,7 +125,7 @@ export function adjustSelectionIndent (
let insertionOffset = 0 let insertionOffset = 0
for (const range of ranges) { for (const range of ranges) {
if (direction > 0 ? range.text === '' : range.indent === 0) { if (direction > 0 ? range.text === '' && ranges.length > 1 : range.indent === 0) {
continue continue
} }
const indentOffset = indentLevelOffset(range.indent, direction) const indentOffset = indentLevelOffset(range.indent, direction)

View File

@ -117,7 +117,8 @@ export const MermaidExtension = CodeBlockLowlight.extend<MermaidOptions>({
}, },
addProseMirrorPlugins () { addProseMirrorPlugins () {
return [...(this.parent?.() ?? []), MermaidDecorator(this.options)] const parent = (this.parent?.() ?? []).filter((p) => p.props.handlePaste === undefined)
return [...parent, MermaidDecorator(this.options)]
}, },
addNodeView () { addNodeView () {

View File

@ -31,8 +31,11 @@ export function isDropdownType (viewOption: ViewOptionModel): viewOption is Drop
return viewOption.type === 'dropdown' return viewOption.type === 'dropdown'
} }
export function makeViewOptionsKey (viewlet: Ref<Viewlet>, variant?: string): string { function makeViewOptionsKey (viewlet: Viewlet, variant?: string, ignoreViewletKey = false): string {
const prefix = viewlet + (variant !== undefined ? `-${variant}` : '') const prefix =
viewlet.viewOptions?.storageKey !== undefined && !ignoreViewletKey
? viewlet.viewOptions.storageKey
: viewlet._id + (variant !== undefined ? `-${variant}` : '')
const loc = getCurrentResolvedLocation() const loc = getCurrentResolvedLocation()
loc.fragment = undefined loc.fragment = undefined
loc.query = undefined loc.query = undefined
@ -40,7 +43,7 @@ export function makeViewOptionsKey (viewlet: Ref<Viewlet>, variant?: string): st
} }
export function setViewOptions (viewlet: Viewlet, options: ViewOptions): void { export function setViewOptions (viewlet: Viewlet, options: ViewOptions): void {
const key = makeViewOptionsKey(viewlet._id, viewlet.variant) const key = makeViewOptionsKey(viewlet, viewlet.variant)
localStorage.setItem(key, JSON.stringify(options)) localStorage.setItem(key, JSON.stringify(options))
setStore(key, options) setStore(key, options)
} }
@ -52,13 +55,19 @@ function setStore (key: string, options: ViewOptions): void {
} }
function _getViewOptions (viewlet: Viewlet, viewOptionStore: Map<string, ViewOptions>): ViewOptions | null { function _getViewOptions (viewlet: Viewlet, viewOptionStore: Map<string, ViewOptions>): ViewOptions | null {
const key = makeViewOptionsKey(viewlet._id, viewlet.variant) const key = makeViewOptionsKey(viewlet, viewlet.variant)
const store = viewOptionStore.get(key) const store = viewOptionStore.get(key)
if (store !== undefined) { if (store !== undefined) {
return store return store
} }
const options = localStorage.getItem(key) let options = localStorage.getItem(key)
if (options === null) return null if (options === null) {
const key = makeViewOptionsKey(viewlet, viewlet.variant, true)
options = localStorage.getItem(key)
if (options === null) {
return null
}
}
const res = JSON.parse(options) const res = JSON.parse(options)
setStore(key, res) setStore(key, res)
return res return res

View File

@ -794,6 +794,7 @@ export interface ViewOptionsModel {
orderBy: OrderOption[] orderBy: OrderOption[]
other: ViewOptionModel[] other: ViewOptionModel[]
groupDepth?: number groupDepth?: number
storageKey?: string
} }
/** /**

View File

@ -173,11 +173,10 @@ export async function OnUserStatus (txes: Tx[], control: TriggerControl): Promis
async function roomJoinHandler (info: ParticipantInfo, control: TriggerControl): Promise<Tx[]> { async function roomJoinHandler (info: ParticipantInfo, control: TriggerControl): Promise<Tx[]> {
const roomInfos = await control.queryFind(control.ctx, love.class.RoomInfo, {}) const roomInfos = await control.queryFind(control.ctx, love.class.RoomInfo, {})
const roomInfo = roomInfos.find((ri) => ri.room === info.room) const roomInfo = roomInfos.find((ri) => ri.room === info.room)
if (roomInfo !== undefined) { if (roomInfo !== undefined && !roomInfo.persons.includes(info.person)) {
roomInfo.persons.push(info.person)
return [ return [
control.txFactory.createTxUpdateDoc(love.class.RoomInfo, core.space.Workspace, roomInfo._id, { control.txFactory.createTxUpdateDoc(love.class.RoomInfo, core.space.Workspace, roomInfo._id, {
persons: Array.from(new Set([...roomInfo.persons, info.person])) $push: { persons: info.person }
}) })
] ]
} else { } else {
@ -221,7 +220,6 @@ async function setDefaultRoomAccess (info: ParticipantInfo, control: TriggerCont
const roomInfos = await control.queryFind(control.ctx, love.class.RoomInfo, {}) const roomInfos = await control.queryFind(control.ctx, love.class.RoomInfo, {})
const oldRoomInfo = roomInfos.find((ri) => ri.persons.includes(info.person)) const oldRoomInfo = roomInfos.find((ri) => ri.persons.includes(info.person))
if (oldRoomInfo !== undefined) { if (oldRoomInfo !== undefined) {
oldRoomInfo.persons = oldRoomInfo.persons.filter((p) => p !== info.person)
if (oldRoomInfo.persons.length === 0) { if (oldRoomInfo.persons.length === 0) {
res.push(control.txFactory.createTxRemoveDoc(oldRoomInfo._class, oldRoomInfo.space, oldRoomInfo._id)) res.push(control.txFactory.createTxRemoveDoc(oldRoomInfo._class, oldRoomInfo.space, oldRoomInfo._id))
@ -237,7 +235,7 @@ async function setDefaultRoomAccess (info: ParticipantInfo, control: TriggerCont
} else { } else {
res.push( res.push(
control.txFactory.createTxUpdateDoc(love.class.RoomInfo, core.space.Workspace, oldRoomInfo._id, { control.txFactory.createTxUpdateDoc(love.class.RoomInfo, core.space.Workspace, oldRoomInfo._id, {
persons: oldRoomInfo.persons $pull: { persons: info.person }
}) })
) )
} }
@ -433,23 +431,44 @@ async function isRoomEmpty (
return false return false
} }
function combineAttributes (attributes: any[], key: string, operator: string, arrayKey: string): any[] {
return Array.from(
new Set(
attributes.flatMap((attr) =>
Array.isArray(attr[operator]?.[key]?.[arrayKey]) ? attr[operator]?.[key]?.[arrayKey] : attr[operator]?.[key]
)
)
).filter((v) => v != null)
}
async function OnRoomInfo (txes: TxCUD<RoomInfo>[], control: TriggerControl): Promise<Tx[]> { async function OnRoomInfo (txes: TxCUD<RoomInfo>[], control: TriggerControl): Promise<Tx[]> {
const result: Tx[] = [] const result: Tx[] = []
const personsByRoom = new Map<Ref<RoomInfo>, Ref<Person>[]>()
for (const tx of txes) { for (const tx of txes) {
if (tx._class === core.class.TxRemoveDoc) { if (tx._class === core.class.TxRemoveDoc) {
const roomInfo = control.removedMap.get(tx.objectId) as RoomInfo const roomInfo = control.removedMap.get(tx.objectId) as RoomInfo
if (roomInfo === undefined) continue if (roomInfo === undefined) continue
if (roomInfo.room === love.ids.Reception) continue if (roomInfo.room === love.ids.Reception) continue
personsByRoom.delete(tx.objectId)
result.push(...(await finishRoomMeetings(roomInfo.room, tx.modifiedOn, control))) result.push(...(await finishRoomMeetings(roomInfo.room, tx.modifiedOn, control)))
continue continue
} }
if (tx._class === core.class.TxUpdateDoc) { if (tx._class === core.class.TxUpdateDoc) {
const newPersons = (tx as TxUpdateDoc<RoomInfo>).operations.persons const updateTx = tx as TxUpdateDoc<RoomInfo>
if (newPersons === undefined) continue const pulled = combineAttributes([updateTx.operations], 'persons', '$pull', '$in')
const pushed = combineAttributes([updateTx.operations], 'persons', '$push', '$each')
if (pulled.length === 0 && pushed.length === 0) continue
const roomInfos = await control.queryFind(control.ctx, love.class.RoomInfo, {}) const roomInfos = await control.queryFind(control.ctx, love.class.RoomInfo, {})
const roomInfo = roomInfos.find((r) => r._id === tx.objectId) const roomInfo = roomInfos.find((r) => r._id === tx.objectId)
if (roomInfo === undefined) continue if (roomInfo === undefined) continue
if (roomInfo.room === love.ids.Reception) continue if (roomInfo.room === love.ids.Reception) continue
const currentPersons = personsByRoom.get(tx.objectId) ?? roomInfo.persons
const newPersons = currentPersons.filter((p) => !pulled.includes(p)).concat(pushed)
personsByRoom.set(tx.objectId, newPersons)
if (await isRoomEmpty(roomInfo.room, roomInfo.isOffice, newPersons, control)) { if (await isRoomEmpty(roomInfo.room, roomInfo.isOffice, newPersons, control)) {
result.push(...(await finishRoomMeetings(roomInfo.room, tx.modifiedOn, control))) result.push(...(await finishRoomMeetings(roomInfo.room, tx.modifiedOn, control)))
} }

View File

@ -761,8 +761,7 @@ export async function createAcc (
const salt = randomBytes(32) const salt = randomBytes(32)
const hash = password !== null ? hashWithSalt(password, salt) : null const hash = password !== null ? hashWithSalt(password, salt) : null
const systemEmails = [systemAccountEmail] if (systemAccountEmail === email) {
if (systemEmails.includes(email)) {
ctx.error('system email used for account', { email }) ctx.error('system email used for account', { email })
throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountAlreadyExists, { account: email })) throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountAlreadyExists, { account: email }))
} }

View File

@ -37,6 +37,7 @@ import core, {
type ModelDb, type ModelDb,
type ObjQueryType, type ObjQueryType,
type Projection, type Projection,
RateLimiter,
type Ref, type Ref,
type ReverseLookups, type ReverseLookups,
type SessionData, type SessionData,
@ -1506,13 +1507,18 @@ interface OperationBulk {
mixins: TxMixin<Doc, Doc>[] mixins: TxMixin<Doc, Doc>[]
} }
const initRateLimit = new RateLimiter(1)
class PostgresAdapter extends PostgresAdapterBase { class PostgresAdapter extends PostgresAdapterBase {
async init (ctx: MeasureContext, domains?: string[], excludeDomains?: string[]): Promise<void> { async init (ctx: MeasureContext, domains?: string[], excludeDomains?: string[]): Promise<void> {
let resultDomains = domains ?? this.hierarchy.domains() let resultDomains = domains ?? this.hierarchy.domains()
if (excludeDomains !== undefined) { if (excludeDomains !== undefined) {
resultDomains = resultDomains.filter((it) => !excludeDomains.includes(it)) resultDomains = resultDomains.filter((it) => !excludeDomains.includes(it))
} }
await createTables(ctx, this.client, resultDomains) const url = this.refClient.url()
await initRateLimit.exec(async () => {
await createTables(ctx, this.client, url, resultDomains)
})
this._helper.domains = new Set(resultDomains as Domain[]) this._helper.domains = new Set(resultDomains as Domain[])
} }
@ -1789,7 +1795,10 @@ class PostgresAdapter extends PostgresAdapterBase {
class PostgresTxAdapter extends PostgresAdapterBase implements TxAdapter { class PostgresTxAdapter extends PostgresAdapterBase implements TxAdapter {
async init (ctx: MeasureContext, domains?: string[], excludeDomains?: string[]): Promise<void> { async init (ctx: MeasureContext, domains?: string[], excludeDomains?: string[]): Promise<void> {
const resultDomains = domains ?? [DOMAIN_TX, DOMAIN_MODEL_TX] const resultDomains = domains ?? [DOMAIN_TX, DOMAIN_MODEL_TX]
await createTables(ctx, this.client, resultDomains) await initRateLimit.exec(async () => {
const url = this.refClient.url()
await createTables(ctx, this.client, url, resultDomains)
})
this._helper.domains = new Set(resultDomains as Domain[]) this._helper.domains = new Set(resultDomains as Domain[])
} }

View File

@ -72,8 +72,13 @@ export const NumericTypes = [
core.class.Collection core.class.Collection
] ]
export async function createTables (ctx: MeasureContext, client: postgres.Sql, domains: string[]): Promise<void> { export async function createTables (
const filtered = domains.filter((d) => !loadedDomains.has(d)) ctx: MeasureContext,
client: postgres.Sql,
url: string,
domains: string[]
): Promise<void> {
const filtered = domains.filter((d) => !loadedDomains.has(url + translateDomain(d)))
if (filtered.length === 0) { if (filtered.length === 0) {
return return
} }
@ -90,17 +95,15 @@ export async function createTables (ctx: MeasureContext, client: postgres.Sql, d
const exists = new Set(tables.map((it) => it.table_name)) const exists = new Set(tables.map((it) => it.table_name))
await retryTxn(client, async (client) => { await retryTxn(client, async (client) => {
await ctx.with('load-schemas', {}, () => const domainsToLoad = mapped.filter((it) => exists.has(it))
getTableSchema( if (domainsToLoad.length > 0) {
client, await ctx.with('load-schemas', {}, () => getTableSchema(client, domainsToLoad))
mapped.filter((it) => exists.has(it)) }
)
)
for (const domain of mapped) { for (const domain of mapped) {
if (!exists.has(domain)) { if (!exists.has(domain)) {
await ctx.with('create-table', {}, () => createTable(client, domain)) await ctx.with('create-table', {}, () => createTable(client, domain))
} }
loadedDomains.add(domain) loadedDomains.add(url + domain)
} }
}) })
} }
@ -188,6 +191,8 @@ export async function shutdown (): Promise<void> {
export interface PostgresClientReference { export interface PostgresClientReference {
getClient: () => Promise<postgres.Sql> getClient: () => Promise<postgres.Sql>
close: () => void close: () => void
url: () => string
} }
class PostgresClientReferenceImpl { class PostgresClientReferenceImpl {
@ -195,6 +200,7 @@ class PostgresClientReferenceImpl {
client: postgres.Sql | Promise<postgres.Sql> client: postgres.Sql | Promise<postgres.Sql>
constructor ( constructor (
readonly connectionString: string,
client: postgres.Sql | Promise<postgres.Sql>, client: postgres.Sql | Promise<postgres.Sql>,
readonly onclose: () => void readonly onclose: () => void
) { ) {
@ -202,6 +208,10 @@ class PostgresClientReferenceImpl {
this.client = client this.client = client
} }
url (): string {
return this.connectionString
}
async getClient (): Promise<postgres.Sql> { async getClient (): Promise<postgres.Sql> {
if (this.client instanceof Promise) { if (this.client instanceof Promise) {
this.client = await this.client this.client = await this.client
@ -233,6 +243,10 @@ export class ClientRef implements PostgresClientReference {
clientRefs.set(this.id, this) clientRefs.set(this.id, this)
} }
url (): string {
return this.client.url()
}
closed = false closed = false
async getClient (): Promise<postgres.Sql> { async getClient (): Promise<postgres.Sql> {
if (!this.closed) { if (!this.closed) {
@ -274,7 +288,7 @@ export function getDBClient (connectionString: string, database?: string): Postg
...extraOptions ...extraOptions
}) })
existing = new PostgresClientReferenceImpl(sql, () => { existing = new PostgresClientReferenceImpl(connectionString, sql, () => {
connections.delete(key) connections.delete(key)
}) })
connections.set(key, existing) connections.set(key, existing)

View File

@ -23,7 +23,6 @@ interface Config {
Secret: string Secret: string
Credentials: string Credentials: string
WATCH_URL: string WATCH_URL: string
SystemEmail: string
InitLimit: number InitLimit: number
} }
@ -37,7 +36,6 @@ const envMap: { [key in keyof Config]: string } = {
ServiceID: 'SERVICE_ID', ServiceID: 'SERVICE_ID',
Secret: 'SECRET', Secret: 'SECRET',
Credentials: 'Credentials', Credentials: 'Credentials',
SystemEmail: 'SYSTEM_EMAIL',
WATCH_URL: 'WATCH_URL', WATCH_URL: 'WATCH_URL',
InitLimit: 'INIT_LIMIT' InitLimit: 'INIT_LIMIT'
} }
@ -52,7 +50,6 @@ const config: Config = (() => {
AccountsURL: process.env[envMap.AccountsURL], AccountsURL: process.env[envMap.AccountsURL],
ServiceID: process.env[envMap.ServiceID] ?? 'calendar-service', ServiceID: process.env[envMap.ServiceID] ?? 'calendar-service',
Secret: process.env[envMap.Secret], Secret: process.env[envMap.Secret],
SystemEmail: process.env[envMap.SystemEmail] ?? 'anticrm@hc.engineering',
Credentials: process.env[envMap.Credentials], Credentials: process.env[envMap.Credentials],
InitLimit: parseNumber(process.env[envMap.InitLimit]) ?? 50, InitLimit: parseNumber(process.env[envMap.InitLimit]) ?? 50,
WATCH_URL: process.env[envMap.WATCH_URL] WATCH_URL: process.env[envMap.WATCH_URL]

View File

@ -18,6 +18,7 @@ import contact, { Channel, Contact, type Employee, type PersonAccount } from '@h
import core, { import core, {
TxOperations, TxOperations,
TxProcessor, TxProcessor,
systemAccountEmail,
toIdMap, toIdMap,
type Account, type Account,
type Client, type Client,
@ -34,7 +35,6 @@ import { Collection, type Db } from 'mongodb'
import { CalendarClient } from './calendar' import { CalendarClient } from './calendar'
import { CalendarController } from './calendarController' import { CalendarController } from './calendarController'
import { getClient } from './client' import { getClient } from './client'
import config from './config'
import { SyncHistory, type ProjectCredentials, type User } from './types' import { SyncHistory, type ProjectCredentials, type User } from './types'
export class WorkspaceClient { export class WorkspaceClient {
@ -159,7 +159,7 @@ export class WorkspaceClient {
} }
private async initClient (workspace: string): Promise<Client> { private async initClient (workspace: string): Promise<Client> {
const token = generateToken(config.SystemEmail, { name: workspace }) const token = generateToken(systemAccountEmail, { name: workspace })
const client = await getClient(token) const client = await getClient(token)
client.notify = (...tx: Tx[]) => { client.notify = (...tx: Tx[]) => {
void this.txHandler(...tx) void this.txHandler(...tx)

View File

@ -5,7 +5,7 @@
import client, { ClientSocket } from '@hcengineering/client' import client, { ClientSocket } from '@hcengineering/client'
import clientResources from '@hcengineering/client-resources' import clientResources from '@hcengineering/client-resources'
import { Client, ClientConnectEvent } from '@hcengineering/core' import { Client, ClientConnectEvent, systemAccountEmail } from '@hcengineering/core'
import { setMetadata } from '@hcengineering/platform' import { setMetadata } from '@hcengineering/platform'
import { getTransactorEndpoint } from '@hcengineering/server-client' import { getTransactorEndpoint } from '@hcengineering/server-client'
import serverToken, { generateToken } from '@hcengineering/server-token' import serverToken, { generateToken } from '@hcengineering/server-token'
@ -30,7 +30,7 @@ export async function createPlatformClient (
setMetadata(serverToken.metadata.Secret, config.ServerSecret) setMetadata(serverToken.metadata.Secret, config.ServerSecret)
const token = generateToken( const token = generateToken(
config.SystemEmail, systemAccountEmail,
{ {
name: workspace name: workspace
}, },

View File

@ -4,7 +4,7 @@
// //
import { CollaboratorClient, getClient as getCollaboratorClient } from '@hcengineering/collaborator-client' import { CollaboratorClient, getClient as getCollaboratorClient } from '@hcengineering/collaborator-client'
import { WorkspaceId } from '@hcengineering/core' import { systemAccountEmail, WorkspaceId } from '@hcengineering/core'
import { generateToken } from '@hcengineering/server-token' import { generateToken } from '@hcengineering/server-token'
import config from './config' import config from './config'
@ -12,6 +12,6 @@ import config from './config'
* @public * @public
*/ */
export function createCollaboratorClient (workspaceId: WorkspaceId): CollaboratorClient { export function createCollaboratorClient (workspaceId: WorkspaceId): CollaboratorClient {
const token = generateToken(config.SystemEmail, workspaceId, { mode: 'github' }) const token = generateToken(systemAccountEmail, workspaceId, { mode: 'github' })
return getCollaboratorClient(workspaceId, token, config.CollaboratorURL) return getCollaboratorClient(workspaceId, token, config.CollaboratorURL)
} }

View File

@ -2,13 +2,10 @@
// Copyright © 2023 Hardcore Engineering Inc. // Copyright © 2023 Hardcore Engineering Inc.
// //
import { systemAccountEmail } from '@hcengineering/core'
interface Config { interface Config {
AccountsURL: string AccountsURL: string
ServiceID: string ServiceID: string
ServerSecret: string ServerSecret: string
SystemEmail: string
FrontURL: string FrontURL: string
// '*' means all workspaces // '*' means all workspaces
@ -36,7 +33,6 @@ const envMap: { [key in keyof Config]: string } = {
AccountsURL: 'ACCOUNTS_URL', AccountsURL: 'ACCOUNTS_URL',
ServiceID: 'SERVICE_ID', ServiceID: 'SERVICE_ID',
ServerSecret: 'SERVER_SECRET', ServerSecret: 'SERVER_SECRET',
SystemEmail: 'SYSTEM_EMAIL',
FrontURL: 'FRONT_URL', FrontURL: 'FRONT_URL',
AppID: 'APP_ID', AppID: 'APP_ID',
@ -62,7 +58,6 @@ const required: Array<keyof Config> = [
'AccountsURL', 'AccountsURL',
'ServerSecret', 'ServerSecret',
'ServiceID', 'ServiceID',
'SystemEmail',
'FrontURL', 'FrontURL',
'AppID', 'AppID',
'ClientID', 'ClientID',
@ -82,7 +77,6 @@ const config: Config = (() => {
AccountsURL: process.env[envMap.AccountsURL], AccountsURL: process.env[envMap.AccountsURL],
ServerSecret: process.env[envMap.ServerSecret], ServerSecret: process.env[envMap.ServerSecret],
ServiceID: process.env[envMap.ServiceID] ?? 'github-service', ServiceID: process.env[envMap.ServiceID] ?? 'github-service',
SystemEmail: process.env[envMap.SystemEmail] ?? systemAccountEmail,
AllowedWorkspaces: process.env[envMap.AllowedWorkspaces]?.split(',') ?? ['*'], AllowedWorkspaces: process.env[envMap.AllowedWorkspaces]?.split(',') ?? ['*'],
FrontURL: process.env[envMap.FrontURL] ?? '', FrontURL: process.env[envMap.FrontURL] ?? '',

View File

@ -14,6 +14,7 @@ import core, {
MeasureContext, MeasureContext,
RateLimiter, RateLimiter,
Ref, Ref,
systemAccountEmail,
TxOperations TxOperations
} from '@hcengineering/core' } from '@hcengineering/core'
import github, { GithubAuthentication, makeQuery, type GithubIntegration } from '@hcengineering/github' import github, { GithubAuthentication, makeQuery, type GithubIntegration } from '@hcengineering/github'
@ -730,7 +731,7 @@ export class PlatformWorker {
} }
await rateLimiter.add(async () => { await rateLimiter.add(async () => {
const token = generateToken( const token = generateToken(
config.SystemEmail, systemAccountEmail,
{ {
name: workspace name: workspace
}, },

View File

@ -24,7 +24,6 @@ interface Config {
Secret: string Secret: string
Credentials: string Credentials: string
WATCH_TOPIC_NAME: string WATCH_TOPIC_NAME: string
SystemEmail: string
FooterMessage: string FooterMessage: string
InitLimit: number InitLimit: number
} }
@ -39,7 +38,6 @@ const envMap: { [key in keyof Config]: string } = {
ServiceID: 'SERVICE_ID', ServiceID: 'SERVICE_ID',
Secret: 'SECRET', Secret: 'SECRET',
Credentials: 'Credentials', Credentials: 'Credentials',
SystemEmail: 'SYSTEM_EMAIL',
WATCH_TOPIC_NAME: 'WATCH_TOPIC_NAME', WATCH_TOPIC_NAME: 'WATCH_TOPIC_NAME',
FooterMessage: 'FOOTER_MESSAGE', FooterMessage: 'FOOTER_MESSAGE',
InitLimit: 'INIT_LIMIT' InitLimit: 'INIT_LIMIT'
@ -55,7 +53,6 @@ const config: Config = (() => {
AccountsURL: process.env[envMap.AccountsURL], AccountsURL: process.env[envMap.AccountsURL],
ServiceID: process.env[envMap.ServiceID] ?? 'gmail-service', ServiceID: process.env[envMap.ServiceID] ?? 'gmail-service',
Secret: process.env[envMap.Secret], Secret: process.env[envMap.Secret],
SystemEmail: process.env[envMap.SystemEmail] ?? 'anticrm@hc.engineering',
Credentials: process.env[envMap.Credentials], Credentials: process.env[envMap.Credentials],
WATCH_TOPIC_NAME: process.env[envMap.WATCH_TOPIC_NAME], WATCH_TOPIC_NAME: process.env[envMap.WATCH_TOPIC_NAME],
InitLimit: parseNumber(process.env[envMap.InitLimit]) ?? 50, InitLimit: parseNumber(process.env[envMap.InitLimit]) ?? 50,

View File

@ -20,6 +20,7 @@ import core, {
type Doc, type Doc,
MeasureContext, MeasureContext,
type Ref, type Ref,
systemAccountEmail,
type Tx, type Tx,
type TxCreateDoc, type TxCreateDoc,
TxProcessor, TxProcessor,
@ -31,7 +32,6 @@ import type { StorageAdapter } from '@hcengineering/server-core'
import { generateToken } from '@hcengineering/server-token' import { generateToken } from '@hcengineering/server-token'
import { type Db } from 'mongodb' import { type Db } from 'mongodb'
import { getClient } from './client' import { getClient } from './client'
import config from './config'
import { GmailClient } from './gmail' import { GmailClient } from './gmail'
import { type Channel, type ProjectCredentials, type User } from './types' import { type Channel, type ProjectCredentials, type User } from './types'
@ -121,7 +121,7 @@ export class WorkspaceClient {
} }
private async initClient (workspace: string): Promise<Client> { private async initClient (workspace: string): Promise<Client> {
const token = generateToken(config.SystemEmail, { name: workspace }) const token = generateToken(systemAccountEmail, { name: workspace })
console.log('token', token, workspace) console.log('token', token, workspace)
const client = await getClient(token) const client = await getClient(token)
client.notify = (...tx: Tx[]) => { client.notify = (...tx: Tx[]) => {

View File

@ -16,7 +16,6 @@
interface Config { interface Config {
AccountsURL: string AccountsURL: string
Port: number Port: number
SystemEmail: string
ServiceID: string ServiceID: string
LiveKitHost: string LiveKitHost: string
@ -42,7 +41,6 @@ const envMap: { [key in keyof Config]: string } = {
StorageProviderName: 'STORAGE_PROVIDER_NAME', StorageProviderName: 'STORAGE_PROVIDER_NAME',
Secret: 'SECRET', Secret: 'SECRET',
ServiceID: 'SERVICE_ID', ServiceID: 'SERVICE_ID',
SystemEmail: 'SYSTEM_EMAIL',
MongoUrl: 'MONGO_URL' MongoUrl: 'MONGO_URL'
} }
@ -59,7 +57,6 @@ const config: Config = (() => {
StorageProviderName: process.env[envMap.StorageProviderName] ?? 's3', StorageProviderName: process.env[envMap.StorageProviderName] ?? 's3',
Secret: process.env[envMap.Secret], Secret: process.env[envMap.Secret],
ServiceID: process.env[envMap.ServiceID] ?? 'love-service', ServiceID: process.env[envMap.ServiceID] ?? 'love-service',
SystemEmail: process.env[envMap.SystemEmail] ?? 'anticrm@hc.engineering',
MongoUrl: process.env[envMap.MongoUrl] MongoUrl: process.env[envMap.MongoUrl]
} }

View File

@ -12,13 +12,20 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import core, { Client, Ref, TxOperations, type Blob, Data, MeasureContext } from '@hcengineering/core' import attachment, { Attachment } from '@hcengineering/attachment'
import core, {
Client,
Data,
MeasureContext,
Ref,
systemAccountEmail,
TxOperations,
type Blob
} from '@hcengineering/core'
import drive, { createFile } from '@hcengineering/drive' import drive, { createFile } from '@hcengineering/drive'
import love, { MeetingMinutes } from '@hcengineering/love' import love, { MeetingMinutes } from '@hcengineering/love'
import { generateToken } from '@hcengineering/server-token' import { generateToken } from '@hcengineering/server-token'
import attachment, { Attachment } from '@hcengineering/attachment'
import { getClient } from './client' import { getClient } from './client'
import config from './config'
export class WorkspaceClient { export class WorkspaceClient {
private client!: TxOperations private client!: TxOperations
@ -39,7 +46,7 @@ export class WorkspaceClient {
} }
private async initClient (workspace: string): Promise<Client> { private async initClient (workspace: string): Promise<Client> {
const token = generateToken(config.SystemEmail, { name: workspace }) const token = generateToken(systemAccountEmail, { name: workspace })
const client = await getClient(token) const client = await getClient(token)
this.client = new TxOperations(client, core.account.System) this.client = new TxOperations(client, core.account.System)
return this.client return this.client

View File

@ -11,7 +11,6 @@ export interface Config {
Port: number Port: number
Secret: string Secret: string
ServiceID: string ServiceID: string
SystemEmail: string
BrandingPath: string BrandingPath: string
} }
@ -25,7 +24,6 @@ const config: Config = (() => {
Port: parseNumber(process.env.PORT) ?? 4006, Port: parseNumber(process.env.PORT) ?? 4006,
Secret: process.env.SECRET, Secret: process.env.SECRET,
ServiceID: process.env.SERVICE_ID, ServiceID: process.env.SERVICE_ID,
SystemEmail: process.env.SYSTEM_EMAIL ?? 'anticrm@hc.engineering',
BrandingPath: process.env.BRANDING_PATH ?? '' BrandingPath: process.env.BRANDING_PATH ?? ''
} }

View File

@ -6,7 +6,7 @@ import { P12Signer } from '@signpdf/signer-p12'
import signpdf from '@signpdf/signpdf' import signpdf from '@signpdf/signpdf'
import { PDFDocument, StandardFonts, degrees, degreesToRadians, rgb } from 'pdf-lib' import { PDFDocument, StandardFonts, degrees, degreesToRadians, rgb } from 'pdf-lib'
import config from './config' import { systemAccountEmail } from '@hcengineering/core'
interface Rect { interface Rect {
x: number x: number
@ -50,7 +50,7 @@ export async function signPDF (file: Buffer, certp12: Buffer, pwd: string, ctx:
// Make it configurable when will be needed to allow signing for different reasons. // Make it configurable when will be needed to allow signing for different reasons.
const options: Options = { const options: Options = {
name: ctx.title, name: ctx.title,
contactInfo: config.SystemEmail, contactInfo: systemAccountEmail,
appName: ctx.title, appName: ctx.title,
reason: 'Export from the system', reason: 'Export from the system',
location: 'N/A' location: 'N/A'

View File

@ -13,11 +13,10 @@
// limitations under the License. // limitations under the License.
// //
import { type Client } from '@hcengineering/core' import { systemAccountEmail, type Client } from '@hcengineering/core'
import { generateToken, type Token } from '@hcengineering/server-token' import { generateToken, type Token } from '@hcengineering/server-token'
import { createClient, getTransactorEndpoint } from '@hcengineering/server-client' import { createClient, getTransactorEndpoint } from '@hcengineering/server-client'
import config from './config'
export class SignController { export class SignController {
private readonly clients: Map<string, Client> = new Map<string, Client>() private readonly clients: Map<string, Client> = new Map<string, Client>()
@ -50,7 +49,7 @@ export class SignController {
} }
private async createPlatformClient (workspace: string): Promise<Client> { private async createPlatformClient (workspace: string): Promise<Client> {
const token = generateToken(config.SystemEmail, { const token = generateToken(systemAccountEmail, {
name: workspace name: workspace
}) })
const endpoint = await getTransactorEndpoint(token) const endpoint = await getTransactorEndpoint(token)

View File

@ -12,7 +12,6 @@ interface Config {
AccountsURL: string AccountsURL: string
ServiceID: string ServiceID: string
Secret: string Secret: string
SystemEmail: string
} }
const envMap: { [key in keyof Config]: string } = { const envMap: { [key in keyof Config]: string } = {
@ -28,8 +27,7 @@ const envMap: { [key in keyof Config]: string } = {
AccountsURL: 'ACCOUNTS_URL', AccountsURL: 'ACCOUNTS_URL',
ServiceID: 'SERVICE_ID', ServiceID: 'SERVICE_ID',
Secret: 'SECRET', Secret: 'SECRET'
SystemEmail: 'SYSTEM_EMAIL'
} }
const defaults: Partial<Config> = { const defaults: Partial<Config> = {
@ -45,7 +43,6 @@ const defaults: Partial<Config> = {
AccountsURL: undefined, AccountsURL: undefined,
ServiceID: 'telegram-service', ServiceID: 'telegram-service',
SystemEmail: 'anticrm@hc.engineering',
Secret: undefined Secret: undefined
} }
@ -76,7 +73,6 @@ const config = (() => {
MongoURI: process.env[envMap.MongoURI], MongoURI: process.env[envMap.MongoURI],
AccountsURL: process.env[envMap.AccountsURL], AccountsURL: process.env[envMap.AccountsURL],
ServiceID: process.env[envMap.ServiceID], ServiceID: process.env[envMap.ServiceID],
SystemEmail: process.env[envMap.SystemEmail],
Secret: process.env[envMap.Secret] Secret: process.env[envMap.Secret]
} }

View File

@ -15,6 +15,7 @@ import core, {
Hierarchy, Hierarchy,
MeasureContext, MeasureContext,
Ref, Ref,
systemAccountEmail,
Tx, Tx,
TxCreateDoc, TxCreateDoc,
TxCUD, TxCUD,
@ -31,7 +32,6 @@ import telegramP, { NewTelegramMessage } from '@hcengineering/telegram'
import type { Collection } from 'mongodb' import type { Collection } from 'mongodb'
import { Api } from 'telegram' import { Api } from 'telegram'
import { v4 as uuid } from 'uuid' import { v4 as uuid } from 'uuid'
import config from './config'
import { platformToTelegram, telegramToPlatform } from './markup' import { platformToTelegram, telegramToPlatform } from './markup'
import { MsgQueue } from './queue' import { MsgQueue } from './queue'
import type { TelegramConnectionInterface } from './telegram' import type { TelegramConnectionInterface } from './telegram'
@ -151,7 +151,7 @@ export class WorkspaceWorker {
lastMsgStorage: Collection<LastMsgRecord>, lastMsgStorage: Collection<LastMsgRecord>,
channelsStorage: Collection<WorkspaceChannel> channelsStorage: Collection<WorkspaceChannel>
): Promise<WorkspaceWorker> { ): Promise<WorkspaceWorker> {
const token = generateToken(config.SystemEmail, { name: workspace }) const token = generateToken(systemAccountEmail, { name: workspace })
const client = await createPlatformClient(token) const client = await createPlatformClient(token)
const worker = new WorkspaceWorker( const worker = new WorkspaceWorker(